Repository: denoland/deno
Pull request: perf(ext/webidl): optimize dictionary converters

## Summary

Optimizes the `createDictionaryConverter` hot path and related type converters:

- **Pre-compute member context strings** at converter creation time instead of allocating template literals per call per field
- **Eliminate `ObjectAssign({}, defaultValues)`** — only copy individual defaults for fields not present in input
- **Remove `opts = { __proto__: null }` default parameters** from integer, longlong, DOMString, and dictionary converters — each was allocating a new object per call even when no caller passed opts
- **Inline `typeof` checks** instead of calling `type()` and comparing returned strings

### Benchmark: release build vs system Deno 2.7.5

| Benchmark | System Deno | This PR | Speedup |
|---|---|---|---|
| `TextDecoder.decode(buf, {stream})` — 1-field dict | 844 ns | 128 ns | **6.6x** |
| `new Response(null, {status})` — 3-field dict | 207 ns | 50 ns | **4.1x** |
| `new Response(null, {})` — empty dict overhead | 135 ns | 45 ns | **3.0x** |
| `new Headers(pairs)` — sequence converter | 1.8 µs | 503 ns | **3.6x** |
| `new Headers(record)` — record converter | 1.6 µs | 370 ns | **4.3x** |

Closes #18431

## Test plan

- [x] `cargo test -p specs_tests -- fetch` (13 tests passed)
- [x] Manual correctness tests covering TextDecoder, Response, Request, Blob, Headers, performance.mark, structuredClone, URLPattern, and crypto roundtrip

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Changed files:
filename: ext/webidl/00_webidl.js
patch: @@ -195,16 +195,11 @@ function createIntegerConversion(bitLength, typeOpts) {
   const twoToTheBitLength = MathPow(2, bitLength);
   const twoToOneLessThanTheBitLength = MathPow(2, bitLength - 1);
 
-  return (
-    V,
-    prefix = undefined,
-    context = undefined,
-    opts = { __proto__: null },
-  ) => {
+  return (V, prefix, context, opts) => {
     let x = toNumber(V);
     x = censorNegativeZero(x);
 
-    if (opts.enforceRange) {
+    if (opts && opts.enforceRange) {
       if (!NumberIsFinite(x)) {
         throw makeException(
           TypeError,
@@ -228,7 +223,7 @@ function createIntegerConversion(bitLength, typeOpts) {
       return x;
     }
 
-    if (!NumberIsNaN(x) && opts.clamp) {
+    if (!NumberIsNaN(x) && opts && opts.clamp) {
       x = MathMin(MathMax(x, lowerBound), upperBound);
       x = evenRound(x);
       return x;
@@ -259,16 +254,11 @@ function createLongLongConversion(bitLength, { unsigned }) {
   const lowerBound = unsigned ? 0 : NumberMIN_SAFE_INTEGER;
   const asBigIntN = unsigned ? BigIntAsUintN : BigIntAsIntN;
 
-  return (
-    V,
-    prefix = undefined,
-    context = undefined,
-    opts = { __proto__: null },
-  ) => {
+  return (V, prefix, context, opts) => {
     let x = toNumber(V);
     x = censorNegativeZero(x);
 
-    if (opts.enforceRange) {
+    if (opts && opts.enforceRange) {
       if (!NumberIsFinite(x)) {
         throw makeException(
           TypeError,
@@ -292,7 +282,7 @@ function createLongLongConversion(bitLength, { unsigned }) {
       return x;
     }
 
-    if (!NumberIsNaN(x) && opts.clamp) {
+    if (!NumberIsNaN(x) && opts && opts.clamp) {
       x = MathMin(MathMax(x, lowerBound), upperBound);
       x = evenRound(x);
       return x;
@@ -409,15 +399,10 @@ converters["unrestricted double?"] = createNullableConverter(
   converters["unrestricted double"],
 );
 
-converters.DOMString = function (
-  V,
-  prefix,
-  context,
-  opts = { __proto__: null },
-) {
+converters.DOMString = function (V, prefix, context, opts) {
   if (typeof V === "string") {
     return V;
-  } else if (V === null && opts.treatNullAsEmptyString) {
+  } else if (V === null && opts && opts.treatNullAsEmptyString) {
     return "";
   } else if (typeof V === "symbol") {
     throw makeException(
@@ -776,65 +761,61 @@ function createDictionaryConverter(name, ...dictionaries) {
     }
   }
 
-  return function (
-    V,
-    prefix = undefined,
-    context = undefined,
-    opts = { __proto__: null },
-  ) {
-    const typeV = type(V);
-    switch (typeV) {
-      case "Undefined":
-      case "Null":
-      case "Object":
-        break;
-      default:
+  // Pre-compute context strings for each member (avoids allocation in hot path)
+  const memberContexts = [];
+  for (let i = 0; i < allMembers.length; ++i) {
+    memberContexts[i] = `'${allMembers[i].key}' of '${name}'`;
+  }
+
+  return function (V, prefix, context, opts) {
+    if (V === undefined || V === null) {
+      if (hasRequiredKey) {
         throw makeException(
           TypeError,
           "can not be converted to a dictionary",
           prefix,
           context,
         );
+      }
+      const idlDict = { __proto__: null };
+      ObjectAssign(idlDict, defaultValues);
+      return idlDict;
     }
-    const esDict = V;
-
-    const idlDict = ObjectAssign({}, defaultValues);
 
-    // NOTE: fast path Null and Undefined.
-    if ((V === undefined || V === null) && !hasRequiredKey) {
-      return idlDict;
+    if (typeof V !== "object" && typeof V !== "function") {
+      throw makeException(
+        TypeError,
+        "can not be converted to a dictionary",
+        prefix,
+        context,
+      );
     }
 
+    const idlDict = { __proto__: null };
     for (let i = 0; i < allMembers.length; ++i) {
       const member = allMembers[i];
       const key = member.key;
-
-      let esMemberValue;
-      if (typeV === "Undefined" || typeV === "Null") {
-        esMemberValue = undefined;
-      } else {
-        esMemberValue = esDict[key];
-      }
+      const esMemberValue = V[key];
 
       if (esMemberValue !== undefined) {
-        const memberContext = `'${key}' of '${name}'${
-          context ? ` (${context})` : ""
-        }`;
-        const converter = member.converter;
-        const idlMemberValue = converter(
+        const memberContext = context
+          ? memberContexts[i] + ` (${context})`
+          : memberContexts[i];
+        idlDict[key] = member.converter(
           esMemberValue,
           prefix,
           memberContext,
           opts,
         );
-        idlDict[key] = idlMemberValue;
       } else if (member.required) {
         throw makeException(
           TypeError,
           `can not be converted to '${name}' because '${key}' is required in '${name}'`,
           prefix,
           context,
         );
+      } else if (ReflectHas(defaultValues, key)) {
+        idlDict[key] = defaultValues[key];
       }
     }