diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a100e374..f131747fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -773,17 +773,22 @@ jobs: run: >- pnpm exec vitest run tests/harness/worktree-files.test.ts --testNamePattern "workspace reset keeps Linux Sandbox paths POSIX" + # The Node host checks with tsc-ts, the default, over the named pipe it + # serves on Windows, and with tsgo. - name: Windows scriptc run smoke shell: pwsh run: | zig version - $output = & node packages/cli/dist/main.js run tests/corpus/001-hello.ts --backend llvm - if ($LASTEXITCODE -ne 0) { - exit $LASTEXITCODE - } - $actual = ($output -join "`n").Trim() - if ($actual -ne "hello world") { - throw "unexpected scriptc output: $actual" + foreach ($checker in @("tsc-ts", "tsgo")) { + $env:SCRIPTC_CHECKER = $checker + $output = & node packages/cli/dist/main.js run tests/corpus/001-hello.ts --backend llvm + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + $actual = ($output -join "`n").Trim() + if ($actual -ne "hello world") { + throw "unexpected scriptc output with ${checker}: $actual" + } } - name: Windows PE subsystem selection env: diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index e177e36c0..4f152e4bb 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -12,6 +12,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `csv-numbers` | cpu | `split`, `Number`, `parseInt`, `toFixed` | | `cyclic-graphs` | cpu | object graphs with back references built and dropped: trees with parent and sibling links, rings of records sharing a long-lived registry, closures capturing their owner | | `dictionary-walks` | cpu | `Record` dictionaries walked with for-in and `Object.keys`/`values`/`entries` while entries are added, updated and deleted | +| `dynamic-methods` | cpu | untyped JavaScript calling builtin methods on values it does not type: string `slice`/`indexOf`/`toUpperCase`/`padStart`, array `push`/`pop`/`includes`, `Map` and `Set`, and an object's own methods | | `functional-pipeline` | cpu | `filter`/`map`/`reduce`/`flatMap` chains, closures, small records | | `intern-table`, `intern-table-map`, `intern-table-1`, `intern-table-map-1` | cpu | worker threads interning the same type-like objects under string keys and records under number keys, then hits and misses: one shared `InternTable` per kind of key, or a module-level `Map` per thread (the `-map` entries), with 8 threads or 1 (the `-1` entries); under Node.js every thread has its own tables | | `json-records` | cpu | `JSON.stringify`, typed `JSON.parse`, Map aggregation | diff --git a/benchmarks/runtime/workloads/dynamic-methods.json b/benchmarks/runtime/workloads/dynamic-methods.json new file mode 100644 index 000000000..fa27f4ea8 --- /dev/null +++ b/benchmarks/runtime/workloads/dynamic-methods.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/dynamic-methods.mjs", + "args": [ + "300000" + ] +} diff --git a/benchmarks/runtime/workloads/dynamic-methods.mjs b/benchmarks/runtime/workloads/dynamic-methods.mjs new file mode 100644 index 000000000..bbf217701 --- /dev/null +++ b/benchmarks/runtime/workloads/dynamic-methods.mjs @@ -0,0 +1,36 @@ +// Untyped JavaScript calling builtin methods on values it never declares +// types for: string slice/indexOf/toUpperCase/startsWith/padStart, array +// push/pop/at/join/includes/indexOf, Map and Set get/set/has/add, number +// toFixed, and calls of an object's own methods. Argument: the number of +// iterations. +const rounds = Number(process.argv[2] ?? "300000"); +const words = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta"]; +const counts = new Map(); +const seen = new Set(); +const stack = []; +const recent = []; +const tally = { + total: 0, + add(value) { + this.total += value; + return this.total; + }, +}; +let checksum = 0; +for (let i = 0; i < rounds; i++) { + const word = words[i % words.length]; + const head = word.slice(0, 3).toUpperCase(); + const key = head.padStart(4, "-"); + if (word.indexOf("e") >= 0 && !word.startsWith("z")) checksum += 1; + counts.set(key, (counts.get(key) ?? 0) + 1); + seen.add(i % 97); + if (seen.has(i % 89)) checksum += 2; + stack.push(i); + if (stack.length > 32) checksum += stack.pop() % 3; + recent.push(word); + if (recent.length > 4) recent.shift(); + if (recent.includes("gamma")) checksum += recent.indexOf("gamma"); + checksum += tally.add(i % 5) % 11; + if (i % 1000 === 0) checksum += Number((i / 7).toFixed(2)) | 0; +} +console.log(checksum, counts.size, seen.size, stack.at(-1), recent.join(","), tally.total); diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index 8c57d2e2a..8b0353620 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -206,7 +206,7 @@ With `--sparse`, the profile leaves out the functions the runs never reached. Th
A runtime pack directory to use instead of an installed @scriptc/runtime-<target> package.
SCRIPTC_CHECKER
-
The TypeScript 7 API server that checks the program: tsc-ts (the default), which scriptc includes, or tsgo, from the typescript package scriptc also includes. Both give the same diagnostics and generated code. On Windows the default is tsgo, because tsc-ts's API server does not yet accept the named pipe scriptc connects over there.
+
The TypeScript 7 API server that checks the program: tsc-ts (the default), which scriptc includes, or tsgo, from the typescript package scriptc also includes. Both give the same diagnostics and generated code.
```console diff --git a/packages/cli/test/native-link-info.test.ts b/packages/cli/test/native-link-info.test.ts index bf5b3be14..dd3fe7af9 100644 --- a/packages/cli/test/native-link-info.test.ts +++ b/packages/cli/test/native-link-info.test.ts @@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => { minimum_os: "14.0", }, program: { object, entry_symbol: "main" }, - runtime_abi: { version: 33, marker: "scr_runtime_abi_v33" }, + runtime_abi: { version: 35, marker: "scr_runtime_abi_v35" }, runtime_pack: { kind: "precompiled", package: "@scriptc/runtime-darwin-arm64", @@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => { () => null, (failure: { stderr?: string }) => failure, ); - expect(error?.stderr).toContain("scr_runtime_abi_v33"); + expect(error?.stderr).toContain("scr_runtime_abi_v35"); }); }); diff --git a/packages/compiler/native/host.c b/packages/compiler/native/host.c index eeead7eff..f54bdaa3f 100644 --- a/packages/compiler/native/host.c +++ b/packages/compiler/native/host.c @@ -1,6 +1,10 @@ #ifndef _WIN32 #define _POSIX_C_SOURCE 200809L #endif +/* flock and LOCK_EX/LOCK_UN, which Darwin hides under _POSIX_C_SOURCE. */ +#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE) +#define _DARWIN_C_SOURCE 1 +#endif #include #include #include diff --git a/packages/compiler/package.json b/packages/compiler/package.json index a1d66963d..a358ae545 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@scriptc/runtime": "workspace:*", - "tsc-ts": "0.0.7", + "tsc-ts": "0.0.8", "typescript": "7.0.2", "typescript5": "npm:typescript@5.9.3" }, diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index 672fddd96..32318d53e 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -28,6 +28,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { ...(features.dynTier ? { dynTier: true } : {}), ...(features.streamDyn ? { streamDyn: true } : {}), ...(features.handleProps ? { handleProps: true } : {}), + ...(features.declAdopt ? { declAdopt: true } : {}), ...(features.dynForAwait ? { dynForAwait: true } : {}), ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), ...(features.dynBuffer ? { dynBuffer: true } : {}), diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index bb00e4563..9ce8922d5 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -153,6 +153,8 @@ export interface DynHost extends WalkerHost { /** See moduleUsesDynBuiltinStream. */ usesDynBuiltinStream?(): boolean; liveDynUnionRefAdapter(t: IrType & { kind: "union" }): string; + /** The module has declaration-typed boundary casts (IrModule.declBoundary). */ + declBoundary?(): boolean; dynPromiseAdapter(t: IrType): string; isErrorClass(className: string): boolean; classSubtypes(className: string): readonly string[]; @@ -4526,6 +4528,31 @@ export class LlDyn { /** The dyn argument spelling of one static value: dyn passes through * (+1), funcs box anonymously, everything else rides toDyn. `expr` is * BORROWED in every arm. Emits into the given builder. */ + /** A live capsule of a record or array value (+1; the value is + * borrowed), as `dynFrom` with `liveRef` builds it; null for other + * types. Closures boxed in a declaration-typed boundary program return + * their records and arrays this way. */ + private liveToDyn(B: BlockBuilder, t: IrType, expr: string): string | null { + const host = this.host; + if (t.kind === "union") { + const arms = host.unionsById.get(t.unionId)?.arms ?? []; + if (!arms.some((arm) => arm.kind === "record" || arm.kind === "array")) return null; + const boxed = B.tmp(); + B.line(`${boxed} = call ptr @${host.liveDynUnionRefAdapter(t)}(ptr ${expr})`); + return boxed; + } + if ((t.kind !== "record" && t.kind !== "array") || !streamTypedRefEligible(t)) return null; + const key = typeKey(t); + const adapter = host.liveDynRefAdapter(t); + const rc = vAdapters(host, t); + const boxed = B.tmp(); + B.line( + `${boxed} = call ptr ${typedRefConstructor(host, t)}(ptr ${expr}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${host.cstr(key)}, ${host.sizeType} ${Buffer.byteLength(key, "utf8")}, ptr @${adapter.snapshot}, ptr ${adapter.commit})`, + ); + for (const line of bindLiveArrayOps(host, adapter, boxed)) B.line(line); + return boxed; + } + private toDynExpr(B: BlockBuilder, t: IrType, expr: string): string { if (t.kind === "dyn") return this.retainDyn(B, expr); if (t.kind === "func") { @@ -4657,6 +4684,26 @@ export class LlDyn { }, "ptr null", ); + // Declaration-typed boundary programs: an argument package code + // passes keeps its identity (scr_dyn_decl_adopt). + const capsule = this.host.declBoundary?.() === true ? this.liveToDyn(B, p, a) : null; + if (capsule !== null) { + host.declare(`declare void @scr_dyn_decl_adopt(ptr, ptr)`); + host.declare(`declare void @scr_dyn_release(ptr)`); + B.line(`call void @scr_dyn_decl_adopt(ptr ${ad}, ptr ${capsule})`); + B.line(`call void @scr_dyn_release(ptr ${capsule})`); + this.pendingBail( + B, + "dfk.adopt", + () => { + t.params.slice(0, i + 1).forEach((q, j) => { + const name = j === i ? a : argNames[j]; + if (isRefCounted(q)) B.line(`call void ${releaseSym(host, q)}(ptr ${name})`); + }); + }, + "ptr null", + ); + } argNames.push(a); } }); @@ -4689,7 +4736,10 @@ export class LlDyn { const r = B.tmp(); B.line(`${r} = call ${retTy} ${fn}(${callArgs})`); this.pendingBail(B, "dfkr", () => {}, "ptr null"); - const out = this.toDynExpr(B, t.ret, r); + const out = + this.host.declBoundary?.() === true + ? (this.liveToDyn(B, t.ret, r) ?? this.toDynExpr(B, t.ret, r)) + : this.toDynExpr(B, t.ret, r); if (isRefCounted(t.ret)) B.line(`call void ${releaseSym(host, t.ret)}(ptr ${r})`); B.terminate(`ret ptr ${out}`); } diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index b6e489c19..16a1b1601 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -873,6 +873,8 @@ export function emitModuleParts(emitter: LlEmitter): string[] { out.push(`@${sym} = internal constant { ptr, ptr, ptr, ptr, ptr, ptr, ptr } { ${body} }`); } if (emitter.mapOpsConsts.size > 0) out.push(``); + for (const sym of emitter.methodIdSlots.values()) out.push(`@${sym} = internal global i32 -1`); + if (emitter.methodIdSlots.size > 0) out.push(``); for (const line of ffiCallbacks.globals) out.push(line); if (ffiCallbacks.globals.length > 0) out.push(``); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index efe6097df..c0cc9fef2 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -92,6 +92,7 @@ import { type IntegerRanges, } from "../../ir/integer-ranges.js"; import { unshareNodes } from "../../ir/unshare.js"; +import { runPairedWhileLoopsAsFor } from "../../ir/paired-while-loops.js"; import { copyStableCaptures } from "../../ir/capture-values.js"; import { resolveSelfCaptures } from "../../ir/self-captures.js"; import { analyzeInt32Slots, dynamicFieldStores, type Int32Slots } from "../../ir/int32-slots.js"; @@ -527,7 +528,9 @@ function moduleEmitter(input: IrModule, options: LlvmTargetOptions): LlEmitter { resolveSelfCaptures( hoistFinalFieldReads( freezeInitOnlyGlobals( - holdCharLocalsAsUnits(dropUnwrittenPropertyBags(mod)), + holdCharLocalsAsUnits( + dropUnwrittenPropertyBags(runPairedWhileLoopsAsFor(mod)), + ), ), ), ), @@ -1076,6 +1079,10 @@ export class LlEmitter { readonly cstrs = new Map(); /** Interned ScrMapOps descriptors (scr_map.h): initializer → symbol. */ readonly mapOpsConsts = new Map(); + /** Method-id slots of checked-dynamic method calls + * (scr_dyn_invoke_prepared_id): method name → symbol of an i32 the + * runtime fills with the name's id on the first call. */ + readonly methodIdSlots = new Map(); /** Type-directed walker functions (JSON serializers, the indent * rewriter, union ToString/join) — interned per typeKey/unionId, defs * flushed with the shape helpers. */ @@ -1844,6 +1851,7 @@ export class LlEmitter { liveDynRefAdapter: (type) => this.liveDynRefAdapter(type), usesDynBuiltinStream: () => this.usesDynBuiltinStream, liveDynUnionRefAdapter: (type) => this.liveDynUnionRefAdapter(type), + declBoundary: () => this.mod.declBoundary === true, dynPromiseAdapter: (type) => this.dynPromiseAdapter(type), isErrorClass: (name) => this.classMeta.get(name)?.root.def.name === "%Error", classRootOf: (name) => this.classMeta.get(name)?.root.def.name, @@ -2736,6 +2744,17 @@ export class LlEmitter { return `@${c.sym}`; } + /** The method-id slot for a constant method name (methodIdSlots). */ + methodIdSlot(name: string): string { + let sym = this.methodIdSlots.get(name); + if (!sym) { + sym = this.contentSymbol("sc_mid_", name); + this.methodIdSlots.set(name, sym); + } + this.journal?.entry("methodIdSlot", name, sym); + return `@${sym}`; + } + needBadTag(): void { this.needsBadTag = true; } @@ -4853,6 +4872,15 @@ export class LlEmitter { if (own !== value) names.set(value as string, own); return; } + case "methodIdSlot": { + let own = this.methodIdSlots.get(key); + if (own === undefined) { + own = this.contentSymbol("sc_mid_", key); + this.methodIdSlots.set(key, own); + } + if (own !== value) names.set(value as string, own); + return; + } case "classObj": addEntry(this.classObjs, key, value as { nameSym: string }); return; diff --git a/packages/compiler/src/backend/llvm/expr-dynamic.ts b/packages/compiler/src/backend/llvm/expr-dynamic.ts index 9af677586..434435eae 100644 --- a/packages/compiler/src/backend/llvm/expr-dynamic.ts +++ b/packages/compiler/src/backend/llvm/expr-dynamic.ts @@ -250,8 +250,10 @@ export function emitDynamicExpr( } // Buffer.prototype method names take their scr_bytes_dyn.c entry, which // runs them on Buffer receivers and dispatches any other receiver. - const invoke = DYN_BUFFER_METHODS.get(e.method) ?? "scr_dyn_invoke_prepared"; - host.declare(`declare ptr @${invoke}(ptr, ptr, ptr, ptr, ${host.sizeType}, ptr)`); + // Each call passes its method name's id slot, so only the first call + // looks the name up. + const invoke = DYN_BUFFER_METHODS.get(e.method) ?? "scr_dyn_invoke_prepared_id"; + host.declare(`declare ptr @${invoke}(ptr, ptr, ptr, ptr, ptr, ${host.sizeType}, ptr)`); let name = host.cstr(e.calleeName); if (e.calleeNameValue !== undefined) { const value = emitBorrowedInput(host, e.calleeNameValue); @@ -260,7 +262,7 @@ export function emitDynamicExpr( } const t = B.tmp(); B.line( - `${t} = call ptr @${invoke}(ptr ${recv.name}, ptr ${prepared}, ptr ${host.cstr(e.method)}, ptr ${argsPtr}, ${host.sizeType} ${args.length}, ptr ${name})`, + `${t} = call ptr @${invoke}(ptr ${recv.name}, ptr ${prepared}, ptr ${host.cstr(e.method)}, ptr ${host.methodIdSlot(e.method)}, ptr ${argsPtr}, ${host.sizeType} ${args.length}, ptr ${name})`, ); const out = host.own({ name: t, type: e.type }); host.emitPendingCheck(); diff --git a/packages/compiler/src/backend/llvm/js-ic.ts b/packages/compiler/src/backend/llvm/js-ic.ts index 45c18b803..7516673cd 100644 --- a/packages/compiler/src/backend/llvm/js-ic.ts +++ b/packages/compiler/src/backend/llvm/js-ic.ts @@ -732,7 +732,7 @@ function emitKeyed(host: Host, e: ExprOf<"call">, callee: IrFunction): LlValue { /** dynInvoke's method read on an ordinary object through the site's * cache, else scr_dyn_prepare_method. Returns the owned method (or NULL, - * which scr_dyn_invoke_prepared answers with the full dispatch). */ + * which scr_dyn_invoke_prepared_id answers with the full dispatch). */ export function emitJsIcMethod(host: Host, e: ExprOf<"dynInvoke">, receiver: string): string { const B = host.B; const method = e.method; diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 8c3c54f15..d118a99a2 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -973,6 +973,7 @@ export const LIB_FN_SYMS: Record = { "dyn.typedRefIs": "scr_dyn_typed_ref_is_key", "dyn.classIs": "scr_dyn_class_is_key", "dyn.iterPack": "scr_dyn_iter_pack", + "dyn.mathFold": "scr_dyn_math_fold", "dyn.destructureRest": "scr_dyn_destructure_rest", "dyn.iterator": "scr_dyn_iterator", "dyn.asyncIteratorOpen": "scr_dyn_async_iterator_open", @@ -1587,6 +1588,7 @@ export const LIB_FN_SYMS: Record = { "dyn.objValueList": "scr_dyn_obj_value_list", "dyn.objEntries": "scr_dyn_obj_entries", "dyn.structuredClone": "scr_structured_clone", + "dyn.declAdopt": "scr_dyn_decl_adopt", "dyn.cloneMissing": "scr_structured_clone_missing", "dyn.cloneTransferFail": "scr_structured_clone_transfer_fail", "dyn.cloneTransferFailWith": "scr_structured_clone_transfer_fail_with", diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index 5782f091d..e018037fd 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -49,6 +49,7 @@ export interface NativeLinkFeatures { /** Own properties JavaScript adds to children: scr_dyn_handle_props.c. * Absent (false) for programs without them. */ handleProps?: boolean; + declAdopt?: boolean; /** for await over checked-dynamic values: scr_dyn_for_await.c. Absent * (false) for programs without it. */ dynForAwait?: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index f683ede3e..61de82e72 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -122,6 +122,7 @@ export interface CcOptions { streamDyn?: boolean; /** Own properties JavaScript adds to children: compiles scr_dyn_handle_props.c. */ handleProps?: boolean; + declAdopt?: boolean; /** for await over checked-dynamic values: compiles scr_dyn_for_await.c. */ dynForAwait?: boolean; /** events.once with options (events.onceSignal): compiles diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 1b90bb1f1..233b9cac2 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -279,6 +279,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ unit((u) => u.opts.streamDyn === true, "scr_stream_dyn.c"), // Own properties JavaScript adds to children. unit((u) => u.opts.handleProps === true, "scr_dyn_handle_props.c"), + // The declaration-typed boundary's adoption. + unit((u) => u.opts.declAdopt === true, "scr_decl_adopt.c"), // for await over checked-dynamic values. unit((u) => u.opts.dynForAwait === true, "scr_dyn_for_await.c"), // for await over checked-dynamic values that may hold native streams. diff --git a/packages/compiler/src/backend/runtime-abi.ts b/packages/compiler/src/backend/runtime-abi.ts index 7d3dcbc02..11647ab0c 100644 --- a/packages/compiler/src/backend/runtime-abi.ts +++ b/packages/compiler/src/backend/runtime-abi.ts @@ -2,8 +2,8 @@ * of the link contract: a program object keeps this symbol undefined and the * matching runtime defines it, so incompatible manual links fail before the * program can start. */ -export const RUNTIME_ABI_VERSION = 33 as const; -export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v33" as const; +export const RUNTIME_ABI_VERSION = 35 as const; +export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v35" as const; /** Object consumption is useful today, but the complete scr_* surface may * still change before 1.0. The versioned marker prevents accidental mixing; diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index b59fdc093..75953be59 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -86,6 +86,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures { dynTier: boolean; streamDyn: boolean; handleProps: boolean; + declAdopt: boolean; dynForAwait: boolean; eventsOnceSignal: boolean; dynBuffer: boolean; @@ -283,6 +284,7 @@ export function effectiveRuntimeFeatures( dynTier: features.dynTier === true, streamDyn: features.streamDyn === true, handleProps: features.handleProps === true, + declAdopt: features.declAdopt === true, dynForAwait: features.dynForAwait === true, eventsOnceSignal: features.eventsOnceSignal === true, dynBuffer: features.dynBuffer === true, diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 532051b88..cb3f8a5d9 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec, workers = false) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 33, marker: "scr_runtime_abi_v33" }, + runtime_abi: { version: 35, marker: "scr_runtime_abi_v35" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" }, flavors: { diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index 18b04b9a6..17ac1db9e 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -127,7 +127,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 33, marker: "scr_runtime_abi_v33" }, + runtime_abi: { version: 35, marker: "scr_runtime_abi_v35" }, compiler: { command: "clang", identity: "fixture clang", diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 5c3904a91..7b7febc06 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -70,6 +70,7 @@ export interface EarlyExecutableNativeFeatures { dynTier?: boolean; streamDyn?: boolean; handleProps?: boolean; + declAdopt?: boolean; dynForAwait?: boolean; eventsOnceSignal?: boolean; dynBuffer?: boolean; diff --git a/packages/compiler/src/frontend/decl-projection.test.ts b/packages/compiler/src/frontend/decl-projection.test.ts new file mode 100644 index 000000000..dee695dd8 --- /dev/null +++ b/packages/compiler/src/frontend/decl-projection.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, test } from "vitest"; +import { applyEdits, isDeclBoundaryFile, isDeclBoundaryTypeName } from "./decl-projection.js"; + +describe("decl-projection", () => { + test("applyEdits closes the wrap that opened last first", () => { + // `x.y` wrapped from 0 (outer) and from 2 (inner), both closing at 5: + // closing parts rank by their wrap's negated start. + const out = applyEdits("x = a.b;", [ + { start: 4, end: 4, text: "(" }, + { start: 7, end: 7, text: " as Outer)", rank: -4 }, + { start: 6, end: 6, text: "(" }, + { start: 7, end: 7, text: " as Inner)", rank: -6 }, + ]); + expect(out).toBe("x = (a.(b as Inner) as Outer);"); + }); + + test("applyEdits keeps the first of overlapping replacements and every line", () => { + const text = "type A = z.infer;\nconst x = 1;"; + const out = applyEdits(text, [ + { start: 9, end: 26, text: "__scriptc_decl_0" }, + { start: 11, end: 16, text: "nope" }, + ]); + expect(out).toBe("type A = __scriptc_decl_0;\nconst x = 1;"); + }); + + test("names", () => { + expect(isDeclBoundaryTypeName("__scriptc_decl_3")).toBe(true); + expect(isDeclBoundaryTypeName("Product")).toBe(false); + expect(isDeclBoundaryFile("/app/src/__scriptc_decl_boundary__.d.ts")).toBe(true); + expect(isDeclBoundaryFile("/app/src/types.d.ts")).toBe(false); + }); +}); diff --git a/packages/compiler/src/frontend/decl-projection.ts b/packages/compiler/src/frontend/decl-projection.ts new file mode 100644 index 000000000..31d32834e --- /dev/null +++ b/packages/compiler/src/frontend/decl-projection.ts @@ -0,0 +1,793 @@ +/* The declaration-typed boundary's projection pass. + * + * An opted-in npm package compiles from its JavaScript, and the program the + * lowering compiles types it from that JavaScript: where the package's + * published declarations are richer (zod's `z.infer`), user code sees `any`. + * This pass asks the DECLARATION world (the same entry checked against the + * packages' declarations) for the types of those user-code positions. Where + * the declared type is pure data, it is printed as structural TypeScript into + * a generated global declaration file, and the user file is rewritten in the + * lowering world: + * + * - type positions that name package types (`z.infer`) name the + * projected alias instead; + * - package-produced expressions (`S.parse(x)`, `r.data`) are wrapped in + * `( as __scriptc_decl_N)`, the boundary cast; + * - untyped parameters of user callbacks the package calls gain the alias. + * + * The lowering world is then rebuilt from the rewritten text, so the + * checker's own inference carries the projected types through the rest of + * the user code, and the lowering sees an ordinary checked cast at each + * crossing (lowered with `adopt`, see narrowing.ts). No edit adds or removes + * a line break, so positions keep their lines. IR records are structural, + * so a printed type interns to the same shape as any equal type. + * + * Only programs whose user code has such positions are rewritten; every + * other program is untouched (the zero-cost rule). See + * scriptc-perf/results/design-decl-boundary/DESIGN.md. */ + +import * as ts from "./ts7/adapter.js"; + +/** Alias names of projected types start with this prefix; the lowering + * recognizes boundary casts and parameters by it. */ +export const DECL_BOUNDARY_PREFIX = "__scriptc_decl_"; + +/** The generated declaration file's base name. */ +export const DECL_BOUNDARY_FILE = "__scriptc_decl_boundary__.d.ts"; + +export function isDeclBoundaryFile(fileName: string): boolean { + return fileName.split("\\").join("/").endsWith(`/${DECL_BOUNDARY_FILE}`); +} + +export function isDeclBoundaryTypeName(name: string): boolean { + return name.startsWith(DECL_BOUNDARY_PREFIX); +} + +/** True for `T` in `x as T` / `(p: T) =>` when T names a projected alias. */ +export function isDeclBoundaryTypeNode(node: ts.TypeNode | undefined): boolean { + return ( + node !== undefined && + ts.isTypeReferenceNode(node) && + ts.isIdentifier(node.typeName) && + isDeclBoundaryTypeName(node.typeName.text) && + node.typeArguments === undefined + ); +} + +interface Edit { + start: number; + end: number; + text: string; + /** Tie-break among edits at one position (lower first). */ + rank?: number; +} + +export interface DeclProjection { + /** Rewritten text per user file. */ + files: Map; + /** The generated declaration file (path and text). */ + declFile: string; + declText: string; + /** Human-readable plan (SCRIPTC_DEBUG_DECL_BOUNDARY). */ + plan: string[]; +} + +export interface DeclProjectionInput { + jsProgram: ts.Program; + declProgram: ts.Program; + /** User source files to consider (TypeScript, outside node_modules). */ + userFiles: readonly string[]; + /** Directory of the generated declaration file. */ + outDir: string; +} + +function isPackageDeclarationFile(sf: ts.SourceFile | undefined): boolean { + if (sf === undefined || !sf.isDeclarationFile) return false; + const f = sf.fileName.split("\\").join("/"); + // The TypeScript standard library ships inside node_modules too. + if (/(^|\/)lib(\.[\w.-]+)?\.d\.ts$/.test(f) || f.startsWith("bundled:")) return false; + return ( + f.includes("/node_modules/") && + !f.includes("/node_modules/@types/node/") && + !f.includes("/node_modules/typescript/") && + !f.includes("/node_modules/@typescript/") + ); +} + +function declaredInPackage(symbol: ts.Symbol | undefined): boolean { + if (symbol === undefined) return false; + return symbol.declarations.some((d) => isPackageDeclarationFile(ts.getSourceFileOfNode(d))); +} + +function declaredInUserFile(symbol: ts.Symbol | undefined): boolean { + if (symbol === undefined) return false; + const decls = symbol.declarations; + return ( + decls.length > 0 && + decls.every((d) => { + const sf = ts.getSourceFileOfNode(d); + return !sf.isDeclarationFile && !sf.fileName.split("\\").join("/").includes("/node_modules/"); + }) + ); +} + +const ANYISH = ts.TypeFlags.Any | ts.TypeFlags.Unknown; +const READONLY_CHECK_FLAG = 8; + +/** Prints declaration-world types as structural TypeScript, or answers null + * when a type is not pure data (DESIGN.md §4.3). */ +class TypePrinter { + readonly aliases = new Map(); + readonly declarations: string[] = []; + private readonly inProgress = new Map(); + + constructor(private readonly checker: ts.TypeChecker) {} + + /** The alias naming a projectable type, or null. Top-level any/unknown + * never projects. */ + project(type: ts.Type): string | null { + if (type.flags & ANYISH) return null; + const text = this.print(type, 0); + if (text === null || text === "unknown") return null; + let name = this.aliases.get(text); + if (name === undefined) { + name = `${DECL_BOUNDARY_PREFIX}${this.aliases.size}`; + this.aliases.set(text, name); + this.declarations.push(`type ${name} = ${text};`); + } + return name; + } + + private print(type: ts.Type, depth: number): string | null { + if (depth > 24) return null; + const f = type.flags; + if (f & ANYISH) return "unknown"; + if (f & ts.TypeFlags.String) return "string"; + if (f & ts.TypeFlags.Number) return "number"; + if (f & ts.TypeFlags.Boolean) return "boolean"; + if (f & ts.TypeFlags.Null) return "null"; + if (f & ts.TypeFlags.Undefined) return "undefined"; + if (f & ts.TypeFlags.EnumLiteral || f & ts.TypeFlags.Enum) return null; + if (f & ts.TypeFlags.BooleanLiteral) return type.intrinsicName === "true" ? "true" : "false"; + if (f & ts.TypeFlags.StringLiteral) return JSON.stringify(String(type.value)); + if (f & ts.TypeFlags.NumberLiteral) { + const v = Number(type.value); + return Number.isFinite(v) ? String(v) : null; + } + if (type.isUnionType()) { + const parts: string[] = []; + for (const arm of type.getTypes()) { + const p = this.print(arm, depth + 1); + if (p === null || p === "unknown") return null; + if (!parts.includes(p)) parts.push(p); + } + // `true | false` reads better as boolean, and maps the same. + const t = parts.indexOf("true"); + const fl = parts.indexOf("false"); + if (t >= 0 && fl >= 0) { + parts.splice(Math.max(t, fl), 1); + parts.splice(Math.min(t, fl), 1, "boolean"); + } + return parts.map((p) => (/=>/.test(p) ? `(${p})` : p)).join(" | "); + } + if (type.isIntersectionType()) { + // Brands: drop members that only carry unique-symbol keys. + const kept = type.getTypes().filter((m) => !this.isBrandOnly(m)); + if (kept.length === 1) return this.print(kept[0]!, depth + 1); + if (kept.length === 0) return null; + if (!kept.every((m) => (m.flags & ts.TypeFlags.Object) !== 0)) return null; + return this.printObject(type, depth); + } + if (f & ts.TypeFlags.Object) return this.printObject(type, depth); + return null; + } + + private isBrandOnly(type: ts.Type): boolean { + if (!(type.flags & ts.TypeFlags.Object) || type.isIntersectionType()) return false; + const props = this.checker.getPropertiesOfType(type); + return props.length > 0 && props.every((p) => String(p.escapedName).startsWith("__@")); + } + + private printObject(type: ts.Type, depth: number): string | null { + const checker = this.checker; + if (checker.isArrayType(type)) { + const [elem] = checker.getTypeArguments(type); + if (elem === undefined) return null; + const e = this.print(elem, depth + 1); + if (e === null) return null; + const ro = type.getSymbol()?.escapedName === "ReadonlyArray" ? "readonly " : ""; + return `${ro}(${e})[]`; + } + if (checker.isTupleType(type)) { + const target = type.getTarget() ?? type; + const flags = target.elementFlags ?? []; + if (flags.some((fl) => fl !== ts.ElementFlags.Required)) return null; + const elems = checker.getTypeArguments(type).slice(0, flags.length); + const out: string[] = []; + for (const e of elems) { + const p = this.print(e, depth + 1); + if (p === null) return null; + out.push(p); + } + return `${target.readonly === true ? "readonly " : ""}[${out.join(", ")}]`; + } + const symbol = type.getSymbol(); + if (symbol !== undefined) { + if (symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Function | ts.SymbolFlags.Method)) + return null; + // Interfaces: only user-declared ones describe plain data reliably + // (package and lib interfaces often describe class instances). + if (symbol.flags & ts.SymbolFlags.Interface && !declaredInUserFile(symbol)) return null; + } + if ( + checker.getCallSignatures(type).length > 0 || + checker.getConstructSignatures(type).length > 0 + ) + return null; + const memo = this.inProgress.get(type.id); + if (memo !== undefined) { + memo.name ??= `${DECL_BOUNDARY_PREFIX}r${type.id}`; + return memo.name; + } + const frame: { name: string | null } = { name: null }; + this.inProgress.set(type.id, frame); + try { + const members: string[] = []; + let readonlyCount = 0; + const props = checker.getPropertiesOfType(type); + for (const prop of props) { + const key = String(prop.escapedName); + if (key.startsWith("__@")) continue; // symbol-keyed (brands) + if ( + prop.flags & + (ts.SymbolFlags.Method | ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor) + ) + return null; + const propType = checker.getTypeOfSymbol(prop); + const optional = (prop.flags & ts.SymbolFlags.Optional) !== 0; + let p = this.print(propType, depth + 1); + if (p === null) return null; + const readonly = (prop.checkFlags & READONLY_CHECK_FLAG) !== 0 || hasReadonlyModifier(prop); + if (readonly) readonlyCount++; + const name = /^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key); + if (optional && !/\bundefined\b/.test(p)) p = `${p} | undefined`; + members.push(`${readonly ? "readonly " : ""}${name}${optional ? "?" : ""}: ${p}`); + } + for (const info of checker.getIndexInfosOfType(type)) { + if (!(info.keyType.flags & ts.TypeFlags.String)) return null; + const v = this.print(info.type, depth + 1); + if (v === null) return null; + members.push(`[key: string]: ${v}`); + } + if (members.length === 0) return null; // `{}` admits any non-nullish value + // All-readonly object types may describe frozen values (.readonly()). + if (props.length > 0 && readonlyCount === props.length) return null; + const text = `{ ${members.join("; ")} }`; + if (frame.name !== null) { + this.declarations.push(`type ${frame.name} = ${text};`); + return frame.name; + } + return text; + } finally { + this.inProgress.delete(type.id); + } + } +} + +function hasReadonlyModifier(prop: ts.Symbol): boolean { + return prop.declarations.some( + (d) => + (ts.isPropertySignature(d) || ts.isPropertyDeclaration(d)) && + (d.modifiers?.nodes ?? []).some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword), + ); +} + +/** Children of a node in source order (forEachChild order). */ +function childrenOf(node: ts.Node): ts.Node[] { + const out: ts.Node[] = []; + ts.forEachChild( + node, + (c) => { + out.push(c); + return undefined; + }, + (list) => { + for (const c of list.nodes) out.push(c); + return undefined; + }, + ); + return out; +} + +function isWriteTarget(node: ts.Node): boolean { + let child = node; + let parent = node.parent; + while (parent !== undefined && ts.isParenthesizedExpression(parent)) { + child = parent; + parent = parent.parent; + } + if (parent === undefined) return false; + if (ts.isBinaryExpression(parent) && parent.left === child) { + const k = parent.operatorToken.kind; + return k >= ts.SyntaxKind.FirstAssignment && k <= ts.SyntaxKind.LastAssignment; + } + if (ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) { + const op = parent.operator; + return op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken; + } + if (ts.isDeleteExpression(parent)) return true; + if (ts.isForOfStatement(parent) || ts.isForInStatement(parent)) + return parent.initializer === child; + if (ts.isShorthandPropertyAssignment(parent)) return true; + if ( + ts.isArrayLiteralExpression(parent) || + ts.isPropertyAssignment(parent) || + ts.isSpreadElement(parent) + ) { + // Destructuring assignment targets: conservative, skip. + let up: ts.Node | undefined = parent; + while ( + up !== undefined && + (ts.isArrayLiteralExpression(up) || + ts.isObjectLiteralExpression(up) || + ts.isPropertyAssignment(up) || + ts.isSpreadElement(up)) + ) + up = up.parent; + if ( + up !== undefined && + ts.isBinaryExpression(up) && + up.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) + return true; + } + return false; +} + +function inOptionalChain(node: ts.Node): boolean { + const parent = node.parent; + if (parent === undefined) return false; + if ( + (ts.isPropertyAccessExpression(parent) || + ts.isElementAccessExpression(parent) || + ts.isCallExpression(parent)) && + parent.expression === node && + (parent.questionDotToken !== undefined || (parent.flags & ts.NodeFlags.OptionalChain) !== 0) + ) + return true; + return ( + (ts.isPropertyAccessExpression(node) || + ts.isElementAccessExpression(node) || + ts.isCallExpression(node)) && + (node.flags & ts.NodeFlags.OptionalChain) !== 0 + ); +} + +function isCallee(node: ts.Node): boolean { + const p = node.parent; + return ( + p !== undefined && + (ts.isCallExpression(p) || ts.isNewExpression(p) || ts.isTaggedTemplateExpression(p)) && + (ts.isTaggedTemplateExpression(p) ? p.tag === node : p.expression === node) + ); +} + +function inTypeContext(node: ts.Node): boolean { + for (let p = node.parent; p !== undefined; p = p.parent) { + if (ts.isTypeNode(p) || ts.isTypeQueryNode(p)) return true; + if (ts.isExpressionWithTypeArguments(p) && p.parent && ts.isHeritageClause(p.parent)) + return true; + if (ts.isStatement(p)) return false; + } + return false; +} + +function unwrapTransparent(node: ts.Expression): ts.Expression { + let e = node; + while (ts.isParenthesizedExpression(e) || ts.isNonNullExpression(e) || ts.isAwaitExpression(e)) + e = e.expression; + return e; +} + +/** Plans the projection for one program pair. Answers null when nothing + * in the user files projects (the program stays as it is). */ +export function planDeclProjection(input: DeclProjectionInput): DeclProjection | null { + const jsChecker = input.jsProgram.getTypeChecker(); + const declChecker = input.declProgram.getTypeChecker(); + const printer = new TypePrinter(declChecker); + const plan: string[] = []; + const edits = new Map(); + + const typeAt = (checker: ts.TypeChecker, node: ts.Node): ts.Type | null => { + try { + return checker.getTypeAtLocation(node); + } catch { + return null; + } + }; + const isAnyish = (t: ts.Type | null): boolean => t !== null && (t.flags & ANYISH) !== 0; + const resolveAlias = (s: ts.Symbol | undefined): ts.Symbol | undefined => { + if (s === undefined || !(s.flags & ts.SymbolFlags.Alias)) return s; + try { + return declChecker.getAliasedSymbol(s); + } catch { + return s; + } + }; + const declSymbol = (n: ts.Node): ts.Symbol | undefined => { + try { + return resolveAlias(declChecker.getSymbolAtLocation(n)); + } catch { + return undefined; + } + }; + const namesPackageSymbol = (node: ts.Node): boolean => { + let found = false; + const visit = (n: ts.Node): void => { + if (found) return; + if (ts.isIdentifier(n) && declaredInPackage(declSymbol(n))) { + found = true; + return; + } + for (const c of childrenOf(n)) visit(c); + }; + visit(node); + return found; + }; + + for (const file of input.userFiles) { + const jsSf = input.jsProgram.getSourceFile(file); + const declSf = input.declProgram.getSourceFile(file); + if (jsSf === undefined || declSf === undefined || jsSf.text !== declSf.text) continue; + const fileEdits: Edit[] = []; + /** Candidate expressions (by js node) with their alias. */ + const exprCandidates = new Map(); + const exprOrder: ts.Node[] = []; + + const considerType = (jsNode: ts.Node, declNode: ts.Node): boolean => { + if ( + !ts.isTypeReferenceNode(jsNode) && + !ts.isIndexedAccessTypeNode(jsNode) && + !ts.isExpressionWithTypeArguments(jsNode) + ) + return false; + if (ts.isExpressionWithTypeArguments(jsNode)) { + if (!jsNode.parent || !ts.isHeritageClause(jsNode.parent)) return false; + if (!jsNode.parent.parent || !ts.isInterfaceDeclaration(jsNode.parent.parent)) return false; + } + let jsType: ts.Type | null; + let declType: ts.Type | null; + try { + jsType = ts.isExpressionWithTypeArguments(jsNode) + ? jsChecker.getTypeAtLocation(jsNode) + : jsChecker.getTypeFromTypeNode(jsNode as ts.TypeNode); + declType = ts.isExpressionWithTypeArguments(declNode) + ? declChecker.getTypeAtLocation(declNode) + : declChecker.getTypeFromTypeNode(declNode as ts.TypeNode); + } catch { + return false; + } + if (!isAnyish(jsType) || declType === null) return false; + if (!namesPackageSymbol(declNode)) return false; + const alias = printer.project(declType); + if (alias === null) return false; + const start = ts.tokenStartOfNode(jsNode); + fileEdits.push({ start, end: jsNode.end, text: alias }); + plan.push(`${file}:${start}: type ${jsSf.text.slice(start, jsNode.end)} -> ${alias}`); + return true; + }; + + const considerExpr = (jsNode: ts.Node, declNode: ts.Node): void => { + if (!ts.isExpression(jsNode)) return; + const isCall = + ts.isCallExpression(jsNode) || + ts.isNewExpression(jsNode) || + ts.isTaggedTemplateExpression(jsNode); + const isAccess = + ts.isPropertyAccessExpression(jsNode) || ts.isElementAccessExpression(jsNode); + const isAwait = ts.isAwaitExpression(jsNode); + if (!isCall && !isAccess && !isAwait && !ts.isIdentifier(jsNode)) return; + if (inTypeContext(jsNode) || isCallee(jsNode) || isWriteTarget(jsNode)) return; + if (inOptionalChain(jsNode)) return; + const parent = jsNode.parent; + if (parent !== undefined && ts.isExpressionStatement(parent)) return; + if ( + parent !== undefined && + (ts.isImportSpecifier(parent) || + ts.isImportClause(parent) || + ts.isNamespaceImport(parent) || + ts.isExportSpecifier(parent)) + ) + return; + if (ts.isIdentifier(jsNode)) { + if (parent !== undefined && ts.isPropertyAccessExpression(parent) && parent.name === jsNode) + return; + } + const jsType = typeAt(jsChecker, jsNode); + if (!isAnyish(jsType)) return; + // Package-produced? + const d = declNode as ts.Expression; + let produced = false; + if (ts.isCallExpression(d) || ts.isNewExpression(d)) { + const callee = d.expression; + const target = ts.isPropertyAccessExpression(callee) ? callee.name : callee; + produced = declaredInPackage(declSymbol(target)); + if (!produced) { + try { + const sig = declChecker.getResolvedSignature(d); + const decl = sig?.declaration; + produced = decl !== undefined && isPackageDeclarationFile(ts.getSourceFileOfNode(decl)); + } catch { + produced = false; + } + } + } else if (ts.isTaggedTemplateExpression(d)) { + produced = declaredInPackage(declSymbol(d.tag)); + } else if (ts.isPropertyAccessExpression(d) || ts.isElementAccessExpression(d)) { + const recvDecl = typeAt(declChecker, d.expression); + const recvJs = typeAt(jsChecker, (jsNode as ts.PropertyAccessExpression).expression); + const recvProjectable = recvDecl !== null && printer.project(recvDecl) !== null; + produced = !recvProjectable || (recvJs !== null && !isAnyish(recvJs)); + } else if (ts.isAwaitExpression(d)) { + const inner = unwrapTransparent(d.expression); + produced = + (ts.isCallExpression(inner) || ts.isPropertyAccessExpression(inner)) && + (declaredInPackage( + declSymbol( + ts.isCallExpression(inner) + ? ts.isPropertyAccessExpression(inner.expression) + ? inner.expression.name + : inner.expression + : inner.name, + ), + ) || + isAnyish(typeAt(jsChecker, inner))); + } else if (ts.isIdentifier(d)) { + const raw = (() => { + try { + return declChecker.getSymbolAtLocation(d); + } catch { + return undefined; + } + })(); + produced = + raw !== undefined && + (raw.flags & ts.SymbolFlags.Alias) !== 0 && + declaredInPackage(resolveAlias(raw)); + } + if (!produced) return; + // A consumer that takes anything needs no conversion. + try { + const ctx = declChecker.getContextualType(d); + if (ctx !== undefined && ctx.flags & ANYISH) return; + } catch { + // no contextual type + } + const declType = typeAt(declChecker, d); + if (declType === null) return; + const alias = printer.project(declType); + if (alias === null) return; + exprCandidates.set(jsNode, alias); + exprOrder.push(jsNode); + }; + + const annotated = new Set(); + const considerParam = (jsNode: ts.Node, declNode: ts.Node): void => { + if (!ts.isParameter(jsNode) || jsNode.type !== undefined || !ts.isIdentifier(jsNode.name)) + return; + const fn = jsNode.parent; + if (fn === undefined || !(ts.isArrowFunction(fn) || ts.isFunctionExpression(fn))) return; + if (jsNode.dotDotDotToken !== undefined) return; + const jsType = typeAt(jsChecker, jsNode.name); + if (!isAnyish(jsType)) return; + const declType = typeAt(declChecker, (declNode as ts.ParameterDeclaration).name); + if (declType === null) return; + // Only parameters the package's declarations type (a callback the + // package calls), not ones the user typed elsewhere. + try { + const ctxFn = declChecker.getContextualType(declNode.parent as ts.Expression); + if (ctxFn === undefined) return; + const sigs = declChecker.getCallSignatures(ctxFn); + if ( + !sigs.some( + (s) => + s.declaration !== undefined && + isPackageDeclarationFile(ts.getSourceFileOfNode(s.declaration)), + ) + ) + return; + } catch { + return; + } + const alias = printer.project(declType); + if (alias === null) return; + annotated.add(jsNode); + const name = jsNode.name; + const nameStart = ts.tokenStartOfNode(name); + const text = jsSf.text; + // `x => ...` or `async x => ...`: nothing but the modifiers comes + // before the parameter, which then needs parentheses for its type. + const head = ts.isArrowFunction(fn) + ? ((ts.getModifiers(fn) ?? []).at(-1)?.end ?? ts.tokenStartOfNode(fn)) + : nameStart; + const bareArrow = + ts.isArrowFunction(fn) && + fn.parameters.nodes.length === 1 && + text.slice(head, nameStart).trim() === ""; + const afterName = jsNode.questionToken !== undefined ? jsNode.questionToken.end : name.end; + if (bareArrow) { + fileEdits.push({ start: nameStart, end: nameStart, text: "(" }); + fileEdits.push({ start: afterName, end: afterName, text: `: ${alias})` }); + } else { + fileEdits.push({ start: afterName, end: afterName, text: `: ${alias}` }); + } + plan.push(`${file}:${nameStart}: parameter ${name.text}: ${alias}`); + }; + + const walk = (jsNode: ts.Node, declNode: ts.Node): void => { + if (jsNode.kind !== declNode.kind) return; + if (ts.isTypeNode(jsNode) || ts.isExpressionWithTypeArguments(jsNode)) { + if (considerType(jsNode, declNode)) return; + } else { + considerParam(jsNode, declNode); + considerExpr(jsNode, declNode); + } + const a = childrenOf(jsNode); + const b = childrenOf(declNode); + if (a.length !== b.length) return; + for (let i = 0; i < a.length; i++) walk(a[i]!, b[i]!); + }; + walk(jsSf, declSf); + + // Suppress candidates whose receiver chain another candidate types. + const symbolTyped = new Map(); + const willBeTyped = (e: ts.Node, depth = 0): boolean => { + if (depth > 32) return false; + if (exprCandidates.has(e)) return true; + if (ts.isParenthesizedExpression(e) || ts.isNonNullExpression(e) || ts.isAwaitExpression(e)) + return willBeTyped(e.expression, depth + 1); + if (ts.isAsExpression(e)) return false; + if (ts.isPropertyAccessExpression(e) || ts.isElementAccessExpression(e)) + return willBeTyped(e.expression, depth + 1); + if (ts.isIdentifier(e)) { + let sym: ts.Symbol | undefined; + try { + sym = jsChecker.getSymbolAtLocation(e); + } catch { + return false; + } + if (sym === undefined) return false; + const known = symbolTyped.get(sym); + if (known !== undefined) return known; + symbolTyped.set(sym, false); + let result = false; + const decls = sym.declarations; + if (decls.length === 1) { + const decl = decls[0]!; + if ( + ts.isVariableDeclaration(decl) && + decl.initializer !== undefined && + decl.type === undefined + ) + result = willBeTyped(decl.initializer, depth + 1); + else if (ts.isParameter(decl) && annotated.has(decl)) result = true; + else if (ts.isBindingElement(decl)) result = false; + } + symbolTyped.set(sym, result); + return result; + } + return false; + }; + const receiverOf = (e: ts.Node): ts.Node | null => { + if (ts.isPropertyAccessExpression(e) || ts.isElementAccessExpression(e)) return e.expression; + if (ts.isIdentifier(e)) return null; + return null; + }; + const kept: ts.Node[] = []; + for (const e of exprOrder) { + const recv = receiverOf(e); + if (recv !== null && willBeTyped(recv)) continue; + // An identifier candidate that is a user binding never gets here + // (only package imports are candidates). + kept.push(e); + } + // Nested candidates: keep only the innermost (an outer one is typed by + // the inner wrap). + const keptSet = new Set(kept); + const containsKept = (outer: ts.Node): boolean => { + let found = false; + const visit = (n: ts.Node): void => { + if (found) return; + for (const c of childrenOf(n)) { + if (keptSet.has(c)) { + found = true; + return; + } + visit(c); + } + }; + visit(outer); + return found; + }; + for (const e of kept) { + if ( + !ts.isAwaitExpression(e) && + containsKept(e) && + (ts.isPropertyAccessExpression(e) || ts.isElementAccessExpression(e)) + ) { + // `a.b.c` with `a.b` wrapped: the outer access reads typed data. + const recv = receiverOf(e); + if ( + recv !== null && + (keptSet.has(unwrapTransparent(recv as ts.Expression)) || willBeTyped(recv)) + ) + continue; + } + const alias = exprCandidates.get(e)!; + const start = ts.tokenStartOfNode(e); + const stmt = e.parent; + let prefix = "("; + // A statement that would start with `(` after a line without a + // semicolon continues the previous expression: guard it. + let up: ts.Node | undefined = e; + while (up?.parent !== undefined && !ts.isStatement(up.parent)) up = up.parent; + // Only a statement in a statement list can follow such a line; an + // unbraced body (`for (...) S.parse(x).run()`) follows its header, + // and a `;` there would become the body. + const list = up?.parent?.parent; + if ( + up?.parent !== undefined && + ts.isExpressionStatement(up.parent) && + ts.tokenStartOfNode(up.parent) === start && + list !== undefined && + (ts.isBlock(list) || + ts.isSourceFile(list) || + ts.isModuleBlock(list) || + ts.isCaseClause(list) || + ts.isDefaultClause(list)) + ) + prefix = ";("; + void stmt; + fileEdits.push({ start, end: start, text: prefix }); + // Closing parts at one position close inner wraps first. + fileEdits.push({ start: e.end, end: e.end, text: ` as ${alias})`, rank: -start }); + plan.push( + `${file}:${start}: cast ${jsSf.text.slice(start, Math.min(e.end, start + 60)).replace(/\s+/g, " ")} as ${alias}`, + ); + } + + if (fileEdits.length === 0) continue; + edits.set(file, fileEdits); + } + + if (edits.size === 0) return null; + const files = new Map(); + for (const [file, list] of edits) { + const sf = input.jsProgram.getSourceFile(file)!; + files.set(file, applyEdits(sf.text, list)); + } + const declFile = `${input.outDir.split("\\").join("/")}/${DECL_BOUNDARY_FILE}`; + const declText = + "// Generated by scriptc: types projected from npm package declarations.\n" + + printer.declarations.join("\n") + + "\n"; + return { files, declFile, declText, plan }; +} + +/** Applies insertions and replacements; overlapping replacements keep the + * first. Insertions at one position keep their order. */ +export function applyEdits(text: string, edits: readonly Edit[]): string { + const sorted = edits + .map((e, i) => ({ ...e, i })) + .sort( + (a, b) => a.start - b.start || a.end - b.end || (a.rank ?? 0) - (b.rank ?? 0) || a.i - b.i, + ); + let out = ""; + let at = 0; + for (const e of sorted) { + if (e.start < at) continue; // overlaps an earlier replacement + out += text.slice(at, e.start) + e.text; + at = e.end; + } + return out + text.slice(at); +} diff --git a/packages/compiler/src/frontend/dts-paths.ts b/packages/compiler/src/frontend/dts-paths.ts index 3b2f32413..009d1c333 100644 --- a/packages/compiler/src/frontend/dts-paths.ts +++ b/packages/compiler/src/frontend/dts-paths.ts @@ -34,6 +34,31 @@ export function packageShimRoot(): string { : join(installedDeclarationRoot, "shims"); } +/** The directories of files scriptc itself ships, each with the prefix + * that names its files independently of where scriptc is installed + * (compilerOwnedFileName). IR artifacts spell source locations in them this + * way (serializeModuleParts). */ +export function compilerOwnedFileRoots(): readonly { from: string; to: string }[] { + return [{ from: tsgoPath(packageShimRoot()) + "/", to: "scriptc:shims/" }]; +} + +/** The name of a file scriptc itself ships (a package shim or one of the + * shipped declaration files) that does not depend on where scriptc is + * installed: `scriptc:shims/` or + * `scriptc:`. Null for every other file. Names and + * artifacts that would otherwise spell the installation's absolute path use + * it, so two installations of one compiler emit the same output. */ +export function compilerOwnedFileName(file: string): string | null { + const path = tsgoPath(file); + for (const { from, to } of compilerOwnedFileRoots()) { + if (path.startsWith(from)) return to + path.slice(from.length); + } + for (const declaration of [ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]) { + if (path === declaration) return `scriptc:${path.slice(path.lastIndexOf("/") + 1)}`; + } + return null; +} + /** Path of the shipped ambient declarations — the always-shipped CORE * (comptime, setTimeout). Part of EVERY program scriptc * builds, the project-world preflight program included. */ diff --git a/packages/compiler/src/frontend/lowering/builtins/global-namespace-keys.ts b/packages/compiler/src/frontend/lowering/builtins/global-namespace-keys.ts new file mode 100644 index 000000000..bb364ee55 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/global-namespace-keys.ts @@ -0,0 +1,123 @@ +/* The string-keyed properties Node 24 exposes on its builtin constructor + * and namespace globals, own and inherited, so `"key" in Error` (a feature + * probe such as `"captureStackTrace" in Error`) answers statically. The + * global must be the unshadowed builtin; compiled programs cannot add + * properties to these objects, so the answer is fixed. */ + +const FUNCTION_PROTOTYPE = [ + "length", + "name", + "arguments", + "caller", + "constructor", + "apply", + "bind", + "call", + "toString", +]; +const OBJECT_PROTOTYPE = [ + "constructor", + "__defineGetter__", + "__defineSetter__", + "hasOwnProperty", + "__lookupGetter__", + "__lookupSetter__", + "isPrototypeOf", + "propertyIsEnumerable", + "toString", + "valueOf", + "__proto__", + "toLocaleString", +]; +const CONSTRUCTOR_OWN = ["length", "name", "prototype"]; +const ERROR_OWN = [ + ...CONSTRUCTOR_OWN, + "captureStackTrace", + "prepareStackTrace", + "isError", + "stackTraceLimit", +]; + +/** Own string keys per global, beyond what the prototype chain adds. */ +const CONSTRUCTORS: Readonly> = { + Error: ERROR_OWN, + // The native error constructors inherit from Error. + TypeError: ERROR_OWN, + RangeError: ERROR_OWN, + SyntaxError: ERROR_OWN, + ReferenceError: ERROR_OWN, + EvalError: ERROR_OWN, + URIError: ERROR_OWN, + Object: [ + ...CONSTRUCTOR_OWN, + "assign", + "getOwnPropertyDescriptor", + "getOwnPropertyDescriptors", + "getOwnPropertyNames", + "getOwnPropertySymbols", + "hasOwn", + "is", + "preventExtensions", + "seal", + "create", + "defineProperties", + "defineProperty", + "freeze", + "getPrototypeOf", + "setPrototypeOf", + "isExtensible", + "isFrozen", + "isSealed", + "keys", + "entries", + "fromEntries", + "values", + "groupBy", + ], + Array: [...CONSTRUCTOR_OWN, "isArray", "from", "fromAsync", "of"], + Promise: [ + ...CONSTRUCTOR_OWN, + "all", + "allSettled", + "any", + "race", + "resolve", + "reject", + "withResolvers", + "try", + ], + Number: [ + ...CONSTRUCTOR_OWN, + "isFinite", + "isInteger", + "isNaN", + "isSafeInteger", + "parseFloat", + "parseInt", + "MAX_VALUE", + "MIN_VALUE", + "NaN", + "NEGATIVE_INFINITY", + "POSITIVE_INFINITY", + "MAX_SAFE_INTEGER", + "MIN_SAFE_INTEGER", + "EPSILON", + ], + String: [...CONSTRUCTOR_OWN, "fromCharCode", "fromCodePoint", "raw"], + Map: [...CONSTRUCTOR_OWN, "groupBy"], + Set: CONSTRUCTOR_OWN, + WeakMap: CONSTRUCTOR_OWN, + WeakSet: CONSTRUCTOR_OWN, + Boolean: CONSTRUCTOR_OWN, + Function: CONSTRUCTOR_OWN, + Date: [...CONSTRUCTOR_OWN, "now", "parse", "UTC"], + BigInt: [...CONSTRUCTOR_OWN, "asUintN", "asIntN"], +}; + +/** Whether Node answers `key in ` with true, or null when `name` is + * not a tabled global. */ +export function builtinGlobalHasKey(name: string, key: string): boolean | null { + const own = CONSTRUCTORS[name]; + if (own === undefined) return null; + return own.includes(key) || FUNCTION_PROTOTYPE.includes(key) || OBJECT_PROTOTYPE.includes(key); +} diff --git a/packages/compiler/src/frontend/lowering/class-callbacks.ts b/packages/compiler/src/frontend/lowering/class-callbacks.ts index 6ca685684..0bda0068e 100644 --- a/packages/compiler/src/frontend/lowering/class-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/class-callbacks.ts @@ -6,12 +6,14 @@ import { STRING, VOID, isClassOwnEnumerableFieldName, + typeEquals, type IrExpr, type IrStmt, type SrcLoc, } from "../../ir/ir.js"; import { isJsSourceFile, locOf } from "../program.js"; -import type { Lowerer } from "./lowerer.js"; +import { PoisonError, type Lowerer } from "./lowerer.js"; +import { unsupportedDiag } from "../../diagnostics/diagnostic.js"; import { findMethodOn, type ClassInfo } from "./lower-classes.js"; import { classPropertiesHelper } from "./class-dynamic-dispatch.js"; import { observedPrototypeMethod } from "./class-prototypes.js"; @@ -162,6 +164,7 @@ export function classCallbackCall( fallback: IrExpr, loc: SrcLoc, calleeName = name, + original?: { originalId: () => string | undefined; directCall: () => IrExpr }, ): IrExpr { const bag = lowerer.declareHiddenLocal("%callbackBag", DYN); const present = lowerer.declareHiddenLocal("%callbackPresent", BOOL); @@ -186,27 +189,91 @@ export function classCallbackCall( loc, }; const observed = observedSlot(lowerer, receiver, name, loc); - const result: IrExpr = observed - ? call - : { - kind: "ternary", - cond: varRef(present.id, BOOL, loc), - then: call, - else_: - fallback.type.kind === "void" - ? { - kind: "seqExpr", - stmts: [{ kind: "exprStmt", expr: fallback, loc }], - result: dynUndefinedExpr(loc), - type: DYN, - loc, - } - : lowerer.coerceToExpected(fallback, DYN), - type: DYN, - loc, - }; - const answer = - fallback.type.kind === "void" ? result : lowerer.coerceToExpected(result, fallback.type); + const boxed = + observed || fallback.type.kind === "void" ? null : lowerer.coerceToExpected(fallback, DYN); + let answer: IrExpr; + if (boxed !== null && boxed.type.kind !== "dyn") { + // A result with no checked-dynamic form (an Iterable, whose + // generator arm does not box) keeps its native type: the unchanged + // method's result stays as it is, and a replacement's dynamic result + // takes the checked conversion into that type. + const converted = lowerer.coerceToExpected(call, fallback.type); + if (!typeEquals(converted.type, fallback.type)) { + lowerer.pushDiag( + unsupportedDiag( + "SC1090", + loc, + `calling replaceable method '${calleeName}' with a '${lowerer.fmt(fallback.type)}' result`, + ), + ); + throw new PoisonError(); + } + answer = { + kind: "ternary", + cond: varRef(present.id, BOOL, loc), + then: converted, + else_: fallback, + type: fallback.type, + loc, + }; + } else { + const result: IrExpr = observed + ? call + : { + kind: "ternary", + cond: varRef(present.id, BOOL, loc), + then: call, + else_: boxed ?? { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: fallback, loc }], + result: dynUndefinedExpr(loc), + type: DYN, + loc, + }, + type: DYN, + loc, + }; + answer = + fallback.type.kind === "void" ? result : lowerer.coerceToExpected(result, fallback.type); + } + // Read after observedSlot built the prototype data and its originals. + const originalId = observed ? original?.originalId() : undefined; + if (original && originalId !== undefined) { + // The slot still holds the method's original value: calling it is + // calling the method, so the call stays native (no boxed arguments, + // dynamic call or checked result). A replacement takes the dynamic call. + const unchanged: IrExpr = { + kind: "libCall", + fn: "dyn.sameValue", + args: [varRef(callback.id, DYN, loc), varRef(originalId, DYN, loc)], + type: BOOL, + loc, + }; + answer = + fallback.type.kind === "void" + ? { + kind: "ternary", + cond: unchanged, + then: { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: original.directCall(), loc }], + result: dynUndefinedExpr(loc), + type: DYN, + loc, + }, + else_: call, + type: DYN, + loc, + } + : { + kind: "ternary", + cond: unchanged, + then: original.directCall(), + else_: answer, + type: answer.type, + loc, + }; + } return { kind: "seqExpr", stmts: [ diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index 8a4a1e417..e394718d1 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -908,6 +908,11 @@ export class ClassDynamicDispatch { private readonly prototypeClasses = new Set(); private prototypeIdentityHelper: IrFunction | null = null; private readonly prototypeIdentityClasses = new Set(); + /** `value instanceof Error` through Error.prototype: boxed instances of + * classes with a heritage evaluated at run time test their prototype + * data's chain. */ + private errorPrototypeHelper: IrFunction | null = null; + private readonly errorPrototypeClasses = new Set(); private readonly instancePrototypes = new Map }>(); private readonly checkedCasts = new Map }>(); /** The for-in key helpers, by libCall (dyn and native string[] keys). */ @@ -1114,6 +1119,49 @@ export class ClassDynamicDispatch { }); changed = true; } + if ( + this.errorPrototypeHelper && + !this.errorPrototypeClasses.has(className) && + hasRuntimeHeritage(info) + ) { + this.errorPrototypeClasses.add(className); + const loc = this.errorPrototypeHelper.loc; + const type: IrType = { kind: "object", className }; + const value = varRef("value", DYN, loc); + const prototype = classPrototypeData(lowerer, info, loc, { + kind: "dynCheck", + value, + type, + loc, + }); + if (prototype) + this.errorPrototypeHelper.body.unshift({ + kind: "if", + cond: { + kind: "libCall", + fn: "dyn.typedRefIs", + args: [value, { kind: "strLit", value: typeKey(type), type: STRING, loc }], + type: BOOL, + loc, + }, + then: [ + { + kind: "return", + value: { + kind: "libCall", + fn: "dyn.inheritsErrorPrototype", + args: [prototype], + type: BOOL, + loc, + }, + loc, + }, + ], + else_: null, + loc, + }); + changed = true; + } if ( this.serializesClasses && !this.jsonPrototypes.has(className) && @@ -1516,6 +1564,31 @@ export class ClassDynamicDispatch { } return { kind: "call", callee: cast.fn.name, args: [expr.value], type, loc: expr.loc }; } + if (expr.kind === "libCall" && expr.fn === "dyn.inheritsErrorPrototype") { + if (!this.errorPrototypeHelper) { + const loc = expr.loc; + this.errorPrototypeHelper = { + name: "%dyn.class.inheritsErrorPrototype", + params: [{ localId: "value", name: "value", type: DYN }], + locals: [{ id: "value", name: "value", type: DYN, mutable: false }], + returnType: BOOL, + loc, + body: [ + { kind: "return", value: { ...expr, args: [varRef("value", DYN, loc)] }, loc }, + ], + }; + lowerer.liftedFns.push(this.errorPrototypeHelper); + this.generated.add(this.errorPrototypeHelper); + changed = true; + } + return { + kind: "call", + callee: this.errorPrototypeHelper.name, + args: expr.args, + type: BOOL, + loc: expr.loc, + }; + } if ( expr.kind === "libCall" && expr.fn === "dyn.getPrototype" && @@ -5942,3 +6015,11 @@ const INHERITED_OBJECT_METHODS: ReadonlySet = new Set([ "hasOwnProperty", "propertyIsEnumerable", ]); + +/** Whether a class or one of its bases has a heritage evaluated at run + * time, whose prototype chain may reach Error.prototype. */ +function hasRuntimeHeritage(info: ClassInfo): boolean { + for (let current: ClassInfo | null = info; current; current = current.base) + if (current.callableBase) return true; + return false; +} diff --git a/packages/compiler/src/frontend/lowering/class-method-mutations.ts b/packages/compiler/src/frontend/lowering/class-method-mutations.ts index abbaf3a75..fd23b6db8 100644 --- a/packages/compiler/src/frontend/lowering/class-method-mutations.ts +++ b/packages/compiler/src/frontend/lowering/class-method-mutations.ts @@ -380,6 +380,13 @@ export function collectClassMethodMutations( markTarget(node.initializer, node); if (ts.isPropertyAccessExpression(node) && isErrorPrototypeValue(lowerer, node)) lowerer.errorPrototypeValues = true; + // A heritage evaluated at run time may be Error, which links the + // class's instances to Error.prototype. + if ( + (ts.isClassDeclaration(node) || ts.isClassExpression(node)) && + runtimeHeritage(lowerer, node) + ) + lowerer.errorPrototypeValues = true; if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) { const base = unwrap(node.expression); const prototype = @@ -621,3 +628,22 @@ export function collectClassMethodMutations( if (!receiverFields.has(field)) lowerer.receiverFreeCallbackFields.add(field); } } + +/** Whether a class's `extends` expression is a value computed at run time: + * anything other than a name of a class declaration or of a builtin + * constructor (`extends Parent` over `const Parent = params.Parent ?? Object`, + * `extends make()`). */ +function runtimeHeritage( + lowerer: Lowerer, + node: ts.ClassDeclaration | ts.ClassExpression, +): boolean { + const heritage = node.heritageClauses?.nodes.find( + (clause) => clause.token === ts.SyntaxKind.ExtendsKeyword, + )?.types?.nodes[0]?.expression; + if (heritage === undefined) return false; + if (!ts.isIdentifier(heritage) && !ts.isPropertyAccessExpression(heritage)) return true; + let symbol = lowerer.checker.getSymbolAtLocation(heritage); + if (symbol === undefined) return true; + if (symbol.flags & ts.SymbolFlags.Alias) symbol = lowerer.checker.getAliasedSymbol(symbol); + return !(symbol.flags & ts.SymbolFlags.Class) && !lowerer.isStdlibSymbol(symbol); +} diff --git a/packages/compiler/src/frontend/lowering/class-prototypes.ts b/packages/compiler/src/frontend/lowering/class-prototypes.ts index 2a05cf977..3ecdd84aa 100644 --- a/packages/compiler/src/frontend/lowering/class-prototypes.ts +++ b/packages/compiler/src/frontend/lowering/class-prototypes.ts @@ -207,12 +207,21 @@ export function classPrototypeData( // promise-returning signature) gets no data slot. const boxedValue = lowerer.coerceToExpected(compiled, DYN); if (boxedValue.type.kind !== "dyn") continue; + const originalId = `%g.${info.def.prototypeDataHelper}.original.${method}`; + lowerer.globalsList.push({ + id: originalId, + name: `${info.def.jsName ?? info.def.name}.prototype.${method}.original`, + type: DYN, + mutable: true, + }); + info.prototypeOriginals ??= new Map(); + info.prototypeOriginals.set(method, originalId); const descriptor: IrExpr = { kind: "dynObjLit", fields: [ { key: { kind: "strLit", value: "value", type: STRING, loc }, - value: boxedValue, + value: varRef(originalId, DYN, loc), }, { key: { kind: "strLit", value: "writable", type: STRING, loc }, @@ -227,6 +236,8 @@ export function classPrototypeData( loc, }; const init = helper.body[0]!; + if (init.kind === "if") + init.then.push({ kind: "assign", localId: originalId, value: boxedValue, loc }); if (init.kind === "if") init.then.push({ kind: "exprStmt", diff --git a/packages/compiler/src/frontend/lowering/coercions/structural.ts b/packages/compiler/src/frontend/lowering/coercions/structural.ts index c3b4ad906..4321a5ead 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural.ts @@ -288,6 +288,14 @@ export function applyWidthLift( case "dynIn": { if (dst.kind !== "dyn") throw new InternalCompilerError("lowerer bug: dynIn lift against a non-dyn slot"); + // The declaration-typed boundary keeps user values' identity when + // they cross into package code (decl-projection.ts). + if ( + lowerer.declBoundary && + !/[\\/]node_modules[\\/]/.test(loc.file) && + liveBoxable(lowerer, value.type) + ) + return { kind: "dynFrom", value, liveRef: true, type: DYN, loc }; return { kind: "dynFrom", value, type: DYN, loc }; } case "dynOut": @@ -775,3 +783,18 @@ function buildRecordProjection( loc, }; } + +/** Records and arrays (and unions holding them), whose identity a live + * capsule keeps. */ +function liveBoxable(lowerer: Lowerer, t: IrType, seen = new Set()): boolean { + if (t.kind === "record") { + const shape = lowerer.shapes.get(t.shapeId); + return shape !== undefined && !shape.tuple; + } + if (t.kind === "array") return true; + if (t.kind !== "union" || seen.has(t.unionId)) return false; + seen.add(t.unionId); + return ( + lowerer.unions.get(t.unionId)?.arms.some((arm) => liveBoxable(lowerer, arm, seen)) ?? false + ); +} diff --git a/packages/compiler/src/frontend/lowering/containers/array-methods.ts b/packages/compiler/src/frontend/lowering/containers/array-methods.ts index 44182fbda..3ab2e84e2 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-methods.ts @@ -361,6 +361,21 @@ export function lowerArrayMethodCall( if (probe.type.kind === "array") { receiverIr = probe.type; probedUntyped = true; + } else if ( + probe.type.kind === "dyn" && + !isJsSourceFile(ts.getSourceFileOfNode(access)) && + lowerer.isStdlibMember(access) + ) { + // An `unknown[]` (or `any[]`) array stored checked-dynamic — a + // generic body instantiated at `unknown`, a spread of unknown + // elements: the method runs on the runtime array (dynInvoke, JS + // exact), and a typed result is checked like any member read. + const dispatched = lowerDynDispatchMethodCall(lowerer, call, access, probe, true); + if (!dispatched) return null; + const result = lowerer.mapTypeOf(lowerer.typeOf(call)); + return result && result.kind !== "void" && result.kind !== "dyn" + ? lowerer.coerceInto(call, dispatched, result) + : dispatched; } } if (receiverIr?.kind === "union") { diff --git a/packages/compiler/src/frontend/lowering/dispatch-sites.ts b/packages/compiler/src/frontend/lowering/dispatch-sites.ts index c8a8d6ba6..f5b8ebdb2 100644 --- a/packages/compiler/src/frontend/lowering/dispatch-sites.ts +++ b/packages/compiler/src/frontend/lowering/dispatch-sites.ts @@ -12,10 +12,11 @@ import { toPrimitiveHint } from "./class-dynamic-dispatch.js"; import { GENERATOR_STEP_METHODS, HELPER_METHODS } from "./generator-dynamic-dispatch.js"; /** Runtime entry points that step a checked-dynamic iterable's iterator: - * destructuring rest, and the collection constructors' and - * Object.fromEntries' seeds. */ + * destructuring rest, the collection constructors' and Object.fromEntries' + * seeds, and Math.min/Math.max over a spread. */ const RUNTIME_STEP_LIB_FNS: ReadonlySet = new Set([ "dyn.destructureRest", + "dyn.mathFold", "dyn.nativeSetNew", "dyn.mapSeedEntries", "dyn.fromEntries", @@ -80,6 +81,7 @@ const DISPATCH_LIB_CALLS = new Set([ "dyn.propertyIsEnumerableComputed", "dyn.defineProperty", "dyn.getPrototype", + "dyn.inheritsErrorPrototype", "dyn.reflectGet", "dyn.reflectSet", ]); diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index ff7848930..48106949e 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -1,4 +1,5 @@ /* Identifier references in expression position: lowerExprInner's identifier branch and its helpers. */ +import { predeclareForwardClass } from "../lower-stmts.js"; import { cjsMemberAliasValueOf } from "../cjs-exports-object.js"; import { builtinClassValue, builtinMemberClassKind } from "../builtins/builtin-class-dyn.js"; import * as ts from "../../ts7/adapter.js"; @@ -860,6 +861,12 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: { const sym = lowerer.resolveValueSymbol(expr); const classInfo = sym ? lowerer.classBySymbol.get(sym) : undefined; + // A local class referenced from a function hoisted above its statement: + // its TDZ box, holding the class as a checked-dynamic constructor. + if (!classInfo && sym && predeclareForwardClass(lowerer, sym)) { + const local = lowerer.resolveLocal(expr); + if (local) return { kind: "varRef", localId: local.id, type: local.type, loc }; + } // A GENERIC class name whose CONTEXTUAL type pins one instantiation // (`const B: new (v: string) => Counter = Counter`): the // value is that instantiation's class object — the generic-fn diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index acac99610..fa8ad686f 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -13,6 +13,7 @@ import { STRING, type SrcLoc, UNDEFINED_T, + VOID, arrayOf, canAdaptDynFuncTo, canDynCheckTo, @@ -47,6 +48,7 @@ import { import { isConstAssertionTypeNode, unitOnlyUnion } from "../../type-mapper.js"; import { assertsGlobalObject, extractionCopiesReference } from "../assertion-identity.js"; import { iterableSlotValue, iterableUnionOf } from "../iterable-values.js"; +import { isDeclBoundaryTypeNode } from "../../decl-projection.js"; /** `c ? a : b`. An explicit destination constructs each branch at that * layout before joining it. Without one, retain the expression's inferred @@ -1516,6 +1518,12 @@ export function lowerAsExpression( const target = lowerer.mapTypeOf(targetTs) ?? dynFallbackType(lowerer, expr.type, targetTs); if (!target) lowerer.badType(expr.type, targetTs); if (target.kind === "dyn") return inner; // `as unknown`: erasure + // The declaration-typed boundary (decl-projection.ts): an identity- + // preserving checked conversion of a value package code produced. + if (isDeclBoundaryTypeNode(expr.type)) { + const adopted = declBoundaryCast(lowerer, inner, target, locOf(expr)); + if (adopted) return adopted; + } // An all-`unknown`-fields record target (`err as { code?: unknown }` // — the errno-probing idiom): there is nothing to validate (every // field is unknown, exactly what the dyn value already answers) and @@ -1603,3 +1611,83 @@ export function lowerAsExpression( } return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) }; } + +/** True when a type holds a record or an array somewhere (values whose + * identity can be observed). */ +function holdsReference(lowerer: Lowerer, t: IrType, seen = new Set()): boolean { + if (t.kind === "record" || t.kind === "array") return true; + if (t.kind !== "union" || seen.has(t.unionId)) return false; + seen.add(t.unionId); + return ( + lowerer.unions.get(t.unionId)?.arms.some((arm) => holdsReference(lowerer, arm, seen)) ?? false + ); +} + +/** `( as __scriptc_decl_N)`: check the dynamic value against + * the projected type and build the native value (dynCheck), then adopt: + * every source object or array that something else still references + * becomes a live capsule of the value built from it + * (scr_dyn_decl_adopt), so identity and mutation visibility hold on both + * sides. Scalar targets are ordinary checked casts. */ +function declBoundaryCast( + lowerer: Lowerer, + inner: IrExpr, + target: IrType, + loc: SrcLoc, +): IrExpr | null { + if ( + !canDynCheckTo( + target, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) && + !(target.kind === "union" && lowerer.dynConvertible(target)) + ) + return null; + if (!holdsReference(lowerer, target)) { + return { kind: "dynCheck", value: inner, type: target, loc }; + } + const source = lowerer.declareHiddenLocal("%boundarySource", DYN); + const value = lowerer.declareHiddenLocal("%boundaryValue", target); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: source.id, init: inner, loc }, + { + kind: "varDecl", + localId: value.id, + init: { + kind: "dynCheck", + value: varRef(source.id, DYN, loc), + adopt: true, + type: target, + loc, + }, + loc, + }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.declAdopt", + args: [ + varRef(source.id, DYN, loc), + { + kind: "dynFrom", + value: varRef(value.id, target, loc), + liveRef: true, + type: DYN, + loc, + }, + ], + type: VOID, + loc, + }, + loc, + }, + ], + result: varRef(value.id, target, loc), + type: target, + loc, + }; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts index 4b8c5692a..104d85a80 100644 --- a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts +++ b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts @@ -1,3 +1,4 @@ +import { isNavigatorGlobal } from "../builtins/navigator.js"; import { isSafeToDiscard } from "./evaluation-safety.js"; import { completeFuncValueArgs } from "../call-arguments.js"; import { dynUndefinedExpr } from "../../../ir/build.js"; @@ -223,6 +224,16 @@ export function lowerOptionalChain( // Native handle methods have no first-class value to guard; when the // method read itself has no lowering, re-dispatch the plain call (which // reports its own diagnostics) instead of failing on the value read. + // `navigator?.userAgent`: Node's navigator global is never nullish and + // has no value form, so the guarded step is the plain member read. + if (isNavigatorGlobal(lowerer, recvNode)) { + lowerer.chainHandled.add(dotNode); + try { + return lowerer.lowerExpr(expr); + } finally { + lowerer.chainHandled.delete(dotNode); + } + } let loweredReceiver: IrExpr | null; if (dotNode === expr && alwaysPresentStdlibMethod(lowerer, recvNode)) { loweredReceiver = tryLowerExpression(lowerer, recvNode); diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 301d6bd11..48b48e780 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -307,10 +307,10 @@ export function lowerPropertyAccessExpr( }; } } - if (!expr.questionDotToken) { - const navigator = lowerNavigatorMember(lowerer, expr, NODE_COMPAT_VERSION); - if (navigator) return navigator; - } + // Node's navigator is never nullish, so `navigator?.userAgent` (zod's + // Cloudflare probe) folds like `navigator.userAgent`. + const navigator = lowerNavigatorMember(lowerer, expr, NODE_COMPAT_VERSION); + if (navigator) return navigator; // util.inspect.custom: Symbol.for('nodejs.util.inspect.custom'). if (isInspectCustomAccess(lowerer, expr)) { return { @@ -564,6 +564,19 @@ export function lowerPropertyAccessExpr( expr.name.text, ) ) { + // A name the program stores on the global object + // (`(_a = globalThis).__config ?? (_a.__config = {})`) reads back from + // the global object's own properties. + const globalType = lowerer.typeOf(staticallySelected(lowerer, expr.expression)); + if (globalThisWrittenNames(lowerer, globalType).has(expr.name.text)) { + return { + kind: "dynKeyGet", + value: lowerer.coerceToExpected(lowerer.lowerExpr(expr.expression), DYN), + key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, + type: DYN, + loc, + }; + } return dynUndefinedExpr(loc); } // `Uint8Array.prototype.toBase64` and its siblings, the base64/hex @@ -4502,3 +4515,29 @@ function contextualArgumentCount(lowerer: Lowerer, fn: ts.Node): number | null { } return count; } + +/** The property names the program assigns on the global object, through + * `globalThis` or a binding of its type (`(_a = globalThis).x = v`). */ +function globalThisWrittenNames(lowerer: Lowerer, globalType: ts.Type): Set { + const known = lowerer.globalThisWrittenNames; + if (known) return known; + const names = new Set(); + lowerer.globalThisWrittenNames = names; + for (const sf of lowerer.program.getImplementationSourceFiles()) { + if (sf.isDeclarationFile || !sf.text.includes("globalThis")) continue; + ts.walkPreorder(sf, (node) => { + if ( + ts.isBinaryExpression(node) && + (node.operatorToken.kind === ts.SyntaxKind.EqualsToken || + node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionEqualsToken || + node.operatorToken.kind === ts.SyntaxKind.BarBarEqualsToken || + node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken) && + ts.isPropertyAccessExpression(node.left) && + lowerer.typeOf(node.left.expression) === globalType + ) + names.add(node.left.name.text); + return undefined; + }); + } + return names; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 0eb8ae653..b731e4912 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -51,6 +51,7 @@ import { isJsSourceFile, locOf } from "../../program.js"; import { checkedClassInstanceOf } from "../class-construction.js"; import { unsupportedDiag } from "../../../diagnostics/diagnostic.js"; import { globalSymbolKey } from "./global-symbols.js"; +import { builtinGlobalHasKey } from "../builtins/global-namespace-keys.js"; import { foldedStringKeyOf } from "./object-literals.js"; import { NARROW_FIRST, sideEffectFreeOptionValue } from "../surfaces.js"; import { isExecExceptionType, refuseExecExceptionCode } from "../builtins/errors.js"; @@ -1430,6 +1431,16 @@ export function lowerInExpression( "'in' with computed (non-literal) keys (a const whose type is one string literal folds; a string-typed key answers over index-signature record receivers)", ); } + // A feature probe on a builtin constructor global (`"captureStackTrace" + // in Error`): the global is a fixed object, so the answer is static. + { + let recvNode = expr.right; + while (ts.isParenthesizedExpression(recvNode)) recvNode = recvNode.expression; + if (ts.isIdentifier(recvNode) && lowerer.isStdlibGlobal(recvNode, recvNode.text)) { + const has = builtinGlobalHasKey(recvNode.text, key); + if (has !== null) return { kind: "boolLit", value: has, type: BOOL, loc }; + } + } // A receiver re-read (the static folds below) is safe for plain reads // AND for side-effect-free expressions (an object literal of literals // — the `"a" in { a: true }` shape). diff --git a/packages/compiler/src/frontend/lowering/key-order-witness.ts b/packages/compiler/src/frontend/lowering/key-order-witness.ts index 4adfb87ab..df4e747d6 100644 --- a/packages/compiler/src/frontend/lowering/key-order-witness.ts +++ b/packages/compiler/src/frontend/lowering/key-order-witness.ts @@ -227,6 +227,11 @@ export function applyKeyOrderWitnesses(lowerer: Lowerer, fns: readonly IrFunctio const opaqueSeen = new Set(); const runtimeShapes = new Set(); const runtimeSeen = new Set(); + /** Shapes the declaration-typed boundary converts into (adopting casts): + * their values come from package code that builds them in the + * declaration's order, the order their table holds. */ + const declaredShapes = new Set(); + const declaredSeen = new Set(); const producedBy = (type: IrType, opaque: boolean): void => { if (opaque) shapesIn(type, opaqueShapes, opaqueSeen); else shapesIn(type, runtimeShapes, runtimeSeen); @@ -332,7 +337,8 @@ export function applyKeyOrderWitnesses(lowerer: Lowerer, fns: readonly IrFunctio producedBy(e.type, true); return; case "dynCheck": - if (!orderPreservingClone(e)) producedBy(e.type, false); + if (e.adopt === true) shapesIn(e.type, declaredShapes, declaredSeen); + else if (!orderPreservingClone(e)) producedBy(e.type, false); return; case "dynDestrCheck": case "caughtCheck": @@ -392,6 +398,7 @@ export function applyKeyOrderWitnesses(lowerer: Lowerer, fns: readonly IrFunctio f.opaque = true; } for (const id of runtimeShapes) factsOf(id).runtime = true; + for (const id of declaredShapes) if (!runtimeShapes.has(id)) factsOf(id).pinned = true; // ── scope ─────────────────────────────────────────────────────────── // Only plain declared-field shapes with a table take part. diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 902155ddc..50ee7c5d2 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -2430,6 +2430,20 @@ function lowerBigIntConstructorValue(lowerer: Lowerer, arg: IrExpr, loc: SrcLoc) return { kind: "libCall", fn: "bigint.parse", args: [arg], type: BIGINT_T, loc }; if (arg.type.kind === "f64") return { kind: "libCall", fn: "bigint.fromF64", args: [arg], type: BIGINT_T, loc }; + // Byte storage converts through ToPrimitive, which BigInt then parses: a + // typed array's text is its elements joined with "," (one element reads + // as that element, none as 0n, several throw the SyntaxError for the + // joined text), a Buffer's is its UTF-8 decoding and a DataView's is + // "[object DataView]". The runtime value tells them apart, as for String. + if (arg.type.kind === "bytes") { + const text: IrExpr = { + kind: "toString", + operand: { kind: "dynFrom", value: arg, type: DYN, loc }, + type: STRING, + loc, + }; + return { kind: "libCall", fn: "bigint.parse", args: [text], type: BIGINT_T, loc }; + } if (arg.type.kind === "bool") { return { kind: "ternary", @@ -2470,6 +2484,7 @@ function lowerBigIntConstructorValue(lowerer: Lowerer, arg: IrExpr, loc: SrcLoc) arm.kind === "string" || arm.kind === "f64" || arm.kind === "bool" || + arm.kind === "bytes" || isUnitType(arm), ) ) @@ -3827,18 +3842,22 @@ function lowerDynReceiverMethodCall( } if (access.name.text === "flatMap") return lowerDynArrayFlatMapCall(lowerer, call, access, recv); // String methods claim only names NO other dyn-representable kind's - // prototype declares (Array carries includes/indexOf/slice too): for - // these, "the receiver is a string, or the call throws V8's TypeError" - // IS Node's semantics for every possible dyn value. Shared names would - // need a receiver-kind dispatch — they keep the fence. + // prototype declares (Array carries includes/indexOf/slice too), so no + // other kind inherits them. Shared names would need a receiver-kind + // dispatch — they keep the fence. if (DYN_STRING_ONLY_METHODS.has(access.name.text) && !ownCallable) { - // An object-typed receiver the member is missing from (a JavaScript - // module object whose members are assigned in a function body, - // `common.repeat(" ", n)`) holds a string or an object with that - // member: dispatch on the receiver's runtime kind. + // A checked-dynamic object can still carry its OWN member of that + // name: zod's schemas assign `trim`, `toLowerCase` and `startsWith` + // in their constructors (`z.string().trim().min(1)`), an object's + // prototype can define `trim`, and a JavaScript module object can + // assign `repeat` in a function body (`common.repeat(" ", n)`, an + // object-typed receiver the member is missing from). An untyped + // receiver, or a member the checker cannot type, dispatches on the + // receiver's runtime kind: a string runs the string lowering, anything + // else calls its member as Node does. if ( - !checkerUntyped && - (lowerer.typeOf(access).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 && + (checkerUntyped || + (lowerer.typeOf(access).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) && !call.questionDotToken && !access.questionDotToken && !call.arguments.nodes.some(ts.isSpreadElement) @@ -4290,6 +4309,7 @@ const DYN_DISPATCH_METHODS = new Set([ "shift", "unshift", "slice", + "splice", "at", "indexOf", "lastIndexOf", @@ -8297,6 +8317,12 @@ function lowerObjectStaticCall( ...(fnName !== null ? { fnName } : {}), }; } + // A class value (`Object.defineProperty(Definition, "name", { value })`) + // boxes to its constructor function, whose own-property table holds the + // definition for every alias of the class. + if (target?.type.kind === "classval" && lowerer.dynConvertible(target.type)) { + target = { kind: "dynFrom", value: target, type: DYN, loc: locOf(call.arguments.nodes[0]!) }; + } const descriptorPrimitive = member === "getOwnPropertyDescriptor" && target && @@ -10214,6 +10240,23 @@ export function lowerObjectMethodCall( type: found.sig.ret, loc, }; + // The declaring class's original value, built with the observed slot. + // An override below (a virtual call) keeps the dynamic call: a slot + // reassigned to an ancestor's method must not reach the override. So do + // methods declared in unchecked JavaScript, whose results are inferred, + // and calls with a spread argument: the dynamic call drains the spread + // through the iterator's current `next`, which may be a replacement. + const declarerFile = found.declarer.decl ? ts.getSourceFileOfNode(found.declarer.decl) : null; + const original = + virtual || + call.arguments.nodes.some(ts.isSpreadElement) || + !declarerFile || + (isJsSourceFile(declarerFile) && !lowerer.checkedJsFile(declarerFile)) + ? undefined + : { + originalId: (): string | undefined => found.declarer.prototypeOriginals?.get(method), + directCall: (): IrExpr => fallback, + }; const result = classCallbackCall( lowerer, value, @@ -10222,6 +10265,7 @@ export function lowerObjectMethodCall( fallback, loc, ts.getTextOfNode(access), + original, ); return { kind: "seqExpr", diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 8a87d7d8f..3fa9fba5a 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -5,6 +5,7 @@ import { isDatePrototypeMethod, lowerDateSuperMethodCall, } from "./builtins/date-classes.js"; +import { predeclareForwardClass } from "./lower-stmts.js"; import { lowerDateStaticCall } from "./builtins/dates.js"; import { keyedHelperName } from "./stable-names.js"; import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js"; @@ -478,6 +479,10 @@ export interface ClassInfo { prototypeMethodValues?: Map; prototypeReflectionReady?: true; prototypeObservedMethodsReady?: true; + /** The globals holding each observed method's original prototype value + * (set when the prototype data is built), by method name. A typed call + * whose slot still holds that value calls the method natively. */ + prototypeOriginals?: Map; prototypeReflectionRefusal?: string; localClass?: { context: FnCtx | null; @@ -2356,6 +2361,7 @@ export function collectClassShapeInner( const checkedStatics = isJsSourceFile(ts.getSourceFileOfNode(decl)) && ((base && hasRuntimeStatics(base)) || + definedThroughReflection(lowerer, decl) || decl.members.nodes.some( (member) => ts.isAccessor(member) && @@ -3209,15 +3215,6 @@ export function collectClassShapeInner( symbolMethods.set(key.identity, mName); } } - // Error values held as untyped values render through their error - // encoding, which keeps no compiled methods to call. - if (mName === INSPECT_CUSTOM_MEMBER && errorRooted) { - lowerer.unsupported( - "SC1090", - member.name, - "[util.inspect.custom] methods on Error subclasses", - ); - } // Generator calls use their spawn wrapper. Public methods are // direct calls only when no subclass overrides the method; // override chains involving generators fence below. @@ -5489,7 +5486,12 @@ export function classValueRef(lowerer: Lowerer, info: ClassInfo, blame: ts.Node) const display = info.def.name.replace(/^%|^%m\d+\./, ""); fenceDecorationThrows(lowerer, info, blame); if (info.localClass && ts.isIdentifier(blame)) { - const stored = lowerer.resolveLocal(blame); + let stored = lowerer.resolveLocal(blame); + // A reference from a function hoisted above the class statement. + if (!stored) { + const symbol = lowerer.resolveValueSymbol(blame); + if (symbol && predeclareForwardClass(lowerer, symbol)) stored = lowerer.resolveLocal(blame); + } if (stored) return lowerer.coerceToExpected( { kind: "varRef", localId: stored.id, type: stored.type, loc: locOf(blame) }, @@ -11285,3 +11287,37 @@ function genericFieldFnNodeOf( } return null; } + +/** Whether a JavaScript class's own binding is the target of + * `Object.defineProperty`/`defineProperties` in its file + * (`Object.defineProperty(Definition, "name", { value })`): its constructor + * then needs a live own-property table. */ +function definedThroughReflection(lowerer: Lowerer, decl: ts.ClassLikeDeclaration): boolean { + const name = decl.name; + const sf = ts.getSourceFileOfNode(decl); + if (name === undefined || !sf.text.includes("definePropert")) return false; + const symbol = lowerer.checker.getSymbolAtLocation(name); + if (symbol === undefined) return false; + let found = false; + ts.walkPreorder(sf, (node) => { + if (found) return "stop"; + if ( + ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression) && + (node.expression.name.text === "defineProperty" || + node.expression.name.text === "defineProperties") && + lowerer.stdlibGlobalMember(node.expression, "Object") !== null + ) { + const target = node.arguments.nodes[0]; + if ( + target !== undefined && + ts.isIdentifier(target) && + target.text === name.text && + lowerer.checker.getSymbolAtLocation(target) === symbol + ) + found = true; + } + return undefined; + }); + return found; +} diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 0ba923c56..94da4c6a5 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -1375,8 +1375,15 @@ function customInspectDynHelper( export function customInspectRegistrations(lowerer: Lowerer, loc: SrcLoc): IrStmt[] { const stmts: IrStmt[] = []; for (const info of lowerer.classes.values()) { - if (info.def.runtime || info.def.abstract || isErrorClass(lowerer, info.def.name)) continue; + if (info.def.runtime || info.def.abstract) continue; + // Error classes register when their own properties render statically + // (fieldedErrorReason): the others keep no method to call. + if (isErrorClass(lowerer, info.def.name) && fieldedErrorReason(lowerer, info) !== null) + continue; if (!lowerer.findMethodOn(info, INSPECT_CUSTOM_MEMBER)) continue; + // Only constructed classes have instances, and only their methods lower + // (the constructor edge reaches the method). + if (!lowerer.classCanBeConstructed(info)) continue; if (customInspectReason(lowerer, info) !== null) continue; const t = { kind: "object" as const, className: info.def.name }; const fn = customInspectDynHelper(lowerer, t, loc); diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index 0beb24a0e..e671a0a08 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -2496,6 +2496,25 @@ export function lowerStdlibMethodCall( // method, drains into the number[] the fold reads. src = classGeneratorIterable(lowerer, src) ?? src; if (src.type.kind === "generator") src = generatorDrain(lowerer, src, F64, loc) ?? src; + // An untyped iterable (`Math.min(...caps.map(f))` in JavaScript): the + // runtime packs its values and folds their ToNumber like Node. V8 names + // a null or undefined source by its text, calls as `f(...)`. + if (src.type.kind === "dyn") { + const what = ts.isCallExpression(spread.expression) + ? `${ts.getTextOfNode(spread.expression.expression)}(...)` + : ts.getTextOfNode(spread.expression); + return { + kind: "libCall", + fn: "dyn.mathFold", + args: [ + src, + { kind: "boolLit", value: name === "max", type: BOOL, loc }, + { kind: "strLit", value: what, type: STRING, loc }, + ], + type: F64, + loc, + }; + } if (src.type.kind !== "array" || src.type.elem.kind !== "f64") { lowerer.unsupported( "SC1090", diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 88dde49a9..66ecea602 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1010,6 +1010,44 @@ export function predeclareForwardCapture(lowerer: Lowerer, symbol: ts.Symbol): b return false; } +/** The CLASS-DECLARATION twin of predeclareForwardCapture: a function + * hoisted above a local `class D` (`function make() { return new D(); }` + * reached through an earlier function) captures the class binding, which + * is in its temporal dead zone until the class statement runs. The class + * cannot be collected yet (its heritage may name bindings declared between + * here and the statement), so the binding is pre-declared as a TDZ box + * holding the class as a checked-dynamic constructor; the class statement + * later initializes it, and every reference reads it from there. */ +export function predeclareForwardClass(lowerer: Lowerer, symbol: ts.Symbol): boolean { + const decl = symbol.valueDeclaration; + if (!decl || !ts.isClassDeclaration(decl) || decl.name === undefined) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) return false; + for (let i = lowerer.activeStmtLists.length - 1; i >= 0; i--) { + const entry = lowerer.activeStmtLists[i]!; + const idx = entry.stmts.indexOf(decl); + if (idx < 0) continue; + if (idx < entry.index || entry.ctx === lowerer.ctx || entry.ctx.tdzPredeclared.has(symbol)) + return false; + const name = decl.name.text; + const count = entry.ctx.localCounters.get(name) ?? 0; + entry.ctx.localCounters.set(name, count + 1); + const local: IrLocal = { + id: `${name}.${count}`, + name, + type: DYN, + mutable: false, + boxed: true, + tdz: true, + }; + entry.ctx.locals.push(local); + entry.frame.set(symbol, local); + entry.out.push({ kind: "varDecl", localId: local.id, init: null, loc: locOf(decl) }); + entry.ctx.tdzPredeclared.set(symbol, local); + return true; + } + return false; +} + /** The FUNCTION-DECLARATION twin of predeclareForwardCapture: JS hoists a * nested `function f() {}` to scope entry — the binding is LIVE from the * first statement, so a reference lexically above the declaration in the @@ -1579,6 +1617,14 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt if (ts.isClassDeclaration(stmt)) { if (!stmt.name) lowerer.unsupported("SC1090", stmt, "anonymous local class declarations"); const info = lowerer.lowerClassExpressionInfo(stmt); + // An earlier hoisted function captured the class: its TDZ box exists. + const symbol = lowerer.checker.getSymbolAtLocation(stmt.name); + const pre = symbol ? lowerer.ctx.tdzPredeclared.get(symbol) : undefined; + if (pre) { + lowerer.ctx.tdzPredeclared.delete(symbol!); + const value = lowerer.coerceToExpected(lowerer.lowerClassExpression(stmt), DYN); + return { kind: "assign", localId: pre.id, value, initializes: true, loc: locOf(stmt) }; + } const type: IrType = { kind: "classval", className: info.def.name }; const local = lowerer.declareLocal(stmt.name, stmt.name.text, type, true); // Static members of a function-local class run while the class value is diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index e35f5564f..c37ae9074 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -5683,7 +5683,10 @@ export function lowerStreamMethodCall( // argument reaches an object-mode side's _write as passed, null when // the callback took its place (a byte-mode side encodes a string // chunk with it). - const writeEnc = (encArg: ts.Expression | undefined, cbArg: ts.Expression | undefined) => { + const writeEnc = ( + encArg: ts.Expression | undefined, + cbArg: ts.Expression | undefined, + ): IrExpr => { const receiver = streamReceiverExpr(lowerer, access.expression); const chunk = lowerer.lowerExprExpecting(args[0]!, DYN); const tail: IrExpr[] = []; @@ -5696,7 +5699,7 @@ export function lowerStreamMethodCall( args: [receiver, numLit(flags, loc), chunk, ...tail], type: BOOL, loc, - } satisfies IrExpr; + }; }; if (kind === "dyn") { if (args.length === 3) return writeEnc(args[1], args[2]); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index cdd661c5b..6987a1071 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,4 +1,6 @@ +import { isDeclBoundaryFile } from "../decl-projection.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; +import { moduleNamespaceObject } from "./module-namespace-elements.js"; import { keyedHelperName } from "./stable-names.js"; import { positionTag, StructuralPositions } from "./structural-positions.js"; import { onlyForwarded, stripTypeWrappers } from "./typed-arguments.js"; @@ -164,7 +166,7 @@ import { import { DeferredModuleInitializers } from "./deferred-module-initializers.js"; import { finalizeClassMethodValues } from "./class-method-values.js"; import { finalizePrivateBrands } from "./private-brands.js"; -import { type ClassSymbolKey, tableKeyFieldType } from "./symbol-fields.js"; +import { type ClassSymbolKey, INSPECT_CUSTOM_MEMBER, tableKeyFieldType } from "./symbol-fields.js"; import type { HttpClientFnBinding } from "./lower-server.js"; export type { WidthLift } from "./width-lift.js"; export { newFnCtx, type FnCtx } from "./function-context.js"; @@ -184,7 +186,7 @@ export { newFnCtx, type FnCtx } from "./function-context.js"; */ import { dirname, relative, resolve } from "node:path"; import { bindingSource } from "../binding-source.js"; -import { tsgoPath } from "../dts-paths.js"; +import { compilerOwnedFileName, tsgoPath } from "../dts-paths.js"; import * as ts from "../ts7/adapter.js"; import { isBundledShimForwarder } from "../package-shims.js"; import { erasedTypeOnlyImport } from "../type-only-imports.js"; @@ -2412,6 +2414,51 @@ export class Lowerer { return fallback ?? null; } + /** A fresh TypeScript data literal (`{ message: "m" }`) passed to a + * checked-dynamic parameter of a callee implemented in JavaScript, whose + * contract lets it reshape the object: a call through an untyped value, an + * untyped parameter of a JavaScript function, or a package-declared + * parameter type (a non-library `.d.ts`) that makes one of the literal's + * properties optional (zod's `normalizeParams` deletes `params.message`). + * No typed alias of a fresh literal exists, so it is built as the plain + * object the callee receives instead of a view whose record layout refuses + * that delete. Standard-library and TypeScript callees, and literals with + * methods or accessors, keep their native storage. */ + private reshapeableContextualLiteral( + literal: ts.ObjectLiteralExpression, + contextual: readonly ts.Type[], + ): boolean { + const call = literal.parent; + if ( + call === undefined || + !(ts.isCallExpression(call) || ts.isNewExpression(call)) || + !call.arguments?.nodes.includes(literal) || + literal.properties.nodes.length === 0 || + !literal.properties.nodes.every( + (prop) => + (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) && + (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)), + ) + ) + return false; + const declaration = this.checker.getResolvedSignature(call)?.declaration; + const file = declaration ? ts.getSourceFileOfNode(declaration) : undefined; + const untyped = contextual.some((part) => part.flags & ts.TypeFlags.Any); + if (file === undefined) return untyped; + if (isJsSourceFile(file)) return untyped; + if (!file.isDeclarationFile || this.isStdlibFile(file)) return false; + if (untyped) return true; + const objects = contextual.filter((part) => part.flags & ts.TypeFlags.Object); + if (objects.length === 0) return false; + return literal.properties.nodes.some((prop) => { + const name = (prop.name as ts.Identifier | ts.StringLiteral).text; + return objects.every((part) => { + const symbol = this.checker.getPropertyOfType(part, name); + return symbol !== undefined && (symbol.flags & ts.SymbolFlags.Optional) !== 0; + }); + }); + } + /** A TypeScript object literal handed to a JavaScript checked-value * parameter whose inferred type is open (`constructor({ factory, ...options * } = {})` — the rest element admits any property): the literal names a @@ -2975,9 +3022,14 @@ export class Lowerer { * ABI and shared prototype descriptors instead of call-site specialization. */ readonly prototypeMethodAccesses = new Map(); /** Whether the program reads `Error.prototype`, the checked-dynamic - * object an ES5 constructor function can inherit from: only then can a - * value that is not an Error instance be `instanceof Error`. */ + * object an ES5 constructor function can inherit from, or has a class + * whose heritage is evaluated at run time (`extends (Base ?? Error)`): + * only then can a value that is not an Error instance be + * `instanceof Error`. */ errorPrototypeValues = false; + /** The property names the program assigns on the global object + * (property-access.ts), collected on first use. */ + globalThisWrittenNames: Set | null = null; /** Well-known symbol methods (`sym:iterator`) the program may replace * through a symbol-keyed write: an instance or prototype property, or a * reflection call. */ @@ -3204,13 +3256,14 @@ export class Lowerer { /** Structural source positions (structural-positions.ts) for names that * stay the same when unrelated code moves. */ /** Files are named by their path relative to the entry's directory, so - * names do not depend on where the program sits, and default library - * files by their base name: two checkers install them in different - * directories. */ + * names do not depend on where the program sits, default library files + * by their base name (two checkers install them in different + * directories), and the files scriptc ships by their name inside scriptc, + * so names do not depend on where scriptc is installed either. */ readonly positions = new StructuralPositions((sf) => this.program.isSourceFileDefaultLibrary(sf) ? `lib:${sf.fileName.slice(sf.fileName.lastIndexOf("/") + 1)}` - : relative(dirname(this.entry.fileName), sf.fileName), + : (compilerOwnedFileName(sf.fileName) ?? relative(dirname(this.entry.fileName), sf.fileName)), ); /** The outermost node whose token span is exactly `loc`, or null. */ @@ -3242,7 +3295,10 @@ export class Lowerer { * before it. */ closureName(loc: SrcLoc, suffix = "", prefix = "%fn"): string { const node = this.nodeAtLoc(loc); - const where = node === null ? `${loc.file}:${loc.start}:${loc.end}` : this.positions.of(node); + const where = + node === null + ? `${compilerOwnedFileName(loc.file) ?? loc.file}:${loc.start}:${loc.end}` + : this.positions.of(node); const key = `${prefix}|${this.closureRoot}|${where}|${suffix}`; const first = keyedHelperName(this, prefix, key); const seen = this.closureNameCounts.get(first) ?? 0; @@ -3383,8 +3439,18 @@ export class Lowerer { if (interned.has(fn.name)) this.sharedHelpers.add(fn); if (this.sharedHelpers.size === 0) return; const shared = new Map([...this.sharedHelpers].map((fn) => [fn.name, fn])); - const earlier = (left: SrcLoc, right: SrcLoc): boolean => - left.file < right.file || (left.file === right.file && left.start < right.start); + // Files scriptc ships compare by their name inside scriptc, so the + // choice does not depend on where scriptc is installed. + const fileKeys = new Map(); + const fileKey = (file: string): string => { + let key = fileKeys.get(file); + if (key === undefined) fileKeys.set(file, (key = compilerOwnedFileName(file) ?? file)); + return key; + }; + const earlier = (left: SrcLoc, right: SrcLoc): boolean => { + if (left.file === right.file) return left.start < right.start; + return fileKey(left.file) < fileKey(right.file); + }; // The earliest use of each shared helper outside the shared helpers, and // which shared helpers each shared helper uses. const uses = new Map(); @@ -3569,6 +3635,18 @@ export class Lowerer { else if (kind === "failed") s.failed += 1; } + /** The program holds the declaration-typed boundary's projection + * (decl-projection.ts): user-file record and array values crossing into + * dynamic slots (package parameters) box as live capsules, so a value + * that comes back through a boundary cast keeps its identity. */ + private declBoundaryMemo: boolean | null = null; + get declBoundary(): boolean { + this.declBoundaryMemo ??= this.program + .getSourceFiles() + .some((sf) => isDeclBoundaryFile(sf.fileName)); + return this.declBoundaryMemo; + } + constructor( readonly program: ts.Program, readonly entry: ts.SourceFile, @@ -3691,8 +3769,11 @@ export class Lowerer { classImplementedProtocol: (type) => this.classImplementedProtocol(type), isStdlibFile: this.isStdlibFile, isNpmFile: this.isNpmFile, + // The declaration-typed boundary's generated types (decl-projection.ts) + // describe data, like an explicitly mapped external type surface. isExternalTypeFile: (sf) => - this.externalTypeSpecifiersByFile.has(tsgoPath(resolve(sf.fileName))), + this.externalTypeSpecifiersByFile.has(tsgoPath(resolve(sf.fileName))) || + isDeclBoundaryFile(sf.fileName), typeMemo: this.typeMemo, ...(this.generalNodeStreams ? { generalNodeStreams: true } : {}), ...(this.dynNodeStreams ? { dynNodeStreams: true } : {}), @@ -7712,6 +7793,7 @@ export class Lowerer { entry: ENTRY_NAME, ...(this.usesWorkers ? { workers: true } : {}), ...(this.usesLifetimeRegions ? { lifetimeRegions: true as const } : {}), + ...(this.declBoundary ? { declBoundary: true as const } : {}), ...(this.threadSlotKeys.size > 0 ? { threadSlotKeys: [...this.threadSlotKeys].sort() } : {}), @@ -8061,6 +8143,19 @@ export class Lowerer { this.constructedClasses.add(name.slice(1, -".constructor".length)); for (const [owner, methods] of this.virtualEdges) for (const method of methods) this.noteVirtualEdge(owner, method); + // An instance's [util.inspect.custom] method runs from the runtime + // inspect walk (customInspectRegistrations), which no IR edge shows. + for ( + let c = this.classes.get(name.slice(1, -".constructor".length)) ?? null; + c; + c = c.base + ) { + const custom = `%${c.def.name}.${INSPECT_CUSTOM_MEMBER}`; + if (units.has(custom)) { + this.onEdge?.(custom); + break; + } + } } }; // Class EXPRESSIONS collect while init bodies lower (below): their @@ -9452,6 +9547,22 @@ export class Lowerer { } if (expr.type.kind === "void" && expected.kind !== "void" && this.neverValued.has(expr)) return this.divergentValue(expr, expected); + // A builtin error constructor global stored in a slot of its class value + // type (`{ Parent: Error }`): the class value itself, which a checked + // cast of the dynamic constructor value could not produce. + if ( + expected.kind === "classval" && + expr.kind === "libCall" && + expr.fn === "dyn.globalConstructor" && + expr.args[0]?.kind === "strLit" && + `%${expr.args[0].value}` === expected.className + ) { + const info = this.classes.get(expected.className); + if (info?.builtinError && info.valueCtorParams !== undefined) { + this.defineClassValueConstruct(info); + return { kind: "classRef", className: info.def.name, type: expected, loc: expr.loc }; + } + } // process.stdout/stderr into a general Writable slot (general-streams.ts). const stdioStream = stdioIntoGeneralStream(this, expr, expected); if (stdioStream) return this.coerceToExpected(stdioStream, expected); @@ -9558,6 +9669,13 @@ export class Lowerer { // constructor. `require("events")` is the EventEmitter class itself, // so its module binding is too (`ReleaseEmitter = EventEmitter` over // `const EventEmitter = require("events")`). + if (expr.type.kind === "moduleNs" && isJsSourceFileName(expr.loc.file)) { + const object = moduleNamespaceObject( + this, + expr as IrExpr & { type: IrType & { kind: "moduleNs" } }, + ); + if (object) return object; + } if ( expr.type.kind === "moduleNs" && expr.type.moduleId === "builtin:events" && @@ -10564,6 +10682,46 @@ export class Lowerer { return this.coerceInto(node, value, expected); } + /** The right side of a write to a member of a typed view of the global + * object (`(globalThis as typeof globalThis & { k?: T }).k = value`, also + * through `??=` and `||=`) that is `value` itself or wraps only it in + * parentheses or `satisfies`; null when `value` is not in such a write. */ + private globalViewSlot(value: ts.Expression): ts.Expression | null { + let operand: ts.Expression = value; + while ( + operand.parent !== undefined && + (ts.isParenthesizedExpression(operand.parent) || ts.isSatisfiesExpression(operand.parent)) + ) + operand = operand.parent; + const write = operand.parent; + if ( + write === undefined || + !ts.isBinaryExpression(write) || + write.right !== operand || + (write.operatorToken.kind !== ts.SyntaxKind.EqualsToken && + write.operatorToken.kind !== ts.SyntaxKind.QuestionQuestionEqualsToken && + write.operatorToken.kind !== ts.SyntaxKind.BarBarEqualsToken) + ) + return null; + let target = write.left; + while (ts.isParenthesizedExpression(target)) target = target.expression; + if (!ts.isPropertyAccessExpression(target) && !ts.isElementAccessExpression(target)) + return null; + const receiver = this.typeOf(target.expression); + const isGlobal = (type: ts.Type): boolean => { + const symbol = type.getSymbol(); + return ( + symbol !== undefined && + ts.symbolName(symbol) === "globalThis" && + symbol.declarations.length === 0 + ); + }; + return isGlobal(receiver) || + (receiver.isIntersectionType() && ts.constituentTypes(receiver).some(isGlobal)) + ? operand + : null; + } + /** Lower for a known destination, then apply ordinary value coercion. * Fresh arrays and records use the destination layout directly. */ lowerExprExpecting(node: ts.Expression, expected: IrType | undefined): IrExpr { @@ -10631,6 +10789,21 @@ export class Lowerer { (element) => !ts.isSpreadElement(element) && !ts.isOmittedExpression(element), ) ) { + // A checked slot that the program declares as a native array (a + // member of a typed view of the global object, `typeof globalThis & + // { cache?: number[] }`) is read back through that declaration. Build + // the native array and store it boxed: every typed read then extracts + // that same array (a dynamic literal would be copied on each read, + // losing writes such as push). + const slot = this.globalViewSlot(x); + const slotType = slot ? this.checker.getContextualType(slot) : undefined; + const declared = slotType && this.checker.getNonNullableType(slotType); + const native = + declared && !(declared.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) + ? this.mapTypeOf(declared) + : null; + if (native?.kind === "array") + return this.coerceInto(node, this.lowerArrayLiteral(x, native), DYN); return { kind: "dynArrLit", elems: x.elements.nodes.map((element) => this.lowerExprExpecting(element, DYN)), @@ -10660,6 +10833,7 @@ export class Lowerer { builtinOption("URLFormatOptions") || !this.mapTypeOf(this.typeOf(x)) || this.excessJsContextualProperty(x, parts) || + this.reshapeableContextualLiteral(x, parts) || holdsEvolvingArray(this, x) ) { return lowerDynObjectLiteral(this, x); diff --git a/packages/compiler/src/frontend/lowering/module-namespace-elements.ts b/packages/compiler/src/frontend/lowering/module-namespace-elements.ts index d2425750b..cf828c6f0 100644 --- a/packages/compiler/src/frontend/lowering/module-namespace-elements.ts +++ b/packages/compiler/src/frontend/lowering/module-namespace-elements.ts @@ -1,6 +1,13 @@ import { dynUndefinedExpr, strLit, varRef } from "../../ir/build.js"; import * as ts from "../ts7/adapter.js"; -import { DYN, STRING, type IrExpr, type IrFunction, type IrStmt } from "../../ir/ir.js"; +import { + DYN, + STRING, + type IrExpr, + type IrFunction, + type IrStmt, + type IrType, +} from "../../ir/ir.js"; import { locOf } from "../program.js"; import { newFnCtx, type Lowerer } from "./lowerer.js"; @@ -25,12 +32,13 @@ export function lowerModuleNamespaceElement( try { const body: IrStmt[] = []; const symbol = lowerer.checker.getSymbolAtLocation(source); - symbol?.getExports().forEach((exported, key) => { + for (const exported of symbol ? lowerer.checker.getExportsOfModule(symbol) : []) { + const key = ts.symbolName(exported); const target = exported.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(exported) : exported; - if (!(target.flags & ts.SymbolFlags.Value)) return; + if (!(target.flags & ts.SymbolFlags.Value)) continue; const declaration = target.valueDeclaration; const identifier = declaration?.name as ts.Node | undefined; if (!identifier || !ts.isIdentifier(identifier)) { @@ -43,7 +51,7 @@ export function lowerModuleNamespaceElement( kind: "strEq", negated: false, left: varRef("key", STRING, loc), - right: strLit(String(key), loc), + right: strLit(key, loc), type: { kind: "bool" }, loc, }, @@ -51,7 +59,7 @@ export function lowerModuleNamespaceElement( else_: null, loc, }); - }); + } body.push({ kind: "return", value: dynUndefinedExpr(loc), loc }); const fn: IrFunction = { name, @@ -88,3 +96,78 @@ export function lowerModuleNamespaceElement( }; return { kind: "call", callee: name, args: [receiver, text], type: DYN, loc }; } + +/** A module namespace taken as an untyped value (`{ ...ns }`, `{ iso: ns }` + * in JavaScript): an object holding each value export, keyed in the + * namespace's sorted key order. The copy is exact while every export is a + * binding that never changes after initialization (const, function or + * class); null for other namespaces, which keep their fence. */ +export function moduleNamespaceObject( + lowerer: Lowerer, + ns: IrExpr & { type: IrType & { kind: "moduleNs" } }, +): IrExpr | null { + const source = lowerer.sourceFileOfModuleNamespace(ns.type); + if (!source) return null; + const symbol = lowerer.checker.getSymbolAtLocation(source); + if (!symbol) return null; + const exports: { key: string; identifier: ts.Identifier }[] = []; + let exact = true; + // The module's resolved exports, which include `export *` members. + for (const exported of lowerer.checker.getExportsOfModule(symbol)) { + const key = ts.symbolName(exported); + const target = + exported.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(exported) : exported; + if (!(target.flags & ts.SymbolFlags.Value)) continue; + const declaration = target.valueDeclaration; + const identifier = declaration?.name as ts.Node | undefined; + const immutable = + declaration !== undefined && + (ts.isFunctionDeclaration(declaration) || + ts.isClassDeclaration(declaration) || + (ts.isVariableDeclaration(declaration) && + (ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const) !== 0)); + if (!identifier || !ts.isIdentifier(identifier) || !immutable) { + exact = false; + break; + } + exports.push({ key, identifier }); + } + if (!exact) return null; + exports.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)); + const name = `%module.object:${ns.type.moduleId}`; + const loc = ns.loc; + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + const context = newFnCtx(false, null, null, DYN); + const previousClass = lowerer.currentClass; + lowerer.currentClass = null; + lowerer.fnStack.push(context); + try { + const fields = exports.map(({ key, identifier }) => ({ + key: strLit(key, loc), + value: lowerer.lowerExprExpecting(identifier, DYN), + })); + if (fields.some((field) => field.value.type.kind !== "dyn")) return null; + const fn: IrFunction = { + name, + params: [], + returnType: DYN, + locals: [...context.locals], + body: [{ kind: "return", value: { kind: "dynObjLit", fields, type: DYN, loc }, loc }], + loc, + }; + lowerer.liftedFns.push(fn); + } finally { + lowerer.fnStack.pop(); + lowerer.currentClass = previousClass; + } + } + const call: IrExpr = { kind: "call", callee: name, args: [], type: DYN, loc }; + if (ns.kind === "moduleNsRef") return call; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: ns, loc }], + result: call, + type: DYN, + loc, + }; +} diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index 193e8adc0..6da81dfbc 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -685,8 +685,8 @@ function loadFrontend( ...finalLoad.program.getImplementationSourceFiles().filter((sf) => !sf.isDeclarationFile), ].map((sf) => [sf.fileName, sf.text]), ), - lower: (opts) => - lowerToIr(finalLoad.program, finalLoad.entry, finalLoad.moduleOrder, { + lower: (opts) => { + const lowered = lowerToIr(finalLoad.program, finalLoad.entry, finalLoad.moduleOrder, { ...opts, frontendServices: finalLoad.services, startupCrash: finalLoad.startupCrash ?? null, @@ -695,7 +695,13 @@ function loadFrontend( projectCheckJs: finalLoad.typeWorld?.projectCheckJs ?? false, externalTypes: finalLoad.externalTypes, externalTypeSpecifiersByFile: finalLoad.externalTypeSpecifiersByFile, - }), + }); + // The declaration-typed boundary's notes (SC6102) join the + // performance warnings; they never fail a build. + const notes = finalLoad.declBoundaryNotes; + if (notes === undefined) return lowered; + return { ...lowered, performance: [...notes, ...(lowered.performance ?? [])] }; + }, npmStatic: statuses, npmImportSites: npmSites, dispose: finalLoad.dispose, diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 507d3392d..68b649b07 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -139,6 +139,7 @@ import { typeOnlyReachedFiles, } from "./type-only-imports.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; +import { planDeclProjection, type DeclProjection } from "./decl-projection.js"; import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js"; import { containsJsx, @@ -386,6 +387,9 @@ export interface LoadResult { * checkPreflight. */ startupCrash?: StartupCrash | null; configDiags: ScrDiagnostic[]; + /** SC6102 performance notes of the declaration-typed boundary + * (decl-projection.ts): files whose projection was dropped. */ + declBoundaryNotes?: ScrDiagnostic[]; /** The files whose JSX the program lowers (jsx-source.ts), by file name. * The program holds their rewritten text; positions in diagnostics refer * to it. */ @@ -397,6 +401,9 @@ export interface LoadResult { /** The program's pre-emit diagnostics, computed once. */ diagnostics: readonly ts.Diagnostic[]; projectWorld: ts.Program | null; + /** The declaration-typed boundary rewrote user files: errors fall + * back to the declaration world (load.projectWorld()), as without it. */ + declWorldFallback?: true; }; /** How scriptc's type world differs from the project's own tsc setup — * used only to label the TypeScript errors those differences cause. */ @@ -846,6 +853,9 @@ function loadProgram7( externalTypes: ReadonlyMap = new Map(), /** The --npm-static lowering world: root opted-in packages' deep edges. */ rootNpmStaticEdges = false, + /** The declaration-typed boundary's projection (decl-projection.ts): + * answers rewritten user files for the lowering world, or null. */ + projector?: (program: ts.Program) => DeclProjection | null, ): LoadResult & { disposeAll: () => void } { const config = adoptProjectConfig7(host, entryPath); const configuredPaths = config.options["paths"]; @@ -925,6 +935,7 @@ function loadProgram7( // lowering compiles holds them with their JSX lowered. const jsx = lowerProgramJsx7(program, options); let tscWorld: LoadResult["tscWorld"]; + const declBoundaryNotes: ScrDiagnostic[] = []; if (jsx.refusal !== null) { config.diags.push(jsx.refusal); } else if (jsx.sources.size > 0) { @@ -940,6 +951,51 @@ function loadProgram7( }; for (const [file, lowered] of jsx.sources) host.addVirtualFile(file, lowered.text); program = host.createProgram([...programRoots, overridesDtsPath()], options); + } else if (projector !== undefined) { + // The declaration-typed boundary: user files typed from package + // declarations (decl-projection.ts). The tsc gate reads the original + // text; the lowering compiles the rewritten files. + const projection = projector(program); + if (projection !== null) { + const original = program; + const baseline = new Map(); + for (const file of projection.files.keys()) { + const sf = original.getSourceFile(file); + if (sf !== undefined) baseline.set(file, original.getSemanticDiagnostics(sf).length); + } + for (const [file, text] of projection.files) host.addVirtualFile(file, text); + host.addVirtualFile(projection.declFile, projection.declText); + programRoots.push(projection.declFile); + program = host.createProgram([...programRoots, overridesDtsPath()], options); + // A rewrite the lowering world disagrees with (new errors) is + // dropped for that file: the file lowers as it would without it. + const dropped: string[] = []; + for (const file of projection.files.keys()) { + const sf = program.getSourceFile(file); + if (sf === undefined) continue; + if (program.getSemanticDiagnostics(sf).length > (baseline.get(file) ?? 0)) { + dropped.push(file); + host.addVirtualFile(file, original.getSourceFile(file)!.text); + } + } + if (dropped.length > 0) { + program.dispose(); + program = host.createProgram([...programRoots, overridesDtsPath()], options); + for (const file of dropped) declBoundaryNotes.push(declBoundaryDroppedDiag(file)); + } + if (dropped.length === projection.files.size) { + program.dispose(); + programRoots.pop(); + program = original; + } else { + tscWorld = { + program: original, + diagnostics: ts.getPreEmitDiagnostics(original), + projectWorld: null, + declWorldFallback: true, + }; + } + } } const entry = program.getSourceFile(entryPath); if (!entry) throw new Error(`could not load ${entryPath}`); @@ -950,6 +1006,7 @@ function loadProgram7( entry, moduleOrder: [], configDiags: config.diags, + ...(declBoundaryNotes.length > 0 ? { declBoundaryNotes } : {}), typeWorld: { nodeFallback: nodeTypes === null, projectLib: config.projectLib ?? null, @@ -1156,31 +1213,69 @@ export function loadProgram( (npmShadow?.hideFile(path) ?? false) || projectDtsRuntimeSibling(path) !== null, }; const host = services.createProgramHost({ cwd: dirname(entryPath), fsShadow }); + let authoredHost: ts.Ts7Host | null = null; + let authoredLoad: ReturnType | null = null; + const authored = (): ReturnType => { + if (!authoredLoad) { + authoredHost = services.createProgramHost({ cwd: dirname(entryPath) }); + authoredLoad = loadProgram7(authoredHost, entryPath, externalTypes); + } + return authoredLoad; + }; + const declPackages = npmStaticPackages.filter((pkg) => { + const resolved = resolveBareModule(entryPath, pkg, "types-only"); + return resolved !== null && /\.d\.(?:ts|mts|cts)$/.test(resolved.typesFile); + }); + const projector = + declPackages.length === 0 + ? undefined + : (program: ts.Program): DeclProjection | null => { + const userFiles = declBoundaryUserFiles(program, declPackages); + if (userFiles.length === 0) return null; + let projection: DeclProjection | null; + try { + projection = planDeclProjection({ + jsProgram: program, + declProgram: authored().program, + userFiles, + outDir: dirname(entryPath), + }); + } catch (error) { + if (process.env["SCRIPTC_DEBUG_DECL_BOUNDARY"] === "1") throw error; + return null; + } + if (process.env["SCRIPTC_DEBUG_DECL_BOUNDARY"] === "1") { + process.stderr.write( + `[decl-boundary] ${projection === null ? "no projection" : projection.plan.join("\n[decl-boundary] ")}\n`, + ); + if (projection !== null) process.stderr.write(projection.declText); + } + return projection; + }; try { - const load = loadProgram7(host, entryPath, externalTypes, npmStaticPackages.length > 0); - let authoredHost: ts.Ts7Host | null = null; - let authoredLoad: ReturnType | null = null; + const load = loadProgram7( + host, + entryPath, + externalTypes, + npmStaticPackages.length > 0, + projector, + ); return { ...load, services, projectWorld: npmStaticPackages.length === 0 ? load.projectWorld - : () => { + : () => // Check authored code against the package's published declarations. // Runtime bodies still lower from JavaScript in the original world; // inference there must not manufacture errors in valid user code. - if (!authoredLoad) { - authoredHost = services.createProgramHost({ cwd: dirname(entryPath) }); - authoredLoad = loadProgram7(authoredHost, entryPath, externalTypes); - } - return authoredLoad.projectWorld(); - }, + authored().projectWorld(), dispose: () => { try { - authoredLoad?.disposeAll(); + (authoredLoad as ReturnType | null)?.disposeAll(); } finally { - authoredHost?.close(); + (authoredHost as ts.Ts7Host | null)?.close(); try { load.disposeAll(); } finally { @@ -1190,11 +1285,47 @@ export function loadProgram( }, }; } catch (error) { + (authoredLoad as ReturnType | null)?.disposeAll(); + (authoredHost as ts.Ts7Host | null)?.close(); host.close(); throw error; } } +/** User TypeScript files (outside node_modules) that import one of the + * packages whose declarations the boundary projects from. */ +function declBoundaryUserFiles(program: ts.Program, packages: readonly string[]): string[] { + const out: string[] = []; + for (const sf of program.getSourceFiles()) { + const name = sf.fileName.split("\\").join("/"); + if (sf.isDeclarationFile || name.includes("/node_modules/") || !/\.[mc]?tsx?$/.test(name)) + continue; + if (name.endsWith(".tsx")) continue; // JSX files: not projected yet + const text = sf.text; + if ( + packages.some( + (pkg) => + text.includes(`"${pkg}"`) || + text.includes(`'${pkg}'`) || + text.includes(`"${pkg}/`) || + text.includes(`'${pkg}/`), + ) + ) + out.push(sf.fileName); + } + return out; +} + +/** SC6102 — a projected file the lowering world disagreed with. */ +function declBoundaryDroppedDiag(file: string): ScrDiagnostic { + return { + code: "SC6102", + message: + "values from npm packages stay checked-dynamic in this file: the types projected from the packages' declarations do not type-check against their JavaScript", + loc: { file, start: 0, end: 0 }, + }; +} + /** tsc diagnostics (syntax + types), the supported-import fence, and the * module evaluation order: fills load.moduleOrder (the SourceFiles * themselves — the lowering consumes them directly) and returns the @@ -4801,7 +4932,9 @@ function preflight7(load: LoadResult): { tscWorld === undefined ? errorsOf(program) : keptErrors(tscWorld.program, tscWorld.diagnostics); if (tscErrors.length > 0) { const world = - tscWorld === undefined ? load.projectWorld() : (tscWorld.projectWorld ?? tscWorld.program); + tscWorld === undefined || tscWorld.declWorldFallback === true + ? load.projectWorld() + : (tscWorld.projectWorld ?? tscWorld.program); for (const d of errorsOf(world)) { const diag = toPassthrough(d); const lowered = tscWorld === undefined ? undefined : load.jsxSources?.get(diag.loc.file); diff --git a/packages/compiler/src/frontend/ts7/checker-backend.test.ts b/packages/compiler/src/frontend/ts7/checker-backend.test.ts index e37357afa..cd37bafff 100644 --- a/packages/compiler/src/frontend/ts7/checker-backend.test.ts +++ b/packages/compiler/src/frontend/ts7/checker-backend.test.ts @@ -24,15 +24,11 @@ function writePackage(directory: string, name: string, executable?: string): voi writeFileSync(join(directory, "lib", executable), ""); } -test("tsc-ts is the default checker except on Windows, and SCRIPTC_CHECKER selects one", () => { - expect(checkerBackend({}, "linux")).toBe("tsc-ts"); - expect(checkerBackend({ SCRIPTC_CHECKER: "" }, "linux")).toBe("tsc-ts"); - expect(checkerBackend({}, "darwin")).toBe("tsc-ts"); - expect(checkerBackend({}, "win32")).toBe("tsgo"); - expect(checkerBackend({ SCRIPTC_CHECKER: "" }, "win32")).toBe("tsgo"); - expect(checkerBackend({ SCRIPTC_CHECKER: "tsc-ts" }, "linux")).toBe("tsc-ts"); - expect(checkerBackend({ SCRIPTC_CHECKER: "tsc-ts" }, "win32")).toBe("tsc-ts"); - expect(checkerBackend({ SCRIPTC_CHECKER: "tsgo" }, "linux")).toBe("tsgo"); +test("tsc-ts is the default checker, and SCRIPTC_CHECKER selects one", () => { + expect(checkerBackend({})).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "" })).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "tsc-ts" })).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "tsgo" })).toBe("tsgo"); expect(() => checkerBackend({ SCRIPTC_CHECKER: "tsc" })).toThrow( 'SCRIPTC_CHECKER must be tsgo or tsc-ts, not "tsc"', ); diff --git a/packages/compiler/src/frontend/ts7/checker-backend.ts b/packages/compiler/src/frontend/ts7/checker-backend.ts index 14418cc94..0b32f24f6 100644 --- a/packages/compiler/src/frontend/ts7/checker-backend.ts +++ b/packages/compiler/src/frontend/ts7/checker-backend.ts @@ -4,17 +4,13 @@ import { fileURLToPath } from "node:url"; /** The TypeScript 7 API servers a compile can check with: tsc-ts, the default, * and tsgo from the `typescript` package, selected with `SCRIPTC_CHECKER=tsgo`. - * Both serve the same API and give the same results. On Windows the default is - * tsgo: the compiler connects to the server over a named pipe there, and tsc-ts's - * API server has no named-pipe transport yet. */ + * Both serve the same API and give the same results. On Windows the Node host + * connects to either over a named pipe the server creates (rpc-process.ts). */ export type CheckerBackend = "tsgo" | "tsc-ts"; -export function checkerBackend( - env: NodeJS.ProcessEnv = process.env, - platform: string = process.platform, -): CheckerBackend { +export function checkerBackend(env: NodeJS.ProcessEnv = process.env): CheckerBackend { const value = env["SCRIPTC_CHECKER"]; - if (value === undefined || value === "") return platform === "win32" ? "tsgo" : "tsc-ts"; + if (value === undefined || value === "") return "tsc-ts"; if (value === "tsc-ts") return "tsc-ts"; if (value === "tsgo") return "tsgo"; throw new Error(`SCRIPTC_CHECKER must be tsgo or tsc-ts, not ${JSON.stringify(value)}`); diff --git a/packages/compiler/src/frontend/ts7/checker.ts b/packages/compiler/src/frontend/ts7/checker.ts index 9550d1dc2..eb2b9e20b 100644 --- a/packages/compiler/src/frontend/ts7/checker.ts +++ b/packages/compiler/src/frontend/ts7/checker.ts @@ -631,6 +631,14 @@ export class CheckerFacade { return aliased; } + /** A module symbol's exports, with the members of its `export *` + * declarations resolved (a symbol's own export table lists only the + * module's own declarations). */ + getExportsOfModule(symbol: Ts7Symbol): readonly Ts7Symbol[] { + this.ensureActive(); + return this.raw.getExportsOfModule(symbol); + } + getDeclaredTypeOfSymbol(symbol: Ts7Symbol): Type { this.ensureActive(); let type = this.cache.declaredTypeOfSymbol.get(symbol); diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index f0f19a408..4f7b93cfd 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -126,6 +126,7 @@ import { moduleRuntimeFeatures } from "./ir/runtime-features.js"; import { serializeModuleParts } from "./ir/serialize.js"; import { readTextParts, writeTextParts } from "./text-parts.js"; import { validateModule } from "./ir/validate.js"; +import { compilerOwnedFileRoots } from "./frontend/dts-paths.js"; import { loadProgram } from "./frontend/program-node.js"; import { runFrontend, type FrontendFactory } from "./frontend/pipeline.js"; import { @@ -600,6 +601,7 @@ async function compileExecutableNative( ...(features.dynTier ? { dynTier: true } : {}), ...(features.streamDyn ? { streamDyn: true } : {}), ...(features.handleProps ? { handleProps: true } : {}), + ...(features.declAdopt ? { declAdopt: true } : {}), ...(features.dynForAwait ? { dynForAwait: true } : {}), ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), ...(features.dynBuffer ? { dynBuffer: true } : {}), @@ -785,7 +787,7 @@ async function prepareExecutableInput( if (outputKind === "ir") { await mkdir(dirname(opts.outPath), { recursive: true }); - await writeTextParts(opts.outPath, serializeModuleParts(mod)); + await writeTextParts(opts.outPath, serializeModuleParts(mod, compilerOwnedFileRoots())); return { ok: true, artifact: { kind: "ir", path: opts.outPath } }; } @@ -885,7 +887,7 @@ async function prepareExecutableInput( if (opts.emitIr) { irPath = defaultSourcePaths.ir; irSourcePath = privateSiblingPath(irPath, "ir"); - await writeTextParts(irSourcePath, serializeModuleParts(mod)); + await writeTextParts(irSourcePath, serializeModuleParts(mod, compilerOwnedFileRoots())); } const nativeFeatures = executableNativeFeatures(mod, backend, opts.optimization ?? "release"); @@ -1986,7 +1988,7 @@ async function emitSemanticLibraryHit( let irPath: string | undefined; if (opts.emitIr) { irPath = join(opts.outDir, `${stem}.lib.ir.json`); - await writeTextParts(irPath, serializeModuleParts(mod)); + await writeTextParts(irPath, serializeModuleParts(mod, compilerOwnedFileRoots())); } await compileLibraryNative(profile, llvmPath, archivePath, opts.sanitize ?? false, hit.native); timing("native-archive"); @@ -2272,7 +2274,7 @@ async function compileLibraryTracked( let irPath: string | undefined; if (opts.emitIr) { irPath = join(opts.outDir, `${stem}.lib.ir.json`); - await writeTextParts(irPath, serializeModuleParts(mod)); + await writeTextParts(irPath, serializeModuleParts(mod, compilerOwnedFileRoots())); } const nativeFeatures = libraryNativeFeatures(mod, profile.emission); diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 14a91f483..86b6ef03d 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -622,6 +622,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ // fence; the zero-argument form always throws ERR_MISSING_ARGS. The // DOMException clone shares the option validation. "dyn.structuredClone", + "dyn.declAdopt", "dyn.cloneMissing", "dyn.cloneTransferFail", "dyn.cloneTransferFailWith", @@ -683,6 +684,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "dyn.propertyIsEnumerableComputed", // the destructuring pack throws V8's TypeError on non-iterable dyn kinds "dyn.iterPack", + // ToNumber of each element can run valueOf + "dyn.mathFold", "dyn.destructureRest", "dyn.arrayFromIterator", "dyn.iterator", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 49f8eede7..4cf579da4 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -219,6 +219,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * not iterable (cannot read property Symbol(Symbol.iterator))"). In the * may-throw seed set. */ "dyn.iterPack": { argTypes: [DYN, STRING], result: DYN }, + "dyn.mathFold": { argTypes: [DYN, BOOL, STRING], result: F64 }, "dyn.destructureRest": { argTypes: [DYN, STRING], result: DYN }, "dyn.arrayFromIterator": { argTypes: [DYN], result: DYN }, "dyn.iterator": { argTypes: [DYN, STRING], result: DYN }, @@ -3776,6 +3777,14 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * call: always throws Node's TypeError [ERR_MISSING_ARGS] with Node's * own (verbatim, doubly-wrapped) message. */ "dyn.structuredClone": { argTypes: [DYN, DYN], result: DYN }, + /** The declaration-typed boundary's adoption (decl-projection.ts): + * `source` is the dynamic value a boundary cast just converted, and + * `capsule` a live capsule of the native value built from it. Every + * object or array of `source` that something else still references + * becomes, in place, a live capsule of the native value built from it. + * Throws the path TypeError, changing nothing, when a converted object + * is not plain data (a prototype, accessors, symbols, frozen state). */ + "dyn.declAdopt": { argTypes: [DYN, DYN], result: VOID }, "dyn.cloneMissing": { argTypes: [], result: DYN }, /** structuredClone with a NON-EMPTY transfer array of static values: * nothing static is transferable, so the call always throws Node's diff --git a/packages/compiler/src/ir/int32-arrays.ts b/packages/compiler/src/ir/int32-arrays.ts index bdb3f21e2..02de7268f 100644 --- a/packages/compiler/src/ir/int32-arrays.ts +++ b/packages/compiler/src/ir/int32-arrays.ts @@ -1,4 +1,6 @@ import { F64, type IrExpr, type IrFunction, type IrModule, type IrStmt } from "./ir.js"; + +type SwitchCase = Extract["cases"][number]; import { reparseExpr } from "./serialize.js"; import { everyExprChild, @@ -382,20 +384,26 @@ function rewriteCandidates( if (!call || simple(call.args[1]!)) return [s]; const { decl, ref } = bind(call.args[1]!); const read: IrExpr = { ...call, args: [call.args[0]!, ref] }; - return [decl, s.kind === "varDecl" ? { ...s, init: read } : { ...s, value: read }]; + if (s.kind === "varDecl") return [decl, { ...s, init: read }]; + return [decl, { ...s, value: read }]; }); + const hoistCase = (c: SwitchCase): SwitchCase => ({ ...c, body: hoist(c.body) }); const lists = (s: IrStmt): IrStmt => { switch (s.kind) { case "if": return { ...s, then: hoist(s.then), else_: s.else_ === null ? null : hoist(s.else_) }; case "block": + return { ...s, body: hoist(s.body) }; case "while": + return { ...s, body: hoist(s.body) }; case "doWhile": + return { ...s, body: hoist(s.body) }; case "for": + return { ...s, body: hoist(s.body) }; case "forOf": return { ...s, body: hoist(s.body) }; case "switch": - return { ...s, cases: s.cases.map((c) => ({ ...c, body: hoist(c.body) })) }; + return { ...s, cases: s.cases.map((c) => hoistCase(c)) }; case "tryCatch": return { ...s, @@ -425,7 +433,10 @@ function rewriteCandidates( }; } const call = readerCall(e); - if (!call) return e.kind === "seqExpr" ? { ...e, stmts: hoist(e.stmts) } : e; + if (!call) { + if (e.kind === "seqExpr") return { ...e, stmts: hoist(e.stmts) }; + return e; + } const helper = readers.get(call.callee)!; const array = call.args[0]!; const [arrayParam, indexParam] = helper.params; @@ -448,7 +459,9 @@ function rewriteCandidates( }, stmt: (x) => x, }); - const typed: IrExpr = { ...body, loc: e.loc }; + // The ternary is a fresh copy (reparseExpr): it takes the call's location. + const typed = body; + typed.loc = e.loc; return stmts.length === 0 ? typed : { kind: "seqExpr", stmts, result: typed, type: typed.type, loc: e.loc }; diff --git a/packages/compiler/src/ir/int32-slots.ts b/packages/compiler/src/ir/int32-slots.ts index c6396db0f..5e9a87319 100644 --- a/packages/compiler/src/ir/int32-slots.ts +++ b/packages/compiler/src/ir/int32-slots.ts @@ -1143,7 +1143,10 @@ export function analyzeInt32Slots(mod: IrModule, inputs: Int32SlotInputs = {}): const key = `l:${fn.name}\0${id}`; if (!slots.hasInt32ElementSlot(key) || values.every(int32)) continue; slots.dropInt32Elements(key); - const checkable = (value: IrExpr): boolean => int32(value) || ranges.get(value) != null; + const checkable = (value: IrExpr): boolean => { + const range = ranges.get(value); + return int32(value) || range != null; + }; if (values.every(checkable) && checkableInt32Allocations(fn, id)) slots.checkedInt32Arrays.add(`${fn.name}\0${id}`); else reason(`array:${fn.name}\0${id}`, "an element store is not int32"); diff --git a/packages/compiler/src/ir/integer-loops.test.ts b/packages/compiler/src/ir/integer-loops.test.ts index b6ac27e3a..2cca43a00 100644 --- a/packages/compiler/src/ir/integer-loops.test.ts +++ b/packages/compiler/src/ir/integer-loops.test.ts @@ -402,6 +402,16 @@ test("paired counters closing in on each other run as integers", () => { guarded: false, range: { min: 0, max: 9 }, }); + // `hi > lo` and `hi >= lo` are `lo < hi` and `lo <= hi`. + for (const op of [">", ">="] as const) { + const reversed = paired(number(0), number(9)); + reversed.cond = { kind: "bin", op, left: ref("hi"), right: ref("lo"), type: BOOL, loc }; + expect(matchIntegerPairedForLoop(reversed, vars)).toMatchObject({ + low: { localId: "lo", step: 1 }, + high: { localId: "hi", step: -1 }, + inclusive: op === ">=", + }); + } // A start without a known range is tested at run time. expect(matchIntegerPairedForLoop(paired(number(0), ref("head")), vars)).toMatchObject({ high: { startGuarded: true }, diff --git a/packages/compiler/src/ir/integer-loops.ts b/packages/compiler/src/ir/integer-loops.ts index f54f6618b..d6942718a 100644 --- a/packages/compiler/src/ir/integer-loops.ts +++ b/packages/compiler/src/ir/integer-loops.ts @@ -382,9 +382,9 @@ export function matchIntegerCountedForLoop( * * The first counter only grows and the second only shrinks, by positive * literal steps, the body writes neither, and the loop runs while the first - * is below (or at most) the second. Every body entry therefore holds both - * between the first start and the second start: exact integers when the - * starts are. A start without a proven integer range is tested at run time + * is below (or at most) the second, written either way round (`hi > lo`). + * Every body entry therefore holds both between the first start and the + * second start: exact integers when the starts are. A start without a proven integer range is tested at run time * on a versioned loop, as a counted loop's is. */ export interface IntegerPairedCounter { localId: string; @@ -412,10 +412,13 @@ export function matchIntegerPairedForLoop( cond = stmt.cond; if (init?.kind !== "block" || init.body.length !== 2) return null; if (update?.kind !== "block" || update.body.length !== 2) return null; - if (cond?.kind !== "bin" || (cond.op !== "<" && cond.op !== "<=")) return null; - if (cond.left.kind !== "varRef" || cond.right.kind !== "varRef") return null; - const lowId = cond.left.localId, - highId = cond.right.localId; + if (cond?.kind !== "bin" || cond.left.kind !== "varRef" || cond.right.kind !== "varRef") + return null; + const below = cond.op === "<" || cond.op === "<="; + if (!below && cond.op !== ">" && cond.op !== ">=") return null; + // `hi > lo` is `lo < hi`. + const lowId = below ? cond.left.localId : cond.right.localId, + highId = below ? cond.right.localId : cond.left.localId; if (lowId === highId) return null; const counter = (id: string): IntegerPairedCounter | null => { const decl = init.body.find((d) => d.kind === "varDecl" && d.localId === id); @@ -485,7 +488,7 @@ export function matchIntegerPairedForLoop( return { low, high, - inclusive: cond.op === "<=", + inclusive: cond.op === "<=" || cond.op === ">=", guarded, // A loop whose starts already fail the condition never enters the body. range: { diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index c9a316501..360a41c5f 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1044,6 +1044,10 @@ export interface IrModule { entry: string; /** Executable roots run in isolated native worker contexts. */ workers?: boolean; + /** The program has declaration-typed boundary casts (decl-projection.ts): + * closures boxed for package code return records and arrays as live + * capsules, so a value that comes back keeps its identity. */ + declBoundary?: true; /** The program constructs LifetimeRegion (@scriptc/experimental): every * ProgramLifetime instance lives in a counted region (the region.* * intrinsics; backend/llvm/regions.ts). */ @@ -3886,10 +3890,19 @@ export type IrExpr = /** `exact`: the value is known to be a capsule this program boxed from * exactly the class `type` names (a `dyn.typedRefKeyIs` test guards the * expression), so the check is one load of the capsule's instance. */ + /** `adopt`: the declaration-typed boundary's cast (decl-projection.ts). + * The conversion preserves identity: a source object or array that + * something else still references (reference count above one) is + * turned, after the whole check succeeded, into a live capsule of the + * value built from it, so every holder sees the same native value; an + * unshared source is simply consumed. Values must be plain data + * (ordinary objects and arrays without accessors, prototypes, symbols + * or frozen state), otherwise the cast throws the path TypeError. */ | { kind: "dynCheck"; value: IrExpr; preserveRefs?: true; + adopt?: true; exact?: true; type: IrType; loc: SrcLoc; diff --git a/packages/compiler/src/ir/paired-while-loops.test.ts b/packages/compiler/src/ir/paired-while-loops.test.ts new file mode 100644 index 000000000..e0a69afa3 --- /dev/null +++ b/packages/compiler/src/ir/paired-while-loops.test.ts @@ -0,0 +1,182 @@ +import { expect, test } from "vitest"; +import { + BOOL, + F64, + arrayOf, + type IrExpr, + type IrFunction, + type IrLocal, + type IrModule, + type IrStmt, +} from "./ir.js"; +import { runPairedWhileLoopsAsFor } from "./paired-while-loops.js"; + +const loc = { file: "paired-while-loops.ts", start: 0, end: 0 }; +const number = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc }); +const ref = (localId: string): IrExpr => ({ kind: "varRef", localId, type: F64, loc }); +const array = { kind: "varRef", localId: "a", type: arrayOf(F64), loc } as const; +const element = (index: IrExpr): IrStmt => ({ + kind: "exprStmt", + expr: { kind: "arrayGet", arr: array, index, type: F64, loc }, + loc, +}); +const step = (id: string, op: "+" | "-"): IrStmt => ({ + kind: "assign", + localId: id, + value: { kind: "bin", op, left: ref(id), right: number(1), type: F64, loc }, + loc, +}); +const declare = (id: string, init: IrExpr): IrStmt => ({ kind: "varDecl", localId: id, init, loc }); +const number_ = (id: string): IrLocal => ({ + id, + name: id.split(".")[0]!, + type: F64, + mutable: true, +}); + +function whileLoop( + body: IrStmt[] = [element(ref("lo.0")), element(ref("hi.0"))], + updates: IrStmt[] = [step("lo.0", "+"), step("hi.0", "-")], +): IrStmt & { kind: "while" } { + return { + kind: "while", + cond: { kind: "bin", op: "<", left: ref("lo.0"), right: ref("hi.0"), type: BOOL, loc }, + body: [...body, ...updates], + loc, + }; +} + +function program(body: IrStmt[], extra: Partial = {}): IrModule { + const fn: IrFunction = { + name: "f", + params: [{ localId: "n.0", name: "n", type: F64 }], + returnType: F64, + locals: [ + number_("n.0"), + number_("lo.0"), + number_("hi.0"), + { id: "a", name: "a", type: arrayOf(F64), mutable: false }, + ], + body, + loc, + ...extra, + }; + return { functions: [fn], globals: [] } as unknown as IrModule; +} + +const rewritten = (mod: IrModule): IrStmt[] => runPairedWhileLoopsAsFor(mod).functions[0]!.body; + +test("a two-counter while loop runs as a for loop over new counters", () => { + const mod = program([declare("lo.0", number(0)), declare("hi.0", ref("n.0")), whileLoop()]); + const out = runPairedWhileLoopsAsFor(mod).functions[0]!; + // The declarations stay; the loop's counters are new locals that start + // from them, and the trailing updates are the for loop's update. + expect(out.body.slice(0, 2)).toEqual(mod.functions[0]!.body.slice(0, 2)); + expect(out.body[2]).toMatchObject({ + kind: "for", + init: { + kind: "block", + body: [ + { kind: "varDecl", localId: "lo.1", init: { kind: "varRef", localId: "lo.0" } }, + { kind: "varDecl", localId: "hi.1", init: { kind: "varRef", localId: "hi.0" } }, + ], + }, + cond: { op: "<", left: { localId: "lo.1" }, right: { localId: "hi.1" } }, + update: { kind: "block", body: [{ localId: "lo.1" }, { localId: "hi.1" }] }, + body: [ + { expr: { kind: "arrayGet", index: { localId: "lo.1" } } }, + { expr: { kind: "arrayGet", index: { localId: "hi.1" } } }, + ], + }); + expect(out.locals.map((l) => l.id)).toEqual(["n.0", "lo.0", "hi.0", "a", "lo.1", "hi.1"]); +}); + +test("parameters, reversed comparisons, labels and either update order qualify", () => { + const loop = whileLoop(undefined, [step("hi.0", "-"), step("lo.0", "+")]); + loop.cond = { kind: "bin", op: ">=", left: ref("hi.0"), right: ref("lo.0"), type: BOOL, loc }; + loop.labels = ["outer"]; + const mod = program([declare("lo.0", number(0)), loop], { + params: [{ localId: "hi.0", name: "hi", type: F64 }], + }); + expect(rewritten(mod)[1]).toMatchObject({ + kind: "for", + labels: ["outer"], + cond: { op: ">=", left: { localId: "hi.1" }, right: { localId: "lo.1" } }, + update: { body: [{ localId: "hi.1" }, { localId: "lo.1" }] }, + }); +}); + +test("loops the for form would change, or whose counters are used elsewhere, stay", () => { + const decls = [declare("lo.0", number(0)), declare("hi.0", ref("n.0"))]; + const kept = (body: IrStmt[], extra: Partial = {}): void => { + const mod = program(body, extra); + expect(runPairedWhileLoopsAsFor(mod)).toBe(mod); + }; + // A continue that binds to the loop skips the trailing updates. + kept([...decls, whileLoop([{ kind: "continue", loc }, element(ref("lo.0"))])]); + const labeled = whileLoop([{ kind: "continue", label: "outer", loc }, element(ref("lo.0"))]); + labeled.labels = ["outer"]; + kept([...decls, labeled]); + // A counter read after the loop, written before it, or declared twice. + kept([...decls, whileLoop(), { kind: "return", value: ref("lo.0"), loc }]); + kept([...decls, step("hi.0", "-"), whileLoop()]); + kept([...decls, declare("lo.0", number(1)), whileLoop()]); + // A declaration outside an enclosing loop: a later entry would resume + // from the values the previous run left. + kept([ + ...decls, + { + kind: "while", + cond: { kind: "boolLit", value: true, type: BOOL, loc }, + body: [whileLoop()], + loc, + }, + ]); + // A parameter counter inside another loop, for the same reason. + const outerLoop: IrStmt = { + kind: "while", + cond: { kind: "boolLit", value: true, type: BOOL, loc }, + body: [whileLoop()], + loc, + }; + kept([outerLoop], { + params: [ + { localId: "lo.0", name: "lo", type: F64 }, + { localId: "hi.0", name: "hi", type: F64 }, + ], + }); + // A counter that was never declared, or that this function captures. + kept([declare("lo.0", number(0)), whileLoop()]); + kept([...decls, whileLoop()], { captures: [{ localId: "hi.0", name: "hi", type: F64 }] }); + // The body writes a counter, a counter is boxed, or no counter is an index. + kept([...decls, whileLoop([element(ref("lo.0")), step("hi.0", "-")])]); + const boxed = program([...decls, whileLoop()]); + boxed.functions[0]!.locals[2] = { ...number_("hi.0"), boxed: true }; + expect(runPairedWhileLoopsAsFor(boxed)).toBe(boxed); + kept([...decls, whileLoop([])]); +}); + +test("a continue in a nested loop keeps the outer loop eligible", () => { + const inner: IrStmt = { + kind: "while", + cond: { kind: "boolLit", value: false, type: BOOL, loc }, + body: [{ kind: "continue", loc }], + loc, + }; + // Literal starts have known ranges, so the loop needs no start test and + // may hold a nested loop. + const mod = program([ + declare("lo.0", number(0)), + declare("hi.0", number(9)), + whileLoop([inner, element(ref("lo.0"))]), + ]); + expect(rewritten(mod)[2]).toMatchObject({ kind: "for", body: [{ kind: "while" }, {}] }); + // A start without a known range is tested at run time, on a loop that + // may not nest another. + const guarded = program([ + declare("lo.0", number(0)), + declare("hi.0", ref("n.0")), + whileLoop([inner, element(ref("lo.0"))]), + ]); + expect(runPairedWhileLoopsAsFor(guarded)).toBe(guarded); +}); diff --git a/packages/compiler/src/ir/paired-while-loops.ts b/packages/compiler/src/ir/paired-while-loops.ts new file mode 100644 index 000000000..e0a994418 --- /dev/null +++ b/packages/compiler/src/ir/paired-while-loops.ts @@ -0,0 +1,307 @@ +import { matchIntegerPairedForLoop } from "./integer-loops.js"; +import { analyzeIntegerRanges, type IntegerRange } from "./integer-ranges.js"; +import { + F64, + type IrExpr, + type IrFunction, + type IrLocal, + type IrModule, + type IrStmt, +} from "./ir.js"; +import { everyExprChild, everyStmtChild, mapExprChildren, mapStmtChildren } from "./traverse.js"; + +type WhileLoop = IrStmt & { kind: "while" }; + +/** Two-counter while loops run as two-counter for loops. + * + * let lo = a, hi = b; + * while (lo < hi) { ...; lo++; hi--; } + * + * runs as `for (let l = lo, h = hi; l < h; l++, h--) { ... }`, whose + * counters the backend stores as integers (matchIntegerPairedForLoop). The + * loop qualifies when: + * - its condition compares the two counters (`<`, `<=`, `>` or `>=`); + * - its body ends with one update of each counter; + * - no `continue` in the body binds to it (a for loop's `continue` runs the + * update, a while loop's skips the body's last statements); + * - the function mentions neither counter outside the loop, except in one + * declaration or as a parameter; + * - every entry starts from the declared or passed values: a declaration is + * an earlier statement of the loop's own statement list, which runs before + * each entry, and a loop over a parameter is in no other loop. + * + * The for loop's counters are new locals that start from the counters' + * values on entry, so the declarations stay where they are and nothing + * between them and the loop moves. The original counters are not read + * after the loop, so they need no final value. A loop that the paired match + * would refuse is left as it is. */ +export function runPairedWhileLoopsAsFor(mod: IrModule): IrModule { + let changed = false; + const functions = mod.functions.map((fn) => { + const next = rewriteFunction(fn); + if (next !== fn) changed = true; + return next; + }); + return changed ? { ...mod, functions } : mod; +} + +/** The local a statement steps: `x++`, `x--` or `x = ...`. */ +function updatedLocal(s: IrStmt): string | null { + if (s.kind === "assign") return s.localId; + if (s.kind === "exprStmt" && s.expr.kind === "incDec") return s.expr.localId; + return null; +} + +/** The counters of a loop with the qualifying condition and trailing + * updates, or null. */ +function loopCounters(loop: WhileLoop, locals: ReadonlyMap): string[] | null { + const cond = loop.cond; + if (cond.kind !== "bin") return null; + if (cond.op !== "<" && cond.op !== "<=" && cond.op !== ">" && cond.op !== ">=") return null; + if (cond.left.kind !== "varRef" || cond.right.kind !== "varRef") return null; + const counters = [cond.left.localId, cond.right.localId]; + if (counters[0] === counters[1]) return null; + const n = loop.body.length; + if (n < 2) return null; + const updated = [updatedLocal(loop.body[n - 2]!), updatedLocal(loop.body[n - 1]!)]; + if (!counters.every((id) => updated.includes(id))) return null; + for (const id of counters) { + const local = locals.get(id); + if (local?.type.kind !== "f64" || !local.mutable || local.boxed || local.tdz) return null; + } + return counters; +} + +function isLoop(s: IrStmt): boolean { + return s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf"; +} + +/** True when a `continue` in the body binds to the loop: an unlabeled one + * outside nested loops, or one that names a label of the loop. */ +function continuesLoop(loop: WhileLoop): boolean { + const labels = loop.labels ?? []; + let depth = 0; + let found = false; + const expr = (e: IrExpr): boolean => !found && everyExprChild(e, expr, stmt); + const stmt = (s: IrStmt): boolean => { + if (found) return false; + if (s.kind === "continue") { + if (s.label === undefined ? depth === 0 : labels.includes(s.label)) found = true; + return !found; + } + const nested = isLoop(s); + if (nested) depth++; + const ok = everyStmtChild(s, expr, stmt); + if (nested) depth--; + return ok; + }; + loop.body.every(stmt); + return found; +} + +/** Mentions of each id in `node`, in any field (a string literal equal to + * an id counts too, which only refuses more loops). */ +function countMentions( + node: unknown, + ids: ReadonlySet, + mentions: Map, +): void { + if (typeof node === "string") { + if (ids.has(node)) mentions.set(node, (mentions.get(node) ?? 0) + 1); + } else if (Array.isArray(node)) { + for (const item of node) countMentions(item, ids, mentions); + } else if (node !== null && typeof node === "object") { + const record = node as Record; + for (const key in record) + if (key !== "loc" && key !== "type") countMentions(record[key], ids, mentions); + } +} + +/** A loop with the qualifying shape, the statements before it in its + * statement list, and whether it is inside another loop. */ +interface Candidate { + loop: WhileLoop; + counters: string[]; + before: readonly IrStmt[]; + nested: boolean; +} + +function findCandidates(fn: IrFunction, locals: ReadonlyMap): Candidate[] { + const candidates: Candidate[] = []; + let depth = 0; + const list = (body: readonly IrStmt[]): void => { + body.forEach((s, i) => { + if (s.kind === "while") { + const counters = loopCounters(s, locals); + if (counters !== null && !continuesLoop(s)) + candidates.push({ loop: s, counters, before: body.slice(0, i), nested: depth > 0 }); + } + visit(s); + }); + }; + // Statement lists inside expressions are not searched: their loops stay. + const visit = (s: IrStmt): void => { + const loop = isLoop(s); + if (loop) depth++; + switch (s.kind) { + case "block": + case "while": + case "doWhile": + case "for": + case "forOf": + list(s.body); + break; + case "if": + list(s.then); + if (s.else_ !== null) list(s.else_); + break; + case "switch": + for (const c of s.cases) list(c.body); + break; + case "tryCatch": + list(s.tryBody); + if (s.catchBody !== null) list(s.catchBody); + if (s.finallyBody !== null) list(s.finallyBody); + break; + default: + break; + } + if (loop) depth--; + }; + list(fn.body); + return candidates; +} + +function rewriteFunction(fn: IrFunction): IrFunction { + const locals = new Map(fn.locals.map((l) => [l.id, l])); + const candidates = findCandidates(fn, locals); + if (candidates.length === 0) return fn; + + const ids = new Set(); + for (const c of candidates) for (const id of c.counters) ids.add(id); + const total = new Map(); + countMentions(fn.body, ids, total); + const params = new Set(fn.params.map((p) => p.localId)); + // A counter this function reaches through its environment. + const outer = new Map(); + countMentions([fn.captures ?? [], fn.classCaptures ?? []], ids, outer); + + let ranges: ReadonlyMap | null = null; + const used = new Set(fn.locals.map((l) => l.id)); + const added: IrLocal[] = []; + const rewritten = new Map>(); + for (const { loop, counters, before, nested } of candidates) { + const inside = new Map(); + countMentions(loop, new Set(counters), inside); + // Each counter's start: its declaration's initializer, or null for a + // parameter. + const starts = new Map(); + for (const id of counters) { + if ((outer.get(id) ?? 0) !== 0) break; + const elsewhere = (total.get(id) ?? 0) - (inside.get(id) ?? 0); + if (params.has(id)) { + if (elsewhere === 0 && !nested) starts.set(id, null); + continue; + } + const decl = before.find((s) => s.kind === "varDecl" && s.localId === id); + if (elsewhere === 1 && decl?.kind === "varDecl" && decl.init !== null) + starts.set(id, decl.init); + } + if (starts.size !== counters.length) continue; + const renamed = new Map(); + const fresh: IrLocal[] = []; + for (const id of counters) { + const local = locals.get(id)!; + let k = 0; + while (used.has(`${local.name}.${k}`)) k++; + const next = `${local.name}.${k}`; + used.add(next); + renamed.set(id, next); + fresh.push({ ...local, id: next }); + } + const candidate = pairedForLoop(loop, renamed); + const forLocals = new Map(locals); + for (const local of fresh) forLocals.set(local.id, local); + // A start reads its counter on entry, which holds the initializer's + // value, with that value's range. + ranges ??= analyzeIntegerRanges(fn); + const startRanges = new Map(); + if (candidate.init?.kind === "block") + for (const decl of candidate.init.body) { + if (decl.kind !== "varDecl" || decl.init?.kind !== "varRef") continue; + const init = starts.get(decl.init.localId); + if (!init) continue; + const literal = + init.kind === "numLit" && Number.isSafeInteger(init.value) && !Object.is(init.value, -0); + startRanges.set( + decl.init, + literal ? { min: init.value, max: init.value } : (ranges.get(init) ?? null), + ); + } + if (matchIntegerPairedForLoop(candidate, forLocals, startRanges) === null) { + for (const local of fresh) used.delete(local.id); + continue; + } + rewritten.set(loop, renamed); + added.push(...fresh); + } + if (rewritten.size === 0) return fn; + + const expr = (e: IrExpr): IrExpr => mapExprChildren(e, expr, stmt); + const stmt = (s: IrStmt): IrStmt => { + const next = mapStmtChildren(s, expr, stmt); + const renamed = s.kind === "while" ? rewritten.get(s) : undefined; + // The body may hold rewritten loops of its own. + return renamed === undefined ? next : pairedForLoop(next as WhileLoop, renamed); + }; + return { ...fn, body: fn.body.map(stmt), locals: [...fn.locals, ...added] }; +} + +/** The for loop over the renamed counters: their starts read the original + * counters, and the body's two trailing updates become the update. */ +function pairedForLoop( + loop: WhileLoop, + renamed: ReadonlyMap, +): IrStmt & { kind: "for" } { + const expr = (e: IrExpr): IrExpr => { + const next = mapExprChildren(e, expr, stmt); + if (next.kind === "varRef" || next.kind === "incDec" || next.kind === "assignExpr") { + const id = renamed.get(next.localId); + if (id !== undefined) return { ...next, localId: id }; + } + return next; + }; + const stmt = (s: IrStmt): IrStmt => { + const next = mapStmtChildren(s, expr, stmt); + if ( + next.kind === "varDecl" || + next.kind === "assign" || + next.kind === "forOf" || + next.kind === "rethrow" + ) { + const id = renamed.get(next.localId); + if (id !== undefined) return { ...next, localId: id }; + } + return next; + }; + const loc = loop.loc; + const n = loop.body.length; + const declarations: IrStmt[] = []; + for (const [id, next] of renamed) + declarations.push({ + kind: "varDecl", + localId: next, + init: { kind: "varRef", localId: id, type: F64, loc }, + loc, + }); + const result: IrStmt & { kind: "for" } = { + kind: "for", + init: { kind: "block", body: declarations, loc }, + cond: expr(loop.cond), + update: { kind: "block", body: loop.body.slice(n - 2).map(stmt), loc }, + body: loop.body.slice(0, n - 2).map(stmt), + loc, + }; + if (loop.labels !== undefined) result.labels = loop.labels; + return result; +} diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 85910369a..819cfbee0 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -858,6 +858,10 @@ const RUNTIME_FEATURE_RULES = { }), /** Own properties JavaScript adds to children (scr_dyn_handle_props.c). */ handleProps: rule({ fns: ["child.propsInstall"] }), + /** The declaration-typed boundary's adoption (scr_decl_adopt.c): its + * casts, and closures boxed in a boundary program, which adopt their + * arguments. */ + declAdopt: rule({ fns: ["dyn.declAdopt"], initial: (mod) => mod.declBoundary === true }), /** for await over checked-dynamic values (scr_dyn_for_await.c). */ dynForAwait: rule({ fns: ["dyn.asyncIteratorOpen", "dyn.asyncIteratorWasSync"] }), /** Buffer.prototype methods called on checked-dynamic values @@ -1081,6 +1085,7 @@ function noRuntimeFeatures(): RuntimeFeatures { stream: false, streamDyn: false, handleProps: false, + declAdopt: false, dynForAwait: false, dynBuffer: false, builtinValues: false, diff --git a/packages/compiler/src/ir/serialize.test.ts b/packages/compiler/src/ir/serialize.test.ts index 8345ab10a..c24e1f198 100644 --- a/packages/compiler/src/ir/serialize.test.ts +++ b/packages/compiler/src/ir/serialize.test.ts @@ -198,3 +198,24 @@ test("byte decoding keeps the whole-document errors and version refusal", () => const sentinel = text.replace('"nan"', '"future"'); expect(() => deserializeModuleBytes(Buffer.from(sentinel))).toThrow("Invalid IR number sentinel"); }); + +test("file roots respell the source locations under them and nothing else", () => { + const mod = numbers([1, 2]); + const shim = { file: '/opt/scriptc "a"/shims/pkg/index.ts', start: 3, end: 4 }; + const other = { file: "/opt/elsewhere/shims/pkg/index.ts", start: 5, end: 6 }; + const body = mod.functions[0]!.body as IrStmt[]; + body.push( + { kind: "exprStmt", expr: { kind: "numLit", value: 3, type: F64, loc: shim }, loc: shim }, + { kind: "exprStmt", expr: { kind: "numLit", value: 4, type: F64, loc: other }, loc: other }, + ); + const roots = [{ from: '/opt/scriptc "a"/shims/', to: "scriptc:shims/" }]; + const relocated = deserializeModule(serializeModuleParts(mod, roots).join("")); + const files = (relocated.functions[0]!.body as IrStmt[]).map((stmt) => stmt.loc.file); + expect(files).toEqual([ + "numbers.ts", + "numbers.ts", + "scriptc:shims/pkg/index.ts", + "/opt/elsewhere/shims/pkg/index.ts", + ]); + expect(serializeModuleParts(mod).join("")).toBe(serializeModule(mod, true)); +}); diff --git a/packages/compiler/src/ir/serialize.ts b/packages/compiler/src/ir/serialize.ts index ca5eb955e..314f258d6 100644 --- a/packages/compiler/src/ir/serialize.ts +++ b/packages/compiler/src/ir/serialize.ts @@ -46,11 +46,41 @@ function encodeFunction(fn: IrFunction): string | undefined { return ordinaryNumbers(fn) ? JSON.stringify(fn) : JSON.stringify(fn, encodeNumber); } +/** A directory whose source location files an IR artifact names by a + * prefix of its own: a location file `from + rest` serializes as + * `to + rest`. */ +export interface SerializedFileRoot { + from: string; + to: string; +} + +/** Respells the source location files under `roots` in serialized parts. + * A location's file is the first member of its JSON object, and the + * escaped spelling of a path prefix is the prefix of the escaped path. */ +function relocateFiles(parts: string[], roots: readonly SerializedFileRoot[]): string[] { + const needles = roots.map(({ from, to }) => ({ + from: `"file":${JSON.stringify(from).slice(0, -1)}`, + to: `"file":${JSON.stringify(to).slice(0, -1)}`, + })); + return parts.map((part) => + needles.reduce( + (text, { from, to }) => (text.includes(from) ? text.replaceAll(from, to) : text), + part, + ), + ); +} + /** The compact encoding as ordered parts: the module header up to the * function array, one part per function (with separate comma parts), and the * header's remainder. Concatenating the parts yields exactly - * `serializeModule(mod, true)`; writers stream them to disk in batches. */ -export function serializeModuleParts(mod: IrModule): string[] { + * `serializeModule(mod, true)`; writers stream them to disk in batches. + * `fileRoots` respells the source locations in those directories, so + * artifacts do not depend on where the compiler that wrote them is + * installed. */ +export function serializeModuleParts( + mod: IrModule, + fileRoots: readonly SerializedFileRoot[] = [], +): string[] { // A replacer reads through live native views. Serializing the complete // function array through one view refreshes every function capsule for // each element, making large compiler artifacts quadratic. The sentinel @@ -65,7 +95,7 @@ export function serializeModuleParts(mod: IrModule): string[] { parts.push(encodeFunction(fn) ?? "null"); } parts.push("]" + header.slice(offset + slot.length)); - return parts; + return fileRoots.length === 0 ? parts : relocateFiles(parts, fileRoots); } /** Compiler artifacts use compact JSON; API consumers can retain the diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 770fee6c6..0e990918b 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -120,6 +120,8 @@ export function validateModule(mod: IrModule): IrValidationError[] { const moduleLoc: SrcLoc = { file: mod.sourceFile, start: 0, end: 0 }; if (mod.workers !== undefined && typeof mod.workers !== "boolean") errors.push({ message: "worker execution mode must be a boolean", loc: moduleLoc }); + if (mod.declBoundary !== undefined && mod.declBoundary !== true) + errors.push({ message: "declBoundary must be true when present", loc: moduleLoc }); if (mod.lifetimeRegions !== undefined && mod.lifetimeRegions !== true) errors.push({ message: "lifetimeRegions must be true when present", loc: moduleLoc }); if (mod.workers && mod.lib !== undefined) diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index 42ff404bd..f7f399f80 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -27,6 +27,7 @@ import { type FrontendInputExclusions, trackedReadFile, } from "../frontend/input-tracker.js"; +import { compilerOwnedFileRoots } from "../frontend/dts-paths.js"; import { provenanceSources } from "../frontend/provenance-registry.js"; import { processFork } from "../frontend/process-fork.js"; import { tokenEquivalentSource } from "../frontend/token-equivalence.js"; @@ -377,7 +378,7 @@ export function prepareNativeExecutable( if (irInput !== "") timing("ir-input"); const ir = outputKind === "ir" || options.emitIr - ? batchTextParts(serializeModuleParts(prepared.mod)) + ? batchTextParts(serializeModuleParts(prepared.mod, compilerOwnedFileRoots())) : null; timing("ir-serialize"); const llvm = @@ -510,7 +511,9 @@ export function prepareNativeLibrary( const input: NativeExecutableInput = { ok: true, llvm, - ir: options.emitIr ? batchTextParts(serializeModuleParts(prepared.mod)) : null, + ir: options.emitIr + ? batchTextParts(serializeModuleParts(prepared.mod, compilerOwnedFileRoots())) + : null, sidecarJson: prepared.sidecarJson, features: executableLinkFeatures(prepared.mod), sources: [...prepared.sourceTexts], diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 49867f0ad..4c031a8e7 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -79,6 +79,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( dynTier: false, streamDyn: false, handleProps: false, + declAdopt: false, dynForAwait: false, eventsOnceSignal: false, dynBuffer: false, diff --git a/packages/compiler/src/native/toolchain.test.ts b/packages/compiler/src/native/toolchain.test.ts index 0ee434300..ad4570c0c 100644 --- a/packages/compiler/src/native/toolchain.test.ts +++ b/packages/compiler/src/native/toolchain.test.ts @@ -99,11 +99,11 @@ test("native toolchain assets resolve from the physical manifest through package const direct = loadNativeToolchain(path); const viaLink = loadNativeToolchain(join(linked, "bin", "scriptc.exe.json")); expect(viaLink).toEqual(direct); - expect(viaLink.ts7Executable).toBe(join(assets, "typescript/lib/tsc.exe")); + expect(viaLink.ts7Executable).toBe(join(assets, "tsc-ts/lib/tsc-ts.exe")); expect( - loadNativeToolchain(join(linked, "bin", "scriptc.exe.json"), { SCRIPTC_CHECKER: "tsc-ts" }) + loadNativeToolchain(join(linked, "bin", "scriptc.exe.json"), { SCRIPTC_CHECKER: "tsgo" }) .ts7Executable, - ).toBe(join(assets, "tsc-ts/lib/tsc-ts.exe")); + ).toBe(join(assets, "typescript/lib/tsc.exe")); expect(viaLink.helperExecutable).toBe(join(assets, "llvm/bin/scriptc-llvm-codegen.exe")); expect( loadNativeToolchain(join(linked, "bin", "scriptc.exe.json"), { diff --git a/packages/compiler/src/native/toolchain.ts b/packages/compiler/src/native/toolchain.ts index fbcd94251..5abbb329e 100644 --- a/packages/compiler/src/native/toolchain.ts +++ b/packages/compiler/src/native/toolchain.ts @@ -177,11 +177,11 @@ export function loadNativeToolchain( : [...target.defaultLinkerArgs]; return { compilerVersion: manifest.compiler_version, - // tsc-ts, the default checker except on Windows, comes with the distribution; - // a development build takes it from the tsc-ts package installed with the - // compiler or for the working directory. SCRIPTC_CHECKER selects a checker. + // tsc-ts, the default checker, comes with the distribution; a development + // build takes it from the tsc-ts package installed with the compiler or for + // the working directory. SCRIPTC_CHECKER selects a checker. ts7Executable: - checkerBackend(env, host.platform) === "tsgo" + checkerBackend(env) === "tsgo" ? pathFrom(root, manifest.ts7) : manifest.tsc_ts !== undefined ? pathFrom(root, manifest.tsc_ts) diff --git a/packages/compiler/test/native-codegen-integration.test.ts b/packages/compiler/test/native-codegen-integration.test.ts index 0fd5249d3..ce1041c99 100644 --- a/packages/compiler/test/native-codegen-integration.test.ts +++ b/packages/compiler/test/native-codegen-integration.test.ts @@ -378,7 +378,7 @@ done: expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"])); expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"])); expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]); - expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v33"); + expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v35"); const clangExe = join(dir, "clang-program"); const linkDriver = join(dir, "clang-driver.c"); @@ -712,7 +712,7 @@ done: .split("\n"); expect(undefinedSymbols).toContain("_sf_scale"); expect(undefinedSymbols).toContain("_sf_callback_mix"); - expect(undefinedSymbols).toContain("_scr_runtime_abi_v33"); + expect(undefinedSymbols).toContain("_scr_runtime_abi_v35"); }); test("helper and clang object paths retain the same outbound FFI C ABI", async () => { diff --git a/packages/compiler/test/observed-method-guard.test.ts b/packages/compiler/test/observed-method-guard.test.ts new file mode 100644 index 000000000..cafe872f9 --- /dev/null +++ b/packages/compiler/test/observed-method-guard.test.ts @@ -0,0 +1,119 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "vitest"; +import { compile, deserializeModule, validateModule } from "../src/index.js"; + +const dirs: string[] = []; + +afterEach(async () => { + await Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); +}); + +type IrRecord = Record; + +function recordsOf(value: unknown, out: IrRecord[] = []): IrRecord[] { + if (value === null || typeof value !== "object") return out; + if (Array.isArray(value)) { + for (const item of value) recordsOf(item, out); + return out; + } + const record = value as IrRecord; + out.push(record); + for (const child of Object.values(record)) recordsOf(child, out); + return out; +} + +// An untyped computed write puts every method name in the census, so typed +// calls read the observable slot. While the slot holds the declared method, +// the call is the native method call, not a dynamic call of the slot. +test("typed calls of an observed method call it natively while its slot is unchanged", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-observed-guard-")); + dirs.push(dir); + const entry = join(dir, "main.ts"); + const outDir = join(dir, ".scriptc"); + const outPath = join(outDir, "main.ir.json"); + await writeFile( + join(dir, "untyped.js"), + "export function put(target, key, value) { target[key] = value; }\n", + ); + await writeFile( + entry, + [ + 'import { put } from "./untyped.js";', + "class Repo {", + " items: number[] = [];", + " total(): number { let s = 0; for (const n of this.items) s += n; return s; }", + "}", + "const repo = new Repo();", + "repo.items.push(1, 2);", + "put({}, String(Math.random()), 1);", + "export function sum(r: Repo): number { return r.total(); }", + "console.log(sum(repo));", + "", + ].join("\n"), + ); + const result = await compile(entry, { outDir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const module = deserializeModule(await readFile(outPath, "utf8")); + expect(validateModule(module)).toEqual([]); + const sum = module.functions.find((fn) => fn.name.endsWith("sum")); + expect(sum).toBeDefined(); + const records = recordsOf(sum); + const guards = records.filter( + (record) => + record.kind === "ternary" && + (record.cond as IrRecord | undefined)?.kind === "libCall" && + (record.cond as IrRecord).fn === "dyn.sameValue", + ); + expect(guards).toHaveLength(1); + const then = guards[0]!.then as IrRecord; + expect(then.kind).toBe("call"); + expect(String(then.callee)).toMatch(/Repo\.total$/); + expect(module.globals?.some((global) => global.name === "Repo.prototype.total.original")).toBe( + true, + ); +}); + +// A spread argument keeps the dynamic call, which drains the spread through +// the iterator's current `next` (a replacement included). +test("typed calls of an observed method with a spread argument keep the dynamic call", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-observed-guard-")); + dirs.push(dir); + const entry = join(dir, "main.ts"); + const outDir = join(dir, ".scriptc"); + const outPath = join(outDir, "main.ir.json"); + await writeFile( + join(dir, "untyped.js"), + "export function put(target, key, value) { target[key] = value; }\n", + ); + await writeFile( + entry, + [ + 'import { put } from "./untyped.js";', + "class Bag {", + " sum(...values: number[]): number { let s = 0; for (const n of values) s += n; return s; }", + "}", + "const bag = new Bag();", + "put({}, String(Math.random()), 1);", + "export function spread(b: Bag, values: number[]): number { return b.sum(...values); }", + "console.log(spread(bag, [1, 2]));", + "", + ].join("\n"), + ); + const result = await compile(entry, { outDir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const module = deserializeModule(await readFile(outPath, "utf8")); + expect(validateModule(module)).toEqual([]); + const spread = module.functions.find((fn) => fn.name.endsWith("spread")); + expect(spread).toBeDefined(); + const guards = recordsOf(spread).filter( + (record) => + record.kind === "ternary" && + (record.cond as IrRecord | undefined)?.kind === "libCall" && + (record.cond as IrRecord).fn === "dyn.sameValue", + ); + expect(guards).toHaveLength(0); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7144-paired-while-counters.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7144-paired-while-counters.ts.json new file mode 100644 index 000000000..678cbd8b0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7144-paired-while-counters.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7144-paired-while-counters.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7262-fresh-literal-untyped-param/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7262-fresh-literal-untyped-param/main.ts.json new file mode 100644 index 000000000..55a678e1b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7262-fresh-literal-untyped-param/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/7262-fresh-literal-untyped-param/lib.js", + "/tests/corpus/7262-fresh-literal-untyped-param/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7365-class-methods-untyped-computed-writes/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7365-class-methods-untyped-computed-writes/main.ts.json new file mode 100644 index 000000000..58cdacb62 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7365-class-methods-untyped-computed-writes/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/7365-class-methods-untyped-computed-writes/untyped.js", + "/tests/corpus/7365-class-methods-untyped-computed-writes/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7366-typed-global-view-arrays.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7366-typed-global-view-arrays.ts.json new file mode 100644 index 000000000..49da80b55 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7366-typed-global-view-arrays.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7366-typed-global-view-arrays.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7402-unknown-array-methods.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7402-unknown-array-methods.ts.json new file mode 100644 index 000000000..db33da675 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7402-unknown-array-methods.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7402-unknown-array-methods.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7403-bigint-of-typed-arrays.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7403-bigint-of-typed-arrays.mjs.json new file mode 100644 index 000000000..476611adb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7403-bigint-of-typed-arrays.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7403-bigint-of-typed-arrays.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7441-untyped-receiver-own-string-method-names.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7441-untyped-receiver-own-string-method-names.ts.json new file mode 100644 index 000000000..7477193ef --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7441-untyped-receiver-own-string-method-names.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7441-untyped-receiver-own-string-method-names.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7480-builtin-constructor-in.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7480-builtin-constructor-in.js.json new file mode 100644 index 000000000..efb9dd725 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7480-builtin-constructor-in.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7480-builtin-constructor-in.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7481-class-runtime-heritage-object-error.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7481-class-runtime-heritage-object-error.js.json new file mode 100644 index 000000000..d0d390662 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7481-class-runtime-heritage-object-error.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7481-class-runtime-heritage-object-error.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7482-define-property-function-class-values.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7482-define-property-function-class-values.js.json new file mode 100644 index 000000000..d1d97ba4f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7482-define-property-function-class-values.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7482-define-property-function-class-values.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7483-constructor-factory-traits.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7483-constructor-factory-traits.js.json new file mode 100644 index 000000000..c3fa9964f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7483-constructor-factory-traits.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7483-constructor-factory-traits.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7484-hoisted-function-captures-later-class.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7484-hoisted-function-captures-later-class.js.json new file mode 100644 index 000000000..515b9bf97 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7484-hoisted-function-captures-later-class.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7484-hoisted-function-captures-later-class.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7485-global-object-own-properties.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7485-global-object-own-properties.js.json new file mode 100644 index 000000000..aa1affb3c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7485-global-object-own-properties.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7485-global-object-own-properties.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7486-prototype-accessor-string-method-names.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7486-prototype-accessor-string-method-names.js.json new file mode 100644 index 000000000..0cf87c443 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7486-prototype-accessor-string-method-names.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7486-prototype-accessor-string-method-names.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7487-namespace-spread-untyped/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7487-namespace-spread-untyped/main.js.json new file mode 100644 index 000000000..b8a66c735 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7487-namespace-spread-untyped/main.js.json @@ -0,0 +1,9 @@ +{ + "order": [ + "/tests/corpus/7487-namespace-spread-untyped/shapes.js", + "/tests/corpus/7487-namespace-spread-untyped/extra.js", + "/tests/corpus/7487-namespace-spread-untyped/all.js", + "/tests/corpus/7487-namespace-spread-untyped/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7488-math-spread-untyped.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7488-math-spread-untyped.js.json new file mode 100644 index 000000000..4d1376a12 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7488-math-spread-untyped.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7488-math-spread-untyped.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7489-math-spread-generator-only.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7489-math-spread-generator-only.js.json new file mode 100644 index 000000000..5277102eb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7489-math-spread-generator-only.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7489-math-spread-generator-only.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7536-inspect-custom-errors.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7536-inspect-custom-errors.ts.json new file mode 100644 index 000000000..2c5325056 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7536-inspect-custom-errors.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7536-inspect-custom-errors.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7537-inspect-custom-errors.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7537-inspect-custom-errors.cjs.json new file mode 100644 index 000000000..16cb948f6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7537-inspect-custom-errors.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7537-inspect-custom-errors.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7538-inspect-custom-prototypes.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7538-inspect-custom-prototypes.mjs.json new file mode 100644 index 000000000..279015554 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7538-inspect-custom-prototypes.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7538-inspect-custom-prototypes.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7565-iterable-replaceable-method-results.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7565-iterable-replaceable-method-results.ts.json new file mode 100644 index 000000000..60631a82f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7565-iterable-replaceable-method-results.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7565-iterable-replaceable-method-results.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7600-navigator-optional-chain.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7600-navigator-optional-chain.js.json new file mode 100644 index 000000000..12465d5e5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7600-navigator-optional-chain.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7600-navigator-optional-chain.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7602-error-stack-limit-value.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7602-error-stack-limit-value.js.json new file mode 100644 index 000000000..99e1b8904 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7602-error-stack-limit-value.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7602-error-stack-limit-value.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7603-error-stack-limit-own.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7603-error-stack-limit-own.js.json new file mode 100644 index 000000000..5ed656fa9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7603-error-stack-limit-own.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7603-error-stack-limit-own.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7604-error-stack-limit-frozen.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7604-error-stack-limit-frozen.js.json new file mode 100644 index 000000000..a437ff57e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7604-error-stack-limit-frozen.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7604-error-stack-limit-frozen.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-boundary/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-boundary/main.ts.json new file mode 100644 index 000000000..0ef25fcbd --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/types-boundary/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/types-boundary/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/zod3-adoption/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/zod3-adoption/main.ts.json new file mode 100644 index 000000000..72fa6d390 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/zod3-adoption/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/zod3-adoption/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/zod3-boundary/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/zod3-boundary/main.ts.json new file mode 100644 index 000000000..4b8187352 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/zod3-boundary/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/zod3-boundary/main.ts" + ], + "diags": [] +} diff --git a/packages/runtime-darwin-arm64/scripts/build.mjs b/packages/runtime-darwin-arm64/scripts/build.mjs index e17312713..4802a7970 100644 --- a/packages/runtime-darwin-arm64/scripts/build.mjs +++ b/packages/runtime-darwin-arm64/scripts/build.mjs @@ -340,7 +340,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: RUNTIME_PACK_MATRIX.target, - runtime_abi: { version: 33, marker: "scr_runtime_abi_v33" }, + runtime_abi: { version: 35, marker: "scr_runtime_abi_v35" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index d0194be05..285e767f4 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -127,6 +127,7 @@ export const OPTIONAL_RUNTIME_SOURCES = [ ["scr_stream_dyn.c", "streamDyn"], // Own properties JavaScript adds to children. ["scr_dyn_handle_props.c", "handleProps"], + ["scr_decl_adopt.c", "declAdopt"], // for await over checked-dynamic values. ["scr_dyn_for_await.c", "dynForAwait"], ["scr_dyn_for_await_stream.c", all("dynForAwait", "stream", "dynAsync")], diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs index 60ef9dc49..329aac00d 100644 --- a/packages/runtime-pack-common/scripts/build.mjs +++ b/packages/runtime-pack-common/scripts/build.mjs @@ -437,7 +437,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: matrix.target, - runtime_abi: { version: 33, marker: "scr_runtime_abi_v33" }, + runtime_abi: { version: 35, marker: "scr_runtime_abi_v35" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime/src/scr_bytes_dyn.c b/packages/runtime/src/scr_bytes_dyn.c index 280aa423c..c492d43b1 100644 --- a/packages/runtime/src/scr_bytes_dyn.c +++ b/packages/runtime/src/scr_bytes_dyn.c @@ -509,8 +509,9 @@ static bool scr_bytes_dyn_buffer(const ScrDyn *recv) { /* One entry per method family, so a program links only the families it * names: each runs its methods on a Buffer receiver and gives any other * receiver the ordinary dispatch. */ -ScrDyn *scr_dyn_invoke_buffer_num(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, - size_t argc, const char *what) { +ScrDyn *scr_dyn_invoke_buffer_num(ScrDyn *recv, ScrDyn *callee, const char *method, + int32_t *mid_slot, ScrDyn *const *args, size_t argc, + const char *what) { if (scr_bytes_dyn_buffer(recv)) { bool read = !strncmp(method, "read", 4); const char *rest = method + (read ? 4 : 5); @@ -518,40 +519,45 @@ ScrDyn *scr_dyn_invoke_buffer_num(ScrDyn *recv, ScrDyn *callee, const char *meth if (!strcmp(rest, scr_bytes_dyn_names[at].name)) return scr_bytes_dyn_num_method(recv->v.bytes, read, at, args, argc); } } - return scr_dyn_invoke_prepared(recv, callee, method, args, argc, what); + return scr_dyn_invoke_prepared_id(recv, callee, method, mid_slot, args, argc, what); } -ScrDyn *scr_dyn_invoke_buffer_write(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, - size_t argc, const char *what) { +ScrDyn *scr_dyn_invoke_buffer_write(ScrDyn *recv, ScrDyn *callee, const char *method, + int32_t *mid_slot, ScrDyn *const *args, size_t argc, + const char *what) { if (scr_bytes_dyn_buffer(recv)) return scr_bytes_dyn_write(recv->v.bytes, args, argc); - return scr_dyn_invoke_prepared(recv, callee, method, args, argc, what); + return scr_dyn_invoke_prepared_id(recv, callee, method, mid_slot, args, argc, what); } -ScrDyn *scr_dyn_invoke_buffer_fill(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, - size_t argc, const char *what) { +ScrDyn *scr_dyn_invoke_buffer_fill(ScrDyn *recv, ScrDyn *callee, const char *method, + int32_t *mid_slot, ScrDyn *const *args, size_t argc, + const char *what) { if (scr_bytes_dyn_buffer(recv)) return scr_bytes_dyn_fill(recv, args, argc); - return scr_dyn_invoke_prepared(recv, callee, method, args, argc, what); + return scr_dyn_invoke_prepared_id(recv, callee, method, mid_slot, args, argc, what); } -ScrDyn *scr_dyn_invoke_buffer_to_string(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, - size_t argc, const char *what) { +ScrDyn *scr_dyn_invoke_buffer_to_string(ScrDyn *recv, ScrDyn *callee, const char *method, + int32_t *mid_slot, ScrDyn *const *args, size_t argc, + const char *what) { if (scr_bytes_dyn_buffer(recv)) return scr_bytes_dyn_to_string(recv->v.bytes, args, argc); - return scr_dyn_invoke_prepared(recv, callee, method, args, argc, what); + return scr_dyn_invoke_prepared_id(recv, callee, method, mid_slot, args, argc, what); } /* indexOf, lastIndexOf and includes. */ -ScrDyn *scr_dyn_invoke_buffer_search(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, - size_t argc, const char *what) { +ScrDyn *scr_dyn_invoke_buffer_search(ScrDyn *recv, ScrDyn *callee, const char *method, + int32_t *mid_slot, ScrDyn *const *args, size_t argc, + const char *what) { if (scr_bytes_dyn_buffer(recv)) { bool includes = !strcmp(method, "includes"); return scr_bytes_dyn_index_of(recv->v.bytes, args, argc, strcmp(method, "lastIndexOf") != 0, includes); } - return scr_dyn_invoke_prepared(recv, callee, method, args, argc, what); + return scr_dyn_invoke_prepared_id(recv, callee, method, mid_slot, args, argc, what); } /* toJSON, equals, compare, copy, swap16/32/64, and the refused members. */ -ScrDyn *scr_dyn_invoke_buffer(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, - size_t argc, const char *what) { +ScrDyn *scr_dyn_invoke_buffer(ScrDyn *recv, ScrDyn *callee, const char *method, + int32_t *mid_slot, ScrDyn *const *args, size_t argc, + const char *what) { if (scr_bytes_dyn_buffer(recv)) { ScrBytes *b = recv->v.bytes; if (!strcmp(method, "toJSON")) return scr_bytes_dyn_to_json(b); @@ -584,5 +590,5 @@ ScrDyn *scr_dyn_invoke_buffer(ScrDyn *recv, ScrDyn *callee, const char *method, return NULL; } } - return scr_dyn_invoke_prepared(recv, callee, method, args, argc, what); + return scr_dyn_invoke_prepared_id(recv, callee, method, mid_slot, args, argc, what); } diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c index f8dd9a0c0..f80c314b8 100644 --- a/packages/runtime/src/scr_console.c +++ b/packages/runtime/src/scr_console.c @@ -148,7 +148,7 @@ static void scr_enable_utf8_console_output(void) { } #endif -void scr_runtime_abi_v33(void) {} +void scr_runtime_abi_v35(void) {} void scr_init(void) { #ifdef SCR_WORKERS diff --git a/packages/runtime/src/scr_core.h b/packages/runtime/src/scr_core.h index d716f0de3..5633977b8 100644 --- a/packages/runtime/src/scr_core.h +++ b/packages/runtime/src/scr_core.h @@ -81,7 +81,7 @@ void scr_init(void); /* Program objects emitted by the bundled LLVM helper reference this symbol. * Its versioned spelling makes a mismatched manual runtime link fail before * the program can start. */ -void scr_runtime_abi_v33(void); +void scr_runtime_abi_v35(void); void scr_runtime_workers_v8(void); /* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ────── diff --git a/packages/runtime/src/scr_decl_adopt.c b/packages/runtime/src/scr_decl_adopt.c new file mode 100644 index 000000000..7372826ab --- /dev/null +++ b/packages/runtime/src/scr_decl_adopt.c @@ -0,0 +1,174 @@ +/* The declaration-typed boundary's adoption, linked only by programs with + * boundary casts (runtime feature declAdopt). + * + * A boundary cast (decl-projection.ts in the compiler) checks a value + * package code produced against the type the package's declarations give + * it and builds the native value. Copying would be observable whenever + * something else still references a source object or array, so this pass + * walks the source next to a live capsule of the built value and turns + * every source node with another holder (rc above one) into a live + * capsule of the native value built from it: every holder then reads and + * writes the same native value. Unshared nodes are left to be released + * with their parent, which no program can tell apart from a move. + * + * Phase one checks that every converted node is plain data (the native + * value has no prototype, accessors, symbol keys or frozen state) and + * collects the shared ones; it throws the path TypeError before anything + * changes. Phase two converts the shared nodes in place + * (scr_dyn_decl_become, scr_json.c). */ +#include "scr_runtime.h" + +#include +#include + +void scr_dyn_decl_become(ScrDyn *n, const ScrDyn *capsule); + +typedef struct { + ScrDyn *node; + ScrDyn *capsule; /* owned */ +} ScrDeclAdopt; + +typedef struct { + ScrDeclAdopt *items; + size_t len, cap; +} ScrDeclAdoptList; + +static void scr_decl_adopt_push(ScrDeclAdoptList *list, ScrDyn *node, ScrDyn *capsule) { + if (list->len == list->cap) { + list->cap = list->cap ? list->cap * 2 : 8; + list->items = realloc(list->items, list->cap * sizeof *list->items); + if (!list->items) scr_trap("scriptc: out of memory\n"); + } + list->items[list->len].node = node; + list->items[list->len].capsule = capsule; + list->len++; +} + +static bool scr_decl_adopt_plain(const ScrDyn *n, const ScrDynPath *path) { + const char *want = NULL; + if (n->kind == SCR_DYN_OBJ) { + if (n->prototype || n->null_proto || n->v.obj.class_name) + want = "a plain object (this value has a prototype)"; + else if (n->symbol_properties || n->symbol_keys) + want = "a plain object (this value has symbol-keyed properties)"; + else if (n->non_extensible) + want = "a plain object (this value is frozen, sealed or not extensible)"; + else if (n->v.obj.source_access && !n->v.obj.source_identity) + want = "a plain object"; + else { + for (size_t i = 0; i < n->v.obj.len && !want; i++) { + const ScrDynEntry *e = &n->v.obj.entries[i]; + if (e->accessor) want = "a plain object (this value has accessor properties)"; + else if (!e->writable || !e->enumerable || !e->configurable) + want = "a plain object (this value has non-default property attributes)"; + } + } + } else if (n->kind == SCR_DYN_ARR) { + if (n->prototype) + want = "a plain array (this value has a prototype)"; + else if (n->v.arr.properties || n->symbol_properties || n->symbol_keys) + want = "a plain array (this value has extra properties)"; + else if (n->v.arr.sealed || n->v.arr.frozen || n->non_extensible) + want = "a plain array (this value is frozen, sealed or not extensible)"; + } + if (!want) return true; + scr_dyn_check_fail(path, want, n); + return false; +} + +static ScrDyn *scr_decl_capsule_ptr_holder(const ScrDeclAdoptList *list, const ScrDyn *node) { + for (size_t i = 0; i < list->len; i++) + if (list->items[i].node == node) return list->items[i].capsule; + return NULL; +} + +/* `capsule` is owned by the call: pushed or released. */ +static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsule, + const ScrDynPath *path, unsigned depth) { + if (!n || (n->kind != SCR_DYN_OBJ && n->kind != SCR_DYN_ARR) || !capsule || + capsule->kind != SCR_DYN_TYPED_REF || capsule == n || depth > 4096) { + scr_dyn_release(capsule); + return true; + } + if (!scr_decl_adopt_plain(n, path)) { + scr_dyn_release(capsule); + return false; + } + if (n->rc > 1 && n->rc != SIZE_MAX) { + ScrDyn *seen = scr_decl_capsule_ptr_holder(list, n); + if (seen) { + bool same = seen->v.typed_ref.ptr == capsule->v.typed_ref.ptr; + scr_dyn_release(capsule); + if (!same) + scr_dyn_check_fail(path, "a value that is not also stored elsewhere in this value (scriptc builds a separate native copy for each place)", n); + return same; + } + scr_decl_adopt_push(list, n, capsule); + } + void *ptr = capsule->v.typed_ref.ptr; + if (capsule->v.typed_ref.record && capsule->v.typed_ref.array && n->kind == SCR_DYN_OBJ) { + const ScrDynTypedRecordOps *ops = (const ScrDynTypedRecordOps *)(const void *)capsule->v.typed_ref.array; + for (size_t i = 0; i < ops->count; i++) { + if (!ops->has(ptr, i)) continue; + const ScrStr *name = ops->names[i]; + ScrDyn *child = scr_dyn_obj_get(n, name->data, name->len); + if (!child || (child->kind != SCR_DYN_OBJ && child->kind != SCR_DYN_ARR)) continue; + ScrDynPath p = { path, name->data, 0 }; + if (!scr_decl_adopt_walk(list, child, ops->get(ptr, i), &p, depth + 1)) { + if (!(n->rc > 1 && n->rc != SIZE_MAX)) scr_dyn_release(capsule); + return false; + } + } + } else if (!capsule->v.typed_ref.record && !capsule->v.typed_ref.klass && !capsule->v.typed_ref.array && + n->kind == SCR_DYN_ARR && !n->v.arr.presence) { + /* A capsule without element hooks over an array: a tuple, whose index + * field names get no record hooks. Its view holds a live capsule of + * each record or array element, which the source element adopts (the + * walk skips the other elements). */ + ScrDyn *view = capsule->v.typed_ref.materialize(ptr); + if (scr_exc_pending()) { + scr_dyn_release(view); + if (!(n->rc > 1 && n->rc != SIZE_MAX)) scr_dyn_release(capsule); + return false; + } + if (view && view->kind == SCR_DYN_ARR) { + size_t len = n->v.arr.len < view->v.arr.len ? n->v.arr.len : view->v.arr.len; + for (size_t i = 0; i < len; i++) { + ScrDyn *child = n->v.arr.items[i], *element = view->v.arr.items[i]; + if (!child || !element || (child->kind != SCR_DYN_OBJ && child->kind != SCR_DYN_ARR)) continue; + ScrDynPath p = { path, NULL, i }; + if (!scr_decl_adopt_walk(list, child, scr_dyn_retain(element), &p, depth + 1)) { + scr_dyn_release(view); + if (!(n->rc > 1 && n->rc != SIZE_MAX)) scr_dyn_release(capsule); + return false; + } + } + } + scr_dyn_release(view); + } else if (!capsule->v.typed_ref.record && !capsule->v.typed_ref.klass && + capsule->v.typed_ref.array && n->kind == SCR_DYN_ARR && !n->v.arr.presence) { + const ScrDynTypedArrayOps *ops = capsule->v.typed_ref.array; + for (size_t i = 0; i < n->v.arr.len; i++) { + ScrDyn *child = n->v.arr.items[i]; + if (!child || (child->kind != SCR_DYN_OBJ && child->kind != SCR_DYN_ARR)) continue; + ScrDynPath p = { path, NULL, i }; + if (!scr_decl_adopt_walk(list, child, ops->get(ptr, (double)i), &p, depth + 1)) { + if (!(n->rc > 1 && n->rc != SIZE_MAX)) scr_dyn_release(capsule); + return false; + } + } + } + if (!(n->rc > 1 && n->rc != SIZE_MAX)) scr_dyn_release(capsule); + return true; +} + +void scr_dyn_decl_adopt(ScrDyn *source, ScrDyn *capsule) { + ScrDeclAdoptList list = { NULL, 0, 0 }; + bool ok = scr_decl_adopt_walk(&list, source, scr_dyn_retain(capsule), NULL, 0); + for (size_t i = 0; i < list.len; i++) { + ScrDeclAdopt *item = &list.items[i]; + if (ok && item->node->kind != SCR_DYN_TYPED_REF) scr_dyn_decl_become(item->node, item->capsule); + scr_dyn_release(item->capsule); + } + free(list.items); +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index d6202fd17..2bd5efd87 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -368,6 +368,12 @@ typedef struct ScrDynTypedRecordOps { /* A record capsule's ops while it answers from the native record (no view * created yet), or NULL. */ const ScrDynTypedRecordOps *scr_dyn_typed_ref_live_record(const ScrDyn *d); +/* The declaration-typed boundary's adoption (scr_decl_adopt.c, linked by + * programs with boundary casts): the nodes of `source` that something else + * still references become live capsules of the native value `capsule` + * holds. Both borrowed; a node that is not plain data throws the path + * TypeError before anything changes. */ +void scr_dyn_decl_adopt(ScrDyn *source, ScrDyn *capsule); /* Prototype members of a compiled class seen as `unknown`, emitted per * class by the compiler: the string names of methods, getters and setters @@ -871,6 +877,7 @@ void scr_dyn_install_iterator_symbol(ScrDyn *key); * when the registry creates it; NULL (borrowed) until then. */ void scr_dyn_install_inspect_custom_symbol(ScrDyn *key); ScrDyn *scr_dyn_inspect_custom_symbol(void); +void scr_dyn_install_has_instance_symbol(ScrDyn *key); void scr_dyn_install_async_iterator_symbol(ScrDyn *key); void scr_dyn_install_async_dispose_symbol(ScrDyn *key); /* True when key is Symbol.asyncDispose (registered by the symbol unit when @@ -1280,6 +1287,10 @@ void scr_error_set_prop(ScrError *e, const char *key, size_t key_len, ScrDyn *va ScrDyn *scr_errdyn_view_of(const ScrError *e); /* borrowed or NULL */ /* ToBoolean over the dyn kind (JS-exact); borrowed, never throws. */ bool scr_dyn_truthy(const ScrDyn *d); +/* Math.max/Math.min(...src) over an untyped iterable; `what` names the + * source in V8's null/undefined spread TypeError. NaN with it pending. */ +double scr_dyn_math_fold(const ScrDyn *src, bool max, const ScrStr *what); +int scr_dyn_has_instance_hook(const ScrDyn *value, const ScrDyn *callee); /* JS String() over a dyn value (arrays join, [object Object], the error * encoding renders "name: message"); borrowed, result +1, never throws. */ ScrStr *scr_dyn_display(const ScrDyn *d); @@ -1295,21 +1306,27 @@ bool scr_dyn_strict_eq(const ScrDyn *a, const ScrDyn *b); * result owned (+1). MAY THROW (NULL with the exception pending). */ ScrDyn *scr_dyn_prepare_method(ScrDyn *recv, const char *method); ScrDyn *scr_dyn_invoke_prepared(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, size_t argc, const char *what); -/* scr_dyn_invoke_prepared for Buffer.prototype method names (scr_bytes_dyn.c), - * one entry per method family: a Buffer receiver runs the method, any other - * takes the ordinary dispatch. */ -ScrDyn *scr_dyn_invoke_buffer_num(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, size_t argc, const char *what); -ScrDyn *scr_dyn_invoke_buffer_write(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, size_t argc, const char *what); -ScrDyn *scr_dyn_invoke_buffer_fill(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, size_t argc, const char *what); -ScrDyn *scr_dyn_invoke_buffer_to_string(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, size_t argc, const char *what); -ScrDyn *scr_dyn_invoke_buffer_search(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, size_t argc, const char *what); -ScrDyn *scr_dyn_invoke_buffer(ScrDyn *recv, ScrDyn *callee, const char *method, ScrDyn *const *args, size_t argc, const char *what); +/* scr_dyn_invoke_prepared for a call site whose method name is a constant: + * *mid_slot, the site's -1-initialized slot, caches the name's method id + * after the first call, so later calls skip the name lookup. */ +ScrDyn *scr_dyn_invoke_prepared_id(ScrDyn *recv, ScrDyn *callee, const char *method, int32_t *mid_slot, ScrDyn *const *args, size_t argc, const char *what); +/* scr_dyn_invoke_prepared_id for Buffer.prototype method names + * (scr_bytes_dyn.c), one entry per method family: a Buffer receiver runs + * the method, any other takes the ordinary dispatch. */ +ScrDyn *scr_dyn_invoke_buffer_num(ScrDyn *recv, ScrDyn *callee, const char *method, int32_t *mid_slot, ScrDyn *const *args, size_t argc, const char *what); +ScrDyn *scr_dyn_invoke_buffer_write(ScrDyn *recv, ScrDyn *callee, const char *method, int32_t *mid_slot, ScrDyn *const *args, size_t argc, const char *what); +ScrDyn *scr_dyn_invoke_buffer_fill(ScrDyn *recv, ScrDyn *callee, const char *method, int32_t *mid_slot, ScrDyn *const *args, size_t argc, const char *what); +ScrDyn *scr_dyn_invoke_buffer_to_string(ScrDyn *recv, ScrDyn *callee, const char *method, int32_t *mid_slot, ScrDyn *const *args, size_t argc, const char *what); +ScrDyn *scr_dyn_invoke_buffer_search(ScrDyn *recv, ScrDyn *callee, const char *method, int32_t *mid_slot, ScrDyn *const *args, size_t argc, const char *what); +ScrDyn *scr_dyn_invoke_buffer(ScrDyn *recv, ScrDyn *callee, const char *method, int32_t *mid_slot, ScrDyn *const *args, size_t argc, const char *what); ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc, const char *what); /* Keyed read on a FUNC node: the own-property table first (defineProperties * writes land there), then "name" (the box's best-effort static name; "" * for anonymous) and "length" (the boxed arity). Returns +1, or NULL when * the key answers nothing (the caller's undefined). Never throws. */ ScrDyn *scr_dyn_fn_get(const ScrDyn *d, const char *key, size_t key_len); +/* A function value's own property table, created on first use. +1. */ +ScrDyn *scr_dyn_function_properties(const ScrDyn *function); /* Object.defineProperties over dyn values (targets: OBJ and FUNC): each * descriptor's `value` becomes an own data property. OBJ targets preserve * writable/enumerable/configurable; FUNC targets still use plain properties. diff --git a/packages/runtime/src/scr_dyn_builtin_class.c b/packages/runtime/src/scr_dyn_builtin_class.c index 2f947048a..0647a65c0 100644 --- a/packages/runtime/src/scr_dyn_builtin_class.c +++ b/packages/runtime/src/scr_dyn_builtin_class.c @@ -1581,6 +1581,8 @@ bool scr_dyn_function_instanceof(ScrDyn *value, ScrDyn *callee) { scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); return false; } + int hooked = scr_dyn_has_instance_hook(value, callee); + if (hooked >= 0) return hooked == 1; ScrDyn *prototype = bc_get(callee, "prototype"); if (!prototype) return false; bool found = false; diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index 14d11dc67..c536d08b1 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -8,6 +8,7 @@ * scr_json.c; this unit uses only its public surface plus ScrJsonBuf. */ #include "scr_runtime.h" +#include "scr_dyn_methods.h" #include #include @@ -549,7 +550,11 @@ ScrDyn *scr_dyn_array_proto_call(ScrDyn *recv, ScrStr *method, ScrDyn *args) { return scr_dyn_retain(scr_dyn_undefined()); } -static bool dyn_name_is(const char *m, const char *n) { return strcmp(m, n) == 0; } +/* The dispatch below maps a call's method name to an id once + * (dyn_method_of, scr_dyn_methods.h) and its branches compare ids. Names + * compare by content, and any name the branches do not test is DYN_M_NONE. + * scripts/gen-dyn-methods.mjs writes the header from the DYN_M_ ids used + * here. */ /* Handle receivers: hand the whole call to the tag's ops (installed by * the owning unit at main() — a missing install is an internal error the @@ -692,29 +697,34 @@ static bool dyn_arr_reversed_copy(ScrDyn *out, const ScrDyn *source) { /* Names each prototype declares BEYOND what's implemented here — these * throw the "not supported yet" Error instead of mis-answering "is not a * function". */ -static bool dyn_arr_proto_unimpl(const char *m) { - static const char *names[] = { "toString", "toLocaleString", NULL }; - for (size_t i = 0; names[i]; i++) if (dyn_name_is(m, names[i])) return true; - return false; +static bool dyn_arr_proto_unimpl(int m) { + return m == DYN_M_toString || m == DYN_M_toLocaleString; } -static bool dyn_arr_proto_mutates(const char *m) { - static const char *names[] = { - "push", "pop", "shift", "unshift", "reverse", "sort", "fill", "copyWithin", "splice", NULL - }; - for (size_t i = 0; names[i]; i++) if (dyn_name_is(m, names[i])) return true; - return false; +static bool dyn_arr_proto_mutates(int m) { + switch (m) { + case DYN_M_push: case DYN_M_pop: case DYN_M_shift: case DYN_M_unshift: case DYN_M_reverse: + case DYN_M_sort: case DYN_M_fill: case DYN_M_copyWithin: case DYN_M_splice: + return true; + default: + return false; + } } -static bool dyn_bytes_proto_real(const char *m) { - static const char *names[] = { "slice", "at", "indexOf", "lastIndexOf", "includes", "join", - "forEach", "map", "filter", "some", "every", "find", "findIndex", "reverse", "fill", "set", - "subarray", "sort", "keys", "values", "entries", "reduce", "reduceRight", "copyWithin", - "toString", "toLocaleString", NULL }; - for (size_t i = 0; names[i]; i++) if (dyn_name_is(m, names[i])) return true; - return false; +static bool dyn_bytes_proto_real(int m) { + switch (m) { + case DYN_M_slice: case DYN_M_at: case DYN_M_indexOf: case DYN_M_lastIndexOf: case DYN_M_includes: + case DYN_M_join: case DYN_M_forEach: case DYN_M_map: case DYN_M_filter: case DYN_M_some: + case DYN_M_every: case DYN_M_find: case DYN_M_findIndex: case DYN_M_reverse: case DYN_M_fill: + case DYN_M_set: case DYN_M_subarray: case DYN_M_sort: case DYN_M_keys: case DYN_M_values: + case DYN_M_entries: case DYN_M_reduce: case DYN_M_reduceRight: case DYN_M_copyWithin: + case DYN_M_toString: case DYN_M_toLocaleString: + return true; + default: + return false; + } } static ScrDyn *scr_dyn_invoke_impl( - ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc, + ScrDyn *recv, const char *method, int mid, ScrDyn *const *args, size_t argc, const char *what, ScrDyn *callback_recv); /* Live native arrays: the constant-time methods that loops call once per @@ -724,18 +734,18 @@ static ScrDyn *scr_dyn_invoke_impl( * array. Anything that could observe more — a hole, argument coercion, a * multi-value push, a value the element type rejects — sets *handled false * and takes the snapshot path. */ -static ScrDyn *dyn_live_array_method(ScrDyn *recv, const char *method, ScrDyn *const *args, +static ScrDyn *dyn_live_array_method(ScrDyn *recv, int mid, ScrDyn *const *args, size_t argc, const char *what, bool *handled) { const ScrDynTypedArrayOps *ops = scr_dyn_typed_ref_array_ops(recv); if (!ops) return NULL; ScrArr *arr = recv->v.typed_ref.ptr; size_t len = arr->len; - if (dyn_name_is(method, "push") && argc == 1 && ops->set) { + if ((mid == DYN_M_push) && argc == 1 && ops->set) { if (!ops->set(arr, (double)len, args[0])) return NULL; *handled = true; return scr_exc_pending() ? NULL : scr_dyn_new_num((double)arr->len); } - if (dyn_name_is(method, "pop")) { + if ((mid == DYN_M_pop)) { if (len == 0) { *handled = true; return scr_dyn_retain(scr_dyn_undefined()); @@ -746,7 +756,7 @@ static ScrDyn *dyn_live_array_method(ScrDyn *recv, const char *method, ScrDyn *c *handled = true; return last; } - if (dyn_name_is(method, "at") && (argc == 0 || args[0]->kind == SCR_DYN_NUM || args[0]->kind == SCR_DYN_UNDEF)) { + if ((mid == DYN_M_at) && (argc == 0 || args[0]->kind == SCR_DYN_NUM || args[0]->kind == SCR_DYN_UNDEF)) { double index = dyn_index_arg(args, argc, 0, 0, what); if (index < 0) index += (double)len; if (index < 0 || index >= (double)len) { @@ -786,11 +796,25 @@ ScrDyn *scr_dyn_invoke_prepared(ScrDyn *recv, ScrDyn *callee, const char *method ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc, const char *what) { - return scr_dyn_invoke_impl(recv, method, args, argc, what, NULL); + return scr_dyn_invoke_impl(recv, method, dyn_method_of(method), args, argc, what, NULL); +} + +ScrDyn *scr_dyn_invoke_prepared_id(ScrDyn *recv, ScrDyn *callee, const char *method, + int32_t *mid_slot, ScrDyn *const *args, size_t argc, + const char *what) { + if (callee) return scr_dyn_invoke_prepared(recv, callee, method, args, argc, what); + /* The slot is the call site's, shared by every thread: each writes the + * same id. */ + int32_t mid = __atomic_load_n(mid_slot, __ATOMIC_RELAXED); + if (mid < 0) { + mid = dyn_method_of(method); + __atomic_store_n(mid_slot, mid, __ATOMIC_RELAXED); + } + return scr_dyn_invoke_impl(recv, method, mid, args, argc, what, NULL); } static ScrDyn *scr_dyn_invoke_impl( - ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc, + ScrDyn *recv, const char *method, int mid, ScrDyn *const *args, size_t argc, const char *what, ScrDyn *callback_recv) { if (recv->kind == SCR_DYN_UNDEF || recv->kind == SCR_DYN_NULL) { ScrJsonBuf b; @@ -811,7 +835,7 @@ static ScrDyn *scr_dyn_invoke_impl( if (recv->kind == SCR_DYN_HANDLE) { return scr_dynh_dispatch(recv, method, args, argc, what); } - if (scr_dyn_is_array_prototype(recv) && dyn_arr_proto_mutates(method)) { + if (scr_dyn_is_array_prototype(recv) && dyn_arr_proto_mutates(mid)) { scr_dyn_array_prototype_element_fence(recv); return NULL; } @@ -836,7 +860,7 @@ static ScrDyn *scr_dyn_invoke_impl( * snapshot identity back to the externally visible capsule. */ if (recv->kind == SCR_DYN_TYPED_REF) { bool handled = false; - ScrDyn *fast = dyn_live_array_method(recv, method, args, argc, what, &handled); + ScrDyn *fast = dyn_live_array_method(recv, mid, args, argc, what, &handled); if (handled) return fast; /* Iterator helpers on a compiled generator. */ if (scr_dyn_generator_helper_hook && @@ -861,9 +885,9 @@ static ScrDyn *scr_dyn_invoke_impl( return NULL; } bool mutates = materialized->kind == SCR_DYN_ARR && - dyn_arr_proto_mutates(method); + dyn_arr_proto_mutates(mid); ScrDyn *result = scr_dyn_invoke_impl( - materialized, method, args, argc, what, recv); + materialized, method, mid, args, argc, what, recv); if (mutates && !scr_exc_pending()) scr_dyn_typed_ref_commit(recv); if (result == materialized) { scr_dyn_release(result); @@ -892,18 +916,18 @@ static ScrDyn *scr_dyn_invoke_impl( } if (recv->kind == SCR_DYN_FUNC) { - if (dyn_name_is(method, "apply")) { + if ((mid == DYN_M_apply)) { return scr_dyn_apply_array_like(recv, argc ? args[0] : scr_dyn_undefined(), argc > 1 ? args[1] : scr_dyn_undefined(), what); } - if (dyn_name_is(method, "call")) { + if ((mid == DYN_M_call)) { scr_dyn_this_push_dyn(argc >= 1 ? args[0] : scr_dyn_undefined()); ScrDyn *r = scr_dyn_call(recv, argc > 1 ? args + 1 : NULL, argc > 1 ? argc - 1 : 0, what); scr_dyn_this_pop(); return r; } - if (dyn_name_is(method, "bind")) return scr_dyn_bind(recv, args, argc); - if (dyn_name_is(method, "toString")) { + if ((mid == DYN_M_bind)) return scr_dyn_bind(recv, args, argc); + if ((mid == DYN_M_toString)) { ScrDyn *member = scr_dyn_fn_get(recv, method, strlen(method)); if (member && member->kind != SCR_DYN_UNDEF) { scr_dyn_this_push_dyn(recv); @@ -938,9 +962,9 @@ static ScrDyn *scr_dyn_invoke_impl( } if ((recv->kind == SCR_DYN_SYMBOL || recv->kind == SCR_DYN_BIGINT || recv->kind == SCR_DYN_NUM || recv->kind == SCR_DYN_BOOL || recv->kind == SCR_DYN_STR) && - dyn_name_is(method, "valueOf")) return scr_dyn_retain(recv); + (mid == DYN_M_valueOf)) return scr_dyn_retain(recv); - if (recv->kind == SCR_DYN_NUM && dyn_name_is(method, "toFixed")) { + if (recv->kind == SCR_DYN_NUM && (mid == DYN_M_toFixed)) { double digits = dyn_index_arg(args, argc, 0, 0, what); if (scr_exc_pending()) return NULL; ScrStr *text = scr_num_to_fixed(recv->v.num, digits); @@ -949,7 +973,7 @@ static ScrDyn *scr_dyn_invoke_impl( scr_str_release(text); return result; } - if (recv->kind == SCR_DYN_NUM && dyn_name_is(method, "toExponential")) { + if (recv->kind == SCR_DYN_NUM && (mid == DYN_M_toExponential)) { if (argc && args[0]->kind != SCR_DYN_UNDEF) { dyn_throw_unsupported("Number", method); return NULL; @@ -962,7 +986,7 @@ static ScrDyn *scr_dyn_invoke_impl( if (recv->kind == SCR_DYN_STR) { ScrStr *s = recv->v.str; - if (dyn_name_is(method, "slice")) { + if ((mid == DYN_M_slice)) { double start = dyn_index_arg(args, argc, 0, 0, what); if (scr_exc_pending()) return NULL; double end = dyn_index_arg(args, argc, 1, scr_str_utf16_len(s), what); @@ -972,16 +996,16 @@ static ScrDyn *scr_dyn_invoke_impl( scr_str_release(piece); return r; } - if (dyn_name_is(method, "at") || dyn_name_is(method, "concat") || - dyn_name_is(method, "indexOf") || dyn_name_is(method, "lastIndexOf") || - dyn_name_is(method, "includes")) { - if ((dyn_name_is(method, "indexOf") || dyn_name_is(method, "lastIndexOf") || - dyn_name_is(method, "includes")) && + if ((mid == DYN_M_at) || (mid == DYN_M_concat) || + (mid == DYN_M_indexOf) || (mid == DYN_M_lastIndexOf) || + (mid == DYN_M_includes)) { + if (((mid == DYN_M_indexOf) || (mid == DYN_M_lastIndexOf) || + (mid == DYN_M_includes)) && argc >= 1 && args[0]->kind == SCR_DYN_STR) { - double position = dyn_index_arg(args, argc, 1, dyn_name_is(method, "lastIndexOf") ? INFINITY : 0, what); + double position = dyn_index_arg(args, argc, 1, (mid == DYN_M_lastIndexOf) ? INFINITY : 0, what); if (scr_exc_pending()) return NULL; - if (dyn_name_is(method, "includes")) return scr_dyn_new_bool(scr_str_index_of(s, args[0]->v.str, position) >= 0); - if (dyn_name_is(method, "indexOf")) return scr_dyn_new_num(scr_str_index_of(s, args[0]->v.str, position)); + if ((mid == DYN_M_includes)) return scr_dyn_new_bool(scr_str_index_of(s, args[0]->v.str, position) >= 0); + if ((mid == DYN_M_indexOf)) return scr_dyn_new_num(scr_str_index_of(s, args[0]->v.str, position)); return scr_dyn_new_num(scr_str_last_index_of_from(s, args[0]->v.str, position)); } dyn_throw_unsupported("String", method); @@ -992,19 +1016,19 @@ static ScrDyn *scr_dyn_invoke_impl( } if (recv->kind == SCR_DYN_ARR) { - if (dyn_name_is(method, "keys") || dyn_name_is(method, "values") || dyn_name_is(method, "entries")) + if ((mid == DYN_M_keys) || (mid == DYN_M_values) || (mid == DYN_M_entries)) return scr_dyn_native_handle_iterator(callback_recv ? callback_recv : recv, - dyn_name_is(method, "keys") ? 1 : dyn_name_is(method, "entries") ? 2 : 0); + (mid == DYN_M_keys) ? 1 : (mid == DYN_M_entries) ? 2 : 0); size_t len = recv->v.arr.len; if (recv->v.arr.presence) { bool sparse = false; for (size_t i = 0; i < len; i++) if (!recv->v.arr.presence[i]) { sparse = true; break; } if (!sparse) { free(recv->v.arr.presence); recv->v.arr.presence = NULL; } - else if (dyn_name_is(method, "forEach") || dyn_name_is(method, "map") || dyn_name_is(method, "filter") || - dyn_name_is(method, "some") || dyn_name_is(method, "every") || dyn_name_is(method, "find") || - dyn_name_is(method, "findIndex") || dyn_name_is(method, "findLast") || dyn_name_is(method, "findLastIndex") || - dyn_name_is(method, "reduce") || dyn_name_is(method, "reduceRight") || dyn_name_is(method, "slice") || - dyn_name_is(method, "indexOf") || dyn_name_is(method, "lastIndexOf") || dyn_name_is(method, "includes")) { + else if ((mid == DYN_M_forEach) || (mid == DYN_M_map) || (mid == DYN_M_filter) || + (mid == DYN_M_some) || (mid == DYN_M_every) || (mid == DYN_M_find) || + (mid == DYN_M_findIndex) || (mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex) || + (mid == DYN_M_reduce) || (mid == DYN_M_reduceRight) || (mid == DYN_M_slice) || + (mid == DYN_M_indexOf) || (mid == DYN_M_lastIndexOf) || (mid == DYN_M_includes)) { ScrDyn *pack = scr_dyn_new_arr(); for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(pack, scr_dyn_retain(args[i])); ScrStr *name = scr_str_new(method, strlen(method)); @@ -1012,7 +1036,7 @@ static ScrDyn *scr_dyn_invoke_impl( scr_str_release(name); scr_dyn_release(pack); return result; - } else if (dyn_arr_proto_mutates(method) && !dyn_name_is(method, "fill") && !dyn_name_is(method, "push") && !dyn_name_is(method, "pop") && !dyn_name_is(method, "shift")) { + } else if (dyn_arr_proto_mutates(mid) && !(mid == DYN_M_fill) && !(mid == DYN_M_push) && !(mid == DYN_M_pop) && !(mid == DYN_M_shift)) { static const char message[] = "This checked array mutation over holes is not supported yet"; scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); return NULL; @@ -1020,11 +1044,11 @@ static ScrDyn *scr_dyn_invoke_impl( } size_t splice_start = 0; size_t splice_removed = 0; - if (dyn_arr_proto_mutates(method)) { - bool grows = dyn_name_is(method, "push") && argc > 0; - grows = grows || (dyn_name_is(method, "unshift") && argc > 0); - bool shrinks = (dyn_name_is(method, "pop") || dyn_name_is(method, "shift")) && len > 0; - if (dyn_name_is(method, "splice")) { + if (dyn_arr_proto_mutates(mid)) { + bool grows = (mid == DYN_M_push) && argc > 0; + grows = grows || ((mid == DYN_M_unshift) && argc > 0); + bool shrinks = ((mid == DYN_M_pop) || (mid == DYN_M_shift)) && len > 0; + if ((mid == DYN_M_splice)) { double startD = dyn_index_arg(args, argc, 0, 0, what); if (scr_exc_pending()) return NULL; splice_start = dyn_rel_index(startD, len); @@ -1061,33 +1085,33 @@ static ScrDyn *scr_dyn_invoke_impl( return NULL; } } - if (recv->v.arr.frozen && (dyn_name_is(method, "push") || - dyn_name_is(method, "pop") || dyn_name_is(method, "shift") || - dyn_name_is(method, "unshift") || dyn_name_is(method, "splice"))) { + if (recv->v.arr.frozen && ((mid == DYN_M_push) || + (mid == DYN_M_pop) || (mid == DYN_M_shift) || + (mid == DYN_M_unshift) || (mid == DYN_M_splice))) { static const char message[] = "Cannot assign to read only property 'length'"; scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); return NULL; } - if ((grows && recv->non_extensible) || (shrinks && recv->v.arr.sealed && !dyn_name_is(method, "shift")) || - (recv->v.arr.frozen && ((dyn_name_is(method, "sort") && len > 1) || - (dyn_name_is(method, "reverse") && len > 1)))) { + if ((grows && recv->non_extensible) || (shrinks && recv->v.arr.sealed && !(mid == DYN_M_shift)) || + (recv->v.arr.frozen && (((mid == DYN_M_sort) && len > 1) || + ((mid == DYN_M_reverse) && len > 1)))) { static const char message[] = "Cannot modify a sealed or frozen array"; scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); return NULL; } } - if (dyn_name_is(method, "push")) { + if ((mid == DYN_M_push)) { for (size_t i = 0; i < argc; i++) scr_dyn_arr_push(recv, scr_dyn_retain(args[i])); return scr_dyn_new_num((double)recv->v.arr.len); } - if (dyn_name_is(method, "pop")) { + if ((mid == DYN_M_pop)) { if (len == 0) return scr_dyn_retain(scr_dyn_undefined()); ScrDyn *result = scr_dyn_arr_at(recv, (double)(len - 1)); if (!result) return NULL; scr_dyn_release(recv->v.arr.items[--recv->v.arr.len]); return result; } - if (dyn_name_is(method, "shift")) { + if ((mid == DYN_M_shift)) { if (len == 0) return scr_dyn_retain(scr_dyn_undefined()); if (recv->v.arr.sealed) { if (recv->v.arr.frozen) { @@ -1109,7 +1133,7 @@ static ScrDyn *scr_dyn_invoke_impl( recv->v.arr.len = len - 1; return first; /* ownership moves out */ } - if (dyn_name_is(method, "unshift")) { + if ((mid == DYN_M_unshift)) { /* Grow the backing store before shifting the old elements. The * appended placeholders are immortal, so overwriting them does not * abandon retained argument references. */ @@ -1121,7 +1145,7 @@ static ScrDyn *scr_dyn_invoke_impl( } return scr_dyn_new_num((double)recv->v.arr.len); } - if (dyn_name_is(method, "splice")) { + if ((mid == DYN_M_splice)) { size_t start = splice_start; size_t removed = splice_removed; size_t inserted = argc > 2 ? argc - 2 : 0; @@ -1151,7 +1175,7 @@ static ScrDyn *scr_dyn_invoke_impl( recv->v.arr.len = next_len; return out; } - if (dyn_name_is(method, "slice")) { + if ((mid == DYN_M_slice)) { double startD = dyn_index_arg(args, argc, 0, 0, what); if (scr_exc_pending()) return NULL; double endD = dyn_index_arg(args, argc, 1, (double)len, what); @@ -1163,7 +1187,7 @@ static ScrDyn *scr_dyn_invoke_impl( for (size_t i = start; i < end; i++) scr_dyn_arr_push(out, scr_dyn_retain(recv->v.arr.items[i])); return out; } - if (dyn_name_is(method, "at")) { + if ((mid == DYN_M_at)) { double iD = dyn_index_arg(args, argc, 0, 0, what); if (scr_exc_pending()) return NULL; if (!dyn_arr_length_unchanged(recv, len)) return NULL; @@ -1171,17 +1195,17 @@ static ScrDyn *scr_dyn_invoke_impl( if (idx < 0 || idx >= (double)len) return scr_dyn_retain(scr_dyn_undefined()); return scr_dyn_arr_at(recv, idx); } - if (dyn_name_is(method, "indexOf") || dyn_name_is(method, "lastIndexOf") || - dyn_name_is(method, "includes")) { - if (len == 0) return dyn_name_is(method, "includes") ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1); + if ((mid == DYN_M_indexOf) || (mid == DYN_M_lastIndexOf) || + (mid == DYN_M_includes)) { + if (len == 0) return (mid == DYN_M_includes) ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1); ScrDyn *needle = argc > 0 ? args[0] : scr_dyn_undefined(); - double from = dyn_name_is(method, "lastIndexOf") ? (double)len - 1 : 0; + double from = (mid == DYN_M_lastIndexOf) ? (double)len - 1 : 0; if (argc > 1) { from = dyn_index_arg(args, argc, 1, 0, what); if (scr_exc_pending()) return NULL; } if (!dyn_arr_length_unchanged(recv, len)) return NULL; - if (dyn_name_is(method, "lastIndexOf")) { + if ((mid == DYN_M_lastIndexOf)) { double first = from < 0 ? (double)len + from : from; if (first >= (double)len) first = (double)len - 1; for (size_t i = first < 0 ? 0 : (size_t)first + 1; i > 0; i--) { @@ -1193,16 +1217,16 @@ static ScrDyn *scr_dyn_invoke_impl( if (first < 0) first = 0; for (size_t i = first >= (double)len ? len : (size_t)first; i < len; i++) { const ScrDyn *item = recv->v.arr.items[i]; - bool nan_match = dyn_name_is(method, "includes") && + bool nan_match = (mid == DYN_M_includes) && item->kind == SCR_DYN_NUM && needle->kind == SCR_DYN_NUM && item->v.num != item->v.num && needle->v.num != needle->v.num; if (nan_match || scr_dyn_strict_eq(item, needle)) { - return dyn_name_is(method, "includes") ? scr_dyn_new_bool(true) : scr_dyn_new_num((double)i); + return (mid == DYN_M_includes) ? scr_dyn_new_bool(true) : scr_dyn_new_num((double)i); } } - return dyn_name_is(method, "includes") ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1); + return (mid == DYN_M_includes) ? scr_dyn_new_bool(false) : scr_dyn_new_num(-1); } - if (dyn_name_is(method, "findLast") || dyn_name_is(method, "findLastIndex")) { + if ((mid == DYN_M_findLast) || (mid == DYN_M_findLastIndex)) { if (!dyn_cb_check(args, argc)) return NULL; ScrDyn *visible_recv = callback_recv ? callback_recv : recv; for (size_t i = len; i > 0; i--) { @@ -1215,16 +1239,16 @@ static ScrDyn *scr_dyn_invoke_impl( bool matched = scr_dyn_truthy(result); scr_dyn_release(result); if (matched) { - if (dyn_name_is(method, "findLast")) return item; + if ((mid == DYN_M_findLast)) return item; scr_dyn_release(item); return scr_dyn_new_num((double)index); } scr_dyn_release(item); } - return dyn_name_is(method, "findLast") + return (mid == DYN_M_findLast) ? scr_dyn_retain(scr_dyn_undefined()) : scr_dyn_new_num(-1); } - if (dyn_name_is(method, "join")) { + if ((mid == DYN_M_join)) { ScrJsonBuf b; scr_jb_init(&b); ScrStr *separator = argc > 0 && args[0]->kind != SCR_DYN_UNDEF @@ -1250,7 +1274,7 @@ static ScrDyn *scr_dyn_invoke_impl( scr_str_release(joined); return r; } - if (dyn_name_is(method, "concat")) { + if ((mid == DYN_M_concat)) { ScrDyn *out = scr_dyn_new_arr(); for (size_t i = 0; i < len; i++) { if (!dyn_arr_copy_item(out, recv, i, true)) { scr_dyn_release(out); return NULL; } @@ -1271,7 +1295,7 @@ static ScrDyn *scr_dyn_invoke_impl( } return out; } - if (dyn_name_is(method, "flat")) { + if ((mid == DYN_M_flat)) { double depth = dyn_index_arg(args, argc, 0, 1, what); if (scr_exc_pending()) return NULL; if (!dyn_arr_length_unchanged(recv, len)) return NULL; @@ -1283,7 +1307,7 @@ static ScrDyn *scr_dyn_invoke_impl( } return out; } - if (dyn_name_is(method, "reverse")) { + if ((mid == DYN_M_reverse)) { for (size_t i = 0; i < len / 2; i++) { ScrDyn *tmp = recv->v.arr.items[i]; recv->v.arr.items[i] = recv->v.arr.items[len - 1 - i]; @@ -1291,12 +1315,12 @@ static ScrDyn *scr_dyn_invoke_impl( } return scr_dyn_retain(recv); } - if (dyn_name_is(method, "toReversed")) { + if ((mid == DYN_M_toReversed)) { ScrDyn *out = scr_dyn_new_arr(); if (!dyn_arr_reversed_copy(out, recv)) { scr_dyn_release(out); return NULL; } return out; } - if (dyn_name_is(method, "with")) { + if ((mid == DYN_M_with)) { double indexD = dyn_index_arg(args, argc, 0, 0, what); if (scr_exc_pending()) return NULL; if (!dyn_arr_length_unchanged(recv, len)) return NULL; @@ -1314,7 +1338,7 @@ static ScrDyn *scr_dyn_invoke_impl( } return out; } - if (dyn_name_is(method, "toSpliced")) { + if ((mid == DYN_M_toSpliced)) { double startD = dyn_index_arg(args, argc, 0, 0, what); if (scr_exc_pending()) return NULL; size_t start = dyn_rel_index(startD, len); @@ -1331,7 +1355,7 @@ static ScrDyn *scr_dyn_invoke_impl( for (size_t i = start + delete_count; i < len; i++) if (!dyn_arr_copy_item(out, recv, i, false)) { scr_dyn_release(out); return NULL; } return out; } - if (dyn_name_is(method, "fill")) { + if ((mid == DYN_M_fill)) { double startD = dyn_index_arg(args, argc, 1, 0, what); if (scr_exc_pending()) return NULL; double endD = dyn_index_arg(args, argc, 2, (double)len, what); @@ -1359,7 +1383,7 @@ static ScrDyn *scr_dyn_invoke_impl( } return scr_dyn_retain(recv); } - if (dyn_name_is(method, "copyWithin")) { + if ((mid == DYN_M_copyWithin)) { double targetD = dyn_index_arg(args, argc, 0, 0, what); if (scr_exc_pending()) return NULL; double startD = dyn_index_arg(args, argc, 1, 0, what); @@ -1392,13 +1416,13 @@ static ScrDyn *scr_dyn_invoke_impl( } return scr_dyn_retain(recv); } - if (dyn_name_is(method, "forEach") || dyn_name_is(method, "map") || - dyn_name_is(method, "filter") || dyn_name_is(method, "some") || - dyn_name_is(method, "every") || dyn_name_is(method, "find") || - dyn_name_is(method, "findIndex")) { + if ((mid == DYN_M_forEach) || (mid == DYN_M_map) || + (mid == DYN_M_filter) || (mid == DYN_M_some) || + (mid == DYN_M_every) || (mid == DYN_M_find) || + (mid == DYN_M_findIndex)) { if (!dyn_cb_check(args, argc)) return NULL; ScrDyn *cb = args[0]; - ScrDyn *out = (dyn_name_is(method, "map") || dyn_name_is(method, "filter")) ? scr_dyn_new_arr() : NULL; + ScrDyn *out = ((mid == DYN_M_map) || (mid == DYN_M_filter)) ? scr_dyn_new_arr() : NULL; /* Array iteration methods snapshot length before the first callback. * A shrinking callback can remove a later dense entry; an expanding * callback must not make the new tail observable to this pass. Live @@ -1413,30 +1437,30 @@ static ScrDyn *scr_dyn_invoke_impl( ScrDyn *item = scr_dyn_retain(recv->v.arr.items[i]); ScrDyn *r = dyn_call_cb(cb, item, i, visible_recv); if (!r) { scr_dyn_release(item); scr_dyn_release(out); return NULL; } - if (dyn_name_is(method, "map")) { + if ((mid == DYN_M_map)) { scr_dyn_arr_push(out, r); /* ownership moves in */ r = NULL; } else { bool truthy = scr_dyn_truthy(r); scr_dyn_release(r); - if (dyn_name_is(method, "filter") && truthy) scr_dyn_arr_push(out, scr_dyn_retain(item)); - if (dyn_name_is(method, "some") && truthy) { scr_dyn_release(item); return scr_dyn_new_bool(true); } - if (dyn_name_is(method, "every") && !truthy) { scr_dyn_release(item); return scr_dyn_new_bool(false); } - if (dyn_name_is(method, "find") && truthy) return item; /* +1 moves out */ - if (dyn_name_is(method, "findIndex") && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)i); } + if ((mid == DYN_M_filter) && truthy) scr_dyn_arr_push(out, scr_dyn_retain(item)); + if ((mid == DYN_M_some) && truthy) { scr_dyn_release(item); return scr_dyn_new_bool(true); } + if ((mid == DYN_M_every) && !truthy) { scr_dyn_release(item); return scr_dyn_new_bool(false); } + if ((mid == DYN_M_find) && truthy) return item; /* +1 moves out */ + if ((mid == DYN_M_findIndex) && truthy) { scr_dyn_release(item); return scr_dyn_new_num((double)i); } } scr_dyn_release(item); } if (out) return out; - if (dyn_name_is(method, "some")) return scr_dyn_new_bool(false); - if (dyn_name_is(method, "every")) return scr_dyn_new_bool(true); - if (dyn_name_is(method, "find")) return scr_dyn_retain(scr_dyn_undefined()); - if (dyn_name_is(method, "findIndex")) return scr_dyn_new_num(-1); + if ((mid == DYN_M_some)) return scr_dyn_new_bool(false); + if ((mid == DYN_M_every)) return scr_dyn_new_bool(true); + if ((mid == DYN_M_find)) return scr_dyn_retain(scr_dyn_undefined()); + if ((mid == DYN_M_findIndex)) return scr_dyn_new_num(-1); return scr_dyn_retain(scr_dyn_undefined()); /* forEach */ } - if (dyn_name_is(method, "reduce") || dyn_name_is(method, "reduceRight")) { + if ((mid == DYN_M_reduce) || (mid == DYN_M_reduceRight)) { if (!dyn_cb_check(args, argc)) return NULL; - bool backward = dyn_name_is(method, "reduceRight"); + bool backward = (mid == DYN_M_reduceRight); ScrDyn *acc = argc > 1 ? scr_dyn_retain(args[1]) : NULL; ScrDyn *visible_recv = callback_recv ? callback_recv : recv; for (size_t step = 0; step < len; step++) { @@ -1458,7 +1482,7 @@ static ScrDyn *scr_dyn_invoke_impl( scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); return NULL; } - if (dyn_name_is(method, "flatMap")) { + if ((mid == DYN_M_flatMap)) { /* JS Array.prototype.flatMap over a dyn array: map + a depth-1 * flatten. Native dyn-array results flatten element-by-element; a * WRAPPED engine array flattens through the routed keyed reads @@ -1496,7 +1520,7 @@ static ScrDyn *scr_dyn_invoke_impl( } return out; } - if (dyn_name_is(method, "sort")) { + if ((mid == DYN_M_sort)) { ScrDyn *cmp = argc > 0 ? args[0] : scr_dyn_undefined(); if (cmp->kind != SCR_DYN_UNDEF && cmp->kind != SCR_DYN_FUNC) { /* V8 appends the received value's string image. */ @@ -1510,7 +1534,7 @@ static ScrDyn *scr_dyn_invoke_impl( if (len > 1 && !dyn_arr_sort(recv, cmp->kind == SCR_DYN_FUNC ? cmp : NULL)) return NULL; return scr_dyn_retain(recv); } - if (dyn_name_is(method, "toSorted")) { + if ((mid == DYN_M_toSorted)) { ScrDyn *cmp = argc > 0 ? args[0] : scr_dyn_undefined(); if (cmp->kind != SCR_DYN_UNDEF && cmp->kind != SCR_DYN_FUNC) { static const char message[] = "The comparison function must be either a function or undefined"; @@ -1525,7 +1549,7 @@ static ScrDyn *scr_dyn_invoke_impl( } return out; } - if (dyn_arr_proto_unimpl(method)) { + if (dyn_arr_proto_unimpl(mid)) { dyn_throw_unsupported("Array", method); return NULL; } @@ -1541,14 +1565,14 @@ static ScrDyn *scr_dyn_invoke_impl( #ifdef SCR_LIB scr_trap("scriptc: promise dispatch is unavailable in library mode\n"); #else - if (dyn_name_is(method, "then")) { + if ((mid == DYN_M_then)) { return scr_dyn_promise_then(recv->v.promise, argc >= 1 ? args[0] : NULL, argc >= 2 ? args[1] : NULL, NULL); } - if (dyn_name_is(method, "catch")) { + if ((mid == DYN_M_catch)) { return scr_dyn_promise_then(recv->v.promise, NULL, argc >= 1 ? args[0] : NULL, NULL); } - if (dyn_name_is(method, "finally")) { + if ((mid == DYN_M_finally)) { return scr_dyn_promise_then(recv->v.promise, NULL, NULL, argc >= 1 ? args[0] : NULL); } dyn_throw_not_fn(what); @@ -1600,44 +1624,44 @@ static ScrDyn *scr_dyn_invoke_impl( } /* The typed array methods below are not DataView's; it inherits * Object.prototype's. */ - if (dyn_name_is(method, "toString") || dyn_name_is(method, "toLocaleString")) { + if ((mid == DYN_M_toString) || (mid == DYN_M_toLocaleString)) { ScrStr *tag = scr_str_new("[object DataView]", 17); ScrDyn *out = scr_dyn_new_str(tag); scr_str_release(tag); return out; } - if (dyn_name_is(method, "valueOf")) return scr_dyn_retain(recv); + if ((mid == DYN_M_valueOf)) return scr_dyn_retain(recv); dyn_throw_not_fn(what); return NULL; } size_t blen = bytes->len; - if (dyn_name_is(method, "at")) { + if ((mid == DYN_M_at)) { double iD = dyn_bytes_index_arg(args, argc, 0, 0); if (scr_exc_pending()) return NULL; double idx = iD < 0 ? (double)blen + iD : iD; if (idx < 0 || idx >= (double)blen) return scr_dyn_retain(scr_dyn_undefined()); return scr_dyn_new_num(scr_bytes_get(bytes, idx)); } - if (dyn_name_is(method, "slice") || dyn_name_is(method, "subarray")) { + if ((mid == DYN_M_slice) || (mid == DYN_M_subarray)) { /* subarray and Buffer.slice alias their source. TypedArray.slice * owns an independent copy; all keep the receiver's Buffer flavor. */ double startD = dyn_bytes_index_arg(args, argc, 0, 0); if (scr_exc_pending()) return NULL; double endD = dyn_bytes_index_arg(args, argc, 1, (double)blen); if (scr_exc_pending()) return NULL; - ScrBytes *out = recv->buffer || dyn_name_is(method, "subarray") + ScrBytes *out = recv->buffer || (mid == DYN_M_subarray) ? scr_bytes_subarray(bytes, startD, endD) : scr_bytes_slice(bytes, startD, endD); ScrDyn *d = recv->buffer ? scr_dyn_new_buffer(out) : scr_dyn_new_bytes(out); scr_bytes_release(out); return d; } - if (dyn_name_is(method, "set")) { + if ((mid == DYN_M_set)) { double offset = dyn_bytes_index_arg(args, argc, 1, 0); if (scr_exc_pending()) return NULL; scr_bytes_set_from_dyn(bytes, argc ? args[0] : scr_dyn_undefined(), offset); return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined()); } - if (dyn_name_is(method, "copyWithin")) { + if ((mid == DYN_M_copyWithin)) { double target = dyn_bytes_index_arg(args, argc, 0, 0); if (scr_exc_pending()) return NULL; double start = dyn_bytes_index_arg(args, argc, 1, 0); @@ -1647,7 +1671,7 @@ static ScrDyn *scr_dyn_invoke_impl( scr_bytes_release(scr_bytes_copy_within(bytes, target, start, end)); return scr_dyn_retain(recv); } - if (dyn_name_is(method, "fill") && !recv->buffer) { + if ((mid == DYN_M_fill) && !recv->buffer) { double value; if (!scr_dyn_number_coerce_js(argc ? args[0] : scr_dyn_undefined(), &value)) return NULL; double start = dyn_bytes_index_arg(args, argc, 1, 0); @@ -1657,8 +1681,8 @@ static ScrDyn *scr_dyn_invoke_impl( scr_bytes_release(scr_bytes_fill_elem(bytes, value, start, end)); return scr_dyn_retain(recv); } - if (dyn_name_is(method, "join") || (dyn_name_is(method, "toString") && !recv->buffer)) { - ScrStr *separator = dyn_name_is(method, "join") && argc && args[0]->kind != SCR_DYN_UNDEF + if ((mid == DYN_M_join) || ((mid == DYN_M_toString) && !recv->buffer)) { + ScrStr *separator = (mid == DYN_M_join) && argc && args[0]->kind != SCR_DYN_UNDEF ? scr_dyn_string_coerce_js(args[0]) : scr_str_new(",", 1); if (scr_exc_pending()) { scr_str_release(separator); return NULL; } ScrStr *joined = scr_bytes_join(bytes, separator); @@ -1667,7 +1691,7 @@ static ScrDyn *scr_dyn_invoke_impl( scr_str_release(joined); return out; } - if (dyn_bytes_proto_real(method)) { + if (dyn_bytes_proto_real(mid)) { dyn_throw_unsupported(scr_bytes_elem_name(bytes->elem), method); return NULL; } diff --git a/packages/runtime/src/scr_dyn_methods.h b/packages/runtime/src/scr_dyn_methods.h new file mode 100644 index 000000000..49e5c7254 --- /dev/null +++ b/packages/runtime/src/scr_dyn_methods.h @@ -0,0 +1,160 @@ +/* Generated by scripts/gen-dyn-methods.mjs from the DYN_M_ ids scr_dyn_invoke.c + * uses. Do not edit by hand. */ +#ifndef SCR_DYN_METHODS_H +#define SCR_DYN_METHODS_H + +#include +#include + +enum { + DYN_M_NONE, + DYN_M_apply, + DYN_M_at, + DYN_M_bind, + DYN_M_call, + DYN_M_catch, + DYN_M_concat, + DYN_M_copyWithin, + DYN_M_entries, + DYN_M_every, + DYN_M_fill, + DYN_M_filter, + DYN_M_finally, + DYN_M_find, + DYN_M_findIndex, + DYN_M_findLast, + DYN_M_findLastIndex, + DYN_M_flat, + DYN_M_flatMap, + DYN_M_forEach, + DYN_M_includes, + DYN_M_indexOf, + DYN_M_join, + DYN_M_keys, + DYN_M_lastIndexOf, + DYN_M_map, + DYN_M_pop, + DYN_M_push, + DYN_M_reduce, + DYN_M_reduceRight, + DYN_M_reverse, + DYN_M_set, + DYN_M_shift, + DYN_M_slice, + DYN_M_some, + DYN_M_sort, + DYN_M_splice, + DYN_M_subarray, + DYN_M_then, + DYN_M_toExponential, + DYN_M_toFixed, + DYN_M_toLocaleString, + DYN_M_toReversed, + DYN_M_toSorted, + DYN_M_toSpliced, + DYN_M_toString, + DYN_M_unshift, + DYN_M_valueOf, + DYN_M_values, + DYN_M_with, + DYN_M_COUNT +}; + +static const char *const dyn_method_names[DYN_M_COUNT] = { + NULL, + "apply", + "at", + "bind", + "call", + "catch", + "concat", + "copyWithin", + "entries", + "every", + "fill", + "filter", + "finally", + "find", + "findIndex", + "findLast", + "findLastIndex", + "flat", + "flatMap", + "forEach", + "includes", + "indexOf", + "join", + "keys", + "lastIndexOf", + "map", + "pop", + "push", + "reduce", + "reduceRight", + "reverse", + "set", + "shift", + "slice", + "some", + "sort", + "splice", + "subarray", + "then", + "toExponential", + "toFixed", + "toLocaleString", + "toReversed", + "toSorted", + "toSpliced", + "toString", + "unshift", + "valueOf", + "values", + "with", +}; + +static const unsigned char dyn_method_lengths[DYN_M_COUNT] = { + 0, 5, 2, 4, 4, 5, 6, 10, 7, 5, 4, 6, 7, 4, 9, 8, + 13, 4, 7, 7, 8, 7, 4, 4, 11, 3, 3, 4, 6, 11, 7, 3, + 5, 5, 4, 4, 6, 8, 4, 13, 7, 14, 10, 8, 9, 8, 7, 7, + 6, 4, +}; + +/* Each name's id at its slot (or the next free one); 0 ends a probe. */ +static const unsigned char dyn_method_slots[256] = { + 0, 0, 0, 0, 12, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 14, 0, 0, 27, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 28, 3, 0, + 22, 0, 20, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, + 0, 0, 0, 0, 0, 33, 31, 0, 0, 0, 0, 0, 36, 35, 0, 0, + 0, 0, 0, 0, 23, 4, 32, 0, 5, 0, 0, 0, 0, 0, 0, 29, + 0, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 6, 0, 0, 37, 0, + 0, 40, 0, 0, 0, 0, 0, 0, 43, 0, 0, 45, 0, 0, 0, 44, + 0, 7, 0, 0, 0, 0, 42, 0, 25, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, + 46, 0, 0, 39, 0, 41, 0, 9, 0, 0, 13, 0, 0, 0, 0, 8, + 0, 47, 10, 0, 0, 0, 0, 48, 0, 0, 17, 0, 0, 49, 0, 0, + 0, 0, 0, 19, 0, 0, 11, 0, 0, 0, 0, 18, 0, 0, 0, 0, +}; + +#define DYN_METHOD_MAX_LENGTH 14 + +/* The id of a method name, compared by content; DYN_M_NONE for any other + * name. */ +static inline int dyn_method_of(const char *m) { + size_t length = strlen(m); + if (length == 0 || length > DYN_METHOD_MAX_LENGTH) return DYN_M_NONE; + unsigned slot = (unsigned)(length * 7 + (unsigned char)m[0] * 31 + + (unsigned char)m[length - 1]) & 255; + for (;;) { + unsigned id = dyn_method_slots[slot]; + if (id == 0) return DYN_M_NONE; + if (dyn_method_lengths[id] == length && memcmp(m, dyn_method_names[id], length) == 0) return (int)id; + slot = (slot + 1) & 255; + } +} + +#endif diff --git a/packages/runtime/src/scr_dyn_methods.test.ts b/packages/runtime/src/scr_dyn_methods.test.ts new file mode 100644 index 000000000..77911aa2d --- /dev/null +++ b/packages/runtime/src/scr_dyn_methods.test.ts @@ -0,0 +1,10 @@ +import { execFileSync } from "node:child_process"; +import { join } from "node:path"; +import { expect, test } from "vitest"; + +test("scr_dyn_methods.h lists every method id the checked-dynamic dispatch compares", () => { + const script = join(import.meta.dirname, "../../../scripts/gen-dyn-methods.mjs"); + expect(() => + execFileSync(process.execPath, [script, "--check"], { stdio: "pipe" }), + ).not.toThrow(); +}); diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 0a7d95d18..63d258b2f 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -1772,12 +1772,35 @@ static void insp_inspect_function_cleanup(void) { g_inspect_function = NULL; } +/* util.inspect's own members (custom, colors, styles, defaultOptions) + * read or written through the function value: the tables are not modeled, + * so each throws SC2020 instead of answering undefined. */ +static ScrDyn *insp_inspect_member_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)args; (void)argc; + char message[128]; + int n = snprintf(message, sizeof message, "util.inspect.%s through the inspect function is not supported yet", + (const char *)closure->fn); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)n, "SC2020"); + return NULL; +} + static ScrDyn *insp_inspect_value(void) { if (!g_inspect_function) { ScrClosure *closure = scr_closure_new(NULL, 0); g_inspect_function = scr_dyn_new_func(closure, insp_inspect_call, 2, "native:util.inspect", "inspect"); scr_atexit(insp_inspect_function_cleanup); + static const char *const members[] = {"custom", "defaultOptions", "colors", "styles"}; + ScrDyn *table = scr_dyn_function_properties(g_inspect_function); + for (size_t i = 0; i < sizeof members / sizeof members[0]; i++) { + ScrDyn *get = scr_dyn_new_func(scr_closure_new((void *)members[i], 0), insp_inspect_member_call, 0, + "native:function", members[i]); + ScrDyn *set = scr_dyn_new_func(scr_closure_new((void *)members[i], 0), insp_inspect_member_call, 1, + "native:function", members[i]); + scr_dyn_obj_set_accessor(table, members[i], strlen(members[i]), get, set); + table->v.obj.entries[table->v.obj.len - 1].enumerable = false; + } + scr_dyn_release(table); } return scr_dyn_retain(g_inspect_function); } @@ -2544,11 +2567,16 @@ static ScrStr *insp_fmt_inspect_full(ScrDyn *value, const ScrInspFormatOptions * return insp_fmt_inspect(value, o, depth, o->compact, o->colors); } -static const InspFmtOps insp_fmt_full_ops = {insp_fmt_s, scr_util_format_j, insp_fmt_number, - insp_fmt_inspect_full}; - +/* The conversion tables are filled where they run: a constant table is a + * global, and sanitized builds keep instrumented globals (and the + * conversions they point at) through --gc-sections. */ ScrStr *scr_insp_format(ScrDyn *const *args, size_t argc, const ScrInspFormatOptions *o) { - return insp_format_loop(args, argc, o, &insp_fmt_full_ops); + InspFmtOps ops; + ops.s = insp_fmt_s; + ops.j = scr_util_format_j; + ops.number = insp_fmt_number; + ops.inspect = insp_fmt_inspect_full; + return insp_format_loop(args, argc, o, &ops); } /* The formatter over arguments that are all strings, numbers, booleans, @@ -2610,9 +2638,6 @@ static ScrStr *insp_prim_number(ScrDyn *v, int spec) { return scr_insp_f64(number); } -static const InspFmtOps insp_fmt_primitive_ops = {insp_prim_s, insp_prim_j, insp_prim_number, - insp_prim_inspect}; - /* A native list of checked values (the compiled format calls) as the * runtime's argument pack. +1. */ static ScrDyn *insp_list_pack(ScrArr *list) { @@ -2625,7 +2650,12 @@ ScrStr *scr_insp_format_primitives(ScrArr *list) { ScrInspFormatOptions o; scr_insp_format_defaults(&o, "util.format"); ScrDyn *pack = insp_list_pack(list); - ScrStr *out = insp_format_loop(pack->v.arr.items, pack->v.arr.len, &o, &insp_fmt_primitive_ops); + InspFmtOps ops; + ops.s = insp_prim_s; + ops.j = insp_prim_j; + ops.number = insp_prim_number; + ops.inspect = insp_prim_inspect; + ScrStr *out = insp_format_loop(pack->v.arr.items, pack->v.arr.len, &o, &ops); scr_dyn_release(pack); return out; } diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 9eadedc6e..13b9644bc 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -207,6 +207,19 @@ void scr_dyn_install_async_iterator_symbol(ScrDyn *key) { scr_atexit(scr_async_iterator_symbol_cleanup); } +/* Symbol.hasInstance, installed when the program first evaluates it: a + * function can only define a hook after reading the symbol. */ +static SCR_TL ScrDyn *scr_has_instance_symbol; +static void scr_has_instance_symbol_cleanup(void) { + scr_dyn_release(scr_has_instance_symbol); + scr_has_instance_symbol = NULL; +} +void scr_dyn_install_has_instance_symbol(ScrDyn *key) { + if (scr_has_instance_symbol) return; + scr_has_instance_symbol = scr_dyn_retain(key); + scr_atexit(scr_has_instance_symbol_cleanup); +} + void scr_dyn_install_iterator_symbol(ScrDyn *key) { if (scr_iterator_symbol) return; scr_iterator_symbol = scr_dyn_retain(key); @@ -1575,6 +1588,50 @@ static ScrDyn *scr_dyn_not_iterable(const ScrDyn *src, const ScrStr *msg) { return NULL; } +static ScrDyn *scr_native_iterator_drain(const ScrDyn *src); + +/* Math.max(...src) / Math.min(...src) over an untyped iterable: every + * value's ToNumber runs in order, then NaN poisons and ±0 orders as in + * Node. A non-iterable source throws V8's call-spread TypeError: `what` + * names the source for null and undefined, every other kind gets the + * Symbol.iterator wording. NaN with the exception pending when packing or + * a coercion throws. */ +double scr_dyn_math_fold(const ScrDyn *src, bool max, const ScrStr *what) { + if (src->kind == SCR_DYN_UNDEF || src->kind == SCR_DYN_NULL) { + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_write(&b, what->data, what->len); + scr_jb_puts(&b, src->kind == SCR_DYN_NULL ? " is not iterable (cannot read property null)" + : " is not iterable (cannot read property undefined)"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b)); + return NAN; + } + static const char spread_msg[] = "Spread syntax requires ...iterable[Symbol.iterator] to be a function"; + ScrStr *msg = scr_str_new(spread_msg, sizeof spread_msg - 1); + /* The spread takes every value before the call: arrays, strings, bytes + * and collections pack, a native iterator (a Map's keys()) drains, and + * other iterables (a generator, an object with a Symbol.iterator method) + * step their iterator to the end. */ + ScrDyn *pack = src->kind == SCR_DYN_HANDLE && (src->v.handle.tag == SCR_DYNH_ITERATOR || + scr_dyn_handle_ops_of(src)->iter_step) + ? scr_native_iterator_drain(src) + : scr_dyn_destructure_rest(src, msg); + scr_str_release(msg); + if (!pack) return NAN; + double best = max ? -INFINITY : INFINITY; + bool nan = false; + for (size_t i = 0; i < pack->v.arr.len; i++) { + double v = scr_dyn_number_coerce(pack->v.arr.items[i]); + if (scr_exc_pending()) break; + if (isnan(v)) nan = true; + else if (max ? (v > best || (v == 0.0 && best == 0.0 && !signbit(v))) + : (v < best || (v == 0.0 && best == 0.0 && signbit(v)))) + best = v; + } + scr_dyn_release(pack); + return nan ? NAN : best; +} + ScrDyn *scr_dyn_iter_pack(const ScrDyn *src, const ScrStr *msg) { if (src->kind == SCR_DYN_HANDLE && scr_dyn_handle_ops_of(src)->iter_pack) return scr_dyn_handle_ops_of(src)->iter_pack(src->v.handle.ptr); @@ -1888,6 +1945,25 @@ ScrDyn *scr_dyn_error_constructor(const char *name) { return NULL; } +/* `Error.stackTraceLimit` read or written through the Error constructor + * held as a value (zod's `const E = Error; E.stackTraceLimit = 0`): the + * same limit the static member reads and stack capture honors. */ +static bool scr_dyn_is_stack_limit(const ScrDyn *d, const char *key, size_t key_len) { + return key_len == 15 && memcmp(key, "stackTraceLimit", 15) == 0 && d->kind == SCR_DYN_FUNC && + scr_error_ctor_values[0] && d->v.fn.clo == scr_error_ctor_values[0]->v.fn.clo; +} + +/* An own `stackTraceLimit` on the Error value, assigned a non-number or + * defined with Object.defineProperty, replaces the limit for reads and + * writes through the value; writes to it keep its descriptor. */ +static bool scr_dyn_stack_limit_shadowed(const ScrDyn *d) { + ScrClosure *owner = scr_closure_identity(d->v.fn.clo); + ScrDyn *table = owner->props ? (ScrDyn *)scr_box_get_ref(owner->props) : NULL; + bool own = table && scr_dyn_find_entry(table, "stackTraceLimit", 15); + scr_dyn_release(table); + return own; +} + ScrDyn *scr_dyn_object_prototype(void) { if (!scr_builtin_object_prototype) { scr_builtin_object_prototype = scr_dyn_alloc(SCR_DYN_OBJ); @@ -4071,12 +4147,49 @@ static int scr_dyn_global_constructor_instanceof(const ScrDyn *value, const ScrD /* `value instanceof callee` for an untyped constructor value. `refusal`, when * given, is the site's SC1090 message for a runtime constructor with no * instance test. */ +/* A function whose own-property table defines Symbol.hasInstance + * (`Object.defineProperty(F, Symbol.hasInstance, { value })`): 1 or 0 by the + * truthiness of the method's answer with `this` bound to F, -1 when F + * defines none, or 0 with the exception pending. Functions without a table + * use the ordinary prototype test. */ +int scr_dyn_has_instance_hook(const ScrDyn *value, const ScrDyn *callee) { + if (!scr_has_instance_symbol || callee->kind != SCR_DYN_FUNC || !scr_closure_identity(callee->v.fn.clo)->props) + return -1; + ScrDyn *hook = scr_dyn_symbol_key_get(callee, scr_has_instance_symbol, true); + if (scr_exc_pending()) { + scr_dyn_release(hook); + return 0; + } + if (!hook || hook->kind != SCR_DYN_FUNC) { + scr_dyn_release(hook); + return -1; + } + scr_dyn_this_push_dyn((ScrDyn *)callee); + ScrDyn *argument = (ScrDyn *)value; + ScrDyn *result = scr_dyn_call(hook, &argument, 1, "Symbol.hasInstance"); + scr_dyn_this_pop(); + scr_dyn_release(hook); + if (!result) return 0; + bool truthy = scr_dyn_truthy(result); + scr_dyn_release(result); + return truthy ? 1 : 0; +} + bool scr_bytes_instanceof_at(const ScrDyn *value, const ScrDyn *callee, const ScrStr *refusal) { if (!callee || callee->kind != SCR_DYN_FUNC) { static const char message[] = "Right-hand side of 'instanceof' is not callable"; scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); return false; } + int hooked = scr_dyn_has_instance_hook(value, callee); + if (hooked >= 0) return hooked == 1; + /* An error constructor held as a value (`inst instanceof params.Parent` + * with Parent === Error): the error kind's interval, and for Error itself + * objects inheriting Error.prototype. */ + for (size_t i = 0; i < SCR_ERROR_CTORS; i++) { + if (!scr_error_ctor_values[i] || callee->v.fn.clo != scr_error_ctor_values[i]->v.fn.clo) continue; + return scr_dyn_err_instanceof(value, (double)i) || (i == 0 && scr_dyn_inherits_error_prototype(value)); + } if (scr_array_buffer_ctor && callee->v.fn.clo == scr_array_buffer_ctor->v.fn.clo) return scr_array_buffer_is(value); if (scr_array_ctor && callee->v.fn.clo == scr_array_ctor->v.fn.clo) return value->kind == SCR_DYN_ARR || scr_dyn_ref_is_array(value); for (size_t i = 0; i < SCR_BYTES_ELEM_COUNT; i++) { @@ -5093,6 +5206,8 @@ static ScrDyn *scr_dyn_fn_properties(const ScrDyn *function) { return (ScrDyn *)scr_box_get_ref(owner->props); } +ScrDyn *scr_dyn_function_properties(const ScrDyn *function) { return scr_dyn_fn_properties(function); } + ScrDyn *scr_dyn_class_inherit(ScrDyn *constructor, ScrDyn *base) { if (constructor->kind != SCR_DYN_FUNC || !constructor->v.fn.class_obj || (base->kind != SCR_DYN_FUNC && base->kind != SCR_DYN_UNDEF)) { static const char message[] = "Class inheritance requires native constructors"; @@ -5109,7 +5224,43 @@ ScrDyn *scr_dyn_class_inherit(ScrDyn *constructor, ScrDyn *base) { return scr_exc_pending() ? NULL : scr_dyn_retain(constructor); } +/* Defines `key` on target as a writable, configurable, non-enumerable data + * property holding value (borrowed), as Error's constructor installs its + * message and cause. */ +static void scr_dyn_define_hidden(ScrDyn *target, const ScrStr *key, ScrDyn *value) { + ScrDyn *descriptor = scr_dyn_new_obj(); + scr_dyn_obj_set(descriptor, "value", 5, scr_dyn_retain(value)); + scr_dyn_obj_set(descriptor, "writable", 8, scr_dyn_new_bool(true)); + scr_dyn_obj_set(descriptor, "enumerable", 10, scr_dyn_new_bool(false)); + scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool(true)); + ScrDyn *name = scr_dyn_new_str((ScrStr *)key); + ScrDyn *defined = scr_dyn_define_property(target, name, descriptor); + scr_dyn_release(name); + scr_dyn_release(descriptor); + scr_dyn_release(defined); +} + +/* A builtin constructor a class expression's runtime heritage can name: + * 1 for Object, 2 for Error, 0 otherwise. */ +static int scr_dyn_builtin_base_kind(const ScrDyn *constructor) { + if (constructor->kind != SCR_DYN_FUNC || constructor->v.fn.class_obj || !constructor->v.fn.sig || + !constructor->v.fn.name || strcmp(constructor->v.fn.sig, "native:constructor") != 0) + return 0; + if (strcmp(constructor->v.fn.name, "Object") == 0) return 1; + if (strcmp(constructor->v.fn.name, "Error") == 0 && constructor->v.fn.clo->fn == (void *)scr_error_ctor_names[0]) + return 2; + return 0; +} + ScrDyn *scr_dyn_class_base_prototype(ScrDyn *constructor) { + /* `class extends (Base ?? Object)`: Object's instances are ordinary + * objects, laid out like a class without heritage (undefined); Error's + * inherit Error.prototype. */ + switch (scr_dyn_builtin_base_kind(constructor)) { + case 1: return scr_dyn_retain(scr_dyn_undefined()); + case 2: return scr_dyn_error_prototype(); + default: break; + } if (constructor->kind != SCR_DYN_FUNC || constructor->v.fn.class_obj || scr_closure_identity(constructor->v.fn.clo)->function_kind != 4) { static const char message[] = "Class extends value is not a native ordinary constructor"; @@ -5126,6 +5277,55 @@ ScrDyn *scr_dyn_class_base_prototype(ScrDyn *constructor) { } void scr_dyn_class_super(ScrDyn *constructor, ScrDyn *receiver, ScrDyn *arguments) { + switch (scr_dyn_builtin_base_kind(constructor)) { + case 1: + /* super() through Object keeps the receiver the derived constructor + * created (OrdinaryCreateFromConstructor). */ + return; + case 2: { + /* super(message, options) through Error: the message and cause become + * non-enumerable own properties, then the stack (its text alone, as for + * scriptc's other errors). */ + size_t argc = arguments->v.arr.len; + ScrDyn *const *argv = arguments->v.arr.items; + if (argc > 0 && argv[0]->kind != SCR_DYN_UNDEF) { + ScrStr *text = scr_dyn_string_coerce_js(argv[0]); + if (!text) return; + ScrDyn *value = scr_dyn_new_str(text); + scr_str_release(text); + ScrStr *key = scr_str_new("message", 7); + scr_dyn_define_hidden(receiver, key, value); + scr_str_release(key); + scr_dyn_release(value); + if (scr_exc_pending()) return; + } + /* InstallErrorCause: an options object with a `cause` property, own or + * inherited, defines the value its getter or data property reads. */ + ScrDyn *options = argc > 1 ? argv[1] : NULL; + if (options && options->kind != SCR_DYN_NULL && + (options->kind == SCR_DYN_FUNC || scr_dyn_is_object(options))) { + ScrStr *key = scr_str_new("cause", 5); + if (scr_dyn_in(options, key) && !scr_exc_pending()) { + ScrDyn *cause = scr_dyn_obj_read(options, "cause", 5); + if (cause && !scr_exc_pending()) scr_dyn_define_hidden(receiver, key, cause); + scr_dyn_release(cause); + } + scr_str_release(key); + if (scr_exc_pending()) return; + } + ScrStr *text = scr_dyn_error_string(receiver); + if (!text) return; + ScrDyn *stack = scr_dyn_new_str(text); + scr_str_release(text); + ScrStr *key = scr_str_new("stack", 5); + scr_dyn_define_hidden(receiver, key, stack); + scr_str_release(key); + scr_dyn_release(stack); + return; + } + default: + break; + } scr_dyn_this_push_dyn(receiver); ScrDyn *result = scr_dyn_call(constructor, arguments->v.arr.items, arguments->v.arr.len, "super"); scr_dyn_this_pop(); @@ -9094,8 +9294,19 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) { } if (recv->kind == SCR_DYN_FUNC) { if (scr_dyn_fn_property_fence(key)) return; + bool limit = scr_dyn_is_stack_limit(recv, key->data, key->len); + /* A frozen Error keeps the limit read-only. */ + if (limit && value->kind == SCR_DYN_NUM && !scr_dyn_stack_limit_shadowed(recv) && !scr_dyn_is_frozen(recv)) { + scr_stack_limit_set(value->v.num); + return; + } ScrDyn *table = scr_dyn_fn_properties(recv); scr_dyn_object_key_set(table, key, value, recv); + /* A number the own data property takes also sets the limit. */ + ScrDynEntry *own = limit && value->kind == SCR_DYN_NUM && !scr_exc_pending() + ? scr_dyn_find_entry(table, key->data, key->len) + : NULL; + if (own && own->value == value) scr_stack_limit_set(value->v.num); scr_dyn_release(table); return; } @@ -11875,6 +12086,8 @@ static ScrDyn *scr_function_constructor(uint32_t kind) { /* Keyed read on a FUNC node (see scr_runtime.h): own props first, then * the function-instance built-ins name/length. +1 or NULL. */ ScrDyn *scr_dyn_fn_get(const ScrDyn *d, const char *key, size_t key_len) { + if (scr_dyn_is_stack_limit(d, key, key_len) && !scr_dyn_stack_limit_shadowed(d)) + return scr_dyn_new_num(scr_stack_limit_get()); ScrClosure *owner = scr_closure_identity(d->v.fn.clo); if (d->v.fn.class_obj && d->v.fn.class_obj->static_data && !owner->props) owner->props = scr_box_retain(d->v.fn.class_obj->static_data); @@ -15857,3 +16070,62 @@ bool scr_error_has_key(ScrError *e, const ScrStr *key) { return true; return scr_dyn_object_proto_name(key); } + +/* ── declaration-typed boundary: in-place capsules ───────────────────── + * scr_decl_adopt.c (linked only into programs with boundary casts) turns a + * shared source node into a live capsule of the native value built from + * it. The node's old payload moves to a scratch node released here; the + * node's weak-collection entries move to the capsule's native identity. + * Nothing else in the runtime calls this. */ +static bool scr_dyn_decl_rekey(ScrDyn *node, void *ptr) { + bool moved = false; + if (!scr_weak_observing) return false; + for (;;) { + ScrWeakEntry **slot = &scr_weak_entries[scr_weak_bucket(node)]; + while (*slot && !((*slot)->key == (void *)node && (*slot)->kind == 0)) slot = &(*slot)->all_next; + ScrWeakEntry *entry = *slot; + if (!entry) return moved; + *slot = entry->all_next; + entry->key = ptr; + entry->kind = 32; + ScrWeakEntry **head = &scr_weak_entries[scr_weak_bucket(ptr)]; + entry->all_next = *head; + *head = entry; + moved = true; + } +} + +void scr_dyn_decl_become(ScrDyn *n, const ScrDyn *capsule); +void scr_dyn_decl_become(ScrDyn *n, const ScrDyn *capsule) { + ScrDynRegion *region = scr_dyn_region_of(n); + if (region) region->intact = false; + ScrDyn *old = scr_dyn_alloc(SCR_DYN_NULL); + old->kind = n->kind; + old->buffer = n->buffer; + old->null_proto = n->null_proto; + old->copied_from_native = n->copied_from_native; + old->non_extensible = n->non_extensible; + old->v = n->v; + old->prototype = n->prototype; + old->symbol_properties = n->symbol_properties; + old->symbol_keys = n->symbol_keys; + n->kind = SCR_DYN_TYPED_REF; + n->buffer = false; + n->null_proto = false; + n->copied_from_native = false; + n->non_extensible = false; + n->prototype = NULL; + n->symbol_properties = NULL; + n->symbol_keys = NULL; + memset(&n->v, 0, sizeof n->v); + n->v.typed_ref = capsule->v.typed_ref; + n->v.typed_ref.ptr = capsule->v.typed_ref.retain(capsule->v.typed_ref.ptr); + n->v.typed_ref.materialized = NULL; + n->v.typed_ref.casts = NULL; + if (scr_dyn_decl_rekey(n, n->v.typed_ref.ptr)) n->v.typed_ref.observed = true; + if (n->v.typed_ref.type_key_len >= 7 && memcmp(n->v.typed_ref.type_key, "record:", 7) == 0) { + ScrDyn *view = scr_record_source_view(n->v.typed_ref.ptr); + if (view) n->v.typed_ref.materialized = scr_dyn_retain(view); + } + scr_dyn_release(old); +} diff --git a/packages/runtime/src/scr_symbol.c b/packages/runtime/src/scr_symbol.c index e49edb370..16a4517d0 100644 --- a/packages/runtime/src/scr_symbol.c +++ b/packages/runtime/src/scr_symbol.c @@ -85,6 +85,11 @@ ScrSym *scr_sym_well_known(ScrStr *name) { scr_dyn_install_iterator_symbol(key); scr_dyn_release(key); } + if (name->len == 11 && !memcmp(name->data, "hasInstance", 11)) { + ScrDyn *key = scr_dyn_new_symbol(symbol); + scr_dyn_install_has_instance_symbol(key); + scr_dyn_release(key); + } if (name->len == 13 && !memcmp(name->data, "asyncIterator", 13)) { ScrDyn *key = scr_dyn_new_symbol(symbol); scr_dyn_install_async_iterator_symbol(key); diff --git a/packages/runtime/src/scr_worker_events.c b/packages/runtime/src/scr_worker_events.c index 3d10183f4..a23395c91 100644 --- a/packages/runtime/src/scr_worker_events.c +++ b/packages/runtime/src/scr_worker_events.c @@ -354,17 +354,23 @@ static bool scr_worker_transfer_has(const ScrDyn *transfer, const ScrWorkerView return false; } +/* Whether a method name is `name`: first characters compare inline, so + * the dispatch ladders below call strcmp only for names that can match. */ +static inline bool method_is(const char *method, const char *name) { + return method[0] == name[0] && strcmp(method, name) == 0; +} + static ScrDyn *scr_worker_view_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { (void)what; ScrWorkerView *view = ptr; - if (!strcmp(method, "ref") || !strcmp(method, "unref")) { - view->referenced = !strcmp(method, "ref"); + if (method_is(method, "ref") || method_is(method, "unref")) { + view->referenced = method_is(method, "ref"); return scr_dyn_retain(scr_dyn_undefined()); } - if (view->port && !strcmp(method, "hasRef")) + if (view->port && method_is(method, "hasRef")) return scr_dyn_new_bool(view->referenced && (!view->closed || (view->channel && view->closing))); - if (!view->port && !strcmp(method, "terminate")) { + if (!view->port && method_is(method, "terminate")) { if (!view->termination) { view->termination = scr_promise_new(); if (view->closed) scr_promise_fulfill_ref(view->termination, @@ -373,7 +379,7 @@ static ScrDyn *scr_worker_view_invoke(void *ptr, ScrDyn *self, const char *metho } return scr_dyn_new_promise(view->termination); } - if (!strcmp(method, "postMessage")) { + if (method_is(method, "postMessage")) { /* Worker drops messages after exit before validating or cloning. A * closed MessagePort still validates its payload, as Node does. */ if (!view->port && view->closed) return scr_dyn_retain(scr_dyn_undefined()); @@ -405,9 +411,9 @@ static ScrDyn *scr_worker_view_invoke(void *ptr, ScrDyn *self, const char *metho else scr_worker_thread_post(view->thread, message); return scr_dyn_retain(scr_dyn_undefined()); } - bool once = !strcmp(method, "once") || !strcmp(method, "prependOnceListener"); - bool prepend = !strcmp(method, "prependListener") || !strcmp(method, "prependOnceListener"); - if (once || prepend || !strcmp(method, "on") || !strcmp(method, "addListener")) { + bool once = method_is(method, "once") || method_is(method, "prependOnceListener"); + bool prepend = method_is(method, "prependListener") || method_is(method, "prependOnceListener"); + if (once || prepend || method_is(method, "on") || method_is(method, "addListener")) { ScrDyn *listener = argc > 1 ? args[1] : scr_dyn_undefined(); scr_dyn_check_listener(listener, "listener"); if (scr_exc_pending()) return NULL; @@ -416,7 +422,7 @@ static ScrDyn *scr_worker_view_invoke(void *ptr, ScrDyn *self, const char *metho scr_emitter_release(scr_emitter_on_flex(view->events, args[0]->v.str, listener, once, prepend)); return scr_exc_pending() ? NULL : scr_dyn_retain(self); } - if (!strcmp(method, "off") || !strcmp(method, "removeListener")) { + if (method_is(method, "off") || method_is(method, "removeListener")) { ScrDyn *listener = argc > 1 ? args[1] : scr_dyn_undefined(); scr_dyn_check_listener(listener, "listener"); if (scr_exc_pending()) return NULL; @@ -425,21 +431,21 @@ static ScrDyn *scr_worker_view_invoke(void *ptr, ScrDyn *self, const char *metho scr_emitter_release(scr_emitter_off_dyn(view->events, args[0]->v.str, listener)); return scr_exc_pending() ? NULL : scr_dyn_retain(self); } - if (!strcmp(method, "removeAllListeners")) { + if (method_is(method, "removeAllListeners")) { bool all = !argc || args[0]->kind == SCR_DYN_UNDEF; if (!all && args[0]->kind != SCR_DYN_STR) return scr_worker_refusal("Worker event names must be strings"); scr_emitter_release(scr_emitter_remove_all(view->events, all ? NULL : args[0]->v.str, all)); return scr_exc_pending() ? NULL : scr_dyn_retain(self); } - if (!strcmp(method, "listenerCount")) { + if (method_is(method, "listenerCount")) { if (!argc || args[0]->kind != SCR_DYN_STR) return scr_worker_refusal("Worker event names must be strings"); return scr_dyn_new_num(argc > 1 && args[1]->kind != SCR_DYN_UNDEF ? scr_emitter_listener_count_dyn(view->events, args[0]->v.str, args[1]) : scr_emitter_listener_count(view->events, args[0]->v.str)); } - if (view->port && !strcmp(method, "close")) { + if (view->port && method_is(method, "close")) { if (argc && args[0]->kind != SCR_DYN_UNDEF) { scr_dyn_check_listener(args[0], "callback"); if (scr_exc_pending()) return NULL; @@ -460,7 +466,7 @@ static ScrDyn *scr_worker_view_invoke(void *ptr, ScrDyn *self, const char *metho } return scr_dyn_retain(scr_dyn_undefined()); } - if (view->port && !strcmp(method, "start")) { + if (view->port && method_is(method, "start")) { view->started = true; return scr_dyn_retain(scr_dyn_undefined()); } @@ -1021,7 +1027,7 @@ static ScrDyn *scr_broadcast_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { (void)what; ScrWorkerView *view = ptr; - if (!strcmp(method, "postMessage")) { + if (method_is(method, "postMessage")) { if (view->closed) { scr_throw_domex("InvalidStateError", "BroadcastChannel is closed."); return NULL; @@ -1045,7 +1051,7 @@ static ScrDyn *scr_broadcast_invoke(void *ptr, ScrDyn *self, const char *method, scr_message_free(first); return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined()); } - if (!strcmp(method, "close")) { + if (method_is(method, "close")) { if (!view->closed) { view->closed = true; scr_broadcast_leave(view->channel); @@ -1053,8 +1059,8 @@ static ScrDyn *scr_broadcast_invoke(void *ptr, ScrDyn *self, const char *method, } return scr_dyn_retain(scr_dyn_undefined()); } - if (!strcmp(method, "ref") || !strcmp(method, "unref")) { - view->referenced = !strcmp(method, "ref"); + if (method_is(method, "ref") || method_is(method, "unref")) { + view->referenced = method_is(method, "ref"); return scr_dyn_retain(self); } ScrDyn *out = NULL; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 589de2a51..525994ba1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,8 +110,8 @@ importers: specifier: workspace:* version: link:../runtime tsc-ts: - specifier: 0.0.7 - version: 0.0.7 + specifier: 0.0.8 + version: 0.0.8 typescript: specifier: 7.0.2 version: 7.0.2 @@ -1360,8 +1360,8 @@ packages: cpu: [x64] os: [win32] - '@scriptc/threads@0.4.1': - resolution: {integrity: sha512-wKgEBiVc07ZIGNFGwJpV5JbTXJN1N42qG6/g7ipnwt3klsU0nuWH2ToKV9vYH+RQstF7+twq6zMCf2qhUloTHQ==} + '@scriptc/threads@0.4.2': + resolution: {integrity: sha512-nR9FUxpOpgtTmJu6dejttElBL+JJTAZzJ2DZM+eNoujmLx9VpdlAVdgj3cu/MDsTUg/1sIUBq1ME1csC/Aa6yg==} engines: {node: '>=24.0.0'} '@sinclair/typebox@0.25.24': @@ -1377,42 +1377,42 @@ packages: '@ts-morph/common@0.11.1': resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==} - '@tsc-ts/darwin-arm64@0.0.7': - resolution: {integrity: sha512-nIaTJKBpG6n60xxRYfBlh02G6aSO2punIvj4qPy8/CXZu0Kw4ra/J3PYJtrtYMIh/eIGo4+f1Q01v5dFPPwZbw==} + '@tsc-ts/darwin-arm64@0.0.8': + resolution: {integrity: sha512-+hB9yWk58lwZHGrVEzqEC4D7MQW7Vd2z7blV3TULIKZPTMwdf8aT7XFAmfP48PoGff0H9yuQKyRXJl9I+sQv9w==} cpu: [arm64] os: [darwin] - '@tsc-ts/darwin-x64@0.0.7': - resolution: {integrity: sha512-8ITDyF3FG4SYeS1cV4+oRpbbk0jVsfd23Ww138wbQymbOLYjWPafzj4bymxnIE5kEWjZPVqTFDbaYqgWwNStag==} + '@tsc-ts/darwin-x64@0.0.8': + resolution: {integrity: sha512-yz4UddicqMXe+T7nV7ftpf155lkiK6gIPNwSB8rH9dlwiV6a17CO/vpSk627QLMqnfGNicB6YwLMk/SJ9moPdw==} cpu: [x64] os: [darwin] - '@tsc-ts/linux-arm64-gnu@0.0.7': - resolution: {integrity: sha512-nXGjtbM3YrARWOb+nkDaFdhKGSzPf3ofl/gV4x8JnVQ8SgK4IWJ5J0u93sk3mTYgdLj/Gcobp/D3lZ6WIzjKvA==} + '@tsc-ts/linux-arm64-gnu@0.0.8': + resolution: {integrity: sha512-LHqfT9mQ4tusPdWmnanCvC8/YBOF5bHM5h5863a3t2HTgFVdps1Qwb0yzrW5DDbojIhQ0Z+GupxtXVUQcV0Q/Q==} cpu: [arm64] os: [linux] libc: [glibc] - '@tsc-ts/linux-arm64-musl@0.0.7': - resolution: {integrity: sha512-hSjpQsOhrLaDsbvEO5EH9nC3Vy0WAt3aB+GejytEh9fl4F3kT0BasbBP4wJqu6S5SG9upLW7ZClXiNVnGYDGlQ==} + '@tsc-ts/linux-arm64-musl@0.0.8': + resolution: {integrity: sha512-I0CC3xZMwoQM6OiVsubRHb0VI/RrJpneb33kDQhadYHq3taPos71KwP+CbCXNu88rNsDsyn7D1oCVfJb73g4Mw==} cpu: [arm64] os: [linux] libc: [musl] - '@tsc-ts/linux-x64-gnu@0.0.7': - resolution: {integrity: sha512-MCU2MFefQGG/++nepg09BtYnsN2mVzeYfIS6GW30ChxUt2nJUMkEvzbDim9czDevW4LRqHgfnwEt+hs2ud1SDQ==} + '@tsc-ts/linux-x64-gnu@0.0.8': + resolution: {integrity: sha512-PLERuwUuxT92x0BZAMQubiQP5jhlqoLmswaZWqcvfKAZyBp0TbWiVOmuNnDbJozvBaNUrfdGPb9suiQ+Me7M6A==} cpu: [x64] os: [linux] libc: [glibc] - '@tsc-ts/linux-x64-musl@0.0.7': - resolution: {integrity: sha512-abTZZkn74772MPABStuXWJ1GHI07nbEf4a3Nwl7NYI5mLFYxeLSbq6+cGh0BUJeBeMhFNsjtQ5/j5sGmsrJ2tg==} + '@tsc-ts/linux-x64-musl@0.0.8': + resolution: {integrity: sha512-zDl36l1uyWc0mQniOAMZmkVD9TrlFExHlDT0fL8JGD4XLgx+bRe+To38yP0KSBehXBzjDNn8xrEoJ+3RP6mYwQ==} cpu: [x64] os: [linux] libc: [musl] - '@tsc-ts/win32-x64-msvc@0.0.7': - resolution: {integrity: sha512-EOhLTkuWYytJYklR5BI1jRtTpEIPh1FQJKD8R37qqlcnPrELlRO6Nc2ncDpdCoYbpN+KrewIVPB3xD4o3VGUaw==} + '@tsc-ts/win32-x64-msvc@0.0.8': + resolution: {integrity: sha512-9gO+pFoLiIxITlAE2P6JL8ihZwns7z/2Raw5+38RqLzHmnSsRN47+H4p/4EqQsLW5/TTsAo9uSeSkf3EuRg+XA==} cpu: [x64] os: [win32] @@ -2577,8 +2577,8 @@ packages: ts-toolbelt@6.15.5: resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} - tsc-ts@0.0.7: - resolution: {integrity: sha512-KRWTXRUvwMXi4At6xOWPDa/0BreY3MR2tPHooEMNWLZUA2eLphNe7PJiHclPLLn9RCnHY4ffDTTeF5hqgyBvtA==} + tsc-ts@0.0.8: + resolution: {integrity: sha512-EiyDziYOryBg/YEvev/mKCn+hD4M6VFjH7VP9nv2WadDKYaPrdb+a3hP+wYr/UsDWNE7AdL7YGnjZppeXlXy6g==} engines: {node: '>=20'} hasBin: true @@ -3439,7 +3439,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true - '@scriptc/threads@0.4.1': {} + '@scriptc/threads@0.4.2': {} '@sinclair/typebox@0.25.24': {} @@ -3454,25 +3454,25 @@ snapshots: mkdirp: 1.0.4 path-browserify: 1.0.1 - '@tsc-ts/darwin-arm64@0.0.7': + '@tsc-ts/darwin-arm64@0.0.8': optional: true - '@tsc-ts/darwin-x64@0.0.7': + '@tsc-ts/darwin-x64@0.0.8': optional: true - '@tsc-ts/linux-arm64-gnu@0.0.7': + '@tsc-ts/linux-arm64-gnu@0.0.8': optional: true - '@tsc-ts/linux-arm64-musl@0.0.7': + '@tsc-ts/linux-arm64-musl@0.0.8': optional: true - '@tsc-ts/linux-x64-gnu@0.0.7': + '@tsc-ts/linux-x64-gnu@0.0.8': optional: true - '@tsc-ts/linux-x64-musl@0.0.7': + '@tsc-ts/linux-x64-musl@0.0.8': optional: true - '@tsc-ts/win32-x64-msvc@0.0.7': + '@tsc-ts/win32-x64-msvc@0.0.8': optional: true '@tybys/wasm-util@0.10.3': @@ -4813,17 +4813,17 @@ snapshots: ts-toolbelt@6.15.5: {} - tsc-ts@0.0.7: + tsc-ts@0.0.8: dependencies: - '@scriptc/threads': 0.4.1 + '@scriptc/threads': 0.4.2 optionalDependencies: - '@tsc-ts/darwin-arm64': 0.0.7 - '@tsc-ts/darwin-x64': 0.0.7 - '@tsc-ts/linux-arm64-gnu': 0.0.7 - '@tsc-ts/linux-arm64-musl': 0.0.7 - '@tsc-ts/linux-x64-gnu': 0.0.7 - '@tsc-ts/linux-x64-musl': 0.0.7 - '@tsc-ts/win32-x64-msvc': 0.0.7 + '@tsc-ts/darwin-arm64': 0.0.8 + '@tsc-ts/darwin-x64': 0.0.8 + '@tsc-ts/linux-arm64-gnu': 0.0.8 + '@tsc-ts/linux-arm64-musl': 0.0.8 + '@tsc-ts/linux-x64-gnu': 0.0.8 + '@tsc-ts/linux-x64-musl': 0.0.8 + '@tsc-ts/win32-x64-msvc': 0.0.8 tslib@2.8.1: optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ceb1506e9..e74d40442 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,15 +7,15 @@ minimumReleaseAgeExclude: - "effect@4.0.0" - "@effect/platform-node@4.0.0" - "@effect/platform-node-shared@4.0.0" - - "@tsc-ts/darwin-arm64@0.0.7" - - "@tsc-ts/darwin-x64@0.0.7" - - "@tsc-ts/linux-arm64-gnu@0.0.7" - - "@tsc-ts/linux-arm64-musl@0.0.7" - - "@tsc-ts/linux-x64-gnu@0.0.7" - - "@tsc-ts/linux-x64-musl@0.0.7" - - "@tsc-ts/win32-x64-msvc@0.0.7" - - "tsc-ts@0.0.7" - - "@scriptc/threads@0.4.1" + - "@tsc-ts/darwin-arm64@0.0.8" + - "@tsc-ts/darwin-x64@0.0.8" + - "@tsc-ts/linux-arm64-gnu@0.0.8" + - "@tsc-ts/linux-arm64-musl@0.0.8" + - "@tsc-ts/linux-x64-gnu@0.0.8" + - "@tsc-ts/linux-x64-musl@0.0.8" + - "@tsc-ts/win32-x64-msvc@0.0.8" + - "tsc-ts@0.0.8" + - "@scriptc/threads@0.4.2" allowBuilds: esbuild: true overrides: diff --git a/scripts/gen-dyn-methods.mjs b/scripts/gen-dyn-methods.mjs new file mode 100644 index 000000000..fe4f81a90 --- /dev/null +++ b/scripts/gen-dyn-methods.mjs @@ -0,0 +1,101 @@ +#!/usr/bin/env node +/* Writes packages/runtime/src/scr_dyn_methods.h: the method-name ids the + * checked-dynamic dispatch in scr_dyn_invoke.c compares (DYN_M_), the + * names, and an open-addressed hash table mapping a name to its id. The ids + * are the DYN_M_ names scr_dyn_invoke.c uses, so the header follows the + * dispatch code. + * + * node scripts/gen-dyn-methods.mjs write the header + * node scripts/gen-dyn-methods.mjs --check fail when it is out of date */ +import { readFileSync, writeFileSync } from "node:fs"; + +const source = new URL("../packages/runtime/src/scr_dyn_invoke.c", import.meta.url); +const target = new URL("../packages/runtime/src/scr_dyn_methods.h", import.meta.url); +const SLOTS = 256; + +const used = new Set(); +for (const match of readFileSync(source, "utf8").matchAll(/\bDYN_M_([A-Za-z][A-Za-z0-9]*)\b/g)) + if (match[1] !== "NONE" && match[1] !== "COUNT") used.add(match[1]); +const names = [...used].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); +if (names.length >= SLOTS / 2) throw new Error("too many method names for the hash table"); + +// The slot dyn_method_of in the header computes. +const slotOf = (name) => + (name.length * 7 + name.charCodeAt(0) * 31 + name.charCodeAt(name.length - 1)) & (SLOTS - 1); +const slots = new Array(SLOTS).fill(0); +names.forEach((name, i) => { + let slot = slotOf(name); + while (slots[slot] !== 0) slot = (slot + 1) & (SLOTS - 1); + slots[slot] = i + 1; +}); + +const rows = (values, width) => { + const out = []; + for (let i = 0; i < values.length; i += width) + out.push(` ${values.slice(i, i + width).join(", ")},`); + return out; +}; +const text = [ + "/* Generated by scripts/gen-dyn-methods.mjs from the DYN_M_ ids scr_dyn_invoke.c", + " * uses. Do not edit by hand. */", + "#ifndef SCR_DYN_METHODS_H", + "#define SCR_DYN_METHODS_H", + "", + "#include ", + "#include ", + "", + "enum {", + " DYN_M_NONE,", + ...names.map((name) => ` DYN_M_${name},`), + " DYN_M_COUNT", + "};", + "", + "static const char *const dyn_method_names[DYN_M_COUNT] = {", + " NULL,", + ...names.map((name) => ` ${JSON.stringify(name)},`), + "};", + "", + "static const unsigned char dyn_method_lengths[DYN_M_COUNT] = {", + ...rows([0, ...names.map((name) => name.length)], 16), + "};", + "", + `/* Each name's id at its slot (or the next free one); 0 ends a probe. */`, + `static const unsigned char dyn_method_slots[${SLOTS}] = {`, + ...rows(slots, 16), + "};", + "", + `#define DYN_METHOD_MAX_LENGTH ${Math.max(...names.map((name) => name.length))}`, + "", + "/* The id of a method name, compared by content; DYN_M_NONE for any other", + " * name. */", + "static inline int dyn_method_of(const char *m) {", + " size_t length = strlen(m);", + " if (length == 0 || length > DYN_METHOD_MAX_LENGTH) return DYN_M_NONE;", + " unsigned slot = (unsigned)(length * 7 + (unsigned char)m[0] * 31 +", + ` (unsigned char)m[length - 1]) & ${SLOTS - 1};`, + " for (;;) {", + " unsigned id = dyn_method_slots[slot];", + " if (id == 0) return DYN_M_NONE;", + " if (dyn_method_lengths[id] == length && memcmp(m, dyn_method_names[id], length) == 0) return (int)id;", + ` slot = (slot + 1) & ${SLOTS - 1};`, + " }", + "}", + "", + "#endif", + "", +].join("\n"); + +if (process.argv.includes("--check")) { + let current = ""; + try { + current = readFileSync(target, "utf8"); + } catch {} + if (current !== text) { + console.error( + "packages/runtime/src/scr_dyn_methods.h is out of date: run node scripts/gen-dyn-methods.mjs", + ); + process.exit(1); + } +} else { + writeFileSync(target, text); +} diff --git a/scripts/verify-native-cli.mjs b/scripts/verify-native-cli.mjs index 115573b5c..92de57938 100644 --- a/scripts/verify-native-cli.mjs +++ b/scripts/verify-native-cli.mjs @@ -94,5 +94,15 @@ if (process.argv.includes("--run")) { }).trim(); if (version !== identity.version) throw new Error(`native command reported ${version}, expected ${identity.version}`); + // tsc-ts, the default checker, runs on the distribution's own platform. + const tscTs = resolve(dirname(binary), hostPath(toolchain.tsc_ts)); + if (windows && !tscTs.endsWith("tsc-ts.exe")) + throw new Error(`tsc_ts is not tsc-ts.exe: ${tscTs}`); + const tscTsVersion = execFileSync(tscTs, ["--version"], { + encoding: "utf8", + env: { ...process.env, PATH: "" }, + }).trim(); + if (!tscTsVersion.startsWith("Version ")) + throw new Error(`the distribution's tsc-ts reported ${JSON.stringify(tscTsVersion)}`); } console.log(`${identity.name}@${identity.version}: native distribution verified`); diff --git a/scripts/zero-cost-compare.mjs b/scripts/zero-cost-compare.mjs index 3158de997..160e33438 100644 --- a/scripts/zero-cost-compare.mjs +++ b/scripts/zero-cost-compare.mjs @@ -394,7 +394,8 @@ export function dynamicTierSymbols(names) { * not yet traced to its typed root. bench/thread-slots, * bench/channel-messages and bench/worker-messages, added later, link them * through worker messages: - * node:worker_threads values are dyn objects. Every other bench workload is fully typed and must link none. The gate and + * node:worker_threads values are dyn objects. bench/dynamic-methods is + * untyped JavaScript. Every other bench workload is fully typed and must link none. The gate and * tests/harness/bench-dynamic-tier.test.ts enforce that, and the test also * fails when a listed workload stops linking them, so remove a workload here * when a leak fix makes its binary clean. */ @@ -408,6 +409,7 @@ export const DYNAMIC_WORKLOADS = new Set([ "bench/intern-table", "bench/thread-slots", "bench/channel-messages", + "bench/dynamic-methods", "bench/worker-messages", "bench/typed-array-scan-workers", "bench/log-summary", diff --git a/tests/corpus/7144-paired-while-counters.ts b/tests/corpus/7144-paired-while-counters.ts new file mode 100644 index 000000000..2198d213f --- /dev/null +++ b/tests/corpus/7144-paired-while-counters.ts @@ -0,0 +1,244 @@ +// While loops with two counters closing in on each other (`lo++; hi--;` at +// the end of the body): reversals over arrays, typed arrays and strings, +// either comparison direction, inclusive bounds, larger steps, parameters as +// counters, early exits, unproven starts, and the shapes that keep the loop +// as it is (a continue, a counter read after the loop, a captured counter, +// a counter the body writes). +function reverse(a: number[], from: number, to: number): number[] { + let lo = from; + let hi = to; + while (lo < hi) { + const t = a[lo]!; + a[lo] = a[hi]!; + a[hi] = t; + lo++; + hi--; + } + return a; +} +const base = (): number[] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; +console.log(reverse(base(), 0, 9).join(",")); +console.log(reverse(base(), 2, 6).join(",")); +console.log(reverse(base(), 6, 2).join(",")); +console.log(reverse(base(), 3, 3).join(",")); +console.log(reverse(base(), -0, 2).join(","), reverse(base(), 7, -0).join(",")); + +// The parameters are the counters; the updates come in the other order. +function reverseRange(a: Float64Array, lo: number, hi: number): string { + while (hi > lo) { + const t = a[lo]!; + a[lo] = a[hi]!; + a[hi] = t; + hi -= 1; + ++lo; + } + return a.join(","); +} +console.log(reverseRange(new Float64Array([1, 2, 3, 4, 5, 6]), 0, 5)); +console.log(reverseRange(new Float64Array([1, 2, 3, 4, 5, 6]), 1, 3.5)); +console.log(reverseRange(new Float64Array([1, 2, 3, 4, 5, 6]), -2, 2)); +console.log(reverseRange(new Float64Array([1, 2, 3, 4, 5, 6]), NaN, 4), reverseRange(new Float64Array([1, 2]), 0, NaN)); + +function bytesReversed(text: string): string { + const bytes = new TextEncoder().encode(text); + let i = 0, + j = bytes.length - 1; + while (i < j) { + const t = bytes[i]!; + bytes[i] = bytes[j]!; + bytes[j] = t; + i++; + j--; + } + return new TextDecoder().decode(bytes); +} +console.log(bytesReversed("hello, world"), bytesReversed(""), bytesReversed("x")); + +// Unproven starts read through charCodeAt (which truncates its index). +function codes(s: string, from: number, to: number): string { + const out: number[] = []; + let lo = from; + let hi = to; + while (lo <= hi) { + out.push(s.charCodeAt(lo), s.charCodeAt(hi), lo, hi); + lo += 2; + hi -= 3; + } + return out.join(","); +} +const word = "abcdefghijkl"; +console.log(codes(word, 0, 11), "|", codes(word, 0.5, 9.25), "|", codes(word, 4, 4)); +console.log(codes(word, 2 ** 53 - 3, 2 ** 53), "|", codes(word, -3, 2), "|", codes(word, 5, 1)); + +// Negative zero starts stay negative zero. +function signs(text: string, from: number): string { + const out: string[] = []; + let lo = from; + let hi = text.length - 1; + while (lo < hi) { + out.push(`${Object.is(lo, -0)}:${text.charCodeAt(lo)}`); + lo++; + hi--; + } + let a = -0; + let b = 2; + while (a < b) { + out.push(`${Object.is(a, -0)}:${text.charCodeAt(b)}`); + a++; + b--; + } + return out.join(" "); +} +console.log(signs("abcd", -0), signs("abcd", 0)); + +function isPalindrome(s: string): boolean { + let i = 0; + let j = s.length - 1; + while (i < j) { + if (s.charCodeAt(i) !== s.charCodeAt(j)) return false; + i++; + j--; + } + return true; +} +console.log(isPalindrome("racecar"), isPalindrome("abca"), isPalindrome(""), isPalindrome("ab")); + +function firstMismatch(a: number[]): number { + let found = -1; + let lo = 0; + let hi = a.length - 1; + while (lo < hi) { + const left = a[lo]!; + if (left !== a[hi]!) { + found = lo; + break; + } + lo++; + hi--; + } + return found; +} +console.log(firstMismatch([1, 2, 3, 2, 1]), firstMismatch([1, 2, 3, 4, 2, 1]), firstMismatch([])); + +// A labeled jump to an outer loop leaves the inner loop from its middle. +function rows(grid: number[][]): string { + const out: string[] = []; + outer: for (const row of grid) { + let lo = 0; + let hi = row.length - 1; + while (lo < hi) { + const left = row[lo]!; + const right = row[hi]!; + if (left === 0) continue outer; + if (right === 0) break outer; + out.push(`${left}${right}`); + lo++; + hi--; + } + out.push("|"); + } + return out.join(" "); +} +console.log(rows([[1, 2, 3, 4], [0, 5, 6], [7, 8, 9, 1], [2, 3, 0, 4]])); + +// Repeated entries: the counters are declared inside an outer loop. Each +// pass reverses the prefix that ends at the index the first element holds, +// until the first element is 0, and the passes are counted. +function prefixReversals(values: number[]): number { + const copy = values.slice(); + let count = 0; + let end = copy[0]!; + while (end !== 0) { + let i = 0; + let j = end; + while (i < j) { + const t = copy[i]!; + copy[i] = copy[j]!; + copy[j] = t; + i++; + j--; + } + count++; + end = copy[0]!; + } + return count; +} +console.log( + prefixReversals([3, 1, 2, 0]), + prefixReversals([0, 1, 2]), + prefixReversals([4, 3, 2, 1, 0]), + prefixReversals([2, 0, 1]), +); + +// Holes and present undefined values move as they are. +const sparse: (number | undefined)[] = [1, , 3, undefined, 5]; +{ + let lo = 0; + let hi = sparse.length - 1; + while (lo < hi) { + const t = sparse[lo]; + sparse[lo] = sparse[hi]; + sparse[hi] = t; + lo++; + hi--; + } +} +console.log(sparse, 1 in sparse, 3 in sparse); + +// These keep the while loop: a continue skips the trailing updates, a +// counter is read after the loop, a closure captures a counter, and the +// body writes a counter. +function skipping(n: number): string { + const out: number[] = []; + let lo = 0; + let hi = n; + while (lo < hi) { + if (lo % 3 === 0) { + lo += 2; + continue; + } + out.push(lo, hi); + lo++; + hi--; + } + return out.join(","); +} +console.log(skipping(12)); + +function meet(n: number): string { + let lo = 0; + let hi = n; + while (lo < hi) { + lo++; + hi--; + } + return `${lo}:${hi}`; +} +console.log(meet(9), meet(10), meet(0.5)); + +function captured(n: number): string { + const seen: (() => number)[] = []; + let lo = 0; + let hi = n; + while (lo < hi) { + seen.push(() => lo * 100 + hi); + lo++; + hi--; + } + return seen.map((f) => f()).join(","); +} +console.log(captured(5)); + +function jumps(a: number[]): string { + const out: number[] = []; + let lo = 0; + let hi = a.length - 1; + while (lo < hi) { + out.push(a[lo]!, a[hi]!); + if (a[lo]! > 5) hi = lo; + lo++; + hi--; + } + return out.join(","); +} +console.log(jumps([1, 2, 9, 4, 5, 6, 7, 8])); diff --git a/tests/corpus/7262-fresh-literal-untyped-param/lib.js b/tests/corpus/7262-fresh-literal-untyped-param/lib.js new file mode 100644 index 000000000..687caf5c2 --- /dev/null +++ b/tests/corpus/7262-fresh-literal-untyped-param/lib.js @@ -0,0 +1,12 @@ +// zod 4's normalizeParams: moves `message` to `error` and deletes it. +export function normalize(params) { + if (params?.message !== undefined) params.error = params.message; + delete params.message; + return params; +} + +export function strip(options) { + for (const key of Object.keys(options)) if (options[key] === undefined) delete options[key]; + options.stripped = true; + return options; +} diff --git a/tests/corpus/7262-fresh-literal-untyped-param/main.ts b/tests/corpus/7262-fresh-literal-untyped-param/main.ts new file mode 100644 index 000000000..c242c7150 --- /dev/null +++ b/tests/corpus/7262-fresh-literal-untyped-param/main.ts @@ -0,0 +1,18 @@ +// A fresh TypeScript object literal handed to an untyped JavaScript +// parameter, or to a parameter type that declares its properties optional, +// is the plain object the callee may reshape: deleting a property works as +// in Node instead of failing on a record layout that requires it. +import { normalize, strip } from "./lib.js"; + +console.log(JSON.stringify(normalize({ message: "must change at least one field" }))); +console.log(JSON.stringify(normalize({ message: "kept", note: 1 }))); +const n = 2; +console.log(JSON.stringify(strip({ a: n, b: undefined, c: "x" }))); + + +// A package-declared parameter whose type makes `message` optional, on a +// method the package's JavaScript defines (zod's `.refine(fn, { message })`). +import { schema } from "params-kit"; +const s = schema("book"); +console.log(s.refine("plain", { message: "must change at least one field" })); +console.log(s.refine("with path", { message: "bad", path: ["a", "b"] })); diff --git a/tests/corpus/7262-fresh-literal-untyped-param/node_modules/params-kit/lib/index.d.ts b/tests/corpus/7262-fresh-literal-untyped-param/node_modules/params-kit/lib/index.d.ts new file mode 100644 index 000000000..608534249 --- /dev/null +++ b/tests/corpus/7262-fresh-literal-untyped-param/node_modules/params-kit/lib/index.d.ts @@ -0,0 +1,10 @@ +export interface RefineParams { + message?: string; + error?: unknown; + path?: string[]; +} +export interface Schema { + name: string; + refine(label: string, params?: string | RefineParams): string; +} +export declare function schema(name: string): Schema; diff --git a/tests/corpus/7262-fresh-literal-untyped-param/node_modules/params-kit/lib/index.js b/tests/corpus/7262-fresh-literal-untyped-param/node_modules/params-kit/lib/index.js new file mode 100644 index 000000000..2433d9129 --- /dev/null +++ b/tests/corpus/7262-fresh-literal-untyped-param/node_modules/params-kit/lib/index.js @@ -0,0 +1,11 @@ +// zod 4's normalizeParams on an instance method, behind a package declaration. +function normalizeParams(params) { + if (params?.message !== undefined) params.error = params.message; + delete params.message; + return params; +} +export function schema(name) { + const inst = { name }; + inst.refine = (label, params) => `${inst.name}.${label}: ${JSON.stringify(normalizeParams(params))}`; + return inst; +} diff --git a/tests/corpus/7262-fresh-literal-untyped-param/node_modules/params-kit/package.json b/tests/corpus/7262-fresh-literal-untyped-param/node_modules/params-kit/package.json new file mode 100644 index 000000000..1addbb8d0 --- /dev/null +++ b/tests/corpus/7262-fresh-literal-untyped-param/node_modules/params-kit/package.json @@ -0,0 +1 @@ +{ "name": "params-kit", "version": "1.0.0", "type": "module", "main": "./lib/index.js", "types": "./lib/index.d.ts" } diff --git a/tests/corpus/7365-class-methods-untyped-computed-writes/main.ts b/tests/corpus/7365-class-methods-untyped-computed-writes/main.ts new file mode 100644 index 000000000..c5475b5d9 --- /dev/null +++ b/tests/corpus/7365-class-methods-untyped-computed-writes/main.ts @@ -0,0 +1,87 @@ +// Typed classes next to untyped computed writes. Calls through a typed +// receiver keep the method while its prototype slot holds the declared +// method, and observe a replacement made by untyped code (on the prototype +// or as an own property of one instance) exactly as Node does. +import { patch, tally, unpatch } from "./untyped.js"; + +class Repository { + private items = new Map(); + add(name: string, cents: number): number { + this.items.set(name, cents); + return this.items.size; + } + list(): string[] { + return [...this.items.keys()]; + } + total(): number { + let sum = 0; + for (const cents of this.items.values()) sum += cents; + return sum; + } +} + +const words: string[] = ["list", "add", "list", "total"]; +console.log(JSON.stringify(tally(words))); + +const repo = new Repository(); +repo.add("keyboard", 12900); +repo.add("mouse", 4900); +let sum = 0; +for (let i = 0; i < 1000; i++) sum += repo.total(); +console.log(repo.list().join(","), sum); + +// untyped code replaces the method on the prototype +const proto: unknown = Object.getPrototypeOf(repo); +patch(proto, "total", function (this: Repository): number { + return -1; +}); +console.log(repo.total(), new Repository().total()); +unpatch(proto, "total"); +try { + repo.total(); +} catch (error) { + console.log("after delete:", error instanceof TypeError); +} + +// an own replacement on one instance shadows the prototype for that instance only +const other = new Repository(); +other.add("cable", 900); +patch(other, "list", () => ["shadowed"]); +console.log(other.list().join(","), repo.list().join(",")); +unpatch(other, "list"); +console.log(other.list().join(",")); + +// restoring the original value through the prototype brings back the method +const saved: unknown = (proto as Record)["list"]; +patch(proto, "list", () => ["patched"]); +console.log(repo.list().join(",")); +patch(proto, "list", saved); +console.log(repo.list().join(",")); + +// a spread argument drains its iterator through the iterator's current `next` +class Bag { + sum(...values: number[]): number { + let total = 0; + for (const value of values) total += value; + return total; + } +} +class Countdown { + private left: number; + constructor(left: number) { + this.left = left; + } + next(): IteratorResult { + return this.left > 0 ? { done: false, value: this.left-- } : { done: true, value: undefined }; + } + [Symbol.iterator](): Countdown { + return this; + } +} +const bag = new Bag(); +console.log(bag.sum(...[1, 2, 3]), bag.sum(...new Countdown(3))); +const replaced = new Countdown(3); +patch(replaced, "next", function (): IteratorResult { + return { done: true, value: undefined }; +}); +console.log(bag.sum(...replaced)); diff --git a/tests/corpus/7365-class-methods-untyped-computed-writes/untyped.js b/tests/corpus/7365-class-methods-untyped-computed-writes/untyped.js new file mode 100644 index 000000000..3c0d20189 --- /dev/null +++ b/tests/corpus/7365-class-methods-untyped-computed-writes/untyped.js @@ -0,0 +1,15 @@ +// Untyped helpers with computed writes whose keys TypeScript cannot know. +// They put every class method name in play for the method census. +export function tally(words) { + const counts = {}; + for (const word of words) counts[word] = (counts[word] || 0) + 1; + return counts; +} + +export function patch(target, name, impl) { + target[name] = impl; +} + +export function unpatch(target, name) { + delete target[name]; +} diff --git a/tests/corpus/7366-typed-global-view-arrays.ts b/tests/corpus/7366-typed-global-view-arrays.ts new file mode 100644 index 000000000..e2d9b23ff --- /dev/null +++ b/tests/corpus/7366-typed-global-view-arrays.ts @@ -0,0 +1,48 @@ +// Arrays stored on the global object through a typed view are the same +// arrays every typed read returns: writes through one read (push, index +// assignment, sort) are visible through the next, as in Node. +type Store = typeof globalThis & { __exampleCache?: number[]; __exampleNames?: string[] }; + +function cache(): number[] { + const g = globalThis as Store; + g.__exampleCache ??= [1]; + return g.__exampleCache; +} + +cache().push(2); +cache().push(3); +cache()[0] = 10; +console.log(cache().length, cache().join(","), cache() === cache()); + +const view = globalThis as Store; +view.__exampleNames = ["b", "a"]; +const names = view.__exampleNames; +names.push("c"); +names.sort(); +console.log(view.__exampleNames.join(","), names === view.__exampleNames); + +// the same array seen through an untyped read +const raw = (globalThis as unknown as Record)["__exampleCache"]; +console.log(Array.isArray(raw), JSON.stringify(raw), raw === cache()); + +// a literal of another element type stays its own value +const counts = globalThis as typeof globalThis & { __exampleCounts?: number[][] }; +counts.__exampleCounts = [[1], [2, 3]]; +counts.__exampleCounts[1]!.push(4); +console.log(JSON.stringify(counts.__exampleCounts)); + +// a parenthesized or `satisfies`-wrapped literal is kept the same way +type Wrapped = typeof globalThis & { __exampleWrapped?: number[]; __exampleSatisfied?: number[] }; +function wrapped(): number[] { + const g = globalThis as Wrapped; + g.__exampleWrapped ??= ([5]); + return g.__exampleWrapped; +} +function satisfied(): number[] { + const g = globalThis as Wrapped; + g.__exampleSatisfied ||= [7] satisfies number[]; + return g.__exampleSatisfied; +} +wrapped().push(6); +satisfied().push(8); +console.log(wrapped().join(","), wrapped() === wrapped(), satisfied().join(","), satisfied() === satisfied()); diff --git a/tests/corpus/7402-unknown-array-methods.ts b/tests/corpus/7402-unknown-array-methods.ts new file mode 100644 index 000000000..e374dd40f --- /dev/null +++ b/tests/corpus/7402-unknown-array-methods.ts @@ -0,0 +1,14 @@ +// Methods on arrays of unknown elements, which are stored checked-dynamic: +// direct `unknown[]` bindings and generic bodies instantiated at `unknown`. +const values: unknown[] = [1, "two", 3, { four: 4 }]; +const removed = values.splice(1, 2); +console.log(values, removed, removed.length); +values.splice(1, 0, "inserted", null); +console.log(values, values.indexOf(null), values.includes("inserted")); +console.log(values.slice(1).reverse(), values.join("|")); + +function takeAt(items: readonly T[], index: number): T[] { + const pool = [...items]; + return pool.splice(index, 1); +} +console.log(takeAt([10, 20, 30], 1), takeAt([true, "x", 7], 2)); diff --git a/tests/corpus/7403-bigint-of-typed-arrays.mjs b/tests/corpus/7403-bigint-of-typed-arrays.mjs new file mode 100644 index 000000000..9cf813d9a --- /dev/null +++ b/tests/corpus/7403-bigint-of-typed-arrays.mjs @@ -0,0 +1,62 @@ +// BigInt of a typed array converts the array to its elements joined with +// "," and parses that text, as in Node: one element reads as its value, +// an empty array as 0n, and anything else throws the SyntaxError for the +// joined text. Every element kind, negative and clamped values, float +// formatting, views and the results in arithmetic. A Buffer converts +// through its UTF-8 text instead, also when a typed array slot holds it, +// and a DataView through "[object DataView]". + +const one = [ + new Int8Array([-5]), + new Uint8Array([7]), + new Uint8ClampedArray([300]), + new Int16Array([-30000]), + new Uint16Array([65535]), + new Int32Array([-2147483648]), + new Uint32Array([4294967295]), + new Float32Array([-0]), + new Float64Array([2 ** 60]), +]; +for (const view of one) console.log(view.constructor.name, BigInt(view)); + +const empty = [new Uint8Array(0), new Float64Array([]), new Int32Array(8).subarray(3, 3)]; +for (const view of empty) console.log("empty", view.constructor.name, BigInt(view)); + +const bad = [ + new Uint8Array([1, 2]), + new Float64Array([1.5]), + new Float32Array([NaN]), + new Float64Array([1e21]), + new Int16Array([3, -4, 5]), + new Float64Array([Infinity]), +]; +for (const view of bad) { + try { + console.log("unexpected", BigInt(view)); + } catch (e) { + console.log(view.constructor.name, e instanceof SyntaxError, e.message); + } +} + +const bytes = new Uint8Array([9, 8, 7]); +const window = bytes.subarray(1, 2); +let total = 0n; +for (let i = 0; i < 100; i++) { + bytes[1] = i; + total += BigInt(window) * 3n; +} +console.log("window", BigInt(window), total, BigInt(bytes.subarray(2)) ** 2n); + +function convert(label, make) { + try { + console.log(label, BigInt(make())); + } catch (e) { + console.log(label, e instanceof SyntaxError, e.message); + } +} +convert("buffer", () => Buffer.from("12")); +convert("spaced buffer", () => Buffer.from(" -7 ")); +convert("empty buffer", () => Buffer.alloc(0)); +convert("fraction buffer", () => Buffer.from("1.5")); +convert("data view", () => new DataView(new ArrayBuffer(2))); +for (const view of [new Uint8Array([5]), Buffer.from("64")]) console.log("slot", BigInt(view)); diff --git a/tests/corpus/7441-untyped-receiver-own-string-method-names.ts b/tests/corpus/7441-untyped-receiver-own-string-method-names.ts new file mode 100644 index 000000000..632ceb16f --- /dev/null +++ b/tests/corpus/7441-untyped-receiver-own-string-method-names.ts @@ -0,0 +1,38 @@ +// An untyped receiver calling a String.prototype method name holds a +// string or a checked-dynamic object with its own method of that name +// (zod's schemas assign `trim`, `toLowerCase` and `startsWith` in their +// constructors, so `z.string().trim().min(1)` chains schema calls). +// Each call dispatches on the receiver's runtime kind. +interface Field { + value: string; + checks: string[]; +} + +function field(value: string, checks: string[]): any { + const self: Field & Record = { value, checks }; + self["trim"] = () => field(value.trim(), [...checks, "trim"]); + self["toLowerCase"] = () => field(value.toLowerCase(), [...checks, "lower"]); + self["startsWith"] = (prefix: string) => checks.length > 0 && value.startsWith(prefix); + self["min"] = (n: number) => field(value, [...checks, `min(${n})`]); + return self; +} + +const chained = field(" Hello World ", []).trim().toLowerCase().min(1); +console.log(JSON.stringify(chained.value), chained.checks.join(",")); +console.log(field("abc", ["x"]).startsWith("ab"), field("abc", []).startsWith("ab")); + +const text: any = " Mixed Case "; +console.log(JSON.stringify(text.trim()), text.toLowerCase(), text.startsWith(" M")); + +const parsed: any = JSON.parse('{"name":" padded "}'); +console.log(JSON.stringify(parsed.name.trim())); + +for (const value of [field(" a ", []), " b ", 42, null]) { + const v: any = value; + try { + const out = v.trim(); + console.log(typeof out === "string" ? JSON.stringify(out) : `field ${JSON.stringify(out.value)}`); + } catch (error) { + console.log(error instanceof TypeError, (error as Error).message); + } +} diff --git a/tests/corpus/7480-builtin-constructor-in.js b/tests/corpus/7480-builtin-constructor-in.js new file mode 100644 index 000000000..781a00105 --- /dev/null +++ b/tests/corpus/7480-builtin-constructor-in.js @@ -0,0 +1,9 @@ +// Feature probes on builtin constructor globals answer like Node. +const E = "captureStackTrace" in Error ? Error : null; +console.log(E === Error, "stackTraceLimit" in Error, "nope" in Error); +console.log("captureStackTrace" in TypeError, "keys" in Object, "fromAsync" in Array, "withResolvers" in Promise); +console.log("call" in Map, "hasOwnProperty" in Set, "groupBy" in Map, "groupBy" in Set, "now" in Date); +const capture = "captureStackTrace" in Error ? Error.captureStackTrace : () => {}; +const target = {}; +capture(target); +console.log(typeof target.stack); diff --git a/tests/corpus/7481-class-runtime-heritage-object-error.js b/tests/corpus/7481-class-runtime-heritage-object-error.js new file mode 100644 index 000000000..32e17c640 --- /dev/null +++ b/tests/corpus/7481-class-runtime-heritage-object-error.js @@ -0,0 +1,51 @@ +function make(name, params) { + const Parent = (params && params.Parent) || Object; + class Definition extends Parent { + describe() { + return `${name}:${this.kind}`; + } + } + return Definition; +} +const A = make("A"); +const a = new A(); +a.kind = "plain"; +console.log(typeof a, a instanceof A, a.describe(), JSON.stringify(a), Object.keys(a)); +const E = make("E", { Parent: Error }); +const e = new E("boom"); +e.kind = "error"; +console.log(e instanceof E, e.message, typeof e.stack, e.describe(), Object.keys(e), String(e)); +const Parent = E; +console.log(e instanceof Parent, new E() .message === "", Object.prototype.hasOwnProperty.call(new E(), "message")); +try { + throw new E("thrown"); +} catch (err) { + console.log(err instanceof E, err.message); +} +console.log(e instanceof Error, new A() instanceof Error); +try { + throw new E("caught"); +} catch (err) { + console.log(err instanceof Error, err.message); +} +// The cause option is read like Error's: inherited and accessor causes +// count, and a throwing getter's exception propagates. +const inherited = new E("inherited", Object.create({ cause: "from prototype" })); +console.log(inherited.cause, Object.prototype.hasOwnProperty.call(inherited, "cause")); +let reads = 0; +const accessor = new E("accessor", { + get cause() { + reads++; + return "from getter"; + }, +}); +console.log(accessor.cause, reads, "cause" in new E("none", {}), "cause" in new E("undef", { cause: undefined })); +try { + new E("throws", { + get cause() { + throw new Error("getter failed"); + }, + }); +} catch (err) { + console.log(err.message); +} diff --git a/tests/corpus/7482-define-property-function-class-values.js b/tests/corpus/7482-define-property-function-class-values.js new file mode 100644 index 000000000..7a682adab --- /dev/null +++ b/tests/corpus/7482-define-property-function-class-values.js @@ -0,0 +1,35 @@ +const desc = { value: undefined, enumerable: false }; +function factory(name) { + class Definition {} + Object.defineProperty(Definition, "name", { value: name }); + function init(inst, def) { + if (!inst._meta) { + desc.value = { def, traits: new Set() }; + try { + Object.defineProperty(inst, "_meta", desc); + } finally { + desc.value = undefined; + } + } + inst._meta.traits.add(name); + } + function _(def) { + const inst = this; + init(inst, def); + return inst; + } + Object.defineProperty(_, "init", { value: init }); + Object.defineProperty(_, Symbol.hasInstance, { + value: (inst) => !!(inst && inst._meta && inst._meta.traits.has(name)), + }); + Object.defineProperty(_, "name", { value: name }); + return { ctor: _, Definition }; +} +const { ctor: Thing, Definition } = factory("Thing"); +console.log(Definition.name, Thing.name, typeof Thing.init); +const t = new Thing({ a: 1 }); +console.log(Object.keys(t), JSON.stringify(t), t._meta.def.a, [...t._meta.traits]); +console.log(t instanceof Thing, {} instanceof Thing); +const plain = {}; +Thing.init(plain, { b: 2 }); +console.log(plain instanceof Thing, Object.keys(plain), plain._meta.def.b); diff --git a/tests/corpus/7483-constructor-factory-traits.js b/tests/corpus/7483-constructor-factory-traits.js new file mode 100644 index 000000000..e022f0672 --- /dev/null +++ b/tests/corpus/7483-constructor-factory-traits.js @@ -0,0 +1,58 @@ +const _desc = { value: undefined, enumerable: false }; +function $constructor(name, initializer, params) { + const proto = {}; + function Internals(def) { + this.def = def; + this.constr = _; + this.traits = new Set(); + } + Internals.prototype = proto; + function init(inst, def) { + if (!inst._zod) { + _desc.value = new Internals(def); + try { + Object.defineProperty(inst, "_zod", _desc); + } finally { + _desc.value = undefined; + } + } else if (inst._zod.traits.has(name)) { + return; + } + inst._zod.traits.add(name); + initializer(inst, def); + } + const Parent = params?.Parent ?? Object; + class Definition extends Parent {} + Object.defineProperty(Definition, "name", { value: name }); + function _(def) { + const inst = params?.Parent ? new Definition() : this; + init(inst, def); + return inst; + } + Object.defineProperty(_, "init", { value: init }); + Object.defineProperty(_, Symbol.hasInstance, { + value: (inst) => { + if (params?.Parent && inst instanceof params.Parent) return true; + return inst?._zod?.traits?.has(name); + }, + }); + Object.defineProperty(_, "name", { value: name }); + return _; +} +const $Base = $constructor("$Base", (inst, def) => { + inst.kind = def.kind; + inst._zod.parse = (v) => (typeof v === def.kind ? { value: v } : { issues: [`expected ${def.kind}`] }); +}); +const $Str = $constructor("$Str", (inst, def) => { + $Base.init(inst, def); + inst.min = def.min; +}); +const $Err = $constructor("$Err", (inst, def) => { + inst.issues = def; + inst.message = JSON.stringify(def); +}, { Parent: Error }); +const s = new $Str({ kind: "string", min: 2 }); +console.log(s.kind, s.min, [...s._zod.traits], s instanceof $Str, s instanceof $Base, Object.keys(s)); +console.log(JSON.stringify(s._zod.parse("ab")), JSON.stringify(s._zod.parse(3))); +const e = new $Err(["bad"]); +console.log(e instanceof $Err, e.issues, e.message, $Str.name, $Err.name); diff --git a/tests/corpus/7484-hoisted-function-captures-later-class.js b/tests/corpus/7484-hoisted-function-captures-later-class.js new file mode 100644 index 000000000..2272882af --- /dev/null +++ b/tests/corpus/7484-hoisted-function-captures-later-class.js @@ -0,0 +1,23 @@ +function f(params) { + function early() { + return make; + } + const Parent = params?.Parent ?? Object; + class D extends Parent {} + function make() { + return new D(); + } + return early()(); +} +console.log(typeof f(), typeof f({ Parent: Error })); +function g() { + function early() { + return make; + } + class E {} + function make() { + return new E(); + } + return early()(); +} +console.log(typeof g()); diff --git a/tests/corpus/7485-global-object-own-properties.js b/tests/corpus/7485-global-object-own-properties.js new file mode 100644 index 000000000..a9a72be18 --- /dev/null +++ b/tests/corpus/7485-global-object-own-properties.js @@ -0,0 +1,22 @@ +var _a; +globalThis.__direct = { n: 1 }; +console.log(typeof globalThis.__direct, globalThis.__direct.n); + +(_a = globalThis).__alias ?? (_a.__alias = { n: 2 }); +console.log(typeof globalThis.__alias); +const g = globalThis; +g.__viaConst = { n: 3 }; +console.log(typeof globalThis.__viaConst); +globalThis.__nullish ??= { n: 4 }; +console.log(typeof globalThis.__nullish); + +(_a = globalThis).__demo_config ?? (_a.__demo_config = {}); +const globalConfig = globalThis.__demo_config; +function config(next) { + if (next) Object.assign(globalConfig, next); + return globalConfig; +} +config({ localeError: (issue) => `bad ${issue}` }); +console.log(typeof globalConfig, typeof globalConfig.localeError, config().localeError("x")); +const pp = globalThis.__demo_config?.postProcessor; +console.log(pp === undefined, globalThis.__demo_config === globalConfig); diff --git a/tests/corpus/7486-prototype-accessor-string-method-names.js b/tests/corpus/7486-prototype-accessor-string-method-names.js new file mode 100644 index 000000000..9920717ea --- /dev/null +++ b/tests/corpus/7486-prototype-accessor-string-method-names.js @@ -0,0 +1,53 @@ +function own(inst, key, value, enumerable = true) { + Object.defineProperty(inst, key, { configurable: true, writable: true, enumerable, value }); + return value; +} +function defineBound(proto, key, fn) { + Object.defineProperty(proto, key, { + configurable: true, + get() { + return this == null ? fn : own(this, key, fn.bind(this)); + }, + set(value) { + own(this, key, value); + }, + }); +} +function members(proto, table) { + for (const key in table) { + const desc = Object.getOwnPropertyDescriptor(table, key); + if (desc.get) Object.defineProperty(proto, key, { ...desc, enumerable: false }); + else defineBound(proto, key, desc.value); + } +} +function Schema(def) { + this.def = def; +} +let installed = false; +function make(def) { + const inst = new Schema(def); + if (!installed) { + installed = true; + members(Object.getPrototypeOf(inst), { + min(n) { return make({ ...this.def, min: n }); }, + max(n) { return make({ ...this.def, max: n }); }, + trim() { return make({ ...this.def, trim: true }); }, + get size() { return Object.keys(this.def).length; }, + }); + } + return inst; +} +const s = make({ kind: "string" }).min(1).max(200).trim(); +console.log(JSON.stringify(s.def), s.size, Object.keys(s), typeof s.trim); +const detached = make({ kind: "x" }).max; +console.log(JSON.stringify(detached(3).def)); +const proto2 = {}; +Object.defineProperty(proto2, "aaa", { configurable: true, get() { return () => "custom-aaa"; } }); +Object.defineProperty(proto2, "trim", { configurable: true, get() { return () => "custom-trim"; } }); +Object.defineProperty(proto2, "trimx", { configurable: true, get() { return () => "custom-trimx"; } }); +Object.defineProperty(proto2, "slice", { configurable: true, get() { return () => "custom-slice"; } }); +const o2 = Object.create(proto2); +console.log(o2.aaa()); +console.log(o2.trimx()); +console.log(o2.slice()); +console.log(o2.trim()); diff --git a/tests/corpus/7487-namespace-spread-untyped/all.js b/tests/corpus/7487-namespace-spread-untyped/all.js new file mode 100644 index 000000000..e317afdab --- /dev/null +++ b/tests/corpus/7487-namespace-spread-untyped/all.js @@ -0,0 +1,2 @@ +export * from "./shapes.js"; +export const own = "own"; diff --git a/tests/corpus/7487-namespace-spread-untyped/extra.js b/tests/corpus/7487-namespace-spread-untyped/extra.js new file mode 100644 index 000000000..8c62c2dd0 --- /dev/null +++ b/tests/corpus/7487-namespace-spread-untyped/extra.js @@ -0,0 +1,2 @@ +export const tag = (s) => `<${s}>`; +export function zeta() { return "z"; } diff --git a/tests/corpus/7487-namespace-spread-untyped/main.js b/tests/corpus/7487-namespace-spread-untyped/main.js new file mode 100644 index 000000000..2352e844d --- /dev/null +++ b/tests/corpus/7487-namespace-spread-untyped/main.js @@ -0,0 +1,16 @@ +import * as shapes from "./shapes.js"; +import * as extra from "./extra.js"; +// A namespace spread into an untyped object (zod's local +// `const z = { ..._schemas, ..._checks, iso: _iso }`). +function untyped(value) { + return value; +} +const z = untyped({ ...shapes, ...extra, extra }); +console.log(Object.keys(z).join(",")); +for (const key of ["circle", "square"]) console.log(key, z[key](2).area.toFixed(2)); +console.log(z.VERSION, typeof z.extra.tag, z.extra.tag("x"), new z.Box(4).size); +// A namespace with star re-exports holds the re-exported members too. +import * as all from "./all.js"; +const w = untyped({ ...all }); +console.log(Object.keys(w).join(","), w.circle(1).area.toFixed(2), w.own, new w.Box(2).size); +for (const key of ["circle", "own", "VERSION", "missing"]) console.log(key, typeof all[key]); diff --git a/tests/corpus/7487-namespace-spread-untyped/shapes.js b/tests/corpus/7487-namespace-spread-untyped/shapes.js new file mode 100644 index 000000000..a8b5cbfc2 --- /dev/null +++ b/tests/corpus/7487-namespace-spread-untyped/shapes.js @@ -0,0 +1,4 @@ +export const VERSION = "1.2"; +export function circle(r) { return { kind: "circle", area: Math.PI * r * r }; } +export function square(s) { return { kind: "square", area: s * s }; } +export class Box { constructor(size) { this.size = size; } } diff --git a/tests/corpus/7488-math-spread-untyped.js b/tests/corpus/7488-math-spread-untyped.js new file mode 100644 index 000000000..028135d41 --- /dev/null +++ b/tests/corpus/7488-math-spread-untyped.js @@ -0,0 +1,69 @@ +// Math.max/Math.min spreading an untyped iterable (zod's +// `Math.min(...caps.map((c) => c[0]))`). +function caps(list) { + return list.map((c) => c[0]); +} +console.log(Math.min(...caps([[5], [2], [9]])), Math.max(...caps([[5], [2], [9]]))); +console.log(Math.min(...caps([])), Math.max(...caps([]))); +console.log(Math.min(...caps([["3"], [true], [null]])), Math.max(...caps([["7"], [undefined]]))); +console.log(Object.is(Math.max(...caps([[-0], [0]])), 0), Object.is(Math.min(...caps([[0], [-0]])), -0)); +const order = []; +const valued = (n) => ({ valueOf() { order.push(n); return n; } }); +console.log(Math.max(...caps([[valued(1)], [NaN], [valued(3)]])), order.join(",")); +try { + Math.min(...caps([[{ valueOf() { throw new Error("boom"); } }]])); +} catch (e) { + console.log(e.message); +} +const sources = { none: undefined, nil: null, num: 5, obj: {}, str: "48" }; +function pick(k) { + return sources[k]; +} +for (const k of Object.keys(sources)) { + try { + console.log(Math.max(...sources[k])); + } catch (e) { + console.log(e.constructor.name, e.message); + } + try { + console.log(Math.min(...pick(k))); + } catch (e) { + console.log(e.constructor.name, e.message); + } +} + +// Iterables that step through the iterator protocol, reached through an +// untyped lookup: a generator, an object with a Symbol.iterator method, a +// Set, a Map's keys() and an iterator that ends early. +function* generate(values) { + for (const value of values) yield value; +} +const makers = { + generator: () => generate([4, "1", 7]), + custom: () => ({ + *[Symbol.iterator]() { + yield 10; + yield -2; + }, + }), + set: () => new Set([3, 8, 3]), + keys: () => new Map([[2, "a"], [6, "b"]]).keys(), + empty: () => generate([]), +}; +for (const k of Object.keys(makers)) { + console.log(k, Math.min(...makers[k]()), Math.max(...makers[k]())); +} +const stepped = []; +const counting = { + [Symbol.iterator]() { + let n = 0; + return { + next() { + stepped.push(n); + return n < 3 ? { value: n++, done: false } : { value: undefined, done: true }; + }, + }; + }, +}; +const lookup = { counting }; +console.log(Math.max(...lookup.counting), stepped.join(",")); diff --git a/tests/corpus/7489-math-spread-generator-only.js b/tests/corpus/7489-math-spread-generator-only.js new file mode 100644 index 000000000..168057aac --- /dev/null +++ b/tests/corpus/7489-math-spread-generator-only.js @@ -0,0 +1,8 @@ +// Math.max and Math.min as the only consumers of an untyped generator: the +// spread steps the generator, whose values fold with ToNumber as in Node. +function* generate(limit) { + for (let i = 1; i <= limit; i++) yield i % 2 === 0 ? String(i * 3) : i; +} +const lookup = { generate }; +console.log(Math.max(...lookup.generate(4)), Math.min(...lookup.generate(4))); +console.log(Math.max(...lookup.generate(0)), Math.min(...lookup.generate(1))); diff --git a/tests/corpus/7536-inspect-custom-errors.ts b/tests/corpus/7536-inspect-custom-errors.ts new file mode 100644 index 000000000..affdd587b --- /dev/null +++ b/tests/corpus/7536-inspect-custom-errors.ts @@ -0,0 +1,23 @@ +// [util.inspect.custom] on Error subclasses: console.log, util.inspect and +// %O call the method for typed values, values held as unknown and errors +// inside containers; a subclass without its own method inherits it, and +// errors without one print as before. +import * as util from "node:util"; + +class Tagged extends Error { + [util.inspect.custom](depth: number): string { + return `Tagged<${this.message}> at depth ${depth}`; + } +} +class Nested extends Tagged {} +class Plain extends Error {} + +const tagged = new Tagged("a"); +console.log(tagged); +console.log(util.inspect(tagged), util.format("%O", new Nested("b"))); +const held: unknown = new Nested("c"); +console.log(held); +const list: unknown[] = [tagged, { inner: new Tagged("d") }]; +console.log(list); +const plain: unknown = new Plain("plain"); +console.log(String(plain), plain instanceof Error); diff --git a/tests/corpus/7537-inspect-custom-errors.cjs b/tests/corpus/7537-inspect-custom-errors.cjs new file mode 100644 index 000000000..3d17f985a --- /dev/null +++ b/tests/corpus/7537-inspect-custom-errors.cjs @@ -0,0 +1,26 @@ +// An Effect-style error class in JavaScript: a method keyed by +// Symbol.for("nodejs.util.inspect.custom") on an Error subclass, called by +// console.log and util.inspect for direct, caught and contained values, +// and the class still constructs and throws when nothing inspects it. +"use strict"; +const util = require("util"); +const NodeInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); + +class YieldableError extends Error { + toJSON() { + return { _tag: "YieldableError", message: this.message }; + } + [NodeInspectSymbol]() { + return `YieldableError: ${this.message}`; + } +} + +const direct = new YieldableError("direct"); +console.log(direct, direct instanceof Error, JSON.stringify(direct)); +try { + throw new YieldableError("boom"); +} catch (e) { + console.log(e instanceof YieldableError, e.message); + console.log(e); + console.log(util.inspect([e]), util.inspect({ e })); +} diff --git a/tests/corpus/7538-inspect-custom-prototypes.mjs b/tests/corpus/7538-inspect-custom-prototypes.mjs new file mode 100644 index 000000000..7a4bfb1a4 --- /dev/null +++ b/tests/corpus/7538-inspect-custom-prototypes.mjs @@ -0,0 +1,44 @@ +// [util.inspect.custom] methods the runtime inspect walk calls: a class +// method returning an object (Effect's `return this.toJSON()`) that no +// typed code inspects, and a method an Effect-style class inherits from a +// constructor function's prototype object. +const NodeInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); + +class Exit { + constructor(value) { + this.value = value; + } + toJSON() { + return { _id: "Exit", value: this.value }; + } + [NodeInspectSymbol]() { + return this.toJSON(); + } +} +const exits = [new Exit(1), new Exit("two")]; +console.log(JSON.stringify(exits)); +console.log(exits); + +const CommitPrototype = { + toJSON() { + return { _id: "Commit" }; + }, + [NodeInspectSymbol]() { + return this.toJSON(); + }, +}; +const Base = (function () { + function Base() {} + Base.prototype = CommitPrototype; + return Base; +})(); +class Effectable extends Base {} +class RefImpl extends Effectable { + constructor(ref) { + super(); + this.ref = ref; + } +} +const ref = new RefImpl(3); +console.log(ref.ref); +console.log(ref, [ref]); diff --git a/tests/corpus/7565-iterable-replaceable-method-results.ts b/tests/corpus/7565-iterable-replaceable-method-results.ts new file mode 100644 index 000000000..4e71a0dcc --- /dev/null +++ b/tests/corpus/7565-iterable-replaceable-method-results.ts @@ -0,0 +1,76 @@ +// A method returning Iterable whose name an untyped write can replace +// (`holder.candidates = ...` through `any`) is called through the +// instance's property lookup: the native method runs while the slot is +// unchanged, and a replacement installed on an instance runs instead, +// its result iterated as an Iterable. + +interface Lookup { + readonly index: string; + readonly key: string; +} +interface Query { + readonly lookups?: readonly Lookup[]; +} +interface Row { + id: string; + n: number; +} + +class Repo { + private readonly rows = new Map(); + add(row: T): void { + this.rows.set(row.id, row); + } + candidates(lookups: readonly Lookup[]): Iterable { + if (lookups.length === 0) return this.rows.values(); + const out: T[] = []; + for (const row of this.rows.values()) if (lookups.every((l) => l.key === row.id)) out.push(row); + return out; + } + query(query: Query): T[] { + const matches: T[] = []; + for (const row of this.candidates(query.lookups ?? [])) matches.push(row); + return matches; + } + ids(query: Query): string { + let out = ""; + for (const row of this.candidates(query.lookups ?? [])) out += (out ? "," : "") + row.id; + return out; + } +} + +function seed(): Repo { + const r = new Repo(); + r.add({ id: "a", n: 1 }); + r.add({ id: "b", n: 2 }); + r.add({ id: "c", n: 3 }); + return r; +} + +const r = seed(); +console.log(r.query({}).length, r.query({ lookups: [{ index: "i", key: "b" }] })); +console.log(r.ids({}), r.ids({ lookups: [{ index: "i", key: "c" }] })); + +// An untyped write of the same name: on an unrelated object first, then +// on one Repo instance, whose own property shadows the class method. +const holder: any = {}; +holder.candidates = (): Row[] => []; +console.log(typeof holder.candidates); + +const replaced = seed(); +const untyped: any = replaced; +untyped.candidates = function (lookups: readonly Lookup[]): Row[] { + return [{ id: "z" + lookups.length, n: 9 }]; +}; +console.log(replaced.query({}), replaced.query({ lookups: [{ index: "i", key: "a" }] })); +console.log(replaced.ids({})); + +// A generator replacement is single-pass, as in JavaScript. +untyped.candidates = function* (): Generator { + yield { id: "g1", n: 10 }; + yield { id: "g2", n: 11 }; +}; +console.log(replaced.query({}).map((row) => row.id + ":" + row.n)); + +// The untouched instance still runs the class method. +console.log(r.ids({})); diff --git a/tests/corpus/7600-navigator-optional-chain.js b/tests/corpus/7600-navigator-optional-chain.js new file mode 100644 index 000000000..5c1dac442 --- /dev/null +++ b/tests/corpus/7600-navigator-optional-chain.js @@ -0,0 +1,7 @@ +// Node's navigator global read through optional chains (zod 4's +// `navigator?.userAgent?.includes("Cloudflare")` probe): navigator is +// never nullish, so each chain answers like the plain member read. +const cloudflare = typeof navigator !== "undefined" && navigator?.userAgent?.includes("Cloudflare"); +console.log(cloudflare); +console.log(navigator?.userAgent === navigator.userAgent, navigator?.userAgent?.startsWith("Node.js/")); +console.log(navigator?.userAgent?.split("/")[0] ?? "none"); diff --git a/tests/corpus/7602-error-stack-limit-value.js b/tests/corpus/7602-error-stack-limit-value.js new file mode 100644 index 000000000..4b3f8cc7e --- /dev/null +++ b/tests/corpus/7602-error-stack-limit-value.js @@ -0,0 +1,18 @@ +// Error.stackTraceLimit through the Error constructor held as a value +// (zod 4's `const E = "captureStackTrace" in Error ? Error : null`): reads +// and writes reach the same limit as the static member. +const E = "captureStackTrace" in Error ? Error : null; +console.log(typeof E, E === Error, E.stackTraceLimit, Error.stackTraceLimit); +const saved = E.stackTraceLimit; +E.stackTraceLimit = 0; +console.log(Error.stackTraceLimit, E.stackTraceLimit, typeof saved === "number"); +console.log(new Error("no frames").stack); +try { + E.stackTraceLimit = 3; + console.log(Error.stackTraceLimit); +} finally { + E.stackTraceLimit = saved; +} +console.log(E.stackTraceLimit, Error.stackTraceLimit); +Error.stackTraceLimit = 4; +console.log(E.stackTraceLimit); diff --git a/tests/corpus/7603-error-stack-limit-own.js b/tests/corpus/7603-error-stack-limit-own.js new file mode 100644 index 000000000..dfd4b7cdf --- /dev/null +++ b/tests/corpus/7603-error-stack-limit-own.js @@ -0,0 +1,13 @@ +// An own stackTraceLimit on the Error constructor held as a value (a +// non-number assigned, or one defined with Object.defineProperty) answers +// reads through the value, and writes to it keep its descriptor. +const E = /** @type {any} */ (Error); +E.stackTraceLimit = "deep"; +console.log(E.stackTraceLimit, typeof E.stackTraceLimit); +E.stackTraceLimit = 2; +console.log(E.stackTraceLimit, Error.stackTraceLimit); +Object.defineProperty(E, "stackTraceLimit", { value: 6, writable: false, configurable: true }); +try { + E.stackTraceLimit = 1; +} catch {} +console.log(E.stackTraceLimit); diff --git a/tests/corpus/7604-error-stack-limit-frozen.js b/tests/corpus/7604-error-stack-limit-frozen.js new file mode 100644 index 000000000..9c73d1403 --- /dev/null +++ b/tests/corpus/7604-error-stack-limit-frozen.js @@ -0,0 +1,9 @@ +// A frozen Error keeps stackTraceLimit read-only through the constructor +// held as a value. +const E = /** @type {any} */ (Error); +E.stackTraceLimit = 3; +Object.freeze(E); +try { + E.stackTraceLimit = 1; +} catch {} +console.log(E.stackTraceLimit, Error.stackTraceLimit); diff --git a/tests/diagnostics/inspect-custom-fences.ts b/tests/diagnostics/inspect-custom-fences.ts index f4c7c593d..404f6ddab 100644 --- a/tests/diagnostics/inspect-custom-fences.ts +++ b/tests/diagnostics/inspect-custom-fences.ts @@ -1,7 +1,7 @@ // [util.inspect.custom] methods keep their boundaries: a class whose -// subclass declares the method without declaring it itself, an Error -// subclass with the method, a method with more than three parameters, a -// method returning a class instance, and %o over instances with one. +// subclass declares the method without declaring it itself, a method with +// more than three parameters, a method returning a class instance, and %o +// over instances with one. import { format, inspect } from "node:util"; class Plain { @@ -15,13 +15,6 @@ class Fancy extends Plain { const items: Plain[] = [new Fancy()]; console.log(inspect(items)); -class Failure extends Error { - [inspect.custom](): string { - return "failure"; - } -} -console.log(new Failure("x").message); - class Wide { [inspect.custom](a: number, b: unknown, c: unknown, d: unknown): string { return `${a}${String(b)}${String(c)}${String(d)}`; diff --git a/tests/fixtures/npm/cases/types-boundary/main.ts b/tests/fixtures/npm/cases/types-boundary/main.ts new file mode 100644 index 000000000..c33e6e9bf --- /dev/null +++ b/tests/fixtures/npm/cases/types-boundary/main.ts @@ -0,0 +1,19 @@ +// A plain-JavaScript package typed by an @types package: rows the +// declarations type as plain data become native records; the cursor, a +// constructor-function instance its declarations describe with an +// interface, keeps its JavaScript-world typing. +import { parseRows, cursor, type Row } from "rowparse"; + +const rows: Row[] = parseRows("name,qty\napple,3\npear,5\nfig,2\n"); +let total = 0; +for (const r of rows) total += Number(r.cells["qty"]); +console.log("total", total, rows.length); +const widest = rows.reduce((w, r) => Math.max(w, r.width), 0); +console.log("widest", widest); +rows[0]!.cells["qty"] = "30"; +console.log("first", JSON.stringify(rows[0]), Object.keys(rows[1]!.cells).join("|")); +const sorted = [...rows].sort((a, b) => a.cells["name"]!.localeCompare(b.cells["name"]!)).map((r) => r.line); +console.log("sorted", sorted.join(",")); +const c = cursor("abcdef"); +c.pos = 2; +console.log("cursor", c.remaining(), c.text); diff --git a/tests/fixtures/npm/cases/types-boundary/node_modules/@types/rowparse/index.d.ts b/tests/fixtures/npm/cases/types-boundary/node_modules/@types/rowparse/index.d.ts new file mode 100644 index 000000000..8e099794d --- /dev/null +++ b/tests/fixtures/npm/cases/types-boundary/node_modules/@types/rowparse/index.d.ts @@ -0,0 +1,12 @@ +export type Row = { + line: number; + cells: { [column: string]: string }; + width: number; +}; +export declare function parseRows(text: string): Row[]; +export interface Cursor { + text: string; + pos: number; + remaining(): number; +} +export declare function cursor(text: string): Cursor; diff --git a/tests/fixtures/npm/cases/types-boundary/node_modules/@types/rowparse/package.json b/tests/fixtures/npm/cases/types-boundary/node_modules/@types/rowparse/package.json new file mode 100644 index 000000000..7ce3bcc0e --- /dev/null +++ b/tests/fixtures/npm/cases/types-boundary/node_modules/@types/rowparse/package.json @@ -0,0 +1 @@ +{ "name": "@types/rowparse", "version": "1.0.0", "types": "index.d.ts" } diff --git a/tests/fixtures/npm/cases/types-boundary/node_modules/rowparse/index.js b/tests/fixtures/npm/cases/types-boundary/node_modules/rowparse/index.js new file mode 100644 index 000000000..4a53c8481 --- /dev/null +++ b/tests/fixtures/npm/cases/types-boundary/node_modules/rowparse/index.js @@ -0,0 +1,26 @@ +"use strict"; +// Plain JavaScript: rows are built key by key, so inference sees nothing. +function Cursor(text) { + this.text = text; + this.pos = 0; +} +Cursor.prototype.remaining = function () { + return this.text.length - this.pos; +}; +function parseRows(text) { + var lines = String(text).split("\n").filter(Boolean); + var header = lines[0].split(","); + var rows = []; + for (var i = 1; i < lines.length; i++) { + var cells = lines[i].split(","); + var row = {}; + for (var j = 0; j < header.length; j++) row[header[j]] = cells[j]; + rows.push({ line: i, cells: row, width: cells.length }); + } + return rows; +} +function cursor(text) { + return new Cursor(text); +} +exports.parseRows = parseRows; +exports.cursor = cursor; diff --git a/tests/fixtures/npm/cases/types-boundary/node_modules/rowparse/package.json b/tests/fixtures/npm/cases/types-boundary/node_modules/rowparse/package.json new file mode 100644 index 000000000..da66597bc --- /dev/null +++ b/tests/fixtures/npm/cases/types-boundary/node_modules/rowparse/package.json @@ -0,0 +1 @@ +{ "name": "rowparse", "version": "1.0.0", "main": "index.js" } diff --git a/tests/fixtures/npm/cases/types-boundary/package.json b/tests/fixtures/npm/cases/types-boundary/package.json new file mode 100644 index 000000000..bae20e44c --- /dev/null +++ b/tests/fixtures/npm/cases/types-boundary/package.json @@ -0,0 +1 @@ +{ "name": "types-boundary-case", "private": true, "type": "module" } diff --git a/tests/fixtures/npm/cases/zod3-adoption/main.ts b/tests/fixtures/npm/cases/zod3-adoption/main.ts new file mode 100644 index 000000000..d21ff69ee --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/main.ts @@ -0,0 +1,48 @@ +// Adoption at the declaration-typed boundary: objects package code still +// references (a cache, weak keys, parsed documents, a cycle through an +// unknown field) keep their identity when they cross into typed code. +import { z } from "zod"; + +const Row = z.object({ id: z.number(), name: z.string(), tags: z.array(z.string()), meta: z.unknown() }); +type Row = z.infer; + +// A package-side cache: the same objects come back from every call. +const cache: unknown[] = []; +const weak = new WeakMap(); +const Cached = z.custom<{ id: number; tags: string[] }>().transform((v) => v); +for (let i = 0; i < 4; i++) { + const raw = JSON.parse(`{"id":${i},"name":"n${i}","tags":["a","b"],"meta":{"deep":[1,2,{"x":${i}}]}}`) as unknown; + cache.push(raw); + weak.set(raw as object, "w" + i); +} +let sum = 0; +for (let round = 0; round < 200; round++) { + for (let i = 0; i < cache.length; i++) { + const r: Row = Row.parse(cache[i]); + sum += r.id + r.tags.length; + const again = Row.parse(cache[i]); + if (round === 0) console.log("fresh", r === again, r.name, JSON.stringify(r.meta)); + } +} +console.log("sum", sum); +// The package keeps the source objects; z.custom passes them through. +const kept = cache.map((c) => Cached.parse(c)); +kept[0]!.tags.push("c"); +console.log("kept", JSON.stringify(cache[0]), kept[0] === Cached.parse(cache[0])); +console.log("weak", weak.get(cache[1] as object), weak.has(cache[2] as object)); +// A cycle through an unknown field. +const AsRow = z.custom(); +const cyc: Row = { id: 9, name: "c", tags: [], meta: null }; +cyc.meta = cyc; +const back = AsRow.parse(cyc); +back.id = 10; +console.log("cycle", cyc.id, back === cyc, back.meta === cyc); +// A tuple's elements keep their identity too: the package-side array +// still holds the element that typed code mutates. +const Pair = z.custom<[{ id: number; tags: string[] }, string]>(); +const pairSource = JSON.parse('[{"id":1,"tags":["a"]},"s"]') as unknown[]; +const pairHead = pairSource[0]; +const pair = Pair.parse(pairSource); +pair[0].tags.push("b"); +pair[0].id = 2; +console.log("tuple", JSON.stringify(pairHead), JSON.stringify(pairSource), pair[1]); diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/LICENSE b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/LICENSE new file mode 100644 index 000000000..c06579627 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Colin McDonnell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/index.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/index.js new file mode 100644 index 000000000..8352ab713 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/index.js @@ -0,0 +1,3 @@ +import z3 from "./v3/index.js"; +export * from "./v3/index.js"; +export default z3; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/package.json b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/package.json new file mode 100644 index 000000000..aead43de3 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} \ No newline at end of file diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/ZodError.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/ZodError.js new file mode 100644 index 000000000..c72ce5288 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/ZodError.js @@ -0,0 +1,132 @@ +import { util } from "./helpers/util.js"; +export const ZodIssueCode = util.arrayToEnum([ + "invalid_type", + "invalid_literal", + "custom", + "invalid_union", + "invalid_union_discriminator", + "invalid_enum_value", + "unrecognized_keys", + "invalid_arguments", + "invalid_return_type", + "invalid_date", + "invalid_string", + "too_small", + "too_big", + "invalid_intersection_types", + "not_multiple_of", + "not_finite", +]); +export const quotelessJson = (obj) => { + const json = JSON.stringify(obj, null, 2); + return json.replace(/"([^"]+)":/g, "$1:"); +}; +export class ZodError extends Error { + get errors() { + return this.issues; + } + constructor(issues) { + super(); + this.issues = []; + this.addIssue = (sub) => { + this.issues = [...this.issues, sub]; + }; + this.addIssues = (subs = []) => { + this.issues = [...this.issues, ...subs]; + }; + const actualProto = new.target.prototype; + if (Object.setPrototypeOf) { + // eslint-disable-next-line ban/ban + Object.setPrototypeOf(this, actualProto); + } + else { + this.__proto__ = actualProto; + } + this.name = "ZodError"; + this.issues = issues; + } + format(_mapper) { + const mapper = _mapper || + function (issue) { + return issue.message; + }; + const fieldErrors = { _errors: [] }; + const processError = (error) => { + for (const issue of error.issues) { + if (issue.code === "invalid_union") { + issue.unionErrors.map(processError); + } + else if (issue.code === "invalid_return_type") { + processError(issue.returnTypeError); + } + else if (issue.code === "invalid_arguments") { + processError(issue.argumentsError); + } + else if (issue.path.length === 0) { + fieldErrors._errors.push(mapper(issue)); + } + else { + let curr = fieldErrors; + let i = 0; + while (i < issue.path.length) { + const el = issue.path[i]; + const terminal = i === issue.path.length - 1; + if (!terminal) { + curr[el] = curr[el] || { _errors: [] }; + // if (typeof el === "string") { + // curr[el] = curr[el] || { _errors: [] }; + // } else if (typeof el === "number") { + // const errorArray: any = []; + // errorArray._errors = []; + // curr[el] = curr[el] || errorArray; + // } + } + else { + curr[el] = curr[el] || { _errors: [] }; + curr[el]._errors.push(mapper(issue)); + } + curr = curr[el]; + i++; + } + } + } + }; + processError(this); + return fieldErrors; + } + static assert(value) { + if (!(value instanceof ZodError)) { + throw new Error(`Not a ZodError: ${value}`); + } + } + toString() { + return this.message; + } + get message() { + return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2); + } + get isEmpty() { + return this.issues.length === 0; + } + flatten(mapper = (issue) => issue.message) { + const fieldErrors = {}; + const formErrors = []; + for (const sub of this.issues) { + if (sub.path.length > 0) { + fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || []; + fieldErrors[sub.path[0]].push(mapper(sub)); + } + else { + formErrors.push(mapper(sub)); + } + } + return { formErrors, fieldErrors }; + } + get formErrors() { + return this.flatten(); + } +} +ZodError.create = (issues) => { + const error = new ZodError(issues); + return error; +}; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/errors.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/errors.js new file mode 100644 index 000000000..26fdf628a --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/errors.js @@ -0,0 +1,9 @@ +import defaultErrorMap from "./locales/en.js"; +let overrideErrorMap = defaultErrorMap; +export { defaultErrorMap }; +export function setErrorMap(map) { + overrideErrorMap = map; +} +export function getErrorMap() { + return overrideErrorMap; +} diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/external.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/external.js new file mode 100644 index 000000000..f0a4be4c7 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/external.js @@ -0,0 +1,6 @@ +export * from "./errors.js"; +export * from "./helpers/parseUtil.js"; +export * from "./helpers/typeAliases.js"; +export * from "./helpers/util.js"; +export * from "./types.js"; +export * from "./ZodError.js"; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/enumUtil.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/enumUtil.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/enumUtil.js @@ -0,0 +1 @@ +export {}; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/errorUtil.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/errorUtil.js new file mode 100644 index 000000000..e1640d1a4 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/errorUtil.js @@ -0,0 +1,6 @@ +export var errorUtil; +(function (errorUtil) { + errorUtil.errToObj = (message) => typeof message === "string" ? { message } : message || {}; + // biome-ignore lint: + errorUtil.toString = (message) => typeof message === "string" ? message : message?.message; +})(errorUtil || (errorUtil = {})); diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/parseUtil.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/parseUtil.js new file mode 100644 index 000000000..91b2afb81 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/parseUtil.js @@ -0,0 +1,109 @@ +import { getErrorMap } from "../errors.js"; +import defaultErrorMap from "../locales/en.js"; +export const makeIssue = (params) => { + const { data, path, errorMaps, issueData } = params; + const fullPath = [...path, ...(issueData.path || [])]; + const fullIssue = { + ...issueData, + path: fullPath, + }; + if (issueData.message !== undefined) { + return { + ...issueData, + path: fullPath, + message: issueData.message, + }; + } + let errorMessage = ""; + const maps = errorMaps + .filter((m) => !!m) + .slice() + .reverse(); + for (const map of maps) { + errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message; + } + return { + ...issueData, + path: fullPath, + message: errorMessage, + }; +}; +export const EMPTY_PATH = []; +export function addIssueToContext(ctx, issueData) { + const overrideMap = getErrorMap(); + const issue = makeIssue({ + issueData: issueData, + data: ctx.data, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, // contextual error map is first priority + ctx.schemaErrorMap, // then schema-bound map if available + overrideMap, // then global override map + overrideMap === defaultErrorMap ? undefined : defaultErrorMap, // then global default map + ].filter((x) => !!x), + }); + ctx.common.issues.push(issue); +} +export class ParseStatus { + constructor() { + this.value = "valid"; + } + dirty() { + if (this.value === "valid") + this.value = "dirty"; + } + abort() { + if (this.value !== "aborted") + this.value = "aborted"; + } + static mergeArray(status, results) { + const arrayValue = []; + for (const s of results) { + if (s.status === "aborted") + return INVALID; + if (s.status === "dirty") + status.dirty(); + arrayValue.push(s.value); + } + return { status: status.value, value: arrayValue }; + } + static async mergeObjectAsync(status, pairs) { + const syncPairs = []; + for (const pair of pairs) { + const key = await pair.key; + const value = await pair.value; + syncPairs.push({ + key, + value, + }); + } + return ParseStatus.mergeObjectSync(status, syncPairs); + } + static mergeObjectSync(status, pairs) { + const finalObject = {}; + for (const pair of pairs) { + const { key, value } = pair; + if (key.status === "aborted") + return INVALID; + if (value.status === "aborted") + return INVALID; + if (key.status === "dirty") + status.dirty(); + if (value.status === "dirty") + status.dirty(); + if (key.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) { + finalObject[key.value] = value.value; + } + } + return { status: status.value, value: finalObject }; + } +} +export const INVALID = Object.freeze({ + status: "aborted", +}); +export const DIRTY = (value) => ({ status: "dirty", value }); +export const OK = (value) => ({ status: "valid", value }); +export const isAborted = (x) => x.status === "aborted"; +export const isDirty = (x) => x.status === "dirty"; +export const isValid = (x) => x.status === "valid"; +export const isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/partialUtil.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/partialUtil.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/partialUtil.js @@ -0,0 +1 @@ +export {}; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/typeAliases.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/typeAliases.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/typeAliases.js @@ -0,0 +1 @@ +export {}; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/util.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/util.js new file mode 100644 index 000000000..b9641f29b --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/helpers/util.js @@ -0,0 +1,133 @@ +export var util; +(function (util) { + util.assertEqual = (_) => { }; + function assertIs(_arg) { } + util.assertIs = assertIs; + function assertNever(_x) { + throw new Error(); + } + util.assertNever = assertNever; + util.arrayToEnum = (items) => { + const obj = {}; + for (const item of items) { + obj[item] = item; + } + return obj; + }; + util.getValidEnumValues = (obj) => { + const validKeys = util.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number"); + const filtered = {}; + for (const k of validKeys) { + filtered[k] = obj[k]; + } + return util.objectValues(filtered); + }; + util.objectValues = (obj) => { + return util.objectKeys(obj).map(function (e) { + return obj[e]; + }); + }; + util.objectKeys = typeof Object.keys === "function" // eslint-disable-line ban/ban + ? (obj) => Object.keys(obj) // eslint-disable-line ban/ban + : (object) => { + const keys = []; + for (const key in object) { + if (Object.prototype.hasOwnProperty.call(object, key)) { + keys.push(key); + } + } + return keys; + }; + util.find = (arr, checker) => { + for (const item of arr) { + if (checker(item)) + return item; + } + return undefined; + }; + util.isInteger = typeof Number.isInteger === "function" + ? (val) => Number.isInteger(val) // eslint-disable-line ban/ban + : (val) => typeof val === "number" && Number.isFinite(val) && Math.floor(val) === val; + function joinValues(array, separator = " | ") { + return array.map((val) => (typeof val === "string" ? `'${val}'` : val)).join(separator); + } + util.joinValues = joinValues; + util.jsonStringifyReplacer = (_, value) => { + if (typeof value === "bigint") { + return value.toString(); + } + return value; + }; +})(util || (util = {})); +export var objectUtil; +(function (objectUtil) { + objectUtil.mergeShapes = (first, second) => { + return { + ...first, + ...second, // second overwrites first + }; + }; +})(objectUtil || (objectUtil = {})); +export const ZodParsedType = util.arrayToEnum([ + "string", + "nan", + "number", + "integer", + "float", + "boolean", + "date", + "bigint", + "symbol", + "function", + "undefined", + "null", + "array", + "object", + "unknown", + "promise", + "void", + "never", + "map", + "set", +]); +export const getParsedType = (data) => { + const t = typeof data; + switch (t) { + case "undefined": + return ZodParsedType.undefined; + case "string": + return ZodParsedType.string; + case "number": + return Number.isNaN(data) ? ZodParsedType.nan : ZodParsedType.number; + case "boolean": + return ZodParsedType.boolean; + case "function": + return ZodParsedType.function; + case "bigint": + return ZodParsedType.bigint; + case "symbol": + return ZodParsedType.symbol; + case "object": + if (Array.isArray(data)) { + return ZodParsedType.array; + } + if (data === null) { + return ZodParsedType.null; + } + if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") { + return ZodParsedType.promise; + } + if (typeof Map !== "undefined" && data instanceof Map) { + return ZodParsedType.map; + } + if (typeof Set !== "undefined" && data instanceof Set) { + return ZodParsedType.set; + } + if (typeof Date !== "undefined" && data instanceof Date) { + return ZodParsedType.date; + } + return ZodParsedType.object; + default: + return ZodParsedType.unknown; + } +}; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/index.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/index.js new file mode 100644 index 000000000..a9dab5738 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/index.js @@ -0,0 +1,4 @@ +import * as z from "./external.js"; +export * from "./external.js"; +export { z }; +export default z; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/locales/en.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/locales/en.js new file mode 100644 index 000000000..3e97ad51a --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/locales/en.js @@ -0,0 +1,107 @@ +import { ZodIssueCode } from "../ZodError.js"; +import { util, ZodParsedType } from "../helpers/util.js"; +const errorMap = (issue, _ctx) => { + let message; + switch (issue.code) { + case ZodIssueCode.invalid_type: + if (issue.received === ZodParsedType.undefined) { + message = "Required"; + } + else { + message = `Expected ${issue.expected}, received ${issue.received}`; + } + break; + case ZodIssueCode.invalid_literal: + message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util.jsonStringifyReplacer)}`; + break; + case ZodIssueCode.unrecognized_keys: + message = `Unrecognized key(s) in object: ${util.joinValues(issue.keys, ", ")}`; + break; + case ZodIssueCode.invalid_union: + message = `Invalid input`; + break; + case ZodIssueCode.invalid_union_discriminator: + message = `Invalid discriminator value. Expected ${util.joinValues(issue.options)}`; + break; + case ZodIssueCode.invalid_enum_value: + message = `Invalid enum value. Expected ${util.joinValues(issue.options)}, received '${issue.received}'`; + break; + case ZodIssueCode.invalid_arguments: + message = `Invalid function arguments`; + break; + case ZodIssueCode.invalid_return_type: + message = `Invalid function return type`; + break; + case ZodIssueCode.invalid_date: + message = `Invalid date`; + break; + case ZodIssueCode.invalid_string: + if (typeof issue.validation === "object") { + if ("includes" in issue.validation) { + message = `Invalid input: must include "${issue.validation.includes}"`; + if (typeof issue.validation.position === "number") { + message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`; + } + } + else if ("startsWith" in issue.validation) { + message = `Invalid input: must start with "${issue.validation.startsWith}"`; + } + else if ("endsWith" in issue.validation) { + message = `Invalid input: must end with "${issue.validation.endsWith}"`; + } + else { + util.assertNever(issue.validation); + } + } + else if (issue.validation !== "regex") { + message = `Invalid ${issue.validation}`; + } + else { + message = "Invalid"; + } + break; + case ZodIssueCode.too_small: + if (issue.type === "array") + message = `Array must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`; + else if (issue.type === "string") + message = `String must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`; + else if (issue.type === "number") + message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`; + else if (issue.type === "date") + message = `Date must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${new Date(Number(issue.minimum))}`; + else + message = "Invalid input"; + break; + case ZodIssueCode.too_big: + if (issue.type === "array") + message = `Array must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`; + else if (issue.type === "string") + message = `String must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`; + else if (issue.type === "number") + message = `Number must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`; + else if (issue.type === "bigint") + message = `BigInt must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`; + else if (issue.type === "date") + message = `Date must be ${issue.exact ? `exactly` : issue.inclusive ? `smaller than or equal to` : `smaller than`} ${new Date(Number(issue.maximum))}`; + else + message = "Invalid input"; + break; + case ZodIssueCode.custom: + message = `Invalid input`; + break; + case ZodIssueCode.invalid_intersection_types: + message = `Intersection results could not be merged`; + break; + case ZodIssueCode.not_multiple_of: + message = `Number must be a multiple of ${issue.multipleOf}`; + break; + case ZodIssueCode.not_finite: + message = "Number must be finite"; + break; + default: + message = _ctx.defaultError; + util.assertNever(issue); + } + return { message }; +}; +export default errorMap; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/standard-schema.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/standard-schema.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/standard-schema.js @@ -0,0 +1 @@ +export {}; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/types.js b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/types.js new file mode 100644 index 000000000..de1e6213e --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/esm/v3/types.js @@ -0,0 +1,3691 @@ +import { ZodError, ZodIssueCode, } from "./ZodError.js"; +import { defaultErrorMap, getErrorMap } from "./errors.js"; +import { errorUtil } from "./helpers/errorUtil.js"; +import { DIRTY, INVALID, OK, ParseStatus, addIssueToContext, isAborted, isAsync, isDirty, isValid, makeIssue, } from "./helpers/parseUtil.js"; +import { util, ZodParsedType, getParsedType } from "./helpers/util.js"; +class ParseInputLazyPath { + constructor(parent, value, path, key) { + this._cachedPath = []; + this.parent = parent; + this.data = value; + this._path = path; + this._key = key; + } + get path() { + if (!this._cachedPath.length) { + if (Array.isArray(this._key)) { + this._cachedPath.push(...this._path, ...this._key); + } + else { + this._cachedPath.push(...this._path, this._key); + } + } + return this._cachedPath; + } +} +const handleResult = (ctx, result) => { + if (isValid(result)) { + return { success: true, data: result.value }; + } + else { + if (!ctx.common.issues.length) { + throw new Error("Validation failed but no issues detected."); + } + return { + success: false, + get error() { + if (this._error) + return this._error; + const error = new ZodError(ctx.common.issues); + this._error = error; + return this._error; + }, + }; + } +}; +function processCreateParams(params) { + if (!params) + return {}; + const { errorMap, invalid_type_error, required_error, description } = params; + if (errorMap && (invalid_type_error || required_error)) { + throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`); + } + if (errorMap) + return { errorMap: errorMap, description }; + const customMap = (iss, ctx) => { + const { message } = params; + if (iss.code === "invalid_enum_value") { + return { message: message ?? ctx.defaultError }; + } + if (typeof ctx.data === "undefined") { + return { message: message ?? required_error ?? ctx.defaultError }; + } + if (iss.code !== "invalid_type") + return { message: ctx.defaultError }; + return { message: message ?? invalid_type_error ?? ctx.defaultError }; + }; + return { errorMap: customMap, description }; +} +export class ZodType { + get description() { + return this._def.description; + } + _getType(input) { + return getParsedType(input.data); + } + _getOrReturnCtx(input, ctx) { + return (ctx || { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent, + }); + } + _processInputParams(input) { + return { + status: new ParseStatus(), + ctx: { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent, + }, + }; + } + _parseSync(input) { + const result = this._parse(input); + if (isAsync(result)) { + throw new Error("Synchronous parse encountered promise."); + } + return result; + } + _parseAsync(input) { + const result = this._parse(input); + return Promise.resolve(result); + } + parse(data, params) { + const result = this.safeParse(data, params); + if (result.success) + return result.data; + throw result.error; + } + safeParse(data, params) { + const ctx = { + common: { + issues: [], + async: params?.async ?? false, + contextualErrorMap: params?.errorMap, + }, + path: params?.path || [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data), + }; + const result = this._parseSync({ data, path: ctx.path, parent: ctx }); + return handleResult(ctx, result); + } + "~validate"(data) { + const ctx = { + common: { + issues: [], + async: !!this["~standard"].async, + }, + path: [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data), + }; + if (!this["~standard"].async) { + try { + const result = this._parseSync({ data, path: [], parent: ctx }); + return isValid(result) + ? { + value: result.value, + } + : { + issues: ctx.common.issues, + }; + } + catch (err) { + if (err?.message?.toLowerCase()?.includes("encountered")) { + this["~standard"].async = true; + } + ctx.common = { + issues: [], + async: true, + }; + } + } + return this._parseAsync({ data, path: [], parent: ctx }).then((result) => isValid(result) + ? { + value: result.value, + } + : { + issues: ctx.common.issues, + }); + } + async parseAsync(data, params) { + const result = await this.safeParseAsync(data, params); + if (result.success) + return result.data; + throw result.error; + } + async safeParseAsync(data, params) { + const ctx = { + common: { + issues: [], + contextualErrorMap: params?.errorMap, + async: true, + }, + path: params?.path || [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data), + }; + const maybeAsyncResult = this._parse({ data, path: ctx.path, parent: ctx }); + const result = await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult)); + return handleResult(ctx, result); + } + refine(check, message) { + const getIssueProperties = (val) => { + if (typeof message === "string" || typeof message === "undefined") { + return { message }; + } + else if (typeof message === "function") { + return message(val); + } + else { + return message; + } + }; + return this._refinement((val, ctx) => { + const result = check(val); + const setError = () => ctx.addIssue({ + code: ZodIssueCode.custom, + ...getIssueProperties(val), + }); + if (typeof Promise !== "undefined" && result instanceof Promise) { + return result.then((data) => { + if (!data) { + setError(); + return false; + } + else { + return true; + } + }); + } + if (!result) { + setError(); + return false; + } + else { + return true; + } + }); + } + refinement(check, refinementData) { + return this._refinement((val, ctx) => { + if (!check(val)) { + ctx.addIssue(typeof refinementData === "function" ? refinementData(val, ctx) : refinementData); + return false; + } + else { + return true; + } + }); + } + _refinement(refinement) { + return new ZodEffects({ + schema: this, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect: { type: "refinement", refinement }, + }); + } + superRefine(refinement) { + return this._refinement(refinement); + } + constructor(def) { + /** Alias of safeParseAsync */ + this.spa = this.safeParseAsync; + this._def = def; + this.parse = this.parse.bind(this); + this.safeParse = this.safeParse.bind(this); + this.parseAsync = this.parseAsync.bind(this); + this.safeParseAsync = this.safeParseAsync.bind(this); + this.spa = this.spa.bind(this); + this.refine = this.refine.bind(this); + this.refinement = this.refinement.bind(this); + this.superRefine = this.superRefine.bind(this); + this.optional = this.optional.bind(this); + this.nullable = this.nullable.bind(this); + this.nullish = this.nullish.bind(this); + this.array = this.array.bind(this); + this.promise = this.promise.bind(this); + this.or = this.or.bind(this); + this.and = this.and.bind(this); + this.transform = this.transform.bind(this); + this.brand = this.brand.bind(this); + this.default = this.default.bind(this); + this.catch = this.catch.bind(this); + this.describe = this.describe.bind(this); + this.pipe = this.pipe.bind(this); + this.readonly = this.readonly.bind(this); + this.isNullable = this.isNullable.bind(this); + this.isOptional = this.isOptional.bind(this); + this["~standard"] = { + version: 1, + vendor: "zod", + validate: (data) => this["~validate"](data), + }; + } + optional() { + return ZodOptional.create(this, this._def); + } + nullable() { + return ZodNullable.create(this, this._def); + } + nullish() { + return this.nullable().optional(); + } + array() { + return ZodArray.create(this); + } + promise() { + return ZodPromise.create(this, this._def); + } + or(option) { + return ZodUnion.create([this, option], this._def); + } + and(incoming) { + return ZodIntersection.create(this, incoming, this._def); + } + transform(transform) { + return new ZodEffects({ + ...processCreateParams(this._def), + schema: this, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect: { type: "transform", transform }, + }); + } + default(def) { + const defaultValueFunc = typeof def === "function" ? def : () => def; + return new ZodDefault({ + ...processCreateParams(this._def), + innerType: this, + defaultValue: defaultValueFunc, + typeName: ZodFirstPartyTypeKind.ZodDefault, + }); + } + brand() { + return new ZodBranded({ + typeName: ZodFirstPartyTypeKind.ZodBranded, + type: this, + ...processCreateParams(this._def), + }); + } + catch(def) { + const catchValueFunc = typeof def === "function" ? def : () => def; + return new ZodCatch({ + ...processCreateParams(this._def), + innerType: this, + catchValue: catchValueFunc, + typeName: ZodFirstPartyTypeKind.ZodCatch, + }); + } + describe(description) { + const This = this.constructor; + return new This({ + ...this._def, + description, + }); + } + pipe(target) { + return ZodPipeline.create(this, target); + } + readonly() { + return ZodReadonly.create(this); + } + isOptional() { + return this.safeParse(undefined).success; + } + isNullable() { + return this.safeParse(null).success; + } +} +const cuidRegex = /^c[^\s-]{8,}$/i; +const cuid2Regex = /^[0-9a-z]+$/; +const ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/i; +// const uuidRegex = +// /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; +const uuidRegex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i; +const nanoidRegex = /^[a-z0-9_-]{21}$/i; +const jwtRegex = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/; +const durationRegex = /^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/; +// from https://stackoverflow.com/a/46181/1550155 +// old version: too slow, didn't support unicode +// const emailRegex = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i; +//old email regex +// const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i; +// eslint-disable-next-line +// const emailRegex = +// /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\])|(\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))\])|([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\.[A-Za-z]{2,})+))$/; +// const emailRegex = +// /^[a-zA-Z0-9\.\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; +// const emailRegex = +// /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i; +const emailRegex = /^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; +// const emailRegex = +// /^[a-z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9\-]+)*$/i; +// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression +const _emojiRegex = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`; +let emojiRegex; +// faster, simpler, safer +const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/; +const ipv4CidrRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/(3[0-2]|[12]?[0-9])$/; +// const ipv6Regex = +// /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/; +const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; +const ipv6CidrRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/; +// https://stackoverflow.com/questions/7860392/determine-if-string-is-in-base64-using-javascript +const base64Regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/; +// https://base64.guru/standards/base64url +const base64urlRegex = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/; +// simple +// const dateRegexSource = `\\d{4}-\\d{2}-\\d{2}`; +// no leap year validation +// const dateRegexSource = `\\d{4}-((0[13578]|10|12)-31|(0[13-9]|1[0-2])-30|(0[1-9]|1[0-2])-(0[1-9]|1\\d|2\\d))`; +// with leap year validation +const dateRegexSource = `((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))`; +const dateRegex = new RegExp(`^${dateRegexSource}$`); +function timeRegexSource(args) { + let secondsRegexSource = `[0-5]\\d`; + if (args.precision) { + secondsRegexSource = `${secondsRegexSource}\\.\\d{${args.precision}}`; + } + else if (args.precision == null) { + secondsRegexSource = `${secondsRegexSource}(\\.\\d+)?`; + } + const secondsQuantifier = args.precision ? "+" : "?"; // require seconds if precision is nonzero + return `([01]\\d|2[0-3]):[0-5]\\d(:${secondsRegexSource})${secondsQuantifier}`; +} +function timeRegex(args) { + return new RegExp(`^${timeRegexSource(args)}$`); +} +// Adapted from https://stackoverflow.com/a/3143231 +export function datetimeRegex(args) { + let regex = `${dateRegexSource}T${timeRegexSource(args)}`; + const opts = []; + opts.push(args.local ? `Z?` : `Z`); + if (args.offset) + opts.push(`([+-]\\d{2}:?\\d{2})`); + regex = `${regex}(${opts.join("|")})`; + return new RegExp(`^${regex}$`); +} +function isValidIP(ip, version) { + if ((version === "v4" || !version) && ipv4Regex.test(ip)) { + return true; + } + if ((version === "v6" || !version) && ipv6Regex.test(ip)) { + return true; + } + return false; +} +function isValidJWT(jwt, alg) { + if (!jwtRegex.test(jwt)) + return false; + try { + const [header] = jwt.split("."); + // Convert base64url to base64 + const base64 = header + .replace(/-/g, "+") + .replace(/_/g, "/") + .padEnd(header.length + ((4 - (header.length % 4)) % 4), "="); + const decoded = JSON.parse(atob(base64)); + if (typeof decoded !== "object" || decoded === null) + return false; + if ("typ" in decoded && decoded?.typ !== "JWT") + return false; + if (!decoded.alg) + return false; + if (alg && decoded.alg !== alg) + return false; + return true; + } + catch { + return false; + } +} +function isValidCidr(ip, version) { + if ((version === "v4" || !version) && ipv4CidrRegex.test(ip)) { + return true; + } + if ((version === "v6" || !version) && ipv6CidrRegex.test(ip)) { + return true; + } + return false; +} +export class ZodString extends ZodType { + _parse(input) { + if (this._def.coerce) { + input.data = String(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.string) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.string, + received: ctx.parsedType, + }); + return INVALID; + } + const status = new ParseStatus(); + let ctx = undefined; + for (const check of this._def.checks) { + if (check.kind === "min") { + if (input.data.length < check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "string", + inclusive: true, + exact: false, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "max") { + if (input.data.length > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "string", + inclusive: true, + exact: false, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "length") { + const tooBig = input.data.length > check.value; + const tooSmall = input.data.length < check.value; + if (tooBig || tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + if (tooBig) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "string", + inclusive: true, + exact: true, + message: check.message, + }); + } + else if (tooSmall) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "string", + inclusive: true, + exact: true, + message: check.message, + }); + } + status.dirty(); + } + } + else if (check.kind === "email") { + if (!emailRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "email", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "emoji") { + if (!emojiRegex) { + emojiRegex = new RegExp(_emojiRegex, "u"); + } + if (!emojiRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "emoji", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "uuid") { + if (!uuidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "uuid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "nanoid") { + if (!nanoidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "nanoid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "cuid") { + if (!cuidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "cuid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "cuid2") { + if (!cuid2Regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "cuid2", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "ulid") { + if (!ulidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "ulid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "url") { + try { + new URL(input.data); + } + catch { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "url", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "regex") { + check.regex.lastIndex = 0; + const testResult = check.regex.test(input.data); + if (!testResult) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "regex", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "trim") { + input.data = input.data.trim(); + } + else if (check.kind === "includes") { + if (!input.data.includes(check.value, check.position)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { includes: check.value, position: check.position }, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "toLowerCase") { + input.data = input.data.toLowerCase(); + } + else if (check.kind === "toUpperCase") { + input.data = input.data.toUpperCase(); + } + else if (check.kind === "startsWith") { + if (!input.data.startsWith(check.value)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { startsWith: check.value }, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "endsWith") { + if (!input.data.endsWith(check.value)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { endsWith: check.value }, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "datetime") { + const regex = datetimeRegex(check); + if (!regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: "datetime", + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "date") { + const regex = dateRegex; + if (!regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: "date", + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "time") { + const regex = timeRegex(check); + if (!regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: "time", + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "duration") { + if (!durationRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "duration", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "ip") { + if (!isValidIP(input.data, check.version)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "ip", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "jwt") { + if (!isValidJWT(input.data, check.alg)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "jwt", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "cidr") { + if (!isValidCidr(input.data, check.version)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "cidr", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "base64") { + if (!base64Regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "base64", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "base64url") { + if (!base64urlRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "base64url", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else { + util.assertNever(check); + } + } + return { status: status.value, value: input.data }; + } + _regex(regex, validation, message) { + return this.refinement((data) => regex.test(data), { + validation, + code: ZodIssueCode.invalid_string, + ...errorUtil.errToObj(message), + }); + } + _addCheck(check) { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + email(message) { + return this._addCheck({ kind: "email", ...errorUtil.errToObj(message) }); + } + url(message) { + return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) }); + } + emoji(message) { + return this._addCheck({ kind: "emoji", ...errorUtil.errToObj(message) }); + } + uuid(message) { + return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) }); + } + nanoid(message) { + return this._addCheck({ kind: "nanoid", ...errorUtil.errToObj(message) }); + } + cuid(message) { + return this._addCheck({ kind: "cuid", ...errorUtil.errToObj(message) }); + } + cuid2(message) { + return this._addCheck({ kind: "cuid2", ...errorUtil.errToObj(message) }); + } + ulid(message) { + return this._addCheck({ kind: "ulid", ...errorUtil.errToObj(message) }); + } + base64(message) { + return this._addCheck({ kind: "base64", ...errorUtil.errToObj(message) }); + } + base64url(message) { + // base64url encoding is a modification of base64 that can safely be used in URLs and filenames + return this._addCheck({ + kind: "base64url", + ...errorUtil.errToObj(message), + }); + } + jwt(options) { + return this._addCheck({ kind: "jwt", ...errorUtil.errToObj(options) }); + } + ip(options) { + return this._addCheck({ kind: "ip", ...errorUtil.errToObj(options) }); + } + cidr(options) { + return this._addCheck({ kind: "cidr", ...errorUtil.errToObj(options) }); + } + datetime(options) { + if (typeof options === "string") { + return this._addCheck({ + kind: "datetime", + precision: null, + offset: false, + local: false, + message: options, + }); + } + return this._addCheck({ + kind: "datetime", + precision: typeof options?.precision === "undefined" ? null : options?.precision, + offset: options?.offset ?? false, + local: options?.local ?? false, + ...errorUtil.errToObj(options?.message), + }); + } + date(message) { + return this._addCheck({ kind: "date", message }); + } + time(options) { + if (typeof options === "string") { + return this._addCheck({ + kind: "time", + precision: null, + message: options, + }); + } + return this._addCheck({ + kind: "time", + precision: typeof options?.precision === "undefined" ? null : options?.precision, + ...errorUtil.errToObj(options?.message), + }); + } + duration(message) { + return this._addCheck({ kind: "duration", ...errorUtil.errToObj(message) }); + } + regex(regex, message) { + return this._addCheck({ + kind: "regex", + regex: regex, + ...errorUtil.errToObj(message), + }); + } + includes(value, options) { + return this._addCheck({ + kind: "includes", + value: value, + position: options?.position, + ...errorUtil.errToObj(options?.message), + }); + } + startsWith(value, message) { + return this._addCheck({ + kind: "startsWith", + value: value, + ...errorUtil.errToObj(message), + }); + } + endsWith(value, message) { + return this._addCheck({ + kind: "endsWith", + value: value, + ...errorUtil.errToObj(message), + }); + } + min(minLength, message) { + return this._addCheck({ + kind: "min", + value: minLength, + ...errorUtil.errToObj(message), + }); + } + max(maxLength, message) { + return this._addCheck({ + kind: "max", + value: maxLength, + ...errorUtil.errToObj(message), + }); + } + length(len, message) { + return this._addCheck({ + kind: "length", + value: len, + ...errorUtil.errToObj(message), + }); + } + /** + * Equivalent to `.min(1)` + */ + nonempty(message) { + return this.min(1, errorUtil.errToObj(message)); + } + trim() { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "trim" }], + }); + } + toLowerCase() { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "toLowerCase" }], + }); + } + toUpperCase() { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "toUpperCase" }], + }); + } + get isDatetime() { + return !!this._def.checks.find((ch) => ch.kind === "datetime"); + } + get isDate() { + return !!this._def.checks.find((ch) => ch.kind === "date"); + } + get isTime() { + return !!this._def.checks.find((ch) => ch.kind === "time"); + } + get isDuration() { + return !!this._def.checks.find((ch) => ch.kind === "duration"); + } + get isEmail() { + return !!this._def.checks.find((ch) => ch.kind === "email"); + } + get isURL() { + return !!this._def.checks.find((ch) => ch.kind === "url"); + } + get isEmoji() { + return !!this._def.checks.find((ch) => ch.kind === "emoji"); + } + get isUUID() { + return !!this._def.checks.find((ch) => ch.kind === "uuid"); + } + get isNANOID() { + return !!this._def.checks.find((ch) => ch.kind === "nanoid"); + } + get isCUID() { + return !!this._def.checks.find((ch) => ch.kind === "cuid"); + } + get isCUID2() { + return !!this._def.checks.find((ch) => ch.kind === "cuid2"); + } + get isULID() { + return !!this._def.checks.find((ch) => ch.kind === "ulid"); + } + get isIP() { + return !!this._def.checks.find((ch) => ch.kind === "ip"); + } + get isCIDR() { + return !!this._def.checks.find((ch) => ch.kind === "cidr"); + } + get isBase64() { + return !!this._def.checks.find((ch) => ch.kind === "base64"); + } + get isBase64url() { + // base64url encoding is a modification of base64 that can safely be used in URLs and filenames + return !!this._def.checks.find((ch) => ch.kind === "base64url"); + } + get minLength() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min; + } + get maxLength() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max; + } +} +ZodString.create = (params) => { + return new ZodString({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodString, + coerce: params?.coerce ?? false, + ...processCreateParams(params), + }); +}; +// https://stackoverflow.com/questions/3966484/why-does-modulus-operator-return-fractional-number-in-javascript/31711034#31711034 +function floatSafeRemainder(val, step) { + const valDecCount = (val.toString().split(".")[1] || "").length; + const stepDecCount = (step.toString().split(".")[1] || "").length; + const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount; + const valInt = Number.parseInt(val.toFixed(decCount).replace(".", "")); + const stepInt = Number.parseInt(step.toFixed(decCount).replace(".", "")); + return (valInt % stepInt) / 10 ** decCount; +} +export class ZodNumber extends ZodType { + constructor() { + super(...arguments); + this.min = this.gte; + this.max = this.lte; + this.step = this.multipleOf; + } + _parse(input) { + if (this._def.coerce) { + input.data = Number(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.number) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.number, + received: ctx.parsedType, + }); + return INVALID; + } + let ctx = undefined; + const status = new ParseStatus(); + for (const check of this._def.checks) { + if (check.kind === "int") { + if (!util.isInteger(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: "integer", + received: "float", + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "min") { + const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value; + if (tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "number", + inclusive: check.inclusive, + exact: false, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "max") { + const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value; + if (tooBig) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "number", + inclusive: check.inclusive, + exact: false, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "multipleOf") { + if (floatSafeRemainder(input.data, check.value) !== 0) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_multiple_of, + multipleOf: check.value, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "finite") { + if (!Number.isFinite(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_finite, + message: check.message, + }); + status.dirty(); + } + } + else { + util.assertNever(check); + } + } + return { status: status.value, value: input.data }; + } + gte(value, message) { + return this.setLimit("min", value, true, errorUtil.toString(message)); + } + gt(value, message) { + return this.setLimit("min", value, false, errorUtil.toString(message)); + } + lte(value, message) { + return this.setLimit("max", value, true, errorUtil.toString(message)); + } + lt(value, message) { + return this.setLimit("max", value, false, errorUtil.toString(message)); + } + setLimit(kind, value, inclusive, message) { + return new ZodNumber({ + ...this._def, + checks: [ + ...this._def.checks, + { + kind, + value, + inclusive, + message: errorUtil.toString(message), + }, + ], + }); + } + _addCheck(check) { + return new ZodNumber({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + int(message) { + return this._addCheck({ + kind: "int", + message: errorUtil.toString(message), + }); + } + positive(message) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: false, + message: errorUtil.toString(message), + }); + } + negative(message) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: false, + message: errorUtil.toString(message), + }); + } + nonpositive(message) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: true, + message: errorUtil.toString(message), + }); + } + nonnegative(message) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: true, + message: errorUtil.toString(message), + }); + } + multipleOf(value, message) { + return this._addCheck({ + kind: "multipleOf", + value: value, + message: errorUtil.toString(message), + }); + } + finite(message) { + return this._addCheck({ + kind: "finite", + message: errorUtil.toString(message), + }); + } + safe(message) { + return this._addCheck({ + kind: "min", + inclusive: true, + value: Number.MIN_SAFE_INTEGER, + message: errorUtil.toString(message), + })._addCheck({ + kind: "max", + inclusive: true, + value: Number.MAX_SAFE_INTEGER, + message: errorUtil.toString(message), + }); + } + get minValue() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min; + } + get maxValue() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max; + } + get isInt() { + return !!this._def.checks.find((ch) => ch.kind === "int" || (ch.kind === "multipleOf" && util.isInteger(ch.value))); + } + get isFinite() { + let max = null; + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "finite" || ch.kind === "int" || ch.kind === "multipleOf") { + return true; + } + else if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + else if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return Number.isFinite(min) && Number.isFinite(max); + } +} +ZodNumber.create = (params) => { + return new ZodNumber({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodNumber, + coerce: params?.coerce || false, + ...processCreateParams(params), + }); +}; +export class ZodBigInt extends ZodType { + constructor() { + super(...arguments); + this.min = this.gte; + this.max = this.lte; + } + _parse(input) { + if (this._def.coerce) { + try { + input.data = BigInt(input.data); + } + catch { + return this._getInvalidInput(input); + } + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.bigint) { + return this._getInvalidInput(input); + } + let ctx = undefined; + const status = new ParseStatus(); + for (const check of this._def.checks) { + if (check.kind === "min") { + const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value; + if (tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + type: "bigint", + minimum: check.value, + inclusive: check.inclusive, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "max") { + const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value; + if (tooBig) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + type: "bigint", + maximum: check.value, + inclusive: check.inclusive, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "multipleOf") { + if (input.data % check.value !== BigInt(0)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_multiple_of, + multipleOf: check.value, + message: check.message, + }); + status.dirty(); + } + } + else { + util.assertNever(check); + } + } + return { status: status.value, value: input.data }; + } + _getInvalidInput(input) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.bigint, + received: ctx.parsedType, + }); + return INVALID; + } + gte(value, message) { + return this.setLimit("min", value, true, errorUtil.toString(message)); + } + gt(value, message) { + return this.setLimit("min", value, false, errorUtil.toString(message)); + } + lte(value, message) { + return this.setLimit("max", value, true, errorUtil.toString(message)); + } + lt(value, message) { + return this.setLimit("max", value, false, errorUtil.toString(message)); + } + setLimit(kind, value, inclusive, message) { + return new ZodBigInt({ + ...this._def, + checks: [ + ...this._def.checks, + { + kind, + value, + inclusive, + message: errorUtil.toString(message), + }, + ], + }); + } + _addCheck(check) { + return new ZodBigInt({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + positive(message) { + return this._addCheck({ + kind: "min", + value: BigInt(0), + inclusive: false, + message: errorUtil.toString(message), + }); + } + negative(message) { + return this._addCheck({ + kind: "max", + value: BigInt(0), + inclusive: false, + message: errorUtil.toString(message), + }); + } + nonpositive(message) { + return this._addCheck({ + kind: "max", + value: BigInt(0), + inclusive: true, + message: errorUtil.toString(message), + }); + } + nonnegative(message) { + return this._addCheck({ + kind: "min", + value: BigInt(0), + inclusive: true, + message: errorUtil.toString(message), + }); + } + multipleOf(value, message) { + return this._addCheck({ + kind: "multipleOf", + value, + message: errorUtil.toString(message), + }); + } + get minValue() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min; + } + get maxValue() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max; + } +} +ZodBigInt.create = (params) => { + return new ZodBigInt({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodBigInt, + coerce: params?.coerce ?? false, + ...processCreateParams(params), + }); +}; +export class ZodBoolean extends ZodType { + _parse(input) { + if (this._def.coerce) { + input.data = Boolean(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.boolean) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.boolean, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } +} +ZodBoolean.create = (params) => { + return new ZodBoolean({ + typeName: ZodFirstPartyTypeKind.ZodBoolean, + coerce: params?.coerce || false, + ...processCreateParams(params), + }); +}; +export class ZodDate extends ZodType { + _parse(input) { + if (this._def.coerce) { + input.data = new Date(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.date) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.date, + received: ctx.parsedType, + }); + return INVALID; + } + if (Number.isNaN(input.data.getTime())) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_date, + }); + return INVALID; + } + const status = new ParseStatus(); + let ctx = undefined; + for (const check of this._def.checks) { + if (check.kind === "min") { + if (input.data.getTime() < check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + message: check.message, + inclusive: true, + exact: false, + minimum: check.value, + type: "date", + }); + status.dirty(); + } + } + else if (check.kind === "max") { + if (input.data.getTime() > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + message: check.message, + inclusive: true, + exact: false, + maximum: check.value, + type: "date", + }); + status.dirty(); + } + } + else { + util.assertNever(check); + } + } + return { + status: status.value, + value: new Date(input.data.getTime()), + }; + } + _addCheck(check) { + return new ZodDate({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + min(minDate, message) { + return this._addCheck({ + kind: "min", + value: minDate.getTime(), + message: errorUtil.toString(message), + }); + } + max(maxDate, message) { + return this._addCheck({ + kind: "max", + value: maxDate.getTime(), + message: errorUtil.toString(message), + }); + } + get minDate() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min != null ? new Date(min) : null; + } + get maxDate() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max != null ? new Date(max) : null; + } +} +ZodDate.create = (params) => { + return new ZodDate({ + checks: [], + coerce: params?.coerce || false, + typeName: ZodFirstPartyTypeKind.ZodDate, + ...processCreateParams(params), + }); +}; +export class ZodSymbol extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.symbol) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.symbol, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } +} +ZodSymbol.create = (params) => { + return new ZodSymbol({ + typeName: ZodFirstPartyTypeKind.ZodSymbol, + ...processCreateParams(params), + }); +}; +export class ZodUndefined extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.undefined) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.undefined, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } +} +ZodUndefined.create = (params) => { + return new ZodUndefined({ + typeName: ZodFirstPartyTypeKind.ZodUndefined, + ...processCreateParams(params), + }); +}; +export class ZodNull extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.null) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.null, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } +} +ZodNull.create = (params) => { + return new ZodNull({ + typeName: ZodFirstPartyTypeKind.ZodNull, + ...processCreateParams(params), + }); +}; +export class ZodAny extends ZodType { + constructor() { + super(...arguments); + // to prevent instances of other classes from extending ZodAny. this causes issues with catchall in ZodObject. + this._any = true; + } + _parse(input) { + return OK(input.data); + } +} +ZodAny.create = (params) => { + return new ZodAny({ + typeName: ZodFirstPartyTypeKind.ZodAny, + ...processCreateParams(params), + }); +}; +export class ZodUnknown extends ZodType { + constructor() { + super(...arguments); + // required + this._unknown = true; + } + _parse(input) { + return OK(input.data); + } +} +ZodUnknown.create = (params) => { + return new ZodUnknown({ + typeName: ZodFirstPartyTypeKind.ZodUnknown, + ...processCreateParams(params), + }); +}; +export class ZodNever extends ZodType { + _parse(input) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.never, + received: ctx.parsedType, + }); + return INVALID; + } +} +ZodNever.create = (params) => { + return new ZodNever({ + typeName: ZodFirstPartyTypeKind.ZodNever, + ...processCreateParams(params), + }); +}; +export class ZodVoid extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.undefined) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.void, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } +} +ZodVoid.create = (params) => { + return new ZodVoid({ + typeName: ZodFirstPartyTypeKind.ZodVoid, + ...processCreateParams(params), + }); +}; +export class ZodArray extends ZodType { + _parse(input) { + const { ctx, status } = this._processInputParams(input); + const def = this._def; + if (ctx.parsedType !== ZodParsedType.array) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.array, + received: ctx.parsedType, + }); + return INVALID; + } + if (def.exactLength !== null) { + const tooBig = ctx.data.length > def.exactLength.value; + const tooSmall = ctx.data.length < def.exactLength.value; + if (tooBig || tooSmall) { + addIssueToContext(ctx, { + code: tooBig ? ZodIssueCode.too_big : ZodIssueCode.too_small, + minimum: (tooSmall ? def.exactLength.value : undefined), + maximum: (tooBig ? def.exactLength.value : undefined), + type: "array", + inclusive: true, + exact: true, + message: def.exactLength.message, + }); + status.dirty(); + } + } + if (def.minLength !== null) { + if (ctx.data.length < def.minLength.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: def.minLength.value, + type: "array", + inclusive: true, + exact: false, + message: def.minLength.message, + }); + status.dirty(); + } + } + if (def.maxLength !== null) { + if (ctx.data.length > def.maxLength.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: def.maxLength.value, + type: "array", + inclusive: true, + exact: false, + message: def.maxLength.message, + }); + status.dirty(); + } + } + if (ctx.common.async) { + return Promise.all([...ctx.data].map((item, i) => { + return def.type._parseAsync(new ParseInputLazyPath(ctx, item, ctx.path, i)); + })).then((result) => { + return ParseStatus.mergeArray(status, result); + }); + } + const result = [...ctx.data].map((item, i) => { + return def.type._parseSync(new ParseInputLazyPath(ctx, item, ctx.path, i)); + }); + return ParseStatus.mergeArray(status, result); + } + get element() { + return this._def.type; + } + min(minLength, message) { + return new ZodArray({ + ...this._def, + minLength: { value: minLength, message: errorUtil.toString(message) }, + }); + } + max(maxLength, message) { + return new ZodArray({ + ...this._def, + maxLength: { value: maxLength, message: errorUtil.toString(message) }, + }); + } + length(len, message) { + return new ZodArray({ + ...this._def, + exactLength: { value: len, message: errorUtil.toString(message) }, + }); + } + nonempty(message) { + return this.min(1, message); + } +} +ZodArray.create = (schema, params) => { + return new ZodArray({ + type: schema, + minLength: null, + maxLength: null, + exactLength: null, + typeName: ZodFirstPartyTypeKind.ZodArray, + ...processCreateParams(params), + }); +}; +function deepPartialify(schema) { + if (schema instanceof ZodObject) { + const newShape = {}; + for (const key in schema.shape) { + const fieldSchema = schema.shape[key]; + newShape[key] = ZodOptional.create(deepPartialify(fieldSchema)); + } + return new ZodObject({ + ...schema._def, + shape: () => newShape, + }); + } + else if (schema instanceof ZodArray) { + return new ZodArray({ + ...schema._def, + type: deepPartialify(schema.element), + }); + } + else if (schema instanceof ZodOptional) { + return ZodOptional.create(deepPartialify(schema.unwrap())); + } + else if (schema instanceof ZodNullable) { + return ZodNullable.create(deepPartialify(schema.unwrap())); + } + else if (schema instanceof ZodTuple) { + return ZodTuple.create(schema.items.map((item) => deepPartialify(item))); + } + else { + return schema; + } +} +export class ZodObject extends ZodType { + constructor() { + super(...arguments); + this._cached = null; + /** + * @deprecated In most cases, this is no longer needed - unknown properties are now silently stripped. + * If you want to pass through unknown properties, use `.passthrough()` instead. + */ + this.nonstrict = this.passthrough; + // extend< + // Augmentation extends ZodRawShape, + // NewOutput extends util.flatten<{ + // [k in keyof Augmentation | keyof Output]: k extends keyof Augmentation + // ? Augmentation[k]["_output"] + // : k extends keyof Output + // ? Output[k] + // : never; + // }>, + // NewInput extends util.flatten<{ + // [k in keyof Augmentation | keyof Input]: k extends keyof Augmentation + // ? Augmentation[k]["_input"] + // : k extends keyof Input + // ? Input[k] + // : never; + // }> + // >( + // augmentation: Augmentation + // ): ZodObject< + // extendShape, + // UnknownKeys, + // Catchall, + // NewOutput, + // NewInput + // > { + // return new ZodObject({ + // ...this._def, + // shape: () => ({ + // ...this._def.shape(), + // ...augmentation, + // }), + // }) as any; + // } + /** + * @deprecated Use `.extend` instead + * */ + this.augment = this.extend; + } + _getCached() { + if (this._cached !== null) + return this._cached; + const shape = this._def.shape(); + const keys = util.objectKeys(shape); + this._cached = { shape, keys }; + return this._cached; + } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.object) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType, + }); + return INVALID; + } + const { status, ctx } = this._processInputParams(input); + const { shape, keys: shapeKeys } = this._getCached(); + const extraKeys = []; + if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === "strip")) { + for (const key in ctx.data) { + if (!shapeKeys.includes(key)) { + extraKeys.push(key); + } + } + } + const pairs = []; + for (const key of shapeKeys) { + const keyValidator = shape[key]; + const value = ctx.data[key]; + pairs.push({ + key: { status: "valid", value: key }, + value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path, key)), + alwaysSet: key in ctx.data, + }); + } + if (this._def.catchall instanceof ZodNever) { + const unknownKeys = this._def.unknownKeys; + if (unknownKeys === "passthrough") { + for (const key of extraKeys) { + pairs.push({ + key: { status: "valid", value: key }, + value: { status: "valid", value: ctx.data[key] }, + }); + } + } + else if (unknownKeys === "strict") { + if (extraKeys.length > 0) { + addIssueToContext(ctx, { + code: ZodIssueCode.unrecognized_keys, + keys: extraKeys, + }); + status.dirty(); + } + } + else if (unknownKeys === "strip") { + } + else { + throw new Error(`Internal ZodObject error: invalid unknownKeys value.`); + } + } + else { + // run catchall validation + const catchall = this._def.catchall; + for (const key of extraKeys) { + const value = ctx.data[key]; + pairs.push({ + key: { status: "valid", value: key }, + value: catchall._parse(new ParseInputLazyPath(ctx, value, ctx.path, key) //, ctx.child(key), value, getParsedType(value) + ), + alwaysSet: key in ctx.data, + }); + } + } + if (ctx.common.async) { + return Promise.resolve() + .then(async () => { + const syncPairs = []; + for (const pair of pairs) { + const key = await pair.key; + const value = await pair.value; + syncPairs.push({ + key, + value, + alwaysSet: pair.alwaysSet, + }); + } + return syncPairs; + }) + .then((syncPairs) => { + return ParseStatus.mergeObjectSync(status, syncPairs); + }); + } + else { + return ParseStatus.mergeObjectSync(status, pairs); + } + } + get shape() { + return this._def.shape(); + } + strict(message) { + errorUtil.errToObj; + return new ZodObject({ + ...this._def, + unknownKeys: "strict", + ...(message !== undefined + ? { + errorMap: (issue, ctx) => { + const defaultError = this._def.errorMap?.(issue, ctx).message ?? ctx.defaultError; + if (issue.code === "unrecognized_keys") + return { + message: errorUtil.errToObj(message).message ?? defaultError, + }; + return { + message: defaultError, + }; + }, + } + : {}), + }); + } + strip() { + return new ZodObject({ + ...this._def, + unknownKeys: "strip", + }); + } + passthrough() { + return new ZodObject({ + ...this._def, + unknownKeys: "passthrough", + }); + } + // const AugmentFactory = + // (def: Def) => + // ( + // augmentation: Augmentation + // ): ZodObject< + // extendShape, Augmentation>, + // Def["unknownKeys"], + // Def["catchall"] + // > => { + // return new ZodObject({ + // ...def, + // shape: () => ({ + // ...def.shape(), + // ...augmentation, + // }), + // }) as any; + // }; + extend(augmentation) { + return new ZodObject({ + ...this._def, + shape: () => ({ + ...this._def.shape(), + ...augmentation, + }), + }); + } + /** + * Prior to zod@1.0.12 there was a bug in the + * inferred type of merged objects. Please + * upgrade if you are experiencing issues. + */ + merge(merging) { + const merged = new ZodObject({ + unknownKeys: merging._def.unknownKeys, + catchall: merging._def.catchall, + shape: () => ({ + ...this._def.shape(), + ...merging._def.shape(), + }), + typeName: ZodFirstPartyTypeKind.ZodObject, + }); + return merged; + } + // merge< + // Incoming extends AnyZodObject, + // Augmentation extends Incoming["shape"], + // NewOutput extends { + // [k in keyof Augmentation | keyof Output]: k extends keyof Augmentation + // ? Augmentation[k]["_output"] + // : k extends keyof Output + // ? Output[k] + // : never; + // }, + // NewInput extends { + // [k in keyof Augmentation | keyof Input]: k extends keyof Augmentation + // ? Augmentation[k]["_input"] + // : k extends keyof Input + // ? Input[k] + // : never; + // } + // >( + // merging: Incoming + // ): ZodObject< + // extendShape>, + // Incoming["_def"]["unknownKeys"], + // Incoming["_def"]["catchall"], + // NewOutput, + // NewInput + // > { + // const merged: any = new ZodObject({ + // unknownKeys: merging._def.unknownKeys, + // catchall: merging._def.catchall, + // shape: () => + // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), + // typeName: ZodFirstPartyTypeKind.ZodObject, + // }) as any; + // return merged; + // } + setKey(key, schema) { + return this.augment({ [key]: schema }); + } + // merge( + // merging: Incoming + // ): //ZodObject = (merging) => { + // ZodObject< + // extendShape>, + // Incoming["_def"]["unknownKeys"], + // Incoming["_def"]["catchall"] + // > { + // // const mergedShape = objectUtil.mergeShapes( + // // this._def.shape(), + // // merging._def.shape() + // // ); + // const merged: any = new ZodObject({ + // unknownKeys: merging._def.unknownKeys, + // catchall: merging._def.catchall, + // shape: () => + // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), + // typeName: ZodFirstPartyTypeKind.ZodObject, + // }) as any; + // return merged; + // } + catchall(index) { + return new ZodObject({ + ...this._def, + catchall: index, + }); + } + pick(mask) { + const shape = {}; + for (const key of util.objectKeys(mask)) { + if (mask[key] && this.shape[key]) { + shape[key] = this.shape[key]; + } + } + return new ZodObject({ + ...this._def, + shape: () => shape, + }); + } + omit(mask) { + const shape = {}; + for (const key of util.objectKeys(this.shape)) { + if (!mask[key]) { + shape[key] = this.shape[key]; + } + } + return new ZodObject({ + ...this._def, + shape: () => shape, + }); + } + /** + * @deprecated + */ + deepPartial() { + return deepPartialify(this); + } + partial(mask) { + const newShape = {}; + for (const key of util.objectKeys(this.shape)) { + const fieldSchema = this.shape[key]; + if (mask && !mask[key]) { + newShape[key] = fieldSchema; + } + else { + newShape[key] = fieldSchema.optional(); + } + } + return new ZodObject({ + ...this._def, + shape: () => newShape, + }); + } + required(mask) { + const newShape = {}; + for (const key of util.objectKeys(this.shape)) { + if (mask && !mask[key]) { + newShape[key] = this.shape[key]; + } + else { + const fieldSchema = this.shape[key]; + let newField = fieldSchema; + while (newField instanceof ZodOptional) { + newField = newField._def.innerType; + } + newShape[key] = newField; + } + } + return new ZodObject({ + ...this._def, + shape: () => newShape, + }); + } + keyof() { + return createZodEnum(util.objectKeys(this.shape)); + } +} +ZodObject.create = (shape, params) => { + return new ZodObject({ + shape: () => shape, + unknownKeys: "strip", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params), + }); +}; +ZodObject.strictCreate = (shape, params) => { + return new ZodObject({ + shape: () => shape, + unknownKeys: "strict", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params), + }); +}; +ZodObject.lazycreate = (shape, params) => { + return new ZodObject({ + shape, + unknownKeys: "strip", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params), + }); +}; +export class ZodUnion extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + const options = this._def.options; + function handleResults(results) { + // return first issue-free validation if it exists + for (const result of results) { + if (result.result.status === "valid") { + return result.result; + } + } + for (const result of results) { + if (result.result.status === "dirty") { + // add issues from dirty option + ctx.common.issues.push(...result.ctx.common.issues); + return result.result; + } + } + // return invalid + const unionErrors = results.map((result) => new ZodError(result.ctx.common.issues)); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union, + unionErrors, + }); + return INVALID; + } + if (ctx.common.async) { + return Promise.all(options.map(async (option) => { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [], + }, + parent: null, + }; + return { + result: await option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: childCtx, + }), + ctx: childCtx, + }; + })).then(handleResults); + } + else { + let dirty = undefined; + const issues = []; + for (const option of options) { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [], + }, + parent: null, + }; + const result = option._parseSync({ + data: ctx.data, + path: ctx.path, + parent: childCtx, + }); + if (result.status === "valid") { + return result; + } + else if (result.status === "dirty" && !dirty) { + dirty = { result, ctx: childCtx }; + } + if (childCtx.common.issues.length) { + issues.push(childCtx.common.issues); + } + } + if (dirty) { + ctx.common.issues.push(...dirty.ctx.common.issues); + return dirty.result; + } + const unionErrors = issues.map((issues) => new ZodError(issues)); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union, + unionErrors, + }); + return INVALID; + } + } + get options() { + return this._def.options; + } +} +ZodUnion.create = (types, params) => { + return new ZodUnion({ + options: types, + typeName: ZodFirstPartyTypeKind.ZodUnion, + ...processCreateParams(params), + }); +}; +///////////////////////////////////////////////////// +///////////////////////////////////////////////////// +////////// ////////// +////////// ZodDiscriminatedUnion ////////// +////////// ////////// +///////////////////////////////////////////////////// +///////////////////////////////////////////////////// +const getDiscriminator = (type) => { + if (type instanceof ZodLazy) { + return getDiscriminator(type.schema); + } + else if (type instanceof ZodEffects) { + return getDiscriminator(type.innerType()); + } + else if (type instanceof ZodLiteral) { + return [type.value]; + } + else if (type instanceof ZodEnum) { + return type.options; + } + else if (type instanceof ZodNativeEnum) { + // eslint-disable-next-line ban/ban + return util.objectValues(type.enum); + } + else if (type instanceof ZodDefault) { + return getDiscriminator(type._def.innerType); + } + else if (type instanceof ZodUndefined) { + return [undefined]; + } + else if (type instanceof ZodNull) { + return [null]; + } + else if (type instanceof ZodOptional) { + return [undefined, ...getDiscriminator(type.unwrap())]; + } + else if (type instanceof ZodNullable) { + return [null, ...getDiscriminator(type.unwrap())]; + } + else if (type instanceof ZodBranded) { + return getDiscriminator(type.unwrap()); + } + else if (type instanceof ZodReadonly) { + return getDiscriminator(type.unwrap()); + } + else if (type instanceof ZodCatch) { + return getDiscriminator(type._def.innerType); + } + else { + return []; + } +}; +export class ZodDiscriminatedUnion extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.object) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType, + }); + return INVALID; + } + const discriminator = this.discriminator; + const discriminatorValue = ctx.data[discriminator]; + const option = this.optionsMap.get(discriminatorValue); + if (!option) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union_discriminator, + options: Array.from(this.optionsMap.keys()), + path: [discriminator], + }); + return INVALID; + } + if (ctx.common.async) { + return option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + } + else { + return option._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + } + } + get discriminator() { + return this._def.discriminator; + } + get options() { + return this._def.options; + } + get optionsMap() { + return this._def.optionsMap; + } + /** + * The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor. + * However, it only allows a union of objects, all of which need to share a discriminator property. This property must + * have a different value for each object in the union. + * @param discriminator the name of the discriminator property + * @param types an array of object schemas + * @param params + */ + static create(discriminator, options, params) { + // Get all the valid discriminator values + const optionsMap = new Map(); + // try { + for (const type of options) { + const discriminatorValues = getDiscriminator(type.shape[discriminator]); + if (!discriminatorValues.length) { + throw new Error(`A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`); + } + for (const value of discriminatorValues) { + if (optionsMap.has(value)) { + throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`); + } + optionsMap.set(value, type); + } + } + return new ZodDiscriminatedUnion({ + typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion, + discriminator, + options, + optionsMap, + ...processCreateParams(params), + }); + } +} +function mergeValues(a, b) { + const aType = getParsedType(a); + const bType = getParsedType(b); + if (a === b) { + return { valid: true, data: a }; + } + else if (aType === ZodParsedType.object && bType === ZodParsedType.object) { + const bKeys = util.objectKeys(b); + const sharedKeys = util.objectKeys(a).filter((key) => bKeys.indexOf(key) !== -1); + const newObj = { ...a, ...b }; + for (const key of sharedKeys) { + const sharedValue = mergeValues(a[key], b[key]); + if (!sharedValue.valid) { + return { valid: false }; + } + newObj[key] = sharedValue.data; + } + return { valid: true, data: newObj }; + } + else if (aType === ZodParsedType.array && bType === ZodParsedType.array) { + if (a.length !== b.length) { + return { valid: false }; + } + const newArray = []; + for (let index = 0; index < a.length; index++) { + const itemA = a[index]; + const itemB = b[index]; + const sharedValue = mergeValues(itemA, itemB); + if (!sharedValue.valid) { + return { valid: false }; + } + newArray.push(sharedValue.data); + } + return { valid: true, data: newArray }; + } + else if (aType === ZodParsedType.date && bType === ZodParsedType.date && +a === +b) { + return { valid: true, data: a }; + } + else { + return { valid: false }; + } +} +export class ZodIntersection extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + const handleParsed = (parsedLeft, parsedRight) => { + if (isAborted(parsedLeft) || isAborted(parsedRight)) { + return INVALID; + } + const merged = mergeValues(parsedLeft.value, parsedRight.value); + if (!merged.valid) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_intersection_types, + }); + return INVALID; + } + if (isDirty(parsedLeft) || isDirty(parsedRight)) { + status.dirty(); + } + return { status: status.value, value: merged.data }; + }; + if (ctx.common.async) { + return Promise.all([ + this._def.left._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }), + this._def.right._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }), + ]).then(([left, right]) => handleParsed(left, right)); + } + else { + return handleParsed(this._def.left._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }), this._def.right._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + })); + } + } +} +ZodIntersection.create = (left, right, params) => { + return new ZodIntersection({ + left: left, + right: right, + typeName: ZodFirstPartyTypeKind.ZodIntersection, + ...processCreateParams(params), + }); +}; +// type ZodTupleItems = [ZodTypeAny, ...ZodTypeAny[]]; +export class ZodTuple extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.array) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.array, + received: ctx.parsedType, + }); + return INVALID; + } + if (ctx.data.length < this._def.items.length) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: this._def.items.length, + inclusive: true, + exact: false, + type: "array", + }); + return INVALID; + } + const rest = this._def.rest; + if (!rest && ctx.data.length > this._def.items.length) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: this._def.items.length, + inclusive: true, + exact: false, + type: "array", + }); + status.dirty(); + } + const items = [...ctx.data] + .map((item, itemIndex) => { + const schema = this._def.items[itemIndex] || this._def.rest; + if (!schema) + return null; + return schema._parse(new ParseInputLazyPath(ctx, item, ctx.path, itemIndex)); + }) + .filter((x) => !!x); // filter nulls + if (ctx.common.async) { + return Promise.all(items).then((results) => { + return ParseStatus.mergeArray(status, results); + }); + } + else { + return ParseStatus.mergeArray(status, items); + } + } + get items() { + return this._def.items; + } + rest(rest) { + return new ZodTuple({ + ...this._def, + rest, + }); + } +} +ZodTuple.create = (schemas, params) => { + if (!Array.isArray(schemas)) { + throw new Error("You must pass an array of schemas to z.tuple([ ... ])"); + } + return new ZodTuple({ + items: schemas, + typeName: ZodFirstPartyTypeKind.ZodTuple, + rest: null, + ...processCreateParams(params), + }); +}; +export class ZodRecord extends ZodType { + get keySchema() { + return this._def.keyType; + } + get valueSchema() { + return this._def.valueType; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.object) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType, + }); + return INVALID; + } + const pairs = []; + const keyType = this._def.keyType; + const valueType = this._def.valueType; + for (const key in ctx.data) { + pairs.push({ + key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)), + value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)), + alwaysSet: key in ctx.data, + }); + } + if (ctx.common.async) { + return ParseStatus.mergeObjectAsync(status, pairs); + } + else { + return ParseStatus.mergeObjectSync(status, pairs); + } + } + get element() { + return this._def.valueType; + } + static create(first, second, third) { + if (second instanceof ZodType) { + return new ZodRecord({ + keyType: first, + valueType: second, + typeName: ZodFirstPartyTypeKind.ZodRecord, + ...processCreateParams(third), + }); + } + return new ZodRecord({ + keyType: ZodString.create(), + valueType: first, + typeName: ZodFirstPartyTypeKind.ZodRecord, + ...processCreateParams(second), + }); + } +} +export class ZodMap extends ZodType { + get keySchema() { + return this._def.keyType; + } + get valueSchema() { + return this._def.valueType; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.map) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.map, + received: ctx.parsedType, + }); + return INVALID; + } + const keyType = this._def.keyType; + const valueType = this._def.valueType; + const pairs = [...ctx.data.entries()].map(([key, value], index) => { + return { + key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index, "key"])), + value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index, "value"])), + }; + }); + if (ctx.common.async) { + const finalMap = new Map(); + return Promise.resolve().then(async () => { + for (const pair of pairs) { + const key = await pair.key; + const value = await pair.value; + if (key.status === "aborted" || value.status === "aborted") { + return INVALID; + } + if (key.status === "dirty" || value.status === "dirty") { + status.dirty(); + } + finalMap.set(key.value, value.value); + } + return { status: status.value, value: finalMap }; + }); + } + else { + const finalMap = new Map(); + for (const pair of pairs) { + const key = pair.key; + const value = pair.value; + if (key.status === "aborted" || value.status === "aborted") { + return INVALID; + } + if (key.status === "dirty" || value.status === "dirty") { + status.dirty(); + } + finalMap.set(key.value, value.value); + } + return { status: status.value, value: finalMap }; + } + } +} +ZodMap.create = (keyType, valueType, params) => { + return new ZodMap({ + valueType, + keyType, + typeName: ZodFirstPartyTypeKind.ZodMap, + ...processCreateParams(params), + }); +}; +export class ZodSet extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.set) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.set, + received: ctx.parsedType, + }); + return INVALID; + } + const def = this._def; + if (def.minSize !== null) { + if (ctx.data.size < def.minSize.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: def.minSize.value, + type: "set", + inclusive: true, + exact: false, + message: def.minSize.message, + }); + status.dirty(); + } + } + if (def.maxSize !== null) { + if (ctx.data.size > def.maxSize.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: def.maxSize.value, + type: "set", + inclusive: true, + exact: false, + message: def.maxSize.message, + }); + status.dirty(); + } + } + const valueType = this._def.valueType; + function finalizeSet(elements) { + const parsedSet = new Set(); + for (const element of elements) { + if (element.status === "aborted") + return INVALID; + if (element.status === "dirty") + status.dirty(); + parsedSet.add(element.value); + } + return { status: status.value, value: parsedSet }; + } + const elements = [...ctx.data.values()].map((item, i) => valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i))); + if (ctx.common.async) { + return Promise.all(elements).then((elements) => finalizeSet(elements)); + } + else { + return finalizeSet(elements); + } + } + min(minSize, message) { + return new ZodSet({ + ...this._def, + minSize: { value: minSize, message: errorUtil.toString(message) }, + }); + } + max(maxSize, message) { + return new ZodSet({ + ...this._def, + maxSize: { value: maxSize, message: errorUtil.toString(message) }, + }); + } + size(size, message) { + return this.min(size, message).max(size, message); + } + nonempty(message) { + return this.min(1, message); + } +} +ZodSet.create = (valueType, params) => { + return new ZodSet({ + valueType, + minSize: null, + maxSize: null, + typeName: ZodFirstPartyTypeKind.ZodSet, + ...processCreateParams(params), + }); +}; +export class ZodFunction extends ZodType { + constructor() { + super(...arguments); + this.validate = this.implement; + } + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.function) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.function, + received: ctx.parsedType, + }); + return INVALID; + } + function makeArgsIssue(args, error) { + return makeIssue({ + data: args, + path: ctx.path, + errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), defaultErrorMap].filter((x) => !!x), + issueData: { + code: ZodIssueCode.invalid_arguments, + argumentsError: error, + }, + }); + } + function makeReturnsIssue(returns, error) { + return makeIssue({ + data: returns, + path: ctx.path, + errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), defaultErrorMap].filter((x) => !!x), + issueData: { + code: ZodIssueCode.invalid_return_type, + returnTypeError: error, + }, + }); + } + const params = { errorMap: ctx.common.contextualErrorMap }; + const fn = ctx.data; + if (this._def.returns instanceof ZodPromise) { + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(async function (...args) { + const error = new ZodError([]); + const parsedArgs = await me._def.args.parseAsync(args, params).catch((e) => { + error.addIssue(makeArgsIssue(args, e)); + throw error; + }); + const result = await Reflect.apply(fn, this, parsedArgs); + const parsedReturns = await me._def.returns._def.type + .parseAsync(result, params) + .catch((e) => { + error.addIssue(makeReturnsIssue(result, e)); + throw error; + }); + return parsedReturns; + }); + } + else { + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(function (...args) { + const parsedArgs = me._def.args.safeParse(args, params); + if (!parsedArgs.success) { + throw new ZodError([makeArgsIssue(args, parsedArgs.error)]); + } + const result = Reflect.apply(fn, this, parsedArgs.data); + const parsedReturns = me._def.returns.safeParse(result, params); + if (!parsedReturns.success) { + throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]); + } + return parsedReturns.data; + }); + } + } + parameters() { + return this._def.args; + } + returnType() { + return this._def.returns; + } + args(...items) { + return new ZodFunction({ + ...this._def, + args: ZodTuple.create(items).rest(ZodUnknown.create()), + }); + } + returns(returnType) { + return new ZodFunction({ + ...this._def, + returns: returnType, + }); + } + implement(func) { + const validatedFunc = this.parse(func); + return validatedFunc; + } + strictImplement(func) { + const validatedFunc = this.parse(func); + return validatedFunc; + } + static create(args, returns, params) { + return new ZodFunction({ + args: (args ? args : ZodTuple.create([]).rest(ZodUnknown.create())), + returns: returns || ZodUnknown.create(), + typeName: ZodFirstPartyTypeKind.ZodFunction, + ...processCreateParams(params), + }); + } +} +export class ZodLazy extends ZodType { + get schema() { + return this._def.getter(); + } + _parse(input) { + const { ctx } = this._processInputParams(input); + const lazySchema = this._def.getter(); + return lazySchema._parse({ data: ctx.data, path: ctx.path, parent: ctx }); + } +} +ZodLazy.create = (getter, params) => { + return new ZodLazy({ + getter: getter, + typeName: ZodFirstPartyTypeKind.ZodLazy, + ...processCreateParams(params), + }); +}; +export class ZodLiteral extends ZodType { + _parse(input) { + if (input.data !== this._def.value) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_literal, + expected: this._def.value, + }); + return INVALID; + } + return { status: "valid", value: input.data }; + } + get value() { + return this._def.value; + } +} +ZodLiteral.create = (value, params) => { + return new ZodLiteral({ + value: value, + typeName: ZodFirstPartyTypeKind.ZodLiteral, + ...processCreateParams(params), + }); +}; +function createZodEnum(values, params) { + return new ZodEnum({ + values, + typeName: ZodFirstPartyTypeKind.ZodEnum, + ...processCreateParams(params), + }); +} +export class ZodEnum extends ZodType { + _parse(input) { + if (typeof input.data !== "string") { + const ctx = this._getOrReturnCtx(input); + const expectedValues = this._def.values; + addIssueToContext(ctx, { + expected: util.joinValues(expectedValues), + received: ctx.parsedType, + code: ZodIssueCode.invalid_type, + }); + return INVALID; + } + if (!this._cache) { + this._cache = new Set(this._def.values); + } + if (!this._cache.has(input.data)) { + const ctx = this._getOrReturnCtx(input); + const expectedValues = this._def.values; + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_enum_value, + options: expectedValues, + }); + return INVALID; + } + return OK(input.data); + } + get options() { + return this._def.values; + } + get enum() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } + get Values() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } + get Enum() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } + extract(values, newDef = this._def) { + return ZodEnum.create(values, { + ...this._def, + ...newDef, + }); + } + exclude(values, newDef = this._def) { + return ZodEnum.create(this.options.filter((opt) => !values.includes(opt)), { + ...this._def, + ...newDef, + }); + } +} +ZodEnum.create = createZodEnum; +export class ZodNativeEnum extends ZodType { + _parse(input) { + const nativeEnumValues = util.getValidEnumValues(this._def.values); + const ctx = this._getOrReturnCtx(input); + if (ctx.parsedType !== ZodParsedType.string && ctx.parsedType !== ZodParsedType.number) { + const expectedValues = util.objectValues(nativeEnumValues); + addIssueToContext(ctx, { + expected: util.joinValues(expectedValues), + received: ctx.parsedType, + code: ZodIssueCode.invalid_type, + }); + return INVALID; + } + if (!this._cache) { + this._cache = new Set(util.getValidEnumValues(this._def.values)); + } + if (!this._cache.has(input.data)) { + const expectedValues = util.objectValues(nativeEnumValues); + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_enum_value, + options: expectedValues, + }); + return INVALID; + } + return OK(input.data); + } + get enum() { + return this._def.values; + } +} +ZodNativeEnum.create = (values, params) => { + return new ZodNativeEnum({ + values: values, + typeName: ZodFirstPartyTypeKind.ZodNativeEnum, + ...processCreateParams(params), + }); +}; +export class ZodPromise extends ZodType { + unwrap() { + return this._def.type; + } + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.promise && ctx.common.async === false) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.promise, + received: ctx.parsedType, + }); + return INVALID; + } + const promisified = ctx.parsedType === ZodParsedType.promise ? ctx.data : Promise.resolve(ctx.data); + return OK(promisified.then((data) => { + return this._def.type.parseAsync(data, { + path: ctx.path, + errorMap: ctx.common.contextualErrorMap, + }); + })); + } +} +ZodPromise.create = (schema, params) => { + return new ZodPromise({ + type: schema, + typeName: ZodFirstPartyTypeKind.ZodPromise, + ...processCreateParams(params), + }); +}; +export class ZodEffects extends ZodType { + innerType() { + return this._def.schema; + } + sourceType() { + return this._def.schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects + ? this._def.schema.sourceType() + : this._def.schema; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + const effect = this._def.effect || null; + const checkCtx = { + addIssue: (arg) => { + addIssueToContext(ctx, arg); + if (arg.fatal) { + status.abort(); + } + else { + status.dirty(); + } + }, + get path() { + return ctx.path; + }, + }; + checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx); + if (effect.type === "preprocess") { + const processed = effect.transform(ctx.data, checkCtx); + if (ctx.common.async) { + return Promise.resolve(processed).then(async (processed) => { + if (status.value === "aborted") + return INVALID; + const result = await this._def.schema._parseAsync({ + data: processed, + path: ctx.path, + parent: ctx, + }); + if (result.status === "aborted") + return INVALID; + if (result.status === "dirty") + return DIRTY(result.value); + if (status.value === "dirty") + return DIRTY(result.value); + return result; + }); + } + else { + if (status.value === "aborted") + return INVALID; + const result = this._def.schema._parseSync({ + data: processed, + path: ctx.path, + parent: ctx, + }); + if (result.status === "aborted") + return INVALID; + if (result.status === "dirty") + return DIRTY(result.value); + if (status.value === "dirty") + return DIRTY(result.value); + return result; + } + } + if (effect.type === "refinement") { + const executeRefinement = (acc) => { + const result = effect.refinement(acc, checkCtx); + if (ctx.common.async) { + return Promise.resolve(result); + } + if (result instanceof Promise) { + throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead."); + } + return acc; + }; + if (ctx.common.async === false) { + const inner = this._def.schema._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + if (inner.status === "aborted") + return INVALID; + if (inner.status === "dirty") + status.dirty(); + // return value is ignored + executeRefinement(inner.value); + return { status: status.value, value: inner.value }; + } + else { + return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((inner) => { + if (inner.status === "aborted") + return INVALID; + if (inner.status === "dirty") + status.dirty(); + return executeRefinement(inner.value).then(() => { + return { status: status.value, value: inner.value }; + }); + }); + } + } + if (effect.type === "transform") { + if (ctx.common.async === false) { + const base = this._def.schema._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + if (!isValid(base)) + return INVALID; + const result = effect.transform(base.value, checkCtx); + if (result instanceof Promise) { + throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`); + } + return { status: status.value, value: result }; + } + else { + return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => { + if (!isValid(base)) + return INVALID; + return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({ + status: status.value, + value: result, + })); + }); + } + } + util.assertNever(effect); + } +} +ZodEffects.create = (schema, effect, params) => { + return new ZodEffects({ + schema, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect, + ...processCreateParams(params), + }); +}; +ZodEffects.createWithPreprocess = (preprocess, schema, params) => { + return new ZodEffects({ + schema, + effect: { type: "preprocess", transform: preprocess }, + typeName: ZodFirstPartyTypeKind.ZodEffects, + ...processCreateParams(params), + }); +}; +export { ZodEffects as ZodTransformer }; +export class ZodOptional extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType === ZodParsedType.undefined) { + return OK(undefined); + } + return this._def.innerType._parse(input); + } + unwrap() { + return this._def.innerType; + } +} +ZodOptional.create = (type, params) => { + return new ZodOptional({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodOptional, + ...processCreateParams(params), + }); +}; +export class ZodNullable extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType === ZodParsedType.null) { + return OK(null); + } + return this._def.innerType._parse(input); + } + unwrap() { + return this._def.innerType; + } +} +ZodNullable.create = (type, params) => { + return new ZodNullable({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodNullable, + ...processCreateParams(params), + }); +}; +export class ZodDefault extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + let data = ctx.data; + if (ctx.parsedType === ZodParsedType.undefined) { + data = this._def.defaultValue(); + } + return this._def.innerType._parse({ + data, + path: ctx.path, + parent: ctx, + }); + } + removeDefault() { + return this._def.innerType; + } +} +ZodDefault.create = (type, params) => { + return new ZodDefault({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodDefault, + defaultValue: typeof params.default === "function" ? params.default : () => params.default, + ...processCreateParams(params), + }); +}; +export class ZodCatch extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + // newCtx is used to not collect issues from inner types in ctx + const newCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [], + }, + }; + const result = this._def.innerType._parse({ + data: newCtx.data, + path: newCtx.path, + parent: { + ...newCtx, + }, + }); + if (isAsync(result)) { + return result.then((result) => { + return { + status: "valid", + value: result.status === "valid" + ? result.value + : this._def.catchValue({ + get error() { + return new ZodError(newCtx.common.issues); + }, + input: newCtx.data, + }), + }; + }); + } + else { + return { + status: "valid", + value: result.status === "valid" + ? result.value + : this._def.catchValue({ + get error() { + return new ZodError(newCtx.common.issues); + }, + input: newCtx.data, + }), + }; + } + } + removeCatch() { + return this._def.innerType; + } +} +ZodCatch.create = (type, params) => { + return new ZodCatch({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodCatch, + catchValue: typeof params.catch === "function" ? params.catch : () => params.catch, + ...processCreateParams(params), + }); +}; +export class ZodNaN extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.nan) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.nan, + received: ctx.parsedType, + }); + return INVALID; + } + return { status: "valid", value: input.data }; + } +} +ZodNaN.create = (params) => { + return new ZodNaN({ + typeName: ZodFirstPartyTypeKind.ZodNaN, + ...processCreateParams(params), + }); +}; +export const BRAND = Symbol("zod_brand"); +export class ZodBranded extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + const data = ctx.data; + return this._def.type._parse({ + data, + path: ctx.path, + parent: ctx, + }); + } + unwrap() { + return this._def.type; + } +} +export class ZodPipeline extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.common.async) { + const handleAsync = async () => { + const inResult = await this._def.in._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + if (inResult.status === "aborted") + return INVALID; + if (inResult.status === "dirty") { + status.dirty(); + return DIRTY(inResult.value); + } + else { + return this._def.out._parseAsync({ + data: inResult.value, + path: ctx.path, + parent: ctx, + }); + } + }; + return handleAsync(); + } + else { + const inResult = this._def.in._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + if (inResult.status === "aborted") + return INVALID; + if (inResult.status === "dirty") { + status.dirty(); + return { + status: "dirty", + value: inResult.value, + }; + } + else { + return this._def.out._parseSync({ + data: inResult.value, + path: ctx.path, + parent: ctx, + }); + } + } + } + static create(a, b) { + return new ZodPipeline({ + in: a, + out: b, + typeName: ZodFirstPartyTypeKind.ZodPipeline, + }); + } +} +export class ZodReadonly extends ZodType { + _parse(input) { + const result = this._def.innerType._parse(input); + const freeze = (data) => { + if (isValid(data)) { + data.value = Object.freeze(data.value); + } + return data; + }; + return isAsync(result) ? result.then((data) => freeze(data)) : freeze(result); + } + unwrap() { + return this._def.innerType; + } +} +ZodReadonly.create = (type, params) => { + return new ZodReadonly({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodReadonly, + ...processCreateParams(params), + }); +}; +//////////////////////////////////////// +//////////////////////////////////////// +////////// ////////// +////////// z.custom ////////// +////////// ////////// +//////////////////////////////////////// +//////////////////////////////////////// +function cleanParams(params, data) { + const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params; + const p2 = typeof p === "string" ? { message: p } : p; + return p2; +} +export function custom(check, _params = {}, +/** + * @deprecated + * + * Pass `fatal` into the params object instead: + * + * ```ts + * z.string().custom((val) => val.length > 5, { fatal: false }) + * ``` + * + */ +fatal) { + if (check) + return ZodAny.create().superRefine((data, ctx) => { + const r = check(data); + if (r instanceof Promise) { + return r.then((r) => { + if (!r) { + const params = cleanParams(_params, data); + const _fatal = params.fatal ?? fatal ?? true; + ctx.addIssue({ code: "custom", ...params, fatal: _fatal }); + } + }); + } + if (!r) { + const params = cleanParams(_params, data); + const _fatal = params.fatal ?? fatal ?? true; + ctx.addIssue({ code: "custom", ...params, fatal: _fatal }); + } + return; + }); + return ZodAny.create(); +} +export { ZodType as Schema, ZodType as ZodSchema }; +export const late = { + object: ZodObject.lazycreate, +}; +export var ZodFirstPartyTypeKind; +(function (ZodFirstPartyTypeKind) { + ZodFirstPartyTypeKind["ZodString"] = "ZodString"; + ZodFirstPartyTypeKind["ZodNumber"] = "ZodNumber"; + ZodFirstPartyTypeKind["ZodNaN"] = "ZodNaN"; + ZodFirstPartyTypeKind["ZodBigInt"] = "ZodBigInt"; + ZodFirstPartyTypeKind["ZodBoolean"] = "ZodBoolean"; + ZodFirstPartyTypeKind["ZodDate"] = "ZodDate"; + ZodFirstPartyTypeKind["ZodSymbol"] = "ZodSymbol"; + ZodFirstPartyTypeKind["ZodUndefined"] = "ZodUndefined"; + ZodFirstPartyTypeKind["ZodNull"] = "ZodNull"; + ZodFirstPartyTypeKind["ZodAny"] = "ZodAny"; + ZodFirstPartyTypeKind["ZodUnknown"] = "ZodUnknown"; + ZodFirstPartyTypeKind["ZodNever"] = "ZodNever"; + ZodFirstPartyTypeKind["ZodVoid"] = "ZodVoid"; + ZodFirstPartyTypeKind["ZodArray"] = "ZodArray"; + ZodFirstPartyTypeKind["ZodObject"] = "ZodObject"; + ZodFirstPartyTypeKind["ZodUnion"] = "ZodUnion"; + ZodFirstPartyTypeKind["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion"; + ZodFirstPartyTypeKind["ZodIntersection"] = "ZodIntersection"; + ZodFirstPartyTypeKind["ZodTuple"] = "ZodTuple"; + ZodFirstPartyTypeKind["ZodRecord"] = "ZodRecord"; + ZodFirstPartyTypeKind["ZodMap"] = "ZodMap"; + ZodFirstPartyTypeKind["ZodSet"] = "ZodSet"; + ZodFirstPartyTypeKind["ZodFunction"] = "ZodFunction"; + ZodFirstPartyTypeKind["ZodLazy"] = "ZodLazy"; + ZodFirstPartyTypeKind["ZodLiteral"] = "ZodLiteral"; + ZodFirstPartyTypeKind["ZodEnum"] = "ZodEnum"; + ZodFirstPartyTypeKind["ZodEffects"] = "ZodEffects"; + ZodFirstPartyTypeKind["ZodNativeEnum"] = "ZodNativeEnum"; + ZodFirstPartyTypeKind["ZodOptional"] = "ZodOptional"; + ZodFirstPartyTypeKind["ZodNullable"] = "ZodNullable"; + ZodFirstPartyTypeKind["ZodDefault"] = "ZodDefault"; + ZodFirstPartyTypeKind["ZodCatch"] = "ZodCatch"; + ZodFirstPartyTypeKind["ZodPromise"] = "ZodPromise"; + ZodFirstPartyTypeKind["ZodBranded"] = "ZodBranded"; + ZodFirstPartyTypeKind["ZodPipeline"] = "ZodPipeline"; + ZodFirstPartyTypeKind["ZodReadonly"] = "ZodReadonly"; +})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {})); +// requires TS 4.4+ +class Class { + constructor(..._) { } +} +const instanceOfType = ( +// const instanceOfType = any>( +cls, params = { + message: `Input not instance of ${cls.name}`, +}) => custom((data) => data instanceof cls, params); +const stringType = ZodString.create; +const numberType = ZodNumber.create; +const nanType = ZodNaN.create; +const bigIntType = ZodBigInt.create; +const booleanType = ZodBoolean.create; +const dateType = ZodDate.create; +const symbolType = ZodSymbol.create; +const undefinedType = ZodUndefined.create; +const nullType = ZodNull.create; +const anyType = ZodAny.create; +const unknownType = ZodUnknown.create; +const neverType = ZodNever.create; +const voidType = ZodVoid.create; +const arrayType = ZodArray.create; +const objectType = ZodObject.create; +const strictObjectType = ZodObject.strictCreate; +const unionType = ZodUnion.create; +const discriminatedUnionType = ZodDiscriminatedUnion.create; +const intersectionType = ZodIntersection.create; +const tupleType = ZodTuple.create; +const recordType = ZodRecord.create; +const mapType = ZodMap.create; +const setType = ZodSet.create; +const functionType = ZodFunction.create; +const lazyType = ZodLazy.create; +const literalType = ZodLiteral.create; +const enumType = ZodEnum.create; +const nativeEnumType = ZodNativeEnum.create; +const promiseType = ZodPromise.create; +const effectsType = ZodEffects.create; +const optionalType = ZodOptional.create; +const nullableType = ZodNullable.create; +const preprocessType = ZodEffects.createWithPreprocess; +const pipelineType = ZodPipeline.create; +const ostring = () => stringType().optional(); +const onumber = () => numberType().optional(); +const oboolean = () => booleanType().optional(); +export const coerce = { + string: ((arg) => ZodString.create({ ...arg, coerce: true })), + number: ((arg) => ZodNumber.create({ ...arg, coerce: true })), + boolean: ((arg) => ZodBoolean.create({ + ...arg, + coerce: true, + })), + bigint: ((arg) => ZodBigInt.create({ ...arg, coerce: true })), + date: ((arg) => ZodDate.create({ ...arg, coerce: true })), +}; +export { anyType as any, arrayType as array, bigIntType as bigint, booleanType as boolean, dateType as date, discriminatedUnionType as discriminatedUnion, effectsType as effect, enumType as enum, functionType as function, instanceOfType as instanceof, intersectionType as intersection, lazyType as lazy, literalType as literal, mapType as map, nanType as nan, nativeEnumType as nativeEnum, neverType as never, nullType as null, nullableType as nullable, numberType as number, objectType as object, oboolean, onumber, optionalType as optional, ostring, pipelineType as pipeline, preprocessType as preprocess, promiseType as promise, recordType as record, setType as set, strictObjectType as strictObject, stringType as string, symbolType as symbol, effectsType as transformer, tupleType as tuple, undefinedType as undefined, unionType as union, unknownType as unknown, voidType as void, }; +export const NEVER = INVALID; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/index.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/index.d.ts new file mode 100644 index 000000000..8352ab713 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/index.d.ts @@ -0,0 +1,3 @@ +import z3 from "./v3/index.js"; +export * from "./v3/index.js"; +export default z3; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/ZodError.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/ZodError.d.ts new file mode 100644 index 000000000..4e71355a3 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/ZodError.d.ts @@ -0,0 +1,164 @@ +import type { Primitive } from "./helpers/typeAliases.js"; +import { util, type ZodParsedType } from "./helpers/util.js"; +import type { TypeOf, ZodType } from "./index.js"; +type allKeys = T extends any ? keyof T : never; +export type inferFlattenedErrors, U = string> = typeToFlattenedError, U>; +export type typeToFlattenedError = { + formErrors: U[]; + fieldErrors: { + [P in allKeys]?: U[]; + }; +}; +export declare const ZodIssueCode: { + invalid_type: "invalid_type"; + invalid_literal: "invalid_literal"; + custom: "custom"; + invalid_union: "invalid_union"; + invalid_union_discriminator: "invalid_union_discriminator"; + invalid_enum_value: "invalid_enum_value"; + unrecognized_keys: "unrecognized_keys"; + invalid_arguments: "invalid_arguments"; + invalid_return_type: "invalid_return_type"; + invalid_date: "invalid_date"; + invalid_string: "invalid_string"; + too_small: "too_small"; + too_big: "too_big"; + invalid_intersection_types: "invalid_intersection_types"; + not_multiple_of: "not_multiple_of"; + not_finite: "not_finite"; +}; +export type ZodIssueCode = keyof typeof ZodIssueCode; +export type ZodIssueBase = { + path: (string | number)[]; + message?: string | undefined; +}; +export interface ZodInvalidTypeIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_type; + expected: ZodParsedType; + received: ZodParsedType; +} +export interface ZodInvalidLiteralIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_literal; + expected: unknown; + received: unknown; +} +export interface ZodUnrecognizedKeysIssue extends ZodIssueBase { + code: typeof ZodIssueCode.unrecognized_keys; + keys: string[]; +} +export interface ZodInvalidUnionIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_union; + unionErrors: ZodError[]; +} +export interface ZodInvalidUnionDiscriminatorIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_union_discriminator; + options: Primitive[]; +} +export interface ZodInvalidEnumValueIssue extends ZodIssueBase { + received: string | number; + code: typeof ZodIssueCode.invalid_enum_value; + options: (string | number)[]; +} +export interface ZodInvalidArgumentsIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_arguments; + argumentsError: ZodError; +} +export interface ZodInvalidReturnTypeIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_return_type; + returnTypeError: ZodError; +} +export interface ZodInvalidDateIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_date; +} +export type StringValidation = "email" | "url" | "emoji" | "uuid" | "nanoid" | "regex" | "cuid" | "cuid2" | "ulid" | "datetime" | "date" | "time" | "duration" | "ip" | "cidr" | "base64" | "jwt" | "base64url" | { + includes: string; + position?: number | undefined; +} | { + startsWith: string; +} | { + endsWith: string; +}; +export interface ZodInvalidStringIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_string; + validation: StringValidation; +} +export interface ZodTooSmallIssue extends ZodIssueBase { + code: typeof ZodIssueCode.too_small; + minimum: number | bigint; + inclusive: boolean; + exact?: boolean; + type: "array" | "string" | "number" | "set" | "date" | "bigint"; +} +export interface ZodTooBigIssue extends ZodIssueBase { + code: typeof ZodIssueCode.too_big; + maximum: number | bigint; + inclusive: boolean; + exact?: boolean; + type: "array" | "string" | "number" | "set" | "date" | "bigint"; +} +export interface ZodInvalidIntersectionTypesIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_intersection_types; +} +export interface ZodNotMultipleOfIssue extends ZodIssueBase { + code: typeof ZodIssueCode.not_multiple_of; + multipleOf: number | bigint; +} +export interface ZodNotFiniteIssue extends ZodIssueBase { + code: typeof ZodIssueCode.not_finite; +} +export interface ZodCustomIssue extends ZodIssueBase { + code: typeof ZodIssueCode.custom; + params?: { + [k: string]: any; + }; +} +export type DenormalizedError = { + [k: string]: DenormalizedError | string[]; +}; +export type ZodIssueOptionalMessage = ZodInvalidTypeIssue | ZodInvalidLiteralIssue | ZodUnrecognizedKeysIssue | ZodInvalidUnionIssue | ZodInvalidUnionDiscriminatorIssue | ZodInvalidEnumValueIssue | ZodInvalidArgumentsIssue | ZodInvalidReturnTypeIssue | ZodInvalidDateIssue | ZodInvalidStringIssue | ZodTooSmallIssue | ZodTooBigIssue | ZodInvalidIntersectionTypesIssue | ZodNotMultipleOfIssue | ZodNotFiniteIssue | ZodCustomIssue; +export type ZodIssue = ZodIssueOptionalMessage & { + fatal?: boolean | undefined; + message: string; +}; +export declare const quotelessJson: (obj: any) => string; +type recursiveZodFormattedError = T extends [any, ...any[]] ? { + [K in keyof T]?: ZodFormattedError; +} : T extends any[] ? { + [k: number]: ZodFormattedError; +} : T extends object ? { + [K in keyof T]?: ZodFormattedError; +} : unknown; +export type ZodFormattedError = { + _errors: U[]; +} & recursiveZodFormattedError>; +export type inferFormattedError, U = string> = ZodFormattedError, U>; +export declare class ZodError extends Error { + issues: ZodIssue[]; + get errors(): ZodIssue[]; + constructor(issues: ZodIssue[]); + format(): ZodFormattedError; + format(mapper: (issue: ZodIssue) => U): ZodFormattedError; + static create: (issues: ZodIssue[]) => ZodError; + static assert(value: unknown): asserts value is ZodError; + toString(): string; + get message(): string; + get isEmpty(): boolean; + addIssue: (sub: ZodIssue) => void; + addIssues: (subs?: ZodIssue[]) => void; + flatten(): typeToFlattenedError; + flatten(mapper?: (issue: ZodIssue) => U): typeToFlattenedError; + get formErrors(): typeToFlattenedError; +} +type stripPath = T extends any ? util.OmitKeys : never; +export type IssueData = stripPath & { + path?: (string | number)[]; + fatal?: boolean | undefined; +}; +export type ErrorMapCtx = { + defaultError: string; + data: any; +}; +export type ZodErrorMap = (issue: ZodIssueOptionalMessage, _ctx: ErrorMapCtx) => { + message: string; +}; +export {}; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/errors.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/errors.d.ts new file mode 100644 index 000000000..52eacf72e --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/errors.d.ts @@ -0,0 +1,5 @@ +import type { ZodErrorMap } from "./ZodError.js"; +import defaultErrorMap from "./locales/en.js"; +export { defaultErrorMap }; +export declare function setErrorMap(map: ZodErrorMap): void; +export declare function getErrorMap(): ZodErrorMap; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/external.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/external.d.ts new file mode 100644 index 000000000..f0a4be4c7 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/external.d.ts @@ -0,0 +1,6 @@ +export * from "./errors.js"; +export * from "./helpers/parseUtil.js"; +export * from "./helpers/typeAliases.js"; +export * from "./helpers/util.js"; +export * from "./types.js"; +export * from "./ZodError.js"; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/enumUtil.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/enumUtil.d.ts new file mode 100644 index 000000000..fad28a711 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/enumUtil.d.ts @@ -0,0 +1,8 @@ +export declare namespace enumUtil { + type UnionToIntersectionFn = (T extends unknown ? (k: () => T) => void : never) extends (k: infer Intersection) => void ? Intersection : never; + type GetUnionLast = UnionToIntersectionFn extends () => infer Last ? Last : never; + type UnionToTuple = [T] extends [never] ? Tuple : UnionToTuple>, [GetUnionLast, ...Tuple]>; + type CastToStringTuple = T extends [string, ...string[]] ? T : never; + export type UnionToTupleString = CastToStringTuple>; + export {}; +} diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/errorUtil.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/errorUtil.d.ts new file mode 100644 index 000000000..bc9b033ff --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/errorUtil.d.ts @@ -0,0 +1,9 @@ +export declare namespace errorUtil { + type ErrMessage = string | { + message?: string | undefined; + }; + const errToObj: (message?: ErrMessage) => { + message?: string | undefined; + }; + const toString: (message?: ErrMessage) => string | undefined; +} diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/parseUtil.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/parseUtil.d.ts new file mode 100644 index 000000000..e4dc1d7b3 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/parseUtil.d.ts @@ -0,0 +1,78 @@ +import type { IssueData, ZodErrorMap, ZodIssue } from "../ZodError.js"; +import type { ZodParsedType } from "./util.js"; +export declare const makeIssue: (params: { + data: any; + path: (string | number)[]; + errorMaps: ZodErrorMap[]; + issueData: IssueData; +}) => ZodIssue; +export type ParseParams = { + path: (string | number)[]; + errorMap: ZodErrorMap; + async: boolean; +}; +export type ParsePathComponent = string | number; +export type ParsePath = ParsePathComponent[]; +export declare const EMPTY_PATH: ParsePath; +export interface ParseContext { + readonly common: { + readonly issues: ZodIssue[]; + readonly contextualErrorMap?: ZodErrorMap | undefined; + readonly async: boolean; + }; + readonly path: ParsePath; + readonly schemaErrorMap?: ZodErrorMap | undefined; + readonly parent: ParseContext | null; + readonly data: any; + readonly parsedType: ZodParsedType; +} +export type ParseInput = { + data: any; + path: (string | number)[]; + parent: ParseContext; +}; +export declare function addIssueToContext(ctx: ParseContext, issueData: IssueData): void; +export type ObjectPair = { + key: SyncParseReturnType; + value: SyncParseReturnType; +}; +export declare class ParseStatus { + value: "aborted" | "dirty" | "valid"; + dirty(): void; + abort(): void; + static mergeArray(status: ParseStatus, results: SyncParseReturnType[]): SyncParseReturnType; + static mergeObjectAsync(status: ParseStatus, pairs: { + key: ParseReturnType; + value: ParseReturnType; + }[]): Promise>; + static mergeObjectSync(status: ParseStatus, pairs: { + key: SyncParseReturnType; + value: SyncParseReturnType; + alwaysSet?: boolean; + }[]): SyncParseReturnType; +} +export interface ParseResult { + status: "aborted" | "dirty" | "valid"; + data: any; +} +export type INVALID = { + status: "aborted"; +}; +export declare const INVALID: INVALID; +export type DIRTY = { + status: "dirty"; + value: T; +}; +export declare const DIRTY: (value: T) => DIRTY; +export type OK = { + status: "valid"; + value: T; +}; +export declare const OK: (value: T) => OK; +export type SyncParseReturnType = OK | DIRTY | INVALID; +export type AsyncParseReturnType = Promise>; +export type ParseReturnType = SyncParseReturnType | AsyncParseReturnType; +export declare const isAborted: (x: ParseReturnType) => x is INVALID; +export declare const isDirty: (x: ParseReturnType) => x is OK | DIRTY; +export declare const isValid: (x: ParseReturnType) => x is OK; +export declare const isAsync: (x: ParseReturnType) => x is AsyncParseReturnType; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/partialUtil.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/partialUtil.d.ts new file mode 100644 index 000000000..d07657a25 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/partialUtil.d.ts @@ -0,0 +1,8 @@ +import type { ZodArray, ZodNullable, ZodObject, ZodOptional, ZodRawShape, ZodTuple, ZodTupleItems, ZodTypeAny } from "../types.js"; +export declare namespace partialUtil { + type DeepPartial = T extends ZodObject ? ZodObject<{ + [k in keyof T["shape"]]: ZodOptional>; + }, T["_def"]["unknownKeys"], T["_def"]["catchall"]> : T extends ZodArray ? ZodArray, Card> : T extends ZodOptional ? ZodOptional> : T extends ZodNullable ? ZodNullable> : T extends ZodTuple ? { + [k in keyof Items]: Items[k] extends ZodTypeAny ? DeepPartial : never; + } extends infer PI ? PI extends ZodTupleItems ? ZodTuple : never : never : T; +} diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/typeAliases.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/typeAliases.d.ts new file mode 100644 index 000000000..32df022ca --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/typeAliases.d.ts @@ -0,0 +1,2 @@ +export type Primitive = string | number | symbol | bigint | boolean | null | undefined; +export type Scalars = Primitive | Primitive[]; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/util.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/util.d.ts new file mode 100644 index 000000000..eeaaeee1c --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/helpers/util.d.ts @@ -0,0 +1,85 @@ +export declare namespace util { + type AssertEqual = (() => V extends T ? 1 : 2) extends () => V extends U ? 1 : 2 ? true : false; + export type isAny = 0 extends 1 & T ? true : false; + export const assertEqual: (_: AssertEqual) => void; + export function assertIs(_arg: T): void; + export function assertNever(_x: never): never; + export type Omit = Pick>; + export type OmitKeys = Pick>; + export type MakePartial = Omit & Partial>; + export type Exactly = T & Record, never>; + export type InexactPartial = { + [k in keyof T]?: T[k] | undefined; + }; + export const arrayToEnum: (items: U) => { [k in U[number]]: k; }; + export const getValidEnumValues: (obj: any) => any[]; + export const objectValues: (obj: any) => any[]; + export const objectKeys: ObjectConstructor["keys"]; + export const find: (arr: T[], checker: (arg: T) => any) => T | undefined; + export type identity = objectUtil.identity; + export type flatten = objectUtil.flatten; + export type noUndefined = T extends undefined ? never : T; + export const isInteger: NumberConstructor["isInteger"]; + export function joinValues(array: T, separator?: string): string; + export const jsonStringifyReplacer: (_: string, value: any) => any; + export {}; +} +export declare namespace objectUtil { + export type MergeShapes = keyof U & keyof V extends never ? U & V : { + [k in Exclude]: U[k]; + } & V; + type optionalKeys = { + [k in keyof T]: undefined extends T[k] ? k : never; + }[keyof T]; + type requiredKeys = { + [k in keyof T]: undefined extends T[k] ? never : k; + }[keyof T]; + export type addQuestionMarks = { + [K in requiredKeys]: T[K]; + } & { + [K in optionalKeys]?: T[K]; + } & { + [k in keyof T]?: unknown; + }; + export type identity = T; + export type flatten = identity<{ + [k in keyof T]: T[k]; + }>; + export type noNeverKeys = { + [k in keyof T]: [T[k]] extends [never] ? never : k; + }[keyof T]; + export type noNever = identity<{ + [k in noNeverKeys]: k extends keyof T ? T[k] : never; + }>; + export const mergeShapes: (first: U, second: T) => T & U; + export type extendShape = keyof A & keyof B extends never ? A & B : { + [K in keyof A as K extends keyof B ? never : K]: A[K]; + } & { + [K in keyof B]: B[K]; + }; + export {}; +} +export declare const ZodParsedType: { + string: "string"; + nan: "nan"; + number: "number"; + integer: "integer"; + float: "float"; + boolean: "boolean"; + date: "date"; + bigint: "bigint"; + symbol: "symbol"; + function: "function"; + undefined: "undefined"; + null: "null"; + array: "array"; + object: "object"; + unknown: "unknown"; + promise: "promise"; + void: "void"; + never: "never"; + map: "map"; + set: "set"; +}; +export type ZodParsedType = keyof typeof ZodParsedType; +export declare const getParsedType: (data: any) => ZodParsedType; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/index.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/index.d.ts new file mode 100644 index 000000000..a9dab5738 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/index.d.ts @@ -0,0 +1,4 @@ +import * as z from "./external.js"; +export * from "./external.js"; +export { z }; +export default z; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/locales/en.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/locales/en.d.ts new file mode 100644 index 000000000..b2bea1ec4 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/locales/en.d.ts @@ -0,0 +1,3 @@ +import { type ZodErrorMap } from "../ZodError.js"; +declare const errorMap: ZodErrorMap; +export default errorMap; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/standard-schema.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/standard-schema.d.ts new file mode 100644 index 000000000..7ddff3b7f --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/standard-schema.d.ts @@ -0,0 +1,102 @@ +/** + * The Standard Schema interface. + */ +export type StandardSchemaV1 = { + /** + * The Standard Schema properties. + */ + readonly "~standard": StandardSchemaV1.Props; +}; +export declare namespace StandardSchemaV1 { + /** + * The Standard Schema properties interface. + */ + export interface Props { + /** + * The version number of the standard. + */ + readonly version: 1; + /** + * The vendor name of the schema library. + */ + readonly vendor: string; + /** + * Validates unknown input values. + */ + readonly validate: (value: unknown) => Result | Promise>; + /** + * Inferred types associated with the schema. + */ + readonly types?: Types | undefined; + } + /** + * The result interface of the validate function. + */ + export type Result = SuccessResult | FailureResult; + /** + * The result interface if validation succeeds. + */ + export interface SuccessResult { + /** + * The typed output value. + */ + readonly value: Output; + /** + * The non-existent issues. + */ + readonly issues?: undefined; + } + /** + * The result interface if validation fails. + */ + export interface FailureResult { + /** + * The issues of failed validation. + */ + readonly issues: ReadonlyArray; + } + /** + * The issue interface of the failure output. + */ + export interface Issue { + /** + * The error message of the issue. + */ + readonly message: string; + /** + * The path of the issue, if any. + */ + readonly path?: ReadonlyArray | undefined; + } + /** + * The path segment interface of the issue. + */ + export interface PathSegment { + /** + * The key representing a path segment. + */ + readonly key: PropertyKey; + } + /** + * The Standard Schema types interface. + */ + export interface Types { + /** + * The input type of the schema. + */ + readonly input: Input; + /** + * The output type of the schema. + */ + readonly output: Output; + } + /** + * Infers the input type of a Standard Schema. + */ + export type InferInput = NonNullable["input"]; + /** + * Infers the output type of a Standard Schema. + */ + export type InferOutput = NonNullable["output"]; + export {}; +} diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/types.d.ts b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/types.d.ts new file mode 100644 index 000000000..301e09128 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/dist/types/v3/types.d.ts @@ -0,0 +1,1031 @@ +import { type IssueData, type StringValidation, type ZodCustomIssue, ZodError, type ZodErrorMap } from "./ZodError.js"; +import type { enumUtil } from "./helpers/enumUtil.js"; +import { errorUtil } from "./helpers/errorUtil.js"; +import { type AsyncParseReturnType, INVALID, type ParseContext, type ParseInput, type ParseParams, type ParseReturnType, ParseStatus, type SyncParseReturnType } from "./helpers/parseUtil.js"; +import type { partialUtil } from "./helpers/partialUtil.js"; +import type { Primitive } from "./helpers/typeAliases.js"; +import { util, type objectUtil } from "./helpers/util.js"; +import type { StandardSchemaV1 } from "./standard-schema.js"; +export interface RefinementCtx { + addIssue: (arg: IssueData) => void; + path: (string | number)[]; +} +export type ZodRawShape = { + [k: string]: ZodTypeAny; +}; +export type ZodTypeAny = ZodType; +export type TypeOf> = T["_output"]; +export type input> = T["_input"]; +export type output> = T["_output"]; +export type { TypeOf as infer }; +export type CustomErrorParams = Partial>; +export interface ZodTypeDef { + errorMap?: ZodErrorMap | undefined; + description?: string | undefined; +} +export type RawCreateParams = { + errorMap?: ZodErrorMap | undefined; + invalid_type_error?: string | undefined; + required_error?: string | undefined; + message?: string | undefined; + description?: string | undefined; +} | undefined; +export type ProcessedCreateParams = { + errorMap?: ZodErrorMap | undefined; + description?: string | undefined; +}; +export type SafeParseSuccess = { + success: true; + data: Output; + error?: never; +}; +export type SafeParseError = { + success: false; + error: ZodError; + data?: never; +}; +export type SafeParseReturnType = SafeParseSuccess | SafeParseError; +export declare abstract class ZodType { + readonly _type: Output; + readonly _output: Output; + readonly _input: Input; + readonly _def: Def; + get description(): string | undefined; + "~standard": StandardSchemaV1.Props; + abstract _parse(input: ParseInput): ParseReturnType; + _getType(input: ParseInput): string; + _getOrReturnCtx(input: ParseInput, ctx?: ParseContext | undefined): ParseContext; + _processInputParams(input: ParseInput): { + status: ParseStatus; + ctx: ParseContext; + }; + _parseSync(input: ParseInput): SyncParseReturnType; + _parseAsync(input: ParseInput): AsyncParseReturnType; + parse(data: unknown, params?: util.InexactPartial): Output; + safeParse(data: unknown, params?: util.InexactPartial): SafeParseReturnType; + "~validate"(data: unknown): StandardSchemaV1.Result | Promise>; + parseAsync(data: unknown, params?: util.InexactPartial): Promise; + safeParseAsync(data: unknown, params?: util.InexactPartial): Promise>; + /** Alias of safeParseAsync */ + spa: (data: unknown, params?: util.InexactPartial) => Promise>; + refine(check: (arg: Output) => arg is RefinedOutput, message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)): ZodEffects; + refine(check: (arg: Output) => unknown | Promise, message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)): ZodEffects; + refinement(check: (arg: Output) => arg is RefinedOutput, refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)): ZodEffects; + refinement(check: (arg: Output) => boolean, refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)): ZodEffects; + _refinement(refinement: RefinementEffect["refinement"]): ZodEffects; + superRefine(refinement: (arg: Output, ctx: RefinementCtx) => arg is RefinedOutput): ZodEffects; + superRefine(refinement: (arg: Output, ctx: RefinementCtx) => void): ZodEffects; + superRefine(refinement: (arg: Output, ctx: RefinementCtx) => Promise): ZodEffects; + constructor(def: Def); + optional(): ZodOptional; + nullable(): ZodNullable; + nullish(): ZodOptional>; + array(): ZodArray; + promise(): ZodPromise; + or(option: T): ZodUnion<[this, T]>; + and(incoming: T): ZodIntersection; + transform(transform: (arg: Output, ctx: RefinementCtx) => NewOut | Promise): ZodEffects; + default(def: util.noUndefined): ZodDefault; + default(def: () => util.noUndefined): ZodDefault; + brand(brand?: B): ZodBranded; + catch(def: Output): ZodCatch; + catch(def: (ctx: { + error: ZodError; + input: Input; + }) => Output): ZodCatch; + describe(description: string): this; + pipe(target: T): ZodPipeline; + readonly(): ZodReadonly; + isOptional(): boolean; + isNullable(): boolean; +} +export type IpVersion = "v4" | "v6"; +export type ZodStringCheck = { + kind: "min"; + value: number; + message?: string | undefined; +} | { + kind: "max"; + value: number; + message?: string | undefined; +} | { + kind: "length"; + value: number; + message?: string | undefined; +} | { + kind: "email"; + message?: string | undefined; +} | { + kind: "url"; + message?: string | undefined; +} | { + kind: "emoji"; + message?: string | undefined; +} | { + kind: "uuid"; + message?: string | undefined; +} | { + kind: "nanoid"; + message?: string | undefined; +} | { + kind: "cuid"; + message?: string | undefined; +} | { + kind: "includes"; + value: string; + position?: number | undefined; + message?: string | undefined; +} | { + kind: "cuid2"; + message?: string | undefined; +} | { + kind: "ulid"; + message?: string | undefined; +} | { + kind: "startsWith"; + value: string; + message?: string | undefined; +} | { + kind: "endsWith"; + value: string; + message?: string | undefined; +} | { + kind: "regex"; + regex: RegExp; + message?: string | undefined; +} | { + kind: "trim"; + message?: string | undefined; +} | { + kind: "toLowerCase"; + message?: string | undefined; +} | { + kind: "toUpperCase"; + message?: string | undefined; +} | { + kind: "jwt"; + alg?: string; + message?: string | undefined; +} | { + kind: "datetime"; + offset: boolean; + local: boolean; + precision: number | null; + message?: string | undefined; +} | { + kind: "date"; + message?: string | undefined; +} | { + kind: "time"; + precision: number | null; + message?: string | undefined; +} | { + kind: "duration"; + message?: string | undefined; +} | { + kind: "ip"; + version?: IpVersion | undefined; + message?: string | undefined; +} | { + kind: "cidr"; + version?: IpVersion | undefined; + message?: string | undefined; +} | { + kind: "base64"; + message?: string | undefined; +} | { + kind: "base64url"; + message?: string | undefined; +}; +export interface ZodStringDef extends ZodTypeDef { + checks: ZodStringCheck[]; + typeName: ZodFirstPartyTypeKind.ZodString; + coerce: boolean; +} +export declare function datetimeRegex(args: { + precision?: number | null; + offset?: boolean; + local?: boolean; +}): RegExp; +export declare class ZodString extends ZodType { + _parse(input: ParseInput): ParseReturnType; + protected _regex(regex: RegExp, validation: StringValidation, message?: errorUtil.ErrMessage): ZodEffects; + _addCheck(check: ZodStringCheck): ZodString; + email(message?: errorUtil.ErrMessage): ZodString; + url(message?: errorUtil.ErrMessage): ZodString; + emoji(message?: errorUtil.ErrMessage): ZodString; + uuid(message?: errorUtil.ErrMessage): ZodString; + nanoid(message?: errorUtil.ErrMessage): ZodString; + cuid(message?: errorUtil.ErrMessage): ZodString; + cuid2(message?: errorUtil.ErrMessage): ZodString; + ulid(message?: errorUtil.ErrMessage): ZodString; + base64(message?: errorUtil.ErrMessage): ZodString; + base64url(message?: errorUtil.ErrMessage): ZodString; + jwt(options?: { + alg?: string; + message?: string | undefined; + }): ZodString; + ip(options?: string | { + version?: IpVersion; + message?: string | undefined; + }): ZodString; + cidr(options?: string | { + version?: IpVersion; + message?: string | undefined; + }): ZodString; + datetime(options?: string | { + message?: string | undefined; + precision?: number | null; + offset?: boolean; + local?: boolean; + }): ZodString; + date(message?: string): ZodString; + time(options?: string | { + message?: string | undefined; + precision?: number | null; + }): ZodString; + duration(message?: errorUtil.ErrMessage): ZodString; + regex(regex: RegExp, message?: errorUtil.ErrMessage): ZodString; + includes(value: string, options?: { + message?: string; + position?: number; + }): ZodString; + startsWith(value: string, message?: errorUtil.ErrMessage): ZodString; + endsWith(value: string, message?: errorUtil.ErrMessage): ZodString; + min(minLength: number, message?: errorUtil.ErrMessage): ZodString; + max(maxLength: number, message?: errorUtil.ErrMessage): ZodString; + length(len: number, message?: errorUtil.ErrMessage): ZodString; + /** + * Equivalent to `.min(1)` + */ + nonempty(message?: errorUtil.ErrMessage): ZodString; + trim(): ZodString; + toLowerCase(): ZodString; + toUpperCase(): ZodString; + get isDatetime(): boolean; + get isDate(): boolean; + get isTime(): boolean; + get isDuration(): boolean; + get isEmail(): boolean; + get isURL(): boolean; + get isEmoji(): boolean; + get isUUID(): boolean; + get isNANOID(): boolean; + get isCUID(): boolean; + get isCUID2(): boolean; + get isULID(): boolean; + get isIP(): boolean; + get isCIDR(): boolean; + get isBase64(): boolean; + get isBase64url(): boolean; + get minLength(): number | null; + get maxLength(): number | null; + static create: (params?: RawCreateParams & { + coerce?: true; + }) => ZodString; +} +export type ZodNumberCheck = { + kind: "min"; + value: number; + inclusive: boolean; + message?: string | undefined; +} | { + kind: "max"; + value: number; + inclusive: boolean; + message?: string | undefined; +} | { + kind: "int"; + message?: string | undefined; +} | { + kind: "multipleOf"; + value: number; + message?: string | undefined; +} | { + kind: "finite"; + message?: string | undefined; +}; +export interface ZodNumberDef extends ZodTypeDef { + checks: ZodNumberCheck[]; + typeName: ZodFirstPartyTypeKind.ZodNumber; + coerce: boolean; +} +export declare class ZodNumber extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams & { + coerce?: boolean; + }) => ZodNumber; + gte(value: number, message?: errorUtil.ErrMessage): ZodNumber; + min: (value: number, message?: errorUtil.ErrMessage) => ZodNumber; + gt(value: number, message?: errorUtil.ErrMessage): ZodNumber; + lte(value: number, message?: errorUtil.ErrMessage): ZodNumber; + max: (value: number, message?: errorUtil.ErrMessage) => ZodNumber; + lt(value: number, message?: errorUtil.ErrMessage): ZodNumber; + protected setLimit(kind: "min" | "max", value: number, inclusive: boolean, message?: string): ZodNumber; + _addCheck(check: ZodNumberCheck): ZodNumber; + int(message?: errorUtil.ErrMessage): ZodNumber; + positive(message?: errorUtil.ErrMessage): ZodNumber; + negative(message?: errorUtil.ErrMessage): ZodNumber; + nonpositive(message?: errorUtil.ErrMessage): ZodNumber; + nonnegative(message?: errorUtil.ErrMessage): ZodNumber; + multipleOf(value: number, message?: errorUtil.ErrMessage): ZodNumber; + step: (value: number, message?: errorUtil.ErrMessage) => ZodNumber; + finite(message?: errorUtil.ErrMessage): ZodNumber; + safe(message?: errorUtil.ErrMessage): ZodNumber; + get minValue(): number | null; + get maxValue(): number | null; + get isInt(): boolean; + get isFinite(): boolean; +} +export type ZodBigIntCheck = { + kind: "min"; + value: bigint; + inclusive: boolean; + message?: string | undefined; +} | { + kind: "max"; + value: bigint; + inclusive: boolean; + message?: string | undefined; +} | { + kind: "multipleOf"; + value: bigint; + message?: string | undefined; +}; +export interface ZodBigIntDef extends ZodTypeDef { + checks: ZodBigIntCheck[]; + typeName: ZodFirstPartyTypeKind.ZodBigInt; + coerce: boolean; +} +export declare class ZodBigInt extends ZodType { + _parse(input: ParseInput): ParseReturnType; + _getInvalidInput(input: ParseInput): INVALID; + static create: (params?: RawCreateParams & { + coerce?: boolean; + }) => ZodBigInt; + gte(value: bigint, message?: errorUtil.ErrMessage): ZodBigInt; + min: (value: bigint, message?: errorUtil.ErrMessage) => ZodBigInt; + gt(value: bigint, message?: errorUtil.ErrMessage): ZodBigInt; + lte(value: bigint, message?: errorUtil.ErrMessage): ZodBigInt; + max: (value: bigint, message?: errorUtil.ErrMessage) => ZodBigInt; + lt(value: bigint, message?: errorUtil.ErrMessage): ZodBigInt; + protected setLimit(kind: "min" | "max", value: bigint, inclusive: boolean, message?: string): ZodBigInt; + _addCheck(check: ZodBigIntCheck): ZodBigInt; + positive(message?: errorUtil.ErrMessage): ZodBigInt; + negative(message?: errorUtil.ErrMessage): ZodBigInt; + nonpositive(message?: errorUtil.ErrMessage): ZodBigInt; + nonnegative(message?: errorUtil.ErrMessage): ZodBigInt; + multipleOf(value: bigint, message?: errorUtil.ErrMessage): ZodBigInt; + get minValue(): bigint | null; + get maxValue(): bigint | null; +} +export interface ZodBooleanDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodBoolean; + coerce: boolean; +} +export declare class ZodBoolean extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams & { + coerce?: boolean; + }) => ZodBoolean; +} +export type ZodDateCheck = { + kind: "min"; + value: number; + message?: string | undefined; +} | { + kind: "max"; + value: number; + message?: string | undefined; +}; +export interface ZodDateDef extends ZodTypeDef { + checks: ZodDateCheck[]; + coerce: boolean; + typeName: ZodFirstPartyTypeKind.ZodDate; +} +export declare class ZodDate extends ZodType { + _parse(input: ParseInput): ParseReturnType; + _addCheck(check: ZodDateCheck): ZodDate; + min(minDate: Date, message?: errorUtil.ErrMessage): ZodDate; + max(maxDate: Date, message?: errorUtil.ErrMessage): ZodDate; + get minDate(): Date | null; + get maxDate(): Date | null; + static create: (params?: RawCreateParams & { + coerce?: boolean; + }) => ZodDate; +} +export interface ZodSymbolDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodSymbol; +} +export declare class ZodSymbol extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodSymbol; +} +export interface ZodUndefinedDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodUndefined; +} +export declare class ZodUndefined extends ZodType { + _parse(input: ParseInput): ParseReturnType; + params?: RawCreateParams; + static create: (params?: RawCreateParams) => ZodUndefined; +} +export interface ZodNullDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodNull; +} +export declare class ZodNull extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodNull; +} +export interface ZodAnyDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodAny; +} +export declare class ZodAny extends ZodType { + _any: true; + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodAny; +} +export interface ZodUnknownDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodUnknown; +} +export declare class ZodUnknown extends ZodType { + _unknown: true; + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodUnknown; +} +export interface ZodNeverDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodNever; +} +export declare class ZodNever extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodNever; +} +export interface ZodVoidDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodVoid; +} +export declare class ZodVoid extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodVoid; +} +export interface ZodArrayDef extends ZodTypeDef { + type: T; + typeName: ZodFirstPartyTypeKind.ZodArray; + exactLength: { + value: number; + message?: string | undefined; + } | null; + minLength: { + value: number; + message?: string | undefined; + } | null; + maxLength: { + value: number; + message?: string | undefined; + } | null; +} +export type ArrayCardinality = "many" | "atleastone"; +export type arrayOutputType = Cardinality extends "atleastone" ? [T["_output"], ...T["_output"][]] : T["_output"][]; +export declare class ZodArray extends ZodType, ZodArrayDef, Cardinality extends "atleastone" ? [T["_input"], ...T["_input"][]] : T["_input"][]> { + _parse(input: ParseInput): ParseReturnType; + get element(): T; + min(minLength: number, message?: errorUtil.ErrMessage): this; + max(maxLength: number, message?: errorUtil.ErrMessage): this; + length(len: number, message?: errorUtil.ErrMessage): this; + nonempty(message?: errorUtil.ErrMessage): ZodArray; + static create: (schema: El, params?: RawCreateParams) => ZodArray; +} +export type ZodNonEmptyArray = ZodArray; +export type UnknownKeysParam = "passthrough" | "strict" | "strip"; +export interface ZodObjectDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodObject; + shape: () => T; + catchall: Catchall; + unknownKeys: UnknownKeys; +} +export type mergeTypes = { + [k in keyof A | keyof B]: k extends keyof B ? B[k] : k extends keyof A ? A[k] : never; +}; +export type objectOutputType = objectUtil.flatten>> & CatchallOutput & PassthroughType; +export type baseObjectOutputType = { + [k in keyof Shape]: Shape[k]["_output"]; +}; +export type objectInputType = objectUtil.flatten> & CatchallInput & PassthroughType; +export type baseObjectInputType = objectUtil.addQuestionMarks<{ + [k in keyof Shape]: Shape[k]["_input"]; +}>; +export type CatchallOutput = ZodType extends T ? unknown : { + [k: string]: T["_output"]; +}; +export type CatchallInput = ZodType extends T ? unknown : { + [k: string]: T["_input"]; +}; +export type PassthroughType = T extends "passthrough" ? { + [k: string]: unknown; +} : unknown; +export type deoptional = T extends ZodOptional ? deoptional : T extends ZodNullable ? ZodNullable> : T; +export type SomeZodObject = ZodObject; +export type noUnrecognized = { + [k in keyof Obj]: k extends keyof Shape ? Obj[k] : never; +}; +export declare class ZodObject, Input = objectInputType> extends ZodType, Input> { + private _cached; + _getCached(): { + shape: T; + keys: string[]; + }; + _parse(input: ParseInput): ParseReturnType; + get shape(): T; + strict(message?: errorUtil.ErrMessage): ZodObject; + strip(): ZodObject; + passthrough(): ZodObject; + /** + * @deprecated In most cases, this is no longer needed - unknown properties are now silently stripped. + * If you want to pass through unknown properties, use `.passthrough()` instead. + */ + nonstrict: () => ZodObject; + extend(augmentation: Augmentation): ZodObject, UnknownKeys, Catchall>; + /** + * @deprecated Use `.extend` instead + * */ + augment: (augmentation: Augmentation) => ZodObject, UnknownKeys, Catchall>; + /** + * Prior to zod@1.0.12 there was a bug in the + * inferred type of merged objects. Please + * upgrade if you are experiencing issues. + */ + merge(merging: Incoming): ZodObject, Incoming["_def"]["unknownKeys"], Incoming["_def"]["catchall"]>; + setKey(key: Key, schema: Schema): ZodObject; + catchall(index: Index): ZodObject; + pick>(mask: Mask): ZodObject>, UnknownKeys, Catchall>; + omit>(mask: Mask): ZodObject, UnknownKeys, Catchall>; + /** + * @deprecated + */ + deepPartial(): partialUtil.DeepPartial; + partial(): ZodObject<{ + [k in keyof T]: ZodOptional; + }, UnknownKeys, Catchall>; + partial>(mask: Mask): ZodObject : T[k]; + }>, UnknownKeys, Catchall>; + required(): ZodObject<{ + [k in keyof T]: deoptional; + }, UnknownKeys, Catchall>; + required>(mask: Mask): ZodObject : T[k]; + }>, UnknownKeys, Catchall>; + keyof(): ZodEnum>; + static create: (shape: Shape, params?: RawCreateParams) => ZodObject, objectInputType>; + static strictCreate: (shape: Shape, params?: RawCreateParams) => ZodObject; + static lazycreate: (shape: () => Shape, params?: RawCreateParams) => ZodObject; +} +export type AnyZodObject = ZodObject; +export type ZodUnionOptions = Readonly<[ZodTypeAny, ...ZodTypeAny[]]>; +export interface ZodUnionDef> extends ZodTypeDef { + options: T; + typeName: ZodFirstPartyTypeKind.ZodUnion; +} +export declare class ZodUnion extends ZodType, T[number]["_input"]> { + _parse(input: ParseInput): ParseReturnType; + get options(): T; + static create: >(types: Options, params?: RawCreateParams) => ZodUnion; +} +export type ZodDiscriminatedUnionOption = ZodObject<{ + [key in Discriminator]: ZodTypeAny; +} & ZodRawShape, UnknownKeysParam, ZodTypeAny>; +export interface ZodDiscriminatedUnionDef[] = ZodDiscriminatedUnionOption[]> extends ZodTypeDef { + discriminator: Discriminator; + options: Options; + optionsMap: Map>; + typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion; +} +export declare class ZodDiscriminatedUnion[]> extends ZodType, ZodDiscriminatedUnionDef, input> { + _parse(input: ParseInput): ParseReturnType; + get discriminator(): Discriminator; + get options(): Options; + get optionsMap(): Map>; + /** + * The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor. + * However, it only allows a union of objects, all of which need to share a discriminator property. This property must + * have a different value for each object in the union. + * @param discriminator the name of the discriminator property + * @param types an array of object schemas + * @param params + */ + static create, + ...ZodDiscriminatedUnionOption[] + ]>(discriminator: Discriminator, options: Types, params?: RawCreateParams): ZodDiscriminatedUnion; +} +export interface ZodIntersectionDef extends ZodTypeDef { + left: T; + right: U; + typeName: ZodFirstPartyTypeKind.ZodIntersection; +} +export declare class ZodIntersection extends ZodType, T["_input"] & U["_input"]> { + _parse(input: ParseInput): ParseReturnType; + static create: (left: TSchema, right: USchema, params?: RawCreateParams) => ZodIntersection; +} +export type ZodTupleItems = [ZodTypeAny, ...ZodTypeAny[]]; +export type AssertArray = T extends any[] ? T : never; +export type OutputTypeOfTuple = AssertArray<{ + [k in keyof T]: T[k] extends ZodType ? T[k]["_output"] : never; +}>; +export type OutputTypeOfTupleWithRest = Rest extends ZodTypeAny ? [...OutputTypeOfTuple, ...Rest["_output"][]] : OutputTypeOfTuple; +export type InputTypeOfTuple = AssertArray<{ + [k in keyof T]: T[k] extends ZodType ? T[k]["_input"] : never; +}>; +export type InputTypeOfTupleWithRest = Rest extends ZodTypeAny ? [...InputTypeOfTuple, ...Rest["_input"][]] : InputTypeOfTuple; +export interface ZodTupleDef extends ZodTypeDef { + items: T; + rest: Rest; + typeName: ZodFirstPartyTypeKind.ZodTuple; +} +export type AnyZodTuple = ZodTuple<[ZodTypeAny, ...ZodTypeAny[]] | [], ZodTypeAny | null>; +export declare class ZodTuple extends ZodType, ZodTupleDef, InputTypeOfTupleWithRest> { + _parse(input: ParseInput): ParseReturnType; + get items(): T; + rest(rest: RestSchema): ZodTuple; + static create: (schemas: Items, params?: RawCreateParams) => ZodTuple; +} +export interface ZodRecordDef extends ZodTypeDef { + valueType: Value; + keyType: Key; + typeName: ZodFirstPartyTypeKind.ZodRecord; +} +export type KeySchema = ZodType; +export type RecordType = [string] extends [K] ? Record : [number] extends [K] ? Record : [symbol] extends [K] ? Record : [BRAND] extends [K] ? Record : Partial>; +export declare class ZodRecord extends ZodType, ZodRecordDef, RecordType> { + get keySchema(): Key; + get valueSchema(): Value; + _parse(input: ParseInput): ParseReturnType; + get element(): Value; + static create(valueType: Value, params?: RawCreateParams): ZodRecord; + static create(keySchema: Keys, valueType: Value, params?: RawCreateParams): ZodRecord; +} +export interface ZodMapDef extends ZodTypeDef { + valueType: Value; + keyType: Key; + typeName: ZodFirstPartyTypeKind.ZodMap; +} +export declare class ZodMap extends ZodType, ZodMapDef, Map> { + get keySchema(): Key; + get valueSchema(): Value; + _parse(input: ParseInput): ParseReturnType; + static create: (keyType: KeySchema, valueType: ValueSchema, params?: RawCreateParams) => ZodMap; +} +export interface ZodSetDef extends ZodTypeDef { + valueType: Value; + typeName: ZodFirstPartyTypeKind.ZodSet; + minSize: { + value: number; + message?: string | undefined; + } | null; + maxSize: { + value: number; + message?: string | undefined; + } | null; +} +export declare class ZodSet extends ZodType, ZodSetDef, Set> { + _parse(input: ParseInput): ParseReturnType; + min(minSize: number, message?: errorUtil.ErrMessage): this; + max(maxSize: number, message?: errorUtil.ErrMessage): this; + size(size: number, message?: errorUtil.ErrMessage): this; + nonempty(message?: errorUtil.ErrMessage): ZodSet; + static create: (valueType: ValueSchema, params?: RawCreateParams) => ZodSet; +} +export interface ZodFunctionDef = ZodTuple, Returns extends ZodTypeAny = ZodTypeAny> extends ZodTypeDef { + args: Args; + returns: Returns; + typeName: ZodFirstPartyTypeKind.ZodFunction; +} +export type OuterTypeOfFunction, Returns extends ZodTypeAny> = Args["_input"] extends Array ? (...args: Args["_input"]) => Returns["_output"] : never; +export type InnerTypeOfFunction, Returns extends ZodTypeAny> = Args["_output"] extends Array ? (...args: Args["_output"]) => Returns["_input"] : never; +export declare class ZodFunction, Returns extends ZodTypeAny> extends ZodType, ZodFunctionDef, InnerTypeOfFunction> { + _parse(input: ParseInput): ParseReturnType; + parameters(): Args; + returnType(): Returns; + args[0]>(...items: Items): ZodFunction, Returns>; + returns>(returnType: NewReturnType): ZodFunction; + implement>(func: F): ReturnType extends Returns["_output"] ? (...args: Args["_input"]) => ReturnType : OuterTypeOfFunction; + strictImplement(func: InnerTypeOfFunction): InnerTypeOfFunction; + validate: >(func: F) => ReturnType extends Returns["_output"] ? (...args: Args["_input"]) => ReturnType : OuterTypeOfFunction; + static create(): ZodFunction, ZodUnknown>; + static create>(args: T): ZodFunction; + static create(args: T, returns: U): ZodFunction; + static create, U extends ZodTypeAny = ZodUnknown>(args: T, returns: U, params?: RawCreateParams): ZodFunction; +} +export interface ZodLazyDef extends ZodTypeDef { + getter: () => T; + typeName: ZodFirstPartyTypeKind.ZodLazy; +} +export declare class ZodLazy extends ZodType, ZodLazyDef, input> { + get schema(): T; + _parse(input: ParseInput): ParseReturnType; + static create: (getter: () => Inner, params?: RawCreateParams) => ZodLazy; +} +export interface ZodLiteralDef extends ZodTypeDef { + value: T; + typeName: ZodFirstPartyTypeKind.ZodLiteral; +} +export declare class ZodLiteral extends ZodType, T> { + _parse(input: ParseInput): ParseReturnType; + get value(): T; + static create: (value: Value, params?: RawCreateParams) => ZodLiteral; +} +export type ArrayKeys = keyof any[]; +export type Indices = Exclude; +export type EnumValues = readonly [T, ...T[]]; +export type Values = { + [k in T[number]]: k; +}; +export interface ZodEnumDef extends ZodTypeDef { + values: T; + typeName: ZodFirstPartyTypeKind.ZodEnum; +} +export type Writeable = { + -readonly [P in keyof T]: T[P]; +}; +export type FilterEnum = Values extends [] ? [] : Values extends [infer Head, ...infer Rest] ? Head extends ToExclude ? FilterEnum : [Head, ...FilterEnum] : never; +export type typecast = A extends T ? A : never; +declare function createZodEnum>(values: T, params?: RawCreateParams): ZodEnum>; +declare function createZodEnum(values: T, params?: RawCreateParams): ZodEnum; +export declare class ZodEnum extends ZodType, T[number]> { + _cache: Set | undefined; + _parse(input: ParseInput): ParseReturnType; + get options(): T; + get enum(): Values; + get Values(): Values; + get Enum(): Values; + extract(values: ToExtract, newDef?: RawCreateParams): ZodEnum>; + exclude(values: ToExclude, newDef?: RawCreateParams): ZodEnum>, [string, ...string[]]>>; + static create: typeof createZodEnum; +} +export interface ZodNativeEnumDef extends ZodTypeDef { + values: T; + typeName: ZodFirstPartyTypeKind.ZodNativeEnum; +} +export type EnumLike = { + [k: string]: string | number; + [nu: number]: string; +}; +export declare class ZodNativeEnum extends ZodType, T[keyof T]> { + _cache: Set | undefined; + _parse(input: ParseInput): ParseReturnType; + get enum(): T; + static create: (values: Elements, params?: RawCreateParams) => ZodNativeEnum; +} +export interface ZodPromiseDef extends ZodTypeDef { + type: T; + typeName: ZodFirstPartyTypeKind.ZodPromise; +} +export declare class ZodPromise extends ZodType, ZodPromiseDef, Promise> { + unwrap(): T; + _parse(input: ParseInput): ParseReturnType; + static create: (schema: Inner, params?: RawCreateParams) => ZodPromise; +} +export type Refinement = (arg: T, ctx: RefinementCtx) => any; +export type SuperRefinement = (arg: T, ctx: RefinementCtx) => void | Promise; +export type RefinementEffect = { + type: "refinement"; + refinement: (arg: T, ctx: RefinementCtx) => any; +}; +export type TransformEffect = { + type: "transform"; + transform: (arg: T, ctx: RefinementCtx) => any; +}; +export type PreprocessEffect = { + type: "preprocess"; + transform: (arg: T, ctx: RefinementCtx) => any; +}; +export type Effect = RefinementEffect | TransformEffect | PreprocessEffect; +export interface ZodEffectsDef extends ZodTypeDef { + schema: T; + typeName: ZodFirstPartyTypeKind.ZodEffects; + effect: Effect; +} +export declare class ZodEffects, Input = input> extends ZodType, Input> { + innerType(): T; + sourceType(): T; + _parse(input: ParseInput): ParseReturnType; + static create: (schema: I, effect: Effect, params?: RawCreateParams) => ZodEffects; + static createWithPreprocess: (preprocess: (arg: unknown, ctx: RefinementCtx) => unknown, schema: I, params?: RawCreateParams) => ZodEffects; +} +export { ZodEffects as ZodTransformer }; +export interface ZodOptionalDef extends ZodTypeDef { + innerType: T; + typeName: ZodFirstPartyTypeKind.ZodOptional; +} +export type ZodOptionalType = ZodOptional; +export declare class ZodOptional extends ZodType, T["_input"] | undefined> { + _parse(input: ParseInput): ParseReturnType; + unwrap(): T; + static create: (type: Inner, params?: RawCreateParams) => ZodOptional; +} +export interface ZodNullableDef extends ZodTypeDef { + innerType: T; + typeName: ZodFirstPartyTypeKind.ZodNullable; +} +export type ZodNullableType = ZodNullable; +export declare class ZodNullable extends ZodType, T["_input"] | null> { + _parse(input: ParseInput): ParseReturnType; + unwrap(): T; + static create: (type: Inner, params?: RawCreateParams) => ZodNullable; +} +export interface ZodDefaultDef extends ZodTypeDef { + innerType: T; + defaultValue: () => util.noUndefined; + typeName: ZodFirstPartyTypeKind.ZodDefault; +} +export declare class ZodDefault extends ZodType, ZodDefaultDef, T["_input"] | undefined> { + _parse(input: ParseInput): ParseReturnType; + removeDefault(): T; + static create: (type: Inner, params: RawCreateParams & { + default: Inner["_input"] | (() => util.noUndefined); + }) => ZodDefault; +} +export interface ZodCatchDef extends ZodTypeDef { + innerType: T; + catchValue: (ctx: { + error: ZodError; + input: unknown; + }) => T["_input"]; + typeName: ZodFirstPartyTypeKind.ZodCatch; +} +export declare class ZodCatch extends ZodType, unknown> { + _parse(input: ParseInput): ParseReturnType; + removeCatch(): T; + static create: (type: Inner, params: RawCreateParams & { + catch: Inner["_output"] | (() => Inner["_output"]); + }) => ZodCatch; +} +export interface ZodNaNDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodNaN; +} +export declare class ZodNaN extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodNaN; +} +export interface ZodBrandedDef extends ZodTypeDef { + type: T; + typeName: ZodFirstPartyTypeKind.ZodBranded; +} +export declare const BRAND: unique symbol; +export type BRAND = { + [BRAND]: { + [k in T]: true; + }; +}; +export declare class ZodBranded extends ZodType, ZodBrandedDef, T["_input"]> { + _parse(input: ParseInput): ParseReturnType; + unwrap(): T; +} +export interface ZodPipelineDef extends ZodTypeDef { + in: A; + out: B; + typeName: ZodFirstPartyTypeKind.ZodPipeline; +} +export declare class ZodPipeline extends ZodType, A["_input"]> { + _parse(input: ParseInput): ParseReturnType; + static create(a: ASchema, b: BSchema): ZodPipeline; +} +type BuiltIn = (((...args: any[]) => any) | (new (...args: any[]) => any)) | { + readonly [Symbol.toStringTag]: string; +} | Date | Error | Generator | Promise | RegExp; +type MakeReadonly = T extends Map ? ReadonlyMap : T extends Set ? ReadonlySet : T extends [infer Head, ...infer Tail] ? readonly [Head, ...Tail] : T extends Array ? ReadonlyArray : T extends BuiltIn ? T : Readonly; +export interface ZodReadonlyDef extends ZodTypeDef { + innerType: T; + typeName: ZodFirstPartyTypeKind.ZodReadonly; +} +export declare class ZodReadonly extends ZodType, ZodReadonlyDef, MakeReadonly> { + _parse(input: ParseInput): ParseReturnType; + static create: (type: Inner, params?: RawCreateParams) => ZodReadonly; + unwrap(): T; +} +type CustomParams = CustomErrorParams & { + fatal?: boolean; +}; +export declare function custom(check?: (data: any) => any, _params?: string | CustomParams | ((input: any) => CustomParams), +/** + * @deprecated + * + * Pass `fatal` into the params object instead: + * + * ```ts + * z.string().custom((val) => val.length > 5, { fatal: false }) + * ``` + * + */ +fatal?: boolean): ZodType; +export { ZodType as Schema, ZodType as ZodSchema }; +export declare const late: { + object: (shape: () => Shape, params?: RawCreateParams) => ZodObject; +}; +export declare enum ZodFirstPartyTypeKind { + ZodString = "ZodString", + ZodNumber = "ZodNumber", + ZodNaN = "ZodNaN", + ZodBigInt = "ZodBigInt", + ZodBoolean = "ZodBoolean", + ZodDate = "ZodDate", + ZodSymbol = "ZodSymbol", + ZodUndefined = "ZodUndefined", + ZodNull = "ZodNull", + ZodAny = "ZodAny", + ZodUnknown = "ZodUnknown", + ZodNever = "ZodNever", + ZodVoid = "ZodVoid", + ZodArray = "ZodArray", + ZodObject = "ZodObject", + ZodUnion = "ZodUnion", + ZodDiscriminatedUnion = "ZodDiscriminatedUnion", + ZodIntersection = "ZodIntersection", + ZodTuple = "ZodTuple", + ZodRecord = "ZodRecord", + ZodMap = "ZodMap", + ZodSet = "ZodSet", + ZodFunction = "ZodFunction", + ZodLazy = "ZodLazy", + ZodLiteral = "ZodLiteral", + ZodEnum = "ZodEnum", + ZodEffects = "ZodEffects", + ZodNativeEnum = "ZodNativeEnum", + ZodOptional = "ZodOptional", + ZodNullable = "ZodNullable", + ZodDefault = "ZodDefault", + ZodCatch = "ZodCatch", + ZodPromise = "ZodPromise", + ZodBranded = "ZodBranded", + ZodPipeline = "ZodPipeline", + ZodReadonly = "ZodReadonly" +} +export type ZodFirstPartySchemaTypes = ZodString | ZodNumber | ZodNaN | ZodBigInt | ZodBoolean | ZodDate | ZodUndefined | ZodNull | ZodAny | ZodUnknown | ZodNever | ZodVoid | ZodArray | ZodObject | ZodUnion | ZodDiscriminatedUnion | ZodIntersection | ZodTuple | ZodRecord | ZodMap | ZodSet | ZodFunction | ZodLazy | ZodLiteral | ZodEnum | ZodEffects | ZodNativeEnum | ZodOptional | ZodNullable | ZodDefault | ZodCatch | ZodPromise | ZodBranded | ZodPipeline | ZodReadonly | ZodSymbol; +declare abstract class Class { + constructor(..._: any[]); +} +declare const instanceOfType: (cls: T, params?: CustomParams) => ZodType, ZodTypeDef, InstanceType>; +declare const stringType: (params?: RawCreateParams & { + coerce?: true; +}) => ZodString; +declare const numberType: (params?: RawCreateParams & { + coerce?: boolean; +}) => ZodNumber; +declare const nanType: (params?: RawCreateParams) => ZodNaN; +declare const bigIntType: (params?: RawCreateParams & { + coerce?: boolean; +}) => ZodBigInt; +declare const booleanType: (params?: RawCreateParams & { + coerce?: boolean; +}) => ZodBoolean; +declare const dateType: (params?: RawCreateParams & { + coerce?: boolean; +}) => ZodDate; +declare const symbolType: (params?: RawCreateParams) => ZodSymbol; +declare const undefinedType: (params?: RawCreateParams) => ZodUndefined; +declare const nullType: (params?: RawCreateParams) => ZodNull; +declare const anyType: (params?: RawCreateParams) => ZodAny; +declare const unknownType: (params?: RawCreateParams) => ZodUnknown; +declare const neverType: (params?: RawCreateParams) => ZodNever; +declare const voidType: (params?: RawCreateParams) => ZodVoid; +declare const arrayType: (schema: El, params?: RawCreateParams) => ZodArray; +declare const objectType: (shape: Shape, params?: RawCreateParams) => ZodObject, objectInputType>; +declare const strictObjectType: (shape: Shape, params?: RawCreateParams) => ZodObject; +declare const unionType: >(types: Options, params?: RawCreateParams) => ZodUnion; +declare const discriminatedUnionType: typeof ZodDiscriminatedUnion.create; +declare const intersectionType: (left: TSchema, right: USchema, params?: RawCreateParams) => ZodIntersection; +declare const tupleType: (schemas: Items, params?: RawCreateParams) => ZodTuple; +declare const recordType: typeof ZodRecord.create; +declare const mapType: (keyType: KeySchema, valueType: ValueSchema, params?: RawCreateParams) => ZodMap; +declare const setType: (valueType: ValueSchema, params?: RawCreateParams) => ZodSet; +declare const functionType: typeof ZodFunction.create; +declare const lazyType: (getter: () => Inner, params?: RawCreateParams) => ZodLazy; +declare const literalType: (value: Value, params?: RawCreateParams) => ZodLiteral; +declare const enumType: typeof createZodEnum; +declare const nativeEnumType: (values: Elements, params?: RawCreateParams) => ZodNativeEnum; +declare const promiseType: (schema: Inner, params?: RawCreateParams) => ZodPromise; +declare const effectsType: (schema: I, effect: Effect, params?: RawCreateParams) => ZodEffects; +declare const optionalType: (type: Inner, params?: RawCreateParams) => ZodOptional; +declare const nullableType: (type: Inner, params?: RawCreateParams) => ZodNullable; +declare const preprocessType: (preprocess: (arg: unknown, ctx: RefinementCtx) => unknown, schema: I, params?: RawCreateParams) => ZodEffects; +declare const pipelineType: typeof ZodPipeline.create; +declare const ostring: () => ZodOptional; +declare const onumber: () => ZodOptional; +declare const oboolean: () => ZodOptional; +export declare const coerce: { + string: (typeof ZodString)["create"]; + number: (typeof ZodNumber)["create"]; + boolean: (typeof ZodBoolean)["create"]; + bigint: (typeof ZodBigInt)["create"]; + date: (typeof ZodDate)["create"]; +}; +export { anyType as any, arrayType as array, bigIntType as bigint, booleanType as boolean, dateType as date, discriminatedUnionType as discriminatedUnion, effectsType as effect, enumType as enum, functionType as function, instanceOfType as instanceof, intersectionType as intersection, lazyType as lazy, literalType as literal, mapType as map, nanType as nan, nativeEnumType as nativeEnum, neverType as never, nullType as null, nullableType as nullable, numberType as number, objectType as object, oboolean, onumber, optionalType as optional, ostring, pipelineType as pipeline, preprocessType as preprocess, promiseType as promise, recordType as record, setType as set, strictObjectType as strictObject, stringType as string, symbolType as symbol, effectsType as transformer, tupleType as tuple, undefinedType as undefined, unionType as union, unknownType as unknown, voidType as void, }; +export declare const NEVER: never; diff --git a/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/package.json b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/package.json new file mode 100644 index 000000000..e02b87614 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/node_modules/zod/package.json @@ -0,0 +1,120 @@ +{ + "name": "zod", + "version": "3.25.67", + "type": "module", + "author": "Colin McDonnell ", + "description": "TypeScript-first schema declaration and validation library with static type inference", + "files": ["dist", "v3", "v4", "v4-mini"], + "funding": "https://github.com/sponsors/colinhacks", + "homepage": "https://zod.dev", + "keywords": ["typescript", "schema", "validation", "type", "inference"], + "license": "MIT", + "sideEffects": false, + "main": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts", + "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": { + "@zod/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "@zod/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "default": "./dist/cjs/index.js" + } + }, + "./v3": { + "import": { + "@zod/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "@zod/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "default": "./dist/cjs/index.js" + } + }, + "./v4": { + "import": { + "@zod/source": "./src/v4/index.ts", + "types": "./dist/types/v4/index.d.ts", + "default": "./dist/esm/v4/index.js" + }, + "require": { + "@zod/source": "./src/v4/index.ts", + "types": "./dist/types/v4/index.d.ts", + "default": "./dist/cjs/v4/index.js" + } + }, + "./v4-mini": { + "@zod/source": "./src/v4/mini/index.ts", + "types": "./dist/types/v4/mini/index.d.ts", + "import": "./dist/esm/v4/mini/index.js", + "require": "./dist/cjs/v4/mini/index.js" + }, + "./v4/mini": { + "@zod/source": "./src/v4/mini/index.ts", + "types": "./dist/types/v4/mini/index.d.ts", + "import": "./dist/esm/v4/mini/index.js", + "require": "./dist/cjs/v4/mini/index.js" + }, + "./v4/core": { + "@zod/source": "./src/v4/core/index.ts", + "types": "./dist/types/v4/core/index.d.ts", + "import": "./dist/esm/v4/core/index.js", + "require": "./dist/cjs/v4/core/index.js" + }, + "./v4/locales": { + "@zod/source": "./src/v4/locales/index.ts", + "types": "./dist/types/v4/locales/index.d.ts", + "import": "./dist/esm/v4/locales/index.js", + "require": "./dist/cjs/v4/locales/index.js" + }, + "./v4/locales/*": { + "@zod/source": "./src/v4/locales/*", + "types": "./dist/types/v4/locales/*", + "import": "./dist/esm/v4/locales/*", + "require": "./dist/cjs/v4/locales/*" + }, + "./package.json": "./package.json" + }, + "tshy": { + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./v3": "./src/v3/index.ts", + "./v4": "./src/v4/index.ts", + "./v4-mini": "./src/v4/mini/index.ts", + "./v4/core": "./src/v4/core/index.ts", + "./v4/locales": "./src/v4/locales/index.ts", + "./v4/locales/*": "./src/v4/locales/*" + }, + "sourceDialects": ["@zod/source"], + "exclude": ["./src/**/*.test.ts"] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/colinhacks/zod.git" + }, + "bugs": { + "url": "https://github.com/colinhacks/zod/issues" + }, + "support": { + "backing": { + "npm-funding": true + } + }, + "scripts": { + "clean": "rm -rf dist", + "build": "tsx ./build.mts", + "postbuild": "pnpm biome check --write .", + "test:watch": "pnpm vitest", + "test": "pnpm vitest run", + "bump:beta": "pnpm version \"v$(pnpm pkg get version | jq -r)-beta.$(date +%Y%m%dT%H%M%S)\"", + "pub:beta": "pnpm bump:beta && pnpm publish --tag next --publish-branch v4 --no-git-checks --dry-run" + } +} diff --git a/tests/fixtures/npm/cases/zod3-adoption/package.json b/tests/fixtures/npm/cases/zod3-adoption/package.json new file mode 100644 index 000000000..ff368ad87 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-adoption/package.json @@ -0,0 +1 @@ +{ "name": "zod3-adoption-case", "private": true, "type": "module" } diff --git a/tests/fixtures/npm/cases/zod3-boundary/lying.ts b/tests/fixtures/npm/cases/zod3-boundary/lying.ts new file mode 100644 index 000000000..107dda7d8 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/lying.ts @@ -0,0 +1,19 @@ +// A declaration that lies about a value: the boundary check throws a +// TypeError naming the path (native only; Node does not check). Run by +// tests/harness/decl-boundary.test.ts. +import { z } from "zod"; + +const Liar = z.custom<{ name: string; n: number }>(() => true); +try { + const v = Liar.parse({ name: "x", n: "not a number" }); + console.log("liar", v.n + 1); +} catch (e) { + console.log("liar threw", e instanceof TypeError, (e as Error).message); +} +const Frozen = z.custom<{ tags: string[] }>(); +try { + const f = Frozen.parse(Object.freeze(JSON.parse('{"tags":["a"]}'))); + console.log("frozen", f.tags.length); +} catch (e) { + console.log("frozen threw", (e as Error).message); +} diff --git a/tests/fixtures/npm/cases/zod3-boundary/main.ts b/tests/fixtures/npm/cases/zod3-boundary/main.ts new file mode 100644 index 000000000..8e8cedee7 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/main.ts @@ -0,0 +1,83 @@ +// Values that cross from zod 3.25 (compiled from its JavaScript) into code +// typed by zod's declarations: identity, aliasing and mutation after parse, +// values zod hands back unchanged, callbacks zod calls, typed array work, +// and failing checks. +import { z } from "zod"; + +const show = (label: string, value: unknown): void => { + console.log(label + " " + JSON.stringify(value)); +}; + +// Typed records: field reads, writes and array work on parsed data. +const Item = z.object({ + sku: z.string(), + price: z.number().nonnegative(), + qty: z.number().int(), + tags: z.array(z.string()).default([]), +}); +type Item = z.infer; +const items: Item[] = z.array(Item).parse([ + { sku: "a", price: 2.5, qty: 4, tags: ["x"] }, + { sku: "b", price: 10, qty: 1 }, + { sku: "c", price: 0.5, qty: 10, tags: ["x", "y"] }, +]); +const total = items.reduce((sum, it) => sum + it.price * it.qty, 0); +console.log("total", total); +const byTag = new Map(); +for (const it of items) for (const t of it.tags) byTag.set(t, (byTag.get(t) ?? 0) + it.qty); +console.log("byTag", [...byTag].map(([k, v]) => `${k}=${v}`).join(",")); +const sorted = [...items].sort((a, b) => b.price - a.price).map((it) => it.sku); +console.log("sorted", sorted.join(",")); + +// Mutation after parse is visible through every reference. +const first = items[0]!; +first.qty += 1; +first.tags.push("z"); +console.log("mutated", items[0]!.qty, items[0]!.tags.join("|"), first === items[0]); +show("item", items[0]); + +// A value zod hands back unchanged keeps its identity and its mutations. +const Point = z.custom<{ x: number; y: number }>(); +const original = { x: 1, y: 2 }; +const same = Point.parse(original); +same.x = 10; +console.log("custom", original.x, same === original); +original.y = 20; +console.log("custom back", same.y); + +// A shared object returned by a transform: every parse sees the same one. +const shared = { hits: 0 }; +const Counter = z.string().transform(() => shared); +const c1 = Counter.parse("a"); +const c2 = Counter.parse("b"); +c1.hits += 1; +console.log("shared", c2.hits, shared.hits, c1 === c2); + +// A refine callback reads typed fields of the value zod checks. +const Range = z + .object({ lo: z.number(), hi: z.number() }) + .refine((r) => r.lo <= r.hi, { message: "lo must not exceed hi" }); +console.log("range ok", JSON.stringify(Range.parse({ lo: 1, hi: 3 }))); +const bad = Range.safeParse({ lo: 5, hi: 3 }); +console.log("range bad", bad.success ? "?" : bad.error.issues[0]?.message); + +// safeParse data, a discriminated union, and passthrough keys. +const Event = z.discriminatedUnion("type", [ + z.object({ type: z.literal("click"), x: z.number(), y: z.number() }), + z.object({ type: z.literal("key"), key: z.string() }), +]); +type Event = z.infer; +const describe = (e: Event): string => (e.type === "click" ? `click@${e.x},${e.y}` : `key:${e.key}`); +const r = Event.safeParse({ type: "key", key: "Enter" }); +if (r.success) console.log("event", describe(r.data)); +const Loose = z.object({ id: z.number() }).passthrough(); +const loose = Loose.parse({ extra: true, id: 3, more: "m" }); +console.log("loose", loose.id, Object.keys(loose).join(","), JSON.stringify(loose)); + +// async parse +async function main(): Promise { + const it = await Item.parseAsync({ sku: "d", price: 1, qty: 2 }); + console.log("async", it.sku, it.price * it.qty, it.tags.length); +} + +void main(); diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/LICENSE b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/LICENSE new file mode 100644 index 000000000..c06579627 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Colin McDonnell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/index.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/index.js new file mode 100644 index 000000000..8352ab713 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/index.js @@ -0,0 +1,3 @@ +import z3 from "./v3/index.js"; +export * from "./v3/index.js"; +export default z3; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/package.json b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/package.json new file mode 100644 index 000000000..aead43de3 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} \ No newline at end of file diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/ZodError.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/ZodError.js new file mode 100644 index 000000000..c72ce5288 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/ZodError.js @@ -0,0 +1,132 @@ +import { util } from "./helpers/util.js"; +export const ZodIssueCode = util.arrayToEnum([ + "invalid_type", + "invalid_literal", + "custom", + "invalid_union", + "invalid_union_discriminator", + "invalid_enum_value", + "unrecognized_keys", + "invalid_arguments", + "invalid_return_type", + "invalid_date", + "invalid_string", + "too_small", + "too_big", + "invalid_intersection_types", + "not_multiple_of", + "not_finite", +]); +export const quotelessJson = (obj) => { + const json = JSON.stringify(obj, null, 2); + return json.replace(/"([^"]+)":/g, "$1:"); +}; +export class ZodError extends Error { + get errors() { + return this.issues; + } + constructor(issues) { + super(); + this.issues = []; + this.addIssue = (sub) => { + this.issues = [...this.issues, sub]; + }; + this.addIssues = (subs = []) => { + this.issues = [...this.issues, ...subs]; + }; + const actualProto = new.target.prototype; + if (Object.setPrototypeOf) { + // eslint-disable-next-line ban/ban + Object.setPrototypeOf(this, actualProto); + } + else { + this.__proto__ = actualProto; + } + this.name = "ZodError"; + this.issues = issues; + } + format(_mapper) { + const mapper = _mapper || + function (issue) { + return issue.message; + }; + const fieldErrors = { _errors: [] }; + const processError = (error) => { + for (const issue of error.issues) { + if (issue.code === "invalid_union") { + issue.unionErrors.map(processError); + } + else if (issue.code === "invalid_return_type") { + processError(issue.returnTypeError); + } + else if (issue.code === "invalid_arguments") { + processError(issue.argumentsError); + } + else if (issue.path.length === 0) { + fieldErrors._errors.push(mapper(issue)); + } + else { + let curr = fieldErrors; + let i = 0; + while (i < issue.path.length) { + const el = issue.path[i]; + const terminal = i === issue.path.length - 1; + if (!terminal) { + curr[el] = curr[el] || { _errors: [] }; + // if (typeof el === "string") { + // curr[el] = curr[el] || { _errors: [] }; + // } else if (typeof el === "number") { + // const errorArray: any = []; + // errorArray._errors = []; + // curr[el] = curr[el] || errorArray; + // } + } + else { + curr[el] = curr[el] || { _errors: [] }; + curr[el]._errors.push(mapper(issue)); + } + curr = curr[el]; + i++; + } + } + } + }; + processError(this); + return fieldErrors; + } + static assert(value) { + if (!(value instanceof ZodError)) { + throw new Error(`Not a ZodError: ${value}`); + } + } + toString() { + return this.message; + } + get message() { + return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2); + } + get isEmpty() { + return this.issues.length === 0; + } + flatten(mapper = (issue) => issue.message) { + const fieldErrors = {}; + const formErrors = []; + for (const sub of this.issues) { + if (sub.path.length > 0) { + fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || []; + fieldErrors[sub.path[0]].push(mapper(sub)); + } + else { + formErrors.push(mapper(sub)); + } + } + return { formErrors, fieldErrors }; + } + get formErrors() { + return this.flatten(); + } +} +ZodError.create = (issues) => { + const error = new ZodError(issues); + return error; +}; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/errors.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/errors.js new file mode 100644 index 000000000..26fdf628a --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/errors.js @@ -0,0 +1,9 @@ +import defaultErrorMap from "./locales/en.js"; +let overrideErrorMap = defaultErrorMap; +export { defaultErrorMap }; +export function setErrorMap(map) { + overrideErrorMap = map; +} +export function getErrorMap() { + return overrideErrorMap; +} diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/external.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/external.js new file mode 100644 index 000000000..f0a4be4c7 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/external.js @@ -0,0 +1,6 @@ +export * from "./errors.js"; +export * from "./helpers/parseUtil.js"; +export * from "./helpers/typeAliases.js"; +export * from "./helpers/util.js"; +export * from "./types.js"; +export * from "./ZodError.js"; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/enumUtil.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/enumUtil.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/enumUtil.js @@ -0,0 +1 @@ +export {}; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/errorUtil.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/errorUtil.js new file mode 100644 index 000000000..e1640d1a4 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/errorUtil.js @@ -0,0 +1,6 @@ +export var errorUtil; +(function (errorUtil) { + errorUtil.errToObj = (message) => typeof message === "string" ? { message } : message || {}; + // biome-ignore lint: + errorUtil.toString = (message) => typeof message === "string" ? message : message?.message; +})(errorUtil || (errorUtil = {})); diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/parseUtil.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/parseUtil.js new file mode 100644 index 000000000..91b2afb81 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/parseUtil.js @@ -0,0 +1,109 @@ +import { getErrorMap } from "../errors.js"; +import defaultErrorMap from "../locales/en.js"; +export const makeIssue = (params) => { + const { data, path, errorMaps, issueData } = params; + const fullPath = [...path, ...(issueData.path || [])]; + const fullIssue = { + ...issueData, + path: fullPath, + }; + if (issueData.message !== undefined) { + return { + ...issueData, + path: fullPath, + message: issueData.message, + }; + } + let errorMessage = ""; + const maps = errorMaps + .filter((m) => !!m) + .slice() + .reverse(); + for (const map of maps) { + errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message; + } + return { + ...issueData, + path: fullPath, + message: errorMessage, + }; +}; +export const EMPTY_PATH = []; +export function addIssueToContext(ctx, issueData) { + const overrideMap = getErrorMap(); + const issue = makeIssue({ + issueData: issueData, + data: ctx.data, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, // contextual error map is first priority + ctx.schemaErrorMap, // then schema-bound map if available + overrideMap, // then global override map + overrideMap === defaultErrorMap ? undefined : defaultErrorMap, // then global default map + ].filter((x) => !!x), + }); + ctx.common.issues.push(issue); +} +export class ParseStatus { + constructor() { + this.value = "valid"; + } + dirty() { + if (this.value === "valid") + this.value = "dirty"; + } + abort() { + if (this.value !== "aborted") + this.value = "aborted"; + } + static mergeArray(status, results) { + const arrayValue = []; + for (const s of results) { + if (s.status === "aborted") + return INVALID; + if (s.status === "dirty") + status.dirty(); + arrayValue.push(s.value); + } + return { status: status.value, value: arrayValue }; + } + static async mergeObjectAsync(status, pairs) { + const syncPairs = []; + for (const pair of pairs) { + const key = await pair.key; + const value = await pair.value; + syncPairs.push({ + key, + value, + }); + } + return ParseStatus.mergeObjectSync(status, syncPairs); + } + static mergeObjectSync(status, pairs) { + const finalObject = {}; + for (const pair of pairs) { + const { key, value } = pair; + if (key.status === "aborted") + return INVALID; + if (value.status === "aborted") + return INVALID; + if (key.status === "dirty") + status.dirty(); + if (value.status === "dirty") + status.dirty(); + if (key.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) { + finalObject[key.value] = value.value; + } + } + return { status: status.value, value: finalObject }; + } +} +export const INVALID = Object.freeze({ + status: "aborted", +}); +export const DIRTY = (value) => ({ status: "dirty", value }); +export const OK = (value) => ({ status: "valid", value }); +export const isAborted = (x) => x.status === "aborted"; +export const isDirty = (x) => x.status === "dirty"; +export const isValid = (x) => x.status === "valid"; +export const isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/partialUtil.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/partialUtil.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/partialUtil.js @@ -0,0 +1 @@ +export {}; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/typeAliases.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/typeAliases.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/typeAliases.js @@ -0,0 +1 @@ +export {}; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/util.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/util.js new file mode 100644 index 000000000..b9641f29b --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/helpers/util.js @@ -0,0 +1,133 @@ +export var util; +(function (util) { + util.assertEqual = (_) => { }; + function assertIs(_arg) { } + util.assertIs = assertIs; + function assertNever(_x) { + throw new Error(); + } + util.assertNever = assertNever; + util.arrayToEnum = (items) => { + const obj = {}; + for (const item of items) { + obj[item] = item; + } + return obj; + }; + util.getValidEnumValues = (obj) => { + const validKeys = util.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number"); + const filtered = {}; + for (const k of validKeys) { + filtered[k] = obj[k]; + } + return util.objectValues(filtered); + }; + util.objectValues = (obj) => { + return util.objectKeys(obj).map(function (e) { + return obj[e]; + }); + }; + util.objectKeys = typeof Object.keys === "function" // eslint-disable-line ban/ban + ? (obj) => Object.keys(obj) // eslint-disable-line ban/ban + : (object) => { + const keys = []; + for (const key in object) { + if (Object.prototype.hasOwnProperty.call(object, key)) { + keys.push(key); + } + } + return keys; + }; + util.find = (arr, checker) => { + for (const item of arr) { + if (checker(item)) + return item; + } + return undefined; + }; + util.isInteger = typeof Number.isInteger === "function" + ? (val) => Number.isInteger(val) // eslint-disable-line ban/ban + : (val) => typeof val === "number" && Number.isFinite(val) && Math.floor(val) === val; + function joinValues(array, separator = " | ") { + return array.map((val) => (typeof val === "string" ? `'${val}'` : val)).join(separator); + } + util.joinValues = joinValues; + util.jsonStringifyReplacer = (_, value) => { + if (typeof value === "bigint") { + return value.toString(); + } + return value; + }; +})(util || (util = {})); +export var objectUtil; +(function (objectUtil) { + objectUtil.mergeShapes = (first, second) => { + return { + ...first, + ...second, // second overwrites first + }; + }; +})(objectUtil || (objectUtil = {})); +export const ZodParsedType = util.arrayToEnum([ + "string", + "nan", + "number", + "integer", + "float", + "boolean", + "date", + "bigint", + "symbol", + "function", + "undefined", + "null", + "array", + "object", + "unknown", + "promise", + "void", + "never", + "map", + "set", +]); +export const getParsedType = (data) => { + const t = typeof data; + switch (t) { + case "undefined": + return ZodParsedType.undefined; + case "string": + return ZodParsedType.string; + case "number": + return Number.isNaN(data) ? ZodParsedType.nan : ZodParsedType.number; + case "boolean": + return ZodParsedType.boolean; + case "function": + return ZodParsedType.function; + case "bigint": + return ZodParsedType.bigint; + case "symbol": + return ZodParsedType.symbol; + case "object": + if (Array.isArray(data)) { + return ZodParsedType.array; + } + if (data === null) { + return ZodParsedType.null; + } + if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") { + return ZodParsedType.promise; + } + if (typeof Map !== "undefined" && data instanceof Map) { + return ZodParsedType.map; + } + if (typeof Set !== "undefined" && data instanceof Set) { + return ZodParsedType.set; + } + if (typeof Date !== "undefined" && data instanceof Date) { + return ZodParsedType.date; + } + return ZodParsedType.object; + default: + return ZodParsedType.unknown; + } +}; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/index.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/index.js new file mode 100644 index 000000000..a9dab5738 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/index.js @@ -0,0 +1,4 @@ +import * as z from "./external.js"; +export * from "./external.js"; +export { z }; +export default z; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/locales/en.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/locales/en.js new file mode 100644 index 000000000..3e97ad51a --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/locales/en.js @@ -0,0 +1,107 @@ +import { ZodIssueCode } from "../ZodError.js"; +import { util, ZodParsedType } from "../helpers/util.js"; +const errorMap = (issue, _ctx) => { + let message; + switch (issue.code) { + case ZodIssueCode.invalid_type: + if (issue.received === ZodParsedType.undefined) { + message = "Required"; + } + else { + message = `Expected ${issue.expected}, received ${issue.received}`; + } + break; + case ZodIssueCode.invalid_literal: + message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util.jsonStringifyReplacer)}`; + break; + case ZodIssueCode.unrecognized_keys: + message = `Unrecognized key(s) in object: ${util.joinValues(issue.keys, ", ")}`; + break; + case ZodIssueCode.invalid_union: + message = `Invalid input`; + break; + case ZodIssueCode.invalid_union_discriminator: + message = `Invalid discriminator value. Expected ${util.joinValues(issue.options)}`; + break; + case ZodIssueCode.invalid_enum_value: + message = `Invalid enum value. Expected ${util.joinValues(issue.options)}, received '${issue.received}'`; + break; + case ZodIssueCode.invalid_arguments: + message = `Invalid function arguments`; + break; + case ZodIssueCode.invalid_return_type: + message = `Invalid function return type`; + break; + case ZodIssueCode.invalid_date: + message = `Invalid date`; + break; + case ZodIssueCode.invalid_string: + if (typeof issue.validation === "object") { + if ("includes" in issue.validation) { + message = `Invalid input: must include "${issue.validation.includes}"`; + if (typeof issue.validation.position === "number") { + message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`; + } + } + else if ("startsWith" in issue.validation) { + message = `Invalid input: must start with "${issue.validation.startsWith}"`; + } + else if ("endsWith" in issue.validation) { + message = `Invalid input: must end with "${issue.validation.endsWith}"`; + } + else { + util.assertNever(issue.validation); + } + } + else if (issue.validation !== "regex") { + message = `Invalid ${issue.validation}`; + } + else { + message = "Invalid"; + } + break; + case ZodIssueCode.too_small: + if (issue.type === "array") + message = `Array must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`; + else if (issue.type === "string") + message = `String must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`; + else if (issue.type === "number") + message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`; + else if (issue.type === "date") + message = `Date must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${new Date(Number(issue.minimum))}`; + else + message = "Invalid input"; + break; + case ZodIssueCode.too_big: + if (issue.type === "array") + message = `Array must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`; + else if (issue.type === "string") + message = `String must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`; + else if (issue.type === "number") + message = `Number must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`; + else if (issue.type === "bigint") + message = `BigInt must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`; + else if (issue.type === "date") + message = `Date must be ${issue.exact ? `exactly` : issue.inclusive ? `smaller than or equal to` : `smaller than`} ${new Date(Number(issue.maximum))}`; + else + message = "Invalid input"; + break; + case ZodIssueCode.custom: + message = `Invalid input`; + break; + case ZodIssueCode.invalid_intersection_types: + message = `Intersection results could not be merged`; + break; + case ZodIssueCode.not_multiple_of: + message = `Number must be a multiple of ${issue.multipleOf}`; + break; + case ZodIssueCode.not_finite: + message = "Number must be finite"; + break; + default: + message = _ctx.defaultError; + util.assertNever(issue); + } + return { message }; +}; +export default errorMap; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/standard-schema.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/standard-schema.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/standard-schema.js @@ -0,0 +1 @@ +export {}; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/types.js b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/types.js new file mode 100644 index 000000000..de1e6213e --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/esm/v3/types.js @@ -0,0 +1,3691 @@ +import { ZodError, ZodIssueCode, } from "./ZodError.js"; +import { defaultErrorMap, getErrorMap } from "./errors.js"; +import { errorUtil } from "./helpers/errorUtil.js"; +import { DIRTY, INVALID, OK, ParseStatus, addIssueToContext, isAborted, isAsync, isDirty, isValid, makeIssue, } from "./helpers/parseUtil.js"; +import { util, ZodParsedType, getParsedType } from "./helpers/util.js"; +class ParseInputLazyPath { + constructor(parent, value, path, key) { + this._cachedPath = []; + this.parent = parent; + this.data = value; + this._path = path; + this._key = key; + } + get path() { + if (!this._cachedPath.length) { + if (Array.isArray(this._key)) { + this._cachedPath.push(...this._path, ...this._key); + } + else { + this._cachedPath.push(...this._path, this._key); + } + } + return this._cachedPath; + } +} +const handleResult = (ctx, result) => { + if (isValid(result)) { + return { success: true, data: result.value }; + } + else { + if (!ctx.common.issues.length) { + throw new Error("Validation failed but no issues detected."); + } + return { + success: false, + get error() { + if (this._error) + return this._error; + const error = new ZodError(ctx.common.issues); + this._error = error; + return this._error; + }, + }; + } +}; +function processCreateParams(params) { + if (!params) + return {}; + const { errorMap, invalid_type_error, required_error, description } = params; + if (errorMap && (invalid_type_error || required_error)) { + throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`); + } + if (errorMap) + return { errorMap: errorMap, description }; + const customMap = (iss, ctx) => { + const { message } = params; + if (iss.code === "invalid_enum_value") { + return { message: message ?? ctx.defaultError }; + } + if (typeof ctx.data === "undefined") { + return { message: message ?? required_error ?? ctx.defaultError }; + } + if (iss.code !== "invalid_type") + return { message: ctx.defaultError }; + return { message: message ?? invalid_type_error ?? ctx.defaultError }; + }; + return { errorMap: customMap, description }; +} +export class ZodType { + get description() { + return this._def.description; + } + _getType(input) { + return getParsedType(input.data); + } + _getOrReturnCtx(input, ctx) { + return (ctx || { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent, + }); + } + _processInputParams(input) { + return { + status: new ParseStatus(), + ctx: { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent, + }, + }; + } + _parseSync(input) { + const result = this._parse(input); + if (isAsync(result)) { + throw new Error("Synchronous parse encountered promise."); + } + return result; + } + _parseAsync(input) { + const result = this._parse(input); + return Promise.resolve(result); + } + parse(data, params) { + const result = this.safeParse(data, params); + if (result.success) + return result.data; + throw result.error; + } + safeParse(data, params) { + const ctx = { + common: { + issues: [], + async: params?.async ?? false, + contextualErrorMap: params?.errorMap, + }, + path: params?.path || [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data), + }; + const result = this._parseSync({ data, path: ctx.path, parent: ctx }); + return handleResult(ctx, result); + } + "~validate"(data) { + const ctx = { + common: { + issues: [], + async: !!this["~standard"].async, + }, + path: [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data), + }; + if (!this["~standard"].async) { + try { + const result = this._parseSync({ data, path: [], parent: ctx }); + return isValid(result) + ? { + value: result.value, + } + : { + issues: ctx.common.issues, + }; + } + catch (err) { + if (err?.message?.toLowerCase()?.includes("encountered")) { + this["~standard"].async = true; + } + ctx.common = { + issues: [], + async: true, + }; + } + } + return this._parseAsync({ data, path: [], parent: ctx }).then((result) => isValid(result) + ? { + value: result.value, + } + : { + issues: ctx.common.issues, + }); + } + async parseAsync(data, params) { + const result = await this.safeParseAsync(data, params); + if (result.success) + return result.data; + throw result.error; + } + async safeParseAsync(data, params) { + const ctx = { + common: { + issues: [], + contextualErrorMap: params?.errorMap, + async: true, + }, + path: params?.path || [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data), + }; + const maybeAsyncResult = this._parse({ data, path: ctx.path, parent: ctx }); + const result = await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult)); + return handleResult(ctx, result); + } + refine(check, message) { + const getIssueProperties = (val) => { + if (typeof message === "string" || typeof message === "undefined") { + return { message }; + } + else if (typeof message === "function") { + return message(val); + } + else { + return message; + } + }; + return this._refinement((val, ctx) => { + const result = check(val); + const setError = () => ctx.addIssue({ + code: ZodIssueCode.custom, + ...getIssueProperties(val), + }); + if (typeof Promise !== "undefined" && result instanceof Promise) { + return result.then((data) => { + if (!data) { + setError(); + return false; + } + else { + return true; + } + }); + } + if (!result) { + setError(); + return false; + } + else { + return true; + } + }); + } + refinement(check, refinementData) { + return this._refinement((val, ctx) => { + if (!check(val)) { + ctx.addIssue(typeof refinementData === "function" ? refinementData(val, ctx) : refinementData); + return false; + } + else { + return true; + } + }); + } + _refinement(refinement) { + return new ZodEffects({ + schema: this, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect: { type: "refinement", refinement }, + }); + } + superRefine(refinement) { + return this._refinement(refinement); + } + constructor(def) { + /** Alias of safeParseAsync */ + this.spa = this.safeParseAsync; + this._def = def; + this.parse = this.parse.bind(this); + this.safeParse = this.safeParse.bind(this); + this.parseAsync = this.parseAsync.bind(this); + this.safeParseAsync = this.safeParseAsync.bind(this); + this.spa = this.spa.bind(this); + this.refine = this.refine.bind(this); + this.refinement = this.refinement.bind(this); + this.superRefine = this.superRefine.bind(this); + this.optional = this.optional.bind(this); + this.nullable = this.nullable.bind(this); + this.nullish = this.nullish.bind(this); + this.array = this.array.bind(this); + this.promise = this.promise.bind(this); + this.or = this.or.bind(this); + this.and = this.and.bind(this); + this.transform = this.transform.bind(this); + this.brand = this.brand.bind(this); + this.default = this.default.bind(this); + this.catch = this.catch.bind(this); + this.describe = this.describe.bind(this); + this.pipe = this.pipe.bind(this); + this.readonly = this.readonly.bind(this); + this.isNullable = this.isNullable.bind(this); + this.isOptional = this.isOptional.bind(this); + this["~standard"] = { + version: 1, + vendor: "zod", + validate: (data) => this["~validate"](data), + }; + } + optional() { + return ZodOptional.create(this, this._def); + } + nullable() { + return ZodNullable.create(this, this._def); + } + nullish() { + return this.nullable().optional(); + } + array() { + return ZodArray.create(this); + } + promise() { + return ZodPromise.create(this, this._def); + } + or(option) { + return ZodUnion.create([this, option], this._def); + } + and(incoming) { + return ZodIntersection.create(this, incoming, this._def); + } + transform(transform) { + return new ZodEffects({ + ...processCreateParams(this._def), + schema: this, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect: { type: "transform", transform }, + }); + } + default(def) { + const defaultValueFunc = typeof def === "function" ? def : () => def; + return new ZodDefault({ + ...processCreateParams(this._def), + innerType: this, + defaultValue: defaultValueFunc, + typeName: ZodFirstPartyTypeKind.ZodDefault, + }); + } + brand() { + return new ZodBranded({ + typeName: ZodFirstPartyTypeKind.ZodBranded, + type: this, + ...processCreateParams(this._def), + }); + } + catch(def) { + const catchValueFunc = typeof def === "function" ? def : () => def; + return new ZodCatch({ + ...processCreateParams(this._def), + innerType: this, + catchValue: catchValueFunc, + typeName: ZodFirstPartyTypeKind.ZodCatch, + }); + } + describe(description) { + const This = this.constructor; + return new This({ + ...this._def, + description, + }); + } + pipe(target) { + return ZodPipeline.create(this, target); + } + readonly() { + return ZodReadonly.create(this); + } + isOptional() { + return this.safeParse(undefined).success; + } + isNullable() { + return this.safeParse(null).success; + } +} +const cuidRegex = /^c[^\s-]{8,}$/i; +const cuid2Regex = /^[0-9a-z]+$/; +const ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/i; +// const uuidRegex = +// /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; +const uuidRegex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i; +const nanoidRegex = /^[a-z0-9_-]{21}$/i; +const jwtRegex = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/; +const durationRegex = /^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/; +// from https://stackoverflow.com/a/46181/1550155 +// old version: too slow, didn't support unicode +// const emailRegex = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i; +//old email regex +// const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i; +// eslint-disable-next-line +// const emailRegex = +// /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\])|(\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))\])|([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\.[A-Za-z]{2,})+))$/; +// const emailRegex = +// /^[a-zA-Z0-9\.\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; +// const emailRegex = +// /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i; +const emailRegex = /^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; +// const emailRegex = +// /^[a-z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9\-]+)*$/i; +// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression +const _emojiRegex = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`; +let emojiRegex; +// faster, simpler, safer +const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/; +const ipv4CidrRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/(3[0-2]|[12]?[0-9])$/; +// const ipv6Regex = +// /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/; +const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; +const ipv6CidrRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/; +// https://stackoverflow.com/questions/7860392/determine-if-string-is-in-base64-using-javascript +const base64Regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/; +// https://base64.guru/standards/base64url +const base64urlRegex = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/; +// simple +// const dateRegexSource = `\\d{4}-\\d{2}-\\d{2}`; +// no leap year validation +// const dateRegexSource = `\\d{4}-((0[13578]|10|12)-31|(0[13-9]|1[0-2])-30|(0[1-9]|1[0-2])-(0[1-9]|1\\d|2\\d))`; +// with leap year validation +const dateRegexSource = `((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))`; +const dateRegex = new RegExp(`^${dateRegexSource}$`); +function timeRegexSource(args) { + let secondsRegexSource = `[0-5]\\d`; + if (args.precision) { + secondsRegexSource = `${secondsRegexSource}\\.\\d{${args.precision}}`; + } + else if (args.precision == null) { + secondsRegexSource = `${secondsRegexSource}(\\.\\d+)?`; + } + const secondsQuantifier = args.precision ? "+" : "?"; // require seconds if precision is nonzero + return `([01]\\d|2[0-3]):[0-5]\\d(:${secondsRegexSource})${secondsQuantifier}`; +} +function timeRegex(args) { + return new RegExp(`^${timeRegexSource(args)}$`); +} +// Adapted from https://stackoverflow.com/a/3143231 +export function datetimeRegex(args) { + let regex = `${dateRegexSource}T${timeRegexSource(args)}`; + const opts = []; + opts.push(args.local ? `Z?` : `Z`); + if (args.offset) + opts.push(`([+-]\\d{2}:?\\d{2})`); + regex = `${regex}(${opts.join("|")})`; + return new RegExp(`^${regex}$`); +} +function isValidIP(ip, version) { + if ((version === "v4" || !version) && ipv4Regex.test(ip)) { + return true; + } + if ((version === "v6" || !version) && ipv6Regex.test(ip)) { + return true; + } + return false; +} +function isValidJWT(jwt, alg) { + if (!jwtRegex.test(jwt)) + return false; + try { + const [header] = jwt.split("."); + // Convert base64url to base64 + const base64 = header + .replace(/-/g, "+") + .replace(/_/g, "/") + .padEnd(header.length + ((4 - (header.length % 4)) % 4), "="); + const decoded = JSON.parse(atob(base64)); + if (typeof decoded !== "object" || decoded === null) + return false; + if ("typ" in decoded && decoded?.typ !== "JWT") + return false; + if (!decoded.alg) + return false; + if (alg && decoded.alg !== alg) + return false; + return true; + } + catch { + return false; + } +} +function isValidCidr(ip, version) { + if ((version === "v4" || !version) && ipv4CidrRegex.test(ip)) { + return true; + } + if ((version === "v6" || !version) && ipv6CidrRegex.test(ip)) { + return true; + } + return false; +} +export class ZodString extends ZodType { + _parse(input) { + if (this._def.coerce) { + input.data = String(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.string) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.string, + received: ctx.parsedType, + }); + return INVALID; + } + const status = new ParseStatus(); + let ctx = undefined; + for (const check of this._def.checks) { + if (check.kind === "min") { + if (input.data.length < check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "string", + inclusive: true, + exact: false, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "max") { + if (input.data.length > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "string", + inclusive: true, + exact: false, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "length") { + const tooBig = input.data.length > check.value; + const tooSmall = input.data.length < check.value; + if (tooBig || tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + if (tooBig) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "string", + inclusive: true, + exact: true, + message: check.message, + }); + } + else if (tooSmall) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "string", + inclusive: true, + exact: true, + message: check.message, + }); + } + status.dirty(); + } + } + else if (check.kind === "email") { + if (!emailRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "email", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "emoji") { + if (!emojiRegex) { + emojiRegex = new RegExp(_emojiRegex, "u"); + } + if (!emojiRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "emoji", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "uuid") { + if (!uuidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "uuid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "nanoid") { + if (!nanoidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "nanoid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "cuid") { + if (!cuidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "cuid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "cuid2") { + if (!cuid2Regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "cuid2", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "ulid") { + if (!ulidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "ulid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "url") { + try { + new URL(input.data); + } + catch { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "url", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "regex") { + check.regex.lastIndex = 0; + const testResult = check.regex.test(input.data); + if (!testResult) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "regex", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "trim") { + input.data = input.data.trim(); + } + else if (check.kind === "includes") { + if (!input.data.includes(check.value, check.position)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { includes: check.value, position: check.position }, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "toLowerCase") { + input.data = input.data.toLowerCase(); + } + else if (check.kind === "toUpperCase") { + input.data = input.data.toUpperCase(); + } + else if (check.kind === "startsWith") { + if (!input.data.startsWith(check.value)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { startsWith: check.value }, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "endsWith") { + if (!input.data.endsWith(check.value)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { endsWith: check.value }, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "datetime") { + const regex = datetimeRegex(check); + if (!regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: "datetime", + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "date") { + const regex = dateRegex; + if (!regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: "date", + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "time") { + const regex = timeRegex(check); + if (!regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: "time", + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "duration") { + if (!durationRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "duration", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "ip") { + if (!isValidIP(input.data, check.version)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "ip", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "jwt") { + if (!isValidJWT(input.data, check.alg)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "jwt", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "cidr") { + if (!isValidCidr(input.data, check.version)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "cidr", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "base64") { + if (!base64Regex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "base64", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "base64url") { + if (!base64urlRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "base64url", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } + else { + util.assertNever(check); + } + } + return { status: status.value, value: input.data }; + } + _regex(regex, validation, message) { + return this.refinement((data) => regex.test(data), { + validation, + code: ZodIssueCode.invalid_string, + ...errorUtil.errToObj(message), + }); + } + _addCheck(check) { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + email(message) { + return this._addCheck({ kind: "email", ...errorUtil.errToObj(message) }); + } + url(message) { + return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) }); + } + emoji(message) { + return this._addCheck({ kind: "emoji", ...errorUtil.errToObj(message) }); + } + uuid(message) { + return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) }); + } + nanoid(message) { + return this._addCheck({ kind: "nanoid", ...errorUtil.errToObj(message) }); + } + cuid(message) { + return this._addCheck({ kind: "cuid", ...errorUtil.errToObj(message) }); + } + cuid2(message) { + return this._addCheck({ kind: "cuid2", ...errorUtil.errToObj(message) }); + } + ulid(message) { + return this._addCheck({ kind: "ulid", ...errorUtil.errToObj(message) }); + } + base64(message) { + return this._addCheck({ kind: "base64", ...errorUtil.errToObj(message) }); + } + base64url(message) { + // base64url encoding is a modification of base64 that can safely be used in URLs and filenames + return this._addCheck({ + kind: "base64url", + ...errorUtil.errToObj(message), + }); + } + jwt(options) { + return this._addCheck({ kind: "jwt", ...errorUtil.errToObj(options) }); + } + ip(options) { + return this._addCheck({ kind: "ip", ...errorUtil.errToObj(options) }); + } + cidr(options) { + return this._addCheck({ kind: "cidr", ...errorUtil.errToObj(options) }); + } + datetime(options) { + if (typeof options === "string") { + return this._addCheck({ + kind: "datetime", + precision: null, + offset: false, + local: false, + message: options, + }); + } + return this._addCheck({ + kind: "datetime", + precision: typeof options?.precision === "undefined" ? null : options?.precision, + offset: options?.offset ?? false, + local: options?.local ?? false, + ...errorUtil.errToObj(options?.message), + }); + } + date(message) { + return this._addCheck({ kind: "date", message }); + } + time(options) { + if (typeof options === "string") { + return this._addCheck({ + kind: "time", + precision: null, + message: options, + }); + } + return this._addCheck({ + kind: "time", + precision: typeof options?.precision === "undefined" ? null : options?.precision, + ...errorUtil.errToObj(options?.message), + }); + } + duration(message) { + return this._addCheck({ kind: "duration", ...errorUtil.errToObj(message) }); + } + regex(regex, message) { + return this._addCheck({ + kind: "regex", + regex: regex, + ...errorUtil.errToObj(message), + }); + } + includes(value, options) { + return this._addCheck({ + kind: "includes", + value: value, + position: options?.position, + ...errorUtil.errToObj(options?.message), + }); + } + startsWith(value, message) { + return this._addCheck({ + kind: "startsWith", + value: value, + ...errorUtil.errToObj(message), + }); + } + endsWith(value, message) { + return this._addCheck({ + kind: "endsWith", + value: value, + ...errorUtil.errToObj(message), + }); + } + min(minLength, message) { + return this._addCheck({ + kind: "min", + value: minLength, + ...errorUtil.errToObj(message), + }); + } + max(maxLength, message) { + return this._addCheck({ + kind: "max", + value: maxLength, + ...errorUtil.errToObj(message), + }); + } + length(len, message) { + return this._addCheck({ + kind: "length", + value: len, + ...errorUtil.errToObj(message), + }); + } + /** + * Equivalent to `.min(1)` + */ + nonempty(message) { + return this.min(1, errorUtil.errToObj(message)); + } + trim() { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "trim" }], + }); + } + toLowerCase() { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "toLowerCase" }], + }); + } + toUpperCase() { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "toUpperCase" }], + }); + } + get isDatetime() { + return !!this._def.checks.find((ch) => ch.kind === "datetime"); + } + get isDate() { + return !!this._def.checks.find((ch) => ch.kind === "date"); + } + get isTime() { + return !!this._def.checks.find((ch) => ch.kind === "time"); + } + get isDuration() { + return !!this._def.checks.find((ch) => ch.kind === "duration"); + } + get isEmail() { + return !!this._def.checks.find((ch) => ch.kind === "email"); + } + get isURL() { + return !!this._def.checks.find((ch) => ch.kind === "url"); + } + get isEmoji() { + return !!this._def.checks.find((ch) => ch.kind === "emoji"); + } + get isUUID() { + return !!this._def.checks.find((ch) => ch.kind === "uuid"); + } + get isNANOID() { + return !!this._def.checks.find((ch) => ch.kind === "nanoid"); + } + get isCUID() { + return !!this._def.checks.find((ch) => ch.kind === "cuid"); + } + get isCUID2() { + return !!this._def.checks.find((ch) => ch.kind === "cuid2"); + } + get isULID() { + return !!this._def.checks.find((ch) => ch.kind === "ulid"); + } + get isIP() { + return !!this._def.checks.find((ch) => ch.kind === "ip"); + } + get isCIDR() { + return !!this._def.checks.find((ch) => ch.kind === "cidr"); + } + get isBase64() { + return !!this._def.checks.find((ch) => ch.kind === "base64"); + } + get isBase64url() { + // base64url encoding is a modification of base64 that can safely be used in URLs and filenames + return !!this._def.checks.find((ch) => ch.kind === "base64url"); + } + get minLength() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min; + } + get maxLength() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max; + } +} +ZodString.create = (params) => { + return new ZodString({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodString, + coerce: params?.coerce ?? false, + ...processCreateParams(params), + }); +}; +// https://stackoverflow.com/questions/3966484/why-does-modulus-operator-return-fractional-number-in-javascript/31711034#31711034 +function floatSafeRemainder(val, step) { + const valDecCount = (val.toString().split(".")[1] || "").length; + const stepDecCount = (step.toString().split(".")[1] || "").length; + const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount; + const valInt = Number.parseInt(val.toFixed(decCount).replace(".", "")); + const stepInt = Number.parseInt(step.toFixed(decCount).replace(".", "")); + return (valInt % stepInt) / 10 ** decCount; +} +export class ZodNumber extends ZodType { + constructor() { + super(...arguments); + this.min = this.gte; + this.max = this.lte; + this.step = this.multipleOf; + } + _parse(input) { + if (this._def.coerce) { + input.data = Number(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.number) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.number, + received: ctx.parsedType, + }); + return INVALID; + } + let ctx = undefined; + const status = new ParseStatus(); + for (const check of this._def.checks) { + if (check.kind === "int") { + if (!util.isInteger(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: "integer", + received: "float", + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "min") { + const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value; + if (tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "number", + inclusive: check.inclusive, + exact: false, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "max") { + const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value; + if (tooBig) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "number", + inclusive: check.inclusive, + exact: false, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "multipleOf") { + if (floatSafeRemainder(input.data, check.value) !== 0) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_multiple_of, + multipleOf: check.value, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "finite") { + if (!Number.isFinite(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_finite, + message: check.message, + }); + status.dirty(); + } + } + else { + util.assertNever(check); + } + } + return { status: status.value, value: input.data }; + } + gte(value, message) { + return this.setLimit("min", value, true, errorUtil.toString(message)); + } + gt(value, message) { + return this.setLimit("min", value, false, errorUtil.toString(message)); + } + lte(value, message) { + return this.setLimit("max", value, true, errorUtil.toString(message)); + } + lt(value, message) { + return this.setLimit("max", value, false, errorUtil.toString(message)); + } + setLimit(kind, value, inclusive, message) { + return new ZodNumber({ + ...this._def, + checks: [ + ...this._def.checks, + { + kind, + value, + inclusive, + message: errorUtil.toString(message), + }, + ], + }); + } + _addCheck(check) { + return new ZodNumber({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + int(message) { + return this._addCheck({ + kind: "int", + message: errorUtil.toString(message), + }); + } + positive(message) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: false, + message: errorUtil.toString(message), + }); + } + negative(message) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: false, + message: errorUtil.toString(message), + }); + } + nonpositive(message) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: true, + message: errorUtil.toString(message), + }); + } + nonnegative(message) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: true, + message: errorUtil.toString(message), + }); + } + multipleOf(value, message) { + return this._addCheck({ + kind: "multipleOf", + value: value, + message: errorUtil.toString(message), + }); + } + finite(message) { + return this._addCheck({ + kind: "finite", + message: errorUtil.toString(message), + }); + } + safe(message) { + return this._addCheck({ + kind: "min", + inclusive: true, + value: Number.MIN_SAFE_INTEGER, + message: errorUtil.toString(message), + })._addCheck({ + kind: "max", + inclusive: true, + value: Number.MAX_SAFE_INTEGER, + message: errorUtil.toString(message), + }); + } + get minValue() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min; + } + get maxValue() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max; + } + get isInt() { + return !!this._def.checks.find((ch) => ch.kind === "int" || (ch.kind === "multipleOf" && util.isInteger(ch.value))); + } + get isFinite() { + let max = null; + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "finite" || ch.kind === "int" || ch.kind === "multipleOf") { + return true; + } + else if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + else if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return Number.isFinite(min) && Number.isFinite(max); + } +} +ZodNumber.create = (params) => { + return new ZodNumber({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodNumber, + coerce: params?.coerce || false, + ...processCreateParams(params), + }); +}; +export class ZodBigInt extends ZodType { + constructor() { + super(...arguments); + this.min = this.gte; + this.max = this.lte; + } + _parse(input) { + if (this._def.coerce) { + try { + input.data = BigInt(input.data); + } + catch { + return this._getInvalidInput(input); + } + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.bigint) { + return this._getInvalidInput(input); + } + let ctx = undefined; + const status = new ParseStatus(); + for (const check of this._def.checks) { + if (check.kind === "min") { + const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value; + if (tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + type: "bigint", + minimum: check.value, + inclusive: check.inclusive, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "max") { + const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value; + if (tooBig) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + type: "bigint", + maximum: check.value, + inclusive: check.inclusive, + message: check.message, + }); + status.dirty(); + } + } + else if (check.kind === "multipleOf") { + if (input.data % check.value !== BigInt(0)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_multiple_of, + multipleOf: check.value, + message: check.message, + }); + status.dirty(); + } + } + else { + util.assertNever(check); + } + } + return { status: status.value, value: input.data }; + } + _getInvalidInput(input) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.bigint, + received: ctx.parsedType, + }); + return INVALID; + } + gte(value, message) { + return this.setLimit("min", value, true, errorUtil.toString(message)); + } + gt(value, message) { + return this.setLimit("min", value, false, errorUtil.toString(message)); + } + lte(value, message) { + return this.setLimit("max", value, true, errorUtil.toString(message)); + } + lt(value, message) { + return this.setLimit("max", value, false, errorUtil.toString(message)); + } + setLimit(kind, value, inclusive, message) { + return new ZodBigInt({ + ...this._def, + checks: [ + ...this._def.checks, + { + kind, + value, + inclusive, + message: errorUtil.toString(message), + }, + ], + }); + } + _addCheck(check) { + return new ZodBigInt({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + positive(message) { + return this._addCheck({ + kind: "min", + value: BigInt(0), + inclusive: false, + message: errorUtil.toString(message), + }); + } + negative(message) { + return this._addCheck({ + kind: "max", + value: BigInt(0), + inclusive: false, + message: errorUtil.toString(message), + }); + } + nonpositive(message) { + return this._addCheck({ + kind: "max", + value: BigInt(0), + inclusive: true, + message: errorUtil.toString(message), + }); + } + nonnegative(message) { + return this._addCheck({ + kind: "min", + value: BigInt(0), + inclusive: true, + message: errorUtil.toString(message), + }); + } + multipleOf(value, message) { + return this._addCheck({ + kind: "multipleOf", + value, + message: errorUtil.toString(message), + }); + } + get minValue() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min; + } + get maxValue() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max; + } +} +ZodBigInt.create = (params) => { + return new ZodBigInt({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodBigInt, + coerce: params?.coerce ?? false, + ...processCreateParams(params), + }); +}; +export class ZodBoolean extends ZodType { + _parse(input) { + if (this._def.coerce) { + input.data = Boolean(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.boolean) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.boolean, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } +} +ZodBoolean.create = (params) => { + return new ZodBoolean({ + typeName: ZodFirstPartyTypeKind.ZodBoolean, + coerce: params?.coerce || false, + ...processCreateParams(params), + }); +}; +export class ZodDate extends ZodType { + _parse(input) { + if (this._def.coerce) { + input.data = new Date(input.data); + } + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.date) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.date, + received: ctx.parsedType, + }); + return INVALID; + } + if (Number.isNaN(input.data.getTime())) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_date, + }); + return INVALID; + } + const status = new ParseStatus(); + let ctx = undefined; + for (const check of this._def.checks) { + if (check.kind === "min") { + if (input.data.getTime() < check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + message: check.message, + inclusive: true, + exact: false, + minimum: check.value, + type: "date", + }); + status.dirty(); + } + } + else if (check.kind === "max") { + if (input.data.getTime() > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + message: check.message, + inclusive: true, + exact: false, + maximum: check.value, + type: "date", + }); + status.dirty(); + } + } + else { + util.assertNever(check); + } + } + return { + status: status.value, + value: new Date(input.data.getTime()), + }; + } + _addCheck(check) { + return new ZodDate({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + min(minDate, message) { + return this._addCheck({ + kind: "min", + value: minDate.getTime(), + message: errorUtil.toString(message), + }); + } + max(maxDate, message) { + return this._addCheck({ + kind: "max", + value: maxDate.getTime(), + message: errorUtil.toString(message), + }); + } + get minDate() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min != null ? new Date(min) : null; + } + get maxDate() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max != null ? new Date(max) : null; + } +} +ZodDate.create = (params) => { + return new ZodDate({ + checks: [], + coerce: params?.coerce || false, + typeName: ZodFirstPartyTypeKind.ZodDate, + ...processCreateParams(params), + }); +}; +export class ZodSymbol extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.symbol) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.symbol, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } +} +ZodSymbol.create = (params) => { + return new ZodSymbol({ + typeName: ZodFirstPartyTypeKind.ZodSymbol, + ...processCreateParams(params), + }); +}; +export class ZodUndefined extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.undefined) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.undefined, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } +} +ZodUndefined.create = (params) => { + return new ZodUndefined({ + typeName: ZodFirstPartyTypeKind.ZodUndefined, + ...processCreateParams(params), + }); +}; +export class ZodNull extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.null) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.null, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } +} +ZodNull.create = (params) => { + return new ZodNull({ + typeName: ZodFirstPartyTypeKind.ZodNull, + ...processCreateParams(params), + }); +}; +export class ZodAny extends ZodType { + constructor() { + super(...arguments); + // to prevent instances of other classes from extending ZodAny. this causes issues with catchall in ZodObject. + this._any = true; + } + _parse(input) { + return OK(input.data); + } +} +ZodAny.create = (params) => { + return new ZodAny({ + typeName: ZodFirstPartyTypeKind.ZodAny, + ...processCreateParams(params), + }); +}; +export class ZodUnknown extends ZodType { + constructor() { + super(...arguments); + // required + this._unknown = true; + } + _parse(input) { + return OK(input.data); + } +} +ZodUnknown.create = (params) => { + return new ZodUnknown({ + typeName: ZodFirstPartyTypeKind.ZodUnknown, + ...processCreateParams(params), + }); +}; +export class ZodNever extends ZodType { + _parse(input) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.never, + received: ctx.parsedType, + }); + return INVALID; + } +} +ZodNever.create = (params) => { + return new ZodNever({ + typeName: ZodFirstPartyTypeKind.ZodNever, + ...processCreateParams(params), + }); +}; +export class ZodVoid extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.undefined) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.void, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } +} +ZodVoid.create = (params) => { + return new ZodVoid({ + typeName: ZodFirstPartyTypeKind.ZodVoid, + ...processCreateParams(params), + }); +}; +export class ZodArray extends ZodType { + _parse(input) { + const { ctx, status } = this._processInputParams(input); + const def = this._def; + if (ctx.parsedType !== ZodParsedType.array) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.array, + received: ctx.parsedType, + }); + return INVALID; + } + if (def.exactLength !== null) { + const tooBig = ctx.data.length > def.exactLength.value; + const tooSmall = ctx.data.length < def.exactLength.value; + if (tooBig || tooSmall) { + addIssueToContext(ctx, { + code: tooBig ? ZodIssueCode.too_big : ZodIssueCode.too_small, + minimum: (tooSmall ? def.exactLength.value : undefined), + maximum: (tooBig ? def.exactLength.value : undefined), + type: "array", + inclusive: true, + exact: true, + message: def.exactLength.message, + }); + status.dirty(); + } + } + if (def.minLength !== null) { + if (ctx.data.length < def.minLength.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: def.minLength.value, + type: "array", + inclusive: true, + exact: false, + message: def.minLength.message, + }); + status.dirty(); + } + } + if (def.maxLength !== null) { + if (ctx.data.length > def.maxLength.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: def.maxLength.value, + type: "array", + inclusive: true, + exact: false, + message: def.maxLength.message, + }); + status.dirty(); + } + } + if (ctx.common.async) { + return Promise.all([...ctx.data].map((item, i) => { + return def.type._parseAsync(new ParseInputLazyPath(ctx, item, ctx.path, i)); + })).then((result) => { + return ParseStatus.mergeArray(status, result); + }); + } + const result = [...ctx.data].map((item, i) => { + return def.type._parseSync(new ParseInputLazyPath(ctx, item, ctx.path, i)); + }); + return ParseStatus.mergeArray(status, result); + } + get element() { + return this._def.type; + } + min(minLength, message) { + return new ZodArray({ + ...this._def, + minLength: { value: minLength, message: errorUtil.toString(message) }, + }); + } + max(maxLength, message) { + return new ZodArray({ + ...this._def, + maxLength: { value: maxLength, message: errorUtil.toString(message) }, + }); + } + length(len, message) { + return new ZodArray({ + ...this._def, + exactLength: { value: len, message: errorUtil.toString(message) }, + }); + } + nonempty(message) { + return this.min(1, message); + } +} +ZodArray.create = (schema, params) => { + return new ZodArray({ + type: schema, + minLength: null, + maxLength: null, + exactLength: null, + typeName: ZodFirstPartyTypeKind.ZodArray, + ...processCreateParams(params), + }); +}; +function deepPartialify(schema) { + if (schema instanceof ZodObject) { + const newShape = {}; + for (const key in schema.shape) { + const fieldSchema = schema.shape[key]; + newShape[key] = ZodOptional.create(deepPartialify(fieldSchema)); + } + return new ZodObject({ + ...schema._def, + shape: () => newShape, + }); + } + else if (schema instanceof ZodArray) { + return new ZodArray({ + ...schema._def, + type: deepPartialify(schema.element), + }); + } + else if (schema instanceof ZodOptional) { + return ZodOptional.create(deepPartialify(schema.unwrap())); + } + else if (schema instanceof ZodNullable) { + return ZodNullable.create(deepPartialify(schema.unwrap())); + } + else if (schema instanceof ZodTuple) { + return ZodTuple.create(schema.items.map((item) => deepPartialify(item))); + } + else { + return schema; + } +} +export class ZodObject extends ZodType { + constructor() { + super(...arguments); + this._cached = null; + /** + * @deprecated In most cases, this is no longer needed - unknown properties are now silently stripped. + * If you want to pass through unknown properties, use `.passthrough()` instead. + */ + this.nonstrict = this.passthrough; + // extend< + // Augmentation extends ZodRawShape, + // NewOutput extends util.flatten<{ + // [k in keyof Augmentation | keyof Output]: k extends keyof Augmentation + // ? Augmentation[k]["_output"] + // : k extends keyof Output + // ? Output[k] + // : never; + // }>, + // NewInput extends util.flatten<{ + // [k in keyof Augmentation | keyof Input]: k extends keyof Augmentation + // ? Augmentation[k]["_input"] + // : k extends keyof Input + // ? Input[k] + // : never; + // }> + // >( + // augmentation: Augmentation + // ): ZodObject< + // extendShape, + // UnknownKeys, + // Catchall, + // NewOutput, + // NewInput + // > { + // return new ZodObject({ + // ...this._def, + // shape: () => ({ + // ...this._def.shape(), + // ...augmentation, + // }), + // }) as any; + // } + /** + * @deprecated Use `.extend` instead + * */ + this.augment = this.extend; + } + _getCached() { + if (this._cached !== null) + return this._cached; + const shape = this._def.shape(); + const keys = util.objectKeys(shape); + this._cached = { shape, keys }; + return this._cached; + } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.object) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType, + }); + return INVALID; + } + const { status, ctx } = this._processInputParams(input); + const { shape, keys: shapeKeys } = this._getCached(); + const extraKeys = []; + if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === "strip")) { + for (const key in ctx.data) { + if (!shapeKeys.includes(key)) { + extraKeys.push(key); + } + } + } + const pairs = []; + for (const key of shapeKeys) { + const keyValidator = shape[key]; + const value = ctx.data[key]; + pairs.push({ + key: { status: "valid", value: key }, + value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path, key)), + alwaysSet: key in ctx.data, + }); + } + if (this._def.catchall instanceof ZodNever) { + const unknownKeys = this._def.unknownKeys; + if (unknownKeys === "passthrough") { + for (const key of extraKeys) { + pairs.push({ + key: { status: "valid", value: key }, + value: { status: "valid", value: ctx.data[key] }, + }); + } + } + else if (unknownKeys === "strict") { + if (extraKeys.length > 0) { + addIssueToContext(ctx, { + code: ZodIssueCode.unrecognized_keys, + keys: extraKeys, + }); + status.dirty(); + } + } + else if (unknownKeys === "strip") { + } + else { + throw new Error(`Internal ZodObject error: invalid unknownKeys value.`); + } + } + else { + // run catchall validation + const catchall = this._def.catchall; + for (const key of extraKeys) { + const value = ctx.data[key]; + pairs.push({ + key: { status: "valid", value: key }, + value: catchall._parse(new ParseInputLazyPath(ctx, value, ctx.path, key) //, ctx.child(key), value, getParsedType(value) + ), + alwaysSet: key in ctx.data, + }); + } + } + if (ctx.common.async) { + return Promise.resolve() + .then(async () => { + const syncPairs = []; + for (const pair of pairs) { + const key = await pair.key; + const value = await pair.value; + syncPairs.push({ + key, + value, + alwaysSet: pair.alwaysSet, + }); + } + return syncPairs; + }) + .then((syncPairs) => { + return ParseStatus.mergeObjectSync(status, syncPairs); + }); + } + else { + return ParseStatus.mergeObjectSync(status, pairs); + } + } + get shape() { + return this._def.shape(); + } + strict(message) { + errorUtil.errToObj; + return new ZodObject({ + ...this._def, + unknownKeys: "strict", + ...(message !== undefined + ? { + errorMap: (issue, ctx) => { + const defaultError = this._def.errorMap?.(issue, ctx).message ?? ctx.defaultError; + if (issue.code === "unrecognized_keys") + return { + message: errorUtil.errToObj(message).message ?? defaultError, + }; + return { + message: defaultError, + }; + }, + } + : {}), + }); + } + strip() { + return new ZodObject({ + ...this._def, + unknownKeys: "strip", + }); + } + passthrough() { + return new ZodObject({ + ...this._def, + unknownKeys: "passthrough", + }); + } + // const AugmentFactory = + // (def: Def) => + // ( + // augmentation: Augmentation + // ): ZodObject< + // extendShape, Augmentation>, + // Def["unknownKeys"], + // Def["catchall"] + // > => { + // return new ZodObject({ + // ...def, + // shape: () => ({ + // ...def.shape(), + // ...augmentation, + // }), + // }) as any; + // }; + extend(augmentation) { + return new ZodObject({ + ...this._def, + shape: () => ({ + ...this._def.shape(), + ...augmentation, + }), + }); + } + /** + * Prior to zod@1.0.12 there was a bug in the + * inferred type of merged objects. Please + * upgrade if you are experiencing issues. + */ + merge(merging) { + const merged = new ZodObject({ + unknownKeys: merging._def.unknownKeys, + catchall: merging._def.catchall, + shape: () => ({ + ...this._def.shape(), + ...merging._def.shape(), + }), + typeName: ZodFirstPartyTypeKind.ZodObject, + }); + return merged; + } + // merge< + // Incoming extends AnyZodObject, + // Augmentation extends Incoming["shape"], + // NewOutput extends { + // [k in keyof Augmentation | keyof Output]: k extends keyof Augmentation + // ? Augmentation[k]["_output"] + // : k extends keyof Output + // ? Output[k] + // : never; + // }, + // NewInput extends { + // [k in keyof Augmentation | keyof Input]: k extends keyof Augmentation + // ? Augmentation[k]["_input"] + // : k extends keyof Input + // ? Input[k] + // : never; + // } + // >( + // merging: Incoming + // ): ZodObject< + // extendShape>, + // Incoming["_def"]["unknownKeys"], + // Incoming["_def"]["catchall"], + // NewOutput, + // NewInput + // > { + // const merged: any = new ZodObject({ + // unknownKeys: merging._def.unknownKeys, + // catchall: merging._def.catchall, + // shape: () => + // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), + // typeName: ZodFirstPartyTypeKind.ZodObject, + // }) as any; + // return merged; + // } + setKey(key, schema) { + return this.augment({ [key]: schema }); + } + // merge( + // merging: Incoming + // ): //ZodObject = (merging) => { + // ZodObject< + // extendShape>, + // Incoming["_def"]["unknownKeys"], + // Incoming["_def"]["catchall"] + // > { + // // const mergedShape = objectUtil.mergeShapes( + // // this._def.shape(), + // // merging._def.shape() + // // ); + // const merged: any = new ZodObject({ + // unknownKeys: merging._def.unknownKeys, + // catchall: merging._def.catchall, + // shape: () => + // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), + // typeName: ZodFirstPartyTypeKind.ZodObject, + // }) as any; + // return merged; + // } + catchall(index) { + return new ZodObject({ + ...this._def, + catchall: index, + }); + } + pick(mask) { + const shape = {}; + for (const key of util.objectKeys(mask)) { + if (mask[key] && this.shape[key]) { + shape[key] = this.shape[key]; + } + } + return new ZodObject({ + ...this._def, + shape: () => shape, + }); + } + omit(mask) { + const shape = {}; + for (const key of util.objectKeys(this.shape)) { + if (!mask[key]) { + shape[key] = this.shape[key]; + } + } + return new ZodObject({ + ...this._def, + shape: () => shape, + }); + } + /** + * @deprecated + */ + deepPartial() { + return deepPartialify(this); + } + partial(mask) { + const newShape = {}; + for (const key of util.objectKeys(this.shape)) { + const fieldSchema = this.shape[key]; + if (mask && !mask[key]) { + newShape[key] = fieldSchema; + } + else { + newShape[key] = fieldSchema.optional(); + } + } + return new ZodObject({ + ...this._def, + shape: () => newShape, + }); + } + required(mask) { + const newShape = {}; + for (const key of util.objectKeys(this.shape)) { + if (mask && !mask[key]) { + newShape[key] = this.shape[key]; + } + else { + const fieldSchema = this.shape[key]; + let newField = fieldSchema; + while (newField instanceof ZodOptional) { + newField = newField._def.innerType; + } + newShape[key] = newField; + } + } + return new ZodObject({ + ...this._def, + shape: () => newShape, + }); + } + keyof() { + return createZodEnum(util.objectKeys(this.shape)); + } +} +ZodObject.create = (shape, params) => { + return new ZodObject({ + shape: () => shape, + unknownKeys: "strip", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params), + }); +}; +ZodObject.strictCreate = (shape, params) => { + return new ZodObject({ + shape: () => shape, + unknownKeys: "strict", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params), + }); +}; +ZodObject.lazycreate = (shape, params) => { + return new ZodObject({ + shape, + unknownKeys: "strip", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params), + }); +}; +export class ZodUnion extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + const options = this._def.options; + function handleResults(results) { + // return first issue-free validation if it exists + for (const result of results) { + if (result.result.status === "valid") { + return result.result; + } + } + for (const result of results) { + if (result.result.status === "dirty") { + // add issues from dirty option + ctx.common.issues.push(...result.ctx.common.issues); + return result.result; + } + } + // return invalid + const unionErrors = results.map((result) => new ZodError(result.ctx.common.issues)); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union, + unionErrors, + }); + return INVALID; + } + if (ctx.common.async) { + return Promise.all(options.map(async (option) => { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [], + }, + parent: null, + }; + return { + result: await option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: childCtx, + }), + ctx: childCtx, + }; + })).then(handleResults); + } + else { + let dirty = undefined; + const issues = []; + for (const option of options) { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [], + }, + parent: null, + }; + const result = option._parseSync({ + data: ctx.data, + path: ctx.path, + parent: childCtx, + }); + if (result.status === "valid") { + return result; + } + else if (result.status === "dirty" && !dirty) { + dirty = { result, ctx: childCtx }; + } + if (childCtx.common.issues.length) { + issues.push(childCtx.common.issues); + } + } + if (dirty) { + ctx.common.issues.push(...dirty.ctx.common.issues); + return dirty.result; + } + const unionErrors = issues.map((issues) => new ZodError(issues)); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union, + unionErrors, + }); + return INVALID; + } + } + get options() { + return this._def.options; + } +} +ZodUnion.create = (types, params) => { + return new ZodUnion({ + options: types, + typeName: ZodFirstPartyTypeKind.ZodUnion, + ...processCreateParams(params), + }); +}; +///////////////////////////////////////////////////// +///////////////////////////////////////////////////// +////////// ////////// +////////// ZodDiscriminatedUnion ////////// +////////// ////////// +///////////////////////////////////////////////////// +///////////////////////////////////////////////////// +const getDiscriminator = (type) => { + if (type instanceof ZodLazy) { + return getDiscriminator(type.schema); + } + else if (type instanceof ZodEffects) { + return getDiscriminator(type.innerType()); + } + else if (type instanceof ZodLiteral) { + return [type.value]; + } + else if (type instanceof ZodEnum) { + return type.options; + } + else if (type instanceof ZodNativeEnum) { + // eslint-disable-next-line ban/ban + return util.objectValues(type.enum); + } + else if (type instanceof ZodDefault) { + return getDiscriminator(type._def.innerType); + } + else if (type instanceof ZodUndefined) { + return [undefined]; + } + else if (type instanceof ZodNull) { + return [null]; + } + else if (type instanceof ZodOptional) { + return [undefined, ...getDiscriminator(type.unwrap())]; + } + else if (type instanceof ZodNullable) { + return [null, ...getDiscriminator(type.unwrap())]; + } + else if (type instanceof ZodBranded) { + return getDiscriminator(type.unwrap()); + } + else if (type instanceof ZodReadonly) { + return getDiscriminator(type.unwrap()); + } + else if (type instanceof ZodCatch) { + return getDiscriminator(type._def.innerType); + } + else { + return []; + } +}; +export class ZodDiscriminatedUnion extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.object) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType, + }); + return INVALID; + } + const discriminator = this.discriminator; + const discriminatorValue = ctx.data[discriminator]; + const option = this.optionsMap.get(discriminatorValue); + if (!option) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union_discriminator, + options: Array.from(this.optionsMap.keys()), + path: [discriminator], + }); + return INVALID; + } + if (ctx.common.async) { + return option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + } + else { + return option._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + } + } + get discriminator() { + return this._def.discriminator; + } + get options() { + return this._def.options; + } + get optionsMap() { + return this._def.optionsMap; + } + /** + * The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor. + * However, it only allows a union of objects, all of which need to share a discriminator property. This property must + * have a different value for each object in the union. + * @param discriminator the name of the discriminator property + * @param types an array of object schemas + * @param params + */ + static create(discriminator, options, params) { + // Get all the valid discriminator values + const optionsMap = new Map(); + // try { + for (const type of options) { + const discriminatorValues = getDiscriminator(type.shape[discriminator]); + if (!discriminatorValues.length) { + throw new Error(`A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`); + } + for (const value of discriminatorValues) { + if (optionsMap.has(value)) { + throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`); + } + optionsMap.set(value, type); + } + } + return new ZodDiscriminatedUnion({ + typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion, + discriminator, + options, + optionsMap, + ...processCreateParams(params), + }); + } +} +function mergeValues(a, b) { + const aType = getParsedType(a); + const bType = getParsedType(b); + if (a === b) { + return { valid: true, data: a }; + } + else if (aType === ZodParsedType.object && bType === ZodParsedType.object) { + const bKeys = util.objectKeys(b); + const sharedKeys = util.objectKeys(a).filter((key) => bKeys.indexOf(key) !== -1); + const newObj = { ...a, ...b }; + for (const key of sharedKeys) { + const sharedValue = mergeValues(a[key], b[key]); + if (!sharedValue.valid) { + return { valid: false }; + } + newObj[key] = sharedValue.data; + } + return { valid: true, data: newObj }; + } + else if (aType === ZodParsedType.array && bType === ZodParsedType.array) { + if (a.length !== b.length) { + return { valid: false }; + } + const newArray = []; + for (let index = 0; index < a.length; index++) { + const itemA = a[index]; + const itemB = b[index]; + const sharedValue = mergeValues(itemA, itemB); + if (!sharedValue.valid) { + return { valid: false }; + } + newArray.push(sharedValue.data); + } + return { valid: true, data: newArray }; + } + else if (aType === ZodParsedType.date && bType === ZodParsedType.date && +a === +b) { + return { valid: true, data: a }; + } + else { + return { valid: false }; + } +} +export class ZodIntersection extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + const handleParsed = (parsedLeft, parsedRight) => { + if (isAborted(parsedLeft) || isAborted(parsedRight)) { + return INVALID; + } + const merged = mergeValues(parsedLeft.value, parsedRight.value); + if (!merged.valid) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_intersection_types, + }); + return INVALID; + } + if (isDirty(parsedLeft) || isDirty(parsedRight)) { + status.dirty(); + } + return { status: status.value, value: merged.data }; + }; + if (ctx.common.async) { + return Promise.all([ + this._def.left._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }), + this._def.right._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }), + ]).then(([left, right]) => handleParsed(left, right)); + } + else { + return handleParsed(this._def.left._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }), this._def.right._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + })); + } + } +} +ZodIntersection.create = (left, right, params) => { + return new ZodIntersection({ + left: left, + right: right, + typeName: ZodFirstPartyTypeKind.ZodIntersection, + ...processCreateParams(params), + }); +}; +// type ZodTupleItems = [ZodTypeAny, ...ZodTypeAny[]]; +export class ZodTuple extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.array) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.array, + received: ctx.parsedType, + }); + return INVALID; + } + if (ctx.data.length < this._def.items.length) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: this._def.items.length, + inclusive: true, + exact: false, + type: "array", + }); + return INVALID; + } + const rest = this._def.rest; + if (!rest && ctx.data.length > this._def.items.length) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: this._def.items.length, + inclusive: true, + exact: false, + type: "array", + }); + status.dirty(); + } + const items = [...ctx.data] + .map((item, itemIndex) => { + const schema = this._def.items[itemIndex] || this._def.rest; + if (!schema) + return null; + return schema._parse(new ParseInputLazyPath(ctx, item, ctx.path, itemIndex)); + }) + .filter((x) => !!x); // filter nulls + if (ctx.common.async) { + return Promise.all(items).then((results) => { + return ParseStatus.mergeArray(status, results); + }); + } + else { + return ParseStatus.mergeArray(status, items); + } + } + get items() { + return this._def.items; + } + rest(rest) { + return new ZodTuple({ + ...this._def, + rest, + }); + } +} +ZodTuple.create = (schemas, params) => { + if (!Array.isArray(schemas)) { + throw new Error("You must pass an array of schemas to z.tuple([ ... ])"); + } + return new ZodTuple({ + items: schemas, + typeName: ZodFirstPartyTypeKind.ZodTuple, + rest: null, + ...processCreateParams(params), + }); +}; +export class ZodRecord extends ZodType { + get keySchema() { + return this._def.keyType; + } + get valueSchema() { + return this._def.valueType; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.object) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType, + }); + return INVALID; + } + const pairs = []; + const keyType = this._def.keyType; + const valueType = this._def.valueType; + for (const key in ctx.data) { + pairs.push({ + key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)), + value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)), + alwaysSet: key in ctx.data, + }); + } + if (ctx.common.async) { + return ParseStatus.mergeObjectAsync(status, pairs); + } + else { + return ParseStatus.mergeObjectSync(status, pairs); + } + } + get element() { + return this._def.valueType; + } + static create(first, second, third) { + if (second instanceof ZodType) { + return new ZodRecord({ + keyType: first, + valueType: second, + typeName: ZodFirstPartyTypeKind.ZodRecord, + ...processCreateParams(third), + }); + } + return new ZodRecord({ + keyType: ZodString.create(), + valueType: first, + typeName: ZodFirstPartyTypeKind.ZodRecord, + ...processCreateParams(second), + }); + } +} +export class ZodMap extends ZodType { + get keySchema() { + return this._def.keyType; + } + get valueSchema() { + return this._def.valueType; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.map) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.map, + received: ctx.parsedType, + }); + return INVALID; + } + const keyType = this._def.keyType; + const valueType = this._def.valueType; + const pairs = [...ctx.data.entries()].map(([key, value], index) => { + return { + key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index, "key"])), + value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index, "value"])), + }; + }); + if (ctx.common.async) { + const finalMap = new Map(); + return Promise.resolve().then(async () => { + for (const pair of pairs) { + const key = await pair.key; + const value = await pair.value; + if (key.status === "aborted" || value.status === "aborted") { + return INVALID; + } + if (key.status === "dirty" || value.status === "dirty") { + status.dirty(); + } + finalMap.set(key.value, value.value); + } + return { status: status.value, value: finalMap }; + }); + } + else { + const finalMap = new Map(); + for (const pair of pairs) { + const key = pair.key; + const value = pair.value; + if (key.status === "aborted" || value.status === "aborted") { + return INVALID; + } + if (key.status === "dirty" || value.status === "dirty") { + status.dirty(); + } + finalMap.set(key.value, value.value); + } + return { status: status.value, value: finalMap }; + } + } +} +ZodMap.create = (keyType, valueType, params) => { + return new ZodMap({ + valueType, + keyType, + typeName: ZodFirstPartyTypeKind.ZodMap, + ...processCreateParams(params), + }); +}; +export class ZodSet extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.set) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.set, + received: ctx.parsedType, + }); + return INVALID; + } + const def = this._def; + if (def.minSize !== null) { + if (ctx.data.size < def.minSize.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: def.minSize.value, + type: "set", + inclusive: true, + exact: false, + message: def.minSize.message, + }); + status.dirty(); + } + } + if (def.maxSize !== null) { + if (ctx.data.size > def.maxSize.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: def.maxSize.value, + type: "set", + inclusive: true, + exact: false, + message: def.maxSize.message, + }); + status.dirty(); + } + } + const valueType = this._def.valueType; + function finalizeSet(elements) { + const parsedSet = new Set(); + for (const element of elements) { + if (element.status === "aborted") + return INVALID; + if (element.status === "dirty") + status.dirty(); + parsedSet.add(element.value); + } + return { status: status.value, value: parsedSet }; + } + const elements = [...ctx.data.values()].map((item, i) => valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i))); + if (ctx.common.async) { + return Promise.all(elements).then((elements) => finalizeSet(elements)); + } + else { + return finalizeSet(elements); + } + } + min(minSize, message) { + return new ZodSet({ + ...this._def, + minSize: { value: minSize, message: errorUtil.toString(message) }, + }); + } + max(maxSize, message) { + return new ZodSet({ + ...this._def, + maxSize: { value: maxSize, message: errorUtil.toString(message) }, + }); + } + size(size, message) { + return this.min(size, message).max(size, message); + } + nonempty(message) { + return this.min(1, message); + } +} +ZodSet.create = (valueType, params) => { + return new ZodSet({ + valueType, + minSize: null, + maxSize: null, + typeName: ZodFirstPartyTypeKind.ZodSet, + ...processCreateParams(params), + }); +}; +export class ZodFunction extends ZodType { + constructor() { + super(...arguments); + this.validate = this.implement; + } + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.function) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.function, + received: ctx.parsedType, + }); + return INVALID; + } + function makeArgsIssue(args, error) { + return makeIssue({ + data: args, + path: ctx.path, + errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), defaultErrorMap].filter((x) => !!x), + issueData: { + code: ZodIssueCode.invalid_arguments, + argumentsError: error, + }, + }); + } + function makeReturnsIssue(returns, error) { + return makeIssue({ + data: returns, + path: ctx.path, + errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), defaultErrorMap].filter((x) => !!x), + issueData: { + code: ZodIssueCode.invalid_return_type, + returnTypeError: error, + }, + }); + } + const params = { errorMap: ctx.common.contextualErrorMap }; + const fn = ctx.data; + if (this._def.returns instanceof ZodPromise) { + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(async function (...args) { + const error = new ZodError([]); + const parsedArgs = await me._def.args.parseAsync(args, params).catch((e) => { + error.addIssue(makeArgsIssue(args, e)); + throw error; + }); + const result = await Reflect.apply(fn, this, parsedArgs); + const parsedReturns = await me._def.returns._def.type + .parseAsync(result, params) + .catch((e) => { + error.addIssue(makeReturnsIssue(result, e)); + throw error; + }); + return parsedReturns; + }); + } + else { + // Would love a way to avoid disabling this rule, but we need + // an alias (using an arrow function was what caused 2651). + // eslint-disable-next-line @typescript-eslint/no-this-alias + const me = this; + return OK(function (...args) { + const parsedArgs = me._def.args.safeParse(args, params); + if (!parsedArgs.success) { + throw new ZodError([makeArgsIssue(args, parsedArgs.error)]); + } + const result = Reflect.apply(fn, this, parsedArgs.data); + const parsedReturns = me._def.returns.safeParse(result, params); + if (!parsedReturns.success) { + throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]); + } + return parsedReturns.data; + }); + } + } + parameters() { + return this._def.args; + } + returnType() { + return this._def.returns; + } + args(...items) { + return new ZodFunction({ + ...this._def, + args: ZodTuple.create(items).rest(ZodUnknown.create()), + }); + } + returns(returnType) { + return new ZodFunction({ + ...this._def, + returns: returnType, + }); + } + implement(func) { + const validatedFunc = this.parse(func); + return validatedFunc; + } + strictImplement(func) { + const validatedFunc = this.parse(func); + return validatedFunc; + } + static create(args, returns, params) { + return new ZodFunction({ + args: (args ? args : ZodTuple.create([]).rest(ZodUnknown.create())), + returns: returns || ZodUnknown.create(), + typeName: ZodFirstPartyTypeKind.ZodFunction, + ...processCreateParams(params), + }); + } +} +export class ZodLazy extends ZodType { + get schema() { + return this._def.getter(); + } + _parse(input) { + const { ctx } = this._processInputParams(input); + const lazySchema = this._def.getter(); + return lazySchema._parse({ data: ctx.data, path: ctx.path, parent: ctx }); + } +} +ZodLazy.create = (getter, params) => { + return new ZodLazy({ + getter: getter, + typeName: ZodFirstPartyTypeKind.ZodLazy, + ...processCreateParams(params), + }); +}; +export class ZodLiteral extends ZodType { + _parse(input) { + if (input.data !== this._def.value) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_literal, + expected: this._def.value, + }); + return INVALID; + } + return { status: "valid", value: input.data }; + } + get value() { + return this._def.value; + } +} +ZodLiteral.create = (value, params) => { + return new ZodLiteral({ + value: value, + typeName: ZodFirstPartyTypeKind.ZodLiteral, + ...processCreateParams(params), + }); +}; +function createZodEnum(values, params) { + return new ZodEnum({ + values, + typeName: ZodFirstPartyTypeKind.ZodEnum, + ...processCreateParams(params), + }); +} +export class ZodEnum extends ZodType { + _parse(input) { + if (typeof input.data !== "string") { + const ctx = this._getOrReturnCtx(input); + const expectedValues = this._def.values; + addIssueToContext(ctx, { + expected: util.joinValues(expectedValues), + received: ctx.parsedType, + code: ZodIssueCode.invalid_type, + }); + return INVALID; + } + if (!this._cache) { + this._cache = new Set(this._def.values); + } + if (!this._cache.has(input.data)) { + const ctx = this._getOrReturnCtx(input); + const expectedValues = this._def.values; + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_enum_value, + options: expectedValues, + }); + return INVALID; + } + return OK(input.data); + } + get options() { + return this._def.values; + } + get enum() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } + get Values() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } + get Enum() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } + extract(values, newDef = this._def) { + return ZodEnum.create(values, { + ...this._def, + ...newDef, + }); + } + exclude(values, newDef = this._def) { + return ZodEnum.create(this.options.filter((opt) => !values.includes(opt)), { + ...this._def, + ...newDef, + }); + } +} +ZodEnum.create = createZodEnum; +export class ZodNativeEnum extends ZodType { + _parse(input) { + const nativeEnumValues = util.getValidEnumValues(this._def.values); + const ctx = this._getOrReturnCtx(input); + if (ctx.parsedType !== ZodParsedType.string && ctx.parsedType !== ZodParsedType.number) { + const expectedValues = util.objectValues(nativeEnumValues); + addIssueToContext(ctx, { + expected: util.joinValues(expectedValues), + received: ctx.parsedType, + code: ZodIssueCode.invalid_type, + }); + return INVALID; + } + if (!this._cache) { + this._cache = new Set(util.getValidEnumValues(this._def.values)); + } + if (!this._cache.has(input.data)) { + const expectedValues = util.objectValues(nativeEnumValues); + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_enum_value, + options: expectedValues, + }); + return INVALID; + } + return OK(input.data); + } + get enum() { + return this._def.values; + } +} +ZodNativeEnum.create = (values, params) => { + return new ZodNativeEnum({ + values: values, + typeName: ZodFirstPartyTypeKind.ZodNativeEnum, + ...processCreateParams(params), + }); +}; +export class ZodPromise extends ZodType { + unwrap() { + return this._def.type; + } + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.promise && ctx.common.async === false) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.promise, + received: ctx.parsedType, + }); + return INVALID; + } + const promisified = ctx.parsedType === ZodParsedType.promise ? ctx.data : Promise.resolve(ctx.data); + return OK(promisified.then((data) => { + return this._def.type.parseAsync(data, { + path: ctx.path, + errorMap: ctx.common.contextualErrorMap, + }); + })); + } +} +ZodPromise.create = (schema, params) => { + return new ZodPromise({ + type: schema, + typeName: ZodFirstPartyTypeKind.ZodPromise, + ...processCreateParams(params), + }); +}; +export class ZodEffects extends ZodType { + innerType() { + return this._def.schema; + } + sourceType() { + return this._def.schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects + ? this._def.schema.sourceType() + : this._def.schema; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + const effect = this._def.effect || null; + const checkCtx = { + addIssue: (arg) => { + addIssueToContext(ctx, arg); + if (arg.fatal) { + status.abort(); + } + else { + status.dirty(); + } + }, + get path() { + return ctx.path; + }, + }; + checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx); + if (effect.type === "preprocess") { + const processed = effect.transform(ctx.data, checkCtx); + if (ctx.common.async) { + return Promise.resolve(processed).then(async (processed) => { + if (status.value === "aborted") + return INVALID; + const result = await this._def.schema._parseAsync({ + data: processed, + path: ctx.path, + parent: ctx, + }); + if (result.status === "aborted") + return INVALID; + if (result.status === "dirty") + return DIRTY(result.value); + if (status.value === "dirty") + return DIRTY(result.value); + return result; + }); + } + else { + if (status.value === "aborted") + return INVALID; + const result = this._def.schema._parseSync({ + data: processed, + path: ctx.path, + parent: ctx, + }); + if (result.status === "aborted") + return INVALID; + if (result.status === "dirty") + return DIRTY(result.value); + if (status.value === "dirty") + return DIRTY(result.value); + return result; + } + } + if (effect.type === "refinement") { + const executeRefinement = (acc) => { + const result = effect.refinement(acc, checkCtx); + if (ctx.common.async) { + return Promise.resolve(result); + } + if (result instanceof Promise) { + throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead."); + } + return acc; + }; + if (ctx.common.async === false) { + const inner = this._def.schema._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + if (inner.status === "aborted") + return INVALID; + if (inner.status === "dirty") + status.dirty(); + // return value is ignored + executeRefinement(inner.value); + return { status: status.value, value: inner.value }; + } + else { + return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((inner) => { + if (inner.status === "aborted") + return INVALID; + if (inner.status === "dirty") + status.dirty(); + return executeRefinement(inner.value).then(() => { + return { status: status.value, value: inner.value }; + }); + }); + } + } + if (effect.type === "transform") { + if (ctx.common.async === false) { + const base = this._def.schema._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + if (!isValid(base)) + return INVALID; + const result = effect.transform(base.value, checkCtx); + if (result instanceof Promise) { + throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`); + } + return { status: status.value, value: result }; + } + else { + return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => { + if (!isValid(base)) + return INVALID; + return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({ + status: status.value, + value: result, + })); + }); + } + } + util.assertNever(effect); + } +} +ZodEffects.create = (schema, effect, params) => { + return new ZodEffects({ + schema, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect, + ...processCreateParams(params), + }); +}; +ZodEffects.createWithPreprocess = (preprocess, schema, params) => { + return new ZodEffects({ + schema, + effect: { type: "preprocess", transform: preprocess }, + typeName: ZodFirstPartyTypeKind.ZodEffects, + ...processCreateParams(params), + }); +}; +export { ZodEffects as ZodTransformer }; +export class ZodOptional extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType === ZodParsedType.undefined) { + return OK(undefined); + } + return this._def.innerType._parse(input); + } + unwrap() { + return this._def.innerType; + } +} +ZodOptional.create = (type, params) => { + return new ZodOptional({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodOptional, + ...processCreateParams(params), + }); +}; +export class ZodNullable extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType === ZodParsedType.null) { + return OK(null); + } + return this._def.innerType._parse(input); + } + unwrap() { + return this._def.innerType; + } +} +ZodNullable.create = (type, params) => { + return new ZodNullable({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodNullable, + ...processCreateParams(params), + }); +}; +export class ZodDefault extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + let data = ctx.data; + if (ctx.parsedType === ZodParsedType.undefined) { + data = this._def.defaultValue(); + } + return this._def.innerType._parse({ + data, + path: ctx.path, + parent: ctx, + }); + } + removeDefault() { + return this._def.innerType; + } +} +ZodDefault.create = (type, params) => { + return new ZodDefault({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodDefault, + defaultValue: typeof params.default === "function" ? params.default : () => params.default, + ...processCreateParams(params), + }); +}; +export class ZodCatch extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + // newCtx is used to not collect issues from inner types in ctx + const newCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [], + }, + }; + const result = this._def.innerType._parse({ + data: newCtx.data, + path: newCtx.path, + parent: { + ...newCtx, + }, + }); + if (isAsync(result)) { + return result.then((result) => { + return { + status: "valid", + value: result.status === "valid" + ? result.value + : this._def.catchValue({ + get error() { + return new ZodError(newCtx.common.issues); + }, + input: newCtx.data, + }), + }; + }); + } + else { + return { + status: "valid", + value: result.status === "valid" + ? result.value + : this._def.catchValue({ + get error() { + return new ZodError(newCtx.common.issues); + }, + input: newCtx.data, + }), + }; + } + } + removeCatch() { + return this._def.innerType; + } +} +ZodCatch.create = (type, params) => { + return new ZodCatch({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodCatch, + catchValue: typeof params.catch === "function" ? params.catch : () => params.catch, + ...processCreateParams(params), + }); +}; +export class ZodNaN extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.nan) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.nan, + received: ctx.parsedType, + }); + return INVALID; + } + return { status: "valid", value: input.data }; + } +} +ZodNaN.create = (params) => { + return new ZodNaN({ + typeName: ZodFirstPartyTypeKind.ZodNaN, + ...processCreateParams(params), + }); +}; +export const BRAND = Symbol("zod_brand"); +export class ZodBranded extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + const data = ctx.data; + return this._def.type._parse({ + data, + path: ctx.path, + parent: ctx, + }); + } + unwrap() { + return this._def.type; + } +} +export class ZodPipeline extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.common.async) { + const handleAsync = async () => { + const inResult = await this._def.in._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + if (inResult.status === "aborted") + return INVALID; + if (inResult.status === "dirty") { + status.dirty(); + return DIRTY(inResult.value); + } + else { + return this._def.out._parseAsync({ + data: inResult.value, + path: ctx.path, + parent: ctx, + }); + } + }; + return handleAsync(); + } + else { + const inResult = this._def.in._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + if (inResult.status === "aborted") + return INVALID; + if (inResult.status === "dirty") { + status.dirty(); + return { + status: "dirty", + value: inResult.value, + }; + } + else { + return this._def.out._parseSync({ + data: inResult.value, + path: ctx.path, + parent: ctx, + }); + } + } + } + static create(a, b) { + return new ZodPipeline({ + in: a, + out: b, + typeName: ZodFirstPartyTypeKind.ZodPipeline, + }); + } +} +export class ZodReadonly extends ZodType { + _parse(input) { + const result = this._def.innerType._parse(input); + const freeze = (data) => { + if (isValid(data)) { + data.value = Object.freeze(data.value); + } + return data; + }; + return isAsync(result) ? result.then((data) => freeze(data)) : freeze(result); + } + unwrap() { + return this._def.innerType; + } +} +ZodReadonly.create = (type, params) => { + return new ZodReadonly({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodReadonly, + ...processCreateParams(params), + }); +}; +//////////////////////////////////////// +//////////////////////////////////////// +////////// ////////// +////////// z.custom ////////// +////////// ////////// +//////////////////////////////////////// +//////////////////////////////////////// +function cleanParams(params, data) { + const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params; + const p2 = typeof p === "string" ? { message: p } : p; + return p2; +} +export function custom(check, _params = {}, +/** + * @deprecated + * + * Pass `fatal` into the params object instead: + * + * ```ts + * z.string().custom((val) => val.length > 5, { fatal: false }) + * ``` + * + */ +fatal) { + if (check) + return ZodAny.create().superRefine((data, ctx) => { + const r = check(data); + if (r instanceof Promise) { + return r.then((r) => { + if (!r) { + const params = cleanParams(_params, data); + const _fatal = params.fatal ?? fatal ?? true; + ctx.addIssue({ code: "custom", ...params, fatal: _fatal }); + } + }); + } + if (!r) { + const params = cleanParams(_params, data); + const _fatal = params.fatal ?? fatal ?? true; + ctx.addIssue({ code: "custom", ...params, fatal: _fatal }); + } + return; + }); + return ZodAny.create(); +} +export { ZodType as Schema, ZodType as ZodSchema }; +export const late = { + object: ZodObject.lazycreate, +}; +export var ZodFirstPartyTypeKind; +(function (ZodFirstPartyTypeKind) { + ZodFirstPartyTypeKind["ZodString"] = "ZodString"; + ZodFirstPartyTypeKind["ZodNumber"] = "ZodNumber"; + ZodFirstPartyTypeKind["ZodNaN"] = "ZodNaN"; + ZodFirstPartyTypeKind["ZodBigInt"] = "ZodBigInt"; + ZodFirstPartyTypeKind["ZodBoolean"] = "ZodBoolean"; + ZodFirstPartyTypeKind["ZodDate"] = "ZodDate"; + ZodFirstPartyTypeKind["ZodSymbol"] = "ZodSymbol"; + ZodFirstPartyTypeKind["ZodUndefined"] = "ZodUndefined"; + ZodFirstPartyTypeKind["ZodNull"] = "ZodNull"; + ZodFirstPartyTypeKind["ZodAny"] = "ZodAny"; + ZodFirstPartyTypeKind["ZodUnknown"] = "ZodUnknown"; + ZodFirstPartyTypeKind["ZodNever"] = "ZodNever"; + ZodFirstPartyTypeKind["ZodVoid"] = "ZodVoid"; + ZodFirstPartyTypeKind["ZodArray"] = "ZodArray"; + ZodFirstPartyTypeKind["ZodObject"] = "ZodObject"; + ZodFirstPartyTypeKind["ZodUnion"] = "ZodUnion"; + ZodFirstPartyTypeKind["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion"; + ZodFirstPartyTypeKind["ZodIntersection"] = "ZodIntersection"; + ZodFirstPartyTypeKind["ZodTuple"] = "ZodTuple"; + ZodFirstPartyTypeKind["ZodRecord"] = "ZodRecord"; + ZodFirstPartyTypeKind["ZodMap"] = "ZodMap"; + ZodFirstPartyTypeKind["ZodSet"] = "ZodSet"; + ZodFirstPartyTypeKind["ZodFunction"] = "ZodFunction"; + ZodFirstPartyTypeKind["ZodLazy"] = "ZodLazy"; + ZodFirstPartyTypeKind["ZodLiteral"] = "ZodLiteral"; + ZodFirstPartyTypeKind["ZodEnum"] = "ZodEnum"; + ZodFirstPartyTypeKind["ZodEffects"] = "ZodEffects"; + ZodFirstPartyTypeKind["ZodNativeEnum"] = "ZodNativeEnum"; + ZodFirstPartyTypeKind["ZodOptional"] = "ZodOptional"; + ZodFirstPartyTypeKind["ZodNullable"] = "ZodNullable"; + ZodFirstPartyTypeKind["ZodDefault"] = "ZodDefault"; + ZodFirstPartyTypeKind["ZodCatch"] = "ZodCatch"; + ZodFirstPartyTypeKind["ZodPromise"] = "ZodPromise"; + ZodFirstPartyTypeKind["ZodBranded"] = "ZodBranded"; + ZodFirstPartyTypeKind["ZodPipeline"] = "ZodPipeline"; + ZodFirstPartyTypeKind["ZodReadonly"] = "ZodReadonly"; +})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {})); +// requires TS 4.4+ +class Class { + constructor(..._) { } +} +const instanceOfType = ( +// const instanceOfType = any>( +cls, params = { + message: `Input not instance of ${cls.name}`, +}) => custom((data) => data instanceof cls, params); +const stringType = ZodString.create; +const numberType = ZodNumber.create; +const nanType = ZodNaN.create; +const bigIntType = ZodBigInt.create; +const booleanType = ZodBoolean.create; +const dateType = ZodDate.create; +const symbolType = ZodSymbol.create; +const undefinedType = ZodUndefined.create; +const nullType = ZodNull.create; +const anyType = ZodAny.create; +const unknownType = ZodUnknown.create; +const neverType = ZodNever.create; +const voidType = ZodVoid.create; +const arrayType = ZodArray.create; +const objectType = ZodObject.create; +const strictObjectType = ZodObject.strictCreate; +const unionType = ZodUnion.create; +const discriminatedUnionType = ZodDiscriminatedUnion.create; +const intersectionType = ZodIntersection.create; +const tupleType = ZodTuple.create; +const recordType = ZodRecord.create; +const mapType = ZodMap.create; +const setType = ZodSet.create; +const functionType = ZodFunction.create; +const lazyType = ZodLazy.create; +const literalType = ZodLiteral.create; +const enumType = ZodEnum.create; +const nativeEnumType = ZodNativeEnum.create; +const promiseType = ZodPromise.create; +const effectsType = ZodEffects.create; +const optionalType = ZodOptional.create; +const nullableType = ZodNullable.create; +const preprocessType = ZodEffects.createWithPreprocess; +const pipelineType = ZodPipeline.create; +const ostring = () => stringType().optional(); +const onumber = () => numberType().optional(); +const oboolean = () => booleanType().optional(); +export const coerce = { + string: ((arg) => ZodString.create({ ...arg, coerce: true })), + number: ((arg) => ZodNumber.create({ ...arg, coerce: true })), + boolean: ((arg) => ZodBoolean.create({ + ...arg, + coerce: true, + })), + bigint: ((arg) => ZodBigInt.create({ ...arg, coerce: true })), + date: ((arg) => ZodDate.create({ ...arg, coerce: true })), +}; +export { anyType as any, arrayType as array, bigIntType as bigint, booleanType as boolean, dateType as date, discriminatedUnionType as discriminatedUnion, effectsType as effect, enumType as enum, functionType as function, instanceOfType as instanceof, intersectionType as intersection, lazyType as lazy, literalType as literal, mapType as map, nanType as nan, nativeEnumType as nativeEnum, neverType as never, nullType as null, nullableType as nullable, numberType as number, objectType as object, oboolean, onumber, optionalType as optional, ostring, pipelineType as pipeline, preprocessType as preprocess, promiseType as promise, recordType as record, setType as set, strictObjectType as strictObject, stringType as string, symbolType as symbol, effectsType as transformer, tupleType as tuple, undefinedType as undefined, unionType as union, unknownType as unknown, voidType as void, }; +export const NEVER = INVALID; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/index.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/index.d.ts new file mode 100644 index 000000000..8352ab713 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/index.d.ts @@ -0,0 +1,3 @@ +import z3 from "./v3/index.js"; +export * from "./v3/index.js"; +export default z3; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/ZodError.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/ZodError.d.ts new file mode 100644 index 000000000..4e71355a3 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/ZodError.d.ts @@ -0,0 +1,164 @@ +import type { Primitive } from "./helpers/typeAliases.js"; +import { util, type ZodParsedType } from "./helpers/util.js"; +import type { TypeOf, ZodType } from "./index.js"; +type allKeys = T extends any ? keyof T : never; +export type inferFlattenedErrors, U = string> = typeToFlattenedError, U>; +export type typeToFlattenedError = { + formErrors: U[]; + fieldErrors: { + [P in allKeys]?: U[]; + }; +}; +export declare const ZodIssueCode: { + invalid_type: "invalid_type"; + invalid_literal: "invalid_literal"; + custom: "custom"; + invalid_union: "invalid_union"; + invalid_union_discriminator: "invalid_union_discriminator"; + invalid_enum_value: "invalid_enum_value"; + unrecognized_keys: "unrecognized_keys"; + invalid_arguments: "invalid_arguments"; + invalid_return_type: "invalid_return_type"; + invalid_date: "invalid_date"; + invalid_string: "invalid_string"; + too_small: "too_small"; + too_big: "too_big"; + invalid_intersection_types: "invalid_intersection_types"; + not_multiple_of: "not_multiple_of"; + not_finite: "not_finite"; +}; +export type ZodIssueCode = keyof typeof ZodIssueCode; +export type ZodIssueBase = { + path: (string | number)[]; + message?: string | undefined; +}; +export interface ZodInvalidTypeIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_type; + expected: ZodParsedType; + received: ZodParsedType; +} +export interface ZodInvalidLiteralIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_literal; + expected: unknown; + received: unknown; +} +export interface ZodUnrecognizedKeysIssue extends ZodIssueBase { + code: typeof ZodIssueCode.unrecognized_keys; + keys: string[]; +} +export interface ZodInvalidUnionIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_union; + unionErrors: ZodError[]; +} +export interface ZodInvalidUnionDiscriminatorIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_union_discriminator; + options: Primitive[]; +} +export interface ZodInvalidEnumValueIssue extends ZodIssueBase { + received: string | number; + code: typeof ZodIssueCode.invalid_enum_value; + options: (string | number)[]; +} +export interface ZodInvalidArgumentsIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_arguments; + argumentsError: ZodError; +} +export interface ZodInvalidReturnTypeIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_return_type; + returnTypeError: ZodError; +} +export interface ZodInvalidDateIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_date; +} +export type StringValidation = "email" | "url" | "emoji" | "uuid" | "nanoid" | "regex" | "cuid" | "cuid2" | "ulid" | "datetime" | "date" | "time" | "duration" | "ip" | "cidr" | "base64" | "jwt" | "base64url" | { + includes: string; + position?: number | undefined; +} | { + startsWith: string; +} | { + endsWith: string; +}; +export interface ZodInvalidStringIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_string; + validation: StringValidation; +} +export interface ZodTooSmallIssue extends ZodIssueBase { + code: typeof ZodIssueCode.too_small; + minimum: number | bigint; + inclusive: boolean; + exact?: boolean; + type: "array" | "string" | "number" | "set" | "date" | "bigint"; +} +export interface ZodTooBigIssue extends ZodIssueBase { + code: typeof ZodIssueCode.too_big; + maximum: number | bigint; + inclusive: boolean; + exact?: boolean; + type: "array" | "string" | "number" | "set" | "date" | "bigint"; +} +export interface ZodInvalidIntersectionTypesIssue extends ZodIssueBase { + code: typeof ZodIssueCode.invalid_intersection_types; +} +export interface ZodNotMultipleOfIssue extends ZodIssueBase { + code: typeof ZodIssueCode.not_multiple_of; + multipleOf: number | bigint; +} +export interface ZodNotFiniteIssue extends ZodIssueBase { + code: typeof ZodIssueCode.not_finite; +} +export interface ZodCustomIssue extends ZodIssueBase { + code: typeof ZodIssueCode.custom; + params?: { + [k: string]: any; + }; +} +export type DenormalizedError = { + [k: string]: DenormalizedError | string[]; +}; +export type ZodIssueOptionalMessage = ZodInvalidTypeIssue | ZodInvalidLiteralIssue | ZodUnrecognizedKeysIssue | ZodInvalidUnionIssue | ZodInvalidUnionDiscriminatorIssue | ZodInvalidEnumValueIssue | ZodInvalidArgumentsIssue | ZodInvalidReturnTypeIssue | ZodInvalidDateIssue | ZodInvalidStringIssue | ZodTooSmallIssue | ZodTooBigIssue | ZodInvalidIntersectionTypesIssue | ZodNotMultipleOfIssue | ZodNotFiniteIssue | ZodCustomIssue; +export type ZodIssue = ZodIssueOptionalMessage & { + fatal?: boolean | undefined; + message: string; +}; +export declare const quotelessJson: (obj: any) => string; +type recursiveZodFormattedError = T extends [any, ...any[]] ? { + [K in keyof T]?: ZodFormattedError; +} : T extends any[] ? { + [k: number]: ZodFormattedError; +} : T extends object ? { + [K in keyof T]?: ZodFormattedError; +} : unknown; +export type ZodFormattedError = { + _errors: U[]; +} & recursiveZodFormattedError>; +export type inferFormattedError, U = string> = ZodFormattedError, U>; +export declare class ZodError extends Error { + issues: ZodIssue[]; + get errors(): ZodIssue[]; + constructor(issues: ZodIssue[]); + format(): ZodFormattedError; + format(mapper: (issue: ZodIssue) => U): ZodFormattedError; + static create: (issues: ZodIssue[]) => ZodError; + static assert(value: unknown): asserts value is ZodError; + toString(): string; + get message(): string; + get isEmpty(): boolean; + addIssue: (sub: ZodIssue) => void; + addIssues: (subs?: ZodIssue[]) => void; + flatten(): typeToFlattenedError; + flatten(mapper?: (issue: ZodIssue) => U): typeToFlattenedError; + get formErrors(): typeToFlattenedError; +} +type stripPath = T extends any ? util.OmitKeys : never; +export type IssueData = stripPath & { + path?: (string | number)[]; + fatal?: boolean | undefined; +}; +export type ErrorMapCtx = { + defaultError: string; + data: any; +}; +export type ZodErrorMap = (issue: ZodIssueOptionalMessage, _ctx: ErrorMapCtx) => { + message: string; +}; +export {}; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/errors.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/errors.d.ts new file mode 100644 index 000000000..52eacf72e --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/errors.d.ts @@ -0,0 +1,5 @@ +import type { ZodErrorMap } from "./ZodError.js"; +import defaultErrorMap from "./locales/en.js"; +export { defaultErrorMap }; +export declare function setErrorMap(map: ZodErrorMap): void; +export declare function getErrorMap(): ZodErrorMap; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/external.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/external.d.ts new file mode 100644 index 000000000..f0a4be4c7 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/external.d.ts @@ -0,0 +1,6 @@ +export * from "./errors.js"; +export * from "./helpers/parseUtil.js"; +export * from "./helpers/typeAliases.js"; +export * from "./helpers/util.js"; +export * from "./types.js"; +export * from "./ZodError.js"; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/enumUtil.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/enumUtil.d.ts new file mode 100644 index 000000000..fad28a711 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/enumUtil.d.ts @@ -0,0 +1,8 @@ +export declare namespace enumUtil { + type UnionToIntersectionFn = (T extends unknown ? (k: () => T) => void : never) extends (k: infer Intersection) => void ? Intersection : never; + type GetUnionLast = UnionToIntersectionFn extends () => infer Last ? Last : never; + type UnionToTuple = [T] extends [never] ? Tuple : UnionToTuple>, [GetUnionLast, ...Tuple]>; + type CastToStringTuple = T extends [string, ...string[]] ? T : never; + export type UnionToTupleString = CastToStringTuple>; + export {}; +} diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/errorUtil.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/errorUtil.d.ts new file mode 100644 index 000000000..bc9b033ff --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/errorUtil.d.ts @@ -0,0 +1,9 @@ +export declare namespace errorUtil { + type ErrMessage = string | { + message?: string | undefined; + }; + const errToObj: (message?: ErrMessage) => { + message?: string | undefined; + }; + const toString: (message?: ErrMessage) => string | undefined; +} diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/parseUtil.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/parseUtil.d.ts new file mode 100644 index 000000000..e4dc1d7b3 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/parseUtil.d.ts @@ -0,0 +1,78 @@ +import type { IssueData, ZodErrorMap, ZodIssue } from "../ZodError.js"; +import type { ZodParsedType } from "./util.js"; +export declare const makeIssue: (params: { + data: any; + path: (string | number)[]; + errorMaps: ZodErrorMap[]; + issueData: IssueData; +}) => ZodIssue; +export type ParseParams = { + path: (string | number)[]; + errorMap: ZodErrorMap; + async: boolean; +}; +export type ParsePathComponent = string | number; +export type ParsePath = ParsePathComponent[]; +export declare const EMPTY_PATH: ParsePath; +export interface ParseContext { + readonly common: { + readonly issues: ZodIssue[]; + readonly contextualErrorMap?: ZodErrorMap | undefined; + readonly async: boolean; + }; + readonly path: ParsePath; + readonly schemaErrorMap?: ZodErrorMap | undefined; + readonly parent: ParseContext | null; + readonly data: any; + readonly parsedType: ZodParsedType; +} +export type ParseInput = { + data: any; + path: (string | number)[]; + parent: ParseContext; +}; +export declare function addIssueToContext(ctx: ParseContext, issueData: IssueData): void; +export type ObjectPair = { + key: SyncParseReturnType; + value: SyncParseReturnType; +}; +export declare class ParseStatus { + value: "aborted" | "dirty" | "valid"; + dirty(): void; + abort(): void; + static mergeArray(status: ParseStatus, results: SyncParseReturnType[]): SyncParseReturnType; + static mergeObjectAsync(status: ParseStatus, pairs: { + key: ParseReturnType; + value: ParseReturnType; + }[]): Promise>; + static mergeObjectSync(status: ParseStatus, pairs: { + key: SyncParseReturnType; + value: SyncParseReturnType; + alwaysSet?: boolean; + }[]): SyncParseReturnType; +} +export interface ParseResult { + status: "aborted" | "dirty" | "valid"; + data: any; +} +export type INVALID = { + status: "aborted"; +}; +export declare const INVALID: INVALID; +export type DIRTY = { + status: "dirty"; + value: T; +}; +export declare const DIRTY: (value: T) => DIRTY; +export type OK = { + status: "valid"; + value: T; +}; +export declare const OK: (value: T) => OK; +export type SyncParseReturnType = OK | DIRTY | INVALID; +export type AsyncParseReturnType = Promise>; +export type ParseReturnType = SyncParseReturnType | AsyncParseReturnType; +export declare const isAborted: (x: ParseReturnType) => x is INVALID; +export declare const isDirty: (x: ParseReturnType) => x is OK | DIRTY; +export declare const isValid: (x: ParseReturnType) => x is OK; +export declare const isAsync: (x: ParseReturnType) => x is AsyncParseReturnType; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/partialUtil.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/partialUtil.d.ts new file mode 100644 index 000000000..d07657a25 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/partialUtil.d.ts @@ -0,0 +1,8 @@ +import type { ZodArray, ZodNullable, ZodObject, ZodOptional, ZodRawShape, ZodTuple, ZodTupleItems, ZodTypeAny } from "../types.js"; +export declare namespace partialUtil { + type DeepPartial = T extends ZodObject ? ZodObject<{ + [k in keyof T["shape"]]: ZodOptional>; + }, T["_def"]["unknownKeys"], T["_def"]["catchall"]> : T extends ZodArray ? ZodArray, Card> : T extends ZodOptional ? ZodOptional> : T extends ZodNullable ? ZodNullable> : T extends ZodTuple ? { + [k in keyof Items]: Items[k] extends ZodTypeAny ? DeepPartial : never; + } extends infer PI ? PI extends ZodTupleItems ? ZodTuple : never : never : T; +} diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/typeAliases.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/typeAliases.d.ts new file mode 100644 index 000000000..32df022ca --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/typeAliases.d.ts @@ -0,0 +1,2 @@ +export type Primitive = string | number | symbol | bigint | boolean | null | undefined; +export type Scalars = Primitive | Primitive[]; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/util.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/util.d.ts new file mode 100644 index 000000000..eeaaeee1c --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/helpers/util.d.ts @@ -0,0 +1,85 @@ +export declare namespace util { + type AssertEqual = (() => V extends T ? 1 : 2) extends () => V extends U ? 1 : 2 ? true : false; + export type isAny = 0 extends 1 & T ? true : false; + export const assertEqual: (_: AssertEqual) => void; + export function assertIs(_arg: T): void; + export function assertNever(_x: never): never; + export type Omit = Pick>; + export type OmitKeys = Pick>; + export type MakePartial = Omit & Partial>; + export type Exactly = T & Record, never>; + export type InexactPartial = { + [k in keyof T]?: T[k] | undefined; + }; + export const arrayToEnum: (items: U) => { [k in U[number]]: k; }; + export const getValidEnumValues: (obj: any) => any[]; + export const objectValues: (obj: any) => any[]; + export const objectKeys: ObjectConstructor["keys"]; + export const find: (arr: T[], checker: (arg: T) => any) => T | undefined; + export type identity = objectUtil.identity; + export type flatten = objectUtil.flatten; + export type noUndefined = T extends undefined ? never : T; + export const isInteger: NumberConstructor["isInteger"]; + export function joinValues(array: T, separator?: string): string; + export const jsonStringifyReplacer: (_: string, value: any) => any; + export {}; +} +export declare namespace objectUtil { + export type MergeShapes = keyof U & keyof V extends never ? U & V : { + [k in Exclude]: U[k]; + } & V; + type optionalKeys = { + [k in keyof T]: undefined extends T[k] ? k : never; + }[keyof T]; + type requiredKeys = { + [k in keyof T]: undefined extends T[k] ? never : k; + }[keyof T]; + export type addQuestionMarks = { + [K in requiredKeys]: T[K]; + } & { + [K in optionalKeys]?: T[K]; + } & { + [k in keyof T]?: unknown; + }; + export type identity = T; + export type flatten = identity<{ + [k in keyof T]: T[k]; + }>; + export type noNeverKeys = { + [k in keyof T]: [T[k]] extends [never] ? never : k; + }[keyof T]; + export type noNever = identity<{ + [k in noNeverKeys]: k extends keyof T ? T[k] : never; + }>; + export const mergeShapes: (first: U, second: T) => T & U; + export type extendShape = keyof A & keyof B extends never ? A & B : { + [K in keyof A as K extends keyof B ? never : K]: A[K]; + } & { + [K in keyof B]: B[K]; + }; + export {}; +} +export declare const ZodParsedType: { + string: "string"; + nan: "nan"; + number: "number"; + integer: "integer"; + float: "float"; + boolean: "boolean"; + date: "date"; + bigint: "bigint"; + symbol: "symbol"; + function: "function"; + undefined: "undefined"; + null: "null"; + array: "array"; + object: "object"; + unknown: "unknown"; + promise: "promise"; + void: "void"; + never: "never"; + map: "map"; + set: "set"; +}; +export type ZodParsedType = keyof typeof ZodParsedType; +export declare const getParsedType: (data: any) => ZodParsedType; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/index.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/index.d.ts new file mode 100644 index 000000000..a9dab5738 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/index.d.ts @@ -0,0 +1,4 @@ +import * as z from "./external.js"; +export * from "./external.js"; +export { z }; +export default z; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/locales/en.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/locales/en.d.ts new file mode 100644 index 000000000..b2bea1ec4 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/locales/en.d.ts @@ -0,0 +1,3 @@ +import { type ZodErrorMap } from "../ZodError.js"; +declare const errorMap: ZodErrorMap; +export default errorMap; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/standard-schema.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/standard-schema.d.ts new file mode 100644 index 000000000..7ddff3b7f --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/standard-schema.d.ts @@ -0,0 +1,102 @@ +/** + * The Standard Schema interface. + */ +export type StandardSchemaV1 = { + /** + * The Standard Schema properties. + */ + readonly "~standard": StandardSchemaV1.Props; +}; +export declare namespace StandardSchemaV1 { + /** + * The Standard Schema properties interface. + */ + export interface Props { + /** + * The version number of the standard. + */ + readonly version: 1; + /** + * The vendor name of the schema library. + */ + readonly vendor: string; + /** + * Validates unknown input values. + */ + readonly validate: (value: unknown) => Result | Promise>; + /** + * Inferred types associated with the schema. + */ + readonly types?: Types | undefined; + } + /** + * The result interface of the validate function. + */ + export type Result = SuccessResult | FailureResult; + /** + * The result interface if validation succeeds. + */ + export interface SuccessResult { + /** + * The typed output value. + */ + readonly value: Output; + /** + * The non-existent issues. + */ + readonly issues?: undefined; + } + /** + * The result interface if validation fails. + */ + export interface FailureResult { + /** + * The issues of failed validation. + */ + readonly issues: ReadonlyArray; + } + /** + * The issue interface of the failure output. + */ + export interface Issue { + /** + * The error message of the issue. + */ + readonly message: string; + /** + * The path of the issue, if any. + */ + readonly path?: ReadonlyArray | undefined; + } + /** + * The path segment interface of the issue. + */ + export interface PathSegment { + /** + * The key representing a path segment. + */ + readonly key: PropertyKey; + } + /** + * The Standard Schema types interface. + */ + export interface Types { + /** + * The input type of the schema. + */ + readonly input: Input; + /** + * The output type of the schema. + */ + readonly output: Output; + } + /** + * Infers the input type of a Standard Schema. + */ + export type InferInput = NonNullable["input"]; + /** + * Infers the output type of a Standard Schema. + */ + export type InferOutput = NonNullable["output"]; + export {}; +} diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/types.d.ts b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/types.d.ts new file mode 100644 index 000000000..301e09128 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/dist/types/v3/types.d.ts @@ -0,0 +1,1031 @@ +import { type IssueData, type StringValidation, type ZodCustomIssue, ZodError, type ZodErrorMap } from "./ZodError.js"; +import type { enumUtil } from "./helpers/enumUtil.js"; +import { errorUtil } from "./helpers/errorUtil.js"; +import { type AsyncParseReturnType, INVALID, type ParseContext, type ParseInput, type ParseParams, type ParseReturnType, ParseStatus, type SyncParseReturnType } from "./helpers/parseUtil.js"; +import type { partialUtil } from "./helpers/partialUtil.js"; +import type { Primitive } from "./helpers/typeAliases.js"; +import { util, type objectUtil } from "./helpers/util.js"; +import type { StandardSchemaV1 } from "./standard-schema.js"; +export interface RefinementCtx { + addIssue: (arg: IssueData) => void; + path: (string | number)[]; +} +export type ZodRawShape = { + [k: string]: ZodTypeAny; +}; +export type ZodTypeAny = ZodType; +export type TypeOf> = T["_output"]; +export type input> = T["_input"]; +export type output> = T["_output"]; +export type { TypeOf as infer }; +export type CustomErrorParams = Partial>; +export interface ZodTypeDef { + errorMap?: ZodErrorMap | undefined; + description?: string | undefined; +} +export type RawCreateParams = { + errorMap?: ZodErrorMap | undefined; + invalid_type_error?: string | undefined; + required_error?: string | undefined; + message?: string | undefined; + description?: string | undefined; +} | undefined; +export type ProcessedCreateParams = { + errorMap?: ZodErrorMap | undefined; + description?: string | undefined; +}; +export type SafeParseSuccess = { + success: true; + data: Output; + error?: never; +}; +export type SafeParseError = { + success: false; + error: ZodError; + data?: never; +}; +export type SafeParseReturnType = SafeParseSuccess | SafeParseError; +export declare abstract class ZodType { + readonly _type: Output; + readonly _output: Output; + readonly _input: Input; + readonly _def: Def; + get description(): string | undefined; + "~standard": StandardSchemaV1.Props; + abstract _parse(input: ParseInput): ParseReturnType; + _getType(input: ParseInput): string; + _getOrReturnCtx(input: ParseInput, ctx?: ParseContext | undefined): ParseContext; + _processInputParams(input: ParseInput): { + status: ParseStatus; + ctx: ParseContext; + }; + _parseSync(input: ParseInput): SyncParseReturnType; + _parseAsync(input: ParseInput): AsyncParseReturnType; + parse(data: unknown, params?: util.InexactPartial): Output; + safeParse(data: unknown, params?: util.InexactPartial): SafeParseReturnType; + "~validate"(data: unknown): StandardSchemaV1.Result | Promise>; + parseAsync(data: unknown, params?: util.InexactPartial): Promise; + safeParseAsync(data: unknown, params?: util.InexactPartial): Promise>; + /** Alias of safeParseAsync */ + spa: (data: unknown, params?: util.InexactPartial) => Promise>; + refine(check: (arg: Output) => arg is RefinedOutput, message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)): ZodEffects; + refine(check: (arg: Output) => unknown | Promise, message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)): ZodEffects; + refinement(check: (arg: Output) => arg is RefinedOutput, refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)): ZodEffects; + refinement(check: (arg: Output) => boolean, refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)): ZodEffects; + _refinement(refinement: RefinementEffect["refinement"]): ZodEffects; + superRefine(refinement: (arg: Output, ctx: RefinementCtx) => arg is RefinedOutput): ZodEffects; + superRefine(refinement: (arg: Output, ctx: RefinementCtx) => void): ZodEffects; + superRefine(refinement: (arg: Output, ctx: RefinementCtx) => Promise): ZodEffects; + constructor(def: Def); + optional(): ZodOptional; + nullable(): ZodNullable; + nullish(): ZodOptional>; + array(): ZodArray; + promise(): ZodPromise; + or(option: T): ZodUnion<[this, T]>; + and(incoming: T): ZodIntersection; + transform(transform: (arg: Output, ctx: RefinementCtx) => NewOut | Promise): ZodEffects; + default(def: util.noUndefined): ZodDefault; + default(def: () => util.noUndefined): ZodDefault; + brand(brand?: B): ZodBranded; + catch(def: Output): ZodCatch; + catch(def: (ctx: { + error: ZodError; + input: Input; + }) => Output): ZodCatch; + describe(description: string): this; + pipe(target: T): ZodPipeline; + readonly(): ZodReadonly; + isOptional(): boolean; + isNullable(): boolean; +} +export type IpVersion = "v4" | "v6"; +export type ZodStringCheck = { + kind: "min"; + value: number; + message?: string | undefined; +} | { + kind: "max"; + value: number; + message?: string | undefined; +} | { + kind: "length"; + value: number; + message?: string | undefined; +} | { + kind: "email"; + message?: string | undefined; +} | { + kind: "url"; + message?: string | undefined; +} | { + kind: "emoji"; + message?: string | undefined; +} | { + kind: "uuid"; + message?: string | undefined; +} | { + kind: "nanoid"; + message?: string | undefined; +} | { + kind: "cuid"; + message?: string | undefined; +} | { + kind: "includes"; + value: string; + position?: number | undefined; + message?: string | undefined; +} | { + kind: "cuid2"; + message?: string | undefined; +} | { + kind: "ulid"; + message?: string | undefined; +} | { + kind: "startsWith"; + value: string; + message?: string | undefined; +} | { + kind: "endsWith"; + value: string; + message?: string | undefined; +} | { + kind: "regex"; + regex: RegExp; + message?: string | undefined; +} | { + kind: "trim"; + message?: string | undefined; +} | { + kind: "toLowerCase"; + message?: string | undefined; +} | { + kind: "toUpperCase"; + message?: string | undefined; +} | { + kind: "jwt"; + alg?: string; + message?: string | undefined; +} | { + kind: "datetime"; + offset: boolean; + local: boolean; + precision: number | null; + message?: string | undefined; +} | { + kind: "date"; + message?: string | undefined; +} | { + kind: "time"; + precision: number | null; + message?: string | undefined; +} | { + kind: "duration"; + message?: string | undefined; +} | { + kind: "ip"; + version?: IpVersion | undefined; + message?: string | undefined; +} | { + kind: "cidr"; + version?: IpVersion | undefined; + message?: string | undefined; +} | { + kind: "base64"; + message?: string | undefined; +} | { + kind: "base64url"; + message?: string | undefined; +}; +export interface ZodStringDef extends ZodTypeDef { + checks: ZodStringCheck[]; + typeName: ZodFirstPartyTypeKind.ZodString; + coerce: boolean; +} +export declare function datetimeRegex(args: { + precision?: number | null; + offset?: boolean; + local?: boolean; +}): RegExp; +export declare class ZodString extends ZodType { + _parse(input: ParseInput): ParseReturnType; + protected _regex(regex: RegExp, validation: StringValidation, message?: errorUtil.ErrMessage): ZodEffects; + _addCheck(check: ZodStringCheck): ZodString; + email(message?: errorUtil.ErrMessage): ZodString; + url(message?: errorUtil.ErrMessage): ZodString; + emoji(message?: errorUtil.ErrMessage): ZodString; + uuid(message?: errorUtil.ErrMessage): ZodString; + nanoid(message?: errorUtil.ErrMessage): ZodString; + cuid(message?: errorUtil.ErrMessage): ZodString; + cuid2(message?: errorUtil.ErrMessage): ZodString; + ulid(message?: errorUtil.ErrMessage): ZodString; + base64(message?: errorUtil.ErrMessage): ZodString; + base64url(message?: errorUtil.ErrMessage): ZodString; + jwt(options?: { + alg?: string; + message?: string | undefined; + }): ZodString; + ip(options?: string | { + version?: IpVersion; + message?: string | undefined; + }): ZodString; + cidr(options?: string | { + version?: IpVersion; + message?: string | undefined; + }): ZodString; + datetime(options?: string | { + message?: string | undefined; + precision?: number | null; + offset?: boolean; + local?: boolean; + }): ZodString; + date(message?: string): ZodString; + time(options?: string | { + message?: string | undefined; + precision?: number | null; + }): ZodString; + duration(message?: errorUtil.ErrMessage): ZodString; + regex(regex: RegExp, message?: errorUtil.ErrMessage): ZodString; + includes(value: string, options?: { + message?: string; + position?: number; + }): ZodString; + startsWith(value: string, message?: errorUtil.ErrMessage): ZodString; + endsWith(value: string, message?: errorUtil.ErrMessage): ZodString; + min(minLength: number, message?: errorUtil.ErrMessage): ZodString; + max(maxLength: number, message?: errorUtil.ErrMessage): ZodString; + length(len: number, message?: errorUtil.ErrMessage): ZodString; + /** + * Equivalent to `.min(1)` + */ + nonempty(message?: errorUtil.ErrMessage): ZodString; + trim(): ZodString; + toLowerCase(): ZodString; + toUpperCase(): ZodString; + get isDatetime(): boolean; + get isDate(): boolean; + get isTime(): boolean; + get isDuration(): boolean; + get isEmail(): boolean; + get isURL(): boolean; + get isEmoji(): boolean; + get isUUID(): boolean; + get isNANOID(): boolean; + get isCUID(): boolean; + get isCUID2(): boolean; + get isULID(): boolean; + get isIP(): boolean; + get isCIDR(): boolean; + get isBase64(): boolean; + get isBase64url(): boolean; + get minLength(): number | null; + get maxLength(): number | null; + static create: (params?: RawCreateParams & { + coerce?: true; + }) => ZodString; +} +export type ZodNumberCheck = { + kind: "min"; + value: number; + inclusive: boolean; + message?: string | undefined; +} | { + kind: "max"; + value: number; + inclusive: boolean; + message?: string | undefined; +} | { + kind: "int"; + message?: string | undefined; +} | { + kind: "multipleOf"; + value: number; + message?: string | undefined; +} | { + kind: "finite"; + message?: string | undefined; +}; +export interface ZodNumberDef extends ZodTypeDef { + checks: ZodNumberCheck[]; + typeName: ZodFirstPartyTypeKind.ZodNumber; + coerce: boolean; +} +export declare class ZodNumber extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams & { + coerce?: boolean; + }) => ZodNumber; + gte(value: number, message?: errorUtil.ErrMessage): ZodNumber; + min: (value: number, message?: errorUtil.ErrMessage) => ZodNumber; + gt(value: number, message?: errorUtil.ErrMessage): ZodNumber; + lte(value: number, message?: errorUtil.ErrMessage): ZodNumber; + max: (value: number, message?: errorUtil.ErrMessage) => ZodNumber; + lt(value: number, message?: errorUtil.ErrMessage): ZodNumber; + protected setLimit(kind: "min" | "max", value: number, inclusive: boolean, message?: string): ZodNumber; + _addCheck(check: ZodNumberCheck): ZodNumber; + int(message?: errorUtil.ErrMessage): ZodNumber; + positive(message?: errorUtil.ErrMessage): ZodNumber; + negative(message?: errorUtil.ErrMessage): ZodNumber; + nonpositive(message?: errorUtil.ErrMessage): ZodNumber; + nonnegative(message?: errorUtil.ErrMessage): ZodNumber; + multipleOf(value: number, message?: errorUtil.ErrMessage): ZodNumber; + step: (value: number, message?: errorUtil.ErrMessage) => ZodNumber; + finite(message?: errorUtil.ErrMessage): ZodNumber; + safe(message?: errorUtil.ErrMessage): ZodNumber; + get minValue(): number | null; + get maxValue(): number | null; + get isInt(): boolean; + get isFinite(): boolean; +} +export type ZodBigIntCheck = { + kind: "min"; + value: bigint; + inclusive: boolean; + message?: string | undefined; +} | { + kind: "max"; + value: bigint; + inclusive: boolean; + message?: string | undefined; +} | { + kind: "multipleOf"; + value: bigint; + message?: string | undefined; +}; +export interface ZodBigIntDef extends ZodTypeDef { + checks: ZodBigIntCheck[]; + typeName: ZodFirstPartyTypeKind.ZodBigInt; + coerce: boolean; +} +export declare class ZodBigInt extends ZodType { + _parse(input: ParseInput): ParseReturnType; + _getInvalidInput(input: ParseInput): INVALID; + static create: (params?: RawCreateParams & { + coerce?: boolean; + }) => ZodBigInt; + gte(value: bigint, message?: errorUtil.ErrMessage): ZodBigInt; + min: (value: bigint, message?: errorUtil.ErrMessage) => ZodBigInt; + gt(value: bigint, message?: errorUtil.ErrMessage): ZodBigInt; + lte(value: bigint, message?: errorUtil.ErrMessage): ZodBigInt; + max: (value: bigint, message?: errorUtil.ErrMessage) => ZodBigInt; + lt(value: bigint, message?: errorUtil.ErrMessage): ZodBigInt; + protected setLimit(kind: "min" | "max", value: bigint, inclusive: boolean, message?: string): ZodBigInt; + _addCheck(check: ZodBigIntCheck): ZodBigInt; + positive(message?: errorUtil.ErrMessage): ZodBigInt; + negative(message?: errorUtil.ErrMessage): ZodBigInt; + nonpositive(message?: errorUtil.ErrMessage): ZodBigInt; + nonnegative(message?: errorUtil.ErrMessage): ZodBigInt; + multipleOf(value: bigint, message?: errorUtil.ErrMessage): ZodBigInt; + get minValue(): bigint | null; + get maxValue(): bigint | null; +} +export interface ZodBooleanDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodBoolean; + coerce: boolean; +} +export declare class ZodBoolean extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams & { + coerce?: boolean; + }) => ZodBoolean; +} +export type ZodDateCheck = { + kind: "min"; + value: number; + message?: string | undefined; +} | { + kind: "max"; + value: number; + message?: string | undefined; +}; +export interface ZodDateDef extends ZodTypeDef { + checks: ZodDateCheck[]; + coerce: boolean; + typeName: ZodFirstPartyTypeKind.ZodDate; +} +export declare class ZodDate extends ZodType { + _parse(input: ParseInput): ParseReturnType; + _addCheck(check: ZodDateCheck): ZodDate; + min(minDate: Date, message?: errorUtil.ErrMessage): ZodDate; + max(maxDate: Date, message?: errorUtil.ErrMessage): ZodDate; + get minDate(): Date | null; + get maxDate(): Date | null; + static create: (params?: RawCreateParams & { + coerce?: boolean; + }) => ZodDate; +} +export interface ZodSymbolDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodSymbol; +} +export declare class ZodSymbol extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodSymbol; +} +export interface ZodUndefinedDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodUndefined; +} +export declare class ZodUndefined extends ZodType { + _parse(input: ParseInput): ParseReturnType; + params?: RawCreateParams; + static create: (params?: RawCreateParams) => ZodUndefined; +} +export interface ZodNullDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodNull; +} +export declare class ZodNull extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodNull; +} +export interface ZodAnyDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodAny; +} +export declare class ZodAny extends ZodType { + _any: true; + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodAny; +} +export interface ZodUnknownDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodUnknown; +} +export declare class ZodUnknown extends ZodType { + _unknown: true; + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodUnknown; +} +export interface ZodNeverDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodNever; +} +export declare class ZodNever extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodNever; +} +export interface ZodVoidDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodVoid; +} +export declare class ZodVoid extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodVoid; +} +export interface ZodArrayDef extends ZodTypeDef { + type: T; + typeName: ZodFirstPartyTypeKind.ZodArray; + exactLength: { + value: number; + message?: string | undefined; + } | null; + minLength: { + value: number; + message?: string | undefined; + } | null; + maxLength: { + value: number; + message?: string | undefined; + } | null; +} +export type ArrayCardinality = "many" | "atleastone"; +export type arrayOutputType = Cardinality extends "atleastone" ? [T["_output"], ...T["_output"][]] : T["_output"][]; +export declare class ZodArray extends ZodType, ZodArrayDef, Cardinality extends "atleastone" ? [T["_input"], ...T["_input"][]] : T["_input"][]> { + _parse(input: ParseInput): ParseReturnType; + get element(): T; + min(minLength: number, message?: errorUtil.ErrMessage): this; + max(maxLength: number, message?: errorUtil.ErrMessage): this; + length(len: number, message?: errorUtil.ErrMessage): this; + nonempty(message?: errorUtil.ErrMessage): ZodArray; + static create: (schema: El, params?: RawCreateParams) => ZodArray; +} +export type ZodNonEmptyArray = ZodArray; +export type UnknownKeysParam = "passthrough" | "strict" | "strip"; +export interface ZodObjectDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodObject; + shape: () => T; + catchall: Catchall; + unknownKeys: UnknownKeys; +} +export type mergeTypes = { + [k in keyof A | keyof B]: k extends keyof B ? B[k] : k extends keyof A ? A[k] : never; +}; +export type objectOutputType = objectUtil.flatten>> & CatchallOutput & PassthroughType; +export type baseObjectOutputType = { + [k in keyof Shape]: Shape[k]["_output"]; +}; +export type objectInputType = objectUtil.flatten> & CatchallInput & PassthroughType; +export type baseObjectInputType = objectUtil.addQuestionMarks<{ + [k in keyof Shape]: Shape[k]["_input"]; +}>; +export type CatchallOutput = ZodType extends T ? unknown : { + [k: string]: T["_output"]; +}; +export type CatchallInput = ZodType extends T ? unknown : { + [k: string]: T["_input"]; +}; +export type PassthroughType = T extends "passthrough" ? { + [k: string]: unknown; +} : unknown; +export type deoptional = T extends ZodOptional ? deoptional : T extends ZodNullable ? ZodNullable> : T; +export type SomeZodObject = ZodObject; +export type noUnrecognized = { + [k in keyof Obj]: k extends keyof Shape ? Obj[k] : never; +}; +export declare class ZodObject, Input = objectInputType> extends ZodType, Input> { + private _cached; + _getCached(): { + shape: T; + keys: string[]; + }; + _parse(input: ParseInput): ParseReturnType; + get shape(): T; + strict(message?: errorUtil.ErrMessage): ZodObject; + strip(): ZodObject; + passthrough(): ZodObject; + /** + * @deprecated In most cases, this is no longer needed - unknown properties are now silently stripped. + * If you want to pass through unknown properties, use `.passthrough()` instead. + */ + nonstrict: () => ZodObject; + extend(augmentation: Augmentation): ZodObject, UnknownKeys, Catchall>; + /** + * @deprecated Use `.extend` instead + * */ + augment: (augmentation: Augmentation) => ZodObject, UnknownKeys, Catchall>; + /** + * Prior to zod@1.0.12 there was a bug in the + * inferred type of merged objects. Please + * upgrade if you are experiencing issues. + */ + merge(merging: Incoming): ZodObject, Incoming["_def"]["unknownKeys"], Incoming["_def"]["catchall"]>; + setKey(key: Key, schema: Schema): ZodObject; + catchall(index: Index): ZodObject; + pick>(mask: Mask): ZodObject>, UnknownKeys, Catchall>; + omit>(mask: Mask): ZodObject, UnknownKeys, Catchall>; + /** + * @deprecated + */ + deepPartial(): partialUtil.DeepPartial; + partial(): ZodObject<{ + [k in keyof T]: ZodOptional; + }, UnknownKeys, Catchall>; + partial>(mask: Mask): ZodObject : T[k]; + }>, UnknownKeys, Catchall>; + required(): ZodObject<{ + [k in keyof T]: deoptional; + }, UnknownKeys, Catchall>; + required>(mask: Mask): ZodObject : T[k]; + }>, UnknownKeys, Catchall>; + keyof(): ZodEnum>; + static create: (shape: Shape, params?: RawCreateParams) => ZodObject, objectInputType>; + static strictCreate: (shape: Shape, params?: RawCreateParams) => ZodObject; + static lazycreate: (shape: () => Shape, params?: RawCreateParams) => ZodObject; +} +export type AnyZodObject = ZodObject; +export type ZodUnionOptions = Readonly<[ZodTypeAny, ...ZodTypeAny[]]>; +export interface ZodUnionDef> extends ZodTypeDef { + options: T; + typeName: ZodFirstPartyTypeKind.ZodUnion; +} +export declare class ZodUnion extends ZodType, T[number]["_input"]> { + _parse(input: ParseInput): ParseReturnType; + get options(): T; + static create: >(types: Options, params?: RawCreateParams) => ZodUnion; +} +export type ZodDiscriminatedUnionOption = ZodObject<{ + [key in Discriminator]: ZodTypeAny; +} & ZodRawShape, UnknownKeysParam, ZodTypeAny>; +export interface ZodDiscriminatedUnionDef[] = ZodDiscriminatedUnionOption[]> extends ZodTypeDef { + discriminator: Discriminator; + options: Options; + optionsMap: Map>; + typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion; +} +export declare class ZodDiscriminatedUnion[]> extends ZodType, ZodDiscriminatedUnionDef, input> { + _parse(input: ParseInput): ParseReturnType; + get discriminator(): Discriminator; + get options(): Options; + get optionsMap(): Map>; + /** + * The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor. + * However, it only allows a union of objects, all of which need to share a discriminator property. This property must + * have a different value for each object in the union. + * @param discriminator the name of the discriminator property + * @param types an array of object schemas + * @param params + */ + static create, + ...ZodDiscriminatedUnionOption[] + ]>(discriminator: Discriminator, options: Types, params?: RawCreateParams): ZodDiscriminatedUnion; +} +export interface ZodIntersectionDef extends ZodTypeDef { + left: T; + right: U; + typeName: ZodFirstPartyTypeKind.ZodIntersection; +} +export declare class ZodIntersection extends ZodType, T["_input"] & U["_input"]> { + _parse(input: ParseInput): ParseReturnType; + static create: (left: TSchema, right: USchema, params?: RawCreateParams) => ZodIntersection; +} +export type ZodTupleItems = [ZodTypeAny, ...ZodTypeAny[]]; +export type AssertArray = T extends any[] ? T : never; +export type OutputTypeOfTuple = AssertArray<{ + [k in keyof T]: T[k] extends ZodType ? T[k]["_output"] : never; +}>; +export type OutputTypeOfTupleWithRest = Rest extends ZodTypeAny ? [...OutputTypeOfTuple, ...Rest["_output"][]] : OutputTypeOfTuple; +export type InputTypeOfTuple = AssertArray<{ + [k in keyof T]: T[k] extends ZodType ? T[k]["_input"] : never; +}>; +export type InputTypeOfTupleWithRest = Rest extends ZodTypeAny ? [...InputTypeOfTuple, ...Rest["_input"][]] : InputTypeOfTuple; +export interface ZodTupleDef extends ZodTypeDef { + items: T; + rest: Rest; + typeName: ZodFirstPartyTypeKind.ZodTuple; +} +export type AnyZodTuple = ZodTuple<[ZodTypeAny, ...ZodTypeAny[]] | [], ZodTypeAny | null>; +export declare class ZodTuple extends ZodType, ZodTupleDef, InputTypeOfTupleWithRest> { + _parse(input: ParseInput): ParseReturnType; + get items(): T; + rest(rest: RestSchema): ZodTuple; + static create: (schemas: Items, params?: RawCreateParams) => ZodTuple; +} +export interface ZodRecordDef extends ZodTypeDef { + valueType: Value; + keyType: Key; + typeName: ZodFirstPartyTypeKind.ZodRecord; +} +export type KeySchema = ZodType; +export type RecordType = [string] extends [K] ? Record : [number] extends [K] ? Record : [symbol] extends [K] ? Record : [BRAND] extends [K] ? Record : Partial>; +export declare class ZodRecord extends ZodType, ZodRecordDef, RecordType> { + get keySchema(): Key; + get valueSchema(): Value; + _parse(input: ParseInput): ParseReturnType; + get element(): Value; + static create(valueType: Value, params?: RawCreateParams): ZodRecord; + static create(keySchema: Keys, valueType: Value, params?: RawCreateParams): ZodRecord; +} +export interface ZodMapDef extends ZodTypeDef { + valueType: Value; + keyType: Key; + typeName: ZodFirstPartyTypeKind.ZodMap; +} +export declare class ZodMap extends ZodType, ZodMapDef, Map> { + get keySchema(): Key; + get valueSchema(): Value; + _parse(input: ParseInput): ParseReturnType; + static create: (keyType: KeySchema, valueType: ValueSchema, params?: RawCreateParams) => ZodMap; +} +export interface ZodSetDef extends ZodTypeDef { + valueType: Value; + typeName: ZodFirstPartyTypeKind.ZodSet; + minSize: { + value: number; + message?: string | undefined; + } | null; + maxSize: { + value: number; + message?: string | undefined; + } | null; +} +export declare class ZodSet extends ZodType, ZodSetDef, Set> { + _parse(input: ParseInput): ParseReturnType; + min(minSize: number, message?: errorUtil.ErrMessage): this; + max(maxSize: number, message?: errorUtil.ErrMessage): this; + size(size: number, message?: errorUtil.ErrMessage): this; + nonempty(message?: errorUtil.ErrMessage): ZodSet; + static create: (valueType: ValueSchema, params?: RawCreateParams) => ZodSet; +} +export interface ZodFunctionDef = ZodTuple, Returns extends ZodTypeAny = ZodTypeAny> extends ZodTypeDef { + args: Args; + returns: Returns; + typeName: ZodFirstPartyTypeKind.ZodFunction; +} +export type OuterTypeOfFunction, Returns extends ZodTypeAny> = Args["_input"] extends Array ? (...args: Args["_input"]) => Returns["_output"] : never; +export type InnerTypeOfFunction, Returns extends ZodTypeAny> = Args["_output"] extends Array ? (...args: Args["_output"]) => Returns["_input"] : never; +export declare class ZodFunction, Returns extends ZodTypeAny> extends ZodType, ZodFunctionDef, InnerTypeOfFunction> { + _parse(input: ParseInput): ParseReturnType; + parameters(): Args; + returnType(): Returns; + args[0]>(...items: Items): ZodFunction, Returns>; + returns>(returnType: NewReturnType): ZodFunction; + implement>(func: F): ReturnType extends Returns["_output"] ? (...args: Args["_input"]) => ReturnType : OuterTypeOfFunction; + strictImplement(func: InnerTypeOfFunction): InnerTypeOfFunction; + validate: >(func: F) => ReturnType extends Returns["_output"] ? (...args: Args["_input"]) => ReturnType : OuterTypeOfFunction; + static create(): ZodFunction, ZodUnknown>; + static create>(args: T): ZodFunction; + static create(args: T, returns: U): ZodFunction; + static create, U extends ZodTypeAny = ZodUnknown>(args: T, returns: U, params?: RawCreateParams): ZodFunction; +} +export interface ZodLazyDef extends ZodTypeDef { + getter: () => T; + typeName: ZodFirstPartyTypeKind.ZodLazy; +} +export declare class ZodLazy extends ZodType, ZodLazyDef, input> { + get schema(): T; + _parse(input: ParseInput): ParseReturnType; + static create: (getter: () => Inner, params?: RawCreateParams) => ZodLazy; +} +export interface ZodLiteralDef extends ZodTypeDef { + value: T; + typeName: ZodFirstPartyTypeKind.ZodLiteral; +} +export declare class ZodLiteral extends ZodType, T> { + _parse(input: ParseInput): ParseReturnType; + get value(): T; + static create: (value: Value, params?: RawCreateParams) => ZodLiteral; +} +export type ArrayKeys = keyof any[]; +export type Indices = Exclude; +export type EnumValues = readonly [T, ...T[]]; +export type Values = { + [k in T[number]]: k; +}; +export interface ZodEnumDef extends ZodTypeDef { + values: T; + typeName: ZodFirstPartyTypeKind.ZodEnum; +} +export type Writeable = { + -readonly [P in keyof T]: T[P]; +}; +export type FilterEnum = Values extends [] ? [] : Values extends [infer Head, ...infer Rest] ? Head extends ToExclude ? FilterEnum : [Head, ...FilterEnum] : never; +export type typecast = A extends T ? A : never; +declare function createZodEnum>(values: T, params?: RawCreateParams): ZodEnum>; +declare function createZodEnum(values: T, params?: RawCreateParams): ZodEnum; +export declare class ZodEnum extends ZodType, T[number]> { + _cache: Set | undefined; + _parse(input: ParseInput): ParseReturnType; + get options(): T; + get enum(): Values; + get Values(): Values; + get Enum(): Values; + extract(values: ToExtract, newDef?: RawCreateParams): ZodEnum>; + exclude(values: ToExclude, newDef?: RawCreateParams): ZodEnum>, [string, ...string[]]>>; + static create: typeof createZodEnum; +} +export interface ZodNativeEnumDef extends ZodTypeDef { + values: T; + typeName: ZodFirstPartyTypeKind.ZodNativeEnum; +} +export type EnumLike = { + [k: string]: string | number; + [nu: number]: string; +}; +export declare class ZodNativeEnum extends ZodType, T[keyof T]> { + _cache: Set | undefined; + _parse(input: ParseInput): ParseReturnType; + get enum(): T; + static create: (values: Elements, params?: RawCreateParams) => ZodNativeEnum; +} +export interface ZodPromiseDef extends ZodTypeDef { + type: T; + typeName: ZodFirstPartyTypeKind.ZodPromise; +} +export declare class ZodPromise extends ZodType, ZodPromiseDef, Promise> { + unwrap(): T; + _parse(input: ParseInput): ParseReturnType; + static create: (schema: Inner, params?: RawCreateParams) => ZodPromise; +} +export type Refinement = (arg: T, ctx: RefinementCtx) => any; +export type SuperRefinement = (arg: T, ctx: RefinementCtx) => void | Promise; +export type RefinementEffect = { + type: "refinement"; + refinement: (arg: T, ctx: RefinementCtx) => any; +}; +export type TransformEffect = { + type: "transform"; + transform: (arg: T, ctx: RefinementCtx) => any; +}; +export type PreprocessEffect = { + type: "preprocess"; + transform: (arg: T, ctx: RefinementCtx) => any; +}; +export type Effect = RefinementEffect | TransformEffect | PreprocessEffect; +export interface ZodEffectsDef extends ZodTypeDef { + schema: T; + typeName: ZodFirstPartyTypeKind.ZodEffects; + effect: Effect; +} +export declare class ZodEffects, Input = input> extends ZodType, Input> { + innerType(): T; + sourceType(): T; + _parse(input: ParseInput): ParseReturnType; + static create: (schema: I, effect: Effect, params?: RawCreateParams) => ZodEffects; + static createWithPreprocess: (preprocess: (arg: unknown, ctx: RefinementCtx) => unknown, schema: I, params?: RawCreateParams) => ZodEffects; +} +export { ZodEffects as ZodTransformer }; +export interface ZodOptionalDef extends ZodTypeDef { + innerType: T; + typeName: ZodFirstPartyTypeKind.ZodOptional; +} +export type ZodOptionalType = ZodOptional; +export declare class ZodOptional extends ZodType, T["_input"] | undefined> { + _parse(input: ParseInput): ParseReturnType; + unwrap(): T; + static create: (type: Inner, params?: RawCreateParams) => ZodOptional; +} +export interface ZodNullableDef extends ZodTypeDef { + innerType: T; + typeName: ZodFirstPartyTypeKind.ZodNullable; +} +export type ZodNullableType = ZodNullable; +export declare class ZodNullable extends ZodType, T["_input"] | null> { + _parse(input: ParseInput): ParseReturnType; + unwrap(): T; + static create: (type: Inner, params?: RawCreateParams) => ZodNullable; +} +export interface ZodDefaultDef extends ZodTypeDef { + innerType: T; + defaultValue: () => util.noUndefined; + typeName: ZodFirstPartyTypeKind.ZodDefault; +} +export declare class ZodDefault extends ZodType, ZodDefaultDef, T["_input"] | undefined> { + _parse(input: ParseInput): ParseReturnType; + removeDefault(): T; + static create: (type: Inner, params: RawCreateParams & { + default: Inner["_input"] | (() => util.noUndefined); + }) => ZodDefault; +} +export interface ZodCatchDef extends ZodTypeDef { + innerType: T; + catchValue: (ctx: { + error: ZodError; + input: unknown; + }) => T["_input"]; + typeName: ZodFirstPartyTypeKind.ZodCatch; +} +export declare class ZodCatch extends ZodType, unknown> { + _parse(input: ParseInput): ParseReturnType; + removeCatch(): T; + static create: (type: Inner, params: RawCreateParams & { + catch: Inner["_output"] | (() => Inner["_output"]); + }) => ZodCatch; +} +export interface ZodNaNDef extends ZodTypeDef { + typeName: ZodFirstPartyTypeKind.ZodNaN; +} +export declare class ZodNaN extends ZodType { + _parse(input: ParseInput): ParseReturnType; + static create: (params?: RawCreateParams) => ZodNaN; +} +export interface ZodBrandedDef extends ZodTypeDef { + type: T; + typeName: ZodFirstPartyTypeKind.ZodBranded; +} +export declare const BRAND: unique symbol; +export type BRAND = { + [BRAND]: { + [k in T]: true; + }; +}; +export declare class ZodBranded extends ZodType, ZodBrandedDef, T["_input"]> { + _parse(input: ParseInput): ParseReturnType; + unwrap(): T; +} +export interface ZodPipelineDef extends ZodTypeDef { + in: A; + out: B; + typeName: ZodFirstPartyTypeKind.ZodPipeline; +} +export declare class ZodPipeline extends ZodType, A["_input"]> { + _parse(input: ParseInput): ParseReturnType; + static create(a: ASchema, b: BSchema): ZodPipeline; +} +type BuiltIn = (((...args: any[]) => any) | (new (...args: any[]) => any)) | { + readonly [Symbol.toStringTag]: string; +} | Date | Error | Generator | Promise | RegExp; +type MakeReadonly = T extends Map ? ReadonlyMap : T extends Set ? ReadonlySet : T extends [infer Head, ...infer Tail] ? readonly [Head, ...Tail] : T extends Array ? ReadonlyArray : T extends BuiltIn ? T : Readonly; +export interface ZodReadonlyDef extends ZodTypeDef { + innerType: T; + typeName: ZodFirstPartyTypeKind.ZodReadonly; +} +export declare class ZodReadonly extends ZodType, ZodReadonlyDef, MakeReadonly> { + _parse(input: ParseInput): ParseReturnType; + static create: (type: Inner, params?: RawCreateParams) => ZodReadonly; + unwrap(): T; +} +type CustomParams = CustomErrorParams & { + fatal?: boolean; +}; +export declare function custom(check?: (data: any) => any, _params?: string | CustomParams | ((input: any) => CustomParams), +/** + * @deprecated + * + * Pass `fatal` into the params object instead: + * + * ```ts + * z.string().custom((val) => val.length > 5, { fatal: false }) + * ``` + * + */ +fatal?: boolean): ZodType; +export { ZodType as Schema, ZodType as ZodSchema }; +export declare const late: { + object: (shape: () => Shape, params?: RawCreateParams) => ZodObject; +}; +export declare enum ZodFirstPartyTypeKind { + ZodString = "ZodString", + ZodNumber = "ZodNumber", + ZodNaN = "ZodNaN", + ZodBigInt = "ZodBigInt", + ZodBoolean = "ZodBoolean", + ZodDate = "ZodDate", + ZodSymbol = "ZodSymbol", + ZodUndefined = "ZodUndefined", + ZodNull = "ZodNull", + ZodAny = "ZodAny", + ZodUnknown = "ZodUnknown", + ZodNever = "ZodNever", + ZodVoid = "ZodVoid", + ZodArray = "ZodArray", + ZodObject = "ZodObject", + ZodUnion = "ZodUnion", + ZodDiscriminatedUnion = "ZodDiscriminatedUnion", + ZodIntersection = "ZodIntersection", + ZodTuple = "ZodTuple", + ZodRecord = "ZodRecord", + ZodMap = "ZodMap", + ZodSet = "ZodSet", + ZodFunction = "ZodFunction", + ZodLazy = "ZodLazy", + ZodLiteral = "ZodLiteral", + ZodEnum = "ZodEnum", + ZodEffects = "ZodEffects", + ZodNativeEnum = "ZodNativeEnum", + ZodOptional = "ZodOptional", + ZodNullable = "ZodNullable", + ZodDefault = "ZodDefault", + ZodCatch = "ZodCatch", + ZodPromise = "ZodPromise", + ZodBranded = "ZodBranded", + ZodPipeline = "ZodPipeline", + ZodReadonly = "ZodReadonly" +} +export type ZodFirstPartySchemaTypes = ZodString | ZodNumber | ZodNaN | ZodBigInt | ZodBoolean | ZodDate | ZodUndefined | ZodNull | ZodAny | ZodUnknown | ZodNever | ZodVoid | ZodArray | ZodObject | ZodUnion | ZodDiscriminatedUnion | ZodIntersection | ZodTuple | ZodRecord | ZodMap | ZodSet | ZodFunction | ZodLazy | ZodLiteral | ZodEnum | ZodEffects | ZodNativeEnum | ZodOptional | ZodNullable | ZodDefault | ZodCatch | ZodPromise | ZodBranded | ZodPipeline | ZodReadonly | ZodSymbol; +declare abstract class Class { + constructor(..._: any[]); +} +declare const instanceOfType: (cls: T, params?: CustomParams) => ZodType, ZodTypeDef, InstanceType>; +declare const stringType: (params?: RawCreateParams & { + coerce?: true; +}) => ZodString; +declare const numberType: (params?: RawCreateParams & { + coerce?: boolean; +}) => ZodNumber; +declare const nanType: (params?: RawCreateParams) => ZodNaN; +declare const bigIntType: (params?: RawCreateParams & { + coerce?: boolean; +}) => ZodBigInt; +declare const booleanType: (params?: RawCreateParams & { + coerce?: boolean; +}) => ZodBoolean; +declare const dateType: (params?: RawCreateParams & { + coerce?: boolean; +}) => ZodDate; +declare const symbolType: (params?: RawCreateParams) => ZodSymbol; +declare const undefinedType: (params?: RawCreateParams) => ZodUndefined; +declare const nullType: (params?: RawCreateParams) => ZodNull; +declare const anyType: (params?: RawCreateParams) => ZodAny; +declare const unknownType: (params?: RawCreateParams) => ZodUnknown; +declare const neverType: (params?: RawCreateParams) => ZodNever; +declare const voidType: (params?: RawCreateParams) => ZodVoid; +declare const arrayType: (schema: El, params?: RawCreateParams) => ZodArray; +declare const objectType: (shape: Shape, params?: RawCreateParams) => ZodObject, objectInputType>; +declare const strictObjectType: (shape: Shape, params?: RawCreateParams) => ZodObject; +declare const unionType: >(types: Options, params?: RawCreateParams) => ZodUnion; +declare const discriminatedUnionType: typeof ZodDiscriminatedUnion.create; +declare const intersectionType: (left: TSchema, right: USchema, params?: RawCreateParams) => ZodIntersection; +declare const tupleType: (schemas: Items, params?: RawCreateParams) => ZodTuple; +declare const recordType: typeof ZodRecord.create; +declare const mapType: (keyType: KeySchema, valueType: ValueSchema, params?: RawCreateParams) => ZodMap; +declare const setType: (valueType: ValueSchema, params?: RawCreateParams) => ZodSet; +declare const functionType: typeof ZodFunction.create; +declare const lazyType: (getter: () => Inner, params?: RawCreateParams) => ZodLazy; +declare const literalType: (value: Value, params?: RawCreateParams) => ZodLiteral; +declare const enumType: typeof createZodEnum; +declare const nativeEnumType: (values: Elements, params?: RawCreateParams) => ZodNativeEnum; +declare const promiseType: (schema: Inner, params?: RawCreateParams) => ZodPromise; +declare const effectsType: (schema: I, effect: Effect, params?: RawCreateParams) => ZodEffects; +declare const optionalType: (type: Inner, params?: RawCreateParams) => ZodOptional; +declare const nullableType: (type: Inner, params?: RawCreateParams) => ZodNullable; +declare const preprocessType: (preprocess: (arg: unknown, ctx: RefinementCtx) => unknown, schema: I, params?: RawCreateParams) => ZodEffects; +declare const pipelineType: typeof ZodPipeline.create; +declare const ostring: () => ZodOptional; +declare const onumber: () => ZodOptional; +declare const oboolean: () => ZodOptional; +export declare const coerce: { + string: (typeof ZodString)["create"]; + number: (typeof ZodNumber)["create"]; + boolean: (typeof ZodBoolean)["create"]; + bigint: (typeof ZodBigInt)["create"]; + date: (typeof ZodDate)["create"]; +}; +export { anyType as any, arrayType as array, bigIntType as bigint, booleanType as boolean, dateType as date, discriminatedUnionType as discriminatedUnion, effectsType as effect, enumType as enum, functionType as function, instanceOfType as instanceof, intersectionType as intersection, lazyType as lazy, literalType as literal, mapType as map, nanType as nan, nativeEnumType as nativeEnum, neverType as never, nullType as null, nullableType as nullable, numberType as number, objectType as object, oboolean, onumber, optionalType as optional, ostring, pipelineType as pipeline, preprocessType as preprocess, promiseType as promise, recordType as record, setType as set, strictObjectType as strictObject, stringType as string, symbolType as symbol, effectsType as transformer, tupleType as tuple, undefinedType as undefined, unionType as union, unknownType as unknown, voidType as void, }; +export declare const NEVER: never; diff --git a/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/package.json b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/package.json new file mode 100644 index 000000000..e02b87614 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/node_modules/zod/package.json @@ -0,0 +1,120 @@ +{ + "name": "zod", + "version": "3.25.67", + "type": "module", + "author": "Colin McDonnell ", + "description": "TypeScript-first schema declaration and validation library with static type inference", + "files": ["dist", "v3", "v4", "v4-mini"], + "funding": "https://github.com/sponsors/colinhacks", + "homepage": "https://zod.dev", + "keywords": ["typescript", "schema", "validation", "type", "inference"], + "license": "MIT", + "sideEffects": false, + "main": "./dist/cjs/index.js", + "types": "./dist/types/index.d.ts", + "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": { + "@zod/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "@zod/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "default": "./dist/cjs/index.js" + } + }, + "./v3": { + "import": { + "@zod/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "@zod/source": "./src/index.ts", + "types": "./dist/types/index.d.ts", + "default": "./dist/cjs/index.js" + } + }, + "./v4": { + "import": { + "@zod/source": "./src/v4/index.ts", + "types": "./dist/types/v4/index.d.ts", + "default": "./dist/esm/v4/index.js" + }, + "require": { + "@zod/source": "./src/v4/index.ts", + "types": "./dist/types/v4/index.d.ts", + "default": "./dist/cjs/v4/index.js" + } + }, + "./v4-mini": { + "@zod/source": "./src/v4/mini/index.ts", + "types": "./dist/types/v4/mini/index.d.ts", + "import": "./dist/esm/v4/mini/index.js", + "require": "./dist/cjs/v4/mini/index.js" + }, + "./v4/mini": { + "@zod/source": "./src/v4/mini/index.ts", + "types": "./dist/types/v4/mini/index.d.ts", + "import": "./dist/esm/v4/mini/index.js", + "require": "./dist/cjs/v4/mini/index.js" + }, + "./v4/core": { + "@zod/source": "./src/v4/core/index.ts", + "types": "./dist/types/v4/core/index.d.ts", + "import": "./dist/esm/v4/core/index.js", + "require": "./dist/cjs/v4/core/index.js" + }, + "./v4/locales": { + "@zod/source": "./src/v4/locales/index.ts", + "types": "./dist/types/v4/locales/index.d.ts", + "import": "./dist/esm/v4/locales/index.js", + "require": "./dist/cjs/v4/locales/index.js" + }, + "./v4/locales/*": { + "@zod/source": "./src/v4/locales/*", + "types": "./dist/types/v4/locales/*", + "import": "./dist/esm/v4/locales/*", + "require": "./dist/cjs/v4/locales/*" + }, + "./package.json": "./package.json" + }, + "tshy": { + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./v3": "./src/v3/index.ts", + "./v4": "./src/v4/index.ts", + "./v4-mini": "./src/v4/mini/index.ts", + "./v4/core": "./src/v4/core/index.ts", + "./v4/locales": "./src/v4/locales/index.ts", + "./v4/locales/*": "./src/v4/locales/*" + }, + "sourceDialects": ["@zod/source"], + "exclude": ["./src/**/*.test.ts"] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/colinhacks/zod.git" + }, + "bugs": { + "url": "https://github.com/colinhacks/zod/issues" + }, + "support": { + "backing": { + "npm-funding": true + } + }, + "scripts": { + "clean": "rm -rf dist", + "build": "tsx ./build.mts", + "postbuild": "pnpm biome check --write .", + "test:watch": "pnpm vitest", + "test": "pnpm vitest run", + "bump:beta": "pnpm version \"v$(pnpm pkg get version | jq -r)-beta.$(date +%Y%m%dT%H%M%S)\"", + "pub:beta": "pnpm bump:beta && pnpm publish --tag next --publish-branch v4 --no-git-checks --dry-run" + } +} diff --git a/tests/fixtures/npm/cases/zod3-boundary/package.json b/tests/fixtures/npm/cases/zod3-boundary/package.json new file mode 100644 index 000000000..be0bf47db --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/package.json @@ -0,0 +1 @@ +{ "name": "zod3-boundary-case", "private": true, "type": "module" } diff --git a/tests/fixtures/npm/cases/zod3-boundary/rewrites.ts b/tests/fixtures/npm/cases/zod3-boundary/rewrites.ts new file mode 100644 index 000000000..7a98cccf0 --- /dev/null +++ b/tests/fixtures/npm/cases/zod3-boundary/rewrites.ts @@ -0,0 +1,21 @@ +// Rewrites the declaration-typed boundary makes in user code: a typed +// parameter of a bare `async` arrow zod calls, and a cast at the start of +// an unbraced loop body. +import { z } from "zod"; + +const Box = z.object({ n: z.number() }); +const raw: unknown = { n: 2 }; + +// The cast opens the loop body's statement; the loop runs it each time. +let seen = 0; +for (let i = 0; i < 3; i++) Box.parse(raw).n > 0 && seen++; +let left = 2; +while (left-- > 0) Box.parse(raw).n > 1 && seen++; +console.log("loops", seen); + +// zod calls the callback with the string it checked. +const Length = z.string().transform(async s => s.length + 1); +async function main(): Promise { + console.log("async arrow", await Length.parseAsync("abc")); +} +void main(); diff --git a/tests/harness/__snapshots__/inspect-custom-fences.ts.txt b/tests/harness/__snapshots__/inspect-custom-fences.ts.txt index ba7cefefc..4f5da9030 100644 --- a/tests/harness/__snapshots__/inspect-custom-fences.ts.txt +++ b/tests/harness/__snapshots__/inspect-custom-fences.ts.txt @@ -7,43 +7,29 @@ inspect-custom-fences.ts:16:21 - error SC2020: 'util.inspect of 'Plain[]' values hint: classes whose subclasses declare [util.inspect.custom] have no inspect lowering yet (declare the method on the class itself) -inspect-custom-fences.ts:19:3 - error SC1090: [util.inspect.custom] methods on Error subclasses are not supported yet - - 18 | class Failure extends Error { - 19 | [inspect.custom](): string { - | ^~~~~~~~~~~~~~~~ - 20 | return "failure"; - -inspect-custom-fences.ts:23:13 - error SC1090: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet +inspect-custom-fences.ts:23:21 - error SC2020: 'util.inspect of 'Wide' values' is part of the standard library types but has no scriptc lowering yet 22 | } - 23 | console.log(new Failure("x").message); - | ^~~~~~~~~~~~~~~~ - 24 | - -inspect-custom-fences.ts:30:21 - error SC2020: 'util.inspect of 'Wide' values' is part of the standard library types but has no scriptc lowering yet - - 29 | } - 30 | console.log(inspect(new Wide())); + 23 | console.log(inspect(new Wide())); | ^~~~~~~~~~ - 31 | + 24 | hint: [util.inspect.custom] methods with more than three parameters have no inspect lowering -inspect-custom-fences.ts:37:21 - error SC2020: 'util.inspect of 'Echo' values' is part of the standard library types but has no scriptc lowering yet +inspect-custom-fences.ts:30:21 - error SC2020: 'util.inspect of 'Echo' values' is part of the standard library types but has no scriptc lowering yet - 36 | } - 37 | console.log(inspect(new Echo())); + 29 | } + 30 | console.log(inspect(new Echo())); | ^~~~~~~~~~ - 38 | + 31 | hint: [util.inspect.custom] methods that return a class instance have no inspect lowering -inspect-custom-fences.ts:44:26 - error SC2020: 'util.format %o over values with [util.inspect.custom] methods' is part of the standard library types but has no scriptc lowering yet +inspect-custom-fences.ts:37:26 - error SC2020: 'util.format %o over values with [util.inspect.custom] methods' is part of the standard library types but has no scriptc lowering yet - 43 | } - 44 | console.log(format("%o", new Shown())); + 36 | } + 37 | console.log(format("%o", new Shown())); | ^~~~~~~~~~~ - 45 | + 38 | hint: %o passes showHidden to the methods — use %O \ No newline at end of file diff --git a/tests/harness/corpus-costs/7144-paired-while-counters.ts.json b/tests/harness/corpus-costs/7144-paired-while-counters.ts.json new file mode 100644 index 000000000..935249de9 --- /dev/null +++ b/tests/harness/corpus-costs/7144-paired-while-counters.ts.json @@ -0,0 +1 @@ +{ "plain": 3300, "san": 4600 } diff --git a/tests/harness/corpus-costs/7262-fresh-literal-untyped-param/main.ts.json b/tests/harness/corpus-costs/7262-fresh-literal-untyped-param/main.ts.json new file mode 100644 index 000000000..62c164e5c --- /dev/null +++ b/tests/harness/corpus-costs/7262-fresh-literal-untyped-param/main.ts.json @@ -0,0 +1 @@ +{ "plain": 2800, "san": 3200 } diff --git a/tests/harness/corpus-costs/7365-class-methods-untyped-computed-writes/main.ts.json b/tests/harness/corpus-costs/7365-class-methods-untyped-computed-writes/main.ts.json new file mode 100644 index 000000000..0347bbd22 --- /dev/null +++ b/tests/harness/corpus-costs/7365-class-methods-untyped-computed-writes/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3400 } diff --git a/tests/harness/corpus-costs/7366-typed-global-view-arrays.ts.json b/tests/harness/corpus-costs/7366-typed-global-view-arrays.ts.json new file mode 100644 index 000000000..27b641ffe --- /dev/null +++ b/tests/harness/corpus-costs/7366-typed-global-view-arrays.ts.json @@ -0,0 +1 @@ +{ "plain": 2900, "san": 3200 } diff --git a/tests/harness/corpus-costs/7402-unknown-array-methods.ts.json b/tests/harness/corpus-costs/7402-unknown-array-methods.ts.json new file mode 100644 index 000000000..9eb118813 --- /dev/null +++ b/tests/harness/corpus-costs/7402-unknown-array-methods.ts.json @@ -0,0 +1 @@ +{ "plain": 2600, "san": 20000 } diff --git a/tests/harness/corpus-costs/7403-bigint-of-typed-arrays.mjs.json b/tests/harness/corpus-costs/7403-bigint-of-typed-arrays.mjs.json new file mode 100644 index 000000000..754441b3e --- /dev/null +++ b/tests/harness/corpus-costs/7403-bigint-of-typed-arrays.mjs.json @@ -0,0 +1 @@ +{ "plain": 1738, "san": 3053 } diff --git a/tests/harness/corpus-costs/7441-untyped-receiver-own-string-method-names.ts.json b/tests/harness/corpus-costs/7441-untyped-receiver-own-string-method-names.ts.json new file mode 100644 index 000000000..615c617ae --- /dev/null +++ b/tests/harness/corpus-costs/7441-untyped-receiver-own-string-method-names.ts.json @@ -0,0 +1 @@ +{ "plain": 5800 } diff --git a/tests/harness/corpus-costs/7480-builtin-constructor-in.js.json b/tests/harness/corpus-costs/7480-builtin-constructor-in.js.json new file mode 100644 index 000000000..20168ce9a --- /dev/null +++ b/tests/harness/corpus-costs/7480-builtin-constructor-in.js.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 3800 } diff --git a/tests/harness/corpus-costs/7481-class-runtime-heritage-object-error.js.json b/tests/harness/corpus-costs/7481-class-runtime-heritage-object-error.js.json new file mode 100644 index 000000000..20168ce9a --- /dev/null +++ b/tests/harness/corpus-costs/7481-class-runtime-heritage-object-error.js.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 3800 } diff --git a/tests/harness/corpus-costs/7482-define-property-function-class-values.js.json b/tests/harness/corpus-costs/7482-define-property-function-class-values.js.json new file mode 100644 index 000000000..20168ce9a --- /dev/null +++ b/tests/harness/corpus-costs/7482-define-property-function-class-values.js.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 3800 } diff --git a/tests/harness/corpus-costs/7483-constructor-factory-traits.js.json b/tests/harness/corpus-costs/7483-constructor-factory-traits.js.json new file mode 100644 index 000000000..20168ce9a --- /dev/null +++ b/tests/harness/corpus-costs/7483-constructor-factory-traits.js.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 3800 } diff --git a/tests/harness/corpus-costs/7484-hoisted-function-captures-later-class.js.json b/tests/harness/corpus-costs/7484-hoisted-function-captures-later-class.js.json new file mode 100644 index 000000000..20168ce9a --- /dev/null +++ b/tests/harness/corpus-costs/7484-hoisted-function-captures-later-class.js.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 3800 } diff --git a/tests/harness/corpus-costs/7485-global-object-own-properties.js.json b/tests/harness/corpus-costs/7485-global-object-own-properties.js.json new file mode 100644 index 000000000..20168ce9a --- /dev/null +++ b/tests/harness/corpus-costs/7485-global-object-own-properties.js.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 3800 } diff --git a/tests/harness/corpus-costs/7486-prototype-accessor-string-method-names.js.json b/tests/harness/corpus-costs/7486-prototype-accessor-string-method-names.js.json new file mode 100644 index 000000000..20168ce9a --- /dev/null +++ b/tests/harness/corpus-costs/7486-prototype-accessor-string-method-names.js.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 3800 } diff --git a/tests/harness/corpus-costs/7487-namespace-spread-untyped/main.js.json b/tests/harness/corpus-costs/7487-namespace-spread-untyped/main.js.json new file mode 100644 index 000000000..20168ce9a --- /dev/null +++ b/tests/harness/corpus-costs/7487-namespace-spread-untyped/main.js.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 3800 } diff --git a/tests/harness/corpus-costs/7488-math-spread-untyped.js.json b/tests/harness/corpus-costs/7488-math-spread-untyped.js.json new file mode 100644 index 000000000..20168ce9a --- /dev/null +++ b/tests/harness/corpus-costs/7488-math-spread-untyped.js.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 3800 } diff --git a/tests/harness/corpus-costs/7489-math-spread-generator-only.js.json b/tests/harness/corpus-costs/7489-math-spread-generator-only.js.json new file mode 100644 index 000000000..2aa0f8038 --- /dev/null +++ b/tests/harness/corpus-costs/7489-math-spread-generator-only.js.json @@ -0,0 +1 @@ +{ "plain": 3384, "san": 4732 } diff --git a/tests/harness/corpus-costs/7536-inspect-custom-errors.ts.json b/tests/harness/corpus-costs/7536-inspect-custom-errors.ts.json new file mode 100644 index 000000000..bc864c5b3 --- /dev/null +++ b/tests/harness/corpus-costs/7536-inspect-custom-errors.ts.json @@ -0,0 +1 @@ +{ "plain": 5921, "san": 72405 } diff --git a/tests/harness/corpus-costs/7537-inspect-custom-errors.cjs.json b/tests/harness/corpus-costs/7537-inspect-custom-errors.cjs.json new file mode 100644 index 000000000..cfee42705 --- /dev/null +++ b/tests/harness/corpus-costs/7537-inspect-custom-errors.cjs.json @@ -0,0 +1 @@ +{ "plain": 4421, "san": 8837 } diff --git a/tests/harness/corpus-costs/7538-inspect-custom-prototypes.mjs.json b/tests/harness/corpus-costs/7538-inspect-custom-prototypes.mjs.json new file mode 100644 index 000000000..d5314d959 --- /dev/null +++ b/tests/harness/corpus-costs/7538-inspect-custom-prototypes.mjs.json @@ -0,0 +1 @@ +{ "plain": 2529, "san": 2333 } diff --git a/tests/harness/corpus-costs/7565-iterable-replaceable-method-results.ts.json b/tests/harness/corpus-costs/7565-iterable-replaceable-method-results.ts.json new file mode 100644 index 000000000..6d81df217 --- /dev/null +++ b/tests/harness/corpus-costs/7565-iterable-replaceable-method-results.ts.json @@ -0,0 +1 @@ +{ "plain": 8000, "san": 14000 } diff --git a/tests/harness/corpus-costs/7600-navigator-optional-chain.js.json b/tests/harness/corpus-costs/7600-navigator-optional-chain.js.json new file mode 100644 index 000000000..e350cf2da --- /dev/null +++ b/tests/harness/corpus-costs/7600-navigator-optional-chain.js.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 22000 } diff --git a/tests/harness/corpus-costs/7602-error-stack-limit-value.js.json b/tests/harness/corpus-costs/7602-error-stack-limit-value.js.json new file mode 100644 index 000000000..bb380e241 --- /dev/null +++ b/tests/harness/corpus-costs/7602-error-stack-limit-value.js.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 3000 } diff --git a/tests/harness/corpus-costs/7603-error-stack-limit-own.js.json b/tests/harness/corpus-costs/7603-error-stack-limit-own.js.json new file mode 100644 index 000000000..bb380e241 --- /dev/null +++ b/tests/harness/corpus-costs/7603-error-stack-limit-own.js.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 3000 } diff --git a/tests/harness/corpus-costs/7604-error-stack-limit-frozen.js.json b/tests/harness/corpus-costs/7604-error-stack-limit-frozen.js.json new file mode 100644 index 000000000..bb380e241 --- /dev/null +++ b/tests/harness/corpus-costs/7604-error-stack-limit-frozen.js.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 3000 } diff --git a/tests/harness/decl-boundary.test.ts b/tests/harness/decl-boundary.test.ts new file mode 100644 index 000000000..931208836 --- /dev/null +++ b/tests/harness/decl-boundary.test.ts @@ -0,0 +1,68 @@ +/* The declaration-typed boundary's checks (decl-projection.ts): a value + * zod's declarations type wrongly fails the boundary cast with a TypeError + * naming the path, and a value the native record cannot represent (a + * frozen object) fails with the reason. Node does not check, so this is a + * native-only test; npm.test.ts runs the differential cases + * (zod3-boundary, zod3-adoption, zod3-infer). The rewrite test checks that + * a file keeps its projection (no SC6102 note) where the rewrite has to + * parenthesize a bare async arrow's parameter and cast the start of an + * unbraced loop body. */ +import { execFile } from "node:child_process"; +import { mkdirSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); +const repoRoot = join(import.meta.dirname, "../.."); +const entry = join(repoRoot, "tests/fixtures/npm/cases/zod3-boundary/lying.ts"); +const outDir = join(repoRoot, "node_modules/.cache/scriptc-tests/decl-boundary"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +test( + "boundary casts throw a path TypeError for values the declarations misdescribe", + { timeout: 600_000 }, + async () => { + mkdirSync(outDir, { recursive: true }); + const result = await compile(entry, { outPath: join(outDir, "lying"), outDir, sanitize }); + if (!result.ok) { + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + } + const pending = execFileAsync(result.binaryPath, [], { cwd: dirname(entry), encoding: "utf8" }); + pending.child.stdin?.end(); + const { stdout } = await pending; + expect(stdout).toBe( + "liar threw true expected number at $.n, got string\n" + + "frozen threw expected a plain object (this value is frozen, sealed or not extensible) at $, got object\n", + ); + }, +); + +test( + "boundary rewrites keep bare async arrows and unbraced loop bodies valid", + { timeout: 600_000 }, + async () => { + const rewrites = join(repoRoot, "tests/fixtures/npm/cases/zod3-boundary/rewrites.ts"); + mkdirSync(outDir, { recursive: true }); + const result = await compile(rewrites, { + outPath: join(outDir, "rewrites"), + outDir, + sanitize, + }); + if (!result.ok) { + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + } + // The file keeps its projection: no SC6102 note drops it (a build + // that lowered nothing carries no warnings at all). + expect((result.warnings ?? []).map((w) => w.code)).not.toContain("SC6102"); + const pending = execFileAsync(result.binaryPath, [], { + cwd: dirname(rewrites), + encoding: "utf8", + }); + pending.child.stdin?.end(); + const { stdout } = await pending; + // Node's output: every loop iteration runs the cast statement. + expect(stdout).toBe("loops 5\nasync arrow 4\n"); + }, +); diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index 400b0be6f..e88121a26 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -1731,9 +1731,10 @@ describe("builtin constructor values (JS lane)", () => { test("members without a lowering stay refused instead of reading undefined", async () => { // Builtin constructors read as interned function values that model only // name, length and constructor. Every other static member or prototype - // read (direct, bracketed, `in`, through an alias or globalThis), and + // read (direct, bracketed, through an alias or globalThis), and // reflection on or redefinition of their members, must stay a located // SC2020/SC1090 refusal, never an undefined or otherwise wrong value. + // `key in Ctor` answers from the constructor's fixed keys. // Test262 lost 769 refusals to undefined reads before this was pinned. const reads = [ ["Map.prototype", "Map.prototype"], @@ -1751,7 +1752,6 @@ describe("builtin constructor values (JS lane)", () => { ], ["isExtensible(Date.prototype)", "Object.isExtensible(Date.prototype)"], ["Map['prototype']", 'Map["prototype"]'], - ["'prototype' in Map", '"prototype" in Map'], ["globalThis.Map.prototype", "globalThis.Map.prototype"], ["Map alias prototype", "MapAlias.prototype"], ["Date alias prototype", "DateAlias.prototype"], @@ -1774,6 +1774,7 @@ console.log(Date.length, RegExp.length, Promise.length, Object.length, Map.lengt console.log(Date.name, Map.name, Map.constructor === Function, Object.constructor === Function); const promisePrototype = Promise.prototype; console.log(typeof promisePrototype, typeof promisePrototype.then, promisePrototype.constructor === Promise); +console.log("prototype" in Map, "groupBy" in Map, "captureStackTrace" in Error, "nope" in Map); `; const r = await compileAndRun("builtin-constructor-members", source, "cjs"); expect(r.stderr).toBe(""); @@ -1788,11 +1789,13 @@ console.log(typeof promisePrototype, typeof promisePrototype.then, promiseProtot ), ), ); - // Promise.prototype reads as an object holding the runtime's methods. + // Promise.prototype reads as an object holding the runtime's methods, + // and `in` on a builtin constructor answers from its fixed keys. expect(lines.slice(reads.length)).toEqual([ "7 2 1 1 0 0", "Date Map true true", "object function true", + "true true true false", ]); }); }); diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index 9a13c0968..461b7fbcd 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -112,13 +112,16 @@ const KNOWN: Record = { const cases = shardSelect(npmCases(fixturesRoot), (c) => c.name); /** Per-case timeouts past the default 120 s: the undici cases compile the - * built-in undici implementation's module graph, and zod3 and zod3-infer - * compile zod 3's 3,700-line types module. A sanitized build of either - * takes minutes on a CI runner. */ + * built-in undici implementation's module graph, and the zod 3 cases + * (zod3, zod3-adoption, zod3-boundary, zod3-infer) compile zod 3's + * 3,700-line types module. A sanitized build of either takes minutes on a + * CI runner. */ const TIMEOUTS: Record = { undici5: 600_000, undici7: 600_000, zod3: 600_000, + "zod3-adoption": 600_000, + "zod3-boundary": 600_000, "zod3-infer": 600_000, }; diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index 4d5546ab6..85fa73410 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -108,7 +108,7 @@ for (const backend of ["llvm"] as const) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 33, marker: "scr_runtime_abi_v33" }, + runtime_abi: { version: 35, marker: "scr_runtime_abi_v35" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: [], sanitizer: "external-toolchain-required" }, flavors: {