zod-compiler 1.28.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +59 -53
  2. package/dist/core/codegen/build-path.d.ts +12 -1
  3. package/dist/core/codegen/build-path.d.ts.map +1 -1
  4. package/dist/core/codegen/build-path.js +47 -9
  5. package/dist/core/codegen/build-path.js.map +1 -1
  6. package/dist/core/codegen/context.d.ts +31 -16
  7. package/dist/core/codegen/context.d.ts.map +1 -1
  8. package/dist/core/codegen/context.js +43 -18
  9. package/dist/core/codegen/context.js.map +1 -1
  10. package/dist/core/codegen/emit-issue.d.ts +17 -20
  11. package/dist/core/codegen/emit-issue.d.ts.map +1 -1
  12. package/dist/core/codegen/emit-issue.js +13 -13
  13. package/dist/core/codegen/emit-issue.js.map +1 -1
  14. package/dist/core/codegen/index.d.ts.map +1 -1
  15. package/dist/core/codegen/index.js +9 -7
  16. package/dist/core/codegen/index.js.map +1 -1
  17. package/dist/core/codegen/issue-decls.d.ts +41 -7
  18. package/dist/core/codegen/issue-decls.d.ts.map +1 -1
  19. package/dist/core/codegen/issue-decls.js +49 -9
  20. package/dist/core/codegen/issue-decls.js.map +1 -1
  21. package/dist/core/codegen/schemas/array.js +1 -1
  22. package/dist/core/codegen/schemas/array.js.map +1 -1
  23. package/dist/core/codegen/schemas/bigint.d.ts.map +1 -1
  24. package/dist/core/codegen/schemas/bigint.js +15 -4
  25. package/dist/core/codegen/schemas/bigint.js.map +1 -1
  26. package/dist/core/codegen/schemas/catch.d.ts.map +1 -1
  27. package/dist/core/codegen/schemas/catch.js +3 -2
  28. package/dist/core/codegen/schemas/catch.js.map +1 -1
  29. package/dist/core/codegen/schemas/discriminated-union.d.ts.map +1 -1
  30. package/dist/core/codegen/schemas/discriminated-union.js +2 -1
  31. package/dist/core/codegen/schemas/discriminated-union.js.map +1 -1
  32. package/dist/core/codegen/schemas/effect.d.ts.map +1 -1
  33. package/dist/core/codegen/schemas/effect.js +4 -2
  34. package/dist/core/codegen/schemas/effect.js.map +1 -1
  35. package/dist/core/codegen/schemas/file.js +2 -8
  36. package/dist/core/codegen/schemas/file.js.map +1 -1
  37. package/dist/core/codegen/schemas/number.js +4 -3
  38. package/dist/core/codegen/schemas/number.js.map +1 -1
  39. package/dist/core/codegen/schemas/object.d.ts +11 -1
  40. package/dist/core/codegen/schemas/object.d.ts.map +1 -1
  41. package/dist/core/codegen/schemas/object.js +58 -14
  42. package/dist/core/codegen/schemas/object.js.map +1 -1
  43. package/dist/core/codegen/schemas/readonly.d.ts.map +1 -1
  44. package/dist/core/codegen/schemas/readonly.js.map +1 -1
  45. package/dist/core/codegen/schemas/record.d.ts.map +1 -1
  46. package/dist/core/codegen/schemas/record.js +8 -3
  47. package/dist/core/codegen/schemas/record.js.map +1 -1
  48. package/dist/core/codegen/schemas/sizeable.d.ts +49 -3
  49. package/dist/core/codegen/schemas/sizeable.d.ts.map +1 -1
  50. package/dist/core/codegen/schemas/sizeable.js +101 -8
  51. package/dist/core/codegen/schemas/sizeable.js.map +1 -1
  52. package/dist/core/codegen/schemas/string.d.ts.map +1 -1
  53. package/dist/core/codegen/schemas/string.js +45 -16
  54. package/dist/core/codegen/schemas/string.js.map +1 -1
  55. package/dist/core/codegen/schemas/tuple.d.ts +14 -11
  56. package/dist/core/codegen/schemas/tuple.d.ts.map +1 -1
  57. package/dist/core/codegen/schemas/tuple.js +99 -49
  58. package/dist/core/codegen/schemas/tuple.js.map +1 -1
  59. package/dist/core/codegen/well-known-regex.d.ts +7 -0
  60. package/dist/core/codegen/well-known-regex.d.ts.map +1 -1
  61. package/dist/core/codegen/well-known-regex.js +10 -3
  62. package/dist/core/codegen/well-known-regex.js.map +1 -1
  63. package/dist/core/extract/checks.d.ts.map +1 -1
  64. package/dist/core/extract/checks.js +1 -0
  65. package/dist/core/extract/checks.js.map +1 -1
  66. package/dist/core/extract/extractors/intersection.js +21 -6
  67. package/dist/core/extract/extractors/intersection.js.map +1 -1
  68. package/dist/core/extract/extractors/object.d.ts.map +1 -1
  69. package/dist/core/extract/extractors/object.js +25 -9
  70. package/dist/core/extract/extractors/object.js.map +1 -1
  71. package/dist/core/extract/extractors/optional.d.ts.map +1 -1
  72. package/dist/core/extract/extractors/optional.js +1 -28
  73. package/dist/core/extract/extractors/optional.js.map +1 -1
  74. package/dist/core/extract/extractors/record.js +9 -3
  75. package/dist/core/extract/extractors/record.js.map +1 -1
  76. package/dist/core/extract/extractors/tuple.d.ts.map +1 -1
  77. package/dist/core/extract/extractors/tuple.js +16 -20
  78. package/dist/core/extract/extractors/tuple.js.map +1 -1
  79. package/dist/core/extract/types.d.ts +16 -8
  80. package/dist/core/extract/types.d.ts.map +1 -1
  81. package/dist/core/iife.d.ts +2 -1
  82. package/dist/core/iife.d.ts.map +1 -1
  83. package/dist/core/iife.js +2 -1
  84. package/dist/core/iife.js.map +1 -1
  85. package/dist/core/types.d.ts +70 -21
  86. package/dist/core/types.d.ts.map +1 -1
  87. package/dist/jit.d.ts.map +1 -1
  88. package/dist/jit.js +47 -12
  89. package/dist/jit.js.map +1 -1
  90. package/dist/runtime.d.ts +3 -1
  91. package/dist/runtime.js +12 -10
  92. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"catch.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/catch.ts"],"mappings":";;;iBAGgB,UAAU,IAAI;EAAa;GAAiB,GAAG;iBAiC/C,UAAU,IAAI,SAAS,GAAG"}
1
+ {"version":3,"file":"catch.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/catch.ts"],"mappings":";;;iBAGgB,UAAU,IAAI;EAAa;GAAiB,GAAG;iBAyC/C,UAAU,IAAI,SAAS,GAAG"}
@@ -3,8 +3,9 @@ function slowCatch(ir, g) {
3
3
  const tempIssues = g.temp("ci");
4
4
  const idxVar = g.temp("ck");
5
5
  const cvVar = g.temp("cv");
6
+ const inVar = g.temp("cin");
6
7
  return [
7
- `var ${tempIssues}=[];`,
8
+ `var ${tempIssues}=[];var ${inVar}=${g.input};`,
8
9
  g.visit(ir.inner, { issues: tempIssues }),
9
10
  `if(${tempIssues}.length>0){`,
10
11
  `for(var ${idxVar}=0;${idxVar}<${tempIssues}.length;${idxVar}++){`,
@@ -12,7 +13,7 @@ function slowCatch(ir, g) {
12
13
  `delete ${tempIssues}[${idxVar}].input;`,
13
14
  `}`,
14
15
  `var ${cvVar}=__rf[${ir.refIndex}]._zod.def.catchValue;`,
15
- `${g.output}=typeof ${cvVar}==="function"?${cvVar}({value:${g.input},issues:${tempIssues},error:{issues:${tempIssues}},input:${g.input}}):${cvVar};`,
16
+ `${g.output}=typeof ${cvVar}==="function"?${cvVar}({value:${inVar},issues:${tempIssues},error:{issues:${tempIssues}},input:${inVar}}):${cvVar};`,
16
17
  `}`,
17
18
  ""
18
19
  ].join("\n");
@@ -1 +1 @@
1
- {"version":3,"file":"catch.js","names":[],"sources":["../../../../src/core/codegen/schemas/catch.ts"],"sourcesContent":["import type { CatchIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\n\nexport function slowCatch(ir: SchemaIR & { type: \"catch\" }, g: SlowGen): string {\n const tempIssues = g.temp(\"ci\");\n const idxVar = g.temp(\"ck\");\n const cvVar = g.temp(\"cv\");\n // Mirrors $ZodCatch: on inner failure, catchValue receives a ctx of\n // { value, issues, error: { issues }, input } and its result replaces the\n // value; the issues are swallowed. Finalize messages first so ctx readers\n // see zod-shaped issues (one finalized array serves both fields).\n //\n // `input` is `delete`d, not assigned `undefined`, so `ctx.error.issues[n]` has\n // exactly zod's key set — zod's `finalizeIssue` deletes it, and the key's\n // presence shows through `Object.keys`/spread/strict deep-equal even when the\n // value reads as `undefined` either way. (`ctx.issues` is a documented\n // divergence: zod hands the callback the RAW payload array there, carrying\n // `inst` — the schema instance — which compiled code has no counterpart for.\n // See tests/known-divergences.test.ts.) Only reached on an inner failure, and\n // that failure is about to be swallowed by the catch value, so nothing on a\n // successful parse pays for the delete.\n return [\n `var ${tempIssues}=[];`,\n g.visit(ir.inner, { issues: tempIssues }),\n `if(${tempIssues}.length>0){`,\n `for(var ${idxVar}=0;${idxVar}<${tempIssues}.length;${idxVar}++){`,\n `if(${tempIssues}[${idxVar}].message===undefined&&typeof __zcMsg===\"function\"){${tempIssues}[${idxVar}].message=__zcMsg(${tempIssues}[${idxVar}]);}`,\n `delete ${tempIssues}[${idxVar}].input;`,\n `}`,\n `var ${cvVar}=__rf[${ir.refIndex}]._zod.def.catchValue;`,\n `${g.output}=typeof ${cvVar}===\"function\"?${cvVar}({value:${g.input},issues:${tempIssues},error:{issues:${tempIssues}},input:${g.input}}):${cvVar};`,\n `}`,\n \"\",\n ].join(\"\\n\");\n}\n\nexport function fastCatch(ir: CatchIR, g: FastGen): string | null {\n return g.visit(ir.inner);\n}\n"],"mappings":";AAGA,SAAgB,UAAU,IAAkC,GAAoB;CAC9E,MAAM,aAAa,EAAE,KAAK,IAAI;CAC9B,MAAM,SAAS,EAAE,KAAK,IAAI;CAC1B,MAAM,QAAQ,EAAE,KAAK,IAAI;CAezB,OAAO;EACL,OAAO,WAAW;EAClB,EAAE,MAAM,GAAG,OAAO,EAAE,QAAQ,WAAW,CAAC;EACxC,MAAM,WAAW;EACjB,WAAW,OAAO,KAAK,OAAO,GAAG,WAAW,UAAU,OAAO;EAC7D,MAAM,WAAW,GAAG,OAAO,sDAAsD,WAAW,GAAG,OAAO,oBAAoB,WAAW,GAAG,OAAO;EAC/I,UAAU,WAAW,GAAG,OAAO;EAC/B;EACA,OAAO,MAAM,QAAQ,GAAG,SAAS;EACjC,GAAG,EAAE,OAAO,UAAU,MAAM,gBAAgB,MAAM,UAAU,EAAE,MAAM,UAAU,WAAW,iBAAiB,WAAW,UAAU,EAAE,MAAM,KAAK,MAAM;EAClJ;EACA;CACF,CAAC,CAAC,KAAK,IAAI;AACb;AAEA,SAAgB,UAAU,IAAa,GAA2B;CAChE,OAAO,EAAE,MAAM,GAAG,KAAK;AACzB"}
1
+ {"version":3,"file":"catch.js","names":[],"sources":["../../../../src/core/codegen/schemas/catch.ts"],"sourcesContent":["import type { CatchIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\n\nexport function slowCatch(ir: SchemaIR & { type: \"catch\" }, g: SlowGen): string {\n const tempIssues = g.temp(\"ci\");\n const idxVar = g.temp(\"ck\");\n const cvVar = g.temp(\"cv\");\n const inVar = g.temp(\"cin\");\n // Mirrors $ZodCatch: on inner failure, catchValue receives a ctx of\n // { value, issues, error: { issues }, input } and its result replaces the\n // value; the issues are swallowed. Finalize messages first so ctx readers\n // see zod-shaped issues (one finalized array serves both fields).\n //\n // `value` and `input` are both the value the catch was HANDED, snapshotted\n // before the inner runs: zod runs the inner on a payload of its own and\n // spreads that, but overwrites `value` with the original input. The compiled\n // walk writes the inner's output through the same slot it reads from, so\n // without the snapshot a rebuilding inner (a strip object, a readonly) would\n // hand the callback its half-built copy instead.\n //\n // `input` is `delete`d, not assigned `undefined`, so `ctx.error.issues[n]` has\n // exactly zod's key set — zod's `finalizeIssue` deletes it, and the key's\n // presence shows through `Object.keys`/spread/strict deep-equal even when the\n // value reads as `undefined` either way. (`ctx.issues` is a documented\n // divergence: zod hands the callback the RAW payload array there, carrying\n // `inst` — the schema instance — which compiled code has no counterpart for.\n // See tests/known-divergences.test.ts.) Only reached on an inner failure, and\n // that failure is about to be swallowed by the catch value, so nothing on a\n // successful parse pays for the delete.\n return [\n `var ${tempIssues}=[];var ${inVar}=${g.input};`,\n g.visit(ir.inner, { issues: tempIssues }),\n `if(${tempIssues}.length>0){`,\n `for(var ${idxVar}=0;${idxVar}<${tempIssues}.length;${idxVar}++){`,\n `if(${tempIssues}[${idxVar}].message===undefined&&typeof __zcMsg===\"function\"){${tempIssues}[${idxVar}].message=__zcMsg(${tempIssues}[${idxVar}]);}`,\n `delete ${tempIssues}[${idxVar}].input;`,\n `}`,\n `var ${cvVar}=__rf[${ir.refIndex}]._zod.def.catchValue;`,\n `${g.output}=typeof ${cvVar}===\"function\"?${cvVar}({value:${inVar},issues:${tempIssues},error:{issues:${tempIssues}},input:${inVar}}):${cvVar};`,\n `}`,\n \"\",\n ].join(\"\\n\");\n}\n\nexport function fastCatch(ir: CatchIR, g: FastGen): string | null {\n return g.visit(ir.inner);\n}\n"],"mappings":";AAGA,SAAgB,UAAU,IAAkC,GAAoB;CAC9E,MAAM,aAAa,EAAE,KAAK,IAAI;CAC9B,MAAM,SAAS,EAAE,KAAK,IAAI;CAC1B,MAAM,QAAQ,EAAE,KAAK,IAAI;CACzB,MAAM,QAAQ,EAAE,KAAK,KAAK;CAsB1B,OAAO;EACL,OAAO,WAAW,UAAU,MAAM,GAAG,EAAE,MAAM;EAC7C,EAAE,MAAM,GAAG,OAAO,EAAE,QAAQ,WAAW,CAAC;EACxC,MAAM,WAAW;EACjB,WAAW,OAAO,KAAK,OAAO,GAAG,WAAW,UAAU,OAAO;EAC7D,MAAM,WAAW,GAAG,OAAO,sDAAsD,WAAW,GAAG,OAAO,oBAAoB,WAAW,GAAG,OAAO;EAC/I,UAAU,WAAW,GAAG,OAAO;EAC/B;EACA,OAAO,MAAM,QAAQ,GAAG,SAAS;EACjC,GAAG,EAAE,OAAO,UAAU,MAAM,gBAAgB,MAAM,UAAU,MAAM,UAAU,WAAW,iBAAiB,WAAW,UAAU,MAAM,KAAK,MAAM;EAC9I;EACA;CACF,CAAC,CAAC,KAAK,IAAI;AACb;AAEA,SAAgB,UAAU,IAAa,GAA2B;CAChE,OAAO,EAAE,MAAM,GAAG,KAAK;AACzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"discriminated-union.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"mappings":";;;;KAcK,oBAAoB;iBAET,uBACd,IAAI;EAAa;GACjB,GAAG;;;;;;;;;;;;;;;;;;iBA8DW,4BACd,GAAG,SACH,uBACA,gBAAgB,qBAChB,kBAAkB;iBAsFJ,uBAAuB,IAAI,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;iBAqDpD,yBACd,kBAAkB;EACf;EAAuB,OAAO"}
1
+ {"version":3,"file":"discriminated-union.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"mappings":";;;;KAcK,oBAAoB;iBAET,uBACd,IAAI;EAAa;GACjB,GAAG;;;;;;;;;;;;;;;;;;iBAoEW,4BACd,GAAG,SACH,uBACA,gBAAgB,qBAChB,kBAAkB;iBAsFJ,uBAAuB,IAAI,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;iBAqDpD,yBACd,kBAAkB;EACf;EAAuB,OAAO"}
@@ -21,9 +21,10 @@ function slowDiscriminatedUnion(ir, g) {
21
21
  break;`;
22
22
  }
23
23
  const msgProp = g.typeMsg === void 0 ? "" : `,message:${JSON.stringify(g.typeMsg)}`;
24
+ const optionsList = ir.cases.map(({ value }) => literalToJs(value)).join(",");
24
25
  code += emit`
25
26
  default:
26
- ${g.issues}.push({code:"invalid_union",errors:[],note:"No matching discriminator",discriminator:${discKey},input:${g.input},path:${extendPath(g.path, discKey)}${msgProp}});
27
+ ${g.issues}.push({code:"invalid_union",errors:[],note:"No matching discriminator",discriminator:${discKey},options:[${optionsList}],input:${g.input},path:${extendPath(g.path, discKey)}${msgProp}});
27
28
  }`;
28
29
  if (ir.options.some(hasMutation)) code += `${g.output}=${objVar};`;
29
30
  code += `}`;
@@ -1 +1 @@
1
- {"version":3,"file":"discriminated-union.js","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"sourcesContent":["import type { DiscriminatedUnionIR, LiteralValue, ObjectIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n declareFastTemps,\n emitConstant,\n escapeString,\n extendPath,\n hasMutation,\n literalToJs,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType } from \"../emit-issue.js\";\n\n/** One `discriminator value → option index` dispatch entry. */\ntype DiscriminatorCase = DiscriminatedUnionIR[\"cases\"][number];\n\nexport function slowDiscriminatedUnion(\n ir: SchemaIR & { type: \"discriminatedUnion\" },\n g: SlowGen,\n): string {\n const discKey = escapeString(ir.discriminator);\n\n let code = emit`\n if(typeof ${g.input}!==\"object\"||${g.input}===null||Array.isArray(${g.input})){\n ${invalidType(g, \"object\", { codeFirst: true })}\n }else{`;\n\n const objVar = g.temp(\"du\");\n code += `var ${objVar}=${g.input};switch(${objVar}[${discKey}]){`;\n\n for (const { value, option: index } of ir.cases) {\n const option = ir.options[index] as SchemaIR;\n code += emit`\n case ${literalToJs(value)}:\n ${g.visit(option, { input: objVar, output: objVar })}\n break;`;\n }\n\n const msgProp = g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`;\n // Field for field what $ZodDiscriminatedUnion pushes when no option matches:\n // `{ code, errors: [], note: \"No matching discriminator\", discriminator,\n // input, path: [def.discriminator] }`. There is deliberately no `options`\n // key — zod never enumerates the valid discriminator values here, so adding\n // them invents a field consumers would find on no real zod issue.\n code += emit`\n default:\n ${g.issues}.push({code:\"invalid_union\",errors:[],note:\"No matching discriminator\",discriminator:${discKey},input:${g.input},path:${extendPath(g.path, discKey)}${msgProp}});\n }`;\n // Propagate option-applied mutations (defaults, coercions, transforms,\n // overwrite checks, stringbool) back to the output location. Each option is\n // visited with output:objVar — a fresh local — so a mutating option's clone is\n // reassigned into objVar and stranded there; without this write-back the caller\n // returns the ORIGINAL input by reference and the mutation is silently lost.\n // Gated on mutation so a pure-validation union stays a zero-write pass-through\n // (objVar still aliases the input). On the no-match/failure paths objVar equals\n // the input, so the write is a harmless self-assignment.\n if (ir.options.some(hasMutation)) {\n code += `${g.output}=${objVar};`;\n }\n code += `}`;\n return `${code}\\n`;\n}\n\n/**\n * Emit an O(1) switch-dispatch fast-check for a discriminated union — real\n * (`z.discriminatedUnion`) or one detected inside a plain `z.union`\n * (see {@link detectUnionDiscriminator}). Both share this so the detected case\n * inherits the size-gating and the per-case guard strip.\n *\n * `discSkipKey` tells each object option to drop its own type-guard and\n * discriminator re-check: the caller's guard (`typeof x===\"object\"&&…` below)\n * already proved object-ness, and the matched switch case has fixed the\n * discriminator value, so re-emitting either is pure redundancy the optimizer\n * only removes when it inlines this helper — which a union large enough to\n * matter won't. Routed through the normal `visit` so size-gated extraction\n * still bounds the switch (the strip survives into any hoisted helper);\n * non-object options ignore the hint and keep their own guard.\n *\n * Returns null if any option is fast-path-ineligible.\n */\nexport function emitFastDiscriminatedSwitch(\n g: FastGen,\n discriminator: string,\n cases: readonly DiscriminatorCase[],\n options: readonly SchemaIR[],\n): string | null {\n const x = g.input;\n const discKey = escapeString(discriminator);\n const helperName = g.temp(\"du\");\n const helperParam = g.temp(\"dx\");\n\n // The switch body is its own function: size-gate the options against the cap\n // in a fresh scope, otherwise many small options accumulate into the caller's\n // scope while this helper itself grows unbounded past the TurboFan budget.\n const body = g.scoped(helperParam);\n const table = stringDispatchTable(cases);\n\n if (table === null) {\n const caseStrs: string[] = [];\n for (const { value, option: index } of cases) {\n const check = body.visit(options[index] as SchemaIR, { discSkipKey: discriminator });\n if (check === null) return null;\n caseStrs.push(`case ${literalToJs(value)}:return ${check};`);\n }\n g.ctx.preamble.push(\n `function ${helperName}(${helperParam}){${declareFastTemps(body.scope)}switch(${helperParam}[${discKey}]){${caseStrs.join(\"\")}default:return false;}}`,\n );\n } else {\n // Dispatch through a string→ordinal table, then switch on the ordinal. V8\n // compiles a switch over STRING labels as sequential `===` comparisons, so\n // the plain form costs ~0.5 ns per preceding case: measured 4.1 ns (2\n // variants), 11.4 (8), 52.1 (80). A dense integer switch becomes a jump\n // table, leaving the lookup flat — 5.9 ns (8) and 15.1 (80), i.e. 1.9x to\n // 3.5x, for a table that adds ~1.3% to a union's generated bytes.\n //\n // `typeof t===\"string\"` is load-bearing: property access would coerce a\n // non-string discriminator (an object's toString, a number) into a key that\n // could hit a case whose own discriminator check the switch has stripped.\n // Strict equality never matched those, so the guard keeps the verdict.\n const caseStrs: string[] = [];\n for (const [optionIndex, ordinal] of table.ordinals) {\n const check = body.visit(options[optionIndex] as SchemaIR, { discSkipKey: discriminator });\n if (check === null) return null;\n caseStrs.push(`case ${ordinal}:return ${check};`);\n }\n const tableVar = emitConstant(g.ctx, \"dt\", table.initializer);\n const t = g.temp(\"dv\");\n g.ctx.preamble.push(\n `function ${helperName}(${helperParam}){${declareFastTemps(body.scope)}var ${t}=${helperParam}[${discKey}];` +\n `switch(typeof ${t}===\"string\"?${tableVar}[${t}]:0){${caseStrs.join(\"\")}default:return false;}}`,\n );\n }\n\n return `typeof ${x}===\"object\"&&${x}!==null&&!Array.isArray(${x})&&${helperName}(${x})`;\n}\n\n/**\n * Minimum case count for ordinal dispatch. Measured crossover is 3: at 2 cases\n * the string switch is one comparison and beats the extra table lookup\n * (4.1 ns vs 4.5), at 3 the table already wins (6.1 vs 4.6).\n */\nconst MIN_TABLE_DISPATCH = 3;\n\n/**\n * Build the `{value: ordinal}` dispatch table for a set of cases, or null when\n * the plain string switch should be kept. Requires every discriminator value to\n * be a string — mixed types would collide once coerced to property keys (`5`\n * and `\"5\"`) — and excludes `__proto__`, which an object literal cannot hold as\n * an own key. Values that select the SAME option share one ordinal, so a\n * multi-value literal emits its check once instead of per value.\n */\nfunction stringDispatchTable(\n cases: readonly DiscriminatorCase[],\n): { initializer: string; ordinals: Map<number, number> } | null {\n if (cases.length < MIN_TABLE_DISPATCH) return null;\n if (!cases.every((c) => typeof c.value === \"string\" && c.value !== \"__proto__\")) return null;\n\n const ordinals = new Map<number, number>();\n const entries: string[] = [];\n for (const { value, option } of cases) {\n let ordinal = ordinals.get(option);\n if (ordinal === undefined) {\n ordinal = ordinals.size + 1;\n ordinals.set(option, ordinal);\n }\n entries.push(`${escapeString(value as string)}:${ordinal}`);\n }\n return { initializer: `{${entries.join(\",\")}}`, ordinals };\n}\n\nexport function fastDiscriminatedUnion(ir: DiscriminatedUnionIR, g: FastGen): string | null {\n return emitFastDiscriminatedSwitch(g, ir.discriminator, ir.cases, ir.options);\n}\n\n/**\n * Minimum option count for rewriting a plain `z.union` to switch dispatch. Below\n * this the switch helper's fixed call overhead loses to a fully-inlined\n * `||`-chain that V8 keeps flat: measured crossover is ~4 options (n=3 can hit\n * 0.6x — a regression — while n=5 is 1.27x and n=32 is 2.34x), so 5 captures the\n * stable wins with margin and never regresses a small union. Real\n * `z.discriminatedUnion` is unaffected — it dispatches via switch by construction\n * regardless of size.\n */\nconst MIN_AUTO_DISCRIMINATE_OPTIONS = 5;\n\n/**\n * Values that switch correctly under `===` (excludes `undefined` and `NaN`).\n *\n * Also excludes a SYMBOL — now that `LiteralIR.values` admits one, this is the\n * guard that keeps it out. A symbol discriminant has no source form, so there\n * is no `case` label to emit for it (`literalToJs` refuses it by type), and the\n * ordinal-table variant is string-keyed besides. Detection bails to the\n * `||`-chain, where the option's own literal check does the right thing by\n * reading the value list off the retained schema.\n */\nfunction isSwitchableDiscriminant(v: LiteralValue): v is string | number | boolean | bigint | null {\n return (\n v === null ||\n typeof v === \"string\" ||\n typeof v === \"boolean\" ||\n typeof v === \"bigint\" ||\n (typeof v === \"number\" && !Number.isNaN(v))\n );\n}\n\n/**\n * Detect whether a plain (untagged) `z.union` is *structurally* a discriminated\n * union, so its fast path can use O(1) switch dispatch instead of probing every\n * arm. Returns the discriminator + dispatch table, or null to keep the\n * `||`-chain.\n *\n * Requires (proving the switch accepts exactly what the `||`-chain would): every\n * option is a plain object that pins one shared key to a REQUIRED literal\n * (`prop.type === \"literal\"` — an optional/non-literal key is rejected), and the\n * literal values are pairwise DISJOINT across options. Disjointness is the crux:\n * it guarantees at most one option can accept any given input, so dispatching to\n * that single option is equivalent to trying them all. Any value shared by two\n * options (ambiguous), a non-switchable value (`undefined`/`NaN`), or a\n * non-object option makes detection bail to the safe `||`-chain.\n *\n * Fast-path only: the slow path keeps `z.union`'s sequential trial and its\n * `invalid_union` error shape, so failure output stays byte-identical to Zod.\n */\nexport function detectUnionDiscriminator(\n options: readonly SchemaIR[],\n): { discriminator: string; cases: DiscriminatorCase[] } | null {\n if (options.length < MIN_AUTO_DISCRIMINATE_OPTIONS) return null;\n const objects: ObjectIR[] = [];\n for (const option of options) {\n if (option.type !== \"object\") return null;\n objects.push(option);\n }\n const first = objects[0];\n if (first === undefined) return null; // unreachable (length checked above)\n\n // Only keys present in the first option can be shared by all; try each.\n candidate: for (const key of Object.keys(first.properties)) {\n const seen = new Set<string | number | boolean | bigint | null>();\n const cases: DiscriminatorCase[] = [];\n for (const [i, object] of objects.entries()) {\n const prop = object.properties[key];\n if (prop === undefined || prop.type !== \"literal\") continue candidate;\n for (const value of prop.values) {\n if (!isSwitchableDiscriminant(value)) continue candidate;\n if (seen.has(value)) continue candidate; // shared value → ambiguous dispatch\n seen.add(value);\n cases.push({ value, option: i });\n }\n }\n return { discriminator: key, cases };\n }\n return null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"discriminated-union.js","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"sourcesContent":["import type { DiscriminatedUnionIR, LiteralValue, ObjectIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n declareFastTemps,\n emitConstant,\n escapeString,\n extendPath,\n hasMutation,\n literalToJs,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType } from \"../emit-issue.js\";\n\n/** One `discriminator value → option index` dispatch entry. */\ntype DiscriminatorCase = DiscriminatedUnionIR[\"cases\"][number];\n\nexport function slowDiscriminatedUnion(\n ir: SchemaIR & { type: \"discriminatedUnion\" },\n g: SlowGen,\n): string {\n const discKey = escapeString(ir.discriminator);\n\n let code = emit`\n if(typeof ${g.input}!==\"object\"||${g.input}===null||Array.isArray(${g.input})){\n ${invalidType(g, \"object\", { codeFirst: true })}\n }else{`;\n\n const objVar = g.temp(\"du\");\n code += `var ${objVar}=${g.input};switch(${objVar}[${discKey}]){`;\n\n for (const { value, option: index } of ir.cases) {\n const option = ir.options[index] as SchemaIR;\n code += emit`\n case ${literalToJs(value)}:\n ${g.visit(option, { input: objVar, output: objVar })}\n break;`;\n }\n\n const msgProp = g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`;\n // Field for field what $ZodDiscriminatedUnion pushes when no option matches:\n // `{ code, errors: [], note: \"No matching discriminator\", discriminator,\n // options, input, path: [def.discriminator] }`. `options` is\n // `Array.from(disc.value.keys())` — every dispatch value in map insertion\n // order, which is exactly `ir.cases` (option order, then each option's\n // `propValues[discriminator]` in ITS order, so an omittable discriminator\n // lists `undefined` right after its own values). The locale reads the list\n // for \"Invalid discriminator value. Expected 'a' | 'b'\". A fresh array\n // literal per push, as zod allocates one, so consumers never share or\n // mutate a hoisted table.\n const optionsList = ir.cases.map(({ value }) => literalToJs(value)).join(\",\");\n code += emit`\n default:\n ${g.issues}.push({code:\"invalid_union\",errors:[],note:\"No matching discriminator\",discriminator:${discKey},options:[${optionsList}],input:${g.input},path:${extendPath(g.path, discKey)}${msgProp}});\n }`;\n // Propagate option-applied mutations (defaults, coercions, transforms,\n // overwrite checks, stringbool) back to the output location. Each option is\n // visited with output:objVar — a fresh local — so a mutating option's clone is\n // reassigned into objVar and stranded there; without this write-back the caller\n // returns the ORIGINAL input by reference and the mutation is silently lost.\n // Gated on mutation so a pure-validation union stays a zero-write pass-through\n // (objVar still aliases the input). On the no-match/failure paths objVar equals\n // the input, so the write is a harmless self-assignment.\n if (ir.options.some(hasMutation)) {\n code += `${g.output}=${objVar};`;\n }\n code += `}`;\n return `${code}\\n`;\n}\n\n/**\n * Emit an O(1) switch-dispatch fast-check for a discriminated union — real\n * (`z.discriminatedUnion`) or one detected inside a plain `z.union`\n * (see {@link detectUnionDiscriminator}). Both share this so the detected case\n * inherits the size-gating and the per-case guard strip.\n *\n * `discSkipKey` tells each object option to drop its own type-guard and\n * discriminator re-check: the caller's guard (`typeof x===\"object\"&&…` below)\n * already proved object-ness, and the matched switch case has fixed the\n * discriminator value, so re-emitting either is pure redundancy the optimizer\n * only removes when it inlines this helper — which a union large enough to\n * matter won't. Routed through the normal `visit` so size-gated extraction\n * still bounds the switch (the strip survives into any hoisted helper);\n * non-object options ignore the hint and keep their own guard.\n *\n * Returns null if any option is fast-path-ineligible.\n */\nexport function emitFastDiscriminatedSwitch(\n g: FastGen,\n discriminator: string,\n cases: readonly DiscriminatorCase[],\n options: readonly SchemaIR[],\n): string | null {\n const x = g.input;\n const discKey = escapeString(discriminator);\n const helperName = g.temp(\"du\");\n const helperParam = g.temp(\"dx\");\n\n // The switch body is its own function: size-gate the options against the cap\n // in a fresh scope, otherwise many small options accumulate into the caller's\n // scope while this helper itself grows unbounded past the TurboFan budget.\n const body = g.scoped(helperParam);\n const table = stringDispatchTable(cases);\n\n if (table === null) {\n const caseStrs: string[] = [];\n for (const { value, option: index } of cases) {\n const check = body.visit(options[index] as SchemaIR, { discSkipKey: discriminator });\n if (check === null) return null;\n caseStrs.push(`case ${literalToJs(value)}:return ${check};`);\n }\n g.ctx.preamble.push(\n `function ${helperName}(${helperParam}){${declareFastTemps(body.scope)}switch(${helperParam}[${discKey}]){${caseStrs.join(\"\")}default:return false;}}`,\n );\n } else {\n // Dispatch through a string→ordinal table, then switch on the ordinal. V8\n // compiles a switch over STRING labels as sequential `===` comparisons, so\n // the plain form costs ~0.5 ns per preceding case: measured 4.1 ns (2\n // variants), 11.4 (8), 52.1 (80). A dense integer switch becomes a jump\n // table, leaving the lookup flat — 5.9 ns (8) and 15.1 (80), i.e. 1.9x to\n // 3.5x, for a table that adds ~1.3% to a union's generated bytes.\n //\n // `typeof t===\"string\"` is load-bearing: property access would coerce a\n // non-string discriminator (an object's toString, a number) into a key that\n // could hit a case whose own discriminator check the switch has stripped.\n // Strict equality never matched those, so the guard keeps the verdict.\n const caseStrs: string[] = [];\n for (const [optionIndex, ordinal] of table.ordinals) {\n const check = body.visit(options[optionIndex] as SchemaIR, { discSkipKey: discriminator });\n if (check === null) return null;\n caseStrs.push(`case ${ordinal}:return ${check};`);\n }\n const tableVar = emitConstant(g.ctx, \"dt\", table.initializer);\n const t = g.temp(\"dv\");\n g.ctx.preamble.push(\n `function ${helperName}(${helperParam}){${declareFastTemps(body.scope)}var ${t}=${helperParam}[${discKey}];` +\n `switch(typeof ${t}===\"string\"?${tableVar}[${t}]:0){${caseStrs.join(\"\")}default:return false;}}`,\n );\n }\n\n return `typeof ${x}===\"object\"&&${x}!==null&&!Array.isArray(${x})&&${helperName}(${x})`;\n}\n\n/**\n * Minimum case count for ordinal dispatch. Measured crossover is 3: at 2 cases\n * the string switch is one comparison and beats the extra table lookup\n * (4.1 ns vs 4.5), at 3 the table already wins (6.1 vs 4.6).\n */\nconst MIN_TABLE_DISPATCH = 3;\n\n/**\n * Build the `{value: ordinal}` dispatch table for a set of cases, or null when\n * the plain string switch should be kept. Requires every discriminator value to\n * be a string — mixed types would collide once coerced to property keys (`5`\n * and `\"5\"`) — and excludes `__proto__`, which an object literal cannot hold as\n * an own key. Values that select the SAME option share one ordinal, so a\n * multi-value literal emits its check once instead of per value.\n */\nfunction stringDispatchTable(\n cases: readonly DiscriminatorCase[],\n): { initializer: string; ordinals: Map<number, number> } | null {\n if (cases.length < MIN_TABLE_DISPATCH) return null;\n if (!cases.every((c) => typeof c.value === \"string\" && c.value !== \"__proto__\")) return null;\n\n const ordinals = new Map<number, number>();\n const entries: string[] = [];\n for (const { value, option } of cases) {\n let ordinal = ordinals.get(option);\n if (ordinal === undefined) {\n ordinal = ordinals.size + 1;\n ordinals.set(option, ordinal);\n }\n entries.push(`${escapeString(value as string)}:${ordinal}`);\n }\n return { initializer: `{${entries.join(\",\")}}`, ordinals };\n}\n\nexport function fastDiscriminatedUnion(ir: DiscriminatedUnionIR, g: FastGen): string | null {\n return emitFastDiscriminatedSwitch(g, ir.discriminator, ir.cases, ir.options);\n}\n\n/**\n * Minimum option count for rewriting a plain `z.union` to switch dispatch. Below\n * this the switch helper's fixed call overhead loses to a fully-inlined\n * `||`-chain that V8 keeps flat: measured crossover is ~4 options (n=3 can hit\n * 0.6x — a regression — while n=5 is 1.27x and n=32 is 2.34x), so 5 captures the\n * stable wins with margin and never regresses a small union. Real\n * `z.discriminatedUnion` is unaffected — it dispatches via switch by construction\n * regardless of size.\n */\nconst MIN_AUTO_DISCRIMINATE_OPTIONS = 5;\n\n/**\n * Values that switch correctly under `===` (excludes `undefined` and `NaN`).\n *\n * Also excludes a SYMBOL — now that `LiteralIR.values` admits one, this is the\n * guard that keeps it out. A symbol discriminant has no source form, so there\n * is no `case` label to emit for it (`literalToJs` refuses it by type), and the\n * ordinal-table variant is string-keyed besides. Detection bails to the\n * `||`-chain, where the option's own literal check does the right thing by\n * reading the value list off the retained schema.\n */\nfunction isSwitchableDiscriminant(v: LiteralValue): v is string | number | boolean | bigint | null {\n return (\n v === null ||\n typeof v === \"string\" ||\n typeof v === \"boolean\" ||\n typeof v === \"bigint\" ||\n (typeof v === \"number\" && !Number.isNaN(v))\n );\n}\n\n/**\n * Detect whether a plain (untagged) `z.union` is *structurally* a discriminated\n * union, so its fast path can use O(1) switch dispatch instead of probing every\n * arm. Returns the discriminator + dispatch table, or null to keep the\n * `||`-chain.\n *\n * Requires (proving the switch accepts exactly what the `||`-chain would): every\n * option is a plain object that pins one shared key to a REQUIRED literal\n * (`prop.type === \"literal\"` — an optional/non-literal key is rejected), and the\n * literal values are pairwise DISJOINT across options. Disjointness is the crux:\n * it guarantees at most one option can accept any given input, so dispatching to\n * that single option is equivalent to trying them all. Any value shared by two\n * options (ambiguous), a non-switchable value (`undefined`/`NaN`), or a\n * non-object option makes detection bail to the safe `||`-chain.\n *\n * Fast-path only: the slow path keeps `z.union`'s sequential trial and its\n * `invalid_union` error shape, so failure output stays byte-identical to Zod.\n */\nexport function detectUnionDiscriminator(\n options: readonly SchemaIR[],\n): { discriminator: string; cases: DiscriminatorCase[] } | null {\n if (options.length < MIN_AUTO_DISCRIMINATE_OPTIONS) return null;\n const objects: ObjectIR[] = [];\n for (const option of options) {\n if (option.type !== \"object\") return null;\n objects.push(option);\n }\n const first = objects[0];\n if (first === undefined) return null; // unreachable (length checked above)\n\n // Only keys present in the first option can be shared by all; try each.\n candidate: for (const key of Object.keys(first.properties)) {\n const seen = new Set<string | number | boolean | bigint | null>();\n const cases: DiscriminatorCase[] = [];\n for (const [i, object] of objects.entries()) {\n const prop = object.properties[key];\n if (prop === undefined || prop.type !== \"literal\") continue candidate;\n for (const value of prop.values) {\n if (!isSwitchableDiscriminant(value)) continue candidate;\n if (seen.has(value)) continue candidate; // shared value → ambiguous dispatch\n seen.add(value);\n cases.push({ value, option: i });\n }\n }\n return { discriminator: key, cases };\n }\n return null;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"effect.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/effect.ts"],"mappings":";;;;;;;iBAqBgB,WAAW,IAAI,oBAAoB,oBAAoB,GAAG;;;;;;;;;iBAmC1D,YAAY,OAAO,qBAAqB,cAAc,GAAG;;;;;iBA8CzD,oBACd,OAAO,0BACP,cACA,GAAG;;;;;;;;;;;;;iBAmBW,iBACd,OAAO,0BACP,cACA,GAAG"}
1
+ {"version":3,"file":"effect.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/effect.ts"],"mappings":";;;;;;;iBAqBgB,WAAW,IAAI,oBAAoB,oBAAoB,GAAG;;;;;;;;;iBAmC1D,YAAY,OAAO,qBAAqB,cAAc,GAAG;;;;;iBA+CzD,oBACd,OAAO,0BACP,cACA,GAAG;;;;;;;;;;;;;iBAmBW,iBACd,OAAO,0BACP,cACA,GAAG"}
@@ -39,7 +39,8 @@ function slowEffect(ir, g) {
39
39
  * @param g - SlowGen context (provides path, issues)
40
40
  */
41
41
  function refineCheck(check, expr, g) {
42
- const messageProp = check.message === void 0 ? "" : `,message:${JSON.stringify(check.message)}`;
42
+ const message = check.message ?? g.typeMsg;
43
+ const messageProp = message === void 0 ? "" : `,message:${JSON.stringify(message)}`;
43
44
  const path = (check.path ?? []).reduce((acc, segment) => typeof segment === "number" ? extendStaticPathIndex(acc, segment) : extendStaticPath(acc, segment), g.path);
44
45
  const paramsProp = check.paramsRefIndex === void 0 ? "" : `,params:${emitConstant(g.ctx, "rpa", `__rf[${check.paramsRefIndex}]`)}`;
45
46
  return emit`
@@ -80,7 +81,8 @@ function superRefineCheck(check, expr, g) {
80
81
  const helper = emitRuntimeHelper(g.ctx, "__zcSr", ZC_SR_DECL);
81
82
  const fn = emitEffectCallable(g.ctx, check);
82
83
  const p = g.temp("sp");
83
- let code = `var ${p}=${helper}(${fn},${expr},${g.path},${g.issues});${g.output}=${p}.value;`;
84
+ const msgArg = g.typeMsg === void 0 ? "" : `,${JSON.stringify(g.typeMsg)}`;
85
+ let code = `var ${p}=${helper}(${fn},${expr},${g.path},${g.issues}${msgArg});${g.output}=${p}.value;`;
84
86
  if (g.aborted) code += `if(${p}.aborted){${g.aborted}=true;}`;
85
87
  return code;
86
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"effect.js","names":[],"sources":["../../../../src/core/codegen/schemas/effect.ts"],"sourcesContent":["import type {\n PreprocessEffectIR,\n RefineEffectCheckIR,\n SuperRefineEffectCheckIR,\n TransformEffectIR,\n} from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n emitConstant,\n emitEffectCallable,\n emitRuntimeHelper,\n extendStaticPath,\n extendStaticPathIndex,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { ZC_SR_DECL, ZC_SR_OK_DECL, ZC_SR_RUN_DECL } from \"../issue-decls.js\";\n\n/**\n * Generate code for a TransformEffectIR node.\n * Validates the inner schema, then applies the transform function and writes back the result.\n */\nexport function slowEffect(ir: TransformEffectIR | PreprocessEffectIR, g: SlowGen): string {\n if (ir.effectKind === \"preprocess\") {\n const valueVar = g.temp(\"pv\");\n return `${emit`\n var ${valueVar}=${emitEffectCallable(g.ctx, ir)}(${g.input});\n ${g.output}=${valueVar};\n ${g.visit(ir.inner, { input: valueVar, output: g.output, aborted: g.aborted })}\n `}\\n`;\n }\n\n const beforeVar = g.temp(\"ib\");\n const innerCode = g.visit(ir.inner);\n // A transform is `inner.transform(fn)` = a pipe(inner, transform): zod's\n // handlePipeResult aborts when `inner` produces any issue. Inside a union the\n // option must therefore count as aborted even if `inner`'s only issue is a\n // non-aborting `custom`/check-level code (mirrors slowPipe's abort branch).\n const abortBranch = g.aborted ? `else{${g.aborted}=true;}` : \"\";\n\n return `${emit`\n var ${beforeVar}=${g.issues}.length;\n ${innerCode}\n if(${g.issues}.length===${beforeVar}){\n ${g.output}=${emitEffectCallable(g.ctx, ir)}(${g.output});\n }${abortBranch}\n `}\\n`;\n}\n\n/**\n * Generate code for a RefineEffectCheckIR (inline refine function call).\n * Called from string/number/object check loops when a refine_effect is encountered.\n *\n * @param check - The refine effect check IR\n * @param expr - The expression to validate (may differ from g.input, e.g. objVar in object generators)\n * @param g - SlowGen context (provides path, issues)\n */\nexport function refineCheck(check: RefineEffectCheckIR, expr: string, g: SlowGen): string {\n // Custom issues are created by the refine check instance, so only the\n // refine's own message applies (never the schema-level error). With no\n // message baked in, __zcFin applies the locale default (\"Invalid input\").\n const messageProp =\n check.message === undefined ? \"\" : `,message:${JSON.stringify(check.message)}`;\n // `.refine(fn, { path })` reports against a member of the refined value, so\n // the configured segments extend this node's path.\n const path = (check.path ?? []).reduce<string>(\n (acc, segment) =>\n typeof segment === \"number\"\n ? extendStaticPathIndex(acc, segment)\n : extendStaticPath(acc, segment),\n g.path,\n );\n // `.refine(fn, { params })`: $ZodCustom's check does\n // `if (def.params) _iss.params = def.params`, so the key is present only when\n // the schema declared one, and holds the ORIGINAL object — the extractor\n // parked it in `__rf` precisely so the reference survives (see\n // RefineEffectCheckIR.paramsRefIndex). Placed before `message` to keep the\n // key order zod produces.\n const paramsProp =\n check.paramsRefIndex === undefined\n ? \"\"\n : `,params:${emitConstant(g.ctx, \"rpa\", `__rf[${check.paramsRefIndex}]`)}`;\n return emit`\n if(!${emitEffectCallable(g.ctx, check)}(${expr})){\n ${g.issues}.push({code:\"custom\",path:${path}${paramsProp},input:${expr}${messageProp}});\n }`;\n}\n\n/**\n * Declare the shared invoker both superRefine helpers call. It is module-local\n * rather than an imported helper (lean mode declares it in the runtime module\n * beside them), so inline mode must place it in the preamble itself.\n */\nfunction emitSuperRefineRunner(ctx: SlowGen[\"ctx\"]): void {\n if (ctx.mode !== \"lean\" && !ctx.preamble.includes(ZC_SR_RUN_DECL)) {\n ctx.preamble.push(ZC_SR_RUN_DECL);\n }\n}\n\n/**\n * Fast-path test for a SuperRefineEffectCheckIR: a boolean term reporting that\n * the callback added no issue and left the value alone (see ZC_SR_OK_DECL).\n */\nexport function superRefineFastTest(\n check: SuperRefineEffectCheckIR,\n expr: string,\n g: FastGen,\n): string {\n emitSuperRefineRunner(g.ctx);\n const ok = emitRuntimeHelper(g.ctx, \"__zcSrOk\", ZC_SR_OK_DECL);\n return `${ok}(${emitEffectCallable(g.ctx, check)},${expr})`;\n}\n\n/**\n * Generate code for a SuperRefineEffectCheckIR: call the payload-taking\n * callback with a synthesized `{ value, issues }` and merge what it collected.\n * zod's own wrapper installs `addIssue` and normalizes the result, so the\n * issues are zod's — the helper only reprojects them onto this node's path and\n * strips the internal bookkeeping zod deletes before they become visible.\n *\n * The payload's `value` is writable public API, so it is written back to the\n * output slot the way an overwrite effect is. This walk is always the eager one\n * (the node reports as mutating, see hasMutation), so the write-back lands on\n * every parse that reaches it — not only on failures.\n */\nexport function superRefineCheck(\n check: SuperRefineEffectCheckIR,\n expr: string,\n g: SlowGen,\n): string {\n emitSuperRefineRunner(g.ctx);\n const helper = emitRuntimeHelper(g.ctx, \"__zcSr\", ZC_SR_DECL);\n const fn = emitEffectCallable(g.ctx, check);\n const p = g.temp(\"sp\");\n let code = `var ${p}=${helper}(${fn},${expr},${g.path},${g.issues});${g.output}=${p}.value;`;\n // Inside a union option, an aborting issue must mark the option aborted so\n // pruning matches zod (see ZC_SR_DECL); elsewhere the flag is unobserved.\n if (g.aborted) code += `if(${p}.aborted){${g.aborted}=true;}`;\n return code;\n}\n"],"mappings":";;;;;;;;AAqBA,SAAgB,WAAW,IAA4C,GAAoB;CACzF,IAAI,GAAG,eAAe,cAAc;EAClC,MAAM,WAAW,EAAE,KAAK,IAAI;EAC5B,OAAO,GAAG,IAAI;YACN,SAAS,GAAG,mBAAmB,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM;QACzD,EAAE,OAAO,GAAG,SAAS;QACrB,EAAE,MAAM,GAAG,OAAO;GAAE,OAAO;GAAU,QAAQ,EAAE;GAAQ,SAAS,EAAE;EAAQ,CAAC,EAAE;MAC/E;CACJ;CAEA,MAAM,YAAY,EAAE,KAAK,IAAI;CAC7B,MAAM,YAAY,EAAE,MAAM,GAAG,KAAK;CAKlC,MAAM,cAAc,EAAE,UAAU,QAAQ,EAAE,QAAQ,WAAW;CAE7D,OAAO,GAAG,IAAI;UACN,UAAU,GAAG,EAAE,OAAO;MAC1B,UAAU;SACP,EAAE,OAAO,YAAY,UAAU;QAChC,EAAE,OAAO,GAAG,mBAAmB,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO;OACvD,YAAY;IACf;AACJ;;;;;;;;;AAUA,SAAgB,YAAY,OAA4B,MAAc,GAAoB;CAIxF,MAAM,cACJ,MAAM,YAAY,KAAA,IAAY,KAAK,YAAY,KAAK,UAAU,MAAM,OAAO;CAG7E,MAAM,QAAQ,MAAM,QAAQ,CAAC,EAAA,CAAG,QAC7B,KAAK,YACJ,OAAO,YAAY,WACf,sBAAsB,KAAK,OAAO,IAClC,iBAAiB,KAAK,OAAO,GACnC,EAAE,IACJ;CAOA,MAAM,aACJ,MAAM,mBAAmB,KAAA,IACrB,KACA,WAAW,aAAa,EAAE,KAAK,OAAO,QAAQ,MAAM,eAAe,EAAE;CAC3E,OAAO,IAAI;UACH,mBAAmB,EAAE,KAAK,KAAK,EAAE,GAAG,KAAK;QAC3C,EAAE,OAAO,4BAA4B,OAAO,WAAW,SAAS,OAAO,YAAY;;AAE3F;;;;;;AAOA,SAAS,sBAAsB,KAA2B;CACxD,IAAI,IAAI,SAAS,UAAU,CAAC,IAAI,SAAS,SAAA,+GAAuB,GAC9D,IAAI,SAAS,KAAK,cAAc;AAEpC;;;;;AAMA,SAAgB,oBACd,OACA,MACA,GACQ;CACR,sBAAsB,EAAE,GAAG;CAE3B,OAAO,GADI,kBAAkB,EAAE,KAAK,YAAY,aACrC,EAAE,GAAG,mBAAmB,EAAE,KAAK,KAAK,EAAE,GAAG,KAAK;AAC3D;;;;;;;;;;;;;AAcA,SAAgB,iBACd,OACA,MACA,GACQ;CACR,sBAAsB,EAAE,GAAG;CAC3B,MAAM,SAAS,kBAAkB,EAAE,KAAK,UAAU,UAAU;CAC5D,MAAM,KAAK,mBAAmB,EAAE,KAAK,KAAK;CAC1C,MAAM,IAAI,EAAE,KAAK,IAAI;CACrB,IAAI,OAAO,OAAO,EAAE,GAAG,OAAO,GAAG,GAAG,GAAG,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,OAAO,IAAI,EAAE,OAAO,GAAG,EAAE;CAGpF,IAAI,EAAE,SAAS,QAAQ,MAAM,EAAE,YAAY,EAAE,QAAQ;CACrD,OAAO;AACT"}
1
+ {"version":3,"file":"effect.js","names":[],"sources":["../../../../src/core/codegen/schemas/effect.ts"],"sourcesContent":["import type {\n PreprocessEffectIR,\n RefineEffectCheckIR,\n SuperRefineEffectCheckIR,\n TransformEffectIR,\n} from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n emitConstant,\n emitEffectCallable,\n emitRuntimeHelper,\n extendStaticPath,\n extendStaticPathIndex,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { ZC_SR_DECL, ZC_SR_OK_DECL, ZC_SR_RUN_DECL } from \"../issue-decls.js\";\n\n/**\n * Generate code for a TransformEffectIR node.\n * Validates the inner schema, then applies the transform function and writes back the result.\n */\nexport function slowEffect(ir: TransformEffectIR | PreprocessEffectIR, g: SlowGen): string {\n if (ir.effectKind === \"preprocess\") {\n const valueVar = g.temp(\"pv\");\n return `${emit`\n var ${valueVar}=${emitEffectCallable(g.ctx, ir)}(${g.input});\n ${g.output}=${valueVar};\n ${g.visit(ir.inner, { input: valueVar, output: g.output, aborted: g.aborted })}\n `}\\n`;\n }\n\n const beforeVar = g.temp(\"ib\");\n const innerCode = g.visit(ir.inner);\n // A transform is `inner.transform(fn)` = a pipe(inner, transform): zod's\n // handlePipeResult aborts when `inner` produces any issue. Inside a union the\n // option must therefore count as aborted even if `inner`'s only issue is a\n // non-aborting `custom`/check-level code (mirrors slowPipe's abort branch).\n const abortBranch = g.aborted ? `else{${g.aborted}=true;}` : \"\";\n\n return `${emit`\n var ${beforeVar}=${g.issues}.length;\n ${innerCode}\n if(${g.issues}.length===${beforeVar}){\n ${g.output}=${emitEffectCallable(g.ctx, ir)}(${g.output});\n }${abortBranch}\n `}\\n`;\n}\n\n/**\n * Generate code for a RefineEffectCheckIR (inline refine function call).\n * Called from string/number/object check loops when a refine_effect is encountered.\n *\n * @param check - The refine effect check IR\n * @param expr - The expression to validate (may differ from g.input, e.g. objVar in object generators)\n * @param g - SlowGen context (provides path, issues)\n */\nexport function refineCheck(check: RefineEffectCheckIR, expr: string, g: SlowGen): string {\n // The refine's own message wins; without one, zod's finalizeIssue falls\n // through to the error map of the schema the check is attached to (the check\n // stamps its owner onto the issue), so the node's schema-level message is the\n // next rung. With neither, __zcFin applies the locale default.\n const message = check.message ?? g.typeMsg;\n const messageProp = message === undefined ? \"\" : `,message:${JSON.stringify(message)}`;\n // `.refine(fn, { path })` reports against a member of the refined value, so\n // the configured segments extend this node's path.\n const path = (check.path ?? []).reduce<string>(\n (acc, segment) =>\n typeof segment === \"number\"\n ? extendStaticPathIndex(acc, segment)\n : extendStaticPath(acc, segment),\n g.path,\n );\n // `.refine(fn, { params })`: $ZodCustom's check does\n // `if (def.params) _iss.params = def.params`, so the key is present only when\n // the schema declared one, and holds the ORIGINAL object — the extractor\n // parked it in `__rf` precisely so the reference survives (see\n // RefineEffectCheckIR.paramsRefIndex). Placed before `message` to keep the\n // key order zod produces.\n const paramsProp =\n check.paramsRefIndex === undefined\n ? \"\"\n : `,params:${emitConstant(g.ctx, \"rpa\", `__rf[${check.paramsRefIndex}]`)}`;\n return emit`\n if(!${emitEffectCallable(g.ctx, check)}(${expr})){\n ${g.issues}.push({code:\"custom\",path:${path}${paramsProp},input:${expr}${messageProp}});\n }`;\n}\n\n/**\n * Declare the shared invoker both superRefine helpers call. It is module-local\n * rather than an imported helper (lean mode declares it in the runtime module\n * beside them), so inline mode must place it in the preamble itself.\n */\nfunction emitSuperRefineRunner(ctx: SlowGen[\"ctx\"]): void {\n if (ctx.mode !== \"lean\" && !ctx.preamble.includes(ZC_SR_RUN_DECL)) {\n ctx.preamble.push(ZC_SR_RUN_DECL);\n }\n}\n\n/**\n * Fast-path test for a SuperRefineEffectCheckIR: a boolean term reporting that\n * the callback added no issue and left the value alone (see ZC_SR_OK_DECL).\n */\nexport function superRefineFastTest(\n check: SuperRefineEffectCheckIR,\n expr: string,\n g: FastGen,\n): string {\n emitSuperRefineRunner(g.ctx);\n const ok = emitRuntimeHelper(g.ctx, \"__zcSrOk\", ZC_SR_OK_DECL);\n return `${ok}(${emitEffectCallable(g.ctx, check)},${expr})`;\n}\n\n/**\n * Generate code for a SuperRefineEffectCheckIR: call the payload-taking\n * callback with a synthesized `{ value, issues }` and merge what it collected.\n * zod's own wrapper installs `addIssue` and normalizes the result, so the\n * issues are zod's — the helper only reprojects them onto this node's path and\n * strips the internal bookkeeping zod deletes before they become visible.\n *\n * The payload's `value` is writable public API, so it is written back to the\n * output slot the way an overwrite effect is. This walk is always the eager one\n * (the node reports as mutating, see hasMutation), so the write-back lands on\n * every parse that reaches it — not only on failures.\n */\nexport function superRefineCheck(\n check: SuperRefineEffectCheckIR,\n expr: string,\n g: SlowGen,\n): string {\n emitSuperRefineRunner(g.ctx);\n const helper = emitRuntimeHelper(g.ctx, \"__zcSr\", ZC_SR_DECL);\n const fn = emitEffectCallable(g.ctx, check);\n const p = g.temp(\"sp\");\n // The owning schema's message covers an added issue that carries none of its\n // own, as it does for a refine (see refineCheck); the helper applies it.\n const msgArg = g.typeMsg === undefined ? \"\" : `,${JSON.stringify(g.typeMsg)}`;\n let code = `var ${p}=${helper}(${fn},${expr},${g.path},${g.issues}${msgArg});${g.output}=${p}.value;`;\n // Inside a union option, an aborting issue must mark the option aborted so\n // pruning matches zod (see ZC_SR_DECL); elsewhere the flag is unobserved.\n if (g.aborted) code += `if(${p}.aborted){${g.aborted}=true;}`;\n return code;\n}\n"],"mappings":";;;;;;;;AAqBA,SAAgB,WAAW,IAA4C,GAAoB;CACzF,IAAI,GAAG,eAAe,cAAc;EAClC,MAAM,WAAW,EAAE,KAAK,IAAI;EAC5B,OAAO,GAAG,IAAI;YACN,SAAS,GAAG,mBAAmB,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM;QACzD,EAAE,OAAO,GAAG,SAAS;QACrB,EAAE,MAAM,GAAG,OAAO;GAAE,OAAO;GAAU,QAAQ,EAAE;GAAQ,SAAS,EAAE;EAAQ,CAAC,EAAE;MAC/E;CACJ;CAEA,MAAM,YAAY,EAAE,KAAK,IAAI;CAC7B,MAAM,YAAY,EAAE,MAAM,GAAG,KAAK;CAKlC,MAAM,cAAc,EAAE,UAAU,QAAQ,EAAE,QAAQ,WAAW;CAE7D,OAAO,GAAG,IAAI;UACN,UAAU,GAAG,EAAE,OAAO;MAC1B,UAAU;SACP,EAAE,OAAO,YAAY,UAAU;QAChC,EAAE,OAAO,GAAG,mBAAmB,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO;OACvD,YAAY;IACf;AACJ;;;;;;;;;AAUA,SAAgB,YAAY,OAA4B,MAAc,GAAoB;CAKxF,MAAM,UAAU,MAAM,WAAW,EAAE;CACnC,MAAM,cAAc,YAAY,KAAA,IAAY,KAAK,YAAY,KAAK,UAAU,OAAO;CAGnF,MAAM,QAAQ,MAAM,QAAQ,CAAC,EAAA,CAAG,QAC7B,KAAK,YACJ,OAAO,YAAY,WACf,sBAAsB,KAAK,OAAO,IAClC,iBAAiB,KAAK,OAAO,GACnC,EAAE,IACJ;CAOA,MAAM,aACJ,MAAM,mBAAmB,KAAA,IACrB,KACA,WAAW,aAAa,EAAE,KAAK,OAAO,QAAQ,MAAM,eAAe,EAAE;CAC3E,OAAO,IAAI;UACH,mBAAmB,EAAE,KAAK,KAAK,EAAE,GAAG,KAAK;QAC3C,EAAE,OAAO,4BAA4B,OAAO,WAAW,SAAS,OAAO,YAAY;;AAE3F;;;;;;AAOA,SAAS,sBAAsB,KAA2B;CACxD,IAAI,IAAI,SAAS,UAAU,CAAC,IAAI,SAAS,SAAA,+GAAuB,GAC9D,IAAI,SAAS,KAAK,cAAc;AAEpC;;;;;AAMA,SAAgB,oBACd,OACA,MACA,GACQ;CACR,sBAAsB,EAAE,GAAG;CAE3B,OAAO,GADI,kBAAkB,EAAE,KAAK,YAAY,aACrC,EAAE,GAAG,mBAAmB,EAAE,KAAK,KAAK,EAAE,GAAG,KAAK;AAC3D;;;;;;;;;;;;;AAcA,SAAgB,iBACd,OACA,MACA,GACQ;CACR,sBAAsB,EAAE,GAAG;CAC3B,MAAM,SAAS,kBAAkB,EAAE,KAAK,UAAU,UAAU;CAC5D,MAAM,KAAK,mBAAmB,EAAE,KAAK,KAAK;CAC1C,MAAM,IAAI,EAAE,KAAK,IAAI;CAGrB,MAAM,SAAS,EAAE,YAAY,KAAA,IAAY,KAAK,IAAI,KAAK,UAAU,EAAE,OAAO;CAC1E,IAAI,OAAO,OAAO,EAAE,GAAG,OAAO,GAAG,GAAG,GAAG,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,SAAS,OAAO,IAAI,EAAE,OAAO,GAAG,EAAE;CAG7F,IAAI,EAAE,SAAS,QAAQ,MAAM,EAAE,YAAY,EAAE,QAAQ;CACrD,OAAO;AACT"}
@@ -27,19 +27,13 @@ function slowFile(ir, g) {
27
27
  const mimeValuesExpr = JSON.stringify(check.mime);
28
28
  if (check.mime.length === 1) code += emit`
29
29
  if(${g.input}.type!==${escapeString(check.mime[0])}){
30
- ${invalidValue(g, mimeValuesExpr, {
31
- message: check.message,
32
- useTypeMsg: false
33
- })}
30
+ ${invalidValue(g, mimeValuesExpr, { message: check.message })}
34
31
  }`;
35
32
  else {
36
33
  const setVar = g.set("mime", check.mime);
37
34
  code += emit`
38
35
  if(!${setVar}.has(${g.input}.type)){
39
- ${invalidValue(g, mimeValuesExpr, {
40
- message: check.message,
41
- useTypeMsg: false
42
- })}
36
+ ${invalidValue(g, mimeValuesExpr, { message: check.message })}
43
37
  }`;
44
38
  }
45
39
  break;
@@ -1 +1 @@
1
- {"version":3,"file":"file.js","names":[],"sources":["../../../../src/core/codegen/schemas/file.ts"],"sourcesContent":["import type { FileIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { emitSet, escapeString } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, invalidValue, tooBig, tooSmall } from \"../emit-issue.js\";\nimport { whenGatedSizeChecks } from \"./sizeable.js\";\n\nexport function slowFile(ir: FileIR, g: SlowGen): string {\n let code = emit`\n if(!(${g.input} instanceof File)){\n ${invalidType(g, \"file\")}\n ${whenGatedSizeChecks(ir.checks ?? [], g, \"size\")}\n }else{`;\n\n if (ir.checks) {\n for (const check of ir.checks) {\n switch (check.kind) {\n case \"min_size\":\n code += emit`\n if(${g.input}.size<${check.minimum}){\n ${tooSmall(g, check.minimum, \"file\", true, { message: check.message })}\n }`;\n break;\n case \"max_size\":\n code += emit`\n if(${g.input}.size>${check.maximum}){\n ${tooBig(g, check.maximum, \"file\", true, { message: check.message })}\n }`;\n break;\n case \"mime_type\": {\n if (check.mime.length === 0) break;\n const mimeValuesExpr = JSON.stringify(check.mime);\n if (check.mime.length === 1) {\n code += emit`\n if(${g.input}.type!==${escapeString(check.mime[0] as string)}){\n ${invalidValue(g, mimeValuesExpr, { message: check.message, useTypeMsg: false })}\n }`;\n } else {\n const setVar = g.set(\"mime\", check.mime);\n code += emit`\n if(!${setVar}.has(${g.input}.type)){\n ${invalidValue(g, mimeValuesExpr, { message: check.message, useTypeMsg: false })}\n }`;\n }\n break;\n }\n }\n }\n }\n\n code += emit`}`;\n return `${code}\\n`;\n}\n\nexport function fastFile(ir: FileIR, g: FastGen): string | null {\n const x = g.input;\n const parts: string[] = [`${x} instanceof File`];\n\n if (ir.checks) {\n for (const check of ir.checks) {\n switch (check.kind) {\n case \"min_size\":\n parts.push(`${x}.size>=${check.minimum}`);\n break;\n case \"max_size\":\n parts.push(`${x}.size<=${check.maximum}`);\n break;\n case \"mime_type\":\n if (check.mime.length === 0) break;\n if (check.mime.length === 1) {\n parts.push(`${x}.type===${escapeString(check.mime[0] as string)}`);\n } else {\n parts.push(`${emitSet(g.ctx, \"mime\", check.mime)}.has(${x}.type)`);\n }\n break;\n }\n }\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":";;;;;AAOA,SAAgB,SAAS,IAAY,GAAoB;CACvD,IAAI,OAAO,IAAI;WACN,EAAE,MAAM;QACX,YAAY,GAAG,MAAM,EAAE;QACvB,oBAAoB,GAAG,UAAU,CAAC,GAAG,GAAG,MAAM,EAAE;;CAGtD,IAAI,GAAG,QACL,KAAK,MAAM,SAAS,GAAG,QACrB,QAAQ,MAAM,MAAd;EACE,KAAK;GACH,QAAQ,IAAI;iBACL,EAAE,MAAM,QAAQ,MAAM,QAAQ;gBAC/B,SAAS,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAE3E;EACF,KAAK;GACH,QAAQ,IAAI;iBACL,EAAE,MAAM,QAAQ,MAAM,QAAQ;gBAC/B,OAAO,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAEzE;EACF,KAAK,aAAa;GAChB,IAAI,MAAM,KAAK,WAAW,GAAG;GAC7B,MAAM,iBAAiB,KAAK,UAAU,MAAM,IAAI;GAChD,IAAI,MAAM,KAAK,WAAW,GACxB,QAAQ,IAAI;mBACL,EAAE,MAAM,UAAU,aAAa,MAAM,KAAK,EAAY,EAAE;kBACzD,aAAa,GAAG,gBAAgB;IAAE,SAAS,MAAM;IAAS,YAAY;GAAM,CAAC,EAAE;;QAEhF;IACL,MAAM,SAAS,EAAE,IAAI,QAAQ,MAAM,IAAI;IACvC,QAAQ,IAAI;oBACJ,OAAO,OAAO,EAAE,MAAM;kBACxB,aAAa,GAAG,gBAAgB;KAAE,SAAS,MAAM;KAAS,YAAY;IAAM,CAAC,EAAE;;GAEvF;GACA;EACF;CACF;CAIJ,QAAQ,IAAI;CACZ,OAAO,GAAG,KAAK;AACjB;AAEA,SAAgB,SAAS,IAAY,GAA2B;CAC9D,MAAM,IAAI,EAAE;CACZ,MAAM,QAAkB,CAAC,GAAG,EAAE,iBAAiB;CAE/C,IAAI,GAAG,QACL,KAAK,MAAM,SAAS,GAAG,QACrB,QAAQ,MAAM,MAAd;EACE,KAAK;GACH,MAAM,KAAK,GAAG,EAAE,SAAS,MAAM,SAAS;GACxC;EACF,KAAK;GACH,MAAM,KAAK,GAAG,EAAE,SAAS,MAAM,SAAS;GACxC;EACF,KAAK;GACH,IAAI,MAAM,KAAK,WAAW,GAAG;GAC7B,IAAI,MAAM,KAAK,WAAW,GACxB,MAAM,KAAK,GAAG,EAAE,UAAU,aAAa,MAAM,KAAK,EAAY,GAAG;QAEjE,MAAM,KAAK,GAAG,QAAQ,EAAE,KAAK,QAAQ,MAAM,IAAI,EAAE,OAAO,EAAE,OAAO;GAEnE;CACJ;CAIJ,OAAO,MAAM,KAAK,IAAI;AACxB"}
1
+ {"version":3,"file":"file.js","names":[],"sources":["../../../../src/core/codegen/schemas/file.ts"],"sourcesContent":["import type { FileIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { emitSet, escapeString } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, invalidValue, tooBig, tooSmall } from \"../emit-issue.js\";\nimport { whenGatedSizeChecks } from \"./sizeable.js\";\n\nexport function slowFile(ir: FileIR, g: SlowGen): string {\n let code = emit`\n if(!(${g.input} instanceof File)){\n ${invalidType(g, \"file\")}\n ${whenGatedSizeChecks(ir.checks ?? [], g, \"size\")}\n }else{`;\n\n if (ir.checks) {\n for (const check of ir.checks) {\n switch (check.kind) {\n case \"min_size\":\n code += emit`\n if(${g.input}.size<${check.minimum}){\n ${tooSmall(g, check.minimum, \"file\", true, { message: check.message })}\n }`;\n break;\n case \"max_size\":\n code += emit`\n if(${g.input}.size>${check.maximum}){\n ${tooBig(g, check.maximum, \"file\", true, { message: check.message })}\n }`;\n break;\n case \"mime_type\": {\n if (check.mime.length === 0) break;\n const mimeValuesExpr = JSON.stringify(check.mime);\n if (check.mime.length === 1) {\n code += emit`\n if(${g.input}.type!==${escapeString(check.mime[0] as string)}){\n ${invalidValue(g, mimeValuesExpr, { message: check.message })}\n }`;\n } else {\n const setVar = g.set(\"mime\", check.mime);\n code += emit`\n if(!${setVar}.has(${g.input}.type)){\n ${invalidValue(g, mimeValuesExpr, { message: check.message })}\n }`;\n }\n break;\n }\n }\n }\n }\n\n code += emit`}`;\n return `${code}\\n`;\n}\n\nexport function fastFile(ir: FileIR, g: FastGen): string | null {\n const x = g.input;\n const parts: string[] = [`${x} instanceof File`];\n\n if (ir.checks) {\n for (const check of ir.checks) {\n switch (check.kind) {\n case \"min_size\":\n parts.push(`${x}.size>=${check.minimum}`);\n break;\n case \"max_size\":\n parts.push(`${x}.size<=${check.maximum}`);\n break;\n case \"mime_type\":\n if (check.mime.length === 0) break;\n if (check.mime.length === 1) {\n parts.push(`${x}.type===${escapeString(check.mime[0] as string)}`);\n } else {\n parts.push(`${emitSet(g.ctx, \"mime\", check.mime)}.has(${x}.type)`);\n }\n break;\n }\n }\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":";;;;;AAOA,SAAgB,SAAS,IAAY,GAAoB;CACvD,IAAI,OAAO,IAAI;WACN,EAAE,MAAM;QACX,YAAY,GAAG,MAAM,EAAE;QACvB,oBAAoB,GAAG,UAAU,CAAC,GAAG,GAAG,MAAM,EAAE;;CAGtD,IAAI,GAAG,QACL,KAAK,MAAM,SAAS,GAAG,QACrB,QAAQ,MAAM,MAAd;EACE,KAAK;GACH,QAAQ,IAAI;iBACL,EAAE,MAAM,QAAQ,MAAM,QAAQ;gBAC/B,SAAS,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAE3E;EACF,KAAK;GACH,QAAQ,IAAI;iBACL,EAAE,MAAM,QAAQ,MAAM,QAAQ;gBAC/B,OAAO,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAEzE;EACF,KAAK,aAAa;GAChB,IAAI,MAAM,KAAK,WAAW,GAAG;GAC7B,MAAM,iBAAiB,KAAK,UAAU,MAAM,IAAI;GAChD,IAAI,MAAM,KAAK,WAAW,GACxB,QAAQ,IAAI;mBACL,EAAE,MAAM,UAAU,aAAa,MAAM,KAAK,EAAY,EAAE;kBACzD,aAAa,GAAG,gBAAgB,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;QAE7D;IACL,MAAM,SAAS,EAAE,IAAI,QAAQ,MAAM,IAAI;IACvC,QAAQ,IAAI;oBACJ,OAAO,OAAO,EAAE,MAAM;kBACxB,aAAa,GAAG,gBAAgB,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAEpE;GACA;EACF;CACF;CAIJ,QAAQ,IAAI;CACZ,OAAO,GAAG,KAAK;AACjB;AAEA,SAAgB,SAAS,IAAY,GAA2B;CAC9D,MAAM,IAAI,EAAE;CACZ,MAAM,QAAkB,CAAC,GAAG,EAAE,iBAAiB;CAE/C,IAAI,GAAG,QACL,KAAK,MAAM,SAAS,GAAG,QACrB,QAAQ,MAAM,MAAd;EACE,KAAK;GACH,MAAM,KAAK,GAAG,EAAE,SAAS,MAAM,SAAS;GACxC;EACF,KAAK;GACH,MAAM,KAAK,GAAG,EAAE,SAAS,MAAM,SAAS;GACxC;EACF,KAAK;GACH,IAAI,MAAM,KAAK,WAAW,GAAG;GAC7B,IAAI,MAAM,KAAK,WAAW,GACxB,MAAM,KAAK,GAAG,EAAE,UAAU,aAAa,MAAM,KAAK,EAAY,GAAG;QAEjE,MAAM,KAAK,GAAG,QAAQ,EAAE,KAAK,QAAQ,MAAM,IAAI,EAAE,OAAO,EAAE,OAAO;GAEnE;CACJ;CAIJ,OAAO,MAAM,KAAK,IAAI;AACxB"}
@@ -13,7 +13,7 @@ function slowNumber(ir, g) {
13
13
  }else if(Number.isNaN(${g.input})){
14
14
  ${invalidType(g, "number", { extra: "received:\"NaN\"" })}
15
15
  }else if(!Number.isFinite(${g.input})){
16
- ${invalidType(g, "number", { extra: "received:\"Infinity\"" })}
16
+ ${invalidType(g, "number", { extra: `received:String(${g.input})` })}
17
17
  }`;
18
18
  if (ir.checks.length > 0) {
19
19
  code += `else{`;
@@ -47,7 +47,7 @@ function slowNumber(ir, g) {
47
47
  }`;
48
48
  break;
49
49
  case "number_format": {
50
- const message = check.message;
50
+ const message = check.message ?? g.typeMsg;
51
51
  if (check.format === "safeint") {
52
52
  const note = `note:"Integers must be within the safe integer range."`;
53
53
  const msgProp = message !== void 0 ? `,message:${JSON.stringify(message)}` : "";
@@ -96,7 +96,8 @@ function slowNumber(ir, g) {
96
96
  break;
97
97
  }
98
98
  case "multiple_of": {
99
- const msgProp = check.message !== void 0 ? `,message:${JSON.stringify(check.message)}` : "";
99
+ const message = check.message ?? g.typeMsg;
100
+ const msgProp = message !== void 0 ? `,message:${JSON.stringify(message)}` : "";
100
101
  const fsr = emitRuntimeHelper(g.ctx, "__zcFsr", ZC_FSR_DECL);
101
102
  code += emit`
102
103
  if(${fsr}(${g.input},${check.value})!==0){
@@ -1 +1 @@
1
- {"version":3,"file":"number.js","names":[],"sources":["../../../../src/core/codegen/schemas/number.ts"],"sourcesContent":["import type { CheckIR, NumberIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { checkPriority, emitEffectCallable, emitRuntimeHelper } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, tooBig, tooSmall } from \"../emit-issue.js\";\nimport { ZC_FSR_DECL } from \"../issue-decls.js\";\nimport { refineCheck, superRefineCheck, superRefineFastTest } from \"./effect.js\";\n\nexport function slowNumber(ir: NumberIR, g: SlowGen): string {\n let code = \"\";\n if (ir.coerce) {\n code += emit`try{${g.output}=Number(${g.input});}catch(_){}`;\n }\n code += emit`\n if(typeof ${g.input}!==\"number\"){\n ${invalidType(g, \"number\")}\n }else if(Number.isNaN(${g.input})){\n ${invalidType(g, \"number\", { extra: 'received:\"NaN\"' })}\n }else if(!Number.isFinite(${g.input})){\n ${invalidType(g, \"number\", { extra: 'received:\"Infinity\"' })}\n }`;\n\n if (ir.checks.length > 0) {\n code += `else{`;\n // An integer FORMAT check (`.int()`, `.int32()`, `.uint32()`) is the one\n // check here that can report `invalid_type` — non-continuable — and zod's\n // `runChecks` then skips every check after it (`else if (isAborted)\n // continue`). So `z.number().int().min(5)` on 1.5 reports the int failure\n // ALONE, where an ungated chain also volunteers too_small. Its aborting\n // branch is exactly `!Number.isInteger(value)` (the range branches report\n // continuable too_small/too_big, which do NOT stop later checks), so one\n // guard opened after the first such check reproduces zod without any\n // runtime abort bookkeeping. Non-integer formats (float32/float64) never\n // report invalid_type and so never open it.\n const opensIntGuard = (check: (typeof ir.checks)[number]): boolean =>\n check.kind === \"number_format\" &&\n (check.format === \"safeint\" || check.format === \"int32\" || check.format === \"uint32\");\n let seenIntFormat = false;\n let intGuardOpen = false;\n // Insertion order mirrors zod's issue order for multi-failure inputs.\n // The slow path collects ALL issues (no short-circuit), so cost ordering\n // buys nothing here — only the fast path's && chain benefits from it.\n for (const check of ir.checks) {\n // Opened lazily, on the first check that actually needs gating: an integer\n // format check in LAST position has nothing after it, so the guard would\n // be dead code.\n if (seenIntFormat && !intGuardOpen) {\n code += `if(Number.isInteger(${g.input})){`;\n intGuardOpen = true;\n }\n switch (check.kind) {\n case \"greater_than\":\n if (check.inclusive) {\n code += emit`\n if(${g.input}<${check.value}){\n ${tooSmall(g, check.value, \"number\", true, { message: check.message })}\n }`;\n } else {\n code += emit`\n if(${g.input}<=${check.value}){\n ${tooSmall(g, check.value, \"number\", false, { message: check.message })}\n }`;\n }\n break;\n case \"less_than\":\n if (check.inclusive) {\n code += emit`\n if(${g.input}>${check.value}){\n ${tooBig(g, check.value, \"number\", true, { message: check.message })}\n }`;\n } else {\n code += emit`\n if(${g.input}>=${check.value}){\n ${tooBig(g, check.value, \"number\", false, { message: check.message })}\n }`;\n }\n break;\n case \"number_format\": {\n const message = check.message;\n if (check.format === \"safeint\") {\n // Mirrors $ZodCheckNumberFormat: non-integers → invalid_type;\n // integers outside the safe range → too_small/too_big with\n // origin \"int\" and zod's explanatory note.\n const note = `note:\"Integers must be within the safe integer range.\"`;\n const msgProp = message !== undefined ? `,message:${JSON.stringify(message)}` : \"\";\n code += emit`\n if(!Number.isInteger(${g.input})){\n ${invalidType(g, \"int\", { extra: 'format:\"safeint\"', extraBeforeCode: true, message })}\n }else if(${g.input}<-9007199254740991){\n ${g.issues}.push({code:\"too_small\",minimum:-9007199254740991,${note},origin:\"int\",inclusive:true,input:${g.input},path:${g.path}${msgProp}});\n }else if(${g.input}>9007199254740991){\n ${g.issues}.push({code:\"too_big\",maximum:9007199254740991,${note},origin:\"int\",inclusive:true,input:${g.input},path:${g.path}${msgProp}});\n }`;\n } else if (check.format === \"int32\") {\n code += emit`\n if(!Number.isInteger(${g.input})){\n ${invalidType(g, \"int\", { extra: 'format:\"int32\"', extraBeforeCode: true, message })}\n }else if(${g.input}<-2147483648){\n ${tooSmall(g, -2147483648, \"number\", true, { message })}\n }else if(${g.input}>2147483647){\n ${tooBig(g, 2147483647, \"number\", true, { message })}\n }`;\n } else if (check.format === \"uint32\") {\n code += emit`\n if(!Number.isInteger(${g.input})){\n ${invalidType(g, \"int\", { extra: 'format:\"uint32\"', extraBeforeCode: true, message })}\n }else if(${g.input}<0){\n ${tooSmall(g, 0, \"number\", true, { message })}\n }else if(${g.input}>4294967295){\n ${tooBig(g, 4294967295, \"number\", true, { message })}\n }`;\n } else if (check.format === \"float32\") {\n code += emit`\n if(${g.input}<-3.4028234663852886e+38){\n ${tooSmall(g, \"-3.4028234663852886e+38\", \"number\", true, { message })}\n }else if(${g.input}>3.4028234663852886e+38){\n ${tooBig(g, \"3.4028234663852886e+38\", \"number\", true, { message })}\n }`;\n }\n // float64 range is [-Number.MAX_VALUE, Number.MAX_VALUE], already covered by the isFinite check above\n break;\n }\n case \"multiple_of\": {\n const msgProp =\n check.message !== undefined ? `,message:${JSON.stringify(check.message)}` : \"\";\n // zod uses a float-safe remainder: raw % mis-rejects 0.3 % 0.1\n const fsr = emitRuntimeHelper(g.ctx, \"__zcFsr\", ZC_FSR_DECL);\n code += emit`\n if(${fsr}(${g.input},${check.value})!==0){\n ${g.issues}.push({origin:\"number\",code:\"not_multiple_of\",divisor:${check.value},input:${g.input},path:${g.path}${msgProp}});\n }`;\n break;\n }\n case \"refine_effect\":\n code += refineCheck(check, g.input, g);\n break;\n case \"super_refine_effect\":\n code += superRefineCheck(check, g.input, g);\n break;\n }\n if (opensIntGuard(check)) seenIntFormat = true;\n }\n if (intGuardOpen) code += `}`;\n code += `}`;\n }\n\n return `${code}\\n`;\n}\n\nexport function fastNumber(ir: NumberIR, g: FastGen): string | null {\n if (ir.coerce) return null;\n\n const x = g.input;\n const checks = ir.checks.filter((c): c is CheckIR => c.kind !== \"refine_effect\");\n\n // Number.isFinite(x) alone implies typeof number && !NaN && finite — zod's\n // entire number type gate in one non-coercing intrinsic. Likewise\n // Number.isSafeInteger covers the safeint format gate. Only the bitwise int\n // formats ((x|0)===x, (x>>>0)===x) still need the typeof guard: applying\n // ToNumber to an arbitrary input would invoke valueOf side effects zod\n // never triggers. Math.fround(Infinity)===Infinity, so float32 keeps the\n // isFinite gate.\n const hasSafeInt = checks.some((c) => c.kind === \"number_format\" && c.format === \"safeint\");\n const hasBitwiseInt = checks.some(\n (c) => c.kind === \"number_format\" && (c.format === \"int32\" || c.format === \"uint32\"),\n );\n const parts: string[] = [];\n if (hasBitwiseInt) {\n parts.push(`typeof ${x}===\"number\"`);\n } else if (!hasSafeInt) {\n parts.push(`Number.isFinite(${x})`);\n }\n\n for (const check of checks.sort(checkPriority)) {\n switch (check.kind) {\n case \"number_format\":\n switch (check.format) {\n case \"safeint\":\n parts.push(`Number.isSafeInteger(${x})`);\n break;\n case \"int32\":\n parts.push(`(${x}|0)===${x}`);\n break;\n case \"uint32\":\n parts.push(`${x}>=0`, `${x}<=4294967295`, `(${x}>>>0)===${x}`);\n break;\n case \"float32\":\n // zod's float32 check is a pure RANGE check (3.14 is accepted\n // even though Math.fround(3.14) !== 3.14) — fround here would\n // make the fast path stricter than the slow path / zod.\n parts.push(`${x}>=-3.4028234663852886e+38`, `${x}<=3.4028234663852886e+38`);\n break;\n case \"float64\":\n // All finite numbers are valid float64\n break;\n }\n break;\n case \"greater_than\":\n parts.push(check.inclusive ? `${x}>=${check.value}` : `${x}>${check.value}`);\n break;\n case \"less_than\":\n parts.push(check.inclusive ? `${x}<=${check.value}` : `${x}<${check.value}`);\n break;\n case \"multiple_of\": {\n const fsr = emitRuntimeHelper(g.ctx, \"__zcFsr\", ZC_FSR_DECL);\n parts.push(`${fsr}(${x},${check.value})===0`);\n break;\n }\n case \"min_length\":\n case \"max_length\":\n case \"length_equals\":\n case \"string_format\":\n case \"includes\":\n case \"starts_with\":\n case \"ends_with\":\n // String-only checks on a number schema — shouldn't happen, skip\n break;\n }\n }\n\n // Refine effect checks (appended last — run after cheap checks short-circuit)\n for (const check of ir.checks) {\n if (check.kind === \"refine_effect\") {\n parts.push(`${emitEffectCallable(g.ctx, check)}(${x})`);\n } else if (check.kind === \"super_refine_effect\") {\n parts.push(superRefineFastTest(check, x, g));\n }\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"number.js","names":[],"sources":["../../../../src/core/codegen/schemas/number.ts"],"sourcesContent":["import type { CheckIR, NumberIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { checkPriority, emitEffectCallable, emitRuntimeHelper } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, tooBig, tooSmall } from \"../emit-issue.js\";\nimport { ZC_FSR_DECL } from \"../issue-decls.js\";\nimport { refineCheck, superRefineCheck, superRefineFastTest } from \"./effect.js\";\n\nexport function slowNumber(ir: NumberIR, g: SlowGen): string {\n let code = \"\";\n if (ir.coerce) {\n code += emit`try{${g.output}=Number(${g.input});}catch(_){}`;\n }\n code += emit`\n if(typeof ${g.input}!==\"number\"){\n ${invalidType(g, \"number\")}\n }else if(Number.isNaN(${g.input})){\n ${invalidType(g, \"number\", { extra: 'received:\"NaN\"' })}\n }else if(!Number.isFinite(${g.input})){\n ${invalidType(g, \"number\", { extra: `received:String(${g.input})` })}\n }`;\n\n if (ir.checks.length > 0) {\n code += `else{`;\n // An integer FORMAT check (`.int()`, `.int32()`, `.uint32()`) is the one\n // check here that can report `invalid_type` — non-continuable — and zod's\n // `runChecks` then skips every check after it (`else if (isAborted)\n // continue`). So `z.number().int().min(5)` on 1.5 reports the int failure\n // ALONE, where an ungated chain also volunteers too_small. Its aborting\n // branch is exactly `!Number.isInteger(value)` (the range branches report\n // continuable too_small/too_big, which do NOT stop later checks), so one\n // guard opened after the first such check reproduces zod without any\n // runtime abort bookkeeping. Non-integer formats (float32/float64) never\n // report invalid_type and so never open it.\n const opensIntGuard = (check: (typeof ir.checks)[number]): boolean =>\n check.kind === \"number_format\" &&\n (check.format === \"safeint\" || check.format === \"int32\" || check.format === \"uint32\");\n let seenIntFormat = false;\n let intGuardOpen = false;\n // Insertion order mirrors zod's issue order for multi-failure inputs.\n // The slow path collects ALL issues (no short-circuit), so cost ordering\n // buys nothing here — only the fast path's && chain benefits from it.\n for (const check of ir.checks) {\n // Opened lazily, on the first check that actually needs gating: an integer\n // format check in LAST position has nothing after it, so the guard would\n // be dead code.\n if (seenIntFormat && !intGuardOpen) {\n code += `if(Number.isInteger(${g.input})){`;\n intGuardOpen = true;\n }\n switch (check.kind) {\n case \"greater_than\":\n if (check.inclusive) {\n code += emit`\n if(${g.input}<${check.value}){\n ${tooSmall(g, check.value, \"number\", true, { message: check.message })}\n }`;\n } else {\n code += emit`\n if(${g.input}<=${check.value}){\n ${tooSmall(g, check.value, \"number\", false, { message: check.message })}\n }`;\n }\n break;\n case \"less_than\":\n if (check.inclusive) {\n code += emit`\n if(${g.input}>${check.value}){\n ${tooBig(g, check.value, \"number\", true, { message: check.message })}\n }`;\n } else {\n code += emit`\n if(${g.input}>=${check.value}){\n ${tooBig(g, check.value, \"number\", false, { message: check.message })}\n }`;\n }\n break;\n case \"number_format\": {\n const message = check.message ?? g.typeMsg;\n if (check.format === \"safeint\") {\n // Mirrors $ZodCheckNumberFormat: non-integers → invalid_type;\n // integers outside the safe range → too_small/too_big with\n // origin \"int\" and zod's explanatory note.\n const note = `note:\"Integers must be within the safe integer range.\"`;\n const msgProp = message !== undefined ? `,message:${JSON.stringify(message)}` : \"\";\n code += emit`\n if(!Number.isInteger(${g.input})){\n ${invalidType(g, \"int\", { extra: 'format:\"safeint\"', extraBeforeCode: true, message })}\n }else if(${g.input}<-9007199254740991){\n ${g.issues}.push({code:\"too_small\",minimum:-9007199254740991,${note},origin:\"int\",inclusive:true,input:${g.input},path:${g.path}${msgProp}});\n }else if(${g.input}>9007199254740991){\n ${g.issues}.push({code:\"too_big\",maximum:9007199254740991,${note},origin:\"int\",inclusive:true,input:${g.input},path:${g.path}${msgProp}});\n }`;\n } else if (check.format === \"int32\") {\n code += emit`\n if(!Number.isInteger(${g.input})){\n ${invalidType(g, \"int\", { extra: 'format:\"int32\"', extraBeforeCode: true, message })}\n }else if(${g.input}<-2147483648){\n ${tooSmall(g, -2147483648, \"number\", true, { message })}\n }else if(${g.input}>2147483647){\n ${tooBig(g, 2147483647, \"number\", true, { message })}\n }`;\n } else if (check.format === \"uint32\") {\n code += emit`\n if(!Number.isInteger(${g.input})){\n ${invalidType(g, \"int\", { extra: 'format:\"uint32\"', extraBeforeCode: true, message })}\n }else if(${g.input}<0){\n ${tooSmall(g, 0, \"number\", true, { message })}\n }else if(${g.input}>4294967295){\n ${tooBig(g, 4294967295, \"number\", true, { message })}\n }`;\n } else if (check.format === \"float32\") {\n code += emit`\n if(${g.input}<-3.4028234663852886e+38){\n ${tooSmall(g, \"-3.4028234663852886e+38\", \"number\", true, { message })}\n }else if(${g.input}>3.4028234663852886e+38){\n ${tooBig(g, \"3.4028234663852886e+38\", \"number\", true, { message })}\n }`;\n }\n // float64 range is [-Number.MAX_VALUE, Number.MAX_VALUE], already covered by the isFinite check above\n break;\n }\n case \"multiple_of\": {\n const message = check.message ?? g.typeMsg;\n const msgProp = message !== undefined ? `,message:${JSON.stringify(message)}` : \"\";\n // zod uses a float-safe remainder: raw % mis-rejects 0.3 % 0.1\n const fsr = emitRuntimeHelper(g.ctx, \"__zcFsr\", ZC_FSR_DECL);\n code += emit`\n if(${fsr}(${g.input},${check.value})!==0){\n ${g.issues}.push({origin:\"number\",code:\"not_multiple_of\",divisor:${check.value},input:${g.input},path:${g.path}${msgProp}});\n }`;\n break;\n }\n case \"refine_effect\":\n code += refineCheck(check, g.input, g);\n break;\n case \"super_refine_effect\":\n code += superRefineCheck(check, g.input, g);\n break;\n }\n if (opensIntGuard(check)) seenIntFormat = true;\n }\n if (intGuardOpen) code += `}`;\n code += `}`;\n }\n\n return `${code}\\n`;\n}\n\nexport function fastNumber(ir: NumberIR, g: FastGen): string | null {\n if (ir.coerce) return null;\n\n const x = g.input;\n const checks = ir.checks.filter((c): c is CheckIR => c.kind !== \"refine_effect\");\n\n // Number.isFinite(x) alone implies typeof number && !NaN && finite — zod's\n // entire number type gate in one non-coercing intrinsic. Likewise\n // Number.isSafeInteger covers the safeint format gate. Only the bitwise int\n // formats ((x|0)===x, (x>>>0)===x) still need the typeof guard: applying\n // ToNumber to an arbitrary input would invoke valueOf side effects zod\n // never triggers. Math.fround(Infinity)===Infinity, so float32 keeps the\n // isFinite gate.\n const hasSafeInt = checks.some((c) => c.kind === \"number_format\" && c.format === \"safeint\");\n const hasBitwiseInt = checks.some(\n (c) => c.kind === \"number_format\" && (c.format === \"int32\" || c.format === \"uint32\"),\n );\n const parts: string[] = [];\n if (hasBitwiseInt) {\n parts.push(`typeof ${x}===\"number\"`);\n } else if (!hasSafeInt) {\n parts.push(`Number.isFinite(${x})`);\n }\n\n for (const check of checks.sort(checkPriority)) {\n switch (check.kind) {\n case \"number_format\":\n switch (check.format) {\n case \"safeint\":\n parts.push(`Number.isSafeInteger(${x})`);\n break;\n case \"int32\":\n parts.push(`(${x}|0)===${x}`);\n break;\n case \"uint32\":\n parts.push(`${x}>=0`, `${x}<=4294967295`, `(${x}>>>0)===${x}`);\n break;\n case \"float32\":\n // zod's float32 check is a pure RANGE check (3.14 is accepted\n // even though Math.fround(3.14) !== 3.14) — fround here would\n // make the fast path stricter than the slow path / zod.\n parts.push(`${x}>=-3.4028234663852886e+38`, `${x}<=3.4028234663852886e+38`);\n break;\n case \"float64\":\n // All finite numbers are valid float64\n break;\n }\n break;\n case \"greater_than\":\n parts.push(check.inclusive ? `${x}>=${check.value}` : `${x}>${check.value}`);\n break;\n case \"less_than\":\n parts.push(check.inclusive ? `${x}<=${check.value}` : `${x}<${check.value}`);\n break;\n case \"multiple_of\": {\n const fsr = emitRuntimeHelper(g.ctx, \"__zcFsr\", ZC_FSR_DECL);\n parts.push(`${fsr}(${x},${check.value})===0`);\n break;\n }\n case \"min_length\":\n case \"max_length\":\n case \"length_equals\":\n case \"string_format\":\n case \"includes\":\n case \"starts_with\":\n case \"ends_with\":\n // String-only checks on a number schema — shouldn't happen, skip\n break;\n }\n }\n\n // Refine effect checks (appended last — run after cheap checks short-circuit)\n for (const check of ir.checks) {\n if (check.kind === \"refine_effect\") {\n parts.push(`${emitEffectCallable(g.ctx, check)}(${x})`);\n } else if (check.kind === \"super_refine_effect\") {\n parts.push(superRefineFastTest(check, x, g));\n }\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":"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"}
@@ -1,10 +1,20 @@
1
1
  import { ObjectIR, SchemaIR } from "../../types.js";
2
2
  import { FastGen, SlowGen } from "../context.js";
3
3
  //#region src/core/codegen/schemas/object.d.ts
4
+ /**
5
+ * The shape entries whose schemas actually RUN. `$ZodObject` skips a declared
6
+ * `__proto__` key in its shape loop (`if (key === "__proto__") continue`), so
7
+ * that property is never validated and never written to the output — it is
8
+ * only a recognized name for the strict/catchall passes, which read
9
+ * `Object.keys(ir.properties)` directly. Writing it would be worse than
10
+ * pointless: `{__proto__: v}` in an object literal, and `o["__proto__"] = v`
11
+ * on a plain object, both SET THE PROTOTYPE rather than define a key.
12
+ */
13
+ declare function parsedProperties(ir: ObjectIR): [string, SchemaIR][];
4
14
  declare function slowObject(ir: SchemaIR & {
5
15
  type: "object";
6
16
  }, g: SlowGen): string;
7
17
  declare function fastObject(ir: ObjectIR, g: FastGen): string | null;
8
18
  //#endregion
9
- export { fastObject, slowObject };
19
+ export { fastObject, parsedProperties, slowObject };
10
20
  //# sourceMappingURL=object.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"object.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/object.ts"],"mappings":";;;iBAmBgB,WAAW,IAAI;EAAa;GAAkB,GAAG;iBA+PjD,WAAW,IAAI,UAAU,GAAG"}
1
+ {"version":3,"file":"object.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/object.ts"],"mappings":";;;;;;;;;;;;iBA4BgB,iBAAiB,IAAI,oBAAoB;iBAIzC,WAAW,IAAI;EAAa;GAAkB,GAAG;iBA4TjD,WAAW,IAAI,UAAU,GAAG"}
@@ -1,10 +1,22 @@
1
1
  import { declareFastTemps, emitEffectCallable, emitRuntimeHelper, escapeString, extendPath, extendStaticPath, hasMutation, keyMembershipTest, outputAlwaysDefined } from "../context.js";
2
2
  import { emit } from "../emit.js";
3
3
  import { invalidType, unrecognizedKeys } from "../emit-issue.js";
4
- import { ZC_AB_DECL } from "../issue-decls.js";
4
+ import { ZC_AB_DECL, ZC_PROTO_SCRUB_DECL } from "../issue-decls.js";
5
5
  import { refineCheck, superRefineCheck, superRefineFastTest } from "./effect.js";
6
6
  import { orderByRuntimeCost } from "../fast-size.js";
7
7
  //#region src/core/codegen/schemas/object.ts
8
+ /**
9
+ * The shape entries whose schemas actually RUN. `$ZodObject` skips a declared
10
+ * `__proto__` key in its shape loop (`if (key === "__proto__") continue`), so
11
+ * that property is never validated and never written to the output — it is
12
+ * only a recognized name for the strict/catchall passes, which read
13
+ * `Object.keys(ir.properties)` directly. Writing it would be worse than
14
+ * pointless: `{__proto__: v}` in an object literal, and `o["__proto__"] = v`
15
+ * on a plain object, both SET THE PROTOTYPE rather than define a key.
16
+ */
17
+ function parsedProperties(ir) {
18
+ return Object.entries(ir.properties).filter(([key]) => key !== "__proto__");
19
+ }
8
20
  function slowObject(ir, g) {
9
21
  let code = emit`
10
22
  if(typeof ${g.input}!=="object"||${g.input}===null||Array.isArray(${g.input})){
@@ -13,13 +25,19 @@ function slowObject(ir, g) {
13
25
  const strip = ir.stripUnknownKeys === true;
14
26
  const needsClone = strip || ir.catchall !== void 0 && hasMutation(ir.catchall) || Object.values(ir.properties).some(hasMutation);
15
27
  const objVar = g.temp("o");
16
- if (!strip) code += needsClone ? `var ${objVar}={...${g.input}};` : `var ${objVar}=${g.input};`;
28
+ if (!strip) {
29
+ code += needsClone ? `var ${objVar}={...${g.input}};` : `var ${objVar}=${g.input};`;
30
+ const scrub = emitRuntimeHelper(g.ctx, "__zcPs", ZC_PROTO_SCRUB_DECL);
31
+ code += `${objVar}=${scrub}(${objVar});`;
32
+ }
17
33
  const refineMark = ir.checks && ir.checks.length > 0 ? g.temp("rm") : "";
18
34
  if (refineMark) code += `var ${refineMark}=${g.issues}.length;`;
35
+ const skipAbsent = new Set(ir.skipAbsentKeys ?? []);
19
36
  const suppressAbsent = new Set(ir.suppressAbsentKeys ?? []);
37
+ const nonoptional = new Set(ir.nonoptionalKeys ?? []);
20
38
  /** Strip only: per-property output slot + whether it is always in the result. */
21
39
  const slots = [];
22
- for (const [key, propIR] of Object.entries(ir.properties)) {
40
+ for (const [key, propIR] of parsedProperties(ir)) {
23
41
  const keyStr = escapeString(key);
24
42
  const propPath = extendStaticPath(g.path, key);
25
43
  const propExpr = strip ? g.temp("sv") : `${objVar}[${keyStr}]`;
@@ -36,13 +54,35 @@ function slowObject(ir, g) {
36
54
  output: propExpr,
37
55
  path: propPath
38
56
  });
39
- if (suppressAbsent.has(key)) {
57
+ const dropValue = strip ? `${propExpr}=undefined;` : `delete ${objVar}[${keyStr}];`;
58
+ if (skipAbsent.has(key)) code += emit`if(${keyStr} in ${g.input}){${propCode}}`;
59
+ else if (suppressAbsent.has(key)) {
60
+ const beforeVar = g.temp("ob");
61
+ const presentVar = g.temp("op");
62
+ code += emit`
63
+ var ${presentVar}=${keyStr} in ${g.input};
64
+ var ${beforeVar}=${g.issues}.length;
65
+ ${propCode}
66
+ if(!${presentVar}&&${g.issues}.length>${beforeVar}){
67
+ ${g.issues}.length=${beforeVar};${dropValue}
68
+ }`;
69
+ } else if (nonoptional.has(key)) {
40
70
  const beforeVar = g.temp("ob");
71
+ const presentVar = g.temp("op");
41
72
  code += emit`
73
+ var ${presentVar}=${keyStr} in ${g.input};
42
74
  var ${beforeVar}=${g.issues}.length;
43
75
  ${propCode}
44
- if(!(${keyStr} in ${strip ? g.input : objVar})&&${g.issues}.length>${beforeVar}){
45
- ${g.issues}.length=${beforeVar};
76
+ if(!${presentVar}){
77
+ ${dropValue}
78
+ if(${g.issues}.length===${beforeVar}){
79
+ ${invalidType(g, "nonoptional", {
80
+ input: "undefined",
81
+ path: propPath,
82
+ codeFirst: true,
83
+ useTypeMsg: false
84
+ })}
85
+ }
46
86
  }`;
47
87
  } else code += propCode;
48
88
  }
@@ -82,7 +122,7 @@ function slowObject(ir, g) {
82
122
  const seed = needsClone ? `${slot}=${g.input}[${kVar}];` : "";
83
123
  code += emit`
84
124
  for(var ${kVar} in ${g.input}){
85
- if(!(${test})){
125
+ if(!(${test})&&${kVar}!=="__proto__"){
86
126
  ${seed}
87
127
  ${g.visit(ir.catchall, {
88
128
  input: slot,
@@ -92,7 +132,7 @@ function slowObject(ir, g) {
92
132
  }
93
133
  }`;
94
134
  }
95
- if (needsClone) code += `${g.output}=${objVar};`;
135
+ if (needsClone || !strip) code += `${g.output}=${objVar};`;
96
136
  if (ir.checks && ir.checks.length > 0) {
97
137
  let refines = "";
98
138
  for (const check of ir.checks) refines += check.kind === "super_refine_effect" ? superRefineCheck(check, objVar, g) : refineCheck(check, objVar, g);
@@ -113,7 +153,7 @@ function slowObject(ir, g) {
113
153
  * output is the issue list, whose order is part of zod parity.
114
154
  */
115
155
  function orderedProperties(ir, g) {
116
- return orderByRuntimeCost(Object.entries(ir.properties), ([, propIR]) => propIR, g.ctx);
156
+ return orderByRuntimeCost(parsedProperties(ir), ([, propIR]) => propIR, g.ctx);
117
157
  }
118
158
  /**
119
159
  * Property/strict/refine fast-checks for an object, WITHOUT the leading
@@ -127,12 +167,16 @@ function orderedProperties(ir, g) {
127
167
  function fastObjectBody(ir, g, skipKey) {
128
168
  const x = g.input;
129
169
  const parts = [];
170
+ const absentAccepts = /* @__PURE__ */ new Set([...ir.skipAbsentKeys ?? [], ...ir.suppressAbsentKeys ?? []]);
171
+ const nonoptional = new Set(ir.nonoptionalKeys ?? []);
130
172
  for (const [key, propIR] of orderedProperties(ir, g)) {
131
173
  if (key === skipKey) continue;
132
- const propExpr = `${x}[${escapeString(key)}]`;
133
- const propCheck = g.visit(propIR, { input: propExpr });
174
+ const keyStr = escapeString(key);
175
+ const propCheck = g.visit(propIR, { input: `${x}[${keyStr}]` });
134
176
  if (propCheck === null) return null;
135
- parts.push(propCheck);
177
+ if (nonoptional.has(key)) parts.push(propCheck === "true" ? `${keyStr} in ${x}` : `${keyStr} in ${x}&&${propCheck}`);
178
+ else if (absentAccepts.has(key)) parts.push(`(!(${keyStr} in ${x})||${propCheck})`);
179
+ else parts.push(propCheck);
136
180
  }
137
181
  if (ir.strict) {
138
182
  const keys = Object.keys(ir.properties);
@@ -150,7 +194,7 @@ function fastObjectBody(ir, g, skipKey) {
150
194
  if (valCheck !== "true") {
151
195
  const fnName = g.temp("co");
152
196
  const test = keyMembershipTest(g.ctx, Object.keys(ir.properties), kv);
153
- g.ctx.preamble.push(`function ${fnName}(o){${declareFastTemps(catchallGen.scope)}var ${kv},${vv};for(${kv} in o){if(!(${test})){${vv}=o[${kv}];if(!(${valCheck}))return false;}}return true;}`);
197
+ g.ctx.preamble.push(`function ${fnName}(o){${declareFastTemps(catchallGen.scope)}var ${kv},${vv};for(${kv} in o){if(!(${test})&&${kv}!=="__proto__"){${vv}=o[${kv}];if(!(${valCheck}))return false;}}return true;}`);
154
198
  parts.push(`${fnName}(${x})`);
155
199
  }
156
200
  }
@@ -173,6 +217,6 @@ function fastObject(ir, g) {
173
217
  ].join("&&");
174
218
  }
175
219
  //#endregion
176
- export { fastObject, slowObject };
220
+ export { fastObject, parsedProperties, slowObject };
177
221
 
178
222
  //# sourceMappingURL=object.js.map