From c271aa85aa4ed98cae5602ccc17393fdbc53b882 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 16:45:21 -0500 Subject: [PATCH 001/230] Count holes written through dynamic views of typed arrays --- .../compiler/src/frontend/lowering/lowerer.ts | 103 ++++++++++++++++++ .../lowering/runtime-optional-elements.ts | 62 +++++++++-- .../test/runtime-optional-precision.test.ts | 25 +++++ .../6067-view-holes-in-typed-arrays.ts.json | 6 + .../corpus/6067-view-holes-in-typed-arrays.ts | 57 ++++++++++ .../6067-view-holes-in-typed-arrays.ts.json | 1 + 6 files changed, 247 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6067-view-holes-in-typed-arrays.ts.json create mode 100644 tests/corpus/6067-view-holes-in-typed-arrays.ts create mode 100644 tests/harness/corpus-costs/6067-view-holes-in-typed-arrays.ts.json diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ae91a87d5..e737eca64 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6309,7 +6309,110 @@ export class Lowerer { }); return keys; }; + // Element ABIs of the native arrays a value converted to a dynamic type + // can reach: the conversion keeps each such array as a live view, so + // stores through the dynamic value write the native storage. + const collectNativeViews = (): Set => { + const keys = new Set(); + const seen = new Set(); + const collect = (type: IrType | null): void => { + if (!type) return; + const id = typeKey(type); + if (seen.has(id)) return; + seen.add(id); + if (type.kind === "array") { + if (type.elem.kind !== "dyn") keys.add(typeKey(type.elem)); + collect(type.elem); + } else if (type.kind === "union") { + for (const arm of this.unions.get(type.unionId)?.arms ?? []) collect(arm); + } else if (type.kind === "record") { + for (const field of this.shapes.get(type.shapeId)?.fields ?? []) collect(field.type); + } else if (type.kind === "object") { + for (const field of this.classes.get(type.className)?.fields.values() ?? []) + collect(field); + } else if (type.kind === "map") { + collect(type.key); + collect(type.value); + } else if (type.kind === "set") collect(type.elem); + else if (type.kind === "promise") collect(type.inner); + }; + const holdsDynamic = (type: IrType | null, depth: number): boolean => { + if (!type || depth > 4) return false; + if (type.kind === "dyn") return true; + if (type.kind === "array" || type.kind === "set") return holdsDynamic(type.elem, depth + 1); + if (type.kind === "map") return holdsDynamic(type.value, depth + 1); + if (type.kind === "promise") return holdsDynamic(type.inner, depth + 1); + if (type.kind === "union") + return (this.unions.get(type.unionId)?.arms ?? []).some((arm) => + holdsDynamic(arm, depth + 1), + ); + if (type.kind === "record") + return (this.shapes.get(type.shapeId)?.fields ?? []).some((field) => + holdsDynamic(field.type, depth + 1), + ); + return false; + }; + const mapped = (type: ts.Type | null | undefined): IrType | null => + type ? panicSafe(() => this.mapTypeOf(type), null) : null; + const flow = (source: ts.Expression, target: ts.Type | null | undefined): void => { + if (target === undefined) return; + const into = target === null ? null : mapped(target); + // An unanswerable target may be dynamic. + if (target !== null && into !== null && !holdsDynamic(into, 0)) return; + const from = typeOrNull(source); + if (from === null) keys.add("*"); + else collect(mapped(from)); + }; + const visit = (node: ts.Node): void => { + if (ts.isAsExpression(node) || ts.isTypeAssertion(node)) + flow(node.expression, typeOrNull(node)); + else if (ts.isVariableDeclaration(node) && node.type && node.initializer) + flow(node.initializer, typeOrNull(node.name)); + else if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) + flow(node.right, typeOrNull(node.left)); + else if (ts.isReturnStatement(node) && node.expression) + flow(node.expression, contextualOrNull(node.expression)); + else if (ts.isCallExpression(node) || ts.isNewExpression(node)) { + for (const arg of node.arguments?.nodes ?? []) + flow(ts.isSpreadElement(arg) ? arg.expression : arg, contextualOrNull(arg)); + } else if (ts.isPropertyAssignment(node)) + flow(node.initializer, contextualOrNull(node.initializer)); + else if (ts.isShorthandPropertyAssignment(node)) { + const name = node.name as ts.Expression; + flow(name, contextualOrNull(name)); + } else if (ts.isArrayLiteralExpression(node)) { + for (const element of node.elements.nodes) + if (!ts.isSpreadElement(element) && !ts.isOmittedExpression(element)) + flow(element, contextualOrNull(element)); + } else if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) + flow(node.body as ts.Expression, contextualOrNull(node.body as ts.Expression)); + }; + for (const sf of sourceFiles) + ts.walkPreorder(sf, (node) => { + // A checker panic hides this conversion: any array. + if (!panicSafe(() => (visit(node), true), false)) keys.add("*"); + return undefined; + }); + return keys; + }; + const dynamicType = (type: ts.Type): boolean => { + if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) return true; + if (this.checker.isArrayType(type) && !this.checker.isTupleType(type)) { + const element = this.checker.getTypeArguments(type as ts.TypeReference)[0]; + if (element && (element.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) + return true; + } + return panicSafe(() => this.mapTypeOf(type)?.kind === "dyn", false); + }; const elementStates = new ArrayElementStates({ + isDynamicArray: (node) => { + const type = typeOrNull(node); + return type === null || dynamicType(type); + }, + nativeViewKeys: collectNativeViews, elementAdmitsUndefined: (key) => { siteDependencies?.read(key); return optionalElementKeys.has(key); diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index 96d20d2c4..8fc0c4a95 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -37,6 +37,13 @@ export interface ElementStateHost { ownerOf(node: ts.Expression): ts.Symbol | null; /** The owning slot a newly built array is stored into. */ allocationOwner(node: ts.Expression): ts.Symbol | null; + /** `node` is a dynamic value (`any`, `unknown`, `unknown[]`): when it is + * a live view of a native array, a store through it writes that array's + * storage. */ + isDynamicArray(node: ts.Expression): boolean; + /** Element keys of the native arrays a value converted to a dynamic type + * can reach, as live views ("*" for any array). */ + nativeViewKeys(): ReadonlySet; /** Debug provenance for a new fact. */ note: ((fact: "undefined" | "holes", key: string, site: ts.Node | undefined) => void) | null; /** The scan read, or added to, the facts of an element key, of an owning @@ -48,6 +55,8 @@ export interface ElementStateHost { /** The fact name of the facts shared by every array. */ const ANY_ARRAY = "*"; +/** The fact name of holes stored through dynamic views. */ +const VIEW_HOLES = "%view"; /** What an array value can contain besides present values. */ interface Contents { @@ -81,6 +90,10 @@ export class ArrayElementStates { * whose element ABI is decided per generic instantiation. */ private anyUndefined = false; private anyHoley = false; + /** A store through a dynamic value can leave holes; they land in every + * native array such a value views. */ + private viewHoles = false; + private viewKeys: ReadonlySet | null = null; /** Facts of arrays owned by one storage slot. */ private readonly undefinedOwners = new Set(); private readonly holeyOwners = new Set(); @@ -330,14 +343,32 @@ export class ArrayElementStates { this.host.read?.(ANY_ARRAY); if (keys === null) return null; for (const key of keys) this.host.read?.(key); + this.host.read?.(VIEW_HOLES); return { undefined: this.anyUndefined || keys.some((key) => this.undefinedKeys.has(key) || this.host.elementAdmitsUndefined(key)), - holes: this.anyHoley || keys.some((key) => this.holeyKeys.has(key)), + holes: this.anyHoley || keys.some((key) => this.holeyKeys.has(key) || this.viewed(key)), }; } + /** Holes stored through a dynamic view can be in arrays of this key. */ + private viewed(key: string): boolean { + if (!this.viewHoles) return false; + this.viewKeys ??= this.host.nativeViewKeys(); + return this.viewKeys.has(key) || this.viewKeys.has(ANY_ARRAY); + } + + /** A store through `receiver`, which has no native element key, can + * leave holes. Only a dynamic value can be a view of a native array. */ + private addViewHoles(receiver: ts.Expression): boolean { + if (this.viewHoles || !this.host.isDynamicArray(receiver)) return false; + this.viewHoles = true; + this.host.changed?.(VIEW_HOLES); + this.host.note?.("holes", VIEW_HOLES, this.site); + return true; + } + /** Reading every index of `node` (a spread, an iteration, `Array.from`) * can produce `undefined`. Only native arrays can: other iterables reject * an undefined value when it is stored. */ @@ -427,7 +458,11 @@ export class ArrayElementStates { /** A store through `receiver` can leave holes. */ private addHolesTo(receiver: ts.Expression): boolean { const owner = this.host.ownerOf(receiver); - if (owner === null) return this.addHoles(this.host.arrayKeys(receiver)); + if (owner === null) { + const keys = this.host.arrayKeys(receiver); + if (keys !== null && keys.length === 0) return this.addViewHoles(receiver); + return this.addHoles(keys); + } if (this.holeyOwners.has(owner)) return false; this.holeyOwners.add(owner); this.host.changed?.(owner); @@ -490,7 +525,8 @@ export class ArrayElementStates { const undefinedValue = storesValue && this.host.mayBeUndefined(node.right); if (!holes && !undefinedValue) return false; const keys = this.host.arrayKeys(target.expression); - if (keys !== null && keys.length === 0) return false; + if (keys !== null && keys.length === 0) + return holes && !namesProperty(target) && this.addViewHoles(target.expression); let changed = false; if (holes && this.addHolesTo(target.expression)) changed = true; if (undefinedValue && this.addUndefinedTo(target.expression)) changed = true; @@ -501,8 +537,6 @@ export class ArrayElementStates { target.name.text === "length" && !isShrinkingLength(node, target) ) { - const keys = this.host.arrayKeys(target.expression); - if (keys !== null && keys.length === 0) return false; return this.addHolesTo(target.expression); } return false; @@ -574,10 +608,16 @@ export class ArrayElementStates { )); if (!reflective) return false; let changed = false; - for (const arg of args) { + // The written object: `Reflect.apply`'s receiver argument, otherwise + // the first argument. + const written = this.host.isGlobal(owner, "Reflect") && method === "apply" ? 1 : 0; + for (const [index, arg] of args.entries()) { const target = ts.isSpreadElement(arg) ? arg.expression : arg; const keys = this.host.arrayKeys(target); - if (keys !== null && keys.length === 0) continue; + if (keys !== null && keys.length === 0) { + if (index === written && this.addViewHoles(target)) changed = true; + continue; + } if (this.addUndefinedTo(target)) changed = true; if (this.addHolesTo(target)) changed = true; } @@ -761,6 +801,14 @@ function isAppendIndex(target: ts.ElementAccessExpression): boolean { ); } +/** `o["name"]`: a string literal key that is not an array index writes a + * named property, never an element. */ +function namesProperty(target: ts.ElementAccessExpression): boolean { + const key = peel(target.argumentExpression); + if (!ts.isStringLiteral(key) && !ts.isNoSubstitutionTemplateLiteral(key)) return false; + return !/^(0|[1-9][0-9]*)$/.test(key.text) || Number(key.text) >= 2 ** 32 - 1; +} + /** `xs.length = 0`, `xs.length = xs.length - k` and `xs.length -= k` * truncate; truncation never leaves holes. */ function isShrinkingLength( diff --git a/packages/compiler/test/runtime-optional-precision.test.ts b/packages/compiler/test/runtime-optional-precision.test.ts index 7840b0c62..3a3251b5a 100644 --- a/packages/compiler/test/runtime-optional-precision.test.ts +++ b/packages/compiler/test/runtime-optional-precision.test.ts @@ -102,6 +102,31 @@ console.log(copy.map((item) => weigh(item)).join(",")); expect(signature(spread, "weigh")).toBe("weigh(Item|undefined): f64"); }); +test("holes stored through a dynamic view widen callbacks over the arrays it views", async () => { + const viewed = await lower(`${ITEM} +const items: Item[] = [new Item(1)]; +const view: unknown[] = items; +view[3] = new Item(2); +console.log(items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(viewed, "weigh")).toBe("weigh(Item|undefined): f64"); + // A dynamic store cannot reach an element type no dynamic value views. + const other = await lower(`${ITEM} +const items: Item[] = [new Item(1)]; +const values: unknown[] = [1]; +values[3] = 2; +console.log(items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(other, "weigh")).toBe("weigh(Item): f64"); + // A view without stores that leave holes keeps the parameters. + const read = await lower(`${ITEM} +const items: Item[] = [new Item(1)]; +const view: unknown[] = items; +console.log(view.length, items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(read, "weigh")).toBe("weigh(Item): f64"); +}); + test("holes that only their owning slot can observe stay out of callbacks", async () => { const module = await lower(`${ITEM} class Cache { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6067-view-holes-in-typed-arrays.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6067-view-holes-in-typed-arrays.ts.json new file mode 100644 index 000000000..9d257fd49 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6067-view-holes-in-typed-arrays.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6067-view-holes-in-typed-arrays.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6067-view-holes-in-typed-arrays.ts b/tests/corpus/6067-view-holes-in-typed-arrays.ts new file mode 100644 index 000000000..b59437646 --- /dev/null +++ b/tests/corpus/6067-view-holes-in-typed-arrays.ts @@ -0,0 +1,57 @@ +// Holes written through an `unknown[]` or `any` view of a typed array are +// holes of the typed array: for...of visits them as undefined and the find +// family passes them to its callback, as in Node. The views reach the array +// through a binding, an `any` parameter, a generic instantiated with +// `unknown`, a record field, a class field and a nested array. + +const numbers: number[] = [1, 2]; +const numbersView: unknown[] = numbers; +numbersView[3] = 5; +for (const value of numbers) console.log("numbers", value, typeof value); +console.log(numbers.findIndex((value) => value === undefined)); +console.log(numbers.findLastIndex((value) => value === undefined)); +console.log(numbers.find((value) => value !== undefined && value > 1)); + +const grown: string[] = ["a"]; +const grownView: unknown[] = grown; +grownView.length = 3; +for (const value of grown) console.log("grown", value); + +function growAny(values: any): void { + values[2] = 9; +} +const passed: number[] = [7]; +growAny(passed); +for (const value of passed) console.log("passed", value); + +function keep(value: T): T { + return value; +} +const kept: number[] = [3]; +(keep(kept) as unknown[])[2] = 4; +for (const value of kept) console.log("kept", value); + +const boxed: number[] = [1]; +const box: unknown = { values: boxed }; +(box as { values: unknown[] }).values[2] = 3; +for (const value of boxed) console.log("boxed", value); + +class Holder { + values: number[] = [1, 2]; +} +const holder = new Holder(); +const holderView: unknown = holder; +(holderView as { values: unknown[] }).values[3] = 4; +for (const value of holder.values) console.log("holder", value); + +const nested: number[][] = [[1]]; +const nestedView: unknown[][] = nested; +nestedView[0]![2] = 3; +for (const value of nested[0]!) console.log("nested", value); + +// A named property written through a view is not an element. +const named: number[] = [1, 2]; +const bag = named as unknown as Record; +bag["label"] = "x"; +for (const value of named) console.log("named", value); +console.log(named.length, bag["label"]); diff --git a/tests/harness/corpus-costs/6067-view-holes-in-typed-arrays.ts.json b/tests/harness/corpus-costs/6067-view-holes-in-typed-arrays.ts.json new file mode 100644 index 000000000..b8b740bf8 --- /dev/null +++ b/tests/harness/corpus-costs/6067-view-holes-in-typed-arrays.ts.json @@ -0,0 +1 @@ +{ "plain": 3967, "san": 4623 } From 7cbb6d3b371003b188201fdff61a6145dad7ed5a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 18:28:44 -0500 Subject: [PATCH 002/230] Count only stores and flows that can reach a typed array's view --- .../compiler/src/frontend/lowering/lowerer.ts | 41 +++++++++++++++---- .../test/runtime-optional-precision.test.ts | 18 ++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index e737eca64..ccb91291c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6312,6 +6312,27 @@ export class Lowerer { // Element ABIs of the native arrays a value converted to a dynamic type // can reach: the conversion keeps each such array as a live view, so // stores through the dynamic value write the native storage. + // A standard library function only reads an array argument it takes as + // `any` (console.log, JSON.stringify, String), except the reflective + // writes: Object.assign and defineProperty, Reflect.set and the like, + // and Array.prototype methods through call or apply. + const reflectiveWriters = new Set([ + "assign", + "defineProperty", + "defineProperties", + "setPrototypeOf", + "set", + "apply", + "call", + ]); + const standardLibraryReader = (node: ts.CallExpression | ts.NewExpression): boolean => + panicSafe(() => { + const callee = peel(node.expression); + if (ts.isPropertyAccessExpression(callee) && reflectiveWriters.has(callee.name.text)) + return false; + const declaration = this.checker.getResolvedSignature(node)?.declaration; + return declaration !== undefined && this.isStdlibFile(ts.getSourceFileOfNode(declaration)); + }, false); const collectNativeViews = (): Set => { const keys = new Set(); const seen = new Set(); @@ -6376,8 +6397,9 @@ export class Lowerer { else if (ts.isReturnStatement(node) && node.expression) flow(node.expression, contextualOrNull(node.expression)); else if (ts.isCallExpression(node) || ts.isNewExpression(node)) { - for (const arg of node.arguments?.nodes ?? []) - flow(ts.isSpreadElement(arg) ? arg.expression : arg, contextualOrNull(arg)); + if (!standardLibraryReader(node)) + for (const arg of node.arguments?.nodes ?? []) + flow(ts.isSpreadElement(arg) ? arg.expression : arg, contextualOrNull(arg)); } else if (ts.isPropertyAssignment(node)) flow(node.initializer, contextualOrNull(node.initializer)); else if (ts.isShorthandPropertyAssignment(node)) { @@ -6398,14 +6420,17 @@ export class Lowerer { }); return keys; }; + // A receiver typed `any`, `unknown` or an array of either can be a live + // view of a native array. A receiver of another type (`Worker[]`, + // `Record`) is one only after a lying assertion, even + // when its values are dynamic. const dynamicType = (type: ts.Type): boolean => { if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) return true; - if (this.checker.isArrayType(type) && !this.checker.isTupleType(type)) { - const element = this.checker.getTypeArguments(type as ts.TypeReference)[0]; - if (element && (element.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) - return true; - } - return panicSafe(() => this.mapTypeOf(type)?.kind === "dyn", false); + if (!this.checker.isArrayType(type) || this.checker.isTupleType(type)) return false; + const element = this.checker.getTypeArguments(type as ts.TypeReference)[0]; + return ( + element !== undefined && (element.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 + ); }; const elementStates = new ArrayElementStates({ isDynamicArray: (node) => { diff --git a/packages/compiler/test/runtime-optional-precision.test.ts b/packages/compiler/test/runtime-optional-precision.test.ts index 3a3251b5a..64f0bd86e 100644 --- a/packages/compiler/test/runtime-optional-precision.test.ts +++ b/packages/compiler/test/runtime-optional-precision.test.ts @@ -125,6 +125,24 @@ const view: unknown[] = items; console.log(view.length, items.findIndex((item) => weigh(item) > 1)); `); expect(signature(read, "weigh")).toBe("weigh(Item): f64"); + // Standard library functions that take arrays as `any` only read them. + const readers = await lower(`${ITEM} +const items: Item[] = [new Item(1)]; +console.log(items.length, JSON.stringify(items)); +const values: unknown[] = [1]; +values[3] = 2; +console.log(items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(readers, "weigh")).toBe("weigh(Item): f64"); + // A store through a Record type is not a store through a view. + const record = await lower(`${ITEM} +const items: Item[] = [new Item(1)]; +const view: unknown = items; +const bag: Record = {}; +bag["3"] = view; +console.log(items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(record, "weigh")).toBe("weigh(Item): f64"); }); test("holes that only their owning slot can observe stay out of callbacks", async () => { From 0d016b7554ebdeb9c537ee768a5a89c5fd482704 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 19:57:46 -0500 Subject: [PATCH 003/230] util: runtime format strings, inspect.custom and colors, untyped promisify --- .../limitations/builtin-function-values.mdx | 2 +- .../console-and-process-streams.mdx | 7 + .../src/generated/node-v24-compatibility.json | 12 +- .../generated/node-v24-internal.json | 35 +- internal/compatibility/static-support.json | 8 +- .../ambient/scriptc-node-fallback.d.ts | 57 +- .../compiler/src/backend/llvm/lib-shared.ts | 16 + .../lowering/builtins/promisify-callback.ts | 26 +- .../src/frontend/lowering/call-arguments.ts | 159 ++- .../lowering/expressions/object-literals.ts | 20 +- .../lowering/expressions/property-access.ts | 11 + .../src/frontend/lowering/lower-calls.ts | 30 + .../src/frontend/lowering/lower-classes.ts | 13 + .../src/frontend/lowering/lower-console.ts | 38 +- .../src/frontend/lowering/lower-inspect.ts | 779 +++++++++++- .../src/frontend/lowering/lower-modules.ts | 13 +- .../compiler/src/frontend/lowering/lowerer.ts | 4 + .../src/frontend/lowering/surfaces.ts | 3 +- .../src/frontend/lowering/symbol-fields.ts | 29 + packages/compiler/src/frontend/type-mapper.ts | 24 + packages/compiler/src/ir/builtin-effects.ts | 11 + .../compiler/src/ir/builtin-signatures.ts | 37 +- .../corpus/7520-util-format-runtime.ts.json | 6 + .../corpus/7521-util-format-runtime.cjs.json | 6 + .../corpus/7522-promisify-untyped.cjs.json | 6 + .../corpus/7523-promisify-untyped.ts.json | 6 + .../tests/corpus/7524-inspect-custom.ts.json | 6 + .../tests/corpus/7525-inspect-custom.cjs.json | 6 + .../7526-inspect-options-runtime.ts.json | 6 + .../diagnostics/inspect-custom-fences.ts.json | 6 + packages/runtime/src/scr_console_native.c | 6 +- packages/runtime/src/scr_dyn.h | 4 + packages/runtime/src/scr_inspect.c | 1123 ++++++++++++++++- packages/runtime/src/scr_inspect.h | 71 +- packages/runtime/src/scr_json.c | 18 + packages/runtime/src/scr_symbol.c | 5 + packages/runtime/src/scr_util_callable.c | 166 +-- tests/corpus/7520-util-format-runtime.ts | 110 ++ tests/corpus/7521-util-format-runtime.cjs | 64 + tests/corpus/7522-promisify-untyped.cjs | 64 + tests/corpus/7523-promisify-untyped.ts | 38 + tests/corpus/7524-inspect-custom.ts | 91 ++ tests/corpus/7525-inspect-custom.cjs | 75 ++ tests/corpus/7526-inspect-options-runtime.ts | 62 + tests/diagnostics/inspect-custom-fences.ts | 44 + .../inspect-custom-fences.ts.txt | 49 + .../7520-util-format-runtime.ts.json | 1 + .../7521-util-format-runtime.cjs.json | 1 + .../7522-promisify-untyped.cjs.json | 1 + .../7523-promisify-untyped.ts.json | 1 + .../corpus-costs/7524-inspect-custom.ts.json | 1 + .../corpus-costs/7525-inspect-custom.cjs.json | 1 + .../7526-inspect-options-runtime.ts.json | 1 + tests/harness/errors.test.ts | 47 + 54 files changed, 3101 insertions(+), 325 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7520-util-format-runtime.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7521-util-format-runtime.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7522-promisify-untyped.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7523-promisify-untyped.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7524-inspect-custom.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7525-inspect-custom.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7526-inspect-options-runtime.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inspect-custom-fences.ts.json create mode 100644 tests/corpus/7520-util-format-runtime.ts create mode 100644 tests/corpus/7521-util-format-runtime.cjs create mode 100644 tests/corpus/7522-promisify-untyped.cjs create mode 100644 tests/corpus/7523-promisify-untyped.ts create mode 100644 tests/corpus/7524-inspect-custom.ts create mode 100644 tests/corpus/7525-inspect-custom.cjs create mode 100644 tests/corpus/7526-inspect-options-runtime.ts create mode 100644 tests/diagnostics/inspect-custom-fences.ts create mode 100644 tests/harness/__snapshots__/inspect-custom-fences.ts.txt create mode 100644 tests/harness/corpus-costs/7520-util-format-runtime.ts.json create mode 100644 tests/harness/corpus-costs/7521-util-format-runtime.cjs.json create mode 100644 tests/harness/corpus-costs/7522-promisify-untyped.cjs.json create mode 100644 tests/harness/corpus-costs/7523-promisify-untyped.ts.json create mode 100644 tests/harness/corpus-costs/7524-inspect-custom.ts.json create mode 100644 tests/harness/corpus-costs/7525-inspect-custom.cjs.json create mode 100644 tests/harness/corpus-costs/7526-inspect-options-runtime.ts.json diff --git a/docs/content/limitations/builtin-function-values.mdx b/docs/content/limitations/builtin-function-values.mdx index 0fa62e558..efd45af2f 100644 --- a/docs/content/limitations/builtin-function-values.mdx +++ b/docs/content/limitations/builtin-function-values.mdx @@ -14,7 +14,7 @@ A class can extend `require("events")` directly. Other immutable aliases of table-backed Node builtins support direct calls, but cannot be passed elsewhere. `util.promisify` supports compile-time conversions of `child_process.execFile`, `child_process.exec`, `setTimeout` and `setImmediate` (to the `timers/promises` functions, as in Node), `stream.finished` and `stream.pipeline` (to the `stream/promises` functions, as in Node), the `zlib` callback one-shots `deflate`, `inflate`, `deflateRaw`, `inflateRaw`, `gzip`, `gunzip` and `unzip` (with or without options), the `crypto` callback functions `randomBytes`, `pbkdf2`, `scrypt`, `hkdf`, `randomFill`, `randomInt` and `generateKey`, `dns.lookup` (settling with `{ address, family }`, or the address array with `all: true`) and `dns.lookupService` (settling with `{ hostname, service }`), and the `fs` callback functions that have an `fs/promises` counterpart (`access`, `appendFile`, `chmod`, `chown`, `copyFile`, `lchown`, `link`, `lstat`, `lutimes`, `mkdir`, `mkdtemp`, `readFile`, `readdir`, `readlink`, `realpath`, `rename`, `rm`, `rmdir`, `stat`, `statfs`, `symlink`, `truncate`, `unlink`, `utimes`, and `writeFile`); calls through the converted function lower as the `fs/promises` call. -`util.promisify` over an untyped (`any`) function returns a native wrapper that follows Node's callback rules and carries the original's `name` and `length`. +`util.promisify` over an untyped function (a value typed `any`, a function whose parameters are untyped, or any other target in JavaScript) returns a native wrapper that follows Node's callback rules and carries the original's `name` and `length`, and throws Node's `ERR_INVALID_ARG_TYPE` for values that are not functions. Such a function with other own properties, `util.promisify.custom` among them, throws `SC2020` where `util.promisify` runs, and the wrapper does not report Node's DEP0174 warning when the original returns a promise. `util.styleText` supports standard formats and aliases, `#RGB` and `#RRGGBB` hex colors, arrays of formats, nested ANSI resets, and explicit color enabling with `{ validateStream: false }`. diff --git a/docs/content/limitations/console-and-process-streams.mdx b/docs/content/limitations/console-and-process-streams.mdx index c62b82f0c..946cb8061 100644 --- a/docs/content/limitations/console-and-process-streams.mdx +++ b/docs/content/limitations/console-and-process-streams.mdx @@ -1,6 +1,13 @@ `console.log`, `info`, and `debug` write to stdout; `error` and `warn` write to stderr. They accept `unknown`, printing strings verbatim and other values through native `util.inspect`. Direct calls support global `console` and `node:console` imports. +Console output, `util.format`, and `util.formatWithOptions` with a format string known only at run time, spread arguments, or an options object format their arguments at run time. +There, `%s` of a function or of a class instance whose class defines `toString` and `%o` of an object throw `SC2020`. +`util.inspect` with `colors`, with option values known only at run time, or in the `inspect(value, showHidden, depth, colors)` form renders the value's untyped view in the runtime with Node's styles. +Colored output of errors, promises, regular expressions, `DataView`, `ArrayBuffer`, weak collections, iterators, and other native objects throws `SC2020`. +Inspect options other than `depth`, `colors`, `customInspect`, `compact` (a number or `false`), `breakLength`, and `sorted` (a boolean) throw `SC2020` when they are set to a value other than Node's default. +`util.inspect` calls `[util.inspect.custom]` methods of classes and objects with Node's depth, options, and `util.inspect` arguments, and `console.dir` and `customInspect: false` skip them. +A class instance with such a method that is held as an untyped value throws `SC2020` when it is inspected, and a class whose subclasses declare the method without declaring it itself is refused. Stored global console values and detached output methods retain identity and accept ordinary arguments. Stored methods do not support format specifiers with substitution arguments. Other stored console APIs, custom `Console` instances, and console mutation are unsupported. diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index c341da852..b94ac47be 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -73027,9 +73027,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -73050,9 +73050,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 9393fcaa8..f95cf3aa7 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -101265,6 +101265,13 @@ "tests/corpus/5835-util-format-integer-specifiers.ts", "tests/corpus/5884-inspect-layout-options.ts", "tests/corpus/6428-promisify-callback-functions.ts", + "tests/corpus/7520-util-format-runtime.ts", + "tests/corpus/7521-util-format-runtime.cjs", + "tests/corpus/7522-promisify-untyped.cjs", + "tests/corpus/7523-promisify-untyped.ts", + "tests/corpus/7524-inspect-custom.ts", + "tests/corpus/7525-inspect-custom.cjs", + "tests/corpus/7526-inspect-options-runtime.ts", "tests/corpus/node24-deep-equality-properties.js", "tests/corpus/node24-deep-equality.cjs", "tests/corpus/node24-deep-equality.ts", @@ -102369,7 +102376,9 @@ "tests": [ "tests/corpus/1636-util-format.ts", "tests/corpus/2440-console-inspect-args.ts", - "tests/corpus/5835-util-format-integer-specifiers.ts" + "tests/corpus/5835-util-format-integer-specifiers.ts", + "tests/corpus/7520-util-format-runtime.ts", + "tests/corpus/7521-util-format-runtime.cjs" ] }, "anchorSource": "exact" @@ -102397,7 +102406,9 @@ "tests": [ "tests/corpus/1636-util-format.ts", "tests/corpus/2440-console-inspect-args.ts", - "tests/corpus/5835-util-format-integer-specifiers.ts" + "tests/corpus/5835-util-format-integer-specifiers.ts", + "tests/corpus/7520-util-format-runtime.ts", + "tests/corpus/7521-util-format-runtime.cjs" ] }, "anchorSource": "exact" @@ -102588,7 +102599,8 @@ "tests/corpus/1632-inspect-records.ts", "tests/corpus/1637-inspect-dyn.ts", "tests/corpus/2485-inspect-circular-refs.ts", - "tests/corpus/5884-inspect-layout-options.ts" + "tests/corpus/5884-inspect-layout-options.ts", + "tests/corpus/7526-inspect-options-runtime.ts" ] }, "anchorSource": "exact" @@ -102611,8 +102623,11 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:util" + "status": "partial", + "evidence": "compiler-feature:util.util.inspect", + "tests": [ + "tests/corpus/7526-inspect-options-runtime.ts" + ] }, "anchorSource": "exact" }, @@ -102634,8 +102649,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:util" + "status": "partial", + "evidence": "compiler-feature:util.custom", + "tests": [ + "tests/corpus/7524-inspect-custom.ts", + "tests/corpus/7525-inspect-custom.cjs" + ] }, "anchorSource": "ancestor" }, @@ -102952,6 +102971,8 @@ "tests/fixtures/node-types/promisify-dns.ts", "tests/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", + "tests/corpus/7522-promisify-untyped.cjs", + "tests/corpus/7523-promisify-untyped.ts", "tests/harness/project-config.test.ts" ] }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 06aad26cf..b5211c967 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -244,9 +244,11 @@ {"chapter": "worker_threads", "symbols": ["resourceLimits"], "status": "partial", "evidence": ["tests/corpus/5806-worker-resource-limits.ts"]}, {"chapter": "worker_threads", "symbols": ["BroadcastChannel extends EventTarget", "broadcastChannel.close", "broadcastChannel.postMessage", "broadcastChannel.ref", "broadcastChannel.unref", "onmessage"], "status": "partial", "evidence": ["tests/corpus/5800-broadcast-channel.ts", "tests/corpus/5801-broadcast-channel.cjs", "tests/corpus/5802-broadcast-channel-workers.ts"]}, {"chapter": "util", "symbols": ["util.types.isAnyArrayBuffer", "util.types.isArrayBuffer", "util.types.isArrayBufferView", "util.types.isDataView", "util.types.isTypedArray", "util.types.isUint8Array", "util.types.isUint8ClampedArray", "util.types.isUint16Array", "util.types.isUint32Array", "util.types.isInt8Array", "util.types.isInt16Array", "util.types.isInt32Array", "util.types.isFloat32Array", "util.types.isFloat64Array", "util.types.isMap", "util.types.isSet", "util.types.isWeakMap", "util.types.isWeakSet", "util.types.isRegExp", "util.types.isNativeError", "util.types.isPromise", "util.types.isProxy", "util.types.isAsyncFunction", "util.types.isGeneratorFunction", "util.types.isGeneratorObject"], "status": "partial", "evidence": ["tests/corpus/node24-util-types.ts", "tests/corpus/node24-util-types.js", "tests/corpus/node24-util-types.cjs", "tests/fixtures/node-types/util-types.ts"]}, - {"chapter": "util", "symbols": ["util.format", "util.formatWithOptions"], "status": "partial", "evidence": ["tests/corpus/1636-util-format.ts", "tests/corpus/2440-console-inspect-args.ts", "tests/corpus/5835-util-format-integer-specifiers.ts"]}, + {"chapter": "util", "symbols": ["util.format", "util.formatWithOptions"], "status": "partial", "evidence": ["tests/corpus/1636-util-format.ts", "tests/corpus/2440-console-inspect-args.ts", "tests/corpus/5835-util-format-integer-specifiers.ts", "tests/corpus/7520-util-format-runtime.ts", "tests/corpus/7521-util-format-runtime.cjs"]}, {"chapter": "util", "symbols": ["util.types.isAnyArrayBuffer", "util.types.isArrayBuffer", "util.types.isArrayBufferView", "util.types.isDataView", "util.types.isTypedArray", "util.types.isUint8Array", "util.types.isUint8ClampedArray", "util.types.isUint16Array", "util.types.isUint32Array", "util.types.isInt8Array", "util.types.isInt16Array", "util.types.isInt32Array", "util.types.isFloat32Array", "util.types.isFloat64Array", "util.types.isMap", "util.types.isSet", "util.types.isWeakMap", "util.types.isWeakSet", "util.types.isRegExp", "util.types.isNativeError", "util.types.isPromise", "util.types.isProxy", "util.types.isAsyncFunction", "util.types.isGeneratorFunction", "util.types.isGeneratorObject", "util.types.isDate", "util.types.isSharedArrayBuffer", "util.types.isMapIterator", "util.types.isSetIterator", "util.types.isKeyObject", "util.types.isNumberObject", "util.types.isStringObject", "util.types.isBooleanObject", "util.types.isBigIntObject", "util.types.isSymbolObject", "util.types.isBoxedPrimitive", "util.types.isBigInt64Array", "util.types.isBigUint64Array", "util.types.isFloat16Array", "util.types.isCryptoKey", "util.types.isExternal"], "status": "partial", "evidence": ["tests/corpus/5831-util-types-brands.ts", "tests/corpus/5832-util-types-brands-dynamic.js", "tests/corpus/node24-util-types.ts", "tests/corpus/node24-util-types.js", "tests/corpus/node24-util-types.cjs", "tests/fixtures/node-types/util-types.ts"]}, - { "chapter": "util", "signatures": ["`util.inspect(object[, options])`"], "status": "partial", "evidence": ["tests/corpus/1630-inspect-scalars.ts", "tests/corpus/1632-inspect-records.ts", "tests/corpus/1637-inspect-dyn.ts", "tests/corpus/2485-inspect-circular-refs.ts", "tests/corpus/5884-inspect-layout-options.ts"] }, + { "chapter": "util", "signatures": ["`util.inspect(object[, options])`"], "status": "partial", "evidence": ["tests/corpus/1630-inspect-scalars.ts", "tests/corpus/1632-inspect-records.ts", "tests/corpus/1637-inspect-dyn.ts", "tests/corpus/2485-inspect-circular-refs.ts", "tests/corpus/5884-inspect-layout-options.ts", "tests/corpus/7526-inspect-options-runtime.ts"] }, + { "chapter": "util", "signatures": ["`util.inspect(object[, showHidden[, depth[, colors]]])`"], "status": "partial", "evidence": ["tests/corpus/7526-inspect-options-runtime.ts"] }, + { "chapter": "util", "signatures": ["`custom` Type: {symbol} that can be used to declare custom inspect functions."], "status": "partial", "evidence": ["tests/corpus/7524-inspect-custom.ts", "tests/corpus/7525-inspect-custom.cjs"] }, { "chapter": "util", "symbols": ["util.styleText"], "status": "partial", "evidence": ["tests/corpus/node24-style-text.ts", "tests/corpus/node24-style-text-validation.js", "tests/corpus/node24-style-text-environment.js", "tests/corpus/node24-style-text.cjs", "tests/fixtures/node-types/style-text.ts"] }, { "chapter": "util", "symbols": ["util.isDeepStrictEqual"], "status": "partial", "evidence": ["tests/corpus/node24-deep-equality.ts", "tests/corpus/node24-deep-equality.cjs", "tests/corpus/node24-deep-equality-properties.js", "tests/fixtures/node-types/deep-equality.ts", "packages/compiler/test/deep-equality.test.ts"] }, { "chapter": "deprecations", "symbols": ["util.toUSVString"], "status": "not-applicable" }, @@ -400,7 +402,7 @@ { "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.before", "context.after", "context.beforeEach", "context.afterEach", "context.plan", "context.fullName"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/ctx-hooks.ts", "tests/fixtures/node-test/cases/ctx-plan.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "symbols": ["getTestContext"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/get-test-context.ts", "tests/fixtures/node-test/cases/get-test-context-cancelled.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "symbols": ["describe.skip", "describe.todo", "describe.only", "suite.skip", "suite.todo", "suite.only", "it.skip", "it.todo", "it.only"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/suite-variants.ts", "tests/fixtures/node-test/cases/only-nested.ts", "tests/fixtures/node-test/cases/todo-suites.ts", "tests/harness/node-test.test.ts"] }, - { "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/harness/project-config.test.ts"] }, + { "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/corpus/7522-promisify-untyped.cjs", "tests/corpus/7523-promisify-untyped.ts", "tests/harness/project-config.test.ts"] }, { "chapter": "typescript", "symbols": ["type"], "status": "supported", "evidence": ["tests/corpus/2702-type-import-link-static/main.ts", "tests/corpus/2703-type-import-link-static-reexport/main.ts", "tests/corpus/2704-type-import-link-static-success/main.ts", "tests/corpus/2707-type-import-link-static-default-reexport/main.ts", "tests/harness/errors.test.ts"] }, { "chapter": "zlib", "signatures": ["Class: `zlib.Deflate`", "Class: `zlib.Inflate`", "Class: `zlib.Gzip`", "Class: `zlib.Gunzip`", "Class: `zlib.DeflateRaw`", "Class: `zlib.InflateRaw`", "Class: `zlib.Unzip`", "Class: `zlib.ZlibBase`", "`zlib.close([callback])`", "`zlib.flush([kind, ]callback)`", "`zlib.params(level, strategy, callback)`", "`zlib.reset()`", "`bytesWritten` Type: {number}", "Class: `Options`"], "status": "partial", "evidence": ["tests/corpus/5370-zlib-stream-roundtrip.ts", "tests/corpus/5371-zlib-stream-chunking.cjs", "tests/corpus/5372-zlib-stream-errors.cjs", "tests/corpus/5373-zlib-stream-options.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts", "tests/corpus/5375-zlib-stream-promises.ts", "tests/corpus/5377-zlib-stream-backpressure.cjs"] }, { "chapter": "zlib", "signatures": ["`zlib.constants`"], "status": "partial", "evidence": ["tests/corpus/5376-zlib-constants.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts"] }, diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index b195c3fbb..80054b4fa 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -3838,7 +3838,13 @@ declare module "util" { export type TextDecoder = globalThis.TextDecoder; export var TextEncoder: typeof globalThis.TextEncoder; export var TextDecoder: typeof globalThis.TextDecoder; - /* stream.pipeline's overload comes first: its __promisify__ member keeps + /* An untyped function: an `any` value, or a function whose parameters + * are untyped (JavaScript's `(value, callback) => ...`), the only + * parameters that accept this object type. The promisified function is + * untyped too. It comes first because such functions match every other + * overload. */ + export function promisify(fn: (...args: { readonly "scriptc:untyped": true }[]) => unknown): any; + /* stream.pipeline's overload comes next: its __promisify__ member keeps * every other target out of it. */ export function promisify( fn: typeof import("node:stream").pipeline, @@ -3959,15 +3965,56 @@ declare module "util" { ): (address: string, port: number) => Promise<{ hostname: string; service: string }>; /* Declared surface without full lowering (the type-level tolerance * story: guarded/diagnostic-path code must TYPECHECK; a reached use - * without a lowering fences at its site). inspect's options carry the - * documented knobs through an index signature. */ + * without a lowering fences at its site). */ + export interface InspectOptions { + showHidden?: boolean | undefined; + depth?: number | null | undefined; + colors?: boolean | undefined; + customInspect?: boolean | undefined; + showProxy?: boolean | undefined; + maxArrayLength?: number | null | undefined; + maxStringLength?: number | null | undefined; + breakLength?: number | undefined; + compact?: boolean | number | undefined; + sorted?: boolean | ((a: string, b: string) => number) | undefined; + getters?: "get" | "set" | boolean | undefined; + numericSeparator?: boolean | undefined; + } + export type Style = + | "special" + | "number" + | "bigint" + | "boolean" + | "undefined" + | "null" + | "string" + | "symbol" + | "date" + | "regexp" + | "module"; + export interface InspectOptionsStylized extends InspectOptions { + stylize(text: string, styleType: Style): string; + } + export type CustomInspectFunction = ( + depth: number, + options: InspectOptionsStylized, + inspect: typeof import("node:util").inspect, + ) => any; export function inspect( object: unknown, - options?: { depth?: number | null; colors?: boolean; [k: string]: unknown }, + showHidden?: boolean, + depth?: number | null, + color?: boolean, ): string; + export function inspect(object: unknown, options?: InspectOptions): string; + export namespace inspect { + /* The key of an object's custom inspect function: + * Symbol.for('nodejs.util.inspect.custom'). */ + const custom: unique symbol; + } export function format(format?: unknown, ...args: unknown[]): string; export function formatWithOptions( - inspectOptions: { depth?: number | null; colors?: boolean; [k: string]: unknown }, + inspectOptions: InspectOptions, format?: unknown, ...args: unknown[] ): string; diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 0b6041034..b02cf44f3 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1034,6 +1034,22 @@ export const LIB_FN_SYMS: Record = { "dyn.generatorThis": "scr_gen_receiver", "insp.dyn": "scr_insp_dyn", "insp.dynS": "scr_insp_dyn_s", + "insp.format": "scr_insp_format_pack", + "insp.formatPrimitives": "scr_insp_format_primitives", + "insp.formatWithOptions": "scr_insp_format_with_options", + "insp.inspect": "scr_insp_inspect", + "insp.customEnabled": "scr_insp_custom_enabled", + "insp.registerCustom": "scr_insp_register_custom", + "insp.customInspect": "scr_insp_custom_inspect", + "insp.depthKind": "scr_insp_depth_kind", + "insp.customDepth": "scr_insp_custom_depth", + "insp.customOptions": "scr_insp_custom_options", + "insp.inspectFunction": "scr_insp_inspect_function", + "insp.customText": "scr_insp_custom_text", + "insp.customIsSelf": "scr_insp_custom_is_self", + "insp.customResult": "scr_insp_custom_result", + "insp.formatSpecifies": "scr_insp_format_specifies", + "insp.formatSpecifiesDyn": "scr_insp_format_specifies_dyn", "fs.readFileSyncDyn": "scr_fs_read_file_sync_dyn", "fs.readFileEncoded": "scr_fs_read_file_encoded", "fs.readFdEncoded": "scr_fs_read_fd_encoded", diff --git a/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts b/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts index b4dbbf513..d3378c8ec 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts @@ -1,7 +1,8 @@ import { keyedHelperName } from "../stable-names.js"; import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; -import { varRef } from "../../../ir/build.js"; +import { dynUndefinedExpr, varRef } from "../../../ir/build.js"; +import { isJsSourceFile } from "../../program.js"; import { BOOL, DYN, @@ -92,12 +93,22 @@ function callbackShape( return { fnT, resultT, args, callbackT, callbackParamT: callbackParamT!, inner, error }; } -/** Whether `promisify(fn)`'s one argument is typed `any`: the function is - * only known at run time, so the promisified function is built there. */ +/** Whether `promisify(fn)`'s one argument is only known at run time, so + * the promisified function is built there: a value typed `any`, a function + * with untyped parameters (the declarations type that call `any`), or any + * argument in JavaScript, whose inferred types do not describe the + * function Node receives (it validates the argument and reads its own + * properties). */ export function untypedPromisifyArgument(lowerer: Lowerer, call: ts.CallExpression): boolean { - if (call.questionDotToken || call.arguments.nodes.length !== 1) return false; - const argNode = call.arguments.nodes[0]!; - return !ts.isSpreadElement(argNode) && (lowerer.typeOf(argNode).flags & ts.TypeFlags.Any) !== 0; + if (call.questionDotToken || call.arguments.nodes.length > 1) return false; + const argNode = call.arguments.nodes[0]; + if (argNode !== undefined && ts.isSpreadElement(argNode)) return false; + if (isJsSourceFile(ts.getSourceFileOfNode(call))) return true; + if (argNode === undefined) return false; + return ( + (lowerer.typeOf(argNode).flags & ts.TypeFlags.Any) !== 0 || + (lowerer.typeOf(call).flags & ts.TypeFlags.Any) !== 0 + ); } /** The callback shape of a `promisify(fn)` call, or null. */ @@ -144,10 +155,11 @@ export function lowerCallbackPromisify( if (shape === null) { // An untyped (`any`) function: Node's promisify at run time. if (!untypedPromisifyArgument(lowerer, call)) return null; + const argNode = call.arguments.nodes[0]; return { kind: "libCall", fn: "util.promisifyDyn", - args: [lowerer.lowerExprExpecting(call.arguments.nodes[0]!, DYN)], + args: [argNode ? lowerer.lowerExprExpecting(argNode, DYN) : dynUndefinedExpr(loc)], type: DYN, loc, }; diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index 060ffe9d2..7dc302365 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -56,6 +56,95 @@ function emptyTupleSpread(lowerer: Lowerer, node: ts.Expression): boolean { return restTupleElementTypes(lowerer.typeOf(operand), lowerer.typeCtx)?.length === 0; } +/** A call's arguments as one checked-dynamic array, built in source order + * into a fresh hidden local: each argument converts to dyn, and a spread + * contributes the values its iteration yields (holes as undefined). + * `lowerSpread` lowers a spread's operand; the default lowers it here. */ +export function dynArgumentPack( + lowerer: Lowerer, + argNodes: readonly ts.Expression[], + loc: SrcLoc, + options: { + /** Pre-lowered values pushed before the arguments. */ + leading?: readonly IrExpr[] | undefined; + blame?: ts.Node | undefined; + lowerSpread?: (arg: ts.SpreadElement) => IrExpr; + } = {}, +): { stmts: IrStmt[]; pack: () => IrExpr } { + const blame = options.blame ?? argNodes[0]!; + const lowerSpread = + options.lowerSpread ?? ((arg: ts.SpreadElement) => lowerer.lowerExpr(arg.expression)); + const pack = lowerer.declareHiddenLocal("%spreadArgs", DYN); + const ref = (): IrExpr => ({ kind: "varRef", localId: pack.id, type: DYN, loc }); + const stmts: IrStmt[] = [ + { + kind: "varDecl", + localId: pack.id, + init: { kind: "dynArrLit", elems: [], type: DYN, loc }, + loc, + }, + ]; + const push = (value: IrExpr, fn: "dyn.packPush" | "dyn.packPushSpreadIter") => { + stmts.push({ + kind: "exprStmt", + expr: { kind: "libCall", fn, args: [ref(), value], type: VOID, loc: value.loc }, + loc: value.loc, + }); + }; + for (const value of options.leading ?? []) + push(lowerer.coerceInto(blame, value, DYN), "dyn.packPush"); + const spreads = argNodes.filter(ts.isSpreadElement).length; + argNodes.forEach((arg, index) => { + if (!ts.isSpreadElement(arg)) { + push(lowerer.lowerExprExpecting(arg, DYN), "dyn.packPush"); + return; + } + let value = lowerSpread(arg); + if (value.type.kind === "set") { + value = { + kind: "setIntrinsic", + method: "toArray", + receiver: value, + args: [], + type: arrayOf(value.type.elem), + loc: value.loc, + }; + } else if (value.type.kind === "object") { + value = lowerer.classIteratorDrainCall(value, locOf(arg)) ?? value; + } + if (value.type.kind === "array") { + // Argument iteration materializes holes as present undefined + // before conversion to the dense checked-dynamic argument pack. + value = { + kind: "arrayLit", + elems: [value], + spreads: [0], + type: value.type, + loc: value.loc, + }; + } + value = checkedIterableSpread(lowerer, value, ts.getTextOfNode(arg.expression), loc); + if (spreads === 1 && index === argNodes.length - 1) { + stmts.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.packPushSpread", + args: [ + ref(), + value, + { kind: "strLit", value: ts.getTextOfNode(arg.expression), type: STRING, loc }, + ], + type: VOID, + loc, + }, + loc, + }); + } else push(value, "dyn.packPushSpreadIter"); + }); + return { stmts, pack: ref }; +} + export function completeArgs( lowerer: Lowerer, argNodes: readonly ts.Expression[], @@ -96,72 +185,10 @@ export function completeArgs( // Runtime-length spreads determine the complete argument list before // any parameter default runs. Build it once in source order, then // extract the fixed native ABI slots; missing elements are undefined. - const pack = lowerer.declareHiddenLocal("%spreadArgs", DYN); - const ref = (): IrExpr => ({ kind: "varRef", localId: pack.id, type: DYN, loc }); - const stmts: IrStmt[] = [ - { - kind: "varDecl", - localId: pack.id, - init: { kind: "dynArrLit", elems: [], type: DYN, loc }, - loc, - }, - ]; - const push = (value: IrExpr, fn: "dyn.packPush" | "dyn.packPushSpreadIter") => { - stmts.push({ - kind: "exprStmt", - expr: { kind: "libCall", fn, args: [ref(), value], type: VOID, loc: value.loc }, - loc: value.loc, - }); - }; - for (const value of leading ?? []) push(lowerer.coerceInto(blame, value, DYN), "dyn.packPush"); - const spreads = argNodes.filter(ts.isSpreadElement).length; - argNodes.forEach((arg, index) => { - if (!ts.isSpreadElement(arg)) { - push(lowerer.lowerExprExpecting(arg, DYN), "dyn.packPush"); - return; - } - let value = lowerSpread(arg); - if (value.type.kind === "set") { - value = { - kind: "setIntrinsic", - method: "toArray", - receiver: value, - args: [], - type: arrayOf(value.type.elem), - loc: value.loc, - }; - } else if (value.type.kind === "object") { - value = lowerer.classIteratorDrainCall(value, locOf(arg)) ?? value; - } - if (value.type.kind === "array") { - // Argument iteration materializes holes as present undefined - // before conversion to the dense checked-dynamic argument pack. - value = { - kind: "arrayLit", - elems: [value], - spreads: [0], - type: value.type, - loc: value.loc, - }; - } - value = checkedIterableSpread(lowerer, value, ts.getTextOfNode(arg.expression), loc); - if (spreads === 1 && index === argNodes.length - 1) { - stmts.push({ - kind: "exprStmt", - expr: { - kind: "libCall", - fn: "dyn.packPushSpread", - args: [ - ref(), - value, - { kind: "strLit", value: ts.getTextOfNode(arg.expression), type: STRING, loc }, - ], - type: VOID, - loc, - }, - loc, - }); - } else push(value, "dyn.packPushSpreadIter"); + const { stmts, pack: ref } = dynArgumentPack(lowerer, argNodes, loc, { + leading, + blame, + lowerSpread, }); const args = shapes.map((shape, index): IrExpr => { if (shape.mode === "arguments") return ref(); diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 4d04be30a..43322246c 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -33,7 +33,11 @@ import { } from "../../type-mapper.js"; import { isSafeToRepeat } from "./evaluation-safety.js"; import { tryLowerExpression } from "./try-lower-expression.js"; -import { fenceSymbolFieldCopy } from "../symbol-fields.js"; +import { + classSymbolKeyOf, + fenceSymbolFieldCopy, + symbolFieldDisplayName, +} from "../symbol-fields.js"; import { planUnionRetag } from "../union-retag.js"; import { jsHoldsDynObject, runtimeOptionalStorageOperand } from "../lower-exprs.js"; import { builtinGlobalValue } from "./property-access.js"; @@ -194,7 +198,19 @@ function propNameText(lowerer: Lowerer, name: ts.PropertyName): string { * (NamedEvaluation): an identifier, string or numeric key, or a computed * key that folds to one string. Null for other keys. */ function propertyFunctionName(lowerer: Lowerer, name: ts.PropertyName): string | null { - if (ts.isComputedPropertyName(name)) return literalComputedKey(lowerer, name); + if (ts.isComputedPropertyName(name)) { + const literal = literalComputedKey(lowerer, name); + if (literal !== null) return literal; + // A symbol key names the function `[description]`. + let key = name.expression; + while (ts.isParenthesizedExpression(key)) key = key.expression; + const symbol = classSymbolKeyOf(lowerer, key); + if (symbol) return `[${symbolFieldDisplayName(symbol.fieldName).slice("Symbol(".length, -1)}]`; + const wellKnown = ts.isPropertyAccessExpression(key) + ? lowerer.stdlibGlobalMember(key, "Symbol") + : null; + return wellKnown ? `[Symbol.${wellKnown}]` : null; + } if (ts.isNumericLiteral(name)) return String(Number(name.text)); if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text; return null; diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 5df3c7fe0..9157c551d 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -5,6 +5,7 @@ import * as ts from "../../ts7/adapter.js"; import { lowerHistogramProperty } from "../builtins/performance.js"; import { staticFunctionNameRead } from "../static-function-names.js"; import { typedArgumentsArity } from "../typed-arguments.js"; +import { INSPECT_CUSTOM_DESCRIPTION, isInspectCustomAccess } from "../symbol-fields.js"; import { isThisParameter } from "../call-signatures.js"; import { checkedPrivateValue, @@ -310,6 +311,16 @@ export function lowerPropertyAccessExpr( 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 { + kind: "libCall", + fn: "sym.for", + args: [{ kind: "strLit", value: INSPECT_CUSTOM_DESCRIPTION, type: STRING, loc }], + type: SYMBOL_T, + loc, + }; + } // A dgram socket's method read as a value in JavaScript (`emit: // socket.emit.bind(socket)`): the socket crosses as its checked-dynamic // handle, whose methods are function values that dispatch on their diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index c0406e77b..711cd7379 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -267,6 +267,28 @@ import { codeGenerationCalleeOf, lowerCodeGenerationCall } from "./code-generati const ZLIB_STREAM_METHODS: ReadonlySet = new Set(["flush", "params", "close", "reset"]); +/** The node:util member a `(0, member)` callee names, or null. */ +function indirectUtilCalleeOf( + lowerer: Lowerer, + callee: ts.Expression, +): { module: string; member: string } | null { + let e = callee; + while (ts.isParenthesizedExpression(e)) e = e.expression; + if ( + !ts.isBinaryExpression(e) || + e.operatorToken.kind !== ts.SyntaxKind.CommaToken || + !ts.isNumericLiteral(e.left) + ) + return null; + const target = e.right; + const bi = ts.isIdentifier(target) + ? lowerer.builtinImportOf(target) + : ts.isPropertyAccessExpression(target) && !target.questionDotToken + ? lowerer.builtinMemberOf(target) + : null; + return bi?.module === "util" ? bi : null; +} + export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const loc = locOf(expr); // Object's checker return type is `any`. Claim known object inputs @@ -295,6 +317,14 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const codeGeneration = codeGenerationCalleeOf(lowerer, expr.expression); if (codeGeneration !== null) return lowerCodeGenerationCall(lowerer, expr, codeGeneration); } + // `(0, util_1.inspect)(value)`: TypeScript's CommonJS output calls a + // named import through a comma expression, which leaves the call's + // receiver undefined. node:util's functions do not read their receiver, + // so the call lowers as the direct one. + { + const util = indirectUtilCalleeOf(lowerer, expr.expression); + if (util) return lowerBuiltinCall(lowerer, expr, util, loc); + } // A package shim's runtime intrinsic (a bodiless `declare function` // named scriptcUnixDgram* in a shipped shim): a direct runtime call. if (ts.isIdentifier(expr.expression) && isShimIntrinsicName(expr.expression.text)) { diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index b23697cab..d4875c9df 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -155,6 +155,7 @@ import { } from "./lower-event-emitter.js"; import { declSymbolOf } from "./lower-modules.js"; import { + INSPECT_CUSTOM_MEMBER, classSymbolKeyOf, classSymbolKeyOfSymbol, symbolFieldDisplayName, @@ -3208,6 +3209,15 @@ 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. @@ -4422,6 +4432,9 @@ export function collectClassShapeInner( ...(methods.size > 0 ? { methods: [...methods.keys()] } : {}), ...((): { prototypeNames?: string[] } => { const names = classPrototypeNames(decl); + // A [util.inspect.custom] method, by its reserved slot name: the + // checked-dynamic inspect walk refuses instances that have one. + if (methods.has(INSPECT_CUSTOM_MEMBER)) names.push(INSPECT_CUSTOM_MEMBER); return names.length > 0 ? { prototypeNames: names } : {}; })(), ...(abstractClass ? { abstract: true as const } : {}), diff --git a/packages/compiler/src/frontend/lowering/lower-console.ts b/packages/compiler/src/frontend/lowering/lower-console.ts index eec308342..db4d39e70 100644 --- a/packages/compiler/src/frontend/lowering/lower-console.ts +++ b/packages/compiler/src/frontend/lowering/lower-console.ts @@ -14,7 +14,14 @@ import { boolLit, dynUndefinedExpr, numLit, strLit, varRef } from "../../ir/buil import { locOf } from "../program.js"; import { type ConsoleMethodMember } from "./builtins/console.js"; import { jsFuncNameOf, type Lowerer } from "./lowerer.js"; -import { lowerConsoleInspectArg, lowerFormatCall, lowerInspectValue } from "./lower-inspect.js"; +import { + heldFormatArgs, + lowerConsoleInspectArg, + lowerFormatCall, + lowerInspectValue, + lowerRuntimeFormat, + runtimeFormatString, +} from "./lower-inspect.js"; import { streamSidesOf } from "./lower-stream.js"; /** node:console Console instances: a native handle (the checked-dynamic @@ -357,9 +364,32 @@ export function lowerConsoleOutput( ) { return consoleWrite(stdout, [lowerFormatCall(lowerer, expr, loc, false)], loc); } - const args = expr.arguments.nodes.map((a) => - lowerConsoleArg(lowerer, a, lowerer.lowerExpr(a), surface, loc), - ); + const nodes = expr.arguments.nodes; + // Spread arguments and format strings that may hold a substitution at + // run time go through the runtime formatter. A string first argument + // without a '%' keeps the space-joined write below. + if (nodes.some(ts.isSpreadElement)) + return consoleWrite(stdout, [lowerRuntimeFormat(lowerer, nodes, loc)], loc); + const runtime = + nodes.length > 1 && !isStringLiteralLike(first) ? runtimeFormatString(lowerer, first!) : null; + if (runtime === "other") + return consoleWrite(stdout, [lowerRuntimeFormat(lowerer, nodes, loc)], loc); + if (runtime !== null) { + const held = heldFormatArgs(lowerer, nodes, loc); + if (!held.joinable) return seq(held.stmts, consoleWrite(stdout, [held.format()], loc), loc); + const joined = held.args.map((arg) => + lowerConsoleArg(lowerer, arg.node, arg.ref(), surface, loc), + ); + const write: IrStmt = { + kind: "if", + cond: held.specifies, + then: [{ kind: "exprStmt", expr: consoleWrite(stdout, [held.format()], loc), loc }], + else_: [{ kind: "exprStmt", expr: consoleWrite(stdout, joined, loc), loc }], + loc, + }; + return seq([...held.stmts, write], noop(loc), loc); + } + const args = nodes.map((a) => lowerConsoleArg(lowerer, a, lowerer.lowerExpr(a), surface, loc)); return consoleWrite(stdout, args, loc); } diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 47f490c7b..dd950a2a2 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -14,13 +14,15 @@ import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; import { unsignedHex } from "../../format-integer.js"; import type { Lowerer } from "./lowerer.js"; -import { isJsSourceFile } from "../program.js"; +import { isJsSourceFile, locOf } from "../program.js"; +import { noLoweringDiag } from "../../diagnostics/diagnostic.js"; import { BOOL, DYN, F64, type IrExpr, type IrFunction, + type IrLocal, type IrStmt, type IrStrIntrinsicMethod, type IrType, @@ -32,6 +34,7 @@ import { type SrcLoc, canConvertToDyn, canDynCheckTo, + funcOf, CLASS_FIELD_ATTRIBUTES_PREFIX, classDynViewSupported, DYN_CLASS_PROPERTIES, @@ -42,15 +45,16 @@ import { shapeHasAccessorSlots, typeKey, } from "../../ir/ir.js"; -import type { ClassInfo } from "./lower-classes.js"; +import { accessorCall, type ClassInfo } from "./lower-classes.js"; import { isSafeToDiscard, isSafeToRepeat } from "./expressions/evaluation-safety.js"; -import { symbolFieldDisplayName } from "./symbol-fields.js"; +import { INSPECT_CUSTOM_MEMBER, symbolFieldDisplayName } from "./symbol-fields.js"; import { eventTargetRootOf } from "./lower-event-target.js"; import { dateRootOf } from "./builtins/date-classes.js"; import { lowerSystemErrorMap } from "./lower-system-errors.js"; import { isPerfEntryType, perfEntryJson } from "./builtins/performance.js"; import { recordMayKeepSource, recordSourceTest } from "./record-sources.js"; import { nullProtoRecordShape, recordNullProtoTest } from "./null-prototype.js"; +import { dynArgumentPack } from "./call-arguments.js"; /* ── IR construction shorthand ───────────────────────────────────────── */ function concatAll(parts: IrExpr[], loc: SrcLoc): IrExpr { @@ -805,6 +809,8 @@ function inspectSupport( case "object": { const info = lowerer.classes.get(t.className); if (!info) return `class '${t.className}' has no inspect lowering`; + const custom = customInspectReason(lowerer, info); + if (custom !== null) return custom; // EventTarget and Event subclasses render through Node's inspectors // for those classes (the runtime's capsule view), and so do classes // declared `extends Date`. @@ -900,6 +906,8 @@ export function inspectExpr( recurse: IrExpr, depth: IrExpr, loc: SrcLoc, + /** The instance's own rendering, without its custom inspect method. */ + ownRendering = false, ): IrExpr { // A Dirent row renders through its dyn view: Node's `Dirent { ... }` // with the Symbol(type) key. @@ -960,6 +968,15 @@ export function inspectExpr( loc, }; case "object": + if (!ownRendering && customInspectMethodOf(lowerer, t.className)) { + return { + kind: "call", + callee: customInspectHelper(lowerer, t, loc), + args: [value, recurse, depth], + type: STRING, + loc, + }; + } if ( eventTargetRootOf(lowerer.classes.get(t.className)) !== null || dateRootOf(lowerer.classes.get(t.className)) !== null @@ -1139,6 +1156,258 @@ function classInspectsAddedProperties(lowerer: Lowerer, info: ClassInfo): boolea ); } +/* ── custom inspect methods ([util.inspect.custom]) ──────────────────── */ + +/** The `[util.inspect.custom]` method a class's instances have, declared + * on the class or a base; null when they have none. */ +function customInspectMethodOf(lowerer: Lowerer, className: string) { + const info = lowerer.classes.get(className); + return info ? (lowerer.findMethodOn(info, INSPECT_CUSTOM_MEMBER) ?? null) : null; +} + +/** Why instances of a class with a custom inspect method have no static + * rendering, or null. Node calls the method with the remaining depth, the + * options and util.inspect; the method's parameters receive them as + * checked-dynamic values, and its result renders in the instance's place. */ +function customInspectReason(lowerer: Lowerer, info: ClassInfo): string | null { + const found = lowerer.findMethodOn(info, INSPECT_CUSTOM_MEMBER); + if (!found) { + const below = (c: ClassInfo): boolean => + c.subclasses.some((s) => s.methods.has(INSPECT_CUSTOM_MEMBER) || below(s)); + return below(info) + ? "classes whose subclasses declare [util.inspect.custom] have no inspect lowering yet (declare the method on the class itself)" + : null; + } + if (found.sig.params.length > 3) + return "[util.inspect.custom] methods with more than three parameters have no inspect lowering"; + for (const param of found.sig.params) { + if ( + param.type.kind !== "dyn" && + !canDynCheckTo( + param.type, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) + ) + return `[util.inspect.custom] methods with a '${lowerer.fmt(param.type)}' parameter have no inspect lowering`; + } + const ret = found.sig.ret; + if (ret.kind === "object") + return "[util.inspect.custom] methods that return a class instance have no inspect lowering"; + if ( + ret.kind !== "string" && + ret.kind !== "dyn" && + ret.kind !== "void" && + inspectSupport(lowerer, ret, new Set(), { recursive: false }) !== null + ) + return `[util.inspect.custom] methods returning '${lowerer.fmt(ret)}' have no inspect lowering`; + return null; +} + +/** Whether values of `t` can hold a checked-dynamic value, whose own custom + * inspect functions the runtime walk calls. */ +function typeReachesDyn(lowerer: Lowerer, t: IrType): boolean { + const seen = new Set(); + const walk = (u: IrType): boolean => { + if (u.kind === "dyn") return true; + const k = typeKey(u); + if (seen.has(k)) return false; + seen.add(k); + return typeConstituents(lowerer, u).some(walk); + }; + return walk(t); +} + +/** Whether values of `t` can hold an instance of a class with a custom + * inspect method. */ +export function typeReachesCustomInspect(lowerer: Lowerer, t: IrType): boolean { + const seen = new Set(); + const walk = (u: IrType): boolean => { + const k = typeKey(u); + if (seen.has(k)) return false; + seen.add(k); + if (u.kind === "object" && customInspectMethodOf(lowerer, u.className)) return true; + return typeConstituents(lowerer, u).some(walk); + }; + return walk(t); +} + +/** `%util.inspCustom.(v, r, d): string` — formatValue's custom hook + * over a class whose instances have a `[util.inspect.custom]` method: + * with ctx.customInspect set, the method's result (a string re-indented + * at the current level, the instance itself rendered normally, any other + * value rendered in its place); otherwise the instance's own rendering. */ +function customInspectHelper( + lowerer: Lowerer, + t: IrType & { kind: "object" }, + loc: SrcLoc, +): string { + const key = `inspCustom:${typeKey(t)}`; + const existing = lowerer.inspectHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, "%util.inspCustom", key); + lowerer.inspectHelpers.set(key, name); + const found = customInspectMethodOf(lowerer, t.className)!; + const v = (): IrExpr => varRef("v.0", t, loc); + const r = (): IrExpr => varRef("r.0", F64, loc); + const d = (): IrExpr => varRef("d.0", F64, loc); + const ret = (value: IrExpr): IrStmt => ({ kind: "return", value, loc }); + const normal = (): IrExpr => inspectExpr(lowerer, t, v(), r(), d(), loc, true); + const runtimeArgs: IrExpr[] = [ + { kind: "libCall", fn: "insp.customDepth", args: [r(), d()], type: DYN, loc }, + { kind: "libCall", fn: "insp.customOptions", args: [d()], type: DYN, loc }, + { kind: "libCall", fn: "insp.inspectFunction", args: [], type: DYN, loc }, + ]; + const args = found.sig.params.map((param, i) => + lowerer.coerceToExpected(runtimeArgs[i]!, param.type), + ); + const resultT = found.sig.ret; + const call = accessorCall(lowerer, t.className, INSPECT_CUSTOM_MEMBER, v(), args, resultT, loc); + const locals: IrLocal[] = [ + { id: "v.0", name: "v", type: t, mutable: false }, + { id: "r.0", name: "r", type: F64, mutable: false }, + { id: "d.0", name: "d", type: F64, mutable: false }, + ]; + const render: IrStmt[] = []; + if (resultT.kind === "string") { + render.push(ret({ kind: "libCall", fn: "insp.customText", args: [call], type: STRING, loc })); + } else if (resultT.kind === "dyn") { + locals.push({ id: "res.0", name: "res", type: DYN, mutable: false }); + const res = (): IrExpr => varRef("res.0", DYN, loc); + render.push( + { kind: "varDecl", localId: "res.0", init: call, loc }, + { + kind: "if", + cond: { + kind: "libCall", + fn: "insp.customIsSelf", + args: [res(), { kind: "dynFrom", value: v(), liveRef: true, type: DYN, loc }], + type: BOOL, + loc, + }, + then: [ret(normal())], + else_: null, + loc, + }, + ret({ kind: "libCall", fn: "insp.customResult", args: [res(), r(), d()], type: STRING, loc }), + ); + } else if (resultT.kind === "void") { + render.push({ kind: "exprStmt", expr: call, loc }, ret(strLit("undefined", loc))); + } else { + render.push(ret(inspectExpr(lowerer, resultT, call, r(), d(), loc))); + } + lowerer.liftedFns.push({ + name, + params: [ + { localId: "v.0", name: "v", type: t }, + { localId: "r.0", name: "r", type: F64 }, + { localId: "d.0", name: "d", type: F64 }, + ], + returnType: STRING, + locals, + body: [ + { + kind: "if", + cond: { kind: "libCall", fn: "insp.customEnabled", args: [], type: BOOL, loc }, + then: render, + else_: null, + loc, + }, + ret(normal()), + ], + loc, + }); + return name; +} + +/** `%util.inspCustomDyn.(self, depth, options, inspect): dyn` — a + * class's custom inspect method for its instances held as checked-dynamic + * values: the runtime's inspect walk calls it with the instance's typed + * ref and the method's three arguments. */ +function customInspectDynHelper( + lowerer: Lowerer, + t: IrType & { kind: "object" }, + loc: SrcLoc, +): string { + const name = keyedHelperName(lowerer, "%util.inspCustomDyn", typeKey(t)); + const found = customInspectMethodOf(lowerer, t.className)!; + const param = (id: string): IrExpr => varRef(id, DYN, loc); + const ids = ["depth.0", "options.0", "inspect.0"]; + const call = accessorCall( + lowerer, + t.className, + INSPECT_CUSTOM_MEMBER, + varRef("v.0", t, loc), + found.sig.params.map((p, i) => lowerer.coerceToExpected(param(ids[i]!), p.type)), + found.sig.ret, + loc, + ); + const body: IrStmt[] = [ + { kind: "varDecl", localId: "v.0", init: lowerer.coerceToExpected(param("self.0"), t), loc }, + ]; + if (found.sig.ret.kind === "void") { + body.push( + { kind: "exprStmt", expr: call, loc }, + { kind: "return", value: dynUndefinedExpr(loc), loc }, + ); + } else { + body.push({ kind: "return", value: lowerer.coerceToExpected(call, DYN), loc }); + } + const params = ["self.0", ...ids]; + lowerer.liftedFns.push({ + name, + params: params.map((id) => ({ localId: id, name: id.slice(0, -2), type: DYN })), + returnType: DYN, + locals: [ + ...params.map((id) => ({ id, name: id.slice(0, -2), type: DYN, mutable: false })), + { id: "v.0", name: "v", type: t, mutable: false }, + ], + body, + loc, + }); + return name; +} + +/** The statements %main runs first, in every thread: each class whose + * instances have a custom inspect method registers its compiled call + * under its instances' typed-ref type key (scr_insp_register_custom). */ +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 (!lowerer.findMethodOn(info, INSPECT_CUSTOM_MEMBER)) continue; + if (customInspectReason(lowerer, info) !== null) continue; + const t = { kind: "object" as const, className: info.def.name }; + const fn = customInspectDynHelper(lowerer, t, loc); + stmts.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "insp.registerCustom", + args: [ + strLit(typeKey(t), loc), + { + kind: "dynFrom", + value: { + kind: "closure", + fnName: fn, + captures: [], + type: funcOf([DYN, DYN, DYN, DYN], DYN), + loc, + }, + type: DYN, + loc, + }, + ], + type: VOID, + loc, + }, + loc, + }); + } + return stmts; +} + /* ── the interned per-type helper ────────────────────────────────────── */ /** `%util.insp.(v, r, d): string` — formatValue over the static type: @@ -2213,13 +2482,22 @@ function isDataViewTypeName(tname: string): boolean { /* ── the callsite: options, direct function/class names ──────────────── */ /** The parsed options literal: the resolved depth (numeric; Infinity for - * `null`), breakLength (numeric; default 80) and compact (numeric, 0 for + * `null`), breakLength (numeric; default 80) and compact (numeric, -1 for * false; default 3), with every other non-default knob fenced by name. */ interface InspectOptions { depth: number; breakLength: number; compact: number; sorted: boolean; + /** Set by the `atRunTime` parse: colors or a computed knob value, which + * the runtime reads. */ + runtime: boolean; + /** How the depth option was spelled, for custom inspect methods: 0 a + * number (also when absent), 1 null, 2 undefined. */ + depthKind: number; + /** customInspect: false skips [util.inspect.custom] methods (console.dir's + * default). */ + customInspect: boolean; } function parseInspectOptions( @@ -2227,9 +2505,18 @@ function parseInspectOptions( node: ts.Expression | undefined, recursive: boolean, dir = false, + atRunTime = false, ): InspectOptions { const surface = dir ? "console.dir" : "util.inspect"; - const options: InspectOptions = { depth: 2, breakLength: 80, compact: 3, sorted: false }; + const options: InspectOptions = { + depth: 2, + breakLength: 80, + compact: 3, + sorted: false, + runtime: false, + depthKind: 0, + customInspect: !dir, + }; if (!node || (ts.isIdentifier(node) && node.text === "undefined")) return options; if (!ts.isObjectLiteralExpression(node)) { lowerer.noLowering( @@ -2240,6 +2527,11 @@ function parseInspectOptions( } for (const prop of node.properties.nodes) { if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) { + // `{ depth }` and `{ ...base }` hold values known at run time. + if (atRunTime && (ts.isShorthandPropertyAssignment(prop) || ts.isSpreadAssignment(prop))) { + options.runtime = true; + continue; + } lowerer.noLowering( `${surface} options in this form`, prop, @@ -2269,8 +2561,17 @@ function parseInspectOptions( // bounded by the (finite) object graph — exactly Node. void recursive; options.depth = Infinity; - } else if (!(ts.isIdentifier(value) && value.text === "undefined")) { + options.depthKind = value.kind === ts.SyntaxKind.NullKeyword ? 1 : 0; + } else if (ts.isIdentifier(value) && value.text === "undefined") { + // Node's ctx.depth is undefined: no depth limit. + options.depth = Infinity; + options.depthKind = 2; + } else { const n = numeric(); + if (n === null && atRunTime) { + options.runtime = true; + continue; + } if (n === null) { lowerer.noLowering( `${surface} with a non-literal depth option`, @@ -2281,7 +2582,8 @@ function parseInspectOptions( options.depth = n; } } else if (key === "colors") { - if (value.kind !== ts.SyntaxKind.FalseKeyword) { + if (value.kind !== ts.SyntaxKind.FalseKeyword && atRunTime) options.runtime = true; + else if (value.kind !== ts.SyntaxKind.FalseKeyword) { lowerer.noLowering( `${surface} with colors: true`, value, @@ -2293,6 +2595,10 @@ function parseInspectOptions( // compact of at least 1; false and smaller numbers break every // composite onto its own lines. compact: true is the older layout. const n = value.kind === ts.SyntaxKind.FalseKeyword ? 0 : numeric(); + if (n === null && atRunTime && value.kind !== ts.SyntaxKind.TrueKeyword) { + options.runtime = true; + continue; + } if (n === null || Number.isNaN(n)) { lowerer.noLowering( `${surface} with this compact option`, @@ -2300,9 +2606,22 @@ function parseInspectOptions( "compact must be false or a numeric literal (compact: true has no lowering)", ); } - options.compact = Math.max(n, 0); + // false is -1: the same layout as 0, and custom inspect functions + // read it back as false. + options.compact = value.kind === ts.SyntaxKind.FalseKeyword ? -1 : Math.max(n, 0); } else if (key === "sorted") { - if (value.kind !== ts.SyntaxKind.TrueKeyword && value.kind !== ts.SyntaxKind.FalseKeyword) { + const sortedLiteral = + value.kind === ts.SyntaxKind.TrueKeyword || value.kind === ts.SyntaxKind.FalseKeyword; + if ( + !sortedLiteral && + atRunTime && + !ts.isArrowFunction(value) && + !ts.isFunctionExpression(value) + ) { + options.runtime = true; + continue; + } + if (!sortedLiteral) { lowerer.noLowering( `${surface} with this sorted option`, value, @@ -2312,6 +2631,10 @@ function parseInspectOptions( options.sorted = value.kind === ts.SyntaxKind.TrueKeyword; } else if (key === "breakLength") { const n = ts.isIdentifier(value) && value.text === "Infinity" ? Infinity : numeric(); + if (n === null && atRunTime) { + options.runtime = true; + continue; + } if (n === null || Number.isNaN(n)) { lowerer.noLowering( `${surface} with a non-literal breakLength option`, @@ -2320,6 +2643,12 @@ function parseInspectOptions( ); } options.breakLength = n; + } else if ( + !dir && + key === "customInspect" && + (value.kind === ts.SyntaxKind.TrueKeyword || value.kind === ts.SyntaxKind.FalseKeyword) + ) { + options.customInspect = value.kind === ts.SyntaxKind.TrueKeyword; } else if (dir && key === "customInspect") { // console.dir's own default; only that value is lowered. if (value.kind !== ts.SyntaxKind.FalseKeyword) { @@ -2417,16 +2746,86 @@ function directCallableInspect(lowerer: Lowerer, node: ts.Expression, loc: SrcLo return null; } -/** util.inspect(value[, options]) — the spoke's inspect entry. */ +/** util.inspect(value[, options]) and inspect(value, showHidden, depth, + * colors) — the spoke's inspect entry. Options the compiler resolves + * render through the static helpers; colors, option values known only at + * run time and the legacy forms render the value's checked-dynamic view + * in the runtime (insp.inspect). */ function lowerInspectCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr { - if (expr.arguments.nodes.length < 1 || expr.arguments.nodes.length > 2) { + const args = expr.arguments.nodes; + if (args.length < 1 || args.length > 4 || args.some(ts.isSpreadElement)) { lowerer.noLowering( - `util.inspect with ${expr.arguments.nodes.length} arguments`, + `util.inspect with ${args.some(ts.isSpreadElement) ? "spread" : args.length} arguments`, expr, - "the supported forms are inspect(value) and inspect(value, options)", + "the supported forms are inspect(value), inspect(value, options) and inspect(value, showHidden, depth, colors)", ); } - return lowerInspectValue(lowerer, expr.arguments.nodes[0]!, expr.arguments.nodes[1], loc, false); + if (args.length <= 2 && !inspectOptionsAtRunTime(lowerer, args[1])) + return lowerInspectValue(lowerer, args[0]!, args[1], loc, false); + return lowerRuntimeInspect(lowerer, args, loc); +} + +/** Whether an options argument needs the runtime reader: an options value + * that is not an object literal, or a literal with colors or a computed + * depth, compact, breakLength or sorted value. Literal options otherwise + * keep the static parse, which also reports the literal options neither + * path lowers. */ +function inspectOptionsAtRunTime(lowerer: Lowerer, node: ts.Expression | undefined): boolean { + if (!node || (ts.isIdentifier(node) && node.text === "undefined")) return false; + if (!ts.isObjectLiteralExpression(node)) return true; + return parseInspectOptions(lowerer, node, false, false, true).runtime; +} + +/** util.inspect over the value's checked-dynamic view, with every argument + * evaluated in order and read in the runtime as Node's inspect reads it. */ +function lowerRuntimeInspect( + lowerer: Lowerer, + args: readonly ts.Expression[], + loc: SrcLoc, +): IrExpr { + const valueNode = args[0]!; + if ( + directCallableInspect(lowerer, valueNode, loc) !== null || + ts.isArrowFunction(valueNode) || + ts.isFunctionExpression(valueNode) + ) { + lowerer.noLowering( + "util.inspect of a function or class with these options", + valueNode, + "pass the options as a literal without colors", + ); + } + const value = lowerer.lowerExpr(valueNode); + if (value.type.kind !== "dyn") { + const reason = inspectSupport(lowerer, value.type, new Set(), { recursive: false }); + if (reason !== null) + lowerer.noLowering(`util.inspect of '${lowerer.fmt(value.type)}' values`, valueNode, reason); + if (!lowerer.dynConvertible(value.type)) + lowerer.noLowering( + `util.inspect with these options of '${lowerer.fmt(value.type)}' values`, + valueNode, + "the options are read at run time, where the value renders through its checked-dynamic view", + ); + } + const stmts: IrStmt[] = []; + const held = (init: IrExpr): IrExpr => { + const local = lowerer.declareHiddenLocal("%inspectArg", DYN); + stmts.push({ kind: "varDecl", localId: local.id, init, loc }); + return varRef(local.id, DYN, loc); + }; + const operands: IrExpr[] = [held(lowerer.coerceToExpected(value, DYN))]; + for (let i = 1; i < 4; i++) { + const node = args[i]; + operands.push(node ? held(lowerer.lowerExprExpecting(node, DYN)) : dynUndefinedExpr(loc)); + } + operands.push(numLit(args.length, loc)); + return { + kind: "seqExpr", + stmts, + result: { kind: "libCall", fn: "insp.inspect", args: operands, type: STRING, loc }, + type: STRING, + loc, + }; } /** `inspect(value, options)` over one value node: the shared body of @@ -2512,6 +2911,46 @@ export function lowerInspectValue( } const options = parseInspectOptions(lowerer, optionsNode, walk.recursive, dir); const depth = options.depth; + // Custom inspect functions read the options as Node spelled them: an + // unbounded depth given as Infinity or undefined, and customInspect: + // false (console.dir's default), apply around the render when the value + // can reach a custom method; the previous settings return afterwards. + const reachesCustom = + typeReachesCustomInspect(lowerer, value.type) || typeReachesDyn(lowerer, value.type); + const depthContext = reachesCustom && depth === Infinity && options.depthKind !== 1; + const customContext = reachesCustom && !options.customInspect; + if (depthContext || customContext) { + const stmts: IrStmt[] = []; + const restore: IrStmt[] = []; + const setting = (fn: "insp.customInspect" | "insp.depthKind", arg: IrExpr, type: IrType) => { + const previous = lowerer.declareHiddenLocal("%inspectContext", type); + const call = (a: IrExpr): IrExpr => ({ kind: "libCall", fn, args: [a], type, loc }); + stmts.push({ kind: "varDecl", localId: previous.id, init: call(arg), loc }); + restore.push({ kind: "exprStmt", expr: call(varRef(previous.id, type, loc)), loc }); + }; + if (customContext) setting("insp.customInspect", boolLit(false, loc), BOOL); + if (depthContext) setting("insp.depthKind", numLit(options.depthKind, loc), F64); + const text = lowerer.declareHiddenLocal("%inspectText", STRING); + stmts.push({ + kind: "varDecl", + localId: text.id, + init: withInspectLayout( + lowerer, + options, + value, + (input) => inspectExpr(lowerer, value.type, input, numLit(0, loc), numLit(depth, loc), loc), + loc, + ), + loc, + }); + return { + kind: "seqExpr", + stmts: [...stmts, ...restore.reverse()], + result: varRef(text.id, STRING, loc), + type: STRING, + loc, + }; + } if (dir && value.type.kind === "dyn") { return withInspectLayout( lowerer, @@ -2542,7 +2981,17 @@ export function lowerInspectValue( * formatNumber, strings verbatim, objects through inspect at the given * depth). Null = fence with the given reason. */ function formatSArg(lowerer: Lowerer, node: ts.Expression, depth: number, loc: SrcLoc): IrExpr { - const value = lowerer.lowerExpr(node); + return formatSValue(lowerer, node, lowerer.lowerExpr(node), depth, loc); +} + +/** formatSArg over the already-lowered value of `node`. */ +function formatSValue( + lowerer: Lowerer, + node: ts.Expression, + value: IrExpr, + depth: number, + loc: SrcLoc, +): IrExpr { const t = value.type; if (t.kind === "string") return value; if (t.kind === "f64") @@ -2626,6 +3075,13 @@ function formatOArg(lowerer: Lowerer, node: ts.Expression, depth: number, loc: S node, reason, ); + if (depth === 4 && typeReachesCustomInspect(lowerer, value.type)) { + lowerer.noLowering( + "util.format %o over values with [util.inspect.custom] methods", + node, + "%o passes showHidden to the methods — use %O", + ); + } if (depth === 4 && typeTreeHasArray(lowerer, value.type, new Set())) { lowerer.noLowering( "util.format %o over arrays", @@ -2708,8 +3164,9 @@ function formatIntegerFixed(value: IrExpr, spec: "%d" | "%i", loc: SrcLoc): IrEx /** util.format(...) / util.formatWithOptions({}, ...): the compile-time * %-substitution over a literal format string (formatWithOptionsInternal * ported exactly — the arg cursor, the args-exhausted guard, %c's - * consume-and-drop), with per-static-type conversions. Non-literal - * format strings lower only in the substitution-free shapes. */ + * consume-and-drop), with per-static-type conversions. Format strings + * known only at run time, spread arguments and non-empty options lower to + * the runtime formatter (lowerRuntimeFormat, heldFormatArgs). */ export function lowerFormatCall( lowerer: Lowerer, expr: ts.CallExpression, @@ -2721,30 +3178,60 @@ export function lowerFormatCall( const argNodes = [...(argNodesOverride ?? expr.arguments.nodes)]; if (withOptions) { const opts = argNodes.shift(); + // The empty literal {} is the defaults: exactly util.format. if (!opts || !ts.isObjectLiteralExpression(opts) || opts.properties.nodes.length > 0) { - lowerer.noLowering( - "util.formatWithOptions with a non-empty options literal", - opts ?? expr, - "only the empty literal {} (the defaults — exactly util.format) is lowered", - ); + if (opts && ts.isSpreadElement(opts)) { + lowerer.noLowering( + "util.formatWithOptions with a spread options argument", + opts, + "pass the options object as the first argument", + ); + } + return lowerRuntimeFormat(lowerer, argNodes, loc, opts ?? null); } } if (argNodes.length === 0) return strLit("", loc); const first = argNodes[0]!; const firstIsLiteral = ts.isStringLiteral(first) || ts.isNoSubstitutionTemplateLiteral(first); + if (argNodes.some(ts.isSpreadElement)) { + if (literalPrefix !== "" && !firstIsLiteral) + throw new InternalCompilerError("a format prefix needs a literal format string"); + return lowerRuntimeFormat(lowerer, argNodes, loc, undefined, literalPrefix); + } /** The rest-args tail: ` ` + (strings verbatim, everything else * inspected at the default depth). */ const restArg = (node: ts.Expression): IrExpr => formatSArg(lowerer, node, 2, loc); if (!firstIsLiteral) { - const firstT = lowerer.mapTypeOf(lowerer.typeOf(first)); - if (firstT?.kind === "string" && argNodes.length > 1) { - lowerer.noLowering( - "util.format with a runtime format string and further arguments", - first, - "the %-substitution positions are runtime-dependent — use a string literal", - ); + const runtime = argNodes.length > 1 ? runtimeFormatString(lowerer, first) : null; + if (runtime === "other") return lowerRuntimeFormat(lowerer, argNodes, loc); + if (runtime !== null) { + // A string that substitutes formats at run time; any other string + // joins with the remaining arguments, as below. + const held = heldFormatArgs(lowerer, argNodes, loc); + if (!held.joinable) { + return { kind: "seqExpr", stmts: held.stmts, result: held.format(), type: STRING, loc }; + } + const joined: IrExpr[] = []; + held.args.forEach((arg, i) => { + if (i > 0) joined.push(strLit(" ", loc)); + joined.push(formatRestValue(lowerer, arg.node, arg.ref(), loc)); + }); + return { + kind: "seqExpr", + stmts: held.stmts, + result: { + kind: "ternary", + cond: held.specifies, + then: held.format(), + else_: concatAll(joined, loc), + type: STRING, + loc, + }, + type: STRING, + loc, + }; } // No substitutions possible: every argument joins with spaces. const parts: IrExpr[] = []; @@ -3030,6 +3517,240 @@ export function lowerFormatCall( return concatAll(parts, loc); } +/** One space-joined argument of util.format, as Node's formatter renders + * it after the format string: strings verbatim, everything else through + * inspect at the default depth. */ +function formatRestValue( + lowerer: Lowerer, + node: ts.Expression, + value: IrExpr, + loc: SrcLoc, +): IrExpr { + switch (value.type.kind) { + case "string": + return value; + case "f64": + return { kind: "libCall", fn: "insp.f64", args: [value], type: STRING, loc }; + case "bool": + return { kind: "toString", operand: value, type: STRING, loc }; + case "dyn": + return { kind: "libCall", fn: "insp.dynS", args: [value, numLit(2, loc)], type: STRING, loc }; + default: + return lowerConsoleInspectArg(lowerer, node, value, "util.format", loc); + } +} + +const UNCONVERTIBLE_HINT = + "the runtime formatter reads its arguments as checked-dynamic values — use a string literal format"; + +/** A string-valued expression that throws the SC2020 refusal where it runs + * (a JavaScript construct fence deferred to the branch that needs it). */ +function runtimeFenceValue( + lowerer: Lowerer, + surface: string, + node: ts.Node, + hint: string, + loc: SrcLoc, +): IrExpr { + const diag = noLoweringDiag(surface, locOf(node), hint); + lowerer.runtimeFences.push(diag); + const sf = ts.getSourceFileOfNode(node); + const line = ts.getLineAndCharacterOfPosition(sf, diag.loc.start).line + 1; + const slot = lowerer.declareHiddenLocal("%never", STRING); + slot.mutable = true; + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: slot.id, init: null, loc }, + { + kind: "runtimeFence", + code: diag.code, + message: `${diag.message} [${diag.code} at ${diag.loc.file}:${line}]`, + loc, + }, + ], + result: varRef(slot.id, STRING, loc), + diverges: true, + type: STRING, + loc, + }; +} + +/** How a non-literal format string is known: "string" and "dyn" values + * test for a substitution at run time, "other" types (unions with a string + * arm) format at run time, and null types never hold a string. */ +export function runtimeFormatString( + lowerer: Lowerer, + first: ts.Expression, +): "string" | "dyn" | "other" | null { + const t = lowerer.mapTypeOf(lowerer.typeOf(first)); + if (t === null) return "other"; + if (t.kind === "string") return "string"; + if (t.kind === "dyn") return "dyn"; + if (t.kind === "union") { + const arms = lowerer.unions.get(t.unionId)?.arms ?? []; + return arms.some((arm) => arm.kind === "string" || arm.kind === "dyn") ? "other" : null; + } + return t.kind === "jsval" || t.kind === "caught" ? "other" : null; +} + +/** The runtime formatter over a call's arguments: one checked-dynamic + * pack in source order (spreads contribute their elements), after the + * options when `options` is given (null: an omitted options argument, + * which Node's validation rejects). A literal prefix joins a literal + * format string. */ +export function lowerRuntimeFormat( + lowerer: Lowerer, + argNodes: readonly ts.Expression[], + loc: SrcLoc, + options?: ts.Expression | null, + literalPrefix = "", +): IrExpr { + const stmts: IrStmt[] = []; + let optionsRef: IrExpr | null = null; + if (options !== undefined) { + const held = lowerer.declareHiddenLocal("%formatOptions", DYN); + stmts.push({ + kind: "varDecl", + localId: held.id, + init: options === null ? dynUndefinedExpr(loc) : lowerer.lowerExprExpecting(options, DYN), + loc, + }); + optionsRef = varRef(held.id, DYN, loc); + } + let nodes = argNodes; + const leading: IrExpr[] = []; + if (literalPrefix !== "") { + const first = argNodes[0] as ts.StringLiteralLike; + leading.push(strLit(literalPrefix + first.text, loc)); + nodes = argNodes.slice(1); + } + const pack = dynArgumentPack(lowerer, nodes, loc, { + leading, + blame: argNodes[0] ?? options ?? undefined, + }); + stmts.push(...pack.stmts); + const result: IrExpr = + optionsRef === null + ? { kind: "libCall", fn: "insp.format", args: [pack.pack()], type: STRING, loc } + : { + kind: "libCall", + fn: "insp.formatWithOptions", + args: [optionsRef, pack.pack()], + type: STRING, + loc, + }; + return { kind: "seqExpr", stmts, result, type: STRING, loc }; +} + +/** The arguments of a call whose first argument is a string or dyn value + * known only at run time, each evaluated once into a hidden local: + * `specifies` tests that string for a '%' before its last character, and + * `format()` is the runtime formatter over the held values. The caller + * renders the other branch (the space-joined arguments) from `args` when + * every argument is `joinable` there. */ +export function heldFormatArgs( + lowerer: Lowerer, + argNodes: readonly ts.Expression[], + loc: SrcLoc, +): { + stmts: IrStmt[]; + args: { node: ts.Expression; ref: () => IrExpr }[]; + specifies: IrExpr; + format: () => IrExpr; + joinable: boolean; +} { + const stmts: IrStmt[] = []; + let unconvertible: { node: ts.Expression; type: IrType } | null = null; + const unconvertibleSurface = (t: IrType): string => + `util.format with a runtime format string over '${lowerer.fmt(t)}' values`; + const args = argNodes.map((node) => { + const value = lowerer.lowerExpr(node); + if (value.type.kind === "void") { + lowerer.unsupported( + "SC1090", + node, + "util.format of a void call result (call it as its own statement, then pass undefined)", + ); + } + if (value.type.kind !== "dyn" && !lowerer.dynConvertible(value.type)) { + // JavaScript keeps the space-joined branch, which printed these + // values before, and refuses only a string that substitutes. + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) { + lowerer.noLowering(unconvertibleSurface(value.type), node, UNCONVERTIBLE_HINT); + } + unconvertible ??= { node, type: value.type }; + } + // Unit values have no local: their effects run here and a literal + // stands in. + const k = value.type.kind; + if (k === "undefinedT" || k === "nullT") { + if (value.kind !== "unitLit") stmts.push({ kind: "exprStmt", expr: value, loc }); + const unit = k === "nullT" ? ("null" as const) : ("undefined" as const); + return { node, ref: (): IrExpr => ({ kind: "unitLit", unit, type: value.type, loc }) }; + } + const held = lowerer.declareHiddenLocal("%formatArg", value.type); + stmts.push({ kind: "varDecl", localId: held.id, init: value, loc }); + return { node, ref: () => varRef(held.id, value.type, loc) }; + }); + const first = args[0]!.ref(); + const specifies: IrExpr = + first.type.kind === "string" + ? { kind: "libCall", fn: "insp.formatSpecifies", args: [first], type: BOOL, loc } + : { + kind: "libCall", + fn: "insp.formatSpecifiesDyn", + args: [lowerer.coerceToExpected(first, DYN)], + type: BOOL, + loc, + }; + const format = (): IrExpr => { + if (unconvertible !== null) { + const fenced = unconvertible; + return runtimeFenceValue( + lowerer, + unconvertibleSurface(fenced.type), + fenced.node, + UNCONVERTIBLE_HINT, + loc, + ); + } + // Strings, numbers, booleans, null and undefined format without the + // checked-dynamic walk other values need, which keeps it out of the + // executable. + const primitive = (t: IrType): boolean => + t.kind === "string" || + t.kind === "f64" || + t.kind === "bool" || + t.kind === "undefinedT" || + t.kind === "nullT" || + (t.kind === "union" && (lowerer.unions.get(t.unionId)?.arms.every(primitive) ?? false)); + return { + kind: "libCall", + fn: args.every((arg) => primitive(arg.ref().type)) ? "insp.formatPrimitives" : "insp.format", + args: [ + { + kind: "dynArrLit", + elems: args.map((arg) => lowerer.coerceToExpected(arg.ref(), DYN)), + type: DYN, + loc, + }, + ], + type: STRING, + loc, + }; + }; + // The space-joined branch renders each argument statically; arguments + // the static rendering refuses take the runtime formatter in both cases. + const joinable = args.every(({ ref }) => { + const t = ref().type; + if (t.kind === "func") return false; + if (t.kind === "bytes") return true; + return inspectSupport(lowerer, t, new Set(), { recursive: false }) === null; + }); + return { stmts, args, specifies, format, joinable }; +} + /** Arrays anywhere in the tree — the %o (showHidden) gate. */ function typeTreeHasArray(lowerer: Lowerer, t: IrType, visiting: Set): boolean { switch (t.kind) { diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 5238333ad..8013b842f 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -93,6 +93,7 @@ import { createRequireProgramModuleDecl, } from "./builtins/module-bindings.js"; import { isPromisifyCall, registerBuiltinCallableAlias } from "./builtins/callable-aliases.js"; +import { customInspectRegistrations } from "./lower-inspect.js"; import { registerBuiltinClassAlias } from "./builtins/class-values.js"; import { stripTypeCasts } from "./builtins/arguments.js"; import { isWebCryptoMemberType } from "./builtins/webcrypto.js"; @@ -1947,18 +1948,21 @@ export function collectGlobals( // symbol registers HERE, before any use site can lower, and the // statement lowering skips the declaration by the same check. A // bad promisify target reports here (recorded once; the statement - // lowering poisons too, like every collectGlobals failure). + // lowering poisons too, like every collectGlobals failure). Targets + // that promisify to a function value (typed callback-last functions + // and untyped functions) keep an ordinary global. if ( ts.isIdentifier(decl.name) && decl.initializer && isPromisifyCall(lowerer, decl.initializer) ) { + let registered = true; try { - lowerer.registerPromisifiedBuiltinDecl(decl.name, decl.initializer); + registered = lowerer.registerPromisifiedBuiltinDecl(decl.name, decl.initializer); } catch (e) { if (!(e instanceof PoisonError)) throw e; } - continue; + if (registered) continue; } // `const inspect = require("util").inspect` at file scope: a // named import in const clothing (builtinImportOf resolves uses) @@ -3523,6 +3527,9 @@ export function buildMain(lowerer: Lowerer): IrFunction { loc, }); } + // Classes with custom inspect methods register their compiled calls for + // the runtime's inspect walk before any module runs, in every thread. + body.unshift(...customInspectRegistrations(lowerer, loc)); // Node's startup refusal (a resolution the graph carries that Node // rejects — preflight's Node-order resolution walk — or a module-LINK // SyntaxError from either named-import checker): the graph is refused diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 93c6246d9..c2c60abdc 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -13677,6 +13677,10 @@ export class Lowerer { * are only dispatch sentinels for call-site-specific lowering. */ lowerBuiltinCallableValue(bi: { module: string; member: string }, loc: SrcLoc): IrExpr | null { if (bi.module === "util" && bi.member === "types") return utilTypesModuleValue(this, loc); + // util.inspect: the function custom inspect methods receive, one + // identity per thread. + if (bi.module === "util" && bi.member === "inspect") + return { kind: "libCall", fn: "insp.inspectFunction", args: [], type: DYN, loc }; if (bi.module === "util/types" && builtinModuleFnOf(this, bi.module, bi.member)) return lowerUtilTypeValue(this, bi.member, loc); if (bi.module === "util" && bi.member === "getSystemErrorMap") diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 8b826304b..80da35ad3 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -1686,7 +1686,8 @@ export const BUILTIN_MODULE_FNS: Record< // lowerVarDecl / collectGlobals BEFORE any call lowering runs — and // inspect/format/formatWithOptions plus parseArgs through the util spoke // (lower-inspect.ts: per-type synthesized traversal helpers, - // compile-time format strings, checked-dynamic config/result for + // compile-time format strings and the runtime formatter for the others, + // checked-dynamic config/result for // parseArgs), which both dispatch paths try before the generic table. // parseArgs is tabled as well so the generated surface manifest records // the dedicated static member; the spoke owns its optional arity and diff --git a/packages/compiler/src/frontend/lowering/symbol-fields.ts b/packages/compiler/src/frontend/lowering/symbol-fields.ts index 56718615e..ea495d364 100644 --- a/packages/compiler/src/frontend/lowering/symbol-fields.ts +++ b/packages/compiler/src/frontend/lowering/symbol-fields.ts @@ -76,7 +76,36 @@ export function fenceSymbolFieldCopy( } } +/** The registry name util.inspect.custom denotes. */ +export const INSPECT_CUSTOM_DESCRIPTION = "nodejs.util.inspect.custom"; +/** The method slot of a class's `[util.inspect.custom]` member. */ +export const INSPECT_CUSTOM_MEMBER = `${FIELD_PREFIX}Symbol(${INSPECT_CUSTOM_DESCRIPTION})`; + +/** `inspect.custom` over node:util's inspect: a named import, `util.inspect` + * or the CommonJS `util_1.inspect`. */ +export function isInspectCustomAccess(lowerer: Lowerer, expr: ts.Expression): boolean { + while (ts.isParenthesizedExpression(expr)) expr = expr.expression; + if (!ts.isPropertyAccessExpression(expr) || expr.questionDotToken) return false; + if (!ts.isIdentifier(expr.name) || expr.name.text !== "custom") return false; + let target = expr.expression; + while (ts.isParenthesizedExpression(target)) target = target.expression; + const bi = ts.isIdentifier(target) + ? lowerer.builtinImportOf(target) + : ts.isPropertyAccessExpression(target) && !target.questionDotToken + ? lowerer.builtinMemberOf(target) + : null; + return bi?.module === "util" && bi.member === "inspect"; +} + export function classSymbolKeyOf(lowerer: Lowerer, key: ts.Expression): ClassSymbolKey | null { + // util.inspect.custom is the registered Symbol.for(INSPECT_CUSTOM_DESCRIPTION), + // one identity with the program's own Symbol.for calls of that name. + if (isInspectCustomAccess(lowerer, key)) { + let e = key; + while (ts.isParenthesizedExpression(e)) e = e.expression; + const sym = lowerer.checker.getSymbolAtLocation((e as ts.PropertyAccessExpression).name); + return sym ? symbolKeyOf(lowerer, sym, true, INSPECT_CUSTOM_DESCRIPTION) : null; + } if ( !ts.isIdentifier(key) && (!ts.isPropertyAccessExpression(key) || key.questionDotToken || !ts.isIdentifier(key.name)) diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index aef61c743..3db647f64 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -2955,6 +2955,30 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // spawn consumes a presence-sensitive option bag, including optional // stdio and env values. Keep it checked-dynamic for native normalization. if (isStdlibInterface("SpawnOptions")) return DYN; + // util.inspect as a value (a custom inspect method's third parameter is + // `typeof inspect`): the runtime's inspect function, called through the + // checked-dynamic boundary. + if ( + psym !== undefined && + ts.symbolName(psym) === "inspect" && + psym.declarations.some( + (d) => + ts.isFunctionDeclaration(d) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + isDeclaredInAmbientModule(d, "util"), + ) + ) + return DYN; + // util.inspect's options bag, which inspect and formatWithOptions read + // at run time (its sorted option can be a comparator function), and the + // options custom inspect functions receive. + if ( + (isStdlibInterface("InspectOptions") || isStdlibInterface("InspectOptionsStylized")) && + psym!.declarations.some( + (d) => ts.isInterfaceDeclaration(d) && isDeclaredInAmbientModule(d, "util"), + ) + ) + return DYN; // dns.LookupAllOptions (`all: true`, the overload whose callback takes // an address list): a lookup option bag dns.lookup reads at run time. // It rides checked-dynamic storage, so the lookup function type that diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index eb3f8c6ab..d676b05dd 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -180,6 +180,17 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "num.toStringRadix", "num.toStringRadixF64", "insp.jsonDyn", + // The runtime formatter calls toString, valueOf and toJSON, and refuses + // renderings it has no lowering for. + "insp.format", + "insp.formatWithOptions", + "insp.inspect", + // The options object reads the caller's options (getters included), and + // the checked-dynamic walk calls custom inspect functions. + "insp.customOptions", + "insp.customResult", + "insp.dyn", + "insp.dynS", // diagnostics_channel: publish runs subscribers synchronously (a throw // propagates — the documented divergence from triggerUncaughtException); // the subscribe/unsubscribe forms throw Node's ERR_INVALID_ARG_TYPE for diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 8edb223b3..6e07e1467 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4370,8 +4370,43 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "insp.errorCause": { argTypes: [null, F64, F64], result: STRING }, "insp.dyn": { argTypes: [DYN, F64, F64], result: STRING }, "insp.dynS": { argTypes: [DYN, F64], result: STRING }, + /** util.format over a dyn array of its arguments, and + * util.formatWithOptions(options, pack): the runtime formatter for format + * strings known at run time and spread arguments. */ + "insp.format": { argTypes: [DYN], result: STRING }, + /** insp.format over arguments that are all strings, numbers, booleans, + * null or undefined, without the checked-dynamic walk. */ + "insp.formatPrimitives": { argTypes: [DYN], result: STRING }, + "insp.formatWithOptions": { argTypes: [DYN, DYN], result: STRING }, + /** util.inspect(value, options, depth, colors) with the call's argument + * count: the runtime reads the options and renders the checked-dynamic + * view. */ + "insp.inspect": { argTypes: [DYN, DYN, DYN, DYN, F64], result: STRING }, + /** A [util.inspect.custom] method's call: whether ctx.customInspect is + * set, its depth argument (the remaining depth at recursion r of depth + * d), its options object and util.inspect, then its result rendered: a + * string re-indented at the current level, or a checked-dynamic value + * (customIsSelf tests for the instance itself). customInspect sets the + * flag for the renders that follow and answers the previous value, and + * depthKind does the same for how an unbounded depth was given (0 + * Infinity, 1 null, 2 undefined). */ + "insp.customEnabled": { argTypes: [], result: BOOL }, + /** Registers a class's compiled custom inspect function under its + * instances' typed-ref type key, for the checked-dynamic walk. */ + "insp.registerCustom": { argTypes: [STRING, DYN], result: VOID }, + "insp.customInspect": { argTypes: [BOOL], result: BOOL }, + "insp.depthKind": { argTypes: [F64], result: F64 }, + "insp.customDepth": { argTypes: [F64, F64], result: DYN }, + "insp.customOptions": { argTypes: [F64], result: DYN }, + "insp.inspectFunction": { argTypes: [], result: DYN }, + "insp.customText": { argTypes: [STRING], result: STRING }, + "insp.customIsSelf": { argTypes: [DYN, DYN], result: BOOL }, + "insp.customResult": { argTypes: [DYN, F64, F64], result: STRING }, + /** Whether a runtime format string has a '%' before its last character. */ + "insp.formatSpecifies": { argTypes: [STRING], result: BOOL }, + "insp.formatSpecifiesDyn": { argTypes: [DYN], result: BOOL }, "insp.begin": { argTypes: [F64], result: VOID }, - /** util.inspect's breakLength, compact (0 for false) and sorted for the + /** util.inspect's breakLength, compact (-1 for false) and sorted for the * renders that follow; the call site restores 80, 3 and false afterwards. */ "insp.layout": { argTypes: [F64, F64, BOOL], result: VOID }, "insp.entry": { argTypes: [STRING, BOOL], result: VOID }, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7520-util-format-runtime.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7520-util-format-runtime.ts.json new file mode 100644 index 000000000..825fd9882 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7520-util-format-runtime.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7520-util-format-runtime.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7521-util-format-runtime.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7521-util-format-runtime.cjs.json new file mode 100644 index 000000000..0714d5c08 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7521-util-format-runtime.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7521-util-format-runtime.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7522-promisify-untyped.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7522-promisify-untyped.cjs.json new file mode 100644 index 000000000..bcd6b54c6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7522-promisify-untyped.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7522-promisify-untyped.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7523-promisify-untyped.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7523-promisify-untyped.ts.json new file mode 100644 index 000000000..20e96495a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7523-promisify-untyped.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7523-promisify-untyped.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7524-inspect-custom.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7524-inspect-custom.ts.json new file mode 100644 index 000000000..4f5a3d007 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7524-inspect-custom.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7524-inspect-custom.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7525-inspect-custom.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7525-inspect-custom.cjs.json new file mode 100644 index 000000000..dddad2358 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7525-inspect-custom.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7525-inspect-custom.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7526-inspect-options-runtime.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7526-inspect-options-runtime.ts.json new file mode 100644 index 000000000..70b202903 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7526-inspect-options-runtime.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7526-inspect-options-runtime.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inspect-custom-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inspect-custom-fences.ts.json new file mode 100644 index 000000000..dff7b7d6b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inspect-custom-fences.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/inspect-custom-fences.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_console_native.c b/packages/runtime/src/scr_console_native.c index 8c7467127..492d57d82 100644 --- a/packages/runtime/src/scr_console_native.c +++ b/packages/runtime/src/scr_console_native.c @@ -523,7 +523,11 @@ ScrStr *scr_console_dir_dyn(ScrDyn *value, double depth) { scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); return scr_str_new("", 0); } - return scr_insp_dyn(value, 0, depth); + /* console.dir is inspect with customInspect: false. */ + bool custom = scr_insp_custom_inspect(false); + ScrStr *out = scr_insp_dyn(value, 0, depth); + scr_insp_custom_inspect(custom); + return out; } /* ── console.table ─────────────────────────────────────────────────── diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 74b710dc9..fdb114535 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -863,6 +863,10 @@ ScrDyn *scr_dyn_open_iterator(const ScrDyn *value, int *kind, bool generators); * for-of protocol reads it. +1, or NULL with the error pending. */ ScrDyn *scr_dyn_iterator_result_get(const ScrDyn *result, const char *key, size_t len); void scr_dyn_install_iterator_symbol(ScrDyn *key); +/* Symbol.for('nodejs.util.inspect.custom'): the symbol unit installs it + * 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_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 diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index a4d124ac3..2c7951103 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -152,6 +152,154 @@ static size_t insp_width(const char *data, size_t len) { return width; } +/* ── colors (stylizeWithColor) ───────────────────────────────────────── */ + +/* ctx.colors for the render in progress: the checked-dynamic walk wraps + * each styled leaf in inspect.colors' SGR codes for its inspect.styles + * style, and the layout measures entries without the codes. Static + * renderings never run with it set. */ +static SCR_TL bool g_colors; + +typedef enum { + INSP_SPECIAL, + INSP_NUMBER, + INSP_BIGINT, + INSP_BOOLEAN, + INSP_UNDEFINED, + INSP_NULL, + INSP_STRING, + INSP_SYMBOL, + INSP_DATE, +} InspStyle; + +/* inspect.styles mapped through inspect.colors: [open, close]. */ +static const unsigned char insp_style_codes[][2] = { + {36, 39}, /* special: cyan */ + {33, 39}, /* number: yellow */ + {33, 39}, /* bigint: yellow */ + {33, 39}, /* boolean: yellow */ + {90, 39}, /* undefined: grey */ + {1, 22}, /* null: bold */ + {32, 39}, /* string: green */ + {32, 39}, /* symbol: green */ + {35, 39}, /* date: magenta */ +}; + +static void insp_style_open(InspBuf *b, InspStyle style) { + char code[8]; + int n = snprintf(code, sizeof code, "\x1b[%um", insp_style_codes[style][0]); + ib_bytes(b, code, (size_t)n); +} + +static void insp_style_close(InspBuf *b, InspStyle style) { + char code[8]; + int n = snprintf(code, sizeof code, "\x1b[%um", insp_style_codes[style][1]); + ib_bytes(b, code, (size_t)n); +} + +/* ctx.stylize(text, style): +1; moves `text` in. */ +static ScrStr *insp_styled(ScrStr *text, InspStyle style) { + if (!g_colors) return text; + InspBuf b = {0}; + insp_style_open(&b, style); + ib_str(&b, text); + insp_style_close(&b, style); + scr_str_release(text); + return ib_take(&b); +} + +static ScrStr *insp_styled_lit(const char *text, size_t len, InspStyle style) { + return insp_styled(scr_str_new(text, len), style); +} + +/* The SC2020 refusal for colored renderings of values whose styles this + * runtime does not produce; answers an empty string. */ +static ScrStr *insp_colors_fence(const char *what) { + InspBuf b = {0}; + ib_cstr(&b, "colored inspect output of "); + ib_cstr(&b, what); + ib_cstr(&b, " has no lowering"); + ScrStr *message = ib_take(&b); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + scr_str_release(message); + return scr_str_new("", 0); +} + +static ScrStr *insp_colors_fence_values(const char *cls) { + InspBuf b = {0}; + ib_cstr(&b, cls); + ib_cstr(&b, " values"); + ScrStr *what = ib_take(&b); + ScrStr *out = insp_colors_fence(what->data); + scr_str_release(what); + return out; +} + +/* The SGR sequence Node's removeColors strips (\x1b[<1 or 2 digits>m) at + * data[i]: its length, or 0. */ +static size_t insp_sgr_at(const char *data, size_t len, size_t i) { + if (data[i] != '\x1b' || i + 3 >= len || data[i + 1] != '[') return 0; + size_t j = i + 2, digits = 0; + while (j < len && digits < 2 && data[j] >= '0' && data[j] <= '9') j++, digits++; + return digits > 0 && j < len && data[j] == 'm' ? j + 1 - i : 0; +} + +/* A control sequence stripVTControlCharacters removes at data[i] (CSI + * sequences after ESC [ or C1 CSI, and OSC sequences ended by BEL): its + * length, or 0. */ +static size_t insp_vt_at(const char *data, size_t len, size_t i) { + const unsigned char *p = (const unsigned char *)data; + size_t j; + if (p[i] == 0x1b && i + 1 < len && p[i + 1] == '[') j = i + 2; + else if (p[i] == 0xc2 && i + 1 < len && p[i + 1] == 0x9b) j = i + 2; + else if (p[i] == 0x1b && i + 1 < len && p[i + 1] == ']') { + for (j = i + 2; j < len; j++) + if (p[j] == 0x07) return j + 1 - i; + return 0; + } else return 0; + while (j < len && ((p[j] >= '0' && p[j] <= '9') || p[j] == ';' || p[j] == '?')) j++; + if (j >= len) return 0; + unsigned char c = p[j]; + bool final = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'P') || (c >= 'R' && c <= 'T') || + c == 'Z' || c == 'c' || (c >= 'f' && c <= 'n') || c == 'q' || c == 'r' || + c == 't' || c == 'y' || c == '=' || c == '>' || c == '<' || c == '~'; + return final ? j + 1 - i : 0; +} + +/* An entry's display width for the grid (getStringWidth(entry, + * ctx.colors)) and its UTF-16 length for the break-length test (Node's + * removeColors under ctx.colors). */ +static size_t insp_entry_width(const ScrStr *s) { + if (!g_colors) return insp_width(s->data, s->len); + size_t width = 0; + for (size_t i = 0; i < s->len;) { + size_t skip = insp_vt_at(s->data, s->len, i); + if (skip) { + i += skip; + continue; + } + uint32_t cp = insp_cp_at(s->data, s->len, &i); + if (insp_full_width(cp)) width += 2; + else if (!insp_zero_width(cp)) width += 1; + } + return width; +} + +static size_t insp_entry_len(const ScrStr *s) { + if (!g_colors) return insp_utf16_len(s->data, s->len); + size_t units = 0; + for (size_t i = 0; i < s->len;) { + size_t skip = insp_sgr_at(s->data, s->len, i); + if (skip) { + i += skip; + continue; + } + uint32_t cp = insp_cp_at(s->data, s->len, &i); + units += cp >= 0x10000 ? 2 : 1; + } + return units; +} + /* ── number rendering ────────────────────────────────────────────────── */ /* formatNumber: JS ToString EXCEPT -0 renders as '-0'. */ @@ -245,12 +393,12 @@ static SCR_TL size_t g_frames_cap; static SCR_TL size_t g_indent; /* ctx.indentationLvl */ static SCR_TL bool g_no_break; /* breakLength: Infinity (console.table cells) */ static SCR_TL double g_break_length = 80; /* ctx.breakLength */ -static SCR_TL double g_compact = 3; /* ctx.compact: a number, 0 for false */ +static SCR_TL double g_compact = 3; /* ctx.compact: a number, -1 for false */ static SCR_TL bool g_sorted; /* ctx.sorted === true */ static SCR_TL double g_cur_depth; /* ctx.currentDepth (last-entered composite) */ /* util.inspect's breakLength, compact and sorted for the renders that - * follow (compact false is 0); the call site restores 80, 3 and false + * follow (compact false is -1); the call site restores 80, 3 and false * after its render. */ void scr_insp_layout(double break_length, double compact, bool sorted) { g_break_length = break_length; @@ -369,7 +517,7 @@ void scr_insp_seen_push(const void *v) { ScrStr *scr_insp_circular(double id) { char buf[32]; int n = snprintf(buf, sizeof buf, "[Circular *%.0f]", id); - return scr_str_new(buf, (size_t)n); + return insp_styled_lit(buf, (size_t)n, INSP_SPECIAL); } /* Pops the value's seen entry after its frame closed. scr_insp_end already @@ -403,7 +551,7 @@ static void insp_group(InspFrame *f, bool trailing_more) { size_t *data_len = malloc((output_len ? output_len : 1) * sizeof(size_t)); if (!data_len) insp_oom(); for (size_t i = 0; i < output_len; i++) { - size_t w = insp_width(f->items[i]->data, f->items[i]->len); + size_t w = insp_entry_width(f->items[i]); data_len[i] = w; total_length += (double)w + 2; if ((double)w > max_length) max_length = (double)w; @@ -494,7 +642,7 @@ static bool insp_below_break_length(InspFrame *f, size_t start, const ScrStr *ba size_t total = f->len + start; if ((double)(total + f->len) > g_break_length) return false; for (size_t i = 0; i < f->len; i++) { - total += insp_utf16_len(f->items[i]->data, f->items[i]->len); + total += insp_entry_len(f->items[i]); if ((double)total > g_break_length) return false; } return base->len == 0 || !insp_contains_byte(base->data, base->len, '\n'); @@ -520,12 +668,16 @@ ScrStr *scr_insp_end(ScrStr *base, ScrStr *b0, ScrStr *b1, double recurse, bool int ref_id = f->seen ? insp_circ_id(f->seen) : 0; if (ref_id) { char buf[32]; - int n = snprintf(buf, sizeof buf, base->len ? " " : "", ref_id); - size_t len = (size_t)n + base->len; - ref_base = scr_str_alloc_raw(len, len); - memcpy(ref_base->data, buf, (size_t)n); - memcpy(ref_base->data + n, base->data, base->len); - ref_base->data[len] = '\0'; + int n = snprintf(buf, sizeof buf, "", ref_id); + InspBuf rb = {0}; + if (g_colors) insp_style_open(&rb, INSP_SPECIAL); + ib_bytes(&rb, buf, (size_t)n); + if (g_colors) insp_style_close(&rb, INSP_SPECIAL); + if (base->len) { + ib_char(&rb, ' '); + ib_str(&rb, base); + } + ref_base = ib_take(&rb); base = ref_base; } size_t entries = f->len; @@ -622,6 +774,19 @@ void scr_insp_nested_begin(void) { g_ncirc = 0; } +/* nested_end's restore alone: the caller's context returns. */ +void scr_insp_nested_restore(void) { + InspSaved *sv = &g_saved[--g_nsaved]; + free(g_seen); + g_indent = sv->indent; + g_cur_depth = sv->cur_depth; + g_seen = sv->seen; + g_nseen = sv->nseen; + g_seen_cap = sv->seen_cap; + memcpy(g_circ, sv->circ, sizeof g_circ); + g_ncirc = sv->ncirc; +} + ScrStr *scr_insp_nested_end(ScrStr *prefix, ScrStr *inner) { InspSaved *sv = &g_saved[--g_nsaved]; free(g_seen); @@ -717,13 +882,17 @@ ScrStr *scr_insp_str(ScrStr *s) { ib_cstr(&out, " +\n"); ib_spaces(&out, g_indent + 2); } + if (g_colors) insp_style_open(&out, INSP_STRING); insp_quote_into(&out, v + chunk_start, end - chunk_start); + if (g_colors) insp_style_close(&out, INSP_STRING); chunk_start = end; first = false; } } } else { + if (g_colors) insp_style_open(&out, INSP_STRING); insp_quote_into(&out, v, vlen); + if (g_colors) insp_style_close(&out, INSP_STRING); } if (trailer[0]) ib_cstr(&out, trailer); free(val.data); @@ -1007,7 +1176,9 @@ static void insp_key_into(InspBuf *b, const char *key, size_t len) { if (bare) { ib_bytes(b, key, len); } else { + if (g_colors) insp_style_open(b, INSP_STRING); insp_quote_into(b, key, len); + if (g_colors) insp_style_close(b, INSP_STRING); } } @@ -1330,7 +1501,7 @@ static ScrStr *scr_insp_dyn_collection(ScrDyn *d, double recurse, double depth, ib_char(&out, '['); ib_cstr(&out, label); ib_char(&out, ']'); - return ib_take(&out); + return insp_styled(ib_take(&out), INSP_SPECIAL); } ScrStr *base = ib_take(&head); scr_insp_begin(recurse + 1); @@ -1395,9 +1566,9 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char : scr_str_new("{}", 2); } if (recurse > depth) { - if (cls) return scr_insp_class_tag(cls, NULL); - return d->null_proto ? scr_str_new("[Object: null prototype]", 24) - : scr_str_new("[Object]", 8); + if (cls) return insp_styled(scr_insp_class_tag(cls, NULL), INSP_SPECIAL); + return d->null_proto ? insp_styled_lit("[Object: null prototype]", 24, INSP_SPECIAL) + : insp_styled_lit("[Object]", 8, INSP_SPECIAL); } scr_insp_begin(recurse + 1); for (size_t i = 0; i < d->v.obj.len; i++) { @@ -1412,6 +1583,7 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char ScrStr *entry = ib_take(&eb); scr_insp_entry(entry, false); scr_str_release(entry); + if (scr_exc_pending()) break; } for (size_t i = 0; i < nsymbols; i++) { ScrDynEntry *ent = scr_insp_symbol_entry(d, i); @@ -1455,12 +1627,12 @@ ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth) { if (recurse > depth) { char tag[32]; int n = snprintf(tag, sizeof tag, "[%s]", scr_bytes_elem_name(b->elem)); - return scr_str_new(tag, (size_t)n); + return insp_styled_lit(tag, (size_t)n, INSP_SPECIAL); } scr_insp_begin(recurse + 1); size_t shown = b->len < 100 ? b->len : 100; for (size_t i = 0; i < shown; i++) { - ScrStr *s = scr_insp_f64(scr_bytes_get(b, (double)i)); + ScrStr *s = insp_styled(scr_insp_f64(scr_bytes_get(b, (double)i)), INSP_NUMBER); scr_insp_entry(s, true); scr_str_release(s); } @@ -1480,27 +1652,374 @@ ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth) { return out; } +/* ── custom inspect functions (util.inspect.custom) ────────────────────── + * formatValue's hook: an object whose [util.inspect.custom] member is a + * function (other than util.inspect itself, on an object that is not its + * constructor's prototype) renders as that function's result. It runs + * before the circular and depth checks, with the remaining depth + * (ctx.depth - recurseTimes), the options object getUserOptions builds and + * util.inspect. A string result is re-indented at the current level; the + * object itself renders normally; any other value renders in its place. + * Class instances held as checked-dynamic values have compiled methods + * only, so one whose class defines the method throws SC2020. */ +static SCR_TL bool g_custom_inspect = true; /* ctx.customInspect */ +/* ctx.depth as the options gave it, for the custom functions' depth + * argument and options when the render's depth is unbounded: 0 the number + * g_depth_number (Infinity or NaN), 1 null, 2 undefined. */ +static SCR_TL int g_depth_kind = 1; +static SCR_TL double g_depth_number; +/* The options object that held keys inspect does not read: getUserOptions + * spreads it into the options the custom functions receive (borrowed for + * the render). */ +static SCR_TL const ScrDyn *g_user_options; + +double scr_insp_depth_kind(double kind) { + double previous = g_depth_kind; + g_depth_kind = (int)kind; + g_depth_number = INFINITY; + return previous; +} + +bool scr_insp_custom_inspect(bool enabled) { + bool previous = g_custom_inspect; + g_custom_inspect = enabled; + return previous; +} + +/* The style inspect.styles maps a styleType to, as [open, close] codes: + * the styles above plus regexp (red) and module (underline). false when + * the type has no style. */ +static bool insp_style_of(const ScrStr *type, unsigned *open, unsigned *close) { + static const struct { + const char *name; + unsigned open, close; + } styles[] = { + {"special", 36, 39}, {"number", 33, 39}, {"bigint", 33, 39}, {"boolean", 33, 39}, + {"undefined", 90, 39}, {"null", 1, 22}, {"string", 32, 39}, {"symbol", 32, 39}, + {"date", 35, 39}, {"regexp", 31, 39}, {"module", 4, 24}, + }; + for (size_t i = 0; i < sizeof styles / sizeof *styles; i++) { + if (type->len == strlen(styles[i].name) && memcmp(type->data, styles[i].name, type->len) == 0) { + *open = styles[i].open; + *close = styles[i].close; + return true; + } + } + return false; +} + +/* options.stylize: stylizeWithColor under colors (the text in its style's + * codes, or unchanged), stylizeNoColor otherwise (the argument itself). + * caps[0] holds whether colors apply. */ +static ScrDyn *insp_stylize_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + ScrDyn *colors = scr_box_get_ref(closure->caps[0]); + bool colored = colors && scr_dyn_truthy(colors); + scr_dyn_release(colors); + ScrDyn *text = argc > 0 ? args[0] : (ScrDyn *)scr_dyn_undefined(); + if (!colored) return scr_dyn_retain(text); + ScrStr *str = scr_dyn_string_coerce_js(text); + if (!str) return NULL; + unsigned open, close; + ScrStr *type = argc > 1 && args[1]->kind == SCR_DYN_STR ? args[1]->v.str : NULL; + if (!type || !insp_style_of(type, &open, &close)) { + ScrDyn *out = scr_dyn_new_str(str); + scr_str_release(str); + return out; + } + InspBuf b = {0}; + char code[8]; + int n = snprintf(code, sizeof code, "\x1b[%um", open); + ib_bytes(&b, code, (size_t)n); + ib_str(&b, str); + n = snprintf(code, sizeof code, "\x1b[%um", close); + ib_bytes(&b, code, (size_t)n); + scr_str_release(str); + ScrStr *styled = ib_take(&b); + ScrDyn *out = scr_dyn_new_str(styled); + scr_str_release(styled); + return out; +} + +static ScrDyn *insp_inspect_call(ScrClosure *closure, ScrDyn *const *args, size_t argc); + +/* Whether `value` is the stylize function getUserOptions built for these + * colors. */ +static bool insp_own_stylize(const ScrDyn *value, bool colors) { + if (value->kind != SCR_DYN_FUNC || value->v.fn.thunk != insp_stylize_call) return false; + ScrDyn *flag = scr_box_get_ref(value->v.fn.clo->caps[0]); + bool colored = flag && scr_dyn_truthy(flag); + scr_dyn_release(flag); + return colored == colors; +} + +/* util.inspect as a function value (the custom functions' third + * argument): a fresh inspect context over the call's arguments, the + * caller's restored afterwards. One function per thread, so the argument + * compares equal to util.inspect. +1. */ +static SCR_TL ScrDyn *g_inspect_function; + +static void insp_inspect_function_cleanup(void) { + scr_dyn_release(g_inspect_function); + g_inspect_function = 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); + } + return scr_dyn_retain(g_inspect_function); +} + +static void insp_opt_set(ScrDyn *options, const char *key, ScrDyn *value /* moves in */) { + scr_dyn_obj_set(options, key, strlen(key), value); +} + +/* getUserOptions(ctx) for the render in progress at `depth`. +1. */ +static ScrDyn *insp_user_options(double depth) { + ScrDyn *options = scr_dyn_new_obj(); + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(closure->caps[0], scr_dyn_new_bool(g_colors)); + insp_opt_set(options, "stylize", + scr_dyn_new_func(closure, insp_stylize_call, 2, "native:util.stylize", + g_colors ? "stylizeWithColor" : "stylizeNoColor")); + insp_opt_set(options, "showHidden", scr_dyn_new_bool(false)); + insp_opt_set(options, "depth", + isfinite(depth) ? scr_dyn_new_num(depth) + : g_depth_kind == 0 ? scr_dyn_new_num(g_depth_number) + : g_depth_kind == 1 ? scr_dyn_new_null() + : scr_dyn_retain(scr_dyn_undefined())); + insp_opt_set(options, "colors", scr_dyn_new_bool(g_colors)); + insp_opt_set(options, "customInspect", scr_dyn_new_bool(true)); + insp_opt_set(options, "showProxy", scr_dyn_new_bool(false)); + insp_opt_set(options, "maxArrayLength", scr_dyn_new_num(100)); + insp_opt_set(options, "maxStringLength", scr_dyn_new_num(10000)); + insp_opt_set(options, "breakLength", scr_dyn_new_num(g_break_length)); + insp_opt_set(options, "compact", g_compact < 0 ? scr_dyn_new_bool(false) : scr_dyn_new_num(g_compact)); + insp_opt_set(options, "sorted", scr_dyn_new_bool(g_sorted)); + insp_opt_set(options, "getters", scr_dyn_new_bool(false)); + insp_opt_set(options, "numericSeparator", scr_dyn_new_bool(false)); + if (g_user_options && g_user_options->kind == SCR_DYN_OBJ) { + for (size_t i = 0; i < g_user_options->v.obj.len; i++) { + const ScrDynEntry *ent = &g_user_options->v.obj.entries[i]; + if (!ent->enumerable) continue; + ScrDyn *value = scr_dyn_obj_read((ScrDyn *)g_user_options, ent->key, ent->key_len); + if (!value) { + scr_dyn_release(options); + return NULL; + } + scr_dyn_obj_set(options, ent->key, ent->key_len, value); + } + } + return options; +} + +/* The custom inspect functions of the program's classes, by the type key + * of their instances' typed refs: compiled functions (self, depth, + * options, inspect) that call the instance's method and answer its + * result as a checked-dynamic value. The program registers them before + * it runs. */ +typedef struct { + char *type_key; + ScrDyn *fn; +} InspCustomClass; + +static SCR_TL InspCustomClass *g_custom_classes; +static SCR_TL size_t g_ncustom_classes; + +static void insp_custom_classes_cleanup(void) { + for (size_t i = 0; i < g_ncustom_classes; i++) { + free(g_custom_classes[i].type_key); + scr_dyn_release(g_custom_classes[i].fn); + } + free(g_custom_classes); + g_custom_classes = NULL; + g_ncustom_classes = 0; +} + +void scr_insp_register_custom(ScrStr *type_key, ScrDyn *fn) { + if (g_ncustom_classes == 0) scr_atexit(insp_custom_classes_cleanup); + InspCustomClass *grown = + realloc(g_custom_classes, (g_ncustom_classes + 1) * sizeof *g_custom_classes); + if (!grown) insp_oom(); + g_custom_classes = grown; + char *key = malloc(type_key->len + 1); + if (!key) insp_oom(); + memcpy(key, type_key->data, type_key->len); + key[type_key->len] = '\0'; + g_custom_classes[g_ncustom_classes].type_key = key; + g_custom_classes[g_ncustom_classes].fn = scr_dyn_retain(fn); + g_ncustom_classes++; +} + +static ScrDyn *insp_custom_class_fn(const ScrDyn *d) { + for (size_t i = 0; i < g_ncustom_classes; i++) + if (strcmp(g_custom_classes[i].type_key, d->v.typed_ref.type_key) == 0) + return g_custom_classes[i].fn; + return NULL; +} + +/* The [util.inspect.custom] result for an object kind, or NULL when the + * value renders normally (*thrown reports a pending exception). +1. */ +static ScrStr *insp_custom(ScrDyn *d, double recurse, double depth, bool *thrown) { + *thrown = false; + if (!g_custom_inspect) return NULL; + ScrDyn *fn; + bool compiled = d->kind == SCR_DYN_TYPED_REF; + if (compiled) { + /* A class instance: its class's compiled function, when it has a + * custom inspect method. A class whose method has no compiled call + * (the compiler refuses such classes where it renders them) refuses + * here too. */ + fn = insp_custom_class_fn(d); + if (!fn) { + ScrStr *name = scr_str_new("%symbol:Symbol(nodejs.util.inspect.custom)", 42); + bool declared = scr_dyn_typed_ref_class_has(d, name); + scr_str_release(name); + if (!declared) return NULL; + static const char message[] = + "util.inspect of this class instance's [util.inspect.custom] method has no lowering"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); + *thrown = true; + return NULL; + } + fn = scr_dyn_retain(fn); + } else { + ScrDyn *key = scr_dyn_inspect_custom_symbol(); + if (!key) return NULL; + if (d->kind != SCR_DYN_OBJ && d->kind != SCR_DYN_ARR && d->kind != SCR_DYN_FUNC) return NULL; + fn = scr_dyn_reflect_get(d, key, d); + if (!fn) { + *thrown = true; + return NULL; + } + } + if (fn->kind != SCR_DYN_FUNC || fn->v.fn.thunk == insp_inspect_call) { + scr_dyn_release(fn); + return NULL; + } + /* A constructor's prototype object renders normally. */ + ScrDyn *ctor = !compiled && d->kind == SCR_DYN_OBJ ? scr_dyn_obj_read(d, "constructor", 11) : NULL; + if (ctor && ctor->kind == SCR_DYN_FUNC) { + ScrDyn *prototype = scr_dyn_fn_get(ctor, "prototype", 9); + bool own_prototype = prototype == d; + scr_dyn_release(prototype); + if (own_prototype) { + scr_dyn_release(ctor); + scr_dyn_release(fn); + return NULL; + } + } + scr_dyn_release(ctor); + ScrDyn *args[3]; + /* ctx.depth - recurseTimes, or null for a null depth. */ + args[0] = scr_insp_custom_depth(recurse, depth); + args[1] = insp_user_options(depth); + if (!args[1]) { + scr_dyn_release(args[0]); + scr_dyn_release(fn); + *thrown = true; + return NULL; + } + args[2] = insp_inspect_value(); + ScrDyn *ret; + if (compiled) { + ScrDyn *call[4] = {d, args[0], args[1], args[2]}; + ret = scr_dyn_call(fn, call, 4, "[nodejs.util.inspect.custom]"); + } else { + scr_dyn_this_push_dyn(d); + ret = scr_dyn_call(fn, args, 3, "[nodejs.util.inspect.custom]"); + scr_dyn_this_pop(); + } + for (size_t i = 0; i < 3; i++) scr_dyn_release(args[i]); + scr_dyn_release(fn); + if (!ret) { + *thrown = true; + return NULL; + } + if (ret == d || scr_insp_custom_is_self(ret, d)) { + scr_dyn_release(ret); + return NULL; + } + ScrStr *out; + if (ret->kind == SCR_DYN_STR) out = scr_insp_custom_text(ret->v.str); + else out = scr_insp_dyn(ret, recurse, depth); + scr_dyn_release(ret); + if (scr_exc_pending()) { + scr_str_release(out); + *thrown = true; + return NULL; + } + return out; +} + +bool scr_insp_custom_enabled(void) { return g_custom_inspect; } + +/* ctx.depth - recurseTimes, or null for a null depth (NaN for undefined). */ +ScrDyn *scr_insp_custom_depth(double recurse, double depth) { + if (isfinite(depth)) return scr_dyn_new_num(depth - recurse); + if (g_depth_kind == 0) return scr_dyn_new_num(g_depth_number - recurse); + return g_depth_kind == 1 ? scr_dyn_new_null() : scr_dyn_new_num(NAN); +} + +ScrDyn *scr_insp_custom_options(double depth) { return insp_user_options(depth); } + +ScrDyn *scr_insp_inspect_function(void) { return insp_inspect_value(); } + +bool scr_insp_custom_is_self(ScrDyn *result, ScrDyn *self) { + return result->kind == SCR_DYN_TYPED_REF && self->kind == SCR_DYN_TYPED_REF && + result->v.typed_ref.ptr == self->v.typed_ref.ptr; +} + +ScrStr *scr_insp_custom_result(ScrDyn *result, double recurse, double depth) { + if (result->kind == SCR_DYN_STR) return scr_insp_custom_text(result->v.str); + return scr_insp_dyn(result, recurse, depth); +} + +/* A custom function's string result: each newline followed by the + * current indentation. +1. */ +ScrStr *scr_insp_custom_text(ScrStr *text) { + if (!memchr(text->data, '\n', text->len)) return scr_str_retain(text); + InspBuf out = {0}; + for (size_t i = 0; i < text->len; i++) { + ib_char(&out, text->data[i]); + if (text->data[i] == '\n') ib_spaces(&out, g_indent); + } + return ib_take(&out); +} + ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { if (d->kind == SCR_DYN_PROXY) return scr_insp_dyn(d->v.proxy.target, recurse, depth); + if ((d->kind == SCR_DYN_TYPED_REF ? g_ncustom_classes > 0 : scr_dyn_inspect_custom_symbol() != NULL) && + (d->kind == SCR_DYN_OBJ || d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_FUNC || + d->kind == SCR_DYN_TYPED_REF)) { + bool thrown; + ScrStr *custom = insp_custom(d, recurse, depth, &thrown); + if (custom) return custom; + if (thrown) return scr_str_new("", 0); + } switch (d->kind) { case SCR_DYN_NULL: - return scr_str_new("null", 4); + return insp_styled_lit("null", 4, INSP_NULL); case SCR_DYN_UNDEF: - return scr_str_new("undefined", 9); + return insp_styled_lit("undefined", 9, INSP_UNDEFINED); case SCR_DYN_BOOL: - return d->v.b ? scr_str_new("true", 4) : scr_str_new("false", 5); + return d->v.b ? insp_styled_lit("true", 4, INSP_BOOLEAN) : insp_styled_lit("false", 5, INSP_BOOLEAN); case SCR_DYN_BIGINT: - return scr_bigint_inspect(d->v.bigint); + return insp_styled(scr_bigint_inspect(d->v.bigint), INSP_BIGINT); case SCR_DYN_SYMBOL: - return d->v.symbol.render(d->v.symbol.value); + return insp_styled(d->v.symbol.render(d->v.symbol.value), INSP_SYMBOL); case SCR_DYN_NUM: - return scr_insp_f64(d->v.num); + return insp_styled(scr_insp_f64(d->v.num), INSP_NUMBER); case SCR_DYN_STR: return scr_insp_str(d->v.str); case SCR_DYN_ARR: { size_t len = d->v.arr.len; if (len == 0) return scr_str_new("[]", 2); - if (recurse > depth) return scr_str_new("[Array]", 7); + if (recurse > depth) return insp_styled_lit("[Array]", 7, INSP_SPECIAL); scr_insp_begin(recurse + 1); /* Node's maxArrayLength (100) counts output entries; a run of holes * is one `` entry (formatSpecialArray). */ @@ -1512,7 +2031,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { while (next < len && !presence[next]) next++; char hole[48]; int hn = snprintf(hole, sizeof hole, "<%zu empty item%s>", next - i, next - i == 1 ? "" : "s"); - ScrStr *s = scr_str_new(hole, (size_t)hn); + ScrStr *s = insp_styled_lit(hole, (size_t)hn, INSP_UNDEFINED); scr_insp_entry(s, false); scr_str_release(s); i = next; @@ -1522,6 +2041,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { ScrStr *s = scr_insp_dyn(item, recurse + 1, depth); scr_insp_entry(s, item->kind == SCR_DYN_NUM); scr_str_release(s); + if (scr_exc_pending()) break; } bool more = i < len; if (more) { @@ -1542,7 +2062,10 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { /* An Error carried as a checked-dynamic value (a caught error, an * Error stored in an `unknown` slot): the error's own rendering, * never the encoding's internal marker. */ - if (scr_dyn_obj_get(d, "%error", 6) != NULL) return scr_insp_dyn_error(d, recurse, depth); + if (scr_dyn_obj_get(d, "%error", 6) != NULL) { + if (g_colors) return insp_colors_fence("Error values"); + return scr_insp_dyn_error(d, recurse, depth); + } /* Object.create(null)'s dictionary: Node prefixes the rendering * with "[Object: null prototype]" (formatValue's constructor-less * base), the empty form included, and the beyond-depth answer IS @@ -1562,9 +2085,12 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { } else { ib_cstr(&out, "[Function (anonymous)]"); } - return ib_take(&out); + return insp_styled(ib_take(&out), INSP_SPECIAL); } case SCR_DYN_HANDLE: { + if (g_colors && d->v.handle.tag != SCR_DYNH_SET && d->v.handle.tag != SCR_DYNH_MAP && + d->v.handle.tag != SCR_DYNH_DATE) + return insp_colors_fence_values(scr_dyn_handle_cls(d)); if (d->v.handle.tag == SCR_DYNH_ARRAY_BUFFER || d->v.handle.tag == SCR_DYNH_SHARED_ARRAY_BUFFER) { return scr_insp_array_buffer(d->v.handle.ptr, scr_dyn_handle_cls(d), recurse, depth); @@ -1574,7 +2100,8 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { if (d->v.handle.tag == SCR_DYNH_WEAK_MAP || d->v.handle.tag == SCR_DYNH_WEAK_SET) return scr_insp_weak_collection(d->v.handle.tag == SCR_DYNH_WEAK_MAP ? "WeakMap" : "WeakSet", recurse, depth); - if (d->v.handle.tag == SCR_DYNH_DATE) return scr_insp_date(scr_dyn_native_date_value(d)); + if (d->v.handle.tag == SCR_DYNH_DATE) + return insp_styled(scr_insp_date(scr_dyn_native_date_value(d)), INSP_DATE); if (d->v.handle.tag == SCR_DYNH_ITERATOR) { ScrStr *iterator = scr_insp_native_iterator(d, recurse, depth); if (iterator) return iterator; @@ -1623,9 +2150,14 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { } case SCR_DYN_TYPED_REF: { + if (g_colors && scr_insp_is_generator(d)) return insp_colors_fence("generator objects"); if (scr_insp_is_generator(d)) return scr_insp_generator(d); const char *cls = NULL; ScrDyn *view = scr_event_ref_hooks ? scr_event_ref_hooks->inspect_view(d, &cls) : NULL; + if (view && g_colors) { + scr_dyn_release(view); + return insp_colors_fence_values(cls); + } if (view) { /* The handle rule above: the class name past the depth limit. */ if (recurse > depth) { @@ -1655,6 +2187,11 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { double time; ScrStr *date_class = materialized->kind == SCR_DYN_OBJ && scr_dyn_date_class_time(d, &time) ? scr_dyn_date_class_name(d) : NULL; + if (date_class && g_colors) { + scr_str_release(date_class); + scr_dyn_release(materialized); + return insp_colors_fence("Date subclass instances"); + } if (date_class) { /* An instance of a class declared `extends Date`: the class name and * the time as Date.prototype.toISOString formats it, then its own @@ -1715,6 +2252,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { } case SCR_DYN_BYTES: { ScrBytes *b = d->v.bytes; + if (b->is_data_view && g_colors) return insp_colors_fence("DataView values"); if (b->is_data_view) return scr_insp_data_view(b, recurse, depth); if (d->buffer) { /* Buffer-flavored (a stream chunk): Node's form. */ @@ -1734,6 +2272,535 @@ ScrStr *scr_insp_dyn_s(ScrDyn *d, double depth) { return scr_insp_dyn(d, 0, depth); } +/* ── util.format over checked-dynamic values ───────────────────────────── + * formatWithOptionsInternal over argument values whose types are known + * only at run time: util.format and util.formatWithOptions with a runtime + * format string or spread arguments, console output with a runtime format + * string, and debuglog's lines. The %-substitutions (%s %d %i %f %j %o %O + * %c %%) follow Node's argument cursor, then the remaining arguments join + * with spaces, strings verbatim and everything else inspected under the + * options. With colors, primitives take inspect's default styles; colored + * object renderings have no lowering. These entries call program code + * (toString, valueOf and toJSON), so unlike the rest of this file they + * can throw: NULL with the exception pending. */ +static void insp_fmt_fence(const ScrInspFormatOptions *o, const char *what) { + InspBuf b = {0}; + ib_cstr(&b, o->surface); + ib_char(&b, ' '); + ib_cstr(&b, what); + ScrStr *message = ib_take(&b); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + scr_str_release(message); +} + +static bool insp_fmt_object(const ScrDyn *value) { + switch (value->kind) { + case SCR_DYN_OBJ: + case SCR_DYN_ARR: + case SCR_DYN_FUNC: + case SCR_DYN_BYTES: + case SCR_DYN_HANDLE: + case SCR_DYN_PROMISE: + case SCR_DYN_TYPED_REF: + case SCR_DYN_PROXY: + return true; + default: + return false; + } +} + +void scr_insp_format_defaults(ScrInspFormatOptions *o, const char *surface) { + o->surface = surface; + o->depth = 2; + o->depth_kind = 0; + o->depth_number = 2; + o->colors = false; + o->custom_inspect = true; + o->break_length = 80; + o->compact = 3; + o->sorted = false; + o->user_options = NULL; +} + +/* inspect(value, options) at the given depth, compact and colors, with + * the options' breakLength and sorted; the previous layout returns + * afterwards. +1, or NULL with the exception pending. */ +static ScrStr *insp_fmt_inspect(ScrDyn *value, const ScrInspFormatOptions *o, double depth, + double compact, bool colors) { + double break_length = g_break_length, saved_compact = g_compact; + bool sorted = g_sorted, saved_colors = g_colors, saved_custom = g_custom_inspect; + int saved_depth_kind = g_depth_kind; + double saved_depth_number = g_depth_number; + const ScrDyn *saved_user = g_user_options; + scr_insp_layout(o->break_length, compact, o->sorted); + g_colors = colors; + g_custom_inspect = o->custom_inspect; + g_depth_kind = o->depth_kind; + g_depth_number = o->depth_number; + g_user_options = o->user_options; + ScrStr *text = scr_insp_dyn(value, 0, depth); + g_colors = saved_colors; + g_custom_inspect = saved_custom; + g_depth_kind = saved_depth_kind; + g_depth_number = saved_depth_number; + g_user_options = saved_user; + scr_insp_layout(break_length, saved_compact, sorted); + if (scr_exc_pending()) { + scr_str_release(text); + return NULL; + } + return text; +} + +/* hasBuiltInToString's negative answer: a toString the program defined on + * the object or its prototype chain (1). Symbol-keyed members (where + * Symbol.toPrimitive would also count) are reported as unknown (-1), and a + * class instance whose class defines toString as -2: its methods are + * compiled for the class's own calls, which this runtime cannot make. */ +static int insp_fmt_custom_to_string(const ScrDyn *value) { + if (value->kind == SCR_DYN_TYPED_REF) { + ScrStr *to_string = scr_str_new("toString", 8); + bool has = scr_dyn_typed_ref_class_has(value, to_string); + scr_str_release(to_string); + return has ? -2 : 0; + } + for (const ScrDyn *current = value; current && current->kind == SCR_DYN_OBJ; current = current->prototype) { + if (current->symbol_properties) return -1; + const ScrDyn *member = scr_dyn_obj_get(current, "toString", 8); + if (member) return member->kind == SCR_DYN_FUNC; + } + return 0; +} + +static ScrStr *insp_fmt_s(ScrDyn *value, const ScrInspFormatOptions *o) { + if (value->kind == SCR_DYN_STR) return scr_str_retain(value->v.str); + if (value->kind == SCR_DYN_NUM) return scr_insp_f64(value->v.num); + if (value->kind == SCR_DYN_BIGINT) return scr_bigint_inspect(value->v.bigint); + if (value->kind == SCR_DYN_FUNC) { + insp_fmt_fence(o, "%s of a function prints its source text, which a compiled binary does not keep"); + return NULL; + } + if ((value->kind == SCR_DYN_OBJ && scr_dyn_obj_get(value, "%error", 6) == NULL) || + value->kind == SCR_DYN_TYPED_REF) { + int custom = insp_fmt_custom_to_string(value); + if (custom == -2) { + insp_fmt_fence(o, "%s of a class instance whose class defines toString has no lowering"); + return NULL; + } + if (custom < 0) { + insp_fmt_fence(o, "%s of objects with symbol-keyed members has no lowering"); + return NULL; + } + if (custom) return scr_dyn_string_coerce_js(value); + } + if (!insp_fmt_object(value)) return scr_insp_dyn(value, 0, 0); + /* inspect(arg, { ...inspectOptions, depth: 0, colors: false, compact: 3 }) */ + return insp_fmt_inspect(value, o, 0, 3, false); +} + +static ScrStr *insp_fmt_number(ScrDyn *value, int spec) { + if (value->kind == SCR_DYN_BIGINT && spec != 'f') return scr_bigint_inspect(value->v.bigint); + if (value->kind == SCR_DYN_SYMBOL) return scr_str_new("NaN", 3); + double number; + if (spec == 'd') { + if (!scr_dyn_number_coerce_js(value, &number)) return NULL; + } else { + ScrStr *text = scr_dyn_string_coerce_js(value); + if (!text) return NULL; + number = spec == 'i' ? scr_parse_int(text, 0) : scr_parse_float(text); + scr_str_release(text); + } + return scr_insp_f64(number); +} + +/* One formatter's conversions: %s, %j, %d/%i/%f, %O/%o and the remaining + * arguments' inspect (depth -1: the options' depth). The loop references + * only the table it runs, so a program links only the conversions it uses. */ +typedef struct { + ScrStr *(*s)(ScrDyn *value, const ScrInspFormatOptions *o); + ScrStr *(*j)(const ScrDyn *value); + ScrStr *(*number)(ScrDyn *value, int spec); + ScrStr *(*inspect)(ScrDyn *value, const ScrInspFormatOptions *o, double depth); +} InspFmtOps; + +static ScrStr *insp_format_loop(ScrDyn *const *args, size_t argc, const ScrInspFormatOptions *o, + const InspFmtOps *ops) { + if (argc == 0) return scr_str_new("", 0); + InspBuf out = {0}; + size_t a = 0; + const char *join = ""; + if (args[0]->kind == SCR_DYN_STR) { + const ScrStr *first = args[0]->v.str; + if (argc == 1) return scr_str_retain((ScrStr *)first); + const char *text = first->data; + size_t last = 0; + for (size_t i = 0; first->len > 0 && i < first->len - 1; i++) { + if (text[i] != '%') continue; + int next = (unsigned char)text[++i]; + if (a + 1 != argc) { + ScrStr *piece = NULL; + switch (next) { + case 's': piece = ops->s(args[++a], o); break; + case 'j': piece = ops->j(args[++a]); break; + case 'd': + case 'i': + case 'f': piece = ops->number(args[++a], next); break; + case 'O': piece = ops->inspect(args[++a], o, o->depth); break; + case 'o': + if (insp_fmt_object(args[a + 1])) { + insp_fmt_fence(o, "%o of objects renders hidden properties, which have no lowering; use %O"); + break; + } + piece = ops->inspect(args[++a], o, 4); + break; + case 'c': + a += 1; + piece = scr_str_new("", 0); + break; + case '%': + ib_bytes(&out, text + last, i - last); + last = i + 1; + continue; + default: + continue; + } + if (!piece) { + free(out.data); + return NULL; + } + if (last != i - 1) ib_bytes(&out, text + last, i - 1 - last); + ib_str(&out, piece); + scr_str_release(piece); + last = i + 1; + } else if (next == '%') { + ib_bytes(&out, text + last, i - last); + last = i + 1; + } + } + if (last != 0) { + a++; + join = " "; + if (last < first->len) ib_bytes(&out, text + last, first->len - last); + } + } + for (; a < argc; a++) { + ib_cstr(&out, join); + if (args[a]->kind == SCR_DYN_STR) { + ib_str(&out, args[a]->v.str); + } else { + ScrStr *piece = ops->inspect(args[a], o, o->depth); + if (!piece) { + free(out.data); + return NULL; + } + ib_str(&out, piece); + scr_str_release(piece); + } + join = " "; + } + return ib_take(&out); +} + + +static ScrStr *insp_fmt_inspect_full(ScrDyn *value, const ScrInspFormatOptions *o, double depth) { + 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}; + +ScrStr *scr_insp_format(ScrDyn *const *args, size_t argc, const ScrInspFormatOptions *o) { + return insp_format_loop(args, argc, o, &insp_fmt_full_ops); +} + +/* The formatter over arguments that are all strings, numbers, booleans, + * null or undefined (the compiler proves it from their types): the same + * rules for those kinds, without the checked-dynamic walk the other + * values need. */ +static ScrStr *insp_prim_to_string(const ScrDyn *v) { + switch (v->kind) { + case SCR_DYN_STR: return scr_str_retain(v->v.str); + case SCR_DYN_NUM: { + char buf[40]; + size_t n = scr_f64_to_str(v->v.num, buf); + return scr_str_new(buf, n); + } + case SCR_DYN_BOOL: return v->v.b ? scr_str_new("true", 4) : scr_str_new("false", 5); + case SCR_DYN_NULL: return scr_str_new("null", 4); + default: return scr_str_new("undefined", 9); + } +} + +static ScrStr *insp_prim_inspect(ScrDyn *v, const ScrInspFormatOptions *o, double depth) { + (void)o; + (void)depth; + if (v->kind == SCR_DYN_STR) return scr_insp_str(v->v.str); + if (v->kind == SCR_DYN_NUM) return scr_insp_f64(v->v.num); + return insp_prim_to_string(v); +} + +static ScrStr *insp_prim_s(ScrDyn *v, const ScrInspFormatOptions *o) { + (void)o; + if (v->kind == SCR_DYN_NUM) return scr_insp_f64(v->v.num); + return insp_prim_to_string(v); +} + +static ScrStr *insp_prim_j(const ScrDyn *v) { + if (v->kind == SCR_DYN_STR) { + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_put_json_str(&b, v->v.str); + return scr_jb_finish(&b); + } + if (v->kind == SCR_DYN_NUM && !isfinite(v->v.num)) return scr_str_new("null", 4); + return insp_prim_to_string(v); +} + +static ScrStr *insp_prim_number(ScrDyn *v, int spec) { + double number; + if (spec == 'd') { + number = v->kind == SCR_DYN_NUM ? v->v.num + : v->kind == SCR_DYN_STR ? scr_string_to_number(v->v.str) + : v->kind == SCR_DYN_BOOL ? (v->v.b ? 1 : 0) + : v->kind == SCR_DYN_NULL ? 0 + : NAN; + } else { + ScrStr *text = insp_prim_to_string(v); + number = spec == 'i' ? scr_parse_int(text, 0) : scr_parse_float(text); + scr_str_release(text); + } + return scr_insp_f64(number); +} + +static const InspFmtOps insp_fmt_primitive_ops = {insp_prim_s, insp_prim_j, insp_prim_number, + insp_prim_inspect}; + +ScrStr *scr_insp_format_primitives(ScrDyn *pack) { + ScrInspFormatOptions o; + scr_insp_format_defaults(&o, "util.format"); + if (pack->kind != SCR_DYN_ARR) return scr_str_new("", 0); + return insp_format_loop(pack->v.arr.items, pack->v.arr.len, &o, &insp_fmt_primitive_ops); +} + +/* The arguments of a dyn array pack (+1 text, or NULL pending). */ +static ScrStr *insp_fmt_pack(ScrDyn *pack, const ScrInspFormatOptions *o) { + if (pack->kind != SCR_DYN_ARR) return scr_insp_format(NULL, 0, o); + return scr_insp_format(pack->v.arr.items, pack->v.arr.len, o); +} + +ScrStr *scr_insp_format_pack(ScrDyn *pack) { + ScrInspFormatOptions o; + scr_insp_format_defaults(&o, "util.format"); + return insp_fmt_pack(pack, &o); +} + +/* Whether a format string can substitute: a '%' before its last character. + * A string without one formats as its arguments joined with spaces. */ +bool scr_insp_format_specifies(ScrStr *first) { + return first->len > 1 && memchr(first->data, '%', first->len - 1) != NULL; +} + +bool scr_insp_format_specifies_dyn(ScrDyn *first) { + return first->kind == SCR_DYN_STR && scr_insp_format_specifies(first->v.str); +} + +/* One own enumerable option of an inspect options object: +1 value, a + * borrowed undefined when absent, or NULL with the exception pending. */ +static ScrDyn *insp_opt(const ScrDyn *options, const char *key, bool *present) { + size_t len = strlen(key); + *present = options->kind == SCR_DYN_OBJ && scr_dyn_obj_get(options, key, len) != NULL && + scr_dyn_obj_enumerable(options, key, len); + if (!*present) return scr_dyn_retain(scr_dyn_undefined()); + return scr_dyn_obj_read(options, key, len); +} + +/* ctx.depth from an option or inspect's third argument: a number, null + * or undefined (no limit for null, undefined and NaN). */ +static bool insp_depth_option(const ScrDyn *value, ScrInspFormatOptions *o) { + if (value->kind == SCR_DYN_NUM) { + o->depth = isnan(value->v.num) ? INFINITY : value->v.num; + o->depth_kind = 0; + o->depth_number = value->v.num; + } else if (value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF) { + o->depth = INFINITY; + o->depth_kind = value->kind == SCR_DYN_NULL ? 1 : 2; + } else { + return false; + } + return true; +} + +/* inspect's option keys (inspectDefaultOptions and stylize) over an + * options object, as `inspect(value, options)` copies them. Options at + * their defaults, depth, colors, compact (a number or false), + * breakLength (a number) and sorted (a boolean) lower; other values of + * the known options throw the SC2020 refusal. */ +bool scr_insp_format_read_options(const ScrDyn *options, ScrInspFormatOptions *o) { + ScrDyn *view = NULL; + if (options->kind == SCR_DYN_TYPED_REF) { + view = scr_dyn_typed_ref_materialize(options); + if (scr_exc_pending()) { + scr_dyn_release(view); + return false; + } + options = view; + } + bool ok = false, present; + ScrDyn *value = NULL; +#define INSP_OPT(key) \ + do { \ + scr_dyn_release(value); \ + value = insp_opt(options, key, &present); \ + if (!value) goto done; \ + } while (0) + INSP_OPT("depth"); + if (present) { + if (!insp_depth_option(value, o)) { + insp_fmt_fence(o, "with a depth option that is not a number or null has no lowering"); + goto done; + } + } + INSP_OPT("colors"); + if (present) o->colors = scr_dyn_truthy(value); + INSP_OPT("customInspect"); + if (present) o->custom_inspect = scr_dyn_truthy(value); + INSP_OPT("compact"); + if (present) { + if (value->kind == SCR_DYN_NUM) o->compact = value->v.num < 0 ? 0 : value->v.num; + else if (value->kind == SCR_DYN_BOOL && !value->v.b) o->compact = -1; + else { + insp_fmt_fence(o, "with this compact option has no lowering (compact must be false or a number)"); + goto done; + } + } + INSP_OPT("breakLength"); + if (present) { + if (value->kind != SCR_DYN_NUM) { + insp_fmt_fence(o, "with a breakLength option that is not a number has no lowering"); + goto done; + } + o->break_length = value->v.num; + } + INSP_OPT("sorted"); + if (present) { + if (value->kind != SCR_DYN_BOOL) { + insp_fmt_fence(o, "with a sorted option that is not a boolean has no lowering"); + goto done; + } + o->sorted = value->v.b; + } + /* The options whose non-default values change renderings this runtime + * does not produce. */ + static const struct { + const char *key; + int kind; /* 0: falsy, 1: true, 2: the number 100, 3: the number 10000 */ + } fixed[] = { + {"showHidden", 0}, {"showProxy", 0}, {"maxArrayLength", 2}, {"maxStringLength", 3}, + {"getters", 0}, {"numericSeparator", 0}, {"stylize", 0}, + }; + for (size_t i = 0; i < sizeof fixed / sizeof *fixed; i++) { + INSP_OPT(fixed[i].key); + if (!present) continue; + bool at_default = fixed[i].kind == 0 ? !scr_dyn_truthy(value) + : fixed[i].kind == 1 ? value->kind == SCR_DYN_BOOL && value->v.b + : value->kind == SCR_DYN_NUM && value->v.num == (fixed[i].kind == 2 ? 100 : 10000); + /* The stylize function a custom inspect function received (options + * passed back into util.inspect) is the default for its colors. */ + if (!at_default && strcmp(fixed[i].key, "stylize") == 0 && insp_own_stylize(value, o->colors)) + at_default = true; + /* stylize is replaced by the color stylizer when colors is set. */ + if (!at_default && !(o->colors && strcmp(fixed[i].key, "stylize") == 0)) { + InspBuf b = {0}; + ib_cstr(&b, "with the "); + ib_cstr(&b, fixed[i].key); + ib_cstr(&b, " option has no lowering"); + ScrStr *what = ib_take(&b); + insp_fmt_fence(o, what->data); + scr_str_release(what); + goto done; + } + } +#undef INSP_OPT + /* Keys inspect does not read: the custom functions receive them. */ + if (!view && options->kind == SCR_DYN_OBJ) { + static const char *const known[] = { + "showHidden", "depth", "colors", "customInspect", "showProxy", + "maxArrayLength", "maxStringLength", "breakLength", "compact", "sorted", + "getters", "numericSeparator", "stylize", + }; + for (size_t i = 0; i < options->v.obj.len && !o->user_options; i++) { + const ScrDynEntry *ent = &options->v.obj.entries[i]; + if (!ent->enumerable) continue; + bool read = false; + for (size_t k = 0; k < sizeof known / sizeof *known && !read; k++) + read = ent->key_len == strlen(known[k]) && memcmp(ent->key, known[k], ent->key_len) == 0; + if (!read) o->user_options = options; + } + } + ok = true; +done: + scr_dyn_release(value); + scr_dyn_release(view); + return ok; +} + +/* util.inspect(value[, options]) and inspect(value, showHidden, depth, + * colors) over checked-dynamic arguments; `argc` is the call's argument + * count. A third argument other than undefined sets the depth and a + * fourth the colors; a boolean second argument is showHidden, and an + * object supplies the options, which apply after the legacy arguments. + * +1, or NULL with the exception pending. */ +ScrStr *scr_insp_inspect(ScrDyn *value, ScrDyn *options, ScrDyn *depth, ScrDyn *colors, + double argc) { + ScrInspFormatOptions o; + scr_insp_format_defaults(&o, "util.inspect"); + if (argc >= 3 && depth->kind != SCR_DYN_UNDEF) { + if (!insp_depth_option(depth, &o)) { + insp_fmt_fence(&o, "with a depth argument that is not a number or null has no lowering"); + return NULL; + } + } + if (argc >= 4 && colors->kind != SCR_DYN_UNDEF) o.colors = scr_dyn_truthy(colors); + if (options->kind == SCR_DYN_BOOL) { + if (options->v.b) { + insp_fmt_fence(&o, "with showHidden has no lowering"); + return NULL; + } + } else if (options->kind == SCR_DYN_OBJ || options->kind == SCR_DYN_TYPED_REF) { + if (!scr_insp_format_read_options(options, &o)) return NULL; + } + return insp_fmt_inspect(value, &o, o.depth, o.compact, o.colors); +} + +static ScrDyn *insp_inspect_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *undefined = scr_dyn_undefined(); + scr_insp_nested_begin(); + ScrStr *text = scr_insp_inspect(argc > 0 ? args[0] : undefined, argc > 1 ? args[1] : undefined, + argc > 2 ? args[2] : undefined, argc > 3 ? args[3] : undefined, + (double)argc); + scr_insp_nested_restore(); + if (!text) return NULL; + ScrDyn *out = scr_dyn_new_str(text); + scr_str_release(text); + return out; +} + +/* util.formatWithOptions(inspectOptions, ...pack): validateObject (arrays + * pass, null and functions do not), then the options as inspect reads + * them. */ +ScrStr *scr_insp_format_with_options(ScrDyn *options, ScrDyn *pack) { + if (options->kind != SCR_DYN_OBJ && options->kind != SCR_DYN_ARR && + options->kind != SCR_DYN_TYPED_REF) { + scr_dyn_arg_type_fail("inspectOptions", "of type object", options); + return NULL; + } + ScrInspFormatOptions o; + scr_insp_format_defaults(&o, "util.formatWithOptions"); + if (!scr_insp_format_read_options(options, &o)) return NULL; + return insp_fmt_pack(pack, &o); +} + /* ── console.table cells ─────────────────────────────────────────────── */ size_t scr_insp_display_width(const char *data, size_t len) { return insp_width(data, len); } diff --git a/packages/runtime/src/scr_inspect.h b/packages/runtime/src/scr_inspect.h index 5ac3aa9dd..a95ac1fde 100644 --- a/packages/runtime/src/scr_inspect.h +++ b/packages/runtime/src/scr_inspect.h @@ -47,8 +47,50 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth); ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth); /* format's %s / rest-args twin: dyn strings pass verbatim. */ ScrStr *scr_insp_dyn_s(ScrDyn *d, double depth); +/* util.format over checked-dynamic arguments (formatWithOptionsInternal): + * the options an inspect of each argument takes, and the API name the + * SC2020 refusals name. These entries call program code, so they answer + * NULL with the exception pending when it throws. */ +typedef struct ScrInspFormatOptions { + const char *surface; + double depth; /* Infinity for null, undefined and NaN */ + /* The depth as the options gave it, which custom inspect functions + * receive when the depth is unbounded: 0 a number (depth_number, + * Infinity or NaN), 1 null, 2 undefined. */ + int depth_kind; + double depth_number; + bool colors; + bool custom_inspect; + double break_length; + double compact; /* -1 for false */ + bool sorted; + /* The options object, when it holds keys inspect does not read: custom + * inspect functions receive them (borrowed; NULL otherwise). */ + const ScrDyn *user_options; +} ScrInspFormatOptions; +void scr_insp_format_defaults(ScrInspFormatOptions *o, const char *surface); +/* Reads inspect's options from an options object into o; false with the + * SC2020 refusal pending for values that have no lowering. */ +bool scr_insp_format_read_options(const ScrDyn *options, ScrInspFormatOptions *o); +ScrStr *scr_insp_format(ScrDyn *const *args, size_t argc, const ScrInspFormatOptions *o); +/* util.format(...pack) and util.formatWithOptions(options, ...pack) over a + * dyn array of the arguments (+1). */ +ScrStr *scr_insp_format_pack(ScrDyn *pack); +/* util.format(...pack) when every argument is a string, number, boolean, + * null or undefined, without the checked-dynamic walk (+1). */ +ScrStr *scr_insp_format_primitives(ScrDyn *pack); +ScrStr *scr_insp_format_with_options(ScrDyn *options, ScrDyn *pack); +/* util.inspect(value, options, depth, colors) over checked-dynamic + * arguments with Node's legacy-argument rules; argc is the call's + * argument count (+1, or NULL with the exception pending). */ +ScrStr *scr_insp_inspect(ScrDyn *value, ScrDyn *options, ScrDyn *depth, ScrDyn *colors, + double argc); +/* Whether a format string has a '%' before its last character, the only + * strings whose substitutions can change the output. */ +bool scr_insp_format_specifies(ScrStr *first); +bool scr_insp_format_specifies_dyn(ScrDyn *first); void scr_insp_begin(double recurse); -/* util.inspect's breakLength, compact (0 for false) and sorted for the +/* util.inspect's breakLength, compact (-1 for false) and sorted for the * renders that follow; the call site restores the defaults (80, 3, false) * afterwards. */ void scr_insp_layout(double break_length, double compact, bool sorted); @@ -75,6 +117,33 @@ ScrStr *scr_insp_end(ScrStr *base, ScrStr *b0, ScrStr *b1, double recurse, * caller's level (+1; borrows both). */ void scr_insp_nested_begin(void); ScrStr *scr_insp_nested_end(ScrStr *prefix, ScrStr *inner); +/* nested_end's restore without its text: the caller's context returns. */ +void scr_insp_nested_restore(void); +/* ctx.customInspect for the renders that follow; answers the previous + * value for the caller to restore. */ +bool scr_insp_custom_inspect(bool enabled); +/* How the unbounded depth of the renders that follow was given, for custom + * inspect functions: 0 Infinity, 1 null, 2 undefined. Answers the + * previous kind for the caller to restore. */ +double scr_insp_depth_kind(double kind); +/* A custom inspect function's string result re-indented at the current + * level (+1; borrows text). */ +ScrStr *scr_insp_custom_text(ScrStr *text); +/* The compiled [util.inspect.custom] calls: ctx.customInspect, the depth + * argument at recursion `recurse` of `depth` and the options object (+1 + * dyn values; options NULL with an exception pending), util.inspect as a + * function value (+1), whether a dyn result is the instance itself, and a + * dyn result's rendering at the instance's level (+1). */ +bool scr_insp_custom_enabled(void); +/* Registers a class's compiled custom inspect function under the type key + * of its instances' typed refs: fn(self, depth, options, inspect) answers + * the method's result (borrows both). */ +void scr_insp_register_custom(ScrStr *type_key, ScrDyn *fn); +ScrDyn *scr_insp_custom_depth(double recurse, double depth); +ScrDyn *scr_insp_custom_options(double depth); +ScrDyn *scr_insp_inspect_function(void); +bool scr_insp_custom_is_self(ScrDyn *result, ScrDyn *self); +ScrStr *scr_insp_custom_result(ScrDyn *result, double recurse, double depth); /* The buf.read* / buf.write* numeric families on u8 bytes, Node-exact: * boundsError's ladder (non-integer offset → 'It must be an integer', a diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index dfa81ace6..3a4e6190d 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -172,6 +172,24 @@ bool scr_dyn_is_async_dispose_symbol(const ScrDyn *key) { key->v.symbol.value == scr_async_dispose_symbol->v.symbol.value; } +/* Symbol.for('nodejs.util.inspect.custom'), installed by the symbol unit + * when the program creates it: the key util.inspect reads custom inspect + * functions under. */ +static SCR_TL ScrDyn *scr_inspect_custom_symbol; + +static void scr_inspect_custom_symbol_cleanup(void) { + scr_dyn_release(scr_inspect_custom_symbol); + scr_inspect_custom_symbol = NULL; +} + +void scr_dyn_install_inspect_custom_symbol(ScrDyn *key) { + if (scr_inspect_custom_symbol) return; + scr_inspect_custom_symbol = scr_dyn_retain(key); + scr_atexit(scr_inspect_custom_symbol_cleanup); +} + +ScrDyn *scr_dyn_inspect_custom_symbol(void) { return scr_inspect_custom_symbol; } + static void scr_to_primitive_symbol_cleanup(void) { scr_dyn_release(scr_to_primitive_symbol); scr_to_primitive_symbol = NULL; diff --git a/packages/runtime/src/scr_symbol.c b/packages/runtime/src/scr_symbol.c index 304f2544f..e49edb370 100644 --- a/packages/runtime/src/scr_symbol.c +++ b/packages/runtime/src/scr_symbol.c @@ -125,6 +125,11 @@ ScrSym *scr_sym_for(ScrStr *key) { s->reg_key = scr_str_retain(key); s->reg_next = g_sym_registry; g_sym_registry = scr_sym_retain(s); + if (key->len == 26 && !memcmp(key->data, "nodejs.util.inspect.custom", 26)) { + ScrDyn *custom = scr_dyn_new_symbol(s); + scr_dyn_install_inspect_custom_symbol(custom); + scr_dyn_release(custom); + } return s; } diff --git a/packages/runtime/src/scr_util_callable.c b/packages/runtime/src/scr_util_callable.c index c905ea8ae..e3efd0be7 100644 --- a/packages/runtime/src/scr_util_callable.c +++ b/packages/runtime/src/scr_util_callable.c @@ -9,8 +9,8 @@ * and reported on the next tick (scr_warning_emit_deferred), which also * applies process.noDeprecation and process.throwDeprecation. debuglog * matches sections against NODE_DEBUG as the process started and writes - * Node's "SECTION PID: message" lines to stderr. All arguments are - * borrowed. */ + * Node's "SECTION PID: message" lines, formatted by scr_insp_format, to + * stderr. All arguments are borrowed. */ #include "scr_runtime.h" #include @@ -513,163 +513,6 @@ ScrDyn *scr_util_callbackify(ScrDyn *original) { return callbackified; } -/* ── util.format over dyn arguments ──────────────────────────────────── - * formatWithOptionsInternal for debuglog's lines: the %-substitutions - * (%s %d %i %f %j %o %O %c %%) with Node's argument cursor, then the - * remaining arguments space-joined, strings verbatim and everything else - * inspected. With terminal colors, primitives take inspect's default - * styles; colored object renderings have no lowering. */ -static bool uc_primitive(const ScrDyn *value) { - return !uc_object(value); -} - -static ScrStr *uc_inspect(ScrDyn *value, double depth, bool colors) { - if (!colors) return scr_insp_dyn(value, 0, depth); - if (!uc_primitive(value)) { - uc_fence("util.debuglog output of objects on a color terminal has no lowering"); - return NULL; - } - const char *open = "\x1b[33m", *close = "\x1b[39m"; - if (value->kind == SCR_DYN_UNDEF) open = "\x1b[90m"; - else if (value->kind == SCR_DYN_NULL) open = "\x1b[1m", close = "\x1b[22m"; - else if (value->kind == SCR_DYN_STR || value->kind == SCR_DYN_SYMBOL) open = "\x1b[32m"; - ScrStr *text = scr_insp_dyn(value, 0, depth); - ScrJsonBuf b; - scr_jb_init(&b); - scr_jb_puts(&b, open); - scr_jb_put_str(&b, text); - scr_jb_puts(&b, close); - scr_str_release(text); - return scr_jb_finish(&b); -} - -/* hasBuiltInToString's negative answer for checked objects: an own or - * inherited toString the program defined. Symbol-keyed members (where - * Symbol.toPrimitive would also count) are reported as unknown. */ -static int uc_custom_to_string(const ScrDyn *value) { - for (const ScrDyn *current = value; current && current->kind == SCR_DYN_OBJ; current = current->prototype) { - if (current->symbol_properties) return -1; - const ScrDyn *member = scr_dyn_obj_get(current, "toString", 8); - if (member) return member->kind == SCR_DYN_FUNC; - } - return 0; -} - -static ScrStr *uc_format_s(ScrDyn *value) { - if (value->kind == SCR_DYN_STR) return scr_str_retain(value->v.str); - if (value->kind == SCR_DYN_NUM) return scr_insp_f64(value->v.num); - if (value->kind == SCR_DYN_FUNC) { - uc_fence("util.debuglog %s of a function prints its source text, which a compiled binary does not keep"); - return NULL; - } - if (value->kind == SCR_DYN_OBJ && scr_dyn_obj_get(value, "%error", 6) == NULL) { - int custom = uc_custom_to_string(value); - if (custom < 0) { - uc_fence("util.debuglog %s of objects with symbol-keyed members has no lowering"); - return NULL; - } - if (custom) return scr_dyn_string_coerce_js(value); - } - return scr_insp_dyn_s(value, 0); -} - -static ScrStr *uc_format_number(ScrDyn *value, int spec) { - if (value->kind == SCR_DYN_BIGINT && spec != 'f') return scr_bigint_inspect(value->v.bigint); - if (value->kind == SCR_DYN_SYMBOL) return scr_str_new("NaN", 3); - double number; - if (spec == 'd') { - if (value->kind == SCR_DYN_BIGINT) return scr_bigint_inspect(value->v.bigint); - if (!scr_dyn_number_coerce_js(value, &number)) return NULL; - } else { - ScrStr *text = scr_dyn_string_coerce_js(value); - if (!text) return NULL; - number = spec == 'i' ? scr_parse_int(text, 0) : scr_parse_float(text); - scr_str_release(text); - } - return scr_insp_f64(number); -} - -static ScrStr *uc_format(ScrDyn *const *args, size_t argc, bool colors) { - if (argc == 0) return scr_str_new("", 0); - ScrJsonBuf out; - scr_jb_init(&out); - size_t a = 0; - const char *join = ""; - if (args[0]->kind == SCR_DYN_STR) { - const ScrStr *first = args[0]->v.str; - if (argc == 1) { - scr_str_release(scr_jb_finish(&out)); - return scr_str_retain((ScrStr *)first); - } - const char *text = first->data; - size_t last = 0; - for (size_t i = 0; first->len > 0 && i < first->len - 1; i++) { - if (text[i] != '%') continue; - int next = (unsigned char)text[++i]; - if (a + 1 != argc) { - ScrStr *piece = NULL; - switch (next) { - case 's': piece = uc_format_s(args[++a]); break; - case 'j': piece = scr_util_format_j(args[++a]); break; - case 'd': - case 'i': - case 'f': piece = uc_format_number(args[++a], next); break; - case 'O': piece = uc_inspect(args[++a], 2, colors); break; - case 'o': - if (!uc_primitive(args[a + 1])) { - uc_fence("util.debuglog %o of objects renders hidden properties, which have no lowering; use %O"); - break; - } - piece = uc_inspect(args[++a], 4, colors); - break; - case 'c': - a += 1; - piece = scr_str_new("", 0); - break; - case '%': - scr_jb_write(&out, text + last, i - last); - last = i + 1; - continue; - default: - continue; - } - if (!piece) { - scr_str_release(scr_jb_finish(&out)); - return NULL; - } - if (last != i - 1) scr_jb_write(&out, text + last, i - 1 - last); - scr_jb_put_str(&out, piece); - scr_str_release(piece); - last = i + 1; - } else if (next == '%') { - scr_jb_write(&out, text + last, i - last); - last = i + 1; - } - } - if (last != 0) { - a++; - join = " "; - if (last < first->len) scr_jb_write(&out, text + last, first->len - last); - } - } - for (; a < argc; a++) { - scr_jb_puts(&out, join); - if (args[a]->kind == SCR_DYN_STR) { - scr_jb_put_str(&out, args[a]->v.str); - } else { - ScrStr *piece = uc_inspect(args[a], 2, colors); - if (!piece) { - scr_str_release(scr_jb_finish(&out)); - return NULL; - } - scr_jb_put_str(&out, piece); - scr_str_release(piece); - } - join = " "; - } - return scr_jb_finish(&out); -} - /* ── util.debuglog ───────────────────────────────────────────────────── * The logger defers its NODE_DEBUG test to its first call or `enabled` * read. The first call also selects the section's shared implementation @@ -735,7 +578,10 @@ static bool uc_debug_test(const ScrStr *section) { static ScrDyn *uc_debug_write(ScrClosure *closure, ScrDyn *const *args, size_t argc) { bool colors = scr_getenv("FORCE_COLOR") ? scr_util_terminal_colors() : scr_process_is_tty(2) && scr_util_terminal_colors(); - ScrStr *message = uc_format(args, argc, colors); + ScrInspFormatOptions options; + scr_insp_format_defaults(&options, "util.debuglog"); + options.colors = colors; + ScrStr *message = scr_insp_format(args, argc, &options); if (!message) return NULL; ScrDyn *set = uc_cap(closure, 0); char pid[64]; diff --git a/tests/corpus/7520-util-format-runtime.ts b/tests/corpus/7520-util-format-runtime.ts new file mode 100644 index 000000000..d4a7f9ea6 --- /dev/null +++ b/tests/corpus/7520-util-format-runtime.ts @@ -0,0 +1,110 @@ +// util.format, util.formatWithOptions and console output with format +// strings known only at run time, spread arguments and options objects: +// every substitution, the argument cursor, %% and %c, missing and extra +// arguments, the space-joined form of strings without a '%', and Node's +// inspectOptions validation. +import * as util from "node:util"; + +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} + +interface Row { + id: number; + tags: string[]; + nested: { deep: { deeper: { deepest: number } } }; +} + +const formats: string[] = [ + "%s and %s", + "%d|%i|%f", + "%j", + "%O / %o", + "100%% of %s", + "%c%s%c", + "no specifiers", + "trailing %", + "%x %s", + "%s", +]; +const row: Row = { id: 7, tags: ["a", "b"], nested: { deep: { deeper: { deepest: 1 } } } }; + +for (const format of formats) { + console.log(util.format(format, "first", 42, row)); + console.log(util.format(format, 3.5, "12.75")); + console.log(util.format(format)); +} + +function pick(i: number): string { + return ["%s=%d", "%j", "%O", "plain"][i % 4]!; +} +for (let i = 0; i < 4; i++) { + console.log(util.format(pick(i), new Point(1, 2), -0, [1, 2, 3])); + console.log(pick(i), new Map([["k", new Point(3, 4)]]), new Set([1n, 2n])); + console.error(pick(i), "to stderr", null, undefined); +} + +const label: string = process.argv.length > 99 ? "" : "count: %d of %s"; +console.log(label, 5, "five", "extra"); +console.log(util.format(label, 10n, Symbol("sym"))); +console.log(util.format(label, new Point(0, 0), [new Point(5, 6)])); +console.log(util.format(pick(0), { toString: "not a function" }, true)); + +// Arguments that are all strings, numbers, booleans, null or undefined. +const prim: string = process.argv.length > 99 ? "" : "%s|%d|%i|%f|%j|%O|%o|%c|%%"; +console.log(prim, -0, "12.5", -0, true, 'q"uote', "s", 3, "css", "rest", null, undefined); +console.log(prim, NaN, null, undefined, "0x10", Infinity, NaN, false); +console.log(util.format(prim, "a\nb", " 7 ", "-3.9e2", "", -Infinity, 'it\'s', 1e21)); +console.log(util.format(label, 2.5)); + +// A union first argument formats only when it holds a string. +const maybe: string | number = process.argv.length > 99 ? 1 : "%s/%s"; +const notString: string | number = process.argv.length > 99 ? "%s" : 9; +console.log(util.format(maybe, "u", "v")); +console.log(util.format(notString, "u", "v")); +console.log(maybe, "w", "x"); + +// An unknown first argument. +const loose: unknown = process.argv.length > 99 ? 0 : "%s-%i"; +console.log(util.format(loose, "a", "7.9")); +console.log(loose, "b", 3.2); + +// Spread arguments. +const values: unknown[] = ["%s + %s = %d", 1, 2, 3, "tail"]; +console.log(util.format(...values)); +console.log(util.format("%s|%s", ...values)); +console.log(...values); +console.log("prefix", ...new Set(["x", "y"]), "suffix"); +console.error(...[1, "two", [3]]); +console.log(util.format(...([] as string[]))); + +// formatWithOptions with an options object. +const deep = { a: { b: { c: { d: { e: 1 } } } } }; +const depth = process.argv.length > 99 ? 1 : 0; +console.log(util.formatWithOptions({ depth }, "%O", deep)); +console.log(util.formatWithOptions({ depth: null }, deep)); +console.log(util.formatWithOptions({ depth: undefined }, "x", deep)); +console.log(util.formatWithOptions({ depth: 0 }, "%s|%O", deep, deep)); +console.log(util.formatWithOptions({ compact: false }, "%s", "v", { a: 1, b: [1, 2] })); +console.log(util.formatWithOptions({ breakLength: 20 }, { alpha: 1, beta: 2, gamma: 3 })); +console.log(util.formatWithOptions({ sorted: true }, "%O", { b: 1, a: 2, c: { z: 1, y: 2 } })); +console.log(util.formatWithOptions({ colors: true }, "%s %d %O", "s", 2, 3), "|"); +console.log(util.formatWithOptions({ colors: true }, 1, "plain", null, undefined, true, 4n)); +const options: util.InspectOptions = { depth: 1, compact: 1 }; +console.log(util.formatWithOptions(options, "%O", deep)); +console.log(util.formatWithOptions(options, ...values)); +console.log(util.formatWithOptions([] as unknown as util.InspectOptions, "%s", 1)); +const invalid: unknown[] = [null, 1, "x", undefined, () => {}]; +for (const bad of invalid) { + try { + util.formatWithOptions(bad as util.InspectOptions, "a"); + } catch (error) { + const e = error as NodeJS.ErrnoException; + console.log(e.name, e.code, e.message); + } +} diff --git a/tests/corpus/7521-util-format-runtime.cjs b/tests/corpus/7521-util-format-runtime.cjs new file mode 100644 index 000000000..559d09243 --- /dev/null +++ b/tests/corpus/7521-util-format-runtime.cjs @@ -0,0 +1,64 @@ +// util.format, util.formatWithOptions and console output over untyped +// values: format strings held in variables and parameters, spread +// arguments, %s over objects with and without their own toString, %d and +// %i coercions, %j over toJSON, and options objects built at run time. +"use strict"; +const util = require("util"); + +function log(message, ...rest) { + console.log(message, ...rest); +} + +function formatAll(format, values) { + return util.format(format, ...values); +} + +const messages = ["%s: %d items", "%s", "%i%%", "%j", "plain text", 42]; +for (const message of messages) { + log(message, "cart", "3.7", { total: 9 }); + console.log(formatAll(message, ["a", 1n])); +} + +const withToString = { + name: "custom", + toString() { + return `<${this.name}>`; + }, +}; +const inherited = Object.create(withToString); +inherited.name = "inherited"; +const fmt = "%s / %s / %s"; +console.log(util.format(fmt, withToString, inherited, [1, [2, [3]]])); +console.log(fmt, new Date(0), new Error("boom").message, null, Symbol("s")); + +const numeric = "%d %i %f"; +console.log(util.format(numeric, "0x1f", "12.9px", "1e3")); +console.log(util.format(numeric, { valueOf: () => 5 }, true, [])); +console.log(util.format(numeric, 7n, 8n, 9)); + +const json = "%j|%j|%j"; +const circular = { name: "loop" }; +circular.self = circular; +console.log(util.format(json, { toJSON: () => ({ replaced: 1 }) }, circular, undefined)); + +const options = {}; +options.depth = 0; +options.breakLength = 40; +console.log(util.formatWithOptions(options, "%O", { a: { b: 1 } }, ["x", { y: { z: 2 } }])); +options.depth = null; +options.unknownOption = true; +console.log(util.formatWithOptions(options, { a: { b: { c: { d: 1 } } } })); +console.log(util.formatWithOptions({ compact: 1 }, ...[{ k: [1, 2] }, "end"])); +for (const bad of [null, 0, "options", () => {}]) { + try { + util.formatWithOptions(bad, "x"); + } catch (error) { + console.log(error.name, error.code, error.message); + } +} + +const args = ["%s=%s", "key"]; +args.push("value", "rest"); +console.log(...args); +console.warn(...args); +console.log(util.format(...args.slice(1))); diff --git a/tests/corpus/7522-promisify-untyped.cjs b/tests/corpus/7522-promisify-untyped.cjs new file mode 100644 index 000000000..2e1476eac --- /dev/null +++ b/tests/corpus/7522-promisify-untyped.cjs @@ -0,0 +1,64 @@ +// util.promisify over JavaScript functions whose types are not known: +// arrows, function expressions, methods and functions chosen at run time. +// The callback's error rejects and its first value fulfills, a +// synchronous throw rejects, the call's receiver reaches the function, +// the wrapper keeps the original's name and length, and arguments that +// are not functions throw Node's ERR_INVALID_ARG_TYPE. +"use strict"; +const util = require("util"); + +function pick(kind) { + if (kind === "double") return (value, callback) => setImmediate(() => callback(null, value * 2)); + if (kind === "fail") return function failing(callback) { callback(new RangeError("failed")); }; + if (kind === "throw") return () => { throw new TypeError("thrown"); }; + return null; +} + +const store = { + prefix: "item", + read(key, callback) { + setTimeout(() => callback(null, `${this.prefix}:${key}`, "ignored"), 1); + }, +}; + +async function main() { + const double = util.promisify(pick("double")); + console.log("double:", await double(21), double.name, double.length); + try { + await util.promisify(pick("fail"))(); + } catch (error) { + console.log("rejected:", error.name, error.message); + } + try { + await util.promisify(pick("throw"))(); + } catch (error) { + console.log("sync:", error.name, error.message); + } + const read = util.promisify(store.read); + console.log("unbound:", await read.call(store, "a")); + store.readAsync = read; + console.log("method:", await store.readAsync("b")); + const bound = util.promisify(store.read.bind({ prefix: "bound" })); + console.log("bound:", await bound("c")); + const named = util.promisify(function lookup(a, b, callback) { + callback(null, [a, b]); + }); + console.log("named:", named.name, named.length, await named(1, 2)); + const values = []; + for (const n of [1, 2, 3]) values.push(await double(n)); + console.log("each:", values); + for (const value of [pick("none"), 42, "fn", {}, undefined]) { + try { + util.promisify(value); + } catch (error) { + console.log(error.name, error.code, error.message); + } + } + try { + util.promisify(); + } catch (error) { + console.log(error.code, error.message); + } +} + +main(); diff --git a/tests/corpus/7523-promisify-untyped.ts b/tests/corpus/7523-promisify-untyped.ts new file mode 100644 index 000000000..65d1d01c1 --- /dev/null +++ b/tests/corpus/7523-promisify-untyped.ts @@ -0,0 +1,38 @@ +// util.promisify over untyped functions in TypeScript: values typed `any` +// and functions whose parameters are `any` promisify at run time, and +// their promises settle with the callback's value (not the type of a +// builtin target's promisified form). +import { promisify } from "node:util"; + +const loose: any = (value: number, callback: (error: Error | null, result?: number) => void) => + callback(null, value + 1); +const looseAsync = promisify(loose); + +function legacy(a: any, b: any, callback: any): void { + setImmediate(() => (b === 0 ? callback(new RangeError("divide by zero")) : callback(null, a / b))); +} +const divide = promisify(legacy); + +const handlers: Record = { + echo: (text: string, callback: (error: null, value: string) => void) => callback(null, text), + later: (callback: (error: null, value: string[]) => void) => + setTimeout(() => callback(null, ["a", "b"]), 1), +}; + +async function main(): Promise { + console.log("loose:", await looseAsync(1)); + looseAsync(2).then((value: unknown) => console.log("then:", value)); + console.log("divide:", await divide(10, 4)); + try { + await divide(1, 0); + } catch (error) { + console.log("rejected:", (error as Error).message); + } + for (const name of ["echo", "later"]) { + const fn = promisify(handlers[name]); + console.log(`${name}:`, name === "echo" ? await fn("hi") : await fn()); + } + console.log("name:", divide.name, "length:", divide.length); +} + +main(); diff --git a/tests/corpus/7524-inspect-custom.ts b/tests/corpus/7524-inspect-custom.ts new file mode 100644 index 000000000..642ce75a9 --- /dev/null +++ b/tests/corpus/7524-inspect-custom.ts @@ -0,0 +1,91 @@ +// [util.inspect.custom] methods on classes: the method's string replaces +// the instance in util.inspect, console output and util.format, with +// Node's depth argument (negative past the depth limit, null for depth: +// null), options object and util.inspect; a multi-line result indents at +// its position, a returned value renders in the instance's place, and +// console.dir, customInspect: false, String() and JSON skip the method. +import * as util from "node:util"; +import { inspect } from "node:util"; + +class Money { + cents: number; + constructor(cents: number) { + this.cents = cents; + } + [inspect.custom](): string { + return `Money(${(this.cents / 100).toFixed(2)})`; + } +} + +class Probe { + label: string; + constructor(label: string) { + this.label = label; + } + [util.inspect.custom](depth: number | null, options: util.InspectOptionsStylized): string { + const keys = Object.keys(options).join(","); + return `Probe<${this.label} depth=${depth} optionsDepth=${options.depth} compact=${options.compact}> ${keys}`; + } +} + +class Lines { + [inspect.custom](): string { + return "first line\nsecond line"; + } +} + +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } + [inspect.custom](): { x: number; y: number; sum: number } { + return { x: this.x, y: this.y, sum: this.x + this.y }; + } +} + +class Wrapper { + inner: { values: number[]; nested: { deep: { deeper: number } } }; + constructor() { + this.inner = { values: [1, 2, 3], nested: { deep: { deeper: 4 } } }; + } + [inspect.custom](depth: number, options: util.InspectOptionsStylized, show: typeof inspect): string { + if (depth < 0) return options.stylize("[Wrapper]", "special"); + return `Wrapper ${show(this.inner, { ...options, depth: options.depth == null ? null : depth - 1 })}`; + } +} + +class Base { + name = "base"; + [inspect.custom](): string { + return `Custom(${this.name})`; + } +} +class Derived extends Base { + extra = 1; +} + +console.log(util.inspect(new Money(1234))); +console.log(new Money(5), [new Money(1), new Money(2)]); +console.log(util.inspect({ wallet: { cash: new Money(99) } })); +console.log(util.inspect(new Probe("top"))); +console.log(util.inspect({ a: { b: new Probe("nested") } })); +console.log(util.inspect([new Probe("past depth")], { depth: 0 })); +console.log(util.inspect(new Probe("unbounded"), { depth: null })); +console.log(util.inspect(new Probe("infinite"), { depth: Infinity })); +console.log(util.inspect([new Probe("undefined")], { depth: undefined })); +console.log(util.inspect(new Probe("compact"), { compact: false, breakLength: 40 })); +console.log(util.inspect({ key: new Lines(), list: [new Lines()] })); +console.log(util.inspect(new Map([["k", new Lines()]]))); +console.log(util.inspect(new Point(1, 2)), util.inspect({ p: new Point(3, 4) })); +console.log(util.inspect(new Wrapper()), util.inspect({ w: new Wrapper() }, { depth: 0 })); +console.log(util.inspect(new Derived()), util.inspect([new Base(), new Derived()])); +console.log(util.format("%s | %O", new Money(10), new Money(20))); +console.log("%s and %d", new Money(1), 2); +console.log(util.inspect(new Money(7), { customInspect: false })); +console.dir(new Money(8)); +console.log(String(new Money(9)), JSON.stringify(new Money(10))); +const values: unknown[] = [new Money(11), { money: new Money(12) }]; +console.log(values, util.inspect(values[1]), [new Lines(), values[0]]); diff --git a/tests/corpus/7525-inspect-custom.cjs b/tests/corpus/7525-inspect-custom.cjs new file mode 100644 index 000000000..9c7302fac --- /dev/null +++ b/tests/corpus/7525-inspect-custom.cjs @@ -0,0 +1,75 @@ +// [util.inspect.custom] in JavaScript: methods on classes and object +// literals (also keyed by Symbol.for('nodejs.util.inspect.custom')), the +// CommonJS call form (0, util_1.inspect)(value), results that are the +// object itself or another value, a non-function member under the key, +// and the options and util.inspect a method receives. +"use strict"; +const util_1 = require("util"); + +class Temperature { + constructor(degrees) { + this.degrees = degrees; + } + [util_1.inspect.custom](depth, options, inspect) { + return `Temperature<${this.degrees}> ${typeof options.stylize} ${inspect === util_1.inspect}`; + } +} + +const literal = { + id: 1, + [util_1.inspect.custom](depth) { + return `literal at depth ${depth}`; + }, +}; +const registered = { + [Symbol.for("nodejs.util.inspect.custom")]() { + return { replaced: [1, 2], nested: { x: 1 } }; + }, +}; +const self = { + v: 1, + [util_1.inspect.custom]() { + return this; + }, +}; +const notFunction = { x: 1, [util_1.inspect.custom]: "not a function" }; +const styled = { + [util_1.inspect.custom](depth, options) { + return options.stylize("styled", "special") + options.stylize(5, "number"); + }, +}; +const nesting = { + data: { a: { b: { c: 1 } } }, + [util_1.inspect.custom](depth, options, inspect) { + return `nesting ${inspect(this.data, { ...options, depth: 0 })}`; + }, +}; + +console.log((0, util_1.inspect)(new Temperature(21))); +console.log((0, util_1.inspect)({ t: new Temperature(22), list: [literal] })); +console.log(util_1.inspect([[literal]], { depth: 0 })); +console.log(util_1.inspect(registered), util_1.inspect({ r: registered })); +console.log(util_1.inspect(self), util_1.inspect(notFunction)); +console.log(util_1.inspect(styled), JSON.stringify(util_1.inspect(styled, { colors: true }))); +console.log(util_1.inspect(nesting), util_1.inspect({ n: nesting })); +console.log(literal, [registered]); +console.log((0, util_1.format)("%s / %O", literal, literal)); +console.log(util_1.inspect(literal, { customInspect: false })); +console.dir(literal); +console.log(util_1.inspect.custom === Symbol.for("nodejs.util.inspect.custom"), util_1.inspect.custom); + +const throwing = { + [util_1.inspect.custom]() { + throw new RangeError("from custom"); + }, +}; +try { + console.log([throwing]); +} catch (error) { + console.log("caught", error.name, error.message); +} +try { + util_1.inspect({ t: throwing }); +} catch (error) { + console.log("caught", error.message); +} diff --git a/tests/corpus/7526-inspect-options-runtime.ts b/tests/corpus/7526-inspect-options-runtime.ts new file mode 100644 index 000000000..b9f0c61d7 --- /dev/null +++ b/tests/corpus/7526-inspect-options-runtime.ts @@ -0,0 +1,62 @@ +// util.inspect with colors, option values and options objects known only +// at run time, and the legacy inspect(value, showHidden, depth, colors) +// form: Node's ANSI styles per value kind, color-aware line breaking and +// grouping, runtime depth and layout options, and colored output from +// util.formatWithOptions. +import * as util from "node:util"; + +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} + +const sparse: number[] = [1, 2, 3]; +sparse.length = 5; +const values: unknown[] = [ + 1, + -0, + "text", + "it's", + null, + undefined, + true, + 10n, + Symbol("sym"), + [1, "two", [3, [4, [5]]]], + { a: 1, "b-c": "d", nested: { deep: { deeper: { deepest: 1 } } } }, + new Map([["k", { v: 1 }]]), + new Set(["s", 2]), + new Date(0), + new Uint8Array([1, 2, 3]), + Buffer.from("hi"), + function named() {}, + new Point(1, 2), + sparse, + Object.create(null), + Array.from({ length: 30 }, (_, i) => i * 37), + "a long string with a newline\nand more text than fits on one line of eighty characters", +]; +for (const value of values) { + console.log(JSON.stringify(util.inspect(value, { colors: true }))); +} +const depth = process.argv.length > 99 ? 5 : 1; +const breakLength = process.argv.length > 99 ? 80 : 30; +console.log(util.inspect(values[10], { depth })); +console.log(util.inspect(values[10], { depth: depth - 1, breakLength })); +console.log(util.inspect(values[20], { breakLength, compact: depth })); +console.log(util.inspect({ b: 2, a: 1 }, { sorted: depth > 0 })); +console.log(util.inspect(values[10], { depth: undefined })); +const options: util.InspectOptions = { depth: 0, colors: true }; +console.log(JSON.stringify(util.inspect(values[10], options))); +options.colors = false; +console.log(util.inspect(values[10], options)); +console.log(util.inspect(values[10], false, 0)); +console.log(util.inspect(values[10], false, null, false)); +console.log(JSON.stringify(util.inspect(values[11], false, 1, true))); +console.log(util.inspect(values[10], undefined, depth)); +console.log(JSON.stringify(util.formatWithOptions({ colors: true }, "%s %O", "x", values[12]))); +console.log(JSON.stringify(util.formatWithOptions({ colors: true }, values[10], values[9]))); diff --git a/tests/diagnostics/inspect-custom-fences.ts b/tests/diagnostics/inspect-custom-fences.ts new file mode 100644 index 000000000..f4c7c593d --- /dev/null +++ b/tests/diagnostics/inspect-custom-fences.ts @@ -0,0 +1,44 @@ +// [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. +import { format, inspect } from "node:util"; + +class Plain { + a = 1; +} +class Fancy extends Plain { + [inspect.custom](): string { + return "fancy"; + } +} +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)}`; + } +} +console.log(inspect(new Wide())); + +class Echo { + [inspect.custom](): Echo { + return new Echo(); + } +} +console.log(inspect(new Echo())); + +class Shown { + [inspect.custom](): string { + return "shown"; + } +} +console.log(format("%o", new Shown())); diff --git a/tests/harness/__snapshots__/inspect-custom-fences.ts.txt b/tests/harness/__snapshots__/inspect-custom-fences.ts.txt new file mode 100644 index 000000000..ba7cefefc --- /dev/null +++ b/tests/harness/__snapshots__/inspect-custom-fences.ts.txt @@ -0,0 +1,49 @@ +inspect-custom-fences.ts:16:21 - error SC2020: 'util.inspect of 'Plain[]' values' is part of the standard library types but has no scriptc lowering yet + + 15 | const items: Plain[] = [new Fancy()]; + 16 | console.log(inspect(items)); + | ^~~~~ + 17 | + + 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 + + 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())); + | ^~~~~~~~~~ + 31 | + + 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 + + 36 | } + 37 | console.log(inspect(new Echo())); + | ^~~~~~~~~~ + 38 | + + 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 + + 43 | } + 44 | console.log(format("%o", new Shown())); + | ^~~~~~~~~~~ + 45 | + + hint: %o passes showHidden to the methods — use %O \ No newline at end of file diff --git a/tests/harness/corpus-costs/7520-util-format-runtime.ts.json b/tests/harness/corpus-costs/7520-util-format-runtime.ts.json new file mode 100644 index 000000000..9cb750202 --- /dev/null +++ b/tests/harness/corpus-costs/7520-util-format-runtime.ts.json @@ -0,0 +1 @@ +{ "plain": 6414, "san": 9821 } diff --git a/tests/harness/corpus-costs/7521-util-format-runtime.cjs.json b/tests/harness/corpus-costs/7521-util-format-runtime.cjs.json new file mode 100644 index 000000000..05cc5696d --- /dev/null +++ b/tests/harness/corpus-costs/7521-util-format-runtime.cjs.json @@ -0,0 +1 @@ +{ "plain": 4635, "san": 6305 } diff --git a/tests/harness/corpus-costs/7522-promisify-untyped.cjs.json b/tests/harness/corpus-costs/7522-promisify-untyped.cjs.json new file mode 100644 index 000000000..c5358642b --- /dev/null +++ b/tests/harness/corpus-costs/7522-promisify-untyped.cjs.json @@ -0,0 +1 @@ +{ "plain": 3984, "san": 6122 } diff --git a/tests/harness/corpus-costs/7523-promisify-untyped.ts.json b/tests/harness/corpus-costs/7523-promisify-untyped.ts.json new file mode 100644 index 000000000..be46b70d4 --- /dev/null +++ b/tests/harness/corpus-costs/7523-promisify-untyped.ts.json @@ -0,0 +1 @@ +{ "plain": 3595, "san": 36166 } diff --git a/tests/harness/corpus-costs/7524-inspect-custom.ts.json b/tests/harness/corpus-costs/7524-inspect-custom.ts.json new file mode 100644 index 000000000..b9c089238 --- /dev/null +++ b/tests/harness/corpus-costs/7524-inspect-custom.ts.json @@ -0,0 +1 @@ +{ "plain": 5331, "san": 10004 } diff --git a/tests/harness/corpus-costs/7525-inspect-custom.cjs.json b/tests/harness/corpus-costs/7525-inspect-custom.cjs.json new file mode 100644 index 000000000..010f8b482 --- /dev/null +++ b/tests/harness/corpus-costs/7525-inspect-custom.cjs.json @@ -0,0 +1 @@ +{ "plain": 4178, "san": 6619 } diff --git a/tests/harness/corpus-costs/7526-inspect-options-runtime.ts.json b/tests/harness/corpus-costs/7526-inspect-options-runtime.ts.json new file mode 100644 index 000000000..0599901b9 --- /dev/null +++ b/tests/harness/corpus-costs/7526-inspect-options-runtime.ts.json @@ -0,0 +1 @@ +{ "plain": 4855, "san": 7570 } diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index c8b0b51b8..cfa8680ee 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -1985,3 +1985,50 @@ console.log("unreachable"); ); }); }); + +// The runtime formatter (format strings known only at run time, spread +// arguments, options objects) throws a coded SC2020 error for renderings +// it has no lowering for. +describe("runtime format refusals", () => { + test("%s of a class instance with toString, %o of objects, colored errors and options", async () => { + const r = await compileAndRun( + "format-runtime-refusals", + `import * as util from "node:util"; +class Named { + name = "n"; + toString(): string { + return this.name; + } +} +const s: string = process.argv.length > 99 ? "" : "%s"; +const o: string = process.argv.length > 99 ? "" : "%o"; +const attempts: Array<() => string> = [ + () => util.format(s, new Named()), + () => util.format(o, { a: 1 }), + () => util.formatWithOptions({ colors: true }, s, "x", new Error("e")), + () => util.formatWithOptions({ maxArrayLength: 1 }, [1, 2]), + () => util.formatWithOptions({ compact: true }, [1, 2]), +]; +for (const attempt of attempts) { + try { + console.log(attempt()); + } catch (error) { + const e = error as Error & { code?: string }; + console.log(e.name, e.code, e.message); + } +} +console.log(util.format(s, { a: 1 }), util.formatWithOptions({ maxArrayLength: 100 }, [1, 2])); +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout).toBe( + "Error SC2020 util.format %s of a class instance whose class defines toString has no lowering\n" + + "Error SC2020 util.format %o of objects renders hidden properties, which have no lowering; use %O\n" + + "Error SC2020 colored inspect output of Error values has no lowering\n" + + "Error SC2020 util.formatWithOptions with the maxArrayLength option has no lowering\n" + + "Error SC2020 util.formatWithOptions with this compact option has no lowering (compact must be false or a number)\n" + + "{ a: 1 } [ 1, 2 ]\n", + ); + expect(r.stderr).toBe(""); + }); +}); From b306a4e11e37194c07f8c2718ba348662d0f13ce Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 20:18:02 -0500 Subject: [PATCH 004/230] Leave fresh unknown[] storage out of the view-hole fact Merged with current main, the hole fact for stores through dynamic views fired on the emitter's per-feature state (`states[slot] = create(emitter)` in emitter-state.ts writes past the end of `emitter.moduleStates`, an `unknown[]`). The viewed element types then widened reads of the IR function arrays, and the object spreads of `IrFunction | undefined` in unshare.ts, callback-specialization.ts and constant-globals.ts were refused when the LLVM emitter bootstraps. A receiver that only holds the array its declaration creates (a const binding or a readonly property whose initializer is an array literal, `new Array` or `Array.of`/`Array.from`, possibly through further such bindings) is never a view of a typed array, so stores through it add no hole fact. --- .../compiler/src/frontend/lowering/lowerer.ts | 46 ++++++++++++++++++- .../test/runtime-optional-precision.test.ts | 14 ++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ccb91291c..bd0981601 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6432,10 +6432,54 @@ export class Lowerer { element !== undefined && (element.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 ); }; + // A binding that only ever holds the array its declaration creates (a + // const variable, or a readonly property with an initializer) is never + // a view of a native array, even when its elements are dynamic. + const freshDynamicStorage = (node: ts.Expression, depth: number): boolean => { + const e = peel(node); + if (ts.isArrayLiteralExpression(e)) return true; + if (ts.isNewExpression(e) || ts.isCallExpression(e)) { + const callee = peel(e.expression); + if (ts.isIdentifier(callee)) return this.isStdlibGlobal(callee, "Array"); + return ( + ts.isPropertyAccessExpression(callee) && + (callee.name.text === "of" || callee.name.text === "from") && + this.isStdlibGlobal(callee.expression, "Array") + ); + } + if (depth > 4 || (!ts.isIdentifier(e) && !ts.isPropertyAccessExpression(e))) return false; + // A typed array along the way is the array a view shows. + const type = typeOrNull(e); + if (type === null || !dynamicType(type)) return false; + const symbol = panicSafe( + () => this.checker.getSymbolAtLocation(ts.isIdentifier(e) ? e : e.name), + undefined, + ); + const declaration = symbol?.valueDeclaration; + if (!declaration) return false; + if (ts.isVariableDeclaration(declaration)) { + const list = declaration.parent; + return ( + ts.isVariableDeclarationList(list) && + (list.flags & ts.NodeFlags.Const) !== 0 && + declaration.initializer !== undefined && + freshDynamicStorage(declaration.initializer, depth + 1) + ); + } + return ( + ts.isPropertyDeclaration(declaration) && + declaration.initializer !== undefined && + (declaration.modifiers?.nodes.some( + (modifier) => modifier.kind === ts.SyntaxKind.ReadonlyKeyword, + ) ?? + false) && + freshDynamicStorage(declaration.initializer, depth + 1) + ); + }; const elementStates = new ArrayElementStates({ isDynamicArray: (node) => { const type = typeOrNull(node); - return type === null || dynamicType(type); + return (type === null || dynamicType(type)) && !freshDynamicStorage(node, 0); }, nativeViewKeys: collectNativeViews, elementAdmitsUndefined: (key) => { diff --git a/packages/compiler/test/runtime-optional-precision.test.ts b/packages/compiler/test/runtime-optional-precision.test.ts index 64f0bd86e..50f1fe138 100644 --- a/packages/compiler/test/runtime-optional-precision.test.ts +++ b/packages/compiler/test/runtime-optional-precision.test.ts @@ -143,6 +143,20 @@ bag["3"] = view; console.log(items.findIndex((item) => weigh(item) > 1)); `); expect(signature(record, "weigh")).toBe("weigh(Item): f64"); + // An unknown[] that only holds the array its declaration creates is never + // a view, so stores past its end leave the viewed arrays alone. + const fresh = await lower(`${ITEM} +class Slots { + readonly states: unknown[] = []; +} +const slots = new Slots(); +const items: Item[] = [new Item(1)]; +const view: unknown = items; +const states = slots.states; +states[3] = view; +console.log(items.findIndex((item) => weigh(item) > 1)); +`); + expect(signature(fresh, "weigh")).toBe("weigh(Item): f64"); }); test("holes that only their owning slot can observe stay out of callbacks", async () => { From e48b2c343508a831bbef57e569b97705586fa2e9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 21:24:51 -0500 Subject: [PATCH 005/230] Keep held format arguments static and compiling The runtime format branch packed its arguments with a checked-dynamic array literal, a dynamic operation: a console.log with a string first argument put its function in the dynamic tier, and the property-bag analysis read the format calls as possible dynamic stores. The held arguments now go to the formatter as a native list of checked values (insp.formatList and insp.formatPrimitives), which is a tier boundary, and the format and inspect entries join the helpers that never store into a class instance. A first argument whose type may hold a string only through a union, or whose checker type does not map, packed every argument as a checked value and failed to compile when one did not convert (7380's `console.log(top ? [...top] : "none", byName)`). It now takes the held-argument form: the lowered value is tested for a substitution and otherwise joins as before. In TypeScript an argument without a checked-dynamic form is refused at compile time only when the joined form cannot print it either; otherwise the branch that substitutes throws SC2020, as in JavaScript. --- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../src/frontend/lowering/lower-console.ts | 3 +- .../src/frontend/lowering/lower-inspect.ts | 55 +++++++++++++------ packages/compiler/src/ir/builtin-effects.ts | 1 + .../compiler/src/ir/builtin-signatures.ts | 3 +- packages/compiler/src/ir/int32-slots.ts | 12 ++++ packages/runtime/src/scr_inspect.c | 23 +++++++- packages/runtime/src/scr_inspect.h | 8 ++- 8 files changed, 81 insertions(+), 25 deletions(-) diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index b02cf44f3..365393326 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1036,6 +1036,7 @@ export const LIB_FN_SYMS: Record = { "insp.dynS": "scr_insp_dyn_s", "insp.format": "scr_insp_format_pack", "insp.formatPrimitives": "scr_insp_format_primitives", + "insp.formatList": "scr_insp_format_list", "insp.formatWithOptions": "scr_insp_format_with_options", "insp.inspect": "scr_insp_inspect", "insp.customEnabled": "scr_insp_custom_enabled", diff --git a/packages/compiler/src/frontend/lowering/lower-console.ts b/packages/compiler/src/frontend/lowering/lower-console.ts index db4d39e70..21540cf47 100644 --- a/packages/compiler/src/frontend/lowering/lower-console.ts +++ b/packages/compiler/src/frontend/lowering/lower-console.ts @@ -21,6 +21,7 @@ import { lowerInspectValue, lowerRuntimeFormat, runtimeFormatString, + formatStringConverts, } from "./lower-inspect.js"; import { streamSidesOf } from "./lower-stream.js"; @@ -372,7 +373,7 @@ export function lowerConsoleOutput( return consoleWrite(stdout, [lowerRuntimeFormat(lowerer, nodes, loc)], loc); const runtime = nodes.length > 1 && !isStringLiteralLike(first) ? runtimeFormatString(lowerer, first!) : null; - if (runtime === "other") + if (runtime === "other" && !formatStringConverts(lowerer, first!)) return consoleWrite(stdout, [lowerRuntimeFormat(lowerer, nodes, loc)], loc); if (runtime !== null) { const held = heldFormatArgs(lowerer, nodes, loc); diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index dd950a2a2..9afe12ed4 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -35,6 +35,7 @@ import { canConvertToDyn, canDynCheckTo, funcOf, + arrayOf, CLASS_FIELD_ATTRIBUTES_PREFIX, classDynViewSupported, DYN_CLASS_PROPERTIES, @@ -3205,7 +3206,8 @@ export function lowerFormatCall( if (!firstIsLiteral) { const runtime = argNodes.length > 1 ? runtimeFormatString(lowerer, first) : null; - if (runtime === "other") return lowerRuntimeFormat(lowerer, argNodes, loc); + if (runtime === "other" && !formatStringConverts(lowerer, first)) + return lowerRuntimeFormat(lowerer, argNodes, loc); if (runtime !== null) { // A string that substitutes formats at run time; any other string // joins with the remaining arguments, as below. @@ -3576,6 +3578,14 @@ function runtimeFenceValue( }; } +/** Whether the held-argument form can test a format string whose type + * only may hold a string: a union that converts to a checked-dynamic value, + * or a value whose checker type does not map (its lowered type decides). */ +export function formatStringConverts(lowerer: Lowerer, first: ts.Expression): boolean { + const t = lowerer.mapTypeOf(lowerer.typeOf(first)); + return t === null || t.kind === "dyn" || lowerer.dynConvertible(t); +} + /** How a non-literal format string is known: "string" and "dyn" values * test for a substitution at run time, "other" types (unions with a string * arm) format at run time, and null types never hold a string. */ @@ -3673,14 +3683,10 @@ export function heldFormatArgs( "util.format of a void call result (call it as its own statement, then pass undefined)", ); } - if (value.type.kind !== "dyn" && !lowerer.dynConvertible(value.type)) { - // JavaScript keeps the space-joined branch, which printed these - // values before, and refuses only a string that substitutes. - if (!isJsSourceFile(ts.getSourceFileOfNode(node))) { - lowerer.noLowering(unconvertibleSurface(value.type), node, UNCONVERTIBLE_HINT); - } + // The space-joined branch prints these values as before; only a string + // that substitutes refuses them, where it runs. + if (value.type.kind !== "dyn" && !lowerer.dynConvertible(value.type)) unconvertible ??= { node, type: value.type }; - } // Unit values have no local: their effects run here and a literal // stands in. const k = value.type.kind; @@ -3694,16 +3700,20 @@ export function heldFormatArgs( return { node, ref: () => varRef(held.id, value.type, loc) }; }); const first = args[0]!.ref(); + // A lowered value that is neither a string nor convertible holds no + // string, so it never substitutes. const specifies: IrExpr = first.type.kind === "string" ? { kind: "libCall", fn: "insp.formatSpecifies", args: [first], type: BOOL, loc } - : { - kind: "libCall", - fn: "insp.formatSpecifiesDyn", - args: [lowerer.coerceToExpected(first, DYN)], - type: BOOL, - loc, - }; + : first.type.kind === "dyn" || lowerer.dynConvertible(first.type) + ? { + kind: "libCall", + fn: "insp.formatSpecifiesDyn", + args: [lowerer.coerceToExpected(first, DYN)], + type: BOOL, + loc, + } + : { kind: "boolLit", value: false, type: BOOL, loc }; const format = (): IrExpr => { if (unconvertible !== null) { const fenced = unconvertible; @@ -3725,14 +3735,18 @@ export function heldFormatArgs( t.kind === "undefinedT" || t.kind === "nullT" || (t.kind === "union" && (lowerer.unions.get(t.unionId)?.arms.every(primitive) ?? false)); + // A native list of the checked values: converting into it is a tier + // boundary, so the statement stays static. return { kind: "libCall", - fn: args.every((arg) => primitive(arg.ref().type)) ? "insp.formatPrimitives" : "insp.format", + fn: args.every((arg) => primitive(arg.ref().type)) + ? "insp.formatPrimitives" + : "insp.formatList", args: [ { - kind: "dynArrLit", + kind: "arrayLit", elems: args.map((arg) => lowerer.coerceToExpected(arg.ref(), DYN)), - type: DYN, + type: arrayOf(DYN), loc, }, ], @@ -3748,6 +3762,11 @@ export function heldFormatArgs( if (t.kind === "bytes") return true; return inspectSupport(lowerer, t, new Set(), { recursive: false }) === null; }); + // Without a joined branch every call formats: TypeScript refuses the + // value at compile time. + const refused = unconvertible as { node: ts.Expression; type: IrType } | null; + if (!joinable && refused !== null && !isJsSourceFile(ts.getSourceFileOfNode(refused.node))) + lowerer.noLowering(unconvertibleSurface(refused.type), refused.node, UNCONVERTIBLE_HINT); return { stmts, args, specifies, format, joinable }; } diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index d676b05dd..357992a5a 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -183,6 +183,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ // The runtime formatter calls toString, valueOf and toJSON, and refuses // renderings it has no lowering for. "insp.format", + "insp.formatList", "insp.formatWithOptions", "insp.inspect", // The options object reads the caller's options (getters included), and diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 6e07e1467..c127af93c 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4376,7 +4376,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "insp.format": { argTypes: [DYN], result: STRING }, /** insp.format over arguments that are all strings, numbers, booleans, * null or undefined, without the checked-dynamic walk. */ - "insp.formatPrimitives": { argTypes: [DYN], result: STRING }, + "insp.formatPrimitives": { argTypes: [arrayOf(DYN)], result: STRING }, + "insp.formatList": { argTypes: [arrayOf(DYN)], result: STRING }, "insp.formatWithOptions": { argTypes: [DYN, DYN], result: STRING }, /** util.inspect(value, options, depth, colors) with the call's argument * count: the runtime reads the options and renders the checked-dynamic diff --git a/packages/compiler/src/ir/int32-slots.ts b/packages/compiler/src/ir/int32-slots.ts index ac0747276..6a30eb92b 100644 --- a/packages/compiler/src/ir/int32-slots.ts +++ b/packages/compiler/src/ir/int32-slots.ts @@ -407,6 +407,18 @@ const DYN_READ_ONLY = new Set([ "fs.utimesSync", "insp.dyn", "insp.dynS", + // util.format and util.inspect over checked values: the inspect walk and + // the format conversions read them; insp.registerCustom keeps a class's + // custom inspect thunk in the runtime's registry. + "insp.customIsSelf", + "insp.customResult", + "insp.format", + "insp.formatList", + "insp.formatPrimitives", + "insp.formatSpecifiesDyn", + "insp.formatWithOptions", + "insp.inspect", + "insp.registerCustom", // The dynamic-tier operators run their dyn twins (scr_js_value.c); the // conversions move a value between dyn and jsval, and js.toF64 is // dynCheck's number check. diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 2c7951103..da49804c1 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -2575,11 +2575,21 @@ static ScrStr *insp_prim_number(ScrDyn *v, int spec) { static const InspFmtOps insp_fmt_primitive_ops = {insp_prim_s, insp_prim_j, insp_prim_number, insp_prim_inspect}; -ScrStr *scr_insp_format_primitives(ScrDyn *pack) { +/* A native list of checked values (the compiled format calls) as the + * runtime's argument pack. +1. */ +static ScrDyn *insp_list_pack(ScrArr *list) { + ScrDyn *pack = scr_dyn_new_arr(); + for (uint32_t i = 0; i < list->len; i++) scr_dyn_arr_push(pack, scr_arr_get_ref(list, i)); + return pack; +} + +ScrStr *scr_insp_format_primitives(ScrArr *list) { ScrInspFormatOptions o; scr_insp_format_defaults(&o, "util.format"); - if (pack->kind != SCR_DYN_ARR) return scr_str_new("", 0); - return insp_format_loop(pack->v.arr.items, pack->v.arr.len, &o, &insp_fmt_primitive_ops); + ScrDyn *pack = insp_list_pack(list); + ScrStr *out = insp_format_loop(pack->v.arr.items, pack->v.arr.len, &o, &insp_fmt_primitive_ops); + scr_dyn_release(pack); + return out; } /* The arguments of a dyn array pack (+1 text, or NULL pending). */ @@ -2594,6 +2604,13 @@ ScrStr *scr_insp_format_pack(ScrDyn *pack) { return insp_fmt_pack(pack, &o); } +ScrStr *scr_insp_format_list(ScrArr *list) { + ScrDyn *pack = insp_list_pack(list); + ScrStr *out = scr_insp_format_pack(pack); + scr_dyn_release(pack); + return out; +} + /* Whether a format string can substitute: a '%' before its last character. * A string without one formats as its arguments joined with spaces. */ bool scr_insp_format_specifies(ScrStr *first) { diff --git a/packages/runtime/src/scr_inspect.h b/packages/runtime/src/scr_inspect.h index a95ac1fde..339fe781b 100644 --- a/packages/runtime/src/scr_inspect.h +++ b/packages/runtime/src/scr_inspect.h @@ -76,9 +76,13 @@ ScrStr *scr_insp_format(ScrDyn *const *args, size_t argc, const ScrInspFormatOpt /* util.format(...pack) and util.formatWithOptions(options, ...pack) over a * dyn array of the arguments (+1). */ ScrStr *scr_insp_format_pack(ScrDyn *pack); -/* util.format(...pack) when every argument is a string, number, boolean, +/* util.format over a native list of checked values, the compiled calls' + * held arguments (+1). */ +struct ScrArr; +ScrStr *scr_insp_format_list(struct ScrArr *list); +/* scr_insp_format_list when every argument is a string, number, boolean, * null or undefined, without the checked-dynamic walk (+1). */ -ScrStr *scr_insp_format_primitives(ScrDyn *pack); +ScrStr *scr_insp_format_primitives(struct ScrArr *list); ScrStr *scr_insp_format_with_options(ScrDyn *options, ScrDyn *pack); /* util.inspect(value, options, depth, colors) over checked-dynamic * arguments with Node's legacy-argument rules; argc is the call's From e7bced94fd9082c9007d1af6f4cdefeb97f752f5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 03:20:04 -0500 Subject: [PATCH 006/230] Let LLVM merge the comparisons of a three-way string comparator --- .../compiler/src/backend/llvm/emit-module.ts | 6 +-- .../src/backend/llvm/string-lifetimes.test.ts | 4 +- tests/harness/string-compare-emission.test.ts | 47 +++++++++++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 tests/harness/string-compare-emission.test.ts diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index 16a1b1601..3c0e91e20 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -1601,8 +1601,8 @@ export function helperDefs(emitter: LlEmitter): string[] { // order; other differing words decide by their first differing byte, // and strings shorter than a word by their first byte. Otherwise a // proven-ASCII string on either side orders by bytes (scr_str_cmp), and - // other pairs call scr_str_cmp_u16_bytes, which only reads the two - // strings, so LLVM can merge repeated comparisons of one pair. Callers + // other pairs call scr_str_cmp_u16_bytes. Both only read the two strings, + // so LLVM can merge repeated comparisons of one pair. Callers // only test the sign of the result. if (emitter.decls.has(`declare i32 @scr_str_cmp_u16(ptr, ptr)`)) { const fn = "scr_str_cmp_u16"; @@ -1732,7 +1732,7 @@ export function helperDefs(emitter: LlEmitter): string[] { ` %anyascii = icmp slt i32 %cws, 0`, ` br i1 %anyascii, label %memorder, label %units`, `memorder:`, - ` %m = call i32 @scr_str_cmp(ptr %a, ptr %b)`, + ` %m = call i32 @scr_str_cmp(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)`, ` ret i32 %m`, `units:`, ` %c = call i32 @scr_str_cmp_u16_bytes(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)`, diff --git a/packages/compiler/src/backend/llvm/string-lifetimes.test.ts b/packages/compiler/src/backend/llvm/string-lifetimes.test.ts index f85addcf8..07ad31153 100644 --- a/packages/compiler/src/backend/llvm/string-lifetimes.test.ts +++ b/packages/compiler/src/backend/llvm/string-lifetimes.test.ts @@ -478,7 +478,9 @@ test("string comparisons decide identity, lengths and leading bytes before the r expect(units).toContain("%below = icmp ult i8 %bx, %by"); expect(units).toContain("%tailable = and i1 %samelen, %short"); expect(units).toContain("%anyascii = icmp slt i32 %cws, 0"); - expect(units).toContain("call i32 @scr_str_cmp(ptr %a, ptr %b)"); + expect(units).toContain( + "call i32 @scr_str_cmp(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)", + ); expect(units).toContain( "call i32 @scr_str_cmp_u16_bytes(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)", ); diff --git a/tests/harness/string-compare-emission.test.ts b/tests/harness/string-compare-emission.test.ts new file mode 100644 index 000000000..2654f56ba --- /dev/null +++ b/tests/harness/string-compare-emission.test.ts @@ -0,0 +1,47 @@ +/* String comparison emission: the runtime calls behind a relational string + * operator only read the two strings, so LLVM merges the comparisons of one + * pair. A three-way comparator (`a < b ? -1 : a > b ? 1 : 0`) then compares + * its strings once, as a comparator that tests one operator does. */ +import { createHash } from "node:crypto"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const repoRoot = join(import.meta.dirname, "../.."); +const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests"); + +async function emittedAsm(name: string, source: string): Promise { + const key = createHash("sha256").update(source).digest("hex").slice(0, 16); + const outDir = join(cacheDir, `string-compare-${key}`); + mkdirSync(outDir, { recursive: true }); + const file = join(outDir, `${name}.ts`); + writeFileSync(file, source); + const result = await compile(file, { + outPath: join(outDir, name), + outDir, + backend: "llvm", + outputKind: "asm", + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return readFileSync(result.artifact.path, "utf8"); +} + +const program = (body: string): string => `function order(a: string, b: string): number { + return ${body}; +} +const words = process.argv.slice(2); +words.sort(order); +console.log(words.join(",")); +`; + +const compareCalls = (asm: string): number => + asm.match(/\bcall[a-z]*\s+(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g)?.length ?? 0; + +test("a three-way string comparator compares its pair once", async () => { + const once = compareCalls(await emittedAsm("once", program("a < b ? -1 : 1"))); + const both = compareCalls(await emittedAsm("both", program("a < b ? -1 : a > b ? 1 : 0"))); + expect(once).toBeGreaterThan(0); + expect(both).toBe(once); +}); From 956292c6274c1aa71b3dcb4ece09cddc920f82d1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 06:06:50 -0500 Subject: [PATCH 007/230] Store foreign elements only in number[] and string[] ToNumber and ToString of a foreign element are reached through a table that scr_arr_set_foreign installs, so a program that never stores a foreign element no longer links the dynamic tier through the array runtime's joins, sorts and numeric reads. Only number[] and string[] store a value written through a dynamic view that their element type cannot hold. Other element types keep the checked conversion's TypeError at the write. Counting every viewed element type as possibly undefined made callbacks over records whose arrays reach a package (zod3-infer) take optional elements and refuse their spreads. --- .../lowering/runtime-optional-elements.ts | 14 +++++++--- packages/runtime/src/scr_array.c | 28 ++++++++++++++++--- .../6068-foreign-elements-through-views.ts | 23 ++++----------- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index 9a85bda1a..4db6ee175 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -62,6 +62,12 @@ const VIEW_HOLES = "%view"; /** The fact name of values stored through dynamic views. */ const VIEW_VALUES = "%view-values"; +/** Element keys whose arrays store a value written through a dynamic view + * that the element type cannot hold, as a foreign element: `number[]` and + * `string[]`. Arrays of other element types reject such a write with the + * checked conversion's TypeError. */ +const FOREIGN_ELEMENT_KEYS: ReadonlySet = new Set(["f64", "string"]); + /** What an array value can contain besides present values. */ interface Contents { undefined: boolean; @@ -373,7 +379,7 @@ export class ArrayElementStates { /** Values of any type stored through a dynamic view can be in arrays of * this key. */ private viewedValues(key: string): boolean { - return this.viewValues && this.isViewed(key); + return this.viewValues && FOREIGN_ELEMENT_KEYS.has(key) && this.isViewed(key); } private isViewed(key: string): boolean { @@ -382,11 +388,11 @@ export class ArrayElementStates { } /** Element keys whose arrays can hold elements outside their element - * type, stored through dynamic views; "*" for every array. Final once - * the fixed point is reached. */ + * type, stored through dynamic views (of FOREIGN_ELEMENT_KEYS). Final + * once the fixed point is reached. */ foreignKeys(): ReadonlySet { if (!this.viewValues) return new Set(); - return (this.viewKeys ??= this.host.nativeViewKeys()); + return new Set([...FOREIGN_ELEMENT_KEYS].filter((key) => this.isViewed(key))); } /** A store through `receiver`, which has no native element key, can diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index e83477142..c7399bcea 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -269,6 +269,25 @@ static uint64_t scr_elem_retain_slot(const ScrArr *a, uint64_t slot) { return scr_elem_retain_with(a->elem, scr_arr_ops(a), slot); } +/* ToNumber and ToString of a foreign element. Both can run user code and + * pull in the dynamic tier, so they are reached through this table, which + * scr_arr_set_foreign installs: every foreign element is stored by it, and + * a program that never stores one links neither conversion. */ +typedef struct ScrArrForeignConversions { + double (*to_number)(const ScrDyn *); + ScrStr *(*to_string)(const ScrDyn *); +} ScrArrForeignConversions; + +static const ScrArrForeignConversions scr_arr_dyn_conversions = { + scr_dyn_number_coerce, + scr_dyn_string_coerce_js, +}; +static const ScrArrForeignConversions *scr_arr_conversions; + +static const ScrArrForeignConversions *scr_arr_foreign_conversions(void) { + return __atomic_load_n(&scr_arr_conversions, __ATOMIC_RELAXED); +} + /* The array may hold SCR_ARR_FOREIGN elements (ScrArrExt.foreign). Such * an array always has states. */ static inline bool scr_arr_may_hold_foreign(const ScrArr *a) { @@ -2056,7 +2075,7 @@ static double *scr_arr_coerced_numbers(const ScrArr *a) { uint64_t slot; uint8_t state = scr_arr_f64_state_at(a, i, &slot); if (state == SCR_ARR_FOREIGN) { - out[i] = scr_dyn_number_coerce(scr_slot_to_ptr(slot)); + out[i] = scr_arr_foreign_conversions()->to_number(scr_slot_to_ptr(slot)); if (scr_exc_pending()) { free(out); return NULL; @@ -2252,7 +2271,7 @@ double scr_arr_get_number(const ScrArr *a, double i) { } else if (!scr_arr_prop_get_state(a, i, &slot, &state)) { return NAN; } - if (state == SCR_ARR_FOREIGN) return scr_dyn_number_coerce(scr_slot_to_ptr(slot)); + if (state == SCR_ARR_FOREIGN) return scr_arr_foreign_conversions()->to_number(scr_slot_to_ptr(slot)); return state == SCR_ARR_VALUE ? scr_slot_to_f64(slot) : NAN; } @@ -2368,6 +2387,7 @@ bool scr_arr_set_foreign(ScrArr *a, double i, ScrDyn *v) { * doubles first, and 1-byte and compact slots cannot hold one. */ if (a->elem == SCR_ELEM_I32) scr_arr_widen_i32(a); if (scr_arr_slot_width(a) != sizeof(uint64_t)) return false; + __atomic_store_n(&scr_arr_conversions, &scr_arr_dyn_conversions, __ATOMIC_RELAXED); scr_dyn_retain(v); scr_arr_need_states(a); if (idx >= a->len) a->len = idx + 1; @@ -3411,7 +3431,7 @@ static ScrArr *scr_arr_sort_foreign(ScrArr *a, bool copy) { if (state != SCR_ARR_VALUE && state != SCR_ARR_FOREIGN) continue; ScrStr *key; if (state == SCR_ARR_FOREIGN) { - key = scr_dyn_string_coerce_js(scr_slot_to_ptr(slot)); + key = scr_arr_foreign_conversions()->to_string(scr_slot_to_ptr(slot)); if (!key) failed = true; } else if (a->elem == SCR_ELEM_STR) { key = scr_str_retain(scr_slot_to_ptr(slot)); @@ -5372,7 +5392,7 @@ ScrStr *scr_arr_join(ScrArr *a, ScrStr *sep) { /* join converts every element but undefined and null with ToString. */ const ScrDyn *value = scr_slot_to_ptr(slot); if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) continue; - ScrStr *text = scr_dyn_string_coerce_js(value); + ScrStr *text = scr_arr_foreign_conversions()->to_string(value); if (!text) continue; scr_join_append(&out, text->data, text->len); scr_str_release(text); diff --git a/tests/corpus/6068-foreign-elements-through-views.ts b/tests/corpus/6068-foreign-elements-through-views.ts index 21cfe25a2..e6676eea4 100644 --- a/tests/corpus/6068-foreign-elements-through-views.ts +++ b/tests/corpus/6068-foreign-elements-through-views.ts @@ -1,8 +1,8 @@ -// A typed array seen as `unknown[]` holds the values written through the -// view that its element type cannot hold, as in Node. The view and the typed -// array are the same array: length, the view's reads and iteration, -// printing, JSON, join, searches, copies, Math over a spread, sorting and -// truncation see the written values. +// A number[] or string[] seen as `unknown[]` holds the values written +// through the view that its element type cannot hold, as in Node. The view +// and the typed array are the same array: length, the view's reads and +// iteration, printing, JSON, join, searches, copies, Math over a spread, +// sorting and truncation see the written values. const numbers: number[] = [1, 2]; const numbersView: unknown[] = numbers; @@ -49,19 +49,8 @@ truncated.length = 1; truncated.length = 3; console.log(truncated, truncatedView.length); -// string[] and class arrays hold values of other types the same way. +// string[] holds values of other types the same way. const words: string[] = ["a"]; const wordsView: unknown[] = words; wordsView.push(1, false); console.log(words, words.join("-"), words.length, typeof wordsView[1]); - -class Item { - id: string; - constructor(id: string) { - this.id = id; - } -} -const items: Item[] = [new Item("a")]; -const itemsView: unknown[] = items; -itemsView.push({ id: "plain" }); -console.log(items, items.length, items[0]!.id, itemsView[1] instanceof Item); From c7c8f5d45ec6b1004a269f33df5688f1f8aec3c6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 09:25:07 -0500 Subject: [PATCH 008/230] Run lone array callbacks as loops with their bodies inlined --- docs/content/docs/contracts.mdx | 2 +- docs/content/limitations/contract-types.mdx | 2 +- .../backend/llvm/appended-array-literals.ts | 35 +- .../src/backend/llvm/emit-statements.ts | 3 + packages/compiler/src/backend/llvm/emitter.ts | 9 +- .../src/backend/llvm/expr-primitives.ts | 57 ++- .../lowering/containers/array-callbacks.ts | 5 +- .../lowering/containers/array-pipelines.ts | 341 ++++++++++++++---- .../src/ir/callback-specialization.ts | 7 +- .../test/array-pipeline-fusion.test.ts | 46 ++- .../test/format-float-arguments.test.ts | 23 +- tests/diagnostics/contract-int32-escapes.ts | 3 +- .../contract-int32-arrays.ts.txt | 6 +- .../contract-int32-escapes.ts.txt | 2 +- 14 files changed, 450 insertions(+), 91 deletions(-) diff --git a/docs/content/docs/contracts.mdx b/docs/content/docs/contracts.mdx index 948fe7960..ad5953fa6 100644 --- a/docs/content/docs/contracts.mdx +++ b/docs/content/docs/contracts.mdx @@ -245,7 +245,7 @@ $ scriptc build scale.ts 1 error. ``` -A copy such as `slice()` or a spread is checked as if it were the same array as its source. The results of callbacks are not followed, so an array built by `map` or `Array.from` with a mapping function cannot be `Int32[]`; build it with a loop that stores `value | 0`. scriptc refuses an `Int32[]` slot whose array can come from code it cannot see, such as `JSON.parse`, dynamic values or the callers of a function used as a value, and an array that can reach one and escapes to such code, such as a call through a function value (SC7003). +A copy such as `slice()` or a spread is checked as if it were the same array as its source. The results of a callback are followed only when scriptc copies it into the loop that runs the array method: a callback written at a `map` or `filter` call whose captured variables are not reassigned, such as `xs.map((x) => (x * 2) | 0)`. Other callback results are not followed, so for example an array built by `Array.from` with a mapping function cannot be `Int32[]`; build it with a loop that stores `value | 0`. scriptc refuses an `Int32[]` slot whose array can come from code it cannot see, such as `JSON.parse`, dynamic values or the callers of a function used as a value, and an array that can reach one and escapes to such code, such as a call through a function value (SC7003). ## Immutable diff --git a/docs/content/limitations/contract-types.mdx b/docs/content/limitations/contract-types.mdx index 35d7a95c0..1fd24a81b 100644 --- a/docs/content/limitations/contract-types.mdx +++ b/docs/content/limitations/contract-types.mdx @@ -1,7 +1,7 @@ [Contract types](/docs/contracts) are checked only where they are written as the annotation of a parameter, variable, class instance field or function result, `Acyclic` only on classes, interfaces and object type aliases, and `ProgramLifetime` only in the `implements` clause of a class declaration; other aliases, interface members and nested type positions are reported. In programs that use worker threads, a surrounding `try`/`catch` does not discharge `NoThrow`. `Int32` refuses parameters of functions used as values and fields of classes whose instances are stored in `any` or `unknown` values. -`Int32[]` refuses arrays that can come from or escape to code scriptc cannot see, does not follow callback results (such as `map` results), and checks a copy made by `slice` or a spread as if it were its source array. +`Int32[]` refuses arrays that can come from or escape to code scriptc cannot see, follows callback results only for callbacks written at a `map` or `filter` call that scriptc runs inline, and checks a copy made by `slice` or a spread as if it were its source array. Element reads that find a value are int32s, but a scalar `Int32` slot still needs `a[i] | 0`, because a read can miss and produce `undefined`. `Immutable` refuses values stored in `any` or `unknown` or that another reference can still change. `ProgramLifetime` applies to root classes of executables only; instances the program drops are never reclaimed unless they belong to a released [lifetime region](/docs/experimental#lifetimeregion). diff --git a/packages/compiler/src/backend/llvm/appended-array-literals.ts b/packages/compiler/src/backend/llvm/appended-array-literals.ts index 3c09904e3..c65aded0c 100644 --- a/packages/compiler/src/backend/llvm/appended-array-literals.ts +++ b/packages/compiler/src/backend/llvm/appended-array-literals.ts @@ -9,7 +9,7 @@ * one directly or through a binding read from it. The choice never changes * behavior. */ -import type { IrExpr, IrModule } from "../../ir/ir.js"; +import type { IrExpr, IrModule, IrStmt } from "../../ir/ir.js"; import { eachNodeOfKinds, type IrNodeIndex, type IrNodeKind } from "../../ir/node-index.js"; const APPENDING_METHODS: ReadonlySet = new Set([ @@ -120,3 +120,36 @@ export function findAppendedArrayLiterals( } return result; } + +/** `const xs = []` directly followed by a capacity hint for `xs` (an + * `arraySetLength` with `reserve`) whose length reads only literals and + * other locals: the literal can be constructed with that capacity, and the + * hint then has nothing left to do. */ +export function reservedArrayLength( + stmts: readonly IrStmt[], + i: number, +): { literal: IrExpr; hint: IrStmt & { kind: "arraySetLength" } } | null { + const decl = stmts[i]; + const next = stmts[i + 1]; + if (decl?.kind !== "varDecl" || decl.init === null || !isEmptyLiteral(decl.init)) return null; + if (next?.kind !== "arraySetLength" || next.reserve !== true) return null; + if (next.arr.kind !== "varRef" || next.arr.localId !== decl.localId) return null; + return plainLength(next.length, decl.localId) ? { literal: decl.init, hint: next } : null; +} + +function plainLength(e: IrExpr, array: string): boolean { + switch (e.kind) { + case "numLit": + return true; + case "varRef": + return e.localId !== array; + case "bin": + return plainLength(e.left, array) && plainLength(e.right, array); + case "ternary": + return ( + plainLength(e.cond, array) && plainLength(e.then, array) && plainLength(e.else_, array) + ); + default: + return false; + } +} diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index c2846a9d4..cbf8fb27d 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -458,6 +458,9 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { emitter.publishedTypes?.containerGuarded(s.arr.type) !== true ) break; + // The literal it follows was constructed with this capacity + // (reservedArrayLength). + if (emitter.consumedReserves.delete(s)) break; const arr = emitBorrowedInput(emitter, s.arr); const length = emitter.emitExpr(s.length); if (s.arr.type.kind !== "array") diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 050c3fb90..b3508b5d0 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -176,7 +176,7 @@ import { ReferenceEffects } from "./reference-effects.js"; import { ReferenceWrites, type Writes } from "./reference-writes.js"; import { LlvmDebugInfo } from "./debug-info.js"; import { StackCallbacks } from "./stack-callbacks.js"; -import { findAppendedArrayLiterals } from "./appended-array-literals.js"; +import { findAppendedArrayLiterals, reservedArrayLength } from "./appended-array-literals.js"; import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callbacks.js"; import { findLentStringParameters, @@ -1235,6 +1235,11 @@ export class LlEmitter { /** Locals whose array literal emitStmts left for the `await Promise.all` * that reads them (deferredPromiseAllLocal). */ private readonly deferredPromiseAllEntries = new Map>(); + /** Empty array literals that a capacity hint follows directly + * (reservedArrayLength), with that hint. Constructing the literal with + * the hinted capacity consumes the hint, which then emits nothing. */ + readonly reservedArrayLengths = new Map(); + readonly consumedReserves = new Set(); /** The promise a `return` emitted past its finally regions already * produced for its adopting await (borrowed from the return's parked * slot); that await takes it instead of evaluating its operand. */ @@ -5514,6 +5519,8 @@ export class LlEmitter { if (this.B.isTerminated()) return; if (cursor) cursor.index = i; this.stmtCursor = cursor; + const reserve = reservedArrayLength(stmts, i); + if (reserve !== null) this.reservedArrayLengths.set(reserve.literal, reserve.hint); const deferred = this.deferredPromiseAllLocal(stmts, i); if (deferred !== null) { this.deferredPromiseAllEntries.set((stmts[i] as { localId: string }).localId, deferred); diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index 1eec6a965..9791de8e5 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -870,6 +870,34 @@ export function emitStringExpr( } } +/** scr_arr_reserve_len reserves dense storage only up to this many slots + * (SCR_ARR_DENSE_LIMIT, scr_array.c). */ +const RESERVE_LIMIT = 2 ** 20; + +/** Elements the inline slots of a small array construction + * (scr_arr_new_small, scr_arr_new_ops_small) hold at least + * (SCR_ARR_SMALL_SLOTS, scr_array.c). */ +const SMALL_ARRAY_SLOTS = 3; + +/** The capacity scr_arr_reserve_len would reserve for `length` on an empty + * array: the length truncated when it is at least 1 and at most + * RESERVE_LIMIT, else 0. */ +function reservedCapacity(host: LlvmEmitterContext, length: IrExpr): string { + const B = host.B; + const n = host.emitExpr(length); + const atLeastOne = B.tmp(), + withinLimit = B.tmp(), + reserves = B.tmp(), + slots = B.tmp(), + capacity = B.tmp(); + B.line(`${atLeastOne} = fcmp oge double ${n.name}, ${f64Lit(1)}`); + B.line(`${withinLimit} = fcmp ole double ${n.name}, ${f64Lit(RESERVE_LIMIT)}`); + B.line(`${reserves} = and i1 ${atLeastOne}, ${withinLimit}`); + B.line(`${slots} = select i1 ${reserves}, double ${n.name}, double ${f64Lit(0)}`); + B.line(`${capacity} = fptoui double ${slots} to ${host.sizeType}`); + return capacity; +} + export function emitContainerExpr( host: LlvmEmitterContext, e: ExprOf< @@ -941,7 +969,34 @@ export function emitContainerExpr( ? `${arr} = call ptr @scr_arr_new_small(i32 ${SCR_ELEM_I32})` : `${arr} = call ptr @scr_arr_new(i32 ${SCR_ELEM_I32}, ${host.sizeType} ${capacity})`, ); - } else B.line(`${arr} = ${arrNewCall(host.shapeHost, elem, String(capacity), small)}`); + } else { + const hint = host.reservedArrayLengths.get(e); + if (hint !== undefined) { + host.reservedArrayLengths.delete(e); + host.consumedReserves.add(hint); + const capacity = reservedCapacity(host, hint.length); + // The small construction's inline slots hold at least + // SMALL_ARRAY_SLOTS elements; larger capacities are sized. + const fits = B.tmp(), + smallArr = B.tmp(), + sizedArr = B.tmp(); + const smallLabel = B.newLabel("arr.cap.small"), + sizedLabel = B.newLabel("arr.cap.sized"), + joined = B.newLabel("arr.cap.j"); + B.line(`${fits} = icmp ule ${host.sizeType} ${capacity}, ${SMALL_ARRAY_SLOTS}`); + B.condBr(fits, smallLabel, sizedLabel); + B.startBlock(smallLabel); + B.line(`${smallArr} = ${arrNewCall(host.shapeHost, elem, "0", true)}`); + B.br(joined); + B.startBlock(sizedLabel); + B.line(`${sizedArr} = ${arrNewCall(host.shapeHost, elem, capacity)}`); + B.br(joined); + B.startBlock(joined); + B.line( + `${arr} = phi ptr [ ${smallArr}, %${smallLabel} ], [ ${sizedArr}, %${sizedLabel} ]`, + ); + } else B.line(`${arr} = ${arrNewCall(host.shapeHost, elem, String(capacity), small)}`); + } const out = host.own({ name: arr, type: e.type }); const acc: ArrayAccess = int32 ? "i32" : elemAccess(elem); let pending: LlValue[] = []; diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index 86f214bc1..7349566d9 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -827,15 +827,16 @@ function buildArrayHofFn( ); returnType = outT; body = [ + readLen, { kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: outT, loc }, loc, }, - readLen, // map's result has the receiver's length, holes where the receiver - // has them. Storage for it is reserved first and the length is set + // has them. Storage for it is reserved first (with the result's + // construction, which directly precedes it) and the length is set // after the loop, so a result without holes is filled by appends and // gets no state bytes. ...(method === "map" diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index 2f839a20b..cdcd0d93c 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -1,4 +1,6 @@ import { keyedHelperName } from "../stable-names.js"; +import { writtenLocals } from "../../../ir/callback-specialization.js"; +import { AmbientReceiverReaders } from "../../../ir/receiver-readers.js"; import { boolLit, countedFor, numLit, varRef } from "../../../ir/build.js"; import { BOOL, @@ -16,9 +18,12 @@ import { typeKey, } from "../../../ir/ir.js"; import { + everyExpr, everyExprChild, + everyStmt, everyStmtChild, everyStmtList, + transformExpr, transformStmtList, } from "../../../ir/traverse.js"; import { @@ -537,6 +542,65 @@ function matchChain(lowerer: Lowerer, e: IrExpr): Chain | null { }; } +/** The outermost array-HOF call of `e` on its own, over its receiver. */ +function loneChain(lowerer: Lowerer, e: IrExpr): Chain | null { + const top = stageOf(lowerer, e); + if (!top) return null; + const { stage, receiver } = top; + if (receiver.type.kind !== "array") return null; + const stages: Stage[] = []; + let terminal: Terminal; + if (TERMINALS.has(stage.info.method)) { + const init = + stage.info.method === "reduce" && stage.info.hasInit && e.kind === "call" ? e.args[2]! : null; + terminal = { kind: "call", stage, init }; + } else if (PRODUCERS.has(stage.info.method)) { + terminal = { kind: "collect" }; + stages.push(stage); + } else return null; + return { + source: receiver, + sourceType: receiver.type, + slice: null, + stages, + terminal, + type: e.type, + loc: e.loc, + }; +} + +/** The callbacks of `chain` in order, or null when its stages cannot run + * in one loop: element types that do not line up, an array argument past + * the first stage, reduce without a seed, or findIndex over holes. */ +function chainCallbacks(chain: Chain): Stage[] | null { + const { stages, terminal } = chain; + const terminalStage = terminal.kind === "call" ? terminal.stage : null; + const callbacks = terminalStage ? [...stages, terminalStage] : stages; + // Element types must line up stage to stage. + let elem = chain.sourceType.elem; + for (const stage of callbacks) { + if (!typeEquals(stage.info.elem, elem)) return null; + if (stage.info.method === "filter" && !typeEquals(stage.info.outElem, stage.info.elem)) + return null; + if ( + (stage.info.method === "map" || stage.info.method === "flatMap") && + !typeEquals(stage.info.outElem, stage.info.fnRet) + ) + return null; + elem = stage.info.outElem; + } + // Index arguments are reproduced; an array argument only names the source. + for (let k = 0; k < callbacks.length; k++) { + const stage = callbacks[k]!; + const lead = stage.info.method === "reduce" ? 1 : 0; + if (stage.info.arity > lead + 3) return null; + if (stage.info.arity === lead + 3 && (k !== 0 || chain.slice)) return null; + } + if (terminalStage?.info.method === "reduce" && !terminalStage.info.hasInit) return null; + if (terminalStage?.info.method === "findIndex" && holeCarrying(chain)) return null; + return callbacks; +} + /* ── the fusion pass ─────────────────────────────────────────────────── */ /** The values a fused loop hands from one stage to the next: a local of a @@ -550,6 +614,10 @@ interface Plan { writes: Set; /** Some callback may throw or loop; at most one does. */ risky: boolean; + /** How the loop inlines callbacks, when not by their effect summaries. */ + inline?: InlineTarget; + /** Stack frame name of the loop: the inlined callback's, for a lone call. */ + frame?: string; } /** Fuse chains of array-HOF helper calls into one loop. @@ -596,12 +664,22 @@ class PipelineFusion { readonly added: IrFunction[] = []; private readonly effects: CallbackEffects; private readonly helpers = new Map(); + /** Bindings each function, or a closure it creates, writes after their + * declaration. */ + private readonly written: Map>; + /** Bindings of the function being fused that inlined callbacks captured. */ + private inlinedCaptures = new Set(); + /** The helper calls each callback with an undefined receiver; a copied + * body runs under the receiver around the array call instead. */ + private readonly receivers: AmbientReceiverReaders; constructor( private readonly lowerer: Lowerer, functions: readonly IrFunction[], ) { this.effects = new CallbackEffects(functions, lowerer.globalsList); + this.written = writtenLocals({ functions }); + this.receivers = new AmbientReceiverReaders(new Map(functions.map((fn) => [fn.name, fn]))); } run(fn: IrFunction): void { @@ -612,6 +690,7 @@ class PipelineFusion { }); if (!candidate) return; const removed = new Set(); + this.inlinedCaptures = new Set(); let body = this.lists(fn.body, fn, removed); body = transformStmtList(body, { stmt: (s) => s, @@ -619,6 +698,7 @@ class PipelineFusion { }); fn.body = body; if (removed.size > 0) fn.locals = fn.locals.filter((l) => !removed.has(l.id)); + if (this.inlinedCaptures.size > 0) unboxUncaptured(fn, this.inlinedCaptures); } /* ── statement lists: fold single-use chain bindings ── */ @@ -733,40 +813,51 @@ class PipelineFusion { /* ── expressions ── */ private fuse(e: IrExpr, fn: IrFunction): IrExpr | null { - const plan = this.plan(e, fn); + const plan = this.plan(e, fn) ?? this.lonePlan(e, fn); return plan ? this.emit(plan) : null; } + /** One array-HOF call on its own (or the last call of a chain that does + * not fuse), when every callback is a closure literal whose body can be + * copied into the loop. A single stage reorders nothing, so the + * callbacks' effects need no model; their captured bindings must keep + * one value while the loop runs (stableTarget), since the loop reads + * them once, when it starts. The loop keeps the helper's order: the + * length read once, each element read when it is visited, holes + * skipped (and kept by map). */ + private lonePlan(e: IrExpr, fn: IrFunction): Plan | null { + const chain = loneChain(this.lowerer, e); + if (!chain) return null; + const callbacks = chainCallbacks(chain); + if (!callbacks) return null; + const inline = this.stableTarget(fn); + let frame: string | undefined; + for (const stage of callbacks) { + for (const arg of [stage.slow, stage.fast]) { + if (!arg) continue; + const target = inline(arg); + if (!target) return null; + frame ??= target.sourceName; + } + } + return { + chain, + callbacks, + writes: new Set(), + risky: true, + inline, + ...(frame === undefined ? {} : { frame }), + }; + } + /** Decide whether `e` is a fusable chain; null when it is not. */ private plan(e: IrExpr, fn: IrFunction): Plan | null { const chain = matchChain(this.lowerer, e); if (!chain) return null; + const callbacks = chainCallbacks(chain); + if (!callbacks) return null; const { stages, terminal } = chain; const terminalStage = terminal.kind === "call" ? terminal.stage : null; - const callbacks = terminalStage ? [...stages, terminalStage] : stages; - // Element types must line up stage to stage. - let elem = chain.sourceType.elem; - for (const stage of callbacks) { - if (!typeEquals(stage.info.elem, elem)) return null; - if (stage.info.method === "filter" && !typeEquals(stage.info.outElem, stage.info.elem)) - return null; - if ( - (stage.info.method === "map" || stage.info.method === "flatMap") && - !typeEquals(stage.info.outElem, stage.info.fnRet) - ) - return null; - elem = stage.info.outElem; - } - // Index arguments are reproduced; an array argument only names the source. - for (let k = 0; k < callbacks.length; k++) { - const stage = callbacks[k]!; - const lead = stage.info.method === "reduce" ? 1 : 0; - if (stage.info.arity > lead + 3) return null; - if (stage.info.arity === lead + 3 && (k !== 0 || chain.slice)) return null; - } - if (terminalStage?.info.method === "reduce" && !terminalStage.info.hasInit) return null; - const holes = holeCarrying(chain); - if (terminalStage?.info.method === "findIndex" && holes) return null; // Effects of each callback, in chain order. const scope = this.effects.scope(fn, true); const summaries: Effects[] = []; @@ -831,30 +922,62 @@ class PipelineFusion { * are passed in by value, read when the fused call starts; no callback * in the chain assigns a binding another one reads). */ private readonly inlineTarget: InlineTarget = (arg) => { + const fn = this.closureTarget(arg); + if (!fn) return null; + const captures = new Set((fn.captures ?? []).map((c) => c.localId)); + const effects = this.effects.ofFunction(fn); + if (!effects || intersects(effects.writes, captures)) return null; + return inlinableBody(fn, captures) ? fn : null; + }; + + /** inlineTarget for a callback created in `fn` whose captured bindings + * nothing assigns after their declaration: `fn`'s own locals and + * parameters, written neither by `fn` nor by any closure that shares + * them, and not by the callback body. The callback must not observe the + * ambient receiver (AmbientReceiverReaders). */ + private stableTarget(fn: IrFunction): InlineTarget { + const locals = new Map(fn.locals.map((l) => [l.id, l])); + const inherited = new Set((fn.captures ?? []).map((c) => c.localId)); + const written = this.written.get(fn.name); + return (arg) => { + const target = this.closureTarget(arg); + if (!target || !written || arg.kind !== "closure") return null; + if (!this.receivers.ignores(target)) return null; + for (const id of arg.captures) { + const local = locals.get(id); + if (!local || local.tdz || inherited.has(id) || written.has(id)) return null; + } + const captures = new Set((target.captures ?? []).map((c) => c.localId)); + const assigns = !everyStmtList(target.body, { + stmt: (st) => !((st.kind === "assign" || st.kind === "forOf") && captures.has(st.localId)), + expr: (ex) => + !((ex.kind === "assignExpr" || ex.kind === "incDec") && captures.has(ex.localId)), + }); + return !assigns && inlinableBody(target, captures) ? target : null; + }; + } + + /** The function behind a closure-literal callback the loop can inline. */ + private closureTarget(arg: IrExpr): IrFunction | null { if (arg.kind !== "closure" || arg.adapts) return null; const fn = this.effects.fn(arg.fnName); if (!fn || fn.async || fn.generator || fn.classCaptures?.length) return null; - const captures = new Set((fn.captures ?? []).map((c) => c.localId)); - if (captures.size !== arg.captures.length) return null; - const effects = this.effects.ofFunction(fn); - if (!effects || intersects(effects.writes, captures)) return null; - if (fn.locals.some((l) => l.tdz || (l.boxed && !captures.has(l.id)))) return null; - let size = 0; - const small = everyStmtList(fn.body, { - expr: (e) => ++size <= MAX_INLINE_NODES && e.kind !== "closure" && e.kind !== "selfRef", - stmt: (s) => ++size <= MAX_INLINE_NODES && s.kind !== "tryCatch", - }); - return small ? fn : null; - }; + if ((fn.captures ?? []).length !== arg.captures.length) return null; + return fn; + } private emit(plan: Plan): IrExpr | null { - const built = new FusedLoop(this.lowerer, plan.chain, this.inlineTarget).build(); + const built = new FusedLoop(this.lowerer, plan.chain, plan.inline ?? this.inlineTarget).build(); if (!built) return null; + for (const id of built.inlinedCaptures) this.inlinedCaptures.add(id); let name = this.helpers.get(built.key); if (!name) { - name = keyedHelperName(this.lowerer, "%arr.fused", built.key); + name = keyedHelperName(this.lowerer, plan.inline ? "%arr.loop" : "%arr.fused", built.key); this.helpers.set(built.key, name); - this.added.push({ ...built.fn, name }); + const helper: IrFunction = { ...built.fn, name }; + // A lone call's loop stands in the inlined callback's stack frame. + if (plan.frame !== undefined) helper.sourceName = plan.frame; + this.added.push(helper); } return { kind: "call", @@ -866,6 +989,48 @@ class PipelineFusion { } } +/** Whether a statement inside `e` (a sequence expression's) returns. */ +function returns(e: IrExpr): boolean { + return !everyExpr(e, { expr: () => true, stmt: (s) => s.kind !== "return" }); +} + +/** Drops the box of each binding in `inlined` that no closure or class + * created in `fn` captures any more: the callbacks that captured it were + * copied into loops, which receive its value. A dead-zone binding keeps the + * box that represents its uninitialized state, and bindings `fn` itself + * captures keep theirs. */ +function unboxUncaptured(fn: IrFunction, inlined: ReadonlySet): void { + const captured = new Set(); + everyStmtList(fn.body, { + expr: (e) => { + if (e.kind === "closure" || e.kind === "classRef") + for (const id of e.captures ?? []) captured.add(id); + return true; + }, + stmt: () => true, + }); + const inherited = new Set( + [...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId), + ); + fn.locals = fn.locals.map((local): IrLocal => { + if (local.boxed !== true || local.tdz === true || !inlined.has(local.id)) return local; + if (captured.has(local.id) || inherited.has(local.id)) return local; + const { boxed: _boxed, ...plain } = local; + return plain; + }); +} + +/** A callback body the loop can copy: small, no nested closures, recursion + * or try statements, and no boxed locals besides its captures. */ +function inlinableBody(fn: IrFunction, captures: ReadonlySet): boolean { + if (fn.locals.some((l) => l.tdz || (l.boxed && !captures.has(l.id)))) return false; + let size = 0; + return everyStmtList(fn.body, { + expr: (e) => ++size <= MAX_INLINE_NODES && e.kind !== "closure" && e.kind !== "selfRef", + stmt: (s) => ++size <= MAX_INLINE_NODES && s.kind !== "tryCatch", + }); +} + function intersects(a: ReadonlySet, b: ReadonlySet): boolean { for (const id of a) if (b.has(id)) return true; return false; @@ -1080,8 +1245,23 @@ class FusedLoop { const id = this.local(l.name, l.type, true); renamed.set(l.id, id); } + const rename = (id: string): string => renamed.get(id) ?? id; + const renameExpr = (e: IrExpr): IrExpr => { + switch (e.kind) { + case "varRef": + case "incDec": + case "assignExpr": + return { ...e, localId: rename(e.localId) }; + default: + return e; + } + }; + const bind: IrStmt[] = fn.params.flatMap((p, index): IrStmt[] => + aliased.has(p.localId) + ? [] + : [{ kind: "varDecl", localId: rename(p.localId), init: args[index]!, loc }], + ); const labels = new Map(); - const exit = this.fresh("%fused.exit"); const label = (name: string): string => { let id = labels.get(name); if (!id) { @@ -1090,19 +1270,48 @@ class FusedLoop { } return id; }; + /** Locals and labels of a copied statement; returns are the caller's. */ + const renameStmt = (st: IrStmt): IrStmt => { + switch (st.kind) { + case "varDecl": + case "assign": + return { ...st, localId: rename(st.localId) }; + case "break": + case "continue": + return st.label ? { ...st, label: label(st.label) } : st; + case "while": + case "doWhile": + case "for": + case "block": + return st.labels ? { ...st, labels: st.labels.map(label) } : st; + default: + return st; + } + }; + // A body whose only return is its last statement (`(x) => x * k`, + // `{ const y = x * 2; return y > 4; }`) needs no exit block: its other + // statements run after the bindings, and the call's value is the + // returned expression. + const last = fn.body[fn.body.length - 1]; + const prefix = fn.body.slice(0, -1); + if ( + last !== undefined && + last.kind === "return" && + last.value !== null && + typeEquals(last.value.type, fn.returnType) && + !returns(last.value) && + prefix.every((st) => everyStmt(st, { expr: () => true, stmt: (x) => x.kind !== "return" })) + ) { + const copy = { expr: renameExpr, stmt: renameStmt }; + return { + pre: [...bind, ...transformStmtList(prefix, copy)], + value: transformExpr(last.value, copy), + }; + } + const exit = this.fresh("%fused.exit"); const result = fn.returnType.kind === "void" ? null : this.local("ret", fn.returnType, true); - const rename = (id: string): string => renamed.get(id) ?? id; const body = transformStmtList(fn.body, { - expr: (e) => { - switch (e.kind) { - case "varRef": - case "incDec": - case "assignExpr": - return { ...e, localId: rename(e.localId) }; - default: - return e; - } - }, + expr: renameExpr, stmt: (s): IrStmt => { switch (s.kind) { case "return": { @@ -1122,27 +1331,14 @@ class FusedLoop { loc: s.loc, }; } - case "varDecl": - case "assign": - return { ...s, localId: rename(s.localId) }; case "break": case "continue": return s.label && s.label !== exit ? { ...s, label: label(s.label) } : s; - case "while": - case "doWhile": - case "for": - case "block": - return s.labels ? { ...s, labels: s.labels.map(label) } : s; default: - return s; + return renameStmt(s); } }, }); - const bind: IrStmt[] = fn.params.flatMap((p, index): IrStmt[] => - aliased.has(p.localId) - ? [] - : [{ kind: "varDecl", localId: rename(p.localId), init: args[index]!, loc }], - ); const pre: IrStmt[] = []; if (result !== null) pre.push({ kind: "varDecl", localId: result, init: null, loc }); pre.push({ kind: "block", labels: [exit], body: [...bind, ...body], loc }); @@ -1155,7 +1351,13 @@ class FusedLoop { }; } - build(): { fn: Omit; args: IrExpr[]; key: string } | null { + build(): { + fn: Omit; + args: IrExpr[]; + key: string; + /** Bindings the callbacks copied into the loop captured at this site. */ + inlinedCaptures: string[]; + } | null { const { chain, loc } = this; const arrT = chain.sourceType; const a = this.param("a", chain.source, "src"); @@ -1640,7 +1842,10 @@ class FusedLoop { }; foldKnownUnions(fn); scalarizeRecordTemporaries(this.lowerer, fn); - return { fn, args: this.args, key: this.keyParts.join("|") }; + const inlinedCaptures = this.callees.flatMap((c) => + c.inline && c.arg.kind === "closure" ? c.arg.captures : [], + ); + return { fn, args: this.args, key: this.keyParts.join("|"), inlinedCaptures }; } } diff --git a/packages/compiler/src/ir/callback-specialization.ts b/packages/compiler/src/ir/callback-specialization.ts index 6038ccaa5..d67b557c4 100644 --- a/packages/compiler/src/ir/callback-specialization.ts +++ b/packages/compiler/src/ir/callback-specialization.ts @@ -16,7 +16,10 @@ const COPIED_NODE_FRACTION = 0.25; * closure it creates writes through its environment (and transitively the * closures those create). A parameter or capture absent from its function's * set holds the value it entered with for the whole call. */ -export function writtenLocals(mod: IrModule, nodes?: IrNodeIndex): Map> { +export function writtenLocals( + mod: { readonly functions: readonly IrFunction[] }, + nodes?: IrNodeIndex, +): Map> { return rebindings(mod, nodes).written; } @@ -31,7 +34,7 @@ const REBINDING_KINDS: readonly IrNodeKind[] = [ ]; function rebindings( - mod: IrModule, + mod: { readonly functions: readonly IrFunction[] }, nodes?: IrNodeIndex, ): { written: Map>; diff --git a/packages/compiler/test/array-pipeline-fusion.test.ts b/packages/compiler/test/array-pipeline-fusion.test.ts index 55cd0025e..0ff456a2c 100644 --- a/packages/compiler/test/array-pipeline-fusion.test.ts +++ b/packages/compiler/test/array-pipeline-fusion.test.ts @@ -108,5 +108,49 @@ const kept = rows.filter((r) => r.value > 3); console.log(kept.map((r) => r.id).length, kept.length); ${postlude}`); expect(init(mod).locals.some((l) => l.name === "kept")).toBe(true); - expect(helperCalls(init(mod))).toEqual(["%arr.filter", "%arr.fused"]); + expect(helperCalls(init(mod))).toEqual(["%arr.fused", "%arr.loop"]); +}); + +test("a lone call with a stable closure-literal callback runs as a loop", async () => { + const mod = await lowerSource(`${prelude} +const scale = 3; +const scaled = rows.map((r) => r.value * scale); +let total = 0; +rows.forEach((r) => { total += r.value; }); +const big = rows.filter((r) => r.value > scale); +const sum = scaled.reduce((acc, v) => acc + v, 0); +console.log(scaled.length, total, big.length, sum, rows.some((r) => r.id === scale)); +${postlude}`); + // forEach assigns \`total\`, a binding of \`run\`: it keeps its helper. + expect(helperCalls(init(mod))).toEqual([ + "%arr.forEach", + "%arr.loop", + "%arr.loop", + "%arr.loop", + "%arr.loop", + ]); + const loops = mod.functions.filter((f) => f.name.startsWith("%arr.loop.")); + expect(loops).toHaveLength(4); + // The callback bodies are copied into the loops: no closure value is + // created or called, and the captured \`scale\` is a plain parameter. + for (const fn of loops) { + expect(count(fn, "callValue")).toBe(0); + expect(count(fn, "closure")).toBe(0); + } + expect(count(init(mod), "closure")).toBe(1); + // No closure captures \`scale\` any more, so it needs no box. + expect(init(mod).locals.find((l) => l.name === "scale")?.boxed).toBeUndefined(); +}); + +test("a lone call keeps its helper when a capture can change while it runs", async () => { + const mod = await lowerSource(`${prelude} +let limit = 2; +const bump = (): void => { limit++; }; +const a = rows.filter((r) => { bump(); return r.id < limit; }); +let step = 1; +step = 2; +const b = rows.map((r) => r.id * step); +console.log(a.length, b.length, limit); +${postlude}`); + expect(helperCalls(init(mod))).toEqual(["%arr.filter", "%arr.map"]); }); diff --git a/packages/compiler/test/format-float-arguments.test.ts b/packages/compiler/test/format-float-arguments.test.ts index 6ce9c4bc0..795e238ef 100644 --- a/packages/compiler/test/format-float-arguments.test.ts +++ b/packages/compiler/test/format-float-arguments.test.ts @@ -3,6 +3,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; import { compile, deserializeModule, validateModule } from "../src/index.js"; +import { everyStmtList } from "../src/ir/traverse.js"; test("util.format %f lowers a boolean argument once", async () => { const dir = await mkdtemp(join(tmpdir(), "scriptc-format-float-")); @@ -22,14 +23,20 @@ test("util.format %f lowers a boolean argument once", async () => { throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); const mod = deserializeModule(await readFile(outPath, "utf8")); expect(validateModule(mod)).toEqual([]); - // One lifted helper per argument: the evaluated-for-effect statement - // reuses the lowered value instead of lowering the argument again. - const helpers = mod.functions - .map((f) => f.name) - .filter((name) => name.startsWith("%arr.")) - .map((name) => name.replace(/\.[0-9a-f]+$/, "")) - .sort(); - expect(helpers).toEqual(["%arr.every", "%arr.some"]); + // One lifted helper call per argument: the evaluated-for-effect + // statement reuses the lowered value instead of lowering the argument + // again. Each call runs its callback as a loop (array-pipelines.ts). + const helpers: string[] = []; + for (const fn of mod.functions) + everyStmtList(fn.body, { + expr: (e) => { + if (e.kind === "call" && e.callee.startsWith("%arr.")) + helpers.push(e.callee.replace(/\.[0-9a-f]+$/, "")); + return true; + }, + stmt: () => true, + }); + expect(helpers).toEqual(["%arr.loop", "%arr.loop"]); } finally { await rm(dir, { recursive: true, force: true }); } diff --git a/tests/diagnostics/contract-int32-escapes.ts b/tests/diagnostics/contract-int32-escapes.ts index fcbbcbfd9..57ed4ce39 100644 --- a/tests/diagnostics/contract-int32-escapes.ts +++ b/tests/diagnostics/contract-int32-escapes.ts @@ -14,4 +14,5 @@ class Box { const box = new Box(); const loose: any = box; loose.size = 0.5; -console.log([1.5, 2].map(double).join(","), box.size); +const pick: (x: number) => number = double; +console.log([1.5, 2].map(pick).join(","), box.size); diff --git a/tests/harness/__snapshots__/contract-int32-arrays.ts.txt b/tests/harness/__snapshots__/contract-int32-arrays.ts.txt index c326adbb5..56db22706 100644 --- a/tests/harness/__snapshots__/contract-int32-arrays.ts.txt +++ b/tests/harness/__snapshots__/contract-int32-arrays.ts.txt @@ -34,14 +34,14 @@ contract-int32-arrays.ts:16:13 - error SC7002: the element stored into 'counts' hint: make the int32 conversion explicit, for example 'Math.PI | 0', so Node stores the same value -contract-int32-arrays.ts:18:26 - error SC7002: the elements 'counts.map((x) => x * 2)' stores are not provably int32s (the array can reach 'doubled', declared Int32[]) +contract-int32-arrays.ts:18:44 - error SC7002: the element stored into 'counts.map((x) => x * 2)' is not provably an int32: 'x * 2' (the array can reach 'doubled', declared Int32[]) 17 | halveInto(counts, ids); 18 | const doubled: Int32[] = counts.map((x) => x * 2); - | ^~~~~~~~~~~~~~~~~~~~~~~~ + | ^~~~~ 19 | const parsed: Int32[] = JSON.parse("[1, 2]"); - hint: callback results are not followed; build the array with a loop that stores 'value | 0' + hint: make the int32 conversion explicit, for example '(x * 2) | 0', so Node stores the same value contract-int32-arrays.ts:19:15 - error SC7003: the Int32[] contract on 'parsed' cannot be checked: the array can come from dynamic (any or unknown) code (contract-int32-arrays.ts:19:25), whose element stores scriptc does not see diff --git a/tests/harness/__snapshots__/contract-int32-escapes.ts.txt b/tests/harness/__snapshots__/contract-int32-escapes.ts.txt index 920f4c4a4..1be04a0cf 100644 --- a/tests/harness/__snapshots__/contract-int32-escapes.ts.txt +++ b/tests/harness/__snapshots__/contract-int32-escapes.ts.txt @@ -21,6 +21,6 @@ contract-int32-escapes.ts:16:1 - error SC7002: the value stored into 'Box.size' 15 | const loose: any = box; 16 | loose.size = 0.5; | ^~~~~~~~~~~~~~~~ - 17 | console.log([1.5, 2].map(double).join(","), box.size); + 17 | const pick: (x: number) => number = double; hint: make the int32 conversion explicit, for example 'loose.size = 0.5 | 0', so Node computes the same value \ No newline at end of file From 116d163e2c96c30bc9c6a9e369ed4ac4476d7d64 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 09:30:37 -0500 Subject: [PATCH 009/230] Take the elements before converting foreign elements Math.max, Math.min and Math.hypot over a spread, and the default sort, convert foreign elements with user code. As in Node, the elements are taken first, each foreign value retained, so a conversion that changes the array changes neither the count nor the values the operation sees, and no slot read before the conversion is released under it. join and the numeric element read hold the value they convert, and join reads the length once and stops at a throw. toSorted of an array with foreign elements now builds a copy of the full length and reads its holes as undefined, as for other arrays. --- packages/runtime/src/scr_array.c | 156 ++++++++++++------ .../6068-foreign-elements-through-views.ts | 35 ++++ 2 files changed, 137 insertions(+), 54 deletions(-) diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index c7399bcea..1405589a3 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -2066,33 +2066,47 @@ ScrArr *scr_arr_fill_undefined(ScrArr *a, double start, double end) { * the empty array yields the zero-argument constants. Borrows the array. */ /* ToNumber of every element in order, as Math.max/min/hypot apply it to * their spread arguments before comparing; for arrays that may hold - * foreign elements, whose conversion can call user code. NULL with the - * exception pending when a conversion throws. +1 malloc'd buffer. */ -static double *scr_arr_coerced_numbers(const ScrArr *a) { - double *out = malloc((a->len ? a->len : 1) * sizeof(*out)); - if (!out) scr_arr_oom(); - for (size_t i = 0; i < a->len; i++) { + * foreign elements, whose conversion can call user code. As in the spread, + * the elements are taken first: the foreign values are retained before + * any conversion runs, so a conversion that changes the array changes + * neither the count (*count) nor the values. NULL with the exception + * pending when a conversion throws. +1 malloc'd buffer. */ +static double *scr_arr_coerced_numbers(const ScrArr *a, size_t *count) { + size_t n = a->len; + double *out = malloc((n ? n : 1) * sizeof(*out)); + ScrDyn **held = calloc(n ? n : 1, sizeof(*held)); + if (!out || !held) scr_arr_oom(); + for (size_t i = 0; i < n; i++) { uint64_t slot; uint8_t state = scr_arr_f64_state_at(a, i, &slot); - if (state == SCR_ARR_FOREIGN) { - out[i] = scr_arr_foreign_conversions()->to_number(scr_slot_to_ptr(slot)); - if (scr_exc_pending()) { - free(out); - return NULL; - } - } else { - out[i] = state == SCR_ARR_VALUE ? scr_slot_to_f64(slot) : NAN; - } + if (state == SCR_ARR_FOREIGN) held[i] = scr_dyn_retain(scr_slot_to_ptr(slot)); + out[i] = state == SCR_ARR_VALUE ? scr_slot_to_f64(slot) : NAN; } + bool failed = false; + for (size_t i = 0; i < n; i++) { + if (!held[i]) continue; + if (!failed) { + out[i] = scr_arr_foreign_conversions()->to_number(held[i]); + failed = scr_exc_pending(); + } + scr_dyn_release(held[i]); + } + free(held); + if (failed) { + free(out); + return NULL; + } + *count = n; return out; } static double scr_math_extreme_foreign(ScrArr *a, bool max) { - double *values = scr_arr_coerced_numbers(a); + size_t n; + double *values = scr_arr_coerced_numbers(a, &n); if (!values) return NAN; double best = max ? -INFINITY : INFINITY; bool nan = false; - for (size_t i = 0; i < a->len; i++) { + for (size_t i = 0; i < n; i++) { double v = values[i]; if (isnan(v)) nan = true; else if (max ? (v > best || (v == 0.0 && best == 0.0 && !signbit(v))) @@ -2135,8 +2149,9 @@ double scr_math_hypot_arr(ScrArr *a) { double scale = 0.0; bool has_nan = false; double *coerced = NULL; - if (scr_arr_may_hold_foreign(a) && !(coerced = scr_arr_coerced_numbers(a))) return NAN; - for (size_t i = 0; i < a->len; i++) { + size_t n = a->len; + if (scr_arr_may_hold_foreign(a) && !(coerced = scr_arr_coerced_numbers(a, &n))) return NAN; + for (size_t i = 0; i < n; i++) { uint64_t slot; double v; if (coerced) { @@ -2160,7 +2175,7 @@ double scr_math_hypot_arr(ScrArr *a) { } double sum = 0.0; double correction = 0.0; - for (size_t i = 0; i < a->len; i++) { + for (size_t i = 0; i < n; i++) { uint64_t slot = 0; if (!coerced) (void)scr_arr_f64_state_at(a, i, &slot); double v = (coerced ? coerced[i] : scr_slot_to_f64(slot)) / scale; @@ -2271,7 +2286,13 @@ double scr_arr_get_number(const ScrArr *a, double i) { } else if (!scr_arr_prop_get_state(a, i, &slot, &state)) { return NAN; } - if (state == SCR_ARR_FOREIGN) return scr_arr_foreign_conversions()->to_number(scr_slot_to_ptr(slot)); + if (state == SCR_ARR_FOREIGN) { + /* The conversion can call user code that removes the element. */ + ScrDyn *value = scr_dyn_retain(scr_slot_to_ptr(slot)); + double number = scr_arr_foreign_conversions()->to_number(value); + scr_dyn_release(value); + return number; + } return state == SCR_ARR_VALUE ? scr_slot_to_f64(slot) : NAN; } @@ -3404,20 +3425,21 @@ static void scr_arr_primitive_order(uint64_t *values, size_t count, } /* The default ordering of an array that may hold foreign elements: present - * elements sort by their string conversions (which may call user code, so - * they are computed first, and a throw leaves the array unchanged), then - * present undefined values, then holes. */ + * elements sort by their string conversions, then present undefined + * values, then holes (toSorted reads them as undefined). The conversions + * may call user code, so the elements are taken first, each retained, as + * the sort's element list is; a throw leaves the array unchanged. */ static ScrArr *scr_arr_sort_foreign(ScrArr *a, bool copy) { - size_t cap = (a->len < a->cap ? a->len : a->cap) + SCR_AX(a)->sparse_len; + size_t length = a->len; + size_t dense = a->len < a->cap ? a->len : a->cap; + size_t cap = dense + SCR_AX(a)->sparse_len; uint64_t *slots = cap ? malloc(cap * sizeof(*slots)) : NULL; uint8_t *states = cap ? malloc(cap) : NULL; ScrStr **keys = cap ? malloc(cap * sizeof(*keys)) : NULL; uint64_t *order_of = cap ? malloc(cap * sizeof(*order_of)) : NULL; if (cap && (!slots || !states || !keys || !order_of)) scr_arr_oom(); size_t count = 0, undefined_count = 0; - bool failed = false; - size_t dense = a->len < a->cap ? a->len : a->cap; - for (size_t at = 0; at < dense + SCR_AX(a)->sparse_len && !failed; at++) { + for (size_t at = 0; at < cap; at++) { uint64_t slot; uint8_t state; if (at < dense) { @@ -3429,10 +3451,17 @@ static ScrArr *scr_arr_sort_foreign(ScrArr *a, bool copy) { } if (state == SCR_ARR_UNDEFINED) undefined_count++; if (state != SCR_ARR_VALUE && state != SCR_ARR_FOREIGN) continue; + slots[count] = scr_arr_retain_state_slot(a, slot, state); + states[count] = state; + count++; + } + bool failed = false; + size_t keyed = 0; + for (; keyed < count; keyed++) { + uint64_t slot = slots[keyed]; ScrStr *key; - if (state == SCR_ARR_FOREIGN) { + if (states[keyed] == SCR_ARR_FOREIGN) { key = scr_arr_foreign_conversions()->to_string(scr_slot_to_ptr(slot)); - if (!key) failed = true; } else if (a->elem == SCR_ELEM_STR) { key = scr_str_retain(scr_slot_to_ptr(slot)); } else { @@ -3440,37 +3469,48 @@ static ScrArr *scr_arr_sort_foreign(ScrArr *a, bool copy) { size_t n = scr_f64_to_str(scr_slot_to_f64(slot), text); key = scr_str_new(text, n); } - if (failed) break; - slots[count] = slot; - states[count] = state; - keys[count] = key; - order_of[count] = count; - count++; + if (!key) { + failed = true; + break; + } + keys[keyed] = key; + order_of[keyed] = keyed; } - ScrArr *out = NULL; + ScrArr *out = a; if (!failed) { ScrPrimitiveOrder order = {a->elem, NULL, keys}; scr_arr_primitive_order(order_of, count, &order); - size_t length = a->len; if (copy) { - out = scr_arr_has_ops(a) ? scr_arr_new_ops(scr_arr_ops(a), count + undefined_count) - : scr_arr_new(a->elem, count + undefined_count); + /* The values, then present undefined for every other index. */ + out = scr_arr_has_ops(a) ? scr_arr_new_ops(scr_arr_ops(a), length) + : scr_arr_new(a->elem, length); + for (size_t i = 0; i < length; i++) { + if (i < count) scr_arr_store_state_owned(out, i, slots[order_of[i]], states[order_of[i]]); + else scr_arr_store_state_owned(out, i, 0, SCR_ARR_UNDEFINED); + out->len = (uint32_t)(i + 1); + } } else { - out = a; + /* The values, then the present undefined values; holes after them. + * The taken elements own their references; release the ones the + * array holds now, which a conversion may have changed. */ + size_t now = a->len < a->cap ? a->len : a->cap; + for (size_t i = 0; i < now; i++) + scr_arr_release_state_slot(a, scr_arr_dense_get(a, i), scr_arr_dense_state(a, i)); + for (size_t i = 0; i < SCR_AX(a)->sparse_len; i++) + scr_arr_release_state_slot(a, SCR_AX(a)->sparse[i].slot, SCR_AX(a)->sparse[i].state); ScrArrStorage old = scr_arr_take_storage(a); scr_arr_free_storage(&old); + a->len = (uint32_t)length; + for (size_t i = 0; i < count; i++) + scr_arr_store_state_owned(a, i, slots[order_of[i]], states[order_of[i]]); + for (size_t i = count; i < count + undefined_count; i++) + scr_arr_store_state_owned(a, i, 0, SCR_ARR_UNDEFINED); } - out->len = length; - for (size_t i = 0; i < count; i++) { - size_t k = order_of[i]; - uint64_t slot = copy ? scr_arr_retain_state_slot(a, slots[k], states[k]) : slots[k]; - scr_arr_store_state_owned(out, i, slot, states[k]); - } - for (size_t i = count; i < count + undefined_count; i++) - scr_arr_store_state_owned(out, i, 0, SCR_ARR_UNDEFINED); scr_arr_refill(out); + } else { + for (size_t i = 0; i < count; i++) scr_arr_release_state_slot(a, slots[i], states[i]); } - for (size_t i = 0; i < count; i++) scr_str_release(keys[i]); + for (size_t i = 0; i < keyed; i++) scr_str_release(keys[i]); free(slots); free(states); free(keys); @@ -5384,16 +5424,24 @@ ScrStr *scr_arr_join(ScrArr *a, ScrStr *sep) { * across its seam; the finished string then needs fusing. */ bool lone = scr_wtf8_any(); bool lead_low = lone && a->len > 1 && scr_wtf8_low_at(sep->data); - for (size_t i = 0; i < a->len; i++) { + /* The length is read once: a conversion that shortens the array leaves + * the indices past its new end empty. */ + size_t length = a->len; + for (size_t i = 0; i < length; i++) { if (i > 0) scr_join_append(&out, sep->data, sep->len); + if (i >= a->len) continue; uint64_t slot; uint8_t state = scr_arr_state_at(a, i, &slot); if (state == SCR_ARR_FOREIGN) { - /* join converts every element but undefined and null with ToString. */ - const ScrDyn *value = scr_slot_to_ptr(slot); + /* join converts every element but undefined and null with ToString, + * which can call user code that removes the element; a throw ends + * the join with the exception pending. */ + ScrDyn *value = scr_slot_to_ptr(slot); if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) continue; + scr_dyn_retain(value); ScrStr *text = scr_arr_foreign_conversions()->to_string(value); - if (!text) continue; + scr_dyn_release(value); + if (!text) break; scr_join_append(&out, text->data, text->len); scr_str_release(text); continue; diff --git a/tests/corpus/6068-foreign-elements-through-views.ts b/tests/corpus/6068-foreign-elements-through-views.ts index e6676eea4..76997c624 100644 --- a/tests/corpus/6068-foreign-elements-through-views.ts +++ b/tests/corpus/6068-foreign-elements-through-views.ts @@ -54,3 +54,38 @@ const words: string[] = ["a"]; const wordsView: unknown[] = words; wordsView.push(1, false); console.log(words, words.join("-"), words.length, typeof wordsView[1]); + +// A conversion that changes the array does not change the spread's values, +// and toSorted reads holes as undefined. +const grown: number[] = [3, 1]; +const grownView: unknown[] = grown; +grownView.push({ + valueOf: () => { + for (let i = 0; i < 64; i++) grown.push(i); + grown.length = 1; + return 2; + }, +}); +console.log(Math.max(...grown), Math.min(...grown), grown.length); +const holey: string[] = ["c", "a"]; +const holeyView: unknown[] = holey; +holeyView[4] = 7; +holeyView[6] = undefined; +console.log(holey.toSorted(), holey.length); +holey.sort(); +console.log(holey, holey.length); + +// A conversion that removes the element it converts. +const shrinking: number[] = [1]; +const shrinkingView: unknown[] = shrinking; +shrinkingView.push({ + toString: () => { + shrinking.length = 0; + return "gone"; + }, +}); +console.log(shrinking.join("+"), shrinking.length); +const cut: string[] = ["a", "b"]; +const cutView: unknown[] = cut; +cutView.splice(1, 0, { toString: () => ((cut.length = 0), "x") }); +console.log(JSON.stringify(cut.join("-")), cut.length); From bbaa7d7ba40427eb7462551f0dc065280af1c68e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 10:28:44 -0500 Subject: [PATCH 010/230] Pass callbacks through bindings in tests of the array-helper and closure paths --- .../test/callback-pending-emission.test.ts | 19 ++++++++++++------- .../test/runtime-optional-precision.test.ts | 4 +++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/compiler/test/callback-pending-emission.test.ts b/packages/compiler/test/callback-pending-emission.test.ts index 9dcbbf0a1..3b2ed3823 100644 --- a/packages/compiler/test/callback-pending-emission.test.ts +++ b/packages/compiler/test/callback-pending-emission.test.ts @@ -9,7 +9,9 @@ import { emitLlvmModule } from "../src/backend/llvm/emitter.js"; // A specialized array helper calls its known callback directly after // checking the closure's entry. A callback that cannot throw skips the // pending test on that path, and the fallback through the closure's own -// entry keeps it; a callback that can throw keeps it on both paths. +// entry keeps it; a callback that can throw keeps it on both paths. The +// callbacks are passed through bindings: a closure literal at the call +// would run as a loop with its body copied in (array-pipelines.ts). test("known callbacks outside the may-throw set test the pending flag only on the fallback", async () => { const dir = mkdtempSync(join(tmpdir(), "scriptc-callback-pending-")); try { @@ -18,13 +20,16 @@ test("known callbacks outside the may-throw set test the pending flag only on th writeFileSync( entry, `const values = [1, 2, 3, 4]; -console.log(values.every((v) => v > 0)); -console.log(values.map((v) => v * 2).join(",")); +const positive = (v: number): boolean => v > 0; +const double = (v: number): number => v * 2; +const stopAtThree = (v: number): boolean => { + if (v > 2) throw new Error("stop at " + v); + return true; +}; +console.log(values.every(positive)); +console.log(values.map(double).join(",")); try { - console.log(values.every((v) => { - if (v > 2) throw new Error("stop at " + v); - return true; - })); + console.log(values.every(stopAtThree)); } catch (error) { console.log("caught", (error as Error).message); } diff --git a/packages/compiler/test/runtime-optional-precision.test.ts b/packages/compiler/test/runtime-optional-precision.test.ts index 7840b0c62..b47cb828d 100644 --- a/packages/compiler/test/runtime-optional-precision.test.ts +++ b/packages/compiler/test/runtime-optional-precision.test.ts @@ -158,7 +158,9 @@ test("an unchanged captured parameter is borrowed and boxed only where a closure const module = await lower(`${ITEM} function heavier(item: Item, limits: number[]): boolean { if (limits.length === 0) return false; - return limits.some((limit) => weigh(item) > limit); + // A closure value: a closure literal at the call would run as a loop. + const over = (limit: number): boolean => weigh(item) > limit; + return limits.some(over); } function replaced(item: Item, other: Item): number { const read = () => item.weight; From 81e2e35e6446973e35a9040e747241c4fe967e37 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 11:09:31 -0500 Subject: [PATCH 011/230] Hold array and record elements whose serialization can call user code JSON.stringify walked a dense native array through its borrowed storage and wrote record fields borrowed from the record. When an element's own serialization can run user code (it reaches an array that may hold foreign elements, whose getters and toJSON run, or a class instance or checked-dynamic value), that code could grow the array, reallocating the storage the walk still read, or replace the field it was writing. In programs that store foreign elements, such arrays now take the general walk, which reads each index afresh and holds its element, and such record fields are held across their write. Programs without foreign elements compile as before. --- packages/compiler/src/backend/llvm/walkers.ts | 65 +++++++++++++++---- .../6068-foreign-elements-through-views.ts | 25 +++++++ 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/packages/compiler/src/backend/llvm/walkers.ts b/packages/compiler/src/backend/llvm/walkers.ts index 77643ca87..ab8c2430c 100644 --- a/packages/compiler/src/backend/llvm/walkers.ts +++ b/packages/compiler/src/backend/llvm/walkers.ts @@ -26,6 +26,7 @@ import { llFieldType, loadStructField, releaseSym, + retainSym, traceAdapter, type ShapeHost, } from "./shapes.js"; @@ -323,9 +324,7 @@ export class LlWalkers { if (i > 0) this.putc(B, "%b", "44"); // ',' if (edgeable(f.type)) this.jbEdgeIdx(B, String(i)); const v = this.loadField(B, "%v", shapeId, fieldIndex.get(f.name)!, f.type); - B.line( - `call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; [${llvmCommentText(f.name)}]`, - ); + this.writeField(B, v, f.type, `[${llvmCommentText(f.name)}]`); }); this.putc(B, "%b", "93"); // ']' if (cyclic) this.jbLeave(B); @@ -357,9 +356,7 @@ export class LlWalkers { this.puts(B, "%b", `${i > 0 ? "," : ""}${jsonObjectKeyLabel(f.name)}`); if (edgeable(f.type)) this.jbEdgeProp(B, f.name); const v = this.loadField(B, "%v", shapeId, fieldIndex.get(f.name)!, f.type); - B.line( - `call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; ${llvmCommentText(f.name)}`, - ); + this.writeField(B, v, f.type, `${llvmCommentText(f.name)}`); }); } else { const first = B.slot(); @@ -394,9 +391,7 @@ export class LlWalkers { comma(); this.puts(B, "%b", jsonObjectKeyLabel(f.name)); if (edgeable(f.type)) this.jbEdgeProp(B, f.name); - B.line( - `call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; ${llvmCommentText(f.name)}`, - ); + this.writeField(B, v, f.type, `${llvmCommentText(f.name)}`); if (skip !== null) { B.br(skip); B.startBlock(skip); @@ -518,9 +513,7 @@ export class LlWalkers { B.startBlock(lKey); this.puts(B, "%b", jsonObjectKeyLabel(fieldName)); if (cyclic && traceAdapter(this.host, f.type) !== null) this.jbEdgeProp(B, fieldName); - B.line( - `call void @${this.jsonWriteHelper(f.type)}(ptr %b, ${this.valTy(f.type)} ${v}) ; ${llvmCommentText(fieldName)}`, - ); + this.writeField(B, v, f.type, `${llvmCommentText(fieldName)}`); B.terminate("ret i1 true"); }); B.startBlock(lSkip); @@ -676,6 +669,47 @@ export class LlWalkers { B.line(`call void @scr_arr_release(ptr ${ks})`); } + /** Serializing a value of `t` can call user code that changes the + * arrays or records holding it: it reaches an array that can hold + * foreign elements (their toJSON and getters), or a value whose own + * serialization may (a class instance, a checked-dynamic value), in a + * program that has foreign elements at all. */ + private serializationMayCallUserCode(t: IrType, seen = new Set()): boolean { + const host = this.host; + if (!host.mayHoldForeign({ kind: "f64" }) && !host.mayHoldForeign({ kind: "string" })) + return false; + const key = typeKey(t); + if (seen.has(key)) return false; + seen.add(key); + switch (t.kind) { + case "dyn": + case "object": + return true; + case "array": + return host.mayHoldForeign(t.elem) || this.serializationMayCallUserCode(t.elem, seen); + case "record": + return (host.recordsById.get(t.shapeId)?.fields ?? []).some((field) => + this.serializationMayCallUserCode(field.type, seen), + ); + case "union": + return (host.unionsById.get(t.unionId)?.arms ?? []).some((arm) => + this.serializationMayCallUserCode(arm, seen), + ); + default: + return false; + } + } + + /** Write a field value loaded borrowed from its record. When its + * serialization can call user code that replaces the field, the value + * is held across the write. */ + private writeField(B: BlockBuilder, v: string, t: IrType, comment: string): void { + const held = isRefCounted(t) && this.serializationMayCallUserCode(t); + if (held) B.line(`call ptr ${retainSym(this.host, t)}(ptr ${v})`); + B.line(`call void @${this.jsonWriteHelper(t)}(ptr %b, ${this.valTy(t)} ${v}) ; ${comment}`); + if (held) B.line(`call void ${releaseSym(this.host, t)}(ptr ${v})`); + } + private emitArrayWriter(B: BlockBuilder, elem: IrType, cyclic: boolean): void { const host = this.host; const w = this.jsonWriteHelper(elem); @@ -685,7 +719,12 @@ export class LlWalkers { if (cyclic) this.jbEnter(B, true); this.putc(B, "%b", "91"); // '[' const finish = B.newLabel("jwa.end"); - if (elem.kind !== "bool") this.emitDenseArrayElements(B, elem, w, cyclic, finish); + // The dense walk borrows the storage and each element. When an + // element's serialization can call user code, which can grow or + // shrink the array or replace the element, the general walk below + // reads each index afresh and holds its element instead. + if (elem.kind !== "bool" && !this.serializationMayCallUserCode(elem)) + this.emitDenseArrayElements(B, elem, w, cyclic, finish); const len = B.tmp(); B.line(`${len} = call double @scr_arr_len(ptr %v)`); B.countedLoop(len, (i) => { diff --git a/tests/corpus/6068-foreign-elements-through-views.ts b/tests/corpus/6068-foreign-elements-through-views.ts index 76997c624..6b1715e01 100644 --- a/tests/corpus/6068-foreign-elements-through-views.ts +++ b/tests/corpus/6068-foreign-elements-through-views.ts @@ -89,3 +89,28 @@ const cut: string[] = ["a", "b"]; const cutView: unknown[] = cut; cutView.splice(1, 0, { toString: () => ((cut.length = 0), "x") }); console.log(JSON.stringify(cut.join("-")), cut.length); + +// JSON of elements whose getters change the array or record holding them. +const fillers: number[][] = []; +const rows: number[][] = [[1], [2]]; +const rowView: unknown[] = rows[0]!; +rowView.push({ + get grown() { + for (let i = 0; i < 64; i++) rows.push([i]); + rows.length = 3; + // Reuse the memory the old storage held. + for (let i = 0; i < 64; i++) fillers.push([i, i]); + return true; + }, +}); +console.log(JSON.stringify(rows), rows.length); +const holder = { values: [5] as number[], name: "h" }; +const holderView: unknown[] = holder.values; +holderView.push({ + get replaced() { + holder.values = [9]; + for (let i = 0; i < 64; i++) fillers.push([i]); + return true; + }, +}); +console.log(JSON.stringify(holder), JSON.stringify(holder)); From b72e164e169d18ca6e23a09b715b3dc186226217 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 18:18:27 -0500 Subject: [PATCH 012/230] Track views through more flows and standard library calls --- .../compiler/src/frontend/lowering/lowerer.ts | 79 ++++++++++++++----- packages/runtime/src/scr_array.c | 3 + .../corpus/6067-view-holes-in-typed-arrays.ts | 58 ++++++++++++++ .../6068-foreign-elements-through-views.ts | 25 ++++++ 4 files changed, 145 insertions(+), 20 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index b23ee147a..9fb65bd4c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6338,6 +6338,20 @@ export class Lowerer { let dynamicViews: Set | null = null; // Element ABIs of the native arrays that an `any` or `unknown` value is // converted to, including arrays nested in the converted value. + // Assignments, including the logical ones (`view ??= numbers`). + const assignmentOperators = new Set([ + ts.SyntaxKind.EqualsToken, + ts.SyntaxKind.QuestionQuestionEqualsToken, + ts.SyntaxKind.BarBarEqualsToken, + ts.SyntaxKind.AmpersandAmpersandEqualsToken, + ]); + // Initializers of class properties, parameter defaults and + // destructuring defaults, which convert to the binding's type. + const initializedBinding = ( + node: ts.Node, + ): node is ts.PropertyDeclaration | ts.ParameterDeclaration | ts.BindingElement => + (ts.isPropertyDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node)) && + node.initializer !== undefined; const collectDynamicViews = (): Set => { const keys = new Set(); const dynamicValue = (node: ts.Expression): boolean => { @@ -6388,14 +6402,18 @@ export class Lowerer { dynamicValue(node.initializer) ) target(typeOrNull(node.name)); + else if (initializedBinding(node) && dynamicValue(node.initializer!)) + target(typeOrNull(node.name ?? node)); else if ( ts.isBinaryExpression(node) && - node.operatorToken.kind === ts.SyntaxKind.EqualsToken && + assignmentOperators.has(node.operatorToken.kind) && dynamicValue(node.right) ) target(typeOrNull(node.left)); else if (ts.isReturnStatement(node) && node.expression && dynamicValue(node.expression)) target(contextualOrNull(node.expression)); + else if (ts.isYieldExpression(node) && node.expression && dynamicValue(node.expression)) + target(contextualOrNull(node.expression)); else if (ts.isCallExpression(node) || ts.isNewExpression(node)) { for (const arg of node.arguments?.nodes ?? []) if (!ts.isSpreadElement(arg) && dynamicValue(arg)) target(contextualOrNull(arg)); @@ -6427,26 +6445,45 @@ export class Lowerer { // Element ABIs of the native arrays a value converted to a dynamic type // can reach: the conversion keeps each such array as a live view, so // stores through the dynamic value write the native storage. - // A standard library function only reads an array argument it takes as - // `any` (console.log, JSON.stringify, String), except the reflective - // writes: Object.assign and defineProperty, Reflect.set and the like, - // and Array.prototype methods through call or apply. - const reflectiveWriters = new Set([ - "assign", - "defineProperty", - "defineProperties", - "setPrototypeOf", - "set", - "apply", - "call", + // Standard library functions that only read the arguments they take as + // `any` or `unknown`: the console methods, String, Number, Boolean, + // Array.isArray, Object.is, JSON.stringify without a replacer function, + // and util's inspect and format. Any other function can keep an + // argument (Promise.resolve, Map.prototype.set, push onto an + // `unknown[]`) or pass it to a callback (a JSON.stringify replacer, an + // emitted event), which can write through it. + const readerGlobals = new Map | null>([ + ["console", null], + ["JSON", new Set(["stringify"])], + ["Array", new Set(["isArray"])], + ["Object", new Set(["is"])], ]); + const readerFunctions = new Set(["String", "Number", "Boolean"]); + const utilReaders = new Set(["inspect", "format"]); const standardLibraryReader = (node: ts.CallExpression | ts.NewExpression): boolean => panicSafe(() => { + if (!ts.isCallExpression(node)) return false; const callee = peel(node.expression); - if (ts.isPropertyAccessExpression(callee) && reflectiveWriters.has(callee.name.text)) - return false; + if (ts.isIdentifier(callee)) + return [...readerFunctions].some((name) => this.isStdlibGlobal(callee, name)); + if (!ts.isPropertyAccessExpression(callee)) return false; + const member = callee.name.text; + for (const [global, members] of readerGlobals) { + if (!this.isStdlibGlobal(callee.expression, global)) continue; + if (members !== null && !members.has(member)) return false; + if (global !== "JSON") return true; + const replacer = node.arguments.nodes[1]; + return ( + replacer === undefined || + ts.isArrayLiteralExpression(peel(replacer)) || + peel(replacer).kind === ts.SyntaxKind.NullKeyword + ); + } + if (!utilReaders.has(member)) return false; const declaration = this.checker.getResolvedSignature(node)?.declaration; - return declaration !== undefined && this.isStdlibFile(ts.getSourceFileOfNode(declaration)); + if (declaration === undefined) return false; + const file = ts.getSourceFileOfNode(declaration); + return this.isStdlibFile(file) && /[\\/]util\.d\.ts$/.test(file.fileName); }, false); const collectNativeViews = (): Set => { const keys = new Set(); @@ -6504,13 +6541,15 @@ export class Lowerer { flow(node.expression, typeOrNull(node)); else if (ts.isVariableDeclaration(node) && node.type && node.initializer) flow(node.initializer, typeOrNull(node.name)); - else if ( - ts.isBinaryExpression(node) && - node.operatorToken.kind === ts.SyntaxKind.EqualsToken - ) + else if (initializedBinding(node)) flow(node.initializer!, typeOrNull(node.name ?? node)); + else if (ts.isBinaryExpression(node) && assignmentOperators.has(node.operatorToken.kind)) flow(node.right, typeOrNull(node.left)); else if (ts.isReturnStatement(node) && node.expression) flow(node.expression, contextualOrNull(node.expression)); + else if (ts.isYieldExpression(node) && node.expression) + flow(node.expression, contextualOrNull(node.expression)); + // A thrown value reaches a catch binding, which is dynamic. + else if (ts.isThrowStatement(node)) flow(node.expression, null); else if (ts.isCallExpression(node) || ts.isNewExpression(node)) { if (!standardLibraryReader(node)) for (const arg of node.arguments?.nodes ?? []) diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index 098a72c4f..a243c94b8 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -2195,6 +2195,8 @@ ScrArr *scr_arr_fill_undefined(ScrArr *a, double start, double end) { * pending when a conversion throws. +1 malloc'd buffer. */ static double *scr_arr_coerced_numbers(const ScrArr *a, size_t *count) { size_t n = a->len; + /* A sparse array's length can exceed what the buffers can count. */ + if (n > SIZE_MAX / sizeof(double)) scr_arr_oom(); double *out = malloc((n ? n : 1) * sizeof(*out)); ScrDyn **held = calloc(n ? n : 1, sizeof(*held)); if (!out || !held) scr_arr_oom(); @@ -3555,6 +3557,7 @@ static ScrArr *scr_arr_sort_foreign(ScrArr *a, bool copy) { size_t length = a->len; size_t dense = a->len < a->cap ? a->len : a->cap; size_t cap = dense + SCR_AX(a)->sparse_len; + if (cap > SIZE_MAX / sizeof(uint64_t)) scr_arr_oom(); uint64_t *slots = cap ? malloc(cap * sizeof(*slots)) : NULL; uint8_t *states = cap ? malloc(cap) : NULL; ScrStr **keys = cap ? malloc(cap * sizeof(*keys)) : NULL; diff --git a/tests/corpus/6067-view-holes-in-typed-arrays.ts b/tests/corpus/6067-view-holes-in-typed-arrays.ts index b59437646..831b3be7a 100644 --- a/tests/corpus/6067-view-holes-in-typed-arrays.ts +++ b/tests/corpus/6067-view-holes-in-typed-arrays.ts @@ -55,3 +55,61 @@ const bag = named as unknown as Record; bag["label"] = "x"; for (const value of named) console.log("named", value); console.log(named.length, bag["label"]); + +// Views that reach dynamic code through class field initializers, +// parameter defaults, logical assignments, yield and throw, and through +// standard library calls that keep their argument or pass it to a +// callback: Promise.resolve, push onto an `unknown[]`, Map.prototype.set +// and a JSON.stringify replacer. +const fieldNumbers: number[] = [1, 2]; +class FieldView { + view: unknown[] = fieldNumbers; +} +new FieldView().view[3] = 4; +for (const value of fieldNumbers) console.log("field", value); +const defaultNumbers: number[] = [1]; +function extend(view: unknown[] = defaultNumbers): void { + view.length = 3; +} +extend(); +console.log(defaultNumbers.findIndex((value) => value === undefined), defaultNumbers.length); +let assigned: unknown[] | undefined; +const assignedNumbers: number[] = [5]; +assigned ??= assignedNumbers; +assigned[2] = 6; +for (const value of assignedNumbers) console.log("assigned", value); +const yieldedNumbers: number[] = [7]; +function* yieldViews(): Generator { + yield yieldedNumbers; +} +for (const view of yieldViews()) view[2] = 8; +console.log(yieldedNumbers.findLastIndex((value) => value === undefined), yieldedNumbers.length); +const thrownNumbers: number[] = [9]; +try { + throw thrownNumbers; +} catch (error) { + (error as unknown[])[2] = 10; +} +for (const value of thrownNumbers) console.log("thrown", value); +const retainedNumbers: number[] = [12]; +const retained: unknown[] = []; +retained.push(retainedNumbers); +(retained[0] as unknown[])[2] = 13; +for (const value of retainedNumbers) console.log("kept", value); +const mappedNumbers: number[] = [14]; +const byName = new Map(); +byName.set("a", mappedNumbers); +(byName.get("a") as unknown[])[2] = 15; +console.log(mappedNumbers.findIndex((value) => value === undefined), mappedNumbers.length); +const replacedNumbers: number[] = [16]; +JSON.stringify({ replacedNumbers: replacedNumbers }, (key, value: unknown) => { + if (key === "replacedNumbers") (value as unknown[])[2] = 17; + return value; +}); +for (const value of replacedNumbers) console.log("replaced", value); +const resolvedNumbers: number[] = [11]; +void Promise.resolve(resolvedNumbers).then((view) => { + (view as unknown[])[2] = 18; + for (const value of resolvedNumbers) console.log("resolved", value); +}); + diff --git a/tests/corpus/6068-foreign-elements-through-views.ts b/tests/corpus/6068-foreign-elements-through-views.ts index 9a64ebc70..5181d7fe5 100644 --- a/tests/corpus/6068-foreign-elements-through-views.ts +++ b/tests/corpus/6068-foreign-elements-through-views.ts @@ -112,6 +112,31 @@ paddedView[1] = { }; padded.sort(); console.log(padded.length, 3 in padded, 4 in padded, padded[5]); +const spread: number[] = [4, 3]; +const spreadView: unknown[] = spread; +let spreadCalls = 0; +spreadView.push({ + toString(): string { + if (spreadCalls++ === 0) { + spreadView[1000] = 1; + for (let i = 0; i < 64; i++) spread.push(i); + } + return "2"; + }, +}); +console.log(spread.toSorted().length, spread.length); +spread.sort(); +console.log(spread.length, spread[0], spread[1], spread[3], 999 in spread, spread[1000]); +const grownMath: number[] = [1]; +const grownMathView: unknown[] = grownMath; +grownMathView[10] = { + valueOf(): number { + grownMathView[5000] = 7; + for (let i = 0; i < 256; i++) grownMath.push(i); + return 3; + }, +}; +console.log(Math.max(...grownMath), grownMath.length); // A conversion that removes the element it converts. const shrinking: number[] = [1]; From f0da37fa423971fcee011da2aa66137975c294c5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 19:52:48 -0500 Subject: [PATCH 013/230] Compile classes that extend Array A class declared `extends Array` (or `extends Array`) was refused with "extending classes not declared in the program ('Array')", which blocked lru-cache at module load. An instance is now an array of the class's element type: its struct begins with the runtime's array header, whose reference count it shares, and the class's own fields follow it. The class's TypeScript type maps to its element array type, so every array operation applies to an instance, and passing it where an array is expected passes the same object. The class's own fields, methods, accessors and statics read the array as the class object. super() follows the Array constructor: one number argument sets the length (a RangeError for an invalid one), other arguments become the elements, and the implicit constructor forwards its arguments the same way. The runtime finds the class through the array's ext (ScrArrClass: name, destroy, the own fields, the species constructor), so instanceof the class (or a binding that holds it) works on static, array-typed and dyn values, util.inspect prints `Name(length) [ ..., field: value ]`, Object.keys lists the own fields, and a release through any array path reaches the class's teardown. Instances whose elements or fields can form cycles carry the collector header and are traced. map, filter, slice, splice, concat, flat and flatMap construct their result through the class as ArraySpeciesCreate does: `new C(length)` with the specification's argument, then the elements, then the length where the specification sets it. A map to another element type retypes the new instance when its constructor added no elements. Array.of and Array.from called on the class construct through it. Programs that declare no Array subclass compile as before. A generic subclass, a subclass of such a class and `static [Symbol.species]` are refused at compile time; a species result the class's constructor cannot construct throws a TypeError. String() of an array with checked-dynamic elements now uses the runtime's String() instead of being refused. Corpus 7720-7726, diagnostics fixture array-subclass-fences.ts. --- docs/content/docs/limitations.mdx | 2 + .../array-subclasses.mdx | 3 + .../compiler/src/backend/cycle-analysis.ts | 4 + packages/compiler/src/backend/llvm/classes.ts | 135 +++- .../compiler/src/backend/llvm/expr-calls.ts | 12 + .../compiler/src/backend/llvm/lib-dispatch.ts | 48 ++ .../compiler/src/backend/llvm/lib-shared.ts | 5 + packages/compiler/src/backend/llvm/shapes.ts | 45 +- .../lowering/builtins/array-classes.ts | 610 ++++++++++++++++++ .../lowering/containers/array-construction.ts | 9 +- .../frontend/lowering/expressions/binary.ts | 6 + .../lowering/expressions/property-access.ts | 32 + .../lowering/expressions/relational.ts | 3 + .../src/frontend/lowering/lower-calls.ts | 18 + .../src/frontend/lowering/lower-classes.ts | 55 +- .../src/frontend/lowering/lower-exprs.ts | 42 +- .../src/frontend/lowering/lower-modules.ts | 6 + .../compiler/src/frontend/lowering/lowerer.ts | 41 +- packages/compiler/src/frontend/type-mapper.ts | 36 ++ .../compiler/src/ir/builtin-signatures.ts | 17 + .../src/ir/function-references.test.ts | 10 + .../compiler/src/ir/function-references.ts | 2 + packages/compiler/src/ir/ir.ts | 11 + packages/compiler/src/ir/traverse.ts | 1 + packages/compiler/src/ir/validate.ts | 43 ++ .../corpus/7720-array-subclass-basics.ts.json | 6 + .../7721-array-subclass-species.ts.json | 6 + .../7722-array-subclass-zero-array.js.json | 6 + .../7723-array-subclass-as-array.ts.json | 6 + .../corpus/7724-array-subclass-cycles.ts.json | 6 + .../7725-array-subclass-operations.ts.json | 6 + .../7726-array-subclass-expressions.js.json | 6 + .../diagnostics/array-subclass-fences.ts.json | 6 + packages/runtime/src/scr_array.c | 242 ++++++- packages/runtime/src/scr_array.h | 58 ++ packages/runtime/src/scr_inspect.c | 26 +- tests/corpus/7720-array-subclass-basics.ts | 44 ++ tests/corpus/7721-array-subclass-species.ts | 37 ++ .../corpus/7722-array-subclass-zero-array.js | 16 + tests/corpus/7723-array-subclass-as-array.ts | 21 + tests/corpus/7724-array-subclass-cycles.ts | 36 ++ .../corpus/7725-array-subclass-operations.ts | 46 ++ .../corpus/7726-array-subclass-expressions.js | 21 + tests/diagnostics/array-subclass-fences.ts | 21 + .../array-subclass-fences.ts.txt | 41 ++ .../7720-array-subclass-basics.ts.json | 1 + .../7721-array-subclass-species.ts.json | 1 + .../7722-array-subclass-zero-array.js.json | 1 + .../7723-array-subclass-as-array.ts.json | 1 + .../7724-array-subclass-cycles.ts.json | 1 + .../7725-array-subclass-operations.ts.json | 1 + .../7726-array-subclass-expressions.js.json | 1 + 52 files changed, 1818 insertions(+), 42 deletions(-) create mode 100644 docs/content/limitations/types-and-class-definitions/array-subclasses.mdx create mode 100644 packages/compiler/src/frontend/lowering/builtins/array-classes.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7720-array-subclass-basics.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7721-array-subclass-species.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7722-array-subclass-zero-array.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7723-array-subclass-as-array.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7724-array-subclass-cycles.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7725-array-subclass-operations.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7726-array-subclass-expressions.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/array-subclass-fences.ts.json create mode 100644 tests/corpus/7720-array-subclass-basics.ts create mode 100644 tests/corpus/7721-array-subclass-species.ts create mode 100644 tests/corpus/7722-array-subclass-zero-array.js create mode 100644 tests/corpus/7723-array-subclass-as-array.ts create mode 100644 tests/corpus/7724-array-subclass-cycles.ts create mode 100644 tests/corpus/7725-array-subclass-operations.ts create mode 100644 tests/corpus/7726-array-subclass-expressions.js create mode 100644 tests/diagnostics/array-subclass-fences.ts create mode 100644 tests/harness/__snapshots__/array-subclass-fences.ts.txt create mode 100644 tests/harness/corpus-costs/7720-array-subclass-basics.ts.json create mode 100644 tests/harness/corpus-costs/7721-array-subclass-species.ts.json create mode 100644 tests/harness/corpus-costs/7722-array-subclass-zero-array.js.json create mode 100644 tests/harness/corpus-costs/7723-array-subclass-as-array.ts.json create mode 100644 tests/harness/corpus-costs/7724-array-subclass-cycles.ts.json create mode 100644 tests/harness/corpus-costs/7725-array-subclass-operations.ts.json create mode 100644 tests/harness/corpus-costs/7726-array-subclass-expressions.js.json diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index a86ed217e..862e49b12 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -52,6 +52,8 @@ title: "Limitations" ../limitations/types-and-class-definitions/static-field-updates.mdx +../limitations/types-and-class-definitions/array-subclasses.mdx + ../limitations/types-and-class-definitions/local-class-expressions.mdx ../limitations/types-and-class-definitions/constructor-function-inheritance.mdx diff --git a/docs/content/limitations/types-and-class-definitions/array-subclasses.mdx b/docs/content/limitations/types-and-class-definitions/array-subclasses.mdx new file mode 100644 index 000000000..ea76e7313 --- /dev/null +++ b/docs/content/limitations/types-and-class-definitions/array-subclasses.mdx @@ -0,0 +1,3 @@ +- **Array subclasses:** A class that extends `Array` compiles: its instances are arrays, and `map`, `filter`, `slice` and the other methods that create arrays return instances of the subclass, as in Node.js. + A generic subclass (`class Bag extends Array`), a class that extends such a subclass, and a subclass that declares `Symbol.species` are unsupported. + A method whose result the subclass's constructor cannot construct throws a `TypeError`: the constructor's parameters must take the one length argument, and a `map` to another element type needs a constructor that adds no elements. diff --git a/packages/compiler/src/backend/cycle-analysis.ts b/packages/compiler/src/backend/cycle-analysis.ts index fbd96e854..b03979bd9 100644 --- a/packages/compiler/src/backend/cycle-analysis.ts +++ b/packages/compiler/src/backend/cycle-analysis.ts @@ -146,6 +146,10 @@ function analyzeCycles( ...(c.localCaptures !== undefined ? [{ name: "", type: { kind: "classval" as const, className: c.name } }] : []), + // An instance of a class that extends Array holds its elements. + ...(c.arrayElem !== undefined + ? [{ name: "", type: { kind: "array" as const, elem: c.arrayElem } }] + : []), ], })), ...(mod.records ?? []).map((r) => ({ diff --git a/packages/compiler/src/backend/llvm/classes.ts b/packages/compiler/src/backend/llvm/classes.ts index 82e34de90..d848b86fc 100644 --- a/packages/compiler/src/backend/llvm/classes.ts +++ b/packages/compiler/src/backend/llvm/classes.ts @@ -58,6 +58,7 @@ import { traceAdapter, type ShapeHost, youngEntryLines, + arrElemInit, } from "./shapes.js"; /** One virtual method slot of a hierarchy: the ROOT-MOST declaring class @@ -371,11 +372,34 @@ function runtimePrefixOf(meta: LlClassMeta): RuntimePrefix | null { * on classes rooted at a runtime base, then the stream-state slot on * stream-rooted ones. */ function headerEnd(meta: LlClassMeta): number { + if (meta.def.arrayElem !== undefined) return ARRAY_PREFIX.length + 1; if (!meta.hierarchy || meta.idInRc) return 1; if (streamRooted(meta)) return 5; return runtimePrefixOf(meta) ? 4 : 2; } +/** The array header a class declared `extends Array` begins with, after + * the size_t reference count: %ScrArr's len, cap, elem, cyc, spare, filled + * and data (scr_array.h). */ +const ARRAY_PREFIX = ["i32", "i32", "i8", "i8", "i16", "i32", "ptr"]; + +/** LLVM `c"..."` text of a string's UTF-8 bytes and a terminating NUL. */ +function nulTerminatedBytes(text: string): string { + let out = ""; + for (const byte of Buffer.from(text, "utf8")) + out += + byte >= 0x20 && byte < 0x7f && byte !== 0x22 && byte !== 0x5c + ? String.fromCharCode(byte) + : `\\${byte.toString(16).padStart(2, "0").toUpperCase()}`; + return out + "\\00"; +} + +/** The class descriptor (ScrArrClass: name, destroy) of a class declared + * `extends Array`. */ +export function arrayClassDescriptor(className: string): string { + return `@${mangleClassStruct(className)}.arrcls`; +} + /** The GEP index of the ThreadSlots slot word (an i32) in a class whose * hierarchy holds ThreadSlots keys: right after the header words, so every * member of the hierarchy has it at the same offset. */ @@ -473,7 +497,11 @@ export interface ClassHost extends ShapeHost { * hierarchy folds away. The cycle analysis leaves the hierarchy untraced, so * no trace adapter ever visits an instance (as for every untraced class). */ export function programLifetimeClass(host: ClassHost, meta: LlClassMeta): boolean { - return host.programLifetime?.has(meta.root.def.name) === true && meta.root.def.runtime !== true; + return ( + host.programLifetime?.has(meta.root.def.name) === true && + meta.root.def.runtime !== true && + meta.root.def.arrayElem === undefined + ); } /** The reference count a program-lifetime instance is created with: the @@ -725,7 +753,12 @@ export function assignCompactLayouts( ): void { const isCompact = (className: string): boolean => { const target = metaMap.get(className); - return target !== undefined && target.def.runtime !== true && compact(target.root); + return ( + target !== undefined && + target.def.runtime !== true && + target.root.def.arrayElem === undefined && + compact(target.root) + ); }; const byPre = [...metaMap.values()].sort((a, b) => a.pre - b.pre); for (const meta of byPre) { @@ -900,13 +933,15 @@ export function emitClassShapes( // usual pointer reinterprets. const runtimePrefix = runtimePrefixOf(meta); const prefix = - meta.hierarchy && !meta.idInRc - ? streamRooted(meta) - ? ["ptr", "ptr", "ptr", "ptr"] - : runtimePrefix - ? ["ptr", "ptr", "ptr"] - : ["ptr"] - : []; + cls.arrayElem !== undefined + ? ARRAY_PREFIX + : meta.hierarchy && !meta.idInRc + ? streamRooted(meta) + ? ["ptr", "ptr", "ptr", "ptr"] + : runtimePrefix + ? ["ptr", "ptr", "ptr"] + : ["ptr"] + : []; const members = [...prefix, ...(meta.threadSlot ? ["i32"] : []), ...fieldTys]; typeDefs.push( `%${mangleClassStruct(cls.name)} = type { ${host.sizeType}${members.length ? ", " + members.join(", ") : ""} } ` + @@ -993,6 +1028,7 @@ export function emitClassShapes( const types = ownedFields(cls).map(({ field }) => storedFieldType(cls, field)); for (const t of types) fieldTypes.push(t); if (programLifetimeClass(host, meta)) continue; + if (cls.arrayElem !== undefined) continue; if (meta.hierarchy && (meta.children.length !== 0 || meta.root.def.runtime === true)) continue; if (!types.some((t) => isRefCounted(t) && needsBoundedRelease(t))) continue; const release = mangleClassRelease(cls.name); @@ -1009,6 +1045,12 @@ export function emitClassShapes( } const directTargets = usedDirectTargets(host, candidates, fieldTypes); + // The descriptor entries of a class declared `extends Array`: a helper + // the module defines, or null (a class that is never constructed has + // neither). + const defined = new Set(mod.functions.map((fn) => fn.name)); + const arrayClassFn = (name: string | undefined): string => + name !== undefined && defined.has(name) ? `@${mangleFunction(name)}` : "null"; for (const cls of emitted) { const meta = metaMap.get(cls.name)!; const struct = mangleClassStruct(cls.name); @@ -1090,6 +1132,7 @@ export function emitClassShapes( const destroy = `${meta.hierarchy ? mangleClassReleaseDirect(cls.name) : mangleClassRelease(cls.name)}_destroy`; const segmented = chained && + cls.arrayElem === undefined && !meta.compact && runtimePrefix === null && !isStreamRooted && @@ -1105,7 +1148,12 @@ export function emitClassShapes( // members). Runs at rc == 0. const teardown = (lines: string[]): void => { const release = (prefix: string, f: (typeof refFields)[number], t: number): string[] => { - const direct = bounded ? directDestroyTarget(host, f.type, directTargets) : null; + // An Array subclass instance's destroy releases its fields plainly + // (it takes no chain length). + const direct = + bounded && cls.arrayElem === undefined + ? directDestroyTarget(host, f.type, directTargets) + : null; if (direct === null) return [ ` call void ${releaseSym(host, f.type)}(ptr %v${t}) ; ${llvmCommentText(f.name)}`, @@ -1236,6 +1284,45 @@ export function emitClassShapes( reld.push(`done:`, ` ret void`, `}`, ``); defs.push(...reld); } + } else if (cls.arrayElem !== undefined) { + // An instance is an array: its release is the array's, which reaches + // the class's destroy (the descriptor's) when the count reaches zero. + host.declare(`declare void @scr_arr_release(ptr)`); + host.declare(`declare void @scr_arr_subclass_drop(ptr)`); + const destroy = `${mangleClassRelease(cls.name)}.arrdestroy`; + const destroyBody: string[] = [ + `define internal void @${destroy}(ptr %o) ${FN_ATTRS} { ; destroy ${cls.name} (an Array subclass instance)`, + `entry:`, + ]; + const fieldsTeardown: string[] = []; + teardown(fieldsTeardown); + const free = emitObjectFree(host, traced); + destroyBody.push( + ...fieldsTeardown.slice(0, fieldsTeardown.length - free.length), + ` call void @scr_arr_subclass_drop(ptr %o)`, + ...free, + ` ret void`, + `}`, + ``, + ); + const displayName = cls.name.includes(".") + ? cls.name.slice(cls.name.lastIndexOf(".") + 1) + : cls.name; + const jsName = cls.jsName ?? displayName; + defs.push( + ...destroyBody, + // The name is the class's own copy: its address identifies the + // class in dyn snapshots of an instance (scr_arr_copy_metadata). + `${arrayClassDescriptor(cls.name)}.name = private constant [${Buffer.byteLength(jsName, "utf8") + 1} x i8] c"${nulTerminatedBytes(jsName)}"`, + `${arrayClassDescriptor(cls.name)} = internal constant { ptr, ptr, ptr, ptr } { ptr ${arrayClassDescriptor(cls.name)}.name, ptr @${destroy}, ptr ${arrayClassFn(cls.arrayFields)}, ptr ${arrayClassFn(cls.arraySpecies)} }`, + ``, + `define internal void @${mangleClassRelease(cls.name)}(ptr %o) ${FN_ATTRS} { ; release ${cls.name} (the array's)`, + `entry:`, + ` call void @scr_arr_release(ptr %o)`, + ` ret void`, + `}`, + ``, + ); } else { const freeBody: string[] = []; teardown(freeBody); @@ -1323,6 +1410,13 @@ export function emitClassShapes( nw.push( ` store ${host.sizeType} ${lifetime ? programLifetimeRc(host.sizeType) : 1}, ptr %o`, ); + if (cls.arrayElem !== undefined) { + const init = arrElemInit(host, cls.arrayElem); + host.declare(`declare void @scr_arr_subclass_init(ptr, i8, ptr, ptr, i1 zeroext)`); + nw.push( + ` call void @scr_arr_subclass_init(ptr %o, i8 ${init.kind}, ptr ${init.ops}, ptr ${arrayClassDescriptor(cls.name)}, i1 ${traced})`, + ); + } if (lifetime && audit) { // --sanitize builds: the RC audit skips its leak report when the // program keeps program-lifetime objects (scr_console.c), and @@ -1359,7 +1453,12 @@ export function emitClassShapes( // Young instances: program-owned traced classes. Classes rooted at a // runtime base (errors, emitters, events, streams) carry runtime state // and stay with trial deletion, like every runtime family. - if (traced && !lifetime && (!meta.hierarchy || meta.root.def.runtime !== true)) + if ( + traced && + !lifetime && + (!meta.hierarchy || meta.root.def.runtime !== true) && + cls.arrayElem === undefined + ) nw.push(...youngEntryLines(host)); nw.push(` ret ptr ${meta.hierarchy ? "%o.vt" : "%o"}`, `}`, ``); defs.push(...nw); @@ -1382,6 +1481,14 @@ export function emitClassShapes( tracedFields.forEach((f, i) => { tr.push(...traceFieldVisitLines(struct, f.index, i, llvmCommentText(f.name))); }); + // An Array subclass instance's elements, when they are cycle-capable. + const tracedElements = + cls.arrayElem !== undefined && + traceAdapter(host, { kind: "array", elem: cls.arrayElem }) !== null; + if (tracedElements) { + host.declare(`declare void @scr_arr_trace_v(ptr, ptr, ptr)`); + tr.push(` call void @scr_arr_trace_v(ptr %o, ptr %visit, ptr %ctx) ; the elements`); + } tr.push(` ret void`, `}`, ``); defs.push(...tr); @@ -1399,6 +1506,12 @@ export function emitClassShapes( ); }); if (meta.threadSlot) gf.push(...slotForgetLines(host, struct, meta, "gfw")); + if (cls.arrayElem !== undefined) { + // The elements: traced ones are the collector's, the others are + // released with the storage. + host.declare(`declare void @scr_arr_subclass_gc_drop(ptr, i1 zeroext)`); + gf.push(` call void @scr_arr_subclass_gc_drop(ptr %o, i1 ${tracedElements})`); + } gf.push(...emitObjectFree(host, true), ` ret void`, `}`, ``); defs.push(...gf); } diff --git a/packages/compiler/src/backend/llvm/expr-calls.ts b/packages/compiler/src/backend/llvm/expr-calls.ts index 6ff52619e..5a16dabf3 100644 --- a/packages/compiler/src/backend/llvm/expr-calls.ts +++ b/packages/compiler/src/backend/llvm/expr-calls.ts @@ -1,4 +1,5 @@ /* Focused LLVM expression emission extracted from emitter.ts. */ +import { arrayClassDescriptor } from "./classes.js"; import { InternalCompilerError } from "../../errors.js"; import { newValueMayThrow } from "../../ir/analysis.js"; import { @@ -1154,6 +1155,17 @@ export function emitCallExpr( return host.own({ name: v.name, type: e.type }); } case "instanceOf": { + // An array is an instance of a class declared `extends Array` when + // its ext names that class (scr_arr_class_of). + if (e.value.type.kind === "array") { + const v = emitBorrowedInputs(host, [e.value])[0]!; + host.declare(`declare ptr @scr_arr_class_of(ptr)`); + const cls = host.B.tmp(); + const result = host.B.tmp(); + host.B.line(`${cls} = call ptr @scr_arr_class_of(ptr ${v.name})`); + host.B.line(`${result} = icmp eq ptr ${cls}, ${arrayClassDescriptor(e.className)}`); + return { name: result, type: e.type }; + } // O(1) preorder-interval test against the vtable the object // carries; the target's interval is a compile-time constant. if (e.value.type.kind !== "object") diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 9cd11feb1..06ac8aa0e 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -1,3 +1,4 @@ +import { arrayClassDescriptor } from "./classes.js"; import { emitBigIntWrap } from "./bigint-wrap.js"; import { emitBorrowedInput, emitBorrowedInputs } from "./borrowed-inputs.js"; import { borrowsIteratorInputs, borrowsJsonInputs } from "./checked-value-lifetimes.js"; @@ -276,6 +277,53 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll host.dyn.emitRecordSourceSpread(B, built.name, value.name, type.shapeId); return { name: "", type: e.type }; } + if (e.fn === "array.speciesAdopt") { + // The receiver is borrowed; the method's result moves into the call, + // which answers it or the species instance that replaces it. + const receiver = emitBorrowedInput(host, e.args[0]!); + const result = host.emitExpr(e.args[1]!); + host.toHeap(result); + const length = host.emitExpr(e.args[2]!); + const setLength = host.emitExpr(e.args[3]!); + host.declare(`declare ptr @scr_arr_species_adopt(ptr, ptr, double, i1 zeroext)`); + const out = B.tmp(); + B.line( + `${out} = call ptr @scr_arr_species_adopt(ptr ${receiver.name}, ptr ${result.name}, double ${length.name}, i1 zeroext ${setLength.name})`, + ); + const value = host.own({ name: out, type: e.type }); + host.emitPendingCheck(); + return value; + } + if (e.fn === "array.dynIsClass") { + const className = e.args[1]!.kind === "strLit" ? e.args[1]!.value : null; + if (className === null) + throw new InternalCompilerError("llvm emitter bug: array.dynIsClass needs a class name"); + const value = emitBorrowedInput(host, e.args[0]!); + host.declare(`declare zeroext i1 @scr_dyn_is_array_class(ptr, ptr)`); + const out = B.tmp(); + B.line( + `${out} = call zeroext i1 @scr_dyn_is_array_class(ptr ${value.name}, ptr ${arrayClassDescriptor(className)})`, + ); + return { name: out, type: e.type }; + } + if (e.fn === "array.classConstructAdopt") { + const className = e.args[0]!.kind === "strLit" ? e.args[0]!.value : null; + if (className === null) + throw new InternalCompilerError( + "llvm emitter bug: array.classConstructAdopt needs a class name", + ); + const result = host.emitExpr(e.args[1]!); + host.toHeap(result); + const length = host.emitExpr(e.args[2]!); + host.declare(`declare ptr @scr_arr_class_construct_adopt(ptr, ptr, double)`); + const out = B.tmp(); + B.line( + `${out} = call ptr @scr_arr_class_construct_adopt(ptr ${arrayClassDescriptor(className)}, ptr ${result.name}, double ${length.name})`, + ); + const value = host.own({ name: out, type: e.type }); + host.emitPendingCheck(); + return value; + } if (e.fn === "dyn.typedRefIs" || e.fn === "dyn.typedRefKeyIs") { const literal = e.args[1]!.kind === "strLit" ? e.args[1]!.value : null; if (e.fn === "dyn.typedRefKeyIs" && literal === null) diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index c9b125cd7..c706fb187 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1348,6 +1348,11 @@ export const LIB_FN_SYMS: Record = { "http.reqPipeSock": "scr_http_req_pipe_sock", "http.reqResume": "scr_http_req_resume", "http.reqPause": "scr_http_req_pause", + "array.subclassConstruct": "scr_arr_subclass_construct", + "array.subclassConstructDyn": "scr_arr_subclass_construct_dyn", + "array.speciesAdopt": "scr_arr_species_adopt", + "array.classConstructAdopt": "scr_arr_class_construct_adopt", + "array.dynIsClass": "scr_dyn_is_array_class", "http.reqDestroy": "scr_http_req_destroy", "http.reqSetEncoding": "scr_http_req_set_encoding", "http.resSetHeader": "scr_http_res_set_header", diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index b3f5084d3..08d0db9b4 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -1084,7 +1084,33 @@ export function arrNewCall( * live in its own block): capText must be "0". */ small = false, ): string { - const useRef = + const useRef = arrUsesRef(host, elem); + if (!useRef) { + if (small) { + host.declare(`declare ptr @scr_arr_new_small(i32)`); + return `call ptr @scr_arr_new_small(i32 ${elemKindNum(elem)})`; + } + host.declare(`declare ptr @scr_arr_new(i32, ${host.sizeType})`); + return `call ptr @scr_arr_new(i32 ${elemKindNum(elem)}, ${host.sizeType} ${capText})`; + } + if (small) { + host.declare(`declare ptr @scr_arr_new_ops_small(ptr)`); + return `call ptr @scr_arr_new_ops_small(ptr ${arrOps(host, elem)})`; + } + host.declare(`declare ptr @scr_arr_new_ops(ptr, ${host.sizeType})`); + return `call ptr @scr_arr_new_ops(ptr ${arrOps(host, elem)}, ${host.sizeType} ${capText})`; +} + +/** The ScrElemKind and ops descriptor (or `null`) an array of `elem` + * starts with: the arrays arrNewCall constructs, for the instances of a + * class that extends Array (scr_arr_subclass_init). */ +export function arrElemInit(host: ShapeHost, elem: IrType): { kind: number; ops: string } { + if (!arrUsesRef(host, elem)) return { kind: elemKindNum(elem), ops: "null" }; + return { kind: host.compactArrayElem?.(elem) === true ? 6 : 5, ops: arrOps(host, elem) }; +} + +function arrUsesRef(host: ShapeHost, elem: IrType): boolean { + return ( elem.kind === "record" || elem.kind === "object" || elem.kind === "union" || @@ -1101,21 +1127,8 @@ export function arrNewCall( elem.kind === "dyn" || // native collection seeds/drains: traced REF elem.kind === "regex" || // RegExp values: scr_regex_* adapters, no trace (no refs inside) elem.kind === "date" || // Date objects: scr_date_* adapters, no trace (no refs inside) - (elem.kind === "array" && traceAdapter(host, elem) !== null); - if (!useRef) { - if (small) { - host.declare(`declare ptr @scr_arr_new_small(i32)`); - return `call ptr @scr_arr_new_small(i32 ${elemKindNum(elem)})`; - } - host.declare(`declare ptr @scr_arr_new(i32, ${host.sizeType})`); - return `call ptr @scr_arr_new(i32 ${elemKindNum(elem)}, ${host.sizeType} ${capText})`; - } - if (small) { - host.declare(`declare ptr @scr_arr_new_ops_small(ptr)`); - return `call ptr @scr_arr_new_ops_small(ptr ${arrOps(host, elem)})`; - } - host.declare(`declare ptr @scr_arr_new_ops(ptr, ${host.sizeType})`); - return `call ptr @scr_arr_new_ops(ptr ${arrOps(host, elem)}, ${host.sizeType} ${capText})`; + (elem.kind === "array" && traceAdapter(host, elem) !== null) + ); } /** The %ScrArrOps descriptor (retain, release, trace, registry id) of a REF diff --git a/packages/compiler/src/frontend/lowering/builtins/array-classes.ts b/packages/compiler/src/frontend/lowering/builtins/array-classes.ts new file mode 100644 index 000000000..0136eca08 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/array-classes.ts @@ -0,0 +1,610 @@ +/* Classes declared `extends Array`. An instance is an array of the class's + * element type: its struct begins with the runtime's array header (the + * reference count is the array's) and the class's own fields follow it, so + * every array operation applies to the instance unchanged and passing it + * where a plain array is expected passes the same object. The class's + * TypeScript type maps to its element array type; the class's own members, + * construction and `instanceof` view the array as the class object + * (arrayClassView). */ +import * as ts from "../../ts7/adapter.js"; +import { type Lowerer } from "../lowerer.js"; +import { isJsSourceFile, locOf } from "../../program.js"; +import { stripTypeWrappers } from "../typed-arguments.js"; +import { dynUndefinedExpr, nodeThrowExpr } from "../../../ir/build.js"; +import { lowerArrayFromCall, lowerArrayOfCall } from "../containers/array-construction.js"; +import { storedClassValueType, type ClassInfo } from "../lower-classes.js"; +import { + BOOL, + DYN, + F64, + STRING, + UNDEFINED_T, + arrayOf, + type IrExpr, + type IrFunction, + type IrLocal, + type IrStmt, + type IrType, + type SrcLoc, +} from "../../../ir/ir.js"; + +/** The element type of `class X extends Array` (`extends Array` in + * JavaScript reads `any[]`, whose elements are checked-dynamic). */ +export function arrayClassBase( + lowerer: Lowerer, + heritage: ts.ExpressionWithTypeArguments, +): { elem: IrType } { + const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(heritage)); + // `extends Array` in JavaScript reads `any[]`: dyn elements. + if (isJsSourceFile(ts.getSourceFileOfNode(heritage)) && mapped?.kind !== "array") + return { elem: DYN }; + if (mapped?.kind !== "array") + lowerer.unsupported( + "SC1090", + heritage, + "extending Array with an element type that does not compile", + ); + return { elem: mapped!.kind === "array" ? mapped!.elem : DYN }; +} + +/** The class declared `extends Array` at the root of `info`'s chain, or null. */ +export function arrayRootOf(info: ClassInfo | null | undefined): ClassInfo | null { + for (let c = info ?? null; c; c = c.base) if (c.extendsArray) return c; + return null; +} + +/** The array a class instance is (a reinterpret: the same object). */ +export function arrayOfClassInstance(info: ClassInfo, value: IrExpr, loc: SrcLoc): IrExpr { + const root = arrayRootOf(info)!; + return { kind: "upcast", value, type: arrayOf(root.extendsArray!.elem), loc }; +} + +/** The class object an instance of `info` is, viewed from its array type + * (a reinterpret; the value is known to be an instance of the class). */ +export function arrayClassView(info: ClassInfo, value: IrExpr, loc: SrcLoc): IrExpr { + return { kind: "downcast", value, type: { kind: "object", className: info.def.name }, loc }; +} + +function thisArray(info: ClassInfo, thisLocal: IrLocal, loc: SrcLoc): IrExpr { + return arrayOfClassInstance( + info, + { + kind: "varRef", + localId: thisLocal.id, + type: { kind: "object", className: info.def.name }, + loc, + }, + loc, + ); +} + +/** The implicit constructor's super(...args): `pack` is its dyn argument + * array, applied with the Array constructor's length-or-elements rule. */ +export function arraySuperCall( + info: ClassInfo, + thisRef: IrExpr, + pack: IrExpr, + loc: SrcLoc, +): IrStmt { + const elem = arrayRootOf(info)!.extendsArray!.elem; + return { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "array.subclassConstructDyn", + args: [ + arrayOfClassInstance(info, thisRef, loc), + pack, + { kind: "boolLit", value: elem.kind === "dyn", type: BOOL, loc }, + ], + type: { kind: "void" }, + loc, + }, + loc, + }; +} + +/** An explicit super(...) in a class declared `extends Array`: one number + * argument sets the length; otherwise the arguments (spreads included) + * become the elements, with the same one-number rule at run time. */ +export function lowerArraySuperCall( + lowerer: Lowerer, + info: ClassInfo, + thisLocal: IrLocal, + superCall: ts.CallExpression, +): IrStmt[] { + const loc = locOf(superCall); + const elem = arrayRootOf(info)!.extendsArray!.elem; + const args = superCall.arguments.nodes; + if (args.length === 0) return []; + const only = args.length === 1 && !ts.isSpreadElement(args[0]!) ? args[0]! : null; + if (only !== null && (lowerer.typeOf(only).flags & ts.TypeFlags.NumberLike) !== 0) { + return [ + { + kind: "arraySetLength", + arr: thisArray(info, thisLocal, loc), + length: lowerer.lowerExprExpecting(only, { kind: "f64" }), + loc, + }, + ]; + } + const arrT = arrayOf(elem); + const elements: IrExpr[] = []; + const spreads: number[] = []; + for (const arg of args) { + if (ts.isSpreadElement(arg)) { + spreads.push(elements.length); + elements.push(lowerer.lowerExprExpecting(arg.expression, arrT)); + } else elements.push(lowerer.lowerExprExpecting(arg, elem)); + } + const pack: IrExpr = { + kind: "arrayLit", + elems: elements, + ...(spreads.length > 0 ? { spreads } : {}), + type: arrT, + loc, + } as IrExpr; + return [ + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "array.subclassConstruct", + args: [ + thisArray(info, thisLocal, loc), + pack, + { kind: "boolLit", value: elem.kind === "dyn", type: BOOL, loc }, + ], + type: { kind: "void" }, + loc, + }, + loc, + }, + ]; +} + +/** The ClassInfo of a class declared `extends Array`, by its declaration. */ +export function arrayClassOfDecl( + lowerer: Lowerer, + decl: ts.ClassLikeDeclaration, +): ClassInfo | null { + const symbol = decl.name ? lowerer.checker.getSymbolAtLocation(decl.name) : undefined; + let info = symbol ? lowerer.classBySymbol.get(symbol) : undefined; + if (!info) + for (const candidate of lowerer.classes.values()) if (candidate.decl === decl) info = candidate; + return info && arrayRootOf(info) ? info : null; +} + +/** The class that declares the member `access` names when that class is + * declared `extends Array` (its own fields, methods and accessors), or null + * for the Array members it inherits and every other access. */ +export function arrayClassMemberOwner( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, +): ClassInfo | null { + const symbol = lowerer.checker.getSymbolAtLocation(access.name); + const decl = symbol?.declarations?.[0]; + const owner = decl?.parent; + if (owner === undefined || !ts.isClassLikeDeclaration(owner)) return null; + const declarer = arrayClassOfDecl(lowerer, owner); + if (declarer === null) return null; + // The receiver's own class (a subclass member reached through a base + // spelling is the same instance). + const receiver = lowerer.typeOf(access.expression).getSymbol()?.valueDeclaration; + if (receiver !== undefined && ts.isClassLikeDeclaration(receiver)) + return arrayClassOfDecl(lowerer, receiver) ?? declarer; + return declarer; +} + +/** `x instanceof Array` on a static array (always true: every array value, + * subclass instances included, is an Array) and `x instanceof X` for a + * class X declared `extends Array`: the array's class, read from its ext + * at run time. Null for every other instanceof. */ +export function lowerArrayClassInstanceOf( + lowerer: Lowerer, + expr: ts.BinaryExpression, + loc: SrcLoc, +): IrExpr | null { + if (!ts.isIdentifier(expr.right)) return null; + const leftType = lowerer.mapTypeOf(lowerer.typeOf(expr.left)); + if (lowerer.isStdlibGlobal(expr.right, "Array")) { + // A checked-dynamic value: the Array.isArray test (one realm). + if (leftType?.kind === "dyn") + return { + kind: "dynTest", + test: "array", + value: lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN), + type: BOOL, + loc, + } as IrExpr; + if (leftType?.kind !== "array") return null; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: lowerer.lowerExpr(expr.left), loc }], + result: { kind: "boolLit", value: true, type: BOOL, loc }, + type: BOOL, + loc, + }; + } + const symbol = lowerer.resolveValueSymbol(expr.right); + const decl = symbol?.valueDeclaration; + let info: ClassInfo | null = null; + if (decl !== undefined && ts.isClassLikeDeclaration(decl)) { + info = arrayClassOfDecl(lowerer, decl); + } else { + // A binding that holds a class value (`const C = class extends Array + // {}`): its static class, the only class it can hold while nothing + // extends that class. + const stored = storedClassValueType(lowerer, expr.right); + const held = stored?.kind === "classval" ? lowerer.classes.get(stored.className) : undefined; + if (held === undefined || arrayRootOf(held) === null) return null; + if (held.subclasses.length > 0) + lowerer.unsupported( + "SC1090", + expr, + "'instanceof' against a binding holding a class that extends Array and has subclasses", + ); + info = held; + } + if (info === null) return null; + const value = lowerer.lowerExpr(expr.left); + if (value.type.kind === "array") + return { kind: "instanceOf", value, className: info.def.name, type: BOOL, loc }; + if (value.type.kind === "dyn") + return { + kind: "libCall", + fn: "array.dynIsClass", + args: [value, { kind: "strLit", value: info.def.name, type: STRING, loc }], + type: BOOL, + loc, + }; + if (value.type.kind === "object" && value.type.className === info.def.name) + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: value, loc }], + result: { kind: "boolLit", value: true, type: BOOL, loc }, + type: BOOL, + loc, + }; + return null; +} + +/** The class declared `extends Array` that a value of `type` is an instance + * of, or null. */ +export function arrayClassOfType(lowerer: Lowerer, type: ts.Type): ClassInfo | null { + const decl = type.getSymbol()?.valueDeclaration; + return decl !== undefined && ts.isClassLikeDeclaration(decl) + ? arrayClassOfDecl(lowerer, decl) + : null; +} + +/** `x.constructor` on an instance of a class declared `extends Array`: the + * class (the class has no subclasses, so the static type names it). */ +export function lowerArrayClassConstructorRead( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, + loc: SrcLoc, +): IrExpr | null { + if (access.name.text !== "constructor") return null; + const info = arrayClassOfType(lowerer, lowerer.typeOf(access.expression)); + if (info === null) return null; + const value = lowerer.lowerExpr(access.expression); + const cls = lowerer.classValueRef(info, access); + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: value, loc }], + result: cls, + type: cls.type, + loc, + }; +} + +/** `%C.arrayFields(a)`: the own fields of an instance of a class declared + * `extends Array` as a dyn object, in declaration order (the descriptor's + * `fields`, which util.inspect and Object.keys read). Fields whose type + * has no dyn form are left out. Null when the class has no such field. */ +export function arrayClassFieldsFn(lowerer: Lowerer, info: ClassInfo): IrFunction | null { + const elem = arrayRootOf(info)!.extendsArray!.elem; + const arrT = arrayOf(elem); + const loc = info.def.loc; + const fields = info.def.fields.filter( + (f) => !f.name.startsWith("%") && !f.name.startsWith("#") && lowerer.dynConvertible(f.type), + ); + if (fields.length === 0) return null; + const name = `%${info.def.name}.arrayFields`; + const self = (): IrExpr => + arrayClassView(info, { kind: "varRef", localId: "a.0", type: arrT, loc }, loc); + const value: IrExpr = { + kind: "dynObjLit", + fields: fields.map((f) => ({ + key: { kind: "strLit", value: f.name, type: STRING, loc }, + value: lowerer.coerceToExpected( + { + kind: "fieldGet", + obj: self(), + className: info.def.name, + field: f.name, + type: f.type, + loc, + }, + DYN, + ), + })), + type: DYN, + loc, + }; + return { + name, + params: [{ localId: "a.0", name: "a", type: arrT }], + returnType: DYN, + locals: [{ id: "a.0", name: "a", type: arrT, mutable: false }], + body: [{ kind: "return", value, loc }], + loc, + }; +} + +/** `%C.species(n, withLength)`: `new C(n)`, or `new C()` without + * `withLength`, as ArraySpeciesCreate, Array.of and Array.from call it (the + * descriptor's `species`). Null when the constructor's parameters cannot + * take those arguments: they must be none, a forwarding rest (dyn or + * number), or a number first parameter whose others may be omitted (whose + * no-argument form throws). */ +export function arrayClassSpeciesFn(lowerer: Lowerer, info: ClassInfo): IrFunction | null { + const elem = arrayRootOf(info)!.extendsArray!.elem; + const arrT = arrayOf(elem); + const loc = info.def.loc; + const n = (): IrExpr => ({ kind: "varRef", localId: "n.0", type: F64, loc }); + const params = info.ctorParams; + // The constructor arguments with the length and without it (null: that + // form throws a TypeError). + let withLength: IrExpr[] | null = null; + let without: IrExpr[] | null = null; + if (params.length === 0) { + withLength = []; + without = []; + } else if (params.length === 1 && params[0]!.mode === "dynRest") { + withLength = [ + { + kind: "dynArrLit", + elems: [{ kind: "dynFrom", value: n(), type: DYN, loc }], + type: DYN, + loc, + }, + ]; + without = [{ kind: "dynArrLit", elems: [], type: DYN, loc }]; + } else if ( + params.length === 1 && + params[0]!.mode === "rest" && + params[0]!.type.kind === "array" && + params[0]!.type.elem.kind === "f64" + ) { + withLength = [{ kind: "arrayLit", elems: [n()], type: params[0]!.type, loc } as IrExpr]; + without = [{ kind: "arrayLit", elems: [], type: params[0]!.type, loc } as IrExpr]; + } else if ( + params[0]!.mode === "required" && + (params[0]!.type.kind === "f64" || params[0]!.type.kind === "dyn") && + params.slice(1).every((p) => p.mode === "omittable") + ) { + const rest = params + .slice(1) + .map((p) => + lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } as IrExpr, + p.type, + ), + ); + // A dyn first parameter (a JavaScript `constructor(size)`) takes the + // length as a number, and undefined in the no-argument form. + if (params[0]!.type.kind === "dyn") without = [dynUndefinedExpr(loc), ...rest.map((e) => e)]; + withLength = [ + params[0]!.type.kind === "dyn" ? { kind: "dynFrom", value: n(), type: DYN, loc } : n(), + ...params + .slice(1) + .map((p) => + lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } as IrExpr, + p.type, + ), + ), + ]; + } + if (withLength === null) return null; + const construct = (args: IrExpr[]): IrStmt => ({ + kind: "return", + value: arrayOfClassInstance( + info, + { + kind: "new", + className: info.def.name, + args, + type: { kind: "object", className: info.def.name }, + loc, + }, + loc, + ), + loc, + }); + const unsupported: IrStmt = { + kind: "exprStmt", + expr: nodeThrowExpr( + 1, + "", + `constructing ${info.def.jsName ?? info.def.name} without arguments is not supported yet`, + DYN, + loc, + ), + loc, + }; + return { + name: `%${info.def.name}.species`, + params: [ + { localId: "n.0", name: "n", type: F64 }, + { localId: "w.0", name: "withLength", type: BOOL }, + ], + returnType: arrT, + locals: [ + { id: "n.0", name: "n", type: F64, mutable: false }, + { id: "w.0", name: "withLength", type: BOOL, mutable: false }, + ], + body: [ + { + kind: "if", + cond: { kind: "varRef", localId: "w.0", type: BOOL, loc }, + then: [construct(withLength)], + else_: [without !== null ? construct(without) : unsupported], + loc, + }, + ], + loc, + }; +} + +/** Whether the program declares a class that extends Array. Array methods + * consult species only then. */ +export function programDeclaresArrayClass(lowerer: Lowerer): boolean { + for (const info of lowerer.classes.values()) if (info.extendsArray) return true; + return false; +} + +/** The Array methods that create their result through ArraySpeciesCreate: + * whether the species constructor receives the result's length (map, + * slice, splice; the others pass 0) and whether the length is set after + * the elements are defined (slice, splice, concat). */ +const SPECIES_METHODS: ReadonlyMap = new Map( + [ + ["map", { resultLength: true, setLength: false }], + ["filter", { resultLength: false, setLength: false }], + ["slice", { resultLength: true, setLength: true }], + ["splice", { resultLength: true, setLength: true }], + ["concat", { resultLength: false, setLength: true }], + ["flat", { resultLength: false, setLength: false }], + ["flatMap", { resultLength: false, setLength: false }], + ], +); + +/** An Array method call whose result an Array subclass instance creates + * through its species (scr_arr_species_adopt): the receiver is evaluated + * once into a hidden local that the method's lowering reads, and the + * method's plain result passes through the species constructor when the + * receiver is a subclass instance. Programs that declare no Array subclass + * keep the plain lowering. */ +export function lowerWithArraySpecies( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, + lower: () => IrExpr | null, +): IrExpr | null { + const mode = SPECIES_METHODS.get(access.name.text); + if (mode === undefined || !programDeclaresArrayClass(lowerer)) return lower(); + const receiverType = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); + if (receiverType?.kind !== "array") return lower(); + const loc = locOf(call); + const receiver = lowerer.lowerExpr(access.expression); + if (receiver.type.kind !== "array") return lower(); + const local = lowerer.declareHiddenLocal("%speciesReceiver", receiver.type); + const key = stripTypeWrappers(access.expression); + lowerer.stagedArguments.set(key, { localId: local.id, type: receiver.type }); + let result: IrExpr | null; + try { + result = lower(); + } finally { + lowerer.stagedArguments.delete(key); + } + if (result === null || result.type.kind !== "array") return result; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: receiver, loc }], + result: { + kind: "libCall", + fn: "array.speciesAdopt", + args: [ + { kind: "varRef", localId: local.id, type: receiver.type, loc }, + result, + { kind: "numLit", value: mode.resultLength ? -1 : 0, type: F64, loc }, + { kind: "boolLit", value: mode.setLength, type: BOOL, loc }, + ], + type: result.type, + loc, + }, + type: result.type, + loc, + }; +} + +/** `C.of(...items)` and `C.from(iterable)` on a class declared `extends + * Array` that declares neither: Array.of / Array.from's elements, in an + * instance the class constructs (`new C(items.length)` for of, `new C()` + * for an iterable's from), as Node's inherited statics do. */ +export function lowerArrayClassStaticCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + const name = access.name.text; + if (name !== "of" && name !== "from") return null; + if (!ts.isIdentifier(access.expression)) return null; + const symbol = lowerer.resolveValueSymbol(access.expression); + const decl = symbol?.valueDeclaration; + if (decl === undefined || !ts.isClassLikeDeclaration(decl)) return null; + const info = arrayClassOfDecl(lowerer, decl); + if (info === null || lowerer.findStaticOn(info, name)) return null; + const loc = locOf(call); + const args = call.arguments.nodes; + let ctorLength: number; + if (name === "of") { + if (args.some(ts.isSpreadElement)) + lowerer.unsupported("SC1090", call, "spreading arguments into Array.of on an Array subclass"); + ctorLength = args.length; + } else { + const source = args[0] ? lowerer.mapTypeOf(lowerer.typeOf(args[0])) : null; + if ( + source?.kind !== "array" && + source?.kind !== "string" && + source?.kind !== "set" && + source?.kind !== "map" + ) + lowerer.unsupported( + "SC1090", + call, + "Array.from on an Array subclass over a value that is not an array, string, Set or Map", + ); + ctorLength = -1; + } + const plain = + name === "of" + ? lowerArrayOfCall(lowerer, call, access, true) + : lowerArrayFromCall(lowerer, call, access, true); + if (plain === null || plain.type.kind !== "array") return plain; + return { + kind: "libCall", + fn: "array.classConstructAdopt", + args: [ + { kind: "strLit", value: info.def.name, type: STRING, loc }, + plain, + { kind: "numLit", value: ctorLength, type: F64, loc }, + ], + type: plain.type, + loc, + }; +} + +/** A class declared `extends Array` that defines `static [Symbol.species]` + * (Node would construct method results through that constructor). */ +export function refuseArrayClassSpecies(lowerer: Lowerer, decl: ts.ClassLikeDeclaration): void { + for (const member of decl.members.nodes) { + const name = member.name; + if ( + name !== undefined && + ts.isComputedPropertyName(name) && + ts.isPropertyAccessExpression(name.expression) && + name.expression.name.text === "species" && + ts.isIdentifier(name.expression.expression) && + name.expression.expression.text === "Symbol" + ) + lowerer.unsupported( + "SC1090", + member, + "Symbol.species on a class that extends Array (its methods' results construct the class itself)", + ); + } +} diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 1f10640c0..284f87104 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -498,12 +498,15 @@ export function lowerArrayOfCall( lowerer: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression, + /** The receiver is a class declared `extends Array` (array-classes.ts + * constructs the result through it). */ + arrayClassReceiver = false, ): IrExpr | null { if ( call.questionDotToken || access.questionDotToken || access.name.text !== "of" || - !lowerer.isStdlibGlobal(access.expression, "Array") + (!arrayClassReceiver && !lowerer.isStdlibGlobal(access.expression, "Array")) ) return null; let result = lowerer.mapTypeOf(lowerer.typeOf(call)); @@ -856,9 +859,11 @@ export function lowerArrayFromCall( lowerer: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression, + /** The receiver is a class declared `extends Array` (array-classes.ts). */ + arrayClassReceiver = false, ): IrExpr | null { if (call.questionDotToken || access.questionDotToken) return null; - if (!lowerer.isStdlibGlobal(access.expression, "Array")) return null; + if (!arrayClassReceiver && !lowerer.isStdlibGlobal(access.expression, "Array")) return null; if (access.name.text !== "from") return null; const loc = locOf(call); const args = call.arguments.nodes; diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index 00a23caf5..dbe89544e 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -773,6 +773,12 @@ export function ensureString(lowerer: Lowerer, e: IrExpr, node: ts.Node): IrExpr isUnitType(a), ) ?? false); + // Checked-dynamic elements: the runtime's String() over the array's + // dyn form (its join, element by element). + if (elem.kind === "dyn") { + const boxed: IrExpr = { kind: "dynFrom", value: e, type: DYN, loc: e.loc }; + return { kind: "toString", operand: boxed, type: STRING, loc: e.loc }; + } if ( elem.kind === "f64" || elem.kind === "string" || diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 8148e85f5..9d76a0f17 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -1,4 +1,9 @@ /* Property reads (`a.b`): lowerExprInner's property-access branch, intrinsic and union properties, field reads and targets, and match-group reads. */ +import { + arrayClassMemberOwner, + arrayClassView, + lowerArrayClassConstructorRead, +} from "../builtins/array-classes.js"; import { keyedHelperName } from "../stable-names.js"; import { staticallySelected } from "../static-typeof.js"; import * as ts from "../../ts7/adapter.js"; @@ -290,6 +295,8 @@ export function lowerPropertyAccessExpr( expr: ts.PropertyAccessExpression, loc: SrcLoc, ): IrExpr { + const arrayClassConstructor = lowerArrayClassConstructorRead(lowerer, expr, loc); + if (arrayClassConstructor !== null) return arrayClassConstructor; // `value.#x` on a checked-dynamic value, or on a checked extraction of // one: the field of the brand-checked receiver (checkedPrivateValue). if (ts.isPrivateIdentifier(expr.name)) { @@ -3717,6 +3724,31 @@ export function fieldTarget( ); } } + // A field declared by a class that extends Array, on an instance (whose + // type maps to its array): the class object's field. + { + const owner = arrayClassMemberOwner(lowerer, access); + const name = access.name.text; + if ( + owner !== null && + (owner.fields.has(name) || + lowerer.findMethodOn(owner, `get:${name}`) || + lowerer.findMethodOn(owner, `set:${name}`)) + ) { + const value = lowerer.lowerExpr(access.expression); + const receiver = + value.type.kind === "array" + ? arrayClassView(owner, value, locOf(access.expression)) + : value; + return classFieldTarget( + lowerer, + access.expression, + { kind: "object", className: owner.def.name }, + access.name.text, + receiver, + ); + } + } const stored = ts.isIdentifier(access.expression) ? (lowerer.peekLocal(access.expression)?.type ?? lowerer.globalOf(access.expression)?.type) : undefined; diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 1651bf0d9..08987e538 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -1,4 +1,5 @@ /* instanceof and in, including private-name and runtime-key in. */ +import { lowerArrayClassInstanceOf } from "../builtins/array-classes.js"; import { dateRootOf } from "../builtins/date-classes.js"; import { keyedHelperName } from "../stable-names.js"; import * as ts from "../../ts7/adapter.js"; @@ -106,6 +107,8 @@ const NO_CLASS_INSTANCE_KINDS: ReadonlySet = new Set([ * Folding is limited to side-effect-free operands — dropping a computed * operand would skip its effects, so those are rejected instead. */ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc: SrcLoc): IrExpr { + const arrayClassTest = lowerArrayClassInstanceOf(lowerer, expr, loc); + if (arrayClassTest !== null) return arrayClassTest; const webAssembly = lowerWebAssemblyInstanceof(lowerer, expr); if (webAssembly !== null) return webAssembly; refuseWebAssemblyUse(lowerer, expr.right); diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 8ac97fd32..2be259226 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1,3 +1,8 @@ +import { + arrayClassMemberOwner, + arrayClassView, + lowerArrayClassStaticCall, +} from "./builtins/array-classes.js"; import { keyedHelperName } from "./stable-names.js"; import { perKeyOrder } from "./key-order-witness.js"; import { isForwardedTupleSpread, lowerForwardingApply } from "./typed-arguments.js"; @@ -1669,6 +1674,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { lowerIteratorStaticFence(lowerer, expr, expr.expression) ?? lowerObjectStaticCall(lowerer, expr, expr.expression) ?? lowerObjectFromEntriesCall(lowerer, expr, expr.expression) ?? + lowerArrayClassStaticCall(lowerer, expr, expr.expression) ?? lowerArrayFromCall(lowerer, expr, expr.expression) ?? lowerArrayFromAsyncCall(lowerer, expr, expr.expression) ?? lowerArrayOfCall(lowerer, expr, expr.expression) ?? @@ -10075,6 +10081,12 @@ export function lowerObjectMethodCall( ? probe : null; if (specializedReceiver) mappedReceiver = specializedReceiver.type; + // A member declared by a class that extends Array, called on an instance + // (whose type maps to its array): the class's own method. + const arrayClassOwner = + mappedReceiver?.kind === "array" ? arrayClassMemberOwner(lowerer, access) : null; + if (arrayClassOwner !== null) + mappedReceiver = { kind: "object", className: arrayClassOwner.def.name }; if (mappedReceiver?.kind === "union") { const dispatched = lowerUnionObjectMethodCall(lowerer, call, access, mappedReceiver) ?? @@ -10108,6 +10120,12 @@ export function lowerObjectMethodCall( : null; if (receiverIr === null) return null; const lowerReceiver = (): IrExpr => { + if (arrayClassOwner !== null) { + const value = lowerer.lowerExpr(access.expression); + return value.type.kind === "array" + ? arrayClassView(arrayClassOwner, value, locOf(access.expression)) + : value; + } const receiver = specializedReceiver ?? lowerer.lowerExpr(access.expression); const optional = lowerer.runtimeOptionalPropertyReceiver( access.expression, diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 3e49304d2..dfc27dd86 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -7,6 +7,14 @@ import { lowerDateSuperMethodCall, } from "./builtins/date-classes.js"; import { predeclareForwardClass } from "./lower-stmts.js"; +import { + arrayClassBase, + arrayClassFieldsFn, + arrayClassSpeciesFn, + arraySuperCall, + refuseArrayClassSpecies, + lowerArraySuperCall, +} from "./builtins/array-classes.js"; import { lowerDateStaticCall } from "./builtins/dates.js"; import { keyedHelperName } from "./stable-names.js"; import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js"; @@ -590,6 +598,10 @@ export interface ClassInfo { * constructor's rules, and the Date methods it does not declare lower * over that value. Subclasses inherit the field. */ extendsDate?: true; + /** A class declared `extends Array` (builtins/array-classes.ts): its + * instances are arrays of `elem` whose own fields follow the array + * header, and its TypeScript type maps to `elem[]`. */ + extendsArray?: { elem: IrType }; /** The synthetic LifetimeRegion class (@scriptc/experimental, * lifetime-regions.ts): its constructor is synthesized with the class, * and its methods lower at their call sites. */ @@ -1978,6 +1990,7 @@ export function collectClassShapeInner( let genericBase: ts.ExpressionWithTypeArguments | undefined; let callableBase: ClassInfo["callableBase"]; let extendsDate = false; + let extendsArray: { elem: IrType } | undefined; let factoryBaseExpression: ts.Expression | undefined; const adoptCallableBase = (expression: ts.Expression): boolean => { if (familyMode || inst || mixin) return false; @@ -2178,6 +2191,13 @@ export function collectClassShapeInner( if (t && adoptCallableBase(t.expression)) continue; lowerer.unsupported("SC1090", clause, "extending computed expressions"); } + if ((familyMode || inst) && lowerer.isStdlibGlobal(t.expression, "Array")) + lowerer.unsupported("SC1090", t, "a generic class that extends Array"); + if (!familyMode && !inst && !mixin && lowerer.isStdlibGlobal(t.expression, "Array")) { + extendsArray = arrayClassBase(lowerer, t); + refuseArrayClassSpecies(lowerer, decl); + continue; + } if (!familyMode && !inst && !mixin && lowerer.isStdlibGlobal(t.expression, "Date")) { if (t.typeArguments?.nodes) lowerer.unsupported("SC1090", t, "extending generic classes"); extendsDate = true; @@ -2278,6 +2298,12 @@ export function collectClassShapeInner( continue; } if (t.typeArguments?.nodes) lowerer.unsupported("SC1090", t, "extending generic classes"); + if (named?.extendsArray) + lowerer.unsupported( + "SC1090", + t, + `extending '${t.expression.text}', a class that extends Array (one level of Array subclass compiles)`, + ); base = named; if (!base) { if (adoptCallableBase(t.expression)) continue; @@ -4384,7 +4410,7 @@ export function collectClassShapeInner( ctor && !mixinForwarding ? lowerer.paramShapes(ctor.parameters.nodes) : (base?.ctorParams ?? - (callableBase || extendsDate ? [{ type: DYN, mode: "dynRest" }] : [])); + (callableBase || extendsDate || extendsArray ? [{ type: DYN, mode: "dynRest" }] : [])); if ( ctor && !mixinForwarding && @@ -4439,6 +4465,13 @@ export function collectClassShapeInner( return am.length > 0 ? { abstractMethods: am } : {}; })(), ...(inst ? { genericOf: inst.family.def.name } : {}), + ...(extendsArray + ? { + arrayElem: extendsArray.elem, + arrayFields: `%${className}.arrayFields`, + arraySpecies: `%${className}.species`, + } + : {}), loc: locOf(decl), }, fields, @@ -4455,6 +4488,7 @@ export function collectClassShapeInner( ...(genericBase ? { genericBase } : {}), ...(callableBase ? { callableBase } : {}), ...(extendsDate ? { extendsDate: true as const } : {}), + ...(extendsArray ? { extendsArray } : {}), ...(factoryBaseExpression ? { factoryBaseExpression } : {}), subclasses: [], throwingSetters, @@ -7926,6 +7960,12 @@ export function lowerClassMembers(lowerer: Lowerer, info: ClassInfo): IrFunction const diagsBefore = lowerer.diags.length; try { out.push(lowerer.lowerClassCtor(info)); + if (info.extendsArray) { + const fields = arrayClassFieldsFn(lowerer, info); + if (fields) out.push(fields); + const species = arrayClassSpeciesFn(lowerer, info); + if (species) out.push(species); + } } catch (e) { if (!(e instanceof PoisonError)) throw e; if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { @@ -7974,6 +8014,10 @@ export function lowerClassMembers(lowerer: Lowerer, info: ClassInfo): IrFunction * the base's signature — forward every param to super(), then run own * field initializers. */ export function lowerClassCtor(lowerer: Lowerer, info: ClassInfo): IrFunction { + if (info.extendsArray) { + lowerer.noteEdge(`%${info.def.name}.arrayFields`); + lowerer.noteEdge(`%${info.def.name}.species`); + } const cached = info.localClass?.bodies.get(`%${info.def.name}.constructor`); if (cached) return cached; return withInstanceBindings(lowerer, info, () => lowerClassCtorInner(lowerer, info)); @@ -8041,7 +8085,7 @@ function lowerClassCtorInner(lowerer: Lowerer, info: ClassInfo): IrFunction { lowerer.ctx.argumentsLocal = local; } body.push(...declared.prologue); - if (!ctorBase && !info.callableBase && !info.extendsDate) { + if (!ctorBase && !info.callableBase && !info.extendsDate && !info.extendsArray) { // Node's base-class order: field initializers run at the start // of construction, the parameter-property assignments open the // constructor body (probed — a field initializer reading a @@ -8053,7 +8097,7 @@ function lowerClassCtorInner(lowerer: Lowerer, info: ClassInfo): IrFunction { body.push(...lowerer.lowerDerivedCtorBody(info, thisLocal)); } } else { - if (ctorBase || info.callableBase || info.extendsDate) { + if (ctorBase || info.callableBase || info.extendsDate || info.extendsArray) { // Synthetic forwarding params (the inherited ABI signature). // Nothing references them by symbol — only the super call below, // which forwards the already-completed values UNCHANGED (defaults @@ -8972,7 +9016,9 @@ export function lowerDerivedCtorBody( loc, }, ]; - if (info.callableBase || info.extendsDate) { + if (info.extendsArray) { + statements.push(...lowerArraySuperCall(lowerer, info, thisLocal, superCall)); + } else if (info.callableBase || info.extendsDate) { if (superCall.arguments.nodes.some(ts.isSpreadElement)) lowerer.unsupported( "SC1090", @@ -9165,6 +9211,7 @@ export function superCallStmt( loc, }; if (info.extendsDate) return dateSuperCall(thisRef, info, args[0]!, loc); + if (info.extendsArray) return arraySuperCall(info, thisRef, args[0]!, loc); if (info.callableBase) return { kind: "exprStmt", diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index c790581d8..e178de2eb 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -1,3 +1,8 @@ +import { + arrayClassMemberOwner, + arrayOfClassInstance, + arrayRootOf, +} from "./builtins/array-classes.js"; import { keyedHelperName } from "./stable-names.js"; import { isNewTarget, @@ -499,7 +504,23 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // noImplicitThis, so the generic scope walk here is preflight-safe. const local = lowerer.resolveThis(expr); if (local) { - const value: IrExpr = { kind: "varRef", localId: local.id, type: local.type, loc }; + let value: IrExpr = { kind: "varRef", localId: local.id, type: local.type, loc }; + // `this` in a class declared `extends Array` is the array, except as + // the receiver of a member the class declares. + if (local.type.kind === "object") { + const arrayClass = lowerer.classes.get(local.type.className); + const parent = expr.parent; + if ( + arrayClass && + arrayRootOf(arrayClass) && + !( + ts.isPropertyAccessExpression(parent) && + parent.expression === expr && + arrayClassMemberOwner(lowerer, parent) !== null + ) + ) + value = arrayOfClassInstance(arrayClass, value, loc); + } // `this.#x` in a function-local class's prototype method, whose `this` // is the call-site value: the field lives on the native instance of // this evaluation (checkedPrivateReceiver). @@ -575,7 +596,24 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // any other super position (field reads, bare references) stays out. lowerer.unsupported("SC1090", expr, "'super' outside super() and super.method() calls"); } - if (ts.isNewExpression(expr)) return lowerer.lowerNew(expr); + if (ts.isNewExpression(expr)) { + const value = lowerer.lowerNew(expr); + // `new X()` for a class declared `extends Array` is the array, except + // as the receiver of a member the class declares (as `this` above). + if (value.type.kind !== "object") return value; + const arrayClass = lowerer.classes.get(value.type.className); + if (!arrayClass || !arrayRootOf(arrayClass)) return value; + let operand: ts.Expression = expr; + while (ts.isParenthesizedExpression(operand.parent)) operand = operand.parent; + const parent = operand.parent; + if ( + ts.isPropertyAccessExpression(parent) && + parent.expression === operand && + arrayClassMemberOwner(lowerer, parent) !== null + ) + return value; + return arrayOfClassInstance(arrayClass, value, loc); + } if (ts.isAwaitExpression(expr)) { if (!lowerer.ctx.isAsync) { // A tsc-clean occurrence here is outside both an async function diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index b0139aba9..b69805adf 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -949,6 +949,12 @@ export function moduleArtifacts( visitClass("%Readable"); visitClass("%Writable"); } + // A class declared `extends Array` named by its instanceof test + // or by X.of / X.from. + if (node.fn === "array.dynIsClass" && node.args[1]?.kind === "strLit") + visitClass(node.args[1].value); + if (node.fn === "array.classConstructAdopt" && node.args[0]?.kind === "strLit") + visitClass(node.args[0].value); break; case "dynFrom": if (boxedChild(node.value.type)) { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 14af97de7..0845826d2 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,10 @@ +import { + arrayClassFieldsFn, + arrayClassSpeciesFn, + arrayOfClassInstance, + arrayRootOf, + lowerWithArraySpecies, +} from "./builtins/array-classes.js"; import { isDeclBoundaryFile } from "../decl-projection.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; import { moduleNamespaceObject } from "./module-namespace-elements.js"; @@ -8129,6 +8136,20 @@ export class Lowerer { lower: () => info.localClass && !info.localClass.ready ? null : this.lowerClassCtor(info), }); + // The descriptor helpers of a class that extends Array, reached + // from its constructor. + if (info.extendsArray) { + units.set(`%${cName}.arrayFields`, { + order: unitOrder++, + roots: [], + lower: () => arrayClassFieldsFn(this, info), + }); + units.set(`%${cName}.species`, { + order: unitOrder++, + roots: [], + lower: () => arrayClassSpeciesFn(this, info), + }); + } for (const { mName, member } of this.classMethodMembers(info)) { units.set(`%${cName}.${mName}`, { order: unitOrder++, @@ -8244,6 +8265,10 @@ export class Lowerer { register(`%${cName}.constructor`, classCtorRoots(info), () => info.localClass && !info.localClass.ready ? null : this.lowerClassCtor(info), ); + if (info.extendsArray) { + register(`%${cName}.arrayFields`, [], () => arrayClassFieldsFn(this, info)); + register(`%${cName}.species`, [], () => arrayClassSpeciesFn(this, info)); + } for (const { mName, member } of this.classMethodMembers(info)) { register(`%${cName}.${mName}`, functionRoots(member), () => info.localClass && !info.localClass.ready @@ -9608,6 +9633,18 @@ export class Lowerer { const result = this.coerceToExpected(expr.result, expected); return { ...expr, result, type: result.type }; } + // An instance of a class declared `extends Array` is its array (the + // same object); an array slot of another element type converts it as + // an array. + if (expr.type.kind === "object" && expected.kind !== "object") { + const arrayClass = this.classes.get(expr.type.className); + if (arrayClass && arrayRootOf(arrayClass)) { + const asArray = arrayOfClassInstance(arrayClass, expr, expr.loc); + return typeEquals(asArray.type, expected) + ? asArray + : this.coerceToExpected(asArray, expected); + } + } // A destination that holds undefined takes an unassigned deferred-init // field's slot as is (Node passes the undefined along). if (expected.kind === "union" && this.armTag(expected.unionId, UNDEFINED_T) >= 0) { @@ -13315,7 +13352,9 @@ export class Lowerer { call: ts.CallExpression, access: ts.PropertyAccessExpression, ): IrExpr | null { - return lowerArrayMethodCall(this, call, access); + return lowerWithArraySpecies(this, call, access, () => + lowerArrayMethodCall(this, call, access), + ); } lowerMapMethodCall(call: ts.CallExpression, access: ts.PropertyAccessExpression): IrExpr | null { diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index fc159116f..4273eace0 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1779,6 +1779,36 @@ export function isFetchHandleType( return handle; } +/** The array type a class declared `extends Array` maps to (its base type, + * `T[]`), or null for every other class. */ +function arrayClassBaseType( + classDecl: ts.ClassLikeDeclaration, + instance: ts.Type, + ctx: TypeMapperCtx, +): IrType | null { + const heritage = classDecl.heritageClauses?.nodes.find( + (clause) => clause.token === ts.SyntaxKind.ExtendsKeyword, + )?.types?.nodes[0]; + if (heritage === undefined || !ts.isIdentifier(heritage.expression)) return null; + if (heritage.expression.text !== "Array") return null; + // The polymorphic `this` type of the class names the class's own type. + const symbol = instance.getSymbol(); + const declared = + (instance.flags & ts.TypeFlags.TypeParameter) !== 0 && symbol !== undefined + ? ctx.checker.getDeclaredTypeOfSymbol(symbol) + : instance; + if ((declared.flags & ts.TypeFlags.Object) === 0) return null; + const target = declared.isTypeReference() ? (declared.getTarget() ?? declared) : declared; + const bases = ctx.checker.getBaseTypes(target as ts.InterfaceType); + const base = bases[0]; + if (base === undefined || !ctx.checker.isArrayType(base)) return null; + const mapped = mapType(base, ctx); + if (mapped?.kind === "array") return mapped; + // `extends Array` in JavaScript reads `any[]`: dyn elements (the class's + // arrayClassBase). + return isJsSourceFile(ts.getSourceFileOfNode(classDecl)) ? { kind: "array", elem: DYN } : null; +} + export function mapType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if (mapTypeDepth >= MAP_TYPE_MAX_DEPTH) return null; // These intrinsic domains cannot depend on generic bindings, declaration @@ -2546,6 +2576,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return viaMixin; } } + // A class declared `extends Array` (frontend/lowering/builtins/ + // array-classes.ts): its instances are arrays of the base's elements. + { + const arrayBase = arrayClassBaseType(classDecl, widened, ctx); + if (arrayBase !== null) return arrayBase; + } if (classExprNeedsContext(classDecl)) { const local = ctx.localClassInstance?.(classDecl, widened); if (local) contextResolutions++; diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index ac7413c6a..db7dc81c3 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3891,6 +3891,23 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ // The string keys `for...in` visits on an array (args[0], any element // type) after its indices. Borrowed; never throws. "array.namedKeys": { argTypes: [null], result: arrayOf(STRING) }, + /** super(...) in a class declared `extends Array`: the instance, the + * arguments (an array of its element type, or the implicit constructor's + * dyn argument array) and whether the elements are dyn values. */ + "array.subclassConstruct": { argTypes: [null, null, BOOL], result: VOID }, + "array.subclassConstructDyn": { argTypes: [null, DYN, BOOL], result: VOID }, + /** ArraySpeciesCreate over an Array method's plain result: the receiver + * (borrowed), the result (moved), the species constructor's length + * argument (-1: the result's length) and whether to set the length. The + * result has the second argument's type. */ + "array.speciesAdopt": { argTypes: [null, null, F64, BOOL], result: VOID }, + /** Array.of / Array.from on a class declared `extends Array`: the class + * (its name, a literal), the plain result (moved) and the constructor's + * length argument (negative: no argument). */ + "array.classConstructAdopt": { argTypes: [STRING, null, F64], result: VOID }, + /** `value instanceof C` for a dyn value and a class that extends Array + * (its name, a literal). */ + "array.dynIsClass": { argTypes: [DYN, STRING], result: BOOL }, // Whether the array (args[0]) still has the key (args[1]) from its // namedKeys list. Borrowed; never throws. "array.hasNamedKey": { argTypes: [null, STRING], result: BOOL }, diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index 91e1d5f05..33d68c621 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -157,6 +157,8 @@ const MODULE_FIELDS: Record>, Role> = { "classes[].errorNameAfter": "other", "classes[].prototypeDataHelper": "reference", "classes[].instancePrototypeHelper": "reference", + "classes[].arrayFields": "reference", + "classes[].arraySpecies": "reference", "classes[].localCaptures[].name": "other", "classes[].localCaptures[].localId": "other", "classes[].symbolFields[].field": "other", @@ -281,6 +283,14 @@ const REFERENCE_FIXTURES: Record IrModule> = { ...module(), classes: [{ name: "%m0.Cell", fields: [], instancePrototypeHelper: TARGET, loc }], }), + "classes[].arrayFields": () => ({ + ...module(), + classes: [{ name: "%m0.Row", fields: [], arrayElem: F64, arrayFields: TARGET, loc }], + }), + "classes[].arraySpecies": () => ({ + ...module(), + classes: [{ name: "%m0.Row", fields: [], arrayElem: F64, arraySpecies: TARGET, loc }], + }), "lib.exports[].fnName": () => ({ ...module(), lib: { diff --git a/packages/compiler/src/ir/function-references.ts b/packages/compiler/src/ir/function-references.ts index db474e9bb..da05e854c 100644 --- a/packages/compiler/src/ir/function-references.ts +++ b/packages/compiler/src/ir/function-references.ts @@ -24,6 +24,8 @@ export function moduleFunctionReferences(mod: IrModule): string[] { for (const cls of mod.classes ?? []) { if (cls.prototypeDataHelper !== undefined) names.push(cls.prototypeDataHelper); if (cls.instancePrototypeHelper !== undefined) names.push(cls.instancePrototypeHelper); + if (cls.arrayFields !== undefined) names.push(cls.arrayFields); + if (cls.arraySpecies !== undefined) names.push(cls.arraySpecies); } return names; } diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 550333edd..b9849152c 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1508,6 +1508,17 @@ export interface IrClassDef { * RUNTIME_ERROR_CLASSES). User subclasses are ordinary emitted classes * whose layout prefix embeds ScrError's fields. */ runtime?: true; + /** A class declared `extends Array`: its instances are arrays of this + * element type. The struct begins with the runtime's array header (the + * reference count is the array's, field 0) and the class's fields follow + * it; the runtime finds the class through the array's ext. */ + arrayElem?: IrType; + /** The function (array) => dyn listing such a class's own fields + * (ScrArrClass.fields); absent when it declares none. */ + arrayFields?: string; + /** The function (length) => array constructing such a class for + * ArraySpeciesCreate (ScrArrClass.species). */ + arraySpecies?: string; /** Base class name (single inheritance, `extends`). A class is IN A * HIERARCHY when it has a base or is some class's base; hierarchy classes * carry a vtable word after `rc` (backends), standalone classes are laid diff --git a/packages/compiler/src/ir/traverse.ts b/packages/compiler/src/ir/traverse.ts index 80425a056..e83bf5c42 100644 --- a/packages/compiler/src/ir/traverse.ts +++ b/packages/compiler/src/ir/traverse.ts @@ -140,6 +140,7 @@ export function everyModuleNode(mod: IrModule, visitor: IrModuleVisitor): boolea for (const cls of mod.classes ?? []) { for (const capture of cls.localCaptures ?? []) if (!type(capture.type, cls.loc)) return false; for (const field of cls.fields) if (!type(field.type, cls.loc)) return false; + if (cls.arrayElem !== undefined && !type(cls.arrayElem, cls.loc)) return false; } for (const global of mod.globals ?? []) if (!type(global.type, global.source?.loc ?? entryLoc)) return false; diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index d7eee18bf..0dea09d1e 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -2993,6 +2993,18 @@ function validateFunction( } break; } + // An instance of a class declared `extends Array` and its array + // are the same object: upcast to the array, downcast back. + { + const instance = e.kind === "upcast" ? e.value.type : e.type; + const array = e.kind === "upcast" ? e.type : e.value.type; + if (instance.kind === "object" && array.kind === "array") { + const elem = classes.get(instance.className)?.arrayElem; + if (elem === undefined || !typeEquals(elem, array.elem)) + err(`${e.kind}: "${instance.className}" is not an array of that element type`, e.loc); + break; + } + } if (e.type.kind !== "object" || e.value.type.kind !== "object") { err(`${e.kind} between non-class types`, e.loc); break; @@ -3100,6 +3112,15 @@ function validateFunction( case "instanceOf": { checkExpr(e.value); if (e.type.kind !== "bool") err("instanceOf must be bool", e.loc); + // An array against a class declared `extends Array` (its ext). + if (e.value.type.kind === "array") { + if (classes.get(e.className)?.arrayElem === undefined) + err( + `instanceOf of an array against "${e.className}", which does not extend Array`, + e.loc, + ); + break; + } if (!hierarchy.has(e.className)) { err(`instanceOf against non-hierarchy class "${e.className}"`, e.loc); } @@ -4190,6 +4211,28 @@ function validateFunction( } function checkLibValueCall(e: IrExpr & { kind: "libCall" }): boolean { + // ArraySpeciesCreate over an Array method's result, and Array.of / + // Array.from on a class that extends Array: the result has the plain + // result's array type. + if (e.fn === "array.dynIsClass") { + const cls = e.args[1]?.kind === "strLit" ? classes.get(e.args[1].value) : undefined; + if (cls?.arrayElem === undefined) + err("libCall array.dynIsClass must name a class that extends Array", e.loc); + return false; + } + if (e.fn === "array.speciesAdopt" || e.fn === "array.classConstructAdopt") { + const plain = e.args[1]; + if (plain?.type.kind !== "array" || !typeEquals(e.type, plain.type)) + err(`libCall ${e.fn} must have its array argument's type`, e.loc); + if (e.fn === "array.speciesAdopt" && e.args[0]?.type.kind !== "array") + err("libCall array.speciesAdopt receiver must be an array", e.loc); + if (e.fn === "array.classConstructAdopt") { + const cls = e.args[0]?.kind === "strLit" ? classes.get(e.args[0].value) : undefined; + if (cls?.arrayElem === undefined) + err("libCall array.classConstructAdopt must name a class that extends Array", e.loc); + } + return true; + } const cryptoPromise = CRYPTO_PROMISE_VALUES[e.fn]; if (cryptoPromise) { const inner = e.type.kind === "promise" ? e.type.inner : undefined; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7720-array-subclass-basics.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7720-array-subclass-basics.ts.json new file mode 100644 index 000000000..11b1d8d51 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7720-array-subclass-basics.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7720-array-subclass-basics.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7721-array-subclass-species.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7721-array-subclass-species.ts.json new file mode 100644 index 000000000..41bd59eb2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7721-array-subclass-species.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7721-array-subclass-species.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7722-array-subclass-zero-array.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7722-array-subclass-zero-array.js.json new file mode 100644 index 000000000..6cbc69c47 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7722-array-subclass-zero-array.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7722-array-subclass-zero-array.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7723-array-subclass-as-array.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7723-array-subclass-as-array.ts.json new file mode 100644 index 000000000..4671e20ea --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7723-array-subclass-as-array.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7723-array-subclass-as-array.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7724-array-subclass-cycles.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7724-array-subclass-cycles.ts.json new file mode 100644 index 000000000..7956c64c5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7724-array-subclass-cycles.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7724-array-subclass-cycles.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7725-array-subclass-operations.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7725-array-subclass-operations.ts.json new file mode 100644 index 000000000..5d9c82a12 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7725-array-subclass-operations.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7725-array-subclass-operations.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7726-array-subclass-expressions.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7726-array-subclass-expressions.js.json new file mode 100644 index 000000000..7d49ce0ac --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7726-array-subclass-expressions.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7726-array-subclass-expressions.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/array-subclass-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/array-subclass-fences.ts.json new file mode 100644 index 000000000..659372fdb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/array-subclass-fences.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/array-subclass-fences.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index 4c7b84d98..aed1b5c0c 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -1590,8 +1590,8 @@ static inline __attribute__((always_inline)) void scr_arr_free(ScrArr *a) { scr_mem_free(a); } -static void scr_arr_destroy(void *object) { - ScrArr *a = object; +/* Releases an array's elements and ext contents (destroy's first half). */ +static void scr_arr_release_contents(ScrArr *a) { if (scr_arr_ext_of(a)) { scr_dyn_release(scr_arr_ext_of(a)->metadata); scr_arr_ext_of(a)->metadata = NULL; @@ -1657,10 +1657,209 @@ static void scr_arr_destroy(void *object) { } } } +} + +static void scr_arr_destroy(void *object) { + ScrArr *a = object; + scr_arr_release_contents(a); if (a->cyc) scr_arr_gc_free(a); else scr_arr_free(a); } +void scr_arr_subclass_init(ScrArr *a, uint8_t elem, const ScrArrOps *ops, const ScrArrClass *cls, + bool cyc) { + a->len = 0; + a->cap = 0; + a->elem = elem; + a->cyc = cyc ? 1 : 0; + uint32_t id = ops ? scr_arr_ops_id(ops) : 0; + a->spare = (uint16_t)(id << SCR_ARR_OPS_SHIFT); + a->filled = 0; + a->data = NULL; + ScrArrExt *x = scr_arr_ext(a); + if (id == SCR_ARR_OPS_OVERFLOW) x->ops = ops; + x->cls = cls; +#ifdef SCR_RC_AUDIT + scr_live_arrays++; +#endif +} + +/* super(...args) in a class that extends Array, as the Array constructor: + * one number argument sets the length (a RangeError for an invalid length), + * otherwise the arguments become the elements. `args` has the instance's + * element type and is borrowed; `dyn_elems` says its elements are dyn + * values, whose number test reads the value. */ +void scr_arr_subclass_construct(ScrArr *a, ScrArr *args, bool dyn_elems) { + if (args->len == 1) { + if (args->elem == SCR_ELEM_F64 || args->elem == SCR_ELEM_I32) { + scr_arr_set_len(a, scr_arr_get_number(args, 0)); + return; + } + if (dyn_elems) { + const ScrDyn *d = scr_arr_borrow_ref(args, 0); + if (d != NULL && d->kind == SCR_DYN_NUM) { + scr_arr_set_len(a, d->v.num); + return; + } + } + } + if (args->len > 0) (void)scr_arr_push_spread(a, args); +} + +/* The implicit constructor of a class that extends Array forwards its + * arguments (a dyn array) to super: the same rule, each argument converted + * to the element type. Element types other than numbers, booleans, strings + * and dyn values take only the length form. */ +void scr_arr_subclass_construct_dyn(ScrArr *a, const ScrDyn *args, bool dyn_elems) { + if (args == NULL || args->kind != SCR_DYN_ARR) return; + size_t n = args->v.arr.len; + ScrDyn *const *items = args->v.arr.items; + if (n == 1 && items[0] != NULL && items[0]->kind == SCR_DYN_NUM) { + scr_arr_set_len(a, items[0]->v.num); + return; + } + for (size_t i = 0; i < n; i++) { + ScrDyn *d = items[i]; + if (dyn_elems) { + (void)scr_arr_push_ref(a, scr_dyn_retain(d)); + continue; + } + if ((a->elem == SCR_ELEM_F64 || a->elem == SCR_ELEM_I32) && d && d->kind == SCR_DYN_NUM) { + (void)scr_arr_push_f64(a, d->v.num); + } else if (a->elem == SCR_ELEM_BOOL && d && d->kind == SCR_DYN_BOOL) { + (void)scr_arr_push_bool(a, d->v.b); + } else if (a->elem == SCR_ELEM_STR && d && d->kind == SCR_DYN_STR) { + (void)scr_arr_push_ref(a, scr_str_retain(d->v.str)); + } else { + static const char msg[] = + "constructing this Array subclass with element arguments is not supported yet"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + return; + } + } +} + +static void scr_arr_set_slot(ScrArr *a, double i, uint64_t slot); + +static ScrArr *scr_arr_species_fill(const ScrArrClass *cls, ScrArr *result, double ctor_length, + bool with_length, bool set_length); + +/* Gives an Array subclass instance holding no elements (only holes) another + * element type: its storage is dropped and its length kept. */ +static void scr_arr_retype_empty(ScrArr *a, uint8_t elem, const ScrArrOps *ops) { + uint32_t len = a->len; + ScrArrExt *x = scr_arr_ext_of(a); + const ScrArrClass *cls = x->cls; + scr_arr_free_parts(a); + a->data = NULL; + a->len = a->cap = a->filled = 0; + a->elem = elem; + uint32_t id = ops ? scr_arr_ops_id(ops) : 0; + a->spare = (uint16_t)(id << SCR_ARR_OPS_SHIFT); + x = scr_arr_ext(a); + if (id == SCR_ARR_OPS_OVERFLOW) x->ops = ops; + x->cls = cls; + scr_arr_set_len(a, (double)len); +} + +ScrArr *scr_arr_species_adopt(ScrArr *recv, ScrArr *result, double ctor_length, bool set_length) { + const ScrArrClass *cls = scr_arr_class_of(recv); + if (cls == NULL) return result; + if (ctor_length < 0) ctor_length = (double)result->len; + return scr_arr_species_fill(cls, result, ctor_length, true, set_length); +} + +ScrArr *scr_arr_class_construct_adopt(const ScrArrClass *cls, ScrArr *result, double ctor_length) { + return scr_arr_species_fill(cls, result, ctor_length < 0 ? 0 : ctor_length, ctor_length >= 0, true); +} + +static ScrArr *scr_arr_species_fill(const ScrArrClass *cls, ScrArr *result, double ctor_length, + bool with_length, bool set_length) { + if (cls->species == NULL) { + static const char msg[] = + "this Array subclass's constructor cannot construct a species result (Symbol.species)"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + return result; + } + ScrArr *species = cls->species(ctor_length, with_length); + if (scr_exc_pending()) { + scr_arr_release(species); + return result; + } + if (species->elem != result->elem || scr_arr_ops(species) != scr_arr_ops(result)) { + /* A result of another element type (map, flatMap): an instance the + * constructor left without elements takes the result's element type; + * its length stays. An instance whose collector header the elements + * would need, or that already holds elements, cannot. */ + const ScrArrOps *ops = scr_arr_ops(result); + bool traced = ops != NULL && ops->trace != NULL; + bool empty = true; + for (size_t i = 0; empty && i < species->len; i++) { + uint64_t slot; + empty = scr_arr_state_at(species, i, &slot) == SCR_ARR_HOLE; + } + if (!empty || (traced && !species->cyc)) { + static const char msg[] = + "an Array subclass's species result of another element type is not supported yet"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + scr_arr_release(species); + return result; + } + scr_arr_retype_empty(species, result->elem, ops); + } + for (size_t i = 0; i < result->len; i++) { + uint64_t slot; + uint8_t state = scr_arr_state_at(result, i, &slot); + if (state == SCR_ARR_VALUE) scr_arr_set_slot(species, (double)i, scr_elem_retain_slot(result, slot)); + else if (state == SCR_ARR_UNDEFINED) scr_arr_set_undefined(species, (double)i); + } + if (set_length) scr_arr_set_len(species, (double)result->len); + scr_arr_release(result); + return species; +} + +bool scr_dyn_is_array_class(const ScrDyn *d, const ScrArrClass *cls) { + if (d == NULL) return false; + /* A live view of the native instance. */ + if (d->kind == SCR_DYN_TYPED_REF && d->v.typed_ref.array != NULL && !d->v.typed_ref.record && + !d->v.typed_ref.klass && !d->v.typed_ref.bag_class) + return scr_arr_class_of(d->v.typed_ref.ptr) == cls; + /* A snapshot: scr_arr_copy_metadata names the class by its name's + * address, which each class owns. */ + if (d->kind == SCR_DYN_ARR) { + const ScrDyn *props = d->v.arr.properties; + return props != NULL && props->v.obj.class_name == cls->name; + } + return false; +} + +const ScrArrClass *scr_arr_class_of(const ScrArr *a) { + if (!(a->spare & SCR_ARR_HAS_EXT)) return NULL; + return scr_arr_ext_of((ScrArr *)a)->cls; +} + +void scr_arr_subclass_gc_drop(ScrArr *a, bool traced) { + if (!traced) scr_arr_release_contents(a); + scr_arr_free_parts(a); + a->data = NULL; + a->len = a->cap = a->filled = 0; + a->spare &= (uint16_t)~SCR_ARR_HAS_EXT; +#ifdef SCR_RC_AUDIT + scr_live_arrays--; +#endif +} + +void scr_arr_subclass_drop(ScrArr *a) { + scr_arr_release_contents(a); + scr_arr_free_parts(a); + a->data = NULL; + a->len = a->cap = a->filled = 0; + a->spare &= (uint16_t)~SCR_ARR_HAS_EXT; +#ifdef SCR_RC_AUDIT + scr_live_arrays--; +#endif +} + /* Whether destroying `a`, an acyclic array, releases nothing: it has no ext * and its elements are numbers, booleans or compact references, or * references whose release does nothing (SCR_ARR_OPS_RELEASE_NOTHING). */ @@ -1674,6 +1873,12 @@ static inline bool scr_arr_releases_nothing(const ScrArr *a) { void scr_arr_release(ScrArr *a) { if (!a || a->rc == SIZE_MAX) return; /* NULL: an uninitialized `let` local */ if (--a->rc == 0) { + /* An Array subclass instance: its class tears it down. */ + if ((a->spare & SCR_ARR_HAS_EXT) && scr_arr_ext_of(a)->cls) { + if (a->cyc) scr_cyc_dead(a); + scr_arr_ext_of(a)->cls->destroy(a); + return; + } /* Nothing to tear down: free the storage without scr_rc_destroy's * queue (no release can recurse) or the element loop. */ if (!a->cyc && scr_arr_releases_nothing(a)) { @@ -5350,10 +5555,32 @@ bool scr_arr_numeric_key(const char *key, size_t len, double *out) { bool scr_arr_has_properties(const ScrArr *a) { const ScrArrExt *x = scr_arr_ext_of(a); - return x && (x->prop_len || x->metadata); + return x && (x->prop_len || x->metadata || x->cls); +} + +/* The own fields of an Array subclass instance (ScrArrClass.fields), a + * fresh dyn object, or NULL. */ +static ScrDyn *scr_arr_class_fields(ScrArr *a) { + const ScrArrClass *cls = scr_arr_class_of(a); + if (!cls || !cls->fields) return NULL; + return cls->fields(scr_arr_retain(a)); } void scr_arr_copy_metadata(ScrArr *a, ScrDyn *target) { + const ScrArrClass *cls = scr_arr_class_of(a); + if (cls) { + /* An Array subclass instance: the class name util.inspect prints before + * the elements, then the class's own fields as properties. */ + if (!target->v.arr.properties) target->v.arr.properties = scr_dyn_new_obj(); + target->v.arr.properties->v.obj.class_name = cls->name; + ScrDyn *fields = scr_arr_class_fields(a); + if (fields) { + for (size_t i = 0; i < fields->v.obj.len; i++) + scr_dyn_copy_entry(target, fields, &fields->v.obj.entries[i]); + scr_dyn_release(fields); + } + return; + } const ScrDyn *metadata = SCR_AX(a)->metadata; if (metadata && metadata->kind == SCR_DYN_HANDLE && metadata->v.handle.tag == SCR_DYNH_MATCH_SOURCE && scr_arr_match_copy(a, target)) @@ -5372,6 +5599,15 @@ ScrArr *scr_arr_named_keys(ScrArr *a) { ScrArr *keys = scr_arr_new(SCR_ELEM_STR, 0); for (size_t i = 0; i < x->prop_len; i++) scr_arr_push_ref(keys, scr_str_new(x->props[i].key, x->props[i].key_len)); + ScrDyn *fields = scr_arr_class_fields(a); + if (fields) { + for (size_t i = 0; i < fields->v.obj.len; i++) { + const ScrDynEntry *entry = &fields->v.obj.entries[i]; + if (entry->enumerable) scr_arr_push_ref(keys, scr_str_new(entry->key, entry->key_len)); + } + scr_dyn_release(fields); + return keys; + } const ScrDyn *metadata = x->metadata; if (metadata && metadata->kind == SCR_DYN_HANDLE && metadata->v.handle.tag == SCR_DYNH_MATCH_SOURCE) { static const char *const names[] = {"index", "input", "groups"}; diff --git a/packages/runtime/src/scr_array.h b/packages/runtime/src/scr_array.h index 23390a183..de51e2938 100644 --- a/packages/runtime/src/scr_array.h +++ b/packages/runtime/src/scr_array.h @@ -171,7 +171,31 @@ typedef struct ScrArrExt { /* The element ops of an array whose ops have no registry id * (SCR_ARR_OPS_OVERFLOW). */ const ScrArrOps *ops; + /* The class of an instance of a program class that extends Array + * (scr_arr_subclass_init); NULL for every other array. */ + const struct ScrArrClass *cls; } ScrArrExt; + +/* A program class that extends Array. Its instances are emitted structs + * that begin with a ScrArr (the reference count is the array's) and keep + * the class's own fields after it; they always have an ext, whose `cls` + * names the class. Every array entry point accepts them as arrays. */ +typedef struct ScrArrClass { + /* The class's name, as constructor.name reads it. */ + const char *name; + /* Destroys an instance whose count reached zero: releases the class's own + * fields, then scr_arr_subclass_drop, then frees the instance. */ + void (*destroy)(ScrArr *a); + /* The class's own fields as a dyn object in declaration order (the array + * is passed owned, +1), or NULL when the class declares none. util.inspect + * and Object.keys list them after the elements. */ + struct ScrDyn *(*fields)(ScrArr *a); + /* `new C(length)` (`new C()` without `with_length`): the instance + * ArraySpeciesCreate makes for map, filter, slice, splice, concat, flat + * and flatMap, and Array.of / Array.from called on the class. NULL when + * the class's constructor cannot take that argument. */ + ScrArr *(*species)(double length, bool with_length); +} ScrArrClass; /* The LLVM backend mirrors ScrArr as %ScrArr: its inline retain/release read * `cyc` (field 4, i8) to decide mark-live / candidate buffering exactly like * scr_arr_retain and scr_arr_release; its inline element access uses len and @@ -471,6 +495,40 @@ static inline ScrArr *scr_arr_retain(ScrArr *a) { * NULL-tolerant (uninitialized locals). Cycle-capable arrays (`cyc`) feed * the candidate-root buffer like every headered type. */ void scr_arr_release(ScrArr *a); +/* Initializes the ScrArr prefix of a zeroed, freshly allocated instance of + * an Array subclass (count already 1): no elements, the element kind and + * ops, and an ext naming the class. `cyc` says the instance carries the + * collector header (its class is cycle-capable). */ +void scr_arr_subclass_init(ScrArr *a, uint8_t elem, const ScrArrOps *ops, const ScrArrClass *cls, + bool cyc); +/* The collector's teardown of an Array subclass instance's storage: the + * elements are released unless they are the collector's (`traced`). */ +void scr_arr_subclass_gc_drop(ScrArr *a, bool traced); +/* super(...args) in a class that extends Array (the Array constructor's + * length-or-elements rule); `args` is borrowed. */ +void scr_arr_subclass_construct(ScrArr *a, ScrArr *args, bool dyn_elems); +/* The implicit constructor's super(...args) over its dyn argument array. */ +void scr_arr_subclass_construct_dyn(ScrArr *a, const struct ScrDyn *args, bool dyn_elems); +/* ArraySpeciesCreate over the result `result` (owned) of an Array method + * called on `recv` (borrowed): a plain receiver keeps the result; a + * subclass instance's result is `new C(ctor_length)`, whose indices then + * receive the result's present elements, and whose length is set to the + * result's when `set_length` (slice, splice and concat set it; map, + * filter, flat and flatMap do not). A negative `ctor_length` passes the + * result's length. Result owned. */ +ScrArr *scr_arr_species_adopt(ScrArr *recv, ScrArr *result, double ctor_length, bool set_length); +/* Array.of / Array.from called on an Array subclass: `new C(ctor_length)` + * (`new C()` when ctor_length is negative), then the elements of `result` + * (owned) and its length, as scr_arr_species_adopt. */ +ScrArr *scr_arr_class_construct_adopt(const ScrArrClass *cls, ScrArr *result, double ctor_length); +/* `value instanceof C` for a dyn value and a class that extends Array: a + * live view of an instance of C, or a snapshot of one. */ +bool scr_dyn_is_array_class(const struct ScrDyn *d, const ScrArrClass *cls); +/* The class of an Array subclass instance, or NULL for a plain array. */ +const ScrArrClass *scr_arr_class_of(const ScrArr *a); +/* Releases the elements of an Array subclass instance and frees its + * storage and ext, leaving the instance itself to its class's destroy. */ +void scr_arr_subclass_drop(ScrArr *a); void scr_arr_copy_metadata(ScrArr *a, struct ScrDyn *target); /* The commit of a live array view calls this on `a`, the array converted * from `view`, before `target` takes its storage: `a` takes the view's named diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 79b2564ff..3ad34cb95 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -1513,8 +1513,25 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { const ScrDyn *props = d->v.arr.properties; size_t nprops = 0; for (size_t i = 0; props && i < props->v.obj.len; i++) nprops += props->v.obj.entries[i].enumerable; - if (len == 0 && nprops == 0) return scr_str_new("[]", 2); - if (recurse > depth) return scr_str_new("[Array]", 7); + /* An instance of a class that extends Array: Node's `Name(length) ` + * prefix (scr_arr_copy_metadata names the class). */ + const char *cls = props ? props->v.obj.class_name : NULL; + char prefix[160] = ""; + if (cls) snprintf(prefix, sizeof prefix, "%.120s(%zu) ", cls, len); + if (len == 0 && nprops == 0) { + InspBuf eb = {0}; + ib_cstr(&eb, prefix); + ib_cstr(&eb, "[]"); + return ib_take(&eb); + } + if (recurse > depth) { + if (!cls) return scr_str_new("[Array]", 7); + InspBuf eb = {0}; + ib_char(&eb, '['); + ib_cstr(&eb, cls); + ib_char(&eb, ']'); + return ib_take(&eb); + } scr_insp_begin(recurse + 1); /* Node's maxArrayLength (100) counts output entries; a run of holes * is one `` entry (formatSpecialArray). */ @@ -1568,7 +1585,10 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { * array has no properties). */ bool last_out = entries > 100; ScrStr *base = scr_str_new("", 0); - ScrStr *b0 = scr_str_new("[", 1); + InspBuf ob = {0}; + ib_cstr(&ob, prefix); + ib_char(&ob, '['); + ScrStr *b0 = ib_take(&ob); ScrStr *b1 = scr_str_new("]", 1); ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, true, last_out); scr_str_release(base); diff --git a/tests/corpus/7720-array-subclass-basics.ts b/tests/corpus/7720-array-subclass-basics.ts new file mode 100644 index 000000000..1e5130f9d --- /dev/null +++ b/tests/corpus/7720-array-subclass-basics.ts @@ -0,0 +1,44 @@ +// A class that extends Array: construction (one number argument sets the +// length, other arguments become the elements), length and index writes, +// Array.isArray, instanceof, constructor, inherited methods, the subclass's +// own fields and methods, and how console.log and JSON show the instance. +class Stack extends Array { + pushes = 0; + + top(): number | undefined { + return this[this.length - 1]; + } + + add(value: number): this { + this.push(value); + this.pushes++; + return this; + } +} + +const s = new Stack(); +s.add(1).add(2).add(3); +console.log(s.length, s.top(), s.pushes); +console.log(Array.isArray(s), s instanceof Stack, s instanceof Array, s.constructor === Stack); +console.log(s); +console.log(JSON.stringify(s)); +s[5] = 9; +console.log(s.length, s[4], s); +s.length = 2; +console.log(s, s.indexOf(2), s.includes(3), s.join("-")); +s.reverse(); +console.log(s.top(), s); + +const sized = new Stack(3); +console.log(sized.length, sized, 0 in sized); +const listed = new Stack(4, 5); +console.log(listed.length, listed); + +try { + new Stack(-1); +} catch (e) { + console.log((e as Error).name, (e as Error).message); +} + +for (const v of listed) console.log("item", v); +console.log([...listed, 6], Object.keys(listed)); diff --git a/tests/corpus/7721-array-subclass-species.ts b/tests/corpus/7721-array-subclass-species.ts new file mode 100644 index 000000000..03e0d19ab --- /dev/null +++ b/tests/corpus/7721-array-subclass-species.ts @@ -0,0 +1,37 @@ +// Array.prototype methods that create a new array construct it through the +// subclass (ArraySpeciesCreate): map, filter, slice, splice, concat, flat +// and flatMap return an instance of the subclass, built by calling its +// constructor with the length the specification passes. Methods that +// return the receiver keep it, and Array.of / Array.from inherited as +// statics construct the subclass too. +class Logged extends Array { + constructor(...args: number[]) { + super(...args); + console.log("construct", args.length === 1 ? `length ${args[0]}` : `items ${args.join(",")}`); + } + + sum(): number { + let total = 0; + for (const v of this) total += v; + return total; + } +} + +const base = Logged.of(1, 2, 3, 4); +console.log(base instanceof Logged, base); +const doubled = base.map((v) => v * 2); +console.log(doubled instanceof Logged, (doubled as Logged).sum(), doubled); +const even = base.filter((v) => v % 2 === 0); +console.log(even instanceof Logged, even); +const part = base.slice(1, 3); +console.log(part instanceof Logged, part); +const removed = base.splice(0, 1); +console.log(removed instanceof Logged, removed, base); +const joined = base.concat([7, 8]); +console.log(joined instanceof Logged, joined.length, joined); +const flat = base.flatMap((v) => [v, v]); +console.log(flat instanceof Logged, flat.length); +const filled = base.fill(0, 0, 1); +console.log(filled === base, base); +const from = Logged.from([5, 6]); +console.log(from instanceof Logged, (from as Logged).sum()); diff --git a/tests/corpus/7722-array-subclass-zero-array.js b/tests/corpus/7722-array-subclass-zero-array.js new file mode 100644 index 000000000..1ed52d386 --- /dev/null +++ b/tests/corpus/7722-array-subclass-zero-array.js @@ -0,0 +1,16 @@ +// lru-cache's ZeroArray: a JavaScript class extending Array whose +// constructor fills the new array with zeros, constructed directly and +// through map (species construction runs the constructor again). +class ZeroArray extends Array { + constructor(size) { + super(size); + this.fill(0); + } +} + +const z = new ZeroArray(4); +z[1] = 7; +console.log(z, z.length, z instanceof ZeroArray, Array.isArray(z)); +const m = z.map((v) => v + 1); +console.log(m, m instanceof ZeroArray); +console.log(z.slice(1) instanceof ZeroArray, String(z)); diff --git a/tests/corpus/7723-array-subclass-as-array.ts b/tests/corpus/7723-array-subclass-as-array.ts new file mode 100644 index 000000000..5faf5b13f --- /dev/null +++ b/tests/corpus/7723-array-subclass-as-array.ts @@ -0,0 +1,21 @@ +// A subclass instance passed where a plain array is expected is the same +// object: writes through either view are seen through the other, and +// species construction still applies in code typed with the plain array. +class Path extends Array { + label = "path"; +} + +function addSegment(parts: string[], segment: string): string[] { + parts.push(segment); + return parts.filter((part) => part.length > 0); +} + +const p = new Path(); +p.push("a", "", "b"); +const filtered = addSegment(p, "c"); +console.log(p.length, filtered.length, filtered instanceof Path, (filtered as Path).label); +const view: string[] = p; +view[0] = "z"; +console.log(p[0], view === p, p.label); +console.log(p.map((s) => s.toUpperCase())); +console.log(Object.keys(p)); diff --git a/tests/corpus/7724-array-subclass-cycles.ts b/tests/corpus/7724-array-subclass-cycles.ts new file mode 100644 index 000000000..9a1a7b813 --- /dev/null +++ b/tests/corpus/7724-array-subclass-cycles.ts @@ -0,0 +1,36 @@ +// An Array subclass whose elements and fields refer back to the instance: +// the cycles are collected once nothing outside them is reachable, and +// the elements and fields read back as Node reads them. +class Member { + name: string; + group: Group | undefined; + constructor(name: string, group: Group | undefined) { + this.name = name; + this.group = group; + } +} + +class Group extends Array { + owner: Member | undefined = undefined; + + add(name: string): Member { + const member = new Member(name, this); + this.push(member); + return member; + } +} + +function build(round: number): number { + const g = new Group(); + const lead = g.add(`lead${round}`); + g.add(`member${round}`); + g.owner = lead; + return g.length + (g[0]!.group === g ? 1 : 0) + (g.owner === lead ? 1 : 0); +} + +let total = 0; +for (let i = 0; i < 200; i++) total += build(i); +console.log(total); +const kept = new Group(); +kept.add("a").group?.add("b"); +console.log(kept.length, kept.map((m) => m.name).join(","), kept[1]!.group === kept); diff --git a/tests/corpus/7725-array-subclass-operations.ts b/tests/corpus/7725-array-subclass-operations.ts new file mode 100644 index 000000000..94cfa8b04 --- /dev/null +++ b/tests/corpus/7725-array-subclass-operations.ts @@ -0,0 +1,46 @@ +// Array operations on an Array subclass instance: iteration, spread, +// destructuring, the mutators that return the instance itself, searches, +// length writes, Array.isArray through unknown, instanceof through unknown, +// and JSON. +class Row extends Array { + tag = "row"; + #reads = 0; + + static of2(a: string, b: string): Row { + const row = new Row(); + row.push(a, b); + return row; + } + + get size(): number { + this.#reads++; + return this.length; + } + + get reads(): number { + return this.#reads; + } + + label(): string { + return `${this.tag}:${this.join("|")}`; + } +} + +const r = new Row(); +console.log(r.push("b", "a", "c"), r.length, r.label()); +const [first, ...rest] = r; +console.log(first, rest, [...r].length); +for (const [i, v] of r.entries()) console.log(i, v); +console.log(r.sort() === r, r.label(), r.reverse() === r, r.label()); +console.log(r.fill("x", 1) === r, r.label(), r.indexOf("x"), r.lastIndexOf("x"), r.at(-1)); +console.log(r.pop(), r.shift(), r.length, r.unshift("z"), r.label()); +r.length = 0; +console.log(r.length, r.label(), JSON.stringify({ r })); +const u: unknown = r; +console.log(Array.isArray(u), u instanceof Row, u instanceof Array); +const rows: Row[] = [new Row(), new Row()]; +rows[1]!.push("q"); +console.log(rows.map((row) => row.label()).join(" ")); +console.log({ nested: rows[1] }); +const pair = Row.of2("m", "n"); +console.log(pair.size, pair.size, pair.reads, pair.label(), pair instanceof Row); diff --git a/tests/corpus/7726-array-subclass-expressions.js b/tests/corpus/7726-array-subclass-expressions.js new file mode 100644 index 000000000..d2e5d2a04 --- /dev/null +++ b/tests/corpus/7726-array-subclass-expressions.js @@ -0,0 +1,21 @@ +// Classes extending Array reached through expressions rather than +// declarations and locals: a construction used in place, a class +// expression held by a binding, and an instanceof test whose class the +// program never constructs. +class Sized extends Array { + describe() { + return `sized ${this.length}`; + } +} + +console.log(new Sized(3).length, new Sized(2).describe(), new Sized(1, 2).join("-")); +console.log(new Sized(4).fill(1), (new Sized(2)).length); + +const Holder = class extends Array {}; +const held = new Holder(); +held.push("a", "b"); +console.log(held instanceof Holder, held instanceof Array, [] instanceof Holder, held.length); + +class Unbuilt extends Array {} +const values = [[1, 2], "text", new Sized(1)]; +for (const value of values) console.log(value instanceof Unbuilt, Array.isArray(value)); diff --git a/tests/diagnostics/array-subclass-fences.ts b/tests/diagnostics/array-subclass-fences.ts new file mode 100644 index 000000000..cc2966201 --- /dev/null +++ b/tests/diagnostics/array-subclass-fences.ts @@ -0,0 +1,21 @@ +// Array subclasses that do not compile yet, each refused where it is +// declared: a generic subclass, a subclass of a class that extends Array, +// and a class that declares its own Symbol.species. +class Bag extends Array { + firstOr(fallback: T): T { + return this.length > 0 ? this[0]! : fallback; + } +} + +class Base extends Array {} +class Derived extends Base {} + +class Speciated extends Array { + static get [Symbol.species](): ArrayConstructor { + return Array; + } +} + +console.log(new Bag().firstOr(1)); +console.log(new Derived().length); +console.log(new Speciated().length); diff --git a/tests/harness/__snapshots__/array-subclass-fences.ts.txt b/tests/harness/__snapshots__/array-subclass-fences.ts.txt new file mode 100644 index 000000000..3d81a58a9 --- /dev/null +++ b/tests/harness/__snapshots__/array-subclass-fences.ts.txt @@ -0,0 +1,41 @@ +array-subclass-fences.ts:4:22 - error SC1090: a generic class that extends Array is not supported yet + + 3 | // and a class that declares its own Symbol.species. + 4 | class Bag extends Array { + | ^~~~~~~~ + 5 | firstOr(fallback: T): T { + +array-subclass-fences.ts:11:23 - error SC1090: extending 'Base', a class that extends Array (one level of Array subclass compiles) is not supported yet + + 10 | class Base extends Array {} + 11 | class Derived extends Base {} + | ^~~~ + 12 | + +array-subclass-fences.ts:14:3 - error SC1090: Symbol.species on a class that extends Array (its methods' results construct the class itself) is not supported yet + + 13 | class Speciated extends Array { + 14 | static get [Symbol.species](): ArrayConstructor { + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 15 | return Array; + +array-subclass-fences.ts:19:13 - error SC1090: constructing values other than classes declared in the program is not supported yet + + 18 | + 19 | console.log(new Bag().firstOr(1)); + | ^~~~~~~~~~~~~~~~~ + 20 | console.log(new Derived().length); + +array-subclass-fences.ts:20: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 + + 19 | console.log(new Bag().firstOr(1)); + 20 | console.log(new Derived().length); + | ^~~~~~~~~~~~~ + 21 | console.log(new Speciated().length); + +array-subclass-fences.ts:21: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 + + 20 | console.log(new Derived().length); + 21 | console.log(new Speciated().length); + | ^~~~~~~~~~~~~~~ + 22 | \ No newline at end of file diff --git a/tests/harness/corpus-costs/7720-array-subclass-basics.ts.json b/tests/harness/corpus-costs/7720-array-subclass-basics.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7720-array-subclass-basics.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7721-array-subclass-species.ts.json b/tests/harness/corpus-costs/7721-array-subclass-species.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7721-array-subclass-species.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7722-array-subclass-zero-array.js.json b/tests/harness/corpus-costs/7722-array-subclass-zero-array.js.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7722-array-subclass-zero-array.js.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7723-array-subclass-as-array.ts.json b/tests/harness/corpus-costs/7723-array-subclass-as-array.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7723-array-subclass-as-array.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7724-array-subclass-cycles.ts.json b/tests/harness/corpus-costs/7724-array-subclass-cycles.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7724-array-subclass-cycles.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7725-array-subclass-operations.ts.json b/tests/harness/corpus-costs/7725-array-subclass-operations.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7725-array-subclass-operations.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7726-array-subclass-expressions.js.json b/tests/harness/corpus-costs/7726-array-subclass-expressions.js.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7726-array-subclass-expressions.js.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } From 15cc7310aca7924abc26f6d2ef91c0543ab945ef Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Fri, 23 Oct 2026 15:15:34 -0700 Subject: [PATCH 014/230] Keep record values that subtype reduction folds into an inferred type TypeScript infers a function's return type as the subtype-reduced union of the values it returns: returning `{ status }` in one branch and `{ status, value }` (or a call returning either) in another infers just `{ status }`. Records have fixed layouts, so that type cannot hold the second value: the return failed its union wrap at run time ('... is not representable in the target union'), and values that reached a binding of the reduced type failed a checked narrowing. - A function with an inferred return type and no contextual signature returns the union of the record types it actually returns, including through calls of other such functions. Only value types the checker's reduction dropped from the inferred union are added, so functions whose inferred type already names every returned type keep their layout. Declared return types keep their meaning. - Reading such a union through the reduced type (a binding, a member read, JSON.stringify) uses a dedicated checked extraction that converts the wider record arms with the ordinary width coercion; other narrowings are unchanged. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/builtins/json.ts | 10 +- .../src/frontend/lowering/coercions/state.ts | 9 +- .../src/frontend/lowering/coercions/unions.ts | 71 ++++++++- .../lowering/expressions/property-access.ts | 20 ++- .../src/frontend/lowering/function-returns.ts | 143 ++++++++++++++++++ .../compiler/src/frontend/lowering/lowerer.ts | 10 +- .../src/frontend/lowering/union-narrow.ts | 17 +++ ...nferred-return-overlapping-records.ts.json | 6 + ...990-inferred-return-overlapping-records.ts | 59 ++++++++ ...nferred-return-overlapping-records.ts.json | 1 + 10 files changed, 340 insertions(+), 6 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7990-inferred-return-overlapping-records.ts.json create mode 100644 tests/corpus/7990-inferred-return-overlapping-records.ts create mode 100644 tests/harness/corpus-costs/7990-inferred-return-overlapping-records.ts.json diff --git a/packages/compiler/src/frontend/lowering/builtins/json.ts b/packages/compiler/src/frontend/lowering/builtins/json.ts index 64f829865..5aad5077f 100644 --- a/packages/compiler/src/frontend/lowering/builtins/json.ts +++ b/packages/compiler/src/frontend/lowering/builtins/json.ts @@ -1,3 +1,4 @@ +import { widensReducedRecords } from "../function-returns.js"; import { keyedHelperName } from "../stable-names.js"; import { isDateJsonSource } from "./dates.js"; import { dynUndefinedExpr, varRef } from "../../../ir/build.js"; @@ -96,7 +97,14 @@ export function lowerJsonMethodCall( !isDateJsonSource(lowerer, argNode) ) { const narrowed = lowerer.mapTypeOf(lowerer.typeOf(argNode)); - if (narrowed && !isUnitType(narrowed) && narrowed.kind !== "void") { + // A union of record types the checker's subtype reduction spelled as + // one of them serializes each value by its own layout. + if ( + narrowed && + !isUnitType(narrowed) && + narrowed.kind !== "void" && + !widensReducedRecords(lowerer, narrowed, value.type) + ) { const helper = narrowed.kind === "union" ? lowerer.narrowedRetagHelper(argNode, value.type.unionId, narrowed.unionId, loc) diff --git a/packages/compiler/src/frontend/lowering/coercions/state.ts b/packages/compiler/src/frontend/lowering/coercions/state.ts index ad1d7c6ea..3c2ecf7bd 100644 --- a/packages/compiler/src/frontend/lowering/coercions/state.ts +++ b/packages/compiler/src/frontend/lowering/coercions/state.ts @@ -12,7 +12,14 @@ export class CoercionState { // Provenance for checked extraction; consumers must not infer it from names. readonly checkedNarrows = new Set(); // The extracted arm of each checked (non-defaulting) extraction helper. - readonly checkedNarrowArms = new Map(); + readonly checkedNarrowArms = new Map< + string, + { unionId: string; tag: number; target: IrType; widens?: true } + >(); readonly retags = new Map(); readonly narrows = new Map(); + readonly widthNarrows = new Map(); + /** Unions widenReducedReturn produced: only these read through the + * width-converting extraction (widthNarrowHelper). */ + readonly widenedReturns = new Set(); } diff --git a/packages/compiler/src/frontend/lowering/coercions/unions.ts b/packages/compiler/src/frontend/lowering/coercions/unions.ts index 434e687a5..776892487 100644 --- a/packages/compiler/src/frontend/lowering/coercions/unions.ts +++ b/packages/compiler/src/frontend/lowering/coercions/unions.ts @@ -421,6 +421,46 @@ export function narrowedArmHelper( return name; } +/** Interned `%union.narrow.width.(u)`: the checked extraction of a + * record arm from a union that also holds records whose fields strictly + * include the arm's (a function's widened inferred return, see + * widenReducedReturn). Those arms convert like any record width coercion; + * every other arm throws like narrowedArmHelper's. */ +export function widthNarrowHelper( + lowerer: Lowerer, + fromId: string, + target: IrType, + loc: SrcLoc, +): string | null { + const from = lowerer.unions.get(fromId); + if (!from || target.kind !== "record") return null; + const tag = lowerer.armTag(fromId, target); + if (tag < 0) return null; + const key = `${fromId}:${tag}`; + const existing = lowerer.coercions.widthNarrows.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, "%union.narrow.width", key); + lowerer.coercions.widthNarrows.set(key, name); + const fn = buildUnionNarrow( + name, + from, + target, + loc, + (type) => lowerer.fmt(type), + null, + (armTag, payload) => widthSubtypeConversion(lowerer, from.arms[armTag]!, target, payload, loc), + ); + if (!fn) throw new InternalCompilerError("lowerer bug: invalid checked union extraction"); + lowerer.coercions.checkedNarrowArms.set(name, { + unionId: fromId, + tag, + target: from.arms[tag]!, + widens: true, + }); + lowerer.liftedFns.push(fn); + return name; +} + /** A checked extraction at its use. A plain read tests the stored tag in * place and projects the payload directly, so member reads can borrow it; * every other arm throws the helper's TypeError with the same message. @@ -442,7 +482,8 @@ export function checkedNarrowCall( value.type.kind !== "union" || value.type.unionId !== arm.unionId || !typeEquals(type, arm.target) || - !isSafeToRepeat(value) + !isSafeToRepeat(value) || + arm.widens === true ) return call; const unionId = arm.unionId; @@ -599,3 +640,31 @@ export function deferredReadHelper( lowerer.liftedFns.push(fn); return name; } + +/** Whether `arm` is a record whose fields strictly include those of the + * record `target`: a value of the target type under TypeScript's structural + * typing, which the checker's subtype reduction may have spelled as the + * target (`{ status } | { status, value }` infers `{ status }`). */ +export function isWidthSubtypeRecord(lowerer: Lowerer, arm: IrType, target: IrType): boolean { + if (arm.kind !== "record" || target.kind !== "record") return false; + const own = lowerer.shapes.get(arm.shapeId); + const want = lowerer.shapes.get(target.shapeId); + if (!own || !want || own.tuple || want.tuple || own.indexValue || want.indexValue) return false; + if (want.fields.length >= own.fields.length) return false; + const names = new Set(own.fields.map((f) => f.name)); + return want.fields.every((f) => names.has(f.name)); +} + +/** A checked narrow that meets a width-subtype record arm converts it like + * any record width coercion instead of reporting a lying narrowing. */ +function widthSubtypeConversion( + lowerer: Lowerer, + arm: IrType, + target: IrType, + payload: IrExpr, + loc: SrcLoc, +): IrExpr | null { + if (!isWidthSubtypeRecord(lowerer, arm, target)) return null; + const lift = lowerer.widthLiftPlan(arm, target); + return lift === null ? null : lowerer.applyWidthLift(lift, payload, target, loc); +} diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 9894cfb71..bac0d0daa 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -1,3 +1,5 @@ +import { widensReducedRecords } from "../function-returns.js"; +import { widthNarrowHelper } from "../coercions/unions.js"; /* Property reads (`a.b`): lowerExprInner's property-access branch, intrinsic and union properties, field reads and targets, and match-group reads. */ import { keyedHelperName } from "../stable-names.js"; import { staticallySelected } from "../static-typeof.js"; @@ -1293,7 +1295,23 @@ export function lowerPropertyAccessExpr( // honest one, not a generic recitation about the outer dot. A // receiver that lowers cleanly means the MEMBER is the gap — name // the property and the receiver's type instead of "property access". - const recvLowered = lowerer.lowerExpr(expr.expression); + let recvLowered = lowerer.lowerExpr(expr.expression); + // A receiver whose value union holds record types the checker's subtype + // reduction folded into its type (`{ status } | { status, value }` typed + // `{ status }`, a call of a function with an inferred return type): read + // through the checker's type, converting the wider arms. + if (recvLowered.type.kind === "union") { + const declared = lowerer.mapTypeOf(lowerer.typeOf(expr.expression)); + if ( + declared !== null && + declared.kind === "record" && + widensReducedRecords(lowerer, declared, recvLowered.type) + ) { + const helper = widthNarrowHelper(lowerer, recvLowered.type.unionId, declared, loc); + if (helper !== null) + recvLowered = lowerer.checkedNarrowCall(helper, recvLowered, declared, loc); + } + } // A dyn receiver — JSON.parse's `any`, or `unknown` the checker // narrowed to `object` — reads through the dyn keyed read: member // or undefined (JS's own-property answer), throwing JS's TypeError diff --git a/packages/compiler/src/frontend/lowering/function-returns.ts b/packages/compiler/src/frontend/lowering/function-returns.ts index dde109ea7..b1132a8b9 100644 --- a/packages/compiler/src/frontend/lowering/function-returns.ts +++ b/packages/compiler/src/frontend/lowering/function-returns.ts @@ -11,12 +11,14 @@ import { VOID, isUnitType, typeEquals, + typeKey, } from "../../ir/ir.js"; import { isJsSourceFile } from "../program.js"; import { PoisonError, dynFallbackType } from "./lowerer.js"; import { functionCanReturnUndefined } from "../function-completion.js"; import { jsBindingAppendsOnly, jsBindingHasOpenWrites } from "./lower-stmts.js"; import type { FnCtx } from "./function-context.js"; +import { constituentTypes } from "../ts7/checker.js"; /** Keep the conservative completion rule shared by return construction * and inference. Nested control flow may still make completion unreachable. */ @@ -175,6 +177,8 @@ export function declaredReturnType( // (`.action(() => { throw ... })`). `never` VALUES stay unmapped. if (retTsType.flags & ts.TypeFlags.Never) return VOID; let mappedReturn = lowerer.mapTypeOf(retTsType); + if (mappedReturn !== null) + mappedReturn = widenReducedReturn(lowerer, decl, retTsType, mappedReturn); // A generator declared to return Iterable returns a generator over T. if (mappedReturn && decl.asteriskToken) mappedReturn = iterableGeneratorArm(lowerer, mappedReturn); @@ -522,3 +526,142 @@ export function blockBodyOf(decl: ts.FunctionLikeDeclaration): ts.Block | null { if (ts.isBlock(body)) return body; return ts.forEachChild(decl, (c) => (ts.isBlock(c) ? c : undefined)) ?? null; } + +const widening = new Set(); + +/** An inferred return type is the checker's SUBTYPE-REDUCED union of the + * returned values' types: `return aborted` (`{ status }`) beside `return + * { status, value }` infers just `{ status }`, because the second is a + * subtype of the first. Records have fixed layouts, so that type cannot + * hold the second value: the return would drop `value` or fail the union + * wrap. A function whose return type is inferred therefore returns the + * union of the record types it actually returns. Declared return types keep + * their meaning. */ +function widenReducedReturn( + lowerer: Lowerer, + decl: ts.SignatureDeclaration, + retTsType: ts.Type, + mapped: IrType, +): IrType { + if (decl.type !== undefined || hasExplicitJsDocReturn(decl)) return mapped; + const body = (decl as { body?: ts.Node }).body; + if (body === undefined || decl.asteriskToken !== undefined) return mapped; + if (decl.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) return mapped; + // A function expression with a contextual signature returns that + // signature's type; only free-standing inference is widened. + if ( + (ts.isFunctionExpression(decl) || ts.isArrowFunction(decl)) && + lowerer.checker.getContextualType(decl) !== undefined + ) + return mapped; + const arms = + mapped.kind === "union" ? (lowerer.unions.get(mapped.unionId)?.arms ?? []) : [mapped]; + const recordArms = arms.filter((a) => a.kind === "record"); + if (recordArms.length === 0 || widening.has(decl)) return mapped; + const checkerArms = new Set( + retTsType.flags & ts.TypeFlags.Union ? constituentTypes(retTsType) : [retTsType], + ); + const fieldsOf = (t: IrType): readonly string[] | null => { + if (t.kind !== "record") return null; + const shape = lowerer.shapes.get(t.shapeId); + return shape === undefined || shape.tuple || shape.indexValue !== undefined + ? null + : shape.fields.map((f) => f.name); + }; + const narrower = (part: IrType): boolean => { + const own = fieldsOf(part); + if (own === null || arms.some((a) => typeKey(a) === typeKey(part))) return false; + return recordArms.some((arm) => { + const fields = fieldsOf(arm); + return fields !== null && fields.length < own.length && fields.every((f) => own.includes(f)); + }); + }; + const extra = new Map(); + const returned = (expr: ts.Expression): void => { + // A call of another inferred function returns ITS widened type. + const callee = ts.isCallExpression(expr) + ? lowerer.checker.getResolvedSignature(expr)?.declaration + : undefined; + if ( + callee !== undefined && + ts.isFunctionLike(callee) && + (callee as { body?: ts.Node }).body !== undefined && + callee.type === undefined + ) { + let t: IrType | null = null; + widening.add(decl); + try { + t = declaredReturnType(lowerer, callee, expr); + } catch { + t = null; + } finally { + widening.delete(decl); + } + if (t === null) return; + const parts = t.kind === "union" ? (lowerer.unions.get(t.unionId)?.arms ?? []) : [t]; + for (const part of parts) if (narrower(part)) extra.set(typeKey(part), part); + return; + } + // Only a value type the checker's reduction dropped from the inferred + // union is a candidate; a constituent of the inferred type is already + // represented by it. + const valueType = lowerer.typeOf(expr); + const valueArms = + valueType.flags & ts.TypeFlags.Union ? constituentTypes(valueType) : [valueType]; + for (const part of valueArms) { + if (checkerArms.has(part) || !(part.flags & ts.TypeFlags.Object)) continue; + if (!lowerer.checker.isTypeAssignableTo(part, retTsType)) continue; + const t = lowerer.mapTypeOf(part); + if (t !== null && narrower(t)) extra.set(typeKey(t), t); + } + }; + const scan = (node: ts.Node): void => { + if (node !== decl && ts.isFunctionLike(node)) return; + if (ts.isReturnStatement(node) && node.expression !== undefined) returned(node.expression); + ts.forEachChild(node, scan); + }; + if (ts.isArrowFunction(decl) && !ts.isBlock(body)) returned(body as ts.Expression); + else scan(body); + if (extra.size === 0) return mapped; + const joined = new Map(); + for (const a of [...arms, ...extra.values()]) joined.set(typeKey(a), a); + const sorted = [...joined.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); + const unionId = lowerer.unions.intern(sorted); + lowerer.coercions.widenedReturns.add(unionId); + return { kind: "union", unionId }; +} + +/** Whether `wide` is a widened inferred return that holds `narrow`'s arms plus records that are width + * subtypes of `narrow`'s record arms: the shape a subtype-reduced inferred + * type takes next to the values it was inferred from. */ +export function widensReducedRecords(lowerer: Lowerer, narrow: IrType, wide: IrType): boolean { + if (wide.kind !== "union" || typeKey(narrow) === typeKey(wide)) return false; + if (!lowerer.coercions.widenedReturns.has(wide.unionId)) return false; + const narrowArms = + narrow.kind === "union" ? (lowerer.unions.get(narrow.unionId)?.arms ?? []) : [narrow]; + const wideArms = lowerer.unions.get(wide.unionId)?.arms ?? []; + const keys = new Set(wideArms.map(typeKey)); + if (!narrowArms.every((a) => keys.has(typeKey(a)))) return false; + const fieldsOf = (t: IrType): readonly string[] | null => { + if (t.kind !== "record") return null; + const shape = lowerer.shapes.get(t.shapeId); + return shape === undefined || shape.tuple || shape.indexValue !== undefined + ? null + : shape.fields.map((f) => f.name); + }; + const narrowKeys = new Set(narrowArms.map(typeKey)); + const extras = wideArms.filter((a) => !narrowKeys.has(typeKey(a))); + return ( + extras.length > 0 && + extras.every((extra) => { + const own = fieldsOf(extra); + return ( + own !== null && + narrowArms.some((arm) => { + const f = fieldsOf(arm); + return f !== null && f.length < own.length && f.every((x) => own.includes(x)); + }) + ); + }) + ); +} diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 7aad166e9..4bf938027 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -115,6 +115,7 @@ import { checkedNarrowCall, checkedNarrowOperand, deferredReadHelper, + widthNarrowHelper, } from "./coercions/unions.js"; import { lowerUtilTypeValue, @@ -443,7 +444,7 @@ import { bindingNeverReassigned } from "./binding-analysis.js"; import type { InstantiationPath } from "./instantiation-path.js"; import { bodyReadsArguments } from "../arguments-usage.js"; import { completeArgs, wrappedUndefined, undefinedArgFor } from "./call-arguments.js"; -import { bodyReturnType, declaredReturnType } from "./function-returns.js"; +import { bodyReturnType, declaredReturnType, widensReducedRecords } from "./function-returns.js"; import { lowerCall, lowerPromiseMethodCall, lowerFilterNarrowCall } from "./lower-calls.js"; import { lowerFfiCall, validateFfiImports } from "./native-ffi.js"; import { @@ -3464,6 +3465,7 @@ export class Lowerer { this.internTableHelpers, this.coercions.retags, this.coercions.narrows, + this.coercions.widthNarrows, ]) for (const name of table.values()) interned.add(name); for (const fn of [...this.liftedFns, ...this.implicitFns]) @@ -10013,7 +10015,11 @@ export class Lowerer { expected.kind !== "void" && this.armTag(expr.type.unionId, expected) >= 0 ) { - const helper = this.narrowedArmHelper(expr.type.unionId, expected, expr.loc); + // A function's widened inferred return (widenReducedReturn) also + // holds records with more fields; those convert instead of throwing. + const helper = widensReducedRecords(this, expected, expr.type) + ? widthNarrowHelper(this, expr.type.unionId, expected, expr.loc) + : this.narrowedArmHelper(expr.type.unionId, expected, expr.loc); if (helper) { return this.checkedNarrowCall(helper, expr, expected, expr.loc); } diff --git a/packages/compiler/src/frontend/lowering/union-narrow.ts b/packages/compiler/src/frontend/lowering/union-narrow.ts index 3bb09cb26..7ae036fad 100644 --- a/packages/compiler/src/frontend/lowering/union-narrow.ts +++ b/packages/compiler/src/frontend/lowering/union-narrow.ts @@ -28,6 +28,9 @@ export function buildUnionNarrow( loc: SrcLoc, formatType: (type: IrType) => string, undefinedDefault: IrExpr | null = null, + /** Converts another arm's payload into the target (a record arm that is + * a width subtype of a record target), or null to throw. */ + convert: ((tag: number, payload: IrExpr) => IrExpr | null) | null = null, ): IrFunction | null { if (isUnitType(target) || target.kind === "void") return null; const targetTag = from.arms.findIndex((arm) => typeEquals(arm, target)); @@ -43,6 +46,7 @@ export function buildUnionNarrow( const inputType: IrType = { kind: "union", unionId: from.id }; const input: IrExpr = { kind: "varRef", localId: "u.0", type: inputType, loc }; const body: IrStmt[] = []; + let converted: IrExpr | null = null; for (let tag = 0; tag < from.arms.length; tag++) { const arm = from.arms[tag]!; let branch: IrStmt[]; @@ -56,6 +60,19 @@ export function buildUnionNarrow( ]; } else if (arm.kind === "undefinedT" && undefinedDefault !== null) { branch = [{ kind: "return", value: undefinedDefault, loc }]; + } else if ( + convert !== null && + !isUnitType(arm) && + (converted = convert(tag, { + kind: "unionNarrow", + unionId: from.id, + tag, + value: input, + type: arm, + loc, + })) !== null + ) { + branch = [{ kind: "return", value: converted, loc }]; } else { branch = [ { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7990-inferred-return-overlapping-records.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7990-inferred-return-overlapping-records.ts.json new file mode 100644 index 000000000..6a55f1f2e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7990-inferred-return-overlapping-records.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7990-inferred-return-overlapping-records.ts" + ], + "diags": [] +} diff --git a/tests/corpus/7990-inferred-return-overlapping-records.ts b/tests/corpus/7990-inferred-return-overlapping-records.ts new file mode 100644 index 000000000..c2d1c9ea0 --- /dev/null +++ b/tests/corpus/7990-inferred-return-overlapping-records.ts @@ -0,0 +1,59 @@ +// Functions whose inferred return type is a union of overlapping records. +// TypeScript's subtype reduction infers `{ status }` for a function that +// returns `{ status }` in one branch and `{ status, value }` in another, so +// the inferred type is narrower than the values. Returned values keep their +// fields, chained returns keep them, and bindings typed by the reduced type +// read their declared fields. +type Aborted = { status: string }; +const ABORTED: Aborted = { status: "aborted" }; + +function inner(ok: boolean, value: unknown) { + if (!ok) return ABORTED; + return { status: "valid", value }; +} + +// Returns inner's union through a call: inferred as `{ status }` alone. +function outer(ok: boolean, value: unknown) { + if (value === "stop") return ABORTED; + return inner(ok, value); +} + +function merge(entries: [string, number][]) { + const result: Record = {}; + for (const [key, n] of entries) { + if (key === "__proto__") return ABORTED; + result[key] = n; + } + return { status: "valid", value: result }; +} + +function parse(entries: [string, number][]) { + if (entries.length === 0) return ABORTED; + return merge(entries); +} + +console.log(JSON.stringify(outer(true, [1, 2]))); +console.log(JSON.stringify(outer(false, 1))); +console.log(JSON.stringify(outer(true, "stop"))); +console.log(JSON.stringify(parse([["a", 1], ["b", 2]]))); +console.log(JSON.stringify(parse([]))); + +// Into untyped positions: the value keeps every field. +const seen: unknown[] = []; +seen.push(outer(true, { nested: true })); +seen.push(parse([["c", 3]])); +console.log(JSON.stringify(seen)); +const loose: unknown = outer(true, "kept"); +console.log(JSON.stringify(loose)); + +// Bindings typed by the reduced type: declared fields read as in Node. +const r = outer(true, 42); +console.log(r.status); +let status = ""; +for (const ok of [true, false]) status += outer(ok, 0).status + " "; +console.log(status.trim()); +function statusOf(ok: boolean): string { + const local = outer(ok, null); + return local.status; +} +console.log(statusOf(true), statusOf(false)); diff --git a/tests/harness/corpus-costs/7990-inferred-return-overlapping-records.ts.json b/tests/harness/corpus-costs/7990-inferred-return-overlapping-records.ts.json new file mode 100644 index 000000000..7da9b7545 --- /dev/null +++ b/tests/harness/corpus-costs/7990-inferred-return-overlapping-records.ts.json @@ -0,0 +1 @@ +{ "plain": 3700 } From c9ce961458c83e81d798e181cbb313a1b79e6df2 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 20:54:08 -0500 Subject: [PATCH 015/230] Restore the inspection context when a custom inspector throws --- .../src/frontend/lowering/lower-inspect.ts | 33 ++++++++++++------ .../compiler/src/ir/builtin-signatures.ts | 7 ++-- .../corpus/8125-inspect-custom-throws.ts.json | 6 ++++ .../8126-inspect-custom-throws.cjs.json | 6 ++++ packages/runtime/src/scr_inspect.c | 34 +++++++++++++++---- packages/runtime/src/scr_inspect.h | 8 ++--- tests/corpus/8125-inspect-custom-throws.ts | 32 +++++++++++++++++ tests/corpus/8126-inspect-custom-throws.cjs | 30 ++++++++++++++++ .../8125-inspect-custom-throws.ts.json | 1 + .../8126-inspect-custom-throws.cjs.json | 1 + 10 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8125-inspect-custom-throws.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8126-inspect-custom-throws.cjs.json create mode 100644 tests/corpus/8125-inspect-custom-throws.ts create mode 100644 tests/corpus/8126-inspect-custom-throws.cjs create mode 100644 tests/harness/corpus-costs/8125-inspect-custom-throws.ts.json create mode 100644 tests/harness/corpus-costs/8126-inspect-custom-throws.cjs.json diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 86255ca56..334c1dcac 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -1277,21 +1277,34 @@ function customInspectHelper( const resultT = found.sig.ret; const call = accessorCall(lowerer, t.className, INSPECT_CUSTOM_MEMBER, v(), args, resultT, loc); // The method runs in a fresh inspect context (insp.customCallBegin); the - // walk's own context returns before the result renders. - const callStmt = (fn: "insp.customCallBegin" | "insp.customCallEnd"): IrStmt => ({ + // walk's own context returns before the result renders. The token lets + // an enclosing end restore this walk's context when the method throws. + locals.push({ id: "tok.0", name: "tok", type: F64, mutable: false }); + const end: IrStmt = { kind: "exprStmt", - expr: { kind: "libCall", fn, args: [], type: VOID, loc }, + expr: { + kind: "libCall", + fn: "insp.customCallEnd", + args: [varRef("tok.0", F64, loc)], + type: VOID, + loc, + }, loc, - }); - const render: IrStmt[] = [...argDecls, callStmt("insp.customCallBegin")]; + }; + const render: IrStmt[] = [ + ...argDecls, + { + kind: "varDecl", + localId: "tok.0", + init: { kind: "libCall", fn: "insp.customCallBegin", args: [], type: F64, loc }, + loc, + }, + ]; if (resultT.kind === "void") { - render.push({ kind: "exprStmt", expr: call, loc }, callStmt("insp.customCallEnd")); + render.push({ kind: "exprStmt", expr: call, loc }, end); } else { locals.push({ id: "res.0", name: "res", type: resultT, mutable: false }); - render.push( - { kind: "varDecl", localId: "res.0", init: call, loc }, - callStmt("insp.customCallEnd"), - ); + render.push({ kind: "varDecl", localId: "res.0", init: call, loc }, end); } const res = (): IrExpr => varRef("res.0", resultT, loc); if (resultT.kind === "string") { diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index d8f150605..4730f283d 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -4492,9 +4492,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "insp.customIsSelf": { argTypes: [DYN, DYN], result: BOOL }, "insp.customResult": { argTypes: [DYN, F64, F64], result: STRING }, /** Around the method's call: the inspect and format calls it makes run - * in a fresh inspect context with the default options. */ - "insp.customCallBegin": { argTypes: [], result: VOID }, - "insp.customCallEnd": { argTypes: [], result: VOID }, + * in a fresh inspect context with the default options. begin answers the + * token end takes; end also discards what a call that threw left. */ + "insp.customCallBegin": { argTypes: [], result: F64 }, + "insp.customCallEnd": { argTypes: [F64], result: VOID }, /** Whether a runtime format string has a '%' before its last character. */ "insp.formatSpecifies": { argTypes: [STRING], result: BOOL }, "insp.formatSpecifiesDyn": { argTypes: [DYN], result: BOOL }, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8125-inspect-custom-throws.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8125-inspect-custom-throws.ts.json new file mode 100644 index 000000000..1dc598fd4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8125-inspect-custom-throws.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8125-inspect-custom-throws.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8126-inspect-custom-throws.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8126-inspect-custom-throws.cjs.json new file mode 100644 index 000000000..374de8db3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8126-inspect-custom-throws.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8126-inspect-custom-throws.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 9905518ab..9cfe3a3ef 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -1706,6 +1706,8 @@ typedef struct { double break_length; double compact; const ScrDyn *user_options; + size_t nsaved; /* the nested-context stack's depth before the call */ + size_t nframes; /* the frame stack's depth before the call */ } InspCallSaved; static SCR_TL InspCallSaved *g_call_saved; @@ -1721,8 +1723,13 @@ static void insp_call_saved_cleanup(void) { /* A custom inspect function's call: Node gives each util.inspect and * util.format call its own ctx, so the calls the function makes start * with no indentation, no seen objects, no colors and the default layout - * and options. end restores the walk's context. */ -void scr_insp_custom_call_begin(void) { + * and options. begin answers a token; end(token) restores the walk's + * context as it was at that begin. A call that threw skips its end, and + * an enclosing custom function's end (or the walk's, when the function + * caught the error) discards what the throw left: the contexts of calls + * that never ended and the frames of renders that never finished. */ +double scr_insp_custom_call_begin(void) { + size_t nsaved = g_nsaved; scr_insp_nested_begin(); if (g_ncall_saved == g_call_saved_cap) { if (g_call_saved_cap == 0) scr_atexit(insp_call_saved_cleanup); @@ -1730,7 +1737,10 @@ void scr_insp_custom_call_begin(void) { g_call_saved = realloc(g_call_saved, g_call_saved_cap * sizeof(InspCallSaved)); if (!g_call_saved) insp_oom(); } + double token = (double)g_ncall_saved; InspCallSaved *sv = &g_call_saved[g_ncall_saved++]; + sv->nsaved = nsaved; + sv->nframes = g_nframes; sv->colors = g_colors; sv->custom_inspect = g_custom_inspect; sv->sorted = g_sorted; @@ -1747,10 +1757,20 @@ void scr_insp_custom_call_begin(void) { g_depth_number = 0; g_user_options = NULL; scr_insp_layout(80, 3, false); + return token; } -void scr_insp_custom_call_end(void) { - InspCallSaved *sv = &g_call_saved[--g_ncall_saved]; +void scr_insp_custom_call_end(double token) { + size_t depth = (size_t)token; + if (depth >= g_ncall_saved) return; /* an enclosing end already restored it */ + InspCallSaved *sv = &g_call_saved[depth]; + g_ncall_saved = depth; + while (g_nframes > sv->nframes) { + InspFrame *f = &g_frames[--g_nframes]; + for (size_t i = 0; i < f->len; i++) scr_str_release(f->items[i]); + free(f->items); + } + g_pending_seen = NULL; g_colors = sv->colors; g_custom_inspect = sv->custom_inspect; g_no_break = sv->no_break; @@ -1758,7 +1778,7 @@ void scr_insp_custom_call_end(void) { g_depth_number = sv->depth_number; g_user_options = sv->user_options; scr_insp_layout(sv->break_length, sv->compact, sv->sorted); - scr_insp_nested_restore(); + while (g_nsaved > sv->nsaved) scr_insp_nested_restore(); } /* The style inspect.styles maps a styleType to, as [open, close] codes: @@ -2024,7 +2044,7 @@ static ScrStr *insp_custom(ScrDyn *d, double recurse, double depth, bool *thrown } args[2] = insp_inspect_value(); ScrDyn *ret; - scr_insp_custom_call_begin(); + double token = scr_insp_custom_call_begin(); if (compiled) { ScrDyn *call[4] = {d, args[0], args[1], args[2]}; ret = scr_dyn_call(fn, call, 4, "[nodejs.util.inspect.custom]"); @@ -2033,7 +2053,7 @@ static ScrStr *insp_custom(ScrDyn *d, double recurse, double depth, bool *thrown ret = scr_dyn_call(fn, args, 3, "[nodejs.util.inspect.custom]"); scr_dyn_this_pop(); } - scr_insp_custom_call_end(); + scr_insp_custom_call_end(token); for (size_t i = 0; i < 3; i++) scr_dyn_release(args[i]); scr_dyn_release(fn); if (!ret) { diff --git a/packages/runtime/src/scr_inspect.h b/packages/runtime/src/scr_inspect.h index d56f17bba..9619f0dee 100644 --- a/packages/runtime/src/scr_inspect.h +++ b/packages/runtime/src/scr_inspect.h @@ -127,10 +127,10 @@ void scr_insp_nested_restore(void); * value for the caller to restore. */ bool scr_insp_custom_inspect(bool enabled); /* Around a custom inspect function's call: begin gives the calls it makes - * a fresh inspect context with the default options; end restores the - * caller's. */ -void scr_insp_custom_call_begin(void); -void scr_insp_custom_call_end(void); + * a fresh inspect context with the default options and answers a token; + * end(token) restores the caller's, also after calls that threw. */ +double scr_insp_custom_call_begin(void); +void scr_insp_custom_call_end(double token); /* How the unbounded depth of the renders that follow was given, for custom * inspect functions: 0 Infinity, 1 null, 2 undefined. Answers the * previous kind for the caller to restore. */ diff --git a/tests/corpus/8125-inspect-custom-throws.ts b/tests/corpus/8125-inspect-custom-throws.ts new file mode 100644 index 000000000..33077a32b --- /dev/null +++ b/tests/corpus/8125-inspect-custom-throws.ts @@ -0,0 +1,32 @@ +// A [util.inspect.custom] method that throws inside an outer inspect: an +// enclosing custom method that catches the error, and the walk around it, +// continue with their own indentation, circular references and colors. +import * as util from "node:util"; +class Throws { + [util.inspect.custom](): string { + throw new Error("inner"); + } +} +class Catches { + [util.inspect.custom](): string { + let note = ""; + try { + note = util.inspect({ a: { b: [new Throws()] } }); + } catch (e) { + note = "caught " + (e as Error).message; + } + return note + " then " + util.inspect({ x: { y: [1, 2] } }); + } +} +const shared = { k: 1 }; +type Outer = { first: { deep: Catches }; list: unknown[]; s: object; t: object; me?: Outer }; +const outer: Outer = { first: { deep: new Catches() }, list: [new Catches(), { z: 3 }], s: shared, t: shared }; +outer.me = outer; +console.log(util.inspect(outer, { breakLength: 50 })); +console.log(JSON.stringify(util.inspect([new Catches(), "s"], { colors: true }))); +try { + console.log(util.inspect({ a: { b: new Throws() } })); +} catch (e) { + console.log("outer caught", (e as Error).message); +} +console.log({ after: { deep: { x: 1 } }, list: [1, 2, 3] }); diff --git a/tests/corpus/8126-inspect-custom-throws.cjs b/tests/corpus/8126-inspect-custom-throws.cjs new file mode 100644 index 000000000..8d4e97608 --- /dev/null +++ b/tests/corpus/8126-inspect-custom-throws.cjs @@ -0,0 +1,30 @@ +// A [util.inspect.custom] method that throws inside an outer inspect: an +// enclosing custom method that catches the error, and the walk around it, +// continue with their own indentation and colors. +const util = require("node:util"); +class Throws { + [util.inspect.custom]() { + throw new Error("inner"); + } +} +class Catches { + [util.inspect.custom]() { + let note = ""; + try { + note = util.inspect({ a: { b: [new Throws()] } }); + } catch (e) { + note = "caught " + e.message; + } + return note + " then " + util.inspect({ x: { y: [1, 2] } }); + } +} +const shared = { k: 1 }; +const outer = { first: { deep: new Catches() }, list: [new Catches(), { z: 3 }], s: shared, t: shared }; +console.log(util.inspect(outer, { breakLength: 50 })); +console.log(JSON.stringify(util.inspect([new Catches(), "s"], { colors: true }))); +try { + console.log(util.inspect({ a: { b: new Throws() } })); +} catch (e) { + console.log("outer caught", e.message); +} +console.log({ after: { deep: { x: 1 } }, list: [1, 2, 3] }); diff --git a/tests/harness/corpus-costs/8125-inspect-custom-throws.ts.json b/tests/harness/corpus-costs/8125-inspect-custom-throws.ts.json new file mode 100644 index 000000000..7d8f08f56 --- /dev/null +++ b/tests/harness/corpus-costs/8125-inspect-custom-throws.ts.json @@ -0,0 +1 @@ +{ "plain": 3090, "san": 5295 } diff --git a/tests/harness/corpus-costs/8126-inspect-custom-throws.cjs.json b/tests/harness/corpus-costs/8126-inspect-custom-throws.cjs.json new file mode 100644 index 000000000..49ea4c6d4 --- /dev/null +++ b/tests/harness/corpus-costs/8126-inspect-custom-throws.cjs.json @@ -0,0 +1 @@ +{ "plain": 2826, "san": 4308 } From 01e88d0b4862fb9d3b5c86e1e0b7a5f372e774b8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 21:30:21 -0500 Subject: [PATCH 016/230] Support named properties and Symbol.species on typed arrays held as untyped values --- packages/cli/test/native-link-info.test.ts | 4 +- packages/compiler/src/backend/runtime-abi.ts | 4 +- .../src/backend/runtime-pack-targets.test.ts | 2 +- .../compiler/src/backend/runtime-pack.test.ts | 2 +- .../src/frontend/lowering/lower-calls.ts | 5 +- .../compiler/test/llvm-runtime-abi.test.ts | 7 +- .../test/native-codegen-integration.test.ts | 4 +- .../7753-typed-array-named-properties.js.json | 6 + .../runtime-darwin-arm64/scripts/build.mjs | 2 +- .../runtime-pack-common/scripts/build.mjs | 2 +- packages/runtime/src/scr_bytes.c | 13 +- packages/runtime/src/scr_bytes.h | 8 + packages/runtime/src/scr_console.c | 2 +- packages/runtime/src/scr_core.h | 2 +- packages/runtime/src/scr_dyn.h | 6 + packages/runtime/src/scr_dyn_invoke.c | 14 + packages/runtime/src/scr_inspect.c | 79 ++- packages/runtime/src/scr_json.c | 497 ++++++++++++++++-- packages/runtime/src/scr_symbol.c | 10 + .../7752-typed-array-dynamic-methods.js | 12 + .../7753-typed-array-named-properties.js | 143 +++++ .../7753-typed-array-named-properties.js.json | 1 + .../typed-array-property-refusals.test.ts | 64 +++ 23 files changed, 836 insertions(+), 53 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7753-typed-array-named-properties.js.json create mode 100644 tests/corpus/7753-typed-array-named-properties.js create mode 100644 tests/harness/corpus-costs/7753-typed-array-named-properties.js.json create mode 100644 tests/harness/typed-array-property-refusals.test.ts diff --git a/packages/cli/test/native-link-info.test.ts b/packages/cli/test/native-link-info.test.ts index 8ac579449..187dbc0d5 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: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 40, marker: "scr_runtime_abi_v40" }, 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_v37"); + expect(error?.stderr).toContain("scr_runtime_abi_v40"); }); }); diff --git a/packages/compiler/src/backend/runtime-abi.ts b/packages/compiler/src/backend/runtime-abi.ts index 11bde6c2b..229d8ec33 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 = 37 as const; -export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v37" as const; +export const RUNTIME_ABI_VERSION = 40 as const; +export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v40" 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-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 9846e8d89..6e7a09fb3 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: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 40, marker: "scr_runtime_abi_v40" }, 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 e2b24f8ca..b2f5995fb 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -128,7 +128,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 40, marker: "scr_runtime_abi_v40" }, compiler: { command: "clang", identity: "fixture clang", diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 073ebed91..9eaf9535b 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1087,7 +1087,10 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if (!argNode || (ts.isIdentifier(argNode) && argNode.text === "undefined")) { return { kind: "libCall", fn: "sym.newAnon", args: [], type: SYMBOL_T, loc }; } - const desc = lowerer.lowerExpr(argNode); + let desc = lowerer.lowerExpr(argNode); + // A number or boolean description is its ToString. + if (desc.type.kind === "f64" || desc.type.kind === "bool") + desc = lowerer.ensureString(desc, argNode); if (desc.type.kind !== "string") { lowerer.noLowering( `Symbol with a '${lowerer.fmt(desc.type)}' description`, diff --git a/packages/compiler/test/llvm-runtime-abi.test.ts b/packages/compiler/test/llvm-runtime-abi.test.ts index 6cfd862b7..a457feb72 100644 --- a/packages/compiler/test/llvm-runtime-abi.test.ts +++ b/packages/compiler/test/llvm-runtime-abi.test.ts @@ -355,12 +355,13 @@ describe("LLVM backend declares match scr_runtime.h prototypes", () => { #include "scr_runtime.h" /* LLVM emits: %ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, i8, ptr }. - * Keep every ABI fact used by its field-index GEPs explicit here. */ + * Keep every ABI fact used by its field-index GEPs explicit here. The C + * struct continues with fields only the runtime reads. */ _Static_assert(sizeof(size_t) == 8, "LLVM ScrBytes expects 64-bit size_t"); _Static_assert(sizeof(void *) == 8, "LLVM ScrBytes expects 64-bit pointers"); _Static_assert(sizeof(ScrBytesElem) == 4, "LLVM ScrBytes elem field is i32"); _Static_assert(_Alignof(ScrBytes) == 8, "LLVM ScrBytes alignment changed"); -_Static_assert(sizeof(ScrBytes) == 56, "LLVM ScrBytes size changed"); +_Static_assert(sizeof(ScrBytes) == 64, "ScrBytes size changed"); _Static_assert(offsetof(ScrBytes, rc) == 0, "LLVM ScrBytes.rc offset changed"); _Static_assert(offsetof(ScrBytes, len) == 8, "LLVM ScrBytes.len offset changed"); _Static_assert(offsetof(ScrBytes, elem) == 16, "LLVM ScrBytes.elem offset changed"); @@ -370,7 +371,9 @@ _Static_assert(offsetof(ScrBytes, is_buffer) == 40, "LLVM ScrBytes.is_buffer off _Static_assert(offsetof(ScrBytes, is_data_view) == 41, "LLVM ScrBytes.is_data_view offset changed"); _Static_assert(offsetof(ScrBytes, external) == 42, "LLVM ScrBytes.external offset changed"); _Static_assert(offsetof(ScrBytes, untransferable) == 43, "LLVM ScrBytes.untransferable offset changed"); +_Static_assert(offsetof(ScrBytes, properties_traced) == 44, "ScrBytes.properties_traced must stay in padding"); _Static_assert(offsetof(ScrBytes, shared) == 48, "LLVM ScrBytes.shared offset changed"); +_Static_assert(offsetof(ScrBytes, properties) == 56, "ScrBytes.properties must follow the LLVM prefix"); `, ); const driver = resolveCc(); diff --git a/packages/compiler/test/native-codegen-integration.test.ts b/packages/compiler/test/native-codegen-integration.test.ts index cecebb49c..d35cbfefd 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_v37"); + expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v40"); 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_v37"); + expect(undefinedSymbols).toContain("_scr_runtime_abi_v40"); }); test("helper and clang object paths retain the same outbound FFI C ABI", async () => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7753-typed-array-named-properties.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7753-typed-array-named-properties.js.json new file mode 100644 index 000000000..74a4316d9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7753-typed-array-named-properties.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7753-typed-array-named-properties.js" + ], + "diags": [] +} diff --git a/packages/runtime-darwin-arm64/scripts/build.mjs b/packages/runtime-darwin-arm64/scripts/build.mjs index e492c36b4..27cafe022 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: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 40, marker: "scr_runtime_abi_v40" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs index e22f2a37c..318880a54 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: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: 40, marker: "scr_runtime_abi_v40" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index 3926bf83e..e761641c2 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -190,6 +190,8 @@ ScrBytes *scr_bytes_take_data(uint8_t *data, size_t len) { b->external = false; b->untransferable = false; b->shared = NULL; + b->properties = NULL; + b->properties_traced = false; #ifdef SCR_RC_AUDIT scr_live_bytes++; #endif @@ -304,6 +306,7 @@ void scr_bytes_release(ScrBytes *b) { if (!b || b->rc == SIZE_MAX) return; /* NULL: an uninitialized `let` local */ if (--b->rc == 0) { scr_weak_dispose_inline(b); + if (b->properties) scr_dyn_release(b->properties); if (b->backing) { scr_bytes_release(b->backing); /* a view: data points into the owner */ } else if (b->shared) { @@ -740,6 +743,8 @@ ScrBytes *scr_bytes_subarray(ScrBytes *b, double start, double end) { v->external = false; v->untransferable = false; v->shared = owner->shared; + v->properties = NULL; + v->properties_traced = false; #ifdef SCR_RC_AUDIT scr_live_bytes++; #endif @@ -866,7 +871,9 @@ ScrBytes *scr_bytes_from_dyn(ScrBytesElem elem, const ScrDyn *value, bool from) if (value->kind == SCR_DYN_BYTES) return scr_bytes_convert(elem, value->v.bytes); if (!from && scr_buffer_storage_is(value)) return scr_array_buffer_view(elem, value, scr_dyn_undefined(), scr_dyn_undefined()); if (!from && (value->kind == SCR_DYN_NUM || value->kind == SCR_DYN_BOOL || - value->kind == SCR_DYN_STR || value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF)) { + value->kind == SCR_DYN_STR || value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF || + value->kind == SCR_DYN_SYMBOL || value->kind == SCR_DYN_BIGINT)) { + /* ToIndex: a Symbol or BigInt length throws ToNumber's TypeError. */ double number; if (!scr_dyn_number_coerce_js(value, &number)) return NULL; return scr_bytes_new(elem, number); @@ -992,6 +999,8 @@ ScrBytes *scr_dataview_new(ScrBytes *src, double byte_off, bool has_len, double v->external = false; v->untransferable = false; v->shared = owner->shared; + v->properties = NULL; + v->properties_traced = false; #ifdef SCR_RC_AUDIT scr_live_bytes++; #endif @@ -2883,6 +2892,8 @@ static ScrBytes *scr_pool_take(size_t len) { v->external = false; v->untransferable = false; v->shared = NULL; + v->properties = NULL; + v->properties_traced = false; #ifdef SCR_RC_AUDIT scr_live_bytes++; #endif diff --git a/packages/runtime/src/scr_bytes.h b/packages/runtime/src/scr_bytes.h index 7c0a935cc..64e3e299a 100644 --- a/packages/runtime/src/scr_bytes.h +++ b/packages/runtime/src/scr_bytes.h @@ -65,7 +65,15 @@ typedef struct ScrBytes { bool is_data_view; /* DataView is distinct from a numeric typed array. */ bool external; /* Root aliases foreign memory; releasing it never frees data. */ bool untransferable; /* A Buffer pool slab: its ArrayBuffer cannot be transferred. */ + /* The cycle collector's last trace of `properties` from the one checked + * value that held this view (see scr_dyn_trace_v). In padding, so the + * offsets compiled code uses hold. */ + bool properties_traced; ScrSharedBytes *shared; /* Neutral backing, borrowed by views from their local owner. */ + /* Named properties a program adds to the view (`ta.foo = 1`, + * Object.defineProperty): a checked object, NULL until the first one. + * Runtime-only and last, so compiled code's field indices hold. */ + struct ScrDyn *properties; } ScrBytes; /* Copies out of and into shared storage, made of relaxed atomic accesses diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c index 086340014..06cc7ccd7 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_v37(void) {} +void scr_runtime_abi_v40(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 3707b8d50..bdb9942fe 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_v37(void); +void scr_runtime_abi_v40(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_dyn.h b/packages/runtime/src/scr_dyn.h index bd76c12e2..b4955220a 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -917,6 +917,9 @@ void scr_dyn_install_iterator_symbol(ScrDyn *key); 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); +void scr_dyn_install_species_symbol(ScrDyn *key); +/* Records each well-known symbol the symbol unit creates. */ +void scr_dyn_install_well_known_symbol(ScrDyn *key); /* True when key is Symbol.asyncDispose (registered by the symbol unit when * the program names it); handle symbol_get ops outside that unit use it. */ bool scr_dyn_is_async_dispose_symbol(const ScrDyn *key); @@ -1003,6 +1006,9 @@ ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrS extern SCR_TL bool (*scr_dyn_global_construct_fn)(const ScrDyn *callee, const ScrDyn *args, ScrDyn **result); ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key); +/* A typed array's, Buffer's or DataView's own named property (+1), or NULL + * without an exception when it has none. */ +ScrDyn *scr_bytes_named_get(const ScrDyn *view, const char *key, size_t key_len); /* The Buffer-flavored twin (stream chunks): string coercion/toString * decode utf8 instead of joining elements. */ ScrDyn *scr_dyn_new_buffer(const ScrBytes *b); diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index b76fe38d2..96035cccc 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1602,6 +1602,20 @@ static ScrDyn *scr_dyn_invoke_impl( if (recv->kind == SCR_DYN_BYTES) { ScrBytes *bytes = recv->v.bytes; + /* An own property of the view shadows the prototype's method. */ + ScrDyn *member = bytes->properties ? scr_bytes_named_get(recv, method, strlen(method)) : NULL; + if (!member && scr_exc_pending()) return NULL; + if (member) { + ScrDyn *result = NULL; + if (!scr_dyn_is_callable(member)) dyn_throw_not_fn(what); + else { + scr_dyn_this_push_dyn(recv); + result = scr_dyn_call(member, args, argc, what); + scr_dyn_this_pop(); + } + scr_dyn_release(member); + return result; + } if (bytes->is_data_view) { static const char *const kinds[] = { "Uint8", "Int8", "Uint16", "Int16", "Uint32", "Int32", "Float32", "Float64", "BigUint64", "BigInt64", "Float16" }; const bool get = !strncmp(method, "get", 3); diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 011a1f582..ce85c00ff 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -743,6 +743,52 @@ ScrStr *scr_insp_regex(ScrRegex *re) { return ib_take(&out); } +/* Named properties of a typed array, Buffer or DataView (its + * ScrBytes.properties table): slot k is the k-th string-keyed entry, then + * the k-th symbol key. */ +static size_t insp_view_property_slots(const ScrDyn *props) { + if (!props) return 0; + return props->v.obj.len + (props->symbol_keys ? props->symbol_keys->v.arr.len : 0); +} + +static ScrStr *insp_accessor_tag(const ScrDynEntry *ent); +static void insp_key_into(InspBuf *b, const char *key, size_t len); + +/* Slot k's `key: value` entry when it is enumerable, else NULL. */ +static ScrStr *insp_view_property(ScrDyn *props, size_t k, double recurse, double depth) { + const ScrDynEntry *ent; + ScrStr *key = NULL; + if (k < props->v.obj.len) { + ent = &props->v.obj.entries[k]; + } else { + ScrDyn *symbol = props->symbol_keys->v.arr.items[k - props->v.obj.len]; + ent = scr_dyn_own_symbol_entry(props, symbol); + if (ent && ent->enumerable) key = scr_insp_dyn(symbol, recurse, depth); + } + if (!ent || !ent->enumerable) return NULL; + ScrStr *val = ent->accessor ? insp_accessor_tag(ent) : scr_insp_dyn(ent->value, recurse, depth); + InspBuf eb = {0}; + if (key) ib_bytes(&eb, key->data, key->len); + else insp_key_into(&eb, ent->key, ent->key_len); + ib_cstr(&eb, ": "); + ib_bytes(&eb, val->data, val->len); + scr_str_release(key); + scr_str_release(val); + return ib_take(&eb); +} + +static size_t insp_view_property_count(ScrDyn *props) { + size_t count = 0; + for (size_t k = 0; k < insp_view_property_slots(props); k++) { + if (k < props->v.obj.len) count += props->v.obj.entries[k].enumerable; + else { + const ScrDynEntry *ent = scr_dyn_own_symbol_entry(props, props->symbol_keys->v.arr.items[k - props->v.obj.len]); + count += ent && ent->enumerable; + } + } + return count; +} + /* Buffer: the custom-inspect hex form ``, capped at * INSPECT_MAX_BYTES (50); renders fully at any depth (custom inspection * runs before the depth check in Node). u8 element kind only (the @@ -765,6 +811,17 @@ ScrStr *scr_insp_buffer(ScrBytes *b) { remaining > 1 ? "s" : ""); ib_bytes(&out, more, (size_t)n); } + /* Named properties follow as `, key: value`, rendered as the members of + * an object at depth 0 (Buffer's custom inspect). */ + bool first = true; + for (size_t k = 0; k < insp_view_property_slots(b->properties); k++) { + ScrStr *entry = insp_view_property(b->properties, k, 1, 2); + if (!entry) continue; + if (!first || b->len) ib_cstr(&out, ", "); + first = false; + ib_bytes(&out, entry->data, entry->len); + scr_str_release(entry); + } ib_char(&out, '>'); return ib_take(&out); } @@ -848,6 +905,12 @@ static ScrStr *scr_insp_data_view(const ScrBytes *b, double recurse, double dept entry = ib_take(&eb); scr_insp_entry(entry, false); scr_str_release(entry); + for (size_t k = 0; k < insp_view_property_slots(b->properties); k++) { + entry = insp_view_property(b->properties, k, recurse + 1, depth); + if (!entry) continue; + scr_insp_entry(entry, false); + scr_str_release(entry); + } ScrStr *base = scr_str_new("", 0); ScrStr *b0 = scr_str_new("DataView {", 10); ScrStr *b1 = scr_str_new("}", 1); @@ -1455,7 +1518,10 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth) { char prefix[48]; int pn = snprintf(prefix, sizeof prefix, "%s(%zu) [", scr_bytes_elem_name(b->elem), b->len); - if (b->len == 0) { + /* Named properties follow the elements as `key: value` entries. */ + ScrDyn *props = b->properties; + size_t nprops = insp_view_property_count(props); + if (b->len == 0 && nprops == 0) { InspBuf out = {0}; ib_bytes(&out, prefix, (size_t)pn); ib_char(&out, ']'); @@ -1479,10 +1545,19 @@ ScrStr *scr_insp_typed_array(ScrBytes *b, double recurse, double depth) { scr_insp_entry(m, true); scr_str_release(m); } + size_t entries = shown + (more ? 1 : 0); + for (size_t k = 0; nprops && k < insp_view_property_slots(props); k++) { + ScrStr *entry = insp_view_property(props, k, recurse + 1, depth); + if (!entry) continue; + /* Node's grouping pads by the element at the entry's output position. */ + scr_insp_entry(entry, entries < b->len); + scr_str_release(entry); + entries++; + } ScrStr *base = scr_str_new("", 0); ScrStr *b0 = scr_str_new(prefix, (size_t)pn); ScrStr *b1 = scr_str_new("]", 1); - ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, true, more); + ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, true, nprops ? entries > 100 : more); scr_str_release(base); scr_str_release(b0); scr_str_release(b1); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 4af8c63b5..928d43490 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -202,6 +202,33 @@ void scr_dyn_install_has_instance_symbol(ScrDyn *key) { scr_atexit(scr_has_instance_symbol_cleanup); } +/* Symbol.species, installed when the program first evaluates it: no + * constructor names a species before then. */ +static SCR_TL ScrDyn *scr_species_symbol; +static void scr_species_symbol_cleanup(void) { + scr_dyn_release(scr_species_symbol); + scr_species_symbol = NULL; +} +void scr_dyn_install_species_symbol(ScrDyn *key) { + if (scr_species_symbol) return; + scr_species_symbol = scr_dyn_retain(key); + scr_atexit(scr_species_symbol_cleanup); +} + +/* The well-known symbols the program has created. They live until exit, + * so their identities are compared without a reference. */ +static SCR_TL const ScrSym *scr_well_known_symbols[24]; +static SCR_TL size_t scr_well_known_count; +void scr_dyn_install_well_known_symbol(ScrDyn *key) { + if (scr_well_known_count < sizeof scr_well_known_symbols / sizeof scr_well_known_symbols[0]) + scr_well_known_symbols[scr_well_known_count++] = key->v.symbol.value; +} +static bool scr_dyn_well_known_symbol(const ScrDyn *key) { + for (size_t i = 0; i < scr_well_known_count; i++) + if (scr_well_known_symbols[i] == key->v.symbol.value) return true; + return false; +} + void scr_dyn_install_iterator_symbol(ScrDyn *key) { if (scr_iterator_symbol) return; scr_iterator_symbol = scr_dyn_retain(key); @@ -880,6 +907,17 @@ void scr_dyn_trace_v(void *ptr, ScrTraceVisit visit, void *ctx) { } break; case SCR_DYN_FUNC: visit(d->v.fn.clo, ctx); break; + case SCR_DYN_BYTES: { + /* A view's named properties belong to its ScrBytes. Only a view this + * value alone holds owns that edge; a shared view's table stays out of + * trial deletion. Teardown reads the snapshot, not the count. */ + ScrBytes *bytes = d->v.bytes; + if (bytes->properties) { + bytes->properties_traced = bytes->rc == 1; + if (bytes->properties_traced) visit(bytes->properties, ctx); + } + break; + } case SCR_DYN_PROXY: visit(d->v.proxy.target, ctx); visit(d->v.proxy.handler, ctx); @@ -1144,6 +1182,8 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { scr_str_release(d->v.str); break; case SCR_DYN_BYTES: + /* The collector frees a traced table itself. */ + if (collected && d->v.bytes->properties && d->v.bytes->properties_traced) d->v.bytes->properties = NULL; scr_bytes_release(d->v.bytes); break; case SCR_DYN_ARR: @@ -2417,10 +2457,27 @@ static bool scr_dyn_typed_ref_view_answer(ScrDyn *object, bool (*query)(const Sc return true; } +static ScrDyn *scr_bytes_properties(const ScrDyn *v); static bool scr_dyn_is_extensible_view(const ScrDyn *view) { + if (view->kind == SCR_DYN_BYTES) return !view->v.bytes->properties || !view->v.bytes->properties->non_extensible; return (view->kind == SCR_DYN_OBJ || view->kind == SCR_DYN_ARR) && !view->non_extensible; } +/* A view's integrity state lives on its named-property table. A typed array + * with elements cannot be frozen, and cannot be sealed while extensible: as + * in V8, the named properties are sealed or frozen and the view made + * non-extensible before the TypeError. */ +static bool scr_bytes_integrity_fence(const ScrDyn *view, const char *verb) { + if (view->v.bytes->is_data_view || view->v.bytes->len == 0) return false; + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "Cannot "); + scr_jb_puts(&message, verb); + scr_jb_puts(&message, " array buffer views with elements"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message)); + return true; +} + /* A plain function's own properties (name, length, prototype, and the ones * a program adds) live in its property table, which carries the function's * integrity state too: freezing or sealing the function freezes or seals @@ -2467,6 +2524,7 @@ ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object) { if (object->kind == SCR_DYN_ARR && object->v.arr.properties) object->v.arr.properties->non_extensible = true; } + if (object->kind == SCR_DYN_BYTES) scr_bytes_properties(object)->non_extensible = true; return scr_dyn_retain(object); } @@ -2495,6 +2553,12 @@ ScrDyn *scr_dyn_seal(ScrDyn *object) { return scr_dyn_retain(object); } if (scr_dyn_class_integrity_fence(object, "Object.seal")) return NULL; + if (object->kind == SCR_DYN_BYTES) { + bool extensible = scr_dyn_is_extensible_view(object); + scr_dyn_release(scr_dyn_seal(scr_bytes_properties(object))); + if (extensible && scr_bytes_integrity_fence(object, "seal")) return NULL; + return scr_dyn_retain(object); + } if (object->symbol_properties) scr_dyn_release(scr_dyn_seal(object->symbol_properties)); ScrDyn *table = scr_dyn_fn_integrity_table(object); if (table) { @@ -2530,6 +2594,8 @@ bool scr_dyn_is_sealed(const ScrDyn *object) { if (object->kind == SCR_DYN_ARR) return object->non_extensible && (object->v.arr.len == 0 || object->v.arr.sealed) && (!object->v.arr.properties || scr_dyn_is_sealed(object->v.arr.properties)); + /* V8 answers from a typed array's named properties alone. */ + if (object->kind == SCR_DYN_BYTES) return object->v.bytes->properties && scr_dyn_is_sealed(object->v.bytes->properties); if (object->kind != SCR_DYN_OBJ) return true; if (!object->non_extensible) return false; for (size_t i = 0; i < object->v.obj.len; i++) if (object->v.obj.entries[i].configurable) return false; @@ -2736,6 +2802,65 @@ static bool scr_dyn_data_view_proto_name(const ScrStr *key) { return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); } +/* A canonical numeric string ("1", "-0", "1.5", "Infinity"): typed arrays + * answer those keys as element indices, never as named properties. */ +static bool scr_bytes_numeric_key(const ScrStr *key, double *index) { + if (key->len == 2 && memcmp(key->data, "-0", 2) == 0) { *index = -0.0; return true; } + *index = scr_string_to_number(key); + char spelling[32]; + size_t length = scr_f64_to_str(*index, spelling); + return length == key->len && memcmp(spelling, key->data, length) == 0; +} + +/* Whether the view has an own named property `key`. */ +static bool scr_bytes_has_named(const ScrDyn *v, const ScrStr *key) { + const ScrDyn *properties = v->v.bytes->properties; + return properties && scr_dyn_obj_get(properties, key->data, key->len); +} + +/* The view's own named property `key` read with the view as a getter's + * `this` (+1), or NULL without an exception when the view has none. Only + * reads the table, so callers do not link the typed array methods. */ +ScrDyn *scr_bytes_named_get(const ScrDyn *view, const char *key, size_t key_len) { + const ScrDyn *properties = view->v.bytes->properties; + if (!properties || !scr_dyn_obj_get(properties, key, key_len)) return NULL; + return scr_dyn_obj_read_receiver(properties, key, key_len, view); +} + +/* The view's named-property table, created on first use. Borrowed. The + * table belongs to the ScrBytes, which several checked values can wrap. */ +static ScrDyn *scr_bytes_properties(const ScrDyn *v) { + ScrBytes *bytes = v->v.bytes; + if (!bytes->properties) bytes->properties = scr_dyn_new_obj_null_proto(); + return bytes->properties; +} + +/* Accessors without a setter on the view's prototype chain: writing one + * that the view does not own throws V8's strict-mode TypeError. */ +static bool scr_bytes_getter_name(const ScrDyn *v, const ScrStr *key) { + static const char *const names[] = { "byteLength", "byteOffset", "buffer", "length", "parent", "offset" }; + size_t count = v->v.bytes->is_data_view ? 3 : v->buffer ? 6 : 4; + return scr_dyn_name_in(key, names, count); +} + +/* Keys that the runtime's conversions, iteration and printing of a view + * read from its prototype without consulting its own properties. */ +static bool scr_bytes_protocol_name(const ScrStr *key) { + static const char *const names[] = { "toString", "valueOf", "toLocaleString", "toJSON", "join" }; + return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); +} + +static void scr_bytes_protocol_fence(const char *key, size_t key_len) { + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "defining "); + scr_jb_write(&message, key, key_len); + scr_jb_puts(&message, " on a typed array, Buffer or DataView is not supported yet"); + ScrStr *text = scr_jb_finish(&message); + scr_throw_error_msg_code(SCR_ERR_ERROR, text->data, text->len, "SC2020"); + scr_str_release(text); +} + /* `in` on a typed array, Buffer or DataView. On a typed array, a canonical * numeric key (one that ToString(ToNumber(key)) reproduces, or "-0") * answers by bounds alone: an integral index, not -0, below the length. @@ -2743,6 +2868,7 @@ static bool scr_dyn_data_view_proto_name(const ScrStr *key) { * Buffer, or Object.prototype. A DataView has no indexed elements: its keys * name members of DataView.prototype or Object.prototype. */ static bool scr_dyn_bytes_in(const ScrDyn *v, const ScrStr *key) { + if (scr_bytes_has_named(v, key)) return true; if (v->v.bytes->is_data_view) return scr_dyn_data_view_proto_name(key) || scr_dyn_object_proto_name(key); if (key->len == 2 && memcmp(key->data, "-0", 2) == 0) return false; double index = scr_string_to_number(key); @@ -2858,6 +2984,19 @@ ScrDyn *scr_dyn_own_descriptor(const ScrDyn *value, const ScrStr *key) { scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(false)); return out; } + if (value->kind == SCR_DYN_BYTES && scr_bytes_has_named(value, key)) + return scr_dyn_own_descriptor(value->v.bytes->properties, key); + if (value->kind == SCR_DYN_BYTES && !value->v.bytes->is_data_view) { + if (!scr_dyn_canonical_own_index(key, value->v.bytes->len)) return scr_dyn_retain(scr_dyn_undefined()); + size_t index = 0; + for (size_t i = 0; i < key->len; i++) index = index * 10 + (size_t)(key->data[i] - '0'); + ScrDyn *out = scr_dyn_new_obj(); + scr_dyn_obj_set(out, "value", 5, scr_dyn_new_num(scr_bytes_get(value->v.bytes, (double)index))); + scr_dyn_obj_set(out, "writable", 8, scr_dyn_new_bool(true)); + scr_dyn_obj_set(out, "enumerable", 10, scr_dyn_new_bool(true)); + scr_dyn_obj_set(out, "configurable", 12, scr_dyn_new_bool(true)); + return out; + } if (value->kind == SCR_DYN_ARR) { if (value->v.arr.properties && scr_dyn_obj_get(value->v.arr.properties, key->data, key->len)) @@ -4847,6 +4986,55 @@ static double *dyn_bytes_values(const ScrBytes *b, size_t n) { return values; } +/* SpeciesConstructor of a typed array: NULL (and no exception) for the + * default constructor of its kind, else the species constructor (+1). + * Only a `constructor` the program set on the view can differ. */ +static ScrDyn *scr_bytes_species(ScrDyn *recv, bool *failed) { + *failed = false; + ScrStr *name = scr_str_new("constructor", 11); + bool own = scr_bytes_has_named(recv, name); + ScrDyn *constructor = own ? scr_dyn_obj_read_receiver(recv->v.bytes->properties, name->data, name->len, recv) : NULL; + scr_str_release(name); + if (!own) return NULL; + if (!constructor) { *failed = true; return NULL; } + if (constructor->kind == SCR_DYN_UNDEF) { scr_dyn_release(constructor); return NULL; } + if (constructor->kind != SCR_DYN_OBJ && constructor->kind != SCR_DYN_FUNC) { + scr_dyn_release(constructor); + static const char message[] = "object.constructor is not an object"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + *failed = true; + return NULL; + } + ScrDyn *species = scr_species_symbol ? scr_dyn_symbol_key_get(constructor, scr_species_symbol, true) : NULL; + scr_dyn_release(constructor); + if (scr_exc_pending()) { scr_dyn_release(species); *failed = true; return NULL; } + if (!species || species->kind == SCR_DYN_UNDEF || species->kind == SCR_DYN_NULL || + scr_bytes_ctor_elem(species) == (int)recv->v.bytes->elem) { + scr_dyn_release(species); + return NULL; + } + if (!scr_dyn_is_callable(species)) { + scr_dyn_release(species); + static const char message[] = "object.constructor[Symbol.species] is not a constructor"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + *failed = true; + return NULL; + } + return species; +} + +/* TypedArraySpeciesCreate(recv, [n]): a new array of the receiver's kind + * (and Buffer flavor), or the species constructor's checked result. */ +static ScrDyn *scr_bytes_species_create(ScrDyn *recv, size_t n) { + bool failed; + ScrDyn *species = scr_bytes_species(recv, &failed); + if (failed) return NULL; + if (!species) return dyn_bytes_wrap(recv, scr_bytes_new(recv->v.bytes->elem, (double)n)); + ScrDyn *result = scr_bytes_static_create(species, n); + scr_dyn_release(species); + return result; +} + ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *args, size_t argc, bool *handled) { ScrBytes *bytes = recv->v.bytes; size_t len = bytes->len; @@ -4866,11 +5054,41 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar if (scr_exc_pending()) return NULL; double endD = scr_dyn_bytes_index_arg(args, argc, 1, (double)blen); if (scr_exc_pending()) return NULL; - ScrBytes *out = recv->buffer || scr_dyn_bytes_named(method, "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); + const bool subarray = recv->buffer || scr_dyn_bytes_named(method, "subarray"); + bool failed; + ScrDyn *species = recv->buffer ? NULL : scr_bytes_species(recv, &failed); + if (!recv->buffer && failed) return NULL; + ScrBytes *out = subarray ? scr_bytes_subarray(bytes, startD, endD) : scr_bytes_slice(bytes, startD, endD); + if (!species) { + ScrDyn *d = recv->buffer ? scr_dyn_new_buffer(out) : scr_dyn_new_bytes(out); + scr_bytes_release(out); + return d; + } + ScrDyn *result; + if (subarray) { + /* The species constructor views the same buffer. */ + ScrDyn *ctor_args = scr_dyn_new_arr(); + scr_dyn_arr_push(ctor_args, scr_array_buffer_from_bytes(bytes)); + scr_dyn_arr_push(ctor_args, scr_dyn_new_num(scr_bytes_byte_offset(out))); + scr_dyn_arr_push(ctor_args, scr_dyn_new_num((double)out->len)); + ScrStr *what = scr_str_new("TypedArray species", 18); + result = scr_dyn_construct(species, ctor_args, what); + scr_str_release(what); + scr_dyn_release(ctor_args); + if (result && (result->kind != SCR_DYN_BYTES || result->v.bytes->is_data_view)) { + scr_dyn_release(result); + result = NULL; + static const char message[] = "this is not a typed array."; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + } + } else { + result = scr_bytes_static_create(species, out->len); + for (size_t i = 0; result && i < out->len; i++) + scr_bytes_set(result->v.bytes, (double)i, scr_bytes_get(out, (double)i)); + } + scr_dyn_release(species); scr_bytes_release(out); - return d; + return result; } if (scr_dyn_bytes_named(method, "set")) { double offset = scr_dyn_bytes_index_arg(args, argc, 1, 0); @@ -4908,6 +5126,22 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar scr_str_release(joined); return out; } + if (scr_dyn_bytes_named(method, "toLocaleString")) { + /* Each element's Number.prototype.toLocaleString, joined by ",". */ + ScrJsonBuf b; + scr_jb_init(&b); + for (size_t i = 0; i < len; i++) { + if (i > 0) scr_jb_putc(&b, ','); + if (i >= bytes->len) continue; + ScrStr *text = scr_intl_num_format_en_us(scr_bytes_get(bytes, (double)i)); + scr_jb_write(&b, text->data, text->len); + scr_str_release(text); + } + ScrStr *joined = scr_jb_finish(&b); + ScrDyn *out = scr_dyn_new_str(joined); + scr_str_release(joined); + return out; + } if (scr_dyn_bytes_named(method, "keys") || scr_dyn_bytes_named(method, "values") || scr_dyn_bytes_named(method, "entries")) return scr_dyn_native_handle_iterator(recv, scr_dyn_bytes_named(method, "keys") ? 1 : scr_dyn_bytes_named(method, "entries") ? 2 : 0); if (scr_dyn_bytes_named(method, "indexOf") || scr_dyn_bytes_named(method, "lastIndexOf") || scr_dyn_bytes_named(method, "includes")) { @@ -4946,7 +5180,8 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar if (!scr_dyn_bytes_cb_check(args, argc)) return NULL; ScrDyn *cb = args[0], *this_arg = argc > 1 ? args[1] : scr_dyn_undefined(); const bool backward = scr_dyn_bytes_named(method, "findLast") || scr_dyn_bytes_named(method, "findLastIndex"); - ScrBytes *mapped = scr_dyn_bytes_named(method, "map") ? scr_bytes_new(bytes->elem, (double)len) : NULL; + ScrDyn *mapped = NULL; + if (scr_dyn_bytes_named(method, "map") && !(mapped = scr_bytes_species_create(recv, len))) return NULL; double *kept = scr_dyn_bytes_named(method, "filter") ? malloc((len ? len : 1) * sizeof *kept) : NULL; size_t nkept = 0; for (size_t step = 0; step < len; step++) { @@ -4957,7 +5192,7 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar scr_dyn_release(index); if (!r) { scr_dyn_release(item); - if (mapped) scr_bytes_release(mapped); + scr_dyn_release(mapped); free(kept); return NULL; } @@ -4966,8 +5201,8 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar bool converted = scr_dyn_number_coerce_js(r, &v); scr_dyn_release(r); scr_dyn_release(item); - if (!converted) { scr_bytes_release(mapped); return NULL; } - if (k < mapped->len) scr_bytes_set(mapped, (double)k, v); + if (!converted) { scr_dyn_release(mapped); return NULL; } + if (k < mapped->v.bytes->len) scr_bytes_set(mapped->v.bytes, (double)k, v); continue; } bool truthy = scr_dyn_truthy(r); @@ -4988,12 +5223,12 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar } scr_dyn_release(item); } - if (mapped) return dyn_bytes_wrap(recv, mapped); + if (mapped) return mapped; if (kept) { - ScrBytes *out = scr_bytes_new(bytes->elem, (double)nkept); - for (size_t i = 0; i < nkept; i++) scr_bytes_set(out, (double)i, kept[i]); + ScrDyn *out = scr_bytes_species_create(recv, nkept); + for (size_t i = 0; out && i < nkept; i++) scr_bytes_set(out->v.bytes, (double)i, kept[i]); free(kept); - return dyn_bytes_wrap(recv, out); + return out; } if (scr_dyn_bytes_named(method, "some")) return scr_dyn_new_bool(false); if (scr_dyn_bytes_named(method, "every")) return scr_dyn_new_bool(true); @@ -5120,6 +5355,8 @@ static ScrDyn *scr_dyn_bytes_method_value(const ScrStr *name, size_t arity) { /* Shared view metadata and indexed reads for both compiler backends. */ ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key) { ScrBytes *bytes = value->v.bytes; + /* Own named properties first; a getter sees the view as `this`. */ + if (scr_bytes_has_named(value, key)) return scr_bytes_named_get(value, key->data, key->len); if (key->len == 10 && memcmp(key->data, "byteLength", 10) == 0) return scr_dyn_new_num(scr_bytes_byte_len(bytes)); if (key->len == 10 && memcmp(key->data, "byteOffset", 10) == 0) return scr_dyn_new_num(scr_bytes_byte_offset(bytes)); if (key->len == 6 && memcmp(key->data, "buffer", 6) == 0) return scr_array_buffer_from_bytes(bytes); @@ -6385,6 +6622,10 @@ static ScrDyn *scr_dyn_symbol_receiver(const ScrDyn *value) { if (value->kind == SCR_DYN_FUNC) return scr_dyn_fn_properties(value); if (value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_HANDLE) return scr_dyn_retain((ScrDyn *)value); + /* A view keeps its symbol properties on its named-property table, which + * writes create first (scr_bytes_symbol_write). */ + if (value->kind == SCR_DYN_BYTES) + return value->v.bytes->properties ? scr_dyn_retain(value->v.bytes->properties) : NULL; if (value->kind == SCR_DYN_PROXY) { static const char message[] = "Symbol properties on this native value have no lowering"; scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); @@ -7798,8 +8039,46 @@ void scr_dyn_symbol_key_placeholder(ScrDyn *value, ScrDyn *key) { entry->value = scr_dyn_retain(scr_dyn_undefined()); } +/* Symbol keys that the runtime reads from a view's prototype without + * consulting its own properties: the well-known symbols and + * util.inspect.custom. */ +static bool scr_bytes_protocol_symbol(const ScrDyn *key) { + if (scr_dyn_well_known_symbol(key)) return true; + ScrStr *description = key->v.symbol.description(key->v.symbol.value); + bool custom = description && description->len == 26 && !memcmp(description->data, "nodejs.util.inspect.custom", 26); + scr_str_release(description); + return custom; +} + +/* Before a symbol-keyed write or definition on a view: false with an + * exception pending for a key the view cannot own, else true with the + * view's table created. Symbol.toStringTag is an accessor without a setter + * on %TypedArray%.prototype, so assigning it throws V8's TypeError. */ +static bool scr_bytes_symbol_write(ScrDyn *value, const ScrDyn *key, bool assign) { + ScrDyn *receiver = scr_dyn_symbol_receiver(value); + bool own = receiver && scr_dyn_symbol_entry(receiver, key, true); + scr_dyn_release(receiver); + if (own) return true; + ScrStr *description = key->v.symbol.description(key->v.symbol.value); + bool tag = assign && !value->v.bytes->is_data_view && scr_dyn_well_known_symbol(key) && description && + description->len == 18 && !memcmp(description->data, "Symbol.toStringTag", 18); + if (tag) { + static const char message[] = "Cannot set property Symbol(Symbol.toStringTag) of [object Object] which has only a getter"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + } else if (scr_bytes_protocol_symbol(key)) { + ScrStr *name = key->v.symbol.render(key->v.symbol.value); + scr_bytes_protocol_fence(name->data, name->len); + scr_str_release(name); + } + scr_str_release(description); + if (scr_exc_pending()) return false; + scr_bytes_properties(value); + return true; +} + void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored) { if (scr_dyn_error_prototype_fence(value)) return; + if (value->kind == SCR_DYN_BYTES && !scr_bytes_symbol_write(value, key, true)) return; ScrDyn *receiver = scr_dyn_symbol_receiver(value); if (!receiver) { if (!scr_exc_pending()) { @@ -7822,6 +8101,18 @@ void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored) { scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); } else { bool present = scr_dyn_symbol_entry(receiver, key, true) != NULL; + if (!present && receiver->non_extensible) { + ScrStr *rendered = key->v.symbol.render(key->v.symbol.value); + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "Cannot add property "); + scr_jb_write(&message, rendered->data, rendered->len); + scr_jb_puts(&message, ", object is not extensible"); + scr_str_release(rendered); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message)); + scr_dyn_release(receiver); + return; + } scr_dyn_symbol_table(receiver); ScrStr *name = scr_dyn_symbol_storage_key(key); scr_dyn_key_set(receiver->symbol_properties, name, stored); @@ -7832,6 +8123,7 @@ void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored) { } static ScrDyn *scr_dyn_define_symbol(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) { + if (target->kind == SCR_DYN_BYTES && !scr_bytes_symbol_write(target, key, false)) return NULL; ScrDyn *receiver = scr_dyn_symbol_receiver(target); if (!receiver) { if (!scr_exc_pending()) { @@ -8026,6 +8318,53 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) scr_dyn_release(defined); return scr_dyn_retain(target); } + if (target->kind == SCR_DYN_BYTES) { + ScrStr *name = scr_dyn_property_key(key); + if (!name) return NULL; + double index; + bool numeric = !target->v.bytes->is_data_view && scr_bytes_numeric_key(name, &index); + if (numeric) { + /* [[DefineOwnProperty]] of an element: a valid index and a data + * descriptor that keeps it writable, enumerable and configurable. */ + bool valid = !signbit(index) && trunc(index) == index && index < (double)target->v.bytes->len; + const ScrDyn *configurable = scr_dyn_obj_get(descriptor, "configurable", 12); + const ScrDyn *enumerable = scr_dyn_obj_get(descriptor, "enumerable", 10); + const ScrDyn *writable = scr_dyn_obj_get(descriptor, "writable", 8); + bool accessor = scr_dyn_obj_get(descriptor, "get", 3) || scr_dyn_obj_get(descriptor, "set", 3); + if (!valid) { + scr_str_release(name); + static const char message[] = "Invalid typed array index"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + return NULL; + } + if (accessor || (configurable && !scr_dyn_truthy(configurable)) || + (enumerable && !scr_dyn_truthy(enumerable)) || (writable && !scr_dyn_truthy(writable))) { + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "Cannot redefine property: "); + scr_jb_write(&message, name->data, name->len); + scr_str_release(name); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message)); + return NULL; + } + scr_str_release(name); + const ScrDyn *value = scr_dyn_obj_get(descriptor, "value", 5); + double number; + if (value) { + if (!scr_dyn_number_coerce_js(value, &number)) return NULL; + scr_bytes_set(target->v.bytes, index, number); + } + return scr_dyn_retain(target); + } + bool fenced = scr_bytes_protocol_name(name) && !scr_bytes_has_named(target, name); + if (fenced) scr_bytes_protocol_fence(name->data, name->len); + scr_str_release(name); + if (fenced) return NULL; + ScrDyn *defined = scr_dyn_define_property(scr_bytes_properties(target), key, descriptor); + if (!defined) return NULL; + scr_dyn_release(defined); + return scr_dyn_retain(target); + } if (target->kind != SCR_DYN_OBJ) { static const char msg[] = "Object.defineProperty called on non-object"; scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); @@ -8173,7 +8512,7 @@ ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key) { scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); return NULL; } - if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_PROXY && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR && target->kind != SCR_DYN_STR && target->kind != SCR_DYN_BOOL && target->kind != SCR_DYN_NUM && target->kind != SCR_DYN_TYPED_REF) { + if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_PROXY && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_ARR && target->kind != SCR_DYN_STR && target->kind != SCR_DYN_BOOL && target->kind != SCR_DYN_NUM && target->kind != SCR_DYN_TYPED_REF && target->kind != SCR_DYN_BYTES) { static const char msg[] = "Object.getOwnPropertyDescriptor on this value is not supported yet"; scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1); return NULL; @@ -8477,7 +8816,8 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) { const ScrBytes *bytes = d->v.bytes; if (d->buffer) scr_jb_puts(b, "{\"type\":\"Buffer\",\"data\":["); else scr_jb_putc(b, '{'); - for (size_t i = 0; i < bytes->len; i++) { + size_t count = bytes->is_data_view ? 0 : bytes->len; + for (size_t i = 0; i < count; i++) { if (i > 0) scr_jb_putc(b, ','); if (!d->buffer) { char idx[32]; @@ -8486,6 +8826,32 @@ static bool scr_dyn_json_write(ScrJsonBuf *b, const ScrDyn *d) { } scr_jb_put_f64(b, scr_bytes_get(bytes, (double)i)); } + /* Named properties follow the elements; a Buffer's toJSON leaves them + * out. */ + ScrDyn *named = d->buffer || !bytes->properties ? NULL : scr_dyn_obj_keys(bytes->properties); + bool first = count == 0; + for (size_t i = 0; named && i < named->v.arr.len; i++) { + const ScrStr *key = named->v.arr.items[i]->v.str; + ScrDyn *value = scr_dyn_obj_read_receiver(bytes->properties, key->data, key->len, d); + if (!value) { scr_dyn_release(named); return false; } + ScrJsonBuf probe; + scr_jb_init(&probe); + bool present = scr_dyn_json_write(&probe, value); + scr_dyn_release(value); + if (!present) { + scr_jb_dispose(&probe); + if (scr_exc_pending()) { scr_dyn_release(named); return false; } + continue; + } + if (!first) scr_jb_putc(b, ','); + first = false; + scr_jb_put_json_str(b, key); + scr_jb_putc(b, ':'); + ScrStr *body = scr_jb_finish(&probe); + scr_jb_write(b, body->data, body->len); + scr_str_release(body); + } + scr_dyn_release(named); scr_jb_puts(b, d->buffer ? "]}" : "}"); return true; } @@ -10221,6 +10587,7 @@ bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key) { } if (v->kind == SCR_DYN_STR) return scr_dyn_canonical_own_index(key, (size_t)scr_str_utf16_len(v->v.str)); + if (v->kind == SCR_DYN_BYTES) return scr_dyn_bytes_in(v, key); return false; } @@ -10283,7 +10650,13 @@ static void scr_dyn_throw_property(const ScrDyn *object, const char *key, size_t scr_jb_puts(&buffer, deleting ? "Cannot delete property '" : "Cannot assign to read only property '"); scr_jb_write(&buffer, key, key_len); scr_jb_puts(&buffer, deleting ? "' of " : "' of object '"); - if (object->null_proto) scr_jb_puts(&buffer, "[object Object]"); + if (object->kind == SCR_DYN_BYTES) { + /* V8 names a typed array by its tag and a DataView by its class. */ + const char *kind = object->v.bytes->is_data_view ? "DataView" : scr_bytes_elem_name(object->v.bytes->elem); + scr_jb_puts(&buffer, object->v.bytes->is_data_view ? "#<" : "[object "); + scr_jb_puts(&buffer, kind); + scr_jb_puts(&buffer, object->v.bytes->is_data_view ? ">" : "]"); + } else if (object->null_proto) scr_jb_puts(&buffer, "[object Object]"); else { scr_jb_puts(&buffer, "#<"); scr_jb_puts(&buffer, name ? name : "Object"); @@ -10497,19 +10870,36 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) { } if (recv->kind == SCR_DYN_BYTES) { /* Integer-indexed exotic writes coerce even for an invalid canonical - * numeric index, then ignore an out-of-range index without an error. */ - double index = scr_string_to_number(key); - char spelling[32]; - size_t length = scr_f64_to_str(index, spelling); - bool negative_zero = key->len == 2 && memcmp(key->data, "-0", 2) == 0; - if (negative_zero || (length == key->len && memcmp(spelling, key->data, length) == 0)) { + * numeric index, then ignore an out-of-range index without an error. + * A DataView has no elements: its numeric keys are named properties. */ + double index; + if (!recv->v.bytes->is_data_view && scr_bytes_numeric_key(key, &index)) { double number; if (!scr_dyn_number_coerce_js(value, &number)) return; - if (!negative_zero && index >= 0 && index < (double)recv->v.bytes->len && trunc(index) == index) { + if (!signbit(index) && index < (double)recv->v.bytes->len && trunc(index) == index) { scr_bytes_set(recv->v.bytes, index, number); } return; } + /* Any other key is a named property of the view. */ + if (!scr_bytes_has_named(recv, key)) { + if (scr_bytes_getter_name(recv, key)) { + ScrJsonBuf message; + scr_jb_init(&message); + scr_jb_puts(&message, "Cannot set property "); + scr_jb_write(&message, key->data, key->len); + scr_jb_puts(&message, recv->v.bytes->is_data_view ? " of # which has only a getter" + : " of [object Object] which has only a getter"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message)); + return; + } + if (scr_bytes_protocol_name(key)) { + scr_bytes_protocol_fence(key->data, key->len); + return; + } + } + scr_dyn_object_key_set(scr_bytes_properties(recv), key, value, recv); + return; } ScrJsonBuf b; @@ -12181,6 +12571,11 @@ static ScrDyn *scr_json_member(const ScrDyn *holder, const ScrStr *key) { scr_dyn_release(view); return member; } + /* A view's named properties, getters included, read through the view. */ + if (holder->kind == SCR_DYN_BYTES && (holder->v.bytes->is_data_view || scr_bytes_has_named(holder, key))) { + ScrDyn *named = scr_bytes_named_get(holder, key->data, key->len); + return named || scr_exc_pending() ? named : scr_dyn_retain(scr_dyn_undefined()); + } if (holder->kind == SCR_DYN_ARR || holder->kind == SCR_DYN_BYTES) { size_t index = 0; for (size_t i = 0; i < key->len; i++) index = index * 10 + (size_t)(key->data[i] - '0'); @@ -12324,6 +12719,23 @@ void scr_dyn_key_delete(ScrDyn *recv, const ScrStr *key, bool strict) { scr_dyn_key_delete(recv->v.arr.properties, key, strict); return; } + if (recv->kind == SCR_DYN_BYTES) { + /* A typed array's elements cannot be deleted; other numeric keys name + * no property. Named properties follow their configurability. */ + double index; + if (!recv->v.bytes->is_data_view && scr_bytes_numeric_key(key, &index)) { + if (strict && !signbit(index) && trunc(index) == index && index < (double)recv->v.bytes->len) + scr_dyn_throw_property(recv, key->data, key->len, true); + return; + } + ScrDynEntry *entry = recv->v.bytes->properties ? scr_dyn_entry(recv->v.bytes->properties, key) : NULL; + if (entry && !entry->configurable) { + if (strict) scr_dyn_throw_property(recv, key->data, key->len, true); + return; + } + if (entry) scr_json_delete_member(recv->v.bytes->properties, key); + return; + } if (recv->kind == SCR_DYN_UNDEF || recv->kind == SCR_DYN_NULL) { const char *message = "Cannot convert undefined or null to object"; scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message)); @@ -13982,7 +14394,7 @@ ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value) { if (value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_STR || value->kind == SCR_DYN_BYTES) { ScrDyn *keys = scr_dyn_new_arr(); size_t length = value->kind == SCR_DYN_ARR ? value->v.arr.len : - value->kind == SCR_DYN_BYTES ? value->v.bytes->len : + value->kind == SCR_DYN_BYTES ? (value->v.bytes->is_data_view ? 0 : value->v.bytes->len) : (size_t)scr_str_utf16_len(value->v.str); for (size_t i = 0; i < length; i++) { if (value->kind == SCR_DYN_ARR && !scr_dyn_arr_has_index(value, i)) continue; @@ -13992,8 +14404,10 @@ ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value) { } if (value->kind != SCR_DYN_BYTES) scr_dyn_arr_push(keys, scr_dyn_objwalk_key("length", 6)); - if (value->kind == SCR_DYN_ARR && value->v.arr.properties) { - ScrDyn *properties = scr_dyn_obj_own_keys(value->v.arr.properties); + ScrDyn *named = value->kind == SCR_DYN_ARR ? value->v.arr.properties + : value->kind == SCR_DYN_BYTES ? value->v.bytes->properties : NULL; + if (named) { + ScrDyn *properties = scr_dyn_obj_own_keys(named); for (size_t i = 0; i < properties->v.arr.len; i++) scr_dyn_arr_push(keys, scr_dyn_retain(properties->v.arr.items[i])); scr_dyn_release(properties); @@ -14258,7 +14672,7 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) { return out; } if (v->kind == SCR_DYN_ARR || v->kind == SCR_DYN_BYTES) { - size_t n = v->kind == SCR_DYN_ARR ? v->v.arr.len : v->v.bytes->len; + size_t n = v->kind == SCR_DYN_ARR ? v->v.arr.len : v->v.bytes->is_data_view ? 0 : v->v.bytes->len; for (size_t i = 0; i < n; i++) { if (v->kind == SCR_DYN_ARR && !scr_dyn_arr_has_index(v, i)) continue; char key[24]; @@ -14279,10 +14693,11 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) { scr_dyn_arr_push(out, pair); } } - if (v->kind == SCR_DYN_ARR && v->v.arr.properties) { - ScrDyn *names = scr_dyn_obj_own_keys(v->v.arr.properties); + ScrDyn *named = v->kind == SCR_DYN_ARR ? v->v.arr.properties : v->v.bytes->properties; + if (named) { + ScrDyn *names = scr_dyn_obj_own_keys(named); for (size_t i = 0; i < names->v.arr.len; i++) { - if (!scr_dyn_objwalk_entry(out, v->v.arr.properties, mode, names->v.arr.items[i]->v.str, v)) { + if (!scr_dyn_objwalk_entry(out, named, mode, names->v.arr.items[i]->v.str, v)) { scr_dyn_release(names); scr_dyn_release(out); return NULL; @@ -14386,6 +14801,7 @@ ScrDyn *scr_dyn_for_in_keys(const ScrDyn *v) { return keys; } case SCR_DYN_ARR: + case SCR_DYN_BYTES: return scr_dyn_obj_keys(v); case SCR_DYN_STR: { ScrDyn *keys = scr_dyn_new_arr(); @@ -14639,7 +15055,7 @@ static void scr_dyn_assign_strings_from(ScrDyn *target, const ScrDyn *src, bool scr_dyn_proxy_unsupported("Object.assign"); return; } - if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_FUNC) return; + if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_FUNC && target->kind != SCR_DYN_BYTES) return; if (src->kind == SCR_DYN_TYPED_REF) { ScrDyn *materialized = scr_dyn_typed_ref_materialize(src); if (materialized) scr_dyn_assign_strings_from(target, materialized, define); @@ -14702,13 +15118,15 @@ static void scr_dyn_assign_from(ScrDyn *target, const ScrDyn *src, bool define) return; } ScrDyn *view = src->kind == SCR_DYN_FUNC ? scr_dyn_fn_properties(src) : scr_dyn_retain((ScrDyn *)src); + /* A view's symbol properties live on its named-property table. */ + const ScrDyn *holder = src->kind == SCR_DYN_BYTES ? src->v.bytes->properties : view; ScrDyn *symbols = scr_dyn_new_arr(); - if (view->symbol_keys) for (size_t i = 0; i < view->symbol_keys->v.arr.len; i++) - scr_dyn_arr_push(symbols, scr_dyn_retain(view->symbol_keys->v.arr.items[i])); + if (holder && holder->symbol_keys) for (size_t i = 0; i < holder->symbol_keys->v.arr.len; i++) + scr_dyn_arr_push(symbols, scr_dyn_retain(holder->symbol_keys->v.arr.items[i])); scr_dyn_assign_strings_from(target, view, define); for (size_t i = 0; i < symbols->v.arr.len && !scr_exc_pending(); i++) { ScrDyn *key = symbols->v.arr.items[i]; - ScrDynEntry *entry = scr_dyn_symbol_entry(view, key, true); + ScrDynEntry *entry = scr_dyn_symbol_entry(holder, key, true); if (!entry || !entry->enumerable) continue; ScrDyn *value = scr_dyn_symbol_key_get(src, key, false); if (!value) break; @@ -15093,7 +15511,7 @@ bool scr_dyn_has_own(const ScrDyn *v, const ScrStr *key) { return scr_dyn_canonical_own_index(key, (size_t)scr_str_utf16_len(v->v.str)); } if (v->kind == SCR_DYN_BYTES) { - return scr_dyn_canonical_own_index(key, v->v.bytes->len); + return (!v->v.bytes->is_data_view && scr_dyn_canonical_own_index(key, v->v.bytes->len)) || scr_bytes_has_named(v, key); } if (v->kind == SCR_DYN_FUNC) { ScrDyn *desc = scr_dyn_own_descriptor(v, key); @@ -15130,7 +15548,8 @@ bool scr_dyn_property_is_enumerable(const ScrDyn *value, const ScrStr *key) { return (scr_dyn_canonical_own_index(key, value->v.arr.len) && scr_dyn_has_own(value, key)) || (value->v.arr.properties && scr_dyn_obj_enumerable(value->v.arr.properties, key->data, key->len)); if (value->kind == SCR_DYN_BYTES) - return scr_dyn_canonical_own_index(key, value->v.bytes->len); + return (!value->v.bytes->is_data_view && scr_dyn_canonical_own_index(key, value->v.bytes->len)) || + (value->v.bytes->properties && scr_dyn_obj_enumerable(value->v.bytes->properties, key->data, key->len)); return false; } ScrDyn *scr_dyn_obj_values(const ScrDyn *v) { return scr_dyn_objwalk(v, SCR_OBJWALK_VALUES); } @@ -16834,6 +17253,11 @@ ScrDyn *scr_dyn_freeze_data(ScrDyn *value) { ScrDyn *native = scr_dyn_freeze_native(value); if (native) return native; if (scr_dyn_class_integrity_fence(value, "Object.freeze")) return NULL; + if (value->kind == SCR_DYN_BYTES) { + scr_dyn_release(scr_dyn_freeze_data(scr_bytes_properties(value))); + if (scr_bytes_integrity_fence(value, "freeze")) return NULL; + return scr_dyn_retain(value); + } if (value->symbol_properties) scr_dyn_release(scr_dyn_freeze_data(value->symbol_properties)); if (value->kind == SCR_DYN_ARR) { value->non_extensible = true; @@ -16875,6 +17299,9 @@ bool scr_dyn_is_frozen(const ScrDyn *value) { if (value->kind == SCR_DYN_ARR) return scr_dyn_is_sealed(value) && (value->v.arr.len == 0 || value->v.arr.frozen) && (!value->v.arr.properties || scr_dyn_is_frozen(value->v.arr.properties)); + if (value->kind == SCR_DYN_BYTES) + return (value->v.bytes->is_data_view || value->v.bytes->len == 0) && value->v.bytes->properties && + scr_dyn_is_frozen(value->v.bytes->properties); if (value->kind == SCR_DYN_OBJ && !value->copied_from_native) { if (!value->non_extensible) return false; for (size_t i = 0; i < value->v.obj.len; i++) { diff --git a/packages/runtime/src/scr_symbol.c b/packages/runtime/src/scr_symbol.c index 647c31dc3..fb0b6cb0b 100644 --- a/packages/runtime/src/scr_symbol.c +++ b/packages/runtime/src/scr_symbol.c @@ -80,6 +80,11 @@ ScrSym *scr_sym_well_known(ScrStr *name) { scr_str_release(description); symbol->reg_next = g_sym_well_known; g_sym_well_known = scr_sym_retain(symbol); + { + ScrDyn *key = scr_dyn_new_symbol(symbol); + scr_dyn_install_well_known_symbol(key); + scr_dyn_release(key); + } if (name->len == 8 && !memcmp(name->data, "iterator", 8)) { ScrDyn *key = scr_dyn_new_symbol(symbol); scr_dyn_install_iterator_symbol(key); @@ -90,6 +95,11 @@ ScrSym *scr_sym_well_known(ScrStr *name) { scr_dyn_install_has_instance_symbol(key); scr_dyn_release(key); } + if (name->len == 7 && !memcmp(name->data, "species", 7)) { + ScrDyn *key = scr_dyn_new_symbol(symbol); + scr_dyn_install_species_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/tests/corpus/7752-typed-array-dynamic-methods.js b/tests/corpus/7752-typed-array-dynamic-methods.js index a728e3ce9..d4f077b49 100644 --- a/tests/corpus/7752-typed-array-dynamic-methods.js +++ b/tests/corpus/7752-typed-array-dynamic-methods.js @@ -123,3 +123,15 @@ function plainIterator(TA) { console.log(new TA(source), TA.from(source, (v) => v * 2), gets, nexts); } plainIterator(Int16Array); +// ToIndex of a Symbol or BigInt length, toLocaleString, and Symbol descriptions +// that are not strings. +function small(TA) { + for (const bad of [Symbol("s"), 1n]) { + try { new TA(bad); console.log("no throw"); } catch (e) { console.log(e.constructor.name); } + } + console.log(new TA([1, 1234.5, -0, 0]).toLocaleString(), new TA(0).toLocaleString(), typeof new TA(1).toLocaleString); +} +small(Float64Array); +small(Uint16Array); +const s = Symbol(1), t = Symbol(true), u = Symbol(2.5); +console.log(s.description, t.description, u.toString(), typeof s.description); diff --git a/tests/corpus/7753-typed-array-named-properties.js b/tests/corpus/7753-typed-array-named-properties.js new file mode 100644 index 000000000..b49f100b5 --- /dev/null +++ b/tests/corpus/7753-typed-array-named-properties.js @@ -0,0 +1,143 @@ +// Named properties on typed arrays, Buffers and DataViews (assignment, +// Object.defineProperty, reflection, deletion, integrity levels, JSON and +// inspection), and the Symbol.species constructor of map, filter, slice and +// subarray. +function box(value) { + const holder = {}; + holder.value = value; + return holder.value; +} + +function expandos(TA) { + const a = new TA([1, 2]); + a.foo = 1; + a.bar = "x"; + a[5] = 9; + a["1.5"] = 7; + console.log(a, a.foo, "foo" in a, a.hasOwnProperty("bar"), Object.keys(a), Object.entries(a).length); + console.log(Object.getOwnPropertyNames(a), JSON.stringify(Object.getOwnPropertyDescriptor(a, "foo")), JSON.stringify(Object.getOwnPropertyDescriptor(a, "0"))); + Object.defineProperty(a, "hidden", { value: 3, enumerable: false }); + Object.defineProperty(a, "0", { value: 8 }); + console.log(a, a.hidden, Object.keys(a)); + try { Object.defineProperty(a, "1", { value: 1, configurable: false }); } catch (e) { console.log(e.constructor.name, e.message); } + try { Object.defineProperty(a, "7", { value: 1 }); } catch (e) { console.log(e.constructor.name, e.message); } + try { Object.defineProperty(a, "-0", { value: 1 }); } catch (e) { console.log(e.constructor.name, e.message); } + try { Object.defineProperty(a, "0", { get() { return 1; } }); } catch (e) { console.log(e.constructor.name, e.message); } + let reads = 0; + Object.defineProperty(a, "g", { get() { reads++; return this.length; }, enumerable: true }); + console.log(a.g, reads, new TA(0)); + const e = new TA(0); + e.k = { deep: [1] }; + console.log(e); +} + +function species(TA, Other) { + const s = new TA([1, 2, 3, 4]); + const calls = []; + s.constructor = {}; + s.constructor[Symbol.species] = function (n) { calls.push(n); return new Other(n); }; + const m = s.map((v) => v * 1.5), f = s.filter((v) => v > 1), sl = s.slice(1, 3); + console.log(m, f, sl, calls); + s.constructor[Symbol.species] = function (buffer, offset, length) { calls.push([offset, length]); return new TA(buffer, offset, length); }; + const sub = s.subarray(1, 3); + sub[0] = 50; + console.log(sub, s[1], calls.length); + s.constructor[Symbol.species] = function () { return {}; }; + try { s.map((v) => v); } catch (err) { console.log(err.constructor.name); } + s.constructor[Symbol.species] = function () { return new TA(1); }; + try { s.slice(); } catch (err) { console.log(err.constructor.name); } + s.constructor = 5; + try { s.filter(() => true); } catch (err) { console.log(err.constructor.name); } + s.constructor = undefined; + console.log(s.map((v) => v + 1)); + const t = new TA([3, 1]); + Object.defineProperty(t, "constructor", { get() { calls.push("get"); return undefined; } }); + console.log(t.slice(), calls[calls.length - 1]); + const u = new TA([1, 2, 3]); + u.constructor = { [Symbol.species]: Uint16Array }; + console.log(u.filter(() => true), u.subarray(1), u.subarray(1).constructor === Uint16Array); +} + +function reflection(TA) { + const a = new TA([1, 2]); + a.foo = 1; + a[Symbol.for("s")] = 2; + const keys = []; + for (const k in a) keys.push(k); + console.log(keys, { ...a }, JSON.stringify(a), Object.values(a)); + console.log(a.propertyIsEnumerable("foo"), Object.hasOwn(a, "foo"), Object.getOwnPropertySymbols(a), a[Symbol.for("s")]); + console.log(Object.assign(new TA(2), { x: 1, 0: 9 }), Object.assign({}, a)); + delete a.foo; + delete a[5]; + delete a.length; + console.log(a, "foo" in a, a.length); + try { delete a[0]; } catch (err) { console.log(err.message); } + Object.defineProperty(a, "fixed", { value: 1 }); + try { delete a.fixed; } catch (err) { console.log(err.message); } + try { a.fixed = 2; } catch (err) { console.log(err.message); } + console.log(a.subarray(0, 1).fixed, a.slice().fixed); + let seen; + Object.defineProperty(a, "s", { set(v) { seen = [v, this === a]; } }); + a.s = 7; + console.log(seen); + a.map = 5; + try { a.map((v) => v); } catch (err) { console.log(err.message); } + for (const key of ["length", "byteLength", "byteOffset", "buffer"]) { + try { a[key] = 1; } catch (err) { console.log(err.message); } + } + try { a[Symbol.toStringTag] = "x"; } catch (err) { console.log(err.message); } + // Cycles through a view's own properties are collected. + const loop = new TA(1); + loop.self = loop; + loop.size = () => loop.length; + console.log(loop.self === loop, loop.size()); +} + +function integrity(TA) { + const results = []; + const attempt = (f) => { try { f(); results.push("ok"); } catch (err) { results.push(err.message); } }; + const sealed = new TA(1); + sealed.x = 1; + attempt(() => Object.seal(sealed)); + console.log(results, Object.isExtensible(sealed), Object.isSealed(sealed), JSON.stringify(Object.getOwnPropertyDescriptor(sealed, "x"))); + attempt(() => Object.seal(sealed)); + const frozen = new TA(1); + frozen.x = 1; + attempt(() => Object.freeze(frozen)); + attempt(() => Object.freeze(frozen)); + console.log(results, Object.isFrozen(frozen), JSON.stringify(Object.getOwnPropertyDescriptor(frozen, "x"))); + const closed = new TA(1); + Object.defineProperty(closed, "q", { value: 1 }); + Object.preventExtensions(closed); + try { closed.z = 1; } catch (err) { console.log(err.message); } + try { closed[Symbol("n")] = 1; } catch (err) { console.log(err.message); } + console.log(Object.isExtensible(closed), Object.isSealed(closed), Object.isFrozen(closed)); + const empty = new TA(0); + empty.k = 1; + Object.freeze(empty); + try { empty.k = 2; } catch (err) { console.log(err.message); } + console.log(Object.isFrozen(empty), Object.isSealed(empty), empty.k); +} + +expandos(Uint8Array); +expandos(Float64Array); +species(Int16Array, Float32Array); +reflection(Uint8Array); +reflection(Int32Array); +integrity(Uint16Array); + +const view = box(new DataView(new ArrayBuffer(2))); +view[0] = 5; +view.x = 1; +console.log(view, Object.keys(view), view[0], JSON.stringify(view), "0" in view); +try { view.byteLength = 1; } catch (err) { console.log(err.message); } +const buffer = box(Buffer.from([1, 2])); +buffer.foo = 1; +buffer[Symbol.for("k")] = 2; +console.log(buffer, Object.keys(buffer), JSON.stringify(buffer)); +for (const key of ["parent", "offset", "length"]) { + try { buffer[key] = 1; } catch (err) { console.log(err.message); } +} +const empty = box(Buffer.alloc(0)); +empty.foo = { a: [1, { b: 2 }] }; +console.log(empty); diff --git a/tests/harness/corpus-costs/7753-typed-array-named-properties.js.json b/tests/harness/corpus-costs/7753-typed-array-named-properties.js.json new file mode 100644 index 000000000..9ed866538 --- /dev/null +++ b/tests/harness/corpus-costs/7753-typed-array-named-properties.js.json @@ -0,0 +1 @@ +{ "plain": 3900, "san": 4400 } diff --git a/tests/harness/typed-array-property-refusals.test.ts b/tests/harness/typed-array-property-refusals.test.ts new file mode 100644 index 000000000..b2d6d498a --- /dev/null +++ b/tests/harness/typed-array-property-refusals.test.ts @@ -0,0 +1,64 @@ +import { execFile } from "node:child_process"; +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); + +// The runtime's conversions, iteration and printing of a typed array, Buffer +// or DataView read these members from the prototype, so defining one on the +// view refuses with a catchable error instead of being ignored. +test("typed array members the runtime reads from the prototype refuse as own properties", async () => { + const outDir = mkdtempSync(join(tmpdir(), "scriptc-typed-array-refusals-")); + const entry = join(outDir, "main.mjs"); + writeFileSync( + entry, + ` +function attempt(define) { + try { define(); console.log("defined"); } catch (error) { console.log(error.name, error.message); } +} +function run(TA, view) { + const a = new TA(2); + attempt(() => { a.toString = () => "x"; }); + attempt(() => { a.valueOf = () => 1; }); + attempt(() => Object.defineProperty(a, "toJSON", { value: () => 1 })); + attempt(() => { a.join = () => ""; }); + attempt(() => { a[Symbol.iterator] = function* () {}; }); + attempt(() => { a[Symbol.for("nodejs.util.inspect.custom")] = () => "x"; }); + attempt(() => { view.toLocaleString = () => ""; }); + attempt(() => { a.label = "kept"; }); + console.log(a.label, String(a)); +} +run(Uint8Array, new DataView(new ArrayBuffer(1))); +`, + ); + const result = await compile(entry, { + backend: "llvm", + sanitize: process.env["SCRIPTC_SAN"] === "1", + outDir, + outPath: join(outDir, "program"), + }); + expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true); + if (!result.ok) return; + const { stdout, stderr } = await execFileAsync(result.binaryPath, [], { encoding: "utf8" }); + const refused = (key: string): string => + `Error defining ${key} on a typed array, Buffer or DataView is not supported yet`; + expect(stdout).toBe( + [ + refused("toString"), + refused("valueOf"), + refused("toJSON"), + refused("join"), + refused("Symbol(Symbol.iterator)"), + refused("Symbol(nodejs.util.inspect.custom)"), + refused("toLocaleString"), + "defined", + "kept 0,0", + "", + ].join("\n"), + ); + expect(stderr).toBe(""); +}); From 82ebbd4df7f88682634541516213345369044eea Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 23:31:58 -0500 Subject: [PATCH 017/230] Fix the species trace, deep release, iteration bound and optional length findings --- packages/compiler/src/backend/llvm/classes.ts | 18 ++- .../lowering/builtins/array-classes.ts | 127 +++++++++++++----- .../lowering/containers/array-callbacks.ts | 30 +++++ .../7730-array-subclass-retyped-trace.ts.json | 6 + .../7731-array-subclass-deep-release.ts.json | 6 + .../7732-array-subclass-species-bound.ts.json | 6 + ...733-array-subclass-optional-length.ts.json | 6 + packages/runtime/src/scr_array.c | 19 ++- packages/runtime/src/scr_array.h | 7 +- .../7730-array-subclass-retyped-trace.ts | 33 +++++ .../7731-array-subclass-deep-release.ts | 17 +++ .../7732-array-subclass-species-bound.ts | 32 +++++ .../7733-array-subclass-optional-length.ts | 26 ++++ tests/diagnostics/array-subclass-fences.ts | 6 +- .../array-subclass-fences.ts.txt | 62 +++++---- .../7730-array-subclass-retyped-trace.ts.json | 1 + .../7731-array-subclass-deep-release.ts.json | 1 + .../7732-array-subclass-species-bound.ts.json | 1 + ...733-array-subclass-optional-length.ts.json | 1 + 19 files changed, 334 insertions(+), 71 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json create mode 100644 tests/corpus/7730-array-subclass-retyped-trace.ts create mode 100644 tests/corpus/7731-array-subclass-deep-release.ts create mode 100644 tests/corpus/7732-array-subclass-species-bound.ts create mode 100644 tests/corpus/7733-array-subclass-optional-length.ts create mode 100644 tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json create mode 100644 tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json create mode 100644 tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json create mode 100644 tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json diff --git a/packages/compiler/src/backend/llvm/classes.ts b/packages/compiler/src/backend/llvm/classes.ts index d848b86fc..e80370405 100644 --- a/packages/compiler/src/backend/llvm/classes.ts +++ b/packages/compiler/src/backend/llvm/classes.ts @@ -1481,13 +1481,11 @@ export function emitClassShapes( tracedFields.forEach((f, i) => { tr.push(...traceFieldVisitLines(struct, f.index, i, llvmCommentText(f.name))); }); - // An Array subclass instance's elements, when they are cycle-capable. - const tracedElements = - cls.arrayElem !== undefined && - traceAdapter(host, { kind: "array", elem: cls.arrayElem }) !== null; - if (tracedElements) { - host.declare(`declare void @scr_arr_trace_v(ptr, ptr, ptr)`); - tr.push(` call void @scr_arr_trace_v(ptr %o, ptr %visit, ptr %ctx) ; the elements`); + // An Array subclass instance's elements, when its current element + // storage is cycle-capable (a species result can retype it). + if (cls.arrayElem !== undefined) { + host.declare(`declare void @scr_arr_subclass_trace(ptr, ptr, ptr)`); + tr.push(` call void @scr_arr_subclass_trace(ptr %o, ptr %visit, ptr %ctx) ; the elements`); } tr.push(` ret void`, `}`, ``); defs.push(...tr); @@ -1508,9 +1506,9 @@ export function emitClassShapes( if (meta.threadSlot) gf.push(...slotForgetLines(host, struct, meta, "gfw")); if (cls.arrayElem !== undefined) { // The elements: traced ones are the collector's, the others are - // released with the storage. - host.declare(`declare void @scr_arr_subclass_gc_drop(ptr, i1 zeroext)`); - gf.push(` call void @scr_arr_subclass_gc_drop(ptr %o, i1 ${tracedElements})`); + // released with the storage (the same test as the trace's). + host.declare(`declare void @scr_arr_subclass_gc_drop(ptr)`); + gf.push(` call void @scr_arr_subclass_gc_drop(ptr %o)`); } gf.push(...emitObjectFree(host, true), ` ret void`, `}`, ``); defs.push(...gf); diff --git a/packages/compiler/src/frontend/lowering/builtins/array-classes.ts b/packages/compiler/src/frontend/lowering/builtins/array-classes.ts index c4ae4a60d..b416a8bec 100644 --- a/packages/compiler/src/frontend/lowering/builtins/array-classes.ts +++ b/packages/compiler/src/frontend/lowering/builtins/array-classes.ts @@ -12,6 +12,7 @@ import { isJsSourceFile, locOf } from "../../program.js"; import { stripTypeWrappers } from "../typed-arguments.js"; import { dynUndefinedExpr, nodeThrowExpr } from "../../../ir/build.js"; import { lowerArrayFromCall, lowerArrayOfCall } from "../containers/array-construction.js"; +import { boundedArrayHofHelper } from "../containers/array-callbacks.js"; import { storedClassValueType, type ClassInfo } from "../lower-classes.js"; import { BOOL, @@ -34,6 +35,18 @@ export function arrayClassBase( lowerer: Lowerer, heritage: ts.ExpressionWithTypeArguments, ): { elem: IrType } { + const decl = heritage.parent?.parent; + const self = + decl && ts.isClassLikeDeclaration(decl) + ? lowerer.checker.getSymbolAtLocation(decl.name ?? decl) + : undefined; + const element = heritage.typeArguments?.nodes[0]; + if ( + self && + element && + mentionsClass(lowerer, lowerer.checker.getTypeAtLocation(element), self, new Set()) + ) + lowerer.unsupported("SC1090", heritage, "classes that extend an Array of their own instances"); const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(heritage)); // `extends Array` in JavaScript reads `any[]`: dyn elements. if (isJsSourceFile(ts.getSourceFileOfNode(heritage)) && mapped?.kind !== "array") @@ -47,6 +60,26 @@ export function arrayClassBase( return { elem: mapped!.kind === "array" ? mapped!.elem : DYN }; } +/** Whether `type` is the class `self`'s instance type, or holds it through + * unions, intersections or type arguments (`Group | null`, `Group[]`, + * `Map`): the class's array type would contain itself. + * Another class's members do not count, its instances are references. */ +function mentionsClass( + lowerer: Lowerer, + type: ts.Type, + self: ts.Symbol, + seen: Set, +): boolean { + if (seen.has(type)) return false; + seen.add(type); + if (type.getSymbol() === self) return true; + if (type.isUnionType() || type.isIntersectionType()) + return ts.constituentTypes(type).some((part) => mentionsClass(lowerer, part, self, seen)); + return lowerer.checker + .getTypeArguments(type as ts.TypeReference) + .some((argument) => mentionsClass(lowerer, argument, self, seen)); +} + /** The class declared `extends Array` at the root of `info`'s chain, or null. */ export function arrayRootOf(info: ClassInfo | null | undefined): ClassInfo | null { for (let c = info ?? null; c; c = c.base) if (c.extendsArray) return c; @@ -349,6 +382,15 @@ export function arrayClassFieldsFn(lowerer: Lowerer, info: ClassInfo): IrFunctio * take those arguments: they must be none, a forwarding rest (dyn or * number), or a number first parameter whose others may be omitted (whose * no-argument form throws). */ +/** A constructor parameter the species length argument can fill: a + * number, a checked-dynamic value, or a union with a number arm + * (`length?: number`). */ +function takesLength(lowerer: Lowerer, type: IrType): boolean { + if (type.kind === "f64" || type.kind === "dyn") return true; + if (type.kind !== "union") return false; + return lowerer.unions.get(type.unionId)?.arms.some((arm) => arm.kind === "f64") === true; +} + export function arrayClassSpeciesFn(lowerer: Lowerer, info: ClassInfo): IrFunction | null { const elem = arrayRootOf(info)!.extendsArray!.elem; const arrT = arrayOf(elem); @@ -381,31 +423,35 @@ export function arrayClassSpeciesFn(lowerer: Lowerer, info: ClassInfo): IrFuncti withLength = [{ kind: "arrayLit", elems: [n()], type: params[0]!.type, loc } as IrExpr]; without = [{ kind: "arrayLit", elems: [], type: params[0]!.type, loc } as IrExpr]; } else if ( - params[0]!.mode === "required" && - (params[0]!.type.kind === "f64" || params[0]!.type.kind === "dyn") && + (params[0]!.mode === "required" || params[0]!.mode === "omittable") && + takesLength(lowerer, params[0]!.type) && params.slice(1).every((p) => p.mode === "omittable") ) { - const rest = params - .slice(1) - .map((p) => - lowerer.coerceToExpected( - { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } as IrExpr, - p.type, - ), - ); - // A dyn first parameter (a JavaScript `constructor(size)`) takes the - // length as a number, and undefined in the no-argument form. - if (params[0]!.type.kind === "dyn") without = [dynUndefinedExpr(loc), ...rest.map((e) => e)]; - withLength = [ - params[0]!.type.kind === "dyn" ? { kind: "dynFrom", value: n(), type: DYN, loc } : n(), - ...params - .slice(1) - .map((p) => - lowerer.coerceToExpected( + // An omitted argument is what a call without it passes: the + // parameter's default, or undefined. + const blame = info.decl ?? null; + const omitted = (p: (typeof params)[number]): IrExpr => + p.callDefault ?? + (blame + ? lowerer.undefinedArgFor(p.type, loc, blame) + : lowerer.coerceToExpected( { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } as IrExpr, p.type, - ), - ), + )); + const rest = params.slice(1).map(omitted); + // A dyn first parameter (a JavaScript `constructor(size)`) and an + // optional or defaulted one take the length as a number, and their + // omitted value in the no-argument form. + if (params[0]!.type.kind === "dyn" || params[0]!.mode === "omittable") + without = [ + params[0]!.type.kind === "dyn" ? dynUndefinedExpr(loc) : omitted(params[0]!), + ...rest, + ]; + withLength = [ + params[0]!.type.kind === "dyn" + ? { kind: "dynFrom", value: n(), type: DYN, loc } + : lowerer.coerceToExpected(n(), params[0]!.type), + ...params.slice(1).map(omitted), ]; } if (withLength === null) return null; @@ -517,14 +563,17 @@ export function lowerWithArraySpecies( const receiverRef: IrExpr = { kind: "varRef", localId: local.id, type: receiver.type, loc }; if (mode.callback) { // The species constructor runs before the first callback: the - // arguments are evaluated in order, then the instance is created (with - // the receiver's length for map, 0 otherwise), then the method runs and - // its elements move into the instance. - if ( - result.kind !== "call" || - result.args[0]?.kind !== "varRef" || - result.args[0].localId !== local.id - ) + // arguments are evaluated in order, the receiver's length is read, the + // instance is created (with that length for map, 0 otherwise), then + // the method runs to that length and its elements move into the + // instance. + const bounded = + result.kind === "call" && + result.args[0]?.kind === "varRef" && + result.args[0].localId === local.id + ? boundedArrayHofHelper(lowerer, result.callee) + : null; + if (bounded === null || result.kind !== "call") lowerer.unsupported( "SC1090", call, @@ -537,9 +586,25 @@ export function lowerWithArraySpecies( stmts.push({ kind: "varDecl", localId: held.id, init: arg, loc }); args.push({ kind: "varRef", localId: held.id, type: arg.type, loc }); } + const bound = lowerer.declareHiddenLocal("%speciesLength", F64); + stmts.push({ + kind: "varDecl", + localId: bound.id, + init: { + kind: "arrIntrinsic", + method: "length", + receiver: receiverRef, + args: [], + type: F64, + loc, + }, + loc, + }); + const boundRef: IrExpr = { kind: "varRef", localId: bound.id, type: F64, loc }; + args.push(boundRef); const instance = lowerer.declareHiddenLocal("%speciesInstance", receiver.type); const length: IrExpr = mode.resultLength - ? { kind: "arrIntrinsic", method: "length", receiver: receiverRef, args: [], type: F64, loc } + ? boundRef : { kind: "numLit", value: 0, type: F64, loc }; stmts.push({ kind: "varDecl", @@ -561,7 +626,7 @@ export function lowerWithArraySpecies( fn: "array.speciesFinish", args: [ { kind: "varRef", localId: instance.id, type: receiver.type, loc }, - { ...result, args }, + { ...result, callee: bounded, args }, { kind: "boolLit", value: mode.setLength, type: BOOL, loc }, ], type: result.type, diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index edf65ab2b..923224950 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -3171,3 +3171,33 @@ export function lowerTuplePredicateCall( loc, }; } + +/** The `%arr.` helper `name` (map, flatMap or filter) taking its + * iteration bound as a last parameter: the loop runs to the bound instead + * of the receiver's length when it starts. ArraySpeciesCreate reads the + * receiver's length before the species constructor runs, so a constructor + * that appends to the receiver does not extend the loop + * (lowerWithArraySpecies). Null when `name` is no such helper. */ +export function boundedArrayHofHelper(lowerer: Lowerer, name: string): string | null { + const info = lowerer.arrHofInfo.get(name); + if (!info || info.method === "forEach") return null; + const bounded = `${name}.bounded`; + if (lowerer.liftedFnNamed(bounded)) return bounded; + const fn = lowerer.liftedFnNamed(name); + if (!fn) return null; + let replaced = false; + const body = fn.body.map((stmt): IrStmt => { + if (replaced || stmt.kind !== "varDecl" || stmt.localId !== "n.0") return stmt; + replaced = true; + return { ...stmt, init: varRef("bound.0", F64, stmt.loc) }; + }); + if (!replaced) return null; + lowerer.liftedFns.push({ + ...fn, + name: bounded, + params: [...fn.params, { localId: "bound.0", name: "bound", type: F64 }], + locals: [...fn.locals, { id: "bound.0", name: "bound", type: F64, mutable: false }], + body, + }); + return bounded; +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json new file mode 100644 index 000000000..a02b66c5a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7730-array-subclass-retyped-trace.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7730-array-subclass-retyped-trace.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json new file mode 100644 index 000000000..76a9b3988 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7731-array-subclass-deep-release.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7731-array-subclass-deep-release.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json new file mode 100644 index 000000000..c36a8a1ba --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7732-array-subclass-species-bound.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7732-array-subclass-species-bound.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json new file mode 100644 index 000000000..96cb08414 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7733-array-subclass-optional-length.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7733-array-subclass-optional-length.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index 3b243dbb0..40e0580a4 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -1868,8 +1868,21 @@ const ScrArrClass *scr_arr_class_of(const ScrArr *a) { return scr_arr_ext_of((ScrArr *)a)->cls; } -void scr_arr_subclass_gc_drop(ScrArr *a, bool traced) { - if (!traced) scr_arr_release_contents(a); +/* Whether an Array subclass instance's elements are the collector's: its + * current element ops trace them. A species result can retype an empty + * instance's storage (scr_arr_species_move), so this follows the storage, + * not the element type the class declares. */ +static bool scr_arr_subclass_elements_traced(const ScrArr *a) { + const ScrArrOps *ops = scr_arr_ops(a); + return ops != NULL && ops->trace != NULL; +} + +void scr_arr_subclass_trace(void *a, ScrTraceVisit visit, void *ctx) { + if (scr_arr_subclass_elements_traced((ScrArr *)a)) scr_arr_trace_v(a, visit, ctx); +} + +void scr_arr_subclass_gc_drop(ScrArr *a) { + if (!scr_arr_subclass_elements_traced(a)) scr_arr_release_contents(a); scr_arr_free_parts(a); a->data = NULL; a->len = a->cap = a->filled = 0; @@ -1906,7 +1919,7 @@ void scr_arr_release(ScrArr *a) { /* An Array subclass instance: its class tears it down. */ if ((a->spare & SCR_ARR_HAS_EXT) && scr_arr_ext_of(a)->cls) { if (a->cyc) scr_cyc_dead(a); - scr_arr_ext_of(a)->cls->destroy(a); + scr_rc_destroy(a, scr_arr_ext_of(a)->cls->destroy); return; } /* Nothing to tear down: free the storage without scr_rc_destroy's diff --git a/packages/runtime/src/scr_array.h b/packages/runtime/src/scr_array.h index 56b7511e2..84057ffc9 100644 --- a/packages/runtime/src/scr_array.h +++ b/packages/runtime/src/scr_array.h @@ -185,7 +185,7 @@ typedef struct ScrArrClass { const char *name; /* Destroys an instance whose count reached zero: releases the class's own * fields, then scr_arr_subclass_drop, then frees the instance. */ - void (*destroy)(ScrArr *a); + void (*destroy)(void *a); /* The class's own fields as a dyn object in declaration order (the array * is passed owned, +1), or NULL when the class declares none. util.inspect * and Object.keys list them after the elements. */ @@ -503,7 +503,10 @@ void scr_arr_subclass_init(ScrArr *a, uint8_t elem, const ScrArrOps *ops, const bool cyc); /* The collector's teardown of an Array subclass instance's storage: the * elements are released unless they are the collector's (`traced`). */ -void scr_arr_subclass_gc_drop(ScrArr *a, bool traced); +void scr_arr_subclass_gc_drop(ScrArr *a); +/* The collector trace of an Array subclass instance's elements: visits + * them when its current element ops trace them. */ +void scr_arr_subclass_trace(void *a, ScrTraceVisit visit, void *ctx); /* super(...args) in a class that extends Array (the Array constructor's * length-or-elements rule); `args` is borrowed. */ void scr_arr_subclass_construct(ScrArr *a, ScrArr *args, bool dyn_elems); diff --git a/tests/corpus/7730-array-subclass-retyped-trace.ts b/tests/corpus/7730-array-subclass-retyped-trace.ts new file mode 100644 index 000000000..d1546c0af --- /dev/null +++ b/tests/corpus/7730-array-subclass-retyped-trace.ts @@ -0,0 +1,33 @@ +// A cycle-capable class that extends Array (its elements and its own field +// can close a cycle) whose map results take other element types: the +// collector traces and tears down each instance by the elements it holds. +// Garbage cycles make the collector run while the results are alive. +class Holder { + group: Group | null = null; +} + +class Group extends Array { + owner: Holder | null = null; +} + +function churn(rounds: number): number { + let made = 0; + for (let i = 0; i < rounds; i++) { + const holder = new Holder(); + const group = new Group(); + group.owner = holder; + holder.group = group; + group.push(holder); + made++; + } + return made; +} + +const groups = new Group(); +groups.push(new Holder(), null, new Holder()); +const numbers = groups.map((_, i) => i * 1.5 + 1e6); +const labels = groups.map((_, i) => `label-${i}`); +console.log(churn(20000)); +console.log(numbers instanceof Group, numbers.length, numbers.join(",")); +console.log(labels instanceof Group, labels.join(",")); +console.log(churn(20000), numbers[2], labels[1]); diff --git a/tests/corpus/7731-array-subclass-deep-release.ts b/tests/corpus/7731-array-subclass-deep-release.ts new file mode 100644 index 000000000..6e4b4c352 --- /dev/null +++ b/tests/corpus/7731-array-subclass-deep-release.ts @@ -0,0 +1,17 @@ +// Releasing a long chain of instances of a class that extends Array, each +// holding the next one, tears them down without growing the native stack. +class Link extends Array { + next: Link | null = null; +} + +let head: Link | null = new Link(); +let tail = head; +for (let i = 0; i < 300000; i++) { + const link = new Link(); + link.push(i); + tail.next = link; + tail = link; +} +console.log(tail.length, tail[0]); +head = null; +console.log("released", head); diff --git a/tests/corpus/7732-array-subclass-species-bound.ts b/tests/corpus/7732-array-subclass-species-bound.ts new file mode 100644 index 000000000..715c51165 --- /dev/null +++ b/tests/corpus/7732-array-subclass-species-bound.ts @@ -0,0 +1,32 @@ +// map, filter and flatMap read the receiver's length before the species +// constructor runs: elements the constructor appends to the receiver are +// not visited. +let watched: Growing | null = null; + +class Growing extends Array { + constructor(...args: number[]) { + super(...args); + if (watched !== null) watched.push(watched.length * 100); + } +} + +const source = Growing.from([1, 2, 3]); +watched = source; +const visits: number[] = []; +const doubled = source.map((value) => { + visits.push(value); + return value * 2; +}); +console.log(doubled.join(","), visits.join(","), source.join(",")); +visits.length = 0; +const kept = source.filter((value) => { + visits.push(value); + return value > 1; +}); +console.log(kept.join(","), visits.join(","), source.length); +visits.length = 0; +const spread = source.flatMap((value) => { + visits.push(value); + return [value]; +}); +console.log(spread.length, visits.length, source.length); diff --git a/tests/corpus/7733-array-subclass-optional-length.ts b/tests/corpus/7733-array-subclass-optional-length.ts new file mode 100644 index 000000000..302fc22cb --- /dev/null +++ b/tests/corpus/7733-array-subclass-optional-length.ts @@ -0,0 +1,26 @@ +// A class that extends Array whose constructor's length parameter is +// optional or has a default takes the species length argument: map, +// filter and slice construct their results through it. +class Optional extends Array { + constructor(length?: number) { + super(length ?? 0); + } +} + +class Defaulted extends Array { + tag: string; + constructor(length = 0, tag = "none") { + super(length); + this.tag = tag; + } +} + +const optional = Optional.from([1, 2, 3]); +const mapped = optional.map((value) => value * 10); +console.log(mapped instanceof Optional, mapped.join(","), optional.slice(1) instanceof Optional); +console.log(optional.filter((value) => value !== 2).join(",")); + +const defaulted = new Defaulted(2).fill("x"); +const sliced = defaulted.slice(0, 1); +if (sliced instanceof Defaulted) console.log(sliced.length, sliced.tag); +console.log(defaulted.map((s) => s + "!").join(",")); diff --git a/tests/diagnostics/array-subclass-fences.ts b/tests/diagnostics/array-subclass-fences.ts index cc2966201..478e615f9 100644 --- a/tests/diagnostics/array-subclass-fences.ts +++ b/tests/diagnostics/array-subclass-fences.ts @@ -1,6 +1,7 @@ // Array subclasses that do not compile yet, each refused where it is // declared: a generic subclass, a subclass of a class that extends Array, -// and a class that declares its own Symbol.species. +// a class that declares its own Symbol.species, and a class whose elements +// are its own instances. class Bag extends Array { firstOr(fallback: T): T { return this.length > 0 ? this[0]! : fallback; @@ -16,6 +17,9 @@ class Speciated extends Array { } } +class Nested extends Array {} + console.log(new Bag().firstOr(1)); console.log(new Derived().length); console.log(new Speciated().length); +console.log(new Nested().length); diff --git a/tests/harness/__snapshots__/array-subclass-fences.ts.txt b/tests/harness/__snapshots__/array-subclass-fences.ts.txt index 3d81a58a9..97e7bdc8d 100644 --- a/tests/harness/__snapshots__/array-subclass-fences.ts.txt +++ b/tests/harness/__snapshots__/array-subclass-fences.ts.txt @@ -1,41 +1,55 @@ -array-subclass-fences.ts:4:22 - error SC1090: a generic class that extends Array is not supported yet +array-subclass-fences.ts:5:22 - error SC1090: a generic class that extends Array is not supported yet - 3 | // and a class that declares its own Symbol.species. - 4 | class Bag extends Array { + 4 | // are its own instances. + 5 | class Bag extends Array { | ^~~~~~~~ - 5 | firstOr(fallback: T): T { + 6 | firstOr(fallback: T): T { -array-subclass-fences.ts:11:23 - error SC1090: extending 'Base', a class that extends Array (one level of Array subclass compiles) is not supported yet +array-subclass-fences.ts:12:23 - error SC1090: extending 'Base', a class that extends Array (one level of Array subclass compiles) is not supported yet - 10 | class Base extends Array {} - 11 | class Derived extends Base {} + 11 | class Base extends Array {} + 12 | class Derived extends Base {} | ^~~~ - 12 | + 13 | -array-subclass-fences.ts:14:3 - error SC1090: Symbol.species on a class that extends Array (its methods' results construct the class itself) is not supported yet +array-subclass-fences.ts:15:3 - error SC1090: Symbol.species on a class that extends Array (its methods' results construct the class itself) is not supported yet - 13 | class Speciated extends Array { - 14 | static get [Symbol.species](): ArrayConstructor { + 14 | class Speciated extends Array { + 15 | static get [Symbol.species](): ArrayConstructor { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 15 | return Array; + 16 | return Array; -array-subclass-fences.ts:19:13 - error SC1090: constructing values other than classes declared in the program is not supported yet +array-subclass-fences.ts:20:22 - error SC1090: classes that extend an Array of their own instances are not supported yet - 18 | - 19 | console.log(new Bag().firstOr(1)); + 19 | + 20 | class Nested extends Array {} + | ^~~~~~~~~~~~~~~~~~~~ + 21 | + +array-subclass-fences.ts:22:13 - error SC1090: constructing values other than classes declared in the program is not supported yet + + 21 | + 22 | console.log(new Bag().firstOr(1)); | ^~~~~~~~~~~~~~~~~ - 20 | console.log(new Derived().length); + 23 | console.log(new Derived().length); -array-subclass-fences.ts:20: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 +array-subclass-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 - 19 | console.log(new Bag().firstOr(1)); - 20 | console.log(new Derived().length); + 22 | console.log(new Bag().firstOr(1)); + 23 | console.log(new Derived().length); | ^~~~~~~~~~~~~ - 21 | console.log(new Speciated().length); + 24 | console.log(new Speciated().length); -array-subclass-fences.ts:21: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 +array-subclass-fences.ts:24: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 - 20 | console.log(new Derived().length); - 21 | console.log(new Speciated().length); + 23 | console.log(new Derived().length); + 24 | console.log(new Speciated().length); | ^~~~~~~~~~~~~~~ - 22 | \ No newline at end of file + 25 | console.log(new Nested().length); + +array-subclass-fences.ts:25: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 + + 24 | console.log(new Speciated().length); + 25 | console.log(new Nested().length); + | ^~~~~~~~~~~~ + 26 | \ No newline at end of file diff --git a/tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json b/tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7730-array-subclass-retyped-trace.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json b/tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7731-array-subclass-deep-release.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json b/tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7732-array-subclass-species-bound.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } diff --git a/tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json b/tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json new file mode 100644 index 000000000..a663de213 --- /dev/null +++ b/tests/harness/corpus-costs/7733-array-subclass-optional-length.ts.json @@ -0,0 +1 @@ +{ "plain": 11000, "san": 9000 } From 1e2f9b2dfcbe82867161d745b8a2d47c053599c8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Fri, 23 Oct 2026 23:52:50 -0500 Subject: [PATCH 018/230] Resolve computed createRequire specifiers at run time In JavaScript dependencies, a createRequire require call with a computed specifier, and require.resolve called with a computed request or read as a value, now compile. The new runtime unit scr_require.c resolves the specifier with Node 24's CommonJS algorithm (builtins, relative and absolute paths with Node's extension search, package "main", "exports" and self-reference, node_modules and NODE_PATH lookup) when the call runs: - require.resolve returns the id or real path Node returns, and throws Node's MODULE_NOT_FOUND error when nothing resolves. - require throws the same MODULE_NOT_FOUND error when nothing resolves, so packages that probe for optional addons, such as pi-tui, take their absent branch as in Node. When the specifier resolves to a native addon, a builtin or another file, Node would load that module: the program prints an SC2020 error naming it and exits with status 1, without running the catch clause around the call. Coverage lists these calls as deferred to runtime. TypeScript sources keep the compile-time refusal. Refs #1777. --- docs/content/limitations/native-addons.mdx | 10 + .../src/backend/executable-features.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 2 + .../src/backend/native-link-info-core.ts | 3 + .../compiler/src/backend/native/contracts.ts | 3 + .../compiler/src/backend/native/executable.ts | 2 + .../compiler/src/backend/runtime-pack-core.ts | 2 + .../compiler/src/diagnostics/diagnostic.ts | 15 + .../src/executable/executable-cache.ts | 1 + .../lowering/builtins/module-resolution.ts | 158 +++- .../lowering/expressions/identifiers.ts | 8 +- .../lowering/expressions/property-access.ts | 14 + .../src/frontend/lowering/import-meta.ts | 13 +- packages/compiler/src/frontend/program.ts | 63 +- packages/compiler/src/index.ts | 1 + packages/compiler/src/ir/builtin-effects.ts | 2 + .../compiler/src/ir/builtin-signatures.ts | 12 + packages/compiler/src/ir/runtime-features.ts | 4 + .../compiler/src/native/sanitizer.test.ts | 1 + .../main.ts.json | 6 + .../main.ts.json | 6 + .../main.ts.json | 6 + .../runtime-pack-matrix.mjs | 2 + packages/runtime/src/scr_path.h | 19 +- packages/runtime/src/scr_require.c | 895 ++++++++++++++++++ .../8210-require-computed-missing/main.ts | 7 + .../node_modules/optional-probe/index.d.ts | 2 + .../node_modules/optional-probe/index.js | 46 + .../node_modules/optional-probe/package.json | 1 + .../8211-require-resolve-computed/main.ts | 7 + .../node_modules/@scope/scoped/lib/node.js | 1 + .../node_modules/@scope/scoped/lib/other.js | 1 + .../node_modules/@scope/scoped/package.json | 1 + .../node_modules/plain-index/index.js | 1 + .../node_modules/resolve-probe/data.json | 1 + .../node_modules/resolve-probe/index.d.ts | 1 + .../node_modules/resolve-probe/index.js | 50 + .../node_modules/resolve-probe/package.json | 1 + .../node_modules/with-exports/cjs.cjs | 1 + .../node_modules/with-exports/esm.mjs | 1 + .../node_modules/with-exports/lib/feature.js | 1 + .../node_modules/with-exports/lib/helper.js | 1 + .../node_modules/with-exports/main.js | 1 + .../node_modules/with-exports/package.json | 1 + .../node_modules/with-main/lib/entry.js | 1 + .../node_modules/with-main/package.json | 1 + .../main.ts | 18 + .../native-helper/dist/index.d.ts | 11 + .../node_modules/native-helper/dist/index.js | 2 + .../native-helper/dist/native-module-path.js | 20 + .../native-helper/dist/native-platform.js | 48 + .../linux-arm64/linux-helper-x11.node | 1 + .../prebuilds/linux-x64/linux-helper-x11.node | 1 + .../node_modules/native-helper/package.json | 1 + tests/fixtures/npm-static/addon-probe-cli.ts | 5 + .../npm-static/addon-probe-installed-cli.ts | 4 + .../node_modules/addon-probe/index.d.ts | 3 + .../node_modules/addon-probe/index.js | 38 + .../node_modules/addon-probe/installed.js | 1 + .../addon-probe/native/installed.node | 1 + .../node_modules/addon-probe/package.json | 7 + .../main.ts.json | 1 + .../main.ts.json | 1 + .../main.ts.json | 1 + tests/harness/npm-static.test.ts | 92 +- 65 files changed, 1597 insertions(+), 36 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8210-require-computed-missing/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8211-require-resolve-computed/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8212-native-helper-probe-no-display/main.ts.json create mode 100644 packages/runtime/src/scr_require.c create mode 100644 tests/corpus/8210-require-computed-missing/main.ts create mode 100644 tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.d.ts create mode 100644 tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.js create mode 100644 tests/corpus/8210-require-computed-missing/node_modules/optional-probe/package.json create mode 100644 tests/corpus/8211-require-resolve-computed/main.ts create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/node.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/other.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/package.json create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/plain-index/index.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/data.json create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.d.ts create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/package.json create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/cjs.cjs create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/esm.mjs create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/feature.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/helper.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/main.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-exports/package.json create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-main/lib/entry.js create mode 100644 tests/corpus/8211-require-resolve-computed/node_modules/with-main/package.json create mode 100644 tests/corpus/8212-native-helper-probe-no-display/main.ts create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.d.ts create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.js create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-module-path.js create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-platform.js create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-arm64/linux-helper-x11.node create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-x64/linux-helper-x11.node create mode 100644 tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/package.json create mode 100644 tests/fixtures/npm-static/addon-probe-cli.ts create mode 100644 tests/fixtures/npm-static/addon-probe-installed-cli.ts create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/index.js create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/installed.js create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/native/installed.node create mode 100644 tests/fixtures/npm-static/node_modules/addon-probe/package.json create mode 100644 tests/harness/corpus-costs/8210-require-computed-missing/main.ts.json create mode 100644 tests/harness/corpus-costs/8211-require-resolve-computed/main.ts.json create mode 100644 tests/harness/corpus-costs/8212-native-helper-probe-no-display/main.ts.json diff --git a/docs/content/limitations/native-addons.mdx b/docs/content/limitations/native-addons.mdx index 469d1cdd2..1df9470cd 100644 --- a/docs/content/limitations/native-addons.mdx +++ b/docs/content/limitations/native-addons.mdx @@ -2,5 +2,15 @@ Node-API (N-API) and V8 `.node` addons depend on Node.js itself, so compiled pro Direct `createRequire` calls to local addons are rejected at compile time. A package that requires an addon needs a JavaScript implementation that compiles, or a replacement through native FFI. +A package that looks for an optional addon at run time compiles with `--npm-static`. +In JavaScript dependencies, a `createRequire` require call with a computed specifier, and `require.resolve` called with a computed request or read as a value, resolve the specifier with the Node.js algorithm when the program runs. +`require.resolve` returns the path Node.js returns. +When nothing resolves, both throw the `MODULE_NOT_FOUND` error that Node.js throws, so a package that probes for a missing addon takes the same branch as in Node.js. +When the specifier of a require call resolves to a native addon, a builtin, or a file that is not part of the compiled program, the program prints an SC2020 error that names it and exits with status 1, because Node.js would load that module. +A `catch` clause around the call does not run in that case. +Resolution steps that the runtime does not reproduce, such as package `imports` specifiers, `require.resolve` options, and subpaths that a package does not export, end the program the same way. +Coverage lists these calls as deferred to runtime. +This applies until compiled programs can load addons through Node.js. + Use [Native FFI](/docs/ffi#replacing-a-node-api-addon) to call a plain C ABI function linked from an object or static archive. The guide includes a replacement for a small Node-API helper. diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index a9076af22..0eae21565 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -33,6 +33,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), ...(features.dynBuffer ? { dynBuffer: true } : {}), ...(features.builtinValues ? { builtinValues: true } : {}), + ...(features.computedRequire ? { computedRequire: true } : {}), events: features.processEvents, emitter: features.emitter, symbol: features.symbol, diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 014191314..49d0bed68 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -270,6 +270,8 @@ export const LIB_FN_SYMS: Record = { "module.id": "scr_module_id", "module.path": "scr_module_path", "module.paths": "scr_module_paths", + "module.requireComputed": "scr_require_computed", + "module.resolveComputed": "scr_require_resolve", "module.children": "scr_module_children", "module.parent": "scr_module_parent", "module.loaded": "scr_module_loaded", diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index c073bb9a6..3ff6b0423 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -62,6 +62,9 @@ export interface NativeLinkFeatures { /** Builtin functions read as values (Buffer.byteLength, Buffer.alloc, * fs.realpathSync) link scr_builtin_values.c; absent (false) otherwise. */ builtinValues?: boolean; + /** createRequire's computed require and require.resolve link + * scr_require.c; absent (false) otherwise. */ + computedRequire?: boolean; events: boolean; emitter: boolean; symbol: boolean; diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 53e160984..6606745a3 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -131,6 +131,9 @@ export interface CcOptions { /** Buffer.prototype methods called on checked-dynamic values: * compiles scr_bytes_dyn.c. */ dynBuffer?: boolean; + /** createRequire's computed require and require.resolve: compiles + * scr_require.c. */ + computedRequire?: boolean; /** Builtin functions read as values: compiles scr_builtin_values.c. */ builtinValues?: boolean; /** The program constructs EventTarget/Event/CustomEvent values or diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 1578a0c80..8fde247a7 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -186,6 +186,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ unit((u) => u.opts.dynBuffer === true && u.opts.dynInvoke, "scr_bytes_dyn.c"), // Builtin functions read as values in JavaScript dependencies. unit((u) => u.opts.builtinValues === true, "scr_builtin_values.c"), + // createRequire's computed require and require.resolve. + unit((u) => u.opts.computedRequire === true, "scr_require.c"), unit((u) => u.opts.dc, "scr_dc.c"), unit((u) => u.opts.dynTier, "scr_js_value.c", "scr_js_object.c"), unit((u) => u.opts.eventTarget || u.nativeFetch, "scr_event_target.c"), diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 75953be59..4b8d20bac 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -91,6 +91,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures { eventsOnceSignal: boolean; dynBuffer: boolean; builtinValues: boolean; + computedRequire: boolean; warningReplace: boolean; stdioExtended: boolean; nativeFetch: boolean; @@ -289,6 +290,7 @@ export function effectiveRuntimeFeatures( eventsOnceSignal: features.eventsOnceSignal === true, dynBuffer: features.dynBuffer === true, builtinValues: features.builtinValues === true, + computedRequire: features.computedRequire === true, warningReplace: features.warningReplace === true, stdioExtended: features.stdioExtended === true, nativeFetch, diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 2fd8b6fb0..4579f637f 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -776,6 +776,21 @@ export function nativeAddonDiag(specifier: string, loc: SrcLoc): ScrDiagnostic { }; } +/** A createRequire require or require.resolve call whose specifier is + * computed at run time, where the runtime resolves it like Node + * (scr_require.c). Coverage lists the call as deferred: a require whose + * specifier resolves to a module ends the program with this code, because + * the module graph is fixed at build time. */ +export function computedRequireDiag(surface: string, loc: SrcLoc): ScrDiagnostic { + return { + code: "SC2020", + message: `${surface} with a computed specifier resolves at run time; a require of a specifier that resolves to a module, such as a native addon, ends the program`, + loc, + milestone: "later", + hint: 'require("") compiles the module into the program; native addons need a replacement through native FFI (https://scriptc.dev/ffi#replacing-a-node-api-addon)', + }; +} + /** Records are monomorphic structs, so a value's shape must match the * expected shape exactly OR narrow through the width-copy family * (recordWidthPlan / the overflow capture: target fields copied off the diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 50f783761..a82fe967f 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -76,6 +76,7 @@ export interface EarlyExecutableNativeFeatures { eventsOnceSignal?: boolean; dynBuffer?: boolean; builtinValues?: boolean; + computedRequire?: boolean; events: boolean; emitter: boolean; symbol: boolean; diff --git a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts index 6b2e9e589..f00e78b0a 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts @@ -1,8 +1,8 @@ -import { nodeThrowExpr, boolLit, strLit, varRef } from "../../../ir/build.js"; +import { dynUndefinedExpr, nodeThrowExpr, boolLit, strLit, varRef } from "../../../ir/build.js"; import { dirname, isAbsolute, resolve } from "node:path"; import * as ts from "../../ts7/adapter.js"; import { type Lowerer, PoisonError } from "../lowerer.js"; -import { canonicalBuiltinModule, isNodeEsmFile, locOf } from "../../program.js"; +import { canonicalBuiltinModule, isJsSourceFile, isNodeEsmFile, locOf } from "../../program.js"; import { isRelativeSpecifier } from "../../workspace-registry.js"; import { probeNodeRequireRefusal } from "../../npm.js"; import { isNpmStaticPackage } from "../../npm-static.js"; @@ -15,21 +15,25 @@ import { type RuntimeResolveResult, } from "../../runtime-resolve.js"; import { + computedRequireDiag, invalidJsonModuleDiag, nativeAddonDiag, unsupportedPackageImportDiag, } from "../../../diagnostics/diagnostic.js"; -import { NODE_BUILTIN_MODULES_V24 } from "../surfaces.js"; +import { NODE_BUILTIN_MODULES_V24, builtinModulesArrayLit } from "../surfaces.js"; +import { moduleFileName } from "../import-meta.js"; import { BOOL, DYN, type IrExpr, + type IrStmt, type IrType, NULL_T, STRING, type SrcLoc, UNDEFINED_T, arrayOf, + funcOf, } from "../../../ir/ir.js"; import { lowerFfiMemoryModule } from "../native-ffi.js"; import { @@ -38,6 +42,7 @@ import { createRequireCalleeFileOf, } from "./module-bindings.js"; import { stripTypeCasts } from "./arguments.js"; +import { keyedHelperName } from "../stable-names.js"; /** node:module's two compiler-only calls. isBuiltin compares one evaluated * string against the pinned Node 24 list (bare builtins also accept their @@ -134,6 +139,102 @@ export function lowerNodeModuleCall( }; } +/** The run-time arguments of scr_require.c's computed require and + * require.resolve after the specifier: the requiring file (Node's require + * stack names it), the call's "file:line" for the SC2020 refusal, and the + * builtin ids. The call joins the runtime-fence ledger, so coverage lists + * it as deferred to runtime. */ +function computedRequireArgs( + lowerer: Lowerer, + surface: string, + node: ts.Node, + baseFile: ts.SourceFile, +): IrExpr[] { + const loc = locOf(node); + lowerer.runtimeFences.push(computedRequireDiag(surface, loc)); + const line = ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(node), loc.start).line + 1; + return [ + strLit(moduleFileName(lowerer, baseFile), loc), + strLit(`${loc.file}:${line}`, loc), + builtinModulesArrayLit(loc), + ]; +} + +/** A createRequire loader's require.resolve with a computed request, in + * JavaScript source: Node's resolution at run time (scr_require.c), with + * the options argument checked there. */ +function computedResolveCall( + lowerer: Lowerer, + node: ts.Node, + baseFile: ts.SourceFile, + request: IrExpr, + options: IrExpr, + loc: SrcLoc, +): IrExpr { + return { + kind: "libCall", + fn: "module.resolveComputed", + args: [ + request, + options, + ...computedRequireArgs(lowerer, "createRequire's require.resolve", node, baseFile), + ], + type: STRING, + loc, + }; +} + +/** A createRequire loader's `resolve` read as a value in JavaScript source + * (passed on, or picked by `??`): a function that resolves its request at + * run time, so `(options.resolve ?? require.resolve)(id)` behaves as in + * Node. */ +export function createRequireResolveValue( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, + loc: SrcLoc, +): IrExpr { + const baseFile = createRequireCalleeFileOf(lowerer, access.expression); + if (baseFile === null || !isJsSourceFile(ts.getSourceFileOfNode(access))) { + lowerer.noLowering( + "createRequire's require.resolve as a value", + access, + 'call it with a string literal (require.resolve("")), which resolves at build time', + ); + } + const name = keyedHelperName(lowerer, "%createRequire.resolve", `${loc.file}:${loc.start}`); + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + const call = computedResolveCall( + lowerer, + access, + baseFile, + varRef("request", DYN, loc), + varRef("options", DYN, loc), + loc, + ); + lowerer.liftedFns.push({ + name, + params: [ + { localId: "request", name: "request", type: DYN }, + { localId: "options", name: "options", type: DYN }, + ], + locals: [ + { id: "request", name: "request", type: DYN, mutable: false }, + { id: "options", name: "options", type: DYN, mutable: false }, + ], + returnType: STRING, + body: [{ kind: "return", value: call, loc }], + loc, + }); + } + return { + kind: "dynFrom", + value: { kind: "closure", fnName: name, captures: [], type: funcOf([DYN, DYN], STRING), loc }, + fnName: access.name.text, + type: DYN, + loc, + }; +} + export function lowerCreateRequireCall( lowerer: Lowerer, call: ts.CallExpression, @@ -142,10 +243,35 @@ export function lowerCreateRequireCall( const cr = createRequireSpecOf(lowerer, call); if (cr === null) return null; if (cr.spec === null) { - lowerer.noLowering( - "createRequire's require with this argument shape", + const args = call.arguments.nodes; + if (!isJsSourceFile(ts.getSourceFileOfNode(call)) || args.some((a) => ts.isSpreadElement(a))) { + lowerer.noLowering( + "createRequire's require with this argument shape", + call, + 'the compiled module graph is fixed at build time — the one lowered form is require("")', + ); + } + // Node evaluates every argument, then requires the first. + const stmts: IrStmt[] = []; + let id = args.length > 0 ? lowerer.lowerExprExpecting(args[0]!, DYN) : dynUndefinedExpr(loc); + if (args.length > 1) { + const slot = lowerer.declareHiddenLocal("%require.id", DYN); + stmts.push({ kind: "varDecl", localId: slot.id, init: id, loc }); + for (const arg of args.slice(1)) + stmts.push({ kind: "exprStmt", expr: lowerer.lowerExpr(arg), loc: locOf(arg) }); + id = varRef(slot.id, DYN, loc); + } + const require: IrExpr = { + kind: "libCall", + fn: "module.requireComputed", + args: [id, ...computedRequireArgs(lowerer, "createRequire's require", call, cr.baseFile)], + type: DYN, + loc, + }; + return lowerer.coerceInto( call, - 'the compiled module graph is fixed at build time — the one lowered form is require("")', + stmts.length === 0 ? require : { kind: "seqExpr", stmts, result: require, type: DYN, loc }, + lowerer.irTypeOf(call), ); } const spec = cr.spec; @@ -380,6 +506,26 @@ export function lowerRequireResolveCall(lowerer: Lowerer, call: ts.CallExpressio ); } const specifier = staticString(call.arguments.nodes[0]); + // A computed request (or options) through a createRequire loader in + // JavaScript source resolves at run time. + if ( + (specifier === null || staticResolveOptionPaths(call.arguments.nodes[1]) === null) && + !pathsCall && + createRequireCalleeFileOf(lowerer, receiver) !== null && + isJsSourceFile(ts.getSourceFileOfNode(call)) + ) { + const [request, options] = call.arguments.nodes; + return computedResolveCall( + lowerer, + call, + baseFile, + lowerer.lowerExprExpecting(request!, DYN), + options === undefined + ? dynUndefinedExpr(locOf(call)) + : lowerer.lowerExprExpecting(options, DYN), + locOf(call), + ); + } if (specifier === null) { lowerer.noLowering( `${pathsCall ? "require.resolve.paths" : "require.resolve"} with a runtime-computed request`, diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index f7ba7a115..159873630 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -88,7 +88,7 @@ import { lowerGlobalValue } from "../lower-global-value.js"; import { fenceEnumObjectValue } from "../lower-enums.js"; import { mixinFnOfCallee } from "../lower-mixins.js"; import { isNavigatorGlobal, refuseNavigator } from "../builtins/navigator.js"; -import { wasiGuestPath } from "../../../wasi-paths.js"; +import { moduleFileName } from "../import-meta.js"; export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: SrcLoc): IrExpr { if (lowerer.isStdlibGlobal(expr, "console")) { @@ -1014,12 +1014,6 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: ); } -function moduleFileName(lowerer: Lowerer, sf: ts.SourceFile): string { - return lowerer.targetPlatform === "wasi" - ? (wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")) - : sf.fileName; -} - /** A computed-key access (`value[key]` after `typeof value === "object"`) * of a runtime-optional binding the checker narrowed to one record arm of a * wider union. The binding re-tags to that record or undefined, so the keyed diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 1a4883e5a..a26caf4d9 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -146,6 +146,8 @@ import { nullishGenericBindingUnitOf } from "../binding-analysis.js"; import { blockedBindingUseDiag } from "../../../diagnostics/diagnostic.js"; import { npmStaticPackageOfPath } from "../../npm-static.js"; import { importMetaField, isImportMeta } from "../import-meta.js"; +import { createRequireCalleeFileOf } from "../builtins/module-bindings.js"; +import { createRequireResolveValue } from "../builtins/module-resolution.js"; import { builtinPrototypeMethod, lowerArrayIsArrayValue, @@ -286,6 +288,14 @@ const DGRAM_METHOD_VALUES = new Set([ "unref", ]); +function isCreateRequireResolveValue(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean { + return ( + expr.name.text === "resolve" && + !expr.questionDotToken && + createRequireCalleeFileOf(lowerer, expr.expression) !== null + ); +} + export function lowerPropertyAccessExpr( lowerer: Lowerer, expr: ts.PropertyAccessExpression, @@ -1275,6 +1285,10 @@ export function lowerPropertyAccessExpr( return declared && declared.kind !== "void" ? lowerer.coerceToExpected(read, declared) : read; } } + // A createRequire loader's resolve read as a value (passed on, or + // picked by `??`): a function that resolves its request at run time. + if (isCreateRequireResolveValue(lowerer, expr)) + return createRequireResolveValue(lowerer, expr, loc); // The lib fence's PROPERTY chokepoint: a stdlib-declared member that // no lowering above claimed ([1,2].entries, Math.SQRT2, Promise.all, // re.exec as a value, ...) reports SC2020 here. diff --git a/packages/compiler/src/frontend/lowering/import-meta.ts b/packages/compiler/src/frontend/lowering/import-meta.ts index 796e64b69..ccc86bfba 100644 --- a/packages/compiler/src/frontend/lowering/import-meta.ts +++ b/packages/compiler/src/frontend/lowering/import-meta.ts @@ -17,14 +17,19 @@ export function isImportMeta(expr: ts.Expression): boolean { ); } +/** The module's file name as the compiled program reports it + * (import.meta.filename): the WASI guest path on wasi targets. */ +export function moduleFileName(lowerer: Lowerer, sf: ts.SourceFile): string { + return lowerer.targetPlatform === "wasi" + ? (wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")) + : sf.fileName; +} + /** File-backed metadata is fixed by the compiled module graph. Reads share * one implementation across dot access, folded keys and selected bindings. */ export function importMetaField(lowerer: Lowerer, node: ts.Node, name: string): IrExpr { const sf = ts.getSourceFileOfNode(node); - const fileName = - lowerer.targetPlatform === "wasi" - ? (wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")) - : sf.fileName; + const fileName = moduleFileName(lowerer, sf); const loc = locOf(node); switch (name) { case "env": diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 8075379b8..517b9accf 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -2519,12 +2519,29 @@ export function isCreateRequireBinding7(program: ts.Program, callee: ts.Identifi ); } -/** Only literal calls and literal resolution queries can erase the loader - * value. In particular, assignments, exported aliases, shorthand properties, - * optional calls, and passing the loader to user code cannot pass this test. */ -function staticCreateRequireUse7(expr: ts.Expression): boolean { +/** How a use of a createRequire loader lowers: + * - "static": a literal call or a literal resolution query, which erases + * the loader value. + * - "runtime": in JavaScript source, a call with a computed specifier, a + * require.resolve call with a computed request, or `resolve` read as a + * value. The runtime resolves the specifier like Node + * (module-resolution.ts, scr_require.c). + * - "computed": the same uses in TypeScript source. + * - "escaping": the loader itself as a value, writes, optional calls, + * spread arguments, `resolve.paths` with a computed request or as a + * value, and other members. */ +type CreateRequireUse7 = "static" | "runtime" | "computed" | "escaping"; + +function createRequireUse7(expr: ts.Expression): CreateRequireUse7 { + const use = createRequireUseShape7(expr); + return use === "runtime" && !isJsSourceFile(ts.getSourceFileOfNode(expr)) ? "computed" : use; +} + +function createRequireUseShape7(expr: ts.Expression): CreateRequireUse7 { let use: ts.Node = expr; while (ts.isParenthesizedExpression(use.parent)) use = use.parent; + let member = false; + let paths = false; if ( ts.isPropertyAccessExpression(use.parent) && use.parent.expression === use && @@ -2532,21 +2549,35 @@ function staticCreateRequireUse7(expr: ts.Expression): boolean { !use.parent.questionDotToken ) { use = use.parent; + member = true; if ( ts.isPropertyAccessExpression(use.parent) && use.parent.expression === use && use.parent.name.text === "paths" && !use.parent.questionDotToken - ) + ) { use = use.parent; + paths = true; + } } const call = use.parent; + if (ts.isCallExpression(call) && call.expression === use) { + if (call.questionDotToken) return "escaping"; + const args = call.arguments.nodes; + if (args.length === 1 && ts.isStringLiteralLike(args[0]!)) return "static"; + if (paths || args.some((arg) => ts.isSpreadElement(arg))) return "escaping"; + return member && (args.length === 0 || args.length > 2) ? "escaping" : "runtime"; + } + return member && !paths && !isAssignmentTarget7(use) ? "runtime" : "escaping"; +} + +function isAssignmentTarget7(node: ts.Node): boolean { + const parent = node.parent; return ( - ts.isCallExpression(call) && - call.expression === use && - !call.questionDotToken && - call.arguments.nodes.length === 1 && - ts.isStringLiteralLike(call.arguments.nodes[0]!) + ts.isBinaryExpression(parent) && + parent.left === node && + parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment ); } @@ -2582,9 +2613,11 @@ function stableCreateRequireBindingReason7( : checker.getSymbolAtLocation(ref); if (target && (target.flags & ts.SymbolFlags.Alias) !== 0) target = checker.getAliasedSymbol(target); - if (target === symbol && !staticCreateRequireUse7(ref)) { + if (target !== symbol) continue; + const use = createRequireUse7(ref); + if (use === "computed") return "its createRequire binding is reassigned, escapes, or uses a computed specifier"; - } + if (use === "escaping") return "its createRequire binding is reassigned or escapes"; } const preceding = stmt.declarationList.declarations.nodes.slice( 0, @@ -2647,10 +2680,12 @@ function npmStaticModuleImportReason7( checker.prefetchSymbolNodesExact(identifierOccurrences7(sf, specifier.name.text)); let reason: string | null = null; const checkUse = (expr: ts.Expression): void => { - if (!staticCreateRequireUse7(expr)) { + const use = createRequireUse7(expr); + if (use === "computed") reason = "its createRequire result escapes or is used with a computed specifier (static requires need literal specifiers)"; - } + else if (use === "escaping") + reason = "its createRequire result escapes (static requires need literal specifiers)"; }; ts.walkPreorder(sf, (node) => { if ( diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index c0b02e8db..63f6d2945 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -609,6 +609,7 @@ async function compileExecutableNative( ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), ...(features.dynBuffer ? { dynBuffer: true } : {}), ...(features.builtinValues ? { builtinValues: true } : {}), + ...(features.computedRequire ? { computedRequire: true } : {}), events: features.events, emitter: features.emitter, symbol: features.symbol, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 390c7fbc3..fbeb05dd3 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -679,6 +679,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "process.builtinId", "process.builtinModule", "process.builtinUnsupported", + "module.requireComputed", + "module.resolveComputed", "fs.callbackValue", "fs.callbackCall", "dyn.keyDelete", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e6ad0614a..a3efc12c8 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -378,6 +378,18 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "module.parent": { argTypes: [F64], result: F64 }, "module.loaded": { argTypes: [F64], result: BOOL }, "module.cacheGet": { argTypes: [STRING], result: F64 }, + /** createRequire's require(id) and require.resolve(request, options) + * with a specifier computed at run time (scr_require.c): the arguments, + * then the requiring file, the call's "file:line" and the builtin ids. + * Both resolve like Node at run time. requireComputed always throws + * (Node's argument errors or MODULE_NOT_FOUND), or ends the program with + * an SC2020 refusal when the specifier resolves; resolveComputed answers + * the resolved id or real path. */ + "module.requireComputed": { argTypes: [DYN, STRING, STRING, arrayOf(STRING)], result: DYN }, + "module.resolveComputed": { + argTypes: [DYN, DYN, STRING, STRING, arrayOf(STRING)], + result: STRING, + }, "module.cacheHas": { argTypes: [STRING], result: BOOL }, "module.cacheKeys": { argTypes: [], result: arrayOf(STRING) }, "timers.setTimeout": { argTypes: [{ kind: "func", params: [], ret: VOID }, F64], result: VOID }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 2ae19375f..54c5ee5be 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -946,6 +946,9 @@ const RUNTIME_FEATURE_RULES = { fns: ["buffer.prototypeMember"], expr: (node, _tables) => node.kind === "dynInvoke" && DYN_BUFFER_METHODS.has(node.method), }), + /** createRequire's require and require.resolve with specifiers computed + * at run time (scr_require.c). */ + computedRequire: rule({ fns: ["module.requireComputed", "module.resolveComputed"] }), /** Builtin functions read as values (scr_builtin_values.c). */ builtinValues: rule({ fns: [ @@ -1182,6 +1185,7 @@ function noRuntimeFeatures(): RuntimeFeatures { dynForAwait: false, dynBuffer: false, builtinValues: false, + computedRequire: false, symbol: false, tls: false, tlsCa: false, diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 33a4c19b9..c6063b2db 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -84,6 +84,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( eventsOnceSignal: false, dynBuffer: false, builtinValues: false, + computedRequire: false, warningReplace: false, stdioExtended: false, regex: true, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8210-require-computed-missing/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8210-require-computed-missing/main.ts.json new file mode 100644 index 000000000..659ee27aa --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8210-require-computed-missing/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8210-require-computed-missing/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8211-require-resolve-computed/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8211-require-resolve-computed/main.ts.json new file mode 100644 index 000000000..44f6cb75f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8211-require-resolve-computed/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8211-require-resolve-computed/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8212-native-helper-probe-no-display/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8212-native-helper-probe-no-display/main.ts.json new file mode 100644 index 000000000..2a83db362 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8212-native-helper-probe-no-display/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8212-native-helper-probe-no-display/main.ts" + ], + "diags": [] +} diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 0ab467236..0fc9fdf79 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -67,6 +67,8 @@ export const OPTIONAL_RUNTIME_SOURCES = [ ["scr_bytes_dyn.c", all("dynBuffer", "dynInvoke")], // Builtin functions read as values in JavaScript dependencies. ["scr_builtin_values.c", "builtinValues"], + // createRequire's computed require and require.resolve. + ["scr_require.c", "computedRequire"], ["scr_dc.c", "dc"], // The dynamic tier's jsval values (two-tier design I3): linked only into // programs whose IR has dynamic-tier operations. diff --git a/packages/runtime/src/scr_path.h b/packages/runtime/src/scr_path.h index 9600b5e95..de5e85491 100644 --- a/packages/runtime/src/scr_path.h +++ b/packages/runtime/src/scr_path.h @@ -1,5 +1,6 @@ -/* scriptc runtime — node:path (scr_path.c) and path globs (scr_glob.c). - * Included by scr_runtime.h, which says where new declarations go. */ +/* scriptc runtime — node:path (scr_path.c), path globs (scr_glob.c), and + * module resolution at run time (scr_require.c). Included by scr_runtime.h, + * which says where new declarations go. */ #ifndef SCR_PATH_H #define SCR_PATH_H @@ -89,4 +90,18 @@ void scr_fs_glob_callback(ScrDyn *args); ScrArr *scr_fsp_glob_strings(const ScrDyn *pattern, const ScrDyn *options); ScrDyn *scr_fsp_glob_error(void); +/* createRequire's require(id) and require.resolve(request, options) with a + * specifier computed at run time (scr_require.c). Both resolve with Node + * 24's CommonJS algorithm from `importer`, the requiring file; `builtins` + * lists the builtin ids (process.getBuiltinModule's table), and `site` is + * the call's "file:line" for the SC2020 refusal. All arguments are + * borrowed. scr_require_computed always leaves an exception pending and + * returns NULL: Node's argument errors, or MODULE_NOT_FOUND when nothing + * resolves; a specifier that resolves ends the program with the refusal. + * scr_require_resolve answers the resolved id or real path (+1), or NULL + * with Node's error pending. */ +ScrDyn *scr_require_computed(ScrDyn *id, ScrStr *importer, ScrStr *site, ScrArr *builtins); +ScrStr *scr_require_resolve(ScrDyn *request, ScrDyn *options, ScrStr *importer, ScrStr *site, + ScrArr *builtins); + #endif /* SCR_PATH_H */ diff --git a/packages/runtime/src/scr_require.c b/packages/runtime/src/scr_require.c new file mode 100644 index 000000000..645273bce --- /dev/null +++ b/packages/runtime/src/scr_require.c @@ -0,0 +1,895 @@ +/* createRequire's require(id) and require.resolve(request) with a specifier + * computed at run time (module-resolution.ts lowers both calls here). + * + * The compiled module graph is fixed at build time, so neither call loads a + * module. Both resolve the specifier with Node 24's CommonJS algorithm + * (lib/internal/modules/cjs/loader.js: Module._resolveFilename and + * Module._findPath, with package.json "main", "exports" and self-reference) + * against the file system at run time: + * + * - require.resolve returns what Node returns: the builtin's id, or the real + * path of the resolved file. It throws Node's MODULE_NOT_FOUND error when + * nothing resolves. + * - require throws the same MODULE_NOT_FOUND error when nothing resolves, so + * packages that probe for optional files take their absent branch as in + * Node. When the specifier resolves (a builtin, or a file such as a native + * addon), Node would load the module, which a compiled program cannot do: + * the program ends with an SC2020 refusal naming the module. The refusal is + * not an exception, so a `catch` clause cannot take a fallback that Node + * would not take. + * + * Resolution steps whose Node result this file does not reproduce (package + * "imports", invalid package configs or export targets, unexported subpaths, + * percent-encoded export targets, an export target that names no file, a + * "main" that names no file) end the program with the same SC2020 refusal, + * naming the step. */ +#include "scr_runtime.h" + +#include +#include +#include + +typedef struct { + ScrStr *request; /* borrowed */ + ScrStr *importer; /* borrowed: the requiring file */ + ScrArr *builtins; /* borrowed: Node 24's require-able builtin ids */ + /* Set when resolution reaches a step it does not reproduce; the caller + * refuses with this reason. Static text. */ + const char *refusal; +} ScrRequire; + +static ScrStr *req_cstr(const char *s) { return scr_str_new(s, strlen(s)); } + +static bool req_has_prefix(const ScrStr *s, const char *prefix) { + size_t n = strlen(prefix); + return s->len >= n && memcmp(s->data, prefix, n) == 0; +} + +static bool req_has_suffix(const ScrStr *s, const char *suffix) { + size_t n = strlen(suffix); + return s->len >= n && memcmp(s->data + s->len - n, suffix, n) == 0; +} + +static bool req_eq(const ScrStr *s, const char *text) { + size_t n = strlen(text); + return s->len == n && memcmp(s->data, text, n) == 0; +} + +static ScrStr *req_concat(const ScrStr *a, const char *b, size_t b_len) { + ScrJsonBuf out; + scr_jb_init(&out); + scr_jb_write(&out, a->data, a->len); + scr_jb_write(&out, b, b_len); + return scr_jb_finish(&out); +} + +static ScrStr *req_resolve2(ScrStr *a, ScrStr *b) { + ScrArr *parts = scr_arr_new(SCR_ELEM_STR, 2); + scr_arr_push_ref(parts, scr_str_retain(a)); + scr_arr_push_ref(parts, scr_str_retain(b)); +#ifdef _WIN32 + ScrStr *out = scr_path_win32_resolve(parts); +#else + ScrStr *out = scr_path_resolve(parts); +#endif + scr_arr_release(parts); + return out; +} + +static ScrStr *req_dirname(ScrStr *path) { +#ifdef _WIN32 + return scr_path_win32_dirname(path); +#else + return scr_path_dirname(path); +#endif +} + +static bool req_is_absolute(ScrStr *path) { +#ifdef _WIN32 + return scr_path_win32_is_absolute(path); +#else + return scr_path_is_absolute(path); +#endif +} + +/* Node's internalModuleStat: 0 for a file, 1 for a directory, -1 when the + * path does not exist or cannot be read. Never throws. */ +static int req_stat(ScrStr *path) { + ScrStats *stats = scr_fs_stat_probe(path, false, false); + if (stats == NULL) { + if (scr_exc_pending()) scr_exc_clear(); + return -1; + } + int kind = scr_stats_is_dir(stats) ? 1 : 0; + scr_stats_release(stats); + return kind; +} + +/* tryFile: the real path of `path` when it is a file (+1), else NULL. */ +static ScrStr *req_try_file(ScrStr *path) { + if (req_stat(path) != 0) return NULL; + ScrStr *real = scr_fs_realpath(path); + if (real != NULL) return real; + if (scr_exc_pending()) scr_exc_clear(); + return scr_str_retain(path); +} + +/* tryExtensions with Module._extensions' keys, in registration order. */ +static ScrStr *req_try_extensions(ScrStr *base) { + static const char *const extensions[] = { ".js", ".json", ".node" }; + for (size_t i = 0; i < sizeof extensions / sizeof extensions[0]; i++) { + ScrStr *candidate = req_concat(base, extensions[i], strlen(extensions[i])); + ScrStr *found = req_try_file(candidate); + scr_str_release(candidate); + if (found != NULL) return found; + } + return NULL; +} + +/* ── package.json ────────────────────────────────────────────────────── */ + +/* The package.json at `pjson` parsed (+1), or NULL when there is none. + * A file Node rejects as an invalid package config (unparsable JSON, a + * non-object document, a non-string "name" or "type") sets the refusal. */ +static ScrDyn *req_read_package(ScrRequire *r, ScrStr *pjson) { + if (req_stat(pjson) != 0) return NULL; + ScrStr *text = scr_fs_read_file(pjson); + if (text == NULL) { + if (scr_exc_pending()) scr_exc_clear(); + return NULL; + } + ScrDyn *pkg = scr_json_parse(text); + scr_str_release(text); + if (pkg == NULL) { + if (scr_exc_pending()) scr_exc_clear(); + r->refusal = "a package.json that is not valid JSON"; + return NULL; + } + const ScrDyn *name = pkg->kind == SCR_DYN_OBJ ? scr_dyn_obj_get(pkg, "name", 4) : NULL; + const ScrDyn *type = pkg->kind == SCR_DYN_OBJ ? scr_dyn_obj_get(pkg, "type", 4) : NULL; + if (pkg->kind != SCR_DYN_OBJ || (name != NULL && name->kind != SCR_DYN_STR) || + (type != NULL && type->kind != SCR_DYN_STR)) { + scr_dyn_release(pkg); + r->refusal = "an invalid package.json"; + return NULL; + } + return pkg; +} + +/* A string field of a parsed package.json (borrowed), or NULL. */ +static ScrStr *req_package_string(const ScrDyn *pkg, const char *key) { + const ScrDyn *value = scr_dyn_obj_get(pkg, key, strlen(key)); + return value != NULL && value->kind == SCR_DYN_STR ? value->v.str : NULL; +} + +/* "exports" or "imports" as Node keeps them: a string, an object or an + * array (borrowed); NULL for every other value. */ +static const ScrDyn *req_package_map(const ScrDyn *pkg, const char *key) { + const ScrDyn *value = scr_dyn_obj_get(pkg, key, strlen(key)); + if (value == NULL) return NULL; + return value->kind == SCR_DYN_STR || value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR ? value : NULL; +} + +/* tryPackage: a directory's "main" entry, then its index files. */ +static ScrStr *req_try_package(ScrRequire *r, ScrStr *dir) { + ScrStr *pjson_name = req_cstr("package.json"); + ScrStr *pjson = req_resolve2(dir, pjson_name); + scr_str_release(pjson_name); + ScrDyn *pkg = req_read_package(r, pjson); + scr_str_release(pjson); + if (r->refusal != NULL) return NULL; + ScrStr *main = pkg != NULL ? req_package_string(pkg, "main") : NULL; + ScrStr *index_name = req_cstr("index"); + ScrStr *index = req_resolve2(dir, index_name); + ScrStr *found = NULL; + if (main == NULL) { + found = req_try_extensions(index); + } else { + ScrStr *filename = req_resolve2(dir, main); + found = req_try_file(filename); + if (found == NULL) found = req_try_extensions(filename); + if (found == NULL) { + ScrStr *main_index = req_resolve2(filename, index_name); + found = req_try_extensions(main_index); + scr_str_release(main_index); + } + scr_str_release(filename); + if (found == NULL) { + /* Node falls back to the index with a DEP0128 warning, or throws an + * error that names the "main" entry. */ + r->refusal = "a package.json \"main\" entry that names no file"; + } + } + scr_str_release(index); + scr_str_release(index_name); + if (pkg != NULL) scr_dyn_release(pkg); + return found; +} + +/* ── package exports (lib/internal/modules/esm/resolve.js) ───────────── */ + +/* A target or subpath segment Node rejects or warns about: "", ".", "..", + * "node_modules" in any case, and their percent-encoded spellings. The + * caller refuses '%' separately, so the plain spellings suffice. */ +static bool req_invalid_segment(const char *s, size_t len) { + size_t start = 0; + for (size_t i = 0; i <= len; i++) { + if (i < len && s[i] != '/' && s[i] != '\\') continue; + size_t n = i - start; + const char *seg = s + start; + if (n == 0 && i < len) return true; + if ((n == 1 && seg[0] == '.') || (n == 2 && seg[0] == '.' && seg[1] == '.')) return true; + if (n == 12) { + static const char nm[] = "node_modules"; + bool same = true; + for (size_t k = 0; k < 12 && same; k++) { + char c = seg[k]; + if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); + same = c == nm[k]; + } + if (same) return true; + } + start = i + 1; + } + return false; +} + +/* Characters whose URL parsing changes the path Node resolves: percent + * escapes, backslashes, query and fragment delimiters, and the control + * characters the URL parser strips. */ +static bool req_url_sensitive(const char *s, size_t len) { + for (size_t i = 0; i < len; i++) { + unsigned char c = (unsigned char)s[i]; + if (c == '%' || c == '\\' || c == '?' || c == '#' || c < 0x20 || c == 0x7f) return true; + } + return false; +} + +typedef enum { REQ_TARGET_PATH, REQ_TARGET_NULL, REQ_TARGET_UNDEFINED, REQ_TARGET_INVALID } ScrRequireTarget; + +static bool req_condition(const ScrStr *key) { + return req_eq(key, "default") || req_eq(key, "require") || req_eq(key, "node") || + req_eq(key, "node-addons") || req_eq(key, "module-sync"); +} + +static bool req_array_index(const ScrStr *key) { + if (key->len == 0 || key->len > 10) return false; + if (key->len > 1 && key->data[0] == '0') return false; + double n = 0; + for (size_t i = 0; i < key->len; i++) { + if (key->data[i] < '0' || key->data[i] > '9') return false; + n = n * 10 + (key->data[i] - '0'); + } + return n < 4294967295.0; +} + +/* resolvePackageTarget for an "exports" target. `pkg_dir` is the package + * directory, `subpath` the part the target receives. A path result is in + * `*out` (+1, not yet checked against the file system). */ +static ScrRequireTarget req_resolve_target(ScrRequire *r, ScrStr *pkg_dir, const ScrDyn *target, + const char *subpath, size_t subpath_len, bool pattern, + ScrStr **out) { + if (target->kind == SCR_DYN_STR) { + ScrStr *t = target->v.str; + if (subpath_len != 0 && !pattern && !req_has_suffix(t, "/")) return REQ_TARGET_INVALID; + if (!req_has_prefix(t, "./")) return REQ_TARGET_INVALID; + if (req_url_sensitive(t->data, t->len) || req_url_sensitive(subpath, subpath_len) || + req_invalid_segment(t->data + 2, t->len - 2) || + (subpath_len != 0 && req_invalid_segment(subpath, subpath_len))) { + r->refusal = "a package \"exports\" target that Node rejects, warns about or decodes"; + return REQ_TARGET_INVALID; + } + if (pattern && memchr(pkg_dir->data, '*', pkg_dir->len) != NULL) { + /* Node substitutes the pattern in the whole URL, directory included. */ + r->refusal = "a package \"exports\" pattern in a directory whose path contains '*'"; + return REQ_TARGET_INVALID; + } + ScrJsonBuf path; + scr_jb_init(&path); + scr_jb_write(&path, pkg_dir->data, pkg_dir->len); + const char *rest = t->data + 1; /* keeps the '/' after "." */ + size_t rest_len = t->len - 1; + if (pattern) { + for (size_t i = 0; i < rest_len; i++) { + if (rest[i] == '*') + scr_jb_write(&path, subpath, subpath_len); + else + scr_jb_putc(&path, rest[i]); + } + } else { + scr_jb_write(&path, rest, rest_len); + scr_jb_write(&path, subpath, subpath_len); + } + ScrStr *joined = scr_jb_finish(&path); +#ifdef _WIN32 + *out = scr_path_win32_normalize(joined); +#else + *out = scr_path_normalize(joined); +#endif + scr_str_release(joined); + return REQ_TARGET_PATH; + } + if (target->kind == SCR_DYN_ARR) { + double n = scr_dyn_arr_len(target); + if (n == 0) return REQ_TARGET_NULL; + ScrRequireTarget last = REQ_TARGET_UNDEFINED; + for (double i = 0; i < n; i++) { + ScrDyn *item = scr_dyn_arr_at(target, i); + ScrRequireTarget result = req_resolve_target(r, pkg_dir, item, subpath, subpath_len, pattern, out); + scr_dyn_release(item); + if (r->refusal != NULL) return REQ_TARGET_INVALID; + if (result == REQ_TARGET_PATH) return result; + if (result == REQ_TARGET_INVALID) last = REQ_TARGET_INVALID; + else if (result == REQ_TARGET_NULL) last = REQ_TARGET_NULL; + } + return last == REQ_TARGET_UNDEFINED ? REQ_TARGET_UNDEFINED : last; + } + if (target->kind == SCR_DYN_OBJ) { + ScrDyn *keys = scr_dyn_obj_own_keys(target); + double n = scr_dyn_arr_len(keys); + ScrRequireTarget result = REQ_TARGET_UNDEFINED; + for (double i = 0; i < n; i++) { + ScrDyn *key = scr_dyn_arr_at(keys, i); + bool index = req_array_index(key->v.str); + scr_dyn_release(key); + if (index) { + r->refusal = "numeric keys in a package's \"exports\""; + scr_dyn_release(keys); + return REQ_TARGET_INVALID; + } + } + for (double i = 0; i < n && result == REQ_TARGET_UNDEFINED; i++) { + ScrDyn *key = scr_dyn_arr_at(keys, i); + if (req_condition(key->v.str)) { + const ScrDyn *value = scr_dyn_obj_get(target, key->v.str->data, key->v.str->len); + result = req_resolve_target(r, pkg_dir, value, subpath, subpath_len, pattern, out); + if (r->refusal != NULL) result = REQ_TARGET_INVALID; + } + scr_dyn_release(key); + } + scr_dyn_release(keys); + return result; + } + if (target->kind == SCR_DYN_NULL) return REQ_TARGET_NULL; + return REQ_TARGET_INVALID; +} + +/* patternKeyCompare(a, b) === 1: `b` is the better pattern match. */ +static bool req_pattern_better(const ScrStr *a, const ScrStr *b) { + const char *as = a ? memchr(a->data, '*', a->len) : NULL; + const char *bs = memchr(b->data, '*', b->len); + size_t a_len = a ? a->len : 0; + size_t base_a = as ? (size_t)(as - a->data) + 1 : a_len; + size_t base_b = bs ? (size_t)(bs - b->data) + 1 : b->len; + if (base_a > base_b) return false; + if (base_b > base_a) return true; + if (as == NULL) return true; + if (bs == NULL) return false; + return b->len > a_len; +} + +/* packageExportsResolve for `subpath` ("." or "./..."), then + * finalizeEsmResolution's file check. +1 real path, or NULL with the + * refusal set. */ +static ScrStr *req_exports_resolve(ScrRequire *r, ScrStr *pkg_dir, const ScrDyn *exports, ScrStr *subpath) { + bool sugar; + if (exports->kind != SCR_DYN_OBJ) { + sugar = true; + } else { + sugar = false; + ScrDyn *keys = scr_dyn_obj_own_keys(exports); + double n = scr_dyn_arr_len(keys); + for (double i = 0; i < n; i++) { + ScrDyn *key = scr_dyn_arr_at(keys, i); + bool conditional = key->v.str->len == 0 || key->v.str->data[0] != '.'; + scr_dyn_release(key); + if (i == 0) { + sugar = conditional; + } else if (sugar != conditional) { + scr_dyn_release(keys); + r->refusal = "a package \"exports\" object that mixes subpaths and conditions"; + return NULL; + } + } + scr_dyn_release(keys); + } + if (exports->kind == SCR_DYN_STR && exports->v.str->len > 0 && + (exports->v.str->data[0] == '[' || exports->v.str->data[0] == '{')) { + r->refusal = "a package \"exports\" string that Node parses as JSON"; + return NULL; + } + ScrStr *path = NULL; + ScrRequireTarget result = REQ_TARGET_UNDEFINED; + bool has_star = memchr(subpath->data, '*', subpath->len) != NULL; + if (sugar) { + if (req_eq(subpath, ".")) + result = req_resolve_target(r, pkg_dir, exports, "", 0, false, &path); + } else { + const ScrDyn *exact = has_star || req_has_suffix(subpath, "/") + ? NULL + : scr_dyn_obj_get(exports, subpath->data, subpath->len); + if (exact != NULL) { + result = req_resolve_target(r, pkg_dir, exact, "", 0, false, &path); + } else { + ScrStr *best = NULL; + size_t best_start = 0, best_end = 0; + ScrDyn *keys = scr_dyn_obj_own_keys(exports); + double n = scr_dyn_arr_len(keys); + for (double i = 0; i < n; i++) { + ScrDyn *key_value = scr_dyn_arr_at(keys, i); + ScrStr *key = key_value->v.str; + const char *star = memchr(key->data, '*', key->len); + size_t at = star ? (size_t)(star - key->data) : 0; + if (star != NULL && subpath->len >= at && memcmp(subpath->data, key->data, at) == 0) { + size_t trailer = key->len - at - 1; + bool single = memchr(star + 1, '*', trailer) == NULL; + if (subpath->len >= key->len && + memcmp(subpath->data + subpath->len - trailer, star + 1, trailer) == 0 && + req_pattern_better(best, key) && single) { + if (best) scr_str_release(best); + best = scr_str_retain(key); + best_start = at; + best_end = subpath->len - trailer; + } + } + scr_dyn_release(key_value); + } + scr_dyn_release(keys); + if (best != NULL) { + if (req_has_suffix(subpath, "/")) { + r->refusal = "a package \"exports\" pattern matched by a subpath ending in '/'"; + } else { + const ScrDyn *target = scr_dyn_obj_get(exports, best->data, best->len); + result = req_resolve_target(r, pkg_dir, target, subpath->data + best_start, + best_end - best_start, true, &path); + } + scr_str_release(best); + } + } + } + if (r->refusal != NULL) { + if (path) scr_str_release(path); + return NULL; + } + if (result != REQ_TARGET_PATH) { + r->refusal = result == REQ_TARGET_INVALID ? "an invalid package \"exports\" target" + : "a subpath that the package's \"exports\" do not export"; + return NULL; + } + ScrStr *found = req_try_file(path); + scr_str_release(path); + if (found == NULL) r->refusal = "a package \"exports\" target that names no file"; + return found; +} + +/* ── Module._findPath ────────────────────────────────────────────────── */ + +/* EXPORTS_PATTERN: /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/ — the + * package name's length in `request`, or 0 when it does not match. */ +static size_t req_package_name_len(const ScrStr *request) { + const char *s = request->data; + size_t len = request->len, i = 0; + if (len > 0 && s[0] == '@') { + i = 1; + size_t start = i; + while (i < len && s[i] != '/' && s[i] != '\\' && s[i] != '%') i++; + if (i == start || i >= len || s[i] != '/') i = 0; + else i++; + } + if (i >= len || s[i] == '.' || s[i] == '/' || s[i] == '\\' || s[i] == '%') return 0; + while (i < len && s[i] != '/' && s[i] != '\\' && s[i] != '%') i++; + if (i < len && s[i] != '/') return 0; + return i; +} + +/* resolveExports: a package under `nm_dir` whose package.json has + * "exports". +1 real path; NULL when the package has no "exports" (or the + * refusal is set). */ +static ScrStr *req_resolve_exports(ScrRequire *r, ScrStr *nm_dir) { + size_t name_len = req_package_name_len(r->request); + if (name_len == 0) return NULL; + ScrStr *name = scr_str_new(r->request->data, name_len); + ScrStr *pkg_dir = req_resolve2(nm_dir, name); + scr_str_release(name); + ScrStr *pjson = req_concat(pkg_dir, "/package.json", 13); + ScrDyn *pkg = req_read_package(r, pjson); + scr_str_release(pjson); + ScrStr *found = NULL; + const ScrDyn *exports = pkg != NULL ? req_package_map(pkg, "exports") : NULL; + if (exports != NULL) { + ScrJsonBuf sub; + scr_jb_init(&sub); + scr_jb_putc(&sub, '.'); + scr_jb_write(&sub, r->request->data + name_len, r->request->len - name_len); + ScrStr *subpath = scr_jb_finish(&sub); + found = req_exports_resolve(r, pkg_dir, exports, subpath); + scr_str_release(subpath); + } + if (pkg != NULL) scr_dyn_release(pkg); + scr_str_release(pkg_dir); + return found; +} + +/* isRelative in Module._findPath: ".", "..", and "./" or "../" prefixes. */ +static bool req_is_relative(const ScrStr *request) { + if (req_eq(request, ".") || req_eq(request, "..")) return true; + if (req_has_prefix(request, "./") || req_has_prefix(request, "../")) return true; +#ifdef _WIN32 + if (req_has_prefix(request, ".\\") || req_has_prefix(request, "..\\")) return true; +#endif + return false; +} + +static bool req_trailing_slash(const ScrStr *request) { + const char *s = request->data; + size_t n = request->len; + if (n == 0) return false; + if (s[n - 1] == '/') return true; + if (s[n - 1] != '.') return false; + if (n == 1 || s[n - 2] == '/') return true; + return s[n - 2] == '.' && (n == 2 || s[n - 3] == '/'); +} + +/* Module._findPath over `paths` (borrowed strings). +1 real path or NULL. */ +static ScrStr *req_find_path(ScrRequire *r, ScrStr *const *paths, size_t count) { + ScrStr *request = r->request; + bool absolute = req_is_absolute(request); + ScrStr *empty = NULL; + if (absolute) { + empty = scr_str_new("", 0); + paths = ∅ + count = 1; + } + bool trailing = req_trailing_slash(request); + bool inside = true; + if (req_is_relative(request)) { +#ifdef _WIN32 + ScrStr *normalized = scr_path_win32_normalize(request); +#else + ScrStr *normalized = scr_path_normalize(request); +#endif + inside = !req_has_prefix(normalized, ".."); + scr_str_release(normalized); + } + ScrStr *found = NULL; + for (size_t i = 0; i < count && found == NULL && r->refusal == NULL; i++) { + ScrStr *dir = paths[i]; + if (inside && dir->len > 0 && req_stat(dir) < 1) continue; + if (!absolute) { + found = req_resolve_exports(r, dir); + if (found != NULL || r->refusal != NULL) break; + } + ScrStr *base = req_resolve2(dir, request); + int kind = req_stat(base); + if (!trailing) { + if (kind == 0) found = req_try_file(base); + if (found == NULL) found = req_try_extensions(base); + } + if (found == NULL && kind == 1) found = req_try_package(r, base); + scr_str_release(base); + } + if (empty) scr_str_release(empty); + return found; +} + +/* Module._nodeModulePaths(from) followed by Module.globalPaths' entries + * that come from the environment (NODE_PATH, then the home directory's + * .node_modules and .node_libraries). Node also searches the lib/node + * directory of its own installation prefix, which a compiled program does + * not have. Fills `out` (+1 each) and answers the count. */ +static size_t req_lookup_paths(ScrStr *from, ScrStr ***out) { + size_t cap = 16, n = 0; + ScrStr **paths = malloc(cap * sizeof *paths); + if (!paths) scr_trap("scriptc: out of memory\n"); +#define REQ_PUSH(value) \ + do { \ + if (n == cap) { \ + cap *= 2; \ + ScrStr **grown = realloc(paths, cap * sizeof *paths); \ + if (!grown) scr_trap("scriptc: out of memory\n"); \ + paths = grown; \ + } \ + paths[n++] = (value); \ + } while (0) + ScrStr *dot = scr_str_new(".", 1); + ScrStr *abs = req_resolve2(from, dot); + scr_str_release(dot); + static const char nm[] = "node_modules"; + const char *s = abs->data; +#ifdef _WIN32 + if (abs->len >= 2 && s[abs->len - 1] == '\\' && s[abs->len - 2] == ':') { + REQ_PUSH(req_concat(abs, "node_modules", 12)); + } else { + for (long i = (long)abs->len - 1, p = 0, last = (long)abs->len; i >= 0; --i) { + char c = s[i]; + if (c == '\\' || c == '/' || c == ':') { + if (p != 12) { + ScrStr *prefix = scr_str_new(s, (size_t)last); + REQ_PUSH(req_concat(prefix, "\\node_modules", 13)); + scr_str_release(prefix); + } + last = i; + p = 0; + } else if (p != -1) { + p = (p < 12 && nm[11 - p] == c) ? p + 1 : -1; + } + } + } +#else + if (abs->len == 1 && s[0] == '/') { + REQ_PUSH(scr_str_new("/node_modules", 13)); + } else { + for (long i = (long)abs->len - 1, p = 0, last = (long)abs->len; i >= 0; --i) { + char c = s[i]; + if (c == '/') { + if (p != 12) { + ScrStr *prefix = scr_str_new(s, (size_t)last); + REQ_PUSH(req_concat(prefix, "/node_modules", 13)); + scr_str_release(prefix); + } + last = i; + p = 0; + } else if (p != -1) { + p = (p < 12 && nm[11 - p] == c) ? p + 1 : -1; + } + } + REQ_PUSH(scr_str_new("/node_modules", 13)); + } +#endif + scr_str_release(abs); +#ifdef _WIN32 + const char *node_path = scr_getenv("NODE_PATH"); + const char *home = scr_getenv("USERPROFILE"); + const char delimiter = ';'; +#else + const char *node_path = scr_getenv("NODE_PATH"); + const char *home = scr_getenv("HOME"); + const char delimiter = ':'; +#endif + if (node_path != NULL) { + for (const char *p = node_path; *p;) { + const char *end = strchr(p, delimiter); + size_t len = end ? (size_t)(end - p) : strlen(p); + if (len > 0) REQ_PUSH(scr_str_new(p, len)); + p += len; + if (*p) p++; + } + } + if (home != NULL && *home) { + ScrStr *home_dir = req_cstr(home); + static const char *const globals[] = { ".node_modules", ".node_libraries" }; + for (size_t i = 0; i < 2; i++) { + ScrStr *name = req_cstr(globals[i]); + REQ_PUSH(req_resolve2(home_dir, name)); + scr_str_release(name); + } + scr_str_release(home_dir); + } +#undef REQ_PUSH + *out = paths; + return n; +} + +/* trySelf: the nearest package.json above the importer (Node stops below + * the root and at a node_modules directory) resolves the request through + * its own "exports" when its "name" prefixes it. */ +static ScrStr *req_try_self(ScrRequire *r) { + ScrStr *dir = req_dirname(r->importer); + ScrStr *found = NULL; + for (;;) { + ScrStr *parent = req_dirname(dir); + bool root = scr_str_eq(parent, dir); + scr_str_release(parent); + if (root) break; + ScrStr *dot = scr_str_new(".", 1); + ScrStr *abs = req_resolve2(dir, dot); + scr_str_release(dot); + bool boundary = req_has_suffix(abs, "/node_modules") +#ifdef _WIN32 + || req_has_suffix(abs, "\\node_modules") +#endif + ; + scr_str_release(abs); + if (boundary) break; + ScrStr *pjson_name = req_cstr("package.json"); + ScrStr *pjson = req_resolve2(dir, pjson_name); + scr_str_release(pjson_name); + ScrDyn *pkg = req_read_package(r, pjson); + scr_str_release(pjson); + if (r->refusal != NULL) break; + if (pkg != NULL) { + ScrStr *name = req_package_string(pkg, "name"); + const ScrDyn *exports = req_package_map(pkg, "exports"); + if (req_has_prefix(r->request, "#") && req_package_map(pkg, "imports") != NULL) { + r->refusal = "a package \"imports\" specifier"; + } else if (name != NULL && exports != NULL) { + bool whole = scr_str_eq(name, r->request); + bool prefixed = r->request->len > name->len && + memcmp(r->request->data, name->data, name->len) == 0 && + r->request->data[name->len] == '/'; + if (whole || prefixed) { + ScrJsonBuf sub; + scr_jb_init(&sub); + scr_jb_putc(&sub, '.'); + scr_jb_write(&sub, r->request->data + name->len, r->request->len - name->len); + ScrStr *subpath = scr_jb_finish(&sub); + found = req_exports_resolve(r, dir, exports, subpath); + scr_str_release(subpath); + } + } + scr_dyn_release(pkg); + break; + } + ScrStr *next = req_dirname(dir); + scr_str_release(dir); + dir = next; + } + scr_str_release(dir); + return found; +} + +/* The builtin id `request` names (+1), or NULL. */ +static ScrStr *req_builtin(ScrRequire *r) { + ScrDyn *id = scr_dyn_new_str(r->request); + ScrStr *bare = scr_process_builtin_id(id, r->builtins); + scr_dyn_release(id); + if (bare == NULL || bare->len == 0) { + if (bare) scr_str_release(bare); + return NULL; + } + scr_str_release(bare); + return scr_str_retain(r->request); +} + +/* Module._resolveFilename: +1 resolved id or path; NULL when nothing + * resolves (or the refusal is set). */ +static ScrStr *req_resolve_filename(ScrRequire *r) { + ScrStr *builtin = req_builtin(r); + if (builtin != NULL) return builtin; + ScrStr *found = req_try_self(r); + if (found != NULL || r->refusal != NULL) return found; + bool lookup = r->request->len == 0 || r->request->data[0] != '.' || + (r->request->len > 1 && r->request->data[1] != '.' && r->request->data[1] != '/' +#ifdef _WIN32 + && r->request->data[1] != '\\' +#endif + ); + if (lookup) { + ScrStr *dir = req_dirname(r->importer); + ScrStr **paths = NULL; + size_t count = req_lookup_paths(dir, &paths); + scr_str_release(dir); + found = req_find_path(r, paths, count); + for (size_t i = 0; i < count; i++) scr_str_release(paths[i]); + free(paths); + } else { + ScrStr *dir = req_dirname(r->importer); + found = req_find_path(r, &dir, 1); + scr_str_release(dir); + } + return found; +} + +/* ── entry points ────────────────────────────────────────────────────── */ + +/* Ends the program with the SC2020 refusal: it names the native addon, the + * builtin or file the specifier resolves to, or the resolution step, then + * the program exits with status 1 as after an uncaught error, running the + * 'exit' listeners. */ +static void req_refuse(ScrRequire *r, const char *call, const ScrStr *found, bool builtin, ScrStr *site) { + ScrJsonBuf text; + scr_jb_init(&text); + if (r->refusal != NULL) { + scr_jb_puts(&text, call); + scr_jb_puts(&text, " of '"); + scr_jb_write(&text, r->request->data, r->request->len); + scr_jb_puts(&text, "' reaches "); + scr_jb_puts(&text, r->refusal); + scr_jb_puts(&text, ", which scriptc does not resolve at run time"); + } else if (builtin) { + scr_jb_puts(&text, call); + scr_jb_puts(&text, " of the builtin '"); + scr_jb_write(&text, found->data, found->len); + scr_jb_puts(&text, "' with a computed specifier is not supported; require it with a string literal"); + } else if (req_has_suffix(found, ".node")) { + scr_jb_puts(&text, "native addon '"); + scr_jb_write(&text, found->data, found->len); + scr_jb_puts(&text, "' requires Node's addon runtime, which is unavailable in scriptc"); + } else { + scr_jb_puts(&text, call); + scr_jb_puts(&text, " of '"); + scr_jb_write(&text, r->request->data, r->request->len); + scr_jb_puts(&text, "' resolves to '"); + scr_jb_write(&text, found->data, found->len); + scr_jb_puts(&text, "', which is not part of the compiled program: the module graph is fixed at build time"); + } + scr_jb_puts(&text, " [SC2020 at "); + scr_jb_write(&text, site->data, site->len); + scr_jb_puts(&text, "]\n"); + ScrStr *message = scr_jb_finish(&text); + fflush(stdout); + fputs("scriptc: ", stderr); + fwrite(message->data, 1, message->len, stderr); + fflush(stderr); + scr_str_release(message); + scr_process_exit(1); +} + +static void req_throw(const char *code, const char *prefix, ScrRequire *r, bool stack) { + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, prefix); + scr_jb_write(&text, r->request->data, r->request->len); + if (stack) { + scr_jb_puts(&text, "'\nRequire stack:\n- "); + scr_jb_write(&text, r->importer->data, r->importer->len); + } + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, code); + scr_str_release(message); +} + +ScrDyn *scr_require_computed(ScrDyn *id, ScrStr *importer, ScrStr *site, ScrArr *builtins) { + if (id->kind != SCR_DYN_STR) { + scr_dyn_arg_type_fail("id", "of type string", id); + return NULL; + } + if (id->v.str->len == 0) { + static const char message[] = "The argument 'id' must be a non-empty string. Received ''"; + scr_throw_error_msg_code(SCR_ERR_TYPE, message, sizeof message - 1, "ERR_INVALID_ARG_VALUE"); + return NULL; + } + ScrRequire r = { id->v.str, importer, builtins, NULL }; + ScrStr *builtin = req_builtin(&r); + if (builtin == NULL && req_has_prefix(r.request, "node:")) { + req_throw("ERR_UNKNOWN_BUILTIN_MODULE", "No such built-in module: ", &r, false); + return NULL; + } + ScrStr *found = builtin != NULL ? builtin : req_resolve_filename(&r); + if (found == NULL && r.refusal == NULL) { + req_throw("MODULE_NOT_FOUND", "Cannot find module '", &r, true); + return NULL; + } + req_refuse(&r, "createRequire's require", found, builtin != NULL, site); + if (found) scr_str_release(found); + return NULL; +} + +/* require.resolve's options: Node reads `paths` and `conditions` from an + * object; other values select the default lookup. */ +static const char *req_options_refusal(const ScrDyn *options) { + switch (options->kind) { + case SCR_DYN_UNDEF: + case SCR_DYN_NULL: + case SCR_DYN_NUM: + case SCR_DYN_BOOL: + case SCR_DYN_STR: + return NULL; + case SCR_DYN_OBJ: { + const ScrDyn *paths = scr_dyn_obj_get(options, "paths", 5); + const ScrDyn *conditions = scr_dyn_obj_get(options, "conditions", 10); + if (paths != NULL && paths->kind != SCR_DYN_UNDEF) return "require.resolve's paths option"; + if (conditions != NULL && conditions->kind != SCR_DYN_UNDEF) return "require.resolve's conditions option"; + return NULL; + } + default: + return "require.resolve options that are not a plain object"; + } +} + +ScrStr *scr_require_resolve(ScrDyn *request, ScrDyn *options, ScrStr *importer, ScrStr *site, + ScrArr *builtins) { + if (request->kind != SCR_DYN_STR) { + scr_dyn_arg_type_fail("request", "of type string", request); + return NULL; + } + ScrRequire r = { request->v.str, importer, builtins, req_options_refusal(options) }; + ScrStr *found = r.refusal == NULL ? req_resolve_filename(&r) : NULL; + if (r.refusal != NULL) { + if (found) scr_str_release(found); + req_refuse(&r, "createRequire's require.resolve", NULL, false, site); + return NULL; + } + if (found == NULL) req_throw("MODULE_NOT_FOUND", "Cannot find module '", &r, true); + return found; +} diff --git a/tests/corpus/8210-require-computed-missing/main.ts b/tests/corpus/8210-require-computed-missing/main.ts new file mode 100644 index 000000000..685222157 --- /dev/null +++ b/tests/corpus/8210-require-computed-missing/main.ts @@ -0,0 +1,7 @@ +// A package that probes for optional files through createRequire with +// computed specifiers. A specifier that names no file throws Node's error, +// so the package takes its absent branch as in Node. +import { probeAddon, requireErrors } from "optional-probe"; + +console.log(probeAddon("clipboard")); +for (const line of requireErrors()) console.log(line); diff --git a/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.d.ts b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.d.ts new file mode 100644 index 000000000..4eb024b0c --- /dev/null +++ b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.d.ts @@ -0,0 +1,2 @@ +export declare function probeAddon(name: string): string; +export declare function requireErrors(): string[]; diff --git a/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.js b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.js new file mode 100644 index 000000000..c824302ba --- /dev/null +++ b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/index.js @@ -0,0 +1,46 @@ +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const importer = fileURLToPath(import.meta.url); +const load = createRequire(import.meta.url); +const here = dirname(importer); + +// Looks for a prebuilt addon in each packaging location. +export function probeAddon(name) { + const codes = []; + const dirs = [join(here, "prebuilds", `${process.platform}-${process.arch}`), join(here, "native"), here]; + for (const dir of dirs) { + try { + const addon = load(join(dir, `${name}.node`)); + return `loaded ${typeof addon}`; + } catch (error) { + codes.push(error.code); + } + } + return `absent: ${codes.join(", ")}`; +} + +function describe(spec) { + try { + load(spec); + return "loaded"; + } catch (error) { + return `${error.name} ${error.code}: ${error.message.replaceAll(importer, "")}`; + } +} + +export function requireErrors() { + const specs = [ + "./missing.js", + "./missing", + "../missing-directory/", + "scriptc-corpus-missing-package", + "@scriptc-corpus/missing/sub", + "node:not-a-builtin", + "", + 42, + undefined, + ]; + return specs.map((spec) => `${typeof spec === "string" ? JSON.stringify(spec) : String(spec)} -> ${describe(spec)}`); +} diff --git a/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/package.json b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/package.json new file mode 100644 index 000000000..50ad53129 --- /dev/null +++ b/tests/corpus/8210-require-computed-missing/node_modules/optional-probe/package.json @@ -0,0 +1 @@ +{ "name": "optional-probe", "version": "1.0.0", "type": "module", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/8211-require-resolve-computed/main.ts b/tests/corpus/8211-require-resolve-computed/main.ts new file mode 100644 index 000000000..d8e441d2f --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/main.ts @@ -0,0 +1,7 @@ +// require.resolve with requests computed at run time, called directly and +// read as a value, resolves like Node: builtins, relative files with +// Node's extension search, package "main" and "exports" entries, a +// package's own name, and MODULE_NOT_FOUND for requests that name nothing. +import { resolveAll } from "resolve-probe"; + +for (const line of resolveAll()) console.log(line); diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/node.js b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/node.js new file mode 100644 index 000000000..c414b6b26 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/node.js @@ -0,0 +1 @@ +module.exports = "node"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/other.js b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/other.js new file mode 100644 index 000000000..7f4090ad9 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/lib/other.js @@ -0,0 +1 @@ +module.exports = "other"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/package.json b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/package.json new file mode 100644 index 000000000..25326e6e5 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/@scope/scoped/package.json @@ -0,0 +1 @@ +{ "name": "@scope/scoped", "version": "1.0.0", "exports": { "node": "./lib/node.js", "default": "./lib/other.js" } } diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/plain-index/index.js b/tests/corpus/8211-require-resolve-computed/node_modules/plain-index/index.js new file mode 100644 index 000000000..0d24e266f --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/plain-index/index.js @@ -0,0 +1 @@ +module.exports = "index"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/data.json b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/data.json new file mode 100644 index 000000000..6d94e13bd --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/data.json @@ -0,0 +1 @@ +{ "kind": "data" } diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.d.ts b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.d.ts new file mode 100644 index 000000000..1b8cc8a0e --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.d.ts @@ -0,0 +1 @@ +export declare function resolveAll(): string[]; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js new file mode 100644 index 000000000..870a14401 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js @@ -0,0 +1,50 @@ +import { createRequire } from "node:module"; +import { dirname, relative } from "node:path"; +import { fileURLToPath } from "node:url"; + +const importer = fileURLToPath(import.meta.url); +const root = dirname(dirname(dirname(importer))); +const load = createRequire(import.meta.url); + +function show(resolved) { + return resolved.startsWith("/") || /^[A-Za-z]:\\/.test(resolved) ? relative(root, resolved).replaceAll("\\", "/") : resolved; +} + +function describe(resolve, request) { + try { + return show(resolve(request)); + } catch (error) { + return `${error.name} ${error.code}: ${error.message.replaceAll(importer, "")}`; + } +} + +const requests = [ + "fs", + "node:fs", + "node:test", + "./data.json", + "./data", + "./index", + "resolve-probe", + "resolve-probe/data", + "with-exports", + "with-exports/feature", + "with-exports/lib/helper", + "with-main", + "plain-index", + "@scope/scoped", + "./missing", + "scriptc-corpus-missing-package", + "", + 42, +]; + +export function resolveAll(options = {}) { + const resolve = options.resolve ?? load.resolve; + const label = (request) => (typeof request === "string" ? JSON.stringify(request) : String(request)); + return [ + ...requests.map((request) => `call ${label(request)} -> ${describe((r) => load.resolve(r), request)}`), + ...requests.slice(0, 4).map((request) => `value ${label(request)} -> ${describe(resolve, request)}`), + `options ${describe((r) => load.resolve(r, {}), "with-main")}`, + ]; +} diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/package.json b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/package.json new file mode 100644 index 000000000..d0935f646 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/package.json @@ -0,0 +1 @@ +{ "name": "resolve-probe", "version": "1.0.0", "type": "module", "exports": { ".": { "types": "./index.d.ts", "default": "./index.js" }, "./data": "./data.json" } } diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/cjs.cjs b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/cjs.cjs new file mode 100644 index 000000000..2cd122bdc --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/cjs.cjs @@ -0,0 +1 @@ +module.exports = "cjs"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/esm.mjs b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/esm.mjs new file mode 100644 index 000000000..06c676e54 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/esm.mjs @@ -0,0 +1 @@ +export default "esm"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/feature.js b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/feature.js new file mode 100644 index 000000000..33535fc6f --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/feature.js @@ -0,0 +1 @@ +module.exports = "feature"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/helper.js b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/helper.js new file mode 100644 index 000000000..4f51f7bfa --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/lib/helper.js @@ -0,0 +1 @@ +module.exports = "helper"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/main.js b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/main.js new file mode 100644 index 000000000..0470f8201 --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/main.js @@ -0,0 +1 @@ +module.exports = "main"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/package.json b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/package.json new file mode 100644 index 000000000..09d74787c --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-exports/package.json @@ -0,0 +1 @@ +{ "name": "with-exports", "version": "1.0.0", "main": "./main.js", "exports": { ".": { "import": "./esm.mjs", "require": "./cjs.cjs" }, "./feature": "./lib/feature.js", "./lib/*": "./lib/*.js", "./private/*": null } } diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-main/lib/entry.js b/tests/corpus/8211-require-resolve-computed/node_modules/with-main/lib/entry.js new file mode 100644 index 000000000..6555fae7c --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-main/lib/entry.js @@ -0,0 +1 @@ +module.exports = "entry"; diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/with-main/package.json b/tests/corpus/8211-require-resolve-computed/node_modules/with-main/package.json new file mode 100644 index 000000000..8c473085d --- /dev/null +++ b/tests/corpus/8211-require-resolve-computed/node_modules/with-main/package.json @@ -0,0 +1 @@ +{ "name": "with-main", "version": "1.0.0", "main": "lib/entry" } diff --git a/tests/corpus/8212-native-helper-probe-no-display/main.ts b/tests/corpus/8212-native-helper-probe-no-display/main.ts new file mode 100644 index 000000000..a9e021017 --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/main.ts @@ -0,0 +1,18 @@ +// A terminal package that loads prebuilt native helpers the way pi-tui does: +// it builds candidate paths (through require.resolve read as a value) and +// requires each one inside a try block. On Linux without DISPLAY the +// clipboard helper is never loaded, and on other platforms no helper for +// the platform is installed, so the package uses its JavaScript fallback as +// in Node. +import { getNativeClipboard, getNativeModuleCandidates, isNativeModifierPressed } from "native-helper"; +import { relative } from "node:path"; + +delete process.env["DISPLAY"]; +console.log("clipboard", typeof getNativeClipboard()); +console.log("modifier", isNativeModifierPressed("shift")); +const root = import.meta.dirname; +for (const candidate of getNativeModuleCandidates("native/helper.node")) { + console.log("candidate", relative(root, candidate).replaceAll("\\", "/")); +} +const custom = getNativeModuleCandidates("native/helper.node", { resolvePackage: () => "/opt/helper/dist/index.js" }); +console.log("custom", custom[0]!.replaceAll("\\", "/")); diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.d.ts b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.d.ts new file mode 100644 index 000000000..8803ba552 --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.d.ts @@ -0,0 +1,11 @@ +export interface NativeHelper { + getText(): string; + getImage(): Uint8Array | undefined; + isModifierPressed?(key: string): boolean; +} +export declare function getNativeClipboard(): NativeHelper | undefined; +export declare function isNativeModifierPressed(key: string): boolean; +export declare function getNativeModuleCandidates( + nativePath: string, + options?: { moduleUrl?: string; resolvePackage?: (id: string) => string; execPath?: string }, +): string[]; diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.js b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.js new file mode 100644 index 000000000..874130379 --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/index.js @@ -0,0 +1,2 @@ +export { getNativeClipboard, isNativeModifierPressed } from "./native-platform.js"; +export { getNativeModuleCandidates } from "./native-module-path.js"; diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-module-path.js b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-module-path.js new file mode 100644 index 000000000..2a82b41f7 --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-module-path.js @@ -0,0 +1,20 @@ +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const moduleRequire = createRequire(import.meta.url); + +// Where a prebuilt helper can live: next to the installed package, next to +// this module, and next to the executable. +export function getNativeModuleCandidates(nativePath, options = {}) { + const moduleDir = dirname(fileURLToPath(options.moduleUrl ?? import.meta.url)); + const candidates = []; + try { + const entry = (options.resolvePackage ?? moduleRequire.resolve)("native-helper"); + candidates.push(join(dirname(entry), "..", nativePath)); + } catch { + // A standalone executable has no installed package. + } + candidates.push(join(moduleDir, "..", nativePath), join(moduleDir, nativePath)); + return Array.from(new Set(candidates)); +} diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-platform.js b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-platform.js new file mode 100644 index 000000000..9aaed1dc1 --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/dist/native-platform.js @@ -0,0 +1,48 @@ +import { createRequire } from "node:module"; +import * as path from "node:path"; +import { getNativeModuleCandidates } from "./native-module-path.js"; + +const load = createRequire(import.meta.url); +const helpers = new Map(); + +function loadHelper(platform, suffix = "") { + const arch = process.arch; + if (arch !== "x64" && arch !== "arm64") return undefined; + const nativePath = path.join("native", platform, "prebuilds", `${platform}-${arch}`, `${platform}-helper${suffix}.node`); + if (helpers.has(nativePath)) return helpers.get(nativePath); + for (const modulePath of getNativeModuleCandidates(nativePath)) { + try { + const helper = load(modulePath); + if (typeof helper?.getText === "function") { + helpers.set(nativePath, helper); + return helper; + } + } catch { + // Try the next location. + } + } + helpers.set(nativePath, undefined); + return undefined; +} + +function getPlatformHelper() { + if (process.platform !== "darwin" && process.platform !== "win32") return undefined; + return loadHelper(process.platform); +} + +// The clipboard helper opens the X display, so it loads only when one is set. +export function getNativeClipboard() { + if (process.platform !== "linux") return getPlatformHelper(); + if (!process.env.DISPLAY) return undefined; + return loadHelper("linux", "-x11"); +} + +export function isNativeModifierPressed(key) { + const helper = getPlatformHelper(); + if (!helper?.isModifierPressed) return false; + try { + return helper.isModifierPressed(key) === true; + } catch { + return false; + } +} diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-arm64/linux-helper-x11.node b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-arm64/linux-helper-x11.node new file mode 100644 index 000000000..72d2c39bd --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-arm64/linux-helper-x11.node @@ -0,0 +1 @@ +placeholder for a prebuilt X11 clipboard helper diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-x64/linux-helper-x11.node b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-x64/linux-helper-x11.node new file mode 100644 index 000000000..72d2c39bd --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/native/linux/prebuilds/linux-x64/linux-helper-x11.node @@ -0,0 +1 @@ +placeholder for a prebuilt X11 clipboard helper diff --git a/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/package.json b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/package.json new file mode 100644 index 000000000..475c1d5af --- /dev/null +++ b/tests/corpus/8212-native-helper-probe-no-display/node_modules/native-helper/package.json @@ -0,0 +1 @@ +{ "name": "native-helper", "version": "1.0.0", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts" } diff --git a/tests/fixtures/npm-static/addon-probe-cli.ts b/tests/fixtures/npm-static/addon-probe-cli.ts new file mode 100644 index 000000000..4515fb5bf --- /dev/null +++ b/tests/fixtures/npm-static/addon-probe-cli.ts @@ -0,0 +1,5 @@ +import { probe, resolveHelper } from "addon-probe"; +import { join } from "node:path"; + +console.log(join("a", "b"), [probe("clipboard"), probe("keys"), resolveHelper("addon-probe-helper")].join(",")); +console.log(resolveHelper("other", (id) => `custom:${id}`)); diff --git a/tests/fixtures/npm-static/addon-probe-installed-cli.ts b/tests/fixtures/npm-static/addon-probe-installed-cli.ts new file mode 100644 index 000000000..5568ce086 --- /dev/null +++ b/tests/fixtures/npm-static/addon-probe-installed-cli.ts @@ -0,0 +1,4 @@ +import { loadInstalled } from "addon-probe"; + +console.log("before"); +console.log(loadInstalled(process.argv[2] === "module" ? "module" : "addon")); diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts b/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts new file mode 100644 index 000000000..7de7bc1fd --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/index.d.ts @@ -0,0 +1,3 @@ +export declare function probe(name: string): string; +export declare function resolveHelper(name: string, resolvePackage?: (id: string) => string): string; +export declare function loadInstalled(kind: "addon" | "module"): string; diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/index.js b/tests/fixtures/npm-static/node_modules/addon-probe/index.js new file mode 100644 index 000000000..b718d7e98 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/index.js @@ -0,0 +1,38 @@ +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const load = createRequire(import.meta.url); +const here = dirname(fileURLToPath(import.meta.url)); + +// Looks for a prebuilt addon in each packaging location, the way +// packages with optional native helpers do. +export function probe(name) { + for (const dir of ["prebuilds", "native"]) { + try { + const addon = load(join(here, dir, `${name}.node`)); + if (typeof addon?.run === "function") return "loaded"; + } catch { + // Try the next location. + } + } + return "absent"; +} + +export function resolveHelper(name, resolvePackage) { + try { + return (resolvePackage ?? load.resolve)(name); + } catch { + return "unresolved"; + } +} + +// Requires a file that is installed: an addon, or a module that is not part +// of the compiled program. +export function loadInstalled(kind) { + try { + return typeof load(join(here, kind === "addon" ? "native/installed.node" : "installed.js")); + } catch { + return "fallback"; + } +} diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/installed.js b/tests/fixtures/npm-static/node_modules/addon-probe/installed.js new file mode 100644 index 000000000..8476dc24e --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/installed.js @@ -0,0 +1 @@ +export const installed = true; diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/native/installed.node b/tests/fixtures/npm-static/node_modules/addon-probe/native/installed.node new file mode 100644 index 000000000..3d1ca6ec7 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/native/installed.node @@ -0,0 +1 @@ +placeholder for a prebuilt addon diff --git a/tests/fixtures/npm-static/node_modules/addon-probe/package.json b/tests/fixtures/npm-static/node_modules/addon-probe/package.json new file mode 100644 index 000000000..a2f7406f5 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/addon-probe/package.json @@ -0,0 +1,7 @@ +{ + "name": "addon-probe", + "version": "1.0.0", + "type": "module", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/harness/corpus-costs/8210-require-computed-missing/main.ts.json b/tests/harness/corpus-costs/8210-require-computed-missing/main.ts.json new file mode 100644 index 000000000..ef439d096 --- /dev/null +++ b/tests/harness/corpus-costs/8210-require-computed-missing/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 4500 } diff --git a/tests/harness/corpus-costs/8211-require-resolve-computed/main.ts.json b/tests/harness/corpus-costs/8211-require-resolve-computed/main.ts.json new file mode 100644 index 000000000..ef439d096 --- /dev/null +++ b/tests/harness/corpus-costs/8211-require-resolve-computed/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 4500 } diff --git a/tests/harness/corpus-costs/8212-native-helper-probe-no-display/main.ts.json b/tests/harness/corpus-costs/8212-native-helper-probe-no-display/main.ts.json new file mode 100644 index 000000000..ef439d096 --- /dev/null +++ b/tests/harness/corpus-costs/8212-native-helper-probe-no-display/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 4500 } diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 388da2138..962dc75b8 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -423,13 +423,99 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { }, ); + test.each(["llvm"] as const)( + "a package probing for optional addons through createRequire resolves at run time (%s)", + async (backend) => { + const entry = join(pilotRoot, "addon-probe-cli.ts"); + const { coverage } = analyze(entry, { npmStatic: "auto" }); + expect(coverage.npmStatic).toEqual([{ package: "addon-probe", status: "static" }]); + expect(coverage.diagnostics).toHaveLength(0); + expect( + (coverage.runtimeFences ?? []).filter((f) => f.code === "SC2020").map((f) => f.message), + ).toEqual([ + expect.stringContaining( + "createRequire's require with a computed specifier resolves at run time", + ), + expect.stringContaining( + "createRequire's require.resolve with a computed specifier resolves at run time", + ), + ]); + const outDir = join(cacheDir, "addon-probe", sanitize ? "san" : "plain", backend); + const result = await compile(entry, { + backend, + npmStatic: "auto", + sanitize, + outDir, + outPath: join(outDir, "program"), + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary(process.execPath, [entry]), + runBinary(result.binaryPath, []), + ]); + expect(nativeRes.stdout.toString()).toBe("a/b absent,absent,unresolved\ncustom:other\n"); + expect(nativeRes.stdout).toEqual(nodeRes.stdout); + expect(comparableStderr(nativeRes.stderr)).toEqual(nodeRes.stderr); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + }, + ); + + // Node would load the installed file, which the compiled program cannot: + // the require ends the program with the refusal instead of throwing, so + // the package's catch clause does not take its fallback. test.each([ [ - "computed specifier", - 'const spec = "node:path"; const path = load(spec);', - "computed specifier", + "addon", + /^scriptc: native addon '.*[\\/]native[\\/]installed\.node' requires Node's addon runtime, which is unavailable in scriptc \[SC2020 at .*index\.js:\d+\]\n$/, ], + [ + "module", + /^scriptc: createRequire's require of '.*[\\/]installed\.js' resolves to '.*[\\/]installed\.js', which is not part of the compiled program: the module graph is fixed at build time \[SC2020 at .*index\.js:\d+\]\n$/, + ], + ] as const)( + "a computed require of an installed %s ends the program with a refusal", + async (kind, stderr) => { + const entry = join(pilotRoot, "addon-probe-installed-cli.ts"); + const outDir = join(cacheDir, "addon-probe-installed", sanitize ? "san" : "plain"); + const result = await compile(entry, { + backend: "llvm", + npmStatic: "auto", + sanitize, + outDir, + outPath: join(outDir, "program"), + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const nativeRes = await runBinary(result.binaryPath, [kind]); + expect(nativeRes.stdout.toString()).toBe("before\n"); + expect(comparableStderr(nativeRes.stderr).toString()).toMatch(stderr); + expect(nativeRes.exitCode).toBe(1); + }, + ); + + test.each([ ["escaped require", 'const alias = load; const path = alias("node:path");', "escapes"], + [ + "escaped require in a try block", + 'try { const alias = load; alias("node:path"); } catch {} const path = load("node:path");', + "escapes", + ], + [ + "computed resolve.paths request", + 'const spec = "node:path"; load.resolve.paths(spec); const path = load("node:path");', + "escapes", + ], + [ + "resolve.paths value", + 'const paths = load.resolve.paths; const path = load("node:path");', + "escapes", + ], + [ + "spread require arguments", + 'const specs = ["node:path"]; load(...specs); const path = load("node:path");', + "escapes", + ], [ "reassigned require", 'let load = createLoader(import.meta.url); load = () => ({}); const path = load("node:path");', From 970a53c1ba17516b203fe445de2769392321ab2f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 00:05:48 -0500 Subject: [PATCH 019/230] Store (T | undefined)[] elements as T so widened arrays stay one array A number[], string[] or boolean[] seen as (T | undefined)[] through a cast, a binding or a parameter is the same array in Node: undefined written through either name is visible through both. The type mapper now stores (T | undefined)[] of a number, string or boolean T as T elements, which hold a present undefined by their slot state, so the wider name shares the storage instead of converting to a separate union array. Writers of these arrays store undefined as a present undefined: element writes, push, unshift and splice, literals, Array.from (with a length or a mapper), Array.of, generator drains, flatMap, rest packs, copies from Map and Set values and Object.values, and checked conversions from dynamic values. Promise.all over Promise[] fills the result inside the combinator's settlement, so it settles on Node's turn. The element-state analysis counts callbacks and iterables whose checker type admits undefined. Readers keep their optional reads: index reads through a union of arrays, searches with a direct needle, and asserted reads, which throw a TypeError for undefined. Comparators and callbacks that receive present elements take the element type, without an adapter. --- packages/compiler/src/backend/llvm/dyn.ts | 25 ++- .../src/backend/llvm/expr-stream-bridges.ts | 19 +- .../src/frontend/lowering/array-values.ts | 140 +++++++++++++++ .../frontend/lowering/builtins/promises.ts | 162 +++++++++++++++++- .../src/frontend/lowering/call-arguments.ts | 10 ++ .../frontend/lowering/coercions/structural.ts | 110 ++++++++++-- .../lowering/containers/array-callbacks.ts | 66 +++++-- .../lowering/containers/array-construction.ts | 67 +++++++- .../lowering/containers/array-methods.ts | 106 +++++++++++- .../lowering/containers/callback-arguments.ts | 13 +- .../lowering/containers/collection-methods.ts | 12 ++ .../lowering/containers/indexed-objects.ts | 106 +++++++++--- .../lowering/expressions/array-literals.ts | 23 ++- .../lowering/expressions/element-access.ts | 45 +++++ .../frontend/lowering/iterator-adapters.ts | 58 +++---- .../compiler/src/frontend/lowering/lowerer.ts | 58 ++++++- .../lowering/runtime-optional-elements.ts | 9 +- .../frontend/lowering/sanitize-class-types.ts | 2 + packages/compiler/src/frontend/type-mapper.ts | 19 +- packages/compiler/src/ir/ir.ts | 11 +- packages/compiler/src/ir/validate.ts | 10 +- ...ional-element-arrays-share-storage.ts.json | 6 + .../8001-optional-element-writers.ts.json | 6 + ...0-optional-element-arrays-share-storage.ts | 61 +++++++ tests/corpus/8001-optional-element-writers.ts | 103 +++++++++++ ...ional-element-arrays-share-storage.ts.json | 1 + .../8001-optional-element-writers.ts.json | 1 + 27 files changed, 1145 insertions(+), 104 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8000-optional-element-arrays-share-storage.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8001-optional-element-writers.ts.json create mode 100644 tests/corpus/8000-optional-element-arrays-share-storage.ts create mode 100644 tests/corpus/8001-optional-element-writers.ts create mode 100644 tests/harness/corpus-costs/8000-optional-element-arrays-share-storage.ts.json create mode 100644 tests/harness/corpus-costs/8001-optional-element-writers.ts.json diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 6d02daa8b..f3c8231e1 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -547,6 +547,13 @@ export class LlDyn { /* ── dyn node plumbing ─────────────────────────────────────────────── */ /** Loads a dyn node's kind tag (after the size_t rc word). */ + /** i1: the checked-dynamic value `d` is undefined. */ + isUndefined(B: BlockBuilder, d: string): string { + const is = B.tmp(); + B.line(`${is} = icmp eq i32 ${this.kindOf(B, d)}, ${DYN_KIND.UNDEF}`); + return is; + } + private kindOf(B: BlockBuilder, d: string): string { const p = B.tmp(); const k = B.tmp(); @@ -1655,7 +1662,8 @@ export class LlDyn { * BUILD the typed value (+1), or throw the catchable path-annotated * TypeError and return a dummy with the pending flag set. */ dynCheckHelper(t: IrType, preserveRefs = false): string { - const key = typeKey(t); + // A `(T | undefined)[]` stored as T elements also takes undefined ones. + const key = t.kind === "array" && t.optionalElements ? `${typeKey(t)}?` : typeKey(t); const builderKey = preserveRefs ? `identity:${key}` : key; const existing = this.dynBuilders.get(builderKey); if (existing) return existing; @@ -2435,6 +2443,21 @@ export class LlDyn { B.line(`${ip} = getelementptr inbounds %ScrDynPath, ptr ${pathSlot}, i64 0, i32 2`); B.line(`store ${host.sizeType} ${i}, ptr ${ip}`); const e = this.itemAt(B, items, i); + if (t.optionalElements) { + // An undefined element is a present undefined element. + const undef = B.tmp(); + const storeUndefined = B.newLabel("dca.undef"); + const checkLabel = B.newLabel("dca.check"); + B.line(`${undef} = icmp eq i32 ${this.kindOf(B, e)}, ${DYN_KIND.UNDEF}`); + B.condBr(undef, storeUndefined, checkLabel); + B.startBlock(storeUndefined); + host.declare(`declare void @scr_arr_set_undefined(ptr, double)`); + const at = B.tmp(); + B.line(`${at} = ${sizeToDouble(this.S)} ${this.S} ${i} to double`); + B.line(`call void @scr_arr_set_undefined(ptr ${a}, double ${at})`); + B.br(doneLabel); + B.startBlock(checkLabel); + } const v = B.tmp(); B.line(`${v} = call ${this.valTy(elem)} @${c}(ptr ${e}, ptr ${pathSlot})`); this.pendingBail( diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 46c2d884f..814ef76b5 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -744,13 +744,16 @@ export function streamTypedRefMaterializeAdapter( t: IrType, ): LlStreamTypedRefAdapter { const key = typeKey(t); - const existing = host.liveDynRefAdapters.get(key); + // A `(T | undefined)[]` stored as T elements shares T[]'s capsule type, + // but its adapters also store undefined elements. + const adapterKey = t.kind === "array" && t.optionalElements ? `${key}?` : key; + const existing = host.liveDynRefAdapters.get(adapterKey); if (existing) return existing; // The converter depends only on the static type. Intern the complete // graph across roots and union arms so recursive types emit once. const snapshot = `sc_ldr_${host.liveDynRefAdapters.size}_materialize`; const adapter: LlStreamTypedRefAdapter = { snapshot, commit: "null" }; - host.liveDynRefAdapters.set(key, adapter); + host.liveDynRefAdapters.set(adapterKey, adapter); host.journal?.allocated( snapshot.replace(/_materialize$/, ""), "streamTypedRefMaterializeAdapter", @@ -1257,6 +1260,18 @@ function liveArrayOps( G.terminate(`ret ptr ${boxed}`); const S = new BlockBuilder(); + if (t.optionalElements) { + // A `(T | undefined)[]` stores undefined as a present undefined. + const undef = host.dyn.isUndefined(S, "%v"); + const storeUndefined = S.newLabel("live.array.undefined"); + const other = S.newLabel("live.array.value"); + S.condBr(undef, storeUndefined, other); + S.startBlock(storeUndefined); + host.declare(`declare void @scr_arr_set_undefined(ptr, double)`); + S.line(`call void @scr_arr_set_undefined(ptr %p, double %i)`); + S.terminate(`ret i1 true`); + S.startBlock(other); + } const fits = S.tmp(); S.line(`${fits} = call zeroext i1 @${host.dyn.dynMatchHelper(elem)}(ptr %v)`); const store = S.newLabel("live.array.store"); diff --git a/packages/compiler/src/frontend/lowering/array-values.ts b/packages/compiler/src/frontend/lowering/array-values.ts index f191ad942..6e4a45f86 100644 --- a/packages/compiler/src/frontend/lowering/array-values.ts +++ b/packages/compiler/src/frontend/lowering/array-values.ts @@ -269,6 +269,140 @@ function readsMutableLocal(lowerer: Lowerer, e: IrExpr): boolean { }); } +/** A number, string or boolean element type. Arrays of it hold a present + * undefined in the element's state, so `(T | undefined)[]` stores T + * elements (the type mapper) and a T[] seen as one stays the same array. */ +export function primitiveElement(elem: IrType): boolean { + return elem.kind === "f64" || elem.kind === "string" || elem.kind === "bool"; +} + +/** `value` is the element type with undefined, stored into an array of a + * primitive element type: a present undefined or the element. */ +export function optionalElementValue(lowerer: Lowerer, value: IrExpr, elem: IrType): boolean { + if (!primitiveElement(elem)) return false; + if (value.type.kind === "undefinedT") return true; + return ( + value.type.kind === "union" && + lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0 && + typeEquals(lowerer.stripUndefinedArm(value.type), elem) + ); +} + +/** A value of a primitive element type that holds a slot until the value + * stored there replaces it (insertedValues), or stands for a slot that is + * not read. */ +export function elementPlaceholder(elem: IrType, loc: SrcLoc): IrExpr { + if (elem.kind === "f64") return { kind: "numLit", value: 0, type: F64, loc }; + if (elem.kind === "bool") return { kind: "boolLit", value: false, type: BOOL, loc }; + return { kind: "strLit", value: "", type: STRING, loc }; +} + +/** Values inserted into an array (push, unshift, splice, a literal), in + * source order. A value that can be undefined (optionalElementValue) is + * kept in a temporary: the insertion takes a placeholder for it, and + * `fill` then stores the value at the placeholder's index. */ +export function insertedValues( + lowerer: Lowerer, + values: readonly IrExpr[], + elem: IrType, + coerce: (value: IrExpr, index: number) => IrExpr, + loc: SrcLoc, +): { + stmts: IrStmt[]; + args: IrExpr[]; + optional: boolean; + fill: (arr: IrExpr, first: IrExpr) => IrStmt[]; +} { + if (!values.some((value) => optionalElementValue(lowerer, value, elem))) + return { stmts: [], args: values.map(coerce), optional: false, fill: () => [] }; + const stmts: IrStmt[] = []; + const args: IrExpr[] = []; + const held: { offset: number; value: IrExpr }[] = []; + values.forEach((value, offset) => { + // Every value is evaluated in order before the insertion. + if (value.type.kind === "undefinedT") { + if (value.kind !== "unitLit") stmts.push({ kind: "exprStmt", expr: value, loc }); + args.push(elementPlaceholder(elem, loc)); + held.push({ offset, value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } }); + return; + } + const temp = lowerer.declareHiddenLocal("%inserted", value.type); + stmts.push({ kind: "varDecl", localId: temp.id, init: value, loc }); + const stable = varRef(temp.id, temp.type, loc); + if (optionalElementValue(lowerer, value, elem)) { + args.push(elementPlaceholder(elem, loc)); + held.push({ offset, value: stable }); + } else { + args.push(coerce(stable, offset)); + } + }); + const fill = (arr: IrExpr, first: IrExpr): IrStmt[] => + held.map(({ offset, value }) => + arrayValueStore( + lowerer, + arr, + offset === 0 + ? first + : { + kind: "bin", + op: "+", + left: first, + right: { kind: "numLit", value: offset, type: F64, loc }, + type: F64, + loc, + }, + value, + elem, + loc, + ), + ); + return { stmts, args, optional: true, fill }; +} + +/** Statements appending `value` to `arr` inside a lifted helper. A value + * that can be undefined in a primitive array (optionalElementValue) is held + * in the helper's local `slot` (which the caller declares with the value's + * type) and stored as a present undefined or the element; any other value + * converts to the element type. */ +export function appendElement( + lowerer: Lowerer, + arr: IrExpr, + value: IrExpr, + elem: IrType, + slot: string, + loc: SrcLoc, +): IrStmt[] { + if (optionalElementValue(lowerer, value, elem) && value.type.kind !== "undefinedT") { + const held = varRef(slot, value.type, loc); + const length: IrExpr = { + kind: "arrIntrinsic", + method: "length", + receiver: arr, + args: [], + type: F64, + loc, + }; + return [ + { kind: "varDecl", localId: slot, init: value, loc }, + arrayValueStore(lowerer, arr, length, held, elem, loc), + ]; + } + return [ + { + kind: "exprStmt", + expr: { + kind: "arrIntrinsic", + method: "push", + receiver: arr, + args: [lowerer.coerceToExpected(value, elem)], + type: F64, + loc, + }, + loc, + }, + ]; +} + /** Store a stabilized value without changing the array's element ABI. */ export function arrayValueStore( lowerer: Lowerer, @@ -279,6 +413,12 @@ export function arrayValueStore( loc: SrcLoc, ): IrStmt { if (typeEquals(value.type, elem)) return { kind: "arraySet", arr, index, value, loc }; + // An undefined value of a `(T | undefined)[]` (primitiveElement). + if (value.type.kind === "undefinedT" && primitiveElement(elem)) { + const write: IrStmt = { kind: "arraySetUndefined", arr, index, loc }; + if (value.kind === "unitLit") return write; + return { kind: "block", body: [{ kind: "exprStmt", expr: value, loc }, write], loc }; + } if (value.type.kind === "union" && lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0) { // The tag test and the VALUE arm extraction both consume the value. Keep // an arbitrary expression (especially an array read or call) single diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index e9d4322ad..c3b2cd479 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -3,7 +3,8 @@ import { checkedPromiseAll, thenReader } from "../checked-promise-all.js"; import { InternalCompilerError } from "../../../errors.js"; import * as ts from "../../ts7/adapter.js"; import type { Lowerer } from "../lowerer.js"; -import { varRef } from "../../../ir/build.js"; +import { countedFor, varRef } from "../../../ir/build.js"; +import { arrayValueStore, optionalElementValue, primitiveElement } from "../array-values.js"; import { maybePromiseResolve, maybePromiseUnionOf } from "../maybe-promise.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { @@ -14,6 +15,7 @@ import { type IrFunction, type IrStmt, type IrType, + type SrcLoc, UNDEFINED_T, VOID, arrayOf, @@ -435,6 +437,28 @@ export function lowerPromiseStaticCall( inner.kind === "void" ? { kind: "promise", inner: VOID } : lowerer.mapTypeOf(lowerer.typeOf(call)); + // Promise[] combines into a `(T | undefined)[]`, which + // stores T elements (primitiveElement): collect the values, then copy + // them in with their present undefined values. + if ( + resultT?.kind === "promise" && + resultT.inner.kind === "array" && + primitiveElement(resultT.inner.elem) && + optionalElementValue( + lowerer, + { kind: "unitLit", unit: "undefined", type: inner, loc }, + resultT.inner.elem, + ) + ) { + const joined = optionalPromiseAll( + lowerer, + entries, + inner, + resultT as IrType & { kind: "promise" }, + loc, + ); + if (joined) return joined; + } if ( resultT?.kind !== "promise" || (inner.kind !== "void" && @@ -954,3 +978,139 @@ export function lowerAwaitedUniformPromiseAll( loc, }; } + +/** `Promise.all(ps)` over `Promise[]` for a number, string or + * boolean T, whose result is a `(T | undefined)[]` (stored as T elements). + * The combinator joins the entries (promise.allTuple), and its builder + * reads each fulfilled entry into the result with its present undefined, + * inside the combinator's settlement, so the result settles on Node's + * turn. Entries of another type keep the general combinator. */ +function optionalPromiseAll( + lowerer: Lowerer, + entries: IrExpr, + inner: IrType, + resultT: IrType & { kind: "promise" }, + loc: SrcLoc, +): IrExpr | null { + const entriesT = entries.type; + if (entriesT.kind !== "array" || entriesT.elem.kind !== "promise") return null; + if (!typeEquals(entriesT.elem.inner, inner)) return null; + const outT = resultT.inner as IrType & { kind: "array" }; + const voidPromise: IrType & { kind: "promise" } = { kind: "promise", inner: VOID }; + const joinedT = arrayOf(voidPromise); + const key = `promise.allOptional:${typeKey(entriesT)}:${typeKey(outT)}`; + let helper = lowerer.arrHofHelpers.get(key); + if (!helper) { + helper = keyedHelperName(lowerer, "%promise.allOptional", key); + lowerer.arrHofHelpers.set(key, helper); + const builder = keyedHelperName(lowerer, "%promise.allOptionalValues", key); + const length = (arr: IrExpr): IrExpr => ({ + kind: "arrIntrinsic", + method: "length", + receiver: arr, + args: [], + type: F64, + loc, + }); + const joined = varRef("joined.0", joinedT, loc); + const out = varRef("out.0", outT, loc); + lowerer.liftedFns.push({ + name: builder, + params: [{ localId: "joined.0", name: "joined", type: joinedT }], + returnType: outT, + locals: [ + { id: "joined.0", name: "joined", type: joinedT, mutable: false }, + { id: "out.0", name: "out", type: outT, mutable: false }, + { id: "i.0", name: "i", type: F64, mutable: true }, + { id: "v.0", name: "v", type: inner, mutable: false }, + ], + body: [ + { + kind: "varDecl", + localId: "out.0", + init: { kind: "arrayLit", elems: [], type: outT, loc }, + loc, + }, + countedFor(loc, length(joined), (index) => [ + { + kind: "varDecl", + localId: "v.0", + init: { + kind: "libCall", + fn: "promise.fulfilledValue", + args: [{ kind: "arrayGet", arr: joined, index, type: voidPromise, loc }], + type: inner, + loc, + }, + loc, + }, + arrayValueStore(lowerer, out, index, varRef("v.0", inner, loc), outT.elem, loc), + ]), + { kind: "return", value: out, loc }, + ], + loc, + }); + const ps = varRef("ps.0", entriesT, loc); + lowerer.liftedFns.push({ + name: helper, + params: [{ localId: "ps.0", name: "ps", type: entriesT }], + returnType: resultT, + locals: [ + { id: "ps.0", name: "ps", type: entriesT, mutable: false }, + { id: "joined.0", name: "joined", type: joinedT, mutable: false }, + { id: "i.0", name: "i", type: F64, mutable: true }, + ], + body: [ + { + kind: "varDecl", + localId: "joined.0", + init: { kind: "arrayLit", elems: [], type: joinedT, loc }, + loc, + }, + countedFor(loc, length(ps), (index) => [ + { + kind: "exprStmt", + expr: { + kind: "arrIntrinsic", + method: "push", + receiver: joined, + args: [ + { + kind: "promiseVoidWiden", + value: { kind: "arrayGet", arr: ps, index, type: entriesT.elem, loc }, + type: voidPromise, + loc, + }, + ], + type: F64, + loc, + }, + loc, + }, + ]), + { + kind: "return", + value: { + kind: "libCall", + fn: "promise.allTuple", + args: [ + joined, + { + kind: "closure", + fnName: builder, + captures: [], + type: funcOf([joinedT], outT), + loc, + }, + ], + type: resultT, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: helper, args: [entries], type: resultT, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index 060ffe9d2..6da0cb337 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -28,6 +28,7 @@ import { checkedIterableSpread } from "./checked-iterable-spread.js"; import { type ParamShape } from "./call-signatures.js"; import { arrayValueRead, arrayValueType } from "./array-values.js"; import { buildArrayConversion } from "./coercions/builders.js"; +import { lowerArraySpreadItems } from "./containers/array-construction.js"; import { restTupleElementTypes, typeKey } from "../type-mapper.js"; function fixedTupleSpreadInfo( @@ -403,6 +404,15 @@ export function completeArgs( } } if (restType.kind !== "array") lowerer.unsupported("SC1090", blame, "this call form"); + // A `(T | undefined)[]` rest (stored as T elements) packs undefined + // arguments as present undefined elements. + const restNodes = sources.slice(restAt); + if (restType.optionalElements === true && !restNodes.some(isIr)) { + out.push( + lowerArraySpreadItems(lowerer, restNodes as ts.Expression[], restType.elem, restType, loc), + ); + return out; + } // The rest pack is a fresh array per call; surplus SPREADS copy // their elements in (JS-exact — `f(a, ...xs, b, ...ys)` packs in // order, sources untouched). diff --git a/packages/compiler/src/frontend/lowering/coercions/structural.ts b/packages/compiler/src/frontend/lowering/coercions/structural.ts index 4321a5ead..93ae1c231 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural.ts @@ -1,12 +1,13 @@ import { keyedHelperName } from "../stable-names.js"; import { constructedValue } from "../constructor-returns.js"; -import { dynUndefinedExpr, varRef } from "../../../ir/build.js"; +import { countedFor, dynUndefinedExpr, varRef } from "../../../ir/build.js"; import type { FieldLift } from "./structural-plans.js"; import { buildArrayConversion } from "./builders.js"; +import { arrayValueRead, arrayValueStore, primitiveElement } from "../array-values.js"; import type { WidthLift } from "../width-lift.js"; import { InternalCompilerError } from "../../../errors.js"; import type { IrExpr, IrFunction, IrRecordShape, IrStmt, IrType, SrcLoc } from "../../../ir/ir.js"; -import { BOOL, DYN, F64, STRING, UNDEFINED_T } from "../../../ir/ir.js"; +import { BOOL, DYN, F64, STRING, UNDEFINED_T, typeEquals } from "../../../ir/ir.js"; import { typeKey } from "../../type-mapper.js"; import { findStaticOn, findGenericStaticOn } from "../lower-classes.js"; import { lowerRecordOvfCaptureHelper } from "../containers/indexed-objects.js"; @@ -54,6 +55,9 @@ export function widthCoerce(lowerer: Lowerer, expr: IrExpr, expected: IrType): I return lowerer.classStaticsProjection(expr.type.className, expected.shapeId, expr.loc); } if (expected.kind === "array" && expr.type.kind === "array") { + const optional = optionalArrayHelper(lowerer, expr.type, expected, expr.loc); + if (optional) + return { kind: "call", callee: optional, args: [expr], type: expected, loc: expr.loc }; const helper = lowerer.arrayWidthHelper(expr.type, expected, expr.loc); if (helper) return { kind: "call", callee: helper, args: [expr], type: expected, loc: expr.loc }; @@ -518,6 +522,69 @@ export function emptyArrayLiftHelper( return name; } +/** Copy an array of `T | undefined` unions (a collection's values, an + * iterator drain) into a `(T | undefined)[]` of a number, string or + * boolean T, which stores T elements: undefined values become present + * undefined elements. Null for other element types. */ +function optionalArrayHelper( + lowerer: Lowerer, + fromT: IrType & { kind: "array" }, + toT: IrType & { kind: "array" }, + loc: SrcLoc, +): string | null { + const from = fromT.elem; + const to = toT.elem; + if ( + !primitiveElement(to) || + from.kind !== "union" || + lowerer.armTag(from.unionId, UNDEFINED_T) < 0 || + !typeEquals(lowerer.stripUndefinedArm(from), to) + ) + return null; + const key = `arrOptional:${typeKey(from)}:${typeKey(to)}`; + const existing = lowerer.valueHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, "%arr.width", key); + lowerer.valueHelpers.set(key, name); + const input = varRef("a.0", fromT, loc); + const out = varRef("out.0", toT, loc); + lowerer.liftedFns.push({ + name, + params: [{ localId: "a.0", name: "a", type: fromT }], + returnType: toT, + locals: [ + { id: "a.0", name: "a", type: fromT, mutable: true }, + { id: "out.0", name: "out", type: toT, mutable: false }, + { id: "i.0", name: "i", type: F64, mutable: true }, + { id: "v.0", name: "v", type: from, mutable: false }, + ], + body: [ + { + kind: "varDecl", + localId: "out.0", + init: { kind: "arrayLit", elems: [], type: toT, loc }, + loc, + }, + countedFor( + loc, + { kind: "arrIntrinsic", method: "length", receiver: input, args: [], type: F64, loc }, + (index) => [ + { + kind: "varDecl", + localId: "v.0", + init: arrayValueRead(lowerer, input, index, from, loc), + loc, + }, + arrayValueStore(lowerer, out, index, varRef("v.0", from, loc), to, loc), + ], + ), + { kind: "return", value: out, loc }, + ], + loc, + }); + return name; +} + /** Copy an array through its validated element conversion. Intern before * building the body so recursive element conversions can reuse this helper. */ export function arrayWidthHelper( @@ -542,14 +609,37 @@ export function arrayWidthHelper( name, arrT, { kind: "arrayLit", elems: [], type: outT, loc }, - (element, _index, result) => ({ - kind: "arrIntrinsic", - method: "push", - receiver: result, - args: [lowerer.applyWidthLift(elemLift, element, toElem, loc)], - type: F64, - loc, - }), + (element, index, result) => { + let value = lowerer.applyWidthLift(elemLift, element, toElem, loc); + // A number, string or boolean array can hold present undefined + // elements (`(T | undefined)[]` stores T): they, and holes, copy + // as the target's undefined. + const missing = primitiveElement(fromElem) ? lowerer.wrappedUndefined(toElem, loc) : null; + if (missing !== null) + value = { + kind: "ternary", + cond: { + kind: "bin", + op: "===", + left: { kind: "arrayState", arr: varRef("a.0", arrT, loc), index, type: F64, loc }, + right: { kind: "numLit", value: 1, type: F64, loc }, + type: BOOL, + loc, + }, + then: value, + else_: missing, + type: toElem, + loc, + }; + return { + kind: "arrIntrinsic", + method: "push", + receiver: result, + args: [value], + type: F64, + loc, + }; + }, loc, ), ); diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index edf65ab2b..1062c56b1 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -42,6 +42,7 @@ import { arrayValueStore, arrayValueType, currentArrayIndexPresent, + primitiveElement, } from "../array-values.js"; import { typeKey } from "../../type-mapper.js"; import { @@ -1689,6 +1690,8 @@ function flatMapUnionPlan( } const converts = (t: IrType): boolean => { if (typeEquals(t, outElem)) return true; + // A `(T | undefined)[]` result stores T elements (primitiveElement). + if (t.kind === "undefinedT" && primitiveElement(outElem)) return true; if (outElem.kind !== "union") return false; return lowerer.armTag(outElem.unionId, t) >= 0; }; @@ -1754,24 +1757,21 @@ function flatMapUnionHelper( }; if (plan.arms[tag] === "empty") return []; if (plan.arms[tag] === "value") { + // A unit arm's value is the unit itself, which has no narrowed + // representation. + const value: IrExpr = isUnitType(arm) + ? { kind: "unitLit", unit: arm.kind === "nullT" ? "null" : "undefined", type: arm, loc } + : narrowed; return [ arrayValueStore( lowerer, outRef(), outLength(), - lowerer.coerceToExpected( - // A unit arm's value is the unit itself, which has no - // narrowed representation. - isUnitType(arm) - ? { - kind: "unitLit", - unit: arm.kind === "nullT" ? "null" : "undefined", - type: arm, - loc, - } - : narrowed, - plan.outElem, - ), + // An undefined value is a present undefined element of a + // primitive array (arrayValueStore). + arm.kind === "undefinedT" && primitiveElement(plan.outElem) + ? value + : lowerer.coerceToExpected(value, plan.outElem), plan.outElem, loc, ), @@ -2347,6 +2347,43 @@ function reduceHelper( return name; } +/** The comparator of a sort over a `(T | undefined)[]`, which stores T + * elements. The comparator only receives present elements (undefined + * elements sort last without a call), so the unannotated parameters of an + * inline comparator take the element type, and the runtime calls it + * without an adapter. */ +function lowerOptionalElementComparator( + lowerer: Lowerer, + argNode: ts.Expression, + comparator: ts.Expression, + elem: IrType, + arrT: IrType & { kind: "array" }, +): IrExpr { + if ( + arrT.optionalElements !== true || + !(ts.isArrowFunction(comparator) || ts.isFunctionExpression(comparator)) || + isJsSourceFile(ts.getSourceFileOfNode(comparator)) + ) + return lowerer.lowerExpr(argNode); + const rebound: { symbol: ts.Symbol; previous: IrType | undefined }[] = []; + for (const param of comparator.parameters.nodes.slice(0, 2)) { + if (param.type || param.initializer || param.dotDotDotToken || !ts.isIdentifier(param.name)) + continue; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if (!symbol) continue; + rebound.push({ symbol, previous: lowerer.runtimeOptionalBindingTypes.get(symbol) }); + lowerer.runtimeOptionalBindingTypes.set(symbol, elem); + } + try { + return lowerer.lowerExpr(argNode); + } finally { + for (const { symbol, previous } of rebound) { + if (previous === undefined) lowerer.runtimeOptionalBindingTypes.delete(symbol); + else lowerer.runtimeOptionalBindingTypes.set(symbol, previous); + } + } +} + /** `a.sort(cmp)` / `a.toSorted(cmp)`, desugared to an interned synthetic * function like the other array HOFs. sort mutates and returns the receiver; * toSorted takes its shallow snapshot INSIDE the helper, after the receiver @@ -2435,7 +2472,8 @@ export function lowerArraySortCall( loc, }; } - let fnArg = undefinedArg ?? lowerer.lowerExpr(argNode); + let fnArg = + undefinedArg ?? lowerOptionalElementComparator(lowerer, argNode, unwrappedArg, elem, arrT); // Reusable JS comparators often accept checked values. Adapt the array's // element ABI just as other array callbacks do before sorting. if ( diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 1ca51f526..25a98c8a6 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -35,7 +35,13 @@ import { typeEquals, } from "../../../ir/ir.js"; import { isJsSourceFile, locOf } from "../../program.js"; -import { arrayValueRead, arrayValueStore, arrayValueType } from "../array-values.js"; +import { + arrayValueRead, + arrayValueStore, + arrayValueType, + optionalElementValue, + primitiveElement, +} from "../array-values.js"; import { typeKey } from "../../type-mapper.js"; import { newFnCtx } from "../lowerer.js"; import { iteratorCanStep, iteratorValue } from "../iterator-consumption.js"; @@ -88,7 +94,8 @@ export function lowerArrayValueItems( ]; for (const value of values) { const stored = - lowerer.runtimeOptionalWidening(value.type, elem) !== null + lowerer.runtimeOptionalWidening(value.type, elem) !== null || + optionalElementValue(lowerer, value, elem) ? value : lowerer.coerceToExpected(value, elem); body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc)); @@ -212,7 +219,8 @@ export function lowerArraySpreadItems( } else { const value = lowerer.lowerExpr(node); const stored = - lowerer.runtimeOptionalWidening(value.type, elem) !== null + lowerer.runtimeOptionalWidening(value.type, elem) !== null || + optionalElementValue(lowerer, value, elem) ? value : lowerer.coerceInto(node, value, elem); body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc)); @@ -518,6 +526,20 @@ export function lowerArrayOfCall( if (call.arguments.nodes.some(ts.isSpreadElement)) { return lowerArraySpreadItems(lowerer, call.arguments.nodes, result.elem, result, locOf(call)); } + // A `(T | undefined)[]` (primitiveElement) stores undefined arguments as + // present undefined elements. + if (result.optionalElements === true) { + const nodes = call.arguments.nodes; + const values = nodes.map((arg) => lowerer.lowerExpr(arg)); + if (values.some((value) => optionalElementValue(lowerer, value, result.elem))) + return lowerArrayValueItems(lowerer, values, result.elem, result, locOf(call)); + return { + kind: "arrayLit", + elems: values.map((value, i) => lowerer.coerceInto(nodes[i]!, value, result.elem)), + type: result, + loc: locOf(call), + }; + } return { kind: "arrayLit", elems: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, result.elem)), @@ -959,6 +981,31 @@ export function lowerArrayFromCall( const arrT = lowerer.mapTypeOf(lowerer.typeOf(call)); if (arrT?.kind !== "array") lowerer.badType(call, lowerer.typeOf(call)); const elem = arrT.elem; + // A `(T | undefined)[]` (primitiveElement): n present undefined + // elements. + const elemTs = lowerer.checker.isArrayType(lowerer.typeOf(call)) + ? lowerer.checker.getTypeArguments(lowerer.typeOf(call) as ts.TypeReference)[0] + : undefined; + if ( + primitiveElement(elem) && + elemTs !== undefined && + (elemTs.isUnionType() ? ts.constituentTypes(elemTs) : [elemTs]).some( + (part) => (part.flags & ts.TypeFlags.Undefined) !== 0, + ) + ) { + return { + kind: "arrIntrinsic", + method: "fillUndefined", + receiver: { kind: "arrayNewLen", length: n, type: arrT, loc }, + // To the end: fill clamps an infinite end to the length. + args: [ + numLit(0, loc), + { kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc }, + ], + type: arrT, + loc, + }; + } const absent = elem.kind === "union" ? lowerer.wrappedUndefined(elem, loc) !== null || @@ -1085,7 +1132,10 @@ export function lowerArrayFromCall( // A generator (or an iterator adapted into one) drains in order. if (src.type.kind === "generator" && args.length === 1) { const arrT = lowerer.mapTypeOf(lowerer.typeOf(call)); - const drained = arrT?.kind === "array" ? generatorDrain(lowerer, src, arrT.elem, loc) : null; + const drained = + arrT?.kind === "array" + ? generatorDrain(lowerer, src, arrT.elem, loc, arrT.optionalElements === true) + : null; if (drained) return drained; } lowerer.noLowering( @@ -1159,6 +1209,11 @@ function buildArrayFromArrayFn( type: fnRet, loc, }; + // A result of another type than the element (`T | undefined` into a + // `(T | undefined)[]`, which stores T elements) is held in a local, so + // the store tests and converts one value. + const held = !typeEquals(fnRet, outElem); + const value: IrExpr = held ? varRef("v.0", fnRet, loc) : mapped; return { name, params: [ @@ -1171,6 +1226,7 @@ function buildArrayFromArrayFn( { id: "f.0", name: "f", type: fnT, mutable: true }, { id: "out.0", name: "out", type: outT, mutable: false }, { id: "i.0", name: "i", type: F64, mutable: true }, + ...(held ? [{ id: "v.0", name: "v", type: fnRet, mutable: false }] : []), ], body: [ { @@ -1191,7 +1247,8 @@ function buildArrayFromArrayFn( loc, }, body: [ - arrayValueStore(lowerer, out, i, mapped, outElem, loc), + ...(held ? [{ kind: "varDecl", localId: "v.0", init: mapped, loc } as const] : []), + arrayValueStore(lowerer, out, i, value, outElem, loc), { kind: "assign", localId: "i.0", diff --git a/packages/compiler/src/frontend/lowering/containers/array-methods.ts b/packages/compiler/src/frontend/lowering/containers/array-methods.ts index 4ef203edd..f406e57eb 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-methods.ts @@ -32,6 +32,10 @@ import { arrayValueRead, arrayValueStore, arrayValueType, + elementPlaceholder, + insertedValues, + optionalElementValue, + primitiveElement, } from "../array-values.js"; import { typeKey } from "../../type-mapper.js"; import { @@ -125,6 +129,14 @@ function lowerOptionalArrayPush( return { kind: "seqExpr", stmts, result: length(), type: F64, loc }; } +/** The checker type of `node` admits undefined. */ +function checkerTypeAdmitsUndefined(lowerer: Lowerer, node: ts.Expression): boolean { + const type = lowerer.typeOf(node); + return (type.isUnionType() ? ts.constituentTypes(type) : [type]).some( + (part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0, + ); +} + /** `a.splice(start, count, v)` where v may hold undefined at run time. * Receiver, start, count, and v evaluate once, in order; a present v is * inserted as a plain element literal (which a statement-position splice @@ -658,7 +670,10 @@ export function lowerArrayMethodCall( const hasSpread = itemNodes.some(ts.isSpreadElement); const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg)); const statefulItems = itemProbes.some( - (probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null, + (probe) => + probe !== null && + (lowerer.runtimeOptionalWidening(probe.type, elem) !== null || + optionalElementValue(lowerer, probe, elem)), ); const items: IrExpr = hasSpread ? lowerArraySpreadItems(lowerer, itemNodes, elem, receiverIr, loc) @@ -875,6 +890,65 @@ export function lowerArrayMethodCall( loc, }; } + // A `(T | undefined)[]` (primitiveElement) takes undefined values as + // present undefined elements, stored after the insertion. + if ( + (name === "push" || name === "unshift") && + primitiveElement(elem) && + call.arguments.nodes.some((a) => checkerTypeAdmitsUndefined(lowerer, a)) + ) { + const nodes = call.arguments.nodes; + const values = nodes.map((a) => lowerer.lowerExpr(a)); + const inserted = insertedValues( + lowerer, + values, + elem, + (value, i) => lowerer.coerceInto(nodes[i]!, value, elem), + loc, + ); + if (inserted.optional) { + const array = lowerer.declareHiddenLocal("%insertInto", receiver.type); + const arr = varRef(array.id, array.type, loc); + const length = lowerer.declareHiddenLocal("%insertLength", F64); + const count: IrExpr = numLit(values.length, loc); + const first: IrExpr = + name === "push" + ? { + kind: "bin", + op: "-", + left: varRef(length.id, F64, loc), + right: count, + type: F64, + loc, + } + : numLit(0, loc); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: array.id, init: receiver, loc }, + ...inserted.stmts, + { + kind: "varDecl", + localId: length.id, + init: { + kind: "arrIntrinsic", + method: name, + receiver: arr, + args: inserted.args, + type: F64, + loc, + }, + loc, + }, + ...inserted.fill(arr, first), + ], + result: varRef(length.id, F64, loc), + type: F64, + loc, + }; + } + return { kind: "arrIntrinsic", method: name, receiver, args: inserted.args, type: F64, loc }; + } // Inserted values flow into the element slot like an assignment would: // union-element arrays wrap plain arm values (coerceInto is inert // when the types already agree). @@ -996,9 +1070,12 @@ export function lowerArrayMethodCall( const hasSpread = itemNodes.some(ts.isSpreadElement); const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg)); const statefulItems = itemProbes.some( - (probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null, + (probe) => + probe !== null && + (lowerer.runtimeOptionalWidening(probe.type, elem) !== null || + optionalElementValue(lowerer, probe, elem)), ); - if (statefulItems && itemNodes.length === 1) { + if (statefulItems && itemNodes.length === 1 && itemProbes[0]?.type.kind === "union") { return lowerOptionalSpliceItem( lowerer, receiver, @@ -1734,7 +1811,28 @@ function arraySearchHelper( type: valueT, loc, }; - const value = directNeedle ? rawRead : optionalValue; + // A direct needle matches only a present value (`match` tests the state + // first); a present undefined of a `(T | undefined)[]` (primitiveElement) + // must not be read as T. + const value = !directNeedle + ? optionalValue + : primitiveElement(elem) + ? { + kind: "ternary" as const, + cond: { + kind: "bin" as const, + op: "===" as const, + left: stateRef, + right: numLit(1, loc), + type: BOOL, + loc, + }, + then: rawRead, + else_: elementPlaceholder(elem, loc), + type: elem, + loc, + } + : rawRead; const readT = directNeedle ? elem : valueT; const equal = arraySearchEquality( lowerer, diff --git a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts index d95640978..de027e08c 100644 --- a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts +++ b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts @@ -2,6 +2,7 @@ import { keyedHelperName } from "../stable-names.js"; import * as ts from "../../ts7/adapter.js"; import type { Lowerer } from "../lowerer.js"; import { isJsSourceFile, locOf } from "../../program.js"; +import { primitiveElement } from "../array-values.js"; import { varRef } from "../../../ir/build.js"; import { typeKey } from "../../type-mapper.js"; import { @@ -144,12 +145,22 @@ export function lowerArrayCallback( const untyped = (checkerType.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0; const raw = untyped ? null : lowerer.mapTypeOf(checkerType); const mapped = raw ? lowerer.runtimeOptionalBindingType(param.name, raw) : null; + // A helper that passes present number, string or boolean elements + // (a `(T | undefined)[]` stores T elements) binds a `T | undefined` + // parameter to T, so the callback takes them without an adapter. + const presentElement = + mapped !== null && + primitiveElement(expected) && + mapped.kind === "union" && + lowerer.armTag(mapped.unionId, UNDEFINED_T) >= 0 && + typeEquals(lowerer.stripUndefinedArm(mapped), expected); if ( untyped ? !bindUntyped : mapped === null || (lowerer.coercibleValue(expected, mapped) && - !runtimeOptionalPairVariant(lowerer, expected, mapped)) + !runtimeOptionalPairVariant(lowerer, expected, mapped) && + !presentElement) ) return; const symbol = lowerer.checker.getSymbolAtLocation(param.name); diff --git a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts index e043daf68..f18291a63 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts @@ -1,4 +1,5 @@ import { keyedHelperName } from "../stable-names.js"; +import { primitiveElement } from "../array-values.js"; import { nodeThrowExpr, boolLit, countedFor, numLit, strLit, varRef } from "../../../ir/build.js"; import { InternalCompilerError } from "../../../errors.js"; import * as ts from "../../ts7/adapter.js"; @@ -1137,6 +1138,17 @@ function setFromSeedValue( seed = strCharsCall(lowerer, source, loc); } else if (source.type.kind === "array" && typeEquals(source.type.elem, setT.elem)) { seed = source; + } else if ( + source.type.kind === "array" && + primitiveElement(source.type.elem) && + setT.elem.kind === "union" && + lowerer.armTag(setT.elem.unionId, UNDEFINED_T) >= 0 && + typeEquals(lowerer.stripUndefinedArm(setT.elem), source.type.elem) + ) { + // A `(T | undefined)[]` stores T elements: its present undefined + // elements copy as the Set's undefined. + seed = lowerer.coerceToExpected(source, arrayOf(setT.elem)); + if (!typeEquals(seed.type, arrayOf(setT.elem))) return null; } else if (source.type.kind === "record") { const shape = lowerer.shapes.get(source.type.shapeId); if (!shape?.tuple || !shape.fields.every((field) => typeEquals(field.type, setT.elem))) diff --git a/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts b/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts index 668603d47..1c07885ae 100644 --- a/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts +++ b/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts @@ -1,6 +1,7 @@ import { keyedHelperName } from "../stable-names.js"; import { nullProtoRecordShape, recordNullProtoTest } from "../null-prototype.js"; import { countedFor, numLit, varRef } from "../../../ir/build.js"; +import { arrayValueStore, optionalElementValue, primitiveElement } from "../array-values.js"; import { objectEnumerationReceiver } from "../object-enumeration-receiver.js"; import { InternalCompilerError } from "../../../errors.js"; import * as ts from "../../ts7/adapter.js"; @@ -156,7 +157,14 @@ export function objectIterOverIndexShape( // also holds numbers): each overflow value checks into the element type, // so a value of a dropped arm throws instead of mistyping. let ivNarrows = false; - if (valueT) { + // `T | undefined` values into a `(T | undefined)[]`, which stores T + // elements (primitiveElement): each value is stored with its present + // undefined. + const optionalValues = + member === "values" && + valueT !== null && + optionalElementValue(lowerer, { kind: "unitLit", unit: "undefined", type: iv, loc }, valueT); + if (valueT && !optionalValues) { const ivSurfaces = typeEquals(iv, valueT) || (valueT.kind === "union" && lowerer.armTag(valueT.unionId, iv) >= 0) || @@ -350,7 +358,25 @@ export function objectIterOverIndexShape( loc, }, then: [fieldStmt], - else_: undefinedElement ? [push(pushedOf(undefinedElement))] : null, + else_: undefinedElement + ? [push(pushedOf(undefinedElement))] + : member === "values" && valueT && primitiveElement(valueT) + ? [ + { + kind: "arraySetUndefined", + arr: outRef, + index: { + kind: "arrIntrinsic", + method: "length", + receiver: outRef, + args: [], + type: F64, + loc, + }, + loc, + }, + ] + : null, loc, }; } @@ -374,35 +400,45 @@ export function objectIterOverIndexShape( const kRef = varRef("k.0", STRING, loc); const readValue: IrExpr | null = !valueT ? null - : ivNarrows + : optionalValues ? { - kind: "dynCheck", - value: { - kind: "dynFrom", - value: { - kind: "recordKeyGet", - obj: rRef, - shapeId: argIr.shapeId, - key: kRef, - overflowOnly: true, - type: iv, - loc, - }, - type: DYN, - loc, - }, - type: valueT, - loc, - } - : { kind: "recordKeyGet", obj: rRef, shapeId: argIr.shapeId, key: kRef, overflowOnly: true, - type: valueT, + type: iv, loc, - }; + } + : ivNarrows + ? { + kind: "dynCheck", + value: { + kind: "dynFrom", + value: { + kind: "recordKeyGet", + obj: rRef, + shapeId: argIr.shapeId, + key: kRef, + overflowOnly: true, + type: iv, + loc, + }, + type: DYN, + loc, + }, + type: valueT, + loc, + } + : { + kind: "recordKeyGet", + obj: rRef, + shapeId: argIr.shapeId, + key: kRef, + overflowOnly: true, + type: valueT, + loc, + }; const loopPushed: IrExpr = member === "keys" ? kRef @@ -440,7 +476,26 @@ export function objectIterOverIndexShape( }, loc, }, - push(loopPushed), + ...(optionalValues + ? [ + { kind: "varDecl" as const, localId: "v.0", init: readValue!, loc }, + arrayValueStore( + lowerer, + outRef, + { + kind: "arrIntrinsic", + method: "length", + receiver: outRef, + args: [], + type: F64, + loc, + }, + varRef("v.0", iv, loc), + valueT!, + loc, + ), + ] + : [push(loopPushed)]), ], ), { kind: "return", value: outRef, loc }, @@ -457,6 +512,7 @@ export function objectIterOverIndexShape( { id: "ks.0", name: "ks", type: ksT, mutable: false }, { id: "i.0", name: "i", type: F64, mutable: true }, { id: "k.0", name: "k", type: STRING, mutable: false }, + ...(optionalValues ? [{ id: "v.0", name: "v", type: iv, mutable: false }] : []), ], body, loc, diff --git a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts index c1b54a417..773f4cf0b 100644 --- a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts @@ -31,7 +31,7 @@ import { strCharsCall } from "../containers/array-construction.js"; import { generatorDrain } from "../iterator-adapters.js"; import { isSearchParamsIterable } from "../builtins/url.js"; import { varRef } from "../../../ir/build.js"; -import { arrayValueStore } from "../array-values.js"; +import { arrayValueStore, primitiveElement } from "../array-values.js"; import { classGeneratorIterable } from "../class-iterables.js"; /** JavaScript: whether `expr` is or holds an existing checked-dynamic @@ -102,6 +102,13 @@ function emptyNeverLiteral(lowerer: Lowerer, expr: ts.ArrayLiteralExpression): b * tsc's API doesn't surface it (ternary arms under an array context — * tsc accepts the arm covariantly, but a tagged element representation * must be BUILT as the slot's element type). */ +/** The checker type of `node` admits undefined. */ +function mayHoldUndefined(lowerer: Lowerer, node: ts.Expression): boolean { + const type = lowerer.typeOf(node); + const parts = type.isUnionType() ? ts.constituentTypes(type) : [type]; + return parts.some((part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0); +} + export function lowerArrayLiteral( lowerer: Lowerer, expr: ts.ArrayLiteralExpression, @@ -515,7 +522,13 @@ export function lowerArrayLiteral( // `[...it]` over a generator (or an iterator adapted into one): // drain it once, in order, into a fresh element array. if (src.type.kind === "generator") { - const drained = generatorDrain(lowerer, src, type.elem, locOf(el)); + const drained = generatorDrain( + lowerer, + src, + type.elem, + locOf(el), + type.optionalElements === true, + ); if (drained) src = drained; } // `[...sp]` and `[...sp.entries()]` over a URLSearchParams: its @@ -606,6 +619,9 @@ export function lowerArrayLiteral( let lowered: IrExpr; const directOptionalCandidate = runtimeOptionalElements.has(i) || + // An element that can be undefined in a `(T | undefined)[]` + // (primitiveElement) is stored as a present undefined. + (primitiveElement(type.elem) && mayHoldUndefined(lowerer, el)) || ts.isElementAccessExpression(el) || (ts.isCallExpression(el) && ts.isPropertyAccessExpression(el.expression) && @@ -683,7 +699,8 @@ export function lowerArrayLiteral( continue; } let value = el; - if (!typeEquals(value.type, type.elem)) { + // An undefined element is stored directly (arrayValueStore). + if (!typeEquals(value.type, type.elem) && value.type.kind !== "undefinedT") { const temp = lowerer.declareHiddenLocal("%arrayValue", value.type); body.push({ kind: "varDecl", localId: temp.id, init: value, loc }); value = varRef(temp.id, temp.type, loc); diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index 9ac6a04b6..70909ca7b 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -61,6 +61,8 @@ import { arrayValueType, lowerSafeBytesRead, lowerSafeIndexRead, + primitiveElement, + unionArrayValueRead, } from "../array-values.js"; import { lowerOptionalStringIndex, lowerUnionStringIndex } from "../string-index.js"; import { InternalCompilerError } from "../../../errors.js"; @@ -594,6 +596,8 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre } } if (key.type.kind === "f64") { + const optional = optionalArmIndexRead(lowerer, value, key, locOf(expr)); + if (optional) return lowerer.maybeNarrow(optional, expr); const safe = lowerOptionalArrayIndexRead(lowerer, expr, value, key); if (safe) return safe; } @@ -884,9 +888,14 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre // must be the element type (library ABI checks and the catchable // RangeError contract for an out-of-bounds assert). Where undefined is // representable, the assertion keeps JavaScript's unchecked read. + // A declared `(T | undefined)[]` element (stored as T) keeps its + // optional read: the assertion then narrows it with a catchable + // TypeError, as for a union element. + const declaredOptional = arr.type.optionalElements === true; const provenPresent = ts.isNonNullExpression(expr.parent) && expr.parent.expression === expr && + !declaredOptional && !assertedElementKeepsUndefined(lowerer, expr.parent); if (provenPresent) { return lowerer.maybeNarrow( @@ -2437,3 +2446,39 @@ export function lowerEnvironmentKey(lowerer: Lowerer, node: ts.Expression): IrEx return { kind: "libCall", fn: "dyn.toStringCoerce", args: [key], type: STRING, loc: key.loc }; return lowerRecordPropertyKey(lowerer, key, node); } + +/** A number-keyed read of a union of arrays with a `(T | undefined)[]` arm + * (stored as T elements, primitiveElement) whose element facts admit + * missing values: each arm reads its element by its state + * (unionArrayValueRead), where the keyed read would trap on a present + * undefined. The receiver and key evaluate once. Null for other unions. */ +function optionalArmIndexRead( + lowerer: Lowerer, + value: IrExpr, + key: IrExpr, + loc: SrcLoc, +): IrExpr | null { + if (value.type.kind !== "union") return null; + const arms = lowerer.unions.get(value.type.unionId)?.arms ?? []; + if ( + !arms.some( + (arm) => + arm.kind === "array" && + primitiveElement(arm.elem) && + arrayValueType(lowerer, arm.elem).kind === "union", + ) + ) + return null; + if (unionArrayValueRead(lowerer, value, key, loc) === null) return null; + const stmts: IrStmt[] = []; + const stable = (e: IrExpr, name: string): IrExpr => { + if (e.kind === "varRef" || e.kind === "numLit") return e; + const local = lowerer.declareHiddenLocal(name, e.type); + stmts.push({ kind: "varDecl", localId: local.id, init: e, loc }); + return varRef(local.id, e.type, loc); + }; + const receiver = stable(value, "%indexReceiver"); + const index = stable(key, "%indexKey"); + const read = unionArrayValueRead(lowerer, receiver, index, loc)!; + return stmts.length === 0 ? read : { kind: "seqExpr", stmts, result: read, type: read.type, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/iterator-adapters.ts b/packages/compiler/src/frontend/lowering/iterator-adapters.ts index 9f2b1e31f..4909e7f45 100644 --- a/packages/compiler/src/frontend/lowering/iterator-adapters.ts +++ b/packages/compiler/src/frontend/lowering/iterator-adapters.ts @@ -1,3 +1,4 @@ +import { appendElement, optionalElementValue, primitiveElement } from "./array-values.js"; import { keyedHelperName } from "./stable-names.js"; import { dynUndefinedExpr, varRef } from "../../ir/build.js"; import { @@ -964,16 +965,27 @@ function generatorRetypeAdapter( return { kind: "call", callee: name, args: [expr], type: expected, loc }; } +/** A checked-dynamic yielded value for a `(T | undefined)[]` of a + * number, string or boolean T: converted to `T | undefined`, which the + * drain stores as a T element or a present undefined. */ +function optionalYield(lowerer: Lowerer, value: IrExpr, elem: IrType, optional: boolean): IrExpr { + if (!optional || value.type.kind !== "dyn" || !primitiveElement(elem)) return value; + return lowerer.coerceToExpected(value, lowerer.withUndefinedArm(elem)); +} + /** Drain a synchronous generator into a fresh `elem[]` — the eager * consumers of an iterator (`[...it]`, `Array.from(it)`). Each step resumes * the generator once and appends its value; completion stops the drain * without appending the return value, as in JS. Null when the yielded - * values cannot be extracted into the element type. */ + * values cannot be extracted into the element type. `optional`: the + * result is a `(T | undefined)[]`, which stores T elements, so a + * checked-dynamic value may also be undefined. */ export function generatorDrain( lowerer: Lowerer, source: IrExpr, elem: IrType, loc: SrcLoc, + optional = false, ): IrExpr | null { const gen = source.type; if (gen.kind !== "generator" || gen.async || gen.yieldT.kind === "void") return null; @@ -982,14 +994,16 @@ export function generatorDrain( const valueT = shape?.fields.find((field) => field.name === "value")?.type; if (!resultType || !valueT) return null; const arrT: IrType = { kind: "array", elem }; - const key = `genDrain:${typeKey(gen)}:${typeKey(elem)}`; + const key = `genDrain:${typeKey(gen)}:${typeKey(elem)}${optional ? "?" : ""}`; let name = lowerer.valueHelpers.get(key); if (!name && stepDrivable(gen)) { // The record-free drive: test done, then take the yielded value. const genRef = varRef("gen.0", gen, loc) as IrExpr & { type: typeof gen }; const outRef = varRef("out.0", arrT, loc); - const value = lowerer.coerceToExpected(genValue(genRef, loc), elem); - if (!typeEquals(value.type, elem)) return null; + const yielded = optionalYield(lowerer, genValue(genRef, loc), elem, optional); + const present = optionalElementValue(lowerer, yielded, elem); + const value = present ? yielded : lowerer.coerceToExpected(yielded, elem); + if (!present && !typeEquals(value.type, elem)) return null; name = keyedHelperName(lowerer, "%gen.drain", key); lowerer.valueHelpers.set(key, name); lowerer.liftedFns.push({ @@ -999,6 +1013,7 @@ export function generatorDrain( locals: [ { id: "gen.0", name: "gen", type: gen, mutable: false }, { id: "out.0", name: "out", type: arrT, mutable: false }, + ...(present ? [{ id: "v.0", name: "v", type: value.type, mutable: false }] : []), ], body: [ { @@ -1010,20 +1025,7 @@ export function generatorDrain( { kind: "while", cond: { kind: "unary", op: "!", operand: genStep(genRef, loc), type: BOOL, loc }, - body: [ - { - kind: "exprStmt", - expr: { - kind: "arrIntrinsic", - method: "push", - receiver: outRef, - args: [value], - type: F64, - loc, - }, - loc, - }, - ], + body: appendElement(lowerer, outRef, value, elem, "v.0", loc), loc, }, { kind: "return", value: outRef, loc }, @@ -1050,8 +1052,10 @@ export function generatorDrain( loc, ); if (!raw) return null; - const value = lowerer.coerceToExpected(raw, elem); - if (!typeEquals(value.type, elem)) return null; + const yielded = optionalYield(lowerer, raw, elem, optional); + const present = optionalElementValue(lowerer, yielded, elem); + const value = present ? yielded : lowerer.coerceToExpected(yielded, elem); + if (!present && !typeEquals(value.type, elem)) return null; name = keyedHelperName(lowerer, "%gen.drain", key); lowerer.valueHelpers.set(key, name); lowerer.liftedFns.push({ @@ -1062,6 +1066,7 @@ export function generatorDrain( { id: "gen.0", name: "gen", type: gen, mutable: false }, { id: "out.0", name: "out", type: arrT, mutable: false }, { id: "step.0", name: "step", type: resultType, mutable: false }, + ...(present ? [{ id: "v.0", name: "v", type: value.type, mutable: false }] : []), ], body: [ { @@ -1101,18 +1106,7 @@ export function generatorDrain( else_: null, loc, }, - { - kind: "exprStmt", - expr: { - kind: "arrIntrinsic", - method: "push", - receiver: outRef, - args: [value], - type: F64, - loc, - }, - loc, - }, + ...appendElement(lowerer, outRef, value, elem, "v.0", loc), ], loc, }, diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index af89c61a4..2ddf02dfc 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6515,6 +6515,17 @@ export class Lowerer { mayBeUndefined: (node) => { if (explicitlyNonNull(node)) return assertedReadMayBeUndefined(node); if (mayBeOptional(node)) return true; + // A value the checker types as possibly undefined: `(T | undefined)[]` + // stores T elements (the type mapper), so its undefined values are + // facts of T's arrays. + const checked = typeOrNull(node); + if ( + checked !== null && + (checked.isUnionType() ? ts.constituentTypes(checked) : [checked]).some( + (part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0, + ) + ) + return true; // `undefined as unknown as T` keeps the undefined value. for ( let inner: ts.Expression = node; @@ -6533,7 +6544,37 @@ export class Lowerer { const symbol = symbolOf(e); return symbol !== null && iterationSymbols.has(symbol) && !provenPresent(e, symbol); }, - callbackReturnsUndefined: (node) => callbackReturnsOptional(node), + iterationMayYieldUndefined: (node) => { + const type = typeOrNull(node); + if (type === null || this.checker.isArrayType(type) || this.checker.isTupleType(type)) + return false; + // The first type argument of Set, Map and Set iterators, + // generators and iterables is the value they yield. + const element = + (type.flags & ts.TypeFlags.Object) !== 0 + ? panicSafe(() => this.checker.getTypeArguments(type as ts.TypeReference)[0], undefined) + : undefined; + if (element === undefined) return false; + return (element.isUnionType() ? ts.constituentTypes(element) : [element]).some( + (part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0, + ); + }, + callbackReturnsUndefined: (node) => { + if (callbackReturnsOptional(node)) return true; + // A callback the checker types as possibly returning undefined + // (`(m) => map.get(m)`): its results are present undefined + // elements of a `(T | undefined)[]`, which stores T elements. + const type = typeOrNull(node); + return ( + type !== null && + this.checker.getCallSignatures(type).some((signature) => { + const result = this.checker.getReturnTypeOfSignature(signature); + return (result.isUnionType() ? ts.constituentTypes(result) : [result]).some( + (part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0, + ); + }) + ); + }, isGlobal: (node, name) => this.isStdlibGlobal(node, name), isString: (node) => { const type = typeOrNull(node); @@ -6594,6 +6635,7 @@ export class Lowerer { const cached = scanSites.get(sf); if (cached !== undefined) return cached; const nodes: ts.Node[] = []; + const values: ts.Node[] = []; ts.walkPreorder(sf, (node) => { if ( ts.isVariableDeclaration(node) || @@ -6609,7 +6651,21 @@ export class Lowerer { ts.isPostfixUnaryExpression(node) ) nodes.push(node); + // The values these sites store, whose checker types mayBeUndefined + // reads: batched here, since dead bodies are scanned too. + if (ts.isArrayLiteralExpression(node)) + for (const element of node.elements.nodes) + values.push(ts.isSpreadElement(element) ? element.expression : element); + else if (ts.isCallExpression(node) || ts.isNewExpression(node)) + for (const arg of node.arguments?.nodes ?? []) + values.push(ts.isSpreadElement(arg) ? arg.expression : arg); + else if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) + values.push(node.right); }); + this.checker.prefetchCollectionTypes(values); scanSites.set(sf, nodes); return nodes; }; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index 96d20d2c4..fffe79a51 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -16,6 +16,9 @@ export interface ElementStateHost { elementAdmitsUndefined(key: string): boolean; /** The value stored by `node` can be `undefined` at runtime. */ mayBeUndefined(node: ts.Expression): boolean; + /** Iterating `node`, an iterable other than an array (a Set, a Map's + * values, a generator), can yield `undefined`. */ + iterationMayYieldUndefined?(node: ts.Expression): boolean; /** A callback argument can return `undefined` at runtime. */ callbackReturnsUndefined(node: ts.Expression): boolean; /** The value may be a plain array-like object, whose missing indices @@ -353,7 +356,11 @@ export class ArrayElementStates { /** One stored value, or the elements a spread copies. */ private valueMayBeUndefined(value: ts.Expression): boolean { - if (ts.isSpreadElement(value)) return this.mayYieldUndefined(value.expression); + if (ts.isSpreadElement(value)) + return ( + this.mayYieldUndefined(value.expression) || + (this.host.iterationMayYieldUndefined?.(value.expression) ?? false) + ); return this.host.mayBeUndefined(value); } diff --git a/packages/compiler/src/frontend/lowering/sanitize-class-types.ts b/packages/compiler/src/frontend/lowering/sanitize-class-types.ts index 77478d76d..6b89b446a 100644 --- a/packages/compiler/src/frontend/lowering/sanitize-class-types.ts +++ b/packages/compiler/src/frontend/lowering/sanitize-class-types.ts @@ -16,6 +16,8 @@ export function sanitizeUnregisteredClassTypes( case "object": return hasClass(type.className) ? type : F64; case "array": + type.elem = rewrite(type.elem); + break; case "set": type.elem = rewrite(type.elem); break; diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 161f73d27..16b77b7f5 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -2341,6 +2341,23 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { elem = unitOnlyUnion(unions); if (!elem) return null; + // `(T | undefined)[]` of a number, string or boolean T stores T + // elements: an array slot holds a present undefined by its own state, + // so a T[] seen as `(T | undefined)[]` stays the same array. + let optionalElements = false; + if (elem.kind === "union") { + const arms = unions.get(elem.unionId)?.arms; + const present = + arms?.length === 2 ? arms.find((arm) => arm.kind !== "undefinedT") : undefined; + if ( + present !== undefined && + arms!.some((arm) => arm.kind === "undefinedT") && + (present.kind === "f64" || present.kind === "string" || present.kind === "bool") + ) { + elem = present; + optionalElements = true; + } + } // A dyn ELEMENT makes the WHOLE array the checked-dynamic value: // `unknown[]`, `object[]`, and the collapsed `(string | object)[]` // (the plugins-slot shape) — the checked-dynamic tree has real arrays, so length/ @@ -2362,7 +2379,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // drain of the auxiliary-server registries): handles are ordinary // refcounted REF elements (their `_v` adapters, no trace — both drop // their closures at their terminal event). - return arrayOf(elem); + return optionalElements ? { kind: "array", elem, optionalElements: true } : arrayOf(elem); } // Tuples: `[string, string]` maps to an interned RECORD shape flagged // `tuple`, with one field per position named by its index ("0", "1", ...) diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 8fb636608..58edd52d3 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -87,7 +87,16 @@ export type IrType = | { kind: "date" } | { kind: "string" } // heap, refcounted, UTF-8 | { kind: "bool" } - | { kind: "array"; elem: IrType } // heap, refcounted, monomorphic elements + | { + kind: "array"; + elem: IrType; + /** A `(T | undefined)[]` of a number, string or boolean T, stored as + * T elements (which hold a present undefined by their state). Not + * part of the type's identity (typeEquals, typeKey): it only lets a + * checked conversion from a dynamic value accept undefined + * elements. */ + optionalElements?: true; + } // heap, refcounted, monomorphic elements /** ES `Map` — heap, refcounted, insertion-ordered hash map with ONE * runtime representation (ScrMap) and type-directed key/value handling, * exactly the array pattern (never per-instantiation structs). Keys use diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index a694e8d2c..b9996fc6c 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -2050,12 +2050,18 @@ function validateFunction( } case "arrayNewLen": { // Mapper-less Array.from({length: n}): absent-slot fill exists - // only for refcounted element kinds (frontend fences scalars). + // only for refcounted element kinds. Number and boolean slots are + // filled with present undefined values right after + // (`(T | undefined)[]`); the frontend fences other scalar uses. if (e.type.kind !== "array") { err(`arrayNewLen must be array-typed, got ${e.type.kind}`, e.loc); break; } - if (!isRefCounted(e.type.elem)) { + if ( + !isRefCounted(e.type.elem) && + e.type.elem.kind !== "f64" && + e.type.elem.kind !== "bool" + ) { err( `arrayNewLen with non-refcounted ${e.type.elem.kind} elements (no absent value)`, e.loc, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8000-optional-element-arrays-share-storage.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8000-optional-element-arrays-share-storage.ts.json new file mode 100644 index 000000000..8cdd32baf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8000-optional-element-arrays-share-storage.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8000-optional-element-arrays-share-storage.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8001-optional-element-writers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8001-optional-element-writers.ts.json new file mode 100644 index 000000000..fe45249bd --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8001-optional-element-writers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8001-optional-element-writers.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8000-optional-element-arrays-share-storage.ts b/tests/corpus/8000-optional-element-arrays-share-storage.ts new file mode 100644 index 000000000..fa31fcad2 --- /dev/null +++ b/tests/corpus/8000-optional-element-arrays-share-storage.ts @@ -0,0 +1,61 @@ +// A number[] or string[] seen as `(T | undefined)[]` is the same array, as +// in Node: undefined written through either name is visible through both, +// whether the wider view is a cast, a binding or a parameter. + +const c: number[] = [1.5, 2]; +(c as (number | undefined)[])[1] = undefined; +console.log(c); +const e: number[] = [1.5, 2]; +const f = e as (number | undefined)[]; +f[1] = undefined; +console.log(e, f, e === f); + +function clearFirst(values: (number | undefined)[]): void { + values[0] = undefined; + values.push(undefined, 4); +} +const a: number[] = [1, 2, 3]; +clearFirst(a); +console.log(a, a.length, a.indexOf(4), a.includes(undefined as unknown as number)); + +const names: string[] = ["x"]; +const wider: (string | undefined)[] = names; +wider.push(undefined); +wider.unshift(undefined); +console.log(names, wider === names, names.length, JSON.stringify(names)); + +// The wider arrays themselves: literals, pushes, splices and copies keep +// their undefined elements. +const lit: (number | undefined)[] = [1, undefined, 3]; +const maybe = (i: number): number | undefined => (i % 2 ? i : undefined); +const built: (number | undefined)[] = []; +for (let i = 0; i < 4; i++) built.push(maybe(i)); +built.splice(1, 0, undefined); +console.log(lit, built, lit.indexOf(undefined), built.filter((x): boolean => x === undefined).length); +console.log(built.map((x) => x ?? -1), built.find((x) => x !== undefined), [...built].reverse()); +let sum = 0; +for (const x of built) sum += x ?? 0; +console.log(sum, built.join("-"), JSON.stringify(built)); + +const flags: (boolean | undefined)[] = [true, undefined, false]; +const strs: (string | undefined)[] = ["a", undefined]; +console.log(flags, flags.filter(Boolean).length, strs.map((s) => s?.toUpperCase())); + +// Values from other containers keep their undefined. +const m = new Map([["a", undefined], ["b", 2]]); +const fromMap = [...m.values()]; +const fromSet = Array.from(new Set(["s", undefined])); +const sized = Array.from({ length: 2 }); +console.log(fromMap, fromSet, sized, sized.length); +const dict: { [key: string]: number | undefined } = { a: 1, b: undefined }; +console.log(Object.values(dict)); + +// An asserted element that is undefined throws a catchable TypeError. +function lengthAt(items: (string | undefined)[], i: number): number { + try { + return items[i]!.length; + } catch { + return -1; + } +} +console.log(lengthAt(strs, 0), lengthAt(strs, 1)); diff --git a/tests/corpus/8001-optional-element-writers.ts b/tests/corpus/8001-optional-element-writers.ts new file mode 100644 index 000000000..5dff693c3 --- /dev/null +++ b/tests/corpus/8001-optional-element-writers.ts @@ -0,0 +1,103 @@ +// Every way a value reaches a `(T | undefined)[]` of numbers, strings or +// booleans keeps its undefined: callbacks, generators, Array.of, rest +// packs, destructuring, class fields and copies into Sets and Maps. + +const slots = new Map([["a", 1]]); +const looked = ["x", "a", "y"].map((key) => slots.get(key)); +console.log(looked[0] === undefined, looked, looked.length); +const fromMapped = Array.from(["a", "b"], (key) => slots.get(key)); +console.log(fromMapped[1] === undefined, fromMapped); +const objects: { n?: number }[] = [{ n: 1 }, {}]; +const fields = objects.map((o) => o.n); +function pick(o: { n?: number }): number | undefined { + return o.n; +} +console.log(fields[1] === undefined, objects.map(pick)[1] === undefined, fields); +function wrap(value: T): T[] { + return [value]; +} +console.log(wrap(undefined)[0] === undefined); + +function* maybe(): Generator { + yield undefined; + yield 2; +} +const drained = [...maybe()]; +console.log(drained[0] === undefined, drained, Array.from(maybe())); +const yielding = function* (): Generator { + yield "s"; + yield undefined; +}; +console.log([...yielding()]); +console.log(Array.of(undefined, 1), Array.of(true)); + +function sum(...values: (number | undefined)[]): number { + return values.reduce((total, v) => total + (v ?? 0), 0); +} +const spread: (number | undefined)[] = [undefined, 4]; +console.log(sum(1, undefined, 2), sum(...spread), sum(...spread, undefined, 1)); + +const [first, second = 7, third] = [3, undefined, 1] as (number | undefined)[]; +console.log(first, second, third); +const strings: (string | undefined)[] = ["a", undefined, "a"]; +const seen = new Set(strings); +console.log(seen.size, seen.has(undefined), [...seen]); +for (const [i, s] of strings.entries()) console.log(i, s); +for (const key in strings) console.log("key", key); + +class Holder { + values: (number | undefined)[] = []; + add(value: number | undefined): void { + this.values.push(value); + } + first(): number | undefined { + return this.values[0]; + } +} +const holder = new Holder(); +holder.add(undefined); +holder.add(2); +console.log(holder.first(), holder.values, holder.values.length); + +const xs: (number | undefined)[] = [3, undefined, 1]; +xs.forEach((x, i) => console.log(i, x, typeof x)); +console.log(xs.findIndex((x) => x === undefined), xs.findLast((x) => x !== undefined)); +console.log(xs.some((x) => x === undefined), xs.every((x) => x !== undefined)); +console.log(xs.reduce((acc, x) => acc + (x ?? 10), 0), xs.join("/"), xs.at(1), xs.at(-1)); +console.log(xs.toSorted((a, b) => (a ?? 0) - (b ?? 0)), [...xs].sort()); +xs[1] ??= 5; +console.log(xs); +const flags: (boolean | undefined)[] = [undefined, true]; +console.log(flags.map((b) => !b), flags.indexOf(undefined), flags.includes(true), String(flags)); +const nested: (number | undefined)[][] = [[undefined], [1]]; +console.log(nested.flat(), nested[0]![0]); +const byKey = new Map([["k", [undefined, 1]]]); +console.log(byKey.get("k")![0], byKey); + +// Promise.all over `Promise[]` settles on Node's turn, with +// its undefined values. +const lookup = async (key: string): Promise => { + if (key === "x") throw new Error("missing " + key); + return slots.get(key); +}; +const turns: string[] = []; +const pending: Promise[] = [lookup("a"), lookup("b")]; +void Promise.all(pending).then((vs) => turns.push("all " + vs.join(",") + " " + (vs[1] === undefined))); +const failing: Promise[] = [lookup("a"), lookup("x")]; +Promise.all(failing).then( + () => turns.push("unexpected"), + (error: unknown) => turns.push("rejected " + (error as Error).message), +); +void Promise.all([] as Promise[]).then((vs) => turns.push("empty " + vs.length)); +void Promise.resolve() + .then(() => turns.push("t1")) + .then(() => turns.push("t2")) + .then(() => turns.push("t3")) + .then(() => console.log(turns.join(" "))); + +// Index reads through a union of arrays, and searches with a number +// needle, see the present undefined elements. +const pairs: ((null | undefined)[] | (number | undefined)[])[] = [[null, undefined], [undefined, 0]]; +for (const pair of pairs) console.log(pair[0], pair[1], pair[2]); +const sparse: (number | undefined)[] = [1, , undefined, 1]; +console.log(sparse.lastIndexOf(undefined), sparse.lastIndexOf(1, 2), sparse.indexOf(1, 1)); diff --git a/tests/harness/corpus-costs/8000-optional-element-arrays-share-storage.ts.json b/tests/harness/corpus-costs/8000-optional-element-arrays-share-storage.ts.json new file mode 100644 index 000000000..b3db55643 --- /dev/null +++ b/tests/harness/corpus-costs/8000-optional-element-arrays-share-storage.ts.json @@ -0,0 +1 @@ +{ "plain": 6206, "san": 8487 } diff --git a/tests/harness/corpus-costs/8001-optional-element-writers.ts.json b/tests/harness/corpus-costs/8001-optional-element-writers.ts.json new file mode 100644 index 000000000..31695bc47 --- /dev/null +++ b/tests/harness/corpus-costs/8001-optional-element-writers.ts.json @@ -0,0 +1 @@ +{ "plain": 3690, "san": 4000 } From b6050f96bd04ea373b5ea18f4f8c3581a19d5c83 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 06:40:00 -0500 Subject: [PATCH 020/230] Read the runtime ABI marker in the native toolchain fixture from the compiler - The native toolchain test's fixture runtime pack hard-coded ABI v37, so with this branch's marker (v40) the toolchain refused the pack and the test saw exit status 1. It now takes RUNTIME_ABI_VERSION and RUNTIME_ABI_MARKER from runtime-abi.ts, as the native driver test does. --- tests/harness/self-hosting-native-toolchain.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index c5476328e..4b4af5ac6 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -9,6 +9,10 @@ import { nativeCodegenTarget } from "../../packages/compiler/src/backend/targets import type { NativeLinkFeatures } from "../../packages/compiler/src/backend/native-link-info.js"; import type { RuntimePackManifest } from "../../packages/compiler/src/backend/runtime-pack-core.js"; import type { NativeToolchainManifest } from "../../packages/compiler/src/native/toolchain.js"; +import { + RUNTIME_ABI_MARKER, + RUNTIME_ABI_VERSION, +} from "../../packages/compiler/src/backend/runtime-abi.js"; const root = join(import.meta.dirname, "../.."); const entry = join(root, "tests/fixtures/self-hosting/native-toolchain.ts"); @@ -109,7 +113,7 @@ for (const backend of ["llvm"] as const) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 37, marker: "scr_runtime_abi_v37" }, + runtime_abi: { version: RUNTIME_ABI_VERSION, marker: RUNTIME_ABI_MARKER }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: [], sanitizer: "external-toolchain-required" }, flavors: { From 878f03288630b2097c7102de0146e016214d74f9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 08:35:30 -0500 Subject: [PATCH 021/230] Crypto parity: RSA key generation lengths, RSA-PSS keys and encrypted private keys --- .../limitations/builtin-apis/crypto.mdx | 9 +- .../ambient/scriptc-node-fallback.d.ts | 31 +- packages/compiler/src/ir/runtime-features.ts | 11 +- .../corpus/8165-rsa-keygen-lengths.cjs.json | 6 + .../tests/corpus/8166-rsa-pss-keys.cjs.json | 6 + .../8167-encrypted-private-keys.cjs.json | 6 + packages/runtime/src/scr_cipher.c | 45 +- packages/runtime/src/scr_cipher_names.h | 100 ++ packages/runtime/src/scr_lib.c | 5 + packages/runtime/src/scr_lib.h | 3 + packages/runtime/src/scr_pki.c | 1387 ++++++++++++++++- packages/runtime/src/scr_webcrypto_pki.c | 3 +- tests/corpus/8165-rsa-keygen-lengths.cjs | 40 + tests/corpus/8166-rsa-pss-keys.cjs | 104 ++ tests/corpus/8167-encrypted-private-keys.cjs | 114 ++ .../8165-rsa-keygen-lengths.cjs.json | 1 + .../corpus-costs/8166-rsa-pss-keys.cjs.json | 1 + .../8167-encrypted-private-keys.cjs.json | 1 + 18 files changed, 1787 insertions(+), 86 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8165-rsa-keygen-lengths.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8166-rsa-pss-keys.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8167-encrypted-private-keys.cjs.json create mode 100644 packages/runtime/src/scr_cipher_names.h create mode 100644 tests/corpus/8165-rsa-keygen-lengths.cjs create mode 100644 tests/corpus/8166-rsa-pss-keys.cjs create mode 100644 tests/corpus/8167-encrypted-private-keys.cjs create mode 100644 tests/harness/corpus-costs/8165-rsa-keygen-lengths.cjs.json create mode 100644 tests/harness/corpus-costs/8166-rsa-pss-keys.cjs.json create mode 100644 tests/harness/corpus-costs/8167-encrypted-private-keys.cjs.json diff --git a/docs/content/limitations/builtin-apis/crypto.mdx b/docs/content/limitations/builtin-apis/crypto.mdx index 329aa6809..bbe9cca21 100644 --- a/docs/content/limitations/builtin-apis/crypto.mdx +++ b/docs/content/limitations/builtin-apis/crypto.mdx @@ -31,18 +31,21 @@ `checkPrime`, `checkPrimeSync`, `generatePrime`, and `generatePrimeSync` support bigint, ArrayBuffer, typed-array, Buffer, and DataView values and the `checks`, `safe`, `add`, `rem`, and `bigint` options on native targets, with Node's errors; the callback forms compute before they return and call back in a later macrotask. Candidates and sizes longer than 262144 bits throw a coded SC2020 error, and wasm32-wasi builds are refused. Node calls their callbacks with `undefined` as the error, which a callback whose error parameter is typed `Error | null`, as @types/node declares it, receives as `null`. - RSA, EC, Ed25519, and X25519 private and public KeyObjects come from `generateKeyPairSync`, `generateKeyPair`, `createPrivateKey`, and `createPublicKey`, on native targets. + RSA, RSA-PSS, EC, Ed25519, and X25519 private and public KeyObjects come from `generateKeyPairSync`, `generateKeyPair`, `createPrivateKey`, and `createPublicKey`, on native targets. `generateKeyPairSync` and `generateKeyPair` check their arguments in Node's order, with Node's errors, including the OpenSSL errors for RSA modulus lengths below 512 or above 2147483647 bits and for public exponents of 0, 1, or an even number, which `generateKeyPair` passes to its callback. - RSA keys generate with even modulus lengths from 1024 to 8192 bits and public exponents up to 2147483647; other lengths and exponents that Node accepts, and `paramEncoding: 'explicit'`, throw a coded SC2020 error. + RSA keys generate with modulus lengths from 512 to 8192 bits, an odd length from 2048 bits yielding one bit less as in Node, and odd public exponents up to 4294967295; longer moduli that Node accepts, and `paramEncoding: 'explicit'`, throw a coded SC2020 error. The EC curves are prime256v1 (P-256), secp384r1 (P-384), secp521r1 (P-521), and secp256k1. Keys read and write PEM and DER as PKCS#1, SEC1, PKCS#8, and SubjectPublicKeyInfo, and JWK (Ed25519 and X25519 keys use PKCS#8, SubjectPublicKeyInfo, and JWK); `createPublicKey` also takes a private key or an X.509 certificate. EC, Ed25519, and X25519 keys also read and write the `raw-public` and `raw-private` formats, including compressed EC points. Raw X448, Ed448, and post-quantum keys, EC scalars of 0 or at least the curve order, and the EC point at infinity, which Node accepts, throw a coded SC2020 error. They support `type`, `asymmetricKeyType`, `asymmetricKeyDetails`, `export()`, `equals()`, and `instanceof KeyObject`. `crypto.sign`, `crypto.verify`, `createSign`, and `createVerify` support RSA PKCS#1 v1.5 and PSS padding and ECDSA with DER and IEEE P1363 encodings over SHA-1 and SHA-2 digests, and Ed25519 through `crypto.sign` and `crypto.verify` with a `null` or `undefined` algorithm. + RSA-PSS keys follow their RSASSA-PSS-params as Node does: PSS padding only, the key's digest and MGF1 digest, and salts of at least the key's length, with OpenSSL's errors; their details name the parameters, they export as PKCS#8 and SubjectPublicKeyInfo only, and digests other than SHA-1 and SHA-2 in their options throw a coded SC2020 error. Ed25519 signing, verification, and private keys link when a string literal in the program names `ed25519` (in any case); without one they throw a coded SC2020 error, while Ed25519 public keys still import and export. Sign and Verify values accumulate `update()` data and are not writable streams. - Encrypted private keys (`passphrase`, `cipher`), the other key types (`rsa-pss`, `dsa`, `ed448`, `x448`, and the rest), and wasm32-wasi builds throw a coded SC2020 error or are refused. + Private keys export encrypted (`cipher`, `passphrase`) with aes-128, aes-192, and aes-256 in CBC and ECB modes and with des-ede3-cbc, PKCS#8 as PBES2 with PBKDF2-HMAC-SHA256 and PKCS#1 and SEC1 as traditional PEM, and the ciphers OpenSSL refuses for a form throw its errors; a cipher option can also be an OID; other ciphers Node encrypts with throw a coded SC2020 error. + Encrypted private keys import with a `passphrase` from PBES2 with HMAC-SHA PRFs and the CBC and ECB ciphers, and from traditional PEM with the CBC ciphers, with Node's errors for a missing or wrong passphrase; other schemes and ciphers throw a coded SC2020 error once a passphrase is given. + The other key types (`dsa`, `ed448`, `x448`, and the rest) and wasm32-wasi builds throw a coded SC2020 error or are refused. Comparing keys of different types with `equals()` answers false; Node also leaves an OpenSSL error queued, which a later key operation can report. `publicEncrypt`/`privateDecrypt` remain unsupported. The WebCrypto `crypto` global, which node:crypto also exports as `webcrypto`, supports `randomUUID()` and `getRandomValues()`, also read as values from untyped JavaScript. diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 1fa787e35..2c056c148 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -2914,6 +2914,8 @@ declare module "crypto" { export interface AsymmetricKeyExportOptions { type: "pkcs1" | "spki" | "pkcs8" | "sec1"; format: T; + cipher?: string | undefined; + passphrase?: string | Buffer | undefined; } export interface AsymmetricKeyDetails { modulusLength?: number; @@ -2938,6 +2940,7 @@ declare module "crypto" { format?: "pem" | "der" | undefined; type?: "pkcs1" | "pkcs8" | "sec1" | undefined; encoding?: string | undefined; + passphrase?: string | Buffer | undefined; } export interface PublicKeyInput { key: string | Buffer; @@ -2974,12 +2977,22 @@ declare module "crypto" { modulusLength: number; publicExponent?: number | undefined; publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; - privateKeyEncoding: { type: "pkcs1" | "pkcs8"; format: PrivF }; + privateKeyEncoding: { + type: "pkcs1" | "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; } export interface ECKeyPairOptions { namedCurve: string; publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; - privateKeyEncoding: { type: "sec1" | "pkcs8"; format: PrivF }; + privateKeyEncoding: { + type: "sec1" | "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; } export function generateKeyPairSync( type: "rsa", @@ -3007,7 +3020,12 @@ declare module "crypto" { ): KeyPairKeyObjectResult; export interface ED25519KeyPairOptions { publicKeyEncoding: { type: "spki"; format: PubF }; - privateKeyEncoding: { type: "pkcs8"; format: PrivF }; + privateKeyEncoding: { + type: "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; } export function generateKeyPairSync( type: "ed25519", @@ -3020,7 +3038,12 @@ declare module "crypto" { export function generateKeyPairSync(type: "ed25519", options?: {}): KeyPairKeyObjectResult; export interface X25519KeyPairOptions { publicKeyEncoding: { type: "spki"; format: PubF }; - privateKeyEncoding: { type: "pkcs8"; format: PrivF }; + privateKeyEncoding: { + type: "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; } export function generateKeyPairSync( type: "x25519", diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 85b21af7b..1cae265d5 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -875,10 +875,11 @@ const RUNTIME_FEATURE_RULES = { mod.functions.some( (fn) => fn.async === true && fn.generator === undefined && fn.returnType.kind === "dyn", ), - // The legacy url functions, crypto.createCipheriv and - // ecdh.setPublicKey emit their deprecation warnings through the - // dyn-async unit (WebCrypto's encrypt and decrypt link scr_cipher.c - // too); scr_wasm.c boxes its handles and promises through it. + // The legacy url functions, crypto.createCipheriv, + // ecdh.setPublicKey and generateKeyPair's rsa-pss options emit their + // deprecation warnings through the dyn-async unit (WebCrypto's + // encrypt and decrypt link scr_cipher.c too); scr_wasm.c boxes its + // handles and promises through it. fns: [ ...DYN_ASYNC_LIB_FNS, ...READABLE_DYN_ASYNC_LIB_FNS, @@ -889,6 +890,8 @@ const RUNTIME_FEATURE_RULES = { "webcrypto.wrapKey", "webcrypto.unwrapKey", "crypto.ecdhNew", + "crypto.generateKeyPairCb", + "crypto.generateKeyPairSync", ], prefixes: [...READABLE_DYN_ASYNC_PREFIXES, "wasm."], expr: (node, tables) => diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8165-rsa-keygen-lengths.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8165-rsa-keygen-lengths.cjs.json new file mode 100644 index 000000000..45346fc86 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8165-rsa-keygen-lengths.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8165-rsa-keygen-lengths.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8166-rsa-pss-keys.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8166-rsa-pss-keys.cjs.json new file mode 100644 index 000000000..623419ef3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8166-rsa-pss-keys.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8166-rsa-pss-keys.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8167-encrypted-private-keys.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8167-encrypted-private-keys.cjs.json new file mode 100644 index 000000000..e5e03f8e9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8167-encrypted-private-keys.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8167-encrypted-private-keys.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_cipher.c b/packages/runtime/src/scr_cipher.c index a2db11434..33780fdfc 100644 --- a/packages/runtime/src/scr_cipher.c +++ b/packages/runtime/src/scr_cipher.c @@ -12,6 +12,7 @@ * compared in constant time. A cipher used as a stream reads and writes * through its own native Transform, which the stream unit builds on first * use, as Node's LazyTransform is. All arguments are borrowed. */ +#include "scr_cipher_names.h" #include "scr_runtime.h" #include @@ -114,52 +115,12 @@ static const ScCipherSpec sc_specs[] = { static const struct { const char *name; unsigned iv_len; -} sc_legacy[] = { - {"des", 8}, {"des-cbc", 8}, {"des-ecb", 0}, {"des-cfb", 8}, {"des-cfb1", 8}, {"des-cfb8", 8}, - {"des-ofb", 8}, {"desx", 8}, {"desx-cbc", 8}, {"bf", 8}, {"bf-cbc", 8}, {"bf-ecb", 0}, - {"bf-cfb", 8}, {"bf-ofb", 8}, {"blowfish", 8}, {"cast", 8}, {"cast-cbc", 8}, {"cast5-cbc", 8}, - {"cast5-ecb", 0}, {"cast5-cfb", 8}, {"cast5-ofb", 8}, {"rc2", 8}, {"rc2-cbc", 8}, {"rc2-ecb", 0}, - {"rc2-cfb", 8}, {"rc2-ofb", 8}, {"rc2-40-cbc", 8}, {"rc2-64-cbc", 8}, {"rc4", 0}, {"rc4-40", 0}, - {"rc4-hmac-md5", 0}, {"seed", 16}, {"seed-cbc", 16}, {"seed-ecb", 0}, {"seed-cfb", 16}, - {"seed-ofb", 16}, {"idea", 8}, {"idea-cbc", 8}, {"idea-ecb", 0}, {"idea-cfb", 8}, {"idea-ofb", 8}, -}; +} sc_legacy[] = {SCR_CIPHER_LEGACY_ENTRIES}; /* crypto.getCiphers() on Node 24.22 (OpenSSL 3.5). Names in this list that * have no implementation throw a coded SC2020 error instead of Node's * ERR_CRYPTO_UNKNOWN_CIPHER. */ -static const char *const sc_known[] = { - "aes-128-cbc", "aes-128-cbc-cts", "aes-128-ccm", "aes-128-cfb", "aes-128-cfb1", "aes-128-cfb8", - "aes-128-ctr", "aes-128-ecb", "aes-128-gcm", "aes-128-gcm-siv", "aes-128-ocb", "aes-128-ofb", - "aes-128-siv", "aes-128-wrap", "aes-128-wrap-inv", "aes-128-wrap-pad", "aes-128-wrap-pad-inv", - "aes-128-xts", "aes-192-cbc", "aes-192-cbc-cts", "aes-192-ccm", "aes-192-cfb", "aes-192-cfb1", - "aes-192-cfb8", "aes-192-ctr", "aes-192-ecb", "aes-192-gcm", "aes-192-gcm-siv", "aes-192-ocb", - "aes-192-ofb", "aes-192-siv", "aes-192-wrap", "aes-192-wrap-inv", "aes-192-wrap-pad", - "aes-192-wrap-pad-inv", "aes-256-cbc", "aes-256-cbc-cts", "aes-256-ccm", "aes-256-cfb", - "aes-256-cfb1", "aes-256-cfb8", "aes-256-ctr", "aes-256-ecb", "aes-256-gcm", "aes-256-gcm-siv", - "aes-256-ocb", "aes-256-ofb", "aes-256-siv", "aes-256-wrap", "aes-256-wrap-inv", - "aes-256-wrap-pad", "aes-256-wrap-pad-inv", "aes-256-xts", "aes128", "aes128-wrap", - "aes128-wrap-inv", "aes128-wrap-pad", "aes128-wrap-pad-inv", "aes192", "aes192-wrap", - "aes192-wrap-inv", "aes192-wrap-pad", "aes192-wrap-pad-inv", "aes256", "aes256-wrap", - "aes256-wrap-inv", "aes256-wrap-pad", "aes256-wrap-pad-inv", "aria-128-cbc", "aria-128-ccm", - "aria-128-cfb", "aria-128-cfb1", "aria-128-cfb8", "aria-128-ctr", "aria-128-ecb", "aria-128-gcm", - "aria-128-ofb", "aria-192-cbc", "aria-192-ccm", "aria-192-cfb", "aria-192-cfb1", "aria-192-cfb8", - "aria-192-ctr", "aria-192-ecb", "aria-192-gcm", "aria-192-ofb", "aria-256-cbc", "aria-256-ccm", - "aria-256-cfb", "aria-256-cfb1", "aria-256-cfb8", "aria-256-ctr", "aria-256-ecb", "aria-256-gcm", - "aria-256-ofb", "aria128", "aria192", "aria256", "camellia-128-cbc", "camellia-128-cbc-cts", - "camellia-128-cfb", "camellia-128-cfb1", "camellia-128-cfb8", "camellia-128-ctr", - "camellia-128-ecb", "camellia-128-ofb", "camellia-192-cbc", "camellia-192-cbc-cts", - "camellia-192-cfb", "camellia-192-cfb1", "camellia-192-cfb8", "camellia-192-ctr", - "camellia-192-ecb", "camellia-192-ofb", "camellia-256-cbc", "camellia-256-cbc-cts", - "camellia-256-cfb", "camellia-256-cfb1", "camellia-256-cfb8", "camellia-256-ctr", - "camellia-256-ecb", "camellia-256-ofb", "camellia128", "camellia192", "camellia256", "chacha20", - "chacha20-poly1305", "des-ede", "des-ede-cbc", "des-ede-cfb", "des-ede-ecb", "des-ede-ofb", - "des-ede3", "des-ede3-cbc", "des-ede3-cfb", "des-ede3-cfb1", "des-ede3-cfb8", "des-ede3-ecb", - "des-ede3-ofb", "des3", "des3-wrap", "id-aes128-ccm", "id-aes128-gcm", "id-aes128-wrap", - "id-aes128-wrap-pad", "id-aes192-ccm", "id-aes192-gcm", "id-aes192-wrap", "id-aes192-wrap-pad", - "id-aes256-ccm", "id-aes256-gcm", "id-aes256-wrap", "id-aes256-wrap-pad", - "id-smime-alg-cms3deswrap", "sm4", "sm4-cbc", "sm4-ccm", "sm4-cfb", "sm4-cfb128", "sm4-ctr", - "sm4-ecb", "sm4-gcm", "sm4-ofb", "sm4-ofb128", "sm4-xts", -}; +static const char *const sc_known[] = {SCR_CIPHER_KNOWN_NAMES}; enum { SC_NO_TAG = -1 }; enum { SC_TAG_UNKNOWN, SC_TAG_SET_BY_USER, SC_TAG_COMPUTED }; diff --git a/packages/runtime/src/scr_cipher_names.h b/packages/runtime/src/scr_cipher_names.h new file mode 100644 index 000000000..38b31e06e --- /dev/null +++ b/packages/runtime/src/scr_cipher_names.h @@ -0,0 +1,100 @@ +/* crypto.getCiphers() on Node 24.22 (OpenSSL 3.5), the names a cipher + * option can give. The cipher and pki units each include it: names + * without an implementation there throw a coded SC2020 error instead of + * Node's ERR_CRYPTO_UNKNOWN_CIPHER. */ +#ifndef SCR_CIPHER_NAMES_H +#define SCR_CIPHER_NAMES_H + +#define SCR_CIPHER_KNOWN_NAMES \ + "aes-128-cbc", "aes-128-cbc-cts", "aes-128-ccm", "aes-128-cfb", "aes-128-cfb1", "aes-128-cfb8", \ + "aes-128-ctr", "aes-128-ecb", "aes-128-gcm", "aes-128-gcm-siv", "aes-128-ocb", "aes-128-ofb", \ + "aes-128-siv", "aes-128-wrap", "aes-128-wrap-inv", "aes-128-wrap-pad", "aes-128-wrap-pad-inv", \ + "aes-128-xts", "aes-192-cbc", "aes-192-cbc-cts", "aes-192-ccm", "aes-192-cfb", "aes-192-cfb1", \ + "aes-192-cfb8", "aes-192-ctr", "aes-192-ecb", "aes-192-gcm", "aes-192-gcm-siv", "aes-192-ocb", \ + "aes-192-ofb", "aes-192-siv", "aes-192-wrap", "aes-192-wrap-inv", "aes-192-wrap-pad", \ + "aes-192-wrap-pad-inv", "aes-256-cbc", "aes-256-cbc-cts", "aes-256-ccm", "aes-256-cfb", \ + "aes-256-cfb1", "aes-256-cfb8", "aes-256-ctr", "aes-256-ecb", "aes-256-gcm", "aes-256-gcm-siv", \ + "aes-256-ocb", "aes-256-ofb", "aes-256-siv", "aes-256-wrap", "aes-256-wrap-inv", \ + "aes-256-wrap-pad", "aes-256-wrap-pad-inv", "aes-256-xts", "aes128", "aes128-wrap", \ + "aes128-wrap-inv", "aes128-wrap-pad", "aes128-wrap-pad-inv", "aes192", "aes192-wrap", \ + "aes192-wrap-inv", "aes192-wrap-pad", "aes192-wrap-pad-inv", "aes256", "aes256-wrap", \ + "aes256-wrap-inv", "aes256-wrap-pad", "aes256-wrap-pad-inv", "aria-128-cbc", "aria-128-ccm", \ + "aria-128-cfb", "aria-128-cfb1", "aria-128-cfb8", "aria-128-ctr", "aria-128-ecb", "aria-128-gcm", \ + "aria-128-ofb", "aria-192-cbc", "aria-192-ccm", "aria-192-cfb", "aria-192-cfb1", "aria-192-cfb8", \ + "aria-192-ctr", "aria-192-ecb", "aria-192-gcm", "aria-192-ofb", "aria-256-cbc", "aria-256-ccm", \ + "aria-256-cfb", "aria-256-cfb1", "aria-256-cfb8", "aria-256-ctr", "aria-256-ecb", "aria-256-gcm", \ + "aria-256-ofb", "aria128", "aria192", "aria256", "camellia-128-cbc", "camellia-128-cbc-cts", \ + "camellia-128-cfb", "camellia-128-cfb1", "camellia-128-cfb8", "camellia-128-ctr", \ + "camellia-128-ecb", "camellia-128-ofb", "camellia-192-cbc", "camellia-192-cbc-cts", \ + "camellia-192-cfb", "camellia-192-cfb1", "camellia-192-cfb8", "camellia-192-ctr", \ + "camellia-192-ecb", "camellia-192-ofb", "camellia-256-cbc", "camellia-256-cbc-cts", \ + "camellia-256-cfb", "camellia-256-cfb1", "camellia-256-cfb8", "camellia-256-ctr", \ + "camellia-256-ecb", "camellia-256-ofb", "camellia128", "camellia192", "camellia256", "chacha20", \ + "chacha20-poly1305", "des-ede", "des-ede-cbc", "des-ede-cfb", "des-ede-ecb", "des-ede-ofb", \ + "des-ede3", "des-ede3-cbc", "des-ede3-cfb", "des-ede3-cfb1", "des-ede3-cfb8", "des-ede3-ecb", \ + "des-ede3-ofb", "des3", "des3-wrap", "id-aes128-ccm", "id-aes128-gcm", "id-aes128-wrap", \ + "id-aes128-wrap-pad", "id-aes192-ccm", "id-aes192-gcm", "id-aes192-wrap", "id-aes192-wrap-pad", \ + "id-aes256-ccm", "id-aes256-gcm", "id-aes256-wrap", "id-aes256-wrap-pad", \ + "id-smime-alg-cms3deswrap", "sm4", "sm4-cbc", "sm4-ccm", "sm4-cfb", "sm4-cfb128", "sm4-ctr", \ + "sm4-ecb", "sm4-gcm", "sm4-ofb", "sm4-ofb128", "sm4-xts", + +/* The ciphers OpenSSL 3 keeps in its legacy provider, which Node 24 does + * not load, with their IV lengths: OpenSSL knows their names, and using + * them fails with its "unsupported" error. */ +#define SCR_CIPHER_LEGACY_ENTRIES \ + {"des", 8}, {"des-cbc", 8}, {"des-ecb", 0}, {"des-cfb", 8}, {"des-cfb1", 8}, {"des-cfb8", 8}, \ + {"des-ofb", 8}, {"desx", 8}, {"desx-cbc", 8}, {"bf", 8}, {"bf-cbc", 8}, {"bf-ecb", 0}, \ + {"bf-cfb", 8}, {"bf-ofb", 8}, {"blowfish", 8}, {"cast", 8}, {"cast-cbc", 8}, {"cast5-cbc", 8}, \ + {"cast5-ecb", 0}, {"cast5-cfb", 8}, {"cast5-ofb", 8}, {"rc2", 8}, {"rc2-cbc", 8}, {"rc2-ecb", 0}, \ + {"rc2-cfb", 8}, {"rc2-ofb", 8}, {"rc2-40-cbc", 8}, {"rc2-64-cbc", 8}, {"rc4", 0}, {"rc4-40", 0}, \ + {"rc4-hmac-md5", 0}, {"seed", 16}, {"seed-cbc", 16}, {"seed-ecb", 0}, {"seed-cfb", 16}, \ + {"seed-ofb", 16}, {"idea", 8}, {"idea-cbc", 8}, {"idea-ecb", 0}, {"idea-cfb", 8}, {"idea-ofb", 8}, + +/* The OID texts OpenSSL's providers register as cipher aliases, with the + * name each fetches (crypto.getCipherInfo(oid).name on Node 24.22). */ +#define SCR_CIPHER_OID_ALIASES \ + {"2.16.840.1.101.3.4.1.1", "aes-128-ecb"}, {"2.16.840.1.101.3.4.1.2", "aes-128-cbc"}, \ + {"2.16.840.1.101.3.4.1.3", "aes-128-ofb"}, {"2.16.840.1.101.3.4.1.4", "aes-128-cfb"}, \ + {"2.16.840.1.101.3.4.1.5", "id-aes128-wrap"}, {"2.16.840.1.101.3.4.1.6", "id-aes128-gcm"}, \ + {"2.16.840.1.101.3.4.1.7", "id-aes128-ccm"}, {"2.16.840.1.101.3.4.1.8", "id-aes128-wrap-pad"}, \ + {"2.16.840.1.101.3.4.1.21", "aes-192-ecb"}, {"2.16.840.1.101.3.4.1.22", "aes-192-cbc"}, \ + {"2.16.840.1.101.3.4.1.23", "aes-192-ofb"}, {"2.16.840.1.101.3.4.1.24", "aes-192-cfb"}, \ + {"2.16.840.1.101.3.4.1.25", "id-aes192-wrap"}, {"2.16.840.1.101.3.4.1.26", "id-aes192-gcm"}, \ + {"2.16.840.1.101.3.4.1.27", "id-aes192-ccm"}, {"2.16.840.1.101.3.4.1.28", "id-aes192-wrap-pad"}, \ + {"2.16.840.1.101.3.4.1.41", "aes-256-ecb"}, {"2.16.840.1.101.3.4.1.42", "aes-256-cbc"}, \ + {"2.16.840.1.101.3.4.1.43", "aes-256-ofb"}, {"2.16.840.1.101.3.4.1.44", "aes-256-cfb"}, \ + {"2.16.840.1.101.3.4.1.45", "id-aes256-wrap"}, {"2.16.840.1.101.3.4.1.46", "id-aes256-gcm"}, \ + {"2.16.840.1.101.3.4.1.47", "id-aes256-ccm"}, {"2.16.840.1.101.3.4.1.48", "id-aes256-wrap-pad"}, \ + {"1.2.410.200046.1.1.1", "aria-128-ecb"}, {"1.2.410.200046.1.1.2", "aria-128-cbc"}, \ + {"1.2.410.200046.1.1.3", "aria-128-cfb"}, {"1.2.410.200046.1.1.4", "aria-128-ofb"}, \ + {"1.2.410.200046.1.1.5", "aria-128-ctr"}, {"1.2.410.200046.1.1.6", "aria-192-ecb"}, \ + {"1.2.410.200046.1.1.7", "aria-192-cbc"}, {"1.2.410.200046.1.1.8", "aria-192-cfb"}, \ + {"1.2.410.200046.1.1.9", "aria-192-ofb"}, {"1.2.410.200046.1.1.10", "aria-192-ctr"}, \ + {"1.2.410.200046.1.1.11", "aria-256-ecb"}, {"1.2.410.200046.1.1.12", "aria-256-cbc"}, \ + {"1.2.410.200046.1.1.13", "aria-256-cfb"}, {"1.2.410.200046.1.1.14", "aria-256-ofb"}, \ + {"1.2.410.200046.1.1.15", "aria-256-ctr"}, {"1.2.410.200046.1.1.34", "aria-128-gcm"}, \ + {"1.2.410.200046.1.1.35", "aria-192-gcm"}, {"1.2.410.200046.1.1.36", "aria-256-gcm"}, \ + {"1.2.410.200046.1.1.37", "aria-128-ccm"}, {"1.2.410.200046.1.1.38", "aria-192-ccm"}, \ + {"1.2.410.200046.1.1.39", "aria-256-ccm"}, {"0.3.4401.5.3.1.9.1", "camellia-128-ecb"}, \ + {"0.3.4401.5.3.1.9.3", "camellia-128-ofb"}, {"0.3.4401.5.3.1.9.4", "camellia-128-cfb"}, \ + {"0.3.4401.5.3.1.9.9", "camellia-128-ctr"}, {"0.3.4401.5.3.1.9.21", "camellia-192-ecb"}, \ + {"0.3.4401.5.3.1.9.23", "camellia-192-ofb"}, {"0.3.4401.5.3.1.9.24", "camellia-192-cfb"}, \ + {"0.3.4401.5.3.1.9.29", "camellia-192-ctr"}, {"0.3.4401.5.3.1.9.41", "camellia-256-ecb"}, \ + {"0.3.4401.5.3.1.9.43", "camellia-256-ofb"}, {"0.3.4401.5.3.1.9.44", "camellia-256-cfb"}, \ + {"0.3.4401.5.3.1.9.49", "camellia-256-ctr"}, {"1.2.156.10197.1.104.1", "sm4-ecb"}, \ + {"1.2.156.10197.1.104.2", "sm4-cbc"}, {"1.2.156.10197.1.104.3", "sm4-ofb"}, \ + {"1.2.156.10197.1.104.4", "sm4-cfb"}, {"1.2.156.10197.1.104.7", "sm4-ctr"}, \ + {"1.2.156.10197.1.104.8", "sm4-gcm"}, {"1.2.156.10197.1.104.9", "sm4-ccm"}, \ + {"1.2.156.10197.1.104.10", "sm4-xts"}, {"1.2.392.200011.61.1.1.1.2", "camellia-128-cbc"}, \ + {"1.2.392.200011.61.1.1.1.3", "camellia-192-cbc"}, \ + {"1.2.392.200011.61.1.1.1.4", "camellia-256-cbc"}, {"1.2.840.113549.3.2", "rc2-cbc"}, \ + {"1.2.840.113549.3.4", "rc4"}, {"1.2.840.113549.3.7", "des-ede3-cbc"}, \ + {"1.2.840.113549.1.9.16.3.6", "id-smime-alg-cms3deswrap"}, {"1.3.14.3.2.6", "des-ecb"}, \ + {"1.3.14.3.2.7", "des-cbc"}, {"1.3.14.3.2.8", "des-ofb"}, {"1.3.14.3.2.9", "des-cfb"}, \ + {"1.3.14.3.2.17", "des-ede"}, {"1.3.111.2.1619.0.1.1", "aes-128-xts"}, \ + {"1.3.111.2.1619.0.1.2", "aes-256-xts"}, {"1.3.6.1.4.1.3029.1.2", "bf-cbc"}, \ + {"1.3.6.1.4.1.188.7.1.1.2", "idea-cbc"}, {"1.2.410.200004.1.3", "seed-ecb"}, \ + {"1.2.410.200004.1.4", "seed-cbc"}, {"1.2.410.200004.1.5", "seed-cfb"}, \ + {"1.2.410.200004.1.6", "seed-ofb"}, {"1.2.840.113533.7.66.10", "cast5-cbc"}, + +#endif diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index fb3524c81..b7736bc35 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -8137,6 +8137,11 @@ static bool scr_digest_alg(const char *name, size_t len, ScrDigestAlg *out) { return false; } +bool scr_digest_known(const char *name, size_t len) { + ScrDigestAlg alg; + return scr_digest_alg(name, len, &alg); +} + /* createHash() and crypto.hash() also accept "dss1" in any case. */ static bool scr_hash_alg(const char *name, size_t len, ScrDigestAlg *out) { if (scr_digest_alg(name, len, out)) return true; diff --git a/packages/runtime/src/scr_lib.h b/packages/runtime/src/scr_lib.h index fa447ff41..7ba9e2907 100644 --- a/packages/runtime/src/scr_lib.h +++ b/packages/runtime/src/scr_lib.h @@ -63,6 +63,9 @@ bool scr_lib_fork_info(double *target, uintptr_t *read_handle, * direct startup never filters user arguments. */ bool scr_lib_should_collapse_reexec_arg(ScrStr *cmd, ScrArr *args); ScrStr *scr_process_platform(void); /* +1 interned ("darwin", "linux", ...) */ +/* Whether OpenSSL, as Node 24 links it, names a digest `name` (its names, + * aliases and object identifiers, case-insensitively). */ +bool scr_digest_known(const char *name, size_t len); ScrStr *scr_process_cwd(void); /* +1 fresh (getcwd) */ bool scr_stdio_write(int fd, const void *data, size_t len); diff --git a/packages/runtime/src/scr_pki.c b/packages/runtime/src/scr_pki.c index a1d5b98a6..908129243 100644 --- a/packages/runtime/src/scr_pki.c +++ b/packages/runtime/src/scr_pki.c @@ -1,7 +1,7 @@ /* node:crypto asymmetric keys and signatures over the vendored mbedTLS * public-key layer: createPrivateKey, createPublicKey, generateKeyPairSync - * and generateKeyPair for "rsa", "ec", "ed25519" and "x25519" keys, - * crypto.diffieHellman (ECDH and X25519), the PrivateKeyObject and + * and generateKeyPair for "rsa", "rsa-pss", "ec", "ed25519" and "x25519" + * keys, crypto.diffieHellman (ECDH and X25519), the PrivateKeyObject and * PublicKeyObject handles (type, asymmetricKeyType, * asymmetricKeyDetails, export(), equals()), crypto.sign and crypto.verify, * and the Sign and Verify objects of createSign and createVerify. Keys read @@ -10,7 +10,8 @@ * prime256v1, secp384r1, secp521r1 and secp256k1, and the Ed25519 * arithmetic is scr_ed25519.c. Argument checks * and errors follow Node 24's lib/internal/crypto/keys.js, keygen.js and - * sig.js. Encrypted keys and other key types throw a coded SC2020 error. + * sig.js. Private keys export and import encrypted as PBES2 PKCS#8 and as + * traditional PEM. Other key types throw a coded SC2020 error. * All arguments are borrowed. */ #include "scr_runtime.h" @@ -23,6 +24,7 @@ #include "mbedtls/asn1.h" #include "mbedtls/asn1write.h" #include "mbedtls/base64.h" +#include "mbedtls/cipher.h" #include "mbedtls/bignum.h" #include "mbedtls/ecdh.h" #include "mbedtls/ecdsa.h" @@ -30,11 +32,13 @@ #include "mbedtls/md.h" #include "mbedtls/oid.h" #include "mbedtls/pk.h" +#include "mbedtls/pkcs5.h" #include "mbedtls/platform_util.h" #include "mbedtls/rsa.h" #include "mbedtls/x509_crt.h" #include "scr_pki.h" +#include "scr_cipher_names.h" /* ── errors ───────────────────────────────────────────────────────────── */ @@ -187,6 +191,13 @@ typedef struct SpKey { * bytes as given, before clamping, and the public key */ uint8_t ed_seed[32]; uint8_t ed_pub[32]; + /* An RSA-PSS key (id-RSASSA-PSS): signatures only, with PSS padding. + * With RSASSA-PSS-params it signs only with pss_md and MGF1 over + * pss_mgf1, and salts of at least pss_salt bytes. */ + bool rsa_pss; + bool pss_params; + mbedtls_md_type_t pss_md, pss_mgf1; + int pss_salt; } SpKey; static void *sp_key_retain(void *h) { @@ -245,6 +256,27 @@ static bool sp_is_ed(const SpKey *k) { return k->ed25519; } static bool sp_is_x(const SpKey *k) { return k->x25519; } +/* The PSS fields of `from` on `to`. */ +static void sp_pss_copy(SpKey *to, const SpKey *from) { + to->rsa_pss = from->rsa_pss; + to->pss_params = from->pss_params; + to->pss_md = from->pss_md; + to->pss_mgf1 = from->pss_mgf1; + to->pss_salt = from->pss_salt; +} + +/* Node's name for a digest RSASSA-PSS-params can name. */ +static const char *sp_pss_md_name(mbedtls_md_type_t md) { + switch (md) { + case MBEDTLS_MD_SHA1: return "sha1"; + case MBEDTLS_MD_SHA224: return "sha224"; + case MBEDTLS_MD_SHA256: return "sha256"; + case MBEDTLS_MD_SHA384: return "sha384"; + case MBEDTLS_MD_SHA512: return "sha512"; + default: return NULL; + } +} + /* An Ed25519 or X25519 key: 32-byte private and public keys, no details. */ static bool sp_is_okp(const SpKey *k) { return k->ed25519 || k->x25519; } @@ -341,10 +373,102 @@ static int sp_write_ed_der(const uint8_t *prefix, size_t prefix_len, const uint8 return (int)n; } +/* A digest AlgorithmIdentifier with NULL parameters, written backwards + * at *c; the written length or a negative error. */ +static int sp_write_md_alg(unsigned char **c, unsigned char *start, mbedtls_md_type_t md) { + const char *oid; + size_t oid_len; + int r, len = 0; + if ((r = mbedtls_oid_get_oid_by_md(md, &oid, &oid_len)) != 0) return r; + if ((r = mbedtls_asn1_write_null(c, start)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_oid(c, start, oid, oid_len)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)len)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0) return r; + return len + r; +} + +/* An RSA-PSS key's AlgorithmIdentifier, as OpenSSL writes it: id-RSASSA-PSS + * with no parameters for a key without restrictions, or RSASSA-PSS-params + * omitting the fields at their defaults (SHA-1, MGF1 with SHA-1, salt 20). */ +static int sp_write_pss_alg(unsigned char **c, unsigned char *start, const SpKey *k) { + int r, len = 0; + if (k->pss_params) { + int params = 0; + if (k->pss_salt != 20) { + int field = 0; + if ((r = mbedtls_asn1_write_int(c, start, k->pss_salt)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)field)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2)) < 0) + return r; + params += field + r; + } + if (k->pss_mgf1 != MBEDTLS_MD_SHA1) { + int field = 0; + if ((r = sp_write_md_alg(c, start, k->pss_mgf1)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_oid(c, start, MBEDTLS_OID_MGF1, MBEDTLS_OID_SIZE(MBEDTLS_OID_MGF1))) < 0) + return r; + field += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)field)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)field)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1)) < 0) + return r; + params += field + r; + } + if (k->pss_md != MBEDTLS_MD_SHA1) { + int field = 0; + if ((r = sp_write_md_alg(c, start, k->pss_md)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)field)) < 0) return r; + field += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0)) < 0) + return r; + params += field + r; + } + if ((r = mbedtls_asn1_write_len(c, start, (size_t)params)) < 0) return r; + params += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0) return r; + len += params + r; + } + if ((r = mbedtls_asn1_write_oid(c, start, MBEDTLS_OID_RSASSA_PSS, MBEDTLS_OID_SIZE(MBEDTLS_OID_RSASSA_PSS))) < 0) + return r; + len += r; + if ((r = mbedtls_asn1_write_len(c, start, (size_t)len)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_tag(c, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0) return r; + return len + r; +} + +static int sp_write_pkcs1_public_der(SpKey *k, unsigned char *buf, size_t size); + static int sp_write_spki_der(SpKey *k, unsigned char *buf, size_t size) { if (sp_is_okp(k)) return sp_write_ed_der(sp_is_x(k) ? sp_x_spki_prefix : sp_ed_spki_prefix, 12, k->ed_pub, buf, size); - return mbedtls_pk_write_pubkey_der(&k->pk, buf, size); + if (!k->rsa_pss) return mbedtls_pk_write_pubkey_der(&k->pk, buf, size); + /* SubjectPublicKeyInfo ::= SEQUENCE { AlgorithmIdentifier, BIT STRING + * RSAPublicKey } */ + unsigned char inner[SP_DER_MAX]; + int n = sp_write_pkcs1_public_der(k, inner, sizeof inner); + if (n < 0) return n; + unsigned char *c = buf + size; + int r, len = 0; + if ((r = mbedtls_asn1_write_bitstring(&c, buf, inner + sizeof inner - n, (size_t)n * 8)) < 0) return r; + len += r; + if ((r = sp_write_pss_alg(&c, buf, k)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_len(&c, buf, (size_t)len)) < 0) return r; + len += r; + if ((r = mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0) return r; + return len + r; } /* RSAPublicKey (PKCS#1) DER. */ @@ -414,6 +538,11 @@ static int sp_write_pkcs8_der(SpKey *k, unsigned char *buf, size_t size) { const char *oid; size_t oid_len; size_t alg_len = 0; + if (k->rsa_pss) { + if ((r = sp_write_pss_alg(&c, buf, k)) < 0) goto done; + len += (size_t)r; + goto version; + } if (sp_is_rsa(k)) { if ((r = mbedtls_asn1_write_null(&c, buf)) < 0) goto done; alg_len += (size_t)r; @@ -439,6 +568,7 @@ static int sp_write_pkcs8_der(SpKey *k, unsigned char *buf, size_t size) { alg_len += (size_t)r; len += alg_len; } +version: if ((r = mbedtls_asn1_write_int(&c, buf, 0)) < 0) goto done; len += (size_t)r; if ((r = mbedtls_asn1_write_len(&c, buf, len)) < 0) goto done; @@ -476,6 +606,622 @@ static ScrStr *sp_pem(const char *label, const unsigned char *der, size_t len) { return s; } +/* ── encrypted private keys ─────────────────────────────────────────── + * PKCS#8 EncryptedPrivateKeyInfo as OpenSSL 3 writes it (PBES2 with + * PBKDF2-HMAC-SHA256 over a 16-byte salt and 2048 iterations), and the + * traditional PEM encryption of PKCS#1 and SEC1 keys (Proc-Type and + * DEK-Info headers, the key from EVP_BytesToKey with MD5), for the ciphers + * below. Reading takes PBES2 with any HMAC-SHA PRF and these ciphers, and + * traditional PEM with the CBC ciphers through mbedTLS. */ + +typedef struct { + const char *name; /* OpenSSL's name */ + const char *pem; /* its DEK-Info name */ + mbedtls_cipher_type_t type; + const char *oid; + size_t oid_len, key_len, iv_len; + bool ecb; + /* What OpenSSL does when PKCS#8 or traditional PEM encryption takes + * the cipher, an SpKeyCipherError. */ + unsigned char pkcs8_error, traditional_error; +} SpKeyCipher; + +typedef enum { + SP_KC_OK, /* it encrypts */ + SP_KC_NO_OID, + SP_KC_EVP_CIPHER, + SP_KC_PARAMETER, + SP_KC_INPUT_LENGTH, + SP_KC_ZERO_CONTENT, + SP_KC_OPERATION, + SP_KC_PEM_CIPHER, + SP_KC_UNSUPPORTED, +} SpKeyCipherError; + +static const struct { + const char *packed, *library, *reason, *code; +} sp_key_cipher_errors[] = { + [SP_KC_NO_OID] = {"0680006C", "asn1 encoding routines", "cipher has no object identifier", + "ERR_OSSL_ASN1_CIPHER_HAS_NO_OBJECT_IDENTIFIER"}, + [SP_KC_EVP_CIPHER] = {"0300006B", "digital envelope routines", "unsupported cipher", + "ERR_OSSL_EVP_UNSUPPORTED_CIPHER"}, + [SP_KC_PARAMETER] = {"0300007A", "digital envelope routines", "cipher parameter error", + "ERR_OSSL_EVP_CIPHER_PARAMETER_ERROR"}, + [SP_KC_INPUT_LENGTH] = {"1C8000E6", "Provider routines", "invalid input length", "ERR_OSSL_INVALID_INPUT_LENGTH"}, + [SP_KC_ZERO_CONTENT] = {"068000DE", "asn1 encoding routines", "illegal zero content", + "ERR_OSSL_ASN1_ILLEGAL_ZERO_CONTENT"}, + [SP_KC_OPERATION] = {"1C800066", "Provider routines", "cipher operation failed", + "ERR_OSSL_CIPHER_OPERATION_FAILED"}, + [SP_KC_PEM_CIPHER] = {"04800071", "PEM routines", "unsupported cipher", "ERR_OSSL_PEM_UNSUPPORTED_CIPHER"}, + [SP_KC_UNSUPPORTED] = {"0308010C", "digital envelope routines", "unsupported", "ERR_OSSL_EVP_UNSUPPORTED"}, +}; + +#define SP_AES_OID(last) "\x60\x86\x48\x01\x65\x03\x04\x01" last, 9 +static const SpKeyCipher sp_key_ciphers[] = { + {"aes-128-cbc", "AES-128-CBC", MBEDTLS_CIPHER_AES_128_CBC, SP_AES_OID("\x02"), 16, 16, false}, + {"aes-192-cbc", "AES-192-CBC", MBEDTLS_CIPHER_AES_192_CBC, SP_AES_OID("\x16"), 24, 16, false}, + {"aes-256-cbc", "AES-256-CBC", MBEDTLS_CIPHER_AES_256_CBC, SP_AES_OID("\x2a"), 32, 16, false}, + {"aes-128-ecb", "AES-128-ECB", MBEDTLS_CIPHER_AES_128_ECB, SP_AES_OID("\x01"), 16, 0, true, SP_KC_OK, SP_KC_PEM_CIPHER}, + {"aes-192-ecb", "AES-192-ECB", MBEDTLS_CIPHER_AES_192_ECB, SP_AES_OID("\x15"), 24, 0, true, SP_KC_OK, SP_KC_PEM_CIPHER}, + {"aes-256-ecb", "AES-256-ECB", MBEDTLS_CIPHER_AES_256_ECB, SP_AES_OID("\x29"), 32, 0, true, SP_KC_OK, SP_KC_PEM_CIPHER}, + {"des-ede3-cbc", "DES-EDE3-CBC", MBEDTLS_CIPHER_DES_EDE3_CBC, "\x2a\x86\x48\x86\xf7\x0d\x03\x07", 8, 24, 8, false}, +}; +static const struct { + const char *alias, *name; +} sp_key_cipher_aliases[] = { + {"aes128", "aes-128-cbc"}, {"aes192", "aes-192-cbc"}, {"aes256", "aes-256-cbc"}, {"des3", "des-ede3-cbc"}, +}; + +/* The names OpenSSL 3.5 refuses to encrypt a private key with in one form + * or both, from Node 24 (the legacy-provider names fail in both). Other + * names Node knows encrypt there and are refused here. */ +#define SP_KC_FAIL(name, pkcs8, traditional) \ + {name, NULL, MBEDTLS_CIPHER_NONE, NULL, 0, 0, 0, false, SP_KC_##pkcs8, SP_KC_##traditional} +static const SpKeyCipher sp_key_cipher_failures[] = { + SP_KC_FAIL("aes-128-cbc-cts", NO_OID, OK), SP_KC_FAIL("aes-128-ccm", EVP_CIPHER, OK), + SP_KC_FAIL("aes-128-ctr", NO_OID, OK), SP_KC_FAIL("aes-128-gcm", PARAMETER, OK), + SP_KC_FAIL("aes-128-gcm-siv", NO_OID, OK), SP_KC_FAIL("aes-128-ocb", NO_OID, OK), + SP_KC_FAIL("aes-128-siv", NO_OID, PEM_CIPHER), + SP_KC_FAIL("aes-128-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes-128-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes-128-wrap-pad-inv", NO_OID, OK), SP_KC_FAIL("aes-128-xts", EVP_CIPHER, OK), + SP_KC_FAIL("aes-192-cbc-cts", NO_OID, OK), SP_KC_FAIL("aes-192-ccm", EVP_CIPHER, OK), + SP_KC_FAIL("aes-192-ctr", NO_OID, OK), SP_KC_FAIL("aes-192-gcm", PARAMETER, OK), + SP_KC_FAIL("aes-192-gcm-siv", NO_OID, OK), SP_KC_FAIL("aes-192-ocb", NO_OID, OK), + SP_KC_FAIL("aes-192-siv", NO_OID, PEM_CIPHER), + SP_KC_FAIL("aes-192-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes-192-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes-192-wrap-pad-inv", NO_OID, OK), SP_KC_FAIL("aes-256-cbc-cts", NO_OID, OK), + SP_KC_FAIL("aes-256-ccm", EVP_CIPHER, OK), SP_KC_FAIL("aes-256-ctr", NO_OID, OK), + SP_KC_FAIL("aes-256-gcm", PARAMETER, OK), SP_KC_FAIL("aes-256-gcm-siv", NO_OID, OK), + SP_KC_FAIL("aes-256-ocb", NO_OID, OK), SP_KC_FAIL("aes-256-siv", NO_OID, PEM_CIPHER), + SP_KC_FAIL("aes-256-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes-256-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes-256-wrap-pad-inv", NO_OID, OK), SP_KC_FAIL("aes-256-xts", EVP_CIPHER, OK), + SP_KC_FAIL("aes128-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes128-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes128-wrap-pad-inv", NO_OID, OK), + SP_KC_FAIL("aes192-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes192-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes192-wrap-pad-inv", NO_OID, OK), + SP_KC_FAIL("aes256-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("aes256-wrap-inv", NO_OID, INPUT_LENGTH), + SP_KC_FAIL("aes256-wrap-pad-inv", NO_OID, OK), SP_KC_FAIL("aria-128-ccm", EVP_CIPHER, OK), + SP_KC_FAIL("aria-128-cfb1", NO_OID, OK), SP_KC_FAIL("aria-128-cfb8", NO_OID, OK), + SP_KC_FAIL("aria-128-ecb", OK, PEM_CIPHER), SP_KC_FAIL("aria-128-gcm", PARAMETER, OK), + SP_KC_FAIL("aria-192-ccm", EVP_CIPHER, OK), SP_KC_FAIL("aria-192-cfb1", NO_OID, OK), + SP_KC_FAIL("aria-192-cfb8", NO_OID, OK), SP_KC_FAIL("aria-192-ecb", OK, PEM_CIPHER), + SP_KC_FAIL("aria-192-gcm", PARAMETER, OK), SP_KC_FAIL("aria-256-ccm", EVP_CIPHER, OK), + SP_KC_FAIL("aria-256-cfb1", NO_OID, OK), SP_KC_FAIL("aria-256-cfb8", NO_OID, OK), + SP_KC_FAIL("aria-256-ecb", OK, PEM_CIPHER), SP_KC_FAIL("aria-256-gcm", PARAMETER, OK), + SP_KC_FAIL("camellia-128-cbc-cts", NO_OID, OK), SP_KC_FAIL("camellia-128-cfb1", NO_OID, OK), + SP_KC_FAIL("camellia-128-cfb8", NO_OID, OK), SP_KC_FAIL("camellia-128-ecb", OK, PEM_CIPHER), + SP_KC_FAIL("camellia-192-cbc-cts", NO_OID, OK), SP_KC_FAIL("camellia-192-cfb1", NO_OID, OK), + SP_KC_FAIL("camellia-192-cfb8", NO_OID, OK), SP_KC_FAIL("camellia-192-ecb", OK, PEM_CIPHER), + SP_KC_FAIL("camellia-256-cbc-cts", NO_OID, OK), SP_KC_FAIL("camellia-256-cfb1", NO_OID, OK), + SP_KC_FAIL("camellia-256-cfb8", NO_OID, OK), SP_KC_FAIL("camellia-256-ecb", OK, PEM_CIPHER), + SP_KC_FAIL("chacha20", NO_OID, OK), SP_KC_FAIL("chacha20-poly1305", NO_OID, OK), + SP_KC_FAIL("des-ede", OK, PEM_CIPHER), SP_KC_FAIL("des-ede-cbc", NO_OID, OK), + SP_KC_FAIL("des-ede-cfb", NO_OID, OK), SP_KC_FAIL("des-ede-ecb", OK, PEM_CIPHER), + SP_KC_FAIL("des-ede-ofb", NO_OID, OK), SP_KC_FAIL("des-ede3", NO_OID, PEM_CIPHER), + SP_KC_FAIL("des-ede3-cfb", ZERO_CONTENT, OK), SP_KC_FAIL("des-ede3-cfb1", ZERO_CONTENT, OK), + SP_KC_FAIL("des-ede3-cfb8", ZERO_CONTENT, OK), SP_KC_FAIL("des-ede3-ecb", NO_OID, PEM_CIPHER), + SP_KC_FAIL("des-ede3-ofb", NO_OID, OK), SP_KC_FAIL("des3-wrap", OPERATION, PEM_CIPHER), + SP_KC_FAIL("id-aes128-ccm", EVP_CIPHER, OK), SP_KC_FAIL("id-aes128-gcm", PARAMETER, OK), + SP_KC_FAIL("id-aes128-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("id-aes192-ccm", EVP_CIPHER, OK), SP_KC_FAIL("id-aes192-gcm", PARAMETER, OK), + SP_KC_FAIL("id-aes192-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("id-aes256-ccm", EVP_CIPHER, OK), SP_KC_FAIL("id-aes256-gcm", PARAMETER, OK), + SP_KC_FAIL("id-aes256-wrap", INPUT_LENGTH, INPUT_LENGTH), + SP_KC_FAIL("id-smime-alg-cms3deswrap", OPERATION, PEM_CIPHER), SP_KC_FAIL("sm4-ccm", NO_OID, OK), + SP_KC_FAIL("sm4-ecb", OK, PEM_CIPHER), SP_KC_FAIL("sm4-gcm", NO_OID, OK), + SP_KC_FAIL("sm4-xts", NO_OID, OK), SP_KC_FAIL("des", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("des-cbc", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("des-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("des-cfb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("des-cfb1", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("des-cfb8", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("des-ofb", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("desx", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("desx-cbc", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("bf", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("bf-cbc", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("bf-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("bf-cfb", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("bf-ofb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("blowfish", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("cast", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("cast-cbc", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("cast5-cbc", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("cast5-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("cast5-cfb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("cast5-ofb", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("rc2", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc2-cbc", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("rc2-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc2-cfb", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("rc2-ofb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc2-40-cbc", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc2-64-cbc", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("rc4", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc4-40", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("rc4-hmac-md5", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("seed", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("seed-cbc", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("seed-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("seed-cfb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("seed-ofb", UNSUPPORTED, UNSUPPORTED), SP_KC_FAIL("idea", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("idea-cbc", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("idea-ecb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("idea-cfb", UNSUPPORTED, UNSUPPORTED), + SP_KC_FAIL("idea-ofb", UNSUPPORTED, UNSUPPORTED), +}; +#undef SP_KC_FAIL + +static bool sp_ascii_eq_ci(const char *a, size_t len, const char *b) { + if (strlen(b) != len) return false; + for (size_t i = 0; i < len; i++) { + char c = a[i]; + if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); + if (c != b[i]) return false; + } + return true; +} + +/* The cipher an export names, by a name or the OID text of one: true with + * *out set (an entry of sp_key_ciphers, or of sp_key_cipher_failures); + * false with Node's + * ERR_CRYPTO_UNKNOWN_CIPHER pending, or the SC2020 refusal for a cipher + * Node encrypts with that keys here do not. */ +static bool sp_key_cipher(const ScrStr *name, const SpKeyCipher **out) { + const char *data = name->data; + size_t len = name->len; + static const struct { + const char *oid, *name; + } oids[] = {SCR_CIPHER_OID_ALIASES}; + for (size_t i = 0; i < sizeof oids / sizeof oids[0]; i++) { + if (strlen(oids[i].oid) == len && memcmp(data, oids[i].oid, len) == 0) { + data = oids[i].name; + len = strlen(data); + } + } + const char *wanted = NULL; + for (size_t i = 0; i < sizeof sp_key_cipher_aliases / sizeof sp_key_cipher_aliases[0]; i++) + if (sp_ascii_eq_ci(data, len, sp_key_cipher_aliases[i].alias)) wanted = sp_key_cipher_aliases[i].name; + for (size_t i = 0; i < sizeof sp_key_ciphers / sizeof sp_key_ciphers[0]; i++) { + if (wanted ? strcmp(wanted, sp_key_ciphers[i].name) == 0 : sp_ascii_eq_ci(data, len, sp_key_ciphers[i].name)) { + *out = &sp_key_ciphers[i]; + return true; + } + } + for (size_t i = 0; i < sizeof sp_key_cipher_failures / sizeof sp_key_cipher_failures[0]; i++) { + if (sp_ascii_eq_ci(data, len, sp_key_cipher_failures[i].name)) { + *out = &sp_key_cipher_failures[i]; + return true; + } + } + static const char *const known[] = {SCR_CIPHER_KNOWN_NAMES}; + for (size_t i = 0; i < sizeof known / sizeof known[0]; i++) { + if (sp_ascii_eq_ci(data, len, known[i])) { + sp_not_supported("private keys encrypted with this cipher"); + return false; + } + } + sp_throw(SCR_ERR_ERROR, "ERR_CRYPTO_UNKNOWN_CIPHER", "Unknown cipher"); + return false; +} + +/* Whether encryption in the form (PKCS#8, or traditional PEM) can take + * the cipher for `len` bytes of key: false with OpenSSL's error pending, + * or the SC2020 refusal for a cipher OpenSSL encrypts with that keys here + * do not. The key wrap ciphers fail only on a length that is not a + * multiple of 8; AES key wrap then encrypts, and Node's 3DES key wrap + * corrupts its heap. */ +static bool sp_key_cipher_ready(const SpKeyCipher *ci, bool pkcs8, size_t len) { + unsigned char error = pkcs8 ? ci->pkcs8_error : ci->traditional_error; + if ((error == SP_KC_INPUT_LENGTH || error == SP_KC_OPERATION) && len % 8 == 0) error = SP_KC_OK; + if (error != SP_KC_OK) { + sp_throw_openssl(sp_key_cipher_errors[error].packed, sp_key_cipher_errors[error].library, + sp_key_cipher_errors[error].reason, sp_key_cipher_errors[error].code, true); + return false; + } + if (ci->type == MBEDTLS_CIPHER_NONE) { + sp_not_supported("private keys encrypted with this cipher"); + return false; + } + return true; +} + +/* `in` through the cipher with PKCS#7 padding, as OpenSSL's EVP layer + * pads CBC and ECB: the output (+1 malloc'd, *out_len), or NULL when + * decryption finds bad padding. */ +static unsigned char *sp_key_cipher_run(const SpKeyCipher *ci, bool encrypt, const unsigned char *key, + const unsigned char *iv, const unsigned char *in, size_t in_len, + size_t *out_len) { + const mbedtls_cipher_info_t *info = mbedtls_cipher_info_from_type(ci->type); + size_t block = mbedtls_cipher_info_get_block_size(info); + size_t padded = encrypt ? (in_len / block + 1) * block : in_len; + if (!encrypt && (in_len == 0 || in_len % block != 0)) return NULL; + unsigned char *src = malloc(padded ? padded : 1), *out = malloc(padded ? padded : 1); + if (!src || !out) scr_trap("scriptc: out of memory\n"); + memcpy(src, in, in_len); + if (encrypt) memset(src + in_len, (int)(padded - in_len), padded - in_len); + mbedtls_cipher_context_t ctx; + mbedtls_cipher_init(&ctx); + int r = mbedtls_cipher_setup(&ctx, info); + if (r == 0) r = mbedtls_cipher_setkey(&ctx, key, (int)ci->key_len * 8, encrypt ? MBEDTLS_ENCRYPT : MBEDTLS_DECRYPT); + if (r == 0 && !ci->ecb) r = mbedtls_cipher_set_padding_mode(&ctx, MBEDTLS_PADDING_NONE); + if (r == 0 && !ci->ecb) r = mbedtls_cipher_set_iv(&ctx, iv, ci->iv_len); + size_t written = 0; + for (size_t i = 0; r == 0 && i < padded; i += block) { + size_t n = 0; + r = mbedtls_cipher_update(&ctx, src + i, block, out + written, &n); + written += n; + } + if (r == 0 && !ci->ecb) { + size_t n = 0; + r = mbedtls_cipher_finish(&ctx, out + written, &n); + written += n; + } + mbedtls_cipher_free(&ctx); + mbedtls_platform_zeroize(src, padded); + free(src); + if (r == 0 && !encrypt) { + unsigned char pad = written ? out[written - 1] : 0; + bool ok = pad >= 1 && pad <= block && pad <= written; + for (size_t i = 0; ok && i < pad; i++) ok = out[written - 1 - i] == pad; + if (ok) written -= pad; + else r = -1; + } + if (r != 0) { + mbedtls_platform_zeroize(out, padded); + free(out); + return NULL; + } + *out_len = written; + return out; +} + +/* EncryptedPrivateKeyInfo DER of the PKCS#8 `plain` (+1 malloc'd, + * *out_len), as OpenSSL 3 writes it; NULL on failure. */ +static unsigned char *sp_encrypt_pkcs8(const unsigned char *plain, size_t plain_len, const SpKeyCipher *ci, + const unsigned char *pass, size_t pass_len, size_t *out_len) { + unsigned char salt[16], iv[16], key[32]; + sp_random(NULL, salt, sizeof salt); + sp_random(NULL, iv, ci->iv_len); + if (mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA256, pass, pass_len, salt, sizeof salt, 2048, + (uint32_t)ci->key_len, key) != 0) + return NULL; + size_t enc_len; + unsigned char *enc = sp_key_cipher_run(ci, true, key, iv, plain, plain_len, &enc_len); + mbedtls_platform_zeroize(key, sizeof key); + if (!enc) return NULL; + size_t size = enc_len + 256; + unsigned char *buf = malloc(size); + if (!buf) scr_trap("scriptc: out of memory\n"); + unsigned char *c = buf + size; + int r, len = 0, w; +#define SP_W(call) do { if ((w = (call)) < 0) { r = w; goto fail; } } while (0) + SP_W(mbedtls_asn1_write_octet_string(&c, buf, enc, enc_len)); + len += w; + /* encryptionScheme: the cipher with its IV (empty for ECB) */ + int scheme = 0; + SP_W(mbedtls_asn1_write_octet_string(&c, buf, iv, ci->iv_len)); + scheme += w; + SP_W(mbedtls_asn1_write_oid(&c, buf, ci->oid, ci->oid_len)); + scheme += w; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)scheme)); + scheme += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + scheme += w; + /* keyDerivationFunc: PBKDF2 { salt, 2048, hmacWithSHA256 } */ + int kdf = 0, prf = 0; + SP_W(mbedtls_asn1_write_null(&c, buf)); + prf += w; + SP_W(mbedtls_asn1_write_oid(&c, buf, MBEDTLS_OID_HMAC_SHA256, MBEDTLS_OID_SIZE(MBEDTLS_OID_HMAC_SHA256))); + prf += w; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)prf)); + prf += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + prf += w; + kdf += prf; + SP_W(mbedtls_asn1_write_int(&c, buf, 2048)); + kdf += w; + SP_W(mbedtls_asn1_write_octet_string(&c, buf, salt, sizeof salt)); + kdf += w; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)kdf)); + kdf += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + kdf += w; + SP_W(mbedtls_asn1_write_oid(&c, buf, MBEDTLS_OID_PKCS5_PBKDF2, MBEDTLS_OID_SIZE(MBEDTLS_OID_PKCS5_PBKDF2))); + kdf += w; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)kdf)); + kdf += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + kdf += w; + int params = kdf + scheme; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)params)); + params += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + params += w; + SP_W(mbedtls_asn1_write_oid(&c, buf, MBEDTLS_OID_PKCS5_PBES2, MBEDTLS_OID_SIZE(MBEDTLS_OID_PKCS5_PBES2))); + params += w; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)params)); + params += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + params += w; + len += params; + SP_W(mbedtls_asn1_write_len(&c, buf, (size_t)len)); + len += w; + SP_W(mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)); + len += w; +#undef SP_W + memmove(buf, c, (size_t)len); + mbedtls_platform_zeroize(enc, enc_len); + free(enc); + *out_len = (size_t)len; + return buf; +fail: + (void)r; + mbedtls_platform_zeroize(enc, enc_len); + free(enc); + free(buf); + return NULL; +} + +/* OpenSSL's EVP_BytesToKey with MD5 and one round over the first 8 IV + * bytes, as traditional PEM encryption derives its key. */ +static void sp_bytes_to_key(const unsigned char *pass, size_t pass_len, const unsigned char *salt, unsigned char *key, + size_t key_len) { + const mbedtls_md_info_t *md5 = mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + unsigned char digest[16]; + size_t done = 0; + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + mbedtls_md_setup(&ctx, md5, 0); + for (bool first = true; done < key_len; first = false) { + mbedtls_md_starts(&ctx); + if (!first) mbedtls_md_update(&ctx, digest, sizeof digest); + mbedtls_md_update(&ctx, pass, pass_len); + mbedtls_md_update(&ctx, salt, 8); + mbedtls_md_finish(&ctx, digest); + size_t n = key_len - done < sizeof digest ? key_len - done : sizeof digest; + memcpy(key + done, digest, n); + done += n; + } + mbedtls_md_free(&ctx); + mbedtls_platform_zeroize(digest, sizeof digest); +} + +/* The traditional encrypted PEM of a PKCS#1 or SEC1 key (+1), or NULL. */ +static ScrStr *sp_encrypted_pem(const char *label, const unsigned char *der, size_t len, const SpKeyCipher *ci, + const unsigned char *pass, size_t pass_len) { + unsigned char iv[16], key[32]; + sp_random(NULL, iv, ci->iv_len); + sp_bytes_to_key(pass, pass_len, iv, key, ci->key_len); + size_t enc_len; + unsigned char *enc = sp_key_cipher_run(ci, true, key, iv, der, len, &enc_len); + mbedtls_platform_zeroize(key, sizeof key); + if (!enc) return NULL; + ScrStr *body = sp_pem(label, enc, enc_len); + mbedtls_platform_zeroize(enc, enc_len); + free(enc); + /* The headers follow the BEGIN line, then a blank line. */ + const char *nl = memchr(body->data, '\n', body->len); + size_t head = (size_t)(nl - body->data) + 1; + ScrJsonBuf out; + scr_jb_init(&out); + for (size_t i = 0; i < head; i++) scr_jb_putc(&out, body->data[i]); + scr_jb_puts(&out, "Proc-Type: 4,ENCRYPTED\nDEK-Info: "); + scr_jb_puts(&out, ci->pem); + scr_jb_putc(&out, ','); + static const char hex[] = "0123456789ABCDEF"; + for (size_t i = 0; i < ci->iv_len; i++) { + scr_jb_putc(&out, hex[iv[i] >> 4]); + scr_jb_putc(&out, hex[iv[i] & 15]); + } + scr_jb_puts(&out, "\n\n"); + for (size_t i = head; i < body->len; i++) scr_jb_putc(&out, body->data[i]); + scr_str_release(body); + return scr_jb_finish(&out); +} + +/* OpenSSL's errors for an encrypted private key: the message, Node's + * code, and the error stack OpenSSL leaves for the input's form. */ +static void sp_throw_key_decrypt(const char *message, const char *library, const char *reason, const char *code, + const char *const *stack, size_t stack_len) { + ScrStr *text = scr_str_new(message, strlen(message)); + ScrError *e = scr_error_new(SCR_ERR_ERROR, text); + scr_str_release(text); + ScrDyn *list = scr_dyn_new_arr(); + for (size_t i = 0; i < stack_len; i++) { + ScrStr *entry = scr_str_new(stack[i], strlen(stack[i])); + scr_dyn_arr_push(list, scr_dyn_new_str(entry)); + scr_str_release(entry); + } + scr_error_set_prop(e, "opensslErrorStack", 17, list); + const char *keys[] = {"library", "reason", "code"}; + const char *values[] = {library, reason, code}; + for (size_t i = 0; i < 3; i++) { + if (i == 2) scr_error_set_code(e, code); + ScrStr *value = scr_str_new(values[i], strlen(values[i])); + scr_error_set_prop(e, keys[i], strlen(keys[i]), scr_dyn_new_str(value)); + scr_str_release(value); + } + scr_throw_obj(e, &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg()); +} + +static const char sp_err_interrupted[] = "error:07880109:common libcrypto routines::interrupted or cancelled"; +static const char sp_err_bad_decrypt[] = "error:1C800064:Provider routines::bad decrypt"; + +/* No passphrase for an encrypted PEM key (pkcs8: whether it is PKCS#8). */ +static void sp_throw_passphrase_missing(bool pkcs8) { + static const char *const pkcs8_stack[] = {"error:04800068:PEM routines::bad password read", sp_err_interrupted, + "error:1C80009F:Provider routines::unable to get passphrase", + sp_err_interrupted, sp_err_interrupted}; + static const char *const pem_stack[] = {"error:04800068:PEM routines::bad password read", sp_err_interrupted, + sp_err_interrupted}; + sp_throw_key_decrypt(sp_err_interrupted, "common libcrypto routines", "interrupted or cancelled", + "ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED", pkcs8 ? pkcs8_stack : pem_stack, pkcs8 ? 5 : 3); +} + +/* A passphrase that does not decrypt the key: PKCS#8 in PEM, PKCS#8 in + * DER, or traditional PEM. */ +static void sp_throw_bad_decrypt(int form) { + static const char cipherfinal[] = "error:11800074:PKCS12 routines::pkcs12 cipherfinal error"; + static const char *const stacks[][4] = { + {cipherfinal, sp_err_bad_decrypt, cipherfinal, sp_err_bad_decrypt}, + {cipherfinal, sp_err_bad_decrypt}, + {"error:04800065:PEM routines::bad decrypt", sp_err_bad_decrypt}, + }; + sp_throw_key_decrypt(sp_err_bad_decrypt, "Provider routines", "bad decrypt", "ERR_OSSL_BAD_DECRYPT", stacks[form], + form == 0 ? 4 : 2); +} + +/* The cipher name of an encrypted traditional PEM key's DEK-Info header + * into `name` (NUL-terminated), or false for other input. */ +static bool sp_pem_dek_info(const unsigned char *data, size_t len, char *name, size_t cap) { + const char *text = (const char *)data; + const char *begin = NULL; + for (size_t i = 0; i + 11 <= len && !begin; i++) + if (memcmp(text + i, "-----BEGIN ", 11) == 0) begin = text + i; + if (!begin) return false; + const char *end = text + len, *line = memchr(begin, '\n', (size_t)(end - begin)); + bool encrypted = false; + while (line && ++line < end) { + const char *eol = memchr(line, '\n', (size_t)(end - line)); + size_t n = (size_t)((eol ? eol : end) - line); + if (n && line[n - 1] == '\r') n--; + if (n >= 10 && memcmp(line, "Proc-Type:", 10) == 0) { + for (size_t i = 10; i + 9 <= n; i++) + if (memcmp(line + i, "ENCRYPTED", 9) == 0) encrypted = true; + } else if (n >= 9 && memcmp(line, "DEK-Info:", 9) == 0) { + if (!encrypted) return false; + size_t i = 9; + while (i < n && line[i] == ' ') i++; + size_t o = 0; + while (i < n && line[i] != ',' && o + 1 < cap) name[o++] = line[i++]; + name[o] = 0; + return true; + } else if (n == 0 || memchr(line, ':', n) == NULL) { + return false; + } + line = eol; + } + return false; +} + +/* Before mbedTLS reads an encrypted traditional PEM key: false with + * OpenSSL's error pending for a DEK-Info cipher it does not know, a + * missing passphrase, or a legacy-provider cipher, or the SC2020 refusal + * for a cipher mbedTLS does not decrypt; true for other input. */ +static bool sp_pem_dek_check(const unsigned char *data, size_t len, const ScrBytes *pass) { + char name[64]; + if (!sp_pem_dek_info(data, len, name, sizeof name)) return true; + size_t n = strlen(name); + static const char *const known[] = {SCR_CIPHER_KNOWN_NAMES}; + static const struct { + const char *name; + unsigned iv_len; + } legacy[] = {SCR_CIPHER_LEGACY_ENTRIES}; + bool is_known = false, is_legacy = false; + for (size_t i = 0; i < sizeof known / sizeof known[0]; i++) + if (sp_ascii_eq_ci(name, n, known[i])) is_known = true; + for (size_t i = 0; i < sizeof legacy / sizeof legacy[0]; i++) + if (sp_ascii_eq_ci(name, n, legacy[i].name)) is_legacy = true; + if (!is_known && !is_legacy) { + static const char *const stack[] = {"error:04800072:PEM routines::unsupported encryption"}; + sp_throw_key_decrypt(stack[0], "PEM routines", "unsupported encryption", "ERR_OSSL_PEM_UNSUPPORTED_ENCRYPTION", + stack, 1); + return false; + } + if (!pass) { + sp_throw_passphrase_missing(false); + return false; + } + if (is_legacy) { + static const char unsupported[] = "error:0308010C:digital envelope routines::unsupported"; + static const char *const stack[] = {"error:04800065:PEM routines::bad decrypt", unsupported}; + sp_throw_key_decrypt(unsupported, "digital envelope routines", "unsupported", "ERR_OSSL_EVP_UNSUPPORTED", stack, + 2); + return false; + } + /* mbedTLS decrypts these names, as OpenSSL writes them. */ + static const char *const read[] = {"AES-128-CBC", "AES-192-CBC", "AES-256-CBC", "DES-EDE3-CBC"}; + for (size_t i = 0; i < sizeof read / sizeof read[0]; i++) + if (strcmp(name, read[i]) == 0) return true; + sp_not_supported("private keys encrypted with this cipher"); + return false; +} + +/* The PKCS#8 inside a PBES2 EncryptedPrivateKeyInfo `der` (+1 malloc'd, + * *out_len): 1 decrypted, 0 another scheme or cipher, -1 a passphrase + * that does not decrypt it. */ +static int sp_decrypt_pkcs8(const unsigned char *der, size_t len, const unsigned char *pass, size_t pass_len, + unsigned char **out, size_t *out_len) { + unsigned char *p = (unsigned char *)der; + const unsigned char *end = der + len; + size_t n; + mbedtls_asn1_buf alg, params; + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return 0; + end = p + n; + if (mbedtls_asn1_get_alg(&p, end, &alg, ¶ms) != 0 || alg.len != MBEDTLS_OID_SIZE(MBEDTLS_OID_PKCS5_PBES2) || + memcmp(alg.p, MBEDTLS_OID_PKCS5_PBES2, alg.len) != 0 || + params.tag != (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) + return 0; + unsigned char *q = params.p; + const unsigned char *params_end = params.p + params.len; + mbedtls_asn1_buf kdf, kdf_params, scheme, scheme_params; + if (mbedtls_asn1_get_alg(&q, params_end, &kdf, &kdf_params) != 0 || + kdf.len != MBEDTLS_OID_SIZE(MBEDTLS_OID_PKCS5_PBKDF2) || memcmp(kdf.p, MBEDTLS_OID_PKCS5_PBKDF2, kdf.len) != 0 || + mbedtls_asn1_get_alg(&q, params_end, &scheme, &scheme_params) != 0) + return 0; + const SpKeyCipher *ci = NULL; + for (size_t i = 0; i < sizeof sp_key_ciphers / sizeof sp_key_ciphers[0]; i++) + if (scheme.len == sp_key_ciphers[i].oid_len && memcmp(scheme.p, sp_key_ciphers[i].oid, scheme.len) == 0) + ci = &sp_key_ciphers[i]; + if (!ci || scheme_params.tag != MBEDTLS_ASN1_OCTET_STRING || scheme_params.len != ci->iv_len) return 0; + /* PBKDF2-params ::= SEQUENCE { salt OCTET STRING, iterationCount + * INTEGER, keyLength INTEGER OPTIONAL, prf AlgorithmIdentifier DEFAULT + * hmacWithSHA1 } */ + unsigned char *k = kdf_params.p; + const unsigned char *k_end = kdf_params.p + kdf_params.len; + size_t salt_len; + int iterations, key_len = (int)ci->key_len; + if (kdf_params.tag != (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) || + mbedtls_asn1_get_tag(&k, k_end, &salt_len, MBEDTLS_ASN1_OCTET_STRING) != 0) + return 0; + const unsigned char *salt = k; + k += salt_len; + if (mbedtls_asn1_get_int(&k, k_end, &iterations) != 0 || iterations < 1) return 0; + if (k < k_end && *k == MBEDTLS_ASN1_INTEGER && (mbedtls_asn1_get_int(&k, k_end, &key_len) != 0 || + key_len != (int)ci->key_len)) + return 0; + mbedtls_md_type_t prf = MBEDTLS_MD_SHA1; + if (k < k_end) { + mbedtls_asn1_buf prf_oid, prf_params; + if (mbedtls_asn1_get_alg(&k, k_end, &prf_oid, &prf_params) != 0 || mbedtls_oid_get_md_hmac(&prf_oid, &prf) != 0) + return 0; + } + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_OCTET_STRING) != 0) return 0; + unsigned char key[32]; + if (mbedtls_pkcs5_pbkdf2_hmac_ext(prf, pass, pass_len, salt, salt_len, (unsigned)iterations, + (uint32_t)ci->key_len, key) != 0) + return 0; + *out = sp_key_cipher_run(ci, false, key, scheme_params.p, p, n, out_len); + mbedtls_platform_zeroize(key, sizeof key); + return *out ? 1 : -1; +} + static ScrDyn *sp_dyn_buffer(const unsigned char *data, size_t len) { ScrBytes *bytes = scr_bytes_new(SCR_BYTES_U8, (double)len); bytes->is_buffer = true; @@ -761,12 +1507,13 @@ static SpKey *sp_public_of(SpKey *k) { return pub; } unsigned char der[SP_DER_MAX]; - int n = sp_write_spki_der(k, der, sizeof der); + int n = k->rsa_pss ? mbedtls_pk_write_pubkey_der(&k->pk, der, sizeof der) : sp_write_spki_der(k, der, sizeof der); SpKey *pub = sp_key_alloc(false); if (n < 0 || mbedtls_pk_parse_public_key(&pub->pk, der + sizeof der - n, (size_t)n) != 0) { sp_key_release(pub); return NULL; } + sp_pss_copy(pub, k); return pub; } @@ -888,7 +1635,158 @@ static SpKey *sp_ed_from_der(const unsigned char *der, size_t len, bool private_ return k; } +/* RSASSA-PSS-params at *p (RFC 4055): true with the restrictions set on + * `k`, false when they are malformed or unsupported, which leaves the key + * without restrictions, as OpenSSL reads a negative or oversized salt. */ +static bool sp_pss_params_read(unsigned char **p, const unsigned char *end, SpKey *k) { + size_t n; + if (mbedtls_asn1_get_tag(p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return false; + const unsigned char *params_end = *p + n; + mbedtls_md_type_t md = MBEDTLS_MD_SHA1, mgf1 = MBEDTLS_MD_SHA1; + int salt = 20; + if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0)) { + mbedtls_asn1_buf oid, params; + if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0) != 0 || + mbedtls_asn1_get_alg(p, *p + n, &oid, ¶ms) != 0 || mbedtls_oid_get_md_alg(&oid, &md) != 0 || + !sp_pss_md_name(md)) + return false; + } + if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1)) { + mbedtls_asn1_buf oid, params; + if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1) != 0 || + mbedtls_asn1_get_alg(p, *p + n, &oid, ¶ms) != 0 || oid.len != MBEDTLS_OID_SIZE(MBEDTLS_OID_MGF1) || + memcmp(oid.p, MBEDTLS_OID_MGF1, oid.len) != 0) + return false; + /* MGF1's parameters are the hash's AlgorithmIdentifier, whose + * content starts with the hash's OID. */ + if (params.tag != (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) return false; + unsigned char *alg = params.p; + mbedtls_asn1_buf hash_oid = {.tag = MBEDTLS_ASN1_OID}; + if (mbedtls_asn1_get_tag(&alg, params.p + params.len, &hash_oid.len, MBEDTLS_ASN1_OID) != 0) return false; + hash_oid.p = alg; + if (mbedtls_oid_get_md_alg(&hash_oid, &mgf1) != 0 || !sp_pss_md_name(mgf1)) return false; + } + if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2)) { + if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2) != 0 || + mbedtls_asn1_get_int(p, *p + n, &salt) != 0 || salt < 0) + return false; + } + *p = (unsigned char *)params_end; + k->pss_params = true; + k->pss_md = md; + k->pss_mgf1 = mgf1; + k->pss_salt = salt; + return true; +} + +/* An RSA-PSS key from PKCS#8 or SubjectPublicKeyInfo DER whose algorithm + * is id-RSASSA-PSS; NULL when the DER is neither, or with *failed set and + * Node's decoder error pending. A private key wanted as public answers its + * public key. */ +static SpKey *sp_public_of(SpKey *k); + +static SpKey *sp_pss_from_der(const unsigned char *der, size_t len, bool private_key, bool *failed) { + *failed = false; + unsigned char *p = (unsigned char *)der; + const unsigned char *end = der + len; + size_t n; + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return NULL; + end = p + n; + bool pkcs8 = p < end && *p == MBEDTLS_ASN1_INTEGER; + if (pkcs8) { + int version; + if (mbedtls_asn1_get_int(&p, end, &version) != 0 || version != 0) return NULL; + } + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return NULL; + const unsigned char *alg_end = p + n; + size_t oid_len; + if (mbedtls_asn1_get_tag(&p, alg_end, &oid_len, MBEDTLS_ASN1_OID) != 0 || + oid_len != MBEDTLS_OID_SIZE(MBEDTLS_OID_RSASSA_PSS) || memcmp(p, MBEDTLS_OID_RSASSA_PSS, oid_len) != 0) + return NULL; + p += oid_len; + SpKey *k = sp_key_alloc(pkcs8); + k->rsa_pss = true; + if (p < alg_end && !sp_pss_params_read(&p, alg_end, k)) k->pss_params = false; + p = (unsigned char *)alg_end; + int r; + if (pkcs8) { + r = mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_OCTET_STRING); + if (r == 0) r = mbedtls_pk_parse_key(&k->pk, p, n, NULL, 0, sp_random, NULL); + } else { + r = private_key ? -1 : mbedtls_asn1_get_bitstring_null(&p, end, &n); + if (r == 0) r = mbedtls_pk_parse_public_key(&k->pk, p, n); + } + if (r != 0 || !sp_is_rsa(k)) { + sp_key_release(k); + *failed = true; + sp_unsupported_input(); + return NULL; + } + if (pkcs8 && !private_key) { + SpKey *pub = sp_public_of(k); + sp_key_release(k); + return pub; + } + return k; +} + +/* Whether `der` is a PKCS#8 EncryptedPrivateKeyInfo: a SEQUENCE of a PKCS#5 + * or PKCS#12 password-based AlgorithmIdentifier and an OCTET STRING. */ +static bool sp_is_encrypted_pkcs8(const unsigned char *der, size_t len) { + unsigned char *p = (unsigned char *)der; + const unsigned char *end = der + len; + size_t n; + mbedtls_asn1_buf alg, params; + if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return false; + end = p + n; + if (mbedtls_asn1_get_alg(&p, end, &alg, ¶ms) != 0) return false; + static const char pkcs5[] = MBEDTLS_OID_PKCS5, pkcs12[] = MBEDTLS_OID_PKCS12_PBE; + bool password = (alg.len > sizeof pkcs5 - 1 && memcmp(alg.p, pkcs5, sizeof pkcs5 - 1) == 0) || + (alg.len > sizeof pkcs12 - 1 && memcmp(alg.p, pkcs12, sizeof pkcs12 - 1) == 0); + return password && mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_OCTET_STRING) == 0 && p + n == end; +} + +static SpKey *sp_parse_with(const unsigned char *data, size_t len, bool private_key, const ScrBytes *pass); + static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) { + return sp_parse_with(data, len, private_key, NULL); +} + +/* sp_parse with a passphrase for an encrypted private key (NULL: none). */ +static SpKey *sp_parse_with(const unsigned char *data, size_t len, bool private_key, const ScrBytes *pass) { + { + unsigned char *decoded; + const unsigned char *der; + size_t der_len; + if (sp_der_of(data, len, &decoded, &der, &der_len) && sp_is_encrypted_pkcs8(der, der_len)) { + bool from_pem = decoded != NULL; + SpKey *k = NULL; + unsigned char *plain = NULL; + size_t plain_len = 0; + int r = 0; + if (!pass) { + if (from_pem) sp_throw_passphrase_missing(true); + else sp_throw(SCR_ERR_TYPE, "ERR_MISSING_PASSPHRASE", "Passphrase required for encrypted key"); + } else { + r = sp_decrypt_pkcs8(der, der_len, pass->data, (size_t)scr_bytes_byte_len(pass), &plain, &plain_len); + if (r < 0) sp_throw_bad_decrypt(from_pem ? 0 : 1); + } + if (r > 0) { + k = sp_parse_with(plain, plain_len, private_key, NULL); + mbedtls_platform_zeroize(plain, plain_len); + free(plain); + } + if (decoded) { + mbedtls_platform_zeroize(decoded, der_len); + free(decoded); + } + if (r == 0 && pass) sp_not_supported("private keys encrypted with this scheme or cipher"); + return k; + } else if (decoded) { + mbedtls_platform_zeroize(decoded, der_len); + free(decoded); + } + } { unsigned char *decoded; const unsigned char *der; @@ -896,6 +1794,7 @@ static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) if (sp_der_of(data, len, &decoded, &der, &der_len)) { bool failed; SpKey *ed = sp_ed_from_der(der, der_len, private_key, &failed); + if (!ed && !failed) ed = sp_pss_from_der(der, der_len, private_key, &failed); if (decoded) { mbedtls_platform_zeroize(decoded, der_len); free(decoded); @@ -912,6 +1811,7 @@ static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) break; } } + if (pem && !sp_pem_dek_check(data, len, pass)) return NULL; unsigned char *buf = malloc(len + 1); if (!buf) scr_trap("scriptc: out of memory\n"); memcpy(buf, data, len); @@ -919,15 +1819,19 @@ static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) size_t n = pem ? len + 1 : len; SpKey *k = sp_key_alloc(private_key); bool ok; + const unsigned char *pwd = pass ? pass->data : NULL; + size_t pwd_len = pass ? (size_t)scr_bytes_byte_len(pass) : 0; + int private_rc = 0; if (private_key) { - ok = mbedtls_pk_parse_key(&k->pk, buf, n, NULL, 0, sp_random, NULL) == 0; + private_rc = mbedtls_pk_parse_key(&k->pk, buf, n, pwd, pwd_len, sp_random, NULL); + ok = private_rc == 0; } else { ok = mbedtls_pk_parse_public_key(&k->pk, buf, n) == 0; if (!ok) { SpKey *priv = sp_key_alloc(true); mbedtls_pk_free(&k->pk); mbedtls_pk_init(&k->pk); - if (mbedtls_pk_parse_key(&priv->pk, buf, n, NULL, 0, sp_random, NULL) == 0) { + if ((private_rc = mbedtls_pk_parse_key(&priv->pk, buf, n, pwd, pwd_len, sp_random, NULL)) == 0) { sp_key_release(k); k = sp_public_of(priv); ok = k != NULL; @@ -949,7 +1853,10 @@ static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) free(buf); if (!ok) { sp_key_release(k); - sp_unsupported_input(); + /* An encrypted traditional PEM key mbedTLS read. */ + if (private_rc == MBEDTLS_ERR_PK_PASSWORD_REQUIRED) sp_throw_passphrase_missing(false); + else if (private_rc == MBEDTLS_ERR_PK_PASSWORD_MISMATCH) sp_throw_bad_decrypt(2); + else sp_unsupported_input(); return NULL; } if (!sp_key_supported(k)) { @@ -1011,13 +1918,16 @@ static ScrDyn *sp_create_key(ScrDyn *input, bool private_key) { const ScrDyn *format = scr_dyn_undefined(); const ScrDyn *type = scr_dyn_undefined(); const ScrDyn *encoding = NULL; + const ScrDyn *passphrase = NULL; if (sp_is_object(input) && !scr_buffer_storage_is(input)) { data = sp_opt(input, "key"); format = sp_opt(input, "format"); type = sp_opt(input, "type"); encoding = sp_opt(input, "encoding"); - if (!sp_absent(sp_opt(input, "passphrase"))) { - sp_not_supported("encrypted private keys"); + passphrase = sp_opt(input, "passphrase"); + if (!sp_absent(passphrase) && passphrase->kind != SCR_DYN_STR && passphrase->kind != SCR_DYN_BYTES && + !scr_buffer_storage_is(passphrase)) { + sp_property_invalid("key.passphrase", passphrase); return NULL; } SpKey *inner = sp_key_of(data); @@ -1055,7 +1965,16 @@ static ScrDyn *sp_create_key(ScrDyn *input, bool private_key) { scr_dyn_arg_type_fail(data == input ? "key" : "key.key", sp_key_input_types, data); return NULL; } - SpKey *k = sp_parse(bytes->data, (size_t)scr_bytes_byte_len(bytes), private_key); + ScrBytes *pass = NULL; + if (passphrase && !sp_absent(passphrase)) { + pass = sp_source_bytes(passphrase, NULL); + if (!pass) { + scr_bytes_release(bytes); + return NULL; + } + } + SpKey *k = sp_parse_with(bytes->data, (size_t)scr_bytes_byte_len(bytes), private_key, pass); + if (pass) scr_bytes_release(pass); scr_bytes_release(bytes); return k ? sp_key_box(k) : NULL; } @@ -1296,7 +2215,13 @@ static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) { scr_dyn_arg_type_fail("options", "of type object", options ? options : scr_dyn_undefined()); return NULL; } - if (sp_is_str(format, "jwk")) return sp_export_jwk(k); + if (sp_is_str(format, "jwk")) { + if (k->rsa_pss) { + sp_throw(SCR_ERR_ERROR, "ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE", "Unsupported JWK Key Type."); + return NULL; + } + return sp_export_jwk(k); + } bool pem = sp_is_str(format, "pem"); if (!pem && !sp_is_str(format, "der")) { sp_property_invalid("options.format", format); @@ -1311,11 +2236,7 @@ static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) { sp_property_invalid("options.type", type); return NULL; } - if (!sp_absent(sp_opt(options, "cipher")) || !sp_absent(sp_opt(options, "passphrase"))) { - sp_not_supported("encrypted private key export"); - return NULL; - } - if (pkcs1 && !sp_is_rsa(k)) { + if (pkcs1 && (!sp_is_rsa(k) || k->rsa_pss)) { sp_incompatible("pkcs1", "can only be used for RSA keys"); return NULL; } @@ -1323,6 +2244,30 @@ static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) { sp_incompatible("sec1", "can only be used for EC keys"); return NULL; } + /* parseKeyEncoding's cipher and passphrase checks, then the cipher. */ + const ScrDyn *cipher = sp_opt(options, "cipher"), *passphrase = sp_opt(options, "passphrase"); + const SpKeyCipher *ci = NULL; + if (k->private_key) { + bool encrypted = !sp_absent(cipher) && cipher->kind != SCR_DYN_NULL; + if (encrypted) { + if (cipher->kind != SCR_DYN_STR) { + sp_property_invalid("options.cipher", cipher); + return NULL; + } + if (!pem && (pkcs1 || sec1)) { + sp_incompatible(pkcs1 ? "pkcs1" : "sec1", "does not support encryption"); + return NULL; + } + if (passphrase->kind != SCR_DYN_STR && passphrase->kind != SCR_DYN_BYTES && !scr_buffer_storage_is(passphrase)) { + sp_property_invalid("options.passphrase", passphrase); + return NULL; + } + if (!sp_key_cipher(cipher->v.str, &ci)) return NULL; + } else if (!sp_absent(passphrase)) { + sp_property_invalid_because("options.cipher", cipher, "is required when a passphrase is specified"); + return NULL; + } + } unsigned char *der = malloc(SP_DER_MAX); if (!der) scr_trap("scriptc: out of memory\n"); int n; @@ -1343,6 +2288,26 @@ static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) { ScrDyn *result = NULL; if (n < 0) { sp_unsupported_input(); + } else if (ci && !sp_key_cipher_ready(ci, pkcs8, (size_t)n)) { + /* OpenSSL's error, raised once the key is encoded. */ + } else if (ci) { + const unsigned char *start = der + SP_DER_MAX - n; + ScrBytes *pass = sp_source_bytes(passphrase, NULL); + const unsigned char *pass_data = pass ? pass->data : (const unsigned char *)""; + size_t pass_len = pass ? (size_t)scr_bytes_byte_len(pass) : 0; + if (pkcs8) { + size_t enc_len; + unsigned char *enc = sp_encrypt_pkcs8(start, (size_t)n, ci, pass_data, pass_len, &enc_len); + if (enc) { + result = pem ? sp_dyn_str(sp_pem("ENCRYPTED PRIVATE KEY", enc, enc_len)) : sp_dyn_buffer(enc, enc_len); + free(enc); + } + } else { + ScrStr *text = sp_encrypted_pem(label, start, (size_t)n, ci, pass_data, pass_len); + if (text) result = sp_dyn_str(text); + } + if (pass) scr_bytes_release(pass); + if (!result) sp_unsupported_input(); } else { const unsigned char *start = der + SP_DER_MAX - n; result = pem ? sp_dyn_str(sp_pem(label, start, (size_t)n)) : sp_dyn_buffer(start, (size_t)n); @@ -1372,6 +2337,12 @@ static ScrDyn *sp_key_details(SpKey *k) { ScrBigInt *big = scr_bigint_from_u64(exponent); sp_set(details, "publicExponent", scr_dyn_new_bigint(big)); scr_bigint_release(big); + if (k->pss_params) { + const char *md = sp_pss_md_name(k->pss_md), *mgf1 = sp_pss_md_name(k->pss_mgf1); + sp_set(details, "hashAlgorithm", sp_dyn_str(scr_str_new(md, strlen(md)))); + sp_set(details, "mgf1HashAlgorithm", sp_dyn_str(scr_str_new(mgf1, strlen(mgf1)))); + sp_set(details, "saltLength", scr_dyn_new_num(k->pss_salt)); + } } else { const SpCurve *curve = sp_curve_of(k); sp_set(details, "namedCurve", sp_dyn_str(scr_str_new(curve->name, strlen(curve->name)))); @@ -1429,6 +2400,7 @@ static ScrDyn *sp_key_get(void *h, const char *name, size_t len) { if (len == 17 && memcmp(name, "asymmetricKeyType", 17) == 0) return sp_is_ed(k) ? sp_dyn_str(scr_str_new("ed25519", 7)) : sp_is_x(k) ? sp_dyn_str(scr_str_new("x25519", 6)) + : k->rsa_pss ? sp_dyn_str(scr_str_new("rsa-pss", 7)) : sp_is_rsa(k) ? sp_dyn_str(scr_str_new("rsa", 3)) : sp_dyn_str(scr_str_new("ec", 2)); if (len == 20 && memcmp(name, "asymmetricKeyDetails", 20) == 0) return sp_key_details(k); @@ -1640,7 +2612,7 @@ static bool sp_check_encoding_plain(const ScrDyn *enc, const char *name, const S return false; } } - if (!*refusal && encrypted) *refusal = "encrypted private key export"; + (void)encrypted; return true; } @@ -1648,10 +2620,122 @@ static bool sp_check_encoding_plain(const ScrDyn *enc, const char *name, const S typedef struct { enum { SP_GEN_RSA, SP_GEN_EC, SP_GEN_ED25519, SP_GEN_X25519 } kind; double bits, exponent; /* RSA */ + bool pss; /* an RSA-PSS key, restricted when pss_params */ + bool pss_params, md_set, mgf1_set, salt_set; + mbedtls_md_type_t md, mgf1; + int salt; const SpCurve *curve; /* EC */ } SpKeyGen; +static bool sp_digest_of(const ScrStr *name, mbedtls_md_type_t *md); + +/* A digest name of rsa-pss's options: true with *md set, or false with + * Node's ERR_CRYPTO_INVALID_DIGEST pending ("Invalid digest: x" or "Invalid + * MGF1 digest: x"), or the SC2020 refusal for a digest OpenSSL has and + * RSASSA-PSS-params here cannot name. */ +static bool sp_pss_digest(const ScrDyn *name, bool mgf1, mbedtls_md_type_t *md) { + if (sp_digest_of(name->v.str, md) && sp_pss_md_name(*md)) return true; + if (scr_digest_known(name->v.str->data, name->v.str->len)) { + sp_not_supported("generateKeyPair rsa-pss with this digest"); + return false; + } + scr_pki_throw_fmt(SCR_ERR_TYPE, "ERR_CRYPTO_INVALID_DIGEST", "Invalid %sdigest: %.*s", mgf1 ? "MGF1 " : "", + (int)name->v.str->len, name->v.str->data); + return false; +} + +/* rsa-pss's options after the modulus and exponent, in Node's order + * (lib/internal/crypto/keygen.js): saltLength, hashAlgorithm, + * mgf1HashAlgorithm, and the deprecated hash and mgf1Hash with their + * DEP0154 warnings, then the digest lookups of RsaKeyGenTraits. */ +static bool sp_pss_keygen_options(const ScrDyn *options, SpKeyGen *gen) { + const ScrDyn *salt = sp_opt(options, "saltLength"); + const ScrDyn *hash_alg = sp_opt(options, "hashAlgorithm"), *mgf1_alg = sp_opt(options, "mgf1HashAlgorithm"); + const ScrDyn *hash = sp_opt(options, "hash"), *mgf1_hash = sp_opt(options, "mgf1Hash"); + if (!sp_absent(salt)) { + if (salt->kind != SCR_DYN_NUM) { + scr_dyn_prop_type_fail("options.saltLength", "of type number", salt); + return false; + } + double x = salt->v.num; + bool integer = isfinite(x) && trunc(x) == x; + if (!integer || x < 0 || x > 2147483647.0) { + ScrStr *received = sp_received(salt, true); + scr_pki_throw_fmt(SCR_ERR_RANGE, "ERR_OUT_OF_RANGE", + "The value of \"options.saltLength\" is out of range. It must be %s. Received %.*s", + integer ? ">= 0 && <= 2147483647" : "an integer", (int)received->len, received->data); + scr_str_release(received); + return false; + } + gen->salt_set = true; + gen->salt = (int)x; + } + if (!sp_absent(hash_alg) && hash_alg->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.hashAlgorithm", "of type string", hash_alg); + return false; + } + if (!sp_absent(mgf1_alg) && mgf1_alg->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.mgf1HashAlgorithm", "of type string", mgf1_alg); + return false; + } + const struct { + const ScrDyn *value, *preferred; + const char *name, *preferred_name; + } deprecated[] = { + {hash, hash_alg, "options.hash", "options.hashAlgorithm"}, + {mgf1_hash, mgf1_alg, "options.mgf1Hash", "options.mgf1HashAlgorithm"}, + }; + for (size_t i = 0; i < 2; i++) { + if (sp_absent(deprecated[i].value)) continue; + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "\""); + scr_jb_puts(&text, deprecated[i].name); + scr_jb_puts(&text, "\" is deprecated, use \""); + scr_jb_puts(&text, deprecated[i].preferred_name); + scr_jb_puts(&text, "\" instead."); + /* process.emitWarning: reported on the next tick, dropped under + * process.noDeprecation, and thrown from its own tick under + * process.throwDeprecation. */ + ScrStr *message = scr_jb_finish(&text); + ScrStr *code = scr_str_new("DEP0154", 7); + ScrDyn *warning = scr_warning_new(message, "DeprecationWarning", code); + scr_str_release(code); + scr_str_release(message); + scr_warning_emit_deferred(warning); + scr_dyn_release(warning); + if (deprecated[i].value->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail(deprecated[i].name, "of type string", deprecated[i].value); + return false; + } + const ScrDyn *preferred = deprecated[i].preferred; + if (preferred->kind == SCR_DYN_STR && preferred->v.str->len > 0 && + !scr_str_eq(preferred->v.str, deprecated[i].value->v.str)) { + sp_property_invalid(deprecated[i].name, deprecated[i].value); + return false; + } + } + /* `hashAlgorithm || hash`: an empty string reads as absent. */ + const ScrDyn *md = hash_alg->kind == SCR_DYN_STR && hash_alg->v.str->len > 0 ? hash_alg : hash; + const ScrDyn *mgf1 = mgf1_alg->kind == SCR_DYN_STR && mgf1_alg->v.str->len > 0 ? mgf1_alg : mgf1_hash; + if (md->kind == SCR_DYN_STR) { + if (!sp_pss_digest(md, false, &gen->md)) return false; + gen->md_set = true; + } + if (mgf1->kind == SCR_DYN_STR) { + if (!sp_pss_digest(mgf1, true, &gen->mgf1)) return false; + gen->mgf1_set = true; + } + /* OpenSSL's defaults: MGF1 over the digest and a salt of the digest's + * length once a digest is given, SHA-1 and 20 otherwise. */ + gen->pss_params = gen->md_set || gen->mgf1_set || gen->salt_set; + if (!gen->md_set) gen->md = MBEDTLS_MD_SHA1; + if (!gen->mgf1_set) gen->mgf1 = gen->md; + if (!gen->salt_set) gen->salt = gen->md_set ? (int)mbedtls_md_get_size(mbedtls_md_info_from_type(gen->md)) : 20; + return true; +} + /* createJob's checks for generateKeyPair(Sync)(type, options) in Node's * order: the type, the options object, the public and private key * encodings, then the options of the type. False with the error pending. @@ -1677,7 +2761,8 @@ static bool sp_keygen_check(const ScrDyn *type, const ScrDyn *options, SpKeyGen if (!sp_absent(exponent) && exponent->kind != SCR_DYN_NULL && !sp_check_uint32(exponent, "options.publicExponent", &gen->exponent)) return false; - if (pss && !refusal) refusal = "generateKeyPairSync for this key type"; + gen->pss = pss; + if (pss && !sp_pss_keygen_options(options, gen)) return false; } else if (sp_is_str(type, "dsa") || sp_is_str(type, "dh")) { if (!sp_check_object(options, "options")) return false; if (!refusal) refusal = "generateKeyPairSync for this key type"; @@ -1723,6 +2808,23 @@ static bool sp_keygen_check(const ScrDyn *type, const ScrDyn *options, SpKeyGen } if (!refusal) refusal = "generateKeyPairSync for this key type"; } + /* The job's private key cipher, which Node looks up once the options + * of the type are read. */ + { + const ScrDyn *enc = sp_opt(options, "privateKeyEncoding"); + const ScrDyn *cipher = sp_is_object(enc) ? sp_opt(enc, "cipher") : scr_dyn_undefined(); + const SpKeyCipher *ci; + if (cipher->kind == SCR_DYN_STR) { + if (!sp_key_cipher(cipher->v.str, &ci)) return false; + /* OpenSSL's errors for the cipher come from the export once the + * key is generated; a cipher it encrypts with and keys here do not + * is refused first. */ + bool pkcs8 = sp_is_str(sp_opt(enc, "type"), "pkcs8"); + if (ci->type == MBEDTLS_CIPHER_NONE && (pkcs8 ? ci->pkcs8_error : ci->traditional_error) == SP_KC_OK && + !refusal) + refusal = "private keys encrypted with this cipher"; + } + } if (refusal) { sp_not_supported(refusal); return false; @@ -1730,6 +2832,54 @@ static bool sp_keygen_check(const ScrDyn *type, const ScrDyn *options, SpKeyGen return gen->kind != SP_GEN_ED25519 || sp_ed() != NULL; } +/* An RSA key of `bits` bits with public exponent `exponent`, within + * OpenSSL's limits rather than mbedtls_rsa_gen_key's (even lengths from + * 1024 bits, exponents up to INT_MAX): any length from 512 bits and odd + * exponents up to 2^32 - 1. From 2048 bits OpenSSL generates two primes + * of equal length (SP 800-56B), so an odd length yields one bit less. + * The primes have (bits + 1) / 2 and bits / 2 bits, each with its top + * bits set (mbedtls_mpi_gen_prime's FIPS 186-4 bound), so their product + * has exactly `bits` bits; each prime minus one is coprime to the + * exponent. 0 on success. */ +static int sp_rsa_generate(mbedtls_rsa_context *rsa, unsigned bits, uint64_t exponent) { + if (bits >= 2048) bits &= ~1u; + mbedtls_mpi P, Q, E, N, T, G; + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&T); + mbedtls_mpi_init(&G); + unsigned char e_bytes[8]; + for (int i = 0; i < 8; i++) e_bytes[i] = (unsigned char)(exponent >> (56 - 8 * i)); + int r = mbedtls_mpi_read_binary(&E, e_bytes, sizeof e_bytes); + const int flags = MBEDTLS_MPI_GEN_PRIME_FLAG_LOW_ERR; + while (r == 0) { + r = mbedtls_mpi_gen_prime(&P, (bits + 1) / 2, flags, sp_random, NULL); + if (r == 0) r = mbedtls_mpi_sub_int(&T, &P, 1); + if (r == 0) r = mbedtls_mpi_gcd(&G, &E, &T); + if (r != 0 || mbedtls_mpi_cmp_int(&G, 1) != 0) continue; + r = mbedtls_mpi_gen_prime(&Q, bits / 2, flags, sp_random, NULL); + if (r == 0) r = mbedtls_mpi_sub_int(&T, &Q, 1); + if (r == 0) r = mbedtls_mpi_gcd(&G, &E, &T); + if (r != 0 || mbedtls_mpi_cmp_int(&G, 1) != 0 || mbedtls_mpi_cmp_mpi(&P, &Q) == 0) continue; + r = mbedtls_mpi_mul_mpi(&N, &P, &Q); + if (r == 0 && mbedtls_mpi_bitlen(&N) == bits) break; + } + /* The larger prime is p, as OpenSSL orders them. */ + if (r == 0 && mbedtls_mpi_cmp_mpi(&P, &Q) < 0) mbedtls_mpi_swap(&P, &Q); + if (r == 0) r = mbedtls_rsa_import(rsa, &N, &P, &Q, NULL, &E); + if (r == 0) r = mbedtls_rsa_complete(rsa); + if (r == 0) r = mbedtls_rsa_check_privkey(rsa); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); + mbedtls_mpi_free(&E); + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&T); + mbedtls_mpi_free(&G); + return r; +} + /* OpenSSL's error for an RSA modulus length or public exponent it does not * generate, as Node reports it, or NULL. */ static const char *sp_rsa_keygen_failure(double bits, double exponent) { @@ -1773,16 +2923,22 @@ static SpKey *sp_keygen_run(const SpKeyGen *gen, bool *job_error) { scr_pki_throw_fmt(SCR_ERR_ERROR, NULL, "%s", failure); return NULL; } - /* mbedTLS generates even modulus lengths from 1024 to 8192 bits and, - * past the checks above, odd exponents up to INT_MAX. */ + /* Moduli past mbedTLS's 8192-bit numbers stay unsupported (OpenSSL + * generates up to 16384 bits). */ double b = gen->bits, e = gen->exponent; - if (b < MBEDTLS_RSA_GEN_KEY_MIN_BITS || b > MBEDTLS_MPI_MAX_BITS || fmod(b, 2) != 0 || e > 2147483647.0 || - mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 || - mbedtls_rsa_gen_key(mbedtls_pk_rsa(k->pk), sp_random, NULL, (unsigned)b, (int)e) != 0) { + if (b > MBEDTLS_MPI_MAX_BITS || mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 || + sp_rsa_generate(mbedtls_pk_rsa(k->pk), (unsigned)b, (uint64_t)e) != 0) { sp_key_release(k); sp_not_supported("generateKeyPairSync with this RSA modulus length or exponent"); return NULL; } + if (gen->pss) { + k->rsa_pss = true; + k->pss_params = gen->pss_params; + k->pss_md = gen->md; + k->pss_mgf1 = gen->mgf1; + k->pss_salt = gen->salt; + } return k; } if (mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)) != 0 || @@ -1799,7 +2955,7 @@ static SpKey *sp_keygen_run(const SpKeyGen *gen, bool *job_error) { * pending, and *job_error as sp_keygen_run sets it. */ static ScrDyn *sp_key_pair(const ScrDyn *type, const ScrDyn *options, bool *job_error) { *job_error = false; - SpKeyGen gen; + SpKeyGen gen = {0}; if (!sp_keygen_check(type, options, &gen)) return NULL; SpKey *k = sp_keygen_run(&gen, job_error); if (!k) return NULL; @@ -1910,8 +3066,11 @@ static bool sp_algorithm(const ScrDyn *alg, mbedtls_md_type_t *md) { typedef struct { SpKey *key; /* +1 */ int padding; /* RSA_PKCS1_PADDING (1) or RSA_PKCS1_PSS_PADDING (6) */ - int salt; /* -1 digest length, -2 maximum (sign) or auto (verify) */ + int salt; /* -1 digest length, -2 maximum (sign) or auto (verify), + * -4 the digest length or the maximum, if smaller */ bool p1363; /* dsaEncoding: "ieee-p1363" */ + bool padding_given, salt_given; + mbedtls_md_type_t mgf1; /* PSS's MGF1 digest; NONE for the message's */ } SpSigKey; /* The key argument of a signing operation: a KeyObject, key material, or @@ -1932,6 +3091,9 @@ static bool sp_sig_key_plain(const ScrDyn *value, bool sign, SpSigKey *out) { out->padding = 1; out->salt = sign ? -2 : -2; out->p1363 = false; + out->padding_given = false; + out->salt_given = false; + out->mgf1 = MBEDTLS_MD_NONE; const ScrDyn *options = NULL; if (sp_is_object(value) && !sp_key_of(value) && !scr_buffer_storage_is(value)) { options = value; @@ -1948,6 +3110,7 @@ static bool sp_sig_key_plain(const ScrDyn *value, bool sign, SpSigKey *out) { return false; } out->padding = (int)padding->v.num; + out->padding_given = true; } const ScrDyn *salt = sp_opt(value, "saltLength"); if (!sp_absent(salt)) { @@ -1956,6 +3119,7 @@ static bool sp_sig_key_plain(const ScrDyn *value, bool sign, SpSigKey *out) { return false; } out->salt = (int)salt->v.num; + out->salt_given = true; } } SpKey *direct = sp_key_of(options ? sp_opt(options, "key") : value); @@ -2022,6 +3186,40 @@ static int sp_p1363_to_der(const unsigned char *sig, size_t len, unsigned char * static size_t sp_ec_size(SpKey *k) { return (mbedtls_pk_ec(k->pk)->MBEDTLS_PRIVATE(grp).nbits + 7) / 8; } +/* An RSA-PSS key's restrictions on a signature over a `md` digest, as + * OpenSSL's provider applies them: PSS padding only (the default), and + * for a key with RSASSA-PSS-params its digest, its MGF1 digest and salts + * of at least its own (the default). Without parameters a signature + * salts with the digest's length, or the most the key holds when that is + * less (RSA_PSS_SALTLEN_AUTO_DIGEST_MAX). False with OpenSSL's error + * pending. */ +static bool sp_pss_key_rules(SpSigKey *key, mbedtls_md_type_t md, size_t hash_len, bool sign) { + const SpKey *k = key->key; + if (!k->rsa_pss) return true; + if (key->padding_given && key->padding != 6) { + sp_throw_openssl("1C8000A5", "Provider routines", "illegal or unsupported padding mode", + "ERR_OSSL_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE", true); + return false; + } + key->padding = 6; + if (!k->pss_params) { + if (!key->salt_given) key->salt = sign ? -4 : -2; + return true; + } + if (md != k->pss_md) { + sp_throw_openssl("1C8000AE", "Provider routines", "digest not allowed", "ERR_OSSL_DIGEST_NOT_ALLOWED", true); + return false; + } + key->mgf1 = k->pss_mgf1; + if (!key->salt_given) { + key->salt = k->pss_salt; + } else if (key->salt != -2 && (key->salt == -1 ? (int)hash_len : key->salt) < k->pss_salt) { + sp_throw_openssl("1C8000AC", "Provider routines", "pss saltlen too small", "ERR_OSSL_PSS_SALTLEN_TOO_SMALL", true); + return false; + } + return true; +} + /* Applies the RSA padding of a signature operation. */ static bool sp_rsa_padding(SpSigKey *key, mbedtls_md_type_t md) { if (!sp_is_rsa(key->key)) return true; @@ -2030,11 +3228,119 @@ static bool sp_rsa_padding(SpSigKey *key, mbedtls_md_type_t md) { return mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE) == 0; } +/* MGF1 over `mgf1` (RFC 8017 B.2.1), XORed into mask[0, len). */ +static int sp_mgf1_xor(mbedtls_md_type_t mgf1, const unsigned char *seed, size_t seed_len, unsigned char *mask, + size_t len) { + const mbedtls_md_info_t *info = mbedtls_md_info_from_type(mgf1); + size_t h = mbedtls_md_get_size(info); + unsigned char block[MBEDTLS_MD_MAX_SIZE]; + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + int r = mbedtls_md_setup(&ctx, info, 0); + for (uint32_t counter = 0; r == 0 && (size_t)counter * h < len; counter++) { + unsigned char c[4] = {(unsigned char)(counter >> 24), (unsigned char)(counter >> 16), + (unsigned char)(counter >> 8), (unsigned char)counter}; + if ((r = mbedtls_md_starts(&ctx)) == 0 && (r = mbedtls_md_update(&ctx, seed, seed_len)) == 0 && + (r = mbedtls_md_update(&ctx, c, 4)) == 0 && (r = mbedtls_md_finish(&ctx, block)) == 0) { + for (size_t i = 0; i < h && (size_t)counter * h + i < len; i++) mask[(size_t)counter * h + i] ^= block[i]; + } + } + mbedtls_md_free(&ctx); + mbedtls_platform_zeroize(block, sizeof block); + return r; +} + +/* An RSASSA-PSS signature whose MGF1 digest differs from the message's + * (RFC 8017 9.1.1 and 8.1.1), which mbedtls_rsa_rsassa_pss_sign_ext + * cannot make: the encoding here, then the private-key operation. */ +static int sp_pss_sign_mgf1(mbedtls_rsa_context *rsa, mbedtls_md_type_t md, mbedtls_md_type_t mgf1, + const unsigned char *hash, size_t hash_len, int salt_len, unsigned char *sig) { + size_t bytes = mbedtls_rsa_get_len(rsa); + size_t em_bits = mbedtls_rsa_get_bitlen(rsa) - 1; + size_t em_len = (em_bits + 7) / 8; + size_t salt = (size_t)salt_len; + if (em_len < hash_len + salt + 2) return MBEDTLS_ERR_RSA_BAD_INPUT_DATA; + unsigned char em[MBEDTLS_MPI_MAX_SIZE] = {0}; + unsigned char *db = em, *h = em + em_len - hash_len - 1; + size_t db_len = em_len - hash_len - 1; + unsigned char salt_bytes[MBEDTLS_MPI_MAX_SIZE]; + int r = sp_random(NULL, salt_bytes, salt); + /* H = Hash(0x00 * 8 || mHash || salt) */ + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + static const unsigned char zeros[8] = {0}; + if (r == 0 && (r = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md), 0)) == 0 && + (r = mbedtls_md_starts(&ctx)) == 0 && (r = mbedtls_md_update(&ctx, zeros, 8)) == 0 && + (r = mbedtls_md_update(&ctx, hash, hash_len)) == 0 && (r = mbedtls_md_update(&ctx, salt_bytes, salt)) == 0) + r = mbedtls_md_finish(&ctx, h); + mbedtls_md_free(&ctx); + if (r == 0) { + /* DB = PS || 0x01 || salt, masked with MGF1(H). */ + db[db_len - salt - 1] = 0x01; + memcpy(db + db_len - salt, salt_bytes, salt); + r = sp_mgf1_xor(mgf1, h, hash_len, db, db_len); + } + if (r == 0) { + db[0] &= (unsigned char)(0xff >> (8 * em_len - em_bits)); + em[em_len - 1] = 0xbc; + unsigned char block[MBEDTLS_MPI_MAX_SIZE] = {0}; + memcpy(block + bytes - em_len, em, em_len); + r = mbedtls_rsa_private(rsa, sp_random, NULL, block, sig); + mbedtls_platform_zeroize(block, sizeof block); + } + mbedtls_platform_zeroize(em, sizeof em); + mbedtls_platform_zeroize(salt_bytes, sizeof salt_bytes); + return r; +} + +/* Verifies an RSASSA-PSS signature whose MGF1 digest differs from the + * message's (RFC 8017 9.1.2), which mbedtls_rsa_rsassa_pss_verify_ext + * cannot: it hashes M' with the MGF1 digest too. `salt_len` -2 accepts any + * salt length. */ +static bool sp_pss_verify_mgf1(mbedtls_rsa_context *rsa, mbedtls_md_type_t md, mbedtls_md_type_t mgf1, + const unsigned char *hash, size_t hash_len, int salt_len, + const unsigned char *sig, size_t sig_len) { + size_t bytes = mbedtls_rsa_get_len(rsa); + if (sig_len != bytes) return false; + unsigned char decoded[MBEDTLS_MPI_MAX_SIZE]; + if (mbedtls_rsa_public(rsa, sig, decoded) != 0) return false; + size_t em_bits = mbedtls_rsa_get_bitlen(rsa) - 1; + size_t em_len = (em_bits + 7) / 8; + if (bytes > em_len && decoded[0] != 0) return false; + unsigned char *em = decoded + (bytes - em_len); + if (em_len < hash_len + 2 || em[em_len - 1] != 0xbc) return false; + size_t db_len = em_len - hash_len - 1; + unsigned char *h = em + db_len; + unsigned char top = (unsigned char)(0xff >> (8 * em_len - em_bits)); + if (em[0] & (unsigned char)~top) return false; + unsigned char db[MBEDTLS_MPI_MAX_SIZE]; + memcpy(db, em, db_len); + if (sp_mgf1_xor(mgf1, h, hash_len, db, db_len) != 0) return false; + db[0] &= top; + size_t i = 0; + while (i < db_len && db[i] == 0) i++; + if (i == db_len || db[i] != 0x01) return false; + size_t salt = db_len - i - 1; + if (salt_len >= 0 && salt != (size_t)salt_len) return false; + static const unsigned char zeros[8] = {0}; + unsigned char check[MBEDTLS_MD_MAX_SIZE]; + mbedtls_md_context_t ctx; + mbedtls_md_init(&ctx); + int r; + if ((r = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md), 0)) == 0 && (r = mbedtls_md_starts(&ctx)) == 0 && + (r = mbedtls_md_update(&ctx, zeros, 8)) == 0 && (r = mbedtls_md_update(&ctx, hash, hash_len)) == 0 && + (r = mbedtls_md_update(&ctx, db + i + 1, salt)) == 0) + r = mbedtls_md_finish(&ctx, check); + mbedtls_md_free(&ctx); + return r == 0 && memcmp(check, h, hash_len) == 0; +} + /* Signs a digest: +1 Buffer or NULL with the error pending. */ static ScrDyn *sp_sign_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned char *hash, size_t hash_len) { unsigned char sig[MBEDTLS_PK_SIGNATURE_MAX_SIZE]; size_t sig_len = 0; int r; + if (!sp_pss_key_rules(key, md, hash_len, true)) return NULL; if (!sp_rsa_padding(key, md)) { sp_unsupported_input(); return NULL; @@ -2047,12 +3353,15 @@ static ScrDyn *sp_sign_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned size_t max = bytes - hash_len - 2 - ((bits - 1) % 8 == 0 ? 1 : 0); if (salt == -1) salt = (int)hash_len; else if (salt == -2) salt = (int)max; + else if (salt == -4) salt = (int)(hash_len < max ? hash_len : max); if (salt < 0 || (size_t)salt > max) { sp_throw(SCR_ERR_ERROR, "ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE", "error:1C8000A5:Provider routines::data too large for key size"); return NULL; } - r = mbedtls_rsa_rsassa_pss_sign_ext(rsa, sp_random, NULL, md, (unsigned)hash_len, hash, salt, sig); + r = key->mgf1 != MBEDTLS_MD_NONE && key->mgf1 != md + ? sp_pss_sign_mgf1(rsa, md, key->mgf1, hash, hash_len, salt, sig) + : mbedtls_rsa_rsassa_pss_sign_ext(rsa, sp_random, NULL, md, (unsigned)hash_len, hash, salt, sig); sig_len = bytes; } else { r = mbedtls_pk_sign(&key->key->pk, md, hash, hash_len, sig, sizeof sig, &sig_len, sp_random, NULL); @@ -2077,6 +3386,7 @@ static ScrDyn *sp_sign_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned * not verify or does not parse. */ static bool sp_verify_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned char *hash, size_t hash_len, const unsigned char *sig, size_t sig_len) { + if (!sp_pss_key_rules(key, md, hash_len, false)) return false; if (!sp_rsa_padding(key, md)) return false; if (sp_is_rsa(key->key) && key->padding == 6) { mbedtls_rsa_context *rsa = mbedtls_pk_rsa(key->key->pk); @@ -2084,6 +3394,9 @@ static bool sp_verify_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned c int salt = key->salt; if (salt == -1) salt = (int)hash_len; else if (salt == -2) salt = MBEDTLS_RSA_SALT_LEN_ANY; + if (key->mgf1 != MBEDTLS_MD_NONE && key->mgf1 != md) + return sp_pss_verify_mgf1(rsa, md, key->mgf1, hash, hash_len, salt == MBEDTLS_RSA_SALT_LEN_ANY ? -2 : salt, + sig, sig_len); return mbedtls_rsa_rsassa_pss_verify_ext(rsa, md, (unsigned)hash_len, hash, md, salt, sig) == 0; } if (sp_is_ec(key->key) && key->p1363) { @@ -2127,6 +3440,9 @@ ScrDyn *scr_crypto_sign_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key) { SpSigKey k; if (!sp_sig_key(key, true, &k)) return NULL; if (sp_reject_x25519(&k)) return NULL; + /* A null algorithm signs with an RSA-PSS key's own digest. */ + if ((algorithm->kind == SCR_DYN_UNDEF || algorithm->kind == SCR_DYN_NULL) && k.key->pss_params) + md = k.key->pss_md; ScrBytes *bytes = scr_bytes_buffer_source(data); if (sp_is_ed(k.key)) { ScrDyn *result = NULL; @@ -2165,6 +3481,8 @@ ScrDyn *scr_crypto_verify_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key, return NULL; } if (sp_reject_x25519(&k)) return NULL; + if ((algorithm->kind == SCR_DYN_UNDEF || algorithm->kind == SCR_DYN_NULL) && k.key->pss_params) + md = k.key->pss_md; ScrBytes *bytes = scr_bytes_buffer_source(data); ScrBytes *sig = scr_bytes_buffer_source(signature); if (sp_is_ed(k.key)) { @@ -2186,7 +3504,7 @@ ScrDyn *scr_crypto_verify_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key, scr_bytes_release(bytes); scr_bytes_release(sig); sp_key_release(k.key); - return scr_dyn_new_bool(ok); + return scr_exc_pending() ? NULL : scr_dyn_new_bool(ok); } /* ── Sign and Verify objects ──────────────────────────────────────────── */ @@ -2305,7 +3623,7 @@ static ScrDyn *sp_signer_invoke(void *h, ScrDyn *self, const char *method, ScrDy sig->data, (size_t)scr_bytes_byte_len(sig)); scr_bytes_release(sig); sp_key_release(k.key); - return scr_dyn_new_bool(ok); + return scr_exc_pending() ? NULL : scr_dyn_new_bool(ok); } scr_pki_throw_fmt(SCR_ERR_TYPE, NULL, "%s is not a function", what); return NULL; @@ -2466,7 +3784,11 @@ static const char sp_dh_public_types[] = /* Node's message names: a KeyObject's asymmetricKeyType. */ static const char *sp_type_name(const SpKey *k) { - return sp_is_ed(k) ? "ed25519" : sp_is_x(k) ? "x25519" : sp_is_rsa(k) ? "rsa" : "ec"; + return sp_is_ed(k) ? "ed25519" + : sp_is_x(k) ? "x25519" + : k->rsa_pss ? "rsa-pss" + : sp_is_rsa(k) ? "rsa" + : "ec"; } /* A key input of diffieHellman's options: a primitive other than a string @@ -2683,8 +4005,9 @@ ScrPkiKey *scr_pki_key_public_of(ScrPkiKey *key) { return sp_public_of(key); } ScrPkiKey *scr_pki_generate_rsa(unsigned bits, uint64_t exponent) { SpKey *k = sp_key_alloc(true); - if (exponent > 2147483647 || mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 || - mbedtls_rsa_gen_key(mbedtls_pk_rsa(k->pk), sp_random, NULL, bits, (int)exponent) != 0) { + if (bits < 512 || bits > MBEDTLS_MPI_MAX_BITS || exponent < 3 || exponent % 2 == 0 || + exponent > 4294967295u || mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 || + sp_rsa_generate(mbedtls_pk_rsa(k->pk), bits, exponent) != 0) { sp_key_release(k); return NULL; } diff --git a/packages/runtime/src/scr_webcrypto_pki.c b/packages/runtime/src/scr_webcrypto_pki.c index 7439a12db..458cfeae8 100644 --- a/packages/runtime/src/scr_webcrypto_pki.c +++ b/packages/runtime/src/scr_webcrypto_pki.c @@ -380,7 +380,8 @@ static ScrPromise *sw_generate_key(const ScrDyn *args) { scr_throw_domex("OperationError", "algorithm.publicExponent must be odd"); goto done; } - if (bits > 16384 || value < 3) { + /* The pki unit's numbers hold moduli up to 8192 bits. */ + if (bits > 8192 || value < 3) { scr_wc_unsupported("generateKey", "this RSA modulus length or public exponent"); goto done; } diff --git a/tests/corpus/8165-rsa-keygen-lengths.cjs b/tests/corpus/8165-rsa-keygen-lengths.cjs new file mode 100644 index 000000000..35e2cb28f --- /dev/null +++ b/tests/corpus/8165-rsa-keygen-lengths.cjs @@ -0,0 +1,40 @@ +// RSA key generation within OpenSSL's limits: any modulus length from +// 512 bits (an odd length from 2048 bits yields one bit less, as +// OpenSSL's SP 800-56B generation does) and odd exponents up to 2^32 - 1. +"use strict"; +const crypto = require("crypto"); + +const cases = [ + [512, 65537], + [513, 65537], + [1023, 3], + [1024, 0x10001], + [2049, 65537], + [768, 4294967295], + [520, 17], +]; +for (const [modulusLength, publicExponent] of cases) { + const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", { modulusLength, publicExponent }); + const details = privateKey.asymmetricKeyDetails; + const data = Buffer.from("rsa key lengths"); + const signature = crypto.sign("sha256", data, privateKey); + console.log( + modulusLength, + details.modulusLength, + details.publicExponent, + publicKey.asymmetricKeyDetails.modulusLength, + signature.length, + crypto.verify("sha256", data, publicKey, signature), + ); +} +for (const [modulusLength, publicExponent] of [[511, 65537], [1024, 1], [1024, 4], [1024, 0]]) { + try { + crypto.generateKeyPairSync("rsa", { modulusLength, publicExponent }); + console.log(modulusLength, publicExponent, "generated"); + } catch (error) { + console.log(modulusLength, publicExponent, error.message); + } +} +crypto.generateKeyPair("rsa", { modulusLength: 515, publicExponent: 3 }, (error, publicKey) => { + console.log("async", error, publicKey.asymmetricKeyDetails.modulusLength); +}); diff --git a/tests/corpus/8166-rsa-pss-keys.cjs b/tests/corpus/8166-rsa-pss-keys.cjs new file mode 100644 index 000000000..aad90f6aa --- /dev/null +++ b/tests/corpus/8166-rsa-pss-keys.cjs @@ -0,0 +1,104 @@ +// RSA-PSS keys (id-RSASSA-PSS): generation with and without +// RSASSA-PSS-params, their details, SubjectPublicKeyInfo and PKCS#8 in +// OpenSSL's encoding, import of both, and the restrictions on signatures: +// PSS padding only, the key's digest and MGF1 digest, and at least its +// salt length. The deprecated hash and mgf1Hash options run in a child +// process, whose DEP0154 reports are printed with process ids normalized. +"use strict"; +const { spawnSync } = require("child_process"); +const c = require("crypto"); +const fmt = (v) => JSON.stringify(v, (k, x) => (typeof x === "bigint" ? `${x}n` : x)); +const tryIt = (label, fn) => { + try { + const r = fn(); + console.log(label, typeof r === "string" ? r.split("\n")[0] : Buffer.isBuffer(r) ? `buffer ${r.length > 0}` : fmt(r)); + } catch (e) { + console.log(label, e.name, e.code, e.message); + } +}; +const algOf = (spki) => { + // The AlgorithmIdentifier: the SEQUENCE after the outer header. + const start = spki[1] & 0x80 ? 2 + (spki[1] & 0x7f) : 2; + const len = spki[start + 1]; + return spki.subarray(start, start + 2 + len).toString("hex"); +}; +const report = (label, options) => tryIt(label, () => c.generateKeyPairSync("rsa-pss", { modulusLength: 512, ...options }).publicKey.asymmetricKeyDetails); + +function main() { + const cases = { + none: {}, + sha256: { hashAlgorithm: "sha256" }, + full: { hashAlgorithm: "sha256", mgf1HashAlgorithm: "sha384", saltLength: 16 }, + sha1: { hashAlgorithm: "sha1" }, + salt: { saltLength: 10 }, + s512: { hashAlgorithm: "sha512", saltLength: 0 }, + mgf: { mgf1HashAlgorithm: "sha256" }, + }; + for (const [name, options] of Object.entries(cases)) { + const { publicKey, privateKey } = c.generateKeyPairSync("rsa-pss", { modulusLength: ["full", "sha1", "salt", "mgf"].includes(name) ? 512 : 1024, ...options }); + const spki = publicKey.export({ format: "der", type: "spki" }); + const pkcs8 = privateKey.export({ format: "der", type: "pkcs8" }); + console.log(name, publicKey.asymmetricKeyType, fmt(publicKey.asymmetricKeyDetails), fmt(privateKey.asymmetricKeyDetails)); + console.log(name, "spki alg", algOf(spki), "pkcs8 alg", pkcs8.indexOf(Buffer.from(algOf(spki), "hex")) > 0); + tryIt(`${name} pkcs1`, () => privateKey.export({ format: "pem", type: "pkcs1" })); + tryIt(`${name} jwk`, () => publicKey.export({ format: "jwk" })); + tryIt(`${name} pem`, () => privateKey.export({ format: "pem", type: "pkcs8" })); + const reimported = c.createPublicKey(publicKey.export({ format: "pem", type: "spki" })); + const fromPrivate = c.createPublicKey(privateKey.export({ format: "pem", type: "pkcs8" })); + const privateAgain = c.createPrivateKey({ key: pkcs8, format: "der", type: "pkcs8" }); + console.log(name, reimported.equals(publicKey), fromPrivate.equals(publicKey), privateAgain.asymmetricKeyType); + const data = Buffer.from("rsa-pss"); + const signature = c.sign(null, data, privateAgain); + console.log(name, c.verify(null, data, publicKey, signature), c.verify(null, data, reimported, signature)); + } + + const data = Buffer.from("restricted"); + const { publicKey, privateKey } = c.generateKeyPairSync("rsa-pss", { + modulusLength: 512, + hashAlgorithm: "sha256", + saltLength: 16, + }); + tryIt("sign sha1", () => c.sign("sha1", data, privateKey)); + tryIt("sign pkcs1", () => c.sign("sha256", data, { key: privateKey, padding: c.constants.RSA_PKCS1_PADDING })); + tryIt("sign salt 8", () => c.sign("sha256", data, { key: privateKey, saltLength: 8 })); + tryIt("verify salt 8", () => c.verify("sha256", data, { key: publicKey, saltLength: 8 }, Buffer.alloc(64))); + for (const saltLength of [16, 20, 30]) { + const signature = c.sign("sha256", data, { key: privateKey, saltLength }); + console.log( + saltLength, + c.verify("sha256", data, publicKey, signature), + c.verify("sha256", data, { key: publicKey, saltLength: c.constants.RSA_PSS_SALTLEN_AUTO }, signature), + ); + } + const signer = c.createSign("sha256").update(data); + console.log("createVerify", c.createVerify("sha256").update(data).verify(publicKey, signer.sign(privateKey))); + const free = c.generateKeyPairSync("rsa-pss", { modulusLength: 1024 }); + const freeSignature = c.sign("sha512", data, free.privateKey); + console.log("free sha512", c.verify("sha512", data, free.publicKey, freeSignature)); + + report("bad digest", { hashAlgorithm: "nope" }); + report("bad mgf1", { mgf1HashAlgorithm: "nope" }); + report("negative salt", { saltLength: -1 }); + report("upper", { hashAlgorithm: "SHA256" }); + report("alias", { hashAlgorithm: "RSA-SHA256" }); +} + +if (process.argv[2] === "deprecated") { + report("hash", { hash: "sha256" }); + report("conflict", { hashAlgorithm: "sha256", hash: "sha1" }); + report("mgf1Hash", { hashAlgorithm: "sha256", mgf1Hash: "sha384" }); + report("hash type", { hash: 5 }); + // Node drops deprecations while noDeprecation is set when it reports + // them, so the flag is set once the reports above are out. + setTimeout(() => { + process.noDeprecation = true; + report("quiet", { hash: "sha512" }); + console.log("end of turn"); + }); +} else { + main(); + const child = spawnSync(process.execPath, [process.argv[1], "deprecated"], { encoding: "utf8" }); + console.log(`status: ${child.status}`); + console.log(child.stdout); + console.log(child.stderr.replace(/\(node:\d+\)/g, "(node:)")); +} diff --git a/tests/corpus/8167-encrypted-private-keys.cjs b/tests/corpus/8167-encrypted-private-keys.cjs new file mode 100644 index 000000000..561a0aec3 --- /dev/null +++ b/tests/corpus/8167-encrypted-private-keys.cjs @@ -0,0 +1,114 @@ +// Encrypted private keys: PKCS#8 as PBES2 and PKCS#1 and SEC1 as +// traditional PEM with the CBC and ECB ciphers, import with a passphrase, +// generateKeyPair with a cipher, and OpenSSL's errors for missing and +// wrong passphrases, unknown ciphers, and ciphers a form refuses. +"use strict"; +const c = require("crypto"); +const show = (e) => + `${e.name} ${e.code} ${e.message}${e.library ? ` [${e.library}: ${e.reason}]` : ""}${ + e.opensslErrorStack ? ` ${JSON.stringify(e.opensslErrorStack)}` : "" + }`; +const tryIt = (label, fn) => { + try { + console.log(label, fn()); + } catch (e) { + console.log(label, show(e)); + } +}; +// The PEM label and headers, without the random IV. +const head = (pem) => + pem + .split("\n") + .filter((l) => l.startsWith("-----BEGIN") || l.includes(":")) + .map((l) => l.replace(/,[0-9A-F]+$/, "")) + .join(" | "); + +const ec = c.generateKeyPairSync("ec", { namedCurve: "P-256" }); +const rsa = c.generateKeyPairSync("rsa", { modulusLength: 1024 }); +const same = (key, pair) => c.createPublicKey(key).equals(pair.publicKey); + +for (const cipher of ["aes-128-cbc", "aes-192-cbc", "aes-256-cbc", "aes-128-ecb", "aes-256-ecb", "des-ede3-cbc", "aes256", "AES-128-CBC", "2.16.840.1.101.3.4.1.42"]) { + for (const format of ["pem", "der"]) { + tryIt(`pkcs8 ${format} ${cipher}`, () => { + const out = ec.privateKey.export({ type: "pkcs8", format, cipher, passphrase: "top secret" }); + const key = c.createPrivateKey({ key: out, format, type: format === "der" ? "pkcs8" : undefined, passphrase: "top secret" }); + return `${format === "pem" ? head(out) : Buffer.isBuffer(out)} ${key.asymmetricKeyType} ${same(key, ec)}`; + }); + } +} +for (const cipher of ["aes-128-cbc", "aes-256-cbc", "des-ede3-cbc", "des3"]) { + tryIt(`sec1 ${cipher}`, () => { + const out = ec.privateKey.export({ type: "sec1", format: "pem", cipher, passphrase: Buffer.from("pw") }); + return `${head(out)} ${same(c.createPrivateKey({ key: out, passphrase: "pw" }), ec)}`; + }); + tryIt(`pkcs1 ${cipher}`, () => { + const out = rsa.privateKey.export({ type: "pkcs1", format: "pem", cipher, passphrase: "pw" }); + return `${head(out)} ${same(c.createPrivateKey({ key: out, passphrase: Buffer.from("pw") }), rsa)}`; + }); +} +tryIt("empty passphrase", () => { + const out = rsa.privateKey.export({ type: "pkcs8", format: "pem", cipher: "aes-128-cbc", passphrase: "" }); + return same(c.createPrivateKey({ key: out, passphrase: "" }), rsa); +}); + +// Reading encrypted keys. +const p8pem = ec.privateKey.export({ type: "pkcs8", format: "pem", cipher: "aes-256-cbc", passphrase: "pw" }); +const p8der = ec.privateKey.export({ type: "pkcs8", format: "der", cipher: "aes-256-cbc", passphrase: "pw" }); +const sec1 = String(ec.privateKey.export({ type: "sec1", format: "pem", cipher: "aes-128-cbc", passphrase: "pw" })); +tryIt("public from encrypted", () => c.createPublicKey({ key: p8pem, passphrase: "pw" }).equals(ec.publicKey)); +tryIt("public from traditional", () => c.createPublicKey({ key: sec1, passphrase: "pw" }).type); +tryIt("pkcs8 pem no passphrase", () => c.createPrivateKey(p8pem)); +tryIt("pkcs8 der no passphrase", () => c.createPrivateKey({ key: p8der, format: "der", type: "pkcs8" })); +tryIt("sec1 no passphrase", () => c.createPrivateKey(sec1)); +tryIt("public no passphrase", () => c.createPublicKey(p8pem)); +tryIt("pkcs8 pem wrong", () => c.createPrivateKey({ key: p8pem, passphrase: "nope" })); +tryIt("pkcs8 der wrong", () => c.createPrivateKey({ key: p8der, format: "der", type: "pkcs8", passphrase: "nope" })); +tryIt("sec1 wrong", () => c.createPrivateKey({ key: sec1, passphrase: "nope" })); +tryIt("passphrase type", () => c.createPrivateKey({ key: p8pem, passphrase: 5 })); +tryIt("dek unknown", () => c.createPrivateKey({ key: sec1.replace(/DEK-Info: AES-128-CBC/, "DEK-Info: NOPE-CBC"), passphrase: "pw" })); +tryIt("dek legacy", () => + c.createPrivateKey({ key: sec1.replace(/DEK-Info: AES-128-CBC,([0-9A-F]{16})[0-9A-F]+/, "DEK-Info: DES-CBC,$1"), passphrase: "pw" }), +); +tryIt("dek legacy no passphrase", () => + c.createPrivateKey(sec1.replace(/DEK-Info: AES-128-CBC,([0-9A-F]{16})[0-9A-F]+/, "DEK-Info: DES-CBC,$1")), +); + +// Export options. +const opts = (extra) => ({ type: "pkcs8", format: "pem", ...extra }); +tryIt("unknown cipher", () => ec.privateKey.export(opts({ cipher: "nope", passphrase: "x" }))); +tryIt("cipher without passphrase", () => ec.privateKey.export(opts({ cipher: "aes-128-cbc" }))); +tryIt("passphrase without cipher", () => ec.privateKey.export(opts({ passphrase: "x" }))); +tryIt("cipher type", () => ec.privateKey.export(opts({ cipher: 5, passphrase: "x" }))); +tryIt("sec1 der", () => ec.privateKey.export({ type: "sec1", format: "der", cipher: "aes-128-cbc", passphrase: "x" })); +tryIt("pkcs1 der", () => rsa.privateKey.export({ type: "pkcs1", format: "der", cipher: "aes-128-cbc", passphrase: "x" })); +// OpenSSL refuses these ciphers for PKCS#8, and the last ones for +// traditional PEM too (the P-256 key's PKCS#8 and SEC1 lengths are not +// multiples of 8, which key wrap needs). +for (const cipher of ["aes-128-gcm", "aes-256-ccm", "aes-128-ctr", "chacha20-poly1305", "aes-128-xts", "des-ede3-cfb", "2.16.840.1.101.3.4.1.6"]) + tryIt(`refused pkcs8 ${cipher}`, () => ec.privateKey.export(opts({ cipher, passphrase: "x" })).length > 0); +for (const cipher of ["aes-128-wrap", "id-aes256-wrap", "des-cbc", "rc4"]) { + tryIt(`refused pkcs8 ${cipher}`, () => ec.privateKey.export(opts({ cipher, passphrase: "x" })).length > 0); + tryIt(`refused sec1 ${cipher}`, () => ec.privateKey.export({ type: "sec1", format: "pem", cipher, passphrase: "x" })); +} +for (const cipher of ["aes-128-ecb", "aes-256-ecb", "des-ede3"]) + tryIt(`refused sec1 ${cipher}`, () => ec.privateKey.export({ type: "sec1", format: "pem", cipher, passphrase: "x" })); + +// generateKeyPair with a cipher. +const encoding = (cipher, type = "pkcs8") => ({ + namedCurve: "P-256", + publicKeyEncoding: { type: "spki", format: "pem" }, + privateKeyEncoding: { type, format: "pem", cipher, passphrase: "gen" }, +}); +tryIt("keygen sync", () => { + const { publicKey, privateKey } = c.generateKeyPairSync("ec", encoding("aes-256-cbc")); + return `${head(privateKey)} ${c.createPublicKey({ key: privateKey, passphrase: "gen" }).equals(c.createPublicKey(publicKey))}`; +}); +tryIt("keygen unknown", () => c.generateKeyPairSync("ec", encoding("nope"))); +tryIt("keygen gcm", () => c.generateKeyPairSync("ec", encoding("aes-128-gcm"))); +tryIt("keygen sec1 ecb", () => c.generateKeyPairSync("ec", encoding("aes-128-ecb", "sec1"))); +// One job at a time: Node's jobs finish in either order. +c.generateKeyPair("ec", encoding("des-ede3-cbc", "sec1"), (err, publicKey, privateKey) => { + console.log("keygen async", err, head(privateKey)); + console.log("keygen async key", c.createPublicKey({ key: privateKey, passphrase: "gen" }).equals(c.createPublicKey(publicKey))); + c.generateKeyPair("ec", encoding("aes-128-ccm"), (err) => console.log("keygen async ccm", show(err))); +}); diff --git a/tests/harness/corpus-costs/8165-rsa-keygen-lengths.cjs.json b/tests/harness/corpus-costs/8165-rsa-keygen-lengths.cjs.json new file mode 100644 index 000000000..bbf64dca5 --- /dev/null +++ b/tests/harness/corpus-costs/8165-rsa-keygen-lengths.cjs.json @@ -0,0 +1 @@ +{ "plain": 11640, "san": 77506 } diff --git a/tests/harness/corpus-costs/8166-rsa-pss-keys.cjs.json b/tests/harness/corpus-costs/8166-rsa-pss-keys.cjs.json new file mode 100644 index 000000000..436e9ed51 --- /dev/null +++ b/tests/harness/corpus-costs/8166-rsa-pss-keys.cjs.json @@ -0,0 +1 @@ +{ "plain": 11459, "san": 74658 } diff --git a/tests/harness/corpus-costs/8167-encrypted-private-keys.cjs.json b/tests/harness/corpus-costs/8167-encrypted-private-keys.cjs.json new file mode 100644 index 000000000..0f258ddbb --- /dev/null +++ b/tests/harness/corpus-costs/8167-encrypted-private-keys.cjs.json @@ -0,0 +1 @@ +{ "plain": 3144, "san": 8504 } From e792cf47713ad7c0e23b2ab10e4f089be894bc69 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 09:49:01 -0500 Subject: [PATCH 022/230] Test RSA-PSS verification with an automatic salt length on restricted keys Node recovers the salt length under RSA_PSS_SALTLEN_AUTO without enforcing the minimum in an RSA-PSS key's parameters, while an explicit saltLength below it throws and the default saltLength requires the key's. The corpus program pins that behavior for MGF1 digests equal to and different from the message digest, through crypto.verify and createVerify. --- .../8178-rsa-pss-auto-min-salt.cjs.json | 6 ++ tests/corpus/8178-rsa-pss-auto-min-salt.cjs | 57 +++++++++++++++++++ .../8178-rsa-pss-auto-min-salt.cjs.json | 1 + 3 files changed, 64 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8178-rsa-pss-auto-min-salt.cjs.json create mode 100644 tests/corpus/8178-rsa-pss-auto-min-salt.cjs create mode 100644 tests/harness/corpus-costs/8178-rsa-pss-auto-min-salt.cjs.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8178-rsa-pss-auto-min-salt.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8178-rsa-pss-auto-min-salt.cjs.json new file mode 100644 index 000000000..7b3e5eef7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8178-rsa-pss-auto-min-salt.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8178-rsa-pss-auto-min-salt.cjs" + ], + "diags": [] +} diff --git a/tests/corpus/8178-rsa-pss-auto-min-salt.cjs b/tests/corpus/8178-rsa-pss-auto-min-salt.cjs new file mode 100644 index 000000000..97b9d1c05 --- /dev/null +++ b/tests/corpus/8178-rsa-pss-auto-min-salt.cjs @@ -0,0 +1,57 @@ +// Verifying with RSA_PSS_SALTLEN_AUTO against an RSA-PSS key whose +// parameters set a minimum salt length still enforces that minimum, as in +// Node: a valid signature with a shorter salt does not verify. The keys use +// an MGF1 digest equal to the message digest and a different one. The +// stricter public key is the generated one with its minimum salt length +// raised from 16 to 32 in its DER. +const c = require("node:crypto"); + +const data = Buffer.from("restricted salt"); + +function raiseMinimumSalt(publicKey) { + const der = publicKey.export({ format: "der", type: "spki" }); + const from = Buffer.from([0xa2, 0x03, 0x02, 0x01, 0x10]); + const at = der.indexOf(from); + if (at < 0) throw new Error("no saltLength field"); + der[at + 4] = 0x20; + return c.createPublicKey({ key: der, format: "der", type: "spki" }); +} + +function attempt(label, fn) { + try { + console.log(label, fn()); + } catch (e) { + console.log(label, "throws", e.code, e.message); + } +} + +for (const mgf1HashAlgorithm of ["sha256", "sha384"]) { + const { privateKey, publicKey } = c.generateKeyPairSync("rsa-pss", { + modulusLength: 1024, + hashAlgorithm: "sha256", + mgf1HashAlgorithm, + saltLength: 16, + }); + const strict = raiseMinimumSalt(publicKey); + const { mgf1HashAlgorithm: mgf1, saltLength: minimum } = strict.asymmetricKeyDetails; + console.log(mgf1HashAlgorithm, "strict key", mgf1, minimum); + for (const saltLength of [16, 20, 32, 40]) { + const signature = c.sign("sha256", data, { key: privateKey, saltLength }); + const label = `${mgf1HashAlgorithm} salt ${saltLength}`; + attempt(`${label} auto, key minimum 16:`, () => + c.verify("sha256", data, { key: publicKey, saltLength: c.constants.RSA_PSS_SALTLEN_AUTO }, signature), + ); + attempt(`${label} auto, key minimum 32:`, () => + c.verify("sha256", data, { key: strict, saltLength: c.constants.RSA_PSS_SALTLEN_AUTO }, signature), + ); + attempt(`${label} default, key minimum 32:`, () => c.verify("sha256", data, strict, signature)); + attempt(`${label} digest length, key minimum 32:`, () => + c.verify("sha256", data, { key: strict, saltLength: c.constants.RSA_PSS_SALTLEN_DIGEST }, signature), + ); + const v = c.createVerify("sha256"); + v.update(data); + attempt(`${label} createVerify auto, key minimum 32:`, () => + v.verify({ key: strict, saltLength: c.constants.RSA_PSS_SALTLEN_AUTO }, signature), + ); + } +} diff --git a/tests/harness/corpus-costs/8178-rsa-pss-auto-min-salt.cjs.json b/tests/harness/corpus-costs/8178-rsa-pss-auto-min-salt.cjs.json new file mode 100644 index 000000000..b1711dea1 --- /dev/null +++ b/tests/harness/corpus-costs/8178-rsa-pss-auto-min-salt.cjs.json @@ -0,0 +1 @@ +{ "plain": 5740, "san": 60808 } From 9e4f1253c620270543a190b4f2f24e29c9744c28 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 10:11:29 -0500 Subject: [PATCH 023/230] Keep unchanged partitions cached across edits --- native/llvm-codegen/README.md | 2 +- native/llvm-codegen/src/emit.cpp | 39 ++++++++++++++++++- packages/compiler/src/backend/llvm/emitter.ts | 28 ++++++++----- .../src/backend/llvm/expr-callbacks.ts | 30 +++++++------- .../src/backend/llvm/expr-containers.ts | 3 +- .../src/backend/llvm/expr-primitives.ts | 2 +- .../src/backend/llvm/expr-stream-callbacks.ts | 12 +++--- .../compiler/src/backend/llvm/lib-connect.ts | 2 +- .../compiler/src/backend/llvm/lib-datagram.ts | 4 +- .../compiler/src/backend/llvm/lib-dispatch.ts | 2 +- .../compiler/src/backend/llvm/lib-http2.ts | 2 +- .../compiler/src/backend/llvm/lib-lookup.ts | 2 +- .../compiler/src/backend/llvm/lib-process.ts | 2 +- packages/compiler/src/backend/mangle.ts | 9 ----- .../lowering/class-dynamic-dispatch.ts | 3 +- 15 files changed, 89 insertions(+), 53 deletions(-) diff --git a/native/llvm-codegen/README.md b/native/llvm-codegen/README.md index 0e8abbc47..d72aebdfd 100644 --- a/native/llvm-codegen/README.md +++ b/native/llvm-codegen/README.md @@ -37,7 +37,7 @@ Repeating `--output` divides the program into that many partitions, which are co WebAssembly code generation differs from LLVM's default pipeline in two ways that do not change its output. The pass that gives multiple-entry loops a single entry runs only on functions whose control flow is irreducible; its reachability analysis grows with the square of a function's block count even when it has nothing to change. Object emission also leaves out the module pass that collects external symbols for assembly declarations, so each function's machine code is freed once it is printed instead of when the whole module is compiled. -`--cache-dir ` names a private directory the caller owns for one helper build. Simplified partitions are stored under the hash of their exact source and objects under the hash of the exact module after importing, each with a trailing digest and published by rename, so a rebuild recompiles only the partitions an edit can affect. +`--cache-dir ` names a private directory the caller owns for one helper build. Simplified partitions are stored under the hash of their exact source and objects under the hash of the exact module after importing, each with a trailing digest and published by rename, so a rebuild recompiles only the partitions an edit can affect. The locals ThinLTO promotes for importing are named after their partition rather than a hash of its bitcode, so an edit to one partition leaves the partitions that import from it unchanged. `--import-bitcode `, given once per runtime-pack unit that ships bitcode, imports runtime functions for inlining; scriptc's program builds do not pass it. Small runtime functions reachable from the program's runtime calls are imported as `available_externally` so LLVM can inline them; the runtime objects remain the only definitions. The helper never runs a sanitizer pass, so with import it also drops the emitter's inert `sanitize_address` attributes, which would otherwise block that inlining. Without `--import-bitcode` the module is optimized exactly as emitted. diff --git a/native/llvm-codegen/src/emit.cpp b/native/llvm-codegen/src/emit.cpp index 9cae164d3..4e8c7a30c 100644 --- a/native/llvm-codegen/src/emit.cpp +++ b/native/llvm-codegen/src/emit.cpp @@ -170,6 +170,38 @@ void limitProfileImports() { (void)Limited; } +// ThinLTO renames the locals it promotes so that the code other partitions +// import can refer to them: switch tables, constants and the other private +// symbols optimization creates. By default the new name carries a hash of +// the defining partition's whole bitcode, so an edit anywhere in a partition +// renames them in every partition that imports from it, and those partitions +// miss the object cache although their own code is unchanged. Named after +// the partition instead (PartitionSourceName), they keep their names while +// the partition changes. The option is process-wide; the helper sets it once. +void stablePromotedNames() { + static const bool Set = [] { + StringRef Name = "use-source-filename-for-promoted-locals"; + if (cl::Option *Option = cl::getRegisteredOptions().lookup(Name)) + Option->addOccurrence(0, Name, "true"); + return true; + }(); + (void)Set; +} + +// Gives a partition's module its own source file name, which the names of +// the locals ThinLTO promotes derive from (stablePromotedNames). It runs +// after the pre-link optimization, so profile annotation still matches the +// program's local functions under their source path. +struct PartitionSourceName : PassInfoMixin { + explicit PartitionSourceName(std::string Name) : Name(std::move(Name)) {} + std::string Name; + PreservedAnalyses run(Module &M, ModuleAnalysisManager &) { + M.setSourceFileName(Name); + return PreservedAnalyses::all(); + } + static bool isRequired() { return true; } +}; + // Reads the profile's header once, before any partition needs it: it must // hold IR-level counts. std::optional inspectProfile(const EmitOptions &Options) { @@ -559,8 +591,12 @@ compileOptimizedPartitions(const EmitOptions &Options, size_t Count = Plan.Modules.size(); std::vector> Summaries(Count); SharedFailure Failures; + stablePromotedNames(); forEachPartition(Count, [&](size_t I) { - std::string Key = hashParts({Salt, "summary", Plan.Modules[I]}); + // The partition's index names its promoted locals, so it keys the + // summary with the partition's source. + std::string Key = + hashParts({Salt, "summary", std::to_string(I), Plan.Modules[I]}); if ((Summaries[I] = Cache.lookup(Key, "bc"))) return; std::string LookupError; @@ -593,6 +629,7 @@ compileOptimizedPartitions(const EmitOptions &Options, // link; the post-link optimization reads the annotated weights. Pipeline P(*Machine, Level, profileOptions(Options)); ModulePassManager MPM = P.PB.buildThinLTOPreLinkDefaultPipeline(Level); + MPM.addPass(PartitionSourceName("scriptc-partition-" + std::to_string(I))); MPM.addPass(BitcodeWriterPass(Stream, /*ShouldPreserveUseListOrder=*/false, /*EmitSummaryIndex=*/true, /*EmitModuleHash=*/true)); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 59b4b64a4..69ca09e71 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -2746,12 +2746,13 @@ export class LlEmitter { // ── plumbing (frame and scope ownership) ────── - /** A symbol derived from the constant's text rather than its first-use - * index, so an edit that interns one more string leaves every other - * constant's symbol, and the code referencing it, unchanged. This lets - * native builds reuse the compiled partitions an edit does not touch. A - * collision retries with a counter, still in first-use order. */ - private contentSymbol(prefix: string, text: string): string { + /** A symbol derived from a constant's text, or a helper's key, rather + * than its first-use index, so an edit that interns one more string or + * helper leaves every other symbol, and the code referencing it, + * unchanged. This lets native builds reuse the compiled partitions an + * edit does not touch. A collision retries with a counter, still in + * first-use order. */ + contentSymbol(prefix: string, text: string): string { for (let attempt = 0; ; attempt++) { const digest = createHash("sha256") .update(attempt === 0 ? text : `${attempt}\0${text}`) @@ -2877,7 +2878,7 @@ export class LlEmitter { const key = `${unionId}:${tag}`; let sym = this.unitInstances.get(key); if (!sym) { - sym = `sc_unit_${this.unitInstances.size}`; + sym = this.contentSymbol("sc_unit_", key); this.unitInstances.set(key, sym); } this.journal?.entry("unit", key, sym); @@ -4997,8 +4998,15 @@ export class LlEmitter { case "retainWrapper": addEntry(this.nullableUnions.retainWrappers, key, value as string); return; - case "unit": - return addNumbered(this.unitInstances, key, value as string, "sc_unit_", names); + case "unit": { + let own = this.unitInstances.get(key); + if (own === undefined) { + own = this.contentSymbol("sc_unit_", key); + this.unitInstances.set(key, own); + } + if (own !== value) names.set(value as string, own); + return; + } case "absent": return addNumbered(this.absentInstances, key, value as string, "sc_absent_", names); case "retype": @@ -5006,7 +5014,7 @@ export class LlEmitter { case "slots": return addNumbered(this.threadSlotsBlocks, key, value as string, "@sc_slots_", names); case "regex": - return addNumberedSymbol(this.regexInstances, key, value as never, "sc_re_", names); + return this.addContentEntry(this.regexInstances, key, value as never, "sc_re_", names); case "templateStrings": return addNumberedSymbol( this.templateStringsInstances, diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index 398aebb21..3d5a53402 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -8,7 +8,7 @@ import { typeEquals, typeKey, } from "../../ir/ir.js"; -import { mangleGenResThunk, mangleRecordNew, mangleRecordStruct } from "../mangle.js"; +import { mangleRecordNew, mangleRecordStruct } from "../mangle.js"; import { FN_ATTRS, llFieldType, releaseSym, retainSym, traceArg, vAdapters } from "./shapes.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext } from "./expr-context.js"; @@ -54,7 +54,7 @@ export function raceAdapterFor(host: LlvmEmitterContext, from: IrType, to: IrTyp const key = `race:${typeKey(from)}=>${typeKey(to)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_race_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_race_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "raceAdapterFor", [from, to]); const toArms = to.kind === "union" ? (host.unionsById.get(to.unionId)?.arms ?? []) : []; @@ -217,7 +217,7 @@ export function genResultThunkFor( const key = `gr:${typeKey(genT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = mangleGenResThunk(host.resolveThunks.size); + sym = host.contentSymbol("sc_gres_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "genResultThunkFor", [genT, recT]); const shape = host.recordsById.get(recT.shapeId); @@ -474,7 +474,7 @@ export function childExitThunkFor(host: LlvmEmitterContext, param: IrType): stri const key = `cx:${param.unionId}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_cx_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_cx_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "childExitThunkFor", [param]); const def = host.unionsById.get(param.unionId); @@ -519,7 +519,7 @@ export function childExitSignalThunkFor( const key = `cx2:${codeParam.unionId}+${sigParam.unionId}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_cx_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_cx_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "childExitSignalThunkFor", [codeParam, sigParam]); const codeDef = host.unionsById.get(codeParam.unionId); @@ -580,7 +580,7 @@ export function execFileThunkFor(host: LlvmEmitterContext, cbT: IrType & { kind: const key = `exec:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_exec_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_exec_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "execFileThunkFor", [cbT]); const errorParam = cbT.params[0]; @@ -652,7 +652,7 @@ export function ipcMessageThunkFor( const key = `ipcmsg:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_ipc_msg_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ipc_msg_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "ipcMessageThunkFor", [cbT]); const param = cbT.params[0]; @@ -703,7 +703,7 @@ export function ipcSendThunkFor(host: LlvmEmitterContext, cbT: IrType & { kind: const key = `ipcsend:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_ipc_send_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ipc_send_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "ipcSendThunkFor", [cbT]); const param = cbT.params[0]; @@ -764,7 +764,7 @@ export function childDataThunkFor(host: LlvmEmitterContext, param: IrType): stri const key = `cd:${param.unionId}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_cd_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_cd_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "childDataThunkFor", [param]); const def = host.unionsById.get(param.unionId); @@ -799,7 +799,7 @@ export function childDataDynThunkFor(host: LlvmEmitterContext, chunk: "bytes" | const key = `cdd:${chunk}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_cdd_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_cdd_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "childDataDynThunkFor", [chunk]); const box = chunk === "bytes" ? "scr_dyn_new_buffer" : "scr_dyn_new_str"; @@ -834,7 +834,7 @@ export function emitterFixedAdapter( const key = `ee:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return { fn: sym, shim }; - sym = `sc_ee_ad_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ee_ad_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "emitterFixedAdapter", [cbT]); host.declare(`declare ptr @scr_box_get_ref(ptr)`); @@ -905,7 +905,7 @@ export function emitterOnceAdapter( : `eeOnce:${fire}:${typeKey(boxerT)}`; let sym = host.resolveThunks.get(key); if (sym) return { fn: sym, shim }; - sym = `sc_ee_once_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ee_once_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "emitterOnceAdapter", [boxerT, fire]); host.declare(`declare ptr @scr_box_get_ref(ptr)`); @@ -1015,7 +1015,7 @@ export function closeBindThunkFor( const key = `ncb:${cbUnion.unionId}:${retServer ? "srv" : "void"}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_ncb_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ncb_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "closeBindThunkFor", [cbUnion, retServer]); const def = host.unionsById.get(cbUnion.unionId); @@ -1121,7 +1121,7 @@ export function closeOverrideWrapFor( const key = `ncw:${cbUnion.unionId}:${retServer ? "srv" : "void"}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_ncw_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ncw_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "closeOverrideWrapFor", [cbUnion, retServer]); const def = host.unionsById.get(cbUnion.unionId); @@ -1169,7 +1169,7 @@ export function settledStoreFor( const key = `settled:${typeKey(from)}=>${typeKey(to)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_settled_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_settled_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "settledStoreFor", [from, to]); const def = host.unionsById.get(to.unionId); diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index 279b8c044..8ffc8727e 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -3,7 +3,6 @@ import { arraySearchHelperName, arraySearchKind } from "./array-search.js"; import { InternalCompilerError } from "../../errors.js"; import { isStableReceiverOperand, undefinedArmTag } from "../../ir/analysis.js"; import { F64, type IrExpr, type IrType, isRefCounted, typeEquals, typeKey } from "../../ir/ir.js"; -import { mangleResolveThunk } from "../mangle.js"; import { arrOps, elemAccess, @@ -71,7 +70,7 @@ export function resolveThunkFor(host: LlvmEmitterContext, inner: IrType): string const key = typeKey(inner); let sym = host.resolveThunks.get(key); if (!sym) { - sym = mangleResolveThunk(host.resolveThunks.size); + sym = host.contentSymbol("sc_rslv_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "resolveThunkFor", [inner]); const v = vAdapters(host.shapeHost, inner); diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index ccbe3edcc..8ecf147f6 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -589,7 +589,7 @@ export function regexLiteralTemplate( let re = host.regexInstances.get(key); if (!re) { re = { - sym: `sc_re_${host.regexInstances.size}`, + sym: host.contentSymbol("sc_re_", key), src: host.internLiteral(e.pattern), fl: host.internLiteral(e.flags), pattern: e.pattern, diff --git a/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts b/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts index b368c91f4..285f9ca9a 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts @@ -13,7 +13,7 @@ export function streamDataAdapter( const key = `eed:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_ee_dad_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_ee_dad_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "streamDataAdapter", [cbT]); const p = cbT.params[0]; @@ -221,7 +221,7 @@ export function streamDoneFnFor( const key = `sd:${kind}:${typeKey(doneT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_sd_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_sd_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "streamDoneFnFor", [kind, doneT]); const errT = doneT.params[0]; @@ -365,7 +365,7 @@ export function fsRenameThunkFor(host: LlvmEmitterContext, cbT: IrType & { kind: const key = `fsren:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_fsren_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_fsren_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "fsRenameThunkFor", [cbT]); const param = cbT.params[0]!; @@ -454,7 +454,7 @@ export function cryptoIntThunkFor( const key = `cryptoint:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_cryptoint_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_cryptoint_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "cryptoIntThunkFor", [cbT]); const d: string[] = [ @@ -516,7 +516,7 @@ export function zlibBytesThunkFor( const key = `${family}bytes:${arrayBuffer ? "arraybuffer:" : ""}${mode}${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_${family}bytes_${host.resolveThunks.size}`; + sym = host.contentSymbol(`sc_${family}bytes_`, key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "zlibBytesThunkFor", [cbT, arrayBuffer, family, valueMode]); const d: string[] = [ @@ -644,7 +644,7 @@ export function streamCbThunkFor( const key = `scb:${kind}:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_scb_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_scb_", key); host.resolveThunks.set(key, sym); host.journal?.allocated(sym, "streamCbThunkFor", [kind, cbT]); const runtimeParams = diff --git a/packages/compiler/src/backend/llvm/lib-connect.ts b/packages/compiler/src/backend/llvm/lib-connect.ts index 4d151fc7e..2ab72c188 100644 --- a/packages/compiler/src/backend/llvm/lib-connect.ts +++ b/packages/compiler/src/backend/llvm/lib-connect.ts @@ -17,7 +17,7 @@ function connectAdapter(host: LlvmEmitterContext, cb: LlValue, h2: boolean): str const key = `connect:${h2}:${typeKey(type)}`; const previous = host.resolveThunks.get(key); if (previous !== undefined) return previous; - const symbol = `sc_connect_${host.resolveThunks.size}`; + const symbol = host.contentSymbol("sc_connect_", key); host.resolveThunks.set(key, symbol); host.journal?.allocated(symbol, "connectAdapter", [cb, h2]); const retain = h2 ? "scr_http_res_retain_v" : "scr_net_sock_retain_v"; diff --git a/packages/compiler/src/backend/llvm/lib-datagram.ts b/packages/compiler/src/backend/llvm/lib-datagram.ts index 07de4e54e..7a83f1d30 100644 --- a/packages/compiler/src/backend/llvm/lib-datagram.ts +++ b/packages/compiler/src/backend/llvm/lib-datagram.ts @@ -19,7 +19,7 @@ function messageAdapter(host: LlvmEmitterContext, type: IrType): string { const key = "dgram:" + type.shapeId; const previous = host.resolveThunks.get(key); if (previous !== undefined) return previous; - const symbol = `sc_dgram_message_${host.resolveThunks.size}`; + const symbol = host.contentSymbol("sc_dgram_message_", key); host.resolveThunks.set(key, symbol); host.journal?.allocated(symbol, "messageAdapter", [type]); const shape = host.recordsById.get(type.shapeId); @@ -68,7 +68,7 @@ function lookupAdapter(host: LlvmEmitterContext, cb: LlValue): string { const key = `dns:${error.unionId}:${arity}`; const previous = host.resolveThunks.get(key); if (previous !== undefined) return previous; - const symbol = `sc_dns_lookup_${host.resolveThunks.size}`; + const symbol = host.contentSymbol("sc_dns_lookup_", key); host.resolveThunks.set(key, symbol); host.journal?.allocated(symbol, "lookupAdapter", [cb]); host.declare("declare ptr @scr_error_new(i32, ptr)"); diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 9cd11feb1..6ab83016d 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -1085,7 +1085,7 @@ function emitPromiseAllTuple(host: LlvmEmitterContext, e: LibCallExpr): LlValue const key = `allTuple:${build.fnName}`; let adapter = host.resolveThunks.get(key); if (adapter === undefined) { - adapter = `sc_all_tuple_${host.resolveThunks.size}`; + adapter = host.contentSymbol("sc_all_tuple_", key); host.resolveThunks.set(key, adapter); const rc = vAdapters(host.shapeHost, tuple); host.declare(`declare ptr @scr_promise_payload_ref(ptr)`); diff --git a/packages/compiler/src/backend/llvm/lib-http2.ts b/packages/compiler/src/backend/llvm/lib-http2.ts index 321f1d005..1808200a0 100644 --- a/packages/compiler/src/backend/llvm/lib-http2.ts +++ b/packages/compiler/src/backend/llvm/lib-http2.ts @@ -21,7 +21,7 @@ function sniAnswer(host: LlvmEmitterContext, type: IrType): string { const key = "sni:" + typeKey(type); const previous = host.resolveThunks.get(key); if (previous !== undefined) return previous; - const symbol = `sc_sni_answer_${host.resolveThunks.size}`; + const symbol = host.contentSymbol("sc_sni_answer_", key); host.resolveThunks.set(key, symbol); host.journal?.allocated(symbol, "sniAnswer", [type]); const params = ["ptr %self"], diff --git a/packages/compiler/src/backend/llvm/lib-lookup.ts b/packages/compiler/src/backend/llvm/lib-lookup.ts index 56e94461f..620833819 100644 --- a/packages/compiler/src/backend/llvm/lib-lookup.ts +++ b/packages/compiler/src/backend/llvm/lib-lookup.ts @@ -37,7 +37,7 @@ function lookupAnswer(host: LlvmEmitterContext, answer: IrType & { kind: "func" const key = "lookup:" + typeKey(answer); let symbol = host.resolveThunks.get(key); if (symbol === undefined) { - symbol = `sc_lookup_answer_${host.resolveThunks.size}`; + symbol = host.contentSymbol("sc_lookup_answer_", key); host.resolveThunks.set(key, symbol); host.journal?.allocated(symbol, "lookupAnswer", [answer]); for (const declaration of [ diff --git a/packages/compiler/src/backend/llvm/lib-process.ts b/packages/compiler/src/backend/llvm/lib-process.ts index 1a3973b07..21205c9f6 100644 --- a/packages/compiler/src/backend/llvm/lib-process.ts +++ b/packages/compiler/src/backend/llvm/lib-process.ts @@ -489,7 +489,7 @@ function boundCallbackThunk(host: LlvmEmitterContext, cbT: IrType & { kind: "fun const key = `alsbound:${typeKey(cbT)}`; let sym = host.resolveThunks.get(key); if (sym) return sym; - sym = `sc_alsbound_${host.resolveThunks.size}`; + sym = host.contentSymbol("sc_alsbound_", key); host.resolveThunks.set(key, sym); host.declare("declare ptr @scr_als_bound_target(ptr)"); host.declare("declare ptr @scr_als_bound_enter(ptr)"); diff --git a/packages/compiler/src/backend/mangle.ts b/packages/compiler/src/backend/mangle.ts index 14736ead7..8542a1c64 100644 --- a/packages/compiler/src/backend/mangle.ts +++ b/packages/compiler/src/backend/mangle.ts @@ -221,15 +221,6 @@ export function mangleGenDrop(fnName: string): string { export function mangleGenSourceClose(fnName: string): string { return `sc_gc_${sanitize(fnName)}`; } -/** Emitted ref-kind resolve thunk, interned per inner-type key. */ -export function mangleResolveThunk(n: number): string { - return `sc_rslv_${n}`; -} -/** Emitted generator-resume result builder (the IteratorResult record), - * interned per generator-type key. */ -export function mangleGenResThunk(n: number): string { - return `sc_gres_${n}`; -} /** Emitted Promise.race fulfillment adapter (entry inner type → result * inner type), interned per type pair. */ export function mangleRaceThunk(n: number): string { diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index fc6d0a922..0789b6d4f 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -80,6 +80,7 @@ import { isClassCallback } from "./class-callbacks.js"; import { CaughtValueDispatch } from "./caught-value-dispatch.js"; import { refreshDescriptorGuards } from "./class-descriptors.js"; import { ClassConstructionDispatch } from "./class-construction.js"; +import { keyedHelperName } from "./stable-names.js"; import { DispatchSites } from "./dispatch-sites.js"; import { emitterRooted, @@ -3909,7 +3910,7 @@ export class ClassDynamicDispatch { if (!dispatch.fn.params.some((param) => param.localId === dispatch.keyLocal)) params.push({ localId: dispatch.keyLocal, name: "key", type: STRING }); const helper: IrFunction = { - name: `%dyn.class.keys.${lowerer.liftedFns.length}`, + name: keyedHelperName(lowerer, "%dyn.class.keys", helperKey), params, returnType: dispatch.fn.returnType, locals: params.map((p) => ({ From 908d87d3e0bdcb3ba91e3d6ad21722bd9a819392 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 13:32:14 -0500 Subject: [PATCH 024/230] Key widened returns by their type-parameter bindings and skip self-calls Two review findings on the widened-return cache: - A nested function lowered under different type-parameter bindings reused the record layout of the first instance it was widened in. In `function outer(x: T) { const inner = () => ...; return inner(); }`, the closures of outer and outer returned the records of outer, and returning one threw a TypeError. A kept result now records the bindings it was mapped under (typeParamBindings, its checker-type and implicit-any twins, and whether an erased body is lowering) and answers only under the same ones. - A function that calls itself on one return cut its own widening, so its result was never kept and a chain of such functions forwarding to each other on two branches was scanned 2^depth times. A call of the function itself returns its own type, whose arms add nothing, so the scan now skips it. --- .../src/frontend/lowering/coercions/state.ts | 18 +++++- .../src/frontend/lowering/function-returns.ts | 25 ++++++++- .../compiler/test/widened-returns.test.ts | 55 ++++++++++++++++++- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/coercions/state.ts b/packages/compiler/src/frontend/lowering/coercions/state.ts index 27667197c..cbc577634 100644 --- a/packages/compiler/src/frontend/lowering/coercions/state.ts +++ b/packages/compiler/src/frontend/lowering/coercions/state.ts @@ -1,5 +1,6 @@ import type { IrType } from "../../../ir/ir.js"; import type * as ts from "../../ts7/adapter.js"; +import type { Lowerer } from "../lowerer.js"; /** Per-compilation conversion state. Recursive plans are scoped to the * active planning stack; only fully validated copy routes are memoized. */ @@ -24,6 +25,19 @@ export class CoercionState { * width-converting extraction (widthNarrowHelper). */ readonly widenedReturns = new Set(); /** widenReducedReturn's results by declaration and inferred return type, - * for the results computed without cutting a recursive widening. */ - readonly widenedReturnTypes = new Map>(); + * for the results computed without cutting a widening cycle through + * another function. Each keeps the type-parameter bindings it was mapped + * under and answers only under the same bindings. */ + readonly widenedReturnTypes = new Map>(); +} + +/** A widened inferred return type and the lowering state that mapped it + * (Lowerer.typeParamBindings and its checker-type and implicit-any twins, + * and whether an erased generic body is lowering). */ +export interface WidenedReturn { + type: IrType; + bindings: Lowerer["typeParamBindings"]; + tsBindings: Lowerer["typeParamTsBindings"]; + implicit: Lowerer["implicitParamTypes"]; + erasing: boolean; } diff --git a/packages/compiler/src/frontend/lowering/function-returns.ts b/packages/compiler/src/frontend/lowering/function-returns.ts index 0b87710e0..4d586fcc3 100644 --- a/packages/compiler/src/frontend/lowering/function-returns.ts +++ b/packages/compiler/src/frontend/lowering/function-returns.ts @@ -584,15 +584,30 @@ function widenReducedReturn( return mapped; } // Functions that forward to the same callees along several return - // branches read each callee's widened type once. + // branches read each callee's widened type once. A nested function + // lowered under other type-parameter bindings maps its returns again. const known = lowerer.coercions.widenedReturnTypes.get(decl)?.get(retTsType); - if (known !== undefined) return known; + const erasing = lowerer.erasingDepth > 0; + if ( + known !== undefined && + known.bindings === lowerer.typeParamBindings && + known.tsBindings === lowerer.typeParamTsBindings && + known.implicit === lowerer.implicitParamTypes && + known.erasing === erasing + ) + return known.type; const cutsBefore = wideningCuts; const widened = widenReturnArms(lowerer, decl, retTsType, mapped, arms, recordArms); if (wideningCuts === cutsBefore) { let byType = lowerer.coercions.widenedReturnTypes.get(decl); if (byType === undefined) lowerer.coercions.widenedReturnTypes.set(decl, (byType = new Map())); - byType.set(retTsType, widened); + byType.set(retTsType, { + type: widened, + bindings: lowerer.typeParamBindings, + tsBindings: lowerer.typeParamTsBindings, + implicit: lowerer.implicitParamTypes, + erasing, + }); } return widened; } @@ -638,6 +653,10 @@ function widenReturnArms( (callee as { body?: ts.Node }).body !== undefined && callee.type === undefined ) { + // A call of the function itself returns its own type, whose arms + // add nothing. Reading it would cut the widening and keep this + // result from being reused. + if (callee === decl) return; let t: IrType | null = null; widening.add(decl); try { diff --git a/packages/compiler/test/widened-returns.test.ts b/packages/compiler/test/widened-returns.test.ts index 63d24c1da..fe78c6b75 100644 --- a/packages/compiler/test/widened-returns.test.ts +++ b/packages/compiler/test/widened-returns.test.ts @@ -22,7 +22,7 @@ async function lowerModule(source: string): Promise { /** Functions that each forward to the previous one along two return * branches, above one whose inferred type the checker reduced. */ -function forwardingChain(depth: number): string { +function forwardingChain(depth: number, recursive = false): string { const lines = [ "export {};", "type Short = { status: string };", @@ -35,6 +35,7 @@ function forwardingChain(depth: number): string { for (let i = 1; i <= depth; i++) lines.push( `function f${i}(a: Short, b: Long, n: number) {`, + ...(recursive ? [` if (n > 20) return f${i}(a, b, n - 1);`] : []), ` if (n > 1) return f${i - 1}(a, b, n - 1);`, ` if (n > 0) return f${i - 1}(a, b, n + 1);`, " return a;", @@ -53,3 +54,55 @@ test("return widening reads each forwarded callee's type once", { timeout: 60_00 expect(top).toBeDefined(); expect(top!.returnType.kind).toBe("union"); }); + +test( + "return widening reads each forwarded callee's type once when the callees call themselves", + { timeout: 60_000 }, + async () => { + const mod = await lowerModule(forwardingChain(40, true)); + const top = mod.functions.find((f) => f.name === "f40"); + expect(top).toBeDefined(); + expect(top!.returnType.kind).toBe("union"); + }, +); + +test("a nested function's widened return follows each instance's type arguments", async () => { + const mod = await lowerModule( + [ + "export {};", + "function outer(x: T, n: number) {", + " const inner = () => {", + " if (n > 0) return { status: x, value: n };", + " return { status: x };", + " };", + " return inner();", + "}", + "console.log(JSON.stringify(outer(1, 1)));", + 'console.log(JSON.stringify(outer("s", 1)));', + "console.log(JSON.stringify(outer(true, 0)));", + ].join("\n"), + ); + const shapes = new Map((mod.records ?? []).map((r) => [r.id, r])); + const unions = new Map((mod.unions ?? []).map((u) => [u.id, u])); + // Each closure returns records whose `status` has the type its captured + // `x` has. + const closures = mod.functions.filter( + (f) => f.name.startsWith("%fn.") && !f.name.includes("adapt"), + ); + expect(closures.length).toBe(3); + for (const closure of closures) { + const captured = closure.captures?.find((c) => c.type.kind !== "f64")?.type.kind ?? "f64"; + const ret = closure.returnType; + expect(ret.kind).toBe("union"); + const arms = ret.kind === "union" ? (unions.get(ret.unionId)?.arms ?? []) : []; + expect(arms.length).toBe(2); + for (const arm of arms) { + expect(arm.kind).toBe("record"); + const status = + arm.kind === "record" + ? shapes.get(arm.shapeId)?.fields.find((f) => f.name === "status") + : undefined; + expect(status?.type.kind).toBe(captured); + } + } +}); From 8f3c2c28d625015a93bb15acbc3cdb5fb1c20d9a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:14:15 -0500 Subject: [PATCH 025/230] Count comparator calls in AArch64 and Mach-O assembly The string comparison emission test matched only x86-64 `call` instructions to unprefixed symbols, so on AArch64 and macOS hosts it counted no calls and failed its positive-count check. It now also matches AArch64 `bl` and Mach-O's leading underscore, and it runs for the host and for aarch64-linux-gnu and x86_64-macos assembly, so every host checks all three spellings. --- tests/harness/string-compare-emission.test.ts | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/tests/harness/string-compare-emission.test.ts b/tests/harness/string-compare-emission.test.ts index 2654f56ba..1a8fdddf6 100644 --- a/tests/harness/string-compare-emission.test.ts +++ b/tests/harness/string-compare-emission.test.ts @@ -11,17 +11,23 @@ import { compile } from "@scriptc/compiler"; const repoRoot = join(import.meta.dirname, "../.."); const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests"); -async function emittedAsm(name: string, source: string): Promise { - const key = createHash("sha256").update(source).digest("hex").slice(0, 16); +async function emittedAsm(name: string, source: string, target: string): Promise { + const key = createHash("sha256").update(target).update(source).digest("hex").slice(0, 16); const outDir = join(cacheDir, `string-compare-${key}`); mkdirSync(outDir, { recursive: true }); const file = join(outDir, `${name}.ts`); writeFileSync(file, source); + const saved = process.env["SCRIPTC_TARGET"]; + if (target === "host") delete process.env["SCRIPTC_TARGET"]; + else process.env["SCRIPTC_TARGET"] = target; const result = await compile(file, { outPath: join(outDir, name), outDir, backend: "llvm", outputKind: "asm", + }).finally(() => { + if (saved === undefined) delete process.env["SCRIPTC_TARGET"]; + else process.env["SCRIPTC_TARGET"] = saved; }); if (!result.ok) throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); @@ -36,12 +42,23 @@ words.sort(order); console.log(words.join(",")); `; +// Calls to the comparison runtime: `call`/`callq` on x86-64 and `bl` on +// AArch64, with Mach-O's leading underscore on macOS. const compareCalls = (asm: string): number => - asm.match(/\bcall[a-z]*\s+(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g)?.length ?? 0; + asm.match( + /\b(?:call[a-z]*|bl)\s+_?(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g, + )?.length ?? 0; -test("a three-way string comparator compares its pair once", async () => { - const once = compareCalls(await emittedAsm("once", program("a < b ? -1 : 1"))); - const both = compareCalls(await emittedAsm("both", program("a < b ? -1 : a > b ? 1 : 0"))); - expect(once).toBeGreaterThan(0); - expect(both).toBe(once); -}); +// The host's assembly, and two other targets' so that every host checks the +// x86-64, AArch64 and Mach-O spellings. +test.each(["host", "aarch64-linux-gnu", "x86_64-macos"])( + "a three-way string comparator compares its pair once (%s)", + async (target) => { + const once = compareCalls(await emittedAsm("once", program("a < b ? -1 : 1"), target)); + const both = compareCalls( + await emittedAsm("both", program("a < b ? -1 : a > b ? 1 : 0"), target), + ); + expect(once).toBeGreaterThan(0); + expect(both).toBe(once); + }, +); From a538ff42f0ab99581f2e65a00a03510781090b19 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:19:16 -0500 Subject: [PATCH 026/230] Read only value slots in lastIndexOf with an element-typed needle lastIndexOf with a needle of the element type read every non-hole index as the element type. An undefined or foreign element written through a dynamic view (or a hole that toSorted turned into undefined) has no element-typed payload, so the read aborted with "array index N is undefined". The search now reads an index only when it holds a value, and skips undefined and foreign elements, as Node's strict equality does for a string or number needle. --- .../lowering/containers/array-methods.ts | 32 ++++++++++++------- .../6068-foreign-elements-through-views.ts | 23 +++++++++++++ 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/containers/array-methods.ts b/packages/compiler/src/frontend/lowering/containers/array-methods.ts index 4ef203edd..0e8b4fd84 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-methods.ts @@ -1788,17 +1788,27 @@ function arraySearchHelper( }, loc, }; - const visitPresent: IrStmt[] = [ - { kind: "varDecl", localId: "value.0", init: value, loc }, - { - kind: "if", - cond: match, - then: [{ kind: "return", value: method === "includes" ? boolLit(true, loc) : indexRef, loc }], - else_: null, - loc, - }, - advance, - ]; + const found: IrStmt = { + kind: "if", + cond: directNeedle ? equal : match, + then: [{ kind: "return", value: method === "includes" ? boolLit(true, loc) : indexRef, loc }], + else_: null, + loc, + }; + // A direct needle reads only value slots: an undefined or foreign + // element written through a dynamic view has no element-typed payload. + const visitPresent: IrStmt[] = directNeedle + ? [ + { + kind: "if", + cond: { kind: "bin", op: "===", left: stateRef, right: numLit(1, loc), type: BOOL, loc }, + then: [{ kind: "varDecl", localId: "value.0", init: value, loc }, found], + else_: null, + loc, + }, + advance, + ] + : [{ kind: "varDecl", localId: "value.0", init: value, loc }, found, advance]; const skipHole: IrStmt[] = method === "lastIndexOf" ? [advance] diff --git a/tests/corpus/6068-foreign-elements-through-views.ts b/tests/corpus/6068-foreign-elements-through-views.ts index 5181d7fe5..1b23f6fa7 100644 --- a/tests/corpus/6068-foreign-elements-through-views.ts +++ b/tests/corpus/6068-foreign-elements-through-views.ts @@ -177,3 +177,26 @@ holderView.push({ }, }); console.log(JSON.stringify(holder), JSON.stringify(holder)); + +// toSorted keeps the full length and reads holes as undefined; indexOf, +// lastIndexOf and includes skip the undefined and foreign elements. +const searched: string[] = ["b", "a"]; +const searchedView: unknown[] = searched; +searchedView[9] = 4; +searchedView[5] = undefined; +console.log(searched.indexOf("b"), searched.lastIndexOf("b"), searched.lastIndexOf("a", 8)); +console.log(searched.includes("a"), searched.lastIndexOf("z")); +const searchedSorted = searched.toSorted(); +console.log(searchedSorted.length, searchedSorted.slice(0, 4), searchedSorted.lastIndexOf("b")); +console.log(searchedSorted.indexOf("a"), searchedSorted.includes("b")); +const counts: number[] = [3, 1]; +const countsView: unknown[] = counts; +countsView[6] = "x"; +countsView[4] = 2; +const countsSorted = counts.toSorted(); +console.log(countsSorted.length, countsSorted, countsSorted.lastIndexOf(3), counts.lastIndexOf(2)); +const far: number[] = [5]; +const farView: unknown[] = far; +farView[70000] = "z"; +const farSorted = far.toSorted(); +console.log(farSorted.length, farSorted.slice(0, 3), farSorted.slice(69999), farSorted.lastIndexOf(5)); From 54f12d73dccf1b2cb62bb36ac920e7b8c4e0f5d0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 16:37:07 -0500 Subject: [PATCH 027/230] Use built-in typed-array species, copy slices after species creation, and read element descriptors - A built-in typed-array constructor set as a view's `constructor` is its own species, as the getter it inherits from %TypedArray% returns it: `u8.constructor = Float64Array; u8.map(f)` makes a Float64Array. An own Symbol.species on the constructor still wins. - TypedArray.prototype.slice with a species constructor creates the result first and then copies the source's current elements, up to its current length: a constructor that changes the source is seen. - Object.defineProperty on a typed-array element reads the descriptor as ToPropertyDescriptor does, through its getters and in its order, and throws a TypeError for a primitive descriptor or an accessor with a value. The generic object path shares the same reader. --- packages/runtime/src/scr_json.c | 176 ++++++++++++------ .../7753-typed-array-named-properties.js | 23 +++ 2 files changed, 140 insertions(+), 59 deletions(-) diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index be63e1436..f28160d42 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -5024,6 +5024,29 @@ static double *dyn_bytes_values(const ScrBytes *b, size_t n) { return values; } +static ScrDyn *scr_dyn_symbol_receiver(const ScrDyn *value); +static ScrDynEntry *scr_dyn_symbol_entry(const ScrDyn *receiver, const ScrDyn *key, bool own); + +/* Whether a typed-array constructor has a Symbol.species of its own, + * shadowing the one it inherits from %TypedArray%. */ +static bool scr_bytes_ctor_own_species(const ScrDyn *constructor) { + ScrDyn *table = scr_dyn_symbol_receiver(constructor); + bool own = table && scr_dyn_symbol_entry(table, scr_species_symbol, true); + scr_dyn_release(table); + return own; +} + +/* A relative index (ToIntegerOrInfinity, negatives from the end), clamped + * to [0, len], as scr_bytes_slice computes it. */ +static size_t scr_bytes_rel_index_of(double i, size_t len) { + if (i != i) return 0; + double t = trunc(i); + if (t < 0) t += (double)len; + if (t < 0) return 0; + if (t > (double)len) return len; + return (size_t)t; +} + /* SpeciesConstructor of a typed array: NULL (and no exception) for the * default constructor of its kind, else the species constructor (+1). * Only a `constructor` the program set on the view can differ. */ @@ -5044,6 +5067,13 @@ static ScrDyn *scr_bytes_species(ScrDyn *recv, bool *failed) { return NULL; } ScrDyn *species = scr_species_symbol ? scr_dyn_symbol_key_get(constructor, scr_species_symbol, true) : NULL; + if (species && species->kind == SCR_DYN_UNDEF && scr_bytes_ctor_elem(constructor) >= 0 && + !scr_bytes_ctor_own_species(constructor)) { + /* A built-in typed-array constructor inherits %TypedArray%'s species + * getter, which returns the constructor itself. */ + scr_dyn_release(species); + species = scr_dyn_retain(constructor); + } scr_dyn_release(constructor); if (scr_exc_pending()) { scr_dyn_release(species); *failed = true; return NULL; } if (!species || species->kind == SCR_DYN_UNDEF || species->kind == SCR_DYN_NULL || @@ -5096,33 +5126,40 @@ ScrDyn *scr_dyn_bytes_method(ScrDyn *recv, const char *method, ScrDyn *const *ar bool failed; ScrDyn *species = recv->buffer ? NULL : scr_bytes_species(recv, &failed); if (!recv->buffer && failed) return NULL; + if (species && !subarray) { + /* TypedArraySpeciesCreate runs first: the constructor can change the + * source, and the copy reads its elements as they are afterwards, + * up to its current length. */ + size_t from = scr_bytes_rel_index_of(startD, blen), to = scr_bytes_rel_index_of(endD, blen); + size_t count = to > from ? to - from : 0; + ScrDyn *result = scr_bytes_static_create(species, count); + scr_dyn_release(species); + if (!result) return NULL; + size_t end = to < bytes->len ? to : bytes->len; + for (size_t k = from, n = 0; k < end; k++, n++) + scr_bytes_set(result->v.bytes, (double)n, scr_bytes_get(bytes, (double)k)); + return result; + } ScrBytes *out = subarray ? scr_bytes_subarray(bytes, startD, endD) : scr_bytes_slice(bytes, startD, endD); if (!species) { ScrDyn *d = recv->buffer ? scr_dyn_new_buffer(out) : scr_dyn_new_bytes(out); scr_bytes_release(out); return d; } - ScrDyn *result; - if (subarray) { - /* The species constructor views the same buffer. */ - ScrDyn *ctor_args = scr_dyn_new_arr(); - scr_dyn_arr_push(ctor_args, scr_array_buffer_from_bytes(bytes)); - scr_dyn_arr_push(ctor_args, scr_dyn_new_num(scr_bytes_byte_offset(out))); - scr_dyn_arr_push(ctor_args, scr_dyn_new_num((double)out->len)); - ScrStr *what = scr_str_new("TypedArray species", 18); - result = scr_dyn_construct(species, ctor_args, what); - scr_str_release(what); - scr_dyn_release(ctor_args); - if (result && (result->kind != SCR_DYN_BYTES || result->v.bytes->is_data_view)) { - scr_dyn_release(result); - result = NULL; - static const char message[] = "this is not a typed array."; - scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); - } - } else { - result = scr_bytes_static_create(species, out->len); - for (size_t i = 0; result && i < out->len; i++) - scr_bytes_set(result->v.bytes, (double)i, scr_bytes_get(out, (double)i)); + /* A subarray's species constructor views the same buffer. */ + ScrDyn *ctor_args = scr_dyn_new_arr(); + scr_dyn_arr_push(ctor_args, scr_array_buffer_from_bytes(bytes)); + scr_dyn_arr_push(ctor_args, scr_dyn_new_num(scr_bytes_byte_offset(out))); + scr_dyn_arr_push(ctor_args, scr_dyn_new_num((double)out->len)); + ScrStr *what = scr_str_new("TypedArray species", 18); + ScrDyn *result = scr_dyn_construct(species, ctor_args, what); + scr_str_release(what); + scr_dyn_release(ctor_args); + if (result && (result->kind != SCR_DYN_BYTES || result->v.bytes->is_data_view)) { + scr_dyn_release(result); + result = NULL; + static const char message[] = "this is not a typed array."; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); } scr_dyn_release(species); scr_bytes_release(out); @@ -6382,6 +6419,45 @@ bool scr_dyn_same_value(const ScrDyn *left, const ScrDyn *right) { static void scr_dyn_descriptor_fields_drop(ScrDyn **fields) { for (size_t i = 0; i < 6; i++) scr_dyn_release(fields[i]); } + +/* ToPropertyDescriptor: the present fields of `descriptor` (enumerable, + * configurable, value, writable, get, set; +1 each, NULL when absent), + * read in that order through any getters. False with a TypeError pending + * for a primitive descriptor, a getter's exception, or an invalid + * combination; every field is then released. */ +static bool scr_dyn_descriptor_fields_read(const ScrDyn *descriptor, ScrDyn **fields) { + for (size_t i = 0; i < 6; i++) fields[i] = NULL; + if (descriptor->kind != SCR_DYN_OBJ) { + static const char msg[] = "Property description must be an object"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + return false; + } + static const char *const names[] = {"enumerable", "configurable", "value", "writable", "get", "set"}; + static const size_t lengths[] = {10, 12, 5, 8, 3, 3}; + for (size_t i = 0; i < 6; i++) { + if (!scr_dyn_obj_get(descriptor, names[i], lengths[i])) continue; + fields[i] = scr_dyn_obj_read(descriptor, names[i], lengths[i]); + if (!fields[i]) { + scr_dyn_descriptor_fields_drop(fields); + return false; + } + } + ScrDyn *get = fields[4], *set = fields[5]; + if ((get || set) && (fields[2] || fields[3])) { + scr_dyn_descriptor_fields_drop(fields); + static const char msg[] = "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + return false; + } + if ((get && get->kind != SCR_DYN_UNDEF && get->kind != SCR_DYN_FUNC) || + (set && set->kind != SCR_DYN_UNDEF && set->kind != SCR_DYN_FUNC)) { + scr_dyn_descriptor_fields_drop(fields); + static const char msg[] = "Getter and setter must be functions"; + scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + return false; + } + return true; +} static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key); static void scr_error_sync_slots(const ScrDyn *view, ScrDyn *obj, const ScrStr *key); @@ -8374,12 +8450,19 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) if (numeric) { /* [[DefineOwnProperty]] of an element: a valid index and a data * descriptor that keeps it writable, enumerable and configurable. */ + /* The descriptor is read first (ToPropertyDescriptor), through its + * getters, and must be an object. */ + ScrDyn *fields[6]; + if (!scr_dyn_descriptor_fields_read(descriptor, fields)) { + scr_str_release(name); + return NULL; + } + const ScrDyn *enumerable = fields[0], *configurable = fields[1], *value = fields[2]; + const ScrDyn *writable = fields[3]; + bool accessor = fields[4] || fields[5]; bool valid = !signbit(index) && trunc(index) == index && index < (double)target->v.bytes->len; - const ScrDyn *configurable = scr_dyn_obj_get(descriptor, "configurable", 12); - const ScrDyn *enumerable = scr_dyn_obj_get(descriptor, "enumerable", 10); - const ScrDyn *writable = scr_dyn_obj_get(descriptor, "writable", 8); - bool accessor = scr_dyn_obj_get(descriptor, "get", 3) || scr_dyn_obj_get(descriptor, "set", 3); if (!valid) { + scr_dyn_descriptor_fields_drop(fields); scr_str_release(name); static const char message[] = "Invalid typed array index"; scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); @@ -8387,6 +8470,7 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) } if (accessor || (configurable && !scr_dyn_truthy(configurable)) || (enumerable && !scr_dyn_truthy(enumerable)) || (writable && !scr_dyn_truthy(writable))) { + scr_dyn_descriptor_fields_drop(fields); ScrJsonBuf message; scr_jb_init(&message); scr_jb_puts(&message, "Cannot redefine property: "); @@ -8396,13 +8480,15 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) return NULL; } scr_str_release(name); - const ScrDyn *value = scr_dyn_obj_get(descriptor, "value", 5); double number; + bool stored = true; if (value) { - if (!scr_dyn_number_coerce_js(value, &number)) return NULL; - scr_bytes_set(target->v.bytes, index, number); + stored = scr_dyn_number_coerce_js(value, &number); + /* The conversion can run user code; the index is checked again. */ + if (stored && index < (double)target->v.bytes->len) scr_bytes_set(target->v.bytes, index, number); } - return scr_dyn_retain(target); + scr_dyn_descriptor_fields_drop(fields); + return stored ? scr_dyn_retain(target) : NULL; } bool fenced = scr_bytes_protocol_name(name) && !scr_bytes_has_named(target, name); if (fenced) scr_bytes_protocol_fence(name->data, name->len); @@ -8420,44 +8506,16 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) } ScrStr *name = scr_dyn_property_key(key); if (!name) return NULL; - if (descriptor->kind != SCR_DYN_OBJ) { + ScrDyn *fields[6]; + if (!scr_dyn_descriptor_fields_read(descriptor, fields)) { scr_str_release(name); - static const char msg[] = "Property description must be an object"; - scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); return NULL; } - static const char *const names[] = {"enumerable", "configurable", "value", "writable", "get", "set"}; - static const size_t lengths[] = {10, 12, 5, 8, 3, 3}; - ScrDyn *fields[6] = {NULL}; - for (size_t i = 0; i < 6; i++) { - if (!scr_dyn_obj_get(descriptor, names[i], lengths[i])) continue; - fields[i] = scr_dyn_obj_read(descriptor, names[i], lengths[i]); - if (!fields[i]) { - scr_dyn_descriptor_fields_drop(fields); - scr_str_release(name); - return NULL; - } - } ScrDyn *e = fields[0], *c = fields[1], *value = fields[2]; ScrDyn *w = fields[3], *get = fields[4], *set = fields[5]; ScrDynEntry *prior = scr_dyn_entry(target, name); bool accessor_descriptor = get || set; bool data_descriptor = value || w; - if (accessor_descriptor && data_descriptor) { - scr_dyn_descriptor_fields_drop(fields); - scr_str_release(name); - static const char msg[] = "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"; - scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); - return NULL; - } - if ((get && get->kind != SCR_DYN_UNDEF && get->kind != SCR_DYN_FUNC) || - (set && set->kind != SCR_DYN_UNDEF && set->kind != SCR_DYN_FUNC)) { - scr_dyn_descriptor_fields_drop(fields); - scr_str_release(name); - static const char msg[] = "Getter and setter must be functions"; - scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); - return NULL; - } bool accessor = accessor_descriptor || (!data_descriptor && prior && prior->accessor); bool writable = w ? scr_dyn_truthy(w) : prior && !prior->accessor && !accessor ? prior->writable : false; bool enumerable = e ? scr_dyn_truthy(e) : prior ? prior->enumerable : false; diff --git a/tests/corpus/7753-typed-array-named-properties.js b/tests/corpus/7753-typed-array-named-properties.js index b49f100b5..449e78ac7 100644 --- a/tests/corpus/7753-typed-array-named-properties.js +++ b/tests/corpus/7753-typed-array-named-properties.js @@ -23,6 +23,17 @@ function expandos(TA) { try { Object.defineProperty(a, "7", { value: 1 }); } catch (e) { console.log(e.constructor.name, e.message); } try { Object.defineProperty(a, "-0", { value: 1 }); } catch (e) { console.log(e.constructor.name, e.message); } try { Object.defineProperty(a, "0", { get() { return 1; } }); } catch (e) { console.log(e.constructor.name, e.message); } + // An element's descriptor is read through its getters, and must be an object. + const got = []; + Object.defineProperty(a, "1", { get value() { got.push("value"); return 6; }, get enumerable() { got.push("enumerable"); return true; } }); + console.log(a[1], got); + for (const bad of [5, "x", undefined]) { + try { Object.defineProperty(a, "0", bad); } catch (e) { console.log(e.constructor.name); } + } + try { Object.defineProperty(a, "9", 5); } catch (e) { console.log(e.constructor.name); } + try { Object.defineProperty(a, "0", { get configurable() { throw new RangeError("read"); } }); } catch (e) { console.log(e.constructor.name, e.message); } + try { Object.defineProperty(a, "0", { value: 1, get: undefined }); } catch (e) { console.log(e.constructor.name); } + console.log(a[0], a[1]); let reads = 0; Object.defineProperty(a, "g", { get() { reads++; return this.length; }, enumerable: true }); console.log(a.g, reads, new TA(0)); @@ -56,6 +67,18 @@ function species(TA, Other) { const u = new TA([1, 2, 3]); u.constructor = { [Symbol.species]: Uint16Array }; console.log(u.filter(() => true), u.subarray(1), u.subarray(1).constructor === Uint16Array); + // A built-in constructor set as `constructor` is its own species. + const w = new TA([1, 2, 3]); + w.constructor = Float64Array; + const wm = w.map((v) => v / 2), ws = w.slice(1); + console.log(wm, wm.constructor === Float64Array, ws, ws.constructor === Float64Array, w.filter((v) => v > 1)); + w.constructor = TA; + console.log(w.map((v) => v / 2).constructor === TA); + // slice copies the source after the species constructor ran. + const x = new TA([1, 2, 3, 4]); + x.constructor = {}; + x.constructor[Symbol.species] = function (n) { x[1] = 40; x[2] = 30; return new Other(n); }; + console.log(x.slice(1, 3), x.slice(-1), x.slice(2, 1)); } function reflection(TA) { From 01e6d55fc56e00e9ba91b55f29e021029d3289f3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:05:18 -0500 Subject: [PATCH 028/230] Keep optional-element arrays through nested conversions, union arms, rest packing and receiver reassignment - Checked conversions from dynamic values key their builders and matchers by the type with `?` after every `(T | undefined)[]`, at any depth. `number[][]` and `(number | undefined)[][]` share a typeKey, so the second conversion reused the first one's builder and rejected undefined elements. - The dynamic matcher of a `(T | undefined)[]` takes undefined elements, so a union with such an arm matches arrays that hold undefined. - An optional-element rest parameter packs tuple-spread values and plain arguments together as present undefined elements, each evaluated once in argument order, instead of converting each value to T. - The element-state scan counts an argument in a rest position that may be undefined, and a spread tuple with undefined elements, as undefined elements of the rest array, so reads in the callee check element states. - A union index read keeps the receiver in a temporary when the key can assign it, so `a[(a = other, 0)]` reads the array `a` held before the key. --- packages/compiler/src/backend/llvm/dyn.ts | 34 +++++++++++-- .../src/frontend/lowering/call-arguments.ts | 26 +++++++++- .../lowering/expressions/element-access.ts | 15 ++++-- .../compiler/src/frontend/lowering/lowerer.ts | 24 ++++++++- .../lowering/runtime-optional-elements.ts | 19 ++++++- .../8004-optional-element-conversions.ts.json | 6 +++ .../8004-optional-element-conversions.ts | 49 +++++++++++++++++++ .../8004-optional-element-conversions.ts.json | 1 + 8 files changed, 162 insertions(+), 12 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8004-optional-element-conversions.ts.json create mode 100644 tests/corpus/8004-optional-element-conversions.ts create mode 100644 tests/harness/corpus-costs/8004-optional-element-conversions.ts.json diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 57c8b5177..4ec710b28 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -66,6 +66,25 @@ import { recordMayKeepSource, recordSourceFieldOrder } from "./record-sources.js import type { WalkerHost } from "./walkers.js"; import type { LlStreamTypedRefAdapter } from "./expr-context.js"; +/** typeKey with `?` after every `(T | undefined)[]` (optionalElements), + * at any depth of arrays, Maps, Sets and promises: checked conversions to + * `number[][]` and `(number | undefined)[][]` need separate helpers, though + * the two types share a typeKey. */ +function checkedConversionKey(t: IrType): string { + switch (t.kind) { + case "array": + return `array<${checkedConversionKey(t.elem)}>${t.optionalElements ? "?" : ""}`; + case "map": + return `map<${checkedConversionKey(t.key)},${checkedConversionKey(t.value)}>`; + case "set": + return `set<${checkedConversionKey(t.elem)}>`; + case "promise": + return `promise<${checkedConversionKey(t.inner)}>`; + default: + return typeKey(t); + } +} + /** Whether to-dyn of a record shape keeps the record itself: an empty shape * and an event-listener record (a `handleEvent` function field) box their * identity. Every other shape, tuples included, copies its fields into a @@ -1060,7 +1079,7 @@ export class LlDyn { /** `sc_dm_(ptr d) -> i1` — does this dyn fit T? Never throws. */ dynMatchHelper(t: IrType, preserveRefs = false): string { - const key = typeKey(t); + const key = checkedConversionKey(t); const matcherKey = preserveRefs ? `identity:${key}` : key; const existing = this.dynMatchers.get(matcherKey); if (existing) return existing; @@ -1375,7 +1394,7 @@ export class LlDyn { B.startBlock(l0); const n = this.lenOf(B, "%d"); const items = this.itemsOf(B, "%d"); - this.i64Loop(B, "dm.el", n, (i, brNext) => { + this.i64Loop(B, "dm.el", n, (i, brNext, next) => { this.host.declare(`declare zeroext i1 @scr_dyn_arr_has_index(ptr, ${this.S})`); const present = B.tmp(); B.line(`${present} = call zeroext i1 @scr_dyn_arr_has_index(ptr %d, ${this.S} ${i})`); @@ -1386,6 +1405,15 @@ export class LlDyn { brNext(); B.startBlock(valueLabel); const e = this.itemAt(B, items, i); + if (t.optionalElements) { + // A `(T | undefined)[]` stored as T elements takes undefined + // elements as well. + const undef = B.tmp(); + const checkLabel = B.newLabel("dm.check"); + B.line(`${undef} = icmp eq i32 ${this.kindOf(B, e)}, ${DYN_KIND.UNDEF}`); + B.condBr(undef, next, checkLabel); + B.startBlock(checkLabel); + } const ok = B.tmp(); B.line(`${ok} = call zeroext i1 @${m}(ptr ${e})`); const lOk = B.newLabel("dm.eo"); @@ -1798,7 +1826,7 @@ export class LlDyn { * TypeError and return a dummy with the pending flag set. */ dynCheckHelper(t: IrType, preserveRefs = false): string { // A `(T | undefined)[]` stored as T elements also takes undefined ones. - const key = t.kind === "array" && t.optionalElements ? `${typeKey(t)}?` : typeKey(t); + const key = checkedConversionKey(t); const builderKey = preserveRefs ? `identity:${key}` : key; const existing = this.dynBuilders.get(builderKey); if (existing) return existing; diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index 6da0cb337..b02d0b85c 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -28,7 +28,7 @@ import { checkedIterableSpread } from "./checked-iterable-spread.js"; import { type ParamShape } from "./call-signatures.js"; import { arrayValueRead, arrayValueType } from "./array-values.js"; import { buildArrayConversion } from "./coercions/builders.js"; -import { lowerArraySpreadItems } from "./containers/array-construction.js"; +import { lowerArraySpreadItems, lowerArrayValueItems } from "./containers/array-construction.js"; import { restTupleElementTypes, typeKey } from "../type-mapper.js"; function fixedTupleSpreadInfo( @@ -413,6 +413,30 @@ export function completeArgs( ); return out; } + // Tuple-spread values (pre-lowered) and plain arguments pack the same + // way. Each value evaluates once, in argument order. + if ( + restType.optionalElements === true && + !restNodes.some((a) => !isIr(a) && ts.isSpreadElement(a)) + ) { + const stmts: IrStmt[] = []; + const values = restNodes.map((a): IrExpr => { + if (isIr(a)) { + if (a.ir.kind !== "seqExpr") return a.ir; + stmts.push(...a.ir.stmts); + return a.ir.result; + } + const value = lowerer.lowerExpr(a as ts.Expression); + const saved = lowerer.declareHiddenLocal("%callArg", value.type); + stmts.push({ kind: "varDecl", localId: saved.id, init: value, loc: value.loc }); + return { kind: "varRef", localId: saved.id, type: value.type, loc: value.loc }; + }); + const items = lowerArrayValueItems(lowerer, values, restType.elem, restType, loc); + out.push( + stmts.length === 0 ? items : { kind: "seqExpr", stmts, result: items, type: restType, loc }, + ); + return out; + } // The rest pack is a fresh array per call; surplus SPREADS copy // their elements in (JS-exact — `f(a, ...xs, b, ...ys)` packs in // order, sources untouched). diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index 70909ca7b..a2f2ebdb6 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -2471,14 +2471,21 @@ function optionalArmIndexRead( return null; if (unionArrayValueRead(lowerer, value, key, loc) === null) return null; const stmts: IrStmt[] = []; - const stable = (e: IrExpr, name: string): IrExpr => { - if (e.kind === "varRef" || e.kind === "numLit") return e; + const stable = (e: IrExpr, name: string, keep: boolean): IrExpr => { + if (keep) return e; const local = lowerer.declareHiddenLocal(name, e.type); stmts.push({ kind: "varDecl", localId: local.id, init: e, loc }); return varRef(local.id, e.type, loc); }; - const receiver = stable(value, "%indexReceiver"); - const index = stable(key, "%indexKey"); + // A receiver local stays a direct read only when the key cannot assign + // it (`a[(a = other, 0)]` reads the array `a` held before the key). + const plainKey = key.kind === "varRef" || key.kind === "numLit"; + const receiver = stable( + value, + "%indexReceiver", + value.kind === "numLit" || (value.kind === "varRef" && plainKey), + ); + const index = stable(key, "%indexKey", plainKey); const read = unionArrayValueRead(lowerer, receiver, index, loc)!; return stmts.length === 0 ? read : { kind: "seqExpr", stmts, result: read, type: read.type, loc }; } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 87825d030..28288a5f1 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -6546,8 +6546,20 @@ export class Lowerer { }, iterationMayYieldUndefined: (node) => { const type = typeOrNull(node); - if (type === null || this.checker.isArrayType(type) || this.checker.isTupleType(type)) - return false; + if (type === null || this.checker.isArrayType(type)) return false; + // A tuple yields its elements, in order. + if (this.checker.isTupleType(type)) + return panicSafe( + () => + this.checker + .getTypeArguments(type as ts.TypeReference) + .some((element) => + (element.isUnionType() ? ts.constituentTypes(element) : [element]).some( + (part) => (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0, + ), + ), + true, + ); // The first type argument of Set, Map and Set iterators, // generators and iterables is the value they yield. const element = @@ -6601,6 +6613,14 @@ export class Lowerer { const rest = sig.params.find((shape) => shape.mode === "rest"); return rest?.type.kind === "array" ? [typeKey(rest.type.elem)] : []; }, + restStart: (call) => { + const callee = peel(call.expression); + if (!ts.isIdentifier(callee) && !ts.isPropertyAccessExpression(callee)) return null; + const symbol = callableSymbolOf(callee); + const sig = symbol ? signatureBySymbol.get(symbol) : undefined; + const at = sig ? sig.params.findIndex((shape) => shape.mode === "rest") : -1; + return at >= 0 ? at : null; + }, markIterationBinding: (node, known) => { const symbol = symbolOf(node); if (!symbol) return false; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index fffe79a51..7561f4a65 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -31,6 +31,9 @@ export interface ElementStateHost { /** Element keys of the rest parameter that the spread arguments of * `call` fill: `[]` when the callee has none, null when it is unknown. */ restKeys(call: ts.CallExpression | ts.NewExpression): readonly string[] | null; + /** The argument index where the callee's rest parameter starts, or null + * when the callee has none or is unknown. */ + restStart?(call: ts.CallExpression | ts.NewExpression): number | null; /** Record that iterating `node`'s binding can observe `undefined`; * `known` when the iterated array's own facts (not only those shared by * its element ABI) say so. Returns true when the binding's record grew. */ @@ -518,14 +521,26 @@ export class ArrayElementStates { private scanCall(node: ts.CallExpression | ts.NewExpression): boolean { let changed = false; const args = node.arguments?.nodes ?? []; - // Spreading a sparse array into a rest parameter fills the holes. + // Spreading a sparse array, or a tuple with undefined elements, into a + // rest parameter fills the holes. if (args.some((arg) => ts.isSpreadElement(arg))) { for (const arg of args) { - if (!ts.isSpreadElement(arg) || !this.mayYieldUndefined(arg.expression)) continue; + if (!ts.isSpreadElement(arg) || !this.valueMayBeUndefined(arg)) continue; const rest = this.host.restKeys(node) ?? this.host.arrayKeys(arg.expression); if (this.addUndefined(rest)) changed = true; } } + // An argument in a rest position that may be undefined is a present + // undefined element of the rest array. + const restStart = this.host.restStart?.(node) ?? null; + if (restStart !== null) + for (const arg of args.slice(restStart)) + if ( + !ts.isSpreadElement(arg) && + this.host.mayBeUndefined(arg) && + this.addUndefined(this.host.restKeys(node)) + ) + changed = true; if (this.record(node)) changed = true; const callee = peel(node.expression); if (!ts.isPropertyAccessExpression(callee)) return changed; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8004-optional-element-conversions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8004-optional-element-conversions.ts.json new file mode 100644 index 000000000..2a1bc7c66 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8004-optional-element-conversions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8004-optional-element-conversions.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8004-optional-element-conversions.ts b/tests/corpus/8004-optional-element-conversions.ts new file mode 100644 index 000000000..2a394c331 --- /dev/null +++ b/tests/corpus/8004-optional-element-conversions.ts @@ -0,0 +1,49 @@ +// `(T | undefined)[]` values built by checked conversions, rest parameters +// and union index reads: nested arrays converted beside their non-optional +// form, a union arm that is an optional-element array, undefined arguments +// and tuple spreads packed into a rest parameter, and an index expression +// that reassigns the receiver. + +const plainRows = JSON.parse("[[1, 2], [3]]") as number[][]; +console.log(plainRows); +const optionalSource: unknown = (JSON.parse("[[1, null], [null, 4]]") as unknown[][]).map((row) => + row.map((v) => (v === null ? undefined : v)), +); +const optionalRows = optionalSource as (number | undefined)[][]; +console.log(optionalRows, optionalRows[0]![1], optionalRows[1]!.length); + +const armSource: unknown = [1, undefined, 3]; +const arm = armSource as (number | undefined)[] | string; +console.log(typeof arm === "string" ? arm : arm.length, arm); +const stringArm = ["a", undefined] as unknown as (string | undefined)[] | number; +console.log(stringArm); +const textArm = "text" as unknown as (string | undefined)[] | string; +console.log(textArm); + +function flags(...values: (boolean | undefined)[]): string { + let text = ""; + for (const value of values) text += value === undefined ? "u" : value ? "t" : "f"; + return `${values.length}:${text}:${String(values[1])}`; +} +console.log(flags(true, undefined)); +const flagPair: [boolean, boolean | undefined] = [false, undefined]; +console.log(flags(...flagPair), flags(true, ...flagPair), flags(...flagPair, true)); +function label(first: string, ...rest: (string | undefined)[]): string { + return `${first}/${rest.length}/${rest.map((v) => v ?? "-").join("")}`; +} +const words: [string, string | undefined, string | undefined] = ["a", undefined, "c"]; +console.log(label(...words), label("b", undefined)); + +function pick(start: (number | undefined)[] | string[], other: (number | undefined)[] | string[]): string { + let a = start; + const read = a[((a = other), 0)]; + return `${String(read)} ${String(a[0])}`; +} +console.log(pick([1, undefined, 3], ["x", "y"]), pick([undefined, 2], ["z"])); +function counted(start: (number | undefined)[] | string[]): string { + let b = start; + let i = 1; + const read = b[((b = ["q"]), i--)]; + return `${String(read)} ${b.length} ${i}`; +} +console.log(counted([4, undefined]), counted(["s", "t"])); diff --git a/tests/harness/corpus-costs/8004-optional-element-conversions.ts.json b/tests/harness/corpus-costs/8004-optional-element-conversions.ts.json new file mode 100644 index 000000000..9f8eb9e05 --- /dev/null +++ b/tests/harness/corpus-costs/8004-optional-element-conversions.ts.json @@ -0,0 +1 @@ +{ "plain": 4882, "san": 6379 } From 34ada100c47f24865125092cfaeb9bab7142a12b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:07:05 -0500 Subject: [PATCH 029/230] Format the string comparison emission test --- tests/harness/string-compare-emission.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/harness/string-compare-emission.test.ts b/tests/harness/string-compare-emission.test.ts index 1a8fdddf6..b4219853e 100644 --- a/tests/harness/string-compare-emission.test.ts +++ b/tests/harness/string-compare-emission.test.ts @@ -45,9 +45,8 @@ console.log(words.join(",")); // Calls to the comparison runtime: `call`/`callq` on x86-64 and `bl` on // AArch64, with Mach-O's leading underscore on macOS. const compareCalls = (asm: string): number => - asm.match( - /\b(?:call[a-z]*|bl)\s+_?(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g, - )?.length ?? 0; + asm.match(/\b(?:call[a-z]*|bl)\s+_?(?:sc_str_cmp_u16|scr_str_cmp_u16_bytes|scr_str_cmp)\b/g) + ?.length ?? 0; // The host's assembly, and two other targets' so that every host checks the // x86-64, AArch64 and Mach-O spellings. From 2edebe7e2ff7df84903a9236aa147472950b3002 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:30:32 -0500 Subject: [PATCH 030/230] Read spread tuple fields before later rest arguments run The packing path for `(T | undefined)[]` rest parameters saved plain arguments to temporaries but read tuple fields only when it built the pack, after every later argument had run. A later argument that wrote the tuple changed values already spread into the call. Each tuple field is now saved where its spread appears, in argument order. --- .../src/frontend/lowering/call-arguments.ts | 14 +++++++++----- .../corpus/8004-optional-element-conversions.ts | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index b02d0b85c..a139bfc64 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -421,12 +421,16 @@ export function completeArgs( ) { const stmts: IrStmt[] = []; const values = restNodes.map((a): IrExpr => { + // A tuple field is read where its spread appears: a later + // argument may write the tuple. + let value: IrExpr; if (isIr(a)) { - if (a.ir.kind !== "seqExpr") return a.ir; - stmts.push(...a.ir.stmts); - return a.ir.result; - } - const value = lowerer.lowerExpr(a as ts.Expression); + value = a.ir; + if (value.kind === "seqExpr") { + stmts.push(...value.stmts); + value = value.result; + } + } else value = lowerer.lowerExpr(a as ts.Expression); const saved = lowerer.declareHiddenLocal("%callArg", value.type); stmts.push({ kind: "varDecl", localId: saved.id, init: value, loc: value.loc }); return { kind: "varRef", localId: saved.id, type: value.type, loc: value.loc }; diff --git a/tests/corpus/8004-optional-element-conversions.ts b/tests/corpus/8004-optional-element-conversions.ts index 2a394c331..07178e9b0 100644 --- a/tests/corpus/8004-optional-element-conversions.ts +++ b/tests/corpus/8004-optional-element-conversions.ts @@ -33,6 +33,22 @@ function label(first: string, ...rest: (string | undefined)[]): string { } const words: [string, string | undefined, string | undefined] = ["a", undefined, "c"]; console.log(label(...words), label("b", undefined)); +// Spread tuple fields are read before later arguments run. +const changing: [boolean, boolean | undefined] = [true, undefined]; +function flip(pair: [boolean, boolean | undefined], second: boolean | undefined): boolean { + pair[0] = !pair[0]; + pair[1] = second; + return pair[0]; +} +console.log(flags(...changing, flip(changing, true))); +console.log(flags(...changing, flip(changing, undefined), ...changing)); +const swapped: [string, string | undefined] = ["x", "y"]; +function swap(pair: [string, string | undefined]): string { + pair[0] = "w"; + pair[1] = undefined; + return "v"; +} +console.log(label("z", ...swapped, swap(swapped), ...swapped)); function pick(start: (number | undefined)[] | string[], other: (number | undefined)[] | string[]): string { let a = start; From 51557931556f79b49f4b8a00fe80c6a59aeaaf05 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:33:30 -0500 Subject: [PATCH 031/230] Decode passphrases with the key's encoding, and refuse RSA-PSS keys restricted to other digests createPrivateKey decoded a string passphrase as UTF-8 whatever the options' encoding, so a correct passphrase given in that encoding failed to decrypt, and an encrypted export read its passphrase the same way. Both now decode it with the encoding, as Node does. An RSA-PSS key whose parameters name a digest the runtime does not sign with, such as sha512-224 or sha512-256, was imported without its digest, MGF1 and salt restrictions, so it allowed operations the key forbids. It now throws a coded SC2020 error that names the digest. Parameters that are malformed still leave the key unrestricted, as OpenSSL reads them. --- .../8183-key-passphrase-encoding.cjs.json | 6 ++ packages/runtime/src/scr_pki.c | 78 +++++++++++++++---- tests/corpus/8183-key-passphrase-encoding.cjs | 28 +++++++ .../8183-key-passphrase-encoding.cjs.json | 1 + tests/harness/errors.test.ts | 32 ++++++++ 5 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8183-key-passphrase-encoding.cjs.json create mode 100644 tests/corpus/8183-key-passphrase-encoding.cjs create mode 100644 tests/harness/corpus-costs/8183-key-passphrase-encoding.cjs.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8183-key-passphrase-encoding.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8183-key-passphrase-encoding.cjs.json new file mode 100644 index 000000000..ee89a2add --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8183-key-passphrase-encoding.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8183-key-passphrase-encoding.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_pki.c b/packages/runtime/src/scr_pki.c index 908129243..9877ac6c9 100644 --- a/packages/runtime/src/scr_pki.c +++ b/packages/runtime/src/scr_pki.c @@ -1635,48 +1635,79 @@ static SpKey *sp_ed_from_der(const unsigned char *der, size_t len, bool private_ return k; } -/* RSASSA-PSS-params at *p (RFC 4055): true with the restrictions set on - * `k`, false when they are malformed or unsupported, which leaves the key - * without restrictions, as OpenSSL reads a negative or oversized salt. */ -static bool sp_pss_params_read(unsigned char **p, const unsigned char *end, SpKey *k) { +/* The name of a digest OID that RSASSA-PSS-params can hold and this + * runtime does not sign with, for its refusal. */ +static const char *sp_pss_other_digest(const mbedtls_asn1_buf *oid) { + /* 2.16.840.1.101.3.4.2.x, the NIST hash arc */ + static const unsigned char nist[] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02}; + if (oid->len == sizeof nist + 1 && memcmp(oid->p, nist, sizeof nist) == 0) { + switch (oid->p[sizeof nist]) { + case 0x05: return "sha512-224"; + case 0x06: return "sha512-256"; + case 0x07: return "sha3-224"; + case 0x08: return "sha3-256"; + case 0x09: return "sha3-384"; + case 0x0a: return "sha3-512"; + } + } + mbedtls_md_type_t md; + if (mbedtls_oid_get_md_alg(oid, &md) == 0) { + if (md == MBEDTLS_MD_MD5) return "md5"; + if (md == MBEDTLS_MD_RIPEMD160) return "ripemd160"; + } + return "this digest"; +} + +/* RSASSA-PSS-params at *p (RFC 4055): 1 with the restrictions set on `k`; + * 0 when they are malformed, which leaves the key without restrictions, as + * OpenSSL reads a negative or oversized salt; -1 with *digest named when a + * hash or MGF1 digest is one this runtime does not sign with, so that the + * key is refused rather than imported without the restrictions it carries. */ +static int sp_pss_params_read(unsigned char **p, const unsigned char *end, SpKey *k, const char **digest) { size_t n; - if (mbedtls_asn1_get_tag(p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return false; + if (mbedtls_asn1_get_tag(p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return 0; const unsigned char *params_end = *p + n; mbedtls_md_type_t md = MBEDTLS_MD_SHA1, mgf1 = MBEDTLS_MD_SHA1; int salt = 20; if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0)) { mbedtls_asn1_buf oid, params; if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0) != 0 || - mbedtls_asn1_get_alg(p, *p + n, &oid, ¶ms) != 0 || mbedtls_oid_get_md_alg(&oid, &md) != 0 || - !sp_pss_md_name(md)) - return false; + mbedtls_asn1_get_alg(p, *p + n, &oid, ¶ms) != 0) + return 0; + if (mbedtls_oid_get_md_alg(&oid, &md) != 0 || !sp_pss_md_name(md)) { + *digest = sp_pss_other_digest(&oid); + return -1; + } } if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1)) { mbedtls_asn1_buf oid, params; if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1) != 0 || mbedtls_asn1_get_alg(p, *p + n, &oid, ¶ms) != 0 || oid.len != MBEDTLS_OID_SIZE(MBEDTLS_OID_MGF1) || memcmp(oid.p, MBEDTLS_OID_MGF1, oid.len) != 0) - return false; + return 0; /* MGF1's parameters are the hash's AlgorithmIdentifier, whose * content starts with the hash's OID. */ - if (params.tag != (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) return false; + if (params.tag != (MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) return 0; unsigned char *alg = params.p; mbedtls_asn1_buf hash_oid = {.tag = MBEDTLS_ASN1_OID}; - if (mbedtls_asn1_get_tag(&alg, params.p + params.len, &hash_oid.len, MBEDTLS_ASN1_OID) != 0) return false; + if (mbedtls_asn1_get_tag(&alg, params.p + params.len, &hash_oid.len, MBEDTLS_ASN1_OID) != 0) return 0; hash_oid.p = alg; - if (mbedtls_oid_get_md_alg(&hash_oid, &mgf1) != 0 || !sp_pss_md_name(mgf1)) return false; + if (mbedtls_oid_get_md_alg(&hash_oid, &mgf1) != 0 || !sp_pss_md_name(mgf1)) { + *digest = sp_pss_other_digest(&hash_oid); + return -1; + } } if (*p < params_end && **p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2)) { if (mbedtls_asn1_get_tag(p, params_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 2) != 0 || mbedtls_asn1_get_int(p, *p + n, &salt) != 0 || salt < 0) - return false; + return 0; } *p = (unsigned char *)params_end; k->pss_params = true; k->pss_md = md; k->pss_mgf1 = mgf1; k->pss_salt = salt; - return true; + return 1; } /* An RSA-PSS key from PKCS#8 or SubjectPublicKeyInfo DER whose algorithm @@ -1706,7 +1737,19 @@ static SpKey *sp_pss_from_der(const unsigned char *der, size_t len, bool private p += oid_len; SpKey *k = sp_key_alloc(pkcs8); k->rsa_pss = true; - if (p < alg_end && !sp_pss_params_read(&p, alg_end, k)) k->pss_params = false; + const char *digest = NULL; + if (p < alg_end) { + int read = sp_pss_params_read(&p, alg_end, k, &digest); + if (read < 0) { + sp_key_release(k); + *failed = true; + char what[96]; + snprintf(what, sizeof what, "RSA-PSS keys restricted to %s", digest); + sp_not_supported(what); + return NULL; + } + if (read == 0) k->pss_params = false; + } p = (unsigned char *)alg_end; int r; if (pkcs8) { @@ -1967,7 +2010,8 @@ static ScrDyn *sp_create_key(ScrDyn *input, bool private_key) { } ScrBytes *pass = NULL; if (passphrase && !sp_absent(passphrase)) { - pass = sp_source_bytes(passphrase, NULL); + /* Node decodes a string passphrase with the key's encoding. */ + pass = sp_source_bytes(passphrase, encoding); if (!pass) { scr_bytes_release(bytes); return NULL; @@ -2292,7 +2336,7 @@ static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) { /* OpenSSL's error, raised once the key is encoded. */ } else if (ci) { const unsigned char *start = der + SP_DER_MAX - n; - ScrBytes *pass = sp_source_bytes(passphrase, NULL); + ScrBytes *pass = sp_source_bytes(passphrase, sp_opt(options, "encoding")); const unsigned char *pass_data = pass ? pass->data : (const unsigned char *)""; size_t pass_len = pass ? (size_t)scr_bytes_byte_len(pass) : 0; if (pkcs8) { diff --git a/tests/corpus/8183-key-passphrase-encoding.cjs b/tests/corpus/8183-key-passphrase-encoding.cjs new file mode 100644 index 000000000..e52fa6c1c --- /dev/null +++ b/tests/corpus/8183-key-passphrase-encoding.cjs @@ -0,0 +1,28 @@ +// A string passphrase is decoded with the key's encoding, as in Node: +// createPrivateKey's { encoding } and the encoding option of an encrypted +// export both apply to it. +const c = require("node:crypto"); + +const { privateKey } = c.generateKeyPairSync("ec", { namedCurve: "P-256" }); +const pass = "pässwörd"; +const hex = Buffer.from(pass).toString("hex"); +const pem = privateKey.export({ format: "pem", type: "pkcs8", cipher: "aes-256-cbc", passphrase: hex, encoding: "hex" }); +const der = privateKey.export({ format: "der", type: "pkcs8", cipher: "aes-128-cbc", passphrase: pass }); + +function attempt(label, options) { + try { + const key = c.createPrivateKey(options); + console.log(label, key.asymmetricKeyType, key.equals(privateKey)); + } catch (e) { + console.log(label, "throws", e.code); + } +} + +attempt("utf8 passphrase", { key: pem, passphrase: pass }); +attempt("hex passphrase, encoding hex", { key: Buffer.from(pem), passphrase: hex, encoding: "hex" }); +attempt("base64 passphrase, encoding base64", { key: Buffer.from(pem), passphrase: Buffer.from(pass).toString("base64"), encoding: "base64" }); +attempt("latin1 passphrase, encoding latin1", { key: Buffer.from(pem), passphrase: Buffer.from(pass).toString("latin1"), encoding: "latin1" }); +attempt("hex passphrase, no encoding", { key: pem, passphrase: hex }); +attempt("Buffer passphrase, encoding hex", { key: Buffer.from(pem), passphrase: Buffer.from(pass), encoding: "hex" }); +attempt("der, hex passphrase, encoding hex", { key: der, format: "der", type: "pkcs8", passphrase: hex, encoding: "hex" }); +attempt("hex key string, encoding hex", { key: Buffer.from(pem).toString("hex"), passphrase: hex, encoding: "hex" }); diff --git a/tests/harness/corpus-costs/8183-key-passphrase-encoding.cjs.json b/tests/harness/corpus-costs/8183-key-passphrase-encoding.cjs.json new file mode 100644 index 000000000..0d507484f --- /dev/null +++ b/tests/harness/corpus-costs/8183-key-passphrase-encoding.cjs.json @@ -0,0 +1 @@ +{ "plain": 3665, "san": 67315 } diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index c8849a10f..663806942 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -870,6 +870,38 @@ console.log(\`\${register(7)}\`); }); }); +// An RSA-PSS key whose parameters restrict it to a digest the runtime does +// not sign with (sha512-224 and sha512-256, which OpenSSL reads) throws a +// coded SC2020 error naming the digest, instead of importing the key +// without the restrictions it carries. +describe("RSA-PSS keys restricted to unimplemented digests", () => { + test("importing one throws SC2020 naming the digest", async () => { + const r = await compileAndRun( + "pss-restricted-digest", + `import { createPublicKey } from "node:crypto"; +const keys = [ + "-----BEGIN PUBLIC KEY-----\\nMIGQMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIFBQChHDAaBgkqhkiG9w0B\\nAQgwDQYJYIZIAWUDBAIFBQCiAwIBCANLADBIAkEArMDQESqjeWDLqFa1E5SzsMl4\\n6tyUsEqcDKSwRUvPilv1AUudf/w6qaXlPnAxBmFjZYihdQSZlk0czRLOX3o7gwID\\nAQAB\\n-----END PUBLIC KEY-----\\n", + "-----BEGIN PUBLIC KEY-----\\nMIGQMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0B\\nAQgwDQYJYIZIAWUDBAIGBQCiAwIBCANLADBIAkEAuOa9NkzXzMDelHCIEkf0mnKe\\nnQdalNUm9vZtIMwpDJcn5U0Vbhhd6MTfRPhSo5nW71Oh5eU7v5M3PMYOHS7cMQID\\nAQAB\\n-----END PUBLIC KEY-----\\n", +]; +for (const key of keys) { + try { + console.log(createPublicKey(key).asymmetricKeyType); + } catch (error) { + const e = error as Error & { code?: string }; + console.log(e.name, e.code, e.message); + } +} +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout).toBe( + "Error SC2020 'RSA-PSS keys restricted to sha512-224' is part of the standard library types but has no scriptc lowering yet\n" + + "Error SC2020 'RSA-PSS keys restricted to sha512-256' is part of the standard library types but has no scriptc lowering yet\n", + ); + expect(r.stderr).toBe(""); + }); +}); + // A cipher name Node lists in crypto.getCiphers() but the runtime does not // implement throws a coded SC2020 error naming the cipher when the name is // computed; literal names are refused at compile time (node-types-fenced). From a711d74ecf3171189109b576fec9378bc5f49709 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:35:51 -0500 Subject: [PATCH 032/230] Keep copied array callbacks exact with local classes, switches and contracts - A binding that a local class captures counts as written: the class's methods may assign it while the loop runs, so the call keeps its helper. - Copied bodies rename the labels of switch and for-of statements and the binding of for-of loops. - The known-union and record-temporary cleanups rewrite switch cases and for-of bodies, where they left declarations of removed locals. - Copied locals keep their source bindings. In programs that write contract types, a parameter with a source binding keeps its own local, so an Int32 parameter is checked against the elements the loop passes. --- .../lowering/containers/array-pipelines.ts | 104 +++++++++++++++--- .../test/array-pipeline-fusion.test.ts | 59 ++++++++++ tests/diagnostics/contract-int32.ts | 4 +- .../__snapshots__/contract-int32.ts.txt | 13 ++- 4 files changed, 164 insertions(+), 16 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index c83a9c447..7c2e07b1c 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -1,3 +1,5 @@ +import * as ts from "../../ts7/adapter.js"; +import { CONTRACT_TYPES_MODULE } from "../../contract-types.js"; import { keyedHelperName } from "../stable-names.js"; import { writtenLocals } from "../../../ir/callback-specialization.js"; import { AmbientReceiverReaders } from "../../../ir/receiver-readers.js"; @@ -6,6 +8,7 @@ import { BOOL, F64, VOID, + type IrBindingSource, type IrExpr, type IrFunction, type IrGlobal, @@ -667,6 +670,10 @@ class PipelineFusion { /** Bindings each function, or a closure it creates, writes after their * declaration. */ private readonly written: Map>; + /** classCaptured's answers by function name. */ + private readonly classCaptures = new Map>(); + /** Whether a module imports or re-exports the contract types. */ + private readonly contractTypes: boolean; /** Bindings of the function being fused that inlined callbacks captured. */ private inlinedCaptures = new Set(); /** The helper calls each callback with an undefined receiver; a copied @@ -680,6 +687,15 @@ class PipelineFusion { this.effects = new CallbackEffects(functions, lowerer.globalsList); this.written = writtenLocals({ functions }); this.receivers = new AmbientReceiverReaders(new Map(functions.map((fn) => [fn.name, fn]))); + this.contractTypes = lowerer.moduleOrder.some((sf) => + sf.statements.nodes.some( + (st) => + (ts.isImportDeclaration(st) || ts.isExportDeclaration(st)) && + st.moduleSpecifier !== undefined && + ts.isStringLiteral(st.moduleSpecifier) && + st.moduleSpecifier.text === CONTRACT_TYPES_MODULE, + ), + ); } run(fn: IrFunction): void { @@ -933,19 +949,22 @@ class PipelineFusion { /** inlineTarget for a callback created in `fn` whose captured bindings * nothing assigns after their declaration: `fn`'s own locals and * parameters, written neither by `fn` nor by any closure that shares - * them, and not by the callback body. The callback must not observe the - * ambient receiver (AmbientReceiverReaders). */ + * them, and not by the callback body. A binding a local class captures + * counts as written, since its methods may assign it. The callback must + * not observe the ambient receiver (AmbientReceiverReaders). */ private stableTarget(fn: IrFunction): InlineTarget { const locals = new Map(fn.locals.map((l) => [l.id, l])); const inherited = new Set((fn.captures ?? []).map((c) => c.localId)); const written = this.written.get(fn.name); + const classCaptured = this.classCaptured(fn); return (arg) => { const target = this.closureTarget(arg); if (!target || !written || arg.kind !== "closure") return null; if (!this.receivers.ignores(target)) return null; for (const id of arg.captures) { const local = locals.get(id); - if (!local || local.tdz || inherited.has(id) || written.has(id)) return null; + if (!local || local.tdz || inherited.has(id) || written.has(id) || classCaptured.has(id)) + return null; } const captures = new Set((target.captures ?? []).map((c) => c.localId)); const assigns = !everyStmtList(target.body, { @@ -957,6 +976,22 @@ class PipelineFusion { }; } + /** The bindings of `fn` that a local class it creates captures. */ + private classCaptured(fn: IrFunction): ReadonlySet { + const known = this.classCaptures.get(fn.name); + if (known) return known; + const ids = new Set(); + everyStmtList(fn.body, { + expr: (e) => { + if (e.kind === "classRef") for (const id of e.captures ?? []) ids.add(id); + return true; + }, + stmt: () => true, + }); + this.classCaptures.set(fn.name, ids); + return ids; + } + /** The function behind a closure-literal callback the loop can inline. */ private closureTarget(arg: IrExpr): IrFunction | null { if (arg.kind !== "closure" || arg.adapts) return null; @@ -967,7 +1002,12 @@ class PipelineFusion { } private emit(plan: Plan): IrExpr | null { - const built = new FusedLoop(this.lowerer, plan.chain, plan.inline ?? this.inlineTarget).build(); + const built = new FusedLoop( + this.lowerer, + plan.chain, + plan.inline ?? this.inlineTarget, + this.contractTypes, + ).build(); if (!built) return null; for (const id of built.inlinedCaptures) this.inlinedCaptures.add(id); let name = this.helpers.get(built.key); @@ -1153,10 +1193,13 @@ class FusedLoop { private next = 0; private failed = false; + /** `contractTypes`: the program writes contract types, which the + * contract checker binds to source bindings. */ constructor( private readonly lowerer: Lowerer, private readonly chain: Chain, private readonly inlineTarget: InlineTarget, + private readonly contractTypes: boolean, ) { this.loc = chain.loc; } @@ -1165,9 +1208,9 @@ class FusedLoop { return `${name}.${this.next++}`; } - private local(name: string, type: IrType, mutable = false): string { + private local(name: string, type: IrType, mutable = false, source?: IrBindingSource): string { const id = this.fresh(name); - this.locals.push({ id, name, type, mutable }); + this.locals.push(source ? { id, name, type, mutable, source } : { id, name, type, mutable }); return id; } @@ -1234,17 +1277,27 @@ class FusedLoop { return true; }, }); + // Copies keep their source bindings, so contract types written on + // them are checked against what the loop stores. In a program that + // writes contract types, a parameter with a source binding keeps its + // own local for the same reason. + const sources = new Map(fn.locals.map((l) => [l.id, l.source])); const aliased = new Set(); fn.params.forEach((p, index) => { const arg = args[index]!; - if (arg.kind === "varRef" && !assigned.has(p.localId) && typeEquals(arg.type, p.type)) { + if ( + arg.kind === "varRef" && + !assigned.has(p.localId) && + typeEquals(arg.type, p.type) && + !(this.contractTypes && sources.get(p.localId) !== undefined) + ) { renamed.set(p.localId, arg.localId); aliased.add(p.localId); } }); for (const l of fn.locals) { if (renamed.has(l.id)) continue; - const id = this.local(l.name, l.type, true); + const id = this.local(l.name, l.type, true, l.source); renamed.set(l.id, id); } const rename = (id: string): string => renamed.get(id) ?? id; @@ -1258,11 +1311,15 @@ class FusedLoop { return e; } }; - const bind: IrStmt[] = fn.params.flatMap((p, index): IrStmt[] => - aliased.has(p.localId) - ? [] - : [{ kind: "varDecl", localId: rename(p.localId), init: args[index]!, loc }], - ); + // A bound parameter's value is an element or index with no source text + // of its own; it is located at the parameter, without a span. + const bind: IrStmt[] = fn.params.flatMap((p, index): IrStmt[] => { + if (aliased.has(p.localId)) return []; + const source = sources.get(p.localId); + const arg = args[index]!; + const init = source ? { ...arg, loc: { ...source.loc, end: source.loc.start } } : arg; + return [{ kind: "varDecl", localId: rename(p.localId), init, loc }]; + }); const labels = new Map(); const label = (name: string): string => { let id = labels.get(name); @@ -1281,9 +1338,14 @@ class FusedLoop { case "break": case "continue": return st.label ? { ...st, label: label(st.label) } : st; + case "forOf": { + const renamedFor = { ...st, localId: rename(st.localId) }; + return st.labels ? { ...renamedFor, labels: st.labels.map(label) } : renamedFor; + } case "while": case "doWhile": case "for": + case "switch": case "block": return st.labels ? { ...st, labels: st.labels.map(label) } : st; default: @@ -1946,8 +2008,11 @@ function foldKnownUnions(fn: Omit): void { case "for": case "while": case "doWhile": + case "forOf": case "block": return { ...s, body: prune(s.body) }; + case "switch": + return { ...s, cases: s.cases.map((c) => ({ ...c, body: prune(c.body) })) }; default: return s; } @@ -2135,6 +2200,19 @@ function scalarizeRecordTemporaries(lowerer: Lowerer, fn: Omit ({ + test: c.test === null ? null : rewriteExpr(c.test), + body: rewriteList(c.body), + })), + }, + ]; case "block": return [{ ...s, body: rewriteList(s.body) }]; default: { diff --git a/packages/compiler/test/array-pipeline-fusion.test.ts b/packages/compiler/test/array-pipeline-fusion.test.ts index 0ff456a2c..ba814105f 100644 --- a/packages/compiler/test/array-pipeline-fusion.test.ts +++ b/packages/compiler/test/array-pipeline-fusion.test.ts @@ -154,3 +154,62 @@ console.log(a.length, b.length, limit); ${postlude}`); expect(helperCalls(init(mod))).toEqual(["%arr.filter", "%arr.map"]); }); + +test("a lone call keeps its helper when a local class's method can assign a capture", async () => { + const mod = await lowerSource(`${prelude} +let k = 1; +class Bump { + go(): void { k = 10; } +} +const b = new Bump(); +const out = rows.map((r) => { if (r.id === 2) b.go(); return r.id * k; }); +console.log(out.join(",")); +${postlude}`); + expect(helperCalls(init(mod))).toEqual(["%arr.map"]); +}); + +test("a copied callback keeps its switch and for-of labels and bindings", async () => { + const mod = await lowerSource(`${prelude} +const out = rows.map((r) => { + let n = 0; + outer: switch (r.id % 3) { + case 0: + n = 10; + break outer; + case 1: + inner: for (const v of [1, 2, 3]) { + n += v; + if (v === 2) break outer; + if (v === 3) continue inner; + } + break; + default: + n = r.value; + } + return n; +}); +console.log(out.join(",")); +${postlude}`); + expect(helperCalls(init(mod))).toEqual(["%arr.loop"]); +}); + +test("a copied callback's switch-local records and known unions are rewritten in its cases", async () => { + const mod = await lowerSource(`${prelude} +type Shape = { kind: "sq"; side: number } | { kind: "rect"; w: number; h: number }; +const out = rows.map((r) => { + switch (r.id % 3) { + case 0: + const p = { a: r.id, b: 2 }; + return p.a + p.b; + case 1: + const sq = { kind: "sq" as const, side: r.value }; + const s: Shape = sq; + return s.kind === "sq" ? s.side * 10 : 0; + default: + return r.value; + } +}); +console.log(out.join(",")); +${postlude}`); + expect(helperCalls(init(mod))).toEqual(["%arr.loop"]); +}); diff --git a/tests/diagnostics/contract-int32.ts b/tests/diagnostics/contract-int32.ts index f6e900626..20231c535 100644 --- a/tests/diagnostics/contract-int32.ts +++ b/tests/diagnostics/contract-int32.ts @@ -29,8 +29,10 @@ function bump(): void { const sizes = [1, 2, 3]; const len: Int32 = sizes.length; +const weights = [1.5, 2]; +const shifted = weights.map((w: Int32) => w + 1); const span = new Span(1.5); span.advance(); bump(); -console.log(width(span), half(7), count, len); +console.log(width(span), half(7), count, len, shifted); diff --git a/tests/harness/__snapshots__/contract-int32.ts.txt b/tests/harness/__snapshots__/contract-int32.ts.txt index 2f3a65c1d..90f046973 100644 --- a/tests/harness/__snapshots__/contract-int32.ts.txt +++ b/tests/harness/__snapshots__/contract-int32.ts.txt @@ -48,6 +48,15 @@ contract-int32.ts:31:20 - error SC7002: the value stored into 'len' (Int32) is n 30 | const sizes = [1, 2, 3]; 31 | const len: Int32 = sizes.length; | ^~~~~~~~~~~~ - 32 | + 32 | const weights = [1.5, 2]; - hint: make the int32 conversion explicit, for example 'sizes.length | 0', so Node computes the same value \ No newline at end of file + hint: make the int32 conversion explicit, for example 'sizes.length | 0', so Node computes the same value + +contract-int32.ts:33:30 - error SC7002: the value stored into 'w' (Int32) is not provably an int32 + + 32 | const weights = [1.5, 2]; + 33 | const shifted = weights.map((w: Int32) => w + 1); + | ^ + 34 | + + hint: make the int32 conversion explicit with '| 0', so Node computes the same value \ No newline at end of file From fe6dd9a5c5fa057f799fc7072e92bc2772c96785 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:38:25 -0500 Subject: [PATCH 033/230] Skip a byte order mark at the start of package.json The run-time resolver parsed package.json as it read it, so a file that starts with a UTF-8 byte order mark failed to parse and computed require and require.resolve ended with the invalid-package-config refusal. Node's reader skips the mark; the resolver now does too. The corpus program writes the package with the marked package.json to a fresh directory at run time and resolves it by its absolute path, so no committed file contains the mark. --- packages/runtime/src/scr_require.c | 7 +++++++ .../8211-require-resolve-computed/main.ts | 6 ++++-- .../node_modules/resolve-probe/index.js | 21 ++++++++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/runtime/src/scr_require.c b/packages/runtime/src/scr_require.c index 645273bce..d1ede2186 100644 --- a/packages/runtime/src/scr_require.c +++ b/packages/runtime/src/scr_require.c @@ -138,6 +138,13 @@ static ScrDyn *req_read_package(ScrRequire *r, ScrStr *pjson) { if (scr_exc_pending()) scr_exc_clear(); return NULL; } + /* Node's reader skips a leading UTF-8 byte order mark. */ + if (text->len >= 3 && (unsigned char)text->data[0] == 0xEF && + (unsigned char)text->data[1] == 0xBB && (unsigned char)text->data[2] == 0xBF) { + ScrStr *body = scr_str_new(text->data + 3, text->len - 3); + scr_str_release(text); + text = body; + } ScrDyn *pkg = scr_json_parse(text); scr_str_release(text); if (pkg == NULL) { diff --git a/tests/corpus/8211-require-resolve-computed/main.ts b/tests/corpus/8211-require-resolve-computed/main.ts index d8e441d2f..ab94d0625 100644 --- a/tests/corpus/8211-require-resolve-computed/main.ts +++ b/tests/corpus/8211-require-resolve-computed/main.ts @@ -1,7 +1,9 @@ // require.resolve with requests computed at run time, called directly and // read as a value, resolves like Node: builtins, relative files with -// Node's extension search, package "main" and "exports" entries, a -// package's own name, and MODULE_NOT_FOUND for requests that name nothing. +// Node's extension search, package "main" and "exports" entries (a +// package.json that starts with a byte order mark included, written at run +// time), a package's own name, and MODULE_NOT_FOUND for requests that name +// nothing. import { resolveAll } from "resolve-probe"; for (const line of resolveAll()) console.log(line); diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js index 870a14401..4be08c224 100644 --- a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js @@ -1,5 +1,7 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { createRequire } from "node:module"; -import { dirname, relative } from "node:path"; +import { tmpdir } from "node:os"; +import { dirname, join, relative } from "node:path"; import { fileURLToPath } from "node:url"; const importer = fileURLToPath(import.meta.url); @@ -39,6 +41,22 @@ const requests = [ 42, ]; +// A package whose package.json starts with a byte order mark, written to a +// fresh directory and resolved by its absolute path: "main" is still read. +function resolveWithByteOrderMark() { + const dir = mkdtempSync(join(tmpdir(), "scriptc-bom-")); + try { + const pkg = join(dir, "with-bom"); + mkdirSync(join(pkg, "lib"), { recursive: true }); + const manifest = JSON.stringify({ name: "with-bom", main: "lib/entry.js" }); + writeFileSync(join(pkg, "package.json"), "\uFEFF" + manifest); + writeFileSync(join(pkg, "lib", "entry.js"), 'module.exports = "with-bom";\n'); + return relative(pkg, load.resolve(pkg)); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + export function resolveAll(options = {}) { const resolve = options.resolve ?? load.resolve; const label = (request) => (typeof request === "string" ? JSON.stringify(request) : String(request)); @@ -46,5 +64,6 @@ export function resolveAll(options = {}) { ...requests.map((request) => `call ${label(request)} -> ${describe((r) => load.resolve(r), request)}`), ...requests.slice(0, 4).map((request) => `value ${label(request)} -> ${describe(resolve, request)}`), `options ${describe((r) => load.resolve(r, {}), "with-main")}`, + `byte order mark -> ${resolveWithByteOrderMark()}`, ]; } From 65555dace3072de3546caeb9f525cea72dbfb221 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 17:47:58 -0500 Subject: [PATCH 034/230] Drop struct type names before the thin link instead of renaming promoted locals The partitions of a program build have no locals for ThinLTO to promote: symbols that other partitions refer to are already hidden. What changed the modules after importing, and with them the object cache keys, of partitions an edit did not touch was the numbering of struct types. Every partition declares the program's types under the same names, and the thin link loads the importing partition and the partitions it imports from into one context, which tells same-named types apart by appending a counter that runs across the whole link. An edit that adds a type anywhere shifted the counter. Partitions now drop the names of their struct types before their summary is written; importing matches unnamed types by their bodies. The promoted-locals option, the per-partition source file name and the partition index in the summary key are removed. --- native/llvm-codegen/README.md | 2 +- native/llvm-codegen/src/emit.cpp | 45 +++++++++----------------------- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/native/llvm-codegen/README.md b/native/llvm-codegen/README.md index d72aebdfd..70c118202 100644 --- a/native/llvm-codegen/README.md +++ b/native/llvm-codegen/README.md @@ -37,7 +37,7 @@ Repeating `--output` divides the program into that many partitions, which are co WebAssembly code generation differs from LLVM's default pipeline in two ways that do not change its output. The pass that gives multiple-entry loops a single entry runs only on functions whose control flow is irreducible; its reachability analysis grows with the square of a function's block count even when it has nothing to change. Object emission also leaves out the module pass that collects external symbols for assembly declarations, so each function's machine code is freed once it is printed instead of when the whole module is compiled. -`--cache-dir ` names a private directory the caller owns for one helper build. Simplified partitions are stored under the hash of their exact source and objects under the hash of the exact module after importing, each with a trailing digest and published by rename, so a rebuild recompiles only the partitions an edit can affect. The locals ThinLTO promotes for importing are named after their partition rather than a hash of its bitcode, so an edit to one partition leaves the partitions that import from it unchanged. +`--cache-dir ` names a private directory the caller owns for one helper build. Simplified partitions are stored under the hash of their exact source and objects under the hash of the exact module after importing, each with a trailing digest and published by rename, so a rebuild recompiles only the partitions an edit can affect. Partitions drop the names of their struct types before the thin link, which would otherwise number same-named types from different partitions in an order that any edit can shift, changing the module after importing in partitions the edit did not touch. `--import-bitcode `, given once per runtime-pack unit that ships bitcode, imports runtime functions for inlining; scriptc's program builds do not pass it. Small runtime functions reachable from the program's runtime calls are imported as `available_externally` so LLVM can inline them; the runtime objects remain the only definitions. The helper never runs a sanitizer pass, so with import it also drops the emitter's inert `sanitize_address` attributes, which would otherwise block that inlining. Without `--import-bitcode` the module is optimized exactly as emitted. diff --git a/native/llvm-codegen/src/emit.cpp b/native/llvm-codegen/src/emit.cpp index 4e8c7a30c..c087dead8 100644 --- a/native/llvm-codegen/src/emit.cpp +++ b/native/llvm-codegen/src/emit.cpp @@ -170,33 +170,18 @@ void limitProfileImports() { (void)Limited; } -// ThinLTO renames the locals it promotes so that the code other partitions -// import can refer to them: switch tables, constants and the other private -// symbols optimization creates. By default the new name carries a hash of -// the defining partition's whole bitcode, so an edit anywhere in a partition -// renames them in every partition that imports from it, and those partitions -// miss the object cache although their own code is unchanged. Named after -// the partition instead (PartitionSourceName), they keep their names while -// the partition changes. The option is process-wide; the helper sets it once. -void stablePromotedNames() { - static const bool Set = [] { - StringRef Name = "use-source-filename-for-promoted-locals"; - if (cl::Option *Option = cl::getRegisteredOptions().lookup(Name)) - Option->addOccurrence(0, Name, "true"); - return true; - }(); - (void)Set; -} - -// Gives a partition's module its own source file name, which the names of -// the locals ThinLTO promotes derive from (stablePromotedNames). It runs -// after the pre-link optimization, so profile annotation still matches the -// program's local functions under their source path. -struct PartitionSourceName : PassInfoMixin { - explicit PartitionSourceName(std::string Name) : Name(std::move(Name)) {} - std::string Name; +// Drops the names of a partition's struct types before its summary is +// written. Every partition declares the program's types under the same +// names, and a thin link loads the importing partition and the partitions +// it imports from into one context, which tells same-named types apart by +// appending a counter that runs across the whole link. An edit that adds a +// type anywhere shifts the counter, so the module after importing, the +// object cache key, changed in partitions whose code had not. Unnamed types +// carry no such suffix; importing still matches them by their bodies. +struct UnnameStructTypes : PassInfoMixin { PreservedAnalyses run(Module &M, ModuleAnalysisManager &) { - M.setSourceFileName(Name); + for (StructType *Type : M.getIdentifiedStructTypes()) + Type->setName(""); return PreservedAnalyses::all(); } static bool isRequired() { return true; } @@ -591,12 +576,8 @@ compileOptimizedPartitions(const EmitOptions &Options, size_t Count = Plan.Modules.size(); std::vector> Summaries(Count); SharedFailure Failures; - stablePromotedNames(); forEachPartition(Count, [&](size_t I) { - // The partition's index names its promoted locals, so it keys the - // summary with the partition's source. - std::string Key = - hashParts({Salt, "summary", std::to_string(I), Plan.Modules[I]}); + std::string Key = hashParts({Salt, "summary", Plan.Modules[I]}); if ((Summaries[I] = Cache.lookup(Key, "bc"))) return; std::string LookupError; @@ -629,7 +610,7 @@ compileOptimizedPartitions(const EmitOptions &Options, // link; the post-link optimization reads the annotated weights. Pipeline P(*Machine, Level, profileOptions(Options)); ModulePassManager MPM = P.PB.buildThinLTOPreLinkDefaultPipeline(Level); - MPM.addPass(PartitionSourceName("scriptc-partition-" + std::to_string(I))); + MPM.addPass(UnnameStructTypes()); MPM.addPass(BitcodeWriterPass(Stream, /*ShouldPreserveUseListOrder=*/false, /*EmitSummaryIndex=*/true, /*EmitModuleHash=*/true)); From c41af6c36dc2a741ee762dbc57c485f39cb6225f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 19:04:38 -0500 Subject: [PATCH 035/230] Move the MIME handle tags to 81 and 82 Main now uses tag 78 (STRING_DECODER), 79 (MODULE_NAMESPACE, after #2161) and 80; 81 and 82 are the next free values. --- packages/runtime/src/scr_dyn.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 4fcba7bfe..f7014f259 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -186,8 +186,8 @@ typedef enum { X(MATCH_SOURCE, 55) /* a match result's pending metadata (scr_regex.c) */ \ X(MESSAGE_EVENT, 50) /* ScrEvent with data — BroadcastChannel's 'message' */ \ X(MESSAGE_PORT, 36) \ - X(MIME_PARAMS, 78) /* util.MIMEParams (scr_mime.c) */ \ - X(MIME_TYPE, 79) /* util.MIMEType (scr_mime.c) */ \ + X(MIME_PARAMS, 81) /* util.MIMEParams (scr_mime.c) */ \ + X(MIME_TYPE, 82) /* util.MIMEType (scr_mime.c) */ \ X(NET_BLOCK_LIST, 48) /* net.BlockList (scr_net.c) */ \ X(NET_SERVER, 3) /* ScrNetServer — net.Server (http.Server rides the same handle) */ \ X(NET_SOCKET, 2) /* ScrNetSocket — net.Socket */ \ From 320a97bf3b8dd57c35376dfedcb5cb67aef44051 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:19:50 -0500 Subject: [PATCH 036/230] Throw Node's TypeError for util.diff of two distinct empty sequences util.diff([], []) with two arrays read one element past its trace row while backtracking. Node's myersDiff answers undefined for two empty sequences, and util.diff's ArrayPrototypeReverse throws "Cannot convert undefined or null to object"; scr_util_diff now throws the same TypeError before it allocates the trace. --- packages/runtime/src/scr_util_callable.c | 11 +++++++++++ tests/corpus/7705-util-diff.cjs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/scr_util_callable.c b/packages/runtime/src/scr_util_callable.c index df6a68272..574b9d423 100644 --- a/packages/runtime/src/scr_util_callable.c +++ b/packages/runtime/src/scr_util_callable.c @@ -881,6 +881,17 @@ ScrDyn *scr_util_diff(ScrDyn *actual, ScrDyn *expected) { scr_throw_error_msg_code(SCR_ERR_RANGE, message, (size_t)n, "ERR_OUT_OF_RANGE"); return NULL; } + /* Two distinct empty sequences: Node's myersDiff reads past its + * one-element state, finds no end and answers undefined, which the + * ArrayPrototypeReverse of util.diff rejects. The trace below would be read + * out of bounds. */ + if (max == 0) { + uc_diff_seq_free(&a); + uc_diff_seq_free(&e); + static const char message[] = "Cannot convert undefined or null to object"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + return NULL; + } size_t width = 2 * max + 1; int32_t *v = calloc(width, sizeof *v); int32_t **trace = malloc((max + 1) * sizeof *trace); diff --git a/tests/corpus/7705-util-diff.cjs b/tests/corpus/7705-util-diff.cjs index 9e52fd165..489586a3b 100644 --- a/tests/corpus/7705-util-diff.cjs +++ b/tests/corpus/7705-util-diff.cjs @@ -7,7 +7,7 @@ const show = (a, e) => { try { console.log(JSON.stringify(util.diff(a, e))); } catch (err) { console.log(err.name, err.code, err.message); } }; show("abc", "abd"); show("kitten", "sitting"); show("", "x"); show("same", "same"); show("", ""); -show(["a", "b", "c"], ["a", "c", "d"]); show(["x"], []); show([], ["y", "z"]); +show(["a", "b", "c"], ["a", "c", "d"]); show(["x"], []); show([], ["y", "z"]); show([], []); const arr = ["p"]; show(arr, arr); show(1, 1); show(1, 2); show("a", 2); show(["a", 1], ["a"]); show(["a"], [, "b"]); show(null, "x"); show("x", undefined); show("héllo😀", "hello😀"); show({}, "x"); show("the quick brown fox", "the quack brown box"); From e6a8d735ca0355d24c3e57fb3a5320119aeab75f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 20:20:39 -0500 Subject: [PATCH 037/230] Bound strict throttle state and evaluate MIME arguments before the call A strict throttler kept every invocation timestamp until its whole window emptied, so sustained traffic grew the array without bound; ticks that left the window now give their slots back, and at most limit ticks are live. new MIMEType, new MIMEParams and MIMEType.parse in JavaScript evaluated surplus arguments after the call. All arguments now evaluate first, in order, as in JavaScript, so a surplus argument's effects happen before the constructor coerces or throws. --- .../src/frontend/lowering/builtins/mime.ts | 80 +++++++++---------- packages/runtime/src/scr_util_timing.c | 7 ++ tests/corpus/7707-util-mime-type.cjs | 8 ++ 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/builtins/mime.ts b/packages/compiler/src/frontend/lowering/builtins/mime.ts index da3d16712..27f95226c 100644 --- a/packages/compiler/src/frontend/lowering/builtins/mime.ts +++ b/packages/compiler/src/frontend/lowering/builtins/mime.ts @@ -2,7 +2,7 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { locOf } from "../../program.js"; import { dynUndefinedExpr, numLit } from "../../../ir/build.js"; -import { BOOL, DYN, type IrExpr } from "../../../ir/ir.js"; +import { BOOL, DYN, type IrExpr, type IrLibFn } from "../../../ir/ir.js"; import { builtinImportOf } from "./module-bindings.js"; /* node:util's MIMEType and MIMEParams are native checked-dynamic handles @@ -34,38 +34,51 @@ function mimeValueClass(lowerer: Lowerer, node: ts.Expression): MimeClass | null return lowerer.isStdlibSymbol(sym) ? name : null; } -/** Every argument in order, as checked-dynamic values; the first `count` - * fill the call, missing ones as undefined, and the others still - * evaluate. */ -function mimeArguments( +/** The call of `fn` over the first `count` arguments, as checked-dynamic + * values (missing ones as undefined). Every argument evaluates in order + * before the call, as in JavaScript: with surplus arguments, the used ones + * are held in hidden locals while the others evaluate. */ +function mimeCall( lowerer: Lowerer, + fn: IrLibFn, nodes: readonly ts.Expression[], count: number, loc: ReturnType, -): { used: IrExpr[]; extra: IrExpr[] } { - const values = nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)); - const used = values.slice(0, count); - while (used.length < count) used.push(dynUndefinedExpr(loc)); - return { used, extra: values.slice(count) }; -} - -/** The call, after which any surplus arguments evaluate. */ -function withSurplus( - lowerer: Lowerer, - call: IrExpr, - extra: IrExpr[], - loc: ReturnType, ): IrExpr { - if (extra.length === 0) return call; - const local = lowerer.declareHiddenLocal("%mimeResult", call.type); + const values = nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)); + if (values.length <= count) { + const args = [...values]; + while (args.length < count) args.push(dynUndefinedExpr(loc)); + return { kind: "libCall", fn, args, type: DYN, loc }; + } + const locals = values.slice(0, count).map((value) => ({ + value, + local: lowerer.declareHiddenLocal("%mimeArg", DYN), + })); return { kind: "seqExpr", stmts: [ - { kind: "varDecl", localId: local.id, init: call, loc }, - ...extra.map((arg) => ({ kind: "exprStmt" as const, expr: arg, loc: arg.loc })), + ...locals.map(({ value, local }) => ({ + kind: "varDecl" as const, + localId: local.id, + init: value, + loc, + })), + ...values.slice(count).map((arg) => ({ kind: "exprStmt" as const, expr: arg, loc: arg.loc })), ], - result: { kind: "varRef", localId: local.id, type: call.type, loc }, - type: call.type, + result: { + kind: "libCall", + fn, + args: locals.map(({ local }): IrExpr => ({ + kind: "varRef", + localId: local.id, + type: DYN, + loc, + })), + type: DYN, + loc, + }, + type: DYN, loc, }; } @@ -77,15 +90,8 @@ export function lowerMimeNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | const nodes = expr.arguments?.nodes ?? []; if (nodes.some(ts.isSpreadElement)) return null; const loc = locOf(expr); - const { used, extra } = mimeArguments(lowerer, nodes, cls === "MIMEType" ? 2 : 0, loc); - const call: IrExpr = { - kind: "libCall", - fn: cls === "MIMEType" ? "mime.typeNew" : "mime.paramsNew", - args: used, - type: DYN, - loc, - }; - return withSurplus(lowerer, call, extra, loc); + const fn: IrLibFn = cls === "MIMEType" ? "mime.typeNew" : "mime.paramsNew"; + return mimeCall(lowerer, fn, nodes, cls === "MIMEType" ? 2 : 0, loc); } const INSTANCE_METHODS: Record> = { @@ -113,13 +119,7 @@ export function lowerMimeCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr const name = access.name.text; const loc = locOf(call); if (name === "parse" && mimeClassOf(lowerer, access.expression) === "MIMEType") { - const { used, extra } = mimeArguments(lowerer, call.arguments.nodes, 1, loc); - return withSurplus( - lowerer, - { kind: "libCall", fn: "mime.typeParse", args: used, type: DYN, loc }, - extra, - loc, - ); + return mimeCall(lowerer, "mime.typeParse", call.arguments.nodes, 1, loc); } const cls = mimeValueClass(lowerer, access.expression); if (cls === null || !INSTANCE_METHODS[cls].has(name)) return null; diff --git a/packages/runtime/src/scr_util_timing.c b/packages/runtime/src/scr_util_timing.c index c5393da27..a8a700d47 100644 --- a/packages/runtime/src/scr_util_timing.c +++ b/packages/runtime/src/scr_util_timing.c @@ -392,6 +392,13 @@ static void th_reserve(UtState *s, double now) { s->window_count++; return; } + /* Ticks that left the window free their slots: at most limit ticks are + * live, so the storage stays within twice that. */ + if (s->tick_len == s->tick_cap && s->tick_head > 0) { + s->tick_len -= s->tick_head; + memmove(s->ticks, s->ticks + s->tick_head, s->tick_len * sizeof *s->ticks); + s->tick_head = 0; + } if (s->tick_len == s->tick_cap) { size_t cap = s->tick_cap ? s->tick_cap * 2 : 8; double *grown = realloc(s->ticks, cap * sizeof *grown); diff --git a/tests/corpus/7707-util-mime-type.cjs b/tests/corpus/7707-util-mime-type.cjs index 06d159306..e860d1d30 100644 --- a/tests/corpus/7707-util-mime-type.cjs +++ b/tests/corpus/7707-util-mime-type.cjs @@ -76,4 +76,12 @@ console.log(seen, `${m}`); const bare = new MIMEType("a/b"); console.log(Object.keys(bare), bare.foo, bare.params.size); console.log(format("%s|%s", bare, bare.params), "%s", new MIMEType("c/d;e=f")); + // Every argument evaluates before the constructor or parse runs. + const order = []; + const note = (label) => { order.push(label); return label; }; + const coerced = { toString() { order.push("coerce"); return "a/b"; } }; + show("surplus, then coercion", () => { new MIMEType(coerced, undefined, note("third")); return order.splice(0).join(","); }); + show("surplus, then parse error", () => { try { new MIMEType("bad", undefined, note("x")); } catch (e) { order.push(e.code); } return order.splice(0).join(","); }); + show("parse surplus", () => { MIMEType.parse(coerced, note("second")); return order.splice(0).join(","); }); + show("params surplus", () => { new MIMEParams(note("p")); return order.splice(0).join(","); }); } From f716dec7567fc2deffc7a05dcdef5bfe69802ad3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 24 Oct 2026 23:26:01 -0500 Subject: [PATCH 038/230] Convert Buffers to and from latin1 and hex text a word at a time - toString("latin1"): counts the high bytes eight at a time, copies words of ASCII bytes, and writes other bytes from a table of their UTF-8 pairs without branches. ASCII-only results are marked ASCII, and other results record their UTF-16 length (one unit per byte), so `.length` does not walk the string. - Buffer.from(text, "latin1" | "ascii" | "binary"): copies text known to be ASCII, copies words of ASCII bytes, and decodes eight bytes of one- and two-byte sequences in one word, storing each output byte without branches. Longer sequences take the previous path. - Buffer.from(text, "hex"): reads digit values from a table. - toString("hex") and toString("base64"/"base64url") mark their results ASCII, so a later decode, length or index read skips the scan for non-ASCII bytes. --- .../corpus/8005-buffer-encoding-paths.ts.json | 6 + packages/runtime/src/scr_bytes.c | 214 +++++++++++++++--- packages/runtime/src/scr_string.c | 13 ++ packages/runtime/src/scr_string.h | 3 + tests/corpus/8005-buffer-encoding-paths.ts | 105 +++++++++ .../8005-buffer-encoding-paths.ts.json | 1 + 6 files changed, 315 insertions(+), 27 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8005-buffer-encoding-paths.ts.json create mode 100644 tests/corpus/8005-buffer-encoding-paths.ts create mode 100644 tests/harness/corpus-costs/8005-buffer-encoding-paths.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8005-buffer-encoding-paths.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8005-buffer-encoding-paths.ts.json new file mode 100644 index 000000000..7d6dac570 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8005-buffer-encoding-paths.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8005-buffer-encoding-paths.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index e6df837c8..c1638596c 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -1577,6 +1577,7 @@ static ScrStr *scr_bytes_encode_b64(const uint8_t *in, size_t n, bool url) { if (!url) out[o++] = '='; } out[outlen] = '\0'; + scr_str_note_ascii(s); return s; } @@ -2497,6 +2498,106 @@ static ScrStr *scr_bytes_decode_utf16le(const uint8_t *in, size_t n) { return s; } +/* The UTF-8 bytes of U+0000..U+00FF: the byte itself below 0x80 (the + * second entry is overwritten by the next write), else a two-byte + * sequence. */ +static const uint8_t scr_latin1_utf8[256][2] = { + {0x00, 0x00}, {0x01, 0x00}, {0x02, 0x00}, {0x03, 0x00}, {0x04, 0x00}, {0x05, 0x00}, {0x06, 0x00}, {0x07, 0x00}, + {0x08, 0x00}, {0x09, 0x00}, {0x0a, 0x00}, {0x0b, 0x00}, {0x0c, 0x00}, {0x0d, 0x00}, {0x0e, 0x00}, {0x0f, 0x00}, + {0x10, 0x00}, {0x11, 0x00}, {0x12, 0x00}, {0x13, 0x00}, {0x14, 0x00}, {0x15, 0x00}, {0x16, 0x00}, {0x17, 0x00}, + {0x18, 0x00}, {0x19, 0x00}, {0x1a, 0x00}, {0x1b, 0x00}, {0x1c, 0x00}, {0x1d, 0x00}, {0x1e, 0x00}, {0x1f, 0x00}, + {0x20, 0x00}, {0x21, 0x00}, {0x22, 0x00}, {0x23, 0x00}, {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x00}, + {0x28, 0x00}, {0x29, 0x00}, {0x2a, 0x00}, {0x2b, 0x00}, {0x2c, 0x00}, {0x2d, 0x00}, {0x2e, 0x00}, {0x2f, 0x00}, + {0x30, 0x00}, {0x31, 0x00}, {0x32, 0x00}, {0x33, 0x00}, {0x34, 0x00}, {0x35, 0x00}, {0x36, 0x00}, {0x37, 0x00}, + {0x38, 0x00}, {0x39, 0x00}, {0x3a, 0x00}, {0x3b, 0x00}, {0x3c, 0x00}, {0x3d, 0x00}, {0x3e, 0x00}, {0x3f, 0x00}, + {0x40, 0x00}, {0x41, 0x00}, {0x42, 0x00}, {0x43, 0x00}, {0x44, 0x00}, {0x45, 0x00}, {0x46, 0x00}, {0x47, 0x00}, + {0x48, 0x00}, {0x49, 0x00}, {0x4a, 0x00}, {0x4b, 0x00}, {0x4c, 0x00}, {0x4d, 0x00}, {0x4e, 0x00}, {0x4f, 0x00}, + {0x50, 0x00}, {0x51, 0x00}, {0x52, 0x00}, {0x53, 0x00}, {0x54, 0x00}, {0x55, 0x00}, {0x56, 0x00}, {0x57, 0x00}, + {0x58, 0x00}, {0x59, 0x00}, {0x5a, 0x00}, {0x5b, 0x00}, {0x5c, 0x00}, {0x5d, 0x00}, {0x5e, 0x00}, {0x5f, 0x00}, + {0x60, 0x00}, {0x61, 0x00}, {0x62, 0x00}, {0x63, 0x00}, {0x64, 0x00}, {0x65, 0x00}, {0x66, 0x00}, {0x67, 0x00}, + {0x68, 0x00}, {0x69, 0x00}, {0x6a, 0x00}, {0x6b, 0x00}, {0x6c, 0x00}, {0x6d, 0x00}, {0x6e, 0x00}, {0x6f, 0x00}, + {0x70, 0x00}, {0x71, 0x00}, {0x72, 0x00}, {0x73, 0x00}, {0x74, 0x00}, {0x75, 0x00}, {0x76, 0x00}, {0x77, 0x00}, + {0x78, 0x00}, {0x79, 0x00}, {0x7a, 0x00}, {0x7b, 0x00}, {0x7c, 0x00}, {0x7d, 0x00}, {0x7e, 0x00}, {0x7f, 0x00}, + {0xc2, 0x80}, {0xc2, 0x81}, {0xc2, 0x82}, {0xc2, 0x83}, {0xc2, 0x84}, {0xc2, 0x85}, {0xc2, 0x86}, {0xc2, 0x87}, + {0xc2, 0x88}, {0xc2, 0x89}, {0xc2, 0x8a}, {0xc2, 0x8b}, {0xc2, 0x8c}, {0xc2, 0x8d}, {0xc2, 0x8e}, {0xc2, 0x8f}, + {0xc2, 0x90}, {0xc2, 0x91}, {0xc2, 0x92}, {0xc2, 0x93}, {0xc2, 0x94}, {0xc2, 0x95}, {0xc2, 0x96}, {0xc2, 0x97}, + {0xc2, 0x98}, {0xc2, 0x99}, {0xc2, 0x9a}, {0xc2, 0x9b}, {0xc2, 0x9c}, {0xc2, 0x9d}, {0xc2, 0x9e}, {0xc2, 0x9f}, + {0xc2, 0xa0}, {0xc2, 0xa1}, {0xc2, 0xa2}, {0xc2, 0xa3}, {0xc2, 0xa4}, {0xc2, 0xa5}, {0xc2, 0xa6}, {0xc2, 0xa7}, + {0xc2, 0xa8}, {0xc2, 0xa9}, {0xc2, 0xaa}, {0xc2, 0xab}, {0xc2, 0xac}, {0xc2, 0xad}, {0xc2, 0xae}, {0xc2, 0xaf}, + {0xc2, 0xb0}, {0xc2, 0xb1}, {0xc2, 0xb2}, {0xc2, 0xb3}, {0xc2, 0xb4}, {0xc2, 0xb5}, {0xc2, 0xb6}, {0xc2, 0xb7}, + {0xc2, 0xb8}, {0xc2, 0xb9}, {0xc2, 0xba}, {0xc2, 0xbb}, {0xc2, 0xbc}, {0xc2, 0xbd}, {0xc2, 0xbe}, {0xc2, 0xbf}, + {0xc3, 0x80}, {0xc3, 0x81}, {0xc3, 0x82}, {0xc3, 0x83}, {0xc3, 0x84}, {0xc3, 0x85}, {0xc3, 0x86}, {0xc3, 0x87}, + {0xc3, 0x88}, {0xc3, 0x89}, {0xc3, 0x8a}, {0xc3, 0x8b}, {0xc3, 0x8c}, {0xc3, 0x8d}, {0xc3, 0x8e}, {0xc3, 0x8f}, + {0xc3, 0x90}, {0xc3, 0x91}, {0xc3, 0x92}, {0xc3, 0x93}, {0xc3, 0x94}, {0xc3, 0x95}, {0xc3, 0x96}, {0xc3, 0x97}, + {0xc3, 0x98}, {0xc3, 0x99}, {0xc3, 0x9a}, {0xc3, 0x9b}, {0xc3, 0x9c}, {0xc3, 0x9d}, {0xc3, 0x9e}, {0xc3, 0x9f}, + {0xc3, 0xa0}, {0xc3, 0xa1}, {0xc3, 0xa2}, {0xc3, 0xa3}, {0xc3, 0xa4}, {0xc3, 0xa5}, {0xc3, 0xa6}, {0xc3, 0xa7}, + {0xc3, 0xa8}, {0xc3, 0xa9}, {0xc3, 0xaa}, {0xc3, 0xab}, {0xc3, 0xac}, {0xc3, 0xad}, {0xc3, 0xae}, {0xc3, 0xaf}, + {0xc3, 0xb0}, {0xc3, 0xb1}, {0xc3, 0xb2}, {0xc3, 0xb3}, {0xc3, 0xb4}, {0xc3, 0xb5}, {0xc3, 0xb6}, {0xc3, 0xb7}, + {0xc3, 0xb8}, {0xc3, 0xb9}, {0xc3, 0xba}, {0xc3, 0xbb}, {0xc3, 0xbc}, {0xc3, 0xbd}, {0xc3, 0xbe}, {0xc3, 0xbf}, +}; + +/* Each byte is U+00XX ("binary" normalizes here upstream): a byte below + * 0x80 is one UTF-8 byte and any other byte is two. Eight bytes without a + * high bit copy as one word; other bytes take their two table bytes and + * advance by their length, without branches (the terminator slot holds + * the last spare byte). */ +/* The number of bytes from 0x80: eight per step, each step adding its + * high bits into byte lanes that are summed every 255 steps. */ +static size_t scr_bytes_count_high(const uint8_t *in, size_t n) { + const uint64_t ones = UINT64_C(0x0101010101010101); + const uint64_t pairs = UINT64_C(0x00ff00ff00ff00ff); + size_t high = 0, i = 0; + while (n - i >= 8) { + size_t steps = (n - i) / 8 < 255 ? (n - i) / 8 : 255; + uint64_t lanes = 0; + for (size_t end = i + steps * 8; i < end; i += 8) { + uint64_t w; + memcpy(&w, in + i, 8); + lanes += (w >> 7) & ones; + } + lanes = (lanes & pairs) + ((lanes >> 8) & pairs); + high += (size_t)((lanes * UINT64_C(0x0001000100010001)) >> 48); + } + for (; i < n; i++) high += in[i] >> 7; + return high; +} + +static ScrStr *scr_bytes_latin1_text(const uint8_t *in, size_t n) { + size_t high = scr_bytes_count_high(in, n); + if (n > SIZE_MAX - high) scr_bytes_oom(); + ScrStr *s = scr_str_alloc_raw(n + high, n + high); + uint8_t *out = (uint8_t *)s->data; + if (high == 0) { + memcpy(out, in, n); + out[n] = '\0'; + scr_str_note_ascii(s); + return s; + } + const uint64_t top = UINT64_C(0x8080808080808080); + size_t i = 0, o = 0; + while (n - i >= 8) { + uint64_t w; + memcpy(&w, in + i, 8); + if (!(w & top)) { + memcpy(out + o, &w, 8); + i += 8; + o += 8; + continue; + } + for (size_t end = i + 8; i < end; i++) { + memcpy(out + o, scr_latin1_utf8[in[i]], 2); + o += 1 + (in[i] >> 7); + } + } + for (; i < n; i++) { + memcpy(out + o, scr_latin1_utf8[in[i]], 2); + o += 1 + (in[i] >> 7); + } + out[o] = '\0'; + scr_str_note_utf16_len(s, n); + return s; +} + ScrStr *scr_bytes_to_str(const ScrBytes *b, const ScrStr *enc) { SCR_BYTES_SNAPSHOT(b); const uint8_t *in = b->data; @@ -2512,21 +2613,12 @@ ScrStr *scr_bytes_to_str(const ScrBytes *b, const ScrStr *enc) { out[i * 2 + 1] = scr_hex_digits[in[i] & 0xf]; } out[n * 2] = '\0'; + scr_str_note_ascii(s); return s; } if (scr_enc_is(enc, "base64")) return scr_bytes_encode_b64(in, n, false); if (scr_enc_is(enc, "base64url")) return scr_bytes_encode_b64(in, n, true); - if (scr_enc_is(enc, "latin1")) { - /* Each byte is U+00XX ("binary" normalizes here upstream). */ - size_t high = 0; - for (size_t i = 0; i < n; i++) high += in[i] >> 7; - if (n > SIZE_MAX - high) scr_bytes_oom(); - ScrStr *s = scr_str_alloc_raw(n + high, n + high); - size_t o = 0; - for (size_t i = 0; i < n; i++) o = scr_bytes_put_cp(s->data, o, in[i]); - s->data[o] = '\0'; - return s; - } + if (scr_enc_is(enc, "latin1")) return scr_bytes_latin1_text(in, n); if (scr_enc_is(enc, "ascii")) { /* Node's ascii decode masks the high bit: byte & 0x7f. */ ScrStr *s = scr_str_alloc_raw(n, n); @@ -2621,12 +2713,25 @@ ScrStr *scr_bytes_to_str_checked_range(const ScrBytes *b, const ScrStr *enc, return out; } -static int scr_hex_val(uint8_t c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return -1; -} +/* Hex digit values, -1 for any other byte. */ +static const int8_t scr_hex_values[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; /* Both alphabets are accepted by both Buffer base64 encoding names. */ static const int8_t scr_b64_values[256] = { @@ -2720,17 +2825,71 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const size_t n = s->len; if (scr_enc_is(enc, "latin1") || scr_enc_is(enc, "ascii")) { /* Node writes charCodeAt(i) & 0xFF for BOTH spellings — an astral - * code point contributes its two surrogates' low bytes. */ + * code point contributes its two surrogates' low bytes. ASCII text + * copies. Elsewhere eight ASCII bytes copy as one word. In eight bytes + * holding two or more high bytes and no sequence longer than two + * bytes, every continuation byte's output byte (the code point's low + * byte, from the continuation and the lead before it) is computed in + * one word; each byte is then stored without branches, a continuation + * replacing the byte its lead wrote. */ ScrBytes *b = scr_bytes_conversion_storage_in(sink, n); + if (scr_str_known_ascii(s)) { + memcpy(b->data, in, n); + b->len = n; + return b; + } + /* A local copy: a byte store could otherwise change b->data. */ + uint8_t *dst = b->data; + const uint64_t top = UINT64_C(0x8080808080808080); size_t o = 0, i = 0; while (i < n) { + if (n - i >= 8) { + uint64_t w; + memcpy(&w, in + i, 8); + uint64_t high = w & top; + if (!high) { + memcpy(dst + o, &w, 8); + i += 8; + o += 8; + continue; + } +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + /* Byte k of the input is bits 8k..8k+7 of w. */ + if (i > 0 && (high & (high - 1)) && !(w & (w << 1) & (w << 2) & top)) { + uint64_t prev = (w << 8) | in[i - 1]; + uint64_t cont = w & ~(w << 1) & top; + uint64_t low = ((prev & UINT64_C(0x0303030303030303)) << 6) | + (w & UINT64_C(0x3f3f3f3f3f3f3f3f)); + uint64_t mask = (cont >> 7) * 0xff; + uint64_t out = (w & ~mask) | (low & mask); + for (unsigned k = 0; k < 8; k++) { + o -= (size_t)(cont >> (8 * k + 7)) & 1; + dst[o++] = (uint8_t)(out >> (8 * k)); + } + i += 8; + continue; + } +#endif + } + unsigned c = in[i]; + if (c < 0x80) { + dst[o++] = (uint8_t)c; + i++; + continue; + } + if (c >> 6 == 2) { + /* The rest of a two-byte sequence whose lead ended a word. */ + dst[o - 1] = (uint8_t)(((in[i - 1] & 3u) << 6) | (c & 0x3f)); + i++; + continue; + } uint32_t cp = scr_bytes_next_cp(in, &i); if (cp > 0xffff) { cp -= 0x10000; - b->data[o++] = (uint8_t)(0xd800 + (cp >> 10)); - b->data[o++] = (uint8_t)(0xdc00 + (cp & 0x3ff)); + dst[o++] = (uint8_t)(0xd800 + (cp >> 10)); + dst[o++] = (uint8_t)(0xdc00 + (cp & 0x3ff)); } else { - b->data[o++] = (uint8_t)cp; + dst[o++] = (uint8_t)cp; } } b->len = o; /* never grows past n: multi-byte cps shrink */ @@ -2762,7 +2921,7 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const /* Node interprets encoded text as the low byte of each UTF-16 unit. * ASCII can borrow storage; non-ASCII must preserve that truncation. */ ScrBytes *narrowed = NULL; - if (scr_bytes_ascii_prefix(in, n) != n) { + if (!scr_str_known_ascii(s) && scr_bytes_ascii_prefix(in, n) != n) { ScrStr *latin1 = scr_str_new("latin1", 6); narrowed = scr_bytes_encode_str(NULL, s, latin1); scr_str_release(latin1); @@ -2773,11 +2932,12 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const if (hex) { /* Stop at the first invalid pair, discarding an odd tail. */ b = scr_bytes_conversion_storage_in(sink, n / 2); - size_t o = 0; - for (size_t i = 0; i + 1 < n; i += 2) { - int hi = scr_hex_val(in[i]), lo = scr_hex_val(in[i + 1]); - if (hi < 0 || lo < 0) break; - b->data[o++] = (uint8_t)((hi << 4) | lo); + uint8_t *dst = b->data; + size_t pairs = n / 2, o = 0; + for (; o < pairs; o++) { + int hi = scr_hex_values[in[2 * o]], lo = scr_hex_values[in[2 * o + 1]]; + if ((hi | lo) < 0) break; + dst[o] = (uint8_t)((hi << 4) | lo); } b->len = o; } else { diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 2d7f46d85..a5cc15ba8 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -1991,6 +1991,19 @@ double scr_str_utf16_len(ScrStr *s) { return (double)(int64_t)units; } +/* Records the UTF-16 length of a string whose producer counted it (latin1 + * text has one unit per byte), so scr_str_utf16_len does not walk it. Only + * strings that scr_str_utf16_len would otherwise record are kept. */ +void scr_str_note_utf16_len(ScrStr *s, size_t units) { + if (s->ascii || s->len <= 64 || s->rc == SIZE_MAX) return; + if (!scr_u16len_tab) { + scr_u16len_tab = calloc(SCR_U16LEN_N, sizeof *scr_u16len_tab); + if (!scr_u16len_tab) return; + scr_sidx_register_cleanup(); + } + *scr_u16len_slot(s) = (ScrU16Len){s, units}; +} + double scr_str_char_code_at(ScrStr *s, double i) { /* A sequential scan of a non-ASCII receiver stays inside the bounded * ASCII span cached at its last resolved position. Its unit offset is an diff --git a/packages/runtime/src/scr_string.h b/packages/runtime/src/scr_string.h index fc2f15562..e269498b1 100644 --- a/packages/runtime/src/scr_string.h +++ b/packages/runtime/src/scr_string.h @@ -83,6 +83,9 @@ static inline void scr_str_hash_forget(ScrStr *s) { /* True only when every byte is proven below 0x80 (see above). */ static inline bool scr_str_known_ascii(const ScrStr *s) { return s->ascii; } +/* Record the UTF-16 length a string's producer counted (scr_string.c). */ +void scr_str_note_utf16_len(ScrStr *s, size_t units); + /* Record a proven-ASCII fact. Immortal strings may live in read-only * memory and are shared across threads, so they are never written. */ static inline void scr_str_note_ascii(ScrStr *s) { diff --git a/tests/corpus/8005-buffer-encoding-paths.ts b/tests/corpus/8005-buffer-encoding-paths.ts new file mode 100644 index 000000000..014f22abf --- /dev/null +++ b/tests/corpus/8005-buffer-encoding-paths.ts @@ -0,0 +1,105 @@ +// Buffer latin1, ascii, hex and base64 conversions in both directions: +// ASCII, binary and mixed contents at sizes around eight-byte words and the +// pool threshold, strings with code points past U+00FF, invalid hex and +// base64 text, and the lengths and characters of the strings produced. + +let seed = 12345; +function next(): number { + seed = (seed * 1103515245 + 12345) & 0x7fffffff; + return seed >>> 8; +} + +function fill(size: number, kind: string): Buffer { + const buf = Buffer.alloc(size); + for (let i = 0; i < size; i++) { + const r = next(); + if (kind === "ascii") buf[i] = 32 + (r % 95); + else if (kind === "binary") buf[i] = r & 255; + else if (kind === "high") buf[i] = 128 + (r % 128); + else buf[i] = r % 23 === 0 ? 160 + (r % 96) : 97 + (r % 26); + } + return buf; +} + +function digest(text: string): number { + let h = 0; + for (let i = 0; i < text.length; i++) h = (h * 31 + text.charCodeAt(i)) >>> 0; + return h; +} + +const sizes = [0, 1, 7, 8, 9, 15, 16, 17, 63, 64, 65, 4095, 4096, 4097, 70001]; +for (const kind of ["ascii", "binary", "high", "sparse"]) { + const rows: string[] = []; + for (const size of sizes) { + const buf = fill(size, kind); + const latin1 = buf.toString("latin1"); + const back = Buffer.from(latin1, "latin1"); + const ascii = Buffer.from(latin1, "ascii"); + const binary = Buffer.from(buf.toString("binary"), "binary"); + const hex = buf.toString("hex"); + const b64 = buf.toString("base64"); + const url = buf.toString("base64url"); + rows.push( + [ + size, + latin1.length, + digest(latin1), + back.equals(buf), + ascii.equals(buf), + binary.equals(buf), + Buffer.from(hex, "hex").equals(buf), + Buffer.from(b64, "base64").equals(buf), + Buffer.from(url, "base64url").equals(buf), + hex.length, + b64.length, + Buffer.byteLength(latin1, "latin1"), + size > 2 ? latin1.charCodeAt(size - 2) : -1, + size > 4 ? latin1.slice(1, 4).length : -1, + ].join(","), + ); + } + console.log(kind, rows.join(" ")); +} + +// Strings with code points past U+00FF keep the low byte of each UTF-16 +// unit, and an astral code point gives its two surrogates' low bytes. +const wide = [ + "abcéÿ", + "ĀŁ߿", + "xࠀ€￿y", + "\u{1F600}a\u{10FFFF}", + "é".repeat(9) + "€" + "è".repeat(9), + "aaaaaaaébbbbbbbb中cccccccc\u{1F600}", + "ÀÁÂÃÄÅÆÇࠀÈ", +]; +for (const text of wide) { + console.log( + Buffer.from(text, "latin1").toString("hex"), + Buffer.from(text, "ascii").toString("hex"), + Buffer.byteLength(text, "latin1"), + ); +} +const mixed = "é".repeat(5000) + "中" + "z".repeat(5000) + "ÿ".repeat(3000); +const mixedBytes = Buffer.from(mixed, "latin1"); +console.log(mixedBytes.length, mixedBytes[0], mixedBytes[5000], mixedBytes[5001], mixedBytes[13000]); + +// Hex stops at the first invalid pair and drops an odd last digit. +for (const text of ["0aFf", "0aF", "0a1g22", "zz00", "", "ABCDEF0123456789", "0aİ0b", "00a"]) { + console.log(JSON.stringify(text), Buffer.from(text, "hex").toString("hex"), Buffer.byteLength(text, "hex")); +} +const longHex = "0123456789abcdef".repeat(600) + "0x" + "ff".repeat(10); +console.log(Buffer.from(longHex, "hex").length); + +// base64 skips characters outside both alphabets and stops at padding. +for (const text of ["aGVsbG8=", "aGVsbG8", "aGV sbG8=", "aGV\nsbG8", "-_-_", "+/+/", "aGVsbG8=d29ybGQ=", "a", "ab", "abc", "aébc", "====", "YQ"]) { + console.log(JSON.stringify(text), Buffer.from(text, "base64").toString("hex"), Buffer.from(text, "base64url").toString("hex")); +} + +// Strings produced by hex and base64 behave as ASCII text afterwards. +const produced = fill(5000, "binary"); +const producedHex = produced.toString("hex"); +const joined = producedHex + "é" + produced.toString("base64"); +console.log(joined.length, joined.indexOf("é"), digest(joined.slice(9990, 10020))); +const latinText = fill(9000, "high").toString("latin1"); +const appended = latinText + "€"; +console.log(latinText.length, appended.length, appended.charCodeAt(8999), appended.charCodeAt(9000), appended.slice(8998).length); diff --git a/tests/harness/corpus-costs/8005-buffer-encoding-paths.ts.json b/tests/harness/corpus-costs/8005-buffer-encoding-paths.ts.json new file mode 100644 index 000000000..3aafbe296 --- /dev/null +++ b/tests/harness/corpus-costs/8005-buffer-encoding-paths.ts.json @@ -0,0 +1 @@ +{ "plain": 2558, "san": 4160 } From 12bbb9bde6069414b66a363696bad7d2bfc986b3 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:00:30 -0500 Subject: [PATCH 039/230] Keep a throttler's pending-call storage within its live calls A queue that never emptied kept the slots of calls already taken, so its storage grew with lifetime traffic even when maxPending bounded the live calls. Before growing, the queue now moves its live calls to the front. --- packages/runtime/src/scr_util_timing.c | 7 +++++++ tests/corpus/7711-util-throttle.cjs | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/packages/runtime/src/scr_util_timing.c b/packages/runtime/src/scr_util_timing.c index a8a700d47..13651f5ff 100644 --- a/packages/runtime/src/scr_util_timing.c +++ b/packages/runtime/src/scr_util_timing.c @@ -303,6 +303,13 @@ static size_t ut_pending_count(const UtState *s) { } static void ut_push(UtState *s, ScrPromise *promise /* moves in */, ScrDyn **args /* moves in */, size_t argc) { + /* Calls already taken free their slots, so a queue that never empties + * keeps its storage within twice its live calls. */ + if (s->len == s->cap && s->head > 0) { + s->len -= s->head; + memmove(s->calls, s->calls + s->head, s->len * sizeof *s->calls); + s->head = 0; + } if (s->len == s->cap) { size_t cap = s->cap ? s->cap * 2 : 4; UtCall *grown = realloc(s->calls, cap * sizeof *grown); diff --git a/tests/corpus/7711-util-throttle.cjs b/tests/corpus/7711-util-throttle.cjs index d786535ef..f776e7174 100644 --- a/tests/corpus/7711-util-throttle.cjs +++ b/tests/corpus/7711-util-throttle.cjs @@ -35,6 +35,18 @@ async function main() { console.log(c.activeCount, c.pendingCount); console.log(await ca, await cb, order, c.activeCount); + // A queue that never empties while calls are taken and others arrive. + const q = util.throttle(async (x) => { await null; return x; }, 1000, 1000, { concurrency: 1 }); + const queued = []; + for (let i = 0; i < 5; i++) queued.push(q(i)); + const results = []; + for (let i = 5; i < 25; i++) { + results.push(await queued[i - 5]); + queued.push(q(i)); + } + for (let i = 20; i < 25; i++) results.push(await queued[i]); + console.log(results.join(","), q.pendingCount, q.activeCount); + // A throwing fn rejects that call only. const e = util.throttle((x) => { if (x === 2) throw new RangeError("two"); return x; }, 5, 10); await settle("e1", e(1)); From 493785a12166c34e1a85343de0cd6804ed4d7788 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:20:44 -0500 Subject: [PATCH 040/230] Test switch labels in copied callbacks that return early A lone array callback whose body has an early return is copied into an exit block, and its statements are renamed by a separate path from the single-return case. The new test covers labeled switches, a nested labeled switch and breaks to both labels in map and some callbacks that return early. Both calls run as loops, and the IR validator accepts every renamed break. --- .../test/array-pipeline-fusion.test.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/compiler/test/array-pipeline-fusion.test.ts b/packages/compiler/test/array-pipeline-fusion.test.ts index ba814105f..e41e378a3 100644 --- a/packages/compiler/test/array-pipeline-fusion.test.ts +++ b/packages/compiler/test/array-pipeline-fusion.test.ts @@ -213,3 +213,39 @@ console.log(out.join(",")); ${postlude}`); expect(helperCalls(init(mod))).toEqual(["%arr.loop"]); }); + +test("a copied callback with early returns keeps its switch labels", async () => { + const mod = await lowerSource(`${prelude} +const out = rows.map((r) => { + let n = 0; + outer: switch (r.id % 4) { + case 0: + return -1; + case 1: + n = 10; + break outer; + default: + inner: switch (r.value % 2) { + case 0: + n = r.value; + break outer; + default: + break inner; + } + n = 1; + } + return n; +}); +const hit = rows.some((r) => { + pick: switch (r.id) { + case 3: + break pick; + default: + return false; + } + return true; +}); +console.log(out.join(","), hit); +${postlude}`); + expect(helperCalls(init(mod))).toEqual(["%arr.loop", "%arr.loop"]); +}); From 2b11f14a041c49b64e304db16abdd9e1d830f7d8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:21:25 -0500 Subject: [PATCH 041/230] Test that typed-array element descriptors are read through their getters Object.defineProperty on a typed-array index reads its descriptor with ToPropertyDescriptor (scr_dyn_descriptor_fields_read): every field once, in specification order, through its getter, and a primitive descriptor throws a TypeError. 7753 now also checks that the stored value is the value getter's result, read once; that a getter-produced writable: false is refused after the fields are read in order; and that null, booleans, BigInts and symbols are refused as descriptors. --- .../corpus/7753-typed-array-named-properties.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/corpus/7753-typed-array-named-properties.js b/tests/corpus/7753-typed-array-named-properties.js index 449e78ac7..e37181a5e 100644 --- a/tests/corpus/7753-typed-array-named-properties.js +++ b/tests/corpus/7753-typed-array-named-properties.js @@ -27,6 +27,23 @@ function expandos(TA) { const got = []; Object.defineProperty(a, "1", { get value() { got.push("value"); return 6; }, get enumerable() { got.push("enumerable"); return true; } }); console.log(a[1], got); + // Each field is read once, through its getter; the stored value is the + // getter's result, and a getter-produced `writable: false` is refused. + let valueReads = 0; + Object.defineProperty(a, "1", { get value() { valueReads++; return 40 + valueReads; } }); + console.log(a[1], valueReads); + const order = []; + try { + Object.defineProperty(a, "1", { + get writable() { order.push("writable"); return false; }, + get value() { order.push("value"); return 2; }, + get configurable() { order.push("configurable"); return true; }, + }); + } catch (e) { console.log(e.constructor.name, e.message); } + console.log(a[1], order); + for (const bad of [null, true, 1n, Symbol("d")]) { + try { Object.defineProperty(a, "1", bad); } catch (e) { console.log(e.constructor.name); } + } for (const bad of [5, "x", undefined]) { try { Object.defineProperty(a, "0", bad); } catch (e) { console.log(e.constructor.name); } } From 1e799cb60abe10a04db4b2395bbeef8abe40ef55 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:40:30 -0500 Subject: [PATCH 042/230] Answer net.Socket write() with Node's backpressure boolean, and fire 'drain' A TCP socket's write() answered nothing in typed code and true through untyped values, and a 'drain' listener never fired. Node 24 answers false when the bytes the write leaves unsent reach the socket's 65536-byte high-water mark (writeOrBuffer computes it after the socket writes), sets needDrain, and emits 'drain' when the write buffer empties; writes to an ended or destroyed socket answer false. The runtime's write entry points now return that answer, record needDrain, and fire the socket's 'drain' listeners from the flush that empties the buffer, unless the write half is ending or the socket is gone. writableLength reads the unsent bytes and writableNeedDrain the flag. on('drain') and once('drain') register on the typed path and through untyped values, and socket.write() may be used as a value. The fallback node:net declarations type write() as boolean, add the 'drain' event and both properties. RUNTIME_ABI_VERSION goes from 43 to 44: the socket write entry points return a boolean. --- packages/cli/test/native-link-info.test.ts | 4 +- .../ambient/scriptc-node-fallback.d.ts | 19 +++- .../compiler/src/backend/llvm/lib-network.ts | 2 + .../compiler/src/backend/llvm/lib-shared.ts | 2 + packages/compiler/src/backend/runtime-abi.ts | 4 +- .../src/backend/runtime-pack-targets.test.ts | 2 +- .../compiler/src/backend/runtime-pack.test.ts | 2 +- .../src/frontend/lowering/lower-server.ts | 59 +++++++++--- .../compiler/src/ir/builtin-signatures.ts | 20 +++- .../test/native-codegen-integration.test.ts | 4 +- .../7515-net-socket-backpressure.ts.json | 6 ++ .../runtime-darwin-arm64/scripts/build.mjs | 2 +- .../runtime-pack-common/scripts/build.mjs | 2 +- packages/runtime/src/scr_console.c | 2 +- packages/runtime/src/scr_core.h | 2 +- packages/runtime/src/scr_net.c | 94 ++++++++++++++++--- packages/runtime/src/scr_net.h | 12 ++- tests/corpus/7515-net-socket-backpressure.ts | 47 ++++++++++ .../7515-net-socket-backpressure.ts.json | 1 + .../self-hosting-native-toolchain.test.ts | 2 +- 20 files changed, 238 insertions(+), 50 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7515-net-socket-backpressure.ts.json create mode 100644 tests/corpus/7515-net-socket-backpressure.ts create mode 100644 tests/harness/corpus-costs/7515-net-socket-backpressure.ts.json diff --git a/packages/cli/test/native-link-info.test.ts b/packages/cli/test/native-link-info.test.ts index b4b11fa95..ef07c838d 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: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, 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_v43"); + expect(error?.stderr).toContain("scr_runtime_abi_v44"); }); }); diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 1924baece..463808a1a 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -5322,10 +5322,10 @@ declare module "node:zlib" { * with the real port discoverable as address().port (the composed read * is the lowered form — bare address() has no lowering); close([cb]) * firing once connections drain; socket write/end/destroy/pipe and the - * data/end/close/error/connect events (on and once — listeners take at - * most the declared parameter). write/end return void here (Node returns - * the backpressure boolean / the socket); on/once return void (chaining - * is fenced). connect's host is a numeric IP or "localhost" (pinned to + * data/end/close/error/connect/drain events (on and once — listeners + * take at most the declared parameter). write returns Node's + * backpressure boolean; end returns void here (Node returns the socket); + * on/once return void (chaining is fenced). connect's host is a numeric IP or "localhost" (pinned to * 127.0.0.1 — SEMANTICS.md); every failure is the async 'error' event, * and an 'error' with no listener exits 1 like an unhandled EventEmitter * 'error'. */ @@ -5357,7 +5357,13 @@ declare module "net" { readonly bytesWritten: number; /* write's callback runs once the data is flushed, with null; end's * once the socket finishes. */ - write(data: string | Uint8Array, callback?: (err?: Error | null) => void): void; + /* false when the bytes this write left unsent reach the 65536-byte + * high-water mark, as in Node; 'drain' fires when they are all sent. */ + write(data: string | Uint8Array, callback?: (err?: Error | null) => void): boolean; + /* the bytes written and not yet sent */ + readonly writableLength: number; + /* a write answered false and 'drain' has not fired yet */ + readonly writableNeedDrain: boolean; end(callback?: () => void): void; end(data: string | Uint8Array, callback?: () => void): void; destroy(): void; @@ -5405,6 +5411,7 @@ declare module "net" { | "connect" | "ready" | "timeout" + | "drain" | "readable" | "finish" | "secureConnect" @@ -5429,6 +5436,7 @@ declare module "net" { | "connect" | "ready" | "timeout" + | "drain" | "readable" | "finish" | "secureConnect" @@ -5452,6 +5460,7 @@ declare module "net" { | "connect" | "ready" | "timeout" + | "drain" | "readable" | "finish" | "secureConnect" diff --git a/packages/compiler/src/backend/llvm/lib-network.ts b/packages/compiler/src/backend/llvm/lib-network.ts index d49bbde14..35eb885cd 100644 --- a/packages/compiler/src/backend/llvm/lib-network.ts +++ b/packages/compiler/src/backend/llvm/lib-network.ts @@ -309,6 +309,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr) e.fn === "net.sockOnClose" || e.fn === "net.sockOnConnect" || e.fn === "net.sockOnTimeout" || + e.fn === "net.sockOnDrain" || e.fn === "net.sockOnReadable" || e.fn === "net.sockOnReady" || e.fn === "http.reqOnEnd" || @@ -329,6 +330,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr) "net.sockOnClose": "scr_net_sock_on_close", "net.sockOnConnect": "scr_net_sock_on_connect", "net.sockOnTimeout": "scr_net_sock_on_timeout", + "net.sockOnDrain": "scr_net_sock_on_drain", "net.sockOnReadable": "scr_net_sock_on_readable", "net.sockOnReady": "scr_net_sock_on_ready", "http.reqOnEnd": "scr_http_req_on_end", diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index def2dd6bf..2548b75d5 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1337,6 +1337,8 @@ export const LIB_FN_SYMS: Record = { "net.sockResetAndDestroy": "scr_net_sock_reset_and_destroy", "net.sockAddress": "scr_net_sock_address", "net.sockBytesWritten": "scr_net_sock_bytes_written", + "net.sockWritableLength": "scr_net_sock_writable_length", + "net.sockWritableNeedDrain": "scr_net_sock_writable_need_drain", "net.sockAddrField": "scr_net_sock_addr_field", "net.sockBytesRead": "scr_net_sock_bytes_read", "net.sockConnecting": "scr_net_sock_connecting", diff --git a/packages/compiler/src/backend/runtime-abi.ts b/packages/compiler/src/backend/runtime-abi.ts index d8e256349..7ac0877eb 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 = 43 as const; -export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v43" as const; +export const RUNTIME_ABI_VERSION = 44 as const; +export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v44" 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-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 5da2b8e59..650e7c543 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: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, 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 a87fabd2e..443c22d9e 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -129,7 +129,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, compiler: { command: "clang", identity: "fixture clang", diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts index 7359b56a8..614d6a206 100644 --- a/packages/compiler/src/frontend/lowering/lower-server.ts +++ b/packages/compiler/src/frontend/lowering/lower-server.ts @@ -2425,7 +2425,9 @@ function lowerNetSocketMethodCall( }; } if (name === "write" || name === "end") { - requireStatementPosition(lowerer, call, `socket.${name}(...)`); + // write() answers Node's backpressure boolean; end() stays a statement + if (name === "end") requireStatementPosition(lowerer, call, `socket.${name}(...)`); + const resultT = name === "write" ? BOOL : VOID; // write(chunk, encoding) — the two-argument encoding form: 'buffer' // beside a string chunk is Writable's encoding check (a synchronous // ERR_UNKNOWN_ENCODING TypeError, "Unknown encoding: buffer" — the @@ -2451,7 +2453,7 @@ function lowerNetSocketMethodCall( const socket = coerceToHandle(lowerer, access.expression, NETSOCKET_T); const chunk = lowerer.lowerExpr(args[0]!); const fn: IrLibFn = chunk.type.kind === "string" ? "net.sockWrite" : "net.sockWriteBytes"; - return { kind: "libCall", fn, args: [socket, chunk], type: VOID, loc }; + return { kind: "libCall", fn, args: [socket, chunk], type: BOOL, loc }; } } } @@ -2510,7 +2512,7 @@ function lowerNetSocketMethodCall( NARROW_DATA_HINT, ); } - main = { kind: "libCall", fn, args: [receiver(), data], type: VOID, loc }; + main = { kind: "libCall", fn, args: [receiver(), data], type: resultT, loc }; } const cb = lowerer.lowerExpr(last); let register: IrExpr; @@ -2531,8 +2533,24 @@ function lowerNetSocketMethodCall( loc, }; } - stmts.push({ kind: "exprStmt", expr: main, loc }); - return { kind: "seqExpr", stmts, result: register, type: VOID, loc }; + if (resultT.kind === "void") { + stmts.push({ kind: "exprStmt", expr: main, loc }); + return { kind: "seqExpr", stmts, result: register, type: VOID, loc }; + } + // write(data, cb): the data is written, the callback registered, and + // the write's answer is the value + const okLocal = lowerer.declareHiddenLocal("%sockWriteOk", BOOL); + stmts.push( + { kind: "varDecl", localId: okLocal.id, init: main, loc }, + { kind: "exprStmt", expr: register, loc }, + ); + return { + kind: "seqExpr", + stmts, + result: { kind: "varRef", localId: okLocal.id, type: BOOL, loc }, + type: BOOL, + loc, + }; } const maxArgs = name === "write" ? 1 : 1; const minArgs = name === "write" ? 1 : 0; @@ -2552,17 +2570,17 @@ function lowerNetSocketMethodCall( const data = lowerer.lowerExpr(args[0]!); if (data.type.kind === "string") { const fn: IrLibFn = name === "write" ? "net.sockWrite" : "net.sockEndStr"; - return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc }; + return { kind: "libCall", fn, args: [receiver, data], type: resultT, loc }; } if (data.type.kind === "bytes" && data.type.elem === "u8") { const fn: IrLibFn = name === "write" ? "net.sockWriteBytes" : "net.sockEndBytes"; - return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc }; + return { kind: "libCall", fn, args: [receiver, data], type: resultT, loc }; } if (data.type.kind === "dyn") { // An untyped JS payload (a helper's parameter): the runtime // dispatches STR/BYTES and throws Node's chunk TypeError otherwise. const fn: IrLibFn = name === "write" ? "net.sockWriteDyn" : "net.sockEndDyn"; - return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc }; + return { kind: "libCall", fn, args: [receiver, data], type: resultT, loc }; } lowerer.noLowering( `${name} of '${lowerer.fmt(data.type)}' data`, @@ -2944,6 +2962,7 @@ function lowerNetSocketMethodCall( event === "connect" || event === "ready" || event === "timeout" || + event === "drain" || event === "readable" ) { const { cb } = lowerCallbackArg( @@ -2962,11 +2981,13 @@ function lowerNetSocketMethodCall( ? "net.sockOnClose" : event === "timeout" ? "net.sockOnTimeout" - : event === "readable" - ? "net.sockOnReadable" - : event === "ready" - ? "net.sockOnReady" - : "net.sockOnConnect"; + : event === "drain" + ? "net.sockOnDrain" + : event === "readable" + ? "net.sockOnReadable" + : event === "ready" + ? "net.sockOnReady" + : "net.sockOnConnect"; return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc }; } if (event === "lookup") { @@ -3957,6 +3978,18 @@ export function lowerServerProperty( const receiver = coerceToHandle(lowerer, expr.expression, NETSOCKET_T); return { kind: "libCall", fn: "net.sockBytesWritten", args: [receiver], type: F64, loc }; } + if ( + recvKind === "netSocket" && + lowerer.isStdlibMember(expr) && + (expr.name.text === "writableLength" || expr.name.text === "writableNeedDrain") + ) { + // The write buffer's unsent bytes, and whether a write answered false + // and 'drain' has not fired yet. + const receiver = coerceToHandle(lowerer, expr.expression, NETSOCKET_T); + return expr.name.text === "writableLength" + ? { kind: "libCall", fn: "net.sockWritableLength", args: [receiver], type: F64, loc } + : { kind: "libCall", fn: "net.sockWritableNeedDrain", args: [receiver], type: BOOL, loc }; + } if (recvKind === "netSocket" && lowerer.isStdlibMember(expr) && expr.name.text === "readable") { // true until the read half is done (peer FIN / destroy). const receiver = coerceToHandle(lowerer, expr.expression, NETSOCKET_T); diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e768f257f..7183b72a1 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1543,15 +1543,18 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * true } and a callback taking an address string or list, then dials * the answers in order. MAY THROW (host validation, a lookup throw). */ "net.connectLookupDyn": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, - "net.sockWrite": { argTypes: [NETSOCKET_T, STRING], result: VOID }, - "net.sockWriteBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, + /** socket.write: Node's Writable answer — false when the bytes the write + * left unsent reach the 65536-byte high-water mark ('drain' follows), + * and on an ended or closed socket. */ + "net.sockWrite": { argTypes: [NETSOCKET_T, STRING], result: BOOL }, + "net.sockWriteBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: BOOL }, "net.sockEnd": { argTypes: [NETSOCKET_T], result: VOID }, "net.sockEndStr": { argTypes: [NETSOCKET_T, STRING], result: VOID }, "net.sockEndBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, /** write/end with a CHECKED-DYNAMIC chunk (an untyped JS payload into a * typed socket): the runtime dispatches STR/BYTES and throws Node's * ERR_INVALID_ARG_TYPE chunk TypeError on any other kind. */ - "net.sockWriteDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, + "net.sockWriteDyn": { argTypes: [NETSOCKET_T, DYN], result: BOOL }, "net.sockEndDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, "net.sockDestroy": { argTypes: [NETSOCKET_T], result: VOID }, "net.sockPipe": { argTypes: [NETSOCKET_T, NETSOCKET_T], result: VOID }, @@ -2009,6 +2012,12 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], result: VOID, }, + /** on('drain') / once('drain'): fires after a write answered false, once + * the write buffer empties. */ + "net.sockOnDrain": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, "net.sockRemoteAddress": { argTypes: [NETSOCKET_T], result: VOID }, /** socket.encrypted — `boolean | undefined`: the true arm iff the * socket carries a TLS transport (Node types `encrypted: true` on @@ -2135,6 +2144,11 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** socket.address(): { address, family, port } or {} as a dyn. */ "net.sockAddress": { argTypes: [NETSOCKET_T], result: DYN }, "net.sockBytesWritten": { argTypes: [NETSOCKET_T], result: F64 }, + /** socket.writableLength: the bytes written and not yet sent. */ + "net.sockWritableLength": { argTypes: [NETSOCKET_T], result: F64 }, + /** socket.writableNeedDrain: a write answered false and 'drain' has not + * fired, with the write half open. */ + "net.sockWritableNeedDrain": { argTypes: [NETSOCKET_T], result: BOOL }, /** remotePort (0), remoteFamily (1), localAddress (2), localPort (3), * localFamily (4): dyn values, undefined when unknown. */ "net.sockAddrField": { argTypes: [NETSOCKET_T, F64], result: DYN }, diff --git a/packages/compiler/test/native-codegen-integration.test.ts b/packages/compiler/test/native-codegen-integration.test.ts index 8e8439749..dc3f94e6c 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_v43"); + expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v44"); 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_v43"); + expect(undefinedSymbols).toContain("_scr_runtime_abi_v44"); }); test("helper and clang object paths retain the same outbound FFI C ABI", async () => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7515-net-socket-backpressure.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7515-net-socket-backpressure.ts.json new file mode 100644 index 000000000..16f6edb8d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7515-net-socket-backpressure.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7515-net-socket-backpressure.ts" + ], + "diags": [] +} diff --git a/packages/runtime-darwin-arm64/scripts/build.mjs b/packages/runtime-darwin-arm64/scripts/build.mjs index 84c3b83b6..7858c9782 100644 --- a/packages/runtime-darwin-arm64/scripts/build.mjs +++ b/packages/runtime-darwin-arm64/scripts/build.mjs @@ -366,7 +366,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: RUNTIME_PACK_MATRIX.target, - runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs index 545c6651b..b770d81b6 100644 --- a/packages/runtime-pack-common/scripts/build.mjs +++ b/packages/runtime-pack-common/scripts/build.mjs @@ -469,7 +469,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: matrix.target, - runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c index 2f7c91b7d..ad49e4f6f 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_v43(void) {} +void scr_runtime_abi_v44(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 5d6d80778..8e50ec0ca 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_v43(void); +void scr_runtime_abi_v44(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_net.c b/packages/runtime/src/scr_net.c index bd1233978..91e8461b7 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -565,6 +565,11 @@ struct ScrNetSocket { ScrNetLs lookup_ls; struct ScrNetLookupEv *lookup_evs; ScrNetLs data_ls, end_ls, close_ls, err_ls, conn_ls, timeout_ls, readable_ls; + /* Writable backpressure: a user write answered false (the bytes it left + * unsent reached SCR_NET_WRITE_HWM), so 'drain' fires once the write + * buffer empties */ + ScrNetLs drain_ls; + bool need_drain; /* Flow control (pause()/resume()): user_paused holds reads OFF even * with consumers (kernel/TCP backpressure is the buffer); flowing * counts as a consumer even with no 'data' listener @@ -747,6 +752,7 @@ void scr_net_sock_release(ScrNetSocket *s) { scr_net_ls_drop(&s->conn_ls); scr_net_ls_drop(&s->timeout_ls); scr_net_ls_drop(&s->readable_ls); + scr_net_ls_drop(&s->drain_ls); scr_net_ls_drop(&s->finish_ls); scr_net_ls_drop(&s->wcb_ls); scr_net_ls_drop(&s->ready_ls); @@ -1103,6 +1109,40 @@ static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len) { s->wlen += len; } +/* Node's net.Socket writableHighWaterMark (the Writable default, + * getDefaultHighWaterMark()). */ +#define SCR_NET_WRITE_HWM 65536 + +/* 'drain' once a false write's bytes are all out: Node's afterWrite emits + * it when needDrain is set and nothing is pending, unless the stream is + * ending or destroyed. */ +static void scr_net_sock_maybe_drain(ScrNetSocket *s) { + if (!s->need_drain || s->wlen > s->whead) return; + if (s->wr_ending || s->close_emitted || s->fd < 0) return; + s->need_drain = false; + scr_net_fire0_this(&s->drain_ls, s, SCR_DYNH_NET_SOCKET); +} + +static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len); + +/* A user write: Writable.write's answer, as Node 24's writeOrBuffer + * computes it after the socket writes — the bytes the write path left + * unsent below the high-water mark — with needDrain set when it is false. + * A write the kernel takes at once answers true whatever its size; the + * buffered bytes of a false one drain from the flush. A write to an ended + * or closed socket answers false. */ +static bool scr_net_sock_user_write(ScrNetSocket *s, const char *data, size_t len) { + ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; + bool pre_dial = w->fd < 0 && (w->dial_deferred || w->lookup_wait) && !w->wr_ending && + !w->close_emitted; + bool live = pre_dial || !(w->fd < 0 || w->wr_ending || w->close_emitted); + scr_net_sock_write_raw(s, data, len); + if (!live) return false; + bool ok = w->wlen - w->whead < SCR_NET_WRITE_HWM; + if (!ok) w->need_drain = true; + return ok; +} + /* The write path shared by write()/end(data)/pipe delivery. Ignored on a * dead or ended write half (Node fires 'error' there; this slice drops — * SEMANTICS.md documents the bound). */ @@ -1252,6 +1292,9 @@ static void scr_net_sock_flush(ScrNetSocket *s) { s->whead = s->wlen = 0; scr_net_sock_update_write(s); scr_net_sock_maybe_finish_write(s); + /* Node's afterWrite for a write the socket completed later: 'drain' + * now, unless the write half is ending or the socket is gone */ + scr_net_sock_maybe_drain(s); } } @@ -3150,16 +3193,40 @@ void scr_net_lookup_answer(ScrClosure *self, bool has_err, ScrStr *msg /*borrowe /* ── the socket surface ──────────────────────────────────────────────── */ -void scr_net_sock_write_str(ScrNetSocket *s, ScrStr *data /*borrowed*/) { +bool scr_net_sock_write_str(ScrNetSocket *s, ScrStr *data /*borrowed*/) { char *owned; const char *bytes = scr_str_utf8(data, &owned); - scr_net_sock_write_raw(s, bytes, data->len); + bool ok = scr_net_sock_user_write(s, bytes, data->len); scr_str_utf8_end(owned); + return ok; } -void scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) { +bool scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) { SCR_BYTES_SNAPSHOT(data); - scr_net_sock_write_raw(s, (const char *)data->data, (data->len * scr_bytes_elem_size(data->elem))); + return scr_net_sock_user_write(s, (const char *)data->data, + (data->len * scr_bytes_elem_size(data->elem))); +} + +/* socket.writableLength: the bytes written and not yet sent. */ +double scr_net_sock_writable_length(ScrNetSocket *s) { + ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; + return (double)(w->wlen - w->whead); +} + +/* socket.writableNeedDrain: a write answered false and 'drain' has not + * fired, on a socket whose write half is open. */ +bool scr_net_sock_writable_need_drain(ScrNetSocket *s) { + ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; + return w->need_drain && !w->wr_ending && !w->close_emitted && w->fd >= 0; +} + +/* on('drain') / once('drain'). */ +void scr_net_sock_on_drain(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) { + if (s->close_emitted) { + scr_closure_release(cb); + return; + } + scr_net_ls_add(&s->drain_ls, cb, NULL, once); } void scr_net_sock_end(ScrNetSocket *s) { @@ -5860,6 +5927,7 @@ static void scr_net_cleanup_atexit(void) { scr_net_ls_drop(&s->conn_ls); scr_net_ls_drop(&s->timeout_ls); scr_net_ls_drop(&s->readable_ls); + scr_net_ls_drop(&s->drain_ls); scr_net_ls_drop(&s->lookup_ls); /* Tear down the protocol layer's parser NOW: an in-flight http * exchange (a handler that threw before res.end()) holds req/res, @@ -5984,8 +6052,7 @@ static ScrDyn *scr_net_dynh_sock_invoke(void *h, ScrDyn *self, const char *metho /* fires once when the FIN goes out — once either way */ scr_net_sock_on_finish(s, scr_dyn_listener_closure0(cb)); } else if (scr_net_dynh_name_is(name, "drain")) { - /* This surface never backpressures (write answers true) — an - * accepted, never-fired registration is the consistent answer. */ + scr_net_sock_on_drain(s, scr_dyn_listener_closure0(cb), once); } else if (scr_net_dynh_tls != NULL && name->kind == SCR_DYN_STR && scr_net_dynh_tls->on(s, name->v.str->data, cb, once)) { /* a TLS event name ('secureConnect'), handled by the TLS unit */ @@ -6008,13 +6075,13 @@ static ScrDyn *scr_net_dynh_sock_invoke(void *h, ScrDyn *self, const char *metho scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", chunk); return NULL; } - if (chunk->kind == SCR_DYN_STR) scr_net_sock_write_str(s, chunk->v.str); - else scr_net_sock_write_bytes(s, chunk->v.bytes); + bool ok = chunk->kind == SCR_DYN_STR ? scr_net_sock_write_str(s, chunk->v.str) + : scr_net_sock_write_bytes(s, chunk->v.bytes); if (argc > 1 && args[1]->kind == SCR_DYN_FUNC) { /* write(chunk, cb): fires when the buffer drains (sweep-deferred) */ scr_net_sock_on_write_flush(s, scr_dyn_listener_closure0(args[1])); } - return scr_dyn_new_bool(true); /* backpressure is not modeled (SEMANTICS.md) */ + return scr_dyn_new_bool(ok); } if (strcmp(method, "end") == 0) { const ScrDyn *chunk = argc > 0 ? args[0] : scr_dyn_undefined(); @@ -6500,10 +6567,11 @@ void scr_net_dyn_install(void) { * STR/BYTES dispatch onto the typed entries; undefined/null end()s plain * (Node accepts them); anything else throws Node's ERR_INVALID_ARG_TYPE * chunk TypeError. Both borrowed. */ -void scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d) { - if (d->kind == SCR_DYN_STR) scr_net_sock_write_str(s, d->v.str); - else if (d->kind == SCR_DYN_BYTES) scr_net_sock_write_bytes(s, d->v.bytes); - else scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", d); +bool scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d) { + if (d->kind == SCR_DYN_STR) return scr_net_sock_write_str(s, d->v.str); + if (d->kind == SCR_DYN_BYTES) return scr_net_sock_write_bytes(s, d->v.bytes); + scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", d); + return false; } void scr_net_sock_end_dynv(ScrNetSocket *s, const ScrDyn *d) { diff --git a/packages/runtime/src/scr_net.h b/packages/runtime/src/scr_net.h index 95490aaa5..6a41e26d8 100644 --- a/packages/runtime/src/scr_net.h +++ b/packages/runtime/src/scr_net.h @@ -227,15 +227,21 @@ ScrNetSocket *scr_net_connect_lookup(double port, ScrStr *host /*borrowed*/, ScrClosure *lookup /*moves*/, void *answer_fn); /* +1 */ void scr_net_lookup_answer(ScrClosure *self, bool has_err, ScrStr *msg /*borrowed, nullable*/, ScrArr *ips /*borrowed, nullable*/); -void scr_net_sock_write_str(ScrNetSocket *s, ScrStr *data /*borrowed*/); -void scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/); +/* write(): Node's Writable answer — false when the bytes the write left + * unsent reach the 65536-byte high-water mark, after which 'drain' fires + * when the write buffer empties; false on an ended or closed socket. */ +bool scr_net_sock_write_str(ScrNetSocket *s, ScrStr *data /*borrowed*/); +bool scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/); +double scr_net_sock_writable_length(ScrNetSocket *s); +bool scr_net_sock_writable_need_drain(ScrNetSocket *s); +void scr_net_sock_on_drain(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once); void scr_net_sock_end(ScrNetSocket *s); void scr_net_sock_end_str(ScrNetSocket *s, ScrStr *data /*borrowed*/); void scr_net_sock_end_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/); /* Checked-dynamic chunk forms (an untyped JS payload into a typed * socket): STR/BYTES dispatch; end() takes undefined/null as no chunk; * anything else throws Node's ERR_INVALID_ARG_TYPE chunk TypeError. */ -void scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d /*borrowed*/); +bool scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d /*borrowed*/); void scr_net_sock_end_dynv(ScrNetSocket *s, const ScrDyn *d /*borrowed*/); void scr_net_sock_destroy(ScrNetSocket *s); /* Flow control (pause/resume — see the struct's flag comments), the diff --git a/tests/corpus/7515-net-socket-backpressure.ts b/tests/corpus/7515-net-socket-backpressure.ts new file mode 100644 index 000000000..a265d3f77 --- /dev/null +++ b/tests/corpus/7515-net-socket-backpressure.ts @@ -0,0 +1,47 @@ +// net.Socket write() backpressure, as in Node 24: write answers false when +// the bytes it leaves unsent reach the 65536-byte high-water mark, which a +// write made before the connection does, writableNeedDrain stays true +// until 'drain', and 'drain' fires once the buffered bytes are sent. An +// echo client that waits for 'drain' when write answers false moves every +// byte. +import { connect, createServer, Socket } from "node:net"; + +const CHUNK = 65536; +const TOTAL = 64 * CHUNK; +const chunk = Buffer.alloc(CHUNK, 120); + +const server = createServer((socket: Socket) => { + socket.pipe(socket); +}); + +server.listen(0, "127.0.0.1", () => { + const addr = server.address(); + const port = typeof addr === "object" && addr !== null ? addr.port : 0; + const client = connect(port, "127.0.0.1"); + console.log("small write before connect", client.write("hi"), client.writableLength); + console.log("large write before connect", client.write(Buffer.alloc(70000, 121)), client.writableLength); + console.log("need drain", client.writableNeedDrain); + let sent = 70002; + let received = 0; + const pump = (): void => { + while (sent < TOTAL) { + sent += CHUNK; + if (!client.write(chunk)) { + client.once("drain", pump); + return; + } + } + client.end(); + }; + client.once("drain", () => { + console.log("drain fired, need drain", client.writableNeedDrain, "pending", client.writableLength); + pump(); + }); + client.on("data", (data: Buffer) => { + received += data.length; + }); + client.on("end", () => { + console.log("received", received, "of", sent); + server.close(); + }); +}); diff --git a/tests/harness/corpus-costs/7515-net-socket-backpressure.ts.json b/tests/harness/corpus-costs/7515-net-socket-backpressure.ts.json new file mode 100644 index 000000000..feabbe802 --- /dev/null +++ b/tests/harness/corpus-costs/7515-net-socket-backpressure.ts.json @@ -0,0 +1 @@ +{ "plain": 2800, "san": 4200 } diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index 8470ce04e..f8d901ee8 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -110,7 +110,7 @@ for (const backend of ["llvm"] as const) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 43, marker: "scr_runtime_abi_v43" }, + runtime_abi: { version: 44, marker: "scr_runtime_abi_v44" }, compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple }, macros: { executable: [], excluded: [], sanitizer: "external-toolchain-required" }, flavors: { From 45171ea02cb01c5d8b2683a7195623464d8d8bcc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 03:59:10 -0500 Subject: [PATCH 043/230] Use a built-in typed-array constructor as its own species before the species symbol exists A typed array whose constructor property is set to another built-in typed-array constructor now creates that kind from map, filter, slice and subarray when the program has not evaluated Symbol.species. --- packages/runtime/src/scr_json.c | 7 ++++-- .../8321-typed-array-constructor-kind.js | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/corpus/8321-typed-array-constructor-kind.js diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index f06080ef6..6e0ca43c0 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -5345,9 +5345,12 @@ static ScrDyn *scr_bytes_species(ScrDyn *recv, bool *failed) { *failed = true; return NULL; } - ScrDyn *species = scr_species_symbol ? scr_dyn_symbol_key_get(constructor, scr_species_symbol, true) : NULL; + /* Before the program creates the species symbol, no constructor can have + * a Symbol.species property of its own. */ + ScrDyn *species = scr_species_symbol ? scr_dyn_symbol_key_get(constructor, scr_species_symbol, true) + : scr_dyn_retain(scr_dyn_undefined()); if (species && species->kind == SCR_DYN_UNDEF && scr_bytes_ctor_elem(constructor) >= 0 && - !scr_bytes_ctor_own_species(constructor)) { + (!scr_species_symbol || !scr_bytes_ctor_own_species(constructor))) { /* A built-in typed-array constructor inherits %TypedArray%'s species * getter, which returns the constructor itself. */ scr_dyn_release(species); diff --git a/tests/corpus/8321-typed-array-constructor-kind.js b/tests/corpus/8321-typed-array-constructor-kind.js new file mode 100644 index 000000000..277128a93 --- /dev/null +++ b/tests/corpus/8321-typed-array-constructor-kind.js @@ -0,0 +1,23 @@ +// A built-in typed-array constructor set as a view's `constructor` is the +// species of map, filter, slice and subarray when the program never names +// the species symbol. +function kinds(TA, Other) { + const a = new TA([1, 2, 3, 4]); + a.constructor = Other; + const m = a.map((v) => v * 1.5), f = a.filter((v) => v > 1), s = a.slice(1, 3); + console.log(m, f, s, m.constructor === Other, f.constructor === Other, s.constructor === Other); + try { + const sub = a.subarray(0, 2); + console.log(sub, sub.constructor === Other); + } catch (err) { + console.log(err.constructor.name); + } + a.constructor = TA; + console.log(a.map((v) => v / 2).constructor === TA); + a.constructor = {}; + console.log(a.slice().constructor === TA); +} +kinds(Uint8Array, Float64Array); +kinds(Int16Array, Uint8Array); +kinds(Float32Array, Int32Array); +kinds(Float64Array, Uint8ClampedArray); From 0d4df6ff6202ae01094be725890c38c3493ee8d6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 04:14:58 -0500 Subject: [PATCH 044/230] Skip fork discovery in programs that never name node:child_process forkTargetPaths asked the checker for the symbol of every identifier callee in every implementation file, on each root-discovery pass and twice more in preflight, to find calls of child_process.fork. A call is a fork only through a binding whose import or alias chain names the module in a module specifier, or through a '#' specifier a package.json "imports" field maps. namesChildProcess reads every program file once per program, outside Node's type declarations and scriptc's own declaration files, which declare the module and alias it only as node:child_process. Implementation files are read from their syntax trees, which discovery fetches anyway; other files as the host serves them to the server (the new Ts7Program.getSourceText), without fetching their syntax trees. When no file holds "child_process" in its text, an escape or line continuation that spells it, or an "imports" entry that names it, forkTargetPaths answers no targets without querying the checker. The discovered fork targets are unchanged. --- .../compiler/src/frontend/fork-target.test.ts | 53 +++++++++++++ packages/compiler/src/frontend/fork-target.ts | 77 ++++++++++++++++++- .../compiler/src/frontend/program-analysis.ts | 3 + packages/compiler/src/frontend/resolve.ts | 6 ++ .../compiler/src/frontend/ts7/program-host.ts | 52 ++++++++----- 5 files changed, 172 insertions(+), 19 deletions(-) diff --git a/packages/compiler/src/frontend/fork-target.test.ts b/packages/compiler/src/frontend/fork-target.test.ts index 5e56cea5d..ffe01ee47 100644 --- a/packages/compiler/src/frontend/fork-target.test.ts +++ b/packages/compiler/src/frontend/fork-target.test.ts @@ -65,3 +65,56 @@ test("fork discovery batches only candidate bindings and preserves aliases and s rmSync(directory, { recursive: true, force: true }); } }); + +/** forkTargetPaths over the entry of a program made of `files`, and + * whether it asked the checker for any binding. */ +function discover(files: Record): { targets: string[]; queried: boolean } { + const directory = mkdtempSync(join(tmpdir(), "scriptc-fork-gate-")); + for (const [name, text] of Object.entries(files)) writeFileSync(join(directory, name), text); + const entry = join(directory, "main.ts"); + const host = new Ts7Host((options) => new Ts7Api(options), { cwd: directory }); + const program = host.createProgram([entry, fallbackDtsPath()], { + noLib: true, + types: [], + module: ModuleKind.ESNext, + moduleResolution: ModuleResolutionKind.Bundler, + }); + const batch = vi.spyOn(program.getTypeChecker(), "prefetchSymbolNodesExact"); + try { + const targets = forkTargetPaths(program, [program.getSourceFile(entry)!]).map((path) => + path.slice(directory.length + 1), + ); + return { targets, queried: batch.mock.calls.length > 0 }; + } finally { + batch.mockRestore(); + program.dispose(); + host.close(); + rmSync(directory, { recursive: true, force: true }); + } +} + +test("fork discovery asks the checker nothing when no file names node:child_process", () => { + expect( + discover({ + "main.ts": + 'function fork(url: URL) { return url; }\nfork(new URL("./child.ts", import.meta.url));\n', + }), + ).toEqual({ targets: [], queried: false }); +}); + +test("fork discovery reads a module specifier spelled with escapes", () => { + expect( + discover({ + "main.ts": + 'import { fork } from "node:child\\u005fprocess";\nfork(new URL("./child.ts", import.meta.url));\n', + }), + ).toEqual({ targets: ["child.ts"], queried: true }); +}); + +test("fork discovery reads a package imports entry that maps node:child_process", () => { + const result = discover({ + "package.json": JSON.stringify({ type: "module", imports: { "#cp": "node:child_process" } }), + "main.ts": 'import { fork } from "#cp";\nfork(new URL("./child.ts", import.meta.url));\n', + }); + expect(result.queried).toBe(true); +}); diff --git a/packages/compiler/src/frontend/fork-target.ts b/packages/compiler/src/frontend/fork-target.ts index 38c8f5f07..93fc7c244 100644 --- a/packages/compiler/src/frontend/fork-target.ts +++ b/packages/compiler/src/frontend/fork-target.ts @@ -1,7 +1,14 @@ import { resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import * as ts from "./ts7/adapter.js"; -import { fallbackDtsPath, isNodeTypesPath, tsgoPath } from "./dts-paths.js"; +import { + ambientDtsPath, + fallbackDtsPath, + isNodeTypesPath, + overridesDtsPath, + tsgoPath, +} from "./dts-paths.js"; +import { nearestPackageImports } from "./resolve.js"; import { canonicalBuiltinModule } from "./builtin-modules.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; @@ -292,9 +299,77 @@ export function forkCallModulePath(program: ts.Program, call: ts.CallExpression) return staticForkModulePath(program, call.arguments.nodes[0]); } +/** The ways a string or template literal can spell one character of a + * module name: the character, \xHH, \uHHHH, \u{H…}, a legacy octal escape, + * or the character after a backslash where that is not another escape. */ +function spelledCharacter(ch: string): string { + const code = ch.charCodeAt(0); + const hex = code.toString(16); + const forms = [ + ch, + "\\\\x" + hex.padStart(2, "0"), + "\\\\u" + hex.padStart(4, "0"), + "\\\\u\\{0*" + hex + "\\}", + "\\\\" + code.toString(8), + ]; + if (!"bfnrtvux0123456789".includes(ch)) forms.push("\\\\" + ch); + return "(?:" + forms.join("|") + ")"; +} + +/** "child_process" with escapes and line continuations in any of its + * characters (case-insensitive, a superset). */ +const SPELLED_CHILD_PROCESS = new RegExp( + [..."child_process"].map(spelledCharacter).join("(?:\\\\(?:\\r\\n|[\\n\\r\\u2028\\u2029]))*"), + "i", +); + +/** Whether a call in the program can reach node:child_process's fork. A + * fork binding is an import or alias whose chain names the module in a + * module specifier (a string), or a '#' specifier the nearest package.json + * "imports" field maps. Node's type declarations and scriptc's own declare + * the module, and alias it only as node:child_process, so they are not + * read; any other file that names the module in a string holds + * "child_process" in its text, or an escape that spells it. */ +function namesChildProcess(program: ts.Program): boolean { + const known = program.analysis.namesChildProcess; + if (known !== null) return known; + const own = new Set([ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]); + // Implementation files are already fetched; any other file is read as + // the host serves it, without fetching its syntax tree. + const texts = new Map(); + for (const sourceFile of program.getImplementationSourceFiles()) + texts.set(tsgoPath(sourceFile.fileName), sourceFile.text); + const scopes = new Set(); + let names = false; + for (const fileName of program.getSourceFileNames()) { + const path = tsgoPath(fileName); + if (isNodeTypesPath(fileName) || own.has(path)) continue; + const text = texts.get(path) ?? program.getSourceText(fileName) ?? ""; + if ( + text.includes("child_process") || + (text.includes("\\") && SPELLED_CHILD_PROCESS.test(text)) + ) { + names = true; + break; + } + const imports = nearestPackageImports(fileName); + if (imports === undefined || scopes.has(imports)) continue; + scopes.add(imports); + if (JSON.stringify(imports).includes("child_process")) { + names = true; + break; + } + } + program.analysis.namesChildProcess = names; + return names; +} + /** Source-order static fork targets in the supplied files, deduplicated by * resolved path. */ export function forkTargetPaths(program: ts.Program, files: readonly ts.SourceFile[]): string[] { + // Without a binding of node:child_process no call is a fork, and the + // checker queries below are skipped. + if (!namesChildProcess(program)) return []; const targets: string[] = []; const seen = new Set(); for (const sourceFile of files) { diff --git a/packages/compiler/src/frontend/program-analysis.ts b/packages/compiler/src/frontend/program-analysis.ts index d4ec9fa98..475942908 100644 --- a/packages/compiler/src/frontend/program-analysis.ts +++ b/packages/compiler/src/frontend/program-analysis.ts @@ -25,6 +25,8 @@ export class ProgramAnalysis { /** The modules of every import cycle the exact initialization rule admitted. */ readonly exactCycleMembers = new Set(); cycleEarlyBindings: EarlyBindingsMemo | null = null; + /** Whether some program file can name node:child_process (namesChildProcess). */ + namesChildProcess: boolean | null = null; moduleEarlyBindings: EarlyBindingsMemo | null = null; /** Top-level require declarations: the first reference to their bindings * that code running before the require can reach (requireEarlyReference). */ @@ -46,6 +48,7 @@ export class ProgramAnalysis { this.moduleSourceCandidates.clear(); this.exactCycleMembers.clear(); this.cycleEarlyBindings = null; + this.namesChildProcess = null; this.moduleEarlyBindings = null; this.requireEarlyReferences.clear(); this.requireFirstRunnable.clear(); diff --git a/packages/compiler/src/frontend/resolve.ts b/packages/compiler/src/frontend/resolve.ts index 3b32dad3a..5b035b6cb 100644 --- a/packages/compiler/src/frontend/resolve.ts +++ b/packages/compiler/src/frontend/resolve.ts @@ -562,6 +562,12 @@ export function nearestPkgJsonPath(fromFile: string): string | null { return dir === null ? null : join(dir, "package.json"); } +/** The nearest package scope's "imports" field (undefined without one). */ +export function nearestPackageImports(fromFile: string): unknown { + const dir = nearestPkgDir(dirname(resolve(fromFile))); + return dir === null ? undefined : pkgJsonOf(dir)?.imports; +} + /** The target the nearest package scope's "imports" field maps a '#' * specifier to under Node's ESM runtime conditions: a './' path inside the * package or another package's specifier. Null when no entry maps it. */ diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index e66d598d9..84cbe2474 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -152,33 +152,37 @@ export class Ts7Host { private readonly virtualChanges = new Map(); private readonly closingProjects = new Set(); private readonly api: Ts7Api; + /** The text the server reads for a file (its readFile hook). */ + private readonly readSource: (fileName: string) => string | null; private closed = false; constructor(createApi: Ts7ApiFactory, options?: Ts7HostOptions) { const virtualFiles = this.virtualFiles; const shadow = options?.fsShadow ?? null; + // A virtual file, a shadow's replacement, a package shim's declaration + // entry or the file on disk; null when shadowed out of existence or + // missing. + this.readSource = (fileName) => { + const virtual = virtualFiles.get(tsgoPath(fileName)); + if (virtual !== undefined) return stripSourceBom(virtual); + if (shadow !== null) { + if (shadow.hideFile(fileName)) return null; + const replacement = shadow.readFile(fileName); + if (replacement !== undefined) return stripSourceBom(replacement); + } + // A package scriptc implements itself (package-shims.ts): its + // installed declaration entry re-exports the shim module, in every + // program the host creates. + const shim = packageShimDeclarationText(fileName); + if (shim !== undefined) return shim; + const source = trackedReadFile(fileName); + return source === null ? null : stripSourceBom(source); + }; this.api = createApi({ cwd: options?.cwd ?? process.cwd(), collectTiming: options?.collectTiming ?? false, fs: { - // string => virtual (or shadowed) hit; null => shadowed out of - // existence; undefined => real-FS fallthrough. - readFile: (fileName) => { - const virtual = virtualFiles.get(tsgoPath(fileName)); - if (virtual !== undefined) return stripSourceBom(virtual); - if (shadow !== null) { - if (shadow.hideFile(fileName)) return null; - const replacement = shadow.readFile(fileName); - if (replacement !== undefined) return stripSourceBom(replacement); - } - // A package scriptc implements itself (package-shims.ts): its - // installed declaration entry re-exports the shim module, in every - // program the host creates. - const shim = packageShimDeclarationText(fileName); - if (shim !== undefined) return shim; - const source = trackedReadFile(fileName); - return source === null ? null : stripSourceBom(source); - }, + readFile: this.readSource, fileExists: (fileName) => { if (virtualFiles.has(tsgoPath(fileName))) return true; if (shadow !== null && shadow.hideFile(fileName)) return false; @@ -193,6 +197,12 @@ export class Ts7Host { }); } + /** The text this host serves for a file (null when it serves none), + * without asking the server for its syntax tree. */ + sourceText(fileName: string): string | null { + return this.readSource(fileName); + } + /** Registers an in-memory file served to tsgo by the virtual-FS hooks. */ addVirtualFile(path: string, content: string): void { this.ensureOpen(); @@ -315,6 +325,12 @@ export class Ts7Program { return this.project.program.getSourceFile(resolve(fileName)); } + /** A file's text as the host serves it, without fetching its syntax + * tree (Ts7Host.sourceText). */ + getSourceText(fileName: string): string | null { + return this.host.sourceText(fileName); + } + getSourceFileNames(): readonly string[] { this.snapshot.ensureActive(); return (this.sourceFileNamesCache ??= this.project.program.getSourceFileNames()); From 080fe5e5e0f3e2660c58a8dd6b64a3ede63827d6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 04:16:32 -0500 Subject: [PATCH 045/230] Record the TypeScript order baseline for the typed-array constructor kind fixture --- .../tests/corpus/8321-typed-array-constructor-kind.js.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8321-typed-array-constructor-kind.js.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8321-typed-array-constructor-kind.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8321-typed-array-constructor-kind.js.json new file mode 100644 index 000000000..6da151987 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8321-typed-array-constructor-kind.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8321-typed-array-constructor-kind.js" + ], + "diags": [] +} From 534c08dd2f30fa2ce5611fdbfcce20598a8f8f77 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 04:34:24 -0500 Subject: [PATCH 046/230] Pack spread tuple fields together with other spreads into optional-element rests A call that spread a fixed tuple and also an array, a Set or another iterable into a `(T | undefined)[]` rest parameter took the general rest path, which converts every value to T, so an undefined tuple field threw ("undefined is not representable in the target union"). Such calls now pack every item in argument order through lowerArraySpreadItems, which takes values already lowered (the tuple's fields) beside argument nodes and keeps undefined values as present undefined elements. A tuple element typed only `undefined`, as in `...["b", undefined]`, has a `null | undefined` field. Stored into an array that keeps undefined elements it is now evaluated and stored as undefined, where it was an internal compiler error; the checker admits it there only as undefined. --- .../src/frontend/lowering/call-arguments.ts | 16 ++++-- .../lowering/containers/array-construction.ts | 53 +++++++++++++++++-- .../8004-optional-element-conversions.ts | 9 ++++ 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index a139bfc64..79e891d4f 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -407,10 +407,18 @@ export function completeArgs( // A `(T | undefined)[]` rest (stored as T elements) packs undefined // arguments as present undefined elements. const restNodes = sources.slice(restAt); - if (restType.optionalElements === true && !restNodes.some(isIr)) { - out.push( - lowerArraySpreadItems(lowerer, restNodes as ts.Expression[], restType.elem, restType, loc), - ); + // Plain arguments and spreads, also together with spread tuple fields + // (pre-lowered): each item, in argument order. + if ( + restType.optionalElements === true && + (!restNodes.some(isIr) || restNodes.some((a) => !isIr(a) && ts.isSpreadElement(a))) + ) { + const items: (ts.Expression | IrExpr)[] = []; + for (const a of restNodes) { + if (isIr(a)) items.push(a.ir); + else items.push(a); + } + out.push(lowerArraySpreadItems(lowerer, items, restType.elem, restType, loc)); return out; } // Tuple-spread values (pre-lowered) and plain arguments pack the same diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 25a98c8a6..63e2e6e07 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -4,6 +4,7 @@ import { generatorDrain } from "../iterator-adapters.js"; import { identityPreservingWidening } from "../coercions/identity.js"; import { liftedFreshSpread } from "../coercions/fresh-spread.js"; import { checkedIterableSpread } from "../checked-iterable-spread.js"; +import { AstNode } from "../../ts7/ast-node.js"; import { InternalCompilerError } from "../../../errors.js"; import { dynUndefinedExpr, @@ -27,7 +28,9 @@ import { type IrType, STRING, type SrcLoc, + UNDEFINED_T, arrayOf, + isUnitType, canConvertToDyn, funcOf, isRefCounted, @@ -57,6 +60,34 @@ import { } from "./callback-arguments.js"; import { classGeneratorIterable } from "../class-iterables.js"; +/** A value whose type admits only null and undefined, stored into an array + * that keeps undefined elements: the checker admits it there only when its + * type is `undefined` (a tuple element typed `undefined` maps to such a + * union), so it is evaluated and stored as undefined. */ +function undefinedForNullish( + lowerer: Lowerer, + value: IrExpr, + arrType: IrType & { kind: "array" }, +): IrExpr { + if (arrType.optionalElements !== true || value.type.kind !== "union") return value; + const arms = lowerer.unions.get(value.type.unionId)?.arms ?? []; + if (arms.length === 0 || !arms.every(isUnitType)) return value; + const loc = value.loc; + const optional: IrType = { + kind: "union", + unionId: lowerer.unions.intern([arrType.elem, UNDEFINED_T]), + }; + const missing = lowerer.wrappedUndefined(optional, loc); + if (!missing) return value; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: value, loc }], + result: missing, + type: optional, + loc, + }; +} + /** Build the argument array for a mutating/copying operation while keeping * the receiver's scalar payload ABI. A missing read is represented as the * present UNDEFINED state in this temporary, so push/unshift and toSpliced @@ -92,7 +123,8 @@ export function lowerArrayValueItems( loc, }, ]; - for (const value of values) { + for (const item of values) { + const value = undefinedForNullish(lowerer, item, arrType); const stored = lowerer.runtimeOptionalWidening(value.type, elem) !== null || optionalElementValue(lowerer, value, elem) @@ -103,16 +135,21 @@ export function lowerArrayValueItems( return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc }; } +/** The items of an array built from argument nodes (a spread copies its + * elements in) and values already lowered (a spread tuple's fields), in + * order. Each value is stored when its turn comes. */ export function lowerArraySpreadItems( lowerer: Lowerer, - nodes: readonly ts.Expression[], + nodes: readonly (ts.Expression | IrExpr)[], elem: IrType, arrType: IrType & { kind: "array" }, loc: SrcLoc, ): IrExpr { const out = lowerer.declareHiddenLocal("%arrayItems", arrType); const outRef = varRef(out.id, arrType, loc); - const plainItems = nodes.filter((node) => !ts.isSpreadElement(node)).length; + const plainItems = nodes.filter( + (node) => !(node instanceof AstNode) || !ts.isSpreadElement(node), + ).length; const length = (): IrExpr => ({ kind: "arrIntrinsic", method: "length", @@ -136,7 +173,15 @@ export function lowerArraySpreadItems( }, ]; for (const node of nodes) { - if (ts.isSpreadElement(node)) { + if (!(node instanceof AstNode)) { + const value = undefinedForNullish(lowerer, node, arrType); + const stored = + lowerer.runtimeOptionalWidening(value.type, elem) !== null || + optionalElementValue(lowerer, value, elem) + ? value + : lowerer.coerceToExpected(value, elem); + body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc)); + } else if (ts.isSpreadElement(node)) { let source = lowerer.lowerExpr(node.expression); if ( source.type.kind === "set" && diff --git a/tests/corpus/8004-optional-element-conversions.ts b/tests/corpus/8004-optional-element-conversions.ts index 07178e9b0..9a4ac55ac 100644 --- a/tests/corpus/8004-optional-element-conversions.ts +++ b/tests/corpus/8004-optional-element-conversions.ts @@ -49,6 +49,15 @@ function swap(pair: [string, string | undefined]): string { return "v"; } console.log(label("z", ...swapped, swap(swapped), ...swapped)); +// Tuple spreads together with array and Set spreads keep undefined +// elements, and every item is read in argument order. +const mixedPair: [boolean, boolean | undefined] = [true, undefined]; +const mixedMore: (boolean | undefined)[] = [false, undefined]; +console.log(flags(...mixedPair, ...mixedMore), flags(...mixedMore, ...mixedPair), flags(true, ...mixedPair, ...mixedMore, undefined)); +const mixedChanging: [boolean, boolean | undefined] = [true, undefined]; +console.log(flags(...mixedChanging, ...mixedMore, flip(mixedChanging, true), ...mixedChanging)); +const mixedWords: [string, string | undefined] = ["a", undefined]; +console.log(label("z", ...["b", undefined, "c"], ...mixedWords), label("y", ...mixedWords, ...new Set(["s", undefined]))); function pick(start: (number | undefined)[] | string[], other: (number | undefined)[] | string[]): string { let a = start; From 50c46bb9de2dfe5ee43604ba5bf0ced4f7bdb7b0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 04:50:20 -0500 Subject: [PATCH 047/230] Count socket writes as Node's state.length, and read write state from untyped code A socket's write() answer and writableLength counted the bytes left unsent. Node counts each write that is not completely sent in full, so a large write the kernel takes in part still counts its whole length. The socket now keeps its buffered writes in order with their lengths and completes each one when its last byte is sent; write() answers false when those reach the 65536-byte high-water mark. write() now checks whether the write half is open after the send, so a send that fails with EPIPE or ECONNRESET and closes the socket answers false. writableNeedDrain also reads true while a custom lookup or a deferred dial is pending, since writes then buffer until the socket connects. Untyped code reads writableLength and writableNeedDrain, and writableHighWaterMark and readableHighWaterMark read 65536, Node's default, instead of 16384. --- packages/runtime/src/scr_net.c | 103 +++++++++--- tests/corpus/7515-net-socket-backpressure.ts | 157 +++++++++++++++---- 2 files changed, 201 insertions(+), 59 deletions(-) diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index 91e8461b7..0fe06cedc 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -516,6 +516,16 @@ struct ScrNetSocket { /* write buffer: [whead, wlen) of wbuf is unsent */ char *wbuf; size_t whead, wlen, wcap; + /* Node's writable state.length: each write with bytes in the buffer + * counts in full until its last byte is sent (a partly sent write + * included). wq[wq_head, wq_n) holds those writes in order, each with + * the offset where its bytes end, counted from wbase, the offset of + * wbuf[0] in everything buffered since the buffer last emptied. */ + struct ScrNetPendingWrite { + size_t end, len; + } *wq; + size_t wq_head, wq_n, wq_cap; + size_t wbase, wpending; /* receive buffer: [rhead, rlen) of rbuf is arrived-but-unconsumed — * fed by paused-mode reads ('readable' consumers) and unshift; drained * FIRST by every consumer (data listeners, the parser attach, the TLS @@ -770,6 +780,7 @@ void scr_net_sock_release(ScrNetSocket *s) { if (s->lookup_evs != NULL) scr_net_lookup_ops->free(s); scr_net_tunnel_free(s->tunnel); free(s->wbuf); + free(s->wq); free(s->rbuf); if (s->pipe_dst) scr_net_sock_release(s->pipe_dst); if (s->conn_pending) scr_net_server_release(s->conn_pending); @@ -1088,13 +1099,34 @@ static void scr_net_sock_maybe_finish_write(ScrNetSocket *s) { else scr_net_sock_update_read(s); /* the drain-to-EOF mode arms here */ } +/* The write buffer emptied (or its bytes moved elsewhere): every pending + * write is complete. */ +static void scr_net_sock_wq_clear(ScrNetSocket *s) { + s->whead = s->wlen = 0; + s->wq_head = s->wq_n = 0; + s->wbase = 0; + s->wpending = 0; +} + +/* Completes the pending writes whose last byte the flush has sent. */ +static void scr_net_sock_wq_settle(ScrNetSocket *s) { + size_t sent_to = s->wbase + s->whead; + while (s->wq_head < s->wq_n && s->wq[s->wq_head].end <= sent_to) { + s->wpending -= s->wq[s->wq_head].len; + s->wq_head++; + } + if (s->wq_head == s->wq_n) s->wq_head = s->wq_n = 0; +} + /* Append bytes to the write buffer (post-immediate-write remainder, or - * everything while connecting/buffered). */ -static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len) { + * everything while connecting/buffered). `counted` is the length of the + * write they belong to, which counts in full until they are sent. */ +static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len, size_t counted) { if (len == 0) return; /* compact the consumed head first */ if (s->whead > 0) { memmove(s->wbuf, s->wbuf + s->whead, s->wlen - s->whead); + s->wbase += s->whead; s->wlen -= s->whead; s->whead = 0; } @@ -1107,6 +1139,15 @@ static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len) { } memcpy(s->wbuf + s->wlen, data, len); s->wlen += len; + if (s->wq_n == s->wq_cap) { + s->wq_cap = s->wq_cap ? s->wq_cap * 2 : 8; + s->wq = realloc(s->wq, s->wq_cap * sizeof *s->wq); + if (!s->wq) scr_net_oom(); + } + s->wq[s->wq_n].end = s->wbase + s->wlen; + s->wq[s->wq_n].len = counted; + s->wq_n++; + s->wpending += counted; } /* Node's net.Socket writableHighWaterMark (the Writable default, @@ -1125,20 +1166,27 @@ static void scr_net_sock_maybe_drain(ScrNetSocket *s) { static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len); +/* The write half takes writes: not ending or closed, and connected, + * connecting, or waiting to dial (the agent's deferred dial, the caller + * lookup), which buffers them until the connection is established. */ +static bool scr_net_sock_write_open(ScrNetSocket *w) { + if (w->wr_ending || w->close_emitted) return false; + return w->fd >= 0 || w->dial_deferred || w->lookup_wait; +} + /* A user write: Writable.write's answer, as Node 24's writeOrBuffer - * computes it after the socket writes — the bytes the write path left - * unsent below the high-water mark — with needDrain set when it is false. - * A write the kernel takes at once answers true whatever its size; the - * buffered bytes of a false one drain from the flush. A write to an ended - * or closed socket answers false. */ + * computes it after the socket writes. Each write with bytes still + * unsent counts in full (wpending, Node's state.length), so the answer is + * false when the pending writes reach the high-water mark, and needDrain + * is set. A write the kernel takes at once answers true whatever its + * size. A write to an ended or closed socket answers false, and so does + * one whose send fails and closes the socket. */ static bool scr_net_sock_user_write(ScrNetSocket *s, const char *data, size_t len) { ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; - bool pre_dial = w->fd < 0 && (w->dial_deferred || w->lookup_wait) && !w->wr_ending && - !w->close_emitted; - bool live = pre_dial || !(w->fd < 0 || w->wr_ending || w->close_emitted); + bool open = scr_net_sock_write_open(w); scr_net_sock_write_raw(s, data, len); - if (!live) return false; - bool ok = w->wlen - w->whead < SCR_NET_WRITE_HWM; + if (!open || !scr_net_sock_write_open(w)) return false; + bool ok = w->wpending < SCR_NET_WRITE_HWM; if (!ok) w->need_drain = true; return ok; } @@ -1176,7 +1224,7 @@ static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len (s->tops && !s->t_est)) { /* mid-connect and mid-handshake bytes buffer; the flush after * 'connect'/establishment sends them */ - scr_net_sock_buffer(s, data, len); + scr_net_sock_buffer(s, data, len, len); scr_net_sock_update_write(s); return; } @@ -1195,7 +1243,7 @@ static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len } } if ((size_t)sent < len) { - scr_net_sock_buffer(s, data + sent, len - (size_t)sent); + scr_net_sock_buffer(s, data + sent, len - (size_t)sent, len); scr_net_sock_update_write(s); } } @@ -1260,7 +1308,7 @@ void scr_net_sock_writev_native(ScrNetSocket *s, const ScrNetSlice *slices, size skip -= len; continue; } - scr_net_sock_buffer(s, (const char *)iov[i].iov_base + skip, len - skip); + scr_net_sock_buffer(s, (const char *)iov[i].iov_base + skip, len - skip, len); skip = 0; } scr_net_sock_update_write(s); @@ -1287,9 +1335,10 @@ static void scr_net_sock_flush(ScrNetSocket *s) { } if (sent > 0) scr_net_sock_touch(s); s->whead += (size_t)sent; + scr_net_sock_wq_settle(s); } if (s->wlen == s->whead) { - s->whead = s->wlen = 0; + scr_net_sock_wq_clear(s); scr_net_sock_update_write(s); scr_net_sock_maybe_finish_write(s); /* Node's afterWrite for a write the socket completed later: 'drain' @@ -2778,7 +2827,7 @@ ScrNetSocket *scr_net_connect_deferred(double port, ScrStr *host /*borrowed, nul * for a slot takes a pooled socket instead of dialing. */ void scr_net_sock_move_writes(ScrNetSocket *from, ScrNetSocket *to) { if (from->wlen > from->whead) scr_net_sock_write_native(to, from->wbuf + from->whead, from->wlen - from->whead); - from->whead = from->wlen = 0; + scr_net_sock_wq_clear(from); } void scr_net_sock_dial_start(ScrNetSocket *s) { @@ -3207,17 +3256,19 @@ bool scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) { (data->len * scr_bytes_elem_size(data->elem))); } -/* socket.writableLength: the bytes written and not yet sent. */ +/* socket.writableLength: Node's state.length, the length of every write + * not yet completely sent. */ double scr_net_sock_writable_length(ScrNetSocket *s) { ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; - return (double)(w->wlen - w->whead); + return (double)w->wpending; } /* socket.writableNeedDrain: a write answered false and 'drain' has not - * fired, on a socket whose write half is open. */ + * fired, on a socket whose write half is open (including one still + * waiting to dial). */ bool scr_net_sock_writable_need_drain(ScrNetSocket *s) { ScrNetSocket *w = s->adopted_by != NULL ? s->adopted_by : s; - return w->need_drain && !w->wr_ending && !w->close_emitted && w->fd >= 0; + return w->need_drain && scr_net_sock_write_open(w); } /* on('drain') / once('drain'). */ @@ -5331,7 +5382,7 @@ ScrNetSocket *scr_net_sock_adopt(ScrNetSocket *raw) { tun->send_only = true; t->tunnel = tun; } - raw->whead = raw->wlen = 0; + scr_net_sock_wq_clear(raw); raw->adopted_by = scr_net_sock_retain(t); t->adopted_from = scr_net_sock_retain(raw); scr_net_sock_register(t); @@ -6260,10 +6311,12 @@ static ScrDyn *scr_net_dynh_sock_get(void *h, const char *key, size_t key_len) { } } if (strcmp(key, "readable") == 0) return scr_dyn_new_bool(scr_net_sock_readable(s)); + if (strcmp(key, "writableLength") == 0) return scr_dyn_new_num(scr_net_sock_writable_length(s)); + if (strcmp(key, "writableNeedDrain") == 0) + return scr_dyn_new_bool(scr_net_sock_writable_need_drain(s)); if (strcmp(key, "writableHighWaterMark") == 0 || strcmp(key, "readableHighWaterMark") == 0) { - /* Node's default stream highWaterMark — a constant here (backpressure - * is not modeled; SEMANTICS.md) */ - return scr_dyn_new_num(16384); + /* Node's default stream highWaterMark (getDefaultHighWaterMark()) */ + return scr_dyn_new_num(SCR_NET_WRITE_HWM); } if (scr_net_dynh_tls != NULL) { ScrDyn *out = NULL; diff --git a/tests/corpus/7515-net-socket-backpressure.ts b/tests/corpus/7515-net-socket-backpressure.ts index a265d3f77..260726a15 100644 --- a/tests/corpus/7515-net-socket-backpressure.ts +++ b/tests/corpus/7515-net-socket-backpressure.ts @@ -1,47 +1,136 @@ // net.Socket write() backpressure, as in Node 24: write answers false when -// the bytes it leaves unsent reach the 65536-byte high-water mark, which a -// write made before the connection does, writableNeedDrain stays true -// until 'drain', and 'drain' fires once the buffered bytes are sent. An -// echo client that waits for 'drain' when write answers false moves every -// byte. +// the writes not yet completely sent reach the 65536-byte high-water mark, +// each counting in full, which a write made before the connection does; +// writableNeedDrain stays true until 'drain', and 'drain' fires once the +// buffered bytes are sent. The same holds while a custom lookup is pending, +// writableLength counts a large write the kernel took in part in full, +// untyped code reads the same properties, and a write whose send fails +// after the peer reset the connection answers false. An echo client that +// waits for 'drain' when write answers false moves every byte. import { connect, createServer, Socket } from "node:net"; +import type { LookupAddress, LookupAllOptions, LookupOneOptions } from "node:dns"; const CHUNK = 65536; const TOTAL = 64 * CHUNK; const chunk = Buffer.alloc(CHUNK, 120); +function untyped(socket: any): string { + return `${socket.writableLength} ${socket.writableNeedDrain} ${socket.writableHighWaterMark}`; +} + +function echo(port: number): Promise { + return new Promise((resolve) => { + const client = connect(port, "127.0.0.1"); + console.log("small write before connect", client.write("hi"), client.writableLength); + console.log("large write before connect", client.write(Buffer.alloc(70000, 121)), client.writableLength); + console.log("need drain", client.writableNeedDrain, "untyped", untyped(client)); + let sent = 70002; + let received = 0; + const pump = (): void => { + while (sent < TOTAL) { + sent += CHUNK; + if (!client.write(chunk)) { + client.once("drain", pump); + return; + } + } + client.end(); + }; + client.once("drain", () => { + console.log("drain fired, need drain", client.writableNeedDrain, "pending", client.writableLength); + pump(); + }); + client.on("data", (data: Buffer) => { + received += data.length; + }); + client.on("end", () => { + console.log("received", received, "of", sent); + resolve(); + }); + }); +} + +function lookupPending(port: number): Promise { + return new Promise((resolve) => { + const client = connect({ + port, + host: "localhost", + lookup: ( + _host: string, + _options: LookupOneOptions | LookupAllOptions | number | undefined, + callback: (err: NodeJS.ErrnoException | null, address: string | LookupAddress[], family?: number) => void, + ) => { + const answer: LookupAddress[] = [{ address: "127.0.0.1", family: 4 }]; + setTimeout(() => callback(null, answer), 20); + }, + }); + console.log("lookup write", client.write(Buffer.alloc(70000, 97))); + console.log("lookup state", client.writableLength, client.writableNeedDrain, "untyped", untyped(client)); + client.once("drain", () => { + console.log("lookup drain", client.writableLength, client.writableNeedDrain); + client.destroy(); + resolve(); + }); + }); +} + +function largeWrite(port: number): Promise { + return new Promise((resolve) => { + const client = connect(port, "127.0.0.1", () => { + const size = 16 * 1024 * 1024; + console.log("large write", client.write(Buffer.alloc(size, 98))); + console.log("large state", client.writableLength === size, client.writableNeedDrain); + client.once("drain", () => { + console.log("large drain", client.writableLength, client.writableNeedDrain); + client.destroy(); + resolve(); + }); + setTimeout(() => { + if (paused !== null) paused.resume(); + }, 30); + }); + }); +} + +function afterReset(port: number): Promise { + return new Promise((resolve) => { + const client = connect(port, "127.0.0.1", () => { + client.pause(); + setTimeout(() => { + console.log("first write after the peer closed", client.write("a")); + setTimeout(() => { + console.log("write after reset", client.write("b")); + }, 50); + }, 50); + }); + client.on("error", () => {}); + client.on("close", () => { + console.log("closed, destroyed", client.destroyed); + resolve(); + }); + }); +} + +let paused: Socket | null = null; +let mode = "echo"; const server = createServer((socket: Socket) => { - socket.pipe(socket); + if (mode === "echo") socket.pipe(socket); + else if (mode === "drain") socket.resume(); + else if (mode === "pause") { + socket.pause(); + paused = socket; + } else socket.destroy(); }); -server.listen(0, "127.0.0.1", () => { +server.listen(0, "127.0.0.1", async () => { const addr = server.address(); const port = typeof addr === "object" && addr !== null ? addr.port : 0; - const client = connect(port, "127.0.0.1"); - console.log("small write before connect", client.write("hi"), client.writableLength); - console.log("large write before connect", client.write(Buffer.alloc(70000, 121)), client.writableLength); - console.log("need drain", client.writableNeedDrain); - let sent = 70002; - let received = 0; - const pump = (): void => { - while (sent < TOTAL) { - sent += CHUNK; - if (!client.write(chunk)) { - client.once("drain", pump); - return; - } - } - client.end(); - }; - client.once("drain", () => { - console.log("drain fired, need drain", client.writableNeedDrain, "pending", client.writableLength); - pump(); - }); - client.on("data", (data: Buffer) => { - received += data.length; - }); - client.on("end", () => { - console.log("received", received, "of", sent); - server.close(); - }); + await echo(port); + mode = "drain"; + await lookupPending(port); + mode = "pause"; + await largeWrite(port); + mode = "end"; + await afterReset(port); + server.close(); }); From 256fbdba20804c8a478f7cb0f77090d6df6dd5a8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 04:54:28 -0500 Subject: [PATCH 048/230] Keep the helper for callbacks that read a local class's captures A lone array callback in a local class's method could read a binding the method reaches through its class's captures. Those bindings are shared by every method of the class, so another method can assign one while the loop runs, but the copied loop read the value once when the call started. stableTarget now counts a method's class captures as inherited, so such a callback keeps its helper and reads the current value on every call. --- .../lowering/containers/array-pipelines.ts | 7 +++++-- .../compiler/test/array-pipeline-fusion.test.ts | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index 7c2e07b1c..197af2a9e 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -950,11 +950,14 @@ class PipelineFusion { * nothing assigns after their declaration: `fn`'s own locals and * parameters, written neither by `fn` nor by any closure that shares * them, and not by the callback body. A binding a local class captures - * counts as written, since its methods may assign it. The callback must + * counts as written, since its methods may assign it; so does a binding + * a method reads through its class's captures. The callback must * not observe the ambient receiver (AmbientReceiverReaders). */ private stableTarget(fn: IrFunction): InlineTarget { const locals = new Map(fn.locals.map((l) => [l.id, l])); - const inherited = new Set((fn.captures ?? []).map((c) => c.localId)); + const inherited = new Set( + [...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId), + ); const written = this.written.get(fn.name); const classCaptured = this.classCaptured(fn); return (arg) => { diff --git a/packages/compiler/test/array-pipeline-fusion.test.ts b/packages/compiler/test/array-pipeline-fusion.test.ts index e41e378a3..2c0c138ce 100644 --- a/packages/compiler/test/array-pipeline-fusion.test.ts +++ b/packages/compiler/test/array-pipeline-fusion.test.ts @@ -249,3 +249,20 @@ console.log(out.join(","), hit); ${postlude}`); expect(helperCalls(init(mod))).toEqual(["%arr.loop", "%arr.loop"]); }); + +test("a lone call in a local class's method keeps its helper for the class's captures", async () => { + const mod = await lowerSource(`${prelude} +let k = 1; +class Box { + set(v: number): void { k = v; } + go(): number[] { + const b = new Box(); + return rows.map((r) => { b.set(r.id); return r.value + k; }); + } +} +console.log(new Box().go().join(",")); +${postlude}`); + const go = mod.functions.find((f) => f.name.endsWith(".Box.go")); + expect(go).toBeDefined(); + expect(helperCalls(go!)).toEqual(["%arr.map"]); +}); From f8b1f9ffc36ade3f470c4e6d98d7625f5124caff Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:02:40 -0500 Subject: [PATCH 049/230] Keep a spread tuple alive until all its fields are packed into the rest A spread tuple's first field carries the statements that evaluate the tuple into a hidden local, and its later fields read that local. When lowerArraySpreadItems stored the first field as one item's expression, the local's frame ended with that store, so a tuple returned by a call was released before its later fields were read (a use-after-free under SCRIPTC_SAN=1). The field's statements now join the packing sequence at the item's position, whose frame lasts until every item is stored. --- .../frontend/lowering/containers/array-construction.ts | 10 +++++++++- tests/corpus/8004-optional-element-conversions.ts | 8 ++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 63e2e6e07..5029abc36 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -174,7 +174,15 @@ export function lowerArraySpreadItems( ]; for (const node of nodes) { if (!(node instanceof AstNode)) { - const value = undefinedForNullish(lowerer, node, arrType); + // A spread tuple's first field declares the tuple's storage, which + // the later fields read: its statements join this sequence, whose + // frame lasts until every item is stored. + let item = node; + if (item.kind === "seqExpr") { + body.push(...item.stmts); + item = item.result; + } + const value = undefinedForNullish(lowerer, item, arrType); const stored = lowerer.runtimeOptionalWidening(value.type, elem) !== null || optionalElementValue(lowerer, value, elem) diff --git a/tests/corpus/8004-optional-element-conversions.ts b/tests/corpus/8004-optional-element-conversions.ts index 9a4ac55ac..a592a4f32 100644 --- a/tests/corpus/8004-optional-element-conversions.ts +++ b/tests/corpus/8004-optional-element-conversions.ts @@ -58,6 +58,14 @@ const mixedChanging: [boolean, boolean | undefined] = [true, undefined]; console.log(flags(...mixedChanging, ...mixedMore, flip(mixedChanging, true), ...mixedChanging)); const mixedWords: [string, string | undefined] = ["a", undefined]; console.log(label("z", ...["b", undefined, "c"], ...mixedWords), label("y", ...mixedWords, ...new Set(["s", undefined]))); +// A tuple returned by a call keeps its string fields alive until every +// field is packed beside the other spreads. +function makeTuple(n: number): [string, string | undefined, string] { + return ["p" + n.toString(16).repeat(4), n % 2 ? undefined : "q" + n, "r".repeat((n % 7) + 3) + n]; +} +for (let i = 0; i < 3; i++) { + console.log(label("z", ...["m", undefined], ...makeTuple(i), ...mixedWords), label("y", ...makeTuple(i + 10), ...new Set(["s" + i]))); +} function pick(start: (number | undefined)[] | string[], other: (number | undefined)[] | string[]): string { let a = start; From cbc062a35f84e7a7099419e4d5b8a852e37c2e90 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:03:04 -0500 Subject: [PATCH 050/230] Skip NULL children and key-cache walks when disposing dynamic values Disposing a checked-dynamic node released each entry's getter and setter and the node's prototype and symbol tables through scr_dyn_release, though almost all of them are NULL: in a zod validation loop, over half of all dyn releases. Dispose now tests each child first. A pooled object also walked all its slots again on reuse, to take the key bytes it kept back from the pool's key budget. It now records that total when it is pooled, and reuse subtracts it. --- packages/runtime/src/scr_dyn.h | 7 ++++- packages/runtime/src/scr_json.c | 51 +++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index d6b05be71..f90fa1838 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -480,7 +480,12 @@ struct ScrDyn { * exits, and those clear it before release; reuse of a freed object * resets it (scr_dyn_alloc). */ const char *class_name; - struct ScrDynRegion *region; /* see ScrDynRegion */ + union { + struct ScrDynRegion *region; /* see ScrDynRegion */ + /* While the node is pooled: the key bytes it keeps for reuse, + * counted in the pool's key budget (scr_dyn_obj_drop_keys). */ + size_t cached_key_bytes; + }; /* The dynamic tier's hidden class (scr_js_object.c): the transition * tree node naming this object's key sequence, or NULL while it has * none. Only programs with dynamic-tier code read or maintain it: diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 1738ba848..bfc63270f 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -846,15 +846,21 @@ static SCR_TL size_t scr_dyn_cached_key_bytes; #define SCR_DYN_KEY_CACHE_LENGTH 64 #endif -static void scr_dyn_obj_drop_keys(ScrDyn *d, bool cache) { +/* Returns the key bytes it kept for reuse, which it added to the budget; + * the pooled node records them (v.obj.cached_key_bytes) so its reuse takes + * them back without walking its slots. */ +static size_t scr_dyn_obj_drop_keys(ScrDyn *d, bool cache) { + size_t kept = 0; for (size_t i = 0; i < d->v.obj.cap; i++) { ScrDynEntry *entry = &d->v.obj.entries[i]; if (!entry->key) continue; #ifndef SCR_RC_AUDIT + size_t bytes = sizeof(ScrDynKey) + entry->key_len + 1; if (cache && d->v.obj.cap <= SCR_DYN_KEY_CACHE_SLOTS && entry->key_len <= SCR_DYN_KEY_CACHE_LENGTH && - sizeof(ScrDynKey) + entry->key_len + 1 <= SCR_DYN_KEY_CACHE_MAX - scr_dyn_cached_key_bytes) { - scr_dyn_cached_key_bytes += sizeof(ScrDynKey) + entry->key_len + 1; + bytes <= SCR_DYN_KEY_CACHE_MAX - scr_dyn_cached_key_bytes) { + scr_dyn_cached_key_bytes += bytes; + kept += bytes; continue; } #else @@ -864,6 +870,7 @@ static void scr_dyn_obj_drop_keys(ScrDyn *d, bool cache) { entry->key = NULL; entry->key_len = 0; } + return kept; } /* Primitive kinds cannot acquire properties or change kind while live, so @@ -1054,15 +1061,11 @@ static ScrDyn *scr_dyn_alloc(ScrDynKind kind) { d->v.arr.region = NULL; } else if (kind == SCR_DYN_OBJ) { d->v.obj.len = 0; /* cap/entries preserved */ + /* The kept keys leave the pool's budget with the node. */ + scr_dyn_cached_key_bytes -= d->v.obj.cached_key_bytes; d->v.obj.region = NULL; d->v.obj.class_name = NULL; /* a released class prototype's name */ SCR_JS_SHAPE_RESET(d); - if (d->v.obj.cap <= SCR_DYN_KEY_CACHE_SLOTS) { - for (size_t i = 0; i < d->v.obj.cap; i++) { - ScrDynEntry *entry = &d->v.obj.entries[i]; - if (entry->key) scr_dyn_cached_key_bytes -= sizeof(ScrDynKey) + entry->key_len + 1; - } - } d->v.obj.source_identity = NULL; d->v.obj.source_access = NULL; } else if (kind != SCR_DYN_NUM && kind != SCR_DYN_STR) { @@ -1128,6 +1131,12 @@ static inline void scr_dyn_weak_dispose(ScrDyn *d) { if (scr_weak_observing) scr_weak_disposed(d); } +/* A disposed node's child: most getters, setters, prototypes and symbol + * tables are NULL, and the test costs less than the call. */ +static inline void scr_dyn_release_child(ScrDyn *child) { + if (child) scr_dyn_release(child); +} + static void scr_dyn_dispose(ScrDyn *d, bool collected) { #ifndef SCR_RC_AUDIT /* A capsule with nothing but its referent and adapters: the common end @@ -1149,9 +1158,9 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { d->v.obj.index = NULL; } if (d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_OBJ) scr_dyn_region_leave(d); - if (!collected) scr_dyn_release(d->prototype); - if (!collected) scr_dyn_release(d->symbol_properties); - if (!collected) scr_dyn_release(d->symbol_keys); + if (!collected) scr_dyn_release_child(d->prototype); + if (!collected) scr_dyn_release_child(d->symbol_properties); + if (!collected) scr_dyn_release_child(d->symbol_keys); /* Pooled nodes keep all three NULL (scr_dyn_leaf_alloc relies on it). */ d->prototype = d->symbol_properties = d->symbol_keys = NULL; switch (d->kind) { @@ -1171,17 +1180,17 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { free(d->v.arr.presence); d->v.arr.presence = NULL; if (!collected) { - for (size_t i = 0; i < d->v.arr.len; i++) scr_dyn_release(d->v.arr.items[i]); - scr_dyn_release(d->v.arr.properties); + for (size_t i = 0; i < d->v.arr.len; i++) scr_dyn_release_child(d->v.arr.items[i]); + scr_dyn_release_child(d->v.arr.properties); } break; case SCR_DYN_OBJ: for (size_t i = 0; i < d->v.obj.len; i++) { ScrDyn *value = d->v.obj.entries[i].value; - if (!collected || !d->v.obj.entries[i].value_traced) scr_dyn_release(value); + if (!collected || !d->v.obj.entries[i].value_traced) scr_dyn_release_child(value); if (!collected) { - scr_dyn_release(d->v.obj.entries[i].getter); - scr_dyn_release(d->v.obj.entries[i].setter); + scr_dyn_release_child(d->v.obj.entries[i].getter); + scr_dyn_release_child(d->v.obj.entries[i].setter); } d->v.obj.entries[i].value = NULL; d->v.obj.entries[i].getter = NULL; @@ -1207,7 +1216,7 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { case SCR_DYN_TYPED_REF: scr_typed_ref_forget(d); - if (!collected) scr_dyn_release(d->v.typed_ref.materialized); + if (!collected) scr_dyn_release_child(d->v.typed_ref.materialized); while (d->v.typed_ref.casts) { ScrDynTypedCast *cast = d->v.typed_ref.casts; d->v.typed_ref.casts = cast->next; @@ -1218,8 +1227,8 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { break; case SCR_DYN_PROXY: if (!collected) { - scr_dyn_release(d->v.proxy.target); - scr_dyn_release(d->v.proxy.handler); + scr_dyn_release_child(d->v.proxy.target); + scr_dyn_release_child(d->v.proxy.handler); } break; default: @@ -1230,7 +1239,7 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) { #endif #ifndef SCR_RC_AUDIT if (!collected && scr_dyn_free_count < SCR_DYN_FREE_MAX) { - if (d->kind == SCR_DYN_OBJ) scr_dyn_obj_drop_keys(d, true); + if (d->kind == SCR_DYN_OBJ) d->v.obj.cached_key_bytes = scr_dyn_obj_drop_keys(d, true); /* Pooled blocks retain their headers and count toward the bounded * allocated heap, but never remain in a candidate buffer. */ scr_dyn_weak_dispose(d); From 9abf35f6a2e999944ee503c545000f9033cf632a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:23:35 -0500 Subject: [PATCH 051/230] Build the large compiler tables on first use The native compiler ran four large module initializers at every startup: the builtin call signatures (LIB_FN_SIGS), the Node 24 fetch compatibility profile, and the builtin module function and ambient surface tables. Together their init functions are about 1.3 MB of code, and --version and warm cache-hit builds paged them in and built the tables without reading them. Each table is now built by a function on its first call and kept in a module variable: - libFnSignatures() replaces LIB_FN_SIGS - node24FetchCompatProfile() and staticFetchMembers() replace NODE24_FETCH_COMPAT_PROFILE and the STATIC_* member sets - builtinModuleFns() and ambientSurfaceFns() replace BUILTIN_MODULE_FNS and AMBIENT_SURFACE_FNS The @scriptc/compiler entry point keeps LIB_FN_SIGS and NODE24_FETCH_COMPAT_PROFILE as deprecated constants built from the new functions when the entry module loads. The native compiler does not load that module. The surface manifest's coverage notes read the fetch target through the profile function, so building the manifest module no longer builds the profile. The table contents are unchanged. --- packages/compiler/src/compat/fetch-profile.ts | 1511 +-- .../compiler/src/coverage/surface-manifest.ts | 53 +- .../src/frontend/lowering/lib-boundary.ts | 4 +- .../frontend/lowering/lower-builtin-values.ts | 6 +- .../src/frontend/lowering/lower-stdlib.ts | 40 +- .../src/frontend/lowering/surfaces.ts | 3116 ++--- packages/compiler/src/index.ts | 12 +- .../compiler/src/ir/builtin-signatures.ts | 10139 ++++++++-------- packages/compiler/src/ir/ir.ts | 2 +- packages/compiler/src/ir/validate.ts | 4 +- packages/compiler/src/library/fence-eval.ts | 12 +- packages/compiler/test/compat-exports.test.ts | 12 + packages/compiler/test/node-pin.test.ts | 4 +- tests/harness/README.md | 2 +- tests/harness/code-quality.test.ts | 2 +- tests/harness/fetch-conformance-program.ts | 2 +- tests/harness/fetch-conformance.test.ts | 4 +- tests/harness/surface-manifest.test.ts | 6 +- 18 files changed, 7525 insertions(+), 7406 deletions(-) create mode 100644 packages/compiler/test/compat-exports.test.ts diff --git a/packages/compiler/src/compat/fetch-profile.ts b/packages/compiler/src/compat/fetch-profile.ts index ff7cedb22..03d261aff 100644 --- a/packages/compiler/src/compat/fetch-profile.ts +++ b/packages/compiler/src/compat/fetch-profile.ts @@ -155,494 +155,104 @@ const typedInterfaceUnsupported = "typed source has no compiler bridge for this const metadataExclusion = "WebIDL brand metadata is observable reflection, not an executable compatibility operation"; -export const NODE24_FETCH_COMPAT_PROFILE = { - schemaVersion: 1, - target: { - node: "24.22.0", - undici: "7.29.1", - }, - requestInit: [ - { - id: "stdlib.fetch.request-init.method", - name: "RequestInit.method", - conversion: "WebIDL ByteString after all call arguments evaluate", - evidence: [fixture("static-coercion")], +function buildFetchCompatProfile() { + return { + schemaVersion: 1, + target: { + node: "24.22.0", + undici: "7.29.1", }, - { - id: "stdlib.fetch.request-init.headers", - name: "RequestInit.headers", - conversion: "Headers, record, or sequence-of-pairs snapshot", - evidence: [fixture("static"), fixture("static-coercion")], - }, - { - id: "stdlib.fetch.request-init.body", - name: "RequestInit.body", - conversion: "string, Uint8Array, ReadableStream, or null", - evidence: [fixture("static"), fixture("static-stream"), fixture("static-coercion")], - }, - { - id: "stdlib.fetch.request-init.duplex", - name: "RequestInit.duplex", - conversion: "WebIDL enum; 'half' required for streaming bodies", - evidence: [fixture("static-stream"), fixture("static-coercion")], - }, - { - id: "stdlib.fetch.request-init.redirect", - name: "RequestInit.redirect", - conversion: "WebIDL enum: follow, error, or manual", - evidence: [fixture("static"), fixture("static-coercion")], - }, - { - id: "stdlib.fetch.request-init.signal", - name: "RequestInit.signal", - conversion: "native AbortSignal handle or absent", - evidence: [fixture("static"), fixture("static-abort-throw")], - }, - ], - responseInit: [ - { - id: "stdlib.response-init.headers", - name: "ResponseInit.headers", - conversion: "Headers, record, or sequence-of-pairs snapshot", - evidence: [fixture("static")], - }, - { - id: "stdlib.response-init.status", - name: "ResponseInit.status", - conversion: "WebIDL unsigned-short conversion followed by the 200–599 range check", - evidence: [fixture("static")], - }, - { - id: "stdlib.response-init.statusText", - name: "ResponseInit.statusText", - conversion: "WebIDL ByteString with HTTP reason-phrase validation", - evidence: [fixture("static")], - }, - ], - members: { - responseReads: [ - "ok", - "status", - "statusText", - "url", - "redirected", - "headers", - "body", - "bodyUsed", - ], - responseCalls: ["json", "text", "bytes"], - readableStreamReads: ["locked"], - readableStreamCalls: ["cancel", "getReader"], - }, - operations: [ - { - id: "stdlib.headers.constructor", - name: "Headers constructor", - kind: "constructor", - facets: ["webidl-conversion", "mutation", "identity"], - scope: "Record and iterable header pairs, including copying native Headers", - evidence: [fixture("static-request")], - }, - { - id: "stdlib.request.constructor", - name: "Request constructor", - kind: "constructor", - facets: ["webidl-conversion", "body-consumption", "error-shape"], - scope: - "Absolute URLs with method, headers, body, duplex, redirect, and signal; other RequestInit members retain explicit fences", - evidence: [fixture("static-request")], - }, - ...[ - "method", - "url", - "headers", - "destination", - "referrer", - "referrerPolicy", - "mode", - "credentials", - "cache", - "redirect", - "integrity", - "keepalive", - "isReloadNavigation", - "isHistoryNavigation", - "signal", - "body", - "bodyUsed", - "duplex", - ].map((member): FetchCompatOperation => ({ - id: `stdlib.request.${member}`, - name: `Request.${member}`, - kind: "property", - facets: ["property-read", "state-machine"], - scope: "Reads on native Requests within the constructor's supported RequestInit subset", - evidence: [fixture("static-request")], - })), - ...["arrayBuffer", "text", "json", "bytes"].map((member): FetchCompatOperation => ({ - id: `stdlib.request.${member}`, - name: `Request.${member}`, - kind: "method", - facets: ["body-consumption", "promise-settlement", "state-machine", "error-shape"], - evidence: [fixture("static-request")], - })), - { - id: "stdlib.response.constructor", - name: "Response constructor", - kind: "constructor", - facets: ["webidl-conversion", "body-consumption", "state-machine", "error-shape"], - scope: - "BodyInit is string, Uint8Array/Buffer, ReadableStream, null/undefined, or a checked-dynamic value that follows the supported WebIDL string conversion; ResponseInit is headers/status/statusText", - evidence: [fixture("static")], - }, - { - id: "stdlib.fetch", - name: "fetch", - kind: "function", - facets: [ - "argument-evaluation", - "webidl-conversion", - "surplus-arguments", - "promise-settlement", - "transport", - "error-shape", - ], - evidence: [fixture("static"), fixture("static-coercion"), fixture("static-network-error")], - }, - { - id: "stdlib.abort-controller.constructor", - name: "AbortController constructor", - kind: "constructor", - facets: ["argument-evaluation", "identity", "state-machine", "surplus-arguments"], - evidence: [fixture("static-controller")], - }, - { - id: "stdlib.abort-controller.signal", - name: "AbortController.signal", - kind: "property", - facets: ["identity", "property-read", "state-machine"], - evidence: [fixture("static-controller")], - }, - { - id: "stdlib.abort-controller.abort", - name: "AbortController.abort", - kind: "method", - facets: ["callback-order", "identity", "liveness", "state-machine", "surplus-arguments"], - evidence: [fixture("static-controller")], - }, - { - id: "stdlib.abort-signal.abort", - name: "AbortSignal.abort", - kind: "static-method", - facets: ["identity", "liveness", "surplus-arguments", "property-read"], - evidence: [generated("webidl-operations"), fixture("static-stream-this")], - }, - { - id: "stdlib.abort-signal.any", - name: "AbortSignal.any", - kind: "static-method", - facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "property-read"], - evidence: [generated("webidl-operations"), fixture("static-stream-this")], - }, - { - id: "stdlib.abort-signal.timeout", - name: "AbortSignal.timeout", - kind: "static-method", - facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "error-shape"], - evidence: [generated("webidl-operations"), fixture("static-stream-this")], - }, - { - id: "stdlib.abort-signal.aborted", - name: "AbortSignal.aborted", - kind: "property", - facets: ["property-read"], - evidence: [generated("webidl-operations")], - }, - { - id: "stdlib.abort-signal.reason", - name: "AbortSignal.reason", - kind: "property", - facets: ["identity", "liveness", "property-read"], - evidence: [generated("webidl-operations"), fixture("static-stream-this")], - }, - { - id: "stdlib.abort-signal.onabort", - name: "AbortSignal.onabort", - kind: "property", - facets: ["callback-order", "callback-this", "mutation", "property-read"], - evidence: [generated("abort-events"), fixture("static-listener-this")], - }, - { - id: "stdlib.abort-signal.throw-if-aborted", - name: "AbortSignal.throwIfAborted", - kind: "method", - facets: ["identity", "error-shape"], - evidence: [generated("webidl-operations"), fixture("static-abort-throw")], - }, - { - id: "stdlib.abort-signal.add-event-listener", - name: "AbortSignal.addEventListener", - kind: "method", - facets: ["webidl-conversion", "identity", "callback-order", "callback-this"], - evidence: [ - generated("abort-events"), - fixture("static-listener-this"), - fixture("static-listener-noncallable"), - ], - }, - { - id: "stdlib.abort-signal.remove-event-listener", - name: "AbortSignal.removeEventListener", - kind: "method", - facets: ["webidl-conversion", "identity", "callback-order"], - evidence: [generated("abort-events"), fixture("static-stream-this")], - }, - { - id: "stdlib.abort-signal.dispatch-event", - name: "AbortSignal.dispatchEvent", - kind: "method", - facets: ["callback-order", "callback-this", "error-shape"], - evidence: [fixture("static-dispatch-throw")], - }, - { - id: "stdlib.readable-stream.constructor", - name: "ReadableStream constructor", - kind: "constructor", - facets: ["webidl-conversion", "callback-this", "callback-order", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream-this")], - }, - { - id: "stdlib.readable-stream.from", - name: "ReadableStream.from", - kind: "static-method", - facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "liveness"], - evidence: [generated("webidl-operations"), generated("stream-traces")], - }, - { - id: "stdlib.readable-stream.locked", - name: "ReadableStream.locked", - kind: "property", - facets: ["property-read", "state-machine"], - evidence: [generated("stream-traces")], - }, - { - id: "stdlib.readable-stream.cancel", - name: "ReadableStream.cancel", - kind: "method", - facets: ["identity", "promise-settlement", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream.get-reader", - name: "ReadableStream.getReader", - kind: "method", - facets: ["webidl-conversion", "identity", "state-machine", "error-shape"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-reader.closed", - name: "ReadableStreamDefaultReader.closed", - kind: "property", - facets: ["promise-settlement", "property-read", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-reader.read", - name: "ReadableStreamDefaultReader.read", - kind: "method", - facets: ["identity", "promise-settlement", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-reader.cancel", - name: "ReadableStreamDefaultReader.cancel", - kind: "method", - facets: ["identity", "promise-settlement", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-reader.release-lock", - name: "ReadableStreamDefaultReader.releaseLock", - kind: "method", - facets: ["promise-settlement", "state-machine", "error-shape"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-controller.desired-size", - name: "ReadableStreamDefaultController.desiredSize", - kind: "property", - facets: ["property-read", "state-machine"], - evidence: [generated("stream-traces")], - }, - { - id: "stdlib.readable-stream-default-controller.enqueue", - name: "ReadableStreamDefaultController.enqueue", - kind: "method", - facets: ["identity", "liveness", "state-machine", "error-shape"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-controller.close", - name: "ReadableStreamDefaultController.close", - kind: "method", - facets: ["state-machine", "error-shape"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - { - id: "stdlib.readable-stream-default-controller.error", - name: "ReadableStreamDefaultController.error", - kind: "method", - facets: ["identity", "promise-settlement", "state-machine"], - evidence: [generated("stream-traces"), fixture("static-stream")], - }, - ...["append", "delete", "get", "getSetCookie", "has", "set", "forEach"].map( - (member): FetchCompatOperation => ({ - id: `stdlib.headers.${member}`, - name: `Headers.${member}`, - kind: "method", - facets: - member === "forEach" - ? ["callback-order", "callback-this", "mutation"] - : member === "get" || member === "has" - ? ["webidl-conversion", "missing-arguments", "property-read"] - : ["webidl-conversion", "mutation", "error-shape"], + requestInit: [ + { + id: "stdlib.fetch.request-init.method", + name: "RequestInit.method", + conversion: "WebIDL ByteString after all call arguments evaluate", + evidence: [fixture("static-coercion")], + }, + { + id: "stdlib.fetch.request-init.headers", + name: "RequestInit.headers", + conversion: "Headers, record, or sequence-of-pairs snapshot", evidence: [fixture("static"), fixture("static-coercion")], - }), - ), - ...["ok", "status", "statusText", "url", "redirected", "headers", "body", "bodyUsed"].map( - (member): FetchCompatOperation => ({ - id: `stdlib.response.${member}`, - name: `Response.${member}`, - kind: "property", - facets: ["property-read"], - evidence: [fixture("static")], - }), - ), - { - id: "stdlib.response.static.json", - name: "Response.json", - kind: "static-method", - facets: ["webidl-conversion", "error-shape"], - scope: - "data that crosses the checked-dynamic boundary, serialized with JSON.stringify (toJSON hooks included); ResponseInit as for the constructor", - evidence: [fixture("static-response-json")], - }, - ...["json", "text", "bytes", "arrayBuffer"].map((member): FetchCompatOperation => ({ - id: `stdlib.response.${member}`, - name: `Response.${member}`, - kind: "method", - facets: ["body-consumption", "promise-settlement", "state-machine", "error-shape"], - evidence: - member === "arrayBuffer" - ? [fixture("static")] - : [fixture("static"), fixture("static-stream")], - })), - ], - inventory: { - interfaces: [ - "AbortController", - "AbortSignal", - "Headers", - "Request", - "Response", - "ReadableStream", - "ReadableStreamDefaultReader", - "ReadableStreamDefaultController", + }, + { + id: "stdlib.fetch.request-init.body", + name: "RequestInit.body", + conversion: "string, Uint8Array, ReadableStream, or null", + evidence: [fixture("static"), fixture("static-stream"), fixture("static-coercion")], + }, + { + id: "stdlib.fetch.request-init.duplex", + name: "RequestInit.duplex", + conversion: "WebIDL enum; 'half' required for streaming bodies", + evidence: [fixture("static-stream"), fixture("static-coercion")], + }, + { + id: "stdlib.fetch.request-init.redirect", + name: "RequestInit.redirect", + conversion: "WebIDL enum: follow, error, or manual", + evidence: [fixture("static"), fixture("static-coercion")], + }, + { + id: "stdlib.fetch.request-init.signal", + name: "RequestInit.signal", + conversion: "native AbortSignal handle or absent", + evidence: [fixture("static"), fixture("static-abort-throw")], + }, ], - entries: [ - staticEntry("stdlib.fetch", "globalThis", "fetch", "global"), - - staticEntry( - "stdlib.abort-controller.constructor", - "AbortController", - "constructor", - "constructor", - ), - staticEntry("stdlib.abort-controller.signal", "AbortController", "signal", "prototype"), - staticEntry("stdlib.abort-controller.abort", "AbortController", "abort", "prototype"), - outOfScopeEntry( - "stdlib.abort-controller.symbol.toStringTag", - "AbortController", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - - unsupportedEntry( - "stdlib.abort-signal.constructor", - "AbortSignal", - "constructor", - "constructor", - "Node exposes the interface object but constructing it throws; the compiler does not preserve that constructor behavior", - ), - staticEntry("stdlib.abort-signal.abort", "AbortSignal", "abort", "static"), - staticEntry("stdlib.abort-signal.timeout", "AbortSignal", "timeout", "static"), - staticEntry("stdlib.abort-signal.any", "AbortSignal", "any", "static"), - staticEntry("stdlib.abort-signal.aborted", "AbortSignal", "aborted", "prototype"), - staticEntry("stdlib.abort-signal.reason", "AbortSignal", "reason", "prototype"), - staticEntry( - "stdlib.abort-signal.throw-if-aborted", - "AbortSignal", - "throwIfAborted", - "prototype", - ), - staticEntry("stdlib.abort-signal.onabort", "AbortSignal", "onabort", "prototype"), - staticEntry( - "stdlib.abort-signal.add-event-listener", - "AbortSignal", - "addEventListener", - "prototype-inherited", - ), - staticEntry( - "stdlib.abort-signal.remove-event-listener", - "AbortSignal", - "removeEventListener", - "prototype-inherited", - ), - staticEntry( - "stdlib.abort-signal.dispatch-event", - "AbortSignal", - "dispatchEvent", - "prototype-inherited", - ), - outOfScopeEntry( - "stdlib.abort-signal.symbol.toStringTag", - "AbortSignal", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - - staticEntry("stdlib.headers.constructor", "Headers", "constructor", "constructor"), - ...["append", "delete", "get", "has", "set", "getSetCookie"].map((member) => - staticEntry(`stdlib.headers.${member}`, "Headers", member, "prototype"), - ), - ...["keys", "values", "entries"].map((member) => - unsupportedEntry( - `stdlib.headers.${member}`, - "Headers", - member, - "prototype", - "native Headers iteration does not yet expose a static iterator handle", - ), - ), - staticEntry("stdlib.headers.forEach", "Headers", "forEach", "prototype"), - unsupportedEntry( - "stdlib.headers.symbol.iterator", - "Headers", - "[Symbol.iterator]", - "prototype-symbol", - "native Headers iteration does not expose an engine-free iterator handle", - ), - outOfScopeEntry( - "stdlib.headers.symbol.toStringTag", - "Headers", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - - staticEntry("stdlib.request.constructor", "Request", "constructor", "constructor"), + responseInit: [ + { + id: "stdlib.response-init.headers", + name: "ResponseInit.headers", + conversion: "Headers, record, or sequence-of-pairs snapshot", + evidence: [fixture("static")], + }, + { + id: "stdlib.response-init.status", + name: "ResponseInit.status", + conversion: "WebIDL unsigned-short conversion followed by the 200–599 range check", + evidence: [fixture("static")], + }, + { + id: "stdlib.response-init.statusText", + name: "ResponseInit.statusText", + conversion: "WebIDL ByteString with HTTP reason-phrase validation", + evidence: [fixture("static")], + }, + ], + members: { + responseReads: [ + "ok", + "status", + "statusText", + "url", + "redirected", + "headers", + "body", + "bodyUsed", + ], + responseCalls: ["json", "text", "bytes"], + readableStreamReads: ["locked"], + readableStreamCalls: ["cancel", "getReader"], + }, + operations: [ + { + id: "stdlib.headers.constructor", + name: "Headers constructor", + kind: "constructor", + facets: ["webidl-conversion", "mutation", "identity"], + scope: "Record and iterable header pairs, including copying native Headers", + evidence: [fixture("static-request")], + }, + { + id: "stdlib.request.constructor", + name: "Request constructor", + kind: "constructor", + facets: ["webidl-conversion", "body-consumption", "error-shape"], + scope: + "Absolute URLs with method, headers, body, duplex, redirect, and signal; other RequestInit members retain explicit fences", + evidence: [fixture("static-request")], + }, ...[ "method", "url", @@ -662,276 +272,695 @@ export const NODE24_FETCH_COMPAT_PROFILE = { "body", "bodyUsed", "duplex", - "clone", - "blob", - "arrayBuffer", - "text", - "json", - "formData", - "bytes", - "attribute", - ].map((member) => - ["clone", "blob", "formData", "attribute"].includes(member) - ? unsupportedEntry( - `stdlib.request.${member}`, - "Request", - member, - "prototype", - typedInterfaceUnsupported, - ) - : staticEntry(`stdlib.request.${member}`, "Request", member, "prototype"), + ].map((member): FetchCompatOperation => ({ + id: `stdlib.request.${member}`, + name: `Request.${member}`, + kind: "property", + facets: ["property-read", "state-machine"], + scope: "Reads on native Requests within the constructor's supported RequestInit subset", + evidence: [fixture("static-request")], + })), + ...["arrayBuffer", "text", "json", "bytes"].map((member): FetchCompatOperation => ({ + id: `stdlib.request.${member}`, + name: `Request.${member}`, + kind: "method", + facets: ["body-consumption", "promise-settlement", "state-machine", "error-shape"], + evidence: [fixture("static-request")], + })), + { + id: "stdlib.response.constructor", + name: "Response constructor", + kind: "constructor", + facets: ["webidl-conversion", "body-consumption", "state-machine", "error-shape"], + scope: + "BodyInit is string, Uint8Array/Buffer, ReadableStream, null/undefined, or a checked-dynamic value that follows the supported WebIDL string conversion; ResponseInit is headers/status/statusText", + evidence: [fixture("static")], + }, + { + id: "stdlib.fetch", + name: "fetch", + kind: "function", + facets: [ + "argument-evaluation", + "webidl-conversion", + "surplus-arguments", + "promise-settlement", + "transport", + "error-shape", + ], + evidence: [fixture("static"), fixture("static-coercion"), fixture("static-network-error")], + }, + { + id: "stdlib.abort-controller.constructor", + name: "AbortController constructor", + kind: "constructor", + facets: ["argument-evaluation", "identity", "state-machine", "surplus-arguments"], + evidence: [fixture("static-controller")], + }, + { + id: "stdlib.abort-controller.signal", + name: "AbortController.signal", + kind: "property", + facets: ["identity", "property-read", "state-machine"], + evidence: [fixture("static-controller")], + }, + { + id: "stdlib.abort-controller.abort", + name: "AbortController.abort", + kind: "method", + facets: ["callback-order", "identity", "liveness", "state-machine", "surplus-arguments"], + evidence: [fixture("static-controller")], + }, + { + id: "stdlib.abort-signal.abort", + name: "AbortSignal.abort", + kind: "static-method", + facets: ["identity", "liveness", "surplus-arguments", "property-read"], + evidence: [generated("webidl-operations"), fixture("static-stream-this")], + }, + { + id: "stdlib.abort-signal.any", + name: "AbortSignal.any", + kind: "static-method", + facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "property-read"], + evidence: [generated("webidl-operations"), fixture("static-stream-this")], + }, + { + id: "stdlib.abort-signal.timeout", + name: "AbortSignal.timeout", + kind: "static-method", + facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "error-shape"], + evidence: [generated("webidl-operations"), fixture("static-stream-this")], + }, + { + id: "stdlib.abort-signal.aborted", + name: "AbortSignal.aborted", + kind: "property", + facets: ["property-read"], + evidence: [generated("webidl-operations")], + }, + { + id: "stdlib.abort-signal.reason", + name: "AbortSignal.reason", + kind: "property", + facets: ["identity", "liveness", "property-read"], + evidence: [generated("webidl-operations"), fixture("static-stream-this")], + }, + { + id: "stdlib.abort-signal.onabort", + name: "AbortSignal.onabort", + kind: "property", + facets: ["callback-order", "callback-this", "mutation", "property-read"], + evidence: [generated("abort-events"), fixture("static-listener-this")], + }, + { + id: "stdlib.abort-signal.throw-if-aborted", + name: "AbortSignal.throwIfAborted", + kind: "method", + facets: ["identity", "error-shape"], + evidence: [generated("webidl-operations"), fixture("static-abort-throw")], + }, + { + id: "stdlib.abort-signal.add-event-listener", + name: "AbortSignal.addEventListener", + kind: "method", + facets: ["webidl-conversion", "identity", "callback-order", "callback-this"], + evidence: [ + generated("abort-events"), + fixture("static-listener-this"), + fixture("static-listener-noncallable"), + ], + }, + { + id: "stdlib.abort-signal.remove-event-listener", + name: "AbortSignal.removeEventListener", + kind: "method", + facets: ["webidl-conversion", "identity", "callback-order"], + evidence: [generated("abort-events"), fixture("static-stream-this")], + }, + { + id: "stdlib.abort-signal.dispatch-event", + name: "AbortSignal.dispatchEvent", + kind: "method", + facets: ["callback-order", "callback-this", "error-shape"], + evidence: [fixture("static-dispatch-throw")], + }, + { + id: "stdlib.readable-stream.constructor", + name: "ReadableStream constructor", + kind: "constructor", + facets: ["webidl-conversion", "callback-this", "callback-order", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream-this")], + }, + { + id: "stdlib.readable-stream.from", + name: "ReadableStream.from", + kind: "static-method", + facets: ["webidl-conversion", "missing-arguments", "surplus-arguments", "liveness"], + evidence: [generated("webidl-operations"), generated("stream-traces")], + }, + { + id: "stdlib.readable-stream.locked", + name: "ReadableStream.locked", + kind: "property", + facets: ["property-read", "state-machine"], + evidence: [generated("stream-traces")], + }, + { + id: "stdlib.readable-stream.cancel", + name: "ReadableStream.cancel", + kind: "method", + facets: ["identity", "promise-settlement", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream.get-reader", + name: "ReadableStream.getReader", + kind: "method", + facets: ["webidl-conversion", "identity", "state-machine", "error-shape"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-reader.closed", + name: "ReadableStreamDefaultReader.closed", + kind: "property", + facets: ["promise-settlement", "property-read", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-reader.read", + name: "ReadableStreamDefaultReader.read", + kind: "method", + facets: ["identity", "promise-settlement", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-reader.cancel", + name: "ReadableStreamDefaultReader.cancel", + kind: "method", + facets: ["identity", "promise-settlement", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-reader.release-lock", + name: "ReadableStreamDefaultReader.releaseLock", + kind: "method", + facets: ["promise-settlement", "state-machine", "error-shape"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-controller.desired-size", + name: "ReadableStreamDefaultController.desiredSize", + kind: "property", + facets: ["property-read", "state-machine"], + evidence: [generated("stream-traces")], + }, + { + id: "stdlib.readable-stream-default-controller.enqueue", + name: "ReadableStreamDefaultController.enqueue", + kind: "method", + facets: ["identity", "liveness", "state-machine", "error-shape"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-controller.close", + name: "ReadableStreamDefaultController.close", + kind: "method", + facets: ["state-machine", "error-shape"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + { + id: "stdlib.readable-stream-default-controller.error", + name: "ReadableStreamDefaultController.error", + kind: "method", + facets: ["identity", "promise-settlement", "state-machine"], + evidence: [generated("stream-traces"), fixture("static-stream")], + }, + ...["append", "delete", "get", "getSetCookie", "has", "set", "forEach"].map( + (member): FetchCompatOperation => ({ + id: `stdlib.headers.${member}`, + name: `Headers.${member}`, + kind: "method", + facets: + member === "forEach" + ? ["callback-order", "callback-this", "mutation"] + : member === "get" || member === "has" + ? ["webidl-conversion", "missing-arguments", "property-read"] + : ["webidl-conversion", "mutation", "error-shape"], + evidence: [fixture("static"), fixture("static-coercion")], + }), ), - outOfScopeEntry( - "stdlib.request.symbol.toStringTag", + ...["ok", "status", "statusText", "url", "redirected", "headers", "body", "bodyUsed"].map( + (member): FetchCompatOperation => ({ + id: `stdlib.response.${member}`, + name: `Response.${member}`, + kind: "property", + facets: ["property-read"], + evidence: [fixture("static")], + }), + ), + { + id: "stdlib.response.static.json", + name: "Response.json", + kind: "static-method", + facets: ["webidl-conversion", "error-shape"], + scope: + "data that crosses the checked-dynamic boundary, serialized with JSON.stringify (toJSON hooks included); ResponseInit as for the constructor", + evidence: [fixture("static-response-json")], + }, + ...["json", "text", "bytes", "arrayBuffer"].map((member): FetchCompatOperation => ({ + id: `stdlib.response.${member}`, + name: `Response.${member}`, + kind: "method", + facets: ["body-consumption", "promise-settlement", "state-machine", "error-shape"], + evidence: + member === "arrayBuffer" + ? [fixture("static")] + : [fixture("static"), fixture("static-stream")], + })), + ], + inventory: { + interfaces: [ + "AbortController", + "AbortSignal", + "Headers", "Request", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - - staticEntry("stdlib.response.constructor", "Response", "constructor", "constructor"), - staticEntry("stdlib.response.static.json", "Response", "json", "static"), - ...["error", "redirect"].map((member) => - unsupportedEntry( - `stdlib.response.static.${member}`, - "Response", - member, - "static", - "Response static constructor-object operations have no compiler lowering", - ), - ), - unsupportedEntry("stdlib.response.type", "Response", "type", "prototype", widerMemberFence), - ...["url", "redirected", "status", "ok", "statusText", "headers", "body", "bodyUsed"].map( - (member) => staticEntry(`stdlib.response.${member}`, "Response", member, "prototype"), - ), - staticEntry("stdlib.response.arrayBuffer", "Response", "arrayBuffer", "prototype"), - ...["clone", "blob", "formData"].map((member) => - unsupportedEntry( - `stdlib.response.${member}`, - "Response", - member, - "prototype", - "the native fetch runtime does not implement this Response operation", - ), - ), - ...["text", "json"].map((member) => - staticEntry(`stdlib.response.${member}`, "Response", member, "prototype"), - ), - staticEntry("stdlib.response.bytes", "Response", "bytes", "prototype"), - outOfScopeEntry( - "stdlib.response.symbol.toStringTag", "Response", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - - staticEntry( - "stdlib.readable-stream.constructor", "ReadableStream", - "constructor", - "constructor", - ), - staticEntry("stdlib.readable-stream.from", "ReadableStream", "from", "static"), - staticEntry("stdlib.readable-stream.locked", "ReadableStream", "locked", "prototype"), - staticEntry("stdlib.readable-stream.cancel", "ReadableStream", "cancel", "prototype"), - staticEntry("stdlib.readable-stream.get-reader", "ReadableStream", "getReader", "prototype"), - ...["pipeThrough", "values"].map((member) => - unsupportedEntry( - `stdlib.readable-stream.${member}`, - "ReadableStream", - member, - "prototype", - member === "values" - ? "direct for-await consumption compiles statically; storing or driving the first-class iterator handle is unsupported" - : "the wider Web Streams graph is outside the native readable-stream slice", + "ReadableStreamDefaultReader", + "ReadableStreamDefaultController", + ], + entries: [ + staticEntry("stdlib.fetch", "globalThis", "fetch", "global"), + + staticEntry( + "stdlib.abort-controller.constructor", + "AbortController", + "constructor", + "constructor", ), - ), - ...["pipeTo", "tee"].map((member) => - unsupportedEntry( - `stdlib.readable-stream.${member}`, - "ReadableStream", - member, - "prototype", - "this operation is outside the native readable-stream implementation", + staticEntry("stdlib.abort-controller.signal", "AbortController", "signal", "prototype"), + staticEntry("stdlib.abort-controller.abort", "AbortController", "abort", "prototype"), + outOfScopeEntry( + "stdlib.abort-controller.symbol.toStringTag", + "AbortController", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, ), - ), - unsupportedEntry( - "stdlib.readable-stream.symbol.asyncIterator", - "ReadableStream", - "[Symbol.asyncIterator]", - "prototype-symbol", - "direct for-await consumption compiles statically; explicit symbol access and stored iterator handles have no compiler lowering", - ), - outOfScopeEntry( - "stdlib.readable-stream.symbol.toStringTag", - "ReadableStream", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), - unsupportedEntry( - "stdlib.readable-stream-default-reader.constructor", - "ReadableStreamDefaultReader", - "constructor", - "constructor", - "reader handles come from ReadableStream.getReader(); the constructor object is unsupported", - ), - staticEntry( - "stdlib.readable-stream-default-reader.read", - "ReadableStreamDefaultReader", - "read", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-reader.release-lock", - "ReadableStreamDefaultReader", - "releaseLock", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-reader.closed", - "ReadableStreamDefaultReader", - "closed", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-reader.cancel", - "ReadableStreamDefaultReader", - "cancel", - "prototype", - ), - outOfScopeEntry( - "stdlib.readable-stream-default-reader.symbol.toStringTag", - "ReadableStreamDefaultReader", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), + unsupportedEntry( + "stdlib.abort-signal.constructor", + "AbortSignal", + "constructor", + "constructor", + "Node exposes the interface object but constructing it throws; the compiler does not preserve that constructor behavior", + ), + staticEntry("stdlib.abort-signal.abort", "AbortSignal", "abort", "static"), + staticEntry("stdlib.abort-signal.timeout", "AbortSignal", "timeout", "static"), + staticEntry("stdlib.abort-signal.any", "AbortSignal", "any", "static"), + staticEntry("stdlib.abort-signal.aborted", "AbortSignal", "aborted", "prototype"), + staticEntry("stdlib.abort-signal.reason", "AbortSignal", "reason", "prototype"), + staticEntry( + "stdlib.abort-signal.throw-if-aborted", + "AbortSignal", + "throwIfAborted", + "prototype", + ), + staticEntry("stdlib.abort-signal.onabort", "AbortSignal", "onabort", "prototype"), + staticEntry( + "stdlib.abort-signal.add-event-listener", + "AbortSignal", + "addEventListener", + "prototype-inherited", + ), + staticEntry( + "stdlib.abort-signal.remove-event-listener", + "AbortSignal", + "removeEventListener", + "prototype-inherited", + ), + staticEntry( + "stdlib.abort-signal.dispatch-event", + "AbortSignal", + "dispatchEvent", + "prototype-inherited", + ), + outOfScopeEntry( + "stdlib.abort-signal.symbol.toStringTag", + "AbortSignal", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), - unsupportedEntry( - "stdlib.readable-stream-default-controller.constructor", - "ReadableStreamDefaultController", - "constructor", - "constructor", - "controller handles come from underlying-source callbacks; the constructor object is unsupported", - ), - staticEntry( - "stdlib.readable-stream-default-controller.desired-size", - "ReadableStreamDefaultController", - "desiredSize", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-controller.close", - "ReadableStreamDefaultController", - "close", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-controller.enqueue", - "ReadableStreamDefaultController", - "enqueue", - "prototype", - ), - staticEntry( - "stdlib.readable-stream-default-controller.error", - "ReadableStreamDefaultController", - "error", - "prototype", - ), - outOfScopeEntry( - "stdlib.readable-stream-default-controller.symbol.toStringTag", - "ReadableStreamDefaultController", - "[Symbol.toStringTag]", - "prototype-symbol", - metadataExclusion, - ), + staticEntry("stdlib.headers.constructor", "Headers", "constructor", "constructor"), + ...["append", "delete", "get", "has", "set", "getSetCookie"].map((member) => + staticEntry(`stdlib.headers.${member}`, "Headers", member, "prototype"), + ), + ...["keys", "values", "entries"].map((member) => + unsupportedEntry( + `stdlib.headers.${member}`, + "Headers", + member, + "prototype", + "native Headers iteration does not yet expose a static iterator handle", + ), + ), + staticEntry("stdlib.headers.forEach", "Headers", "forEach", "prototype"), + unsupportedEntry( + "stdlib.headers.symbol.iterator", + "Headers", + "[Symbol.iterator]", + "prototype-symbol", + "native Headers iteration does not expose an engine-free iterator handle", + ), + outOfScopeEntry( + "stdlib.headers.symbol.toStringTag", + "Headers", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), - ...[ - "body", - "cache", - "credentials", - "dispatcher", - "duplex", - "headers", - "integrity", - "keepalive", - "method", - "mode", - "priority", - "redirect", - "referrer", - "referrerPolicy", - "signal", - "window", - ].map((member) => { - const id = `stdlib.fetch.request-init.${member}`; - const supported = new Set(["body", "duplex", "headers", "method", "redirect", "signal"]); - return supported.has(member) - ? staticEntry(id, "RequestInit", member, "dictionary") - : member === "dispatcher" + staticEntry("stdlib.request.constructor", "Request", "constructor", "constructor"), + ...[ + "method", + "url", + "headers", + "destination", + "referrer", + "referrerPolicy", + "mode", + "credentials", + "cache", + "redirect", + "integrity", + "keepalive", + "isReloadNavigation", + "isHistoryNavigation", + "signal", + "body", + "bodyUsed", + "duplex", + "clone", + "blob", + "arrayBuffer", + "text", + "json", + "formData", + "bytes", + "attribute", + ].map((member) => + ["clone", "blob", "formData", "attribute"].includes(member) ? unsupportedEntry( - id, - "RequestInit", + `stdlib.request.${member}`, + "Request", member, - "dictionary", - "custom dispatchers are unsupported; native environment-proxy routing uses NODE_USE_ENV_PROXY", + "prototype", + typedInterfaceUnsupported, ) - : unsupportedEntry( - id, - "RequestInit", - member, - "dictionary", - "the compiler does not preserve this RequestInit member's conversion or transport behavior", - ); - }), - ...["headers", "status", "statusText"].map((member) => - staticEntry(`stdlib.response-init.${member}`, "ResponseInit", member, "dictionary"), - ), - ], - excludedInterfaces: [ - { - name: "Blob/File/FormData bodies", - reason: - "fetch request and response bodies do not accept or produce Blob, File, or FormData values", - }, - { - name: "WritableStream/TransformStream", - reason: - "native support covers readable fetch bodies and the identity `new TransformStream()`; the WritableStream constructor, transformers, and general Web Streams graphs are unsupported", - }, - { - name: "Readable byte/BYOB streams", - reason: - "the native stream projection currently supports default readers and controllers only", - }, - { - name: "EventSource/WebSocket", - reason: "these are separate protocol clients, not fetch request/response compatibility", - }, - ], - }, -} satisfies FetchCompatProfile; + : staticEntry(`stdlib.request.${member}`, "Request", member, "prototype"), + ), + outOfScopeEntry( + "stdlib.request.symbol.toStringTag", + "Request", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), -export const STATIC_RESPONSE_READS = new Set(NODE24_FETCH_COMPAT_PROFILE.members.responseReads); + staticEntry("stdlib.response.constructor", "Response", "constructor", "constructor"), + staticEntry("stdlib.response.static.json", "Response", "json", "static"), + ...["error", "redirect"].map((member) => + unsupportedEntry( + `stdlib.response.static.${member}`, + "Response", + member, + "static", + "Response static constructor-object operations have no compiler lowering", + ), + ), + unsupportedEntry("stdlib.response.type", "Response", "type", "prototype", widerMemberFence), + ...["url", "redirected", "status", "ok", "statusText", "headers", "body", "bodyUsed"].map( + (member) => staticEntry(`stdlib.response.${member}`, "Response", member, "prototype"), + ), + staticEntry("stdlib.response.arrayBuffer", "Response", "arrayBuffer", "prototype"), + ...["clone", "blob", "formData"].map((member) => + unsupportedEntry( + `stdlib.response.${member}`, + "Response", + member, + "prototype", + "the native fetch runtime does not implement this Response operation", + ), + ), + ...["text", "json"].map((member) => + staticEntry(`stdlib.response.${member}`, "Response", member, "prototype"), + ), + staticEntry("stdlib.response.bytes", "Response", "bytes", "prototype"), + outOfScopeEntry( + "stdlib.response.symbol.toStringTag", + "Response", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), -export const STATIC_RESPONSE_CALLS = new Set(NODE24_FETCH_COMPAT_PROFILE.members.responseCalls); + staticEntry( + "stdlib.readable-stream.constructor", + "ReadableStream", + "constructor", + "constructor", + ), + staticEntry("stdlib.readable-stream.from", "ReadableStream", "from", "static"), + staticEntry("stdlib.readable-stream.locked", "ReadableStream", "locked", "prototype"), + staticEntry("stdlib.readable-stream.cancel", "ReadableStream", "cancel", "prototype"), + staticEntry( + "stdlib.readable-stream.get-reader", + "ReadableStream", + "getReader", + "prototype", + ), + ...["pipeThrough", "values"].map((member) => + unsupportedEntry( + `stdlib.readable-stream.${member}`, + "ReadableStream", + member, + "prototype", + member === "values" + ? "direct for-await consumption compiles statically; storing or driving the first-class iterator handle is unsupported" + : "the wider Web Streams graph is outside the native readable-stream slice", + ), + ), + ...["pipeTo", "tee"].map((member) => + unsupportedEntry( + `stdlib.readable-stream.${member}`, + "ReadableStream", + member, + "prototype", + "this operation is outside the native readable-stream implementation", + ), + ), + unsupportedEntry( + "stdlib.readable-stream.symbol.asyncIterator", + "ReadableStream", + "[Symbol.asyncIterator]", + "prototype-symbol", + "direct for-await consumption compiles statically; explicit symbol access and stored iterator handles have no compiler lowering", + ), + outOfScopeEntry( + "stdlib.readable-stream.symbol.toStringTag", + "ReadableStream", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), -export const STATIC_HEADERS_CALLS = new Set( - NODE24_FETCH_COMPAT_PROFILE.inventory.entries - .filter( - (entry) => - entry.owner === "Headers" && entry.placement === "prototype" && entry.status === "static", - ) - .map((entry) => entry.member), -); + unsupportedEntry( + "stdlib.readable-stream-default-reader.constructor", + "ReadableStreamDefaultReader", + "constructor", + "constructor", + "reader handles come from ReadableStream.getReader(); the constructor object is unsupported", + ), + staticEntry( + "stdlib.readable-stream-default-reader.read", + "ReadableStreamDefaultReader", + "read", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-reader.release-lock", + "ReadableStreamDefaultReader", + "releaseLock", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-reader.closed", + "ReadableStreamDefaultReader", + "closed", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-reader.cancel", + "ReadableStreamDefaultReader", + "cancel", + "prototype", + ), + outOfScopeEntry( + "stdlib.readable-stream-default-reader.symbol.toStringTag", + "ReadableStreamDefaultReader", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), -export const STATIC_READABLE_STREAM_READS = new Set( - NODE24_FETCH_COMPAT_PROFILE.members.readableStreamReads, -); + unsupportedEntry( + "stdlib.readable-stream-default-controller.constructor", + "ReadableStreamDefaultController", + "constructor", + "constructor", + "controller handles come from underlying-source callbacks; the constructor object is unsupported", + ), + staticEntry( + "stdlib.readable-stream-default-controller.desired-size", + "ReadableStreamDefaultController", + "desiredSize", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-controller.close", + "ReadableStreamDefaultController", + "close", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-controller.enqueue", + "ReadableStreamDefaultController", + "enqueue", + "prototype", + ), + staticEntry( + "stdlib.readable-stream-default-controller.error", + "ReadableStreamDefaultController", + "error", + "prototype", + ), + outOfScopeEntry( + "stdlib.readable-stream-default-controller.symbol.toStringTag", + "ReadableStreamDefaultController", + "[Symbol.toStringTag]", + "prototype-symbol", + metadataExclusion, + ), -export const STATIC_READABLE_STREAM_CALLS = new Set( - NODE24_FETCH_COMPAT_PROFILE.members.readableStreamCalls, -); + ...[ + "body", + "cache", + "credentials", + "dispatcher", + "duplex", + "headers", + "integrity", + "keepalive", + "method", + "mode", + "priority", + "redirect", + "referrer", + "referrerPolicy", + "signal", + "window", + ].map((member) => { + const id = `stdlib.fetch.request-init.${member}`; + const supported = new Set(["body", "duplex", "headers", "method", "redirect", "signal"]); + return supported.has(member) + ? staticEntry(id, "RequestInit", member, "dictionary") + : member === "dispatcher" + ? unsupportedEntry( + id, + "RequestInit", + member, + "dictionary", + "custom dispatchers are unsupported; native environment-proxy routing uses NODE_USE_ENV_PROXY", + ) + : unsupportedEntry( + id, + "RequestInit", + member, + "dictionary", + "the compiler does not preserve this RequestInit member's conversion or transport behavior", + ); + }), + ...["headers", "status", "statusText"].map((member) => + staticEntry(`stdlib.response-init.${member}`, "ResponseInit", member, "dictionary"), + ), + ], + excludedInterfaces: [ + { + name: "Blob/File/FormData bodies", + reason: + "fetch request and response bodies do not accept or produce Blob, File, or FormData values", + }, + { + name: "WritableStream/TransformStream", + reason: + "native support covers readable fetch bodies and the identity `new TransformStream()`; the WritableStream constructor, transformers, and general Web Streams graphs are unsupported", + }, + { + name: "Readable byte/BYOB streams", + reason: + "the native stream projection currently supports default readers and controllers only", + }, + { + name: "EventSource/WebSocket", + reason: "these are separate protocol clients, not fetch request/response compatibility", + }, + ], + }, + } satisfies FetchCompatProfile; +} + +let fetchCompatProfile: ReturnType | undefined; + +/** The Node 24 fetch compatibility profile. It is built on the first call, + * so runs that do not lower a module do not construct it. */ +export function node24FetchCompatProfile(): ReturnType { + fetchCompatProfile ??= buildFetchCompatProfile(); + return fetchCompatProfile; +} + +/** The members the native static fetch handles project. */ +export interface StaticFetchMembers { + responseReads: ReadonlySet; + responseCalls: ReadonlySet; + headersCalls: ReadonlySet; + readableStreamReads: ReadonlySet; + readableStreamCalls: ReadonlySet; +} + +let staticFetchMemberSets: StaticFetchMembers | undefined; + +/** The profile's static members as sets, built on the first call. */ +export function staticFetchMembers(): StaticFetchMembers { + if (staticFetchMemberSets !== undefined) return staticFetchMemberSets; + const profile = node24FetchCompatProfile(); + staticFetchMemberSets = { + responseReads: new Set(profile.members.responseReads), + responseCalls: new Set(profile.members.responseCalls), + headersCalls: new Set( + profile.inventory.entries + .filter( + (entry) => + entry.owner === "Headers" && + entry.placement === "prototype" && + entry.status === "static", + ) + .map((entry) => entry.member), + ), + readableStreamReads: new Set(profile.members.readableStreamReads), + readableStreamCalls: new Set(profile.members.readableStreamCalls), + }; + return staticFetchMemberSets; +} diff --git a/packages/compiler/src/coverage/surface-manifest.ts b/packages/compiler/src/coverage/surface-manifest.ts index 9867a5617..b87a8065d 100644 --- a/packages/compiler/src/coverage/surface-manifest.ts +++ b/packages/compiler/src/coverage/surface-manifest.ts @@ -39,14 +39,14 @@ import { InternalCompilerError } from "../errors.js"; * byte-deterministic: entries sort by id, keys are emitted in one fixed * order, and the output carries no timestamps or absolute paths. */ import { FENCE_CODES, UNSUPPORTED, webAssemblyDiag } from "../diagnostics/diagnostic.js"; -import { NODE24_FETCH_COMPAT_PROFILE } from "../compat/fetch-profile.js"; +import { node24FetchCompatProfile } from "../compat/fetch-profile.js"; import { SUPPORTED_BUILTIN_MODULES, SUPPORTED_NODE_MODULES } from "../frontend/builtin-modules.js"; import { - AMBIENT_SURFACE_FNS, + ambientSurfaceFns, ARRAY_METHODS, BUILTIN_MODULE_CONSTS, BUILTIN_MODULE_FENCE_HINTS, - BUILTIN_MODULE_FNS, + builtinModuleFns, COLLECTION_ITERATOR_METHODS, COMPOUND_ASSIGN_OPS, STDLIB_SURFACE, @@ -88,18 +88,21 @@ export const MANIFEST_SCHEMA_VERSION = 2; /** The manifest's own scope statement: what the projection covers and — * just as load-bearing — what it deliberately leaves out, so absence is * never read as a support claim in either direction. */ -const COVERAGE_NOTES: string[] = [ - "Entries are projected mechanically from the compiler's own decision tables: the diagnostics registry, the unsupported-syntax dispatch tables, the stdlib and node-builtin lowering tables, and the supported-builtin-module list. Nothing is hand-maintained; the manifest regenerates byte-identically from the source tree at this version.", - "Absence from this manifest means 'not projected', never 'unsupported'. Surfaces lowered through dedicated code paths rather than tables are not yet projected: console, JSON, Promise/async and the timer surface, the net/http/tls/https/dgram/dns/assert/test/stream/readline module member surfaces, template literals, the regex slice, global functions (parseInt, parseFloat, isNaN, isFinite), and the process surface outside its ambient slice.", - "The ambient-nondeterminism and ambient-authority surfaces the library sidecar's determinism attestation scans ARE projected even where they lower through dedicated code paths — the Date compositions (stdlib.date.*), perf_hooks' performance.now, and the process global's ambient reads and authority calls (node-builtin.process.*) — so a determinism fence can name every surface the attestation demotes on.", - "stdlib and node-builtin member entries name surface whose LOWERED call forms are constrained (arity, argument shapes); declared call forms outside the lowered set are refused per site, with code SC2020 for standard-library and node-builtin surface.", - "Entries with status 'unsupported' describe where the named code is raised: forms of the construct outside the supported subset are refused with that code — not that every form of the named feature is refused. Supported forms appear as their own static entries where a table projects them.", - `The engine-free fetch projection targets Node ${NODE24_FETCH_COMPAT_PROFILE.target.node} with bundled Undici ${NODE24_FETCH_COMPAT_PROFILE.target.undici}. Each projected row names the differential evidence that guards it; changing the pinned Node or Undici version is an explicit profile update.`, - "The fetch profile also contains a runtime-reflected census of the selected fetch, abort, Headers, and readable-stream interfaces plus RequestInit/ResponseInit dictionary reads. Static and unsupported census rows are projected here; its explicitly out-of-scope metadata rows and adjacent-interface exclusions remain in the profile so absence is deliberate rather than ambiguous.", - "Process-level diagnostic codes are not surface entries: SC0001-SC0004 are preflight gates, SC1110 is a comptime evaluation failure, SC3001/SC3002 are backend/target tier refusals, SC9001/SC9002 are internal errors.", - "Entry statuses are projected for the desktop targets. The mobile targets (aarch64-apple-ios, aarch64-apple-ios-simulator, aarch64-linux-android) compile library-mode archives only: the library-admissible surface (what SC4005's async_free requirement and the library link set admit) is supported there, the executable lane refuses those triples with SC3002, and no entry outside the library-admissible surface carries a mobile support claim. iOS archives build for iOS 15.0 on darwin hosts; Android archives build against NDK API level 26.", - "No scheduling metadata is published; entry ids are the stable diff keys across releases.", -]; +function coverageNotes(): string[] { + const fetchTarget = node24FetchCompatProfile().target; + return [ + "Entries are projected mechanically from the compiler's own decision tables: the diagnostics registry, the unsupported-syntax dispatch tables, the stdlib and node-builtin lowering tables, and the supported-builtin-module list. Nothing is hand-maintained; the manifest regenerates byte-identically from the source tree at this version.", + "Absence from this manifest means 'not projected', never 'unsupported'. Surfaces lowered through dedicated code paths rather than tables are not yet projected: console, JSON, Promise/async and the timer surface, the net/http/tls/https/dgram/dns/assert/test/stream/readline module member surfaces, template literals, the regex slice, global functions (parseInt, parseFloat, isNaN, isFinite), and the process surface outside its ambient slice.", + "The ambient-nondeterminism and ambient-authority surfaces the library sidecar's determinism attestation scans ARE projected even where they lower through dedicated code paths — the Date compositions (stdlib.date.*), perf_hooks' performance.now, and the process global's ambient reads and authority calls (node-builtin.process.*) — so a determinism fence can name every surface the attestation demotes on.", + "stdlib and node-builtin member entries name surface whose LOWERED call forms are constrained (arity, argument shapes); declared call forms outside the lowered set are refused per site, with code SC2020 for standard-library and node-builtin surface.", + "Entries with status 'unsupported' describe where the named code is raised: forms of the construct outside the supported subset are refused with that code — not that every form of the named feature is refused. Supported forms appear as their own static entries where a table projects them.", + `The engine-free fetch projection targets Node ${fetchTarget.node} with bundled Undici ${fetchTarget.undici}. Each projected row names the differential evidence that guards it; changing the pinned Node or Undici version is an explicit profile update.`, + "The fetch profile also contains a runtime-reflected census of the selected fetch, abort, Headers, and readable-stream interfaces plus RequestInit/ResponseInit dictionary reads. Static and unsupported census rows are projected here; its explicitly out-of-scope metadata rows and adjacent-interface exclusions remain in the profile so absence is deliberate rather than ambiguous.", + "Process-level diagnostic codes are not surface entries: SC0001-SC0004 are preflight gates, SC1110 is a comptime evaluation failure, SC3001/SC3002 are backend/target tier refusals, SC9001/SC9002 are internal errors.", + "Entry statuses are projected for the desktop targets. The mobile targets (aarch64-apple-ios, aarch64-apple-ios-simulator, aarch64-linux-android) compile library-mode archives only: the library-admissible surface (what SC4005's async_free requirement and the library link set admit) is supported there, the executable lane refuses those triples with SC3002, and no entry outside the library-admissible surface carries a mobile support claim. iOS archives build for iOS 15.0 on darwin hosts; Android archives build against NDK API level 26.", + "No scheduling metadata is published; entry ids are the stable diff keys across releases.", + ]; +} /** Stable kebab id segment from prose: quotes dropped, the parenthetical * tail dropped, non-alphanumeric runs collapsed to one dash. */ @@ -410,7 +413,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes : "recognized module (bare and node:-prefixed specifiers)", }); } - for (const [mod, members] of Object.entries(BUILTIN_MODULE_FNS)) { + for (const [mod, members] of Object.entries(builtinModuleFns())) { for (const member of Object.keys(members!)) { add({ id: `${moduleId(mod)}.${member}`, @@ -459,10 +462,9 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes } // ── fetch/Web platform: one explicit, versioned compatibility profile ─ - const fetchTarget = - `Node ${NODE24_FETCH_COMPAT_PROFILE.target.node} / ` + - `Undici ${NODE24_FETCH_COMPAT_PROFILE.target.undici}`; - for (const operation of NODE24_FETCH_COMPAT_PROFILE.operations) { + const fetchProfile = node24FetchCompatProfile(); + const fetchTarget = `Node ${fetchProfile.target.node} / Undici ${fetchProfile.target.undici}`; + for (const operation of fetchProfile.operations) { const evidence = operation.evidence.map((item) => item.generated !== undefined ? `generated:${item.generated}` : `fixture:${item.fixture!}`, ); @@ -478,10 +480,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes `differential evidence: ${evidence.join(", ")}`, }); } - for (const options of [ - NODE24_FETCH_COMPAT_PROFILE.requestInit, - NODE24_FETCH_COMPAT_PROFILE.responseInit, - ]) { + for (const options of [fetchProfile.requestInit, fetchProfile.responseInit]) { for (const option of options) { const evidence = option.evidence.map((item) => item.generated !== undefined ? `generated:${item.generated}` : `fixture:${item.fixture!}`, @@ -497,7 +496,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes }); } } - for (const row of NODE24_FETCH_COMPAT_PROFILE.inventory.entries) { + for (const row of fetchProfile.inventory.entries) { if (row.status !== "unsupported") continue; if (row.code === undefined || row.reason === undefined) { throw new InternalCompilerError(`non-static fetch inventory row '${row.id}' is incomplete`); @@ -524,7 +523,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes // Date/perf_hooks/process rows from the one ambient table — each entry // is fenceable by construction (its row carries the reach-witnessing // libCall spellings the fence detector uses). - for (const row of AMBIENT_SURFACE_FNS) { + for (const row of ambientSurfaceFns()) { add({ id: row.id, kind: row.kind, @@ -578,7 +577,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes return { schemaVersion: MANIFEST_SCHEMA_VERSION, compilerVersion, - coverage: COVERAGE_NOTES, + coverage: coverageNotes(), entries, }; } diff --git a/packages/compiler/src/frontend/lowering/lib-boundary.ts b/packages/compiler/src/frontend/lowering/lib-boundary.ts index 5be10e997..8a264d346 100644 --- a/packages/compiler/src/frontend/lowering/lib-boundary.ts +++ b/packages/compiler/src/frontend/lowering/lib-boundary.ts @@ -57,7 +57,7 @@ import { isUnitType, typeEquals, } from "../../ir/ir.js"; -import { LIB_FN_SIGS } from "../../ir/builtin-signatures.js"; +import { libFnSignatures } from "../../ir/builtin-signatures.js"; import { REGEX_INTRINSIC_SIGS, STR_INTRINSIC_SIGS } from "../../ir/intrinsic-signatures.js"; import { unionMismatchDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js"; @@ -162,7 +162,7 @@ function enforceExprBoundary(lowerer: Lowerer, node: IrExpr): void { const kind = node.kind; if (kind === "libCall") { const e = node; - const sig = LIB_FN_SIGS[e.fn]; + const sig = libFnSignatures()[e.fn]; if (!sig) return; e.args.forEach((a, i) => { const want = sig.argTypes[i]; diff --git a/packages/compiler/src/frontend/lowering/lower-builtin-values.ts b/packages/compiler/src/frontend/lowering/lower-builtin-values.ts index e5ebc6d8e..d59b234d3 100644 --- a/packages/compiler/src/frontend/lowering/lower-builtin-values.ts +++ b/packages/compiler/src/frontend/lowering/lower-builtin-values.ts @@ -16,7 +16,7 @@ import { strLit, varRef } from "../../ir/build.js"; import type { Lowerer } from "./lowerer.js"; import { BUILTIN_MODULE_CONSTS, - BUILTIN_MODULE_FNS, + builtinModuleFns, OBJECT_CALLABLE_VALUES, builtinConstLit, builtinModuleConstOf, @@ -430,7 +430,7 @@ export function pathModuleValue(lowerer: Lowerer, module: string, loc: SrcLoc): const add = (member: string, value: IrExpr): void => { fields.push({ key: strLit(member, loc), value: box(value) }); }; - for (const member of Object.keys(BUILTIN_MODULE_FNS[module] ?? {})) { + for (const member of Object.keys(builtinModuleFns()[module] ?? {})) { const value = lowerer.lowerBuiltinCallableValue({ module, member }, loc); if (value) add(member, value); } @@ -512,7 +512,7 @@ function moduleValue(lowerer: Lowerer, module: string, loc: SrcLoc): IrExpr { loc, }); }; - for (const member of Object.keys(BUILTIN_MODULE_FNS[module] ?? {})) { + for (const member of Object.keys(builtinModuleFns()[module] ?? {})) { const callable = lowerer.lowerBuiltinCallableValue({ module, member }, loc); if (callable) add(member, callable); } diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index fc0fd338f..fe748bb93 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -35,14 +35,7 @@ import { esmNamedImportLinkCrash, isCjsJsFile, locOf, npmPackageNameOf } from ". import { foldedStringKeyOf, lowerDynObjectLiteral } from "./expressions/object-literals.js"; import { noteEventListeners } from "./lower-event-target.js"; import { PoisonError, newFnCtx, own } from "./lowerer.js"; -import { - NODE24_FETCH_COMPAT_PROFILE, - STATIC_HEADERS_CALLS, - STATIC_READABLE_STREAM_CALLS, - STATIC_READABLE_STREAM_READS, - STATIC_RESPONSE_CALLS, - STATIC_RESPONSE_READS, -} from "../../compat/fetch-profile.js"; +import { node24FetchCompatProfile, staticFetchMembers } from "../../compat/fetch-profile.js"; import { classGeneratorIterable } from "./class-iterables.js"; import { generatorDrain } from "./iterator-adapters.js"; @@ -434,7 +427,7 @@ function fenceRequestInitObject( : contextual ? [contextual] : []; - const rows = NODE24_FETCH_COMPAT_PROFILE.inventory.entries.filter( + const rows = node24FetchCompatProfile().inventory.entries.filter( (entry) => entry.owner === "RequestInit" && entry.placement === "dictionary", ); const shadowed = new Set(); @@ -837,7 +830,7 @@ function fetchInventoryStatus( placement: "static" | "prototype" | "prototype-symbol", ): "static" | "unsupported" | "out-of-scope" | null { return ( - NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find( + node24FetchCompatProfile().inventory.entries.find( (entry) => entry.owner === owner && entry.member === member && entry.placement === placement, )?.status ?? null ); @@ -848,7 +841,7 @@ function fetchInventoryReason( member: string, placement: "static" | "prototype" | "prototype-symbol", ): string | undefined { - return NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find( + return node24FetchCompatProfile().inventory.entries.find( (entry) => entry.owner === owner && entry.member === member && entry.placement === placement, )?.reason; } @@ -951,8 +944,9 @@ export function fenceStaticResponseMember( member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype", ); + const projected = staticFetchMembers(); const supported = (member: string) => - use === "call" ? STATIC_RESPONSE_CALLS.has(member) : STATIC_RESPONSE_READS.has(member); + use === "call" ? projected.responseCalls.has(member) : projected.responseReads.has(member); if (members.every(supported)) return null; const member = members.find((candidate) => !supported(candidate))!; if (status(member) === "unsupported") { @@ -993,12 +987,9 @@ export function fenceStaticHeadersMember( member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype", ); - if (use === "call" && members.every((member) => STATIC_HEADERS_CALLS.has(member))) { - return null; - } - const member = members.find( - (candidate) => use !== "call" || !STATIC_HEADERS_CALLS.has(candidate), - )!; + const headersCalls = staticFetchMembers().headersCalls; + if (use === "call" && members.every((member) => headersCalls.has(member))) return null; + const member = members.find((candidate) => use !== "call" || !headersCalls.has(candidate))!; if (status(member) === "unsupported") { lowerer.noLowering( `Headers.${member}`, @@ -1108,7 +1099,7 @@ function fetchInterfaceInventoryOwner( const symbol = [type.getSymbol(), type.getAliasSymbol()].find( (candidate) => candidate !== undefined && - NODE24_FETCH_COMPAT_PROFILE.inventory.interfaces.includes(ts.symbolName(candidate)) && + node24FetchCompatProfile().inventory.interfaces.includes(ts.symbolName(candidate)) && lowerer.isStdlibSymbol(candidate), ); return symbol ? { owner: ts.symbolName(symbol), symbol } : null; @@ -1123,7 +1114,7 @@ function fenceFetchObjectMembers( if (!resolved) return; const { owner, symbol } = resolved; for (const { member, node } of members) { - const row = NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find( + const row = node24FetchCompatProfile().inventory.entries.find( (entry) => entry.owner === owner && entry.member === member && @@ -1136,7 +1127,7 @@ function fenceFetchObjectMembers( // Static data properties retain the ordinary checked-dynamic keyed-read // lowering. Only a method extraction loses the receiver that its direct // call bridge would preserve; non-static rows keep their inventory fence. - const operation = NODE24_FETCH_COMPAT_PROFILE.operations.find( + const operation = node24FetchCompatProfile().operations.find( (candidate) => candidate.id === row.id, ); if (row.status === "static" && operation?.kind === "property") { @@ -1235,7 +1226,7 @@ export function lowerFetchElementMethodCall( return null; } const rows = members.map((member) => - NODE24_FETCH_COMPAT_PROFILE.inventory.entries.find( + node24FetchCompatProfile().inventory.entries.find( (entry) => entry.owner === resolved.owner && entry.member === member && @@ -1397,10 +1388,11 @@ export function fenceStaticReadableStreamMember( member.startsWith("[Symbol.") ? "prototype-symbol" : "prototype", ); + const projected = staticFetchMembers(); const supported = (member: string) => use === "call" - ? STATIC_READABLE_STREAM_CALLS.has(member) - : STATIC_READABLE_STREAM_READS.has(member); + ? projected.readableStreamCalls.has(member) + : projected.readableStreamReads.has(member); if (members.every(supported)) return null; const member = members.find((candidate) => !supported(candidate))!; if (status(member) === "unsupported") { diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 0303d34d7..b5051d79d 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -1129,921 +1129,940 @@ export const UTIL_TYPES_KEYS: readonly string[] = [ "isCryptoKey", ]; -export const BUILTIN_MODULE_FNS: Record< - string, - Record | undefined -> = { - fs: { - readFileSync: { fn: "fs.readFileSync", params: [STRING, STRING], result: STRING }, - // The exact-API subset is first-class. A fixed native ABI is not enough: - // rows whose Node function has behavior-bearing trailing options remain - // call-only, or function-width adaptation could discard those options - // without a diagnostic before entering the fixed runtime call. - writeFileSync: { fn: "fs.writeFileSync", params: [STRING, STRING], result: VOID }, - appendFileSync: { fn: "fs.appendFileSync", params: [STRING, STRING], result: VOID }, - existsSync: { - fn: "fs.existsSync", - params: [STRING], - result: BOOL, - valueParams: exactValueParams(STRING), - }, - mkdirSync: { fn: "fs.mkdirSync", params: [STRING], result: VOID }, - rmSync: { fn: "fs.rmSync", params: [STRING], result: VOID }, - rmdirSync: { fn: "fs.rmdirSync", params: [STRING], result: VOID }, - readdirSync: { fn: "fs.readdirSync", params: [STRING], result: arrayOf(STRING) }, - // Pattern and options are checked by the runtime (lowerFsGlobSyncCall). - globSync: { fn: "fs.globSync", params: [DYN, DYN], result: arrayOf(STRING) }, - statSync: { fn: "fs.statSync", params: [STRING], result: STATS_T }, - mkdtempSync: { fn: "fs.mkdtempSync", params: [STRING], result: STRING }, - // accessSync's omitted mode completes to 0 (F_OK) in the special case - // in lowerBuiltinModuleCall — string `defaults` can't spell a number. - accessSync: { fn: "fs.accessSync", params: [STRING, F64], result: VOID }, - unlinkSync: { - fn: "fs.unlinkSync", - params: [STRING], - result: VOID, - valueParams: exactValueParams(STRING), - }, - chmodSync: { - fn: "fs.chmodSync", - params: [STRING, F64], - result: VOID, - valueParams: exactValueParams(STRING, F64), - }, - chownSync: { - fn: "fs.chownSync", - params: [STRING, F64, F64], - result: VOID, - valueParams: exactValueParams(STRING, F64, F64), - }, - lchownSync: { - fn: "fs.lchownSync", - params: [STRING, F64, F64], - result: VOID, - valueParams: exactValueParams(STRING, F64, F64), - }, - fchownSync: { - fn: "fs.fchownSync", - params: [F64, F64, F64], - result: VOID, - valueParams: exactValueParams(F64, F64, F64), - }, - // truncateSync's omitted len completes to 0 in lowerBuiltinModuleCall, - // like accessSync's mode. - truncateSync: { fn: "fs.truncateSync", params: [STRING, F64], result: VOID }, - // The 2-argument form only: Node's mode flags (COPYFILE_EXCL, ...) - // land on the arity fence. - copyFileSync: { fn: "fs.copyFileSync", params: [STRING, STRING], result: VOID }, - // The callback form is special-cased in lowerBuiltinModuleCall: its - // Error | null parameter is program-shaped and needs an emitted - // adapter. The row still projects the static surface and routes the - // dispatch. - rename: { fn: "fs.renameCb", params: [], result: VOID }, - renameSync: { - fn: "fs.renameSync", - params: [STRING, STRING], - result: VOID, - valueParams: exactValueParams(STRING, STRING), - }, - // statSync's no-follow sibling; stats.isSymbolicLink answers what the - // follow-free snapshot saw. - // opendirSync's options (a literal { bufferSize, encoding: "utf8" }) - // lower in lowerBuiltinModuleCall; the row routes the dispatch. - opendirSync: { fn: "fs.opendirSync", params: [STRING, F64], result: DIR_T }, - // createReadStream/createWriteStream lower in lowerBuiltinModuleCall - // (path and options cross as dyn values); the rows route the dispatch. - createReadStream: { - fn: "fs.createReadStream", - params: [DYN, DYN], - result: { kind: "object", className: "%Readable" }, - }, - createWriteStream: { - fn: "fs.createWriteStream", - params: [DYN, DYN], - result: { kind: "object", className: "%Writable" }, - }, - lstatSync: { fn: "fs.lstatSync", params: [STRING], result: STATS_T }, - fstatSync: { - fn: "fs.fstatSync", - params: [F64], - result: STATS_T, - valueParams: exactValueParams(F64), - }, - fchmodSync: { - fn: "fs.fchmodSync", - params: [F64, F64], - result: VOID, - valueParams: exactValueParams(F64, F64), - }, - fsyncSync: { - fn: "fs.fsyncSync", - params: [F64], - result: VOID, - valueParams: exactValueParams(F64), - }, - fdatasyncSync: { - fn: "fs.fdatasyncSync", - params: [F64], - result: VOID, - valueParams: exactValueParams(F64), - }, - ftruncateSync: { fn: "fs.ftruncateSync", params: [F64, F64], result: VOID }, - utimesSync: { fn: "fs.utimesSync", params: [DYN, DYN, DYN], result: VOID }, - futimesSync: { fn: "fs.futimesSync", params: [DYN, DYN, DYN], result: VOID }, - lutimesSync: { fn: "fs.lutimesSync", params: [DYN, DYN, DYN], result: VOID }, - readvSync: { fn: "fs.readvSync", params: [F64, arrayOf(BYTES_U8), F64], result: F64 }, - writevSync: { fn: "fs.writevSync", params: [F64, arrayOf(BYTES_U8), F64], result: F64 }, - linkSync: { - fn: "fs.linkSync", - params: [DYN, DYN], - result: VOID, - valueParams: exactValueParams(DYN, DYN), - }, - // Options and paths are checked by the runtime (scr_fs_cp_begin). - cpSync: { - fn: "fs.cpSync", - params: [DYN, DYN, DYN], - result: VOID, - valueParams: [ - ...exactValueParams(DYN, DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - symlinkSync: { - fn: "fs.symlinkSync", - params: [DYN, DYN, DYN], - result: VOID, - valueParams: [ - ...exactValueParams(DYN, DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - readlinkSync: { - fn: "fs.readlinkSync", - params: [DYN, DYN], - result: DYN, - valueParams: [ - ...exactValueParams(DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - statfsSync: { - fn: "fs.statfsSync", - params: [DYN, DYN], - result: DYN, - valueParams: [ - ...exactValueParams(DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - // realpath(3) — Node's realpathSync (failures spell syscall "lstat", - // Node's own message shape). - realpathSync: { fn: "fs.realpathSync", params: [STRING], result: STRING }, - "realpathSync.native": { fn: "fs.realpathNativeSync", params: [STRING], result: STRING }, - // The fd pair behind spawn's fd-stdio form (the daemon-log idiom: - // openSync(logPath, "a") → spawn stdio ["ignore", fd, fd] → - // closeSync). String flags use the established two-argument path; - // inline numeric O_* expressions route through openNumericSync, with - // an optional creation mode as the third argument. - openSync: { fn: "fs.openSync", params: [STRING, STRING], result: F64 }, - // The buffer forms (fd, buffer, offset, length[, position]). The - // lowering completes an omitted or literal-null position to -1 (the - // runtime's current-offset sentinel); numeric positions read without - // advancing the fd. The options-object and bigint forms still fence. - readSync: { fn: "fs.readSync", params: [F64, BYTES_U8, F64, F64, F64], result: F64 }, - // Entirely special-cased: the classic Buffer window and utf8 string - // overloads lower to separate fixed-width runtime ABIs. As with - // readSync, -1 is the current-offset sentinel. - writeSync: { fn: "fs.writeSync", params: [F64, BYTES_U8, F64, F64, F64], result: F64 }, - closeSync: { - fn: "fs.closeSync", - params: [F64], - result: VOID, - valueParams: exactValueParams(F64), - }, - // Entirely special-cased (lowerFsWatchCall — the callback needs an - // adapter per listener shape); this entry only routes the dispatch. - watch: { fn: "fs.watch", params: [], result: VOID }, - }, - "fs/promises": { - // The sync operations behind already-settled promises: failures - // REJECT (catchable at the await) instead of throwing. readFile is - // utf8-fenced exactly like readFileSync (same special case below). - readFile: { - fn: "fsp.readFile", - params: [STRING, STRING], - result: { kind: "promise", inner: STRING }, - }, - writeFile: { - fn: "fsp.writeFile", - params: [STRING, STRING], - result: { kind: "promise", inner: VOID }, - }, - mkdir: { fn: "fsp.mkdir", params: [STRING], result: { kind: "promise", inner: VOID } }, - readdir: { - fn: "fsp.readdir", - params: [STRING], - result: { kind: "promise", inner: arrayOf(STRING) }, - }, - rm: { fn: "fsp.rm", params: [STRING], result: { kind: "promise", inner: VOID } }, - // The async iterator lowers only as a for await head (lowerFspGlobCall). - glob: { fn: "fsp.globStrings", params: [DYN, DYN], result: arrayOf(STRING) }, - stat: { fn: "fsp.stat", params: [STRING], result: { kind: "promise", inner: STATS_T } }, - realpath: { fn: "fsp.realpath", params: [STRING], result: { kind: "promise", inner: STRING } }, - lstat: { fn: "fsp.lstat", params: [STRING], result: { kind: "promise", inner: STATS_T } }, - opendir: { - fn: "fsp.opendir", - params: [STRING, F64], - result: { kind: "promise", inner: DIR_T }, - }, - unlink: { - fn: "fsp.unlink", - params: [STRING], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(STRING), - }, - chmod: { - fn: "fsp.chmod", - params: [STRING, F64], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(STRING, F64), - }, - chown: { - fn: "fsp.chown", - params: [STRING, F64, F64], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(STRING, F64, F64), - }, - lchown: { - fn: "fsp.lchown", - params: [STRING, F64, F64], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(STRING, F64, F64), - }, - // access' omitted mode and truncate's omitted len complete to 0 in - // lowerBuiltinModuleCall, like their sync spellings. - access: { fn: "fsp.access", params: [STRING, F64], result: { kind: "promise", inner: VOID } }, - truncate: { - fn: "fsp.truncate", - params: [STRING, F64], - result: { kind: "promise", inner: VOID }, - }, - // String data; Uint8Array/Buffer data routes to fsp.appendFileBytes in - // lowerBuiltinModuleCall. - appendFile: { - fn: "fsp.appendFile", - params: [STRING, STRING], - result: { kind: "promise", inner: VOID }, - }, - // The 2-argument form only, like copyFileSync. - copyFile: { - fn: "fsp.copyFile", - params: [STRING, STRING], - result: { kind: "promise", inner: VOID }, - }, - mkdtemp: { fn: "fsp.mkdtemp", params: [STRING], result: { kind: "promise", inner: STRING } }, - rmdir: { fn: "fsp.rmdir", params: [STRING], result: { kind: "promise", inner: VOID } }, - link: { - fn: "fsp.link", - params: [DYN, DYN], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(DYN, DYN), - }, - cp: { - fn: "fsp.cp", - params: [DYN, DYN, DYN], - result: { kind: "promise", inner: VOID }, - valueParams: [ - ...exactValueParams(DYN, DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - symlink: { - fn: "fsp.symlink", - params: [DYN, DYN, DYN], - result: { kind: "promise", inner: VOID }, - valueParams: [ - ...exactValueParams(DYN, DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - readlink: { - fn: "fsp.readlink", - params: [DYN, DYN], - result: { kind: "promise", inner: DYN }, - valueParams: [ - ...exactValueParams(DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - statfs: { - fn: "fsp.statfs", - params: [DYN, DYN], - result: { kind: "promise", inner: DYN }, - valueParams: [ - ...exactValueParams(DYN), - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - utimes: { fn: "fsp.utimes", params: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, - lutimes: { - fn: "fsp.lutimes", - params: [DYN, DYN, DYN], - result: { kind: "promise", inner: VOID }, - }, - rename: { - fn: "fsp.rename", - params: [STRING, STRING], - result: { kind: "promise", inner: VOID }, - valueParams: exactValueParams(STRING, STRING), - }, - // open's optional flags/mode completion is special-cased in - // lowerBuiltinModuleCall; this row routes all import spellings and - // gives coverage the static member. - open: { - fn: "fsp.open", - params: [STRING, STRING, F64], - result: { kind: "promise", inner: FILEHANDLE_T }, - valueParams: [ - { mode: "required", type: STRING }, - { mode: "optional", type: STRING, defaultValue: "r" }, - { mode: "optional", type: F64, defaultValue: 0o666 }, - ], - }, - }, - // The bare module's POSIX-target binding; a win32 target rebinds it to - // the win32 table (builtinModuleFnsOf — Node on Windows IS path.win32). - path: PATH_MODULE_FNS, - // Real Node modules ("node:path/posix" resolves), and the receivers of - // `path.posix.` / `path.win32.` through the namespace - // chain (builtinNamespaceModuleOf composes "path" + ".posix" to these - // keys). Both namespaces carry the FULL surface on every platform, - // Node's own rule: each answers its own platform's semantics anywhere. - "path/posix": PATH_MODULE_FNS, - "path/win32": PATH_WIN32_MODULE_FNS, - os: { - // One platform implementation: os.platform() === process.platform. - platform: { fn: "process.platform", params: [], result: STRING, valueParams: [] }, - homedir: { fn: "os.homedir", params: [], result: STRING, valueParams: [] }, - tmpdir: { fn: "os.tmpdir", params: [], result: STRING, valueParams: [] }, - // uname(2)'s release field — Node's own implementation. - release: { fn: "os.release", params: [], result: STRING, valueParams: [] }, - // uname(2)'s sysname field ("Darwin", "Linux", "Windows_NT") — the - // libFn/runtime pair predates this entry (the portless surface used - // them); this row makes the MODULE call reach them (test/common's - // isAIX/isIBMi getters are the canonical callers). - type: { fn: "os.type", params: [], result: STRING, valueParams: [] }, - // Total physical memory in bytes — same predating-pair story. - totalmem: { fn: "os.totalmem", params: [], result: F64, valueParams: [] }, - // uv_available_parallelism: the CPUs this process may use (affinity, - // then cgroup quota on Linux), at least 1. - availableParallelism: { - fn: "os.availableParallelism", - params: [], - result: F64, - valueParams: [], - }, - // os.arch() IS process.arch, like platform(). - arch: { fn: "process.arch", params: [], result: STRING, valueParams: [] }, - hostname: { fn: "os.hostname", params: [], result: STRING, valueParams: [] }, - // uv_os_uname's machine and version fields, read once at module load - // in Node (they never change while a process runs). - machine: { fn: "os.machine", params: [], result: STRING, valueParams: [] }, - version: { fn: "os.version", params: [], result: STRING, valueParams: [] }, - endianness: { fn: "os.endianness", params: [], result: STRING, valueParams: [] }, - freemem: { fn: "os.freemem", params: [], result: F64, valueParams: [] }, - uptime: { fn: "os.uptime", params: [], result: F64, valueParams: [] }, - loadavg: { fn: "os.loadavg", params: [], result: arrayOf(F64), valueParams: [] }, - // An omitted (or undefined) pid is 0, the current process - // (lowerOsGetPriorityCall completes it). - getPriority: { - fn: "os.getPriority", - params: [F64], - result: F64, - valueParams: [{ mode: "optional", type: F64, defaultValue: 0 }], - }, - // A one-argument call is setPriority(0, priority) - // (lowerOsSetPriorityCall completes it). - setPriority: { fn: "os.setPriority", params: [F64, F64], result: VOID }, - // Special-cased (lowerOsCpusCall): the call site's CpuInfo[] assembles - // from one uv_cpu_info snapshot. - cpus: { fn: "os.cpusTake", params: [], result: VOID }, - // Entirely special-cased (lowerOsNetworkInterfacesCall): the result is - // the call site's mapped Dict shape, verified - // structurally there — this entry only routes the dispatch. - networkInterfaces: { fn: "os.networkInterfaces", params: [], result: VOID }, - // Special-cased too (lowerOsUserInfoCall): the call site's mapped - // UserInfo record assembles field-by-field from scalar libCalls. - userInfo: { fn: "os.userName", params: [], result: VOID }, - }, - crypto: { - randomUUID: { - fn: "crypto.randomUUID", - params: [], - result: STRING, - // As a value (uuid's `export default { randomUUID }`), the optional - // options argument goes through the validating entry point. - valueFn: "crypto.randomUUIDOpts", - valueParams: [{ mode: "optional", type: DYN, defaultValue: undefined }], - }, - // A real Buffer result. The composed randomBytes(n).toString(enc) - // form KEEPS its one-libCall lowering (lowerCryptoComposedCall runs - // first and the Buffer never materializes there); this entry covers - // the bare calls and non-composed uses. - randomBytes: { - fn: "crypto.randomBytes", - params: [F64], - result: BYTES_U8, - valueParams: exactValueParams(F64), - }, - // The crypto utility surface is special-cased for overloads and - // string/Buffer splits in lowerCryptoModuleCall. These rows carry the - // canonical signatures for manifest projection and fallback dispatch. - createHash: { fn: "crypto.hashNew", params: [STRING], result: CRYPTOHASH_T }, - createHmac: { fn: "crypto.hmacNewStr", params: [STRING, STRING], result: CRYPTOHMAC_T }, - hash: { fn: "crypto.hashDigestStr", params: [STRING, STRING, STRING], result: STRING }, - timingSafeEqual: { fn: "crypto.timingSafeEqual", params: [BYTES_U8, BYTES_U8], result: BOOL }, - randomFillSync: { fn: "crypto.randomFill", params: [BYTES_U8, F64, F64], result: BYTES_U8 }, - getRandomValues: { fn: "crypto.getRandomValues", params: [BYTES_U8], result: BYTES_U8 }, - randomInt: { fn: "crypto.randomInt", params: [F64, F64], result: F64 }, - randomFill: { fn: "crypto.randomFillCb", params: [], result: VOID }, - randomUUIDv7: { - fn: "crypto.randomUUIDv7", - params: [DYN], - result: STRING, - valueParams: [{ mode: "optional", type: DYN, defaultValue: undefined }], - }, - createSecretKey: { fn: "crypto.createSecretKey", params: [DYN, DYN], result: DYN }, - generateKeySync: { fn: "crypto.generateKeySync", params: [DYN, DYN], result: DYN }, - generateKey: { fn: "crypto.generateKeyCb", params: [], result: VOID }, - // Asymmetric keys and signatures (lowerPkiCall): checked-dynamic - // arguments, results converted to the call site's type. - createPrivateKey: { fn: "crypto.createPrivateKey", params: [DYN], result: DYN }, - createPublicKey: { fn: "crypto.createPublicKey", params: [DYN], result: DYN }, - createSign: { fn: "crypto.createSign", params: [DYN], result: DYN }, - createVerify: { fn: "crypto.createVerify", params: [DYN], result: DYN }, - generateKeyPairSync: { fn: "crypto.generateKeyPairSync", params: [], result: DYN }, - generateKeyPair: { fn: "crypto.generateKeyPairCb", params: [], result: VOID }, - sign: { fn: "crypto.signOneshot", params: [], result: DYN }, - verify: { fn: "crypto.verifyOneshot", params: [], result: DYN }, - diffieHellman: { fn: "crypto.diffieHellman", params: [], result: DYN }, - pbkdf2Sync: { - fn: "crypto.pbkdf2", - params: [BYTES_U8, BYTES_U8, F64, F64, STRING], - result: BYTES_U8, - }, - pbkdf2: { fn: "crypto.pbkdf2Cb", params: [], result: VOID }, - hkdfSync: { - fn: "crypto.hkdf", - params: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], - result: DYN, - }, - hkdf: { fn: "crypto.hkdfCb", params: [], result: VOID }, - scryptSync: { fn: "crypto.scrypt", params: [BYTES_U8, BYTES_U8, F64, DYN], result: BYTES_U8 }, - scrypt: { fn: "crypto.scryptCb", params: [], result: VOID }, - // Checked-dynamic arguments; the result is a native Cipheriv or - // Decipheriv handle (lowerCryptoModuleCall). - createCipheriv: { fn: "crypto.cipherNew", params: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, - createDecipheriv: { fn: "crypto.cipherNew", params: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, - // A checked-dynamic curve name; the result is a native ECDH handle - // (lowerCreateEcdh). - createECDH: { fn: "crypto.ecdhNew", params: [DYN], result: DYN }, - // Checked-dynamic arguments; the result is a native DiffieHellman or - // DiffieHellmanGroup handle (lowerCreateDiffieHellman and - // lowerGetDiffieHellman). - createDiffieHellman: { fn: "crypto.dhNew", params: [DYN, DYN, DYN, DYN], result: DYN }, - getDiffieHellman: { fn: "crypto.dhGroupNew", params: [DYN], result: DYN }, - createDiffieHellmanGroup: { fn: "crypto.dhGroupNew", params: [DYN], result: DYN }, - // Prime checks and generation (lowerPrimeCall): checked-dynamic - // arguments, results converted to the call site's type. - checkPrimeSync: { fn: "crypto.checkPrimeSync", params: [], result: DYN }, - // Checked-dynamic arguments, the result converted to the call site's - // type (lowerPrimeCall). - getCipherInfo: { fn: "crypto.getCipherInfo", params: [], result: DYN }, - checkPrime: { fn: "crypto.checkPrimeCb", params: [], result: VOID }, - generatePrimeSync: { fn: "crypto.generatePrimeSync", params: [], result: DYN }, - generatePrime: { fn: "crypto.generatePrimeCb", params: [], result: VOID }, - }, - zlib: { - // The default-options convenience family is special-cased in - // lowerBuiltinModuleCall: string inputs UTF-8 encode, callback forms - // carry program-shaped closures, and explicit options fence per site. - // native-toolchain.ts links libz only when these appear on the IR. - deflate: { fn: "zlib.deflateCb", params: [], result: VOID }, - deflateSync: { fn: "zlib.deflateSync", params: [BYTES_U8], result: BYTES_U8 }, - inflate: { fn: "zlib.inflateCb", params: [], result: VOID }, - inflateSync: { fn: "zlib.inflateSync", params: [BYTES_U8], result: BYTES_U8 }, - deflateRaw: { fn: "zlib.deflateRawCb", params: [], result: VOID }, - deflateRawSync: { fn: "zlib.deflateRawSync", params: [BYTES_U8], result: BYTES_U8 }, - inflateRaw: { fn: "zlib.inflateRawCb", params: [], result: VOID }, - inflateRawSync: { fn: "zlib.inflateRawSync", params: [BYTES_U8], result: BYTES_U8 }, - gzip: { fn: "zlib.gzipCb", params: [], result: VOID }, - gzipSync: { fn: "zlib.gzipSync", params: [BYTES_U8], result: BYTES_U8 }, - gunzip: { fn: "zlib.gunzipCb", params: [], result: VOID }, - gunzipSync: { fn: "zlib.gunzipSync", params: [BYTES_U8], result: BYTES_U8 }, - unzip: { fn: "zlib.unzipCb", params: [], result: VOID }, - unzipSync: { fn: "zlib.unzipSync", params: [BYTES_U8], result: BYTES_U8 }, - crc32: { fn: "zlib.crc32", params: [], result: F64 }, - // Brotli (special-cased in lowerZlibModuleCall): every form takes the - // options entries with Node's BROTLI_ENCODE or BROTLI_DECODE mode. - brotliCompress: { fn: "zlib.cbOpts", params: [], result: VOID }, - brotliCompressSync: { fn: "zlib.syncOpts", params: [], result: BYTES_U8 }, - brotliDecompress: { fn: "zlib.cbOpts", params: [], result: VOID }, - brotliDecompressSync: { fn: "zlib.syncOpts", params: [], result: BYTES_U8 }, - // The stream factories (special-cased in lowerZlibModuleCall): each - // answers a runtime Transform stream over the zlib engine. - createDeflate: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createInflate: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createGzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createGunzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createDeflateRaw: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createInflateRaw: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createUnzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createBrotliCompress: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - createBrotliDecompress: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, - }, - url: { - fileURLToPath: { - fn: "url.fileURLToPathOptions", - params: [DYN, DYN], - result: STRING, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - fileURLToPathBuffer: { - fn: "url.fileURLToPathBuffer", - params: [DYN, DYN], - result: BYTES_U8, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - pathToFileURL: { - fn: "url.pathToFileURLChecked", - params: [DYN, DYN], - result: URL_T, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - // The legacy API and the domain pair (lowerLegacyUrlCall): arguments - // cross as dyn values for Node's own validators; url.parse() answers - // the checked-dynamic Url instance. - parse: { fn: "url.legacyParse", params: [DYN, DYN, DYN], result: DYN }, - format: { fn: "url.legacyFormat", params: [DYN, DYN], result: STRING }, - resolve: { fn: "url.legacyResolve", params: [DYN, DYN], result: STRING }, - domainToASCII: { fn: "url.domainToASCII", params: [DYN], result: STRING }, - domainToUnicode: { fn: "url.domainToUnicode", params: [DYN], result: STRING }, - urlToHttpOptions: { fn: "url.toHttpOptions", params: [DYN], result: DYN }, - }, - child_process: { - // spawnSync's and spawn's call completions are entirely special-cased - // (an omitted args list completes to an empty string[]; spawnSync - // accepts its documented literal options, while spawn supports the - // stdio/detached/env/cwd/windowsHide/shell subset — see - // lowerBuiltinModuleCall); the - // entries carry the canonical shapes. - spawnSync: { fn: "cp.spawnSync", params: [STRING, arrayOf(STRING)], result: SPAWNRES_T }, - spawn: { fn: "cp.spawn", params: [STRING, arrayOf(STRING)], result: CHILD_T }, - fork: { fn: "cp.fork", params: [STRING, arrayOf(STRING)], result: CHILD_T }, - // execFileSync(file, args?, options?) and execSync(command, options?) - // share the cp.execSync runtime entry (execSync sets the shell flag); - // both call completions are special-cased in lowerBuiltinModuleCall. - // The entries carry canonical shapes for the coverage/fence machinery. - execFileSync: { fn: "cp.execSync", params: [STRING, arrayOf(STRING)], result: STRING }, - execSync: { fn: "cp.execSync", params: [STRING], result: STRING }, - }, - // node:util lowers through dedicated paths: promisify - // only in the const-binding-over-execFile shape — recognized in - // lowerVarDecl / collectGlobals BEFORE any call lowering runs — and - // inspect/format/formatWithOptions plus parseArgs through the util spoke - // (lower-inspect.ts: per-type synthesized traversal helpers, - // compile-time format strings, checked-dynamic config/result for - // parseArgs), which both dispatch paths try before the generic table. - // parseArgs is tabled as well so the generated surface manifest records - // the dedicated static member; the spoke owns its optional arity and - // call-site-specific result type before this canonical signature is used. - util: { - styleText: { - fn: "util.styleText", - params: [DYN, DYN, DYN], - result: STRING, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - isDeepStrictEqual: { - fn: "util.isDeepStrictEqual", - params: [DYN, DYN, DYN], - result: BOOL, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - parseArgs: { fn: "util.parseArgs", params: [DYN], result: DYN }, - parseEnv: { fn: "util.parseEnv", params: [DYN], result: DYN }, - getSystemErrorName: { - fn: "util.getSystemErrorName", - params: [DYN], - result: STRING, - valueParams: exactValueParams(DYN), - }, - getSystemErrorMessage: { - fn: "util.getSystemErrorMessage", - params: [DYN], - result: STRING, - valueParams: exactValueParams(DYN), - }, - getSystemErrorMap: { fn: "util.systemErrorEntries", params: [], result: DYN }, - stripVTControlCharacters: { - fn: "util.stripVTControlCharacters", - params: [DYN], - result: STRING, - valueParams: exactValueParams(DYN), - }, - toUSVString: { - fn: "util.toUSVString", - params: [DYN], - result: STRING, - valueParams: exactValueParams(DYN), - }, - // The function helpers take checked-dynamic arguments and answer native - // dyn functions, which a static function slot checks when it receives - // one (lowerUtilCallableCall in the util spoke). - inherits: { - fn: "util.inherits", - params: [DYN, DYN], - result: VOID, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "required", type: DYN }, - ], - }, - deprecate: { - fn: "util.deprecate", - params: [DYN, DYN, DYN, DYN], - result: DYN, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - callbackify: { - fn: "util.callbackify", - params: [DYN], - result: DYN, - valueParams: [{ mode: "required", type: DYN }], - }, - debuglog: { - fn: "util.debuglog", - params: [DYN, DYN], - result: DYN, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - debug: { - fn: "util.debuglog", - params: [DYN, DYN], - result: DYN, - valueParams: [ - { mode: "required", type: DYN }, - { mode: "optional", type: DYN, defaultValue: undefined }, - ], - }, - }, - // The util/types spoke supplies the probe name to the shared brand check. - "util/types": { - isAnyArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isArrayBufferView: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isDataView: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isTypedArray: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isUint8Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isUint8ClampedArray: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isUint16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isUint32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isInt8Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isInt16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isInt32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isFloat32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isFloat64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isMap: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isSet: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isWeakMap: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isWeakSet: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isRegExp: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isNativeError: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isPromise: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isProxy: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isAsyncFunction: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isGeneratorFunction: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isGeneratorObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isDate: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isSharedArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isMapIterator: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isSetIterator: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isKeyObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isNumberObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isStringObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isBooleanObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isBigIntObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isSymbolObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isBoxedPrimitive: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isBigInt64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isBigUint64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isFloat16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isCryptoKey: { fn: "util.typeIs", params: [DYN], result: BOOL }, - isExternal: { fn: "util.typeIs", params: [DYN], result: BOOL }, - }, - buffer: { - isAscii: { fn: "buffer.isAscii", params: [DYN], result: BOOL }, - isUtf8: { fn: "buffer.isUtf8", params: [DYN], result: BOOL }, - transcode: { fn: "buffer.transcode", params: [DYN, DYN, DYN], result: BYTES_U8 }, - }, - tty: { - isatty: { fn: "process.isTTY", params: [F64], result: BOOL }, - }, - process: { - loadEnvFile: { fn: "process.loadEnvFile", params: [DYN], result: VOID }, - }, - // node:string_decoder's surface is the StringDecoder CLASS — new/write/ - // end are special-cased (lowerNew + lowerStringDecoderMethodCall); no - // function members exist to table. - string_decoder: {}, - // node:querystring — the legacy query-string codec (NOT URLSearchParams; - // the escaping and '+' rules differ). escape/unescape ride the table - // path directly; parse and stringify are special-cased in - // lowerBuiltinModuleCall (parse's result is the call site's mapped - // ParsedUrlQuery dictionary — the networkInterfaces verification stance - // — and its sep/eq/options complete there; stringify's object argument - // crosses as a dyn value). decode/encode are Node's own aliases of - // parse/stringify (`const decode = parse` in lib/querystring.js) and - // route to the same special cases; the entries carry canonical shapes. - querystring: { - parse: { fn: "qs.parse", params: [STRING], result: VOID }, - decode: { fn: "qs.parse", params: [STRING], result: VOID }, - stringify: { fn: "qs.stringify", params: [DYN], result: STRING }, - encode: { fn: "qs.stringify", params: [DYN], result: STRING }, - escape: { - fn: "qs.escape", - params: [STRING], - result: STRING, - valueParams: [{ mode: "required", type: STRING }], - }, - unescape: { - fn: "qs.unescape", - params: [STRING], - result: STRING, - valueParams: [{ mode: "required", type: STRING }], - }, - }, - // node:punycode (deprecated): punycode.js 2.1.0's conversions over - // strings (scr_url.c). ucs2.decode/encode are members of the module's - // ucs2 object, dispatched as module members (builtinMemberOf). The - // DEP0040 report rides the module's load sites (punycodeLoadWarning). - punycode: { - decode: { fn: "punycode.decode", params: [STRING], result: STRING }, - encode: { fn: "punycode.encode", params: [STRING], result: STRING }, - toASCII: { fn: "punycode.toASCII", params: [STRING], result: STRING }, - toUnicode: { fn: "punycode.toUnicode", params: [STRING], result: STRING }, - "ucs2.decode": { fn: "punycode.ucs2Decode", params: [STRING], result: arrayOf(F64) }, - "ucs2.encode": { fn: "punycode.ucs2Encode", params: [arrayOf(F64)], result: STRING }, - }, - // node:constants (deprecated) is one frozen object (Object.assign of - // os.constants' groups, fs.constants and crypto.constants): bindings of - // the module read it as a checked-dynamic value (builtinConstantsObject). - constants: {}, - // node:readline: createInterface's options are entirely special-cased - // (exactly { input: process.stdin, output?: process.stdout } — see - // lowerBuiltinModuleCall); the entry carries the canonical shape. The - // interface's question/close/on lower through - // lowerReadlineMethodCall. - readline: { - createInterface: { fn: "rl.create", params: [], result: F64 }, - }, - // node:readline/promises: the same Interface with the promise question - // (lowerReadlineMethodCall tells the forms apart by the call's result). - "readline/promises": { - createInterface: { fn: "rl.create", params: [], result: F64 }, - }, - // node:net and node:http lower ENTIRELY through the server spoke - // (lower-server.ts — every call shape is special-cased: closures, the - // optional connect host, writeHead's literal headers); the member list - // is the lower-server dispatch there. The modules still need keys here so future - // table entries have a home, and so the "recognized module, unlowered - // member" fence wording applies. - net: { - // The process-wide happy-eyeballs attempt budget: one runtime double - // in the core unit (never links scr_net.c by itself), default 250ms - // like Node's. - getDefaultAutoSelectFamilyAttemptTimeout: { - fn: "net.getAutoSelTimeout", - params: [], - result: F64, - }, - setDefaultAutoSelectFamilyAttemptTimeout: { - fn: "net.setAutoSelTimeout", - params: [F64], - result: VOID, - }, - // Node's IPv4Reg / IPv6Reg tests over String(input). - isIP: { fn: "net.isIP", params: [DYN], result: F64 }, - isIPv4: { fn: "net.isIPv4", params: [DYN], result: BOOL }, - isIPv6: { fn: "net.isIPv6", params: [DYN], result: BOOL }, - }, - http: {}, - // node:tls and node:https ride the same spoke (tls.createServer's - // options + secureConnection handler, https.createServer, and the - // https client's rejectUnauthorized/ca options are all special-cased - // in lower-server.ts). - tls: {}, - https: {}, - // node:dgram and node:dns lower ENTIRELY through the dgram spoke - // (lower-dgram.ts — createSocket's option literal, dns.lookup's - // options + callback, and the Socket method surface are all - // special-cased); the keys exist for the same reason net/http's do. - dgram: {}, - dns: {}, - // node:dns/promises rides the same spoke (lookup's promise form). - "dns/promises": {}, - // node:v8: getHeapStatistics answers allocator-derived figures in - // Node's shape (builtins/v8.ts — the entry carries the canonical - // shape); every other member fences with the by-design reason - // (builtinFenceHintOf). - v8: { - getHeapStatistics: { fn: "process.memoryUsageTake", params: [], result: VOID }, - }, - // node:stream/web re-exports the web stream globals (the same objects); - // its members lower through the globals' own paths. - "stream/web": {}, - // node:assert lowers ENTIRELY through the assert spoke (lower-assert.ts - // — every call shape is special-cased: optional messages complete, the - // comparisons pick per-type libCalls, deep equality synthesizes - // helpers); the keys exist so unlowered members fence module-qualified - // with the hints below. - assert: {}, - "assert/strict": {}, - // node:test lowers ENTIRELY through the test spoke (lower-test.ts — - // registrations, suites, hooks, the TestContext surface); the key - // exists so unlowered members fence module-qualified. - test: {}, - // node:stream/promises lowers through the stream spoke too - // (lower-stream.ts — finished/pipeline's promise forms special-case - // their stream arguments exactly like the callback forms); the key - // exists so unlowered members fence module-qualified. - "stream/promises": {}, - // node:stream/consumers rides the stream spoke as well (text/json/ - // buffer/arrayBuffer special-case their stream argument); blob fences - // module-qualified with the pointed hint below. - "stream/consumers": {}, - // node:timers/promises — the delay-only setTimeout and bare setImmediate - // (void promises the shared timer heap settles), and scheduler.wait / - // scheduler.yield, which Node defines as setTimeout(delay, undefined, - // options) and setImmediate(). The arity completion (omitted delay = - // Node's 1ms) and the value/options fences are special-cased in - // lowerBuiltinModuleCall; these entries carry the canonical shapes and - // route the dispatch. - "timers/promises": { - setTimeout: { fn: "tp.setTimeout", params: [F64], result: { kind: "promise", inner: VOID } }, - setImmediate: { fn: "tp.setImmediate", params: [], result: { kind: "promise", inner: VOID } }, - "scheduler.wait": { - fn: "tp.setTimeout", - params: [F64], - result: { kind: "promise", inner: VOID }, - }, - "scheduler.yield": { - fn: "tp.setImmediate", - params: [], - result: { kind: "promise", inner: VOID }, - }, - }, - // node:diagnostics_channel — the pub/sub core. channel() answers an f64 - // handle (type-mapper.ts maps Channel to F64); the subscriber arguments box - // into the checked-dynamic tree (special-cased in lowerBuiltinModuleCall — the entries - // carry canonical shapes and route the dispatch). The Channel method - // surface lowers through lowerDiagnosticsChannelMethodCall/lowerDiagnosticsChannelProperty. - diagnostics_channel: { - channel: { fn: "dc.channel", params: [STRING], result: F64 }, - subscribe: { fn: "dc.subscribe", params: [STRING, DYN], result: VOID }, - unsubscribe: { fn: "dc.unsubscribe", params: [STRING, DYN], result: BOOL }, - hasSubscribers: { fn: "dc.hasSubscribers", params: [STRING], result: BOOL }, - // The string form's canonical shape; the collection form (an object - // literal of five Channels) is special-cased in lowerBuiltinModuleCall. - tracingChannel: { fn: "dc.tracingChannel", params: [STRING], result: F64 }, - }, -}; +type BuiltinModuleFnTable = Record | undefined>; + +let builtinModuleFnTable: BuiltinModuleFnTable | undefined; + +/** Every builtin module's lowered function members, keyed by module and + * member. The table is built on the first call, so runs that do not lower + * a module do not construct it. */ +export function builtinModuleFns(): BuiltinModuleFnTable { + builtinModuleFnTable ??= buildBuiltinModuleFns(); + return builtinModuleFnTable; +} + +function buildBuiltinModuleFns(): BuiltinModuleFnTable { + return { + fs: { + readFileSync: { fn: "fs.readFileSync", params: [STRING, STRING], result: STRING }, + // The exact-API subset is first-class. A fixed native ABI is not enough: + // rows whose Node function has behavior-bearing trailing options remain + // call-only, or function-width adaptation could discard those options + // without a diagnostic before entering the fixed runtime call. + writeFileSync: { fn: "fs.writeFileSync", params: [STRING, STRING], result: VOID }, + appendFileSync: { fn: "fs.appendFileSync", params: [STRING, STRING], result: VOID }, + existsSync: { + fn: "fs.existsSync", + params: [STRING], + result: BOOL, + valueParams: exactValueParams(STRING), + }, + mkdirSync: { fn: "fs.mkdirSync", params: [STRING], result: VOID }, + rmSync: { fn: "fs.rmSync", params: [STRING], result: VOID }, + rmdirSync: { fn: "fs.rmdirSync", params: [STRING], result: VOID }, + readdirSync: { fn: "fs.readdirSync", params: [STRING], result: arrayOf(STRING) }, + // Pattern and options are checked by the runtime (lowerFsGlobSyncCall). + globSync: { fn: "fs.globSync", params: [DYN, DYN], result: arrayOf(STRING) }, + statSync: { fn: "fs.statSync", params: [STRING], result: STATS_T }, + mkdtempSync: { fn: "fs.mkdtempSync", params: [STRING], result: STRING }, + // accessSync's omitted mode completes to 0 (F_OK) in the special case + // in lowerBuiltinModuleCall — string `defaults` can't spell a number. + accessSync: { fn: "fs.accessSync", params: [STRING, F64], result: VOID }, + unlinkSync: { + fn: "fs.unlinkSync", + params: [STRING], + result: VOID, + valueParams: exactValueParams(STRING), + }, + chmodSync: { + fn: "fs.chmodSync", + params: [STRING, F64], + result: VOID, + valueParams: exactValueParams(STRING, F64), + }, + chownSync: { + fn: "fs.chownSync", + params: [STRING, F64, F64], + result: VOID, + valueParams: exactValueParams(STRING, F64, F64), + }, + lchownSync: { + fn: "fs.lchownSync", + params: [STRING, F64, F64], + result: VOID, + valueParams: exactValueParams(STRING, F64, F64), + }, + fchownSync: { + fn: "fs.fchownSync", + params: [F64, F64, F64], + result: VOID, + valueParams: exactValueParams(F64, F64, F64), + }, + // truncateSync's omitted len completes to 0 in lowerBuiltinModuleCall, + // like accessSync's mode. + truncateSync: { fn: "fs.truncateSync", params: [STRING, F64], result: VOID }, + // The 2-argument form only: Node's mode flags (COPYFILE_EXCL, ...) + // land on the arity fence. + copyFileSync: { fn: "fs.copyFileSync", params: [STRING, STRING], result: VOID }, + // The callback form is special-cased in lowerBuiltinModuleCall: its + // Error | null parameter is program-shaped and needs an emitted + // adapter. The row still projects the static surface and routes the + // dispatch. + rename: { fn: "fs.renameCb", params: [], result: VOID }, + renameSync: { + fn: "fs.renameSync", + params: [STRING, STRING], + result: VOID, + valueParams: exactValueParams(STRING, STRING), + }, + // statSync's no-follow sibling; stats.isSymbolicLink answers what the + // follow-free snapshot saw. + // opendirSync's options (a literal { bufferSize, encoding: "utf8" }) + // lower in lowerBuiltinModuleCall; the row routes the dispatch. + opendirSync: { fn: "fs.opendirSync", params: [STRING, F64], result: DIR_T }, + // createReadStream/createWriteStream lower in lowerBuiltinModuleCall + // (path and options cross as dyn values); the rows route the dispatch. + createReadStream: { + fn: "fs.createReadStream", + params: [DYN, DYN], + result: { kind: "object", className: "%Readable" }, + }, + createWriteStream: { + fn: "fs.createWriteStream", + params: [DYN, DYN], + result: { kind: "object", className: "%Writable" }, + }, + lstatSync: { fn: "fs.lstatSync", params: [STRING], result: STATS_T }, + fstatSync: { + fn: "fs.fstatSync", + params: [F64], + result: STATS_T, + valueParams: exactValueParams(F64), + }, + fchmodSync: { + fn: "fs.fchmodSync", + params: [F64, F64], + result: VOID, + valueParams: exactValueParams(F64, F64), + }, + fsyncSync: { + fn: "fs.fsyncSync", + params: [F64], + result: VOID, + valueParams: exactValueParams(F64), + }, + fdatasyncSync: { + fn: "fs.fdatasyncSync", + params: [F64], + result: VOID, + valueParams: exactValueParams(F64), + }, + ftruncateSync: { fn: "fs.ftruncateSync", params: [F64, F64], result: VOID }, + utimesSync: { fn: "fs.utimesSync", params: [DYN, DYN, DYN], result: VOID }, + futimesSync: { fn: "fs.futimesSync", params: [DYN, DYN, DYN], result: VOID }, + lutimesSync: { fn: "fs.lutimesSync", params: [DYN, DYN, DYN], result: VOID }, + readvSync: { fn: "fs.readvSync", params: [F64, arrayOf(BYTES_U8), F64], result: F64 }, + writevSync: { fn: "fs.writevSync", params: [F64, arrayOf(BYTES_U8), F64], result: F64 }, + linkSync: { + fn: "fs.linkSync", + params: [DYN, DYN], + result: VOID, + valueParams: exactValueParams(DYN, DYN), + }, + // Options and paths are checked by the runtime (scr_fs_cp_begin). + cpSync: { + fn: "fs.cpSync", + params: [DYN, DYN, DYN], + result: VOID, + valueParams: [ + ...exactValueParams(DYN, DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + symlinkSync: { + fn: "fs.symlinkSync", + params: [DYN, DYN, DYN], + result: VOID, + valueParams: [ + ...exactValueParams(DYN, DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + readlinkSync: { + fn: "fs.readlinkSync", + params: [DYN, DYN], + result: DYN, + valueParams: [ + ...exactValueParams(DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + statfsSync: { + fn: "fs.statfsSync", + params: [DYN, DYN], + result: DYN, + valueParams: [ + ...exactValueParams(DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + // realpath(3) — Node's realpathSync (failures spell syscall "lstat", + // Node's own message shape). + realpathSync: { fn: "fs.realpathSync", params: [STRING], result: STRING }, + "realpathSync.native": { fn: "fs.realpathNativeSync", params: [STRING], result: STRING }, + // The fd pair behind spawn's fd-stdio form (the daemon-log idiom: + // openSync(logPath, "a") → spawn stdio ["ignore", fd, fd] → + // closeSync). String flags use the established two-argument path; + // inline numeric O_* expressions route through openNumericSync, with + // an optional creation mode as the third argument. + openSync: { fn: "fs.openSync", params: [STRING, STRING], result: F64 }, + // The buffer forms (fd, buffer, offset, length[, position]). The + // lowering completes an omitted or literal-null position to -1 (the + // runtime's current-offset sentinel); numeric positions read without + // advancing the fd. The options-object and bigint forms still fence. + readSync: { fn: "fs.readSync", params: [F64, BYTES_U8, F64, F64, F64], result: F64 }, + // Entirely special-cased: the classic Buffer window and utf8 string + // overloads lower to separate fixed-width runtime ABIs. As with + // readSync, -1 is the current-offset sentinel. + writeSync: { fn: "fs.writeSync", params: [F64, BYTES_U8, F64, F64, F64], result: F64 }, + closeSync: { + fn: "fs.closeSync", + params: [F64], + result: VOID, + valueParams: exactValueParams(F64), + }, + // Entirely special-cased (lowerFsWatchCall — the callback needs an + // adapter per listener shape); this entry only routes the dispatch. + watch: { fn: "fs.watch", params: [], result: VOID }, + }, + "fs/promises": { + // The sync operations behind already-settled promises: failures + // REJECT (catchable at the await) instead of throwing. readFile is + // utf8-fenced exactly like readFileSync (same special case below). + readFile: { + fn: "fsp.readFile", + params: [STRING, STRING], + result: { kind: "promise", inner: STRING }, + }, + writeFile: { + fn: "fsp.writeFile", + params: [STRING, STRING], + result: { kind: "promise", inner: VOID }, + }, + mkdir: { fn: "fsp.mkdir", params: [STRING], result: { kind: "promise", inner: VOID } }, + readdir: { + fn: "fsp.readdir", + params: [STRING], + result: { kind: "promise", inner: arrayOf(STRING) }, + }, + rm: { fn: "fsp.rm", params: [STRING], result: { kind: "promise", inner: VOID } }, + // The async iterator lowers only as a for await head (lowerFspGlobCall). + glob: { fn: "fsp.globStrings", params: [DYN, DYN], result: arrayOf(STRING) }, + stat: { fn: "fsp.stat", params: [STRING], result: { kind: "promise", inner: STATS_T } }, + realpath: { + fn: "fsp.realpath", + params: [STRING], + result: { kind: "promise", inner: STRING }, + }, + lstat: { fn: "fsp.lstat", params: [STRING], result: { kind: "promise", inner: STATS_T } }, + opendir: { + fn: "fsp.opendir", + params: [STRING, F64], + result: { kind: "promise", inner: DIR_T }, + }, + unlink: { + fn: "fsp.unlink", + params: [STRING], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(STRING), + }, + chmod: { + fn: "fsp.chmod", + params: [STRING, F64], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(STRING, F64), + }, + chown: { + fn: "fsp.chown", + params: [STRING, F64, F64], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(STRING, F64, F64), + }, + lchown: { + fn: "fsp.lchown", + params: [STRING, F64, F64], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(STRING, F64, F64), + }, + // access' omitted mode and truncate's omitted len complete to 0 in + // lowerBuiltinModuleCall, like their sync spellings. + access: { fn: "fsp.access", params: [STRING, F64], result: { kind: "promise", inner: VOID } }, + truncate: { + fn: "fsp.truncate", + params: [STRING, F64], + result: { kind: "promise", inner: VOID }, + }, + // String data; Uint8Array/Buffer data routes to fsp.appendFileBytes in + // lowerBuiltinModuleCall. + appendFile: { + fn: "fsp.appendFile", + params: [STRING, STRING], + result: { kind: "promise", inner: VOID }, + }, + // The 2-argument form only, like copyFileSync. + copyFile: { + fn: "fsp.copyFile", + params: [STRING, STRING], + result: { kind: "promise", inner: VOID }, + }, + mkdtemp: { fn: "fsp.mkdtemp", params: [STRING], result: { kind: "promise", inner: STRING } }, + rmdir: { fn: "fsp.rmdir", params: [STRING], result: { kind: "promise", inner: VOID } }, + link: { + fn: "fsp.link", + params: [DYN, DYN], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(DYN, DYN), + }, + cp: { + fn: "fsp.cp", + params: [DYN, DYN, DYN], + result: { kind: "promise", inner: VOID }, + valueParams: [ + ...exactValueParams(DYN, DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + symlink: { + fn: "fsp.symlink", + params: [DYN, DYN, DYN], + result: { kind: "promise", inner: VOID }, + valueParams: [ + ...exactValueParams(DYN, DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + readlink: { + fn: "fsp.readlink", + params: [DYN, DYN], + result: { kind: "promise", inner: DYN }, + valueParams: [ + ...exactValueParams(DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + statfs: { + fn: "fsp.statfs", + params: [DYN, DYN], + result: { kind: "promise", inner: DYN }, + valueParams: [ + ...exactValueParams(DYN), + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + utimes: { + fn: "fsp.utimes", + params: [DYN, DYN, DYN], + result: { kind: "promise", inner: VOID }, + }, + lutimes: { + fn: "fsp.lutimes", + params: [DYN, DYN, DYN], + result: { kind: "promise", inner: VOID }, + }, + rename: { + fn: "fsp.rename", + params: [STRING, STRING], + result: { kind: "promise", inner: VOID }, + valueParams: exactValueParams(STRING, STRING), + }, + // open's optional flags/mode completion is special-cased in + // lowerBuiltinModuleCall; this row routes all import spellings and + // gives coverage the static member. + open: { + fn: "fsp.open", + params: [STRING, STRING, F64], + result: { kind: "promise", inner: FILEHANDLE_T }, + valueParams: [ + { mode: "required", type: STRING }, + { mode: "optional", type: STRING, defaultValue: "r" }, + { mode: "optional", type: F64, defaultValue: 0o666 }, + ], + }, + }, + // The bare module's POSIX-target binding; a win32 target rebinds it to + // the win32 table (builtinModuleFnsOf — Node on Windows IS path.win32). + path: PATH_MODULE_FNS, + // Real Node modules ("node:path/posix" resolves), and the receivers of + // `path.posix.` / `path.win32.` through the namespace + // chain (builtinNamespaceModuleOf composes "path" + ".posix" to these + // keys). Both namespaces carry the FULL surface on every platform, + // Node's own rule: each answers its own platform's semantics anywhere. + "path/posix": PATH_MODULE_FNS, + "path/win32": PATH_WIN32_MODULE_FNS, + os: { + // One platform implementation: os.platform() === process.platform. + platform: { fn: "process.platform", params: [], result: STRING, valueParams: [] }, + homedir: { fn: "os.homedir", params: [], result: STRING, valueParams: [] }, + tmpdir: { fn: "os.tmpdir", params: [], result: STRING, valueParams: [] }, + // uname(2)'s release field — Node's own implementation. + release: { fn: "os.release", params: [], result: STRING, valueParams: [] }, + // uname(2)'s sysname field ("Darwin", "Linux", "Windows_NT") — the + // libFn/runtime pair predates this entry (the portless surface used + // them); this row makes the MODULE call reach them (test/common's + // isAIX/isIBMi getters are the canonical callers). + type: { fn: "os.type", params: [], result: STRING, valueParams: [] }, + // Total physical memory in bytes — same predating-pair story. + totalmem: { fn: "os.totalmem", params: [], result: F64, valueParams: [] }, + // uv_available_parallelism: the CPUs this process may use (affinity, + // then cgroup quota on Linux), at least 1. + availableParallelism: { + fn: "os.availableParallelism", + params: [], + result: F64, + valueParams: [], + }, + // os.arch() IS process.arch, like platform(). + arch: { fn: "process.arch", params: [], result: STRING, valueParams: [] }, + hostname: { fn: "os.hostname", params: [], result: STRING, valueParams: [] }, + // uv_os_uname's machine and version fields, read once at module load + // in Node (they never change while a process runs). + machine: { fn: "os.machine", params: [], result: STRING, valueParams: [] }, + version: { fn: "os.version", params: [], result: STRING, valueParams: [] }, + endianness: { fn: "os.endianness", params: [], result: STRING, valueParams: [] }, + freemem: { fn: "os.freemem", params: [], result: F64, valueParams: [] }, + uptime: { fn: "os.uptime", params: [], result: F64, valueParams: [] }, + loadavg: { fn: "os.loadavg", params: [], result: arrayOf(F64), valueParams: [] }, + // An omitted (or undefined) pid is 0, the current process + // (lowerOsGetPriorityCall completes it). + getPriority: { + fn: "os.getPriority", + params: [F64], + result: F64, + valueParams: [{ mode: "optional", type: F64, defaultValue: 0 }], + }, + // A one-argument call is setPriority(0, priority) + // (lowerOsSetPriorityCall completes it). + setPriority: { fn: "os.setPriority", params: [F64, F64], result: VOID }, + // Special-cased (lowerOsCpusCall): the call site's CpuInfo[] assembles + // from one uv_cpu_info snapshot. + cpus: { fn: "os.cpusTake", params: [], result: VOID }, + // Entirely special-cased (lowerOsNetworkInterfacesCall): the result is + // the call site's mapped Dict shape, verified + // structurally there — this entry only routes the dispatch. + networkInterfaces: { fn: "os.networkInterfaces", params: [], result: VOID }, + // Special-cased too (lowerOsUserInfoCall): the call site's mapped + // UserInfo record assembles field-by-field from scalar libCalls. + userInfo: { fn: "os.userName", params: [], result: VOID }, + }, + crypto: { + randomUUID: { + fn: "crypto.randomUUID", + params: [], + result: STRING, + // As a value (uuid's `export default { randomUUID }`), the optional + // options argument goes through the validating entry point. + valueFn: "crypto.randomUUIDOpts", + valueParams: [{ mode: "optional", type: DYN, defaultValue: undefined }], + }, + // A real Buffer result. The composed randomBytes(n).toString(enc) + // form KEEPS its one-libCall lowering (lowerCryptoComposedCall runs + // first and the Buffer never materializes there); this entry covers + // the bare calls and non-composed uses. + randomBytes: { + fn: "crypto.randomBytes", + params: [F64], + result: BYTES_U8, + valueParams: exactValueParams(F64), + }, + // The crypto utility surface is special-cased for overloads and + // string/Buffer splits in lowerCryptoModuleCall. These rows carry the + // canonical signatures for manifest projection and fallback dispatch. + createHash: { fn: "crypto.hashNew", params: [STRING], result: CRYPTOHASH_T }, + createHmac: { fn: "crypto.hmacNewStr", params: [STRING, STRING], result: CRYPTOHMAC_T }, + hash: { fn: "crypto.hashDigestStr", params: [STRING, STRING, STRING], result: STRING }, + timingSafeEqual: { fn: "crypto.timingSafeEqual", params: [BYTES_U8, BYTES_U8], result: BOOL }, + randomFillSync: { fn: "crypto.randomFill", params: [BYTES_U8, F64, F64], result: BYTES_U8 }, + getRandomValues: { fn: "crypto.getRandomValues", params: [BYTES_U8], result: BYTES_U8 }, + randomInt: { fn: "crypto.randomInt", params: [F64, F64], result: F64 }, + randomFill: { fn: "crypto.randomFillCb", params: [], result: VOID }, + randomUUIDv7: { + fn: "crypto.randomUUIDv7", + params: [DYN], + result: STRING, + valueParams: [{ mode: "optional", type: DYN, defaultValue: undefined }], + }, + createSecretKey: { fn: "crypto.createSecretKey", params: [DYN, DYN], result: DYN }, + generateKeySync: { fn: "crypto.generateKeySync", params: [DYN, DYN], result: DYN }, + generateKey: { fn: "crypto.generateKeyCb", params: [], result: VOID }, + // Asymmetric keys and signatures (lowerPkiCall): checked-dynamic + // arguments, results converted to the call site's type. + createPrivateKey: { fn: "crypto.createPrivateKey", params: [DYN], result: DYN }, + createPublicKey: { fn: "crypto.createPublicKey", params: [DYN], result: DYN }, + createSign: { fn: "crypto.createSign", params: [DYN], result: DYN }, + createVerify: { fn: "crypto.createVerify", params: [DYN], result: DYN }, + generateKeyPairSync: { fn: "crypto.generateKeyPairSync", params: [], result: DYN }, + generateKeyPair: { fn: "crypto.generateKeyPairCb", params: [], result: VOID }, + sign: { fn: "crypto.signOneshot", params: [], result: DYN }, + verify: { fn: "crypto.verifyOneshot", params: [], result: DYN }, + diffieHellman: { fn: "crypto.diffieHellman", params: [], result: DYN }, + pbkdf2Sync: { + fn: "crypto.pbkdf2", + params: [BYTES_U8, BYTES_U8, F64, F64, STRING], + result: BYTES_U8, + }, + pbkdf2: { fn: "crypto.pbkdf2Cb", params: [], result: VOID }, + hkdfSync: { + fn: "crypto.hkdf", + params: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], + result: DYN, + }, + hkdf: { fn: "crypto.hkdfCb", params: [], result: VOID }, + scryptSync: { fn: "crypto.scrypt", params: [BYTES_U8, BYTES_U8, F64, DYN], result: BYTES_U8 }, + scrypt: { fn: "crypto.scryptCb", params: [], result: VOID }, + // Checked-dynamic arguments; the result is a native Cipheriv or + // Decipheriv handle (lowerCryptoModuleCall). + createCipheriv: { fn: "crypto.cipherNew", params: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, + createDecipheriv: { fn: "crypto.cipherNew", params: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, + // A checked-dynamic curve name; the result is a native ECDH handle + // (lowerCreateEcdh). + createECDH: { fn: "crypto.ecdhNew", params: [DYN], result: DYN }, + // Checked-dynamic arguments; the result is a native DiffieHellman or + // DiffieHellmanGroup handle (lowerCreateDiffieHellman and + // lowerGetDiffieHellman). + createDiffieHellman: { fn: "crypto.dhNew", params: [DYN, DYN, DYN, DYN], result: DYN }, + getDiffieHellman: { fn: "crypto.dhGroupNew", params: [DYN], result: DYN }, + createDiffieHellmanGroup: { fn: "crypto.dhGroupNew", params: [DYN], result: DYN }, + // Prime checks and generation (lowerPrimeCall): checked-dynamic + // arguments, results converted to the call site's type. + checkPrimeSync: { fn: "crypto.checkPrimeSync", params: [], result: DYN }, + // Checked-dynamic arguments, the result converted to the call site's + // type (lowerPrimeCall). + getCipherInfo: { fn: "crypto.getCipherInfo", params: [], result: DYN }, + checkPrime: { fn: "crypto.checkPrimeCb", params: [], result: VOID }, + generatePrimeSync: { fn: "crypto.generatePrimeSync", params: [], result: DYN }, + generatePrime: { fn: "crypto.generatePrimeCb", params: [], result: VOID }, + }, + zlib: { + // The default-options convenience family is special-cased in + // lowerBuiltinModuleCall: string inputs UTF-8 encode, callback forms + // carry program-shaped closures, and explicit options fence per site. + // native-toolchain.ts links libz only when these appear on the IR. + deflate: { fn: "zlib.deflateCb", params: [], result: VOID }, + deflateSync: { fn: "zlib.deflateSync", params: [BYTES_U8], result: BYTES_U8 }, + inflate: { fn: "zlib.inflateCb", params: [], result: VOID }, + inflateSync: { fn: "zlib.inflateSync", params: [BYTES_U8], result: BYTES_U8 }, + deflateRaw: { fn: "zlib.deflateRawCb", params: [], result: VOID }, + deflateRawSync: { fn: "zlib.deflateRawSync", params: [BYTES_U8], result: BYTES_U8 }, + inflateRaw: { fn: "zlib.inflateRawCb", params: [], result: VOID }, + inflateRawSync: { fn: "zlib.inflateRawSync", params: [BYTES_U8], result: BYTES_U8 }, + gzip: { fn: "zlib.gzipCb", params: [], result: VOID }, + gzipSync: { fn: "zlib.gzipSync", params: [BYTES_U8], result: BYTES_U8 }, + gunzip: { fn: "zlib.gunzipCb", params: [], result: VOID }, + gunzipSync: { fn: "zlib.gunzipSync", params: [BYTES_U8], result: BYTES_U8 }, + unzip: { fn: "zlib.unzipCb", params: [], result: VOID }, + unzipSync: { fn: "zlib.unzipSync", params: [BYTES_U8], result: BYTES_U8 }, + crc32: { fn: "zlib.crc32", params: [], result: F64 }, + // Brotli (special-cased in lowerZlibModuleCall): every form takes the + // options entries with Node's BROTLI_ENCODE or BROTLI_DECODE mode. + brotliCompress: { fn: "zlib.cbOpts", params: [], result: VOID }, + brotliCompressSync: { fn: "zlib.syncOpts", params: [], result: BYTES_U8 }, + brotliDecompress: { fn: "zlib.cbOpts", params: [], result: VOID }, + brotliDecompressSync: { fn: "zlib.syncOpts", params: [], result: BYTES_U8 }, + // The stream factories (special-cased in lowerZlibModuleCall): each + // answers a runtime Transform stream over the zlib engine. + createDeflate: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createInflate: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createGzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createGunzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createDeflateRaw: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createInflateRaw: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createUnzip: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createBrotliCompress: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + createBrotliDecompress: { fn: "zlib.streamNew", params: [], result: ZLIB_STREAM }, + }, + url: { + fileURLToPath: { + fn: "url.fileURLToPathOptions", + params: [DYN, DYN], + result: STRING, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + fileURLToPathBuffer: { + fn: "url.fileURLToPathBuffer", + params: [DYN, DYN], + result: BYTES_U8, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + pathToFileURL: { + fn: "url.pathToFileURLChecked", + params: [DYN, DYN], + result: URL_T, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + // The legacy API and the domain pair (lowerLegacyUrlCall): arguments + // cross as dyn values for Node's own validators; url.parse() answers + // the checked-dynamic Url instance. + parse: { fn: "url.legacyParse", params: [DYN, DYN, DYN], result: DYN }, + format: { fn: "url.legacyFormat", params: [DYN, DYN], result: STRING }, + resolve: { fn: "url.legacyResolve", params: [DYN, DYN], result: STRING }, + domainToASCII: { fn: "url.domainToASCII", params: [DYN], result: STRING }, + domainToUnicode: { fn: "url.domainToUnicode", params: [DYN], result: STRING }, + urlToHttpOptions: { fn: "url.toHttpOptions", params: [DYN], result: DYN }, + }, + child_process: { + // spawnSync's and spawn's call completions are entirely special-cased + // (an omitted args list completes to an empty string[]; spawnSync + // accepts its documented literal options, while spawn supports the + // stdio/detached/env/cwd/windowsHide/shell subset — see + // lowerBuiltinModuleCall); the + // entries carry the canonical shapes. + spawnSync: { fn: "cp.spawnSync", params: [STRING, arrayOf(STRING)], result: SPAWNRES_T }, + spawn: { fn: "cp.spawn", params: [STRING, arrayOf(STRING)], result: CHILD_T }, + fork: { fn: "cp.fork", params: [STRING, arrayOf(STRING)], result: CHILD_T }, + // execFileSync(file, args?, options?) and execSync(command, options?) + // share the cp.execSync runtime entry (execSync sets the shell flag); + // both call completions are special-cased in lowerBuiltinModuleCall. + // The entries carry canonical shapes for the coverage/fence machinery. + execFileSync: { fn: "cp.execSync", params: [STRING, arrayOf(STRING)], result: STRING }, + execSync: { fn: "cp.execSync", params: [STRING], result: STRING }, + }, + // node:util lowers through dedicated paths: promisify + // only in the const-binding-over-execFile shape — recognized in + // lowerVarDecl / collectGlobals BEFORE any call lowering runs — and + // inspect/format/formatWithOptions plus parseArgs through the util spoke + // (lower-inspect.ts: per-type synthesized traversal helpers, + // compile-time format strings, checked-dynamic config/result for + // parseArgs), which both dispatch paths try before the generic table. + // parseArgs is tabled as well so the generated surface manifest records + // the dedicated static member; the spoke owns its optional arity and + // call-site-specific result type before this canonical signature is used. + util: { + styleText: { + fn: "util.styleText", + params: [DYN, DYN, DYN], + result: STRING, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + isDeepStrictEqual: { + fn: "util.isDeepStrictEqual", + params: [DYN, DYN, DYN], + result: BOOL, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + parseArgs: { fn: "util.parseArgs", params: [DYN], result: DYN }, + parseEnv: { fn: "util.parseEnv", params: [DYN], result: DYN }, + getSystemErrorName: { + fn: "util.getSystemErrorName", + params: [DYN], + result: STRING, + valueParams: exactValueParams(DYN), + }, + getSystemErrorMessage: { + fn: "util.getSystemErrorMessage", + params: [DYN], + result: STRING, + valueParams: exactValueParams(DYN), + }, + getSystemErrorMap: { fn: "util.systemErrorEntries", params: [], result: DYN }, + stripVTControlCharacters: { + fn: "util.stripVTControlCharacters", + params: [DYN], + result: STRING, + valueParams: exactValueParams(DYN), + }, + toUSVString: { + fn: "util.toUSVString", + params: [DYN], + result: STRING, + valueParams: exactValueParams(DYN), + }, + // The function helpers take checked-dynamic arguments and answer native + // dyn functions, which a static function slot checks when it receives + // one (lowerUtilCallableCall in the util spoke). + inherits: { + fn: "util.inherits", + params: [DYN, DYN], + result: VOID, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "required", type: DYN }, + ], + }, + deprecate: { + fn: "util.deprecate", + params: [DYN, DYN, DYN, DYN], + result: DYN, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + callbackify: { + fn: "util.callbackify", + params: [DYN], + result: DYN, + valueParams: [{ mode: "required", type: DYN }], + }, + debuglog: { + fn: "util.debuglog", + params: [DYN, DYN], + result: DYN, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + debug: { + fn: "util.debuglog", + params: [DYN, DYN], + result: DYN, + valueParams: [ + { mode: "required", type: DYN }, + { mode: "optional", type: DYN, defaultValue: undefined }, + ], + }, + }, + // The util/types spoke supplies the probe name to the shared brand check. + "util/types": { + isAnyArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isArrayBufferView: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isDataView: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isTypedArray: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isUint8Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isUint8ClampedArray: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isUint16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isUint32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isInt8Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isInt16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isInt32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isFloat32Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isFloat64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isMap: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isSet: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isWeakMap: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isWeakSet: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isRegExp: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isNativeError: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isPromise: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isProxy: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isAsyncFunction: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isGeneratorFunction: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isGeneratorObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isDate: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isSharedArrayBuffer: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isMapIterator: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isSetIterator: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isKeyObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isNumberObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isStringObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isBooleanObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isBigIntObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isSymbolObject: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isBoxedPrimitive: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isBigInt64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isBigUint64Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isFloat16Array: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isCryptoKey: { fn: "util.typeIs", params: [DYN], result: BOOL }, + isExternal: { fn: "util.typeIs", params: [DYN], result: BOOL }, + }, + buffer: { + isAscii: { fn: "buffer.isAscii", params: [DYN], result: BOOL }, + isUtf8: { fn: "buffer.isUtf8", params: [DYN], result: BOOL }, + transcode: { fn: "buffer.transcode", params: [DYN, DYN, DYN], result: BYTES_U8 }, + }, + tty: { + isatty: { fn: "process.isTTY", params: [F64], result: BOOL }, + }, + process: { + loadEnvFile: { fn: "process.loadEnvFile", params: [DYN], result: VOID }, + }, + // node:string_decoder's surface is the StringDecoder CLASS — new/write/ + // end are special-cased (lowerNew + lowerStringDecoderMethodCall); no + // function members exist to table. + string_decoder: {}, + // node:querystring — the legacy query-string codec (NOT URLSearchParams; + // the escaping and '+' rules differ). escape/unescape ride the table + // path directly; parse and stringify are special-cased in + // lowerBuiltinModuleCall (parse's result is the call site's mapped + // ParsedUrlQuery dictionary — the networkInterfaces verification stance + // — and its sep/eq/options complete there; stringify's object argument + // crosses as a dyn value). decode/encode are Node's own aliases of + // parse/stringify (`const decode = parse` in lib/querystring.js) and + // route to the same special cases; the entries carry canonical shapes. + querystring: { + parse: { fn: "qs.parse", params: [STRING], result: VOID }, + decode: { fn: "qs.parse", params: [STRING], result: VOID }, + stringify: { fn: "qs.stringify", params: [DYN], result: STRING }, + encode: { fn: "qs.stringify", params: [DYN], result: STRING }, + escape: { + fn: "qs.escape", + params: [STRING], + result: STRING, + valueParams: [{ mode: "required", type: STRING }], + }, + unescape: { + fn: "qs.unescape", + params: [STRING], + result: STRING, + valueParams: [{ mode: "required", type: STRING }], + }, + }, + // node:punycode (deprecated): punycode.js 2.1.0's conversions over + // strings (scr_url.c). ucs2.decode/encode are members of the module's + // ucs2 object, dispatched as module members (builtinMemberOf). The + // DEP0040 report rides the module's load sites (punycodeLoadWarning). + punycode: { + decode: { fn: "punycode.decode", params: [STRING], result: STRING }, + encode: { fn: "punycode.encode", params: [STRING], result: STRING }, + toASCII: { fn: "punycode.toASCII", params: [STRING], result: STRING }, + toUnicode: { fn: "punycode.toUnicode", params: [STRING], result: STRING }, + "ucs2.decode": { fn: "punycode.ucs2Decode", params: [STRING], result: arrayOf(F64) }, + "ucs2.encode": { fn: "punycode.ucs2Encode", params: [arrayOf(F64)], result: STRING }, + }, + // node:constants (deprecated) is one frozen object (Object.assign of + // os.constants' groups, fs.constants and crypto.constants): bindings of + // the module read it as a checked-dynamic value (builtinConstantsObject). + constants: {}, + // node:readline: createInterface's options are entirely special-cased + // (exactly { input: process.stdin, output?: process.stdout } — see + // lowerBuiltinModuleCall); the entry carries the canonical shape. The + // interface's question/close/on lower through + // lowerReadlineMethodCall. + readline: { + createInterface: { fn: "rl.create", params: [], result: F64 }, + }, + // node:readline/promises: the same Interface with the promise question + // (lowerReadlineMethodCall tells the forms apart by the call's result). + "readline/promises": { + createInterface: { fn: "rl.create", params: [], result: F64 }, + }, + // node:net and node:http lower ENTIRELY through the server spoke + // (lower-server.ts — every call shape is special-cased: closures, the + // optional connect host, writeHead's literal headers); the member list + // is the lower-server dispatch there. The modules still need keys here so future + // table entries have a home, and so the "recognized module, unlowered + // member" fence wording applies. + net: { + // The process-wide happy-eyeballs attempt budget: one runtime double + // in the core unit (never links scr_net.c by itself), default 250ms + // like Node's. + getDefaultAutoSelectFamilyAttemptTimeout: { + fn: "net.getAutoSelTimeout", + params: [], + result: F64, + }, + setDefaultAutoSelectFamilyAttemptTimeout: { + fn: "net.setAutoSelTimeout", + params: [F64], + result: VOID, + }, + // Node's IPv4Reg / IPv6Reg tests over String(input). + isIP: { fn: "net.isIP", params: [DYN], result: F64 }, + isIPv4: { fn: "net.isIPv4", params: [DYN], result: BOOL }, + isIPv6: { fn: "net.isIPv6", params: [DYN], result: BOOL }, + }, + http: {}, + // node:tls and node:https ride the same spoke (tls.createServer's + // options + secureConnection handler, https.createServer, and the + // https client's rejectUnauthorized/ca options are all special-cased + // in lower-server.ts). + tls: {}, + https: {}, + // node:dgram and node:dns lower ENTIRELY through the dgram spoke + // (lower-dgram.ts — createSocket's option literal, dns.lookup's + // options + callback, and the Socket method surface are all + // special-cased); the keys exist for the same reason net/http's do. + dgram: {}, + dns: {}, + // node:dns/promises rides the same spoke (lookup's promise form). + "dns/promises": {}, + // node:v8: getHeapStatistics answers allocator-derived figures in + // Node's shape (builtins/v8.ts — the entry carries the canonical + // shape); every other member fences with the by-design reason + // (builtinFenceHintOf). + v8: { + getHeapStatistics: { fn: "process.memoryUsageTake", params: [], result: VOID }, + }, + // node:stream/web re-exports the web stream globals (the same objects); + // its members lower through the globals' own paths. + "stream/web": {}, + // node:assert lowers ENTIRELY through the assert spoke (lower-assert.ts + // — every call shape is special-cased: optional messages complete, the + // comparisons pick per-type libCalls, deep equality synthesizes + // helpers); the keys exist so unlowered members fence module-qualified + // with the hints below. + assert: {}, + "assert/strict": {}, + // node:test lowers ENTIRELY through the test spoke (lower-test.ts — + // registrations, suites, hooks, the TestContext surface); the key + // exists so unlowered members fence module-qualified. + test: {}, + // node:stream/promises lowers through the stream spoke too + // (lower-stream.ts — finished/pipeline's promise forms special-case + // their stream arguments exactly like the callback forms); the key + // exists so unlowered members fence module-qualified. + "stream/promises": {}, + // node:stream/consumers rides the stream spoke as well (text/json/ + // buffer/arrayBuffer special-case their stream argument); blob fences + // module-qualified with the pointed hint below. + "stream/consumers": {}, + // node:timers/promises — the delay-only setTimeout and bare setImmediate + // (void promises the shared timer heap settles), and scheduler.wait / + // scheduler.yield, which Node defines as setTimeout(delay, undefined, + // options) and setImmediate(). The arity completion (omitted delay = + // Node's 1ms) and the value/options fences are special-cased in + // lowerBuiltinModuleCall; these entries carry the canonical shapes and + // route the dispatch. + "timers/promises": { + setTimeout: { fn: "tp.setTimeout", params: [F64], result: { kind: "promise", inner: VOID } }, + setImmediate: { fn: "tp.setImmediate", params: [], result: { kind: "promise", inner: VOID } }, + "scheduler.wait": { + fn: "tp.setTimeout", + params: [F64], + result: { kind: "promise", inner: VOID }, + }, + "scheduler.yield": { + fn: "tp.setImmediate", + params: [], + result: { kind: "promise", inner: VOID }, + }, + }, + // node:diagnostics_channel — the pub/sub core. channel() answers an f64 + // handle (type-mapper.ts maps Channel to F64); the subscriber arguments box + // into the checked-dynamic tree (special-cased in lowerBuiltinModuleCall — the entries + // carry canonical shapes and route the dispatch). The Channel method + // surface lowers through lowerDiagnosticsChannelMethodCall/lowerDiagnosticsChannelProperty. + diagnostics_channel: { + channel: { fn: "dc.channel", params: [STRING], result: F64 }, + subscribe: { fn: "dc.subscribe", params: [STRING, DYN], result: VOID }, + unsubscribe: { fn: "dc.unsubscribe", params: [STRING, DYN], result: BOOL }, + hasSubscribers: { fn: "dc.hasSubscribers", params: [STRING], result: BOOL }, + // The string form's canonical shape; the collection form (an object + // literal of five Channels) is special-cased in lowerBuiltinModuleCall. + tracingChannel: { fn: "dc.tracingChannel", params: [STRING], result: F64 }, + }, + }; +} /** Builtin-module CONSTANTS (value reads of named imports): each lowers to * a literal of its value's kind. The bare-module values are the TARGET @@ -2080,7 +2099,7 @@ export const BUILTIN_MODULE_CONSTS: Record< * the sidecar's determinism attestation demotes on them by prefix, so the * member's fence detector must witness them too (fencing one spelling * fences the operation, the trimLeft/trimStart rule). Keyed like - * BUILTIN_MODULE_FNS; consumed by the fence taxonomy + * builtinModuleFns(); consumed by the fence taxonomy * (library/fence-eval.ts) and the attestation-parity test. */ export const BUILTIN_MODULE_FN_ALIASES: Record< string, @@ -2232,628 +2251,643 @@ export interface AmbientSurfaceRow { note?: string; } -export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ - { - id: "node-builtin.worker_threads.Worker", - kind: "node-builtin", - name: "worker_threads.Worker", - fns: ["worker.new"], - note: "native threads with statically compiled entry points, workerData, argv and message events", - }, - { - id: "node-builtin.worker_threads.metadata", - kind: "node-builtin", - name: "worker_threads metadata", - fns: [ - "worker.isMainThread", - "worker.threadId", - "worker.data", - "worker.parentPort", - "worker.resourceLimits", - "worker.threadName", - ], - note: "current-thread metadata and the worker's parent message port", - }, - { - id: "stdlib.sharedArrayBuffer", - kind: "stdlib", - name: "SharedArrayBuffer", - fns: ["sharedArrayBuffer.new"], - note: "fixed shared storage with context-local typed-array and DataView wrappers", - }, - { - id: "stdlib.blob", - kind: "stdlib", - name: "Blob", - fns: ["blob.new", "blob.slice", "blob.text", "blob.bytes", "blob.arrayBuffer", "blob.is"], - note: "immutable bytes with size, type, slice(), text(), bytes(), arrayBuffer() and structured cloning", - }, - { - id: "stdlib.file", - kind: "stdlib", - name: "File", - fns: ["blob.fileNew"], - note: "a Blob with name and lastModified (Date.now() when absent)", - }, - { - id: "stdlib.webcrypto.subtle", - kind: "stdlib", - name: "globalThis.crypto.subtle", - fns: [ - "webcrypto.generateKey", - "webcrypto.importKey", - "webcrypto.exportKey", - "webcrypto.encrypt", - "webcrypto.decrypt", - "webcrypto.wrapKey", - "webcrypto.unwrapKey", - "webcrypto.deriveBits", - "webcrypto.deriveKey", - "webcrypto.sign", - "webcrypto.verify", - "webcrypto.digest", - ], - note: "importKey, generateKey and exportKey (raw and jwk) for HMAC and AES keys, importKey for PBKDF2 and HKDF keys, AES-GCM, AES-CBC and AES-CTR encrypt and decrypt, wrapKey and unwrapKey of secret keys with AES-KW or those ciphers, PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign and verify, generateKey, importKey, exportKey, sign and verify for ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and RSA-PSS, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused", - }, - { - id: "stdlib.webcrypto.CryptoKey", - kind: "stdlib", - name: "CryptoKey", - fns: ["webcrypto.isCryptoKey"], - note: "secret HMAC, AES, PBKDF2 and HKDF keys with type, extractable, algorithm and usages, and instanceof CryptoKey", - }, - { - id: "stdlib.atomics", - kind: "stdlib", - name: "Atomics", - fns: ["atomics.op", "atomics.wait", "atomics.notify"], - note: "8-, 16- and 32-bit integer operations, plus Int32Array wait and notify", - }, - { - id: "node-builtin.fs.callbacks", - kind: "node-builtin", - name: "fs error-first callbacks", - fns: ["fs.callbackValue", "fs.callbackCall", "fs.callbackGlob"], - note: "native filesystem callbacks, including stored platform adapter functions", - }, - // ── the Date slice (lowerDateCall/lowerNew): statics, construction, - // stored read-only values, calendar getters, and ISO formatting. - { - id: "stdlib.date.now", - kind: "stdlib", - name: "Date.now", - fns: ["date.now"], - note: "the live clock", - }, - { - id: "stdlib.date.parse", - kind: "stdlib", - name: "Date.parse", - fns: ["date.parse"], - note: "one date string, using V8's date parser shared with new Date(dateString)", - }, - { - id: "stdlib.date.constructor", - kind: "stdlib", - name: "Date constructor", - fns: [ - "date.newNow", - "date.newMs", - "date.newString", - "date.nativeNew", - "date.newLocal", - "date.nativeNewFields", - "date.nativeNewValue", - "date.argumentsTime", - "date.classOperation", - ], - note: "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice", - }, - { - id: "stdlib.date.setters", - kind: "stdlib", - name: "Date.prototype setters", - fns: ["date.setFields", "date.timeClip", "date.store"], - note: "setTime and the local and UTC field setters, with their optional arguments", - }, - { - id: "stdlib.date.UTC", - kind: "stdlib", - name: "Date.UTC", - fns: ["date.utc"], - note: "the lowered call form takes 1 to 7 number arguments", - }, - { - id: "stdlib.date.getTime", - kind: "stdlib", - name: "Date.prototype.getTime", - fns: ["date.getTime", "date.parseGetTime"], - note: "the millisecond value of a stored Date", - }, - { - id: "stdlib.date.valueOf", - kind: "stdlib", - name: "Date.prototype.valueOf", - fns: ["date.valueOf"], - note: "the same millisecond read as getTime()", - }, - { - id: "stdlib.date.toISOString", - kind: "stdlib", - name: "Date.prototype.toISOString", - fns: ["date.toISOString", "date.toISOStringValue"], - note: "UTC ISO formatting over constructed and stored Date values", - }, - { - id: "stdlib.date.toUTCString", - kind: "stdlib", - name: "Date.prototype.toUTCString", - fns: ["date.toUTCString"], - note: "RFC 7231 IMF-fixdate formatting over stored Date values", - }, - { - id: "stdlib.date.getFullYear", - kind: "stdlib", - name: "Date.prototype.getFullYear", - fns: ["date.getFullYear"], - }, - { - id: "stdlib.date.getUTCFullYear", - kind: "stdlib", - name: "Date.prototype.getUTCFullYear", - fns: ["date.getUTCFullYear"], - }, - { - id: "stdlib.date.getMonth", - kind: "stdlib", - name: "Date.prototype.getMonth", - fns: ["date.getMonth"], - }, - { - id: "stdlib.date.getUTCMonth", - kind: "stdlib", - name: "Date.prototype.getUTCMonth", - fns: ["date.getUTCMonth"], - }, - { - id: "stdlib.date.getDate", - kind: "stdlib", - name: "Date.prototype.getDate", - fns: ["date.getDate"], - }, - { - id: "stdlib.date.getUTCDate", - kind: "stdlib", - name: "Date.prototype.getUTCDate", - fns: ["date.getUTCDate"], - }, - { id: "stdlib.date.getDay", kind: "stdlib", name: "Date.prototype.getDay", fns: ["date.getDay"] }, - { - id: "stdlib.date.getUTCDay", - kind: "stdlib", - name: "Date.prototype.getUTCDay", - fns: ["date.getUTCDay"], - }, - { - id: "stdlib.date.getHours", - kind: "stdlib", - name: "Date.prototype.getHours", - fns: ["date.getHours"], - }, - { - id: "stdlib.date.getUTCHours", - kind: "stdlib", - name: "Date.prototype.getUTCHours", - fns: ["date.getUTCHours"], - }, - { - id: "stdlib.date.getMinutes", - kind: "stdlib", - name: "Date.prototype.getMinutes", - fns: ["date.getMinutes"], - }, - { - id: "stdlib.date.getUTCMinutes", - kind: "stdlib", - name: "Date.prototype.getUTCMinutes", - fns: ["date.getUTCMinutes"], - }, - { - id: "stdlib.date.getSeconds", - kind: "stdlib", - name: "Date.prototype.getSeconds", - fns: ["date.getSeconds"], - }, - { - id: "stdlib.date.getUTCSeconds", - kind: "stdlib", - name: "Date.prototype.getUTCSeconds", - fns: ["date.getUTCSeconds"], - }, - { - id: "stdlib.date.getMilliseconds", - kind: "stdlib", - name: "Date.prototype.getMilliseconds", - fns: ["date.getMilliseconds"], - }, - { - id: "stdlib.date.getUTCMilliseconds", - kind: "stdlib", - name: "Date.prototype.getUTCMilliseconds", - fns: ["date.getUTCMilliseconds"], - }, - { - id: "stdlib.date.getTimezoneOffset", - kind: "stdlib", - name: "Date.prototype.getTimezoneOffset", - fns: ["date.getTimezoneOffset"], - }, - { - id: "stdlib.date.toString", - kind: "stdlib", - name: "Date.prototype.toString", - fns: ["date.toString"], - note: "local time with the host zone's long English name; String(date) and template literals use it", - }, - { - id: "stdlib.date.toDateString", - kind: "stdlib", - name: "Date.prototype.toDateString", - fns: ["date.toDateString"], - }, - { - id: "stdlib.date.toTimeString", - kind: "stdlib", - name: "Date.prototype.toTimeString", - fns: ["date.toTimeString"], - }, - // ── perf_hooks (lowerPerfHooksCall): the monotonic clock. - { - id: "node-builtin.perf_hooks.performance.now", - kind: "node-builtin", - name: "perf_hooks.performance.now", - fns: ["perf.now"], - note: "the global performance object and the performance.now.bind(performance) function value reach the same clock", - }, - { - id: "node-builtin.perf_hooks.performance.mark", - kind: "node-builtin", - name: "perf_hooks.performance.mark", - fns: [ - "perf.mark", - "perf.entryName", - "perf.entryType", - "perf.entryStart", - "perf.entryDuration", - "perf.entryDetail", - "perf.entryKeep", - "perf.entryRef", - ], - note: "records a mark in the thread's performance timeline at the live clock or the given startTime", - }, - { - id: "node-builtin.perf_hooks.performance.measure", - kind: "node-builtin", - name: "perf_hooks.performance.measure", - fns: ["perf.measure"], - note: "the mark-name, timestamp, and options forms are one surface", - }, - { - id: "node-builtin.perf_hooks.performance.getEntries", - kind: "node-builtin", - name: "perf_hooks.performance.getEntries", - fns: ["perf.entries"], - note: "getEntries, getEntriesByName, and getEntriesByType read the same timeline", - }, - { - id: "node-builtin.perf_hooks.performance.clearMarks", - kind: "node-builtin", - name: "perf_hooks.performance.clearMarks", - fns: ["perf.clearMarks"], - }, - { - id: "node-builtin.perf_hooks.performance.clearMeasures", - kind: "node-builtin", - name: "perf_hooks.performance.clearMeasures", - fns: ["perf.clearMeasures"], - }, - { - id: "node-builtin.perf_hooks.performance.eventLoopUtilization", - kind: "node-builtin", - name: "perf_hooks.performance.eventLoopUtilization", - fns: ["perf.loopStart", "perf.idleTime"], - note: "active time is the main thread's CPU time since the loop started; the one- and two-argument difference forms are supported", - }, - { - id: "node-builtin.perf_hooks.monitorEventLoopDelay", - kind: "node-builtin", - name: "perf_hooks.monitorEventLoopDelay", - fns: [ - "perf.eldNew", - "perf.eldEnable", - "perf.eldDisable", - "perf.eldReset", - "perf.eldPercentile", - "perf.eldStat", - ], - note: "an unref'd sampling interval records the delay histogram; enable, disable, reset, percentile and the min, max, mean, stddev, count and exceeds readings are supported", - }, - // ── the process global's ambient reads and authority calls - // (lowerProcessProperty/lowerProcessMethodCall — process is a - // provenance-checked global here, not an importable module). - { - id: "node-builtin.process.env", - kind: "node-builtin", - name: "process.env", - fns: ["process.envGet", "process.envSet", "process.envUnset", "process.envPairs"], - note: "reads, writes, deletes, and enumeration of the process environment (the process global)", - }, - { - id: "node-builtin.process.argv", - kind: "node-builtin", - name: "process.argv", - fns: ["process.argv"], - }, - { - id: "node-builtin.process.hrtime", - kind: "node-builtin", - name: "process.hrtime", - fns: ["process.hrtimeValue"], - note: "native monotonic tuple clock and bigint member; direct calls and stored JavaScript callable values", - }, - { - id: "node-builtin.process.getBuiltinModule", - kind: "node-builtin", - name: "process.getBuiltinModule", - fns: ["process.builtinId", "process.builtinModule", "process.builtinUnsupported"], - note: "native path and os export subsets plus current-thread worker_threads metadata; other modules and exports throw SC2020", - }, - { - id: "node-builtin.process.versions", - kind: "node-builtin", - name: "process.versions", - fns: ["process.versions"], - note: "shared version dictionary containing node and openssl; other components are absent unless defined by the program", - }, - { - id: "node-builtin.process.release", - kind: "node-builtin", - name: "process.release", - fns: ["process.release"], - note: "the compatibility target's release metadata as one stable read-only object", - }, - { - id: "node-builtin.process.cwd", - kind: "node-builtin", - name: "process.cwd", - fns: ["process.cwd"], - }, - { - id: "node-builtin.process.chdir", - kind: "node-builtin", - name: "process.chdir", - fns: ["process.chdir"], - }, - { - id: "node-builtin.process.pid", - kind: "node-builtin", - name: "process.pid", - fns: ["process.pid"], - }, - { - id: "node-builtin.process.getuid", - kind: "node-builtin", - name: "process.getuid", - fns: ["process.getuid"], - }, - { - id: "node-builtin.process.getgid", - kind: "node-builtin", - name: "process.getgid", - fns: ["process.getgid"], - }, - { - id: "node-builtin.process.geteuid", - kind: "node-builtin", - name: "process.geteuid", - fns: ["process.geteuid"], - }, - { - id: "node-builtin.process.getegid", - kind: "node-builtin", - name: "process.getegid", - fns: ["process.getegid"], - }, - { - id: "node-builtin.process.getgroups", - kind: "node-builtin", - name: "process.getgroups", - fns: ["process.getgroups"], - }, - { - id: "node-builtin.process.ppid", - kind: "node-builtin", - name: "process.ppid", - fns: ["process.ppid"], - }, - { - id: "node-builtin.process.title", - kind: "node-builtin", - name: "process.title", - fns: ["process.title", "process.setTitle"], - note: "reads and writes of the process title are one surface", - }, - { - id: "node-builtin.process.argv0", - kind: "node-builtin", - name: "process.argv0", - fns: ["process.argv0"], - }, - { - id: "node-builtin.process.execArgv", - kind: "node-builtin", - name: "process.execArgv", - fns: ["process.execArgv"], - }, - { - id: "node-builtin.process.execPath", - kind: "node-builtin", - name: "process.execPath", - fns: ["process.execPath"], - }, - { - id: "node-builtin.process.uptime", - kind: "node-builtin", - name: "process.uptime", - fns: ["process.uptime"], - }, - { - id: "node-builtin.process.availableMemory", - kind: "node-builtin", - name: "process.availableMemory", - fns: ["process.availableMemory"], - }, - { - id: "node-builtin.process.constrainedMemory", - kind: "node-builtin", - name: "process.constrainedMemory", - fns: ["process.constrainedMemory"], - }, - { - id: "node-builtin.process.memoryUsage", - kind: "node-builtin", - name: "process.memoryUsage", - fns: ["process.memoryUsageTake", "process.memoryUsageField", "process.rss"], - note: "the record and memoryUsage.rss() read live memory state; external and arrayBuffers are 0", - }, - { - id: "node-builtin.process.resourceUsage", - kind: "node-builtin", - name: "process.resourceUsage", - fns: ["process.rusage"], - note: "getrusage's 16 fields — every field read samples live machine state", - }, - { - id: "node-builtin.process.cpuUsage", - kind: "node-builtin", - name: "process.cpuUsage", - fns: [ - "process.cpuUser", - "process.cpuSystem", - "process.cpuUserDiff", - "process.cpuSystemDiff", - "process.cpuPrevValidate", - ], - note: "the plain-sample and previous-value diff forms are one surface", - }, - { - id: "node-builtin.process.threadCpuUsage", - kind: "node-builtin", - name: "process.threadCpuUsage", - fns: [ - "process.threadCpuUser", - "process.threadCpuSystem", - "process.threadCpuUserDiff", - "process.threadCpuSystemDiff", - ], - note: "the plain-sample and previous-value diff forms are one surface", - }, - { - id: "node-builtin.process.isTTY", - kind: "node-builtin", - name: "process.isTTY", - fns: ["process.isTTY"], - note: "the isTTY read on process.stdin/stdout/stderr — one surface across the three streams", - }, - { - id: "node-builtin.process.columns", - kind: "node-builtin", - name: "process.columns", - fns: ["process.columns"], - note: "the columns read on the process stdio streams (terminal geometry)", - }, - { - id: "node-builtin.process.rows", - kind: "node-builtin", - name: "process.rows", - fns: ["process.rows"], - note: "the rows read on process.stdout/stderr (terminal geometry)", - }, - { - id: "node-builtin.process.kill", - kind: "node-builtin", - name: "process.kill", - fns: ["process.kill", "process.killNum"], - note: "the signal-name and signal-number forms are one surface", - }, - { - id: "node-builtin.process.umask", - kind: "node-builtin", - name: "process.umask", - fns: ["process.umask"], - }, - { - id: "node-builtin.process.exit", - kind: "node-builtin", - name: "process.exit", - fns: ["process.exit", "process.exiting"], - note: "process.exit and the process._exiting flag read are one surface", - }, - { - id: "node-builtin.process.exitCode", - kind: "node-builtin", - name: "process.exitCode", - fns: ["process.setExitCode", "process.setExitCodeTracked", "process.hasExitCode"], - note: "numeric writes in statement position set the implicit exit status; reads answer the assigned code or undefined; process.exit() reads it", - }, - // ── the tls CA store (lowerTlsCaCall / lowerTlsRootCertificates): the - // host's trust anchors, read and replaced. Dedicated paths, and - // rootCertificates is a VALUE read with no call form at all, so none of - // the three can hang off a lowering-table row. Split three ways rather - // than folded into one "CA store" surface: a library author who denies - // REPLACING the trust anchors is making a different decision from one - // who denies reading them, and each Node spelling is the name they will - // reach for when writing the fence. - { - id: "node-builtin.tls.getCACertificates", - kind: "node-builtin", - name: "tls.getCACertificates", - fns: ["tlsca.get"], - note: "the per-type cached PEM bundle: 'default' and 'extra' additionally read NODE_EXTRA_CA_CERTS, 'system' the platform store", - }, - { - id: "node-builtin.tls.rootCertificates", - kind: "node-builtin", - name: "tls.rootCertificates", - fns: ["tlsca.root"], - note: "the value read; answers the same bundled array as getCACertificates('bundled'), but fenced under its own id — the spelling an author writes", - }, - { - id: "node-builtin.tls.setDefaultCACertificates", - kind: "node-builtin", - name: "tls.setDefaultCACertificates", - fns: ["tlsca.set"], - note: "replaces the default set and the client trust anchors for the rest of the process", - }, - // os.constants is a VALUE read (lowerNamespaceBuiltinProperty and the - // named-import path): the platform's null-prototype constants object. - { - id: "node-builtin.os.constants", - kind: "node-builtin", - name: "os.constants", - fns: ["os.constants"], - note: "the platform's dlopen, errno, signals and priority groups; members a platform lacks read as undefined", - }, - // fs.constants as a value, and its members beyond the baked access-mode - // bits: the target libc's null-prototype constants object. - { - id: "node-builtin.fs.constants", - kind: "node-builtin", - name: "fs.constants", - fns: ["fs.constants"], - note: "node_constants.cc's fs group under the target libc's macros; members a platform lacks read as undefined", - }, -]; +let ambientSurfaceRows: readonly AmbientSurfaceRow[] | undefined; + +/** The ambient surface rows, built on the first call. */ +export function ambientSurfaceFns(): readonly AmbientSurfaceRow[] { + ambientSurfaceRows ??= buildAmbientSurfaceFns(); + return ambientSurfaceRows; +} + +function buildAmbientSurfaceFns(): readonly AmbientSurfaceRow[] { + return [ + { + id: "node-builtin.worker_threads.Worker", + kind: "node-builtin", + name: "worker_threads.Worker", + fns: ["worker.new"], + note: "native threads with statically compiled entry points, workerData, argv and message events", + }, + { + id: "node-builtin.worker_threads.metadata", + kind: "node-builtin", + name: "worker_threads metadata", + fns: [ + "worker.isMainThread", + "worker.threadId", + "worker.data", + "worker.parentPort", + "worker.resourceLimits", + "worker.threadName", + ], + note: "current-thread metadata and the worker's parent message port", + }, + { + id: "stdlib.sharedArrayBuffer", + kind: "stdlib", + name: "SharedArrayBuffer", + fns: ["sharedArrayBuffer.new"], + note: "fixed shared storage with context-local typed-array and DataView wrappers", + }, + { + id: "stdlib.blob", + kind: "stdlib", + name: "Blob", + fns: ["blob.new", "blob.slice", "blob.text", "blob.bytes", "blob.arrayBuffer", "blob.is"], + note: "immutable bytes with size, type, slice(), text(), bytes(), arrayBuffer() and structured cloning", + }, + { + id: "stdlib.file", + kind: "stdlib", + name: "File", + fns: ["blob.fileNew"], + note: "a Blob with name and lastModified (Date.now() when absent)", + }, + { + id: "stdlib.webcrypto.subtle", + kind: "stdlib", + name: "globalThis.crypto.subtle", + fns: [ + "webcrypto.generateKey", + "webcrypto.importKey", + "webcrypto.exportKey", + "webcrypto.encrypt", + "webcrypto.decrypt", + "webcrypto.wrapKey", + "webcrypto.unwrapKey", + "webcrypto.deriveBits", + "webcrypto.deriveKey", + "webcrypto.sign", + "webcrypto.verify", + "webcrypto.digest", + ], + note: "importKey, generateKey and exportKey (raw and jwk) for HMAC and AES keys, importKey for PBKDF2 and HKDF keys, AES-GCM, AES-CBC and AES-CTR encrypt and decrypt, wrapKey and unwrapKey of secret keys with AES-KW or those ciphers, PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign and verify, generateKey, importKey, exportKey, sign and verify for ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and RSA-PSS, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused", + }, + { + id: "stdlib.webcrypto.CryptoKey", + kind: "stdlib", + name: "CryptoKey", + fns: ["webcrypto.isCryptoKey"], + note: "secret HMAC, AES, PBKDF2 and HKDF keys with type, extractable, algorithm and usages, and instanceof CryptoKey", + }, + { + id: "stdlib.atomics", + kind: "stdlib", + name: "Atomics", + fns: ["atomics.op", "atomics.wait", "atomics.notify"], + note: "8-, 16- and 32-bit integer operations, plus Int32Array wait and notify", + }, + { + id: "node-builtin.fs.callbacks", + kind: "node-builtin", + name: "fs error-first callbacks", + fns: ["fs.callbackValue", "fs.callbackCall", "fs.callbackGlob"], + note: "native filesystem callbacks, including stored platform adapter functions", + }, + // ── the Date slice (lowerDateCall/lowerNew): statics, construction, + // stored read-only values, calendar getters, and ISO formatting. + { + id: "stdlib.date.now", + kind: "stdlib", + name: "Date.now", + fns: ["date.now"], + note: "the live clock", + }, + { + id: "stdlib.date.parse", + kind: "stdlib", + name: "Date.parse", + fns: ["date.parse"], + note: "one date string, using V8's date parser shared with new Date(dateString)", + }, + { + id: "stdlib.date.constructor", + kind: "stdlib", + name: "Date constructor", + fns: [ + "date.newNow", + "date.newMs", + "date.newString", + "date.nativeNew", + "date.newLocal", + "date.nativeNewFields", + "date.nativeNewValue", + "date.argumentsTime", + "date.classOperation", + ], + note: "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice", + }, + { + id: "stdlib.date.setters", + kind: "stdlib", + name: "Date.prototype setters", + fns: ["date.setFields", "date.timeClip", "date.store"], + note: "setTime and the local and UTC field setters, with their optional arguments", + }, + { + id: "stdlib.date.UTC", + kind: "stdlib", + name: "Date.UTC", + fns: ["date.utc"], + note: "the lowered call form takes 1 to 7 number arguments", + }, + { + id: "stdlib.date.getTime", + kind: "stdlib", + name: "Date.prototype.getTime", + fns: ["date.getTime", "date.parseGetTime"], + note: "the millisecond value of a stored Date", + }, + { + id: "stdlib.date.valueOf", + kind: "stdlib", + name: "Date.prototype.valueOf", + fns: ["date.valueOf"], + note: "the same millisecond read as getTime()", + }, + { + id: "stdlib.date.toISOString", + kind: "stdlib", + name: "Date.prototype.toISOString", + fns: ["date.toISOString", "date.toISOStringValue"], + note: "UTC ISO formatting over constructed and stored Date values", + }, + { + id: "stdlib.date.toUTCString", + kind: "stdlib", + name: "Date.prototype.toUTCString", + fns: ["date.toUTCString"], + note: "RFC 7231 IMF-fixdate formatting over stored Date values", + }, + { + id: "stdlib.date.getFullYear", + kind: "stdlib", + name: "Date.prototype.getFullYear", + fns: ["date.getFullYear"], + }, + { + id: "stdlib.date.getUTCFullYear", + kind: "stdlib", + name: "Date.prototype.getUTCFullYear", + fns: ["date.getUTCFullYear"], + }, + { + id: "stdlib.date.getMonth", + kind: "stdlib", + name: "Date.prototype.getMonth", + fns: ["date.getMonth"], + }, + { + id: "stdlib.date.getUTCMonth", + kind: "stdlib", + name: "Date.prototype.getUTCMonth", + fns: ["date.getUTCMonth"], + }, + { + id: "stdlib.date.getDate", + kind: "stdlib", + name: "Date.prototype.getDate", + fns: ["date.getDate"], + }, + { + id: "stdlib.date.getUTCDate", + kind: "stdlib", + name: "Date.prototype.getUTCDate", + fns: ["date.getUTCDate"], + }, + { + id: "stdlib.date.getDay", + kind: "stdlib", + name: "Date.prototype.getDay", + fns: ["date.getDay"], + }, + { + id: "stdlib.date.getUTCDay", + kind: "stdlib", + name: "Date.prototype.getUTCDay", + fns: ["date.getUTCDay"], + }, + { + id: "stdlib.date.getHours", + kind: "stdlib", + name: "Date.prototype.getHours", + fns: ["date.getHours"], + }, + { + id: "stdlib.date.getUTCHours", + kind: "stdlib", + name: "Date.prototype.getUTCHours", + fns: ["date.getUTCHours"], + }, + { + id: "stdlib.date.getMinutes", + kind: "stdlib", + name: "Date.prototype.getMinutes", + fns: ["date.getMinutes"], + }, + { + id: "stdlib.date.getUTCMinutes", + kind: "stdlib", + name: "Date.prototype.getUTCMinutes", + fns: ["date.getUTCMinutes"], + }, + { + id: "stdlib.date.getSeconds", + kind: "stdlib", + name: "Date.prototype.getSeconds", + fns: ["date.getSeconds"], + }, + { + id: "stdlib.date.getUTCSeconds", + kind: "stdlib", + name: "Date.prototype.getUTCSeconds", + fns: ["date.getUTCSeconds"], + }, + { + id: "stdlib.date.getMilliseconds", + kind: "stdlib", + name: "Date.prototype.getMilliseconds", + fns: ["date.getMilliseconds"], + }, + { + id: "stdlib.date.getUTCMilliseconds", + kind: "stdlib", + name: "Date.prototype.getUTCMilliseconds", + fns: ["date.getUTCMilliseconds"], + }, + { + id: "stdlib.date.getTimezoneOffset", + kind: "stdlib", + name: "Date.prototype.getTimezoneOffset", + fns: ["date.getTimezoneOffset"], + }, + { + id: "stdlib.date.toString", + kind: "stdlib", + name: "Date.prototype.toString", + fns: ["date.toString"], + note: "local time with the host zone's long English name; String(date) and template literals use it", + }, + { + id: "stdlib.date.toDateString", + kind: "stdlib", + name: "Date.prototype.toDateString", + fns: ["date.toDateString"], + }, + { + id: "stdlib.date.toTimeString", + kind: "stdlib", + name: "Date.prototype.toTimeString", + fns: ["date.toTimeString"], + }, + // ── perf_hooks (lowerPerfHooksCall): the monotonic clock. + { + id: "node-builtin.perf_hooks.performance.now", + kind: "node-builtin", + name: "perf_hooks.performance.now", + fns: ["perf.now"], + note: "the global performance object and the performance.now.bind(performance) function value reach the same clock", + }, + { + id: "node-builtin.perf_hooks.performance.mark", + kind: "node-builtin", + name: "perf_hooks.performance.mark", + fns: [ + "perf.mark", + "perf.entryName", + "perf.entryType", + "perf.entryStart", + "perf.entryDuration", + "perf.entryDetail", + "perf.entryKeep", + "perf.entryRef", + ], + note: "records a mark in the thread's performance timeline at the live clock or the given startTime", + }, + { + id: "node-builtin.perf_hooks.performance.measure", + kind: "node-builtin", + name: "perf_hooks.performance.measure", + fns: ["perf.measure"], + note: "the mark-name, timestamp, and options forms are one surface", + }, + { + id: "node-builtin.perf_hooks.performance.getEntries", + kind: "node-builtin", + name: "perf_hooks.performance.getEntries", + fns: ["perf.entries"], + note: "getEntries, getEntriesByName, and getEntriesByType read the same timeline", + }, + { + id: "node-builtin.perf_hooks.performance.clearMarks", + kind: "node-builtin", + name: "perf_hooks.performance.clearMarks", + fns: ["perf.clearMarks"], + }, + { + id: "node-builtin.perf_hooks.performance.clearMeasures", + kind: "node-builtin", + name: "perf_hooks.performance.clearMeasures", + fns: ["perf.clearMeasures"], + }, + { + id: "node-builtin.perf_hooks.performance.eventLoopUtilization", + kind: "node-builtin", + name: "perf_hooks.performance.eventLoopUtilization", + fns: ["perf.loopStart", "perf.idleTime"], + note: "active time is the main thread's CPU time since the loop started; the one- and two-argument difference forms are supported", + }, + { + id: "node-builtin.perf_hooks.monitorEventLoopDelay", + kind: "node-builtin", + name: "perf_hooks.monitorEventLoopDelay", + fns: [ + "perf.eldNew", + "perf.eldEnable", + "perf.eldDisable", + "perf.eldReset", + "perf.eldPercentile", + "perf.eldStat", + ], + note: "an unref'd sampling interval records the delay histogram; enable, disable, reset, percentile and the min, max, mean, stddev, count and exceeds readings are supported", + }, + // ── the process global's ambient reads and authority calls + // (lowerProcessProperty/lowerProcessMethodCall — process is a + // provenance-checked global here, not an importable module). + { + id: "node-builtin.process.env", + kind: "node-builtin", + name: "process.env", + fns: ["process.envGet", "process.envSet", "process.envUnset", "process.envPairs"], + note: "reads, writes, deletes, and enumeration of the process environment (the process global)", + }, + { + id: "node-builtin.process.argv", + kind: "node-builtin", + name: "process.argv", + fns: ["process.argv"], + }, + { + id: "node-builtin.process.hrtime", + kind: "node-builtin", + name: "process.hrtime", + fns: ["process.hrtimeValue"], + note: "native monotonic tuple clock and bigint member; direct calls and stored JavaScript callable values", + }, + { + id: "node-builtin.process.getBuiltinModule", + kind: "node-builtin", + name: "process.getBuiltinModule", + fns: ["process.builtinId", "process.builtinModule", "process.builtinUnsupported"], + note: "native path and os export subsets plus current-thread worker_threads metadata; other modules and exports throw SC2020", + }, + { + id: "node-builtin.process.versions", + kind: "node-builtin", + name: "process.versions", + fns: ["process.versions"], + note: "shared version dictionary containing node and openssl; other components are absent unless defined by the program", + }, + { + id: "node-builtin.process.release", + kind: "node-builtin", + name: "process.release", + fns: ["process.release"], + note: "the compatibility target's release metadata as one stable read-only object", + }, + { + id: "node-builtin.process.cwd", + kind: "node-builtin", + name: "process.cwd", + fns: ["process.cwd"], + }, + { + id: "node-builtin.process.chdir", + kind: "node-builtin", + name: "process.chdir", + fns: ["process.chdir"], + }, + { + id: "node-builtin.process.pid", + kind: "node-builtin", + name: "process.pid", + fns: ["process.pid"], + }, + { + id: "node-builtin.process.getuid", + kind: "node-builtin", + name: "process.getuid", + fns: ["process.getuid"], + }, + { + id: "node-builtin.process.getgid", + kind: "node-builtin", + name: "process.getgid", + fns: ["process.getgid"], + }, + { + id: "node-builtin.process.geteuid", + kind: "node-builtin", + name: "process.geteuid", + fns: ["process.geteuid"], + }, + { + id: "node-builtin.process.getegid", + kind: "node-builtin", + name: "process.getegid", + fns: ["process.getegid"], + }, + { + id: "node-builtin.process.getgroups", + kind: "node-builtin", + name: "process.getgroups", + fns: ["process.getgroups"], + }, + { + id: "node-builtin.process.ppid", + kind: "node-builtin", + name: "process.ppid", + fns: ["process.ppid"], + }, + { + id: "node-builtin.process.title", + kind: "node-builtin", + name: "process.title", + fns: ["process.title", "process.setTitle"], + note: "reads and writes of the process title are one surface", + }, + { + id: "node-builtin.process.argv0", + kind: "node-builtin", + name: "process.argv0", + fns: ["process.argv0"], + }, + { + id: "node-builtin.process.execArgv", + kind: "node-builtin", + name: "process.execArgv", + fns: ["process.execArgv"], + }, + { + id: "node-builtin.process.execPath", + kind: "node-builtin", + name: "process.execPath", + fns: ["process.execPath"], + }, + { + id: "node-builtin.process.uptime", + kind: "node-builtin", + name: "process.uptime", + fns: ["process.uptime"], + }, + { + id: "node-builtin.process.availableMemory", + kind: "node-builtin", + name: "process.availableMemory", + fns: ["process.availableMemory"], + }, + { + id: "node-builtin.process.constrainedMemory", + kind: "node-builtin", + name: "process.constrainedMemory", + fns: ["process.constrainedMemory"], + }, + { + id: "node-builtin.process.memoryUsage", + kind: "node-builtin", + name: "process.memoryUsage", + fns: ["process.memoryUsageTake", "process.memoryUsageField", "process.rss"], + note: "the record and memoryUsage.rss() read live memory state; external and arrayBuffers are 0", + }, + { + id: "node-builtin.process.resourceUsage", + kind: "node-builtin", + name: "process.resourceUsage", + fns: ["process.rusage"], + note: "getrusage's 16 fields — every field read samples live machine state", + }, + { + id: "node-builtin.process.cpuUsage", + kind: "node-builtin", + name: "process.cpuUsage", + fns: [ + "process.cpuUser", + "process.cpuSystem", + "process.cpuUserDiff", + "process.cpuSystemDiff", + "process.cpuPrevValidate", + ], + note: "the plain-sample and previous-value diff forms are one surface", + }, + { + id: "node-builtin.process.threadCpuUsage", + kind: "node-builtin", + name: "process.threadCpuUsage", + fns: [ + "process.threadCpuUser", + "process.threadCpuSystem", + "process.threadCpuUserDiff", + "process.threadCpuSystemDiff", + ], + note: "the plain-sample and previous-value diff forms are one surface", + }, + { + id: "node-builtin.process.isTTY", + kind: "node-builtin", + name: "process.isTTY", + fns: ["process.isTTY"], + note: "the isTTY read on process.stdin/stdout/stderr — one surface across the three streams", + }, + { + id: "node-builtin.process.columns", + kind: "node-builtin", + name: "process.columns", + fns: ["process.columns"], + note: "the columns read on the process stdio streams (terminal geometry)", + }, + { + id: "node-builtin.process.rows", + kind: "node-builtin", + name: "process.rows", + fns: ["process.rows"], + note: "the rows read on process.stdout/stderr (terminal geometry)", + }, + { + id: "node-builtin.process.kill", + kind: "node-builtin", + name: "process.kill", + fns: ["process.kill", "process.killNum"], + note: "the signal-name and signal-number forms are one surface", + }, + { + id: "node-builtin.process.umask", + kind: "node-builtin", + name: "process.umask", + fns: ["process.umask"], + }, + { + id: "node-builtin.process.exit", + kind: "node-builtin", + name: "process.exit", + fns: ["process.exit", "process.exiting"], + note: "process.exit and the process._exiting flag read are one surface", + }, + { + id: "node-builtin.process.exitCode", + kind: "node-builtin", + name: "process.exitCode", + fns: ["process.setExitCode", "process.setExitCodeTracked", "process.hasExitCode"], + note: "numeric writes in statement position set the implicit exit status; reads answer the assigned code or undefined; process.exit() reads it", + }, + // ── the tls CA store (lowerTlsCaCall / lowerTlsRootCertificates): the + // host's trust anchors, read and replaced. Dedicated paths, and + // rootCertificates is a VALUE read with no call form at all, so none of + // the three can hang off a lowering-table row. Split three ways rather + // than folded into one "CA store" surface: a library author who denies + // REPLACING the trust anchors is making a different decision from one + // who denies reading them, and each Node spelling is the name they will + // reach for when writing the fence. + { + id: "node-builtin.tls.getCACertificates", + kind: "node-builtin", + name: "tls.getCACertificates", + fns: ["tlsca.get"], + note: "the per-type cached PEM bundle: 'default' and 'extra' additionally read NODE_EXTRA_CA_CERTS, 'system' the platform store", + }, + { + id: "node-builtin.tls.rootCertificates", + kind: "node-builtin", + name: "tls.rootCertificates", + fns: ["tlsca.root"], + note: "the value read; answers the same bundled array as getCACertificates('bundled'), but fenced under its own id — the spelling an author writes", + }, + { + id: "node-builtin.tls.setDefaultCACertificates", + kind: "node-builtin", + name: "tls.setDefaultCACertificates", + fns: ["tlsca.set"], + note: "replaces the default set and the client trust anchors for the rest of the process", + }, + // os.constants is a VALUE read (lowerNamespaceBuiltinProperty and the + // named-import path): the platform's null-prototype constants object. + { + id: "node-builtin.os.constants", + kind: "node-builtin", + name: "os.constants", + fns: ["os.constants"], + note: "the platform's dlopen, errno, signals and priority groups; members a platform lacks read as undefined", + }, + // fs.constants as a value, and its members beyond the baked access-mode + // bits: the target libc's null-prototype constants object. + { + id: "node-builtin.fs.constants", + kind: "node-builtin", + name: "fs.constants", + fns: ["fs.constants"], + note: "node_constants.cc's fs group under the target libc's macros; members a platform lacks read as undefined", + }, + ]; +} /** The win32-target overrides of the bare modules' constants: path's * constants ARE the path/win32 namespace's, and os.EOL is CRLF (Node's @@ -2866,7 +2900,7 @@ const WIN32_TARGET_CONSTS: Record< os: { EOL: "\r\n", devNull: "\\\\.\\nul" }, }; -/** BUILTIN_MODULE_FNS with the platform-conditional modules bound per +/** builtinModuleFns() with the platform-conditional modules bound per * TARGET: a win32 triple compiles Node-on-Windows semantics — `path` is * path.win32 and url's bridge selects the Windows runtime behavior. Fence wording is * unaffected — callers keep naming the module the source spelled. */ @@ -2875,9 +2909,9 @@ function builtinModuleFnsOf( module: string, ): Record | undefined { if (lowerer.targetPlatform === "win32") { - if (module === "path") return BUILTIN_MODULE_FNS["path/win32"]; + if (module === "path") return builtinModuleFns()["path/win32"]; } - return ownEntry(BUILTIN_MODULE_FNS, module); + return ownEntry(builtinModuleFns(), module); } /** Own-property lookup — lowerer.ts's own(), duplicated locally because diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 44b5f63ec..3e4871084 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -46,6 +46,8 @@ export type { AnalyzeResult, } from "./compile-types.js"; import { compilePackedLibrary } from "./backend/library-pack.js"; +import { node24FetchCompatProfile } from "./compat/fetch-profile.js"; +import { libFnSignatures } from "./ir/builtin-signatures.js"; import { InternalCompilerError } from "./errors.js"; import { frontendProcessExecArgv } from "./frontend-process-args.js"; import { type ChildProcess, spawn } from "node:child_process"; @@ -249,7 +251,7 @@ export { type SurfaceManifestEntry, } from "./coverage/surface-manifest.js"; export { - NODE24_FETCH_COMPAT_PROFILE, + node24FetchCompatProfile, type FetchCompatEvidence, type FetchCompatFacet, type FetchCompatInventory, @@ -261,7 +263,13 @@ export { type FetchCompatOption, type FetchCompatProfile, } from "./compat/fetch-profile.js"; -export { LIB_FN_SIGS } from "./ir/builtin-signatures.js"; +export { libFnSignatures } from "./ir/builtin-signatures.js"; +/** @deprecated Use `libFnSignatures()`. Built when this module loads; the + * native compiler does not load this module. */ +export const LIB_FN_SIGS = libFnSignatures(); +/** @deprecated Use `node24FetchCompatProfile()`. Built when this module + * loads; the native compiler does not load this module. */ +export const NODE24_FETCH_COMPAT_PROFILE = node24FetchCompatProfile(); export { validateModule } from "./ir/validate.js"; export { assignLegacyTiers, diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e768f257f..916c64583 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -58,5058 +58,5103 @@ function defineLibFnSignatures( * together here; frontend coercion and IR validation consume the same table. * All arguments are required. Lowering supplies optional-source defaults * before this boundary and preserves their evaluation order. */ -export const LIB_FN_SIGS = defineLibFnSignatures({ - /** Native static fetch and its Web-platform companions. fetch.start - * answers once the response head arrives; the response body readers - * consume the native body stream. AbortSignal and ReadableStream values - * are opaque checked-dynamic handles. */ - "fetch.start": { argTypes: [STRING, DYN], result: { kind: "promise", inner: DYN } }, - "fetch.responseNew": { argTypes: [DYN, DYN], result: DYN }, - "fetch.responseJsonStatic": { argTypes: [DYN, DYN], result: DYN }, - "fetch.responseLight": { argTypes: [DYN], result: arrayOf(STRING) }, - "fetch.input": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "abort.controllerNew": { argTypes: [], result: DYN }, - /** EventTarget / Event / CustomEvent construction (scr_event_target.c): - * the Event constructors take the argument vector as one dyn array and - * throw Node's validation errors; `event.is` is `v instanceof Event` - * (kind 0), `CustomEvent` (1), `EventTarget` (2). addAbortListener - * answers the disposer (the Disposable's [Symbol.dispose]). */ - "event.targetNew": { argTypes: [], result: DYN }, - "event.new": { argTypes: [DYN], result: DYN }, - "event.customNew": { argTypes: [DYN], result: DYN }, - "event.is": { argTypes: [DYN, F64], result: BOOL }, - /** Compiled EventTarget and Event subclasses: the receiver is the - * instance (an object rooted at %EventTarget / %Event). targetInit and - * init are super() (init takes the argument vector as one dyn array - * and throws the constructor's validation errors; initType is the - * super(type) form with a string); targetCall runs an EventTarget method - * with the instance's own capsule as `self` (targetDispatch is its - * dispatchEvent(event) form, answering the bool); call, get and set - * reach the Event members. */ - "event.targetInit": { argTypes: [null], result: VOID }, - "event.init": { argTypes: [null, DYN], result: VOID }, - "event.initType": { argTypes: [null, STRING], result: VOID }, - /** %main hands the engine the compiled `listener.handleEvent` reader - * (lower-event-target.ts noteEventListeners). */ - "event.handleEventReader": { argTypes: [DYN], result: VOID }, - "event.targetDispatch": { argTypes: [null, DYN, DYN], result: BOOL }, - "event.targetCall": { argTypes: [null, DYN, STRING, DYN], result: DYN }, - "event.call": { argTypes: [null, STRING, DYN], result: DYN }, - "event.get": { argTypes: [null, STRING], result: DYN }, - "event.set": { argTypes: [null, STRING, DYN], result: VOID }, - "events.addAbortListener": { argTypes: [DYN, DYN], result: DYN }, - /** Blob / File (scr_blob.c): the constructors and slice() take their - * argument vector as one dyn array and throw Node's validation errors; - * the readers settle on Node's microtask turn for the Blob's parts. - * `blob.is` is `v instanceof Blob` (kind 0) or `File` (1). */ - "blob.new": { argTypes: [DYN], result: DYN }, - "blob.fileNew": { argTypes: [DYN], result: DYN }, - "blob.slice": { argTypes: [DYN, DYN], result: DYN }, - "blob.text": { argTypes: [DYN], result: { kind: "promise", inner: STRING } }, - "blob.bytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } }, - "blob.arrayBuffer": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "blob.is": { argTypes: [DYN, F64], result: BOOL }, - /** WebAssembly (scr_wasm.c): modules translated at build time - * (wasm/translate.ts), found at run time by the SHA-256 of their bytes. - * compile and instantiate over bytes settle on a later loop turn; - * instantiate over a Module settles now, as in Node. `wasm.is` answers - * instanceof for Module, Instance, Memory, Table and Global (kinds 0-4) - * and CompileError, LinkError and RuntimeError (5-7); `wasm.errorNew` - * constructs one of the error classes (kind 5-7, message, options). */ - "wasm.compile": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "wasm.validate": { argTypes: [DYN], result: BOOL }, - "wasm.instantiate": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "wasm.moduleNew": { argTypes: [DYN], result: DYN }, - "wasm.instanceNew": { argTypes: [DYN, DYN], result: DYN }, - "wasm.is": { argTypes: [DYN, F64], result: BOOL }, - "wasm.errorNew": { argTypes: [F64, DYN, DYN], result: DYN }, - /** new WebAssembly.Memory(descriptor), Table(descriptor, value) and - * Global(descriptor, value) (an undefined value is the type's default); - * `wasm.withoutNew` throws the TypeError for Module, Instance, Memory, - * Table or Global (kinds 0-4) called without new, after its arguments - * evaluate. */ - "wasm.memoryNew": { argTypes: [DYN], result: DYN }, - "wasm.tableNew": { argTypes: [DYN, DYN], result: DYN }, - "wasm.globalNew": { argTypes: [DYN, DYN], result: DYN }, - "wasm.withoutNew": { argTypes: [F64, DYN, DYN], result: DYN }, - /** WebAssembly.Module.imports(module), exports(module) and - * customSections(module, name): checked-dynamic arrays of descriptor - * objects and ArrayBuffers. */ - "wasm.moduleImports": { argTypes: [DYN], result: DYN }, - "wasm.moduleExports": { argTypes: [DYN], result: DYN }, - "wasm.moduleCustomSections": { argTypes: [DYN, DYN], result: DYN }, - /** `v.valueOf()` on a WebAssembly-typed receiver: a Global's value, - * dyn.valueOf's answer for anything else. */ - "wasm.valueOf": { argTypes: [DYN], result: DYN }, - /** WebAssembly.compile (0), instantiate (1) and validate (2) as function - * values, and the namespace object holding them. */ - "wasm.function": { argTypes: [F64], result: DYN }, - "wasm.namespace": { argTypes: [], result: DYN }, - /** The global object viewed through a cast to a type with a WebAssembly - * member: an object holding the namespace. */ - "wasm.globalView": { argTypes: [], result: DYN }, - "abort.timeout": { argTypes: [DYN], result: DYN }, - "abort.now": { argTypes: [DYN], result: DYN }, - "abort.any": { argTypes: [DYN], result: DYN }, - "fetch.function": { argTypes: [], result: DYN }, - "fetch.requestNew": { argTypes: [DYN, DYN], result: DYN }, - "fetch.headersNew": { argTypes: [DYN], result: DYN }, - "fetch.responseArrayBuffer": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "fetch.responseJson": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "fetch.responseText": { argTypes: [DYN], result: { kind: "promise", inner: STRING } }, - "fetch.responseBytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } }, - "fetch.abortControllerNew": { argTypes: [], result: DYN }, - "fetch.abortTimeout": { argTypes: [DYN], result: DYN }, - "fetch.abortNow": { argTypes: [DYN], result: DYN }, - "fetch.abortAny": { argTypes: [DYN], result: DYN }, - "fetch.streamNew": { argTypes: [DYN], result: DYN }, - "fetch.transformNew": { argTypes: [], result: DYN }, - // Program-dependent iterable: typed arrays/bytes/string stay intact so - // the native stream can pull lazily; checked-dynamic values are the - // fallback. The libCall validator in validate.ts checks the closed set. - "fetch.webIs": { argTypes: [DYN, STRING], result: BOOL }, - "fetch.streamIs": { argTypes: [DYN], result: BOOL }, - "fetch.streamFrom": { argTypes: [null], result: DYN }, - // The chunk/result record depends on ReadableStream; validated by validate.ts. - "fetch.readerRead": { argTypes: [DYN], result: VOID }, - /** Readable.fromWeb (scr_stream_from_web.c): the stream, objectMode, - * highWaterMark (< 0: the mode's default) and encoding ("": none). */ - "fetch.readableFromWeb": { argTypes: [DYN, BOOL, F64, STRING], result: VOID }, - "json.parse": { argTypes: [STRING], result: DYN }, - "json.parseReviver": { argTypes: [STRING, DYN], result: DYN }, - "json.stringifyReplacer": { argTypes: [DYN, DYN, STRING], result: DYN }, - "json.stringifyValue": { argTypes: [DYN, DYN, DYN], result: DYN }, - /** Keyed WRITE on a dyn value — `h.onDone = cb` / `h["k"] = v` on a - * checked-dynamic object (args: receiver, key string, value — all - * borrowed; the runtime copies the key and retains the value in). An - * OBJ receiver sets the member (later writes win, insertion order - * preserved — JS exactly); undefined/null throws Node's catchable - * "Cannot set properties of undefined (setting 'k')"; every other kind - * throws Node's STRICT-mode "Cannot create property 'k' on " — - * primitives quoting their rendering, V8's "on number '5'" - * (sloppy mode would ignore the write — suite tests are 'use strict'; - * SEMANTICS.md notes the sloppy divergence: the write throws). Void - * result; in the may-throw seed set. */ - "dyn.keySet": { argTypes: [DYN, STRING, DYN], result: VOID }, - "dyn.keySetComputed": { argTypes: [DYN, DYN, DYN], result: VOID }, - /** Delete an ordinary checked-native object's own key. Borrows both - * arguments; other receiver representations retain a runtime refusal. */ - "dyn.keyDelete": { argTypes: [DYN, STRING, BOOL], result: VOID }, - "dyn.keyDeleteComputed": { argTypes: [DYN, DYN, BOOL], result: VOID }, - "dyn.reflectDeleteProperty": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.hasKeyComputed": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.hasOwnComputed": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.propertyIsEnumerableComputed": { argTypes: [DYN, DYN], result: BOOL }, - /** Native own properties on globalThis keyed by symbol identity. */ - "dyn.globalSymbolGet": { argTypes: [SYMBOL_T], result: DYN }, - "dyn.globalSymbolSet": { argTypes: [SYMBOL_T, DYN], result: VOID }, - "dyn.globalSymbolHas": { argTypes: [SYMBOL_T], result: BOOL }, - "dyn.globalSymbolDelete": { argTypes: [SYMBOL_T], result: VOID }, - /** Exact native class capsule identity, without materializing its fields. */ - "dyn.typedRefIs": { argTypes: [DYN, STRING], result: BOOL }, - /** A capsule boxed by this program from exactly this class: one key - * pointer test, false for equal keys boxed elsewhere. Guards fast paths - * whose fallback handles every other value. The key is a literal. */ - "dyn.typedRefKeyIs": { argTypes: [DYN, STRING], result: BOOL }, - "dyn.classIs": { argTypes: [DYN, STRING], result: BOOL }, - /** Destructuring pack over a dyn source — `const [a, b] = d`, a - * destructured dyn callback param (args: the source and the STATIC - * TypeError spelling, "" when the source has none — both borrowed; - * result: a fresh dyn array, +1). Iterable kinds collect like spread - * (arrays element-by-element, strings by code point, bytes by byte); - * every other kind throws V8's destructuring TypeError — the spelling - * verbatim when non-empty, else the runtime kind wording ("number 5 is - * 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 }, - /** for await over a checked-dynamic value: the loop's iterator (a - * compiled async generator, Symbol.asyncIterator's result, or the sync - * iterator), and whether that iterator is synchronous. */ - "dyn.asyncIteratorOpen": { argTypes: [DYN, STRING], result: DYN }, - "dyn.asyncIteratorWasSync": { argTypes: [], result: BOOL }, - "dyn.iteratorResult": { argTypes: [DYN], result: DYN }, - "dyn.iteratorCanStep": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.iteratorStep": { argTypes: [DYN], result: DYN }, - "dyn.iteratorStepDone": { argTypes: [DYN], result: BOOL }, - "dyn.setGeneratorMembers": { argTypes: [DYN, DYN], result: VOID }, - "dyn.iteratorHelperMethod": { argTypes: [STRING], result: DYN }, - "dyn.collectionIteratorIndex": { argTypes: [DYN], result: F64 }, - "dyn.mapSeedEntries": { argTypes: [DYN], result: DYN }, - "dyn.mapSeedEntry": { argTypes: [DYN], result: DYN }, - /** The for-of-over-dyn pack accessors — the emitted index loop drives - * them over a dyn.iterPack result (ARR by construction). arrLen: the - * ARR length as f64 (0 for non-ARR kinds; arg borrowed). arrAt: the - * element at index (+1; the undefined singleton past the end). Neither - * throws. */ - "dyn.arrLen": { argTypes: [DYN], result: F64 }, - "dyn.arrAt": { argTypes: [DYN, F64], result: DYN }, - /** `key in v` with a RUNTIME (string) key on a checked-dynamic - * receiver (args: value dyn, key string; result bool): OBJ answers - * own-member presence, ARR answers 'length'/a valid index — exactly - * the literal-key dynHasKey path. Proxy has traps may throw. */ - "dyn.hasKey": { argTypes: [DYN, STRING], result: BOOL }, - /** The `in` operator with a runtime string key: dyn.hasKey plus the - * members inherited from builtin and compiled class prototypes. */ - "dyn.in": { argTypes: [DYN, STRING], result: BOOL }, - "dyn.freeze": { argTypes: [DYN], result: DYN }, - "dyn.isFrozen": { argTypes: [DYN], result: BOOL }, - /** Object.defineProperty on a declared class field (scr_json.c): the - * field's current value (borrowed), its FIELD_* attribute word, the - * instance's CLASS_* integrity word, the field name and the descriptor. - * Validates as Node does (TypeError "Cannot redefine property: f") and - * answers a one-property holder of the defined value and attributes (+1), - * read back with class.definedBits and class.definedValue. */ - "class.defineField": { argTypes: [DYN, F64, F64, STRING, DYN], result: DYN }, - "class.definedBits": { argTypes: [DYN, STRING], result: F64 }, - "class.definedValue": { argTypes: [DYN, STRING], result: DYN }, - /** Whether a property descriptor has a `value` member. */ - "class.descriptorHasValue": { argTypes: [DYN], result: BOOL }, - /** Throws Node's TypeError for a write to a read-only class field: the - * field name and the instance's class name. */ - "class.throwReadOnly": { argTypes: [STRING, STRING], result: VOID }, - /** Throws Node's TypeError for a class field initializer on a - * non-extensible instance: the field name. */ - "class.throwNotExtensible": { argTypes: [STRING], result: VOID }, - /** Object.freeze of a record whose shape is freezable (frozen-records.ts): - * sets the frozen bit in its rc word (scr_record_freeze). The receiver is - * any record type. */ - "record.freeze": { argTypes: [null], result: VOID }, - /** Whether a record is frozen: its rc word's frozen bit, set by - * record.freeze and dyn.freeze of a capsule. Emitted inline. The receiver - * is any record type. */ - "record.isFrozen": { argTypes: [null], result: BOOL }, - /** Throws Node's TypeError for a write to a frozen record: the key, the - * object's text in the message ("#") and the operation (0 assign, - * 1 add, 2 delete). */ - "record.throwFrozen": { argTypes: [STRING, STRING, F64], result: VOID }, - /** Object.freeze of an array (scr_arr_freeze): sets its frozen bit, which - * the write guards of IrModule.freezableArrays test, and isFrozen reads. - * The receiver is any array type. */ - "array.freeze": { argTypes: [null], result: VOID }, - "array.isFrozen": { argTypes: [null], result: BOOL }, - /** Object.freeze, seal or preventExtensions of a Map or Set - * (scr_map_prevent_extensions), which only makes it not extensible: its - * entries stay writable. The receiver is any map or set type. */ - "collection.preventExtensions": { argTypes: [null], result: VOID }, - "collection.isExtensible": { argTypes: [null], result: BOOL }, - "dyn.nativeSetNew": { argTypes: [DYN], result: DYN }, - "dyn.nativeSetIs": { argTypes: [DYN], result: BOOL }, - "dyn.nativeMapIs": { argTypes: [DYN], result: BOOL }, - "dyn.nativeUrlIs": { argTypes: [DYN], result: BOOL }, - "dyn.nativeDateIs": { argTypes: [DYN], result: BOOL }, - "dyn.nativeDateHandleIs": { argTypes: [DYN], result: BOOL }, - /** A builtin constructor (`Object`, `Date`, `RegExp`, `Map`, `Set`, - * `Promise`, `WeakMap`, `WeakSet`) read as a JavaScript value: one - * interned function per name, the same one dynamic values answer for - * `.constructor`. The name is a compiler-chosen literal. */ - "dyn.globalConstructor": { argTypes: [STRING], result: DYN }, - /** A builtin global function (setTimeout, setInterval, setImmediate, - * the clear functions, queueMicrotask, process.nextTick) read as a - * JavaScript value: one interned function per name whose calls run the - * timer APIs (it pulls in the event loop). The name is a compiler-chosen - * literal. */ - "dyn.builtinFunction": { argTypes: [STRING], result: DYN }, - "dyn.namespaceNew": { argTypes: [STRING, DYN], result: DYN }, - "dyn.namespaceDefine": { argTypes: [DYN, STRING, DYN, F64, F64], result: VOID }, - "date.nativeNew": { argTypes: [DYN], result: DYN }, - /** `new Date(year, monthIndex, ...)` in JavaScript: the argument array's - * first seven values pass through ToNumber in order (later ones are - * evaluated, never converted), then the local-time field constructor. */ - "date.nativeNewFields": { argTypes: [DYN], result: DYN }, - /** `new Date(value)` in JavaScript: a string argument parses with V8's - * full date parser; every other value constructs as date.nativeNew. */ - "date.nativeNewValue": { argTypes: [DYN], result: DYN }, - "date.argumentsTime": { argTypes: [DYN], result: DATE_T }, - "date.classOperation": { argTypes: [DYN, DYN], result: VOID }, - "date.checkedValue": { argTypes: [DYN], result: DATE_T }, - "dyn.nativeRegexIs": { argTypes: [DYN], result: BOOL }, - /** toString() on a checked-dynamic receiver: runtime kind dispatch - * (bytes decode per the literal encoding — utf8 default; strings, - * numbers, booleans, arrays, objects answer JS-exactly; undefined and - * null throw the catchable TypeError). */ - "dyn.toString": { argTypes: [DYN, DYN, STRING], result: STRING }, - "dyn.valueOf": { argTypes: [DYN, STRING], result: DYN }, - /** Object.defineProperties over dyn values (args: target, descriptors — - * both borrowed dyn; result: the target, +1 — JS's return value). - * Value descriptors become own properties on OBJ and FUNC targets; - * OBJ targets preserve data attributes. get/set - * descriptors and non-object targets/descriptors throw catchably - * (Node's TypeError texts; accessors the "not supported yet" Error). In - * the may-throw seed set. */ - "dyn.defineProps": { argTypes: [DYN, DYN], result: DYN }, - "dyn.defineProperty": { argTypes: [DYN, DYN, DYN], result: DYN }, - "dyn.getOwnPropertyDescriptor": { argTypes: [DYN, DYN], result: DYN }, - "dyn.arrayProtoCall": { argTypes: [DYN, STRING, DYN], result: DYN }, - /** Promise.all / Promise.allSettled / Promise.any over a spread - * checked-dynamic array: the values, the settled-record array, or the - * first fulfillment (AggregateError once every entry rejects). - * Thenable elements adopt; the second argument reads a program class - * instance's `then` (the compiled property dispatch). */ - "dyn.promiseAll": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "dyn.promiseAllSettled": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "dyn.promiseAny": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - /** PromiseResolve over one checked-dynamic value: the promise it holds, - * a promise that adopts a thenable's settlement, or a promise fulfilled - * with the value. The second argument reads `then` as above. */ - "dyn.promiseResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - /** Bare `typeof v` on a dyn value AS A STRING (arg: the dyn value, - * borrowed; result: an owned string) — the dyn kind's JS answer: - * undefined→"undefined", null/object/array/bytes→"object" (JS's oldest - * wart preserved), boolean/number/string by kind, function→"function". - * Never throws. */ - "dyn.typeof": { argTypes: [DYN], result: STRING }, - /** Object.prototype.toString.call on a checked-dynamic value. */ - "dyn.objectTag": { argTypes: [DYN], result: STRING }, - /** Engine-free CommonJS module graph. Module values are scalar f64 - * handles; generated startup defines the registry, init wrappers update - * loading/cache state, and the read surface answers Node's live metadata. */ - "module.registryInit": { argTypes: [F64], result: VOID }, - "module.define": { argTypes: [F64, STRING, STRING, STRING, arrayOf(STRING), BOOL], result: VOID }, - "module.enter": { argTypes: [F64], result: VOID }, - "module.link": { argTypes: [F64, F64], result: VOID }, - "module.finish": { argTypes: [F64], result: VOID }, - "module.fail": { argTypes: [F64], result: VOID }, - "module.filename": { argTypes: [F64], result: STRING }, - "module.id": { argTypes: [F64], result: STRING }, - "module.path": { argTypes: [F64], result: STRING }, - "module.paths": { argTypes: [F64], result: arrayOf(STRING) }, - "module.children": { argTypes: [F64], result: arrayOf(F64) }, - "module.parent": { argTypes: [F64], result: F64 }, - "module.loaded": { argTypes: [F64], result: BOOL }, - "module.cacheGet": { argTypes: [STRING], result: F64 }, - "module.cacheHas": { argTypes: [STRING], result: BOOL }, - "module.cacheKeys": { argTypes: [], result: arrayOf(STRING) }, - "timers.setTimeout": { argTypes: [{ kind: "func", params: [], ret: VOID }, F64], result: VOID }, - /** The repeating timer pair. setInterval takes (callback, ms) like - * setTimeout and RETURNS the f64 handle the fallback declarations - * promise (ids start at 1, so truthiness narrowing works); the loop - * owns the callback until clearInterval removes the entry (eagerly — a - * live interval keeps the loop alive, a cleared one releases it, like - * Node). Neither throws; a throw ESCAPING an interval callback ends the - * program like a setTimeout throw. */ - "timers.setInterval": { argTypes: [{ kind: "func", params: [], ret: VOID }, F64], result: F64 }, - "timers.clearInterval": { argTypes: [F64], result: VOID }, - /** setTimeout WITH a clear handle (the f64 id) — the clearable/unref-able - * one-shot; clearTimeout cancels it, .unref() drops it from loop - * liveness. The plain timers.setTimeout stays the handle-less - * fire-and-forget. */ - "timers.setTimeoutHandle": { - argTypes: [{ kind: "func", params: [], ret: VOID }, F64], - result: F64, - }, - "timers.clearTimeout": { argTypes: [F64], result: VOID }, - /** Timeout.unref()/ref()/hasRef() — loop-liveness bookkeeping over the - * handle id. unref/ref RETURN the handle (f64) for chaining; hasRef - * returns bool. */ - "timers.unref": { argTypes: [F64], result: F64 }, - "timers.ref": { argTypes: [F64], result: F64 }, - "timers.hasRef": { argTypes: [F64], result: BOOL }, - /** Timeout.refresh() — re-arm to now + the original delay (from the - * heap or from inside the firing callback; a one-shot that fired on an - * earlier turn is gone and no-ops — documented divergence). Returns - * the handle for chaining like unref/ref. */ - "timers.refresh": { argTypes: [F64], result: F64 }, - /** setImmediate/clearImmediate — Node's check phase: callbacks run - * once per loop turn AFTER due timers, FIFO, and immediates queued - * mid-phase wait for the next turn. setImmediate returns the f64 - * handle (its own id space — clearTimeout of an Immediate no-ops, - * like Node); the Immediate ref trio mirrors the Timeout one (an - * unref'd pending immediate neither keeps the loop alive nor fires - * once nothing reffed remains). */ - "timers.setImmediate": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: F64 }, - /** queueMicrotask (scr_async.c): the callback enters the SAME FIFO - * promise continuations ride — one microtask order, like V8's queue — - * and a throw is an UNCAUGHT exception (never a rejection). - * timers.queueMicrotask takes an owned zero-param closure and never - * throws; the Dyn form (checked-dynamic arguments — the mustCall - * wrapper, the suite's invalid-input probes) throws Node's - * ERR_INVALID_ARG_TYPE synchronously on a non-function value and calls - * the function with zero arguments at drain. */ - "timers.queueMicrotask": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, - "timers.queueMicrotaskDyn": { argTypes: [DYN], result: VOID }, - "timers.clearImmediate": { argTypes: [F64], result: VOID }, - /** process.nextTick(cb, ...args) — the user tick queue. args: [cb - * (() => void; trailing call arguments ride the timer surface's - * interned dyn thunk)]. Ticks drain BEFORE promise jobs at every loop - * checkpoint, to joint exhaustion with them (Node's tick-then- - * microtask order); ticks enqueued by station listeners run at the - * NEXT checkpoint (the stream-tick station divergence, SEMANTICS.md). - * Pending ticks are always-ready work — the loop neither sleeps nor - * exits while any exist; ticks scheduled from 'exit' listeners never - * run (Node). The enqueue itself never throws. */ - "process.nextTick": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, - /** The process introspection statics. uptime: seconds since the - * binary's own start (fractional — a load-time monotonic anchor). - * availableMemory/constrainedMemory: libuv's numbers (free-ish bytes; - * the cgroup cap or 0). cpuUser/cpuSystem (+ threadCpu twins): the - * process/thread CPU clocks in microseconds — the frontend composes - * the {user, system} records. The *Diff forms answer current − prev - * for one field; cpuPrevValidate throws Node's ERR_INVALID_ARG_VALUE - * RangeError for negative/non-finite prev fields (user first, then - * system — Node's order; MAY THROW). rusage(idx): one - * process.resourceUsage() field by canonical index (Node's units). - * activeResources: the loop's own bookkeeping — 'Timeout' per armed - * (or firing, uncleared) timer, 'Immediate' per queued unfired - * immediate; unmodeled resource kinds are absent (SEMANTICS.md). */ - "process.uptime": { argTypes: [], result: F64 }, - /** perf_hooks performance.now(): fractional ms since process start. */ - "perf.now": { argTypes: [], result: F64 }, - /** perf_hooks user timing over the thread's performance timeline. The - * producing calls take the call's whole argument vector as one dyn - * array (Node's validation and errors run in the runtime, MAY THROW): - * mark/measure answer 1, entries(args, which) the entry count of - * getEntries (0), getEntriesByName (1) or getEntriesByType (2). The - * entryName/Type/Start/Duration/Detail accessors read entry i of that - * answer; the frontend builds a new entry's record from them and hands - * it to entryKeep (a typed reference capsule), and the getters read the - * kept records back through entryRef, so entries keep their identity. */ - "perf.mark": { argTypes: [DYN], result: F64 }, - "perf.measure": { argTypes: [DYN], result: F64 }, - "perf.entries": { argTypes: [DYN, F64], result: F64 }, - "perf.clearMarks": { argTypes: [DYN], result: VOID }, - "perf.clearMeasures": { argTypes: [DYN], result: VOID }, - "perf.entryName": { argTypes: [F64], result: STRING }, - "perf.entryType": { argTypes: [F64], result: STRING }, - "perf.entryStart": { argTypes: [F64], result: F64 }, - "perf.entryDuration": { argTypes: [F64], result: F64 }, - "perf.entryDetail": { argTypes: [F64], result: DYN }, - "perf.entryKeep": { argTypes: [DYN], result: VOID }, - "perf.entryRef": { argTypes: [F64], result: DYN }, - /** perf_hooks event-loop metrics (scr_perf_loop.c): loopStart is the - * loop's start in performance.now() ms (0 before it), idleTime the idle - * ms since. eldNew(resolution ms) creates a monitorEventLoopDelay - * histogram handle (MAY THROW Node's resolution RangeError); enable/disable answer whether the state changed; - * eldPercentile expects Node's validated range; eldStat(which) reads - * 0 min, 1 max, 2 mean, 3 stddev, 4 count, 5 exceeds. */ - "perf.loopStart": { argTypes: [], result: F64 }, - "perf.idleTime": { argTypes: [], result: F64 }, - "perf.eldNew": { argTypes: [F64], result: F64 }, - "perf.eldEnable": { argTypes: [F64], result: BOOL }, - "perf.eldDisable": { argTypes: [F64], result: BOOL }, - "perf.eldReset": { argTypes: [F64], result: VOID }, - "perf.eldPercentile": { argTypes: [F64, F64], result: F64 }, - "perf.eldStat": { argTypes: [F64, F64], result: F64 }, - /** process.memoryUsage(): memoryUsageTake samples the resident set and - * the runtime heap once (MAY THROW Node's uv_resident_set_memory error) - * and memoryUsageField(k) reads that sample (0 rss, 1 heapTotal, 2 - * heapUsed, 3 external, 4 arrayBuffers). rss is memoryUsage.rss(). */ - "process.memoryUsageTake": { argTypes: [], result: VOID }, - "process.memoryUsageField": { argTypes: [F64], result: F64 }, - "process.rss": { argTypes: [], result: F64 }, - "process.availableMemory": { argTypes: [], result: F64 }, - "process.constrainedMemory": { argTypes: [], result: F64 }, - "process.cpuUser": { argTypes: [], result: F64 }, - "process.cpuSystem": { argTypes: [], result: F64 }, - "process.cpuUserDiff": { argTypes: [F64], result: F64 }, - "process.cpuSystemDiff": { argTypes: [F64], result: F64 }, - "process.threadCpuUser": { argTypes: [], result: F64 }, - "process.threadCpuSystem": { argTypes: [], result: F64 }, - "process.threadCpuUserDiff": { argTypes: [F64], result: F64 }, - "process.threadCpuSystemDiff": { argTypes: [F64], result: F64 }, - "process.cpuPrevValidate": { argTypes: [F64, F64], result: VOID }, - "process.rusage": { argTypes: [F64], result: F64 }, - "process.activeResources": { argTypes: [], result: arrayOf(STRING) }, - "timers.immediateUnref": { argTypes: [F64], result: F64 }, - "timers.immediateRef": { argTypes: [F64], result: F64 }, - "timers.immediateHasRef": { argTypes: [F64], result: BOOL }, - /** The tolerated non-handle clear (`clearTimeout(null)`, - * `clearInterval({})`, zero-argument forms): Node ignores anything - * that is not a live handle without an error — a VOID no-op the emitter drops - * (only syntactically side-effect-free arguments take this path). */ - "timers.clearNoop": { argTypes: [], result: VOID }, - // Signal listeners are zero-param (the ambient shape); exit/stdin - // callbacks carry program-dependent one-param shapes — null slots, the - // libCall case checks them (child.onExit precedent). - /** Named process signal events: [name string, callback dyn, once bool] - * or [name, callback] for removal. The runtime resolves platform signal - * numbers and delivers (name, number). Listeners do not keep the loop - * alive. Invalid callbacks and uncatchable signals throw. */ - "process.onSignal": { argTypes: [STRING, DYN, BOOL], result: VOID }, - "process.offSignal": { argTypes: [STRING, DYN], result: VOID }, - /** The process 'exit' event — process.on/once/off("exit"). args: [cb, - * once bool] / [cb]. Listeners run SYNCHRONOUSLY at termination - * (normal exit, process.exit(), the uncaught/unhandled exit-1 paths) - * with the exit code; anything they schedule never runs, like Node. - * Callback shapes: () => void or (code: number) => void — the emitter - * picks the runtime adapter. Never throw. */ - "process.onExit": { argTypes: [null, BOOL], result: VOID }, - "process.offExit": { argTypes: [null], result: VOID }, - /** process.stdin listener registration — stdin.on/once("data" | "end" | - * "error", cb). args: [cb, once bool]. data callbacks: () => void or - * (chunk: Uint8Array) => void; end: () => void; error: () => void or - * (err: Error) => void (the child error adapters are reused). While a - * data listener (or a parked for-await chunk promise) exists, stdin - * keeps the loop alive — Node's flowing-stdin keep-alive. Never - * throw. */ - "stdin.onData": { argTypes: [null, BOOL], result: VOID }, - "stdin.onEnd": { argTypes: [{ kind: "func", params: [], ret: VOID }, BOOL], result: VOID }, - "stdin.onError": { argTypes: [null, BOOL], result: VOID }, - /** process.stdin.on/once('keypress', cb) (scr_readline_tty.c): cb is the - * listener as a checked-dynamic function, called with (str, key). */ - "stdin.onKeypress": { argTypes: [DYN, BOOL], result: VOID }, - /** The for-await chunk source over process.stdin: no args, result - * Promise (+1). Fulfills with the next arrived chunk; the - * EMPTY bytes value is the done sentinel (POSIX reads never deliver - * empty data chunks), which the for-await desugar turns into loop - * exit. Never throws itself; awaiting it re-throws nothing (stdin - * errors surface through 'error' listeners, not the iterator). */ - "stdin.nextChunk": { argTypes: [], result: { kind: "promise", inner: BYTES_U8 } }, - "fs.readFileSync": { argTypes: [STRING, STRING], result: STRING }, - "fs.readFileEncoded": { argTypes: [STRING, STRING], result: STRING }, - "fs.readFdEncoded": { argTypes: [F64, STRING], result: STRING }, - /** readFileSync(path) — the Buffer read (+1 bytes); throws catchably - * like the utf8 form. */ - "fs.readFileSyncBuf": { argTypes: [STRING], result: BYTES_U8 }, - /** readFileSync(path, enc) with a RUNTIME encoding (an untyped JS - * parameter): undefined/null answer Buffers, utf8 a string, other real - * encodings throw the "not supported yet" Error, unknown names throw - * ERR_UNKNOWN_ENCODING. */ - "fs.readFileSyncDyn": { argTypes: [STRING, DYN], result: DYN }, - "fs.writeFileSync": { argTypes: [STRING, STRING], result: VOID }, - "fs.appendFileSync": { argTypes: [STRING, STRING], result: VOID }, - "fs.existsSync": { argTypes: [STRING], result: BOOL }, - "fs.mkdirSync": { argTypes: [STRING], result: VOID }, - "fs.rmSync": { argTypes: [STRING], result: VOID }, - "fs.rmdirSync": { argTypes: [STRING], result: VOID }, - "fs.readdirSync": { argTypes: [STRING], result: arrayOf(STRING) }, - // Result is the call site's Dirent record array (VOID is the - // structure-checked sentinel, the os.networkInterfaces pattern). - /** `fs.readdirSync(path, { withFileTypes: true })` — Dirent rows over - * one readdir pass (scr_lib.c's scandir snapshot; DT_UNKNOWN falls back - * to lstat, Node's getDirents rule). The result type is the call site's - * interned Dirent record array (name, parentPath, hidden %dtype in - * libuv's UV_DIRENT encoding) — verified by the frontend; the emitter - * assembles the rows from the snapshot. Byte order on POSIX (libuv - * sorts), the directory's order on Windows; no "."/"..". Throws - * Node's scandir errno error (may-throw seed set); fresh +1 array. */ - "fs.readdirTypesSync": { argTypes: [STRING], result: VOID }, - /** fs.globSync / fs.glob / fs/promises glob (scr_glob.c, Node's - * lib/internal/fs/glob.js walk): pattern and options are dyn values the - * runtime validates in Node's order. The string forms answer string[]; - * the Dirent forms answer the call site's Dirent record array (validated - * in validate.ts) assembled from the snapshot; fs.callbackGlob takes the - * argument vector and calls back with string[]. */ - "fs.globSync": { argTypes: [DYN, DYN], result: arrayOf(STRING) }, - "fs.globTypesSync": { argTypes: [DYN, DYN], result: VOID }, - "fs.callbackGlob": { argTypes: [DYN], result: VOID }, - "fsp.globStrings": { argTypes: [DYN, DYN], result: arrayOf(STRING) }, - "fsp.globTypes": { argTypes: [DYN, DYN], result: VOID }, - /** The error an fs/promises glob walk ended with (undefined when none), - * read once right after fsp.globStrings / fsp.globTypes. */ - "fsp.globError": { argTypes: [], result: DYN }, - /** fs.Dir (scr_lib.c): opendirSync(path, bufferSize) throws Node's - * opendir error (or the options.bufferSize RangeError); dir.readSync - * answers the call site's `Dirent | null` (VOID here — the emitter builds - * the row like readdirTypesSync) and dir.read is its promise twin; - * readSync and closeSync throw ERR_DIR_CLOSED on a closed Dir, read and - * close reject with it. */ - "fs.opendirSync": { argTypes: [STRING, F64], result: DIR_T }, - "fsp.opendir": { argTypes: [STRING, F64], result: { kind: "promise", inner: DIR_T } }, - "dir.path": { argTypes: [DIR_T], result: STRING }, - "dir.readSync": { argTypes: [DIR_T], result: VOID }, - "dir.read": { argTypes: [DIR_T], result: { kind: "promise", inner: VOID } }, - "dir.closeSync": { argTypes: [DIR_T], result: VOID }, - "dir.close": { argTypes: [DIR_T], result: { kind: "promise", inner: VOID } }, - /** node:path (scr_path.c ports BOTH of Node's implementations - * function-by-function): the `path.*` family is posix, the - * `path.win32*` family is Node v24's path.win32 ported exactly — the - * frontend binds the bare module to the TARGET platform's family - * (Node on Windows IS path.win32) and the path.posix / path.win32 - * namespaces to their own family everywhere. join and resolve take ONE - * string[] arg — the frontend packs the variadic call's arguments into - * an array literal. basename always receives its suffix (the frontend - * completes an omitted one with "", a Node no-op). toNamespacedPath is - * the posix identity and the win32 \\?\-prefixer. None of these throw; - * the resolves consult the process cwd like Node's. */ - "path.join": { argTypes: [arrayOf(STRING)], result: STRING }, - "path.resolve": { argTypes: [arrayOf(STRING)], result: STRING }, - "path.normalize": { argTypes: [STRING], result: STRING }, - "path.dirname": { argTypes: [STRING], result: STRING }, - "path.basename": { argTypes: [STRING, STRING], result: STRING }, - "path.extname": { argTypes: [STRING], result: STRING }, - "path.isAbsolute": { argTypes: [STRING], result: BOOL }, - "path.relative": { argTypes: [STRING, STRING], result: STRING }, - "path.toNamespacedPath": { argTypes: [STRING], result: STRING }, - "path.win32Join": { argTypes: [arrayOf(STRING)], result: STRING }, - "path.win32Resolve": { argTypes: [arrayOf(STRING)], result: STRING }, - "path.win32Normalize": { argTypes: [STRING], result: STRING }, - "path.win32Dirname": { argTypes: [STRING], result: STRING }, - "path.win32Basename": { argTypes: [STRING, STRING], result: STRING }, - "path.win32Extname": { argTypes: [STRING], result: STRING }, - "path.win32IsAbsolute": { argTypes: [STRING], result: BOOL }, - "path.win32Relative": { argTypes: [STRING, STRING], result: STRING }, - "path.win32ToNamespacedPath": { argTypes: [STRING], result: STRING }, - /** path.parse: [root, dir, base, ext, name] as a fresh string[] (the - * frontend builds the ParsedPath record); path.format: the pathObject's - * root, dir, base, name and ext with undefined spelled "". */ - "path.parse": { argTypes: [STRING], result: arrayOf(STRING) }, - "path.win32Parse": { argTypes: [STRING], result: arrayOf(STRING) }, - "path.format": { argTypes: [STRING, STRING, STRING, STRING, STRING], result: STRING }, - "path.win32Format": { argTypes: [STRING, STRING, STRING, STRING, STRING], result: STRING }, - /** path.matchesGlob(path, pattern): Node's matchGlobPattern over its - * bundled minimatch (scr_glob.c). Both families match case-insensitively - * on macOS and Windows hosts like Node's; win32 also accepts either - * separator. Throws TypeError for a pattern over 64 Ki code units, - * RangeError for a brace sequence too long to expand, and SyntaxError - * when a segment's regex is invalid under the u flag. */ - "path.matchesGlob": { argTypes: [STRING, STRING], result: BOOL }, - "path.win32MatchesGlob": { argTypes: [STRING, STRING], result: BOOL }, - /** node:os: homedir is $HOME else getpwuid(3); tmpdir is Node's env - * cascade ($TMPDIR/$TMP/$TEMP else /tmp, one trailing slash trimmed). - * os.platform() lowers to process.platform — one implementation. */ - "os.homedir": { argTypes: [], result: STRING }, - /** os.release(): uname(2)'s release field — Node's own implementation - * (the kernel version string, e.g. "24.6.0" on macOS 15). Interned; +1 - * per read. Never throws. */ - "os.release": { argTypes: [], result: STRING }, - /** os.type(): uname(2)'s sysname ("Darwin", "Linux"; "Windows_NT" on - * win32) — Node's uv_os_uname answer. Interned per call; never throws. */ - "os.type": { argTypes: [], result: STRING }, - /** os.totalmem(): total physical memory in bytes. Never throws. */ - "os.totalmem": { argTypes: [], result: F64 }, - /** os.availableParallelism(): CPUs the process may use (libuv's answer), >= 1. Never throws. */ - "os.availableParallelism": { argTypes: [], result: F64 }, - /** os.hostname/machine/version/endianness, freemem, uptime and loadavg: - * libuv's answers (uname's machine and version fields, gethostname, - * "LE"/"BE", free memory in bytes, uptime in seconds, the three load - * averages). Fresh results; never throw. */ - "os.hostname": { argTypes: [], result: STRING }, - "os.machine": { argTypes: [], result: STRING }, - "os.version": { argTypes: [], result: STRING }, - "os.endianness": { argTypes: [], result: STRING }, - "os.freemem": { argTypes: [], result: F64 }, - "os.uptime": { argTypes: [], result: F64 }, - "os.loadavg": { argTypes: [], result: arrayOf(F64) }, - /** os.getPriority(pid): validateInt32 (RangeError ERR_OUT_OF_RANGE), - * then the priority; a failure throws Node's SystemError. */ - "os.getPriority": { argTypes: [F64], result: F64 }, - /** os.setPriority(pid, priority): validateInt32 for both (priority in - * -20..19, RangeError ERR_OUT_OF_RANGE), then the priority; a failure - * throws Node's SystemError. */ - "os.setPriority": { argTypes: [F64, F64], result: VOID }, - /** os.cpus(): cpusTake snapshots every CPU and answers the count; - * cpusModel(i) and cpusField(i, k) (k: 0 speed, 1 user, 2 nice, 3 sys, - * 4 idle, 5 irq) read the snapshot. Never throw. */ - "os.cpusTake": { argTypes: [], result: F64 }, - "os.cpusModel": { argTypes: [F64], result: STRING }, - "os.cpusField": { argTypes: [F64, F64], result: F64 }, - /** os.constants: the per-thread null-prototype constants object (dlopen, - * errno, signals, priority, UV_UDP_REUSEADDR). Never throws. */ - "os.constants": { argTypes: [], result: DYN }, - /** fs.constants: the target's cached null-prototype constants object. */ - "fs.constants": { argTypes: [], result: DYN }, - /** The deprecated node:constants object (scr_os.c): the os groups and - * fs.constants merged with the borrowed baked crypto.constants table, - * frozen, one per thread. */ - "constants.object": { argTypes: [DYN], result: DYN }, - /** umask(2): arg < 0 reads without setting (umask has no read-only form - * — set 0, restore); otherwise sets and answers the PREVIOUS mask. - * Never throws. */ - "process.umask": { argTypes: [F64], result: F64 }, - /** chdir(2) — throws Node's fs-shaped error (ENOENT/EACCES/ENOTDIR, - * syscall "chdir") on failure. */ - "process.chdir": { argTypes: [STRING], result: VOID }, - "process.loadEnvFile": { argTypes: [DYN], result: VOID }, - /** process._exiting: true once the exit sequence began (exit listeners - * running) — the runtime flag scr_run_exit_listeners/process.exit set. - * Never throws. */ - "process.exiting": { argTypes: [], result: BOOL }, - /** net's process-wide happy-eyeballs attempt budget (Node's default - * 250ms): one runtime double in the core unit, so reading/writing it - * never forces the net unit into the link. Never throw. */ - "net.getAutoSelTimeout": { argTypes: [], result: F64 }, - "net.setAutoSelTimeout": { argTypes: [F64], result: VOID }, - /** realpath(3) with Node's error shape (syscall "lstat" in the message, - * Node's own spelling for realpathSync failures). +1 fresh string. */ - "fs.realpathSync": { argTypes: [STRING], result: STRING }, - /** fs.realpathSync (or its .native when the flag is true) as a function - * value: path and options arrive unchecked; the result is a string or a - * Buffer by the options' encoding. */ - "fs.realpathValue": { argTypes: [DYN, DYN, BOOL], result: DYN }, - /** Native realpath with syscall "realpath" on failure. +1 fresh string. */ - "fs.realpathNativeSync": { argTypes: [STRING], result: STRING }, - /** The os.userInfo() field trio (uv_os_get_passwd's slices): pw_name, - * pw_shell, pw_dir — the PASSWD homedir, not os.homedir's $HOME-first - * cascade (Node's own split). The frontend assembles the UserInfo - * record from these plus getuid/getgid. +1 fresh strings; a passwd - * lookup failure aborts (Node throws a system error there — no - * compiled program path reaches it for the running uid). */ - "os.userName": { argTypes: [], result: STRING }, - "os.userShell": { argTypes: [], result: STRING }, - "os.userHomedir": { argTypes: [], result: STRING }, - "os.tmpdir": { argTypes: [], result: STRING }, - // Result is the call site's Dict record (VOID is - // the dgram.address sentinel — the libCall case checks the structure). - /** os.networkInterfaces(): getifaddrs(3) → the Dict - * record, built inline by the emitter from a runtime snapshot (scr_lib.c). - * The result type is the CALL SITE's mapped @types/node shape — a pure - * index-signature record whose value is `Info[] | undefined`, Info a - * two-record union (IPv4: scopeid `number | undefined` holding undefined; - * IPv6: scopeid number) — verified structurally by the frontend. Rows - * match libuv's filter (IFF_UP && IFF_RUNNING, AF_INET/AF_INET6; loopback - * = internal; MACs from the interface's link-level sibling entry, zeros - * when absent; cidr from the netmask's contiguous prefix, the null arm - * when it is missing or non-contiguous). Key/row order follows the OS's - * getifaddrs enumeration — Node itself does not guarantee an order. - * Fresh +1 record; never throws (a getifaddrs failure yields {}). */ - "os.networkInterfaces": { argTypes: [], result: VOID }, - /** `Math.max(...xs)` / `Math.min(...xs)` over one spread number[] - * (scr_number.c): the JS fold exactly — any NaN element poisons the - * result, ±0 order by the JS comparison (max prefers +0, min prefers - * -0), and the empty array yields -Infinity / +Infinity like the - * zero-argument calls. Borrows the array; never throws. */ - "math.maxArr": { argTypes: [arrayOf(F64)], result: F64 }, - "math.minArr": { argTypes: [arrayOf(F64)], result: F64 }, - "math.hypotArr": { argTypes: [arrayOf(F64)], result: F64 }, - /** `Math.floor(x)` — C floor() IS the JS operation (NaN/±0/±Infinity - * pass through bit-exactly). Never throws. */ - "math.floor": { argTypes: [F64], result: F64 }, - /** Math.abs (C fabs — IS the JS operation) and Math.round (scr_lib.c: - * ECMA half-toward-+Infinity with the exact-fraction comparison — C - * round() is half-away-from-zero and floor(x+0.5) drifts at the - * epsilon boundary). Borrow nothing; never throw. */ - "math.abs": { argTypes: [F64], result: F64 }, - "math.round": { argTypes: [F64], result: F64 }, +function buildLibFnSignatures() { + return defineLibFnSignatures({ + /** Native static fetch and its Web-platform companions. fetch.start + * answers once the response head arrives; the response body readers + * consume the native body stream. AbortSignal and ReadableStream values + * are opaque checked-dynamic handles. */ + "fetch.start": { argTypes: [STRING, DYN], result: { kind: "promise", inner: DYN } }, + "fetch.responseNew": { argTypes: [DYN, DYN], result: DYN }, + "fetch.responseJsonStatic": { argTypes: [DYN, DYN], result: DYN }, + "fetch.responseLight": { argTypes: [DYN], result: arrayOf(STRING) }, + "fetch.input": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "abort.controllerNew": { argTypes: [], result: DYN }, + /** EventTarget / Event / CustomEvent construction (scr_event_target.c): + * the Event constructors take the argument vector as one dyn array and + * throw Node's validation errors; `event.is` is `v instanceof Event` + * (kind 0), `CustomEvent` (1), `EventTarget` (2). addAbortListener + * answers the disposer (the Disposable's [Symbol.dispose]). */ + "event.targetNew": { argTypes: [], result: DYN }, + "event.new": { argTypes: [DYN], result: DYN }, + "event.customNew": { argTypes: [DYN], result: DYN }, + "event.is": { argTypes: [DYN, F64], result: BOOL }, + /** Compiled EventTarget and Event subclasses: the receiver is the + * instance (an object rooted at %EventTarget / %Event). targetInit and + * init are super() (init takes the argument vector as one dyn array + * and throws the constructor's validation errors; initType is the + * super(type) form with a string); targetCall runs an EventTarget method + * with the instance's own capsule as `self` (targetDispatch is its + * dispatchEvent(event) form, answering the bool); call, get and set + * reach the Event members. */ + "event.targetInit": { argTypes: [null], result: VOID }, + "event.init": { argTypes: [null, DYN], result: VOID }, + "event.initType": { argTypes: [null, STRING], result: VOID }, + /** %main hands the engine the compiled `listener.handleEvent` reader + * (lower-event-target.ts noteEventListeners). */ + "event.handleEventReader": { argTypes: [DYN], result: VOID }, + "event.targetDispatch": { argTypes: [null, DYN, DYN], result: BOOL }, + "event.targetCall": { argTypes: [null, DYN, STRING, DYN], result: DYN }, + "event.call": { argTypes: [null, STRING, DYN], result: DYN }, + "event.get": { argTypes: [null, STRING], result: DYN }, + "event.set": { argTypes: [null, STRING, DYN], result: VOID }, + "events.addAbortListener": { argTypes: [DYN, DYN], result: DYN }, + /** Blob / File (scr_blob.c): the constructors and slice() take their + * argument vector as one dyn array and throw Node's validation errors; + * the readers settle on Node's microtask turn for the Blob's parts. + * `blob.is` is `v instanceof Blob` (kind 0) or `File` (1). */ + "blob.new": { argTypes: [DYN], result: DYN }, + "blob.fileNew": { argTypes: [DYN], result: DYN }, + "blob.slice": { argTypes: [DYN, DYN], result: DYN }, + "blob.text": { argTypes: [DYN], result: { kind: "promise", inner: STRING } }, + "blob.bytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } }, + "blob.arrayBuffer": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "blob.is": { argTypes: [DYN, F64], result: BOOL }, + /** WebAssembly (scr_wasm.c): modules translated at build time + * (wasm/translate.ts), found at run time by the SHA-256 of their bytes. + * compile and instantiate over bytes settle on a later loop turn; + * instantiate over a Module settles now, as in Node. `wasm.is` answers + * instanceof for Module, Instance, Memory, Table and Global (kinds 0-4) + * and CompileError, LinkError and RuntimeError (5-7); `wasm.errorNew` + * constructs one of the error classes (kind 5-7, message, options). */ + "wasm.compile": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "wasm.validate": { argTypes: [DYN], result: BOOL }, + "wasm.instantiate": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "wasm.moduleNew": { argTypes: [DYN], result: DYN }, + "wasm.instanceNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.is": { argTypes: [DYN, F64], result: BOOL }, + "wasm.errorNew": { argTypes: [F64, DYN, DYN], result: DYN }, + /** new WebAssembly.Memory(descriptor), Table(descriptor, value) and + * Global(descriptor, value) (an undefined value is the type's default); + * `wasm.withoutNew` throws the TypeError for Module, Instance, Memory, + * Table or Global (kinds 0-4) called without new, after its arguments + * evaluate. */ + "wasm.memoryNew": { argTypes: [DYN], result: DYN }, + "wasm.tableNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.globalNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.withoutNew": { argTypes: [F64, DYN, DYN], result: DYN }, + /** WebAssembly.Module.imports(module), exports(module) and + * customSections(module, name): checked-dynamic arrays of descriptor + * objects and ArrayBuffers. */ + "wasm.moduleImports": { argTypes: [DYN], result: DYN }, + "wasm.moduleExports": { argTypes: [DYN], result: DYN }, + "wasm.moduleCustomSections": { argTypes: [DYN, DYN], result: DYN }, + /** `v.valueOf()` on a WebAssembly-typed receiver: a Global's value, + * dyn.valueOf's answer for anything else. */ + "wasm.valueOf": { argTypes: [DYN], result: DYN }, + /** WebAssembly.compile (0), instantiate (1) and validate (2) as function + * values, and the namespace object holding them. */ + "wasm.function": { argTypes: [F64], result: DYN }, + "wasm.namespace": { argTypes: [], result: DYN }, + /** The global object viewed through a cast to a type with a WebAssembly + * member: an object holding the namespace. */ + "wasm.globalView": { argTypes: [], result: DYN }, + "abort.timeout": { argTypes: [DYN], result: DYN }, + "abort.now": { argTypes: [DYN], result: DYN }, + "abort.any": { argTypes: [DYN], result: DYN }, + "fetch.function": { argTypes: [], result: DYN }, + "fetch.requestNew": { argTypes: [DYN, DYN], result: DYN }, + "fetch.headersNew": { argTypes: [DYN], result: DYN }, + "fetch.responseArrayBuffer": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "fetch.responseJson": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "fetch.responseText": { argTypes: [DYN], result: { kind: "promise", inner: STRING } }, + "fetch.responseBytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } }, + "fetch.abortControllerNew": { argTypes: [], result: DYN }, + "fetch.abortTimeout": { argTypes: [DYN], result: DYN }, + "fetch.abortNow": { argTypes: [DYN], result: DYN }, + "fetch.abortAny": { argTypes: [DYN], result: DYN }, + "fetch.streamNew": { argTypes: [DYN], result: DYN }, + "fetch.transformNew": { argTypes: [], result: DYN }, + // Program-dependent iterable: typed arrays/bytes/string stay intact so + // the native stream can pull lazily; checked-dynamic values are the + // fallback. The libCall validator in validate.ts checks the closed set. + "fetch.webIs": { argTypes: [DYN, STRING], result: BOOL }, + "fetch.streamIs": { argTypes: [DYN], result: BOOL }, + "fetch.streamFrom": { argTypes: [null], result: DYN }, + // The chunk/result record depends on ReadableStream; validated by validate.ts. + "fetch.readerRead": { argTypes: [DYN], result: VOID }, + /** Readable.fromWeb (scr_stream_from_web.c): the stream, objectMode, + * highWaterMark (< 0: the mode's default) and encoding ("": none). */ + "fetch.readableFromWeb": { argTypes: [DYN, BOOL, F64, STRING], result: VOID }, + "json.parse": { argTypes: [STRING], result: DYN }, + "json.parseReviver": { argTypes: [STRING, DYN], result: DYN }, + "json.stringifyReplacer": { argTypes: [DYN, DYN, STRING], result: DYN }, + "json.stringifyValue": { argTypes: [DYN, DYN, DYN], result: DYN }, + /** Keyed WRITE on a dyn value — `h.onDone = cb` / `h["k"] = v` on a + * checked-dynamic object (args: receiver, key string, value — all + * borrowed; the runtime copies the key and retains the value in). An + * OBJ receiver sets the member (later writes win, insertion order + * preserved — JS exactly); undefined/null throws Node's catchable + * "Cannot set properties of undefined (setting 'k')"; every other kind + * throws Node's STRICT-mode "Cannot create property 'k' on " — + * primitives quoting their rendering, V8's "on number '5'" + * (sloppy mode would ignore the write — suite tests are 'use strict'; + * SEMANTICS.md notes the sloppy divergence: the write throws). Void + * result; in the may-throw seed set. */ + "dyn.keySet": { argTypes: [DYN, STRING, DYN], result: VOID }, + "dyn.keySetComputed": { argTypes: [DYN, DYN, DYN], result: VOID }, + /** Delete an ordinary checked-native object's own key. Borrows both + * arguments; other receiver representations retain a runtime refusal. */ + "dyn.keyDelete": { argTypes: [DYN, STRING, BOOL], result: VOID }, + "dyn.keyDeleteComputed": { argTypes: [DYN, DYN, BOOL], result: VOID }, + "dyn.reflectDeleteProperty": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.hasKeyComputed": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.hasOwnComputed": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.propertyIsEnumerableComputed": { argTypes: [DYN, DYN], result: BOOL }, + /** Native own properties on globalThis keyed by symbol identity. */ + "dyn.globalSymbolGet": { argTypes: [SYMBOL_T], result: DYN }, + "dyn.globalSymbolSet": { argTypes: [SYMBOL_T, DYN], result: VOID }, + "dyn.globalSymbolHas": { argTypes: [SYMBOL_T], result: BOOL }, + "dyn.globalSymbolDelete": { argTypes: [SYMBOL_T], result: VOID }, + /** Exact native class capsule identity, without materializing its fields. */ + "dyn.typedRefIs": { argTypes: [DYN, STRING], result: BOOL }, + /** A capsule boxed by this program from exactly this class: one key + * pointer test, false for equal keys boxed elsewhere. Guards fast paths + * whose fallback handles every other value. The key is a literal. */ + "dyn.typedRefKeyIs": { argTypes: [DYN, STRING], result: BOOL }, + "dyn.classIs": { argTypes: [DYN, STRING], result: BOOL }, + /** Destructuring pack over a dyn source — `const [a, b] = d`, a + * destructured dyn callback param (args: the source and the STATIC + * TypeError spelling, "" when the source has none — both borrowed; + * result: a fresh dyn array, +1). Iterable kinds collect like spread + * (arrays element-by-element, strings by code point, bytes by byte); + * every other kind throws V8's destructuring TypeError — the spelling + * verbatim when non-empty, else the runtime kind wording ("number 5 is + * 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 }, + /** for await over a checked-dynamic value: the loop's iterator (a + * compiled async generator, Symbol.asyncIterator's result, or the sync + * iterator), and whether that iterator is synchronous. */ + "dyn.asyncIteratorOpen": { argTypes: [DYN, STRING], result: DYN }, + "dyn.asyncIteratorWasSync": { argTypes: [], result: BOOL }, + "dyn.iteratorResult": { argTypes: [DYN], result: DYN }, + "dyn.iteratorCanStep": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.iteratorStep": { argTypes: [DYN], result: DYN }, + "dyn.iteratorStepDone": { argTypes: [DYN], result: BOOL }, + "dyn.setGeneratorMembers": { argTypes: [DYN, DYN], result: VOID }, + "dyn.iteratorHelperMethod": { argTypes: [STRING], result: DYN }, + "dyn.collectionIteratorIndex": { argTypes: [DYN], result: F64 }, + "dyn.mapSeedEntries": { argTypes: [DYN], result: DYN }, + "dyn.mapSeedEntry": { argTypes: [DYN], result: DYN }, + /** The for-of-over-dyn pack accessors — the emitted index loop drives + * them over a dyn.iterPack result (ARR by construction). arrLen: the + * ARR length as f64 (0 for non-ARR kinds; arg borrowed). arrAt: the + * element at index (+1; the undefined singleton past the end). Neither + * throws. */ + "dyn.arrLen": { argTypes: [DYN], result: F64 }, + "dyn.arrAt": { argTypes: [DYN, F64], result: DYN }, + /** `key in v` with a RUNTIME (string) key on a checked-dynamic + * receiver (args: value dyn, key string; result bool): OBJ answers + * own-member presence, ARR answers 'length'/a valid index — exactly + * the literal-key dynHasKey path. Proxy has traps may throw. */ + "dyn.hasKey": { argTypes: [DYN, STRING], result: BOOL }, + /** The `in` operator with a runtime string key: dyn.hasKey plus the + * members inherited from builtin and compiled class prototypes. */ + "dyn.in": { argTypes: [DYN, STRING], result: BOOL }, + "dyn.freeze": { argTypes: [DYN], result: DYN }, + "dyn.isFrozen": { argTypes: [DYN], result: BOOL }, + /** Object.defineProperty on a declared class field (scr_json.c): the + * field's current value (borrowed), its FIELD_* attribute word, the + * instance's CLASS_* integrity word, the field name and the descriptor. + * Validates as Node does (TypeError "Cannot redefine property: f") and + * answers a one-property holder of the defined value and attributes (+1), + * read back with class.definedBits and class.definedValue. */ + "class.defineField": { argTypes: [DYN, F64, F64, STRING, DYN], result: DYN }, + "class.definedBits": { argTypes: [DYN, STRING], result: F64 }, + "class.definedValue": { argTypes: [DYN, STRING], result: DYN }, + /** Whether a property descriptor has a `value` member. */ + "class.descriptorHasValue": { argTypes: [DYN], result: BOOL }, + /** Throws Node's TypeError for a write to a read-only class field: the + * field name and the instance's class name. */ + "class.throwReadOnly": { argTypes: [STRING, STRING], result: VOID }, + /** Throws Node's TypeError for a class field initializer on a + * non-extensible instance: the field name. */ + "class.throwNotExtensible": { argTypes: [STRING], result: VOID }, + /** Object.freeze of a record whose shape is freezable (frozen-records.ts): + * sets the frozen bit in its rc word (scr_record_freeze). The receiver is + * any record type. */ + "record.freeze": { argTypes: [null], result: VOID }, + /** Whether a record is frozen: its rc word's frozen bit, set by + * record.freeze and dyn.freeze of a capsule. Emitted inline. The receiver + * is any record type. */ + "record.isFrozen": { argTypes: [null], result: BOOL }, + /** Throws Node's TypeError for a write to a frozen record: the key, the + * object's text in the message ("#") and the operation (0 assign, + * 1 add, 2 delete). */ + "record.throwFrozen": { argTypes: [STRING, STRING, F64], result: VOID }, + /** Object.freeze of an array (scr_arr_freeze): sets its frozen bit, which + * the write guards of IrModule.freezableArrays test, and isFrozen reads. + * The receiver is any array type. */ + "array.freeze": { argTypes: [null], result: VOID }, + "array.isFrozen": { argTypes: [null], result: BOOL }, + /** Object.freeze, seal or preventExtensions of a Map or Set + * (scr_map_prevent_extensions), which only makes it not extensible: its + * entries stay writable. The receiver is any map or set type. */ + "collection.preventExtensions": { argTypes: [null], result: VOID }, + "collection.isExtensible": { argTypes: [null], result: BOOL }, + "dyn.nativeSetNew": { argTypes: [DYN], result: DYN }, + "dyn.nativeSetIs": { argTypes: [DYN], result: BOOL }, + "dyn.nativeMapIs": { argTypes: [DYN], result: BOOL }, + "dyn.nativeUrlIs": { argTypes: [DYN], result: BOOL }, + "dyn.nativeDateIs": { argTypes: [DYN], result: BOOL }, + "dyn.nativeDateHandleIs": { argTypes: [DYN], result: BOOL }, + /** A builtin constructor (`Object`, `Date`, `RegExp`, `Map`, `Set`, + * `Promise`, `WeakMap`, `WeakSet`) read as a JavaScript value: one + * interned function per name, the same one dynamic values answer for + * `.constructor`. The name is a compiler-chosen literal. */ + "dyn.globalConstructor": { argTypes: [STRING], result: DYN }, + /** A builtin global function (setTimeout, setInterval, setImmediate, + * the clear functions, queueMicrotask, process.nextTick) read as a + * JavaScript value: one interned function per name whose calls run the + * timer APIs (it pulls in the event loop). The name is a compiler-chosen + * literal. */ + "dyn.builtinFunction": { argTypes: [STRING], result: DYN }, + "dyn.namespaceNew": { argTypes: [STRING, DYN], result: DYN }, + "dyn.namespaceDefine": { argTypes: [DYN, STRING, DYN, F64, F64], result: VOID }, + "date.nativeNew": { argTypes: [DYN], result: DYN }, + /** `new Date(year, monthIndex, ...)` in JavaScript: the argument array's + * first seven values pass through ToNumber in order (later ones are + * evaluated, never converted), then the local-time field constructor. */ + "date.nativeNewFields": { argTypes: [DYN], result: DYN }, + /** `new Date(value)` in JavaScript: a string argument parses with V8's + * full date parser; every other value constructs as date.nativeNew. */ + "date.nativeNewValue": { argTypes: [DYN], result: DYN }, + "date.argumentsTime": { argTypes: [DYN], result: DATE_T }, + "date.classOperation": { argTypes: [DYN, DYN], result: VOID }, + "date.checkedValue": { argTypes: [DYN], result: DATE_T }, + "dyn.nativeRegexIs": { argTypes: [DYN], result: BOOL }, + /** toString() on a checked-dynamic receiver: runtime kind dispatch + * (bytes decode per the literal encoding — utf8 default; strings, + * numbers, booleans, arrays, objects answer JS-exactly; undefined and + * null throw the catchable TypeError). */ + "dyn.toString": { argTypes: [DYN, DYN, STRING], result: STRING }, + "dyn.valueOf": { argTypes: [DYN, STRING], result: DYN }, + /** Object.defineProperties over dyn values (args: target, descriptors — + * both borrowed dyn; result: the target, +1 — JS's return value). + * Value descriptors become own properties on OBJ and FUNC targets; + * OBJ targets preserve data attributes. get/set + * descriptors and non-object targets/descriptors throw catchably + * (Node's TypeError texts; accessors the "not supported yet" Error). In + * the may-throw seed set. */ + "dyn.defineProps": { argTypes: [DYN, DYN], result: DYN }, + "dyn.defineProperty": { argTypes: [DYN, DYN, DYN], result: DYN }, + "dyn.getOwnPropertyDescriptor": { argTypes: [DYN, DYN], result: DYN }, + "dyn.arrayProtoCall": { argTypes: [DYN, STRING, DYN], result: DYN }, + /** Promise.all / Promise.allSettled / Promise.any over a spread + * checked-dynamic array: the values, the settled-record array, or the + * first fulfillment (AggregateError once every entry rejects). + * Thenable elements adopt; the second argument reads a program class + * instance's `then` (the compiled property dispatch). */ + "dyn.promiseAll": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dyn.promiseAllSettled": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dyn.promiseAny": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + /** PromiseResolve over one checked-dynamic value: the promise it holds, + * a promise that adopts a thenable's settlement, or a promise fulfilled + * with the value. The second argument reads `then` as above. */ + "dyn.promiseResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + /** Bare `typeof v` on a dyn value AS A STRING (arg: the dyn value, + * borrowed; result: an owned string) — the dyn kind's JS answer: + * undefined→"undefined", null/object/array/bytes→"object" (JS's oldest + * wart preserved), boolean/number/string by kind, function→"function". + * Never throws. */ + "dyn.typeof": { argTypes: [DYN], result: STRING }, + /** Object.prototype.toString.call on a checked-dynamic value. */ + "dyn.objectTag": { argTypes: [DYN], result: STRING }, + /** Engine-free CommonJS module graph. Module values are scalar f64 + * handles; generated startup defines the registry, init wrappers update + * loading/cache state, and the read surface answers Node's live metadata. */ + "module.registryInit": { argTypes: [F64], result: VOID }, + "module.define": { + argTypes: [F64, STRING, STRING, STRING, arrayOf(STRING), BOOL], + result: VOID, + }, + "module.enter": { argTypes: [F64], result: VOID }, + "module.link": { argTypes: [F64, F64], result: VOID }, + "module.finish": { argTypes: [F64], result: VOID }, + "module.fail": { argTypes: [F64], result: VOID }, + "module.filename": { argTypes: [F64], result: STRING }, + "module.id": { argTypes: [F64], result: STRING }, + "module.path": { argTypes: [F64], result: STRING }, + "module.paths": { argTypes: [F64], result: arrayOf(STRING) }, + "module.children": { argTypes: [F64], result: arrayOf(F64) }, + "module.parent": { argTypes: [F64], result: F64 }, + "module.loaded": { argTypes: [F64], result: BOOL }, + "module.cacheGet": { argTypes: [STRING], result: F64 }, + "module.cacheHas": { argTypes: [STRING], result: BOOL }, + "module.cacheKeys": { argTypes: [], result: arrayOf(STRING) }, + "timers.setTimeout": { argTypes: [{ kind: "func", params: [], ret: VOID }, F64], result: VOID }, + /** The repeating timer pair. setInterval takes (callback, ms) like + * setTimeout and RETURNS the f64 handle the fallback declarations + * promise (ids start at 1, so truthiness narrowing works); the loop + * owns the callback until clearInterval removes the entry (eagerly — a + * live interval keeps the loop alive, a cleared one releases it, like + * Node). Neither throws; a throw ESCAPING an interval callback ends the + * program like a setTimeout throw. */ + "timers.setInterval": { argTypes: [{ kind: "func", params: [], ret: VOID }, F64], result: F64 }, + "timers.clearInterval": { argTypes: [F64], result: VOID }, + /** setTimeout WITH a clear handle (the f64 id) — the clearable/unref-able + * one-shot; clearTimeout cancels it, .unref() drops it from loop + * liveness. The plain timers.setTimeout stays the handle-less + * fire-and-forget. */ + "timers.setTimeoutHandle": { + argTypes: [{ kind: "func", params: [], ret: VOID }, F64], + result: F64, + }, + "timers.clearTimeout": { argTypes: [F64], result: VOID }, + /** Timeout.unref()/ref()/hasRef() — loop-liveness bookkeeping over the + * handle id. unref/ref RETURN the handle (f64) for chaining; hasRef + * returns bool. */ + "timers.unref": { argTypes: [F64], result: F64 }, + "timers.ref": { argTypes: [F64], result: F64 }, + "timers.hasRef": { argTypes: [F64], result: BOOL }, + /** Timeout.refresh() — re-arm to now + the original delay (from the + * heap or from inside the firing callback; a one-shot that fired on an + * earlier turn is gone and no-ops — documented divergence). Returns + * the handle for chaining like unref/ref. */ + "timers.refresh": { argTypes: [F64], result: F64 }, + /** setImmediate/clearImmediate — Node's check phase: callbacks run + * once per loop turn AFTER due timers, FIFO, and immediates queued + * mid-phase wait for the next turn. setImmediate returns the f64 + * handle (its own id space — clearTimeout of an Immediate no-ops, + * like Node); the Immediate ref trio mirrors the Timeout one (an + * unref'd pending immediate neither keeps the loop alive nor fires + * once nothing reffed remains). */ + "timers.setImmediate": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: F64 }, + /** queueMicrotask (scr_async.c): the callback enters the SAME FIFO + * promise continuations ride — one microtask order, like V8's queue — + * and a throw is an UNCAUGHT exception (never a rejection). + * timers.queueMicrotask takes an owned zero-param closure and never + * throws; the Dyn form (checked-dynamic arguments — the mustCall + * wrapper, the suite's invalid-input probes) throws Node's + * ERR_INVALID_ARG_TYPE synchronously on a non-function value and calls + * the function with zero arguments at drain. */ + "timers.queueMicrotask": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, + "timers.queueMicrotaskDyn": { argTypes: [DYN], result: VOID }, + "timers.clearImmediate": { argTypes: [F64], result: VOID }, + /** process.nextTick(cb, ...args) — the user tick queue. args: [cb + * (() => void; trailing call arguments ride the timer surface's + * interned dyn thunk)]. Ticks drain BEFORE promise jobs at every loop + * checkpoint, to joint exhaustion with them (Node's tick-then- + * microtask order); ticks enqueued by station listeners run at the + * NEXT checkpoint (the stream-tick station divergence, SEMANTICS.md). + * Pending ticks are always-ready work — the loop neither sleeps nor + * exits while any exist; ticks scheduled from 'exit' listeners never + * run (Node). The enqueue itself never throws. */ + "process.nextTick": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, + /** The process introspection statics. uptime: seconds since the + * binary's own start (fractional — a load-time monotonic anchor). + * availableMemory/constrainedMemory: libuv's numbers (free-ish bytes; + * the cgroup cap or 0). cpuUser/cpuSystem (+ threadCpu twins): the + * process/thread CPU clocks in microseconds — the frontend composes + * the {user, system} records. The *Diff forms answer current − prev + * for one field; cpuPrevValidate throws Node's ERR_INVALID_ARG_VALUE + * RangeError for negative/non-finite prev fields (user first, then + * system — Node's order; MAY THROW). rusage(idx): one + * process.resourceUsage() field by canonical index (Node's units). + * activeResources: the loop's own bookkeeping — 'Timeout' per armed + * (or firing, uncleared) timer, 'Immediate' per queued unfired + * immediate; unmodeled resource kinds are absent (SEMANTICS.md). */ + "process.uptime": { argTypes: [], result: F64 }, + /** perf_hooks performance.now(): fractional ms since process start. */ + "perf.now": { argTypes: [], result: F64 }, + /** perf_hooks user timing over the thread's performance timeline. The + * producing calls take the call's whole argument vector as one dyn + * array (Node's validation and errors run in the runtime, MAY THROW): + * mark/measure answer 1, entries(args, which) the entry count of + * getEntries (0), getEntriesByName (1) or getEntriesByType (2). The + * entryName/Type/Start/Duration/Detail accessors read entry i of that + * answer; the frontend builds a new entry's record from them and hands + * it to entryKeep (a typed reference capsule), and the getters read the + * kept records back through entryRef, so entries keep their identity. */ + "perf.mark": { argTypes: [DYN], result: F64 }, + "perf.measure": { argTypes: [DYN], result: F64 }, + "perf.entries": { argTypes: [DYN, F64], result: F64 }, + "perf.clearMarks": { argTypes: [DYN], result: VOID }, + "perf.clearMeasures": { argTypes: [DYN], result: VOID }, + "perf.entryName": { argTypes: [F64], result: STRING }, + "perf.entryType": { argTypes: [F64], result: STRING }, + "perf.entryStart": { argTypes: [F64], result: F64 }, + "perf.entryDuration": { argTypes: [F64], result: F64 }, + "perf.entryDetail": { argTypes: [F64], result: DYN }, + "perf.entryKeep": { argTypes: [DYN], result: VOID }, + "perf.entryRef": { argTypes: [F64], result: DYN }, + /** perf_hooks event-loop metrics (scr_perf_loop.c): loopStart is the + * loop's start in performance.now() ms (0 before it), idleTime the idle + * ms since. eldNew(resolution ms) creates a monitorEventLoopDelay + * histogram handle (MAY THROW Node's resolution RangeError); enable/disable answer whether the state changed; + * eldPercentile expects Node's validated range; eldStat(which) reads + * 0 min, 1 max, 2 mean, 3 stddev, 4 count, 5 exceeds. */ + "perf.loopStart": { argTypes: [], result: F64 }, + "perf.idleTime": { argTypes: [], result: F64 }, + "perf.eldNew": { argTypes: [F64], result: F64 }, + "perf.eldEnable": { argTypes: [F64], result: BOOL }, + "perf.eldDisable": { argTypes: [F64], result: BOOL }, + "perf.eldReset": { argTypes: [F64], result: VOID }, + "perf.eldPercentile": { argTypes: [F64, F64], result: F64 }, + "perf.eldStat": { argTypes: [F64, F64], result: F64 }, + /** process.memoryUsage(): memoryUsageTake samples the resident set and + * the runtime heap once (MAY THROW Node's uv_resident_set_memory error) + * and memoryUsageField(k) reads that sample (0 rss, 1 heapTotal, 2 + * heapUsed, 3 external, 4 arrayBuffers). rss is memoryUsage.rss(). */ + "process.memoryUsageTake": { argTypes: [], result: VOID }, + "process.memoryUsageField": { argTypes: [F64], result: F64 }, + "process.rss": { argTypes: [], result: F64 }, + "process.availableMemory": { argTypes: [], result: F64 }, + "process.constrainedMemory": { argTypes: [], result: F64 }, + "process.cpuUser": { argTypes: [], result: F64 }, + "process.cpuSystem": { argTypes: [], result: F64 }, + "process.cpuUserDiff": { argTypes: [F64], result: F64 }, + "process.cpuSystemDiff": { argTypes: [F64], result: F64 }, + "process.threadCpuUser": { argTypes: [], result: F64 }, + "process.threadCpuSystem": { argTypes: [], result: F64 }, + "process.threadCpuUserDiff": { argTypes: [F64], result: F64 }, + "process.threadCpuSystemDiff": { argTypes: [F64], result: F64 }, + "process.cpuPrevValidate": { argTypes: [F64, F64], result: VOID }, + "process.rusage": { argTypes: [F64], result: F64 }, + "process.activeResources": { argTypes: [], result: arrayOf(STRING) }, + "timers.immediateUnref": { argTypes: [F64], result: F64 }, + "timers.immediateRef": { argTypes: [F64], result: F64 }, + "timers.immediateHasRef": { argTypes: [F64], result: BOOL }, + /** The tolerated non-handle clear (`clearTimeout(null)`, + * `clearInterval({})`, zero-argument forms): Node ignores anything + * that is not a live handle without an error — a VOID no-op the emitter drops + * (only syntactically side-effect-free arguments take this path). */ + "timers.clearNoop": { argTypes: [], result: VOID }, + // Signal listeners are zero-param (the ambient shape); exit/stdin + // callbacks carry program-dependent one-param shapes — null slots, the + // libCall case checks them (child.onExit precedent). + /** Named process signal events: [name string, callback dyn, once bool] + * or [name, callback] for removal. The runtime resolves platform signal + * numbers and delivers (name, number). Listeners do not keep the loop + * alive. Invalid callbacks and uncatchable signals throw. */ + "process.onSignal": { argTypes: [STRING, DYN, BOOL], result: VOID }, + "process.offSignal": { argTypes: [STRING, DYN], result: VOID }, + /** The process 'exit' event — process.on/once/off("exit"). args: [cb, + * once bool] / [cb]. Listeners run SYNCHRONOUSLY at termination + * (normal exit, process.exit(), the uncaught/unhandled exit-1 paths) + * with the exit code; anything they schedule never runs, like Node. + * Callback shapes: () => void or (code: number) => void — the emitter + * picks the runtime adapter. Never throw. */ + "process.onExit": { argTypes: [null, BOOL], result: VOID }, + "process.offExit": { argTypes: [null], result: VOID }, + /** process.stdin listener registration — stdin.on/once("data" | "end" | + * "error", cb). args: [cb, once bool]. data callbacks: () => void or + * (chunk: Uint8Array) => void; end: () => void; error: () => void or + * (err: Error) => void (the child error adapters are reused). While a + * data listener (or a parked for-await chunk promise) exists, stdin + * keeps the loop alive — Node's flowing-stdin keep-alive. Never + * throw. */ + "stdin.onData": { argTypes: [null, BOOL], result: VOID }, + "stdin.onEnd": { argTypes: [{ kind: "func", params: [], ret: VOID }, BOOL], result: VOID }, + "stdin.onError": { argTypes: [null, BOOL], result: VOID }, + /** process.stdin.on/once('keypress', cb) (scr_readline_tty.c): cb is the + * listener as a checked-dynamic function, called with (str, key). */ + "stdin.onKeypress": { argTypes: [DYN, BOOL], result: VOID }, + /** The for-await chunk source over process.stdin: no args, result + * Promise (+1). Fulfills with the next arrived chunk; the + * EMPTY bytes value is the done sentinel (POSIX reads never deliver + * empty data chunks), which the for-await desugar turns into loop + * exit. Never throws itself; awaiting it re-throws nothing (stdin + * errors surface through 'error' listeners, not the iterator). */ + "stdin.nextChunk": { argTypes: [], result: { kind: "promise", inner: BYTES_U8 } }, + "fs.readFileSync": { argTypes: [STRING, STRING], result: STRING }, + "fs.readFileEncoded": { argTypes: [STRING, STRING], result: STRING }, + "fs.readFdEncoded": { argTypes: [F64, STRING], result: STRING }, + /** readFileSync(path) — the Buffer read (+1 bytes); throws catchably + * like the utf8 form. */ + "fs.readFileSyncBuf": { argTypes: [STRING], result: BYTES_U8 }, + /** readFileSync(path, enc) with a RUNTIME encoding (an untyped JS + * parameter): undefined/null answer Buffers, utf8 a string, other real + * encodings throw the "not supported yet" Error, unknown names throw + * ERR_UNKNOWN_ENCODING. */ + "fs.readFileSyncDyn": { argTypes: [STRING, DYN], result: DYN }, + "fs.writeFileSync": { argTypes: [STRING, STRING], result: VOID }, + "fs.appendFileSync": { argTypes: [STRING, STRING], result: VOID }, + "fs.existsSync": { argTypes: [STRING], result: BOOL }, + "fs.mkdirSync": { argTypes: [STRING], result: VOID }, + "fs.rmSync": { argTypes: [STRING], result: VOID }, + "fs.rmdirSync": { argTypes: [STRING], result: VOID }, + "fs.readdirSync": { argTypes: [STRING], result: arrayOf(STRING) }, + // Result is the call site's Dirent record array (VOID is the + // structure-checked sentinel, the os.networkInterfaces pattern). + /** `fs.readdirSync(path, { withFileTypes: true })` — Dirent rows over + * one readdir pass (scr_lib.c's scandir snapshot; DT_UNKNOWN falls back + * to lstat, Node's getDirents rule). The result type is the call site's + * interned Dirent record array (name, parentPath, hidden %dtype in + * libuv's UV_DIRENT encoding) — verified by the frontend; the emitter + * assembles the rows from the snapshot. Byte order on POSIX (libuv + * sorts), the directory's order on Windows; no "."/"..". Throws + * Node's scandir errno error (may-throw seed set); fresh +1 array. */ + "fs.readdirTypesSync": { argTypes: [STRING], result: VOID }, + /** fs.globSync / fs.glob / fs/promises glob (scr_glob.c, Node's + * lib/internal/fs/glob.js walk): pattern and options are dyn values the + * runtime validates in Node's order. The string forms answer string[]; + * the Dirent forms answer the call site's Dirent record array (validated + * in validate.ts) assembled from the snapshot; fs.callbackGlob takes the + * argument vector and calls back with string[]. */ + "fs.globSync": { argTypes: [DYN, DYN], result: arrayOf(STRING) }, + "fs.globTypesSync": { argTypes: [DYN, DYN], result: VOID }, + "fs.callbackGlob": { argTypes: [DYN], result: VOID }, + "fsp.globStrings": { argTypes: [DYN, DYN], result: arrayOf(STRING) }, + "fsp.globTypes": { argTypes: [DYN, DYN], result: VOID }, + /** The error an fs/promises glob walk ended with (undefined when none), + * read once right after fsp.globStrings / fsp.globTypes. */ + "fsp.globError": { argTypes: [], result: DYN }, + /** fs.Dir (scr_lib.c): opendirSync(path, bufferSize) throws Node's + * opendir error (or the options.bufferSize RangeError); dir.readSync + * answers the call site's `Dirent | null` (VOID here — the emitter builds + * the row like readdirTypesSync) and dir.read is its promise twin; + * readSync and closeSync throw ERR_DIR_CLOSED on a closed Dir, read and + * close reject with it. */ + "fs.opendirSync": { argTypes: [STRING, F64], result: DIR_T }, + "fsp.opendir": { argTypes: [STRING, F64], result: { kind: "promise", inner: DIR_T } }, + "dir.path": { argTypes: [DIR_T], result: STRING }, + "dir.readSync": { argTypes: [DIR_T], result: VOID }, + "dir.read": { argTypes: [DIR_T], result: { kind: "promise", inner: VOID } }, + "dir.closeSync": { argTypes: [DIR_T], result: VOID }, + "dir.close": { argTypes: [DIR_T], result: { kind: "promise", inner: VOID } }, + /** node:path (scr_path.c ports BOTH of Node's implementations + * function-by-function): the `path.*` family is posix, the + * `path.win32*` family is Node v24's path.win32 ported exactly — the + * frontend binds the bare module to the TARGET platform's family + * (Node on Windows IS path.win32) and the path.posix / path.win32 + * namespaces to their own family everywhere. join and resolve take ONE + * string[] arg — the frontend packs the variadic call's arguments into + * an array literal. basename always receives its suffix (the frontend + * completes an omitted one with "", a Node no-op). toNamespacedPath is + * the posix identity and the win32 \\?\-prefixer. None of these throw; + * the resolves consult the process cwd like Node's. */ + "path.join": { argTypes: [arrayOf(STRING)], result: STRING }, + "path.resolve": { argTypes: [arrayOf(STRING)], result: STRING }, + "path.normalize": { argTypes: [STRING], result: STRING }, + "path.dirname": { argTypes: [STRING], result: STRING }, + "path.basename": { argTypes: [STRING, STRING], result: STRING }, + "path.extname": { argTypes: [STRING], result: STRING }, + "path.isAbsolute": { argTypes: [STRING], result: BOOL }, + "path.relative": { argTypes: [STRING, STRING], result: STRING }, + "path.toNamespacedPath": { argTypes: [STRING], result: STRING }, + "path.win32Join": { argTypes: [arrayOf(STRING)], result: STRING }, + "path.win32Resolve": { argTypes: [arrayOf(STRING)], result: STRING }, + "path.win32Normalize": { argTypes: [STRING], result: STRING }, + "path.win32Dirname": { argTypes: [STRING], result: STRING }, + "path.win32Basename": { argTypes: [STRING, STRING], result: STRING }, + "path.win32Extname": { argTypes: [STRING], result: STRING }, + "path.win32IsAbsolute": { argTypes: [STRING], result: BOOL }, + "path.win32Relative": { argTypes: [STRING, STRING], result: STRING }, + "path.win32ToNamespacedPath": { argTypes: [STRING], result: STRING }, + /** path.parse: [root, dir, base, ext, name] as a fresh string[] (the + * frontend builds the ParsedPath record); path.format: the pathObject's + * root, dir, base, name and ext with undefined spelled "". */ + "path.parse": { argTypes: [STRING], result: arrayOf(STRING) }, + "path.win32Parse": { argTypes: [STRING], result: arrayOf(STRING) }, + "path.format": { argTypes: [STRING, STRING, STRING, STRING, STRING], result: STRING }, + "path.win32Format": { argTypes: [STRING, STRING, STRING, STRING, STRING], result: STRING }, + /** path.matchesGlob(path, pattern): Node's matchGlobPattern over its + * bundled minimatch (scr_glob.c). Both families match case-insensitively + * on macOS and Windows hosts like Node's; win32 also accepts either + * separator. Throws TypeError for a pattern over 64 Ki code units, + * RangeError for a brace sequence too long to expand, and SyntaxError + * when a segment's regex is invalid under the u flag. */ + "path.matchesGlob": { argTypes: [STRING, STRING], result: BOOL }, + "path.win32MatchesGlob": { argTypes: [STRING, STRING], result: BOOL }, + /** node:os: homedir is $HOME else getpwuid(3); tmpdir is Node's env + * cascade ($TMPDIR/$TMP/$TEMP else /tmp, one trailing slash trimmed). + * os.platform() lowers to process.platform — one implementation. */ + "os.homedir": { argTypes: [], result: STRING }, + /** os.release(): uname(2)'s release field — Node's own implementation + * (the kernel version string, e.g. "24.6.0" on macOS 15). Interned; +1 + * per read. Never throws. */ + "os.release": { argTypes: [], result: STRING }, + /** os.type(): uname(2)'s sysname ("Darwin", "Linux"; "Windows_NT" on + * win32) — Node's uv_os_uname answer. Interned per call; never throws. */ + "os.type": { argTypes: [], result: STRING }, + /** os.totalmem(): total physical memory in bytes. Never throws. */ + "os.totalmem": { argTypes: [], result: F64 }, + /** os.availableParallelism(): CPUs the process may use (libuv's answer), >= 1. Never throws. */ + "os.availableParallelism": { argTypes: [], result: F64 }, + /** os.hostname/machine/version/endianness, freemem, uptime and loadavg: + * libuv's answers (uname's machine and version fields, gethostname, + * "LE"/"BE", free memory in bytes, uptime in seconds, the three load + * averages). Fresh results; never throw. */ + "os.hostname": { argTypes: [], result: STRING }, + "os.machine": { argTypes: [], result: STRING }, + "os.version": { argTypes: [], result: STRING }, + "os.endianness": { argTypes: [], result: STRING }, + "os.freemem": { argTypes: [], result: F64 }, + "os.uptime": { argTypes: [], result: F64 }, + "os.loadavg": { argTypes: [], result: arrayOf(F64) }, + /** os.getPriority(pid): validateInt32 (RangeError ERR_OUT_OF_RANGE), + * then the priority; a failure throws Node's SystemError. */ + "os.getPriority": { argTypes: [F64], result: F64 }, + /** os.setPriority(pid, priority): validateInt32 for both (priority in + * -20..19, RangeError ERR_OUT_OF_RANGE), then the priority; a failure + * throws Node's SystemError. */ + "os.setPriority": { argTypes: [F64, F64], result: VOID }, + /** os.cpus(): cpusTake snapshots every CPU and answers the count; + * cpusModel(i) and cpusField(i, k) (k: 0 speed, 1 user, 2 nice, 3 sys, + * 4 idle, 5 irq) read the snapshot. Never throw. */ + "os.cpusTake": { argTypes: [], result: F64 }, + "os.cpusModel": { argTypes: [F64], result: STRING }, + "os.cpusField": { argTypes: [F64, F64], result: F64 }, + /** os.constants: the per-thread null-prototype constants object (dlopen, + * errno, signals, priority, UV_UDP_REUSEADDR). Never throws. */ + "os.constants": { argTypes: [], result: DYN }, + /** fs.constants: the target's cached null-prototype constants object. */ + "fs.constants": { argTypes: [], result: DYN }, + /** The deprecated node:constants object (scr_os.c): the os groups and + * fs.constants merged with the borrowed baked crypto.constants table, + * frozen, one per thread. */ + "constants.object": { argTypes: [DYN], result: DYN }, + /** umask(2): arg < 0 reads without setting (umask has no read-only form + * — set 0, restore); otherwise sets and answers the PREVIOUS mask. + * Never throws. */ + "process.umask": { argTypes: [F64], result: F64 }, + /** chdir(2) — throws Node's fs-shaped error (ENOENT/EACCES/ENOTDIR, + * syscall "chdir") on failure. */ + "process.chdir": { argTypes: [STRING], result: VOID }, + "process.loadEnvFile": { argTypes: [DYN], result: VOID }, + /** process._exiting: true once the exit sequence began (exit listeners + * running) — the runtime flag scr_run_exit_listeners/process.exit set. + * Never throws. */ + "process.exiting": { argTypes: [], result: BOOL }, + /** net's process-wide happy-eyeballs attempt budget (Node's default + * 250ms): one runtime double in the core unit, so reading/writing it + * never forces the net unit into the link. Never throw. */ + "net.getAutoSelTimeout": { argTypes: [], result: F64 }, + "net.setAutoSelTimeout": { argTypes: [F64], result: VOID }, + /** realpath(3) with Node's error shape (syscall "lstat" in the message, + * Node's own spelling for realpathSync failures). +1 fresh string. */ + "fs.realpathSync": { argTypes: [STRING], result: STRING }, + /** fs.realpathSync (or its .native when the flag is true) as a function + * value: path and options arrive unchecked; the result is a string or a + * Buffer by the options' encoding. */ + "fs.realpathValue": { argTypes: [DYN, DYN, BOOL], result: DYN }, + /** Native realpath with syscall "realpath" on failure. +1 fresh string. */ + "fs.realpathNativeSync": { argTypes: [STRING], result: STRING }, + /** The os.userInfo() field trio (uv_os_get_passwd's slices): pw_name, + * pw_shell, pw_dir — the PASSWD homedir, not os.homedir's $HOME-first + * cascade (Node's own split). The frontend assembles the UserInfo + * record from these plus getuid/getgid. +1 fresh strings; a passwd + * lookup failure aborts (Node throws a system error there — no + * compiled program path reaches it for the running uid). */ + "os.userName": { argTypes: [], result: STRING }, + "os.userShell": { argTypes: [], result: STRING }, + "os.userHomedir": { argTypes: [], result: STRING }, + "os.tmpdir": { argTypes: [], result: STRING }, + // Result is the call site's Dict record (VOID is + // the dgram.address sentinel — the libCall case checks the structure). + /** os.networkInterfaces(): getifaddrs(3) → the Dict + * record, built inline by the emitter from a runtime snapshot (scr_lib.c). + * The result type is the CALL SITE's mapped @types/node shape — a pure + * index-signature record whose value is `Info[] | undefined`, Info a + * two-record union (IPv4: scopeid `number | undefined` holding undefined; + * IPv6: scopeid number) — verified structurally by the frontend. Rows + * match libuv's filter (IFF_UP && IFF_RUNNING, AF_INET/AF_INET6; loopback + * = internal; MACs from the interface's link-level sibling entry, zeros + * when absent; cidr from the netmask's contiguous prefix, the null arm + * when it is missing or non-contiguous). Key/row order follows the OS's + * getifaddrs enumeration — Node itself does not guarantee an order. + * Fresh +1 record; never throws (a getifaddrs failure yields {}). */ + "os.networkInterfaces": { argTypes: [], result: VOID }, + /** `Math.max(...xs)` / `Math.min(...xs)` over one spread number[] + * (scr_number.c): the JS fold exactly — any NaN element poisons the + * result, ±0 order by the JS comparison (max prefers +0, min prefers + * -0), and the empty array yields -Infinity / +Infinity like the + * zero-argument calls. Borrows the array; never throws. */ + "math.maxArr": { argTypes: [arrayOf(F64)], result: F64 }, + "math.minArr": { argTypes: [arrayOf(F64)], result: F64 }, + "math.hypotArr": { argTypes: [arrayOf(F64)], result: F64 }, + /** `Math.floor(x)` — C floor() IS the JS operation (NaN/±0/±Infinity + * pass through bit-exactly). Never throws. */ + "math.floor": { argTypes: [F64], result: F64 }, + /** Math.abs (C fabs — IS the JS operation) and Math.round (scr_lib.c: + * ECMA half-toward-+Infinity with the exact-fraction comparison — C + * round() is half-away-from-zero and floor(x+0.5) drifts at the + * epsilon boundary). Borrow nothing; never throw. */ + "math.abs": { argTypes: [F64], result: F64 }, + "math.round": { argTypes: [F64], result: F64 }, - "math.trunc": { argTypes: [F64], result: F64 }, - "math.ceil": { argTypes: [F64], result: F64 }, - "math.sin": { argTypes: [F64], result: F64 }, - "math.sinh": { argTypes: [F64], result: F64 }, - "math.cos": { argTypes: [F64], result: F64 }, - "math.cosh": { argTypes: [F64], result: F64 }, - "math.tan": { argTypes: [F64], result: F64 }, - "math.tanh": { argTypes: [F64], result: F64 }, - "math.asin": { argTypes: [F64], result: F64 }, - "math.asinh": { argTypes: [F64], result: F64 }, - "math.acos": { argTypes: [F64], result: F64 }, - "math.acosh": { argTypes: [F64], result: F64 }, - "math.atan": { argTypes: [F64], result: F64 }, - "math.atanh": { argTypes: [F64], result: F64 }, - "math.cbrt": { argTypes: [F64], result: F64 }, - "math.clz32": { argTypes: [F64], result: F64 }, - /** Return -1, +1, or the original NaN/zero. In particular, -0 remains -0. */ - "math.sign": { argTypes: [F64], result: F64 }, - "math.exp": { argTypes: [F64], result: F64 }, - "math.expm1": { argTypes: [F64], result: F64 }, - "math.f16round": { argTypes: [F64], result: F64 }, - "math.fround": { argTypes: [F64], result: F64 }, - "math.sqrt": { argTypes: [F64], result: F64 }, - "math.log": { argTypes: [F64], result: F64 }, - "math.log1p": { argTypes: [F64], result: F64 }, - "math.log2": { argTypes: [F64], result: F64 }, - "math.log10": { argTypes: [F64], result: F64 }, - "math.atan2": { argTypes: [F64, F64], result: F64 }, - "math.pow": { argTypes: [F64, F64], result: F64 }, - "math.imul": { argTypes: [F64, F64], result: F64 }, - /** `Math.min(a, b)` / `Math.max(a, b)` — the two-argument scalar forms - * (scr_lib.c), JS-exact like the Arr folds: NaN poisons, max prefers +0 - * over -0 (min the reverse). C fmin/fmax are NOT these (they drop NaN). - * Never throw. */ - "math.min": { argTypes: [F64, F64], result: F64 }, - "math.max": { argTypes: [F64, F64], result: F64 }, - /** `Math.random()` — a uniform double in [0,1) with the spec's 53-bit - * granularity, drawn from arc4random_buf (the CSPRNG behind the crypto - * lowerings). Same distribution as Node, NECESSARILY different sequence - * (SEMANTICS.md 62 — no seeded sequence exists to match). Never throws. */ - "math.random": { argTypes: [], result: F64 }, - /** The static global parsers/tests (scr_string.c). num.parseInt is - * ECMA-262 19.2.5 exactly — JS whitespace, sign, ToInt32 radix (the - * frontend completes an omitted radix to 0 = the spec's "undefined": - * base 10 with the 0x hex escape), longest digit prefix, and the exact - * mathematical value correctly rounded (u64 fast path, bignum beyond — - * overflow is ±Infinity). num.isNaN is the NaN self-test on an - * already-number argument (tsc pins the argument to number, so no - * ToNumber coercion exists to model). Borrow; never throw. */ - "num.parseInt": { argTypes: [STRING, F64], result: F64 }, - /** ES parseFloat (scr_string.c): the longest StrDecimalLiteral prefix - * of the trimmed input (no hex, "Infinity" exact-case), NaN when none — - * ECMA-262 19.2.4 over a string argument (non-string arguments keep the - * fence: Node would ToNumber-coerce). Borrows; never throws. */ - "num.parseFloat": { argTypes: [STRING], result: F64 }, - /** `a.localeCompare(b)`: -1/0/1 in Node's root-locale collation for - * Latin-script text (scr_str_locale_compare). Never throws. */ - "str.localeCompare": { argTypes: [STRING, STRING], result: F64 }, - /** Annex B substr(start, length) with start ToNumber'd and length's - * presence; never throws. */ - "str.substr": { argTypes: [STRING, F64, F64, BOOL], result: STRING }, - /** toLocaleLowerCase/toLocaleUpperCase (scr_regex.c): the locales value - * as written, upper=true for toLocaleUpperCase. Throws for null and for - * locale forms with no native rules. */ - "str.localeCase": { argTypes: [STRING, DYN, BOOL], result: STRING }, - "str.replaceLiteral": { argTypes: [STRING, STRING, STRING, BOOL], result: STRING }, - // (subject, characters, NUL-separated replacement texts): the fused - // chain of global one-character replacements. - "str.replaceChars": { argTypes: [STRING, STRING, STRING], result: STRING }, - /* ToNumber(string) — ECMA-262 7.1.4.1 StringToNumber (scr_string.c): - * trim the JS StrWhiteSpace set, empty/whitespace-only → +0, then the - * whole span must be one StrNumericLiteral — signed decimal (Infinity - * included, strtod-over-validated-span correct rounding) or unsigned - * 0x/0o/0b (exact value, nearest-even; signed forms are NaN) — with - * any trailing garbage answering NaN. Number(aString), unary + on - * strings, and util.format %d over strings lower here. Borrows; never - * throws. */ - "num.fromString": { argTypes: [STRING], result: F64 }, - "num.isNaN": { argTypes: [F64], result: BOOL }, - /** The static URI component codecs (scr_string.c), ECMA-262 Encode/ - * Decode with the component sets over the runtime's UTF-8 strings. - * str.encodeUriComponent percent-encodes every byte outside the - * unreserved component set (ALPHA/DIGIT/- _ . ! ~ * ' ( )) as uppercase - * %XX — the spec's per-code-point UTF-8 encoding IS a byte scan here — - * and THROWS the spec's URIError ("URI malformed") on a lone surrogate. - * str.decodeUriComponent - * decodes %XX escapes bytewise (raw non-escape bytes copy through) and - * requires the escaped bytes to form strictly valid UTF-8 (overlong - * forms, surrogate code points, and >U+10FFFF refused, per UTF8-decode - * without replacement); bad hex or an invalid sequence THROWS the - * spec's URIError ("URI malformed"), catchable. Borrow; results +1. */ - "str.encodeUriComponent": { argTypes: [STRING], result: STRING }, - // The base64 globals: the argument is a dyn value (WebIDL ToString - // runs in the runtime); the zero-argument form always throws. - /** The WHATWG base64 globals (scr_string.c), Node-global since v16. - * Arguments are borrowed dyn values — WebIDL ToString runs in the - * runtime over the dyn kind (String(null) is "null": the html spec's - * coercion, which Node's atob(null) exercises). str.atob decodes - * forgiving-base64 (ASCII whitespace stripped, %4==0 strips up to two - * '=', %4==1 refuses, leftover bits discarded) into the latin1 code - * points as a string; a malformed input THROWS the catchable - * DOMException InvalidCharacterError ("The string to be decoded is not - * correctly encoded."). str.btoa encodes the string's code points as - * base64; any code point over U+00FF THROWS InvalidCharacterError - * ("Invalid character"). str.b64Missing is the zero-argument call of - * either: always throws Node's TypeError [ERR_MISSING_ARGS] "The - * \"input\" argument must be specified". Results +1. */ - "str.atob": { argTypes: [DYN], result: STRING }, - "str.btoa": { argTypes: [DYN], result: STRING }, - "str.b64Missing": { argTypes: [], result: STRING }, - "str.decodeUriComponent": { argTypes: [STRING], result: STRING }, - "str.decodeUri": { argTypes: [STRING], result: STRING }, - /** encodeURI: the same Encode() keeping the reserved set and '#' - * unescaped — throwing like the component encoder. Borrow; result +1. */ - "str.encodeUri": { argTypes: [STRING], result: STRING }, - /** RegExp.escape (ES2025): per-code-point EncodeForRegExpEscape — - * leading ASCII alphanumeric hex-escapes, syntax characters and '/' - * take a backslash, other punctuators/whitespace/line terminators - * hex-escape, the rest passes through. Total; borrows; result +1. */ - "regexp.escape": { argTypes: [STRING], result: STRING }, - /** Number.prototype formatters (scr_lib.c), JS-exact: - * num.toExponential is toExponential() with the spec's "as many digits - * as necessary"; num.toFixed0 is the non-throwing omitted-argument - * toFixed() fast path; num.toFixed implements an explicit fractionDigits - * with exact binary-value rounding and THROWS RangeError outside 0..100. - * Successful results +1. */ - "num.toExponential": { argTypes: [F64], result: STRING }, - "num.toFixed0": { argTypes: [F64], result: STRING }, - "num.toFixed": { argTypes: [F64, F64], result: STRING }, - /** toPrecision(precision) with the argument ToNumber'd and its presence - * (undefined answers ToString); toExponential with a DEFINED - * fractionDigits. Both throw V8's RangeError for out-of-range digits. */ - "num.toPrecision": { argTypes: [F64, F64, BOOL], result: STRING }, - "num.toExponentialDigits": { argTypes: [F64, F64], result: STRING }, - "num.toStringRadix": { argTypes: [F64, DYN], result: STRING }, - /** toString(radix) with a number radix (scr_lib.c): truncates it as - * ToIntegerOrInfinity does, THROWS RangeError outside 2..36. */ - "num.toStringRadixF64": { argTypes: [F64, F64], result: STRING }, - /** Object.is over two numbers — the spec's SameValue on doubles: NaN - * equals NaN, +0 differs from -0, everything else is `===`. Plain bool - * result; never throws. (Union-armed operands take unionEq's sameValue - * flag instead — this is the both-f64 fast path.) */ - "num.sameValue": { argTypes: [F64, F64], result: BOOL }, - /** `new Intl.NumberFormat("en-US").format(x)` and - * `x.toLocaleString("en-US")` with DEFAULT options — the one locale - * whose data the runtime embeds (Node's default-build locale): decimal - * notation, 0–3 fraction digits rounded half-up on the SHORTEST - * round-tripping decimal (ICU's rounding input, probed vs Node — - * format(1.0005) is "1.001" though toFixed(3) answers "1.000"), "," - * grouping every three integer digits, "∞"/"NaN" texts, and "-0" for - * negative inputs rounding to zero. Result +1; never throws. */ - "intl.numFormatEnUs": { argTypes: [F64], result: STRING }, - /** ES Symbol values (scr_symbol.c — link-gated by moduleUsesSymbol). - * sym.new: `Symbol(desc)` — a fresh runtime-unique identity (+1) whose - * one arg is the description string (borrowed); sym.newAnon is the - * description-less `Symbol()` form. sym.for: the Symbol.for global - * registry — one interned symbol per key (borrowed), +1 on every call. - * sym.toString: "Symbol(desc)" (+1 string; "Symbol()" when absent). - * sym.desc / sym.keyFor answer the interned `string | undefined` union - * (the runtime returns +1-or-NULL; the backend builds the union arms — - * the child.stdout pattern). None of these throw. */ - "sym.new": { argTypes: [STRING], result: SYMBOL_T }, - "sym.newAnon": { argTypes: [], result: SYMBOL_T }, - "sym.for": { argTypes: [STRING], result: SYMBOL_T }, - "sym.wellKnown": { argTypes: [STRING], result: SYMBOL_T }, - // Result is the interned `string | undefined` union — the libCall case - // checks the arms (the spawnRes.error pattern). - "sym.keyFor": { argTypes: [SYMBOL_T], result: VOID }, - "sym.desc": { argTypes: [SYMBOL_T], result: VOID }, - "sym.toString": { argTypes: [SYMBOL_T], result: STRING }, - /** Engine-free arbitrary-precision bigint operations (scr_bigint.c). - * parse accepts the ECMAScript BigInt string grammar; fromF64 accepts - * finite integral Numbers. Arithmetic returns fresh immutable values. - * cmp returns -1/0/1 as f64; toString accepts a numeric radix. */ - "bigint.parse": { argTypes: [STRING], result: BIGINT_T }, - "bigint.fromF64": { argTypes: [F64], result: BIGINT_T }, - "bigint.neg": { argTypes: [BIGINT_T], result: BIGINT_T }, - "bigint.not": { argTypes: [BIGINT_T], result: BIGINT_T }, - "bigint.add": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.sub": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.mul": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.div": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.mod": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.pow": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.and": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.or": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.xor": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.shl": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.shr": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, - "bigint.eq": { argTypes: [BIGINT_T, BIGINT_T], result: BOOL }, - "bigint.eqString": { argTypes: [BIGINT_T, STRING], result: BOOL }, - "bigint.cmp": { argTypes: [BIGINT_T, BIGINT_T], result: F64 }, - "bigint.cmpNumber": { argTypes: [BIGINT_T, F64], result: F64 }, - "bigint.truthy": { argTypes: [BIGINT_T], result: BOOL }, - "bigint.toString": { argTypes: [BIGINT_T, F64], result: STRING }, - "bigint.inspect": { argTypes: [BIGINT_T], result: STRING }, - "bigint.toF64": { argTypes: [BIGINT_T], result: F64 }, - "bigint.asUintN": { argTypes: [F64, BIGINT_T], result: BIGINT_T }, - "bigint.asIntN": { argTypes: [F64, BIGINT_T], result: BIGINT_T }, - "bigint.bufferRead": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: BIGINT_T }, - "bigint.bufferWrite": { argTypes: [BYTES_U8, BIGINT_T, F64, BOOL, BOOL], result: F64 }, - "bigint.dataViewGet": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: BIGINT_T }, - "bigint.dataViewSet": { argTypes: [BYTES_U8, F64, BIGINT_T, BOOL], result: VOID }, - /** node:url + the URL class (scr_url.c). url.new parses one absolute - * URL string into a mutable URL value (+1) — invalid input THROWS a - * catchable TypeError ("Invalid URL"), like Node's constructor. The - * getters (borrowed receiver, +1 string) never throw; url.href doubles - * as toString(). fileURLToPath has one libFn per receiver form (URL - * value / string) — both THROW Node's TypeErrors on non-file schemes, - * encoded slashes, and non-empty hosts. url.pathToFileURL resolves the - * path (getcwd) and never throws. */ - "url.new": { argTypes: [STRING], result: URL_T }, - "url.newBase": { argTypes: [STRING, STRING], result: URL_T }, - /** Factories consume already-converted input/base strings. canParse - * answers a boolean; parse returns a checked native URL or null (+1). - * Parser failures are suppressed; conversion occurs before these calls. */ - "url.canParse": { argTypes: [STRING], result: BOOL }, - "url.canParseBase": { argTypes: [STRING, STRING], result: BOOL }, - "url.parse": { argTypes: [STRING], result: DYN }, - "url.parseBase": { argTypes: [STRING, STRING], result: DYN }, - "url.protocol": { argTypes: [URL_T], result: STRING }, - "url.origin": { argTypes: [URL_T], result: STRING }, - "url.username": { argTypes: [URL_T], result: STRING }, - "url.password": { argTypes: [URL_T], result: STRING }, - "url.host": { argTypes: [URL_T], result: STRING }, - "url.hostname": { argTypes: [URL_T], result: STRING }, - "url.port": { argTypes: [URL_T], result: STRING }, - "url.pathname": { argTypes: [URL_T], result: STRING }, - "url.href": { argTypes: [URL_T], result: STRING }, - /** Selected component writes borrow the receiver and converted string. - * href replaces its parsed fields while retaining cached params identity. */ - "url.set": { argTypes: [URL_T, STRING, STRING], result: VOID }, - "url.setChecked": { argTypes: [URL_T, STRING, DYN], result: VOID }, - "url.fileURLToPathUrl": { argTypes: [URL_T], result: STRING }, - /** fileURLToPathUrl as a value: the path string, or the error it would - * throw (fs.existsSync and fs.promises URL paths). Never throws. */ - "url.fileURLToPathValue": { argTypes: [URL_T], result: DYN }, - /** getValidatedPath's null-byte check on a string path, and - * readFileSync's encoding check: what Node validates before it converts - * a later URL path. (path, argument name) and (encoding). */ - "fs.checkPath": { argTypes: [STRING, STRING], result: VOID }, - "fs.checkReadEncoding": { argTypes: [STRING], result: VOID }, - "url.fileURLToPathChecked": { argTypes: [DYN], result: STRING }, - "url.fileURLToPathOptions": { argTypes: [DYN, DYN], result: STRING }, - /** node:url's legacy API (scr_url_legacy.c): parse(url, parseQueryString, - * slashesDenoteHost) answers the checked-dynamic Url instance; format - * takes the urlObject (a Url-shaped object, a string, or a URL) and the - * URL form's options; resolve takes (from, to). The trailing bool says - * whether the call site emits DEP0169. domainToASCII/domainToUnicode - * take the domain and whether it was omitted. Node's validation errors - * throw catchably. */ - "url.legacyParse": { argTypes: [DYN, DYN, DYN, BOOL], result: DYN }, - "url.legacyFormat": { argTypes: [DYN, DYN, BOOL], result: STRING }, - "url.legacyResolve": { argTypes: [DYN, DYN, BOOL], result: STRING }, - "url.domainToASCII": { argTypes: [DYN, BOOL], result: STRING }, - "url.domainToUnicode": { argTypes: [DYN, BOOL], result: STRING }, - /** url.urlToHttpOptions over a checked-dynamic argument: a URL answers - * Node's null-prototype options object; a primitive or null throws - * ERR_INVALID_ARG_TYPE. */ - "url.toHttpOptions": { argTypes: [DYN], result: DYN }, - /** node:punycode (scr_url.c): punycode.js 2.1.0's string conversions; - * decode/toUnicode (and, past 32-bit deltas, encode/toASCII) throw the - * module's RangeErrors, ucs2Encode String.fromCodePoint's. warn marks the - * module's first load (scr_async_dyn.c) and reports DEP0040 then unless - * the second argument is false: at once for a static ES import, on the - * next tick for a require() call. */ - "punycode.decode": { argTypes: [STRING], result: STRING }, - "punycode.encode": { argTypes: [STRING], result: STRING }, - "punycode.toASCII": { argTypes: [STRING], result: STRING }, - "punycode.toUnicode": { argTypes: [STRING], result: STRING }, - "punycode.ucs2Decode": { argTypes: [STRING], result: arrayOf(F64) }, - "punycode.ucs2Encode": { argTypes: [arrayOf(F64)], result: STRING }, - "punycode.warn": { argTypes: [BOOL, BOOL], result: VOID }, - "url.fileURLToPathBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 }, - "url.pathToFileURLChecked": { argTypes: [DYN, DYN], result: URL_T }, - "url.fileURLToPathStr": { argTypes: [STRING], result: STRING }, - "url.pathToFileURL": { argTypes: [STRING], result: URL_T }, - /** Explicit path syntax from the options.windows argument, independent - * of the executable's host platform. Windows UNC input can throw. */ - "url.pathToFileURLPlatform": { argTypes: [STRING, BOOL], result: URL_T }, - /** pathToFileURL under a win32 TARGET: the same scr_url_from_path call - * (the runtime selects the win32 arm by _WIN32), but a distinct IR name - * because that arm THROWS for malformed UNC inputs — may-throw seeds on - * it while posix pathToFileURL emission stays byte-identical. */ - "url.pathToFileURLWin32": { argTypes: [STRING], result: URL_T }, - /** URLSearchParams (scr_url.c — always linked with the url unit). - * Construction: sp.new (empty), sp.parse (one borrowed init string — - * a single leading '?' strips, Node's constructor), sp.copy (snapshot - * of another list), sp.fromPairs (a string[][] value — THROWS Node's - * ERR_INVALID_TUPLE TypeError on a non-[name, value] row; the one - * throwing entry in the family), sp.with (the record-literal init - * desugar: append one pair, answer the same list +1 — chains fold - * `new URLSearchParams({...})` into nested calls). url.searchParams - * answers the URL's LIVE cached view (+1, one identity per URL); - * url.search is the WHATWG search getter ("" for no/empty query). - * Methods mirror the WHATWG surface: sp.get answers +1-or-NULL (the - * sym.desc union pattern, null arm), sp.getAll a fresh string[]; - * sp.append/sp.set/sp.delete/sp.deleteValue/sp.sort mutate and - * re-serialize a live view's URL query; sp.has/sp.hasValue answer - * bools; sp.size/sp.toString are pure reads. sp.keyAt/sp.valAt are - * the for-of/forEach desugar's index reads (live — the loop re-reads - * sp.size each pass). */ - "sp.new": { argTypes: [], result: SEARCH_PARAMS_T }, - "sp.newChecked": { argTypes: [DYN], result: SEARCH_PARAMS_T }, - "sp.parse": { argTypes: [STRING], result: SEARCH_PARAMS_T }, - "sp.copy": { argTypes: [SEARCH_PARAMS_T], result: SEARCH_PARAMS_T }, - // The pairs argument is string[][] — checked structurally in validate.ts (the - // generic array-of slot has no named constant here). - "sp.fromPairs": { argTypes: [null], result: SEARCH_PARAMS_T }, - "sp.with": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: SEARCH_PARAMS_T }, - "url.searchParams": { argTypes: [URL_T], result: SEARCH_PARAMS_T }, - "url.search": { argTypes: [URL_T], result: STRING }, - "url.hash": { argTypes: [URL_T], result: STRING }, - // Result is the interned `string | null` union — the libCall case - // checks the arms (the spawnRes.signal pattern). - "sp.get": { argTypes: [SEARCH_PARAMS_T, STRING], result: VOID }, - "sp.getAll": { argTypes: [SEARCH_PARAMS_T, STRING], result: arrayOf(STRING) }, - "sp.append": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, - "sp.set": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, - "sp.delete": { argTypes: [SEARCH_PARAMS_T, STRING], result: VOID }, - "sp.deleteValue": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, - "sp.has": { argTypes: [SEARCH_PARAMS_T, STRING], result: BOOL }, - "sp.hasValue": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: BOOL }, - "sp.sort": { argTypes: [SEARCH_PARAMS_T], result: VOID }, - "sp.size": { argTypes: [SEARCH_PARAMS_T], result: F64 }, - "sp.toString": { argTypes: [SEARCH_PARAMS_T], result: STRING }, - "sp.keyAt": { argTypes: [SEARCH_PARAMS_T, F64], result: STRING }, - "sp.valAt": { argTypes: [SEARCH_PARAMS_T, F64], result: STRING }, - // node:querystring. qs.parse's result is the call site's ParsedUrlQuery - // dictionary record (VOID is the networkInterfaces sentinel — the - // libCall case checks the structure); qs.stringify's object argument - // is a dyn value (the frontend dynFroms typed records). - /** node:querystring (scr_qs.c — link-gated by moduleUsesQs; NOT - * URLSearchParams: the legacy codec's escaping and '+' rules differ). - * qs.escape is Node's qsEscape, which encodes exactly the component - * unreserved set — it emits the always-linked scr_qs_escape, so - * escape-only programs never pull the unit. qs.unescape is Node's qsUnescape: strict decodeURIComponent - * first, the lenient legacy unescapeBuffer fallback on failure (never - * throws). qs.parse takes (str, sep, eq, maxKeys) — the frontend - * completes omitted/null sep/eq to "&"/"=" and the omitted maxKeys - * option to Node's 1000 (0 and negatives mean unlimited, Node's rule) — - * and its result type is the CALL SITE's mapped ParsedUrlQuery shape (a - * pure index-signature record over `string | string[]`, an undefined - * arm tolerated — @types/node's Dict), verified structurally by the - * frontend (lowerQuerystringParseCall); the emitters construct the - * record and hand its overflow map to scr_qs_parse_into with the two - * union tags. qs.stringify takes (obj, sep, eq) with obj a dyn value - * (the frontend dynFroms the typed record; JS-world dyn values pass - * through) — Node's encodeStringified rules run in the runtime, so - * arrays expand to repeated keys and null/undefined/nested objects are - * empty values. Custom encoder/decoder options fence at compile time. - * All borrow; string results +1; escape and stringify throw URIError - * [ERR_INVALID_URI] on a lone surrogate. */ - "qs.parse": { argTypes: [STRING, STRING, STRING, F64], result: VOID }, - "qs.stringify": { argTypes: [DYN, STRING, STRING], result: STRING }, - "qs.escape": { argTypes: [STRING], result: STRING }, - "qs.unescape": { argTypes: [STRING], result: STRING }, - /** node:util.parseArgs: one checked-dynamic config object in, one - * checked-dynamic ParsedResults tree out. Statically typed member reads - * validate their values when leaving that tree. The native parser may - * throw Node's coded validation/grammar TypeErrors. */ - "util.parseArgs": { argTypes: [DYN], result: DYN }, - "util.parseEnv": { argTypes: [DYN], result: DYN }, - "util.getSystemErrorName": { argTypes: [DYN], result: STRING }, - "util.getSystemErrorMessage": { argTypes: [DYN], result: STRING }, - "util.systemErrorEntries": { argTypes: [], result: DYN }, - "util.stripVTControlCharacters": { argTypes: [DYN], result: STRING }, - "util.toUSVString": { argTypes: [DYN], result: STRING }, - "util.isDeepStrictEqual": { argTypes: [DYN, DYN, DYN], result: BOOL }, - "util.styleText": { argTypes: [DYN, DYN, DYN], result: STRING }, - /** util.inherits/deprecate/callbackify/debuglog over checked-dynamic - * arguments (scr_util_callable.c). The function results are native dyn - * wrappers; call sites with a static function type check them back. */ - "util.inherits": { argTypes: [DYN, DYN], result: VOID }, - "util.inheritsPrototype": { argTypes: [DYN, DYN, DYN], result: VOID }, - "util.deprecate": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, - "util.callbackify": { argTypes: [DYN], result: DYN }, - /** util.promisify over an untyped function value: a native dyn function - * whose calls answer a dyn promise the appended callback settles. */ - "util.promisifyDyn": { argTypes: [DYN], result: DYN }, - "util.debuglog": { argTypes: [DYN, DYN], result: DYN }, - "util.typeIs": { argTypes: [DYN, STRING], result: BOOL }, - "util.typeValue": { argTypes: [DYN, STRING], result: DYN }, - /** fs.statSync → a Stats value (may throw, like the other sync fs - * calls); the stats.* getters are pure reads on it. */ - "fs.statSync": { argTypes: [STRING], result: STATS_T }, - "fs.lstatSync": { argTypes: [STRING], result: STATS_T }, - // statSync / lstatSync(path, { throwIfNoEntry }): arg 1 is the flag. - // Result is the interned `Stats | undefined` union (the runtime answers - // +1-or-NULL; the backend builds the arms). - "fs.statProbeSync": { argTypes: [STRING, BOOL], result: VOID }, - "fs.lstatProbeSync": { argTypes: [STRING, BOOL], result: VOID }, - "fs.fstatSync": { argTypes: [F64], result: STATS_T }, - "fs.fchmodSync": { argTypes: [F64, F64], result: VOID }, - "fs.fsyncSync": { argTypes: [F64], result: VOID }, - "fs.fdatasyncSync": { argTypes: [F64], result: VOID }, - "fs.ftruncateSync": { argTypes: [F64, F64], result: VOID }, - "fs.utimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, - "fs.futimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, - "fs.lutimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, - "fs.readvSync": { argTypes: [F64, arrayOf(BYTES_U8), F64], result: F64 }, - "fs.writevSync": { argTypes: [F64, arrayOf(BYTES_U8), F64], result: F64 }, - "fs.linkSync": { argTypes: [DYN, DYN], result: VOID }, - "fs.symlinkSync": { argTypes: [DYN, DYN, DYN], result: VOID }, - /** fs.cpSync / fs/promises cp (scr_fs_cp.c): src, dest and options are - * dyn values the runtime validates in Node's order. */ - "fs.cpSync": { argTypes: [DYN, DYN, DYN], result: VOID }, - "fs.readlinkSync": { argTypes: [DYN, DYN], result: DYN }, - "fs.statfsSync": { argTypes: [DYN, DYN], result: DYN }, - "fs.readlinkSyncStr": { argTypes: [DYN, DYN], result: STRING }, - "fs.readlinkSyncBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 }, - /** fs.openSync(path, flags) → the raw fd as f64; fs.readSync/fs.writeSync - * over Buffer windows perform sequential I/O when position is -1 and - * offset-preserving positioned I/O otherwise; fs.writeStrSync is the - * utf8 string twin; and fs.closeSync(fd) closes the descriptor. This is - * the fd slice behind spawn/log-processing forms. - * flags is Node's string - * grammar ("r", "w", "a" and the +/x variants; unknown flags throw - * Node's ERR_INVALID_ARG_VALUE TypeError text). All throw Node-shaped - * fs errors (openSync ENOENT/EACCES..., readSync EBADF/range errors, - * closeSync EBADF). */ - "fs.openSync": { argTypes: [STRING, STRING], result: F64 }, - "fs.openNumericSync": { argTypes: [STRING, F64, F64], result: F64 }, - "fs.readSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 }, - "fs.readSyncDyn": { argTypes: [DYN, DYN, DYN, DYN, DYN], result: F64 }, - "fs.writeSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 }, - "fs.writeStrSync": { argTypes: [F64, STRING, F64, STRING], result: F64 }, - // fs.watch's callback func type is program-dependent (zero params, or - // the eventType string) — the slot pins arity and the path/receiver. - /** fs.watch(path, listener?) → an FSWatcher handle (scr_watch.c — - * linked, and scr_watch_install() emitted, only when these appear on - * the IR; moduleUsesFsWatch is the switch). The path opens NOW — - * failure throws Node's fs error synchronously ("ENOENT: ..., watch - * 'x'", the polling-fallback catch shape) — and the unit's kqueue - * (EVFILT_VNODE) delivers "rename"/"change" through the loop's watch - * hook. The callback (nullable) MOVES in with an adapter per listener - * shape (zero-param, or the (eventType: string) form — runtime- - * provided); an open watcher keeps the loop alive until watcher.close() - * (idempotent, receiver borrowed, statement position only). */ - "fs.watch": { argTypes: [STRING], result: FSWATCHER_T }, - "fs.watchCb": { argTypes: [STRING, null], result: FSWATCHER_T }, - "watcher.close": { argTypes: [FSWATCHER_T], result: VOID }, - /** The composed `new crypto.X509Certificate(data).fingerprint` read - * (scr_lib.c — no certificate handle exists): the SHA-1 of the DER, - * uppercase colon-separated, over PEM or raw-DER Buffer input; other - * inputs throw Node's ERR_OSSL_PEM_NO_START_LINE Error (may-throw). */ - "crypto.x509Fingerprint": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509FingerprintStr": { argTypes: [STRING], result: STRING }, - /** The certificate's Validity window (validFrom / validTo reads — - * scr_lib.c's minimal ASN.1 walk to the TBSCertificate validity - * SEQUENCE): UTCTime and GeneralizedTime render in Node's exact - * ASN1_TIME_print shape ("Jul 1 00:00:00 2026 GMT" — %2d space-padded - * day). Same input contract and PEM error as the fingerprint pair - * (may-throw). */ - "crypto.x509ValidFrom": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509ValidFromStr": { argTypes: [STRING], result: STRING }, - "crypto.x509ValidTo": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509ValidToStr": { argTypes: [STRING], result: STRING }, - /** The certificate's DER as a fresh Buffer (the raw field), decoded - * once from the constructor's input; the same PEM error (may-throw). */ - "crypto.x509Raw": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "crypto.x509RawDer": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "crypto.x509RawStr": { argTypes: [STRING], result: BYTES_U8 }, - /** The remaining X509Certificate fields over that DER, in Node's - * renderings (scr_lib.c). subjectAltName, infoAccess and - * signatureAlgorithm answer "" where Node answers undefined. Borrowed; - * never throw. */ - "crypto.x509Subject": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509Issuer": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509SubjectAltName": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509InfoAccess": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509SerialNumber": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509Fingerprint256": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509Fingerprint512": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509Ca": { argTypes: [BYTES_U8], result: BOOL }, - "crypto.x509SignatureAlgorithm": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509SignatureAlgorithmOid": { argTypes: [BYTES_U8], result: STRING }, - /** X509Certificate methods over its raw DER. checkHost/checkEmail/checkIP - * answer the matching name or "" (undefined), checkIssued compares two - * certificates, x509Pem is toString's PEM (scr_x509.c); publicKey, - * verify and checkPrivateKey read keys (scr_pki.c). */ - "crypto.x509CheckHost": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, - "crypto.x509CheckEmail": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, - "crypto.x509CheckIP": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, - "crypto.x509CheckIssued": { argTypes: [BYTES_U8, BYTES_U8], result: BOOL }, - "crypto.x509LegacyObject": { argTypes: [BYTES_U8], result: DYN }, - "crypto.x509Pem": { argTypes: [BYTES_U8], result: STRING }, - "crypto.x509PublicKey": { argTypes: [BYTES_U8], result: DYN }, - "crypto.x509Verify": { argTypes: [BYTES_U8, DYN], result: BOOL }, - "crypto.x509CheckPrivateKey": { argTypes: [BYTES_U8, DYN], result: BOOL }, - "fs.closeSync": { argTypes: [F64], result: VOID }, - "stats.isFile": { argTypes: [STATS_T], result: BOOL }, - "stats.isDirectory": { argTypes: [STATS_T], result: BOOL }, - "stats.isSymbolicLink": { argTypes: [STATS_T], result: BOOL }, - "stats.size": { argTypes: [STATS_T], result: F64 }, - "stats.dev": { argTypes: [STATS_T], result: F64 }, - "stats.ino": { argTypes: [STATS_T], result: F64 }, - "stats.blocks": { argTypes: [STATS_T], result: F64 }, - "stats.nlink": { argTypes: [STATS_T], result: F64 }, - "stats.atimeMs": { argTypes: [STATS_T], result: F64 }, - "stats.mtimeMs": { argTypes: [STATS_T], result: F64 }, - "stats.ctimeMs": { argTypes: [STATS_T], result: F64 }, - "stats.birthtimeMs": { argTypes: [STATS_T], result: F64 }, - "stats.mode": { argTypes: [STATS_T], result: F64 }, - "stats.uid": { argTypes: [STATS_T], result: F64 }, - "stats.gid": { argTypes: [STATS_T], result: F64 }, - "stats.rdev": { argTypes: [STATS_T], result: F64 }, - "stats.blksize": { argTypes: [STATS_T], result: F64 }, - "stats.isBlockDevice": { argTypes: [STATS_T], result: BOOL }, - "stats.isCharacterDevice": { argTypes: [STATS_T], result: BOOL }, - "stats.isFIFO": { argTypes: [STATS_T], result: BOOL }, - "stats.isSocket": { argTypes: [STATS_T], result: BOOL }, - /** The Date fields: the *Ms value rounded (Node's dateFromMs). */ - "stats.atime": { argTypes: [STATS_T], result: DATE_T }, - "stats.mtime": { argTypes: [STATS_T], result: DATE_T }, - "stats.ctime": { argTypes: [STATS_T], result: DATE_T }, - "stats.birthtime": { argTypes: [STATS_T], result: DATE_T }, - // The wider sync fs slice (unlink/chmod/chown/copyfile and the - // mode-carrying write/mkdir forms). - /** The wider sync fs slice (scr_lib.c), all throwing catchably with - * Node's errno message shapes and `.code` stamped like the rest of - * sync fs. unlink/chmod/chown wrap the syscalls 1:1 (Node reports the - * syscall's own name). copyFileSync copies contents into a fresh (or - * truncated) destination carrying the SOURCE's mode (libuv's - * uv_fs_copyfile behavior); its errors carry BOTH paths — Node's - * "copyfile 'src' -> 'dest'". lstatSync is statSync without following - * a trailing symlink (Node reports lstat); stats.isSymbolicLink / - * stats.dev / ino / blocks / nlink / atimeMs / mtimeMs / ctimeMs are pure reads on the widened - * snapshot (blocks is allocated 512-byte units; the times are milliseconds - * with their sub-second fractions, Node's arithmetic). - * writeFileModeSync is writeFileSync(path, data, { mode }): the mode - * applies at CREATION only (open(2) with O_CREAT, umask applying), - * exactly Node — an existing file keeps its permissions. mkdirModeSync - * / mkdirRecursiveModeSync are the mkdirSync option forms with an - * explicit mode (the recursive walk passes it to every directory it - * creates, like Node's). */ - "fs.unlinkSync": { argTypes: [STRING], result: VOID }, - "fs.chmodSync": { argTypes: [STRING, F64], result: VOID }, - "fs.chownSync": { argTypes: [STRING, F64, F64], result: VOID }, - "fs.lchownSync": { argTypes: [STRING, F64, F64], result: VOID }, - "fs.fchownSync": { argTypes: [F64, F64, F64], result: VOID }, - /** truncateSync opens the path with 'r+', then truncates (len checked and - * clamped like ftruncateSync's) and closes it. */ - "fs.truncateSync": { argTypes: [STRING, F64], result: VOID }, - "fs.copyFileSync": { argTypes: [STRING, STRING], result: VOID }, - /** renameSync is the direct two-path syscall wrapper. renameCb defers - * the operation to the loop and fires a program-shaped Error | null - * callback through a backend-emitted adapter. */ - "fs.renameSync": { argTypes: [STRING, STRING], result: VOID }, - // Callback type is program-dependent (zero params or Error | null). - "fs.renameCb": { argTypes: [STRING, STRING, null], result: VOID }, - "fs.writeFileModeSync": { argTypes: [STRING, STRING, F64], result: VOID }, - "fs.mkdirModeSync": { argTypes: [STRING, F64], result: VOID }, - "fs.mkdirRecursiveModeSync": { argTypes: [STRING, F64], result: VOID }, - "atomics.wait": { argTypes: [bytesOf("i32"), F64, F64, F64], result: STRING }, - "atomics.notify": { argTypes: [bytesOf("i32"), F64, F64], result: F64 }, - "atomics.op": { argTypes: [null, F64, F64, F64, F64], result: F64 }, - "atomics.opDyn": { argTypes: [DYN, F64, F64, F64, F64], result: F64 }, - "atomics.waitDyn": { argTypes: [DYN, F64, F64, F64], result: STRING }, - "atomics.notifyDyn": { argTypes: [DYN, F64, F64], result: F64 }, - "worker.new": { argTypes: [F64, STRING, DYN], result: DYN }, - "worker.isMainThread": { argTypes: [], result: BOOL }, - "worker.threadId": { argTypes: [], result: F64 }, - "worker.root": { argTypes: [], result: F64 }, - "worker.data": { argTypes: [], result: DYN }, - "worker.parentPort": { argTypes: [], result: DYN }, - /** worker_threads.resourceLimits: {} in the main thread; a refusal in a - * worker (scr_worker_events.c). */ - "worker.resourceLimits": { argTypes: [], result: DYN }, - /** worker_threads.threadName: the Worker's name in a worker, "" in the - * main thread (scr_worker.c). */ - "worker.threadName": { argTypes: [], result: DYN }, - /** new MessageChannel() ({ port1, port2 }) and receiveMessageOnPort(port) - * (scr_worker_events.c; the latter may throw). */ - "worker.channel": { argTypes: [], result: DYN }, - "worker.receiveMessageOnPort": { argTypes: [DYN], result: DYN }, - /** setEnvironmentData(key, value) and getEnvironmentData(key) - * (scr_worker_events.c; arguments borrowed). */ - "worker.setEnvironmentData": { argTypes: [DYN, DYN], result: VOID }, - "worker.getEnvironmentData": { argTypes: [DYN], result: DYN }, - /** markAsUntransferable(value) and isMarkedAsUntransferable(value) - * (scr_worker_events.c; the value borrowed). */ - "worker.markUntransferable": { argTypes: [DYN], result: VOID }, - "worker.isMarkedUntransferable": { argTypes: [DYN], result: BOOL }, - /** markAsUncloneable(value) (scr_json.c; the value borrowed). */ - "dyn.markUncloneable": { argTypes: [DYN], result: VOID }, - /** before new Worker() with the stdout or stderr option: links the stream - * unit that worker.stdout and worker.stderr read through (scr_stream.c). */ - "stream.workerStdio": { argTypes: [], result: VOID }, - /** new BroadcastChannel(...args) (scr_worker_events.c; may throw), after - * event.broadcastInstall, which installs its EventTarget side. */ - "worker.broadcastChannel": { argTypes: [DYN], result: DYN }, - "event.broadcastInstall": { argTypes: [], result: VOID }, - /** child_process.spawnSync (scr_child.c): posix_spawn + waitpid + piped - * utf8 capture — cmd borrowed, args one borrowed string[] (the frontend - * completes an omitted list to an empty literal), result an owned (+1) - * spawnRes. NEVER throws: spawn failure (nonexistent binary, EACCES) is - * data, like Node's error property — status null and empty outputs - * (SEMANTICS.md documents the divergence from Node's null stdout). - * The getters are pure reads: status is the interned `number | null` - * union (null = spawn failure or signal death, type-directed - * construction in the backend like process.envGet); stdout/stderr are - * +1 strings. */ - "cp.spawnSync": { argTypes: [STRING, arrayOf(STRING)], result: SPAWNRES_T }, - // spawnSync's options form: timeout, killSignal name ("" = SIGTERM), - // and the three stdio modes. - /** spawnSync with options (scr_child.c): the cp.spawnSync core plus the - * option slice portless-class CLIs pass — timeout (killSignal fires at - * the deadline and the result carries error: ETIMEDOUT + the signal, - * Node's shape), killSignal (a signal NAME; "" = the SIGTERM default), - * and per-fd stdio modes (in: 0 /dev/null, 1 ignore, 2 inherit; out/ - * err: 0 capture, 1 ignore → "", 2 inherit → ""). NEVER throws — spawn - * failure and timeout are data on the result, like Node's error - * property. spawnRes.signal is the result's termination signal as the - * call site's `Signals | null` union (null = exited normally or spawn - * failure), constructed type-directedly like spawnRes.status. */ - "cp.spawnSyncOpts": { - argTypes: [STRING, arrayOf(STRING), F64, STRING, F64, F64, F64], - result: SPAWNRES_T, - }, - /** cp.spawnSyncOpts with the `env` option: the trailing [k,v,...] - * pairs replace the child's environment (recordToEnvPairs). */ - "cp.spawnSyncEnv": { - argTypes: [STRING, arrayOf(STRING), F64, STRING, F64, F64, F64, arrayOf(STRING)], - result: SPAWNRES_T, - }, - /** cp.spawnSyncOpts with the stdio carried as a RUNTIME string — - * "pipe" | "ignore" | "inherit", proven by the call site's TYPE (the - * defaultRunner idiom `stdio: options?.stdio ?? "pipe"`); the runtime - * maps the value to the three modes. Args (cmd, argv, timeout, - * killSignal, stdio). Never throws, like the other spawnSync forms. */ - "cp.spawnSyncStdioStr": { - argTypes: [STRING, arrayOf(STRING), F64, STRING, STRING], - result: SPAWNRES_T, - }, - // node:net (scr_net.c). Listener slots are program-dependent closures — - // null slots, checked in the libCall case (the child.onExit precedent); - // the zero-param callbacks pin the exact func type here. The trailing - // BOOL on registrations is the once flag. - /** node:net (scr_net.c — linked, and scr_net_install() emitted, only - * when one of these appears on the IR; moduleUsesNet is the switch). - * Receivers are borrowed; CALLBACKS MOVE into the handle's listener - * registry and are released at settlement (the child.onExit story). - * All listener registrations carry a trailing BOOL once-flag (`on` vs - * `once`) and are void — chaining is fenced. None of these throw: - * listen/connect failures are the async 'error' event, like Node. - * - * net.createServer's optional connection handler and the - * serverOnConnection listeners take the runtime-provided adapters - * (zero-param, or the one-param socket shape); sockOnData's adapters - * are the stdin pair's shapes (zero-param / bytes chunk); the error - * events reuse the child %Error adapters. net.listen/net.listenCb bind - * NOW and defer 'listening' to the next loop turn (Node's next-tick - * emit); net.serverPort is the composed `server.address().port` read. - * net.connect's host argument is a string ("localhost" pins to - * 127.0.0.1 — SEMANTICS.md); the connect callback is once('connect'). */ - "net.createServer": { argTypes: [], result: NETSERVER_T }, - "net.createServerCb": { argTypes: [null], result: NETSERVER_T }, - "net.listen": { argTypes: [NETSERVER_T, F64], result: VOID }, - /** listen(path[, cb]): a Unix-socket server; the socket file is removed - * when the server closes. Failures are the async 'error'. */ - "net.listenPath": { argTypes: [NETSERVER_T, STRING], result: VOID }, - "net.listenPathCb": { - argTypes: [NETSERVER_T, STRING, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "net.listenCb": { - argTypes: [NETSERVER_T, F64, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - /** listen({ port, host, ipv6Only }[, cb]) — the explicit-interface bind - * (portless's listenOnProxyInterface): args [server, port, host, - * ipv6Only]. host is an IP literal string ("" = the host-less - * dual-stack any default); ipv6Only sets IPV6_V6ONLY before the bind. - * These are the old-shaped calls, retained for omitted reusePort so - * serialized IR remains compatible. Failures are the async 'error', - * message in Node's listen shape with the requested host. Never throws. */ - "net.listenOpts": { argTypes: [NETSERVER_T, F64, STRING, BOOL], result: VOID }, - // The callback slot is a zero-param void closure OR its - // `(() => void) | undefined` optional-binding union (checked specially). - "net.listenOptsCb": { argTypes: [NETSERVER_T, F64, STRING, BOOL, null], result: VOID }, - /** Additive ABI for listen({ ..., reusePort }) — args - * [server, port, host, ipv6Only, reusePort], with the callback following - * those values in the callback form. The final BOOL is the requested - * kernel connection-distribution option; old spellings remain - * four/five-argument calls so serialized IR stays compatible. */ - "net.listenOptsReusePort": { argTypes: [NETSERVER_T, F64, STRING, BOOL, BOOL], result: VOID }, - // The callback is a zero-param void closure OR its optional-binding union; - // the callback follows the reusePort BOOL in this additive ABI. - "net.listenOptsReusePortCb": { - argTypes: [NETSERVER_T, F64, STRING, BOOL, BOOL, null], - result: VOID, - }, - "net.serverPort": { argTypes: [NETSERVER_T], result: F64 }, - "net.serverListening": { argTypes: [NETSERVER_T], result: BOOL }, - /** socket.ref()/unref() and server.ref()/unref(): whether the handle - * keeps the event loop alive. */ - "net.sockSetRef": { argTypes: [NETSOCKET_T, BOOL], result: VOID }, - "net.serverSetRef": { argTypes: [NETSERVER_T, BOOL], result: VOID }, - // net.serverAddress's record result is shape-checked in the libCall case - // (the dgram.address sentinel pattern). - /** server.address() as the full AddressInfo record (the dgram.address - * materialization pattern: the emitter builds the record from the three - * runtime reads; the frontend pinned the shape). Never throws — before - * listen it answers the any-form defaults with port 0 where Node - * answers null (the serverPort stance). */ - "net.serverAddress": { argTypes: [NETSERVER_T], result: VOID }, - "net.serverClose": { argTypes: [NETSERVER_T], result: VOID }, - /** server.close((err) => ...): the callback (a dyn function) gets - * undefined once the server closes, or Node's ERR_SERVER_NOT_RUNNING - * when it was not listening. */ - "net.serverCloseDyn": { argTypes: [NETSERVER_T, DYN], result: VOID }, - /** server.address() in programs that may listen on a Unix socket: the - * path a listening server answers ("" for a TCP server), and the check - * a record-typed address() makes first (throws for a Unix server). */ - "net.serverUnixPath": { argTypes: [NETSERVER_T], result: STRING }, - "net.serverAddressCheck": { argTypes: [NETSERVER_T], result: VOID }, - "net.serverCloseCb": { - argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "net.serverOnError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - "net.serverOnClose": { - argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "net.serverOnConnection": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - /** 'secureConnection' — the TLS server's deferred 'connection' list - * (handshake-completion timing); on a server without deferred - * connections (a plain net server) the registration never fires, - * exactly Node's split. */ - "net.serverOnSecureConnection": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - "net.connect": { argTypes: [F64, STRING], result: NETSOCKET_T }, - /** connect with a validated autoSelectFamilyAttemptTimeout option (the - * budget runs Node's validateInt32-from-1 ladder and is then inert — - * the single dial has nothing to time). May-throw. */ - "net.connectAttempt": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, - /** net.connect/createConnection over a RUNTIME option bag (computed - * keys — the invalid-input probes): Node-order validation (the - * objectMode trio's ERR_INVALID_ARG_VALUE, validatePort, host string, - * autoSelectFamily boolean, the attempt budget), then the trailing - * compiler-rendered fence — ALWAYS THROWS (the error.nodeThrow - * polymorphic-result carve-out). May-throw seed. */ - "net.connectOptsChk": { argTypes: [DYN, STRING], result: VOID }, - "net.connectCb": { - argTypes: [F64, STRING, { kind: "func", params: [], ret: VOID }], - result: NETSOCKET_T, - }, - // The lookup's exact func shape is program data (its answer callback's - // union/record types) — checked specially in the libCall case. - /** connect({ port, host, autoSelectFamily: true, lookup }) — the - * caller-resolver dial (portless's createLoopbackConnection): args - * [port, host, lookup]. The runtime creates the (connecting) socket - * handle, invokes the lookup as Node does — lookup(hostname, options, - * callback), options crossing as the dyn undefined — and the answer - * closure (an emitter-synthesized per-shape thunk over a boxed socket, - * the SNI-answer pattern) dials the answered addresses IN ORDER: each - * connect failure tries the next, the LAST failure's message is the - * socket's 'error' (Node's autoSelectFamily aggregate is a documented - * divergence), and a lookup error surfaces as the deferred socket - * 'error'. A synchronous throw INSIDE the lookup propagates out of the - * connect call (may-throw seed). */ - "net.connectLookup": { argTypes: [F64, STRING, null], result: NETSOCKET_T }, - /** connect({ host, port, lookup }) with a lookup of Node's own type, as - * a checked-dynamic function: the runtime calls it with { hints, all: - * true } and a callback taking an address string or list, then dials - * the answers in order. MAY THROW (host validation, a lookup throw). */ - "net.connectLookupDyn": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, - "net.sockWrite": { argTypes: [NETSOCKET_T, STRING], result: VOID }, - "net.sockWriteBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, - "net.sockEnd": { argTypes: [NETSOCKET_T], result: VOID }, - "net.sockEndStr": { argTypes: [NETSOCKET_T, STRING], result: VOID }, - "net.sockEndBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, - /** write/end with a CHECKED-DYNAMIC chunk (an untyped JS payload into a - * typed socket): the runtime dispatches STR/BYTES and throws Node's - * ERR_INVALID_ARG_TYPE chunk TypeError on any other kind. */ - "net.sockWriteDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, - "net.sockEndDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, - "net.sockDestroy": { argTypes: [NETSOCKET_T], result: VOID }, - "net.sockPipe": { argTypes: [NETSOCKET_T, NETSOCKET_T], result: VOID }, - /** socket.pipe(res) — raw socket chunks into a ServerResponse body - * (the extended-CONNECT bridge leg): each chunk is a body write (the - * response's own framing applies); source EOF end()s the response, - * pipe's default. Borrows both. Never throws. */ - "net.sockPipeRes": { argTypes: [NETSOCKET_T, HTTPRES_T], result: VOID }, - "net.sockOnData": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, - "net.sockOnEnd": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "net.sockOnClose": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "net.sockOnError": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, - "net.sockOnConnect": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - // node:dgram + node:dns (scr_dgram.c). The message/error listeners and - // dns.lookup's callback are program-dependent closures (null slots, - // checked in the libCall case); dgram.address's record result is - // program-dependent too (VOID here is the envGet sentinel — the libCall - // case checks the {address, family, port} shape). - /** node:dgram + node:dns (scr_dgram.c — linked, and - * scr_dgram_install() emitted, only when one of these appears on the - * IR; moduleUsesDgram is the switch — dns.lookup rides the same unit). - * The net listener discipline verbatim: receivers borrowed, CALLBACKS - * MOVE into the handle's registry and release at settlement, `on` vs - * `once` is the trailing bool, registrations are void. bind/connect - * bind NOW and defer 'listening'/'connect' to the next loop turn (the - * net.listen story); their optional-host completions are "" - * (bind → 0.0.0.0) and "127.0.0.1" (connect — udp4's Node default). - * bind/connect/send/close/address THROW Node's state errors ("Socket - * is already bound", "Already connected", "Not running") — may-throw - * seeded. dgram.address returns the AddressInfo RECORD (the frontend - * pins the {address, family, port} shape; the emitter builds it from - * runtime parts). onMessage adapters are emitted per rinfo record - * shape (the child.onExit precedent); onError reuses the child %Error - * adapters. dns.lookup resolves via getaddrinfo AT CALL TIME and - * defers the callback to the next turn (SEMANTICS.md documents the - * blocking divergence); its per-union adapter builds the - * `Error | null` first argument. */ - "dgram.createSocket": { argTypes: [BOOL], result: DGRAMSOCK_T }, - "dgram.bind": { argTypes: [DGRAMSOCK_T, F64, STRING], result: VOID }, - "dgram.bindCb": { - argTypes: [DGRAMSOCK_T, F64, STRING, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "dgram.connect": { argTypes: [DGRAMSOCK_T, F64, STRING], result: VOID }, - "dgram.connectCb": { - argTypes: [DGRAMSOCK_T, F64, STRING, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "dgram.sendStr": { argTypes: [DGRAMSOCK_T, STRING, F64, STRING], result: VOID }, - "dgram.sendBytes": { argTypes: [DGRAMSOCK_T, BYTES_U8, F64, STRING], result: VOID }, - /** Connected AF_UNIX datagram sockets for the hot-shots package shim - * (the unix-dgram addon's surface): fd or result >= 0, -errno on - * failure; send answers 1 when the socket would block. */ - "dgram.unixSocket": { argTypes: [], result: F64 }, - "dgram.unixConnect": { argTypes: [F64, STRING], result: F64 }, - "dgram.unixSend": { argTypes: [F64, BYTES_U8], result: F64 }, - "dgram.unixClose": { argTypes: [F64], result: F64 }, - /** The send argument-validation ladder over dyn arguments (Node's - * signature shuffle: slice bounds, list/type contracts, port/address - * validation, connected-state errors), then the send: lists, connected - * sends and completion callbacks included. Up to six arguments, the - * missing ones undefined. May-throw. */ - "dgram.sendChk": { argTypes: [DGRAMSOCK_T, DYN, DYN, DYN, DYN, DYN, DYN], result: VOID }, - "dgram.address": { argTypes: [DGRAMSOCK_T], result: VOID }, - "dgram.close": { argTypes: [DGRAMSOCK_T], result: VOID }, - "dgram.closeCb": { - argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "dgram.unref": { argTypes: [DGRAMSOCK_T], result: VOID }, - "dgram.ref": { argTypes: [DGRAMSOCK_T], result: VOID }, - /** The socket options (scr_dgram.c): the membership calls take the - * interface and whether it was given. May-throw. */ - "dgram.setBroadcast": { argTypes: [DGRAMSOCK_T, BOOL], result: VOID }, - "dgram.setTTL": { argTypes: [DGRAMSOCK_T, F64], result: F64 }, - "dgram.setMulticastTTL": { argTypes: [DGRAMSOCK_T, F64], result: F64 }, - "dgram.setMulticastLoopback": { argTypes: [DGRAMSOCK_T, BOOL], result: BOOL }, - "dgram.setMulticastInterface": { argTypes: [DGRAMSOCK_T, STRING], result: VOID }, - "dgram.addMembership": { argTypes: [DGRAMSOCK_T, STRING, STRING, BOOL], result: VOID }, - "dgram.dropMembership": { argTypes: [DGRAMSOCK_T, STRING, STRING, BOOL], result: VOID }, - "dgram.getRecvBufferSize": { argTypes: [DGRAMSOCK_T], result: F64 }, - "dgram.getSendBufferSize": { argTypes: [DGRAMSOCK_T], result: F64 }, - "dgram.setRecvBufferSize": { argTypes: [DGRAMSOCK_T, F64], result: VOID }, - "dgram.setSendBufferSize": { argTypes: [DGRAMSOCK_T, F64], result: VOID }, - "dgram.getSendQueueSize": { argTypes: [DGRAMSOCK_T], result: F64 }, - "dgram.getSendQueueCount": { argTypes: [DGRAMSOCK_T], result: F64 }, - "dgram.disconnect": { argTypes: [DGRAMSOCK_T], result: VOID }, - "dgram.onMessage": { argTypes: [DGRAMSOCK_T, null, BOOL], result: VOID }, - "dgram.onError": { argTypes: [DGRAMSOCK_T, null, BOOL], result: VOID }, - "dgram.onListening": { - argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "dgram.onClose": { - argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "dgram.onConnect": { - argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "dns.lookup": { argTypes: [STRING, F64, null], result: VOID }, - /** Node's argument forms with dyn callbacks: lookup(hostname, options, - * callback) and lookupService(address, port, callback). */ - "dns.lookupDyn": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, - "dns.lookupService": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, - /** node:dns/promises: lookup(hostname, options) and lookupService(address, - * port) over the callback forms; the promise fulfills with the dyn - * { address, family } (array for all: true) / { hostname, service }. - * Argument errors throw synchronously (may-throw), unlike the - * util.promisify forms below. */ - "dns.promisesLookup": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "dns.promisesLookupService": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "dns.getServers": { argTypes: [], result: DYN }, - "dns.getDefaultResultOrder": { argTypes: [], result: STRING }, - "dns.setDefaultResultOrder": { argTypes: [DYN], result: VOID }, - // node:test (scr_test.c). Bodies are program-dependent closures (0 or - // 1 testCtx param, void or Promise result — the spoke pinned the - // shape): null slots. sub's result is the settled Promise the - // await consumes. - /** node:test (scr_test.c — linked only when one of these appears on - * the IR; moduleUsesNodeTest is the switch, and the main epilogue asks - * scr_test_exit_code() for the process's exit status). Strings are - * BORROWED, callbacks MOVE. register/suite/hook attach to the runner - * tree (register: name, mode 0|1|2 run/skip/todo, directive message "" - * = none, cb or absent via registerEmpty, flags 1 async | 2 takes-ctx - * | 4 only, "file:line:col"); suite runs its body AT registration - * (Node's collection phase). sub is t.test — runs the subtest INLINE - * on the runner fiber and returns the settled promise the await - * consumes. ctxSkip/ctxTodo mark the running test; ctxDiagnostic - * queues an ℹ line; ctxName reads t.name. Every registration keeps - * the loop-run emitted (usesTimers) so the runner fiber drains. */ - // The trailing three are the { plan, timeout, concurrency } options (plan - // -1 without one; timeout read under flag 8; concurrency read under flag - // 16, -1 for true). An out-of-range timeout or concurrency throws. - "test.register": { - argTypes: [STRING, F64, STRING, null, F64, STRING, F64, F64, F64], - result: VOID, - }, - "test.registerEmpty": { - argTypes: [STRING, F64, STRING, F64, STRING, F64, F64, F64], - result: VOID, - }, - // The suite body takes no parameters or its SuiteContext (flag 2). - "test.suite": { argTypes: [STRING, F64, STRING, null, STRING, F64, F64], result: VOID }, - // The trailing string is the hook call's "file:line:col". - "test.hook": { argTypes: [F64, null, F64, STRING], result: VOID }, - "test.sub": { - argTypes: [TESTCTX_T, STRING, F64, STRING, null, F64, STRING, F64, F64, F64], - result: { kind: "promise", inner: VOID }, - }, - "test.subEmpty": { - argTypes: [TESTCTX_T, STRING, F64, STRING, F64, STRING, F64, F64, F64], - result: VOID, - }, - "test.ctxSkip": { argTypes: [TESTCTX_T, STRING], result: VOID }, - "test.ctxTodo": { argTypes: [TESTCTX_T, STRING], result: VOID }, - "test.ctxDiagnostic": { argTypes: [TESTCTX_T, STRING], result: VOID }, - "test.ctxName": { argTypes: [TESTCTX_T], result: STRING }, - "test.ctxFullName": { argTypes: [TESTCTX_T], result: STRING }, - // t.passed, and t.workerId's Number(NODE_TEST_WORKER_ID) with 0 for - // undefined. - "test.ctxPassed": { argTypes: [TESTCTX_T], result: BOOL }, - "test.workerId": { argTypes: [], result: F64 }, - // getTestContext(): whether a test, suite or hook is running in the - // current async context, and that context. - "test.hasContext": { argTypes: [], result: BOOL }, - "test.context": { argTypes: [], result: TESTCTX_T }, - // t.before/t.after/t.beforeEach/t.afterEach (which: 0 before, 1 after, - // 2 beforeEach, 3 afterEach); a throwing t.before is captured on the test. - "test.ctxHook": { argTypes: [TESTCTX_T, F64, null, F64], result: VOID }, - // t.plan(count), and the count of one t.assert call. - "test.ctxPlan": { argTypes: [TESTCTX_T, F64], result: VOID }, - "test.ctxCount": { argTypes: [TESTCTX_T], result: VOID }, - // The mock trackers: the module's `mock` and t.mock keep each mock's - // restore closure (moved) for restoreAll and reset. - "test.mockTrack": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, - "test.mockRestoreAll": { argTypes: [], result: VOID }, - "test.mockReset": { argTypes: [], result: VOID }, - "test.ctxMockTrack": { - argTypes: [TESTCTX_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "test.ctxMockRestoreAll": { argTypes: [TESTCTX_T], result: VOID }, - "test.ctxMockReset": { argTypes: [TESTCTX_T], result: VOID }, - // mock.timers on the module's `mock` and on t.mock. enable takes one - // code per listed API ('T' setTimeout, 'I' setInterval), the `now` - // value and its flags (1 given, 2 from a Date). enable, tick, runAll and - // setTime throw Node's errors; tick and runAll run timer callbacks. - // t.assert.snapshot and fileSnapshot: the escaped, wrapped serialized - // value, and the stored value it is compared with (snapshot: the test, - // the value, the snapshot file; fileSnapshot: the value, the path). - // Both read or, in update mode, record; missing and unreadable - // snapshots throw Node's ERR_INVALID_STATE. - "test.snapshotValue": { argTypes: [STRING], result: STRING }, - "test.ctxSnapshot": { argTypes: [TESTCTX_T, STRING, STRING], result: STRING }, - "test.fileSnapshot": { argTypes: [STRING, STRING], result: STRING }, - // Node's serializeValue over a dyn array of serializers (undefined for - // the default JSON.stringify(value, null, 2)); a throw becomes Node's - // "The provided serializers did not generate a string." ERR_INVALID_STATE. - "test.snapshotSerialize": { argTypes: [DYN, DYN], result: DYN }, - "test.mockTimersEnable": { argTypes: [STRING, F64, F64], result: VOID }, - "test.mockTimersTick": { argTypes: [F64], result: VOID }, - "test.mockTimersRunAll": { argTypes: [], result: VOID }, - "test.mockTimersReset": { argTypes: [], result: VOID }, - "test.mockTimersSetTime": { argTypes: [F64], result: VOID }, - "test.ctxMockTimersEnable": { argTypes: [TESTCTX_T, STRING, F64, F64], result: VOID }, - "test.ctxMockTimersTick": { argTypes: [TESTCTX_T, F64], result: VOID }, - "test.ctxMockTimersRunAll": { argTypes: [TESTCTX_T], result: VOID }, - "test.ctxMockTimersReset": { argTypes: [TESTCTX_T], result: VOID }, - "test.ctxMockTimersSetTime": { argTypes: [TESTCTX_T, F64], result: VOID }, - // node:http (scr_http.c over scr_net.c). The handler and the data - // listeners are program-dependent closures (null slots, checked in the - // libCall case); reqHeader's result is the interned string|undefined - // union, checked like process.envGet. - /** node:http, the server slice (scr_http.c over scr_net.c — linked - * only when these appear on the IR; moduleUsesHttpServer is the - * switch, and any http.* libCall also counts as net use so scr_net.c - * links and installs). http.createServer's handler MOVES in and takes - * the runtime adapters for its (req, res) / (req) / () shapes; req - * body listeners follow the net.sockOnData story (bytes chunks, once - * flags); reqHeader answers the interned `string | undefined` union - * exactly like process.envGet; the res writers are borrowed-argument - * voids with Node's framing decided in the runtime (Content-Length - * for end-before-head, chunked after an explicit writeHead/write). */ - "http.createServer": { argTypes: [null], result: NETSERVER_T }, - /** http.createServer() / http.Server() with no handler — the - * on("request") route; option helpers set parser behavior on a fresh - * server before the constructor/factory result is returned. */ - "http.createServerEmpty": { argTypes: [], result: NETSERVER_T }, - "http.validateHeaderName": { argTypes: [STRING, STRING], result: VOID }, - "http.validateHeaderValue": { argTypes: [STRING, DYN], result: VOID }, - "http.serverJoinDupHeaders": { argTypes: [NETSERVER_T], result: VOID }, - "http.serverAllowMissingHostHeader": { argTypes: [NETSERVER_T], result: VOID }, - /** The five writable numeric http.Server timeout fields use one - * selector ABI: 0 timeout, 1 keepAliveTimeout, 2 headersTimeout, - * 3 requestTimeout, 4 keepAliveTimeoutBuffer. These calls store/read - * the property values; timer enforcement remains outside this surface. - * The constructor-option setter takes dyn so explicit undefined remains - * distinguishable from a numeric value until its Node validation ladder. */ - "http.serverTimeoutGet": { argTypes: [NETSERVER_T, F64], result: F64 }, - "http.serverSetTimeout": { argTypes: [NETSERVER_T, F64], result: VOID }, - "http.serverSetTimeoutCb": { argTypes: [NETSERVER_T, F64, null], result: VOID }, - "http.serverOnTimeout": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - "http.serverCloseAllConnections": { argTypes: [NETSERVER_T], result: VOID }, - "http.serverCloseIdleConnections": { argTypes: [NETSERVER_T], result: VOID }, - "http.serverTimeoutSet": { argTypes: [NETSERVER_T, F64, F64], result: VOID }, - "http.serverTimeoutOptionSet": { argTypes: [NETSERVER_T, F64, DYN], result: VOID }, - /** server.on("listening", cb) — the deferred listen-callback list. */ - "net.serverOnListening": { - argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - /** The ServerResponse member surface: statusCode/statusMessage reads - * and assignments (Node's writable properties), the header CRUD trio - * (getHeader answers `string | undefined` like reqHeader), and - * end(cb)'s callback slot (resOnEndCallback registers, the end call - * follows — the callback fires deferred, just before Node's 'finish' - * emit; resOnFinish is the 'finish' listener slot). writeHead's - * statusMessage forms compose in interned helpers: resStatusMsgSet - * then the ordinary writeHead entry. */ - "http.resStatusGet": { argTypes: [HTTPRES_T], result: F64 }, - "http.resStatusSet": { argTypes: [HTTPRES_T, F64], result: VOID }, - "http.resStatusMsgGet": { argTypes: [HTTPRES_T], result: VOID }, - "http.resStatusMsgSet": { argTypes: [HTTPRES_T, STRING], result: VOID }, - "http.resRequest": { argTypes: [HTTPRES_T], result: HTTPREQ_T }, - "http.resSocket": { argTypes: [HTTPRES_T], result: VOID }, - "http.resWritableFinished": { argTypes: [HTTPRES_T], result: BOOL }, - "http.resSendDateGet": { argTypes: [HTTPRES_T], result: BOOL }, - "http.resSendDateSet": { argTypes: [HTTPRES_T, BOOL], result: VOID }, - "http.resStrictContentLengthGet": { argTypes: [HTTPRES_T], result: BOOL }, - "http.resStrictContentLengthSet": { argTypes: [HTTPRES_T, BOOL], result: VOID }, - "http.resSetTimeout": { argTypes: [HTTPRES_T, F64], result: VOID }, - "http.resSetTimeoutCb": { - argTypes: [HTTPRES_T, F64, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - // resGetHeader answers the interned `string | undefined` union — the - // reqHeader/envGet sentinel pattern (VOID here, checked specially). - "http.resGetHeader": { argTypes: [HTTPRES_T, STRING], result: VOID }, - /** getHeader as Node answers it: the number, string or string[] that was - * set, or undefined, as a checked-dynamic value. */ - "http.resGetHeaderDyn": { argTypes: [HTTPRES_T, STRING], result: DYN }, - "http.resGetHeaderNames": { argTypes: [HTTPRES_T], result: arrayOf(STRING) }, - "http.resGetRawHeaderNames": { argTypes: [HTTPRES_T], result: arrayOf(STRING) }, - "http.resGetHeaders": { argTypes: [HTTPRES_T], result: DYN }, - "http.resHasHeader": { argTypes: [HTTPRES_T, STRING], result: BOOL }, - "http.resRemoveHeader": { argTypes: [HTTPRES_T, STRING], result: VOID }, - "http.resOnFinish": { - argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "http.resOnEndCallback": { - argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "http.reqUrl": { argTypes: [HTTPREQ_T], result: STRING }, - "http.reqMethod": { argTypes: [HTTPREQ_T], result: STRING }, - "http.reqHeader": { argTypes: [HTTPREQ_T, STRING], result: VOID }, - "http.reqTrailer": { argTypes: [HTTPREQ_T, STRING], result: VOID }, - "http.reqHeaderValues": { argTypes: [HTTPREQ_T, STRING], result: VOID }, - "http.reqTrailerValues": { argTypes: [HTTPREQ_T, STRING], result: VOID }, - "http.reqOnData": { argTypes: [HTTPREQ_T, null, BOOL], result: VOID }, - "http.reqOnEnd": { - argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.resSetHeader": { argTypes: [HTTPRES_T, STRING, STRING], result: VOID }, - /** setHeader / appendHeader with a number, string or array value; the - * stored value keeps its type for getHeader/getHeaders. MAY THROW. */ - "http.resSetHeaderDyn": { argTypes: [HTTPRES_T, STRING, DYN], result: VOID }, - "http.resAppendHeader": { argTypes: [HTTPRES_T, STRING, DYN], result: VOID }, - "http.resWriteHead": { argTypes: [HTTPRES_T, F64], result: VOID }, - "http.resWriteHeadN": { - argTypes: [HTTPRES_T, F64, arrayOf(STRING), arrayOf(STRING)], - result: VOID, - }, - /** HTTP/1.1 informational heads; early hints takes flat string pairs, - * with a lowercase `link` key required to send anything. */ - "http.resWriteContinue": { argTypes: [HTTPRES_T], result: VOID }, - "http.resWriteProcessing": { argTypes: [HTTPRES_T], result: VOID }, - "http.resWriteEarlyHints": { argTypes: [HTTPRES_T, arrayOf(STRING)], result: VOID }, - "http.resWrite": { argTypes: [HTTPRES_T, STRING], result: VOID }, - "http.resWriteBytes": { argTypes: [HTTPRES_T, BYTES_U8], result: VOID }, - "http.resEnd": { argTypes: [HTTPRES_T], result: VOID }, - "http.resEndStr": { argTypes: [HTTPRES_T, STRING], result: VOID }, - "http.resEndBytes": { argTypes: [HTTPRES_T, BYTES_U8], result: VOID }, - /** The checked-dynamic chunk twins (the net.sockWriteDyn story). */ - "http.resWriteDyn": { argTypes: [HTTPRES_T, DYN], result: VOID }, - "http.resEndDyn": { argTypes: [HTTPRES_T, DYN], result: VOID }, - "http.resHeadersSent": { argTypes: [HTTPRES_T], result: BOOL }, - "http.resWritableEnded": { argTypes: [HTTPRES_T], result: BOOL }, - "http.resFlushHeaders": { argTypes: [HTTPRES_T], result: VOID }, - "http.resAddTrailers": { argTypes: [HTTPRES_T, arrayOf(STRING)], result: VOID }, - "http.resCork": { argTypes: [HTTPRES_T], result: VOID }, - "http.resUncork": { argTypes: [HTTPRES_T], result: VOID }, - "http.resWritableCorked": { argTypes: [HTTPRES_T], result: F64 }, - // The member follow-ups: reqStatusCode's `number | undefined` and - // sockRemoteAddress's `string | undefined` results are shape-checked in - // the special cases in validate.ts (like reqHeader/columns). - /** The server-surface member follow-ups: reqStatusCode answers the - * interned `number | undefined` union (negative = the undefined arm — - * a SERVER request, where Node's statusCode is undefined; every client - * response carries a real status); reqSocket is the underlying - * connection (+1, the same handle net.connect would give); - * sockRemoteAddress answers `string | undefined` (NULL after the fd - * closed, Node's destroyed-socket undefined; a dual-stack accept of an - * IPv4 peer reads "::ffff:a.b.c.d" like Node). reqResume/reqPause/reqDestroy and - * the req error/close listener slots complete the IncomingMessage - * surface portless's client responses use; resDestroy/resOnClose and - * resWriteHeadPairs ([k0,v0,k1,v1,...] — the env.pairs helper's flat - * shape) complete ServerResponse. sockSetTimeout arms the idle - * EVFILT_TIMER ('timeout' fires after ms of inactivity, once per idle - * period, never destroying the socket — Node's semantics). */ - "http.reqStatusCode": { argTypes: [HTTPREQ_T], result: VOID }, - // reqStatusMessage's `string | undefined` is program-interned like - // reqHeader's (the VOID here is the envGet sentinel). - "http.reqStatusMessage": { argTypes: [HTTPREQ_T], result: VOID }, - /** req.rawHeaders — [name, value, name, value, ...] in arrival order, - * names in their ORIGINAL case (Node's shape); a fresh string[] per - * read. reqStatusMessage answers the interned `string | undefined` - * union — the reason phrase on client responses ("" when the status - * line carried none), the undefined arm on server requests (the - * statusCode split). sockDestroyed is socket.destroyed — true once the - * fd is gone (destroy() or full close). */ - "http.reqRawHeaders": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, - /** The `{ ...req.headers }` snapshot feed: [lowercased name, value, - * ...] pairs in arrival order — the interned %headers.snapshot helper - * builds the record over it, exactly the process.envPairs pattern. */ - "http.reqHeaderPairs": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, - /** Every value of one header in arrival order (empty when absent): the - * set-cookie array of a `{ ...req.headers }` snapshot. */ - "http.reqHeaderList": { argTypes: [HTTPREQ_T, STRING], result: arrayOf(STRING) }, - "http.reqRawTrailers": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, - "http.reqTrailerPairs": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, - "net.sockDestroyed": { argTypes: [NETSOCKET_T], result: BOOL }, - /** socket.writable — the write half is open: no end() yet, no FIN sent, - * fd alive (connecting sockets answer true; writes queue). Node's - * stream flag. Borrows; never throws. */ - "net.sockWritable": { argTypes: [NETSOCKET_T], result: BOOL }, - // The 'upgrade' registrations: the callback shapes are program-typed - // ((req, socket, head) and shorter prefixes — the libCall case checks). - /** The 'upgrade' events, both sides (SEMANTICS.md — the WebSocket - * proxying surface): serverOnUpgrade registers (req, socket, head) - * listeners fired INSTEAD of 'request' for Connection: upgrade - * requests (the parser steps aside; the socket is raw; `head` carries - * bytes past the request head; no listener = the socket destroys, - * Node's default). clientOnUpgrade is the client twin: a 101 response - * fires (res, socket, head) INSTEAD of 'response'. */ - "http.serverOnUpgrade": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - /** server.on("connect", ...) — HTTP CONNECT tunneling, the 'upgrade' - * machinery's twin: (req, socket, head) fired INSTEAD of 'request' for - * CONNECT-method requests (no listener = the socket destroys, Node's - * default). The h2 compat server's 'connect' (portless's RFC 8441 - * handler) only ever sees the HTTP/1.1 arm under the allowHTTP1 - * lowering, so a listener whose second parameter is a UNION with a - * netSocket arm takes the socket wrapped at that arm (an emitted - * per-shape adapter — the tags are program data). */ - "http.serverOnConnect": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - "http.clientOnUpgrade": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, - "http.reqSocket": { argTypes: [HTTPREQ_T], result: NETSOCKET_T }, - // reqH2Stream's program-interned Http2Stream | undefined result is - // checked in the special libCall cases in validate.ts. - "http.reqH2Stream": { argTypes: [HTTPREQ_T], result: VOID }, - "http.reqH2StreamOrThrow": { argTypes: [HTTPREQ_T, STRING], result: HTTP2STREAM_T }, - /** req.pipe(dest) — the IncomingMessage body streaming into a - * ServerResponse (the proxy's response leg), a ClientRequest (the - * request-body forward), or a raw socket (the upgrade-rejection leg); - * chunk-for-chunk, natural end ends the destination (Node's pipe - * default; no backpressure — divergence 54's stream model). */ - "http.reqPipeRes": { argTypes: [HTTPREQ_T, HTTPRES_T], result: VOID }, - "http.reqPipeClient": { argTypes: [HTTPREQ_T, HTTPCLIENTREQ_T], result: VOID }, - "http.reqPipeSock": { argTypes: [HTTPREQ_T, NETSOCKET_T], result: VOID }, - "http.reqResume": { argTypes: [HTTPREQ_T], result: VOID }, - "http.reqPause": { argTypes: [HTTPREQ_T], result: VOID }, - "http.reqDestroy": { argTypes: [HTTPREQ_T], result: VOID }, - "http.reqOnError": { argTypes: [HTTPREQ_T, null, BOOL], result: VOID }, - "http.reqOnClose": { - argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.reqOnAborted": { - argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.reqHttpVersion": { argTypes: [HTTPREQ_T], result: STRING }, - "http.reqHttpVersionMajor": { argTypes: [HTTPREQ_T], result: F64 }, - "http.reqHttpVersionMinor": { argTypes: [HTTPREQ_T], result: F64 }, - "http.reqAborted": { argTypes: [HTTPREQ_T], result: BOOL }, - "http.reqComplete": { argTypes: [HTTPREQ_T], result: BOOL }, - "http.resDestroy": { argTypes: [HTTPRES_T], result: VOID }, - "http.resOnClose": { - argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.resWriteHeadPairs": { argTypes: [HTTPRES_T, F64, arrayOf(STRING)], result: VOID }, - /** writeHead(status, headers) with a checked-dynamic headers value — - * the runtime OBJ walk (string/number values; other values throw - * Node's invalid-argument TypeError; may throw). */ - "http.resWriteHeadDyn": { argTypes: [HTTPRES_T, F64, DYN], result: VOID }, - "net.sockSetTimeout": { argTypes: [NETSOCKET_T, F64], result: VOID }, - /** setEncoding('utf8') — 'data' delivers strings inside the chunk-encoding window; other real encodings throw the "not supported yet" Error, unknown names throw ERR_UNKNOWN_ENCODING (may throw). */ - "net.sockSetEncoding": { argTypes: [NETSOCKET_T, STRING], result: VOID }, - "http.reqSetEncoding": { argTypes: [HTTPREQ_T, STRING], result: VOID }, - "net.sockOnTimeout": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "net.sockRemoteAddress": { argTypes: [NETSOCKET_T], result: VOID }, - /** socket.encrypted — `boolean | undefined`: the true arm iff the - * socket carries a TLS transport (Node types `encrypted: true` on - * TLSSocket; plain sockets answer undefined — the proxy.ts isEncrypted - * idiom reads it through a cast). */ - "net.sockEncrypted": { argTypes: [NETSOCKET_T], result: VOID }, - // node:http, the client slice. The response callback shapes are - // program-dependent (checked by validate.ts); header pairs arrive flat. - /** node:http, the CLIENT slice (http.request/http.get over the net - * client machinery): request/requestCb take (host, port, path, method, - * timeoutMs, headerPairs, autoEnd[, responseCb]) — headerPairs is the - * flat [k0,v0,...] array (empty for none), autoEnd true is http.get's - * eager end(). The handle owns one dialed connection (NO pooling — the - * wire still carries Node's exact head: user headers, then Host, - * Connection: keep-alive, and the framing header; the socket closes - * when the response completes). The response delivered to responseCb / - * 'response' listeners IS an httpReq (IncomingMessage), status and - * headers parsed from the wire, body via reqOnData/reqOnEnd. Errors are - * Node-shaped ('connect ECONNREFUSED ip:port', 'socket hang up') and - * fire 'error' then 'close'; an unhandled 'error' exits 1 like every - * net handle. clientWrite before end commits to chunked framing unless - * the caller set content-length; clientEnd(data) before any write sends - * Content-Length exactly like Node. */ - "http.request": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL], - result: HTTPCLIENTREQ_T, - }, - "http.requestCb": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, null], - result: HTTPCLIENTREQ_T, - }, - /** new http.Agent(opts) / new https.Agent(opts): (secure, keepAlive, - * keepAliveMsecs, maxSockets, maxFreeSockets, timeoutMs, port) — the - * numeric options arrive < 0 for "unset" (Infinity/256/none; port - * seeds the settable defaultPort, Node's option merge). Returns the - * Agent as a checked-dynamic HANDLE (getName/destroy and the - * sockets/requests/freeSockets counters dispatch through the dyn - * handle ops). keepAlive: true THROWS the named construction fence — - * socket POOLING is not modeled (one dial per request); maxSockets - * accounting is real: over-limit requests defer their dial and queue. - * MAY THROW. */ - "http.agentNew": { argTypes: [BOOL, BOOL, F64, F64, F64, F64, F64, DYN], result: DYN }, - /** http.globalAgent / https.globalAgent (true): Node's keepAlive global - * agent, the one requests without an agent option use. */ - "http.globalAgent": { argTypes: [BOOL], result: DYN }, - /** http.request's `signal` option over the fresh request: an aborted - * signal destroys it with Node's AbortError now, a later abort event - * does the same. A falsy value is ignored; a non-signal throws - * ERR_INVALID_ARG_TYPE. */ - "http.clientSignal": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, - /** options.signal checked before the request is built (ERR_INVALID_ARG_TYPE). */ - "http.clientSignalCheck": { argTypes: [DYN], result: VOID }, - /** https.request's servername option, stored for the construction call - * that follows: the TLS name sent and verified, and part of the pooled - * socket key. The "https." prefix keeps the TLS unit linked. */ - "https.nextServername": { argTypes: [STRING], result: VOID }, - /** The maxHeaderSize option of createServer / http.request: Node's - * validateInteger (ERR_OUT_OF_RANGE) then the header size bound. - * MAY THROW. */ - "http.serverMaxHeaderSize": { argTypes: [NETSERVER_T, F64], result: VOID }, - "http.clientMaxHeaderSize": { argTypes: [HTTPCLIENTREQ_T, F64], result: VOID }, - /** req.destroy(err): err becomes the request's 'error'. */ - "http.clientDestroyError": { - argTypes: [HTTPCLIENTREQ_T, { kind: "object", className: "%Error" }], - result: VOID, - }, - /** The agent-threaded request rows: the http.request/https.request - * shape with a trailing `agent` dyn argument (an Agent handle, false — - * the one-shot Connection: close dial — or null/undefined for the - * default path). port < 0 means "no port option": the agent's settable - * defaultPort, then the scheme's. MAY THROW (a non-Agent value is - * Node's ERR_INVALID_ARG_TYPE). */ - "http.requestAgent": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, DYN], - result: HTTPCLIENTREQ_T, - }, - "http.requestAgentCb": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, DYN, null], - result: HTTPCLIENTREQ_T, - }, - /** request/get with a URL-STRING first argument: the runtime parses it - * (WHATWG) and dials — throws catchably on an unparsable input or a - * non-http scheme. */ - "http.requestUrl": { argTypes: [STRING, STRING, BOOL], result: HTTPCLIENTREQ_T }, - "http.requestUrlCb": { argTypes: [STRING, STRING, BOOL, null], result: HTTPCLIENTREQ_T }, - /** request/get with a runtime options record: a RequestOptions - * variable, or a literal that spreads one or sets socketPath. Args - * (options, autoEnd[, cb]). The runtime reads the record in the - * ClientRequest constructor's order (host/port or socketPath, path, - * method, headers, timeout, agent, signal, maxHeaderSize) with Node's - * errors, and documented options it does not implement throw the - * runtime fence. The "https." pair adds the TLS options. MAY THROW. */ - "http.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, - "http.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, - "https.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, - "https.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, - /** The paused-mode demux surface (portless's first-byte TLS peek: - * once('readable') + read(1) + unshift + emit('connection')). - * sockOnReadable registers a zero-param listener (a consumer: arrived - * bytes buffer instead of flowing, and EOF announces too); sockRead - * answers `Buffer | null` (exactly n buffered bytes, or null — Node's - * less-than-n answer; n <= 0 drains everything); sockUnshift returns - * bytes to the front of the stream; serverEmitConnection routes a - * socket into another server's protocol layer (a TLS target's - * 'connection' waits for its handshake). */ - "net.sockOnReadable": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, - // sockRead's result is the interned `Buffer | null` union — checked - // specially in validate.ts (the reqHeader/envGet pattern; VOID here is a - // placeholder the special case overrides). - "net.sockRead": { argTypes: [NETSOCKET_T, F64], result: VOID }, - "net.sockUnshift": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, - /** Socket flow control and the compat surface: pause/resume (reads - * gate off/on — kernel backpressure holds paused bytes; resume flows - * and discards sans listeners) and setNoDelay answer the SOCKET (+1, - * Node's chaining); destroySoon ends now and destroys once the FIN - * flushed; bytesWritten counts accepted bytes; readable is true until - * the read half ends. */ - "net.sockPause": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, - "net.sockResume": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, - "net.sockSetNoDelay": { argTypes: [NETSOCKET_T, BOOL], result: NETSOCKET_T }, - "net.sockDestroySoon": { argTypes: [NETSOCKET_T], result: VOID }, - /** RST close, answers the socket; throws ERR_INVALID_HANDLE_TYPE on TLS. */ - "net.sockResetAndDestroy": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, - /** socket.address(): { address, family, port } or {} as a dyn. */ - "net.sockAddress": { argTypes: [NETSOCKET_T], result: DYN }, - "net.sockBytesWritten": { argTypes: [NETSOCKET_T], result: F64 }, - /** remotePort (0), remoteFamily (1), localAddress (2), localPort (3), - * localFamily (4): dyn values, undefined when unknown. */ - "net.sockAddrField": { argTypes: [NETSOCKET_T, F64], result: DYN }, - "net.sockBytesRead": { argTypes: [NETSOCKET_T], result: F64 }, - "net.sockConnecting": { argTypes: [NETSOCKET_T], result: BOOL }, - "net.sockPending": { argTypes: [NETSOCKET_T], result: BOOL }, - "net.sockReadyState": { argTypes: [NETSOCKET_T], result: STRING }, - "net.sockSetKeepAlive": { argTypes: [NETSOCKET_T, BOOL, F64], result: NETSOCKET_T }, - "net.sockOnReady": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - /** Listeners with a parameter, as dyn functions: close(hadError), - * write's cb(err) and end's cb. */ - "net.sockOnCloseDyn": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, - /** socket.on/once('lookup', listener): the listener (a dyn function) - * gets (err, address, family, host) once per resolved address, or once - * with the lookup error, before the connect. */ - "net.sockOnLookup": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, - "net.sockOnWriteFlush": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "net.sockOnWriteFlushDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, - "net.sockOnFinishDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, - "net.sockSetTimeoutCb": { - argTypes: [NETSOCKET_T, F64, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "net.isIP": { argTypes: [DYN], result: F64 }, - "net.isIPv4": { argTypes: [DYN], result: BOOL }, - "net.isIPv6": { argTypes: [DYN], result: BOOL }, - "net.serverMaxConnectionsGet": { argTypes: [NETSERVER_T], result: DYN }, - "net.serverMaxConnectionsSet": { argTypes: [NETSERVER_T, DYN], result: VOID }, - "net.serverDropMaxConnectionGet": { argTypes: [NETSERVER_T], result: DYN }, - "net.serverDropMaxConnectionSet": { argTypes: [NETSERVER_T, DYN], result: VOID }, - /** net.SocketAddress and net.BlockList (scr_net.c): the constructors - * take the argument array; the handles are dyn values. */ - "net.socketAddressNew": { argTypes: [DYN], result: DYN }, - "net.socketAddressParse": { argTypes: [DYN], result: DYN }, - "net.isSocketAddress": { argTypes: [DYN], result: BOOL }, - "net.blockListNew": { argTypes: [DYN], result: DYN }, - "net.isBlockList": { argTypes: [DYN], result: BOOL }, - "net.blockListPrivateRanges": { argTypes: [], result: DYN }, - "net.serverOnDrop": { argTypes: [NETSERVER_T, DYN, BOOL], result: VOID }, - "net.serverGetConnections": { argTypes: [NETSERVER_T, DYN], result: VOID }, - "net.sockReadable": { argTypes: [NETSOCKET_T], result: BOOL }, - /** socket.on('finish', cb) / end(cb): fires once when the FIN goes out - * (sweep-deferred, never the registering stack). */ - "net.sockOnFinish": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "net.serverEmitConnection": { argTypes: [NETSERVER_T, NETSOCKET_T], result: VOID }, - // tls/https: cert/key/ca PEM arguments are strings OR Buffers (null = - // both accepted; the emitter passes data+len either way). - /** node:tls + node:https (scr_tls.c over scr_net.c/scr_http.c, with - * the vendored mbedTLS archive — linked only when one of these appears - * on the IR; moduleUsesTls is the switch, and every tls/https libCall - * also counts as net AND http use so both units link and install). - * tls.createServer takes (cert, key[, handler]) — cert/key are PEM - * strings or Buffers (the emitter passes data+len for either); the - * handler is Node's 'secureConnection' (fires post-handshake with a - * socket that behaves exactly like a net socket, the same adapters as - * net.createServer). https.createServer is (cert, key, handler) with - * the http request-handler adapters. https.request/requestCb extend - * the http client row with (…, rejectUnauthorized: bool, ca: PEM - * string/Buffer — "" for none) and default port 443; everything else - * (write/end/destroy/events, the response surface) IS the http client - * surface — the handles are the same kinds. */ - "tls.createServer": { argTypes: [null, null], result: NETSERVER_T }, - "tls.createServerCb": { argTypes: [null, null, null], result: NETSERVER_T }, - /** The SNICallback forms: (cert, key, SNICallback[, handler]). The - * callback is http2.createSecureServerSni's shape, a function or a - * function | undefined union. */ - "tls.createServerSni": { argTypes: [null, null, null], result: NETSERVER_T }, - "tls.createServerSniCb": { argTypes: [null, null, null, null], result: NETSERVER_T }, - // The runtime options records (divergence 66's stance): a dyn options - // value whose members read at runtime; pemDyn extracts a runtime-valued - // cert/key member (arg 1 is the precomposed fence label). - /** RUNTIME options records (the divergence-66 stance): the *Dyn - * creators take a checked-dynamic (dyn) options value whose members - * read at runtime — cert/key extract like the literal path, members - * whose literal forms fence THROW the catchable fence at runtime, and - * undocumented keys drop like Node drops them. tls.pemDyn is the - * literal walk's runtime-valued cert/key extraction: (value, whatLit) - * → PEM bytes (strings/Buffers/one-element arrays of those) or the - * thrown fence. All of them may throw. */ - "tls.pemDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, - "tls.createServerDyn": { argTypes: [DYN], result: NETSERVER_T }, - "tls.createServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, - "https.createServerDyn": { argTypes: [DYN], result: NETSERVER_T }, - "https.createServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, - /** createSecureServer(options, handler) — the eager COMPAT handler as - * the first 'request' listener (Node's exact route), on both literal - * flavors: Req is the dual ALPN allowHTTP1 server, H2Req the ALPN=h2-only - * server (both use compat handles over h2 streams). Args are - * (cert, key, cb, enableConnectProtocol). */ - "http2.createSecureServerReq": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, - "http2.createSecureServerH2Req": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, - /** createSecureServer with a RUNTIME options record (the divergence-66 - * stance): allowHTTP1/cert/key read at runtime — allowHTTP1 picks the - * flavor, the TLS server walk fences its out-of-bounds members with - * the catchable runtime fence, and h2 session-tuning keys drop exactly - * like the literal walk ignores them. May throw. The Cb form carries - * the eager compat handler. */ - "http2.createSecureServerDyn": { argTypes: [DYN], result: NETSERVER_T }, - "http2.createSecureServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, - // tls.connect(port, host, opts[, cb]) — port -1 / host "" read the - // options record; the callback fires post-handshake (secureConnect). - /** tls.connect — the TLS client socket: (port, host, opts[, cb]) where - * port -1 reads options.port, host "" reads options.host, and opts is - * the runtime (dyn) options record (rejectUnauthorized/ca/servername - * implemented; other documented members throw the runtime fence). The - * callback fires post-handshake — Node's secureConnect timing. */ - "tls.connect": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, - "tls.connectCb": { argTypes: [F64, STRING, DYN, null], result: NETSOCKET_T }, - // The TLSSocket member surface (authError's `string | null` union is - // shape-checked in the special cases in validate.ts — the reqHeader pattern). - /** The TLSSocket member surface on the socket kind: authorized (bool — - * Node's verify verdict), authorizationError (the verify-failure CODE - * STRING or null), the 'secureConnect' registration (a TLS socket's - * conn list fires at establishment; plain sockets never fire it), and - * the 'session' registration (fires once with the serialized session — - * a Buffer; the received-ticket event). */ - "tls.sockAuthorized": { argTypes: [NETSOCKET_T], result: BOOL }, - /** getProtocol() (string | null), getCipher() (CipherNameAndProtocol, - * undefined before the handshake, null after close) and alpnProtocol - * (string | false | null), as dyn values. */ - "tls.sockGetProtocol": { argTypes: [NETSOCKET_T], result: DYN }, - "tls.sockGetCipher": { argTypes: [NETSOCKET_T], result: DYN }, - "tls.sockAlpnProtocol": { argTypes: [NETSOCKET_T], result: DYN }, - /** getPeerCertificate(detailed) and getCertificate() (the legacy object, - * {} or null; scr_tls_x509.c), and the DER behind getPeerX509Certificate() - * and getX509Certificate() (a Buffer, empty without a certificate). */ - "tls.sockGetPeerCertificate": { argTypes: [NETSOCKET_T, DYN], result: DYN }, - "tls.sockGetCertificate": { argTypes: [NETSOCKET_T], result: DYN }, - "tls.sockPeerCertDer": { argTypes: [NETSOCKET_T], result: BYTES_U8 }, - "tls.sockOwnCertDer": { argTypes: [NETSOCKET_T], result: BYTES_U8 }, - "tls.sockAuthError": { argTypes: [NETSOCKET_T], result: VOID }, - "tls.sockOnSecureConnect": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "tls.sockOnSession": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, - /** The 'secure' event: the handshake completed (after secureConnect on - * a client). Plain sockets never fire it. */ - "tls.sockOnSecure": { - argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - /** new tls.TLSSocket(socket, options) in server mode: a new TLS socket - * takes over the socket's connection (wrapSocketCtx passes a literal's - * secureContext member apart from the runtime options record). The - * record reads at runtime: isServer, cert, key, ALPNProtocols, - * minVersion and maxVersion are implemented, the client mode and other - * documented members throw the runtime fence. MAY THROW. */ - "tls.wrapSocket": { argTypes: [NETSOCKET_T, DYN], result: NETSOCKET_T }, - "tls.wrapSocketCtx": { argTypes: [NETSOCKET_T, DYN, SECURECTX_T], result: NETSOCKET_T }, - // createSecureContext({ cert, key }) mints the opaque SNI-answer handle. - /** tls.createSecureContext({ cert, key }) — parses the PEM pair into an - * opaque SecureContext handle (secureCtx kind) for SNI callbacks to - * answer with; cert/key are PEM strings or Buffers like createServer's. */ - "tls.createSecureContext": { argTypes: [null, null], result: SECURECTX_T }, - /** createSecureContext over a RUNTIME options record (the checked- - * dynamic lane): Node's typed option validations first (the ciphers/ - * passphrase/engine/version/timeout/ticketKeys ladders), then the pem - * walk — a validated { cert, key } bag builds the real context. - * May-throw. */ - "tls.createSecureContextDyn": { argTypes: [DYN], result: SECURECTX_T }, - /** server.addContext(servername, options | SecureContext), - * server.setSecureContext(options) and the 'tlsClientError' listener of - * a tls or https server (scr_tls.c). */ - "tls.serverAddContext": { argTypes: [NETSERVER_T, DYN, DYN], result: VOID }, - "tls.serverAddContextCtx": { argTypes: [NETSERVER_T, DYN, SECURECTX_T], result: VOID }, - "tls.serverSetSecureContext": { argTypes: [NETSERVER_T, DYN], result: VOID }, - "tls.serverOnClientError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - /** tls.getCACertificates(type): validateString + the documented name - * set, then the trailing compiler-rendered fence — ALWAYS THROWS (the - * error.nodeThrow polymorphic-result carve-out). May-throw seed. */ - "tls.caCertsChk": { argTypes: [DYN, STRING], result: VOID }, - // The CA-store introspection unit (scr_tls_ca.c): per-type cached PEM - // string arrays and the default-set replacement. - /** The CA-store introspection surface (scr_tls_ca.c — its own unit and - * link gate, "tlsca." NOT "tls.", so a getCACertificates-only binary - * never pulls mbedTLS): tlsca.get is tls.getCACertificates(type) — the - * cached per-type string[] of PEM blocks (identity-stable across calls, - * Node's caching; an unknown type throws Node's ERR_INVALID_ARG_VALUE - * TypeError); tlsca.root is the tls.rootCertificates value read; - * tlsca.set is tls.setDefaultCACertificates(certs) — replaces the - * default set (deduped) and the anchors the TLS client verifies - * against, throwing Node's ERR_CRYPTO_OPERATION_FAILED when no entry - * carries a certificate block. */ - "tlsca.get": { argTypes: [STRING], result: arrayOf(STRING) }, - "tlsca.root": { argTypes: [], result: arrayOf(STRING) }, - "tlsca.set": { argTypes: [arrayOf(STRING)], result: VOID }, - "https.createServer": { argTypes: [null, null, null], result: NETSERVER_T }, - // http2's allowHTTP1 compatibility server (divergence 57): cert/key - // like tls.createServer; the 'request' handler arrives separately via - // http.serverOnRequest (shape checked in the libCall case, like - // http.createServer's). sessionError callback shape is checked by validate.ts. - /** node:http2 allowHTTP1: one TLS server advertises h2 + http/1.1 and - * dispatches to the matching parser after ALPN. Request listeners mirror - * into both compatibility lists; the remaining session/req.stream rows - * below retain their explicitly limited behavior. */ - "http2.createSecureServer": { argTypes: [null, null, BOOL], result: NETSERVER_T }, - // The SNI-callback form: arg 2 is the JS SNICallback closure — a - // `(servername, cb) => void` func, or its `| undefined` union from the - // conditional-spread spelling (the libCall case checks the shape). - /** createSecureServer with an SNI callback: args are (cert, key, sniCb) - * where sniCb is the JS SNICallback — a `(servername, cb) => void` - * closure, or the `SNICallback | undefined` union from the conditional- - * spread spelling (`...(x ? { SNICallback: x } : {})`; the emitter - * unwraps the union — the undefined arm means "no callback", exactly - * the no-SNI server). The runtime parses each connection's ClientHello - * for the server_name extension BEFORE the TLS handshake begins, calls - * the callback with (servername, answer-closure), and resumes the - * handshake when the answer arrives — cb(err) tears the socket down - * without an error event (Node's 'tlsClientError' default), cb(null, ctx) serves - * ctx's cert/key, cb(null, undefined) serves the default pair. */ - "http2.createSecureServerSni": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, - // The ALPN=h2 server (createSecureServer without allowHTTP1): the real - // h2 session machinery behind the TLS handshake. - /** The REAL h2-over-TLS server (createSecureServer WITHOUT allowHTTP1): - * scr_http2_create_secure_server — the h2c session machinery behind an - * mbedTLS handshake whose ALPN advertises h2 alone (an http/1.1-only - * client fails the handshake with no_application_protocol, Node's - * h2-only split). args are (cert, key) — PEM strings or Buffers. */ - "http2.createSecureServerH2": { argTypes: [null, null, BOOL], result: NETSERVER_T }, - "http.serverOnRequest": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - /** server.on('checkContinue' | 'checkExpectation', (req, res)): the - * 'request' listener shape; the last argument picks checkExpectation. */ - "http.serverOnCheck": { argTypes: [NETSERVER_T, null, BOOL, BOOL], result: VOID }, - "http2.serverOnSessionError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - /** The guarded absent-session compatibility call remains a no-op. */ - "http2.streamNoop": { argTypes: [], result: VOID }, - /** The UNGUARDED h2-only stream call (`req.stream.on(...)`): stream is - * undefined on every connection the allowHTTP1 lowering accepts — and - * on every HTTP/1.1 connection of Node's own allowHTTP1 server — so the - * call IS Node's member read on undefined: throws the exact catchable - * TypeError ("Cannot read properties of undefined (reading 'on')"). - * One arg: the read member's name (a string literal). Never returns. */ - "http2.streamUndefCall": { argTypes: [STRING], result: VOID }, - // The REAL h2c surface (scr_http2.c). Callback slots are null (the - // emitter picks the thunk); pairs slots are string arrays; the - // endStream tri-state and event flags ride as f64. - /** node:http2, the REAL h2c surface (scr_http2.c — frame codec + HPACK - * over the net loop; the design note atop that file has the story). - * Sessions and streams are first-class handle kinds; 'stream'/'response' - * payloads cross as flat [name, value, ...] pairs arrays and an EMITTED - * adapter closure builds the program-side headers record (the response - * :status rides separately as a number). */ - "http2.createServer": { argTypes: [], result: NETSERVER_T }, - "http2.createServerReq": { argTypes: [null], result: NETSERVER_T }, - "http2.serverOnStream": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - "http2.serverOnSession": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, - // connect(authority, reject, ca[, cb]): the TLS client knobs an https - // authority reads (inert on http ones, exactly Node); ca is a PEM - // string/Buffer ("" = the system anchors). - /** connect(authority[, listener]) — h2c prior knowledge; the listener - * closure (if any) is the 'connect' once-listener. */ - "http2.connect": { argTypes: [STRING, BOOL, null], result: HTTP2SESSION_T }, - "http2.connectCb": { argTypes: [STRING, BOOL, null, null], result: HTTP2SESSION_T }, - /** session.request(pairs?, endStream) — endStream is a tri-state f64: - * -1 the method's payload-meaningless default, 0/1 explicit. */ - "http2.sessionRequest": { - argTypes: [HTTP2SESSION_T, arrayOf(STRING), F64], - result: HTTP2STREAM_T, - }, - "http2.sessionClose": { argTypes: [HTTP2SESSION_T], result: VOID }, - "http2.sessionCloseCb": { argTypes: [HTTP2SESSION_T, null], result: VOID }, - "http2.sessionDestroy": { argTypes: [HTTP2SESSION_T], result: VOID }, - "http2.sessionOnClose": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, - "http2.sessionOnError": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, - "http2.sessionOnConnect": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, - "http2.sessionOnStream": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, - "http2.sessionOnGoaway": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, - "http2.sessionClosed": { argTypes: [HTTP2SESSION_T], result: BOOL }, - "http2.sessionDestroyed": { argTypes: [HTTP2SESSION_T], result: BOOL }, - "http2.sessionEncrypted": { argTypes: [HTTP2SESSION_T], result: BOOL }, - "http2.sessionType": { argTypes: [HTTP2SESSION_T], result: F64 }, - "http2.sessionAlpn": { argTypes: [HTTP2SESSION_T], result: STRING }, - "http2.sessionSocket": { argTypes: [HTTP2SESSION_T], result: NETSOCKET_T }, - /** stream.respond(pairs?, endStream) — the server answer. */ - "http2.streamRespond": { argTypes: [HTTP2STREAM_T, arrayOf(STRING), BOOL], result: VOID }, - "http2.streamWrite": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, - "http2.streamWriteBytes": { argTypes: [HTTP2STREAM_T, BYTES_U8], result: VOID }, - "http2.streamEnd": { argTypes: [HTTP2STREAM_T], result: VOID }, - "http2.streamEndStr": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, - "http2.streamEndBytes": { argTypes: [HTTP2STREAM_T, BYTES_U8], result: VOID }, - "http2.streamClose": { argTypes: [HTTP2STREAM_T, F64], result: VOID }, - "http2.streamCloseCb": { argTypes: [HTTP2STREAM_T, F64, null], result: VOID }, - "http2.streamDestroy": { argTypes: [HTTP2STREAM_T], result: VOID }, - "http2.sessionSettings0": { argTypes: [HTTP2SESSION_T], result: VOID }, - "http2.sessionSettings": { argTypes: [HTTP2SESSION_T, DYN], result: VOID }, - "http2.sessionSettingsDynCb": { argTypes: [HTTP2SESSION_T, DYN, DYN], result: VOID }, - "http2.sessionSettingsCb0": { - argTypes: [HTTP2SESSION_T, DYN, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "http2.sessionOnSettingsDyn": { argTypes: [HTTP2SESSION_T, DYN, BOOL, BOOL], result: VOID }, - "http2.sessionOnSettings0": { - argTypes: [HTTP2SESSION_T, { kind: "func", params: [], ret: VOID }, BOOL, BOOL], - result: VOID, - }, - "http2.sessionSettingsGet": { argTypes: [HTTP2SESSION_T, BOOL], result: DYN }, - "http2.sessionPendingSettingsAck": { argTypes: [HTTP2SESSION_T], result: BOOL }, - "http2.getDefaultSettings": { argTypes: [], result: DYN }, - "http2.streamSetEncoding": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, - "http2.streamSetEncodingRet": { argTypes: [HTTP2STREAM_T, STRING], result: HTTP2STREAM_T }, - "http2.streamResume": { argTypes: [HTTP2STREAM_T], result: VOID }, - "http2.streamPause": { argTypes: [HTTP2STREAM_T], result: VOID }, - "http2.streamOnData": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamOnEnd": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamOnClose": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamOnAborted": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamOnError": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamOnResponse": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, - "http2.streamId": { argTypes: [HTTP2STREAM_T], result: F64 }, - "http2.streamRstCode": { argTypes: [HTTP2STREAM_T], result: F64 }, - "http2.streamDestroyed": { argTypes: [HTTP2STREAM_T], result: BOOL }, - "http2.streamClosed": { argTypes: [HTTP2STREAM_T], result: BOOL }, - "http2.streamAborted": { argTypes: [HTTP2STREAM_T], result: BOOL }, - "http2.streamPending": { argTypes: [HTTP2STREAM_T], result: BOOL }, - "http2.streamSession": { argTypes: [HTTP2STREAM_T], result: HTTP2SESSION_T }, - // The createConnection forms: arg 0 is the dialer closure (() => - // Socket — the libCall case checks), then path/method/timeout/headers/ - // autoEnd like http.request. - /** The createConnection form (the proxy's own dialer): args are - * (connCb, path, method, timeout, headers, autoEnd[, cb]) — connCb is - * a `() => net.Socket` closure the runtime invokes ONCE, synchronously - * (Node's onSocket timing); everything else matches http.request. The - * Host header defaults to "localhost" — a headers.host entry wins - * verbatim, the proxy shape. */ - "http.requestConn": { - argTypes: [null, STRING, STRING, F64, arrayOf(STRING), BOOL], - result: HTTPCLIENTREQ_T, - }, - "http.requestConnCb": { - argTypes: [null, STRING, STRING, F64, arrayOf(STRING), BOOL, null], - result: HTTPCLIENTREQ_T, - }, - "https.request": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null], - result: HTTPCLIENTREQ_T, - }, - "https.requestCb": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, null], - result: HTTPCLIENTREQ_T, - }, - /** The URL-string first argument, the http.requestUrl row over TLS: - * no options means Node's defaults (verification on, default trust - * anchors), and a non-https scheme is ERR_INVALID_PROTOCOL. */ - "https.requestUrl": { argTypes: [STRING, STRING, BOOL], result: HTTPCLIENTREQ_T }, - "https.requestUrlCb": { argTypes: [STRING, STRING, BOOL, null], result: HTTPCLIENTREQ_T }, - "https.requestAgent": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, DYN], - result: HTTPCLIENTREQ_T, - }, - "https.requestAgentCb": { - argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, DYN, null], - result: HTTPCLIENTREQ_T, - }, - // The requestFn binding's runtime-secure rows: https.request's shape - // with the leading `secure` bool. - /** A call through a `const requestFn = tls ? https.request : - * http.request` binding — the module-function-as-value ternary between - * the two known clients: the https.request row with a leading `secure` - * bool that picks the dial at RUNTIME (true = the TLS client, exactly - * https.request; false = the plain client, exactly http.request — - * rejectUnauthorized/ca ignored there like Node ignores TLS options on - * http.request). The "https." prefix keeps the TLS unit linked. */ - "https.requestFn": { - argTypes: [BOOL, STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null], - result: HTTPCLIENTREQ_T, - }, - "https.requestFnCb": { - argTypes: [BOOL, STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, null], - result: HTTPCLIENTREQ_T, - }, - "http.clientWrite": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, - "http.clientSetHeader": { argTypes: [HTTPCLIENTREQ_T, STRING, STRING], result: VOID }, - "http.clientGetHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, - "http.clientHasHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: BOOL }, - "http.clientRemoveHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, - "http.clientGetHeaderNames": { argTypes: [HTTPCLIENTREQ_T], result: arrayOf(STRING) }, - "http.clientGetRawHeaderNames": { argTypes: [HTTPCLIENTREQ_T], result: arrayOf(STRING) }, - "http.clientGetHeaders": { argTypes: [HTTPCLIENTREQ_T], result: DYN }, - "http.clientWriteBytes": { argTypes: [HTTPCLIENTREQ_T, BYTES_U8], result: VOID }, - "http.clientEnd": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientEndStr": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, - "http.clientEndBytes": { argTypes: [HTTPCLIENTREQ_T, BYTES_U8], result: VOID }, - /** The checked-dynamic chunk twins (the net.sockWriteDyn story). */ - "http.clientWriteDyn": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, - "http.clientEndDyn": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, - "http.clientFlushHeaders": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientAddTrailers": { argTypes: [HTTPCLIENTREQ_T, arrayOf(STRING)], result: VOID }, - "http.clientCork": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientUncork": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientWritableCorked": { argTypes: [HTTPCLIENTREQ_T], result: F64 }, - "http.clientMethod": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, - "http.clientPath": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, - "http.clientHost": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, - "http.clientProtocol": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, - "http.clientHeadersSent": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientWritableEnded": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientWritableFinished": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientSocket": { argTypes: [HTTPCLIENTREQ_T], result: NETSOCKET_T }, - "http.clientReusedSocket": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientSetNoDelay": { argTypes: [HTTPCLIENTREQ_T, BOOL], result: VOID }, - "http.clientSetSocketKeepAlive": { argTypes: [HTTPCLIENTREQ_T, BOOL, F64], result: VOID }, - "http.clientSetTimeout": { argTypes: [HTTPCLIENTREQ_T, F64], result: VOID }, - "http.clientSetTimeoutCb": { - argTypes: [HTTPCLIENTREQ_T, F64, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "http.statusCodes": { argTypes: [], result: DYN }, - "http.methods": { argTypes: [], result: arrayOf(STRING) }, - "http.reqSetTimeout": { argTypes: [HTTPREQ_T, F64], result: VOID }, - "http.reqSetTimeoutCb": { - argTypes: [HTTPREQ_T, F64, { kind: "func", params: [], ret: VOID }], - result: VOID, - }, - "http.clientDestroy": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientAbort": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, - "http.clientAborted": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientDestroyed": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, - "http.clientOnResponse": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, - "http.clientOnSocket": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, - "http.clientOnFinish": { - argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.clientOnError": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, - "http.clientOnTimeout": { - argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.clientOnClose": { - argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.clientOnAbort": { - argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - "http.clientOnContinue": { - argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], - result: VOID, - }, - /** execFileSync/execSync as ONE entry (args: cmd, argv, shell, input, - * cwd, hasEnv, envPairs, timeoutMs, stdoutMode, stderrMode, buffers — - * see scr_child.h). Throws Node's exact errors: "Command failed: " - * (+ captured stderr) on non-zero exit or signal death, "spawnSync - * ENOENT" on spawn failure, "spawnSync ETIMEDOUT" after - * the SIGTERM timeout, with Node's status, signal, output, pid, stdout - * and stderr properties (Buffer outputs when buffers is set: no - * encoding option). Result is the captured utf8 stdout (+1). */ - "cp.execSync": { - argTypes: [ - STRING, - arrayOf(STRING), - BOOL, - STRING, - BOOL, - STRING, - BOOL, - arrayOf(STRING), - F64, - F64, - F64, - BOOL, - ], - result: STRING, - }, - /** The promisified-execFile/exec capture (args: cmd, argv, shell, cwd, - * hasEnv, envPairs, timeoutMs; shell marks exec's /bin/sh -c argv, as - * for execSync, so failures display the command string): the same exec - * core in the async shape — both streams captured, no echo, Node's ASYNC messages on the throw paths - * ("Command failed: \n" with the unconditional newline, - * "spawn ENOENT" with .code, timeouts reporting as ordinary - * SIGTERM command failures — never ETIMEDOUT). Result reuses the - * ScrSpawnRes container (+1; stdout/stderr strings, status unused). - * Called only from the frontend's interned %execFileAsync and - * %execAsync ASYNC helpers, whose fiber turns the throw into the - * rejection. */ - "cp.execCapture": { - argTypes: [STRING, arrayOf(STRING), BOOL, STRING, BOOL, arrayOf(STRING), F64], - result: SPAWNRES_T, - }, - /** child_process.spawn (scr_child.c + the scr_async.c loop): posix_spawnp - * with per-slot ignore/inherit/pipe modes (and number output fds), the - * all-piped Node default, and the child registered with the event loop, - * which polls waitpid(WNOHANG) at quiescence like timers. - * NEVER throws: spawn failure defers to the "error" event, Node-exact - * (the error message is Node's "spawn "; an "error" - * event with no listener prints it and exits 1 like an EventEmitter). - * cmd/args borrowed; result an owned (+1) child handle. The loop will - * not exhaust while any spawned child is unreaped — Node's keep-alive. - * - * child.onExit / child.onClose / child.onError — terminal listener - * registration through child.on/once. Close uses the exit adapter shape - * but fires only after every piped stdio handle reaches EOF. The - * receiver is borrowed, the CALLBACK MOVES into the child's listener - * registry (released after the terminal event fires, or at reap for - * the event that never fires). Both are void (chaining is fenced). - * onExit's third emitted ingredient is an ADAPTER the backend interns - * per callback shape: the runtime invokes adapter(cb, has_code, code) - * and the adapter builds the `number | null` union (tags are program- - * dependent) or ignores the code for a zero-param callback. onError's - * adapters are runtime-provided (zero-param, or the %Error one-param - * shape — scr_error_new needs no program types). "exit" fires once - * with the code (f64 arm) or null (signal death); "error" fires only - * for spawn failure, exactly Node's split. */ - "cp.spawn": { argTypes: [STRING, arrayOf(STRING)], result: CHILD_T }, - /** Static-native fork startup and IPC. process.forkTarget initializes a - * re-executed child's inherited channel and returns its embedded target id - * (-1 in the parent). The remaining entries mirror Node's JSON channel. */ - "process.forkTarget": { argTypes: [F64], result: F64 }, - "cp.fork": { - argTypes: [F64, arrayOf(STRING), F64, F64, F64, BOOL, arrayOf(STRING), STRING], - result: CHILD_T, - }, - "child.connected": { argTypes: [CHILD_T], result: BOOL }, - "child.hasChannel": { argTypes: [CHILD_T], result: BOOL }, - "child.send": { argTypes: [CHILD_T, STRING], result: BOOL }, - "child.sendCb": { argTypes: [CHILD_T, STRING, null], result: BOOL }, - "child.disconnect": { argTypes: [CHILD_T], result: VOID }, - "child.onMessage": { argTypes: [CHILD_T, null, BOOL], result: VOID }, - "child.onDisconnect": { argTypes: [CHILD_T, null, BOOL], result: VOID }, - "child.onSpawn": { argTypes: [CHILD_T, null], result: VOID }, - /** Rest listeners use the checked function bridge to pack event arguments. */ - "child.onDyn": { argTypes: [CHILD_T, STRING, DYN], result: VOID }, - // Own properties JavaScript adds to children (handle-properties.ts). - "child.propsInstall": { argTypes: [], result: VOID }, - "child.propsDefine": { argTypes: [DYN, DYN, DYN], result: DYN }, - "child.propsDescriptor": { argTypes: [DYN, DYN], result: DYN }, - "child.propsResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "child.propsOnDyn": { argTypes: [CHILD_T, STRING, DYN], result: VOID }, - "process.connected": { argTypes: [], result: BOOL }, - /** Whether this fork child opened its channel; part of process.send's - * presence in programs with fork targets. Never throws. */ - "process.ipcOpen": { argTypes: [], result: BOOL }, - "process.send": { argTypes: [STRING], result: BOOL }, - "process.sendCb": { argTypes: [STRING, null], result: BOOL }, - "process.disconnect": { argTypes: [], result: VOID }, - "process.onMessage": { argTypes: [null, BOOL], result: VOID }, - "process.onDisconnect": { argTypes: [null, BOOL], result: VOID }, - /** child_process.execFile's callback slice: starts an all-piped child, - * captures stdout/stderr, and moves the error-first callback into the - * child registry. The callback shape is program-dependent and checked - * by the validator/backends. The last argument is exec's shell flag: - * the argv is ["-c", command] and failure messages show the command. */ - "cp.execFile": { argTypes: [STRING, arrayOf(STRING), null, BOOL], result: CHILD_T }, - // spawn's options form: per-slot stdio modes (0 ignore / 1 inherit / - // 2 fd) with the out/err fds for mode 2, detached, env replacement - // pairs, cwd ("" = inherit). - /** spawn with options (scr_child.c): the cp.spawn core plus PER-SLOT - * stdio — args are (cmd, argv, inMode, outMode, errMode, outFd, errFd, - * detached, hasEnv, envPairs, cwd); modes 0 ignore (/dev/null), 1 - * inherit, 2 fd (out/err only — the fd dup2s into the child's slot, - * Node's stdio fd form; the daemon-log idiom ["ignore", logFd, logFd]). - * detached is POSIX_SPAWN_SETSID (the child gets its own session and - * process group, Node's semantics), env is a REPLACEMENT ([k,v,...] - * pairs like cp.execSync's), cwd ""=inherit. Same event/loop story as - * cp.spawn. */ - "cp.spawnOpts": { - argTypes: [ - STRING, - arrayOf(STRING), - F64, - F64, - F64, - F64, - F64, - BOOL, - BOOL, - BOOL, - arrayOf(STRING), - STRING, - ], - result: CHILD_T, - }, - /** Runtime options normalized into the native spawn core; unsupported - * process features retain explicit refusals. (command, args, options). */ - "cp.spawnDynamic": { argTypes: [STRING, arrayOf(STRING), DYN], result: CHILD_T }, - // The callback's func type is program-dependent (zero params, or the - // `number | null` union / the %Error class) — the libCall case checks - // the shape; the slot here only pins arity and the child receiver. - "child.onExit": { argTypes: [CHILD_T, null], result: VOID }, - "child.onClose": { argTypes: [CHILD_T, null], result: VOID }, - "child.onError": { argTypes: [CHILD_T, null], result: VOID }, - // Like process.envGet, spawnRes.status's result type is program-dependent - // (the interned `number | null` union) — the libCall case checks the arms. - "spawnRes.status": { argTypes: [SPAWNRES_T], result: VOID }, - // spawnRes.error's result is the interned `Error | undefined` union — - // the libCall case checks the arms, the spawnRes.status pattern. - /** Node's spawn-failure carrier `error?: Error`: a fresh +1 %Error - * ("spawnSync ENOENT", `code` stamped) when the spawn itself - * failed, the interned undefined arm otherwise — the result type is the - * call site's `Error | undefined` union, constructed type-directedly in - * the backend (the envGet convention). Never throws. */ - "spawnRes.error": { argTypes: [SPAWNRES_T], result: VOID }, - // spawnRes.signal's result is the interned `string | null` union — - // same pattern. - "spawnRes.signal": { argTypes: [SPAWNRES_T], result: VOID }, - // The ChildProcess lifecycle members: pid/exitCode are program-dependent - // unions (`number | undefined` / `number | null` — the libCall case - // checks the arms, the spawnRes.status pattern). - /** The ChildProcess lifecycle members (scr_child.c), Node's shapes - * exactly (SEMANTICS.md has the pinned matrix). child.pid is the - * checker's `number | undefined` (undefined = spawn failure) and - * child.exitCode its `number | null` (null while running and after a - * signal death; -errno once a spawn failure settled) — both unions are - * type-directed constructions in the backend over a has/get runtime - * pair, the spawnRes.status pattern. child.killed is Node's - * sent-a-signal flag. child.kill sends while the child is un-reaped - * (false after — Node's null-handle answer) and THROWS the - * Unknown-signal TypeError on bad names (may-throw); killNum passes - * numbers through (0 probes; never throws). child.unref drops the - * child from the loop's keep-alive set — it is still reaped while the - * loop runs for other reasons, and one the loop never reaps is left to - * the OS at exit. All receivers borrowed. */ - "child.pid": { argTypes: [CHILD_T], result: VOID }, - "child.exitCode": { argTypes: [CHILD_T], result: VOID }, - // The close-override pair: the bound close's result func type and the - // override's func argument are program-dependent (the callback union's - // tags) — the slots pin the server receiver; the libCall case checks - // the bound value's shape. - /** The close-override pair (the portless close-proxy idiom). - * serverCloseBind is `wrapper.close.bind(wrapper)` as a VALUE: a - * compiler-emitted closure over the server whose invocation runs the - * REAL close (scr_net_server_close_direct — never the override), so - * the override body's `origClose(cb)` cannot recurse; its callback - * argument (the `((err?: Error) => void) | undefined` union) registers - * as a once-'close' listener, a one-param callback wrapped by an - * emitted zero-arg trampoline firing the undefined arm (a clean close - * carries no error). serverSetCloseOverride is `wrapper.close = fn`: - * the override MOVES in behind an emitted zero-arg wrapper (it invokes - * the user function with the undefined-arm callback — tags are program - * data), and server.close() consults it before closing. */ - "net.serverCloseBind": { argTypes: [NETSERVER_T], result: VOID }, - "net.serverSetCloseOverride": { argTypes: [NETSERVER_T, null], result: VOID }, - // The piped-output stream reads: `Readable | null` unions (the libCall - // case checks the arms — the child.pid pattern with a ref arm). - /** The piped-output streams (stdio mode 3 — scr_child.c's stream - * slice). child.stdout/child.stderr answer the checker's - * `Readable | null` union (type-directed construction in the backend - * over the +1-or-NULL runtime pair — the child.pid pattern with a ref - * arm). stream.onData/onEnd register 'data'/'end' listeners (receiver - * borrowed, CALLBACK MOVES, trailing once-flag, void — chaining - * fenced): 'data' fires one Buffer chunk per read (zero-param and - * Buffer/string adapters are runtime-provided; setEncoding threads - * split multibyte sequences through the shared StringDecoder core. A - * union-param listener — - * ngrok's `Buffer | string` — gets a compiler-emitted adapter wrapping - * the chunk at its Buffer arm), 'end' fires once at EOF, always BEFORE - * the child's 'exit' (the pinned ordering). A flowing stream keeps the - * loop alive: usesTimers. */ - "child.stdout": { argTypes: [CHILD_T], result: VOID }, - "child.stderr": { argTypes: [CHILD_T], result: VOID }, - /** The piped-input writer (stdio mode 3 on fd 0). child.stdin answers - * `Writable | null`; writes copy borrowed data into the nonblocking - * queue, end/destroy settle it, writable is a pure state read, and - * listener callbacks move into the writer registry. */ - "child.stdin": { argTypes: [CHILD_T], result: VOID }, - /** Under the general stream representation (general-streams.ts), a - * stdio read (slot 0, 1 or 2) answers the child's bridged runtime stream - * — `Writable | null` for stdin, `Readable | null` otherwise; the bridge - * throws when the program lacks the stream unit's boxing helpers. */ - "child.stdioStream": { argTypes: [CHILD_T, F64], result: VOID }, - // Listener registrations: callback func shapes are program-dependent - // (zero-param / Buffer / string / unknown / Buffer-armed union) — the slots pin arity, - // the stream receiver, and the once-flag. - "stream.onData": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, - "stream.onDataStr": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, - "stream.onEnd": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, - "stream.childSetEncoding": { argTypes: [CHILDSTREAM_T, STRING], result: CHILDSTREAM_T }, - "writer.writeString": { argTypes: [CHILDWRITER_T, STRING], result: BOOL }, - "writer.writeBytes": { argTypes: [CHILDWRITER_T, BYTES_U8], result: BOOL }, - "writer.end": { argTypes: [CHILDWRITER_T], result: VOID }, - "writer.destroy": { argTypes: [CHILDWRITER_T], result: VOID }, - "writer.writable": { argTypes: [CHILDWRITER_T], result: BOOL }, - "writer.onDrain": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, - "writer.onFinish": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, - "writer.onError": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, - /** The first-class WritableStream write (`output.write(line)` — the - * prefixStream idiom): the receiver IS the fd scalar (process.stdout/ - * stderr reads mint 1/2), dispatched onto the exact stdoutWrite/ - * stderrWrite paths so buffering and ordering stay identical. Data - * borrowed; the bool is Node's always-true backpressure signal. */ - "procStream.write": { argTypes: [PROCSTREAM_T, STRING], result: BOOL }, - /** process.stdout/stderr flowing into a runtime Writable slot under the - * general stream representation: an fd-backed Writable (+1) whose - * writes take process.stdout.write's synchronous path. */ - /** A pipeline() stage that is a checked-dynamic value: a boxed native - * stream unwraps by identity (+1); anything else throws the given - * not-supported message. Typed as a Duplex so every stage position - * accepts it; the runtime pipeline checks the stream's real sides. */ - "stream.unboxDyn": { - argTypes: [DYN, STRING], - result: { kind: "object", className: "%Duplex" }, - }, - /** Whether pipeline() pumps a checked-dynamic first stage as an - * iterable (a string, or a value with an iterator method that is not a - * stream with pipe() and on()) instead of piping it as a stream. */ - "stream.dynIterableSource": { argTypes: [DYN], result: BOOL }, - /** The Transform a Cipheriv or Decipheriv reads and writes as a stream - * (+1, created on first use and owned by the cipher). */ - "stream.handleStream": { - argTypes: [DYN], - result: { kind: "object", className: "%Transform" }, - }, - /** Checked-dynamic stream slots (dyn-streams.ts): the native stream a - * checked-dynamic value boxes (+1) — a runtime stream with the readable - * (1) or writable (2) side — or null for any other value. Never throws; - * the result union is `%Readable | null` or `%Writable | null`. */ - "stream.dynNative": { argTypes: [DYN, F64], result: VOID }, - /** stream.dynNative for a pipe or unpipe destination: also a - * constructor-function stream's runtime stream, in programs that link - * that unit, unless it overrides write() or end(). */ - "stream.dynNativePipe": { argTypes: [DYN, F64], result: VOID }, - /** readable.pipe(destination, end) into a checked-dynamic destination - * that is not a native stream: [source, the source boxed, destination, - * end]. Node's pipe listener protocol over the destination's members - * (scr_stream_dyn.c); throws what those members throw. */ - "stream.pipeDyn": { argTypes: [null, DYN, DYN, BOOL], result: VOID }, - /** readable.unpipe(destination) for a checked-dynamic destination that - * is not a native stream: [source, destination]. Unpipes the pipe - * stream.pipeDyn registered into it, if any (scr_stream_dyn.c). */ - "stream.unpipeDyn": { argTypes: [null, DYN], result: VOID }, - "stream.stdioWritable": { - argTypes: [PROCSTREAM_T], - result: { kind: "object", className: "%Writable" }, - }, - "child.killed": { argTypes: [CHILD_T], result: BOOL }, - "child.kill": { argTypes: [CHILD_T, STRING], result: BOOL }, - "child.killNum": { argTypes: [CHILD_T, F64], result: BOOL }, - "child.unref": { argTypes: [CHILD_T], result: VOID }, - "child.ref": { argTypes: [CHILD_T], result: VOID }, - "spawnRes.stdout": { argTypes: [SPAWNRES_T], result: STRING }, - "spawnRes.stderr": { argTypes: [SPAWNRES_T], result: STRING }, - /** fs/promises (scr_lib.c over scr_async.c's settled minting): the SAME - * sync syscalls, wrapped in an ALREADY-SETTLED promise — failure - * REJECTS (catchable at the await) instead of throwing, so none of - * these are in the may-throw seed. readFile is utf8-fenced like - * readFileSync. The non-interleaving divergence is documented in - * SEMANTICS.md. */ - /** node:crypto randomness: randomUUID plus randomBytes as either a real - * Buffer or the fused randomBytes(n).toString("hex"|"base64") path. - * The size-taking forms throw Node's RangeError on invalid values. */ - "crypto.randomUUID": { argTypes: [], result: STRING }, - "crypto.randomBytesToString": { argTypes: [F64, STRING], result: STRING }, - /** crypto.randomBytes(n) → a real u8 Buffer (+1). THROWS Node's - * RangeError on out-of-range sizes, exactly like the composed - * randomBytesToString (which keeps its one-libCall lowering — the two - * coexist: the composed form never materializes the Buffer). */ - "crypto.randomBytes": { argTypes: [F64], result: BYTES_U8 }, - /** The fused createHash(alg).update(data).digest(enc) fast path and the - * one-shot crypto.hash implementation. First-class Hash/Hmac handles use - * the entries below; all paths share the incremental runtime core. */ - "crypto.hashDigestStr": { argTypes: [STRING, STRING, STRING], result: STRING }, - "crypto.hashDigestBytes": { argTypes: [STRING, BYTES_U8, STRING], result: STRING }, - /** First-class static Hash/Hmac handles. Constructors validate the - * runtime algorithm string (every digest Node 24 lists); update returns the same - * handle by retained identity, copy snapshots Hash state, and digest - * finalizes the handle and returns either a Buffer or encoded string. */ - "crypto.native": { argTypes: [], result: DYN }, - "crypto.hashNew": { argTypes: [STRING], result: CRYPTOHASH_T }, - /** The Transform a Hash or Hmac reads and writes as a stream (+1, - * created on first use and owned by the value; scr_stream.c). The Piped - * form, for pipe destinations and pipeline stages, also keeps the value - * alive until the stream flushes or is destroyed. */ - "crypto.hashStream": { argTypes: [null], result: { kind: "object", className: "%Transform" } }, - "crypto.hashStreamPiped": { - argTypes: [null], - result: { kind: "object", className: "%Transform" }, - }, - /** createCipheriv (false) / createDecipheriv (true) over checked-dynamic - * cipher, key, iv and options arguments (scr_cipher.c): a native - * Cipheriv/Decipheriv handle whose methods dispatch through dynInvoke. */ - "crypto.cipherNew": { argTypes: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, - /** createECDH(curveName) over a checked-dynamic curve (scr_ecdh.c): a - * native ECDH handle whose methods dispatch through dynInvoke. */ - "crypto.ecdhNew": { argTypes: [DYN], result: DYN }, - /** ECDH.convertKey(key, curve, inputEncoding, outputEncoding, format): - * the converted point as a Buffer or string. */ - "crypto.ecdhConvertKey": { argTypes: [DYN, DYN, DYN, DYN, DYN], result: DYN }, - /** createDiffieHellman(sizeOrKey, keyEncoding, generator, - * generatorEncoding) over checked-dynamic arguments (scr_dh.c): a native - * DiffieHellman handle whose methods dispatch through dynInvoke. */ - "crypto.dhNew": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, - /** getDiffieHellman(groupName) and createDiffieHellmanGroup(name): a - * native DiffieHellmanGroup handle. */ - "crypto.dhGroupNew": { argTypes: [DYN], result: DYN }, - /** checkPrimeSync(candidate, options) and generatePrimeSync(size, - * options) over checked-dynamic arguments (scr_dh.c): a boolean, and an - * ArrayBuffer or bigint, as checked-dynamic results. The Cb forms take - * the callback and whether its error parameter takes null for Node's - * undefined, and call it in a later macrotask; Node's argument errors - * throw (may-throw). */ - "crypto.checkPrimeSync": { argTypes: [DYN, DYN], result: DYN }, - /** getCipherInfo(nameOrNid, options) over checked-dynamic arguments - * (scr_cipher_info.c): the info object or undefined. */ - "crypto.getCipherInfo": { argTypes: [DYN, DYN], result: DYN }, - "crypto.checkPrimeCb": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, - "crypto.generatePrimeSync": { argTypes: [DYN, DYN], result: DYN }, - "crypto.generatePrimeCb": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, - "crypto.hmacNewStr": { argTypes: [STRING, STRING], result: CRYPTOHMAC_T }, - "crypto.hmacNewBytes": { argTypes: [STRING, BYTES_U8], result: CRYPTOHMAC_T }, - "crypto.hashUpdateStr": { argTypes: [CRYPTOHASH_T, STRING], result: CRYPTOHASH_T }, - "crypto.hashUpdateBytes": { argTypes: [CRYPTOHASH_T, BYTES_U8], result: CRYPTOHASH_T }, - "crypto.hmacUpdateStr": { argTypes: [CRYPTOHMAC_T, STRING], result: CRYPTOHMAC_T }, - "crypto.hmacUpdateBytes": { argTypes: [CRYPTOHMAC_T, BYTES_U8], result: CRYPTOHMAC_T }, - "crypto.hashCopy": { argTypes: [CRYPTOHASH_T], result: CRYPTOHASH_T }, - "crypto.hashDigestString": { argTypes: [CRYPTOHASH_T, STRING], result: STRING }, - "crypto.hashDigestBuffer": { argTypes: [CRYPTOHASH_T], result: BYTES_U8 }, - "crypto.hmacDigestString": { argTypes: [CRYPTOHMAC_T, STRING], result: STRING }, - "crypto.hmacDigestBuffer": { argTypes: [CRYPTOHMAC_T], result: BYTES_U8 }, - /** The option and any-value forms, with Node's checks at run time. The - * Dyn digests return a string or a Buffer, and crypto.hash's Dyn form - * either one by its outputEncoding. */ - "crypto.hashNewOpts": { argTypes: [STRING, DYN], result: CRYPTOHASH_T }, - "crypto.hmacNewDyn": { argTypes: [STRING, DYN, DYN], result: CRYPTOHMAC_T }, - "crypto.hashCopyOpts": { argTypes: [CRYPTOHASH_T, DYN], result: CRYPTOHASH_T }, - "crypto.hashUpdateEnc": { argTypes: [CRYPTOHASH_T, STRING, STRING], result: CRYPTOHASH_T }, - "crypto.hmacUpdateEnc": { argTypes: [CRYPTOHMAC_T, STRING, STRING], result: CRYPTOHMAC_T }, - "crypto.hashUpdateDyn": { argTypes: [CRYPTOHASH_T, DYN, DYN], result: CRYPTOHASH_T }, - "crypto.hmacUpdateDyn": { argTypes: [CRYPTOHMAC_T, DYN, DYN], result: CRYPTOHMAC_T }, - "crypto.hashDigestDyn": { argTypes: [CRYPTOHASH_T, DYN], result: DYN }, - "crypto.hmacDigestDyn": { argTypes: [CRYPTOHMAC_T, DYN], result: DYN }, - "crypto.hashOneshot": { argTypes: [STRING, DYN, DYN], result: DYN }, - /** Views of any element kind (the validator checks both are views). */ - "crypto.timingSafeEqual": { argTypes: [null, null], result: BOOL }, - "crypto.timingSafeEqualDyn": { argTypes: [DYN, DYN], result: BOOL }, - "crypto.randomFill": { argTypes: [BYTES_U8, F64, F64], result: BYTES_U8 }, - "crypto.randomFillRest": { argTypes: [BYTES_U8, F64], result: BYTES_U8 }, - "crypto.randomInt": { argTypes: [F64, F64], result: F64 }, - "crypto.randomIntMax": { argTypes: [F64], result: F64 }, - /** randomFillSync over any view returns that view at its own type (the - * validator checks the result against the first argument). The Dyn form - * takes ArrayBuffers and other values. The callback forms carry - * randomFill's argument count before the callback; randomIntCb carries - * whether min was given. */ - "crypto.randomFillView": { argTypes: [null, F64, F64], result: BYTES_U8 }, - "crypto.randomFillViewRest": { argTypes: [null, F64], result: BYTES_U8 }, - /** crypto.getRandomValues: a view returns itself at its own type (the - * float/size DOMExceptions are thrown at run time); any other value - * takes the checked form. */ - "crypto.getRandomValues": { argTypes: [null], result: BYTES_U8 }, - "crypto.getRandomValuesDyn": { argTypes: [DYN], result: DYN }, - "crypto.randomFillSyncDyn": { argTypes: [DYN, DYN, DYN], result: DYN }, - "crypto.randomFillCb": { argTypes: [null, F64, F64, F64, null], result: VOID }, - "crypto.randomFillCbDyn": { argTypes: [DYN, DYN, DYN, F64, null], result: VOID }, - "crypto.randomIntCb": { argTypes: [F64, F64, F64, null], result: VOID }, - "crypto.randomUUIDOpts": { argTypes: [DYN], result: STRING }, - /** Secret KeyObjects: native handles whose getters and methods dispatch - * through the handle. keyObjectInput reads a key's bytes for hkdf. */ - "crypto.createSecretKey": { argTypes: [DYN, DYN], result: DYN }, - "crypto.generateKeySync": { argTypes: [DYN, DYN], result: DYN }, - "crypto.generateKeyCb": { argTypes: [DYN, DYN, null], result: VOID }, - "crypto.keyObjectInput": { argTypes: [DYN], result: BYTES_U8 }, - "crypto.isKeyObject": { argTypes: [DYN], result: BOOL }, - /** WebCrypto (scr_webcrypto.c): each method takes its argument vector as - * one dyn array and answers a promise that argument and validation - * failures reject. CryptoKey values are native handles. */ - "webcrypto.generateKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.importKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.exportKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.encrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.decrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.wrapKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.unwrapKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.deriveBits": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.deriveKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.sign": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.verify": { argTypes: [DYN], result: { kind: "promise", inner: BOOL } }, - "webcrypto.digest": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, - "webcrypto.isCryptoKey": { argTypes: [DYN], result: BOOL }, - "webcrypto.object": { argTypes: [], result: DYN }, - "crypto.randomUUIDv7": { argTypes: [DYN], result: STRING }, - "crypto.pbkdf2": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING], result: BYTES_U8 }, - "crypto.randomBytesCb": { argTypes: [F64, null], result: VOID }, - "crypto.pbkdf2Cb": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING, null], result: VOID }, - "crypto.hkdf": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], result: DYN }, - "crypto.hkdfCb": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64, null], result: VOID }, - "crypto.scrypt": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN], result: BYTES_U8 }, - "crypto.scryptCb": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN, null], result: VOID }, - /** util.promisify over the callback forms: the callback form's - * arguments without the callback → a promise the callback form settles, - * or rejected with an argument error; never throws. The result's value - * type is the declared one (a Buffer, the filled view, a number, or a - * checked-dynamic value); the validator checks it. */ - "crypto.randomBytesPromise": { argTypes: [F64], result: VOID }, - "crypto.pbkdf2Promise": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING], result: VOID }, - "crypto.scryptPromise": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN], result: VOID }, - "crypto.hkdfPromise": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], result: VOID }, - "crypto.randomFillPromise": { argTypes: [null, F64, F64, F64], result: VOID }, - "crypto.randomFillPromiseDyn": { argTypes: [DYN, DYN, DYN, F64], result: VOID }, - "crypto.randomIntPromise": { argTypes: [F64, F64, F64], result: VOID }, - "crypto.generateKeyPromise": { argTypes: [DYN, DYN], result: VOID }, - /** util.promisify(dns.lookup) and util.promisify(dns.lookupService): - * [hostname, options] and [address, port] → a promise of the - * checked-dynamic result object (the address array with options.all), - * or rejected with the error; never throws. */ - "dns.lookupPromise": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "dns.lookupServicePromise": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - // The Buffer statics and the fs/zlib Buffer forms: fixed always-u8 - // signatures (Buffer IS a Uint8Array — one bytes kind). - /** The Buffer statics with fixed (always-u8) signatures. fromStr is - * `Buffer.from(string, enc)` — the frontend completes an omitted - * encoding to "utf8" and fences non-literal/unsupported ones; hex and - * base64 decode Node-leniently, so it never throws. concat takes ONE - * bytes[] arg (the list) and returns a fresh copy. `Buffer.from(u8)` - * and `Buffer.alloc(n)` need no libFn — they lower to bytesNew. */ - "buffer.fromStr": { argTypes: [STRING, STRING], result: BYTES_U8 }, - "buffer.fromStrChecked": { argTypes: [STRING, STRING], result: BYTES_U8 }, - "buffer.brand": { argTypes: [BYTES_U8], result: BYTES_U8 }, - /** Buffer.isBuffer over a typed view: whether the view carries the - * Buffer brand (any element kind; only Buffers do). Never throws. */ - "buffer.isBranded": { argTypes: [null], result: BOOL }, - /** Buffer.from on checked-native strings, bytes, arrays and data-only - * array-like/Buffer-JSON objects. The encoding is a normalized literal; - * non-string inputs ignore it. Copies the input and may throw during - * element coercion or argument validation. Custom object valueOf and - * opaque reference inputs retain explicit runtime refusals. */ - "buffer.fromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, - "buffer.concat": { argTypes: [arrayOf(BYTES_U8)], result: BYTES_U8 }, - "buffer.concatDyn": { argTypes: [DYN], result: BYTES_U8 }, - /** The pooled Buffer factories (Node's allocate/fromStringFast/ - * fromArrayLike): allocUnsafe, Buffer.from of strings, Uint8Arrays, - * number[] and checked values, and concat return views into the - * per-thread pool slab below Buffer.poolSize >>> 1 bytes. alloc and - * allocUnsafeSlow never pool. The size forms validate like Node - * (ERR_OUT_OF_RANGE). poolSize reads and writes Buffer.poolSize. */ - "buffer.alloc": { argTypes: [F64], result: BYTES_U8 }, - /** Buffer.alloc as a function value: the size, fill and encoding arrive - * unchecked, and the runtime fills as Node's alloc does. */ - "buffer.allocValue": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, - "buffer.allocUnsafe": { argTypes: [F64], result: BYTES_U8 }, - "buffer.allocUnsafeSlow": { argTypes: [F64], result: BYTES_U8 }, - "buffer.poolFromStr": { argTypes: [STRING, STRING], result: BYTES_U8 }, - "buffer.poolFromStrChecked": { argTypes: [STRING, STRING], result: BYTES_U8 }, - "buffer.poolFromBytes": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "buffer.poolFromArray": { argTypes: [arrayOf(F64)], result: BYTES_U8 }, - "buffer.poolFromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, - /** poolFromDyn that also copies an array crossing by reference. */ - "buffer.poolFromDynRef": { argTypes: [DYN, STRING], result: BYTES_U8 }, - "buffer.poolFromDynOffset": { argTypes: [DYN, DYN], result: BYTES_U8 }, - /** Buffer.from(value, byteOffset, length): an ArrayBuffer's range with - * Node's fromArrayBuffer checks; other values as poolFromDynOffset. */ - "buffer.poolFromDynRange": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, - "buffer.poolSize": { argTypes: [], result: F64 }, - "buffer.setPoolSize": { argTypes: [F64], result: VOID }, - /** Buffer.byteLength(string, enc) — enc a NORMALIZED literal like - * fromStr's — and Buffer.isEncoding(name) over a runtime string - * (case-insensitive against Node's alias set). Pure; never throw. */ - "buffer.byteLenDyn": { argTypes: [DYN, STRING], result: F64 }, - /** Buffer.byteLength as a function value: the encoding arrives at run time. */ - "buffer.byteLenValue": { argTypes: [DYN, DYN], result: F64 }, - "buffer.byteLenStr": { argTypes: [STRING, STRING], result: F64 }, - "buffer.isEncoding": { argTypes: [STRING], result: BOOL }, - /** Buffer.concat(list, totalLength): the concatenation truncated or - * zero-padded to the total. THROWS Node's 'length' RangeError on a - * negative/non-integer total (may-throw seed). */ - "buffer.concatLen": { argTypes: [arrayOf(BYTES_U8), F64], result: BYTES_U8 }, - "buffer.concatLenDyn": { argTypes: [DYN, F64], result: BYTES_U8 }, - // The checked-dynamic compare/equals validators (Node's argument - // ladders over dyn-boxed invalid-input probes). - /** The checked-dynamic compare/equals validators (scr_bytes_io.c) — - * the lowered form when an argument is NOT statically bytes (the - * invalid-input probes; a dyn from an untyped JS helper): Node's own - * argument ladders run at runtime — ERR_INVALID_ARG_TYPE with the - * API's argument name ("buf1"/"buf2", "otherBuffer", "target"; offsets - * "of type number"), validateOffset's ERR_OUT_OF_RANGE for bad - * numbers, undefined offsets taking their Node defaults — and a - * well-typed value still computes the real answer. All args borrowed - * dyn; compareChk's four offset slots pass the undefined dyn when - * syntactically absent. May-throw seeds. */ - "buffer.compareChk": { argTypes: [DYN, DYN], result: F64 }, - "bytes.equalsChk": { argTypes: [BYTES_U8, DYN], result: BOOL }, - "bytes.compareChk": { argTypes: [BYTES_U8, DYN, DYN, DYN, DYN, DYN], result: F64 }, - /** The deprecated `new Buffer(number, 'enc')` string-arm rejection: - * always throws Node's ERR_INVALID_ARG_TYPE ("The \"string\" argument - * must be of type string. Received ..."). Borrowed dyn; may-throw. */ - "buffer.newStringFail": { argTypes: [DYN], result: BYTES_U8 }, - "buffer.deprecation": { argTypes: [], result: VOID }, - "buffer.deprecationCount": { argTypes: [], result: VOID }, - "buffer.constructor": { argTypes: [], result: DYN }, - "buffer.installValue": { argTypes: [], result: VOID }, - "buffer.staticValue": { argTypes: [STRING], result: DYN }, - "buffer.prototypeMember": { argTypes: [DYN], result: DYN }, - /** fs._toUnixTimestamp(time) over a dyn value: numeric strings and - * finite numbers coerce (negatives answer now/1000, Node's shape); - * everything else throws Node's ERR_INVALID_ARG_TYPE. May-throw. */ - "fs.toUnixTimestamp": { argTypes: [DYN], result: F64 }, - /** The fs argument-validation ladders (checked-dynamic lane): each Chk - * replicates its API's Node-order validation over dyn values (Node's - * exact typed errors — ERR_INVALID_ARG_TYPE/VALUE, ERR_OUT_OF_RANGE), - * and a full pass meets the trailing compiler-rendered SC2020 fence - * string — so the ALWAYS-THROW forms take the error.nodeThrow - * polymorphic-result carve-out. mkdtempSyncChk and the lchmod sync/ - * promise pair run the REAL operation on a validated pass instead - * (macOS lchmod(2); non-APPLE answers Node's not-a-function / - * ERR_METHOD_NOT_IMPLEMENTED shapes). May-throw seeds, all of them. */ - "fs.existsChk": { argTypes: [DYN, DYN], result: DYN }, - "fs.mkdtempChk": { argTypes: [DYN, DYN, STRING], result: VOID }, - "fs.mkdtempSyncChk": { argTypes: [DYN, DYN, STRING], result: STRING }, - "fs.readFileChk": { argTypes: [DYN, DYN, DYN, STRING], result: VOID }, - "fs.opendirChk": { argTypes: [DYN, DYN, STRING], result: VOID }, - "fs.watchFileChk": { argTypes: [DYN, DYN, STRING], result: VOID }, - "fs.lchmodChk": { argTypes: [DYN, DYN, DYN, STRING], result: VOID }, - "fs.lchmodSyncChk": { argTypes: [DYN, DYN], result: DYN }, - "fsp.lchmodChk": { argTypes: [DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fs.readChk": { argTypes: [DYN, DYN, DYN, DYN, DYN, STRING], result: VOID }, - "fs.streamOptsChk": { argTypes: [DYN, DYN, STRING], result: VOID }, - /** fs.ReadStream / fs.WriteStream (scr_fs_stream.c): the constructors - * take the path and options as dyn values and throw Node's option - * errors; the members read bytesRead/bytesWritten, pending and the path - * value, and close() / close(cb) (a `() => void` callback). */ - "fs.createReadStream": { - argTypes: [DYN, DYN], - result: { kind: "object", className: "%Readable" }, - }, - "fs.createWriteStream": { - argTypes: [DYN, DYN], - result: { kind: "object", className: "%Writable" }, - }, - "fs.fsStreamBytes": { argTypes: [null], result: F64 }, - "fs.fsStreamPending": { argTypes: [null], result: BOOL }, - "fs.fsStreamPath": { argTypes: [null], result: DYN }, - "fs.fsStreamPropDyn": { argTypes: [null, STRING], result: DYN }, - "fileHandle.createReadStream": { - argTypes: [FILEHANDLE_T, DYN], - result: { kind: "object", className: "%Readable" }, - }, - "fileHandle.createWriteStream": { - argTypes: [FILEHANDLE_T, DYN], - result: { kind: "object", className: "%Writable" }, - }, - "fs.fsStreamClose": { argTypes: [null], result: VOID }, - "fs.fsStreamCloseCb": { argTypes: [null, funcOf([], VOID)], result: VOID }, - /** The compiler-resolved ERR_INVALID_ARG_TYPE throw with a RUNTIME- - * rendered Received tail: args [argname, "of type ..." clause, the - * offending dyn value]. ALWAYS THROWS; polymorphic result (the - * error.nodeThrow pattern). May-throw seed. */ - "error.argTypeThrow": { argTypes: [STRING, STRING, DYN], result: VOID }, - /** The property flavor of argTypeThrow ("The \"options.x\" property - * must be ..."): the option-bag ladders' provably-invalid arms. ALWAYS - * THROWS; polymorphic result. May-throw seed. */ - "error.propTypeThrow": { argTypes: [STRING, STRING, DYN], result: VOID }, - /** The checked-dynamic max-listeners ladders: setMaxChk is the - * instance form over a dyn n (non-numbers ERR_INVALID_ARG_TYPE, - * negatives/NaN ERR_OUT_OF_RANGE; +1 receiver back — chaining); - * setDefaultMaxChk is the static/property form, its second argument - * naming the message slot ("setMaxListeners" for the static call, - * "defaultMaxListeners" for the module-property assignment). */ - "emitter.setMaxChk": { argTypes: [null, DYN], result: VOID }, - "emitter.setDefaultMaxChk": { argTypes: [DYN, STRING], result: VOID }, - /** The Buffer forms of fs: readFileSync(path) with NO - * encoding → bytes (+1), writeFileSync/appendFileSync(path, bytes), and the - * fs/promises readFile(path) no-encoding form (an already-settled - * promise, rejecting on failure like the other fsp members). The sync - * forms THROW catchably on failure exactly like the utf8 forms. */ - "fs.readFileSyncBytes": { argTypes: [STRING], result: BYTES_U8 }, - "fs.writeFileSyncBytes": { argTypes: [STRING, BYTES_U8], result: VOID }, - "fs.appendFileSyncBytes": { argTypes: [STRING, BYTES_U8], result: VOID }, - "fsp.readFileBytes": { argTypes: [STRING], result: { kind: "promise", inner: BYTES_U8 } }, - /** node:zlib (scr_zlib.c — native-toolchain.ts compiles/links it ONLY when these - * appear on the IR, the regex/libcurl gating precedent): one-shot zlib, - * raw-DEFLATE, gzip, and auto-detect codecs over u8 bytes with Node's - * default options, or a validated literal compression level from -1 to 9. - * Compression never throws (OOM aborts); decompression - * of corrupt input THROWS Node's error catchably. */ - "zlib.deflateSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "zlib.inflateSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "zlib.deflateRawSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - /** bytes, mode (0 zlib / 1 raw / 2 gzip), validated compression level. */ - "zlib.deflateLevelSync": { argTypes: [BYTES_U8, F64, F64], result: BYTES_U8 }, - "zlib.inflateRawSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "zlib.gzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "zlib.gunzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - "zlib.unzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, - /** The default-options callback twins run on the executable's shared - * native work pool and deliver (Error | null, Buffer) on a later loop - * turn. crc32 validates its optional uint32 seed synchronously. */ - "zlib.deflateCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.inflateCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.deflateRawCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.inflateRawCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.gzipCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.gunzipCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.unzipCb": { argTypes: [BYTES_U8, null], result: VOID }, - "zlib.crc32": { argTypes: [BYTES_U8, F64], result: F64 }, - /** The one-shot forms with an options value: node_zlib_mode, the input, - * the options (validated at runtime with Node's ladder). The Cb form - * takes the program-shaped (Error | null, Buffer) callback, which moves. */ - "zlib.syncOpts": { argTypes: [F64, BYTES_U8, DYN], result: BYTES_U8 }, - "zlib.cbOpts": { argTypes: [F64, BYTES_U8, DYN, null], result: VOID }, - /** The checked-dynamic input twins (the JS lane's untyped data). */ - "zlib.syncOptsDyn": { argTypes: [F64, DYN, DYN], result: BYTES_U8 }, - "zlib.cbOptsDyn": { argTypes: [F64, DYN, DYN, null], result: VOID }, - /** util.promisify over the callback one-shots: [mode, data, options] - * like zlib.cbOpts → a promise the worker-pool run settles with the - * Buffer or the callback form's Error. An options or input error rejects - * it; never throws. The Dyn twin takes the JS lane's untyped data. */ - "zlib.promise": { argTypes: [F64, BYTES_U8, DYN], result: { kind: "promise", inner: BYTES_U8 } }, - "zlib.promiseDyn": { argTypes: [F64, DYN, DYN], result: { kind: "promise", inner: BYTES_U8 } }, - /** The stream classes (scr_zlib_stream.c, linked with the stream unit): - * streamNew takes Node's node_zlib_mode number and the options value - * (undefined when absent), validates it with Node's ladder, and answers - * the +1 %Transform stream. flush takes the kind value, params the level - * and strategy values; the Cb forms move their () => void callback. The - * receivers are borrowed. */ - /** Connects scr_brotli.c to the zlib engine (%main, before any module). */ - "zlib.brotliInstall": { argTypes: [], result: VOID }, - "zlib.streamNew": { argTypes: [F64, DYN], result: VOID }, - "zlib.streamBytesWritten": { argTypes: [null], result: F64 }, - /** `stream instanceof zlib.Gzip`: the borrowed stream and the class's - * node_zlib_mode; true when the stream is a zlib stream of that mode. */ - "zlib.streamIs": { argTypes: [null, F64], result: BOOL }, - "zlib.streamFlush": { argTypes: [null, DYN], result: VOID }, - "zlib.streamFlushCb": { argTypes: [null, DYN, null], result: VOID }, - "zlib.streamParams": { argTypes: [null, DYN, DYN], result: VOID }, - "zlib.streamParamsCb": { argTypes: [null, DYN, DYN, null], result: VOID }, - "zlib.streamClose": { argTypes: [null], result: VOID }, - "zlib.streamCloseCb": { argTypes: [null, null], result: VOID }, - "zlib.streamReset": { argTypes: [null], result: VOID }, - /** The Buffer overloads of the raw stream writes — same promptly - * submitted streams as process.stdoutWrite/stderrWrite, constantly true. - * The encoding arg is evaluated but ignored for bytes, like Node. The Cb - * forms move their program-shaped completion callback to the tick queue. */ - "process.stdoutWriteBytes": { argTypes: [BYTES_U8, STRING], result: BOOL }, - "process.stderrWriteBytes": { argTypes: [BYTES_U8, STRING], result: BOOL }, - // Completion callback is program-dependent: zero params, checked-dynamic, - // or an optional Error | null slot (same success shape as fs.rename). - "process.stdoutWriteBytesCb": { argTypes: [BYTES_U8, STRING, null], result: BOOL }, - "process.stderrWriteBytesCb": { argTypes: [BYTES_U8, STRING, null], result: BOOL }, - /** The value of a FULFILLED promise, read without a microtask turn (+1 - * for references): the tuple form of Promise.all reads its entries this - * way once the combinator fulfills, each entry widened to Promise - * and read at the type it was built with (program-dependent). */ - "promise.fulfilledValue": { argTypes: [null], result: VOID }, - /** Promise.all's tuple form as a value: the entries (each widened to - * Promise) join the runtime combinator, and when it fulfills the - * function reads the fulfilled entries into the tuple inside that - * settlement, so the result settles on the combinator's turn; a - * rejection settles it the same way. The result is the tuple's promise - * (program-dependent). */ - "promise.allTuple": { argTypes: [null, null], result: VOID }, - "fsp.readFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: STRING } }, - "fsp.writeFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, - /** fs.promises.writeFile(path, data, { mode }): the settled-promise - * twin of fs.writeFileModeSync. */ - "fsp.writeFileMode": { - argTypes: [STRING, STRING, F64], - result: { kind: "promise", inner: VOID }, - }, - "fsp.mkdir": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - /** The fs/promises option/member tail the certs pipeline uses: mkdir's - * literal { recursive?, mode? } options (the mkdirSync matrix behind - * settled promises), unlink, chmod. Failures REJECT (catchable at the - * await), like the rest of the fsp family. */ - "fsp.mkdirMode": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, - "fsp.mkdirRecursive": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - "fsp.mkdirRecursiveMode": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, - "fsp.unlink": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - "fsp.chmod": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, - "fsp.chown": { argTypes: [STRING, F64, F64], result: { kind: "promise", inner: VOID } }, - "fsp.lchown": { argTypes: [STRING, F64, F64], result: { kind: "promise", inner: VOID } }, - "fsp.access": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, - "fsp.truncate": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, - "fsp.appendFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, - "fsp.appendFileBytes": { argTypes: [STRING, BYTES_U8], result: { kind: "promise", inner: VOID } }, - "fsp.copyFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, - "fsp.mkdtemp": { argTypes: [STRING], result: { kind: "promise", inner: STRING } }, - /** readFile(path, encoding | { encoding }) for any runtime encoding name. */ - "fsp.readFileEncoded": { argTypes: [STRING, STRING], result: { kind: "promise", inner: STRING } }, - "fsp.rmdir": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - "fsp.link": { argTypes: [DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fsp.symlink": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fsp.cp": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fsp.readlink": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "fsp.statfs": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, - "fsp.readlinkStr": { argTypes: [DYN, DYN], result: { kind: "promise", inner: STRING } }, - "fsp.readlinkBuffer": { argTypes: [DYN, DYN], result: { kind: "promise", inner: BYTES_U8 } }, - "fsp.utimes": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fsp.lutimes": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, - "fsp.rename": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, - "fsp.readdir": { argTypes: [STRING], result: { kind: "promise", inner: arrayOf(STRING) } }, - // Result is promise; the libCall case - // validates the program-dependent record shape in validate.ts. - /** `fs.promises.readdir(path, { withFileTypes: true })` — the settled- - * promise twin of fs.readdirTypesSync. The backends assemble the same - * call-site-shaped Dirent rows, then move the array into a promise; - * scandir failure becomes its rejection. */ - "fsp.readdirTypes": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - "fsp.rm": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, - /** `fs.promises.rm(path, options)`: fs.rmOptsSync and fs.rmRetrySync - * behind a settled promise; their errors become its rejection. */ - "fsp.rmOpts": { argTypes: [STRING, BOOL, BOOL], result: { kind: "promise", inner: VOID } }, - "fsp.rmRetry": { - argTypes: [STRING, BOOL, BOOL, F64, F64], - result: { kind: "promise", inner: VOID }, - }, - "fsp.stat": { argTypes: [STRING], result: { kind: "promise", inner: STATS_T } }, - "fsp.realpath": { argTypes: [STRING], result: { kind: "promise", inner: STRING } }, - "fsp.lstat": { argTypes: [STRING], result: { kind: "promise", inner: STATS_T } }, - /** fs/promises.open and the statically represented FileHandle surface. - * Every operation returns an already-settled promise; syscall failures - * become rejections rather than escaping synchronously. read/write - * results are call-site record shapes ({ bytesRead/bytesWritten, - * buffer }) assembled by the backends around the fixed runtime ABI. */ - "fsp.open": { argTypes: [STRING, STRING, F64], result: { kind: "promise", inner: FILEHANDLE_T } }, - "fileHandle.fd": { argTypes: [FILEHANDLE_T], result: F64 }, - "fileHandle.close": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, - "fileHandle.sync": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, - "fileHandle.datasync": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, - "fileHandle.truncate": { - argTypes: [FILEHANDLE_T, F64], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.chmod": { argTypes: [FILEHANDLE_T, F64], result: { kind: "promise", inner: VOID } }, - "fileHandle.utimes": { - argTypes: [FILEHANDLE_T, DYN, DYN], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.readv": { - argTypes: [FILEHANDLE_T, arrayOf(BYTES_U8), F64], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.writev": { - argTypes: [FILEHANDLE_T, arrayOf(BYTES_U8), F64], - result: { kind: "promise", inner: VOID }, - }, - // read/write carry call-site result record shapes; the validator checks - // those in validate.ts, so promise is only a table sentinel. - "fileHandle.read": { - argTypes: [FILEHANDLE_T, BYTES_U8, F64, F64, F64, BOOL], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.writeBytes": { - argTypes: [FILEHANDLE_T, BYTES_U8, F64, F64, F64, BOOL], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.writeStr": { - argTypes: [FILEHANDLE_T, STRING, F64, STRING], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.readFile": { - argTypes: [FILEHANDLE_T, STRING], - result: { kind: "promise", inner: STRING }, - }, - "fileHandle.readFileBytes": { - argTypes: [FILEHANDLE_T, STRING], - result: { kind: "promise", inner: BYTES_U8 }, - }, - "fileHandle.writeFile": { - argTypes: [FILEHANDLE_T, STRING, STRING], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.writeFileBytes": { - argTypes: [FILEHANDLE_T, BYTES_U8, STRING], - result: { kind: "promise", inner: VOID }, - }, - "fileHandle.stat": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: STATS_T } }, - "process.argv": { argTypes: [], result: arrayOf(STRING) }, - // The Node runtime flag a node:test binary took out of process.argv - // (--test-update-snapshots), or [] — a fresh array per read. - "process.execArgv": { argTypes: [], result: arrayOf(STRING) }, - "process.platform": { argTypes: [], result: STRING }, - // The one libCall whose result type is program-dependent (union ids are - // per-module): the `result` here is a placeholder — the libCall case - // checks the union's ARMS ([string, undefinedT] in canonical order) - // against the module's registry instead. - /** getenv(3): one string key arg → the interned `string | undefined` - * union (present: +1 string wrapped into the string arm; absent: the - * interned undefined-arm instance). BOTH source forms — `process.env.FOO` - * and `process.env[expr]` — lower here. Purely static; never throws. */ - "process.envGet": { argTypes: [STRING], result: VOID }, - /** setenv(3): (name, value) string args → void. Later envGet reads and - * spawned children observe the write, like Node (values are strings — - * the frontend fences non-string RHS). Never throws. */ - "process.envSet": { argTypes: [STRING, STRING], result: VOID }, - /** `delete process.env[NAME]` — unsetenv(3): the mutation is visible to - * every later read (process.envGet asks getenv fresh) and inherited by - * spawned children, exactly Node. Statement position only (JS's boolean - * result is constant true there). Borrows the name; never throws. */ - "process.envUnset": { argTypes: [STRING], result: VOID }, - /** The whole environment as alternating [k0, v0, k1, v1, ...] strings in - * environ order — the raw material of the process.env SNAPSHOT record - * (the frontend's interned %env.snapshot helper keyed-writes the pairs - * into a fresh `{ [k: string]: string | undefined }` record). Fresh +1 - * array; never throws. */ - "process.envPairs": { argTypes: [], result: arrayOf(STRING) }, - "process.exit": { argTypes: [F64], result: VOID }, - /** Numeric process.exitCode write (integer validation, implicit exit status). */ - "process.setExitCode": { argTypes: [F64], result: VOID }, - /** The process.exitCode write of a program that also reads the - * property: the same validation, and the code counts as assigned. */ - "process.setExitCodeTracked": { argTypes: [F64], result: VOID }, - /** The code for process.exit() with no argument, or zero when unset. */ - "process.currentExitCode": { argTypes: [], result: F64 }, - /** Whether process.exitCode reads a code rather than undefined. Never throws. */ - "process.hasExitCode": { argTypes: [], result: BOOL }, - "process.cwd": { argTypes: [], result: STRING }, - /** getpid(2) / getuid(2): zero args → f64. POSIX-only target, so both - * always answer (the checker's `getuid?` optionality covers Windows — - * `process.getuid?.()` lowers as the plain call). Never throw. */ - "process.pid": { argTypes: [], result: F64 }, - "dyn.this": { argTypes: [], result: DYN }, - "dyn.generatorThis": { argTypes: [], result: DYN }, - "process.getuid": { argTypes: [], result: F64 }, - "process.getgid": { argTypes: [], result: F64 }, - /** geteuid(2) / getegid(2) / getgroups(2) (the effective gid appended - * when the list lacks it, like Node). Windows has no such members: the - * calls throw the TypeError a missing member does (MAY THROW). */ - "process.geteuid": { argTypes: [], result: F64 }, - "process.getegid": { argTypes: [], result: F64 }, - "process.getgroups": { argTypes: [], result: arrayOf(F64) }, - /** process.ppid: the parent's pid at the read. Never throws. */ - "process.ppid": { argTypes: [], result: F64 }, - /** process.title: libuv's process title (argv[0] until set); setTitle - * writes it (truncated to the original argument area on POSIX, the - * console title on Windows). Never throw. */ - "process.title": { argTypes: [], result: STRING }, - "process.setTitle": { argTypes: [STRING], result: VOID }, - /** process.argv0: the original argv[0]. Never throws. */ - "process.argv0": { argTypes: [], result: STRING }, - /** process.release: the compatibility target's stable release object - * (name, lts, sourceUrl, headersUrl, and libUrl on Windows). +1. */ - "process.release": { argTypes: [], result: DYN }, - /** process.execPath: the compiled binary's own resolved absolute path - * (one interned string, +1 per read) — the honest answer where Node's - * is the node executable's (SEMANTICS.md divergence 12). Never throws. */ - "process.execPath": { argTypes: [], result: STRING }, - /** process.arch: the compiled binary's OWN architecture ("arm64", - * "x64") — Node's answer for its own build on the same machine. - * Interned; +1 per read. Never throws. */ - "process.arch": { argTypes: [], result: STRING }, - /** Stable native version dictionary. node/openssl identify compatibility - * targets; components absent from the native runtime are omitted. */ - "process.versions": { argTypes: [], result: DYN }, - "process.builtinId": { argTypes: [DYN, arrayOf(STRING)], result: STRING }, - "process.builtinModule": { argTypes: [STRING, DYN], result: DYN }, - "process.builtinUnsupported": { argTypes: [STRING, STRING], result: DYN }, - /** A module namespace object as a checked-dynamic value - * (scr_module_namespace_value): the module id, its sorted export names - * and the compiled getter. One object per module. Never throws. */ - "module.namespaceValue": { argTypes: [STRING, arrayOf(STRING), DYN], result: DYN }, - "fs.callbackValue": { argTypes: [STRING], result: DYN }, - "fs.callbackCall": { argTypes: [STRING, DYN], result: DYN }, - "process.hrtimeValue": { argTypes: [], result: DYN }, - /** process.versions.node: the runtime's Node COMPATIBILITY TARGET — - * there is no Node under the binary, so this reports the version whose - * semantics the runtime implements (SEMANTICS.md divergence 60, the - * execPath stance). Interned; +1 per read. Never throws. */ - "process.versionsNode": { argTypes: [], result: STRING }, - "process.versionsOpenssl": { argTypes: [], result: STRING }, - /** kill(2) with Node's exact semantics and error shapes: the pid must be - * an int32 (else the ERR_INVALID_ARG_TYPE TypeError text), the named - * form resolves Node's signal-name table (unknown names throw the - * ERR_UNKNOWN_SIGNAL TypeError), an omitted signal completes to - * "SIGTERM" in the frontend, signal 0 probes, and a kill(2) failure - * throws Node's `kill ESRCH`/`kill EPERM` Error. Result is Node's - * constant true. */ - "process.kill": { argTypes: [F64, STRING], result: BOOL }, - "process.killNum": { argTypes: [F64, F64], result: BOOL }, - /** The raw byte writes: one borrowed string arg → bool (constantly true - * — this synchronous runtime never queues backpressure). stdoutWrite - * shares console.log's stream, each call is submitted promptly, and - * interleaved output keeps source order. No newline, no formatting, - * never throws. */ - "process.stdoutWrite": { argTypes: [STRING], result: BOOL }, - "process.stdio": { argTypes: [F64], result: DYN }, - "process.stderrWrite": { argTypes: [STRING], result: BOOL }, - // error.new's result and the receiver slots are builtin-error classes — - // program-dependent object types, checked in the libCall case. - /** The runtime-provided Error hierarchy's entry points (scr_error.c). - * error.new: one borrowed string arg (the message), result an owned (+1) - * builtin error instance — the result TYPE names which builtin class - * (backends derive the runtime kind from it). error.ctor: the - * super(message) call of an `extends Error` constructor — borrowed - * receiver (already allocated by the derived class's new) + borrowed - * message, void; the receiver's type names the builtin class whose name - * field to stamp. error.toString: borrowed `%Error`-typed receiver, +1 - * string in Node's "name: message" shape. Formatting can invoke a user - * message accessor, which can throw. */ - "error.new": { argTypes: [STRING], result: VOID }, - /** ECMAScript constructors with raw checked-dynamic message/options. - * Options retain cause presence; constructor calls borrow their args. - * Message coercion may throw before the cause is installed. */ - "error.newOptions": { argTypes: [DYN, DYN], result: VOID }, - "error.ctorOptions": { argTypes: [null, DYN, DYN], result: VOID }, - /** Borrowed Error receiver. cause returns an owned dyn value (undefined - * when absent); hasCause distinguishes absence from present undefined. */ - "error.cause": { argTypes: [null], result: DYN }, - "error.hasCause": { argTypes: [null], result: BOOL }, - /** Assignment borrows both operands and retains the new cause. */ - "error.setCause": { argTypes: [null, DYN], result: VOID }, - /** `err.code = s` on an error-hierarchy receiver: the code slot that - * error.code reads and coded throws stamp. Borrows both; never throws. */ - "error.setCode": { argTypes: [null, STRING], result: VOID }, - /** `err.code = v` with a value that is not statically a string: strings - * take the code slot, anything else clears it and sets the dynamic own - * property. Borrows both; never throws. */ - "error.setCodeDyn": { argTypes: [null, DYN], result: VOID }, - /** `Object.assign(err, ...sources)` onto an error-hierarchy receiver - * (args: the error, a dyn array of the sources): the own-member copy - * onto its checked-dynamic node, then the typed slots (name, message, - * stack, cause, code) re-read from it. Throws what the copy throws. */ - "error.assign": { argTypes: [null, DYN], result: VOID }, - /** `key in err` for a key no compiled class of the receiver declares - * (args: the error, the key): a runtime-set own property (Object.assign, - * throw-site fields, dynamic writes) or an Object.prototype member. */ - "error.hasKey": { argTypes: [null, STRING], result: BOOL }, - "error.hasCode": { argTypes: [null], result: BOOL }, - /** Whether the error's own `name` or `message` (the key) is an accessor - * a reflective definition installed. Borrows both; never throws. */ - "error.ownAccessor": { argTypes: [null, STRING], result: BOOL }, - /** That accessor's getter called on the error (owned result), and its - * setter called with the value (V8's TypeError without one). Borrow - * their arguments; throw what the accessor throws. */ - "error.accessorGet": { argTypes: [null, STRING], result: DYN }, - "error.accessorSet": { argTypes: [null, STRING, DYN], result: VOID }, - "error.defineCause": { argTypes: [null, DYN], result: VOID }, - "error.deleteCause": { argTypes: [null], result: VOID }, - /** The compiler-resolved Node-parity throw for always-throwing lowered - * arms (ERR_INVALID_THIS receivers, ERR_MISSING_ARGS arity ladders, - * the symbol-to-string TypeError): args are [error-kind f64 (the - * SCR_ERR_* index: 0 Error, 1 TypeError, 2 RangeError), code (empty = - * no code slot), message]. ALWAYS THROWS catchably; the result type is - * the replaced expression's own (never materialized — the - * global.undefRead pattern). May-throw seed. */ - "error.nodeThrow": { argTypes: [F64, STRING, STRING], result: VOID }, - /** JS ToString over a dyn value WITH the object protocol (a user - * toString/valueOf member is CALLED and its throw propagates; - * exhaustion throws "Cannot convert object to primitive value"; units - * render "null"/"undefined") — the WHATWG USVString conversions - * (URLSearchParams names/values). Borrowed dyn; +1 string. May-throw. */ - "dyn.toStringCoerce": { argTypes: [DYN], result: STRING }, - "dyn.stringConstructor": { argTypes: [DYN], result: STRING }, - "dyn.propertyKey": { argTypes: [DYN], result: DYN }, - "dyn.toNumeric": { argTypes: [DYN], result: DYN }, - "dyn.increment": { argTypes: [DYN, BOOL], result: DYN }, - /** JS ToNumber over a dyn value WITH the object protocol (number-hint - * valueOf/toString ordering; user throws propagate). Borrowed dyn; - * f64 result, or a throw. Used by statically lowered numeric coercions - * whose checker type remained any. */ - "dyn.numberConstructor": { argTypes: [DYN], result: F64 }, - "dyn.bigintConstructor": { argTypes: [DYN], result: BIGINT_T }, - "dyn.toNumberCoerce": { argTypes: [DYN], result: F64 }, - "dyn.add": { argTypes: [DYN, DYN], result: DYN }, - "dyn.arithmetic": { argTypes: [DYN, DYN, STRING], result: DYN }, - "dyn.compare": { argTypes: [DYN, DYN, STRING], result: BOOL }, - "dyn.bitwise": { argTypes: [DYN, DYN, STRING], result: DYN }, - "dyn.proxyNew": { argTypes: [DYN, DYN], result: DYN }, - /* Dynamic-tier values (jsval; two-tier design stage S3). The jsval pass - * (ir/jsval.ts) introduces these in dynamic-tier functions only; the - * runtime is scr_js_value.c. Arguments are borrowed and results owned, - * like every libCall. Boundary conversions: js.fromDyn (a cell keeps its - * identity), js.toDyn (an immediate is boxed), js.fromF64/fromBool/ - * fromStr/fromBigInt (the design's toJs: immediates, and strings and - * BigInts under a tag without a copy) and js.toF64 (fromJs to number: dynCheck's - * number check and TypeError). The operators are the dyn.* operators - * over jsvals, with identical JS semantics. */ - "js.fromDyn": { argTypes: [DYN], result: JSVAL }, - "js.toDyn": { argTypes: [JSVAL], result: DYN }, - "js.fromF64": { argTypes: [F64], result: JSVAL }, - "js.fromBool": { argTypes: [BOOL], result: JSVAL }, - "js.fromStr": { argTypes: [STRING], result: JSVAL }, - "js.fromBigInt": { argTypes: [BIGINT_T], result: JSVAL }, - "js.toF64": { argTypes: [JSVAL], result: F64 }, - "js.add": { argTypes: [JSVAL, JSVAL], result: JSVAL }, - "js.arithmetic": { argTypes: [JSVAL, JSVAL, STRING], result: JSVAL }, - "js.bitwise": { argTypes: [JSVAL, JSVAL, STRING], result: JSVAL }, - "js.compare": { argTypes: [JSVAL, JSVAL, STRING], result: BOOL }, - "js.toNumeric": { argTypes: [JSVAL], result: JSVAL }, - "js.increment": { argTypes: [JSVAL, BOOL], result: JSVAL }, - "js.toNumberCoerce": { argTypes: [JSVAL], result: F64 }, - // Always throws; the result is the READ's declared type (a typed dummy - // the unwind abandons) — the libCall case skips the result check. - /** A read of a `declare`d const NOTHING defines (the bundler-define - * pattern — __VERSION__): always throws the catchable ReferenceError - * Node raises at the access (" is not defined"). args[0] is the - * name; the result type is the read's declared type (a typed dummy the - * unwind abandons — the value never exists). */ - "global.undefRead": { argTypes: [STRING], result: VOID }, - "console.native": { argTypes: [], result: DYN }, - /** node:console Console instances (scr_console_native.c): new mints the - * handle over the stdout/stderr fds (1/2; 0 = a stream binds next) and - * the ignoreErrors flag; bind attaches a borrowed writable stream to - * target 0 (stdout) or 1 (stderr); write prints `text\n` to the - * selected target of an instance or the global console handle. */ - "console.new": { argTypes: [F64, F64, BOOL], result: DYN }, - "console.bind": { argTypes: [DYN, F64, null], result: VOID }, - "console.write": { argTypes: [DYN, STRING, BOOL], result: VOID }, - /** The global console's remaining members (scr_console_native.c; the - * group indentation itself lives with the writers in scr_console.c). - * Arguments are borrowed; string results are owned. noop is the - * emitter-dropped tail of a lowering whose effects sit in statements. - * label is `${label}` over a defaulted label (undefined -> "default"; - * a Symbol throws Node's TypeError). count prints "label: n". - * countReset answers the warning text for an unknown (or non-string) - * label, "" otherwise. time answers the duplicate-label warning text - * or ""; timeEnd/timeLog answer Node's formatted elapsed time, or "" - * for an unknown label (timeEnd drops the timer). group adds (+1) or - * removes (-1) one two-space indentation step. clear writes the - * cursor-home and erase-down sequences when stdout is a terminal. - * table renders and prints Node's table for a dyn value (undefined - * properties = all). dirDyn is console.dir's inspect of a dyn value - * (custom inspection off: a Buffer anywhere inside throws the fence). */ - "console.noop": { argTypes: [], result: VOID }, - "console.label": { argTypes: [DYN], result: STRING }, - "console.count": { argTypes: [STRING], result: VOID }, - "console.countReset": { argTypes: [DYN], result: STRING }, - "console.time": { argTypes: [STRING], result: STRING }, - "console.timeEnd": { argTypes: [STRING], result: STRING }, - "console.timeLog": { argTypes: [STRING], result: STRING }, - "console.group": { argTypes: [F64], result: VOID }, - "console.clear": { argTypes: [], result: VOID }, - "console.table": { argTypes: [DYN, DYN], result: VOID }, - "console.dirDyn": { argTypes: [DYN, F64], result: STRING }, - /** A native reference to the global object; known names retain value fences. */ - "global.native": { argTypes: [arrayOf(STRING)], result: DYN }, - // `X.name` through a class value: the arg is a program-dependent - // classval (a null slot; the libCall case checks the kind). - /** `X.name` through a class VALUE (scr_object.c): args[0] is a borrowed - * classval; the result is the class object's stored .name string, - * retained (+1 — the string is an interned immortal, so the retain is a - * no-op, kept for ownership uniformity). Never throws. A direct - * `C.name` on the class name itself folds to a strLit instead. */ - "class.name": { argTypes: [null], result: STRING }, - "error.ctor": { argTypes: [null, STRING], result: VOID }, - "error.toString": { argTypes: [null], result: STRING }, - /** SameValue over checked values, including NaN, signed zero and reference identity. */ - "dyn.sameValue": { argTypes: [DYN, DYN], result: BOOL }, - "error.stack": { argTypes: [null], result: STRING }, - "error.setStack": { argTypes: [null, STRING], result: VOID }, - "error.stackLimitGet": { argTypes: [], result: F64 }, - "error.stackLimitSet": { argTypes: [F64], result: VOID }, - // Receiver (any error-hierarchy object) and the program-dependent - // `string | undefined` result are checked in the libCall case. - /** NodeJS.ErrnoException's `.code` read: borrowed error-hierarchy - * receiver → the interned `string | undefined` union (type-directed - * construction in the backend, the process.envGet pattern) — the errno - * name where a throw site stamped one (fs, exec spawn/timeout, - * process.kill, the spawn 'error' event), the undefined arm everywhere - * else. Never throws. */ - "error.code": { argTypes: [null], result: VOID }, - // DOMException: construction takes the two dyn args (WebIDL's - // resolution runs in the runtime); the %DOMException result is a - // program-dependent object type, checked in the libCall case. The - // read surface takes the %DOMException receiver (a null slot — the - // libCall case checks the class name). - /** `new DOMException(message?, nameOrOptions?)` (scr_error.c): both args - * are borrowed dyn values (the lowering passes the dyn undefined for an - * absent argument, so WebIDL's optionality lives in one place). The - * runtime ToStrings the message ("" for absent/undefined), resolves the - * name — absent/undefined → "Error", a non-null object → ToString of its - * `name` member plus the `cause` own-property record, anything else → - * ToString — and stamps the legacy code from the WebIDL name table (0 - * off-table). Result is an owned (+1) %DOMException. Never throws (dyn - * ToString is total). */ - "error.newDom": { argTypes: [DYN, DYN], result: VOID }, - /** DOMException's own read surface (scr_error.c; %DOMException receivers - * only — borrowed). domCode: the legacy numeric code. domHasCause: the - * options form's own-property record (`'cause' in e`). domCause: the - * cause value, +1 (the dyn undefined when absent — matching Node's - * undefined read). None throws. */ - "error.domCode": { argTypes: [null], result: F64 }, - "error.domHasCause": { argTypes: [null], result: BOOL }, - "error.domCause": { argTypes: [null], result: DYN }, - /** structuredClone of a %DOMException receiver (scr_error.c): WebIDL - * serialization — name/message copy, the legacy code re-derives, cause - * does not serialize. args are the borrowed receiver and the borrowed - * options dyn value (the dyn undefined when absent — the shared - * validation throws Node's exact option errors; any non-empty transfer - * list throws DataCloneError, nothing static is transferable). Result - * +1 %DOMException. */ - "error.domClone": { argTypes: [null, DYN], result: VOID }, - /** `new AggregateError(errors, message?, options?)` (scr_json.c), in - * spec order: newAggregate installs the message and cause (both dyn, - * borrowed — the message conversion can throw) and returns an owned - * %AggregateError; aggregateSetErrors then stores the iterated list - * (borrowed receiver, the dyn array borrowed and retained). - * aggregateErrors reads the list (+1, the same array every read). */ - "error.newAggregate": { - argTypes: [DYN, DYN], - result: { kind: "object", className: "%AggregateError" }, - }, - "error.aggregateSetErrors": { argTypes: [null, DYN], result: VOID }, - "error.aggregateErrors": { argTypes: [null], result: DYN }, - /** `new SuppressedError(error, suppressed, message?)` (scr_json.c): all - * three dyn and borrowed; the message conversion can throw. Returns an - * owned %SuppressedError. */ - "error.newSuppressed": { - argTypes: [DYN, DYN, DYN], - result: { kind: "object", className: "%SuppressedError" }, - }, - /** `d instanceof TypeError` (and the other BUILTIN error classes) on a - * checked-dynamic value (scr_json.c): the from_error cache holds the - * dyn↔error identity edge, so the test resolves the runtime error and - * asks its vtable's stamped preorder interval — exact for every error - * that crossed the boundary. A dyn object that never came from an - * error (a hand-built {%error} literal) answers false: subclass - * identity is unknowable there (the root keeps dynTest's marker - * answer). args are the borrowed dyn and the SCR_ERR_* kind literal. - * Never throws. */ - "dyn.errInstanceof": { argTypes: [DYN, F64], result: BOOL }, - "weakMap.is": { argTypes: [DYN], result: BOOL }, - "weakSet.is": { argTypes: [DYN], result: BOOL }, - "weakMap.new": { argTypes: [DYN], result: DYN }, - "weakSet.new": { argTypes: [DYN], result: DYN }, - "finalizationRegistry.new": { argTypes: [DYN], result: DYN }, - "finalizationRegistry.is": { argTypes: [DYN], result: BOOL }, - "dyn.fromEntries": { argTypes: [DYN], result: DYN }, - "bytes.constructor": { argTypes: [STRING], result: DYN }, - "bytes.instanceOf": { argTypes: [DYN, DYN, STRING], result: BOOL }, - "bytes.construct": { argTypes: [DYN, DYN, STRING], result: DYN }, - /** The ambient receiver — JS `this` in a plain (non-method) function - * body: the innermost binding the current firing/dispatch window - * pushed (Node's listener receiver, a dyn OBJ method's object, an - * apply/call thisArg), or the undefined dyn singleton with none bound - * (the strict-mode plain-call answer, the old constant). Zero args → - * dyn (+1). Never throws. */ - "dyn.dataViewIs": { argTypes: [DYN], result: BOOL }, - "dyn.construct": { argTypes: [DYN, DYN, STRING], result: DYN }, - "arrayBuffer.constructor": { argTypes: [], result: DYN }, - "sharedArrayBuffer.new": { argTypes: [DYN], result: DYN }, - "sharedArrayBuffer.is": { argTypes: [DYN], result: BOOL }, - "arrayBuffer.new": { argTypes: [DYN], result: DYN }, - "ffi.argument": { argTypes: [DYN, STRING], result: DYN }, - "ffi.memoryModule": { argTypes: [DYN], result: DYN }, - "intl.segmenterNew": { argTypes: [], result: DYN }, - "intl.segmenterRefusing": { argTypes: [STRING], result: DYN }, - "arrayBuffer.is": { argTypes: [DYN], result: BOOL }, - "arrayBuffer.isView": { argTypes: [DYN], result: BOOL }, - "arrayBuffer.byteLengthGetter": { argTypes: [], result: F64 }, - "arrayBuffer.byteLengthDescriptor": { argTypes: [DYN], result: DYN }, - "arrayBuffer.viewU8C": { argTypes: [DYN, DYN, DYN], result: bytesOf("u8c") }, - "arrayBuffer.viewI8": { argTypes: [DYN, DYN, DYN], result: bytesOf("i8") }, - "arrayBuffer.viewU16": { argTypes: [DYN, DYN, DYN], result: bytesOf("u16") }, - "arrayBuffer.viewI16": { argTypes: [DYN, DYN, DYN], result: bytesOf("i16") }, - "arrayBuffer.viewU8": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, - "arrayBuffer.viewU32": { argTypes: [DYN, DYN, DYN], result: bytesOf("u32") }, - "arrayBuffer.viewI32": { argTypes: [DYN, DYN, DYN], result: bytesOf("i32") }, - "arrayBuffer.viewF16": { argTypes: [DYN, DYN, DYN], result: bytesOf("f16") }, - "arrayBuffer.viewF32": { argTypes: [DYN, DYN, DYN], result: bytesOf("f32") }, - "arrayBuffer.viewF64": { argTypes: [DYN, DYN, DYN], result: bytesOf("f64") }, - "arrayBuffer.viewDV": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, - /** Object.keys/values/entries over a CHECKED-DYNAMIC receiver - * (scr_json.c): the runtime walks the dyn node's own members in JS - * own-key order (array-index keys ascending first, then insertion - * order) and answers a dyn array (entries: an array of [key, value] - * pairs; values RETAIN the member nodes — reference semantics, like - * JS). Strings/arrays/bytes answer their index keys; other scalars an - * empty array; null/undefined throw Node's catchable TypeError - * ("Cannot convert undefined or null to object"). */ - "dyn.objKeys": { argTypes: [DYN], result: DYN }, - "dyn.forInKeys": { argTypes: [DYN], result: DYN }, - "dyn.forInKeyStrings": { argTypes: [DYN], result: arrayOf(STRING) }, - "dyn.forInStable": { argTypes: [DYN], result: BOOL }, - "dyn.objKeyStrings": { argTypes: [DYN], result: arrayOf(STRING) }, - /** Object.entries as a native [string, any][] (VOID: the tuple row's - * shape is program-dependent; the validator checks it). */ - "dyn.objEntryPairs": { argTypes: [DYN], result: VOID }, - "dyn.hasOwn": { argTypes: [DYN, STRING], result: BOOL }, - "dyn.propertyIsEnumerable": { argTypes: [DYN, STRING], result: BOOL }, - "dyn.assign": { argTypes: [DYN, DYN], result: DYN }, - "dyn.copyDataProperties": { argTypes: [DYN, DYN], result: DYN }, - "dyn.objectRest": { argTypes: [DYN, DYN], result: DYN }, - /** Variadic Object.assign over CHECKED-DYNAMIC targets (`Object.assign( - * {}, ...arr.map(f), tail)` — the option-table merge): the lowering - * builds one fresh dyn pack of sources (packPush retains a plain source - * in; packPushSpread flattens a spread source through the spread-call - * walk — V8's exact TypeError texts, the string arg spelling the spread - * expression for the nullish form), so every source evaluates and - * flattens BEFORE any copying (JS's ArgumentListEvaluation), then - * assignAll copies each pack element's own enumerable keys onto the - * target left to right and answers the TARGET (+1) — identity, like JS. - * assignAll throws Node's ToObject TypeError on a nullish target. */ - "dyn.packPush": { argTypes: [DYN, DYN], result: VOID }, - "dyn.packPushSpread": { argTypes: [DYN, DYN, STRING], result: VOID }, - /** The iterated-path spread twin: V8 spells the optimized apply-path - * texts (packPushSpread's — the expression named for nullish sources) - * only for the SINGLE LAST argument's spread; any other spread position - * drives the real iterator protocol, whose failure describes the VALUE - * ("object null is not iterable (cannot read property - * Symbol(Symbol.iterator))"). The frontend picks by position. */ - "dyn.packPushSpreadIter": { argTypes: [DYN, DYN], result: VOID }, - "dyn.reflectApply": { argTypes: [DYN, DYN, DYN], result: DYN }, - "dyn.reflectGet": { argTypes: [DYN, DYN, DYN], result: DYN }, - /** `using` / `await using` with a dyn value (scr_json.c): usingMethod - * runs the declaration's checks and answers what usingDispose calls at - * scope exit (undefined for a null or undefined value). The bool selects - * `await using`. Both throw Node's TypeErrors or the method's error. */ - "dyn.usingMethod": { argTypes: [DYN, BOOL], result: DYN }, - "dyn.usingDispose": { argTypes: [DYN, DYN], result: DYN }, - /** DisposableStack (scr_symbol.c): a new stack handle; whether a value is - * one; and `stack.use(value)` for a class instance, registering the - * compiler's disposal function for it. The last throws Node's - * ReferenceError on a disposed stack. */ - "disposable.stackNew": { argTypes: [], result: DYN }, - "disposable.stackIs": { argTypes: [DYN], result: BOOL }, - "disposable.stackUseCallback": { argTypes: [DYN, DYN, DYN], result: BOOL }, - "dyn.reflectSet": { argTypes: [DYN, DYN, DYN, DYN], result: BOOL }, - "dyn.reflectDefine": { argTypes: [DYN, DYN, DYN], result: BOOL }, - "dyn.abstractEq": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.assignAll": { argTypes: [DYN, DYN], result: DYN }, + "math.trunc": { argTypes: [F64], result: F64 }, + "math.ceil": { argTypes: [F64], result: F64 }, + "math.sin": { argTypes: [F64], result: F64 }, + "math.sinh": { argTypes: [F64], result: F64 }, + "math.cos": { argTypes: [F64], result: F64 }, + "math.cosh": { argTypes: [F64], result: F64 }, + "math.tan": { argTypes: [F64], result: F64 }, + "math.tanh": { argTypes: [F64], result: F64 }, + "math.asin": { argTypes: [F64], result: F64 }, + "math.asinh": { argTypes: [F64], result: F64 }, + "math.acos": { argTypes: [F64], result: F64 }, + "math.acosh": { argTypes: [F64], result: F64 }, + "math.atan": { argTypes: [F64], result: F64 }, + "math.atanh": { argTypes: [F64], result: F64 }, + "math.cbrt": { argTypes: [F64], result: F64 }, + "math.clz32": { argTypes: [F64], result: F64 }, + /** Return -1, +1, or the original NaN/zero. In particular, -0 remains -0. */ + "math.sign": { argTypes: [F64], result: F64 }, + "math.exp": { argTypes: [F64], result: F64 }, + "math.expm1": { argTypes: [F64], result: F64 }, + "math.f16round": { argTypes: [F64], result: F64 }, + "math.fround": { argTypes: [F64], result: F64 }, + "math.sqrt": { argTypes: [F64], result: F64 }, + "math.log": { argTypes: [F64], result: F64 }, + "math.log1p": { argTypes: [F64], result: F64 }, + "math.log2": { argTypes: [F64], result: F64 }, + "math.log10": { argTypes: [F64], result: F64 }, + "math.atan2": { argTypes: [F64, F64], result: F64 }, + "math.pow": { argTypes: [F64, F64], result: F64 }, + "math.imul": { argTypes: [F64, F64], result: F64 }, + /** `Math.min(a, b)` / `Math.max(a, b)` — the two-argument scalar forms + * (scr_lib.c), JS-exact like the Arr folds: NaN poisons, max prefers +0 + * over -0 (min the reverse). C fmin/fmax are NOT these (they drop NaN). + * Never throw. */ + "math.min": { argTypes: [F64, F64], result: F64 }, + "math.max": { argTypes: [F64, F64], result: F64 }, + /** `Math.random()` — a uniform double in [0,1) with the spec's 53-bit + * granularity, drawn from arc4random_buf (the CSPRNG behind the crypto + * lowerings). Same distribution as Node, NECESSARILY different sequence + * (SEMANTICS.md 62 — no seeded sequence exists to match). Never throws. */ + "math.random": { argTypes: [], result: F64 }, + /** The static global parsers/tests (scr_string.c). num.parseInt is + * ECMA-262 19.2.5 exactly — JS whitespace, sign, ToInt32 radix (the + * frontend completes an omitted radix to 0 = the spec's "undefined": + * base 10 with the 0x hex escape), longest digit prefix, and the exact + * mathematical value correctly rounded (u64 fast path, bignum beyond — + * overflow is ±Infinity). num.isNaN is the NaN self-test on an + * already-number argument (tsc pins the argument to number, so no + * ToNumber coercion exists to model). Borrow; never throw. */ + "num.parseInt": { argTypes: [STRING, F64], result: F64 }, + /** ES parseFloat (scr_string.c): the longest StrDecimalLiteral prefix + * of the trimmed input (no hex, "Infinity" exact-case), NaN when none — + * ECMA-262 19.2.4 over a string argument (non-string arguments keep the + * fence: Node would ToNumber-coerce). Borrows; never throws. */ + "num.parseFloat": { argTypes: [STRING], result: F64 }, + /** `a.localeCompare(b)`: -1/0/1 in Node's root-locale collation for + * Latin-script text (scr_str_locale_compare). Never throws. */ + "str.localeCompare": { argTypes: [STRING, STRING], result: F64 }, + /** Annex B substr(start, length) with start ToNumber'd and length's + * presence; never throws. */ + "str.substr": { argTypes: [STRING, F64, F64, BOOL], result: STRING }, + /** toLocaleLowerCase/toLocaleUpperCase (scr_regex.c): the locales value + * as written, upper=true for toLocaleUpperCase. Throws for null and for + * locale forms with no native rules. */ + "str.localeCase": { argTypes: [STRING, DYN, BOOL], result: STRING }, + "str.replaceLiteral": { argTypes: [STRING, STRING, STRING, BOOL], result: STRING }, + // (subject, characters, NUL-separated replacement texts): the fused + // chain of global one-character replacements. + "str.replaceChars": { argTypes: [STRING, STRING, STRING], result: STRING }, + /* ToNumber(string) — ECMA-262 7.1.4.1 StringToNumber (scr_string.c): + * trim the JS StrWhiteSpace set, empty/whitespace-only → +0, then the + * whole span must be one StrNumericLiteral — signed decimal (Infinity + * included, strtod-over-validated-span correct rounding) or unsigned + * 0x/0o/0b (exact value, nearest-even; signed forms are NaN) — with + * any trailing garbage answering NaN. Number(aString), unary + on + * strings, and util.format %d over strings lower here. Borrows; never + * throws. */ + "num.fromString": { argTypes: [STRING], result: F64 }, + "num.isNaN": { argTypes: [F64], result: BOOL }, + /** The static URI component codecs (scr_string.c), ECMA-262 Encode/ + * Decode with the component sets over the runtime's UTF-8 strings. + * str.encodeUriComponent percent-encodes every byte outside the + * unreserved component set (ALPHA/DIGIT/- _ . ! ~ * ' ( )) as uppercase + * %XX — the spec's per-code-point UTF-8 encoding IS a byte scan here — + * and THROWS the spec's URIError ("URI malformed") on a lone surrogate. + * str.decodeUriComponent + * decodes %XX escapes bytewise (raw non-escape bytes copy through) and + * requires the escaped bytes to form strictly valid UTF-8 (overlong + * forms, surrogate code points, and >U+10FFFF refused, per UTF8-decode + * without replacement); bad hex or an invalid sequence THROWS the + * spec's URIError ("URI malformed"), catchable. Borrow; results +1. */ + "str.encodeUriComponent": { argTypes: [STRING], result: STRING }, + // The base64 globals: the argument is a dyn value (WebIDL ToString + // runs in the runtime); the zero-argument form always throws. + /** The WHATWG base64 globals (scr_string.c), Node-global since v16. + * Arguments are borrowed dyn values — WebIDL ToString runs in the + * runtime over the dyn kind (String(null) is "null": the html spec's + * coercion, which Node's atob(null) exercises). str.atob decodes + * forgiving-base64 (ASCII whitespace stripped, %4==0 strips up to two + * '=', %4==1 refuses, leftover bits discarded) into the latin1 code + * points as a string; a malformed input THROWS the catchable + * DOMException InvalidCharacterError ("The string to be decoded is not + * correctly encoded."). str.btoa encodes the string's code points as + * base64; any code point over U+00FF THROWS InvalidCharacterError + * ("Invalid character"). str.b64Missing is the zero-argument call of + * either: always throws Node's TypeError [ERR_MISSING_ARGS] "The + * \"input\" argument must be specified". Results +1. */ + "str.atob": { argTypes: [DYN], result: STRING }, + "str.btoa": { argTypes: [DYN], result: STRING }, + "str.b64Missing": { argTypes: [], result: STRING }, + "str.decodeUriComponent": { argTypes: [STRING], result: STRING }, + "str.decodeUri": { argTypes: [STRING], result: STRING }, + /** encodeURI: the same Encode() keeping the reserved set and '#' + * unescaped — throwing like the component encoder. Borrow; result +1. */ + "str.encodeUri": { argTypes: [STRING], result: STRING }, + /** RegExp.escape (ES2025): per-code-point EncodeForRegExpEscape — + * leading ASCII alphanumeric hex-escapes, syntax characters and '/' + * take a backslash, other punctuators/whitespace/line terminators + * hex-escape, the rest passes through. Total; borrows; result +1. */ + "regexp.escape": { argTypes: [STRING], result: STRING }, + /** Number.prototype formatters (scr_lib.c), JS-exact: + * num.toExponential is toExponential() with the spec's "as many digits + * as necessary"; num.toFixed0 is the non-throwing omitted-argument + * toFixed() fast path; num.toFixed implements an explicit fractionDigits + * with exact binary-value rounding and THROWS RangeError outside 0..100. + * Successful results +1. */ + "num.toExponential": { argTypes: [F64], result: STRING }, + "num.toFixed0": { argTypes: [F64], result: STRING }, + "num.toFixed": { argTypes: [F64, F64], result: STRING }, + /** toPrecision(precision) with the argument ToNumber'd and its presence + * (undefined answers ToString); toExponential with a DEFINED + * fractionDigits. Both throw V8's RangeError for out-of-range digits. */ + "num.toPrecision": { argTypes: [F64, F64, BOOL], result: STRING }, + "num.toExponentialDigits": { argTypes: [F64, F64], result: STRING }, + "num.toStringRadix": { argTypes: [F64, DYN], result: STRING }, + /** toString(radix) with a number radix (scr_lib.c): truncates it as + * ToIntegerOrInfinity does, THROWS RangeError outside 2..36. */ + "num.toStringRadixF64": { argTypes: [F64, F64], result: STRING }, + /** Object.is over two numbers — the spec's SameValue on doubles: NaN + * equals NaN, +0 differs from -0, everything else is `===`. Plain bool + * result; never throws. (Union-armed operands take unionEq's sameValue + * flag instead — this is the both-f64 fast path.) */ + "num.sameValue": { argTypes: [F64, F64], result: BOOL }, + /** `new Intl.NumberFormat("en-US").format(x)` and + * `x.toLocaleString("en-US")` with DEFAULT options — the one locale + * whose data the runtime embeds (Node's default-build locale): decimal + * notation, 0–3 fraction digits rounded half-up on the SHORTEST + * round-tripping decimal (ICU's rounding input, probed vs Node — + * format(1.0005) is "1.001" though toFixed(3) answers "1.000"), "," + * grouping every three integer digits, "∞"/"NaN" texts, and "-0" for + * negative inputs rounding to zero. Result +1; never throws. */ + "intl.numFormatEnUs": { argTypes: [F64], result: STRING }, + /** ES Symbol values (scr_symbol.c — link-gated by moduleUsesSymbol). + * sym.new: `Symbol(desc)` — a fresh runtime-unique identity (+1) whose + * one arg is the description string (borrowed); sym.newAnon is the + * description-less `Symbol()` form. sym.for: the Symbol.for global + * registry — one interned symbol per key (borrowed), +1 on every call. + * sym.toString: "Symbol(desc)" (+1 string; "Symbol()" when absent). + * sym.desc / sym.keyFor answer the interned `string | undefined` union + * (the runtime returns +1-or-NULL; the backend builds the union arms — + * the child.stdout pattern). None of these throw. */ + "sym.new": { argTypes: [STRING], result: SYMBOL_T }, + "sym.newAnon": { argTypes: [], result: SYMBOL_T }, + "sym.for": { argTypes: [STRING], result: SYMBOL_T }, + "sym.wellKnown": { argTypes: [STRING], result: SYMBOL_T }, + // Result is the interned `string | undefined` union — the libCall case + // checks the arms (the spawnRes.error pattern). + "sym.keyFor": { argTypes: [SYMBOL_T], result: VOID }, + "sym.desc": { argTypes: [SYMBOL_T], result: VOID }, + "sym.toString": { argTypes: [SYMBOL_T], result: STRING }, + /** Engine-free arbitrary-precision bigint operations (scr_bigint.c). + * parse accepts the ECMAScript BigInt string grammar; fromF64 accepts + * finite integral Numbers. Arithmetic returns fresh immutable values. + * cmp returns -1/0/1 as f64; toString accepts a numeric radix. */ + "bigint.parse": { argTypes: [STRING], result: BIGINT_T }, + "bigint.fromF64": { argTypes: [F64], result: BIGINT_T }, + "bigint.neg": { argTypes: [BIGINT_T], result: BIGINT_T }, + "bigint.not": { argTypes: [BIGINT_T], result: BIGINT_T }, + "bigint.add": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.sub": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.mul": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.div": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.mod": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.pow": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.and": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.or": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.xor": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.shl": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.shr": { argTypes: [BIGINT_T, BIGINT_T], result: BIGINT_T }, + "bigint.eq": { argTypes: [BIGINT_T, BIGINT_T], result: BOOL }, + "bigint.eqString": { argTypes: [BIGINT_T, STRING], result: BOOL }, + "bigint.cmp": { argTypes: [BIGINT_T, BIGINT_T], result: F64 }, + "bigint.cmpNumber": { argTypes: [BIGINT_T, F64], result: F64 }, + "bigint.truthy": { argTypes: [BIGINT_T], result: BOOL }, + "bigint.toString": { argTypes: [BIGINT_T, F64], result: STRING }, + "bigint.inspect": { argTypes: [BIGINT_T], result: STRING }, + "bigint.toF64": { argTypes: [BIGINT_T], result: F64 }, + "bigint.asUintN": { argTypes: [F64, BIGINT_T], result: BIGINT_T }, + "bigint.asIntN": { argTypes: [F64, BIGINT_T], result: BIGINT_T }, + "bigint.bufferRead": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: BIGINT_T }, + "bigint.bufferWrite": { argTypes: [BYTES_U8, BIGINT_T, F64, BOOL, BOOL], result: F64 }, + "bigint.dataViewGet": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: BIGINT_T }, + "bigint.dataViewSet": { argTypes: [BYTES_U8, F64, BIGINT_T, BOOL], result: VOID }, + /** node:url + the URL class (scr_url.c). url.new parses one absolute + * URL string into a mutable URL value (+1) — invalid input THROWS a + * catchable TypeError ("Invalid URL"), like Node's constructor. The + * getters (borrowed receiver, +1 string) never throw; url.href doubles + * as toString(). fileURLToPath has one libFn per receiver form (URL + * value / string) — both THROW Node's TypeErrors on non-file schemes, + * encoded slashes, and non-empty hosts. url.pathToFileURL resolves the + * path (getcwd) and never throws. */ + "url.new": { argTypes: [STRING], result: URL_T }, + "url.newBase": { argTypes: [STRING, STRING], result: URL_T }, + /** Factories consume already-converted input/base strings. canParse + * answers a boolean; parse returns a checked native URL or null (+1). + * Parser failures are suppressed; conversion occurs before these calls. */ + "url.canParse": { argTypes: [STRING], result: BOOL }, + "url.canParseBase": { argTypes: [STRING, STRING], result: BOOL }, + "url.parse": { argTypes: [STRING], result: DYN }, + "url.parseBase": { argTypes: [STRING, STRING], result: DYN }, + "url.protocol": { argTypes: [URL_T], result: STRING }, + "url.origin": { argTypes: [URL_T], result: STRING }, + "url.username": { argTypes: [URL_T], result: STRING }, + "url.password": { argTypes: [URL_T], result: STRING }, + "url.host": { argTypes: [URL_T], result: STRING }, + "url.hostname": { argTypes: [URL_T], result: STRING }, + "url.port": { argTypes: [URL_T], result: STRING }, + "url.pathname": { argTypes: [URL_T], result: STRING }, + "url.href": { argTypes: [URL_T], result: STRING }, + /** Selected component writes borrow the receiver and converted string. + * href replaces its parsed fields while retaining cached params identity. */ + "url.set": { argTypes: [URL_T, STRING, STRING], result: VOID }, + "url.setChecked": { argTypes: [URL_T, STRING, DYN], result: VOID }, + "url.fileURLToPathUrl": { argTypes: [URL_T], result: STRING }, + /** fileURLToPathUrl as a value: the path string, or the error it would + * throw (fs.existsSync and fs.promises URL paths). Never throws. */ + "url.fileURLToPathValue": { argTypes: [URL_T], result: DYN }, + /** getValidatedPath's null-byte check on a string path, and + * readFileSync's encoding check: what Node validates before it converts + * a later URL path. (path, argument name) and (encoding). */ + "fs.checkPath": { argTypes: [STRING, STRING], result: VOID }, + "fs.checkReadEncoding": { argTypes: [STRING], result: VOID }, + "url.fileURLToPathChecked": { argTypes: [DYN], result: STRING }, + "url.fileURLToPathOptions": { argTypes: [DYN, DYN], result: STRING }, + /** node:url's legacy API (scr_url_legacy.c): parse(url, parseQueryString, + * slashesDenoteHost) answers the checked-dynamic Url instance; format + * takes the urlObject (a Url-shaped object, a string, or a URL) and the + * URL form's options; resolve takes (from, to). The trailing bool says + * whether the call site emits DEP0169. domainToASCII/domainToUnicode + * take the domain and whether it was omitted. Node's validation errors + * throw catchably. */ + "url.legacyParse": { argTypes: [DYN, DYN, DYN, BOOL], result: DYN }, + "url.legacyFormat": { argTypes: [DYN, DYN, BOOL], result: STRING }, + "url.legacyResolve": { argTypes: [DYN, DYN, BOOL], result: STRING }, + "url.domainToASCII": { argTypes: [DYN, BOOL], result: STRING }, + "url.domainToUnicode": { argTypes: [DYN, BOOL], result: STRING }, + /** url.urlToHttpOptions over a checked-dynamic argument: a URL answers + * Node's null-prototype options object; a primitive or null throws + * ERR_INVALID_ARG_TYPE. */ + "url.toHttpOptions": { argTypes: [DYN], result: DYN }, + /** node:punycode (scr_url.c): punycode.js 2.1.0's string conversions; + * decode/toUnicode (and, past 32-bit deltas, encode/toASCII) throw the + * module's RangeErrors, ucs2Encode String.fromCodePoint's. warn marks the + * module's first load (scr_async_dyn.c) and reports DEP0040 then unless + * the second argument is false: at once for a static ES import, on the + * next tick for a require() call. */ + "punycode.decode": { argTypes: [STRING], result: STRING }, + "punycode.encode": { argTypes: [STRING], result: STRING }, + "punycode.toASCII": { argTypes: [STRING], result: STRING }, + "punycode.toUnicode": { argTypes: [STRING], result: STRING }, + "punycode.ucs2Decode": { argTypes: [STRING], result: arrayOf(F64) }, + "punycode.ucs2Encode": { argTypes: [arrayOf(F64)], result: STRING }, + "punycode.warn": { argTypes: [BOOL, BOOL], result: VOID }, + "url.fileURLToPathBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 }, + "url.pathToFileURLChecked": { argTypes: [DYN, DYN], result: URL_T }, + "url.fileURLToPathStr": { argTypes: [STRING], result: STRING }, + "url.pathToFileURL": { argTypes: [STRING], result: URL_T }, + /** Explicit path syntax from the options.windows argument, independent + * of the executable's host platform. Windows UNC input can throw. */ + "url.pathToFileURLPlatform": { argTypes: [STRING, BOOL], result: URL_T }, + /** pathToFileURL under a win32 TARGET: the same scr_url_from_path call + * (the runtime selects the win32 arm by _WIN32), but a distinct IR name + * because that arm THROWS for malformed UNC inputs — may-throw seeds on + * it while posix pathToFileURL emission stays byte-identical. */ + "url.pathToFileURLWin32": { argTypes: [STRING], result: URL_T }, + /** URLSearchParams (scr_url.c — always linked with the url unit). + * Construction: sp.new (empty), sp.parse (one borrowed init string — + * a single leading '?' strips, Node's constructor), sp.copy (snapshot + * of another list), sp.fromPairs (a string[][] value — THROWS Node's + * ERR_INVALID_TUPLE TypeError on a non-[name, value] row; the one + * throwing entry in the family), sp.with (the record-literal init + * desugar: append one pair, answer the same list +1 — chains fold + * `new URLSearchParams({...})` into nested calls). url.searchParams + * answers the URL's LIVE cached view (+1, one identity per URL); + * url.search is the WHATWG search getter ("" for no/empty query). + * Methods mirror the WHATWG surface: sp.get answers +1-or-NULL (the + * sym.desc union pattern, null arm), sp.getAll a fresh string[]; + * sp.append/sp.set/sp.delete/sp.deleteValue/sp.sort mutate and + * re-serialize a live view's URL query; sp.has/sp.hasValue answer + * bools; sp.size/sp.toString are pure reads. sp.keyAt/sp.valAt are + * the for-of/forEach desugar's index reads (live — the loop re-reads + * sp.size each pass). */ + "sp.new": { argTypes: [], result: SEARCH_PARAMS_T }, + "sp.newChecked": { argTypes: [DYN], result: SEARCH_PARAMS_T }, + "sp.parse": { argTypes: [STRING], result: SEARCH_PARAMS_T }, + "sp.copy": { argTypes: [SEARCH_PARAMS_T], result: SEARCH_PARAMS_T }, + // The pairs argument is string[][] — checked structurally in validate.ts (the + // generic array-of slot has no named constant here). + "sp.fromPairs": { argTypes: [null], result: SEARCH_PARAMS_T }, + "sp.with": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: SEARCH_PARAMS_T }, + "url.searchParams": { argTypes: [URL_T], result: SEARCH_PARAMS_T }, + "url.search": { argTypes: [URL_T], result: STRING }, + "url.hash": { argTypes: [URL_T], result: STRING }, + // Result is the interned `string | null` union — the libCall case + // checks the arms (the spawnRes.signal pattern). + "sp.get": { argTypes: [SEARCH_PARAMS_T, STRING], result: VOID }, + "sp.getAll": { argTypes: [SEARCH_PARAMS_T, STRING], result: arrayOf(STRING) }, + "sp.append": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, + "sp.set": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, + "sp.delete": { argTypes: [SEARCH_PARAMS_T, STRING], result: VOID }, + "sp.deleteValue": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: VOID }, + "sp.has": { argTypes: [SEARCH_PARAMS_T, STRING], result: BOOL }, + "sp.hasValue": { argTypes: [SEARCH_PARAMS_T, STRING, STRING], result: BOOL }, + "sp.sort": { argTypes: [SEARCH_PARAMS_T], result: VOID }, + "sp.size": { argTypes: [SEARCH_PARAMS_T], result: F64 }, + "sp.toString": { argTypes: [SEARCH_PARAMS_T], result: STRING }, + "sp.keyAt": { argTypes: [SEARCH_PARAMS_T, F64], result: STRING }, + "sp.valAt": { argTypes: [SEARCH_PARAMS_T, F64], result: STRING }, + // node:querystring. qs.parse's result is the call site's ParsedUrlQuery + // dictionary record (VOID is the networkInterfaces sentinel — the + // libCall case checks the structure); qs.stringify's object argument + // is a dyn value (the frontend dynFroms typed records). + /** node:querystring (scr_qs.c — link-gated by moduleUsesQs; NOT + * URLSearchParams: the legacy codec's escaping and '+' rules differ). + * qs.escape is Node's qsEscape, which encodes exactly the component + * unreserved set — it emits the always-linked scr_qs_escape, so + * escape-only programs never pull the unit. qs.unescape is Node's qsUnescape: strict decodeURIComponent + * first, the lenient legacy unescapeBuffer fallback on failure (never + * throws). qs.parse takes (str, sep, eq, maxKeys) — the frontend + * completes omitted/null sep/eq to "&"/"=" and the omitted maxKeys + * option to Node's 1000 (0 and negatives mean unlimited, Node's rule) — + * and its result type is the CALL SITE's mapped ParsedUrlQuery shape (a + * pure index-signature record over `string | string[]`, an undefined + * arm tolerated — @types/node's Dict), verified structurally by the + * frontend (lowerQuerystringParseCall); the emitters construct the + * record and hand its overflow map to scr_qs_parse_into with the two + * union tags. qs.stringify takes (obj, sep, eq) with obj a dyn value + * (the frontend dynFroms the typed record; JS-world dyn values pass + * through) — Node's encodeStringified rules run in the runtime, so + * arrays expand to repeated keys and null/undefined/nested objects are + * empty values. Custom encoder/decoder options fence at compile time. + * All borrow; string results +1; escape and stringify throw URIError + * [ERR_INVALID_URI] on a lone surrogate. */ + "qs.parse": { argTypes: [STRING, STRING, STRING, F64], result: VOID }, + "qs.stringify": { argTypes: [DYN, STRING, STRING], result: STRING }, + "qs.escape": { argTypes: [STRING], result: STRING }, + "qs.unescape": { argTypes: [STRING], result: STRING }, + /** node:util.parseArgs: one checked-dynamic config object in, one + * checked-dynamic ParsedResults tree out. Statically typed member reads + * validate their values when leaving that tree. The native parser may + * throw Node's coded validation/grammar TypeErrors. */ + "util.parseArgs": { argTypes: [DYN], result: DYN }, + "util.parseEnv": { argTypes: [DYN], result: DYN }, + "util.getSystemErrorName": { argTypes: [DYN], result: STRING }, + "util.getSystemErrorMessage": { argTypes: [DYN], result: STRING }, + "util.systemErrorEntries": { argTypes: [], result: DYN }, + "util.stripVTControlCharacters": { argTypes: [DYN], result: STRING }, + "util.toUSVString": { argTypes: [DYN], result: STRING }, + "util.isDeepStrictEqual": { argTypes: [DYN, DYN, DYN], result: BOOL }, + "util.styleText": { argTypes: [DYN, DYN, DYN], result: STRING }, + /** util.inherits/deprecate/callbackify/debuglog over checked-dynamic + * arguments (scr_util_callable.c). The function results are native dyn + * wrappers; call sites with a static function type check them back. */ + "util.inherits": { argTypes: [DYN, DYN], result: VOID }, + "util.inheritsPrototype": { argTypes: [DYN, DYN, DYN], result: VOID }, + "util.deprecate": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, + "util.callbackify": { argTypes: [DYN], result: DYN }, + /** util.promisify over an untyped function value: a native dyn function + * whose calls answer a dyn promise the appended callback settles. */ + "util.promisifyDyn": { argTypes: [DYN], result: DYN }, + "util.debuglog": { argTypes: [DYN, DYN], result: DYN }, + "util.typeIs": { argTypes: [DYN, STRING], result: BOOL }, + "util.typeValue": { argTypes: [DYN, STRING], result: DYN }, + /** fs.statSync → a Stats value (may throw, like the other sync fs + * calls); the stats.* getters are pure reads on it. */ + "fs.statSync": { argTypes: [STRING], result: STATS_T }, + "fs.lstatSync": { argTypes: [STRING], result: STATS_T }, + // statSync / lstatSync(path, { throwIfNoEntry }): arg 1 is the flag. + // Result is the interned `Stats | undefined` union (the runtime answers + // +1-or-NULL; the backend builds the arms). + "fs.statProbeSync": { argTypes: [STRING, BOOL], result: VOID }, + "fs.lstatProbeSync": { argTypes: [STRING, BOOL], result: VOID }, + "fs.fstatSync": { argTypes: [F64], result: STATS_T }, + "fs.fchmodSync": { argTypes: [F64, F64], result: VOID }, + "fs.fsyncSync": { argTypes: [F64], result: VOID }, + "fs.fdatasyncSync": { argTypes: [F64], result: VOID }, + "fs.ftruncateSync": { argTypes: [F64, F64], result: VOID }, + "fs.utimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, + "fs.futimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, + "fs.lutimesSync": { argTypes: [DYN, DYN, DYN], result: VOID }, + "fs.readvSync": { argTypes: [F64, arrayOf(BYTES_U8), F64], result: F64 }, + "fs.writevSync": { argTypes: [F64, arrayOf(BYTES_U8), F64], result: F64 }, + "fs.linkSync": { argTypes: [DYN, DYN], result: VOID }, + "fs.symlinkSync": { argTypes: [DYN, DYN, DYN], result: VOID }, + /** fs.cpSync / fs/promises cp (scr_fs_cp.c): src, dest and options are + * dyn values the runtime validates in Node's order. */ + "fs.cpSync": { argTypes: [DYN, DYN, DYN], result: VOID }, + "fs.readlinkSync": { argTypes: [DYN, DYN], result: DYN }, + "fs.statfsSync": { argTypes: [DYN, DYN], result: DYN }, + "fs.readlinkSyncStr": { argTypes: [DYN, DYN], result: STRING }, + "fs.readlinkSyncBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 }, + /** fs.openSync(path, flags) → the raw fd as f64; fs.readSync/fs.writeSync + * over Buffer windows perform sequential I/O when position is -1 and + * offset-preserving positioned I/O otherwise; fs.writeStrSync is the + * utf8 string twin; and fs.closeSync(fd) closes the descriptor. This is + * the fd slice behind spawn/log-processing forms. + * flags is Node's string + * grammar ("r", "w", "a" and the +/x variants; unknown flags throw + * Node's ERR_INVALID_ARG_VALUE TypeError text). All throw Node-shaped + * fs errors (openSync ENOENT/EACCES..., readSync EBADF/range errors, + * closeSync EBADF). */ + "fs.openSync": { argTypes: [STRING, STRING], result: F64 }, + "fs.openNumericSync": { argTypes: [STRING, F64, F64], result: F64 }, + "fs.readSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 }, + "fs.readSyncDyn": { argTypes: [DYN, DYN, DYN, DYN, DYN], result: F64 }, + "fs.writeSync": { argTypes: [F64, BYTES_U8, F64, F64, F64], result: F64 }, + "fs.writeStrSync": { argTypes: [F64, STRING, F64, STRING], result: F64 }, + // fs.watch's callback func type is program-dependent (zero params, or + // the eventType string) — the slot pins arity and the path/receiver. + /** fs.watch(path, listener?) → an FSWatcher handle (scr_watch.c — + * linked, and scr_watch_install() emitted, only when these appear on + * the IR; moduleUsesFsWatch is the switch). The path opens NOW — + * failure throws Node's fs error synchronously ("ENOENT: ..., watch + * 'x'", the polling-fallback catch shape) — and the unit's kqueue + * (EVFILT_VNODE) delivers "rename"/"change" through the loop's watch + * hook. The callback (nullable) MOVES in with an adapter per listener + * shape (zero-param, or the (eventType: string) form — runtime- + * provided); an open watcher keeps the loop alive until watcher.close() + * (idempotent, receiver borrowed, statement position only). */ + "fs.watch": { argTypes: [STRING], result: FSWATCHER_T }, + "fs.watchCb": { argTypes: [STRING, null], result: FSWATCHER_T }, + "watcher.close": { argTypes: [FSWATCHER_T], result: VOID }, + /** The composed `new crypto.X509Certificate(data).fingerprint` read + * (scr_lib.c — no certificate handle exists): the SHA-1 of the DER, + * uppercase colon-separated, over PEM or raw-DER Buffer input; other + * inputs throw Node's ERR_OSSL_PEM_NO_START_LINE Error (may-throw). */ + "crypto.x509Fingerprint": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509FingerprintStr": { argTypes: [STRING], result: STRING }, + /** The certificate's Validity window (validFrom / validTo reads — + * scr_lib.c's minimal ASN.1 walk to the TBSCertificate validity + * SEQUENCE): UTCTime and GeneralizedTime render in Node's exact + * ASN1_TIME_print shape ("Jul 1 00:00:00 2026 GMT" — %2d space-padded + * day). Same input contract and PEM error as the fingerprint pair + * (may-throw). */ + "crypto.x509ValidFrom": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509ValidFromStr": { argTypes: [STRING], result: STRING }, + "crypto.x509ValidTo": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509ValidToStr": { argTypes: [STRING], result: STRING }, + /** The certificate's DER as a fresh Buffer (the raw field), decoded + * once from the constructor's input; the same PEM error (may-throw). */ + "crypto.x509Raw": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "crypto.x509RawDer": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "crypto.x509RawStr": { argTypes: [STRING], result: BYTES_U8 }, + /** The remaining X509Certificate fields over that DER, in Node's + * renderings (scr_lib.c). subjectAltName, infoAccess and + * signatureAlgorithm answer "" where Node answers undefined. Borrowed; + * never throw. */ + "crypto.x509Subject": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Issuer": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509SubjectAltName": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509InfoAccess": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509SerialNumber": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Fingerprint256": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Fingerprint512": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509Ca": { argTypes: [BYTES_U8], result: BOOL }, + "crypto.x509SignatureAlgorithm": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509SignatureAlgorithmOid": { argTypes: [BYTES_U8], result: STRING }, + /** X509Certificate methods over its raw DER. checkHost/checkEmail/checkIP + * answer the matching name or "" (undefined), checkIssued compares two + * certificates, x509Pem is toString's PEM (scr_x509.c); publicKey, + * verify and checkPrivateKey read keys (scr_pki.c). */ + "crypto.x509CheckHost": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, + "crypto.x509CheckEmail": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, + "crypto.x509CheckIP": { argTypes: [BYTES_U8, DYN, DYN], result: STRING }, + "crypto.x509CheckIssued": { argTypes: [BYTES_U8, BYTES_U8], result: BOOL }, + "crypto.x509LegacyObject": { argTypes: [BYTES_U8], result: DYN }, + "crypto.x509Pem": { argTypes: [BYTES_U8], result: STRING }, + "crypto.x509PublicKey": { argTypes: [BYTES_U8], result: DYN }, + "crypto.x509Verify": { argTypes: [BYTES_U8, DYN], result: BOOL }, + "crypto.x509CheckPrivateKey": { argTypes: [BYTES_U8, DYN], result: BOOL }, + "fs.closeSync": { argTypes: [F64], result: VOID }, + "stats.isFile": { argTypes: [STATS_T], result: BOOL }, + "stats.isDirectory": { argTypes: [STATS_T], result: BOOL }, + "stats.isSymbolicLink": { argTypes: [STATS_T], result: BOOL }, + "stats.size": { argTypes: [STATS_T], result: F64 }, + "stats.dev": { argTypes: [STATS_T], result: F64 }, + "stats.ino": { argTypes: [STATS_T], result: F64 }, + "stats.blocks": { argTypes: [STATS_T], result: F64 }, + "stats.nlink": { argTypes: [STATS_T], result: F64 }, + "stats.atimeMs": { argTypes: [STATS_T], result: F64 }, + "stats.mtimeMs": { argTypes: [STATS_T], result: F64 }, + "stats.ctimeMs": { argTypes: [STATS_T], result: F64 }, + "stats.birthtimeMs": { argTypes: [STATS_T], result: F64 }, + "stats.mode": { argTypes: [STATS_T], result: F64 }, + "stats.uid": { argTypes: [STATS_T], result: F64 }, + "stats.gid": { argTypes: [STATS_T], result: F64 }, + "stats.rdev": { argTypes: [STATS_T], result: F64 }, + "stats.blksize": { argTypes: [STATS_T], result: F64 }, + "stats.isBlockDevice": { argTypes: [STATS_T], result: BOOL }, + "stats.isCharacterDevice": { argTypes: [STATS_T], result: BOOL }, + "stats.isFIFO": { argTypes: [STATS_T], result: BOOL }, + "stats.isSocket": { argTypes: [STATS_T], result: BOOL }, + /** The Date fields: the *Ms value rounded (Node's dateFromMs). */ + "stats.atime": { argTypes: [STATS_T], result: DATE_T }, + "stats.mtime": { argTypes: [STATS_T], result: DATE_T }, + "stats.ctime": { argTypes: [STATS_T], result: DATE_T }, + "stats.birthtime": { argTypes: [STATS_T], result: DATE_T }, + // The wider sync fs slice (unlink/chmod/chown/copyfile and the + // mode-carrying write/mkdir forms). + /** The wider sync fs slice (scr_lib.c), all throwing catchably with + * Node's errno message shapes and `.code` stamped like the rest of + * sync fs. unlink/chmod/chown wrap the syscalls 1:1 (Node reports the + * syscall's own name). copyFileSync copies contents into a fresh (or + * truncated) destination carrying the SOURCE's mode (libuv's + * uv_fs_copyfile behavior); its errors carry BOTH paths — Node's + * "copyfile 'src' -> 'dest'". lstatSync is statSync without following + * a trailing symlink (Node reports lstat); stats.isSymbolicLink / + * stats.dev / ino / blocks / nlink / atimeMs / mtimeMs / ctimeMs are pure reads on the widened + * snapshot (blocks is allocated 512-byte units; the times are milliseconds + * with their sub-second fractions, Node's arithmetic). + * writeFileModeSync is writeFileSync(path, data, { mode }): the mode + * applies at CREATION only (open(2) with O_CREAT, umask applying), + * exactly Node — an existing file keeps its permissions. mkdirModeSync + * / mkdirRecursiveModeSync are the mkdirSync option forms with an + * explicit mode (the recursive walk passes it to every directory it + * creates, like Node's). */ + "fs.unlinkSync": { argTypes: [STRING], result: VOID }, + "fs.chmodSync": { argTypes: [STRING, F64], result: VOID }, + "fs.chownSync": { argTypes: [STRING, F64, F64], result: VOID }, + "fs.lchownSync": { argTypes: [STRING, F64, F64], result: VOID }, + "fs.fchownSync": { argTypes: [F64, F64, F64], result: VOID }, + /** truncateSync opens the path with 'r+', then truncates (len checked and + * clamped like ftruncateSync's) and closes it. */ + "fs.truncateSync": { argTypes: [STRING, F64], result: VOID }, + "fs.copyFileSync": { argTypes: [STRING, STRING], result: VOID }, + /** renameSync is the direct two-path syscall wrapper. renameCb defers + * the operation to the loop and fires a program-shaped Error | null + * callback through a backend-emitted adapter. */ + "fs.renameSync": { argTypes: [STRING, STRING], result: VOID }, + // Callback type is program-dependent (zero params or Error | null). + "fs.renameCb": { argTypes: [STRING, STRING, null], result: VOID }, + "fs.writeFileModeSync": { argTypes: [STRING, STRING, F64], result: VOID }, + "fs.mkdirModeSync": { argTypes: [STRING, F64], result: VOID }, + "fs.mkdirRecursiveModeSync": { argTypes: [STRING, F64], result: VOID }, + "atomics.wait": { argTypes: [bytesOf("i32"), F64, F64, F64], result: STRING }, + "atomics.notify": { argTypes: [bytesOf("i32"), F64, F64], result: F64 }, + "atomics.op": { argTypes: [null, F64, F64, F64, F64], result: F64 }, + "atomics.opDyn": { argTypes: [DYN, F64, F64, F64, F64], result: F64 }, + "atomics.waitDyn": { argTypes: [DYN, F64, F64, F64], result: STRING }, + "atomics.notifyDyn": { argTypes: [DYN, F64, F64], result: F64 }, + "worker.new": { argTypes: [F64, STRING, DYN], result: DYN }, + "worker.isMainThread": { argTypes: [], result: BOOL }, + "worker.threadId": { argTypes: [], result: F64 }, + "worker.root": { argTypes: [], result: F64 }, + "worker.data": { argTypes: [], result: DYN }, + "worker.parentPort": { argTypes: [], result: DYN }, + /** worker_threads.resourceLimits: {} in the main thread; a refusal in a + * worker (scr_worker_events.c). */ + "worker.resourceLimits": { argTypes: [], result: DYN }, + /** worker_threads.threadName: the Worker's name in a worker, "" in the + * main thread (scr_worker.c). */ + "worker.threadName": { argTypes: [], result: DYN }, + /** new MessageChannel() ({ port1, port2 }) and receiveMessageOnPort(port) + * (scr_worker_events.c; the latter may throw). */ + "worker.channel": { argTypes: [], result: DYN }, + "worker.receiveMessageOnPort": { argTypes: [DYN], result: DYN }, + /** setEnvironmentData(key, value) and getEnvironmentData(key) + * (scr_worker_events.c; arguments borrowed). */ + "worker.setEnvironmentData": { argTypes: [DYN, DYN], result: VOID }, + "worker.getEnvironmentData": { argTypes: [DYN], result: DYN }, + /** markAsUntransferable(value) and isMarkedAsUntransferable(value) + * (scr_worker_events.c; the value borrowed). */ + "worker.markUntransferable": { argTypes: [DYN], result: VOID }, + "worker.isMarkedUntransferable": { argTypes: [DYN], result: BOOL }, + /** markAsUncloneable(value) (scr_json.c; the value borrowed). */ + "dyn.markUncloneable": { argTypes: [DYN], result: VOID }, + /** before new Worker() with the stdout or stderr option: links the stream + * unit that worker.stdout and worker.stderr read through (scr_stream.c). */ + "stream.workerStdio": { argTypes: [], result: VOID }, + /** new BroadcastChannel(...args) (scr_worker_events.c; may throw), after + * event.broadcastInstall, which installs its EventTarget side. */ + "worker.broadcastChannel": { argTypes: [DYN], result: DYN }, + "event.broadcastInstall": { argTypes: [], result: VOID }, + /** child_process.spawnSync (scr_child.c): posix_spawn + waitpid + piped + * utf8 capture — cmd borrowed, args one borrowed string[] (the frontend + * completes an omitted list to an empty literal), result an owned (+1) + * spawnRes. NEVER throws: spawn failure (nonexistent binary, EACCES) is + * data, like Node's error property — status null and empty outputs + * (SEMANTICS.md documents the divergence from Node's null stdout). + * The getters are pure reads: status is the interned `number | null` + * union (null = spawn failure or signal death, type-directed + * construction in the backend like process.envGet); stdout/stderr are + * +1 strings. */ + "cp.spawnSync": { argTypes: [STRING, arrayOf(STRING)], result: SPAWNRES_T }, + // spawnSync's options form: timeout, killSignal name ("" = SIGTERM), + // and the three stdio modes. + /** spawnSync with options (scr_child.c): the cp.spawnSync core plus the + * option slice portless-class CLIs pass — timeout (killSignal fires at + * the deadline and the result carries error: ETIMEDOUT + the signal, + * Node's shape), killSignal (a signal NAME; "" = the SIGTERM default), + * and per-fd stdio modes (in: 0 /dev/null, 1 ignore, 2 inherit; out/ + * err: 0 capture, 1 ignore → "", 2 inherit → ""). NEVER throws — spawn + * failure and timeout are data on the result, like Node's error + * property. spawnRes.signal is the result's termination signal as the + * call site's `Signals | null` union (null = exited normally or spawn + * failure), constructed type-directedly like spawnRes.status. */ + "cp.spawnSyncOpts": { + argTypes: [STRING, arrayOf(STRING), F64, STRING, F64, F64, F64], + result: SPAWNRES_T, + }, + /** cp.spawnSyncOpts with the `env` option: the trailing [k,v,...] + * pairs replace the child's environment (recordToEnvPairs). */ + "cp.spawnSyncEnv": { + argTypes: [STRING, arrayOf(STRING), F64, STRING, F64, F64, F64, arrayOf(STRING)], + result: SPAWNRES_T, + }, + /** cp.spawnSyncOpts with the stdio carried as a RUNTIME string — + * "pipe" | "ignore" | "inherit", proven by the call site's TYPE (the + * defaultRunner idiom `stdio: options?.stdio ?? "pipe"`); the runtime + * maps the value to the three modes. Args (cmd, argv, timeout, + * killSignal, stdio). Never throws, like the other spawnSync forms. */ + "cp.spawnSyncStdioStr": { + argTypes: [STRING, arrayOf(STRING), F64, STRING, STRING], + result: SPAWNRES_T, + }, + // node:net (scr_net.c). Listener slots are program-dependent closures — + // null slots, checked in the libCall case (the child.onExit precedent); + // the zero-param callbacks pin the exact func type here. The trailing + // BOOL on registrations is the once flag. + /** node:net (scr_net.c — linked, and scr_net_install() emitted, only + * when one of these appears on the IR; moduleUsesNet is the switch). + * Receivers are borrowed; CALLBACKS MOVE into the handle's listener + * registry and are released at settlement (the child.onExit story). + * All listener registrations carry a trailing BOOL once-flag (`on` vs + * `once`) and are void — chaining is fenced. None of these throw: + * listen/connect failures are the async 'error' event, like Node. + * + * net.createServer's optional connection handler and the + * serverOnConnection listeners take the runtime-provided adapters + * (zero-param, or the one-param socket shape); sockOnData's adapters + * are the stdin pair's shapes (zero-param / bytes chunk); the error + * events reuse the child %Error adapters. net.listen/net.listenCb bind + * NOW and defer 'listening' to the next loop turn (Node's next-tick + * emit); net.serverPort is the composed `server.address().port` read. + * net.connect's host argument is a string ("localhost" pins to + * 127.0.0.1 — SEMANTICS.md); the connect callback is once('connect'). */ + "net.createServer": { argTypes: [], result: NETSERVER_T }, + "net.createServerCb": { argTypes: [null], result: NETSERVER_T }, + "net.listen": { argTypes: [NETSERVER_T, F64], result: VOID }, + /** listen(path[, cb]): a Unix-socket server; the socket file is removed + * when the server closes. Failures are the async 'error'. */ + "net.listenPath": { argTypes: [NETSERVER_T, STRING], result: VOID }, + "net.listenPathCb": { + argTypes: [NETSERVER_T, STRING, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "net.listenCb": { + argTypes: [NETSERVER_T, F64, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + /** listen({ port, host, ipv6Only }[, cb]) — the explicit-interface bind + * (portless's listenOnProxyInterface): args [server, port, host, + * ipv6Only]. host is an IP literal string ("" = the host-less + * dual-stack any default); ipv6Only sets IPV6_V6ONLY before the bind. + * These are the old-shaped calls, retained for omitted reusePort so + * serialized IR remains compatible. Failures are the async 'error', + * message in Node's listen shape with the requested host. Never throws. */ + "net.listenOpts": { argTypes: [NETSERVER_T, F64, STRING, BOOL], result: VOID }, + // The callback slot is a zero-param void closure OR its + // `(() => void) | undefined` optional-binding union (checked specially). + "net.listenOptsCb": { argTypes: [NETSERVER_T, F64, STRING, BOOL, null], result: VOID }, + /** Additive ABI for listen({ ..., reusePort }) — args + * [server, port, host, ipv6Only, reusePort], with the callback following + * those values in the callback form. The final BOOL is the requested + * kernel connection-distribution option; old spellings remain + * four/five-argument calls so serialized IR stays compatible. */ + "net.listenOptsReusePort": { argTypes: [NETSERVER_T, F64, STRING, BOOL, BOOL], result: VOID }, + // The callback is a zero-param void closure OR its optional-binding union; + // the callback follows the reusePort BOOL in this additive ABI. + "net.listenOptsReusePortCb": { + argTypes: [NETSERVER_T, F64, STRING, BOOL, BOOL, null], + result: VOID, + }, + "net.serverPort": { argTypes: [NETSERVER_T], result: F64 }, + "net.serverListening": { argTypes: [NETSERVER_T], result: BOOL }, + /** socket.ref()/unref() and server.ref()/unref(): whether the handle + * keeps the event loop alive. */ + "net.sockSetRef": { argTypes: [NETSOCKET_T, BOOL], result: VOID }, + "net.serverSetRef": { argTypes: [NETSERVER_T, BOOL], result: VOID }, + // net.serverAddress's record result is shape-checked in the libCall case + // (the dgram.address sentinel pattern). + /** server.address() as the full AddressInfo record (the dgram.address + * materialization pattern: the emitter builds the record from the three + * runtime reads; the frontend pinned the shape). Never throws — before + * listen it answers the any-form defaults with port 0 where Node + * answers null (the serverPort stance). */ + "net.serverAddress": { argTypes: [NETSERVER_T], result: VOID }, + "net.serverClose": { argTypes: [NETSERVER_T], result: VOID }, + /** server.close((err) => ...): the callback (a dyn function) gets + * undefined once the server closes, or Node's ERR_SERVER_NOT_RUNNING + * when it was not listening. */ + "net.serverCloseDyn": { argTypes: [NETSERVER_T, DYN], result: VOID }, + /** server.address() in programs that may listen on a Unix socket: the + * path a listening server answers ("" for a TCP server), and the check + * a record-typed address() makes first (throws for a Unix server). */ + "net.serverUnixPath": { argTypes: [NETSERVER_T], result: STRING }, + "net.serverAddressCheck": { argTypes: [NETSERVER_T], result: VOID }, + "net.serverCloseCb": { + argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "net.serverOnError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + "net.serverOnClose": { + argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "net.serverOnConnection": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + /** 'secureConnection' — the TLS server's deferred 'connection' list + * (handshake-completion timing); on a server without deferred + * connections (a plain net server) the registration never fires, + * exactly Node's split. */ + "net.serverOnSecureConnection": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + "net.connect": { argTypes: [F64, STRING], result: NETSOCKET_T }, + /** connect with a validated autoSelectFamilyAttemptTimeout option (the + * budget runs Node's validateInt32-from-1 ladder and is then inert — + * the single dial has nothing to time). May-throw. */ + "net.connectAttempt": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, + /** net.connect/createConnection over a RUNTIME option bag (computed + * keys — the invalid-input probes): Node-order validation (the + * objectMode trio's ERR_INVALID_ARG_VALUE, validatePort, host string, + * autoSelectFamily boolean, the attempt budget), then the trailing + * compiler-rendered fence — ALWAYS THROWS (the error.nodeThrow + * polymorphic-result carve-out). May-throw seed. */ + "net.connectOptsChk": { argTypes: [DYN, STRING], result: VOID }, + "net.connectCb": { + argTypes: [F64, STRING, { kind: "func", params: [], ret: VOID }], + result: NETSOCKET_T, + }, + // The lookup's exact func shape is program data (its answer callback's + // union/record types) — checked specially in the libCall case. + /** connect({ port, host, autoSelectFamily: true, lookup }) — the + * caller-resolver dial (portless's createLoopbackConnection): args + * [port, host, lookup]. The runtime creates the (connecting) socket + * handle, invokes the lookup as Node does — lookup(hostname, options, + * callback), options crossing as the dyn undefined — and the answer + * closure (an emitter-synthesized per-shape thunk over a boxed socket, + * the SNI-answer pattern) dials the answered addresses IN ORDER: each + * connect failure tries the next, the LAST failure's message is the + * socket's 'error' (Node's autoSelectFamily aggregate is a documented + * divergence), and a lookup error surfaces as the deferred socket + * 'error'. A synchronous throw INSIDE the lookup propagates out of the + * connect call (may-throw seed). */ + "net.connectLookup": { argTypes: [F64, STRING, null], result: NETSOCKET_T }, + /** connect({ host, port, lookup }) with a lookup of Node's own type, as + * a checked-dynamic function: the runtime calls it with { hints, all: + * true } and a callback taking an address string or list, then dials + * the answers in order. MAY THROW (host validation, a lookup throw). */ + "net.connectLookupDyn": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, + "net.sockWrite": { argTypes: [NETSOCKET_T, STRING], result: VOID }, + "net.sockWriteBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, + "net.sockEnd": { argTypes: [NETSOCKET_T], result: VOID }, + "net.sockEndStr": { argTypes: [NETSOCKET_T, STRING], result: VOID }, + "net.sockEndBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, + /** write/end with a CHECKED-DYNAMIC chunk (an untyped JS payload into a + * typed socket): the runtime dispatches STR/BYTES and throws Node's + * ERR_INVALID_ARG_TYPE chunk TypeError on any other kind. */ + "net.sockWriteDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, + "net.sockEndDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, + "net.sockDestroy": { argTypes: [NETSOCKET_T], result: VOID }, + "net.sockPipe": { argTypes: [NETSOCKET_T, NETSOCKET_T], result: VOID }, + /** socket.pipe(res) — raw socket chunks into a ServerResponse body + * (the extended-CONNECT bridge leg): each chunk is a body write (the + * response's own framing applies); source EOF end()s the response, + * pipe's default. Borrows both. Never throws. */ + "net.sockPipeRes": { argTypes: [NETSOCKET_T, HTTPRES_T], result: VOID }, + "net.sockOnData": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, + "net.sockOnEnd": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "net.sockOnClose": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "net.sockOnError": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, + "net.sockOnConnect": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + // node:dgram + node:dns (scr_dgram.c). The message/error listeners and + // dns.lookup's callback are program-dependent closures (null slots, + // checked in the libCall case); dgram.address's record result is + // program-dependent too (VOID here is the envGet sentinel — the libCall + // case checks the {address, family, port} shape). + /** node:dgram + node:dns (scr_dgram.c — linked, and + * scr_dgram_install() emitted, only when one of these appears on the + * IR; moduleUsesDgram is the switch — dns.lookup rides the same unit). + * The net listener discipline verbatim: receivers borrowed, CALLBACKS + * MOVE into the handle's registry and release at settlement, `on` vs + * `once` is the trailing bool, registrations are void. bind/connect + * bind NOW and defer 'listening'/'connect' to the next loop turn (the + * net.listen story); their optional-host completions are "" + * (bind → 0.0.0.0) and "127.0.0.1" (connect — udp4's Node default). + * bind/connect/send/close/address THROW Node's state errors ("Socket + * is already bound", "Already connected", "Not running") — may-throw + * seeded. dgram.address returns the AddressInfo RECORD (the frontend + * pins the {address, family, port} shape; the emitter builds it from + * runtime parts). onMessage adapters are emitted per rinfo record + * shape (the child.onExit precedent); onError reuses the child %Error + * adapters. dns.lookup resolves via getaddrinfo AT CALL TIME and + * defers the callback to the next turn (SEMANTICS.md documents the + * blocking divergence); its per-union adapter builds the + * `Error | null` first argument. */ + "dgram.createSocket": { argTypes: [BOOL], result: DGRAMSOCK_T }, + "dgram.bind": { argTypes: [DGRAMSOCK_T, F64, STRING], result: VOID }, + "dgram.bindCb": { + argTypes: [DGRAMSOCK_T, F64, STRING, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "dgram.connect": { argTypes: [DGRAMSOCK_T, F64, STRING], result: VOID }, + "dgram.connectCb": { + argTypes: [DGRAMSOCK_T, F64, STRING, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "dgram.sendStr": { argTypes: [DGRAMSOCK_T, STRING, F64, STRING], result: VOID }, + "dgram.sendBytes": { argTypes: [DGRAMSOCK_T, BYTES_U8, F64, STRING], result: VOID }, + /** Connected AF_UNIX datagram sockets for the hot-shots package shim + * (the unix-dgram addon's surface): fd or result >= 0, -errno on + * failure; send answers 1 when the socket would block. */ + "dgram.unixSocket": { argTypes: [], result: F64 }, + "dgram.unixConnect": { argTypes: [F64, STRING], result: F64 }, + "dgram.unixSend": { argTypes: [F64, BYTES_U8], result: F64 }, + "dgram.unixClose": { argTypes: [F64], result: F64 }, + /** The send argument-validation ladder over dyn arguments (Node's + * signature shuffle: slice bounds, list/type contracts, port/address + * validation, connected-state errors), then the send: lists, connected + * sends and completion callbacks included. Up to six arguments, the + * missing ones undefined. May-throw. */ + "dgram.sendChk": { argTypes: [DGRAMSOCK_T, DYN, DYN, DYN, DYN, DYN, DYN], result: VOID }, + "dgram.address": { argTypes: [DGRAMSOCK_T], result: VOID }, + "dgram.close": { argTypes: [DGRAMSOCK_T], result: VOID }, + "dgram.closeCb": { + argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "dgram.unref": { argTypes: [DGRAMSOCK_T], result: VOID }, + "dgram.ref": { argTypes: [DGRAMSOCK_T], result: VOID }, + /** The socket options (scr_dgram.c): the membership calls take the + * interface and whether it was given. May-throw. */ + "dgram.setBroadcast": { argTypes: [DGRAMSOCK_T, BOOL], result: VOID }, + "dgram.setTTL": { argTypes: [DGRAMSOCK_T, F64], result: F64 }, + "dgram.setMulticastTTL": { argTypes: [DGRAMSOCK_T, F64], result: F64 }, + "dgram.setMulticastLoopback": { argTypes: [DGRAMSOCK_T, BOOL], result: BOOL }, + "dgram.setMulticastInterface": { argTypes: [DGRAMSOCK_T, STRING], result: VOID }, + "dgram.addMembership": { argTypes: [DGRAMSOCK_T, STRING, STRING, BOOL], result: VOID }, + "dgram.dropMembership": { argTypes: [DGRAMSOCK_T, STRING, STRING, BOOL], result: VOID }, + "dgram.getRecvBufferSize": { argTypes: [DGRAMSOCK_T], result: F64 }, + "dgram.getSendBufferSize": { argTypes: [DGRAMSOCK_T], result: F64 }, + "dgram.setRecvBufferSize": { argTypes: [DGRAMSOCK_T, F64], result: VOID }, + "dgram.setSendBufferSize": { argTypes: [DGRAMSOCK_T, F64], result: VOID }, + "dgram.getSendQueueSize": { argTypes: [DGRAMSOCK_T], result: F64 }, + "dgram.getSendQueueCount": { argTypes: [DGRAMSOCK_T], result: F64 }, + "dgram.disconnect": { argTypes: [DGRAMSOCK_T], result: VOID }, + "dgram.onMessage": { argTypes: [DGRAMSOCK_T, null, BOOL], result: VOID }, + "dgram.onError": { argTypes: [DGRAMSOCK_T, null, BOOL], result: VOID }, + "dgram.onListening": { + argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "dgram.onClose": { + argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "dgram.onConnect": { + argTypes: [DGRAMSOCK_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "dns.lookup": { argTypes: [STRING, F64, null], result: VOID }, + /** Node's argument forms with dyn callbacks: lookup(hostname, options, + * callback) and lookupService(address, port, callback). */ + "dns.lookupDyn": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, + "dns.lookupService": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, + /** node:dns/promises: lookup(hostname, options) and lookupService(address, + * port) over the callback forms; the promise fulfills with the dyn + * { address, family } (array for all: true) / { hostname, service }. + * Argument errors throw synchronously (may-throw), unlike the + * util.promisify forms below. */ + "dns.promisesLookup": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dns.promisesLookupService": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dns.getServers": { argTypes: [], result: DYN }, + "dns.getDefaultResultOrder": { argTypes: [], result: STRING }, + "dns.setDefaultResultOrder": { argTypes: [DYN], result: VOID }, + // node:test (scr_test.c). Bodies are program-dependent closures (0 or + // 1 testCtx param, void or Promise result — the spoke pinned the + // shape): null slots. sub's result is the settled Promise the + // await consumes. + /** node:test (scr_test.c — linked only when one of these appears on + * the IR; moduleUsesNodeTest is the switch, and the main epilogue asks + * scr_test_exit_code() for the process's exit status). Strings are + * BORROWED, callbacks MOVE. register/suite/hook attach to the runner + * tree (register: name, mode 0|1|2 run/skip/todo, directive message "" + * = none, cb or absent via registerEmpty, flags 1 async | 2 takes-ctx + * | 4 only, "file:line:col"); suite runs its body AT registration + * (Node's collection phase). sub is t.test — runs the subtest INLINE + * on the runner fiber and returns the settled promise the await + * consumes. ctxSkip/ctxTodo mark the running test; ctxDiagnostic + * queues an ℹ line; ctxName reads t.name. Every registration keeps + * the loop-run emitted (usesTimers) so the runner fiber drains. */ + // The trailing three are the { plan, timeout, concurrency } options (plan + // -1 without one; timeout read under flag 8; concurrency read under flag + // 16, -1 for true). An out-of-range timeout or concurrency throws. + "test.register": { + argTypes: [STRING, F64, STRING, null, F64, STRING, F64, F64, F64], + result: VOID, + }, + "test.registerEmpty": { + argTypes: [STRING, F64, STRING, F64, STRING, F64, F64, F64], + result: VOID, + }, + // The suite body takes no parameters or its SuiteContext (flag 2). + "test.suite": { argTypes: [STRING, F64, STRING, null, STRING, F64, F64], result: VOID }, + // The trailing string is the hook call's "file:line:col". + "test.hook": { argTypes: [F64, null, F64, STRING], result: VOID }, + "test.sub": { + argTypes: [TESTCTX_T, STRING, F64, STRING, null, F64, STRING, F64, F64, F64], + result: { kind: "promise", inner: VOID }, + }, + "test.subEmpty": { + argTypes: [TESTCTX_T, STRING, F64, STRING, F64, STRING, F64, F64, F64], + result: VOID, + }, + "test.ctxSkip": { argTypes: [TESTCTX_T, STRING], result: VOID }, + "test.ctxTodo": { argTypes: [TESTCTX_T, STRING], result: VOID }, + "test.ctxDiagnostic": { argTypes: [TESTCTX_T, STRING], result: VOID }, + "test.ctxName": { argTypes: [TESTCTX_T], result: STRING }, + "test.ctxFullName": { argTypes: [TESTCTX_T], result: STRING }, + // t.passed, and t.workerId's Number(NODE_TEST_WORKER_ID) with 0 for + // undefined. + "test.ctxPassed": { argTypes: [TESTCTX_T], result: BOOL }, + "test.workerId": { argTypes: [], result: F64 }, + // getTestContext(): whether a test, suite or hook is running in the + // current async context, and that context. + "test.hasContext": { argTypes: [], result: BOOL }, + "test.context": { argTypes: [], result: TESTCTX_T }, + // t.before/t.after/t.beforeEach/t.afterEach (which: 0 before, 1 after, + // 2 beforeEach, 3 afterEach); a throwing t.before is captured on the test. + "test.ctxHook": { argTypes: [TESTCTX_T, F64, null, F64], result: VOID }, + // t.plan(count), and the count of one t.assert call. + "test.ctxPlan": { argTypes: [TESTCTX_T, F64], result: VOID }, + "test.ctxCount": { argTypes: [TESTCTX_T], result: VOID }, + // The mock trackers: the module's `mock` and t.mock keep each mock's + // restore closure (moved) for restoreAll and reset. + "test.mockTrack": { argTypes: [{ kind: "func", params: [], ret: VOID }], result: VOID }, + "test.mockRestoreAll": { argTypes: [], result: VOID }, + "test.mockReset": { argTypes: [], result: VOID }, + "test.ctxMockTrack": { + argTypes: [TESTCTX_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "test.ctxMockRestoreAll": { argTypes: [TESTCTX_T], result: VOID }, + "test.ctxMockReset": { argTypes: [TESTCTX_T], result: VOID }, + // mock.timers on the module's `mock` and on t.mock. enable takes one + // code per listed API ('T' setTimeout, 'I' setInterval), the `now` + // value and its flags (1 given, 2 from a Date). enable, tick, runAll and + // setTime throw Node's errors; tick and runAll run timer callbacks. + // t.assert.snapshot and fileSnapshot: the escaped, wrapped serialized + // value, and the stored value it is compared with (snapshot: the test, + // the value, the snapshot file; fileSnapshot: the value, the path). + // Both read or, in update mode, record; missing and unreadable + // snapshots throw Node's ERR_INVALID_STATE. + "test.snapshotValue": { argTypes: [STRING], result: STRING }, + "test.ctxSnapshot": { argTypes: [TESTCTX_T, STRING, STRING], result: STRING }, + "test.fileSnapshot": { argTypes: [STRING, STRING], result: STRING }, + // Node's serializeValue over a dyn array of serializers (undefined for + // the default JSON.stringify(value, null, 2)); a throw becomes Node's + // "The provided serializers did not generate a string." ERR_INVALID_STATE. + "test.snapshotSerialize": { argTypes: [DYN, DYN], result: DYN }, + "test.mockTimersEnable": { argTypes: [STRING, F64, F64], result: VOID }, + "test.mockTimersTick": { argTypes: [F64], result: VOID }, + "test.mockTimersRunAll": { argTypes: [], result: VOID }, + "test.mockTimersReset": { argTypes: [], result: VOID }, + "test.mockTimersSetTime": { argTypes: [F64], result: VOID }, + "test.ctxMockTimersEnable": { argTypes: [TESTCTX_T, STRING, F64, F64], result: VOID }, + "test.ctxMockTimersTick": { argTypes: [TESTCTX_T, F64], result: VOID }, + "test.ctxMockTimersRunAll": { argTypes: [TESTCTX_T], result: VOID }, + "test.ctxMockTimersReset": { argTypes: [TESTCTX_T], result: VOID }, + "test.ctxMockTimersSetTime": { argTypes: [TESTCTX_T, F64], result: VOID }, + // node:http (scr_http.c over scr_net.c). The handler and the data + // listeners are program-dependent closures (null slots, checked in the + // libCall case); reqHeader's result is the interned string|undefined + // union, checked like process.envGet. + /** node:http, the server slice (scr_http.c over scr_net.c — linked + * only when these appear on the IR; moduleUsesHttpServer is the + * switch, and any http.* libCall also counts as net use so scr_net.c + * links and installs). http.createServer's handler MOVES in and takes + * the runtime adapters for its (req, res) / (req) / () shapes; req + * body listeners follow the net.sockOnData story (bytes chunks, once + * flags); reqHeader answers the interned `string | undefined` union + * exactly like process.envGet; the res writers are borrowed-argument + * voids with Node's framing decided in the runtime (Content-Length + * for end-before-head, chunked after an explicit writeHead/write). */ + "http.createServer": { argTypes: [null], result: NETSERVER_T }, + /** http.createServer() / http.Server() with no handler — the + * on("request") route; option helpers set parser behavior on a fresh + * server before the constructor/factory result is returned. */ + "http.createServerEmpty": { argTypes: [], result: NETSERVER_T }, + "http.validateHeaderName": { argTypes: [STRING, STRING], result: VOID }, + "http.validateHeaderValue": { argTypes: [STRING, DYN], result: VOID }, + "http.serverJoinDupHeaders": { argTypes: [NETSERVER_T], result: VOID }, + "http.serverAllowMissingHostHeader": { argTypes: [NETSERVER_T], result: VOID }, + /** The five writable numeric http.Server timeout fields use one + * selector ABI: 0 timeout, 1 keepAliveTimeout, 2 headersTimeout, + * 3 requestTimeout, 4 keepAliveTimeoutBuffer. These calls store/read + * the property values; timer enforcement remains outside this surface. + * The constructor-option setter takes dyn so explicit undefined remains + * distinguishable from a numeric value until its Node validation ladder. */ + "http.serverTimeoutGet": { argTypes: [NETSERVER_T, F64], result: F64 }, + "http.serverSetTimeout": { argTypes: [NETSERVER_T, F64], result: VOID }, + "http.serverSetTimeoutCb": { argTypes: [NETSERVER_T, F64, null], result: VOID }, + "http.serverOnTimeout": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + "http.serverCloseAllConnections": { argTypes: [NETSERVER_T], result: VOID }, + "http.serverCloseIdleConnections": { argTypes: [NETSERVER_T], result: VOID }, + "http.serverTimeoutSet": { argTypes: [NETSERVER_T, F64, F64], result: VOID }, + "http.serverTimeoutOptionSet": { argTypes: [NETSERVER_T, F64, DYN], result: VOID }, + /** server.on("listening", cb) — the deferred listen-callback list. */ + "net.serverOnListening": { + argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + /** The ServerResponse member surface: statusCode/statusMessage reads + * and assignments (Node's writable properties), the header CRUD trio + * (getHeader answers `string | undefined` like reqHeader), and + * end(cb)'s callback slot (resOnEndCallback registers, the end call + * follows — the callback fires deferred, just before Node's 'finish' + * emit; resOnFinish is the 'finish' listener slot). writeHead's + * statusMessage forms compose in interned helpers: resStatusMsgSet + * then the ordinary writeHead entry. */ + "http.resStatusGet": { argTypes: [HTTPRES_T], result: F64 }, + "http.resStatusSet": { argTypes: [HTTPRES_T, F64], result: VOID }, + "http.resStatusMsgGet": { argTypes: [HTTPRES_T], result: VOID }, + "http.resStatusMsgSet": { argTypes: [HTTPRES_T, STRING], result: VOID }, + "http.resRequest": { argTypes: [HTTPRES_T], result: HTTPREQ_T }, + "http.resSocket": { argTypes: [HTTPRES_T], result: VOID }, + "http.resWritableFinished": { argTypes: [HTTPRES_T], result: BOOL }, + "http.resSendDateGet": { argTypes: [HTTPRES_T], result: BOOL }, + "http.resSendDateSet": { argTypes: [HTTPRES_T, BOOL], result: VOID }, + "http.resStrictContentLengthGet": { argTypes: [HTTPRES_T], result: BOOL }, + "http.resStrictContentLengthSet": { argTypes: [HTTPRES_T, BOOL], result: VOID }, + "http.resSetTimeout": { argTypes: [HTTPRES_T, F64], result: VOID }, + "http.resSetTimeoutCb": { + argTypes: [HTTPRES_T, F64, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + // resGetHeader answers the interned `string | undefined` union — the + // reqHeader/envGet sentinel pattern (VOID here, checked specially). + "http.resGetHeader": { argTypes: [HTTPRES_T, STRING], result: VOID }, + /** getHeader as Node answers it: the number, string or string[] that was + * set, or undefined, as a checked-dynamic value. */ + "http.resGetHeaderDyn": { argTypes: [HTTPRES_T, STRING], result: DYN }, + "http.resGetHeaderNames": { argTypes: [HTTPRES_T], result: arrayOf(STRING) }, + "http.resGetRawHeaderNames": { argTypes: [HTTPRES_T], result: arrayOf(STRING) }, + "http.resGetHeaders": { argTypes: [HTTPRES_T], result: DYN }, + "http.resHasHeader": { argTypes: [HTTPRES_T, STRING], result: BOOL }, + "http.resRemoveHeader": { argTypes: [HTTPRES_T, STRING], result: VOID }, + "http.resOnFinish": { + argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "http.resOnEndCallback": { + argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "http.reqUrl": { argTypes: [HTTPREQ_T], result: STRING }, + "http.reqMethod": { argTypes: [HTTPREQ_T], result: STRING }, + "http.reqHeader": { argTypes: [HTTPREQ_T, STRING], result: VOID }, + "http.reqTrailer": { argTypes: [HTTPREQ_T, STRING], result: VOID }, + "http.reqHeaderValues": { argTypes: [HTTPREQ_T, STRING], result: VOID }, + "http.reqTrailerValues": { argTypes: [HTTPREQ_T, STRING], result: VOID }, + "http.reqOnData": { argTypes: [HTTPREQ_T, null, BOOL], result: VOID }, + "http.reqOnEnd": { + argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.resSetHeader": { argTypes: [HTTPRES_T, STRING, STRING], result: VOID }, + /** setHeader / appendHeader with a number, string or array value; the + * stored value keeps its type for getHeader/getHeaders. MAY THROW. */ + "http.resSetHeaderDyn": { argTypes: [HTTPRES_T, STRING, DYN], result: VOID }, + "http.resAppendHeader": { argTypes: [HTTPRES_T, STRING, DYN], result: VOID }, + "http.resWriteHead": { argTypes: [HTTPRES_T, F64], result: VOID }, + "http.resWriteHeadN": { + argTypes: [HTTPRES_T, F64, arrayOf(STRING), arrayOf(STRING)], + result: VOID, + }, + /** HTTP/1.1 informational heads; early hints takes flat string pairs, + * with a lowercase `link` key required to send anything. */ + "http.resWriteContinue": { argTypes: [HTTPRES_T], result: VOID }, + "http.resWriteProcessing": { argTypes: [HTTPRES_T], result: VOID }, + "http.resWriteEarlyHints": { argTypes: [HTTPRES_T, arrayOf(STRING)], result: VOID }, + "http.resWrite": { argTypes: [HTTPRES_T, STRING], result: VOID }, + "http.resWriteBytes": { argTypes: [HTTPRES_T, BYTES_U8], result: VOID }, + "http.resEnd": { argTypes: [HTTPRES_T], result: VOID }, + "http.resEndStr": { argTypes: [HTTPRES_T, STRING], result: VOID }, + "http.resEndBytes": { argTypes: [HTTPRES_T, BYTES_U8], result: VOID }, + /** The checked-dynamic chunk twins (the net.sockWriteDyn story). */ + "http.resWriteDyn": { argTypes: [HTTPRES_T, DYN], result: VOID }, + "http.resEndDyn": { argTypes: [HTTPRES_T, DYN], result: VOID }, + "http.resHeadersSent": { argTypes: [HTTPRES_T], result: BOOL }, + "http.resWritableEnded": { argTypes: [HTTPRES_T], result: BOOL }, + "http.resFlushHeaders": { argTypes: [HTTPRES_T], result: VOID }, + "http.resAddTrailers": { argTypes: [HTTPRES_T, arrayOf(STRING)], result: VOID }, + "http.resCork": { argTypes: [HTTPRES_T], result: VOID }, + "http.resUncork": { argTypes: [HTTPRES_T], result: VOID }, + "http.resWritableCorked": { argTypes: [HTTPRES_T], result: F64 }, + // The member follow-ups: reqStatusCode's `number | undefined` and + // sockRemoteAddress's `string | undefined` results are shape-checked in + // the special cases in validate.ts (like reqHeader/columns). + /** The server-surface member follow-ups: reqStatusCode answers the + * interned `number | undefined` union (negative = the undefined arm — + * a SERVER request, where Node's statusCode is undefined; every client + * response carries a real status); reqSocket is the underlying + * connection (+1, the same handle net.connect would give); + * sockRemoteAddress answers `string | undefined` (NULL after the fd + * closed, Node's destroyed-socket undefined; a dual-stack accept of an + * IPv4 peer reads "::ffff:a.b.c.d" like Node). reqResume/reqPause/reqDestroy and + * the req error/close listener slots complete the IncomingMessage + * surface portless's client responses use; resDestroy/resOnClose and + * resWriteHeadPairs ([k0,v0,k1,v1,...] — the env.pairs helper's flat + * shape) complete ServerResponse. sockSetTimeout arms the idle + * EVFILT_TIMER ('timeout' fires after ms of inactivity, once per idle + * period, never destroying the socket — Node's semantics). */ + "http.reqStatusCode": { argTypes: [HTTPREQ_T], result: VOID }, + // reqStatusMessage's `string | undefined` is program-interned like + // reqHeader's (the VOID here is the envGet sentinel). + "http.reqStatusMessage": { argTypes: [HTTPREQ_T], result: VOID }, + /** req.rawHeaders — [name, value, name, value, ...] in arrival order, + * names in their ORIGINAL case (Node's shape); a fresh string[] per + * read. reqStatusMessage answers the interned `string | undefined` + * union — the reason phrase on client responses ("" when the status + * line carried none), the undefined arm on server requests (the + * statusCode split). sockDestroyed is socket.destroyed — true once the + * fd is gone (destroy() or full close). */ + "http.reqRawHeaders": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, + /** The `{ ...req.headers }` snapshot feed: [lowercased name, value, + * ...] pairs in arrival order — the interned %headers.snapshot helper + * builds the record over it, exactly the process.envPairs pattern. */ + "http.reqHeaderPairs": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, + /** Every value of one header in arrival order (empty when absent): the + * set-cookie array of a `{ ...req.headers }` snapshot. */ + "http.reqHeaderList": { argTypes: [HTTPREQ_T, STRING], result: arrayOf(STRING) }, + "http.reqRawTrailers": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, + "http.reqTrailerPairs": { argTypes: [HTTPREQ_T], result: arrayOf(STRING) }, + "net.sockDestroyed": { argTypes: [NETSOCKET_T], result: BOOL }, + /** socket.writable — the write half is open: no end() yet, no FIN sent, + * fd alive (connecting sockets answer true; writes queue). Node's + * stream flag. Borrows; never throws. */ + "net.sockWritable": { argTypes: [NETSOCKET_T], result: BOOL }, + // The 'upgrade' registrations: the callback shapes are program-typed + // ((req, socket, head) and shorter prefixes — the libCall case checks). + /** The 'upgrade' events, both sides (SEMANTICS.md — the WebSocket + * proxying surface): serverOnUpgrade registers (req, socket, head) + * listeners fired INSTEAD of 'request' for Connection: upgrade + * requests (the parser steps aside; the socket is raw; `head` carries + * bytes past the request head; no listener = the socket destroys, + * Node's default). clientOnUpgrade is the client twin: a 101 response + * fires (res, socket, head) INSTEAD of 'response'. */ + "http.serverOnUpgrade": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + /** server.on("connect", ...) — HTTP CONNECT tunneling, the 'upgrade' + * machinery's twin: (req, socket, head) fired INSTEAD of 'request' for + * CONNECT-method requests (no listener = the socket destroys, Node's + * default). The h2 compat server's 'connect' (portless's RFC 8441 + * handler) only ever sees the HTTP/1.1 arm under the allowHTTP1 + * lowering, so a listener whose second parameter is a UNION with a + * netSocket arm takes the socket wrapped at that arm (an emitted + * per-shape adapter — the tags are program data). */ + "http.serverOnConnect": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + "http.clientOnUpgrade": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, + "http.reqSocket": { argTypes: [HTTPREQ_T], result: NETSOCKET_T }, + // reqH2Stream's program-interned Http2Stream | undefined result is + // checked in the special libCall cases in validate.ts. + "http.reqH2Stream": { argTypes: [HTTPREQ_T], result: VOID }, + "http.reqH2StreamOrThrow": { argTypes: [HTTPREQ_T, STRING], result: HTTP2STREAM_T }, + /** req.pipe(dest) — the IncomingMessage body streaming into a + * ServerResponse (the proxy's response leg), a ClientRequest (the + * request-body forward), or a raw socket (the upgrade-rejection leg); + * chunk-for-chunk, natural end ends the destination (Node's pipe + * default; no backpressure — divergence 54's stream model). */ + "http.reqPipeRes": { argTypes: [HTTPREQ_T, HTTPRES_T], result: VOID }, + "http.reqPipeClient": { argTypes: [HTTPREQ_T, HTTPCLIENTREQ_T], result: VOID }, + "http.reqPipeSock": { argTypes: [HTTPREQ_T, NETSOCKET_T], result: VOID }, + "http.reqResume": { argTypes: [HTTPREQ_T], result: VOID }, + "http.reqPause": { argTypes: [HTTPREQ_T], result: VOID }, + "http.reqDestroy": { argTypes: [HTTPREQ_T], result: VOID }, + "http.reqOnError": { argTypes: [HTTPREQ_T, null, BOOL], result: VOID }, + "http.reqOnClose": { + argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.reqOnAborted": { + argTypes: [HTTPREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.reqHttpVersion": { argTypes: [HTTPREQ_T], result: STRING }, + "http.reqHttpVersionMajor": { argTypes: [HTTPREQ_T], result: F64 }, + "http.reqHttpVersionMinor": { argTypes: [HTTPREQ_T], result: F64 }, + "http.reqAborted": { argTypes: [HTTPREQ_T], result: BOOL }, + "http.reqComplete": { argTypes: [HTTPREQ_T], result: BOOL }, + "http.resDestroy": { argTypes: [HTTPRES_T], result: VOID }, + "http.resOnClose": { + argTypes: [HTTPRES_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.resWriteHeadPairs": { argTypes: [HTTPRES_T, F64, arrayOf(STRING)], result: VOID }, + /** writeHead(status, headers) with a checked-dynamic headers value — + * the runtime OBJ walk (string/number values; other values throw + * Node's invalid-argument TypeError; may throw). */ + "http.resWriteHeadDyn": { argTypes: [HTTPRES_T, F64, DYN], result: VOID }, + "net.sockSetTimeout": { argTypes: [NETSOCKET_T, F64], result: VOID }, + /** setEncoding('utf8') — 'data' delivers strings inside the chunk-encoding window; other real encodings throw the "not supported yet" Error, unknown names throw ERR_UNKNOWN_ENCODING (may throw). */ + "net.sockSetEncoding": { argTypes: [NETSOCKET_T, STRING], result: VOID }, + "http.reqSetEncoding": { argTypes: [HTTPREQ_T, STRING], result: VOID }, + "net.sockOnTimeout": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "net.sockRemoteAddress": { argTypes: [NETSOCKET_T], result: VOID }, + /** socket.encrypted — `boolean | undefined`: the true arm iff the + * socket carries a TLS transport (Node types `encrypted: true` on + * TLSSocket; plain sockets answer undefined — the proxy.ts isEncrypted + * idiom reads it through a cast). */ + "net.sockEncrypted": { argTypes: [NETSOCKET_T], result: VOID }, + // node:http, the client slice. The response callback shapes are + // program-dependent (checked by validate.ts); header pairs arrive flat. + /** node:http, the CLIENT slice (http.request/http.get over the net + * client machinery): request/requestCb take (host, port, path, method, + * timeoutMs, headerPairs, autoEnd[, responseCb]) — headerPairs is the + * flat [k0,v0,...] array (empty for none), autoEnd true is http.get's + * eager end(). The handle owns one dialed connection (NO pooling — the + * wire still carries Node's exact head: user headers, then Host, + * Connection: keep-alive, and the framing header; the socket closes + * when the response completes). The response delivered to responseCb / + * 'response' listeners IS an httpReq (IncomingMessage), status and + * headers parsed from the wire, body via reqOnData/reqOnEnd. Errors are + * Node-shaped ('connect ECONNREFUSED ip:port', 'socket hang up') and + * fire 'error' then 'close'; an unhandled 'error' exits 1 like every + * net handle. clientWrite before end commits to chunked framing unless + * the caller set content-length; clientEnd(data) before any write sends + * Content-Length exactly like Node. */ + "http.request": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL], + result: HTTPCLIENTREQ_T, + }, + "http.requestCb": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, null], + result: HTTPCLIENTREQ_T, + }, + /** new http.Agent(opts) / new https.Agent(opts): (secure, keepAlive, + * keepAliveMsecs, maxSockets, maxFreeSockets, timeoutMs, port) — the + * numeric options arrive < 0 for "unset" (Infinity/256/none; port + * seeds the settable defaultPort, Node's option merge). Returns the + * Agent as a checked-dynamic HANDLE (getName/destroy and the + * sockets/requests/freeSockets counters dispatch through the dyn + * handle ops). keepAlive: true THROWS the named construction fence — + * socket POOLING is not modeled (one dial per request); maxSockets + * accounting is real: over-limit requests defer their dial and queue. + * MAY THROW. */ + "http.agentNew": { argTypes: [BOOL, BOOL, F64, F64, F64, F64, F64, DYN], result: DYN }, + /** http.globalAgent / https.globalAgent (true): Node's keepAlive global + * agent, the one requests without an agent option use. */ + "http.globalAgent": { argTypes: [BOOL], result: DYN }, + /** http.request's `signal` option over the fresh request: an aborted + * signal destroys it with Node's AbortError now, a later abort event + * does the same. A falsy value is ignored; a non-signal throws + * ERR_INVALID_ARG_TYPE. */ + "http.clientSignal": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, + /** options.signal checked before the request is built (ERR_INVALID_ARG_TYPE). */ + "http.clientSignalCheck": { argTypes: [DYN], result: VOID }, + /** https.request's servername option, stored for the construction call + * that follows: the TLS name sent and verified, and part of the pooled + * socket key. The "https." prefix keeps the TLS unit linked. */ + "https.nextServername": { argTypes: [STRING], result: VOID }, + /** The maxHeaderSize option of createServer / http.request: Node's + * validateInteger (ERR_OUT_OF_RANGE) then the header size bound. + * MAY THROW. */ + "http.serverMaxHeaderSize": { argTypes: [NETSERVER_T, F64], result: VOID }, + "http.clientMaxHeaderSize": { argTypes: [HTTPCLIENTREQ_T, F64], result: VOID }, + /** req.destroy(err): err becomes the request's 'error'. */ + "http.clientDestroyError": { + argTypes: [HTTPCLIENTREQ_T, { kind: "object", className: "%Error" }], + result: VOID, + }, + /** The agent-threaded request rows: the http.request/https.request + * shape with a trailing `agent` dyn argument (an Agent handle, false — + * the one-shot Connection: close dial — or null/undefined for the + * default path). port < 0 means "no port option": the agent's settable + * defaultPort, then the scheme's. MAY THROW (a non-Agent value is + * Node's ERR_INVALID_ARG_TYPE). */ + "http.requestAgent": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, DYN], + result: HTTPCLIENTREQ_T, + }, + "http.requestAgentCb": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, DYN, null], + result: HTTPCLIENTREQ_T, + }, + /** request/get with a URL-STRING first argument: the runtime parses it + * (WHATWG) and dials — throws catchably on an unparsable input or a + * non-http scheme. */ + "http.requestUrl": { argTypes: [STRING, STRING, BOOL], result: HTTPCLIENTREQ_T }, + "http.requestUrlCb": { argTypes: [STRING, STRING, BOOL, null], result: HTTPCLIENTREQ_T }, + /** request/get with a runtime options record: a RequestOptions + * variable, or a literal that spreads one or sets socketPath. Args + * (options, autoEnd[, cb]). The runtime reads the record in the + * ClientRequest constructor's order (host/port or socketPath, path, + * method, headers, timeout, agent, signal, maxHeaderSize) with Node's + * errors, and documented options it does not implement throw the + * runtime fence. The "https." pair adds the TLS options. MAY THROW. */ + "http.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, + "http.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, + "https.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, + "https.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, + /** The paused-mode demux surface (portless's first-byte TLS peek: + * once('readable') + read(1) + unshift + emit('connection')). + * sockOnReadable registers a zero-param listener (a consumer: arrived + * bytes buffer instead of flowing, and EOF announces too); sockRead + * answers `Buffer | null` (exactly n buffered bytes, or null — Node's + * less-than-n answer; n <= 0 drains everything); sockUnshift returns + * bytes to the front of the stream; serverEmitConnection routes a + * socket into another server's protocol layer (a TLS target's + * 'connection' waits for its handshake). */ + "net.sockOnReadable": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, + // sockRead's result is the interned `Buffer | null` union — checked + // specially in validate.ts (the reqHeader/envGet pattern; VOID here is a + // placeholder the special case overrides). + "net.sockRead": { argTypes: [NETSOCKET_T, F64], result: VOID }, + "net.sockUnshift": { argTypes: [NETSOCKET_T, BYTES_U8], result: VOID }, + /** Socket flow control and the compat surface: pause/resume (reads + * gate off/on — kernel backpressure holds paused bytes; resume flows + * and discards sans listeners) and setNoDelay answer the SOCKET (+1, + * Node's chaining); destroySoon ends now and destroys once the FIN + * flushed; bytesWritten counts accepted bytes; readable is true until + * the read half ends. */ + "net.sockPause": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, + "net.sockResume": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, + "net.sockSetNoDelay": { argTypes: [NETSOCKET_T, BOOL], result: NETSOCKET_T }, + "net.sockDestroySoon": { argTypes: [NETSOCKET_T], result: VOID }, + /** RST close, answers the socket; throws ERR_INVALID_HANDLE_TYPE on TLS. */ + "net.sockResetAndDestroy": { argTypes: [NETSOCKET_T], result: NETSOCKET_T }, + /** socket.address(): { address, family, port } or {} as a dyn. */ + "net.sockAddress": { argTypes: [NETSOCKET_T], result: DYN }, + "net.sockBytesWritten": { argTypes: [NETSOCKET_T], result: F64 }, + /** remotePort (0), remoteFamily (1), localAddress (2), localPort (3), + * localFamily (4): dyn values, undefined when unknown. */ + "net.sockAddrField": { argTypes: [NETSOCKET_T, F64], result: DYN }, + "net.sockBytesRead": { argTypes: [NETSOCKET_T], result: F64 }, + "net.sockConnecting": { argTypes: [NETSOCKET_T], result: BOOL }, + "net.sockPending": { argTypes: [NETSOCKET_T], result: BOOL }, + "net.sockReadyState": { argTypes: [NETSOCKET_T], result: STRING }, + "net.sockSetKeepAlive": { argTypes: [NETSOCKET_T, BOOL, F64], result: NETSOCKET_T }, + "net.sockOnReady": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + /** Listeners with a parameter, as dyn functions: close(hadError), + * write's cb(err) and end's cb. */ + "net.sockOnCloseDyn": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, + /** socket.on/once('lookup', listener): the listener (a dyn function) + * gets (err, address, family, host) once per resolved address, or once + * with the lookup error, before the connect. */ + "net.sockOnLookup": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, + "net.sockOnWriteFlush": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "net.sockOnWriteFlushDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, + "net.sockOnFinishDyn": { argTypes: [NETSOCKET_T, DYN], result: VOID }, + "net.sockSetTimeoutCb": { + argTypes: [NETSOCKET_T, F64, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "net.isIP": { argTypes: [DYN], result: F64 }, + "net.isIPv4": { argTypes: [DYN], result: BOOL }, + "net.isIPv6": { argTypes: [DYN], result: BOOL }, + "net.serverMaxConnectionsGet": { argTypes: [NETSERVER_T], result: DYN }, + "net.serverMaxConnectionsSet": { argTypes: [NETSERVER_T, DYN], result: VOID }, + "net.serverDropMaxConnectionGet": { argTypes: [NETSERVER_T], result: DYN }, + "net.serverDropMaxConnectionSet": { argTypes: [NETSERVER_T, DYN], result: VOID }, + /** net.SocketAddress and net.BlockList (scr_net.c): the constructors + * take the argument array; the handles are dyn values. */ + "net.socketAddressNew": { argTypes: [DYN], result: DYN }, + "net.socketAddressParse": { argTypes: [DYN], result: DYN }, + "net.isSocketAddress": { argTypes: [DYN], result: BOOL }, + "net.blockListNew": { argTypes: [DYN], result: DYN }, + "net.isBlockList": { argTypes: [DYN], result: BOOL }, + "net.blockListPrivateRanges": { argTypes: [], result: DYN }, + "net.serverOnDrop": { argTypes: [NETSERVER_T, DYN, BOOL], result: VOID }, + "net.serverGetConnections": { argTypes: [NETSERVER_T, DYN], result: VOID }, + "net.sockReadable": { argTypes: [NETSOCKET_T], result: BOOL }, + /** socket.on('finish', cb) / end(cb): fires once when the FIN goes out + * (sweep-deferred, never the registering stack). */ + "net.sockOnFinish": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "net.serverEmitConnection": { argTypes: [NETSERVER_T, NETSOCKET_T], result: VOID }, + // tls/https: cert/key/ca PEM arguments are strings OR Buffers (null = + // both accepted; the emitter passes data+len either way). + /** node:tls + node:https (scr_tls.c over scr_net.c/scr_http.c, with + * the vendored mbedTLS archive — linked only when one of these appears + * on the IR; moduleUsesTls is the switch, and every tls/https libCall + * also counts as net AND http use so both units link and install). + * tls.createServer takes (cert, key[, handler]) — cert/key are PEM + * strings or Buffers (the emitter passes data+len for either); the + * handler is Node's 'secureConnection' (fires post-handshake with a + * socket that behaves exactly like a net socket, the same adapters as + * net.createServer). https.createServer is (cert, key, handler) with + * the http request-handler adapters. https.request/requestCb extend + * the http client row with (…, rejectUnauthorized: bool, ca: PEM + * string/Buffer — "" for none) and default port 443; everything else + * (write/end/destroy/events, the response surface) IS the http client + * surface — the handles are the same kinds. */ + "tls.createServer": { argTypes: [null, null], result: NETSERVER_T }, + "tls.createServerCb": { argTypes: [null, null, null], result: NETSERVER_T }, + /** The SNICallback forms: (cert, key, SNICallback[, handler]). The + * callback is http2.createSecureServerSni's shape, a function or a + * function | undefined union. */ + "tls.createServerSni": { argTypes: [null, null, null], result: NETSERVER_T }, + "tls.createServerSniCb": { argTypes: [null, null, null, null], result: NETSERVER_T }, + // The runtime options records (divergence 66's stance): a dyn options + // value whose members read at runtime; pemDyn extracts a runtime-valued + // cert/key member (arg 1 is the precomposed fence label). + /** RUNTIME options records (the divergence-66 stance): the *Dyn + * creators take a checked-dynamic (dyn) options value whose members + * read at runtime — cert/key extract like the literal path, members + * whose literal forms fence THROW the catchable fence at runtime, and + * undocumented keys drop like Node drops them. tls.pemDyn is the + * literal walk's runtime-valued cert/key extraction: (value, whatLit) + * → PEM bytes (strings/Buffers/one-element arrays of those) or the + * thrown fence. All of them may throw. */ + "tls.pemDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, + "tls.createServerDyn": { argTypes: [DYN], result: NETSERVER_T }, + "tls.createServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, + "https.createServerDyn": { argTypes: [DYN], result: NETSERVER_T }, + "https.createServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, + /** createSecureServer(options, handler) — the eager COMPAT handler as + * the first 'request' listener (Node's exact route), on both literal + * flavors: Req is the dual ALPN allowHTTP1 server, H2Req the ALPN=h2-only + * server (both use compat handles over h2 streams). Args are + * (cert, key, cb, enableConnectProtocol). */ + "http2.createSecureServerReq": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, + "http2.createSecureServerH2Req": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, + /** createSecureServer with a RUNTIME options record (the divergence-66 + * stance): allowHTTP1/cert/key read at runtime — allowHTTP1 picks the + * flavor, the TLS server walk fences its out-of-bounds members with + * the catchable runtime fence, and h2 session-tuning keys drop exactly + * like the literal walk ignores them. May throw. The Cb form carries + * the eager compat handler. */ + "http2.createSecureServerDyn": { argTypes: [DYN], result: NETSERVER_T }, + "http2.createSecureServerDynCb": { argTypes: [DYN, null], result: NETSERVER_T }, + // tls.connect(port, host, opts[, cb]) — port -1 / host "" read the + // options record; the callback fires post-handshake (secureConnect). + /** tls.connect — the TLS client socket: (port, host, opts[, cb]) where + * port -1 reads options.port, host "" reads options.host, and opts is + * the runtime (dyn) options record (rejectUnauthorized/ca/servername + * implemented; other documented members throw the runtime fence). The + * callback fires post-handshake — Node's secureConnect timing. */ + "tls.connect": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, + "tls.connectCb": { argTypes: [F64, STRING, DYN, null], result: NETSOCKET_T }, + // The TLSSocket member surface (authError's `string | null` union is + // shape-checked in the special cases in validate.ts — the reqHeader pattern). + /** The TLSSocket member surface on the socket kind: authorized (bool — + * Node's verify verdict), authorizationError (the verify-failure CODE + * STRING or null), the 'secureConnect' registration (a TLS socket's + * conn list fires at establishment; plain sockets never fire it), and + * the 'session' registration (fires once with the serialized session — + * a Buffer; the received-ticket event). */ + "tls.sockAuthorized": { argTypes: [NETSOCKET_T], result: BOOL }, + /** getProtocol() (string | null), getCipher() (CipherNameAndProtocol, + * undefined before the handshake, null after close) and alpnProtocol + * (string | false | null), as dyn values. */ + "tls.sockGetProtocol": { argTypes: [NETSOCKET_T], result: DYN }, + "tls.sockGetCipher": { argTypes: [NETSOCKET_T], result: DYN }, + "tls.sockAlpnProtocol": { argTypes: [NETSOCKET_T], result: DYN }, + /** getPeerCertificate(detailed) and getCertificate() (the legacy object, + * {} or null; scr_tls_x509.c), and the DER behind getPeerX509Certificate() + * and getX509Certificate() (a Buffer, empty without a certificate). */ + "tls.sockGetPeerCertificate": { argTypes: [NETSOCKET_T, DYN], result: DYN }, + "tls.sockGetCertificate": { argTypes: [NETSOCKET_T], result: DYN }, + "tls.sockPeerCertDer": { argTypes: [NETSOCKET_T], result: BYTES_U8 }, + "tls.sockOwnCertDer": { argTypes: [NETSOCKET_T], result: BYTES_U8 }, + "tls.sockAuthError": { argTypes: [NETSOCKET_T], result: VOID }, + "tls.sockOnSecureConnect": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "tls.sockOnSession": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID }, + /** The 'secure' event: the handshake completed (after secureConnect on + * a client). Plain sockets never fire it. */ + "tls.sockOnSecure": { + argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + /** new tls.TLSSocket(socket, options) in server mode: a new TLS socket + * takes over the socket's connection (wrapSocketCtx passes a literal's + * secureContext member apart from the runtime options record). The + * record reads at runtime: isServer, cert, key, ALPNProtocols, + * minVersion and maxVersion are implemented, the client mode and other + * documented members throw the runtime fence. MAY THROW. */ + "tls.wrapSocket": { argTypes: [NETSOCKET_T, DYN], result: NETSOCKET_T }, + "tls.wrapSocketCtx": { argTypes: [NETSOCKET_T, DYN, SECURECTX_T], result: NETSOCKET_T }, + // createSecureContext({ cert, key }) mints the opaque SNI-answer handle. + /** tls.createSecureContext({ cert, key }) — parses the PEM pair into an + * opaque SecureContext handle (secureCtx kind) for SNI callbacks to + * answer with; cert/key are PEM strings or Buffers like createServer's. */ + "tls.createSecureContext": { argTypes: [null, null], result: SECURECTX_T }, + /** createSecureContext over a RUNTIME options record (the checked- + * dynamic lane): Node's typed option validations first (the ciphers/ + * passphrase/engine/version/timeout/ticketKeys ladders), then the pem + * walk — a validated { cert, key } bag builds the real context. + * May-throw. */ + "tls.createSecureContextDyn": { argTypes: [DYN], result: SECURECTX_T }, + /** server.addContext(servername, options | SecureContext), + * server.setSecureContext(options) and the 'tlsClientError' listener of + * a tls or https server (scr_tls.c). */ + "tls.serverAddContext": { argTypes: [NETSERVER_T, DYN, DYN], result: VOID }, + "tls.serverAddContextCtx": { argTypes: [NETSERVER_T, DYN, SECURECTX_T], result: VOID }, + "tls.serverSetSecureContext": { argTypes: [NETSERVER_T, DYN], result: VOID }, + "tls.serverOnClientError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + /** tls.getCACertificates(type): validateString + the documented name + * set, then the trailing compiler-rendered fence — ALWAYS THROWS (the + * error.nodeThrow polymorphic-result carve-out). May-throw seed. */ + "tls.caCertsChk": { argTypes: [DYN, STRING], result: VOID }, + // The CA-store introspection unit (scr_tls_ca.c): per-type cached PEM + // string arrays and the default-set replacement. + /** The CA-store introspection surface (scr_tls_ca.c — its own unit and + * link gate, "tlsca." NOT "tls.", so a getCACertificates-only binary + * never pulls mbedTLS): tlsca.get is tls.getCACertificates(type) — the + * cached per-type string[] of PEM blocks (identity-stable across calls, + * Node's caching; an unknown type throws Node's ERR_INVALID_ARG_VALUE + * TypeError); tlsca.root is the tls.rootCertificates value read; + * tlsca.set is tls.setDefaultCACertificates(certs) — replaces the + * default set (deduped) and the anchors the TLS client verifies + * against, throwing Node's ERR_CRYPTO_OPERATION_FAILED when no entry + * carries a certificate block. */ + "tlsca.get": { argTypes: [STRING], result: arrayOf(STRING) }, + "tlsca.root": { argTypes: [], result: arrayOf(STRING) }, + "tlsca.set": { argTypes: [arrayOf(STRING)], result: VOID }, + "https.createServer": { argTypes: [null, null, null], result: NETSERVER_T }, + // http2's allowHTTP1 compatibility server (divergence 57): cert/key + // like tls.createServer; the 'request' handler arrives separately via + // http.serverOnRequest (shape checked in the libCall case, like + // http.createServer's). sessionError callback shape is checked by validate.ts. + /** node:http2 allowHTTP1: one TLS server advertises h2 + http/1.1 and + * dispatches to the matching parser after ALPN. Request listeners mirror + * into both compatibility lists; the remaining session/req.stream rows + * below retain their explicitly limited behavior. */ + "http2.createSecureServer": { argTypes: [null, null, BOOL], result: NETSERVER_T }, + // The SNI-callback form: arg 2 is the JS SNICallback closure — a + // `(servername, cb) => void` func, or its `| undefined` union from the + // conditional-spread spelling (the libCall case checks the shape). + /** createSecureServer with an SNI callback: args are (cert, key, sniCb) + * where sniCb is the JS SNICallback — a `(servername, cb) => void` + * closure, or the `SNICallback | undefined` union from the conditional- + * spread spelling (`...(x ? { SNICallback: x } : {})`; the emitter + * unwraps the union — the undefined arm means "no callback", exactly + * the no-SNI server). The runtime parses each connection's ClientHello + * for the server_name extension BEFORE the TLS handshake begins, calls + * the callback with (servername, answer-closure), and resumes the + * handshake when the answer arrives — cb(err) tears the socket down + * without an error event (Node's 'tlsClientError' default), cb(null, ctx) serves + * ctx's cert/key, cb(null, undefined) serves the default pair. */ + "http2.createSecureServerSni": { argTypes: [null, null, null, BOOL], result: NETSERVER_T }, + // The ALPN=h2 server (createSecureServer without allowHTTP1): the real + // h2 session machinery behind the TLS handshake. + /** The REAL h2-over-TLS server (createSecureServer WITHOUT allowHTTP1): + * scr_http2_create_secure_server — the h2c session machinery behind an + * mbedTLS handshake whose ALPN advertises h2 alone (an http/1.1-only + * client fails the handshake with no_application_protocol, Node's + * h2-only split). args are (cert, key) — PEM strings or Buffers. */ + "http2.createSecureServerH2": { argTypes: [null, null, BOOL], result: NETSERVER_T }, + "http.serverOnRequest": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + /** server.on('checkContinue' | 'checkExpectation', (req, res)): the + * 'request' listener shape; the last argument picks checkExpectation. */ + "http.serverOnCheck": { argTypes: [NETSERVER_T, null, BOOL, BOOL], result: VOID }, + "http2.serverOnSessionError": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + /** The guarded absent-session compatibility call remains a no-op. */ + "http2.streamNoop": { argTypes: [], result: VOID }, + /** The UNGUARDED h2-only stream call (`req.stream.on(...)`): stream is + * undefined on every connection the allowHTTP1 lowering accepts — and + * on every HTTP/1.1 connection of Node's own allowHTTP1 server — so the + * call IS Node's member read on undefined: throws the exact catchable + * TypeError ("Cannot read properties of undefined (reading 'on')"). + * One arg: the read member's name (a string literal). Never returns. */ + "http2.streamUndefCall": { argTypes: [STRING], result: VOID }, + // The REAL h2c surface (scr_http2.c). Callback slots are null (the + // emitter picks the thunk); pairs slots are string arrays; the + // endStream tri-state and event flags ride as f64. + /** node:http2, the REAL h2c surface (scr_http2.c — frame codec + HPACK + * over the net loop; the design note atop that file has the story). + * Sessions and streams are first-class handle kinds; 'stream'/'response' + * payloads cross as flat [name, value, ...] pairs arrays and an EMITTED + * adapter closure builds the program-side headers record (the response + * :status rides separately as a number). */ + "http2.createServer": { argTypes: [], result: NETSERVER_T }, + "http2.createServerReq": { argTypes: [null], result: NETSERVER_T }, + "http2.serverOnStream": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + "http2.serverOnSession": { argTypes: [NETSERVER_T, null, BOOL], result: VOID }, + // connect(authority, reject, ca[, cb]): the TLS client knobs an https + // authority reads (inert on http ones, exactly Node); ca is a PEM + // string/Buffer ("" = the system anchors). + /** connect(authority[, listener]) — h2c prior knowledge; the listener + * closure (if any) is the 'connect' once-listener. */ + "http2.connect": { argTypes: [STRING, BOOL, null], result: HTTP2SESSION_T }, + "http2.connectCb": { argTypes: [STRING, BOOL, null, null], result: HTTP2SESSION_T }, + /** session.request(pairs?, endStream) — endStream is a tri-state f64: + * -1 the method's payload-meaningless default, 0/1 explicit. */ + "http2.sessionRequest": { + argTypes: [HTTP2SESSION_T, arrayOf(STRING), F64], + result: HTTP2STREAM_T, + }, + "http2.sessionClose": { argTypes: [HTTP2SESSION_T], result: VOID }, + "http2.sessionCloseCb": { argTypes: [HTTP2SESSION_T, null], result: VOID }, + "http2.sessionDestroy": { argTypes: [HTTP2SESSION_T], result: VOID }, + "http2.sessionOnClose": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, + "http2.sessionOnError": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, + "http2.sessionOnConnect": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, + "http2.sessionOnStream": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, + "http2.sessionOnGoaway": { argTypes: [HTTP2SESSION_T, null, BOOL], result: VOID }, + "http2.sessionClosed": { argTypes: [HTTP2SESSION_T], result: BOOL }, + "http2.sessionDestroyed": { argTypes: [HTTP2SESSION_T], result: BOOL }, + "http2.sessionEncrypted": { argTypes: [HTTP2SESSION_T], result: BOOL }, + "http2.sessionType": { argTypes: [HTTP2SESSION_T], result: F64 }, + "http2.sessionAlpn": { argTypes: [HTTP2SESSION_T], result: STRING }, + "http2.sessionSocket": { argTypes: [HTTP2SESSION_T], result: NETSOCKET_T }, + /** stream.respond(pairs?, endStream) — the server answer. */ + "http2.streamRespond": { argTypes: [HTTP2STREAM_T, arrayOf(STRING), BOOL], result: VOID }, + "http2.streamWrite": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, + "http2.streamWriteBytes": { argTypes: [HTTP2STREAM_T, BYTES_U8], result: VOID }, + "http2.streamEnd": { argTypes: [HTTP2STREAM_T], result: VOID }, + "http2.streamEndStr": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, + "http2.streamEndBytes": { argTypes: [HTTP2STREAM_T, BYTES_U8], result: VOID }, + "http2.streamClose": { argTypes: [HTTP2STREAM_T, F64], result: VOID }, + "http2.streamCloseCb": { argTypes: [HTTP2STREAM_T, F64, null], result: VOID }, + "http2.streamDestroy": { argTypes: [HTTP2STREAM_T], result: VOID }, + "http2.sessionSettings0": { argTypes: [HTTP2SESSION_T], result: VOID }, + "http2.sessionSettings": { argTypes: [HTTP2SESSION_T, DYN], result: VOID }, + "http2.sessionSettingsDynCb": { argTypes: [HTTP2SESSION_T, DYN, DYN], result: VOID }, + "http2.sessionSettingsCb0": { + argTypes: [HTTP2SESSION_T, DYN, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "http2.sessionOnSettingsDyn": { argTypes: [HTTP2SESSION_T, DYN, BOOL, BOOL], result: VOID }, + "http2.sessionOnSettings0": { + argTypes: [HTTP2SESSION_T, { kind: "func", params: [], ret: VOID }, BOOL, BOOL], + result: VOID, + }, + "http2.sessionSettingsGet": { argTypes: [HTTP2SESSION_T, BOOL], result: DYN }, + "http2.sessionPendingSettingsAck": { argTypes: [HTTP2SESSION_T], result: BOOL }, + "http2.getDefaultSettings": { argTypes: [], result: DYN }, + "http2.streamSetEncoding": { argTypes: [HTTP2STREAM_T, STRING], result: VOID }, + "http2.streamSetEncodingRet": { argTypes: [HTTP2STREAM_T, STRING], result: HTTP2STREAM_T }, + "http2.streamResume": { argTypes: [HTTP2STREAM_T], result: VOID }, + "http2.streamPause": { argTypes: [HTTP2STREAM_T], result: VOID }, + "http2.streamOnData": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamOnEnd": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamOnClose": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamOnAborted": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamOnError": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamOnResponse": { argTypes: [HTTP2STREAM_T, null, BOOL], result: VOID }, + "http2.streamId": { argTypes: [HTTP2STREAM_T], result: F64 }, + "http2.streamRstCode": { argTypes: [HTTP2STREAM_T], result: F64 }, + "http2.streamDestroyed": { argTypes: [HTTP2STREAM_T], result: BOOL }, + "http2.streamClosed": { argTypes: [HTTP2STREAM_T], result: BOOL }, + "http2.streamAborted": { argTypes: [HTTP2STREAM_T], result: BOOL }, + "http2.streamPending": { argTypes: [HTTP2STREAM_T], result: BOOL }, + "http2.streamSession": { argTypes: [HTTP2STREAM_T], result: HTTP2SESSION_T }, + // The createConnection forms: arg 0 is the dialer closure (() => + // Socket — the libCall case checks), then path/method/timeout/headers/ + // autoEnd like http.request. + /** The createConnection form (the proxy's own dialer): args are + * (connCb, path, method, timeout, headers, autoEnd[, cb]) — connCb is + * a `() => net.Socket` closure the runtime invokes ONCE, synchronously + * (Node's onSocket timing); everything else matches http.request. The + * Host header defaults to "localhost" — a headers.host entry wins + * verbatim, the proxy shape. */ + "http.requestConn": { + argTypes: [null, STRING, STRING, F64, arrayOf(STRING), BOOL], + result: HTTPCLIENTREQ_T, + }, + "http.requestConnCb": { + argTypes: [null, STRING, STRING, F64, arrayOf(STRING), BOOL, null], + result: HTTPCLIENTREQ_T, + }, + "https.request": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null], + result: HTTPCLIENTREQ_T, + }, + "https.requestCb": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, null], + result: HTTPCLIENTREQ_T, + }, + /** The URL-string first argument, the http.requestUrl row over TLS: + * no options means Node's defaults (verification on, default trust + * anchors), and a non-https scheme is ERR_INVALID_PROTOCOL. */ + "https.requestUrl": { argTypes: [STRING, STRING, BOOL], result: HTTPCLIENTREQ_T }, + "https.requestUrlCb": { argTypes: [STRING, STRING, BOOL, null], result: HTTPCLIENTREQ_T }, + "https.requestAgent": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, DYN], + result: HTTPCLIENTREQ_T, + }, + "https.requestAgentCb": { + argTypes: [STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, DYN, null], + result: HTTPCLIENTREQ_T, + }, + // The requestFn binding's runtime-secure rows: https.request's shape + // with the leading `secure` bool. + /** A call through a `const requestFn = tls ? https.request : + * http.request` binding — the module-function-as-value ternary between + * the two known clients: the https.request row with a leading `secure` + * bool that picks the dial at RUNTIME (true = the TLS client, exactly + * https.request; false = the plain client, exactly http.request — + * rejectUnauthorized/ca ignored there like Node ignores TLS options on + * http.request). The "https." prefix keeps the TLS unit linked. */ + "https.requestFn": { + argTypes: [BOOL, STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null], + result: HTTPCLIENTREQ_T, + }, + "https.requestFnCb": { + argTypes: [BOOL, STRING, F64, STRING, STRING, F64, arrayOf(STRING), BOOL, BOOL, null, null], + result: HTTPCLIENTREQ_T, + }, + "http.clientWrite": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, + "http.clientSetHeader": { argTypes: [HTTPCLIENTREQ_T, STRING, STRING], result: VOID }, + "http.clientGetHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, + "http.clientHasHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: BOOL }, + "http.clientRemoveHeader": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, + "http.clientGetHeaderNames": { argTypes: [HTTPCLIENTREQ_T], result: arrayOf(STRING) }, + "http.clientGetRawHeaderNames": { argTypes: [HTTPCLIENTREQ_T], result: arrayOf(STRING) }, + "http.clientGetHeaders": { argTypes: [HTTPCLIENTREQ_T], result: DYN }, + "http.clientWriteBytes": { argTypes: [HTTPCLIENTREQ_T, BYTES_U8], result: VOID }, + "http.clientEnd": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientEndStr": { argTypes: [HTTPCLIENTREQ_T, STRING], result: VOID }, + "http.clientEndBytes": { argTypes: [HTTPCLIENTREQ_T, BYTES_U8], result: VOID }, + /** The checked-dynamic chunk twins (the net.sockWriteDyn story). */ + "http.clientWriteDyn": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, + "http.clientEndDyn": { argTypes: [HTTPCLIENTREQ_T, DYN], result: VOID }, + "http.clientFlushHeaders": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientAddTrailers": { argTypes: [HTTPCLIENTREQ_T, arrayOf(STRING)], result: VOID }, + "http.clientCork": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientUncork": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientWritableCorked": { argTypes: [HTTPCLIENTREQ_T], result: F64 }, + "http.clientMethod": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, + "http.clientPath": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, + "http.clientHost": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, + "http.clientProtocol": { argTypes: [HTTPCLIENTREQ_T], result: STRING }, + "http.clientHeadersSent": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientWritableEnded": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientWritableFinished": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientSocket": { argTypes: [HTTPCLIENTREQ_T], result: NETSOCKET_T }, + "http.clientReusedSocket": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientSetNoDelay": { argTypes: [HTTPCLIENTREQ_T, BOOL], result: VOID }, + "http.clientSetSocketKeepAlive": { argTypes: [HTTPCLIENTREQ_T, BOOL, F64], result: VOID }, + "http.clientSetTimeout": { argTypes: [HTTPCLIENTREQ_T, F64], result: VOID }, + "http.clientSetTimeoutCb": { + argTypes: [HTTPCLIENTREQ_T, F64, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "http.statusCodes": { argTypes: [], result: DYN }, + "http.methods": { argTypes: [], result: arrayOf(STRING) }, + "http.reqSetTimeout": { argTypes: [HTTPREQ_T, F64], result: VOID }, + "http.reqSetTimeoutCb": { + argTypes: [HTTPREQ_T, F64, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, + "http.clientDestroy": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientAbort": { argTypes: [HTTPCLIENTREQ_T], result: VOID }, + "http.clientAborted": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientDestroyed": { argTypes: [HTTPCLIENTREQ_T], result: BOOL }, + "http.clientOnResponse": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, + "http.clientOnSocket": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, + "http.clientOnFinish": { + argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.clientOnError": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID }, + "http.clientOnTimeout": { + argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.clientOnClose": { + argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.clientOnAbort": { + argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + "http.clientOnContinue": { + argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], + result: VOID, + }, + /** execFileSync/execSync as ONE entry (args: cmd, argv, shell, input, + * cwd, hasEnv, envPairs, timeoutMs, stdoutMode, stderrMode, buffers — + * see scr_child.h). Throws Node's exact errors: "Command failed: " + * (+ captured stderr) on non-zero exit or signal death, "spawnSync + * ENOENT" on spawn failure, "spawnSync ETIMEDOUT" after + * the SIGTERM timeout, with Node's status, signal, output, pid, stdout + * and stderr properties (Buffer outputs when buffers is set: no + * encoding option). Result is the captured utf8 stdout (+1). */ + "cp.execSync": { + argTypes: [ + STRING, + arrayOf(STRING), + BOOL, + STRING, + BOOL, + STRING, + BOOL, + arrayOf(STRING), + F64, + F64, + F64, + BOOL, + ], + result: STRING, + }, + /** The promisified-execFile/exec capture (args: cmd, argv, shell, cwd, + * hasEnv, envPairs, timeoutMs; shell marks exec's /bin/sh -c argv, as + * for execSync, so failures display the command string): the same exec + * core in the async shape — both streams captured, no echo, Node's ASYNC messages on the throw paths + * ("Command failed: \n" with the unconditional newline, + * "spawn ENOENT" with .code, timeouts reporting as ordinary + * SIGTERM command failures — never ETIMEDOUT). Result reuses the + * ScrSpawnRes container (+1; stdout/stderr strings, status unused). + * Called only from the frontend's interned %execFileAsync and + * %execAsync ASYNC helpers, whose fiber turns the throw into the + * rejection. */ + "cp.execCapture": { + argTypes: [STRING, arrayOf(STRING), BOOL, STRING, BOOL, arrayOf(STRING), F64], + result: SPAWNRES_T, + }, + /** child_process.spawn (scr_child.c + the scr_async.c loop): posix_spawnp + * with per-slot ignore/inherit/pipe modes (and number output fds), the + * all-piped Node default, and the child registered with the event loop, + * which polls waitpid(WNOHANG) at quiescence like timers. + * NEVER throws: spawn failure defers to the "error" event, Node-exact + * (the error message is Node's "spawn "; an "error" + * event with no listener prints it and exits 1 like an EventEmitter). + * cmd/args borrowed; result an owned (+1) child handle. The loop will + * not exhaust while any spawned child is unreaped — Node's keep-alive. + * + * child.onExit / child.onClose / child.onError — terminal listener + * registration through child.on/once. Close uses the exit adapter shape + * but fires only after every piped stdio handle reaches EOF. The + * receiver is borrowed, the CALLBACK MOVES into the child's listener + * registry (released after the terminal event fires, or at reap for + * the event that never fires). Both are void (chaining is fenced). + * onExit's third emitted ingredient is an ADAPTER the backend interns + * per callback shape: the runtime invokes adapter(cb, has_code, code) + * and the adapter builds the `number | null` union (tags are program- + * dependent) or ignores the code for a zero-param callback. onError's + * adapters are runtime-provided (zero-param, or the %Error one-param + * shape — scr_error_new needs no program types). "exit" fires once + * with the code (f64 arm) or null (signal death); "error" fires only + * for spawn failure, exactly Node's split. */ + "cp.spawn": { argTypes: [STRING, arrayOf(STRING)], result: CHILD_T }, + /** Static-native fork startup and IPC. process.forkTarget initializes a + * re-executed child's inherited channel and returns its embedded target id + * (-1 in the parent). The remaining entries mirror Node's JSON channel. */ + "process.forkTarget": { argTypes: [F64], result: F64 }, + "cp.fork": { + argTypes: [F64, arrayOf(STRING), F64, F64, F64, BOOL, arrayOf(STRING), STRING], + result: CHILD_T, + }, + "child.connected": { argTypes: [CHILD_T], result: BOOL }, + "child.hasChannel": { argTypes: [CHILD_T], result: BOOL }, + "child.send": { argTypes: [CHILD_T, STRING], result: BOOL }, + "child.sendCb": { argTypes: [CHILD_T, STRING, null], result: BOOL }, + "child.disconnect": { argTypes: [CHILD_T], result: VOID }, + "child.onMessage": { argTypes: [CHILD_T, null, BOOL], result: VOID }, + "child.onDisconnect": { argTypes: [CHILD_T, null, BOOL], result: VOID }, + "child.onSpawn": { argTypes: [CHILD_T, null], result: VOID }, + /** Rest listeners use the checked function bridge to pack event arguments. */ + "child.onDyn": { argTypes: [CHILD_T, STRING, DYN], result: VOID }, + // Own properties JavaScript adds to children (handle-properties.ts). + "child.propsInstall": { argTypes: [], result: VOID }, + "child.propsDefine": { argTypes: [DYN, DYN, DYN], result: DYN }, + "child.propsDescriptor": { argTypes: [DYN, DYN], result: DYN }, + "child.propsResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "child.propsOnDyn": { argTypes: [CHILD_T, STRING, DYN], result: VOID }, + "process.connected": { argTypes: [], result: BOOL }, + /** Whether this fork child opened its channel; part of process.send's + * presence in programs with fork targets. Never throws. */ + "process.ipcOpen": { argTypes: [], result: BOOL }, + "process.send": { argTypes: [STRING], result: BOOL }, + "process.sendCb": { argTypes: [STRING, null], result: BOOL }, + "process.disconnect": { argTypes: [], result: VOID }, + "process.onMessage": { argTypes: [null, BOOL], result: VOID }, + "process.onDisconnect": { argTypes: [null, BOOL], result: VOID }, + /** child_process.execFile's callback slice: starts an all-piped child, + * captures stdout/stderr, and moves the error-first callback into the + * child registry. The callback shape is program-dependent and checked + * by the validator/backends. The last argument is exec's shell flag: + * the argv is ["-c", command] and failure messages show the command. */ + "cp.execFile": { argTypes: [STRING, arrayOf(STRING), null, BOOL], result: CHILD_T }, + // spawn's options form: per-slot stdio modes (0 ignore / 1 inherit / + // 2 fd) with the out/err fds for mode 2, detached, env replacement + // pairs, cwd ("" = inherit). + /** spawn with options (scr_child.c): the cp.spawn core plus PER-SLOT + * stdio — args are (cmd, argv, inMode, outMode, errMode, outFd, errFd, + * detached, hasEnv, envPairs, cwd); modes 0 ignore (/dev/null), 1 + * inherit, 2 fd (out/err only — the fd dup2s into the child's slot, + * Node's stdio fd form; the daemon-log idiom ["ignore", logFd, logFd]). + * detached is POSIX_SPAWN_SETSID (the child gets its own session and + * process group, Node's semantics), env is a REPLACEMENT ([k,v,...] + * pairs like cp.execSync's), cwd ""=inherit. Same event/loop story as + * cp.spawn. */ + "cp.spawnOpts": { + argTypes: [ + STRING, + arrayOf(STRING), + F64, + F64, + F64, + F64, + F64, + BOOL, + BOOL, + BOOL, + arrayOf(STRING), + STRING, + ], + result: CHILD_T, + }, + /** Runtime options normalized into the native spawn core; unsupported + * process features retain explicit refusals. (command, args, options). */ + "cp.spawnDynamic": { argTypes: [STRING, arrayOf(STRING), DYN], result: CHILD_T }, + // The callback's func type is program-dependent (zero params, or the + // `number | null` union / the %Error class) — the libCall case checks + // the shape; the slot here only pins arity and the child receiver. + "child.onExit": { argTypes: [CHILD_T, null], result: VOID }, + "child.onClose": { argTypes: [CHILD_T, null], result: VOID }, + "child.onError": { argTypes: [CHILD_T, null], result: VOID }, + // Like process.envGet, spawnRes.status's result type is program-dependent + // (the interned `number | null` union) — the libCall case checks the arms. + "spawnRes.status": { argTypes: [SPAWNRES_T], result: VOID }, + // spawnRes.error's result is the interned `Error | undefined` union — + // the libCall case checks the arms, the spawnRes.status pattern. + /** Node's spawn-failure carrier `error?: Error`: a fresh +1 %Error + * ("spawnSync ENOENT", `code` stamped) when the spawn itself + * failed, the interned undefined arm otherwise — the result type is the + * call site's `Error | undefined` union, constructed type-directedly in + * the backend (the envGet convention). Never throws. */ + "spawnRes.error": { argTypes: [SPAWNRES_T], result: VOID }, + // spawnRes.signal's result is the interned `string | null` union — + // same pattern. + "spawnRes.signal": { argTypes: [SPAWNRES_T], result: VOID }, + // The ChildProcess lifecycle members: pid/exitCode are program-dependent + // unions (`number | undefined` / `number | null` — the libCall case + // checks the arms, the spawnRes.status pattern). + /** The ChildProcess lifecycle members (scr_child.c), Node's shapes + * exactly (SEMANTICS.md has the pinned matrix). child.pid is the + * checker's `number | undefined` (undefined = spawn failure) and + * child.exitCode its `number | null` (null while running and after a + * signal death; -errno once a spawn failure settled) — both unions are + * type-directed constructions in the backend over a has/get runtime + * pair, the spawnRes.status pattern. child.killed is Node's + * sent-a-signal flag. child.kill sends while the child is un-reaped + * (false after — Node's null-handle answer) and THROWS the + * Unknown-signal TypeError on bad names (may-throw); killNum passes + * numbers through (0 probes; never throws). child.unref drops the + * child from the loop's keep-alive set — it is still reaped while the + * loop runs for other reasons, and one the loop never reaps is left to + * the OS at exit. All receivers borrowed. */ + "child.pid": { argTypes: [CHILD_T], result: VOID }, + "child.exitCode": { argTypes: [CHILD_T], result: VOID }, + // The close-override pair: the bound close's result func type and the + // override's func argument are program-dependent (the callback union's + // tags) — the slots pin the server receiver; the libCall case checks + // the bound value's shape. + /** The close-override pair (the portless close-proxy idiom). + * serverCloseBind is `wrapper.close.bind(wrapper)` as a VALUE: a + * compiler-emitted closure over the server whose invocation runs the + * REAL close (scr_net_server_close_direct — never the override), so + * the override body's `origClose(cb)` cannot recurse; its callback + * argument (the `((err?: Error) => void) | undefined` union) registers + * as a once-'close' listener, a one-param callback wrapped by an + * emitted zero-arg trampoline firing the undefined arm (a clean close + * carries no error). serverSetCloseOverride is `wrapper.close = fn`: + * the override MOVES in behind an emitted zero-arg wrapper (it invokes + * the user function with the undefined-arm callback — tags are program + * data), and server.close() consults it before closing. */ + "net.serverCloseBind": { argTypes: [NETSERVER_T], result: VOID }, + "net.serverSetCloseOverride": { argTypes: [NETSERVER_T, null], result: VOID }, + // The piped-output stream reads: `Readable | null` unions (the libCall + // case checks the arms — the child.pid pattern with a ref arm). + /** The piped-output streams (stdio mode 3 — scr_child.c's stream + * slice). child.stdout/child.stderr answer the checker's + * `Readable | null` union (type-directed construction in the backend + * over the +1-or-NULL runtime pair — the child.pid pattern with a ref + * arm). stream.onData/onEnd register 'data'/'end' listeners (receiver + * borrowed, CALLBACK MOVES, trailing once-flag, void — chaining + * fenced): 'data' fires one Buffer chunk per read (zero-param and + * Buffer/string adapters are runtime-provided; setEncoding threads + * split multibyte sequences through the shared StringDecoder core. A + * union-param listener — + * ngrok's `Buffer | string` — gets a compiler-emitted adapter wrapping + * the chunk at its Buffer arm), 'end' fires once at EOF, always BEFORE + * the child's 'exit' (the pinned ordering). A flowing stream keeps the + * loop alive: usesTimers. */ + "child.stdout": { argTypes: [CHILD_T], result: VOID }, + "child.stderr": { argTypes: [CHILD_T], result: VOID }, + /** The piped-input writer (stdio mode 3 on fd 0). child.stdin answers + * `Writable | null`; writes copy borrowed data into the nonblocking + * queue, end/destroy settle it, writable is a pure state read, and + * listener callbacks move into the writer registry. */ + "child.stdin": { argTypes: [CHILD_T], result: VOID }, + /** Under the general stream representation (general-streams.ts), a + * stdio read (slot 0, 1 or 2) answers the child's bridged runtime stream + * — `Writable | null` for stdin, `Readable | null` otherwise; the bridge + * throws when the program lacks the stream unit's boxing helpers. */ + "child.stdioStream": { argTypes: [CHILD_T, F64], result: VOID }, + // Listener registrations: callback func shapes are program-dependent + // (zero-param / Buffer / string / unknown / Buffer-armed union) — the slots pin arity, + // the stream receiver, and the once-flag. + "stream.onData": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, + "stream.onDataStr": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, + "stream.onEnd": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, + "stream.childSetEncoding": { argTypes: [CHILDSTREAM_T, STRING], result: CHILDSTREAM_T }, + "writer.writeString": { argTypes: [CHILDWRITER_T, STRING], result: BOOL }, + "writer.writeBytes": { argTypes: [CHILDWRITER_T, BYTES_U8], result: BOOL }, + "writer.end": { argTypes: [CHILDWRITER_T], result: VOID }, + "writer.destroy": { argTypes: [CHILDWRITER_T], result: VOID }, + "writer.writable": { argTypes: [CHILDWRITER_T], result: BOOL }, + "writer.onDrain": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, + "writer.onFinish": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, + "writer.onError": { argTypes: [CHILDWRITER_T, null, BOOL], result: VOID }, + /** The first-class WritableStream write (`output.write(line)` — the + * prefixStream idiom): the receiver IS the fd scalar (process.stdout/ + * stderr reads mint 1/2), dispatched onto the exact stdoutWrite/ + * stderrWrite paths so buffering and ordering stay identical. Data + * borrowed; the bool is Node's always-true backpressure signal. */ + "procStream.write": { argTypes: [PROCSTREAM_T, STRING], result: BOOL }, + /** process.stdout/stderr flowing into a runtime Writable slot under the + * general stream representation: an fd-backed Writable (+1) whose + * writes take process.stdout.write's synchronous path. */ + /** A pipeline() stage that is a checked-dynamic value: a boxed native + * stream unwraps by identity (+1); anything else throws the given + * not-supported message. Typed as a Duplex so every stage position + * accepts it; the runtime pipeline checks the stream's real sides. */ + "stream.unboxDyn": { + argTypes: [DYN, STRING], + result: { kind: "object", className: "%Duplex" }, + }, + /** Whether pipeline() pumps a checked-dynamic first stage as an + * iterable (a string, or a value with an iterator method that is not a + * stream with pipe() and on()) instead of piping it as a stream. */ + "stream.dynIterableSource": { argTypes: [DYN], result: BOOL }, + /** The Transform a Cipheriv or Decipheriv reads and writes as a stream + * (+1, created on first use and owned by the cipher). */ + "stream.handleStream": { + argTypes: [DYN], + result: { kind: "object", className: "%Transform" }, + }, + /** Checked-dynamic stream slots (dyn-streams.ts): the native stream a + * checked-dynamic value boxes (+1) — a runtime stream with the readable + * (1) or writable (2) side — or null for any other value. Never throws; + * the result union is `%Readable | null` or `%Writable | null`. */ + "stream.dynNative": { argTypes: [DYN, F64], result: VOID }, + /** stream.dynNative for a pipe or unpipe destination: also a + * constructor-function stream's runtime stream, in programs that link + * that unit, unless it overrides write() or end(). */ + "stream.dynNativePipe": { argTypes: [DYN, F64], result: VOID }, + /** readable.pipe(destination, end) into a checked-dynamic destination + * that is not a native stream: [source, the source boxed, destination, + * end]. Node's pipe listener protocol over the destination's members + * (scr_stream_dyn.c); throws what those members throw. */ + "stream.pipeDyn": { argTypes: [null, DYN, DYN, BOOL], result: VOID }, + /** readable.unpipe(destination) for a checked-dynamic destination that + * is not a native stream: [source, destination]. Unpipes the pipe + * stream.pipeDyn registered into it, if any (scr_stream_dyn.c). */ + "stream.unpipeDyn": { argTypes: [null, DYN], result: VOID }, + "stream.stdioWritable": { + argTypes: [PROCSTREAM_T], + result: { kind: "object", className: "%Writable" }, + }, + "child.killed": { argTypes: [CHILD_T], result: BOOL }, + "child.kill": { argTypes: [CHILD_T, STRING], result: BOOL }, + "child.killNum": { argTypes: [CHILD_T, F64], result: BOOL }, + "child.unref": { argTypes: [CHILD_T], result: VOID }, + "child.ref": { argTypes: [CHILD_T], result: VOID }, + "spawnRes.stdout": { argTypes: [SPAWNRES_T], result: STRING }, + "spawnRes.stderr": { argTypes: [SPAWNRES_T], result: STRING }, + /** fs/promises (scr_lib.c over scr_async.c's settled minting): the SAME + * sync syscalls, wrapped in an ALREADY-SETTLED promise — failure + * REJECTS (catchable at the await) instead of throwing, so none of + * these are in the may-throw seed. readFile is utf8-fenced like + * readFileSync. The non-interleaving divergence is documented in + * SEMANTICS.md. */ + /** node:crypto randomness: randomUUID plus randomBytes as either a real + * Buffer or the fused randomBytes(n).toString("hex"|"base64") path. + * The size-taking forms throw Node's RangeError on invalid values. */ + "crypto.randomUUID": { argTypes: [], result: STRING }, + "crypto.randomBytesToString": { argTypes: [F64, STRING], result: STRING }, + /** crypto.randomBytes(n) → a real u8 Buffer (+1). THROWS Node's + * RangeError on out-of-range sizes, exactly like the composed + * randomBytesToString (which keeps its one-libCall lowering — the two + * coexist: the composed form never materializes the Buffer). */ + "crypto.randomBytes": { argTypes: [F64], result: BYTES_U8 }, + /** The fused createHash(alg).update(data).digest(enc) fast path and the + * one-shot crypto.hash implementation. First-class Hash/Hmac handles use + * the entries below; all paths share the incremental runtime core. */ + "crypto.hashDigestStr": { argTypes: [STRING, STRING, STRING], result: STRING }, + "crypto.hashDigestBytes": { argTypes: [STRING, BYTES_U8, STRING], result: STRING }, + /** First-class static Hash/Hmac handles. Constructors validate the + * runtime algorithm string (every digest Node 24 lists); update returns the same + * handle by retained identity, copy snapshots Hash state, and digest + * finalizes the handle and returns either a Buffer or encoded string. */ + "crypto.native": { argTypes: [], result: DYN }, + "crypto.hashNew": { argTypes: [STRING], result: CRYPTOHASH_T }, + /** The Transform a Hash or Hmac reads and writes as a stream (+1, + * created on first use and owned by the value; scr_stream.c). The Piped + * form, for pipe destinations and pipeline stages, also keeps the value + * alive until the stream flushes or is destroyed. */ + "crypto.hashStream": { argTypes: [null], result: { kind: "object", className: "%Transform" } }, + "crypto.hashStreamPiped": { + argTypes: [null], + result: { kind: "object", className: "%Transform" }, + }, + /** createCipheriv (false) / createDecipheriv (true) over checked-dynamic + * cipher, key, iv and options arguments (scr_cipher.c): a native + * Cipheriv/Decipheriv handle whose methods dispatch through dynInvoke. */ + "crypto.cipherNew": { argTypes: [BOOL, DYN, DYN, DYN, DYN], result: DYN }, + /** createECDH(curveName) over a checked-dynamic curve (scr_ecdh.c): a + * native ECDH handle whose methods dispatch through dynInvoke. */ + "crypto.ecdhNew": { argTypes: [DYN], result: DYN }, + /** ECDH.convertKey(key, curve, inputEncoding, outputEncoding, format): + * the converted point as a Buffer or string. */ + "crypto.ecdhConvertKey": { argTypes: [DYN, DYN, DYN, DYN, DYN], result: DYN }, + /** createDiffieHellman(sizeOrKey, keyEncoding, generator, + * generatorEncoding) over checked-dynamic arguments (scr_dh.c): a native + * DiffieHellman handle whose methods dispatch through dynInvoke. */ + "crypto.dhNew": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, + /** getDiffieHellman(groupName) and createDiffieHellmanGroup(name): a + * native DiffieHellmanGroup handle. */ + "crypto.dhGroupNew": { argTypes: [DYN], result: DYN }, + /** checkPrimeSync(candidate, options) and generatePrimeSync(size, + * options) over checked-dynamic arguments (scr_dh.c): a boolean, and an + * ArrayBuffer or bigint, as checked-dynamic results. The Cb forms take + * the callback and whether its error parameter takes null for Node's + * undefined, and call it in a later macrotask; Node's argument errors + * throw (may-throw). */ + "crypto.checkPrimeSync": { argTypes: [DYN, DYN], result: DYN }, + /** getCipherInfo(nameOrNid, options) over checked-dynamic arguments + * (scr_cipher_info.c): the info object or undefined. */ + "crypto.getCipherInfo": { argTypes: [DYN, DYN], result: DYN }, + "crypto.checkPrimeCb": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, + "crypto.generatePrimeSync": { argTypes: [DYN, DYN], result: DYN }, + "crypto.generatePrimeCb": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, + "crypto.hmacNewStr": { argTypes: [STRING, STRING], result: CRYPTOHMAC_T }, + "crypto.hmacNewBytes": { argTypes: [STRING, BYTES_U8], result: CRYPTOHMAC_T }, + "crypto.hashUpdateStr": { argTypes: [CRYPTOHASH_T, STRING], result: CRYPTOHASH_T }, + "crypto.hashUpdateBytes": { argTypes: [CRYPTOHASH_T, BYTES_U8], result: CRYPTOHASH_T }, + "crypto.hmacUpdateStr": { argTypes: [CRYPTOHMAC_T, STRING], result: CRYPTOHMAC_T }, + "crypto.hmacUpdateBytes": { argTypes: [CRYPTOHMAC_T, BYTES_U8], result: CRYPTOHMAC_T }, + "crypto.hashCopy": { argTypes: [CRYPTOHASH_T], result: CRYPTOHASH_T }, + "crypto.hashDigestString": { argTypes: [CRYPTOHASH_T, STRING], result: STRING }, + "crypto.hashDigestBuffer": { argTypes: [CRYPTOHASH_T], result: BYTES_U8 }, + "crypto.hmacDigestString": { argTypes: [CRYPTOHMAC_T, STRING], result: STRING }, + "crypto.hmacDigestBuffer": { argTypes: [CRYPTOHMAC_T], result: BYTES_U8 }, + /** The option and any-value forms, with Node's checks at run time. The + * Dyn digests return a string or a Buffer, and crypto.hash's Dyn form + * either one by its outputEncoding. */ + "crypto.hashNewOpts": { argTypes: [STRING, DYN], result: CRYPTOHASH_T }, + "crypto.hmacNewDyn": { argTypes: [STRING, DYN, DYN], result: CRYPTOHMAC_T }, + "crypto.hashCopyOpts": { argTypes: [CRYPTOHASH_T, DYN], result: CRYPTOHASH_T }, + "crypto.hashUpdateEnc": { argTypes: [CRYPTOHASH_T, STRING, STRING], result: CRYPTOHASH_T }, + "crypto.hmacUpdateEnc": { argTypes: [CRYPTOHMAC_T, STRING, STRING], result: CRYPTOHMAC_T }, + "crypto.hashUpdateDyn": { argTypes: [CRYPTOHASH_T, DYN, DYN], result: CRYPTOHASH_T }, + "crypto.hmacUpdateDyn": { argTypes: [CRYPTOHMAC_T, DYN, DYN], result: CRYPTOHMAC_T }, + "crypto.hashDigestDyn": { argTypes: [CRYPTOHASH_T, DYN], result: DYN }, + "crypto.hmacDigestDyn": { argTypes: [CRYPTOHMAC_T, DYN], result: DYN }, + "crypto.hashOneshot": { argTypes: [STRING, DYN, DYN], result: DYN }, + /** Views of any element kind (the validator checks both are views). */ + "crypto.timingSafeEqual": { argTypes: [null, null], result: BOOL }, + "crypto.timingSafeEqualDyn": { argTypes: [DYN, DYN], result: BOOL }, + "crypto.randomFill": { argTypes: [BYTES_U8, F64, F64], result: BYTES_U8 }, + "crypto.randomFillRest": { argTypes: [BYTES_U8, F64], result: BYTES_U8 }, + "crypto.randomInt": { argTypes: [F64, F64], result: F64 }, + "crypto.randomIntMax": { argTypes: [F64], result: F64 }, + /** randomFillSync over any view returns that view at its own type (the + * validator checks the result against the first argument). The Dyn form + * takes ArrayBuffers and other values. The callback forms carry + * randomFill's argument count before the callback; randomIntCb carries + * whether min was given. */ + "crypto.randomFillView": { argTypes: [null, F64, F64], result: BYTES_U8 }, + "crypto.randomFillViewRest": { argTypes: [null, F64], result: BYTES_U8 }, + /** crypto.getRandomValues: a view returns itself at its own type (the + * float/size DOMExceptions are thrown at run time); any other value + * takes the checked form. */ + "crypto.getRandomValues": { argTypes: [null], result: BYTES_U8 }, + "crypto.getRandomValuesDyn": { argTypes: [DYN], result: DYN }, + "crypto.randomFillSyncDyn": { argTypes: [DYN, DYN, DYN], result: DYN }, + "crypto.randomFillCb": { argTypes: [null, F64, F64, F64, null], result: VOID }, + "crypto.randomFillCbDyn": { argTypes: [DYN, DYN, DYN, F64, null], result: VOID }, + "crypto.randomIntCb": { argTypes: [F64, F64, F64, null], result: VOID }, + "crypto.randomUUIDOpts": { argTypes: [DYN], result: STRING }, + /** Secret KeyObjects: native handles whose getters and methods dispatch + * through the handle. keyObjectInput reads a key's bytes for hkdf. */ + "crypto.createSecretKey": { argTypes: [DYN, DYN], result: DYN }, + "crypto.generateKeySync": { argTypes: [DYN, DYN], result: DYN }, + "crypto.generateKeyCb": { argTypes: [DYN, DYN, null], result: VOID }, + "crypto.keyObjectInput": { argTypes: [DYN], result: BYTES_U8 }, + "crypto.isKeyObject": { argTypes: [DYN], result: BOOL }, + /** WebCrypto (scr_webcrypto.c): each method takes its argument vector as + * one dyn array and answers a promise that argument and validation + * failures reject. CryptoKey values are native handles. */ + "webcrypto.generateKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.importKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.exportKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.encrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.decrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.wrapKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.unwrapKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.deriveBits": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.deriveKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.sign": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.verify": { argTypes: [DYN], result: { kind: "promise", inner: BOOL } }, + "webcrypto.digest": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "webcrypto.isCryptoKey": { argTypes: [DYN], result: BOOL }, + "webcrypto.object": { argTypes: [], result: DYN }, + "crypto.randomUUIDv7": { argTypes: [DYN], result: STRING }, + "crypto.pbkdf2": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING], result: BYTES_U8 }, + "crypto.randomBytesCb": { argTypes: [F64, null], result: VOID }, + "crypto.pbkdf2Cb": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING, null], result: VOID }, + "crypto.hkdf": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], result: DYN }, + "crypto.hkdfCb": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64, null], result: VOID }, + "crypto.scrypt": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN], result: BYTES_U8 }, + "crypto.scryptCb": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN, null], result: VOID }, + /** util.promisify over the callback forms: the callback form's + * arguments without the callback → a promise the callback form settles, + * or rejected with an argument error; never throws. The result's value + * type is the declared one (a Buffer, the filled view, a number, or a + * checked-dynamic value); the validator checks it. */ + "crypto.randomBytesPromise": { argTypes: [F64], result: VOID }, + "crypto.pbkdf2Promise": { argTypes: [BYTES_U8, BYTES_U8, F64, F64, STRING], result: VOID }, + "crypto.scryptPromise": { argTypes: [BYTES_U8, BYTES_U8, F64, DYN], result: VOID }, + "crypto.hkdfPromise": { argTypes: [STRING, BYTES_U8, BYTES_U8, BYTES_U8, F64], result: VOID }, + "crypto.randomFillPromise": { argTypes: [null, F64, F64, F64], result: VOID }, + "crypto.randomFillPromiseDyn": { argTypes: [DYN, DYN, DYN, F64], result: VOID }, + "crypto.randomIntPromise": { argTypes: [F64, F64, F64], result: VOID }, + "crypto.generateKeyPromise": { argTypes: [DYN, DYN], result: VOID }, + /** util.promisify(dns.lookup) and util.promisify(dns.lookupService): + * [hostname, options] and [address, port] → a promise of the + * checked-dynamic result object (the address array with options.all), + * or rejected with the error; never throws. */ + "dns.lookupPromise": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dns.lookupServicePromise": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + // The Buffer statics and the fs/zlib Buffer forms: fixed always-u8 + // signatures (Buffer IS a Uint8Array — one bytes kind). + /** The Buffer statics with fixed (always-u8) signatures. fromStr is + * `Buffer.from(string, enc)` — the frontend completes an omitted + * encoding to "utf8" and fences non-literal/unsupported ones; hex and + * base64 decode Node-leniently, so it never throws. concat takes ONE + * bytes[] arg (the list) and returns a fresh copy. `Buffer.from(u8)` + * and `Buffer.alloc(n)` need no libFn — they lower to bytesNew. */ + "buffer.fromStr": { argTypes: [STRING, STRING], result: BYTES_U8 }, + "buffer.fromStrChecked": { argTypes: [STRING, STRING], result: BYTES_U8 }, + "buffer.brand": { argTypes: [BYTES_U8], result: BYTES_U8 }, + /** Buffer.isBuffer over a typed view: whether the view carries the + * Buffer brand (any element kind; only Buffers do). Never throws. */ + "buffer.isBranded": { argTypes: [null], result: BOOL }, + /** Buffer.from on checked-native strings, bytes, arrays and data-only + * array-like/Buffer-JSON objects. The encoding is a normalized literal; + * non-string inputs ignore it. Copies the input and may throw during + * element coercion or argument validation. Custom object valueOf and + * opaque reference inputs retain explicit runtime refusals. */ + "buffer.fromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, + "buffer.concat": { argTypes: [arrayOf(BYTES_U8)], result: BYTES_U8 }, + "buffer.concatDyn": { argTypes: [DYN], result: BYTES_U8 }, + /** The pooled Buffer factories (Node's allocate/fromStringFast/ + * fromArrayLike): allocUnsafe, Buffer.from of strings, Uint8Arrays, + * number[] and checked values, and concat return views into the + * per-thread pool slab below Buffer.poolSize >>> 1 bytes. alloc and + * allocUnsafeSlow never pool. The size forms validate like Node + * (ERR_OUT_OF_RANGE). poolSize reads and writes Buffer.poolSize. */ + "buffer.alloc": { argTypes: [F64], result: BYTES_U8 }, + /** Buffer.alloc as a function value: the size, fill and encoding arrive + * unchecked, and the runtime fills as Node's alloc does. */ + "buffer.allocValue": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, + "buffer.allocUnsafe": { argTypes: [F64], result: BYTES_U8 }, + "buffer.allocUnsafeSlow": { argTypes: [F64], result: BYTES_U8 }, + "buffer.poolFromStr": { argTypes: [STRING, STRING], result: BYTES_U8 }, + "buffer.poolFromStrChecked": { argTypes: [STRING, STRING], result: BYTES_U8 }, + "buffer.poolFromBytes": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "buffer.poolFromArray": { argTypes: [arrayOf(F64)], result: BYTES_U8 }, + "buffer.poolFromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, + /** poolFromDyn that also copies an array crossing by reference. */ + "buffer.poolFromDynRef": { argTypes: [DYN, STRING], result: BYTES_U8 }, + "buffer.poolFromDynOffset": { argTypes: [DYN, DYN], result: BYTES_U8 }, + /** Buffer.from(value, byteOffset, length): an ArrayBuffer's range with + * Node's fromArrayBuffer checks; other values as poolFromDynOffset. */ + "buffer.poolFromDynRange": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, + "buffer.poolSize": { argTypes: [], result: F64 }, + "buffer.setPoolSize": { argTypes: [F64], result: VOID }, + /** Buffer.byteLength(string, enc) — enc a NORMALIZED literal like + * fromStr's — and Buffer.isEncoding(name) over a runtime string + * (case-insensitive against Node's alias set). Pure; never throw. */ + "buffer.byteLenDyn": { argTypes: [DYN, STRING], result: F64 }, + /** Buffer.byteLength as a function value: the encoding arrives at run time. */ + "buffer.byteLenValue": { argTypes: [DYN, DYN], result: F64 }, + "buffer.byteLenStr": { argTypes: [STRING, STRING], result: F64 }, + "buffer.isEncoding": { argTypes: [STRING], result: BOOL }, + /** Buffer.concat(list, totalLength): the concatenation truncated or + * zero-padded to the total. THROWS Node's 'length' RangeError on a + * negative/non-integer total (may-throw seed). */ + "buffer.concatLen": { argTypes: [arrayOf(BYTES_U8), F64], result: BYTES_U8 }, + "buffer.concatLenDyn": { argTypes: [DYN, F64], result: BYTES_U8 }, + // The checked-dynamic compare/equals validators (Node's argument + // ladders over dyn-boxed invalid-input probes). + /** The checked-dynamic compare/equals validators (scr_bytes_io.c) — + * the lowered form when an argument is NOT statically bytes (the + * invalid-input probes; a dyn from an untyped JS helper): Node's own + * argument ladders run at runtime — ERR_INVALID_ARG_TYPE with the + * API's argument name ("buf1"/"buf2", "otherBuffer", "target"; offsets + * "of type number"), validateOffset's ERR_OUT_OF_RANGE for bad + * numbers, undefined offsets taking their Node defaults — and a + * well-typed value still computes the real answer. All args borrowed + * dyn; compareChk's four offset slots pass the undefined dyn when + * syntactically absent. May-throw seeds. */ + "buffer.compareChk": { argTypes: [DYN, DYN], result: F64 }, + "bytes.equalsChk": { argTypes: [BYTES_U8, DYN], result: BOOL }, + "bytes.compareChk": { argTypes: [BYTES_U8, DYN, DYN, DYN, DYN, DYN], result: F64 }, + /** The deprecated `new Buffer(number, 'enc')` string-arm rejection: + * always throws Node's ERR_INVALID_ARG_TYPE ("The \"string\" argument + * must be of type string. Received ..."). Borrowed dyn; may-throw. */ + "buffer.newStringFail": { argTypes: [DYN], result: BYTES_U8 }, + "buffer.deprecation": { argTypes: [], result: VOID }, + "buffer.deprecationCount": { argTypes: [], result: VOID }, + "buffer.constructor": { argTypes: [], result: DYN }, + "buffer.installValue": { argTypes: [], result: VOID }, + "buffer.staticValue": { argTypes: [STRING], result: DYN }, + "buffer.prototypeMember": { argTypes: [DYN], result: DYN }, + /** fs._toUnixTimestamp(time) over a dyn value: numeric strings and + * finite numbers coerce (negatives answer now/1000, Node's shape); + * everything else throws Node's ERR_INVALID_ARG_TYPE. May-throw. */ + "fs.toUnixTimestamp": { argTypes: [DYN], result: F64 }, + /** The fs argument-validation ladders (checked-dynamic lane): each Chk + * replicates its API's Node-order validation over dyn values (Node's + * exact typed errors — ERR_INVALID_ARG_TYPE/VALUE, ERR_OUT_OF_RANGE), + * and a full pass meets the trailing compiler-rendered SC2020 fence + * string — so the ALWAYS-THROW forms take the error.nodeThrow + * polymorphic-result carve-out. mkdtempSyncChk and the lchmod sync/ + * promise pair run the REAL operation on a validated pass instead + * (macOS lchmod(2); non-APPLE answers Node's not-a-function / + * ERR_METHOD_NOT_IMPLEMENTED shapes). May-throw seeds, all of them. */ + "fs.existsChk": { argTypes: [DYN, DYN], result: DYN }, + "fs.mkdtempChk": { argTypes: [DYN, DYN, STRING], result: VOID }, + "fs.mkdtempSyncChk": { argTypes: [DYN, DYN, STRING], result: STRING }, + "fs.readFileChk": { argTypes: [DYN, DYN, DYN, STRING], result: VOID }, + "fs.opendirChk": { argTypes: [DYN, DYN, STRING], result: VOID }, + "fs.watchFileChk": { argTypes: [DYN, DYN, STRING], result: VOID }, + "fs.lchmodChk": { argTypes: [DYN, DYN, DYN, STRING], result: VOID }, + "fs.lchmodSyncChk": { argTypes: [DYN, DYN], result: DYN }, + "fsp.lchmodChk": { argTypes: [DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fs.readChk": { argTypes: [DYN, DYN, DYN, DYN, DYN, STRING], result: VOID }, + "fs.streamOptsChk": { argTypes: [DYN, DYN, STRING], result: VOID }, + /** fs.ReadStream / fs.WriteStream (scr_fs_stream.c): the constructors + * take the path and options as dyn values and throw Node's option + * errors; the members read bytesRead/bytesWritten, pending and the path + * value, and close() / close(cb) (a `() => void` callback). */ + "fs.createReadStream": { + argTypes: [DYN, DYN], + result: { kind: "object", className: "%Readable" }, + }, + "fs.createWriteStream": { + argTypes: [DYN, DYN], + result: { kind: "object", className: "%Writable" }, + }, + "fs.fsStreamBytes": { argTypes: [null], result: F64 }, + "fs.fsStreamPending": { argTypes: [null], result: BOOL }, + "fs.fsStreamPath": { argTypes: [null], result: DYN }, + "fs.fsStreamPropDyn": { argTypes: [null, STRING], result: DYN }, + "fileHandle.createReadStream": { + argTypes: [FILEHANDLE_T, DYN], + result: { kind: "object", className: "%Readable" }, + }, + "fileHandle.createWriteStream": { + argTypes: [FILEHANDLE_T, DYN], + result: { kind: "object", className: "%Writable" }, + }, + "fs.fsStreamClose": { argTypes: [null], result: VOID }, + "fs.fsStreamCloseCb": { argTypes: [null, funcOf([], VOID)], result: VOID }, + /** The compiler-resolved ERR_INVALID_ARG_TYPE throw with a RUNTIME- + * rendered Received tail: args [argname, "of type ..." clause, the + * offending dyn value]. ALWAYS THROWS; polymorphic result (the + * error.nodeThrow pattern). May-throw seed. */ + "error.argTypeThrow": { argTypes: [STRING, STRING, DYN], result: VOID }, + /** The property flavor of argTypeThrow ("The \"options.x\" property + * must be ..."): the option-bag ladders' provably-invalid arms. ALWAYS + * THROWS; polymorphic result. May-throw seed. */ + "error.propTypeThrow": { argTypes: [STRING, STRING, DYN], result: VOID }, + /** The checked-dynamic max-listeners ladders: setMaxChk is the + * instance form over a dyn n (non-numbers ERR_INVALID_ARG_TYPE, + * negatives/NaN ERR_OUT_OF_RANGE; +1 receiver back — chaining); + * setDefaultMaxChk is the static/property form, its second argument + * naming the message slot ("setMaxListeners" for the static call, + * "defaultMaxListeners" for the module-property assignment). */ + "emitter.setMaxChk": { argTypes: [null, DYN], result: VOID }, + "emitter.setDefaultMaxChk": { argTypes: [DYN, STRING], result: VOID }, + /** The Buffer forms of fs: readFileSync(path) with NO + * encoding → bytes (+1), writeFileSync/appendFileSync(path, bytes), and the + * fs/promises readFile(path) no-encoding form (an already-settled + * promise, rejecting on failure like the other fsp members). The sync + * forms THROW catchably on failure exactly like the utf8 forms. */ + "fs.readFileSyncBytes": { argTypes: [STRING], result: BYTES_U8 }, + "fs.writeFileSyncBytes": { argTypes: [STRING, BYTES_U8], result: VOID }, + "fs.appendFileSyncBytes": { argTypes: [STRING, BYTES_U8], result: VOID }, + "fsp.readFileBytes": { argTypes: [STRING], result: { kind: "promise", inner: BYTES_U8 } }, + /** node:zlib (scr_zlib.c — native-toolchain.ts compiles/links it ONLY when these + * appear on the IR, the regex/libcurl gating precedent): one-shot zlib, + * raw-DEFLATE, gzip, and auto-detect codecs over u8 bytes with Node's + * default options, or a validated literal compression level from -1 to 9. + * Compression never throws (OOM aborts); decompression + * of corrupt input THROWS Node's error catchably. */ + "zlib.deflateSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "zlib.inflateSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "zlib.deflateRawSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + /** bytes, mode (0 zlib / 1 raw / 2 gzip), validated compression level. */ + "zlib.deflateLevelSync": { argTypes: [BYTES_U8, F64, F64], result: BYTES_U8 }, + "zlib.inflateRawSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "zlib.gzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "zlib.gunzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + "zlib.unzipSync": { argTypes: [BYTES_U8], result: BYTES_U8 }, + /** The default-options callback twins run on the executable's shared + * native work pool and deliver (Error | null, Buffer) on a later loop + * turn. crc32 validates its optional uint32 seed synchronously. */ + "zlib.deflateCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.inflateCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.deflateRawCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.inflateRawCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.gzipCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.gunzipCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.unzipCb": { argTypes: [BYTES_U8, null], result: VOID }, + "zlib.crc32": { argTypes: [BYTES_U8, F64], result: F64 }, + /** The one-shot forms with an options value: node_zlib_mode, the input, + * the options (validated at runtime with Node's ladder). The Cb form + * takes the program-shaped (Error | null, Buffer) callback, which moves. */ + "zlib.syncOpts": { argTypes: [F64, BYTES_U8, DYN], result: BYTES_U8 }, + "zlib.cbOpts": { argTypes: [F64, BYTES_U8, DYN, null], result: VOID }, + /** The checked-dynamic input twins (the JS lane's untyped data). */ + "zlib.syncOptsDyn": { argTypes: [F64, DYN, DYN], result: BYTES_U8 }, + "zlib.cbOptsDyn": { argTypes: [F64, DYN, DYN, null], result: VOID }, + /** util.promisify over the callback one-shots: [mode, data, options] + * like zlib.cbOpts → a promise the worker-pool run settles with the + * Buffer or the callback form's Error. An options or input error rejects + * it; never throws. The Dyn twin takes the JS lane's untyped data. */ + "zlib.promise": { + argTypes: [F64, BYTES_U8, DYN], + result: { kind: "promise", inner: BYTES_U8 }, + }, + "zlib.promiseDyn": { argTypes: [F64, DYN, DYN], result: { kind: "promise", inner: BYTES_U8 } }, + /** The stream classes (scr_zlib_stream.c, linked with the stream unit): + * streamNew takes Node's node_zlib_mode number and the options value + * (undefined when absent), validates it with Node's ladder, and answers + * the +1 %Transform stream. flush takes the kind value, params the level + * and strategy values; the Cb forms move their () => void callback. The + * receivers are borrowed. */ + /** Connects scr_brotli.c to the zlib engine (%main, before any module). */ + "zlib.brotliInstall": { argTypes: [], result: VOID }, + "zlib.streamNew": { argTypes: [F64, DYN], result: VOID }, + "zlib.streamBytesWritten": { argTypes: [null], result: F64 }, + /** `stream instanceof zlib.Gzip`: the borrowed stream and the class's + * node_zlib_mode; true when the stream is a zlib stream of that mode. */ + "zlib.streamIs": { argTypes: [null, F64], result: BOOL }, + "zlib.streamFlush": { argTypes: [null, DYN], result: VOID }, + "zlib.streamFlushCb": { argTypes: [null, DYN, null], result: VOID }, + "zlib.streamParams": { argTypes: [null, DYN, DYN], result: VOID }, + "zlib.streamParamsCb": { argTypes: [null, DYN, DYN, null], result: VOID }, + "zlib.streamClose": { argTypes: [null], result: VOID }, + "zlib.streamCloseCb": { argTypes: [null, null], result: VOID }, + "zlib.streamReset": { argTypes: [null], result: VOID }, + /** The Buffer overloads of the raw stream writes — same promptly + * submitted streams as process.stdoutWrite/stderrWrite, constantly true. + * The encoding arg is evaluated but ignored for bytes, like Node. The Cb + * forms move their program-shaped completion callback to the tick queue. */ + "process.stdoutWriteBytes": { argTypes: [BYTES_U8, STRING], result: BOOL }, + "process.stderrWriteBytes": { argTypes: [BYTES_U8, STRING], result: BOOL }, + // Completion callback is program-dependent: zero params, checked-dynamic, + // or an optional Error | null slot (same success shape as fs.rename). + "process.stdoutWriteBytesCb": { argTypes: [BYTES_U8, STRING, null], result: BOOL }, + "process.stderrWriteBytesCb": { argTypes: [BYTES_U8, STRING, null], result: BOOL }, + /** The value of a FULFILLED promise, read without a microtask turn (+1 + * for references): the tuple form of Promise.all reads its entries this + * way once the combinator fulfills, each entry widened to Promise + * and read at the type it was built with (program-dependent). */ + "promise.fulfilledValue": { argTypes: [null], result: VOID }, + /** Promise.all's tuple form as a value: the entries (each widened to + * Promise) join the runtime combinator, and when it fulfills the + * function reads the fulfilled entries into the tuple inside that + * settlement, so the result settles on the combinator's turn; a + * rejection settles it the same way. The result is the tuple's promise + * (program-dependent). */ + "promise.allTuple": { argTypes: [null, null], result: VOID }, + "fsp.readFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: STRING } }, + "fsp.writeFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, + /** fs.promises.writeFile(path, data, { mode }): the settled-promise + * twin of fs.writeFileModeSync. */ + "fsp.writeFileMode": { + argTypes: [STRING, STRING, F64], + result: { kind: "promise", inner: VOID }, + }, + "fsp.mkdir": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + /** The fs/promises option/member tail the certs pipeline uses: mkdir's + * literal { recursive?, mode? } options (the mkdirSync matrix behind + * settled promises), unlink, chmod. Failures REJECT (catchable at the + * await), like the rest of the fsp family. */ + "fsp.mkdirMode": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, + "fsp.mkdirRecursive": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + "fsp.mkdirRecursiveMode": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, + "fsp.unlink": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + "fsp.chmod": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, + "fsp.chown": { argTypes: [STRING, F64, F64], result: { kind: "promise", inner: VOID } }, + "fsp.lchown": { argTypes: [STRING, F64, F64], result: { kind: "promise", inner: VOID } }, + "fsp.access": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, + "fsp.truncate": { argTypes: [STRING, F64], result: { kind: "promise", inner: VOID } }, + "fsp.appendFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, + "fsp.appendFileBytes": { + argTypes: [STRING, BYTES_U8], + result: { kind: "promise", inner: VOID }, + }, + "fsp.copyFile": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, + "fsp.mkdtemp": { argTypes: [STRING], result: { kind: "promise", inner: STRING } }, + /** readFile(path, encoding | { encoding }) for any runtime encoding name. */ + "fsp.readFileEncoded": { + argTypes: [STRING, STRING], + result: { kind: "promise", inner: STRING }, + }, + "fsp.rmdir": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + "fsp.link": { argTypes: [DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fsp.symlink": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fsp.cp": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fsp.readlink": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "fsp.statfs": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "fsp.readlinkStr": { argTypes: [DYN, DYN], result: { kind: "promise", inner: STRING } }, + "fsp.readlinkBuffer": { argTypes: [DYN, DYN], result: { kind: "promise", inner: BYTES_U8 } }, + "fsp.utimes": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fsp.lutimes": { argTypes: [DYN, DYN, DYN], result: { kind: "promise", inner: VOID } }, + "fsp.rename": { argTypes: [STRING, STRING], result: { kind: "promise", inner: VOID } }, + "fsp.readdir": { argTypes: [STRING], result: { kind: "promise", inner: arrayOf(STRING) } }, + // Result is promise; the libCall case + // validates the program-dependent record shape in validate.ts. + /** `fs.promises.readdir(path, { withFileTypes: true })` — the settled- + * promise twin of fs.readdirTypesSync. The backends assemble the same + * call-site-shaped Dirent rows, then move the array into a promise; + * scandir failure becomes its rejection. */ + "fsp.readdirTypes": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + "fsp.rm": { argTypes: [STRING], result: { kind: "promise", inner: VOID } }, + /** `fs.promises.rm(path, options)`: fs.rmOptsSync and fs.rmRetrySync + * behind a settled promise; their errors become its rejection. */ + "fsp.rmOpts": { argTypes: [STRING, BOOL, BOOL], result: { kind: "promise", inner: VOID } }, + "fsp.rmRetry": { + argTypes: [STRING, BOOL, BOOL, F64, F64], + result: { kind: "promise", inner: VOID }, + }, + "fsp.stat": { argTypes: [STRING], result: { kind: "promise", inner: STATS_T } }, + "fsp.realpath": { argTypes: [STRING], result: { kind: "promise", inner: STRING } }, + "fsp.lstat": { argTypes: [STRING], result: { kind: "promise", inner: STATS_T } }, + /** fs/promises.open and the statically represented FileHandle surface. + * Every operation returns an already-settled promise; syscall failures + * become rejections rather than escaping synchronously. read/write + * results are call-site record shapes ({ bytesRead/bytesWritten, + * buffer }) assembled by the backends around the fixed runtime ABI. */ + "fsp.open": { + argTypes: [STRING, STRING, F64], + result: { kind: "promise", inner: FILEHANDLE_T }, + }, + "fileHandle.fd": { argTypes: [FILEHANDLE_T], result: F64 }, + "fileHandle.close": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, + "fileHandle.sync": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, + "fileHandle.datasync": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: VOID } }, + "fileHandle.truncate": { + argTypes: [FILEHANDLE_T, F64], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.chmod": { argTypes: [FILEHANDLE_T, F64], result: { kind: "promise", inner: VOID } }, + "fileHandle.utimes": { + argTypes: [FILEHANDLE_T, DYN, DYN], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.readv": { + argTypes: [FILEHANDLE_T, arrayOf(BYTES_U8), F64], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.writev": { + argTypes: [FILEHANDLE_T, arrayOf(BYTES_U8), F64], + result: { kind: "promise", inner: VOID }, + }, + // read/write carry call-site result record shapes; the validator checks + // those in validate.ts, so promise is only a table sentinel. + "fileHandle.read": { + argTypes: [FILEHANDLE_T, BYTES_U8, F64, F64, F64, BOOL], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.writeBytes": { + argTypes: [FILEHANDLE_T, BYTES_U8, F64, F64, F64, BOOL], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.writeStr": { + argTypes: [FILEHANDLE_T, STRING, F64, STRING], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.readFile": { + argTypes: [FILEHANDLE_T, STRING], + result: { kind: "promise", inner: STRING }, + }, + "fileHandle.readFileBytes": { + argTypes: [FILEHANDLE_T, STRING], + result: { kind: "promise", inner: BYTES_U8 }, + }, + "fileHandle.writeFile": { + argTypes: [FILEHANDLE_T, STRING, STRING], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.writeFileBytes": { + argTypes: [FILEHANDLE_T, BYTES_U8, STRING], + result: { kind: "promise", inner: VOID }, + }, + "fileHandle.stat": { argTypes: [FILEHANDLE_T], result: { kind: "promise", inner: STATS_T } }, + "process.argv": { argTypes: [], result: arrayOf(STRING) }, + // The Node runtime flag a node:test binary took out of process.argv + // (--test-update-snapshots), or [] — a fresh array per read. + "process.execArgv": { argTypes: [], result: arrayOf(STRING) }, + "process.platform": { argTypes: [], result: STRING }, + // The one libCall whose result type is program-dependent (union ids are + // per-module): the `result` here is a placeholder — the libCall case + // checks the union's ARMS ([string, undefinedT] in canonical order) + // against the module's registry instead. + /** getenv(3): one string key arg → the interned `string | undefined` + * union (present: +1 string wrapped into the string arm; absent: the + * interned undefined-arm instance). BOTH source forms — `process.env.FOO` + * and `process.env[expr]` — lower here. Purely static; never throws. */ + "process.envGet": { argTypes: [STRING], result: VOID }, + /** setenv(3): (name, value) string args → void. Later envGet reads and + * spawned children observe the write, like Node (values are strings — + * the frontend fences non-string RHS). Never throws. */ + "process.envSet": { argTypes: [STRING, STRING], result: VOID }, + /** `delete process.env[NAME]` — unsetenv(3): the mutation is visible to + * every later read (process.envGet asks getenv fresh) and inherited by + * spawned children, exactly Node. Statement position only (JS's boolean + * result is constant true there). Borrows the name; never throws. */ + "process.envUnset": { argTypes: [STRING], result: VOID }, + /** The whole environment as alternating [k0, v0, k1, v1, ...] strings in + * environ order — the raw material of the process.env SNAPSHOT record + * (the frontend's interned %env.snapshot helper keyed-writes the pairs + * into a fresh `{ [k: string]: string | undefined }` record). Fresh +1 + * array; never throws. */ + "process.envPairs": { argTypes: [], result: arrayOf(STRING) }, + "process.exit": { argTypes: [F64], result: VOID }, + /** Numeric process.exitCode write (integer validation, implicit exit status). */ + "process.setExitCode": { argTypes: [F64], result: VOID }, + /** The process.exitCode write of a program that also reads the + * property: the same validation, and the code counts as assigned. */ + "process.setExitCodeTracked": { argTypes: [F64], result: VOID }, + /** The code for process.exit() with no argument, or zero when unset. */ + "process.currentExitCode": { argTypes: [], result: F64 }, + /** Whether process.exitCode reads a code rather than undefined. Never throws. */ + "process.hasExitCode": { argTypes: [], result: BOOL }, + "process.cwd": { argTypes: [], result: STRING }, + /** getpid(2) / getuid(2): zero args → f64. POSIX-only target, so both + * always answer (the checker's `getuid?` optionality covers Windows — + * `process.getuid?.()` lowers as the plain call). Never throw. */ + "process.pid": { argTypes: [], result: F64 }, + "dyn.this": { argTypes: [], result: DYN }, + "dyn.generatorThis": { argTypes: [], result: DYN }, + "process.getuid": { argTypes: [], result: F64 }, + "process.getgid": { argTypes: [], result: F64 }, + /** geteuid(2) / getegid(2) / getgroups(2) (the effective gid appended + * when the list lacks it, like Node). Windows has no such members: the + * calls throw the TypeError a missing member does (MAY THROW). */ + "process.geteuid": { argTypes: [], result: F64 }, + "process.getegid": { argTypes: [], result: F64 }, + "process.getgroups": { argTypes: [], result: arrayOf(F64) }, + /** process.ppid: the parent's pid at the read. Never throws. */ + "process.ppid": { argTypes: [], result: F64 }, + /** process.title: libuv's process title (argv[0] until set); setTitle + * writes it (truncated to the original argument area on POSIX, the + * console title on Windows). Never throw. */ + "process.title": { argTypes: [], result: STRING }, + "process.setTitle": { argTypes: [STRING], result: VOID }, + /** process.argv0: the original argv[0]. Never throws. */ + "process.argv0": { argTypes: [], result: STRING }, + /** process.release: the compatibility target's stable release object + * (name, lts, sourceUrl, headersUrl, and libUrl on Windows). +1. */ + "process.release": { argTypes: [], result: DYN }, + /** process.execPath: the compiled binary's own resolved absolute path + * (one interned string, +1 per read) — the honest answer where Node's + * is the node executable's (SEMANTICS.md divergence 12). Never throws. */ + "process.execPath": { argTypes: [], result: STRING }, + /** process.arch: the compiled binary's OWN architecture ("arm64", + * "x64") — Node's answer for its own build on the same machine. + * Interned; +1 per read. Never throws. */ + "process.arch": { argTypes: [], result: STRING }, + /** Stable native version dictionary. node/openssl identify compatibility + * targets; components absent from the native runtime are omitted. */ + "process.versions": { argTypes: [], result: DYN }, + "process.builtinId": { argTypes: [DYN, arrayOf(STRING)], result: STRING }, + "process.builtinModule": { argTypes: [STRING, DYN], result: DYN }, + "process.builtinUnsupported": { argTypes: [STRING, STRING], result: DYN }, + /** A module namespace object as a checked-dynamic value + * (scr_module_namespace_value): the module id, its sorted export names + * and the compiled getter. One object per module. Never throws. */ + "module.namespaceValue": { argTypes: [STRING, arrayOf(STRING), DYN], result: DYN }, + "fs.callbackValue": { argTypes: [STRING], result: DYN }, + "fs.callbackCall": { argTypes: [STRING, DYN], result: DYN }, + "process.hrtimeValue": { argTypes: [], result: DYN }, + /** process.versions.node: the runtime's Node COMPATIBILITY TARGET — + * there is no Node under the binary, so this reports the version whose + * semantics the runtime implements (SEMANTICS.md divergence 60, the + * execPath stance). Interned; +1 per read. Never throws. */ + "process.versionsNode": { argTypes: [], result: STRING }, + "process.versionsOpenssl": { argTypes: [], result: STRING }, + /** kill(2) with Node's exact semantics and error shapes: the pid must be + * an int32 (else the ERR_INVALID_ARG_TYPE TypeError text), the named + * form resolves Node's signal-name table (unknown names throw the + * ERR_UNKNOWN_SIGNAL TypeError), an omitted signal completes to + * "SIGTERM" in the frontend, signal 0 probes, and a kill(2) failure + * throws Node's `kill ESRCH`/`kill EPERM` Error. Result is Node's + * constant true. */ + "process.kill": { argTypes: [F64, STRING], result: BOOL }, + "process.killNum": { argTypes: [F64, F64], result: BOOL }, + /** The raw byte writes: one borrowed string arg → bool (constantly true + * — this synchronous runtime never queues backpressure). stdoutWrite + * shares console.log's stream, each call is submitted promptly, and + * interleaved output keeps source order. No newline, no formatting, + * never throws. */ + "process.stdoutWrite": { argTypes: [STRING], result: BOOL }, + "process.stdio": { argTypes: [F64], result: DYN }, + "process.stderrWrite": { argTypes: [STRING], result: BOOL }, + // error.new's result and the receiver slots are builtin-error classes — + // program-dependent object types, checked in the libCall case. + /** The runtime-provided Error hierarchy's entry points (scr_error.c). + * error.new: one borrowed string arg (the message), result an owned (+1) + * builtin error instance — the result TYPE names which builtin class + * (backends derive the runtime kind from it). error.ctor: the + * super(message) call of an `extends Error` constructor — borrowed + * receiver (already allocated by the derived class's new) + borrowed + * message, void; the receiver's type names the builtin class whose name + * field to stamp. error.toString: borrowed `%Error`-typed receiver, +1 + * string in Node's "name: message" shape. Formatting can invoke a user + * message accessor, which can throw. */ + "error.new": { argTypes: [STRING], result: VOID }, + /** ECMAScript constructors with raw checked-dynamic message/options. + * Options retain cause presence; constructor calls borrow their args. + * Message coercion may throw before the cause is installed. */ + "error.newOptions": { argTypes: [DYN, DYN], result: VOID }, + "error.ctorOptions": { argTypes: [null, DYN, DYN], result: VOID }, + /** Borrowed Error receiver. cause returns an owned dyn value (undefined + * when absent); hasCause distinguishes absence from present undefined. */ + "error.cause": { argTypes: [null], result: DYN }, + "error.hasCause": { argTypes: [null], result: BOOL }, + /** Assignment borrows both operands and retains the new cause. */ + "error.setCause": { argTypes: [null, DYN], result: VOID }, + /** `err.code = s` on an error-hierarchy receiver: the code slot that + * error.code reads and coded throws stamp. Borrows both; never throws. */ + "error.setCode": { argTypes: [null, STRING], result: VOID }, + /** `err.code = v` with a value that is not statically a string: strings + * take the code slot, anything else clears it and sets the dynamic own + * property. Borrows both; never throws. */ + "error.setCodeDyn": { argTypes: [null, DYN], result: VOID }, + /** `Object.assign(err, ...sources)` onto an error-hierarchy receiver + * (args: the error, a dyn array of the sources): the own-member copy + * onto its checked-dynamic node, then the typed slots (name, message, + * stack, cause, code) re-read from it. Throws what the copy throws. */ + "error.assign": { argTypes: [null, DYN], result: VOID }, + /** `key in err` for a key no compiled class of the receiver declares + * (args: the error, the key): a runtime-set own property (Object.assign, + * throw-site fields, dynamic writes) or an Object.prototype member. */ + "error.hasKey": { argTypes: [null, STRING], result: BOOL }, + "error.hasCode": { argTypes: [null], result: BOOL }, + /** Whether the error's own `name` or `message` (the key) is an accessor + * a reflective definition installed. Borrows both; never throws. */ + "error.ownAccessor": { argTypes: [null, STRING], result: BOOL }, + /** That accessor's getter called on the error (owned result), and its + * setter called with the value (V8's TypeError without one). Borrow + * their arguments; throw what the accessor throws. */ + "error.accessorGet": { argTypes: [null, STRING], result: DYN }, + "error.accessorSet": { argTypes: [null, STRING, DYN], result: VOID }, + "error.defineCause": { argTypes: [null, DYN], result: VOID }, + "error.deleteCause": { argTypes: [null], result: VOID }, + /** The compiler-resolved Node-parity throw for always-throwing lowered + * arms (ERR_INVALID_THIS receivers, ERR_MISSING_ARGS arity ladders, + * the symbol-to-string TypeError): args are [error-kind f64 (the + * SCR_ERR_* index: 0 Error, 1 TypeError, 2 RangeError), code (empty = + * no code slot), message]. ALWAYS THROWS catchably; the result type is + * the replaced expression's own (never materialized — the + * global.undefRead pattern). May-throw seed. */ + "error.nodeThrow": { argTypes: [F64, STRING, STRING], result: VOID }, + /** JS ToString over a dyn value WITH the object protocol (a user + * toString/valueOf member is CALLED and its throw propagates; + * exhaustion throws "Cannot convert object to primitive value"; units + * render "null"/"undefined") — the WHATWG USVString conversions + * (URLSearchParams names/values). Borrowed dyn; +1 string. May-throw. */ + "dyn.toStringCoerce": { argTypes: [DYN], result: STRING }, + "dyn.stringConstructor": { argTypes: [DYN], result: STRING }, + "dyn.propertyKey": { argTypes: [DYN], result: DYN }, + "dyn.toNumeric": { argTypes: [DYN], result: DYN }, + "dyn.increment": { argTypes: [DYN, BOOL], result: DYN }, + /** JS ToNumber over a dyn value WITH the object protocol (number-hint + * valueOf/toString ordering; user throws propagate). Borrowed dyn; + * f64 result, or a throw. Used by statically lowered numeric coercions + * whose checker type remained any. */ + "dyn.numberConstructor": { argTypes: [DYN], result: F64 }, + "dyn.bigintConstructor": { argTypes: [DYN], result: BIGINT_T }, + "dyn.toNumberCoerce": { argTypes: [DYN], result: F64 }, + "dyn.add": { argTypes: [DYN, DYN], result: DYN }, + "dyn.arithmetic": { argTypes: [DYN, DYN, STRING], result: DYN }, + "dyn.compare": { argTypes: [DYN, DYN, STRING], result: BOOL }, + "dyn.bitwise": { argTypes: [DYN, DYN, STRING], result: DYN }, + "dyn.proxyNew": { argTypes: [DYN, DYN], result: DYN }, + /* Dynamic-tier values (jsval; two-tier design stage S3). The jsval pass + * (ir/jsval.ts) introduces these in dynamic-tier functions only; the + * runtime is scr_js_value.c. Arguments are borrowed and results owned, + * like every libCall. Boundary conversions: js.fromDyn (a cell keeps its + * identity), js.toDyn (an immediate is boxed), js.fromF64/fromBool/ + * fromStr/fromBigInt (the design's toJs: immediates, and strings and + * BigInts under a tag without a copy) and js.toF64 (fromJs to number: dynCheck's + * number check and TypeError). The operators are the dyn.* operators + * over jsvals, with identical JS semantics. */ + "js.fromDyn": { argTypes: [DYN], result: JSVAL }, + "js.toDyn": { argTypes: [JSVAL], result: DYN }, + "js.fromF64": { argTypes: [F64], result: JSVAL }, + "js.fromBool": { argTypes: [BOOL], result: JSVAL }, + "js.fromStr": { argTypes: [STRING], result: JSVAL }, + "js.fromBigInt": { argTypes: [BIGINT_T], result: JSVAL }, + "js.toF64": { argTypes: [JSVAL], result: F64 }, + "js.add": { argTypes: [JSVAL, JSVAL], result: JSVAL }, + "js.arithmetic": { argTypes: [JSVAL, JSVAL, STRING], result: JSVAL }, + "js.bitwise": { argTypes: [JSVAL, JSVAL, STRING], result: JSVAL }, + "js.compare": { argTypes: [JSVAL, JSVAL, STRING], result: BOOL }, + "js.toNumeric": { argTypes: [JSVAL], result: JSVAL }, + "js.increment": { argTypes: [JSVAL, BOOL], result: JSVAL }, + "js.toNumberCoerce": { argTypes: [JSVAL], result: F64 }, + // Always throws; the result is the READ's declared type (a typed dummy + // the unwind abandons) — the libCall case skips the result check. + /** A read of a `declare`d const NOTHING defines (the bundler-define + * pattern — __VERSION__): always throws the catchable ReferenceError + * Node raises at the access (" is not defined"). args[0] is the + * name; the result type is the read's declared type (a typed dummy the + * unwind abandons — the value never exists). */ + "global.undefRead": { argTypes: [STRING], result: VOID }, + "console.native": { argTypes: [], result: DYN }, + /** node:console Console instances (scr_console_native.c): new mints the + * handle over the stdout/stderr fds (1/2; 0 = a stream binds next) and + * the ignoreErrors flag; bind attaches a borrowed writable stream to + * target 0 (stdout) or 1 (stderr); write prints `text\n` to the + * selected target of an instance or the global console handle. */ + "console.new": { argTypes: [F64, F64, BOOL], result: DYN }, + "console.bind": { argTypes: [DYN, F64, null], result: VOID }, + "console.write": { argTypes: [DYN, STRING, BOOL], result: VOID }, + /** The global console's remaining members (scr_console_native.c; the + * group indentation itself lives with the writers in scr_console.c). + * Arguments are borrowed; string results are owned. noop is the + * emitter-dropped tail of a lowering whose effects sit in statements. + * label is `${label}` over a defaulted label (undefined -> "default"; + * a Symbol throws Node's TypeError). count prints "label: n". + * countReset answers the warning text for an unknown (or non-string) + * label, "" otherwise. time answers the duplicate-label warning text + * or ""; timeEnd/timeLog answer Node's formatted elapsed time, or "" + * for an unknown label (timeEnd drops the timer). group adds (+1) or + * removes (-1) one two-space indentation step. clear writes the + * cursor-home and erase-down sequences when stdout is a terminal. + * table renders and prints Node's table for a dyn value (undefined + * properties = all). dirDyn is console.dir's inspect of a dyn value + * (custom inspection off: a Buffer anywhere inside throws the fence). */ + "console.noop": { argTypes: [], result: VOID }, + "console.label": { argTypes: [DYN], result: STRING }, + "console.count": { argTypes: [STRING], result: VOID }, + "console.countReset": { argTypes: [DYN], result: STRING }, + "console.time": { argTypes: [STRING], result: STRING }, + "console.timeEnd": { argTypes: [STRING], result: STRING }, + "console.timeLog": { argTypes: [STRING], result: STRING }, + "console.group": { argTypes: [F64], result: VOID }, + "console.clear": { argTypes: [], result: VOID }, + "console.table": { argTypes: [DYN, DYN], result: VOID }, + "console.dirDyn": { argTypes: [DYN, F64], result: STRING }, + /** A native reference to the global object; known names retain value fences. */ + "global.native": { argTypes: [arrayOf(STRING)], result: DYN }, + // `X.name` through a class value: the arg is a program-dependent + // classval (a null slot; the libCall case checks the kind). + /** `X.name` through a class VALUE (scr_object.c): args[0] is a borrowed + * classval; the result is the class object's stored .name string, + * retained (+1 — the string is an interned immortal, so the retain is a + * no-op, kept for ownership uniformity). Never throws. A direct + * `C.name` on the class name itself folds to a strLit instead. */ + "class.name": { argTypes: [null], result: STRING }, + "error.ctor": { argTypes: [null, STRING], result: VOID }, + "error.toString": { argTypes: [null], result: STRING }, + /** SameValue over checked values, including NaN, signed zero and reference identity. */ + "dyn.sameValue": { argTypes: [DYN, DYN], result: BOOL }, + "error.stack": { argTypes: [null], result: STRING }, + "error.setStack": { argTypes: [null, STRING], result: VOID }, + "error.stackLimitGet": { argTypes: [], result: F64 }, + "error.stackLimitSet": { argTypes: [F64], result: VOID }, + // Receiver (any error-hierarchy object) and the program-dependent + // `string | undefined` result are checked in the libCall case. + /** NodeJS.ErrnoException's `.code` read: borrowed error-hierarchy + * receiver → the interned `string | undefined` union (type-directed + * construction in the backend, the process.envGet pattern) — the errno + * name where a throw site stamped one (fs, exec spawn/timeout, + * process.kill, the spawn 'error' event), the undefined arm everywhere + * else. Never throws. */ + "error.code": { argTypes: [null], result: VOID }, + // DOMException: construction takes the two dyn args (WebIDL's + // resolution runs in the runtime); the %DOMException result is a + // program-dependent object type, checked in the libCall case. The + // read surface takes the %DOMException receiver (a null slot — the + // libCall case checks the class name). + /** `new DOMException(message?, nameOrOptions?)` (scr_error.c): both args + * are borrowed dyn values (the lowering passes the dyn undefined for an + * absent argument, so WebIDL's optionality lives in one place). The + * runtime ToStrings the message ("" for absent/undefined), resolves the + * name — absent/undefined → "Error", a non-null object → ToString of its + * `name` member plus the `cause` own-property record, anything else → + * ToString — and stamps the legacy code from the WebIDL name table (0 + * off-table). Result is an owned (+1) %DOMException. Never throws (dyn + * ToString is total). */ + "error.newDom": { argTypes: [DYN, DYN], result: VOID }, + /** DOMException's own read surface (scr_error.c; %DOMException receivers + * only — borrowed). domCode: the legacy numeric code. domHasCause: the + * options form's own-property record (`'cause' in e`). domCause: the + * cause value, +1 (the dyn undefined when absent — matching Node's + * undefined read). None throws. */ + "error.domCode": { argTypes: [null], result: F64 }, + "error.domHasCause": { argTypes: [null], result: BOOL }, + "error.domCause": { argTypes: [null], result: DYN }, + /** structuredClone of a %DOMException receiver (scr_error.c): WebIDL + * serialization — name/message copy, the legacy code re-derives, cause + * does not serialize. args are the borrowed receiver and the borrowed + * options dyn value (the dyn undefined when absent — the shared + * validation throws Node's exact option errors; any non-empty transfer + * list throws DataCloneError, nothing static is transferable). Result + * +1 %DOMException. */ + "error.domClone": { argTypes: [null, DYN], result: VOID }, + /** `new AggregateError(errors, message?, options?)` (scr_json.c), in + * spec order: newAggregate installs the message and cause (both dyn, + * borrowed — the message conversion can throw) and returns an owned + * %AggregateError; aggregateSetErrors then stores the iterated list + * (borrowed receiver, the dyn array borrowed and retained). + * aggregateErrors reads the list (+1, the same array every read). */ + "error.newAggregate": { + argTypes: [DYN, DYN], + result: { kind: "object", className: "%AggregateError" }, + }, + "error.aggregateSetErrors": { argTypes: [null, DYN], result: VOID }, + "error.aggregateErrors": { argTypes: [null], result: DYN }, + /** `new SuppressedError(error, suppressed, message?)` (scr_json.c): all + * three dyn and borrowed; the message conversion can throw. Returns an + * owned %SuppressedError. */ + "error.newSuppressed": { + argTypes: [DYN, DYN, DYN], + result: { kind: "object", className: "%SuppressedError" }, + }, + /** `d instanceof TypeError` (and the other BUILTIN error classes) on a + * checked-dynamic value (scr_json.c): the from_error cache holds the + * dyn↔error identity edge, so the test resolves the runtime error and + * asks its vtable's stamped preorder interval — exact for every error + * that crossed the boundary. A dyn object that never came from an + * error (a hand-built {%error} literal) answers false: subclass + * identity is unknowable there (the root keeps dynTest's marker + * answer). args are the borrowed dyn and the SCR_ERR_* kind literal. + * Never throws. */ + "dyn.errInstanceof": { argTypes: [DYN, F64], result: BOOL }, + "weakMap.is": { argTypes: [DYN], result: BOOL }, + "weakSet.is": { argTypes: [DYN], result: BOOL }, + "weakMap.new": { argTypes: [DYN], result: DYN }, + "weakSet.new": { argTypes: [DYN], result: DYN }, + "finalizationRegistry.new": { argTypes: [DYN], result: DYN }, + "finalizationRegistry.is": { argTypes: [DYN], result: BOOL }, + "dyn.fromEntries": { argTypes: [DYN], result: DYN }, + "bytes.constructor": { argTypes: [STRING], result: DYN }, + "bytes.instanceOf": { argTypes: [DYN, DYN, STRING], result: BOOL }, + "bytes.construct": { argTypes: [DYN, DYN, STRING], result: DYN }, + /** The ambient receiver — JS `this` in a plain (non-method) function + * body: the innermost binding the current firing/dispatch window + * pushed (Node's listener receiver, a dyn OBJ method's object, an + * apply/call thisArg), or the undefined dyn singleton with none bound + * (the strict-mode plain-call answer, the old constant). Zero args → + * dyn (+1). Never throws. */ + "dyn.dataViewIs": { argTypes: [DYN], result: BOOL }, + "dyn.construct": { argTypes: [DYN, DYN, STRING], result: DYN }, + "arrayBuffer.constructor": { argTypes: [], result: DYN }, + "sharedArrayBuffer.new": { argTypes: [DYN], result: DYN }, + "sharedArrayBuffer.is": { argTypes: [DYN], result: BOOL }, + "arrayBuffer.new": { argTypes: [DYN], result: DYN }, + "ffi.argument": { argTypes: [DYN, STRING], result: DYN }, + "ffi.memoryModule": { argTypes: [DYN], result: DYN }, + "intl.segmenterNew": { argTypes: [], result: DYN }, + "intl.segmenterRefusing": { argTypes: [STRING], result: DYN }, + "arrayBuffer.is": { argTypes: [DYN], result: BOOL }, + "arrayBuffer.isView": { argTypes: [DYN], result: BOOL }, + "arrayBuffer.byteLengthGetter": { argTypes: [], result: F64 }, + "arrayBuffer.byteLengthDescriptor": { argTypes: [DYN], result: DYN }, + "arrayBuffer.viewU8C": { argTypes: [DYN, DYN, DYN], result: bytesOf("u8c") }, + "arrayBuffer.viewI8": { argTypes: [DYN, DYN, DYN], result: bytesOf("i8") }, + "arrayBuffer.viewU16": { argTypes: [DYN, DYN, DYN], result: bytesOf("u16") }, + "arrayBuffer.viewI16": { argTypes: [DYN, DYN, DYN], result: bytesOf("i16") }, + "arrayBuffer.viewU8": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, + "arrayBuffer.viewU32": { argTypes: [DYN, DYN, DYN], result: bytesOf("u32") }, + "arrayBuffer.viewI32": { argTypes: [DYN, DYN, DYN], result: bytesOf("i32") }, + "arrayBuffer.viewF16": { argTypes: [DYN, DYN, DYN], result: bytesOf("f16") }, + "arrayBuffer.viewF32": { argTypes: [DYN, DYN, DYN], result: bytesOf("f32") }, + "arrayBuffer.viewF64": { argTypes: [DYN, DYN, DYN], result: bytesOf("f64") }, + "arrayBuffer.viewDV": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, + /** Object.keys/values/entries over a CHECKED-DYNAMIC receiver + * (scr_json.c): the runtime walks the dyn node's own members in JS + * own-key order (array-index keys ascending first, then insertion + * order) and answers a dyn array (entries: an array of [key, value] + * pairs; values RETAIN the member nodes — reference semantics, like + * JS). Strings/arrays/bytes answer their index keys; other scalars an + * empty array; null/undefined throw Node's catchable TypeError + * ("Cannot convert undefined or null to object"). */ + "dyn.objKeys": { argTypes: [DYN], result: DYN }, + "dyn.forInKeys": { argTypes: [DYN], result: DYN }, + "dyn.forInKeyStrings": { argTypes: [DYN], result: arrayOf(STRING) }, + "dyn.forInStable": { argTypes: [DYN], result: BOOL }, + "dyn.objKeyStrings": { argTypes: [DYN], result: arrayOf(STRING) }, + /** Object.entries as a native [string, any][] (VOID: the tuple row's + * shape is program-dependent; the validator checks it). */ + "dyn.objEntryPairs": { argTypes: [DYN], result: VOID }, + "dyn.hasOwn": { argTypes: [DYN, STRING], result: BOOL }, + "dyn.propertyIsEnumerable": { argTypes: [DYN, STRING], result: BOOL }, + "dyn.assign": { argTypes: [DYN, DYN], result: DYN }, + "dyn.copyDataProperties": { argTypes: [DYN, DYN], result: DYN }, + "dyn.objectRest": { argTypes: [DYN, DYN], result: DYN }, + /** Variadic Object.assign over CHECKED-DYNAMIC targets (`Object.assign( + * {}, ...arr.map(f), tail)` — the option-table merge): the lowering + * builds one fresh dyn pack of sources (packPush retains a plain source + * in; packPushSpread flattens a spread source through the spread-call + * walk — V8's exact TypeError texts, the string arg spelling the spread + * expression for the nullish form), so every source evaluates and + * flattens BEFORE any copying (JS's ArgumentListEvaluation), then + * assignAll copies each pack element's own enumerable keys onto the + * target left to right and answers the TARGET (+1) — identity, like JS. + * assignAll throws Node's ToObject TypeError on a nullish target. */ + "dyn.packPush": { argTypes: [DYN, DYN], result: VOID }, + "dyn.packPushSpread": { argTypes: [DYN, DYN, STRING], result: VOID }, + /** The iterated-path spread twin: V8 spells the optimized apply-path + * texts (packPushSpread's — the expression named for nullish sources) + * only for the SINGLE LAST argument's spread; any other spread position + * drives the real iterator protocol, whose failure describes the VALUE + * ("object null is not iterable (cannot read property + * Symbol(Symbol.iterator))"). The frontend picks by position. */ + "dyn.packPushSpreadIter": { argTypes: [DYN, DYN], result: VOID }, + "dyn.reflectApply": { argTypes: [DYN, DYN, DYN], result: DYN }, + "dyn.reflectGet": { argTypes: [DYN, DYN, DYN], result: DYN }, + /** `using` / `await using` with a dyn value (scr_json.c): usingMethod + * runs the declaration's checks and answers what usingDispose calls at + * scope exit (undefined for a null or undefined value). The bool selects + * `await using`. Both throw Node's TypeErrors or the method's error. */ + "dyn.usingMethod": { argTypes: [DYN, BOOL], result: DYN }, + "dyn.usingDispose": { argTypes: [DYN, DYN], result: DYN }, + /** DisposableStack (scr_symbol.c): a new stack handle; whether a value is + * one; and `stack.use(value)` for a class instance, registering the + * compiler's disposal function for it. The last throws Node's + * ReferenceError on a disposed stack. */ + "disposable.stackNew": { argTypes: [], result: DYN }, + "disposable.stackIs": { argTypes: [DYN], result: BOOL }, + "disposable.stackUseCallback": { argTypes: [DYN, DYN, DYN], result: BOOL }, + "dyn.reflectSet": { argTypes: [DYN, DYN, DYN, DYN], result: BOOL }, + "dyn.reflectDefine": { argTypes: [DYN, DYN, DYN], result: BOOL }, + "dyn.abstractEq": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.assignAll": { argTypes: [DYN, DYN], result: DYN }, - "dyn.objCreateNullProto": { argTypes: [], result: DYN }, - "dyn.arrayPrototype": { argTypes: [], result: DYN }, - "dyn.arrayConstructor": { argTypes: [], result: DYN }, - "dyn.objectPrototype": { argTypes: [], result: DYN }, - "dyn.errorPrototype": { argTypes: [], result: DYN }, - "dyn.inheritsErrorPrototype": { argTypes: [DYN], result: BOOL }, - "dyn.errorCaptureStackTrace": { argTypes: [DYN], result: DYN }, - "dyn.errorCaptureStackTraceFunction": { argTypes: [], result: DYN }, - "dyn.functionApply": { argTypes: [], result: DYN }, - "dyn.builtinMethod": { argTypes: [STRING, STRING], result: DYN }, - "dyn.classPrototype": { argTypes: [DYN, DYN], result: DYN }, - "dyn.classBasePrototype": { argTypes: [DYN], result: DYN }, - "dyn.classInherit": { argTypes: [DYN, DYN], result: DYN }, - /** EventEmitter (0), Stream (1), Readable (2), Writable (3), Duplex (4), - * Transform (5) and PassThrough (6) as untyped constructor values, one - * per thread (scr_dyn_builtin_class.c). */ - "dyn.builtinClass": { argTypes: [F64], result: DYN }, - /** The stream classes (2-6 above) with their stream half: constructor - * functions over them get native stream state - * (scr_dyn_builtin_stream.c). */ - "dyn.builtinStreamClass": { argTypes: [F64], result: DYN }, - /** `value instanceof C` for those constructors, over the value's - * prototype chain. */ - "dyn.builtinInstanceOf": { argTypes: [DYN, F64], result: BOOL }, - /** `value instanceof f` for an untyped constructor function: the - * prototype chain against f.prototype. */ - "dyn.functionInstanceOf": { argTypes: [DYN, DYN], result: BOOL }, - "dyn.classSuper": { argTypes: [DYN, DYN, DYN], result: VOID }, - "dyn.assignPrototype": { argTypes: [DYN, DYN, DYN], result: DYN }, - "dyn.definePrototypeProps": { argTypes: [DYN, DYN, DYN], result: DYN }, - "dyn.bagGet": { argTypes: [DYN, STRING, DYN], result: DYN }, - /** A class field's creation in its instance's property bag: adds the - * string key as a default data property when absent, answering false - * (nothing changed) when the bag needs the general defineProperty path. */ - "dyn.bagCreateField": { argTypes: [DYN, STRING], result: BOOL }, - "dyn.bagSet": { argTypes: [DYN, STRING, DYN, DYN], result: VOID }, - "dyn.objCreate": { argTypes: [DYN], result: DYN }, - // Names a compiled class's prototype object (args[0]) after the class - // (args[1]) for util.inspect. - "dyn.nameClassPrototype": { argTypes: [DYN, STRING], result: VOID }, - "dyn.objCreateWithProperties": { argTypes: [DYN, DYN], result: DYN }, - "dyn.getPrototype": { argTypes: [DYN], result: DYN }, - // dyn.getPrototype for walks that compare each prototype with a program - // class's prototype: native builtin values end the walk (null). - "dyn.getPrototypeForIdentity": { argTypes: [DYN], result: DYN }, - "dyn.setPrototype": { argTypes: [DYN, DYN], result: DYN }, - "dyn.getOwnPropertyNames": { argTypes: [DYN], result: DYN }, - "dyn.ownKeys": { argTypes: [DYN], result: DYN }, - "dyn.getOwnPropertySymbols": { argTypes: [DYN], result: DYN }, - "dyn.getOwnPropertyDescriptors": { argTypes: [DYN], result: DYN }, - "dyn.preventExtensions": { argTypes: [DYN], result: DYN }, - "dyn.isExtensible": { argTypes: [DYN], result: BOOL }, - "dyn.seal": { argTypes: [DYN], result: DYN }, - "dyn.isSealed": { argTypes: [DYN], result: BOOL }, - "dyn.objValues": { argTypes: [DYN], result: DYN }, - /** Object.values as a native vector of checked values, for results the - * program consumes directly (for-of, index reads, read-only methods). */ - "dyn.objValueList": { argTypes: [DYN], result: arrayOf(DYN) }, - "dyn.objEntries": { argTypes: [DYN], result: DYN }, - /** structuredClone over the checked-dynamic tree (scr_json.c): the JSON-safe subset plus - * bytes (a fresh copy — a Buffer clones as a plain Uint8Array, like - * Node), deep. Functions and handle kinds throw the spec's catchable - * DataCloneError; CYCLES throw the scriptc fence (the checked-dynamic tree cannot - * represent them — Node clones cycles; documented divergence). The - * options dyn value validates with Node's exact errors (dictionary - * conversion, the transfer-sequence member; any non-empty transfer - * list throws DataCloneError). dyn.cloneMissing is the zero-argument - * 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 - * catchable DataCloneError ("Found invalid value in transferList.") — - * lowered directly (the list's values need no dyn representation to - * fail). */ - "dyn.cloneTransferFail": { argTypes: [], result: DYN }, - /** The same refusal given the transfer list's first member, an - * ArrayBuffer: a Buffer pool slab gets Node's untransferable message. */ - "dyn.cloneTransferFailWith": { argTypes: [DYN], result: DYN }, - /** `new RegExp(pattern, flags?)` (scr_regex.c): a heap regex over the - * same libregexp engine the literals use. The pattern compiles EAGERLY - * so an invalid pattern (or an unknown flag letter) throws Node's - * catchable SyntaxError at construction — Node's message shape with - * libregexp's detail text (approximate fidelity; e.name exact). An - * empty pattern stores the spec's "(?:)" source. Both args borrowed - * strings (the lowering completes an absent flags to ""); result +1. - * The result TYPE is the regex kind, so the link switch pulls the - * engine exactly like a literal. */ - "regex.new": { argTypes: [STRING, STRING], result: REGEX }, - "regex.resetLastIndex": { argTypes: [REGEX, F64], result: VOID }, - "regex.newChecked": { argTypes: [DYN, DYN], result: REGEX }, - "regex.replaceCallback": { argTypes: [STRING, REGEX, DYN, BOOL], result: STRING }, - // Reads of capture args[1] of an execSpans/matchGroups result: - // `capture === args[2]`, Number(capture) and parseInt(capture, - // args[2]). Borrowed; never throw. - "regex.groupEquals": { argTypes: [arrayOf(STRING), F64, STRING], result: BOOL }, - "regex.groupNumber": { argTypes: [arrayOf(STRING), F64], result: F64 }, - "regex.groupParseInt": { argTypes: [arrayOf(STRING), F64, F64], result: F64 }, - // `.index` / `.input` (args[1]) of a match result. Borrowed; never throws. - "regex.resultProp": { argTypes: [arrayOf(STRING), STRING], result: DYN }, - // The string keys `for...in` visits on an array (args[0], any element - // type) after its indices. Borrowed; never throws. - "array.namedKeys": { argTypes: [null], result: arrayOf(STRING) }, - // Whether the array (args[0]) still has the key (args[1]) from its - // namedKeys list. Borrowed; never throws. - "array.hasNamedKey": { argTypes: [null, STRING], result: BOOL }, - // Whether an array (args[0], any element type) has properties besides its - // elements: noncanonical numeric keys, match metadata or named - // properties. Borrowed; never throws. - "array.hasProperties": { argTypes: [null], result: BOOL }, - // The key of an element access whose index is undefined (the property - // named "undefined"; frontend/lowering/undefined-index.ts). - "array.undefinedKey": { argTypes: [], result: F64 }, - // node:events EventEmitter: receivers are emitter-hierarchy objects and - // the chaining forms (on/off/removeAll/setMax) return the receiver's - // own class — program-dependent object types, checked in the libCall - // case. emitter.emit is the one VARIADIC libCall: args beyond - // (recv, name) are the event's frontend-unified tuple, any borrowable - // kind — the count check admits a longer list for it alone. - /** node:events EventEmitter (scr_events_emitter.c, link-gated by - * moduleUsesEmitter). The receiver of every instance form is a borrowed - * emitter-hierarchy object (`%EventEmitter` or a user subclass — the - * backend reinterprets to ScrEmitter*, the identical prefix); event - * names are borrowed strings (compile-time literals — the frontend - * fences non-literals and unifies each event's argument tuple program- - * wide). The chaining forms (on/off/removeAll/setMax) return the - * receiver +1 typed as its static class, Node's `return this`. - * - * emitter.new: `new EventEmitter()` → a +1 bare emitter. emitter.ctor: - * super() into the prefix of an emitted subclass (borrowed receiver, - * void — allocation already initialized the prefix). emitter.on: - * (recv, name, cb /moves/, once, prepend) — the backend synthesizes the - * per-signature va_list invoke adapter from the cb's func type. - * emitter.emit: (recv, name, ...tuple) — VARIADIC, the one libCall - * whose arg count exceeds its signature; args are borrowed, result is - * the had-listeners bool. emitter.emitError: emit('error', err) — - * throws err when unhandled. count/countFn/names/listeners/getMax/ - * setMax/setDefaultMax/getDefaultMax are the introspection surface. */ - "emitter.new": { argTypes: [], result: VOID }, - /** `x.constructor.name` of an emitter or stream (a borrowed object of - * EventEmitter's hierarchy): the +1 class name the instance records. */ - "emitter.className": { argTypes: [null], result: STRING }, - "emitter.ctor": { argTypes: [null], result: VOID }, - "emitter.on": { argTypes: [null, STRING, null, BOOL, BOOL], result: VOID }, - "emitter.off": { argTypes: [null, STRING, null], result: VOID }, - // The dyn-adapted registration family (JS-lane checked-dynamic - // listeners): checkListener has NO receiver (it validates the listener - // argument alone); onDyn carries (recv, name, cb dyn, adapter func, - // once, prepend), offDyn (recv, name, cb dyn). - "emitter.checkListener": { argTypes: [DYN], result: VOID }, - "emitter.onDyn": { argTypes: [null, STRING, DYN, null, BOOL, BOOL], result: VOID }, - "emitter.onFlex": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, - /** on/once/prepend* and emit through an untyped value (the class - * dispatch of compiled emitters): onFlex/emitFlex plus runtime typed - * adapters under 'error' and the meta events, and Node's unhandled-'error' - * contract. emitAny is VARIADIC like emitFlex. */ - "emitter.onAny": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, - "emitter.offDyn": { argTypes: [null, STRING, DYN], result: VOID }, - "emitter.removeAll": { argTypes: [null, STRING, BOOL], result: VOID }, - "emitter.emit": { argTypes: [null, STRING], result: BOOL }, - "emitter.emitFlex": { argTypes: [null, STRING], result: BOOL }, - "emitter.emitAny": { argTypes: [null, STRING], result: BOOL }, - /** An untyped emit of an event with a fixed tuple: (recv, name, k, then - * k checked-dynamic arguments as given, then the event's converted - * tuple) — VARIADIC like emitter.emit. hasTyped answers whether a - * listener of the name needs the converted tuple. */ - "emitter.emitMixed": { argTypes: [null, STRING, F64], result: BOOL }, - "emitter.hasTyped": { argTypes: [null, STRING], result: BOOL }, - "emitter.emitError": { argTypes: [null, STRING, null], result: BOOL }, - "emitter.count": { argTypes: [null, STRING], result: F64 }, - "emitter.countFn": { argTypes: [null, STRING, null], result: F64 }, - "emitter.countDyn": { argTypes: [null, STRING, DYN], result: F64 }, - "emitter.names": { argTypes: [null], result: VOID }, - "emitter.listeners": { argTypes: [null, STRING], result: VOID }, - /** listeners()/rawListeners() through an untyped value: a checked- - * dynamic array (throws when a listener has typed parameters). */ - "emitter.listenersDyn": { argTypes: [null, STRING], result: DYN }, - /** Stream-'data' registration twins of emitter.on/onDyn (same args): - * chosen at registration sites whose receiver is stream-rooted, so the - * backend emits DATA thunks — the runtime's 'data' emission carries - * BOTH payload slots (bytes, string; exactly one non-NULL — encoded - * streams deliver strings), and the thunk unwraps the listener's - * declared side (typed) or boxes by tag (dyn). emitter.emitData is the - * user-emit form: (recv, name, chunk) with a bytes OR string chunk. */ - "emitter.onData": { argTypes: [null, STRING, null, BOOL, BOOL], result: VOID }, - "emitter.onDataDyn": { argTypes: [null, STRING, DYN, null, BOOL, BOOL], result: VOID }, - "emitter.emitData": { argTypes: [null, STRING, null], result: BOOL }, - "emitter.setMax": { argTypes: [null, F64], result: VOID }, - "emitter.getMax": { argTypes: [null], result: F64 }, - "emitter.setDefaultMax": { argTypes: [F64], result: VOID }, - "emitter.getDefaultMax": { argTypes: [], result: F64 }, - // The ES module namespace's copy of defaultMaxListeners, and - // module.syncBuiltinESMExports() refreshing it. - "emitter.esmDefaultMax": { argTypes: [], result: F64 }, - "emitter.esmSync": { argTypes: [], result: VOID }, - /** The events.errorMonitor event key: an immortal string the runtime - * registry matches by identity, so no string event name reaches it. */ - "emitter.errorMonitor": { argTypes: [], result: STRING }, - // node:stream: receivers are stream-hierarchy objects and several - // results are program-dependent (the receiver's class, unions) — the - // libCall case owns those checks. The constructors and a few methods - // are VARIADIC (trailing option callbacks / the optional chunk+cb tail): - // argTypes here is the fixed prefix, VARIADIC_LIB_FNS admits the rest. - /** node:stream (scr_stream.c, link-gated by moduleUsesStream — which - * implies the emitter unit: stream events dispatch through the embedded - * ScrEmitter registry). Receivers are borrowed stream-class objects - * (`%Readable`/`%Writable`/`%Duplex`/`%Transform`/`%PassThrough` — one - * runtime layout, reinterpreted by side). - * - * Constructors (`readable.new` et al): args are [hwmR, hwmW, flags] - * followed by the PRESENT user callbacks in canonical order (read, - * write, final, destroy, transform, flush — the flags f64 is a bitmask - * naming which follow; absent ones emit NULL). Every callback closure - * MOVES and carries a leading `this` param (the stream), invoked - * through compiler-emitted adapters. Results are +1. - * - * readable.push / readable.pushStr / readable.pushNull: Node's push — - * buffers or delivers (bytes chunk borrowed; string converted utf8); - * returns the below-hwm answer. readable.read: (recv, size — -1 for - * absent) → Buffer|null union. pause/resume return recv +1 (`this`); - * isPaused answers the flag. readable.pipe: (recv, dst, end) → dst +1, - * fires 'pipe' on dst; readable.pipeGeneral: the same, with `end` off - * when dst is the converted process.stdout / process.stderr; - * readable.unpipe (recv[, dst]) → recv +1. - * writable.write/writeStr: (recv, chunk[, cb]) → below-hwm bool (cb - * MOVES when present, called after the user write completes). - * writable.writeEnc: (recv, flags, chunk[, encoding][, cb]) — flags 1 - * an encoding argument, 2 a callback (the encoding is then null, as - * Node's write shifts it). - * writable.end: (recv, flags[, chunk][, encoding][, cb]) → recv +1; - * flags 16: an encoding argument follows the dyn chunk. cork/uncork are - * void. stream.destroy/destroyErr: (recv[, err]) → recv +1. - * stream.prop: (recv, name-literal) → the flag/number the name asks - * for; stream.errored → Error|null union; readable.flowing → - * bool|null union. All may leave a listener's exception pending - * (dispatch runs user code synchronously, like emit). */ - "readable.new": { argTypes: [F64, BOOL, BOOL, F64], result: VOID }, - "writable.new": { argTypes: [F64, BOOL, BOOL, F64], result: VOID }, - "duplex.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, - "transform.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, - "passthrough.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, - // The subclass-initialization twins: the borrowed receiver leads, then - // the .new tail (variadic callbacks likewise). - // The dyn-options twins: newDyn takes the record, initDyn the borrowed - // receiver + record + fallback flags (trailing wrapper closures ride - // the variadic tail like the static forms). - /** The underscore-method assignment surface (`r._read = fn` after - * construction — Node's own-property shadow of the prototype method): - * args [stream receiver (borrowed), callback closure (+1 moves)]. The - * runtime slot the matching option callback fills swaps its closure - * and invoke thunk; the next dispatch uses it (Node's timing). The - * setters themselves never throw. */ - "stream.setRead": { argTypes: [null, null], result: VOID }, - "stream.setWrite": { argTypes: [null, null], result: VOID }, - "stream.setFinal": { argTypes: [null, null], result: VOID }, - "stream.setDestroy": { argTypes: [null, null], result: VOID }, - "stream.setTransform": { argTypes: [null, null], result: VOID }, - "stream.setFlush": { argTypes: [null, null], result: VOID }, - "stream.setConstruct": { argTypes: [null, null], result: VOID }, - "stream.replaceConstruct": { argTypes: [null, null], result: VOID }, - "stream.setWritev": { argTypes: [null, null], result: VOID }, - "stream.setWritevDefault": { argTypes: [null, null], result: VOID }, - /** The dyn-options twins (a checked-dynamic options record — the JS - * lane's `super(options)` forwarding and `new Readable(dynVar)`): the - * option walk runs at RUNTIME with Node's reading rules. newDyn: - * (optsDyn) → the fresh stream; initDyn: (recv, optsDyn, flags, - * ...fallback wrapper closures in canonical order — the flags literal - * names which ride, exactly the .init callback ABI). MAY THROW (a - * consumed-but-unlowered option is the compile fence's runtime twin). */ - /** stream.finished(s, cb) — the callback form: the watcher fires once - * at the terminal point with the finish status; the result is the +1 - * cleanup closure. stream.pipeline(count, s1..sn, cb): chains pipes, - * propagates the first error by destroying the rest, calls cb after the - * last 'close'; answers the destination +1. The Dyn twins take the - * callback as a checked-dynamic VALUE (mustCall wrappers). */ - /** stream.addAbortSignal: [stream, signal] — the constructors' signal - * option, chained over the fresh stream (falsy signals are ignored); - * stream.addAbortSignalFn: [signal, stream] — addAbortSignal(signal, - * stream). Both answer the stream (+1) and throw ERR_INVALID_ARG_TYPE - * for a value that is not an AbortSignal; they link the event-target - * unit (addAbortListener). */ - "stream.addAbortSignal": { argTypes: [null, DYN], result: VOID }, - "stream.addAbortSignalFn": { argTypes: [DYN, null], result: VOID }, - "stream.finished": { argTypes: [null, null], result: VOID }, - "stream.finishedDyn": { argTypes: [null, DYN], result: VOID }, - "stream.pipeline": { argTypes: [F64], result: VOID }, - "stream.pipelineDyn": { argTypes: [F64], result: VOID }, - "stream.pipelineStages": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, - "stream.composeStages": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, - "stream.pipelineStagesDyn": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, - /** stream.compose(count, s1..sn): the Duplex over a pipeline of the - * streams (+1). MAY THROW (ERR_INVALID_ARG_VALUE for a stream that is - * no longer readable or writable). */ - "stream.compose": { argTypes: [F64], result: VOID }, - /** node:stream/promises — the promise forms over the same machinery: - * sp.finished: (s) → a pending void promise the terminal watcher - * settles; sp.pipeline: (count, s1..sn) → the callback pipeline's - * chaining/destroyer semantics settling a void promise (fulfilled on a - * clean finish, rejected with the finish status otherwise). */ - "sp.finished": { argTypes: [null], result: { kind: "promise", inner: VOID } }, - /** The options forms: finished(s, { readable, writable, error, cleanup, - * signal }) as [s, readable, writable, error, cleanup, signal?] — - * readable/writable -1 (absent), 0 or 1 — and pipeline(...streams, - * { signal, end }) as [count, s1..sn, end, signal?]. The Signal forms - * link the event-target unit (addAbortListener). The callback form - * stream.finishedOpts: [s, readable, writable, error, signal?, cb] → - * the cleanup closure. */ - "sp.finishedOpts": { - argTypes: [null, F64, F64, BOOL, BOOL], - result: { kind: "promise", inner: VOID }, - }, - "sp.finishedOptsSignal": { - argTypes: [null, F64, F64, BOOL, BOOL, DYN], - result: { kind: "promise", inner: VOID }, - }, - "sp.pipelineOpts": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - "sp.pipelineOptsSignal": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - "stream.finishedOpts": { argTypes: [null, F64, F64, BOOL, null], result: VOID }, - "stream.finishedOptsSignal": { argTypes: [null, F64, F64, BOOL, DYN, null], result: VOID }, - "sp.pipeline": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - /** pipeline with function stages: [n, k, positions, flags, mask, - * s1..sn, stage1..stagek, helpers..., box?, (signal, abort)?] (+ the - * callback for the stream.* forms): the user's stage functions at the - * positions the bitmask names, the frontend's (next, read, return) - * helpers for each pumped generator (pipelinePumpedRuns) and a promise - * destination's box, and the pipeline's AbortSignal with its abort - * closure; flags bits 1-2 give the destination kind and bit 5 the - * signal group (scr_stream_pipeline_stages). A promise destination's - * promise type is sp.pipelineStages' result. */ - "sp.pipelineStages": { - argTypes: [F64, F64, F64, F64, F64], - result: { kind: "promise", inner: VOID }, - }, - /** sp.pipelineStages with the options argument: the same head, then - * end (a bool) and, with flags bit 6, the options' signal (dyn). */ - "sp.pipelineStagesOpts": { - argTypes: [F64, F64, F64, F64, F64], - result: { kind: "promise", inner: VOID }, - }, - /** node:stream/consumers — the promise consumers over the readable - * machinery: (s) → a pending promise settled at the terminal point - * with the accumulated result (sc.text: the utf8 decode, sc.json: the - * parsed dyn — malformed input rejects with the parse's SyntaxError, - * sc.buffer: the concatenated bytes, sc.arrayBuffer: an ArrayBuffer - * dyn over them) or rejected with the stream's - * error / ERR_STREAM_PREMATURE_CLOSE. */ - "sc.text": { argTypes: [null], result: { kind: "promise", inner: STRING } }, - "sc.json": { argTypes: [null], result: { kind: "promise", inner: DYN } }, - "sc.buffer": { argTypes: [null], result: { kind: "promise", inner: BYTES_U8 } }, - "sc.arrayBuffer": { argTypes: [null], result: { kind: "promise", inner: DYN } }, - "readable.newDyn": { argTypes: [DYN], result: VOID }, - "writable.newDyn": { argTypes: [DYN], result: VOID }, - "duplex.newDyn": { argTypes: [DYN], result: VOID }, - "transform.newDyn": { argTypes: [DYN], result: VOID }, - "passthrough.newDyn": { argTypes: [DYN], result: VOID }, - "readable.initDyn": { argTypes: [null, DYN, F64], result: VOID }, - "writable.initDyn": { argTypes: [null, DYN, F64], result: VOID }, - "duplex.initDyn": { argTypes: [null, DYN, F64], result: VOID }, - "transform.initDyn": { argTypes: [null, DYN, F64], result: VOID }, - "passthrough.initDyn": { argTypes: [null, DYN, F64], result: VOID }, - /** Subclass initialization (`super(options?)` in a user `extends - * Readable` constructor): same tail as the `.new` forms, prefixed with - * the BORROWED receiver (the emitted subclass allocation — vtable and - * display name stamped, state NULL until here). Overridden underscore - * methods arrive as synthesized wrapper closures dispatching through - * the vtable. Void result. */ - "readable.init": { argTypes: [null, F64, BOOL, BOOL, F64], result: VOID }, - "writable.init": { argTypes: [null, F64, BOOL, BOOL, F64], result: VOID }, - "duplex.init": { argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, - "transform.init": { argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, - "passthrough.init": { - argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], - result: VOID, - }, - "readable.push": { argTypes: [null, BYTES_U8], result: BOOL }, - "readable.pushStr": { argTypes: [null, STRING], result: BOOL }, - "readable.pushNull": { argTypes: [null], result: BOOL }, - "readable.pushU": { argTypes: [null, null], result: BOOL }, - "readable.pushDyn": { argTypes: [null, DYN], result: BOOL }, - "readable.unshift": { argTypes: [null, BYTES_U8], result: VOID }, - "readable.unshiftDyn": { argTypes: [null, DYN], result: VOID }, - "readable.unshiftStr": { argTypes: [null, STRING], result: VOID }, - "stream.onDyn": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, - "readable.readDyn": { argTypes: [null, DYN], result: DYN }, - "readable.read": { argTypes: [null, F64], result: VOID }, - "readable.pause": { argTypes: [null], result: VOID }, - "readable.setEncoding": { argTypes: [null, STRING], result: VOID }, - /** push(chunk, enc) with a literal non-utf8 encoding, and the - * defaultEncoding option's push side (how push(string) decodes — - * Buffer.from(chunk, enc)); both carry the CANONICAL literal. */ - "readable.pushStrEnc": { argTypes: [null, STRING, STRING], result: BOOL }, - "readable.pushEncoding": { argTypes: [null, STRING], result: VOID }, - /** The objectMode option's sides (readable, writable), applied right - * after construction; answers the receiver (+1, the chaining shape). */ - "stream.objectMode": { argTypes: [null, BOOL, BOOL], result: VOID }, - "stream.decodeStrings": { argTypes: [null, BOOL], result: VOID }, - /** Runtime decodeStrings and defaultEncoding option values, applied - * after construction (answers the receiver +1); the encoding was - * validated by stream.checkEncodingOption before construction. */ - "stream.optionValues": { argTypes: [null, DYN, DYN], result: VOID }, - "stream.checkEncodingOption": { argTypes: [DYN], result: VOID }, - "stream.duplexFromGen": { argTypes: [null, null, null, null, null, null], result: VOID }, - "stream.duplexFromDyn": { argTypes: [DYN], result: VOID }, - /** Duplex.from over a lone Readable or Writable, or a { readable, - * writable } pair of streams (duplexify.js's _duplexify). */ - "stream.duplexifyReadable": { argTypes: [null], result: VOID }, - "stream.duplexifyWritable": { argTypes: [null], result: VOID }, - "stream.duplexifyPair": { argTypes: [null, null], result: VOID }, - /** Duplex.from(promise): the promise and its box helper. */ - "stream.duplexFromPromise": { argTypes: [null, null], result: VOID }, - /** Duplex.from(asyncFunction): the stage, its source helper and the - * promise's box helper. */ - "stream.duplexFromAsyncFn": { argTypes: [null, null, null], result: VOID }, - /** The same for a body that declares the options argument: then the - * AbortController's abort() and its signal. */ - "stream.duplexFromGenSignal": { - argTypes: [null, null, null, null, null, null, null, DYN], - result: VOID, - }, - "stream.duplexFromAsyncFnSignal": { argTypes: [null, null, null, null, DYN], result: VOID }, - "writable.writeEncoding": { argTypes: [null, STRING], result: VOID }, - "writable.setDefaultEncoding": { argTypes: [null, DYN], result: VOID }, - /** Whether a readable.nextChunkDyn step answered the end (the done - * marker; an object-mode stream can yield undefined). */ - "stream.chunkDone": { argTypes: [DYN], result: BOOL }, - /** for-await over a readable (the desugared loop's per-pass promise): - * +1 promise of the next chunk — buffered content, the EOF sentinel - * (empty Buffer / dyn undefined), or a rejection with the stream's - * error. The Dyn twin boxes chunks by runtime tag (the JS lane). - * readable.fromArr is Readable.from over a typed array, string or - * Buffer: [array, SCR_FROM_* flags, highWaterMark]. readable.fromDyn is - * Readable.from over a checked-dynamic source: [source, options]. - * readable.fromIter is Readable.from over a class iterator, already - * opened: [checked-dynamic { next } object, flags, highWaterMark]; - * readable.fromAsyncIter is the async class iterator's twin. */ - "readable.nextChunk": { argTypes: [null], result: VOID }, - "readable.nextChunkDyn": { argTypes: [null], result: VOID }, - "readable.fromArr": { argTypes: [null, F64, F64], result: VOID }, - "readable.fromDyn": { argTypes: [DYN, DYN], result: VOID }, - "readable.fromIter": { argTypes: [DYN, F64, F64], result: VOID }, - "readable.fromAsyncIter": { argTypes: [DYN, F64, F64], result: VOID }, - /** Readable.from over a compiled generator: [generator, next, return, - * throw, flags, highWaterMark] (sync) or [generator, next, read, - * return, throw, flags, highWaterMark] (async), the helpers being the - * frontend's interned closures. readable.fromDone is the done marker - * the helpers answer. */ - "readable.fromGen": { argTypes: [null, null, null, null, F64, F64], result: VOID }, - "readable.fromAsyncGen": { argTypes: [null, null, null, null, null, F64, F64], result: VOID }, - "readable.fromDone": { argTypes: [], result: DYN }, - /** The Readable operators (scr_stream_ops.c): [stream, ...] with every - * other argument a checked-dynamic value (the callback, the count, the - * options). map/filter/flatMap/drop/take answer the object-mode output - * stream (validated as a stream class; THROW Node's validation errors); - * toArray settles a dyn array, find a dyn value, some/every a boolean, - * forEach nothing; reduce: [stream, fn, init, hasInit, options, kind] - * with the payload kind (0 dyn, 1 number, 2 boolean, 3 string). The - * promise-returning ones reject instead of throwing. */ - "readable.opMap": { argTypes: [null, DYN, DYN, DYN], result: VOID }, - "readable.opFilter": { argTypes: [null, DYN, DYN, DYN], result: VOID }, - "readable.opFlatMap": { argTypes: [null, DYN, DYN, DYN], result: VOID }, - /** flatMap whose callback returns a compiled generator: the helpers - * over its type follow (async: next, read, return, throw; sync: next, - * return, throw). */ - "readable.opFlatMapAsyncGen": { - argTypes: [null, DYN, DYN, DYN, null, null, null, null], - result: VOID, - }, - "readable.opFlatMapSyncGen": { argTypes: [null, DYN, DYN, DYN, null, null, null], result: VOID }, - "readable.opDrop": { argTypes: [null, DYN, DYN], result: VOID }, - "readable.opTake": { argTypes: [null, DYN, DYN], result: VOID }, - "readable.opToArray": { argTypes: [null, DYN], result: { kind: "promise", inner: DYN } }, - "readable.opForEach": { - argTypes: [null, DYN, DYN, DYN], - result: { kind: "promise", inner: VOID }, - }, - "readable.opSome": { argTypes: [null, DYN, DYN, DYN], result: { kind: "promise", inner: BOOL } }, - "readable.opEvery": { argTypes: [null, DYN, DYN, DYN], result: { kind: "promise", inner: BOOL } }, - "readable.opFind": { argTypes: [null, DYN, DYN, DYN], result: { kind: "promise", inner: DYN } }, - /** The map family's last argument is the callback's { signal } (or - * undefined). reduceSignal adds ac.abort() (a closure) and - * { signal: ac.signal } for a reducer that reads them. */ - "readable.opReduce": { argTypes: [null, DYN, DYN, BOOL, DYN, F64], result: VOID }, - "readable.opReduceSignal": { - argTypes: [null, DYN, DYN, BOOL, DYN, F64, null, DYN], - result: VOID, - }, - "readable.opSignalList": { argTypes: [DYN], result: DYN }, - "readable.opSignalOpts": { argTypes: [DYN], result: DYN }, - "readable.opSignalOf": { argTypes: [DYN], result: DYN }, - "readable.opOnceOptions": { argTypes: [], result: DYN }, - "readable.resume": { argTypes: [null], result: VOID }, - "readable.isPaused": { argTypes: [null], result: BOOL }, - "readable.pipe": { argTypes: [null, null, BOOL], result: VOID }, - "readable.pipeGeneral": { argTypes: [null, null, BOOL], result: VOID }, - "readable.unpipe": { argTypes: [null], result: VOID }, - "readable.flowing": { argTypes: [null], result: VOID }, - "writable.write": { argTypes: [null, BYTES_U8], result: BOOL }, - "writable.writeStr": { argTypes: [null, STRING], result: BOOL }, - "writable.writeU": { argTypes: [null, null], result: BOOL }, - "writable.writeDyn": { argTypes: [null, DYN], result: BOOL }, - "writable.writeEnc": { argTypes: [null, F64, DYN], result: BOOL }, - "writable.end": { argTypes: [null, F64], result: VOID }, - "writable.cork": { argTypes: [null], result: VOID }, - "writable.uncork": { argTypes: [null], result: VOID }, - "stream.destroy": { argTypes: [null], result: VOID }, - "stream.destroyErr": { argTypes: [null, null], result: VOID }, - /** AsyncIteratorClose for Readable[Symbol.asyncIterator](): destroy with - * Node's AbortError/ABORT_ERR payload while the iterator's internal error - * consumer prevents an unhandled-error crash. */ - "stream.iteratorClose": { argTypes: [null], result: VOID }, - "stream.prop": { argTypes: [null, STRING], result: VOID }, - "stream.errored": { argTypes: [null], result: VOID }, - // node:assert: pass/negated/deep/hasMsg are frontend-computed bools; the - // message slot always carries a string ("" when hasMsg is false). - /** node:assert (scr_assert.c; assert.match in scr_regex.c — every call - * site carries a regex value, so the regex link switch is already on). - * Failures throw a catchable AssertionError — a runtime %Error whose - * name is "AssertionError" and whose code slot is "ERR_ASSERTION" — so - * `instanceof Error`, `.name`, `.message`, and `.code` all answer like - * Node's. Generated messages are Node's assertion_error.js scalar forms - * byte-exactly (the short `a !== b` form, the stacked `+ actual - * - expected` diff with the string `^` indicator, the inline-vs-block - * not-equal split); composite deep failures carry the header line - * without the rendered inspect diff (documented divergence). - * - * assert.ok: (pass, message) — the frontend computed the truthiness AND - * the full message (the user's, or the compile-time source-text form — - * assert.fail lowers here too with pass=false). assert.eqF64/eqBigInt/ - * eqStr/eqBool: (a, b, negated, deep, msg, hasMsg) — Object.is comparison, - * covering strictEqual/notStrictEqual and the scalar deepStrictEqual - * pair; msg is a typed dummy ("" literal) when hasMsg is false (Node - * distinguishes an omitted message from an empty one per operator). - * assert.deepResult: (equal, negated, msg, hasMsg) — the verdict of a - * frontend-synthesized structural comparison, turned into Node's throw. - * assert.sameValue: Object.is over doubles (the deep-equal helpers' - * number leaf; never throws). assert.match: (s, regex, negated, msg, - * hasMsg) — a fresh exec from index 0. assert.throwsNone: (rejection, - * ename, hasEname, msg, hasMsg) — the "Missing expected - * exception|rejection" throw of assert.throws/rejects whose callback - * returned (fulfilled) normally, with Node's ` (${expected.name})` - * detail when the expected class or shape carries a name. - * assert.throwsMismatch: (expectedName, error) — the wrong-class throw - * of the assert.throws(fn, ErrorClass) form. assert.eqSym: - * (a, b, negated, deep, msg, hasMsg) — strict equality over symbol - * values, pointer identity with v24's "Symbol(desc)" stacked-diff - * messages (scr_symbol.c, the assert.match pattern — symbol-typed - * slots already flip the symbol link switch). assert.eqDyn: - * (a, b, negated, deep, msg, hasMsg) — the whole quartet over - * checked-dynamic operands (both slots dyn; the frontend boxes a - * static side with dynFrom first): SameValue for the strict pair over - * the dyn kinds (boxed-closure identity for functions), the structural - * dyn walk for the deep pair, with assertion_error.js's messages — - * scalar forms byte-exact, composites rendered compact:false/sorted - * through the checked-dynamic tree and diffed with the real myers line printer. - * - * The assert.throws(fn, {name/code/message}) shape check - * (expectedException over the static error surface): shapeBegin - * stashes the caught error, shapeStr/shapeRe add one expected key each - * (key ids 0 code / 1 message / 2 name; shapeRe lives in scr_regex.c — - * its regex argument flips the regex link switch — and tests eagerly - * so scr_assert.c stays libregexp-free), then shapeEnd throws Node's - * deep-equal Comparison diff BYTE-EXACTLY (the bounded key set makes - * the rendering enumerable) or the custom message. - * assert.throwsRegex: (regex, error, msg, hasMsg) — the - * assert.throws(fn, /re/) check over String(error), Node's - * regex-mismatch message. assert.regexErrTest: doesNotReject's - * regex predicate over String(error) (never throws). - * assert.unwantedRejection: (error, msg, hasMsg) — doesNotReject's - * "Got unwanted rejection" throw. - * assert.ifErrorErr/F64/Str/Bool: assert.ifError's per-type throws - * ("ifError got unwanted exception: " + the error's message/name or - * the value's inspection) — the frontend routes null/undefined to a - * no-op and everything else here (Node throws for falsy values too). - * All arguments are borrowed. */ - "assert.ok": { argTypes: [BOOL, STRING, BOOL], result: VOID }, - /** The next AssertionError's actual, expected and operator, recorded on - * a failure branch. */ - "assert.record": { argTypes: [DYN, DYN, STRING], result: VOID }, - "assert.errorNew": { argTypes: [DYN], result: { kind: "object", className: "%AssertionError" } }, - "assert.eqF64": { argTypes: [F64, F64, BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.eqBigInt": { argTypes: [BIGINT_T, BIGINT_T, BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.eqStr": { argTypes: [STRING, STRING, BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.eqBool": { argTypes: [BOOL, BOOL, BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.deepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.looseDeepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.looseDeepDyn": { argTypes: [DYN, DYN, BOOL, STRING, BOOL], result: VOID }, - "assert.partialDeepDyn": { argTypes: [DYN, DYN, STRING, BOOL], result: VOID }, - "assert.partialDeepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.partialEqDyn": { argTypes: [DYN, DYN], result: BOOL }, - "assert.looseF64Eq": { argTypes: [F64, F64], result: BOOL }, - "assert.sameValue": { argTypes: [F64, F64], result: BOOL }, - // The deep-equality pair memo: both slots are program-dependent - // (any cycle-capable record/array/map type). - /* deepStrictEqual's pair memo over cycle-capable types: enter answers - * true for a pair already being compared (Node's memo — equal cyclic - * structures compare true); leave pops. */ - "assert.deqEnter": { argTypes: [null, null], result: BOOL }, - "assert.deqLeave": { argTypes: [], result: VOID }, - "assert.match": { argTypes: [STRING, REGEX, BOOL, STRING, BOOL], result: VOID }, - "assert.throwsNone": { argTypes: [BOOL, STRING, BOOL, STRING, BOOL], result: VOID }, - "assert.throwsMismatch": { - argTypes: [STRING, { kind: "object", className: "%Error" }, STRING, BOOL, BOOL], - result: VOID, - }, - "assert.throwsRegex": { - argTypes: [REGEX, { kind: "object", className: "%Error" }, STRING, BOOL, BOOL], - result: VOID, - }, - // Symbol strict equality (pointer identity; scr_symbol.c). - "assert.eqSym": { - argTypes: [{ kind: "symbol" }, { kind: "symbol" }, BOOL, BOOL, STRING, BOOL], - result: VOID, - }, - // The equality quartet over checked-dynamic operands (the frontend - // boxes a static side into the checked-dynamic tree first). - "assert.eqDyn": { argTypes: [DYN, DYN, BOOL, BOOL, STRING, BOOL], result: VOID }, - /** A message argument that is not a string, registered for the next - * failing assertion (scr_assert_message_value). */ - "assert.messageValue": { argTypes: [DYN, BOOL], result: VOID }, - /** An assert method as a function value, by canonical name. */ - "assert.methodValue": { argTypes: [STRING], result: DYN }, - "assert.messageClear": { argTypes: [], result: VOID }, - "assert.looseResult": { argTypes: [BOOL, BOOL, STRING, STRING, STRING, BOOL], result: VOID }, - // The throws(fn, {shape}) accumulator: begin/slot calls never throw; - // shapeEnd throws the Comparison diff. The error slot is the - // %Error-narrowed caught value. - "assert.shapeBegin": { argTypes: [{ kind: "object", className: "%Error" }], result: VOID }, - "assert.shapeStr": { argTypes: [F64, STRING], result: VOID }, - "assert.shapeRe": { argTypes: [F64, REGEX], result: VOID }, - "assert.shapeEnd": { argTypes: [STRING, BOOL, BOOL], result: VOID }, - "assert.regexErrTest": { - argTypes: [REGEX, { kind: "object", className: "%Error" }], - result: BOOL, - }, - "assert.unwantedRejection": { - argTypes: [{ kind: "object", className: "%Error" }, STRING, BOOL], - result: VOID, - }, - "assert.unwantedError": { argTypes: [STRING, BOOL, STRING, BOOL], result: VOID }, - "assert.noErrorPredicate": { argTypes: [DYN, DYN], result: BOOL }, - "assert.regexDynTest": { argTypes: [REGEX, DYN], result: BOOL }, - /** Node's expectsError over an error-INSTANCE expected (assert.throws/ - * rejects second argument): walk the expected dyn error's keys (name, - * message, code — the %error marker skipped) and deep-compare each - * against the caught value's; a mismatch throws the deep-equal - * AssertionError (scr_assert.c). MAY THROW by design. */ - "assert.expectsErrDyn": { argTypes: [DYN, DYN, STRING, BOOL], result: VOID }, - /** A validation function against the caught value (Node's expectedException). */ - "assert.throwsValidate": { argTypes: [DYN, DYN, STRING, STRING, BOOL], result: VOID }, - // assert.ifError's typed entries (always throw; unit args never - // lower). The error slot is program-dependent — any %Error-hierarchy - // class, root or subclass (the insp.error precedent, null slot). - "assert.ifErrorErr": { argTypes: [null], result: VOID }, - "assert.ifErrorF64": { argTypes: [F64], result: VOID }, - "assert.ifErrorStr": { argTypes: [STRING], result: VOID }, - "assert.ifErrorBool": { argTypes: [BOOL], result: VOID }, - "assert.ifErrorDyn": { argTypes: [DYN], result: VOID }, - // Bytes equality: the two value slots are any bytes kind (the frontend - // gates both sides to ONE static bytes type; the libCall case checks - // the kind and the pair below). - "assert.refEqBytes": { argTypes: [null, null, BOOL, BOOL, STRING, BOOL], result: VOID }, - "assert.refEqFn": { argTypes: [null, null, BOOL, STRING, BOOL], result: VOID }, - "assert.bytesDeepEq": { argTypes: [null, null, BOOL], result: BOOL }, - "assert.looseBytesEq": { argTypes: [null, null], result: BOOL }, - // util.inspect: the error receiver is program-dependent (a builtin or - // user error class — the error.toString precedent, null slot). + "dyn.objCreateNullProto": { argTypes: [], result: DYN }, + "dyn.arrayPrototype": { argTypes: [], result: DYN }, + "dyn.arrayConstructor": { argTypes: [], result: DYN }, + "dyn.objectPrototype": { argTypes: [], result: DYN }, + "dyn.errorPrototype": { argTypes: [], result: DYN }, + "dyn.inheritsErrorPrototype": { argTypes: [DYN], result: BOOL }, + "dyn.errorCaptureStackTrace": { argTypes: [DYN], result: DYN }, + "dyn.errorCaptureStackTraceFunction": { argTypes: [], result: DYN }, + "dyn.functionApply": { argTypes: [], result: DYN }, + "dyn.builtinMethod": { argTypes: [STRING, STRING], result: DYN }, + "dyn.classPrototype": { argTypes: [DYN, DYN], result: DYN }, + "dyn.classBasePrototype": { argTypes: [DYN], result: DYN }, + "dyn.classInherit": { argTypes: [DYN, DYN], result: DYN }, + /** EventEmitter (0), Stream (1), Readable (2), Writable (3), Duplex (4), + * Transform (5) and PassThrough (6) as untyped constructor values, one + * per thread (scr_dyn_builtin_class.c). */ + "dyn.builtinClass": { argTypes: [F64], result: DYN }, + /** The stream classes (2-6 above) with their stream half: constructor + * functions over them get native stream state + * (scr_dyn_builtin_stream.c). */ + "dyn.builtinStreamClass": { argTypes: [F64], result: DYN }, + /** `value instanceof C` for those constructors, over the value's + * prototype chain. */ + "dyn.builtinInstanceOf": { argTypes: [DYN, F64], result: BOOL }, + /** `value instanceof f` for an untyped constructor function: the + * prototype chain against f.prototype. */ + "dyn.functionInstanceOf": { argTypes: [DYN, DYN], result: BOOL }, + "dyn.classSuper": { argTypes: [DYN, DYN, DYN], result: VOID }, + "dyn.assignPrototype": { argTypes: [DYN, DYN, DYN], result: DYN }, + "dyn.definePrototypeProps": { argTypes: [DYN, DYN, DYN], result: DYN }, + "dyn.bagGet": { argTypes: [DYN, STRING, DYN], result: DYN }, + /** A class field's creation in its instance's property bag: adds the + * string key as a default data property when absent, answering false + * (nothing changed) when the bag needs the general defineProperty path. */ + "dyn.bagCreateField": { argTypes: [DYN, STRING], result: BOOL }, + "dyn.bagSet": { argTypes: [DYN, STRING, DYN, DYN], result: VOID }, + "dyn.objCreate": { argTypes: [DYN], result: DYN }, + // Names a compiled class's prototype object (args[0]) after the class + // (args[1]) for util.inspect. + "dyn.nameClassPrototype": { argTypes: [DYN, STRING], result: VOID }, + "dyn.objCreateWithProperties": { argTypes: [DYN, DYN], result: DYN }, + "dyn.getPrototype": { argTypes: [DYN], result: DYN }, + // dyn.getPrototype for walks that compare each prototype with a program + // class's prototype: native builtin values end the walk (null). + "dyn.getPrototypeForIdentity": { argTypes: [DYN], result: DYN }, + "dyn.setPrototype": { argTypes: [DYN, DYN], result: DYN }, + "dyn.getOwnPropertyNames": { argTypes: [DYN], result: DYN }, + "dyn.ownKeys": { argTypes: [DYN], result: DYN }, + "dyn.getOwnPropertySymbols": { argTypes: [DYN], result: DYN }, + "dyn.getOwnPropertyDescriptors": { argTypes: [DYN], result: DYN }, + "dyn.preventExtensions": { argTypes: [DYN], result: DYN }, + "dyn.isExtensible": { argTypes: [DYN], result: BOOL }, + "dyn.seal": { argTypes: [DYN], result: DYN }, + "dyn.isSealed": { argTypes: [DYN], result: BOOL }, + "dyn.objValues": { argTypes: [DYN], result: DYN }, + /** Object.values as a native vector of checked values, for results the + * program consumes directly (for-of, index reads, read-only methods). */ + "dyn.objValueList": { argTypes: [DYN], result: arrayOf(DYN) }, + "dyn.objEntries": { argTypes: [DYN], result: DYN }, + /** structuredClone over the checked-dynamic tree (scr_json.c): the JSON-safe subset plus + * bytes (a fresh copy — a Buffer clones as a plain Uint8Array, like + * Node), deep. Functions and handle kinds throw the spec's catchable + * DataCloneError; CYCLES throw the scriptc fence (the checked-dynamic tree cannot + * represent them — Node clones cycles; documented divergence). The + * options dyn value validates with Node's exact errors (dictionary + * conversion, the transfer-sequence member; any non-empty transfer + * list throws DataCloneError). dyn.cloneMissing is the zero-argument + * 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 + * catchable DataCloneError ("Found invalid value in transferList.") — + * lowered directly (the list's values need no dyn representation to + * fail). */ + "dyn.cloneTransferFail": { argTypes: [], result: DYN }, + /** The same refusal given the transfer list's first member, an + * ArrayBuffer: a Buffer pool slab gets Node's untransferable message. */ + "dyn.cloneTransferFailWith": { argTypes: [DYN], result: DYN }, + /** `new RegExp(pattern, flags?)` (scr_regex.c): a heap regex over the + * same libregexp engine the literals use. The pattern compiles EAGERLY + * so an invalid pattern (or an unknown flag letter) throws Node's + * catchable SyntaxError at construction — Node's message shape with + * libregexp's detail text (approximate fidelity; e.name exact). An + * empty pattern stores the spec's "(?:)" source. Both args borrowed + * strings (the lowering completes an absent flags to ""); result +1. + * The result TYPE is the regex kind, so the link switch pulls the + * engine exactly like a literal. */ + "regex.new": { argTypes: [STRING, STRING], result: REGEX }, + "regex.resetLastIndex": { argTypes: [REGEX, F64], result: VOID }, + "regex.newChecked": { argTypes: [DYN, DYN], result: REGEX }, + "regex.replaceCallback": { argTypes: [STRING, REGEX, DYN, BOOL], result: STRING }, + // Reads of capture args[1] of an execSpans/matchGroups result: + // `capture === args[2]`, Number(capture) and parseInt(capture, + // args[2]). Borrowed; never throw. + "regex.groupEquals": { argTypes: [arrayOf(STRING), F64, STRING], result: BOOL }, + "regex.groupNumber": { argTypes: [arrayOf(STRING), F64], result: F64 }, + "regex.groupParseInt": { argTypes: [arrayOf(STRING), F64, F64], result: F64 }, + // `.index` / `.input` (args[1]) of a match result. Borrowed; never throws. + "regex.resultProp": { argTypes: [arrayOf(STRING), STRING], result: DYN }, + // The string keys `for...in` visits on an array (args[0], any element + // type) after its indices. Borrowed; never throws. + "array.namedKeys": { argTypes: [null], result: arrayOf(STRING) }, + // Whether the array (args[0]) still has the key (args[1]) from its + // namedKeys list. Borrowed; never throws. + "array.hasNamedKey": { argTypes: [null, STRING], result: BOOL }, + // Whether an array (args[0], any element type) has properties besides its + // elements: noncanonical numeric keys, match metadata or named + // properties. Borrowed; never throws. + "array.hasProperties": { argTypes: [null], result: BOOL }, + // The key of an element access whose index is undefined (the property + // named "undefined"; frontend/lowering/undefined-index.ts). + "array.undefinedKey": { argTypes: [], result: F64 }, + // node:events EventEmitter: receivers are emitter-hierarchy objects and + // the chaining forms (on/off/removeAll/setMax) return the receiver's + // own class — program-dependent object types, checked in the libCall + // case. emitter.emit is the one VARIADIC libCall: args beyond + // (recv, name) are the event's frontend-unified tuple, any borrowable + // kind — the count check admits a longer list for it alone. + /** node:events EventEmitter (scr_events_emitter.c, link-gated by + * moduleUsesEmitter). The receiver of every instance form is a borrowed + * emitter-hierarchy object (`%EventEmitter` or a user subclass — the + * backend reinterprets to ScrEmitter*, the identical prefix); event + * names are borrowed strings (compile-time literals — the frontend + * fences non-literals and unifies each event's argument tuple program- + * wide). The chaining forms (on/off/removeAll/setMax) return the + * receiver +1 typed as its static class, Node's `return this`. + * + * emitter.new: `new EventEmitter()` → a +1 bare emitter. emitter.ctor: + * super() into the prefix of an emitted subclass (borrowed receiver, + * void — allocation already initialized the prefix). emitter.on: + * (recv, name, cb /moves/, once, prepend) — the backend synthesizes the + * per-signature va_list invoke adapter from the cb's func type. + * emitter.emit: (recv, name, ...tuple) — VARIADIC, the one libCall + * whose arg count exceeds its signature; args are borrowed, result is + * the had-listeners bool. emitter.emitError: emit('error', err) — + * throws err when unhandled. count/countFn/names/listeners/getMax/ + * setMax/setDefaultMax/getDefaultMax are the introspection surface. */ + "emitter.new": { argTypes: [], result: VOID }, + /** `x.constructor.name` of an emitter or stream (a borrowed object of + * EventEmitter's hierarchy): the +1 class name the instance records. */ + "emitter.className": { argTypes: [null], result: STRING }, + "emitter.ctor": { argTypes: [null], result: VOID }, + "emitter.on": { argTypes: [null, STRING, null, BOOL, BOOL], result: VOID }, + "emitter.off": { argTypes: [null, STRING, null], result: VOID }, + // The dyn-adapted registration family (JS-lane checked-dynamic + // listeners): checkListener has NO receiver (it validates the listener + // argument alone); onDyn carries (recv, name, cb dyn, adapter func, + // once, prepend), offDyn (recv, name, cb dyn). + "emitter.checkListener": { argTypes: [DYN], result: VOID }, + "emitter.onDyn": { argTypes: [null, STRING, DYN, null, BOOL, BOOL], result: VOID }, + "emitter.onFlex": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, + /** on/once/prepend* and emit through an untyped value (the class + * dispatch of compiled emitters): onFlex/emitFlex plus runtime typed + * adapters under 'error' and the meta events, and Node's unhandled-'error' + * contract. emitAny is VARIADIC like emitFlex. */ + "emitter.onAny": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, + "emitter.offDyn": { argTypes: [null, STRING, DYN], result: VOID }, + "emitter.removeAll": { argTypes: [null, STRING, BOOL], result: VOID }, + "emitter.emit": { argTypes: [null, STRING], result: BOOL }, + "emitter.emitFlex": { argTypes: [null, STRING], result: BOOL }, + "emitter.emitAny": { argTypes: [null, STRING], result: BOOL }, + /** An untyped emit of an event with a fixed tuple: (recv, name, k, then + * k checked-dynamic arguments as given, then the event's converted + * tuple) — VARIADIC like emitter.emit. hasTyped answers whether a + * listener of the name needs the converted tuple. */ + "emitter.emitMixed": { argTypes: [null, STRING, F64], result: BOOL }, + "emitter.hasTyped": { argTypes: [null, STRING], result: BOOL }, + "emitter.emitError": { argTypes: [null, STRING, null], result: BOOL }, + "emitter.count": { argTypes: [null, STRING], result: F64 }, + "emitter.countFn": { argTypes: [null, STRING, null], result: F64 }, + "emitter.countDyn": { argTypes: [null, STRING, DYN], result: F64 }, + "emitter.names": { argTypes: [null], result: VOID }, + "emitter.listeners": { argTypes: [null, STRING], result: VOID }, + /** listeners()/rawListeners() through an untyped value: a checked- + * dynamic array (throws when a listener has typed parameters). */ + "emitter.listenersDyn": { argTypes: [null, STRING], result: DYN }, + /** Stream-'data' registration twins of emitter.on/onDyn (same args): + * chosen at registration sites whose receiver is stream-rooted, so the + * backend emits DATA thunks — the runtime's 'data' emission carries + * BOTH payload slots (bytes, string; exactly one non-NULL — encoded + * streams deliver strings), and the thunk unwraps the listener's + * declared side (typed) or boxes by tag (dyn). emitter.emitData is the + * user-emit form: (recv, name, chunk) with a bytes OR string chunk. */ + "emitter.onData": { argTypes: [null, STRING, null, BOOL, BOOL], result: VOID }, + "emitter.onDataDyn": { argTypes: [null, STRING, DYN, null, BOOL, BOOL], result: VOID }, + "emitter.emitData": { argTypes: [null, STRING, null], result: BOOL }, + "emitter.setMax": { argTypes: [null, F64], result: VOID }, + "emitter.getMax": { argTypes: [null], result: F64 }, + "emitter.setDefaultMax": { argTypes: [F64], result: VOID }, + "emitter.getDefaultMax": { argTypes: [], result: F64 }, + // The ES module namespace's copy of defaultMaxListeners, and + // module.syncBuiltinESMExports() refreshing it. + "emitter.esmDefaultMax": { argTypes: [], result: F64 }, + "emitter.esmSync": { argTypes: [], result: VOID }, + /** The events.errorMonitor event key: an immortal string the runtime + * registry matches by identity, so no string event name reaches it. */ + "emitter.errorMonitor": { argTypes: [], result: STRING }, + // node:stream: receivers are stream-hierarchy objects and several + // results are program-dependent (the receiver's class, unions) — the + // libCall case owns those checks. The constructors and a few methods + // are VARIADIC (trailing option callbacks / the optional chunk+cb tail): + // argTypes here is the fixed prefix, VARIADIC_LIB_FNS admits the rest. + /** node:stream (scr_stream.c, link-gated by moduleUsesStream — which + * implies the emitter unit: stream events dispatch through the embedded + * ScrEmitter registry). Receivers are borrowed stream-class objects + * (`%Readable`/`%Writable`/`%Duplex`/`%Transform`/`%PassThrough` — one + * runtime layout, reinterpreted by side). + * + * Constructors (`readable.new` et al): args are [hwmR, hwmW, flags] + * followed by the PRESENT user callbacks in canonical order (read, + * write, final, destroy, transform, flush — the flags f64 is a bitmask + * naming which follow; absent ones emit NULL). Every callback closure + * MOVES and carries a leading `this` param (the stream), invoked + * through compiler-emitted adapters. Results are +1. + * + * readable.push / readable.pushStr / readable.pushNull: Node's push — + * buffers or delivers (bytes chunk borrowed; string converted utf8); + * returns the below-hwm answer. readable.read: (recv, size — -1 for + * absent) → Buffer|null union. pause/resume return recv +1 (`this`); + * isPaused answers the flag. readable.pipe: (recv, dst, end) → dst +1, + * fires 'pipe' on dst; readable.pipeGeneral: the same, with `end` off + * when dst is the converted process.stdout / process.stderr; + * readable.unpipe (recv[, dst]) → recv +1. + * writable.write/writeStr: (recv, chunk[, cb]) → below-hwm bool (cb + * MOVES when present, called after the user write completes). + * writable.writeEnc: (recv, flags, chunk[, encoding][, cb]) — flags 1 + * an encoding argument, 2 a callback (the encoding is then null, as + * Node's write shifts it). + * writable.end: (recv, flags[, chunk][, encoding][, cb]) → recv +1; + * flags 16: an encoding argument follows the dyn chunk. cork/uncork are + * void. stream.destroy/destroyErr: (recv[, err]) → recv +1. + * stream.prop: (recv, name-literal) → the flag/number the name asks + * for; stream.errored → Error|null union; readable.flowing → + * bool|null union. All may leave a listener's exception pending + * (dispatch runs user code synchronously, like emit). */ + "readable.new": { argTypes: [F64, BOOL, BOOL, F64], result: VOID }, + "writable.new": { argTypes: [F64, BOOL, BOOL, F64], result: VOID }, + "duplex.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, + "transform.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, + "passthrough.new": { argTypes: [F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, + // The subclass-initialization twins: the borrowed receiver leads, then + // the .new tail (variadic callbacks likewise). + // The dyn-options twins: newDyn takes the record, initDyn the borrowed + // receiver + record + fallback flags (trailing wrapper closures ride + // the variadic tail like the static forms). + /** The underscore-method assignment surface (`r._read = fn` after + * construction — Node's own-property shadow of the prototype method): + * args [stream receiver (borrowed), callback closure (+1 moves)]. The + * runtime slot the matching option callback fills swaps its closure + * and invoke thunk; the next dispatch uses it (Node's timing). The + * setters themselves never throw. */ + "stream.setRead": { argTypes: [null, null], result: VOID }, + "stream.setWrite": { argTypes: [null, null], result: VOID }, + "stream.setFinal": { argTypes: [null, null], result: VOID }, + "stream.setDestroy": { argTypes: [null, null], result: VOID }, + "stream.setTransform": { argTypes: [null, null], result: VOID }, + "stream.setFlush": { argTypes: [null, null], result: VOID }, + "stream.setConstruct": { argTypes: [null, null], result: VOID }, + "stream.replaceConstruct": { argTypes: [null, null], result: VOID }, + "stream.setWritev": { argTypes: [null, null], result: VOID }, + "stream.setWritevDefault": { argTypes: [null, null], result: VOID }, + /** The dyn-options twins (a checked-dynamic options record — the JS + * lane's `super(options)` forwarding and `new Readable(dynVar)`): the + * option walk runs at RUNTIME with Node's reading rules. newDyn: + * (optsDyn) → the fresh stream; initDyn: (recv, optsDyn, flags, + * ...fallback wrapper closures in canonical order — the flags literal + * names which ride, exactly the .init callback ABI). MAY THROW (a + * consumed-but-unlowered option is the compile fence's runtime twin). */ + /** stream.finished(s, cb) — the callback form: the watcher fires once + * at the terminal point with the finish status; the result is the +1 + * cleanup closure. stream.pipeline(count, s1..sn, cb): chains pipes, + * propagates the first error by destroying the rest, calls cb after the + * last 'close'; answers the destination +1. The Dyn twins take the + * callback as a checked-dynamic VALUE (mustCall wrappers). */ + /** stream.addAbortSignal: [stream, signal] — the constructors' signal + * option, chained over the fresh stream (falsy signals are ignored); + * stream.addAbortSignalFn: [signal, stream] — addAbortSignal(signal, + * stream). Both answer the stream (+1) and throw ERR_INVALID_ARG_TYPE + * for a value that is not an AbortSignal; they link the event-target + * unit (addAbortListener). */ + "stream.addAbortSignal": { argTypes: [null, DYN], result: VOID }, + "stream.addAbortSignalFn": { argTypes: [DYN, null], result: VOID }, + "stream.finished": { argTypes: [null, null], result: VOID }, + "stream.finishedDyn": { argTypes: [null, DYN], result: VOID }, + "stream.pipeline": { argTypes: [F64], result: VOID }, + "stream.pipelineDyn": { argTypes: [F64], result: VOID }, + "stream.pipelineStages": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, + "stream.composeStages": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, + "stream.pipelineStagesDyn": { argTypes: [F64, F64, F64, F64, F64], result: VOID }, + /** stream.compose(count, s1..sn): the Duplex over a pipeline of the + * streams (+1). MAY THROW (ERR_INVALID_ARG_VALUE for a stream that is + * no longer readable or writable). */ + "stream.compose": { argTypes: [F64], result: VOID }, + /** node:stream/promises — the promise forms over the same machinery: + * sp.finished: (s) → a pending void promise the terminal watcher + * settles; sp.pipeline: (count, s1..sn) → the callback pipeline's + * chaining/destroyer semantics settling a void promise (fulfilled on a + * clean finish, rejected with the finish status otherwise). */ + "sp.finished": { argTypes: [null], result: { kind: "promise", inner: VOID } }, + /** The options forms: finished(s, { readable, writable, error, cleanup, + * signal }) as [s, readable, writable, error, cleanup, signal?] — + * readable/writable -1 (absent), 0 or 1 — and pipeline(...streams, + * { signal, end }) as [count, s1..sn, end, signal?]. The Signal forms + * link the event-target unit (addAbortListener). The callback form + * stream.finishedOpts: [s, readable, writable, error, signal?, cb] → + * the cleanup closure. */ + "sp.finishedOpts": { + argTypes: [null, F64, F64, BOOL, BOOL], + result: { kind: "promise", inner: VOID }, + }, + "sp.finishedOptsSignal": { + argTypes: [null, F64, F64, BOOL, BOOL, DYN], + result: { kind: "promise", inner: VOID }, + }, + "sp.pipelineOpts": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + "sp.pipelineOptsSignal": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + "stream.finishedOpts": { argTypes: [null, F64, F64, BOOL, null], result: VOID }, + "stream.finishedOptsSignal": { argTypes: [null, F64, F64, BOOL, DYN, null], result: VOID }, + "sp.pipeline": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + /** pipeline with function stages: [n, k, positions, flags, mask, + * s1..sn, stage1..stagek, helpers..., box?, (signal, abort)?] (+ the + * callback for the stream.* forms): the user's stage functions at the + * positions the bitmask names, the frontend's (next, read, return) + * helpers for each pumped generator (pipelinePumpedRuns) and a promise + * destination's box, and the pipeline's AbortSignal with its abort + * closure; flags bits 1-2 give the destination kind and bit 5 the + * signal group (scr_stream_pipeline_stages). A promise destination's + * promise type is sp.pipelineStages' result. */ + "sp.pipelineStages": { + argTypes: [F64, F64, F64, F64, F64], + result: { kind: "promise", inner: VOID }, + }, + /** sp.pipelineStages with the options argument: the same head, then + * end (a bool) and, with flags bit 6, the options' signal (dyn). */ + "sp.pipelineStagesOpts": { + argTypes: [F64, F64, F64, F64, F64], + result: { kind: "promise", inner: VOID }, + }, + /** node:stream/consumers — the promise consumers over the readable + * machinery: (s) → a pending promise settled at the terminal point + * with the accumulated result (sc.text: the utf8 decode, sc.json: the + * parsed dyn — malformed input rejects with the parse's SyntaxError, + * sc.buffer: the concatenated bytes, sc.arrayBuffer: an ArrayBuffer + * dyn over them) or rejected with the stream's + * error / ERR_STREAM_PREMATURE_CLOSE. */ + "sc.text": { argTypes: [null], result: { kind: "promise", inner: STRING } }, + "sc.json": { argTypes: [null], result: { kind: "promise", inner: DYN } }, + "sc.buffer": { argTypes: [null], result: { kind: "promise", inner: BYTES_U8 } }, + "sc.arrayBuffer": { argTypes: [null], result: { kind: "promise", inner: DYN } }, + "readable.newDyn": { argTypes: [DYN], result: VOID }, + "writable.newDyn": { argTypes: [DYN], result: VOID }, + "duplex.newDyn": { argTypes: [DYN], result: VOID }, + "transform.newDyn": { argTypes: [DYN], result: VOID }, + "passthrough.newDyn": { argTypes: [DYN], result: VOID }, + "readable.initDyn": { argTypes: [null, DYN, F64], result: VOID }, + "writable.initDyn": { argTypes: [null, DYN, F64], result: VOID }, + "duplex.initDyn": { argTypes: [null, DYN, F64], result: VOID }, + "transform.initDyn": { argTypes: [null, DYN, F64], result: VOID }, + "passthrough.initDyn": { argTypes: [null, DYN, F64], result: VOID }, + /** Subclass initialization (`super(options?)` in a user `extends + * Readable` constructor): same tail as the `.new` forms, prefixed with + * the BORROWED receiver (the emitted subclass allocation — vtable and + * display name stamped, state NULL until here). Overridden underscore + * methods arrive as synthesized wrapper closures dispatching through + * the vtable. Void result. */ + "readable.init": { argTypes: [null, F64, BOOL, BOOL, F64], result: VOID }, + "writable.init": { argTypes: [null, F64, BOOL, BOOL, F64], result: VOID }, + "duplex.init": { argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], result: VOID }, + "transform.init": { + argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], + result: VOID, + }, + "passthrough.init": { + argTypes: [null, F64, F64, BOOL, BOOL, BOOL, BOOL, BOOL, F64], + result: VOID, + }, + "readable.push": { argTypes: [null, BYTES_U8], result: BOOL }, + "readable.pushStr": { argTypes: [null, STRING], result: BOOL }, + "readable.pushNull": { argTypes: [null], result: BOOL }, + "readable.pushU": { argTypes: [null, null], result: BOOL }, + "readable.pushDyn": { argTypes: [null, DYN], result: BOOL }, + "readable.unshift": { argTypes: [null, BYTES_U8], result: VOID }, + "readable.unshiftDyn": { argTypes: [null, DYN], result: VOID }, + "readable.unshiftStr": { argTypes: [null, STRING], result: VOID }, + "stream.onDyn": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID }, + "readable.readDyn": { argTypes: [null, DYN], result: DYN }, + "readable.read": { argTypes: [null, F64], result: VOID }, + "readable.pause": { argTypes: [null], result: VOID }, + "readable.setEncoding": { argTypes: [null, STRING], result: VOID }, + /** push(chunk, enc) with a literal non-utf8 encoding, and the + * defaultEncoding option's push side (how push(string) decodes — + * Buffer.from(chunk, enc)); both carry the CANONICAL literal. */ + "readable.pushStrEnc": { argTypes: [null, STRING, STRING], result: BOOL }, + "readable.pushEncoding": { argTypes: [null, STRING], result: VOID }, + /** The objectMode option's sides (readable, writable), applied right + * after construction; answers the receiver (+1, the chaining shape). */ + "stream.objectMode": { argTypes: [null, BOOL, BOOL], result: VOID }, + "stream.decodeStrings": { argTypes: [null, BOOL], result: VOID }, + /** Runtime decodeStrings and defaultEncoding option values, applied + * after construction (answers the receiver +1); the encoding was + * validated by stream.checkEncodingOption before construction. */ + "stream.optionValues": { argTypes: [null, DYN, DYN], result: VOID }, + "stream.checkEncodingOption": { argTypes: [DYN], result: VOID }, + "stream.duplexFromGen": { argTypes: [null, null, null, null, null, null], result: VOID }, + "stream.duplexFromDyn": { argTypes: [DYN], result: VOID }, + /** Duplex.from over a lone Readable or Writable, or a { readable, + * writable } pair of streams (duplexify.js's _duplexify). */ + "stream.duplexifyReadable": { argTypes: [null], result: VOID }, + "stream.duplexifyWritable": { argTypes: [null], result: VOID }, + "stream.duplexifyPair": { argTypes: [null, null], result: VOID }, + /** Duplex.from(promise): the promise and its box helper. */ + "stream.duplexFromPromise": { argTypes: [null, null], result: VOID }, + /** Duplex.from(asyncFunction): the stage, its source helper and the + * promise's box helper. */ + "stream.duplexFromAsyncFn": { argTypes: [null, null, null], result: VOID }, + /** The same for a body that declares the options argument: then the + * AbortController's abort() and its signal. */ + "stream.duplexFromGenSignal": { + argTypes: [null, null, null, null, null, null, null, DYN], + result: VOID, + }, + "stream.duplexFromAsyncFnSignal": { argTypes: [null, null, null, null, DYN], result: VOID }, + "writable.writeEncoding": { argTypes: [null, STRING], result: VOID }, + "writable.setDefaultEncoding": { argTypes: [null, DYN], result: VOID }, + /** Whether a readable.nextChunkDyn step answered the end (the done + * marker; an object-mode stream can yield undefined). */ + "stream.chunkDone": { argTypes: [DYN], result: BOOL }, + /** for-await over a readable (the desugared loop's per-pass promise): + * +1 promise of the next chunk — buffered content, the EOF sentinel + * (empty Buffer / dyn undefined), or a rejection with the stream's + * error. The Dyn twin boxes chunks by runtime tag (the JS lane). + * readable.fromArr is Readable.from over a typed array, string or + * Buffer: [array, SCR_FROM_* flags, highWaterMark]. readable.fromDyn is + * Readable.from over a checked-dynamic source: [source, options]. + * readable.fromIter is Readable.from over a class iterator, already + * opened: [checked-dynamic { next } object, flags, highWaterMark]; + * readable.fromAsyncIter is the async class iterator's twin. */ + "readable.nextChunk": { argTypes: [null], result: VOID }, + "readable.nextChunkDyn": { argTypes: [null], result: VOID }, + "readable.fromArr": { argTypes: [null, F64, F64], result: VOID }, + "readable.fromDyn": { argTypes: [DYN, DYN], result: VOID }, + "readable.fromIter": { argTypes: [DYN, F64, F64], result: VOID }, + "readable.fromAsyncIter": { argTypes: [DYN, F64, F64], result: VOID }, + /** Readable.from over a compiled generator: [generator, next, return, + * throw, flags, highWaterMark] (sync) or [generator, next, read, + * return, throw, flags, highWaterMark] (async), the helpers being the + * frontend's interned closures. readable.fromDone is the done marker + * the helpers answer. */ + "readable.fromGen": { argTypes: [null, null, null, null, F64, F64], result: VOID }, + "readable.fromAsyncGen": { argTypes: [null, null, null, null, null, F64, F64], result: VOID }, + "readable.fromDone": { argTypes: [], result: DYN }, + /** The Readable operators (scr_stream_ops.c): [stream, ...] with every + * other argument a checked-dynamic value (the callback, the count, the + * options). map/filter/flatMap/drop/take answer the object-mode output + * stream (validated as a stream class; THROW Node's validation errors); + * toArray settles a dyn array, find a dyn value, some/every a boolean, + * forEach nothing; reduce: [stream, fn, init, hasInit, options, kind] + * with the payload kind (0 dyn, 1 number, 2 boolean, 3 string). The + * promise-returning ones reject instead of throwing. */ + "readable.opMap": { argTypes: [null, DYN, DYN, DYN], result: VOID }, + "readable.opFilter": { argTypes: [null, DYN, DYN, DYN], result: VOID }, + "readable.opFlatMap": { argTypes: [null, DYN, DYN, DYN], result: VOID }, + /** flatMap whose callback returns a compiled generator: the helpers + * over its type follow (async: next, read, return, throw; sync: next, + * return, throw). */ + "readable.opFlatMapAsyncGen": { + argTypes: [null, DYN, DYN, DYN, null, null, null, null], + result: VOID, + }, + "readable.opFlatMapSyncGen": { + argTypes: [null, DYN, DYN, DYN, null, null, null], + result: VOID, + }, + "readable.opDrop": { argTypes: [null, DYN, DYN], result: VOID }, + "readable.opTake": { argTypes: [null, DYN, DYN], result: VOID }, + "readable.opToArray": { argTypes: [null, DYN], result: { kind: "promise", inner: DYN } }, + "readable.opForEach": { + argTypes: [null, DYN, DYN, DYN], + result: { kind: "promise", inner: VOID }, + }, + "readable.opSome": { + argTypes: [null, DYN, DYN, DYN], + result: { kind: "promise", inner: BOOL }, + }, + "readable.opEvery": { + argTypes: [null, DYN, DYN, DYN], + result: { kind: "promise", inner: BOOL }, + }, + "readable.opFind": { argTypes: [null, DYN, DYN, DYN], result: { kind: "promise", inner: DYN } }, + /** The map family's last argument is the callback's { signal } (or + * undefined). reduceSignal adds ac.abort() (a closure) and + * { signal: ac.signal } for a reducer that reads them. */ + "readable.opReduce": { argTypes: [null, DYN, DYN, BOOL, DYN, F64], result: VOID }, + "readable.opReduceSignal": { + argTypes: [null, DYN, DYN, BOOL, DYN, F64, null, DYN], + result: VOID, + }, + "readable.opSignalList": { argTypes: [DYN], result: DYN }, + "readable.opSignalOpts": { argTypes: [DYN], result: DYN }, + "readable.opSignalOf": { argTypes: [DYN], result: DYN }, + "readable.opOnceOptions": { argTypes: [], result: DYN }, + "readable.resume": { argTypes: [null], result: VOID }, + "readable.isPaused": { argTypes: [null], result: BOOL }, + "readable.pipe": { argTypes: [null, null, BOOL], result: VOID }, + "readable.pipeGeneral": { argTypes: [null, null, BOOL], result: VOID }, + "readable.unpipe": { argTypes: [null], result: VOID }, + "readable.flowing": { argTypes: [null], result: VOID }, + "writable.write": { argTypes: [null, BYTES_U8], result: BOOL }, + "writable.writeStr": { argTypes: [null, STRING], result: BOOL }, + "writable.writeU": { argTypes: [null, null], result: BOOL }, + "writable.writeDyn": { argTypes: [null, DYN], result: BOOL }, + "writable.writeEnc": { argTypes: [null, F64, DYN], result: BOOL }, + "writable.end": { argTypes: [null, F64], result: VOID }, + "writable.cork": { argTypes: [null], result: VOID }, + "writable.uncork": { argTypes: [null], result: VOID }, + "stream.destroy": { argTypes: [null], result: VOID }, + "stream.destroyErr": { argTypes: [null, null], result: VOID }, + /** AsyncIteratorClose for Readable[Symbol.asyncIterator](): destroy with + * Node's AbortError/ABORT_ERR payload while the iterator's internal error + * consumer prevents an unhandled-error crash. */ + "stream.iteratorClose": { argTypes: [null], result: VOID }, + "stream.prop": { argTypes: [null, STRING], result: VOID }, + "stream.errored": { argTypes: [null], result: VOID }, + // node:assert: pass/negated/deep/hasMsg are frontend-computed bools; the + // message slot always carries a string ("" when hasMsg is false). + /** node:assert (scr_assert.c; assert.match in scr_regex.c — every call + * site carries a regex value, so the regex link switch is already on). + * Failures throw a catchable AssertionError — a runtime %Error whose + * name is "AssertionError" and whose code slot is "ERR_ASSERTION" — so + * `instanceof Error`, `.name`, `.message`, and `.code` all answer like + * Node's. Generated messages are Node's assertion_error.js scalar forms + * byte-exactly (the short `a !== b` form, the stacked `+ actual + * - expected` diff with the string `^` indicator, the inline-vs-block + * not-equal split); composite deep failures carry the header line + * without the rendered inspect diff (documented divergence). + * + * assert.ok: (pass, message) — the frontend computed the truthiness AND + * the full message (the user's, or the compile-time source-text form — + * assert.fail lowers here too with pass=false). assert.eqF64/eqBigInt/ + * eqStr/eqBool: (a, b, negated, deep, msg, hasMsg) — Object.is comparison, + * covering strictEqual/notStrictEqual and the scalar deepStrictEqual + * pair; msg is a typed dummy ("" literal) when hasMsg is false (Node + * distinguishes an omitted message from an empty one per operator). + * assert.deepResult: (equal, negated, msg, hasMsg) — the verdict of a + * frontend-synthesized structural comparison, turned into Node's throw. + * assert.sameValue: Object.is over doubles (the deep-equal helpers' + * number leaf; never throws). assert.match: (s, regex, negated, msg, + * hasMsg) — a fresh exec from index 0. assert.throwsNone: (rejection, + * ename, hasEname, msg, hasMsg) — the "Missing expected + * exception|rejection" throw of assert.throws/rejects whose callback + * returned (fulfilled) normally, with Node's ` (${expected.name})` + * detail when the expected class or shape carries a name. + * assert.throwsMismatch: (expectedName, error) — the wrong-class throw + * of the assert.throws(fn, ErrorClass) form. assert.eqSym: + * (a, b, negated, deep, msg, hasMsg) — strict equality over symbol + * values, pointer identity with v24's "Symbol(desc)" stacked-diff + * messages (scr_symbol.c, the assert.match pattern — symbol-typed + * slots already flip the symbol link switch). assert.eqDyn: + * (a, b, negated, deep, msg, hasMsg) — the whole quartet over + * checked-dynamic operands (both slots dyn; the frontend boxes a + * static side with dynFrom first): SameValue for the strict pair over + * the dyn kinds (boxed-closure identity for functions), the structural + * dyn walk for the deep pair, with assertion_error.js's messages — + * scalar forms byte-exact, composites rendered compact:false/sorted + * through the checked-dynamic tree and diffed with the real myers line printer. + * + * The assert.throws(fn, {name/code/message}) shape check + * (expectedException over the static error surface): shapeBegin + * stashes the caught error, shapeStr/shapeRe add one expected key each + * (key ids 0 code / 1 message / 2 name; shapeRe lives in scr_regex.c — + * its regex argument flips the regex link switch — and tests eagerly + * so scr_assert.c stays libregexp-free), then shapeEnd throws Node's + * deep-equal Comparison diff BYTE-EXACTLY (the bounded key set makes + * the rendering enumerable) or the custom message. + * assert.throwsRegex: (regex, error, msg, hasMsg) — the + * assert.throws(fn, /re/) check over String(error), Node's + * regex-mismatch message. assert.regexErrTest: doesNotReject's + * regex predicate over String(error) (never throws). + * assert.unwantedRejection: (error, msg, hasMsg) — doesNotReject's + * "Got unwanted rejection" throw. + * assert.ifErrorErr/F64/Str/Bool: assert.ifError's per-type throws + * ("ifError got unwanted exception: " + the error's message/name or + * the value's inspection) — the frontend routes null/undefined to a + * no-op and everything else here (Node throws for falsy values too). + * All arguments are borrowed. */ + "assert.ok": { argTypes: [BOOL, STRING, BOOL], result: VOID }, + /** The next AssertionError's actual, expected and operator, recorded on + * a failure branch. */ + "assert.record": { argTypes: [DYN, DYN, STRING], result: VOID }, + "assert.errorNew": { + argTypes: [DYN], + result: { kind: "object", className: "%AssertionError" }, + }, + "assert.eqF64": { argTypes: [F64, F64, BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.eqBigInt": { argTypes: [BIGINT_T, BIGINT_T, BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.eqStr": { argTypes: [STRING, STRING, BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.eqBool": { argTypes: [BOOL, BOOL, BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.deepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.looseDeepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.looseDeepDyn": { argTypes: [DYN, DYN, BOOL, STRING, BOOL], result: VOID }, + "assert.partialDeepDyn": { argTypes: [DYN, DYN, STRING, BOOL], result: VOID }, + "assert.partialDeepResult": { argTypes: [BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.partialEqDyn": { argTypes: [DYN, DYN], result: BOOL }, + "assert.looseF64Eq": { argTypes: [F64, F64], result: BOOL }, + "assert.sameValue": { argTypes: [F64, F64], result: BOOL }, + // The deep-equality pair memo: both slots are program-dependent + // (any cycle-capable record/array/map type). + /* deepStrictEqual's pair memo over cycle-capable types: enter answers + * true for a pair already being compared (Node's memo — equal cyclic + * structures compare true); leave pops. */ + "assert.deqEnter": { argTypes: [null, null], result: BOOL }, + "assert.deqLeave": { argTypes: [], result: VOID }, + "assert.match": { argTypes: [STRING, REGEX, BOOL, STRING, BOOL], result: VOID }, + "assert.throwsNone": { argTypes: [BOOL, STRING, BOOL, STRING, BOOL], result: VOID }, + "assert.throwsMismatch": { + argTypes: [STRING, { kind: "object", className: "%Error" }, STRING, BOOL, BOOL], + result: VOID, + }, + "assert.throwsRegex": { + argTypes: [REGEX, { kind: "object", className: "%Error" }, STRING, BOOL, BOOL], + result: VOID, + }, + // Symbol strict equality (pointer identity; scr_symbol.c). + "assert.eqSym": { + argTypes: [{ kind: "symbol" }, { kind: "symbol" }, BOOL, BOOL, STRING, BOOL], + result: VOID, + }, + // The equality quartet over checked-dynamic operands (the frontend + // boxes a static side into the checked-dynamic tree first). + "assert.eqDyn": { argTypes: [DYN, DYN, BOOL, BOOL, STRING, BOOL], result: VOID }, + /** A message argument that is not a string, registered for the next + * failing assertion (scr_assert_message_value). */ + "assert.messageValue": { argTypes: [DYN, BOOL], result: VOID }, + /** An assert method as a function value, by canonical name. */ + "assert.methodValue": { argTypes: [STRING], result: DYN }, + "assert.messageClear": { argTypes: [], result: VOID }, + "assert.looseResult": { argTypes: [BOOL, BOOL, STRING, STRING, STRING, BOOL], result: VOID }, + // The throws(fn, {shape}) accumulator: begin/slot calls never throw; + // shapeEnd throws the Comparison diff. The error slot is the + // %Error-narrowed caught value. + "assert.shapeBegin": { argTypes: [{ kind: "object", className: "%Error" }], result: VOID }, + "assert.shapeStr": { argTypes: [F64, STRING], result: VOID }, + "assert.shapeRe": { argTypes: [F64, REGEX], result: VOID }, + "assert.shapeEnd": { argTypes: [STRING, BOOL, BOOL], result: VOID }, + "assert.regexErrTest": { + argTypes: [REGEX, { kind: "object", className: "%Error" }], + result: BOOL, + }, + "assert.unwantedRejection": { + argTypes: [{ kind: "object", className: "%Error" }, STRING, BOOL], + result: VOID, + }, + "assert.unwantedError": { argTypes: [STRING, BOOL, STRING, BOOL], result: VOID }, + "assert.noErrorPredicate": { argTypes: [DYN, DYN], result: BOOL }, + "assert.regexDynTest": { argTypes: [REGEX, DYN], result: BOOL }, + /** Node's expectsError over an error-INSTANCE expected (assert.throws/ + * rejects second argument): walk the expected dyn error's keys (name, + * message, code — the %error marker skipped) and deep-compare each + * against the caught value's; a mismatch throws the deep-equal + * AssertionError (scr_assert.c). MAY THROW by design. */ + "assert.expectsErrDyn": { argTypes: [DYN, DYN, STRING, BOOL], result: VOID }, + /** A validation function against the caught value (Node's expectedException). */ + "assert.throwsValidate": { argTypes: [DYN, DYN, STRING, STRING, BOOL], result: VOID }, + // assert.ifError's typed entries (always throw; unit args never + // lower). The error slot is program-dependent — any %Error-hierarchy + // class, root or subclass (the insp.error precedent, null slot). + "assert.ifErrorErr": { argTypes: [null], result: VOID }, + "assert.ifErrorF64": { argTypes: [F64], result: VOID }, + "assert.ifErrorStr": { argTypes: [STRING], result: VOID }, + "assert.ifErrorBool": { argTypes: [BOOL], result: VOID }, + "assert.ifErrorDyn": { argTypes: [DYN], result: VOID }, + // Bytes equality: the two value slots are any bytes kind (the frontend + // gates both sides to ONE static bytes type; the libCall case checks + // the kind and the pair below). + "assert.refEqBytes": { argTypes: [null, null, BOOL, BOOL, STRING, BOOL], result: VOID }, + "assert.refEqFn": { argTypes: [null, null, BOOL, STRING, BOOL], result: VOID }, + "assert.bytesDeepEq": { argTypes: [null, null, BOOL], result: BOOL }, + "assert.looseBytesEq": { argTypes: [null, null], result: BOOL }, + // util.inspect: the error receiver is program-dependent (a builtin or + // user error class — the error.toString precedent, null slot). - "insp.f64": { argTypes: [F64], result: STRING }, - /** util.format %j over a checked-dynamic argument: the runtime dyn - * walk (JS-exact stringify; root undefined/function prints - * "undefined"; a handle in the tree throws the "not supported yet" - * Error). */ - "insp.jsonDyn": { argTypes: [DYN], result: STRING }, - "insp.str": { argTypes: [STRING], result: STRING }, - "insp.regex": { argTypes: [REGEX], result: STRING }, - /** A Date's inspect text: toISOString(), or "Invalid Date". */ - "insp.date": { argTypes: [DATE_T], result: STRING }, - "insp.buffer": { argTypes: [BYTES_U8], result: STRING }, - /** A numeric typed array that is not a Buffer (any element kind). */ - "insp.typedArray": { argTypes: [null, F64, F64], result: STRING }, - /** A u8 byte view in a union arm, element or field: a DataView, Buffer - * or Uint8Array by its own flags. */ - "insp.bytes": { argTypes: [null, F64, F64], result: STRING }, - "insp.error": { argTypes: [null, F64, F64], result: STRING }, - "insp.errorHasCause": { argTypes: [null], result: BOOL }, - "insp.errorBagHasProperties": { argTypes: [DYN], result: BOOL }, - "insp.errorCause": { argTypes: [null, F64, F64], result: STRING }, - "insp.dyn": { argTypes: [DYN, F64, F64], result: STRING }, - "insp.dynS": { argTypes: [DYN, F64], result: STRING }, - /** util.format's %o over a checked-dynamic value: inspect with - * showHidden. Kinds whose hidden entries have no rendering throw the - * 'not supported yet' Error. */ - "insp.dynHidden": { argTypes: [DYN, F64], result: STRING }, - "insp.begin": { argTypes: [F64], result: VOID }, - /** util.inspect's breakLength, compact (0 for false) and sorted for the - * renders that follow; the call site restores 80, 3 and false afterwards. */ - "insp.layout": { argTypes: [F64, F64, BOOL], result: VOID }, - "insp.entry": { argTypes: [STRING, BOOL], result: VOID }, - // Circular references: the receiver slot is program-dependent (any - // cycle-capable record/array/map/class type — the insp.error precedent). - /* Circular references over cycle-capable composites (recursive record/ - * class types): circCheck answers a value's circular id when it is - * already on the traversal stack (0 otherwise), seenPush/refWrap - * bracket the frame (insp.end adds Node's " " prefix to values - * the walk found circular), circular renders "[Circular *N]". */ - "insp.circCheck": { argTypes: [null], result: F64 }, - "insp.seenPush": { argTypes: [null], result: VOID }, - "insp.refWrap": { argTypes: [null, STRING], result: STRING }, - "insp.circular": { argTypes: [F64], result: STRING }, - /* Whether a closed declared-field record kept the undeclared properties - * of its checked source (scr_record_source_note): reflective surfaces - * (inspect, Object.keys/values/entries) then answer through its live - * checked view. The receiver is any such record type. Never throws. */ - "record.sourceHas": { argTypes: [null], result: BOOL }, - /** `record.sourceSpread(built, value)`: the record a literal starting - * with `...value` just built keeps the order of value's kept source - * (key-order-witness.ts ordered spreads). Both are record types. Never - * throws. */ - "record.sourceSpread": { argTypes: [null, null], result: VOID }, - "insp.key": { argTypes: [STRING], result: STRING }, - "insp.moreItems": { argTypes: [F64], result: STRING }, - "insp.end": { argTypes: [STRING, STRING, STRING, F64, BOOL, BOOL], result: STRING }, - /** A nested inspect inside a custom rendering (PerformanceEntry's - * `${name} ${inspect(toJSON)}`): nestedBegin starts a fresh inspect - * context, nestedEnd(prefix, inner) restores the caller's and answers - * `prefix inner` re-indented at the caller's level (+1). */ - "insp.nestedBegin": { argTypes: [], result: VOID }, - "insp.nestedEnd": { argTypes: [STRING, STRING], result: STRING }, - /** Around a top-level render that an inspection refusal can end: mark - * records the inspector's frame and nesting depth, unwind (in the - * render's finally) closes what the render left open above it. */ - "insp.mark": { argTypes: [], result: VOID }, - "insp.unwind": { argTypes: [], result: VOID }, - /** node:string_decoder's utf8 StringDecoder (scr_string.c): the decoder - * value is a one-field record whose f64 PACKS the pending partial - * sequence (count + up to 3 raw bytes — Node buffers at most 3 for - * every encoding); the frontend's interned %strdec helpers thread it, - * with the decoder's CANONICAL encoding name first, through these pure - * functions. write: (enc, pending, chunk) → the decoded complete - * prefix of pending+chunk (+1); next: (enc, pending, chunk) → the - * packed NEW pending; end: (enc, pending) → the buffered partial's - * flush (+1). Node-exact per encoding (oracle-pinned); none throws. */ - "strdec.write": { argTypes: [STRING, F64, BYTES_U8], result: STRING }, - "strdec.next": { argTypes: [STRING, F64, BYTES_U8], result: F64 }, - "strdec.end": { argTypes: [STRING, F64], result: STRING }, - /** A runtime encoding argument's canonical name (Node's normalizeEncoding: - * nullish and "" are utf8); an unknown name throws ERR_UNKNOWN_ENCODING. */ - "strdec.encoding": { argTypes: [DYN], result: STRING }, - // node:readline: the interface handle is f64; the callbacks' func types - // are program-dependent (the emitter picks the adapter by parameter - // count), so those slots are null like child.onExit's. - /** node:readline and node:readline/promises (scr_readline.c, linked - * under the events gate — these fns imply moduleUsesProcessEvents; a - * stream input or output adds scr_stream.c's bridge). The interface - * value is an f64 handle (the Timeout-id precedent); an open interface - * on stdin keeps the loop alive until close/EOF. - * rl.create: [mode, input?, output?, crlfDelay, prompt?] → f64, mode - * bits 1 stream input, 2 stream output, 4 stdout output, 16 prompt - * (lowerReadlineCreateCall); rl.createSignal: the same plus the - * trailing AbortSignal (mode bit 8), which links the event-target unit - * for addAbortListener. rl.question: [handle, query, cb] — the callback - * form; THROWS ERR_USE_AFTER_CLOSE on a closed interface (may-throw). - * rl.questionAsync(Signal): [handle, query, signal?] → promise, - * rejecting instead of throwing. rl.close/pause/resume/prompt: - * [handle] (pause/resume/prompt throw ERR_USE_AFTER_CLOSE when - * closed); rl.setPrompt/getPrompt; rl.write: [handle, data] (throws - * when closed). rl.on: [handle, event, cb, once] — the event number - * (0 'line', 1 'close', 2 'pause', 3 'resume', 4 'error') and the - * listener (moves). rl.iterOpen/iterNext/iterReturn: the async - * iterator — iterNext answers promise of the line or undefined. */ - "rl.create": { argTypes: [F64], result: F64 }, - "rl.createSignal": { argTypes: [F64], result: F64 }, - "rl.question": { argTypes: [F64, STRING, null], result: VOID }, - "rl.questionAsync": { argTypes: [F64, STRING], result: { kind: "promise", inner: STRING } }, - "rl.questionAsyncSignal": { - argTypes: [F64, STRING, DYN], - result: { kind: "promise", inner: STRING }, - }, - /** The question forms with a checked-dynamic query (a JavaScript value - * that need not be a string). */ - "rl.questionDyn": { argTypes: [F64, DYN, null], result: VOID }, - "rl.questionAsyncDyn": { argTypes: [F64, DYN], result: { kind: "promise", inner: STRING } }, - "rl.questionAsyncSignalDyn": { - argTypes: [F64, DYN, DYN], - result: { kind: "promise", inner: STRING }, - }, - "rl.close": { argTypes: [F64], result: VOID }, - "rl.pause": { argTypes: [F64], result: VOID }, - "rl.resume": { argTypes: [F64], result: VOID }, - "rl.prompt": { argTypes: [F64], result: VOID }, - /** prompt(preserveCursor): [handle, preserveCursor] */ - "rl.promptWith": { argTypes: [F64, BOOL], result: VOID }, - "rl.setPrompt": { argTypes: [F64, STRING], result: VOID }, - "rl.setPromptDyn": { argTypes: [F64, DYN], result: VOID }, - "rl.getPrompt": { argTypes: [F64], result: STRING }, - "rl.write": { argTypes: [F64, STRING], result: VOID }, - /** write(data, key): [handle, hasData, data, hasKey, hasName, name, - * ctrl, meta, shift] */ - "rl.writeKey": { - argTypes: [F64, BOOL, STRING, BOOL, BOOL, STRING, BOOL, BOOL, BOOL], - result: VOID, - }, - "rl.on": { argTypes: [F64, F64, null, BOOL], result: VOID }, - "rl.iterOpen": { argTypes: [F64], result: VOID }, - "rl.iterNext": { argTypes: [F64], result: { kind: "promise", inner: DYN } }, - "rl.iterReturn": { argTypes: [F64], result: VOID }, - /** createInterface's history and editing options (scr_rl_check_options: - * [flags, tabSize, escapeCodeTimeout, historySize], may throw; - * rl.setOptions: [handle, flags, historySize, removeHistoryDuplicates, - * tabSize, escapeCodeTimeout]; rl.setHistory: [handle, history]) and - * the member reads (rl.history answers the array, rl.numProp the - * numeric member by number). */ - "rl.checkOptions": { argTypes: [F64, F64, F64, F64], result: VOID }, - "rl.checkCompleter": { argTypes: [DYN], result: VOID }, - "rl.setOptions": { argTypes: [F64, F64, F64, BOOL, F64, F64], result: VOID }, - "rl.setHistory": { argTypes: [F64, arrayOf(STRING)], result: VOID }, - "rl.history": { argTypes: [F64], result: arrayOf(STRING) }, - "rl.numProp": { argTypes: [F64, F64], result: F64 }, - /** Terminal mode (scr_readline_tty.c): rl.terminal: [handle, mode (0 - * the option's value, 2 output.isTTY of process.stdout), terminal, - * completer (undefined when absent), readline/promises], emitted with - * scr_insp_dyn when there is a completer; the line, terminal and cursor - * members. */ - "rl.terminal": { argTypes: [F64, F64, BOOL, DYN, BOOL], result: VOID }, - "rl.line": { argTypes: [F64], result: STRING }, - "rl.isTerminal": { argTypes: [F64], result: BOOL }, - "rl.isPromises": { argTypes: [F64], result: BOOL }, - /** createInterface's input or output as a checked-dynamic value: - * [value, output] — the stream; refused when it is not one. */ - "rl.inputFromDyn": { argTypes: [DYN, BOOL], result: { kind: "object", className: "%Readable" } }, - "rl.outputFromDyn": { - argTypes: [DYN, BOOL], - result: { kind: "object", className: "%Writable" }, - }, - "rl.cursor": { argTypes: [F64], result: F64 }, - /** readline.cursorTo / moveCursor / clearLine / clearScreenDown: rl.seq - * [kind (0-3), the first and second numbers, whether the second is - * given] answers the data to write after the argument checks (may - * throw); rl.moves [dx, dy] says whether moveCursor writes. */ - "rl.seq": { argTypes: [F64, F64, F64, BOOL], result: STRING }, - "rl.moves": { argTypes: [F64, F64], result: BOOL }, - /** getCursorPos() (scr_readline_tty.c, linked through the regex unit - * like rl.terminal): [handle, 0 rows | 1 cols] */ - "rl.cursorPos": { argTypes: [F64, F64], result: F64 }, - /** readline.emitKeypressEvents (scr_readline_tty.c): rl.keypressStdin - * [interface (0: none), escapeCodeTimeout (NaN: the interface's or - * 500)], rl.keypressStream [stream, interface, timeout]. */ - "rl.keypressStdin": { argTypes: [F64, F64], result: VOID }, - "rl.keypressStream": { argTypes: [null, F64, F64], result: VOID }, - /** readline/promises Readline over an f64 handle: rl.uiOpen [fd (1 - * stdout, 2 stderr), autoCommit], rl.uiOpenStream [stream, autoCommit] - * (isWritable, may throw); the actions take [handle, ...numbers] (the - * checks may throw; cursorTo: [handle, x, whether y is given, y]); - * rl.uiCommit answers a promise. */ - "rl.uiOpen": { argTypes: [F64, BOOL], result: F64 }, - "rl.uiOpenStream": { argTypes: [null, BOOL], result: F64 }, - "rl.uiCursorTo": { argTypes: [F64, F64, BOOL, F64], result: VOID }, - "rl.uiMoveCursor": { argTypes: [F64, F64, F64], result: VOID }, - "rl.uiClearLine": { argTypes: [F64, F64], result: VOID }, - "rl.uiClearScreenDown": { argTypes: [F64], result: VOID }, - "rl.uiRollback": { argTypes: [F64], result: VOID }, - "rl.uiCommit": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - /** node:timers/promises — the promisified pair (scr_async.c, beside - * the timer heap they ride): tp.setTimeout: [ms] → a pending void - * promise a one-shot heap timer fulfills (the loop's timer phase, FIFO - * against equal deadlines like Node); tp.setImmediate: [] → the same - * through the immediate queue (fires before due timers of later loop - * turns, Node's check phase). Neither throws. */ - "tp.setTimeout": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - // scheduler.wait(delay): tp.setTimeout's promise. It throws Node's - // ERR_INVALID_THIS once mock.timers has restored the real scheduler.wait. - "tp.schedulerWait": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, - "tp.setImmediate": { argTypes: [], result: { kind: "promise", inner: VOID } }, - /** events.once (scr_events_emitter.c): [emitter, name, boxer, - * errorBoxer, listenForError] → a pending promise of any[]; the boxers - * turn the event's and the 'error' tuple into that array. */ - "events.once": { argTypes: [null, STRING, null, null, BOOL], result: VOID }, - /** events.once(emitter, name, { signal }) (scr_events_once_signal.c): - * events.once's arguments plus the checked-dynamic signal (an - * AbortSignal or undefined). */ - "events.onceSignal": { argTypes: [null, STRING, null, null, BOOL, DYN], result: VOID }, - /** The resolve-value forms: [ms?, value, ref] → a pending promise of the - * value's type that copies the borrowed already-resolved `value` promise - * when the timer (or immediate) fires; ref false unrefs it. The result - * type follows the value promise. */ - "tp.setTimeoutValue": { argTypes: [F64, null, BOOL], result: VOID }, - "tp.setImmediateValue": { argTypes: [null, BOOL], result: VOID }, - /** The same with options.signal (a checked-dynamic AbortSignal or - * undefined) last: an abort clears the timer and rejects with an - * AbortError (scr_event_target.c). */ - "tp.setTimeoutSignal": { argTypes: [F64, null, BOOL, DYN], result: VOID }, - "tp.setImmediateSignal": { argTypes: [null, BOOL, DYN], result: VOID }, - /** scheduler.wait(delay, options): tp.setTimeoutValue's and - * tp.setTimeoutSignal's shapes, with tp.schedulerWait's throws. */ - "tp.schedulerWaitValue": { argTypes: [F64, null, BOOL], result: VOID }, - "tp.schedulerWaitSignal": { argTypes: [F64, null, BOOL, DYN], result: VOID }, - /** node:diagnostics_channel (scr_dc.c, linked when any dc.* appears — - * the zlib gating precedent): a process-global name→channel registry; - * channel values are f64 handles (type-mapper.ts maps Channel to F64, the - * readline.Interface pattern). Subscribers are dyn function values — - * identity-compared by unsubscribe, called (message, name) by publish - * over a SNAPSHOT of the list (a subscriber unsubscribing itself - * mid-publish still lets its siblings fire, Node's behavior). dc.publish - * MAY THROW: a subscriber's throw propagates out of publish (catchable - * there) where Node routes it to triggerUncaughtException — the - * documented divergence. subscribe/unsubscribe throw Node's - * ERR_INVALID_ARG_TYPE TypeError for non-function subscribers. */ - "dc.channel": { argTypes: [STRING], result: F64 }, - "dc.subscribe": { argTypes: [STRING, DYN], result: VOID }, - "dc.unsubscribe": { argTypes: [STRING, DYN], result: BOOL }, - "dc.hasSubscribers": { argTypes: [STRING], result: BOOL }, - "dc.publish": { argTypes: [F64, DYN], result: VOID }, - "dc.chanSubscribe": { argTypes: [F64, DYN], result: VOID }, - "dc.chanUnsubscribe": { argTypes: [F64, DYN], result: BOOL }, - "dc.chanHasSubscribers": { argTypes: [F64], result: BOOL }, - "dc.chanName": { argTypes: [F64], result: STRING }, - /** TracingChannel (dc.tracingChannel): a registry entry of the five - * event channels, an f64 handle like Channel (type-mapper.ts). tcSubscribe/ - * tcUnsubscribe walk a dyn handlers object's five event keys (truthy - * non-function slots throw the per-channel ERR_INVALID_ARG_TYPE); - * tcTraceSync/tcTraceCallback/tcTracePromise run Node's publish choreography in C over - * dyn values (fn, ctx, thisArg, args-array) with thisArg bound as the - * ambient receiver — the traced call's throw and any subscriber throw - * both propagate (MAY THROW). tcTraceCallback wraps args[position] in a - * native error/result + asyncStart/asyncEnd publisher and throws Node's - * TypeError when that slot is not callable. tracingChannelOf is the - * five-Channel collection form of the constructor. */ - /** setImmediate as a first-class dyn value (scr_async.c): a minted dyn - * callable scheduling args[0](args[1..]) on the immediate queue — the - * Node-suite traceCallback shape (`traceCallback(setImmediate, ...)`). - * Calling it validates the callback (the dyn call machinery); minting - * never throws. */ - "timers.setImmediateFnValue": { argTypes: [], result: DYN }, - /** `new Promise(setImmediate)` (the Node-suite early-exit shape): a - * fresh promise an immediate fulfills with the undefined dyn value — - * the executor IS setImmediate, so resolve rides the immediate queue - * (scr_async.c). Never throws. */ - "timers.immediatePromise": { argTypes: [], result: { kind: "promise", inner: DYN } }, - "dc.tracingChannel": { argTypes: [STRING], result: F64 }, - "dc.tracingChannelOf": { argTypes: [F64, F64, F64, F64, F64], result: F64 }, - "dc.tcChannel": { argTypes: [F64, F64], result: F64 }, - "dc.tcHasSubscribers": { argTypes: [F64], result: BOOL }, - "dc.tcSubscribe": { argTypes: [F64, DYN], result: VOID }, - "dc.tcUnsubscribe": { argTypes: [F64, DYN], result: BOOL }, - "dc.tcTraceSync": { argTypes: [F64, DYN, DYN, DYN, DYN], result: DYN }, - "dc.tcTraceCallback": { argTypes: [F64, DYN, F64, DYN, DYN, DYN], result: DYN }, - /** tracePromise (scr_dc.c): start publish, the traced call, a wrap of - * non-promise results, the end publish, and a REACTION FIBER that - * awaits the traced promise and publishes asyncStart/asyncEnd (error - * first on rejection) before settling the returned promise with - * the passed-through outcome. MAY THROW (the traced call and the - * synchronous publishes). */ - "dc.tcTracePromise": { - argTypes: [F64, DYN, DYN, DYN, DYN], - result: { kind: "promise", inner: DYN }, - }, - /** process.on/once('unhandledRejection', fn): registers a dyn listener - * the checkpoint report dispatches per never-observed rejection — - * (reason, promise) — instead of printing and exiting 1 (scr_async.c). - * The bool second arg is `once` (auto-removed after one delivery, - * Node's once); off/removeListener remove by closure identity, the - * offWarning stance. Throws Node's ERR_INVALID_ARG_TYPE on a - * non-function. */ - /** Uncaught exception handlers and monitors: [callback dyn, once, monitor]. */ - "process.onUncaughtException": { argTypes: [DYN, BOOL, BOOL], result: VOID }, - "process.offUncaughtException": { argTypes: [DYN, BOOL], result: VOID }, - "process.onUnhandledRejection": { argTypes: [DYN, BOOL], result: VOID }, - "process.offUnhandledRejection": { argTypes: [DYN], result: VOID }, - /** process.on/once/off('rejectionHandled', fn): the sibling registry. - * A handler attached after unhandledRejection delivery fires the event - * once. Dispatch is synchronous at the attach, with the promise, - * Node's payload. */ - "process.onRejectionHandled": { argTypes: [DYN, BOOL], result: VOID }, - "process.offRejectionHandled": { argTypes: [DYN], result: VOID }, - /** process warnings (scr_async_dyn.c): onWarning/offWarning register - * dyn listeners; emitWarning applies Node's full argument grammar over - * the call's dyn argument vector (ERR_INVALID_ARG_TYPE TypeErrors — MAY - * THROW) and emits on the next tick, like Node: Node's stderr report, - * then the listeners. */ - "process.onWarning": { argTypes: [DYN], result: VOID }, - "process.offWarning": { argTypes: [DYN], result: VOID }, - "process.emitWarning": { argTypes: [DYN], result: VOID }, - /** A Warning-typed process warning with this message (the console - * members' warnings), emitted on the next tick as Node's emitWarning - * does (so the loop runs); an empty message emits nothing. */ - "process.emitWarningText": { argTypes: [STRING], result: VOID }, - /** process.noDeprecation (0) / process.throwDeprecation (1): the stored - * value (undefined until assigned) and its assignment. util.deprecate's - * warnings and DeprecationWarning emissions read their truthiness. */ - "process.deprecationFlag": { argTypes: [F64], result: DYN }, - "process.setDeprecationFlag": { argTypes: [F64, DYN], result: VOID }, - "process.setEmitWarning": { argTypes: [DYN], result: VOID }, - /** AsyncLocalStorage (node:async_hooks — scr_async.c): stores are f64 - * handles; contexts are immutable fiber-carried snapshots (spawned - * fibers inherit the spawner's — Node's init-time capture). run/exit - * enter (or clear) the store, call the dyn function with forwarded - * arguments, and restore (the finally); getStore answers the current - * dyn value or undefined; enterWith installs with no restore point. - * run/exit MAY THROW (the callback's own throws propagate). */ - /** `await v` over a checked-dynamic VALUE (scr_async.c): a dyn promise - * adopts (rejections re-throw — MAY THROW), anything else takes JS's - * one-microtask non-thenable await and answers itself. Only emitted - * inside async bodies (the frontend's isAsync gate). */ - "async.awaitDyn": { argTypes: [DYN], result: DYN }, - /** The bare one-microtask hop (scr_await_hop): `await v` over a typed - * NON-promise value — JS awaits non-thenables through exactly one - * microtask turn and yields the value itself. Never throws. */ - "async.hop": { argTypes: [], result: VOID }, - "als.new": { argTypes: [], result: F64 }, - "als.get": { argTypes: [F64], result: DYN }, - "als.run": { argTypes: [F64, DYN, DYN, DYN], result: DYN }, - "als.exitRun": { argTypes: [F64, DYN, DYN], result: DYN }, - "als.enterWith": { argTypes: [F64, DYN], result: VOID }, - "als.disable": { argTypes: [F64], result: VOID }, - /** new AsyncLocalStorage(options) (Node's object check throws; MAY - * THROW) and the store's name option (dyn; "" without one). */ - "als.newOptions": { argTypes: [DYN], result: F64 }, - "als.name": { argTypes: [F64], result: DYN }, - /** A function bound to the active context: the completion callbacks of - * callback-style APIs (always false: an empty context leaves the - * callback unbound) and AsyncLocalStorage.bind (always true). The typed - * form answers its closure argument's type; the dyn form throws Node's - * ERR_INVALID_ARG_TYPE for a non-function when always (MAY THROW). */ - "als.bindCallback": { argTypes: [null, BOOL], result: VOID }, - "als.bindCallbackDyn": { argTypes: [DYN, BOOL], result: DYN }, - /** AsyncLocalStorage.snapshot(): a dyn function (cb, ...args) that runs - * cb in the context active at the snapshot. */ - "als.snapshot": { argTypes: [], result: DYN }, - /** Channel.bindStore/unbindStore/runStores (scr_dc.c): the - * AsyncLocalStorage integration — set-semantics bindings per store, - * runStores entering every bound store with transform(data) around the - * publish and the callback (MAY THROW: transforms, subscribers, and - * the callback all run). */ - "dc.chanBindStore": { argTypes: [F64, F64, DYN], result: VOID }, - "dc.chanUnbindStore": { argTypes: [F64, F64], result: BOOL }, - "dc.chanRunStores": { argTypes: [F64, DYN, DYN, DYN, DYN], result: DYN }, - /** The Number statics with a static C implementation (scr_lib.c): one - * f64 arg → bool, JS-exact BY CONSTRUCTION — Number.isFinite/isNaN/ - * isInteger/isSafeInteger never coerce, and the frontend routes only - * f64-typed arguments here (other static types fence). None throws. */ - "number.isFinite": { argTypes: [F64], result: BOOL }, - "number.isNaN": { argTypes: [F64], result: BOOL }, - "number.isInteger": { argTypes: [F64], result: BOOL }, - "number.isSafeInteger": { argTypes: [F64], result: BOOL }, - /** Date (scr_lib.c). Values are TimeClip'd epoch-millisecond scalars: - * construction/store/pass/getters are exact while identity and mutation - * remain fenced. date.now is Node's integer milliseconds since epoch; - * date.toISOString formats one raw f64 millisecond time value and - * date.toISOStringValue formats a stored Date, both with Node's rules (UTC, - * YYYY-MM-DDTHH:mm:ss.sssZ, expanded ±YYYYYY years outside 0–9999, - * ToInteger truncation of fractional ms) and THROWS Node's "Invalid - * time value" RangeError on NaN / out-of-range input (may-throw seed). - * Results: f64 / owned (+1) string. */ - "date.now": { argTypes: [], result: F64 }, - "date.newNow": { argTypes: [], result: DATE_T }, - "date.newMs": { argTypes: [F64], result: DATE_T }, - "date.newString": { argTypes: [STRING], result: DATE_T }, - /** Date.parse(dateString), with its own reach witness for exact fences. */ - "date.parse": { argTypes: [STRING], result: F64 }, - "date.getTime": { argTypes: [DATE_T], result: F64 }, - "date.valueOf": { argTypes: [DATE_T], result: F64 }, - "date.toISOString": { argTypes: [F64], result: STRING }, - "date.toISOStringValue": { argTypes: [DATE_T], result: STRING }, - /** Date.prototype.toUTCString over a stored Date ("Invalid Date" for - * NaN; never throws). */ - "date.toUTCString": { argTypes: [DATE_T], result: STRING }, - "date.getFullYear": { argTypes: [DATE_T], result: F64 }, - "date.getUTCFullYear": { argTypes: [DATE_T], result: F64 }, - "date.getMonth": { argTypes: [DATE_T], result: F64 }, - "date.getUTCMonth": { argTypes: [DATE_T], result: F64 }, - "date.getDate": { argTypes: [DATE_T], result: F64 }, - "date.getUTCDate": { argTypes: [DATE_T], result: F64 }, - "date.getDay": { argTypes: [DATE_T], result: F64 }, - "date.getUTCDay": { argTypes: [DATE_T], result: F64 }, - "date.getHours": { argTypes: [DATE_T], result: F64 }, - "date.getUTCHours": { argTypes: [DATE_T], result: F64 }, - "date.getMinutes": { argTypes: [DATE_T], result: F64 }, - "date.getUTCMinutes": { argTypes: [DATE_T], result: F64 }, - "date.getSeconds": { argTypes: [DATE_T], result: F64 }, - "date.getUTCSeconds": { argTypes: [DATE_T], result: F64 }, - "date.getMilliseconds": { argTypes: [DATE_T], result: F64 }, - "date.getUTCMilliseconds": { argTypes: [DATE_T], result: F64 }, - "date.getTimezoneOffset": { argTypes: [DATE_T], result: F64 }, - /** A Date object for a time value a local holds (date-values.ts): a - * lib-call argument passes the time value itself, and any other use - * creates the Date. */ - "date.fromTime": { argTypes: [F64], result: DATE_T }, - /** Date.prototype's field setters over a time value - * (scr_date_set_fields): time value, setter code, argument count and four - * arguments; answers the new time value. */ - "date.setFields": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, - /** TimeClip of a number (setTime's argument). */ - "date.timeClip": { argTypes: [F64], result: F64 }, - /** Stores a time value into a Date object and answers it (a setter's - * write). */ - "date.store": { argTypes: [DATE_T, F64], result: F64 }, - /** Date.prototype.toString / toDateString / toTimeString and String(date): - * V8's local-time formats with the host zone's long name, "Invalid Date" - * for an invalid date. Owned (+1) string; never throws. */ - "date.toString": { argTypes: [DATE_T], result: STRING }, - "date.toDateString": { argTypes: [DATE_T], result: STRING }, - "date.toTimeString": { argTypes: [DATE_T], result: STRING }, - /** The composed `new Date(dateString).getTime()` read: one borrowed - * string, f64 milliseconds since epoch, with V8's date-string grammar - * (the ES5 format, then the legacy forms: RFC 1123/2822, Date#toString, - * "Jan 2, 2024", ...). Zone-less dates are local time; anything V8 - * rejects is NaN, Node's invalid-date getTime. Never throws. */ - "date.parseGetTime": { argTypes: [STRING], result: F64 }, - /** `Date.UTC(...)` — seven f64 arguments (the frontend completes the - * spec's defaults for omitted trailing parts: month 0, date 1, time - * parts 0), the spec's MakeDay/MakeTime/TimeClip exactly: 0–99 years - * map to 1900+year, out-of-range months/dates roll over, non-finite - * parts and out-of-range results answer NaN. Never throws. */ - "date.utc": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, - /** `new Date(year, monthIndex[, date, hours, minutes, seconds, ms])` — - * the same seven-f64 completion as date.utc, but the fields are LOCAL - * time: V8's MakeDay/MakeTime, then UTC(t) in the host zone (DST gaps - * read with the offset before the transition, overlaps answer the - * earlier instant), then TimeClip. Never throws. */ - "date.newLocal": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: DATE_T }, - /** WHATWG TextDecoder.decode over u8 bytes (scr_bytes.c): utf-8 with - * default options — the same maximal-subpart replacement decode as - * Buffer.toString("utf8"), with the leading BOM stripped (the one - * behavioral difference; ignoreBOM defaults to false). Inline calls - * lower directly; stored decoder records dispatch by encoding id. - * TextEncoder.encode needs no libFn: its matching forms - * lower to buffer.fromStr(s, "utf8") (identical bytes — both write each - * lone surrogate as U+FFFD). Borrowed arg; owned (+1) string; never throws. */ - "text.decode": { argTypes: [BYTES_U8], result: STRING }, - "bytes.bufferSource": { argTypes: [DYN], result: BYTES_U8 }, - /** Whether an error-first callback's Buffer result is absent: the zlib - * and crypto callback adapters pass NULL after an error. Borrowed arg; - * never throws. */ - "bytes.isAbsent": { argTypes: [BYTES_U8], result: BOOL }, - /** Whether two typed arrays share no byte: their element ranges in - * memory do not overlap. Borrowed args of any element kind; never throws. */ - "bytes.disjoint": { argTypes: [null, null], result: BOOL }, - /** The absent Buffer result itself (NULL), where an error-path callback - * body passes its undefined result on into a Buffer slot. Owned (+1); - * never throws. */ - "bytes.absent": { argTypes: [], result: BYTES_U8 }, - /** node:crypto asymmetric keys and signatures (scr_pki.c): checked- - * dynamic arguments, borrowed; KeyObject, Sign and Verify handles, - * Buffers and booleans as checked-dynamic results (+1). Node's argument - * and decoder errors throw (may-throw); generateKeyPairCb's callback - * receives (null, publicKey, privateKey) in a later macrotask. */ - "crypto.createPrivateKey": { argTypes: [DYN], result: DYN }, - "crypto.createPublicKey": { argTypes: [DYN], result: DYN }, - "crypto.generateKeyPairSync": { argTypes: [DYN, DYN], result: DYN }, - "crypto.generateKeyPairCb": { argTypes: [DYN, DYN, DYN], result: VOID }, - "crypto.signOneshot": { argTypes: [DYN, DYN, DYN], result: DYN }, - "crypto.diffieHellman": { argTypes: [DYN, DYN], result: DYN }, - "crypto.verifyOneshot": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, - "crypto.createSign": { argTypes: [DYN], result: DYN }, - "crypto.createVerify": { argTypes: [DYN], result: DYN }, - /** The Buffer brand of a Uint8Array source onto a fresh Uint8Array - * result (map and filter of a Buffer return a Buffer). */ - "bytes.copyBrand": { argTypes: [BYTES_U8, BYTES_U8], result: VOID }, - "text.decodeBufferSource": { argTypes: [DYN], result: STRING }, - "text.decodeOptions": { argTypes: [BYTES_U8, BOOL, BOOL], result: STRING }, - "text.decodeStream": { argTypes: [DYN, BYTES_U8, F64, BOOL, BOOL, BOOL], result: STRING }, - "text.decodeLegacyOptions": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: STRING }, - /** TextDecoder.decode for a compile-time non-UTF-8 WHATWG label. The - * second f64 is the frontend-owned encoding id consumed by scr_bytes.c; - * label aliases/case/ASCII whitespace are canonicalized before IR. The - * mapping tables are generated from the pinned Node 24 oracle, keeping - * output portable without an ICU/iconv dependency. Borrowed args; owned - * (+1) string; never throws. */ - "text.decodeLegacy": { argTypes: [BYTES_U8, F64], result: STRING }, - "text.decoderEncoding": { argTypes: [DYN], result: F64 }, - "text.decoderName": { argTypes: [F64], result: STRING }, - "text.encodeInto": { argTypes: [DYN, DYN], result: DYN }, - /** `value instanceof TextDecoder` (true) or `TextEncoder` (false) on a - * checked-dynamic value: a codec record's handle box (scr_text_codec_dyn.c). - * Borrowed; never throws. */ - "text.codecIs": { argTypes: [DYN, BOOL], result: BOOL }, - "buffer.isAscii": { argTypes: [DYN], result: BOOL }, - "buffer.isUtf8": { argTypes: [DYN], result: BOOL }, - "buffer.transcode": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, - /** The fs option forms and friends (scr_lib.c), all throwing catchably - * like the rest of sync fs. mkdirRecursiveSync is Node's recursive - * algorithm (try mkdir, EEXIST-dir is fine, ENOENT creates the parent - * first — errors report Node's errno at Node's path, EEXIST at a file - * target, ENOTDIR at the full path past a file); the first-created-dir - * return value has no lowering (statement position only, frontend- - * fenced). rmOptsSync is rmSync with (recursive, force) bools: force - * swallows ENOENT, recursive removes trees post-order, a directory - * without recursive throws Node's ERR_FS_EISDIR SystemError. mkdtempSync appends the six X's and returns the - * created path (+1). accessSync takes the F_OK/R_OK/W_OK/X_OK bits as - * one f64 (the frontend bakes fs.constants.* as literals and completes - * an omitted mode to 0). readFdSync/readFdSyncBytes are the - * readFileSync(fd[, "utf8"]) forms — a read(2) loop to EOF on the fd - * (the stdin path); errors carry Node's no-path message shape. */ - "fs.mkdirRecursiveSync": { argTypes: [STRING], result: VOID }, - "fs.rmOptsSync": { argTypes: [STRING, BOOL, BOOL], result: VOID }, - /** rmOptsSync's maxRetries/retryDelay form (path, recursive, force, - * maxRetries, retryDelay): Node's linear-backoff retry on - * EBUSY/EMFILE/ENFILE/ENOTEMPTY/EPERM — the tmpdir-harness shape - * rmSync(p, { maxRetries: 3, recursive: true, force: true }). Throws - * ERR_OUT_OF_RANGE for a retryDelay outside int32 >= 0 or a maxRetries - * outside uint32, before checking the path. */ - "fs.rmRetrySync": { argTypes: [STRING, BOOL, BOOL, F64, F64], result: VOID }, - "fs.mkdtempSync": { argTypes: [STRING], result: STRING }, - "fs.accessSync": { argTypes: [STRING, F64], result: VOID }, - "fs.readFdSync": { argTypes: [F64, STRING], result: STRING }, - "fs.readFdSyncBytes": { argTypes: [F64], result: BYTES_U8 }, - /** Validated isatty(3) probe. Stream property lowering maps false to - * undefined; tty.isatty exposes the boolean result directly. */ - "process.isTTY": { argTypes: [F64], result: BOOL }, - // Like process.envGet: the result is the module's interned - // `number | undefined` union — checked by arms in the libCall case. - /** Terminal width over an fd literal (1/2 — process.stdout/stderr - * .columns): ioctl(TIOCGWINSZ). The result is the module's interned - * `number | undefined` union — a non-TTY stream (or an ioctl refusal) - * yields the undefined arm, exactly Node's missing `.columns`. Never - * throws. */ - "process.columns": { argTypes: [F64], result: VOID }, - /** Terminal height, with the same fd and optional-number contract. */ - "process.rows": { argTypes: [F64], result: VOID }, - /** process.stdin.destroy(): a deliberate no-op (no stream machinery - * exists to tear down; documented in SEMANTICS.md). */ - "process.stdinDestroy": { argTypes: [], result: VOID }, - /** process.stdin.setRawMode(mode) — one borrowed bool arg, void. On a - * TTY stdin: termios raw mode on/off (libuv's UV_TTY_MODE_RAW flag set, - * the mode Node's setRawMode(true) applies; false restores the entry - * state). On a NON-TTY stdin Node's process.stdin has no such method at - * all, so the call throws Node's exact catchable TypeError - * ("process.stdin.setRawMode is not a function") — the may-throw seed - * carries it. */ - "process.stdinSetRawMode": { argTypes: [BOOL], result: VOID }, - // Arg 0 is a packed f64[] OR a bytes value (the spread-typed-array - // form) — checked in the libCall case. - /** fromCharCode takes one packed f64[] or bytes arg and builds a string - * from UTF-16 code units. Adjacent surrogate pairs combine; lone - * surrogates follow the runtime's replacement policy. */ - "string.fromCharCode": { argTypes: [null], result: STRING }, - /** Numeric code points, with catchable RangeError for non-integers or - * values outside 0..0x10ffff. Uses the same UTF-8 surrogate policy. */ - "string.fromCodePoint": { argTypes: [null], result: STRING }, - /** lastIndexOf returns the last UTF-16 start index, or -1. The two-arg - * form searches at or before its numeric position; NaN starts at the end. - * String arguments are borrowed and neither form throws. */ - "string.lastIndexOf": { argTypes: [STRING, STRING], result: F64 }, - "string.lastIndexOfFrom": { argTypes: [STRING, STRING, F64], result: F64 }, - /** String.raw(template, ...subs): the raw literals array (a string[] - * read off the template record) interleaved with the PRE-STRINGIFIED - * substitutions (the frontend applies the static ToString and packs - * them into one string[] literal) — extra substitutions drop, missing - * ones skip, the spec's loop exactly (scr_array.c). Borrowed args; - * +1 string; never throws. */ - "string.raw": { argTypes: [arrayOf(STRING), arrayOf(STRING)], result: STRING }, -}); + "insp.f64": { argTypes: [F64], result: STRING }, + /** util.format %j over a checked-dynamic argument: the runtime dyn + * walk (JS-exact stringify; root undefined/function prints + * "undefined"; a handle in the tree throws the "not supported yet" + * Error). */ + "insp.jsonDyn": { argTypes: [DYN], result: STRING }, + "insp.str": { argTypes: [STRING], result: STRING }, + "insp.regex": { argTypes: [REGEX], result: STRING }, + /** A Date's inspect text: toISOString(), or "Invalid Date". */ + "insp.date": { argTypes: [DATE_T], result: STRING }, + "insp.buffer": { argTypes: [BYTES_U8], result: STRING }, + /** A numeric typed array that is not a Buffer (any element kind). */ + "insp.typedArray": { argTypes: [null, F64, F64], result: STRING }, + /** A u8 byte view in a union arm, element or field: a DataView, Buffer + * or Uint8Array by its own flags. */ + "insp.bytes": { argTypes: [null, F64, F64], result: STRING }, + "insp.error": { argTypes: [null, F64, F64], result: STRING }, + "insp.errorHasCause": { argTypes: [null], result: BOOL }, + "insp.errorBagHasProperties": { argTypes: [DYN], result: BOOL }, + "insp.errorCause": { argTypes: [null, F64, F64], result: STRING }, + "insp.dyn": { argTypes: [DYN, F64, F64], result: STRING }, + "insp.dynS": { argTypes: [DYN, F64], result: STRING }, + /** util.format's %o over a checked-dynamic value: inspect with + * showHidden. Kinds whose hidden entries have no rendering throw the + * 'not supported yet' Error. */ + "insp.dynHidden": { argTypes: [DYN, F64], result: STRING }, + "insp.begin": { argTypes: [F64], result: VOID }, + /** util.inspect's breakLength, compact (0 for false) and sorted for the + * renders that follow; the call site restores 80, 3 and false afterwards. */ + "insp.layout": { argTypes: [F64, F64, BOOL], result: VOID }, + "insp.entry": { argTypes: [STRING, BOOL], result: VOID }, + // Circular references: the receiver slot is program-dependent (any + // cycle-capable record/array/map/class type — the insp.error precedent). + /* Circular references over cycle-capable composites (recursive record/ + * class types): circCheck answers a value's circular id when it is + * already on the traversal stack (0 otherwise), seenPush/refWrap + * bracket the frame (insp.end adds Node's " " prefix to values + * the walk found circular), circular renders "[Circular *N]". */ + "insp.circCheck": { argTypes: [null], result: F64 }, + "insp.seenPush": { argTypes: [null], result: VOID }, + "insp.refWrap": { argTypes: [null, STRING], result: STRING }, + "insp.circular": { argTypes: [F64], result: STRING }, + /* Whether a closed declared-field record kept the undeclared properties + * of its checked source (scr_record_source_note): reflective surfaces + * (inspect, Object.keys/values/entries) then answer through its live + * checked view. The receiver is any such record type. Never throws. */ + "record.sourceHas": { argTypes: [null], result: BOOL }, + /** `record.sourceSpread(built, value)`: the record a literal starting + * with `...value` just built keeps the order of value's kept source + * (key-order-witness.ts ordered spreads). Both are record types. Never + * throws. */ + "record.sourceSpread": { argTypes: [null, null], result: VOID }, + "insp.key": { argTypes: [STRING], result: STRING }, + "insp.moreItems": { argTypes: [F64], result: STRING }, + "insp.end": { argTypes: [STRING, STRING, STRING, F64, BOOL, BOOL], result: STRING }, + /** A nested inspect inside a custom rendering (PerformanceEntry's + * `${name} ${inspect(toJSON)}`): nestedBegin starts a fresh inspect + * context, nestedEnd(prefix, inner) restores the caller's and answers + * `prefix inner` re-indented at the caller's level (+1). */ + "insp.nestedBegin": { argTypes: [], result: VOID }, + "insp.nestedEnd": { argTypes: [STRING, STRING], result: STRING }, + /** Around a top-level render that an inspection refusal can end: mark + * records the inspector's frame and nesting depth, unwind (in the + * render's finally) closes what the render left open above it. */ + "insp.mark": { argTypes: [], result: VOID }, + "insp.unwind": { argTypes: [], result: VOID }, + /** node:string_decoder's utf8 StringDecoder (scr_string.c): the decoder + * value is a one-field record whose f64 PACKS the pending partial + * sequence (count + up to 3 raw bytes — Node buffers at most 3 for + * every encoding); the frontend's interned %strdec helpers thread it, + * with the decoder's CANONICAL encoding name first, through these pure + * functions. write: (enc, pending, chunk) → the decoded complete + * prefix of pending+chunk (+1); next: (enc, pending, chunk) → the + * packed NEW pending; end: (enc, pending) → the buffered partial's + * flush (+1). Node-exact per encoding (oracle-pinned); none throws. */ + "strdec.write": { argTypes: [STRING, F64, BYTES_U8], result: STRING }, + "strdec.next": { argTypes: [STRING, F64, BYTES_U8], result: F64 }, + "strdec.end": { argTypes: [STRING, F64], result: STRING }, + /** A runtime encoding argument's canonical name (Node's normalizeEncoding: + * nullish and "" are utf8); an unknown name throws ERR_UNKNOWN_ENCODING. */ + "strdec.encoding": { argTypes: [DYN], result: STRING }, + // node:readline: the interface handle is f64; the callbacks' func types + // are program-dependent (the emitter picks the adapter by parameter + // count), so those slots are null like child.onExit's. + /** node:readline and node:readline/promises (scr_readline.c, linked + * under the events gate — these fns imply moduleUsesProcessEvents; a + * stream input or output adds scr_stream.c's bridge). The interface + * value is an f64 handle (the Timeout-id precedent); an open interface + * on stdin keeps the loop alive until close/EOF. + * rl.create: [mode, input?, output?, crlfDelay, prompt?] → f64, mode + * bits 1 stream input, 2 stream output, 4 stdout output, 16 prompt + * (lowerReadlineCreateCall); rl.createSignal: the same plus the + * trailing AbortSignal (mode bit 8), which links the event-target unit + * for addAbortListener. rl.question: [handle, query, cb] — the callback + * form; THROWS ERR_USE_AFTER_CLOSE on a closed interface (may-throw). + * rl.questionAsync(Signal): [handle, query, signal?] → promise, + * rejecting instead of throwing. rl.close/pause/resume/prompt: + * [handle] (pause/resume/prompt throw ERR_USE_AFTER_CLOSE when + * closed); rl.setPrompt/getPrompt; rl.write: [handle, data] (throws + * when closed). rl.on: [handle, event, cb, once] — the event number + * (0 'line', 1 'close', 2 'pause', 3 'resume', 4 'error') and the + * listener (moves). rl.iterOpen/iterNext/iterReturn: the async + * iterator — iterNext answers promise of the line or undefined. */ + "rl.create": { argTypes: [F64], result: F64 }, + "rl.createSignal": { argTypes: [F64], result: F64 }, + "rl.question": { argTypes: [F64, STRING, null], result: VOID }, + "rl.questionAsync": { argTypes: [F64, STRING], result: { kind: "promise", inner: STRING } }, + "rl.questionAsyncSignal": { + argTypes: [F64, STRING, DYN], + result: { kind: "promise", inner: STRING }, + }, + /** The question forms with a checked-dynamic query (a JavaScript value + * that need not be a string). */ + "rl.questionDyn": { argTypes: [F64, DYN, null], result: VOID }, + "rl.questionAsyncDyn": { argTypes: [F64, DYN], result: { kind: "promise", inner: STRING } }, + "rl.questionAsyncSignalDyn": { + argTypes: [F64, DYN, DYN], + result: { kind: "promise", inner: STRING }, + }, + "rl.close": { argTypes: [F64], result: VOID }, + "rl.pause": { argTypes: [F64], result: VOID }, + "rl.resume": { argTypes: [F64], result: VOID }, + "rl.prompt": { argTypes: [F64], result: VOID }, + /** prompt(preserveCursor): [handle, preserveCursor] */ + "rl.promptWith": { argTypes: [F64, BOOL], result: VOID }, + "rl.setPrompt": { argTypes: [F64, STRING], result: VOID }, + "rl.setPromptDyn": { argTypes: [F64, DYN], result: VOID }, + "rl.getPrompt": { argTypes: [F64], result: STRING }, + "rl.write": { argTypes: [F64, STRING], result: VOID }, + /** write(data, key): [handle, hasData, data, hasKey, hasName, name, + * ctrl, meta, shift] */ + "rl.writeKey": { + argTypes: [F64, BOOL, STRING, BOOL, BOOL, STRING, BOOL, BOOL, BOOL], + result: VOID, + }, + "rl.on": { argTypes: [F64, F64, null, BOOL], result: VOID }, + "rl.iterOpen": { argTypes: [F64], result: VOID }, + "rl.iterNext": { argTypes: [F64], result: { kind: "promise", inner: DYN } }, + "rl.iterReturn": { argTypes: [F64], result: VOID }, + /** createInterface's history and editing options (scr_rl_check_options: + * [flags, tabSize, escapeCodeTimeout, historySize], may throw; + * rl.setOptions: [handle, flags, historySize, removeHistoryDuplicates, + * tabSize, escapeCodeTimeout]; rl.setHistory: [handle, history]) and + * the member reads (rl.history answers the array, rl.numProp the + * numeric member by number). */ + "rl.checkOptions": { argTypes: [F64, F64, F64, F64], result: VOID }, + "rl.checkCompleter": { argTypes: [DYN], result: VOID }, + "rl.setOptions": { argTypes: [F64, F64, F64, BOOL, F64, F64], result: VOID }, + "rl.setHistory": { argTypes: [F64, arrayOf(STRING)], result: VOID }, + "rl.history": { argTypes: [F64], result: arrayOf(STRING) }, + "rl.numProp": { argTypes: [F64, F64], result: F64 }, + /** Terminal mode (scr_readline_tty.c): rl.terminal: [handle, mode (0 + * the option's value, 2 output.isTTY of process.stdout), terminal, + * completer (undefined when absent), readline/promises], emitted with + * scr_insp_dyn when there is a completer; the line, terminal and cursor + * members. */ + "rl.terminal": { argTypes: [F64, F64, BOOL, DYN, BOOL], result: VOID }, + "rl.line": { argTypes: [F64], result: STRING }, + "rl.isTerminal": { argTypes: [F64], result: BOOL }, + "rl.isPromises": { argTypes: [F64], result: BOOL }, + /** createInterface's input or output as a checked-dynamic value: + * [value, output] — the stream; refused when it is not one. */ + "rl.inputFromDyn": { + argTypes: [DYN, BOOL], + result: { kind: "object", className: "%Readable" }, + }, + "rl.outputFromDyn": { + argTypes: [DYN, BOOL], + result: { kind: "object", className: "%Writable" }, + }, + "rl.cursor": { argTypes: [F64], result: F64 }, + /** readline.cursorTo / moveCursor / clearLine / clearScreenDown: rl.seq + * [kind (0-3), the first and second numbers, whether the second is + * given] answers the data to write after the argument checks (may + * throw); rl.moves [dx, dy] says whether moveCursor writes. */ + "rl.seq": { argTypes: [F64, F64, F64, BOOL], result: STRING }, + "rl.moves": { argTypes: [F64, F64], result: BOOL }, + /** getCursorPos() (scr_readline_tty.c, linked through the regex unit + * like rl.terminal): [handle, 0 rows | 1 cols] */ + "rl.cursorPos": { argTypes: [F64, F64], result: F64 }, + /** readline.emitKeypressEvents (scr_readline_tty.c): rl.keypressStdin + * [interface (0: none), escapeCodeTimeout (NaN: the interface's or + * 500)], rl.keypressStream [stream, interface, timeout]. */ + "rl.keypressStdin": { argTypes: [F64, F64], result: VOID }, + "rl.keypressStream": { argTypes: [null, F64, F64], result: VOID }, + /** readline/promises Readline over an f64 handle: rl.uiOpen [fd (1 + * stdout, 2 stderr), autoCommit], rl.uiOpenStream [stream, autoCommit] + * (isWritable, may throw); the actions take [handle, ...numbers] (the + * checks may throw; cursorTo: [handle, x, whether y is given, y]); + * rl.uiCommit answers a promise. */ + "rl.uiOpen": { argTypes: [F64, BOOL], result: F64 }, + "rl.uiOpenStream": { argTypes: [null, BOOL], result: F64 }, + "rl.uiCursorTo": { argTypes: [F64, F64, BOOL, F64], result: VOID }, + "rl.uiMoveCursor": { argTypes: [F64, F64, F64], result: VOID }, + "rl.uiClearLine": { argTypes: [F64, F64], result: VOID }, + "rl.uiClearScreenDown": { argTypes: [F64], result: VOID }, + "rl.uiRollback": { argTypes: [F64], result: VOID }, + "rl.uiCommit": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + /** node:timers/promises — the promisified pair (scr_async.c, beside + * the timer heap they ride): tp.setTimeout: [ms] → a pending void + * promise a one-shot heap timer fulfills (the loop's timer phase, FIFO + * against equal deadlines like Node); tp.setImmediate: [] → the same + * through the immediate queue (fires before due timers of later loop + * turns, Node's check phase). Neither throws. */ + "tp.setTimeout": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + // scheduler.wait(delay): tp.setTimeout's promise. It throws Node's + // ERR_INVALID_THIS once mock.timers has restored the real scheduler.wait. + "tp.schedulerWait": { argTypes: [F64], result: { kind: "promise", inner: VOID } }, + "tp.setImmediate": { argTypes: [], result: { kind: "promise", inner: VOID } }, + /** events.once (scr_events_emitter.c): [emitter, name, boxer, + * errorBoxer, listenForError] → a pending promise of any[]; the boxers + * turn the event's and the 'error' tuple into that array. */ + "events.once": { argTypes: [null, STRING, null, null, BOOL], result: VOID }, + /** events.once(emitter, name, { signal }) (scr_events_once_signal.c): + * events.once's arguments plus the checked-dynamic signal (an + * AbortSignal or undefined). */ + "events.onceSignal": { argTypes: [null, STRING, null, null, BOOL, DYN], result: VOID }, + /** The resolve-value forms: [ms?, value, ref] → a pending promise of the + * value's type that copies the borrowed already-resolved `value` promise + * when the timer (or immediate) fires; ref false unrefs it. The result + * type follows the value promise. */ + "tp.setTimeoutValue": { argTypes: [F64, null, BOOL], result: VOID }, + "tp.setImmediateValue": { argTypes: [null, BOOL], result: VOID }, + /** The same with options.signal (a checked-dynamic AbortSignal or + * undefined) last: an abort clears the timer and rejects with an + * AbortError (scr_event_target.c). */ + "tp.setTimeoutSignal": { argTypes: [F64, null, BOOL, DYN], result: VOID }, + "tp.setImmediateSignal": { argTypes: [null, BOOL, DYN], result: VOID }, + /** scheduler.wait(delay, options): tp.setTimeoutValue's and + * tp.setTimeoutSignal's shapes, with tp.schedulerWait's throws. */ + "tp.schedulerWaitValue": { argTypes: [F64, null, BOOL], result: VOID }, + "tp.schedulerWaitSignal": { argTypes: [F64, null, BOOL, DYN], result: VOID }, + /** node:diagnostics_channel (scr_dc.c, linked when any dc.* appears — + * the zlib gating precedent): a process-global name→channel registry; + * channel values are f64 handles (type-mapper.ts maps Channel to F64, the + * readline.Interface pattern). Subscribers are dyn function values — + * identity-compared by unsubscribe, called (message, name) by publish + * over a SNAPSHOT of the list (a subscriber unsubscribing itself + * mid-publish still lets its siblings fire, Node's behavior). dc.publish + * MAY THROW: a subscriber's throw propagates out of publish (catchable + * there) where Node routes it to triggerUncaughtException — the + * documented divergence. subscribe/unsubscribe throw Node's + * ERR_INVALID_ARG_TYPE TypeError for non-function subscribers. */ + "dc.channel": { argTypes: [STRING], result: F64 }, + "dc.subscribe": { argTypes: [STRING, DYN], result: VOID }, + "dc.unsubscribe": { argTypes: [STRING, DYN], result: BOOL }, + "dc.hasSubscribers": { argTypes: [STRING], result: BOOL }, + "dc.publish": { argTypes: [F64, DYN], result: VOID }, + "dc.chanSubscribe": { argTypes: [F64, DYN], result: VOID }, + "dc.chanUnsubscribe": { argTypes: [F64, DYN], result: BOOL }, + "dc.chanHasSubscribers": { argTypes: [F64], result: BOOL }, + "dc.chanName": { argTypes: [F64], result: STRING }, + /** TracingChannel (dc.tracingChannel): a registry entry of the five + * event channels, an f64 handle like Channel (type-mapper.ts). tcSubscribe/ + * tcUnsubscribe walk a dyn handlers object's five event keys (truthy + * non-function slots throw the per-channel ERR_INVALID_ARG_TYPE); + * tcTraceSync/tcTraceCallback/tcTracePromise run Node's publish choreography in C over + * dyn values (fn, ctx, thisArg, args-array) with thisArg bound as the + * ambient receiver — the traced call's throw and any subscriber throw + * both propagate (MAY THROW). tcTraceCallback wraps args[position] in a + * native error/result + asyncStart/asyncEnd publisher and throws Node's + * TypeError when that slot is not callable. tracingChannelOf is the + * five-Channel collection form of the constructor. */ + /** setImmediate as a first-class dyn value (scr_async.c): a minted dyn + * callable scheduling args[0](args[1..]) on the immediate queue — the + * Node-suite traceCallback shape (`traceCallback(setImmediate, ...)`). + * Calling it validates the callback (the dyn call machinery); minting + * never throws. */ + "timers.setImmediateFnValue": { argTypes: [], result: DYN }, + /** `new Promise(setImmediate)` (the Node-suite early-exit shape): a + * fresh promise an immediate fulfills with the undefined dyn value — + * the executor IS setImmediate, so resolve rides the immediate queue + * (scr_async.c). Never throws. */ + "timers.immediatePromise": { argTypes: [], result: { kind: "promise", inner: DYN } }, + "dc.tracingChannel": { argTypes: [STRING], result: F64 }, + "dc.tracingChannelOf": { argTypes: [F64, F64, F64, F64, F64], result: F64 }, + "dc.tcChannel": { argTypes: [F64, F64], result: F64 }, + "dc.tcHasSubscribers": { argTypes: [F64], result: BOOL }, + "dc.tcSubscribe": { argTypes: [F64, DYN], result: VOID }, + "dc.tcUnsubscribe": { argTypes: [F64, DYN], result: BOOL }, + "dc.tcTraceSync": { argTypes: [F64, DYN, DYN, DYN, DYN], result: DYN }, + "dc.tcTraceCallback": { argTypes: [F64, DYN, F64, DYN, DYN, DYN], result: DYN }, + /** tracePromise (scr_dc.c): start publish, the traced call, a wrap of + * non-promise results, the end publish, and a REACTION FIBER that + * awaits the traced promise and publishes asyncStart/asyncEnd (error + * first on rejection) before settling the returned promise with + * the passed-through outcome. MAY THROW (the traced call and the + * synchronous publishes). */ + "dc.tcTracePromise": { + argTypes: [F64, DYN, DYN, DYN, DYN], + result: { kind: "promise", inner: DYN }, + }, + /** process.on/once('unhandledRejection', fn): registers a dyn listener + * the checkpoint report dispatches per never-observed rejection — + * (reason, promise) — instead of printing and exiting 1 (scr_async.c). + * The bool second arg is `once` (auto-removed after one delivery, + * Node's once); off/removeListener remove by closure identity, the + * offWarning stance. Throws Node's ERR_INVALID_ARG_TYPE on a + * non-function. */ + /** Uncaught exception handlers and monitors: [callback dyn, once, monitor]. */ + "process.onUncaughtException": { argTypes: [DYN, BOOL, BOOL], result: VOID }, + "process.offUncaughtException": { argTypes: [DYN, BOOL], result: VOID }, + "process.onUnhandledRejection": { argTypes: [DYN, BOOL], result: VOID }, + "process.offUnhandledRejection": { argTypes: [DYN], result: VOID }, + /** process.on/once/off('rejectionHandled', fn): the sibling registry. + * A handler attached after unhandledRejection delivery fires the event + * once. Dispatch is synchronous at the attach, with the promise, + * Node's payload. */ + "process.onRejectionHandled": { argTypes: [DYN, BOOL], result: VOID }, + "process.offRejectionHandled": { argTypes: [DYN], result: VOID }, + /** process warnings (scr_async_dyn.c): onWarning/offWarning register + * dyn listeners; emitWarning applies Node's full argument grammar over + * the call's dyn argument vector (ERR_INVALID_ARG_TYPE TypeErrors — MAY + * THROW) and emits on the next tick, like Node: Node's stderr report, + * then the listeners. */ + "process.onWarning": { argTypes: [DYN], result: VOID }, + "process.offWarning": { argTypes: [DYN], result: VOID }, + "process.emitWarning": { argTypes: [DYN], result: VOID }, + /** A Warning-typed process warning with this message (the console + * members' warnings), emitted on the next tick as Node's emitWarning + * does (so the loop runs); an empty message emits nothing. */ + "process.emitWarningText": { argTypes: [STRING], result: VOID }, + /** process.noDeprecation (0) / process.throwDeprecation (1): the stored + * value (undefined until assigned) and its assignment. util.deprecate's + * warnings and DeprecationWarning emissions read their truthiness. */ + "process.deprecationFlag": { argTypes: [F64], result: DYN }, + "process.setDeprecationFlag": { argTypes: [F64, DYN], result: VOID }, + "process.setEmitWarning": { argTypes: [DYN], result: VOID }, + /** AsyncLocalStorage (node:async_hooks — scr_async.c): stores are f64 + * handles; contexts are immutable fiber-carried snapshots (spawned + * fibers inherit the spawner's — Node's init-time capture). run/exit + * enter (or clear) the store, call the dyn function with forwarded + * arguments, and restore (the finally); getStore answers the current + * dyn value or undefined; enterWith installs with no restore point. + * run/exit MAY THROW (the callback's own throws propagate). */ + /** `await v` over a checked-dynamic VALUE (scr_async.c): a dyn promise + * adopts (rejections re-throw — MAY THROW), anything else takes JS's + * one-microtask non-thenable await and answers itself. Only emitted + * inside async bodies (the frontend's isAsync gate). */ + "async.awaitDyn": { argTypes: [DYN], result: DYN }, + /** The bare one-microtask hop (scr_await_hop): `await v` over a typed + * NON-promise value — JS awaits non-thenables through exactly one + * microtask turn and yields the value itself. Never throws. */ + "async.hop": { argTypes: [], result: VOID }, + "als.new": { argTypes: [], result: F64 }, + "als.get": { argTypes: [F64], result: DYN }, + "als.run": { argTypes: [F64, DYN, DYN, DYN], result: DYN }, + "als.exitRun": { argTypes: [F64, DYN, DYN], result: DYN }, + "als.enterWith": { argTypes: [F64, DYN], result: VOID }, + "als.disable": { argTypes: [F64], result: VOID }, + /** new AsyncLocalStorage(options) (Node's object check throws; MAY + * THROW) and the store's name option (dyn; "" without one). */ + "als.newOptions": { argTypes: [DYN], result: F64 }, + "als.name": { argTypes: [F64], result: DYN }, + /** A function bound to the active context: the completion callbacks of + * callback-style APIs (always false: an empty context leaves the + * callback unbound) and AsyncLocalStorage.bind (always true). The typed + * form answers its closure argument's type; the dyn form throws Node's + * ERR_INVALID_ARG_TYPE for a non-function when always (MAY THROW). */ + "als.bindCallback": { argTypes: [null, BOOL], result: VOID }, + "als.bindCallbackDyn": { argTypes: [DYN, BOOL], result: DYN }, + /** AsyncLocalStorage.snapshot(): a dyn function (cb, ...args) that runs + * cb in the context active at the snapshot. */ + "als.snapshot": { argTypes: [], result: DYN }, + /** Channel.bindStore/unbindStore/runStores (scr_dc.c): the + * AsyncLocalStorage integration — set-semantics bindings per store, + * runStores entering every bound store with transform(data) around the + * publish and the callback (MAY THROW: transforms, subscribers, and + * the callback all run). */ + "dc.chanBindStore": { argTypes: [F64, F64, DYN], result: VOID }, + "dc.chanUnbindStore": { argTypes: [F64, F64], result: BOOL }, + "dc.chanRunStores": { argTypes: [F64, DYN, DYN, DYN, DYN], result: DYN }, + /** The Number statics with a static C implementation (scr_lib.c): one + * f64 arg → bool, JS-exact BY CONSTRUCTION — Number.isFinite/isNaN/ + * isInteger/isSafeInteger never coerce, and the frontend routes only + * f64-typed arguments here (other static types fence). None throws. */ + "number.isFinite": { argTypes: [F64], result: BOOL }, + "number.isNaN": { argTypes: [F64], result: BOOL }, + "number.isInteger": { argTypes: [F64], result: BOOL }, + "number.isSafeInteger": { argTypes: [F64], result: BOOL }, + /** Date (scr_lib.c). Values are TimeClip'd epoch-millisecond scalars: + * construction/store/pass/getters are exact while identity and mutation + * remain fenced. date.now is Node's integer milliseconds since epoch; + * date.toISOString formats one raw f64 millisecond time value and + * date.toISOStringValue formats a stored Date, both with Node's rules (UTC, + * YYYY-MM-DDTHH:mm:ss.sssZ, expanded ±YYYYYY years outside 0–9999, + * ToInteger truncation of fractional ms) and THROWS Node's "Invalid + * time value" RangeError on NaN / out-of-range input (may-throw seed). + * Results: f64 / owned (+1) string. */ + "date.now": { argTypes: [], result: F64 }, + "date.newNow": { argTypes: [], result: DATE_T }, + "date.newMs": { argTypes: [F64], result: DATE_T }, + "date.newString": { argTypes: [STRING], result: DATE_T }, + /** Date.parse(dateString), with its own reach witness for exact fences. */ + "date.parse": { argTypes: [STRING], result: F64 }, + "date.getTime": { argTypes: [DATE_T], result: F64 }, + "date.valueOf": { argTypes: [DATE_T], result: F64 }, + "date.toISOString": { argTypes: [F64], result: STRING }, + "date.toISOStringValue": { argTypes: [DATE_T], result: STRING }, + /** Date.prototype.toUTCString over a stored Date ("Invalid Date" for + * NaN; never throws). */ + "date.toUTCString": { argTypes: [DATE_T], result: STRING }, + "date.getFullYear": { argTypes: [DATE_T], result: F64 }, + "date.getUTCFullYear": { argTypes: [DATE_T], result: F64 }, + "date.getMonth": { argTypes: [DATE_T], result: F64 }, + "date.getUTCMonth": { argTypes: [DATE_T], result: F64 }, + "date.getDate": { argTypes: [DATE_T], result: F64 }, + "date.getUTCDate": { argTypes: [DATE_T], result: F64 }, + "date.getDay": { argTypes: [DATE_T], result: F64 }, + "date.getUTCDay": { argTypes: [DATE_T], result: F64 }, + "date.getHours": { argTypes: [DATE_T], result: F64 }, + "date.getUTCHours": { argTypes: [DATE_T], result: F64 }, + "date.getMinutes": { argTypes: [DATE_T], result: F64 }, + "date.getUTCMinutes": { argTypes: [DATE_T], result: F64 }, + "date.getSeconds": { argTypes: [DATE_T], result: F64 }, + "date.getUTCSeconds": { argTypes: [DATE_T], result: F64 }, + "date.getMilliseconds": { argTypes: [DATE_T], result: F64 }, + "date.getUTCMilliseconds": { argTypes: [DATE_T], result: F64 }, + "date.getTimezoneOffset": { argTypes: [DATE_T], result: F64 }, + /** A Date object for a time value a local holds (date-values.ts): a + * lib-call argument passes the time value itself, and any other use + * creates the Date. */ + "date.fromTime": { argTypes: [F64], result: DATE_T }, + /** Date.prototype's field setters over a time value + * (scr_date_set_fields): time value, setter code, argument count and four + * arguments; answers the new time value. */ + "date.setFields": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, + /** TimeClip of a number (setTime's argument). */ + "date.timeClip": { argTypes: [F64], result: F64 }, + /** Stores a time value into a Date object and answers it (a setter's + * write). */ + "date.store": { argTypes: [DATE_T, F64], result: F64 }, + /** Date.prototype.toString / toDateString / toTimeString and String(date): + * V8's local-time formats with the host zone's long name, "Invalid Date" + * for an invalid date. Owned (+1) string; never throws. */ + "date.toString": { argTypes: [DATE_T], result: STRING }, + "date.toDateString": { argTypes: [DATE_T], result: STRING }, + "date.toTimeString": { argTypes: [DATE_T], result: STRING }, + /** The composed `new Date(dateString).getTime()` read: one borrowed + * string, f64 milliseconds since epoch, with V8's date-string grammar + * (the ES5 format, then the legacy forms: RFC 1123/2822, Date#toString, + * "Jan 2, 2024", ...). Zone-less dates are local time; anything V8 + * rejects is NaN, Node's invalid-date getTime. Never throws. */ + "date.parseGetTime": { argTypes: [STRING], result: F64 }, + /** `Date.UTC(...)` — seven f64 arguments (the frontend completes the + * spec's defaults for omitted trailing parts: month 0, date 1, time + * parts 0), the spec's MakeDay/MakeTime/TimeClip exactly: 0–99 years + * map to 1900+year, out-of-range months/dates roll over, non-finite + * parts and out-of-range results answer NaN. Never throws. */ + "date.utc": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, + /** `new Date(year, monthIndex[, date, hours, minutes, seconds, ms])` — + * the same seven-f64 completion as date.utc, but the fields are LOCAL + * time: V8's MakeDay/MakeTime, then UTC(t) in the host zone (DST gaps + * read with the offset before the transition, overlaps answer the + * earlier instant), then TimeClip. Never throws. */ + "date.newLocal": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: DATE_T }, + /** WHATWG TextDecoder.decode over u8 bytes (scr_bytes.c): utf-8 with + * default options — the same maximal-subpart replacement decode as + * Buffer.toString("utf8"), with the leading BOM stripped (the one + * behavioral difference; ignoreBOM defaults to false). Inline calls + * lower directly; stored decoder records dispatch by encoding id. + * TextEncoder.encode needs no libFn: its matching forms + * lower to buffer.fromStr(s, "utf8") (identical bytes — both write each + * lone surrogate as U+FFFD). Borrowed arg; owned (+1) string; never throws. */ + "text.decode": { argTypes: [BYTES_U8], result: STRING }, + "bytes.bufferSource": { argTypes: [DYN], result: BYTES_U8 }, + /** Whether an error-first callback's Buffer result is absent: the zlib + * and crypto callback adapters pass NULL after an error. Borrowed arg; + * never throws. */ + "bytes.isAbsent": { argTypes: [BYTES_U8], result: BOOL }, + /** Whether two typed arrays share no byte: their element ranges in + * memory do not overlap. Borrowed args of any element kind; never throws. */ + "bytes.disjoint": { argTypes: [null, null], result: BOOL }, + /** The absent Buffer result itself (NULL), where an error-path callback + * body passes its undefined result on into a Buffer slot. Owned (+1); + * never throws. */ + "bytes.absent": { argTypes: [], result: BYTES_U8 }, + /** node:crypto asymmetric keys and signatures (scr_pki.c): checked- + * dynamic arguments, borrowed; KeyObject, Sign and Verify handles, + * Buffers and booleans as checked-dynamic results (+1). Node's argument + * and decoder errors throw (may-throw); generateKeyPairCb's callback + * receives (null, publicKey, privateKey) in a later macrotask. */ + "crypto.createPrivateKey": { argTypes: [DYN], result: DYN }, + "crypto.createPublicKey": { argTypes: [DYN], result: DYN }, + "crypto.generateKeyPairSync": { argTypes: [DYN, DYN], result: DYN }, + "crypto.generateKeyPairCb": { argTypes: [DYN, DYN, DYN], result: VOID }, + "crypto.signOneshot": { argTypes: [DYN, DYN, DYN], result: DYN }, + "crypto.diffieHellman": { argTypes: [DYN, DYN], result: DYN }, + "crypto.verifyOneshot": { argTypes: [DYN, DYN, DYN, DYN], result: DYN }, + "crypto.createSign": { argTypes: [DYN], result: DYN }, + "crypto.createVerify": { argTypes: [DYN], result: DYN }, + /** The Buffer brand of a Uint8Array source onto a fresh Uint8Array + * result (map and filter of a Buffer return a Buffer). */ + "bytes.copyBrand": { argTypes: [BYTES_U8, BYTES_U8], result: VOID }, + "text.decodeBufferSource": { argTypes: [DYN], result: STRING }, + "text.decodeOptions": { argTypes: [BYTES_U8, BOOL, BOOL], result: STRING }, + "text.decodeStream": { argTypes: [DYN, BYTES_U8, F64, BOOL, BOOL, BOOL], result: STRING }, + "text.decodeLegacyOptions": { argTypes: [BYTES_U8, F64, BOOL, BOOL], result: STRING }, + /** TextDecoder.decode for a compile-time non-UTF-8 WHATWG label. The + * second f64 is the frontend-owned encoding id consumed by scr_bytes.c; + * label aliases/case/ASCII whitespace are canonicalized before IR. The + * mapping tables are generated from the pinned Node 24 oracle, keeping + * output portable without an ICU/iconv dependency. Borrowed args; owned + * (+1) string; never throws. */ + "text.decodeLegacy": { argTypes: [BYTES_U8, F64], result: STRING }, + "text.decoderEncoding": { argTypes: [DYN], result: F64 }, + "text.decoderName": { argTypes: [F64], result: STRING }, + "text.encodeInto": { argTypes: [DYN, DYN], result: DYN }, + /** `value instanceof TextDecoder` (true) or `TextEncoder` (false) on a + * checked-dynamic value: a codec record's handle box (scr_text_codec_dyn.c). + * Borrowed; never throws. */ + "text.codecIs": { argTypes: [DYN, BOOL], result: BOOL }, + "buffer.isAscii": { argTypes: [DYN], result: BOOL }, + "buffer.isUtf8": { argTypes: [DYN], result: BOOL }, + "buffer.transcode": { argTypes: [DYN, DYN, DYN], result: BYTES_U8 }, + /** The fs option forms and friends (scr_lib.c), all throwing catchably + * like the rest of sync fs. mkdirRecursiveSync is Node's recursive + * algorithm (try mkdir, EEXIST-dir is fine, ENOENT creates the parent + * first — errors report Node's errno at Node's path, EEXIST at a file + * target, ENOTDIR at the full path past a file); the first-created-dir + * return value has no lowering (statement position only, frontend- + * fenced). rmOptsSync is rmSync with (recursive, force) bools: force + * swallows ENOENT, recursive removes trees post-order, a directory + * without recursive throws Node's ERR_FS_EISDIR SystemError. mkdtempSync appends the six X's and returns the + * created path (+1). accessSync takes the F_OK/R_OK/W_OK/X_OK bits as + * one f64 (the frontend bakes fs.constants.* as literals and completes + * an omitted mode to 0). readFdSync/readFdSyncBytes are the + * readFileSync(fd[, "utf8"]) forms — a read(2) loop to EOF on the fd + * (the stdin path); errors carry Node's no-path message shape. */ + "fs.mkdirRecursiveSync": { argTypes: [STRING], result: VOID }, + "fs.rmOptsSync": { argTypes: [STRING, BOOL, BOOL], result: VOID }, + /** rmOptsSync's maxRetries/retryDelay form (path, recursive, force, + * maxRetries, retryDelay): Node's linear-backoff retry on + * EBUSY/EMFILE/ENFILE/ENOTEMPTY/EPERM — the tmpdir-harness shape + * rmSync(p, { maxRetries: 3, recursive: true, force: true }). Throws + * ERR_OUT_OF_RANGE for a retryDelay outside int32 >= 0 or a maxRetries + * outside uint32, before checking the path. */ + "fs.rmRetrySync": { argTypes: [STRING, BOOL, BOOL, F64, F64], result: VOID }, + "fs.mkdtempSync": { argTypes: [STRING], result: STRING }, + "fs.accessSync": { argTypes: [STRING, F64], result: VOID }, + "fs.readFdSync": { argTypes: [F64, STRING], result: STRING }, + "fs.readFdSyncBytes": { argTypes: [F64], result: BYTES_U8 }, + /** Validated isatty(3) probe. Stream property lowering maps false to + * undefined; tty.isatty exposes the boolean result directly. */ + "process.isTTY": { argTypes: [F64], result: BOOL }, + // Like process.envGet: the result is the module's interned + // `number | undefined` union — checked by arms in the libCall case. + /** Terminal width over an fd literal (1/2 — process.stdout/stderr + * .columns): ioctl(TIOCGWINSZ). The result is the module's interned + * `number | undefined` union — a non-TTY stream (or an ioctl refusal) + * yields the undefined arm, exactly Node's missing `.columns`. Never + * throws. */ + "process.columns": { argTypes: [F64], result: VOID }, + /** Terminal height, with the same fd and optional-number contract. */ + "process.rows": { argTypes: [F64], result: VOID }, + /** process.stdin.destroy(): a deliberate no-op (no stream machinery + * exists to tear down; documented in SEMANTICS.md). */ + "process.stdinDestroy": { argTypes: [], result: VOID }, + /** process.stdin.setRawMode(mode) — one borrowed bool arg, void. On a + * TTY stdin: termios raw mode on/off (libuv's UV_TTY_MODE_RAW flag set, + * the mode Node's setRawMode(true) applies; false restores the entry + * state). On a NON-TTY stdin Node's process.stdin has no such method at + * all, so the call throws Node's exact catchable TypeError + * ("process.stdin.setRawMode is not a function") — the may-throw seed + * carries it. */ + "process.stdinSetRawMode": { argTypes: [BOOL], result: VOID }, + // Arg 0 is a packed f64[] OR a bytes value (the spread-typed-array + // form) — checked in the libCall case. + /** fromCharCode takes one packed f64[] or bytes arg and builds a string + * from UTF-16 code units. Adjacent surrogate pairs combine; lone + * surrogates follow the runtime's replacement policy. */ + "string.fromCharCode": { argTypes: [null], result: STRING }, + /** Numeric code points, with catchable RangeError for non-integers or + * values outside 0..0x10ffff. Uses the same UTF-8 surrogate policy. */ + "string.fromCodePoint": { argTypes: [null], result: STRING }, + /** lastIndexOf returns the last UTF-16 start index, or -1. The two-arg + * form searches at or before its numeric position; NaN starts at the end. + * String arguments are borrowed and neither form throws. */ + "string.lastIndexOf": { argTypes: [STRING, STRING], result: F64 }, + "string.lastIndexOfFrom": { argTypes: [STRING, STRING, F64], result: F64 }, + /** String.raw(template, ...subs): the raw literals array (a string[] + * read off the template record) interleaved with the PRE-STRINGIFIED + * substitutions (the frontend applies the static ToString and packs + * them into one string[] literal) — extra substitutions drop, missing + * ones skip, the spec's loop exactly (scr_array.c). Borrowed args; + * +1 string; never throws. */ + "string.raw": { argTypes: [arrayOf(STRING), arrayOf(STRING)], result: STRING }, + }); +} + +let libFnSignatureTable: ReturnType | undefined; + +/** The runtime call slots of every native builtin, keyed by name. The table + * is built on the first call, so runs that neither lower nor validate a + * module do not construct it. */ +export function libFnSignatures(): ReturnType { + libFnSignatureTable ??= buildLibFnSignatures(); + return libFnSignatureTable; +} /** A builtin name exists exactly when its runtime signature exists. */ -export type IrLibFn = keyof typeof LIB_FN_SIGS; +export type IrLibFn = keyof ReturnType; /** The function-stage runs of a pipeline of `total` stages that a stream * follows (positions bit j: stage j is a function), and an async diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 251d14904..655288945 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -3940,7 +3940,7 @@ export type IrExpr = } /** Standard-library call (`process` members, node:fs functions). `fn` is a * closed union; arg/result types are fixed per member (validated against - * LIB_FN_SIGS). Property READS (`process.argv`, `process.platform`) are + * libFnSignatures()). Property READS (`process.argv`, `process.platform`) are * zero-arg libCalls. Args are BORROWED by the operation (frame temps * release at statement end); refcounted results come back owned (+1) — * `process.argv` returns +1 on ONE interned array (JS identity: diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index eba319f85..f27d11f5b 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -66,7 +66,7 @@ import { import { unionWideningTags } from "./analysis.js"; import { alwaysReturns } from "./control-flow.js"; import { dynamicOperationOf, functionTier } from "./tiers.js"; -import { LIB_FN_SIGS, pipelinePumpedRuns } from "./builtin-signatures.js"; +import { libFnSignatures, pipelinePumpedRuns } from "./builtin-signatures.js"; import { ALWAYS_THROW_LIB_FNS } from "./builtin-effects.js"; /** The promisified crypto calls' value kinds besides checked-dynamic. */ @@ -4104,7 +4104,7 @@ function validateFunction( } if (e.fn === "worker.new" && !workers) errors.push({ message: "Worker construction requires worker execution mode", loc: e.loc }); - const sig = LIB_FN_SIGS[e.fn]; + const sig = libFnSignatures()[e.fn]; if (!sig) { err(`libCall of unknown library function "${e.fn as string}"`, e.loc); return; diff --git a/packages/compiler/src/library/fence-eval.ts b/packages/compiler/src/library/fence-eval.ts index 8b0450857..a950b3990 100644 --- a/packages/compiler/src/library/fence-eval.ts +++ b/packages/compiler/src/library/fence-eval.ts @@ -56,9 +56,9 @@ import { } from "../coverage/surface-manifest.js"; import { libFenceDiag, type ScrDiagnostic } from "../diagnostics/diagnostic.js"; import { - AMBIENT_SURFACE_FNS, + ambientSurfaceFns, BUILTIN_MODULE_CONSTS, - BUILTIN_MODULE_FNS, + builtinModuleFns, BUILTIN_MODULE_FN_ALIASES, STATIC_MATH_FNS, STATIC_MATH_PROPS, @@ -125,7 +125,7 @@ interface FenceTaxonomy { /** node-builtin constant ids: reads fold to literals at compile time. */ foldedIds: Set; /** Ambient dedicated-path surfaces (Date/perf_hooks/process): manifest - * id → the libCall spellings witnessing reach (AMBIENT_SURFACE_FNS — + * id → the libCall spellings witnessing reach (ambientSurfaceFns() — * the same rows the manifest projects the entries from). */ ambientFns: Map; } @@ -148,7 +148,7 @@ function fenceTaxonomy(): FenceTaxonomy { const builtinFns = new Map(); const builtinRoots = new Map(); const foldedIds = new Set(); - for (const [mod, members] of Object.entries(BUILTIN_MODULE_FNS)) { + for (const [mod, members] of Object.entries(builtinModuleFns())) { const root = new Set(); for (const [member, entry] of Object.entries(members!)) { if (entry === undefined) continue; @@ -156,7 +156,7 @@ function fenceTaxonomy(): FenceTaxonomy { // The bare-path and url tables rebind per TARGET platform // (builtinModuleFnsOf): a fence on the bare id covers both flavors. if (mod === "path") { - const w = BUILTIN_MODULE_FNS["path/win32"]?.[member]; + const w = builtinModuleFns()["path/win32"]?.[member]; if (w !== undefined) fns.add(w.fn); } if (mod === "url" && member === "pathToFileURL") fns.add("url.pathToFileURLWin32"); @@ -173,7 +173,7 @@ function fenceTaxonomy(): FenceTaxonomy { } for (const member of Object.keys(STATIC_MATH_PROPS)) foldedIds.add(`stdlib.math.${member}`); const ambientFns = new Map( - AMBIENT_SURFACE_FNS.map((row) => [row.id, row.fns as readonly string[]]), + ambientSurfaceFns().map((row) => [row.id, row.fns as readonly string[]]), ); taxonomy = { byId, diff --git a/packages/compiler/test/compat-exports.test.ts b/packages/compiler/test/compat-exports.test.ts new file mode 100644 index 000000000..57ee41339 --- /dev/null +++ b/packages/compiler/test/compat-exports.test.ts @@ -0,0 +1,12 @@ +import { expect, test } from "vitest"; +import { + LIB_FN_SIGS, + libFnSignatures, + NODE24_FETCH_COMPAT_PROFILE, + node24FetchCompatProfile, +} from "../src/index.js"; + +test("the table constants name the tables their functions build", () => { + expect(LIB_FN_SIGS).toBe(libFnSignatures()); + expect(NODE24_FETCH_COMPAT_PROFILE).toBe(node24FetchCompatProfile()); +}); diff --git a/packages/compiler/test/node-pin.test.ts b/packages/compiler/test/node-pin.test.ts index 7f032c69e..4d4c4a7e8 100644 --- a/packages/compiler/test/node-pin.test.ts +++ b/packages/compiler/test/node-pin.test.ts @@ -5,7 +5,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { expect, test } from "vitest"; -import { NODE24_FETCH_COMPAT_PROFILE } from "../src/compat/fetch-profile.js"; +import { node24FetchCompatProfile } from "../src/compat/fetch-profile.js"; import { NODE_COMPAT_VERSION } from "../src/frontend/lowering/builtins/process.js"; import { CRYPTO_CONSTANTS } from "../src/frontend/lowering/crypto-tables.js"; import { ZLIB_CONSTANTS } from "../src/frontend/lowering/zlib-constants.js"; @@ -23,7 +23,7 @@ test("the Node pins agree", () => { expect(compat.tag).toBe(`v${pinned}`); expect(read("Dockerfile.sandbox")).toMatch(new RegExp(`^ARG NODE_VERSION=${pinned}$`, "m")); expect(NODE_COMPAT_VERSION).toBe(pinned); - expect(NODE24_FETCH_COMPAT_PROFILE.target.node).toBe(pinned); + expect(node24FetchCompatProfile().target.node).toBe(pinned); expect(read("packages/runtime/src/scr_lib.c")).toContain( `#define SCR_NODE_COMPAT_VERSION "${pinned}"`, ); diff --git a/tests/harness/README.md b/tests/harness/README.md index 3922c72cc..66a528f4e 100644 --- a/tests/harness/README.md +++ b/tests/harness/README.md @@ -52,7 +52,7 @@ that may be newer than the installed declarations). Every item is classified: The selected adjacent interface families excluded from the census carry reasons too. A Node upgrade that adds/removes a public member or dictionary key fails the focused suite until that member is classified. The static and unsupported rows project into the shipped surface manifest; -filter `NODE24_FETCH_COMPAT_PROFILE.inventory.entries` by `status`/`owner` for +filter `node24FetchCompatProfile().inventory.entries` by `status`/`owner` for the next cohesive implementation queue. When Node changes, update `.node-version` and the profile's Node/Undici tuple diff --git a/tests/harness/code-quality.test.ts b/tests/harness/code-quality.test.ts index deff9afe0..9a683e50a 100644 --- a/tests/harness/code-quality.test.ts +++ b/tests/harness/code-quality.test.ts @@ -93,7 +93,7 @@ test("IR imports and the explicit parser islands remain usable", () => { source( work, "packages/compiler/src/frontend/lowering/probe.ts", - 'export { LIB_FN_SIGS } from "../../ir/builtin-signatures.js";\nexport { STR_INTRINSIC_SIGS } from "../../ir/intrinsic-signatures.js";\n', + 'export { libFnSignatures } from "../../ir/builtin-signatures.js";\nexport { STR_INTRINSIC_SIGS } from "../../ir/intrinsic-signatures.js";\n', ); source( work, diff --git a/tests/harness/fetch-conformance-program.ts b/tests/harness/fetch-conformance-program.ts index 0858f5bfe..322443e73 100644 --- a/tests/harness/fetch-conformance-program.ts +++ b/tests/harness/fetch-conformance-program.ts @@ -429,7 +429,7 @@ export function generateFetchConformanceProgram( const traceCount = options.traceCount ?? 12; const scenarios = generatedScenarioIds(profile); const sections: string[] = [ - `// Generated from NODE24_FETCH_COMPAT_PROFILE. Seed: 0x${seed.toString(16)}.`, + `// Generated from node24FetchCompatProfile(). Seed: 0x${seed.toString(16)}.`, ]; for (const scenario of scenarios) { if (!Object.hasOwn(SCENARIO_GENERATORS, scenario)) { diff --git a/tests/harness/fetch-conformance.test.ts b/tests/harness/fetch-conformance.test.ts index 5b6d62f08..abeac58ae 100644 --- a/tests/harness/fetch-conformance.test.ts +++ b/tests/harness/fetch-conformance.test.ts @@ -17,7 +17,7 @@ import { promisify } from "node:util"; import { beforeAll, describe, expect, test } from "vitest"; import { compile, - NODE24_FETCH_COMPAT_PROFILE, + node24FetchCompatProfile, renderDiagnostics, type FetchCompatEvidence, } from "@scriptc/compiler"; @@ -32,7 +32,7 @@ const execFileAsync = promisify(execFile); const repoRoot = join(import.meta.dirname, "../.."); const fixturesRoot = join(repoRoot, "tests/fixtures/fetch"); const sanitize = process.env["SCRIPTC_SAN"] === "1"; -const profile = NODE24_FETCH_COMPAT_PROFILE; +const profile = node24FetchCompatProfile(); function configuredInteger(name: string, fallback: number, maximum: number): number { const raw = process.env[name]; if (raw === undefined || raw === "") return fallback; diff --git a/tests/harness/surface-manifest.test.ts b/tests/harness/surface-manifest.test.ts index 58958c102..8a838aac6 100644 --- a/tests/harness/surface-manifest.test.ts +++ b/tests/harness/surface-manifest.test.ts @@ -6,7 +6,7 @@ import { analyze, generateSurfaceManifest, ir, - LIB_FN_SIGS, + libFnSignatures, renderSurfaceManifest, resolveLibraryFences, type SurfaceManifest, @@ -563,7 +563,7 @@ describe("determinism attestation ↔ fence parity", () => { // initializer is the Date expression the fences witness. "date.fromTime", ]); - const missing = Object.keys(LIB_FN_SIGS).filter( + const missing = Object.keys(libFnSignatures()).filter( (fn) => ir.LIB_NONDETERMINISTIC_PREFIXES.some(([prefix]) => fn.startsWith(prefix)) && !covered.has(fn) && @@ -571,7 +571,7 @@ describe("determinism attestation ↔ fence parity", () => { ); expect( missing, - "attestation-demoting spellings no declarable fence can deny — add a manifest projection (AMBIENT_SURFACE_FNS) or a member alias (BUILTIN_MODULE_FN_ALIASES)", + "attestation-demoting spellings no declarable fence can deny — add a manifest projection (ambientSurfaceFns()) or a member alias (BUILTIN_MODULE_FN_ALIASES)", ).toEqual([]); // The excuse list stays honest in the other direction too: an excused // spelling that gains detector coverage must leave the list. From 0e92be2bf79e9e244e32ffd3aef52d05cb9e5a93 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:41:44 -0500 Subject: [PATCH 052/230] Poll worker cancellation between blocks of typed-array loop iterations In a worker program, every loop without a pending test on each iteration decremented a cancellation budget in its body and polled the exception flag every 64 iterations. The poll's early return made each loop an early-exit loop, which LLVM's loop vectorizer does not vectorize. A counted for loop (integer counter, invariant limit), or a loop to a typed array's length, whose body reads or writes a typed-array element outside nested loops and closures, now runs as blocks of at most 64 iterations: the outer header tests the counter against the limit, the inner loop runs to the nearer of the limit and 64 steps on, and the poll follows each block. A typed array's length is fixed, so each block takes it as its limit. The loop polls as often as the budget did, and the inner loop's only exit besides break and return is its bound. The block bound lives in one slot shared by the loop's speculative copies, which resume iterations in each other's bodies. Other loops keep the budget, which costs less in loops of a few iterations. An elementwise Float64Array loop in a worker-enabled program vectorizes again: 949 ms to 691 ms. A byte-classifying loop over a 100M-byte Uint8Array takes 13 ms instead of 141 ms. New corpus program: native-worker-terminate-counted-loops.ts. --- .../src/backend/llvm/emit-statements.ts | 157 +++++++++++++++++- packages/compiler/src/backend/llvm/emitter.ts | 27 ++- packages/compiler/src/backend/loop-polls.ts | 31 ++++ .../test/counted-loop-emission.test.ts | 80 ++++++++- ...ive-worker-terminate-counted-loops.ts.json | 6 + .../native-worker-terminate-counted-loops.ts | 66 ++++++++ ...ive-worker-terminate-counted-loops.ts.json | 1 + 7 files changed, 358 insertions(+), 10 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-counted-loops.ts.json create mode 100644 tests/corpus/native-worker-terminate-counted-loops.ts create mode 100644 tests/harness/corpus-costs/native-worker-terminate-counted-loops.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index cbdeb0558..2efe2cf3a 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -91,6 +91,7 @@ import { } from "./string-construction.js"; import { emitStringInputs } from "./string-lifetimes.js"; import { LOOPS } from "./loop-state.js"; +import { loopAccessesTypedArrays } from "../loop-polls.js"; /** A callback loop version copies the loop; keep the copies small. */ const MAX_CALLBACK_VERSION_COST = 400; @@ -1211,9 +1212,67 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { B.line(`store ptr ${fresh}, ptr ${slot}`); } }; - const workerBudget = emitter.workerLoopBudget(s); + // A counted loop over typed arrays, or a loop to a typed array's + // length, polls between blocks of WORKER_POLL_BLOCK iterations instead + // of counting a budget in its body: the inner loop over a block then + // has no exit besides its own bound, which LLVM can vectorize. A typed + // array's length is fixed, so each block can take it as its limit. + const stripMined = + capturedLets.length === 0 && + integerSlot !== null && + ((countedLoop !== null && + countedLimit !== null && + Math.abs(countedLoop.step) * WORKER_POLL_BLOCK <= 2 ** 52) || + (integerLoop !== null && integerArrayLoop === null)) && + emitter.workerLoopPolls(s) && + loopAccessesTypedArrays(s); + const workerBudget = stripMined ? null : emitter.workerLoopBudget(s); + // One slot for every copy of the loop: a speculative copy resumes an + // iteration in another copy's body, which then reads this bound. + let blockBound: string | null = null; + if (stripMined) { + blockBound = B.slot(); + B.entryAllocas.push( + `${blockBound} = alloca ${countedLoop ? "i64" : emitter.sizeType} ; cancellation poll block bound`, + ); + } freshenBindings(); emitter.emitLoopCopies(s, capturedLets.length === 0, le, (exit) => { + if (stripMined) { + const counter: StripMinedCounter = countedLoop + ? { + slot: integerSlot!, + type: "i64", + signed: true, + step: countedLoop.step, + followers: countedLoop.followers.map((f, i) => ({ + slot: followerSlots[i]!, + step: f.step, + })), + limit: () => countedLimit!, + } + : { + slot: integerSlot!, + type: emitter.sizeType, + signed: false, + step: 1, + followers: [], + limit: () => { + const receiver = emitter.emitStableReceiver(integerLoop!.limitReceiver, []); + const lenPtr = B.tmp(); + const len = B.tmp(); + B.line( + `${lenPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver.name}, i64 0, i32 1`, + ); + B.line( + `${len} = load ${emitter.sizeType}, ptr ${lenPtr}${bytesHeaderAlias(emitter)}`, + ); + return len; + }, + }; + emitStripMinedCountedLoop(emitter, s, counter, blockBound!, exit); + return; + } const lc = B.newLabel("loop.c"); const lb = B.newLabel("loop.b"); const lu = @@ -2549,6 +2608,102 @@ function emitInt32KindLoop( /** At the top of a loop copy's header: lets a speculative loop find its * ordinary copy's header, and keeps its third copy out of the loop. */ +/** Iterations between cancellation polls of a strip-mined counted loop. */ +const WORKER_POLL_BLOCK = 64; + +/** The counter of a strip-mined loop: its slot, the LLVM integer type and + * signedness of its values, its step, the counters stepped beside it, and + * the limit, emitted in the outer header before each block. */ +interface StripMinedCounter { + slot: string; + type: string; + signed: boolean; + step: number; + followers: { slot: string; step: number }[]; + limit: () => string; +} + +/** A counted `for` loop of a worker program as blocks of WORKER_POLL_BLOCK + * iterations, with the cancellation poll between blocks: the outer header + * tests the counter against the loop's limit, each block runs the body up + * to the nearer of the limit and WORKER_POLL_BLOCK steps on, and the poll + * follows the block. It polls as often as the budget it replaces, and the + * inner loop's only exit besides `break` and `return` is its bound. */ +function emitStripMinedCountedLoop( + emitter: LlEmitter, + s: Extract, + counter: StripMinedCounter, + boundSlot: string, + exit: string, +): void { + const B = emitter.B; + const { slot, type, signed, step } = counter; + const ascending = step > 0; + const compare = !signed ? "ult" : ascending ? "slt" : "sgt"; + const saturating = `llvm.${signed ? "sadd" : "uadd"}.sat.${type}`; + const nearer = `llvm.${!signed ? "umin" : ascending ? "smin" : "smax"}.${type}`; + const wrap = signed ? "nsw" : "nuw"; + const lc = B.newLabel("loop.c"); + const block = B.newLabel("loop.block"); + const bc = B.newLabel("loop.bc"); + const lb = B.newLabel("loop.b"); + const lu = B.newLabel("loop.u"); + const poll = B.newLabel("loop.poll"); + emitter.declare(`declare ${type} @${saturating}(${type}, ${type})`); + emitter.declare(`declare ${type} @${nearer}(${type}, ${type})`); + B.br(lc); + B.startBlock(lc); + enterLoopHeader(emitter, lc); + const start = B.tmp(); + const open = B.tmp(); + B.line(`${start} = load ${type}, ptr ${slot}`); + const limit = counter.limit(); + B.line(`${open} = icmp ${compare} ${type} ${start}, ${limit}`); + B.condBr(open, block, exit); + B.startBlock(block); + const stepped = B.tmp(); + const bound = B.tmp(); + B.line( + `${stepped} = call ${type} @${saturating}(${type} ${start}, ${type} ${step * WORKER_POLL_BLOCK})`, + ); + B.line(`${bound} = call ${type} @${nearer}(${type} ${stepped}, ${type} ${limit})`); + B.line(`store ${type} ${bound}, ptr ${boundSlot}`); + B.br(bc); + B.startBlock(bc); + const index = B.tmp(); + const end = B.tmp(); + const inBlock = B.tmp(); + B.line(`${index} = load ${type}, ptr ${slot}`); + B.line(`${end} = load ${type}, ptr ${boundSlot}`); + B.line(`${inBlock} = icmp ${compare} ${type} ${index}, ${end}`); + B.condBr(inBlock, lb, poll); + B.startBlock(lb); + emitter.jumpTargets.push({ + kind: "loop", + brkLabel: exit, + contLabel: lu, + labels: s.labels, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + finallyDepth: emitter.finallyStack.length, + }); + emitter.emitBlock(s.body); + emitter.jumpTargets.pop(); + B.br(lu); + B.startBlock(lu); + for (const { slot: stepSlot, step: by } of [{ slot, step }, ...counter.followers]) { + const old = B.tmp(); + const next = B.tmp(); + B.line(`${old} = load ${type}, ptr ${stepSlot}`); + B.line(`${next} = add ${wrap} ${type} ${old}, ${by}`); + B.line(`store ${type} ${next}, ptr ${stepSlot}`); + } + B.br(bc); + B.startBlock(poll); + emitter.emitWorkerPoll(); + B.br(lc); +} + function enterLoopHeader(emitter: LlEmitter, header: string): void { const dispatch = emitter.speculationHeader; emitter.speculationHeader = null; diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index d1f4ebcca..c1ea7bf35 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -3932,13 +3932,19 @@ export class LlEmitter { } } + /** Whether `loop` needs a cancellation poll: in a worker program, unless + * the loop already runs a pending test on every iteration, which observes + * a terminate request itself (a separate poll would only add work). */ + workerLoopPolls(loop: Extract): boolean { + return ( + this.mod.workers === true && !loopTestsEveryIteration(loop, (e) => this.callTestsPending(e)) + ); + } + workerLoopBudget( loop: Extract, ): string | null { - if (!this.mod.workers) return null; - // A loop that runs a pending test on every iteration observes a - // terminate request through it; a separate poll would only add work. - if (loopTestsEveryIteration(loop, (e) => this.callTestsPending(e))) return null; + if (!this.workerLoopPolls(loop)) return null; const slot = this.B.slot(); this.B.entryAllocas.push(`${slot} = alloca i32 ; worker cancellation budget`); this.B.line(`store i32 0, ptr ${slot}`); @@ -4056,14 +4062,19 @@ export class LlEmitter { B.condBr(due, poll, next); B.startBlock(poll); B.line(`store i32 63, ptr ${slot}`); - // Calls and throwing operations retain their own checks. Amortize the - // extra cancellation poll across bounded loop batches so scalar work - // does not cross the runtime ABI on every iteration. - this.emitPendingCheck(undefined, true); + this.emitWorkerPoll(); B.br(next); B.startBlock(next); } + /** The cancellation poll itself. Calls and throwing operations retain + * their own checks. Loops amortize this extra poll across bounded + * batches of iterations so scalar work does not cross the runtime ABI on + * every iteration. */ + emitWorkerPoll(): void { + this.emitPendingCheck(undefined, true); + } + /** Moves an already-evaluated value into the runtime's exception cell — * the `throw` statement's kind dispatch (stmts.ts's), shared with * every synthetic thrower. Ownership of a refcounted payload must have diff --git a/packages/compiler/src/backend/loop-polls.ts b/packages/compiler/src/backend/loop-polls.ts index 14c2b049c..c582a4974 100644 --- a/packages/compiler/src/backend/loop-polls.ts +++ b/packages/compiler/src/backend/loop-polls.ts @@ -106,3 +106,34 @@ export function loopTestsEveryIteration(loop: Loop, tested: (call: IrExpr) => bo } return false; } + +/** True when the body of `loop`, outside its nested loops and closures, + * reads or writes a typed-array element: the loops whose cancellation poll + * the emitter moves between blocks of iterations, so that LLVM can + * vectorize them. Other loops keep the budget in their body, which costs + * less in a loop of a few iterations. */ +export function loopAccessesTypedArrays(loop: Loop): boolean { + const inExpr = (e: IrExpr): boolean => { + if (e.kind === "bytesIntrinsic" && e.method === "get") return true; + let found = false; + everyExprChild( + e, + (child) => !(found = inExpr(child)), + (child) => !(found = inStmt(child)), + ); + return found; + }; + const inStmt = (s: IrStmt): boolean => { + if (s.kind === "bytesSet") return true; + if (s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf") + return false; + let found = false; + everyStmtChild( + s, + (child) => !(found = inExpr(child)), + (child) => !(found = inStmt(child)), + ); + return found; + }; + return loop.body.some(inStmt); +} diff --git a/packages/compiler/test/counted-loop-emission.test.ts b/packages/compiler/test/counted-loop-emission.test.ts index 7a3fc1f44..206a3c57e 100644 --- a/packages/compiler/test/counted-loop-emission.test.ts +++ b/packages/compiler/test/counted-loop-emission.test.ts @@ -1,4 +1,4 @@ -import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; @@ -45,3 +45,81 @@ test("counted loops guard exact induction and preserve wide remainders on both p await rm(dir, { recursive: true, force: true }); } }); + +test("worker programs poll counted loops over typed arrays between blocks of iterations", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-counted-poll-")); + try { + const source = (workers: boolean): string => + [ + workers ? 'import { isMainThread, Worker } from "node:worker_threads";' : "", + "export function scale(a: Float64Array, b: Float64Array, n: number): void {", + " for (let i = 0; i < n; i++) b[i] = a[i]! * 2;", + "}", + "export function codes(a: Uint8Array): void {", + " for (let i = 0; i < a.length; ++i) a[i] = a[i] === 97 ? 0 : 3;", + "}", + "export function mix(n: number): number {", + " let t = 0;", + " for (let i = 0; i < n; i++) t = (t * 31 + i) % 1000003;", + " return t;", + "}", + "const a = new Float64Array(8).fill(1);", + "const b = new Float64Array(8);", + "scale(a, b, 8);", + "const c = new Uint8Array(8).fill(97);", + "codes(c);", + "console.log(b[7], mix(8), c[0]);", + workers ? "if (!isMainThread) new Worker(new URL(import.meta.url));" : "", + ].join("\n"); + const emit = async (workers: boolean): Promise => { + const entry = join(dir, `${workers ? "worker" : "plain"}.ts`); + await writeFile(entry, source(workers)); + const outPath = join(dir, `${workers ? "worker" : "plain"}.ir.json`); + const result = await compile(entry, { outPath, outDir: dir, outputKind: "ir" }); + if (!result.ok) throw new Error(result.diagnostics.map((d) => d.message).join("\n")); + return emitLlvmModule(deserializeModule(await readFile(outPath, "utf8"))); + }; + // Every emitted version of a function (the number and int32 entries). + const bodies = (ll: string, name: string): string => + [ + ...ll.matchAll( + new RegExp( + `define internal [^\\n]+ @sc_b?f_${name}(?:\\.i32)?\\([^\\n]*\\n([\\s\\S]*?)\\n\\}`, + "g", + ), + ), + ] + .map((match) => match[1]!) + .join("\n"); + const workerModule = await emit(true); + const worker = bodies(workerModule, "scale"); + const blocks = worker.split(/\n(?=[\w.]+:)/); + // No budget in the body: the inner loop's latch only steps the counter, + // the poll runs after each block, and the block's bound is the nearer of + // the limit and 64 steps on. + expect(worker).not.toContain("worker cancellation budget"); + expect(worker).toContain("@llvm.sadd.sat.i64(i64 %"); + expect(worker).toMatch(/@llvm\.smin\.i64\(i64 %\w+, i64 %\w+\)/); + for (const latch of blocks.filter((block) => block.startsWith("loop.u"))) + expect(latch).not.toContain("scr_exc_flag"); + const polls = blocks.filter((block) => block.startsWith("loop.poll")); + expect(polls.length).toBeGreaterThan(0); + for (const poll of polls) expect(poll).toContain("@scr_exc_flag"); + // A counted loop without typed-array accesses keeps the budget. + // A loop to a typed array's length is strip-mined the same way, with the + // length as each block's limit. + const codes = bodies(workerModule, "codes"); + expect(codes).not.toContain("worker cancellation budget"); + expect(codes).toMatch(/@llvm\.uadd\.sat\.i64\(i64 %\w+, i64 64\)/); + expect(codes).toMatch(/@llvm\.umin\.i64\(i64 %\w+, i64 %\w+\)/); + expect(codes).toMatch(/^loop\.poll\d+:$/m); + const mix = bodies(workerModule, "mix"); + expect(mix).toContain("worker cancellation budget"); + expect(mix).not.toContain("loop.poll"); + const plain = bodies(await emit(false), "scale"); + expect(plain).not.toContain("loop.poll"); + expect(plain).not.toContain("@scr_exc_flag"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-counted-loops.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-counted-loops.ts.json new file mode 100644 index 000000000..5f7c54075 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-counted-loops.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/native-worker-terminate-counted-loops.ts" + ], + "diags": [] +} diff --git a/tests/corpus/native-worker-terminate-counted-loops.ts b/tests/corpus/native-worker-terminate-counted-loops.ts new file mode 100644 index 000000000..55972c4f6 --- /dev/null +++ b/tests/corpus/native-worker-terminate-counted-loops.ts @@ -0,0 +1,66 @@ +// worker.terminate() stops a worker in counted loops over a typed array +// with long bounds: ascending and descending counters, a step above one, a +// second counter that steps with the first, and loops to a typed array's +// length run again and again. +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +function ascending(values: Float64Array, limit: number): number { + let acc = 0; + for (let i = 0; i < limit; i++) { + acc = (acc + values[i & 1023]!) % 1000003; + if (i === 5000) parentPort!.postMessage("ready"); + } + return acc; +} + +function descending(values: Float64Array, limit: number): number { + let acc = 0; + for (let i = limit; i > 0; i -= 3) { + acc = (acc * 31 + i + values[i & 1023]!) % 1000003; + if (i === limit - 15000) parentPort!.postMessage("ready"); + } + return acc; +} + +function paired(values: Float64Array, limit: number): number { + let acc = 0; + for (let i = 0, j = 7; i < limit; i++, j += 2) { + acc = (acc + i * j + values[j & 1023]!) % 1000003; + if (i === 5000) parentPort!.postMessage("ready"); + } + return acc; +} + +function lengths(bytes: Uint8Array): number { + let acc = 0; + for (let round = 0; ; round++) { + for (let i = 0; i < bytes.length; ++i) bytes[i] = bytes[i] === 97 ? 0 : (bytes[i]! + round) & 127; + for (let i = 0; i < bytes.length; i++) acc = (acc + bytes[i]!) % 1000003; + if (round === 50) parentPort!.postMessage("ready"); + } + return acc; +} + +if (isMainThread) { + for (const kind of ["ascending", "descending", "paired", "lengths"]) { + await new Promise((resolve) => { + const worker = new Worker(new URL(import.meta.url), { workerData: kind }); + worker.once("message", () => { + void worker.terminate().then((code: number) => console.log(kind, "terminate", code)); + }); + worker.once("exit", (code: number) => { + console.log(kind, "exit", code); + resolve(); + }); + }); + } +} else { + const values = new Float64Array(1024).map((_, i) => i); + const limit = 1e15; + let result = 0; + if (workerData === "ascending") result = ascending(values, limit); + else if (workerData === "descending") result = descending(values, limit); + else if (workerData === "paired") result = paired(values, limit); + else result = lengths(new Uint8Array(1 << 16).fill(97)); + console.log("unexpected result", result); +} diff --git a/tests/harness/corpus-costs/native-worker-terminate-counted-loops.ts.json b/tests/harness/corpus-costs/native-worker-terminate-counted-loops.ts.json new file mode 100644 index 000000000..66a146275 --- /dev/null +++ b/tests/harness/corpus-costs/native-worker-terminate-counted-loops.ts.json @@ -0,0 +1 @@ +{ "plain": 3594, "san": 23593 } From 1e9afec1d412240843aa865a84173f7dafd82f1c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:42:02 -0500 Subject: [PATCH 053/230] Reclaim completed socket writes before growing the pending-write queue The queue of writes with buffered bytes dropped completed entries only when the buffer emptied. On a socket whose buffer stays nonempty while old writes complete and new ones arrive, it kept every write since the buffer last emptied. Appending to a full queue now first moves the pending entries to the front, so its size follows the writes still pending. --- packages/runtime/src/scr_net.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index 0fe06cedc..85c7b7db7 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -1139,6 +1139,13 @@ static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len, s } memcpy(s->wbuf + s->wlen, data, len); s->wlen += len; + if (s->wq_n == s->wq_cap && s->wq_head > 0) { + /* reclaim the completed writes before growing: a buffer that never + * empties completes old writes while new ones arrive */ + memmove(s->wq, s->wq + s->wq_head, (s->wq_n - s->wq_head) * sizeof *s->wq); + s->wq_n -= s->wq_head; + s->wq_head = 0; + } if (s->wq_n == s->wq_cap) { s->wq_cap = s->wq_cap ? s->wq_cap * 2 : 8; s->wq = realloc(s->wq, s->wq_cap * sizeof *s->wq); From b3c98139e40d5b2c4bd57ec2dd9b06768baaf6d8 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:48:40 -0500 Subject: [PATCH 054/230] Move small number-key maps to the direct index from the second key A number-keyed Map or Set with keys the direct index allows now moves to it at its second entry rather than its fifth, so a map over a few small integer keys (a histogram over 0..3) reads through the inline direct index instead of the out-of-line linear scan. --- ...6310-small-number-map-direct-index.ts.json | 6 ++ packages/runtime/src/scr_map.c | 8 +- packages/runtime/src/scr_map.test.c | 8 +- packages/runtime/test/test_map.c | 23 +++--- .../6310-small-number-map-direct-index.ts | 77 +++++++++++++++++++ 5 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6310-small-number-map-direct-index.ts.json create mode 100644 tests/corpus/6310-small-number-map-direct-index.ts diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6310-small-number-map-direct-index.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6310-small-number-map-direct-index.ts.json new file mode 100644 index 000000000..72472aa95 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6310-small-number-map-direct-index.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6310-small-number-map-direct-index.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_map.c b/packages/runtime/src/scr_map.c index 1cb77b0f6..ccfd4b907 100644 --- a/packages/runtime/src/scr_map.c +++ b/packages/runtime/src/scr_map.c @@ -1205,9 +1205,11 @@ static __attribute__((noinline)) void scr_map_set_num(ScrMap *m, uint64_t hash, } /* A number-key map with at least this many entries moves to the direct - * index when its keys allow it, so small integer keys are indexed before - * the linear scan ends. */ -#define SCR_MAP_DENSE_AFTER 4 + * index when its keys allow it: from its second key, so a map of a few + * small integer keys (a histogram over 0..3) reads through the inline + * direct index rather than the out-of-line linear scan. A map with one + * key keeps its linear storage. */ +#define SCR_MAP_DENSE_AFTER 1 /* Whether appending one more entry would grow the lookup index rather than * reuse tombstones: scr_map_reserve_append compacts a linear map with a diff --git a/packages/runtime/src/scr_map.test.c b/packages/runtime/src/scr_map.test.c index 9916c7406..e04e0dac9 100644 --- a/packages/runtime/src/scr_map.test.c +++ b/packages/runtime/src/scr_map.test.c @@ -184,9 +184,11 @@ static void iterate_while_mutating(void) { static void modes(void) { ScrMap *map = scr_map_new(SCR_MAP_KEY_F64, SCR_MAP_VAL_F64, NULL); - for (int i = 1; i <= 4; i++) scr_map_set_f64_f64(map, i, i); - assert(!map->ndense && !map->nbuckets); /* still a linear scan */ - scr_map_set_f64_f64(map, 5, 5); + scr_map_set_f64_f64(map, 1, 1); + assert(!map->ndense && !map->nbuckets); /* one key: still a linear scan */ + scr_map_set_f64_f64(map, 2, 2); + assert(map->ndense >= 16 && !map->nbuckets); /* from the second small integer key */ + for (int i = 3; i <= 5; i++) scr_map_set_f64_f64(map, i, i); assert(map->ndense >= 16 && !map->nbuckets); for (int i = 6; i < 1000; i++) scr_map_set_f64_f64(map, i, i); assert(map->ndense >= 1000 && map->ndense <= 1000 + 1000 / 4 + 64); /* near the largest key */ diff --git a/packages/runtime/test/test_map.c b/packages/runtime/test/test_map.c index 46805da1e..e23618f77 100644 --- a/packages/runtime/test/test_map.c +++ b/packages/runtime/test/test_map.c @@ -422,12 +422,14 @@ static void test_live_iteration_indices(void) { /* A small (linear) map that must grow its lookup index while an iteration * is active may not compact: entry indices, tombstones and the append * position stay put. Integer keys in a compact range move to the direct - * index (dense) from the fifth entry, any other number keys to the bucket + * index (dense) from the second entry, any other number keys to the bucket * table from the ninth; both transitions are covered by running the same - * script with `offset` 0 and 0.5. */ + * script with `offset` 0 and 0.5. The walk deletes `victim`, an existing + * key, then appends the key that makes the index grow. */ static void run_small_storage_transitions(double offset) { bool dense = offset == 0; - int n = dense ? 4 : 8; /* entries a linear map holds before its index grows */ + int n = dense ? 1 : 8; /* entries a linear map holds before its index grows */ + int victim = dense ? 0 : 1; ScrMap *m = scr_map_new(SCR_MAP_KEY_F64, SCR_MAP_VAL_F64, NULL); for (int i = 0; i < n; i++) scr_map_set_f64_f64(m, i + offset, i + 10); check(m->nbuckets == 0 && m->buckets == NULL && m->ndense == 0, @@ -441,22 +443,23 @@ static void run_small_storage_transitions(double offset) { ScrMap *copy = scr_map_clone(m, false, NULL); check(copy->nbuckets == 0 && copy->ndense == 0, "small clone preserves compact storage"); scr_map_iter_enter(m); - scr_map_delete_f64(m, 1 + offset); + scr_map_delete_f64(m, victim + offset); scr_map_set_f64_f64(m, n + offset, 14); check(dense ? m->ndense > 0 && m->nbuckets == 0 : m->nbuckets > 0 && m->ndense == 0, "live iteration promotes without moving entry indices"); - check(m->nentries == (uint32_t)n + 1 && !scr_map_iter_live(m, 1) && + check(m->nentries == (uint32_t)n + 1 && !scr_map_iter_live(m, victim) && scr_map_iter_key_f64(m, n) == n + offset, "promotion preserves tombstones and append position"); for (int i = n + 1; i < 64; i++) scr_map_set_f64_f64(m, i + offset, i + 10); - check(m->nentries == 64 && !scr_map_iter_live(m, 1) && scr_map_iter_key_f64(m, 63) == 63 + offset, + check(m->nentries == 64 && !scr_map_iter_live(m, victim) && scr_map_iter_key_f64(m, 63) == 63 + offset, "growth during iteration keeps entry indices"); double out = 0; - check(scr_map_get_f64_f64(m, offset, &out) && out == 10, - "original key survives promotion and growth"); - check(!scr_map_has_f64(m, 1 + offset), "deleted small key stays absent after rehash"); + check(dense ? scr_map_get_f64_f64(m, n + offset, &out) && out == 14 + : scr_map_get_f64_f64(m, offset, &out) && out == 10, + "keys written before the promotion survive it and the growth"); + check(!scr_map_has_f64(m, victim + offset), "deleted small key stays absent after rehash"); scr_map_iter_exit(m); - check(scr_map_has_f64(copy, 1 + offset) && !scr_map_has_f64(copy, n + offset), + check(scr_map_has_f64(copy, victim + offset) && !scr_map_has_f64(copy, n + offset), "small clone remains independent"); scr_map_clear(copy); scr_map_iter_enter(copy); diff --git a/tests/corpus/6310-small-number-map-direct-index.ts b/tests/corpus/6310-small-number-map-direct-index.ts new file mode 100644 index 000000000..5614a56a1 --- /dev/null +++ b/tests/corpus/6310-small-number-map-direct-index.ts @@ -0,0 +1,77 @@ +// Number-keyed Maps and Sets with two to four small integer keys use the +// direct index. Results must match Node through overwrites, deletes and +// re-adds, a fractional, negative or large key that moves the map to +// hashing, clear, and mutation during a for-of or forEach walk. + +const hist = new Map(); +const codes = [0, 3, 1, 3, 2, 0, 3, 3, 1]; +for (const c of codes) hist.set(c, (hist.get(c) ?? 0) + 1); +console.log([...hist].map(([k, v]) => `${k}:${v}`).join(" "), hist.size, hist.has(4), hist.get(-0)); + +// Delete and re-add: the re-added key moves to the end. +hist.delete(3); +hist.set(3, 9); +hist.delete(0); +console.log([...hist.keys()].join(","), hist.get(3), hist.has(0)); + +// A key the direct index cannot hold moves the map to hashing. +for (const odd of [0.5, -1, 2 ** 40]) { + const m = new Map([ + [1, "a"], + [2, "b"], + ]); + m.set(odd, "c"); + m.set(1, "A"); + console.log([...m].map(([k, v]) => `${k}=${v}`).join(" "), m.get(2), m.get(odd), m.size); +} + +// Two keys, then clear, then reuse. +const pair = new Map(); +pair.set(5, 50); +pair.set(7, 70); +pair.clear(); +pair.set(7, 1); +pair.set(1, 2); +console.log([...pair].map(([k, v]) => `${k}=${v}`).join(" "), pair.size); + +// A walk that deletes ahead and adds keys. +const walk = new Map([ + [0, 0], + [1, 10], + [2, 20], +]); +const seen: number[] = []; +for (const [k, v] of walk) { + seen.push(v); + if (k === 0) { + walk.delete(2); + walk.set(3, 30); + } + if (k === 3) walk.set(0, -1); +} +console.log(seen.join(","), [...walk].map(([k, v]) => `${k}=${v}`).join(" ")); + +// Sets of small integers, forEach with deletion and addition. +const set = new Set([2, 0]); +set.add(1); +set.add(2); +const order: number[] = []; +set.forEach((v) => { + order.push(v); + if (v === 2) { + set.delete(1); + set.add(3); + } +}); +console.log(order.join(","), [...set].join(","), set.has(1), set.size); + +// Many small maps, each with a few keys. +let total = 0; +for (let i = 0; i < 1000; i++) { + const m = new Map(); + m.set(i % 3, i); + m.set((i + 1) % 3, 1); + if (i % 5 === 0) m.delete(i % 3); + total += (m.get(i % 3) ?? 100) + (m.get((i + 1) % 3) ?? 0) + m.size; +} +console.log(total); From 4eb3a16a0b0de657f7b4de5253e2545f1a0a2682 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:52:34 -0500 Subject: [PATCH 055/230] Match the Promise.all tuple adapter by its key-derived name in the chunked-emission test The adapter is named from its key since the helpers moved to contentSymbol, so the test no longer finds the counter name sc_all_tuple_0. It now matches any sc_all_tuple_ name; the serial and chunked emissions are still compared in full. --- packages/compiler/test/chunked-emission.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/compiler/test/chunked-emission.test.ts b/packages/compiler/test/chunked-emission.test.ts index 6d2b2e06a..703669648 100644 --- a/packages/compiler/test/chunked-emission.test.ts +++ b/packages/compiler/test/chunked-emission.test.ts @@ -95,7 +95,8 @@ test("a Promise.all tuple adapter requested in a range is defined", async () => throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); const ir = await readFile(outPath, "utf8"); const serial = text(emitLlvmModuleSource(deserializeModule(ir), {})); - expect(serial).toContain("define internal void @sc_all_tuple_0("); + // The adapter is named from its key (contentSymbol). + expect(serial).toMatch(/define internal void @sc_all_tuple_\d+\(/); const chunked = text(emitLlvmModuleSource(deserializeModule(ir), { functionChunks: 2 })); expect(chunked === serial).toBe(true); } finally { From cac5a19fc07a7cb13a8085645723a37d1a110d52 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:58:42 -0500 Subject: [PATCH 056/230] Fill Buffers with multi-byte values, swap byte order and decode utf16le a word at a time - fill with a string or Buffer value longer than one byte: writes the value once, then doubles the filled prefix with memcpy, as Node does. The value is copied with memmove, so a value that is a view of the buffer being filled gives Node's result; the previous per-byte loop read such a view while overwriting it. The loop also computed a division per byte. - swap16, swap32 and swap64: one load, byte swap and store per group, through a local data pointer. - toString("utf16le" | "ucs2"): four units below 0x80 copy as four bytes, and other units outside the surrogate range are written inline (a three-byte sequence as one four-byte store). Surrogates keep the previous path. The result records its UTF-16 length (one unit per input unit), or is marked ASCII, so `.length` does not walk it. --- .../8007-buffer-fill-swap-utf16le.ts.json | 6 + packages/runtime/src/scr_bytes.c | 85 ++++++++++++-- packages/runtime/src/scr_string.c | 13 +++ packages/runtime/src/scr_string.h | 3 + tests/corpus/8007-buffer-fill-swap-utf16le.ts | 110 ++++++++++++++++++ .../8007-buffer-fill-swap-utf16le.ts.json | 1 + 6 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8007-buffer-fill-swap-utf16le.ts.json create mode 100644 tests/corpus/8007-buffer-fill-swap-utf16le.ts create mode 100644 tests/harness/corpus-costs/8007-buffer-fill-swap-utf16le.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8007-buffer-fill-swap-utf16le.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8007-buffer-fill-swap-utf16le.ts.json new file mode 100644 index 000000000..f629dac24 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8007-buffer-fill-swap-utf16le.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8007-buffer-fill-swap-utf16le.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index 398e6eb86..df64b2ab2 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -2495,14 +2495,57 @@ static uint32_t scr_bytes_utf16le_cp(const uint8_t *in, size_t units, size_t *u) return cu; } +/* Four units below 0x80 copy as four bytes (one word holds them), and + * other units outside the surrogate range are written inline; surrogates + * take scr_bytes_utf16le_cp. The string has one UTF-16 unit per input + * unit, which is recorded so its length needs no walk. */ static ScrStr *scr_bytes_decode_utf16le(const uint8_t *in, size_t n) { size_t units = n / 2; if (units > SIZE_MAX / 3) scr_bytes_oom(); ScrStr *s = scr_str_alloc_raw(0, units * 3); - size_t o = 0; - for (size_t u = 0; u < units;) o = scr_bytes_put_cp(s->data, o, scr_bytes_utf16le_cp(in, units, &u)); + uint8_t *out = (uint8_t *)s->data; + size_t o = 0, u = 0; + while (u < units) { +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + if (units - u >= 4) { + uint64_t w; + memcpy(&w, in + u * 2, 8); + if (!(w & UINT64_C(0xff80ff80ff80ff80))) { + out[o] = (uint8_t)w; + out[o + 1] = (uint8_t)(w >> 16); + out[o + 2] = (uint8_t)(w >> 32); + out[o + 3] = (uint8_t)(w >> 48); + o += 4; + u += 4; + continue; + } + } +#endif + uint32_t cu = (uint32_t)in[u * 2] | ((uint32_t)in[u * 2 + 1] << 8); + if (cu >= 0x800 && (cu < 0xd800 || cu > 0xdfff)) { + /* Three bytes in one four-byte store; the next write (or the + * terminator) replaces the fourth. */ + uint8_t seq[4] = {(uint8_t)(0xe0 | (cu >> 12)), (uint8_t)(0x80 | ((cu >> 6) & 0x3f)), + (uint8_t)(0x80 | (cu & 0x3f)), 0}; + memcpy(out + o, seq, 4); + o += 3; + u++; + } else if (cu < 0x80) { + out[o++] = (uint8_t)cu; + u++; + } else if (cu < 0x800) { + out[o] = (uint8_t)(0xc0 | (cu >> 6)); + out[o + 1] = (uint8_t)(0x80 | (cu & 0x3f)); + o += 2; + u++; + } else { + o = scr_bytes_put_cp(s->data, o, scr_bytes_utf16le_cp(in, units, &u)); + } + } s->len = o; s->data[o] = '\0'; + if (o == units) scr_str_note_ascii(s); + else scr_str_note_utf16_len(s, units); return s; } @@ -3479,7 +3522,15 @@ static ScrBytes *scr_bytes_fill_core(ScrBytes *b, const uint8_t *pat, size_t pat } else if (patn == 1) { memset(dst, pat[0], e - o); } else { - for (size_t i = o; i < e; i++) dst[i - o] = pat[(i - o) % patn]; + /* Node's fill: the pattern's first bytes (memmove: the pattern may + * be a view of this buffer), then the filled prefix doubled. */ + size_t len = e - o, done = patn < len ? patn : len; + memmove(dst, pat, done); + while (done < len) { + size_t step = done < len - done ? done : len - done; + memcpy(dst + done, dst, step); + done += step; + } } if (b->shared) { scr_shared_store(b->data + o, dst, e - o); @@ -3558,11 +3609,29 @@ ScrBytes *scr_bytes_swap(ScrBytes *b, double width) { scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)mlen, "ERR_INVALID_BUFFER_SIZE"); return NULL; } - for (size_t g = 0; g < b->len; g += w) { - for (size_t i = 0; i < w / 2; i++) { - uint8_t t = b->data[g + i]; - b->data[g + i] = b->data[g + w - 1 - i]; - b->data[g + w - 1 - i] = t; + /* One load, byte swap and store per group. */ + uint8_t *p = b->data; + size_t len = b->len; + if (w == 2) { + for (size_t g = 0; g < len; g += 2) { + uint16_t v; + memcpy(&v, p + g, 2); + v = __builtin_bswap16(v); + memcpy(p + g, &v, 2); + } + } else if (w == 4) { + for (size_t g = 0; g < len; g += 4) { + uint32_t v; + memcpy(&v, p + g, 4); + v = __builtin_bswap32(v); + memcpy(p + g, &v, 4); + } + } else { + for (size_t g = 0; g < len; g += 8) { + uint64_t v; + memcpy(&v, p + g, 8); + v = __builtin_bswap64(v); + memcpy(p + g, &v, 8); } } return scr_bytes_retain(b); diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index d2d397e25..dfd0d23f8 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -2006,6 +2006,19 @@ double scr_str_utf16_len(ScrStr *s) { return (double)(int64_t)units; } +/* Records the UTF-16 length of a string whose producer counted it (latin1 + * text has one unit per byte), so scr_str_utf16_len does not walk it. Only + * strings that scr_str_utf16_len would otherwise record are kept. */ +void scr_str_note_utf16_len(ScrStr *s, size_t units) { + if (s->ascii || s->len <= 64 || s->rc == SIZE_MAX) return; + if (!scr_u16len_tab) { + scr_u16len_tab = calloc(SCR_U16LEN_N, sizeof *scr_u16len_tab); + if (!scr_u16len_tab) return; + scr_sidx_register_cleanup(); + } + *scr_u16len_slot(s) = (ScrU16Len){s, units}; +} + double scr_str_char_code_at(ScrStr *s, double i) { /* A sequential scan of a non-ASCII receiver stays inside the bounded * ASCII span cached at its last resolved position. Its unit offset is an diff --git a/packages/runtime/src/scr_string.h b/packages/runtime/src/scr_string.h index fc2f15562..e269498b1 100644 --- a/packages/runtime/src/scr_string.h +++ b/packages/runtime/src/scr_string.h @@ -83,6 +83,9 @@ static inline void scr_str_hash_forget(ScrStr *s) { /* True only when every byte is proven below 0x80 (see above). */ static inline bool scr_str_known_ascii(const ScrStr *s) { return s->ascii; } +/* Record the UTF-16 length a string's producer counted (scr_string.c). */ +void scr_str_note_utf16_len(ScrStr *s, size_t units); + /* Record a proven-ASCII fact. Immortal strings may live in read-only * memory and are shared across threads, so they are never written. */ static inline void scr_str_note_ascii(ScrStr *s) { diff --git a/tests/corpus/8007-buffer-fill-swap-utf16le.ts b/tests/corpus/8007-buffer-fill-swap-utf16le.ts new file mode 100644 index 000000000..850c5b964 --- /dev/null +++ b/tests/corpus/8007-buffer-fill-swap-utf16le.ts @@ -0,0 +1,110 @@ +// Buffer fill with values of more than one byte, swap16/32/64, and +// toString("utf16le"): pattern lengths that do and do not divide the +// filled range, offsets and ends, patterns that are views of the buffer +// being filled, string encodings, sizes around word and pool boundaries, +// and utf16le text with ASCII runs, other BMP characters, surrogate pairs, +// lone surrogates and an odd last byte. + +function show(buf: Uint8Array): string { + return Buffer.from(buf).toString("hex"); +} + +const fills: string[] = []; +for (const size of [0, 1, 2, 3, 7, 8, 9, 31, 32, 33]) { + const buf = Buffer.alloc(size); + buf.fill("ab"); + fills.push(buf.toString("latin1")); + buf.fill(Buffer.from([1, 2, 3])); + fills.push(show(buf)); +} +console.log(fills.join(" ")); + +const ranged = Buffer.alloc(20); +ranged.fill("xyz", 3, 17); +console.log(ranged.toString("latin1").replace(/\0/g, ".")); +ranged.fill(Buffer.from("0123456789"), 5); +console.log(ranged.toString("latin1").replace(/\0/g, ".")); +ranged.fill("long pattern longer than the range", 2, 6); +console.log(ranged.toString("latin1").replace(/\0/g, ".")); +ranged.fill("é中", 0, 11); +console.log(show(ranged)); +ranged.fill("6869", 0, 7, "hex"); +ranged.fill("aGk=", 10, 15, "base64"); +console.log(show(ranged)); +ranged.fill("éa", 0, 20, "latin1"); +console.log(show(ranged)); +ranged.fill("ab", 0, 9, "utf16le"); +console.log(show(ranged)); + +// Patterns that are views of the buffer being filled. +const self = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +self.fill(self.subarray(1, 3)); +console.log(show(self)); +const later = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +later.fill(later.subarray(6, 9), 2); +console.log(show(later)); +const whole = Buffer.from([9, 8, 7, 6]); +whole.fill(whole); +console.log(show(whole)); +const typed = new Uint8Array(12); +typed.set([5, 6, 7], 0); +Buffer.from(typed.buffer).fill(typed.subarray(0, 3), 3); +console.log(show(typed)); + +const big = Buffer.alloc(70001); +big.fill("0123456"); +console.log(big.length, big.toString("latin1", 0, 10), big.toString("latin1", 69990), big.toString("latin1", 35000, 35010)); +try { + Buffer.alloc(4).fill(Buffer.alloc(0)); +} catch (error) { + console.log((error as NodeJS.ErrnoException).code); +} +console.log(show(Buffer.alloc(5).fill(""))); + +// swap16/32/64. +for (const size of [0, 8, 16, 24, 64, 1000]) { + const buf = Buffer.alloc(size); + for (let i = 0; i < size; i++) buf[i] = (i * 37 + 11) & 255; + const a = Buffer.from(buf).swap16(); + const b = Buffer.from(buf).swap32(); + const c = Buffer.from(buf).swap64(); + console.log(size, show(a.subarray(0, 8)), show(b.subarray(0, 8)), show(c.subarray(0, 8)), show(c.subarray(c.length - 8)), a.swap16().equals(buf)); +} +for (const [size, width] of [[3, 16], [6, 32], [12, 64]] as const) { + try { + const buf = Buffer.alloc(size); + if (width === 16) buf.swap16(); + else if (width === 32) buf.swap32(); + else buf.swap64(); + } catch (error) { + console.log((error as NodeJS.ErrnoException).code, (error as Error).message); + } +} +const view = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).subarray(1, 9); +view.swap32(); +console.log(show(view)); + +// toString("utf16le"). +const texts = [ + "plain ascii text", + "abcéÿĀ߿ࠀ中￿", + "ascii run then 中文 and more ascii after it", + "\u{1F600}\u{10FFFF}a\u{10000}", + "lone \ud800 high and \udc00 low and \ud83d", + "x".repeat(4097) + "é" + "y".repeat(3), +]; +for (const text of texts) { + const bytes = Buffer.from(text, "utf16le"); + const back = bytes.toString("utf16le"); + console.log(back === text, back.length, bytes.length, JSON.stringify(back.slice(0, 24)), back.charCodeAt(back.length - 1)); +} +const odd = Buffer.from([0x61, 0, 0x62, 0, 0x63]); +console.log(JSON.stringify(odd.toString("utf16le")), odd.toString("utf16le").length); +const pairSplit = Buffer.from([0x3d, 0xd8, 0x00, 0xde, 0x3d, 0xd8]); +const split = pairSplit.toString("utf16le"); +console.log(split.length, split.codePointAt(0), split.charCodeAt(2)); +const units = Buffer.alloc(40); +for (let i = 0; i < 40; i++) units[i] = (i * 53 + 7) & 255; +const decoded = units.toString("utf16le"); +console.log(decoded.length, [...decoded].map((c) => c.codePointAt(0)).join(",")); +console.log(units.toString("utf16le", 3, 21).length, units.toString("ucs2", 0, 8) === units.toString("utf16le", 0, 8)); diff --git a/tests/harness/corpus-costs/8007-buffer-fill-swap-utf16le.ts.json b/tests/harness/corpus-costs/8007-buffer-fill-swap-utf16le.ts.json new file mode 100644 index 000000000..2c773a1b8 --- /dev/null +++ b/tests/harness/corpus-costs/8007-buffer-fill-swap-utf16le.ts.json @@ -0,0 +1 @@ +{ "plain": 4406, "san": 2190 } From 872c12e247952524485022a9e1af90c89ce05c3e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 05:59:55 -0500 Subject: [PATCH 057/230] Emit dev builds' function bodies on the emit workers --- .../compiler/src/backend/llvm/debug-info.ts | 104 ++++++++++++++++- .../compiler/src/backend/llvm/emit-journal.ts | 44 ++++++- .../compiler/src/backend/llvm/emit-module.ts | 8 +- packages/compiler/src/backend/llvm/emitter.ts | 32 ++++-- .../compiler/test/chunked-emission.test.ts | 108 +++++++++++------- 5 files changed, 232 insertions(+), 64 deletions(-) diff --git a/packages/compiler/src/backend/llvm/debug-info.ts b/packages/compiler/src/backend/llvm/debug-info.ts index 3a75c6263..aaa35d666 100644 --- a/packages/compiler/src/backend/llvm/debug-info.ts +++ b/packages/compiler/src/backend/llvm/debug-info.ts @@ -13,9 +13,37 @@ import { import { SourceLocations } from "../source-locations.js"; import { mangleFunction } from "../mangle.js"; -/** DWARF describes source bindings at their actual native storage locations. */ +/** A request a range emitter made of its debug info that added nodes + * (LlvmDebugInfo.record), with the ids it answered. */ +export type DebugRequest = + | { readonly op: "function"; readonly fn: string; readonly result: string } + | { + readonly op: "local"; + readonly local: IrLocal; + readonly fn: string; + readonly arg: number; + readonly captured: boolean; + readonly result: { variable: string; location: string }; + } + | { + readonly op: "location"; + readonly loc: SrcLoc; + readonly fn: string; + readonly result: string; + }; + +/** DWARF describes source bindings at their actual native storage locations. + * + * Nodes are numbered in request order. A range emitter (emit-module.ts) + * records the requests that added nodes; the emitter that merges the range + * replays them in order, so every node gets the number serial emission + * gives it, and renames the range's text from the ids the range answered to + * its own. A request that added no node answered an id that existed before + * the range started or that an earlier recorded request created. */ export class LlvmDebugInfo { private readonly nodes: string[] = []; + /** The requests that added nodes, while recording (record). */ + private requests: DebugRequest[] | null = null; private readonly files = new Map(); private readonly locations = new Map(); private readonly scopes = new Map(); @@ -73,7 +101,53 @@ export class LlvmDebugInfo { return unit; } + /** Records the requests that add nodes from here on. */ + record(): void { + this.requests = []; + } + + /** The requests recorded since record. */ + recorded(): readonly DebugRequest[] { + return this.requests ?? []; + } + + /** Replays a range's recorded requests in order and adds to `ids` the + * rename from the ids the range answered to this debug info's. */ + replay( + requests: readonly DebugRequest[], + functionNamed: (name: string) => IrFunction, + ids: Map, + ): void { + const own = (id: string, to: string | null | undefined): void => { + if (to === null || to === undefined) + throw new Error(`llvm emitter bug: debug request for ${id} added no node on replay`); + if (to !== id) ids.set(id, to); + }; + for (const request of requests) { + if (request.op === "function") { + own(request.result, this.function(functionNamed(request.fn))); + continue; + } + const fn = ids.get(request.fn) ?? request.fn; + if (request.op === "location") { + own(request.result, this.location(request.loc, fn)); + continue; + } + const local = this.local(request.local, fn, request.arg, request.captured); + own(request.result.variable, local?.variable); + own(request.result.location, local?.location); + } + } + function(fn: IrFunction): string | null { + const before = this.nodes.length; + const id = this.functionNode(fn); + if (this.requests !== null && id !== null && this.nodes.length > before) + this.requests.push({ op: "function", fn: fn.name, result: id }); + return id; + } + + private functionNode(fn: IrFunction): string | null { const pos = this.source.position(fn.loc); if (pos === null) return null; const file = this.file(pos.file); @@ -230,6 +304,26 @@ export class LlvmDebugInfo { fn: string | null, arg = 0, captured = false, + ): { variable: string; expression: string; location: string } | null { + const before = this.nodes.length; + const result = this.localNodes(local, fn, arg, captured); + if (this.requests !== null && fn !== null && result !== null && this.nodes.length > before) + this.requests.push({ + op: "local", + local, + fn, + arg, + captured, + result: { variable: result.variable, location: result.location }, + }); + return result; + } + + private localNodes( + local: IrLocal, + fn: string | null, + arg: number, + captured: boolean, ): { variable: string; expression: string; location: string } | null { if (fn === null || local.source === undefined) return null; const pos = this.source.position(local.source.loc); @@ -279,6 +373,14 @@ export class LlvmDebugInfo { } location(loc: SrcLoc, fn: string | null): string | null { + const before = this.nodes.length; + const id = this.locationNode(loc, fn); + if (this.requests !== null && fn !== null && id !== null && this.nodes.length > before) + this.requests.push({ op: "location", loc, fn, result: id }); + return id; + } + + private locationNode(loc: SrcLoc, fn: string | null): string | null { if (fn === null) return null; const pos = this.source.position(loc); if (pos === null) return null; diff --git a/packages/compiler/src/backend/llvm/emit-journal.ts b/packages/compiler/src/backend/llvm/emit-journal.ts index ad89d2671..d6fed03a6 100644 --- a/packages/compiler/src/backend/llvm/emit-journal.ts +++ b/packages/compiler/src/backend/llvm/emit-journal.ts @@ -5,6 +5,7 @@ * replay calls the same entry point with the same arguments, so every * helper gets the number and the definition the serial emission gives it, * and the range's text is renamed to those numbers. */ +import type { DebugRequest } from "./debug-info.js"; import type { LlEmitter } from "./emitter.js"; /** One event of a range's emission, in order. */ @@ -97,6 +98,8 @@ export class EmitJournal { * worker process can write it. */ export interface ChunkResult { readonly entries: readonly JournalEntry[]; + /** The requests that added debug metadata nodes (LlvmDebugInfo.record). */ + readonly debugRequests: readonly DebugRequest[]; readonly flags: { readonly usesCoroutines: boolean; readonly usesTimers: boolean; @@ -152,23 +155,56 @@ export function replayJournal( } /** The rename of a range's text: numbered names (`names`, without the - * sigil) and field alias tags (`tags`). One scan for each kind of name: - * a numbered name starts `sc_` after a character that cannot be part of a - * name, and ends at the last digit after its counter prefix. */ + * sigil), field alias tags (`tags`) and debug metadata ids (`debugIds`, + * `!N`). One scan for each kind of name: a numbered name starts `sc_` + * after a character that cannot be part of a name, and ends at the last + * digit after its counter prefix. */ export function renameChunk( names: ReadonlyMap, tags: ReadonlyMap, + debugIds: ReadonlyMap = new Map(), ): (text: string) => string { const prefixes = new Set(); for (const name of names.keys()) prefixes.add(name.replace(/\d+$/, "")); - if (prefixes.size === 0 && tags.size === 0) return (text) => text; + if (prefixes.size === 0 && tags.size === 0 && debugIds.size === 0) return (text) => text; return (text) => { let out = prefixes.size === 0 ? text : renameNames(text, names, prefixes); if (tags.size > 0) out = renameTags(out, tags); + if (debugIds.size > 0) out = renameDebugIds(out, debugIds); return out; }; } +/** Renames metadata references `!N` outside string literals. Debug builds + * attach no alias tags, so every such reference in a function's text is a + * debug metadata id. */ +function renameDebugIds(text: string, ids: ReadonlyMap): string { + const parts: string[] = []; + let copied = 0; + // The next quote at or after the scan, and whether the scan is inside a + // string literal. + let quote = text.indexOf('"'); + let quoted = false; + for (let at = text.indexOf("!"); at >= 0; at = text.indexOf("!", at + 1)) { + while (quote >= 0 && quote < at) { + quoted = !quoted; + quote = text.indexOf('"', quote + 1); + } + if (quoted) continue; + if (at > 0 && isNameChar(text.charCodeAt(at - 1))) continue; + let end = at + 1; + while (end < text.length && isDigit(text.charCodeAt(end))) end++; + if (end === at + 1 || (end < text.length && isNameChar(text.charCodeAt(end)))) continue; + const own = ids.get(text.slice(at, end)); + if (own === undefined) continue; + parts.push(text.slice(copied, at), own); + copied = end; + } + if (copied === 0) return text; + parts.push(text.slice(copied)); + return parts.join(""); +} + function renameNames( text: string, names: ReadonlyMap, diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index dfdd95451..6a33924c3 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -134,6 +134,7 @@ function emitFunctionsOnWorkers(emitter: LlEmitter, workers: number): string[] | let code = 1; try { emitter.journal = new EmitJournal(); + emitter.debug?.record(); const texts: string[] = []; for (const fn of functions.slice(range * size, (range + 1) * size)) { texts.push(emitter.emitFunction(fn)); @@ -230,13 +231,10 @@ const PARTITION_GROUP_END = "; scriptc-group"; export function emitModuleParts(emitter: LlEmitter): string[] { // Function bodies first (the literal/unit/fn-value tables fill as they // emit), then the file assembles around them — the runtime ABI’s order. - // Debug metadata is numbered per emitter and not journaled, so a debug - // build emits every body here. - const ranges = emitter.debug === null; const chunks = emitter.targetOptions.functionChunks ?? 1; const fnDefs = - (ranges && chunks > 1 ? emitFunctionsInChunks(emitter, chunks) : null) ?? - (ranges ? emitFunctionsOnWorkers(emitter, emitter.targetOptions.functionWorkers ?? 1) : null) ?? + (chunks > 1 ? emitFunctionsInChunks(emitter, chunks) : null) ?? + emitFunctionsOnWorkers(emitter, emitter.targetOptions.functionWorkers ?? 1) ?? []; if (fnDefs.length === 0) for (const fn of emitter.mod.functions) { diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index d1f4ebcca..8dc1f912a 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -476,14 +476,13 @@ export interface LlvmTargetOptions { * earlier emission found breaking its rules (UnwindAbiViolation). */ unwindExcluded?: ReadonlySet; /** Emit the function bodies in this many contiguous ranges, each by its - * own emitter, and merge them in function order (emit-journal.ts). The - * output is the same as one emitter's. Debug builds (`debugSources`) - * emit in one emitter. */ + * own emitter, and merge them in function order (emit-journal.ts, and + * debug-info.ts for debug metadata). The output is the same as one + * emitter's. */ functionChunks?: number; /** Emit the function bodies of a large module on this many forked worker * processes at a time (emit-module.ts), forked with `forkPrimitives`. The - * output is the same as one emitter's. Debug builds (`debugSources`) - * emit in one emitter. */ + * output is the same as one emitter's. */ functionWorkers?: number; /** The host's process primitives, where it can fork (the native * compiler's). */ @@ -1889,9 +1888,7 @@ export class LlEmitter { this.journal = journal; this.journaled = journal !== null || - ((options.functionWorkers ?? 1) > 1 && - options.forkPrimitives !== undefined && - options.debugSources === undefined); + ((options.functionWorkers ?? 1) > 1 && options.forkPrimitives !== undefined); // One walk of every body, which the analyses below read instead of // walking the bodies themselves. this.nodeIndex = int32Analysis?.nodeIndex ?? new IrNodeIndex(mod); @@ -5264,6 +5261,7 @@ export class LlEmitter { chunkResult(): ChunkResult { return { entries: this.journal!.entries, + debugRequests: this.debug?.recorded() ?? [], flags: { usesCoroutines: this.usesCoroutines, usesTimers: this.usesTimers, @@ -5299,6 +5297,8 @@ export class LlEmitter { // emission left them (a helper set guards the declarations its first // request adds). replayJournal(this, chunk.entries, names, tags); + const debugIds = new Map(); + this.debug?.replay(chunk.debugRequests, (name) => this.functionNamed(name), debugIds); const flags = chunk.flags; this.usesCoroutines ||= flags.usesCoroutines; this.usesTimers ||= flags.usesTimers; @@ -5319,7 +5319,13 @@ export class LlEmitter { for (const name of chunk.adoptCallees) this.adoptCallees.add(name); for (const name of chunk.directAwaitCallees) this.directAwaitCallees.add(name); for (const name of chunk.inlineAwaitCallees) this.inlineAwaitCallees.add(name); - return renameChunk(names, tags); + return renameChunk(names, tags, debugIds); + } + + private functionNamed(name: string): IrFunction { + const fn = this.fnByName.get(name); + if (fn === undefined) throw new InternalCompilerError(`llvm emitter bug: no function ${name}`); + return fn; } /** Adds a content-named constant a chunk interned. Its symbol is this @@ -5447,7 +5453,13 @@ export class LlEmitter { /** An emitter for a range of this module's functions: the same facts, * empty module-level tables, and a journal of what the range adds. */ chunkEmitter(): LlEmitter { - return new LlEmitter(this.mod, { ...this.targetOptions, functionChunks: 1 }, new EmitJournal()); + const chunk = new LlEmitter( + this.mod, + { ...this.targetOptions, functionChunks: 1 }, + new EmitJournal(), + ); + chunk.debug?.record(); + return chunk; } /** The double-ABI entry of a function with an i32 entry: every caller diff --git a/packages/compiler/test/chunked-emission.test.ts b/packages/compiler/test/chunked-emission.test.ts index 6d2b2e06a..567a082ef 100644 --- a/packages/compiler/test/chunked-emission.test.ts +++ b/packages/compiler/test/chunked-emission.test.ts @@ -103,57 +103,77 @@ test("a Promise.all tuple adapter requested in a range is defined", async () => } }, 60_000); -// Debug metadata is numbered per emitter (debug-info.ts) and not journaled: -// a debug build emits its function bodies in one emitter. Its one fork -// computes the int32 slot analysis; when the fork fails, the emitter -// computes it itself. -test("debug builds emit function bodies serially, with every metadata reference defined", async () => { +// Debug builds journal their metadata requests (debug-info.ts): ranges +// merged in function order number every node as one emitter does. +const DEBUG_PROGRAMS: { entry: string; sources: string[] }[] = [ + { + entry: "3066-source-debugging/main.ts", + sources: ["3066-source-debugging/main.ts", "3066-source-debugging/helper.ts"], + }, + ...[ + "1020-async-basics.ts", + "7103-fiberless-plain-calls.ts", + "7110-als-completion-callbacks.ts", + "1301-errors-subclass.ts", + "1529-class-with-statics.ts", + "1591-js-closures.js", + "2010-generators-basics.ts", + "2013-generators-sent-values.ts", + "4451-int32-field-storage.ts", + ].map((program) => ({ entry: program, sources: [program] })), +]; + +test("debug builds emitted in ranges merge into the text one emitter gives", async () => { const dir = await mkdtemp(join(tmpdir(), "scriptc-chunked-debug-")); const minFunctions = process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"]; try { - const corpus = join(import.meta.dirname, "../../../tests/corpus/3066-source-debugging"); - const entry = join(corpus, "main.ts"); - const outPath = join(dir, "main.ir.json"); - const result = await compile(entry, { - outDir: dir, - outPath, - outputKind: "ir", - optimization: "dev", - }); - if (!result.ok) - throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); - const ir = await readFile(outPath, "utf8"); - const debugSources = new Map(); - for (const name of ["main.ts", "helper.ts"]) - debugSources.set(join(corpus, name), await readFile(join(corpus, name), "utf8")); - const options: LlvmTargetOptions = { debugSources }; - const serial = text(emitLlvmModuleSource(deserializeModule(ir), options)); - expect(serial).toContain("!DILocation"); + const corpus = join(import.meta.dirname, "../../../tests/corpus"); + for (const program of DEBUG_PROGRAMS) { + const outPath = join(dir, "main.ir.json"); + const result = await compile(join(corpus, program.entry), { + outDir: dir, + outPath, + outputKind: "ir", + optimization: "dev", + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const ir = await readFile(outPath, "utf8"); + const debugSources = new Map(); + for (const name of program.sources) + debugSources.set(join(corpus, name), await readFile(join(corpus, name), "utf8")); + const options: LlvmTargetOptions = { debugSources }; + const serial = text(emitLlvmModuleSource(deserializeModule(ir), options)); + expect(serial, program.entry).toContain("!DILocation"); - let forks = 0; - process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = "1"; - const forkPrimitives = { - fork: (): number => { - forks++; - return -1; - }, - exit: (): void => {}, - wait: (): number => -1, - }; - for (const variant of [ - { functionChunks: 3 }, - { functionWorkers: 4, forkPrimitives }, - ] satisfies LlvmTargetOptions[]) { - const emitted = text(emitLlvmModuleSource(deserializeModule(ir), { ...options, ...variant })); - expect(emitted === serial, JSON.stringify(Object.keys(variant))).toBe(true); - const defined = new Set([...emitted.matchAll(/^(![0-9]+) = /gm)].map((m) => m[1])); - const referenced = new Set([...emitted.matchAll(/(![0-9]+)\b/g)].map((m) => m[1])); - expect([...referenced].filter((id) => !defined.has(id))).toEqual([]); + // A fork that fails leaves every range to the merging emitter. + process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = "1"; + const forkPrimitives = { + fork: (): number => -1, + exit: (): void => {}, + wait: (): number => -1, + }; + for (const variant of [ + { functionChunks: 2 }, + { functionChunks: 5 }, + { functionWorkers: 4, forkPrimitives }, + ] satisfies LlvmTargetOptions[]) { + const emitted = text( + emitLlvmModuleSource(deserializeModule(ir), { ...options, ...variant }), + ); + expect(emitted === serial, `${program.entry} ${JSON.stringify(Object.keys(variant))}`).toBe( + true, + ); + const defined = new Set([...emitted.matchAll(/^(![0-9]+) = /gm)].map((m) => m[1])); + const referenced = new Set([...emitted.matchAll(/(![0-9]+)\b/g)].map((m) => m[1])); + expect([...referenced].filter((id) => !defined.has(id))).toEqual([]); + } + if (minFunctions === undefined) delete process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"]; + else process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = minFunctions; } - expect(forks).toBe(1); } finally { if (minFunctions === undefined) delete process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"]; else process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = minFunctions; await rm(dir, { recursive: true, force: true }); } -}, 120_000); +}, 300_000); From 627ebd86a51546f2fd6d40a82caab8191ceee667 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:16:32 -0500 Subject: [PATCH 058/230] Call replace callbacks with unannotated parameters directly TypeScript types the parameters of an inline replace callback after the match `any`. An unannotated parameter that is never reassigned, whose every read means the same for a string or number as for `any`, and that receives a capture every match sets, the offset or the subject now takes that value's type. String-search callbacks get the same treatment for the offset and the subject. scr_regex_replace_fn calls the closure directly in more cases: - checked-dynamic parameters, which receive the boxed value Node passes at their position (a capture or undefined, the offset, the subject, the groups object, then undefined); - `string | undefined` capture parameters, which receive NULL when the match leaves the capture unset; - a checked-dynamic result, converted with ToString; - up to six parameters. Callbacks of the earlier typed form compile to the same call, with a kinds argument of 0, and run the same per-match work. --- benchmarks/runtime/README.md | 1 + .../runtime/workloads/regex-replacers.json | 6 + .../runtime/workloads/regex-replacers.ts | 60 ++++ .../compiler/src/backend/llvm/lib-dispatch.ts | 77 +++-- .../containers/replacer-params.test.ts | 63 ++++ .../lowering/containers/replacer-params.ts | 292 ++++++++++++++++++ .../lowering/containers/string-and-regexp.ts | 7 +- .../lowering/containers/string-replacement.ts | 14 +- .../compiler/test/replacer-params.test.ts | 84 +++++ .../tests/corpus/8118-replacer-params.ts.json | 6 + ...45-replace-callback-untyped-params.ts.json | 6 + ...6-replace-callback-untyped-params.mjs.json | 6 + packages/runtime/src/scr_regex.c | 203 +++++++++--- packages/runtime/src/scr_regex.h | 26 +- tests/corpus/8118-replacer-params.ts | 43 +++ .../8345-replace-callback-untyped-params.ts | 85 +++++ .../8346-replace-callback-untyped-params.mjs | 32 ++ .../corpus-costs/8118-replacer-params.ts.json | 1 + ...45-replace-callback-untyped-params.ts.json | 1 + ...6-replace-callback-untyped-params.mjs.json | 1 + 20 files changed, 941 insertions(+), 73 deletions(-) create mode 100644 benchmarks/runtime/workloads/regex-replacers.json create mode 100644 benchmarks/runtime/workloads/regex-replacers.ts create mode 100644 packages/compiler/src/frontend/lowering/containers/replacer-params.test.ts create mode 100644 packages/compiler/src/frontend/lowering/containers/replacer-params.ts create mode 100644 packages/compiler/test/replacer-params.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8118-replacer-params.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8345-replace-callback-untyped-params.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8346-replace-callback-untyped-params.mjs.json create mode 100644 tests/corpus/8118-replacer-params.ts create mode 100644 tests/corpus/8345-replace-callback-untyped-params.ts create mode 100644 tests/corpus/8346-replace-callback-untyped-params.mjs create mode 100644 tests/harness/corpus-costs/8118-replacer-params.ts.json create mode 100644 tests/harness/corpus-costs/8345-replace-callback-untyped-params.ts.json create mode 100644 tests/harness/corpus-costs/8346-replace-callback-untyped-params.mjs.json diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index 520964cf3..af3d3d239 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -30,6 +30,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `record-boundaries` | cpu | records built from unchecked array reads passed to and returned from annotated functions | | `records-sort` | cpu | comparator sorts, group-by, `reduce`/`filter` | | `regex-logs` | cpu | regex `exec` with groups, `test`, global `replace` | +| `regex-replacers` | cpu | regex `replace`/`replaceAll` with inline callbacks whose parameters have no annotations: dictionary placeholders, snake_case to camelCase, date reordering, optional captures, capitalization, and a string search that reads the offset | | `region-builds` | cpu | a watch-mode build loop over `LifetimeRegion`: each build binds a program in a new region and releases the previous one, which a cache keeps referenced for two builds | | `template-render` | cpu | template literals, escaping, `map`/`join` | | `throw-chains` | cpu | exceptions caught 2, 5 and 20 recursive calls above the throw, through direct calls and through a callback, and a backtracking parser that throws on a failed alternative | diff --git a/benchmarks/runtime/workloads/regex-replacers.json b/benchmarks/runtime/workloads/regex-replacers.json new file mode 100644 index 000000000..c01c6c272 --- /dev/null +++ b/benchmarks/runtime/workloads/regex-replacers.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/regex-replacers.ts", + "args": [ + "1" + ] +} diff --git a/benchmarks/runtime/workloads/regex-replacers.ts b/benchmarks/runtime/workloads/regex-replacers.ts new file mode 100644 index 000000000..be3820b89 --- /dev/null +++ b/benchmarks/runtime/workloads/regex-replacers.ts @@ -0,0 +1,60 @@ +// Regex replace and replaceAll with inline callbacks, written as they +// usually are, without parameter annotations: template placeholders looked +// up in a dictionary, snake_case to camelCase, date reordering, optional +// version parts, capitalization and a string search with its offset. +let seed = 7; +function random(): number { + seed = (Math.imul(seed, 1103515245) + 12345) >>> 0; + return seed / 4294967296; +} +function pick(items: readonly T[]): T { + return items[Math.floor(random() * items.length)]!; +} + +const words = ["user", "order", "item", "cart", "price", "total", "status", "created", "updated", "id"]; +const names = ["Ada", "Grace", "Linus", "Barbara", "Ken", "Margaret"]; +const vars: Record = { + name: "world", + city: "Lisbon", + day: "Tuesday", + plan: "pro", + count: "12", +}; +const keys = [...Object.keys(vars), "missing"]; + +const scale = Number(process.argv[2] ?? "1"); +const count = Math.floor(20000 * scale); +const templates: string[] = []; +const identifiers: string[] = []; +const dates: string[] = []; +const versions: string[] = []; +const sentences: string[] = []; +for (let i = 0; i < count; i++) { + templates.push(`Hello {${pick(keys)}}, your {${pick(keys)}} plan renews on {${pick(keys)}} in {${pick(keys)}}.`); + identifiers.push(`${pick(words)}_${pick(words)}_${pick(words)}${random() < 0.5 ? "_" + pick(words) : ""}`); + const year = 1990 + Math.floor(random() * 40); + const month = String(1 + Math.floor(random() * 12)).padStart(2, "0"); + const day = String(1 + Math.floor(random() * 28)).padStart(2, "0"); + dates.push(`from ${year}-${month}-${day} to ${year + 1}-${month}-${day} (${pick(names)})`); + versions.push(`v${i % 9}.${i % 13}${random() < 0.5 ? "." + (i % 7) : ""} and v${i % 5}.${i % 3}`); + sentences.push(`${pick(names)} said the ${pick(words)} and the ${pick(words)} were ready for ${pick(names)}`); +} + +let length = 0; +let checksum = 0; +for (let round = 0; round < 4; round++) { + for (let i = 0; i < count; i++) { + const filled = templates[i]!.replace(/\{(\w+)\}/g, (_m, key) => vars[key] ?? `<${key}>`); + const camel = identifiers[i]!.replace(/_([a-z])/g, (_m, c) => c.toUpperCase()); + const reordered = dates[i]!.replace(/(\d{4})-(\d{2})-(\d{2})/g, (_m, y, m, d) => `${d}/${m}/${y}`); + const padded = versions[i]!.replace(/v(\d+)\.(\d+)(?:\.(\d+))?/g, (_m, major, minor, patch) => `v${major}.${minor}.${patch ?? "0"}`); + const titled = sentences[i]!.replace(/\b[a-z]/g, (c) => c.toUpperCase()); + const marked = sentences[i]!.replaceAll("the", (m, at) => (at % 2 === 0 ? m.toUpperCase() : m)); + length += filled.length + camel.length + reordered.length + padded.length + titled.length + marked.length; + checksum = (checksum * 31 + camel.charCodeAt(camel.length - 1) + padded.charCodeAt(padded.length - 1)) >>> 0; + } +} +console.log(templates[0]!.replace(/\{(\w+)\}/g, (_m, key) => vars[key] ?? `<${key}>`)); +console.log(identifiers[1]!.replace(/_([a-z])/g, (_m, c) => c.toUpperCase())); +console.log(versions[2]!.replace(/v(\d+)\.(\d+)(?:\.(\d+))?/g, (_m, major, minor, patch) => `v${major}.${minor}.${patch ?? "0"}`)); +console.log("length", length, "checksum", checksum); diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 8a98fb154..4af8182a7 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -843,35 +843,68 @@ function emitAssertFailing( * `a.name.localeCompare(b.name)` reads both fields without retaining). */ const READ_ONLY_STRING_LIB_FNS: ReadonlySet = new Set(["str.localeCompare"]); -/** The parameter count of a replace callback the runtime can call through - * the closure ABI (scr_regex_replace_fn), or null when it needs the dyn - * box. Each declared parameter must receive a value of its declared kind - * in every match: the match string at 0; with more than one parameter the +/** The most parameters scr_regex_replace_fn calls a replacer with + * (SCR_REPLACER_MAX_ARITY), the first bit of its string-or-undefined + * parameters (SCR_REPLACER_OPTIONAL), and its flag for a checked-dynamic + * result (SCR_REPLACER_BOXED_RESULT). */ +const REPLACER_MAX_ARITY = 6; +const REPLACER_OPTIONAL = 2 ** 8; +const REPLACER_BOXED_RESULT = 2 ** 31; + +/** `string | undefined` held as the string pointer, NULL for undefined. */ +function optionalStringType(host: LlvmEmitterContext, type: IrType): boolean { + if (type.kind !== "union") return false; + const nullable = host.nullableUnions.get(type.unionId); + if (nullable?.repr !== "single" || nullable.arm.kind !== "string" || nullable.nullTag >= 0) + return false; + return host.unionsById.get(type.unionId)?.arms[nullable.unitTag]?.kind === "undefinedT"; +} + +/** The parameter count and kinds (scr_regex_replace_fn's `kinds`) of a + * replace callback the runtime can call through the closure ABI, or null + * when it needs the dyn box. A checked-dynamic parameter receives whatever + * Node passes at its position. Each typed parameter must receive a value of + * its declared kind in every match: the match string at 0; past 0 the * pattern must be a literal whose captures are known, so position i is a - * capture that every match sets (a string), the offset (a number) right - * after the captures, or the subject (a string) after that. */ -function typedReplacerArity(callback: IrType, pattern: IrExpr): number | null { + * capture (a string when every match sets it, otherwise `string | + * undefined`), the offset (a number) right after the captures, or the + * subject (a string) after that. The result is a string or a + * checked-dynamic value. */ +function typedReplacerShape( + host: LlvmEmitterContext, + callback: IrType, + pattern: IrExpr, +): { arity: number; kinds: number } | null { if (callback.kind !== "func" || callback.rest || callback.argumentsAll) return null; - if (callback.ret.kind !== "string") return null; + if (callback.ret.kind !== "string" && callback.ret.kind !== "dyn") return null; const params = callback.params; - if (params.length > 4) return null; - if (params.length === 0) return 0; - if (params[0]!.kind !== "string") return null; - if (params.length === 1) return 1; - if (pattern.kind !== "regexLit") return null; - const captures = regexLiteralCaptures(pattern.pattern, pattern.flags); - if (!captures) return null; - for (let i = 1; i < params.length; i++) { + if (params.length > REPLACER_MAX_ARITY) return null; + let kinds = callback.ret.kind === "dyn" ? REPLACER_BOXED_RESULT : 0; + let captures: { count: number; always: boolean[] } | null | undefined; + for (let i = 0; i < params.length; i++) { const kind = params[i]!.kind; + if (kind === "dyn") { + kinds += 2 ** i; + continue; + } + const optional = optionalStringType(host, params[i]!); + if (i === 0) { + if (kind !== "string") return null; + continue; + } + if (pattern.kind !== "regexLit") return null; + captures ??= regexLiteralCaptures(pattern.pattern, pattern.flags); + if (!captures) return null; const ok = i < captures.count - ? kind === "string" && captures.always[i] === true + ? (kind === "string" && captures.always[i] === true) || optional : i === captures.count ? kind === "f64" : i === captures.count + 1 && kind === "string"; if (!ok) return null; + if (optional) kinds += REPLACER_OPTIONAL * 2 ** i; } - return params.length; + return { arity: params.length, kinds }; } /** `s.replace(re, fn)` / `replaceAll` with a typed callback the runtime can @@ -881,16 +914,16 @@ function typedReplacerArity(callback: IrType, pattern: IrExpr): number | null { function emitTypedRegexReplace(host: LlvmEmitterContext, e: LibCallExpr): LlValue | null { const [subject, pattern, callback, all] = e.args; if (!subject || !pattern || callback?.kind !== "dynFrom" || all?.kind !== "boolLit") return null; - const arity = typedReplacerArity(callback.value.type, pattern); - if (arity === null) return null; + const shape = typedReplacerShape(host, callback.value.type, pattern); + if (shape === null) return null; const B = host.B; const s = host.emitExpr(subject); const re = host.emitExpr(pattern); const fn = host.emitExpr(callback.value); - host.declare(`declare ptr @scr_regex_replace_fn(ptr, ptr, ptr, i32, i1 zeroext)`); + host.declare(`declare ptr @scr_regex_replace_fn(ptr, ptr, ptr, i32, i32, i1 zeroext)`); const result = B.tmp(); B.line( - `${result} = call ptr @scr_regex_replace_fn(ptr ${s.name}, ptr ${re.name}, ptr ${fn.name}, i32 ${arity}, i1 zeroext ${all.value})`, + `${result} = call ptr @scr_regex_replace_fn(ptr ${s.name}, ptr ${re.name}, ptr ${fn.name}, i32 ${shape.arity}, i32 ${shape.kinds | 0}, i1 zeroext ${all.value})`, ); const output = host.own({ name: result, type: e.type }); host.emitPendingCheck(); diff --git a/packages/compiler/src/frontend/lowering/containers/replacer-params.test.ts b/packages/compiler/src/frontend/lowering/containers/replacer-params.test.ts new file mode 100644 index 000000000..1d5d376e9 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/containers/replacer-params.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, test } from "vitest"; +import { regexCaptureParticipation } from "./replacer-params.js"; + +describe("regex capture participation", () => { + test("counts captures and marks the ones every match sets", () => { + expect(regexCaptureParticipation("\\{(\\w+)\\}", "g")).toEqual({ + count: 2, + always: [true, true], + }); + expect(regexCaptureParticipation("(\\d{4})-(\\d{2})(?:-(\\d{2}))?", "g")).toEqual({ + count: 4, + always: [true, true, true, false], + }); + expect(regexCaptureParticipation("(a)|(b)", "")).toEqual({ + count: 3, + always: [true, false, false], + }); + expect(regexCaptureParticipation("((a)|b)", "")).toEqual({ + count: 3, + always: [true, true, false], + }); + expect(regexCaptureParticipation("(?:(a)|b)(c)", "")).toEqual({ + count: 3, + always: [true, false, true], + }); + expect(regexCaptureParticipation("(a)*(b)+(c){0,2}(d){1,3}(e){00}", "")).toEqual({ + count: 6, + always: [true, false, true, false, true, false], + }); + expect(regexCaptureParticipation("(?=(a))(?!(b))(?<=(c))(?e)", "")).toEqual({ + count: 6, + always: [true, false, false, false, false, true], + }); + }); + + test("skips escapes, classes and non-capturing groups", () => { + expect(regexCaptureParticipation("\\((a)\\)[(|)\\]](?:b)(?i:c)", "")).toEqual({ + count: 2, + always: [true, true], + }); + expect(regexCaptureParticipation("[](a)", "")).toEqual({ count: 2, always: [true, true] }); + expect(regexCaptureParticipation("(a)[", "")).toBeNull(); + expect(regexCaptureParticipation("(a", "")).toBeNull(); + expect(regexCaptureParticipation("a)", "")).toBeNull(); + expect(regexCaptureParticipation("(a)", "v")).toBeNull(); + }); + test("marks captures in alternations, optional groups and lookarounds", () => { + const always = (pattern: string): boolean[] | null => + regexCaptureParticipation(pattern, "")?.always ?? null; + expect(always("tag(\\d+)")).toEqual([true, true]); + expect(always("((a)b)+c")).toEqual([true, true, true]); + expect(always("(?\\d)(x)")).toEqual([true, true, true]); + expect(always("(a){2,3}")).toEqual([true, true]); + expect(always("(?:x|y)(a)")).toEqual([true, true]); + expect(always("(?:(a)|b)c")).toEqual([true, false]); + expect(always("(x)?(\\d)")).toEqual([true, false, true]); + expect(always("(?:(t)a)*g")).toEqual([true, false]); + expect(always("((a)?)")).toEqual([true, true, false]); + expect(always("(?<=(-))(\\w)")).toEqual([true, false, true]); + expect(always("[\\]|(](b)")).toEqual([true, true]); + expect(always("[^)](c)\\|")).toEqual([true, true]); + }); +}); diff --git a/packages/compiler/src/frontend/lowering/containers/replacer-params.ts b/packages/compiler/src/frontend/lowering/containers/replacer-params.ts new file mode 100644 index 000000000..e84ca1461 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/containers/replacer-params.ts @@ -0,0 +1,292 @@ +import * as ts from "../../ts7/adapter.js"; +import { F64, STRING, type IrExpr, type IrType } from "../../../ir/ir.js"; +import type { Lowerer } from "../lowerer.js"; +import { bindingNeverReassigned } from "../binding-analysis.js"; + +/** The captures of a regex literal's pattern (the whole match included) + * and, per capture, whether every successful match sets it. A capture may + * be unset when an enclosing group has alternatives, when an enclosing + * group or the capture itself is quantified with a minimum of zero, or + * when it sits inside a lookaround. Null for the `v` flag (nested + * classes) and for a pattern the scan does not finish. */ +export function regexCaptureParticipation( + pattern: string, + flags: string, +): { count: number; always: boolean[] } | null { + if (flags.includes("v")) return null; + const always: boolean[] = [true]; + /** Open groups: the group's own capture (-1 when it does not capture), + * the captures nested in it, whether it has alternatives, and whether it + * is a lookaround. */ + const frames: { own: number; nested: number[]; alternatives: boolean; lookaround: boolean }[] = [ + { own: -1, nested: [], alternatives: false, lookaround: false }, + ]; + let i = 0; + while (i < pattern.length) { + const c = pattern[i]!; + if (c === "\\") { + i += 2; + continue; + } + if (c === "[") { + // The first unescaped `]` ends the class (`[]` is an empty class). + i++; + while (i < pattern.length && pattern[i] !== "]") i += pattern[i] === "\\" ? 2 : 1; + if (i >= pattern.length) return null; + i++; + continue; + } + if (c === "|") { + frames[frames.length - 1]!.alternatives = true; + i++; + continue; + } + if (c === "(") { + let capture = false; + let lookaround = false; + if (pattern[i + 1] !== "?") { + capture = true; + i++; + } else if (pattern[i + 2] === "<" && pattern[i + 3] !== "=" && pattern[i + 3] !== "!") { + const gt = pattern.indexOf(">", i + 3); + if (gt < 0) return null; + capture = true; + i = gt + 1; + } else { + const kind = pattern[i + 2] === "<" ? pattern[i + 3] : pattern[i + 2]; + lookaround = kind === "=" || kind === "!"; + i += 2; + } + frames.push({ + own: capture ? always.length : -1, + nested: [], + alternatives: false, + lookaround, + }); + if (capture) always.push(true); + continue; + } + if (c === ")") { + if (frames.length === 1) return null; + const frame = frames.pop()!; + i++; + const next = pattern[i]; + const optional = + next === "?" || next === "*" || (next === "{" && /^\{0+[,}]/.test(pattern.slice(i))); + const parent = frames[frames.length - 1]!; + // Alternatives inside the group leave its nested captures unset, but + // not the group's own capture. + for (const index of frame.nested) { + if (optional || frame.lookaround || frame.alternatives) always[index] = false; + parent.nested.push(index); + } + if (frame.own >= 0) { + if (optional || frame.lookaround) always[frame.own] = false; + parent.nested.push(frame.own); + } + continue; + } + i++; + } + if (frames.length !== 1) return null; + if (frames[0]!.alternatives) for (const index of frames[0]!.nested) always[index] = false; + return { count: always.length, always }; +} + +/** String members a refined parameter may use: these lower from the + * receiver's type whatever the checker resolved for an `any` receiver. */ +const STRING_MEMBERS: ReadonlySet = new Set([ + "length", + "toUpperCase", + "toLowerCase", + "trim", + "charCodeAt", + "codePointAt", + "slice", + "substring", + "repeat", + "padStart", + "padEnd", + "includes", + "startsWith", + "endsWith", + "indexOf", +]); + +/** Whether every read of `symbol` in `body` is one that means the same + * for an `any` value and for a `kind` value, and lowers for both: a + * template span, a Number/String/parseInt/parseFloat argument, a listed + * string member, `+`, strict equality, arithmetic and comparisons of + * numbers, a returned value, and a string key of an element access on a + * string-indexed record or an `any` value. Anything else keeps the + * parameter `any`. */ +function readsFit( + lowerer: Lowerer, + body: ts.Node, + symbol: ts.Symbol, + kind: "string" | "f64", +): boolean { + const name = ts.symbolName(symbol); + const primitive = (e: ts.Expression): boolean => { + const flags = lowerer.checker.getTypeAtLocation(e).flags; + return (flags & (ts.TypeFlags.NumberLike | ts.TypeFlags.StringLike)) !== 0; + }; + const fits = (id: ts.Identifier): boolean => { + let node: ts.Node = id; + while (ts.isParenthesizedExpression(node.parent)) node = node.parent; + const parent = node.parent; + if (ts.isTemplateSpan(parent) || ts.isReturnStatement(parent)) return true; + if (ts.isElementAccessExpression(parent)) { + if (kind !== "string" || parent.argumentExpression !== node) return false; + const receiver = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(parent.expression)); + if (receiver?.kind === "dyn") return true; + const shape = receiver?.kind === "record" ? lowerer.shapes.get(receiver.shapeId) : undefined; + return shape?.indexValue !== undefined && !shape.tuple; + } + if (ts.isArrowFunction(parent)) return parent.body === node; + if (ts.isCallExpression(parent)) + return ( + parent.expression !== node && + ["Number", "String", "parseInt", "parseFloat"].some((global) => + lowerer.isStdlibGlobal(parent.expression, global), + ) + ); + if (ts.isPropertyAccessExpression(parent)) { + const member = parent.name.text; + if (kind !== "string" || parent.expression !== node || !STRING_MEMBERS.has(member)) + return false; + return ( + member === "length" || + (ts.isCallExpression(parent.parent) && parent.parent.expression === parent) + ); + } + if (ts.isBinaryExpression(parent)) { + const op = parent.operatorToken.kind; + if ( + op === ts.SyntaxKind.EqualsEqualsEqualsToken || + op === ts.SyntaxKind.ExclamationEqualsEqualsToken + ) + return true; + const other = parent.left === node ? parent.right : parent.left; + if (op === ts.SyntaxKind.PlusToken) return kind === "string" || primitive(other); + return ( + kind === "f64" && + (op === ts.SyntaxKind.MinusToken || + op === ts.SyntaxKind.AsteriskToken || + op === ts.SyntaxKind.SlashToken || + op === ts.SyntaxKind.PercentToken || + op === ts.SyntaxKind.LessThanToken || + op === ts.SyntaxKind.GreaterThanToken || + op === ts.SyntaxKind.LessThanEqualsToken || + op === ts.SyntaxKind.GreaterThanEqualsToken) && + (lowerer.checker.getTypeAtLocation(other).flags & ts.TypeFlags.NumberLike) !== 0 + ); + } + return false; + }; + let ok = true; + const visit = (n: ts.Node): void => { + if (!ok) return; + if (ts.isIdentifier(n) && n.text === name && lowerer.resolveValueSymbol(n) === symbol) { + if (!fits(n)) ok = false; + return; + } + ts.forEachChild(n, visit); + }; + visit(body); + return ok; +} + +/** The capture layout of a string search: the match, then the offset and + * the subject. */ +export const PLAIN_SEARCH_CAPTURES: { count: number; always: boolean[] } = { + count: 1, + always: [true], +}; + +/** The captures of a regex literal argument (regexCaptureParticipation), + * or null for any other pattern expression. */ +export function regexLiteralCapturesOf( + pattern: ts.Expression, +): { count: number; always: boolean[] } | null { + let regex = pattern; + while (ts.isParenthesizedExpression(regex)) regex = regex.expression; + if (!ts.isRegularExpressionLiteral(regex)) return null; + const text = regex.text; + const slash = text.lastIndexOf("/"); + return regexCaptureParticipation(text.slice(1, slash), text.slice(slash + 1)); +} + +/** Lowers an inline replace callback. JavaScript passes it the match, the + * captures, the match offset and the subject (`captures` gives their + * layout; null lowers the callback as written). TypeScript types the + * parameters after the match `any`, so an unannotated parameter that is + * never reassigned, whose every read fits its value (readsFit), and that + * receives a capture every match sets, the offset or the subject takes + * that value's type: a string or a number. The closure can then be called + * directly (typed replacers in lib-dispatch.ts) with fewer boxed values. */ +export function lowerReplacerCallback( + lowerer: Lowerer, + callback: ts.Expression, + captures: { count: number; always: boolean[] } | null, +): IrExpr { + let node = callback; + while (ts.isParenthesizedExpression(node)) node = node.expression; + // A named function expression can call itself with other arguments. + const literal = + ts.isArrowFunction(node) || (ts.isFunctionExpression(node) && !node.name) ? node : null; + if (!literal || !captures) return lowerer.lowerExpr(callback); + const bound: { symbol: ts.Symbol; previous: IrType | undefined }[] = []; + const previousImplicit = lowerer.implicitParamTypes; + let implicit: Map | null = null; + const params = literal.parameters.nodes; + // A declared `this` parameter takes no argument position. + const first = + params[0] && ts.isIdentifier(params[0].name) && params[0].name.text === "this" ? 1 : 0; + params.slice(first).forEach((param, i) => { + const type = + i === 0 || i > captures.count + 1 + ? null + : i < captures.count + ? captures.always[i] === true + ? STRING + : null + : i === captures.count + ? F64 + : STRING; + if ( + type === null || + !ts.isIdentifier(param.name) || + param.type || + param.initializer || + param.dotDotDotToken || + param.questionToken + ) + return; + if ((lowerer.checker.getTypeAtLocation(param.name).flags & ts.TypeFlags.Any) === 0) return; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if ( + !symbol || + !bindingNeverReassigned(lowerer, symbol, param) || + !readsFit(lowerer, literal.body, symbol, type === STRING ? "string" : "f64") + ) + return; + bound.push({ symbol, previous: lowerer.runtimeOptionalBindingTypes.get(symbol) }); + lowerer.runtimeOptionalBindingTypes.set(symbol, type); + implicit ??= new Map(previousImplicit ?? []); + implicit.set( + symbol, + type === STRING ? lowerer.checker.getStringType() : lowerer.checker.getNumberType(), + ); + }); + if (implicit !== null) lowerer.implicitParamTypes = implicit; + try { + return lowerer.lowerExpr(callback); + } finally { + for (const { symbol, previous } of bound) { + if (previous === undefined) lowerer.runtimeOptionalBindingTypes.delete(symbol); + else lowerer.runtimeOptionalBindingTypes.set(symbol, previous); + } + lowerer.implicitParamTypes = previousImplicit; + } +} diff --git a/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts b/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts index d55f71ff0..12885418b 100644 --- a/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts +++ b/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts @@ -33,6 +33,7 @@ import { isSafeToDiscard } from "../expressions/evaluation-safety.js"; import { STRING_INDEX_METHODS, STRING_REPLACE_METHODS, STR_METHODS } from "../surfaces.js"; import { execResultCaptureMask } from "./exec-result-uses.js"; import { lowerStringReplacement } from "./string-replacement.js"; +import { lowerReplacerCallback, regexLiteralCapturesOf } from "./replacer-params.js"; import { coerceStringSearchValue, defaultAfterUndefined, @@ -1025,7 +1026,11 @@ export function lowerRegexMethodCall( : [ lowerer.lowerExprExpecting(arg0, { kind: "regex" }), call.arguments.nodes[1] - ? lowerer.lowerExpr(call.arguments.nodes[1]) + ? lowerReplacerCallback( + lowerer, + call.arguments.nodes[1], + regexLiteralCapturesOf(arg0), + ) : dynUndefinedExpr(loc), ]; if (name !== "split") { diff --git a/packages/compiler/src/frontend/lowering/containers/string-replacement.ts b/packages/compiler/src/frontend/lowering/containers/string-replacement.ts index 156f74869..c21808464 100644 --- a/packages/compiler/src/frontend/lowering/containers/string-replacement.ts +++ b/packages/compiler/src/frontend/lowering/containers/string-replacement.ts @@ -18,6 +18,7 @@ import { import { dynUndefinedExpr, numLit, strLit, varRef } from "../../../ir/build.js"; import { InternalCompilerError } from "../../../errors.js"; import { locOf } from "../../program.js"; +import { PLAIN_SEARCH_CAPTURES, lowerReplacerCallback } from "./replacer-params.js"; import type { Lowerer } from "../lowerer.js"; import { coerceStringSearchValue, @@ -48,11 +49,18 @@ export function lowerStringReplacement( type.kind === "bool" || type.kind === "bigint" || isUnitType(type); - const argument = (node: ts.Expression | undefined, callback: boolean): IrExpr => { + const argument = ( + node: ts.Expression | undefined, + callback: boolean, + plainSearch = false, + ): IrExpr => { if (!node) return strLit("undefined", loc); const absent = lowerStaticallyUndefinedArgument(lowerer, node); if (absent) return defaultAfterUndefined(absent, strLit("undefined", loc)); - const value = lowerer.lowerExpr(node); + // A plain search passes (match, offset, subject). + const value = plainSearch + ? lowerReplacerCallback(lowerer, node, PLAIN_SEARCH_CAPTURES) + : lowerer.lowerExpr(node); if (value.type.kind === "dyn" || (callback && value.type.kind === "func")) return value; if (!primitive(value.type)) { lowerer.noLowering( @@ -64,7 +72,7 @@ export function lowerStringReplacement( return coerceStringSearchValue(lowerer, value, node, loc); }; const search = argument(args[0], false); - const replacement = argument(args[1], true); + const replacement = argument(args[1], true, search.type.kind !== "dyn"); if (search.type.kind === "dyn" || replacement.type.kind === "dyn") { // Evaluate all inputs once before choosing the search protocol. Regexes // retain their native pattern; plain values use the string-search helper. diff --git a/packages/compiler/test/replacer-params.test.ts b/packages/compiler/test/replacer-params.test.ts new file mode 100644 index 000000000..bb5549a86 --- /dev/null +++ b/packages/compiler/test/replacer-params.test.ts @@ -0,0 +1,84 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, test } from "vitest"; +import { compile } from "../src/index.js"; +import { regexLiteralCaptures } from "../src/backend/llvm/regex-matchers.js"; +import { regexCaptureParticipation } from "../src/frontend/lowering/containers/replacer-params.js"; + +// The frontend binds a replace callback parameter as a string only for a +// capture every match sets; the backend then calls the callback directly +// only when its own parse agrees. +describe("replace callback captures", () => { + test("never claims a capture the backend's parse can leave unset", () => { + const patterns = [ + "(\\w+)=(\\d)+", + "(?a)(b)?|(c)", + "x(?:(a)|(b))y(c)*(d){0,2}(e){1,3}", + "((a)(b)?)+", + "(a(b|c))d", + "(?:a|(b))(c)", + ]; + for (const pattern of patterns) { + const frontend = regexCaptureParticipation(pattern, ""); + const backend = regexLiteralCaptures(pattern, ""); + expect(frontend?.count).toBe(backend?.count); + frontend!.always.forEach((always, index) => { + if (always) expect(backend!.always[index]).toBe(true); + }); + } + }); +}); + +const directories: string[] = []; +afterEach(async () => { + await Promise.all(directories.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); +}); + +async function emit(source: string): Promise { + const outDir = await mkdtemp(join(tmpdir(), "scriptc-replacer-")); + directories.push(outDir); + const entry = join(outDir, "main.ts"); + await writeFile(entry, source); + const outPath = join(outDir, "program.ll"); + const result = await compile(entry, { entry, outDir, outPath, outputKind: "llvm" }); + expect(result.ok, JSON.stringify(result)).toBe(true); + return readFile(outPath, "utf8"); +} + +/** The (arity, kinds) arguments of each direct replacer call, and the + * number of calls through the boxed argument array. */ +function replacerCalls(llvm: string): { direct: string[]; boxed: number } { + const lines = llvm.split("\n").filter((line) => line.includes("call ")); + return { + direct: lines + .filter((line) => line.includes("@scr_regex_replace_fn(")) + .map((line) => /i32 (-?\d+), i32 (-?\d+), i1/.exec(line)!.slice(1, 3).join(":")), + boxed: lines.filter((line) => line.includes("@scr_regex_replace_callback(")).length, + }; +} + +describe("replace callback calls", () => { + test("unannotated parameters take the kinds the replace passes", async () => { + const llvm = await emit(` +const vars: Record = { a: "1" }; +const text = String(Date.now() > 0 ? "{a} {b}" : ""); +console.log(text.replace(/\\{(\\w+)\\}/g, (_m, key) => vars[key] ?? "")); +console.log(text.replace(/\\{(\\w)/g, (_m, c) => c.toUpperCase())); +console.log(text.replace(/(a)|(b)/g, (m, a, b) => \`\${m}\${a}\${b}\`)); +console.log(text.replace(/(a)(b)?/g, (m: string, a: string, b: string | undefined, at: number) => m + a + (b ?? "") + at)); +const half = (n: number): number => n / 2; +console.log(text.replace(/b/g, (m, at) => String(half(at)))); +console.log(text.replace(/(a)(b)(c)/g, (m, a, b, c, at, all, extra) => m + a + b + c + at + all + extra)); +`); + expect(replacerCalls(llvm)).toEqual({ + // Typed parameters and result (0); a checked-dynamic result (bit 31); + // two captures a match can leave unset (bits 1 and 2); + // \`string | undefined\` at capture 2 (bit 8 + 2); an offset passed to a + // number parameter keeps its checked-dynamic box (bit 1). + direct: ["2:0", "2:-2147483648", "3:6", "4:1024", "2:2"], + // Seven parameters exceed the direct calls' limit. + boxed: 1, + }); + }); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8118-replacer-params.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8118-replacer-params.ts.json new file mode 100644 index 000000000..4de6ead6b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8118-replacer-params.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8118-replacer-params.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8345-replace-callback-untyped-params.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8345-replace-callback-untyped-params.ts.json new file mode 100644 index 000000000..9a4c2b510 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8345-replace-callback-untyped-params.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8345-replace-callback-untyped-params.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8346-replace-callback-untyped-params.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8346-replace-callback-untyped-params.mjs.json new file mode 100644 index 000000000..301203783 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8346-replace-callback-untyped-params.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8346-replace-callback-untyped-params.mjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_regex.c b/packages/runtime/src/scr_regex.c index 158697a14..ab323c288 100644 --- a/packages/runtime/src/scr_regex.c +++ b/packages/runtime/src/scr_regex.c @@ -1529,49 +1529,101 @@ ScrStr *scr_regex_replace_callback(ScrStr *s, ScrRegex *re, ScrDyn *callback, bo return scr_jb_finish(&output); } -/* One call of a typed replacer (scr_regex_replace_fn's contract): string - * arguments are passed +1 (the closure ABI's owned parameters), the offset - * as a number, and the result is the closure's +1 string. */ -static ScrStr *scr_regex_call_replacer(ScrClosure *f, uint32_t arity, int ncap, - ScrStr *const *caps, double offset, ScrStr *subject) { - void *p[4] = { NULL, NULL, NULL, NULL }; - for (uint32_t i = 0; i < arity && i < 4; i++) { - if ((int)i < ncap) { - if (!caps[i]) scr_trap("scriptc: internal error: typed replacer capture is unset\n"); - p[i] = scr_str_retain(caps[i]); - } else if ((int)i == ncap + 1) { - p[i] = scr_str_retain(subject); - } - } +/* The closure signature scr_regex_replace_fn calls: `arity` pointer + * parameters, except a number at `number_at` (-1 when there is none). A + * string and a checked-dynamic value are both pointers, and so are both + * results. */ +#define SCR_REPLACER_CALL(...) return ((void *(*)(ScrClosure *, __VA_ARGS__))fn) +static inline __attribute__((always_inline)) void *scr_regex_call_replacer( + ScrClosure *f, uint32_t arity, int number_at, void *const *p, double offset) { void *fn = f->fn; - typedef ScrStr *R; - switch (arity) { - case 0: return ((R (*)(ScrClosure *))fn)(f); - case 1: return ((R (*)(ScrClosure *, void *))fn)(f, p[0]); - case 2: - if (ncap == 1) return ((R (*)(ScrClosure *, void *, double))fn)(f, p[0], offset); - return ((R (*)(ScrClosure *, void *, void *))fn)(f, p[0], p[1]); - case 3: - if (ncap == 1) return ((R (*)(ScrClosure *, void *, double, void *))fn)(f, p[0], offset, p[2]); - if (ncap == 2) return ((R (*)(ScrClosure *, void *, void *, double))fn)(f, p[0], p[1], offset); - return ((R (*)(ScrClosure *, void *, void *, void *))fn)(f, p[0], p[1], p[2]); - case 4: - if (ncap == 2) - return ((R (*)(ScrClosure *, void *, void *, double, void *))fn)(f, p[0], p[1], offset, p[3]); - if (ncap == 3) - return ((R (*)(ScrClosure *, void *, void *, void *, double))fn)(f, p[0], p[1], p[2], offset); - if (ncap >= 4) - return ((R (*)(ScrClosure *, void *, void *, void *, void *))fn)(f, p[0], p[1], p[2], p[3]); - break; + typedef void *P; + typedef double D; + switch (arity * 8 + (uint32_t)(number_at < 0 ? 7 : number_at)) { + case 0 * 8 + 7: return ((void *(*)(ScrClosure *))fn)(f); + case 1 * 8 + 7: SCR_REPLACER_CALL(P)(f, p[0]); + case 2 * 8 + 7: SCR_REPLACER_CALL(P, P)(f, p[0], p[1]); + case 2 * 8 + 1: SCR_REPLACER_CALL(P, D)(f, p[0], offset); + case 3 * 8 + 7: SCR_REPLACER_CALL(P, P, P)(f, p[0], p[1], p[2]); + case 3 * 8 + 1: SCR_REPLACER_CALL(P, D, P)(f, p[0], offset, p[2]); + case 3 * 8 + 2: SCR_REPLACER_CALL(P, P, D)(f, p[0], p[1], offset); + case 4 * 8 + 7: SCR_REPLACER_CALL(P, P, P, P)(f, p[0], p[1], p[2], p[3]); + case 4 * 8 + 1: SCR_REPLACER_CALL(P, D, P, P)(f, p[0], offset, p[2], p[3]); + case 4 * 8 + 2: SCR_REPLACER_CALL(P, P, D, P)(f, p[0], p[1], offset, p[3]); + case 4 * 8 + 3: SCR_REPLACER_CALL(P, P, P, D)(f, p[0], p[1], p[2], offset); + case 5 * 8 + 7: SCR_REPLACER_CALL(P, P, P, P, P)(f, p[0], p[1], p[2], p[3], p[4]); + case 5 * 8 + 1: SCR_REPLACER_CALL(P, D, P, P, P)(f, p[0], offset, p[2], p[3], p[4]); + case 5 * 8 + 2: SCR_REPLACER_CALL(P, P, D, P, P)(f, p[0], p[1], offset, p[3], p[4]); + case 5 * 8 + 3: SCR_REPLACER_CALL(P, P, P, D, P)(f, p[0], p[1], p[2], offset, p[4]); + case 5 * 8 + 4: SCR_REPLACER_CALL(P, P, P, P, D)(f, p[0], p[1], p[2], p[3], offset); + case 6 * 8 + 7: SCR_REPLACER_CALL(P, P, P, P, P, P)(f, p[0], p[1], p[2], p[3], p[4], p[5]); + case 6 * 8 + 1: SCR_REPLACER_CALL(P, D, P, P, P, P)(f, p[0], offset, p[2], p[3], p[4], p[5]); + case 6 * 8 + 2: SCR_REPLACER_CALL(P, P, D, P, P, P)(f, p[0], p[1], offset, p[3], p[4], p[5]); + case 6 * 8 + 3: SCR_REPLACER_CALL(P, P, P, D, P, P)(f, p[0], p[1], p[2], offset, p[4], p[5]); + case 6 * 8 + 4: SCR_REPLACER_CALL(P, P, P, P, D, P)(f, p[0], p[1], p[2], p[3], offset, p[5]); + case 6 * 8 + 5: SCR_REPLACER_CALL(P, P, P, P, P, D)(f, p[0], p[1], p[2], p[3], p[4], offset); default: break; } scr_trap("scriptc: internal error: typed replacer signature\n"); } +#undef SCR_REPLACER_CALL + +/* Typed replacer argument k: a capture (NULL for an unset optional one) + * or the subject; the offset is passed separately. The compiler admits a + * typed parameter only at those positions. Inlined with each constant k. */ +static inline __attribute__((always_inline)) void scr_regex_typed_arg( + void **p, uint32_t k, uint32_t kinds, int capture_count, ScrStr *const *caps, ScrStr *subject) { + if ((int)k < capture_count) { + if (!caps[k] && !(kinds & (SCR_REPLACER_OPTIONAL << k))) + scr_trap("scriptc: internal error: typed replacer capture is unset\n"); + p[k] = caps[k] ? scr_str_retain(caps[k]) : NULL; + } else if ((int)k == capture_count + 1) { + p[k] = scr_str_retain(subject); + } +} + +/* One match's replacer arguments when some parameter is checked-dynamic + * (scr_regex_replace_fn's `kinds`): typed ones +1, the rest new boxes. + * `boxed_subject` is the subject's box; `groups` the match's groups + * object, or NULL when no parameter reads it. */ +__attribute__((noinline)) static void scr_regex_replacer_args( + void **p, uint32_t arity, uint32_t kinds, int capture_count, ScrStr *const *caps, double offset, + ScrStr *subject, ScrDyn *boxed_subject, ScrDyn *groups) { + for (uint32_t k = 0; k < arity; k++) { + int at = (int)k; + if (kinds & (1u << k)) { + if (at < capture_count) + p[k] = caps[k] ? scr_dyn_new_str(caps[k]) : scr_dyn_retain(scr_dyn_undefined()); + else if (at == capture_count) + p[k] = scr_dyn_new_num(offset); + else if (at == capture_count + 1) + p[k] = scr_dyn_retain(boxed_subject); + else if (at == capture_count + 2 && groups) + p[k] = scr_dyn_retain(groups); + else + p[k] = scr_dyn_retain(scr_dyn_undefined()); + } else if (at < capture_count) { + if (!caps[k] && !(kinds & (SCR_REPLACER_OPTIONAL << k))) + scr_trap("scriptc: internal error: typed replacer capture is unset\n"); + p[k] = caps[k] ? scr_str_retain(caps[k]) : NULL; + } else if (at == capture_count + 1) { + p[k] = scr_str_retain(subject); + } else if (at != capture_count) { + scr_trap("scriptc: internal error: typed replacer signature\n"); + } + } +} /* scr_regex_replace_callback's algorithm for a typed replacer: the matches - * are collected first, keeping only the captures the replacer reads, then - * the replacer runs once per match in order. */ -ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t arity, bool all) { + * are collected first, keeping only the captures and groups objects the + * replacer reads, then the replacer runs once per match in order. Typed + * parameters receive their values +1 (the closure ABI's owned + * parameters) and an unset optional capture NULL; checked-dynamic ones + * receive new boxes. Inlined into one copy for kinds 0, where the boxed + * handling folds away, and one for the other kinds. */ +static inline __attribute__((always_inline)) ScrStr * +scr_regex_replace_fn_impl(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t arity, uint32_t kinds, + bool all) { uint8_t *bc = scr_regex_bc(re); int flags = lre_get_flags(bc); bool global = (flags & LRE_FLAG_GLOBAL) != 0; @@ -1580,15 +1632,28 @@ ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t ar scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); return NULL; } + if (arity > SCR_REPLACER_MAX_ARITY) scr_trap("scriptc: internal error: typed replacer arity\n"); bool sticky = (flags & LRE_FLAG_STICKY) != 0; bool unicode = (flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0; int capture_count = lre_get_capture_count(bc); size_t keep = arity < (uint32_t)capture_count ? arity : (size_t)capture_count; + /* Node passes the groups object after the subject when the pattern has + * named groups; only a checked-dynamic parameter can read it, and no + * typed parameter follows the subject. */ + bool past_subject = (uint32_t)capture_count + 2 < arity; + if (past_subject && !(kinds & SCR_REPLACER_BOXED_PARAMS)) + scr_trap("scriptc: internal error: typed replacer signature\n"); + const char *groupnames = past_subject ? lre_get_groupnames(bc) : NULL; + bool groups_read = groupnames != NULL; + if (groups_read && !(kinds & (1u << (capture_count + 2)))) + scr_trap("scriptc: internal error: typed replacer groups parameter\n"); + int number_at = capture_count < (int)arity && !(kinds & (1u << capture_count)) ? capture_count : -1; ScrRegexInput input; scr_regex_input_init(&input, re, bc, s); int len = input.len; struct { int start, end; } *matches = NULL; ScrStr **caps = NULL; + ScrDyn **groups = NULL; size_t count = 0, capacity = 0; if (global) re->last_index = 0; int position = sticky ? scr_regex_start(re, len) : 0; @@ -1610,6 +1675,11 @@ ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t ar if (!grown) scr_regex_oom(); caps = grown; } + if (groups_read) { + grown = realloc(groups, next * sizeof(*groups)); + if (!grown) scr_regex_oom(); + groups = grown; + } capacity = next; } for (size_t group = 0; group < keep; group++) { @@ -1618,6 +1688,18 @@ ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t ar cs && ce ? scr_regex_span(&input, scr_capture_index(&input, cs), scr_capture_index(&input, ce)) : NULL; } + if (groups_read) { + ScrDyn *object = scr_dyn_new_obj_null_proto(); + const char *name = groupnames; + for (int group = 1; group < capture_count; group++) { + size_t length = strlen(name); + if (length && (capture[group * 2] || !scr_dyn_obj_get(object, name, length))) + scr_dyn_obj_set(object, name, length, + scr_regex_capture_value(&input, capture[group * 2], capture[group * 2 + 1])); + name += length + LRE_GROUP_NAME_TRAILER_LEN; + } + groups[count] = object; + } matches[count].start = start; matches[count].end = end; count++; @@ -1627,18 +1709,43 @@ ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t ar ScrJsonBuf output; scr_jb_init(&output); int next = 0; + /* One box of the subject serves every match. */ + ScrDyn *subject = NULL; + if (kinds & SCR_REPLACER_BOXED_PARAMS) subject = scr_dyn_new_str(s); scr_dyn_this_push_dyn(NULL); for (size_t i = 0; i < count; i++) { - ScrStr *replacement = scr_regex_call_replacer(f, arity, capture_count, caps + i * keep, - (double)matches[i].start, s); - if (scr_exc_pending()) break; + void *p[SCR_REPLACER_MAX_ARITY] = { NULL }; + double offset = (double)matches[i].start; + ScrStr *const *match_caps = caps + i * keep; + if (kinds & SCR_REPLACER_BOXED_PARAMS) { + scr_regex_replacer_args(p, arity, kinds, capture_count, match_caps, offset, s, subject, + groups_read ? groups[i] : NULL); + } else { + for (uint32_t k = 0; k < SCR_REPLACER_MAX_ARITY; k++) + if (k < arity) scr_regex_typed_arg(p, k, kinds, capture_count, match_caps, s); + } + void *result = scr_regex_call_replacer(f, arity, number_at, p, offset); + ScrStr *replacement = (ScrStr *)result; + if (kinds & SCR_REPLACER_BOXED_RESULT) { + replacement = scr_exc_pending() ? NULL : scr_dyn_string_coerce_js((ScrDyn *)result); + scr_dyn_release((ScrDyn *)result); + } + if (scr_exc_pending()) { + scr_str_release(replacement); + break; + } scr_regex_put_span(&output, &input, next, matches[i].start); scr_jb_put_str(&output, replacement); scr_str_release(replacement); next = matches[i].end; } scr_dyn_this_pop(); + if (subject) scr_dyn_release(subject); for (size_t i = 0; i < count * keep; i++) scr_str_release(caps[i]); + if (groups_read) { + for (size_t i = 0; i < count; i++) scr_dyn_release(groups[i]); + free(groups); + } free(caps); free(matches); if (scr_exc_pending()) { @@ -1651,6 +1758,24 @@ ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t ar return scr_jb_finish(&output); } +__attribute__((noinline)) static ScrStr *scr_regex_replace_typed(ScrStr *s, ScrRegex *re, + ScrClosure *f, uint32_t arity, + bool all) { + return scr_regex_replace_fn_impl(s, re, f, arity, 0, all); +} + +__attribute__((noinline)) static ScrStr *scr_regex_replace_kinds(ScrStr *s, ScrRegex *re, + ScrClosure *f, uint32_t arity, + uint32_t kinds, bool all) { + return scr_regex_replace_fn_impl(s, re, f, arity, kinds, all); +} + +ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t arity, + uint32_t kinds, bool all) { + if (kinds == 0) return scr_regex_replace_typed(s, re, f, arity, all); + return scr_regex_replace_kinds(s, re, f, arity, kinds, all); +} + /* The pinned Node ANSI matcher derives from ansi-regex, copyright (c) * Sindre Sorhus , used under the MIT license: * Permission is hereby granted, free of charge, to any person obtaining a diff --git a/packages/runtime/src/scr_regex.h b/packages/runtime/src/scr_regex.h index 01c545bc5..cb61e49f3 100644 --- a/packages/runtime/src/scr_regex.h +++ b/packages/runtime/src/scr_regex.h @@ -80,16 +80,26 @@ ScrStr *scr_regex_flags(ScrRegex *re); /* +1 */ ScrStr *scr_regex_to_string(ScrRegex *re); /* +1 */ /* replace: first match without /g, every match with it. */ ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep); -/* replace/replaceAll with a typed replacer closure of `arity` (at most 4) - * parameters returning a string. Parameter i receives what Node passes at - * position i: the match and captures (strings) below the capture count n, - * the match offset (a number) at n, the subject (a string) at n + 1. The - * compiler admits a closure only when every position it declares is one of - * those with the declared kind, and every capture it reads is set in every - * match (scr_regex_replace_callback handles the rest). Borrows all. */ +/* replace/replaceAll with a replacer closure of `arity` (at most + * SCR_REPLACER_MAX_ARITY) parameters. Parameter i receives what Node passes + * at position i: the match and captures below the capture count n, the + * match offset at n, the subject at n + 1, the groups object at n + 2 when + * the pattern has named groups, and undefined after that. Bit i of + * `kinds` makes parameter i a checked-dynamic value, and bit + * SCR_REPLACER_OPTIONAL << i a capture held as a string or NULL for + * undefined; otherwise it is a string, or a number at n. + * SCR_REPLACER_BOXED_RESULT makes the result a checked-dynamic value, + * converted with ToString. The compiler admits a closure only when every + * typed position it declares is one of those with the declared kind, and + * every capture a string parameter reads is set in every match + * (scr_regex_replace_callback handles the rest). Borrows all. */ +#define SCR_REPLACER_MAX_ARITY 6 +#define SCR_REPLACER_BOXED_PARAMS 0xffu +#define SCR_REPLACER_OPTIONAL (1u << 8) +#define SCR_REPLACER_BOXED_RESULT (1u << 31) struct ScrClosure; ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, struct ScrClosure *f, uint32_t arity, - bool all); + uint32_t kinds, bool all); /* replaceAll: throws Node's TypeError when /g is missing (may-throw). */ ScrStr *scr_regex_replace_all(ScrStr *s, ScrRegex *re, ScrStr *rep); /* split: captured substrings are interleaved with the split pieces; diff --git a/tests/corpus/8118-replacer-params.ts b/tests/corpus/8118-replacer-params.ts new file mode 100644 index 000000000..14579ec84 --- /dev/null +++ b/tests/corpus/8118-replacer-params.ts @@ -0,0 +1,43 @@ +// Inline replace callbacks whose parameters after the match are left +// untyped: captures every match sets, captures that can be unset (in an +// alternation, an optional or zero-repeat group, a lookahead), the offset, +// the subject and the groups object, reassigned parameters, named groups, +// patterns with classes, escapes and lookbehind, function expressions and +// replaceAll. +const lines = ["id7,tag12;tag3", "x9 y10", "", "naïve 😀 42", "a-b-c"]; +for (const line of lines) { + console.log(line.replace(/tag(\d+)/g, (_m, d) => String(Number(d) + 1))); + console.log(line.replace(/(\w)(\d+)/g, (m, letter, digits, offset, subject) => + `[${letter}:${digits.length}@${offset}/${subject.length}]`)); + console.log(line.replace(/(a)|(\d)/g, (m, a, digit) => `<${typeof a},${typeof digit}>`)); + console.log(line.replace(/(x)?(\d)/g, (m, x, d) => `${x === undefined ? "-" : x}${d}`)); + console.log(line.replace(/(?:(t)a)*g/g, (m, t, offset) => `{${t}|${offset}}`)); + console.log(line.replace(/(?=(\d))\d/g, (m, ahead, offset) => `${ahead}${offset}`)); + console.log(line.replace(/(?\d)(\d)?/g, (m, n, rest, offset, subject, groups) => + `${n}${rest ?? "_"}${offset}${typeof subject}${JSON.stringify(groups)}`)); + console.log(line.replace(/([\(\)\[\]]|[a-c])(-)/g, (m, c, dash) => c.toUpperCase() + dash.length)); + console.log(line.replace(/(?<=-)(\w)/g, (m, c, offset) => c + offset.toFixed(1))); + console.log(line.replaceAll(/(\d)/g, function (m, d, offset) { + return `${d * 2}:${offset + 1}`; + })); + console.log(line.replace(/(\d)/g, (m, d) => { + d = d.length > 0 ? 5 : 6; + return String(d + 1); + })); + console.log(line.replace(/(\d)\1?/g, (m, d, offset, subject) => `${d}${offset}${subject === line}`)); + console.log(line.replace(/(.)/u, (m, c, offset, subject, extra) => `${c.codePointAt(0)}${offset}${extra}`)); + console.log(line.replace(/(\d){0,2}x/g, (m, d) => `${d}`)); + console.log(line.replace(/(\w)(\w)/g, (m, a, b, offset) => + [a.toUpperCase(), b.toLowerCase(), a.trim(), a.charCodeAt(0), b.codePointAt(0), a.slice(0, 1), + b.substring(0), a.repeat(2), b.padStart(3, "."), a.padEnd(2, "!"), b.includes("1"), + a.startsWith("t"), b.endsWith("g"), a.indexOf("a"), a.length, parseInt(b, 36), + parseFloat(b), Number(b), String(offset), offset - 1, offset * 2, offset % 3, offset < 4, + a === b, a !== "x", a + offset, offset + 1, `${offset}`].join(","))); + console.log(line.replace(/(\d+)/g, (m, d) => (d))); + console.log(line.replace(/(\d+)/g, function (m, d) { return d + d; })); +} +let count = 0; +console.log("tag1 tag2".replace(/tag(\d)/g, (m, d) => { + count += Number(d); + return d.repeat(2); +}), count); diff --git a/tests/corpus/8345-replace-callback-untyped-params.ts b/tests/corpus/8345-replace-callback-untyped-params.ts new file mode 100644 index 000000000..31d3a038b --- /dev/null +++ b/tests/corpus/8345-replace-callback-untyped-params.ts @@ -0,0 +1,85 @@ +// String replace/replaceAll with inline callbacks whose parameters have no +// annotations, so the lib types every parameter after the match `any`. +// Each such parameter holds what Node passes at its position: the captures, +// the offset, the subject, the groups object and then undefined. Captures a +// match can leave unset hold undefined, and a result that is not a string +// converts with ToString. +const vars: Record = { name: "world", place: "earth" }; +const template = "Hello {name}, welcome to {place}! {missing} {name}."; +console.log(template.replace(/\{(\w+)\}/g, (_m, key) => vars[key] ?? `<${key}>`)); +console.log(template.replace(/\{(\w+)\}/, (_m, key) => key.toUpperCase())); +console.log("some_snake_case_name".replace(/_([a-z])/g, (_m, c) => c.toUpperCase())); +console.log("2024-01-15 and 1999-07-04".replace(/(\d{4})-(\d{2})-(\d{2})/g, (_m, y, mo, d) => `${d}/${mo}/${y}`)); +console.log("a1b22".replace(/\d+/g, (m, offset, subject) => `[${m}@${offset}/${subject.length}]`)); +console.log("x1y2".replace(/(\d)/g, (m, digit, offset, subject) => `${m}${digit}${offset}${subject}`)); +console.log("k=v".replace(/(\w)=(\w)/, function (m, k, v, at, all) { + return `${v}=${k}:${at}:${all}:${m}`; +})); + +console.log("k=v".replace(/(\w)=(\w)/, function (this: void, m, k, v, at) { + return `${typeof this}:${v}=${k}:${at}:${m}`; +})); + +// A parameter the body assigns keeps `any`. +console.log("a1".replace(/(\d)/, (_m, d) => { + d = Number(d) + 1; + return String(d * 2); +})); + +// Captures that a match can leave unset. +console.log("ab".replace(/(a)|(b)/g, (m, a, b) => `${m}:${a}:${b};`)); +console.log("b xb".replace(/(x)?b/g, (m, x, at) => `${m}:${x}:${at}`)); +console.log("ab".replace(/((a)|b)/g, (m, outer, inner) => `${outer}:${inner};`)); +console.log("ab".replace(/(?:(a)|b)/g, (m, a, at) => `${m}:${a}:${at};`)); +console.log("aab".replace(/(a){0,2}b/, (m, a, at) => `${m}:${a}:${at}`)); +console.log("ab".replace(/(?=(a))a|b/g, (m, look, at) => `${m}:${look}:${at};`)); +console.log("ab".replace(/(?!(x))a/g, (m, x, at) => `${m}:${x}:${at};`)); +console.log("ab".replace(/(a)*b/, (m, a, at) => `${m}:${a}:${at}`)); +console.log("a(b)".replace(/\((b)\)/, (m, b, at) => `${m}:${b}:${at}`)); +console.log("a|b".replace(/[|(](b)?/, (m, b, at) => `${m}:${b}:${at}`)); + +// Annotated `string | undefined` captures hold undefined when unset. +console.log("2024-01-15 1999-07".replace(/(\d{4})-(\d{2})(-(\d{2}))?/g, (_m, y: string, mo: string, _x: string | undefined, d: string | undefined) => `${d ?? "??"}/${mo}/${y}`)); +console.log("ab".replace(/(a)|(b)/g, (m: string, a: string | undefined, b: string | undefined, at: number) => `${m}:${a ?? "-"}:${b ?? "-"}:${at};`)); +console.log("abc".replace(/(a)(b)(c)/, (m: string, a: string, b: string, c: string, at: number, all: string) => `${c}${b}${a}${m}${at}${all}`)); +console.log("abcd".replace(/(a)(b)(c)(d)/, (m, a, b, c, d, at, all) => `${d}${c}${b}${a}${m}${at}${all}`)); + +// Named groups pass the groups object after the subject; later parameters +// are undefined. +console.log("2024-01".replace(/(?\d{4})-(?\d{2})/, (m, y, mo, at, all, groups) => `${groups.m}.${groups.y}:${y}${mo}:${at}:${all}`)); +console.log("ab".replace(/(?a)|(?b)/g, (m, a, b, at, all, groups) => `${m}:${JSON.stringify(groups)};`)); +console.log("ab".replace(/b/, (m, at, all, extra, more) => `${m}:${at}:${all}:${extra}:${more}`)); +console.log("ab".replace(/(a)(b)/, (m, a, b, at, all, extra, more) => `${m}${a}${b}${at}${all}${extra}${more}`)); + +// Results that are not strings convert with ToString. +const counts: Record = { a: 1, b: true, c: null, d: undefined, e: { toString: () => "E" } }; +console.log("a b c d e".replace(/\w/g, (m) => counts[m])); +console.log("abc".replace(/(\w)/g, (m, c) => counts[c])); + +// String search patterns pass (match, offset, subject). +console.log("one two one".replaceAll("one", (m, at, subject) => `${m.length}@${at}/${subject.length}`)); +console.log("one two one".replace("two", (m, at, subject, extra) => `${m}${at}${subject.length}${extra}`)); + +// A throw stops the replacement. +try { + "a1b2c3".replace(/(\d)/g, (_m, d) => { + if (d === "2") throw new Error("stop at " + d); + return "#"; + }); +} catch (error) { + console.log((error as Error).message); +} +try { + "a1".replace(/(\d)/g, (_m, d) => counts.e.missing.field + d); +} catch (error) { + console.log(error instanceof TypeError); +} +try { + "ab".replace(/(a)|b/g, (m, a) => a.toUpperCase()); +} catch (error) { + console.log(error instanceof TypeError, (error as Error).message); +} + +let total = 0; +for (let i = 0; i < 2000; i++) total += template.replace(/\{(\w+)\}/g, (_m, key) => vars[key] ?? String(i)).length; +console.log(total); diff --git a/tests/corpus/8346-replace-callback-untyped-params.mjs b/tests/corpus/8346-replace-callback-untyped-params.mjs new file mode 100644 index 000000000..2552113f3 --- /dev/null +++ b/tests/corpus/8346-replace-callback-untyped-params.mjs @@ -0,0 +1,32 @@ +// JavaScript string replace/replaceAll with inline callbacks. Each +// parameter holds what Node passes at its position: the captures, the +// offset, the subject, the groups object and then undefined. Captures a +// match can leave unset hold undefined, and a result that is not a string +// converts with ToString. +const vars = { name: "world", place: "earth" }; +const template = "Hello {name}, welcome to {place}! {missing} {name}."; +console.log(template.replace(/\{(\w+)\}/g, (_m, key) => vars[key] ?? `<${key}>`)); +console.log("some_snake_case_name".replace(/_([a-z])/g, (_m, c) => c.toUpperCase())); +console.log("2024-01-15 and 1999-07-04".replace(/(\d{4})-(\d{2})-(\d{2})/g, (_m, y, mo, d) => `${d}/${mo}/${y}`)); +console.log("a1b22".replace(/\d+/g, (m, offset, subject) => `[${m}@${offset}/${subject.length}]`)); +console.log("k=v".replace(/(\w)=(\w)/, function (m, k, v, at, all) { + return `${this === undefined}:${v}=${k}:${at}:${all}:${m}`; +})); +console.log("ab".replace(/(a)|(b)/g, (m, a, b) => `${m}:${a}:${b};`)); +console.log("b xb".replace(/(x)?b/g, (m, x, at) => `${m}:${x}:${at}`)); +console.log("2024-01".replace(/(?\d{4})-(?\d{2})/, (m, y, mo, at, all, groups) => `${groups.m}.${groups.y}:${y}${mo}:${at}:${all}`)); +console.log("ab".replace(/b/, (m, at, all, extra, more) => `${m}:${at}:${all}:${extra}:${more}`)); +const counts = { a: 1, b: true, c: null, d: undefined, e: { toString: () => "E" } }; +console.log("a b c d e".replace(/\w/g, (m) => counts[m])); +console.log("1-2".replace(/(\d)/g, (_m, d) => Number(d) * 10)); +console.log("one two one".replaceAll("one", (m, at, subject) => `${m.length}@${at}/${subject.length}`)); +let seen = ""; +console.log("xyz".replace(/(y)/, (m, y, at, subject) => { + seen = typeof m + typeof y + typeof at + typeof subject; + return y + at; +}), seen); +try { + "ab".replace(/(a)|b/g, (m, a) => a.toUpperCase()); +} catch (error) { + console.log(error instanceof TypeError, error.message); +} diff --git a/tests/harness/corpus-costs/8118-replacer-params.ts.json b/tests/harness/corpus-costs/8118-replacer-params.ts.json new file mode 100644 index 000000000..9b0bcec50 --- /dev/null +++ b/tests/harness/corpus-costs/8118-replacer-params.ts.json @@ -0,0 +1 @@ +{ "plain": 3670, "san": 4410 } diff --git a/tests/harness/corpus-costs/8345-replace-callback-untyped-params.ts.json b/tests/harness/corpus-costs/8345-replace-callback-untyped-params.ts.json new file mode 100644 index 000000000..5d5bbcefe --- /dev/null +++ b/tests/harness/corpus-costs/8345-replace-callback-untyped-params.ts.json @@ -0,0 +1 @@ +{ "plain": 3630, "san": 4420 } diff --git a/tests/harness/corpus-costs/8346-replace-callback-untyped-params.mjs.json b/tests/harness/corpus-costs/8346-replace-callback-untyped-params.mjs.json new file mode 100644 index 000000000..cdddf6d76 --- /dev/null +++ b/tests/harness/corpus-costs/8346-replace-callback-untyped-params.mjs.json @@ -0,0 +1 @@ +{ "plain": 2880, "san": 5080 } From ceb29078dc763bb0ccad928bb563ee90963d1b8c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:31:51 -0500 Subject: [PATCH 059/230] Skip comments when renaming a range's debug ids --- .../compiler/src/backend/llvm/emit-journal.ts | 47 ++++++++++++++---- .../compiler/test/chunked-emission.test.ts | 49 +++++++++++++++++++ 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/packages/compiler/src/backend/llvm/emit-journal.ts b/packages/compiler/src/backend/llvm/emit-journal.ts index d6fed03a6..b2b3fc4cc 100644 --- a/packages/compiler/src/backend/llvm/emit-journal.ts +++ b/packages/compiler/src/backend/llvm/emit-journal.ts @@ -175,22 +175,49 @@ export function renameChunk( }; } -/** Renames metadata references `!N` outside string literals. Debug builds - * attach no alias tags, so every such reference in a function's text is a - * debug metadata id. */ +/** Renames metadata references `!N` outside string literals and comments. + * Debug builds attach no alias tags, so every such reference in a + * function's text is a debug metadata id. A string literal or a comment + * (`;` outside a string literal) ends at the end of its line; a quote + * inside a string literal is escaped (`\22`). */ function renameDebugIds(text: string, ids: ReadonlyMap): string { const parts: string[] = []; let copied = 0; - // The next quote at or after the scan, and whether the scan is inside a - // string literal. - let quote = text.indexOf('"'); + // The line of the reference being looked at, and whether the reference + // is inside a string literal or a comment there. The next quote and + // semicolon positions only move forward, so each is found once. + let lineEnd = -1; let quoted = false; + let comment = false; + let quote = text.indexOf('"'); + let semicolon = text.indexOf(";"); for (let at = text.indexOf("!"); at >= 0; at = text.indexOf("!", at + 1)) { - while (quote >= 0 && quote < at) { - quoted = !quoted; - quote = text.indexOf('"', quote + 1); + if (at > lineEnd) { + lineEnd = text.indexOf("\n", at); + if (lineEnd < 0) lineEnd = text.length; + quoted = false; + comment = false; + // Quotes and semicolons of earlier lines end with them. + if ((quote >= 0 && quote < at) || (semicolon >= 0 && semicolon < at)) { + const lineStart = text.lastIndexOf("\n", at) + 1; + while (quote >= 0 && quote < lineStart) quote = text.indexOf('"', quote + 1); + while (semicolon >= 0 && semicolon < lineStart) + semicolon = text.indexOf(";", semicolon + 1); + } } - if (quoted) continue; + while (!comment) { + const nextQuote = quote >= 0 && quote < at ? quote : -1; + const nextSemicolon = semicolon >= 0 && semicolon < at ? semicolon : -1; + if (nextQuote < 0 && nextSemicolon < 0) break; + if (nextQuote >= 0 && (nextSemicolon < 0 || nextQuote < nextSemicolon)) { + quoted = !quoted; + quote = text.indexOf('"', quote + 1); + } else { + if (!quoted) comment = true; + semicolon = text.indexOf(";", semicolon + 1); + } + } + if (quoted || comment) continue; if (at > 0 && isNameChar(text.charCodeAt(at - 1))) continue; let end = at + 1; while (end < text.length && isDigit(text.charCodeAt(end))) end++; diff --git a/packages/compiler/test/chunked-emission.test.ts b/packages/compiler/test/chunked-emission.test.ts index 567a082ef..47e82cf9e 100644 --- a/packages/compiler/test/chunked-emission.test.ts +++ b/packages/compiler/test/chunked-emission.test.ts @@ -177,3 +177,52 @@ test("debug builds emitted in ranges merge into the text one emitter gives", asy await rm(dir, { recursive: true, force: true }); } }, 300_000); + +// A method name holding a double quote is written into a comment after the +// function's `!dbg` attachment; the rename of the range's debug ids reads +// comments and string literals as ending at the end of their line. +test("debug ids after a comment that holds a quote are renamed", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-chunked-debug-quote-")); + try { + const entry = join(dir, "main.ts"); + const source = [ + "class Greeter {", + " ['say \"hi'](n: number): number {", + " return n + 1;", + " }", + "}", + "const greeter = new Greeter();", + "function twice(x: number): number {", + " return x * 2;", + "}", + "function report(label: string, value: number): void {", + " console.log(label, value);", + "}", + 'report("method", greeter[\'say "hi\'](1));', + 'report("twice", twice(3));', + "", + ].join("\n"); + await writeFile(entry, source); + const outPath = join(dir, "main.ir.json"); + const result = await compile(entry, { + outDir: dir, + outPath, + outputKind: "ir", + optimization: "dev", + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const ir = await readFile(outPath, "utf8"); + const options: LlvmTargetOptions = { debugSources: new Map([[entry, source]]) }; + const serial = text(emitLlvmModuleSource(deserializeModule(ir), options)); + expect(serial).toMatch(/!dbg ![0-9]+ \{ ;[^\n]*"/); + for (const functionChunks of [2, 3]) { + const chunked = text( + emitLlvmModuleSource(deserializeModule(ir), { ...options, functionChunks }), + ); + expect(chunked === serial, `${functionChunks} chunks`).toBe(true); + } + } finally { + await rm(dir, { recursive: true, force: true }); + } +}, 60_000); From 24ef53ac237dd58c79aa590cff61c1775b275a86 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:32:18 -0500 Subject: [PATCH 060/230] Have tsc-ts read its library files from disk when it can Every file tsc-ts reads goes through scriptc's file system callbacks, including the ~80 files of tsc-ts's own default library directory: one round trip from the checker to the compiler and back for each. Those files belong to the installed tsc-ts (the build caches key on its executable's identity), and no build serves them differently. When tsc-ts takes --default-libraries-from-disk, the native compiler passes it to the checker it starts and to the shared checker server, so tsc-ts reads those files itself; every other path keeps the callbacks. Released tsc-ts versions reject the flag, so checker-features.ts asks each tsc-ts executable once (tsc-ts --api --default-libraries-from-disk with an empty input exits with 0 only when it knows the flag) and keeps the answer in the native cache, keyed by the executable's identity. With a tsc-ts that does not know the flag, builds run as before. Edit builds of small programs through the shared checker server are about 18 ms faster, and 6-9 ms faster without it. --- .../compiler/native/ts7-process-child.test.c | 10 ++-- packages/compiler/native/ts7-process.c | 27 ++++++---- packages/compiler/native/ts7-process.ffi.json | 4 +- packages/compiler/native/ts7-process.h | 5 +- packages/compiler/native/ts7-process.test.c | 24 +++++---- .../src/frontend/ts7/checker-features.test.ts | 45 ++++++++++++++++ .../src/frontend/ts7/checker-features.ts | 53 +++++++++++++++++++ .../src/frontend/ts7/checker-server.ts | 7 ++- .../compiler/src/frontend/ts7/native-api.ts | 12 +++-- .../src/frontend/ts7/native-process.ts | 27 ++++++++-- packages/compiler/src/native/cli.ts | 6 +++ 11 files changed, 185 insertions(+), 35 deletions(-) create mode 100644 packages/compiler/src/frontend/ts7/checker-features.test.ts create mode 100644 packages/compiler/src/frontend/ts7/checker-features.ts diff --git a/packages/compiler/native/ts7-process-child.test.c b/packages/compiler/native/ts7-process-child.test.c index 386a0a7c3..4d1cbc081 100644 --- a/packages/compiler/native/ts7-process-child.test.c +++ b/packages/compiler/native/ts7-process-child.test.c @@ -64,8 +64,11 @@ static int child_main(int argc, char **argv) { #endif int listening = argc >= 9 && strcmp(argv[5], "--listen") == 0 && strcmp(argv[7], "--idle-exit") == 0; int fixed = listening ? 9 : 5; - if (argc < fixed || argc > fixed + 1 || strcmp(argv[1], "--api") != 0 || strcmp(argv[2], "--cwd") != 0 || - strncmp(argv[4], "--callbacks=", 12) != 0 || (argc == fixed + 1 && strcmp(argv[fixed], "--timing") != 0)) + int at = fixed, timing = 0, libraries = 0; + if (at < argc && strcmp(argv[at], "--timing") == 0) { timing = 1; at++; } + if (at < argc && strcmp(argv[at], "--default-libraries-from-disk") == 0) { libraries = 1; at++; } + if (argc < fixed || at != argc || strcmp(argv[1], "--api") != 0 || strcmp(argv[2], "--cwd") != 0 || + strncmp(argv[4], "--callbacks=", 12) != 0) return 71; const char *mode = argv[3]; #ifndef _WIN32 @@ -76,7 +79,8 @@ static int child_main(int argc, char **argv) { fputc('\n', stdout); fputs(argv[4], stdout); fputc('\n', stdout); - fputs(argc == 6 ? "timing\n" : "plain\n", stdout); + fputs(timing ? "timing\n" : "plain\n", stdout); + if (libraries) fputs("libraries from disk\n", stdout); return ferror(stdout) ? 72 : 0; } if (strcmp(mode, "exit") == 0) return 0; diff --git a/packages/compiler/native/ts7-process.c b/packages/compiler/native/ts7-process.c index 3fd38a423..8261c341b 100644 --- a/packages/compiler/native/ts7-process.c +++ b/packages/compiler/native/ts7-process.c @@ -161,7 +161,7 @@ static wchar_t *command_line(wchar_t **arguments, size_t count) { } static int start_process(Ts7Process *p, char **args, size_t argc) { - wchar_t *wide[6] = {0}; + wchar_t *wide[7] = {0}; wchar_t *line = NULL; HANDLE child_input = NULL, child_output = NULL, child_error = NULL; LPPROC_THREAD_ATTRIBUTE_LIST attributes = NULL; @@ -330,7 +330,7 @@ static void stop_process(Ts7Process *p) { uint32_t scriptc_ts7_open(const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, - uint8_t timing) { + uint8_t timing, uint8_t libraries_from_disk) { if (next_id == 0) { error_text("server handle IDs are exhausted"); return 0; } char *exe = copy_argument(executable, executable_size, ""); if (exe == NULL) return 0; @@ -346,8 +346,12 @@ uint32_t scriptc_ts7_open(const uint8_t *executable, size_t executable_size, } exit_registered = 1; } - char *args[] = {exe, "--api", "--cwd", directory, callback_flag, timing ? "--timing" : NULL, NULL}; - int started = start_process(p, args, timing ? 6 : 5); + char *args[8] = {exe, "--api", "--cwd", directory, callback_flag}; + size_t argc = 5; + if (timing) args[argc++] = "--timing"; + if (libraries_from_disk) args[argc++] = "--default-libraries-from-disk"; + args[argc] = NULL; + int started = start_process(p, args, argc); free(exe); free(directory); free(callback_flag); if (!started) { free(p); return 0; } p->id = next_id++; @@ -361,9 +365,10 @@ uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, - uint8_t timing, uint32_t idle_exit, uint32_t memory) { + uint8_t timing, uint8_t libraries_from_disk, uint32_t idle_exit, + uint32_t memory) { (void)name; (void)name_size; (void)executable; (void)executable_size; (void)cwd; (void)cwd_size; - (void)callbacks; (void)callbacks_size; (void)timing; (void)idle_exit; (void)memory; + (void)callbacks; (void)callbacks_size; (void)timing; (void)libraries_from_disk; (void)idle_exit; (void)memory; error_text("a shared checker server needs Unix sockets"); return 0; } @@ -455,7 +460,8 @@ uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, - uint8_t timing, uint32_t idle_exit, uint32_t memory) { + uint8_t timing, uint8_t libraries_from_disk, uint32_t idle_exit, + uint32_t memory) { if (next_id == 0) { error_text("server handle IDs are exhausted"); return 0; } char *server = copy_argument(name, name_size, ""); if (server == NULL) return 0; @@ -491,8 +497,11 @@ uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, (void)snprintf(limit, sizeof(limit), "TSC_TS_LISTEN_MEMORY=%lu", (unsigned long)memory); pid_t pid = -1; if (callback_flag != NULL) { - char *args[] = {exe, "--api", "--cwd", dir, callback_flag, "--listen", socket_path, "--idle-exit", idle, - timing ? "--timing" : NULL, NULL}; + char *args[12] = {exe, "--api", "--cwd", dir, callback_flag, "--listen", socket_path, "--idle-exit", idle}; + size_t argc = 9; + if (timing) args[argc++] = "--timing"; + if (libraries_from_disk) args[argc++] = "--default-libraries-from-disk"; + args[argc] = NULL; pid = start_server(args, limit); } free(exe); free(dir); free(callback_flag); diff --git a/packages/compiler/native/ts7-process.ffi.json b/packages/compiler/native/ts7-process.ffi.json index c503e3cae..963c81f15 100644 --- a/packages/compiler/native/ts7-process.ffi.json +++ b/packages/compiler/native/ts7-process.ffi.json @@ -1,8 +1,8 @@ { "ffi_format": 6, "functions": [ - { "name": "ts7NativeOpen", "symbol": "scriptc_ts7_open", "params": ["string", "string", "string", "bool"], "returns": "u32" }, - { "name": "ts7NativeAttach", "symbol": "scriptc_ts7_attach", "params": ["string", "string", "string", "string", "bool", "u32", "u32"], "returns": "u32" }, + { "name": "ts7NativeOpen", "symbol": "scriptc_ts7_open", "params": ["string", "string", "string", "bool", "bool"], "returns": "u32" }, + { "name": "ts7NativeAttach", "symbol": "scriptc_ts7_attach", "params": ["string", "string", "string", "string", "bool", "bool", "u32", "u32"], "returns": "u32" }, { "name": "ts7NativeRead", "symbol": "scriptc_ts7_read", "params": ["u32", "mutable-bytes", "u32", "u32"], "returns": "i32" }, { "name": "ts7NativeWrite", "symbol": "scriptc_ts7_write", "params": ["u32", "bytes", "u32", "u32"], "returns": "i32" }, { "name": "ts7NativePid", "symbol": "scriptc_ts7_pid", "params": ["u32"], "returns": "u32" }, diff --git a/packages/compiler/native/ts7-process.h b/packages/compiler/native/ts7-process.h index 4199e8795..d51f939a9 100644 --- a/packages/compiler/native/ts7-process.h +++ b/packages/compiler/native/ts7-process.h @@ -12,7 +12,7 @@ uint32_t scriptc_ts7_open(const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, - uint8_t timing); + uint8_t timing, uint8_t libraries_from_disk); /* Connects to the shared checker server `name` (`--api --listen`) in this * user's private server directory, starting it in its own process group * with `--idle-exit idle_exit` and a limit of `memory` megabytes @@ -25,7 +25,8 @@ uint32_t scriptc_ts7_attach(const uint8_t *name, size_t name_size, const uint8_t *executable, size_t executable_size, const uint8_t *cwd, size_t cwd_size, const uint8_t *callbacks, size_t callbacks_size, - uint8_t timing, uint32_t idle_exit, uint32_t memory); + uint8_t timing, uint8_t libraries_from_disk, uint32_t idle_exit, + uint32_t memory); int32_t scriptc_ts7_read(uint32_t handle, uint8_t *bytes, size_t size, uint32_t offset, uint32_t length); int32_t scriptc_ts7_write(uint32_t handle, const uint8_t *bytes, size_t size, diff --git a/packages/compiler/native/ts7-process.test.c b/packages/compiler/native/ts7-process.test.c index dc0c67ced..b6d760abd 100644 --- a/packages/compiler/native/ts7-process.test.c +++ b/packages/compiler/native/ts7-process.test.c @@ -21,12 +21,14 @@ static const char *executable; static const char *callbacks = "readFile,fileExists"; -static uint32_t start(const char *mode, int timing) { +static uint32_t start_with(const char *mode, int timing, int libraries) { return scriptc_ts7_open((const uint8_t *)executable, strlen(executable), (const uint8_t *)mode, strlen(mode), - (const uint8_t *)callbacks, strlen(callbacks), (uint8_t)timing); + (const uint8_t *)callbacks, strlen(callbacks), (uint8_t)timing, (uint8_t)libraries); } +static uint32_t start(const char *mode, int timing) { return start_with(mode, timing, 0); } + static void fail_message(void) { uint8_t message[1024]; int32_t size = scriptc_ts7_error(message, sizeof(message)); @@ -83,11 +85,13 @@ static void argument_cases(void) { "", "a b", "double\"quote", "end\\", "many\\\\\"quotes\\\\", "a;&$()`'b", "h\xc3\xa9llo \xf0\x9f\x8c\x8d", }; for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) { - for (int timing = 0; timing <= 1; timing++) { + for (int flags = 0; flags < 4; flags++) { + int timing = flags & 1, libraries = flags >> 1; char mode[256], expected[512]; snprintf(mode, sizeof(mode), "argv:%s", values[i]); - snprintf(expected, sizeof(expected), "%s\n--callbacks=%s\n%s\n", values[i], callbacks, timing ? "timing" : "plain"); - uint32_t id = start(mode, timing); + snprintf(expected, sizeof(expected), "%s\n--callbacks=%s\n%s\n%s", values[i], callbacks, + timing ? "timing" : "plain", libraries ? "libraries from disk\n" : ""); + uint32_t id = start_with(mode, timing, libraries); assert(id != 0); uint8_t actual[512]; read_exact(id, actual, strlen(expected)); @@ -147,10 +151,10 @@ static void failure_cases(void) { executable = "/scriptc/does-not-exist/tsgo"; for (int i = 0; i < 30; i++) { assert(start("echo", 0) == 0); fail_message(); } executable = saved; - assert(scriptc_ts7_open(NULL, 0, bytes, 1, bytes, 1, 0) == 0); - assert(scriptc_ts7_open((const uint8_t *)"abc\0def", 7, bytes, 1, bytes, 1, 0) == 0); - assert(scriptc_ts7_open((const uint8_t *)executable, strlen(executable), NULL, 0, bytes, 1, 0) == 0); - assert(scriptc_ts7_open((const uint8_t *)executable, strlen(executable), bytes, 1, NULL, 0, 0) == 0); + assert(scriptc_ts7_open(NULL, 0, bytes, 1, bytes, 1, 0, 0) == 0); + assert(scriptc_ts7_open((const uint8_t *)"abc\0def", 7, bytes, 1, bytes, 1, 0, 0) == 0); + assert(scriptc_ts7_open((const uint8_t *)executable, strlen(executable), NULL, 0, bytes, 1, 0, 0) == 0); + assert(scriptc_ts7_open((const uint8_t *)executable, strlen(executable), bytes, 1, NULL, 0, 0, 0) == 0); for (int i = 0; i < 30; i++) { uint32_t id = start("exit", 0); assert(id != 0); @@ -177,7 +181,7 @@ static void failure_cases(void) { static uint32_t attach(const char *name, const char *mode, uint32_t memory) { return scriptc_ts7_attach((const uint8_t *)name, strlen(name), (const uint8_t *)executable, strlen(executable), (const uint8_t *)mode, strlen(mode), (const uint8_t *)callbacks, strlen(callbacks), - 0, 1, memory); + 0, 0, 1, memory); } static void expect_line(uint32_t id, const char *expected) { diff --git a/packages/compiler/src/frontend/ts7/checker-features.test.ts b/packages/compiler/src/frontend/ts7/checker-features.test.ts new file mode 100644 index 000000000..fcd3576cb --- /dev/null +++ b/packages/compiler/src/frontend/ts7/checker-features.test.ts @@ -0,0 +1,45 @@ +import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, afterEach, expect, test } from "vitest"; +import { checkerReadsLibrariesFromDisk, setCheckerFeatureStore } from "./checker-features.js"; + +const dir = mkdtempSync(join(tmpdir(), "scriptc-checker-features-")); +afterAll(() => rmSync(dir, { recursive: true, force: true })); +afterEach(() => setCheckerFeatureStore(null)); + +/** A stand-in checker that records its arguments and exits with `status`. */ +function checker(name: string, status: number): { path: string; log: string } { + const path = join(dir, name); + const log = path + ".log"; + writeFileSync(path, `#!/bin/sh\necho "$@" >> '${log}'\nexit ${status}\n`); + chmodSync(path, 0o755); + return { path, log }; +} + +test.skipIf(process.platform === "win32")( + "the checker is asked once whether it reads its library files from the disk, and the answer is stored", + async () => { + const { readFileSync, existsSync } = await import("node:fs"); + const entries = new Map(); + setCheckerFeatureStore({ + read: (key) => entries.get(key) ?? null, + write: (key, text) => void entries.set(key, text), + }); + const current = checker("current", 0); + const released = checker("released", 2); + expect(checkerReadsLibrariesFromDisk(current.path, {})).toBe(true); + expect(checkerReadsLibrariesFromDisk(released.path, {})).toBe(false); + expect(readFileSync(current.log, "utf8")).toBe("--api --default-libraries-from-disk\n"); + expect([...entries.values()].sort()).toEqual(["0", "1"]); + // Later asks use the answers: the checkers do not run again. + expect(checkerReadsLibrariesFromDisk(current.path, {})).toBe(true); + expect(checkerReadsLibrariesFromDisk(released.path, {})).toBe(false); + expect(readFileSync(current.log, "utf8").split("\n").length).toBe(2); + expect(readFileSync(released.log, "utf8").split("\n").length).toBe(2); + // tsgo is never asked. + const tsgo = checker("tsgo", 0); + expect(checkerReadsLibrariesFromDisk(tsgo.path, { SCRIPTC_CHECKER: "tsgo" })).toBe(false); + expect(existsSync(tsgo.log)).toBe(false); + }, +); diff --git a/packages/compiler/src/frontend/ts7/checker-features.ts b/packages/compiler/src/frontend/ts7/checker-features.ts new file mode 100644 index 000000000..b80a54994 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/checker-features.ts @@ -0,0 +1,53 @@ +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { checkerBackend } from "./checker-backend.js"; +import { checkerExecutableIdentity } from "./lib-diagnostics.js"; + +/** Text entries kept between builds (the native compiler's cache). */ +export interface CheckerFeatureStore { + read(key: string): string | null; + write(key: string, text: string): void; +} + +let featureStore: CheckerFeatureStore | null = null; +const answers = new Map(); + +/** Keeps the answers of checkerReadsLibrariesFromDisk between builds. */ +export function setCheckerFeatureStore(store: CheckerFeatureStore | null): void { + featureStore = store; +} + +/** + * Whether the tsc-ts at `executable` takes `--api --default-libraries-from-disk`, which has it read the files + * of its default library directory from the disk instead of through the file system callbacks. tsc-ts releases + * before it reject the flag, so it is asked once per executable identity: the stored answer, or else + * `tsc-ts --api --default-libraries-from-disk` with an empty input, which exits with 0 when the flag is known + * and with a usage error when it is not. tsgo is never asked. + */ +export function checkerReadsLibrariesFromDisk( + executable: string, + env: NodeJS.ProcessEnv = process.env, +): boolean { + if (checkerBackend(env) !== "tsc-ts") return false; + const key = createHash("sha256") + .update(JSON.stringify(["default-libraries-from-disk", checkerExecutableIdentity(executable)])) + .digest("hex"); + const known = answers.get(key); + if (known !== undefined) return known; + const stored = featureStore?.read(key) ?? null; + let answer: boolean; + if (stored === "1" || stored === "0") answer = stored === "1"; + else { + const probe = spawnSync(executable, ["--api", "--default-libraries-from-disk"], { + encoding: "utf8", + stdio: "pipe", + timeout: 10_000, + }); + answer = probe.error === undefined && probe.status === 0; + // A probe that could not run says nothing about the flag: ask again next time. + if (probe.error === undefined && probe.status !== null) + featureStore?.write(key, answer ? "1" : "0"); + } + answers.set(key, answer); + return answer; +} diff --git a/packages/compiler/src/frontend/ts7/checker-server.ts b/packages/compiler/src/frontend/ts7/checker-server.ts index 6d80e2599..94c80f8db 100644 --- a/packages/compiler/src/frontend/ts7/checker-server.ts +++ b/packages/compiler/src/frontend/ts7/checker-server.ts @@ -19,14 +19,16 @@ export const CHECKER_SERVER_MEMORY = 2048; * answers, and the server keeps the files it parsed for the next build. * Returns the server's name, a digest of everything a server's answers * depend on besides the files it reads through the build: the tsc-ts - * executable, the working directory, the file system callbacks, timing, and - * the `TSC_TS_*` environment. Returns null when the shared server is not used. + * executable, the working directory, the file system callbacks, timing, + * whether it reads its library files from the disk, and the `TSC_TS_*` + * environment. Returns null when the shared server is not used. */ export function checkerServerName( executable: string, cwd: string, timing: boolean, env: NodeJS.ProcessEnv = process.env, + librariesFromDisk = false, ): string | null { if (env["SCRIPTC_CHECKER_SERVER"] !== "1") return null; if (process.platform !== "linux" && process.platform !== "darwin") return null; @@ -42,6 +44,7 @@ export function checkerServerName( cwd, TS7_FILE_SYSTEM_CALLBACKS, timing, + librariesFromDisk, tscTsEnv, ]), ) diff --git a/packages/compiler/src/frontend/ts7/native-api.ts b/packages/compiler/src/frontend/ts7/native-api.ts index f8e10efc5..fa0690e69 100644 --- a/packages/compiler/src/frontend/ts7/native-api.ts +++ b/packages/compiler/src/frontend/ts7/native-api.ts @@ -1,4 +1,5 @@ import { Ts7Api } from "./api.js"; +import { checkerReadsLibrariesFromDisk } from "./checker-features.js"; import { checkerServerName } from "./checker-server.js"; import { attachNativeTs7Process, spawnNativeTs7Wire } from "./native-process.js"; import { Ts7RpcClient } from "./rpc-client.js"; @@ -15,12 +16,17 @@ export function createNativeTs7Api(options: { shared?: boolean; }): Ts7Api { const timing = options.collectTiming ?? false; + // The checker reads its own library files when it can: they are part of the + // installed tsc-ts, so no build serves them differently. + const libraries = checkerReadsLibrariesFromDisk(options.executable); const server = - options.shared === false ? null : checkerServerName(options.executable, options.cwd, timing); + options.shared === false + ? null + : checkerServerName(options.executable, options.cwd, timing, process.env, libraries); const shared = server === null ? null - : attachNativeTs7Process(server, options.executable, options.cwd, timing); + : attachNativeTs7Process(server, options.executable, options.cwd, timing, libraries); if (shared !== null) { const rpc = new Ts7RpcClient(new Ts7Wire(shared)); try { @@ -32,7 +38,7 @@ export function createNativeTs7Api(options: { } } return new Ts7Api( - new Ts7RpcClient(spawnNativeTs7Wire(options.executable, options.cwd, timing)), + new Ts7RpcClient(spawnNativeTs7Wire(options.executable, options.cwd, timing, libraries)), options.fs, timing, ); diff --git a/packages/compiler/src/frontend/ts7/native-process.ts b/packages/compiler/src/frontend/ts7/native-process.ts index 0582ae673..d59543f32 100644 --- a/packages/compiler/src/frontend/ts7/native-process.ts +++ b/packages/compiler/src/frontend/ts7/native-process.ts @@ -10,6 +10,7 @@ declare function ts7NativeOpen( cwd: string, callbacks: string, timing: boolean, + librariesFromDisk: boolean, ): number; declare function ts7NativeAttach( name: string, @@ -17,6 +18,7 @@ declare function ts7NativeAttach( cwd: string, callbacks: string, timing: boolean, + librariesFromDisk: boolean, idleExit: number, memory: number, ): number; @@ -63,12 +65,26 @@ export class NativeTs7Process implements Ts7WireIo { private handle: number; readonly pid: number; - /** With `handle`, a connection to a shared checker server (attachNativeTs7Process). */ - constructor(executable: string, cwd: string, collectTiming = false, handle = 0) { + /** With `handle`, a connection to a shared checker server (attachNativeTs7Process). With + * `librariesFromDisk`, the server reads its default library files from the disk + * (checkerReadsLibrariesFromDisk). */ + constructor( + executable: string, + cwd: string, + collectTiming = false, + handle = 0, + librariesFromDisk = false, + ) { this.handle = handle !== 0 ? handle - : ts7NativeOpen(executable, cwd, TS7_FILE_SYSTEM_CALLBACKS, collectTiming); + : ts7NativeOpen( + executable, + cwd, + TS7_FILE_SYSTEM_CALLBACKS, + collectTiming, + librariesFromDisk, + ); if (this.handle === 0) throw nativeError("startup"); this.pid = ts7NativePid(this.handle); } @@ -125,6 +141,7 @@ export function attachNativeTs7Process( executable: string, cwd: string, collectTiming = false, + librariesFromDisk = false, ): NativeTs7Process | null { const handle = ts7NativeAttach( name, @@ -132,6 +149,7 @@ export function attachNativeTs7Process( cwd, TS7_FILE_SYSTEM_CALLBACKS, collectTiming, + librariesFromDisk, CHECKER_SERVER_IDLE_EXIT, CHECKER_SERVER_MEMORY, ); @@ -142,6 +160,7 @@ export function spawnNativeTs7Wire( executable: string, cwd: string, collectTiming = false, + librariesFromDisk = false, ): Ts7Wire { - return new Ts7Wire(new NativeTs7Process(executable, cwd, collectTiming)); + return new Ts7Wire(new NativeTs7Process(executable, cwd, collectTiming, 0, librariesFromDisk)); } diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index b729b2557..b3de05846 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -21,6 +21,7 @@ import { checkerExecutableIdentity, setLibDiagnosticsStore, } from "../frontend/ts7/lib-diagnostics.js"; +import { setCheckerFeatureStore } from "../frontend/ts7/checker-features.js"; import { loadNativeToolchain } from "./toolchain.js"; import { buildSanitizedRuntime } from "./sanitizer.js"; import { runCompilerTask } from "./task.js"; @@ -67,6 +68,11 @@ async function main(): Promise { }, checkerExecutableIdentity(toolchain.ts7Executable), ); + setCheckerFeatureStore({ + read: (key) => + store.read("checker-features", key, NATIVE_STRING_BYTES)?.toString("utf8") ?? null, + write: (key, text) => store.write("checker-features", key, text), + }); } compiler = new NativeCompiler(toolchain, cache); return compiler; From d8f287486087df2931f33db768b615c42fe8535f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:42:25 -0500 Subject: [PATCH 061/230] Keep replace callback parameters checked-dynamic where parseInt or parseFloat expects another type parseFloat and the text of parseInt take a string, and the radix of parseInt takes a number. An unannotated offset passed as their text, or a capture passed as the radix, is no longer bound to its value's type; it stays checked-dynamic, so the call compiles as it did before the binding. --- .../lowering/containers/replacer-params.ts | 26 +++++++++++++------ .../compiler/test/replacer-params.test.ts | 7 ++++- .../8345-replace-callback-untyped-params.ts | 5 ++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/containers/replacer-params.ts b/packages/compiler/src/frontend/lowering/containers/replacer-params.ts index e84ca1461..3a3eff1cf 100644 --- a/packages/compiler/src/frontend/lowering/containers/replacer-params.ts +++ b/packages/compiler/src/frontend/lowering/containers/replacer-params.ts @@ -115,7 +115,10 @@ const STRING_MEMBERS: ReadonlySet = new Set([ /** Whether every read of `symbol` in `body` is one that means the same * for an `any` value and for a `kind` value, and lowers for both: a - * template span, a Number/String/parseInt/parseFloat argument, a listed + * template span, the argument of Number or String, the text argument of + * parseInt or parseFloat for a string and the radix of parseInt for a + * number (each a parameter of that type, which an `any` value converts + * to), a listed * string member, `+`, strict equality, arithmetic and comparisons of * numbers, a returned value, and a string key of an element access on a * string-indexed record or an `any` value. Anything else keeps the @@ -144,13 +147,20 @@ function readsFit( return shape?.indexValue !== undefined && !shape.tuple; } if (ts.isArrowFunction(parent)) return parent.body === node; - if (ts.isCallExpression(parent)) - return ( - parent.expression !== node && - ["Number", "String", "parseInt", "parseFloat"].some((global) => - lowerer.isStdlibGlobal(parent.expression, global), - ) - ); + if (ts.isCallExpression(parent)) { + if (parent.expression === node) return false; + const position = parent.arguments.nodes.indexOf(node as ts.Expression); + if ( + lowerer.isStdlibGlobal(parent.expression, "Number") || + lowerer.isStdlibGlobal(parent.expression, "String") + ) + return position === 0; + if (lowerer.isStdlibGlobal(parent.expression, "parseFloat")) + return position === 0 && kind === "string"; + if (lowerer.isStdlibGlobal(parent.expression, "parseInt")) + return position === 0 ? kind === "string" : position === 1 && kind === "f64"; + return false; + } if (ts.isPropertyAccessExpression(parent)) { const member = parent.name.text; if (kind !== "string" || parent.expression !== node || !STRING_MEMBERS.has(member)) diff --git a/packages/compiler/test/replacer-params.test.ts b/packages/compiler/test/replacer-params.test.ts index bb5549a86..a5d22bb82 100644 --- a/packages/compiler/test/replacer-params.test.ts +++ b/packages/compiler/test/replacer-params.test.ts @@ -69,6 +69,8 @@ console.log(text.replace(/(a)|(b)/g, (m, a, b) => \`\${m}\${a}\${b}\`)); console.log(text.replace(/(a)(b)?/g, (m: string, a: string, b: string | undefined, at: number) => m + a + (b ?? "") + at)); const half = (n: number): number => n / 2; console.log(text.replace(/b/g, (m, at) => String(half(at)))); +console.log(text.replace(/b/g, (m, at) => String(parseFloat(at) + parseInt(at)))); +console.log(text.replace(/(\\d)/g, (m, d) => String(parseInt("11", d)))); console.log(text.replace(/(a)(b)(c)/g, (m, a, b, c, at, all, extra) => m + a + b + c + at + all + extra)); `); expect(replacerCalls(llvm)).toEqual({ @@ -76,7 +78,10 @@ console.log(text.replace(/(a)(b)(c)/g, (m, a, b, c, at, all, extra) => m + a + b // two captures a match can leave unset (bits 1 and 2); // \`string | undefined\` at capture 2 (bit 8 + 2); an offset passed to a // number parameter keeps its checked-dynamic box (bit 1). - direct: ["2:0", "2:-2147483648", "3:6", "4:1024", "2:2"], + // parseFloat and parseInt take text, and parseInt's radix is a + // number: the offset passed as text and the capture passed as the + // radix stay checked-dynamic (bit 1), so both calls compile. + direct: ["2:0", "2:-2147483648", "3:6", "4:1024", "2:2", "2:2", "2:2"], // Seven parameters exceed the direct calls' limit. boxed: 1, }); diff --git a/tests/corpus/8345-replace-callback-untyped-params.ts b/tests/corpus/8345-replace-callback-untyped-params.ts index 31d3a038b..18ddb3389 100644 --- a/tests/corpus/8345-replace-callback-untyped-params.ts +++ b/tests/corpus/8345-replace-callback-untyped-params.ts @@ -51,6 +51,11 @@ console.log("ab".replace(/(?a)|(?b)/g, (m, a, b, at, all, groups) => `${m} console.log("ab".replace(/b/, (m, at, all, extra, more) => `${m}:${at}:${all}:${extra}:${more}`)); console.log("ab".replace(/(a)(b)/, (m, a, b, at, all, extra, more) => `${m}${a}${b}${at}${all}${extra}${more}`)); +// An offset passed to parseFloat converts with ToString, and captures +// passed to parseInt and parseFloat are their text. +console.log("a1b22".replace(/\d+/g, (m, at) => `<${parseFloat(at) + parseInt(m, at + 10)}>`)); +console.log("1.5 2e1".replace(/(\S+)/g, (_m, d) => String(parseFloat(d) + parseInt(d) + Number(d)))); + // Results that are not strings convert with ToString. const counts: Record = { a: 1, b: true, c: null, d: undefined, e: { toString: () => "E" } }; console.log("a b c d e".replace(/\w/g, (m) => counts[m])); From c2e6c962e84988d9e178b82eb80b9841f4cdbec9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:49:46 -0500 Subject: [PATCH 062/230] Build the sanitized three.js nodes fixture in its own CI job On the san 1/20 shard the sanitized nodes build shares the runner with the corpus's other two workers and the side queue. It took 944 s and 860 s on #2194's runs and passed 1200 s on main's 7fa88083, failing the test timeout. The Linux san corpus now skips that one case (SCRIPTC_THREE_SANITIZED_NODES_JOB), and a three_nodes_san job runs it alone with the corpus's LeakSanitizer settings. The required test check waits for the new job. Every other three.js case, the plain nodes build and the macOS suites are unchanged. --- .github/workflows/ci.yml | 33 ++++++++++++++++++++++++++ scripts/ci-test-plan.mjs | 10 ++++++++ tests/harness/ci-test-plan.test.ts | 37 ++++++++++++++++++++++++++++++ tests/harness/three.test.ts | 9 ++++++++ 4 files changed, 89 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e949339fc..2b690b100 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -346,6 +346,37 @@ jobs: - run: tar -xzf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz" - run: pnpm test tests/harness/effect.test.ts tests/harness/effect4.test.ts + # The sanitized three.js nodes build alone: beside the san corpus's other + # workers it took 860 to more than 1200 seconds of its shard + # (SCRIPTC_THREE_SANITIZED_NODES_JOB in scripts/ci-test-plan.mjs). It keeps + # the corpus's LeakSanitizer settings. + three_nodes_san: + needs: [validation_scope, lanes, native_linux] + if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }} + name: test (Linux, san, three.js nodes) + runs-on: ubuntu-24.04 + timeout-minutes: 40 + env: + SCRIPTC_SAN: "1" + ASAN_OPTIONS: detect_leaks=1 + LSAN_OPTIONS: suppressions=${{ github.workspace }}/tests/harness/lsan.supp:print_suppressions=0 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 11.1.3 + - uses: actions/setup-node@v4 + with: + node-version-file: .node-version + cache: pnpm + - run: pnpm install --frozen-lockfile + - uses: actions/download-artifact@v4 + with: + name: native-linux-x64-gnu + path: ${{ runner.temp }} + - run: tar -xzf "$RUNNER_TEMP/native-linux-x64-gnu.tar.gz" + - run: pnpm test tests/harness/three.test.ts -t "'nodes' runs statically through LLVM on 'native'" + # Build each instrumented seed once. Downstream contracts retain the same # compiler and source artifacts without repeating seed construction. bootstrap_seed: @@ -950,6 +981,7 @@ jobs: tests_macos, native_linux, effects, + three_nodes_san, bootstrap_seed, bootstrap, linux_host_clang, @@ -982,6 +1014,7 @@ jobs: lane tests_macos "${{ needs.tests_macos.result }}" "${{ needs.lanes.outputs.macos }}" lane native_linux "${{ needs.native_linux.result }}" "${{ needs.lanes.outputs.linux }}" lane effects "${{ needs.effects.result }}" "${{ needs.lanes.outputs.effects }}" + lane three_nodes_san "${{ needs.three_nodes_san.result }}" "${{ needs.lanes.outputs.linux }}" lane bootstrap_seed "${{ needs.bootstrap_seed.result }}" "${{ needs.lanes.outputs.bootstrap }}" lane bootstrap "${{ needs.bootstrap.result }}" "${{ needs.lanes.outputs.bootstrap }}" lane linux_host_clang "${{ needs.linux_host_clang.result }}" "${{ needs.lanes.outputs.linux }}" diff --git a/scripts/ci-test-plan.mjs b/scripts/ci-test-plan.mjs index 184f04d1c..88a77e32e 100644 --- a/scripts/ci-test-plan.mjs +++ b/scripts/ci-test-plan.mjs @@ -134,6 +134,15 @@ export const leakSuppressions = fileURLToPath( new URL("../tests/harness/lsan.supp", import.meta.url), ); +// The sanitized three.js nodes build runs in its own Linux job +// (three_nodes_san in .github/workflows/ci.yml). Beside the corpus's other +// workers it took 860 to more than 1200 seconds, past its test timeout. +export const sanitizedNodesJobEnv = { SCRIPTC_THREE_SANITIZED_NODES_JOB: "1" }; + +function corpusThreeEnv(flavor, platform) { + return flavor === "san" && platform === "linux" ? sanitizedNodesJobEnv : {}; +} + function corpusLeakEnv(flavor, platform) { if (flavor !== "san" || platform !== "linux") return {}; return { @@ -246,6 +255,7 @@ export function ciTestPlan({ env: { ...env, ...corpusLeakEnv(flavor, platform), + ...corpusThreeEnv(flavor, platform), SCRIPTC_TEST_WORKERS: String(workers - sideWorkers), SCRIPTC_LLVM_TEST_MODE: "dev", }, diff --git a/tests/harness/ci-test-plan.test.ts b/tests/harness/ci-test-plan.test.ts index 29268c841..e67e5582b 100644 --- a/tests/harness/ci-test-plan.test.ts +++ b/tests/harness/ci-test-plan.test.ts @@ -12,6 +12,7 @@ import { isolatedFiles, leakSuppressions, memoryHeavyFiles, + sanitizedNodesJobEnv, sideOwners, testCosts, } from "../../scripts/ci-test-plan.mjs"; @@ -90,6 +91,42 @@ test("LeakSanitizer runs over the sanitized corpus on Linux only", () => { for (const task of plan("san", "linux").side) expect(task.env.ASAN_OPTIONS).toBeUndefined(); }); +test("the sanitized three.js nodes build leaves the Linux corpus for its own job", () => { + const plan = (flavor: "plain" | "san", platform: string) => + ciTestPlan({ shard: "1/20", flavor, workers: 4, platform }); + const [name, value] = Object.entries(sanitizedNodesJobEnv)[0]!; + expect(plan("san", "linux").corpus.env[name]).toBe(value); + for (const env of [plan("plain", "linux").corpus.env, plan("san", "darwin").corpus.env]) + expect(env[name]).toBeUndefined(); + const three = readFileSync(new URL("./three.test.ts", import.meta.url), "utf8"); + expect(three).toContain(`process.env["${name}"] === "${value}"`); + const { jobs } = load( + readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"), + ) as { + jobs: Record< + string, + { needs: string[]; env: Record; steps: { run?: string }[] } + >; + }; + const job = jobs["three_nodes_san"]!; + expect(job.needs).toContain("native_linux"); + expect(jobs["test"]!.needs).toContain("three_nodes_san"); + expect(job.env[name]).toBeUndefined(); + expect(job.env["SCRIPTC_SAN"]).toBe("1"); + // The same LeakSanitizer settings as the corpus it left. + const corpus = plan("san", "linux").corpus.env; + expect(job.env["ASAN_OPTIONS"]).toBe(corpus.ASAN_OPTIONS); + expect(job.env["LSAN_OPTIONS"]).toBe( + corpus.LSAN_OPTIONS!.replace( + leakSuppressions, + "${{ github.workspace }}/tests/harness/lsan.supp", + ), + ); + expect(job.steps.at(-1)!.run).toBe( + `pnpm test tests/harness/three.test.ts -t "'nodes' runs statically through LLVM on 'native'"`, + ); +}); + test("the side queue balances measured files across the twenty Linux shards", () => { for (const flavor of ["plain", "san"] as const) { const costs = testCosts[flavor] as Record; diff --git a/tests/harness/three.test.ts b/tests/harness/three.test.ts index 27fb86d1f..e55242149 100644 --- a/tests/harness/three.test.ts +++ b/tests/harness/three.test.ts @@ -68,6 +68,15 @@ test.for(cases)( async ({ fixture, target }, context) => { const entry = join(import.meta.dirname, `../fixtures/three/${fixture}.mjs`); if (target === "wasm32-wasi" && !hasZig) context.skip(); + // CI's Linux sanitized lane builds this one in its own job + // (scripts/ci-test-plan.mjs). + if ( + fixture === "nodes" && + target === "native" && + process.env["SCRIPTC_SAN"] === "1" && + process.env["SCRIPTC_THREE_SANITIZED_NODES_JOB"] === "1" + ) + context.skip(); const dir = await mkdtemp("/tmp/scriptc-three-"); const previousTarget = process.env["SCRIPTC_TARGET"]; const previousCc = process.env["SCRIPTC_CC"]; From c80945ec21ddbd208f7191b2547ad217d6b76987 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 06:50:19 -0500 Subject: [PATCH 063/230] Leave timing and cache placement variables out of the frontend cache key The native frontend cache key included every SCRIPTC_* variable, so turning SCRIPTC_TIMING on or off, or changing SCRIPTC_CACHE_DIR, SCRIPTC_CACHE_MAX_MB, SCRIPTC_EMIT_WORKERS or SCRIPTC_EMIT_WORKER_FUNCTIONS, reran the frontend on an unchanged program. The frontend's output does not depend on these variables, so the key now leaves them out. Every other SCRIPTC_* variable still keys the entry. --- packages/compiler/src/native/prepare.test.ts | 27 +++++++++++++++++++- packages/compiler/src/native/prepare.ts | 26 ++++++++++++++----- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/native/prepare.test.ts b/packages/compiler/src/native/prepare.test.ts index 5d0f9faae..07698820e 100644 --- a/packages/compiler/src/native/prepare.test.ts +++ b/packages/compiler/src/native/prepare.test.ts @@ -1,7 +1,7 @@ import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, expect, test } from "vitest"; +import { afterEach, expect, test, vi } from "vitest"; import { LINUX_ARM64_GNU_TARGET, nativeCodegenTarget, @@ -23,6 +23,7 @@ function text(input: ReturnType["prepare"]>) { const scratch: string[] = []; afterEach(() => { + vi.unstubAllEnvs(); for (const path of scratch.splice(0)) rmSync(path, { recursive: true, force: true }); }); @@ -197,6 +198,30 @@ test("structurally invalid cached features cause a rebuild even with a valid dig expect(f.loads()).toBe(2); }); +test("timing, cache placement and emission worker variables keep the frontend output", () => { + for (const name of [ + "SCRIPTC_CACHE_DIR", + "SCRIPTC_CACHE_MAX_MB", + "SCRIPTC_EMIT_WORKERS", + "SCRIPTC_EMIT_WORKER_FUNCTIONS", + "SCRIPTC_TIMING", + "SCRIPTC_TEST_FRONTEND_KEY", + ]) + vi.stubEnv(name, undefined); + const f = fixture(); + expect(f.prepare().ok).toBe(true); + vi.stubEnv("SCRIPTC_CACHE_DIR", f.cache.root); + vi.stubEnv("SCRIPTC_CACHE_MAX_MB", "8192"); + vi.stubEnv("SCRIPTC_EMIT_WORKERS", "1"); + vi.stubEnv("SCRIPTC_EMIT_WORKER_FUNCTIONS", "100"); + vi.stubEnv("SCRIPTC_TIMING", "0"); + expect(f.prepare().ok).toBe(true); + expect(f.loads()).toBe(1); + vi.stubEnv("SCRIPTC_TEST_FRONTEND_KEY", "1"); + expect(f.prepare().ok).toBe(true); + expect(f.loads()).toBe(2); +}); + test("serialized IR travels as bounded parts through the frontend cache", () => { const f = fixture(); const first = f.prepare({ emitIr: true }); diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index a10d16585..11b8f9dd1 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -211,6 +211,24 @@ function implementationIdentity(toolchain: NativeToolchain): string { ); } +/** SCRIPTC_* variables that report on a build, place its cache or set + * backend emission parallelism. The frontend's output does not depend on + * them, so they are left out of its cache key. */ +const FRONTEND_INDEPENDENT_VARIABLES = new Set([ + "SCRIPTC_CACHE_DIR", + "SCRIPTC_CACHE_MAX_MB", + "SCRIPTC_EMIT_WORKERS", + "SCRIPTC_EMIT_WORKER_FUNCTIONS", + "SCRIPTC_TIMING", +]); + +/** The SCRIPTC_* variables a frontend cache entry depends on, sorted by name. */ +function frontendEnvironment(): [string, string | undefined][] { + return Object.entries(process.env) + .filter(([name]) => name.startsWith("SCRIPTC_") && !FRONTEND_INDEPENDENT_VARIABLES.has(name)) + .sort(([a], [b]) => a.localeCompare(b)); +} + function validFeatures(value: unknown): value is NativeLinkFeatures { if (value === null || typeof value !== "object" || Array.isArray(value)) return false; const features = value as Record; @@ -363,9 +381,7 @@ export function prepareNativeExecutable( implementation: implementationIdentity(toolchain), target: toolchain.target.name, declarations: toolchain.declarationsRoot ?? null, - environment: Object.entries(process.env) - .filter(([name]) => name.startsWith("SCRIPTC_")) - .sort(([a], [b]) => a.localeCompare(b)), + environment: frontendEnvironment(), }), ); const cached = readFrontendCache(cache, key, exclusions); @@ -493,9 +509,7 @@ export function prepareNativeLibrary( implementation: implementationIdentity(toolchain), target: toolchain.target.name, declarations: toolchain.declarationsRoot ?? null, - environment: Object.entries(process.env) - .filter(([name]) => name.startsWith("SCRIPTC_")) - .sort(([a], [b]) => a.localeCompare(b)), + environment: frontendEnvironment(), }), ); const cached = readFrontendCache(cache, key, exclusions); From 5247d2dfdd6bc2d0274151aec0d3059a5191962b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 07:28:48 -0500 Subject: [PATCH 064/230] List bench/regex-replacers among the dynamic-tier workloads Its replace callbacks receive checked-dynamic values where the lib types their parameters any: captures a match can leave unset, and results the checker types any. --- scripts/zero-cost-compare.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/zero-cost-compare.mjs b/scripts/zero-cost-compare.mjs index 7ae33d64a..df407bbb7 100644 --- a/scripts/zero-cost-compare.mjs +++ b/scripts/zero-cost-compare.mjs @@ -398,7 +398,10 @@ export function dynamicTierSymbols(names) { * later too, decodes JSON.parse trees. bench/throw-chains, also added * later, tests caught values with `instanceof`, as bench/validate-errors * does. bench/dynamic-methods and - * bench/dynamic-properties are untyped JavaScript. Every other bench workload is fully typed and must link none. The gate and + * bench/dynamic-properties are untyped JavaScript. bench/regex-replacers, + * also added later, passes checked-dynamic values to replace callbacks + * whose parameters the lib types `any`: captures a match can leave unset + * and results the checker types `any`. 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. */ @@ -421,6 +424,7 @@ export const DYNAMIC_WORKLOADS = new Set([ "bench/line-reader", "bench/json-unions", "bench/throw-chains", + "bench/regex-replacers", ]); /** Names of the functions (text and weak symbols) in `nm` output. Mach-O From 63a99cc4d94be47f7282a6df4926e8041ac1ced1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 07:37:50 -0500 Subject: [PATCH 065/230] Convert a loop's string receiver to UTF-16 at once only when the loop can read enough of it A versioned loop over a non-ASCII string passes the range of indexes it reads: a counted loop's start and limit, or a while loop's counter and bound. The runtime converts the string at once when that range has at least one unit per 16 bytes of it, and otherwise counts the loop as one indexed read, so a loop over a line of a long source text no longer keeps a UTF-16 copy of all of it. The publish call is weighted as unlikely, since loops re-entered over one string skip it. --- .../src/backend/llvm/bytes-loop-versions.ts | 48 +++++++-- .../compiler/src/backend/llvm/string-reads.ts | 8 +- .../test/string-unit-loop-emission.test.ts | 101 ++++++++++++++++-- packages/runtime/src/scr_string.c | 22 ++-- packages/runtime/src/scr_string_methods.h | 6 +- packages/runtime/test/test_string.c | 28 ++++- 6 files changed, 182 insertions(+), 31 deletions(-) diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts index de704ad42..ab941e658 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts @@ -1146,7 +1146,7 @@ export function emitBytesLoopVersionGuard( }; const lengths = receivers.map((receiver, index) => { if (index === unitsIndex) { - const read = emitUnitsLength(host, receiver); + const read = emitUnitsLength(host, receiver, () => emitUnitsSpan(host, version, index)); units.read = read; return read.length; } @@ -1359,15 +1359,49 @@ export function emitBytesLoopVersionGuard( return { units: taken.pointer, length }; } +/** The range of indexes a version's loop reads from its units receiver, as + * two doubles: a counted loop's start and limit, or a while loop's one + * counter and the receiver's bound. Both NaN when the version gives no such + * range. The runtime converts the receiver at once only when the range is + * long enough to pay for it (scr_str_scan_units_for). */ +function emitUnitsSpan( + host: LlvmEmitterContext, + version: BytesLoopVersion, + receiver: number, +): { from: string; to: string } { + // Locals and literals only: they read no string (a `length` of the + // receiver would measure it). Anything else leaves the range unbounded. + const number = (expr: IrExpr): string | null => { + if (expr.kind !== "varRef" && expr.kind !== "numLit") return null; + const value = host.emitExpr(expr); + return value.type.kind === "f64" ? value.name : null; + }; + const unbounded = { from: "0x7FF8000000000000", to: "0x7FF8000000000000" }; + const counted = version.counted; + let from: IrExpr, to: IrExpr; + if (counted) [from, to] = [counted.start, counted.limit]; + else { + const bounded = version.bounded.filter((check) => check.receiver === receiver); + if (version.counters.length !== 1 || bounded.length !== 1 || version.paired !== null) + return unbounded; + [from, to] = [version.counters[0]!.ref, bounded[0]!.bound]; + } + const start = number(from), + limit = number(to); + return start !== null && limit !== null ? { from: start, to: limit } : unbounded; +} + /** The length of a version's units receiver `s` in UTF-16 units: its byte * length when it is ASCII; otherwise, once it is the scan receiver, the * published unit count (0 when the runtime does not hold its units). When * `s` is not already the scan receiver, the runtime converts it to units - * and publishes it (scr_str_scan_units). Also the units pointer, whether - * the loop is to read it, and whether the published receiver is `s`. */ + * and publishes it if the loop's `span` makes that pay + * (scr_str_scan_units_for). Also the units pointer, whether the loop is to + * read it, and whether the published receiver is `s`. */ function emitUnitsLength( host: LlvmEmitterContext, s: string, + span: () => { from: string; to: string }, ): { length: string; pointer: string; taken: string; published: string } { const B = host.B; const sz = host.sizeType; @@ -1381,14 +1415,15 @@ function emitUnitsLength( B.startBlock(publish); // When `s` is already the scan receiver (an earlier loop over it published // it), its units stay published until the runtime clears the scan: skip - // the call. + // the call. Loops re-entered over one string take that path; the weights + // keep the call and its reach out of the loop's code. const held = B.tmp(), holds = B.tmp(); B.line(`${held} = load ptr, ptr @scr_str_scan`); B.line(`${holds} = icmp eq ptr ${held}, ${s}`); const call = B.newLabel("bytes.version.publish.units"), load = B.newLabel("bytes.version.publish.load"); - B.condBr(holds, load, call); + B.condBr(holds, load, call, `, !prof !{!"branch_weights", i32 1000, i32 1}`); B.startBlock(call); host.declare(STRING_SCAN_UNITS_DECL); const receiver = B.tmp(), @@ -1398,7 +1433,8 @@ function emitUnitsLength( countSlot = B.tmp(), count = B.tmp(), published = B.tmp(); - B.line(`call void @scr_str_scan_units(ptr ${s})`); + const { from: first, to: bound } = span(); + B.line(`call void @scr_str_scan_units_for(ptr ${s}, double ${first}, double ${bound})`); B.br(load); B.startBlock(load); B.line(`${receiver} = load ptr, ptr @scr_str_scan`); diff --git a/packages/compiler/src/backend/llvm/string-reads.ts b/packages/compiler/src/backend/llvm/string-reads.ts index 9fd2fa78c..5b7a26c6c 100644 --- a/packages/compiler/src/backend/llvm/string-reads.ts +++ b/packages/compiler/src/backend/llvm/string-reads.ts @@ -25,9 +25,11 @@ import { literalCapWord, literalCapWord32 } from "./string-key-hash.js"; export const STRING_UTF16_LEN_DECL = "declare double @scr_str_utf16_len(ptr)"; export const STRING_CHAR_CODE_AT_DECL = "declare double @scr_str_char_code_at(ptr, double)"; /** Converts a string to UTF-16 units and publishes it as the scan receiver - * (bytes-loop-versions.ts). Like the fallbacks below, it can evict an - * index entry, so it carries no memory restriction. */ -export const STRING_SCAN_UNITS_DECL = "declare void @scr_str_scan_units(ptr)"; + * when the loop about to read it can read enough of it (the other two + * operands are the range of indexes it reads; bytes-loop-versions.ts). + * Like the fallbacks below, it can evict an index entry, so it carries no + * memory restriction. */ +export const STRING_SCAN_UNITS_DECL = "declare void @scr_str_scan_units_for(ptr, double, double)"; /** The runtime fallbacks are cold: both common cases read inline. They * carry no * memory restriction: a fallback can evict an index entry, which resets diff --git a/packages/compiler/test/string-unit-loop-emission.test.ts b/packages/compiler/test/string-unit-loop-emission.test.ts index fa11af007..f522159dc 100644 --- a/packages/compiler/test/string-unit-loop-emission.test.ts +++ b/packages/compiler/test/string-unit-loop-emission.test.ts @@ -51,8 +51,11 @@ console.log(total, spaces("plain ascii text"), spaces("")); const spaces = body("spaces"); // A string that is not ASCII is converted to units and published as the - // scan receiver once, before the loop, and its copy loads units. - expect(spaces).toMatch(/call void @scr_str_scan_units\(ptr %\S+\)/); + // scan receiver once, before the loop, and its copy loads units. The + // loop runs to the length, so it gives the runtime no range (NaN). + expect(spaces).toMatch( + /call void @scr_str_scan_units_for\(ptr %\S+, double 0x7FF8000000000000, double 0x7FF8000000000000\)/, + ); // The copies follow the checked one: bytes, then units. const bytesAt = spaces.search(/^bytes\.version\.fast\d+:/m); const unitsAt = spaces.search(/^bytes\.version\.fast\.units\d+:/m); @@ -127,19 +130,27 @@ console.log(words(text), words(text.replace(/a/g, "ä")), words("plain ascii")); // the runtime to publish `s` only when they differ. const guards = [ ...words.matchAll( - /^bytes\.version\.publish\d+:[^\n]*\n\s+(%\S+) = load ptr, ptr @scr_str_scan\n\s+(%\S+) = icmp eq ptr \1, (%\S+)\n\s+br i1 \2, label %(bytes\.version\.publish\.load\d+), label %(bytes\.version\.publish\.units\d+)\n/gm, + /^bytes\.version\.publish\d+:[^\n]*\n\s+(%\S+) = load ptr, ptr @scr_str_scan\n\s+(%\S+) = icmp eq ptr \1, (%\S+)\n\s+br i1 \2, label %(bytes\.version\.publish\.load\d+), label %(bytes\.version\.publish\.units\d+)[^\n]*\n/gm, ), ]; expect(guards.length).toBeGreaterThanOrEqual(2); for (const [, , , s, load, convert] of guards) { - const block = words.slice(words.search(new RegExp(`^${convert}:`, "m"))); - expect(block).toMatch( - new RegExp( - `^${convert}:[^\\n]*\\n\\s+call void @scr_str_scan_units\\(ptr ${s}\\)\\n\\s+br label %${load}\\n`, - ), + // The block reads the loop's range and makes no other call. + const block = words + .slice(words.search(new RegExp(`^${convert}:`, "m"))) + .match(new RegExp(`^${convert}:[^\\n]*\\n([\\s\\S]*?)\\n\\s+br label %${load}\\n`))!; + expect(block).not.toBeNull(); + const lines = block[1]!.split("\n").map((line) => line.trim()); + expect(lines.at(-1)).toMatch( + new RegExp(`^call void @scr_str_scan_units_for\\(ptr ${s}, double \\S+, double \\S+\\)$`), + ); + expect(lines.slice(0, -1).filter((line) => /\bcall\b(?! double @llvm\.)/.test(line))).toEqual( + [], ); } - const calls = words.match(/call void @scr_str_scan_units\(ptr %\S+\)/g)!; + const calls = words.match( + /call void @scr_str_scan_units_for\(ptr %\S+, double \S+, double \S+\)/g, + )!; expect(calls.length).toBe(guards.length); const exe = join(dir, "main"); @@ -150,3 +161,75 @@ console.log(words(text), words(text.replace(/a/g, "ä")), words("plain ascii")); await rm(dir, { recursive: true, force: true }); } }); + +test("loops over part of a string give the runtime the range of indexes they read", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-unit-reach-")); + try { + const entry = join(dir, "main.ts"); + await writeFile( + entry, + ` +function trim(text: string, start: number, end: number): string { + while (start < end && text.charCodeAt(start) === 32) start++; + return text.substring(start, end); +} +function hasBreak(text: string, pos: number, end: number): boolean { + for (let i = pos; i < end; i++) if (text.charCodeAt(i) === 10) return true; + return false; +} +let text = ""; +for (let k = 0; k < 2000; k++) text += k % 7 === 0 ? " café\\n" : " naïve façade"; +let kept = 0, + breaks = 0; +for (let at = 0; at + 12 < text.length; at += 12) { + kept += trim(text, at, at + 12).length; + if (hasBreak(text, at, at + 12)) breaks++; +} +console.log(kept, breaks); +`, + ); + const outPath = join(dir, "main.ir.json"); + const lowered = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!lowered.ok) + throw new Error(lowered.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const ll = emitLlvmModule(deserializeModule(await readFile(outPath, "utf8"))); + const body = (name: string): string => + ll.match( + new RegExp( + `define internal [^\\n]+ @sc_bf_${name}(?:\\.i32)?\\([^\\n]*\\) #0 \\{[\\s\\S]*?\\n\\}`, + ), + )![0]; + // The publish block passes the loop's range with the receiver: the + // counter and the bound (a while loop), or the start and the limit (a + // counted loop), here the function's parameters. + for (const name of ["trim", "hasBreak"]) { + const block = body(name).match( + /^bytes\.version\.publish\.units\d+:[^\n]*\n([\s\S]*?)\n\s+br label %bytes\.version\.publish\.load\d+\n/m, + )!; + expect(block, name).not.toBeNull(); + const [, lines] = block; + const call = lines!.match( + /call void @scr_str_scan_units_for\(ptr %\S+, double (%\S+), double (%\S+)\)/, + )!; + expect(call, name).not.toBeNull(); + expect(call[1]).not.toBe(call[2]); + } + + const exe = join(dir, "main"); + const built = await compile(entry, { outputKind: "exe", outDir: dir, outPath: exe }); + if (!built.ok) throw new Error(JSON.stringify(built.diagnostics)); + let text = ""; + for (let k = 0; k < 2000; k++) text += k % 7 === 0 ? " café\n" : " naïve façade"; + let kept = 0, + breaks = 0; + for (let at = 0; at + 12 < text.length; at += 12) { + let start = at; + while (start < at + 12 && text.charCodeAt(start) === 32) start++; + kept += at + 12 - start; + if (text.slice(at, at + 12).includes("\n")) breaks++; + } + expect(execFileSync(exe, { encoding: "utf8" })).toBe(`${kept} ${breaks}\n`); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index dfd0d23f8..1e7802fbb 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -1889,14 +1889,24 @@ static bool scr_sidx_units(const ScrStr *s, ScrSidx *e) { return true; } -/* A loop about to read every unit of s (bytes-loop-versions.ts) converts it - * at once instead of after the threshold's reads, and publishes it as the - * scan's receiver. ASCII receivers and receivers past SCR_SIDX_UNITS_MAX - * publish nothing; the caller compares scr_str_scan.s with s. */ -void scr_str_scan_units(ScrStr *s) { +/* A loop about to read s (bytes-loop-versions.ts) between the indexes + * `from` and `to` (NaN: no bound) converts it at once instead of after the + * threshold's reads, and publishes it as the scan's receiver, when that + * range has at least one unit per 16 bytes of s (the threshold's ratio). + * A shorter loop, such as one over a line of a source text, counts as one + * read, so a short loop does not keep all of a long string's units. ASCII + * receivers and receivers past SCR_SIDX_UNITS_MAX publish nothing; the + * caller compares scr_str_scan.s with s. */ +void scr_str_scan_units_for(ScrStr *s, double from, double to) { if (s->ascii || s->len > SCR_SIDX_UNITS_MAX) return; ScrSidx *e = scr_sidx(s); - if (!e->units) e->units = scr_str_units(s, &e->nunits); + if (!e->units) { + if (fabs(to - from) * 16 < (double)s->len) { + (void)scr_sidx_units(s, e); + return; + } + e->units = scr_str_units(s, &e->nunits); + } if (scr_str_scan.s != s) { scr_str_scan.s = s; scr_str_scan.units = e->units; diff --git a/packages/runtime/src/scr_string_methods.h b/packages/runtime/src/scr_string_methods.h index 820b1c9e6..01ccdd8ae 100644 --- a/packages/runtime/src/scr_string_methods.h +++ b/packages/runtime/src/scr_string_methods.h @@ -43,9 +43,11 @@ extern SCR_TL ScrStrScan scr_str_scan; double scr_str_char_code_at(ScrStr *s, double i); /* Converts a non-ASCII s up to the units cap to UTF-16 at once and - * publishes it in scr_str_scan, for a loop about to read all of it. Leaves + * publishes it in scr_str_scan, for a loop about to read it between the + * indexes `from` and `to` (NaN: no bound), when that range has at least + * one unit per 16 bytes. A shorter loop counts as one indexed read. Leaves * scr_str_scan unchanged for any other s. */ -void scr_str_scan_units(ScrStr *s); +void scr_str_scan_units_for(ScrStr *s, double from, double to); /* indexOf(needle, fromIndex): UTF-16 index of the first occurrence at or * after fromIndex (clamped to [0, length]), or -1. Empty needle returns the diff --git a/packages/runtime/test/test_string.c b/packages/runtime/test/test_string.c index 3e14a5958..f41c2de50 100644 --- a/packages/runtime/test/test_string.c +++ b/packages/runtime/test/test_string.c @@ -523,27 +523,45 @@ static void short_string_asserts(void) { } /* A loop's publish converts a non-ASCII receiver before any read, while a - * lone read still walks bytes. ASCII receivers publish nothing. */ + * lone read still walks bytes. ASCII receivers publish nothing. A loop + * that can read fewer than one unit per 16 bytes counts as one read. */ static void scan_units_asserts(void) { ScrStr *s = scr_str_new("a\xC3\xA9\xF0\x9F\x98\x80" "b", 8); /* a é 😀 b */ static const double codes[] = {97, 233, 0xD83D, 0xDE00, 98}; if (scr_str_char_code_at(s, 0) != 97 || scr_str_scan.s == s) failed++; - scr_str_scan_units(s); + scr_str_scan_units_for(s, NAN, NAN); if (scr_str_scan.s != s || scr_str_scan.n != 5) failed++; for (size_t i = 0; i < 5; i++) if (scr_str_scan.n == 5 && (scr_str_scan.units[i] != codes[i] || scr_str_char_code_at(s, (double)i) != codes[i])) failed++; if (!isnan(scr_str_char_code_at(s, 5))) failed++; - scr_str_scan_units(s); /* republishing keeps the same units */ + scr_str_scan_units_for(s, NAN, NAN); /* republishing keeps the same units */ const uint16_t *units = scr_str_scan.units; - scr_str_scan_units(s); + scr_str_scan_units_for(s, 0, 1); if (scr_str_scan.s != s || scr_str_scan.units != units) failed++; ScrStr *ascii = scr_str_new("plain", 5); - scr_str_scan_units(ascii); + scr_str_scan_units_for(ascii, NAN, NAN); if (scr_str_scan.s != s) failed++; scr_str_release(ascii); scr_str_release(s); if (scr_str_scan.s != NULL) failed++; + /* 200 units in 400 bytes: a loop over 25 indexes converts at once. */ + char text[400]; + for (size_t i = 0; i < sizeof text; i += 2) memcpy(text + i, "\xC3\xA9", 2); + ScrStr *wide = scr_str_new(text, sizeof text); + scr_str_scan_units_for(wide, 175, 200); + if (scr_str_scan.s != wide || scr_str_scan.n != 200 || scr_str_scan.units[199] != 233) failed++; + scr_str_release(wide); + /* A loop over 24 does not; each such loop counts as one read, so the + * 32nd converts (32 reads, one per 12.5 bytes). */ + ScrStr *line = scr_str_new(text, sizeof text); + scr_str_scan_units_for(line, 24, 0); + if (scr_str_scan.s == line) failed++; + for (int i = 1; i < 31; i++) scr_str_scan_units_for(line, i, i + 1); + if (scr_str_scan.s == line) failed++; + scr_str_scan_units_for(line, 31, 32); + if (scr_str_scan.s != line || scr_str_scan.n != 200) failed++; + scr_str_release(line); total++; } #ifdef SCR_SIDX_TEST From 390b404251c2894582740f1373937eb9d7d659bc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 07:46:24 -0500 Subject: [PATCH 066/230] Keep a union loop binding in the debug-variables fixture The fixture's loop iterated `[6, 7] as (number | undefined)[]` so that its binding kept the union layout the LLDB check reads through `item->tag` and `item->slot.arm0`. Since (T | undefined)[] stores T elements, an array that never holds undefined gives a `number` binding, and the check failed with "item is not a pointer". The loop now iterates `[6, 7, undefined]`, whose binding needs the union, and the check also reads the undefined arm's tag. --- packages/compiler/test/debug-variables.test.ts | 3 +++ tests/corpus/3068-debug-variables/main.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/compiler/test/debug-variables.test.ts b/packages/compiler/test/debug-variables.test.ts index ea92cc1d3..946f0f5d1 100644 --- a/packages/compiler/test/debug-variables.test.ts +++ b/packages/compiler/test/debug-variables.test.ts @@ -63,6 +63,8 @@ test.runIf(process.platform === "darwin")( "continue", "frame variable item->tag item->slot.arm0", "continue", + "frame variable item->tag", + "continue", "frame variable hoisted", "continue", "frame variable moduleValue", @@ -91,6 +93,7 @@ test.runIf(process.platform === "darwin")( "item->tag = f64", "item->slot.arm0 = 6", "item->slot.arm0 = 7", + "item->tag = undefined", "hoisted = 13", ]) { expect(debug.stdout).toContain(value); diff --git a/tests/corpus/3068-debug-variables/main.ts b/tests/corpus/3068-debug-variables/main.ts index d9399b49d..2e7cef4d6 100644 --- a/tests/corpus/3068-debug-variables/main.ts +++ b/tests/corpus/3068-debug-variables/main.ts @@ -21,7 +21,7 @@ function inspect(input: number, enabled: boolean, fallback = 9): void { for (let i = 0; i < bytes.length; i++) { console.log("loop", i, bytes[i]); // debug: loop } - for (const item of [6, 7] as (number | undefined)[]) { + for (const item of [6, 7, undefined]) { console.log("item"); // debug: item console.log(item); } From 2823840134976580d129093f6f95cbf1a534f50f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 07:49:06 -0500 Subject: [PATCH 067/230] Miss non-object receivers early in the property probes and inline the entry check Since the probes began caching absent properties (#2186), an untyped property read that reaches the load or get probe costs more than before: - Receivers that are not ordinary objects, such as class instances, have no shape and always miss, but they set up the probe's frame first. The exported probes now return the miss for them before calling the probe body, which stays out of line. - Each matching entry was answered through an out-of-line call (scr_js_entry_answer). It is inlined into the probe again. dynamic-properties executed 3.1% more instructions than before #2186; it now executes 0.4% fewer. --- packages/runtime/src/scr_js_object.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/runtime/src/scr_js_object.c b/packages/runtime/src/scr_js_object.c index e97651557..e3d0b3a54 100644 --- a/packages/runtime/src/scr_js_object.c +++ b/packages/runtime/src/scr_js_object.c @@ -308,13 +308,10 @@ static bool scr_js_absent_hit(const ScrDyn *receiver, const ScrJsIcEntry *entry) walked->v.obj.shape == entry->holder_shape && walked->prototype == NULL; } -/* A load site's probe. With `absent`, a read of a key that the receiver and - * its chain of plain shaped objects (ending in the default prototype) lack - * answers undefined and caches that answer. Method reads leave absent - * methods to the slow path, which names the missing function. */ /* The answer of a matching entry for `receiver`, or NULL when the entry no * longer holds. */ -static ScrDyn *scr_js_entry_answer(ScrDyn *receiver, const ScrJsIcEntry *entry) { +static inline __attribute__((always_inline)) ScrDyn *scr_js_entry_answer(ScrDyn *receiver, + const ScrJsIcEntry *entry) { if (entry->target == &scr_js_absent) return scr_js_absent_hit(receiver, entry) ? scr_dyn_retain(scr_dyn_undefined()) : NULL; return scr_js_load_hit(receiver, entry); @@ -345,7 +342,8 @@ static void scr_js_load_fill(ScrJsIc *ic, uint32_t stale, ScrJsSharedLoad *share * its chain of plain shaped objects (ending in the default prototype) lack * answers undefined and caches that answer. Method reads leave absent * methods to the slow path, which names the missing function. */ -static ScrDyn *scr_js_probe_load(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic, bool absent) { +static __attribute__((noinline)) ScrDyn *scr_js_probe_load(ScrDyn *receiver, const ScrStr *key, + ScrJsIc *ic, bool absent) { const ScrJsShape *shape = scr_js_shape_of(receiver); if (!shape) return NULL; uint32_t stale = UINT32_MAX; @@ -397,11 +395,15 @@ static ScrDyn *scr_js_probe_load(ScrDyn *receiver, const ScrStr *key, ScrJsIc *i return scr_dyn_retain(found->value); } +/* Receivers other than ordinary objects have no shape: they miss before + * the probe sets up its frame. */ ScrDyn *scr_js_load_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { + if (receiver->kind != SCR_DYN_OBJ) return NULL; return scr_js_probe_load(receiver, key, ic, false); } ScrDyn *scr_js_get_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { + if (receiver->kind != SCR_DYN_OBJ) return NULL; return scr_js_probe_load(receiver, key, ic, true); } From b28af56ba87803d8ec352d709a9649803df3c9d1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 07:53:02 -0500 Subject: [PATCH 068/230] Skip the weak-key table when freeing objects that were never keys Once a program creates a weak collection, every object freed afterwards called into the weak-key table to remove entries keyed by it, although almost none are keys: zod's validation freed 16.6 million objects against 23 keys. A per-thread bit filter indexed by the object's address now marks every address that entered the table (cleared when the table empties), and the free paths test it inline, so an object whose bit is clear makes no call. --- .../corpus/weak-keys-freed-reused.mjs.json | 6 ++++ packages/runtime/src/scr_json.c | 19 +++++++++-- tests/corpus/weak-keys-freed-reused.mjs | 32 +++++++++++++++++++ .../weak-keys-freed-reused.mjs.json | 1 + 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/weak-keys-freed-reused.mjs.json create mode 100644 tests/corpus/weak-keys-freed-reused.mjs create mode 100644 tests/harness/corpus-costs/weak-keys-freed-reused.mjs.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/weak-keys-freed-reused.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/weak-keys-freed-reused.mjs.json new file mode 100644 index 000000000..ab4ca8bda --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/weak-keys-freed-reused.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/weak-keys-freed-reused.mjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 43b107ae3..cd9eeecde 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1165,8 +1165,20 @@ static bool scr_dyn_to_primitive_result_is_object(const ScrDyn *d); * are never the native records the kept-source table is keyed by. */ static SCR_TL bool scr_weak_observing; static void scr_weak_disposed(void *key); +/* One bit per key address, set when a key enters the weak-key table: a + * disposed object whose bit is clear was never a key, so its disposal makes + * no call. Bits are cleared when the table empties. */ +#define SCR_WEAK_KEY_FILTER_BITS ((size_t)1 << 16) +static SCR_TL uint64_t scr_weak_key_filter[SCR_WEAK_KEY_FILTER_BITS / 64]; +static inline size_t scr_weak_key_filter_bit(const void *key) { + return ((uintptr_t)key >> 4) & (SCR_WEAK_KEY_FILTER_BITS - 1); +} +static inline bool scr_weak_maybe_key(const void *key) { + size_t bit = scr_weak_key_filter_bit(key); + return scr_weak_observing && (scr_weak_key_filter[bit >> 6] & (UINT64_C(1) << (bit & 63))); +} static inline void scr_dyn_weak_dispose(ScrDyn *d) { - if (scr_weak_observing) scr_weak_disposed(d); + if (scr_weak_maybe_key(d)) scr_weak_disposed(d); } /* A disposed node's child: most getters, setters, prototypes and symbol @@ -3479,7 +3491,7 @@ static void scr_record_source_disposed(void *rec) { /* The single native dispose hook serves weak collections and the table * above; each observer runs only while it has entries to forget. */ static void scr_dispose_observers(void *object) { - if (scr_weak_observing) scr_weak_disposed(object); + if (scr_weak_maybe_key(object)) scr_weak_disposed(object); if (scr_record_source_count) scr_record_source_disposed(object); } @@ -17413,6 +17425,8 @@ static ScrWeakKeySlot *scr_weak_key_slot_put(void *key) { scr_weak_keys[j].key = key; scr_weak_keys[j].head = NULL; scr_weak_keys_used++; + size_t bit = scr_weak_key_filter_bit(key); + scr_weak_key_filter[bit >> 6] |= UINT64_C(1) << (bit & 63); } return &scr_weak_keys[j]; } @@ -17422,6 +17436,7 @@ static void scr_weak_key_slot_delete(ScrWeakKeySlot *slot) { if (--scr_weak_keys_used == 0) { free(scr_weak_keys); scr_weak_keys = NULL; + memset(scr_weak_key_filter, 0, sizeof scr_weak_key_filter); return; } for (size_t k = i;;) { diff --git a/tests/corpus/weak-keys-freed-reused.mjs b/tests/corpus/weak-keys-freed-reused.mjs new file mode 100644 index 000000000..b24823526 --- /dev/null +++ b/tests/corpus/weak-keys-freed-reused.mjs @@ -0,0 +1,32 @@ +// Weak collection keys that become unreachable leave the collection, and +// new objects allocated afterwards (which can reuse the freed storage) are +// not found in it: plain objects, arrays, class instances and functions, +// alongside many objects that were never keys. +class Point { + constructor(x) { + this.x = x; + } +} +const map = new WeakMap(); +const set = new WeakSet(); +const kept = []; +function fill(round) { + for (let i = 0; i < 200; i++) { + const key = i % 4 === 0 ? { i } : i % 4 === 1 ? [i] : i % 4 === 2 ? new Point(i) : () => i; + map.set(key, round * 1000 + i); + set.add(key); + if (i % 50 === 0) kept.push(key); + } +} +let stale = 0; +let found = 0; +for (let round = 0; round < 20; round++) { + fill(round); + // Objects that were never keys, freed and reallocated in between. + for (let i = 0; i < 500; i++) { + const other = i % 2 ? { other: i } : new Point(-i); + if (map.has(other) || set.has(other)) stale++; + } +} +for (const key of kept) if (map.has(key) && set.has(key)) found++; +console.log(stale, found, kept.length, map.get(kept[0]), map.get(kept[kept.length - 1])); diff --git a/tests/harness/corpus-costs/weak-keys-freed-reused.mjs.json b/tests/harness/corpus-costs/weak-keys-freed-reused.mjs.json new file mode 100644 index 000000000..738211b5e --- /dev/null +++ b/tests/harness/corpus-costs/weak-keys-freed-reused.mjs.json @@ -0,0 +1 @@ +{ "plain": 5167, "san": 10508 } From b1a68147966691e22246422c80ef03d6e0d533cf Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 08:06:24 -0500 Subject: [PATCH 069/230] Prove typed-array loop bounds in worker programs when a view has shared storage A typed-array loop version in a worker program tested its bounds and its views' ownership together: every view had to have no shared storage. A loop over views of a SharedArrayBuffer always failed that guard and ran the checked copy, testing every element's index. The guard now tests the bounds first and the ownership after. When the bounds hold and every view is unshared, the loop runs the owned copy as before. When a view is shared, it runs a new third copy whose accesses skip their bounds checks and keep the shared-or-local path, without alias scopes. Versions with a UTF-16 units copy, and programs without workers, are unchanged. Corpus 8350 runs such loops over shared, unshared and mixed views, to counts within and past the views and over subarrays, with a worker writing through shared views. The emission test checks the split guard and the shared copy's accesses. --- .../backend/llvm/bytes-loop-versions.test.ts | 10 ++- .../src/backend/llvm/bytes-loop-versions.ts | 38 +++++++- .../src/backend/llvm/emit-statements.ts | 20 ++++- .../8350-worker-shared-loop-bounds.ts.json | 6 ++ ...orker-shared-loop-version-emission.test.ts | 74 ++++++++++++++++ .../corpus/8350-worker-shared-loop-bounds.ts | 86 +++++++++++++++++++ 6 files changed, 227 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8350-worker-shared-loop-bounds.ts.json create mode 100644 packages/compiler/test/worker-shared-loop-version-emission.test.ts create mode 100644 tests/corpus/8350-worker-shared-loop-bounds.ts diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts index 096712c97..0fa8fea34 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts @@ -317,13 +317,19 @@ test("worker programs test each receiver's shared storage once in the guard", () const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0]; const guard = body.slice(0, body.search(/^bytes\.version\.(fast|slow)\d+:$/m)); expect(guard.match(/icmp eq ptr %\S+, null/g)).toHaveLength(2); - const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec(body)![0]; + const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.fast\.shared\d+:$/m.exec(body)![0]; expect(fast).not.toContain("bytes.shared"); expect(fast).not.toMatch(/(load|store) atomic double/); - // The checked copy keeps the shared-memory path for each access. + // The checked copy keeps the shared-memory path for each access, and so + // does the copy for shared views whose bounds hold. const slow = body.slice(body.search(/^bytes\.version\.slow\d+:$/m), body.indexOf(fast)); expect(slow).toContain("load atomic double"); expect(slow).toContain("store atomic double"); + const shared = /^bytes\.version\.fast\.shared\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec( + body, + )![0]; + expect(shared).toContain("load atomic double"); + expect(shared).toContain("store atomic double"); }); test("index temps of a version's counter are integers in the fast copy", () => { diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts index de704ad42..472934f72 100644 --- a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts @@ -20,6 +20,7 @@ import { } from "./counted-loops.js"; import { emitBytesData, emitBytesHeaderLength, emitBytesSharedStorage } from "./expr-bytes.js"; import { f64Lit } from "./common.js"; +import { InternalCompilerError } from "../../errors.js"; import { emitStringAsciiFlag, emitStringByteLength, @@ -85,8 +86,15 @@ export interface BytesLoopVersion { /** Root receiver bindings, read once before the loop; `string` marks a * string whose `charCodeAt` and `charAt` reads the version covers. */ receivers: { ref: VarRef; global: boolean; string: boolean }[]; - /** Receivers the guard requires to have no shared storage. */ + /** Receivers the guard requires to have no shared storage for the fast + * copy. */ ownedReceivers: number[]; + /** Worker programs: when the bounds hold but a receiver in + * `ownedReceivers` has shared storage, the loop runs a second fast copy + * instead of the checked one. It skips the bounds checks of `bounds` and + * keeps shared-safe accesses (emitBytesLoopVersionGuard's `sharedFast`). Set + * when the version proves bounds as well as ownership. */ + sharedCopy: boolean; /** The string reads among `bounds`. */ stringReads: ReadonlySet; /** The version's one string receiver, when the loop may also run over @@ -752,6 +760,7 @@ export function matchBytesLoopVersion( owned: ownedNodes, receivers: order.map((index) => receivers[index]!), ownedReceivers: ownedKept.map(([receiver]) => renumber.get(receiver)!), + sharedCopy: ownedKept.length > 0 && bounds.size > 0, stringReads, unitsReceiver, unitLengths: unitLengths ?? new Set(), @@ -1102,6 +1111,10 @@ function findWhileCounterAccesses( * storage, and to `slow` otherwise (including a counted loop whose * integer form is not established, or a module receiver not yet set). * + * With `sharedFast` (a version with `sharedCopy`), the ownership tests are + * separate: when everything else holds but a view has shared storage, the + * guard branches to `sharedFast` instead of `slow`. + * * With `unitsFast`, the version's units receiver may instead be a string * that is not ASCII: the guard has the runtime publish it as the scan * receiver (one read), tests the same accesses against its UTF-16 length, @@ -1115,6 +1128,7 @@ export function emitBytesLoopVersionGuard( fast: string, slow: string, unitsFast?: string, + sharedFast?: string, ): { units: string | null; length: string | null } | null { const B = host.B; const next = (cond: string): void => { @@ -1165,10 +1179,13 @@ export function emitBytesLoopVersionGuard( if (string && index !== unitsIndex) conditions.push(emitStringAsciiFlag(host, receivers[index]!)); }); + // Without `sharedFast`, shared storage sends the loop to the checked copy + // with the other failed tests; with it, only to the shared fast copy. + const ownership: string[] = []; for (const index of version.ownedReceivers) { const owned = B.tmp(); B.line(`${owned} = icmp eq ptr ${emitBytesSharedStorage(host, receivers[index]!)}, null`); - conditions.push(owned); + (sharedFast !== undefined ? ownership : conditions).push(owned); } for (const check of version.fixed) { const ok = B.tmp(); @@ -1351,7 +1368,22 @@ export function emitBytesLoopVersionGuard( const length = version.unitsReceiver !== null ? lengths[version.unitsReceiver]! : null; const taken = units.read; const target = taken ? B.newLabel("bytes.version.units") : fast; - if (valid === "true") B.br(target); + if (sharedFast !== undefined && taken) + throw new InternalCompilerError("llvm emitter bug: a shared fast copy with a units copy"); + if (sharedFast !== undefined && ownership.length > 0) { + // The bounds hold: unshared views take the owned copy. + const split = B.newLabel("bytes.version.owned"); + let owned = ownership[0]!; + for (const cond of ownership.slice(1)) { + const all = B.tmp(); + B.line(`${all} = and i1 ${owned}, ${cond}`); + owned = all; + } + if (valid === "true") B.br(split); + else B.condBr(valid, split, slow); + B.startBlock(split); + B.condBr(owned, target, sharedFast); + } else if (valid === "true") B.br(target); else B.condBr(valid, target, slow); if (!taken) return { units: null, length }; B.startBlock(target); diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index cbdeb0558..dc4e5090c 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -2139,7 +2139,9 @@ function declareAliasScopes(emitter: LlEmitter, scopes: readonly string[]): void * fast copy is only emitted when every resume point exists. */ /** A typed-array loop version: the guard, then the checked copy, which * runs whenever the guard fails, and the copy whose accesses the guard - * proves in bounds (or thread-owned). */ + * proves in bounds (or thread-owned). In worker programs a version that + * proves both also has a copy for views with shared storage, in bounds but + * not owned. */ function emitVersionedLoop( emitter: LlEmitter, s: IrStmt & { kind: "for" | "while" }, @@ -2151,7 +2153,11 @@ function emitVersionedLoop( done = B.newLabel("bytes.version.done"); const unitsFast = version.unitsReceiver !== null ? B.newLabel("bytes.version.fast.units") : undefined; - const guard = emitBytesLoopVersionGuard(emitter, version, fast, slow, unitsFast); + const sharedFast = + version.sharedCopy && unitsFast === undefined + ? B.newLabel("bytes.version.fast.shared") + : undefined; + const guard = emitBytesLoopVersionGuard(emitter, version, fast, slow, unitsFast, sharedFast); const units = guard?.units ?? null; const originalBounds = emitter.bytesBounds, originalOwned = emitter.bytesOwned, @@ -2190,6 +2196,16 @@ function emitVersionedLoop( ]); emitter.emitStmt(s); B.br(done); + if (sharedFast !== undefined) { + // The bounds hold and a view has shared storage: the accesses skip + // their bounds checks and stay shared-safe, without alias scopes + // (which worker programs give only thread-owned views). + B.startBlock(sharedFast); + emitter.bytesOwned = originalOwned; + emitter.bytesAliasScopes = originalScopes; + emitter.emitStmt(s); + B.br(done); + } if (unitsFast !== undefined && units !== null) { // The string reads take the receiver's UTF-16 units. B.startBlock(unitsFast); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8350-worker-shared-loop-bounds.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8350-worker-shared-loop-bounds.ts.json new file mode 100644 index 000000000..db4376635 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8350-worker-shared-loop-bounds.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8350-worker-shared-loop-bounds.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/worker-shared-loop-version-emission.test.ts b/packages/compiler/test/worker-shared-loop-version-emission.test.ts new file mode 100644 index 000000000..7eebcc2e4 --- /dev/null +++ b/packages/compiler/test/worker-shared-loop-version-emission.test.ts @@ -0,0 +1,74 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile, deserializeModule } from "../src/index.js"; +import { emitLlvmModule } from "../src/backend/llvm/emitter.js"; + +async function emitted(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-worker-shared-loop-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ir.json"); + await writeFile(entry, source); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return emitLlvmModule(deserializeModule(await readFile(outPath, "utf8"))); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +const kernel = ` +function sumTo(view: Float64Array, n: number): number { + let total = 0; + for (let i = 0; i < n; i++) total += view[i]!; + return total; +} +`; + +function body(ll: string, name: string): string { + return new RegExp(`^define internal [^\\n]*@sc_bf_${name}(?:\\.i32)?\\([^]*?^}`, "m").exec( + ll, + )![0]; +} + +/** The instructions of the blocks whose labels start with `prefix`. */ +function blocks(fn: string, prefix: string): string { + return fn + .split(/\n(?=[\w.$-]+:)/) + .filter((block) => block.startsWith(prefix)) + .join("\n"); +} + +test("a worker program's loop over views that may be shared keeps a bounds-proven copy", async () => { + const ll = await emitted(` +import { Worker, isMainThread } from "node:worker_threads"; +${kernel} +if (isMainThread) { + const view = new Float64Array(new SharedArrayBuffer(64)); + console.log(sumTo(view, 8)); + new Worker(new URL(import.meta.url)); +} +`); + const fn = body(ll, "sumTo"); + // The guard tests the bounds first, then whether the view is shared. + expect(fn).toMatch(/^bytes\.version\.owned\d*:/m); + expect(fn).toMatch(/^bytes\.version\.fast\.shared\d*:/m); + // The shared copy reads with no bounds test, and shared-safe: a view + // with shared storage loads atomically. + const shared = blocks(fn, "bytes.version.fast.shared"); + expect(shared).not.toMatch(/icmp (ult|uge|ugt) i64 %\w+, %\w+/); + expect(fn).toMatch(/load atomic double, ptr %\w+ monotonic/); +}); + +test("a program without workers keeps two copies of the loop", async () => { + const ll = await emitted(`${kernel} +console.log(sumTo(new Float64Array(8), 8)); +`); + const fn = body(ll, "sumTo"); + expect(fn).toMatch(/^bytes\.version\.fast\d*:/m); + expect(fn).not.toMatch(/bytes\.version\.fast\.shared/); + expect(fn).not.toMatch(/bytes\.version\.owned/); +}); diff --git a/tests/corpus/8350-worker-shared-loop-bounds.ts b/tests/corpus/8350-worker-shared-loop-bounds.ts new file mode 100644 index 000000000..e5fd1ea9e --- /dev/null +++ b/tests/corpus/8350-worker-shared-loop-bounds.ts @@ -0,0 +1,86 @@ +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +// Typed-array loops in a worker program that run to a count passed in, not +// to a view's length, over views of a SharedArrayBuffer, of a thread's own +// buffer, and both in one loop. Counts within the views, past their ends +// (reads give undefined, writes are dropped) and over subarrays at an +// offset. A worker's writes through shared views are visible to the parent. + +interface Job { + shared: SharedArrayBuffer; + n: number; +} + +function rowSums(u: Float64Array, v: Float64Array, n: number, rows: number): void { + for (let i = 0; i < rows; i++) { + let t = 0; + for (let j = 0; j < n; j++) t += u[j]! / (i + j + 1); + v[i] = t; + } +} + +function copyScaled(src: Int32Array, dst: Int32Array, n: number, k: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + dst[i] = (src[i]! * k) | 0; + sum += dst[i]!; + } + return sum; +} + +function sumTo(view: Float64Array, n: number): number { + let total = 0; + for (let i = 0; i < n; i++) total += view[i]!; + return total; +} + +function fixed(view: Float64Array, count: number): string { + const parts: string[] = []; + for (let i = 0; i < count; i++) parts.push(view[i]!.toFixed(6)); + return parts.join(" "); +} + +function run(label: string, u: Float64Array, v: Float64Array, ints: Int32Array, out: Int32Array, n: number): string { + rowSums(u, v, n, 4); + const scaled = copyScaled(ints, out, n, 3); + return `${label}: rows ${fixed(v, 4)}; scaled ${scaled}; tail ${sumTo(u, n + 3)}`; +} + +function views(buffer: ArrayBufferLike, n: number): { u: Float64Array; v: Float64Array; ints: Int32Array; out: Int32Array } { + const u = new Float64Array(buffer, 0, n); + const v = new Float64Array(buffer, 8 * n, n); + const ints = new Int32Array(buffer, 16 * n, n); + const out = new Int32Array(buffer, 20 * n, n); + for (let i = 0; i < n; i++) { + u[i] = 1 + i * 0.25; + ints[i] = i * 7 - 20; + } + return { u, v, ints, out }; +} + +if (isMainThread) { + const n = 12; + const shared = new SharedArrayBuffer(24 * n); + const own = views(new ArrayBuffer(24 * n), n); + console.log(run("own", own.u, own.v, own.ints, own.out, n)); + const sh = views(shared, n); + console.log(run("shared", sh.u, sh.v, sh.ints, sh.out, n)); + console.log(run("shared, count past the end", sh.u, sh.v, sh.ints, sh.out, n + 5)); + const mixed = new Float64Array(n); + rowSums(sh.u, mixed, n, 4); + console.log("mixed", fixed(mixed, 4)); + console.log("subarray", sumTo(sh.u.subarray(3), n - 3), sumTo(sh.u.subarray(3), n)); + const worker = new Worker(new URL(import.meta.url), { workerData: { shared, n } satisfies Job }); + worker.on("message", (line: string) => { + console.log(line); + console.log("parent sees", sh.out.subarray(0, 5).join(","), sh.v[0]!.toFixed(6)); + }); +} else { + const { shared, n } = workerData as Job; + const u = new Float64Array(shared, 0, n); + const v = new Float64Array(shared, 8 * n, n); + const ints = new Int32Array(shared, 16 * n, n); + const out = new Int32Array(shared, 20 * n, n); + for (let i = 0; i < n; i++) ints[i] = i * 5 + 1; + parentPort!.postMessage(run("worker", u, v, ints, out, n)); +} From c6679d43fbc63840755e9e2e5c0e3a3063537284 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 08:18:46 -0500 Subject: [PATCH 070/230] Add no empty chunk to a byte-mode Readable's buffer push() of an empty Buffer outside object mode put a zero-length entry into the read buffer, and the chunks pushed after it stalled behind it: a 'data' listener saw nothing more and a readline interface never closed. Node's readableAddChunk adds no empty chunk outside object mode; it clears the reading flag and may read more. The runtime now does the same, as it already did for an encoded push that decodes to nothing. --- .../corpus/7516-readable-empty-push.ts.json | 6 ++ packages/runtime/src/scr_stream.c | 8 ++- tests/corpus/7516-readable-empty-push.ts | 69 +++++++++++++++++++ .../7516-readable-empty-push.ts.json | 1 + 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7516-readable-empty-push.ts.json create mode 100644 tests/corpus/7516-readable-empty-push.ts create mode 100644 tests/harness/corpus-costs/7516-readable-empty-push.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7516-readable-empty-push.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7516-readable-empty-push.ts.json new file mode 100644 index 000000000..9388c4f9a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7516-readable-empty-push.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7516-readable-empty-push.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 406d91599..201d6e34b 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -954,8 +954,14 @@ static bool scr_stream_add_chunk(ScrStream *s, ScrBytes *chunk, bool front) { } entry = dec; /* owned */ } else { - entry = scr_bytes_retain(chunk); entry_len = chunk->len; + if (entry_len == 0) { + /* Node's readableAddChunk adds no empty chunk outside object mode: + * it clears reading and may read more */ + if (!front) scr_stream_maybe_read_more(s); + return !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0); + } + entry = scr_bytes_retain(chunk); } if (entry_len > 0 && st->r.flowing == 1 && st->r.length == 0 && !st->r.in_read_sync && !front && diff --git a/tests/corpus/7516-readable-empty-push.ts b/tests/corpus/7516-readable-empty-push.ts new file mode 100644 index 000000000..27d88e3ed --- /dev/null +++ b/tests/corpus/7516-readable-empty-push.ts @@ -0,0 +1,69 @@ +// readable.push() of an empty Buffer outside object mode adds nothing, as +// in Node: the chunks pushed after it still reach 'data' listeners, +// readline and read(), and push(null) still ends the stream. In object +// mode an empty Buffer is a value like any other. +import { createInterface } from "node:readline"; +import { Readable } from "node:stream"; + +function lines(chunks: string[]): Promise { + return new Promise((resolve) => { + const src = new Readable({ read() {} }); + const rl = createInterface({ input: src, crlfDelay: Infinity }); + rl.on("line", (line: string) => console.log("line", JSON.stringify(line))); + rl.on("close", () => { + console.log("readline closed"); + resolve(); + }); + for (const chunk of chunks) src.push(Buffer.from(chunk, "utf8")); + src.push(null); + }); +} + +function data(): Promise { + return new Promise((resolve) => { + const src = new Readable({ read() {} }); + src.on("data", (chunk: Buffer) => console.log("data", JSON.stringify(String(chunk)))); + src.on("end", () => { + console.log("data end"); + resolve(); + }); + console.log("push a", src.push(Buffer.from("a"))); + console.log("push empty", src.push(Buffer.alloc(0))); + console.log("push b", src.push(Buffer.from("b"))); + src.push(null); + }); +} + +function paused(): void { + const src = new Readable({ read() {} }); + src.push(Buffer.from("x")); + src.push(Buffer.alloc(0)); + src.push(Buffer.from("y")); + console.log("readableLength", src.readableLength); + console.log("read", JSON.stringify(String(src.read()))); +} + +function objectMode(): Promise { + return new Promise((resolve) => { + const src = new Readable({ objectMode: true, read() {} }); + src.on("data", (chunk: Buffer) => console.log("object chunk length", chunk.length)); + src.on("end", () => { + console.log("object end"); + resolve(); + }); + src.push(Buffer.alloc(0)); + src.push(Buffer.from("z")); + src.push(null); + }); +} + +async function main(): Promise { + await lines(["a\nb", "", "c\n"]); + await lines(["", "", "only\n"]); + await data(); + paused(); + await objectMode(); + console.log("done"); +} + +main(); diff --git a/tests/harness/corpus-costs/7516-readable-empty-push.ts.json b/tests/harness/corpus-costs/7516-readable-empty-push.ts.json new file mode 100644 index 000000000..1cfe56769 --- /dev/null +++ b/tests/harness/corpus-costs/7516-readable-empty-push.ts.json @@ -0,0 +1 @@ +{ "plain": 2200, "san": 3200 } From 9b2b97033aae21cf659623d605442f8f988eaa85 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 08:24:12 -0500 Subject: [PATCH 071/230] Add object literal members through the store caches in untyped code Untyped functions built each member of an object literal with scr_dyn_key_set: a lookup of the key in the new object, a walk of its empty prototype chain, a second lookup before the append, and a hash of the key to share its bytes. The first cached read of the object then computed its shape from its keys, hashing each key again. A member whose key is a constant string other than __proto__ now owns a store cache site and calls scr_js_store_probe, which appends the member through the cached shape transition, takes the key bytes from the shape, and leaves the object shaped. Members the probe does not take (Error field names, members past the 64th, a site's first object of a shape) keep scr_dyn_key_set, as do computed keys and literals in module initializers. --- .../compiler/src/backend/llvm/expr-dynamic.ts | 8 ++- packages/compiler/src/backend/llvm/js-ic.ts | 23 +++++++ packages/compiler/src/ir/js-ic.test.ts | 17 +++++ packages/compiler/src/ir/js-ic.ts | 17 ++++- packages/compiler/test/js-ic-emission.test.ts | 31 +++++++++ .../corpus/object-literal-members.mjs.json | 6 ++ tests/corpus/object-literal-members.mjs | 67 +++++++++++++++++++ .../object-literal-members.mjs.json | 1 + tests/harness/library-mode.test.ts | 4 +- tests/library-mode/js-ic-sessions/work.mjs | 9 ++- 10 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-members.mjs.json create mode 100644 tests/corpus/object-literal-members.mjs create mode 100644 tests/harness/corpus-costs/object-literal-members.mjs.json diff --git a/packages/compiler/src/backend/llvm/expr-dynamic.ts b/packages/compiler/src/backend/llvm/expr-dynamic.ts index 849715ab0..55bbb828b 100644 --- a/packages/compiler/src/backend/llvm/expr-dynamic.ts +++ b/packages/compiler/src/backend/llvm/expr-dynamic.ts @@ -31,7 +31,8 @@ import { elemAccess, vAdapters } from "./shapes.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext, ExprOf, LlValue } from "./expr-context.js"; import { emitUnionWiden } from "./expr-records.js"; -import { emitJsIcMethod, jsIcMethods } from "./js-ic.js"; +import { emitJsIcLiteralMember, emitJsIcMethod, jsIcMethods } from "./js-ic.js"; +import { jsIcLiteralKey } from "../../ir/js-ic.js"; import { DYN_BUFFER_METHODS } from "../../ir/runtime-features.js"; export function emitDynamicExpr( @@ -316,9 +317,14 @@ export function emitDynamicExpr( const obj = B.tmp(); B.line(`${obj} = call ptr @scr_dyn_new_obj()`); const out = host.own({ name: obj, type: e.type }); + const cached = jsIcMethods(host); for (const f of e.fields ?? []) { const k = emitBorrowedInput(host, f.key); const v = emitBorrowedInput(host, f.value); + if (cached && jsIcLiteralKey(f.key) !== null) { + emitJsIcLiteralMember(host, obj, k.name, v.name); + continue; + } B.line( `call void @${f.key.type.kind === "dyn" ? "scr_dyn_key_set_computed" : "scr_dyn_key_set"}(ptr ${obj}, ptr ${k.name}, ptr ${v.name})`, ); diff --git a/packages/compiler/src/backend/llvm/js-ic.ts b/packages/compiler/src/backend/llvm/js-ic.ts index b69d3199d..11b894a60 100644 --- a/packages/compiler/src/backend/llvm/js-ic.ts +++ b/packages/compiler/src/backend/llvm/js-ic.ts @@ -14,6 +14,8 @@ * keyed dense array element at an integral number key: inline; * otherwise scr_js_keyed_probe (string keys of ordinary objects) * method dynInvoke's method read: scr_js_load_probe + * literal an object literal's constant-key member: scr_js_store_probe, + * else scr_dyn_key_set * * A probe that cannot answer from plain data leaves everything untouched, * and the site calls its helper (or the generic runtime entry), which @@ -838,6 +840,27 @@ function emitKeyed(host: Host, e: ExprOf<"call">, callee: IrFunction): LlValue { return host.own({ name: out, type: e.type }); } +/** One member of an object literal (ir/js-ic.ts jsIcLiteralKey) added to + * the fresh object `obj` through the site's store cache, else with + * scr_dyn_key_set. The key and value are borrowed. */ +export function emitJsIcLiteralMember(host: Host, obj: string, key: string, value: string): void { + const B = host.B; + const ic = host.jsIcSite(); + host.declare(`declare zeroext i1 @scr_js_store_probe(ptr, ptr, ptr, ptr)`); + const done = B.tmp(); + B.line( + `${done} = call zeroext i1 @scr_js_store_probe(ptr ${obj}, ptr ${key}, ptr ${value}, ptr ${ic})`, + ); + const slow = B.newLabel("icl.slow"); + const join = B.newLabel("icl.j"); + B.condBr(done, join, slow); + B.startBlock(slow); + B.line(`call void @scr_dyn_key_set(ptr ${obj}, ptr ${key}, ptr ${value})`); + host.emitPendingCheck(); + B.br(join); + B.startBlock(join); +} + /** 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_id answers with the full dispatch). */ diff --git a/packages/compiler/src/ir/js-ic.test.ts b/packages/compiler/src/ir/js-ic.test.ts index f5391b83e..35de18085 100644 --- a/packages/compiler/src/ir/js-ic.test.ts +++ b/packages/compiler/src/ir/js-ic.test.ts @@ -311,6 +311,23 @@ test("a function holds a site when it calls a classified helper or dispatches a expect(hasJsIcSite({ ...caller, name: "%init.0" }, sites)).toBe(true); }); +test("a function holds a site when an object literal has a constant-key member", () => { + const sites = sitesOf(); + const literal = (key: IrExpr): IrFunction => + helper( + "f", + ["o"], + [ret({ kind: "dynObjLit", fields: [{ key, value: ref("o") }], type: DYN, loc })], + ); + expect(hasJsIcSite(literal(str("x")), sites)).toBe(true); + // `__proto__` sets the prototype, and a computed key has no constant + // name: both keep the generic member store. + expect(hasJsIcSite(literal(str("__proto__")), sites)).toBe(false); + expect(hasJsIcSite(literal(ref("k")), sites)).toBe(false); + // Module initializers build their literals once. + expect(hasJsIcSite({ ...literal(str("x")), name: "%init.0" }, sites)).toBe(false); +}); + test("bag helpers that exit early for values no class branch matches are still the receiver", () => { /** `if (!(v)) return `: the class tables' early exit. */ const exit = (test: "classCapsule" | "funcKind", value: IrExpr, negated = true): IrStmt => ({ diff --git a/packages/compiler/src/ir/js-ic.ts b/packages/compiler/src/ir/js-ic.ts index 48019636a..5a483c585 100644 --- a/packages/compiler/src/ir/js-ic.ts +++ b/packages/compiler/src/ir/js-ic.ts @@ -296,20 +296,33 @@ function unwrapFromDyn(e: IrExpr | null | undefined): IrExpr | null { return e; } -/** Whether a function holds a cache site: a call to a classified helper or - * a method dispatch. */ +/** Whether a function holds a cache site: a call to a classified helper, + * a method dispatch, or an object literal member with a constant key. */ export function hasJsIcSite(fn: IrFunction, sites: JsIcSites): boolean { const methods = jsIcMethodSites(fn.name); return !everyStmtList(fn.body, { expr: (e) => !( (methods && e.kind === "dynInvoke") || + (methods && + e.kind === "dynObjLit" && + (e.fields ?? []).some((f) => jsIcLiteralKey(f.key) !== null)) || (e.kind === "call" && sites.site(e.callee) !== null) ), stmt: () => true, }); } +/** The key of an object literal member that its site adds through the + * store cache: a constant string other than `__proto__`, or null. The + * literal starts as an empty object with the default prototype, so each + * such member appends one data property, and the site caches the shape + * transition and shares the key bytes the shape holds. Module + * initializers have no literal sites (jsIcMethodSites). */ +export function jsIcLiteralKey(key: IrExpr): string | null { + return key.kind === "strLit" && key.value !== "__proto__" ? key.value : null; +} + /** Whether method dispatch in a function goes through caches. Module * initializers (`%init.N`) run once, and the methods they call at top level * are overwhelmingly registrations on native handles (`worker.on(...)`, diff --git a/packages/compiler/test/js-ic-emission.test.ts b/packages/compiler/test/js-ic-emission.test.ts index 15e8e92bc..79342e99d 100644 --- a/packages/compiler/test/js-ic-emission.test.ts +++ b/packages/compiler/test/js-ic-emission.test.ts @@ -64,6 +64,37 @@ console.log(read(o), at(dyn([5, 6]), 1), call(o), o.n); } }); +test("object literal members with constant keys add through store caches", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-js-ic-literal-")); + try { + const entry = join(dir, "main.js"); + const outPath = join(dir, "main.ir.json"); + await writeFile( + entry, + ` +function make(v, k) { return { a: v, b: 2, [k]: 3 }; } +var top = { t: 1 }; +console.log(JSON.stringify(make(1, "c")), top.t); +`, + ); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const mod = deserializeModule(await readFile(outPath, "utf8")); + expect(moduleUsesDynTier(mod)).toBe(true); + const llvm = emitLlvmModule(mod); + const make = body(llvm, "make"); + // Each constant-key member tries its site's store cache first and + // falls back to the generic member store; the computed key keeps the + // generic store only. + expect(make.match(/@scr_js_store_probe\(/g)).toHaveLength(2); + expect(make.match(/@scr_dyn_key_set\(/g)).toHaveLength(2); + expect(make).toContain("@scr_dyn_key_set_computed("); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); + test("property sites keep their caches in programs that declare classes and subclasses", async () => { const dir = await mkdtemp(join(tmpdir(), "scriptc-js-ic-class-")); try { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-members.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-members.mjs.json new file mode 100644 index 000000000..f1f9017ac --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-members.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/object-literal-members.mjs" + ], + "diags": [] +} diff --git a/tests/corpus/object-literal-members.mjs b/tests/corpus/object-literal-members.mjs new file mode 100644 index 000000000..4bd211435 --- /dev/null +++ b/tests/corpus/object-literal-members.mjs @@ -0,0 +1,67 @@ +// Object literals built in untyped functions, many times through the same +// sites: key order (integer-like keys first), members named +// like Object.prototype members and Error fields, __proto__ members, wide +// literals, literals that differ only in a later member, and objects +// changed after they are built. +function point(x, y) { + return { x, y, label: "p" + x }; +} +function ordered(i) { + return { b: i, 2: "two", a: i + 1, 1: "one", "01": "zero-one" }; +} +function named(i) { + return { constructor: i, toString: "s" + i, message: "m" + i, name: "n" + i, code: i, stack: "st" }; +} +function withProto(proto, i) { + if (proto === null) return { __proto__: null, own: i }; + const o = { own: i }; + Object.setPrototypeOf(o, proto); + return o; +} +function wide(i) { + return { + k0: i, k1: i, k2: i, k3: i, k4: i, k5: i, k6: i, k7: i, k8: i, k9: i, + k10: i, k11: i, k12: i, k13: i, k14: i, k15: i, k16: i, k17: i, k18: i, k19: i, + k20: i, k21: i, k22: i, k23: i, k24: i, k25: i, k26: i, k27: i, k28: i, k29: i, + k30: i, k31: i, k32: i, k33: i, k34: i, k35: i, k36: i, k37: i, k38: i, k39: i, + k40: i, k41: i, k42: i, k43: i, k44: i, k45: i, k46: i, k47: i, k48: i, k49: i, + k50: i, k51: i, k52: i, k53: i, k54: i, k55: i, k56: i, k57: i, k58: i, k59: i, + k60: i, k61: i, k62: i, k63: i, k64: i, k65: i, k66: i, k67: i, k68: i, k69: i, + }; +} +function variant(i) { + const head = { kind: "v", n: i }; + return i % 2 ? { ...head, odd: true } : { ...head, even: true, n: -i }; +} + +let sum = 0; +const points = []; +for (let i = 0; i < 2000; i++) { + const p = point(i, i * 2); + sum += p.x + p.y; + if (i % 500 === 0) points.push(p); +} +points[0].z = 9; +delete points[1].label; +console.log(sum, JSON.stringify(points), Object.keys(points[2]).join()); + +const o = [ordered(1), ordered(2)]; +console.log(Object.keys(o[1]).join(), JSON.stringify(o[0]), o[1]["01"], o[1][1]); + +const n = [named(1), named(2)]; +console.log(JSON.stringify(n[1]), typeof n[0].toString, n[1].constructor, n[1] instanceof Error); + +const proto = { greet() { return "hi " + this.own; } }; +const w = [withProto(proto, 1), withProto(proto, 2), withProto(null, 3)]; +console.log(w[1].greet(), Object.getPrototypeOf(w[0]) === proto, Object.getPrototypeOf(w[2]), Object.keys(w[1]).join()); + +let wideSum = 0; +for (let i = 0; i < 50; i++) { + const big = wide(i); + wideSum += big.k0 + big.k69; +} +console.log(wideSum, Object.keys(wide(1)).length, Object.keys(wide(2))[69]); + +const v = []; +for (let i = 0; i < 6; i++) v.push(variant(i)); +console.log(JSON.stringify(v)); diff --git a/tests/harness/corpus-costs/object-literal-members.mjs.json b/tests/harness/corpus-costs/object-literal-members.mjs.json new file mode 100644 index 000000000..882d60088 --- /dev/null +++ b/tests/harness/corpus-costs/object-literal-members.mjs.json @@ -0,0 +1 @@ +{ "plain": 3565, "san": 6266 } diff --git a/tests/harness/library-mode.test.ts b/tests/harness/library-mode.test.ts index 4c1b96b37..4a3d4c390 100644 --- a/tests/harness/library-mode.test.ts +++ b/tests/harness/library-mode.test.ts @@ -386,7 +386,7 @@ globals: undefined fenced const run = runProbe(probe); expect(run.signal).toBeNull(); expect(run.status).toBe(0); - expect(run.stdout).toBe("run: 20000\n".repeat(3)); + expect(run.stdout).toBe("run: 40000\n".repeat(3)); }); /* ── K5/K6/K7: the trap channel ──────────────────────────────────────── */ @@ -614,7 +614,7 @@ survived, sink_calls=1 const run = runProbe(probe); expect(run.signal).toBeNull(); expect(run.status).toBe(0); - expect(run.stdout).toBe("run: 20000\n".repeat(3)); + expect(run.stdout).toBe("run: 40000\n".repeat(3)); }); platformTest("K10: K5/K7 under ASan", async () => { diff --git a/tests/library-mode/js-ic-sessions/work.mjs b/tests/library-mode/js-ic-sessions/work.mjs index 5c1afcd18..e6c4a2fc3 100644 --- a/tests/library-mode/js-ic-sessions/work.mjs +++ b/tests/library-mode/js-ic-sessions/work.mjs @@ -1,6 +1,6 @@ // Dynamic-tier property caches across library sessions: objects built empty -// and filled through untyped stores, read back through untyped loads, and a -// class instance at the same kind of sites. Each session must start with +// and filled through untyped stores or built by object literals, read back +// through untyped loads, and a class instance at the same kind of sites. Each session must start with // empty caches, since the shapes their entries named are freed when the // previous session ends. class Box { @@ -13,6 +13,9 @@ function fill(o, n) { o.b = n + 1; return o; } +function pair(n) { + return { a: n, b: n + 1 }; +} function sum(o) { return o.a + o.b; } @@ -21,6 +24,6 @@ function unbox(b) { } export function runAll() { let total = 0; - for (let i = 0; i < 100; i++) total += sum(fill({}, i)); + for (let i = 0; i < 100; i++) total += sum(fill({}, i)) + sum(pair(i)); return total + unbox(new Box(total)); } From 0488bef1a9ad7959de22df9ab78108e1c499f657 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 09:00:40 -0500 Subject: [PATCH 072/230] Describe RSA-PSS AUTO verification against restricted keys as Node does RSA_PSS_SALTLEN_AUTO recovers the salt length from the signature and does not enforce the key's minimum salt length, as in Node (OpenSSL). The runtime's exemption now says so, and corpus 8178's header, which said the minimum was still enforced, describes what it checks. --- packages/runtime/src/scr_pki.c | 3 +++ tests/corpus/8178-rsa-pss-auto-min-salt.cjs | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/scr_pki.c b/packages/runtime/src/scr_pki.c index 9877ac6c9..e41dd79c3 100644 --- a/packages/runtime/src/scr_pki.c +++ b/packages/runtime/src/scr_pki.c @@ -3255,6 +3255,9 @@ static bool sp_pss_key_rules(SpSigKey *key, mbedtls_md_type_t md, size_t hash_le return false; } key->mgf1 = k->pss_mgf1; + /* RSA_PSS_SALTLEN_AUTO (-2) is exempt from the key's minimum, as in + * OpenSSL: verification recovers the salt length from the signature and + * accepts any length (corpus 8178). */ if (!key->salt_given) { key->salt = k->pss_salt; } else if (key->salt != -2 && (key->salt == -1 ? (int)hash_len : key->salt) < k->pss_salt) { diff --git a/tests/corpus/8178-rsa-pss-auto-min-salt.cjs b/tests/corpus/8178-rsa-pss-auto-min-salt.cjs index 97b9d1c05..78e288591 100644 --- a/tests/corpus/8178-rsa-pss-auto-min-salt.cjs +++ b/tests/corpus/8178-rsa-pss-auto-min-salt.cjs @@ -1,6 +1,8 @@ // Verifying with RSA_PSS_SALTLEN_AUTO against an RSA-PSS key whose -// parameters set a minimum salt length still enforces that minimum, as in -// Node: a valid signature with a shorter salt does not verify. The keys use +// parameters set a minimum salt length recovers the salt length from the +// signature and does not enforce that minimum, as in Node: a valid +// signature with a shorter salt verifies. The default salt length (the +// key's minimum) accepts only that length. The keys use // an MGF1 digest equal to the message digest and a different one. The // stricter public key is the generated one with its minimum salt length // raised from 16 to 32 in its DER. From 21e4c3683febd387866bfd20d8cee2ea2ade4420 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 09:13:52 -0500 Subject: [PATCH 073/230] Keep the budget in typed-array length loops that assign their array A loop to a typed array's length reads the length again on every iteration, so a body that assigns the array's binding changes the limit for the next iteration. The strip-mined form took the length once per block of 64 iterations, and after the body assigned a shorter array it ran on to the block's end, reading past the new length. The loop to a typed array's length is now strip-mined only when the array is a local of the function that the body never assigns (stableReceiver on the match); others keep the budget and test the length on every iteration. New corpus program: native-worker-length-loop-reassign.ts. --- .../src/backend/llvm/emit-statements.ts | 5 +- packages/compiler/src/ir/integer-loops.ts | 18 +++++- .../test/counted-loop-emission.test.ts | 15 ++++- ...native-worker-length-loop-reassign.ts.json | 6 ++ .../native-worker-length-loop-reassign.ts | 58 +++++++++++++++++++ ...native-worker-length-loop-reassign.ts.json | 1 + 6 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-length-loop-reassign.ts.json create mode 100644 tests/corpus/native-worker-length-loop-reassign.ts create mode 100644 tests/harness/corpus-costs/native-worker-length-loop-reassign.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index 2efe2cf3a..d7fccb2d4 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -1216,14 +1216,15 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { // length, polls between blocks of WORKER_POLL_BLOCK iterations instead // of counting a budget in its body: the inner loop over a block then // has no exit besides its own bound, which LLVM can vectorize. A typed - // array's length is fixed, so each block can take it as its limit. + // array's length is fixed, so each block can take it as its limit when + // the loop does not assign the array's binding. const stripMined = capturedLets.length === 0 && integerSlot !== null && ((countedLoop !== null && countedLimit !== null && Math.abs(countedLoop.step) * WORKER_POLL_BLOCK <= 2 ** 52) || - (integerLoop !== null && integerArrayLoop === null)) && + (integerLoop !== null && integerArrayLoop === null && integerLoop.stableReceiver)) && emitter.workerLoopPolls(s) && loopAccessesTypedArrays(s); const workerBudget = stripMined ? null : emitter.workerLoopBudget(s); diff --git a/packages/compiler/src/ir/integer-loops.ts b/packages/compiler/src/ir/integer-loops.ts index 7cc49b7a0..ea866a134 100644 --- a/packages/compiler/src/ir/integer-loops.ts +++ b/packages/compiler/src/ir/integer-loops.ts @@ -12,6 +12,9 @@ import { preservesArrayLength } from "./array-length-stability.js"; export interface IntegerBytesForLoop { localId: string; limitReceiver: IrExpr; + /** The receiver is a local of the function that the body never assigns + * (a typed array's length is then the same on every iteration). */ + stableReceiver: boolean; } export interface IntegerCountedForLoop { @@ -682,7 +685,12 @@ export function matchIntegerBytesForLoop( if (!isUnitIncrement(stmt.update, init.localId)) return null; if (writesLocal(stmt.body, init.localId)) return null; - return { localId: init.localId, limitReceiver: cond.right.receiver }; + return { + localId: init.localId, + limitReceiver: cond.right.receiver, + stableReceiver: + receiverLocal !== undefined && !writesLocal(stmt.body, cond.right.receiver.localId), + }; } /** Array lengths are uint32, so zero-based loops and nested `j = i + 1` @@ -729,5 +737,11 @@ export function matchIntegerArrayForLoop( writesLocal(stmt.body, local.id) ) return null; - return { localId: local.id, limitReceiver: cond.right.receiver }; + return { + localId: local.id, + limitReceiver: cond.right.receiver, + stableReceiver: + locals.has(cond.right.receiver.localId) && + !writesLocal(stmt.body, cond.right.receiver.localId), + }; } diff --git a/packages/compiler/test/counted-loop-emission.test.ts b/packages/compiler/test/counted-loop-emission.test.ts index 206a3c57e..f617c6ab4 100644 --- a/packages/compiler/test/counted-loop-emission.test.ts +++ b/packages/compiler/test/counted-loop-emission.test.ts @@ -58,6 +58,14 @@ test("worker programs poll counted loops over typed arrays between blocks of ite "export function codes(a: Uint8Array): void {", " for (let i = 0; i < a.length; ++i) a[i] = a[i] === 97 ? 0 : 3;", "}", + "export function shrink(a: Uint8Array): number {", + " let t = 0;", + " for (let i = 0; i < a.length; ++i) {", + " t += a[i]!;", + " if (i === 2) a = a.subarray(0, 3);", + " }", + " return t;", + "}", "export function mix(n: number): number {", " let t = 0;", " for (let i = 0; i < n; i++) t = (t * 31 + i) % 1000003;", @@ -68,7 +76,7 @@ test("worker programs poll counted loops over typed arrays between blocks of ite "scale(a, b, 8);", "const c = new Uint8Array(8).fill(97);", "codes(c);", - "console.log(b[7], mix(8), c[0]);", + "console.log(b[7], mix(8), c[0], shrink(new Uint8Array(8).fill(1)));", workers ? "if (!isMainThread) new Worker(new URL(import.meta.url));" : "", ].join("\n"); const emit = async (workers: boolean): Promise => { @@ -113,6 +121,11 @@ test("worker programs poll counted loops over typed arrays between blocks of ite expect(codes).toMatch(/@llvm\.uadd\.sat\.i64\(i64 %\w+, i64 64\)/); expect(codes).toMatch(/@llvm\.umin\.i64\(i64 %\w+, i64 %\w+\)/); expect(codes).toMatch(/^loop\.poll\d+:$/m); + // A loop that assigns its array reads the new length on the next + // iteration: it keeps the budget. + const shrink = bodies(workerModule, "shrink"); + expect(shrink).toContain("worker cancellation budget"); + expect(shrink).not.toMatch(/^loop\.poll\d+:$/m); const mix = bodies(workerModule, "mix"); expect(mix).toContain("worker cancellation budget"); expect(mix).not.toContain("loop.poll"); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-length-loop-reassign.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-length-loop-reassign.ts.json new file mode 100644 index 000000000..f5230bace --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-length-loop-reassign.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/native-worker-length-loop-reassign.ts" + ], + "diags": [] +} diff --git a/tests/corpus/native-worker-length-loop-reassign.ts b/tests/corpus/native-worker-length-loop-reassign.ts new file mode 100644 index 000000000..7ec152b16 --- /dev/null +++ b/tests/corpus/native-worker-length-loop-reassign.ts @@ -0,0 +1,58 @@ +// Loops to a typed array's length in a program that starts workers, where +// the body assigns the loop's array: the condition reads the new array's +// length on the next iteration, shorter or longer. +import { Worker, isMainThread, parentPort } from "node:worker_threads"; + +function shrink(values: Float64Array): string { + let total = 0; + let steps = 0; + for (let i = 0; i < values.length; i++) { + total += values[i]!; + steps++; + if (i === 100) values = values.subarray(0, 50); + } + return `shrink ${steps} ${total}`; +} + +function grow(values: Uint8Array, longer: Uint8Array): string { + let total = 0; + let steps = 0; + for (let i = 0; i < values.length; i++) { + total += values[i]!; + steps++; + if (i === 10) values = longer; + } + return `grow ${steps} ${total}`; +} + +function swap(a: Int32Array, b: Int32Array): string { + let total = 0; + let steps = 0; + for (let i = 0; i < a.length; i++) { + total += a[i]!; + steps++; + if (i % 70 === 69) { + const t = a; + a = b; + b = t; + } + } + return `swap ${steps} ${total}`; +} + +function run(): string[] { + const values = new Float64Array(300).map((_, i) => i * 0.5); + const short = new Uint8Array(20).fill(3); + const long = new Uint8Array(200).map((_, i) => i & 7); + const a = new Int32Array(100).map((_, i) => i); + const b = new Int32Array(160).map((_, i) => -i); + return [shrink(values), grow(short, long), swap(a, b)]; +} + +if (isMainThread) { + for (const line of run()) console.log(line); + const worker = new Worker(new URL(import.meta.url)); + worker.on("message", (lines: string[]) => { + for (const line of lines) console.log("worker", line); + }); +} else parentPort!.postMessage(run()); diff --git a/tests/harness/corpus-costs/native-worker-length-loop-reassign.ts.json b/tests/harness/corpus-costs/native-worker-length-loop-reassign.ts.json new file mode 100644 index 000000000..5ee17db16 --- /dev/null +++ b/tests/harness/corpus-costs/native-worker-length-loop-reassign.ts.json @@ -0,0 +1 @@ +{ "plain": 3158, "san": 20404 } From 64e350edb6fca488a26982b9abc0a3729ccbb5fd Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 09:25:45 -0500 Subject: [PATCH 074/230] Drop a socket's 'drain' listeners when it closes The close step drops a socket's listener lists so that a listener which captures its socket does not keep it alive; the 'drain' list was not among them. A once('drain') listener still pending when the peer disconnected, or an on('drain') listener, held its socket and the socket's buffers after 'close'. The close step now drops the 'drain' listeners with the others. --- ...19-net-socket-drain-listener-close.ts.json | 6 ++ packages/runtime/src/scr_net.c | 1 + .../7519-net-socket-drain-listener-close.ts | 62 +++++++++++++++++++ ...19-net-socket-drain-listener-close.ts.json | 1 + 4 files changed, 70 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7519-net-socket-drain-listener-close.ts.json create mode 100644 tests/corpus/7519-net-socket-drain-listener-close.ts create mode 100644 tests/harness/corpus-costs/7519-net-socket-drain-listener-close.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7519-net-socket-drain-listener-close.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7519-net-socket-drain-listener-close.ts.json new file mode 100644 index 000000000..a3c10da99 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7519-net-socket-drain-listener-close.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7519-net-socket-drain-listener-close.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index 85c7b7db7..a940fb7f1 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -5648,6 +5648,7 @@ static bool scr_net_sock_settle(ScrNetSocket *sock) { scr_net_ls_drop(&sock->conn_ls); scr_net_ls_drop(&sock->timeout_ls); scr_net_ls_drop(&sock->readable_ls); + scr_net_ls_drop(&sock->drain_ls); scr_net_ls_drop(&sock->finish_ls); scr_net_ls_drop(&sock->wcb_ls); scr_net_ls_drop(&sock->ready_ls); diff --git a/tests/corpus/7519-net-socket-drain-listener-close.ts b/tests/corpus/7519-net-socket-drain-listener-close.ts new file mode 100644 index 000000000..e1b51e7b4 --- /dev/null +++ b/tests/corpus/7519-net-socket-drain-listener-close.ts @@ -0,0 +1,62 @@ +// 'drain' listeners that capture their socket do not keep it after it +// closes: a once('drain') listener still pending when the peer disconnects +// before the buffer drains, and an on('drain') listener on a socket that +// drained and then closed. The sanitized lane's exit audit checks that +// both sockets are freed. +import { connect, createServer, Socket } from "node:net"; + +function disconnectBeforeDrain(): Promise { + return new Promise((resolve) => { + const server = createServer((socket: Socket) => { + socket.on("error", () => {}); + socket.once("drain", () => console.log("unexpected drain", socket.writableLength)); + console.log("server write", socket.write(Buffer.alloc(16 * 1024 * 1024, 97))); + socket.on("close", () => { + console.log("server socket closed, need drain", socket.writableNeedDrain); + server.close(() => resolve()); + }); + }); + server.listen(0, "127.0.0.1", () => { + const addr = server.address(); + const port = typeof addr === "object" && addr !== null ? addr.port : 0; + const client = connect(port, "127.0.0.1", () => { + client.pause(); + setTimeout(() => client.destroy(), 30); + }); + }); + }); +} + +function drainThenClose(): Promise { + return new Promise((resolve) => { + const server = createServer((socket: Socket) => { + socket.pause(); + setTimeout(() => socket.resume(), 30); + }); + server.listen(0, "127.0.0.1", () => { + const addr = server.address(); + const port = typeof addr === "object" && addr !== null ? addr.port : 0; + const client = connect(port, "127.0.0.1", () => { + let drains = 0; + client.on("drain", () => { + drains++; + console.log("drain", drains, client.writableLength); + client.end(); + }); + console.log("client write", client.write(Buffer.alloc(16 * 1024 * 1024, 98))); + }); + client.on("close", () => { + console.log("client closed"); + server.close(() => resolve()); + }); + }); + }); +} + +async function main(): Promise { + await disconnectBeforeDrain(); + await drainThenClose(); + console.log("done"); +} + +main(); diff --git a/tests/harness/corpus-costs/7519-net-socket-drain-listener-close.ts.json b/tests/harness/corpus-costs/7519-net-socket-drain-listener-close.ts.json new file mode 100644 index 000000000..78ee9421a --- /dev/null +++ b/tests/harness/corpus-costs/7519-net-socket-drain-listener-close.ts.json @@ -0,0 +1 @@ +{ "plain": 2300, "san": 3100 } From 16a7ae69c534225a1ed914476f01a8a32348e334 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 09:32:47 -0500 Subject: [PATCH 075/230] Describe socket write state as Node's state.length in the signature table The comments on net.sockWrite and net.sockWritableLength still described the unsent bytes; the runtime counts each write not yet completely sent in full. --- packages/compiler/src/ir/builtin-signatures.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index f5c2ea339..576386851 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -1547,9 +1547,9 @@ function buildLibFnSignatures() { * true } and a callback taking an address string or list, then dials * the answers in order. MAY THROW (host validation, a lookup throw). */ "net.connectLookupDyn": { argTypes: [F64, STRING, DYN], result: NETSOCKET_T }, - /** socket.write: Node's Writable answer — false when the bytes the write - * left unsent reach the 65536-byte high-water mark ('drain' follows), - * and on an ended or closed socket. */ + /** socket.write: Node's Writable answer — false when the writes not yet + * completely sent, each counted in full, reach the 65536-byte + * high-water mark ('drain' follows), and on an ended or closed socket. */ "net.sockWrite": { argTypes: [NETSOCKET_T, STRING], result: BOOL }, "net.sockWriteBytes": { argTypes: [NETSOCKET_T, BYTES_U8], result: BOOL }, "net.sockEnd": { argTypes: [NETSOCKET_T], result: VOID }, @@ -2148,7 +2148,8 @@ function buildLibFnSignatures() { /** socket.address(): { address, family, port } or {} as a dyn. */ "net.sockAddress": { argTypes: [NETSOCKET_T], result: DYN }, "net.sockBytesWritten": { argTypes: [NETSOCKET_T], result: F64 }, - /** socket.writableLength: the bytes written and not yet sent. */ + /** socket.writableLength: Node's state.length, the length of every + * write not yet completely sent. */ "net.sockWritableLength": { argTypes: [NETSOCKET_T], result: F64 }, /** socket.writableNeedDrain: a write answered false and 'drain' has not * fired, with the write half open. */ From c1c4daed82d7c52ab1016d40c317bf37fecab52b Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sat, 24 Oct 2026 22:36:05 -0700 Subject: [PATCH 076/230] Throw Node's TypeError when an asserted callee holds no function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `f!()`, `t[k]!()` and `o.cb!()` on a value that holds undefined threw scriptc's checked-extraction message ("undefined is not representable in the target union …"). The non-null assertion is erased in JavaScript, so Node throws the call's own " is not a function". The checked extraction behind an asserted callee now carries that message, with V8's spelling of the callee: parentheses and non-null assertions dropped and call results printed as `f(...)`. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/coercions/state.ts | 5 +++- .../src/frontend/lowering/coercions/unions.ts | 27 +++++++++++++++---- .../lowering/expressions/narrowing.ts | 9 ++++++- .../src/frontend/lowering/lower-exprs.ts | 22 +++++++++++++-- .../compiler/src/frontend/lowering/lowerer.ts | 4 +-- .../frontend/lowering/union-narrow.test.ts | 19 +++++++++++++ .../src/frontend/lowering/union-narrow.ts | 8 ++++-- .../8240-asserted-callee-errors.ts.json | 6 +++++ tests/corpus/8240-asserted-callee-errors.ts | 25 +++++++++++++++++ .../8240-asserted-callee-errors.ts.json | 1 + 10 files changed, 113 insertions(+), 13 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8240-asserted-callee-errors.ts.json create mode 100644 tests/corpus/8240-asserted-callee-errors.ts create mode 100644 tests/harness/corpus-costs/8240-asserted-callee-errors.ts.json diff --git a/packages/compiler/src/frontend/lowering/coercions/state.ts b/packages/compiler/src/frontend/lowering/coercions/state.ts index ad1d7c6ea..db9bb53ad 100644 --- a/packages/compiler/src/frontend/lowering/coercions/state.ts +++ b/packages/compiler/src/frontend/lowering/coercions/state.ts @@ -12,7 +12,10 @@ export class CoercionState { // Provenance for checked extraction; consumers must not infer it from names. readonly checkedNarrows = new Set(); // The extracted arm of each checked (non-defaulting) extraction helper. - readonly checkedNarrowArms = new Map(); + readonly checkedNarrowArms = new Map< + string, + { unionId: string; tag: number; target: IrType; message?: string } + >(); readonly retags = new Map(); readonly narrows = new Map(); } diff --git a/packages/compiler/src/frontend/lowering/coercions/unions.ts b/packages/compiler/src/frontend/lowering/coercions/unions.ts index 434e687a5..53c87e429 100644 --- a/packages/compiler/src/frontend/lowering/coercions/unions.ts +++ b/packages/compiler/src/frontend/lowering/coercions/unions.ts @@ -394,18 +394,21 @@ export function unionRetagHelper( * the catchable TypeError — divergence 38's lying-assertion stance (an * unchecked unionNarrow would misread the payload where JS lets the * impossible value flow on). Null when the target isn't a non-unit arm - * of the union — those uses keep their erasure/fences. */ + * of the union — those uses keep their erasure/fences. `message` replaces + * the failure message (the TypeError of the operation consuming the + * value, such as a call's "f is not a function"). */ export function narrowedArmHelper( lowerer: Lowerer, fromId: string, target: IrType, loc: SrcLoc, + message?: string, ): string | null { const from = lowerer.unions.get(fromId); if (!from || isUnitType(target)) return null; const tag = lowerer.armTag(fromId, target); if (tag < 0) return null; - const key = `${fromId}:${tag}`; + const key = message === undefined ? `${fromId}:${tag}` : `${fromId}:${tag}:${message}`; const existing = lowerer.coercions.narrows.get(key); if (existing) { lowerer.coercions.checkedNarrows.add(existing); @@ -414,9 +417,22 @@ export function narrowedArmHelper( const name = keyedHelperName(lowerer, "%union.narrow", key); lowerer.coercions.narrows.set(key, name); lowerer.coercions.checkedNarrows.add(name); - const fn = buildUnionNarrow(name, from, target, loc, (type) => lowerer.fmt(type)); + const fn = buildUnionNarrow( + name, + from, + target, + loc, + (type) => lowerer.fmt(type), + null, + message ?? null, + ); if (!fn) throw new InternalCompilerError("lowerer bug: invalid checked union extraction"); - lowerer.coercions.checkedNarrowArms.set(name, { unionId: fromId, tag, target: from.arms[tag]! }); + lowerer.coercions.checkedNarrowArms.set(name, { + unionId: fromId, + tag, + target: from.arms[tag]!, + ...(message === undefined ? {} : { message }), + }); lowerer.liftedFns.push(fn); return name; } @@ -458,7 +474,8 @@ export function checkedNarrowCall( let failure: IrExpr | null = null; for (let tag = from.arms.length - 1; tag >= 0; tag--) { if (tag === arm.tag) continue; - const message = unionNarrowFailureMessage(from.arms[tag]!, (t) => lowerer.fmt(t)); + const message = + arm.message ?? unionNarrowFailureMessage(from.arms[tag]!, (t) => lowerer.fmt(t)); const thrown = nodeThrowExpr(1, "", message, type, loc); failure = failure === null diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 01cab9901..9f0559672 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -875,8 +875,15 @@ function checkedStaleArm( /** V8's spelling of a callee in "is not a function": a string-literal * element key reads as a property name (`o["a"]` is `o.a`), other keys - * keep their brackets. */ + * keep their brackets, parentheses and non-null assertions disappear, and + * a call result reads `f(...)`. */ export function v8CalleeText(node: ts.Node): string { + // `!` is type-only syntax: the running JavaScript has no trace of it. + if (ts.isNonNullExpression(node) || ts.isParenthesizedExpression(node)) + return v8CalleeText(node.expression); + // A call result prints with its arguments elided. + if (ts.isCallExpression(node) && !node.questionDotToken) + return `${v8CalleeText(node.expression)}(...)`; if (ts.isElementAccessExpression(node)) { const key = node.argumentExpression; const object = v8CalleeText(node.expression); diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 20d05f3b7..006975085 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -89,7 +89,7 @@ import { classSymbolKeyOf } from "./symbol-fields.js"; import { lowerTypeofExpr } from "./expressions/typeof.js"; import { lowerIdentifierExpr } from "./expressions/identifiers.js"; import { lowerFieldCompoundValue, lowerIncDec } from "./expressions/updates.js"; -import { lowerTernary } from "./expressions/narrowing.js"; +import { lowerTernary, v8CalleeText } from "./expressions/narrowing.js"; import { classFieldTarget, lowerPropertyAccessExpr, @@ -433,7 +433,12 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { const operand = assertionOperand(lowerer, expr, inner, target ?? null, loc); if (operand) return operand; if (target && target.kind !== "union" && !typeEquals(target, inner.type)) { - const helper = lowerer.narrowedArmHelper(inner.type.unionId, target, loc); + const helper = lowerer.narrowedArmHelper( + inner.type.unionId, + target, + loc, + assertedCalleeMessage(expr, target), + ); if (helper) { return lowerer.checkedNarrowCall(helper, inner, target, loc); } @@ -1160,6 +1165,19 @@ const NUMERIC_COMPOUNDS = new Set([ ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, ]); +/** The TypeError Node raises when an asserted callee (`t[k]!(…)`, `o.f!()`) + * holds no function: `!` is erased, so the call itself throws " is + * not a function" in V8's spelling of the callee. */ +function assertedCalleeMessage(expr: ts.NonNullExpression, target: IrType): string | undefined { + if (target.kind !== "func") return undefined; + let child: ts.Expression = expr; + while (ts.isParenthesizedExpression(child.parent)) child = child.parent; + const call = child.parent; + if (!ts.isCallExpression(call) || call.expression !== child || call.questionDotToken) + return undefined; + return `${v8CalleeText(expr.expression)} is not a function`; +} + /** Whether the use of `t[i]!` tells undefined from NaN: a converting use * that takes the value itself (see convertingUseOf), or a destination that * holds a missing element (see assertedElementKeepsUndefined) other than a diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index e49fde6bb..56e39ff0c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -10525,8 +10525,8 @@ export class Lowerer { return unionRetagHelper(this, fromId, toId, loc, trappable); } - narrowedArmHelper(fromId: string, target: IrType, loc: SrcLoc): string | null { - return narrowedArmHelper(this, fromId, target, loc); + narrowedArmHelper(fromId: string, target: IrType, loc: SrcLoc, message?: string): string | null { + return narrowedArmHelper(this, fromId, target, loc, message); } checkedNarrowCall(helper: string, value: IrExpr, type: IrType, loc: SrcLoc): IrExpr { diff --git a/packages/compiler/src/frontend/lowering/union-narrow.test.ts b/packages/compiler/src/frontend/lowering/union-narrow.test.ts index ed60fac8d..5c2a81b5e 100644 --- a/packages/compiler/src/frontend/lowering/union-narrow.test.ts +++ b/packages/compiler/src/frontend/lowering/union-narrow.test.ts @@ -97,3 +97,22 @@ test("a deferred read requires a real undefined tag", () => { const value: IrExpr = { kind: "boolLit", value: false, type: BOOL, loc }; expect(buildUnionNarrow("bad", from, BOOL, loc, () => "", value)).toBeNull(); }); + +test("a failure message replaces every other tag's TypeError", () => { + const fn = buildUnionNarrow( + "call", + union, + STRING, + loc, + (type) => type.kind, + null, + "f is not a function", + )!; + const thrown = branches(fn).flatMap((branch) => { + const statement = branch.then[0]!; + return statement.kind === "throw" ? [statement.value] : []; + }); + expect(thrown).toHaveLength(4); + for (const value of thrown) + expect(value).toMatchObject({ kind: "libCall", args: [{ value: "f is not a function" }] }); +}); diff --git a/packages/compiler/src/frontend/lowering/union-narrow.ts b/packages/compiler/src/frontend/lowering/union-narrow.ts index 3bb09cb26..a167631fc 100644 --- a/packages/compiler/src/frontend/lowering/union-narrow.ts +++ b/packages/compiler/src/frontend/lowering/union-narrow.ts @@ -20,7 +20,10 @@ export function unionNarrowFailureMessage( * * Deferred scalar fields can supply the established undefined default: * false for booleans or NaN for numbers. No other tag receives that default, - * and reference-bearing targets always use checked extraction. */ + * and reference-bearing targets always use checked extraction. + * + * `failureMessage` replaces the TypeError message of every other tag (the + * error Node raises for the operation consuming the value). */ export function buildUnionNarrow( name: string, from: IrUnionDef, @@ -28,6 +31,7 @@ export function buildUnionNarrow( loc: SrcLoc, formatType: (type: IrType) => string, undefinedDefault: IrExpr | null = null, + failureMessage: string | null = null, ): IrFunction | null { if (isUnitType(target) || target.kind === "void") return null; const targetTag = from.arms.findIndex((arm) => typeEquals(arm, target)); @@ -66,7 +70,7 @@ export function buildUnionNarrow( args: [ { kind: "strLit", - value: unionNarrowFailureMessage(arm, formatType), + value: failureMessage ?? unionNarrowFailureMessage(arm, formatType), type: STRING, loc, }, diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8240-asserted-callee-errors.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8240-asserted-callee-errors.ts.json new file mode 100644 index 000000000..365bf7076 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8240-asserted-callee-errors.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8240-asserted-callee-errors.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8240-asserted-callee-errors.ts b/tests/corpus/8240-asserted-callee-errors.ts new file mode 100644 index 000000000..91537ed70 --- /dev/null +++ b/tests/corpus/8240-asserted-callee-errors.ts @@ -0,0 +1,25 @@ +// A non-null assertion is erased in JavaScript, so calling an asserted callee +// that holds undefined throws the call's own TypeError, with V8's spelling of +// the callee: element keys, literal keys as names, parentheses and asserted +// links dropped, call results as f(...). +type F = (x: number) => number; +const table: Record = { inc: (x) => x + 1 }; +const obj: { cb?: F; inner?: { cb?: F } } = { inner: {} }; +const arr: (F | undefined)[] = [undefined]; +let maybe: F | undefined; +function get(): F | undefined { return undefined; } +const key = process.argv.length > 99 ? "inc" : "missing"; +const cases: [string, () => number][] = [ + ["computed", () => table[key]!(1)], + ["literal key", () => table["missing"]!(1)], + ["dotted", () => obj.cb!(1)], + ["nested", () => obj.inner!.cb!(1)], + ["element", () => arr[0]!(1)], + ["local", () => maybe!(1)], + ["parens", () => (table[key])!(1)], + ["call result", () => get()!(1)], + ["parens outside", () => (table[key]!)(1)], +]; +for (const [name, run] of cases) { + try { console.log(name, run()); } catch (e) { console.log(name, (e as Error).name + ": " + (e as Error).message); } +} diff --git a/tests/harness/corpus-costs/8240-asserted-callee-errors.ts.json b/tests/harness/corpus-costs/8240-asserted-callee-errors.ts.json new file mode 100644 index 000000000..8c0e66c3c --- /dev/null +++ b/tests/harness/corpus-costs/8240-asserted-callee-errors.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } From ffc7af36c760b0660e7ba6c2fcb3bda1cd66e14d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 09:59:43 -0500 Subject: [PATCH 077/230] Read a typed array's length without an invariant header load `length` and `byteLength` loaded the typed array's header with `!invariant.load` (#2016). LLVM may move such a load down to its use, past the release that frees the view, so `const slots = this.slots; this.slots = next; return slots.length` returned a garbage number in release builds. These reads are now ordinary loads that keep the header's alias tag. Header loads that only bound or address an element access stay invariant: their values reach only that access, which runs while the view is alive, so typed-array loops still hoist the length and data pointer. Corpus typed-array-length-after-release covers a replaced field, a deleted map entry and a rebound global. --- .../compiler/src/backend/llvm/bytes-alias.ts | 18 ++++-- .../compiler/src/backend/llvm/expr-bytes.ts | 17 ++++-- .../test/bytes-header-load-emission.test.ts | 60 +++++++++++++++++++ .../typed-array-length-after-release.ts.json | 6 ++ .../typed-array-length-after-release.ts | 56 +++++++++++++++++ .../typed-array-length-after-release.ts.json | 1 + 6 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 packages/compiler/test/bytes-header-load-emission.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typed-array-length-after-release.ts.json create mode 100644 tests/corpus/typed-array-length-after-release.ts create mode 100644 tests/harness/corpus-costs/typed-array-length-after-release.ts.json diff --git a/packages/compiler/src/backend/llvm/bytes-alias.ts b/packages/compiler/src/backend/llvm/bytes-alias.ts index b7a672754..2dff9f70b 100644 --- a/packages/compiler/src/backend/llvm/bytes-alias.ts +++ b/packages/compiler/src/backend/llvm/bytes-alias.ts @@ -9,15 +9,25 @@ import type { LlvmEmitterContext } from "./expr-context.js"; * receiver slots loop-invariant. Untagged accesses and calls still alias * every category. * - * Header loads are also `!invariant.load`: no store or call between the - * construction of a view and its release changes them. Without it, a data - * pointer load ahead of a bounds branch is sunk into the in-bounds block, - * and LICM keeps a conditionally executed load inside the loop. */ + * Header loads that only bound or address an access to the view are also + * `!invariant.load`: no store or call between the construction of a view + * and its release changes them, and their values reach only that access, + * which runs while the view is alive. Without it, a data pointer load + * ahead of a bounds branch is sunk into the in-bounds block, and LICM keeps + * a conditionally executed load inside the loop. */ export function bytesHeaderAlias(host: LlvmEmitterContext): string { const tag = host.memoryAliasAttachment("bytes:header"); return tag === "" ? "" : `${tag}, !invariant.load !{}`; } +/** A header load whose value the program reads (`length`, `byteLength`) + * keeps the alias tag only. The value can outlive the view, and LLVM may + * move an invariant load to its use, past the release that frees the + * view. */ +export function bytesHeaderValueAlias(host: LlvmEmitterContext): string { + return host.memoryAliasAttachment("bytes:header"); +} + /** Element storage's tag, and the alias scope of the access being emitted * in a loop version's fast copy (bytesAccessScope). */ export function bytesElementAlias(host: LlvmEmitterContext): string { diff --git a/packages/compiler/src/backend/llvm/expr-bytes.ts b/packages/compiler/src/backend/llvm/expr-bytes.ts index a50464cd6..6f0b0380d 100644 --- a/packages/compiler/src/backend/llvm/expr-bytes.ts +++ b/packages/compiler/src/backend/llvm/expr-bytes.ts @@ -8,7 +8,7 @@ import { alwaysExactInteger, exactInteger } from "./integer-values.js"; import { byteNumberAccess } from "../../ir/byte-numbers.js"; import { emitByteNumber } from "./byte-numbers.js"; import { integerOrNaNIndex, integerOrNaNRange } from "./wrapping-sums.js"; -import { bytesElementAlias, bytesHeaderAlias } from "./bytes-alias.js"; +import { bytesElementAlias, bytesHeaderAlias, bytesHeaderValueAlias } from "./bytes-alias.js"; const BYTES_NUM_KIND: Record = { u8: { kind: 0, le: false }, @@ -160,12 +160,19 @@ export function emitBytesIndex( return idx; } -/** The element count of a typed array, in the target's size type. */ -export function emitBytesHeaderLength(host: LlvmEmitterContext, receiver: string): string { +/** The element count of a typed array, in the target's size type. `value`: + * the program reads the count itself (bytesHeaderValueAlias), rather than + * only bounding an access. */ +export function emitBytesHeaderLength( + host: LlvmEmitterContext, + receiver: string, + value = false, +): string { const p = host.B.tmp(); const len = host.B.tmp(); + const alias = value ? bytesHeaderValueAlias(host) : bytesHeaderAlias(host); host.B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`); - host.B.line(`${len} = load ${host.sizeType}, ptr ${p}${bytesHeaderAlias(host)}`); + host.B.line(`${len} = load ${host.sizeType}, ptr ${p}${alias}`); return len; } @@ -184,7 +191,7 @@ export function emitBytesLength( bytes: boolean, ): LlValue { const B = host.B; - const len = emitBytesHeaderLength(host, receiver); + const len = emitBytesHeaderLength(host, receiver, true); const size = BYTES_ELEMENT_SIZE[elem]; const count = bytes && size !== 1 ? B.tmp() : len; if (count !== len) B.line(`${count} = shl ${host.sizeType} ${len}, ${Math.log2(size)}`); diff --git a/packages/compiler/test/bytes-header-load-emission.test.ts b/packages/compiler/test/bytes-header-load-emission.test.ts new file mode 100644 index 000000000..bc7cb9803 --- /dev/null +++ b/packages/compiler/test/bytes-header-load-emission.test.ts @@ -0,0 +1,60 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile, deserializeModule, validateModule } from "../src/index.js"; +import { emitLlvmModule } from "../src/backend/llvm/emitter.js"; +import { type IrModule } from "../src/ir/ir.js"; + +async function lower(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-bytes-header-")); + try { + const entry = join(dir, "main.ts"), + output = join(dir, "main.ir.json"); + await writeFile(entry, source); + const result = await compile(entry, { outDir: dir, outPath: output, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((item) => `${item.code}: ${item.message}`).join("\n")); + const module = deserializeModule(await readFile(output, "utf8")); + expect(validateModule(module)).toEqual([]); + return module; + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +function body(llvm: string, symbol: string): string { + const found = new RegExp(`^define internal [^\\n]*@${symbol}(?:\\.i32)?\\([^]*?^}`, "m").exec( + llvm, + ); + expect(found, symbol).not.toBeNull(); + return found![0]; +} + +const INVARIANT_HEADER = /load (?:i64|i32|ptr), ptr [^\n]*, !tbaa !\d+, !invariant\.load !\{\}$/m; + +test("a typed array's length read as a value is an ordinary load; access bounds stay invariant", async () => { + const module = await lower(` +class Table { + slots: Int32Array = new Int32Array(4); + swap(): number { + const slots = this.slots; + this.slots = new Int32Array(1); + return slots.length + slots.byteLength; + } +} +function sum(values: Float64Array, scale: number): number { + let total = 0; + for (let i = 0; i < values.length; i++) total += values[i]! * scale; + return total; +} +const table = new Table(); +console.log(table.swap(), sum(new Float64Array([1, 2]), 2)); +`); + const llvm = emitLlvmModule(module); + // The length outlives the view: the release must not be crossed. + const swap = body(llvm, "sc_bf__x25_Table_swap"); + expect(swap).toMatch(/load i64, ptr [^\n]*, !tbaa !\d+$/m); + expect(swap).not.toContain("!invariant.load"); + expect(body(llvm, "sc_bf_sum")).toMatch(INVARIANT_HEADER); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typed-array-length-after-release.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typed-array-length-after-release.ts.json new file mode 100644 index 000000000..ca6fcb551 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/typed-array-length-after-release.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/typed-array-length-after-release.ts" + ], + "diags": [] +} diff --git a/tests/corpus/typed-array-length-after-release.ts b/tests/corpus/typed-array-length-after-release.ts new file mode 100644 index 000000000..81fad4c77 --- /dev/null +++ b/tests/corpus/typed-array-length-after-release.ts @@ -0,0 +1,56 @@ +// A typed array's length and byteLength read just before the view's last +// reference goes away: the values stay those of the old view after a field +// replaces it, a map entry is deleted, or a local is rebound. +function grow(slots: Int32Array): Int32Array { + const next = new Int32Array(slots.length * 2); + next.set(slots); + return next; +} + +class Table { + slots: Int32Array = new Int32Array(4); + count = 0; + add(value: number): number { + const slots = this.slots; + slots[this.count % slots.length] = value; + this.count++; + if (this.count === slots.length) this.slots = grow(slots); + return this.slots.length; + } + swap(): number { + const slots = this.slots; + this.slots = new Int32Array(1); + return slots.length; + } + swapBytes(): number { + const slots = this.slots; + const bytes = slots.byteLength; + this.slots = new Int32Array(2); + return bytes + slots.length; + } +} + +const table = new Table(); +let size = 0; +for (let i = 0; i < 9; i++) size = table.add(i); +console.log(size, table.swap(), table.slots.length, table.add(5)); +console.log(table.swapBytes(), table.slots.length); + +const views = new Map(); +views.set("a", new Float64Array(6)); +function take(key: string): number { + const view = views.get(key); + if (view === undefined) return -1; + const length = view.length; + views.delete(key); + return length + view.byteLength; +} +console.log(take("a"), take("a"), views.size); + +let current = new Uint8Array(3); +function rebind(): number { + const old = current; + current = new Uint8Array(old.length + 1); + return old.length * 10 + current.length; +} +console.log(rebind(), rebind(), current.length); diff --git a/tests/harness/corpus-costs/typed-array-length-after-release.ts.json b/tests/harness/corpus-costs/typed-array-length-after-release.ts.json new file mode 100644 index 000000000..b062243b7 --- /dev/null +++ b/tests/harness/corpus-costs/typed-array-length-after-release.ts.json @@ -0,0 +1 @@ +{ "plain": 2800, "san": 2500 } From 541b05ddea63bf8ddde6b4828769ba4e46886717 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 09:22:06 -0700 Subject: [PATCH 078/230] Map generic alias casts inside generic bodies with the instance's arguments A cast to a generic type alias spelled over the enclosing function's own type parameters (`return x as Catalog`) stays symbolic in the checker inside the body, so a mapped type with key remapping, a template key filter or a conditional member refused with SC2001 in every instance. The checker interns alias instantiations, so the body's `Catalog` is the same type the signature declares. Binding unification now records, per call-keyed instance, each declared generic alias reference together with the alias the call's resolved signature instantiates. When the symbolic form does not map, mapType maps that concrete instantiation instead. A body forwarding its own alias to another generic resolves through the enclosing instance. Types that already mapped are unaffected. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/generic-functions.ts | 28 +++++ .../compiler/src/frontend/lowering/lowerer.ts | 10 ++ packages/compiler/src/frontend/type-mapper.ts | 15 +++ .../generic-alias-cast-instances.ts.json | 6 + tests/corpus/generic-alias-cast-instances.ts | 103 ++++++++++++++++++ .../generic-alias-cast-instances.ts.json | 1 + 6 files changed, 163 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-alias-cast-instances.ts.json create mode 100644 tests/corpus/generic-alias-cast-instances.ts create mode 100644 tests/harness/corpus-costs/generic-alias-cast-instances.ts.json diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index 0f9e41723..7d7124027 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -921,6 +921,27 @@ function bindDefaultTypeParams( }); } +/** Generic alias references of a call-keyed instance's declared signature + * (`Catalog`), each paired with the same alias as the call's resolved + * signature instantiates it (`Catalog<{ g1: … }, "acme">`). The checker + * keeps the alias symbolic inside the generic body and offers no way to + * instantiate it with the bound arguments, but alias instantiations are + * interned: the body's `x as Catalog` is the very type the signature + * declares, so the pair recorded during unification is that re-instantiation. + * Keyed by the instance's ts-level bindings map, which every scope that + * lowers the instance installs (typeParamTsBindings). */ +const boundAliasInstances = new WeakMap, Map>(); + +/** The concrete instantiation of a generic alias reference inside the + * instance whose ts-level bindings are `tsBindings` (boundAliasInstances), + * or null when its signature never instantiated that reference. */ +export function boundAliasInstance( + tsBindings: Map, + type: ts.Type, +): ts.Type | null { + return boundAliasInstances.get(tsBindings)?.get(type) ?? null; +} + /** The structural half of binding inference: unify the DECLARED signature * (whose types mention the type parameters) against a TARGET signature * with the substitution applied — a call's resolved signature, or the @@ -1040,6 +1061,13 @@ function unifySignatureBindings( dAliasArgs.length && iAliasArgs.length === dAliasArgs.length ) { + if (tsBindings && declared !== inst) { + let pairs = boundAliasInstances.get(tsBindings); + if (!pairs) boundAliasInstances.set(tsBindings, (pairs = new Map())); + // A body forwarding its own alias (`inner(g)` returning `Catalog` inside an instance) resolves through the enclosing one. + if (!pairs.has(declared)) pairs.set(declared, lowerer.boundAliasTsResolver(inst) ?? inst); + } dAliasArgs.forEach((da, i) => { const ia = iAliasArgs[i]; if (ia) unify(da, ia, depth + 1); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ee7477e43..52f3567fb 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -456,6 +456,7 @@ import { inferTypeParamBindings, lowerGenericInstance, genericLocalClassBinding, + boundAliasInstance, } from "./generic-functions.js"; import { bindingNeverReassigned } from "./binding-analysis.js"; import type { InstantiationPath } from "./instantiation-path.js"; @@ -3792,6 +3793,7 @@ export class Lowerer { classNamer: (decl) => this.classNamer(decl), resolveTypeParam: this.typeParamResolver, resolveTypeParamTs: this.typeParamTsResolver, + resolveBoundAliasTs: this.boundAliasTsResolver, isErasing: () => this.erasingDepth > 0, genericClassInstance: (decl, ref, mapArgument) => this.genericClassInstanceType(decl, ref, mapArgument), @@ -9550,6 +9552,14 @@ export class Lowerer { return (sym && this.typeParamTsBindings.get(sym)) ?? null; }; + /** The concrete instantiation of a generic alias reference (`Catalog`) the current call-keyed instance's signature declares + * (boundAliasInstance). Inert outside generic function instantiation. */ + readonly boundAliasTsResolver = (t: ts.Type): ts.Type | null => { + if (!this.typeParamTsBindings || t.getAliasTypeArguments().length === 0) return null; + return boundAliasInstance(this.typeParamTsBindings, t); + }; + irTypeOf(node: ts.Node): IrType { const t = this.typeOf(node); // JS inference can leave mutable fields, accessor parameters and diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..5fa361db2 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1266,6 +1266,11 @@ export interface TypeMapperCtx { * only where literal identity matters — indexed accesses (`T[K]`) inside * generic bodies whose K is bound to a literal key. */ resolveTypeParamTs?: TypeParamTsResolver; + /** A generic alias reference inside a generic body (`Catalog`) + * re-instantiated with the instance's bound type arguments: the concrete + * checker type its signature instantiates, or null. Consulted only when + * the symbolic form does not map. */ + resolveBoundAliasTs?: TypeParamTsResolver; /** GENERIC program classes (monomorphization by flow): the mapping of a * concrete instantiation reference (`Box`) — the Lowerer * registers/reuses the instantiation (`Box%0`) and answers its object @@ -1830,6 +1835,16 @@ export function mapType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if (!topLevel || !ts.isUnreadableConstituentsPanic(e)) throw e; mapped = null; } + // A generic alias the body spells over its own type parameters (`x as + // Catalog`) stays symbolic in the checker; when that form does not + // map, the instance's concrete instantiation of the same alias does. + if (mapped === null && ctx.resolveBoundAliasTs) { + const concrete = ctx.resolveBoundAliasTs(type); + if (concrete !== null && concrete !== type) { + mapped = mapType(concrete, ctx); + if (mapped !== null) contextResolutions++; + } + } if ( mapped !== null && memoKey !== null && diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-alias-cast-instances.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-alias-cast-instances.ts.json new file mode 100644 index 000000000..a229ee862 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-alias-cast-instances.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/generic-alias-cast-instances.ts" + ], + "diags": [] +} diff --git a/tests/corpus/generic-alias-cast-instances.ts b/tests/corpus/generic-alias-cast-instances.ts new file mode 100644 index 000000000..11a098214 --- /dev/null +++ b/tests/corpus/generic-alias-cast-instances.ts @@ -0,0 +1,103 @@ +// Casts to a generic type alias spelled over the enclosing function's own +// type parameters (`x as Catalog`). The checker keeps such an alias +// symbolic inside the generic body; each instance maps it with its own bound +// type arguments. + +type Groups = Record>; + +type EntryKey = { [A in keyof G]: keyof G[A] }[keyof G] & string; +type KeyOfKind = Extract, `${K}:${string}`>; +type EntryId = K extends `${string}:${infer I}` ? I : never; + +type Catalog = { + [Key in KeyOfKind as EntryId]: { id: EntryId; label: string; owner: P }; +}; + +function flatten(groups: Groups, kind: string): Record { + return Object.fromEntries( + Object.values(groups) + .flatMap((entries) => Object.entries(entries)) + .filter(([key]) => key.startsWith(kind + ":")) + .map(([, entry]) => [(entry as { id: string }).id, entry]), + ); +} + +function toolCatalog( + _owner: P, + groups: G, +): Catalog { + return flatten(groups, "tool") as Catalog; +} + +function partCatalog( + _owner: P, + groups: G, +): Catalog { + return flatten(groups, "part") as Catalog; +} + +const workshop = { + bench: { + "tool:saw": { id: "saw", label: "Hand saw", owner: "acme" }, + "part:bolt": { id: "bolt", label: "Hex bolt", owner: "acme" }, + }, + shelf: { + "tool:drill": { id: "drill", label: "Cordless drill", owner: "acme" }, + }, +}; + +const tools = toolCatalog("acme", workshop); +console.log(tools.saw.label, tools.drill.id, tools.drill.owner); +console.log(Object.keys(tools).join(","), JSON.stringify(tools.saw)); + +const parts = partCatalog("acme", workshop); +console.log(parts.bolt.label, Object.keys(parts).length); + +// A second instantiation with different groups gets its own layout. +const garden = { + shed: { + "tool:rake": { id: "rake", label: "Leaf rake", owner: "green" }, + "tool:hoe": { id: "hoe", label: "Garden hoe", owner: "green" }, + }, +}; +const gardenTools = toolCatalog("green", garden); +console.log(gardenTools.rake.label, gardenTools.hoe.label, Object.keys(gardenTools).join(",")); + +// No entry of the kind: the alias resolves to the empty record. +const gardenParts = partCatalog("green", garden); +console.log(Object.keys(gardenParts).length, JSON.stringify(gardenParts)); + +// A homomorphic mapped alias over the bound type, cast inside the body. +type Boxed = { [K in keyof T]: { value: T[K] } }; + +function boxAll>(values: T): Boxed { + const out: Record = {}; + for (const key of Object.keys(values)) out[key] = { value: values[key] }; + return out as Boxed; +} + +const boxed = boxAll({ width: 3, height: 4 }); +console.log(boxed.width.value * boxed.height.value); + +// The alias nested in an array type of the signature. +function boxEach>(rows: T[]): Boxed[] { + return rows.map((row) => { + const out: Record = {}; + for (const key of Object.keys(row)) out[key] = { value: row[key] }; + return out as Boxed; + }); +} + +const rows = boxEach([ + { x: 1, y: 2 }, + { x: 5, y: 8 }, +]); +console.log(rows.map((r) => r.x.value + r.y.value).join(" ")); + +// A generic body forwarding its parameters to another generic cast. +function ownedTools(groups: G): Catalog { + return toolCatalog("shared", groups); +} + +const shared = ownedTools({ rack: { "tool:clamp": { id: "clamp", label: "C-clamp", owner: "shared" } } }); +console.log(shared.clamp.label, shared.clamp.owner); diff --git a/tests/harness/corpus-costs/generic-alias-cast-instances.ts.json b/tests/harness/corpus-costs/generic-alias-cast-instances.ts.json new file mode 100644 index 000000000..8c0e66c3c --- /dev/null +++ b/tests/harness/corpus-costs/generic-alias-cast-instances.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } From 7a175c374f1ea2a241a6958858abd44b1b84e5ef Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 11:26:13 -0500 Subject: [PATCH 079/230] Prepare v0.4.5 release --- CHANGELOG.md | 38 ++++++++++++++++++- packages/cli-darwin-arm64/package.json | 2 +- packages/cli-darwin-x64/package.json | 2 +- packages/cli-linux-arm64-gnu/package.json | 2 +- packages/cli-linux-arm64-musl/package.json | 2 +- packages/cli-linux-x64-gnu/package.json | 2 +- packages/cli-linux-x64-musl/package.json | 2 +- packages/cli-win32-x64-msvc/package.json | 2 +- packages/cli/package.json | 2 +- packages/compiler/package.json | 2 +- packages/compiler/surface-manifest.json | 2 +- packages/experimental/package.json | 2 +- packages/llvm-darwin-arm64/package.json | 2 +- packages/llvm-darwin-x64/package.json | 2 +- packages/llvm-linux-arm64-gnu/package.json | 2 +- packages/llvm-linux-arm64-musl/package.json | 2 +- packages/llvm-linux-x64-gnu/package.json | 2 +- packages/llvm-linux-x64-musl/package.json | 2 +- packages/llvm-win32-x64-msvc/package.json | 2 +- packages/runtime-android-arm64/package.json | 2 +- packages/runtime-darwin-arm64/package.json | 2 +- packages/runtime-darwin-x64/package.json | 2 +- packages/runtime-ios-arm64/package.json | 2 +- .../runtime-ios-simulator-arm64/package.json | 2 +- packages/runtime-linux-arm64-gnu/package.json | 2 +- .../runtime-linux-arm64-musl/package.json | 2 +- packages/runtime-linux-x64-gnu/package.json | 2 +- packages/runtime-linux-x64-musl/package.json | 2 +- packages/runtime-wasm32-wasi/package.json | 2 +- packages/runtime-win32-x64-msvc/package.json | 2 +- packages/runtime/package.json | 2 +- packages/threads/package.json | 2 +- packages/types/package.json | 2 +- 33 files changed, 68 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 612df47b5..e8ab725f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,42 @@ All notable changes to scriptc will be documented in this file. +## 0.4.5 + +### Highlights + +- **Fixes:** + - Release builds read a typed array's length without a header load that could read a freed view. + - `net.Socket` `write()` returns Node's backpressure boolean and fires `'drain'`. + - Super calls in JavaScript method values call the declared base method. + - Calling a non-function through an asserted callee throws Node's TypeError. +- **Faster programs:** + - Exceptions propagate by unwinding from directly called functions on Linux, instead of status checks after every call. + - Typed-array loops prove their bounds and keep their views disjoint in more cases, including views over shared memory in worker programs; integer loops stay in integers across nested counted loops, pure helper calls, powers of two and byte reads. + - Lone array callbacks run as loops with their bodies inlined, and row sums run two rows per pass. + - Number-key maps look keys up on their integer view and write updates through the entry the read found; small number-key maps use a direct index. + - Strings: base64 decodes eight characters at a time; Buffers fill, swap byte order and decode `utf16le` a word at a time; readline splits input in one scan; string scans over non-ASCII strings use their UTF-16 units. + - Untyped code: property caches also cache absent properties, object literal members go through the store caches, calls on plain arrays skip the method ladder, and dynamic values dispose without key-cache walks. + - JSON parses into discriminated unions, and dynamic values convert into nested unions, in one pass. + - BigInt division settles quotients from the top limbs, and limb shifts are vectorized. +- **Faster builds:** + - Function bodies are emitted on up to eight workers, including in dev builds, and repeated work in LLVM module emission is cut. + - Linux executables link with lld when the driver finds it, and unchanged executables are reused without selecting the runtime pack. + - Dev builds reuse their frontend output after edits that keep token positions, and unchanged frontend inputs are checked by file identity before hashing. + - Lowering fetches only the syntax trees it reads, and the large compiler tables are built on first use. + - Release builds of the native compiler use a profile collected on the release commit. + - Type checking can use a tsc-ts server shared by later builds (opt-in). +- **Node.js parity:** + - Crypto: RSA key generation lengths, RSA-PSS keys, encrypted private keys, `X509Certificate` fields and methods, and TLS certificates and `tls.Server` contexts. + - zlib: Brotli compression and decompression. + - Dates are mutable objects with Node's setters and conversions; URL hosts and paths parse as the URL Standard does, including non-ASCII hosts with UTS #46. + - `Math`, `JSON` and `Reflect` are real objects in JavaScript; objects can inherit from JavaScript class prototypes; typed arrays held as untyped values support named properties and `Symbol.species`. + - Error messages: `JSON.parse` syntax errors use V8's messages and positions; `Object.getPrototypeOf` of an Error answers `Error.prototype`. + - `util.inspect` of dynamic classes, functions and `%o` arguments, and `Symbol.toStringTag` fields and getters, match Node. +- **npm packages:** more npm and CommonJS dependencies compile statically, including namespace-style packages with guarded declarations and `var` exports, and untyped Maps, Sets and arrays are typed from their uses. + + + ## 0.4.4 ### Highlights @@ -29,8 +65,6 @@ All notable changes to scriptc will be documented in this file. - Split CommonJS `require` bindings, `__`-prefixed exports, string-key destructuring of `any` values, and record key order for values JavaScript built. - **npm packages:** zod 4 and `@hono/zod-validator` values are projected at the declaration boundary. - - ## 0.4.3 ### Highlights diff --git a/packages/cli-darwin-arm64/package.json b/packages/cli-darwin-arm64/package.json index bac01cc44..1a8ee5d2c 100644 --- a/packages/cli-darwin-arm64/package.json +++ b/packages/cli-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-darwin-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for darwin-arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-darwin-x64/package.json b/packages/cli-darwin-x64/package.json index 6c2fb3123..0cb24b908 100644 --- a/packages/cli-darwin-x64/package.json +++ b/packages/cli-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-darwin-x64", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for darwin-x64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-arm64-gnu/package.json b/packages/cli-linux-arm64-gnu/package.json index 289d8d6bd..3a1c7488d 100644 --- a/packages/cli-linux-arm64-gnu/package.json +++ b/packages/cli-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-arm64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for linux-arm64-gnu", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-arm64-musl/package.json b/packages/cli-linux-arm64-musl/package.json index 3a601ca33..e671e926b 100644 --- a/packages/cli-linux-arm64-musl/package.json +++ b/packages/cli-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-arm64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for linux-arm64-musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-x64-gnu/package.json b/packages/cli-linux-x64-gnu/package.json index 8f6b1aa5e..2b56308fd 100644 --- a/packages/cli-linux-x64-gnu/package.json +++ b/packages/cli-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-x64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for linux-x64-gnu", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-x64-musl/package.json b/packages/cli-linux-x64-musl/package.json index ff0d1d686..4ca4c2837 100644 --- a/packages/cli-linux-x64-musl/package.json +++ b/packages/cli-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-x64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for linux-x64-musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-win32-x64-msvc/package.json b/packages/cli-win32-x64-msvc/package.json index e94ba31df..d734c0807 100644 --- a/packages/cli-win32-x64-msvc/package.json +++ b/packages/cli-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-win32-x64-msvc", - "version": "0.4.4", + "version": "0.4.5", "description": "Native scriptc compiler for win32-x64-msvc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli/package.json b/packages/cli/package.json index 26cf10f07..59f9db90a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "scriptc", - "version": "0.4.4", + "version": "0.4.5", "description": "Compile ordinary TypeScript and JavaScript to small, fast native executables and WebAssembly modules that run without Node.js", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 570c0a1a5..03af418a5 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/compiler", - "version": "0.4.4", + "version": "0.4.5", "description": "The scriptc compiler — TypeScript/JavaScript frontend, typed IR, LLVM backend", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index 58b3da7e2..5f5d8f7e0 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -1,6 +1,6 @@ { "schemaVersion": 2, - "compilerVersion": "0.4.4", + "compilerVersion": "0.4.5", "coverage": [ "Entries are projected mechanically from the compiler's own decision tables: the diagnostics registry, the unsupported-syntax dispatch tables, the stdlib and node-builtin lowering tables, and the supported-builtin-module list. Nothing is hand-maintained; the manifest regenerates byte-identically from the source tree at this version.", "Absence from this manifest means 'not projected', never 'unsupported'. Surfaces lowered through dedicated code paths rather than tables are not yet projected: console, JSON, Promise/async and the timer surface, the net/http/tls/https/dgram/dns/assert/test/stream/readline module member surfaces, template literals, the regex slice, global functions (parseInt, parseFloat, isNaN, isFinite), and the process surface outside its ambient slice.", diff --git a/packages/experimental/package.json b/packages/experimental/package.json index 583a2bc58..4636be674 100644 --- a/packages/experimental/package.json +++ b/packages/experimental/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/experimental", - "version": "0.4.4", + "version": "0.4.5", "description": "Experimental scriptc APIs, with Node.js implementations: lifetime regions for ProgramLifetime objects, intern tables shared between threads, and per-thread slots on shared objects", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-darwin-arm64/package.json b/packages/llvm-darwin-arm64/package.json index 76d5fdb72..d1ac54f3e 100644 --- a/packages/llvm-darwin-arm64/package.json +++ b/packages/llvm-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-darwin-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Pinned LLVM code-generation helper for scriptc on macOS arm64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-darwin-x64/package.json b/packages/llvm-darwin-x64/package.json index fbb755f4a..b96a90cae 100644 --- a/packages/llvm-darwin-x64/package.json +++ b/packages/llvm-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-darwin-x64", - "version": "0.4.4", + "version": "0.4.5", "description": "Pinned LLVM code-generation helper for scriptc on macOS x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-arm64-gnu/package.json b/packages/llvm-linux-arm64-gnu/package.json index 81fdc7d5b..49796f522 100644 --- a/packages/llvm-linux-arm64-gnu/package.json +++ b/packages/llvm-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-arm64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Pinned LLVM code-generation helper for scriptc on Linux arm64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-arm64-musl/package.json b/packages/llvm-linux-arm64-musl/package.json index 8b3b2f185..3001949c6 100644 --- a/packages/llvm-linux-arm64-musl/package.json +++ b/packages/llvm-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-arm64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Statically linked LLVM code-generation helper for scriptc on Linux arm64 musl", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-x64-gnu/package.json b/packages/llvm-linux-x64-gnu/package.json index 79b7eba3f..11b375ba4 100644 --- a/packages/llvm-linux-x64-gnu/package.json +++ b/packages/llvm-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-x64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Pinned LLVM code-generation helper for scriptc on Linux x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-x64-musl/package.json b/packages/llvm-linux-x64-musl/package.json index 5bffa5bef..ffe1ca7ae 100644 --- a/packages/llvm-linux-x64-musl/package.json +++ b/packages/llvm-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-x64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Statically linked LLVM code-generation helper for scriptc on Linux x64 musl", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-win32-x64-msvc/package.json b/packages/llvm-win32-x64-msvc/package.json index 14c747a4a..77e6fe687 100644 --- a/packages/llvm-win32-x64-msvc/package.json +++ b/packages/llvm-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-win32-x64-msvc", - "version": "0.4.4", + "version": "0.4.5", "description": "Pinned LLVM code-generation helper for scriptc on Windows x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-android-arm64/package.json b/packages/runtime-android-arm64/package.json index 1dfa1b177..7aea1da1e 100644 --- a/packages/runtime-android-arm64/package.json +++ b/packages/runtime-android-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-android-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc library runtime pack for Android arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-darwin-arm64/package.json b/packages/runtime-darwin-arm64/package.json index 32463357f..ae068a00a 100644 --- a/packages/runtime-darwin-arm64/package.json +++ b/packages/runtime-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-darwin-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for macOS arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-darwin-x64/package.json b/packages/runtime-darwin-x64/package.json index c0577b04c..7dbdaf6b9 100644 --- a/packages/runtime-darwin-x64/package.json +++ b/packages/runtime-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-darwin-x64", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for macOS x64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-ios-arm64/package.json b/packages/runtime-ios-arm64/package.json index 9c2134280..8a868a606 100644 --- a/packages/runtime-ios-arm64/package.json +++ b/packages/runtime-ios-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-ios-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc library runtime pack for iOS arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-ios-simulator-arm64/package.json b/packages/runtime-ios-simulator-arm64/package.json index 6b7563a6a..011d41b39 100644 --- a/packages/runtime-ios-simulator-arm64/package.json +++ b/packages/runtime-ios-simulator-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-ios-simulator-arm64", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc library runtime pack for iOS simulator arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-arm64-gnu/package.json b/packages/runtime-linux-arm64-gnu/package.json index f0ae56929..92a3192e7 100644 --- a/packages/runtime-linux-arm64-gnu/package.json +++ b/packages/runtime-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-arm64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for Linux arm64 glibc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-arm64-musl/package.json b/packages/runtime-linux-arm64-musl/package.json index 79fa8e7dd..5e751d829 100644 --- a/packages/runtime-linux-arm64-musl/package.json +++ b/packages/runtime-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-arm64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for Linux arm64 musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-x64-gnu/package.json b/packages/runtime-linux-x64-gnu/package.json index 45af8d2b7..63c045385 100644 --- a/packages/runtime-linux-x64-gnu/package.json +++ b/packages/runtime-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-x64-gnu", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for Linux x64 glibc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-x64-musl/package.json b/packages/runtime-linux-x64-musl/package.json index fe9d11bcd..a8602b395 100644 --- a/packages/runtime-linux-x64-musl/package.json +++ b/packages/runtime-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-x64-musl", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for Linux x64 musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-wasm32-wasi/package.json b/packages/runtime-wasm32-wasi/package.json index ac2f4a5e9..799863cf0 100644 --- a/packages/runtime-wasm32-wasi/package.json +++ b/packages/runtime-wasm32-wasi/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-wasm32-wasi", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for WASI Preview 1", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-win32-x64-msvc/package.json b/packages/runtime-win32-x64-msvc/package.json index 9a5731da2..421f1f71f 100644 --- a/packages/runtime-win32-x64-msvc/package.json +++ b/packages/runtime-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-win32-x64-msvc", - "version": "0.4.4", + "version": "0.4.5", "description": "Precompiled scriptc runtime pack for Windows x64 MSVC", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 1d58b00e5..90140ab15 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime", - "version": "0.4.4", + "version": "0.4.5", "description": "scriptc native runtime sources and vendored dependencies", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/threads/package.json b/packages/threads/package.json index c29967cc6..6ebcf5b8b 100644 --- a/packages/threads/package.json +++ b/packages/threads/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/threads", - "version": "0.4.4", + "version": "0.4.5", "description": "Zero-copy publication of immutable object graphs to worker threads (scriptc), with a deep-freezing Node.js implementation", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/types/package.json b/packages/types/package.json index db88b4b2f..b2aa805be 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/types", - "version": "0.4.4", + "version": "0.4.5", "description": "Contract types for scriptc: type-only annotations (Int32, NoThrow, Immutable) that scriptc checks and optimizes, with no runtime semantics", "license": "Apache-2.0", "homepage": "https://scriptc.dev", From 25a630a8ca359a4e0dfd4883e5f045000d89d562 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 09:37:46 -0700 Subject: [PATCH 080/230] Map intersections of equally represented constituents as that type An intersection that restates an optional member as required with a separately written element type (`Opts & { files: Array<{ path: string }> }` over `files?: Array<{ path: string }>`) gives the member the type `A[] & B[]`. The checker keeps both element literals, and no intersection rule mapped an intersection of arrays, so the record was refused (SC2009). When every constituent maps to one IR type that is not the dynamic value, the intersection now maps to it. The rule runs only after every other intersection rule declined, so intersections that already mapped keep their mapping. pi's NormalizedBuildSystemPromptOptions is such an intersection; it sits on the chain from main()'s options to the extension API and refused main(). Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.ts | 26 ++++++ .../intersection-restated-members.ts.json | 6 ++ tests/corpus/intersection-restated-members.ts | 82 +++++++++++++++++++ .../intersection-restated-members.ts.json | 1 + 4 files changed, 115 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/intersection-restated-members.ts.json create mode 100644 tests/corpus/intersection-restated-members.ts create mode 100644 tests/harness/corpus-costs/intersection-restated-members.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..edb3cf144 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -4948,6 +4948,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const record = mapRecordType(widened, ctx); if (record) return record; } + // An intersection whose constituents all map to ONE representation is + // that representation. An intersection that restates an optional member + // as required (`Opts & { files: Array<{ path: string }> }` over + // `files?: Array<{ path: string }>`) gives the member the type `A[] & B[]` + // with two separately written but structurally equal element literals; + // the checker keeps both, while their arrays (tuples, maps, ...) intern + // to one IR type. Reached only after every other intersection rule + // declined, so intersections that already mapped keep their mapping. A + // dynamic result stays unmapped: two opaque types are not one type. + if (widened.isIntersectionType()) { + const same = mapEqualConstituentsIntersection(widened, ctx); + if (same) return same; + } // Unions. `boolean` is internally `true | false`, and narrowing produces // literal unions like `"a" | "b"` — parts that map to the SAME IR type // collapse (deduplicated by typeKey), so a single surviving arm is just @@ -6813,6 +6826,19 @@ export function withUnitArm( * assigned keys, which is also Node's answer for Object.assign(fn, obj) * (a function's own length/name are non-enumerable). Null whenever the * shape doesn't fit — the caller falls through to the plain-func mapping. */ +/** The IR type every constituent of an intersection maps to, or null when a + * constituent is unmappable or dynamic, or two constituents differ. */ +function mapEqualConstituentsIntersection(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { + let same: IrType | null = null; + for (const part of ts.constituentTypes(widened)) { + const mapped = mapType(part, ctx); + if (mapped === null || mapped.kind === "dyn") return null; + if (same === null) same = mapped; + else if (!typeEquals(same, mapped)) return null; + } + return same; +} + function mapHybridCallableIntersection(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { const { checker, shapes } = ctx; if (!widened.isIntersectionType()) return null; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/intersection-restated-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/intersection-restated-members.ts.json new file mode 100644 index 000000000..4560cb4ae --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/intersection-restated-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/intersection-restated-members.ts" + ], + "diags": [] +} diff --git a/tests/corpus/intersection-restated-members.ts b/tests/corpus/intersection-restated-members.ts new file mode 100644 index 000000000..249b9ddfb --- /dev/null +++ b/tests/corpus/intersection-restated-members.ts @@ -0,0 +1,82 @@ +// An intersection that restates a member with a separately written but +// structurally equal type maps as that one type. Restating an optional member +// as required makes it required; the checker keeps both element literals +// (`A[] & B[]`), and their arrays, tuples, and maps share one representation. + +interface Options { + root: string; + files?: Array<{ path: string; size: number }>; +} +type Normalized = Options & { files: Array<{ path: string; size: number }> }; + +function normalize(options: Options): Normalized { + return { ...options, files: options.files ?? [] }; +} + +function totalSize(n: Normalized): number { + let total = 0; + for (const f of n.files) total += f.size; + return total; +} + +const plain = normalize({ root: "/a" }); +console.log(plain.root, plain.files.length, totalSize(plain)); +const full = normalize({ + root: "/b", + files: [ + { path: "x.txt", size: 3 }, + { path: "y.txt", size: 4 }, + ], +}); +full.files.push({ path: "z.txt", size: 5 }); +console.log(full.files.map((f) => f.path).join(","), totalSize(full)); +console.log(JSON.stringify(full)); + +let maybe: Normalized | undefined; +if (process.argv.length < 99) maybe = { root: "/", files: [{ path: "a", size: 1 }] }; +console.log(maybe?.root, maybe?.files[0]?.path); + +// Nested records whose inner arrays are restated. +interface Settings { + name: string; + meta?: { tags: Array<{ key: string; value: string }> }; +} +type Resolved = Settings & { meta: { tags: Array<{ key: string; value: string }> } }; +const resolved: Resolved = { name: "s", meta: { tags: [{ key: "k", value: "v" }] } }; +resolved.meta.tags.push({ key: "k2", value: "v2" }); +for (const t of resolved.meta.tags) console.log(`${t.key}=${t.value}`); + +// Tuples, nested arrays, and maps. +interface Layout { + pair?: [{ x: number }, string]; + grid?: Array>; + index?: Map; +} +type Filled = Layout & { + pair: [{ x: number }, string]; + grid: Array>; + index: Map; +}; +const filled: Filled = { + pair: [{ x: 7 }, "seven"], + grid: [[{ cell: 1 }, { cell: 2 }], [{ cell: 3 }]], + index: new Map([["home", { hits: 2 }]]), +}; +filled.index.set("about", { hits: 1 }); +console.log(filled.pair[0].x, filled.pair[1]); +console.log(filled.grid.map((row) => row.map((c) => c.cell).join("+")).join(" | ")); +for (const [k, v] of filled.index) console.log(k, v.hits); + +// Both sides required, and both sides optional (the member stays optional). +type Twice = { items: Array<{ n: number }> } & { items: Array<{ n: number }> }; +const twice: Twice = { items: [{ n: 1 }, { n: 2 }] }; +console.log(twice.items.reduce((s, i) => s + i.n, 0)); +type BothOptional = { extra?: Array<{ n: number }> } & { extra?: Array<{ n: number }> }; +const none: BothOptional = {}; +const some: BothOptional = { extra: [{ n: 9 }] }; +console.log(none.extra === undefined, some.extra?.[0]?.n); + +// A restated member read through a function value. +type Handler = (event: Normalized) => string; +const describe: Handler = (event) => `${event.root}:${event.files.length}`; +console.log(describe(full), describe(plain)); diff --git a/tests/harness/corpus-costs/intersection-restated-members.ts.json b/tests/harness/corpus-costs/intersection-restated-members.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/intersection-restated-members.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 30e6ca9160d33cb24933fc392e86c36358dfc681 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 10:07:51 -0700 Subject: [PATCH 081/230] Convert promise and function values into a union's arm of their kind A promise or function value flowing into a union whose only arm of that kind has a wider payload or result (`Promise` into `Promise | undefined`, `(s) => Promise` into an optional `(s) => Promise` member) was refused with SC2003: an arm value wrapped into a union only when the union held an identical arm. It now takes the arm-level conversion (the checked payload widening, the function adapter) and wraps the result. `p === u` against such a union compares both sides as untyped values, so a promise keeps its identity although converting it into the union's arm builds a new promise. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/expressions/binary.ts | 25 +++++++ .../compiler/src/frontend/lowering/lowerer.ts | 25 +++++++ ...nion-promise-function-arm-widening.ts.json | 6 ++ .../union-promise-function-arm-widening.ts | 68 +++++++++++++++++++ ...nion-promise-function-arm-widening.ts.json | 1 + 5 files changed, 125 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-promise-function-arm-widening.ts.json create mode 100644 tests/corpus/union-promise-function-arm-widening.ts create mode 100644 tests/harness/corpus-costs/union-promise-function-arm-widening.ts.json diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index c172fc662..01b48554e 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -2388,6 +2388,31 @@ export function lowerStrictEquality( loc, }; } + // A plain promise against a union whose promise arm has a different + // payload (`chains.get(id) === tail` with a `Map>`): + // wrapping would convert the promise into that arm, and the converted + // promise is a new object. Promises are objects to ===, so compare both + // sides as untyped values, where a converted promise answers with its + // origin's identity (the plain promise-vs-promise rule below). + const plainPromise = + left.type.kind === "promise" ? left : right.type.kind === "promise" ? right : null; + if ( + !bothUnion && + plainPromise !== null && + lowerer.armTag(ut.unionId, plainPromise.type) < 0 && + unionArms.some((arm) => arm.kind === "promise") && + lowerer.dynConvertible(left.type) && + lowerer.dynConvertible(right.type) + ) { + return { + kind: "dynScalarEq", + left: lowerer.coerceToExpected(left, DYN), + right: lowerer.coerceToExpected(right, DYN), + ...(negated ? { negated: true as const } : {}), + type: BOOL, + loc, + }; + } if ((sameUnion || !bothUnion) && lowerer.eqComparableUnion(ut.unionId)) { aliasRefusal(ut); return { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ee7477e43..059fab488 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -10397,6 +10397,31 @@ export class Lowerer { } } } + // A promise or function value against a union whose ONLY arm of + // that kind differs from it (`Promise` into `Promise | + // undefined`, `() => Promise` into an optional + // `() => Promise` member): convert into + // that arm with the arm-level conversion (the checked payload + // widening, the function adapter), then wrap. Several arms of the + // kind stay ambiguous and fall through to the union fence. + if (expr.type.kind === "promise" || expr.type.kind === "func") { + const arms = this.unions.get(expected.unionId)?.arms ?? []; + const candidates = arms.filter((arm) => arm.kind === expr.type.kind); + const arm = candidates.length === 1 ? candidates[0]! : null; + if (arm) { + const converted = this.coerceToExpected(expr, arm); + if (converted !== expr && typeEquals(converted.type, arm)) { + return { + kind: "unionWrap", + unionId: expected.unionId, + tag: arms.indexOf(arm), + value: converted, + type: expected, + loc: expr.loc, + }; + } + } + } // A width-coercible value against a union: coerce into the SINGLE // width-liftable arm, then wrap like any arm value (widthLiftPlan's // liftWrap — several candidate arms are ambiguous and decline). diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-promise-function-arm-widening.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-promise-function-arm-widening.ts.json new file mode 100644 index 000000000..91c268992 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-promise-function-arm-widening.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/union-promise-function-arm-widening.ts" + ], + "diags": [] +} diff --git a/tests/corpus/union-promise-function-arm-widening.ts b/tests/corpus/union-promise-function-arm-widening.ts new file mode 100644 index 000000000..751af6f41 --- /dev/null +++ b/tests/corpus/union-promise-function-arm-widening.ts @@ -0,0 +1,68 @@ +// A promise or function value flowing into a union whose only arm of that +// kind has a wider payload or result: the value converts into that arm. +// Promises keep their identity under === and !== against the union. + +const chains = new Map>(); + +async function serialize(key: string, task: () => Promise): Promise { + const previous = chains.get(key) ?? Promise.resolve(); + const queued = (async () => { + await previous.catch(() => {}); + return task(); + })(); + const tail = queued.catch(() => {}); + chains.set(key, tail); + void tail.then(() => { + if (chains.get(key) === tail) chains.delete(key); + console.log(`settled ${key}: ${chains.get(key) !== tail}`); + }); + return queued; +} + +interface Store { + pending?: Promise; + lookup?: (name: string) => Promise; + scale?: (n: number) => number | string; +} + +async function findName(name: string): Promise { + return name.length > 3 ? name.toUpperCase() : null; +} + +function double(n: number): number { + return n * 2; +} + +async function failing(): Promise { + throw new Error("task failed"); +} + +async function main(): Promise { + const a = await serialize("alpha", async () => 1); + const b = await serialize("alpha", async () => "two"); + const c = await serialize("beta", async (): Promise => true); + console.log(a, b, c, chains.size); + try { + await serialize("alpha", failing); + } catch (e) { + console.log("caught", (e as Error).message); + } + for (let i = 0; i < 4; i++) await Promise.resolve(); + console.log("remaining", chains.size); + + const store: Store = { lookup: findName, scale: double }; + const work: Promise = Promise.resolve(41); + store.pending = work; + console.log(store.pending === work, store.pending !== work); + const other: Promise = Promise.resolve(41); + console.log(store.pending === other); + console.log(await store.pending); + + const lookup = store.lookup; + if (lookup) console.log(await lookup("abcd"), await lookup("ab")); + console.log(store.scale?.(21)); + const empty: Store = {}; + console.log(empty.pending === work, empty.lookup === undefined, empty.scale?.(1)); +} + +main(); diff --git a/tests/harness/corpus-costs/union-promise-function-arm-widening.ts.json b/tests/harness/corpus-costs/union-promise-function-arm-widening.ts.json new file mode 100644 index 000000000..5c3da345d --- /dev/null +++ b/tests/harness/corpus-costs/union-promise-function-arm-widening.ts.json @@ -0,0 +1 @@ +{ "plain": 14002, "san": 16102 } From 9cdbc83aee7e85428c44bff096663e16b94fd6b4 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 10:07:59 -0700 Subject: [PATCH 082/230] Prefer the union arm that keeps every field of a converted record A record flowing into a union where several record arms could hold it by a width conversion was refused with SC2003 as ambiguous, even when one arm keeps every field of the record and the others hold it only by dropping fields (a model record whose option union is narrower than the catalog's, beside a classifier arm with a subset of its fields). That arm is the value's own layout, widened: it now wins when it is the only one. Plans that already resolved (a single candidate, the absent-field preference, discriminant and presence routes) are unchanged. Co-Authored-By: Claude Opus 5.5 --- .../lowering/coercions/structural-plans.ts | 19 +++++++ .../corpus/union-record-lossless-arm.ts.json | 6 ++ tests/corpus/union-record-lossless-arm.ts | 55 +++++++++++++++++++ .../union-record-lossless-arm.ts.json | 1 + 4 files changed, 81 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-lossless-arm.ts.json create mode 100644 tests/corpus/union-record-lossless-arm.ts create mode 100644 tests/harness/corpus-costs/union-record-lossless-arm.ts.json diff --git a/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts b/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts index f42663a13..bff419d48 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts @@ -128,6 +128,25 @@ export function widthLiftPlan(lowerer: Lowerer, src: IrType, dst: IrType): Width const route = presenceRoute(lowerer, src.shapeId, selected[0]!, selected[1]!); if (route) return route; } + // A record whose fields some arm keeps EVERY one of, each lifting + // (`{ id; limit; opts?: A | B }` into `{ id; limit; opts?: A | B | C } | + // { id; opts?: C }`): that arm is the value's own layout, widened; the + // others hold it only by dropping fields. Prefer the unique lossless arm. + if (selected.length > 1 && src.kind === "record") { + const source = lowerer.shapes.get(src.shapeId); + const lossless = selected.filter(({ arm }) => { + const target = arm.kind === "record" ? lowerer.shapes.get(arm.shapeId) : undefined; + return ( + source !== undefined && + target !== undefined && + !source.tuple && + !target.tuple && + target.fields.length === source.fields.length && + source.fields.every((field) => target.fields.some((kept) => kept.name === field.name)) + ); + }); + if (lossless.length === 1) selected = lossless; + } if (selected.length !== 1) return null; return { how: "liftWrap", tag: selected[0]!.tag, arm: selected[0]!.arm }; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-lossless-arm.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-lossless-arm.ts.json new file mode 100644 index 000000000..143cda7d0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-record-lossless-arm.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/union-record-lossless-arm.ts" + ], + "diags": [] +} diff --git a/tests/corpus/union-record-lossless-arm.ts b/tests/corpus/union-record-lossless-arm.ts new file mode 100644 index 000000000..b90d5486e --- /dev/null +++ b/tests/corpus/union-record-lossless-arm.ts @@ -0,0 +1,55 @@ +// A record flowing into a union where several record arms can hold it by a +// width conversion: the arm that keeps every one of its fields is the +// value's own layout, widened, and wins over arms that drop fields. + +interface FastOpts { + fast?: boolean; + strict?: boolean; +} +interface DepthOpts { + depth?: number; +} +interface StrictOpts { + strict?: boolean; +} + +interface Wide { + id: string; + window: number; + kind?: string; + limit: number; + opts?: FastOpts | DepthOpts | StrictOpts; +} + +interface Judge { + id: string; + window: number; + kind: string; + opts?: StrictOpts; +} + +interface Narrow { + id: string; + window: number; + kind?: string; + limit: number; + opts?: FastOpts | DepthOpts; +} + +function show(entry: Wide | Judge): string { + const limit = "limit" in entry ? ` limit=${entry.limit}` : ""; + return `${entry.kind ?? "wide"} ${entry.id} window=${entry.window}${limit} ${JSON.stringify(entry.opts ?? null)}`; +} + +const narrow: Narrow = { id: "n1", window: 4, limit: 2, opts: { depth: 3 } }; +console.log(show(narrow)); + +const entries: (Wide | Judge)[] = [narrow, { id: "j1", window: 1, kind: "judge", opts: { strict: true } }]; +const plain: Narrow = { id: "n2", window: 8, kind: "custom", limit: 5 }; +entries.push(plain); +for (const entry of entries) console.log(show(entry)); + +let latest: Wide | Judge | undefined; +for (const item of [narrow, plain]) latest = item; +console.log(latest === undefined ? "none" : show(latest)); + diff --git a/tests/harness/corpus-costs/union-record-lossless-arm.ts.json b/tests/harness/corpus-costs/union-record-lossless-arm.ts.json new file mode 100644 index 000000000..011a8fbbf --- /dev/null +++ b/tests/harness/corpus-costs/union-record-lossless-arm.ts.json @@ -0,0 +1 @@ +{ "plain": 11809, "san": 13580 } From c46f70e451ba33c75183764b7797eafea6b8c6c3 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 10:27:52 -0700 Subject: [PATCH 083/230] Fix wrong npm declarations locally with type patches A project can now patch a package's declarations in package.json: "scriptc": { "typePatches": { "@": { "declarations": { "": "" } } } }. A leading "|" widens the declared type, any other type replaces it. Selectors name declarations by path (Type.member, fn(), fn(param), list()[], Env[string], export=(), default, file.js:local()) and apply to the package's .d.ts, its JSDoc and its @types twin alike. Patches edit file text where every consumer reads it: the program host's source reads (both checker worlds) and the npm-static passes that read package files themselves. Only type nodes and JSDoc type tags change, and no edit adds or removes a line. Installed copies are selected by version; an @types twin by the runtime package's installed version. Patches that do not apply as written warn (SC8030-SC8033) and the warnings repeat on executable cache hits. Without the setting nothing changes. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/dependencies.mdx | 33 + packages/compiler/src/cli/command.ts | 23 +- .../compiler/src/diagnostics/diagnostic.ts | 23 +- packages/compiler/src/diagnostics/envelope.ts | 8 +- packages/compiler/src/diagnostics/render.ts | 17 +- .../frontend/npm-static-type-exports-host.ts | 7 +- packages/compiler/src/frontend/npm-static.ts | 9 +- packages/compiler/src/frontend/pipeline.ts | 12 +- packages/compiler/src/frontend/program.ts | 6 +- .../compiler/src/frontend/ts7/program-host.ts | 6 +- .../src/frontend/type-patches/layer.ts | 360 +++++++ .../src/frontend/type-patches/locate.test.ts | 201 ++++ .../src/frontend/type-patches/locate.ts | 936 ++++++++++++++++++ .../frontend/type-patches/selector.test.ts | 59 ++ .../src/frontend/type-patches/selector.ts | 133 +++ .../src/frontend/type-patches/semver.test.ts | 48 + .../src/frontend/type-patches/semver.ts | 229 +++++ packages/compiler/src/index.ts | 26 +- packages/compiler/src/project-config.test.ts | 77 ++ packages/compiler/src/project-config.ts | 164 ++- .../npm/cases/type-patch-dts/main.ts.json | 6 + .../cases/type-patch-types-twin/main.ts.json | 6 + packages/compiler/test/type-patches.test.ts | 124 +++ tests/fixtures/commander-calc/package.json | 10 + .../fixtures/npm/cases/type-patch-dts/main.ts | 15 + .../node_modules/profilekit/index.d.ts | 10 + .../node_modules/profilekit/index.js | 14 + .../node_modules/profilekit/package.json | 1 + .../npm/cases/type-patch-dts/package.json | 15 + .../npm/cases/type-patch-types-twin/main.ts | 11 + .../node_modules/@types/tallyjs/index.d.ts | 7 + .../node_modules/@types/tallyjs/package.json | 1 + .../node_modules/tallyjs/index.js | 11 + .../node_modules/tallyjs/package.json | 1 + .../cases/type-patch-types-twin/package.json | 15 + tests/fixtures/type-patches/jsdoc.ts | 11 + tests/fixtures/type-patches/ms.ts | 9 + .../type-patches/node_modules/ms/index.js | 162 +++ .../type-patches/node_modules/ms/license.md | 21 + .../type-patches/node_modules/ms/package.json | 38 + .../node_modules/termctx/index.js | 43 + .../node_modules/termctx/package.json | 1 + tests/fixtures/type-patches/package.json | 26 + tests/fixtures/type-patches/stale/main.ts | 4 + .../fixtures/type-patches/stale/package.json | 17 + tests/harness/type-patches.test.ts | 143 +++ 46 files changed, 3076 insertions(+), 23 deletions(-) create mode 100644 packages/compiler/src/frontend/type-patches/layer.ts create mode 100644 packages/compiler/src/frontend/type-patches/locate.test.ts create mode 100644 packages/compiler/src/frontend/type-patches/locate.ts create mode 100644 packages/compiler/src/frontend/type-patches/selector.test.ts create mode 100644 packages/compiler/src/frontend/type-patches/selector.ts create mode 100644 packages/compiler/src/frontend/type-patches/semver.test.ts create mode 100644 packages/compiler/src/frontend/type-patches/semver.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-dts/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-twin/main.ts.json create mode 100644 packages/compiler/test/type-patches.test.ts create mode 100644 tests/fixtures/npm/cases/type-patch-dts/main.ts create mode 100644 tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.d.ts create mode 100644 tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.js create mode 100644 tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-dts/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/main.ts create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/index.d.ts create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/index.js create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-types-twin/package.json create mode 100644 tests/fixtures/type-patches/jsdoc.ts create mode 100644 tests/fixtures/type-patches/ms.ts create mode 100644 tests/fixtures/type-patches/node_modules/ms/index.js create mode 100644 tests/fixtures/type-patches/node_modules/ms/license.md create mode 100644 tests/fixtures/type-patches/node_modules/ms/package.json create mode 100644 tests/fixtures/type-patches/node_modules/termctx/index.js create mode 100644 tests/fixtures/type-patches/node_modules/termctx/package.json create mode 100644 tests/fixtures/type-patches/package.json create mode 100644 tests/fixtures/type-patches/stale/main.ts create mode 100644 tests/fixtures/type-patches/stale/package.json create mode 100644 tests/harness/type-patches.test.ts diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index a9c5ae138..ec1eb0601 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -141,6 +141,39 @@ CommonJS packages compile with Node.js's module semantics. Each `require()` eval A JavaScript package can contain operations scriptc can't compile, often in functions your program never calls. These don't fail the build: each one compiles to code that throws a runtime error if it ever runs. Coverage reports list them as deferred sites, so review the list and test the parts of the package your program uses. Sites in a module's top-level code are listed under `runs at startup`: that code runs when the module loads, at startup for a module the program imports. +## Fix a package's types + +scriptc trusts declared types: a package's `.d.ts` files, its `@types/` package, and the JSDoc types in its JavaScript. A value that contradicts its declared type throws a `TypeError` instead of flowing on, so a package whose declarations are wrong can stop a program that runs under Node.js. A common case is a member declared `string` or `number` that is sometimes `undefined`. + +Fix such a declaration in your project with a type patch in `package.json`: + +```json:package.json +{ + "scriptc": { + "typePatches": { + "commander@^15.0.0": { + "declarations": { + "HelpContext.helpWidth": "| undefined" + } + } + } + } +} +``` + +Each key names a package and a semver range of the installed versions the patch describes. `declarations` maps a declaration to a type: + +
+
`"| undefined"`
+
A type that starts with `|` widens the declared type, here to `number | undefined`.
+
`"string | null"`
+
Any other type replaces the declared type.
+
+ +A declaration is named by its path: `Options.name` for a member of a type, `parse()` for a function's return type, `parse(text)` for a parameter, `list()[]` for an array's elements, and `Env[string]` for an index signature. `export=()` names the return type of the function the module exports, `default` its default export, and a file prefix such as `index.js:parse()` names a declaration inside one of the package's files. One patch applies to the package's own declarations, its JSDoc, and its `@types/` package. The patched type then holds everywhere, so your code has to handle the `undefined` that a widened declaration allows. An optional `"note"` string can link the upstream issue. + +Patches change only declarations, never the package's code, and values are still checked against the patched types. A build warns when a patch doesn't apply as written: when no range covers the installed version, when a declaration path matches nothing, when the declaration already has the type, or when a path is ambiguous. Report wrong declarations to the package too, so the patch can be removed later. + ## Provenance sources (experimental) `--provenance-sources` compiles a package from the source at the commit its npm provenance attestation names, instead of from its published JavaScript. This lets packages written in TypeScript compile from their original TypeScript. Packages without a usable attestation compile from their installed files as usual. diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index 608b97b67..8b2e4c480 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -19,7 +19,12 @@ import { } from "../node-islands/policy.js"; import { TIER_MODES, type TierMode } from "../frontend/tier-analysis.js"; import { buildEnvelope } from "../diagnostics/envelope.js"; -import { renderConfigWarnings, renderWarnings } from "../diagnostics/render.js"; +import { + renderConfigWarnings, + renderTypePatchWarnings, + renderWarnings, +} from "../diagnostics/render.js"; +import { isTypePatchCode } from "../diagnostics/diagnostic.js"; import { isPerformanceCode, renderPerformanceWarnings } from "../diagnostics/dynamic-tier.js"; import { isNativeOptimization } from "../backend/optimization.js"; import { @@ -637,20 +642,30 @@ async function main(args: string[], host: CliHost): Promise { // keeps stderr for the program's own output. const color = process.stderr.isTTY ?? false; const config = warnings.filter((d) => d.code === "SC8004"); - const divergences = warnings.filter((d) => !isPerformanceCode(d.code) && d.code !== "SC8004"); + const patches = warnings.filter((d) => isTypePatchCode(d.code)); + const divergences = warnings.filter( + (d) => !isPerformanceCode(d.code) && d.code !== "SC8004" && !isTypePatchCode(d.code), + ); const performance = warnings.filter((d) => isPerformanceCode(d.code)); if (config.length > 0) process.stderr.write( renderConfigWarnings(config, result.sourceTexts ?? new Map(), { color }) + "\n", ); - if (divergences.length > 0) { + if (patches.length > 0) { if (config.length > 0) process.stderr.write("\n"); + process.stderr.write( + renderTypePatchWarnings(patches, result.sourceTexts ?? new Map(), { color }) + "\n", + ); + } + if (divergences.length > 0) { + if (config.length > 0 || patches.length > 0) process.stderr.write("\n"); process.stderr.write( renderWarnings(divergences, result.sourceTexts ?? new Map(), { color }) + "\n", ); } if (performance.length > 0) { - if (divergences.length > 0 || config.length > 0) process.stderr.write("\n"); + if (divergences.length > 0 || config.length > 0 || patches.length > 0) + process.stderr.write("\n"); process.stderr.write(renderPerformanceWarnings(performance) + "\n"); } } diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 75827376e..8010cbb17 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -71,7 +71,9 @@ * package required to be native (SC8003) or every island * refused (SC8002) while outside the native subset, and the * R2 object-model constructs in user JavaScript (SC8101–SC8105, - * behind --strict-user-js) + * behind --strict-user-js); type patches from + * `scriptc.typePatches` that did not apply as written + * (SC8030–SC8033, warnings) * SC9xxx internal compiler errors (still source-anchored) */ import type { SrcLoc } from "../ir/ir.js"; @@ -264,6 +266,25 @@ export function nodeIslandsOffModuleDiag( }; } +/** SC8030–SC8033 — a type patch (`scriptc.typePatches`) that did not + * apply as written: no range covers the installed copy (SC8030), its + * selector matches nothing (SC8031), the declaration already has the type + * (SC8032), or the selector is ambiguous or not patchable (SC8033). + * Warnings: the build goes on with the declarations it has. */ +export function typePatchDiag( + code: "SC8030" | "SC8031" | "SC8032" | "SC8033", + message: string, + hint: string, + loc: SrcLoc, +): ScrDiagnostic { + return { code, message, loc, hint }; +} + +/** True for the type patch warnings (SC8030–SC8033). */ +export function isTypePatchCode(code: string): boolean { + return /^SC803[0-3]$/.test(code); +} + /** SC8003 — a package marked "native" is outside the native subset. */ export function nativePackageDiag(pkg: string, why: string, loc: SrcLoc): ScrDiagnostic { return { diff --git a/packages/compiler/src/diagnostics/envelope.ts b/packages/compiler/src/diagnostics/envelope.ts index ad2e31507..bdb545ff3 100644 --- a/packages/compiler/src/diagnostics/envelope.ts +++ b/packages/compiler/src/diagnostics/envelope.ts @@ -15,7 +15,7 @@ * version; renaming or removing a field, or changing a field's meaning, * bumps it. */ import { readFileSync } from "node:fs"; -import type { ScrDiagnostic } from "./diagnostic.js"; +import { isTypePatchCode, type ScrDiagnostic } from "./diagnostic.js"; import { isPerformanceCode } from "./dynamic-tier.js"; import type { WasmModuleReport } from "../wasm/discover.js"; @@ -217,6 +217,7 @@ export function categoryOf(diag: ScrDiagnostic): DiagnosticCategory { if (code === "SC0002" || code === "SC0004") return "environment"; if (/^SC[35]\d{3}$/.test(code)) return "environment"; if (code === "SC8004" || code === "SC8005" || code === "SC8012") return "environment"; + if (isTypePatchCode(code)) return "environment"; if (/^SC9\d{3}$/.test(code)) return "internal"; return "unsupported"; } @@ -335,7 +336,10 @@ export function locateDiagnostic( code: diag.code, category: categoryOf(diag), severity: - isDivergenceCode(diag.code) || isPerformanceCode(diag.code) || diag.code === "SC8004" + isDivergenceCode(diag.code) || + isPerformanceCode(diag.code) || + diag.code === "SC8004" || + isTypePatchCode(diag.code) ? "warning" : "error", scope, diff --git a/packages/compiler/src/diagnostics/render.ts b/packages/compiler/src/diagnostics/render.ts index 16fe3f0bf..a370eb3b1 100644 --- a/packages/compiler/src/diagnostics/render.ts +++ b/packages/compiler/src/diagnostics/render.ts @@ -4,7 +4,7 @@ * longer than FRAME_MAX_WIDTH (minified sources) shows a window of that * width around the diagnostic's column, with "…" where text is cut. */ -import type { ScrDiagnostic } from "./diagnostic.js"; +import { isTypePatchCode, type ScrDiagnostic } from "./diagnostic.js"; export interface RenderOptions { color?: boolean; @@ -157,7 +157,20 @@ export function renderDiagnostics( * settings in the project's scriptc configuration (SC8004), which builds do * not use. */ export function isWarningCode(code: string): boolean { - return /^SC6\d{3}$/.test(code) || code === "SC8004"; + return /^SC6\d{3}$/.test(code) || code === "SC8004" || isTypePatchCode(code); +} + +/** A build's type patch warnings (SC8030–SC8033). */ +export function renderTypePatchWarnings( + diags: readonly ScrDiagnostic[], + sourceTextByFile: Map, + opts: RenderOptions = {}, +): string { + const n = diags.length; + return ( + renderDiagnostics([...diags], sourceTextByFile, opts) + + `\n\n${n} warning${n === 1 ? "" : "s"}: ${n === 1 ? "this type patch" : "these type patches"} did not apply as written; the build uses the package's own declarations there.` + ); } /** A build's SC8004 warnings: the unknown scriptc settings it ignored. */ diff --git a/packages/compiler/src/frontend/npm-static-type-exports-host.ts b/packages/compiler/src/frontend/npm-static-type-exports-host.ts index 8a61ed62b..f8aaa20f9 100644 --- a/packages/compiler/src/frontend/npm-static-type-exports-host.ts +++ b/packages/compiler/src/frontend/npm-static-type-exports-host.ts @@ -2,7 +2,8 @@ * package's declaration entry and runtime entry, copies the declaration * files the entry reaches, and builds the runtime entry's typedefs. */ import { dirname, relative, resolve as resolvePath } from "node:path"; -import { trackedFileExists, trackedReadFile } from "./input-tracker.js"; +import { trackedFileExists } from "./input-tracker.js"; +import { packageSourceText } from "./type-patches/layer.js"; import { resolveBareModule } from "./resolve.js"; import { isJsSourceFileName } from "./tsc-codes.js"; import { NPM_STATIC_TYPES_SUFFIX } from "./npm-static.js"; @@ -78,7 +79,7 @@ export function npmStaticTypeExportPlan( const parse = (file: string): ts.SourceFile | null => { let sf = parsed.get(file); if (sf === undefined) { - const text = trackedReadFile(file); + const text = packageSourceText(file); sf = text === null ? null : services.parse(file, text, "ts"); parsed.set(file, sf); } @@ -212,7 +213,7 @@ export function npmStaticTypeExportPlan( continue; const runtime = resolveBareModule(entryPath, pkg, "js-only"); if (runtime === null || !isJsSourceFileName(runtime.typesFile)) continue; - const runtimeText = trackedReadFile(runtime.typesFile); + const runtimeText = packageSourceText(runtime.typesFile); if (runtimeText === null) continue; const { types, values } = typesOf(declared.typesFile, new Set()); const declaredByRuntime = runtimeTypeNames(services, runtime.typesFile, runtimeText); diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index f51c6bbe2..ed442be79 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -23,6 +23,7 @@ import { trackedRealpath, } from "./input-tracker.js"; import { npmBuiltinAliasOf } from "./builtin-modules.js"; +import { packageSourceText } from "./type-patches/layer.js"; let activePackages: ReadonlySet = new Set(); let declarationOverloads: ReadonlyMap = new Map(); @@ -82,7 +83,7 @@ export function setNpmStaticTypeExports( function withTypeExportTypedefs(path: string, text: string | null): string | null { const typedefs = typeExportTypedefs.get(path.split("\\").join("/")); if (typedefs === undefined) return text; - const source = text ?? trackedReadFile(path); + const source = text ?? packageSourceText(path); return source === null ? text : `${source}\n${typedefs}\n`; } @@ -467,7 +468,11 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow if (hit !== undefined) return hit ?? undefined; let rewritten: string | null = null; try { - let original = trackedReadFile(path); + // A project type patch (type-patches/layer.ts) edits the text + // as the package wrote it, before any rewrite reads its JSDoc + // (unrewritten files fall through to the host, which serves the + // patched text too). + let original = packageSourceText(path); // A bundle's CommonJS chunks move to their own modules first, so // every later pass (and this shadow, for each chunk) sees plain // CommonJS module text. diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index b5771dedd..585aca867 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -47,6 +47,7 @@ import { THREADS_MODULE } from "./threads-import.js"; import { erasedTypeOnlyImport } from "./type-only-imports.js"; import { EXPERIMENTAL_MODULE } from "./experimental-import.js"; import { readProjectConfig } from "../project-config.js"; +import { typePatchDiagnostics } from "./type-patches/layer.js"; import { packageDirOf } from "../node-islands/origin.js"; import { scanFiles, @@ -735,10 +736,14 @@ function loadFrontend( const projectConfig = readProjectConfig(entryPath); const configDiagnostics = projectConfig.diagnostics; const report = options.nodeIslands === true; + // Type patches that did not apply as written (SC8030–SC8033) warn; the + // build goes on with the packages' own declarations there. + const patchDiagnostics = typePatchDiagnostics()?.diagnostics ?? []; + const buildWarnings = [...(report ? [] : configDiagnostics), ...patchDiagnostics]; return { preflight: report && configDiagnostics.length > 0 ? [...configDiagnostics, ...preflight] : preflight, - ...(!report && configDiagnostics.length > 0 ? { warnings: configDiagnostics } : {}), + ...(buildWarnings.length > 0 ? { warnings: buildWarnings } : {}), entryText: () => finalLoad.entry.text, entryExports: () => entryFunctionExports(finalLoad.entry), // The contract scans the PROGRAM's source files, not the runtime @@ -773,9 +778,10 @@ function loadFrontend( ...finalLoad.program.getImplementationSourceFiles().filter((sf) => !sf.isDeclarationFile), ].map((sf) => [sf.fileName, sf.text]), ); - // SC8004 diagnostics point into the project's package.json. + // SC8004 and type patch diagnostics point into the project's + // package.json. if ( - configDiagnostics.length > 0 && + (configDiagnostics.length > 0 || patchDiagnostics.length > 0) && projectConfig.manifest !== null && projectConfig.manifestText !== null ) diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 00afcd696..b93e3f217 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -140,6 +140,7 @@ import { } from "./type-only-imports.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; import { planDeclProjection, type DeclProjection } from "./decl-projection.js"; +import { configureTypePatches, packageSourceText } from "./type-patches/layer.js"; import { reportShapes } from "./shape-inference/index.js"; import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js"; import { @@ -477,7 +478,7 @@ function visitNpmStaticDeclarationClosure( if (normalized !== packageRoot && !normalized.startsWith(packageRoot + "/")) return; if (seen.has(path)) return; seen.add(path); - const source = trackedReadFile(path); + const source = packageSourceText(path); if (source === null) return; onSource(path, source); for (const specifier of npmStaticDeclarationReexports(services.parse(path, source, "ts"))) { @@ -1068,6 +1069,9 @@ export function loadProgram( // JavaScript, even when named. A registry entry's `sourceVersions` // (chalk 6) compile from their own source. configurePackageShims(entryPath); + // The project's type patches (type-patches/layer.ts), read by every host + // this load creates; nothing at all without `scriptc.typePatches`. + configureTypePatches(entryPath, (path, text, kind) => services.parse(path, text, kind)); const npmStaticPackages = [...new Set(opts?.npmStatic ?? [])].filter( (name) => !npmStaticOmitsPackage(entryPath, name), ); diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 84cbe2474..9e8f51bb3 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -46,6 +46,7 @@ import { trackedRealpath, } from "../input-tracker.js"; import { packageShimDeclarationText } from "../package-shims.js"; +import { typePatchedText } from "../type-patches/layer.js"; import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../dts-paths.js"; import { reusedLibDiagnostics } from "./lib-diagnostics.js"; import { ts7Diagnostic } from "./diagnostic.js"; @@ -160,7 +161,7 @@ export class Ts7Host { const virtualFiles = this.virtualFiles; const shadow = options?.fsShadow ?? null; // A virtual file, a shadow's replacement, a package shim's declaration - // entry or the file on disk; null when shadowed out of existence or + // entry, a type-patched package file or the file on disk; null when shadowed out of existence or // missing. this.readSource = (fileName) => { const virtual = virtualFiles.get(tsgoPath(fileName)); @@ -175,6 +176,9 @@ export class Ts7Host { // program the host creates. const shim = packageShimDeclarationText(fileName); if (shim !== undefined) return shim; + // A package file a project type patch edits (type-patches/layer.ts). + const patched = typePatchedText(fileName); + if (patched !== undefined) return stripSourceBom(patched); const source = trackedReadFile(fileName); return source === null ? null : stripSourceBom(source); }; diff --git a/packages/compiler/src/frontend/type-patches/layer.ts b/packages/compiler/src/frontend/type-patches/layer.ts new file mode 100644 index 000000000..48ef6ecf0 --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/layer.ts @@ -0,0 +1,360 @@ +/* Type patches: project-owned fixes for wrong npm declarations. + * + * `scriptc.typePatches` in the entry's package.json maps + * `"@"` to declaration patches (project-config.ts): + * + * "commander@^15.0.0": { "declarations": { "HelpContext.helpWidth": "| undefined" } } + * + * The patches are applied to file TEXT, in one place every consumer reads + * through: the program host's source reads (ts7/program-host.ts), which + * serve both checker worlds (the lowering world that types package + * JavaScript from its JSDoc, and the declaration world that types user + * code at the declaration boundary), and the npm-static passes that read + * package files themselves (`packageSourceText`). Downstream, the patched + * declaration simply IS the declaration: scriptc enforces it exactly as it + * enforced the original, so a patch can never make a check weaker than the + * declaration it replaces. + * + * A patch applies to an installed copy whose version satisfies its range; + * an `@types/` twin is matched by the runtime package's name and + * installed version. Without patches nothing here runs: `typePatchedText` + * answers undefined from one null test, so every program reads the same + * bytes as before (the zero-cost rule). + * + * See scriptc-perf/results/design-type-patches/DESIGN.md. */ + +import { dirname, relative } from "node:path"; +import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; +import { typePatchDiag } from "../../diagnostics/diagnostic.js"; +import { readProjectConfig, type TypePatchEntry } from "../../project-config.js"; +import { trackedReadFile } from "../input-tracker.js"; +import type { SourceFile } from "../ts7/syntax.js"; +import { applyPatchEdits, patchFile, type PatchMatch, type PatchRequest } from "./locate.js"; +import { parseSelector, parseTypeText, type Selector, type TypePatchOp } from "./selector.js"; +import { satisfies } from "./semver.js"; + +export type TypePatchParse = (path: string, text: string, kind: "ts" | "js") => SourceFile; + +interface Request extends PatchRequest { + entry: TypePatchEntry; + /** Offset of the selector in the manifest. */ + offset: number; +} + +interface CopyInfo { + /** The runtime package's name (for an `@types` twin, the package it + * types). */ + pkg: string; + version: string | null; + root: string; + /** Requests whose entry's range the version satisfies. */ + requests: Request[]; +} + +interface MatchRecord extends PatchMatch { + file: string; + pkg: string; + version: string | null; +} + +interface State { + manifest: string; + manifestText: string; + parse: TypePatchParse; + byPackage: Map; + copies: Map; + files: Map; + /** Package copies whose version no patch range covers. */ + unmatchedCopies: Map; + /** Requests applicable to a copy this load read files of. */ + touched: Set; + matches: MatchRecord[]; + problems: Map; + /** Patched spans per served file (patched-text coordinates). */ + spans: Map; +} + +let state: State | null = null; + +/** Registers the project's type patches for one program load (or clears + * them). Called once per load, like configurePackageShims. */ +export function configureTypePatches(entryPath: string, parse: TypePatchParse): void { + const config = readProjectConfig(entryPath); + if (config.typePatches.length === 0 || config.manifest === null) { + state = null; + return; + } + const byPackage = new Map(); + let id = 0; + for (const entry of config.typePatches) { + for (const decl of entry.declarations) { + const selector = parseSelector(decl.selector); + const op = parseTypeText(decl.type); + if ("error" in selector || "error" in op) continue; // reported as SC8004 + const list = byPackage.get(entry.pkg) ?? []; + list.push({ + id: id++, + selector: selector as Selector, + op: op as TypePatchOp, + entry, + offset: decl.offset, + }); + byPackage.set(entry.pkg, list); + } + } + state = { + manifest: config.manifest, + manifestText: config.manifestText ?? "", + parse, + byPackage, + copies: new Map(), + files: new Map(), + unmatchedCopies: new Map(), + touched: new Set(), + matches: [], + problems: new Map(), + spans: new Map(), + }; +} + +/** True while the current load has type patches. */ +export function typePatchesActive(): boolean { + return state !== null; +} + +const PATCHABLE = /\.(?:[cm]?js|d\.[cm]?ts|[cm]?ts)$/; + +/** `@types/foo__bar` types `@foo/bar`. */ +function unmangleTypesName(name: string): string { + const bare = name.slice("@types/".length); + const i = bare.indexOf("__"); + return i > 0 ? `@${bare.slice(0, i)}/${bare.slice(i + 2)}` : bare; +} + +function versionAt(dir: string): string | null { + const text = trackedReadFile(`${dir}/package.json`); + if (text === null) return null; + try { + const parsed = JSON.parse(text) as { version?: unknown }; + return typeof parsed.version === "string" ? parsed.version : null; + } catch { + return null; + } +} + +/** The installed package copy a file belongs to, with the requests that + * apply to it; null for files outside node_modules or of unpatched + * packages. */ +function copyOf(s: State, path: string): { copy: CopyInfo; rel: string } | null { + const marker = "/node_modules/"; + const at = path.lastIndexOf(marker); + if (at < 0) return null; + const after = path.slice(at + marker.length); + const parts = after.split("/"); + const nameParts = parts[0]!.startsWith("@") ? 2 : 1; + if (parts.length <= nameParts) return null; + const name = parts.slice(0, nameParts).join("/"); + const root = path.slice(0, at + marker.length) + name; + const rel = parts.slice(nameParts).join("/"); + let copy = s.copies.get(root); + if (copy === undefined) { + copy = null; + const pkg = name.startsWith("@types/") ? unmangleTypesName(name) : name; + const requests = s.byPackage.get(pkg); + if (requests !== undefined) { + // A twin describes the runtime package installed beside it. + const version = name.startsWith("@types/") + ? (versionAt(path.slice(0, at + marker.length) + pkg) ?? versionAt(root)) + : versionAt(root); + const applicable = requests.filter( + (r) => version !== null && satisfies(version, r.entry.range), + ); + // Later entries win on the same selector. + const bySelector = new Map(); + for (const r of applicable) bySelector.set(r.selector.text, r); + const chosen = [...bySelector.values()]; + if (chosen.length === 0) { + const entries = [...new Set(requests.map((r) => r.entry))]; + s.unmatchedCopies.set(root, { pkg, version, entries }); + } + copy = chosen.length === 0 ? null : { pkg, version, root, requests: chosen }; + } + s.copies.set(root, copy); + } + if (copy === null) return null; + for (const r of copy.requests) s.touched.add(r); + return { copy, rel }; +} + +/** Names a request's root can appear under in a file's text. */ +function rootWords(sel: Selector): string[] { + if (sel.root === "export=") return ["exports", "export"]; + if (sel.root === "default") return ["default"]; + return [sel.root]; +} + +/** The patched text of a package file, or undefined when no patch applies + * to it (callers then read the file as usual). */ +export function typePatchedText(fileName: string): string | undefined { + const s = state; + if (s === null) return undefined; + const path = fileName.split("\\").join("/"); + if (!PATCHABLE.test(path)) return undefined; + const cached = s.files.get(path); + if (cached !== undefined) return cached ?? undefined; + let result: string | null = null; + const located = copyOf(s, path); + if (located !== null) { + const text = trackedReadFile(path); + const requests = located.copy.requests.filter( + (r) => + (r.selector.file === null || r.selector.file === located.rel) && + rootWords(r.selector).some((w) => text?.includes(w)), + ); + if (text !== null && requests.length > 0) { + const kind = /\.[cm]?ts$/.test(path) ? "ts" : "js"; + let out: ReturnType | null = null; + try { + out = patchFile(located.rel, text, s.parse(path, text, kind), requests); + } catch { + out = null; // unparseable: served unpatched, like other npm-static passes + } + if (out !== null) { + const byId = new Map(requests.map((r) => [r.id, r])); + for (const m of out.matches) { + s.matches.push({ + ...m, + file: path, + pkg: located.copy.pkg, + version: located.copy.version, + }); + } + for (const p of out.problems) { + const r = byId.get(p.id); + const prior = r === undefined ? undefined : s.problems.get(r); + if (r !== undefined && (prior === undefined || (p.always === true && !prior.always))) + s.problems.set(r, { + message: `${p.message} (${located.rel})`, + always: p.always === true, + }); + } + if (out.edits.length > 0) { + result = applyPatchEdits(text, out.edits); + s.spans.set(path, patchedSpans(out.edits)); + } + } + } + } + s.files.set(path, result); + return result ?? undefined; +} + +/** Edit spans in the patched text's coordinates. */ +function patchedSpans( + edits: readonly { start: number; end: number; text: string }[], +): { start: number; end: number }[] { + const sorted = [...edits].sort((a, b) => a.start - b.start || a.end - b.end); + const out: { start: number; end: number }[] = []; + let shift = 0; + for (const e of sorted) { + const start = e.start + shift; + out.push({ start, end: start + e.text.length }); + shift += e.text.length - (e.end - e.start); + } + return out; +} + +/** Text a package file is read as by passes that read package files + * themselves (npm-static): the patched text, or the file. */ +export function packageSourceText(path: string): string | null { + return typePatchedText(path) ?? trackedReadFile(path); +} + +/** The spans a type patch edited in a served file (patched-text offsets), + * for passes that rewrite package JSDoc and must keep patched comments + * (the shape overlay). Empty when nothing was patched. */ +export function typePatchedSpans(fileName: string): readonly { start: number; end: number }[] { + return state?.spans.get(fileName.split("\\").join("/")) ?? []; +} + +/** Every applied match of the current load (for `scriptc types patch + * --list` and tests). */ +export function typePatchMatches(): readonly MatchRecord[] { + return state?.matches ?? []; +} + +function loc(s: State, offset: number): { file: string; start: number; end: number } { + return { file: s.manifest, start: offset, end: offset }; +} + +/** The current load's type patch warnings: SC8030 (no range covers an + * installed copy), SC8031 (a selector matched nothing), SC8032 (redundant: + * the declaration already has the type), SC8033 (ambiguous or not + * patchable as written). Only packages the program loads are reported. */ +export function typePatchDiagnostics(): { + diagnostics: ScrDiagnostic[]; + manifest: string; + manifestText: string; +} | null { + const s = state; + if (s === null) return null; + const out: ScrDiagnostic[] = []; + for (const [root, info] of s.unmatchedCopies) { + const shown = info.version ?? "an unknown version"; + for (const entry of info.entries) { + out.push( + typePatchDiag( + "SC8030", + `type patch '${entry.key}' does not apply to ${info.pkg} ${shown} at ${relativeToManifest(s, root)}; its declarations are used unpatched`, + info.version === null + ? "the installed package has no version" + : `check whether the declarations of ${info.pkg} ${info.version} still need the patch, then widen the range or remove it`, + loc(s, entry.offset), + ), + ); + } + } + for (const r of s.touched) { + const mine = s.matches.filter((m) => m.id === r.id); + // A selector that matched somewhere (the .d.ts) is not a problem where + // it cannot apply (undocumented JavaScript), unless a declaration it + // names was left unpatched. + const problem = s.problems.get(r); + if (problem !== undefined && (mine.length === 0 || problem.always)) { + out.push( + typePatchDiag( + "SC8033", + `type patch '${r.entry.key}': ${problem.message}`, + "name one declaration, or patch the declared type it refers to", + loc(s, r.offset), + ), + ); + continue; + } + if (mine.length === 0) { + out.push( + typePatchDiag( + "SC8031", + `type patch '${r.entry.key}': '${r.selector.text}' matches no declaration in the files of ${r.entry.pkg} this program loads`, + "check the spelling against the package's declarations and JSDoc; the package may have renamed it", + loc(s, r.offset), + ), + ); + continue; + } + if (mine.every((m) => m.redundant)) + out.push( + typePatchDiag( + "SC8032", + `type patch '${r.entry.key}': '${r.selector.text}' is already declared as ${mine[0]!.before}`, + "the package's declarations already say this; remove the patch", + loc(s, r.offset), + ), + ); + } + return { diagnostics: out, manifest: s.manifest, manifestText: s.manifestText }; +} + +function relativeToManifest(s: State, path: string): string { + return relative(dirname(s.manifest), path).split("\\").join("/"); +} diff --git a/packages/compiler/src/frontend/type-patches/locate.test.ts b/packages/compiler/src/frontend/type-patches/locate.test.ts new file mode 100644 index 000000000..d43695d9a --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/locate.test.ts @@ -0,0 +1,201 @@ +import { describe, expect, test } from "vitest"; +import { nodeFrontendServices } from "../services-node.js"; +import { applyPatchEdits, patchFile, type PatchRequest } from "./locate.js"; +import { parseSelector, parseTypeText, type Selector, type TypePatchOp } from "./selector.js"; + +function patch(file: string, text: string, patches: Record) { + const requests: PatchRequest[] = Object.entries(patches).map(([sel, type], id) => ({ + id, + selector: parseSelector(sel) as Selector, + op: parseTypeText(type) as TypePatchOp, + })); + const kind = /\.[cm]?ts$/.test(file) ? "ts" : "js"; + const sf = nodeFrontendServices().parse(`/pkg/${file}`, text, kind); + const result = patchFile(file, text, sf, requests); + const out = applyPatchEdits(text, result.edits); + expect(out.split("\n").length).toBe(text.split("\n").length); + return { ...result, text: out }; +} + +const COMMANDER_LIKE = `class Command { + /** + * @typedef HelpContext + * @type {object} + * @property {boolean} error + * @property {number} helpWidth + * @property {function} write - includes stripColor if needed + * + * @returns {HelpContext} + * @private + */ + _getOutputContext(contextOptions) { + let helpWidth; + return { error: false, write: () => {}, helpWidth }; + } +} +module.exports = { Command }; +`; + +const MS_LIKE = `/** + * @param {String|Number} val + * @param {Object} [options] + * @return {String|Number} + */ +module.exports = function (val, options) { + return parse(val); +}; + +/** + * @param {String} str + * @return {Number} + */ +function parse(str) { + if (str.length > 100) { + return; + } + return 1; +} +`; + +describe("JSDoc", () => { + test("a @typedef property widens", () => { + const r = patch("lib/command.js", COMMANDER_LIKE, { "HelpContext.helpWidth": "| undefined" }); + expect(r.text).toContain("@property {number | undefined} helpWidth"); + expect(r.text).toContain("@returns {HelpContext}"); + expect(r.matches).toMatchObject([ + { line: 6, before: "number", after: "number | undefined", redundant: false }, + ]); + }); + + test("returns of export= and of a file-local function widen", () => { + const r = patch("index.js", MS_LIKE, { + "export=()": "| undefined", + "index.js:parse()": "| undefined", + }); + expect(r.text).toContain("@return {String|Number | undefined}\n */\nmodule.exports"); + expect(r.text).toContain("@return {Number | undefined}\n */\nfunction parse"); + expect(r.problems).toEqual([]); + }); + + test("a file prefix restricts the root to that file", () => { + const r = patch("other.js", MS_LIKE, { "index.js:parse()": "| undefined" }); + expect(r.matches).toEqual([]); + expect(r.text).toBe(MS_LIKE); + }); + + test("parameters, optional parameters and redundancy", () => { + const r = patch("index.js", MS_LIKE, { + "export=(options)": "| undefined", + "parse(str)": "string | undefined", + }); + expect(r.matches[0]).toMatchObject({ redundant: true }); + expect(r.text).toContain("@param {string | undefined} str"); + }); + + test("a replacement on an undocumented declaration inserts a tag", () => { + const text = + "/** Adds. */\nfunction add(a, b) { return a + b; }\nfunction sub(a, b) { return a - b; }\n"; + const r = patch("index.js", text, { "add()": "number", "sub(a)": "number" }); + expect(r.text).toBe( + "/** Adds. @returns {number} */\nfunction add(a, b) { return a + b; }\n/** @param {number} a */ function sub(a, b) { return a - b; }\n", + ); + }); + + test("a widening needs a written type", () => { + const r = patch("index.js", "function f() { return 1; }\n", { "f()": "| undefined" }); + expect(r.problems[0]!.message).toContain("no written type to widen"); + }); + + test("members of exported objects, classes and constructor assignments", () => { + const text = `class Box { + constructor() { + /** @type {string} */ + this.label = ""; + } + /** @returns {number} */ + size() { return 1; } +} +module.exports = { + /** @returns {string} */ + name() { return ""; }, + Box, +}; +`; + const r = patch("index.js", text, { + "Box.label": "| undefined", + "Box.size()": "| null", + "export=.name()": "| undefined", + }); + expect(r.text).toContain("@type {string | undefined} */\n this.label"); + expect(r.text).toContain("@returns {number | null} */\n size()"); + expect(r.text).toContain("@returns {string | undefined} */\n name()"); + }); + + test("function types are parenthesized before widening", () => { + const r = patch("index.js", "/** @type {(a: string) => void} */\nvar cb = null;\n", { + cb: "| undefined", + }); + expect(r.text).toContain("@type {((a: string) => void) | undefined}"); + }); +}); + +describe("declaration files", () => { + const DTS = `export interface Options { + name: string; + retries?: number; + env: Env; +} +export interface Env { + [key: string]: string; +} +export declare function fetchUser(id: string): Promise<{ email: string; id: string }>; +export declare function load(): User; +interface User { name: string; tags: string[] } +declare function ms(value: string): number; +declare function ms(value: number): string; +declare namespace ms { const version: string; } +export = ms; +`; + + test("members, optional members, index signatures and navigation", () => { + const r = patch("index.d.ts", DTS, { + "Options.name": "| undefined", + "Options.retries": "| undefined", + "Env[string]": "| undefined", + "fetchUser().email": "string | null", + "load().tags[]": "| undefined", + "Options.env[string]": "| null", + }); + expect(r.text).toContain(" name: string | undefined;"); + expect(r.text).toContain("[key: string]: string | undefined | null;"); + expect(r.text).toContain("Promise<{ email: string | null; id: string }>"); + expect(r.text).toContain("tags: (string | undefined)[]"); + expect(r.matches.find((m) => m.id === 1)).toMatchObject({ redundant: true }); + }); + + test("export= widens every overload, and a replacement of several is refused", () => { + const r = patch("index.d.ts", DTS, { "export=()": "| undefined" }); + expect(r.text).toContain("declare function ms(value: string): number | undefined;"); + expect(r.text).toContain("declare function ms(value: number): string | undefined;"); + const refused = patch("index.d.ts", DTS, { "export=()": "number" }); + expect(refused.problems[0]!.message).toContain("matches 2 declarations"); + expect(refused.text).toBe(DTS); + }); + + test("namespace members and generic types", () => { + const r = patch("index.d.ts", DTS, { "ms.version": "| undefined" }); + expect(r.text).toContain("const version: string | undefined;"); + const generic = patch( + "index.d.ts", + "export declare function f(): Map;\n", + { "f().a": "| undefined" }, + ); + expect(generic.problems[0]!.message).toContain("cannot follow"); + }); + + test("multi-line types keep their line count when replaced", () => { + const text = "export interface A {\n b: {\n c: number;\n };\n}\n"; + const r = patch("index.d.ts", text, { "A.b": "string" }); + expect(r.text.startsWith("export interface A {\n b: string\n\n;")).toBe(true); + }); +}); diff --git a/packages/compiler/src/frontend/type-patches/locate.ts b/packages/compiler/src/frontend/type-patches/locate.ts new file mode 100644 index 000000000..fc85a1634 --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/locate.ts @@ -0,0 +1,936 @@ +/* Finds the declarations a type patch selector names in one package file + * and computes the text edits that apply the patch. + * + * Declaration files (and TypeScript sources) are matched on their syntax + * tree: the edit replaces or widens a written type node. JavaScript is + * matched on its JSDoc: the edit replaces or widens the `{...}` of a + * `@typedef`'s `@property`, a `@returns`, a `@param` or a `@type` tag. + * Executable code is never edited, and no edit adds or removes a line + * break, so every later pass and diagnostic keeps its line. */ + +import * as ts from "../ts7/syntax.js"; +import { tokenStartOfNode } from "../ts7/node-functions.js"; +import { + normalizeType, + splitUnion, + type Selector, + type SelectorSegment, + type TypePatchOp, +} from "./selector.js"; + +export interface PatchRequest { + /** Caller's id, echoed in matches and problems. */ + id: number; + selector: Selector; + op: TypePatchOp; +} + +export interface PatchMatch { + id: number; + /** 1-based line of the patched type. */ + line: number; + before: string; + after: string; + /** The declaration already has the patched type: nothing was edited. */ + redundant: boolean; +} + +export interface PatchProblem { + id: number; + message: string; + /** Reported even when the selector matched elsewhere (an ambiguous + * replacement left a declaration unpatched). */ + always?: true; +} + +export interface PatchEdit { + start: number; + end: number; + text: string; +} + +export interface FilePatchResult { + edits: PatchEdit[]; + matches: PatchMatch[]; + problems: PatchProblem[]; +} + +/** A place a type is (or can be) written. */ +interface Slot { + /** The written type's span, or null when there is none. */ + span: { start: number; end: number } | null; + /** The declaration is optional (`x?:`, `[x]`), so it already admits + * undefined. */ + optional: boolean; + /** Where a replacement goes when nothing is written: the inserted text + * is `prefix + type + suffix`. */ + insert: { at: number; prefix: string; suffix: string } | null; + /** A JSDoc slot inside a comment (one-line edits only). */ + jsdoc: boolean; + /** The type is an array element (`T[]`): a union must be wrapped. */ + wrap?: boolean; +} + +type Step = { slots: Slot[] } | { problem: string }; + +/** Type texts that need parentheses before `| T` is appended. */ +function needsParens(text: string): boolean { + return /=>|\?|\bextends\b|^\s*new\b|^\s*function\b|^\s*!/.test(text); +} + +function lineOf(text: string, pos: number): number { + let line = 1; + for (let i = 0; i < pos && i < text.length; i++) if (text.charCodeAt(i) === 10) line++; + return line; +} + +function editsFor( + text: string, + req: PatchRequest, + slot: Slot, +): { edits: PatchEdit[]; match: PatchMatch } | { problem: string } { + const op = req.op; + if (slot.span === null) { + if (op.kind === "widen" || slot.insert === null) + return { + problem: `'${req.selector.text}' has no written type to widen; give the whole type instead`, + }; + const ins = slot.insert; + return { + edits: [{ start: ins.at, end: ins.at, text: ins.prefix + op.text + ins.suffix }], + match: { + id: req.id, + line: lineOf(text, ins.at), + before: "(none)", + after: op.text, + redundant: false, + }, + }; + } + const { start, end } = slot.span; + const original = text.slice(start, end); + const line = lineOf(text, start); + if (op.kind === "widen") { + const present = new Set(splitUnion(original).map(normalizeType)); + if (slot.optional) present.add("undefined"); + const add = op.arms.filter((a) => !present.has(normalizeType(a))); + if (add.length === 0) + return { + edits: [], + match: { id: req.id, line, before: original, after: original, redundant: true }, + }; + // `(() => void) | undefined`; an array element wraps the whole union: + // `(string | undefined)[]`. + const inner = needsParens(original); + const outer = slot.wrap === true; + const suffix = ` | ${add.join(" | ")}`; + const open = (outer ? "(" : "") + (inner ? "(" : ""); + const close = (inner ? ")" : "") + suffix + (outer ? ")" : ""); + const edits: PatchEdit[] = + open === "" + ? [{ start: end, end, text: close }] + : [ + { start, end: start, text: open }, + { start: end, end, text: close }, + ]; + const after = `${open}${original}${close}`; + return { edits, match: { id: req.id, line, before: original, after, redundant: false } }; + } + if (normalizeType(original) === normalizeType(op.text)) + return { + edits: [], + match: { id: req.id, line, before: original, after: original, redundant: true }, + }; + const breaks = original.split("\n").length - 1; + if (breaks > 0 && slot.jsdoc) + return { + problem: `'${req.selector.text}' names a JSDoc type that spans lines; replace it in one line upstream or widen it`, + }; + const replacement = + slot.wrap === true && (splitUnion(op.text).length > 1 || needsParens(op.text)) + ? `(${op.text})` + : op.text; + return { + edits: [{ start, end, text: replacement + "\n".repeat(breaks) }], + match: { id: req.id, line, before: original, after: replacement, redundant: false }, + }; +} + +/** Applies a file's requests. `sf` is the file's syntax tree, parsed as + * TypeScript for declaration files and TypeScript sources and as + * JavaScript otherwise. */ +export function patchFile( + relPath: string, + text: string, + sf: ts.SourceFile, + requests: readonly PatchRequest[], +): FilePatchResult { + const js = !/\.(d\.)?[mc]?ts$/.test(relPath); + const resolver = js ? new JsResolver(text, sf) : new TsResolver(text, sf); + const result: FilePatchResult = { edits: [], matches: [], problems: [] }; + for (const req of requests) { + if (req.selector.file !== null && req.selector.file !== relPath) continue; + const step = resolver.resolve(req.selector); + if ("problem" in step) { + result.problems.push({ id: req.id, message: step.problem }); + continue; + } + if (step.slots.length === 0) continue; + if (req.op.kind === "replace" && step.slots.length > 1) { + result.problems.push({ + id: req.id, + message: `'${req.selector.text}' matches ${step.slots.length} declarations in ${relPath}; a replacement must name one (widen them, or replace the declaration file)`, + always: true, + }); + continue; + } + for (const slot of step.slots) { + const out = editsFor(text, req, slot); + if ("problem" in out) { + result.problems.push({ id: req.id, message: out.problem }); + continue; + } + result.edits.push(...out.edits); + result.matches.push(out.match); + } + } + return result; +} + +/** Applies edits (sorted by position; at one position, in request order). + * An edit overlapping an earlier replacement is dropped. */ +export function applyPatchEdits(text: string, edits: readonly PatchEdit[]): string { + const sorted = edits + .map((e, i) => ({ e, i })) + .sort((a, b) => a.e.start - b.e.start || a.e.end - b.e.end || a.i - b.i) + .map((x) => x.e); + let out = ""; + let at = 0; + for (const e of sorted) { + if (e.start < at) continue; + out += text.slice(at, e.start) + e.text; + at = e.end; + } + return out + text.slice(at); +} + +function nameText(name: ts.Node | undefined): string | null { + if (name === undefined) return null; + if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isPrivateIdentifier(name)) + return name.text; + if (ts.isNumericLiteral(name)) return name.text; + return null; +} + +function hasDefaultModifier(node: ts.Node): boolean { + return (ts.getModifiers(node) ?? []).some((m) => m.kind === ts.SyntaxKind.DefaultKeyword); +} + +/* ── declaration files ─────────────────────────────────────────────── */ + +type TsPos = { node: ts.Node; optional: boolean; element?: boolean }; + +class TsResolver { + /** Top-level declarations by name, ambient module bodies included. */ + private readonly roots = new Map(); + private exportEquals: string | null = null; + private exportDefault: ts.Node[] = []; + + constructor( + private readonly text: string, + sf: ts.SourceFile, + ) { + this.collect(sf.statements.nodes, true); + } + + private collect(statements: readonly ts.Statement[], top: boolean): void { + for (const s of statements) { + if (ts.isExportAssignment(s)) { + if (ts.isIdentifier(s.expression)) { + if (s.isExportEquals) this.exportEquals = s.expression.text; + else this.exportDefault.push(...this.rootsNamed(s.expression.text)); + } + continue; + } + if (ts.isModuleDeclaration(s) && ts.isStringLiteral(s.name)) { + // `declare module "pkg" { ... }`: its statements are the module's. + if (top && s.body !== undefined && ts.isModuleBlock(s.body)) + this.collect(s.body.statements.nodes, false); + continue; + } + if (ts.isVariableStatement(s)) { + for (const d of s.declarationList.declarations.nodes) this.add(nameText(d.name), d); + continue; + } + const named = + ts.isFunctionDeclaration(s) || + ts.isClassDeclaration(s) || + ts.isInterfaceDeclaration(s) || + ts.isTypeAliasDeclaration(s) || + ts.isModuleDeclaration(s) || + ts.isEnumDeclaration(s); + if (!named) continue; + this.add(nameText((s as ts.FunctionDeclaration).name), s); + if ((ts.isFunctionDeclaration(s) || ts.isClassDeclaration(s)) && hasDefaultModifier(s)) + this.exportDefault.push(s); + } + } + + private add(name: string | null, node: ts.Node): void { + if (name === null) return; + const list = this.roots.get(name); + if (list === undefined) this.roots.set(name, [node]); + else list.push(node); + } + + private rootsNamed(name: string): ts.Node[] { + return this.roots.get(name) ?? []; + } + + resolve(sel: Selector): Step { + let start: ts.Node[]; + if (sel.root === "export=") + start = this.exportEquals === null ? [] : this.rootsNamed(this.exportEquals); + else if (sel.root === "default") start = this.exportDefault; + else start = this.rootsNamed(sel.root); + let positions: TsPos[] = start.map((node) => ({ node, optional: false })); + // A position that cannot continue is a problem only when no other + // position of the same name matches (a function merged with a + // namespace, an export and its declaration). + let problem: string | null = null; + for (const seg of sel.segments) { + const next: TsPos[] = []; + for (const pos of positions) { + const step = this.step(pos, seg); + if (typeof step === "string") problem ??= step; + else next.push(...step); + } + positions = next; + if (positions.length === 0) + return problem === null ? { slots: [] } : { problem: `'${sel.text}' ${problem}` }; + } + const slots: Slot[] = []; + for (const pos of positions) { + const slot = this.slotOf(pos); + if (typeof slot === "string") problem ??= slot; + else slots.push(slot); + } + if (slots.length === 0 && problem !== null) return { problem: `'${sel.text}' ${problem}` }; + return { slots }; + } + + private typeSlot(type: ts.TypeNode | undefined, optional: boolean): Slot { + return { + span: type === undefined ? null : { start: tokenStartOfNode(type), end: type.end }, + optional, + insert: null, + jsdoc: false, + }; + } + + private slotOf(pos: TsPos): Slot | string { + const n = pos.node; + if (ts.isTypeNode(n)) return { ...this.typeSlot(n, pos.optional), wrap: pos.element === true }; + if (ts.isPropertySignature(n) || ts.isPropertyDeclaration(n)) + return this.typeSlot( + n.type, + pos.optional || n.postfixToken?.kind === ts.SyntaxKind.QuestionToken, + ); + if ( + ts.isVariableDeclaration(n) || + ts.isTypeAliasDeclaration(n) || + ts.isIndexSignatureDeclaration(n) + ) + return this.typeSlot(n.type, false); + if (ts.isParameter(n)) + return this.typeSlot(n.type, n.questionToken !== undefined || n.initializer !== undefined); + if (ts.isGetAccessor(n)) return this.typeSlot(n.type, false); + if (this.isFunctionLike(n)) + return "names a function; add '()' for its return type or '(name)' for a parameter"; + return "names a declaration without a type of its own; name one of its members"; + } + + private isFunctionLike(n: ts.Node): boolean { + return ( + ts.isFunctionDeclaration(n) || + ts.isMethodSignatureDeclaration(n) || + ts.isMethodDeclaration(n) || + ts.isCallSignatureDeclaration(n) || + ts.isFunctionTypeNode(n) + ); + } + + /** The type a position stands for when a segment navigates into it. */ + private typeOf(n: ts.Node): ts.TypeNode | undefined { + if ( + ts.isPropertySignature(n) || + ts.isPropertyDeclaration(n) || + ts.isVariableDeclaration(n) || + ts.isTypeAliasDeclaration(n) || + ts.isParameter(n) + ) + return n.type; + return undefined; + } + + /** Object-like members a type node holds, following parentheses, unions, + * intersections, `Promise` and references to declarations in this + * file. Null when it passes through a type it cannot follow. */ + private containers(type: ts.TypeNode, depth = 0): ts.Node[] | null { + if (depth > 8) return null; + if (ts.isTypeLiteralNode(type)) return [type]; + if (ts.isParenthesizedTypeNode(type)) return this.containers(type.type, depth + 1); + if (ts.isUnionTypeNode(type) || ts.isIntersectionTypeNode(type)) { + const out: ts.Node[] = []; + for (const arm of type.types.nodes) { + if (arm.kind === ts.SyntaxKind.LiteralType || this.isKeywordType(arm)) continue; + const c = this.containers(arm, depth + 1); + if (c === null) return null; + out.push(...c); + } + return out; + } + if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) { + const name = type.typeName.text; + const args = type.typeArguments?.nodes ?? []; + if ((name === "Promise" || name === "PromiseLike") && args.length === 1) + return this.containers(args[0]!, depth + 1); + if (args.length > 0) return null; + const decls = this.rootsNamed(name); + if (decls.length === 0) return null; + const out: ts.Node[] = []; + for (const d of decls) { + if (ts.isTypeAliasDeclaration(d)) { + const c = this.containers(d.type, depth + 1); + if (c === null) return null; + out.push(...c); + } else out.push(d); + } + return out; + } + return null; + } + + private isKeywordType(n: ts.Node): boolean { + return ( + n.kind === ts.SyntaxKind.NullKeyword || + n.kind === ts.SyntaxKind.UndefinedKeyword || + n.kind === ts.SyntaxKind.StringKeyword || + n.kind === ts.SyntaxKind.NumberKeyword || + n.kind === ts.SyntaxKind.BooleanKeyword + ); + } + + private membersOf(n: ts.Node): readonly ts.Node[] | null { + if (ts.isInterfaceDeclaration(n) || ts.isTypeLiteralNode(n) || ts.isClassDeclaration(n)) + return n.members.nodes; + if (ts.isModuleDeclaration(n)) + return n.body !== undefined && ts.isModuleBlock(n.body) ? n.body.statements.nodes : []; + return null; + } + + /** Expands a position into the object-like nodes that hold members. */ + private holders(pos: TsPos): ts.Node[] | string { + const n = pos.node; + if (this.membersOf(n) !== null) return [n]; + if (ts.isTypeAliasDeclaration(n) || ts.isTypeNode(n) || this.typeOf(n) !== undefined) { + const type = ts.isTypeNode(n) ? n : this.typeOf(n)!; + const c = this.containers(type); + if (c === null) + return "passes through a type it cannot follow (a generic, mapped, conditional or imported type); name the declared type itself"; + return c; + } + if (ts.isFunctionDeclaration(n) || ts.isEnumDeclaration(n)) return []; + return "has a member segment after a declaration without members"; + } + + private signaturesOf(pos: TsPos): ts.Node[] | string { + const n = pos.node; + if (this.isFunctionLike(n)) return [n]; + if (ts.isInterfaceDeclaration(n) || ts.isTypeLiteralNode(n)) + return n.members.nodes.filter((m) => ts.isCallSignatureDeclaration(m)); + const type = ts.isTypeNode(n) ? n : this.typeOf(n); + if (type !== undefined) { + let t: ts.TypeNode = type; + while (ts.isParenthesizedTypeNode(t)) t = t.type; + if (ts.isFunctionTypeNode(t)) return [t]; + const c = this.containers(t); + if (c === null) + return "passes through a type it cannot follow; name the declared type itself"; + return c.flatMap((h) => + ts.isInterfaceDeclaration(h) || ts.isTypeLiteralNode(h) + ? h.members.nodes.filter((m) => ts.isCallSignatureDeclaration(m)) + : [], + ); + } + if (ts.isModuleDeclaration(n) || ts.isClassDeclaration(n)) return []; + return "has '()' after a declaration that is not a function"; + } + + private step(pos: TsPos, seg: SelectorSegment): TsPos[] | string { + switch (seg.kind) { + case "member": { + const holders = this.holders(pos); + if (typeof holders === "string") return holders; + const out: TsPos[] = []; + for (const h of holders) { + const members = this.membersOf(h) ?? []; + for (const m of members) { + if (ts.isVariableStatement(m)) { + for (const d of m.declarationList.declarations.nodes) + if (nameText(d.name) === seg.name) out.push({ node: d, optional: false }); + continue; + } + if (nameText((m as ts.PropertySignatureDeclaration).name) === seg.name) + out.push({ node: m, optional: false }); + } + } + return out; + } + case "return": + case "param": { + const sigs = this.signaturesOf(pos); + if (typeof sigs === "string") return sigs; + const out: TsPos[] = []; + for (const s of sigs) { + const sig = s as ts.FunctionDeclaration; + if (seg.kind === "return") { + if (sig.type !== undefined) out.push({ node: sig.type, optional: false }); + else return "has no written return type"; + } else { + for (const p of sig.parameters.nodes) + if (nameText(p.name) === seg.name) out.push({ node: p, optional: false }); + } + } + return out; + } + case "element": { + let t: ts.TypeNode | undefined = ts.isTypeNode(pos.node) ? pos.node : this.typeOf(pos.node); + while (t !== undefined && ts.isParenthesizedTypeNode(t)) t = t.type; + if (t !== undefined && ts.isArrayTypeNode(t)) + return [{ node: t.elementType, optional: false, element: true }]; + if ( + t !== undefined && + ts.isTypeReferenceNode(t) && + ts.isIdentifier(t.typeName) && + (t.typeName.text === "Array" || t.typeName.text === "ReadonlyArray") && + t.typeArguments?.nodes.length === 1 + ) + return [{ node: t.typeArguments.nodes[0]!, optional: false }]; + return "has '[]' after a type that is not an array"; + } + case "index": { + const holders = this.holders(pos); + if (typeof holders === "string") return holders; + const want = + seg.key === "string" ? ts.SyntaxKind.StringKeyword : ts.SyntaxKind.NumberKeyword; + const out: TsPos[] = []; + for (const h of holders) + for (const m of this.membersOf(h) ?? []) + if (ts.isIndexSignatureDeclaration(m) && m.parameters.nodes[0]?.type?.kind === want) + out.push({ node: m, optional: false }); + return out; + } + } + } +} + +/* ── JSDoc in JavaScript ───────────────────────────────────────────── */ + +interface JsDocTag { + tag: string; + /** Inner span of the `{...}` type, or null. */ + type: { start: number; end: number } | null; + /** Parameter or property name (brackets and defaults removed). */ + name: string | null; + optional: boolean; + /** Offset of the `@`. */ + at: number; +} + +interface JsDocComment { + start: number; + end: number; + tags: JsDocTag[]; +} + +function parseJsDoc(text: string, start: number, end: number): JsDocComment { + const tags: JsDocTag[] = []; + const re = /(^|[\s*])@([A-Za-z]+)/g; + re.lastIndex = start + 3; + const body = text.slice(0, end - 2); + for (let m = re.exec(body); m !== null; m = re.exec(body)) { + const at = m.index + m[1]!.length; + const tag = m[2]!; + let i = at + 1 + tag.length; + while (i < body.length && (body[i] === " " || body[i] === "\t")) i++; + let type: JsDocTag["type"] = null; + if (body[i] === "{") { + let depth = 0; + let j = i; + for (; j < body.length; j++) { + if (body[j] === "{") depth++; + else if (body[j] === "}") { + depth--; + if (depth === 0) break; + } + } + if (j >= body.length) continue; + type = { start: i + 1, end: j }; + // Trim the inner span to the type text. + while (type.start < type.end && /\s/.test(text[type.start]!)) type.start++; + while (type.end > type.start && /\s/.test(text[type.end - 1]!)) type.end--; + i = j + 1; + re.lastIndex = i; + } + let name: string | null = null; + let optional = false; + const rest = /^[ \t]*(\[)?[ \t]*([A-Za-z_$][\w$.]*)/.exec(body.slice(i, i + 200)); + if (rest !== null) { + name = rest[2]!; + optional = rest[1] !== undefined; + } + if (type !== null && text[type.end - 1] === "=") optional = true; + tags.push({ tag, type, name, optional, at }); + } + return { start, end, tags }; +} + +const RETURN_TAGS = new Set(["returns", "return"]); +const PROPERTY_TAGS = new Set(["property", "prop"]); + +/** A declaration in JavaScript a selector can stand on. */ +type JsPos = + | { kind: "decl"; node: ts.Node; owner: ts.Node; fn: ts.Node | null; members: ts.Node | null } + | { kind: "typedef"; comment: JsDocComment; tagIndex: number; prefix: string } + | { kind: "slot"; slot: Slot }; + +class JsResolver { + private readonly comments: JsDocComment[] = []; + private readonly roots = new Map(); + private readonly exportEquals: JsPos[] = []; + private readonly exportDefault: JsPos[] = []; + + constructor( + private readonly text: string, + sf: ts.SourceFile, + ) { + const re = /\/\*\*(?!\/)[\s\S]*?\*\//g; + for (let m = re.exec(text); m !== null; m = re.exec(text)) + this.comments.push(parseJsDoc(text, m.index, m.index + m[0].length)); + for (const c of this.comments) + c.tags.forEach((t, i) => { + if ((t.tag === "typedef" || t.tag === "callback") && t.name !== null) + this.add(t.name, { kind: "typedef", comment: c, tagIndex: i, prefix: "" }); + }); + for (const s of sf.statements.nodes) this.collect(s); + } + + private add(name: string, pos: JsPos): void { + const list = this.roots.get(name); + if (list === undefined) this.roots.set(name, [pos]); + else list.push(pos); + } + + /** The position of a declaration whose value is `value` (or none). */ + private declPos(node: ts.Node, owner: ts.Node, value: ts.Node | undefined): JsPos { + let fn: ts.Node | null = null; + let members: ts.Node | null = null; + if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) fn = node; + else if (ts.isClassDeclaration(node)) members = node; + if (value !== undefined) { + let v = value; + while (ts.isParenthesizedExpression(v)) v = v.expression; + if (ts.isFunctionExpression(v) || ts.isArrowFunction(v)) fn = v; + else if (ts.isClassExpression(v) || ts.isObjectLiteralExpression(v)) members = v; + } + return { kind: "decl", node, owner, fn, members }; + } + + private collect(s: ts.Statement): void { + if (ts.isFunctionDeclaration(s) || ts.isClassDeclaration(s)) { + const pos = this.declPos(s, s, undefined); + const name = nameText(s.name); + if (name !== null) this.add(name, pos); + if (hasDefaultModifier(s)) this.exportDefault.push(pos); + return; + } + if (ts.isVariableStatement(s)) { + const decls = s.declarationList.declarations.nodes; + for (const d of decls) { + const name = nameText(d.name); + if (name !== null) + this.add(name, this.declPos(d, decls.length === 1 ? s : d, d.initializer)); + } + return; + } + if (ts.isExportAssignment(s)) { + if (!s.isExportEquals) this.exportDefault.push(this.declPos(s, s, s.expression)); + return; + } + if (!ts.isExpressionStatement(s)) return; + const e = s.expression; + if (!ts.isBinaryExpression(e) || e.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return; + const target = exportTarget(e.left); + if (target === null) return; + if (target === "export=") { + this.exportEquals.push(this.declPos(s, s, e.right)); + // `module.exports = name`: the named declaration is the export. + if (ts.isIdentifier(e.right)) { + const named = this.roots.get(e.right.text); + if (named !== undefined) this.exportEquals.push(...named); + } + } else if (ts.isIdentifier(e.right) && this.roots.has(e.right.text)) { + // `exports.name = name`: the named declaration is the export. + for (const pos of this.roots.get(e.right.text)!) + if (pos.kind === "decl") this.add(target, pos); + } else this.add(target, this.declPos(s, s, e.right)); + } + + /** The last JSDoc comment directly before a node's first token. */ + private docOf(owner: ts.Node): JsDocComment | null { + const start = tokenStartOfNode(owner); + for (let i = this.comments.length - 1; i >= 0; i--) { + const c = this.comments[i]!; + if (c.end > start) continue; + return /^\s*$/.test(this.text.slice(c.end, start)) ? c : null; + } + return null; + } + + /** Tags of a comment that belong to the declaration it documents, not + * to a `@typedef` or `@callback` block inside it. */ + private ownTags(c: JsDocComment): JsDocTag[] { + const out: JsDocTag[] = []; + let inBlock = false; + for (const t of c.tags) { + if (t.tag === "typedef" || t.tag === "callback") { + inBlock = true; + continue; + } + if (inBlock && (PROPERTY_TAGS.has(t.tag) || t.tag === "type")) continue; + if (inBlock && t.tag !== "param" && !RETURN_TAGS.has(t.tag)) inBlock = false; + if (inBlock) continue; + out.push(t); + } + return out; + } + + private tagSlot(tag: JsDocTag): Slot { + return { span: tag.type, optional: tag.optional, insert: null, jsdoc: true }; + } + + /** A slot for a tag of the declaration `owner` documents; when it has no + * such tag, a replacement inserts one. */ + private docSlot(owner: ts.Node, tagNames: ReadonlySet, name: string | null): Slot { + const doc = this.docOf(owner); + if (doc !== null) { + const tag = this.ownTags(doc).find( + (t) => tagNames.has(t.tag) && (name === null || t.name === name), + ); + if (tag !== undefined && tag.type !== null) return this.tagSlot(tag); + } + const tagName = [...tagNames][0]!; + const named = name === null ? "" : ` ${name}`; + if (doc !== null) { + const at = doc.end - 2; + const pad = /\s/.test(this.text[at - 1] ?? "") ? "" : " "; + return { + span: null, + optional: false, + insert: { at, prefix: `${pad}@${tagName} {`, suffix: `}${named} ` }, + jsdoc: true, + }; + } + return { + span: null, + optional: false, + insert: { at: tokenStartOfNode(owner), prefix: `/** @${tagName} {`, suffix: `}${named} */ ` }, + jsdoc: true, + }; + } + + resolve(sel: Selector): Step { + let positions: JsPos[]; + if (sel.root === "export=") positions = this.exportEquals; + else if (sel.root === "default") positions = this.exportDefault; + else positions = this.roots.get(sel.root) ?? []; + let problem: string | null = null; + for (const seg of sel.segments) { + const next: JsPos[] = []; + for (const pos of positions) { + const step = this.step(pos, seg); + if (typeof step === "string") problem ??= step; + else next.push(...step); + } + positions = next; + if (positions.length === 0) + return problem === null ? { slots: [] } : { problem: `'${sel.text}' ${problem}` }; + } + const slots: Slot[] = []; + for (const pos of positions) { + const slot = this.slotOf(pos); + if (typeof slot === "string") problem ??= slot; + else if (slot !== null) slots.push(slot); + } + if (slots.length === 0 && problem !== null) return { problem: `'${sel.text}' ${problem}` }; + return { slots }; + } + + private slotOf(pos: JsPos): Slot | string | null { + if (pos.kind === "slot") return pos.slot; + if (pos.kind === "typedef") { + const own = pos.comment.tags[pos.tagIndex]!; + if (pos.prefix === "") { + // A typedef's own type: only when it is not the `object` that + // its @property tags describe. + const typeTag = this.typedefTypeTag(pos); + if (typeTag === null || /^object$/i.test(this.text.slice(typeTag.start, typeTag.end))) + return "names a @typedef of an object; name one of its properties"; + return { span: typeTag, optional: false, insert: null, jsdoc: true }; + } + void own; + return null; + } + if (pos.fn !== null && pos.members === null) + return "names a function; add '()' for its return type or '(name)' for a parameter"; + if (pos.members !== null && !ts.isObjectLiteralExpression(pos.members)) + return "names a class; name one of its members"; + return this.docSlot(pos.owner, new Set(["type"]), null); + } + + private typedefTypeTag(pos: { + comment: JsDocComment; + tagIndex: number; + }): { start: number; end: number } | null { + const tags = pos.comment.tags; + const own = tags[pos.tagIndex]!; + if (own.type !== null) return own.type; + for (let i = pos.tagIndex + 1; i < tags.length; i++) { + const t = tags[i]!; + if (t.tag === "type") return t.type; + if (!PROPERTY_TAGS.has(t.tag)) break; + } + return null; + } + + private step(pos: JsPos, seg: SelectorSegment): JsPos[] | string { + // A JSDoc type is not navigated (a nested @property path continues + // from its typedef position instead). + if (pos.kind === "slot") return []; + if (pos.kind === "typedef") { + const tags = pos.comment.tags; + const own = tags[pos.tagIndex]!; + if (own.tag === "callback") { + if (seg.kind !== "return" && seg.kind !== "param") + return "has a member segment after a @callback"; + for (let i = pos.tagIndex + 1; i < tags.length; i++) { + const t = tags[i]!; + if (t.tag === "typedef" || t.tag === "callback") break; + if ( + seg.kind === "return" + ? RETURN_TAGS.has(t.tag) + : t.tag === "param" && t.name === seg.name + ) + return t.type === null ? [] : [{ kind: "slot", slot: this.tagSlot(t) }]; + } + return []; + } + if (seg.kind !== "member") return "can only name properties of a @typedef"; + const name = pos.prefix === "" ? seg.name : `${pos.prefix}.${seg.name}`; + for (let i = pos.tagIndex + 1; i < tags.length; i++) { + const t = tags[i]!; + if (t.tag === "typedef" || t.tag === "callback") break; + if (!PROPERTY_TAGS.has(t.tag) && t.tag !== "type") break; + if (PROPERTY_TAGS.has(t.tag) && t.name === name && t.type !== null) { + // A nested property path continues from here. + return [ + { kind: "slot", slot: this.tagSlot(t) }, + { ...pos, prefix: name }, + ]; + } + } + return []; + } + switch (seg.kind) { + case "return": + if (pos.fn === null) return "has '()' after a declaration that is not a function"; + return [{ kind: "slot", slot: this.docSlot(this.docOwner(pos), RETURN_TAGS, null) }]; + case "param": { + if (pos.fn === null) return "has '(name)' after a declaration that is not a function"; + const fn = pos.fn as ts.FunctionExpression; + if (!fn.parameters.nodes.some((p) => nameText(p.name) === seg.name)) return []; + return [ + { kind: "slot", slot: this.docSlot(this.docOwner(pos), new Set(["param"]), seg.name) }, + ]; + } + case "member": { + if (pos.members === null) return []; + return this.membersNamed(pos.members, seg.name); + } + default: + return "navigates into a JSDoc type; name the declared type itself"; + } + } + + /** The node whose leading comment documents a declaration. */ + private docOwner(pos: { owner: ts.Node }): ts.Node { + return pos.owner; + } + + private membersNamed(holder: ts.Node, name: string): JsPos[] { + const out: JsPos[] = []; + if (ts.isObjectLiteralExpression(holder)) { + for (const p of holder.properties.nodes) { + if (nameText((p as ts.PropertyAssignment).name) !== name) continue; + if (ts.isPropertyAssignment(p)) out.push(this.declPos(p, p, p.initializer)); + else if (ts.isMethodDeclaration(p)) out.push(this.declPos(p, p, undefined)); + // `{ parse }`: the declaration of that name. + else if (ts.isShorthandPropertyAssignment(p)) out.push(...(this.roots.get(name) ?? [])); + } + return out; + } + const cls = holder as ts.ClassDeclaration; + for (const m of cls.members.nodes) { + if (ts.isConstructorDeclaration(m) && m.body !== undefined) { + // `this.name = value` in the constructor, documented by its comment. + for (const s of m.body.statements.nodes) { + if (!ts.isExpressionStatement(s) || !ts.isBinaryExpression(s.expression)) continue; + const e = s.expression; + if ( + e.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(e.left) && + e.left.expression.kind === ts.SyntaxKind.ThisKeyword && + nameText(e.left.name) === name + ) + out.push(this.declPos(s, s, e.right)); + } + continue; + } + if (nameText((m as ts.MethodDeclaration).name) !== name) continue; + if (ts.isMethodDeclaration(m)) out.push(this.declPos(m, m, undefined)); + else if (ts.isPropertyDeclaration(m)) out.push(this.declPos(m, m, m.initializer)); + else if (ts.isGetAccessor(m)) + out.push({ kind: "decl", node: m, owner: m, fn: m, members: null }); + } + return out; + } +} + +/** `module.exports` → "export="; `exports.x` / `module.exports.x` → "x". */ +function exportTarget(left: ts.Expression): string | null { + if (!ts.isPropertyAccessExpression(left)) return null; + const obj = left.expression; + const prop = nameText(left.name); + if (prop === null) return null; + if (ts.isIdentifier(obj) && obj.text === "module" && prop === "exports") return "export="; + if (ts.isIdentifier(obj) && obj.text === "exports") return prop; + if ( + ts.isPropertyAccessExpression(obj) && + ts.isIdentifier(obj.expression) && + obj.expression.text === "module" && + nameText(obj.name) === "exports" + ) + return prop; + return null; +} diff --git a/packages/compiler/src/frontend/type-patches/selector.test.ts b/packages/compiler/src/frontend/type-patches/selector.test.ts new file mode 100644 index 000000000..716c994a8 --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/selector.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, test } from "vitest"; +import { parseSelector, parseTypeText, splitUnion } from "./selector.js"; + +describe("parseSelector", () => { + test("members, returns, parameters, elements and index signatures", () => { + expect(parseSelector("HelpContext.helpWidth")).toEqual({ + file: null, + root: "HelpContext", + segments: [{ kind: "member", name: "helpWidth" }], + text: "HelpContext.helpWidth", + }); + expect(parseSelector("index.js:parse()")).toMatchObject({ + file: "index.js", + root: "parse", + segments: [{ kind: "return" }], + }); + expect(parseSelector("export=()")).toMatchObject({ + root: "export=", + segments: [{ kind: "return" }], + }); + expect(parseSelector("fetchUser(id)")).toMatchObject({ + segments: [{ kind: "param", name: "id" }], + }); + expect(parseSelector("list()[].name")).toMatchObject({ + segments: [{ kind: "return" }, { kind: "element" }, { kind: "member", name: "name" }], + }); + expect(parseSelector("Env[string]")).toMatchObject({ + segments: [{ kind: "index", key: "string" }], + }); + }); + + test("malformed selectors say why", () => { + expect(parseSelector("")).toEqual({ error: "is empty" }); + expect(parseSelector("../x.js:f()")).toEqual({ error: "names a file outside the package" }); + expect(parseSelector("1abc")).toHaveProperty("error"); + expect(parseSelector("a.")).toHaveProperty("error"); + expect(parseSelector("a(1)")).toHaveProperty("error"); + }); +}); + +describe("parseTypeText", () => { + test("a leading bar widens, anything else replaces", () => { + expect(parseTypeText("| undefined")).toEqual({ kind: "widen", arms: ["undefined"] }); + expect(parseTypeText("| undefined | null")).toEqual({ + kind: "widen", + arms: ["undefined", "null"], + }); + expect(parseTypeText("string | null")).toEqual({ kind: "replace", text: "string | null" }); + expect(parseTypeText("a\nb")).toEqual({ error: "must be one line" }); + }); + + test("splitUnion keeps nested unions and arrows together", () => { + expect(splitUnion("(a | b) | (() => c | d) | { x: e | f }")).toEqual([ + "(a | b)", + "(() => c | d)", + "{ x: e | f }", + ]); + }); +}); diff --git a/packages/compiler/src/frontend/type-patches/selector.ts b/packages/compiler/src/frontend/type-patches/selector.ts new file mode 100644 index 000000000..8fdd8b67c --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/selector.ts @@ -0,0 +1,133 @@ +/* Type patch selectors: names of declarations in a package, written the + * same way for JSDoc in its JavaScript and for its declaration files. + * + * selector := [ file ":" ] root segment* + * root := identifier | "export=" | "default" + * segment := "." name member (interface, type alias, @typedef, + * class, namespace, object type) + * | "()" return type + * | "(" name ")" type of the named parameter + * | "[]" element type of an array type + * | "[string]" | "[number]" value type of an index signature + * + * Examples: `HelpContext.helpWidth`, `index.js:parse()`, `export=()`, + * `fetchUser().email`, `Env[string]`. */ + +export type SelectorSegment = + | { kind: "member"; name: string } + | { kind: "return" } + | { kind: "param"; name: string } + | { kind: "element" } + | { kind: "index"; key: "string" | "number" }; + +export interface Selector { + /** Package-relative file the root must be declared in, or null. */ + file: string | null; + /** A declared name, `export=` or `default`. */ + root: string; + segments: readonly SelectorSegment[]; + /** The selector as written. */ + text: string; +} + +const IDENT = /^[A-Za-z_$][\w$]*/; + +/** The parsed selector, or a message saying what is wrong with it. */ +export function parseSelector(text: string): Selector | { error: string } { + let rest = text.trim(); + if (rest === "") return { error: "is empty" }; + let file: string | null = null; + const colon = rest.indexOf(":"); + if (colon >= 0) { + file = rest.slice(0, colon).replace(/^\.\//, ""); + rest = rest.slice(colon + 1); + if (file === "" || file.startsWith("/") || file.split("/").includes("..")) + return { error: "names a file outside the package" }; + } + let root: string; + if (rest.startsWith("export=")) { + root = "export="; + rest = rest.slice("export=".length); + } else { + const m = IDENT.exec(rest); + if (m === null) return { error: "must start with a declared name, 'export=' or 'default'" }; + root = m[0]; + rest = rest.slice(root.length); + } + const segments: SelectorSegment[] = []; + while (rest !== "") { + if (rest.startsWith(".")) { + const m = IDENT.exec(rest.slice(1)); + if (m === null) return { error: `has no member name after '.' at '${rest}'` }; + segments.push({ kind: "member", name: m[0] }); + rest = rest.slice(1 + m[0].length); + } else if (rest.startsWith("()")) { + segments.push({ kind: "return" }); + rest = rest.slice(2); + } else if (rest.startsWith("(")) { + const m = /^\(\s*([A-Za-z_$][\w$]*)\s*\)/.exec(rest); + if (m === null) return { error: `has a malformed parameter at '${rest}'` }; + segments.push({ kind: "param", name: m[1]! }); + rest = rest.slice(m[0].length); + } else if (rest.startsWith("[]")) { + segments.push({ kind: "element" }); + rest = rest.slice(2); + } else if (rest.startsWith("[string]") || rest.startsWith("[number]")) { + segments.push({ kind: "index", key: rest.startsWith("[string]") ? "string" : "number" }); + rest = rest.slice("[string]".length); + } else { + return { error: `has an unexpected '${rest}'` }; + } + } + return { file, root, segments, text: text.trim() }; +} + +/** A widening (`| undefined`) or a replacement type. */ +export type TypePatchOp = + | { kind: "widen"; arms: readonly string[] } + | { kind: "replace"; text: string }; + +/** The operation a patch's type text stands for, or a message. */ +export function parseTypeText(text: string): TypePatchOp | { error: string } { + if (/[\r\n]/.test(text)) return { error: "must be one line" }; + const t = text.trim(); + if (t === "") return { error: "is empty" }; + if (t.startsWith("|")) { + const arms = splitUnion(t.slice(1)); + if (arms.some((a) => a === "")) return { error: "has an empty union member" }; + return { kind: "widen", arms }; + } + return { kind: "replace", text: t }; +} + +/** The top-level union members of a type text, trimmed. */ +export function splitUnion(text: string): string[] { + const out: string[] = []; + let depth = 0; + let quote: string | null = null; + let start = 0; + for (let i = 0; i < text.length; i++) { + const ch = text[i]!; + if (quote !== null) { + if (ch === "\\") i++; + else if (ch === quote) quote = null; + continue; + } + if (ch === '"' || ch === "'" || ch === "`") quote = ch; + else if (ch === "(" || ch === "[" || ch === "{" || ch === "<") depth++; + else if (ch === ")" || ch === "]" || ch === "}" || ch === ">") { + // `=>` is not a closing bracket. + if (!(ch === ">" && text[i - 1] === "=")) depth--; + } else if (ch === "|" && depth === 0) { + out.push(text.slice(start, i).trim()); + start = i + 1; + } + } + out.push(text.slice(start).trim()); + return out; +} + +/** Whitespace-insensitive text of a type for comparisons. */ +export function normalizeType(text: string): string { + return text.replace(/\s+/g, ""); +} diff --git a/packages/compiler/src/frontend/type-patches/semver.test.ts b/packages/compiler/src/frontend/type-patches/semver.test.ts new file mode 100644 index 000000000..83663e3a2 --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/semver.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from "vitest"; +import { isValidRange, satisfies } from "./semver.js"; + +describe("satisfies", () => { + test.each([ + ["15.0.0", "^15.0.0", true], + ["15.4.2", "^15.0.0", true], + ["16.0.0", "^15.0.0", false], + ["0.2.5", "^0.2.3", true], + ["0.3.0", "^0.2.3", false], + ["0.0.3", "^0.0.3", true], + ["0.0.4", "^0.0.3", false], + ["1.2.9", "~1.2.3", true], + ["1.3.0", "~1.2.3", false], + ["1.9.0", "~1", true], + ["2.1.3", "2.1.x", true], + ["2.2.0", "2.1.x", false], + ["2.1.3", "2", true], + ["3.0.0", "2", false], + ["2.1.3", "*", true], + ["1.2.3", "1.2.3", true], + ["1.2.4", "=1.2.3", false], + ["1.5.0", ">=1.2.0 <2.0.0", true], + ["2.0.0", ">=1.2.0 <2.0.0", false], + ["2.0.0", ">= 1.2.0", true], + ["1.3.0", ">1.2", true], + ["1.2.9", ">1.2", false], + ["1.2.9", "<=1.2", true], + ["1.3.0", "<=1.2", false], + ["2.3.9", "1.2.3 - 2.3", true], + ["2.4.0", "1.2.3 - 2.3", false], + ["3.1.0", "^1.0.0 || ^3.0.0", true], + ["2.1.0", "^1.0.0 || ^3.0.0", false], + ["2.0.0-beta.1", "^1.0.0", false], + ["2.0.0-beta.1", "^2.0.0", false], + ["2.0.0-beta.2", "^2.0.0-beta.1", true], + ["2.0.0-alpha", "^2.0.0-beta.1", false], + ["2.1.0-beta.2", "^2.0.0-beta.1", false], + ])("%s satisfies %s: %s", (version, range, expected) => { + expect(satisfies(version, range)).toBe(expected); + }); + + test("malformed input never satisfies", () => { + expect(satisfies("not-a-version", "^1.0.0")).toBe(false); + expect(isValidRange("^banana")).toBe(false); + expect(isValidRange(">=1 <2 || 3.x")).toBe(true); + }); +}); diff --git a/packages/compiler/src/frontend/type-patches/semver.ts b/packages/compiler/src/frontend/type-patches/semver.ts new file mode 100644 index 000000000..86e9d1183 --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/semver.ts @@ -0,0 +1,229 @@ +/* The npm semver range subset type patches key on (`commander@^15.0.0`): + * exact versions, x-ranges, `^`, `~`, comparators, hyphen ranges and `||`. + * A prerelease version satisfies a comparator set only when one of the + * set's comparators names a prerelease of the same major.minor.patch, as + * npm does. */ + +interface Version { + major: number; + minor: number; + patch: number; + pre: readonly (string | number)[]; +} + +interface Comparator { + op: "<" | "<=" | ">" | ">=" | "="; + version: Version; +} + +/** A partial version: missing or wildcard parts are null. */ +interface Partial { + major: number | null; + minor: number | null; + patch: number | null; + pre: readonly (string | number)[]; +} + +const VERSION = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/; + +function preParts(text: string | undefined): (string | number)[] { + if (text === undefined || text === "") return []; + return text.split(".").map((p) => (/^\d+$/.test(p) ? Number(p) : p)); +} + +/** A full version, or null. */ +export function parseVersion(text: string): Version | null { + const m = VERSION.exec(text.trim()); + if (m === null) return null; + return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), pre: preParts(m[4]) }; +} + +function parsePartial(text: string): Partial | null { + const m = + /^v?(\d+|[xX*])(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec( + text, + ); + if (m === null) return null; + const part = (s: string | undefined): number | null => + s === undefined || s === "x" || s === "X" || s === "*" ? null : Number(s); + const major = part(m[1]); + const minor = major === null ? null : part(m[2]); + const patch = minor === null ? null : part(m[3]); + return { major, minor, patch, pre: patch === null ? [] : preParts(m[4]) }; +} + +function v( + major: number, + minor: number, + patch: number, + pre: readonly (string | number)[] = [], +): Version { + return { major, minor, patch, pre }; +} + +/** `-0` marks the lowest prerelease, so `<2.0.0-0` excludes 2.0.0's + * prereleases as npm's desugaring does. */ +const LOWEST: readonly (string | number)[] = [0]; + +function comparePre(a: readonly (string | number)[], b: readonly (string | number)[]): number { + if (a.length === 0 && b.length === 0) return 0; + if (a.length === 0) return 1; + if (b.length === 0) return -1; + for (let i = 0; i < Math.max(a.length, b.length); i++) { + const x = a[i]; + const y = b[i]; + if (x === undefined) return -1; + if (y === undefined) return 1; + if (x === y) continue; + if (typeof x === "number" && typeof y === "number") return x < y ? -1 : 1; + if (typeof x === "number") return -1; + if (typeof y === "number") return 1; + return x < y ? -1 : 1; + } + return 0; +} + +export function compareVersions(a: Version, b: Version): number { + if (a.major !== b.major) return a.major < b.major ? -1 : 1; + if (a.minor !== b.minor) return a.minor < b.minor ? -1 : 1; + if (a.patch !== b.patch) return a.patch < b.patch ? -1 : 1; + return comparePre(a.pre, b.pre); +} + +/** The comparators a partial with an operator stands for. */ +function desugar(op: string, p: Partial): Comparator[] | null { + const { major, minor, patch } = p; + if (op === "^") { + if (major === null) return []; + const lo = v(major, minor ?? 0, patch ?? 0, p.pre); + let hi: Version; + if (major > 0 || minor === null) hi = v(major + 1, 0, 0, LOWEST); + else if (minor > 0 || patch === null) hi = v(0, minor + 1, 0, LOWEST); + else hi = v(0, 0, patch + 1, LOWEST); + return [ + { op: ">=", version: lo }, + { op: "<", version: hi }, + ]; + } + if (op === "~") { + if (major === null) return []; + const lo = v(major, minor ?? 0, patch ?? 0, p.pre); + const hi = minor === null ? v(major + 1, 0, 0, LOWEST) : v(major, minor + 1, 0, LOWEST); + return [ + { op: ">=", version: lo }, + { op: "<", version: hi }, + ]; + } + if (op === "" || op === "=") { + if (major === null) return []; + if (minor === null) + return [ + { op: ">=", version: v(major, 0, 0) }, + { op: "<", version: v(major + 1, 0, 0, LOWEST) }, + ]; + if (patch === null) + return [ + { op: ">=", version: v(major, minor, 0) }, + { op: "<", version: v(major, minor + 1, 0, LOWEST) }, + ]; + return [{ op: "=", version: v(major, minor, patch, p.pre) }]; + } + if (major === null) + return op === "<" || op === ">" ? [{ op: "<", version: v(0, 0, 0, LOWEST) }] : []; + if (op === ">") { + if (minor === null) return [{ op: ">=", version: v(major + 1, 0, 0) }]; + if (patch === null) return [{ op: ">=", version: v(major, minor + 1, 0) }]; + return [{ op: ">", version: v(major, minor, patch, p.pre) }]; + } + if (op === ">=") return [{ op: ">=", version: v(major, minor ?? 0, patch ?? 0, p.pre) }]; + if (op === "<") { + if (minor === null) return [{ op: "<", version: v(major, 0, 0, LOWEST) }]; + if (patch === null) return [{ op: "<", version: v(major, minor, 0, LOWEST) }]; + return [{ op: "<", version: v(major, minor, patch, p.pre) }]; + } + if (op === "<=") { + if (minor === null) return [{ op: "<", version: v(major + 1, 0, 0, LOWEST) }]; + if (patch === null) return [{ op: "<", version: v(major, minor + 1, 0, LOWEST) }]; + return [{ op: "<=", version: v(major, minor, patch, p.pre) }]; + } + return null; +} + +/** One `||` alternative: a list of comparators, or null when malformed. */ +function parseSet(text: string): Comparator[] | null { + const t = text.trim(); + const hyphen = /^(\S+)\s+-\s+(\S+)$/.exec(t); + if (hyphen !== null) { + const lo = parsePartial(hyphen[1]!); + const hi = parsePartial(hyphen[2]!); + if (lo === null || hi === null) return null; + return [...(desugar(">=", lo) ?? []), ...(desugar("<=", hi) ?? [])]; + } + if (t === "") return []; + const out: Comparator[] = []; + // `>= 1.2.3` may carry a space between the operator and the version. + const tokens = t.replace(/(<=|>=|<|>|=|\^|~)\s+/g, "$1").split(/\s+/); + for (const token of tokens) { + const m = /^(<=|>=|<|>|=|\^|~>?|)(.*)$/.exec(token); + if (m === null) return null; + const op = m[1] === "~>" ? "~" : m[1]!; + const p = parsePartial(m[2]!); + if (p === null) return null; + const cs = desugar(op, p); + if (cs === null) return null; + out.push(...cs); + } + return out; +} + +/** The parsed range, or null when it is not a semver range. */ +export function parseRange(range: string): Comparator[][] | null { + const sets: Comparator[][] = []; + for (const alt of range.split("||")) { + const set = parseSet(alt); + if (set === null) return null; + sets.push(set); + } + return sets; +} + +export function isValidRange(range: string): boolean { + return parseRange(range) !== null; +} + +function test(c: Comparator, version: Version): boolean { + const cmp = compareVersions(version, c.version); + switch (c.op) { + case "<": + return cmp < 0; + case "<=": + return cmp <= 0; + case ">": + return cmp > 0; + case ">=": + return cmp >= 0; + default: + return cmp === 0; + } +} + +/** True when `version` satisfies `range` (false for malformed input). */ +export function satisfies(version: string, range: string): boolean { + const ver = parseVersion(version); + const sets = parseRange(range); + if (ver === null || sets === null) return false; + return sets.some((set) => { + if (!set.every((c) => test(c, ver))) return false; + if (ver.pre.length === 0) return true; + // A prerelease only matches a set that names a prerelease of the same + // version tuple (the LOWEST markers of upper bounds do not count). + return set.some( + (c) => + c.version.pre.length > 0 && + c.version.pre !== LOWEST && + c.version.major === ver.major && + c.version.minor === ver.minor && + c.version.patch === ver.patch, + ); + }); +} diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 3e4871084..ffd56a1bf 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -52,7 +52,7 @@ import { InternalCompilerError } from "./errors.js"; import { frontendProcessExecArgv } from "./frontend-process-args.js"; import { type ChildProcess, spawn } from "node:child_process"; import { createHash } from "node:crypto"; -import { existsSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import { copyFile, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { basename, dirname, join, resolve } from "node:path"; @@ -119,6 +119,7 @@ import { ffiNativeBuildDiag, iceDiag, nativeCodegenDiag, + isTypePatchCode, type ScrDiagnostic, } from "./diagnostics/diagnostic.js"; import { loadLibraryProfile, type LibraryProfile } from "./library/library-profile.js"; @@ -485,12 +486,31 @@ interface CompileWarningSink { sourceTexts: Map | null; } +/** The package.json texts type patch warnings point into, re-read for a + * cache hit's code frames (performance warnings need none). */ +function cachedWarningSources(warnings: readonly ScrDiagnostic[]): Map { + const texts = new Map(); + for (const w of warnings) { + if (!isTypePatchCode(w.code) || texts.has(w.loc.file)) continue; + try { + texts.set(w.loc.file, readFileSync(w.loc.file, "utf8")); + } catch { + // The frame is omitted; the message stands alone. + } + } + return texts; +} + /** The build's performance warnings (SC61xx), stored with its cache entry * so a cache hit repeats them. */ function performanceWarnings(sink: CompileWarningSink | undefined): { performance?: ScrDiagnostic[]; } { - const performance = (sink?.warnings ?? []).filter((d) => isPerformanceCode(d.code)); + // Type patch warnings (SC8030–SC8033) describe the same unchanged inputs, + // so a hit repeats them too. + const performance = (sink?.warnings ?? []).filter( + (d) => isPerformanceCode(d.code) || isTypePatchCode(d.code), + ); return performance.length === 0 ? {} : { performance }; } @@ -1602,7 +1622,7 @@ async function compileTracked( // names is just as untyped as when it was built. if (warningSink !== undefined && earlyHit.performance !== undefined) { warningSink.warnings = earlyHit.performance; - warningSink.sourceTexts = new Map(); + warningSink.sourceTexts = cachedWarningSources(earlyHit.performance); } const executableCacheOptions = earlyCacheOptions; // Route/proof metadata is independently evictable. A full-compiler diff --git a/packages/compiler/src/project-config.test.ts b/packages/compiler/src/project-config.test.ts index 58ae914ca..04a5d2e0b 100644 --- a/packages/compiler/src/project-config.test.ts +++ b/packages/compiler/src/project-config.test.ts @@ -164,3 +164,80 @@ test("package setting keys named like Object.prototype members are unknown setti ]); expect(c.packages.has("debug")).toBe(false); }); + +test("typePatches: entries keyed by package and range", () => { + const c = config({ + typePatches: { + "commander@^15.0.0": { declarations: { "HelpContext.helpWidth": "| undefined" } }, + "@scope/lib@1.x || 2.x": { + declarations: { "Options.name": "string | null" }, + note: "https://example.invalid/issue/1", + }, + }, + }); + expect(c.diagnostics).toEqual([]); + expect( + c.typePatches.map(({ key, pkg, range, declarations }) => ({ + key, + pkg, + range, + declarations: declarations.map((d) => [d.selector, d.type]), + })), + ).toEqual([ + { + key: "commander@^15.0.0", + pkg: "commander", + range: "^15.0.0", + declarations: [["HelpContext.helpWidth", "| undefined"]], + }, + { + key: "@scope/lib@1.x || 2.x", + pkg: "@scope/lib", + range: "1.x || 2.x", + declarations: [["Options.name", "string | null"]], + }, + ]); + expect(c.typePatches[0]!.declarations[0]!.offset).toBeGreaterThan(0); +}); + +test("typePatches: malformed keys, ranges, selectors and types are SC8004", () => { + const c = config({ + typePatches: { + commander: { declarations: { "A.b": "| undefined" } }, + "ms@^banana": { declarations: {} }, + "@types/node@*": { declarations: {} }, + "typescript@5": { declarations: {} }, + "lib@^1.0.0": { + declarations: { + "1bad": "| undefined", + "A.b": 3, + "A.c": "string\nnumber", + "A.d": "| undefined", + }, + files: {}, + }, + "other@^1.0.0": [], + }, + }); + expect(c.diagnostics.every((d) => d.code === "SC8004")).toBe(true); + expect(c.diagnostics.map((d) => d.message)).toEqual([ + "package.json 'scriptc.typePatches.commander' must name a package and a version range", + `package.json 'scriptc.typePatches.ms@^banana' has the version range "^banana", which is not a semver range`, + "package.json 'scriptc.typePatches.@types/node@*' patches @types/node, whose declarations ship with scriptc or TypeScript", + "package.json 'scriptc.typePatches.typescript@5' patches typescript, whose declarations ship with scriptc or TypeScript", + "package.json 'scriptc.typePatches.lib@^1.0.0.files' is not a known type patch setting", + "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.1bad' is not a selector: it must start with a declared name, 'export=' or 'default'", + "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.A.b' must map to a type string, not 3", + "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.A.c' has a type that must be one line", + "package.json 'scriptc.typePatches.other@^1.0.0' must be an object, not an array", + ]); + // The valid selector of an entry with an unknown key is dropped with it; + // entries with only bad selectors keep their valid ones. + expect(c.typePatches).toEqual([]); + const partial = config({ + typePatches: { + "lib@^1.0.0": { declarations: { "1bad": "| undefined", "A.d": "| undefined" } }, + }, + }); + expect(partial.typePatches[0]!.declarations.map((d) => d.selector)).toEqual(["A.d"]); +}); diff --git a/packages/compiler/src/project-config.ts b/packages/compiler/src/project-config.ts index 997e997c1..c43a6bd2c 100644 --- a/packages/compiler/src/project-config.ts +++ b/packages/compiler/src/project-config.ts @@ -13,7 +13,12 @@ * "proper-lockfile": { "run": "node", "evaluate": "lazy" } * }, * "nodeIslands": { "auto": true, "modules": ["src/plugins/*.ts"] }, - * "wasm": ["build/module.wasm"] + * "wasm": ["build/module.wasm"], + * "typePatches": { // frontend/type-patches/layer.ts + * "commander@^15.0.0": { + * "declarations": { "HelpContext.helpWidth": "| undefined" } + * } + * } * } * * Node islands are report-only in this version: the policy decides which @@ -23,6 +28,8 @@ import { dirname, join, resolve } from "node:path"; import { projectConfigDiag, type ScrDiagnostic } from "./diagnostics/diagnostic.js"; import { trackedReadFile } from "./frontend/input-tracker.js"; +import { parseSelector, parseTypeText } from "./frontend/type-patches/selector.js"; +import { isValidRange } from "./frontend/type-patches/semver.js"; /** How a package runs: compiled natively, in a Node island, or decided by * the detector. */ @@ -57,6 +64,25 @@ export interface NodeIslandsSetting { speculativeStart: boolean; } +/** One declaration patch: a selector and the type text it maps to. */ +export interface TypePatchDeclaration { + selector: string; + type: string; + /** Offset of the selector key in the manifest, for diagnostics. */ + offset: number; +} + +/** One `scriptc.typePatches["@"]` entry. */ +export interface TypePatchEntry { + /** The key as written. */ + key: string; + pkg: string; + range: string; + declarations: TypePatchDeclaration[]; + /** Offset of the key in the manifest. */ + offset: number; +} + export interface ProjectConfig { /** The package.json the configuration came from, or null. */ manifest: string | null; @@ -66,6 +92,8 @@ export interface ProjectConfig { nodeIslands: NodeIslandsSetting; /** `scriptc.wasm`: module paths, resolved against the manifest. */ wasm: string[]; + /** `scriptc.typePatches`, in manifest order. */ + typePatches: TypePatchEntry[]; /** SC8004 for every unknown key or value. */ diagnostics: ScrDiagnostic[]; } @@ -117,6 +145,7 @@ export function emptyProjectConfig(): ProjectConfig { packages: new Map(), nodeIslands: defaultNodeIslands(), wasm: [], + typePatches: [], diagnostics: [], }; } @@ -299,6 +328,9 @@ export function parseProjectConfig(manifest: string, text: string): ProjectConfi } } + const patches = root["typePatches"]; + if (patches !== undefined) readTypePatches(patches, config, problem, locate); + // `scriptc.wasm` keeps its lenient reading: string entries of an array. const wasm = root["wasm"]; if (Array.isArray(wasm)) { @@ -309,6 +341,136 @@ export function parseProjectConfig(manifest: string, text: string): ProjectConfi return config; } +const TYPE_PATCH_KEYS = ["declarations", "note"]; +/** Packages whose declarations ship with scriptc or TypeScript: a wrong + * one is a bug to report, not a project-local patch. */ +const UNPATCHABLE = /^(?:@types\/node|typescript|@typescript\/.*)$/; +const PACKAGE_NAME = /^(?:@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i; + +/** Reads and validates `scriptc.typePatches`. */ +function readTypePatches( + patches: unknown, + config: ProjectConfig, + problem: (path: string[], detail: string, hint: string) => void, + locate: JsonLocator, +): void { + if (!isRecord(patches)) { + problem( + ["typePatches"], + `must be an object mapping "@" to patches, not ${shown(patches)}`, + `write "typePatches": { "@^1.0.0": { "declarations": { "Type.member": "| undefined" } } }`, + ); + return; + } + for (const key of Object.keys(patches)) { + const at = key.lastIndexOf("@"); + const pkg = at > 0 ? key.slice(0, at) : key; + const range = at > 0 ? key.slice(at + 1).trim() : ""; + if (at <= 0 || range === "" || !PACKAGE_NAME.test(pkg)) { + problem( + ["typePatches", key], + "must name a package and a version range", + `write "${at > 0 ? pkg : key}@^1.0.0": the range says which installed versions the patch describes`, + ); + continue; + } + if (!isValidRange(range)) { + problem( + ["typePatches", key], + `has the version range "${range}", which is not a semver range`, + `use a range such as "^${/^\d/.test(range) ? range.replace(/[^\d.]/g, "") : "1.0.0"}" or "1.2.x"`, + ); + continue; + } + if (UNPATCHABLE.test(pkg)) { + problem( + ["typePatches", key], + `patches ${pkg}, whose declarations ship with scriptc or TypeScript`, + "report a wrong declaration there instead of patching it", + ); + continue; + } + const value = patches[key]; + if (!isRecord(value)) { + problem( + ["typePatches", key], + `must be an object, not ${shown(value)}`, + `write { "declarations": { "Type.member": "| undefined" } }`, + ); + continue; + } + const entry: TypePatchEntry = { + key, + pkg, + range, + declarations: [], + offset: locate.offsetOf(["scriptc", "typePatches", key]), + }; + let valid = true; + for (const k of Object.keys(value)) { + if (!TYPE_PATCH_KEYS.includes(k)) { + problem( + ["typePatches", key, k], + "is not a known type patch setting", + `the settings are ${TYPE_PATCH_KEYS.join(", ")}`, + ); + valid = false; + } + } + const note = value["note"]; + if (note !== undefined && typeof note !== "string") { + problem( + ["typePatches", key, "note"], + `must be a string, not ${shown(note)}`, + "write a short note, such as a link to the upstream issue", + ); + valid = false; + } + const decls = value["declarations"]; + if (decls !== undefined && !isRecord(decls)) { + problem( + ["typePatches", key, "declarations"], + `must be an object mapping selectors to types, not ${shown(decls)}`, + `write "declarations": { "Type.member": "| undefined" }`, + ); + valid = false; + } else if (decls !== undefined) { + for (const selector of Object.keys(decls)) { + const path = ["typePatches", key, "declarations", selector]; + const parsed = parseSelector(selector); + if ("error" in parsed) { + problem( + path, + `is not a selector: it ${parsed.error}`, + `name a declaration such as "Type.member", "fn()", "fn(param)", "file.js:local()" or "export=()"`, + ); + continue; + } + const type = decls[selector]; + if (typeof type !== "string") { + problem( + path, + `must map to a type string, not ${shown(type)}`, + `write "| undefined" to widen, or the whole type to replace it`, + ); + continue; + } + const op = parseTypeText(type); + if ("error" in op) { + problem( + path, + `has a type that ${op.error}`, + `write "| undefined" to widen, or the whole type on one line`, + ); + continue; + } + entry.declarations.push({ selector, type, offset: locate.offsetOf(["scriptc", ...path]) }); + } + } + if (valid) config.typePatches.push(entry); + } +} + let cachedManifest: string | null = null; let cachedText: string | null = null; let cachedConfig: ProjectConfig | null = null; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-dts/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-dts/main.ts.json new file mode 100644 index 000000000..c9a8e15fc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-dts/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/type-patch-dts/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-twin/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-twin/main.ts.json new file mode 100644 index 000000000..faa48f074 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-twin/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/type-patch-types-twin/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/type-patches.test.ts b/packages/compiler/test/type-patches.test.ts new file mode 100644 index 000000000..729a0da37 --- /dev/null +++ b/packages/compiler/test/type-patches.test.ts @@ -0,0 +1,124 @@ +/* The type patch layer (frontend/type-patches/layer.ts) against real + * packages: commander's HelpContext.helpWidth @typedef property and ms's + * @return tags, patched from the fixtures' package.json; installed-version + * selection; and the warnings for patches that do not apply as written. */ +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "vitest"; +import { nodeFrontendServices } from "../src/frontend/services-node.js"; +import { + configureTypePatches, + typePatchDiagnostics, + typePatchedSpans, + typePatchedText, + typePatchMatches, +} from "../src/frontend/type-patches/layer.js"; + +const fixtures = join(import.meta.dirname, "../../../tests/fixtures"); +const parse = (path: string, text: string, kind: "ts" | "js") => + nodeFrontendServices().parse(path, text, kind); + +afterEach(() => configureTypePatches(join(tmpdir(), "none", "main.ts"), parse)); + +test("commander's HelpContext.helpWidth typedef property reads as number | undefined", () => { + const root = join(fixtures, "commander-calc"); + configureTypePatches(join(root, "calc.ts"), parse); + const command = join(root, "node_modules/commander/lib/command.js"); + const text = typePatchedText(command); + expect(text).toBeDefined(); + expect(text).toContain(" * @property {number | undefined} helpWidth\n"); + expect(text).toContain(" * @returns {HelpContext}\n"); + expect(typePatchMatches()).toMatchObject([ + { + file: command, + line: 2481, + before: "number", + after: "number | undefined", + pkg: "commander", + version: "15.0.0", + }, + ]); + const [span] = typePatchedSpans(command); + expect(text!.slice(span!.start, span!.end)).toBe(" | undefined"); + // Its declaration file has an unrelated HelpContext without helpWidth. + expect(typePatchedText(join(root, "node_modules/commander/typings/index.d.ts"))).toBeUndefined(); + // Other files of the package are served as they are. + expect(typePatchedText(join(root, "node_modules/commander/lib/help.js"))).toBeUndefined(); + expect(typePatchDiagnostics()?.diagnostics).toEqual([]); +}); + +test("ms's parse() and export return tags widen", () => { + const root = join(fixtures, "type-patches"); + configureTypePatches(join(root, "ms.ts"), parse); + const file = join(root, "node_modules/ms/index.js"); + const text = typePatchedText(file)!; + const original = readFileSync(file, "utf8"); + expect(text).toBe( + original + .replace(" * @return {String|Number}\n", " * @return {String|Number | undefined}\n") + .replace( + " * @return {Number}\n * @api private\n */\n\nfunction parse", + " * @return {Number | undefined}\n * @api private\n */\n\nfunction parse", + ), + ); + expect(text).not.toBe(original); + expect(typePatchDiagnostics()?.diagnostics.map((d) => d.code)).toEqual([]); +}); + +test("a project without type patches serves every file unchanged", () => { + configureTypePatches(join(fixtures, "npm-static", "ms-cli.ts"), parse); + expect(typePatchedText(join(fixtures, "npm-static/node_modules/ms/index.js"))).toBeUndefined(); + expect(typePatchDiagnostics()).toBeNull(); +}); + +test("patches that do not apply as written warn", () => { + const root = join(fixtures, "type-patches"); + configureTypePatches(join(root, "stale", "main.ts"), parse); + expect(typePatchedText(join(root, "node_modules/termctx/index.js"))).toBeUndefined(); + typePatchedText(join(root, "node_modules/ms/index.js")); + const diags = typePatchDiagnostics()!.diagnostics; + expect(diags.map((d) => [d.code, d.message])).toEqual([ + [ + "SC8030", + "type patch 'termctx@^2.0.0' does not apply to termctx 1.2.0 at ../node_modules/termctx; its declarations are used unpatched", + ], + [ + "SC8031", + "type patch 'ms@^2.1.0': 'index.js:format()' matches no declaration in the files of ms this program loads", + ], + ["SC8032", "type patch 'ms@^2.1.0': 'index.js:fmtShort(ms)' is already declared as Number"], + [ + "SC8033", + "type patch 'ms@^2.1.0': 'index.js:plural()' has no written type to widen; give the whole type instead (index.js)", + ], + ]); + expect( + diags.every((d) => d.loc.file === join(root, "stale", "package.json") && d.loc.start > 0), + ).toBe(true); +}); + +test("an @types twin is matched by the runtime package's installed version", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-")); + try { + const twin = join(fixtures, "npm/cases/type-patch-types-twin"); + configureTypePatches(join(twin, "main.ts"), parse); + const text = typePatchedText(join(twin, "node_modules/@types/tallyjs/index.d.ts"))!; + expect(text).toContain(" max: number | undefined;\n mean: number | null;\n"); + // A range the installed runtime (0.4.2) does not satisfy leaves it alone, + // even though the twin's own version (0.4.0) would. + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { + typePatches: { "tallyjs@0.4.0": { declarations: { "Totals.max": "| undefined" } } }, + }, + }), + ); + configureTypePatches(join(root, "main.ts"), parse); + expect(typePatchedText(join(twin, "node_modules/@types/tallyjs/index.d.ts"))).toBeUndefined(); + expect(typePatchDiagnostics()!.diagnostics.map((d) => d.code)).toEqual(["SC8030"]); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/tests/fixtures/commander-calc/package.json b/tests/fixtures/commander-calc/package.json index f0ea76992..59f181b32 100644 --- a/tests/fixtures/commander-calc/package.json +++ b/tests/fixtures/commander-calc/package.json @@ -4,5 +4,15 @@ "type": "module", "dependencies": { "commander": "15.0.0" + }, + "scriptc": { + "typePatches": { + "commander@^15.0.0": { + "note": "off a TTY, _getOutputContext() returns helpWidth undefined although the @typedef says number", + "declarations": { + "HelpContext.helpWidth": "| undefined" + } + } + } } } diff --git a/tests/fixtures/npm/cases/type-patch-dts/main.ts b/tests/fixtures/npm/cases/type-patch-dts/main.ts new file mode 100644 index 000000000..1e442054b --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-dts/main.ts @@ -0,0 +1,15 @@ +// A package whose .d.ts says `email: string` for a value that is missing, +// patched to `string | undefined` (package.json scriptc.typePatches): the +// profiles cross the declaration boundary as native records, and user +// code handles the missing email the patched type now makes visible. +import { findProfile, loadProfiles } from "profilekit"; + +const profiles = loadProfiles(); +for (const p of profiles) console.log(p.id, p.name, p.email ?? "(no email)", "email" in p); +console.log(JSON.stringify(profiles)); +const emails = profiles.filter((p) => p.email !== undefined).map((p) => p.email!.toUpperCase()); +console.log(emails.join(",")); +const found = findProfile(3); +console.log(found === undefined ? "none" : found.name); +const missing = findProfile(9); +console.log(missing === undefined ? "none" : missing.name); diff --git a/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.d.ts b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.d.ts new file mode 100644 index 000000000..445d78ea2 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.d.ts @@ -0,0 +1,10 @@ +// The declarations say every profile has an email and every lookup finds +// one; the JavaScript leaves email out for guests and returns undefined +// for unknown ids. +export type Profile = { + id: number; + name: string; + email: string; +}; +export declare function loadProfiles(): Profile[]; +export declare function findProfile(id: number): Profile; diff --git a/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.js b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.js new file mode 100644 index 000000000..a20a082f6 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/index.js @@ -0,0 +1,14 @@ +"use strict"; +function row(id, name, email) { + const profile = { id: id, name: name }; + if (email !== null) profile.email = email; + return profile; +} +function loadProfiles() { + return [row(1, "ada", "ada@example.com"), row(2, "guest", null), row(3, "lin", "lin@example.com")]; +} +function findProfile(id) { + return loadProfiles().find((p) => p.id === id); +} +exports.loadProfiles = loadProfiles; +exports.findProfile = findProfile; diff --git a/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/package.json b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/package.json new file mode 100644 index 000000000..9982f9eef --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-dts/node_modules/profilekit/package.json @@ -0,0 +1 @@ +{ "name": "profilekit", "version": "3.1.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/fixtures/npm/cases/type-patch-dts/package.json b/tests/fixtures/npm/cases/type-patch-dts/package.json new file mode 100644 index 000000000..822491420 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-dts/package.json @@ -0,0 +1,15 @@ +{ + "name": "type-patch-dts-case", + "private": true, + "type": "module", + "scriptc": { + "typePatches": { + "profilekit@^3.0.0": { + "declarations": { + "Profile.email": "| undefined", + "findProfile()": "| undefined" + } + } + } + } +} diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/main.ts b/tests/fixtures/npm/cases/type-patch-types-twin/main.ts new file mode 100644 index 000000000..702b51972 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/main.ts @@ -0,0 +1,11 @@ +// A plain-JavaScript package typed by an @types twin that says `max: +// number` and `mean: number`; the project's type patches (keyed by the +// runtime package and its installed version) make them `number | +// undefined` and `number | null`. +import { totals } from "tallyjs"; + +for (const values of [[3, 9, 4], []]) { + const t = totals(values); + console.log(t.count, t.sum, t.max ?? "no max", t.mean === null ? "no mean" : t.mean.toFixed(2)); + console.log(JSON.stringify(t), Object.keys(t).join("|")); +} diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/index.d.ts b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/index.d.ts new file mode 100644 index 000000000..4a7b81dcc --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/index.d.ts @@ -0,0 +1,7 @@ +export type Totals = { + count: number; + sum: number; + max: number; + mean: number; +}; +export declare function totals(values: number[]): Totals; diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/package.json b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/package.json new file mode 100644 index 000000000..fed3cc37a --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/@types/tallyjs/package.json @@ -0,0 +1 @@ +{ "name": "@types/tallyjs", "version": "0.4.0", "types": "index.d.ts" } diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/index.js b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/index.js new file mode 100644 index 000000000..88d5ffc8f --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/index.js @@ -0,0 +1,11 @@ +"use strict"; +// Plain JavaScript; @types/tallyjs describes it, except that an empty +// list has no max (undefined) and no mean (null). +function totals(values) { + var sum = 0; + for (var i = 0; i < values.length; i++) sum += values[i]; + var max = values.length === 0 ? undefined : Math.max.apply(null, values); + var mean = values.length === 0 ? null : sum / values.length; + return { count: values.length, sum: sum, max: max, mean: mean }; +} +exports.totals = totals; diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/package.json b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/package.json new file mode 100644 index 000000000..ad80ad430 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/node_modules/tallyjs/package.json @@ -0,0 +1 @@ +{ "name": "tallyjs", "version": "0.4.2", "main": "index.js" } diff --git a/tests/fixtures/npm/cases/type-patch-types-twin/package.json b/tests/fixtures/npm/cases/type-patch-types-twin/package.json new file mode 100644 index 000000000..53ec04ede --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-twin/package.json @@ -0,0 +1,15 @@ +{ + "name": "type-patch-types-twin-case", + "private": true, + "type": "module", + "scriptc": { + "typePatches": { + "tallyjs@~0.4.0": { + "declarations": { + "Totals.max": "| undefined", + "Totals.mean": "number | null" + } + } + } + } +} diff --git a/tests/fixtures/type-patches/jsdoc.ts b/tests/fixtures/type-patches/jsdoc.ts new file mode 100644 index 000000000..52cd13638 --- /dev/null +++ b/tests/fixtures/type-patches/jsdoc.ts @@ -0,0 +1,11 @@ +// A JSDoc @typedef property and a @returns that say number for a value +// that is undefined off a TTY, fixed by the project's type patches. +import { Terminal } from "termctx"; + +for (const tty of [false, true]) { + const term = new Terminal(tty); + const context = term.outputContext(false); + console.log(context.error, context.width, term.columns()); + console.log(term.describe(context)); + console.log(term.describe(term.outputContext(true))); +} diff --git a/tests/fixtures/type-patches/ms.ts b/tests/fixtures/type-patches/ms.ts new file mode 100644 index 000000000..a70e221d6 --- /dev/null +++ b/tests/fixtures/type-patches/ms.ts @@ -0,0 +1,9 @@ +// ms's JSDoc says parse() returns a Number and ms() a String or Number; +// for text it cannot parse both return undefined. +import ms from "ms"; + +for (const text of ["2 hours", "not a duration", "1".repeat(101), "-1.5d"]) { + const value = ms(text); + console.log(value === undefined ? "invalid" : value); +} +console.log(ms(90_000), ms(90_000, { long: true })); diff --git a/tests/fixtures/type-patches/node_modules/ms/index.js b/tests/fixtures/type-patches/node_modules/ms/index.js new file mode 100644 index 000000000..ea734fb73 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/ms/index.js @@ -0,0 +1,162 @@ +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var w = d * 7; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function (val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isFinite(val)) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'weeks': + case 'week': + case 'w': + return n * w; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return Math.round(ms / d) + 'd'; + } + if (msAbs >= h) { + return Math.round(ms / h) + 'h'; + } + if (msAbs >= m) { + return Math.round(ms / m) + 'm'; + } + if (msAbs >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return plural(ms, msAbs, d, 'day'); + } + if (msAbs >= h) { + return plural(ms, msAbs, h, 'hour'); + } + if (msAbs >= m) { + return plural(ms, msAbs, m, 'minute'); + } + if (msAbs >= s) { + return plural(ms, msAbs, s, 'second'); + } + return ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, msAbs, n, name) { + var isPlural = msAbs >= n * 1.5; + return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : ''); +} diff --git a/tests/fixtures/type-patches/node_modules/ms/license.md b/tests/fixtures/type-patches/node_modules/ms/license.md new file mode 100644 index 000000000..fa5d39b62 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/ms/license.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Vercel, Inc. + +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/type-patches/node_modules/ms/package.json b/tests/fixtures/type-patches/node_modules/ms/package.json new file mode 100644 index 000000000..b49dca98d --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/ms/package.json @@ -0,0 +1,38 @@ +{ + "name": "ms", + "version": "2.1.3", + "description": "Tiny millisecond conversion utility", + "repository": "vercel/ms", + "main": "./index", + "files": [ + "index.js" + ], + "scripts": { + "precommit": "lint-staged", + "lint": "eslint lib/* bin/*", + "test": "mocha tests.js" + }, + "eslintConfig": { + "extends": "eslint:recommended", + "env": { + "node": true, + "es6": true + } + }, + "lint-staged": { + "*.js": [ + "npm run lint", + "the formatter --single-quote --write", + "git add" + ] + }, + "license": "MIT", + "devDependencies": { + "eslint": "4.18.2", + "expect.js": "0.3.1", + "husky": "0.14.3", + "lint-staged": "5.0.0", + "mocha": "4.0.1", + "the formatter": "2.0.5" + } +} diff --git a/tests/fixtures/type-patches/node_modules/termctx/index.js b/tests/fixtures/type-patches/node_modules/termctx/index.js new file mode 100644 index 000000000..c8b2b9d2d --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/termctx/index.js @@ -0,0 +1,43 @@ +"use strict"; +// A terminal helper whose JSDoc says widths are numbers; off a TTY they +// are undefined (the shape of commander's HelpContext.helpWidth). + +class Terminal { + constructor(tty) { + this.tty = tty; + this.settings = { + columns: () => (this.tty ? 80 : undefined), + }; + } + + /** + * @returns {number} + */ + columns() { + return this.settings.columns(); + } + + /** + * @typedef OutputContext + * @type {object} + * @property {boolean} error + * @property {number} width + * + * @param {boolean} error + * @returns {OutputContext} + */ + outputContext(error) { + const width = this.settings.columns(); + return { error, width }; + } + + /** + * @param {OutputContext} context + * @returns {string} + */ + describe(context) { + return `width ${context.width}${context.error ? " (stderr)" : ""}`; + } +} + +module.exports = { Terminal }; diff --git a/tests/fixtures/type-patches/node_modules/termctx/package.json b/tests/fixtures/type-patches/node_modules/termctx/package.json new file mode 100644 index 000000000..1791f57af --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/termctx/package.json @@ -0,0 +1 @@ +{ "name": "termctx", "version": "1.2.0", "main": "index.js" } diff --git a/tests/fixtures/type-patches/package.json b/tests/fixtures/type-patches/package.json new file mode 100644 index 000000000..f1b9f630e --- /dev/null +++ b/tests/fixtures/type-patches/package.json @@ -0,0 +1,26 @@ +{ + "name": "type-patches-fixture", + "private": true, + "type": "module", + "dependencies": { + "ms": "2.1.3", + "termctx": "1.2.0" + }, + "scriptc": { + "typePatches": { + "termctx@^1.0.0": { + "declarations": { + "OutputContext.width": "| undefined", + "Terminal.columns()": "| undefined" + } + }, + "ms@^2.1.0": { + "note": "parse() returns undefined for text it cannot parse", + "declarations": { + "index.js:parse()": "| undefined", + "export=()": "| undefined" + } + } + } + } +} diff --git a/tests/fixtures/type-patches/stale/main.ts b/tests/fixtures/type-patches/stale/main.ts new file mode 100644 index 000000000..05ae25057 --- /dev/null +++ b/tests/fixtures/type-patches/stale/main.ts @@ -0,0 +1,4 @@ +import ms from "ms"; +import { Terminal } from "termctx"; + +console.log(ms(1000), new Terminal(true).columns()); diff --git a/tests/fixtures/type-patches/stale/package.json b/tests/fixtures/type-patches/stale/package.json new file mode 100644 index 000000000..53669c8b5 --- /dev/null +++ b/tests/fixtures/type-patches/stale/package.json @@ -0,0 +1,17 @@ +{ + "name": "type-patches-stale", + "private": true, + "type": "module", + "scriptc": { + "typePatches": { + "termctx@^2.0.0": { "declarations": { "OutputContext.width": "| undefined" } }, + "ms@^2.1.0": { + "declarations": { + "index.js:format()": "| undefined", + "index.js:fmtShort(ms)": "| Number", + "index.js:plural()": "| undefined" + } + } + } + } +} diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts new file mode 100644 index 000000000..ecd8ee923 --- /dev/null +++ b/tests/harness/type-patches.test.ts @@ -0,0 +1,143 @@ +/* Type patches (package.json scriptc.typePatches): project-owned fixes for + * npm declarations that do not describe what the package does. A JSDoc + * @typedef property and @returns tag that say number for an undefined + * value (the shape of commander's HelpContext.helpWidth), and ms's @return + * tags, compile natively and byte-match Node once patched; without the + * patches the same declarations throw where Node runs. Patches that do not + * apply as written warn (SC8030-SC8033). The `.d.ts` and `@types` cases + * run in npm.test.ts (type-patch-dts, type-patch-types-twin), and + * commander-calc carries the HelpContext.helpWidth patch. */ +import { execFile } from "node:child_process"; +import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { describe, expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); +const repoRoot = join(import.meta.dirname, "../.."); +const fixture = join(repoRoot, "tests/fixtures/type-patches"); +const outDir = join(repoRoot, "node_modules/.cache/scriptc-tests/type-patches"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +interface Run { + stdout: string; + stderr: string; + exitCode: number; +} + +async function run(cmd: string, args: string[]): Promise { + try { + const pending = execFileAsync(cmd, args, { encoding: "utf8" }); + pending.child.stdin?.end(); + const { stdout, stderr } = await pending; + return { stdout, stderr, exitCode: 0 }; + } catch (err) { + const e = err as { code?: unknown; stdout?: string; stderr?: string }; + if (typeof e.code !== "number") throw err; + return { stdout: e.stdout ?? "", stderr: e.stderr ?? "", exitCode: e.code }; + } +} + +async function build(entry: string, name: string, npmStatic: string[]) { + mkdirSync(outDir, { recursive: true }); + const result = await compile(entry, { outPath: join(outDir, name), outDir, sanitize, npmStatic }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return result; +} + +/** A copy of a fixture directory whose package.json has no scriptc key. */ +function unpatched(dir: string): string { + const copy = mkdtempSync(join(tmpdir(), "scriptc-unpatched-")); + cpSync(dir, copy, { recursive: true }); + const manifest = join(copy, "package.json"); + const parsed = JSON.parse(readFileSync(manifest, "utf8")) as Record; + delete parsed["scriptc"]; + writeFileSync(manifest, JSON.stringify(parsed, null, 2)); + return copy; +} + +describe("type patches", () => { + test.for([ + ["jsdoc.ts", ["termctx"]], + ["ms.ts", ["ms"]], + ] as const)( + "%s compiles with its patches and byte-matches Node", + { timeout: 600_000 }, + async ([file, pkgs]) => { + const entry = join(fixture, file); + const result = await build(entry, file.replace(".ts", ""), [...pkgs]); + expect(result.warnings ?? []).toEqual([]); + const [node, native] = await Promise.all([ + run(process.execPath, [entry]), + run(result.binaryPath, []), + ]); + expect(node.exitCode).toBe(0); + expect(native).toEqual(node); + }, + ); + + test( + "without the patches the lying JSDoc throws where Node runs", + { timeout: 600_000 }, + async () => { + const copy = unpatched(fixture); + try { + const result = await build(join(copy, "jsdoc.ts"), "jsdoc-unpatched", ["termctx"]); + const native = await run(result.binaryPath, []); + expect(native.exitCode).not.toBe(0); + expect(native.stderr).toContain("TypeError"); + } finally { + rmSync(copy, { recursive: true, force: true }); + } + }, + ); + + test( + "without the patch the lying .d.ts throws at the declaration boundary", + { timeout: 600_000 }, + async () => { + const copy = unpatched(join(repoRoot, "tests/fixtures/npm/cases/type-patch-dts")); + try { + mkdirSync(outDir, { recursive: true }); + const result = await compile(join(copy, "main.ts"), { + outPath: join(outDir, "dts-unpatched"), + outDir, + sanitize, + }); + // Unpatched, `p.email ?? ...` and `found === undefined` test values + // the declarations say cannot be missing; the build may refuse them + // or the boundary cast throws when the guest profile crosses. + if (result.ok) { + const native = await run(result.binaryPath, []); + expect(native.exitCode).not.toBe(0); + expect(native.stderr).toContain("TypeError: expected string at $, got undefined"); + } + } finally { + rmSync(copy, { recursive: true, force: true }); + } + }, + ); + + test( + "patches that do not apply as written warn and the build goes on", + { timeout: 600_000 }, + async () => { + const entry = join(fixture, "stale/main.ts"); + const result = await build(entry, "stale", ["ms", "termctx"]); + expect((result.warnings ?? []).map((w) => w.code)).toEqual([ + "SC8030", + "SC8031", + "SC8032", + "SC8033", + ]); + const [node, native] = await Promise.all([ + run(process.execPath, [entry]), + run(result.binaryPath, []), + ]); + expect(native).toEqual(node); + }, + ); +}); From b0c01dc34174fb0f4257f686b9466ba1dcdbbcba Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 12:48:07 -0500 Subject: [PATCH 084/230] Adapt the nursery trigger to where cyclic garbage dies A program's nursery trigger starts at 256 candidates and doubles, up to 4096, after each scheduled mature or full pass whose dead set held more mature and old objects than an eighth of what the nursery passes freed since the previous one; it halves after one that held less than a thirty-second of that. SCR_CYCLE_THRESHOLD and scr_cyc_set_nursery_threshold pin the trigger as before. The scheduled-age rule keeps counting collection points in the pinned or default trigger. --- packages/runtime/src/scr_cycle.c | 73 +++++++++++++++++++++++++++--- packages/runtime/src/scr_rc.h | 5 +- packages/runtime/test/test_cycle.c | 58 ++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 9 deletions(-) diff --git a/packages/runtime/src/scr_cycle.c b/packages/runtime/src/scr_cycle.c index d08f54a21..0d9df7106 100644 --- a/packages/runtime/src/scr_cycle.c +++ b/packages/runtime/src/scr_cycle.c @@ -603,7 +603,8 @@ static SCR_TL unsigned scr_cyc_backlog_shift[SCR_CYC_NGENS]; static SCR_TL bool scr_cyc_backlog_judged[SCR_CYC_NGENS]; #endif -/* Mature objects in the last full pass's dead set (scr_cyc_adapt_growth). */ +/* Mature objects in the last pass's dead set (scr_cyc_adapt_growth, + * scr_cyc_adapt_nursery). */ #ifndef SCR_WORKERS static SCR_TL size_t scr_cyc_mature_dead = 0; #endif @@ -639,23 +640,74 @@ static SCR_TL size_t scr_cyc_death_due_live = 0; /* The nursery trigger a program sets for itself * (scr_cyc_set_nursery_threshold), 0 for the default; SCR_CYCLE_THRESHOLD - * takes precedence over it. */ + * takes precedence over it. Either pins the trigger. */ static long scr_cyc_nursery_threshold_set = 0; static SCR_TL size_t scr_cyc_nursery_threshold_cached = 0; +#define SCR_CYC_NURSERY_ADAPTIVE SIZE_MAX -static size_t scr_cyc_nursery_threshold(void) { +/* An unpinned trigger follows where cyclic garbage dies. Garbage that + * outlives the nursery window survives its nursery pass and is promoted, + * and then only a mature or full pass finds it, after walking that level's + * live candidates. So after each scheduled mature or full pass whose dead + * set held more mature and old objects than 1/SCR_CYC_NURSERY_RATIO of what + * the nursery passes since the previous one freed, the window doubles, up + * to SCR_CYC_NURSERY_MAX_SHIFT doublings (4096 candidates); after one that + * held less than a quarter of that share, it halves, down to + * SCR_CYC_NURSERY_CANDIDATES. + * `nursery_freed`: what the nursery passes freed since the last scheduled + * mature or full pass. */ +#define SCR_CYC_NURSERY_RATIO 8 +#define SCR_CYC_NURSERY_MAX_SHIFT 4 +static SCR_TL unsigned char scr_cyc_nursery_shift = 0; +static SCR_TL size_t scr_cyc_nursery_freed = 0; + +static size_t scr_cyc_nursery_pinned(void) { if (scr_cyc_nursery_threshold_cached == 0) { const char *env = scr_getenv("SCR_CYCLE_THRESHOLD"); long v = env ? strtol(env, NULL, 10) : 0; if (v <= 0) v = scr_cyc_nursery_threshold_set; - scr_cyc_nursery_threshold_cached = v > 0 ? (size_t)v : SCR_CYC_NURSERY_CANDIDATES; + scr_cyc_nursery_threshold_cached = v > 0 ? (size_t)v : SCR_CYC_NURSERY_ADAPTIVE; } return scr_cyc_nursery_threshold_cached; } +/* The pinned trigger, or the default one. The scheduled-age rule counts + * collection points in it, so the adaptation does not lengthen how long an + * older backlog waits at them. */ +static size_t scr_cyc_nursery_base(void) { + size_t pinned = scr_cyc_nursery_pinned(); + return pinned != SCR_CYC_NURSERY_ADAPTIVE ? pinned : SCR_CYC_NURSERY_CANDIDATES; +} + +static size_t scr_cyc_nursery_threshold(void) { + size_t pinned = scr_cyc_nursery_pinned(); + if (pinned != SCR_CYC_NURSERY_ADAPTIVE) return pinned; + return (size_t)SCR_CYC_NURSERY_CANDIDATES << scr_cyc_nursery_shift; +} + +/* Adapts an unpinned trigger to the mature and old objects a scheduled + * mature or full pass reclaimed; returns whether the trigger changed. */ +static SCR_CYC_SCHEDULING bool scr_cyc_adapt_nursery(size_t older_freed) { + size_t nursery_freed = scr_cyc_nursery_freed; + scr_cyc_nursery_freed = 0; + if (scr_cyc_nursery_pinned() != SCR_CYC_NURSERY_ADAPTIVE) return false; + if (older_freed * SCR_CYC_NURSERY_RATIO > nursery_freed) { + if (scr_cyc_nursery_shift == SCR_CYC_NURSERY_MAX_SHIFT) return false; + scr_cyc_nursery_shift++; + return true; + } + if (older_freed * SCR_CYC_NURSERY_RATIO * 4 < nursery_freed && scr_cyc_nursery_shift > 0) { + scr_cyc_nursery_shift--; + return true; + } + return false; +} + void scr_cyc_set_nursery_threshold(long threshold) { scr_cyc_nursery_threshold_set = threshold; scr_cyc_nursery_threshold_cached = 0; + scr_cyc_nursery_shift = 0; + scr_cyc_nursery_freed = 0; } /* The number of buffered candidates (across every generation) at which the @@ -881,7 +933,14 @@ static SCR_CYC_SCHEDULING void scr_cyc_scheduled_pass(unsigned gen, bool by_grow size_t freed = scr_cyc_pass(gen); scr_cyc_keep_base = false; size_t old_reclaimed = scr_cyc_old_freed - old_freed_before + scr_cyc_mature_dead; - if (gen == SCR_CYC_NURSERY) return; + if (gen == SCR_CYC_NURSERY) { + scr_cyc_nursery_freed += freed; + return; + } + if (scr_cyc_adapt_nursery(old_reclaimed)) { + scr_cyc_trigger = scr_cyc_buffered() + scr_cyc_nursery_threshold(); + scr_cyc_window_update(); + } if (scr_cands.n != 0) { scr_cyc_backlog_judged[gen] = true; if (freed * SCR_CYC_GROWTH_DIV >= scr_cands.n) @@ -940,7 +999,7 @@ void scr_cyc_collect_scheduled(void) { if (!scr_cyc_older_backlog()) { scr_cyc_scheduled_mature_age = 0; } else if (++scr_cyc_scheduled_mature_age - >= scr_cyc_nursery_threshold()) { + >= scr_cyc_nursery_base()) { scr_cyc_scheduled_pass(SCR_CYC_OLD, false, false); return; } @@ -1981,7 +2040,7 @@ static SCR_CYC_COMPACT size_t scr_cyc_settle(unsigned gen_limit, bool doomed) { ScrCycHdr *h = scr_cyc_hdr(obj); const ScrCycType *type = scr_cyc_type(h); if (gen_limit < SCR_CYC_OLD) scr_cyc_freed(type, gen_limit); - else scr_cyc_mature_dead += scr_cyc_gen(h) == SCR_CYC_MATURE; + scr_cyc_mature_dead += scr_cyc_gen(h) == SCR_CYC_MATURE; #ifdef SCR_YIN if (scr_cyc_gen(h) == SCR_CYC_YOUNG) scr_yin_leave(h, obj); #endif diff --git a/packages/runtime/src/scr_rc.h b/packages/runtime/src/scr_rc.h index de9f21f44..171e420c2 100644 --- a/packages/runtime/src/scr_rc.h +++ b/packages/runtime/src/scr_rc.h @@ -703,8 +703,9 @@ void scr_cyc_set_growth_cap(long cap); /* Sets the nursery trigger SCR_CYCLE_THRESHOLD sets (scr_cycle.c): the * number of new candidates at which the release path runs a pass, for a * program that knows its own collection profile. The environment variable - * takes precedence; 0 restores the default. The trigger re-arms with it at - * the end of the next pass. Call it before the program allocates much. */ + * takes precedence; 0 restores the default, which starts at 256 and adapts + * to the program. The trigger re-arms with it at the end of the next pass. + * Call it before the program allocates much. */ void scr_cyc_set_nursery_threshold(long threshold); void scr_cyc_context_cleanup(void); diff --git a/packages/runtime/test/test_cycle.c b/packages/runtime/test/test_cycle.c index 0ab827e47..dc48abd26 100644 --- a/packages/runtime/test/test_cycle.c +++ b/packages/runtime/test/test_cycle.c @@ -575,6 +575,60 @@ static void check_program_nursery_threshold(void) { scr_collect_cycles(); } +/* How many dead rings the release path takes to run its next pass, from + * the end of the previous one: the nursery trigger in effect. */ +static size_t nursery_window(void) { + size_t before = freed, n = 0; + while (freed == before && n < 65536) { + release_live(make_ring(1)); + n++; + } + check(freed - before == n, "a nursery pass left dead rings behind"); + return n; +} + +/* A scheduled full pass that reclaims nothing: its only candidate is an + * older live leaf. */ +static void scheduled_pass_finding_nothing(Node *leaf) { + size_t before = freed; + read_live(leaf); + for (size_t i = 0; i < configured_nursery_threshold(); i++) + scr_cyc_collect_scheduled(); + check(freed == before, "a scheduled pass over a live leaf freed something"); +} + +/* Unpinned, the nursery trigger starts at 256 candidates. Each scheduled + * full pass that reclaims promoted rings doubles it, up to 4096; each one + * that reclaims nothing after the nursery passes freed rings halves it, + * down to 256. */ +static void check_adaptive_nursery_threshold(void) { + if (getenv("SCR_CYCLE_THRESHOLD") != NULL) return; /* it pins the trigger */ + scr_cyc_set_nursery_threshold(0); + scr_collect_cycles(); + check(nursery_window() == 256, "the unpinned nursery trigger did not start at 256"); + static const size_t widened[] = { 512, 1024, 2048, 4096, 4096 }; + for (size_t i = 0; i < sizeof(widened) / sizeof(widened[0]); i++) { + collect_dead_mature_rings(); + check(nursery_window() == widened[i], + "a full pass reclaiming promoted rings did not double the nursery trigger"); + } + + static const size_t narrowed[] = { 2048, 1024, 512, 256, 256 }; + Node *leaf = make_leaf(); + read_live(leaf); + scr_collect_cycles(); /* promote */ + for (size_t i = 0; i < sizeof(narrowed) / sizeof(narrowed[0]); i++) { + scheduled_pass_finding_nothing(leaf); + check(nursery_window() == narrowed[i], + "a full pass reclaiming nothing did not halve the nursery trigger"); + } + leaf->rc--; + scr_cyc_on_dead(leaf); + node_free(leaf); + scr_cyc_set_nursery_threshold(0); + scr_collect_cycles(); +} + /* The phase sweep (scr_collect_cycles_phase) is for a caller that knows a * phase's data has just died: it sweeps, and the growth schedule starts * over. After passes that find nothing have widened the old level's @@ -1506,6 +1560,9 @@ static void check_acyclic_objects(void) { } int main(void) { + /* The scheduling checks count candidates against a fixed nursery trigger; + * check_adaptive_nursery_threshold covers the unpinned one. */ + scr_cyc_set_nursery_threshold((long)configured_nursery_threshold()); check_inline_buffering(); check_old_garbage_reclaimed_while_growing(); check_older_rings_into_old(); @@ -1541,6 +1598,7 @@ int main(void) { check_acyclic_objects(); check_program_growth_cap(); check_program_nursery_threshold(); + check_adaptive_nursery_threshold(); printf("cycle collection checks passed: threshold=%zu growth_cap=%zu\n", configured_nursery_threshold(), configured_growth_cap()); return 0; From e24ed129334c694ca15d0c04b36291fef7fa9b22 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 12:48:21 -0500 Subject: [PATCH 085/230] Decode string storage within its length in Buffer encodings String storage can hold bytes that are not well-formed UTF-8: an HTTP header value keeps the raw bytes a client sent. Encoding such a string read past its end. - scr_bytes_next_cp decoded any high byte as the lead of a sequence and read up to three bytes after it without a bound. It now takes the storage length, decodes only complete, well-formed sequences, and returns any other byte as U+0000..U+00FF, advancing by one. That is each such byte's value in Node, which decodes header values as latin1. This covers latin1, ascii, binary and utf16le encoding, hex and base64 of non-ASCII text (which narrow through latin1), Buffer#write and TextEncoder#encodeInto. - base64 decoding read one byte past its input when the eight-character groups ended exactly at the end. The runtime's bytes unit test (built with ASan) encodes ill-formed storage at every offset within a word and random high bytes, and compares latin1, ascii and utf16le with a scalar reference. A corpus program sends such header bytes to a server and encodes them as Node does. --- .../corpus/8010-http-header-raw-bytes.ts.json | 6 + packages/runtime/src/scr_bytes.c | 54 ++++---- packages/runtime/test/test_bytes.c | 130 ++++++++++++++++++ tests/corpus/8010-http-header-raw-bytes.ts | 72 ++++++++++ .../8010-http-header-raw-bytes.ts.json | 1 + 5 files changed, 235 insertions(+), 28 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8010-http-header-raw-bytes.ts.json create mode 100644 tests/corpus/8010-http-header-raw-bytes.ts create mode 100644 tests/harness/corpus-costs/8010-http-header-raw-bytes.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8010-http-header-raw-bytes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8010-http-header-raw-bytes.ts.json new file mode 100644 index 000000000..78240d310 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8010-http-header-raw-bytes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8010-http-header-raw-bytes.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index df64b2ab2..5fb8a58d4 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -2240,29 +2240,26 @@ ScrStr *scr_text_decode_stream(ScrDyn *state, const ScrBytes *input, return result; } -/* Decode the next code point from an ScrStr's storage — ALWAYS valid - * UTF-8 (the string runtime never stores ill-formed sequences), so no - * validation re-runs here. */ -static uint32_t scr_bytes_next_cp(const uint8_t *in, size_t *ip) { - uint8_t b = in[*ip]; - if (b < 0x80) { - *ip += 1; - return b; - } - if ((b & 0xe0) == 0xc0) { - uint32_t cp = ((uint32_t)(b & 0x1f) << 6) | (in[*ip + 1] & 0x3f); - *ip += 2; - return cp; - } - if ((b & 0xf0) == 0xe0) { - uint32_t cp = ((uint32_t)(b & 0xf) << 12) | ((uint32_t)(in[*ip + 1] & 0x3f) << 6) | - (in[*ip + 2] & 0x3f); - *ip += 3; - return cp; - } - uint32_t cp = ((uint32_t)(b & 0x7) << 18) | ((uint32_t)(in[*ip + 1] & 0x3f) << 12) | - ((uint32_t)(in[*ip + 2] & 0x3f) << 6) | (in[*ip + 3] & 0x3f); - *ip += 4; +/* Decode the next code point from the n bytes of an ScrStr's storage. + * Storage is WTF-8, except where raw outside bytes become a string + * unchecked (HTTP header values). A byte that does not start a complete, + * well-formed sequence stands for U+0000..U+00FF, as each byte of such a + * header does in Node, and advances by one; nothing is read past n. */ +static uint32_t scr_bytes_next_cp(const uint8_t *in, size_t n, size_t *ip) { + size_t i = *ip; + uint8_t b = in[i]; + *ip = i + 1; + if (b < 0x80) return b; + size_t len = b >= 0xc2 && b <= 0xdf ? 2 : b >= 0xe0 && b <= 0xef ? 3 : b >= 0xf0 && b <= 0xf4 ? 4 : 0; + if (len == 0 || n - i < len) return b; + uint8_t lo = b == 0xe0 ? 0xa0 : b == 0xf0 ? 0x90 : 0x80; + uint8_t hi = b == 0xf4 ? 0x8f : 0xbf; + if (in[i + 1] < lo || in[i + 1] > hi) return b; + for (size_t k = 2; k < len; k++) + if ((in[i + k] & 0xc0) != 0x80) return b; + uint32_t cp = b & (0x7f >> len); + for (size_t k = 1; k < len; k++) cp = (cp << 6) | (in[i + k] & 0x3f); + *ip = i + len; return cp; } @@ -2287,7 +2284,7 @@ ScrDyn *scr_text_encode_into(const ScrDyn *source, const ScrDyn *destination) { read += ascii; if (written == limit) break; size_t end = written; - uint32_t cp = scr_bytes_next_cp((const uint8_t *)text->data, &end); + uint32_t cp = scr_bytes_next_cp((const uint8_t *)text->data, text->len, &end); if (end > dest->len) break; read += cp > 0xffff ? 2 : 1; written = end; @@ -2440,7 +2437,7 @@ ScrBytes *scr_buffer_transcode(const ScrDyn *source, const ScrDyn *from, const S } ScrStr *text = scr_str_from_utf8_lossy(input->data, len); for (size_t i = 0; i < text->len;) { - uint32_t cp = scr_bytes_next_cp((const uint8_t *)text->data, &i); + uint32_t cp = scr_bytes_next_cp((const uint8_t *)text->data, text->len, &i); written = scr_transcode_put(result->data, written, cp, to_encoding); } scr_str_release(text); @@ -2774,6 +2771,7 @@ static ScrBytes *scr_bytes_decode_b64(ScrBytesSink *sink, const uint8_t *in, siz o += 6; i += 8; } + if (i == n) break; /* the groups ended the input */ } /* Common complete groups need neither per-character branches nor state. */ if (have == 0 && n - i >= 4) { @@ -2824,7 +2822,7 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const size_t o = s->ascii ? n : scr_bytes_ascii_prefix(in, n), i = o; memcpy(b->data, in, o); while (i < n) { - uint32_t cp = scr_bytes_next_cp(in, &i); + uint32_t cp = scr_bytes_next_cp(in, n, &i); if (cp > 0xffff) { cp -= 0x10000; b->data[o++] = (uint8_t)(0xd800 + (cp >> 10)); @@ -2841,7 +2839,7 @@ static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const ScrBytes *b = scr_bytes_conversion_storage_in(sink, n * 2); size_t o = 0, i = 0; while (i < n) { - uint32_t cp = scr_bytes_next_cp(in, &i); + uint32_t cp = scr_bytes_next_cp(in, n, &i); if (cp > 0xffff) { cp -= 0x10000; uint32_t hi = 0xd800 + (cp >> 10), lo = 0xdc00 + (cp & 0x3ff); @@ -3745,7 +3743,7 @@ double scr_bytes_write_str(ScrBytes *b, const ScrStr *s, const ScrStr *enc, continue; } } - uint32_t cp = scr_bytes_next_cp((const uint8_t *)s->data, &i); + uint32_t cp = scr_bytes_next_cp((const uint8_t *)s->data, s->len, &i); uint32_t first = cp, second = 0; if (cp > 0xffff) { cp -= 0x10000; diff --git a/packages/runtime/test/test_bytes.c b/packages/runtime/test/test_bytes.c index 8c6602948..1aea7a2d0 100644 --- a/packages/runtime/test/test_bytes.c +++ b/packages/runtime/test/test_bytes.c @@ -216,6 +216,135 @@ static void test_encodings(void) { scr_str_release(b64); } +/* The length of the well-formed WTF-8 sequence at in[i], or 0. */ +static size_t ref_seq_len(const uint8_t *in, size_t n, size_t i) { + uint8_t b = in[i]; + if (b < 0x80) return 1; + size_t len; + uint8_t lo = 0x80, hi = 0xbf; + if (b >= 0xc2 && b <= 0xdf) len = 2; + else if (b >= 0xe0 && b <= 0xef) len = 3; + else if (b >= 0xf0 && b <= 0xf4) len = 4; + else return 0; + if (b == 0xe0) lo = 0xa0; + if (b == 0xf0) lo = 0x90; + if (b == 0xf4) hi = 0x8f; + if (n - i < len || in[i + 1] < lo || in[i + 1] > hi) return 0; + for (size_t k = 2; k < len; k++) + if ((in[i + k] & 0xc0) != 0x80) return 0; + return len; +} + +/* The UTF-16 units of string storage, one at a time: a well-formed + * sequence gives its code point's units and any other byte stands for + * U+0000..U+00FF (a raw HTTP header byte). */ +static size_t ref_units(const uint8_t *in, size_t n, uint16_t *out) { + size_t k = 0; + for (size_t i = 0; i < n;) { + size_t len = ref_seq_len(in, n, i); + if (len == 0) { + out[k++] = in[i++]; + continue; + } + uint32_t cp = len == 1 ? in[i] : (uint32_t)(in[i] & (0x7f >> len)); + for (size_t j = 1; j < len; j++) cp = (cp << 6) | (in[i + j] & 0x3f); + i += len; + if (cp > 0xffff) { + out[k++] = (uint16_t)(0xd800 + ((cp - 0x10000) >> 10)); + out[k++] = (uint16_t)(0xdc00 + ((cp - 0x10000) & 0x3ff)); + } else { + out[k++] = (uint16_t)cp; + } + } + return k; +} + +/* Encodes storage that is not well-formed (raw header bytes become string + * storage unchecked) and compares latin1, ascii and utf16le with the + * reference; hex and base64 decode the latin1 bytes. ASan checks that no + * path reads or writes outside either buffer. */ +/* encs: latin1, ascii, utf16le, hex, base64. */ +static void check_raw_encodings(ScrStr *const *encs, const uint8_t *in, size_t n, const char *what) { + ScrStr *latin1 = encs[0], *ascii = encs[1], *utf16le = encs[2], *hex = encs[3], *b64 = encs[4]; + ScrStr *s = scr_str_new((const char *)in, n); + uint16_t units[64]; + size_t k = ref_units(in, n, units); + ScrStr *narrow[] = {latin1, ascii}; + for (size_t e = 0; e < 2; e++) { + ScrBytes *b = scr_bytes_from_str(s, narrow[e]); + bool ok = b->len == k; + for (size_t j = 0; ok && j < k; j++) ok = b->data[j] == (uint8_t)units[j]; + check(ok, what); + scr_bytes_release(b); + } + ScrBytes *wide = scr_bytes_from_str(s, utf16le); + bool ok = wide->len == 2 * k; + for (size_t j = 0; ok && j < k; j++) + ok = wide->data[2 * j] == (uint8_t)units[j] && wide->data[2 * j + 1] == units[j] >> 8; + check(ok, what); + scr_bytes_release(wide); + scr_bytes_release(scr_bytes_from_str(s, hex)); + scr_bytes_release(scr_bytes_from_str(s, b64)); + scr_str_release(s); +} + +static void test_raw_storage_encodings(void) { + /* Each pattern at every offset in an eight-byte word, alone and followed + * by ASCII, two-byte characters or more of the pattern. */ + static const struct { const char *bytes; size_t n; } patterns[] = { + {"\x80", 1}, {"\x80\x81\xbf\x90", 4}, {"\xc3", 1}, {"\xc3\xa9", 2}, + {"\xc3\xa9\xc3", 3}, {"\xc0\x80\xc1\xbf", 4}, {"\xe9\x41\xe9", 3}, + {"\xf0\x9f\x41", 3}, {"\xe2\x82", 2}, {"\xe2\x82\xac", 3}, + {"\xf0\x9f\x98\x80", 4}, {"\xed\xa0\x80", 3}, {"\xff\xfe\xf5", 3}, + {"\xdf\xbf\x80\x80", 4}, {"\xc2\x80\xc2", 3}, {"x\xe9y\x80\x80z\xdf", 7}, + }; + static const struct { const char *bytes; size_t n; } tails[] = { + {"", 0}, {"bcdefghij", 9}, {"\xc3\xa9\xc3\xa9\xc3\xa9\xc3\xa9", 8}, {"\x80\xc3\xa9", 3}, + }; + ScrStr *encs[] = {S("latin1"), S("ascii"), S("utf16le"), S("hex"), S("base64")}; + uint8_t buf[64]; + char what[96]; + for (size_t p = 0; p < sizeof patterns / sizeof patterns[0]; p++) { + for (size_t offset = 0; offset < 17; offset++) { + for (size_t t = 0; t < sizeof tails / sizeof tails[0]; t++) { + for (size_t lead = 0; lead < 2; lead++) { + /* lead: the offset is two-byte characters instead of ASCII. */ + size_t n = 0; + for (size_t j = 0; j < offset; j++) { + if (lead && j + 1 < offset) { + buf[n++] = 0xc3; + buf[n++] = 0xa9; + j++; + } else { + buf[n++] = 'a'; + } + } + memcpy(buf + n, patterns[p].bytes, patterns[p].n); + n += patterns[p].n; + memcpy(buf + n, tails[t].bytes, tails[t].n); + n += tails[t].n; + snprintf(what, sizeof what, "raw storage encodings: pattern %zu offset %zu tail %zu lead %zu", + p, offset, t, lead); + check_raw_encodings(encs, buf, n, what); + } + } + } + } + /* Random bytes, mostly high, of every length up to 40. */ + uint32_t seed = 12345; + for (int round = 0; round < 4000; round++) { + size_t n = (size_t)round % 41; + for (size_t j = 0; j < n; j++) { + seed = seed * 1103515245u + 12345u; + uint8_t r = (uint8_t)(seed >> 16); + buf[j] = (seed >> 27) & 1 ? (uint8_t)(0x80 | r) : (uint8_t)(r & 0x7f); + } + snprintf(what, sizeof what, "raw storage encodings: random round %d", round); + check_raw_encodings(encs, buf, n, what); + } + for (size_t e = 0; e < sizeof encs / sizeof encs[0]; e++) scr_str_release(encs[e]); +} + static void test_concat_and_u32be(void) { ScrArr *list = scr_arr_new(SCR_ELEM_BYTES, 0); ScrBytes *a = scr_bytes_new(SCR_BYTES_U8, 2); @@ -445,6 +574,7 @@ int main(int argc, char **argv) { scr_bytes_test_bulk_storage(); scr_bytes_test_buffer_pool(); test_encodings(); + test_raw_storage_encodings(); test_concat_and_u32be(); test_from_arr_and_random(); test_zlib_roundtrip(); diff --git a/tests/corpus/8010-http-header-raw-bytes.ts b/tests/corpus/8010-http-header-raw-bytes.ts new file mode 100644 index 000000000..d8408f335 --- /dev/null +++ b/tests/corpus/8010-http-header-raw-bytes.ts @@ -0,0 +1,72 @@ +// Header values hold the raw bytes a client sent, one character per byte +// (Node decodes them as latin1). Bytes that are not well-formed UTF-8 (lone +// continuation bytes, runs of them, truncated leads, overlong leads and +// bytes no sequence uses) at every offset within an eight-byte word encode +// to the same bytes as in Node with latin1, ascii, binary, hex, base64 and +// utf16le. Strings built in the program take the same encodings with two-, +// three- and four-byte characters at every offset. +import { createServer } from "node:http"; +import { connect } from "node:net"; + +const patterns: number[][] = [ + [0x80], + [0x80, 0x81, 0xbf, 0x90], + [0xc3], + [0xc0, 0x80, 0xc1, 0xbf], + [0xe9, 0x41, 0xe9], + [0xf0, 0x9f, 0x41], + [0xe2, 0x82], + [0xff, 0xfe, 0xf5], + [0x78, 0xe9, 0x79, 0x80, 0x80, 0x7a, 0xdf], +]; +const encodings = ["latin1", "ascii", "binary", "hex", "base64", "utf16le"] as const; + +function hex(bytes: Buffer): string { + return bytes.toString("hex"); +} + +function describe(label: string, value: string): void { + console.log(label, encodings.map((encoding) => `${encoding}=${hex(Buffer.from(value, encoding))}`).join(" ")); +} + +const names: string[] = []; +const head: Buffer[] = [Buffer.from("GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n")]; +for (let p = 0; p < patterns.length; p++) { + for (let offset = 0; offset < 10; offset++) { + for (const tail of ["", "bcdefghij"]) { + const name = `x-${p}-${offset}-${tail.length}`; + names.push(name); + head.push(Buffer.from(`${name}: ${"a".repeat(offset)}`)); + head.push(Buffer.from(patterns[p]!)); + head.push(Buffer.from(`${tail}\r\n`)); + } + } +} +head.push(Buffer.from("\r\n")); + +const server = createServer((req, res) => { + for (const name of names) { + const value = req.headers[name]; + if (typeof value === "string") describe(name, value); + else console.log(name, "missing"); + } + res.end("ok"); +}); + +server.listen(0, "127.0.0.1", () => { + const address = server.address(); + const port = typeof address === "object" && address !== null ? address.port : 0; + const socket = connect({ port, host: "127.0.0.1" }, () => { + socket.write(Buffer.concat(head)); + }); + socket.on("data", () => {}); + socket.on("close", () => { + server.close(); + for (const text of ["é", "éü", "߀", "😀", "é😀ü", "߿\u0080"]) { + for (let offset = 0; offset < 17; offset++) { + const value = "a".repeat(offset) + text + "b".repeat(offset % 9) + text; + describe(`text ${offset} ${JSON.stringify(text)}`, value); + } + } + }); +}); diff --git a/tests/harness/corpus-costs/8010-http-header-raw-bytes.ts.json b/tests/harness/corpus-costs/8010-http-header-raw-bytes.ts.json new file mode 100644 index 000000000..532e3bd74 --- /dev/null +++ b/tests/harness/corpus-costs/8010-http-header-raw-bytes.ts.json @@ -0,0 +1 @@ +{ "plain": 3468, "san": 7463 } From a88fdacbe2ab53692a6e8ed9c76f6acba0847a7a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:00:18 -0500 Subject: [PATCH 086/230] Iterate strings over their bytes for-of, spread, Array.from, yield* and [Symbol.iterator]() over a string walked a UTF-16 index. Each step mapped the index to a byte offset, built the character, and measured its UTF-16 length to advance. They now walk a byte cursor over the WTF-8 data: - The cpAtByte intrinsic reads the character that starts at the cursor. ASCII bytes take the one-character table inline; other characters call scr_str_cp_at_byte. byteLength advances the cursor by the character's byte count. A lone surrogate is its own three-byte character, so it stays a one-unit character, as in Node. - Spread and Array.from of a string call scr_str_code_points, which counts the characters, allocates the array once and fills it. - The one-character strings of U+0080..U+07FF are made immortal on first use and shared, as the ASCII ones are static. The cpAt intrinsic, which no lowering emits any more, is removed. --- benchmarks/runtime/README.md | 1 + .../runtime/workloads/string-iteration.json | 6 + .../runtime/workloads/string-iteration.ts | 59 +++++++++ .../src/backend/llvm/expr-containers.ts | 36 ++++-- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../src/backend/llvm/string-lifetimes.test.ts | 2 +- .../src/backend/llvm/string-lifetimes.ts | 3 +- .../compiler/src/backend/llvm/string-reads.ts | 27 ++++ .../lowering/containers/array-construction.ts | 115 +----------------- .../lowering/containers/array-pipelines.ts | 3 +- .../lowering/containers/collection-methods.ts | 2 +- .../lowering/expressions/array-literals.ts | 2 +- .../frontend/lowering/generator-delegation.ts | 5 +- .../frontend/lowering/iterator-adapters.ts | 6 +- .../src/frontend/lowering/lower-stmts.ts | 32 ++--- .../compiler/src/ir/builtin-signatures.ts | 1 + packages/compiler/src/ir/integer-ranges.ts | 2 + .../compiler/src/ir/intrinsic-signatures.ts | 3 +- packages/compiler/src/ir/ir.ts | 11 +- .../test/string-iteration-emission.test.ts | 47 +++++++ .../8347-string-iteration-code-points.ts.json | 6 + packages/runtime/src/scr_string.c | 72 ++++++++++- packages/runtime/src/scr_string_methods.h | 9 ++ tests/corpus/1561-forof-strings.ts | 4 +- .../8347-string-iteration-code-points.ts | 96 +++++++++++++++ .../8347-string-iteration-code-points.ts.json | 1 + 26 files changed, 399 insertions(+), 153 deletions(-) create mode 100644 benchmarks/runtime/workloads/string-iteration.json create mode 100644 benchmarks/runtime/workloads/string-iteration.ts create mode 100644 packages/compiler/test/string-iteration-emission.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8347-string-iteration-code-points.ts.json create mode 100644 tests/corpus/8347-string-iteration-code-points.ts create mode 100644 tests/harness/corpus-costs/8347-string-iteration-code-points.ts.json diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index af3d3d239..0157503fd 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -32,6 +32,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `regex-logs` | cpu | regex `exec` with groups, `test`, global `replace` | | `regex-replacers` | cpu | regex `replace`/`replaceAll` with inline callbacks whose parameters have no annotations: dictionary placeholders, snake_case to camelCase, date reordering, optional captures, capitalization, and a string search that reads the offset | | `region-builds` | cpu | a watch-mode build loop over `LifetimeRegion`: each build binds a program in a new region and releases the previous one, which a cache keeps referenced for two builds | +| `string-iteration` | cpu | code point iteration over text that mixes ASCII, Latin-1 letters, CJK and emoji: a for-of word tokenizer, spread reversal, and `Array.from` over words | | `template-render` | cpu | template literals, escaping, `map`/`join` | | `throw-chains` | cpu | exceptions caught 2, 5 and 20 recursive calls above the throw, through direct calls and through a callback, and a backtracking parser that throws on a failed alternative | | `thread-slots`, `thread-slots-cache` | cpu | worker threads reading one published type graph from an `InternTable` and keeping per-thread state for each type they visit: a `ThreadSlots` family, or a direct-mapped cache by id in front of a `Map` by id (the `-cache` entry); under Node.js both are per thread | diff --git a/benchmarks/runtime/workloads/string-iteration.json b/benchmarks/runtime/workloads/string-iteration.json new file mode 100644 index 000000000..21456bd23 --- /dev/null +++ b/benchmarks/runtime/workloads/string-iteration.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/string-iteration.ts", + "args": [ + "1" + ] +} diff --git a/benchmarks/runtime/workloads/string-iteration.ts b/benchmarks/runtime/workloads/string-iteration.ts new file mode 100644 index 000000000..2abebd635 --- /dev/null +++ b/benchmarks/runtime/workloads/string-iteration.ts @@ -0,0 +1,59 @@ +// Code point iteration over mixed text: a word tokenizer over for-of, a +// character histogram, reversal through spread, and Array.from over +// words. The text mixes ASCII, Latin-1 letters, CJK and emoji. +let seed = 11; +function random(): number { + seed = (Math.imul(seed, 1103515245) + 12345) >>> 0; + return seed / 4294967296; +} +const pieces = [ + "the", "quick", "brown", "fox", "jumps", "over", "lazy", "dog", + "café", "naïve", "über", "señor", "straße", "déjà", "vu", + "日本", "語", "テキスト", "中文", "한국어", + "😀", "🚀", "👍", "🎉", +]; +const punctuation = [" ", " ", " ", ", ", ". ", "! ", "\n"]; +const scale = Number(process.argv[2] ?? "1"); +const lines: string[] = []; +for (let i = 0; i < Math.floor(4000 * scale); i++) { + let line = ""; + const words = 6 + Math.floor(random() * 10); + for (let w = 0; w < words; w++) { + line += pieces[Math.floor(random() * pieces.length)]!; + line += punctuation[Math.floor(random() * punctuation.length)]!; + } + lines.push(line); +} + +function isWordChar(c: string): boolean { + const code = c.codePointAt(0)!; + return (code >= 0x61 && code <= 0x7a) || (code >= 0x41 && code <= 0x5a) || code >= 0xc0; +} + +let tokens = 0; +let tokenUnits = 0; +let reversedLength = 0; +let fromLength = 0; +const histogram = new Map(); +for (let round = 0; round < 30; round++) { + for (const line of lines) { + let word = ""; + for (const c of line) { + if (isWordChar(c)) { + word += c; + continue; + } + if (word !== "") { + tokens++; + tokenUnits += word.length; + if (round === 0) histogram.set(word, (histogram.get(word) ?? 0) + 1); + fromLength += Array.from(word).length; + word = ""; + } + } + reversedLength += [...line].reverse().join("").length; + } +} +const top = [...histogram].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)).slice(0, 5); +console.log(top.map(([word, count]) => `${word}:${count}`).join(" ")); +console.log("tokens", tokens, "units", tokenUnits, "reversed", reversedLength, "from", fromLength); diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index 307481737..4360ae924 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -49,11 +49,13 @@ import { emitFusedMapSet, rememberMapEntry } from "./map-entry-fusion.js"; import { emitScratchStringSlice, emitStringSliceRead } from "./string-slices.js"; import { STRING_CHAR_AT_DECL, + STRING_CP_AT_BYTE_DECL, STRING_CHAR_AT_INT_MARKER, STRING_CHAR_CODE_AT_DECL, STRING_CHAR_CODE_AT_INT_MARKER, emitAsciiChar, emitAsciiCharCode, + emitStringByteLength, emitUnitCharCode, STRING_UTF16_LEN_DECL, } from "./string-reads.js"; @@ -564,16 +566,30 @@ export function emitStrIntrinsic( return call("scr_str_is_well_formed", "zeroext i1 (ptr)", `ptr ${r.name}`, "i1", false); case "toWellFormed": return call("scr_str_to_well_formed", "ptr (ptr)", `ptr ${r.name}`, "ptr", true); - case "cpAt": - // The code point AT an index as a one-code-point string (+1) — - // the string-for-of desugar's read. - return call( - "scr_str_cp_at", - "ptr (ptr, double)", - `ptr ${r.name}, double ${args[0]!.name}`, - "ptr", - true, - ); + case "byteLength": { + const t = B.tmp(); + // A byte count fits in a signed size, whose conversion is one instruction. + B.line(`${t} = sitofp ${host.sizeType} ${emitStringByteLength(host, r.name)} to double`); + return { name: t, type: e.type }; + } + case "cpAtByte": { + // sc_str_cp_at_byte (string-reads.ts) answers ASCII bytes from the + // charAt table inline. The cursor is a character start, an exact + // integer below the byte count. + host.declare(STRING_CHAR_AT_DECL); + host.declare(STRING_CP_AT_BYTE_DECL); + const index = e.args[0] ? exactInteger(host, args[0]!, e.args[0]) : null; + let wide: string; + if (index) { + wide = widenInteger(host, index); + } else { + wide = B.tmp(); + B.line(`${wide} = fptosi double ${args[0]!.name} to i64`); + } + const t = B.tmp(); + B.line(`${t} = call ptr @sc_str_cp_at_byte(ptr ${r.name}, i64 ${wide})`); + return host.own({ name: t, type: e.type }); + } default: { const _exhaustive: never = method; void _exhaustive; diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 2548b75d5..7688746b8 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -104,6 +104,7 @@ export const LIB_FN_SYMS: Record = { "str.localeCase": "scr_str_locale_case", "str.replaceLiteral": "scr_str_replace_literal", "str.replaceChars": "scr_str_replace_chars", + "str.codePoints": "scr_str_code_points", "num.fromString": "scr_string_to_number", "math.round": "scr_math_round", "math.sin": "sin", diff --git a/packages/compiler/src/backend/llvm/string-lifetimes.test.ts b/packages/compiler/src/backend/llvm/string-lifetimes.test.ts index 07ad31153..72fc57752 100644 --- a/packages/compiler/src/backend/llvm/string-lifetimes.test.ts +++ b/packages/compiler/src/backend/llvm/string-lifetimes.test.ts @@ -341,7 +341,7 @@ const methodCases: { { method: "normalize", args: [ref("argument")], result: STRING, target: "normalize" }, { method: "isWellFormed", args: [], result: BOOL, target: "is_well_formed" }, { method: "toWellFormed", args: [], result: STRING, target: "to_well_formed" }, - { method: "cpAt", args: [num(1)], result: STRING, target: "cp_at" }, + { method: "cpAtByte", args: [num(1)], result: STRING, target: "cp_at_byte", inline: true }, ]; test.each(methodCases)( diff --git a/packages/compiler/src/backend/llvm/string-lifetimes.ts b/packages/compiler/src/backend/llvm/string-lifetimes.ts index 6c3079cc1..5a4504813 100644 --- a/packages/compiler/src/backend/llvm/string-lifetimes.ts +++ b/packages/compiler/src/backend/llvm/string-lifetimes.ts @@ -30,7 +30,8 @@ export function borrowsStringInputs(method: IrStrIntrinsicMethod): boolean { case "normalize": case "isWellFormed": case "toWellFormed": - case "cpAt": + case "byteLength": + case "cpAtByte": return true; default: return false; diff --git a/packages/compiler/src/backend/llvm/string-reads.ts b/packages/compiler/src/backend/llvm/string-reads.ts index 5b7a26c6c..1f77de2c2 100644 --- a/packages/compiler/src/backend/llvm/string-reads.ts +++ b/packages/compiler/src/backend/llvm/string-reads.ts @@ -43,6 +43,10 @@ export const STRING_CHAR_CODE_AT_INT_MARKER = "; uses @sc_str_char_code_at_int"; export const STRING_CHAR_AT_DECL = "declare ptr @scr_str_char_at(ptr, double)"; /** Requests the integer-index charAt helper, as the marker above does. */ export const STRING_CHAR_AT_INT_MARKER = "; uses @sc_str_char_at_int"; +/** The code-point character starting at a byte offset (the string + * iterator's step). Requested together with STRING_CHAR_AT_DECL, which + * defines the ASCII table. */ +export const STRING_CP_AT_BYTE_DECL = "declare ptr @scr_str_cp_at_byte(ptr, i64)"; /** The proven-ASCII flag of string `s`, as an i1. */ export function emitStringAsciiFlag(host: LlvmEmitterContext, s: string): string { @@ -351,6 +355,29 @@ export const STRING_READ_HELPERS: readonly StringReadHelper[] = [ ]; }, }, + { + // The string iterator's step at byte `%b`, a character start below the + // byte count: an ASCII byte is the table entry for it; any other lead + // byte takes the runtime, which copies the character's bytes. + decl: STRING_CP_AT_BYTE_DECL, + define: (sz, attrs) => [ + `define internal ptr @sc_str_cp_at_byte(ptr %s, i64 %b) ${attrs} {`, + `entry:`, + ` %data = getelementptr inbounds %ScrStr, ptr %s, i32 1`, + ` %p = getelementptr inbounds i8, ptr %data, i64 %b`, + ` %byte = load i8, ptr %p`, + ` %ascii = icmp sge i8 %byte, 0`, + ` br i1 %ascii, label %read, label %slow`, + `read:`, + ` %code = zext i8 %byte to i64`, + ` %char = getelementptr inbounds [128 x { ${sz}, ${sz}, ${sz}, [2 x i8] }], ptr @sc_ascii_chars, i64 0, i64 %code`, + ` ret ptr %char`, + `slow:`, + ` %r = call ptr @scr_str_cp_at_byte(ptr %s, i64 %b) nounwind willreturn`, + ` ret ptr %r`, + `}`, + ], + }, { // An exact integer index, as for sc_str_char_code_at_int. Requested // together with STRING_CHAR_AT_DECL, which defines the table. diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 5029abc36..702837e40 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -1081,9 +1081,7 @@ export function lowerArrayFromCall( if ((args.length === 1 || args.length === 2) && !ts.isObjectLiteralExpression(args[0]!)) { const source = lowerer.lowerExpr(args[0]!); let src = - source.type.kind === "string" && args.length === 2 - ? strCharsCall(lowerer, source, loc) - : source; + source.type.kind === "string" && args.length === 2 ? strCharsCall(source, loc) : source; if (src.type.kind === "array") { const arrT = src.type; if (args.length === 1) { @@ -1171,7 +1169,7 @@ export function lowerArrayFromCall( } // String iteration advances by code point; the same helper serves // `[...s]` and both Array.from(s) forms. - if (src.type.kind === "string" && args.length === 1) return strCharsCall(lowerer, src, loc); + if (src.type.kind === "string" && args.length === 1) return strCharsCall(src, loc); if (src.type.kind === "dyn" && args.length === 1) { return lowerCheckedArrayFrom(lowerer, src, loc); } @@ -1318,112 +1316,9 @@ function buildArrayFromArrayFn( } /** `Array.from(s)` / `[...s]` on a STRING: the code-point split into a - * fresh string[], through one interned helper per module. */ -export function strCharsCall(lowerer: Lowerer, src: IrExpr, loc: SrcLoc): IrExpr { - const key = "strChars"; - let helper = lowerer.arrHofHelpers.get(key); - if (!helper) { - helper = "%str.chars"; - lowerer.arrHofHelpers.set(key, helper); - lowerer.liftedFns.push(buildStrCharsFn(helper, loc)); - } - return { kind: "call", callee: helper, args: [src], type: arrayOf(STRING), loc }; -} - -/** The code-point split, from existing IR nodes — the string for-of - * desugar's UTF-16 cursor as a function: - * - * out = []; - * i = 0; - * while (i < s.length) { ch = cpAt(s, i); i += ch.length; out.push(ch); } - * return out; - */ -function buildStrCharsFn(name: string, loc: SrcLoc): IrFunction { - const outT = arrayOf(STRING); - - const sLen = (recv: IrExpr): IrExpr => ({ - kind: "strIntrinsic", - method: "length", - receiver: recv, - args: [], - type: F64, - loc, - }); - const body: IrStmt[] = [ - { - kind: "varDecl", - localId: "out.0", - init: { kind: "arrayLit", elems: [], type: outT, loc }, - loc, - }, - { kind: "varDecl", localId: "i.0", init: { kind: "numLit", value: 0, type: F64, loc }, loc }, - { - kind: "while", - cond: { - kind: "bin", - op: "<", - left: varRef("i.0", F64, loc), - right: sLen(varRef("s.0", STRING, loc)), - type: BOOL, - loc, - }, - body: [ - { - kind: "varDecl", - localId: "ch.0", - init: { - kind: "strIntrinsic", - method: "cpAt", - receiver: varRef("s.0", STRING, loc), - args: [varRef("i.0", F64, loc)], - type: STRING, - loc, - }, - loc, - }, - { - kind: "assign", - localId: "i.0", - value: { - kind: "bin", - op: "+", - left: varRef("i.0", F64, loc), - right: sLen(varRef("ch.0", STRING, loc)), - type: F64, - loc, - }, - loc, - }, - { - kind: "exprStmt", - expr: { - kind: "arrIntrinsic", - method: "push", - receiver: varRef("out.0", outT, loc), - args: [varRef("ch.0", STRING, loc)], - type: F64, - loc, - }, - loc, - }, - ], - loc, - }, - { kind: "return", value: varRef("out.0", outT, loc), loc }, - ]; - return { - name, - params: [{ localId: "s.0", name: "s", type: STRING }], - returnType: outT, - locals: [ - { id: "s.0", name: "s", type: STRING, mutable: true }, - { id: "out.0", name: "out", type: outT, mutable: false }, - { id: "i.0", name: "i", type: F64, mutable: true }, - { id: "ch.0", name: "ch", type: STRING, mutable: false }, - ], - body, - loc, - }; + * fresh string[] (scr_str_code_points). */ +export function strCharsCall(src: IrExpr, loc: SrcLoc): IrExpr { + return { kind: "libCall", fn: "str.codePoints", args: [src], type: arrayOf(STRING), loc }; } /** The lowered `length` value of the one-property source literal, or null diff --git a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts index 197af2a9e..9cc96d0a5 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-pipelines.ts @@ -156,7 +156,8 @@ const PURE_STRING = new Set([ "toUpperCase", "isWellFormed", "toWellFormed", - "cpAt", + "byteLength", + "cpAtByte", ]); function primitive(t: IrType): boolean { diff --git a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts index f18291a63..bfaca6f96 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts @@ -1135,7 +1135,7 @@ function setFromSeedValue( if (source.type.kind === "set" && typeEquals(source.type, setT)) { return { kind: "setIntrinsic", method: "clone", receiver: source, args: [], type: setT, loc }; } else if (source.type.kind === "string" && setT.elem.kind === "string") { - seed = strCharsCall(lowerer, source, loc); + seed = strCharsCall(source, loc); } else if (source.type.kind === "array" && typeEquals(source.type.elem, setT.elem)) { seed = source; } else if ( diff --git a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts index 903f0884d..77c16594f 100644 --- a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts @@ -504,7 +504,7 @@ export function lowerArrayLiteral( // interned helper as Array.from(s); the result rides the array // machinery below like any string[] source. if (src.type.kind === "string") { - src = strCharsCall(lowerer, src, locOf(el)); + src = strCharsCall(src, locOf(el)); } // `[...typedArray]`: represented typed arrays are dense numeric // iterables, so drain their elements into the fresh number[] that diff --git a/packages/compiler/src/frontend/lowering/generator-delegation.ts b/packages/compiler/src/frontend/lowering/generator-delegation.ts index a606c177e..81ecc8353 100644 --- a/packages/compiler/src/frontend/lowering/generator-delegation.ts +++ b/packages/compiler/src/frontend/lowering/generator-delegation.ts @@ -577,10 +577,11 @@ export function lowerBuiltinDelegation( loc, }); } else if (sourceT.kind === "string") { + // A byte cursor over the WTF-8 data, as for-of over a string walks. const ch = lowerer.declareHiddenLocal("%delegateElement", STRING); const length = (receiver: IrExpr): IrExpr => ({ kind: "strIntrinsic", - method: "length", + method: "byteLength", receiver, args: [], type: F64, @@ -595,7 +596,7 @@ export function lowerBuiltinDelegation( localId: ch.id, init: { kind: "strIntrinsic", - method: "cpAt", + method: "cpAtByte", receiver: src(), args: [at()], type: STRING, diff --git a/packages/compiler/src/frontend/lowering/iterator-adapters.ts b/packages/compiler/src/frontend/lowering/iterator-adapters.ts index 4909e7f45..78fcff84d 100644 --- a/packages/compiler/src/frontend/lowering/iterator-adapters.ts +++ b/packages/compiler/src/frontend/lowering/iterator-adapters.ts @@ -743,7 +743,7 @@ export function lowerStringIteratorValue( left: index, right: { kind: "strIntrinsic", - method: "length", + method: "byteLength", receiver: text, args: [], type: F64, @@ -758,7 +758,7 @@ export function lowerStringIteratorValue( localId: "point.0", init: { kind: "strIntrinsic", - method: "cpAt", + method: "cpAtByte", receiver: text, args: [index], type: STRING, @@ -775,7 +775,7 @@ export function lowerStringIteratorValue( left: index, right: { kind: "strIntrinsic", - method: "length", + method: "byteLength", receiver: point, args: [], type: F64, diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index d6d8c1cc7..9586727b3 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -2668,7 +2668,7 @@ export function lowerBindingPattern( lowerer.unsupported("SC1031", el, "rest elements over a union of array or tuple layouts"); const loc = locOf(el); let value = unionArrayValueRead(lowerer, srcRef(), numLit(i, loc), loc, (text) => - strCharsCall(lowerer, text, loc), + strCharsCall(text, loc), ); if (!value) lowerer.unsupported( @@ -2798,7 +2798,7 @@ export function lowerBindingPattern( } // STRING sources (`const [a, b] = str`): array destructuring walks // the STRING ITERATOR — code points, astral characters whole — - // which is Array.from(string)'s own machinery (%str.chars), so the + // which is Array.from(string)'s own machinery (str.codePoints), so the // source splits once into a hidden chars array and the pattern // lowers as an array pattern over string[]. Positions, holes, // defaults (with the bounds test), rest (the remaining code points @@ -2817,7 +2817,7 @@ export function lowerBindingPattern( out.push({ kind: "varDecl", localId: chars.id, - init: strCharsCall(lowerer, srcRef(), loc), + init: strCharsCall(srcRef(), loc), loc, }); lowerer.lowerBindingPattern( @@ -9826,7 +9826,7 @@ function lowerArrayAssignInto( let srcType = tmpRef().type; // A STRING source (`[a, b] = s`): the declaration path's rule in // assignment position — the string iterator's code-point split - // (%str.chars, astral characters whole) into a hidden chars array, + // (str.codePoints, astral characters whole) into a hidden chars array, // then the pattern proceeds as an array pattern over string[] // (defaults with the bounds test, rest packing the remaining code // points, elisions; positions past the last code point inherit the @@ -9839,7 +9839,7 @@ function lowerArrayAssignInto( out.push({ kind: "varDecl", localId: chars.id, - init: strCharsCall(lowerer, tmpRef(), loc), + init: strCharsCall(tmpRef(), loc), loc, }); tmpRef = () => ({ kind: "varRef", localId: chars.id, type: charsT, loc }); @@ -11383,16 +11383,18 @@ function lowerForOfMatchAll( /** `for (const ch of s)` over a STRING: JS's string iterator, which walks * code POINTS — each pass binds the whole character (a two-unit string * for astral chars, where charAt/slice would return one surrogate half). - * Desugars over the UTF-16 cursor machinery: a hidden unit - * index reads the character with the cpAt intrinsic and advances by ITS - * length before the body runs (so `continue` never skips the advance): + * Desugars over a byte cursor into the WTF-8 data: a hidden byte offset + * reads the character that starts there with the cpAtByte intrinsic and + * advances by ITS byte count before the body runs (so `continue` never + * skips the advance): * * { const %s = ; let %i = 0; - * while (%i < %s.length) { const ch = cpAt(%s, %i); %i += ch.length; } } + * while (%i < byteLength(%s)) { const ch = cpAtByte(%s, %i); %i += byteLength(ch); } } * - * Strings are immutable, so the length is stable and there is no + * Strings are immutable, so the byte count is stable and there is no * iterator state to unwind on throw; a lone surrogate is a one-unit - * character, so every step lands on a character start. */ + * character with its own three-byte encoding, so every step lands on a + * character start. */ /** for-of over a CLASS ITERABLE (classIteratorOf's shape) — the * iterator protocol as ordinary method calls: * @@ -12087,7 +12089,7 @@ function lowerForOfString( try { const s = lowerer.declareHiddenLocal("%strof", STRING); const i = lowerer.declareHiddenLocal("%iterof", F64); - i.mutable = true; // the unit index reassigns (hidden locals default const) + i.mutable = true; // the byte cursor reassigns (hidden locals default const) const sRef = (): IrExpr => ({ kind: "varRef", localId: s.id, type: STRING, loc }); const iRef = (): IrExpr => ({ kind: "varRef", localId: i.id, type: F64, loc }); // `for (var c of str)`: the character mechanics stay on a hidden @@ -12118,7 +12120,7 @@ function lowerForOfString( localId: ch.id, init: { kind: "strIntrinsic", - method: "cpAt", + method: "cpAtByte", receiver: sRef(), args: [iRef()], type: STRING, @@ -12145,7 +12147,7 @@ function lowerForOfString( left: iRef(), right: { kind: "strIntrinsic", - method: "length", + method: "byteLength", receiver: chRef, args: [], type: F64, @@ -12177,7 +12179,7 @@ function lowerForOfString( left: iRef(), right: { kind: "strIntrinsic", - method: "length", + method: "byteLength", receiver: sRef(), args: [], type: F64, diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 576386851..bf92c8da3 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -848,6 +848,7 @@ function buildLibFnSignatures() { // (subject, characters, NUL-separated replacement texts): the fused // chain of global one-character replacements. "str.replaceChars": { argTypes: [STRING, STRING, STRING], result: STRING }, + "str.codePoints": { argTypes: [STRING], result: arrayOf(STRING) }, /* ToNumber(string) — ECMA-262 7.1.4.1 StringToNumber (scr_string.c): * trim the JS StrWhiteSpace set, empty/whitespace-only → +0, then the * whole span must be one StrNumericLiteral — signed decimal (Infinity diff --git a/packages/compiler/src/ir/integer-ranges.ts b/packages/compiler/src/ir/integer-ranges.ts index 1909040c4..51c0a929b 100644 --- a/packages/compiler/src/ir/integer-ranges.ts +++ b/packages/compiler/src/ir/integer-ranges.ts @@ -867,6 +867,8 @@ export function analyzeIntegerRangeFacts( ); if (e.type.kind === "f64") { if (e.method === "length") range = { min: 0, max: STRING_MAX_UNITS }; + // A string holds at most SCR_STR_MAX_CAP (0x7fffffff) bytes. + else if (e.method === "byteLength") range = { min: 0, max: STRING_MAX_UNITS }; // An empty needle is found at the clamped position, up to the length. else if (e.method === "indexOf") range = { min: -1, max: STRING_MAX_UNITS }; } diff --git a/packages/compiler/src/ir/intrinsic-signatures.ts b/packages/compiler/src/ir/intrinsic-signatures.ts index eda569883..3e26e77f2 100644 --- a/packages/compiler/src/ir/intrinsic-signatures.ts +++ b/packages/compiler/src/ir/intrinsic-signatures.ts @@ -31,7 +31,8 @@ export const STR_INTRINSIC_SIGS: Record< normalize: { argTypes: [STRING], minArgs: 1, result: STRING }, isWellFormed: { argTypes: [], minArgs: 0, result: BOOL }, toWellFormed: { argTypes: [], minArgs: 0, result: STRING }, - cpAt: { argTypes: [F64], minArgs: 1, result: STRING }, + byteLength: { argTypes: [], minArgs: 0, result: F64 }, + cpAtByte: { argTypes: [F64], minArgs: 1, result: STRING }, }; /** Per-method signature for regexIntrinsic. test/source/flags take the diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 19a5be71d..6118e4955 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -2447,10 +2447,13 @@ export type IrStrIntrinsicMethod = // surrogate, and the copy with each one replaced by U+FFFD. | "isWellFormed" | "toWellFormed" - // The string iterator's step: the full code-POINT character at a UTF-16 - // index (astral chars come back whole where charAt truncates) — for-of - // over strings desugars onto it, advancing by the result's length. - | "cpAt"; + // The string iterator over the WTF-8 bytes: the byte count of a string, + // and the code-point character that starts at a byte offset (a lone + // surrogate is a one-unit character). for-of, spread and Array.from of a + // string walk a byte cursor with them, advancing by the character's + // byte count; the cursor is always a character start below the count. + | "byteLength" + | "cpAtByte"; /** The typed-array/Buffer method surface (bytesIntrinsic). Receiver/arg * conventions (validated): `length`/`byteLength` are property reads → f64; diff --git a/packages/compiler/test/string-iteration-emission.test.ts b/packages/compiler/test/string-iteration-emission.test.ts new file mode 100644 index 000000000..7cda3da55 --- /dev/null +++ b/packages/compiler/test/string-iteration-emission.test.ts @@ -0,0 +1,47 @@ +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 } from "../src/index.js"; + +const directories: string[] = []; +afterEach(async () => { + await Promise.all(directories.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); +}); + +async function emit(source: string): Promise { + const outDir = await mkdtemp(join(tmpdir(), "scriptc-string-iteration-")); + directories.push(outDir); + const entry = join(outDir, "main.ts"); + await writeFile(entry, source); + const outPath = join(outDir, "program.ll"); + const result = await compile(entry, { entry, outDir, outPath, outputKind: "llvm" }); + expect(result.ok, JSON.stringify(result)).toBe(true); + return readFile(outPath, "utf8"); +} + +const calls = (llvm: string, symbol: string): string[] => + llvm.split("\n").filter((line) => line.includes("call ") && line.includes(`@${symbol}(`)); + +test("for-of over a string walks a byte cursor", async () => { + const llvm = await emit(` +function count(s: string): number { + let n = 0; + for (const c of s) n += c.length; + return n; +} +console.log(count(process.argv[1] ?? "")); +`); + expect(calls(llvm, "sc_str_cp_at_byte")).toHaveLength(1); + expect(calls(llvm, "scr_str_cp_at")).toHaveLength(0); + // The helper answers ASCII bytes from the one-character table. + expect(llvm).toContain("define internal ptr @sc_str_cp_at_byte(ptr %s, i64 %b)"); +}); + +test("spread and Array.from of a string build the array in one call", async () => { + const llvm = await emit(` +const s = process.argv[1] ?? ""; +console.log([...s].length, Array.from(s).length); +`); + expect(calls(llvm, "scr_str_code_points")).toHaveLength(2); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8347-string-iteration-code-points.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8347-string-iteration-code-points.ts.json new file mode 100644 index 000000000..1e83f7b6f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8347-string-iteration-code-points.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8347-string-iteration-code-points.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 1e7802fbb..cd17da0f8 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -3514,6 +3514,8 @@ ScrStr *scr_bool_to_scrstr(bool b) { * decode is a whole char; an i addressing the LOW half of an astral pair * (unreachable from the iterator, which only lands on char starts) answers * the containing char. Out of range → empty string. Borrows s; +1. */ +static ScrStr *scr_str_char_of(const char *bytes, size_t adv); + ScrStr *scr_str_cp_at(ScrStr *s, double i) { double idx = scr_to_integer_or_infinity(i); if (!(idx >= 0)) return scr_str_empty(); @@ -3528,7 +3530,75 @@ ScrStr *scr_str_cp_at(ScrStr *s, double i) { if (off >= s->len) return scr_str_empty(); /* out of range */ size_t adv; (void)scr_utf8_decode(s->data + off, &adv); - return scr_str_from_span(s->data + off, adv); + return scr_str_char_of(s->data + off, adv); +} + +/* The one-character strings of U+0080..U+07FF (two-byte sequences, lead + * bytes 0xC2..0xDF), made immortal on first use and shared by every + * thread, as the ASCII ones are static: the string iterator returns them + * instead of a new string per character. A string is immortal before its + * slot publishes it; a thread that loses the race to fill a slot keeps its + * own immortal copy, so at most one extra string per slot and thread. */ +static ScrStr *scr_two_byte_chars[0x800 - 0x80]; + +static ScrStr *scr_two_byte_char(const char *bytes) { + uint32_t cp = ((uint32_t)((unsigned char)bytes[0] & 0x1F) << 6) | ((unsigned char)bytes[1] & 0x3F); + ScrStr **slot = &scr_two_byte_chars[cp - 0x80]; + ScrStr *known = __atomic_load_n(slot, __ATOMIC_ACQUIRE); + if (known) return known; + ScrStr *made = scr_str_from_span(bytes, 2); + scr_str_hash_prime(made); + made->rc = SIZE_MAX; +#ifdef SCR_RC_AUDIT + scr_str_live_forget(); +#endif + scr_lsan_immortal(made); + ScrStr *expected = NULL; + if (__atomic_compare_exchange_n(slot, &expected, made, false, __ATOMIC_ACQ_REL, + __ATOMIC_ACQUIRE)) + return made; + return expected; +} + +/* The character whose encoding starts at bytes and is adv bytes long (+1, + * or immortal). */ +static ScrStr *scr_str_char_of(const char *bytes, size_t adv) { + unsigned char lead = (unsigned char)bytes[0]; + if (adv == 1 && lead < 0x80) return (ScrStr *)&scr_ascii1[lead]; + if (adv == 2) return scr_two_byte_char(bytes); + return scr_str_from_span(bytes, adv); +} + +ScrStr *scr_str_cp_at_byte(ScrStr *s, uint64_t b) { + if (b >= s->len) return scr_str_empty(); + size_t adv; + (void)scr_utf8_decode(s->data + b, &adv); + return scr_str_char_of(s->data + b, adv); +} + +ScrArr *scr_str_code_points(ScrStr *s) { + /* Every byte but a continuation byte starts one character; a lone + * surrogate's three bytes are one character. */ + size_t count = s->len; + if (!s->ascii) + for (size_t b = 0; b < s->len; b++) count -= ((unsigned char)s->data[b] & 0xC0) == 0x80; + ScrArr *out = scr_arr_new_inline(SCR_ELEM_STR, count, false); + size_t k = 0; + for (size_t b = 0; b < s->len;) { + unsigned char c = (unsigned char)s->data[b]; + if (c < 0x80) { + out->data[k++] = (uint64_t)(uintptr_t)&scr_ascii1[c]; + b++; + continue; + } + size_t adv; + (void)scr_utf8_decode(s->data + b, &adv); + out->data[k++] = (uint64_t)(uintptr_t)scr_str_char_of(s->data + b, adv); + b += adv; + } + out->len = (uint32_t)count; + out->filled = (uint32_t)count; + return out; } /* ── the URI component codecs (encodeURIComponent/decodeURIComponent) ── diff --git a/packages/runtime/src/scr_string_methods.h b/packages/runtime/src/scr_string_methods.h index 01ccdd8ae..2e69ee6ac 100644 --- a/packages/runtime/src/scr_string_methods.h +++ b/packages/runtime/src/scr_string_methods.h @@ -150,6 +150,15 @@ ScrStr *scr_str_char_at(ScrStr *s, double i); * (two units); the consuming loop advances by the result's length. * Out of range → empty string. Returns +1. */ ScrStr *scr_str_cp_at(ScrStr *s, double i); +/* The string iterator's step over the WTF-8 bytes: the code-point + * character whose encoding starts at byte offset b, a character start + * below s->len (a lone surrogate is its own three-byte character). The + * compiler answers ASCII bytes inline and calls this for the rest. + * Returns +1. */ +ScrStr *scr_str_cp_at_byte(ScrStr *s, uint64_t b); +/* [...s] and Array.from(s): the code-point characters of s in order, as a + * fresh string array of exactly that length. Returns +1. */ +struct ScrArr *scr_str_code_points(ScrStr *s); /* trimStart()/trimEnd(): the one-sided halves of trim. Returns +1. */ ScrStr *scr_str_trim_start(ScrStr *s); diff --git a/tests/corpus/1561-forof-strings.ts b/tests/corpus/1561-forof-strings.ts index 9d16efd9a..0ea09e755 100644 --- a/tests/corpus/1561-forof-strings.ts +++ b/tests/corpus/1561-forof-strings.ts @@ -1,7 +1,7 @@ // for-of over STRINGS: JS's string iterator walks code POINTS — each pass // binds the whole character, so astral chars arrive as one two-unit string -// (where charAt/slice would truncate the halves). Desugars over the UTF-16 -// cursor with the cpAt step intrinsic. +// (where charAt/slice would truncate the halves). Desugars over a byte +// cursor with the cpAtByte step intrinsic. for (const ch of "héllo 🎉x") console.log(ch, ch.length); // The empty string iterates zero times. diff --git a/tests/corpus/8347-string-iteration-code-points.ts b/tests/corpus/8347-string-iteration-code-points.ts new file mode 100644 index 000000000..3a1286140 --- /dev/null +++ b/tests/corpus/8347-string-iteration-code-points.ts @@ -0,0 +1,96 @@ +// The string iterator: for-of, spread, Array.from, yield* and +// [Symbol.iterator]() walk code points. Astral characters come back whole, +// a lone surrogate is a one-unit character, and halves joined by +// concatenation form one character, as in Node. +function unitCodes(c: string): string { + const codes: string[] = []; + for (let k = 0; k < c.length; k++) codes.push(c.charCodeAt(k).toString(16)); + return `${c.length}:${codes.join(".")}`; +} +const show = (chars: string[]): string => chars.map(unitCodes).join(" "); + +const samples = [ + "", + "abc", + "aé😀b", + "\u0000\u007f\u0080߿ࠀ￿", + "\u{10000}\u{10ffff}", + "é ñ", + "\ud800", + "\udc00", + "x\ud83d", + "\ude00x", + "\udc00\ud800", + "\ud83d" + "\ude00", + "a\ud83d" + "\ude00b\ud83d", + "日本語テキスト", +]; +for (const s of samples) { + const looped: string[] = []; + for (const c of s) looped.push(c); + console.log(JSON.stringify(s), show(looped), show([...s]), show(Array.from(s)), [...s].length, s.length); +} + +// Loop control, heads and bindings. +const text = "a😀b\ud800c"; +let out = ""; +outer: for (const c of text) { + if (c === "b") continue; + for (const d of "xy") { + if (d === "y" && c === "c") continue outer; + out += c + d; + } + if (c === "\ud800") out += "|"; +} +console.log(out); +for (var v of "hé") out += v; +console.log(out); +let last = ""; +for (last of "q😀") out += last.length; +console.log(out, last); +for (const [first] of ["😀z", "ab"].join("")) out += first; +console.log(out); +let source = "abc"; +for (const c of source) { + source += c; + out += source.length; +} +console.log(out, source); +let n = 0; +for (const c of "stop😀here") { + if (c === "😀") break; + n += c.length; +} +console.log(n); + +// Characters as values: lengths, Map keys and code points. +const counts = new Map(); +for (const c of "abracadabra😀😀é") counts.set(c, (counts.get(c) ?? 0) + 1); +console.log([...counts].map(([k, v]) => `${k}=${v}`).join(",")); +let points = 0; +for (const c of "😀é\ud800a") points += c.codePointAt(0)!; +console.log(points); + +// Generators and explicit iterators. +function* letters(s: string): Generator { + yield* s; +} +console.log(show([...letters("x😀\udc00y")])); +const it = "o😀"[Symbol.iterator](); +let step = it.next(); +while (!step.done) { + out += step.value.length; + step = it.next(); +} +console.log(out); + +// A long string built from mixed pieces. +let long = ""; +for (let i = 0; i < 2000; i++) long += i % 3 === 0 ? "a" : i % 3 === 1 ? "é" : "😀"; +let units = 0; +let chars = 0; +for (const c of long) { + units += c.length; + chars++; +} +console.log(units, chars, long.length, [...long].length); diff --git a/tests/harness/corpus-costs/8347-string-iteration-code-points.ts.json b/tests/harness/corpus-costs/8347-string-iteration-code-points.ts.json new file mode 100644 index 000000000..3e2ae0706 --- /dev/null +++ b/tests/harness/corpus-costs/8347-string-iteration-code-points.ts.json @@ -0,0 +1 @@ +{ "plain": 2770, "san": 3750 } From 8f425a8a1b14a3dc0f832c944674f73483502fab Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:01:23 -0500 Subject: [PATCH 087/230] Wait for mailbox events in ppoll on Linux A thread waiting for its mailbox slept on a condition variable. The kernel then places the woken thread without a hint, so with every CPU busy it can queue behind a running thread until that thread is preempted or blocks: a worker program's phase barrier (post to each worker, wait for every reply) lost about 1 ms per phase, and up to 5 ms, on 4 CPUs. On Linux the wait now sleeps in ppoll(2) on the mailbox's wake pipe, which every post already writes. A pipe write is a synchronous wakeup: the woken thread can run on the poster's CPU, which the poster frees when it waits in turn. Bytes left by signals whose events were already taken are drained under the lock first. Other platforms keep the condition variable. --- packages/runtime/src/scr_mailbox.c | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/runtime/src/scr_mailbox.c b/packages/runtime/src/scr_mailbox.c index b2af22774..6e5678378 100644 --- a/packages/runtime/src/scr_mailbox.c +++ b/packages/runtime/src/scr_mailbox.c @@ -21,6 +21,7 @@ #include #include #ifdef __linux__ +#include #include #endif #endif @@ -321,7 +322,32 @@ void scr_mailbox_wait(ScrMailbox *mailbox, double milliseconds) { if (!(milliseconds > 0)) return; if (scr_mailbox_spin(mailbox, milliseconds)) return; scr_mailbox_lock(mailbox); - if (!mailbox->head && !mailbox->closed) { + bool waiting = !mailbox->head && !mailbox->closed; +#ifdef __linux__ + /* Linux sleeps in ppoll(2) on the wake pipe rather than on the + * condition: a pipe write wakes its reader as a synchronous wakeup, + * which the scheduler can run on the writer's CPU when the writer is + * about to block. A worker that posts its result and then waits frees + * that CPU, so the thread it woke runs there at once instead of queueing + * behind another busy thread until the scheduler preempts it. Bytes left + * by signals whose events were already taken are drained first, under + * the lock: an event posted after the unlock writes a new byte. */ + if (waiting) scr_mailbox_drain(mailbox); + scr_mailbox_unlock(mailbox); + if (!waiting) return; + struct pollfd wake = {mailbox->read_fd, POLLIN, 0}; + if (isinf(milliseconds)) (void)ppoll(&wake, 1, NULL, NULL); + else { + /* The event loop rechecks its monotonic deadlines after every wake, + * including spurious ones. Bound conversion before casting time_t. */ + if (milliseconds > 86400000) milliseconds = 86400000; + struct timespec timeout = {(time_t)(milliseconds / 1000), + (long)ceil(fmod(milliseconds, 1000) * 1000000)}; + if (timeout.tv_nsec == 1000000000) { timeout.tv_sec++; timeout.tv_nsec = 0; } + (void)ppoll(&wake, 1, &timeout, NULL); + } +#else + if (waiting) { #ifdef _WIN32 DWORD timeout = milliseconds >= INFINITE ? INFINITE : (DWORD)ceil(milliseconds); (void)SleepConditionVariableSRW(&mailbox->condition, &mailbox->lock, timeout, 0); @@ -348,6 +374,7 @@ void scr_mailbox_wait(ScrMailbox *mailbox, double milliseconds) { #endif } scr_mailbox_unlock(mailbox); +#endif } int scr_mailbox_pollfd(const ScrMailbox *mailbox) { From ed50daa853aedeea1d644df779db93ada8479e80 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 11:01:57 -0700 Subject: [PATCH 088/230] Keep the whole union of an optional parameter that reaches a recursive placeholder An optional parameter typed by a union that is still being mapped gets a recursive placeholder: a union id whose arms arrive when the outer frame finishes. The function-type mapping then armed that placeholder with undefined, but the placeholder's arm list was still empty, so the result was a fresh union holding only `undefined`. The parameter lost its record arm, the same checker type mapped to two different layouts depending on where the mapping started, and later conversions failed (SC2001, SC2002, SC2003). This happens when an interface method takes an options object whose callbacks are typed by a subinterface of that same interface: `newSession(options?: { withSession?: (ctx: Replaced) => Promise })` with `interface Replaced extends CommandContext`. When the checker spelled the parameter with an undefined constituent, the placeholder already carries that arm, so the parameter keeps the placeholder. withUnitArm now refuses a pending placeholder instead of building the unit-only union. pi's ExtensionCommandContext is such an interface. It refused ExtensionAPI.registerCommand, and with it pi's main(). Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/frontend/type-mapper.test.ts | 14 +++ packages/compiler/src/frontend/type-mapper.ts | 24 +++- ...recursive-optional-callback-params.ts.json | 6 + .../recursive-optional-callback-params.ts | 107 ++++++++++++++++++ ...recursive-optional-callback-params.ts.json | 1 + 5 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-optional-callback-params.ts.json create mode 100644 tests/corpus/recursive-optional-callback-params.ts create mode 100644 tests/harness/corpus-costs/recursive-optional-callback-params.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.test.ts b/packages/compiler/src/frontend/type-mapper.test.ts index 163ba7b9e..6e7e339d6 100644 --- a/packages/compiler/src/frontend/type-mapper.test.ts +++ b/packages/compiler/src/frontend/type-mapper.test.ts @@ -53,6 +53,20 @@ describe("union arm lookup", () => { }); }); +describe("recursive union placeholders", () => { + test("refuse a unit arm until their arms are known", () => { + const unions = new UnionRegistry(); + const recursive = {} as Type; + const union = unions.recursiveRef(recursive, () => "Recursive"); + const placeholder: IrType = { kind: "union", unionId: union }; + expect(unions.isPendingPlaceholder(union)).toBe(true); + expect(withUndefinedArm(placeholder, unions)).toBeNull(); + unions.finalizeRecursive(recursive, [{ kind: "record", shapeId: "r1" }, UNDEFINED_T]); + expect(unions.isPendingPlaceholder(union)).toBe(false); + expect(withUndefinedArm(placeholder, unions)).toEqual(placeholder); + }); +}); + describe("nullable collection union builders", () => { test.each([mapOf(STRING, F64), setOf(STRING)])( "optional %j fields and generator results share the same union", diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..63eed378d 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -717,6 +717,12 @@ export class UnionRegistry { return id !== undefined && this.pendingRec.has(id); } + /** True when `unionId` is a recursive placeholder whose arms are not + * known yet (its outer frame is still mapping, or failed). */ + isPendingPlaceholder(unionId: string): boolean { + return this.pendingRec.has(unionId); + } + /** Completes a recursive placeholder with its canonical arm list and * registers the structural key (first writer wins, like shapes). */ finalizeRecursive(t: ts.Type, arms: IrType[], discriminant?: IrUnionDiscriminant): string { @@ -5302,8 +5308,19 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null if (pt) pt = jsOpenObjectType(decl, pt, ctx.shapes, ctx.unions); // Belt and braces for non-strict type worlds: an optional param's // ABI slot is always the undefined-armed union (strictNullChecks - // already spells it that way; arm it here if the world didn't). - if (optional && pt && pt.kind !== "void" && pt.kind !== "dyn") { + // already spells it that way; arm it here if the world didn't). A + // back-reference to a union still being mapped (`options?: { then?: + // (ctx: Sub) => void }` where Sub extends the interface declaring the + // method) is a recursive placeholder with no arms yet; when the checker + // spelled the parameter `X | undefined`, the placeholder receives that + // undefined arm when its frame finishes. + const pendingOptional = + pt?.kind === "union" && + ctx.unions.isPendingPlaceholder(pt.unionId) && + ts + .constituentTypes(checker.getTypeOfSymbol(p)) + .some((part) => (part.flags & ts.TypeFlags.Undefined) !== 0); + if (optional && pt && pt.kind !== "void" && pt.kind !== "dyn" && !pendingOptional) { const armed = withUndefinedArm(pt, ctx.unions); if (!armed) return null; pt = armed; @@ -6782,6 +6799,9 @@ export function withUnitArm( if (t.kind === "union") { const def = unions.get(t.unionId); if (!def) return null; + // A recursive placeholder's arms arrive when its outer frame finishes; + // extending its (still empty) arm list would drop every arm but the unit. + if (unions.isPendingPlaceholder(t.unionId)) return null; if (def.arms.some((a) => a.kind === kind)) return t; const arms = [...def.arms, unit]; // Date and Promise arms beside other data arms have no union home. diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-optional-callback-params.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-optional-callback-params.ts.json new file mode 100644 index 000000000..abf783f85 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-optional-callback-params.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/recursive-optional-callback-params.ts" + ], + "diags": [] +} diff --git a/tests/corpus/recursive-optional-callback-params.ts b/tests/corpus/recursive-optional-callback-params.ts new file mode 100644 index 000000000..0c81235d6 --- /dev/null +++ b/tests/corpus/recursive-optional-callback-params.ts @@ -0,0 +1,107 @@ +// An optional parameter whose type reaches back to the interface that +// declares the method keeps its whole union. A command context's +// `newSession(options?)` takes callbacks typed by a subinterface of the +// context, so mapping the context passes through the options union while +// that union is still being mapped; the parameter still holds the options +// record, not only `undefined`. + +interface CommandContext { + cwd: string; + newSession(options?: { + parent?: string; + withSession?: (ctx: ReplacedContext) => Promise; + }): Promise<{ cancelled: boolean }>; + fork( + entryId: string, + options?: { position?: "before" | "at"; withSession?: (ctx: ReplacedContext) => Promise }, + ): Promise<{ cancelled: boolean }>; +} + +interface ReplacedContext extends CommandContext { + depth: number; + send(content: string): Promise; +} + +interface Command { + description?: string; + handler: (args: string, ctx: CommandContext) => Promise; +} + +const log: string[] = []; +const commands = new Map(); + +function register(name: string, command: Command): void { + commands.set(name, command); +} + +register("new", { + description: "start a session", + handler: async (args, ctx) => { + const first = await ctx.newSession({ + parent: args, + withSession: async (next) => { + await next.send(`hello from ${next.cwd} at depth ${next.depth}`); + const nested = await next.newSession({ + withSession: async (inner) => { + await inner.send(`nested at depth ${inner.depth}`); + }, + }); + log.push(`nested cancelled=${nested.cancelled}`); + }, + }); + log.push(`first cancelled=${first.cancelled}`); + const bare = await ctx.newSession(); + log.push(`bare cancelled=${bare.cancelled}`); + }, +}); + +register("fork", { + handler: async (args, ctx) => { + const r = await ctx.fork(args, { + position: "at", + withSession: async (next) => next.send(`forked ${args} at depth ${next.depth}`), + }); + log.push(`fork cancelled=${r.cancelled}`); + const plain = await ctx.fork(args); + log.push(`plain fork cancelled=${plain.cancelled}`); + }, +}); + +function makeContext(depth: number): ReplacedContext { + return { + cwd: `/work/${depth}`, + depth, + newSession: async (options) => { + log.push(`newSession depth=${depth} parent=${options?.parent ?? "-"}`); + if (options?.withSession) { + await options.withSession(makeContext(depth + 1)); + return { cancelled: false }; + } + return { cancelled: true }; + }, + fork: async (entryId, options) => { + log.push(`fork ${entryId} position=${options?.position ?? "-"}`); + const callback = options?.withSession; + if (callback === undefined) return { cancelled: true }; + await callback(makeContext(depth + 1)); + return { cancelled: false }; + }, + send: async (content) => { + log.push(`send: ${content}`); + }, + }; +} + +async function run(): Promise { + const root: CommandContext = makeContext(0); + for (const [name, command] of commands) { + log.push(`run ${name} (${command.description ?? "no description"})`); + await command.handler(`entry-${name}`, root); + } + for (const line of log) console.log(line); +} + +run().catch((e: unknown) => { + console.log("failed", e instanceof Error ? e.message : String(e)); + process.exitCode = 1; +}); diff --git a/tests/harness/corpus-costs/recursive-optional-callback-params.ts.json b/tests/harness/corpus-costs/recursive-optional-callback-params.ts.json new file mode 100644 index 000000000..c33a4b32c --- /dev/null +++ b/tests/harness/corpus-costs/recursive-optional-callback-params.ts.json @@ -0,0 +1 @@ +{ "plain": 6000 } From ffbbf1590f2208d19a14ddb2bdf237aeb7c60c5e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:11:47 -0500 Subject: [PATCH 089/230] Send every dyn kind that can be a cycle candidate through one release path The inline dyn release sorted a surviving value into three groups by kind: arrays and objects tested the candidate-buffer word and then called scr_dyn_release_survivor, capsules and handles called it directly, and functions and proxies appended to the candidate buffer inline. All six kinds now take one path: a value already in a candidate buffer, or never one, returns inline, and scr_dyn_release_survivor, which now also covers functions and proxies, decides the rest. Behavior is unchanged, and each inlined release site keeps one kind test and one call. Effect's executable text is 10% smaller and it runs 5.8% faster; zod runs 3.8% faster, and bench:runtime's dynamic-properties 14% faster. --- .../compiler/src/backend/llvm/shapes.test.ts | 56 ++++++++++++------- packages/compiler/src/backend/llvm/shapes.ts | 39 +++++++------ packages/runtime/src/scr_dyn.h | 3 +- packages/runtime/src/scr_json.c | 10 +++- 4 files changed, 67 insertions(+), 41 deletions(-) diff --git a/packages/compiler/src/backend/llvm/shapes.test.ts b/packages/compiler/src/backend/llvm/shapes.test.ts index 61c1a074d..d9e5fa641 100644 --- a/packages/compiler/src/backend/llvm/shapes.test.ts +++ b/packages/compiler/src/backend/llvm/shapes.test.ts @@ -146,11 +146,19 @@ describe("LLVM runtime RC symbols", () => { // The dyn fast path buffers surviving values by kind, as scr_dyn_release does. const ir = emitInlineRcHelpers(host).join("\n"); expect(ir).toContain("call void @scr_dyn_release_survivor(ptr %o)"); - expect(ir).toMatch( - /switch i32 %kind, label %done \[ i32 4, label %container i32 5, label %container i32 8, label %root/, + // Every kind that can be a candidate takes one path; scalars stay inline. + expect(ir).toContain( + "switch i32 %kind, label %done [ i32 4, label %candidate i32 5, label %candidate i32 8, label %candidate i32 9, label %candidate i32 12, label %candidate i32 13, label %candidate ]", ); - // Arrays and objects not yet buffered ask the runtime, which skips intact documents. + // Values not yet buffered ask the runtime, which decides per value. expect(ir).toMatch(/br i1 %cqueued, label %done, label %survivor/); + // The runtime buffers dyn candidates, so the dyn helper has no inline append. + const dynHelper = ir.slice( + ir.indexOf("@sc_rc_release_dyn("), + ir.indexOf("\n}", ir.indexOf("@sc_rc_release_dyn(")), + ); + expect(dynHelper).not.toContain("sc_cyc_append"); + expect(dynHelper).not.toContain("scr_cyc_on_release"); }); test("sanitized builds call the fast-path helpers out of line", () => { @@ -208,36 +216,42 @@ describe("LLVM runtime RC symbols", () => { }); test("optimized builds buffer candidates through the module's append helper", () => { - const emit = (inlineRc: true | "release", extra: Partial) => { + const emit = (inlineRc: true | "release", extra: Partial, kind: "union" | "dyn") => { const { host } = declarationHost(64, inlineRc); const configured: ShapeHost = { ...host, inlineAlloc: true, ...extra }; - releaseSym(configured, { kind: "dyn" }); + releaseSym(configured, kind === "union" ? ({ kind, unionId: "u0" } as never) : { kind }); return { ir: emitInlineRcHelpers(configured).join("\n"), decls: inlineRcDecls(configured), }; }; + const { ir, decls } = emit(true, {}, "union"); + expect(ir).toContain("call preserve_mostcc void @sc_cyc_append(ptr %o)"); + expect(ir).toContain("define internal preserve_mostcc void @sc_cyc_append(ptr %o) noinline"); + // The young test reads the generation byte at obj-5; the append fills + // scr_cyc_buffers' nursery buffer and the header's position at obj-4. + expect(ir).toContain("getelementptr i8, ptr %o, i64 -5"); + expect(ir).toContain("getelementptr inbounds i8, ptr @scr_cyc_buffers, i64 96"); + expect(ir).toContain("getelementptr inbounds i8, ptr @scr_cyc_buffers, i64 104"); + expect(ir).toContain("load i64, ptr @scr_cyc_live"); + expect(ir).toContain("or i32 %i, -2147483648"); + expect(ir).toContain("call void @scr_cyc_on_release(ptr %o)"); + expect(decls).toContain( + "@scr_cyc_buffers = external global { [3 x { ptr, i64, i64, i64 }], i64, i64 }", + ); + expect(decls).toContain("@scr_cyc_live = external global i64"); + // The dyn release buffers through scr_dyn_release_survivor, so a module + // whose only headered release is dyn's has no append helper. for (const mode of [true, "release"] as const) { - const { ir, decls } = emit(mode, {}); - expect(ir).toContain("call preserve_mostcc void @sc_cyc_append(ptr %o)"); - expect(ir).toContain("define internal preserve_mostcc void @sc_cyc_append(ptr %o) noinline"); - // The young test reads the generation byte at obj-5; the append fills - // scr_cyc_buffers' nursery buffer and the header's position at obj-4. - expect(ir).toContain("getelementptr i8, ptr %o, i64 -5"); - expect(ir).toContain("getelementptr inbounds i8, ptr @scr_cyc_buffers, i64 96"); - expect(ir).toContain("getelementptr inbounds i8, ptr @scr_cyc_buffers, i64 104"); - expect(ir).toContain("load i64, ptr @scr_cyc_live"); - expect(ir).toContain("or i32 %i, -2147483648"); - expect(ir).toContain("call void @scr_cyc_on_release(ptr %o)"); - expect(decls).toContain( - "@scr_cyc_buffers = external global { [3 x { ptr, i64, i64, i64 }], i64, i64 }", - ); - expect(decls).toContain("@scr_cyc_live = external global i64"); + const dyn = emit(mode, {}, "dyn"); + expect(dyn.ir).not.toContain("sc_cyc_append"); + expect(dyn.ir).not.toContain("scr_cyc_on_release"); + expect(dyn.decls.join("\n")).not.toContain("scr_cyc_buffers"); } // Worker executables and targets without inline allocation call // scr_cyc_on_release, which tries the same append first. for (const extra of [{ threadLocalAlloc: true }, { inlineAlloc: false }]) { - const { ir, decls } = emit(true, extra); + const { ir, decls } = emit(true, extra, "union"); expect(ir).not.toContain("sc_cyc_append"); expect(ir).toContain("call void @scr_cyc_on_release(ptr %o)"); expect(decls.join("\n")).not.toContain("scr_cyc_buffers"); diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index ab623f934..7498235b0 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -589,9 +589,10 @@ interface InlineRcFamily { * repeats its own checks, so the fast path never duplicates it. * * The dyn release buffers a surviving value by kind (scr_dyn_release): - * the fast path decides the always and never cases and calls - * scr_dyn_release_survivor for kinds whose candidacy is per value, - * including arrays and objects of intact documents (ScrDynRegion). + * the fast path skips scalars and values already in a candidate buffer or + * never candidates, and calls scr_dyn_release_survivor for the rest, which + * decides per value (intact documents, ScrDynRegion; capsules and handles + * without edges). * * The array release inlines the decrement of an array that stays alive * and is not headered (`a->cyc`): those are most array releases in @@ -698,9 +699,10 @@ function requestRcHelper(host: ShapeHost, family: string, op: RcOp): string { for (const decl of rcHelperDecls(family, op)) host.declare(decl); // Its body is written last (emitInlineRcHelpers), after the requests are // collected: a release that can buffer a candidate requests the append - // helper now. The jsval row (JS_RC_ROW) has no collector header; its - // cells release through dyn's helper, which makes its own request. - if (op === "release" && row.cycle === "always") requestCycleAppend(host); + // helper now. The dyn release buffers through scr_dyn_release_survivor + // instead. The jsval row (JS_RC_ROW) has no collector header; its cells + // release through dyn's helper. + if (op === "release" && row.cycle === "always" && family !== "dyn") requestCycleAppend(host); return `@${inlineRcName(family, op)}`; } @@ -820,8 +822,11 @@ function rcHelperDecls(family: string, op: RcOp): string[] { if (op === "retain" || row.release === null) return []; return [ `declare void @${row.release}(ptr)`, - ...(row.cycle === "always" ? [`declare void @scr_cyc_on_release(ptr)`] : []), - ...(family === "dyn" ? [`declare void @scr_dyn_release_survivor(ptr)`] : []), + ...(family === "dyn" + ? [`declare void @scr_dyn_release_survivor(ptr)`] + : row.cycle === "always" + ? [`declare void @scr_cyc_on_release(ptr)`] + : []), ]; } @@ -931,21 +936,21 @@ function releaseHelper(host: ShapeHost, family: string, row: InlineRcFamily): st // scr_dyn_release buffers a surviving value by kind: always for // functions and proxies, per value for arrays and objects (not // while their document is intact, ScrDynRegion), capsules and - // handles (scr_dyn_release_survivor), never for scalars. An - // array or object already in a candidate buffer stays inline. + // handles, never for scalars. Every kind that can be buffered + // takes one path, which keeps each inlined site small: a value + // already in a candidate buffer (or never one) stays inline, and + // scr_dyn_release_survivor decides the rest. ` %kp = getelementptr i8, ptr %o, ${S} ${S === "i64" ? 8 : 4} ; ScrDyn.kind`, ` %kind = load i32, ptr %kp`, - // SCR_DYN_ARR 4, OBJ 5, FUNC 8, PROXY 13; HANDLE 9, TYPED_REF 12. - ` switch i32 %kind, label %done [ i32 4, label %container i32 5, label %container i32 8, label %root i32 13, label %root i32 9, label %survivor i32 12, label %survivor ]`, - `container:`, + // SCR_DYN_ARR 4, OBJ 5, FUNC 8, HANDLE 9, TYPED_REF 12, PROXY 13. + ` switch i32 %kind, label %done [ i32 4, label %candidate i32 5, label %candidate i32 8, label %candidate i32 9, label %candidate i32 12, label %candidate i32 13, label %candidate ]`, + `candidate:`, ` %cbufp = getelementptr i8, ptr %o, ${S} -${host.cycleBufOffset}`, ` %cbuf = load i32, ptr %cbufp`, ` %cqueued = icmp slt i32 %cbuf, 0`, ` br i1 %cqueued, label %done, label %survivor`, `survivor:`, ` call void @scr_dyn_release_survivor(ptr %o)`, - ` br label %done`, - `root:`, ] : family === "box" ? [ @@ -964,7 +969,9 @@ function releaseHelper(host: ShapeHost, family: string, row: InlineRcFamily): st `root:`, ] : []), - ...(cycle === "always" ? cycleRootLines(host, "done") : [` br label %done`]), + ...(cycle === "always" && family !== "dyn" + ? cycleRootLines(host, "done") + : [` br label %done`]), `done:`, ` ret void`, `}`, diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 656f2b994..33cd63c05 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1941,7 +1941,8 @@ extern void scr_dyn_listener_fire_data(ScrClosure *cb, ScrBytes *chunk); extern void scr_dyn_listener_fire_err(ScrClosure *cb, ScrStr *msg); void *scr_dyn_retain_v(void *d); -/* After a compiled fast path decremented a surviving capsule or handle. */ +/* After a compiled fast path decremented a surviving value of a kind that + * can be a cycle candidate and is not one yet (scr_json.c). */ void scr_dyn_release_survivor(ScrDyn *d); void scr_dyn_release_v(void *d); void scr_dyn_trace_v(void *d, ScrTraceVisit visit, void *ctx); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cd9eeecde..f2254cd44 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1317,9 +1317,9 @@ static void scr_dyn_gcfree(void *ptr) { scr_dyn_dispose(ptr, true); } static void scr_dyn_destroy(void *ptr) { scr_dyn_dispose(ptr, false); } /* The cycle bookkeeping of a value that survives a release. Compiled - * release fast paths decrement first and call this for the kinds whose - * candidacy depends on per-value state, matching scr_dyn_release: arrays - * and objects not yet buffered (skipped while their document is intact, + * release fast paths decrement first and call this for every kind that can + * be a candidate and is not one yet, matching scr_dyn_release: functions + * and proxies, arrays and objects (skipped while their document is intact, * ScrDynRegion), capsules and handles. */ void scr_dyn_release_survivor(ScrDyn *d) { if (d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_OBJ) { @@ -1327,6 +1327,10 @@ void scr_dyn_release_survivor(ScrDyn *d) { if (!region || !region->intact) scr_cyc_released(d); return; } + if (d->kind == SCR_DYN_FUNC || d->kind == SCR_DYN_PROXY) { + scr_cyc_released(d); + return; + } if ((d->kind == SCR_DYN_TYPED_REF && (d->v.typed_ref.materialized || d->v.typed_ref.casts || d->v.typed_ref.traced || d->prototype || d->symbol_properties || d->symbol_keys)) || From fb17fc0715cfef4e8df42d3f4625e464ad127fcc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:15:28 -0500 Subject: [PATCH 090/230] Read fixed encrypted keys in corpus 8167's wrong-passphrase cases The cases that decrypt with a wrong passphrase used keys encrypted afresh on each run. A wrong key decrypts to random bytes, and about one run in two hundred those bytes pass the padding check, so OpenSSL fails at the ASN.1 step with a longer error than "bad decrypt" and the program's output changes. The section that reads encrypted keys now uses fixed encryptions of one P-256 key, so each case fails the same way on every run. --- tests/corpus/8167-encrypted-private-keys.cjs | 39 +++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/tests/corpus/8167-encrypted-private-keys.cjs b/tests/corpus/8167-encrypted-private-keys.cjs index 561a0aec3..7b274e500 100644 --- a/tests/corpus/8167-encrypted-private-keys.cjs +++ b/tests/corpus/8167-encrypted-private-keys.cjs @@ -51,11 +51,40 @@ tryIt("empty passphrase", () => { return same(c.createPrivateKey({ key: out, passphrase: "" }), rsa); }); -// Reading encrypted keys. -const p8pem = ec.privateKey.export({ type: "pkcs8", format: "pem", cipher: "aes-256-cbc", passphrase: "pw" }); -const p8der = ec.privateKey.export({ type: "pkcs8", format: "der", cipher: "aes-256-cbc", passphrase: "pw" }); -const sec1 = String(ec.privateKey.export({ type: "sec1", format: "pem", cipher: "aes-128-cbc", passphrase: "pw" })); -tryIt("public from encrypted", () => c.createPublicKey({ key: p8pem, passphrase: "pw" }).equals(ec.publicKey)); +// Reading encrypted keys. These are fixed encryptions of one P-256 key +// with the passphrase "pw": decrypting with a wrong passphrase gives +// random-looking bytes, which a fresh encryption would make fail at +// different steps from run to run. +const fixedKey = [ + "-----BEGIN PRIVATE KEY-----", + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgExcc5/xZ3vYL2Dpg", + "XnB7E8gQhcW1bkUcIGJWBGvrrGChRANCAAR0wxNUH1yo/U9nqFY4Gt5SVzPjvWq6", + "NzlStUC6Njk9BdR7SvBlNIGDAFlOfMwhfZbvvvMP+rLMR3RtBg3Ketxe", + "-----END PRIVATE KEY-----", +].join("\n") + "\n"; +const p8pem = [ + "-----BEGIN ENCRYPTED PRIVATE KEY-----", + "MIH0MF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBBkpqs2611cRjGDZTd4", + "FmNeAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQRhL9yxS4i0ngtke+", + "frhURASBkN0dk+GQNCGU5cFC+MMswOr9XwGaQnLbJo7VnhCje3//JaVJDjexrd69", + "YZliX7pKGNghfFf+eBAeOAkwbAK39T8KQPwTJVJ+Z5O485N2fl5dPlr3gaEA3+cm", + "LQgasmL7Zv9UOH2iwvWllFYL3bnwr7VsqTCW2xe53qTB+dWVurP0cCiZNGEnUzrf", + "4bvWNrW0nA==", + "-----END ENCRYPTED PRIVATE KEY-----", +].join("\n") + "\n"; +const p8der = Buffer.from("MIH0MF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBAOrVavYYEvy3KUHwK0hbQnAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQmEnqHOLegUirb49LD8oNhASBkGUwe3SZciScH/tWuoK5JWujckNHRKROwu81a5AGjIKxkvqCm+Qp9Gr4rPsb2vbv3bgfojbEKvSGvtuqB/X/jpN5UMZwWI3D9Ocd1+jEq1FqDb3rb3qxcoO9jmwuFFzNeYy4sW29XFuKTxu4aJHT1sEtMzIVlRLv53aXzpp9eQ9j47EOTWFqIpkt02TRFuazCQ==", "base64"); +const sec1 = [ + "-----BEGIN EC PRIVATE KEY-----", + "Proc-Type: 4,ENCRYPTED", + "DEK-Info: AES-128-CBC,E73A95A6783C45F914B397FE018521D2", + "", + "nsuhQFMPr+QFITcpmr2JWs+Zep4wxoCWMEk1jzyyXSJddfebIXOFg4xmtK8YJXIy", + "vqa6pvAIiak0iEZjVSn20+JAg2wqQ0sLaFPhBmhcZk+xuQoLD8CcYvROO5VtVELm", + "DkgOUcJ1oEfZJIRM6U2oDNFmj2NB/SnSQUf1niy7ba8=", + "-----END EC PRIVATE KEY-----", +].join("\n") + "\n"; +const fixedPublic = c.createPublicKey(fixedKey); +tryIt("public from encrypted", () => c.createPublicKey({ key: p8pem, passphrase: "pw" }).equals(fixedPublic)); tryIt("public from traditional", () => c.createPublicKey({ key: sec1, passphrase: "pw" }).type); tryIt("pkcs8 pem no passphrase", () => c.createPrivateKey(p8pem)); tryIt("pkcs8 der no passphrase", () => c.createPrivateKey({ key: p8der, format: "der", type: "pkcs8" })); From bd1bf39a765d7b8993f25862b40225388fc5e0f8 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 11:18:18 -0700 Subject: [PATCH 091/230] Close recursive record knots through erased generic method members A record or union that refers back to itself only closes its recursive knot when the frame did not resolve anything through context-sensitive hooks; otherwise the same checker type could answer differently per instantiation, so the knot is refused. Erasing a generic method member (`send(message: Message): string`) resolves the method's own type parameters to `unknown`, and that resolution counted as context-sensitive. Any recursive interface declaring a generic method was therefore refused: `interface Sub extends Base { send(m: Message): string }` reached from `Base { child?: Sub }`, or a list node with a generic method beside its `next` field. The member erasure now records its own type parameters, and resolving one of them no longer counts. Consulting an enclosing binding still does, so a generic method that mentions an outer type parameter keeps the fence. The erased member's mapping is not memoized under the member's type, since the same type maps to nothing outside the erasure. pi's ReplacedSessionContext (sendMessage) extends ExtensionCommandContext, whose methods take callbacks over ReplacedSessionContext. The fence refused ExtensionAPI.registerCommand and, through it, pi's main(). Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.ts | 22 ++++++- .../recursive-generic-method-members.ts.json | 6 ++ .../recursive-generic-method-members.ts | 58 +++++++++++++++++++ .../recursive-generic-method-members.ts.json | 1 + 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-generic-method-members.ts.json create mode 100644 tests/corpus/recursive-generic-method-members.ts create mode 100644 tests/harness/corpus-costs/recursive-generic-method-members.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..99766d0ab 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1255,6 +1255,10 @@ export interface TypeMapperCtx { * (erasedGenericMemberType): the signature's own type parameters * resolve to `unknown` instead of refusing the mapping. */ erasingSignature?: ts.Signature; + /** Set with erasingSignature: the signature's own type parameters, and + * whether the erasure consulted an enclosing binding. An erasure that + * resolved only its own parameters answers the same in every context. */ + erasedMember?: { own: ReadonlySet; outerConsulted: boolean }; /** True while an erased generic body lowers (its type parameters are * bound to `unknown`). */ isErasing?: () => boolean; @@ -1835,6 +1839,7 @@ export function mapType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { memoKey !== null && topLevel && contextResolutions === sensitivityAtEntry && + ctx.erasingSignature === undefined && (ctx.canMemoizeType?.() ?? true) ) { ctx.typeMemo?.set(memoKey, mapped); @@ -1991,7 +1996,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { !ERASURE_KEPT_CONTAINERS.has(ts.symbolNameOf(type.getSymbol()) ?? "") && mentionsErasedTypeParam(type, ctx, 0) ) { - contextResolutions++; + if (!ctx.erasedMember || ctx.erasedMember.outerConsulted) contextResolutions++; return DYN; } // Readonly over a program class (Object.freeze's result type) is the @@ -2001,7 +2006,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if (resolveTypeParam && type.flags & ts.TypeFlags.TypeParameter) { const bound = resolveTypeParam(type); if (bound) { - contextResolutions++; + if (!ctx.erasedMember?.own.has(type)) contextResolutions++; return bound; } } @@ -7309,10 +7314,21 @@ export function erasedGenericMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType const sig = sigs[0]!; const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; + // Resolving only the member's own parameters does not make the erasure + // context-sensitive, so a recursive record holding a generic method + // (`interface Sub extends Base { send(m: Msg): void }` reached + // from Base) still closes its knot. Consulting an enclosing binding does. + const erasedMember = { own, outerConsulted: false }; const erased = mapType(t, { ...ctx, erasingSignature: sig, - resolveTypeParam: (param) => (own.has(param) ? DYN : (outer?.(param) ?? null)), + erasedMember, + resolveTypeParam: (param) => { + if (own.has(param)) return DYN; + if (outer === undefined) return null; + erasedMember.outerConsulted = true; + return outer(param); + }, }); return erased?.kind === "func" ? erased : undefined; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-generic-method-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-generic-method-members.ts.json new file mode 100644 index 000000000..6c3590ab8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/recursive-generic-method-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/recursive-generic-method-members.ts" + ], + "diags": [] +} diff --git a/tests/corpus/recursive-generic-method-members.ts b/tests/corpus/recursive-generic-method-members.ts new file mode 100644 index 000000000..1f4d8372d --- /dev/null +++ b/tests/corpus/recursive-generic-method-members.ts @@ -0,0 +1,58 @@ +// A recursive record that declares a generic method compiles. Erasing the +// method's own type parameters does not depend on any enclosing generic +// binding, so the record still closes its recursive knot: a parent +// interface whose child is a subinterface declaring a generic method, and a +// list node whose generic method sits beside a self-referencing field. + +interface Message { + content: string; + details?: T; +} + +interface TreeNode { + name: string; + child?: SendingNode; +} + +interface SendingNode extends TreeNode { + send(message: Message): string; +} + +interface ListNode { + value: number; + next: ListNode | null; + map(f: (value: number) => U): string; +} + +function describe(node: TreeNode): string { + const inner = node.child; + return inner ? `${node.name} > ${describe(inner)}` : node.name; +} + +function leaf(name: string): SendingNode { + return { name, send: () => `${name} got a message` }; +} + +const tree: SendingNode = { + name: "root", + child: { name: "middle", child: leaf("leaf"), send: () => "middle got a message" }, + send: () => "root got a message", +}; +console.log(describe(tree)); +console.log(tree.send({ content: "hello" })); +const middle = tree.child; +if (middle !== undefined) console.log(middle.send({ content: "hi", details: 42 })); + +function cons(value: number, next: ListNode | null): ListNode { + return { value, next, map: () => `node ${value}` }; +} + +let list: ListNode | null = cons(1, cons(2, cons(3, null))); +const values: number[] = []; +let labels = ""; +while (list !== null) { + values.push(list.value); + labels += `[${list.map((v) => v * 2)}]`; + list = list.next; +} +console.log(values.join(","), labels); diff --git a/tests/harness/corpus-costs/recursive-generic-method-members.ts.json b/tests/harness/corpus-costs/recursive-generic-method-members.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/recursive-generic-method-members.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From ea599922a431abcc51210f7a07b2bc3a895721db Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:23:22 -0500 Subject: [PATCH 092/230] Iterate and read native arrays from untyped code without type-name scans Untyped code that loops over a native array (`for (const ch of checks)` in zod, over arrays the compiled code built) paid for the loop before its first element: - Every check of whether a typed capsule is a generator compared its type name against "async-generator<" with strncmp, and native array type names also start with 'a'. Iterator creation made several such checks. The test now looks at the second byte before comparing. - scr_dyn_iterator called the builtin values() through the general call path for a native array; it now creates the native iterator directly, as it already did for untyped arrays. - Read sites of `length` and `next` call scr_js_get_probe_native, which answers a native array's `length` and a native iterator's `next`, where the generic read built the array's checked view or looked the method up by name. Other read sites keep scr_js_get_probe, so their receivers that are not ordinary objects still miss without a further test. --- packages/compiler/src/backend/llvm/js-ic.ts | 8 +++-- packages/compiler/test/js-ic-emission.test.ts | 2 +- .../native-array-dynamic-reads/main.ts.json | 7 ++++ packages/runtime/src/scr_dyn.h | 3 ++ packages/runtime/src/scr_js.h | 4 +++ packages/runtime/src/scr_js_object.c | 5 +++ packages/runtime/src/scr_json.c | 31 +++++++++++++++--- .../corpus/native-array-dynamic-reads/main.ts | 32 +++++++++++++++++++ .../native-array-dynamic-reads/reads.js | 29 +++++++++++++++++ .../native-array-dynamic-reads/main.ts.json | 1 + 10 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-array-dynamic-reads/main.ts.json create mode 100644 tests/corpus/native-array-dynamic-reads/main.ts create mode 100644 tests/corpus/native-array-dynamic-reads/reads.js create mode 100644 tests/harness/corpus-costs/native-array-dynamic-reads/main.ts.json diff --git a/packages/compiler/src/backend/llvm/js-ic.ts b/packages/compiler/src/backend/llvm/js-ic.ts index 11b894a60..d51b838bb 100644 --- a/packages/compiler/src/backend/llvm/js-ic.ts +++ b/packages/compiler/src/backend/llvm/js-ic.ts @@ -406,9 +406,13 @@ function emitGet(host: Host, e: ExprOf<"call">, callee: IrFunction, key: string) inlineLoad(host, receiver.name, ic, INLINE_WAYS, INLINE_PROTO, (cell) => cell, hits, hit, probe); B.startBlock(probe); // The read's probe also answers keys the receiver's plain chain lacks. - host.declare(`declare ptr @scr_js_get_probe(ptr, ptr, ptr)`); + // Only `length` and `next` reads can be answered for a native array or + // iterator, so only those sites take the probe that tests them. + const probeFn = + key === "length" || key === "next" ? "scr_js_get_probe_native" : "scr_js_get_probe"; + host.declare(`declare ptr @${probeFn}(ptr, ptr, ptr)`); const probed = B.tmp(); - B.line(`${probed} = call ptr @scr_js_get_probe(ptr ${receiver.name}, ptr ${name}, ptr ${ic})`); + B.line(`${probed} = call ptr @${probeFn}(ptr ${receiver.name}, ptr ${name}, ptr ${ic})`); const found = B.tmp(); B.line(`${found} = icmp ne ptr ${probed}, null`); const probeEnd = B.currentLabel(); diff --git a/packages/compiler/test/js-ic-emission.test.ts b/packages/compiler/test/js-ic-emission.test.ts index 79342e99d..b350c743d 100644 --- a/packages/compiler/test/js-ic-emission.test.ts +++ b/packages/compiler/test/js-ic-emission.test.ts @@ -201,7 +201,7 @@ console.log(lib.total([1, 2, 3])); expect(fn).toContain("@scr_js_keyed_probe("); // The length read answers from the native array's length. expect(fn).toContain("icg.rest"); - expect(fn).toContain("@scr_js_get_probe("); + expect(fn).toContain("@scr_js_get_probe_native("); } finally { await rm(dir, { recursive: true, force: true }); } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-array-dynamic-reads/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-array-dynamic-reads/main.ts.json new file mode 100644 index 000000000..c3b3013ea --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-array-dynamic-reads/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/native-array-dynamic-reads/reads.js", + "/tests/corpus/native-array-dynamic-reads/main.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 656f2b994..bc20be862 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1955,6 +1955,9 @@ long scr_dyn_live_count(void); * scr_json.c, which programs with dynamic-tier code link. */ /* The builtin Object.prototype node, or NULL before its first use. */ const ScrDyn *scr_dyn_tier_object_prototype(void); +/* A read site's answer for a native array's length or a native iterator's + * next (scr_js_get_probe_native): +1, or NULL. */ +ScrDyn *scr_dyn_tier_unshaped_get(const ScrDyn *receiver, const ScrStr *key); /* Shared key bytes for `key` (+1, NUL-terminated), and their release. */ char *scr_dyn_tier_key_copy(const char *key, size_t key_len); void scr_dyn_tier_key_release(char *key, size_t key_len); diff --git a/packages/runtime/src/scr_js.h b/packages/runtime/src/scr_js.h index 3ff9becd3..5abab2053 100644 --- a/packages/runtime/src/scr_js.h +++ b/packages/runtime/src/scr_js.h @@ -235,6 +235,10 @@ ScrDyn *scr_js_load_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic); * a key that the receiver and its plain prototype chain lack, and caches * that answer. A method read uses scr_js_load_probe. */ ScrDyn *scr_js_get_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic); +/* scr_js_get_probe for a site whose key is `length` or `next`, which also + * answers a native array's length and a native iterator's next + * (scr_dyn_tier_unshaped_get). */ +ScrDyn *scr_js_get_probe_native(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic); /* `receiver.key = value` (value borrowed) when it replaces a writable own * data property or adds one the prototype chain lacks: true when done. */ bool scr_js_store_probe(ScrDyn *receiver, const ScrStr *key, ScrDyn *value, ScrJsIc *ic); diff --git a/packages/runtime/src/scr_js_object.c b/packages/runtime/src/scr_js_object.c index e3d0b3a54..4cee9e7e8 100644 --- a/packages/runtime/src/scr_js_object.c +++ b/packages/runtime/src/scr_js_object.c @@ -407,6 +407,11 @@ ScrDyn *scr_js_get_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { return scr_js_probe_load(receiver, key, ic, true); } +ScrDyn *scr_js_get_probe_native(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) { + if (receiver->kind != SCR_DYN_OBJ) return scr_dyn_tier_unshaped_get(receiver, key); + return scr_js_probe_load(receiver, key, ic, true); +} + /* ── stores ───────────────────────────────────────────────────────────── */ /* The generic store keeps a native Error's fields in sync with these keys diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cd9eeecde..69339b0ca 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -96,9 +96,13 @@ static SCR_TL ScrDyn *scr_iterator_symbol; static SCR_TL ScrDyn *scr_async_iterator_symbol; static SCR_TL ScrDyn *scr_to_primitive_symbol; static bool scr_dyn_generator(const ScrDyn *value) { - return value && value->kind == SCR_DYN_TYPED_REF && - (value->v.typed_ref.type_key[0] == 'g' || value->v.typed_ref.type_key[0] == 'a') && - (!strncmp(value->v.typed_ref.type_key, "generator<", 10) || !strncmp(value->v.typed_ref.type_key, "async-generator<", 16)); + if (!value || value->kind != SCR_DYN_TYPED_REF) return false; + /* Array keys also start with 'a': the second byte sets them apart before + * any comparison. */ + const char *key = value->v.typed_ref.type_key; + size_t len = value->v.typed_ref.type_key_len; + if (key[0] == 'g') return len >= 10 && !memcmp(key, "generator<", 10); + return key[0] == 'a' && key[1] == 's' && len >= 16 && !memcmp(key, "async-generator<", 16); } static bool scr_dyn_generator_symbol(const ScrDyn *value, const ScrDyn *key) { const ScrDyn *symbol = value->v.typed_ref.type_key[0] == 'a' ? scr_async_iterator_symbol : scr_iterator_symbol; @@ -7494,6 +7498,23 @@ static ScrDyn *scr_native_iterator_new(ScrDyn *source) { return result; } +#ifdef SCR_JS_TIER +/* A read site's answer for a receiver without a JS shape whose property is + * plain data the runtime holds directly: a native array's length (the + * generic read would build its checked view first) and a native iterator's + * `next` (the builtin the generic read returns). +1, or NULL for the slow + * path. */ +ScrDyn *scr_dyn_tier_unshaped_get(const ScrDyn *receiver, const ScrStr *key) { + if (key->len == 6 && receiver->kind == SCR_DYN_TYPED_REF && !memcmp(key->data, "length", 6) && + scr_dyn_typed_ref_array_ops(receiver) && scr_dyn_ref_is_array(receiver)) + return scr_dyn_new_num((double)((ScrArr *)receiver->v.typed_ref.ptr)->len); + if (key->len == 4 && receiver->kind == SCR_DYN_HANDLE && + receiver->v.handle.tag == SCR_DYNH_ITERATOR && !memcmp(key->data, "next", 4)) + return scr_native_iterator_get(receiver->v.handle.ptr, key->data, key->len); + return NULL; +} +#endif + static ScrDyn *scr_native_collection_iterator(ScrDyn *source, unsigned selection) { ScrDyn *result = scr_native_iterator_new(source); ((ScrNativeIterator *)result->v.handle.ptr)->selection = selection; @@ -8384,7 +8405,9 @@ ScrDyn *scr_dyn_iterator(const ScrDyn *source, const ScrStr *spell) { } /* An array whose iterator method is the builtin values(): what calling * it with the array as `this` returns, without the call. */ - if (source->kind == SCR_DYN_ARR && method == scr_iterator_functions[1]) { + if (method == scr_iterator_functions[1] && + (source->kind == SCR_DYN_ARR || + (source->kind == SCR_DYN_TYPED_REF && scr_dyn_ref_is_array(source)))) { scr_dyn_release(method); return scr_native_iterator_new((ScrDyn *)source); } diff --git a/tests/corpus/native-array-dynamic-reads/main.ts b/tests/corpus/native-array-dynamic-reads/main.ts new file mode 100644 index 000000000..0641114bf --- /dev/null +++ b/tests/corpus/native-array-dynamic-reads/main.ts @@ -0,0 +1,32 @@ +// Native arrays and iterators from typed code, read by untyped code: for-of +// over them, their length, an iterator's next read as a value and called, +// generators and async generators beside them, and an array whose +// iteration a Symbol.iterator use elsewhere in the program does not change. +import { asyncKinds, describe, lengths, sumAll, stepper } from "./reads.js"; + +interface Item { + sku: string; + qty: number; +} +const numbers: number[] = [1, 2, 3, 4]; +const items: Item[] = [ + { sku: "a", qty: 2 }, + { sku: "b", qty: 5 }, +]; +const empty: string[] = []; +function* counting(): Generator { + yield 10; + yield 20; +} +console.log(sumAll(numbers), sumAll(items.map((i) => i.qty)), sumAll(empty.map(Number))); +console.log(lengths([numbers, items, empty]).join()); +numbers.push(5); +console.log(lengths([numbers]).join(), sumAll(numbers)); +console.log(describe(items), describe(counting())); +console.log(stepper(numbers).join(), stepper(counting()).join()); +const iterable = { *[Symbol.iterator]() { yield "own"; } }; +console.log([...iterable].join(), sumAll(numbers)); +async function* later(): AsyncGenerator { + yield 1; +} +asyncKinds(later(), numbers).then((kinds) => console.log(kinds.join())); diff --git a/tests/corpus/native-array-dynamic-reads/reads.js b/tests/corpus/native-array-dynamic-reads/reads.js new file mode 100644 index 000000000..67e1a824a --- /dev/null +++ b/tests/corpus/native-array-dynamic-reads/reads.js @@ -0,0 +1,29 @@ +export function sumAll(values) { + let total = 0; + for (const value of values) total += value; + return total; +} +export function lengths(arrays) { + const out = []; + for (const array of arrays) out.push(array.length); + return out; +} +export function describe(values) { + const parts = []; + for (const value of values) parts.push(typeof value === "object" ? value.sku : value); + return parts.join("+"); +} +export function stepper(values) { + const iterator = values[Symbol.iterator](); + const next = iterator.next; + const out = []; + for (let step = next.call(iterator); !step.done; step = next.call(iterator)) out.push(step.value); + out.push(typeof iterator.next, iterator.next === next); + return out; +} +export async function asyncKinds(generator, array) { + const out = []; + for await (const value of generator) out.push(value); + for (const value of array) out.push(value); + return out; +} diff --git a/tests/harness/corpus-costs/native-array-dynamic-reads/main.ts.json b/tests/harness/corpus-costs/native-array-dynamic-reads/main.ts.json new file mode 100644 index 000000000..ac2ef5ae8 --- /dev/null +++ b/tests/harness/corpus-costs/native-array-dynamic-reads/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3567, "san": 6402 } From 15a183026f1874aeca4c9cadb6220224600d7a61 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:27:58 -0500 Subject: [PATCH 093/230] Allocate the shared two-byte characters from the system allocator The one-character strings of U+0080..U+07FF that string iteration shares between threads were allocated from the calling thread's memory. On Linux a worker that exits without sharing anything returns that memory, so the table kept strings that a later read found discarded. They now come from the system allocator, as other state that outlives a thread does. --- ...string-iteration-worker-characters.ts.json | 6 +++ packages/runtime/src/scr_string.c | 15 ++++--- ...8348-string-iteration-worker-characters.ts | 44 +++++++++++++++++++ ...string-iteration-worker-characters.ts.json | 1 + .../harness/string-iteration-workers.test.ts | 44 +++++++++++++++++++ 5 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8348-string-iteration-worker-characters.ts.json create mode 100644 tests/corpus/8348-string-iteration-worker-characters.ts create mode 100644 tests/harness/corpus-costs/8348-string-iteration-worker-characters.ts.json create mode 100644 tests/harness/string-iteration-workers.test.ts diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8348-string-iteration-worker-characters.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8348-string-iteration-worker-characters.ts.json new file mode 100644 index 000000000..e767eaf65 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8348-string-iteration-worker-characters.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8348-string-iteration-worker-characters.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index cd17da0f8..64a4772e5 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -3538,7 +3538,11 @@ ScrStr *scr_str_cp_at(ScrStr *s, double i) { * thread, as the ASCII ones are static: the string iterator returns them * instead of a new string per character. A string is immortal before its * slot publishes it; a thread that loses the race to fill a slot keeps its - * own immortal copy, so at most one extra string per slot and thread. */ + * own immortal copy, so at most one extra string per slot and thread. The + * strings come from the system allocator, as other state that outlives a + * thread does: a worker's own memory goes back when it exits without + * sharing (scr_alloc.c), and the table outlives the worker that filled a + * slot. */ static ScrStr *scr_two_byte_chars[0x800 - 0x80]; static ScrStr *scr_two_byte_char(const char *bytes) { @@ -3546,12 +3550,13 @@ static ScrStr *scr_two_byte_char(const char *bytes) { ScrStr **slot = &scr_two_byte_chars[cp - 0x80]; ScrStr *known = __atomic_load_n(slot, __ATOMIC_ACQUIRE); if (known) return known; - ScrStr *made = scr_str_from_span(bytes, 2); + ScrStr *made = calloc(1, sizeof(ScrStr) + 3); + if (!made) scr_oom(); + made->len = 2; + made->cap = 2; + memcpy(made->data, bytes, 2); scr_str_hash_prime(made); made->rc = SIZE_MAX; -#ifdef SCR_RC_AUDIT - scr_str_live_forget(); -#endif scr_lsan_immortal(made); ScrStr *expected = NULL; if (__atomic_compare_exchange_n(slot, &expected, made, false, __ATOMIC_ACQ_REL, diff --git a/tests/corpus/8348-string-iteration-worker-characters.ts b/tests/corpus/8348-string-iteration-worker-characters.ts new file mode 100644 index 000000000..52a38fb78 --- /dev/null +++ b/tests/corpus/8348-string-iteration-worker-characters.ts @@ -0,0 +1,44 @@ +import { Worker, isMainThread, parentPort } from "node:worker_threads"; + +// The one-character strings of U+0080..U+07FF that string iteration returns +// are shared by every thread. A worker that is the first to iterate them +// and exits without sharing anything else leaves them in use: the main +// thread iterates the same characters after the worker's memory goes back. + +const text = "aé ñ ß ü Ω ж א ع ߿"; + +function codes(s: string): string { + const out: string[] = []; + for (const c of s) out.push(`${c.length}:${c.charCodeAt(0).toString(16)}`); + return out.join(" "); +} + +function runWorker(): Promise { + return new Promise((resolve, reject) => { + const worker = new Worker(new URL(import.meta.url)); + let count = -1; + worker.on("message", (value: number) => { + count = value; + }); + worker.on("error", reject); + worker.on("exit", () => resolve(count)); + }); +} + +async function main(): Promise { + console.log("worker", await runWorker()); + // The worker's memory goes back shortly after it exits. + await new Promise((resolve) => setTimeout(resolve, 300)); + console.log("main", codes(text)); + console.log("spread", [...text].join("|")); + console.log("from", Array.from("ñandú").map((c) => c.length).join(",")); +} + +if (isMainThread) { + void main(); +} else { + let count = 0; + for (const c of text) count += c.length; + count += [...text].length + Array.from("ñandú").length; + parentPort!.postMessage(count); +} diff --git a/tests/harness/corpus-costs/8348-string-iteration-worker-characters.ts.json b/tests/harness/corpus-costs/8348-string-iteration-worker-characters.ts.json new file mode 100644 index 000000000..4c96af583 --- /dev/null +++ b/tests/harness/corpus-costs/8348-string-iteration-worker-characters.ts.json @@ -0,0 +1 @@ +{ "plain": 3960, "san": 3900 } diff --git a/tests/harness/string-iteration-workers.test.ts b/tests/harness/string-iteration-workers.test.ts new file mode 100644 index 000000000..bc1feca95 --- /dev/null +++ b/tests/harness/string-iteration-workers.test.ts @@ -0,0 +1,44 @@ +/* The one-character strings that string iteration shares between threads + * outlive the worker that first made them. On Linux a worker that exits + * without sharing anything returns its memory; with SCR_SA_DISCARD=poison + * that memory becomes inaccessible instead, so a cached character + * allocated from the worker's memory would fault when the main thread + * reads it. The corpus program pins Node.js parity; this suite runs it + * with the discard poisoned and immediate. Under SCRIPTC_SAN=1 the runtime + * keeps every worker's memory, so the check does not apply. */ +import { execFile } from "node:child_process"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const exec = promisify(execFile); +const program = join(import.meta.dirname, "../corpus/8348-string-iteration-worker-characters.ts"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +test.skipIf(process.platform !== "linux" || sanitize)( + "characters a discarded worker cached stay readable", + { timeout: 240_000 }, + async () => { + const outDir = await mkdtemp(join(tmpdir(), "scriptc-string-iteration-workers-")); + try { + const result = await compile(program, { + backend: "llvm", + outDir, + outPath: join(outDir, "program"), + }); + expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true); + if (!result.ok) return; + const { stdout } = await exec(result.binaryPath, [], { + env: { ...process.env, SCR_SA_DISCARD: "poison", SCR_SA_DISCARD_DELAY_MS: "0" }, + timeout: 120_000, + }); + expect(stdout).toContain("main 1:61 1:e9 1:20 1:f1"); + expect(stdout).toContain("spread a|é| |ñ"); + } finally { + await rm(outDir, { recursive: true, force: true }); + } + }, +); From dec6be6ebddb41f1996ba7e71264bbc224dc07af Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:35:04 -0500 Subject: [PATCH 094/230] Run global substitutions with a literal replacement through build-time replacers A global replace or replaceAll of a regex literal called the compiled matcher once per match: each call set up its frame and seek again, and the runtime then copied the gap and the replacement. A substitution of a literal inside the compiled subset with an ASCII replacement without $ patterns now gets a replacer built for that pattern and replacement. It appends each gap and the replacement to the output and continues the search after the match, as the runtime's loop does (one past an empty match), without returning. Gaps of up to 16 bytes are one 16-byte copy, and the search after a match is a 16-byte scan instead of a memchr call. scr_regex_replace_with runs the replacer on an ASCII subject; a subject outside ASCII keeps the runtime's loop. Plain-text patterns, which the runtime searches as strings, get no replacer. New corpus program: 8351-regex-literal-replacers.ts. --- .../compiler/src/backend/llvm/emit-module.ts | 18 ++ packages/compiler/src/backend/llvm/emitter.ts | 8 + .../src/backend/llvm/expr-primitives.ts | 23 +++ .../compiler/src/backend/llvm/expr-records.ts | 45 +++- .../src/backend/llvm/regex-matchers.test.ts | 44 ++++ .../src/backend/llvm/regex-matchers.ts | 194 ++++++++++++++++-- .../compiler/test/regex-replacers.test.ts | 42 ++++ .../8351-regex-literal-replacers.ts.json | 6 + packages/runtime/src/scr_regex.c | 45 ++++ packages/runtime/src/scr_regex.h | 16 ++ tests/corpus/8351-regex-literal-replacers.ts | 72 +++++++ .../8351-regex-literal-replacers.ts.json | 1 + 12 files changed, 491 insertions(+), 23 deletions(-) create mode 100644 packages/compiler/test/regex-replacers.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8351-regex-literal-replacers.ts.json create mode 100644 tests/corpus/8351-regex-literal-replacers.ts create mode 100644 tests/harness/corpus-costs/8351-regex-literal-replacers.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index ee9089071..af00ebc2c 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -600,6 +600,23 @@ export function emitModuleParts(emitter: LlEmitter): string[] { regexMatchers.set(key, code); for (const decl of code.decls) emitter.declare(decl); } + // Replacers of global substitutions with a literal replacement; the call + // sites register only patterns inside the compiled subset. + const regexReplacers: RegexMatcherCode[] = []; + for (const rr of emitter.regexReplacers.values()) { + const code = compileRegexMatcher( + rr.sym, + rr.pattern, + rr.flags, + emitter.sizeType, + !emitter.wasi, + rr.replacement, + ); + if (code === null) + throw new InternalCompilerError(`llvm emitter bug: replacer ${rr.sym} did not compile`); + regexReplacers.push(code); + for (const decl of code.decls) emitter.declare(decl); + } const out: string[] = [ `; Generated by scriptc (LLVM backend) from ${emitter.mod.sourceFile}. Do not edit.`, @@ -882,6 +899,7 @@ export function emitModuleParts(emitter: LlEmitter): string[] { } if (emitter.regexInstances.size > 0) out.push(``); for (const code of regexMatchers.values()) out.push(...code.lines, ``); + for (const code of regexReplacers) out.push(...code.lines, ``); for (const [, inst] of emitter.templateStringsInstances) { // One immortal ScrArr per tagged-template site: a data global of // interned cooked-string literals, and the ScrArr header over it diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 1a2c8bef8..d15a8c017 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1303,6 +1303,12 @@ export class LlEmitter { string, { sym: string; src: string; fl: string; pattern: string; flags: string } >(); + /** Build-time replacers: one per (pattern, flags, replacement) of a + * global substitution with a literal replacement (regex-matchers.ts). */ + readonly regexReplacers = new Map< + string, + { sym: string; pattern: string; flags: string; replacement: string } + >(); /** Interned tagged-template strings objects: per-site key → { symbol, * interned cooked-literal refs }. One immortal ScrArr of string slots * per template SITE (the spec's per-occurrence identity — the C @@ -5439,6 +5445,8 @@ export class LlEmitter { return addNumbered(this.threadSlotsBlocks, key, value as string, "@sc_slots_", names); case "regex": return addNumberedSymbol(this.regexInstances, key, value as never, "sc_re_", names); + case "regexReplacer": + return addNumberedSymbol(this.regexReplacers, key, value as never, "sc_rr_", names); case "templateStrings": return addNumberedSymbol( this.templateStringsInstances, diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index d3b72897e..eed138fe2 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -661,6 +661,29 @@ export function regexLiteralTemplate( return `@${re.sym}`; } +/** The build-time replacer of a global substitution of regex literal `e` + * with the literal `replacement` (regex-matchers.ts), registered on first + * use. */ +export function regexLiteralReplacer( + host: LlvmEmitterContext, + e: IrExpr & { kind: "regexLit" }, + replacement: string, +): string { + const key = `${e.flags}/${e.pattern}\0${replacement}`; + let rr = host.regexReplacers.get(key); + if (!rr) { + rr = { + sym: `sc_rr_${host.regexReplacers.size}`, + pattern: e.pattern, + flags: e.flags, + replacement, + }; + host.regexReplacers.set(key, rr); + } + host.journal?.entry("regexReplacer", key, rr); + return `@${rr.sym}`; +} + /** A string equality or comparison between `s.charAt(i)` and a literal of * exactly one UTF-16 code unit: the charAt call, the unit, and whether the * charAt is the left operand. */ diff --git a/packages/compiler/src/backend/llvm/expr-records.ts b/packages/compiler/src/backend/llvm/expr-records.ts index 59ab625e5..51688eafa 100644 --- a/packages/compiler/src/backend/llvm/expr-records.ts +++ b/packages/compiler/src/backend/llvm/expr-records.ts @@ -6,7 +6,8 @@ import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext, LlValue } from "./expr-context.js"; import { f64Lit, llvmCommentText } from "./common.js"; import { borrowableInputs, emitBorrowedInputs, emitBorrowedInput } from "./borrowed-inputs.js"; -import { regexLiteralTemplate } from "./expr-primitives.js"; +import { regexLiteralReplacer, regexLiteralTemplate } from "./expr-primitives.js"; +import { canReplaceWith, regexLiteralCompiles } from "./regex-matchers.js"; import { isScalarUnionDef, loadInlineUnion, presentBits } from "./inline-unions.js"; import { preservesRegexInputs } from "./reference-effects.js"; @@ -160,6 +161,39 @@ function directRegexOperandMethod(method: string): boolean { } } +/** The build-time replacer of a substitution: a regex literal running on + * its template, with a literal replacement the replacer can write as is. + * Null when the substitution takes the runtime's loop. */ +function literalReplacer( + host: LlvmEmitterContext, + literal: IrExpr | null, + template: string | null, + replacement: IrExpr, +): string | null { + if (literal === null || template === null || literal.kind !== "regexLit") return null; + if (replacement.kind !== "strLit") return null; + if (!canReplaceWith(literal.pattern, literal.flags, replacement.value)) return null; + if (!regexLiteralCompiles(literal.pattern, literal.flags)) return null; + return regexLiteralReplacer(host, literal, replacement.value); +} + +/** scr_regex_replace_with: the runtime runs `replacer` on an ASCII subject + * and its own loop on any other. */ +function emitReplaceWith( + host: LlvmEmitterContext, + e: IrExpr, + r: LlValue, + args: LlValue[], + replacer: string, +): LlValue { + host.declare(`declare ptr @scr_regex_replace_with(ptr, ptr, ptr, ptr)`); + const t = host.B.tmp(); + host.B.line( + `${t} = call ptr @scr_regex_replace_with(ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${replacer})`, + ); + return host.own({ name: t, type: e.type }); +} + export function emitRegexIntrinsic( host: LlvmEmitterContext, e: IrExpr & { kind: "regexIntrinsic" }, @@ -234,11 +268,16 @@ export function emitRegexIntrinsic( "scr_regex_match_all_captures_into", `ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}`, ); - case "replaceAll": + case "replaceAll": { + // A global literal (the only kind a replacer is built for) never takes + // replaceAll's non-global TypeError. + const replacer = literalReplacer(host, literal, template, e.args[1]!); + if (replacer !== null) return emitReplaceWith(host, e, r, args, replacer); return fallible( "scr_regex_replace_all", `ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}`, ); + } case "split": host.declare(`declare ptr @scr_regex_split_limit(ptr, ptr, double)`); { @@ -323,6 +362,8 @@ export function emitRegexIntrinsic( return host.own({ name: t, type: e.type }); } case "replace": { + const replacer = literalReplacer(host, literal, template, e.args[1]!); + if (replacer !== null) return emitReplaceWith(host, e, r, args, replacer); host.declare(`declare ptr @scr_regex_replace(ptr, ptr, ptr)`); const t = B.tmp(); B.line( diff --git a/packages/compiler/src/backend/llvm/regex-matchers.test.ts b/packages/compiler/src/backend/llvm/regex-matchers.test.ts index d009de7c9..5f93f7295 100644 --- a/packages/compiler/src/backend/llvm/regex-matchers.test.ts +++ b/packages/compiler/src/backend/llvm/regex-matchers.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from "vitest"; import { + canReplaceWith, compileRegexMatcher, regexLiteralBailReason, regexLiteralCaptures, @@ -152,3 +153,46 @@ describe("regex literal matcher code", () => { expect(text).not.toContain("constant [128 x i8]"); }); }); + +describe("regex literal replacers", () => { + test("take global, non-sticky, non-plain-text patterns and ASCII replacements without $", () => { + expect(canReplaceWith("a[bc]", "g", "<2>")).toBe(true); + expect(canReplaceWith("a[bc]", "gi", "")).toBe(true); + expect(canReplaceWith("a[bc]", "", "x")).toBe(false); + expect(canReplaceWith("a[bc]", "gy", "x")).toBe(false); + expect(canReplaceWith("a[bc]", "g", "$1")).toBe(false); + expect(canReplaceWith("a[bc]", "g", "é")).toBe(false); + // Plain text is a string search at run time. + expect(canReplaceWith("&", "g", "&")).toBe(false); + expect(canReplaceWith("a\\.b", "g", "x")).toBe(false); + // Letters under i are not plain text. + expect(canReplaceWith("ab", "gi", "x")).toBe(true); + }); + + test("append the gap and the replacement and continue the search", () => { + const code = compileRegexMatcher("r", "<[^>]*>", "g", "i64", true, "|"); + const text = code!.lines.join("\n"); + expect(text).toContain('@r.rep = private unnamed_addr constant [1 x i8] c"|"'); + expect(text).toContain("call void @scr_re_replace_out_reserve(ptr %out, i64 %m.need)"); + expect(text).toContain("load <16 x i8>, ptr %m.src, align 1"); + expect(text).toMatch(/br i1 %m\.past, label %nomatch, label %scan/); + expect(text).not.toContain("m.ret:"); + expect(text).toContain("ret i32 %n.found"); + // The search after a match is a 16-byte scan, not a memchr call. + expect(text).not.toContain("@memchr"); + expect(code!.decls).toContain("declare void @scr_re_replace_out_reserve(ptr, i64)"); + // Without vectors the gap is a memcpy. + const scalar = compileRegexMatcher("r", "<[^>]*>", "g", "i64", false, "|")!.lines.join("\n"); + expect(scalar).not.toContain("<16 x i8>"); + expect(scalar).toContain( + "call void @llvm.memcpy.p0.p0.i64(ptr %m.dst, ptr %m.src, i64 %m.gapw, i1 false)", + ); + }); + + test("escape the replacement bytes and skip the copy of an empty one", () => { + const quoted = compileRegexMatcher("r", "a[bc]", "g", "i64", true, '"\\\n')!.lines.join("\n"); + expect(quoted).toContain('c"\\22\\5C\\0A"'); + const empty = compileRegexMatcher("r", "a[bc]", "g", "i64", true, "")!.lines.join("\n"); + expect(empty).not.toContain("@r.rep"); + }); +}); diff --git a/packages/compiler/src/backend/llvm/regex-matchers.ts b/packages/compiler/src/backend/llvm/regex-matchers.ts index d83d6bae5..0491a6446 100644 --- a/packages/compiler/src/backend/llvm/regex-matchers.ts +++ b/packages/compiler/src/backend/llvm/regex-matchers.ts @@ -24,6 +24,8 @@ * `len`, it returns the capture count (whole match included) so the runtime * can check it against the compiled bytecode. */ +import { InternalCompilerError } from "../../errors.js"; + const ASCII = 128; const INF = -1; /** Larger patterns keep the interpreter (binary size). */ @@ -1853,10 +1855,13 @@ export function compileRegexMatcher( flags: string, sizeType: string, vector: boolean, + replacement: string | null = null, ): RegexMatcherCode | null { const parsed = parseSubset(pattern, flags); if (parsed === null) return null; const sticky = flags.includes("y"); + if (replacement !== null && !canReplaceWith(pattern, flags, replacement)) + throw new InternalCompilerError(`llvm emitter bug: no replacer for /${pattern}/${flags}`); const { root, ncap } = parsed; const gen = new RxGen(sym, ncap, vector); const end: FirstInfo = { set: emptySet(), nullable: true, bol: false, uncond: true }; @@ -1873,6 +1878,12 @@ export function compileRegexMatcher( fn.push(` %stk = alloca ptr`); fn.push(` %sbuf = alloca [64 x i32]`); for (let r = 0; r < gen.regs; r++) fn.push(` %r${r} = alloca i32`); + if (replacement !== null) { + fn.push(` %next = alloca i32`); + fn.push(` %nfound = alloca i32`); + fn.push(` store i32 0, ptr %next`); + fn.push(` store i32 0, ptr %nfound`); + } fn.push(` store ptr %sbuf, ptr %stk`); fn.push(` store i32 64, ptr %scap`); fn.push(` store i32 %start, ptr %at`); @@ -1900,7 +1911,9 @@ export function compileRegexMatcher( } else { fn.push(` br label %seek`); } - if (firstCount === 1) { + // A replacer seeks again after every match, often a few bytes on: the + // 16-byte scan avoids a memchr call per match. + if (firstCount === 1 && replacement === null) { fn.push(`seek:`); const byte = intervals(first.set)[0]![0]!; fn.push(` %rest = sub i32 %len, %a`); @@ -1944,6 +1957,98 @@ export function compileRegexMatcher( fn.push(` br i1 %over, label %nomatch, label %scan`); } + if (replacement !== null) { + emitReplaceMatch(fn, sym, replacement, vector); + } else { + emitReturnMatch(fn, ncap); + } + + const result = replacement !== null ? "%n.found" : "0"; + fn.push(`nomatch:`); + if (replacement !== null) { + fn.push(` %n.next = load i32, ptr %next`); + fn.push(` %n.nextp = getelementptr inbounds i8, ptr %out, i64 16`); + fn.push(` %n.nextw = sext i32 %n.next to i64`); + fn.push(` store i64 %n.nextw, ptr %n.nextp`); + fn.push(` %n.found = load i32, ptr %nfound`); + } + fn.push(` %n.stk = load ptr, ptr %stk`); + fn.push(` %n.heap = icmp ne ptr %n.stk, %sbuf`); + fn.push(` br i1 %n.heap, label %n.free, label %n.ret`); + fn.push(`n.free:`); + fn.push(` call void @scr_regex_stack_free(ptr %n.stk)`); + fn.push(` br label %n.ret`); + fn.push(`n.ret:`); + fn.push(` ret i32 ${result}`); + + gen.emitTail(); + for (const l of gen.body) fn.push(l); + fn.push(`}`); + + const decls = REGEX_MATCHER_DECLS.slice(); + if (gen.body.some((l) => l.includes("@llvm.cttz.i16"))) + decls.push(`declare i16 @llvm.cttz.i16(i16, i1 immarg)`); + if (!sticky && !first.nullable && firstCount === 1 && replacement === null) + decls.push(`declare ptr @memchr(ptr, i32, ${sizeType})`); + if (replacement !== null) { + decls.push(`declare void @scr_re_replace_out_reserve(ptr, i64)`); + decls.push(`declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)`); + } + const globals = [...gen.globals]; + if (replacement !== null && replacement.length > 0) + globals.push( + `@${sym}.rep = private unnamed_addr constant [${replacement.length} x i8] c"${llvmBytes(replacement)}"`, + ); + return { lines: [...globals, ...fn], decls }; +} + +/** Whether a substitution can run through a build-time replacer: a global, + * non-sticky pattern that is not plain text (the runtime searches those as + * strings) and an ASCII replacement without `$` patterns. */ +export function canReplaceWith(pattern: string, flags: string, replacement: string): boolean { + if (!flags.includes("g") || flags.includes("y") || isPlainTextPattern(pattern, flags)) + return false; + for (let i = 0; i < replacement.length; i++) { + const c = replacement.charCodeAt(i); + if (c >= ASCII || c === 0x24) return false; + } + return true; +} + +/** scr_regex_plain_text's rule: a pattern of literal characters and escaped + * metacharacters (no letters under `i`) that the runtime searches as text. */ +function isPlainTextPattern(pattern: string, flags: string): boolean { + const fold = flags.includes("i"); + if (pattern.length === 0 || pattern.length > 256) return false; + for (let i = 0; i < pattern.length; i++) { + let c = pattern[i]!; + if (c === "\\") { + if (i + 1 >= pattern.length) return false; + c = pattern[++i]!; + if (!"^$\\.*+?()[]{}|/".includes(c)) return false; + } else { + const code = c.charCodeAt(0); + if (code < 0x20 || code >= 0x7f || "^$.*+?()[]{}|".includes(c)) return false; + } + if (fold && /[a-z]/i.test(c)) return false; + } + return true; +} + +function llvmBytes(text: string): string { + let out = ""; + for (let i = 0; i < text.length; i++) { + const c = text.charCodeAt(i); + out += + c >= 0x20 && c < 0x7f && c !== 0x22 && c !== 0x5c + ? text[i] + : `\\${c.toString(16).toUpperCase().padStart(2, "0")}`; + } + return out; +} + +/** The match block of a matcher: store the capture pointers, return 1. */ +function emitReturnMatch(fn: string[], ncap: number): void { fn.push(`match:`); fn.push(` %m.end = load i32, ptr %pos`); fn.push(` %m.start = load i32, ptr %at`); @@ -1969,25 +2074,72 @@ export function compileRegexMatcher( fn.push(` br label %m.ret`); fn.push(`m.ret:`); fn.push(` ret i32 1`); +} - fn.push(`nomatch:`); - fn.push(` %n.stk = load ptr, ptr %stk`); - fn.push(` %n.heap = icmp ne ptr %n.stk, %sbuf`); - fn.push(` br i1 %n.heap, label %n.free, label %n.ret`); - fn.push(`n.free:`); - fn.push(` call void @scr_regex_stack_free(ptr %n.stk)`); - fn.push(` br label %n.ret`); - fn.push(`n.ret:`); - fn.push(` ret i32 0`); - - gen.emitTail(); - for (const l of gen.body) fn.push(l); - fn.push(`}`); - - const decls = REGEX_MATCHER_DECLS.slice(); - if (gen.body.some((l) => l.includes("@llvm.cttz.i16"))) - decls.push(`declare i16 @llvm.cttz.i16(i16, i1 immarg)`); - if (!sticky && !first.nullable && firstCount === 1) - decls.push(`declare ptr @memchr(ptr, i32, ${sizeType})`); - return { lines: [...gen.globals, ...fn], decls }; +/** The match block of a replacer: append the gap since the previous match + * and the replacement to the output (ScrReReplaceOut: len, cap, next at + * 0, 8 and 16, then data), and continue the search at the match end, or one past it for an + * empty match, as the runtime's replace loop does. */ +function emitReplaceMatch(fn: string[], sym: string, replacement: string, vector: boolean): void { + const n = replacement.length; + fn.push(`match:`); + fn.push(` %m.end = load i32, ptr %pos`); + fn.push(` %m.start = load i32, ptr %at`); + fn.push(` %m.next = load i32, ptr %next`); + fn.push(` %m.gap = sub i32 %m.start, %m.next`); + fn.push(` %m.gapw = zext i32 %m.gap to i64`); + fn.push(` %m.lenp = getelementptr inbounds i8, ptr %out, i64 0`); + fn.push(` %m.capp = getelementptr inbounds i8, ptr %out, i64 8`); + fn.push(` %m.datap = getelementptr inbounds i8, ptr %out, i64 24`); + fn.push(` %m.len0 = load i64, ptr %m.lenp`); + fn.push(` %m.cap = load i64, ptr %m.capp`); + fn.push(` %m.room = sub i64 %m.cap, %m.len0`); + // 16 bytes of slack past the gap and the replacement for the 16-byte copy. + fn.push(` %m.need = add i64 %m.gapw, ${n + 32}`); + fn.push(` %m.fits = icmp uge i64 %m.room, %m.need`); + fn.push(` br i1 %m.fits, label %m.put, label %m.grow`); + fn.push(`m.grow:`); + fn.push(` call void @scr_re_replace_out_reserve(ptr %out, i64 %m.need)`); + fn.push(` br label %m.put`); + fn.push(`m.put:`); + fn.push(` %m.data = load ptr, ptr %m.datap`); + fn.push(` %m.len = load i64, ptr %m.lenp`); + fn.push(` %m.dst = getelementptr inbounds i8, ptr %m.data, i64 %m.len`); + fn.push(` %m.nextw = sext i32 %m.next to i64`); + fn.push(` %m.src = getelementptr inbounds i8, ptr %s, i64 %m.nextw`); + if (vector) { + // A gap of at most 16 bytes with 16 readable subject bytes is one + // unaligned 16-byte copy. + fn.push(` %m.small = icmp ule i32 %m.gap, 16`); + fn.push(` %m.rest = sub i32 %len, %m.next`); + fn.push(` %m.readable = icmp sge i32 %m.rest, 16`); + fn.push(` %m.fast = and i1 %m.small, %m.readable`); + fn.push(` br i1 %m.fast, label %m.copy16, label %m.copy`); + fn.push(`m.copy16:`); + fn.push(` %m.v = load <16 x i8>, ptr %m.src, align 1`); + fn.push(` store <16 x i8> %m.v, ptr %m.dst, align 1`); + fn.push(` br label %m.rep`); + } else { + fn.push(` br label %m.copy`); + } + fn.push(`m.copy:`); + fn.push(` call void @llvm.memcpy.p0.p0.i64(ptr %m.dst, ptr %m.src, i64 %m.gapw, i1 false)`); + fn.push(` br label %m.rep`); + fn.push(`m.rep:`); + fn.push(` %m.rdst = getelementptr inbounds i8, ptr %m.dst, i64 %m.gapw`); + if (n > 0) + fn.push(` call void @llvm.memcpy.p0.p0.i64(ptr %m.rdst, ptr @${sym}.rep, i64 ${n}, i1 false)`); + fn.push(` %m.len1 = add i64 %m.len, %m.gapw`); + fn.push(` %m.len2 = add i64 %m.len1, ${n}`); + fn.push(` store i64 %m.len2, ptr %m.lenp`); + fn.push(` store i32 %m.end, ptr %next`); + fn.push(` %m.found0 = load i32, ptr %nfound`); + fn.push(` %m.found1 = add i32 %m.found0, 1`); + fn.push(` store i32 %m.found1, ptr %nfound`); + fn.push(` %m.empty = icmp eq i32 %m.end, %m.start`); + fn.push(` %m.after = add i32 %m.end, 1`); + fn.push(` %m.at = select i1 %m.empty, i32 %m.after, i32 %m.end`); + fn.push(` store i32 %m.at, ptr %at`); + fn.push(` %m.past = icmp sgt i32 %m.at, %len`); + fn.push(` br i1 %m.past, label %nomatch, label %scan`); } diff --git a/packages/compiler/test/regex-replacers.test.ts b/packages/compiler/test/regex-replacers.test.ts new file mode 100644 index 000000000..746279eb4 --- /dev/null +++ b/packages/compiler/test/regex-replacers.test.ts @@ -0,0 +1,42 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile } from "../src/index.js"; + +async function emitLlvm(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-regex-replacers-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ll"); + await writeFile(entry, source); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "llvm" }); + expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true); + return await readFile(outPath, "utf8"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +test("global substitutions of regex literals with literal replacements run a replacer", async () => { + const llvm = await emitLlvm(` +const s = process.argv[2] ?? "ac"; +const rep = process.argv[3] ?? "-"; +console.log( + s.replace(/<[^>]*>/g, "|"), + s.replaceAll(/a[bc]/g, "<2>"), + s.replace(/<[^>]*>/g, "|"), + s.replace(/<[^>]*>/g, "$&$&"), + s.replace(/<[^>]*>/, "|"), + s.replace(/<[^>]*>/gy, "|"), + s.replace(/<[^>]*>/g, rep), +); +`); + const replacers = llvm.match(/^define internal i32 @sc_rr_\d+\(/gm) ?? []; + // One replacer per (pattern, flags, replacement): the repeated site shares it. + expect(replacers).toHaveLength(2); + expect(llvm.match(/call ptr @scr_regex_replace_with\(/g)).toHaveLength(3); + // $ patterns, non-global and sticky patterns, and computed replacements + // keep the runtime's loop. + expect(llvm.match(/call ptr @scr_regex_replace\(/g)!.length).toBeGreaterThanOrEqual(4); +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8351-regex-literal-replacers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8351-regex-literal-replacers.ts.json new file mode 100644 index 000000000..be0149287 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8351-regex-literal-replacers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8351-regex-literal-replacers.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_regex.c b/packages/runtime/src/scr_regex.c index ab323c288..2a98e92c6 100644 --- a/packages/runtime/src/scr_regex.c +++ b/packages/runtime/src/scr_regex.c @@ -1416,6 +1416,51 @@ ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep) { return scr_replace_impl(s, re, rep); } +void scr_re_replace_out_reserve(ScrReReplaceOut *o, int64_t need) { + if (need < 0 || (uint64_t)need > SCR_STR_MAX_CAP - (uint64_t)o->len) scr_regex_oom(); + size_t cap = (size_t)o->cap * 2; + if (cap < (size_t)(o->len + need)) cap = (size_t)(o->len + need); + if (cap > SCR_STR_MAX_CAP) cap = SCR_STR_MAX_CAP; + o->str->len = (size_t)o->len; + o->str = scr_str_regrow(o->str, cap); + o->data = o->str->data; + o->cap = (int64_t)o->str->cap; +} + +/* A global substitution with a literal ASCII replacement through the + * build-time replacer of its (pattern, replacement) pair: on an ASCII + * subject the replacer writes each gap and the replacement into the output + * and continues the search after every match without returning. Every + * other subject, and a plain-text pattern (string search), takes + * scr_replace_impl. */ +ScrStr *scr_regex_replace_with(ScrStr *s, ScrRegex *re, ScrStr *rep, + ScrRegexMatcherFn replacer) { + (void)scr_regex_bc(re); + if (scr_regex_lit(re) || !scr_text_ascii(s)) return scr_replace_impl(s, re, rep); + if (s->len > (size_t)INT32_MAX - 1) scr_regex_oom(); + re->last_index = 0; + ScrReReplaceOut o; + o.str = scr_str_alloc_raw(0, s->len + 64); + o.data = o.str->data; + o.len = 0; + o.cap = (int64_t)o.str->cap; + o.next = 0; + int32_t n = replacer((const uint8_t *)s->data, (int32_t)s->len, 0, (uint8_t **)(void *)&o); + if (n == 0) { + o.str->len = 0; + scr_str_release(o.str); + return scr_str_retain(s); + } + size_t tail = s->len - (size_t)o.next; + if ((int64_t)(tail + 1) > o.cap - o.len) scr_re_replace_out_reserve(&o, (int64_t)tail + 1); + scr_copy_bytes(o.data + o.len, s->data + o.next, tail); + o.len += (int64_t)tail; + o.str->len = (size_t)o.len; + o.data[o.len] = '\0'; + scr_str_note_ascii(o.str); + return o.str; +} + typedef struct { ScrDyn *arguments; int start, end; diff --git a/packages/runtime/src/scr_regex.h b/packages/runtime/src/scr_regex.h index cb61e49f3..502a29266 100644 --- a/packages/runtime/src/scr_regex.h +++ b/packages/runtime/src/scr_regex.h @@ -80,6 +80,22 @@ ScrStr *scr_regex_flags(ScrRegex *re); /* +1 */ ScrStr *scr_regex_to_string(ScrRegex *re); /* +1 */ /* replace: first match without /g, every match with it. */ ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep); +/* The output of a build-time replacer (regex-matchers.ts): the replacer + * appends each gap and the replacement at data[len] after reserving room, + * and leaves in `next` the subject index the unmatched tail starts at. */ +typedef struct { + int64_t len; + int64_t cap; + int64_t next; + char *data; /* at offset 24 on every target */ + ScrStr *str; +} ScrReReplaceOut; +/* Grows o->str so that `need` more bytes fit at o->data[o->len]. */ +void scr_re_replace_out_reserve(ScrReReplaceOut *o, int64_t need); +/* A global substitution with a literal ASCII replacement, through the + * replacer built for that pattern and replacement. Borrows all. */ +ScrStr *scr_regex_replace_with(ScrStr *s, ScrRegex *re, ScrStr *rep, + ScrRegexMatcherFn replacer); /* replace/replaceAll with a replacer closure of `arity` (at most * SCR_REPLACER_MAX_ARITY) parameters. Parameter i receives what Node passes * at position i: the match and captures below the capture count n, the diff --git a/tests/corpus/8351-regex-literal-replacers.ts b/tests/corpus/8351-regex-literal-replacers.ts new file mode 100644 index 000000000..12968d4d9 --- /dev/null +++ b/tests/corpus/8351-regex-literal-replacers.ts @@ -0,0 +1,72 @@ +// Global substitutions of regex literals with literal replacements: matches +// at every offset around 16-character blocks and at the end of the +// subject, empty matches, alternation order, case-insensitive and +// multiline flags, word boundaries, replacements that are empty, longer +// than the match or hold quotes and backslashes, outputs that grow far past +// the subject, subjects without a match, and subjects outside ASCII (which +// take the general loop). replace and replaceAll. +const substitutions: [string, (s: string) => string][] = [ + ["<[^>]*>", (s) => s.replace(/<[^>]*>/g, "|")], + ["a[NSt]|BY", (s) => s.replace(/a[NSt]|BY/g, "<2>")], + ["tHa[Nt]", (s) => s.replace(/tHa[Nt]/g, "<4>")], + ["\\|[^|][^|]*\\|", (s) => s.replace(/\|[^|][^|]*\|/g, "-")], + ["x*", (s) => s.replace(/x*/g, "-")], + ["\\b", (s) => s.replace(/\b/g, "^")], + ["^ab", (s) => s.replace(/^ab/gm, "Z")], + ["ab|abc|b", (s) => s.replace(/ab|abc|b/g, "#")], + ["AB (i)", (s) => s.replace(/AB/gi, "")], + ["(a)(b)?", (s) => s.replace(/(a)(b)?/g, "q")], + ["\\d+", (s) => s.replace(/\d+/g, '"\\\n')], + ["[^a-z]", (s) => s.replace(/[^a-z]/g, "")], + ["a", (s) => s.replace(/a/g, "<<<
>>>")], + ["cat (replaceAll)", (s) => s.replaceAll(/cat/g, "dog")], + ["[aeiou]{2} (replaceAll)", (s) => s.replaceAll(/[aeiou]{2}/g, "~")], +]; + +function hash(text: string, h: number): number { + for (let i = 0; i < text.length; i++) h = (h * 31 + text.charCodeAt(i)) >>> 0; + return h; +} + +const filler = "qwertyuiop asdfghjkl zxcvbnm"; +const needles = ["<2>", "", "aN", "BY", "tHat", "|ab|", "||", "xx", "ab", "abc", "AB", "123", "cat", "aeio", "a"]; +let checksum = 0; +let lines = 0; +for (let length = 0; length <= 70; length += 7) { + for (const needle of needles) { + for (const offset of [0, 1, 14, 15, 16, 17, 31, 32, 33]) { + if (offset + needle.length > length) continue; + let subject = ""; + while (subject.length < length) subject += filler; + subject = subject.slice(0, offset) + needle + subject.slice(offset + needle.length, length); + for (const [name, sub] of substitutions) { + const out = sub(subject); + checksum = hash(out, checksum); + lines++; + if (length === 35 && (offset === 15 || offset === 16)) console.log(name, offset, JSON.stringify(out)); + } + } + } +} +console.log("lines", lines, "checksum", checksum); + +// Dense matches, matches that end the subject, and an output many times +// larger than the subject. +const dense = "<1><22><333>".repeat(40) + "tail"; +for (const [name, sub] of substitutions) console.log(name, JSON.stringify(sub(dense).slice(-40)), sub(dense).length); +const many = "a".repeat(5000); +console.log("grow", many.replace(/a/g, "<<<>>>").length, many.replace(/a/g, "<<<>>>").slice(4990)); +console.log("empty", JSON.stringify("".replace(/x*/g, "-")), JSON.stringify("".replace(/a/g, "b"))); +console.log("none", "zzzz".replace(/a[bc]/g, "!"), "zzzz".replaceAll(/a[bc]/g, "!")); + +// Subjects outside ASCII run the general loop with the same results. +for (const subject of ["héllo wörld ", "日本<語>", "a\u{1F600}b", "cat café cat"]) { + console.log(JSON.stringify(subject.replace(/<[^>]*>/g, "|")), JSON.stringify(subject.replaceAll(/cat/g, "dog")), JSON.stringify(subject.replace(/x*/g, "-"))); +} + +// A long DNA-like subject, as in regex-redux. +let dna = ""; +for (let i = 0; i < 4000; i++) dna += "acgt"[(i * 7 + (i >> 2)) % 4] + (i % 61 === 0 ? "tHaN" : i % 37 === 0 ? "aNBY" : ""); +const step1 = dna.replace(/tHa[Nt]/g, "<4>").replace(/a[NSt]|BY/g, "<2>"); +const step2 = step1.replace(/<[^>]*>/g, "|").replace(/\|[^|][^|]*\|/g, "-"); +console.log("dna", dna.length, step1.length, step2.length, hash(step2, 0)); diff --git a/tests/harness/corpus-costs/8351-regex-literal-replacers.ts.json b/tests/harness/corpus-costs/8351-regex-literal-replacers.ts.json new file mode 100644 index 000000000..56546d44c --- /dev/null +++ b/tests/harness/corpus-costs/8351-regex-literal-replacers.ts.json @@ -0,0 +1 @@ +{ "plain": 3074, "san": 44176 } From c5131cb958e4331770db8e3b23a6aec1ba733be1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:39:37 -0500 Subject: [PATCH 095/230] Name the byte order mark fixture's directory by its real path The 8211 program resolved a package written to a fresh directory under os.tmpdir() and printed the result relative to that directory. On macOS the temporary directory is a symbolic link (/var to /private/var), so the resolved real path printed relative to the linked name included the random directory suffix, which differs between the Node run and the compiled run. The fixture now names the directory by its real path, and the result is lib/entry.js on every host. --- .../node_modules/resolve-probe/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js index 4be08c224..8d581c90e 100644 --- a/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js +++ b/tests/corpus/8211-require-resolve-computed/node_modules/resolve-probe/index.js @@ -1,4 +1,4 @@ -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import { createRequire } from "node:module"; import { tmpdir } from "node:os"; import { dirname, join, relative } from "node:path"; @@ -43,8 +43,10 @@ const requests = [ // A package whose package.json starts with a byte order mark, written to a // fresh directory and resolved by its absolute path: "main" is still read. +// The directory is named by its real path (the temporary directory is a +// symbolic link on macOS), so the resolved path is relative to it. function resolveWithByteOrderMark() { - const dir = mkdtempSync(join(tmpdir(), "scriptc-bom-")); + const dir = realpathSync(mkdtempSync(join(tmpdir(), "scriptc-bom-"))); try { const pkg = join(dir, "with-bom"); mkdirSync(join(pkg, "lib"), { recursive: true }); From 734ced4f4ff5ef1ca55944a127269a3433a39719 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:49:34 -0500 Subject: [PATCH 096/230] Address review feedback --- .../src/frontend/type-patches/layer.ts | 23 ++++- .../src/frontend/type-patches/locate.ts | 44 ++++++++- packages/compiler/test/type-patches.test.ts | 97 ++++++++++++++++++- 3 files changed, 156 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/frontend/type-patches/layer.ts b/packages/compiler/src/frontend/type-patches/layer.ts index 48ef6ecf0..79b409110 100644 --- a/packages/compiler/src/frontend/type-patches/layer.ts +++ b/packages/compiler/src/frontend/type-patches/layer.ts @@ -59,6 +59,8 @@ interface MatchRecord extends PatchMatch { interface State { manifest: string; + /** The directory of the project's package.json. */ + projectDir: string; manifestText: string; parse: TypePatchParse; byPackage: Map; @@ -104,6 +106,7 @@ export function configureTypePatches(entryPath: string, parse: TypePatchParse): } state = { manifest: config.manifest, + projectDir: dirname(config.manifest), manifestText: config.manifestText ?? "", parse, byPackage, @@ -142,6 +145,22 @@ function versionAt(dir: string): string | null { } } +/** The version of the runtime package `pkg` an `@types` twin under + * `nodeModules` describes: the copy installed beside it, else the one the + * project resolves (pnpm keeps each package in its own directory, so a + * twin has no runtime package beside it), else null. */ +function twinRuntimeVersion(s: State, nodeModules: string, pkg: string): string | null { + const beside = versionAt(nodeModules + pkg); + if (beside !== null) return beside; + for (let dir = s.projectDir; ;) { + const version = versionAt(`${dir}/node_modules/${pkg}`); + if (version !== null) return version; + const parent = dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + /** The installed package copy a file belongs to, with the requests that * apply to it; null for files outside node_modules or of unpatched * packages. */ @@ -162,9 +181,9 @@ function copyOf(s: State, path: string): { copy: CopyInfo; rel: string } | null const pkg = name.startsWith("@types/") ? unmangleTypesName(name) : name; const requests = s.byPackage.get(pkg); if (requests !== undefined) { - // A twin describes the runtime package installed beside it. + // A twin is matched by the runtime package's version, never its own. const version = name.startsWith("@types/") - ? (versionAt(path.slice(0, at + marker.length) + pkg) ?? versionAt(root)) + ? twinRuntimeVersion(s, path.slice(0, at + marker.length), pkg) : versionAt(root); const applicable = requests.filter( (r) => version !== null && satisfies(version, r.entry.range), diff --git a/packages/compiler/src/frontend/type-patches/locate.ts b/packages/compiler/src/frontend/type-patches/locate.ts index fc85a1634..6c762f5c3 100644 --- a/packages/compiler/src/frontend/type-patches/locate.ts +++ b/packages/compiler/src/frontend/type-patches/locate.ts @@ -596,6 +596,26 @@ function parseJsDoc(text: string, start: number, end: number): JsDocComment { return { start, end, tags }; } +/** The spans of a file's string, template and regular expression literal + * tokens. */ +function literalSpans(sf: ts.SourceFile): { start: number; end: number }[] { + const out: { start: number; end: number }[] = []; + const kinds = new Set([ + ts.SyntaxKind.StringLiteral, + ts.SyntaxKind.NoSubstitutionTemplateLiteral, + ts.SyntaxKind.TemplateHead, + ts.SyntaxKind.TemplateMiddle, + ts.SyntaxKind.TemplateTail, + ts.SyntaxKind.RegularExpressionLiteral, + ]); + const visit = (n: ts.Node): void => { + if (kinds.has(n.kind)) out.push({ start: tokenStartOfNode(n), end: n.end }); + ts.forEachChild(n, visit); + }; + visit(sf); + return out.sort((a, b) => a.start - b.start); +} + const RETURN_TAGS = new Set(["returns", "return"]); const PROPERTY_TAGS = new Set(["property", "prop"]); @@ -615,15 +635,27 @@ class JsResolver { private readonly text: string, sf: ts.SourceFile, ) { + // JSDoc comments, not text inside string, template or regular + // expression literals that looks like one. + const literals = literalSpans(sf); const re = /\/\*\*(?!\/)[\s\S]*?\*\//g; - for (let m = re.exec(text); m !== null; m = re.exec(text)) + for (let m = re.exec(text); m !== null; m = re.exec(text)) { + const inside = literals.find((l) => l.start <= m!.index && m!.index < l.end); + if (inside !== undefined) { + re.lastIndex = inside.end; + continue; + } this.comments.push(parseJsDoc(text, m.index, m.index + m[0].length)); + } for (const c of this.comments) c.tags.forEach((t, i) => { if ((t.tag === "typedef" || t.tag === "callback") && t.name !== null) this.add(t.name, { kind: "typedef", comment: c, tagIndex: i, prefix: "" }); }); - for (const s of sf.statements.nodes) this.collect(s); + // Declarations first: `exports.name = name` may come before the + // function declaration it exports, which is hoisted. + for (const s of sf.statements.nodes) if (!ts.isExpressionStatement(s)) this.collect(s); + for (const s of sf.statements.nodes) if (ts.isExpressionStatement(s)) this.collect(s); } private add(name: string, pos: JsPos): void { @@ -681,9 +713,11 @@ class JsResolver { if (named !== undefined) this.exportEquals.push(...named); } } else if (ts.isIdentifier(e.right) && this.roots.has(e.right.text)) { - // `exports.name = name`: the named declaration is the export. - for (const pos of this.roots.get(e.right.text)!) - if (pos.kind === "decl") this.add(target, pos); + // `exports.name = local`: the named declaration is the export (and + // already is under its own name). + if (target !== e.right.text) + for (const pos of [...this.roots.get(e.right.text)!]) + if (pos.kind === "decl") this.add(target, pos); } else this.add(target, this.declPos(s, s, e.right)); } diff --git a/packages/compiler/test/type-patches.test.ts b/packages/compiler/test/type-patches.test.ts index 729a0da37..b4c0af9bc 100644 --- a/packages/compiler/test/type-patches.test.ts +++ b/packages/compiler/test/type-patches.test.ts @@ -2,7 +2,7 @@ * packages: commander's HelpContext.helpWidth @typedef property and ms's * @return tags, patched from the fixtures' package.json; installed-version * selection; and the warnings for patches that do not apply as written. */ -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, expect, test } from "vitest"; @@ -122,3 +122,98 @@ test("an @types twin is matched by the runtime package's installed version", () rmSync(root, { recursive: true, force: true }); } }); + +test("an @types twin in pnpm's layout is matched by the runtime package the project resolves", () => { + // pnpm keeps each package in its own directory under node_modules/.pnpm + // and links the project's dependencies from node_modules: the twin has no + // runtime package beside it. + const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-pnpm-")); + try { + const store = join(root, "node_modules/.pnpm"); + const runtime = join(store, "tallyjs@0.5.0/node_modules/tallyjs"); + const types = join(store, "@types+tallyjs@0.4.0/node_modules/@types/tallyjs"); + mkdirSync(runtime, { recursive: true }); + mkdirSync(types, { recursive: true }); + writeFileSync(join(runtime, "package.json"), '{ "name": "tallyjs", "version": "0.5.0" }'); + writeFileSync(join(runtime, "index.js"), "exports.totals = () => ({});\n"); + writeFileSync(join(types, "package.json"), '{ "name": "@types/tallyjs", "version": "0.4.0" }'); + writeFileSync( + join(types, "index.d.ts"), + "export type Totals = {\n max: number;\n};\nexport declare function totals(): Totals;\n", + ); + mkdirSync(join(root, "node_modules/@types"), { recursive: true }); + symlinkSync(runtime, join(root, "node_modules/tallyjs")); + symlinkSync(types, join(root, "node_modules/@types/tallyjs")); + const patchFor = (range: string) => + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { + typePatches: { + [`tallyjs@${range}`]: { declarations: { "Totals.max": "| undefined" } }, + }, + }, + }), + ); + // A range the runtime (0.5.0) satisfies applies, though the twin's own + // version (0.4.0) does not satisfy it. + patchFor("0.5.0"); + configureTypePatches(join(root, "main.ts"), parse); + expect(typePatchedText(join(types, "index.d.ts"))).toContain("max: number | undefined;"); + // A range only the twin's own version satisfies does not. + patchFor("0.4.0"); + configureTypePatches(join(root, "main.ts"), parse); + expect(typePatchedText(join(types, "index.d.ts"))).toBeUndefined(); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test("an export before its hoisted function is patched, and string text that looks like JSDoc is not", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-js-")); + try { + const pkg = join(root, "node_modules/hoistkit"); + mkdirSync(pkg, { recursive: true }); + writeFileSync(join(pkg, "package.json"), '{ "name": "hoistkit", "version": "1.0.0" }'); + const source = [ + '"use strict";', + "exports.measure = size;", + 'exports.doc = "/** @typedef {object} Opts\\n * @property {number} width */";', + "", + "/**", + " * @returns {number}", + " */", + "function size(s) {", + " return s.length || undefined;", + "}", + "", + ].join("\n"); + writeFileSync(join(pkg, "index.js"), source); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { + typePatches: { + "hoistkit@^1.0.0": { + declarations: { "measure()": "| undefined", "Opts.width": "| undefined" }, + }, + }, + }, + }), + ); + configureTypePatches(join(root, "main.ts"), parse); + // The export, under another name, comes before the function it exports: + // the function's @returns tag widens. + expect(typePatchedText(join(pkg, "index.js"))).toBe( + source.replace(" * @returns {number}\n", " * @returns {number | undefined}\n"), + ); + // The string's text is a value, not a typedef: the patch finds nothing. + expect( + typePatchDiagnostics()! + .diagnostics.map((d) => d.message) + .join("\n"), + ).toContain("Opts.width"); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); From 177b2fde496334d47b025951cc6c1393866b2136 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 11:51:24 -0700 Subject: [PATCH 097/230] Cross package values at their declared type where the JS literal type lacks members An opted-in package's JavaScript types its own values. The lowering types a consumer's view of such a value from that JavaScript, and the declaration boundary (decl-projection.ts) casts to the declared type only where the JavaScript world answers `any`. A function that returns an object literal has a precise JavaScript type, so it never crossed, even where that literal type lacks members the declarations promise: - `{ [key: string]: string }` over `{ size: "12px" }`: the consumer read `settings["font"] ?? "x"` through the literal's fixed key set. The checker types the miss `any` (a JavaScript literal), but the keyed read took the declared fields' common type, so the miss trapped with "record has no key" and aborted the process. - an expando `out.max = ...` on a non-empty literal, which the JavaScript checker leaves out of the literal's type: reading `stats(xs).max` was refused with SC1090. A package value whose JavaScript type is an object literal that lacks a declared property or string index signature now crosses at its declared type, so missing keys read undefined through the index signature and expando members are read from the object. An index signature whose values do not project (a package interface) keeps the value untyped (`as any`), so its reads are the JavaScript reads. Only programs compiled with --npm-static whose consumers read such values are rewritten. Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/frontend/decl-projection.ts | 62 +++++++++++++++++-- .../npm-static/declared-members-cli.ts | 21 +++++++ .../node_modules/declared-members/index.d.ts | 7 +++ .../node_modules/declared-members/index.js | 34 ++++++++++ .../declared-members/package.json | 6 ++ tests/harness/npm-static.test.ts | 12 ++++ 6 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/npm-static/declared-members-cli.ts create mode 100644 tests/fixtures/npm-static/node_modules/declared-members/index.d.ts create mode 100644 tests/fixtures/npm-static/node_modules/declared-members/index.js create mode 100644 tests/fixtures/npm-static/node_modules/declared-members/package.json diff --git a/packages/compiler/src/frontend/decl-projection.ts b/packages/compiler/src/frontend/decl-projection.ts index 02a300996..a999559c2 100644 --- a/packages/compiler/src/frontend/decl-projection.ts +++ b/packages/compiler/src/frontend/decl-projection.ts @@ -519,6 +519,41 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | return undefined; } }; + const stringIndex = (checker: ts.TypeChecker, type: ts.Type): boolean => { + try { + return checker + .getIndexInfosOfType(type) + .some((info) => (info.keyType.flags & ts.TypeFlags.String) !== 0); + } catch { + return false; + } + }; + /** Whether the JavaScript world types a package value as an object + * literal that lacks a member its declaration promises: a property (an + * expando `out.max = ...` on a non-empty literal, which the JavaScript + * checker does not add to the literal's type) or a string index + * signature (`Record` over a literal). Lowering the value + * at the narrower literal type would drop the member or fix the key set, + * so it crosses at its declared type instead. */ + const missesDeclaredMembers = (jsType: ts.Type, declType: ts.Type): boolean => { + if (!(jsType.flags & ts.TypeFlags.Object) || !(declType.flags & ts.TypeFlags.Object)) + return false; + const literal = jsType.getSymbol(); + if (literal === undefined || (literal.flags & ts.SymbolFlags.ObjectLiteral) === 0) return false; + try { + if (declChecker.isArrayType(declType) || declChecker.isTupleType(declType)) return false; + if (stringIndex(declChecker, declType) && !stringIndex(jsChecker, jsType)) return true; + return declChecker + .getPropertiesOfType(declType) + .some( + (prop) => + !String(prop.escapedName).startsWith("__@") && + jsChecker.getPropertyOfType(jsType, String(prop.escapedName)) === undefined, + ); + } catch { + return false; + } + }; const namesPackageSymbol = (node: ts.Node): boolean => { let found = false; const visit = (n: ts.Node): void => { @@ -540,7 +575,7 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | const fileEdits: Edit[] = []; /** Candidate expressions (by js node) with their declared type, named * only for the casts that survive suppression. */ - const exprCandidates = new Map(); + const exprCandidates = new Map(); const exprOrder: ts.Node[] = []; /** Reads whose projectable receiver the JavaScript world types `any`: * kept only when nothing types the receiver. */ @@ -610,7 +645,14 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | return; } const jsType = typeAt(jsChecker, jsNode); - if (!isAnyish(jsType)) return; + if (jsType === null) return; + // A typed JavaScript value crosses only where its object type lacks a + // member the declaration promises (missesDeclaredMembers). + const typedJs = !isAnyish(jsType); + if (typedJs) { + const declType = typeAt(declChecker, declNode); + if (declType === null || !missesDeclaredMembers(jsType, declType)) return; + } // Package-produced? const d = declNode as ts.Expression; let produced = false; @@ -686,7 +728,18 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | // no contextual type } const declType = typeAt(declChecker, d); - if (declType === null || !printer.projectable(declType)) return; + if (declType === null) return; + if (!printer.projectable(declType)) { + // An index signature over values that do not project (package + // interfaces): the literal's fixed key set would still answer for + // keys it lacks, so the value stays untyped and every read is the + // JavaScript read. + if (typedJs && stringIndex(declChecker, declType)) { + exprCandidates.set(jsNode, "any"); + exprOrder.push(jsNode); + } + return; + } exprCandidates.set(jsNode, declType); exprOrder.push(jsNode); }; @@ -890,7 +943,8 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | ) continue; } - const alias = printer.project(exprCandidates.get(e)!); + const candidate = exprCandidates.get(e)!; + const alias = candidate === "any" ? "any" : printer.project(candidate); if (alias === null) continue; const start = ts.tokenStartOfNode(e); const stmt = e.parent; diff --git a/tests/fixtures/npm-static/declared-members-cli.ts b/tests/fixtures/npm-static/declared-members-cli.ts new file mode 100644 index 000000000..107ffc54d --- /dev/null +++ b/tests/fixtures/npm-static/declared-members-cli.ts @@ -0,0 +1,21 @@ +// A consumer reads members a package's declarations promise but its +// JavaScript adds in ways the inferred literal type misses: absent keys of +// index signatures read undefined, and expando properties read their values, +// as in Node. +import { counts, fonts, settings, stats } from "declared-members"; + +const key = process.argv.length > 10 ? "size" : "missing"; + +const s = settings(); +console.log(s["font"] ?? "default font", s["size"] ?? "default size", s.family); +console.log(s["font"] === undefined, typeof s["weight"], s.weight ?? "dot miss"); +console.log(s[key] ?? "dynamic miss", s[key], Object.keys(s)); + +const c = counts(); +console.log(c["chars"] ?? -1, c["lines"], c[key] === undefined); + +const st = stats([4, 9, 2]); +console.log(st.max, st.sum, st.count, st); + +const f = fonts(); +console.log(f["title"]?.name ?? "no title", f[key] === undefined, f["body"]?.name, f.code.name); diff --git a/tests/fixtures/npm-static/node_modules/declared-members/index.d.ts b/tests/fixtures/npm-static/node_modules/declared-members/index.d.ts new file mode 100644 index 000000000..8061fd5cb --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/declared-members/index.d.ts @@ -0,0 +1,7 @@ +export declare function settings(): { [key: string]: string }; +export declare function counts(): Record; +export declare function stats(xs: number[]): { count: number; sum: number; max: number }; +export interface Font { + name: string; +} +export declare function fonts(): Record; diff --git a/tests/fixtures/npm-static/node_modules/declared-members/index.js b/tests/fixtures/npm-static/node_modules/declared-members/index.js new file mode 100644 index 000000000..381bd4a46 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/declared-members/index.js @@ -0,0 +1,34 @@ +"use strict"; +// Each function returns an object literal whose JavaScript-inferred type +// lacks members that index.d.ts promises. + +// An index signature over a literal built empty, then assigned. +function settings() { + const out = {}; + out.size = "12px"; + out.family = "serif"; + return out; +} + +// An index signature over a literal with fixed keys. +function counts() { + return { lines: 3, words: 12 }; +} + +// An expando on a non-empty literal: the checker's literal type has no `max`. +function stats(xs) { + const out = { count: xs.length, sum: 0 }; + for (const x of xs) out.sum += x; + out.max = Math.max(...xs); + return out; +} + +// An index signature whose values are a declared interface. +function fonts() { + return { body: { name: "serif" }, code: { name: "mono" } }; +} + +exports.settings = settings; +exports.counts = counts; +exports.stats = stats; +exports.fonts = fonts; diff --git a/tests/fixtures/npm-static/node_modules/declared-members/package.json b/tests/fixtures/npm-static/node_modules/declared-members/package.json new file mode 100644 index 000000000..4a42ebdf2 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/declared-members/package.json @@ -0,0 +1,6 @@ +{ + "name": "declared-members", + "version": "1.0.0", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index f3a6225c4..076733739 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -1207,6 +1207,18 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { expect(nativeRes.exitCode).toBe(nodeRes.exitCode); }, 180_000); + test("package values cross at the members their declarations promise", async () => { + const entry = join(pilotRoot, "declared-members-cli.ts"); + const binary = await buildStatic(entry, ["declared-members"]); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry]), + runBinary(binary, []), + ]); + expect(nativeRes.stdout).toEqual(nodeRes.stdout); + expect(comparableStderr(nativeRes.stderr)).toEqual(nodeRes.stderr); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + }, 180_000); + test("the published Effect Function subpath initializes and runs statically", async () => { const entry = join(pilotRoot, "effect-function-cli.ts"); const { coverage } = analyze(entry, { npmStatic: ["effect"] }); From a4bc01bafbbf5f0b9b2c8c346b1c3bae3cac6d20 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 11:52:19 -0700 Subject: [PATCH 098/230] Count shorthand property reads of an initializer-less JavaScript let An initializer-less JavaScript `let` whose writes all agree gets a scalar slot when every read already has that flow type (inferredEvolvingLetType). The read scan resolved each identifier with getSymbolAtLocation, which answers the property symbol for a shorthand property name, so the `width` in `return { width }` was not seen as a read. A function that assigns the let on one path only (`let width; if (tty) width = 80; return { width };`) then stored it in a number slot: the path that never assigns returned 80 or 0 instead of undefined. The capture scan missed a closure that reads the let through a shorthand the same way. Both scans now resolve a shorthand name to the binding it reads, so such a let keeps its checked-dynamic slot and reads undefined until assigned, as in Node. TypeScript lets are unaffected (the inference applies to JavaScript only). Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-stmts.ts | 14 ++++--- .../evolving-let-shorthand-reads.js.json | 6 +++ tests/corpus/evolving-let-shorthand-reads.js | 41 +++++++++++++++++++ .../evolving-let-shorthand-reads.js.json | 1 + 4 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/evolving-let-shorthand-reads.js.json create mode 100644 tests/corpus/evolving-let-shorthand-reads.js create mode 100644 tests/harness/corpus-costs/evolving-let-shorthand-reads.js.json diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index d6d8c1cc7..0563f6165 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -5422,6 +5422,12 @@ function inferredEvolvingLetType(lowerer: Lowerer, decl: ts.VariableDeclaration) const scope = decl.parent?.parent?.parent; if (symbol === undefined || (!ts.isBlock(scope) && !ts.isSourceFile(scope))) return null; + // A shorthand property's name (`{ width }`) resolves to the property + // symbol, but it reads the binding: the checker's shorthand-value symbol. + const refersToBinding = (node: ts.Identifier): boolean => + (ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node + ? lowerer.checker.getShorthandAssignmentValueSymbol(node.parent) + : lowerer.checker.getSymbolAtLocation(node)) === symbol; const isPlainAssignmentTarget = (node: ts.Identifier): boolean => { let target: ts.Expression = node; while (ts.isParenthesizedExpression(target.parent) && target.parent.expression === target) { @@ -5437,11 +5443,7 @@ function inferredEvolvingLetType(lowerer: Lowerer, decl: ts.VariableDeclaration) const captures = (fn: ts.Node): boolean => { let captured = false; ts.walkPreorder(fn, (node) => { - if ( - node !== fn && - ts.isIdentifier(node) && - lowerer.checker.getSymbolAtLocation(node) === symbol - ) { + if (node !== fn && ts.isIdentifier(node) && refersToBinding(node)) { captured = true; return "stop"; } @@ -5493,7 +5495,7 @@ function inferredEvolvingLetType(lowerer: Lowerer, decl: ts.VariableDeclaration) if ( ts.isIdentifier(node) && node !== decl.name && - lowerer.checker.getSymbolAtLocation(node) === symbol && + refersToBinding(node) && !isPlainAssignmentTarget(node) ) { reads.push(node); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/evolving-let-shorthand-reads.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/evolving-let-shorthand-reads.js.json new file mode 100644 index 000000000..26d07a9f9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/evolving-let-shorthand-reads.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/evolving-let-shorthand-reads.js" + ], + "diags": [] +} diff --git a/tests/corpus/evolving-let-shorthand-reads.js b/tests/corpus/evolving-let-shorthand-reads.js new file mode 100644 index 000000000..201171bcc --- /dev/null +++ b/tests/corpus/evolving-let-shorthand-reads.js @@ -0,0 +1,41 @@ +// An initializer-less JavaScript `let` read through a shorthand property +// (`{ width }`) still reads undefined on the paths that never assign it, as +// npm packages do when they build an options or size object. +function size(tty) { + let width; + if (tty) width = 80; + return { width }; +} + +/** + * @param {boolean} tty + * @returns {{ width: number | undefined }} + */ +function documented(tty) { + /** @type {number | undefined} */ + let width; + if (tty) width = 80; + return { width }; +} + +function captured(tty) { + let width; + const read = () => ({ width }); + const before = read().width; + if (tty) width = 80; + return [before, read().width]; +} + +function nested(flags) { + let columns; + let rows; + if (flags.columns) columns = 120; + if (flags.rows) rows = 40; + return { size: { columns, rows } }; +} + +console.log(size(true).width, size(false).width); +console.log(documented(true).width, documented(false).width); +console.log(JSON.stringify(size(false)), "width" in size(false)); +console.log(captured(true), captured(false)); +console.log(nested({ columns: true }), nested({ rows: true }), nested({})); diff --git a/tests/harness/corpus-costs/evolving-let-shorthand-reads.js.json b/tests/harness/corpus-costs/evolving-let-shorthand-reads.js.json new file mode 100644 index 000000000..f93ba161d --- /dev/null +++ b/tests/harness/corpus-costs/evolving-let-shorthand-reads.js.json @@ -0,0 +1 @@ +{ "plain": 3700, "san": 2800 } From 701173a2aebea76753aca83d6b4b2ae829a88a07 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 13:55:44 -0500 Subject: [PATCH 099/230] Read builtin globals through the global object, and support Node's navigator --- .../limitations/builtin-apis/globals.mdx | 6 +- .../limitations/builtin-function-values.mdx | 1 + .../src/generated/node-v24-compatibility.json | 24 +- .../generated/node-v24-internal.json | 34 ++- internal/compatibility/static-support.json | 4 +- .../compiler/src/backend/llvm/lib-dispatch.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 6 + .../frontend/lowering/builtins/navigator.ts | 32 +- .../lowering/expressions/identifiers.ts | 96 +++--- .../lowering/expressions/property-access.ts | 58 ++-- .../src/frontend/lowering/global-values.ts | 274 +++++++++++++++++ .../src/frontend/lowering/lower-modules.ts | 5 + .../src/frontend/lowering/lower-stmts.ts | 31 +- .../compiler/src/frontend/lowering/lowerer.ts | 18 +- .../compiler/src/ir/builtin-signatures.ts | 9 + .../8161-global-computed-reads.cjs.json | 6 + .../corpus/8162-navigator-global.cjs.json | 6 + .../corpus/8163-data-function-values.cjs.json | 6 + .../corpus/8164-atob-validation.cjs.json | 6 + packages/runtime/src/scr_dyn.h | 13 + packages/runtime/src/scr_dyn_invoke.c | 38 ++- packages/runtime/src/scr_json.c | 106 ++++--- packages/runtime/src/scr_node_builtin.c | 289 ++++++++++++++++++ tests/corpus/8161-global-computed-reads.cjs | 87 ++++++ tests/corpus/8162-navigator-global.cjs | 47 +++ tests/corpus/8163-data-function-values.cjs | 32 ++ tests/corpus/8164-atob-validation.cjs | 35 +++ tests/fixtures/node-types/fenced.ts | 8 +- tests/fixtures/node-types/navigator.ts | 6 +- .../__snapshots__/node-types-fenced.txt | 30 +- .../8161-global-computed-reads.cjs.json | 1 + .../8162-navigator-global.cjs.json | 1 + .../8163-data-function-values.cjs.json | 1 + .../8164-atob-validation.cjs.json | 1 + 34 files changed, 1132 insertions(+), 186 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/global-values.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8161-global-computed-reads.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8162-navigator-global.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8163-data-function-values.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8164-atob-validation.cjs.json create mode 100644 tests/corpus/8161-global-computed-reads.cjs create mode 100644 tests/corpus/8162-navigator-global.cjs create mode 100644 tests/corpus/8163-data-function-values.cjs create mode 100644 tests/corpus/8164-atob-validation.cjs create mode 100644 tests/harness/corpus-costs/8161-global-computed-reads.cjs.json create mode 100644 tests/harness/corpus-costs/8162-navigator-global.cjs.json create mode 100644 tests/harness/corpus-costs/8163-data-function-values.cjs.json create mode 100644 tests/harness/corpus-costs/8164-atob-validation.cjs.json diff --git a/docs/content/limitations/builtin-apis/globals.mdx b/docs/content/limitations/builtin-apis/globals.mdx index f875bd761..e54530b29 100644 --- a/docs/content/limitations/builtin-apis/globals.mdx +++ b/docs/content/limitations/builtin-apis/globals.mdx @@ -1,4 +1,8 @@ - **Globals:** Stored `globalThis` references support identity and probes for absent capabilities. - Most builtin members require direct global access; access through a stored global object can raise `SC2020`. + In JavaScript, reading a builtin global through the global object by name (`globalThis[name]`, or a stored global object) answers what the global's own spelling reads, and builtin module members that are global objects in Node, such as `require("buffer").atob` and `require("timers").setTimeout`, are the same values. + `WebAssembly` read this way, and builtin members read through a global object that untyped code received, can raise `SC2020`. + `for (key in globalThis)` visits Node's enumerable globals in Node's order, then the program's own global properties. + `navigator` answers Node's `hardwareConcurrency`, `language`, `languages`, `platform`, and `userAgent`, with `language` derived from `LC_ALL`, `LC_MESSAGES`, or `LANG` as Node does. + `navigator.locks` raises `SC2020`, as does `navigator.language` for a locale outside the `language_TERRITORY.codeset@modifier` form, or one whose language or territory needs ICU's tables (a three-letter territory, for example). `Temporal` is not defined, as in Node.js 24: `typeof Temporal` is `"undefined"`, `globalThis.Temporal` is `undefined`, and any other use throws `ReferenceError: Temporal is not defined`. Programs that name it type-check against TypeScript's `Temporal` declarations. diff --git a/docs/content/limitations/builtin-function-values.mdx b/docs/content/limitations/builtin-function-values.mdx index 0fa62e558..24050f35d 100644 --- a/docs/content/limitations/builtin-function-values.mdx +++ b/docs/content/limitations/builtin-function-values.mdx @@ -7,6 +7,7 @@ Some builtins can be stored and passed as functions: In JavaScript, `setTimeout`, `setInterval`, `setImmediate`, `clearTimeout`, `clearInterval`, `clearImmediate`, `queueMicrotask`, and `process.nextTick` are function values wherever they are stored, including conditional and logical expressions and assignments: `typeof` answers `"function"`, identity holds across spellings, and calls schedule as the direct calls do, returning the same numeric timer handles. Calls through these values bypass `node:test` mock timers. +In JavaScript, `atob`, `btoa`, and `structuredClone` are function values the same way, and calls through them run what the direct calls run. Other builtin globals stored in JavaScript remain opaque identity values. In JavaScript, `require("events")` and the `EventEmitter` class assigned to an untyped binding or stored in an object (`ReleaseEmitter = EventEmitter`) are the untyped `EventEmitter` constructor that constructor functions inherit from (see below): `new` through it builds such an emitter object, and identity and `instanceof` agree with the class. A class can extend `require("events")` directly. diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index 130da748c..a3dd6f747 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -36684,9 +36684,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "supported", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -36707,9 +36707,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -36730,9 +36730,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -36776,9 +36776,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "supported", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index c3a0f90b3..fea2ecee7 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -52415,6 +52415,7 @@ "tests/corpus/6331-jose-root-entry/main.ts", "tests/corpus/7743-transform-stream-identity.ts", "tests/corpus/7747-text-codec-untyped-slot.js", + "tests/corpus/8162-navigator-global.cjs", "tests/corpus/text-codec-values/main.ts", "tests/corpus/webassembly-api.mjs", "tests/corpus/webassembly-dylink/main.mjs", @@ -54467,6 +54468,7 @@ "status": "partial", "evidence": "compiler-feature:globals.navigator", "tests": [ + "tests/corpus/8162-navigator-global.cjs", "tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts", "tests/corpus/6331-jose-root-entry/main.ts" @@ -54492,8 +54494,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:globals" + "status": "supported", + "evidence": "compiler-feature:globals.hardwareConcurrency", + "tests": [ + "tests/corpus/8162-navigator-global.cjs", + "tests/fixtures/node-types/navigator.ts" + ] }, "anchorSource": "ancestor" }, @@ -54515,8 +54521,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:globals" + "status": "partial", + "evidence": "compiler-feature:globals.language", + "tests": [ + "tests/corpus/8162-navigator-global.cjs", + "tests/fixtures/node-types/navigator.ts" + ] }, "anchorSource": "ancestor" }, @@ -54538,8 +54548,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:globals" + "status": "partial", + "evidence": "compiler-feature:globals.languages", + "tests": [ + "tests/corpus/8162-navigator-global.cjs", + "tests/fixtures/node-types/navigator.ts" + ] }, "anchorSource": "ancestor" }, @@ -54584,8 +54598,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:globals" + "status": "supported", + "evidence": "compiler-feature:globals.platform", + "tests": [ + "tests/corpus/8162-navigator-global.cjs", + "tests/fixtures/node-types/navigator.ts" + ] }, "anchorSource": "ancestor" }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index ab626fe21..160523cc1 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -215,8 +215,10 @@ { "chapter": "webcrypto", "signatures": ["Class: `CryptoKey`", "`cryptoKey.algorithm`", "`extractable` Type: {boolean}", "`type` Type: {string} One of `'secret'`, `'private'`, or `'public'`.", "`usages` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] }, { "chapter": "webcrypto", "signatures": ["Class: `Crypto`"], "status": "partial", "evidence": ["tests/corpus/7120-webcrypto-random-uuid.ts", "tests/corpus/7121-webcrypto-crypto-object.mjs", "tests/corpus/6293-webcrypto-digest.ts"] }, { "chapter": "webcrypto", "symbols": ["crypto.randomUUID", "crypto.getRandomValues"], "status": "supported", "evidence": ["tests/corpus/7120-webcrypto-random-uuid.ts", "tests/corpus/7121-webcrypto-crypto-object.mjs", "tests/corpus/6204-crypto-get-random-values.ts", "tests/corpus/6205-crypto-get-random-values.js"] }, - { "chapter": "globals", "signatures": ["`navigator`"], "status": "partial", "evidence": ["tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts", "tests/corpus/6331-jose-root-entry/main.ts"] }, + { "chapter": "globals", "signatures": ["`navigator`"], "status": "partial", "evidence": ["tests/corpus/8162-navigator-global.cjs", "tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts", "tests/corpus/6331-jose-root-entry/main.ts"] }, { "chapter": "globals", "signatures": ["`userAgent` Type: {string}"], "status": "supported", "evidence": ["tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts"] }, + { "chapter": "globals", "signatures": ["`hardwareConcurrency` Type: {number}", "`platform` Type: {string}"], "status": "supported", "evidence": ["tests/corpus/8162-navigator-global.cjs", "tests/fixtures/node-types/navigator.ts"] }, + { "chapter": "globals", "signatures": ["`language` Type: {string}", "`languages` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/8162-navigator-global.cjs", "tests/fixtures/node-types/navigator.ts"] }, { "chapter": "globals", "signatures": ["Class: `CryptoKey`", "Class: `SubtleCrypto`"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] }, { "chapter": "errors", "parent": "Class: `SystemError`", "symbols": ["code", "errno", "syscall", "path", "dest"], "status": "partial", "evidence": ["tests/corpus/caught-system-error-fields.ts", "tests/fixtures/node-types/terminal-error-fields.ts", "tests/harness/project-config.test.ts"] }, {"chapter": "events", "symbols": ["EventTarget", "eventTarget.addEventListener", "eventTarget.removeEventListener", "eventTarget.dispatchEvent"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5131-event-target-listener-errors.js", "tests/corpus/5132-event-target-typed.ts", "tests/corpus/5133-abort-signal-event-target.ts", "tests/corpus/5135-event-target-warnings.js", "tests/corpus/5240-event-target-subclass.ts", "tests/corpus/5242-event-target-subclass-hierarchy.ts", "tests/corpus/5243-event-target-subclass-dynamic.ts", "tests/corpus/5244-event-target-subclass.js", "tests/corpus/5245-event-target-subclass-signals.ts", "tests/corpus/5248-event-target-subclass-warnings.ts"]}, diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 4af8182a7..b513e17a6 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -1149,6 +1149,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { case "punycode": case "constants": case "promise": + case "navigator": return host.emitGenericLibCall(e); case "test": return emitTestLibCall(host, e); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 2548b75d5..4afdf8c81 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -255,6 +255,12 @@ export const LIB_FN_SYMS: Record = { "console.table": "scr_console_table", "console.dirDyn": "scr_console_dir_dyn", "global.native": "scr_global_native", + "global.installValues": "scr_global_values_install", + "global.forInObject": "scr_global_for_in_object", + "navigator.object": "scr_navigator_object", + "navigator.language": "scr_navigator_language", + "navigator.languages": "scr_navigator_languages", + "navigator.platform": "scr_navigator_platform", "process.builtinModule": "scr_process_builtin_module", "process.builtinUnsupported": "scr_process_builtin_unsupported", "module.namespaceValue": "scr_module_namespace_value", diff --git a/packages/compiler/src/frontend/lowering/builtins/navigator.ts b/packages/compiler/src/frontend/lowering/builtins/navigator.ts index f90448c1e..f4fbf2567 100644 --- a/packages/compiler/src/frontend/lowering/builtins/navigator.ts +++ b/packages/compiler/src/frontend/lowering/builtins/navigator.ts @@ -1,13 +1,13 @@ -/* Node 24's `navigator` global (lib/internal/navigator.js), the part - * libraries read to tell runtimes apart: `typeof navigator` is "object" - * and `navigator.userAgent` is `Node.js/`. jose reads exactly - * that at module load (jwks/remote.js). Both fold to string literals, so - * nothing links. The other members (hardwareConcurrency, language, - * languages, platform, locks) and the object as a value keep a named +/* Node 24's `navigator` global (lib/internal/navigator.js): `typeof + * navigator` is "object", `navigator.userAgent` is `Node.js/` (both + * fold to string literals; jose reads them at module load), and + * hardwareConcurrency, language, languages and platform read the runtime's + * answers (scr_node_builtin.c). The object as a value is the runtime's + * Navigator handle, whose getters answer the same. `locks` keeps a named * refusal. */ import * as ts from "../../ts7/adapter.js"; import { strLit } from "../../../ir/build.js"; -import type { IrExpr } from "../../../ir/ir.js"; +import { DYN, F64, STRING, type IrExpr, type SrcLoc } from "../../../ir/ir.js"; import { isJsSourceFile, locOf } from "../../program.js"; import type { Lowerer } from "../lowerer.js"; @@ -34,9 +34,18 @@ export function lowerNavigatorMember( nodeVersion: string, ): IrExpr | null { if (!isNavigatorGlobal(lowerer, access.expression)) return null; + const loc = locOf(access); if (access.name.text === "userAgent") { - return strLit(`Node.js/${nodeVersion.split(".")[0]}`, locOf(access)); + return strLit(`Node.js/${nodeVersion.split(".")[0]}`, loc); } + if (access.name.text === "hardwareConcurrency") + return { kind: "libCall", fn: "os.availableParallelism", args: [], type: F64, loc }; + if (access.name.text === "language" || access.name.text === "platform") { + const fn = access.name.text === "language" ? "navigator.language" : "navigator.platform"; + return { kind: "libCall", fn, args: [], type: STRING, loc }; + } + if (access.name.text === "languages") + return { kind: "libCall", fn: "navigator.languages", args: [], type: DYN, loc }; return refuseNavigator( lowerer, access, @@ -55,7 +64,12 @@ export function refuseNavigator( return lowerer.noLowering( what, node, - "of Node's navigator global only typeof navigator and navigator.userAgent compile", + "navigator.locks (the Web Locks API) has no lowering", symbol ?? null, ); } + +/** The navigator object as a value: the runtime's Navigator handle. */ +export function navigatorValue(loc: SrcLoc): IrExpr { + return { kind: "libCall", fn: "navigator.object", args: [], type: DYN, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index 62b2c7960..6541e0788 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 { globalMemberValue } from "../global-values.js"; import { predeclareForwardClass } from "../lower-stmts.js"; import { cjsMemberAliasValueOf } from "../cjs-exports-object.js"; import { builtinClassValue, builtinMemberClassKind } from "../builtins/builtin-class-dyn.js"; @@ -89,7 +90,7 @@ import { unsupportedModuleFeatureOf } from "../../builtin-modules.js"; import { lowerGlobalValue } from "../lower-global-value.js"; import { fenceEnumObjectValue } from "../lower-enums.js"; import { mixinFnOfCallee } from "../lower-mixins.js"; -import { isNavigatorGlobal, refuseNavigator } from "../builtins/navigator.js"; +import { isNavigatorGlobal, navigatorValue } from "../builtins/navigator.js"; import { wasiGuestPath } from "../../../wasi-paths.js"; export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: SrcLoc): IrExpr { @@ -597,6 +598,9 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // the same identity-token story as stdlib globals above (the // harness adds worker_threads.Worker to its identity Set). if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { + // A member that is a global object reads as the global does. + const global = globalMemberValue(lowerer, bi, loc); + if (global) return global; // EventEmitter and the stream classes are the runtime's untyped // constructors, so constructor functions can inherit from them. const classKind = builtinMemberClassKind(bi.module, bi.member); @@ -606,12 +610,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: return builtinFunctionFenceValue(lowerer, bi, expr, loc); return { kind: "strLit", - // node:stream/web exports the web stream globals themselves: - // the same identity token as the global spelling. - value: - bi.module === "stream/web" - ? `[builtin ${bi.member}]` - : `[builtin ${bi.module}.${bi.member}]`, + value: `[builtin ${bi.module}.${bi.member}]`, type: STRING, loc, }; @@ -729,43 +728,11 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: expr.text === "decodeURI") && lowerer.isStdlibGlobal(expr, expr.text) ) { - const name = `%builtin.${expr.text}`; - const type = funcOf([DYN], STRING); - if (!lowerer.liftedFns.some((fn) => fn.name === name)) { - const value: IrExpr = { - kind: "libCall", - fn: "dyn.toStringCoerce", - args: [varRef("value", DYN, loc)], - type: STRING, - loc, - }; - const fn = - expr.text === "encodeURIComponent" - ? "str.encodeUriComponent" - : expr.text === "encodeURI" - ? "str.encodeUri" - : expr.text === "decodeURI" - ? "str.decodeUri" - : "str.decodeUriComponent"; - lowerer.liftedFns.push({ - name, - params: [{ localId: "value", name: "value", type: DYN }], - locals: [{ id: "value", name: "value", type: DYN, mutable: false }], - returnType: STRING, - body: [ - { - kind: "return", - value: { kind: "libCall", fn, args: [value], type: STRING, loc }, - loc, - }, - ], - loc, - }); - } - return { kind: "closure", fnName: name, captures: [], type, loc }; + return uriFunctionValue(lowerer, expr.text, loc); } if (lowerer.isStdlibGlobal(expr, "crypto")) return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc }; + if (isNavigatorGlobal(lowerer, expr)) return navigatorValue(loc); const nativeAlias = stdlibGlobalNameOf(lowerer, expr); if (nativeAlias === "Buffer" && expr.text !== "Buffer") { // An alias through a cast of globalThis carries the cast's static type, @@ -1024,14 +991,6 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: const ns = lowerNsIdentifierValue(lowerer, expr); if (ns) return ns; } - if (isNavigatorGlobal(lowerer, expr)) { - refuseNavigator( - lowerer, - expr, - "the navigator object as a value", - lowerer.checker.getSymbolAtLocation(expr), - ); - } // An import of a CommonJS member export whose value is a binding of the // exporting module (`module.exports = Client; module.exports.StatsD = // Client`): the member holds that binding's value. @@ -1167,3 +1126,42 @@ function cjsExportAccessorRead(lowerer: Lowerer, ident: ts.Identifier): IrExpr | } return cjsAccessorCall(lowerer, getter, locOf(ident)); } + +/** encodeURIComponent, encodeURI, decodeURI or decodeURIComponent as a + * function value: one interned closure per function. */ +export function uriFunctionValue(lowerer: Lowerer, which: string, loc: SrcLoc): IrExpr { + const name = `%builtin.${which}`; + const type = funcOf([DYN], STRING); + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + const value: IrExpr = { + kind: "libCall", + fn: "dyn.toStringCoerce", + args: [varRef("value", DYN, loc)], + type: STRING, + loc, + }; + const fn = + which === "encodeURIComponent" + ? "str.encodeUriComponent" + : which === "encodeURI" + ? "str.encodeUri" + : which === "decodeURI" + ? "str.decodeUri" + : "str.decodeUriComponent"; + lowerer.liftedFns.push({ + name, + params: [{ localId: "value", name: "value", type: DYN }], + locals: [{ id: "value", name: "value", type: DYN, mutable: false }], + returnType: STRING, + body: [ + { + kind: "return", + value: { kind: "libCall", fn, args: [value], type: STRING, loc }, + loc, + }, + ], + loc, + }); + } + return { kind: "closure", fnName: name, captures: [], type, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 35bb16cd3..92fb2b2cd 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -1,4 +1,5 @@ /* Property reads (`a.b`): lowerExprInner's property-access branch, intrinsic and union properties, field reads and targets, and match-group reads. */ +import { jsSpecialGlobalValue } from "../global-values.js"; import { indexedUnionArms, lowerIndexedUnionMember } from "../indexed-unions.js"; import { keyedHelperName } from "../stable-names.js"; import { builtinNamespaceValue } from "../builtin-namespaces.js"; @@ -35,7 +36,6 @@ import { } from "../../type-mapper.js"; import { BOOL, - BYTES_ELEMENT_NAME, BYTES_U8, DYN, F64, @@ -60,7 +60,7 @@ import { unionContainerArmsOk, BYTES_ELEMENT_SIZE, } from "../../../ir/ir.js"; -import { lowerNavigatorMember } from "../builtins/navigator.js"; +import { lowerNavigatorMember, navigatorValue } from "../builtins/navigator.js"; import { lowerX509PublicKey } from "../builtins/x509.js"; import { NODE_COMPAT_VERSION } from "../builtins/process.js"; import { @@ -593,7 +593,10 @@ export function lowerPropertyAccessExpr( // (`(_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)) { + if ( + globalThisWrittenNames(lowerer, globalType).has(expr.name.text) || + lowerer.globalThisComputedWrite + ) { return { kind: "dynKeyGet", value: lowerer.coerceToExpected(lowerer.lowerExpr(expr.expression), DYN), @@ -602,6 +605,10 @@ export function lowerPropertyAccessExpr( loc, }; } + // Node's navigator, which JavaScript reads as a free name when no + // declaration types it (isNavigatorGlobal). + if (expr.name.text === "navigator" && isJsSourceFile(ts.getSourceFileOfNode(expr))) + return navigatorValue(loc); return dynUndefinedExpr(loc); } // `Uint8Array.prototype.toBase64` and its siblings, the base64/hex @@ -975,18 +982,8 @@ export function lowerPropertyAccessExpr( if (canonical === "globalThis") return lowerGlobalValue(lowerer, expr); // Typed for the programs that name it, absent in Node 24. if (canonical === TEMPORAL_GLOBAL) return dynUndefinedExpr(loc); - if (canonical === "ArrayBuffer") - return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc }; - if (canonical === "fetch") - return { kind: "libCall", fn: "fetch.function", args: [], type: DYN, loc }; - if (Object.values(BYTES_ELEMENT_NAME).includes(canonical)) - return { - kind: "libCall", - fn: "bytes.constructor", - args: [{ kind: "strLit", value: canonical, type: STRING, loc }], - type: DYN, - loc, - }; + const value = jsSpecialGlobalValue(lowerer, canonical, loc); + if (value) return value; return ( builtinGlobalValueAt(lowerer, expr, canonical, loc, true) ?? { kind: "strLit", @@ -4259,9 +4256,13 @@ const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet = new Set([ /** The builtin global functions JavaScript sources read as interned * function values (the `cond ? setImmediate : process.nextTick` feature - * probe of bcryptjs and async libraries): typeof answers "function", - * identity holds across spellings, and calls run the timer APIs. */ + * probe of bcryptjs and async libraries, and the known globals Node's test + * harness collects): typeof answers "function", identity holds across + * spellings, and calls run what the direct calls run. */ const GLOBAL_FUNCTION_VALUES: ReadonlySet = new Set([ + "atob", + "btoa", + "structuredClone", "setTimeout", "setInterval", "setImmediate", @@ -4639,11 +4640,24 @@ function globalThisWrittenNames(lowerer: Lowerer, globalType: ts.Type): Set { + if ( + (ts.isElementAccessExpression(node) || ts.isForInStatement(node)) && + stdlibGlobalNameOf(lowerer, node.expression) === "globalThis" && + (ts.isForInStatement(node) || readsBuiltinByName(lowerer, node)) + ) { + found = node.expression; + return "stop"; + } + return undefined; + }); + lowerer.computedGlobalReads.set(sf, found); + return found; +} + +/** Whether the element access `node` of the global object can read a + * builtin global by name: its key is not a symbol (`globalThis[key]` with + * `key = Symbol.for(...)` reads the global object's symbol properties), + * and it is neither the target of `=` nor the operand of `delete`. */ +function readsBuiltinByName(lowerer: Lowerer, node: ts.ElementAccessExpression): boolean { + if (lowerer.typeOf(node.argumentExpression).flags & ts.TypeFlags.ESSymbolLike) return false; + let outer: ts.Node = node; + while (ts.isParenthesizedExpression(outer.parent)) outer = outer.parent; + const parent = outer.parent; + if (ts.isDeleteExpression(parent)) return false; + return !( + ts.isBinaryExpression(parent) && + parent.left === outer && + parent.operatorToken.kind === ts.SyntaxKind.EqualsToken + ); +} + +/** The value a JavaScript source reads for the declared global `name` + * taken as a value, whatever its spelling (`name`, `globalThis.name`, + * `const { name } = globalThis`, `globalThis[name]`), or null when it + * reads the opaque identity token. */ +export function jsGlobalValueByName(lowerer: Lowerer, name: string, loc: SrcLoc): IrExpr | null { + return ( + jsSpecialGlobalValue(lowerer, name, loc) ?? + builtinNamespaceValue(lowerer, name, loc) ?? + builtinGlobalValue(name, loc, true) + ); +} + +/** jsGlobalValueByName for the globals other than the interned builtin + * functions and constructors (builtinGlobalValue). */ +export function jsSpecialGlobalValue(lowerer: Lowerer, name: string, loc: SrcLoc): IrExpr | null { + if (name === "String" || name === "Number" || name === "Boolean" || name === "BigInt") + return primitiveCtorClosure(lowerer, name, loc, true); + if (name === "ArrayBuffer") + return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc }; + if (name === "Array") + return { kind: "libCall", fn: "dyn.arrayConstructor", args: [], type: DYN, loc }; + if ((Object.values(BYTES_ELEMENT_NAME) as string[]).includes(name)) + return { + kind: "libCall", + fn: "bytes.constructor", + args: [{ kind: "strLit", value: name, type: STRING, loc }], + type: DYN, + loc, + }; + if (URI_FUNCTIONS.has(name)) return uriFunctionValue(lowerer, name, loc); + if (name === "crypto") + return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc }; + if (name === "navigator") return navigatorValue(loc); + if (name === "Buffer") + return { kind: "libCall", fn: "buffer.constructor", args: [], type: DYN, loc }; + if (name === "fetch") return { kind: "libCall", fn: "fetch.function", args: [], type: DYN, loc }; + if (name === "console") + return { kind: "libCall", fn: "console.native", args: [], type: DYN, loc }; + return null; +} + +const URI_FUNCTIONS = new Set([ + "encodeURIComponent", + "encodeURI", + "decodeURI", + "decodeURIComponent", +]); + +/** The statement that installs the program's lookup, built on first use + * from the names the checker declares on `receiver` (a global object). */ +export function globalValuesInstall( + lowerer: Lowerer, + receiver: ts.Expression, + loc: SrcLoc, +): IrStmt { + const fnType: IrType & { kind: "func" } = { kind: "func", params: [STRING], ret: DYN }; + if (!lowerer.liftedFnNamed(LOOKUP_FN)) { + const names = lowerer.checker + .getPropertiesOfType(lowerer.typeOf(receiver)) + .map((property) => ts.symbolName(property)) + .filter((name) => name !== TEMPORAL_GLOBAL && !RUNTIME_ANSWERED.has(name)); + const name: IrExpr = { kind: "varRef", localId: "name.0", type: STRING, loc }; + const body: IrStmt[] = []; + for (const global of [...new Set(names)].sort()) { + const value: IrExpr | null = REFUSED.has(global) + ? { kind: "unitLit", unit: "null", type: { kind: "nullT" }, loc } + : jsGlobalValueByName(lowerer, global, loc); + if (value === null) continue; + body.push({ + kind: "if", + cond: { + kind: "strEq", + negated: false, + left: name, + right: { kind: "strLit", value: global, type: STRING, loc }, + type: { kind: "bool" }, + loc, + }, + then: [{ kind: "return", value: lowerer.coerceToExpected(value, DYN), loc }], + else_: null, + loc, + }); + } + body.push({ + kind: "return", + value: lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + DYN, + ), + loc, + }); + lowerer.liftedFns.push({ + name: LOOKUP_FN, + params: [{ localId: "name.0", name: "name", type: STRING }], + returnType: DYN, + locals: [{ id: "name.0", name: "name", type: STRING, mutable: false }], + body, + loc, + }); + } + const lookup = lowerer.coerceToExpected( + { kind: "closure", fnName: LOOKUP_FN, captures: [], type: fnType, loc }, + DYN, + ); + return { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "global.installValues", + args: [lookup], + type: { kind: "void" }, + loc, + }, + loc, + }; +} + +/** Builtin module members that are the global objects of the same name in + * Node 24 (`require("buffer").atob === globalThis.atob`). */ +const GLOBAL_MEMBERS: Readonly>> = { + buffer: new Set(["atob", "btoa", "Blob", "File", "Buffer"]), + timers: new Set([ + "setTimeout", + "setInterval", + "setImmediate", + "clearTimeout", + "clearInterval", + "clearImmediate", + ]), + perf_hooks: new Set([ + "performance", + "PerformanceEntry", + "PerformanceMark", + "PerformanceMeasure", + "PerformanceObserver", + "PerformanceObserverEntryList", + "PerformanceResourceTiming", + ]), + util: new Set(["TextEncoder", "TextDecoder"]), + url: new Set(["URL", "URLSearchParams"]), + worker_threads: new Set(["BroadcastChannel", "MessageChannel", "MessagePort"]), + "stream/web": new Set([ + "ReadableStream", + "ReadableStreamDefaultReader", + "ReadableStreamBYOBReader", + "ReadableStreamBYOBRequest", + "ReadableByteStreamController", + "ReadableStreamDefaultController", + "TransformStream", + "TransformStreamDefaultController", + "WritableStream", + "WritableStreamDefaultWriter", + "WritableStreamDefaultController", + "ByteLengthQueuingStrategy", + "CountQueuingStrategy", + "TextEncoderStream", + "TextDecoderStream", + "CompressionStream", + "DecompressionStream", + ]), +}; + +/** In a JavaScript source, the value of a builtin module member that is a + * global object (GLOBAL_MEMBERS): what the global's own spelling reads, + * so identity holds across the spellings. Null for other members. */ +export function globalMemberValue( + lowerer: Lowerer, + bi: { module: string; member: string }, + loc: SrcLoc, +): IrExpr | null { + if (!GLOBAL_MEMBERS[bi.module]?.has(bi.member)) return null; + return ( + jsGlobalValueByName(lowerer, bi.member, loc) ?? { + kind: "strLit", + value: `[builtin ${bi.member}]`, + type: STRING, + loc, + } + ); +} diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index 4fe908ab4..06fc16080 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -1,3 +1,4 @@ +import { computedGlobalReadOf, globalValuesInstall } from "./global-values.js"; import { THREADS_MODULE } from "../threads-import.js"; import { positionTag } from "./structural-positions.js"; import { EXPERIMENTAL_MODULE } from "../experimental-import.js"; @@ -3022,6 +3023,10 @@ export function lowerFileInit( }); } if (sf === lowerer.entry) header.push(...lowerer.punycodeLinkWarning()); + // A module that reads builtin globals through the global object by + // computed names installs the program's lookup before its body runs. + const computedGlobalRead = computedGlobalReadOf(lowerer, sf); + if (computedGlobalRead) header.push(globalValuesInstall(lowerer, computedGlobalRead, loc0)); // Top-level `this` of a CommonJS module: its object exists before the // module's first statement (lowerCjsTopLevelThis). const exportsThis = lowerer.cjsExportsThis.get(sf); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index d6d8c1cc7..6024849a6 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1,3 +1,4 @@ +import { jsGlobalValueByName } from "./global-values.js"; import { lowerTupleAliasDecl, stripTypeWrappers } from "./typed-arguments.js"; import { allBytesArms, @@ -33,7 +34,6 @@ import { InternalCompilerError } from "../../errors.js"; * explicit resource management, and blocked-binding poisoning. */ import * as ts from "../ts7/adapter.js"; import { lowerOptionalIndex } from "./undefined-index.js"; -import { builtinNamespaceValue } from "./builtin-namespaces.js"; import { processDataMemberValue, programReadsExitCode } from "./builtins/process.js"; import { requireInitFlagStmt } from "./require-initialization.js"; import { isJsonRequireDecl } from "./json-require.js"; @@ -238,7 +238,6 @@ import { globalSymbolKey } from "./expressions/global-symbols.js"; import { identifiersNamed } from "./identifier-uses.js"; import { lowerShortCircuitAssignment } from "./expressions/nullish-assignment.js"; import { - builtinGlobalValue, jsHoldsDynObject, lowerEnvironmentKey, lowerNativeFunctionAssignment, @@ -4326,14 +4325,9 @@ function stdlibGlobalTokenDestructure( const canonical = ts.symbolName(member) === "global" ? "globalThis" : ts.symbolName(member); token = `[builtin ${canonical}]`; alias = canonical; - // Builtin constructors read as their interned function values, and - // crypto as the WebCrypto object: the same values the bare and - // `globalThis.X` spellings read. - value = - canonical === "crypto" - ? { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc: locOf(el) } - : (builtinNamespaceValue(lowerer, canonical, locOf(el)) ?? - builtinGlobalValue(canonical, locOf(el), true)); + // The values the bare and `globalThis.X` spellings read + // (jsGlobalValueByName). + value = jsGlobalValueByName(lowerer, canonical, locOf(el)); } else if (globalName === "console" && !CONSOLE_CALL_MEMBERS.has(propName)) { token = `[builtin console.${propName}]`; } else if (TOKEN_OPAQUE_GLOBALS.has(globalName)) { @@ -12223,11 +12217,8 @@ function lowerForOfString( * own-keys snapshot, verified against Node) while each visit re-checks * presence (i < live length), so pops during the body skip exactly * like Node. Sparse arrays cannot exist here (hole creation traps). - * - globalThis: the harness's leaked-globals sweep — a compiled binary's - * global bindings are compile-time names, never enumerable runtime - * properties, so the enumeration is honestly EMPTY (zero iterations; - * the body typechecked but never evaluates — SEMANTICS.md documents - * the divergence). + * - globalThis: Node 24's enumerable globals in Node's order, then the + * program's own global properties, as keys of a fresh object. * - JavaScript program classes use their live property-creation table. * TypeScript and runtime-owned layouts keep the class-instance fence. * Bindings mirror for-of's: const/let declare the per-iteration string @@ -12237,10 +12228,12 @@ function lowerForOfString( function lowerForIn(lowerer: Lowerer, stmt: ts.ForInStatement): IrStmt { const labels = lowerer.takeLabels(); const loc = locOf(stmt); - if (stdlibGlobalNameOf(lowerer, stmt.expression) === "globalThis") { - return { kind: "block", body: [], loc }; - } - let receiver = lowerer.lowerExpr(stmt.expression); + // The global object visits Node's enumerable globals, then the + // program's own global properties (scr_global_for_in_object). + let receiver: IrExpr = + stdlibGlobalNameOf(lowerer, stmt.expression) === "globalThis" + ? { kind: "libCall", fn: "global.forInObject", args: [], type: DYN, loc } + : lowerer.lowerExpr(stmt.expression); if (receiver.type.kind === "object") { const info = lowerer.classes.get(receiver.type.className); let root = info; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index ee7477e43..66e78ec02 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,4 @@ +import { globalMemberValue } from "./global-values.js"; import { isDeclBoundaryFile } from "../decl-projection.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; import { moduleNamespaceObject, moduleNamespaceValue } from "./module-namespace-elements.js"; @@ -3105,6 +3106,9 @@ export class Lowerer { /** The property names the program assigns on the global object * (property-access.ts), collected on first use. */ globalThisWrittenNames: Set | null = null; + /** Set with globalThisWrittenNames: the program writes the global object + * by a computed name somewhere. */ + globalThisComputedWrite = false; /** Well-known symbol methods (`sym:iterator`) the program may replace * through a symbol-keyed write: an instance or prototype property, or a * reflection call. */ @@ -3339,6 +3343,9 @@ export class Lowerer { * values other than fresh literals (noteUntypedIntegrity). */ untypedIntegrity = false; readonly liftedFns: IrFunction[] = []; + /** global-values.ts's answer per JavaScript source: the receiver of its + * first read of the global object by a computed name, or null. */ + readonly computedGlobalReads = new Map(); /** The first lifted function of each name (liftedFnNamed), for the * liftedFns indexed so far. liftedFns only grows. */ private readonly liftedFnsByName = new Map(); @@ -14151,11 +14158,12 @@ export class Lowerer { if (bi.module === "crypto" && bi.member === "webcrypto") { return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc }; } - // node:stream/web's exports are the web stream globals themselves: in - // JavaScript sources the same identity token as the global spelling - // (`require("stream/web").ReadableStream === globalThis.ReadableStream`). - if (bi.module === "stream/web" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { - return { kind: "strLit", value: `[builtin ${bi.member}]`, type: STRING, loc }; + // Members that are global objects (`require("stream/web").ReadableStream + // === globalThis.ReadableStream`) read in JavaScript sources as their + // global spelling does. + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { + const global = globalMemberValue(this, bi, loc); + if (global) return global; } // A bare `fs.constants` read (not one of the baked bits above). if (bi.member === "constants" && bi.module === "fs") return fsConstantsObject(loc); diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 576386851..0d3d5c1ac 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3691,6 +3691,15 @@ function buildLibFnSignatures() { "console.dirDyn": { argTypes: [DYN, F64], result: STRING }, /** A native reference to the global object; known names retain value fences. */ "global.native": { argTypes: [arrayOf(STRING)], result: DYN }, + /** Installs the program's builtin-global lookup (global-values.ts). */ + "global.installValues": { argTypes: [DYN], result: VOID }, + /** The keys `for (key in globalThis)` visits, as a fresh object. */ + "global.forInObject": { argTypes: [], result: DYN }, + /** Node's navigator global (scr_node_builtin.c). */ + "navigator.object": { argTypes: [], result: DYN }, + "navigator.language": { argTypes: [], result: STRING }, + "navigator.languages": { argTypes: [], result: DYN }, + "navigator.platform": { argTypes: [], result: STRING }, // `X.name` through a class value: the arg is a program-dependent // classval (a null slot; the libCall case checks the kind). /** `X.name` through a class VALUE (scr_object.c): args[0] is a borrowed diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8161-global-computed-reads.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8161-global-computed-reads.cjs.json new file mode 100644 index 000000000..7124f14a8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8161-global-computed-reads.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8161-global-computed-reads.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8162-navigator-global.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8162-navigator-global.cjs.json new file mode 100644 index 000000000..2fd2d279e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8162-navigator-global.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8162-navigator-global.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8163-data-function-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8163-data-function-values.cjs.json new file mode 100644 index 000000000..f76f88160 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8163-data-function-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8163-data-function-values.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8164-atob-validation.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8164-atob-validation.cjs.json new file mode 100644 index 000000000..6081b76dd --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8164-atob-validation.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8164-atob-validation.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 656f2b994..899c7f0ca 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -187,6 +187,7 @@ typedef enum { X(MESSAGE_EVENT, 50) /* ScrEvent with data — BroadcastChannel's 'message' */ \ X(MESSAGE_PORT, 36) \ X(MODULE_NAMESPACE, 79) /* module namespace object read through its exports (scr_module_namespace.c) */ \ + X(NAVIGATOR, 81) /* Node's navigator global (scr_node_builtin.c) */ \ X(NET_BLOCK_LIST, 48) /* net.BlockList (scr_net.c) */ \ X(NET_SERVER, 3) /* ScrNetServer — net.Server (http.Server rides the same handle) */ \ X(NET_SOCKET, 2) /* ScrNetSocket — net.Socket */ \ @@ -1288,6 +1289,18 @@ void scr_dyn_obj_delete_symbol_field(ScrDyn *obj, ScrDyn *symbol); * object answers it; its symbol-keyed properties are the global object's * (scr_node_builtin.c). */ ScrDyn *scr_global_object(void); +/* The program's builtin-global lookup, a dyn function (borrowed); the first + * installation stands. */ +void scr_global_values_install(ScrDyn *lookup); +/* An object whose keys are what `for (key in globalThis)` visits (+1). */ +ScrDyn *scr_global_for_in_object(void); +/* Node's navigator global (+1, one box per thread) and its members: + * navigator.language (+1), navigator.languages (+1, one frozen array per + * thread) and navigator.platform (+1). */ +ScrDyn *scr_navigator_object(void); +ScrStr *scr_navigator_language(void); +ScrDyn *scr_navigator_languages(void); +ScrStr *scr_navigator_platform(void); ScrDyn *scr_dyn_global_symbol_get(ScrSym *key); /* borrowed key; +1 value */ void scr_dyn_global_symbol_set(ScrSym *key, ScrDyn *value); /* both borrowed */ bool scr_dyn_global_symbol_has(ScrSym *key); diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index 2fd3aa406..99d5ebad3 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1971,6 +1971,12 @@ static const char dyn_s32[] SCR_DYN_COMPAT_STR = ""; static const char dyn_s33[] SCR_DYN_COMPAT_STR = "constructing %s%s through a stored value is not supported yet"; static const char dyn_s34[] SCR_DYN_COMPAT_STR = " from a primitive"; static const char dyn_s35[] SCR_DYN_COMPAT_STR = "FinalizationRegistry"; +static const char dyn_s36[] SCR_DYN_COMPAT_STR = "atob"; +static const char dyn_s37[] SCR_DYN_COMPAT_STR = "btoa"; +static const char dyn_s38[] SCR_DYN_COMPAT_STR = "structuredClone"; +static const char dyn_s39[] SCR_DYN_COMPAT_STR = "The \"input\" argument must be specified"; +static const char dyn_s40[] SCR_DYN_COMPAT_STR = "The \"The value argument must be specified\" argument must be specified"; +static const char dyn_s41[] SCR_DYN_COMPAT_STR = "ERR_MISSING_ARGS"; static const char *const dyn_global_ctor_names[] = { dyn_s26, dyn_s29, dyn_s25, dyn_s30, dyn_s31, dyn_s21, dyn_s22, dyn_s24, dyn_s35, @@ -2236,7 +2242,36 @@ static ScrClosure *dyn_builtin_fn_deferred(ScrDyn *const *args, size_t argc, siz #endif +/* atob, btoa and structuredClone as values: the calls the direct + * spellings make, with Node's error for a missing argument. NULL with the + * exception pending, or the result (+1); `*handled` reports whether `name` + * is one of them. */ +static ScrDyn *dyn_builtin_data_fn_call(const ScrStr *name, ScrDyn *const *args, size_t argc, bool *handled) { + bool atob = strcmp(name->data, dyn_s36) == 0, btoa = strcmp(name->data, dyn_s37) == 0; + bool clone = strcmp(name->data, dyn_s38) == 0; + *handled = atob || btoa || clone; + if (!*handled) return NULL; + if (argc == 0) { + const char *message = clone ? dyn_s40 : dyn_s39; + scr_throw_error_msg_code(SCR_ERR_TYPE, message, strlen(message), dyn_s41); + return NULL; + } + if (clone) return scr_structured_clone(args[0], argc > 1 ? args[1] : scr_dyn_undefined()); + ScrStr *text = atob ? scr_atob(args[0]) : scr_btoa(args[0]); + if (!text) return NULL; + ScrDyn *result = scr_dyn_new_str(text); /* retains */ + scr_str_release(text); + return result; +} + static ScrDyn *dyn_builtin_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + { + ScrStr *name = scr_box_get_ref(clo->caps[0]); + bool handled; + ScrDyn *result = dyn_builtin_data_fn_call(name, args, argc, &handled); + scr_str_release(name); + if (handled) return result; + } #ifdef SCR_LIB (void)clo; (void)args; @@ -2295,7 +2330,8 @@ ScrDyn *scr_dyn_builtin_function(const ScrStr *name) { closure->caps[0] = scr_box_new(SCR_BOX_STR); scr_box_set_ref(closure->caps[0], scr_str_retain((ScrStr *)name)); const char *dot = memchr(name->data, '.', name->len); - bool two = strcmp(name->data, dyn_s16) == 0 || strcmp(name->data, dyn_s15) == 0; + bool two = strcmp(name->data, dyn_s16) == 0 || strcmp(name->data, dyn_s15) == 0 || + strcmp(name->data, dyn_s38) == 0; value = scr_dyn_new_func(closure, dyn_builtin_fn_call, two ? 2 : 1, dyn_s10, dot ? dot + 1 : name->data); scr_dyn_obj_set(dyn_builtin_fns, name->data, name->len, value); /* the table owns it */ } diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cd9eeecde..f2da26331 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -16723,9 +16723,10 @@ ScrError *scr_domex_clone(ScrError *e, const ScrDyn *options) { * They live HERE (not scr_string.c) because the argument is a dyn value: * WebIDL ToString runs over the dyn kind (Node's atob(null) decodes the * string "null"), and the string unit must stay linkable without the - * dyn. atob is forgiving-base64 exactly — ASCII whitespace stripped, a - * %4==0 input sheds up to two trailing '=', %4==1 refuses, leftover - * bits discard — decoding to the latin1 code points as a UTF-8 string; + * dyn. atob checks its input as Node's lib/buffer.js does (an invalid + * character or a misplaced '=' is "Invalid character", a length the + * forgiving-base64 rules refuse "not correctly encoded"), then decodes + * with leftover bits discarded to the latin1 code points as a UTF-8 string; * btoa refuses any code point over U+00FF. Malformed input throws the * catchable DOMException InvalidCharacterError with Node's exact * message. */ @@ -16741,61 +16742,70 @@ static int scr_b64_val(unsigned char c) { ScrStr *scr_atob(const ScrDyn *data) { ScrStr *s = scr_dyn_string_coerce(data); - /* Strip ASCII whitespace (the forgiving step). */ - char *buf = malloc(s->len ? s->len : 1); - if (!buf) scr_json_oom(); - size_t n = 0; + /* Node's checks (lib/buffer.js): only ASCII whitespace and the base64 + * alphabet with '=' are allowed, '=' only at the end and at most twice; + * the count of the other characters decides the rest. One pass checks + * the input and keeps the 6-bit value of each alphabet character. */ + unsigned char *values = malloc(s->len ? s->len : 1); + if (!values) scr_json_oom(); + size_t n = 0, equals = 0; + const char *message = NULL; for (size_t i = 0; i < s->len; i++) { unsigned char c = (unsigned char)s->data[i]; if (c == 0x09 || c == 0x0a || c == 0x0c || c == 0x0d || c == 0x20) continue; - buf[n++] = (char)c; + if (c == '=') { + if (++equals > 2) { + message = "Invalid character"; + break; + } + continue; + } + int v = scr_b64_val(c); + if (v < 0 || equals) { + message = "Invalid character"; + break; + } + values[n++] = (unsigned char)v; + } + if (!message) { + size_t remainder = (n + equals) % 4; + if (!remainder) remainder = n % 4; + else if (equals) message = "Invalid character"; + if (!message && remainder == 1) message = "The string to be decoded is not correctly encoded."; } scr_str_release(s); - /* A %4==0 input sheds one or two trailing '='. */ - if (n % 4 == 0 && n > 0) { - if (buf[n - 1] == '=') n--; - if (n > 0 && buf[n - 1] == '=') n--; + if (message) { + free(values); + scr_throw_domex("InvalidCharacterError", message); + return NULL; } - if (n % 4 == 1) goto invalid; /* Decode 6-bit groups; latin1 code points expand to UTF-8 (bytes over - * 0x7F become two-byte sequences). */ - { - size_t outBytes = (n / 4) * 3 + (n % 4 == 2 ? 1 : n % 4 == 3 ? 2 : 0); - char *out = malloc(outBytes * 2 ? outBytes * 2 : 1); - if (!out) scr_json_oom(); - size_t w = 0; - unsigned acc = 0; - int bits = 0; - for (size_t i = 0; i < n; i++) { - int v = scr_b64_val((unsigned char)buf[i]); - if (v < 0) { - free(out); - goto invalid; - } - acc = (acc << 6) | (unsigned)v; - bits += 6; - if (bits >= 8) { - bits -= 8; - unsigned char b = (unsigned char)((acc >> bits) & 0xff); - if (b < 0x80) { - out[w++] = (char)b; - } else { - out[w++] = (char)(0xc0 | (b >> 6)); - out[w++] = (char)(0x80 | (b & 0x3f)); - } + * 0x7F become two-byte sequences). Leftover bits discard + * (forgiving-base64's final step). */ + size_t outBytes = (n / 4) * 3 + (n % 4 == 2 ? 1 : n % 4 == 3 ? 2 : 0); + char *out = malloc(outBytes * 2 ? outBytes * 2 : 1); + if (!out) scr_json_oom(); + size_t w = 0; + unsigned acc = 0; + int bits = 0; + for (size_t i = 0; i < n; i++) { + acc = (acc << 6) | values[i]; + bits += 6; + if (bits >= 8) { + bits -= 8; + unsigned char b = (unsigned char)((acc >> bits) & 0xff); + if (b < 0x80) { + out[w++] = (char)b; + } else { + out[w++] = (char)(0xc0 | (b >> 6)); + out[w++] = (char)(0x80 | (b & 0x3f)); } } - /* Leftover bits discard (forgiving-base64's final step). */ - free(buf); - ScrStr *result = scr_str_new(out, w); - free(out); - return result; } -invalid: - free(buf); - scr_throw_domex("InvalidCharacterError", - "The string to be decoded is not correctly encoded."); - return NULL; + free(values); + ScrStr *result = scr_str_new(out, w); + free(out); + return result; } ScrStr *scr_btoa(const ScrDyn *data) { diff --git a/packages/runtime/src/scr_node_builtin.c b/packages/runtime/src/scr_node_builtin.c index c0031e67b..8604134e7 100644 --- a/packages/runtime/src/scr_node_builtin.c +++ b/packages/runtime/src/scr_node_builtin.c @@ -97,6 +97,10 @@ static SCR_TL const ScrArr *scr_global_registered; static SCR_TL bool scr_global_cleanup_registered; static SCR_TL ScrDyn *(*scr_global_console_get)(void); static SCR_TL ScrDyn *(*scr_global_fetch_get)(void); +/* The program's builtin-global lookup (scr_global_values_install), or + * NULL: a dyn function from a name to the value its static spelling reads, + * undefined for the opaque identity token, or null for a name without one. */ +static SCR_TL ScrDyn *scr_global_values; void scr_global_fetch_install(ScrDyn *(*get)(void)) { scr_global_fetch_get = get; } @@ -200,6 +204,13 @@ static void scr_global_cleanup(void) { static void *scr_global_retain(void *handle) { return handle; } static void scr_global_release(void *handle) { (void)handle; } +/* A dyn string holding `s` (+1); takes over the caller's reference. */ +static ScrDyn *scr_global_take_str(ScrStr *s) { + ScrDyn *value = scr_dyn_new_str(s); + scr_str_release(s); + return value; +} + static ScrDyn *scr_global_get(void *handle, const char *key, size_t length) { (void)handle; ScrDyn *own = scr_global_own ? scr_dyn_obj_get(scr_global_own, key, length) : NULL; @@ -210,10 +221,29 @@ static ScrDyn *scr_global_get(void *handle, const char *key, size_t length) { if (length == 7 && memcmp(key, "console", length) == 0 && scr_global_console_get) return scr_global_console_get(); if (length == 5 && memcmp(key, "fetch", length) == 0 && scr_global_fetch_get) return scr_global_fetch_get(); + if (length == 9 && memcmp(key, "navigator", length) == 0) return scr_navigator_object(); if (length == 9 && memcmp(key, "undefined", length) == 0) return scr_dyn_undefined(); if (length == 3 && memcmp(key, "NaN", length) == 0) return scr_dyn_new_num(NAN); if (length == 8 && memcmp(key, "Infinity", length) == 0) return scr_dyn_new_num(INFINITY); if (!scr_dyn_obj_get(scr_global_known, key, length)) return scr_dyn_undefined(); + if (scr_global_values) { + ScrDyn *name = scr_global_take_str(scr_str_new(key, length)); + ScrDyn *value = scr_global_values->v.fn.thunk(scr_global_values->v.fn.clo, &name, 1); + scr_dyn_release(name); + if (!value) return NULL; + if (value->kind == SCR_DYN_UNDEF) { + /* The opaque identity token a JavaScript source reads for the name. */ + scr_dyn_release(value); + ScrJsonBuf token; + scr_jb_init(&token); + scr_jb_puts(&token, "[builtin "); + for (size_t i = 0; i < length; i++) scr_jb_putc(&token, key[i]); + scr_jb_putc(&token, ']'); + return scr_global_take_str(scr_jb_finish(&token)); + } + if (value->kind != SCR_DYN_NULL) return value; + scr_dyn_release(value); + } ScrJsonBuf text; scr_jb_init(&text); scr_jb_puts(&text, "reading globalThis."); @@ -250,6 +280,47 @@ ScrDyn *scr_global_object(void) { return scr_global_box; } +/* Installs the program's builtin-global lookup (borrowed): reads of + * builtin names through the global object answer what their static + * spellings read. The first installation stands; modules that read the + * global object by computed names each install the same function. */ +static void scr_global_values_cleanup(void) { + if (scr_global_values) scr_dyn_release(scr_global_values); + scr_global_values = NULL; +} + +void scr_global_values_install(ScrDyn *lookup) { + if (scr_global_values || lookup->kind != SCR_DYN_FUNC) return; + scr_global_values = scr_dyn_retain(lookup); + scr_atexit(scr_global_values_cleanup); +} + +/* What `for (key in globalThis)` visits (+1): Node 24's enumerable global + * properties in its order, then the program's own global properties in + * creation order, as keys of a fresh object. */ +ScrDyn *scr_global_for_in_object(void) { + static const char *const node_names[] = { + "global", "clearImmediate", "setImmediate", "clearInterval", "clearTimeout", + "setInterval", "setTimeout", "queueMicrotask", "structuredClone", "atob", + "btoa", "performance", "fetch", "crypto", "navigator", + }; + ScrDyn *object = scr_dyn_new_obj(); + for (size_t i = 0; i < sizeof node_names / sizeof node_names[0]; i++) + scr_dyn_obj_set(object, node_names[i], strlen(node_names[i]), scr_dyn_undefined()); + if (scr_global_own) { + ScrDyn *keys = scr_dyn_obj_keys(scr_global_own); + double count = scr_dyn_arr_len(keys); + for (double i = 0; i < count; i++) { + ScrDyn *key = scr_dyn_arr_at(keys, i); + if (key->kind == SCR_DYN_STR && !scr_dyn_obj_get(object, key->v.str->data, key->v.str->len)) + scr_dyn_obj_set(object, key->v.str->data, key->v.str->len, scr_dyn_undefined()); + scr_dyn_release(key); + } + scr_dyn_release(keys); + } + return object; +} + ScrDyn *scr_global_native_init(ScrArr *known, ScrDyn *(*console_get)(void)) { /* The compiler passes one immortal name table per program: once it is * registered, later reads of the global object only take a reference. */ @@ -388,3 +459,221 @@ ScrDyn *scr_process_builtin_unsupported(ScrStr *id, ScrStr *member) { scr_str_release(message); return NULL; } + +/* ── Node's navigator global (lib/internal/navigator.js) ───────────────── + * hardwareConcurrency is os.availableParallelism(), language the default + * locale as Node's ICU answers it from the environment, languages a frozen + * one-element array of it, platform Node's name for the process platform + * and architecture, and userAgent `Node.js/`. locks has no lowering. + * The members are getters on Navigator.prototype, so the object owns no + * properties: Object.keys answers [] and JSON.stringify `{}`. */ + +static SCR_TL ScrDyn *scr_navigator_box; +static SCR_TL ScrDyn *scr_navigator_langs; +static SCR_TL ScrStr *scr_navigator_lang; + +static void scr_navigator_refuse(const char *what) { + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "'"); + scr_jb_puts(&text, what); + scr_jb_puts(&text, "' is part of the standard library types but has no scriptc lowering yet [SC2020]"); + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + scr_str_release(message); +} + +static bool scr_navigator_alpha(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } +static bool scr_navigator_digit(char c) { return c >= '0' && c <= '9'; } +static char scr_navigator_lower(char c) { return c >= 'A' && c <= 'Z' ? (char)(c + 32) : c; } +static char scr_navigator_upper(char c) { return c >= 'a' && c <= 'z' ? (char)(c - 32) : c; } + +/* The BCP 47 tag of the POSIX locale ID `id`, as ICU derives the default + * locale: `language[_TERRITORY][.codeset][@modifier]`, the codeset + * dropped, the C and POSIX locales and en_US_POSIX as en-US, the ISO 639 + * codes ICU replaces, and the modifier as a variant (5 to 8 characters) or + * an lvariant private-use subtag (1 to 4). NULL for an ID outside that + * grammar, whose tag needs ICU's tables. */ +static ScrStr *scr_navigator_tag(const char *id) { + if (!id) return scr_str_new("en-US", 5); + const char *end = id + strlen(id); + const char *at = strchr(id, '@'); + const char *dot = strchr(id, '.'); + const char *base_end = dot && (!at || dot < at) ? dot : at ? at : end; + const char *modifier = at ? at + 1 : NULL; + size_t base_len = (size_t)(base_end - id); + if (base_len == 0 && !modifier) return scr_str_new("und", 3); + if ((base_len == 1 && id[0] == 'C') || (base_len == 5 && memcmp(id, "POSIX", 5) == 0)) { + if (modifier) return NULL; + return scr_str_new("en-US", 5); + } + const char *underscore = memchr(id, '_', base_len); + size_t lang_len = underscore ? (size_t)(underscore - id) : base_len; + const char *region = underscore ? underscore + 1 : NULL; + size_t region_len = region ? (size_t)(base_end - region) : 0; + if (region && memchr(region, '_', region_len)) { + /* en_US_POSIX is ICU's name for the C locale. */ + if (lang_len == 2 && scr_navigator_lower(id[0]) == 'e' && scr_navigator_lower(id[1]) == 'n' && + region_len == 8 && scr_navigator_upper(region[0]) == 'U' && scr_navigator_upper(region[1]) == 'S' && + region[2] == '_') { + bool posix = true; + for (size_t i = 0; i < 5; i++) posix = posix && scr_navigator_upper(region[3 + i]) == "POSIX"[i]; + if (posix && !modifier) return scr_str_new("en-US", 5); + } + return NULL; + } + for (size_t i = 0; i < lang_len; i++) + if (!scr_navigator_alpha(id[i])) return NULL; + if (lang_len == 1 || lang_len == 4 || lang_len > 8) return NULL; + ScrJsonBuf tag; + scr_jb_init(&tag); + char lang[9] = {0}; + for (size_t i = 0; i < lang_len; i++) lang[i] = scr_navigator_lower(id[i]); + static const char *const replaced[][2] = { + {"in", "id"}, {"iw", "he"}, {"ji", "yi"}, {"jw", "jv"}, {"mo", "ro"}, + }; + const char *language = lang_len == 0 ? "und" : lang; + for (size_t i = 0; i < sizeof replaced / sizeof replaced[0]; i++) + if (!strcmp(lang, replaced[i][0])) language = replaced[i][1]; + scr_jb_puts(&tag, language); + if (region_len > 0) { + bool alpha2 = region_len == 2 && scr_navigator_alpha(region[0]) && scr_navigator_alpha(region[1]); + bool digits3 = region_len == 3 && scr_navigator_digit(region[0]) && scr_navigator_digit(region[1]) && + scr_navigator_digit(region[2]); + if (!alpha2 && !digits3) { + scr_str_release(scr_jb_finish(&tag)); + return NULL; + } + scr_jb_putc(&tag, '-'); + for (size_t i = 0; i < region_len; i++) scr_jb_putc(&tag, scr_navigator_upper(region[i])); + } + if (modifier && *modifier) { + size_t length = (size_t)(end - modifier); + bool alnum = length <= 8; + for (size_t i = 0; i < length && alnum; i++) + alnum = scr_navigator_alpha(modifier[i]) || scr_navigator_digit(modifier[i]); + if (!alnum || (length == 7 && !memcmp(modifier, "nynorsk", 7))) { + scr_str_release(scr_jb_finish(&tag)); + return NULL; + } + scr_jb_puts(&tag, length >= 5 ? "-" : "-x-lvariant-"); + for (size_t i = 0; i < length; i++) scr_jb_putc(&tag, scr_navigator_lower(modifier[i])); + } + return scr_jb_finish(&tag); +} + +ScrStr *scr_navigator_language(void) { + if (!scr_navigator_lang) { + /* ICU reads LC_ALL, then LC_MESSAGES, then LANG; a variable that is + * set, even to the empty string, decides. */ + const char *id = getenv("LC_ALL"); + if (!id) id = getenv("LC_MESSAGES"); + if (!id) id = getenv("LANG"); + ScrStr *tag = scr_navigator_tag(id); + if (!tag) { + scr_navigator_refuse("navigator.language for this locale"); + return NULL; + } + scr_navigator_lang = tag; + } + return scr_str_retain(scr_navigator_lang); +} + +ScrDyn *scr_navigator_languages(void) { + if (!scr_navigator_langs) { + ScrStr *language = scr_navigator_language(); + if (!language) return NULL; + ScrDyn *languages = scr_dyn_new_arr(); + scr_dyn_arr_push(languages, scr_global_take_str(language)); + scr_dyn_release(scr_dyn_freeze_data(languages)); + scr_navigator_langs = languages; + } + return scr_dyn_retain(scr_navigator_langs); +} + +ScrStr *scr_navigator_platform(void) { + ScrStr *platform = scr_process_platform(); + ScrStr *arch = scr_process_arch(); + const char *p = platform->data, *a = arch->data; + ScrJsonBuf text; + scr_jb_init(&text); + bool ia32 = !strcmp(a, "ia32"), x64 = !strcmp(a, "x64"); + if (!strcmp(p, "darwin")) scr_jb_puts(&text, "MacIntel"); + else if (!strcmp(p, "win32")) scr_jb_puts(&text, "Win32"); + else if (!strcmp(p, "linux")) { + scr_jb_puts(&text, ia32 ? "Linux i686" : x64 ? "Linux x86_64" : "Linux "); + if (!ia32 && !x64) scr_jb_puts(&text, a); + } else if (!strcmp(p, "freebsd") || !strcmp(p, "openbsd")) { + scr_jb_puts(&text, p[0] == 'f' ? "FreeBSD " : "OpenBSD "); + scr_jb_puts(&text, ia32 ? "i386" : x64 ? "amd64" : a); + } else if (!strcmp(p, "sunos")) { + scr_jb_puts(&text, "SunOS "); + scr_jb_puts(&text, ia32 ? "i86pc" : a); + } else if (!strcmp(p, "aix")) scr_jb_puts(&text, "AIX"); + else { + scr_jb_putc(&text, scr_navigator_upper(p[0])); + if (p[0]) scr_jb_puts(&text, p + 1); + scr_jb_putc(&text, ' '); + scr_jb_puts(&text, a); + } + scr_str_release(platform); + scr_str_release(arch); + return scr_jb_finish(&text); +} + +static ScrDyn *scr_navigator_get(void *handle, const char *key, size_t length) { + (void)handle; + if (length == 19 && !memcmp(key, "hardwareConcurrency", length)) + return scr_dyn_new_num(scr_os_available_parallelism()); + if (length == 8 && !memcmp(key, "language", length)) { + ScrStr *language = scr_navigator_language(); + return language ? scr_global_take_str(language) : NULL; + } + if (length == 9 && !memcmp(key, "languages", length)) return scr_navigator_languages(); + if (length == 8 && !memcmp(key, "platform", length)) return scr_global_take_str(scr_navigator_platform()); + if (length == 9 && !memcmp(key, "userAgent", length)) { + ScrStr *version = scr_process_versions_node(); + const char *dot = memchr(version->data, '.', version->len); + size_t major = dot ? (size_t)(dot - version->data) : version->len; + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "Node.js/"); + for (size_t i = 0; i < major; i++) scr_jb_putc(&text, version->data[i]); + scr_str_release(version); + return scr_global_take_str(scr_jb_finish(&text)); + } + if (length == 5 && !memcmp(key, "locks", length)) { + scr_navigator_refuse("navigator.locks"); + return NULL; + } + return NULL; +} + +static ScrDyn *scr_navigator_inspect_view(void *handle) { + (void)handle; + return scr_dyn_new_obj(); +} + +static void scr_navigator_cleanup(void) { + if (scr_navigator_box) scr_dyn_release(scr_navigator_box); + if (scr_navigator_langs) scr_dyn_release(scr_navigator_langs); + if (scr_navigator_lang) scr_str_release(scr_navigator_lang); + scr_navigator_box = NULL; + scr_navigator_langs = NULL; + scr_navigator_lang = NULL; +} + +ScrDyn *scr_navigator_object(void) { + static const ScrDynHandleOps ops = { + .cls = "Navigator", .retain = scr_global_retain, .release = scr_global_release, + .get = scr_navigator_get, .inspect_view = scr_navigator_inspect_view, + .json_empty_object = true, .inspect_view_empty = true, + }; + if (!scr_navigator_box) { + scr_dyn_handle_install(SCR_DYNH_NAVIGATOR, &ops); + static SCR_TL char token; + scr_navigator_box = scr_dyn_new_handle(&token, SCR_DYNH_NAVIGATOR); + scr_atexit(scr_navigator_cleanup); + } + return scr_dyn_retain(scr_navigator_box); +} diff --git a/tests/corpus/8161-global-computed-reads.cjs b/tests/corpus/8161-global-computed-reads.cjs new file mode 100644 index 000000000..ff5883154 --- /dev/null +++ b/tests/corpus/8161-global-computed-reads.cjs @@ -0,0 +1,87 @@ +// @no-node-shims +// Builtin globals read through the global object by computed names, and +// builtin module members that are global objects: each answers what the +// global's own spelling reads, so identity holds across the spellings. +// for-in over the global object visits Node's enumerable globals first and +// the program's own global properties after them. +"use strict"; +const { atob: bufferAtob, btoa: bufferBtoa } = require("buffer"); +const timers = require("timers"); +const { performance: perf } = require("perf_hooks"); +const util = require("util"); +const url = require("url"); +const { MessageChannel } = require("worker_threads"); +const web = require("stream/web"); + +const names = [ + "setTimeout", + "setInterval", + "setImmediate", + "clearTimeout", + "clearInterval", + "clearImmediate", + "queueMicrotask", + "global", + "globalThis", + "fetch", + "Map", + "Object", + "Promise", + "Buffer", + "crypto", +]; +for (const name of names) { + const value = globalThis[name]; + console.log(name, typeof value, value === globalThis[name]); +} +console.log( + globalThis["setTimeout"] === setTimeout, + globalThis["Map"] === Map, + globalThis["crypto"] === crypto, + globalThis["Buffer"] === Buffer, +); +console.log(timers.setTimeout === setTimeout, timers.clearInterval === globalThis["clearInterval"]); +console.log( + bufferAtob === atob, + bufferBtoa === globalThis["btoa"], + perf === performance, + util.TextEncoder === TextEncoder, + url.URL === URL, + MessageChannel === globalThis["MessageChannel"], + web.ReadableStream === ReadableStream, +); +console.log(globalThis["noSuchGlobal"], typeof globalThis["Temporal"]); +const pick = (name) => globalThis[name]; +console.log(pick("Math") === Math, pick("JSON") === JSON, pick("Reflect") === Reflect, pick("Math").max(1, 3)); + +const before = []; +for (const key in globalThis) before.push(key); +globalThis.ownA = 1; +globalThis["own" + "B"] = { b: 2 }; +const after = []; +for (const key in globalThis) after.push(key); +console.log(after.length - before.length, after.slice(-2).join(",")); +console.log(before.slice(0, 9).join(",")); +console.log(globalThis.ownA, globalThis.ownB.b, globalThis["ownB"] === globalThis.ownB); + +// The harness's leak check: every enumerated global is a known value. +const known = new Set([ + global, + clearImmediate, + setImmediate, + clearInterval, + clearTimeout, + setInterval, + setTimeout, + queueMicrotask, + structuredClone, + bufferAtob, + bufferBtoa, + performance, + fetch, + crypto, + globalThis.ownA, + globalThis.ownB, +]); +const leaked = after.filter((key) => key !== "navigator" && !known.has(globalThis[key])); +console.log("leaked", leaked.length); diff --git a/tests/corpus/8162-navigator-global.cjs b/tests/corpus/8162-navigator-global.cjs new file mode 100644 index 000000000..3944d6efd --- /dev/null +++ b/tests/corpus/8162-navigator-global.cjs @@ -0,0 +1,47 @@ +// @no-node-shims +// Node 24's navigator global: its members are getters on +// Navigator.prototype answering the process's values, and the object +// itself owns no properties. It is one of the global object's enumerable +// properties. +"use strict"; +const os = require("os"); +const util = require("util"); + +console.log(typeof navigator, navigator.userAgent); +console.log(navigator.hardwareConcurrency === os.availableParallelism()); +console.log(typeof navigator.language, navigator.language.length > 0); +console.log(navigator.languages.length, navigator.languages[0] === navigator.language); +console.log(navigator.languages === navigator.languages, Object.isFrozen(navigator.languages)); +const platforms = { + linux: { x64: "Linux x86_64", arm64: "Linux arm64" }, + darwin: { x64: "MacIntel", arm64: "MacIntel" }, +}; +console.log(navigator.platform === platforms[process.platform]?.[process.arch]); + +const value = navigator; +console.log(value === navigator, value === globalThis.navigator, value === globalThis["navigator"]); +console.log(value.userAgent, value.platform === navigator.platform, value.missing); +console.log(Object.keys(value), JSON.stringify(value), util.inspect(value)); +console.log(String(value), Object.prototype.toString.call(value)); + +const keys = []; +for (const key in globalThis) keys.push(key); +console.log(keys.join(",")); +const known = new Set([ + global, + clearImmediate, + setImmediate, + clearInterval, + clearTimeout, + setInterval, + setTimeout, + queueMicrotask, + structuredClone, + atob, + btoa, + performance, + fetch, + crypto, + navigator, +]); +console.log("leaked", keys.filter((key) => !known.has(globalThis[key])).length); diff --git a/tests/corpus/8163-data-function-values.cjs b/tests/corpus/8163-data-function-values.cjs new file mode 100644 index 000000000..d752f8405 --- /dev/null +++ b/tests/corpus/8163-data-function-values.cjs @@ -0,0 +1,32 @@ +// atob, btoa and structuredClone as JavaScript values: function values +// whose calls run what the direct calls run, the same value through every +// spelling. +"use strict"; +const buffer = require("buffer"); + +const fns = [atob, btoa, structuredClone]; +for (const fn of fns) console.log(typeof fn, fn.name, fn.length); +console.log(buffer.atob === atob, buffer.btoa === globalThis.btoa, globalThis["structuredClone"] === structuredClone); + +const pick = (name) => ({ atob, btoa, structuredClone })[name]; +const encode = pick("btoa"); +const decode = pick("atob"); +console.log(encode("scriptc"), decode(encode("scriptc")), decode("aGk=")); +const clone = pick("structuredClone"); +const original = { list: [1, { deep: true }], text: "t" }; +const copy = clone(original); +console.log(copy !== original, copy.list[1].deep, copy.list !== original.list, copy.text); + +const report = (label, run) => { + try { + console.log(label, run()); + } catch (error) { + console.log(label, error.name, error.code, error.message); + } +}; +report("atob()", () => decode()); +report("btoa()", () => encode()); +report("structuredClone()", () => clone()); +report("atob invalid", () => decode("a")); +report("btoa wide", () => encode("Ā")); +report("clone symbol", () => clone(Symbol("s"))); diff --git a/tests/corpus/8164-atob-validation.cjs b/tests/corpus/8164-atob-validation.cjs new file mode 100644 index 000000000..89d46e3b1 --- /dev/null +++ b/tests/corpus/8164-atob-validation.cjs @@ -0,0 +1,35 @@ +// atob checks its input as Node does: only ASCII whitespace and the base64 +// alphabet are allowed, '=' only at the end and at most twice, and a +// length the forgiving-base64 rules refuse is a different error. +"use strict"; +const inputs = [ + "@@@", + "a", + "ab", + "ab=", + "ab==", + "ab=c", + "abc==", + "a===", + "ab===", + " Y W J j ", + "YQ", + "Y Q = =", + "YWJjZA==", + "YWJjZA=", + "\tYW\nJj\fZA\r==", + "é", + "YW-J", + "", +]; +const decode = (label, input) => { + try { + console.log(label, JSON.stringify(atob(input))); + } catch (error) { + console.log(label, error.name, error.code, error.message); + } +}; +for (const input of inputs) decode(JSON.stringify(input), input); +decode("null", null); +decode("undefined", undefined); +decode("12", 12); diff --git a/tests/fixtures/node-types/fenced.ts b/tests/fixtures/node-types/fenced.ts index 0d90e8cac..d94c7c19a 100644 --- a/tests/fixtures/node-types/fenced.ts +++ b/tests/fixtures/node-types/fenced.ts @@ -108,8 +108,8 @@ async function publicKeyWithWebCrypto(key: import("node:crypto").webcrypto.Crypt await crypto.subtle.getPublicKey(key, ["verify"]); } void publicKeyWithWebCrypto; -/* Node's navigator global: typeof navigator and navigator.userAgent - * compile; the other members fence by name. */ -const cores = navigator.hardwareConcurrency; -void cores; +/* Node's navigator global: navigator.locks (the Web Locks API) fences + * by name. */ +const locks = navigator.locks; +void locks; // End of the declared-but-not-lowered surface. diff --git a/tests/fixtures/node-types/navigator.ts b/tests/fixtures/node-types/navigator.ts index 2f1ee2d9f..901f0e290 100644 --- a/tests/fixtures/node-types/navigator.ts +++ b/tests/fixtures/node-types/navigator.ts @@ -1,6 +1,10 @@ /* Node 24's navigator global under @types/node: the runtime probe - * libraries use (jose's user-agent check) folds to Node's values. */ + * libraries use (jose's user-agent check) folds to Node's values, and the + * other members read the process's values. */ const browser = !navigator.userAgent.startsWith("Mozilla/5.0 "); console.log(typeof navigator, navigator.userAgent, browser); console.log(typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers"); console.log(navigator.userAgent.split("/")[0], navigator.userAgent.length); +console.log(navigator.hardwareConcurrency > 0, Number.isInteger(navigator.hardwareConcurrency)); +console.log(typeof navigator.language, navigator.languages.length, navigator.languages[0] === navigator.language); +console.log(navigator.platform.length > 0, navigator.languages === navigator.languages); diff --git a/tests/harness/__snapshots__/node-types-fenced.txt b/tests/harness/__snapshots__/node-types-fenced.txt index 32eb08fba..a7618fa58 100644 --- a/tests/harness/__snapshots__/node-types-fenced.txt +++ b/tests/harness/__snapshots__/node-types-fenced.txt @@ -232,11 +232,29 @@ fenced.ts:108:9 - error SC2020: 'crypto.subtle.getPublicKey' is part of the stan hint: the native WebCrypto surface is crypto.subtle's importKey, generateKey and exportKey for HMAC and AES keys (importKey also for PBKDF2 and HKDF keys), encrypt and decrypt with AES-GCM, AES-CBC or AES-CTR, wrapKey and unwrapKey of secret keys with AES-KW or those ciphers, deriveBits and deriveKey with PBKDF2 or HKDF, sign and verify with HMAC, generateKey, importKey, exportKey, sign and verify for ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and RSA-PSS, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle -fenced.ts:113:15 - error SC2020: 'navigator.hardwareConcurrency' is typed by @types/node but has no scriptc lowering yet +fenced.ts:113:7 - error SC2020: 'LockManager' is typed by @types/node but has no scriptc lowering yet - 112 | * compile; the other members fence by name. */ - 113 | const cores = navigator.hardwareConcurrency; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 114 | void cores; + 112 | * by name. */ + 113 | const locks = navigator.locks; + | ^~~~~ + 114 | void locks; - hint: of Node's navigator global only typeof navigator and navigator.userAgent compile \ No newline at end of file + hint: the type checker sees everything @types/node declares, but only the supported surface compiles (https://scriptc.dev/limitations) + +fenced.ts:113:15 - error SC2020: 'navigator.locks' is typed by @types/node but has no scriptc lowering yet + + 112 | * by name. */ + 113 | const locks = navigator.locks; + | ^~~~~~~~~~~~~~~ + 114 | void locks; + + hint: navigator.locks (the Web Locks API) has no lowering + +fenced.ts:114:6 - error SC2004: uses of 'locks' inherit the blocker on its declaration + + 113 | const locks = navigator.locks; + 114 | void locks; + | ^~~~~ + 115 | // End of the declared-but-not-lowered surface. + + hint: the declaration of 'locks' did not compile — fix the diagnostic reported there and these sites clear with it \ No newline at end of file diff --git a/tests/harness/corpus-costs/8161-global-computed-reads.cjs.json b/tests/harness/corpus-costs/8161-global-computed-reads.cjs.json new file mode 100644 index 000000000..24129b327 --- /dev/null +++ b/tests/harness/corpus-costs/8161-global-computed-reads.cjs.json @@ -0,0 +1 @@ +{ "plain": 3708, "san": 12618 } diff --git a/tests/harness/corpus-costs/8162-navigator-global.cjs.json b/tests/harness/corpus-costs/8162-navigator-global.cjs.json new file mode 100644 index 000000000..433a92b5d --- /dev/null +++ b/tests/harness/corpus-costs/8162-navigator-global.cjs.json @@ -0,0 +1 @@ +{ "plain": 3686, "san": 6899 } diff --git a/tests/harness/corpus-costs/8163-data-function-values.cjs.json b/tests/harness/corpus-costs/8163-data-function-values.cjs.json new file mode 100644 index 000000000..5c1fdff5e --- /dev/null +++ b/tests/harness/corpus-costs/8163-data-function-values.cjs.json @@ -0,0 +1 @@ +{ "plain": 3362, "san": 7680 } diff --git a/tests/harness/corpus-costs/8164-atob-validation.cjs.json b/tests/harness/corpus-costs/8164-atob-validation.cjs.json new file mode 100644 index 000000000..5fd135960 --- /dev/null +++ b/tests/harness/corpus-costs/8164-atob-validation.cjs.json @@ -0,0 +1 @@ +{ "plain": 3065, "san": 4575 } From 317d724a7b439cf8056eee530bf9cf9458227b16 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 10:46:52 -0700 Subject: [PATCH 100/230] Keep bound alias instantiations on the lowering pass The compiler is compiled by scriptc and keeps caches on their owners instead of weak collections (compiler-weak-collections.test.ts). Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/generic-functions.ts | 25 ++----------------- .../compiler/src/frontend/lowering/lowerer.ts | 15 ++++++++--- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index 7d7124027..a2eda839a 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -921,27 +921,6 @@ function bindDefaultTypeParams( }); } -/** Generic alias references of a call-keyed instance's declared signature - * (`Catalog`), each paired with the same alias as the call's resolved - * signature instantiates it (`Catalog<{ g1: … }, "acme">`). The checker - * keeps the alias symbolic inside the generic body and offers no way to - * instantiate it with the bound arguments, but alias instantiations are - * interned: the body's `x as Catalog` is the very type the signature - * declares, so the pair recorded during unification is that re-instantiation. - * Keyed by the instance's ts-level bindings map, which every scope that - * lowers the instance installs (typeParamTsBindings). */ -const boundAliasInstances = new WeakMap, Map>(); - -/** The concrete instantiation of a generic alias reference inside the - * instance whose ts-level bindings are `tsBindings` (boundAliasInstances), - * or null when its signature never instantiated that reference. */ -export function boundAliasInstance( - tsBindings: Map, - type: ts.Type, -): ts.Type | null { - return boundAliasInstances.get(tsBindings)?.get(type) ?? null; -} - /** The structural half of binding inference: unify the DECLARED signature * (whose types mention the type parameters) against a TARGET signature * with the substitution applied — a call's resolved signature, or the @@ -1062,8 +1041,8 @@ function unifySignatureBindings( iAliasArgs.length === dAliasArgs.length ) { if (tsBindings && declared !== inst) { - let pairs = boundAliasInstances.get(tsBindings); - if (!pairs) boundAliasInstances.set(tsBindings, (pairs = new Map())); + let pairs = lowerer.boundAliasInstances.get(tsBindings); + if (!pairs) lowerer.boundAliasInstances.set(tsBindings, (pairs = new Map())); // A body forwarding its own alias (`inner(g)` returning `Catalog` inside an instance) resolves through the enclosing one. if (!pairs.has(declared)) pairs.set(declared, lowerer.boundAliasTsResolver(inst) ?? inst); diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 52f3567fb..216851c9c 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -456,7 +456,6 @@ import { inferTypeParamBindings, lowerGenericInstance, genericLocalClassBinding, - boundAliasInstance, } from "./generic-functions.js"; import { bindingNeverReassigned } from "./binding-analysis.js"; import type { InstantiationPath } from "./instantiation-path.js"; @@ -2234,6 +2233,16 @@ export class Lowerer { * the body needs — indexed accesses (`T[K]` needs K's literal key) and * keyed record reads (`o[k]` where k's type is a literal-bound K). */ typeParamTsBindings: Map | null = null; + /** Generic alias references of a call-keyed instance's declared signature + * (`Catalog`), each paired with the same alias as the call's + * resolved signature instantiates it (`Catalog<{ g1: … }, "acme">`), + * keyed by the instance's ts-level bindings (typeParamTsBindings). The + * checker keeps the alias symbolic inside the generic body and offers no + * way to instantiate it with the bound arguments, but alias + * instantiations are interned: the body's `x as Catalog` is the + * very type the signature declares, so the pair recorded during binding + * unification is that re-instantiation. */ + readonly boundAliasInstances = new Map, Map>(); /** Non-null while an IMPLICIT-ANY instance body lowers (npm-static JS; * see generic-functions.ts): bound param symbol → * the call site's checker type, consulted by typeOf for identifier @@ -9554,10 +9563,10 @@ export class Lowerer { /** The concrete instantiation of a generic alias reference (`Catalog`) the current call-keyed instance's signature declares - * (boundAliasInstance). Inert outside generic function instantiation. */ + * (boundAliasInstances). Inert outside generic function instantiation. */ readonly boundAliasTsResolver = (t: ts.Type): ts.Type | null => { if (!this.typeParamTsBindings || t.getAliasTypeArguments().length === 0) return null; - return boundAliasInstance(this.typeParamTsBindings, t); + return this.boundAliasInstances.get(this.typeParamTsBindings)?.get(t) ?? null; }; irTypeOf(node: ts.Node): IrType { From 6000485a17155e71dba5a33c482a347309aadbba Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:04:10 -0500 Subject: [PATCH 101/230] Report either decryption error of a wrong passphrase in corpus 8183 With a fresh key and IV each run, Node's OpenSSL now and then accepts the padding of a wrong-passphrase decryption and rejects the decrypted structure instead: ERR_OSSL_UNSUPPORTED rather than ERR_OSSL_BAD_DECRYPT, in about 1 run in 100. The program now prints both as one decryption error. --- tests/corpus/8183-key-passphrase-encoding.cjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/corpus/8183-key-passphrase-encoding.cjs b/tests/corpus/8183-key-passphrase-encoding.cjs index e52fa6c1c..ca817f386 100644 --- a/tests/corpus/8183-key-passphrase-encoding.cjs +++ b/tests/corpus/8183-key-passphrase-encoding.cjs @@ -9,12 +9,17 @@ const hex = Buffer.from(pass).toString("hex"); const pem = privateKey.export({ format: "pem", type: "pkcs8", cipher: "aes-256-cbc", passphrase: hex, encoding: "hex" }); const der = privateKey.export({ format: "der", type: "pkcs8", cipher: "aes-128-cbc", passphrase: pass }); +// A wrong passphrase usually fails the decryption's padding check +// (ERR_OSSL_BAD_DECRYPT); with a fresh key and IV each run, Node's +// OpenSSL now and then accepts the padding and rejects the decrypted +// structure instead (ERR_OSSL_UNSUPPORTED). Both are reported as one. function attempt(label, options) { try { const key = c.createPrivateKey(options); console.log(label, key.asymmetricKeyType, key.equals(privateKey)); } catch (e) { - console.log(label, "throws", e.code); + const wrong = e.code === "ERR_OSSL_BAD_DECRYPT" || e.code === "ERR_OSSL_UNSUPPORTED"; + console.log(label, "throws", wrong ? "a decryption error" : e.code); } } From a2be7d8bc03ca9be9a3e1f941bbc4d759e449bd3 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 12:05:14 -0700 Subject: [PATCH 102/230] Answer Object.prototype's methods on typed records Records inherit toString, toLocaleString, valueOf, hasOwnProperty, isPrototypeOf and propertyIsEnumerable. Calls of these methods by name run an own callable entry when the record's type allows one and the record holds it, and otherwise the built-in with the record as receiver; toLocaleString, valueOf and isPrototypeOf on records were refused, and toString ignored an own entry. Keyed reads of records of functions answer the built-in as a native function value when the key misses: bound to the record when called at once, with an undefined `this` otherwise. The backend fuses the read with its fallback, so hits cost nothing extra. Records of other value types keep reading undefined for these names; the limitations page says so. Co-Authored-By: Claude Opus 5.5 --- .../record-identity-and-property-order.mdx | 7 + .../compiler/src/backend/llvm/expr-control.ts | 22 +- .../compiler/src/backend/llvm/expr-records.ts | 40 +- .../lowering/expressions/element-access.ts | 13 +- .../lowering/expressions/property-access.ts | 7 + .../src/frontend/lowering/lower-calls.ts | 4 + .../src/frontend/lowering/record-inherited.ts | 815 ++++++++++++++++++ ...8360-record-prototype-method-calls.ts.json | 6 + ...61-record-function-table-inherited.ts.json | 6 + .../8360-record-prototype-method-calls.ts | 56 ++ .../8361-record-function-table-inherited.ts | 40 + ...8360-record-prototype-method-calls.ts.json | 1 + ...61-record-function-table-inherited.ts.json | 1 + 13 files changed, 1014 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/record-inherited.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8360-record-prototype-method-calls.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8361-record-function-table-inherited.ts.json create mode 100644 tests/corpus/8360-record-prototype-method-calls.ts create mode 100644 tests/corpus/8361-record-function-table-inherited.ts create mode 100644 tests/harness/corpus-costs/8360-record-prototype-method-calls.ts.json create mode 100644 tests/harness/corpus-costs/8361-record-function-table-inherited.ts.json diff --git a/docs/content/limitations/record-identity-and-property-order.mdx b/docs/content/limitations/record-identity-and-property-order.mdx index dd180cb7b..fc0b2be92 100644 --- a/docs/content/limitations/record-identity-and-property-order.mdx +++ b/docs/content/limitations/record-identity-and-property-order.mdx @@ -25,3 +25,10 @@ Index-signature records list declared fields before the other keys. Optional fields and properties added through a record's `unknown` view can be deleted. A required field has no absent state, so deleting it through the view throws `TypeError: Cannot delete property 'x' of #` and leaves the record unchanged, where Node.js removes the property. + +Records inherit `toString`, `toLocaleString`, `valueOf`, `hasOwnProperty`, `isPrototypeOf` and `propertyIsEnumerable` from `Object.prototype`. +A call of one of these methods by name runs the built-in with the record as its receiver, unless the record holds an own function under that name. +A keyed read such as `handlers[name]` of a record whose values are functions answers the built-in when the record lacks the key, so truthiness, `typeof` and calls match Node.js. +A keyed read of a record whose values are not functions, such as `counts["toString"]` on a `Record`, answers `undefined` where Node.js answers the inherited function, so `counts[word] ?? 0` counts such a word as new. +Calling a built-in read from a record of functions throws a `TypeError` when its answer does not fit the record's function type, for example `valueOf` read from a `Record number>`. +Other inherited names, such as `constructor`, read `undefined` from typed records. diff --git a/packages/compiler/src/backend/llvm/expr-control.ts b/packages/compiler/src/backend/llvm/expr-control.ts index 2f7c31813..5ffa54eb5 100644 --- a/packages/compiler/src/backend/llvm/expr-control.ts +++ b/packages/compiler/src/backend/llvm/expr-control.ts @@ -2,11 +2,12 @@ import { emitBorrowedInput } from "./borrowed-inputs.js"; /* Focused LLVM expression emission extracted from emitter.ts. */ import { InternalCompilerError } from "../../errors.js"; import { undefinedArmTag } from "../../ir/analysis.js"; -import { isRefCounted, isUnitType, typeEquals, type IrExpr } from "../../ir/ir.js"; +import { isRefCounted, isUnitType, typeEquals, type IrExpr, type IrType } from "../../ir/ir.js"; import { DYN_KIND } from "./dyn.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext, ExprOf, LlValue } from "./expr-context.js"; import { f64Lit } from "./common.js"; +import { emitRecordKeyGetOr } from "./expr-records.js"; import { NULLABLE_NULL, SHAPE_WORD_INDEX, shapeWordHeader } from "./nullable-unions.js"; import { emitUnionTag } from "./union-repr.js"; @@ -397,6 +398,13 @@ export function emitControlExpr( throw new InternalCompilerError("llvm emitter bug: nullish union lacks unit arms"); const fused = emitNullishOptionalChain(host, e); if (fused) return fused; + if ( + e.left.kind === "recordKeyGet" && + e.left.missNaN !== true && + typeEquals(e.type, e.left.type) && + storesNoUnit(host, e.left) + ) + return emitRecordKeyGetOr(host, e.left, e.right); if ( !typeEquals(e.type, e.left.type) && (e.left.kind === "varRef" || @@ -700,3 +708,15 @@ function emitUnionTagSet(host: LlvmEmitterContext, set: TagSet): string { B.line(`${t} = phi i1 [ true, %${yes} ], [ false, %${no} ]`); return t; } + +/** Every value a keyed read can hit is present (no stored undefined or + * null), so only a missing key yields the read's unit arm. */ +function storesNoUnit(host: LlvmEmitterContext, e: ExprOf<"recordKeyGet">): boolean { + const shape = host.recordShape(e.shapeId); + const holdsUnit = (t: IrType): boolean => + isUnitType(t) || + t.kind === "dyn" || + (t.kind === "union" && (host.unionsById.get(t.unionId)?.arms ?? []).some(isUnitType)); + if (shape.indexValue !== undefined && holdsUnit(shape.indexValue)) return false; + return e.overflowOnly === true || !shape.fields.some((f) => holdsUnit(f.type)); +} diff --git a/packages/compiler/src/backend/llvm/expr-records.ts b/packages/compiler/src/backend/llvm/expr-records.ts index 59ab625e5..86f20fd45 100644 --- a/packages/compiler/src/backend/llvm/expr-records.ts +++ b/packages/compiler/src/backend/llvm/expr-records.ts @@ -338,6 +338,39 @@ export function emitRegexIntrinsic( } } +/** `r[k] ?? d` in its pass-through shape: a missing key evaluates `d` + * where the read would store undefined, so a hit costs nothing extra. */ +export function emitRecordKeyGetOr( + host: LlvmEmitterContext, + e: IrExpr & { kind: "recordKeyGet" }, + fallback: IrExpr, +): LlValue { + const B = host.B; + const obj = host.emitExpr(e.obj); + const k = host.emitExpr(e.key); + const ty = host.llType(e.type); + const slot = B.slot(); + B.entryAllocas.push(`${slot} = alloca ${ty}`); + const join = B.newLabel("rkg.j"); + keyedRecordReadInto( + host, + slot, + join, + obj.name, + k.name, + e.shapeId, + e.type, + e.overflowOnly === true, + e.loc, + false, + () => host.emitBranchInto(slot, fallback), + ); + B.startBlock(join); + const t = B.tmp(); + B.line(`${t} = load ${ty}, ptr ${slot}`); + return host.own({ name: t, type: e.type }); +} + export function emitRecordKeyGet( host: LlvmEmitterContext, e: IrExpr & { kind: "recordKeyGet" }, @@ -485,6 +518,7 @@ export function keyedRecordReadInto( overflowOnly: boolean, loc?: SrcLoc, missNaN = false, + missInto: (() => void) | null = null, ): void { const B = host.B; const shape = host.recordShape(shapeId); @@ -687,8 +721,10 @@ export function keyedRecordReadInto( } } if (def !== null && resultType.kind === "union" && undefTag >= 0) { - // The miss path: the result union's undefined arm. - B.line(`store ptr ${host.unitInstanceRef(resultType.unionId, undefTag)}, ptr ${slot}`); + // The miss path: the result union's undefined arm, or the caller's + // own value for a miss (missInto stores it into the slot). + if (missInto !== null) missInto(); + else B.line(`store ptr ${host.unitInstanceRef(resultType.unionId, undefTag)}, ptr ${slot}`); B.br(join); return; } diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index a2f2ebdb6..565d0f843 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -69,6 +69,7 @@ import { InternalCompilerError } from "../../../errors.js"; import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../../ir/build.js"; import { npmStaticPackageOfPath } from "../../npm-static.js"; import { isSafeToRepeat } from "./evaluation-safety.js"; +import { withInheritedMethods } from "../record-inherited.js"; import { lowerClassPrototypeComputedAssignment } from "../class-prototypes.js"; import { isClassCallback } from "../class-callbacks.js"; import { isKeyedFieldAccess } from "./keyed-field-targets.js"; @@ -1444,7 +1445,17 @@ export function lowerRecordKeyRead( loc, }; if (nanCoded) return read; - return includeUndefined || optionalRead ? read : lowerer.maybeNarrow(read, expr); + const answered = withInheritedMethods(lowerer, read, litKey, isCalledAccess(expr)); + return includeUndefined || optionalRead ? answered : lowerer.maybeNarrow(answered, expr); +} + +/** The access is the callee of a call (`o[k](…)`, `o[k]?.(…)`, `o[k]!(…)`), + * which passes the object as `this`. */ +function isCalledAccess(node: ts.Expression): boolean { + let child: ts.Expression = node; + while (ts.isParenthesizedExpression(child.parent) || ts.isNonNullExpression(child.parent)) + child = child.parent; + return ts.isCallExpression(child.parent) && child.parent.expression === child; } /** ToPropertyKey for represented record keys. Missing array-derived string diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 35bb16cd3..f213b6cbf 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -115,6 +115,7 @@ import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../. import { typedArrayLacksMember } from "../builtins/typed-array-members.js"; import { TEMPORAL_GLOBAL } from "../../builtin-modules.js"; import { tryLowerExpression } from "./try-lower-expression.js"; +import { lowerRecordInheritedMember } from "../record-inherited.js"; import { DATE_METHOD_HINT } from "../builtins/dates.js"; import { cjsClassExprWholeExportOf, @@ -3188,6 +3189,12 @@ export function lowerFieldRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressi loc: locOf(expr), }; } + // Object.prototype's methods read as values: an own entry of the + // member's function type, else the built-in. + if (actual?.type.kind === "record") { + const inherited = lowerRecordInheritedMember(lowerer, actual, expr); + if (inherited) return inherited; + } // A member the checker declares on the receiver, stored in a pure // index-signature record (an object literal that spread one, see // dictSpreadLiteralShape): the key is always present, so the keyed diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index c8f02562e..aeb1993d4 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -280,6 +280,7 @@ import { } from "./generic-functions.js"; import { codeGenerationCalleeOf, lowerCodeGenerationCall } from "./code-generation.js"; +import { lowerRecordInheritedCall } from "./record-inherited.js"; const ZLIB_STREAM_METHODS: ReadonlySet = new Set(["flush", "params", "close", "reset"]); @@ -1643,6 +1644,9 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // Union receivers whose every arm has a text — the ngrok // `(chunk: Buffer | string) => chunk.toString()` idiom. lowerUnionToStringCall(lowerer, expr, expr.expression) ?? + // Object.prototype's methods called by name on records: an own + // callable entry, else the built-in with the record as receiver. + lowerRecordInheritedCall(lowerer, expr, expr.expression) ?? // Object.prototype.toString's default answer on records and // override-free program classes — "[object Object]", folded. lowerDefaultToStringCall(lowerer, expr, expr.expression) ?? diff --git a/packages/compiler/src/frontend/lowering/record-inherited.ts b/packages/compiler/src/frontend/lowering/record-inherited.ts new file mode 100644 index 000000000..2c01ccd0a --- /dev/null +++ b/packages/compiler/src/frontend/lowering/record-inherited.ts @@ -0,0 +1,815 @@ +/* Object.prototype's methods on typed records. + * + * A record is an ordinary object, so `r.toString()`, `r.hasOwnProperty(k)` + * and the other Object.prototype methods answer from its prototype unless + * the record holds an own entry under that name. Method calls with a + * constant name call an own entry when the record's type lets one be + * callable and otherwise run the built-in natively with the record as its + * receiver. Keyed reads of records whose values are functions or checked + * values answer the built-in as a native function value on the miss path, + * so truthiness, typeof and calls behave as in Node; hits are untouched. + * Records of other value types keep reading undefined for these names + * (a documented limitation). No dynamic-tier unit is involved: every + * built-in is a small lifted function over the record. */ +import * as ts from "../ts7/adapter.js"; +import { dynUndefinedExpr, nodeThrowExpr, varRef } from "../../ir/build.js"; +import { + BOOL, + DYN, + STRING, + UNDEFINED_T, + isUnitType, + typeEquals, + typeKey, + type IrExpr, + type IrLocal, + type IrStmt, + type IrType, + type SrcLoc, +} from "../../ir/ir.js"; +import type { Lowerer } from "./lowerer.js"; +import { locOf } from "../program.js"; +import { keyedHelperName } from "./stable-names.js"; +import { isSafeToRepeat } from "./expressions/evaluation-safety.js"; +import { nullProtoRecordShape, recordNullProtoTest } from "./null-prototype.js"; +import { recordHasOwnHelper } from "./lower-calls.js"; +import { OTHER_ARM_FAILURE_MESSAGE } from "./coercions/unions.js"; + +/** Object.prototype's methods a record answers from its prototype. */ +export const RECORD_INHERITED_METHODS: ReadonlySet = new Set([ + "toString", + "toLocaleString", + "valueOf", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", +]); + +type FuncType = IrType & { kind: "func" }; + +/** Declares a hidden local in the function being built: the lowerer's + * current function, or a lifted helper's own local list. */ +type DeclareLocal = (name: string, type: IrType) => string; + +function liftedLocals(): { declare: DeclareLocal; locals: IrLocal[] } { + const locals: IrLocal[] = []; + return { + locals, + declare: (name, type) => { + const id = `${name}.${locals.length}`; + locals.push({ id, name, type, mutable: false }); + return id; + }, + }; +} + +/** The form a read of `type` gives a built-in method value: the union's one + * function arm (beside an undefined arm), a checked value, or null when the + * type cannot hold a function. */ +function methodValueForm(lowerer: Lowerer, type: IrType): FuncType | "dyn" | null { + if (type.kind === "dyn") return "dyn"; + if (type.kind !== "union") return null; + const arms = lowerer.unions.get(type.unionId)?.arms ?? []; + if (!arms.some((arm) => arm.kind === "undefinedT")) return null; + const funcs = arms.filter((arm): arm is FuncType => arm.kind === "func"); + if (funcs.length !== 1 || funcs[0]!.restAbi !== undefined) return null; + return funcs[0]!; +} + +/** A value of `type` may be a function that a call can enter. */ +function callableType(lowerer: Lowerer, type: IrType): boolean { + if (type.kind === "func" || type.kind === "dyn") return true; + if (type.kind !== "union") return false; + return (lowerer.unions.get(type.unionId)?.arms ?? []).some( + (arm) => arm.kind === "func" || arm.kind === "dyn", + ); +} + +/** A keyed record read (`r[k]`, `r["toString"]`) whose miss answers the + * inherited Object.prototype method for those names. Hits keep the + * ordinary read; only the miss path tests the key. `bound`: the read is + * the callee of a call (`r[k](…)`, `r[k]?.(…)`), whose `this` is the + * record; any other read yields the method as a plain value, whose calls + * see an undefined `this` as in Node. */ +export function withInheritedMethods( + lowerer: Lowerer, + read: IrExpr & { kind: "recordKeyGet" }, + litKey: string | null, + bound: boolean, +): IrExpr { + if (litKey !== null && !RECORD_INHERITED_METHODS.has(litKey)) return read; + if (read.missNaN === true) return read; + const form = methodValueForm(lowerer, read.type); + const shape = lowerer.shapes.get(read.shapeId); + if (form === null || !shape || shape.tuple) return read; + const loc = read.loc; + const stmts: IrStmt[] = []; + const stable = (value: IrExpr, name: string): IrExpr => { + if (isSafeToRepeat(value) || value.kind === "strLit") return value; + const local = lowerer.declareHiddenLocal(name, value.type); + stmts.push({ kind: "varDecl", localId: local.id, init: value, loc }); + return varRef(local.id, value.type, loc); + }; + const obj = stable(read.obj, "%inheritedRecv"); + const key = stable(read.key, "%inheritedKey"); + const helper = inheritedReadHelper(lowerer, read.shapeId, read.type, form, bound, loc); + const fallback: IrExpr = { kind: "call", callee: helper, args: [obj, key], type: read.type, loc }; + if ( + form !== "dyn" && + read.type.kind === "union" && + !(lowerer.unions.get(read.type.unionId)?.arms ?? []).some((arm) => arm.kind === "nullT") + ) { + // `r[k] ?? inherited(r, k)`: the hit is the read's own value, untouched. + const coalesced: IrExpr = { + kind: "nullish", + left: { ...read, obj, key }, + right: fallback, + type: read.type, + loc, + }; + return stmts.length === 0 + ? coalesced + : { kind: "seqExpr", stmts, result: coalesced, type: read.type, loc }; + } + const value = lowerer.declareHiddenLocal("%inheritedRead", read.type); + stmts.push({ kind: "varDecl", localId: value.id, init: { ...read, obj, key }, loc }); + const found = varRef(value.id, read.type, loc); + let missing: IrExpr; + if (form === "dyn") { + missing = { kind: "dynTest", test: "undefined", value: found, type: BOOL, loc } as IrExpr; + } else { + const unionId = (read.type as IrType & { kind: "union" }).unionId; + const tag = (lowerer.unions.get(unionId)?.arms ?? []).findIndex( + (arm) => arm.kind === "undefinedT", + ); + missing = { kind: "unionIsTag", unionId, tag, negated: false, value: found, type: BOOL, loc }; + } + return { + kind: "seqExpr", + stmts, + result: { + kind: "ternary", + cond: missing, + then: fallback, + else_: found, + type: read.type, + loc, + }, + type: read.type, + loc, + }; +} + +/** `%rec.inherited.(o, k)`: the inherited method `k` names as a value + * of `readType` (bound to `o`), or undefined. */ +function inheritedReadHelper( + lowerer: Lowerer, + shapeId: string, + readType: IrType, + form: FuncType | "dyn", + bound: boolean, + loc: SrcLoc, +): string { + const key = `rec.inherited:${bound ? "bound" : "value"}:${shapeId}:${typeKey(readType)}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, "%rec.inherited", key); + lowerer.arrHofHelpers.set(key, name); + const recT: IrType = { kind: "record", shapeId }; + const o = varRef("o.0", recT, loc); + const k = varRef("k.0", STRING, loc); + const fnType: FuncType = + form === "dyn" ? ({ kind: "func", params: [DYN], ret: DYN } as FuncType) : form; + const undefinedValue: IrExpr = + form === "dyn" + ? dynUndefinedExpr(loc) + : lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + readType, + ); + const named = (method: string): IrExpr => ({ + kind: "strEq", + negated: false, + left: k, + right: { kind: "strLit", value: method, type: STRING, loc }, + type: BOOL, + loc, + }); + const methods = [...RECORD_INHERITED_METHODS]; + let anyName: IrExpr = named(methods[0]!); + for (const method of methods.slice(1)) + anyName = { kind: "logical", op: "||", left: anyName, right: named(method), type: BOOL, loc }; + const body: IrStmt[] = [ + // Most misses name no inherited method: one comparison chain, nothing else. + { + kind: "if", + cond: { kind: "unary", op: "!", operand: anyName, type: BOOL, loc }, + then: [{ kind: "return", value: { ...undefinedValue }, loc }], + else_: null, + loc, + }, + // An own key holding undefined shadows the prototype like any own key. + { + kind: "if", + cond: { + kind: "call", + callee: recordHasOwnHelper(lowerer, shapeId, loc), + args: [o, k], + type: BOOL, + loc, + }, + then: [{ kind: "return", value: { ...undefinedValue }, loc }], + else_: null, + loc, + }, + ]; + if (nullProtoRecordShape(lowerer, recT)) + body.push({ + kind: "if", + cond: recordNullProtoTest(o, shapeId, loc), + then: [{ kind: "return", value: { ...undefinedValue }, loc }], + else_: null, + loc, + }); + for (const method of methods) { + const value: IrExpr = bound + ? { + kind: "call", + callee: boundMethodFactory(lowerer, shapeId, method, fnType, loc), + args: [o], + type: fnType, + loc, + } + : { + kind: "closure", + fnName: unboundMethodImpl(lowerer, method, fnType, loc), + captures: [], + type: fnType, + loc, + }; + body.push({ + kind: "if", + cond: named(method), + then: [ + { + kind: "return", + value: lowerer.coerceToExpected(value, form === "dyn" ? DYN : readType), + loc, + }, + ], + else_: null, + loc, + }); + } + body.push({ kind: "return", value: { ...undefinedValue }, loc }); + lowerer.liftedFns.push({ + name, + params: [ + { localId: "o.0", name: "o", type: recT }, + { localId: "k.0", name: "k", type: STRING }, + ], + returnType: readType, + locals: [ + { id: "o.0", name: "o", type: recT, mutable: false }, + { id: "k.0", name: "k", type: STRING, mutable: false }, + ], + body, + loc, + }); + return name; +} + +/** `%rec.bind.(o)`: the built-in `method` bound to `o` as a closure. */ +function boundMethodFactory( + lowerer: Lowerer, + shapeId: string, + method: string, + fnType: FuncType, + loc: SrcLoc, +): string { + const key = `rec.bind:${shapeId}:${method}:${typeKey(fnType)}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, "%rec.bind", key); + lowerer.arrHofHelpers.set(key, name); + const recT: IrType = { kind: "record", shapeId }; + lowerer.liftedFns.push({ + name, + params: [{ localId: "o.0", name: "o", type: recT }], + returnType: fnType, + locals: [{ id: "o.0", name: "o", type: recT, mutable: false, boxed: true }], + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: inheritedMethodImpl(lowerer, shapeId, method, fnType, loc), + captures: ["o.0"], + type: fnType, + loc, + }, + loc, + }, + ], + loc, + }); + return name; +} + +/** The lifted body of one built-in bound to its record, with the read's + * function signature: the built-in's answer converted to the signature's + * return type, or the checked-conversion TypeError where it cannot be. */ +function inheritedMethodImpl( + lowerer: Lowerer, + shapeId: string, + method: string, + fnType: FuncType, + loc: SrcLoc, +): string { + const key = `rec.inheritedImpl:${shapeId}:${method}:${typeKey(fnType)}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, `%rec.${method}`, key); + lowerer.arrHofHelpers.set(key, name); + const recT: IrType = { kind: "record", shapeId }; + const o = varRef("o.0", recT, loc); + const params = fnType.params.map((type, i) => ({ localId: `p${i}.0`, name: `p${i}`, type })); + const args = params.map((p) => varRef(p.localId, p.type, loc)); + const body: IrStmt[] = []; + const hidden = liftedLocals(); + const answer = builtinAnswer(lowerer, hidden.declare, o, shapeId, method, args, fnType.ret, loc); + body.push(...returnAs(lowerer, answer, fnType.ret, loc)); + lowerer.liftedFns.push({ + name, + params, + returnType: fnType.ret, + captures: [{ localId: "o.0", name: "o", type: recT }], + locals: [ + { id: "o.0", name: "o", type: recT, mutable: false, boxed: true }, + ...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + ...hidden.locals, + ], + body, + loc, + }); + return name; +} + +/** One built-in as a plain function value (no receiver): its answer for an + * undefined `this`, converted like inheritedMethodImpl's. */ +function unboundMethodImpl( + lowerer: Lowerer, + method: string, + fnType: FuncType, + loc: SrcLoc, +): string { + const key = `rec.unboundImpl:${method}:${typeKey(fnType)}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const name = keyedHelperName(lowerer, `%obj.${method}`, key); + lowerer.arrHofHelpers.set(key, name); + const params = fnType.params.map((type, i) => ({ localId: `p${i}.0`, name: `p${i}`, type })); + const arg: IrExpr | undefined = params[0] && varRef(params[0].localId, params[0].type, loc); + const coerce = "Cannot convert undefined or null to object"; + let answer: IrExpr; + switch (method) { + case "toString": + answer = { kind: "strLit", value: "[object Undefined]", type: STRING, loc }; + break; + case "toLocaleString": + answer = nodeThrowExpr( + 1, + "", + "Object.prototype.toLocaleString called on null or undefined", + STRING, + loc, + ); + break; + case "isPrototypeOf": { + // A primitive argument answers false before `this` converts. + const no: IrExpr = { kind: "boolLit", value: false, type: BOOL, loc }; + const kind = arg?.type.kind; + if ( + arg === undefined || + isUnitType(arg.type) || + kind === "string" || + kind === "f64" || + kind === "bool" || + kind === "bigint" + ) + answer = no; + else if (kind === "dyn") + answer = { + kind: "ternary", + cond: { + kind: "logical", + op: "||", + left: { kind: "dynTest", test: "object", value: arg, type: BOOL, loc }, + right: { kind: "dynTest", test: "function", value: arg, type: BOOL, loc }, + type: BOOL, + loc, + } as IrExpr, + then: nodeThrowExpr(1, "", coerce, BOOL, loc), + else_: no, + type: BOOL, + loc, + }; + else answer = nodeThrowExpr(1, "", coerce, BOOL, loc); + break; + } + default: + answer = nodeThrowExpr(1, "", coerce, method === "valueOf" ? DYN : BOOL, loc); + } + const body: IrStmt[] = + answer.kind === "libCall" && answer.fn === "error.nodeThrow" + ? [ + { + kind: "return", + value: nodeThrowExpr( + 1, + "", + (answer.args[2] as IrExpr & { kind: "strLit" }).value, + fnType.ret, + loc, + ), + loc, + }, + ] + : returnAs(lowerer, answer, fnType.ret, loc); + lowerer.liftedFns.push({ + name, + params, + returnType: fnType.ret, + locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + body, + loc, + }); + return name; +} + +/** The built-in's answer with `o` as its receiver. `want` is the type the + * caller needs (valueOf answers the record itself). */ +function builtinAnswer( + lowerer: Lowerer, + declare: DeclareLocal, + o: IrExpr, + shapeId: string, + method: string, + args: readonly IrExpr[], + want: IrType, + loc: SrcLoc, +): IrExpr { + switch (method) { + case "toString": + return { kind: "strLit", value: "[object Object]", type: STRING, loc }; + case "toLocaleString": + // Object.prototype.toLocaleString calls this.toString(). + return callOwnOrBuiltin( + lowerer, + declare, + o, + shapeId, + "toString", + [], + STRING, + "toString", + loc, + ); + case "valueOf": + return want.kind === "dyn" ? lowerer.coerceToExpected(o, DYN) : o; + case "isPrototypeOf": + // No object is created with a typed record as its prototype. + return { kind: "boolLit", value: false, type: BOOL, loc }; + default: { + const key = propertyKeyOf(lowerer, args[0], loc); + return { + kind: "call", + callee: recordHasOwnHelper(lowerer, shapeId, loc), + args: [o, key], + type: BOOL, + loc, + }; + } + } +} + +/** ToPropertyKey of a built-in's key argument (absent: "undefined"). */ +function propertyKeyOf(lowerer: Lowerer, arg: IrExpr | undefined, loc: SrcLoc): IrExpr { + if (arg === undefined || isUnitType(arg.type)) + return { + kind: "strLit", + value: arg?.type.kind === "nullT" ? "null" : "undefined", + type: STRING, + loc, + }; + if (arg.type.kind === "string") return arg; + if (arg.type.kind === "f64" || arg.type.kind === "bool") + return { kind: "toString", operand: arg, type: STRING, loc }; + return { + kind: "libCall", + fn: "dyn.toStringCoerce", + args: [lowerer.coerceToExpected(arg, DYN)], + type: STRING, + loc, + }; +} + +/** `value` returned as `ret`: as is, wrapped into a union arm or a checked + * value, discarded for void, or the checked-conversion TypeError. */ +function returnAs(lowerer: Lowerer, value: IrExpr, ret: IrType, loc: SrcLoc): IrStmt[] { + const converted = convertTo(lowerer, value, ret); + if (converted !== null) return [{ kind: "return", value: converted, loc }]; + if (ret.kind === "void") + return [ + { kind: "exprStmt", expr: value, loc }, + { kind: "return", value: null, loc }, + ]; + return [ + { kind: "exprStmt", expr: value, loc }, + { kind: "return", value: nodeThrowExpr(1, "", OTHER_ARM_FAILURE_MESSAGE, ret, loc), loc }, + ]; +} + +function convertTo(lowerer: Lowerer, value: IrExpr, want: IrType): IrExpr | null { + if (typeEquals(value.type, want)) return value; + if (want.kind === "dyn" && lowerer.dynConvertible(value.type)) + return lowerer.coerceToExpected(value, DYN); + if (want.kind === "union" && lowerer.armTag(want.unionId, value.type) >= 0) + return lowerer.coerceToExpected(value, want); + return null; +} + +/** `o.method(...args)` on a record: an own callable entry when the record + * holds one, else the built-in. `calleeText` names the callee for Node's + * "is not a function". */ +function callOwnOrBuiltin( + lowerer: Lowerer, + declare: DeclareLocal, + o: IrExpr, + shapeId: string, + method: string, + args: readonly IrExpr[], + want: IrType, + calleeText: string, + loc: SrcLoc, +): IrExpr { + const shape = lowerer.shapes.get(shapeId)!; + const field = shape.fields.find((f) => f.name === method); + const ownType = field ? field.type : shape.indexValue; + let builtin = builtinAnswer(lowerer, declare, o, shapeId, method, args, want, loc); + const recT: IrType = { kind: "record", shapeId }; + if (nullProtoRecordShape(lowerer, recT)) + builtin = { + kind: "ternary", + cond: recordNullProtoTest(o, shapeId, loc), + then: nodeThrowExpr(1, "", `${calleeText} is not a function`, builtin.type, loc), + else_: builtin, + type: builtin.type, + loc, + }; + const builtinAsWant = convertTo(lowerer, builtin, want) ?? builtin; + if (ownType === undefined || !callableType(lowerer, ownType)) return builtinAsWant; + const own: IrExpr = field + ? { kind: "recordGet", obj: o, shapeId, field: method, type: field.type, loc } + : { + kind: "recordKeyGet", + obj: o, + shapeId, + key: { kind: "strLit", value: method, type: STRING, loc }, + overflowOnly: true, + type: lowerer.withUndefinedArmOf(shape.indexValue!) ?? shape.indexValue!, + loc, + }; + const called = callOwn(lowerer, declare, own, args, builtinAsWant.type, calleeText, loc); + return { + kind: "ternary", + cond: { + kind: "call", + callee: recordHasOwnHelper(lowerer, shapeId, loc), + args: [o, { kind: "strLit", value: method, type: STRING, loc }], + type: BOOL, + loc, + }, + then: called, + else_: builtinAsWant, + type: builtinAsWant.type, + loc, + }; +} + +/** Call the own entry `own` (present, of any stored type) with `args`. A + * non-function throws Node's "is not a function"; a function whose + * parameters or result cannot carry the call throws the checked-conversion + * TypeError (the program's types claimed otherwise). */ +function callOwn( + lowerer: Lowerer, + declare: DeclareLocal, + own: IrExpr, + args: readonly IrExpr[], + want: IrType, + calleeText: string, + loc: SrcLoc, +): IrExpr { + const notFunction = nodeThrowExpr(1, "", `${calleeText} is not a function`, want, loc); + const lie = nodeThrowExpr(1, "", OTHER_ARM_FAILURE_MESSAGE, want, loc); + const localId = declare("%ownMethod", own.type); + const value = varRef(localId, own.type, loc); + const enter = (fn: IrExpr): IrExpr => { + if (fn.type.kind === "dyn") { + const result: IrExpr = { + kind: "dynCall", + callee: fn, + calleeName: calleeText, + args: args.map((a) => lowerer.coerceToExpected(a, DYN)), + type: DYN, + loc, + } as IrExpr; + return convertTo(lowerer, result, want) ?? sequence(result, lie, loc); + } + const sig = fn.type as FuncType; + if (sig.restAbi !== undefined) return lie; + const passed: IrExpr[] = []; + for (let i = 0; i < sig.params.length; i++) { + const param = sig.params[i]!; + const arg = args[i]; + const given = + arg === undefined + ? convertTo( + lowerer, + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + param, + ) + : convertTo(lowerer, arg, param); + if (given === null) return lie; + passed.push(given); + } + const result: IrExpr = { kind: "callValue", callee: fn, args: passed, type: sig.ret, loc }; + if (sig.ret.kind === "void") return sequence(result, lie, loc); + return convertTo(lowerer, result, want) ?? sequence(result, lie, loc); + }; + let call: IrExpr; + if (own.type.kind === "func" || own.type.kind === "dyn") call = enter(value); + else if (own.type.kind === "union") { + const unionId = own.type.unionId; + const arms = lowerer.unions.get(unionId)?.arms ?? []; + call = notFunction; + for (let tag = arms.length - 1; tag >= 0; tag--) { + const arm = arms[tag]!; + if (arm.kind !== "func" && arm.kind !== "dyn") continue; + call = { + kind: "ternary", + cond: { kind: "unionIsTag", unionId, tag, negated: false, value, type: BOOL, loc }, + then: enter({ kind: "unionNarrow", unionId, tag, value, type: arm, loc }), + else_: call, + type: want, + loc, + }; + } + } else call = notFunction; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId, init: own, loc }], + result: call, + type: want, + loc, + }; +} + +function sequence(first: IrExpr, result: IrExpr, loc: SrcLoc): IrExpr { + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: first, loc }], + result, + type: result.type, + loc, + }; +} + +/** `r.toString()`, `r.hasOwnProperty(k)` and the other Object.prototype + * methods called by name on a typed record (the checker resolved the + * member to Object.prototype's). Null for other calls. */ +export function lowerRecordInheritedCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + const method = access.name.text; + if ( + call.questionDotToken || + access.questionDotToken || + !RECORD_INHERITED_METHODS.has(method) || + call.arguments.nodes.some(ts.isSpreadElement) || + !lowerer.isStdlibMember(access) + ) + return null; + const recvT = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); + if (recvT?.kind !== "record") return null; + const shape = lowerer.shapes.get(recvT.shapeId); + if (!shape || shape.tuple) return null; + const want = + method === "valueOf" + ? (lowerer.mapTypeOf(lowerer.typeOf(call)) ?? recvT) + : method === "toString" || method === "toLocaleString" + ? STRING + : BOOL; + // Without a possible own function, toString and the own-key tests keep + // their existing lowerings (with the special record views they know). + if ( + (method === "hasOwnProperty" || method === "propertyIsEnumerable" || method === "toString") && + !shape.fields.some((f) => f.name === method && callableType(lowerer, f.type)) && + !(shape.indexValue && callableType(lowerer, shape.indexValue)) + ) + return null; + const loc = locOf(call); + const stmts: IrStmt[] = []; + const receiver = lowerer.lowerExpr(access.expression); + if (receiver.type.kind !== "record") return null; + const recvLocal = lowerer.declareHiddenLocal("%protoRecv", receiver.type); + stmts.push({ kind: "varDecl", localId: recvLocal.id, init: receiver, loc }); + const o = varRef(recvLocal.id, receiver.type, loc); + const args: IrExpr[] = []; + for (const node of call.arguments.nodes) { + const arg = lowerer.lowerExpr(node); + const local = lowerer.declareHiddenLocal("%protoArg", arg.type); + stmts.push({ kind: "varDecl", localId: local.id, init: arg, loc }); + args.push(varRef(local.id, arg.type, loc)); + } + const result = callOwnOrBuiltin( + lowerer, + (name, type) => lowerer.declareHiddenLocal(name, type).id, + o, + receiver.type.shapeId, + method, + args, + want, + ts.getTextOfNode(access), + loc, + ); + return { kind: "seqExpr", stmts, result, type: result.type, loc }; +} + +/** `r.toString` and the other Object.prototype methods read by name (not + * called) on an index-signature record: an own entry of the member's + * function type when the record holds one, else the built-in as a plain + * function value. Null when the checker's member type is not a function. */ +export function lowerRecordInheritedMember( + lowerer: Lowerer, + receiver: IrExpr, + expr: ts.PropertyAccessExpression, +): IrExpr | null { + const method = expr.name.text; + if (!RECORD_INHERITED_METHODS.has(method) || !lowerer.isStdlibMember(expr)) return null; + if (receiver.type.kind !== "record") return null; + const want = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (want?.kind !== "func" || want.restAbi !== undefined) return null; + const shapeId = receiver.type.shapeId; + const shape = lowerer.shapes.get(shapeId); + if (!shape || shape.tuple) return null; + const loc = locOf(expr); + const builtin: IrExpr = { + kind: "closure", + fnName: unboundMethodImpl(lowerer, method, want, loc), + captures: [], + type: want, + loc, + }; + const recvLocal = lowerer.declareHiddenLocal("%protoRecv", receiver.type); + const o = varRef(recvLocal.id, receiver.type, loc); + const stmts: IrStmt[] = [{ kind: "varDecl", localId: recvLocal.id, init: receiver, loc }]; + let result: IrExpr = builtin; + if (shape.indexValue && typeEquals(shape.indexValue, want)) { + const ownType = lowerer.withUndefinedArmOf(want); + if (ownType?.kind === "union") { + const read: IrExpr = { + kind: "recordKeyGet", + obj: o, + shapeId, + key: { kind: "strLit", value: method, type: STRING, loc }, + overflowOnly: true, + type: ownType, + loc, + }; + const tag = lowerer.armTag(ownType.unionId, want); + const ownLocal = lowerer.declareHiddenLocal("%ownMethod", ownType); + const own = varRef(ownLocal.id, ownType, loc); + stmts.push({ kind: "varDecl", localId: ownLocal.id, init: read, loc }); + result = { + kind: "ternary", + cond: { + kind: "unionIsTag", + unionId: ownType.unionId, + tag, + negated: false, + value: own, + type: BOOL, + loc, + }, + then: { kind: "unionNarrow", unionId: ownType.unionId, tag, value: own, type: want, loc }, + else_: builtin, + type: want, + loc, + }; + } + } + return { kind: "seqExpr", stmts, result, type: want, loc }; +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8360-record-prototype-method-calls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8360-record-prototype-method-calls.ts.json new file mode 100644 index 000000000..2bbaaaa88 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8360-record-prototype-method-calls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8360-record-prototype-method-calls.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8361-record-function-table-inherited.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8361-record-function-table-inherited.ts.json new file mode 100644 index 000000000..a9d8bf4b0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8361-record-function-table-inherited.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8361-record-function-table-inherited.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8360-record-prototype-method-calls.ts b/tests/corpus/8360-record-prototype-method-calls.ts new file mode 100644 index 000000000..980e4b5d2 --- /dev/null +++ b/tests/corpus/8360-record-prototype-method-calls.ts @@ -0,0 +1,56 @@ +// Object.prototype's methods called by name on typed records: the built-in +// with the record as its receiver, or an own entry that shadows it. +interface Point { + x: number; + y: number; +} +const counts: Record = { apple: 2, pear: 0 }; +const point: Point = { x: 1, y: 2 }; +const handlers: Record number> = { double: (n) => n * 2 }; +const own: Record string> = { + toString: () => "own toString", + valueOf: () => "own valueOf", +}; + +function line(...parts: unknown[]): void { + console.log(parts.map(String).join(" | ")); +} + +line("toString", counts.toString(), point.toString(), handlers.toString(), String(counts)); +line("toLocaleString", counts.toLocaleString(), point.toLocaleString()); +line("valueOf", counts.valueOf() === counts, point.valueOf() === point); +line( + "hasOwnProperty", + counts.hasOwnProperty("apple"), + counts.hasOwnProperty("pear"), + counts.hasOwnProperty("plum"), + counts.hasOwnProperty("toString"), + point.hasOwnProperty("x"), + point.hasOwnProperty("z"), + handlers.hasOwnProperty("double"), + handlers.hasOwnProperty("valueOf"), +); +line("propertyIsEnumerable", counts.propertyIsEnumerable("apple"), point.propertyIsEnumerable("toString")); +line("isPrototypeOf", counts.isPrototypeOf(point), point.isPrototypeOf(counts)); + +// Own entries shadow the built-ins; toLocaleString calls the own toString. +line("own", own.toString(), own.toLocaleString(), own.valueOf(), own.hasOwnProperty("toString")); +delete own["toString"]; +line("own deleted", own.toString(), own.toLocaleString()); +own.toString = () => "own again"; +line("own again", own.toString(), `${String(own.toString())}`); + +// Keys added at run time are own keys too. +const later: Record = {}; +for (const word of "toString valueOf plain toString".split(" ")) { + later[word] = (later.hasOwnProperty(word) ? later[word]! : 0) + 1; +} +line("later", JSON.stringify(later), later.hasOwnProperty("toString"), later.hasOwnProperty("constructor")); + +// A null-prototype record has no inherited methods. +const groups = Object.groupBy([1, 2, 3], (n): string => (n % 2 ? "odd" : "even")); +try { + line("groups", groups.toString()); +} catch (e) { + line("groups threw", (e as Error).name + ": " + (e as Error).message); +} diff --git a/tests/corpus/8361-record-function-table-inherited.ts b/tests/corpus/8361-record-function-table-inherited.ts new file mode 100644 index 000000000..eecf74f02 --- /dev/null +++ b/tests/corpus/8361-record-function-table-inherited.ts @@ -0,0 +1,40 @@ +// Records of functions used as dispatch tables meet Object.prototype's method +// names: a keyed read answers the inherited method, bound to the record when +// the read is called at once and a plain function value otherwise. +type Command = (arg: string) => unknown; +const commands: Record = { + echo: (arg) => arg, + shout: (arg) => arg.toUpperCase(), +}; +const inputs = process.argv.length > 99 ? [] : ["echo", "toString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "shout", "nope"]; +function show(v: unknown): string { + return v === commands ? "" : typeof v + ":" + String(v); +} +for (const name of inputs) { + const handler = commands[name]; + if (!handler) { + console.log(name, "unknown handler"); + continue; + } + console.log(name, typeof handler, "direct", show(commands[name](name === "hasOwnProperty" ? "echo" : "x"))); + console.log(name, "optional", show(commands[name]?.("shout"))); + try { + console.log(name, "detached", show(handler("echo"))); + } catch (e) { + console.log(name, "detached threw", (e as Error).name + ": " + (e as Error).message); + } +} +console.log(typeof commands["toString"], typeof commands["valueOf"], commands["toString"] === undefined, commands["toString"] === commands["toString"]); +console.log(String(commands["toString"]("x")), commands["hasOwnProperty"]("shout"), commands["hasOwnProperty"]("nope")); +const literal: Record = {}; +console.log(Object.keys(literal).length, literal["isPrototypeOf"]?.("x"), typeof literal.toString); + +// A word count whose words are not Object.prototype names keeps `?? 0`. +const freq: Record = {}; +for (const w of "a rose is a rose is a rose".split(" ")) freq[w] = (freq[w] ?? 0) + 1; +console.log(JSON.stringify(freq), freq["tulip"] ?? 0); + +// Read by name without a call: a plain function value. +const counts: Record = { a: 1 }; +const named = counts.toString; +console.log(typeof counts.toString, typeof commands.valueOf, String(named())); diff --git a/tests/harness/corpus-costs/8360-record-prototype-method-calls.ts.json b/tests/harness/corpus-costs/8360-record-prototype-method-calls.ts.json new file mode 100644 index 000000000..1b2d59a51 --- /dev/null +++ b/tests/harness/corpus-costs/8360-record-prototype-method-calls.ts.json @@ -0,0 +1 @@ +{ "plain": 3200, "san": 3900 } diff --git a/tests/harness/corpus-costs/8361-record-function-table-inherited.ts.json b/tests/harness/corpus-costs/8361-record-function-table-inherited.ts.json new file mode 100644 index 000000000..1b2d59a51 --- /dev/null +++ b/tests/harness/corpus-costs/8361-record-function-table-inherited.ts.json @@ -0,0 +1 @@ +{ "plain": 3200, "san": 3900 } From f4825e05a7ad5fc48d6bf188ba4d272ec6401d2a Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 11:33:12 -0700 Subject: [PATCH 103/230] Replace a package's declaration files or supply its declarations with type patches A scriptc.typePatches entry can now replace a declaration file of the package (or of its @types twin, written @types//) with a local .d.ts ("files"), or give the package a declaration entry ("types"). A supplied entry answers the package's types file and the declarations TypeScript probes beside its JavaScript entries, and exists for the program host, the resolver and npm-static's untyped-package test, so a package without declarations becomes a typed package. Declaration patches still apply on top. A files target the installed copy does not ship is SC8031. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/dependencies.mdx | 19 +++ packages/compiler/src/frontend/npm-static.ts | 3 +- packages/compiler/src/frontend/resolve.ts | 4 +- .../compiler/src/frontend/ts7/program-host.ts | 4 +- .../src/frontend/type-patches/layer.ts | 157 +++++++++++++++--- packages/compiler/src/project-config.test.ts | 44 ++++- packages/compiler/src/project-config.ts | 68 +++++++- .../npm/cases/type-patch-files/main.ts.json | 25 +++ .../cases/type-patch-types-entry/main.ts.json | 16 ++ packages/compiler/test/type-patches.test.ts | 55 ++++++ .../node_modules/@effect/platform-node | 1 + tests/fixtures/effect4/node_modules/effect | 1 + .../npm/cases/type-patch-files/main.ts | 10 ++ .../node_modules/eventlog/dist/entry.d.ts | 8 + .../node_modules/eventlog/dist/index.d.ts | 4 + .../node_modules/eventlog/dist/index.js | 17 ++ .../node_modules/eventlog/package.json | 1 + .../npm/cases/type-patch-files/package.json | 13 ++ .../type-patch-files/type-patches/entry.d.ts | 7 + .../npm/cases/type-patch-types-entry/main.ts | 9 + .../node_modules/pointjs/lib/point.js | 17 ++ .../node_modules/pointjs/package.json | 1 + .../cases/type-patch-types-entry/package.json | 10 ++ .../type-patches/pointjs.d.ts | 4 + tests/harness/type-patches.test.ts | 48 +++++- 25 files changed, 513 insertions(+), 33 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-files/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-entry/main.ts.json create mode 120000 tests/fixtures/effect4/node_modules/@effect/platform-node create mode 120000 tests/fixtures/effect4/node_modules/effect create mode 100644 tests/fixtures/npm/cases/type-patch-files/main.ts create mode 100644 tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/entry.d.ts create mode 100644 tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.d.ts create mode 100644 tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.js create mode 100644 tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-files/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-files/type-patches/entry.d.ts create mode 100644 tests/fixtures/npm/cases/type-patch-types-entry/main.ts create mode 100644 tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/lib/point.js create mode 100644 tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-types-entry/package.json create mode 100644 tests/fixtures/npm/cases/type-patch-types-entry/type-patches/pointjs.d.ts diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index ec1eb0601..9b660c76f 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -172,6 +172,25 @@ Each key names a package and a semver range of the installed versions the patch A declaration is named by its path: `Options.name` for a member of a type, `parse()` for a function's return type, `parse(text)` for a parameter, `list()[]` for an array's elements, and `Env[string]` for an index signature. `export=()` names the return type of the function the module exports, `default` its default export, and a file prefix such as `index.js:parse()` names a declaration inside one of the package's files. One patch applies to the package's own declarations, its JSDoc, and its `@types/` package. The patched type then holds everywhere, so your code has to handle the `undefined` that a widened declaration allows. An optional `"note"` string can link the upstream issue. +For larger fixes, replace a whole declaration file, or give a package without declarations its own: + +```json:package.json +{ + "scriptc": { + "typePatches": { + "some-client@^3.2.0": { + "files": { "dist/types/options.d.ts": "./type-patches/options.d.ts" } + }, + "untyped-helper@1.x": { + "types": "./type-patches/untyped-helper.d.ts" + } + } + } +} +``` + +`files` maps a declaration file inside the package, or inside its `@types/` package written as `@types//`, to a local `.d.ts` file. `types` supplies the package's declaration entry. Paths of local files are relative to the `package.json`, and a replaced file can import the package's other declaration files with the same relative paths as the file it replaces. + Patches change only declarations, never the package's code, and values are still checked against the patched types. A build warns when a patch doesn't apply as written: when no range covers the installed version, when a declaration path matches nothing, when the declaration already has the type, or when a path is ambiguous. Report wrong declarations to the package too, so the patch can be removed later. ## Provenance sources (experimental) diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index ed442be79..8e26e3058 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -23,7 +23,7 @@ import { trackedRealpath, } from "./input-tracker.js"; import { npmBuiltinAliasOf } from "./builtin-modules.js"; -import { packageSourceText } from "./type-patches/layer.js"; +import { packageSourceText, typePatchSuppliesTypes } from "./type-patches/layer.js"; let activePackages: ReadonlySet = new Set(); let declarationOverloads: ReadonlyMap = new Map(); @@ -374,6 +374,7 @@ function packageIsUntyped(path: string): boolean { /* no package.json — keep probing */ } if (untyped && trackedExists(`${pkgDir}/index.d.ts`)) untyped = false; + if (untyped && typePatchSuppliesTypes(pkgDir)) untyped = false; if (untyped) { // the @types twin, hoisted anywhere up the realm chain const mangled = mangledTypesName(dirName); diff --git a/packages/compiler/src/frontend/resolve.ts b/packages/compiler/src/frontend/resolve.ts index 5b035b6cb..86252499f 100644 --- a/packages/compiler/src/frontend/resolve.ts +++ b/packages/compiler/src/frontend/resolve.ts @@ -37,9 +37,11 @@ import { trackedRealpath, } from "./input-tracker.js"; import { isRelativeSpecifier, packageNameOfSpecifier } from "./workspace-registry.js"; +import { typePatchFileExists } from "./type-patches/layer.js"; function isFile(path: string): boolean { - return trackedFileExists(path); + // A declaration entry a project type patch supplies (type-patches/layer.ts). + return trackedFileExists(path) || typePatchFileExists(path); } function isDirectory(path: string): boolean { diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 9e8f51bb3..303092acf 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -46,7 +46,7 @@ import { trackedRealpath, } from "../input-tracker.js"; import { packageShimDeclarationText } from "../package-shims.js"; -import { typePatchedText } from "../type-patches/layer.js"; +import { typePatchedText, typePatchFileExists } from "../type-patches/layer.js"; import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../dts-paths.js"; import { reusedLibDiagnostics } from "./lib-diagnostics.js"; import { ts7Diagnostic } from "./diagnostic.js"; @@ -190,7 +190,7 @@ export class Ts7Host { fileExists: (fileName) => { if (virtualFiles.has(tsgoPath(fileName))) return true; if (shadow !== null && shadow.hideFile(fileName)) return false; - return trackedFileExists(fileName); + return trackedFileExists(fileName) || typePatchFileExists(fileName); }, directoryExists: (path) => trackedDirectoryExists(path), realpath: (path) => diff --git a/packages/compiler/src/frontend/type-patches/layer.ts b/packages/compiler/src/frontend/type-patches/layer.ts index 48ef6ecf0..5fb41f968 100644 --- a/packages/compiler/src/frontend/type-patches/layer.ts +++ b/packages/compiler/src/frontend/type-patches/layer.ts @@ -27,7 +27,7 @@ import { dirname, relative } from "node:path"; import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; import { typePatchDiag } from "../../diagnostics/diagnostic.js"; import { readProjectConfig, type TypePatchEntry } from "../../project-config.js"; -import { trackedReadFile } from "../input-tracker.js"; +import { trackedFileExists, trackedReadFile } from "../input-tracker.js"; import type { SourceFile } from "../ts7/syntax.js"; import { applyPatchEdits, patchFile, type PatchMatch, type PatchRequest } from "./locate.js"; import { parseSelector, parseTypeText, type Selector, type TypePatchOp } from "./selector.js"; @@ -49,6 +49,11 @@ interface CopyInfo { root: string; /** Requests whose entry's range the version satisfies. */ requests: Request[]; + /** Replaced declaration files: package-relative path → local file. */ + files: Map; + /** Declaration entries a `types` patch supplies: package-relative path + * → local file (existing entries are replaced, missing ones appear). */ + entries: Map; } interface MatchRecord extends PatchMatch { @@ -61,9 +66,11 @@ interface State { manifest: string; manifestText: string; parse: TypePatchParse; - byPackage: Map; + byPackage: Map; copies: Map; files: Map; + /** `files` targets that do not exist in a copy. */ + missingFiles: { entry: TypePatchEntry; root: string; rel: string }[]; /** Package copies whose version no patch range covers. */ unmatchedCopies: Map; /** Requests applicable to a copy this load read files of. */ @@ -84,22 +91,23 @@ export function configureTypePatches(entryPath: string, parse: TypePatchParse): state = null; return; } - const byPackage = new Map(); + const byPackage = new Map(); let id = 0; for (const entry of config.typePatches) { + const pkg = byPackage.get(entry.pkg) ?? { entries: [], requests: [] }; + byPackage.set(entry.pkg, pkg); + pkg.entries.push(entry); for (const decl of entry.declarations) { const selector = parseSelector(decl.selector); const op = parseTypeText(decl.type); if ("error" in selector || "error" in op) continue; // reported as SC8004 - const list = byPackage.get(entry.pkg) ?? []; - list.push({ + pkg.requests.push({ id: id++, selector: selector as Selector, op: op as TypePatchOp, entry, offset: decl.offset, }); - byPackage.set(entry.pkg, list); } } state = { @@ -109,6 +117,7 @@ export function configureTypePatches(entryPath: string, parse: TypePatchParse): byPackage, copies: new Map(), files: new Map(), + missingFiles: [], unmatchedCopies: new Map(), touched: new Set(), matches: [], @@ -145,7 +154,7 @@ function versionAt(dir: string): string | null { /** The installed package copy a file belongs to, with the requests that * apply to it; null for files outside node_modules or of unpatched * packages. */ -function copyOf(s: State, path: string): { copy: CopyInfo; rel: string } | null { +function copyOf(s: State, path: string, touch = true): { copy: CopyInfo; rel: string } | null { const marker = "/node_modules/"; const at = path.lastIndexOf(marker); if (at < 0) return null; @@ -159,33 +168,120 @@ function copyOf(s: State, path: string): { copy: CopyInfo; rel: string } | null let copy = s.copies.get(root); if (copy === undefined) { copy = null; - const pkg = name.startsWith("@types/") ? unmangleTypesName(name) : name; - const requests = s.byPackage.get(pkg); - if (requests !== undefined) { + const twin = name.startsWith("@types/"); + const pkg = twin ? unmangleTypesName(name) : name; + const patches = s.byPackage.get(pkg); + if (patches !== undefined) { // A twin describes the runtime package installed beside it. - const version = name.startsWith("@types/") + const version = twin ? (versionAt(path.slice(0, at + marker.length) + pkg) ?? versionAt(root)) : versionAt(root); - const applicable = requests.filter( - (r) => version !== null && satisfies(version, r.entry.range), + const applicable = patches.entries.filter( + (e) => version !== null && satisfies(version, e.range), ); - // Later entries win on the same selector. - const bySelector = new Map(); - for (const r of applicable) bySelector.set(r.selector.text, r); - const chosen = [...bySelector.values()]; - if (chosen.length === 0) { - const entries = [...new Set(requests.map((r) => r.entry))]; - s.unmatchedCopies.set(root, { pkg, version, entries }); + if (applicable.length === 0) { + s.unmatchedCopies.set(root, { pkg, version, entries: patches.entries }); + } else { + // Later entries win on the same selector, file or entry. + const bySelector = new Map(); + for (const r of patches.requests) + if (applicable.includes(r.entry)) bySelector.set(r.selector.text, r); + const files = new Map(); + let types: string | null = null; + const prefix = `${name}/`; + for (const e of applicable) { + for (const [target, local] of e.files) { + // `@types//` names the twin's file; plain paths the + // package's own. + const forTwin = target.startsWith("@types/"); + if (forTwin !== twin) continue; + const rel = forTwin + ? target.startsWith(prefix) + ? target.slice(prefix.length) + : null + : target; + if (rel === null) continue; + if (!trackedFileExists(`${root}/${rel}`)) { + s.missingFiles.push({ entry: e, root, rel }); + continue; + } + files.set(rel, local); + } + if (e.types !== null && !twin) types = e.types; + } + copy = { + pkg, + version, + root, + requests: [...bySelector.values()], + files, + entries: types === null ? new Map() : declarationEntries(root, types), + }; } - copy = chosen.length === 0 ? null : { pkg, version, root, requests: chosen }; } s.copies.set(root, copy); } if (copy === null) return null; - for (const r of copy.requests) s.touched.add(r); + if (touch) for (const r of copy.requests) s.touched.add(r); return { copy, rel }; } +/** The declaration entry paths of a package a `types` patch stands for: + * its `types`/`typings` file and every declaration TypeScript probes + * beside a JavaScript entry (`main`, the `exports` targets of "."). */ +function declarationEntries(root: string, local: string): Map { + const out = new Map(); + let pkg: Record = {}; + try { + pkg = JSON.parse(trackedReadFile(`${root}/package.json`) ?? "{}") as Record; + } catch { + pkg = {}; + } + const add = (target: unknown): void => { + if (typeof target !== "string") return; + const rel = target.replace(/^\.\//, ""); + if (/\.d\.[cm]?ts$/.test(rel)) out.set(rel, local); + else if (/\.[cm]?js$/.test(rel)) out.set(rel.replace(/\.([cm]?)js$/, ".d.$1ts"), local); + else if (!/\.\w+$/.test(rel)) out.set(`${rel}.d.ts`, local); + }; + add(pkg["types"]); + add(pkg["typings"]); + add(typeof pkg["main"] === "string" ? pkg["main"] : "index.js"); + const exportsField = pkg["exports"]; + const dot = + exportsField !== null && typeof exportsField === "object" && "." in exportsField + ? (exportsField as Record)["."] + : exportsField; + const visit = (v: unknown): void => { + if (typeof v === "string") add(v); + else if (v !== null && typeof v === "object") for (const x of Object.values(v)) visit(x); + }; + // Subpath exports ("./x") are not the entry. + if (dot !== null && typeof dot === "object" && Object.keys(dot).some((k) => k.startsWith("."))) + return out; + visit(dot); + return out; +} + +/** True for a declaration entry a `types` patch supplies that does not + * exist on disk (a package without declarations gains one). */ +export function typePatchFileExists(fileName: string): boolean { + const s = state; + if (s === null) return false; + const path = fileName.split("\\").join("/"); + if (!/\.d\.[cm]?ts$/.test(path)) return false; + const located = copyOf(s, path, false); + return located !== null && located.copy.entries.has(located.rel); +} + +/** True when a `types` patch gives the package at `pkgDir` declarations. */ +export function typePatchSuppliesTypes(pkgDir: string): boolean { + const s = state; + if (s === null) return false; + const located = copyOf(s, `${pkgDir.split("\\").join("/")}/package.json`, false); + return located !== null && located.copy.entries.size > 0; +} + /** Names a request's root can appear under in a file's text. */ function rootWords(sel: Selector): string[] { if (sel.root === "export=") return ["exports", "export"]; @@ -205,7 +301,12 @@ export function typePatchedText(fileName: string): string | undefined { let result: string | null = null; const located = copyOf(s, path); if (located !== null) { - const text = trackedReadFile(path); + // A replaced file or a supplied entry reads as the local file; the + // declaration patches then apply to it like to the package's own. + const local = located.copy.files.get(located.rel) ?? located.copy.entries.get(located.rel); + const replaced = local === undefined ? null : trackedReadFile(local); + const text = replaced ?? trackedReadFile(path); + if (replaced !== null) result = replaced; const requests = located.copy.requests.filter( (r) => (r.selector.file === null || r.selector.file === located.rel) && @@ -314,6 +415,16 @@ export function typePatchDiagnostics(): { ); } } + for (const missing of s.missingFiles) { + out.push( + typePatchDiag( + "SC8031", + `type patch '${missing.entry.key}': ${missing.rel} is not a file of ${missing.entry.pkg} at ${relativeToManifest(s, missing.root)}`, + "name a declaration file the installed package ships; the package may have moved it", + loc(s, missing.entry.offset), + ), + ); + } for (const r of s.touched) { const mine = s.matches.filter((m) => m.id === r.id); // A selector that matched somewhere (the .d.ts) is not a problem where diff --git a/packages/compiler/src/project-config.test.ts b/packages/compiler/src/project-config.test.ts index 04a5d2e0b..51d9520a0 100644 --- a/packages/compiler/src/project-config.test.ts +++ b/packages/compiler/src/project-config.test.ts @@ -214,7 +214,7 @@ test("typePatches: malformed keys, ranges, selectors and types are SC8004", () = "A.c": "string\nnumber", "A.d": "| undefined", }, - files: {}, + patches: {}, }, "other@^1.0.0": [], }, @@ -225,7 +225,7 @@ test("typePatches: malformed keys, ranges, selectors and types are SC8004", () = `package.json 'scriptc.typePatches.ms@^banana' has the version range "^banana", which is not a semver range`, "package.json 'scriptc.typePatches.@types/node@*' patches @types/node, whose declarations ship with scriptc or TypeScript", "package.json 'scriptc.typePatches.typescript@5' patches typescript, whose declarations ship with scriptc or TypeScript", - "package.json 'scriptc.typePatches.lib@^1.0.0.files' is not a known type patch setting", + "package.json 'scriptc.typePatches.lib@^1.0.0.patches' is not a known type patch setting", "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.1bad' is not a selector: it must start with a declared name, 'export=' or 'default'", "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.A.b' must map to a type string, not 3", "package.json 'scriptc.typePatches.lib@^1.0.0.declarations.A.c' has a type that must be one line", @@ -241,3 +241,43 @@ test("typePatches: malformed keys, ranges, selectors and types are SC8004", () = }); expect(partial.typePatches[0]!.declarations.map((d) => d.selector)).toEqual(["A.d"]); }); + +test("typePatches: files and types name local declaration files", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-project-config-")); + try { + mkdirSync(join(root, "patches")); + writeFileSync(join(root, "patches", "entry.d.ts"), "export {};\n"); + const text = JSON.stringify({ + scriptc: { + typePatches: { + "lib@^1.0.0": { + files: { + "dist/entry.d.ts": "./patches/entry.d.ts", + "@types/lib/index.d.ts": "patches/entry.d.ts", + "dist/index.js": "./patches/entry.d.ts", + "../outside.d.ts": "./patches/entry.d.ts", + "dist/other.d.ts": "./patches/missing.d.ts", + }, + types: "./patches/entry.d.ts", + }, + "other@^1.0.0": { types: "./patches/entry.ts" }, + }, + }, + }); + const c = parseProjectConfig(join(root, "package.json"), text); + expect(c.diagnostics.map((d) => d.message)).toEqual([ + "package.json 'scriptc.typePatches.lib@^1.0.0.files.dist/index.js' must name a declaration file inside the package", + "package.json 'scriptc.typePatches.lib@^1.0.0.files.../outside.d.ts' must name a declaration file inside the package", + `package.json 'scriptc.typePatches.lib@^1.0.0.files.dist/other.d.ts' names "./patches/missing.d.ts", which does not exist`, + `package.json 'scriptc.typePatches.other@^1.0.0.types' names "./patches/entry.ts", which is not a declaration file`, + ]); + const entry = c.typePatches.find((e) => e.pkg === "lib")!; + expect([...entry.files]).toEqual([ + ["dist/entry.d.ts", join(root, "patches/entry.d.ts")], + ["@types/lib/index.d.ts", join(root, "patches/entry.d.ts")], + ]); + expect(entry.types).toBe(join(root, "patches/entry.d.ts")); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/src/project-config.ts b/packages/compiler/src/project-config.ts index c43a6bd2c..4aad9cbc3 100644 --- a/packages/compiler/src/project-config.ts +++ b/packages/compiler/src/project-config.ts @@ -27,7 +27,7 @@ * to their own readers. */ import { dirname, join, resolve } from "node:path"; import { projectConfigDiag, type ScrDiagnostic } from "./diagnostics/diagnostic.js"; -import { trackedReadFile } from "./frontend/input-tracker.js"; +import { trackedFileExists, trackedReadFile } from "./frontend/input-tracker.js"; import { parseSelector, parseTypeText } from "./frontend/type-patches/selector.js"; import { isValidRange } from "./frontend/type-patches/semver.js"; @@ -79,6 +79,13 @@ export interface TypePatchEntry { pkg: string; range: string; declarations: TypePatchDeclaration[]; + /** `files`: declaration files of the package (or of its `@types/` twin, + * written `@types//`) replaced by local files, by + * package-relative path. The local paths are absolute. */ + files: Map; + /** `types`: a local declaration file that becomes the package's + * declaration entry (absolute), or null. */ + types: string | null; /** Offset of the key in the manifest. */ offset: number; } @@ -341,7 +348,8 @@ export function parseProjectConfig(manifest: string, text: string): ProjectConfi return config; } -const TYPE_PATCH_KEYS = ["declarations", "note"]; +const TYPE_PATCH_KEYS = ["declarations", "files", "types", "note"]; +const DECLARATION_FILE = /\.d\.[cm]?ts$/; /** Packages whose declarations ship with scriptc or TypeScript: a wrong * one is a bug to report, not a project-local patch. */ const UNPATCHABLE = /^(?:@types\/node|typescript|@typescript\/.*)$/; @@ -404,8 +412,64 @@ function readTypePatches( pkg, range, declarations: [], + files: new Map(), + types: null, offset: locate.offsetOf(["scriptc", "typePatches", key]), }; + const base = dirname(config.manifest!); + const localFile = (path: string[], v: unknown): string | null => { + if (typeof v !== "string" || v === "") { + problem( + path, + `must be the path of a local declaration file, not ${shown(v)}`, + `write "./type-patches/${pkg}.d.ts"`, + ); + return null; + } + const abs = resolve(base, v); + if (!DECLARATION_FILE.test(abs)) { + problem( + path, + `names "${v}", which is not a declaration file`, + "use a .d.ts, .d.mts or .d.cts file", + ); + return null; + } + if (!trackedFileExists(abs)) { + problem( + path, + `names "${v}", which does not exist`, + "give the path relative to this package.json", + ); + return null; + } + return abs; + }; + const files = value["files"]; + if (files !== undefined && !isRecord(files)) { + problem( + ["typePatches", key, "files"], + `must be an object mapping declaration files of the package to local files, not ${shown(files)}`, + `write "files": { "dist/index.d.ts": "./type-patches/${pkg}.d.ts" }`, + ); + } else if (files !== undefined) { + for (const target of Object.keys(files)) { + const path = ["typePatches", key, "files", target]; + const rel = target.replace(/^\.\//, ""); + if (!DECLARATION_FILE.test(rel) || rel.startsWith("/") || rel.split("/").includes("..")) { + problem( + path, + "must name a declaration file inside the package", + `write a package-relative .d.ts path such as "dist/index.d.ts", or "@types//index.d.ts" for its @types package`, + ); + continue; + } + const abs = localFile(path, files[target]); + if (abs !== null) entry.files.set(rel, abs); + } + } + if (value["types"] !== undefined) + entry.types = localFile(["typePatches", key, "types"], value["types"]); let valid = true; for (const k of Object.keys(value)) { if (!TYPE_PATCH_KEYS.includes(k)) { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-files/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-files/main.ts.json new file mode 100644 index 000000000..4a15a1975 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-files/main.ts.json @@ -0,0 +1,25 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/type-patch-files/main.ts" + ], + "diags": [ + { + "code": "SC0001", + "message": "Property 'toUpperCase' does not exist on type 'number'.", + "loc": { + "file": "/tests/fixtures/npm/cases/type-patch-files/main.ts", + "start": 346, + "end": 357 + } + }, + { + "code": "SC0001", + "message": "This comparison appears to be unintentional because the types 'number' and 'string' have no overlap.", + "loc": { + "file": "/tests/fixtures/npm/cases/type-patch-files/main.ts", + "start": 429, + "end": 447 + } + } + ] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-entry/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-entry/main.ts.json new file mode 100644 index 000000000..bc1622724 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/type-patch-types-entry/main.ts.json @@ -0,0 +1,16 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/type-patch-types-entry/main.ts" + ], + "diags": [ + { + "code": "SC0001", + "message": "Could not find a declaration file for module 'pointjs'. '/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/lib/point.js' implicitly has an 'any' type.\n Try `npm i --save-dev @types/pointjs` if it exists or add a new declaration (.d.ts) file containing `declare module 'pointjs';`", + "loc": { + "file": "/tests/fixtures/npm/cases/type-patch-types-entry/main.ts", + "start": 246, + "end": 255 + } + } + ] +} diff --git a/packages/compiler/test/type-patches.test.ts b/packages/compiler/test/type-patches.test.ts index 729a0da37..116715db5 100644 --- a/packages/compiler/test/type-patches.test.ts +++ b/packages/compiler/test/type-patches.test.ts @@ -12,7 +12,9 @@ import { typePatchDiagnostics, typePatchedSpans, typePatchedText, + typePatchFileExists, typePatchMatches, + typePatchSuppliesTypes, } from "../src/frontend/type-patches/layer.js"; const fixtures = join(import.meta.dirname, "../../../tests/fixtures"); @@ -122,3 +124,56 @@ test("an @types twin is matched by the runtime package's installed version", () rmSync(root, { recursive: true, force: true }); } }); + +test("files replaces a declaration file and declarations still apply on top", () => { + const root = join(fixtures, "npm/cases/type-patch-files"); + configureTypePatches(join(root, "main.ts"), parse); + const entry = typePatchedText(join(root, "node_modules/eventlog/dist/entry.d.ts")); + expect(entry).toBe(readFileSync(join(root, "type-patches/entry.d.ts"), "utf8")); + expect(typePatchedText(join(root, "node_modules/eventlog/dist/index.d.ts"))).toContain( + "{ count: number; last: string | undefined }", + ); + expect(typePatchDiagnostics()!.diagnostics).toEqual([]); +}); + +test("types gives a package without declarations an entry beside its main", () => { + const root = join(fixtures, "npm/cases/type-patch-types-entry"); + configureTypePatches(join(root, "main.ts"), parse); + const pkg = join(root, "node_modules/pointjs"); + expect(typePatchFileExists(join(pkg, "lib/point.d.ts"))).toBe(true); + expect(typePatchFileExists(join(pkg, "index.d.ts"))).toBe(false); + expect(typePatchFileExists(join(pkg, "lib/other.d.ts"))).toBe(false); + expect(typePatchSuppliesTypes(pkg)).toBe(true); + expect(typePatchedText(join(pkg, "lib/point.d.ts"))).toBe( + readFileSync(join(root, "type-patches/pointjs.d.ts"), "utf8"), + ); + // Without patches nothing exists that is not on disk. + configureTypePatches(join(tmpdir(), "none", "main.ts"), parse); + expect(typePatchFileExists(join(pkg, "lib/point.d.ts"))).toBe(false); + expect(typePatchSuppliesTypes(pkg)).toBe(false); +}); + +test("a files target the installed package does not ship warns", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-")); + try { + const twin = join(fixtures, "npm/cases/type-patch-files"); + writeFileSync(join(root, "local.d.ts"), "export type Entry = {};\n"); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { + typePatches: { "eventlog@^2.0.0": { files: { "dist/gone.d.ts": "./local.d.ts" } } }, + }, + }), + ); + configureTypePatches(join(root, "main.ts"), parse); + expect(typePatchedText(join(twin, "node_modules/eventlog/dist/index.d.ts"))).toBeUndefined(); + const diags = typePatchDiagnostics()!.diagnostics; + expect(diags.map((d) => d.code)).toEqual(["SC8031"]); + expect(diags[0]!.message).toMatch( + /^type patch 'eventlog@\^2\.0\.0': dist\/gone\.d\.ts is not a file of eventlog at .*type-patch-files\/node_modules\/eventlog$/, + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/tests/fixtures/effect4/node_modules/@effect/platform-node b/tests/fixtures/effect4/node_modules/@effect/platform-node new file mode 120000 index 000000000..aaa396705 --- /dev/null +++ b/tests/fixtures/effect4/node_modules/@effect/platform-node @@ -0,0 +1 @@ +../../../../../node_modules/.pnpm/@effect+platform-node@4.0.0_effect@4.0.0_redis@6.3.0/node_modules/@effect/platform-node \ No newline at end of file diff --git a/tests/fixtures/effect4/node_modules/effect b/tests/fixtures/effect4/node_modules/effect new file mode 120000 index 000000000..1b933cdef --- /dev/null +++ b/tests/fixtures/effect4/node_modules/effect @@ -0,0 +1 @@ +../../../../node_modules/.pnpm/effect@4.0.0/node_modules/effect \ No newline at end of file diff --git a/tests/fixtures/npm/cases/type-patch-files/main.ts b/tests/fixtures/npm/cases/type-patch-files/main.ts new file mode 100644 index 000000000..32e041ce0 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/main.ts @@ -0,0 +1,10 @@ +// A package declaration file replaced by a local one (scriptc.typePatches +// files), plus a declaration patch on another file of the same package. +import { parseLog, summarize } from "eventlog"; + +const entries = parseLog("10 info ada started\n12 warn - disk low\n15 error lin crashed hard\n"); +for (const e of entries) console.log(e.at, e.level.toUpperCase(), e.user ?? "(system)", e.message); +console.log(entries.filter((e) => e.level !== "info").length, JSON.stringify(entries[1])); +const all = summarize(entries); +const none = summarize([]); +console.log(all.count, all.last ?? "-", none.count, none.last ?? "-"); diff --git a/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/entry.d.ts b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/entry.d.ts new file mode 100644 index 000000000..0a42b298f --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/entry.d.ts @@ -0,0 +1,8 @@ +// Out of date: the JavaScript reports levels as strings and has no +// `user` for system entries. +export type Entry = { + at: number; + level: number; + user: string; + message: string; +}; diff --git a/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.d.ts b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.d.ts new file mode 100644 index 000000000..c16d59d46 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.d.ts @@ -0,0 +1,4 @@ +export type { Entry } from "./entry"; +import type { Entry } from "./entry"; +export declare function parseLog(text: string): Entry[]; +export declare function summarize(entries: Entry[]): { count: number; last: string }; diff --git a/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.js b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.js new file mode 100644 index 000000000..4c10d88f4 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/dist/index.js @@ -0,0 +1,17 @@ +"use strict"; +function parseLog(text) { + const rows = String(text) + .split("\n") + .filter(Boolean) + .map((line) => { + const [at, level, user, ...rest] = line.split(" "); + return { at: Number(at), level: level, user: user === "-" ? undefined : user, message: rest.join(" ") }; + }); + // Entries travel as JSON (the log format's own wire form). + return JSON.parse(JSON.stringify(rows)); +} +function summarize(entries) { + return { count: entries.length, last: entries.length === 0 ? undefined : entries[entries.length - 1].message }; +} +exports.parseLog = parseLog; +exports.summarize = summarize; diff --git a/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/package.json b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/package.json new file mode 100644 index 000000000..27ebf4625 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/node_modules/eventlog/package.json @@ -0,0 +1 @@ +{ "name": "eventlog", "version": "2.3.1", "main": "dist/index.js", "types": "dist/index.d.ts" } diff --git a/tests/fixtures/npm/cases/type-patch-files/package.json b/tests/fixtures/npm/cases/type-patch-files/package.json new file mode 100644 index 000000000..92a38c108 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/package.json @@ -0,0 +1,13 @@ +{ + "name": "type-patch-files-case", + "private": true, + "type": "module", + "scriptc": { + "typePatches": { + "eventlog@^2.0.0": { + "files": { "dist/entry.d.ts": "./type-patches/entry.d.ts" }, + "declarations": { "summarize().last": "| undefined" } + } + } + } +} diff --git a/tests/fixtures/npm/cases/type-patch-files/type-patches/entry.d.ts b/tests/fixtures/npm/cases/type-patch-files/type-patches/entry.d.ts new file mode 100644 index 000000000..380338e96 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-files/type-patches/entry.d.ts @@ -0,0 +1,7 @@ +// The project's replacement for eventlog's dist/entry.d.ts. +export type Entry = { + at: number; + level: "info" | "warn" | "error"; + user?: string; + message: string; +}; diff --git a/tests/fixtures/npm/cases/type-patch-types-entry/main.ts b/tests/fixtures/npm/cases/type-patch-types-entry/main.ts new file mode 100644 index 000000000..3d0ab5ac9 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-entry/main.ts @@ -0,0 +1,9 @@ +// A package without declarations, typed by a local declaration file +// (scriptc.typePatches types): user code is checked against it, and the +// values cross the declaration boundary as native records. +import { centroid, point, type Point } from "pointjs"; + +const points: Point[] = [point(0, 0), point(4, 0), point(4, 3)]; +const c = centroid(points); +console.log(c === null ? "none" : `${c.x.toFixed(2)},${c.y.toFixed(2)}`); +console.log(centroid([]) === null, JSON.stringify(points.map((p) => p.x + p.y))); diff --git a/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/lib/point.js b/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/lib/point.js new file mode 100644 index 000000000..f324e9acb --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/lib/point.js @@ -0,0 +1,17 @@ +"use strict"; +// Plain JavaScript without declarations. +function point(x, y) { + return { x: x, y: y }; +} +function centroid(points) { + if (points.length === 0) return null; + var sx = 0; + var sy = 0; + for (var i = 0; i < points.length; i++) { + sx += points[i].x; + sy += points[i].y; + } + return point(sx / points.length, sy / points.length); +} +exports.point = point; +exports.centroid = centroid; diff --git a/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/package.json b/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/package.json new file mode 100644 index 000000000..917576875 --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-entry/node_modules/pointjs/package.json @@ -0,0 +1 @@ +{ "name": "pointjs", "version": "1.4.0", "main": "lib/point.js" } diff --git a/tests/fixtures/npm/cases/type-patch-types-entry/package.json b/tests/fixtures/npm/cases/type-patch-types-entry/package.json new file mode 100644 index 000000000..06846a01d --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-entry/package.json @@ -0,0 +1,10 @@ +{ + "name": "type-patch-types-entry-case", + "private": true, + "type": "module", + "scriptc": { + "typePatches": { + "pointjs@1.x": { "types": "./type-patches/pointjs.d.ts" } + } + } +} diff --git a/tests/fixtures/npm/cases/type-patch-types-entry/type-patches/pointjs.d.ts b/tests/fixtures/npm/cases/type-patch-types-entry/type-patches/pointjs.d.ts new file mode 100644 index 000000000..30ae94a5e --- /dev/null +++ b/tests/fixtures/npm/cases/type-patch-types-entry/type-patches/pointjs.d.ts @@ -0,0 +1,4 @@ +// The project's declarations for pointjs, which ships none. +export type Point = { x: number; y: number }; +export declare function point(x: number, y: number): Point; +export declare function centroid(points: Point[]): Point | null; diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index ecd8ee923..816418a96 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -4,8 +4,10 @@ * value (the shape of commander's HelpContext.helpWidth), and ms's @return * tags, compile natively and byte-match Node once patched; without the * patches the same declarations throw where Node runs. Patches that do not - * apply as written warn (SC8030-SC8033). The `.d.ts` and `@types` cases - * run in npm.test.ts (type-patch-dts, type-patch-types-twin), and + * apply as written warn (SC8030-SC8033). The `.d.ts`, `@types`, replaced + * file and supplied entry cases run in npm.test.ts (type-patch-dts, + * type-patch-types-twin, type-patch-files, type-patch-types-entry); here + * their unpatched copies show the patches are what makes them compile. And * commander-calc carries the HelpContext.helpWidth patch. */ import { execFile } from "node:child_process"; import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; @@ -121,6 +123,48 @@ describe("type patches", () => { }, ); + test( + "without its patch, code using the out-of-date declaration file does not type-check", + { timeout: 600_000 }, + async () => { + const copy = unpatched(join(repoRoot, "tests/fixtures/npm/cases/type-patch-files")); + try { + mkdirSync(outDir, { recursive: true }); + const result = await compile(join(copy, "main.ts"), { + outPath: join(outDir, "files-unpatched"), + outDir, + sanitize, + }); + expect(result.ok).toBe(false); + if (!result.ok) + expect(result.diagnostics.map((d) => d.message)).toContain( + "Property 'toUpperCase' does not exist on type 'number'.", + ); + } finally { + rmSync(copy, { recursive: true, force: true }); + } + }, + ); + + test( + "without its patch, a package without declarations is not compiled for a typed import", + { timeout: 600_000 }, + async () => { + const copy = unpatched(join(repoRoot, "tests/fixtures/npm/cases/type-patch-types-entry")); + try { + mkdirSync(outDir, { recursive: true }); + const result = await compile(join(copy, "main.ts"), { + outPath: join(outDir, "types-entry-unpatched"), + outDir, + sanitize, + }); + expect(result.ok).toBe(false); + } finally { + rmSync(copy, { recursive: true, force: true }); + } + }, + ); + test( "patches that do not apply as written warn and the build goes on", { timeout: 600_000 }, From be0e13aa1ef057c04ab397aa801a00b7577a1378 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:28:23 -0500 Subject: [PATCH 104/230] Run paired row sums two columns per step A paired row-sum nest now also takes two columns per step: each row adds the first column's term and then the second's, and an odd column count runs its last column on its own, so every sum keeps its order of additions. The nest runs paired when all four bounds are integers in [0, 2^25 - 1]; it reads them masked to that range, which proves both counters' ranges, so its counted loops need no run-time guard or second copy of the body. Corpus 8355 covers the column counts and bounds, and corpus 8356 covers worker loops over shared views that run past a view's end or write overlapping views of one buffer. --- packages/compiler/src/ir/row-pairs.test.ts | 37 +++- packages/compiler/src/ir/row-pairs.ts | 200 +++++++++++++----- .../corpus/8355-row-pair-columns.ts.json | 6 + .../8356-shared-view-loop-versions.ts.json | 6 + tests/corpus/8355-row-pair-columns.ts | 73 +++++++ .../corpus/8356-shared-view-loop-versions.ts | 93 ++++++++ 6 files changed, 352 insertions(+), 63 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8355-row-pair-columns.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8356-shared-view-loop-versions.ts.json create mode 100644 tests/corpus/8355-row-pair-columns.ts create mode 100644 tests/corpus/8356-shared-view-loop-versions.ts diff --git a/packages/compiler/src/ir/row-pairs.test.ts b/packages/compiler/src/ir/row-pairs.test.ts index f9db5b89d..8ef01c665 100644 --- a/packages/compiler/src/ir/row-pairs.test.ts +++ b/packages/compiler/src/ir/row-pairs.test.ts @@ -93,12 +93,39 @@ test("a row-sum nest runs two rows per pass behind its bounds and overlap test", // from the array the term reads. expect(JSON.stringify(go.init)).toContain('"fn":"bytes.disjoint"'); expect(branch.else_).toBeNull(); - const pairs = branch.then[0] as IrStmt & { kind: "for" }; + // The four bounds, masked to the range the test proved, then the pairs. + expect(branch.then.slice(0, 4)).toMatchObject( + Array(4).fill({ kind: "varDecl", init: { op: "&", right: { value: 2 ** 25 - 1 } } }), + ); + const pairs = branch.then[4] as IrStmt & { kind: "for" }; expect(pairs.body.filter((s) => s.kind === "bytesSet")).toHaveLength(2); - const inner = pairs.body[2] as IrStmt & { kind: "for" }; - // One read of u[j] for both rows, then the two sums. - expect(inner.body).toHaveLength(3); + const [, , inner, last] = pairs.body as [ + IrStmt, + IrStmt, + IrStmt & { kind: "for" }, + IrStmt & { kind: "if" }, + ]; + const column = inner.init as IrStmt & { kind: "varDecl" }; + // Two columns per step: for each, one read of u[j] for both rows, then + // the two sums; the second column reads `column + 1`. + expect(inner.body).toHaveLength(6); expect(inner.body[0]).toMatchObject({ kind: "varDecl", init: { kind: "bytesIntrinsic" } }); + expect(inner.body[3]).toMatchObject({ + kind: "varDecl", + init: { + kind: "bytesIntrinsic", + args: [{ op: "+", left: { localId: column.localId }, right: { value: 1 } }], + }, + }); + expect(inner.cond).toMatchObject({ op: "<", right: { op: "-", right: { value: 1 } } }); + expect(inner.update).toMatchObject({ value: { op: "+", right: { value: 2 } } }); + // The last column of an odd count, `to - 1`. + expect(last.then).toHaveLength(3); + expect(last.then[0]).toMatchObject({ + kind: "varDecl", + init: { args: [{ op: "-", right: { value: 1 } }] }, + }); + expect(last.else_).toBeNull(); // The second row's term reads `row + 1`. const row = pairs.init?.kind === "varDecl" ? pairs.init.localId : ""; expect((inner.body[2] as IrStmt & { kind: "assign" }).value).toMatchObject({ @@ -130,7 +157,7 @@ test("a row-sum nest runs two rows per pass behind its bounds and overlap test", expect(top.body).toHaveLength(4); expect( result.functions[0]!.locals.map((l) => l.id).filter((id) => id.startsWith("%pair.")), - ).toHaveLength(7); + ).toHaveLength(13); }); test("nests whose term calls, writes or reads the sum keep their loops", () => { diff --git a/packages/compiler/src/ir/row-pairs.ts b/packages/compiler/src/ir/row-pairs.ts index 2e189f42e..e6b3889a5 100644 --- a/packages/compiler/src/ir/row-pairs.ts +++ b/packages/compiler/src/ir/row-pairs.ts @@ -8,10 +8,17 @@ import { type IrStmt, } from "./ir.js"; import { everyExpr, everyExprChild, mapExprChildren, transformStmtList } from "./traverse.js"; +import { PRODUCT_COUNTER_BOUND } from "./integer-loops.js"; /** Inner-loop expressions above this many nodes keep the loop as written. */ const MAX_TERM_NODES = 64; +/** The largest bound a paired nest runs with. Counters below + * PRODUCT_COUNTER_BOUND keep products of their sums exact, so the nest's + * counted loops take integer counters without a run-time guard (and its + * copy of the body). */ +const SMALL_BOUND = PRODUCT_COUNTER_BOUND - 1; + /** The number operators a term may use: they run no other code and cannot * throw. */ const PURE_BINARY = new Set([ @@ -210,7 +217,8 @@ const bin = ( }); /** Runs the row sums of `match` two rows per pass when the test holds and - * as written otherwise. */ + * as written otherwise. Each pass takes two columns per step, then the + * last column when their count is odd. */ function pairRows( s: IrStmt & { kind: "for" }, match: RowSum, @@ -225,42 +233,93 @@ function pairRows( sum = fresh("sum"), next = fresh("next"), column = fresh("column"); - const atColumn = (r: IrExpr & { kind: "varRef" }): IrExpr => - r.localId === match.column ? { ...r, localId: column } : { ...r }; // Element reads that do not depend on the row are read once per column // and shared by both rows. - const shared = new Map(); - const elements: IrStmt[] = []; + const sharedReads: IrExpr[] = []; const collect = (e: IrExpr): boolean => { if (e.kind === "bytesIntrinsic" && e.method === "get" && !readsLocal(e, match.row)) { - const id = fresh("element"); - shared.set(e, id); - elements.push({ kind: "varDecl", localId: id, init: copy(e, atColumn), loc }); + sharedReads.push(e); return true; } return everyExprChild(e, collect, () => true); }; collect(match.term); - // The second row's term reads `row + 1`, so its counter sums stay visible - // to the counted-loop analysis. - const termAt = (rowExpr: () => IrExpr): IrExpr => - copy( - match.term, - (r) => (r.localId === match.row ? rowExpr() : atColumn(r)), - (node) => { - const id = shared.get(node); - return id === undefined ? null : { kind: "varRef", localId: id, type: node.type, loc }; + /** Both rows' additions at the column `columnAt()`: its shared element + * reads, then each row's sum. The second row's term reads `row + 1`, and + * the second column's `column + 1`, so their counter sums stay visible to + * the counted-loop analysis. */ + const step = (columnAt: () => IrExpr): IrStmt[] => { + const at = (r: IrExpr & { kind: "varRef" }): IrExpr => + r.localId === match.column ? columnAt() : { ...r }; + const shared = new Map(); + const elements: IrStmt[] = sharedReads.map((read) => { + const id = fresh("element"); + shared.set(read, id); + return { kind: "varDecl", localId: id, init: copy(read, at), loc }; + }); + const termAt = (rowExpr: () => IrExpr): IrExpr => + copy( + match.term, + (r) => (r.localId === match.row ? rowExpr() : at(r)), + (node) => { + const id = shared.get(node); + return id === undefined ? null : { kind: "varRef", localId: id, type: node.type, loc }; + }, + ); + return [ + ...elements, + { + kind: "assign", + localId: sum, + value: bin( + "+", + ref(sum), + termAt(() => ref(row)), + loc, + ), + loc, }, - ); + { + kind: "assign", + localId: next, + value: bin( + "+", + ref(next), + termAt(() => bin("+", ref(row), num(1), loc)), + loc, + ), + loc, + }, + ]; + }; const innerCond = match.inner.cond as IrExpr & { kind: "bin" }; + const columnStart = (match.inner.init as IrStmt & { kind: "varDecl" }).init!; + // The bounds, as the paired nest reads them (see `small` below). + const low = fresh("low"), + high = fresh("high"), + first = fresh("first"), + last = fresh("last"); + const bounds: IrStmt[] = ( + [ + [low, match.start], + [high, match.end], + [first, columnStart], + [last, innerCond.right], + ] as const + ).map(([id, bound]) => ({ + kind: "varDecl", + localId: id, + init: bin("&", again(bound), num(SMALL_BOUND), loc), + loc, + })); const pairs: IrStmt = { kind: "for", - init: { kind: "varDecl", localId: row, init: again(match.start), loc }, + init: { kind: "varDecl", localId: row, init: ref(low), loc }, cond: { kind: "bin", op: "<", left: ref(row), - right: bin("-", again(match.end), num(1), loc), + right: bin("-", ref(high), num(1), loc), type: BOOL, loc, }, @@ -268,41 +327,40 @@ function pairRows( body: [ { kind: "varDecl", localId: sum, init: { ...match.init }, loc }, { kind: "varDecl", localId: next, init: { ...match.init }, loc }, + // Both column bounds are int32, so `column < to - 1` is + // `column + 1 < to`. Each row adds its columns in order. { kind: "for", - init: { - kind: "varDecl", - localId: column, - init: again((match.inner.init as IrStmt & { kind: "varDecl" }).init!), + init: { kind: "varDecl", localId: column, init: ref(first), loc }, + cond: { + ...innerCond, + left: ref(column), + right: bin("-", ref(last), num(1), loc), + }, + update: { kind: "assign", localId: column, value: bin("+", ref(column), num(2), loc), loc }, + body: [...step(() => ref(column)), ...step(() => bin("+", ref(column), num(1), loc))], + loc, + }, + // An odd column count leaves the last column, `to - 1`. + { + kind: "if", + cond: { + kind: "logical", + op: "&&", + left: { ...innerCond, left: ref(first), right: ref(last) }, + right: { + kind: "bin", + op: "===", + left: bin("&", bin("-", ref(last), ref(first), loc), num(1), loc), + right: num(1), + type: BOOL, + loc, + }, + type: BOOL, loc, }, - cond: { ...innerCond, left: ref(column), right: again(innerCond.right) }, - update: { kind: "assign", localId: column, value: bin("+", ref(column), num(1), loc), loc }, - body: [ - ...elements, - { - kind: "assign", - localId: sum, - value: bin( - "+", - ref(sum), - termAt(() => ref(row)), - loc, - ), - loc, - }, - { - kind: "assign", - localId: next, - value: bin( - "+", - ref(next), - termAt(() => bin("+", ref(row), num(1), loc)), - loc, - ), - loc, - }, - ], + then: step(() => bin("-", ref(last), num(1), loc)), + else_: null, loc, }, { kind: "bytesSet", arr: { ...match.out }, index: ref(row), value: ref(sum), loc }, @@ -316,9 +374,12 @@ function pairRows( ], loc, }; - // Both bounds are int32, so `row < end - 1` is `row + 1 < end`, and the - // nest has at least two rows. The overlap tests run only then, so they never - // read an array the program assigns only when there are rows. Rows are + // Every bound is an integer in [0, SMALL_BOUND], so `row < end - 1` is + // `row + 1 < end` (and the same for the columns), and the masks in + // `bounds` leave the bounds unchanged while proving both counters' ranges. + // The nest has at least two rows. The column bounds' tests and the + // overlap tests run only then, so they never read an array the program + // assigns only when there are rows. Rows are // independent when the written array shares no byte with an array the // term reads; an array that is not there answers false. const int32 = (e: IrExpr): IrExpr => ({ @@ -329,9 +390,22 @@ function pairRows( type: BOOL, loc, }); + const compare = (op: "<=" | ">=", e: IrExpr, value: number): IrExpr => ({ + kind: "bin", + op, + left: again(e), + right: num(value), + type: BOOL, + loc, + }); + const small = (e: IrExpr): IrExpr[] => [ + int32(e), + compare(">=", e, 0), + compare("<=", e, SMALL_BOUND), + ]; const tests: IrExpr[] = [ - int32(match.start), - int32(match.end), + ...small(match.start), + ...small(match.end), { kind: "bin", op: "<", @@ -340,6 +414,8 @@ function pairRows( type: BOOL, loc, }, + ...small(columnStart), + ...small(innerCond.right), ...match.reads.map((read): IrExpr => ({ kind: "libCall", fn: "bytes.disjoint", @@ -394,7 +470,7 @@ function pairRows( }, loc, }, - { kind: "if", cond: { ...paired }, then: [pairs], else_: null, loc }, + { kind: "if", cond: { ...paired }, then: [...bounds, pairs], else_: null, loc }, rest, ], loc, @@ -413,7 +489,15 @@ function pairRows( * store into shares no byte with any array the term reads, so no row reads * another row's result. An odd row count runs its last row as written. * Otherwise the nest runs as written. Two independent sums take turns in - * the inner loop, so neither waits on the other's addition. */ + * the inner loop, so neither waits on the other's addition. + * + * The inner loop takes two columns per step, each row adding the first + * column's term and then the second's, and the last column on its own + * when the count is odd, so the additions keep their order. Both column + * bounds must also be int32 (so `column < to - 1` is `column + 1 < to`). + * Neighboring rows and columns share counter sums (`row + 1 + column` and + * `row + (column + 1)`), and the step's divisions have no dependence + * between them. */ export function pairRowSums(mod: IrModule): IrModule { let changed = false; const functions = mod.functions.map((fn): IrFunction => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8355-row-pair-columns.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8355-row-pair-columns.ts.json new file mode 100644 index 000000000..900de6fb0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8355-row-pair-columns.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8355-row-pair-columns.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8356-shared-view-loop-versions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8356-shared-view-loop-versions.ts.json new file mode 100644 index 000000000..76cd99065 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8356-shared-view-loop-versions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8356-shared-view-loop-versions.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8355-row-pair-columns.ts b/tests/corpus/8355-row-pair-columns.ts new file mode 100644 index 000000000..96ba26153 --- /dev/null +++ b/tests/corpus/8355-row-pair-columns.ts @@ -0,0 +1,73 @@ +// Row sums whose inner loop runs two columns per step: even, odd, zero, one +// and two columns, column ranges that start above zero or at or past their +// end, fractional, negative, NaN and very large column bounds, a column +// bound read from an array's length, terms that read two elements per +// column, and row bounds at and past 2^25. Every sum adds its columns in +// order, so each has the bits of the loop as written. +function rows(u: Float64Array, v: Float64Array, start: number, end: number, from: number, to: number): void { + for (let i = start; i < end; i++) { + let t = 0; + for (let j = from; j < to; j++) t += u[j]! / ((((i + j) * (i + j + 1)) >>> 1) + i + 1); + v[i] = t; + } +} + +function pairs(u: Float64Array, w: Float32Array, v: Float64Array, end: number): void { + for (let i = 0; i < end; i++) { + let t = -0.25; + for (let j = 0; j < u.length; j++) t += u[j]! * w[j + 1]! - (i + 1) / (j + 3); + v[i] = t; + } +} + +const show = (label: string, v: Float64Array): void => { + const parts: string[] = []; + for (const x of v) parts.push(Object.is(x, -0) ? "-0" : x.toPrecision(17)); + console.log(label, parts.join(" ")); +}; + +const n = 9; +const u = new Float64Array(n).map((_, k) => 1 / (k + 1) + k); +for (const [from, to] of [ + [0, n], + [0, n - 1], + [0, 0], + [0, 1], + [0, 2], + [3, n], + [4, n], + [5, 5], + [7, 2], + [0.5, 6], + [0, 6.5], + [-2, 5], + [-0, 4], + [NaN, 4], + [0, NaN], + [0, n + 3], + [2 ** 25, 2 ** 25 + 3], +]) { + const v = new Float64Array(6); + rows(u, v, 0, 6, from!, to!); + show(`columns ${from} ${to}`, v); +} +for (const [start, end] of [ + [2 ** 25 - 3, 2 ** 25 + 1], + [2 ** 25, 2 ** 25 + 2], + [1, 6], +]) { + const v = new Float64Array(8); + rows(u, v, start!, end!, 0, n); + show(`rows ${start} ${end}`, v); +} + +const w = new Float32Array(n + 1).map((_, k) => (k % 4) - 1.5); +for (const end of [0, 1, 4, 5]) { + const v = new Float64Array(5); + pairs(u, w, v, end); + show(`two reads ${end}`, v); +} +const short = new Float64Array(4).map((_, k) => k * 2.5); +const out = new Float64Array(5); +pairs(short, w, out, 5); +show("two reads short", out); diff --git a/tests/corpus/8356-shared-view-loop-versions.ts b/tests/corpus/8356-shared-view-loop-versions.ts new file mode 100644 index 000000000..28f4307fb --- /dev/null +++ b/tests/corpus/8356-shared-view-loop-versions.ts @@ -0,0 +1,93 @@ +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +// Loops over typed-array views of shared memory in a worker program: row +// sums and element-wise passes whose accesses are in bounds, the same loops +// running past a view's end, a loop that mixes a shared view with one the +// thread owns, and views of one SharedArrayBuffer that overlap, written in +// an order that shows whether each read sees the previous write. The worker +// writes its results into shared memory, which the main thread reads after +// the worker reports. + +interface Job { + sab: SharedArrayBuffer; + n: number; +} + +function rowSums(u: Float64Array, v: Float64Array, start: number, end: number, n: number): void { + for (let i = start; i < end; i++) { + let t = 0; + for (let j = 0; j < n; j++) t = t + u[j]! / (((i + j) * (i + j + 1)) / 2 + i + 1); + v[i] = t; + } +} + +function scaled(src: Float64Array, dst: Float64Array, k: number, count: number): void { + for (let i = 0; i < count; i++) dst[i] = src[i]! * k + 1; +} + +function mixed(shared: Int32Array, owned: Int32Array, count: number): number { + let sum = 0; + for (let i = 0; i < count; i++) { + owned[i] = shared[i]! * 3; + shared[i] = owned[i]! - i; + sum = (sum + shared[i]! + owned[i]!) | 0; + } + return sum; +} + +function shift(a: Float64Array, b: Float64Array, count: number): void { + for (let i = 0; i < count; i++) a[i + 1] = b[i]! + 1; +} + +function checksum(values: Float64Array): string { + let h = 0; + for (let i = 0; i < values.length; i++) h = (Math.imul(h, 31) + Math.round(values[i]! * 1e6)) >>> 0; + return h.toString(16); +} + +function work({ sab, n }: Job): string[] { + const out: string[] = []; + const u = new Float64Array(sab, 0, n); + const v = new Float64Array(sab, n * 8, n); + for (let i = 0; i < n; i++) u[i] = 1 + (i % 5) / 4; + rowSums(u, v, 0, n, n); + out.push(`rows ${checksum(v)}`); + // Past the end of u: the reads beyond it are undefined, so the sums are NaN. + rowSums(u, v, 0, 4, n + 3); + out.push(`past ${v[0]} ${v[3]} ${checksum(v.subarray(4))}`); + scaled(u, v, 0.5, n); + out.push(`scaled ${checksum(v)}`); + scaled(u, v, 2, n + 2); + out.push(`scaled past ${checksum(v)}`); + const ints = new Int32Array(sab, 2 * n * 8, 32); + for (let i = 0; i < 32; i++) ints[i] = i * 7 - 50; + const own = new Int32Array(32); + out.push(`mixed ${mixed(ints, own, 32)} ${mixed(ints, own, 34)} ${ints[5]} ${own[31]}`); + // a[i + 1] lands on b[i + 1] when b starts one element after a. + const a = new Float64Array(sab, 0, 24); + const b = new Float64Array(sab, 8, 24); + for (let i = 0; i < 25; i++) u[i] = i; + shift(a, b, 20); + out.push(`overlap ${Array.from(a.subarray(0, 8)).join(",")}`); + shift(b, a, 20); + out.push(`overlap back ${Array.from(b.subarray(0, 8)).join(",")}`); + return out; +} + +if (isMainThread) { + const n = 40; + const sab = new SharedArrayBuffer(2 * n * 8 + 32 * 4); + const worker = new Worker(new URL(import.meta.url), { workerData: { sab, n } satisfies Job }); + worker.on("message", (lines: string[]) => { + for (const line of lines) console.log("worker", line); + const v = new Float64Array(sab, n * 8, n); + const ints = new Int32Array(sab, 2 * n * 8, 32); + console.log("main sees", checksum(v), ints[0], ints[31]); + // The same passes on the main thread, over a fresh buffer. + const again = new SharedArrayBuffer(2 * n * 8 + 32 * 4); + for (const line of work({ sab: again, n })) console.log("main", line); + }); + worker.on("exit", (code: number) => console.log("exit", code)); +} else { + parentPort!.postMessage(work(workerData as Job)); +} From 7d458303a632d9b3842e52cea373c3325cff3b19 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:32:00 -0500 Subject: [PATCH 105/230] Check type patch files again on a cached configuration, and supply .jsx entries - The project configuration records the local files a type patch names, with whether each existed. A configuration read again from an unchanged package.json is reused only while every answer still holds, so creating or deleting a local declaration file between builds in one process validates again. - A types patch supplies the declaration TypeScript probes for a .jsx JavaScript entry (x.d.ts for x.jsx), as it does for .js, .mjs and .cjs. - Remove the two tests/fixtures/effect4/node_modules links the previous commit added; that directory is ignored and pnpm creates them. --- .../src/frontend/type-patches/layer.ts | 2 ++ packages/compiler/src/project-config.test.ts | 25 +++++++++++++++++ packages/compiler/src/project-config.ts | 16 +++++++++-- packages/compiler/test/type-patches.test.ts | 28 ++++++++++++++++++- .../node_modules/@effect/platform-node | 1 - tests/fixtures/effect4/node_modules/effect | 1 - 6 files changed, 68 insertions(+), 5 deletions(-) delete mode 120000 tests/fixtures/effect4/node_modules/@effect/platform-node delete mode 120000 tests/fixtures/effect4/node_modules/effect diff --git a/packages/compiler/src/frontend/type-patches/layer.ts b/packages/compiler/src/frontend/type-patches/layer.ts index 5fb41f968..9c156b68c 100644 --- a/packages/compiler/src/frontend/type-patches/layer.ts +++ b/packages/compiler/src/frontend/type-patches/layer.ts @@ -242,6 +242,8 @@ function declarationEntries(root: string, local: string): Map { const rel = target.replace(/^\.\//, ""); if (/\.d\.[cm]?ts$/.test(rel)) out.set(rel, local); else if (/\.[cm]?js$/.test(rel)) out.set(rel.replace(/\.([cm]?)js$/, ".d.$1ts"), local); + // TypeScript probes x.tsx and then x.d.ts for x.jsx. + else if (rel.endsWith(".jsx")) out.set(`${rel.slice(0, -".jsx".length)}.d.ts`, local); else if (!/\.\w+$/.test(rel)) out.set(`${rel}.d.ts`, local); }; add(pkg["types"]); diff --git a/packages/compiler/src/project-config.test.ts b/packages/compiler/src/project-config.test.ts index 51d9520a0..f12a8ea89 100644 --- a/packages/compiler/src/project-config.test.ts +++ b/packages/compiler/src/project-config.test.ts @@ -281,3 +281,28 @@ test("typePatches: files and types name local declaration files", () => { rmSync(root, { recursive: true, force: true }); } }); + +test("readProjectConfig checks local type patch files again for an unchanged package.json", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-project-config-")); + try { + writeFileSync( + join(root, "package.json"), + JSON.stringify({ scriptc: { typePatches: { "lib@^1.0.0": { types: "./lib.d.ts" } } } }), + ); + const entry = join(root, "main.ts"); + const missing = readProjectConfig(entry); + expect(missing.diagnostics.map((d) => d.message)).toEqual([ + `package.json 'scriptc.typePatches.lib@^1.0.0.types' names "./lib.d.ts", which does not exist`, + ]); + expect(readProjectConfig(entry)).toBe(missing); + writeFileSync(join(root, "lib.d.ts"), "export {};\n"); + const created = readProjectConfig(entry); + expect(created.diagnostics).toEqual([]); + expect(created.typePatches[0]!.types).toBe(join(root, "lib.d.ts")); + expect(readProjectConfig(entry)).toBe(created); + rmSync(join(root, "lib.d.ts")); + expect(readProjectConfig(entry).diagnostics.map((d) => d.code)).toEqual(["SC8004"]); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/src/project-config.ts b/packages/compiler/src/project-config.ts index 4aad9cbc3..b87b67838 100644 --- a/packages/compiler/src/project-config.ts +++ b/packages/compiler/src/project-config.ts @@ -101,6 +101,10 @@ export interface ProjectConfig { wasm: string[]; /** `scriptc.typePatches`, in manifest order. */ typePatches: TypePatchEntry[]; + /** The local files the configuration checked for existence, with the + * answer. A configuration read again from an unchanged package.json is + * reused only while every answer still holds. */ + localFiles: { path: string; exists: boolean }[]; /** SC8004 for every unknown key or value. */ diagnostics: ScrDiagnostic[]; } @@ -153,6 +157,7 @@ export function emptyProjectConfig(): ProjectConfig { nodeIslands: defaultNodeIslands(), wasm: [], typePatches: [], + localFiles: [], diagnostics: [], }; } @@ -435,7 +440,9 @@ function readTypePatches( ); return null; } - if (!trackedFileExists(abs)) { + const exists = trackedFileExists(abs); + config.localFiles.push({ path: abs, exists }); + if (!exists) { problem( path, `names "${v}", which does not exist`, @@ -546,7 +553,12 @@ export function readProjectConfig(entryPath: string): ProjectConfig { const manifest = join(dir, "package.json"); const text = trackedReadFile(manifest); if (text !== null) { - if (cachedConfig !== null && cachedManifest === manifest && cachedText === text) + if ( + cachedConfig !== null && + cachedManifest === manifest && + cachedText === text && + cachedConfig.localFiles.every((file) => trackedFileExists(file.path) === file.exists) + ) return cachedConfig; // A manifest without a `scriptc` key (none spelled out, none behind a // \u escape) has the empty configuration; builds of such projects diff --git a/packages/compiler/test/type-patches.test.ts b/packages/compiler/test/type-patches.test.ts index 116715db5..ddcd5b88d 100644 --- a/packages/compiler/test/type-patches.test.ts +++ b/packages/compiler/test/type-patches.test.ts @@ -2,7 +2,7 @@ * packages: commander's HelpContext.helpWidth @typedef property and ms's * @return tags, patched from the fixtures' package.json; installed-version * selection; and the warnings for patches that do not apply as written. */ -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, expect, test } from "vitest"; @@ -153,6 +153,32 @@ test("types gives a package without declarations an entry beside its main", () = expect(typePatchSuppliesTypes(pkg)).toBe(false); }); +test("types gives a package whose main is a .jsx file an entry beside it", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-")); + try { + const pkg = join(root, "node_modules/viewjs"); + mkdirSync(join(pkg, "lib"), { recursive: true }); + writeFileSync( + join(pkg, "package.json"), + JSON.stringify({ name: "viewjs", version: "1.0.0", main: "lib/view.jsx" }), + ); + writeFileSync(join(pkg, "lib/view.jsx"), "export const view = 1;\n"); + writeFileSync(join(root, "viewjs.d.ts"), "export declare const view: number;\n"); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ scriptc: { typePatches: { "viewjs@^1.0.0": { types: "./viewjs.d.ts" } } } }), + ); + configureTypePatches(join(root, "main.ts"), parse); + expect(typePatchFileExists(join(pkg, "lib/view.d.ts"))).toBe(true); + expect(typePatchSuppliesTypes(pkg)).toBe(true); + expect(typePatchedText(join(pkg, "lib/view.d.ts"))).toBe( + "export declare const view: number;\n", + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + test("a files target the installed package does not ship warns", () => { const root = mkdtempSync(join(tmpdir(), "scriptc-type-patches-")); try { diff --git a/tests/fixtures/effect4/node_modules/@effect/platform-node b/tests/fixtures/effect4/node_modules/@effect/platform-node deleted file mode 120000 index aaa396705..000000000 --- a/tests/fixtures/effect4/node_modules/@effect/platform-node +++ /dev/null @@ -1 +0,0 @@ -../../../../../node_modules/.pnpm/@effect+platform-node@4.0.0_effect@4.0.0_redis@6.3.0/node_modules/@effect/platform-node \ No newline at end of file diff --git a/tests/fixtures/effect4/node_modules/effect b/tests/fixtures/effect4/node_modules/effect deleted file mode 120000 index 1b933cdef..000000000 --- a/tests/fixtures/effect4/node_modules/effect +++ /dev/null @@ -1 +0,0 @@ -../../../../node_modules/.pnpm/effect@4.0.0/node_modules/effect \ No newline at end of file From 6d34f4f0a5c23fe2001d8c454af7de5217e52104 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 12:41:21 -0700 Subject: [PATCH 106/230] Leave withUnitArm unchanged for pending placeholders The lowerer's optional-union cache relies on withUnitArm answering for a placeholder before it completes and recomputing after the revision moves (lowerer.test.ts). Only the optional-parameter site needs to keep the placeholder, so the refusal goes back out. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.test.ts | 14 -------------- packages/compiler/src/frontend/type-mapper.ts | 3 --- 2 files changed, 17 deletions(-) diff --git a/packages/compiler/src/frontend/type-mapper.test.ts b/packages/compiler/src/frontend/type-mapper.test.ts index 6e7e339d6..163ba7b9e 100644 --- a/packages/compiler/src/frontend/type-mapper.test.ts +++ b/packages/compiler/src/frontend/type-mapper.test.ts @@ -53,20 +53,6 @@ describe("union arm lookup", () => { }); }); -describe("recursive union placeholders", () => { - test("refuse a unit arm until their arms are known", () => { - const unions = new UnionRegistry(); - const recursive = {} as Type; - const union = unions.recursiveRef(recursive, () => "Recursive"); - const placeholder: IrType = { kind: "union", unionId: union }; - expect(unions.isPendingPlaceholder(union)).toBe(true); - expect(withUndefinedArm(placeholder, unions)).toBeNull(); - unions.finalizeRecursive(recursive, [{ kind: "record", shapeId: "r1" }, UNDEFINED_T]); - expect(unions.isPendingPlaceholder(union)).toBe(false); - expect(withUndefinedArm(placeholder, unions)).toEqual(placeholder); - }); -}); - describe("nullable collection union builders", () => { test.each([mapOf(STRING, F64), setOf(STRING)])( "optional %j fields and generator results share the same union", diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 63eed378d..d819d30a2 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -6799,9 +6799,6 @@ export function withUnitArm( if (t.kind === "union") { const def = unions.get(t.unionId); if (!def) return null; - // A recursive placeholder's arms arrive when its outer frame finishes; - // extending its (still empty) arm list would drop every arm but the unit. - if (unions.isPendingPlaceholder(t.unionId)) return null; if (def.arms.some((a) => a.kind === kind)) return t; const arms = [...def.arms, unit]; // Date and Promise arms beside other data arms have no union home. From 7222c3e900320b99c906b61f0a2987ed7e008d13 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:44:05 -0500 Subject: [PATCH 107/230] Skip module-pass work that changes nothing in the functions it walks The LLVM emitter's module passes walk every function, and on large programs most of them find nothing to change in most functions. Each pass now avoids work whose result it does not use, with the same output: - scalarizeLiteralResults built, for every function, a set of all locals and all module globals before looking for a producer call. It now collects the names a fresh binding must avoid when it makes the first one. - specializeCallbackCalls walked every body five times: rebindings, node counts and definitions, call keys, untouched bodies, and callers. One walk now records each body's node count, rebindings, written values and helper calls in walk order; the later steps read those records and walk only the bodies the rewrite changed and the copies it made. - holdCharLocalsAsUnits skips its two object walks in functions without a candidate local, and its mention count where no candidate is declared from a charAt or an optional read (the only ones it can hold). - resolveSelfCaptures only asks CaptureFlow whether bindings are unchanged, so its flow collects the facts of the bodies those questions reach instead of scanning every body. - copyStableCaptures reads the function references of the bodies it kept from its CaptureFlow facts instead of walking them again, and makes the set of local ids a copy must avoid only when it makes a copy. - versionLoopBoundParameters counts call sites only when a function has loop-bound parameters. - pairRowSums and inlineExpressionFunctions look for a row sum or an inlinable call before rebuilding a body. --- .../src/ir/callback-specialization.ts | 168 ++++++++++++------ packages/compiler/src/ir/capture-values.ts | 32 +++- packages/compiler/src/ir/char-unit-locals.ts | 10 +- .../compiler/src/ir/expression-inlining.ts | 19 +- .../compiler/src/ir/parameter-versions.ts | 9 +- packages/compiler/src/ir/row-pairs.ts | 18 +- packages/compiler/src/ir/scalar-records.ts | 12 +- packages/compiler/src/ir/self-captures.ts | 3 +- 8 files changed, 195 insertions(+), 76 deletions(-) diff --git a/packages/compiler/src/ir/callback-specialization.ts b/packages/compiler/src/ir/callback-specialization.ts index f4421f34b..ba579061c 100644 --- a/packages/compiler/src/ir/callback-specialization.ts +++ b/packages/compiler/src/ir/callback-specialization.ts @@ -33,6 +33,22 @@ const REBINDING_KINDS: readonly IrNodeKind[] = [ "closure", ]; +/** The rebindings one function's body makes: locals it writes after + * declaring them, and the closures it creates over captured bindings. */ +interface FunctionRebindings { + ids: Set; + sites: { owner: string; fnName: string; captures: string[] }[]; +} + +/** Records `node`'s rebinding into `into` (owner `fnName`). */ +function noteRebinding(fnName: string, node: IrExpr | IrStmt, into: FunctionRebindings): void { + if (node.kind === "assign" || node.kind === "forOf") into.ids.add(node.localId); + else if (node.kind === "tryCatch" && node.catchLocalId !== null) into.ids.add(node.catchLocalId); + else if (node.kind === "assignExpr" || node.kind === "incDec") into.ids.add(node.localId); + else if (node.kind === "closure" && node.captures.length) + into.sites.push({ owner: fnName, fnName: node.fnName, captures: node.captures }); +} + function rebindings( mod: { readonly functions: readonly IrFunction[] }, nodes?: IrNodeIndex, @@ -41,21 +57,31 @@ function rebindings( /** Bindings of each function that a closure it creates writes. */ closureWritten: Map>; } { + const scan = nodes?.scan(REBINDING_KINDS); + const each = mod.functions.map((fn) => { + const own: FunctionRebindings = { ids: new Set(), sites: [] }; + eachNodeOfKinds(fn, scan, (node) => noteRebinding(fn.name, node, own)); + return own; + }); + return closeRebindings(mod, each); +} + +/** The module's rebindings from each function's own (`each`, in module + * order): + * a binding a closure writes through its environment is written by the + * function that created the closure, transitively. */ +function closeRebindings( + mod: { readonly functions: readonly IrFunction[] }, + each: readonly FunctionRebindings[], +): { written: Map>; closureWritten: Map> } { const written = new Map>(); const closureWritten = new Map>(); const sites: { owner: string; fnName: string; captures: string[] }[] = []; - const scan = nodes?.scan(REBINDING_KINDS); - for (const fn of mod.functions) { - const ids = new Set(); - eachNodeOfKinds(fn, scan, (node) => { - if (node.kind === "assign" || node.kind === "forOf") ids.add(node.localId); - else if (node.kind === "tryCatch" && node.catchLocalId !== null) ids.add(node.catchLocalId); - else if (node.kind === "assignExpr" || node.kind === "incDec") ids.add(node.localId); - else if (node.kind === "closure" && node.captures.length) - sites.push({ owner: fn.name, fnName: node.fnName, captures: node.captures }); - }); - written.set(fn.name, ids); - } + mod.functions.forEach((fn, index) => { + const own = each[index]!; + written.set(fn.name, own.ids); + for (const site of own.sites) sites.push(site); + }); const functions = new Map(mod.functions.map((fn) => [fn.name, fn])); for (let changed = true; changed;) { changed = false; @@ -130,9 +156,10 @@ export function specializeCallbackCalls( symbolName: (name: string) => string = (name) => name, ): IrModule { const functions = new Map(mod.functions.map((fn) => [fn.name, fn])); - const { written, closureWritten } = rebindings(mod); const copies = new Map(); - const candidates = new Map(); + // Helpers that may take callbacks: their callback parameters are final + // once the walk below finds which of them the body rebinds. + const helpers = new Map(); for (const fn of mod.functions) { if ( !(fn.ownsPrototype || fn.name.startsWith("%arr.")) || @@ -142,19 +169,13 @@ export function specializeCallbackCalls( fn.captures !== undefined || fn.classCaptures?.length || fn.fastRead !== undefined || - fn.speculativeDispatch + fn.speculativeDispatch || + !fn.params.some((param) => param.type.kind === "func") ) continue; - const nodes = countNodes(fn); - if (nodes > MAX_FUNCTION_NODES) continue; - const rebound = written.get(fn.name)!; - const slots: number[] = []; - fn.params.forEach((param, index) => { - if (param.type.kind === "func" && !rebound.has(param.localId)) slots.push(index); - }); - if (slots.length) candidates.set(fn.name, { fn, slots, nodes }); + helpers.set(fn.name, fn); } - if (!candidates.size) return mod; + if (!helpers.size) return mod; // Function values, exports, entry points and methods: an original // reachable this way stays even when every direct call moves to a copy. @@ -162,16 +183,17 @@ export function specializeCallbackCalls( for (const cls of mod.classes ?? []) for (const method of ["constructor", ...(cls.methods ?? [])]) referenced.add(`%${cls.name}.${method}`); - // Single definitions of function-typed locals and globals. - const definitions = new Map>(); const globalIds = new Set(mod.globals?.map((global) => global.id)); const globalWrites = new Map(); - // The sum of countNodes over every function, counted by the walk below. - let totalNodes = 0; - for (const fn of mod.functions) { + // One walk of every body records what the steps below read: its node + // count (countNodes), its rebindings, the values written into each of its + // bindings, and its direct calls of helpers, in walk order. + const each = mod.functions.map((fn) => { let nodes = 0; + const own: FunctionRebindings = { ids: new Set(), sites: [] }; const values = new Map(); const counts = new Map(); + const calls: (IrExpr & { kind: "call" })[] = []; const record = (id: string, value: IrExpr | null, weight: number): void => { counts.set(id, (counts.get(id) ?? 0) + weight); if (value) { @@ -183,6 +205,7 @@ export function specializeCallbackCalls( everyStmtList(fn.body, { stmt: (stmt) => { nodes++; + noteRebinding(fn.name, stmt, own); if (stmt.kind === "varDecl") record(stmt.localId, stmt.init, stmt.init ? 1 : 0); if (stmt.kind === "assign") { if (globalIds.has(stmt.localId)) { @@ -199,15 +222,42 @@ export function specializeCallbackCalls( }, expr: (expr) => { nodes++; + noteRebinding(fn.name, expr, own); if (expr.kind === "assignExpr" || expr.kind === "incDec") { record(expr.localId, null, 2); if (globalIds.has(expr.localId)) globalWrites.set(expr.localId, []); } + if (expr.kind === "call" && helpers.has(expr.callee)) calls.push(expr); const name = exprFunctionReference(expr); if (name !== undefined) referenced.add(name); return true; }, }); + return { fn, nodes: Math.min(nodes, MAX_FUNCTION_NODES + 1), own, values, counts, calls }; + }); + const { written, closureWritten } = closeRebindings( + mod, + each.map((entry) => entry.own), + ); + const nodesOf = new Map(each.map((entry) => [entry.fn.name, entry.nodes])); + const candidates = new Map(); + for (const fn of helpers.values()) { + const nodes = nodesOf.get(fn.name)!; + if (nodes > MAX_FUNCTION_NODES) continue; + const rebound = written.get(fn.name)!; + const slots: number[] = []; + fn.params.forEach((param, index) => { + if (param.type.kind === "func" && !rebound.has(param.localId)) slots.push(index); + }); + if (slots.length) candidates.set(fn.name, { fn, slots, nodes }); + } + if (!candidates.size) return mod; + + // Single definitions of function-typed locals and globals. + const definitions = new Map>(); + // The sum of countNodes over every function. + let totalNodes = 0; + for (const { fn, nodes, values, counts } of each) { const single = new Map(); const locals = new Set(fn.locals.map((local) => local.id)); const shared = closureWritten.get(fn.name); @@ -221,7 +271,7 @@ export function specializeCallbackCalls( ) single.set(id, list[0]!); definitions.set(fn.name, single); - totalNodes += Math.min(nodes, MAX_FUNCTION_NODES + 1); + totalNodes += nodes; } const globalDefinitions = new Map(); for (const global of mod.globals ?? []) @@ -288,23 +338,18 @@ export function specializeCallbackCalls( // unknown callbacks or function-value entries. const keysOf = new Map>(); const unknown = new Set(); - for (const fn of mod.functions) - everyStmtList(fn.body, { - stmt: () => true, - expr: (expr) => { - if (expr.kind !== "call") return true; - const candidate = candidates.get(expr.callee); - if (!candidate) return true; - const key = callKey(fn, new Map(), expr, candidate); - if (key === "") unknown.add(expr.callee); - else if (key !== null) { - const keys = keysOf.get(expr.callee) ?? new Set(); - keys.add(key); - keysOf.set(expr.callee, keys); - } - return true; - }, - }); + for (const { fn, calls } of each) + for (const call of calls) { + const candidate = candidates.get(call.callee); + if (!candidate) continue; + const key = callKey(fn, new Map(), call, candidate); + if (key === "") unknown.add(call.callee); + else if (key !== null) { + const keys = keysOf.get(call.callee) ?? new Set(); + keys.add(key); + keysOf.set(call.callee, keys); + } + } const specialized = new Set(); for (const [name, keys] of keysOf) if (keys.size > 1 || unknown.has(name) || referenced.has(name)) specialized.add(name); @@ -419,12 +464,9 @@ export function specializeCallbackCalls( return owner.body.map(stmt); } - const functionsOut = mod.functions.map((fn) => { + const functionsOut = each.map(({ fn, calls }) => { // Most bodies call no specialized original; keep their nodes. - const untouched = everyStmtList(fn.body, { - stmt: () => true, - expr: (expr) => expr.kind !== "call" || !specialized.has(expr.callee), - }); + const untouched = calls.every((call) => !specialized.has(call.callee)); return untouched ? fn : { ...fn, body: rewriteBody(fn, new Map(), new Set(), false) }; }); for (let i = 0; i < pending.length; i++) { @@ -437,19 +479,29 @@ export function specializeCallbackCalls( // Drop originals no remaining code reaches: every direct call moved to a // copy and nothing names them as a value. const callers = new Map>(); - for (const fn of functionsOut) + const call = (callee: string, caller: string): void => { + if (!specialized.has(callee)) return; + const set = callers.get(callee) ?? new Set(); + set.add(caller); + callers.set(callee, set); + }; + functionsOut.forEach((fn, index) => { + // A body the rewrite kept has the calls the walk recorded, and its + // closures are already in `referenced`. + const kept = each[index]; + if (kept !== undefined && kept.fn === fn) { + for (const site of kept.calls) call(site.callee, fn.name); + return; + } everyStmtList(fn.body, { stmt: () => true, expr: (expr) => { - if (expr.kind === "call" && specialized.has(expr.callee)) { - const set = callers.get(expr.callee) ?? new Set(); - set.add(fn.name); - callers.set(expr.callee, set); - } + if (expr.kind === "call") call(expr.callee, fn.name); if (expr.kind === "closure" && specialized.has(expr.fnName)) referenced.add(expr.fnName); return true; }, }); + }); const live = new Set(); const work: string[] = []; for (const fn of functionsOut) diff --git a/packages/compiler/src/ir/capture-values.ts b/packages/compiler/src/ir/capture-values.ts index bcc587f1b..19ec41363 100644 --- a/packages/compiler/src/ir/capture-values.ts +++ b/packages/compiler/src/ir/capture-values.ts @@ -86,7 +86,12 @@ export function copyStableCaptures(mod: IrModule): IrModule { // A closure body whose every use became a direct call is only reachable // through its copy now. const referenced = new Set(moduleFunctionReferences(mod)); - for (const fn of functions) + functions.forEach((fn, index) => { + // A body the rewrite kept names what the flow recorded of it. + if (fn === mod.functions[index]) { + for (const name of flow.references(fn)) referenced.add(name); + return; + } everyStmtList(fn.body, { stmt: () => true, expr: (e) => { @@ -95,6 +100,7 @@ export function copyStableCaptures(mod: IrModule): IrModule { return true; }, }); + }); const clones = flow.directClones(); return { ...mod, @@ -251,6 +257,9 @@ interface FunctionFacts { captureIndex: Map; classCaptures: Set; params: Set; + /** Functions the body names: direct call callees and function values + * (exprFunctionReference). */ + references: Set; } interface CreationSite { @@ -273,17 +282,26 @@ export class CaptureFlow { /** Environment-free bodies called directly, and their copies' names. */ private readonly clones = new Map(); - constructor(functions: readonly IrFunction[]) { + /** With `scan` false, each body's facts are collected when a question + * first needs them, and only unchanged() may be asked: the other + * questions read creation sites, which come from scanning every body. */ + constructor(functions: readonly IrFunction[], scan = true) { for (const fn of functions) this.functions.set(fn.name, fn); this.receivers = new AmbientReceiverReaders(this.functions); // Creation sites are collected while scanning each body. - for (const fn of functions) this.factsOf(fn); + if (scan) for (const fn of functions) this.factsOf(fn); } directClones(): ReadonlyMap { return this.clones; } + /** The functions the body of `fn`, one of the functions this flow was + * made from, names (FunctionFacts.references). */ + references(fn: IrFunction): ReadonlySet { + return this.factsOf(fn).references; + } + /** A lifted body that captures nothing and can be entered like a declared * function: synchronous, never naming its own closure, and unable to * observe the ambient receiver a closure call would bracket. */ @@ -459,6 +477,7 @@ export class CaptureFlow { captureIndex: new Map(), classCaptures: new Set((fn.classCaptures ?? []).map((c) => c.localId)), params: new Set(fn.params.map((p) => p.localId)), + references: new Set(), }; (fn.captures ?? []).forEach((c, index) => { if (!facts.captureIndex.has(c.localId)) facts.captureIndex.set(c.localId, index); @@ -511,6 +530,8 @@ export class CaptureFlow { facts.writes.set(id, (facts.writes.get(id) ?? 0) + 1); }; const expr = (e: IrExpr): boolean => { + const referenced = e.kind === "call" ? e.callee : exprFunctionReference(e); + if (referenced !== undefined) facts.references.add(referenced); switch (e.kind) { case "varRef": facts.reads.set(e.localId, (facts.reads.get(e.localId) ?? 0) + 1); @@ -707,8 +728,11 @@ export class CaptureFlow { const splits = new Map(); const entry: IrStmt[] = []; const locals: IrLocal[] = []; - const used = new Set(fn.locals.map((l) => l.id)); + // The local ids a copy must avoid, collected for the first copy: most + // functions make none. + let used: Set | null = null; const fresh = (local: IrLocal): IrLocal => { + used ??= new Set(fn.locals.map((l) => l.id)); let k = 0; while (used.has(`${local.name}.${k}`)) k++; const id = `${local.name}.${k}`; diff --git a/packages/compiler/src/ir/char-unit-locals.ts b/packages/compiler/src/ir/char-unit-locals.ts index aff952909..fc1d5873b 100644 --- a/packages/compiler/src/ir/char-unit-locals.ts +++ b/packages/compiler/src/ir/char-unit-locals.ts @@ -300,6 +300,8 @@ function rewriteFunction(fn: IrFunction): IrFunction { // or as copies of another such local (a switch's discriminant temp). const reads = new Map(); const copyOf = new Map(); + // Whether a candidate string is declared from a charAt. + let charAtDeclared = false; const collect = (value: unknown): void => { if (Array.isArray(value)) for (const item of value) collect(item); else if (value !== null && typeof value === "object") { @@ -307,6 +309,7 @@ function rewriteFunction(fn: IrFunction): IrFunction { if (node.kind === "varDecl" && candidates.has(node.localId as string) && node.init) { const id = node.localId as string; const init = node.init as IrExpr; + if (locals.get(id)?.type.kind === "string" && isCharAt(init)) charAtDeclared = true; const read = optionalCharRead(init); if (read) reads.set(id, read); else if (init.kind === "varRef" && reads.has(init.localId)) { @@ -317,7 +320,8 @@ function rewriteFunction(fn: IrFunction): IrFunction { for (const key in node) if (key !== "loc") collect(node[key]); } }; - collect(fn.body); + // Both walks only record candidates: a function without one holds none. + if (candidates.size > 0) collect(fn.body); const localTags = (operand: IrExpr): OptionalCharRead | null => operand.kind === "varRef" ? (reads.get(operand.localId) ?? null) : null; @@ -344,7 +348,9 @@ function rewriteFunction(fn: IrFunction): IrFunction { for (const key in node) if (key !== "loc") count(node[key]); } }; - count(fn.body); + // Only candidates declared from a charAt or an optional read can be held + // (understoodMention), and count() only decides which are. + if (reads.size > 0 || charAtDeclared) count(fn.body); // A copy is held only with its source, and the source's mention in the // copy's initializer is understood only while the copy is held. const held = new Set(); diff --git a/packages/compiler/src/ir/expression-inlining.ts b/packages/compiler/src/ir/expression-inlining.ts index 377cfaf3f..1282cdb79 100644 --- a/packages/compiler/src/ir/expression-inlining.ts +++ b/packages/compiler/src/ir/expression-inlining.ts @@ -128,15 +128,24 @@ export function inlineExpressionFunctions(mod: IrModule): IrModule { const functions = mod.functions.map((fn) => { const locals = new Map(fn.locals.map((local) => [local.id, local])); const inLoops = callsInLoops(fn.body); - if (inLoops.size === 0) return fn; + // Each call the rewrite inlines: of an expression function, in a loop, + // with arguments the copy may repeat. + const inlinable = (e: IrExpr & { kind: "call" }): boolean => { + if (e.callee === fn.name) return false; + const target = expressions.get(e.callee); + return ( + target !== undefined && + target.fn.params.length === e.args.length && + e.args.every((arg) => duplicableArgument(arg, locals)) + ); + }; + if (![...inLoops].some((e) => e.kind === "call" && inlinable(e))) return fn; let inlined = false; const body = transformStmtList(fn.body, { stmt: (s) => s, expr: (e) => { - if (e.kind !== "call" || e.callee === fn.name || !inLoops.has(e)) return e; - const target = expressions.get(e.callee); - if (target === undefined || target.fn.params.length !== e.args.length) return e; - if (!e.args.every((arg) => duplicableArgument(arg, locals))) return e; + if (e.kind !== "call" || !inLoops.has(e) || !inlinable(e)) return e; + const target = expressions.get(e.callee)!; const args = new Map(target.fn.params.map((param, i) => [param.localId, e.args[i]!])); inlined = true; return copy(target.value, (ref) => { diff --git a/packages/compiler/src/ir/parameter-versions.ts b/packages/compiler/src/ir/parameter-versions.ts index 88232251c..5156d4bff 100644 --- a/packages/compiler/src/ir/parameter-versions.ts +++ b/packages/compiler/src/ir/parameter-versions.ts @@ -184,7 +184,8 @@ export function versionLoopBoundParameters( symbolName: (name: string) => string = (name) => name, ): IrModule { const candidates: { fn: IrFunction; params: Set }[] = []; - const sites = callSites(mod); + // Call sites are read only for functions with loop-bound parameters. + let sites: ReturnType | null = null; const functions = new Map(mod.functions.map((fn) => [fn.name, fn])); const members = new Set((mod.classes ?? []).flatMap((cls) => cls.methods ?? [])); for (const fn of mod.functions) { @@ -207,7 +208,11 @@ export function versionLoopBoundParameters( const local = locals.get(id); if (!local || local.boxed || local.tdz) params.delete(id); } - if (params.size === 0 || size(fn) > MAX_FUNCTION_NODES || keepsSingleBody(fn, sites, functions)) + if ( + params.size === 0 || + size(fn) > MAX_FUNCTION_NODES || + keepsSingleBody(fn, (sites ??= callSites(mod)), functions) + ) continue; candidates.push({ fn, params }); } diff --git a/packages/compiler/src/ir/row-pairs.ts b/packages/compiler/src/ir/row-pairs.ts index 2e189f42e..d5342d113 100644 --- a/packages/compiler/src/ir/row-pairs.ts +++ b/packages/compiler/src/ir/row-pairs.ts @@ -7,7 +7,13 @@ import { type IrModule, type IrStmt, } from "./ir.js"; -import { everyExpr, everyExprChild, mapExprChildren, transformStmtList } from "./traverse.js"; +import { + everyExpr, + everyExprChild, + everyStmtList, + mapExprChildren, + transformStmtList, +} from "./traverse.js"; /** Inner-loop expressions above this many nodes keep the loop as written. */ const MAX_TERM_NODES = 64; @@ -419,6 +425,16 @@ export function pairRowSums(mod: IrModule): IrModule { const functions = mod.functions.map((fn): IrFunction => { if (fn.async || fn.generator) return fn; const locals = new Map(fn.locals.map((local) => [local.id, local])); + // Most bodies hold no row sum: find one before copying the body. + let found = false; + everyStmtList(fn.body, { + expr: () => !found, + stmt: (s) => { + if (s.kind === "for" && matchRowSum(s, locals) !== null) found = true; + return !found; + }, + }); + if (!found) return fn; const added: IrLocal[] = []; const ids = new Set(fn.locals.map((local) => local.id)); const fresh = (name: string): string => { diff --git a/packages/compiler/src/ir/scalar-records.ts b/packages/compiler/src/ir/scalar-records.ts index 5d57cb3ec..088fd8461 100644 --- a/packages/compiler/src/ir/scalar-records.ts +++ b/packages/compiler/src/ir/scalar-records.ts @@ -554,17 +554,23 @@ export function scalarizeLiteralResults(mod: IrModule): IrModule { } if (producers.size === 0) return mod; let changed = false; + const globalIds = new Set((mod.globals ?? []).map((g) => g.id)); const functions = mod.functions.map((fn): IrFunction => { if (fn.async || fn.generator) return fn; const locals = new Map(fn.locals.map((l) => [l.id, l])); - const used = new Set([...locals.keys(), ...(mod.globals ?? []).map((g) => g.id)]); - collectLabels(fn.body, used); + // The names a fresh binding must avoid, collected when the first one is + // made: most functions inline no producer. + let used: Set | null = null; let next = 0; const fresh = (): string => { + if (used === null) { + used = new Set(locals.keys()); + collectLabels(fn.body, used); + } let id: string; do { id = `%literal.${next++}`; - } while (used.has(id)); + } while (used.has(id) || globalIds.has(id)); used.add(id); return id; }; diff --git a/packages/compiler/src/ir/self-captures.ts b/packages/compiler/src/ir/self-captures.ts index 74f634d4a..70242cb8b 100644 --- a/packages/compiler/src/ir/self-captures.ts +++ b/packages/compiler/src/ir/self-captures.ts @@ -107,7 +107,8 @@ export function resolveSelfCaptures(mod: IrModule): IrModule { if (creator.captures?.some((c) => c.localId === source)) return false; if (creator.classCaptures?.some((c) => c.localId === source)) return false; if (declarations.get(owner)!.get(source) !== 1) return false; - flow ??= new CaptureFlow(mod.functions); + // Only unchanged() is asked: the flow reads just the bodies it needs. + flow ??= new CaptureFlow(mod.functions, false); return flow.unchanged(owner, source, definition.writes); }); if (qualifies) { From 6b51aa883f99337b45a7e854f0c27eef1f6b2f65 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:46:01 -0500 Subject: [PATCH 108/230] Keep -0 row and column starts out of the paired row-sum nest The paired nest reads its bounds masked to [0, 2^25 - 1], which turns a -0 start into +0, and a term can tell the two apart (1 / i on the first row). The bounds test now requires 1 / b > 0, which -0 fails, so such a nest runs as written. Corpus 8355 adds row and column starts of -0 with terms that see the sign. --- packages/compiler/src/ir/row-pairs.ts | 13 ++++++++----- tests/corpus/8355-row-pair-columns.ts | 25 +++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/ir/row-pairs.ts b/packages/compiler/src/ir/row-pairs.ts index e6b3889a5..d54f37b0b 100644 --- a/packages/compiler/src/ir/row-pairs.ts +++ b/packages/compiler/src/ir/row-pairs.ts @@ -203,7 +203,7 @@ function copy( } const bin = ( - op: "+" | "-" | "&" | "|", + op: "+" | "-" | "/" | "&" | "|", left: IrExpr, right: IrExpr, loc: IrExpr["loc"], @@ -374,9 +374,10 @@ function pairRows( ], loc, }; - // Every bound is an integer in [0, SMALL_BOUND], so `row < end - 1` is - // `row + 1 < end` (and the same for the columns), and the masks in - // `bounds` leave the bounds unchanged while proving both counters' ranges. + // Every bound is an integer in [0, SMALL_BOUND] and not -0, so `row < + // end - 1` is `row + 1 < end` (and the same for the columns), and the + // masks in `bounds` leave the bounds unchanged while proving both + // counters' ranges. // The nest has at least two rows. The column bounds' tests and the // overlap tests run only then, so they never read an array the program // assigns only when there are rows. Rows are @@ -398,9 +399,11 @@ function pairRows( type: BOOL, loc, }); + // `1 / e > 0` holds for +0 and every positive bound, but not for -0: + // masking would turn a -0 start into +0, which a term can tell apart. const small = (e: IrExpr): IrExpr[] => [ int32(e), - compare(">=", e, 0), + { kind: "bin", op: ">", left: bin("/", num(1), again(e), loc), right: num(0), type: BOOL, loc }, compare("<=", e, SMALL_BOUND), ]; const tests: IrExpr[] = [ diff --git a/tests/corpus/8355-row-pair-columns.ts b/tests/corpus/8355-row-pair-columns.ts index 96ba26153..dccd9ca6d 100644 --- a/tests/corpus/8355-row-pair-columns.ts +++ b/tests/corpus/8355-row-pair-columns.ts @@ -2,8 +2,9 @@ // and two columns, column ranges that start above zero or at or past their // end, fractional, negative, NaN and very large column bounds, a column // bound read from an array's length, terms that read two elements per -// column, and row bounds at and past 2^25. Every sum adds its columns in -// order, so each has the bits of the loop as written. +// column, row bounds at and past 2^25, and row and column starts of -0 +// with terms that tell -0 from +0. Every sum adds its columns in order, +// so each has the bits of the loop as written. function rows(u: Float64Array, v: Float64Array, start: number, end: number, from: number, to: number): void { for (let i = start; i < end; i++) { let t = 0; @@ -20,6 +21,16 @@ function pairs(u: Float64Array, w: Float32Array, v: Float64Array, end: number): } } +// 1 / (i * 4 - j) is -Infinity at a -0 row and +0 column, and +// 1 / (j - i * 4) at a -0 column and +0 row: the first pass sees the sign. +function signs(v: Float64Array, start: number, end: number, from: number, to: number): void { + for (let i = start; i < end; i++) { + let t = 0; + for (let j = from; j < to; j++) t += 1 / (i * 4 - j) + 1 / (j - i * 4) + 1 / (i * 4 + j + 1); + v[i] = t; + } +} + const show = (label: string, v: Float64Array): void => { const parts: string[] = []; for (const x of v) parts.push(Object.is(x, -0) ? "-0" : x.toPrecision(17)); @@ -71,3 +82,13 @@ const short = new Float64Array(4).map((_, k) => k * 2.5); const out = new Float64Array(5); pairs(short, w, out, 5); show("two reads short", out); +for (const [start, from] of [ + [0, 0], + [-0, 0], + [0, -0], + [-0, -0], +]) { + const v = new Float64Array(4); + signs(v, start!, 4, from!, 5); + show(`signs ${Object.is(start, -0) ? "-0" : start} ${Object.is(from, -0) ? "-0" : from}`, v); +} From f791c1d0061b946c4f1d2f24ca9673ca50e6efba Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:47:24 -0500 Subject: [PATCH 109/230] Address review feedback --- .../src/frontend/lowering/record-inherited.ts | 144 +++++++++++++++--- .../8362-record-inherited-order.ts.json | 6 + tests/corpus/8362-record-inherited-order.ts | 40 +++++ .../record-inherited/member-signature.ts | 12 ++ .../8362-record-inherited-order.ts.json | 1 + tests/harness/record-inherited-member.test.ts | 32 ++++ 6 files changed, 211 insertions(+), 24 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8362-record-inherited-order.ts.json create mode 100644 tests/corpus/8362-record-inherited-order.ts create mode 100644 tests/fixtures/record-inherited/member-signature.ts create mode 100644 tests/harness/corpus-costs/8362-record-inherited-order.ts.json create mode 100644 tests/harness/record-inherited-member.test.ts diff --git a/packages/compiler/src/frontend/lowering/record-inherited.ts b/packages/compiler/src/frontend/lowering/record-inherited.ts index 2c01ccd0a..65d070f1d 100644 --- a/packages/compiler/src/frontend/lowering/record-inherited.ts +++ b/packages/compiler/src/frontend/lowering/record-inherited.ts @@ -104,14 +104,19 @@ export function withInheritedMethods( if (form === null || !shape || shape.tuple) return read; const loc = read.loc; const stmts: IrStmt[] = []; - const stable = (value: IrExpr, name: string): IrExpr => { - if (isSafeToRepeat(value) || value.kind === "strLit") return value; + const repeatable = (value: IrExpr): boolean => isSafeToRepeat(value) || value.kind === "strLit"; + const stable = (value: IrExpr, name: string, keep: boolean): IrExpr => { + if (keep) return value; const local = lowerer.declareHiddenLocal(name, value.type); stmts.push({ kind: "varDecl", localId: local.id, init: value, loc }); return varRef(local.id, value.type, loc); }; - const obj = stable(read.obj, "%inheritedRecv"); - const key = stable(read.key, "%inheritedKey"); + // The receiver is evaluated before the key: when computing the key runs + // code (it may reassign the receiver's variable), the receiver is held + // first, even when reading it again would be safe on its own. + const keyRepeatable = repeatable(read.key); + const obj = stable(read.obj, "%inheritedRecv", repeatable(read.obj) && keyRepeatable); + const key = stable(read.key, "%inheritedKey", keyRepeatable); const helper = inheritedReadHelper(lowerer, read.shapeId, read.type, form, bound, loc); const fallback: IrExpr = { kind: "call", callee: helper, args: [obj, key], type: read.type, loc }; if ( @@ -481,19 +486,52 @@ function builtinAnswer( // No object is created with a typed record as its prototype. return { kind: "boolLit", value: false, type: BOOL, loc }; default: { - const key = propertyKeyOf(lowerer, args[0], loc); - return { + // hasOwnProperty and propertyIsEnumerable: ToPropertyKey keeps a + // symbol a symbol, and a record's own keys are strings, so a symbol + // key is no entry of it. + const no: IrExpr = { kind: "boolLit", value: false, type: BOOL, loc }; + const arg = args[0]; + if (arg?.type.kind === "symbol") return sequence(arg, no, loc); + const has = (key: IrExpr): IrExpr => ({ kind: "call", callee: recordHasOwnHelper(lowerer, shapeId, loc), args: [o, key], type: BOOL, loc, + }); + if (arg === undefined || !mayBeSymbol(arg.type)) return has(propertyKeyOf(lowerer, arg, loc)); + const id = declare("%protoKey", DYN); + const held = varRef(id, DYN, loc); + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: id, init: lowerer.coerceToExpected(arg, DYN), loc }], + result: { + kind: "ternary", + cond: { kind: "dynTest", test: "symbol", value: held, type: BOOL, loc } as IrExpr, + then: no, + else_: has(propertyKeyOf(lowerer, held, loc)), + type: BOOL, + loc, + }, + type: BOOL, + loc, }; } } } -/** ToPropertyKey of a built-in's key argument (absent: "undefined"). */ +/** Whether a value of `type` may be a symbol at run time. */ +function mayBeSymbol(type: IrType): boolean { + return !( + isUnitType(type) || + type.kind === "string" || + type.kind === "f64" || + type.kind === "bool" + ); +} + +/** ToPropertyKey of a built-in's key argument (absent: "undefined"), for + * keys that are not symbols. */ function propertyKeyOf(lowerer: Lowerer, arg: IrExpr | undefined, loc: SrcLoc): IrExpr { if (arg === undefined || isUnitType(arg.type)) return { @@ -552,6 +590,7 @@ function callOwnOrBuiltin( want: IrType, calleeText: string, loc: SrcLoc, + resolved?: { has: IrExpr; own: IrExpr }, ): IrExpr { const shape = lowerer.shapes.get(shapeId)!; const field = shape.fields.find((f) => f.name === method); @@ -569,7 +608,47 @@ function callOwnOrBuiltin( }; const builtinAsWant = convertTo(lowerer, builtin, want) ?? builtin; if (ownType === undefined || !callableType(lowerer, ownType)) return builtinAsWant; - const own: IrExpr = field + const own = resolved?.own ?? ownEntryRead(lowerer, o, shapeId, method, loc); + const called = callOwn(lowerer, declare, own, args, builtinAsWant.type, calleeText, loc); + return { + kind: "ternary", + cond: resolved?.has ?? ownEntryTest(lowerer, o, shapeId, method, loc), + then: called, + else_: builtinAsWant, + type: builtinAsWant.type, + loc, + }; +} + +/** Whether the record holds an own entry named `method`. */ +function ownEntryTest( + lowerer: Lowerer, + o: IrExpr, + shapeId: string, + method: string, + loc: SrcLoc, +): IrExpr { + return { + kind: "call", + callee: recordHasOwnHelper(lowerer, shapeId, loc), + args: [o, { kind: "strLit", value: method, type: STRING, loc }], + type: BOOL, + loc, + }; +} + +/** The record's own entry named `method`: its field, or its overflow entry + * (undefined when absent). */ +function ownEntryRead( + lowerer: Lowerer, + o: IrExpr, + shapeId: string, + method: string, + loc: SrcLoc, +): IrExpr { + const shape = lowerer.shapes.get(shapeId)!; + const field = shape.fields.find((f) => f.name === method); + return field ? { kind: "recordGet", obj: o, shapeId, field: method, type: field.type, loc } : { kind: "recordKeyGet", @@ -580,21 +659,6 @@ function callOwnOrBuiltin( type: lowerer.withUndefinedArmOf(shape.indexValue!) ?? shape.indexValue!, loc, }; - const called = callOwn(lowerer, declare, own, args, builtinAsWant.type, calleeText, loc); - return { - kind: "ternary", - cond: { - kind: "call", - callee: recordHasOwnHelper(lowerer, shapeId, loc), - args: [o, { kind: "strLit", value: method, type: STRING, loc }], - type: BOOL, - loc, - }, - then: called, - else_: builtinAsWant, - type: builtinAsWant.type, - loc, - }; } /** Call the own entry `own` (present, of any stored type) with `args`. A @@ -727,6 +791,25 @@ export function lowerRecordInheritedCall( const recvLocal = lowerer.declareHiddenLocal("%protoRecv", receiver.type); stmts.push({ kind: "varDecl", localId: recvLocal.id, init: receiver, loc }); const o = varRef(recvLocal.id, receiver.type, loc); + // `o.m(...)` reads the member before its arguments run: an argument that + // adds, deletes or replaces the own method does not change the callee. + const field = shape.fields.find((f) => f.name === method); + const ownType = field ? field.type : shape.indexValue; + let resolved: { has: IrExpr; own: IrExpr } | undefined; + if (ownType !== undefined && callableType(lowerer, ownType)) { + const shapeId = receiver.type.shapeId; + const hasLocal = lowerer.declareHiddenLocal("%protoHas", BOOL); + stmts.push({ + kind: "varDecl", + localId: hasLocal.id, + init: ownEntryTest(lowerer, o, shapeId, method, loc), + loc, + }); + const read = ownEntryRead(lowerer, o, shapeId, method, loc); + const ownLocal = lowerer.declareHiddenLocal("%protoOwn", read.type); + stmts.push({ kind: "varDecl", localId: ownLocal.id, init: read, loc }); + resolved = { has: varRef(hasLocal.id, BOOL, loc), own: varRef(ownLocal.id, read.type, loc) }; + } const args: IrExpr[] = []; for (const node of call.arguments.nodes) { const arg = lowerer.lowerExpr(node); @@ -744,6 +827,7 @@ export function lowerRecordInheritedCall( want, ts.getTextOfNode(access), loc, + resolved, ); return { kind: "seqExpr", stmts, result, type: result.type, loc }; } @@ -751,7 +835,10 @@ export function lowerRecordInheritedCall( /** `r.toString` and the other Object.prototype methods read by name (not * called) on an index-signature record: an own entry of the member's * function type when the record holds one, else the built-in as a plain - * function value. Null when the checker's member type is not a function. */ + * function value. An own entry of another type than the member's (a + * function of another signature) throws the checked-conversion TypeError: + * the program's types claimed the member. Null when the checker's member + * type is not a function. */ export function lowerRecordInheritedMember( lowerer: Lowerer, receiver: IrExpr, @@ -810,6 +897,15 @@ export function lowerRecordInheritedMember( loc, }; } + } else if (shape.indexValue && callableType(lowerer, shape.indexValue)) { + result = { + kind: "ternary", + cond: ownEntryTest(lowerer, o, shapeId, method, loc), + then: nodeThrowExpr(1, "", OTHER_ARM_FAILURE_MESSAGE, want, loc), + else_: builtin, + type: want, + loc, + }; } return { kind: "seqExpr", stmts, result, type: want, loc }; } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8362-record-inherited-order.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8362-record-inherited-order.ts.json new file mode 100644 index 000000000..e080ae4d1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8362-record-inherited-order.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8362-record-inherited-order.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8362-record-inherited-order.ts b/tests/corpus/8362-record-inherited-order.ts new file mode 100644 index 000000000..33a72eb91 --- /dev/null +++ b/tests/corpus/8362-record-inherited-order.ts @@ -0,0 +1,40 @@ +// Object.prototype's methods on typed records keep JavaScript's evaluation +// order and key conversions. + +// A function-table key that reassigns the receiver reads the table +// evaluated before the key. +let table: Record string> = { a: () => "first" }; +const other: Record string> = { a: () => "second" }; +function key(): string { + table = other; + return "a"; +} +console.log(String(table[key()]())); + +// hasOwnProperty and propertyIsEnumerable with a symbol key on a record of +// functions test the symbol key. +const fns: Record number> = { double: (n) => n * 2 }; +const sym = Symbol("s"); +console.log(fns.hasOwnProperty(sym as unknown as string), fns.propertyIsEnumerable(sym as unknown as string)); + +// A call by name reads the method before its arguments run: an argument +// that adds, replaces or deletes the own method does not change the callee. +type Has = Record boolean>; +function add(r: Has): string { + r.hasOwnProperty = () => true; + return "zzz"; +} +function swap(r: Has): string { + r.hasOwnProperty = (key) => key === "b"; + return "a"; +} +function drop(r: Has): string { + delete r["hasOwnProperty"]; + return "zzz"; +} +const added: Has = {}; +console.log(added.hasOwnProperty(add(added)), added.hasOwnProperty("zzz")); +const swapped: Has = { hasOwnProperty: (key) => key === "a" }; +console.log(swapped.hasOwnProperty(swap(swapped)), swapped.hasOwnProperty("a")); +const removed: Has = { hasOwnProperty: () => true }; +console.log(removed.hasOwnProperty(drop(removed)), removed.hasOwnProperty("zzz")); diff --git a/tests/fixtures/record-inherited/member-signature.ts b/tests/fixtures/record-inherited/member-signature.ts new file mode 100644 index 000000000..c9c65f8ad --- /dev/null +++ b/tests/fixtures/record-inherited/member-signature.ts @@ -0,0 +1,12 @@ +// A dot read of a prototype-named member whose own entry has another +// signature than the member's (tests/harness/record-inherited-member.test.ts). +const plain: Record number> = { double: (x) => x * 2 }; +const builtin = plain.toString; +console.log(typeof builtin); +const numeric: Record number> = { toString: (x: number) => x + 1 }; +try { + const own = numeric.toString; + console.log(typeof own); +} catch (e) { + console.log(e instanceof TypeError, (e as Error).message); +} diff --git a/tests/harness/corpus-costs/8362-record-inherited-order.ts.json b/tests/harness/corpus-costs/8362-record-inherited-order.ts.json new file mode 100644 index 000000000..8bcdf4a18 --- /dev/null +++ b/tests/harness/corpus-costs/8362-record-inherited-order.ts.json @@ -0,0 +1 @@ +{ "plain": 2000, "san": 2000 } diff --git a/tests/harness/record-inherited-member.test.ts b/tests/harness/record-inherited-member.test.ts new file mode 100644 index 000000000..ed14524d2 --- /dev/null +++ b/tests/harness/record-inherited-member.test.ts @@ -0,0 +1,32 @@ +/* Object.prototype's methods on typed records (frontend/lowering/ + * record-inherited.ts): a dot read of a prototype-named member answers the + * built-in when the record has no own entry, and throws the checked + * conversion's TypeError when its own entry is a function of another + * signature than the member's, which the program's types claimed. Node reads + * the own function there, so the program cannot be a corpus case. */ +import { execFile } from "node:child_process"; +import { mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; +import { OTHER_ARM_FAILURE_MESSAGE } from "../../packages/compiler/src/frontend/lowering/coercions/unions.js"; + +const execFileAsync = promisify(execFile); +const repoRoot = join(import.meta.dirname, "../.."); +const entry = join(repoRoot, "tests/fixtures/record-inherited/member-signature.ts"); +const outDir = join(repoRoot, "node_modules/.cache/scriptc-tests/record-inherited-member"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +test( + "a prototype-named member read with an own function of another signature throws", + { timeout: 300_000 }, + async () => { + mkdirSync(outDir, { recursive: true }); + const result = await compile(entry, { outPath: join(outDir, "main"), outDir, sanitize }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const { stdout } = await execFileAsync(result.binaryPath, [], { encoding: "utf8" }); + expect(stdout).toBe(`function\ntrue ${OTHER_ARM_FAILURE_MESSAGE}\n`); + }, +); From c21fc0120180fb89bf6ff2251bba30d6739f3c7b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:49:55 -0500 Subject: [PATCH 110/230] Key bound alias instances by the declared alias The compiler compiles itself, and a Map keyed by another Map is outside the key domains it compiles (SC2009 in the self-hosted frontend). The Lowerer's table is now keyed by the declared alias reference: each entry lists the instances that instantiate it with their ts-level bindings maps, and the resolver picks the current instance's by identity. --- .../frontend/lowering/generic-functions.ts | 7 ++-- .../compiler/src/frontend/lowering/lowerer.ts | 32 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index a2eda839a..1769e8562 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -1041,11 +1041,12 @@ function unifySignatureBindings( iAliasArgs.length === dAliasArgs.length ) { if (tsBindings && declared !== inst) { - let pairs = lowerer.boundAliasInstances.get(tsBindings); - if (!pairs) lowerer.boundAliasInstances.set(tsBindings, (pairs = new Map())); + let entries = lowerer.boundAliasInstances.get(declared); + if (!entries) lowerer.boundAliasInstances.set(declared, (entries = [])); // A body forwarding its own alias (`inner(g)` returning `Catalog` inside an instance) resolves through the enclosing one. - if (!pairs.has(declared)) pairs.set(declared, lowerer.boundAliasTsResolver(inst) ?? inst); + if (!entries.some((entry) => entry.tsBindings === tsBindings)) + entries.push({ tsBindings, instance: lowerer.boundAliasTsResolver(inst) ?? inst }); } dAliasArgs.forEach((da, i) => { const ia = iAliasArgs[i]; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 216851c9c..c77741997 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2233,16 +2233,19 @@ export class Lowerer { * the body needs — indexed accesses (`T[K]` needs K's literal key) and * keyed record reads (`o[k]` where k's type is a literal-bound K). */ typeParamTsBindings: Map | null = null; - /** Generic alias references of a call-keyed instance's declared signature - * (`Catalog`), each paired with the same alias as the call's - * resolved signature instantiates it (`Catalog<{ g1: … }, "acme">`), - * keyed by the instance's ts-level bindings (typeParamTsBindings). The - * checker keeps the alias symbolic inside the generic body and offers no - * way to instantiate it with the bound arguments, but alias - * instantiations are interned: the body's `x as Catalog` is the - * very type the signature declares, so the pair recorded during binding - * unification is that re-instantiation. */ - readonly boundAliasInstances = new Map, Map>(); + /** Generic alias references of call-keyed instances' declared signatures + * (`Catalog`), each with the same alias as every call's resolved + * signature instantiates it (`Catalog<{ g1: … }, "acme">`), together with + * that instance's ts-level bindings map (typeParamTsBindings), which every + * scope that lowers the instance installs. The checker keeps the alias + * symbolic inside the generic body and offers no way to instantiate it + * with the bound arguments, but alias instantiations are interned: the + * body's `x as Catalog` is the very type the signature declares, so + * the pair recorded during unification is that re-instantiation. */ + readonly boundAliasInstances = new Map< + ts.Type, + { tsBindings: Map; instance: ts.Type }[] + >(); /** Non-null while an IMPLICIT-ANY instance body lowers (npm-static JS; * see generic-functions.ts): bound param symbol → * the call site's checker type, consulted by typeOf for identifier @@ -9563,10 +9566,15 @@ export class Lowerer { /** The concrete instantiation of a generic alias reference (`Catalog`) the current call-keyed instance's signature declares - * (boundAliasInstances). Inert outside generic function instantiation. */ + * (boundAliasInstances), or null when its signature never instantiated + * that reference. Inert outside generic function instantiation. */ readonly boundAliasTsResolver = (t: ts.Type): ts.Type | null => { if (!this.typeParamTsBindings || t.getAliasTypeArguments().length === 0) return null; - return this.boundAliasInstances.get(this.typeParamTsBindings)?.get(t) ?? null; + const tsBindings = this.typeParamTsBindings; + return ( + this.boundAliasInstances.get(t)?.find((entry) => entry.tsBindings === tsBindings)?.instance ?? + null + ); }; irTypeOf(node: ts.Node): IrType { From da616a27cd64135bf76401162630d0817a8df1b4 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 14:57:45 -0500 Subject: [PATCH 111/230] Install the global lookup for function-local snapshots, and release navigator values read alone A computed read through a snapshot of the global object taken in a function (`const g = globalThis; return g[name];`) threw SC2020 for builtin globals: the scan that decides whether a module installs the builtin-global lookup runs before function bodies are lowered, when the snapshot's alias is not registered yet. The scan now also recognizes a variable whose only declaration initializes it with the global object. navigator.language and navigator.languages cache their answers, but only taking the navigator object registered their release at exit, so a program that read just those members left them live and a sanitized build exited with status 99. The first cached value now registers the release. Corpus 8161 reads through a function-local snapshot, and 8168 reads navigator.language, navigator.languages and navigator.platform without the navigator object. --- .../src/frontend/lowering/global-values.ts | 23 ++++++++++++++++++- .../8168-navigator-language-values.cjs.json | 6 +++++ packages/runtime/src/scr_node_builtin.c | 17 +++++++++++++- tests/corpus/8161-global-computed-reads.cjs | 11 +++++++++ .../corpus/8168-navigator-language-values.cjs | 8 +++++++ .../8168-navigator-language-values.cjs.json | 1 + 6 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8168-navigator-language-values.cjs.json create mode 100644 tests/corpus/8168-navigator-language-values.cjs create mode 100644 tests/harness/corpus-costs/8168-navigator-language-values.cjs.json diff --git a/packages/compiler/src/frontend/lowering/global-values.ts b/packages/compiler/src/frontend/lowering/global-values.ts index 44e8639f4..264c99337 100644 --- a/packages/compiler/src/frontend/lowering/global-values.ts +++ b/packages/compiler/src/frontend/lowering/global-values.ts @@ -63,7 +63,7 @@ export function computedGlobalReadOf(lowerer: Lowerer, sf: ts.SourceFile): ts.Ex ts.walkPreorder(sf, (node) => { if ( (ts.isElementAccessExpression(node) || ts.isForInStatement(node)) && - stdlibGlobalNameOf(lowerer, node.expression) === "globalThis" && + isGlobalObjectRead(lowerer, node.expression) && (ts.isForInStatement(node) || readsBuiltinByName(lowerer, node)) ) { found = node.expression; @@ -75,6 +75,27 @@ export function computedGlobalReadOf(lowerer: Lowerer, sf: ts.SourceFile): ts.Ex return found; } +/** Whether `expr` reads the global object: a spelling of it, or a variable + * whose only declaration initializes it with one (`const g = globalThis`). + * The scan runs before function bodies are lowered, so the alias of a + * variable declared in a function is not registered yet. */ +function isGlobalObjectRead(lowerer: Lowerer, expr: ts.Expression): boolean { + if (stdlibGlobalNameOf(lowerer, expr) === "globalThis") return true; + let inner = expr; + while (ts.isParenthesizedExpression(inner)) inner = inner.expression; + if (!ts.isIdentifier(inner)) return false; + const declarations = lowerer.checker.getSymbolAtLocation(inner)?.declarations ?? []; + const declaration = declarations[0]; + return ( + declarations.length === 1 && + declaration !== undefined && + ts.isVariableDeclaration(declaration) && + ts.isIdentifier(declaration.name) && + declaration.initializer !== undefined && + stdlibGlobalNameOf(lowerer, declaration.initializer) === "globalThis" + ); +} + /** Whether the element access `node` of the global object can read a * builtin global by name: its key is not a symbol (`globalThis[key]` with * `key = Symbol.for(...)` reads the global object's symbol properties), diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8168-navigator-language-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8168-navigator-language-values.cjs.json new file mode 100644 index 000000000..616b0a1b4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8168-navigator-language-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8168-navigator-language-values.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_node_builtin.c b/packages/runtime/src/scr_node_builtin.c index 8604134e7..37efa8822 100644 --- a/packages/runtime/src/scr_node_builtin.c +++ b/packages/runtime/src/scr_node_builtin.c @@ -471,6 +471,18 @@ ScrDyn *scr_process_builtin_unsupported(ScrStr *id, ScrStr *member) { static SCR_TL ScrDyn *scr_navigator_box; static SCR_TL ScrDyn *scr_navigator_langs; static SCR_TL ScrStr *scr_navigator_lang; +static SCR_TL bool scr_navigator_cleanup_registered; + +static void scr_navigator_cleanup(void); + +/* Registers the release of the cached navigator values at exit, once per + * thread: a program can read navigator.language or navigator.languages + * without ever taking the navigator object. */ +static void scr_navigator_keep(void) { + if (scr_navigator_cleanup_registered) return; + scr_navigator_cleanup_registered = true; + scr_atexit(scr_navigator_cleanup); +} static void scr_navigator_refuse(const char *what) { ScrJsonBuf text; @@ -575,6 +587,7 @@ ScrStr *scr_navigator_language(void) { return NULL; } scr_navigator_lang = tag; + scr_navigator_keep(); } return scr_str_retain(scr_navigator_lang); } @@ -587,6 +600,7 @@ ScrDyn *scr_navigator_languages(void) { scr_dyn_arr_push(languages, scr_global_take_str(language)); scr_dyn_release(scr_dyn_freeze_data(languages)); scr_navigator_langs = languages; + scr_navigator_keep(); } return scr_dyn_retain(scr_navigator_langs); } @@ -661,6 +675,7 @@ static void scr_navigator_cleanup(void) { scr_navigator_box = NULL; scr_navigator_langs = NULL; scr_navigator_lang = NULL; + scr_navigator_cleanup_registered = false; } ScrDyn *scr_navigator_object(void) { @@ -673,7 +688,7 @@ ScrDyn *scr_navigator_object(void) { scr_dyn_handle_install(SCR_DYNH_NAVIGATOR, &ops); static SCR_TL char token; scr_navigator_box = scr_dyn_new_handle(&token, SCR_DYNH_NAVIGATOR); - scr_atexit(scr_navigator_cleanup); + scr_navigator_keep(); } return scr_dyn_retain(scr_navigator_box); } diff --git a/tests/corpus/8161-global-computed-reads.cjs b/tests/corpus/8161-global-computed-reads.cjs index ff5883154..19dfdc3e7 100644 --- a/tests/corpus/8161-global-computed-reads.cjs +++ b/tests/corpus/8161-global-computed-reads.cjs @@ -51,6 +51,17 @@ console.log( web.ReadableStream === ReadableStream, ); console.log(globalThis["noSuchGlobal"], typeof globalThis["Temporal"]); +// A snapshot of the global object in a function reads the same values. +function readThroughSnapshot(name) { + const g = globalThis; + return g[name]; +} +console.log( + readThroughSnapshot("setTimeout") === setTimeout, + readThroughSnapshot("Map") === Map, + typeof readThroughSnapshot("structuredClone"), + readThroughSnapshot("noSuchGlobal"), +); const pick = (name) => globalThis[name]; console.log(pick("Math") === Math, pick("JSON") === JSON, pick("Reflect") === Reflect, pick("Math").max(1, 3)); diff --git a/tests/corpus/8168-navigator-language-values.cjs b/tests/corpus/8168-navigator-language-values.cjs new file mode 100644 index 000000000..41368a1ef --- /dev/null +++ b/tests/corpus/8168-navigator-language-values.cjs @@ -0,0 +1,8 @@ +// @no-node-shims +// navigator.language, navigator.languages and navigator.platform read +// without the navigator object ever being taken as a value: the runtime +// caches the answers and releases them at exit. +"use strict"; +console.log(typeof navigator.language, navigator.language.length > 0); +console.log(navigator.languages.length, navigator.languages[0] === navigator.language); +console.log(Object.isFrozen(navigator.languages), typeof navigator.platform); diff --git a/tests/harness/corpus-costs/8168-navigator-language-values.cjs.json b/tests/harness/corpus-costs/8168-navigator-language-values.cjs.json new file mode 100644 index 000000000..e86b2692e --- /dev/null +++ b/tests/harness/corpus-costs/8168-navigator-language-values.cjs.json @@ -0,0 +1 @@ +{ "plain": 3104, "san": 4714 } From aead30fc2635a67d62764fbd1a61147cec16e592 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:10:32 -0500 Subject: [PATCH 112/230] Mark emitted functions sanitize_address only in sanitized builds The sanitized lane's -fsanitize=address link needs the attribute to instrument the emitted functions. Without a sanitizer pass it is not inert: LLVM does not shrink-wrap functions that carry it, so every call saved and restored all of a function's callee-saved registers on entry, including on paths that return before any call, and it blocks speculative loads. The LLVM helper already dropped it for builds that import runtime bitcode (speed); release builds kept it. - The new sanitizeAttributes option (default true for direct emission) is set from --sanitize wherever objectAudit is. - Without it and without a target CPU level, attribute group #0 holds a string attribute LLVM ignores, since a group cannot be empty. --- packages/cli/test/target-cpu.test.ts | 4 +++- .../compiler/src/backend/llvm/emit-module.ts | 20 ++++++++++--------- .../compiler/src/backend/llvm/emitter.test.ts | 13 ++++++++++++ packages/compiler/src/backend/llvm/emitter.ts | 9 +++++++++ packages/compiler/src/backend/llvm/shapes.ts | 5 +++-- packages/compiler/src/index.ts | 3 +++ packages/compiler/src/native/prepare.ts | 2 ++ .../compiler/test/generator-emission.test.ts | 2 +- 8 files changed, 45 insertions(+), 13 deletions(-) diff --git a/packages/cli/test/target-cpu.test.ts b/packages/cli/test/target-cpu.test.ts index ca5243564..5bc6717c3 100644 --- a/packages/cli/test/target-cpu.test.ts +++ b/packages/cli/test/target-cpu.test.ts @@ -50,9 +50,11 @@ test.runIf(process.platform === "linux" && process.arch === "x64")( const expected = `${(await execFileAsync(process.execPath, [entry])).stdout}`; const baseline = await build([]); expect(baseline).not.toContain('"target-cpu"'); + // Only --sanitize builds mark the functions for ASan instrumentation. + expect(baseline).not.toContain("sanitize_address"); expect(await build(["--target-cpu=baseline"])).toBe(baseline); const v3 = await build(["--target-cpu=x86-64-v3"]); - expect(v3).toMatch(/^attributes #0 = \{ sanitize_address "target-cpu"="x86-64-v3" \}$/m); + expect(v3).toMatch(/^attributes #0 = \{ "target-cpu"="x86-64-v3" \}$/m); if (runsV3) expect((await execFileAsync(outPath)).stdout).toBe(expected); // The bootstrap's cached route builds without a level. expect(await build([], true)).toBe(baseline); diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index af00ebc2c..8145429f1 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -196,15 +196,17 @@ function readRange(dir: string, range: number): { result: ChunkResult; texts: st /** The attribute groups of the emitted functions: #0 for every function, * #1 for coroutine bodies and #2 for out-of-line record clones. Each carries - * the build's `--target-cpu` level. */ + * the build's `--target-cpu` level, and `sanitize_address` in sanitized + * builds. A group needs one attribute: without either, #0 holds a string + * attribute that LLVM ignores. */ function functionAttributes(emitter: LlEmitter, hasNoInlineRecordClone: boolean): string[] { const cpu = targetCpuAttribute(emitter.targetCpu); + const asan = emitter.sanitizeAttributes ? " sanitize_address" : ""; + const base = `${asan}${cpu}` || ` "scriptc-function"`; return [ - `attributes #0 = { sanitize_address${cpu} }`, - ...(emitter.usesCoroutines - ? [`attributes #1 = { sanitize_address presplitcoroutine${cpu} }`] - : []), - ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address${cpu} }`] : []), + `attributes #0 = {${base} }`, + ...(emitter.usesCoroutines ? [`attributes #1 = {${asan} presplitcoroutine${cpu} }`] : []), + ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline${asan}${cpu} }`] : []), ]; } @@ -1179,9 +1181,9 @@ export function emitModuleParts(emitter: LlEmitter): string[] { ); for (const line of emitter.inlineRcTail(flushedDecls)) out.push(line); out.push( - // sanitize_address is inert under the plain pipeline; the sanitized - // lane's -fsanitize=address link activates instrumentation over the - // emitted functions too (the runtime TUs get theirs from clang). + // The sanitized lane's -fsanitize=address link instruments the + // functions marked sanitize_address (the runtime TUs get theirs from + // clang). ...functionAttributes(emitter, hasNoInlineRecordClone), ``, ); diff --git a/packages/compiler/src/backend/llvm/emitter.test.ts b/packages/compiler/src/backend/llvm/emitter.test.ts index 876476cc1..880685bf2 100644 --- a/packages/compiler/src/backend/llvm/emitter.test.ts +++ b/packages/compiler/src/backend/llvm/emitter.test.ts @@ -211,6 +211,19 @@ test("a target CPU level reaches every function attribute group and nothing else expect(emitLlvmModule(module, { targetCpu: "baseline" })).toBe(plain); }); +test("only sanitized builds mark the emitted functions sanitize_address", () => { + const module = moduleFor([call()]); + const sanitized = emitLlvmModule(module); + expect(emitLlvmModule(module, { sanitizeAttributes: true })).toBe(sanitized); + expect(sanitized).toMatch(/^attributes #0 = \{ sanitize_address \}$/m); + const plain = emitLlvmModule(module, { sanitizeAttributes: false }); + expect(plain).not.toContain("sanitize_address"); + // A group needs an attribute: an ignored string attribute stands in. + expect(plain).toMatch(/^attributes #0 = \{ "scriptc-function" \}$/m); + const v3 = emitLlvmModule(module, { sanitizeAttributes: false, targetCpu: "x86-64-v3" }); + expect(v3).toMatch(/^attributes #0 = \{ "target-cpu"="x86-64-v3" \}$/m); +}); + test("inline RC inlines every fast path at speed and keeps box releases as calls in release", () => { for (const boxed of [false, true]) { const module = moduleFor(Array.from({ length: 4 }, call), { boxed }); diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 5569599ee..f1f1fa7d6 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -511,6 +511,12 @@ export interface LlvmTargetOptions { inlineRc?: InlineRcMode; /** Define the inline RC fast-path helpers `noinline` (sanitized builds). */ rcHelpersNoinline?: boolean; + /** Mark every emitted function `sanitize_address`, which the sanitized + * lane's `-fsanitize=address` link needs to instrument it. Without a + * sanitizer pass the attribute still turns off shrink-wrapping and + * speculative loads, so plain builds pass false. Defaults to true for + * direct emission. */ + sanitizeAttributes?: boolean; /** Define `scr_detached_exit` so the Linux runtime hands the address-space * teardown after `process.exit` to a helper process (`--detached-exit`). * Executables only; the runtime's weak default keeps it off. */ @@ -1269,6 +1275,8 @@ export class LlEmitter { readonly runtimeAbiMarker: boolean; readonly detachedExit: boolean; readonly targetCpu: TargetCpu | undefined; + /** Emitted functions carry `sanitize_address` (LlvmTargetOptions). */ + readonly sanitizeAttributes: boolean; readonly wasmBoundsChecks: boolean; /** Interned string literals: UTF-8 text → { symbol, byte length } — * first-use order, the runtime ABI’s determinism discipline. */ @@ -1971,6 +1979,7 @@ export class LlEmitter { this.runtimeAbiMarker = options.runtimeAbiMarker === true; this.detachedExit = options.detachedExit === true; this.targetCpu = options.targetCpu; + this.sanitizeAttributes = options.sanitizeAttributes !== false; this.wasmBoundsChecks = options.wasmBoundsChecks === true || wasmBoundsChecks({ diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index 7498235b0..6d03db222 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -216,8 +216,9 @@ export interface RegionHooks { classDefinitions(cls: IrClassDef, sizeOf: string): string[]; } -/** Every emitted function/helper carries #0 = { sanitize_address } — see - * the emitter header. */ +/** Every emitted function/helper carries attribute group #0: the build's + * target CPU level, and `sanitize_address` in sanitized builds (see + * emit-module.ts functionAttributes). */ export const FN_ATTRS = "#0"; export { computeTraced } from "../cycle-analysis.js"; diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index ffd56a1bf..11b0f6d13 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -854,6 +854,7 @@ async function prepareExecutableInput( // the pending flag, since their link may not add an unwinder. unwindAbi: buildPlatform === "linux" && outputKind !== "obj", objectAudit: opts.sanitize === true, + sanitizeAttributes: opts.sanitize === true, lowRegion: lowRegionTarget(buildPlatform, opts.sanitize === true), ...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}), ...inlineRcOption(opts.optimization, opts.sanitize === true), @@ -925,6 +926,7 @@ async function prepareExecutableInput( runtimeAbiMarker: useRuntimePack, unwindAbi: buildPlatform === "linux", objectAudit: opts.sanitize === true, + sanitizeAttributes: opts.sanitize === true, lowRegion: lowRegionTarget(buildPlatform, opts.sanitize === true), ...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}), ...inlineRcOption(opts.optimization, opts.sanitize === true), @@ -2357,6 +2359,7 @@ async function compileLibraryTracked( pointerBits: buildPlatform === "wasi" ? 32 : 64, wasi: buildPlatform === "wasi", objectAudit: opts.sanitize === true, + sanitizeAttributes: opts.sanitize === true, ...inlineRcOption(profile.optimization, opts.sanitize === true), }); timing("llvm-emit", { diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index 11b8f9dd1..749027b80 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -431,6 +431,7 @@ export function prepareNativeExecutable( (outputKind === "exe" || outputKind === "llvm" || outputKind === "asm") && toolchain.target.platform === "linux", objectAudit: options.sanitize === true, + sanitizeAttributes: options.sanitize === true, lowRegion: lowRegionTarget(toolchain.target.platform, options.sanitize === true), functionWorkers: emitWorkers(), forkPrimitives: emitForkPrimitives(), @@ -545,6 +546,7 @@ export function prepareNativeLibrary( pointerBits: toolchain.target.pointerBits, wasi: toolchain.target.platform === "wasi", objectAudit: options.sanitize === true, + sanitizeAttributes: options.sanitize === true, ...inlineRcOption(profile.optimization, options.sanitize === true), }); timing("llvm-emit"); diff --git a/packages/compiler/test/generator-emission.test.ts b/packages/compiler/test/generator-emission.test.ts index 8c43925ea..47e5f16c5 100644 --- a/packages/compiler/test/generator-emission.test.ts +++ b/packages/compiler/test/generator-emission.test.ts @@ -50,7 +50,7 @@ console.log(sum); expect(definition(llvm, "sc_f_range")).not.toContain("@scr_coro_yield_f64("); expect(llvm).not.toContain("@scr_gen_take_out_f64("); expect(llvm).toContain("%ScrGenHead = type { i64, ptr, ptr, i32, %ScrGenSlot }"); - expect(llvm).toContain("attributes #1 = { sanitize_address presplitcoroutine }"); + expect(llvm).toContain("attributes #1 = { presplitcoroutine }"); expect(definition(llvm, "sc_gs_range")).toContain("call ptr @scr_gen_new_stackless("); expect(llvm).not.toContain("@scr_gen_yield_f64("); }); From a4ae5f9e27f89e238ca24d6dd91273674a7ba092 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:17:20 -0500 Subject: [PATCH 113/230] Run the linker job directly for host Linux links with lld A Linux executable link runs `clang -fuse-ld=lld ...`, and the driver only starts ld.lld with a job it derives from its arguments, its environment and the installed toolchain. Starting the driver costs about 10 ms of a small program's 29 ms link. LinkerJobs (native/linker-job.ts) records that job in the native cache, with the link's own inputs and output as positions. A traced link (the Linux executable cache's `-v -Wl,-t` link) records it from the job line the driver prints with -v; a link the executable cache does not trace lists it with -v -### first. The entry also keeps the identities of the files and directories the job and the driver's -v lines name (ld.lld, startup objects, library directories, the GCC installation, the driver's directory): the set the Linux executable cache records from its traced link. It is keyed by the driver's identity, the link's arguments, the toolchain environment, PATH and the working directory. A later link with the same key whose recorded paths still have those identities runs ld.lld with the job directly, and the executable cache traces it with the job's -t output and the recorded identities. Every other link runs through the driver as before: cross-target links, links without lld, a driver that is not clang, SCRIPTC_LINKER, sanitizer links, a toolchain environment that names mutable inputs, a listing without exactly one ld.lld job naming each of the link's paths once, a recorded path whose identity changed, and a job that fails (the driver then reports the error). The linker gets the same arguments, so the executables are identical. --- packages/compiler/src/native/cache.ts | 1 + packages/compiler/src/native/compiler.ts | 8 +- .../src/native/executable-cache.test.ts | 42 +- .../compiler/src/native/executable-cache.ts | 28 +- .../compiler/src/native/linker-job.test.ts | 408 ++++++++++++++++++ packages/compiler/src/native/linker-job.ts | 286 ++++++++++++ 6 files changed, 760 insertions(+), 13 deletions(-) create mode 100644 packages/compiler/src/native/linker-job.test.ts create mode 100644 packages/compiler/src/native/linker-job.ts diff --git a/packages/compiler/src/native/cache.ts b/packages/compiler/src/native/cache.ts index e204b8a6b..0250e9618 100644 --- a/packages/compiler/src/native/cache.ts +++ b/packages/compiler/src/native/cache.ts @@ -168,6 +168,7 @@ export class NativeCache { "sanitizer", "executable", "kept", + "linker-job", "runtime-pack", "binary", "dsym", diff --git a/packages/compiler/src/native/compiler.ts b/packages/compiler/src/native/compiler.ts index ff49e2ac9..0b56b0a53 100644 --- a/packages/compiler/src/native/compiler.ts +++ b/packages/compiler/src/native/compiler.ts @@ -70,6 +70,7 @@ import { prepareNativeExecutable, prepareNativeLibrary } from "./prepare.js"; import { buildSanitizedRuntime, sanitizerDriver, sanitizerFlags } from "./sanitizer.js"; import { lldLinkerArgs } from "../backend/lld.js"; import { openNativeExecutableCache } from "./executable-cache.js"; +import { LinkerJobs } from "./linker-job.js"; import { nativeFileIdentity } from "./file-identity.js"; /** The first partition keeps the whole-object key used before partitioning. */ @@ -530,11 +531,14 @@ export class NativeCompiler { "-o", stagedOutput, ]; - const cacheable = executableCache?.trace(linkArgs, stage) ?? false; + // Host Linux links with lld run the linker job the driver would. + const jobs = LinkerJobs.open(this.cache, toolchain, lld, options.sanitize ?? false); + const job = jobs?.link([...plan.inputs, stagedOutput], stage); + const cacheable = executableCache?.trace(linkArgs, stage, job) ?? false; timing("link-inputs"); // A successful dependency trace already produced this executable. // Keep its verified output instead of asking the linker to repeat it. - if (!cacheable) { + if (!cacheable && (job?.run(linkArgs, true) ?? null) === null) { const driver = options.sanitize ? sanitizerDriver(toolchain) : toolchain.linker; try { runNativeTool(driver, linkArgs); diff --git a/packages/compiler/src/native/executable-cache.test.ts b/packages/compiler/src/native/executable-cache.test.ts index 8bec9916e..4f950e5b5 100644 --- a/packages/compiler/src/native/executable-cache.test.ts +++ b/packages/compiler/src/native/executable-cache.test.ts @@ -15,7 +15,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, expect, test, vi } from "vitest"; import { contentDigest, NativeCache } from "./cache.js"; -import { NativeExecutableCache } from "./executable-cache.js"; +import { identity, NativeExecutableCache } from "./executable-cache.js"; vi.mock("node:child_process", async (original) => ({ ...(await original()), @@ -331,3 +331,43 @@ test("a Linux build that keeps its output does not read the deferred inputs", () expect(open().restore(f.destination)).toBe(true); expect(deferred).toHaveBeenCalledTimes(3); }); + +test("a Linux link that runs as its linker job is traced from the job's run, and otherwise records it", () => { + const f = fixture(); + const lib = join(f.root, "lib"); + mkdirSync(lib); + const crt = join(lib, "Scrt1.o"); + writeFileSync(crt, "startup object"); + const program = join(f.stage, "program.o"); + writeFileSync(program, "program"); + const job = { + run: vi.fn((_args: string[], _list: boolean) => ({ + output: [crt, program].join("\n") + "\n", + driverInputs: [identity(crt), identity(lib)], + })), + record: vi.fn(), + }; + const open = () => new NativeExecutableCache(f.cache, f.key, "clang", false, [f.runtime], true); + const entry = open(); + expect(entry.trace(["-o", f.output], f.stage, job)).toBe(true); + // The job runs the traced link, and the driver does not. + expect(job.run.mock.calls[0]).toEqual([["-o", f.output, "-v", "-Wl,-t"], false]); + expect(spawnSync).not.toHaveBeenCalled(); + expect(job.record).not.toHaveBeenCalled(); + entry.recordOutput(f.output); + const recorded = ( + JSON.parse(f.cache.read("kept", f.key)!.toString()) as { inputs: { path: string }[] } + ).inputs.map((saved) => saved.path); + expect(recorded).toEqual(expect.arrayContaining([crt, lib])); + expect(recorded).not.toContain(program); + expect(open().keep(f.output)).toBe(true); + // A link without a job runs through the driver, and records the job from + // the driver's output. + const record = vi.fn(); + expect(open().trace(["-o", f.output], f.stage, { run: () => null, record })).toBe(true); + expect(spawnSync).toHaveBeenCalledTimes(1); + expect(record).toHaveBeenCalledWith(["-o", f.output, "-v", "-Wl,-t"], f.library + "\n\n", [ + identity(f.sdk), + identity(f.library), + ]); +}); diff --git a/packages/compiler/src/native/executable-cache.ts b/packages/compiler/src/native/executable-cache.ts index bafd2c020..a26bc1d87 100644 --- a/packages/compiler/src/native/executable-cache.ts +++ b/packages/compiler/src/native/executable-cache.ts @@ -14,8 +14,9 @@ import type { NativeOptimization } from "../backend/optimization.js"; import type { NativeRuntimeSelection } from "../backend/runtime-pack-native.js"; import type { NativeToolchain } from "./toolchain.js"; import { contentDigest, NativeCache } from "./cache.js"; +import type { LinkerJobLink } from "./linker-job.js"; -interface InputIdentity { +export interface InputIdentity { path: string; canonical: string; kind: "file" | "directory"; @@ -33,7 +34,7 @@ interface ExecutableEntry { symbols: string | null; } -function identity(path: string): InputIdentity { +export function identity(path: string): InputIdentity { path = resolve(path); const info = statSync(path); if (!info.isFile() && !info.isDirectory()) @@ -63,7 +64,7 @@ function sameIdentity(a: InputIdentity, b: InputIdentity): boolean { ); } -function stillMatches(inputs: InputIdentity[]): boolean { +export function stillMatches(inputs: InputIdentity[]): boolean { try { return inputs.every((input) => sameIdentity(identity(input.path), input)); } catch { @@ -71,7 +72,7 @@ function stillMatches(inputs: InputIdentity[]): boolean { } } -function commandPath(command: string): string { +export function commandPath(command: string): string { if (command.includes("/") || command.includes("\\")) return resolve(command); for (const directory of (process.env["PATH"] ?? "/usr/bin:/bin").split(delimiter)) { const candidate = join(directory || process.cwd(), command); @@ -92,7 +93,7 @@ function toolOutput(executable: string, args: string[]): string { return result.stdout + "\n" + result.stderr; } -function tracePaths(output: string, driver: boolean, roots: string[]): string[] { +export function tracePaths(output: string, driver: boolean, roots: string[]): string[] { const paths = new Set(); for (const line of output.split(/\r?\n/)) { for (const candidate of driver ? driverTraceCandidates(line) : linkTraceCandidate(line)) { @@ -134,7 +135,7 @@ interface KeptEntry { inputs: InputIdentity[]; } -function validIdentity(input: InputIdentity): boolean { +export function validIdentity(input: InputIdentity): boolean { return ( input !== null && typeof input === "object" && @@ -290,8 +291,10 @@ export class NativeExecutableCache { * Private build inputs already belong to the LLVM/runtime cache identity. * A true result means the requested output was linked successfully and its * inputs remained stable. The caller can publish that output directly. On - * false, the caller links the output itself. */ - trace(args: string[], stage: string): boolean { + * false, the caller links the output itself. On Linux, `job` runs the + * traced link as its recorded linker job when it can, and otherwise records + * the job the driver ran (see LinkerJobs). */ + trace(args: string[], stage: string, job?: LinkerJobLink): boolean { this.traced = false; try { this.addDeferredInputs(); @@ -300,8 +303,13 @@ export class NativeExecutableCache { ? [] : tracePaths(toolOutput(this.linker, [...args, "-###"]), true, roots).map(identity); // On Linux, -v prints the jobs that -### would, while the link runs. - const linked = toolOutput(this.linker, [...args, ...(this.linux ? ["-v"] : []), "-Wl,-t"]); - if (this.linux) driverInputs = tracePaths(linked, true, roots).map(identity); + const traced = [...args, ...(this.linux ? ["-v"] : []), "-Wl,-t"]; + const direct = this.linux ? (job?.run(traced, false) ?? null) : null; + const linked = direct?.output ?? toolOutput(this.linker, traced); + if (this.linux) { + driverInputs = direct?.driverInputs ?? tracePaths(linked, true, roots).map(identity); + if (direct === null) job?.record(traced, linked, driverInputs); + } const paths = tracePaths(linked, false, roots); if (paths.length === 0 || !stillMatches(this.inputs) || !stillMatches(driverInputs)) return false; diff --git a/packages/compiler/src/native/linker-job.test.ts b/packages/compiler/src/native/linker-job.test.ts new file mode 100644 index 000000000..b9d718c03 --- /dev/null +++ b/packages/compiler/src/native/linker-job.test.ts @@ -0,0 +1,408 @@ +import { spawnSync } from "node:child_process"; +import { + chmodSync, + mkdirSync, + mkdtempSync, + readFileSync, + renameSync, + rmSync, + statSync, + utimesSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, expect, test } from "vitest"; +import { + LINUX_ARM64_GNU_TARGET, + LINUX_X64_GNU_TARGET, + MACOS_ARM64_TARGET, + type NativeTargetSpec, +} from "../backend/targets.js"; +import { NativeCache } from "./cache.js"; +import { LinkerJobs } from "./linker-job.js"; +import type { NativeToolchain } from "./toolchain.js"; + +const directories: string[] = []; +const saved = new Map(); +const variables = [ + "SCRIPTC_LINKER", + "COMPILER_PATH", + "LOG", + "LD", + "CRT", + "LIB", + "FAIL", + "BROKEN", + "DOUBLE", +]; +beforeEach(() => { + for (const name of variables) { + saved.set(name, process.env[name]); + delete process.env[name]; + } +}); +afterEach(() => { + for (const [name, value] of saved) { + if (value === undefined) delete process.env[name]; + else process.env[name] = value; + } + saved.clear(); + for (const path of directories.splice(0)) rmSync(path, { recursive: true, force: true }); +}); + +function script(path: string, text: string): string { + writeFileSync(path, text); + chmodSync(path, 0o755); + return path; +} + +/** A driver named `name` and an ld.lld that log their arguments to $LOG. The + * driver's job runs $LD with `-pie`, the startup object $CRT, `-L$LIB` and + * its own arguments (without `-fuse-ld=lld` and `-v`, `-Wl,` removed). With + * -### it prints that job as clang does, every argument quoted; with -v it + * prints the job it runs as clang does, only the program quoted. With + * $BROKEN, -### prints the job twice; with $DOUBLE, it names the path $DOUBLE + * once more. The linker writes its inputs to its output, prints them with + * `-t`, and fails with $FAIL. */ +function fixture(name = "clang") { + const root = mkdtempSync(join(tmpdir(), "scriptc-linker-job-")); + directories.push(root); + const bin = join(root, "bin"); + const lib = join(root, "lib"); + // The recorded inputs include the parents of the toolchain's paths (here + // `root`); stages are elsewhere, as a build's are. + const stages = join(root, "stages"); + for (const directory of [bin, lib, stages]) mkdirSync(directory); + const log = join(root, "log"); + writeFileSync(log, ""); + const driver = script( + join(bin, name), + `#!/bin/sh +echo "driver $*" >> "$LOG" +job=" \\"$LD\\" \\"-pie\\" \\"$CRT\\" \\"-L$LIB\\"" +raw=" \\"$LD\\" -pie $CRT -L$LIB" +listing=0; verbose=0 +for a; do + case "$a" in + -'###') listing=1 ;; + -v) verbose=1 ;; + -fuse-ld=lld) ;; + -Wl,*) job="$job \\"\${a#-Wl,}\\""; raw="$raw \${a#-Wl,}" ;; + *) job="$job \\"$a\\""; raw="$raw $a" ;; + esac +done +if [ $listing = 1 ]; then + echo "InstalledDir: $(dirname "$0")" >&2 + [ -z "$DOUBLE" ] && echo "$job" >&2 + [ -n "$BROKEN" ] && echo "$job" >&2 + [ -n "$DOUBLE" ] && echo "$job \\"$DOUBLE\\"" >&2 && exit 0 + exit 0 +fi +[ $verbose = 1 ] && echo "$raw" >&2 +eval "exec $job" +`, + ); + const ld = script( + join(bin, "ld.lld"), + `#!/bin/sh +echo "ld.lld $*" >> "$LOG" +[ -n "$FAIL" ] && exit 1 +out=""; prev="" +for a; do [ "$prev" = "-o" ] && out="$a"; prev="$a"; done +printf '%s\\n' "$@" > "$out" +for a; do [ "$a" = "-t" ] && printf '%s\\n' "$@" | grep '^/'; done +exit 0 +`, + ); + const crt = join(lib, "Scrt1.o"); + writeFileSync(crt, "startup object"); + Object.assign(process.env, { LOG: log, LD: ld, CRT: crt, LIB: lib }); + // A directory's identity is its stat: an old time makes a later change visible. + utimesSync(lib, 0, 0); + const cache = new NativeCache(join(root, "cache")); + const toolchain = (target: NativeTargetSpec = LINUX_X64_GNU_TARGET, helper = target) => + ({ + target, + helper: helper.helper, + linker: driver, + }) as unknown as NativeToolchain; + let count = 0; + /** A stage with a program object, the link's paths and its driver arguments. */ + const stage = () => { + const directory = join(stages, `stage-${++count}`); + mkdirSync(directory); + const program = join(directory, "program.o"); + const output = join(directory, "out"); + writeFileSync(program, "program"); + return { + directory, + output, + paths: [program, output], + args: ["-fuse-ld=lld", "-pthread", program, "-lm", "-o", output], + }; + }; + const calls = () => + readFileSync(log, "utf8") + .split("\n") + .filter((line) => line !== ""); + return { root, bin, lib, crt, ld, driver, cache, toolchain, stage, calls }; +} + +test.skipIf(process.platform === "win32")( + "a link records the driver's job once, and later links run ld.lld with their own paths", + () => { + const f = fixture(); + const jobs = LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + expect(jobs).not.toBeNull(); + const first = f.stage(); + const run = jobs.run(first.args, first.paths, first.directory, true); + expect(run).not.toBeNull(); + // The driver only listed the job; ld.lld linked. + expect(f.calls()).toEqual([ + `driver ${first.args.join(" ")} -v -###`, + `ld.lld -pie ${f.crt} -L${f.lib} -pthread ${first.paths[0]} -lm -o ${first.output}`, + ]); + expect(readFileSync(first.output, "utf8")).toContain(first.paths[0]); + const recorded = run!.driverInputs.map((input) => input.path); + expect(recorded).toEqual(expect.arrayContaining([f.ld, f.bin, f.crt, f.lib])); + expect(recorded.some((path) => path.startsWith(first.directory))).toBe(false); + // Another build, with other paths, runs the recorded job without the driver. + const second = f.stage(); + const again = LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + expect(again.run(second.args, second.paths, second.directory, true)).not.toBeNull(); + expect(f.calls().slice(2)).toEqual([ + `ld.lld -pie ${f.crt} -L${f.lib} -pthread ${second.paths[0]} -lm -o ${second.output}`, + ]); + // Other arguments are another job. + const third = f.stage(); + expect( + again.run([...third.args, "-Wl,-t"], third.paths, third.directory, true)?.output, + ).toContain(third.paths[0]); + expect(f.calls().slice(3)[0]).toBe(`driver ${third.args.join(" ")} -Wl,-t -v -###`); + }, +); + +test.skipIf(process.platform === "win32")( + "a driver link records its job from its -v output, and later links run it", + () => { + const f = fixture(); + const open = () => LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + const first = f.stage(); + const traced = [...first.args, "-v", "-Wl,-t"]; + // Without an entry, a link that is not listed runs through the driver. + const link = open().link(first.paths, first.directory); + expect(link.run(traced, false)).toBeNull(); + expect(f.calls()).toEqual([]); + const linked = spawnSync(f.driver, traced, { encoding: "utf8" }); + expect(linked.status).toBe(0); + link.record(traced, linked.stdout + "\n" + linked.stderr); + const second = f.stage(); + const run = open() + .link(second.paths, second.directory) + .run([...second.args, "-v", "-Wl,-t"], false); + expect(run?.output).toContain(second.paths[0]); + expect(run!.driverInputs.map((input) => input.path)).toEqual( + expect.arrayContaining([f.ld, f.bin, f.crt, f.lib]), + ); + expect(f.calls().slice(2)).toEqual([ + `ld.lld -pie ${f.crt} -L${f.lib} -pthread ${second.paths[0]} -lm -o ${second.output} -t`, + ]); + }, +); + +test.skipIf(process.platform === "win32")( + "a changed startup object, library directory or linker sends the link to the driver", + () => { + for (const change of ["crt", "lib", "ld"] as const) { + const f = fixture(); + const open = () => LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + const first = f.stage(); + expect(open().run(first.args, first.paths, first.directory, true)).not.toBeNull(); + if (change === "lib") writeFileSync(join(f.lib, "libm.so"), "new library"); + else { + const path = change === "crt" ? f.crt : f.ld; + const before = statSync(path); + writeFileSync(path + ".new", readFileSync(path)); + chmodSync(path + ".new", 0o755); + utimesSync(path + ".new", before.atime, before.mtime); + renameSync(path + ".new", path); + } + const second = f.stage(); + const count = f.calls().length; + expect(open().run(second.args, second.paths, second.directory, true)).toBeNull(); + expect(f.calls().length).toBe(count); + } + }, +); + +test.skipIf(process.platform === "win32")("a changed driver records the job again", () => { + const f = fixture(); + const first = f.stage(); + expect( + LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!.run( + first.args, + first.paths, + first.directory, + true, + ), + ).not.toBeNull(); + writeFileSync(f.driver, readFileSync(f.driver, "utf8") + "\n"); + const second = f.stage(); + expect( + LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!.run( + second.args, + second.paths, + second.directory, + true, + ), + ).not.toBeNull(); + expect(f.calls()[2]).toBe(`driver ${second.args.join(" ")} -v -###`); +}); + +test.skipIf(process.platform === "win32")( + "a job that fails sends this and the build's later links to the driver", + () => { + const f = fixture(); + const first = f.stage(); + const open = () => LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + expect(open().run(first.args, first.paths, first.directory, true)).not.toBeNull(); + process.env["FAIL"] = "1"; + const jobs = open(); + const second = f.stage(); + expect(jobs.run(second.args, second.paths, second.directory, true)).toBeNull(); + const count = f.calls().length; + expect(jobs.run(second.args, second.paths, second.directory, true)).toBeNull(); + expect(f.calls().length).toBe(count); + }, +); + +test.skipIf(process.platform === "win32")( + "a listing without one ld.lld job naming each path once keeps the driver, and is not asked again", + () => { + for (const listing of ["two jobs", "another linker", "a path twice"] as const) { + const f = fixture(); + if (listing === "two jobs") process.env["BROKEN"] = "1"; + if (listing === "another linker") + process.env["LD"] = script(join(f.bin, "ld.bfd"), readFileSync(f.ld, "utf8")); + const open = () => LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + const first = f.stage(); + if (listing === "a path twice") process.env["DOUBLE"] = first.paths[0]; + expect(open().run(first.args, first.paths, first.directory, true)).toBeNull(); + expect(f.calls()).toEqual([`driver ${first.args.join(" ")} -v -###`]); + // The answer is stored: the next build does not list the job again. + expect(open().run(first.args, first.paths, first.directory, true)).toBeNull(); + expect(f.calls().length).toBe(1); + delete process.env["BROKEN"]; + delete process.env["DOUBLE"]; + } + }, +); + +test.skipIf(process.platform === "win32")( + "arguments that name the stage besides the link's paths keep the driver", + () => { + const f = fixture(); + const first = f.stage(); + const jobs = LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!; + const script = `-Wl,--version-script=${join(first.directory, "exports")}`; + expect(jobs.run([...first.args, script], first.paths, first.directory, true)).toBeNull(); + expect(f.calls()).toEqual([]); + }, +); + +test.skipIf(process.platform === "win32")( + "only host Linux links through a clang driver with lld run the job", + () => { + const f = fixture(); + const lld = ["-fuse-ld=lld"]; + expect(LinkerJobs.open(f.cache, f.toolchain(), lld, false)).not.toBeNull(); + // A cross-target link: the arm64 target with the x64 host's helper. + expect( + LinkerJobs.open( + f.cache, + f.toolchain(LINUX_ARM64_GNU_TARGET, LINUX_X64_GNU_TARGET), + lld, + false, + ), + ).toBeNull(); + expect( + LinkerJobs.open( + f.cache, + f.toolchain(LINUX_X64_GNU_TARGET, LINUX_ARM64_GNU_TARGET), + lld, + false, + ), + ).toBeNull(); + expect(LinkerJobs.open(f.cache, f.toolchain(MACOS_ARM64_TARGET), lld, false)).toBeNull(); + // The driver's default linker, a sanitizer link, no cache. + expect(LinkerJobs.open(f.cache, f.toolchain(), [], false)).toBeNull(); + expect(LinkerJobs.open(f.cache, f.toolchain(), lld, true)).toBeNull(); + expect(LinkerJobs.open(null, f.toolchain(), lld, false)).toBeNull(); + // A linker that is not a clang driver. + const gcc = fixture("gcc"); + expect(LinkerJobs.open(gcc.cache, gcc.toolchain(), lld, false)).toBeNull(); + // An explicit linker, or an environment that names mutable toolchain inputs. + process.env["SCRIPTC_LINKER"] = f.driver; + expect(LinkerJobs.open(f.cache, f.toolchain(), lld, false)).toBeNull(); + delete process.env["SCRIPTC_LINKER"]; + process.env["COMPILER_PATH"] = f.bin; + expect(LinkerJobs.open(f.cache, f.toolchain(), lld, false)).toBeNull(); + }, +); + +const hostTarget = + process.arch === "x64" + ? LINUX_X64_GNU_TARGET + : process.arch === "arm64" + ? LINUX_ARM64_GNU_TARGET + : null; +const hasLld = + process.platform === "linux" && + spawnSync("clang", ["-fuse-ld=lld", "-###", "-x", "c", "/dev/null"], { + encoding: "utf8", + }).stderr?.includes("ld.lld") === true; + +test.skipIf(hostTarget === null || !hasLld)("the job links the executable the driver links", () => { + const root = mkdtempSync(join(tmpdir(), "scriptc-linker-job-clang-")); + directories.push(root); + const stage = join(root, "stage"); + mkdirSync(stage); + const program = join(stage, "program.o"); + const compiled = spawnSync("clang", ["-c", "-x", "c", "-", "-o", program], { + input: '#include \nint main(void) { puts("linked"); return 0; }\n', + }); + expect(compiled.status).toBe(0); + const toolchain = { + target: hostTarget, + helper: hostTarget!.helper, + linker: "clang", + } as unknown as NativeToolchain; + const args = (output: string) => ["-fuse-ld=lld", "-pthread", program, "-lm", "-o", output]; + const viaDriver = join(stage, "driver"); + expect(spawnSync("clang", args(viaDriver)).status).toBe(0); + const cache = new NativeCache(join(root, "cache")); + // Recorded from the -v output of a traced link, and listed with -###. + const traced = (output: string) => [...args(output), "-v", "-Wl,-t"]; + const tracedOutput = join(stage, "traced"); + const linked = spawnSync("clang", traced(tracedOutput), { encoding: "utf8" }); + expect(linked.status).toBe(0); + LinkerJobs.open(cache, toolchain, ["-fuse-ld=lld"], false)! + .link([program, tracedOutput], stage) + .record(traced(tracedOutput), linked.stdout + "\n" + linked.stderr); + for (const [name, list] of [ + ["first", false], + ["second", true], + ["third", true], + ] as const) { + const output = join(stage, name); + const link = LinkerJobs.open(cache, toolchain, ["-fuse-ld=lld"], false)!.link( + [program, output], + stage, + ); + const run = list ? link.run(args(output), true) : link.run(traced(output), false); + expect(run).not.toBeNull(); + expect(readFileSync(output).equals(readFileSync(viaDriver))).toBe(true); + expect(spawnSync(output, { encoding: "utf8" }).stdout).toBe("linked\n"); + } +}); diff --git a/packages/compiler/src/native/linker-job.ts b/packages/compiler/src/native/linker-job.ts new file mode 100644 index 000000000..e1195e4c4 --- /dev/null +++ b/packages/compiler/src/native/linker-job.ts @@ -0,0 +1,286 @@ +import { spawnSync } from "node:child_process"; +import { realpathSync } from "node:fs"; +import { basename, extname, isAbsolute, resolve } from "node:path"; +import { + toolchainEnvironmentCachePolicy, + toolchainEnvironmentFingerprint, +} from "../backend/toolchain-environment.js"; +import type { NativeToolchain } from "./toolchain.js"; +import { contentDigest, type NativeCache } from "./cache.js"; +import { + commandPath, + identity, + type InputIdentity, + stillMatches, + tracePaths, + validIdentity, +} from "./executable-cache.js"; + +/** A recorded linker job. In `job`, a number is the index of one of the link's + * own paths (its inputs and output); `inputs` are the identities of the + * other files and directories the job and the driver's `-v` lines name. */ +interface LinkerJobEntry { + schema: "scriptc.linker-job.v1"; + /** null when the driver's job could not be recorded: the link runs through + * the driver. */ + job: (string | number)[] | null; + inputs: InputIdentity[]; +} + +/** A link that ran as its recorded job: the linker's output, and the + * identities the job depends on besides the link's own paths. */ +export interface LinkerJobRun { + output: string; + driverInputs: InputIdentity[]; +} + +/** One link's use of LinkerJobs, with its own paths and stage. */ +export interface LinkerJobLink { + run(args: string[], list: boolean): LinkerJobRun | null; + record(args: string[], output: string, driverInputs?: InputIdentity[]): void; +} + +const MAX_ENTRY_BYTES = 1024 * 1024; + +function validEntry(value: unknown, paths: number): value is LinkerJobEntry { + if (value === null || typeof value !== "object") return false; + const entry = value as Partial; + if (entry.schema !== "scriptc.linker-job.v1" || !Array.isArray(entry.inputs)) return false; + if (!entry.inputs.every(validIdentity)) return false; + if (entry.job === null) return true; + if (!Array.isArray(entry.job) || typeof entry.job[0] !== "string") return false; + const used = new Set(); + for (const token of entry.job) { + if (typeof token === "string") continue; + if (!Number.isInteger(token) || token < 0 || token >= paths || used.has(token)) return false; + used.add(token); + } + return used.size === paths; +} + +function underRoots(text: string, roots: string[]): boolean { + return roots.some((root) => text.includes(root)); +} + +/** The arguments of one job line as the driver prints them: with `-###` + * each argument is quoted, with `-v` only the program and the arguments that + * contain a space, `"`, `\` or `$`; a quoted argument escapes `"`, `\` and + * `$` with `\`. Null for any other line. */ +function jobArguments(line: string): string[] | null { + const pattern = /\s*(?:"((?:\\.|[^"\\])*)"|([^\s"\\]+))/y; + const args: string[] = []; + let end = 0; + let match: RegExpExecArray | null; + while ((match = pattern.exec(line)) !== null) { + args.push(match[1] === undefined ? match[2]! : match[1].replace(/\\(.)/g, "$1")); + end = pattern.lastIndex; + } + return args.length === 0 || line.slice(end).trim() !== "" ? null : args; +} + +/** + * Linux links through a clang driver with lld. The driver only starts ld.lld + * with a job it derives from its arguments, its environment, its own + * installation and the system's (startup objects, the GCC installation, + * library directories). The job is recorded once per driver identity, + * arguments and environment, with the identities of the files and + * directories it and the driver's `-v` lines name. A later link whose recorded + * paths still have those identities runs ld.lld with the job directly; every + * other link runs through the driver. + */ +export class LinkerJobs { + /** A job run that failed in this build: later links run through the + * driver, which reports the error. */ + private failed = false; + + private constructor( + private readonly cache: NativeCache, + private readonly driver: string, + private readonly driverIdentity: InputIdentity, + ) {} + + /** Null unless the link is a host Linux link through a clang driver with + * `-fuse-ld=lld` (`lld`, the link's lld arguments), outside the sanitizer, + * with a native cache and a toolchain environment that allows complete + * artifacts. */ + static open( + cache: NativeCache | null, + toolchain: NativeToolchain, + lld: readonly string[], + sanitize: boolean, + ): LinkerJobs | null { + const target = toolchain.target; + if ( + cache === null || + sanitize || + target.platform !== "linux" || + // A cross-target link keeps the driver. + target.llvmTriple !== toolchain.helper.defaultTarget || + !lld.includes("-fuse-ld=lld") || + process.env["SCRIPTC_LINKER"] !== undefined || + !toolchainEnvironmentCachePolicy().completeArtifacts + ) + return null; + try { + const driver = commandPath(toolchain.linker); + if (!/^clang(-\d+)?$/.test(basename(realpathSync(driver)))) return null; + return new LinkerJobs(cache, driver, identity(driver)); + } catch { + return null; + } + } + + /** The link whose own inputs and output are `paths`, in `stage`. */ + link(paths: readonly string[], stage: string): LinkerJobLink { + return { + run: (args, list) => this.run(args, paths, stage, list), + record: (args, output, driverInputs) => this.record(args, paths, stage, output, driverInputs), + }; + } + + /** The entry key of the link `args` with its own `paths`, or null when + * the arguments name the stage besides those paths. */ + private key(args: readonly string[], paths: readonly string[], roots: string[]): string | null { + const index = new Map(paths.map((path, i) => [path, i])); + if (index.size !== paths.length || !paths.every((path) => args.includes(path))) return null; + if (args.some((arg) => !index.has(arg) && underRoots(arg, roots))) return null; + const shape = args.map((arg) => { + const at = index.get(arg); + return at === undefined ? arg : { path: at, extension: extname(arg) }; + }); + return contentDigest( + JSON.stringify({ + schema: 1, + driver: this.driverIdentity, + args: shape, + environment: toolchainEnvironmentFingerprint(), + cwd: process.cwd(), + path: process.env["PATH"] ?? null, + }), + ); + } + + /** + * Runs the link that the driver arguments `args` describe as its recorded + * job. `paths` are the link's own inputs and output among `args`, which may + * differ from build to build; `stage` is the directory they are in. Without + * an entry, `list` records the job first with the driver's `-v -###`; + * otherwise the caller links through the driver and passes its `-v` output + * to record(). Null when the link has to run through the driver: no job is + * recorded, a recorded path changed, or the job failed. + */ + run( + args: readonly string[], + paths: readonly string[], + stage: string, + list: boolean, + ): LinkerJobRun | null { + if (this.failed) return null; + try { + const roots = [resolve(stage), realpathSync(stage)]; + const key = this.key(args, paths, roots); + if (key === null) return null; + let entry = this.read(key, paths.length); + if (entry === null && list) { + const result = spawnSync(this.driver, [...args, "-v", "-###"], { + encoding: "utf8", + stdio: "pipe", + }); + if (result.error !== undefined || result.status !== 0 || result.signal !== null) + return null; + entry = this.store(key, result.stdout + "\n" + result.stderr, paths, roots); + } + if (entry === null || entry.job === null || !stillMatches(entry.inputs)) return null; + const job = entry.job.map((token) => (typeof token === "number" ? paths[token]! : token)); + const result = spawnSync(job[0]!, job.slice(1), { + encoding: "utf8", + stdio: "pipe", + }); + if (result.error !== undefined || result.status !== 0 || result.signal !== null) { + this.failed = true; + return null; + } + return { output: result.stdout + "\n" + result.stderr, driverInputs: entry.inputs }; + } catch { + return null; + } + } + + private read(key: string, paths: number): LinkerJobEntry | null { + const bytes = this.cache.read("linker-job", key, MAX_ENTRY_BYTES); + if (bytes === null) return null; + try { + const entry: unknown = JSON.parse(bytes.toString("utf8")); + return validEntry(entry, paths) ? entry : null; + } catch { + return null; + } + } + + /** Records the job of the link `args` from `output`, the `-v` output of + * the driver's successful link, unless an entry exists. `driverInputs` + * are the identities of the paths `output` names, when the caller has + * them. */ + record( + args: readonly string[], + paths: readonly string[], + stage: string, + output: string, + driverInputs?: InputIdentity[], + ): void { + try { + const roots = [resolve(stage), realpathSync(stage)]; + const key = this.key(args, paths, roots); + if (key !== null && this.read(key, paths.length) === null) + this.store(key, output, paths, roots, driverInputs); + } catch { + /* The next link records it. */ + } + } + + /** Stores the entry for `output`, the driver's `-v` lines and job. */ + private store( + key: string, + output: string, + paths: readonly string[], + roots: string[], + driverInputs?: InputIdentity[], + ): LinkerJobEntry { + const lines = output.split(/\r?\n/).filter((line) => line.startsWith(' "')); + const job = lines.length === 1 ? this.jobOf(jobArguments(lines[0]!), paths, roots) : null; + const entry: LinkerJobEntry = { + schema: "scriptc.linker-job.v1", + job, + inputs: job === null ? [] : (driverInputs ?? tracePaths(output, true, roots).map(identity)), + }; + this.cache.write("linker-job", key, JSON.stringify(entry)); + return entry; + } + + /** `job` with each of `paths` as its index: null unless the job runs + * ld.lld and names each of the paths once, as one whole argument, and names + * nothing else in the stage. */ + private jobOf( + job: string[] | null, + paths: readonly string[], + roots: string[], + ): (string | number)[] | null { + if (job === null || job.length === 0) return null; + if (!isAbsolute(job[0]!) || basename(job[0]!) !== "ld.lld") return null; + const index = new Map(paths.map((path, i) => [path, i])); + const used = new Set(); + const tokens: (string | number)[] = []; + for (const arg of job) { + const at = index.get(arg); + if (at === undefined) { + if (underRoots(arg, roots)) return null; + tokens.push(arg); + } else { + if (used.has(at)) return null; + used.add(at); + tokens.push(at); + } + } + return used.size === paths.length ? tokens : null; + } +} From e842da2ceb7bcb8e7c914e2d20203624497ff06a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:24:38 -0500 Subject: [PATCH 114/230] Batch the program's identifier symbols before collection --- .../compiler/src/frontend/lowering/lowerer.ts | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 2a04b33c3..d272b7014 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -5221,11 +5221,11 @@ export class Lowerer { const sourceFiles = parts.map((p) => p.sf); const fnDecls = parts.flatMap((p) => p.fnDecls); // This fixed-point scan intentionally sees deferred bodies before - // reachability is known. Batch their binding queries through the - // facade's panic fence, then keep lookup free of lowering side effects: - // resolveValueSymbol also flushes deferred diagnostics and applies - // merged-namespace fences, whose authority remains ordinary lowering. - this.checker.prefetchSymbolRoots(sourceFiles, true); + // reachability is known. Its binding queries were batched through the + // facade's panic fence before collection (prefetchProgramSymbols); keep + // lookup free of lowering side effects: resolveValueSymbol also flushes + // deferred diagnostics and applies merged-namespace fences, whose + // authority remains ordinary lowering. // The facts each fixed-point site reads, by the symbols, nodes and // element keys they belong to (created with the site list below). let siteDependencies: SiteDependencies | null = null; @@ -8027,6 +8027,7 @@ export class Lowerer { if (this.remainder) { for (const fp of parts) this.checker.prefetchSourceFile(fp.sf); } + this.prefetchProgramSymbols(parts); this.collectProgram(parts); this.speculativeTypeQueries(() => { extendAssertedRecordMembers( @@ -8486,6 +8487,7 @@ export class Lowerer { this.onEdge = (name) => { collectionEdges.add(name); }; + this.prefetchProgramSymbols(parts); this.collectProgram(parts); this.speculativeTypeQueries(() => { extendAssertedRecordMembers( @@ -12183,6 +12185,17 @@ export class Lowerer { return lowerClassExpressionInfo(this, expr); } + /** Batch-prefetches the symbols of every runtime identifier of the + * program's files. Collection's early-binding scan and the runtime- + * optional analysis look them up across every body, reached or not; + * one batch answers both, instead of one request per identifier. */ + private prefetchProgramSymbols(parts: FileParts[]): void { + this.checker.prefetchSymbolRoots( + parts.map((fp) => fp.sf), + true, + ); + } + /** Depth of the whole-program analyses that query types across every * file before reachability is known (speculativeTypeQueries). */ private speculativeTypeQueryDepth = 0; From f0f91ac1ff5a5540479d9dab10c8a69fb52631d9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:36:08 -0500 Subject: [PATCH 115/230] Load a Node module's fallback declarations only when the program names it Without @types/node, every program loaded scriptc-node-fallback.d.ts whole: the console and process globals and 47 Node modules, about 8,100 lines, parsed, bound and checked in every cold build whether or not the program imported them. - The fallback file keeps the globals and the modules they name (console, readline, readline/promises, perf_hooks, tty). Every other module, under both spellings, has its own file in ambient/node-fallback/, which references the files of the modules its declarations name. - A module's file joins the program when one of the program's files names the module in a string literal: imports, requires, import types and JSDoc types all name modules that way. The entry is read before the first program; the program-root discovery passes add modules that other files name. Files are read as tsgo reads them, and the TypeScript libraries and scriptc's own declarations are skipped. - The checks that recognized the fallback file by its path recognize every fallback file. --- .oxlintrc.jsonc | 14 +- .../ambient/node-fallback/assert-strict.d.ts | 47 + .../ambient/node-fallback/assert.d.ts | 86 + .../ambient/node-fallback/async_hooks.d.ts | 41 + .../ambient/node-fallback/buffer.d.ts | 23 + .../ambient/node-fallback/child_process.d.ts | 223 + .../ambient/node-fallback/cluster.d.ts | 17 + .../ambient/node-fallback/constants.d.ts | 15 + .../ambient/node-fallback/crypto.d.ts | 837 +++ .../compiler/ambient/node-fallback/dgram.d.ts | 58 + .../node-fallback/diagnostics_channel.d.ts | 87 + .../ambient/node-fallback/dns-promises.d.ts | 27 + .../compiler/ambient/node-fallback/dns.d.ts | 65 + .../ambient/node-fallback/events.d.ts | 60 + .../ambient/node-fallback/fs-promises.d.ts | 225 + .../compiler/ambient/node-fallback/fs.d.ts | 577 ++ .../ambient/node-fallback/graceful-fs.d.ts | 9 + .../compiler/ambient/node-fallback/http.d.ts | 326 + .../compiler/ambient/node-fallback/http2.d.ts | 816 ++ .../compiler/ambient/node-fallback/https.d.ts | 59 + .../ambient/node-fallback/module.d.ts | 10 + .../compiler/ambient/node-fallback/net.d.ts | 368 + .../compiler/ambient/node-fallback/os.d.ts | 288 + .../ambient/node-fallback/path-posix.d.ts | 22 + .../ambient/node-fallback/path-win32.d.ts | 22 + .../compiler/ambient/node-fallback/path.d.ts | 55 + .../ambient/node-fallback/process.d.ts | 11 + .../ambient/node-fallback/punycode.d.ts | 18 + .../ambient/node-fallback/querystring.d.ts | 55 + .../node-fallback/stream-consumers.d.ts | 21 + .../node-fallback/stream-promises.d.ts | 28 + .../ambient/node-fallback/stream-web.d.ts | 17 + .../ambient/node-fallback/stream.d.ts | 644 ++ .../ambient/node-fallback/string_decoder.d.ts | 20 + .../compiler/ambient/node-fallback/test.d.ts | 236 + .../node-fallback/timers-promises.d.ts | 31 + .../ambient/node-fallback/timers.d.ts | 28 + .../compiler/ambient/node-fallback/tls.d.ts | 169 + .../compiler/ambient/node-fallback/url.d.ts | 95 + .../ambient/node-fallback/util-types.d.ts | 50 + .../compiler/ambient/node-fallback/util.d.ts | 270 + .../compiler/ambient/node-fallback/v8.d.ts | 43 + .../ambient/node-fallback/worker_threads.d.ts | 113 + .../compiler/ambient/node-fallback/zlib.d.ts | 422 ++ .../ambient/scriptc-node-fallback.d.ts | 6557 ----------------- packages/compiler/src/frontend/dts-paths.ts | 139 +- .../compiler/src/frontend/fork-target.test.ts | 32 +- packages/compiler/src/frontend/fork-target.ts | 8 +- .../compiler/src/frontend/lowering/lowerer.ts | 5 +- packages/compiler/src/frontend/program.ts | 46 +- .../compiler/src/frontend/ts7/program-host.ts | 5 +- .../src/frontend/worker-target.test.ts | 17 +- .../test/node-fallback-modules.test.ts | 43 + packages/compiler/test/node-pin.test.ts | 4 +- packages/compiler/test/ts7/bench.test.ts | 10 +- packages/compiler/test/ts7/harness.ts | 4 +- scripts/native-cli-assets.mts | 5 + 57 files changed, 6924 insertions(+), 6599 deletions(-) create mode 100644 packages/compiler/ambient/node-fallback/assert-strict.d.ts create mode 100644 packages/compiler/ambient/node-fallback/assert.d.ts create mode 100644 packages/compiler/ambient/node-fallback/async_hooks.d.ts create mode 100644 packages/compiler/ambient/node-fallback/buffer.d.ts create mode 100644 packages/compiler/ambient/node-fallback/child_process.d.ts create mode 100644 packages/compiler/ambient/node-fallback/cluster.d.ts create mode 100644 packages/compiler/ambient/node-fallback/constants.d.ts create mode 100644 packages/compiler/ambient/node-fallback/crypto.d.ts create mode 100644 packages/compiler/ambient/node-fallback/dgram.d.ts create mode 100644 packages/compiler/ambient/node-fallback/diagnostics_channel.d.ts create mode 100644 packages/compiler/ambient/node-fallback/dns-promises.d.ts create mode 100644 packages/compiler/ambient/node-fallback/dns.d.ts create mode 100644 packages/compiler/ambient/node-fallback/events.d.ts create mode 100644 packages/compiler/ambient/node-fallback/fs-promises.d.ts create mode 100644 packages/compiler/ambient/node-fallback/fs.d.ts create mode 100644 packages/compiler/ambient/node-fallback/graceful-fs.d.ts create mode 100644 packages/compiler/ambient/node-fallback/http.d.ts create mode 100644 packages/compiler/ambient/node-fallback/http2.d.ts create mode 100644 packages/compiler/ambient/node-fallback/https.d.ts create mode 100644 packages/compiler/ambient/node-fallback/module.d.ts create mode 100644 packages/compiler/ambient/node-fallback/net.d.ts create mode 100644 packages/compiler/ambient/node-fallback/os.d.ts create mode 100644 packages/compiler/ambient/node-fallback/path-posix.d.ts create mode 100644 packages/compiler/ambient/node-fallback/path-win32.d.ts create mode 100644 packages/compiler/ambient/node-fallback/path.d.ts create mode 100644 packages/compiler/ambient/node-fallback/process.d.ts create mode 100644 packages/compiler/ambient/node-fallback/punycode.d.ts create mode 100644 packages/compiler/ambient/node-fallback/querystring.d.ts create mode 100644 packages/compiler/ambient/node-fallback/stream-consumers.d.ts create mode 100644 packages/compiler/ambient/node-fallback/stream-promises.d.ts create mode 100644 packages/compiler/ambient/node-fallback/stream-web.d.ts create mode 100644 packages/compiler/ambient/node-fallback/stream.d.ts create mode 100644 packages/compiler/ambient/node-fallback/string_decoder.d.ts create mode 100644 packages/compiler/ambient/node-fallback/test.d.ts create mode 100644 packages/compiler/ambient/node-fallback/timers-promises.d.ts create mode 100644 packages/compiler/ambient/node-fallback/timers.d.ts create mode 100644 packages/compiler/ambient/node-fallback/tls.d.ts create mode 100644 packages/compiler/ambient/node-fallback/url.d.ts create mode 100644 packages/compiler/ambient/node-fallback/util-types.d.ts create mode 100644 packages/compiler/ambient/node-fallback/util.d.ts create mode 100644 packages/compiler/ambient/node-fallback/v8.d.ts create mode 100644 packages/compiler/ambient/node-fallback/worker_threads.d.ts create mode 100644 packages/compiler/ambient/node-fallback/zlib.d.ts create mode 100644 packages/compiler/test/node-fallback-modules.test.ts diff --git a/.oxlintrc.jsonc b/.oxlintrc.jsonc index b0acea28a..55c8d0353 100644 --- a/.oxlintrc.jsonc +++ b/.oxlintrc.jsonc @@ -283,10 +283,22 @@ }, { // Ambient declarations describe an existing constructor and its prototype methods. - "files": ["packages/compiler/ambient/scriptc-node-fallback.d.ts"], + "files": [ + "packages/compiler/ambient/scriptc-node-fallback.d.ts", + "packages/compiler/ambient/node-fallback/*.d.ts", + ], "rules": { "typescript/no-unsafe-declaration-merging": "off", }, }, + { + // Each module's fallback declarations are a script that declares the + // module ambiently; it names the files of the modules it uses by + // reference, since a script cannot import them. + "files": ["packages/compiler/ambient/node-fallback/*.d.ts"], + "rules": { + "typescript/triple-slash-reference": "off", + }, + }, ], } diff --git a/packages/compiler/ambient/node-fallback/assert-strict.d.ts b/packages/compiler/ambient/node-fallback/assert-strict.d.ts new file mode 100644 index 000000000..ee0eaf8fa --- /dev/null +++ b/packages/compiler/ambient/node-fallback/assert-strict.d.ts @@ -0,0 +1,47 @@ +/// + +/* node:assert/strict — the same surface with the loose names bound to the + * strict comparisons (equal IS strictEqual, ...), exactly Node's aliasing; + * the module object is callable like assert's. */ +declare module "assert/strict" { + function strict(value: unknown, message?: string): void; + namespace strict { + function ok(value: unknown, message?: string): void; + function equal(actual: unknown, expected: unknown, message?: string): void; + function notEqual(actual: unknown, expected: unknown, message?: string): void; + function deepEqual(actual: unknown, expected: unknown, message?: string): void; + function notDeepEqual(actual: unknown, expected: unknown, message?: string): void; + function strictEqual(actual: unknown, expected: unknown, message?: string): void; + function notStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function partialDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function fail(message?: string): never; + function throws(fn: () => unknown, expected?: unknown, message?: string): void; + function doesNotThrow(fn: () => unknown, expected?: unknown, message?: string): void; + function rejects( + fn: (() => Promise) | Promise, + expected?: unknown, + message?: string, + ): Promise; + function doesNotReject( + fn: (() => Promise) | Promise, + expected?: unknown, + message?: string, + ): Promise; + function match(value: string, regExp: RegExp, message?: string): void; + function doesNotMatch(value: string, regExp: RegExp, message?: string): void; + function ifError(value: unknown): void; + const strict: typeof import("assert/strict"); + type AssertOptions = import("assert").AssertOptions; + type Assert = import("assert").Assert; + type AssertStrict = import("assert").AssertStrict; + const Assert: typeof import("assert").Assert; + } + export = strict; +} + +declare module "node:assert/strict" { + import strict = require("assert/strict"); + export = strict; +} diff --git a/packages/compiler/ambient/node-fallback/assert.d.ts b/packages/compiler/ambient/node-fallback/assert.d.ts new file mode 100644 index 000000000..830d33e01 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/assert.d.ts @@ -0,0 +1,86 @@ +/// + +declare module "assert" { + function assert(value: unknown, message?: string): void; + namespace assert { + interface AssertionErrorOptions { + message?: string | undefined; + actual?: unknown; + expected?: unknown; + operator?: string | undefined; + stackStartFn?: Function | undefined; + diff?: "simple" | "full" | undefined; + } + class AssertionError extends Error { + constructor(options: AssertionErrorOptions); + actual: unknown; + expected: unknown; + generatedMessage: boolean; + code: "ERR_ASSERTION"; + operator: string; + } + function ok(value: unknown, message?: string): void; + function strictEqual(actual: unknown, expected: unknown, message?: string): void; + function notStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function partialDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + function equal(actual: unknown, expected: unknown, message?: string): void; + function notEqual(actual: unknown, expected: unknown, message?: string): void; + function deepEqual(actual: unknown, expected: unknown, message?: string): void; + function notDeepEqual(actual: unknown, expected: unknown, message?: string): void; + function fail(message?: string): never; + function throws(fn: () => unknown, expected?: unknown, message?: string): void; + function doesNotThrow(fn: () => unknown, expected?: unknown, message?: string): void; + function rejects( + fn: (() => Promise) | Promise, + expected?: unknown, + message?: string, + ): Promise; + function doesNotReject( + fn: (() => Promise) | Promise, + expected?: unknown, + message?: string, + ): Promise; + function match(value: string, regExp: RegExp, message?: string): void; + function doesNotMatch(value: string, regExp: RegExp, message?: string): void; + function ifError(value: unknown): void; + const strict: typeof import("assert/strict"); + interface AssertOptions { + diff?: "simple" | "full" | undefined; + strict?: boolean | undefined; + skipPrototype?: boolean | undefined; + } + type AssertMethodNames = + | "deepEqual" + | "deepStrictEqual" + | "doesNotMatch" + | "doesNotReject" + | "doesNotThrow" + | "equal" + | "fail" + | "ifError" + | "match" + | "notDeepEqual" + | "notDeepStrictEqual" + | "notEqual" + | "notStrictEqual" + | "ok" + | "partialDeepStrictEqual" + | "rejects" + | "strictEqual" + | "throws"; + interface Assert extends Pick {} + interface AssertStrict extends Pick {} + const Assert: { + new (options?: AssertOptions & { strict?: true | undefined }): AssertStrict; + new (options: AssertOptions): Assert; + }; + } + export = assert; +} + +declare module "node:assert" { + import assert = require("assert"); + export = assert; +} diff --git a/packages/compiler/ambient/node-fallback/async_hooks.d.ts b/packages/compiler/ambient/node-fallback/async_hooks.d.ts new file mode 100644 index 000000000..91082ee12 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/async_hooks.d.ts @@ -0,0 +1,41 @@ +/* node:async_hooks — declared surface without lowering: async-hook + * instrumentation is guarded behind env flags in harness code; a reached + * use fences at its site. */ +declare module "async_hooks" { + /* AsyncLocalStorage: fiber-carried context snapshots — run/getStore/ + * exit/enterWith/disable lower onto the runtime's active-slot machinery + * (als.* libCalls); values cross as dyn values. bind and snapshot bind + * functions to the active context; the options carry the store's + * default value and name. */ + export interface AsyncLocalStorageOptions { + defaultValue?: any; + name?: string | undefined; + } + export class AsyncLocalStorage { + constructor(options?: AsyncLocalStorageOptions); + static bind any>(fn: Func): Func; + static snapshot(): (fn: (...args: TArgs) => R, ...args: TArgs) => R; + readonly name: string; + run(store: T, callback: (...args: any[]) => R, ...args: any[]): R; + exit(callback: (...args: any[]) => R, ...args: any[]): R; + getStore(): T | undefined; + enterWith(store: T): void; + disable(): void; + } + export interface AsyncHook { + enable(): AsyncHook; + disable(): AsyncHook; + } + export function createHook(callbacks: { + init?: (asyncId: number, type: string, triggerAsyncId: number, resource: unknown) => void; + before?: (asyncId: number) => void; + after?: (asyncId: number) => void; + destroy?: (asyncId: number) => void; + }): AsyncHook; + export function executionAsyncId(): number; + export function triggerAsyncId(): number; +} + +declare module "node:async_hooks" { + export * from "async_hooks"; +} diff --git a/packages/compiler/ambient/node-fallback/buffer.d.ts b/packages/compiler/ambient/node-fallback/buffer.d.ts new file mode 100644 index 000000000..c37b91c70 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/buffer.d.ts @@ -0,0 +1,23 @@ +/* node:buffer — the module spelling of the Buffer/atob/btoa globals. + * Buffer re-exports the GLOBAL declaration, so an imported binding + * resolves to the same symbol the lowerings already answer for (and + * third-party .d.ts files that `import { Buffer } from "node:buffer"` — + * file-entry-cache in a typed-JS source graph — typecheck). */ +declare module "buffer" { + export function isAscii(input: ArrayBuffer | ArrayBufferView): boolean; + export function isUtf8(input: ArrayBuffer | ArrayBufferView): boolean; + export function transcode(source: Uint8Array, fromEncoding: string, toEncoding: string): Buffer; + export function atob(data: string): string; + export function btoa(data: string): string; + export const kMaxLength: number; + export const kStringMaxLength: number; + export const Buffer: BufferConstructor; + export type Blob = globalThis.Blob; + export type File = globalThis.File; + export const Blob: typeof globalThis.Blob; + export const File: typeof globalThis.File; +} + +declare module "node:buffer" { + export * from "buffer"; +} diff --git a/packages/compiler/ambient/node-fallback/child_process.d.ts b/packages/compiler/ambient/node-fallback/child_process.d.ts new file mode 100644 index 000000000..c9e6483c4 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/child_process.d.ts @@ -0,0 +1,223 @@ +/* node:child_process — the synchronous slice: spawnSync(command, args?) + * runs the child to completion (posix_spawn + waitpid) with stdout/stderr + * captured as utf8 strings. `status` is the exit code, or null when the + * child died to a signal OR could not be spawned at all (nonexistent + * binary, permission denied) — Node reports spawn failure through the + * result's `error` property, which this surface does not carry, and types + * stdout/stderr null there where scriptc returns "" (SEMANTICS.md). + * No shell, no options object: the command runs directly (PATH-searched, + * like Node). */ +declare module "child_process" { + export interface SpawnSyncReturns { + readonly status: number | null; + readonly stdout: string; + readonly stderr: string; + /* The termination signal's name ("SIGTERM", ...) when a signal + * killed the child (a timeout's killSignal included), null otherwise + * — Node's shape. */ + readonly signal: string | null; + /* Node's spawn-failure carrier: a real Error ("spawnSync + * ENOENT", `code` stamped) when the spawn itself failed — status is + * null and both outputs "" then — or ETIMEDOUT after a timeout kill; + * undefined otherwise. */ + readonly error?: Error; + } + /* scriptc always captures utf8 strings; pass { encoding: "utf8" } + * whenever stdout/stderr is READ so plain Node agrees (without it Node + * hands Buffers). The other supported options: timeout (ms — sends + * killSignal at the deadline and the result carries error: ETIMEDOUT + + * the signal, never a throw), killSignal (a signal NAME literal), + * stdio ("ignore"/"inherit"/"pipe" or a 3-tuple of those — captures + * read "" for non-piped streams), env (replaces the child's + * environment), and windowsHide (a POSIX no-op). */ + export function spawnSync( + command: string, + args?: string[], + options?: { + encoding?: "utf8" | "utf-8"; + timeout?: number; + killSignal?: string; + stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; + env?: { [k: string]: string | undefined }; + windowsHide?: boolean; + }, + ): SpawnSyncReturns; + + /* The asynchronous slice: spawn with stdio "ignore"/"inherit"/fd/"pipe" + * tuples (piped stdin is writable through child.stdin; piped stdout/stderr + * deliver through child.stdout/stderr) and the terminal events. "exit" + * fires once with the exit code, or + * null when the child died to a signal; "error" fires ONLY when the + * child could not be spawned at all (Node's split: a spawn failure + * emits "error" and never "exit"). "close" fires after exit/error and + * every piped stdio handle reaches EOF. Exit/close listeners may take + * Node's code and signal parameters. `on`/`once` return void here (Node + * returns the child; chaining is fenced). The event loop keeps the process + * alive until every spawned child is reaped, like Node — reaping polls at + * loop quiescence (SEMANTICS.md documents the divergence). An "error" + * event with no registered listener prints the error and exits 1, + * exactly the unhandled-'error' EventEmitter behavior. */ + export interface ChildStdin { + readonly writable: boolean; + write(chunk: string | Uint8Array): boolean; + end(): void; + destroy(): void; + on(event: "drain" | "finish", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: string, listener: (...args: any[]) => void): void; + once(event: "drain" | "finish", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: string, listener: (...args: any[]) => void): void; + } + + export interface ChildProcess extends Disposable { + ref(): void; + on(event: "spawn", listener: () => void): this; + once(event: "spawn", listener: () => void): this; + /* The exit listener may also take Node's second parameter — the + * terminating signal's name, null for a normal exit. */ + on(event: "exit", listener: (code: number | null, signal: string | null) => void): this; + on(event: "close", listener: (code: number | null, signal: string | null) => void): this; + on(event: "error", listener: (err: Error) => void): this; + once(event: "exit", listener: (code: number | null, signal: string | null) => void): this; + once(event: "close", listener: (code: number | null, signal: string | null) => void): this; + once(event: "error", listener: (err: Error) => void): this; + on(event: "message", listener: (message: any) => void): this; + on(event: "disconnect", listener: () => void): this; + once(event: "message", listener: (message: any) => void): this; + once(event: "disconnect", listener: () => void): this; + /* The lifecycle members, Node's exact shapes: pid is undefined exactly + * when the spawn failed; exitCode is null while running, the code + * after a normal exit, null for a signal death, and -errno once a + * spawn failure's "error" event has fired; kill resolves signal names + * through Node's table (unknown names throw the Unknown signal + * TypeError), answers true when the signal was sent, false once the + * child was reaped (or never spawned), and sets `killed` on success; + * unref() drops the child from the event loop's keep-alive set (it is + * still reaped while the loop runs for other reasons — SEMANTICS.md + * has the reap story). */ + readonly pid: number | undefined; + readonly exitCode: number | null; + readonly killed: boolean; + kill(signal?: string | number): boolean; + unref(): void; + readonly connected: boolean; + send(message: object, callback?: (error: Error | null) => void): boolean; + disconnect(): void; + [Symbol.dispose](): void; + /* The piped streams — non-null exactly when the matching stdio slot + * was "pipe" (Node's shape). */ + readonly stdin: ChildStdin | null; + readonly stdout: NodeJS.ReadableStream | null; + readonly stderr: NodeJS.ReadableStream | null; + } + export interface SpawnOptions { + /* The 3-tuple form admits number fds in the stdout/stderr slots — + * openSync results dup2'd into the child (the daemon-log idiom + * ["ignore", logFd, logFd]) — and "pipe" there too (child.stdout/ + * child.stderr streams); "pipe" in the stdin slot exposes + * child.stdin. */ + stdio?: "ignore" | "inherit" | "pipe" | ("ignore" | "inherit" | "pipe" | number)[]; + /* detached gives the child its own session and process group + * (POSIX_SPAWN_SETSID); env REPLACES the child environment; cwd + * sets its working directory; windowsHide is a POSIX no-op; shell + * runs the complete command string through /bin/sh or cmd.exe. */ + detached?: boolean; + env?: { [k: string]: string | undefined }; + cwd?: string; + windowsHide?: boolean; + shell?: boolean; + } + export function spawn(command: string, args?: string[], options?: SpawnOptions): ChildProcess; + export function spawn(command: string, options?: SpawnOptions): ChildProcess; + + export function fork( + modulePath: string | URL, + args?: string[], + options?: { + cwd?: string; + env?: { [k: string]: string | undefined }; + execArgv?: string[]; + silent?: boolean; + stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit" | "ipc")[]; + serialization?: "json" | "advanced"; + windowsHide?: boolean; + }, + ): ChildProcess; + + /* The synchronous exec pair: execFileSync runs a file directly (no + * shell), execSync runs a command through /bin/sh -c. Both capture + * stdout as a utf8 string (pass { encoding: "utf8" } so plain Node + * agrees — without it Node returns a Buffer) and THROW on a non-zero + * exit / signal death — the Error's message is Node's "Command failed: + * " (with the captured stderr appended). Supported options: + * encoding ("utf8"), cwd, env (REPLACES the child environment), input + * (fed to stdin), timeout (ms — SIGTERMs the child, then throws + * ETIMEDOUT), stdio ("pipe"/"ignore" or a 3-tuple of those — the + * default captures stderr AND echoes it to the parent, like Node), and + * maxBuffer (accepted, not enforced). Node's status/stdout/stderr error + * properties are not carried (SEMANTICS.md). */ + interface ExecSyncOptions { + encoding?: "utf8" | "utf-8"; + cwd?: string; + env?: { [k: string]: string | undefined }; + input?: string; + timeout?: number; + stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; + maxBuffer?: number; + killSignal?: string; + windowsHide?: boolean; + shell?: boolean; + } + /* The named options interface for the string-encoding exec form — + * @types/node's ExecFileSyncOptionsWithStringEncoding, narrowed to the + * honestly-implemented members. VALUES of this type are a real record + * (type-mapper.ts interns the shape), so a typed const or a runner function's + * options parameter flows to execFileSync at runtime — the windows-ca + * command-runner idiom. */ + export interface ExecFileSyncOptionsWithStringEncoding { + encoding: "utf8" | "utf-8"; + cwd?: string; + input?: string; + timeout?: number; + stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; + maxBuffer?: number; + windowsHide?: boolean; + } + export function execFileSync(file: string, args?: string[], options?: ExecSyncOptions): string; + export function execSync(command: string, options?: ExecSyncOptions): string; + export type ExecFileCallback = (error: Error | null, stdout: string, stderr: string) => void; + /* The callback form starts an asynchronous child and captures utf8 + * stdout/stderr. The no-callback overload remains for util.promisify's + * static target recognition; reached no-callback calls still fence. */ + export function execFile(file: string, callback: ExecFileCallback): ChildProcess; + export function execFile( + file: string, + args: string[] | null, + callback: ExecFileCallback, + ): ChildProcess; + export function execFile( + file: string, + args: string[] | null, + options: ExecSyncOptions, + callback: ExecFileCallback, + ): ChildProcess; + export function execFile( + file: string, + args?: string[] | null, + options?: ExecSyncOptions, + ): unknown; + /* The async shell form. The callback forms lower like execFile's, with + * the command line run by /bin/sh -c, and util.promisify recognizes it + * statically, as it does execFile; reached calls without a callback + * fence at their site. */ + export function exec( + command: string, + options?: ExecSyncOptions | ((error: Error | null, stdout: string, stderr: string) => void), + callback?: (error: Error | null, stdout: string, stderr: string) => void, + ): ChildProcess; +} + +declare module "node:child_process" { + export * from "child_process"; +} diff --git a/packages/compiler/ambient/node-fallback/cluster.d.ts b/packages/compiler/ambient/node-fallback/cluster.d.ts new file mode 100644 index 000000000..7cb89b09f --- /dev/null +++ b/packages/compiler/ambient/node-fallback/cluster.d.ts @@ -0,0 +1,17 @@ +/* node:cluster — the primary-probe slice. A compiled binary never runs + * as a cluster worker (cluster forks the node binary itself — the + * mechanism is na for compiled programs), so isPrimary lowers to `true` + * and isWorker to `false`; everything else is undeclared. */ +declare module "cluster" { + const cluster: { + readonly isPrimary: boolean; + readonly isMaster: boolean; + readonly isWorker: boolean; + }; + export = cluster; +} + +declare module "node:cluster" { + import cluster = require("cluster"); + export = cluster; +} diff --git a/packages/compiler/ambient/node-fallback/constants.d.ts b/packages/compiler/ambient/node-fallback/constants.d.ts new file mode 100644 index 000000000..8ff410bfa --- /dev/null +++ b/packages/compiler/ambient/node-fallback/constants.d.ts @@ -0,0 +1,15 @@ +/* node:constants (deprecated, DEP0008) — one frozen object: os.constants' + * dlopen, errno, priority and signals groups, fs.constants, and + * crypto.constants. A platform's missing constant reads as undefined. */ +declare module "constants" { + const constants: { readonly [name: string]: number } & { + readonly defaultCoreCipherList: string; + readonly defaultCipherList: string; + }; + export = constants; +} + +declare module "node:constants" { + import constants = require("constants"); + export = constants; +} diff --git a/packages/compiler/ambient/node-fallback/crypto.d.ts b/packages/compiler/ambient/node-fallback/crypto.d.ts new file mode 100644 index 000000000..05ed6c69e --- /dev/null +++ b/packages/compiler/ambient/node-fallback/crypto.d.ts @@ -0,0 +1,837 @@ +/// +/// + +/* node:crypto: the static hashing/MAC/random/key-derivation utility slice. */ +declare module "crypto" { + import { Transform } from "stream"; + export type KeyObjectType = "secret" | "public" | "private"; + export interface JsonWebKey { + crv?: string; + d?: string; + dp?: string; + dq?: string; + e?: string; + k?: string; + kty?: string; + n?: string; + p?: string; + q?: string; + qi?: string; + x?: string; + y?: string; + [key: string]: unknown; + } + export interface JwkKeyExportOptions { + format: "jwk"; + } + export interface KeyExportOptions { + format?: "buffer" | undefined; + } + export type KeyFormat = "pem" | "der" | "jwk"; + export interface AsymmetricKeyExportOptions { + type: "pkcs1" | "spki" | "pkcs8" | "sec1"; + format: T; + cipher?: string | undefined; + passphrase?: string | Buffer | undefined; + } + export interface AsymmetricKeyDetails { + modulusLength?: number; + publicExponent?: bigint; + namedCurve?: string; + } + /* Secret keys, and RSA and EC private and public keys. */ + export class KeyObject { + private constructor(); + type: KeyObjectType; + symmetricKeySize?: number; + asymmetricKeyType?: string; + asymmetricKeyDetails?: AsymmetricKeyDetails; + export(options: AsymmetricKeyExportOptions<"pem">): string | Buffer; + export(options: AsymmetricKeyExportOptions<"der">): Buffer; + export(options?: KeyExportOptions): Buffer; + export(options: JwkKeyExportOptions): JsonWebKey; + equals(otherKeyObject: KeyObject): boolean; + } + export interface PrivateKeyInput { + key: string | Buffer; + format?: "pem" | "der" | undefined; + type?: "pkcs1" | "pkcs8" | "sec1" | undefined; + encoding?: string | undefined; + passphrase?: string | Buffer | undefined; + } + export interface PublicKeyInput { + key: string | Buffer; + format?: "pem" | "der" | undefined; + type?: "pkcs1" | "spki" | undefined; + encoding?: string | undefined; + } + export interface JsonWebKeyInput { + key: JsonWebKey; + format: "jwk"; + } + export function createPrivateKey( + key: PrivateKeyInput | string | Buffer | JsonWebKeyInput, + ): KeyObject; + export function createPublicKey( + key: PublicKeyInput | string | Buffer | KeyObject | JsonWebKeyInput, + ): KeyObject; + export interface KeyPairKeyObjectResult { + publicKey: KeyObject; + privateKey: KeyObject; + } + export interface KeyPairSyncResult { + publicKey: T1; + privateKey: T2; + } + export interface RSAKeyPairKeyObjectOptions { + modulusLength: number; + publicExponent?: number | undefined; + } + export interface ECKeyPairKeyObjectOptions { + namedCurve: string; + } + export interface RSAKeyPairOptions { + modulusLength: number; + publicExponent?: number | undefined; + publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; + privateKeyEncoding: { + type: "pkcs1" | "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; + } + export interface ECKeyPairOptions { + namedCurve: string; + publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; + privateKeyEncoding: { + type: "sec1" | "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; + } + export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"pem", "pem">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairOptions<"der", "der">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "rsa", + options: RSAKeyPairKeyObjectOptions, + ): KeyPairKeyObjectResult; + export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"pem", "pem">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "ec", + options: ECKeyPairOptions<"der", "der">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "ec", + options: ECKeyPairKeyObjectOptions, + ): KeyPairKeyObjectResult; + export interface ED25519KeyPairOptions { + publicKeyEncoding: { type: "spki"; format: PubF }; + privateKeyEncoding: { + type: "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; + } + export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"pem", "pem">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "ed25519", + options: ED25519KeyPairOptions<"der", "der">, + ): KeyPairSyncResult; + export function generateKeyPairSync(type: "ed25519", options?: {}): KeyPairKeyObjectResult; + export interface X25519KeyPairOptions { + publicKeyEncoding: { type: "spki"; format: PubF }; + privateKeyEncoding: { + type: "pkcs8"; + format: PrivF; + cipher?: string | undefined; + passphrase?: string | undefined; + }; + } + export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"pem", "pem">, + ): KeyPairSyncResult; + export function generateKeyPairSync( + type: "x25519", + options: X25519KeyPairOptions<"der", "der">, + ): KeyPairSyncResult; + export function generateKeyPairSync(type: "x25519", options?: {}): KeyPairKeyObjectResult; + export function generateKeyPair( + type: "x25519", + options: {} | undefined, + callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, + ): void; + export function generateKeyPair( + type: "ed25519", + options: {} | undefined, + callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, + ): void; + export function generateKeyPair( + type: "rsa", + options: RSAKeyPairKeyObjectOptions, + callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, + ): void; + export function generateKeyPair( + type: "ec", + options: ECKeyPairKeyObjectOptions, + callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, + ): void; + export interface SigningOptions { + padding?: number | undefined; + saltLength?: number | undefined; + dsaEncoding?: "der" | "ieee-p1363" | undefined; + } + export interface SignPrivateKeyInput extends PrivateKeyInput, SigningOptions {} + export interface SignKeyObjectInput extends SigningOptions { + key: KeyObject; + } + export interface VerifyPublicKeyInput extends PublicKeyInput, SigningOptions {} + export interface VerifyKeyObjectInput extends SigningOptions { + key: KeyObject; + } + export function diffieHellman(options: { privateKey: KeyObject; publicKey: KeyObject }): Buffer; + export function diffieHellman( + options: { privateKey: KeyObject; publicKey: KeyObject }, + callback: (err: Error | null, secret: Buffer) => void, + ): void; + export function sign( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, + ): Buffer; + export function verify( + algorithm: string | null | undefined, + data: ArrayBufferView, + key: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: ArrayBufferView, + ): boolean; + /* Sign and Verify accumulate update() data; they are not usable as + * writable streams. */ + export class Sign { + private constructor(); + update(data: string | ArrayBufferView): this; + update(data: string, inputEncoding: Encoding): this; + sign( + privateKey: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, + ): Buffer; + sign( + privateKey: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, + outputFormat: BinaryToTextEncoding, + ): string; + } + export class Verify { + private constructor(); + update(data: string | ArrayBufferView): this; + update(data: string, inputEncoding: Encoding): this; + verify( + object: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: ArrayBufferView, + ): boolean; + verify( + object: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, + signature: string, + signatureEncoding?: BinaryToTextEncoding, + ): boolean; + } + export function createSign(algorithm: string): Sign; + export function createVerify(algorithm: string): Verify; + export function createSecretKey(key: ArrayBufferView | ArrayBufferLike): KeyObject; + export function createSecretKey(key: string, encoding: BufferEncoding): KeyObject; + export function generateKeySync(type: "hmac" | "aes", options: { length: number }): KeyObject; + export function generateKey( + type: "hmac" | "aes", + options: { length: number }, + callback: (err: Error | null, key: KeyObject) => void, + ): void; + export interface RandomUUIDOptions { + disableEntropyCache?: boolean | undefined; + } + export function randomUUID(options?: RandomUUIDOptions): string; + export function randomUUIDv7(options?: RandomUUIDOptions): string; + export function randomBytes(size: number): Buffer; + export type BinaryToTextEncoding = "base64" | "base64url" | "hex" | "binary"; + export type CharacterEncoding = "utf8" | "utf-8" | "utf16le" | "utf-16le" | "latin1"; + export type LegacyCharacterEncoding = "ascii" | "binary" | "ucs2" | "ucs-2"; + export type Encoding = BinaryToTextEncoding | CharacterEncoding | LegacyCharacterEncoding; + export interface HashOptions { + outputLength?: number | undefined; + } + /* Hash and Hmac are Transforms: written chunks update the digest, which + * the readable side delivers once the writable side ends. */ + export interface Hash extends Transform { + copy(options?: HashOptions): Hash; + update(data: string | ArrayBufferView): Hash; + update(data: string, inputEncoding: Encoding): Hash; + digest(): Buffer; + digest(encoding: BinaryToTextEncoding): string; + } + export interface Hmac extends Transform { + update(data: string | ArrayBufferView): Hmac; + update(data: string, inputEncoding: Encoding): Hmac; + digest(): Buffer; + digest(encoding: BinaryToTextEncoding): string; + } + export function createHash(algorithm: string, options?: HashOptions): Hash; + export function createHmac( + algorithm: string, + key: BinaryLike | KeyObject, + options?: { encoding?: BufferEncoding | undefined }, + ): Hmac; + export interface OneShotDigestOptions { + outputEncoding?: BinaryToTextEncoding | "buffer" | undefined; + outputLength?: number | undefined; + } + export interface OneShotDigestOptionsWithStringEncoding extends OneShotDigestOptions { + outputEncoding?: BinaryToTextEncoding | undefined; + } + export interface OneShotDigestOptionsWithBufferEncoding extends OneShotDigestOptions { + outputEncoding: "buffer"; + } + export function hash( + algorithm: string, + data: string | ArrayBufferView, + options?: OneShotDigestOptionsWithStringEncoding | BinaryToTextEncoding, + ): string; + export function hash( + algorithm: string, + data: string | ArrayBufferView, + options: OneShotDigestOptionsWithBufferEncoding | "buffer", + ): Buffer; + export function hash( + algorithm: string, + data: string | ArrayBufferView, + options: OneShotDigestOptions | BinaryToTextEncoding | "buffer", + ): string | Buffer; + export function timingSafeEqual( + a: ArrayBufferView | ArrayBufferLike, + b: ArrayBufferView | ArrayBufferLike, + ): boolean; + /* WebCrypto's getRandomValues (also the webcrypto object's): integer + * typed arrays, filled in place and returned. */ + type RandomValuesArray = + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | BigInt64Array + | BigUint64Array; + export function getRandomValues(typedArray: T): T; + /* The WebCrypto global's object and its SubtleCrypto. */ + export const webcrypto: Crypto; + export const subtle: SubtleCrypto; + export function randomFillSync( + buffer: T, + offset?: number, + size?: number, + ): T; + export function randomFill( + buffer: T, + callback: (err: Error | null, buf: T) => void, + ): void; + export function randomFill( + buffer: T, + offset: number, + callback: (err: Error | null, buf: T) => void, + ): void; + export function randomFill( + buffer: T, + offset: number, + size: number, + callback: (err: Error | null, buf: T) => void, + ): void; + export function randomInt(max: number): number; + export function randomInt(min: number, max: number): number; + export function randomInt( + max: number, + callback: (err: Error | null, value: number) => void, + ): void; + export function randomInt( + min: number, + max: number, + callback: (err: Error | null, value: number) => void, + ): void; + export function pbkdf2Sync( + password: string | Uint8Array, + salt: string | Uint8Array, + iterations: number, + keylen: number, + digest: string, + ): Buffer; + export function pbkdf2( + password: string | Uint8Array, + salt: string | Uint8Array, + iterations: number, + keylen: number, + digest: string, + callback: (error: Error | null, derivedKey: Buffer) => void, + ): void; + export function randomBytes( + size: number, + callback: (error: Error | null, buffer: Buffer) => void, + ): void; + export type BinaryLike = string | ArrayBuffer | ArrayBufferView; + /* Cipheriv/Decipheriv: aes-128/192/256 in cbc, ctr, gcm, ccm, ecb, cfb, + * cfb8, cfb1, and ofb modes and their key wraps, des-ede3 and des-ede, + * chacha20, and chacha20-poly1305. Both are Transforms: written chunks go + * through update(), and the end pushes final(). */ + export type CipherCCMTypes = "aes-128-ccm" | "aes-192-ccm" | "aes-256-ccm"; + export type CipherGCMTypes = "aes-128-gcm" | "aes-192-gcm" | "aes-256-gcm"; + export type CipherChaCha20Poly1305Types = "chacha20-poly1305"; + export type CipherKey = BinaryLike | KeyObject; + export interface CipherCCMOptions { + authTagLength: number; + } + export interface CipherGCMOptions { + authTagLength?: number; + } + export interface CipherChaCha20Poly1305Options { + authTagLength?: number; + } + export interface Cipheriv extends Transform { + update(data: string | ArrayBufferView): Buffer; + update(data: string, inputEncoding: BufferEncoding): Buffer; + update(data: ArrayBufferView, inputEncoding: undefined, outputEncoding: BufferEncoding): string; + update( + data: string, + inputEncoding: BufferEncoding | undefined, + outputEncoding: BufferEncoding, + ): string; + final(): Buffer; + final(outputEncoding: BufferEncoding): string; + setAutoPadding(autoPadding?: boolean): this; + } + export interface CipherCCM extends Cipheriv { + setAAD(buffer: ArrayBufferView, options: { plaintextLength: number }): this; + getAuthTag(): Buffer; + } + export interface CipherGCM extends Cipheriv { + setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; + getAuthTag(): Buffer; + } + export interface CipherChaCha20Poly1305 extends Cipheriv { + setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; + getAuthTag(): Buffer; + } + export interface Decipheriv extends Transform { + update(data: ArrayBufferView): Buffer; + update(data: string, inputEncoding: BufferEncoding): Buffer; + update(data: ArrayBufferView, inputEncoding: undefined, outputEncoding: BufferEncoding): string; + update( + data: string, + inputEncoding: BufferEncoding | undefined, + outputEncoding: BufferEncoding, + ): string; + final(): Buffer; + final(outputEncoding: BufferEncoding): string; + setAutoPadding(autoPadding?: boolean): this; + } + export interface DecipherCCM extends Decipheriv { + setAuthTag(buffer: ArrayBufferView): this; + setAAD(buffer: ArrayBufferView, options: { plaintextLength: number }): this; + } + export interface DecipherGCM extends Decipheriv { + setAuthTag(buffer: ArrayBufferView): this; + setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; + } + export interface DecipherChaCha20Poly1305 extends Decipheriv { + setAuthTag(buffer: ArrayBufferView): this; + setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; + } + export function createCipheriv( + algorithm: CipherCCMTypes, + key: CipherKey, + iv: BinaryLike, + options: CipherCCMOptions, + ): CipherCCM; + export function createCipheriv( + algorithm: CipherGCMTypes, + key: CipherKey, + iv: BinaryLike, + options?: CipherGCMOptions, + ): CipherGCM; + export function createCipheriv( + algorithm: CipherChaCha20Poly1305Types, + key: CipherKey, + iv: BinaryLike, + options?: CipherChaCha20Poly1305Options, + ): CipherChaCha20Poly1305; + export function createCipheriv( + algorithm: string, + key: CipherKey, + iv: BinaryLike | null, + options?: { authTagLength?: number }, + ): Cipheriv; + export function createDecipheriv( + algorithm: CipherCCMTypes, + key: CipherKey, + iv: BinaryLike, + options: CipherCCMOptions, + ): DecipherCCM; + export function createDecipheriv( + algorithm: CipherGCMTypes, + key: CipherKey, + iv: BinaryLike, + options?: CipherGCMOptions, + ): DecipherGCM; + export function createDecipheriv( + algorithm: CipherChaCha20Poly1305Types, + key: CipherKey, + iv: BinaryLike, + options?: CipherChaCha20Poly1305Options, + ): DecipherChaCha20Poly1305; + export function createDecipheriv( + algorithm: string, + key: CipherKey, + iv: BinaryLike | null, + options?: { authTagLength?: number }, + ): Decipheriv; + export type ECDHKeyFormat = "compressed" | "uncompressed" | "hybrid"; + export class ECDH { + private constructor(); + static convertKey( + key: BinaryLike, + curve: string, + inputEncoding?: BinaryToTextEncoding, + outputEncoding?: "latin1" | "hex" | "base64" | "base64url", + format?: ECDHKeyFormat, + ): Buffer | string; + generateKeys(): Buffer; + generateKeys(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; + computeSecret(otherPublicKey: ArrayBufferView): Buffer; + computeSecret(otherPublicKey: string, inputEncoding: BinaryToTextEncoding): Buffer; + computeSecret(otherPublicKey: ArrayBufferView, outputEncoding: BinaryToTextEncoding): string; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding: BinaryToTextEncoding, + ): string; + getPrivateKey(): Buffer; + getPrivateKey(encoding: BinaryToTextEncoding): string; + getPublicKey(encoding?: null, format?: ECDHKeyFormat): Buffer; + getPublicKey(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; + setPrivateKey(privateKey: ArrayBufferView): void; + setPrivateKey(privateKey: string, encoding: BinaryToTextEncoding): void; + } + export function createECDH(curveName: string): ECDH; + export class DiffieHellman { + private constructor(); + generateKeys(): Buffer; + generateKeys(encoding: BinaryToTextEncoding): string; + computeSecret( + otherPublicKey: ArrayBufferView, + inputEncoding?: null, + outputEncoding?: null, + ): Buffer; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding?: null, + ): Buffer; + computeSecret( + otherPublicKey: ArrayBufferView, + inputEncoding: null, + outputEncoding: BinaryToTextEncoding, + ): string; + computeSecret( + otherPublicKey: string, + inputEncoding: BinaryToTextEncoding, + outputEncoding: BinaryToTextEncoding, + ): string; + getPrime(): Buffer; + getPrime(encoding: BinaryToTextEncoding): string; + getGenerator(): Buffer; + getGenerator(encoding: BinaryToTextEncoding): string; + getPublicKey(): Buffer; + getPublicKey(encoding: BinaryToTextEncoding): string; + getPrivateKey(): Buffer; + getPrivateKey(encoding: BinaryToTextEncoding): string; + setPublicKey(publicKey: ArrayBufferView): void; + setPublicKey(publicKey: string, encoding: BufferEncoding): void; + setPrivateKey(privateKey: ArrayBufferView): void; + setPrivateKey(privateKey: string, encoding: BufferEncoding): void; + verifyError: number; + } + export type DiffieHellmanGroup = Omit; + export function createDiffieHellman(primeLength: number, generator?: number): DiffieHellman; + export function createDiffieHellman( + prime: ArrayBuffer | ArrayBufferView, + generator?: number | ArrayBuffer | ArrayBufferView, + ): DiffieHellman; + export function createDiffieHellman( + prime: ArrayBuffer | ArrayBufferView, + generator: string, + generatorEncoding: BinaryToTextEncoding, + ): DiffieHellman; + export function createDiffieHellman( + prime: string, + primeEncoding: BinaryToTextEncoding, + generator?: number | ArrayBuffer | ArrayBufferView, + ): DiffieHellman; + export function createDiffieHellman( + prime: string, + primeEncoding: BinaryToTextEncoding, + generator: string, + generatorEncoding: BinaryToTextEncoding, + ): DiffieHellman; + export function getDiffieHellman(groupName: string): DiffieHellmanGroup; + export function createDiffieHellmanGroup(name: string): DiffieHellmanGroup; + export type LargeNumberLike = ArrayBufferView | SharedArrayBuffer | ArrayBuffer | bigint; + export interface GeneratePrimeOptions { + add?: LargeNumberLike | undefined; + rem?: LargeNumberLike | undefined; + safe?: boolean | undefined; + bigint?: boolean | undefined; + } + export interface GeneratePrimeOptionsBigInt extends GeneratePrimeOptions { + bigint: true; + } + export interface GeneratePrimeOptionsArrayBuffer extends GeneratePrimeOptions { + bigint?: false | undefined; + } + export function generatePrime( + size: number, + callback: (err: Error | null, prime: ArrayBuffer) => void, + ): void; + export function generatePrime( + size: number, + options: GeneratePrimeOptionsBigInt, + callback: (err: Error | null, prime: bigint) => void, + ): void; + export function generatePrime( + size: number, + options: GeneratePrimeOptionsArrayBuffer, + callback: (err: Error | null, prime: ArrayBuffer) => void, + ): void; + export function generatePrime( + size: number, + options: GeneratePrimeOptions, + callback: (err: Error | null, prime: ArrayBuffer | bigint) => void, + ): void; + export function generatePrimeSync(size: number): ArrayBuffer; + export function generatePrimeSync(size: number, options: GeneratePrimeOptionsBigInt): bigint; + export function generatePrimeSync( + size: number, + options: GeneratePrimeOptionsArrayBuffer, + ): ArrayBuffer; + export function generatePrimeSync( + size: number, + options: GeneratePrimeOptions, + ): ArrayBuffer | bigint; + export interface CheckPrimeOptions { + checks?: number | undefined; + } + export function checkPrime( + value: LargeNumberLike, + callback: (err: Error | null, result: boolean) => void, + ): void; + export function checkPrime( + value: LargeNumberLike, + options: CheckPrimeOptions, + callback: (err: Error | null, result: boolean) => void, + ): void; + export function checkPrimeSync(candidate: LargeNumberLike, options?: CheckPrimeOptions): boolean; + export interface ScryptOptions { + N?: number; + r?: number; + p?: number; + cost?: number; + blockSize?: number; + parallelization?: number; + maxmem?: number; + } + export function scryptSync( + password: BinaryLike, + salt: BinaryLike, + keylen: number, + options?: ScryptOptions, + ): Buffer; + export function scrypt( + password: BinaryLike, + salt: BinaryLike, + keylen: number, + callback: (error: Error | null, derivedKey: Buffer) => void, + ): void; + export function scrypt( + password: BinaryLike, + salt: BinaryLike, + keylen: number, + options: ScryptOptions, + callback: (error: Error | null, derivedKey: Buffer) => void, + ): void; + export function hkdfSync( + digest: string, + ikm: BinaryLike | KeyObject, + salt: BinaryLike, + info: BinaryLike, + keylen: number, + ): ArrayBuffer; + export function hkdf( + digest: string, + ikm: BinaryLike | KeyObject, + salt: BinaryLike, + info: BinaryLike, + keylen: number, + callback: (error: Error | null, derivedKey: ArrayBuffer) => void, + ): void; + /* The lowered X509Certificate surface is the data-record slice: the + * fields below, all computed at construction (raw decodes the input, + * and the others read raw). validFrom/validTo are Node's + * ASN1_TIME_print strings ("Jul 1 00:00:00 2026 GMT"). Unparseable + * input throws Node's ERR_OSSL_PEM_NO_START_LINE Error. */ + export class X509Certificate { + constructor(buffer: Uint8Array | string); + readonly ca: boolean; + readonly fingerprint: string; + readonly fingerprint256: string; + readonly fingerprint512: string; + readonly subject: string; + readonly subjectAltName: string | undefined; + readonly infoAccess: string | undefined; + readonly issuer: string; + readonly raw: Buffer; + readonly serialNumber: string; + readonly signatureAlgorithm: string | undefined; + readonly signatureAlgorithmOid: string; + readonly validFrom: string; + readonly validFromDate: Date; + readonly validTo: string; + readonly validToDate: Date; + /* the certificate's public key; every read answers the same KeyObject */ + readonly publicKey: KeyObject; + verify(publicKey: KeyObject): boolean; + checkPrivateKey(privateKey: KeyObject): boolean; + checkIssued(otherCert: X509Certificate): boolean; + checkHost(name: string, options?: X509CheckOptions): string | undefined; + checkEmail(email: string, options?: X509CheckOptions): string | undefined; + checkIP(ip: string): string | undefined; + toLegacyObject(): import("tls").PeerCertificate; + } + export interface X509CheckOptions { + subject?: "always" | "default" | "never"; + wildcards?: boolean; + partialWildcards?: boolean; + multiLabelWildcards?: boolean; + singleLabelSubdomains?: boolean; + } + /* The crypto introspection statics: getFips() answers 0 (a compiled + * binary is never a FIPS build), the three name lists bake as fresh + * string[] literals of Node v24 answers, and constants (below) bakes + * per member like http2.constants. */ + export function getFips(): 1 | 0; + export function getCiphers(): string[]; + export type CipherMode = + | "cbc" + | "ccm" + | "cfb" + | "ctr" + | "ecb" + | "gcm" + | "ocb" + | "ofb" + | "stream" + | "wrap" + | "xts"; + export interface CipherInfoOptions { + keyLength?: number | undefined; + ivLength?: number | undefined; + } + export interface CipherInfo { + name: string; + nid: number; + blockSize?: number | undefined; + ivLength?: number | undefined; + keyLength: number; + mode: CipherMode; + } + export function getCipherInfo( + nameOrNid: string | number, + options?: CipherInfoOptions, + ): CipherInfo | undefined; + export function getHashes(): string[]; + export function getCurves(): string[]; + /* Node v24 crypto.constants, literal-typed (the Http2Constants + * stance). Every ACCESS bakes as its literal at lowering; the object + * itself never materializes (bare crypto.constants value uses fence). */ + export interface CryptoConstants { + readonly OPENSSL_VERSION_NUMBER: 810549376; + readonly SSL_OP_ALL: 2147485776; + readonly SSL_OP_ALLOW_NO_DHE_KEX: 1024; + readonly SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: 262144; + readonly SSL_OP_CIPHER_SERVER_PREFERENCE: 4194304; + readonly SSL_OP_CISCO_ANYCONNECT: 32768; + readonly SSL_OP_COOKIE_EXCHANGE: 8192; + readonly SSL_OP_CRYPTOPRO_TLSEXT_BUG: 2147483648; + readonly SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: 2048; + readonly SSL_OP_LEGACY_SERVER_CONNECT: 4; + readonly SSL_OP_NO_COMPRESSION: 131072; + readonly SSL_OP_NO_ENCRYPT_THEN_MAC: 524288; + readonly SSL_OP_NO_QUERY_MTU: 4096; + readonly SSL_OP_NO_RENEGOTIATION: 1073741824; + readonly SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: 65536; + readonly SSL_OP_NO_SSLv2: 0; + readonly SSL_OP_NO_SSLv3: 33554432; + readonly SSL_OP_NO_TICKET: 16384; + readonly SSL_OP_NO_TLSv1: 67108864; + readonly SSL_OP_NO_TLSv1_1: 268435456; + readonly SSL_OP_NO_TLSv1_2: 134217728; + readonly SSL_OP_NO_TLSv1_3: 536870912; + readonly SSL_OP_PRIORITIZE_CHACHA: 2097152; + readonly SSL_OP_TLS_ROLLBACK_BUG: 8388608; + readonly ENGINE_METHOD_RSA: 1; + readonly ENGINE_METHOD_DSA: 2; + readonly ENGINE_METHOD_DH: 4; + readonly ENGINE_METHOD_RAND: 8; + readonly ENGINE_METHOD_EC: 2048; + readonly ENGINE_METHOD_CIPHERS: 64; + readonly ENGINE_METHOD_DIGESTS: 128; + readonly ENGINE_METHOD_PKEY_METHS: 512; + readonly ENGINE_METHOD_PKEY_ASN1_METHS: 1024; + readonly ENGINE_METHOD_ALL: 65535; + readonly ENGINE_METHOD_NONE: 0; + readonly DH_CHECK_P_NOT_SAFE_PRIME: 2; + readonly DH_CHECK_P_NOT_PRIME: 1; + readonly DH_UNABLE_TO_CHECK_GENERATOR: 4; + readonly DH_NOT_SUITABLE_GENERATOR: 8; + readonly RSA_PKCS1_PADDING: 1; + readonly RSA_SSLV23_PADDING: 2; + readonly RSA_NO_PADDING: 3; + readonly RSA_PKCS1_OAEP_PADDING: 4; + readonly RSA_X931_PADDING: 5; + readonly RSA_PKCS1_PSS_PADDING: 6; + readonly RSA_PSS_SALTLEN_DIGEST: -1; + readonly RSA_PSS_SALTLEN_MAX_SIGN: -2; + readonly RSA_PSS_SALTLEN_AUTO: -2; + readonly defaultCoreCipherList: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA"; + readonly TLS1_VERSION: 769; + readonly TLS1_1_VERSION: 770; + readonly TLS1_2_VERSION: 771; + readonly TLS1_3_VERSION: 772; + readonly POINT_CONVERSION_COMPRESSED: 2; + readonly POINT_CONVERSION_UNCOMPRESSED: 4; + readonly POINT_CONVERSION_HYBRID: 6; + readonly defaultCipherList: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA"; + } + export const constants: CryptoConstants; +} + +declare module "node:crypto" { + export * from "crypto"; +} diff --git a/packages/compiler/ambient/node-fallback/dgram.d.ts b/packages/compiler/ambient/node-fallback/dgram.d.ts new file mode 100644 index 000000000..27a98b26d --- /dev/null +++ b/packages/compiler/ambient/node-fallback/dgram.d.ts @@ -0,0 +1,58 @@ +/* node:dgram — UDP sockets over the event loop (scr_dgram.c, linked only + * into using binaries). createSocket takes "udp4" or the + * { type: "udp4", reuseAddr? } literal; bind/connect bind NOW and defer + * 'listening'/'connect' to the next loop turn; send takes one string or + * Buffer datagram with an explicit port + address (the connected-send + * and callback forms have no lowering); address() returns the real + * {address, family, port} record and THROWS "Not running" before bind, + * like Node. The socket options throw Node's errors (EBADF before bind); + * add/dropMembership before bind is refused at run time (SC2020). 'error' + * with no listener exits 1 (the unhandled EventEmitter 'error' behavior). */ +declare module "dgram" { + export interface AddressInfo { + address: string; + family: string; + port: number; + } + export interface RemoteInfo { + address: string; + family: string; + port: number; + size: number; + } + export interface Socket { + bind(port: number, address?: string, callback?: () => void): void; + connect(port: number, address?: string, callback?: () => void): void; + send(msg: string | Uint8Array, port: number, address: string): void; + address(): AddressInfo; + close(callback?: () => void): void; + disconnect(): void; + unref(): void; + ref(): void; + setBroadcast(flag: boolean): void; + setTTL(ttl: number): number; + setMulticastTTL(ttl: number): number; + setMulticastLoopback(flag: boolean): boolean; + setMulticastInterface(multicastInterface: string): void; + addMembership(multicastAddress: string, multicastInterface?: string): void; + dropMembership(multicastAddress: string, multicastInterface?: string): void; + getRecvBufferSize(): number; + getSendBufferSize(): number; + setRecvBufferSize(size: number): void; + setSendBufferSize(size: number): void; + getSendQueueSize(): number; + getSendQueueCount(): number; + on(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): void; + on(event: "listening" | "close" | "connect", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + once(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): void; + once(event: "listening" | "close" | "connect", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + } + export function createSocket(type: "udp4"): Socket; + export function createSocket(options: { type: "udp4"; reuseAddr?: boolean }): Socket; +} + +declare module "node:dgram" { + export * from "dgram"; +} diff --git a/packages/compiler/ambient/node-fallback/diagnostics_channel.d.ts b/packages/compiler/ambient/node-fallback/diagnostics_channel.d.ts new file mode 100644 index 000000000..53e195e89 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/diagnostics_channel.d.ts @@ -0,0 +1,87 @@ +/// + +/* node:diagnostics_channel — the in-process pub/sub core: named channels, + * subscribe/unsubscribe (module-level and channel-level), publish with any + * dyn-convertible message. Channel values are f64 handles into the + * runtime's channel registry (the readline.Interface precedent — channels + * are process-lived in Node too, its WeakRef machinery aside). + * The store-binding surface (bindStore/unbindStore/runStores) lowers onto + * the AsyncLocalStorage integration (dc.chan* libCalls). + * + * TracingChannel (tracingChannel) is the five-event-channel collection: + * an f64 handle into the runtime's tracing registry (type-mapper.ts maps the + * name like Channel). subscribe/unsubscribe walk a handlers object; + * traceSync/traceCallback run Node's publish choreography in the runtime + * (context defaults to a fresh {}); tracePromise is declared (the member + * exists in Node) but fences at the call site — the traced function's + * promise cannot cross the checked-dynamic tree boundary yet. */ +declare module "node:diagnostics_channel" { + export type ChannelListener = (message: unknown, name: string) => void; + export interface Channel { + readonly name: string; + readonly hasSubscribers: boolean; + publish(message: unknown): void; + subscribe(onMessage: ChannelListener): void; + unsubscribe(onMessage: ChannelListener): boolean; + bindStore( + store: import("async_hooks").AsyncLocalStorage, + transform?: (context: any) => any, + ): void; + unbindStore(store: import("async_hooks").AsyncLocalStorage): boolean; + runStores(context: unknown, fn: (...args: any[]) => any, thisArg?: any, ...args: any[]): any; + } + export function channel(name: string): Channel; + export function subscribe(name: string, onMessage: ChannelListener): void; + export function unsubscribe(name: string, onMessage: ChannelListener): boolean; + export function hasSubscribers(name: string): boolean; + export interface TracingChannelSubscribers { + start?: (message: any) => void; + end?: (message: any) => void; + asyncStart?: (message: any) => void; + asyncEnd?: (message: any) => void; + error?: (message: any) => void; + } + export interface TracingChannelCollection { + start: Channel; + end: Channel; + asyncStart: Channel; + asyncEnd: Channel; + error: Channel; + } + export interface TracingChannel { + start: Channel; + end: Channel; + asyncStart: Channel; + asyncEnd: Channel; + error: Channel; + readonly hasSubscribers: boolean; + subscribe(subscribers: TracingChannelSubscribers): void; + /** True when every present handler was found and removed (Node's + * all-found conjunction; @types/node under-declares this as void). */ + unsubscribe(subscribers: TracingChannelSubscribers): boolean; + traceSync( + fn: (this: ThisArg, ...args: Args) => Result, + context?: object, + thisArg?: ThisArg, + ...args: Args + ): Result; + tracePromise( + fn: (this: ThisArg, ...args: Args) => Promise, + context?: object, + thisArg?: ThisArg, + ...args: Args + ): Promise; + traceCallback( + fn: (this: ThisArg, ...args: Args) => Result, + position?: number, + context?: object, + thisArg?: ThisArg, + ...args: Args + ): Result; + } + export function tracingChannel(nameOrChannels: string | TracingChannelCollection): TracingChannel; +} + +declare module "diagnostics_channel" { + export * from "node:diagnostics_channel"; +} diff --git a/packages/compiler/ambient/node-fallback/dns-promises.d.ts b/packages/compiler/ambient/node-fallback/dns-promises.d.ts new file mode 100644 index 000000000..29fadd6d4 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/dns-promises.d.ts @@ -0,0 +1,27 @@ +/// + +/* node:dns/promises — lookup and lookupService as promises over the same + * resolution as the callback forms: argument errors throw synchronously, + * failures reject with the callbacks' errors. */ +declare module "dns/promises" { + import type { LookupAddress, LookupAllOptions, LookupOneOptions, LookupOptions } from "dns"; + export function lookup(hostname: string, family: number): Promise; + export function lookup(hostname: string, options: LookupOneOptions): Promise; + export function lookup(hostname: string, options: LookupAllOptions): Promise; + export function lookup( + hostname: string, + options: LookupOptions, + ): Promise; + export function lookup(hostname: string): Promise; + export function lookupService( + address: string, + port: number, + ): Promise<{ hostname: string; service: string }>; + export function getServers(): string[]; + export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; + export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; +} + +declare module "node:dns/promises" { + export * from "dns/promises"; +} diff --git a/packages/compiler/ambient/node-fallback/dns.d.ts b/packages/compiler/ambient/node-fallback/dns.d.ts new file mode 100644 index 000000000..234f03463 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/dns.d.ts @@ -0,0 +1,65 @@ +/// + +/* node:dns — the lookup slice (scr_dgram.c hosts it; getaddrinfo runs AT + * CALL TIME and the callback defers to the next loop turn — SEMANTICS.md + * documents the blocking divergence). Only the { family: 4 } options + * form lowers; failures deliver Node's error shape ("getaddrinfo + * ENOTFOUND ") as the callback's Error argument, with "" for + * the address where Node passes undefined. */ +declare module "dns" { + export interface LookupAddress { + address: string; + family: number; + } + export interface LookupOptions { + family?: number | "IPv4" | "IPv6"; + hints?: number; + all?: boolean; + order?: "ipv4first" | "ipv6first" | "verbatim"; + verbatim?: boolean; + } + export interface LookupOneOptions extends LookupOptions { + all?: false; + } + export interface LookupAllOptions extends LookupOptions { + all: true; + } + /* The callback comes with the other lookup deliveries (literal + * addresses on the next tick); the options follow lib/dns.js. */ + export function lookup( + hostname: string, + callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, + ): void; + export function lookup( + hostname: string, + family: number, + callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, + ): void; + export function lookup( + hostname: string, + options: LookupOneOptions, + callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, + ): void; + export function lookup( + hostname: string, + options: LookupAllOptions, + callback: (err: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void, + ): void; + /* getnameinfo with NI_NAMEREQD: the host's name and the port's service + * name (the number when it has none). */ + export function lookupService( + address: string, + port: number, + callback: (err: NodeJS.ErrnoException | null, hostname: string, service: string) => void, + ): void; + /* The nameservers /etc/resolv.conf lists. */ + export function getServers(): string[]; + export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; + export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; + /* The promise forms (node:dns/promises). */ + export const promises: typeof import("dns/promises"); +} + +declare module "node:dns" { + export * from "dns"; +} diff --git a/packages/compiler/ambient/node-fallback/events.d.ts b/packages/compiler/ambient/node-fallback/events.d.ts new file mode 100644 index 000000000..dda1cd086 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/events.d.ts @@ -0,0 +1,60 @@ +/* node:events — the EventEmitter class (the module object IS the class, + * like Node's `module.exports = EventEmitter`). Listener/emit signatures + * mirror @types/node's untyped `...args: any[]` surface; the compiler's + * lowering unifies each event name's argument tuple program-wide and + * checks annotated listener parameters against it (unannotated non-empty + * parameter lists have no static types and fence at the registration). */ +declare module "events" { + class EventEmitter { + constructor(); + static defaultMaxListeners: number; + /* Listeners registered under this symbol observe 'error' emits first + * (an emit with no 'error' listener still throws). */ + static readonly errorMonitor: unique symbol; + static addAbortListener(signal: AbortSignal, listener: (event: Event) => void): Disposable; + on(eventName: string | symbol, listener: (...args: any[]) => void): this; + addListener(eventName: string | symbol, listener: (...args: any[]) => void): this; + once(eventName: string | symbol, listener: (...args: any[]) => void): this; + prependListener(eventName: string | symbol, listener: (...args: any[]) => void): this; + prependOnceListener(eventName: string | symbol, listener: (...args: any[]) => void): this; + off(eventName: string | symbol, listener: (...args: any[]) => void): this; + removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this; + removeAllListeners(eventName?: string | symbol): this; + emit(eventName: string | symbol, ...args: any[]): boolean; + listenerCount(eventName: string | symbol, listener?: (...args: any[]) => void): number; + /* Declared as zero-parameter closures so the RESULT is statically + * consumable (.length, indexing, identity): the lowering only admits + * these calls for events whose argument tuple is empty — a listener + * pulled from the array is then safely callable too. Events with + * arguments fence (their element type would lie). */ + listeners(eventName: string): Array<() => void>; + rawListeners(eventName: string): Array<() => void>; + eventNames(): string[]; + setMaxListeners(n: number): this; + getMaxListeners(): number; + } + import internal = require("node:events"); + namespace EventEmitter { + // The class under its own name, so `import { EventEmitter }` resolves + // (the @types/node namespace-alias pattern). + export { internal as EventEmitter }; + // The event's arguments, as checked-dynamic values. + export function once( + emitter: EventEmitter | NodeJS.ReadableStream | NodeJS.WritableStream, + eventName: string, + options?: { signal?: AbortSignal }, + ): Promise; + /* The emitter's own listenerCount/getMaxListeners/setMaxListeners/ + * listeners, over EventEmitter instances. */ + export function listenerCount(emitter: EventEmitter, eventName: string | symbol): number; + export function getMaxListeners(emitter: EventEmitter): number; + export function setMaxListeners(n?: number, ...eventTargets: EventEmitter[]): void; + export function getEventListeners(emitter: EventEmitter, name: string): Array<() => void>; + } + export = EventEmitter; +} + +declare module "node:events" { + import events = require("events"); + export = events; +} diff --git a/packages/compiler/ambient/node-fallback/fs-promises.d.ts b/packages/compiler/ambient/node-fallback/fs-promises.d.ts new file mode 100644 index 000000000..6f3f2c460 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/fs-promises.d.ts @@ -0,0 +1,225 @@ +/// + +/* fs/promises — the SAME synchronous operations behind already-settled + * promises: success fulfills, failure REJECTS with the would-be thrown + * error (catchable at the await, like Node). DOCUMENTED DIVERGENCE + * (SEMANTICS.md): the syscall blocks the event loop, so I/O never + * interleaves with timers or other fibers — observable only in concurrent + * code. readFile is utf8-only, like readFileSync. */ +declare module "fs/promises" { + export interface FileReadResult { + bytesRead: number; + buffer: T; + } + export interface FileWriteResult { + bytesWritten: number; + buffer: T; + } + export interface FileReadvResult { + bytesRead: number; + buffers: T; + } + export interface FileWritevResult { + bytesWritten: number; + buffers: T; + } + export interface FileReadOptions { + buffer?: T; + offset?: number | null; + length?: number | null; + position?: number | null; + } + /* The streams over a FileHandle (createReadStream, createWriteStream, + * readLines): fs streams that hold the handle until they close; closing + * the stream closes the handle unless autoClose is false. */ + export interface CreateReadStreamOptions { + encoding?: BufferEncoding | null; + autoClose?: boolean; + emitClose?: boolean; + start?: number; + end?: number; + highWaterMark?: number; + } + export interface CreateWriteStreamOptions { + encoding?: BufferEncoding | null; + autoClose?: boolean; + emitClose?: boolean; + start?: number; + highWaterMark?: number; + flush?: boolean; + } + export interface FileHandle extends AsyncDisposable { + readonly fd: number; + close(): Promise; + createReadStream(options?: CreateReadStreamOptions): import("node:fs").ReadStream; + createWriteStream(options?: CreateWriteStreamOptions): import("node:fs").WriteStream; + readLines(options?: CreateReadStreamOptions): import("node:readline").Interface; + sync(): Promise; + datasync(): Promise; + truncate(len?: number): Promise; + chmod(mode: number): Promise; + utimes(atime: number | string | Date, mtime: number | string | Date): Promise; + readv( + buffers: T, + position?: number | null, + ): Promise>; + writev( + buffers: T, + position?: number | null, + ): Promise>; + read( + options?: FileReadOptions | null, + ): Promise>; + read( + buffer: T, + options: Omit, "buffer">, + ): Promise>; + read( + buffer: T, + offset?: number | null, + length?: number | null, + position?: number | null, + ): Promise>; + write( + buffer: T, + offset?: number | null, + length?: number | null, + position?: number | null, + ): Promise>; + write( + buffer: T, + options: Omit, "buffer">, + ): Promise>; + write( + data: string, + position?: number | null, + encoding?: "utf8" | "utf-8" | null, + ): Promise>; + readFile(options?: null): Promise; + readFile(encoding: "utf8" | "utf-8"): Promise; + writeFile(data: string | Uint8Array, encoding?: "utf8" | "utf-8" | null): Promise; + appendFile(data: string | Uint8Array, encoding?: "utf8" | "utf-8" | null): Promise; + stat(): Promise; + [Symbol.asyncDispose](): Promise; + } + export function open(path: string | URL, flags?: string, mode?: number): Promise; + export function link( + existingPath: string | Uint8Array | URL, + newPath: string | Uint8Array | URL, + ): Promise; + export function symlink( + target: string | Uint8Array | URL, + path: string | Uint8Array | URL, + type?: string | null, + ): Promise; + export function cp( + source: string | URL, + destination: string | URL, + options?: import("node:fs").CopyOptions, + ): Promise; + export function readlink( + path: string | Uint8Array | URL, + options: "buffer" | { encoding: "buffer" }, + ): Promise; + export function readlink( + path: string | Uint8Array | URL, + options?: BufferEncoding | { encoding?: BufferEncoding | null } | null, + ): Promise; + export function readlink( + path: string | Uint8Array | URL, + options: string | { encoding?: string | null } | null, + ): Promise; + export function utimes( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + ): Promise; + export function lutimes( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + ): Promise; + export function readFile(path: string | URL, encoding: "utf8" | "utf-8"): Promise; + export function readFile( + path: string | URL, + options: BufferEncoding | { encoding: BufferEncoding }, + ): Promise; + export function readFile(path: string | URL): Promise; + export function writeFile( + path: string | URL, + data: string, + options?: { + mode?: number; + encoding?: "utf8" | "utf-8"; + flag?: string; + [option: string]: unknown; + }, + ): Promise; + export function writeFile( + path: string | URL, + data: string, + encoding: "utf8" | "utf-8", + ): Promise; + export function mkdir( + path: string | URL, + options?: { recursive?: boolean; mode?: number }, + ): Promise; + export function readdir(path: string | URL): Promise; + export function readdir( + path: string | URL, + options: { + encoding?: "utf8" | "utf-8"; + withFileTypes: true; + recursive?: boolean; + [option: string]: unknown; + }, + ): Promise; + /* The async iterator form of fs.globSync: iterate it with for await. */ + export function glob( + pattern: string | readonly string[], + options?: import("node:fs").GlobOptions, + ): AsyncIterableIterator; + export function glob( + pattern: string | readonly string[], + options: import("node:fs").GlobOptionsWithFileTypes, + ): AsyncIterableIterator; + export function rm(path: string | URL): Promise; + export function rm( + path: string | URL, + options: { recursive?: boolean; force?: boolean; maxRetries?: number; retryDelay?: number }, + ): Promise; + export function stat(path: string | URL): Promise; + export function statfs( + path: string | Uint8Array | URL, + options?: import("node:fs").StatFsOptions & { bigint?: false }, + ): Promise; + export function statfs( + path: string | Uint8Array | URL, + options: import("node:fs").StatFsOptions & { bigint: true }, + ): Promise; + export function statfs( + path: string | Uint8Array | URL, + options?: import("node:fs").StatFsOptions, + ): Promise; + export function realpath(path: string | URL): Promise; + export function lstat(path: string | URL): Promise; + export function unlink(path: string | URL): Promise; + export function chmod(path: string | URL, mode: number): Promise; + export function chown(path: string | URL, uid: number, gid: number): Promise; + export function lchown(path: string | URL, uid: number, gid: number): Promise; + export function access(path: string | URL, mode?: number): Promise; + export function truncate(path: string | URL, len?: number): Promise; + export function appendFile(path: string | URL, data: string | Uint8Array): Promise; + export function copyFile(src: string | URL, dest: string | URL): Promise; + export function mkdtemp(prefix: string | URL): Promise; + export function rmdir(path: string | URL): Promise; + export function rename(oldPath: string | URL, newPath: string | URL): Promise; + export function opendir( + path: string | URL, + options?: import("node:fs").OpenDirOptions, + ): Promise; +} + +declare module "node:fs/promises" { + export * from "fs/promises"; +} diff --git a/packages/compiler/ambient/node-fallback/fs.d.ts b/packages/compiler/ambient/node-fallback/fs.d.ts new file mode 100644 index 000000000..8d22ec66c --- /dev/null +++ b/packages/compiler/ambient/node-fallback/fs.d.ts @@ -0,0 +1,577 @@ +/// +/// + +/* The synchronous node:fs surface — utf8-only, no options objects, no + * Buffers. Importing "node:fs" resolves here (preflight allowlists exactly + * the ambient-declared node: modules); every function lowers to a `libCall` + * with a scr_fs_* runtime implementation. Failures throw CATCHABLE string + * values formatted like Node's error messages ("ENOENT: no such file or + * directory, open 'x'"). `rmSync` removes FILES only (like Node without + * `recursive`); empty directories go through `rmdirSync`. */ +declare module "node:fs" { + import { Readable, Writable } from "stream"; + export function readFileSync(path: string | number | URL, encoding: BufferEncoding): string; + export function readFileSync( + path: string | number | URL, + options: { encoding: BufferEncoding }, + ): string; + export function readFileSync(path: string | number | URL): Buffer; + export function readFileSync(path: string | URL, options: unknown): string | Buffer; + /* Error-first callbacks select string or Buffer data from their encoding. + * Unknown options retain that union until the caller validates the value. */ + export function readFile( + path: string, + options: unknown, + callback: (error: Error | null, data: string | Buffer) => void, + ): void; + /* The options form carries the mode (applied at CREATION only, like + * Node — an existing file keeps its permissions) and/or the utf8 + * encoding spelling. */ + export function writeFileSync( + path: string | URL, + data: string, + /* The options-record stance (see http.RequestOptions): flag fences + * by name at the call site; undocumented keys drop like Node. */ + options?: { + mode?: number; + encoding?: "utf8" | "utf-8"; + flag?: string; + [option: string]: unknown; + }, + ): void; + /* The bare-encoding spelling — the options record's encoding key alone. */ + export function writeFileSync(path: string | URL, data: string, encoding: "utf8" | "utf-8"): void; + export function writeFileSync(path: string | URL, data: string | Uint8Array): void; + export function appendFileSync(path: string | URL, data: string | Uint8Array): void; + export function existsSync(path: string | URL): boolean; + export function mkdirSync(path: string | URL): void; + export function mkdirSync( + path: string | URL, + options: { recursive?: boolean; mode?: number }, + ): void; + export function unlinkSync(path: string | URL): void; + export function chmodSync(path: string | URL, mode: number): void; + export function fchmodSync(fd: number, mode: number): void; + export function fsyncSync(fd: number): void; + export function linkSync( + existingPath: string | Uint8Array | URL, + newPath: string | Uint8Array | URL, + ): void; + export function symlinkSync( + target: string | Uint8Array | URL, + path: string | Uint8Array | URL, + type?: string | null, + ): void; + /* fs.cpSync / fs.cp: copies a file, symlink or (with recursive) a + * directory tree. filter decides per path; the callback and promise + * forms complete the copy before reporting. */ + export interface CopySyncOptions { + dereference?: boolean; + errorOnExist?: boolean; + filter?: (source: string, destination: string) => boolean; + force?: boolean; + mode?: number; + preserveTimestamps?: boolean; + recursive?: boolean; + verbatimSymlinks?: boolean; + } + export interface CopyOptions { + dereference?: boolean; + errorOnExist?: boolean; + filter?: (source: string, destination: string) => boolean | Promise; + force?: boolean; + mode?: number; + preserveTimestamps?: boolean; + recursive?: boolean; + verbatimSymlinks?: boolean; + } + export function cpSync( + source: string | URL, + destination: string | URL, + options?: CopySyncOptions, + ): void; + export function cp( + source: string | URL, + destination: string | URL, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function cp( + source: string | URL, + destination: string | URL, + options: CopyOptions, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function symlink( + target: string | Uint8Array | URL, + path: string | Uint8Array | URL, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function symlink( + target: string | Uint8Array | URL, + path: string | Uint8Array | URL, + type: string | null | undefined, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function link( + existingPath: string | Uint8Array | URL, + newPath: string | Uint8Array | URL, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function readlink( + path: string | Uint8Array | URL, + callback: (err: NodeJS.ErrnoException | null, target: string) => void, + ): void; + export function readlink( + path: string | Uint8Array | URL, + options: "buffer" | { encoding: "buffer" }, + callback: (err: NodeJS.ErrnoException | null, target: Buffer) => void, + ): void; + export function readlink( + path: string | Uint8Array | URL, + options: BufferEncoding | { encoding?: BufferEncoding | null } | null, + callback: (err: NodeJS.ErrnoException | null, target: string) => void, + ): void; + export function readlink( + path: string | Uint8Array | URL, + options: string | { encoding?: string | null } | null, + callback: (err: NodeJS.ErrnoException | null, target: string | Buffer) => void, + ): void; + export function readlinkSync( + path: string | Uint8Array | URL, + options: "buffer" | { encoding: "buffer" }, + ): Buffer; + export function readlinkSync( + path: string | Uint8Array | URL, + options?: BufferEncoding | { encoding?: BufferEncoding | null } | null, + ): string; + export function readlinkSync( + path: string | Uint8Array | URL, + options: string | { encoding?: string | null } | null, + ): string | Buffer; + export function chownSync(path: string | URL, uid: number, gid: number): void; + export function lchownSync(path: string | URL, uid: number, gid: number): void; + export function fchownSync(fd: number, uid: number, gid: number): void; + export function truncateSync(path: string | URL, len?: number): void; + /* The 2-argument form only (Node's mode flags have no lowering). The + * destination is created or truncated carrying the SOURCE's mode. */ + export function copyFileSync(src: string | URL, dest: string | URL): void; + export function rename( + oldPath: string, + newPath: string, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function renameSync(oldPath: string | URL, newPath: string | URL): void; + export function rmSync(path: string | URL): void; + /* maxRetries/retryDelay are accepted no-ops (Node retries around + * EBUSY-class races; the synchronous lowering has no re-entrancy that + * needs them). */ + export function rmSync( + path: string | URL, + options: { recursive?: boolean; force?: boolean; maxRetries?: number; retryDelay?: number }, + ): void; + export function rmdirSync(path: string | URL): void; + export function readdirSync(path: string | URL): string[]; + /* The withFileTypes form: Dirent rows (name + parentPath + the type + * probes — the honest subset of Node's Dirent surface). The type is + * interned explicitly in type-mapper.ts (the record's hidden %dtype field + * carries the entry kind; isFile/isDirectory/isSymbolicLink lower as + * reads of it in builtins/process.ts). */ + export interface Dirent { + name: string; + parentPath: string; + isFile(): boolean; + isDirectory(): boolean; + isSymbolicLink(): boolean; + } + export function readdirSync( + path: string | URL, + /* The options-record stance (see http.RequestOptions): recursive + * fences by name at the call site; undocumented keys drop like Node. */ + options: { withFileTypes: true; recursive?: boolean; [option: string]: unknown }, + ): Dirent[]; + /* fs.globSync / fs.glob: Node's glob walk over the file system. Patterns + * are minimatch globs (case-insensitive on macOS and Windows); results + * are relative to cwd unless the pattern is absolute. withFileTypes must + * be a literal so the result type is known. */ + export interface GlobOptions { + cwd?: string | URL; + exclude?: readonly string[] | ((fileName: string) => boolean); + followSymlinks?: boolean; + withFileTypes?: false; + } + export interface GlobOptionsWithFileTypes { + cwd?: string | URL; + exclude?: readonly string[]; + followSymlinks?: boolean; + withFileTypes: true; + } + export function globSync(pattern: string | readonly string[], options?: GlobOptions): string[]; + export function globSync( + pattern: string | readonly string[], + options: GlobOptionsWithFileTypes, + ): Dirent[]; + export function glob( + pattern: string | readonly string[], + callback: (err: NodeJS.ErrnoException | null, matches?: string[]) => void, + ): void; + export function glob( + pattern: string | readonly string[], + options: GlobOptions, + callback: (err: NodeJS.ErrnoException | null, matches?: string[]) => void, + ): void; + /* fs.Dir: an open directory stream (opendirSync, fs.promises.opendir) + * read one Dirent at a time in OS order; utf8 names, no recursive walk. */ + export interface Dir { + readonly path: string; + readSync(): Dirent | null; + read(): Promise; + closeSync(): void; + close(): Promise; + [Symbol.asyncIterator](): AsyncIterableIterator; + } + export interface OpenDirOptions { + encoding?: "utf8" | "utf-8"; + bufferSize?: number; + } + export function opendirSync(path: string | URL, options?: OpenDirOptions): Dir; + /* fs.ReadStream / fs.WriteStream: runtime Readable and Writable streams + * over a file (scr_fs_stream.c). The options are checked at runtime + * with Node's rules; flags and mode are checked when the file opens, so + * their errors arrive as 'error' events. Instances also emit 'open' + * (with the fd) and 'ready'. */ + export interface StreamOptions { + flags?: string; + encoding?: BufferEncoding; + fd?: number; + mode?: number | string; + autoClose?: boolean; + emitClose?: boolean; + start?: number; + highWaterMark?: number; + } + export interface ReadStreamOptions extends StreamOptions { + end?: number; + } + export interface WriteStreamOptions extends StreamOptions { + flush?: boolean; + } + export class ReadStream extends Readable { + readonly bytesRead: number; + readonly path: string | Buffer; + readonly pending: boolean; + close(callback?: () => void): void; + } + export class WriteStream extends Writable { + readonly bytesWritten: number; + readonly path: string | Buffer; + readonly pending: boolean; + close(callback?: () => void): void; + } + export function createReadStream( + path: string | Buffer | URL, + options?: BufferEncoding | ReadStreamOptions, + ): ReadStream; + export function createWriteStream( + path: string | Buffer | URL, + options?: BufferEncoding | WriteStreamOptions, + ): WriteStream; + export function accessSync(path: string | URL, mode?: number): void; + export function mkdtempSync(prefix: string | URL): string; + export const constants: { + readonly F_OK: number; + readonly R_OK: number; + readonly W_OK: number; + readonly X_OK: number; + readonly O_RDONLY: number; + readonly O_WRONLY: number; + readonly O_RDWR: number; + readonly O_CREAT: number; + readonly O_EXCL: number; + readonly O_NOFOLLOW: number; + readonly O_NONBLOCK: number; + readonly O_TRUNC: number; + readonly O_APPEND: number; + readonly COPYFILE_EXCL: number; + readonly COPYFILE_FICLONE: number; + readonly COPYFILE_FICLONE_FORCE: number; + readonly UV_FS_SYMLINK_DIR: number; + readonly UV_FS_SYMLINK_JUNCTION: number; + readonly UV_DIRENT_UNKNOWN: number; + readonly UV_DIRENT_FILE: number; + readonly UV_DIRENT_DIR: number; + readonly UV_DIRENT_LINK: number; + readonly UV_DIRENT_FIFO: number; + readonly UV_DIRENT_SOCKET: number; + readonly UV_DIRENT_CHAR: number; + readonly UV_DIRENT_BLOCK: number; + readonly UV_FS_O_FILEMAP: number; + readonly UV_FS_O_TEMPORARY: number; + readonly UV_FS_O_SHORT_LIVED: number; + readonly UV_FS_O_SEQUENTIAL: number; + readonly UV_FS_O_RANDOM: number; + readonly UV_FS_COPYFILE_EXCL: number; + readonly UV_FS_COPYFILE_FICLONE: number; + readonly UV_FS_COPYFILE_FICLONE_FORCE: number; + readonly S_IFMT: number; + readonly S_IFREG: number; + readonly S_IFDIR: number; + readonly S_IFCHR: number; + readonly S_IFBLK: number; + readonly S_IFIFO: number; + readonly S_IFLNK: number; + readonly S_IFSOCK: number; + readonly O_NOCTTY: number; + readonly O_DIRECTORY: number; + readonly O_NOATIME: number; + readonly O_SYNC: number; + readonly O_DSYNC: number; + readonly O_SYMLINK: number; + readonly O_DIRECT: number; + readonly S_IRWXU: number; + readonly S_IRUSR: number; + readonly S_IWUSR: number; + readonly S_IXUSR: number; + readonly S_IRWXG: number; + readonly S_IRGRP: number; + readonly S_IWGRP: number; + readonly S_IXGRP: number; + readonly S_IRWXO: number; + readonly S_IROTH: number; + readonly S_IWOTH: number; + readonly S_IXOTH: number; + }; + /* A stat(2) snapshot (statSync follows symlinks, lstatSync does not — + * Node's split) — immutable; the supported surface is exactly these + * members. dev/ino identify the filesystem entry, blocks is the allocated + * size in 512-byte units, and the time fields are milliseconds with their + * sub-second fractions. */ + export interface Stats { + isFile(): boolean; + isDirectory(): boolean; + isSymbolicLink(): boolean; + isBlockDevice(): boolean; + isCharacterDevice(): boolean; + isFIFO(): boolean; + isSocket(): boolean; + readonly dev: number; + readonly mode: number; + readonly nlink: number; + readonly uid: number; + readonly gid: number; + readonly rdev: number; + readonly blksize: number; + readonly ino: number; + readonly size: number; + readonly blocks: number; + readonly atimeMs: number; + readonly mtimeMs: number; + readonly ctimeMs: number; + readonly birthtimeMs: number; + readonly atime: Date; + readonly mtime: Date; + readonly ctime: Date; + readonly birthtime: Date; + } + /* throwIfNoEntry: false answers undefined for a missing entry (ENOENT or + * ENOTDIR) instead of throwing. BigIntStats (bigint: true) has no + * lowering. */ + export interface StatSyncOptions { + bigint?: boolean; + throwIfNoEntry?: boolean; + } + export function statSync(path: string | URL, options?: undefined): Stats; + export function statSync( + path: string | URL, + options: StatSyncOptions & { bigint?: false; throwIfNoEntry: false }, + ): Stats | undefined; + export function statSync( + path: string | URL, + options: StatSyncOptions & { bigint?: false; throwIfNoEntry?: true }, + ): Stats; + /* bigint: true type-checks so the lowering can name it as unsupported. */ + export function statSync(path: string | URL, options: StatSyncOptions & { bigint: true }): Stats; + export function statSync(path: string | URL, options: StatSyncOptions): Stats | undefined; + export function lstatSync(path: string | URL, options?: undefined): Stats; + export function lstatSync( + path: string | URL, + options: StatSyncOptions & { bigint?: false; throwIfNoEntry: false }, + ): Stats | undefined; + export function lstatSync( + path: string | URL, + options: StatSyncOptions & { bigint?: false; throwIfNoEntry?: true }, + ): Stats; + /* bigint: true type-checks so the lowering can name it as unsupported. */ + export function lstatSync(path: string | URL, options: StatSyncOptions & { bigint: true }): Stats; + export function lstatSync(path: string | URL, options: StatSyncOptions): Stats | undefined; + /* The fd pair behind spawn's fd-stdio form: openSync(path, flags) → + * the raw fd. Numeric flags use an inline OR of the O_* constants. */ + export function openSync(path: string | URL, flags: string): number; + export function openSync(path: string | URL, flags: number, mode?: number): number; + export function closeSync(fd: number): void; + export function fdatasyncSync(fd: number): void; + export function ftruncateSync(fd: number, len?: number): void; + export function utimesSync( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + ): void; + export function futimesSync( + fd: number, + atime: number | string | Date, + mtime: number | string | Date, + ): void; + export function lutimesSync( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + ): void; + export function utimes( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function futimes( + fd: number, + atime: number | string | Date, + mtime: number | string | Date, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function lutimes( + path: string | Uint8Array | URL, + atime: number | string | Date, + mtime: number | string | Date, + callback: (err: NodeJS.ErrnoException | null) => void, + ): void; + export function readvSync(fd: number, buffers: Uint8Array[], position?: number | null): number; + export function writevSync(fd: number, buffers: Uint8Array[], position?: number | null): number; + /* Read into a caller buffer from the fd's current position when position + * is omitted/null, or from a numeric byte position without advancing the + * fd. Answers the byte count, 0 at EOF. */ + export interface ReadSyncOptions { + offset?: number; + length?: number; + position?: number | null; + } + export function readSync(fd: number, buffer: Uint8Array, options?: ReadSyncOptions): number; + export function readSync( + fd: number, + buffer: Uint8Array, + offset: number, + length: number, + position?: number | null, + ): number; + /* Write a caller-buffer window, or a utf8 string, at the fd's current + * position when position is omitted/null (advancing it), or at a numeric + * byte position without advancing it. Answers the byte count. */ + export interface WriteSyncOptions { + offset?: number; + length?: number; + position?: number | null; + } + export function writeSync(fd: number, buffer: Uint8Array, options: WriteSyncOptions): number; + export function writeSync( + fd: number, + buffer: Uint8Array, + offset?: number, + length?: number, + position?: number | null, + ): number; + export function writeSync( + fd: number, + string: string, + position?: number | null, + encoding?: "utf8" | "utf-8", + ): number; + /* statSync over an open fd — the same Stats snapshot. */ + export function fstatSync(fd: number): Stats; + /* realpath(3) — resolves symlinks, `.`/`..`, throwing Node's fs error + * shapes for missing paths. */ + export function realpathSync(path: string | URL): string; + export namespace realpathSync { + function native(path: string | URL): string; + } + /* statfs(2)/statvfs(3) — the filesystem-capacity snapshot (the fields + * Node's statfsSync reports; bavail × bsize is the free-space probe). */ + export interface StatsFsBase { + type: T; + bsize: T; + blocks: T; + bfree: T; + bavail: T; + files: T; + ffree: T; + } + export interface StatsFs extends StatsFsBase {} + export interface BigIntStatsFs extends StatsFsBase {} + export interface StatFsOptions { + bigint?: boolean; + } + export function statfsSync( + path: string | Uint8Array | URL, + options?: StatFsOptions & { bigint?: false }, + ): StatsFs; + export function statfsSync( + path: string | Uint8Array | URL, + options: StatFsOptions & { bigint: true }, + ): BigIntStatsFs; + export function statfsSync( + path: string | Uint8Array | URL, + options?: StatFsOptions, + ): StatsFs | BigIntStatsFs; + export function statfs( + path: string | Uint8Array | URL, + callback: (err: NodeJS.ErrnoException | null, stats: StatsFs) => void, + ): void; + export function statfs( + path: string | Uint8Array | URL, + options: (StatFsOptions & { bigint?: false }) | undefined, + callback: (err: NodeJS.ErrnoException | null, stats: StatsFs) => void, + ): void; + export function statfs( + path: string | Uint8Array | URL, + options: StatFsOptions & { bigint: true }, + callback: (err: NodeJS.ErrnoException | null, stats: BigIntStatsFs) => void, + ): void; + export function statfs( + path: string | Uint8Array | URL, + options: StatFsOptions | undefined, + callback: (err: NodeJS.ErrnoException | null, stats: StatsFs | BigIntStatsFs) => void, + ): void; + /* fs.watch — the file-watching slice (scr_watch.c, kqueue EVFILT_VNODE): + * "rename"/"change" events on ONE path, watcher.close() to stop. The + * listener takes zero parameters or the eventType string (the filename + * parameter has no lowering); an open watcher keeps the loop alive. A + * path that cannot be opened THROWS Node's fs error synchronously. */ + export interface FSWatcher { + close(): void; + } + /* The options-record stance (see http.RequestOptions): persistent: true, + * recursive: false, and encoding: "utf8" state the lowered behavior and + * are accepted; persistent: false, recursive: true, and signal fence by + * name at the call site; undocumented keys drop like Node. */ + export interface WatchOptions { + persistent?: boolean; + recursive?: boolean; + encoding?: string; + [option: string]: unknown; + } + export function watch(path: string, listener?: (eventType: string) => void): FSWatcher; + export function watch( + path: string, + options: WatchOptions, + listener?: (eventType: string) => void, + ): FSWatcher; + /* fs.promises IS the fs/promises module (Node's rule) — the namespace- + * import form `fs.promises.readFile(...)` lowers through the same + * fs/promises table as `import { readFile } from "node:fs/promises"`. */ + export const promises: typeof import("node:fs/promises"); +} + +/* Every builtin answers to BOTH specifier forms, like in Node: the bare + * module re-exports its node:-prefixed twin (or holds the declarations + * itself — one of the pair forwards). The lowerer recognizes members by + * their import SPECIFIER, so both forms lower identically. */ +declare module "fs" { + export * from "node:fs"; +} diff --git a/packages/compiler/ambient/node-fallback/graceful-fs.d.ts b/packages/compiler/ambient/node-fallback/graceful-fs.d.ts new file mode 100644 index 000000000..e735fa3e6 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/graceful-fs.d.ts @@ -0,0 +1,9 @@ +/// + +/* graceful-fs — served as node:fs (it monkeypatches fs and retries on + * EMFILE/ENFILE; compiled programs call fs directly, without the retry + * queue). gracefulify is a no-op statement. The @types/graceful-fs shape. */ +declare module "graceful-fs" { + export * from "node:fs"; + export function gracefulify(fsModule: T): T; +} diff --git a/packages/compiler/ambient/node-fallback/http.d.ts b/packages/compiler/ambient/node-fallback/http.d.ts new file mode 100644 index 000000000..5588767eb --- /dev/null +++ b/packages/compiler/ambient/node-fallback/http.d.ts @@ -0,0 +1,326 @@ +/// +/// + +/* node:http — the SERVER and CLIENT slices (scr_http.c layers a + * hand-written HTTP/1.1 parser + serializer on scr_net.c; both link only + * into using binaries). createServer(handler) returns a net.Server — + * listen/close/address().port/'error' are the same lowered surface. The + * request carries url/method (always present on server requests), + * statusCode (a number on client responses, undefined on server + * requests), the underlying socket, and lowercased headers read as + * `string | undefined`; the body streams through on("data"/"end"). The + * response is setHeader/writeHead/write/end/destroy with Node's framing + * (Content-Length for a single end(body), chunked after writeHead/write) + * and the auto Date/Connection headers; writeHead's headers argument is + * an object literal or a Record. request/get dial one + * connection per call (no agent pooling) with Node's exact wire head. */ +declare module "http" { + import { Server as NetServer, Socket } from "net"; + export const METHODS: string[]; + export const STATUS_CODES: { [status: number]: string | undefined }; + export const maxHeaderSize: number; + export interface Server extends NetServer { + timeout: number; + setTimeout(msecs: number, callback?: (socket: Socket) => void): this; + closeAllConnections(): void; + closeIdleConnections(): void; + keepAliveTimeout: number; + keepAliveTimeoutBuffer: number; + headersTimeout: number; + requestTimeout: number; + } + export type RequestListener = (req: IncomingMessage, res: ServerResponse) => void; + /* The Server VALUE — Node's constructor works with and without `new` + * (test/parallel's http.Server(fn) spelling); both route to the + * createServer lowering. */ + export const Server: { + new (requestListener?: (req: IncomingMessage, res: ServerResponse) => void): Server; + new ( + options: ServerOptions, + requestListener?: (req: IncomingMessage, res: ServerResponse) => void, + ): Server; + (requestListener?: (req: IncomingMessage, res: ServerResponse) => void): Server; + ( + options: ServerOptions, + requestListener?: (req: IncomingMessage, res: ServerResponse) => void, + ): Server; + }; + /* The options-record stance (the RequestOptions precedent): every + * documented key typechecks; the lowering's option walk decides per + * key — requireHostHeader: false, joinDuplicateHeaders, and + * keepAliveTimeoutBuffer lower, + * documented-but-unlowered keys fence by name, unknown keys drop. */ + export interface ServerOptions { + requireHostHeader?: boolean; + joinDuplicateHeaders?: boolean; + keepAliveTimeoutBuffer?: number; + maxHeaderSize?: number; + [option: string]: unknown; + } + /* The outgoing-header shape, @types/node's matrix: numbers format via + * String(n), arrays write one line per element (set-cookie's shape). + * Incoming headers stay `string | undefined` — what the parser answers + * (under @types/node the two SLOTS unify so `{ ...req.headers }` + * merges into outgoing literals as a copy; the fallback keeps reads + * simple and spreads target the same string | undefined slot). */ + export type OutgoingHttpHeaders = { [name: string]: number | string | string[] | undefined }; + export interface IncomingMessage { + readonly url: string; + readonly method: string; + readonly httpVersion: string; + readonly complete: boolean; + readonly aborted: boolean; + readonly statusCode: number | undefined; + readonly statusMessage: string | undefined; + readonly socket: Socket; + readonly connection: Socket; + readonly headers: { [name: string]: string | undefined }; + readonly headersDistinct: { [name: string]: string[] | undefined }; + readonly rawHeaders: string[]; + readonly trailers: { [name: string]: string | undefined }; + readonly trailersDistinct: { [name: string]: string[] | undefined }; + readonly rawTrailers: string[]; + resume(): void; + pause(): void; + destroy(): void; + setTimeout(msecs: number, callback?: () => void): this; + /* setEncoding('utf8'): 'data' delivers strings (other real encodings + * throw a "not supported yet" Error at runtime; unknown names throw + * ERR_UNKNOWN_ENCODING). */ + setEncoding(encoding: string): void; + /* The proxy legs: the body streams into a ServerResponse, a + * ClientRequest, or a raw Socket; natural end ends the destination. */ + pipe( + destination: ServerResponse | import("http2").Http2ServerResponse | ClientRequest | Socket, + ): void; + on(event: "data", listener: (chunk: any) => void): void; + on(event: "end" | "close" | "aborted", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + /* addListener IS on (Node aliases them) — the suite spells both. */ + addListener(event: "data", listener: (chunk: any) => void): void; + addListener(event: "end" | "close" | "aborted", listener: () => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + once(event: "data", listener: (chunk: any) => void): void; + once(event: "end" | "close" | "aborted", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + } + export interface ServerResponse { + readonly headersSent: boolean; + readonly writableEnded: boolean; + readonly finished: boolean; + readonly writableFinished: boolean; + readonly writableCorked: number; + readonly req: IncomingMessage; + readonly socket: Socket | null; + readonly connection: Socket | null; + sendDate: boolean; + strictContentLength: boolean; + /* Node's writable head properties: the implicit head reads them. */ + statusCode: number; + statusMessage: string | undefined; + /* The value keeps its type: getHeader answers the number, string or + * array that was set. */ + setHeader(name: string, value: number | string | readonly string[]): void; + appendHeader(name: string, value: string | readonly string[]): void; + getHeader(name: string): number | string | string[] | undefined; + getHeaderNames(): string[]; + getRawHeaderNames(): string[]; + getHeaders(): OutgoingHttpHeaders; + hasHeader(name: string): boolean; + removeHeader(name: string): void; + /* Both overloads answer the response (`return this` chaining); the + * headers argument also takes Node's flat [name, value, ...] array. */ + writeHead(statusCode: number, headers?: OutgoingHttpHeaders | string[]): ServerResponse; + writeHead( + statusCode: number, + statusMessage: string, + headers?: OutgoingHttpHeaders | string[], + ): ServerResponse; + writeContinue(callback?: () => void): void; + writeProcessing(callback?: () => void): void; + writeEarlyHints(hints: Record, callback?: () => void): void; + write(data: string | Uint8Array): void; + flushHeaders(): void; + cork(): void; + uncork(): void; + addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void; + setTimeout(msecs: number, callback?: () => void): this; + /* end's callback forms fire once the body went out (the 'finish' + * emit, deferred past the handler's synchronous tail). */ + end(data?: string | Uint8Array, callback?: () => void): void; + end(callback: () => void): void; + destroy(): void; + on(event: "close" | "finish", listener: () => void): void; + /* addListener IS on (Node aliases them) — the suite spells both. */ + addListener(event: "close" | "finish", listener: () => void): void; + once(event: "close" | "finish", listener: () => void): void; + } + /* The CLIENT slice (http.request/http.get): the options-object form + * with hostname/host, port, path, method, timeout, and headers; the + * callback receives the response as an IncomingMessage (statusCode, + * headers, body via on("data"/"end")). */ + export interface RequestOptions { + hostname?: string | null; + host?: string | null; + /* A string port (a URL's .port) is accepted, as by Node. */ + port?: number | string | null; + path?: string | null; + method?: string; + timeout?: number; + headers?: OutgoingHttpHeaders; + /* The caller's own dialer (the proxy's loopback dial): invoked once, + * synchronously; its socket carries the exchange. */ + createConnection?: () => Socket; + /* agent: false is a one-shot dial with Connection: close — exactly + * the compiled client's model; null/undefined are the default; an + * Agent VALUE threads through (getName-keyed maxSockets accounting + * over one-dial-per-request connections). */ + agent?: Agent | boolean | null; + /* Aborting destroys the request with an AbortError. */ + signal?: AbortSignal; + /* HTTP over the Unix socket at this path instead of host:port. */ + socketPath?: string; + /* The options-record stance: Node ignores an options record's + * unknown keys, so every key typechecks here — the option WALK in + * the lowering decides per key (lower, fence by name, or drop + * undocumented keys exactly like Node). */ + [option: string]: unknown; + } + /* The Agent lowers to a checked-dynamic HANDLE: option parsing at the + * literal construction site, getName, destroy, the sockets/requests/ + * freeSockets snapshots, and REAL maxSockets accounting (over-limit + * requests defer their dial and queue). keepAlive: true keeps finished + * connections in freeSockets for the next request to the same name, + * taken in `scheduling` order. */ + export interface AgentOptions { + keepAlive?: boolean; + keepAliveMsecs?: number; + maxSockets?: number; + maxFreeSockets?: number; + maxTotalSockets?: number; + scheduling?: "fifo" | "lifo"; + timeout?: number; + port?: number; + [option: string]: unknown; + } + export class Agent { + constructor(options?: AgentOptions); + destroy(): void; + /* Node's exact key shape: host:port:localAddress[:family][:socketPath]. */ + getName(options?: { + host?: string; + port?: number; + localAddress?: string; + family?: number; + socketPath?: string; + [option: string]: unknown; + }): string; + maxSockets: number; + maxFreeSockets: number; + keepAliveMsecs: number; + readonly keepAlive: boolean; + readonly protocol: string; + /* Settable — a portless request through this agent dials it (Node's + * option merge). */ + defaultPort: number; + readonly totalSocketCount: number; + readonly sockets: { [key: string]: unknown[] }; + readonly freeSockets: { [key: string]: unknown[] }; + readonly requests: { [key: string]: unknown[] }; + } + /* Node's keepAlive global agent for requests without an agent option. */ + export const globalAgent: Agent; + export interface ClientRequest { + readonly destroyed: boolean; + readonly aborted: boolean; + readonly socket: Socket; + readonly connection: Socket; + readonly writableCorked: number; + readonly method: string; + readonly path: string; + readonly host: string; + readonly protocol: string; + readonly headersSent: boolean; + readonly writableEnded: boolean; + readonly writableFinished: boolean; + readonly finished: boolean; + readonly reusedSocket: boolean; + setHeader(name: string, value: string): void; + getHeader(name: string): string | undefined; + hasHeader(name: string): boolean; + removeHeader(name: string): void; + getHeaderNames(): string[]; + getRawHeaderNames(): string[]; + getHeaders(): OutgoingHttpHeaders; + flushHeaders(): void; + addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void; + cork(): void; + uncork(): void; + setNoDelay(noDelay?: boolean): void; + setSocketKeepAlive(enable?: boolean, initialDelay?: number): void; + setTimeout(timeout: number, callback?: () => void): this; + write(data: string | Uint8Array): void; + end(data?: string | Uint8Array): void; + destroy(error?: Error): void; + abort(): void; + on(event: "response", listener: (res: IncomingMessage) => void): void; + on(event: "socket", listener: (socket: Socket) => void): void; + on( + event: "upgrade", + listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + on(event: "timeout" | "close" | "finish" | "abort" | "continue", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + /* addListener IS on (Node aliases them) — the suite spells both. */ + addListener(event: "response", listener: (res: IncomingMessage) => void): void; + addListener(event: "socket", listener: (socket: Socket) => void): void; + addListener( + event: "upgrade", + listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + addListener(event: "timeout" | "close" | "finish" | "abort", listener: () => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + once(event: "response", listener: (res: IncomingMessage) => void): void; + once(event: "socket", listener: (socket: Socket) => void): void; + once( + event: "upgrade", + listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + once(event: "timeout" | "close" | "finish" | "abort" | "continue", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + } + export function createServer( + requestListener?: (req: IncomingMessage, res: ServerResponse) => void, + ): Server; + export function createServer( + options: ServerOptions, + requestListener?: (req: IncomingMessage, res: ServerResponse) => void, + ): Server; + /* One signature with a UNION first parameter rather than Node's two + * overloads: `const requestFn = tls ? https.request : http.request` + * (the portless isProxyRunning shape) unions two function types, and + * resolving a call on that union collapses each constituent to a + * single signature — with an overload SET on both sides the options + * literal ends up checked against URL, in either declaration order. + * A union parameter has nothing to collapse. The lowering dispatches + * on the argument's own type, so it reads the same either way. + * + * `target` is a URL string or URL object (the spelling a from-scratch + * client reaches for first — dialed directly, with a non-http scheme + * an ERR_INVALID_PROTOCOL at runtime rather than a type error), or + * the options record. */ + export function request( + target: RequestOptions | string | URL, + callback?: (res: IncomingMessage) => void, + ): ClientRequest; + export function get( + target: RequestOptions | string | URL, + callback?: (res: IncomingMessage) => void, + ): ClientRequest; + export function validateHeaderName(name: string, label?: string): void; + export function validateHeaderValue(name: string, value: unknown): void; +} + +declare module "node:http" { + export * from "http"; +} diff --git a/packages/compiler/ambient/node-fallback/http2.d.ts b/packages/compiler/ambient/node-fallback/http2.d.ts new file mode 100644 index 000000000..d47be0189 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/http2.d.ts @@ -0,0 +1,816 @@ +/// +/// +/// +/// + +/* node:http2 — the compatibility surface: allowHTTP1 servers negotiate + * h2 or HTTP/1.1 and expose the shared request/response API. HTTP/2 + * requests retain their backing stream; server session failures surface + * through sessionError. */ +declare module "http2" { + import { Socket } from "net"; + export interface Http2Stream { + on(event: "error" | "close", listener: (err: Error) => void): void; + once(event: "error" | "close", listener: (err: Error) => void): void; + addListener(event: "error" | "close", listener: (err: Error) => void): void; + destroy(): void; + } + export interface Http2ServerRequest { + readonly url: string; + readonly method: string; + readonly statusCode: number | undefined; + readonly socket: Socket; + readonly headers: { [name: string]: string | undefined }; + readonly stream: Http2Stream; + /* The h2 compat request's version triple ("2.0" over real h2 streams; + * the allowHTTP1 lowering's HTTP/1.1 connections answer 1.x). */ + readonly httpVersion: string; + readonly httpVersionMajor: number; + readonly httpVersionMinor: number; + readonly aborted: boolean; + readonly complete: boolean; + resume(): void; + destroy(): void; + setEncoding(encoding: string): void; + pipe(destination: Socket | import("http").ClientRequest): void; + on(event: "data", listener: (chunk: Buffer) => void): void; + on(event: "end" | "close" | "aborted", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + /* addListener IS on (Node aliases them) — and the compat req IS the + * http.IncomingMessage surface, so the alias mirrors here too. */ + addListener(event: "data", listener: (chunk: Buffer) => void): void; + addListener(event: "end" | "close" | "aborted", listener: () => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + once(event: "data", listener: (chunk: Buffer) => void): void; + once(event: "end" | "close" | "aborted", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + } + export interface Http2ServerResponse { + readonly headersSent: boolean; + readonly writableEnded: boolean; + readonly writableCorked: number; + /* The same lowered surface as http.ServerResponse — the allowHTTP1 + * lowering serves every connection as HTTP/1.1, where the compat + * response IS this parser's response handle. */ + statusCode: number; + statusMessage: string; + setHeader(name: string, value: string | number): void; + getHeader(name: string): string | undefined; + getHeaderNames(): string[]; + getRawHeaderNames(): string[]; + getHeaders(): import("http").OutgoingHttpHeaders; + hasHeader(name: string): boolean; + removeHeader(name: string): void; + writeHead( + statusCode: number, + headers?: import("http").OutgoingHttpHeaders | string[], + ): Http2ServerResponse; + writeHead( + statusCode: number, + statusMessage: string, + headers?: import("http").OutgoingHttpHeaders | string[], + ): Http2ServerResponse; + writeContinue(): void; + writeEarlyHints(hints: Record): void; + write(data: string | Uint8Array): void; + flushHeaders(): void; + cork(): void; + uncork(): void; + addTrailers( + headers: import("http").OutgoingHttpHeaders | ReadonlyArray<[string, string]>, + ): void; + end(data?: string | Uint8Array, callback?: () => void): void; + end(callback: () => void): void; + destroy(): void; + on(event: "close", listener: () => void): void; + /* addListener IS on (Node aliases them) — and the compat res IS the + * http.ServerResponse surface (assignability into ServerResponse + * unions — IncomingMessage.pipe's destination — needs the alias). */ + addListener(event: "close", listener: () => void): void; + once(event: "close", listener: () => void): void; + } + export interface Http2SecureServer { + listen(port: number, callback?: () => void): void; + close(callback?: () => void): void; + /* The bound AddressInfo (Node answers null before listen — this + * surface answers the record with port 0 there, the serverPort + * stance). */ + address(): import("node:dgram").AddressInfo; + emit(event: "connection", socket: Socket): void; + on(event: "connection", listener: (socket: Socket) => void): void; + on( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: "close", listener: () => void): void; + on(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; + /* The ALPN=h2 server's surface (createSecureServer WITHOUT + * allowHTTP1 — the real h2-over-TLS stack): per-stream and + * per-session listeners, exactly the h2c server's. */ + on( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + on(event: "session", listener: (session: ServerHttp2Session) => void): void; + /* HTTP CONNECT (and, in Node, the h2 extended CONNECT — which the + * allowHTTP1 lowering never sees): the second argument is the raw + * socket on HTTP/1.1 connections; narrow with instanceof. */ + on( + event: "connect", + listener: ( + req: Http2ServerRequest | import("http").IncomingMessage, + resOrSocket: Http2ServerResponse | Socket, + ) => void, + ): void; + once(event: "connection", listener: (socket: Socket) => void): void; + once( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: "close", listener: () => void): void; + once(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; + once( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + once(event: "session", listener: (session: ServerHttp2Session) => void): void; + once( + event: "connect", + listener: ( + req: Http2ServerRequest | import("http").IncomingMessage, + resOrSocket: Http2ServerResponse | Socket, + ) => void, + ): void; + addListener(event: "connection", listener: (socket: Socket) => void): void; + addListener( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener(event: "close", listener: () => void): void; + addListener( + event: "sessionError", + listener: (err: Error, session: ServerHttp2Session) => void, + ): void; + addListener( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener(event: "session", listener: (session: ServerHttp2Session) => void): void; + addListener( + event: "connect", + listener: ( + req: Http2ServerRequest | import("http").IncomingMessage, + resOrSocket: Http2ServerResponse | Socket, + ) => void, + ): void; + } + export interface SecureServerOptions { + allowHTTP1?: boolean; + cert?: string | Uint8Array; + key?: string | Uint8Array; + SNICallback?: ( + servername: string, + cb: (err: Error | null, ctx?: import("tls").SecureContext) => void, + ) => void; + /* The options-record stance (see http.RequestOptions): h2 session- + * tuning knobs are accepted as literals and ignored (no h2 session + * ever exists under the allowHTTP1 lowering); other documented keys + * fence by name; undocumented keys drop like Node. */ + [option: string]: unknown; + } + /* The full Node v24 constants table (240 members), literal-typed so + * computed header keys ([HTTP2_HEADER_PATH]: value) stay literal keys. + * Every ACCESS bakes as its literal at lowering; the object itself + * never materializes (bare http2.constants value uses fence). */ + export interface Http2Constants { + readonly NGHTTP2_ERR_FRAME_SIZE_ERROR: -522; + readonly NGHTTP2_SESSION_SERVER: 0; + readonly NGHTTP2_SESSION_CLIENT: 1; + readonly NGHTTP2_STREAM_STATE_IDLE: 1; + readonly NGHTTP2_STREAM_STATE_OPEN: 2; + readonly NGHTTP2_STREAM_STATE_RESERVED_LOCAL: 3; + readonly NGHTTP2_STREAM_STATE_RESERVED_REMOTE: 4; + readonly NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL: 5; + readonly NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: 6; + readonly NGHTTP2_STREAM_STATE_CLOSED: 7; + readonly NGHTTP2_FLAG_NONE: 0; + readonly NGHTTP2_FLAG_END_STREAM: 1; + readonly NGHTTP2_FLAG_END_HEADERS: 4; + readonly NGHTTP2_FLAG_ACK: 1; + readonly NGHTTP2_FLAG_PADDED: 8; + readonly NGHTTP2_FLAG_PRIORITY: 32; + readonly DEFAULT_SETTINGS_HEADER_TABLE_SIZE: 4096; + readonly DEFAULT_SETTINGS_ENABLE_PUSH: 1; + readonly DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS: 4294967295; + readonly DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 65535; + readonly DEFAULT_SETTINGS_MAX_FRAME_SIZE: 16384; + readonly DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: 65535; + readonly DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL: 0; + readonly MAX_MAX_FRAME_SIZE: 16777215; + readonly MIN_MAX_FRAME_SIZE: 16384; + readonly MAX_INITIAL_WINDOW_SIZE: 2147483647; + readonly NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: 1; + readonly NGHTTP2_SETTINGS_ENABLE_PUSH: 2; + readonly NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: 3; + readonly NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: 4; + readonly NGHTTP2_SETTINGS_MAX_FRAME_SIZE: 5; + readonly NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: 6; + readonly NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: 8; + readonly PADDING_STRATEGY_NONE: 0; + readonly PADDING_STRATEGY_ALIGNED: 1; + readonly PADDING_STRATEGY_MAX: 2; + readonly PADDING_STRATEGY_CALLBACK: 1; + readonly NGHTTP2_NO_ERROR: 0; + readonly NGHTTP2_PROTOCOL_ERROR: 1; + readonly NGHTTP2_INTERNAL_ERROR: 2; + readonly NGHTTP2_FLOW_CONTROL_ERROR: 3; + readonly NGHTTP2_SETTINGS_TIMEOUT: 4; + readonly NGHTTP2_STREAM_CLOSED: 5; + readonly NGHTTP2_FRAME_SIZE_ERROR: 6; + readonly NGHTTP2_REFUSED_STREAM: 7; + readonly NGHTTP2_CANCEL: 8; + readonly NGHTTP2_COMPRESSION_ERROR: 9; + readonly NGHTTP2_CONNECT_ERROR: 10; + readonly NGHTTP2_ENHANCE_YOUR_CALM: 11; + readonly NGHTTP2_INADEQUATE_SECURITY: 12; + readonly NGHTTP2_HTTP_1_1_REQUIRED: 13; + readonly NGHTTP2_DEFAULT_WEIGHT: 16; + readonly HTTP2_HEADER_STATUS: ":status"; + readonly HTTP2_HEADER_METHOD: ":method"; + readonly HTTP2_HEADER_AUTHORITY: ":authority"; + readonly HTTP2_HEADER_SCHEME: ":scheme"; + readonly HTTP2_HEADER_PATH: ":path"; + readonly HTTP2_HEADER_PROTOCOL: ":protocol"; + readonly HTTP2_HEADER_ACCEPT_ENCODING: "accept-encoding"; + readonly HTTP2_HEADER_ACCEPT_LANGUAGE: "accept-language"; + readonly HTTP2_HEADER_ACCEPT_RANGES: "accept-ranges"; + readonly HTTP2_HEADER_ACCEPT: "accept"; + readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS: "access-control-allow-credentials"; + readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS: "access-control-allow-headers"; + readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS: "access-control-allow-methods"; + readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN: "access-control-allow-origin"; + readonly HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS: "access-control-expose-headers"; + readonly HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS: "access-control-request-headers"; + readonly HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD: "access-control-request-method"; + readonly HTTP2_HEADER_AGE: "age"; + readonly HTTP2_HEADER_AUTHORIZATION: "authorization"; + readonly HTTP2_HEADER_CACHE_CONTROL: "cache-control"; + readonly HTTP2_HEADER_CONNECTION: "connection"; + readonly HTTP2_HEADER_CONTENT_DISPOSITION: "content-disposition"; + readonly HTTP2_HEADER_CONTENT_ENCODING: "content-encoding"; + readonly HTTP2_HEADER_CONTENT_LENGTH: "content-length"; + readonly HTTP2_HEADER_CONTENT_TYPE: "content-type"; + readonly HTTP2_HEADER_COOKIE: "cookie"; + readonly HTTP2_HEADER_DATE: "date"; + readonly HTTP2_HEADER_ETAG: "etag"; + readonly HTTP2_HEADER_FORWARDED: "forwarded"; + readonly HTTP2_HEADER_HOST: "host"; + readonly HTTP2_HEADER_IF_MODIFIED_SINCE: "if-modified-since"; + readonly HTTP2_HEADER_IF_NONE_MATCH: "if-none-match"; + readonly HTTP2_HEADER_IF_RANGE: "if-range"; + readonly HTTP2_HEADER_LAST_MODIFIED: "last-modified"; + readonly HTTP2_HEADER_LINK: "link"; + readonly HTTP2_HEADER_LOCATION: "location"; + readonly HTTP2_HEADER_RANGE: "range"; + readonly HTTP2_HEADER_REFERER: "referer"; + readonly HTTP2_HEADER_SERVER: "server"; + readonly HTTP2_HEADER_SET_COOKIE: "set-cookie"; + readonly HTTP2_HEADER_STRICT_TRANSPORT_SECURITY: "strict-transport-security"; + readonly HTTP2_HEADER_TRANSFER_ENCODING: "transfer-encoding"; + readonly HTTP2_HEADER_TE: "te"; + readonly HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS: "upgrade-insecure-requests"; + readonly HTTP2_HEADER_UPGRADE: "upgrade"; + readonly HTTP2_HEADER_USER_AGENT: "user-agent"; + readonly HTTP2_HEADER_VARY: "vary"; + readonly HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS: "x-content-type-options"; + readonly HTTP2_HEADER_X_FRAME_OPTIONS: "x-frame-options"; + readonly HTTP2_HEADER_KEEP_ALIVE: "keep-alive"; + readonly HTTP2_HEADER_PROXY_CONNECTION: "proxy-connection"; + readonly HTTP2_HEADER_X_XSS_PROTECTION: "x-xss-protection"; + readonly HTTP2_HEADER_ALT_SVC: "alt-svc"; + readonly HTTP2_HEADER_CONTENT_SECURITY_POLICY: "content-security-policy"; + readonly HTTP2_HEADER_EARLY_DATA: "early-data"; + readonly HTTP2_HEADER_EXPECT_CT: "expect-ct"; + readonly HTTP2_HEADER_ORIGIN: "origin"; + readonly HTTP2_HEADER_PURPOSE: "purpose"; + readonly HTTP2_HEADER_TIMING_ALLOW_ORIGIN: "timing-allow-origin"; + readonly HTTP2_HEADER_X_FORWARDED_FOR: "x-forwarded-for"; + readonly HTTP2_HEADER_PRIORITY: "priority"; + readonly HTTP2_HEADER_ACCEPT_CHARSET: "accept-charset"; + readonly HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE: "access-control-max-age"; + readonly HTTP2_HEADER_ALLOW: "allow"; + readonly HTTP2_HEADER_CONTENT_LANGUAGE: "content-language"; + readonly HTTP2_HEADER_CONTENT_LOCATION: "content-location"; + readonly HTTP2_HEADER_CONTENT_MD5: "content-md5"; + readonly HTTP2_HEADER_CONTENT_RANGE: "content-range"; + readonly HTTP2_HEADER_DNT: "dnt"; + readonly HTTP2_HEADER_EXPECT: "expect"; + readonly HTTP2_HEADER_EXPIRES: "expires"; + readonly HTTP2_HEADER_FROM: "from"; + readonly HTTP2_HEADER_IF_MATCH: "if-match"; + readonly HTTP2_HEADER_IF_UNMODIFIED_SINCE: "if-unmodified-since"; + readonly HTTP2_HEADER_MAX_FORWARDS: "max-forwards"; + readonly HTTP2_HEADER_PREFER: "prefer"; + readonly HTTP2_HEADER_PROXY_AUTHENTICATE: "proxy-authenticate"; + readonly HTTP2_HEADER_PROXY_AUTHORIZATION: "proxy-authorization"; + readonly HTTP2_HEADER_REFRESH: "refresh"; + readonly HTTP2_HEADER_RETRY_AFTER: "retry-after"; + readonly HTTP2_HEADER_TRAILER: "trailer"; + readonly HTTP2_HEADER_TK: "tk"; + readonly HTTP2_HEADER_VIA: "via"; + readonly HTTP2_HEADER_WARNING: "warning"; + readonly HTTP2_HEADER_WWW_AUTHENTICATE: "www-authenticate"; + readonly HTTP2_HEADER_HTTP2_SETTINGS: "http2-settings"; + readonly HTTP2_METHOD_ACL: "ACL"; + readonly HTTP2_METHOD_BASELINE_CONTROL: "BASELINE-CONTROL"; + readonly HTTP2_METHOD_BIND: "BIND"; + readonly HTTP2_METHOD_CHECKIN: "CHECKIN"; + readonly HTTP2_METHOD_CHECKOUT: "CHECKOUT"; + readonly HTTP2_METHOD_CONNECT: "CONNECT"; + readonly HTTP2_METHOD_COPY: "COPY"; + readonly HTTP2_METHOD_DELETE: "DELETE"; + readonly HTTP2_METHOD_GET: "GET"; + readonly HTTP2_METHOD_HEAD: "HEAD"; + readonly HTTP2_METHOD_LABEL: "LABEL"; + readonly HTTP2_METHOD_LINK: "LINK"; + readonly HTTP2_METHOD_LOCK: "LOCK"; + readonly HTTP2_METHOD_MERGE: "MERGE"; + readonly HTTP2_METHOD_MKACTIVITY: "MKACTIVITY"; + readonly HTTP2_METHOD_MKCALENDAR: "MKCALENDAR"; + readonly HTTP2_METHOD_MKCOL: "MKCOL"; + readonly HTTP2_METHOD_MKREDIRECTREF: "MKREDIRECTREF"; + readonly HTTP2_METHOD_MKWORKSPACE: "MKWORKSPACE"; + readonly HTTP2_METHOD_MOVE: "MOVE"; + readonly HTTP2_METHOD_OPTIONS: "OPTIONS"; + readonly HTTP2_METHOD_ORDERPATCH: "ORDERPATCH"; + readonly HTTP2_METHOD_PATCH: "PATCH"; + readonly HTTP2_METHOD_POST: "POST"; + readonly HTTP2_METHOD_PRI: "PRI"; + readonly HTTP2_METHOD_PROPFIND: "PROPFIND"; + readonly HTTP2_METHOD_PROPPATCH: "PROPPATCH"; + readonly HTTP2_METHOD_PUT: "PUT"; + readonly HTTP2_METHOD_REBIND: "REBIND"; + readonly HTTP2_METHOD_REPORT: "REPORT"; + readonly HTTP2_METHOD_SEARCH: "SEARCH"; + readonly HTTP2_METHOD_TRACE: "TRACE"; + readonly HTTP2_METHOD_UNBIND: "UNBIND"; + readonly HTTP2_METHOD_UNCHECKOUT: "UNCHECKOUT"; + readonly HTTP2_METHOD_UNLINK: "UNLINK"; + readonly HTTP2_METHOD_UNLOCK: "UNLOCK"; + readonly HTTP2_METHOD_UPDATE: "UPDATE"; + readonly HTTP2_METHOD_UPDATEREDIRECTREF: "UPDATEREDIRECTREF"; + readonly HTTP2_METHOD_VERSION_CONTROL: "VERSION-CONTROL"; + readonly HTTP_STATUS_CONTINUE: 100; + readonly HTTP_STATUS_SWITCHING_PROTOCOLS: 101; + readonly HTTP_STATUS_PROCESSING: 102; + readonly HTTP_STATUS_EARLY_HINTS: 103; + readonly HTTP_STATUS_OK: 200; + readonly HTTP_STATUS_CREATED: 201; + readonly HTTP_STATUS_ACCEPTED: 202; + readonly HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION: 203; + readonly HTTP_STATUS_NO_CONTENT: 204; + readonly HTTP_STATUS_RESET_CONTENT: 205; + readonly HTTP_STATUS_PARTIAL_CONTENT: 206; + readonly HTTP_STATUS_MULTI_STATUS: 207; + readonly HTTP_STATUS_ALREADY_REPORTED: 208; + readonly HTTP_STATUS_IM_USED: 226; + readonly HTTP_STATUS_MULTIPLE_CHOICES: 300; + readonly HTTP_STATUS_MOVED_PERMANENTLY: 301; + readonly HTTP_STATUS_FOUND: 302; + readonly HTTP_STATUS_SEE_OTHER: 303; + readonly HTTP_STATUS_NOT_MODIFIED: 304; + readonly HTTP_STATUS_USE_PROXY: 305; + readonly HTTP_STATUS_TEMPORARY_REDIRECT: 307; + readonly HTTP_STATUS_PERMANENT_REDIRECT: 308; + readonly HTTP_STATUS_BAD_REQUEST: 400; + readonly HTTP_STATUS_UNAUTHORIZED: 401; + readonly HTTP_STATUS_PAYMENT_REQUIRED: 402; + readonly HTTP_STATUS_FORBIDDEN: 403; + readonly HTTP_STATUS_NOT_FOUND: 404; + readonly HTTP_STATUS_METHOD_NOT_ALLOWED: 405; + readonly HTTP_STATUS_NOT_ACCEPTABLE: 406; + readonly HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED: 407; + readonly HTTP_STATUS_REQUEST_TIMEOUT: 408; + readonly HTTP_STATUS_CONFLICT: 409; + readonly HTTP_STATUS_GONE: 410; + readonly HTTP_STATUS_LENGTH_REQUIRED: 411; + readonly HTTP_STATUS_PRECONDITION_FAILED: 412; + readonly HTTP_STATUS_PAYLOAD_TOO_LARGE: 413; + readonly HTTP_STATUS_URI_TOO_LONG: 414; + readonly HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: 415; + readonly HTTP_STATUS_RANGE_NOT_SATISFIABLE: 416; + readonly HTTP_STATUS_EXPECTATION_FAILED: 417; + readonly HTTP_STATUS_TEAPOT: 418; + readonly HTTP_STATUS_MISDIRECTED_REQUEST: 421; + readonly HTTP_STATUS_UNPROCESSABLE_ENTITY: 422; + readonly HTTP_STATUS_LOCKED: 423; + readonly HTTP_STATUS_FAILED_DEPENDENCY: 424; + readonly HTTP_STATUS_TOO_EARLY: 425; + readonly HTTP_STATUS_UPGRADE_REQUIRED: 426; + readonly HTTP_STATUS_PRECONDITION_REQUIRED: 428; + readonly HTTP_STATUS_TOO_MANY_REQUESTS: 429; + readonly HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: 431; + readonly HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: 451; + readonly HTTP_STATUS_INTERNAL_SERVER_ERROR: 500; + readonly HTTP_STATUS_NOT_IMPLEMENTED: 501; + readonly HTTP_STATUS_BAD_GATEWAY: 502; + readonly HTTP_STATUS_SERVICE_UNAVAILABLE: 503; + readonly HTTP_STATUS_GATEWAY_TIMEOUT: 504; + readonly HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED: 505; + readonly HTTP_STATUS_VARIANT_ALSO_NEGOTIATES: 506; + readonly HTTP_STATUS_INSUFFICIENT_STORAGE: 507; + readonly HTTP_STATUS_LOOP_DETECTED: 508; + readonly HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED: 509; + readonly HTTP_STATUS_NOT_EXTENDED: 510; + readonly HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: 511; + } + export const constants: Http2Constants; + /* The eager handler is the first COMPAT 'request' listener — Node's + * route on either flavor (the allowHTTP1 server's HTTP/1.1 handles, + * the ALPN=h2 server's compat handles over streams). */ + export function createSecureServer( + options: SecureServerOptions, + onRequestHandler?: ( + req: import("http").IncomingMessage, + res: import("http").ServerResponse, + ) => void, + ): Http2SecureServer; + + /* ── the REAL h2c surface (scr_http2.c: frame codec + HPACK over the + * net loop) — createServer/connect and the session/stream handles the + * Node suite's http2 family exercises. Headers cross as the canonical + * header record (the IncomingHttpHeaders index-signature shape); the + * response :status reads back as a number. Members declared here + * without a lowering fence by name at their use sites. */ + + /** The http2 module's own header shapes: pure index-signature records + * (the HEADER-FAMILY canonicalization — string[] arm included). */ + export interface IncomingHttpHeaders { + [name: string]: number | string | string[] | undefined; + } + export interface OutgoingHttpHeaders { + [name: string]: number | string | string[] | undefined; + } + + export interface Http2Session { + close(callback?: () => void): void; + destroy(): void; + readonly closed: boolean; + readonly destroyed: boolean; + readonly encrypted: boolean; + readonly type: number; + readonly alpnProtocol: string; + readonly socket: import("net").Socket; + readonly pendingSettingsAck: boolean; + ref(): void; + unref(): void; + setTimeout(msecs: number, callback?: () => void): void; + ping(callback: (err: Error | null, duration: number, payload: Uint8Array) => void): boolean; + /* The settings surface rides the checked-dynamic machinery (records + * cross as dyn values — the suite's mustCall listeners are dyn). */ + settings(settings?: any, callback?: any): void; + readonly localSettings: any; + readonly remoteSettings: any; + goaway(code?: number, lastStreamID?: number, opaqueData?: Uint8Array): void; + on(event: "close" | "timeout", listener: () => void): void; + on(event: "error", listener: (err: Error) => void): void; + on( + event: "connect", + listener: (session: Http2Session, socket: import("net").Socket) => void, + ): void; + on( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + on( + event: "goaway", + listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, + ): void; + on(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; + on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + on(event: "ping", listener: (payload: Uint8Array) => void): void; + once(event: "close" | "timeout", listener: () => void): void; + once(event: "error", listener: (err: Error) => void): void; + once( + event: "connect", + listener: (session: Http2Session, socket: import("net").Socket) => void, + ): void; + once( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + once( + event: "goaway", + listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, + ): void; + once(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; + once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + once(event: "ping", listener: (payload: Uint8Array) => void): void; + addListener(event: "close" | "timeout", listener: () => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener( + event: "connect", + listener: (session: Http2Session, socket: import("net").Socket) => void, + ): void; + addListener( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener( + event: "goaway", + listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, + ): void; + addListener(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; + addListener( + event: "frameError", + listener: (type: number, code: number, id: number) => void, + ): void; + addListener(event: "ping", listener: (payload: Uint8Array) => void): void; + } + + export interface ClientHttp2Session extends Http2Session { + request( + headers?: OutgoingHttpHeaders, + options?: { + endStream?: boolean; + exclusive?: boolean; + parent?: number; + weight?: number; + waitForTrailers?: boolean; + }, + ): ClientHttp2Stream; + } + + export interface ServerHttp2Session extends Http2Session { + altsvc(alt: string, originOrStream: number | string): void; + origin(...origins: string[]): void; + } + + /** The shared Http2Stream core (both roles). 'data' chunks are Buffers + * (strings once setEncoding('utf8') is in force — the IncomingMessage + * chunk:any stance keeps both working). */ + interface Http2StreamCore { + readonly id: number | undefined; + readonly rstCode: number; + readonly closed: boolean; + readonly destroyed: boolean; + readonly pending: boolean; + readonly aborted: boolean; + readonly session: Http2Session; + readonly sentHeaders: OutgoingHttpHeaders; + readonly state: Record; + write(chunk: string | Uint8Array, callback?: (err?: Error | null) => void): boolean; + end(data?: string | Uint8Array, callback?: () => void): void; + end(callback: () => void): void; + close(code?: number, callback?: () => void): void; + destroy(error?: Error): void; + setEncoding(encoding: string): this; + resume(): void; + pause(): void; + setTimeout(msecs: number, callback?: () => void): void; + priority(options: Record): void; + sendTrailers(headers: OutgoingHttpHeaders): void; + } + + export interface ServerHttp2Stream extends Http2StreamCore { + on(event: "data", listener: (chunk: any) => void): void; + on( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + on(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; + once(event: "data", listener: (chunk: any) => void): void; + once( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + once(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; + addListener(event: "data", listener: (chunk: any) => void): void; + addListener( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener( + event: "frameError", + listener: (type: number, code: number, id: number) => void, + ): void; + addListener( + event: "trailers", + listener: (trailers: IncomingHttpHeaders, flags: number) => void, + ): void; + respond( + headers?: OutgoingHttpHeaders, + options?: { endStream?: boolean; waitForTrailers?: boolean }, + ): void; + respondWithFile( + path: string, + headers?: OutgoingHttpHeaders, + options?: Record, + ): void; + respondWithFD( + fd: number, + headers?: OutgoingHttpHeaders, + options?: Record, + ): void; + pushStream( + headers: OutgoingHttpHeaders, + callback: ( + err: Error | null, + pushStream: ServerHttp2Stream, + headers: OutgoingHttpHeaders, + ) => void, + ): void; + readonly headersSent: boolean; + readonly pushAllowed: boolean; + additionalHeaders(headers: OutgoingHttpHeaders): void; + } + + export interface ClientHttp2Stream extends Http2StreamCore { + on(event: "response", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + on(event: "push", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + on(event: "headers", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + on(event: "data", listener: (chunk: any) => void): void; + on( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + on(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; + once(event: "response", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + once(event: "push", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + once(event: "headers", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; + once(event: "data", listener: (chunk: any) => void): void; + once( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; + once(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; + addListener( + event: "response", + listener: (headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener( + event: "push", + listener: (headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener( + event: "headers", + listener: (headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener(event: "data", listener: (chunk: any) => void): void; + addListener( + event: + | "end" + | "close" + | "aborted" + | "ready" + | "timeout" + | "drain" + | "finish" + | "wantTrailers", + listener: () => void, + ): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener( + event: "frameError", + listener: (type: number, code: number, id: number) => void, + ): void; + addListener( + event: "trailers", + listener: (trailers: IncomingHttpHeaders, flags: number) => void, + ): void; + } + + export interface Http2Server { + listen(port?: number, callback?: () => void): void; + listen(port: number, host: string, callback?: () => void): void; + close(callback?: () => void): void; + address(): import("node:dgram").AddressInfo; + setTimeout(msecs?: number, callback?: () => void): void; + ref(): void; + unref(): void; + on( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + on(event: "session", listener: (session: ServerHttp2Session) => void): void; + on( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: "close" | "listening" | "timeout", listener: () => void): void; + on(event: "connection", listener: (socket: import("net").Socket) => void): void; + on(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; + once( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + once(event: "session", listener: (session: ServerHttp2Session) => void): void; + once( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: "close" | "listening" | "timeout", listener: () => void): void; + once(event: "connection", listener: (socket: import("net").Socket) => void): void; + once(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; + addListener( + event: "stream", + listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, + ): void; + addListener(event: "session", listener: (session: ServerHttp2Session) => void): void; + addListener( + event: "request", + listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener(event: "close" | "listening" | "timeout", listener: () => void): void; + addListener(event: "connection", listener: (socket: import("net").Socket) => void): void; + addListener( + event: "sessionError", + listener: (err: Error, session: ServerHttp2Session) => void, + ): void; + } + + export interface ServerOptions { + [option: string]: unknown; + } + + export function createServer( + onRequestHandler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): Http2Server; + export function createServer( + options: ServerOptions, + onRequestHandler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void, + ): Http2Server; + + export function connect( + authority: string, + listener?: (session: ClientHttp2Session, socket: import("net").Socket) => void, + ): ClientHttp2Session; + export function connect( + authority: string, + options: Record, + listener?: (session: ClientHttp2Session, socket: import("net").Socket) => void, + ): ClientHttp2Session; + + export function getDefaultSettings(): any; + export function getPackedSettings(settings: Record): Uint8Array; + export const sensitiveHeaders: unique symbol; +} + +declare module "node:http2" { + export * from "http2"; +} diff --git a/packages/compiler/ambient/node-fallback/https.d.ts b/packages/compiler/ambient/node-fallback/https.d.ts new file mode 100644 index 000000000..a3171f401 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/https.d.ts @@ -0,0 +1,59 @@ +/// + +/* node:https — http over the TLS transport (scr_tls.c). + * createServer({ cert, key }, handler) is the http server behind a + * handshake; request/get are the http client with port 443 default and + * the local-CA knobs: `ca` (PEM string/Buffer) replaces the trust + * anchors, rejectUnauthorized: false disables verification (the + * self-signed probe shape). With neither, /etc/ssl/cert.pem stands in + * for Node's bundled roots. */ +declare module "https" { + import { Server } from "http"; + import { Agent, ClientRequest, IncomingMessage, ServerResponse } from "http"; + export { Agent, Server }; + /* Node's keepAlive global agent for requests without an agent option. */ + export const globalAgent: Agent; + export interface ServerOptions { + cert: string | Uint8Array; + key: string | Uint8Array; + /* The options-record stance (see http.RequestOptions). */ + [option: string]: unknown; + } + export interface RequestOptions { + hostname?: string; + host?: string; + port?: number; + path?: string; + method?: string; + timeout?: number; + headers?: { [name: string]: string }; + rejectUnauthorized?: boolean; + ca?: string | Uint8Array; + agent?: Agent | boolean | null; + /* Aborting destroys the request with an AbortError. */ + signal?: AbortSignal; + socketPath?: string; + /* The options-record stance (see http.RequestOptions). */ + [option: string]: unknown; + } + export function createServer( + options: ServerOptions, + requestListener: (req: IncomingMessage, res: ServerResponse) => void, + ): Server; + /* One signature with a union first parameter, for the request-fn + * ternary's sake (see the http block). A URL target takes no options, + * which means Node's defaults — the certificate is verified against + * the default trust anchors. */ + export function request( + target: RequestOptions | string | URL, + callback?: (res: IncomingMessage) => void, + ): ClientRequest; + export function get( + target: RequestOptions | string | URL, + callback?: (res: IncomingMessage) => void, + ): ClientRequest; +} + +declare module "node:https" { + export * from "https"; +} diff --git a/packages/compiler/ambient/node-fallback/module.d.ts b/packages/compiler/ambient/node-fallback/module.d.ts new file mode 100644 index 000000000..0ea020456 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/module.d.ts @@ -0,0 +1,10 @@ +declare module "node:module" { + export function createRequire(filename: string | URL): ScriptcRequire; + export const builtinModules: string[]; + export function isBuiltin(moduleName: string): boolean; + export function syncBuiltinESMExports(): void; +} + +declare module "module" { + export * from "node:module"; +} diff --git a/packages/compiler/ambient/node-fallback/net.d.ts b/packages/compiler/ambient/node-fallback/net.d.ts new file mode 100644 index 000000000..744fbb0b9 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/net.d.ts @@ -0,0 +1,368 @@ +/// +/// +/// +/// +/// + +/* node:net — the inbound-networking slice (scr_net.c links only into + * net-using binaries). TCP only, Node's shapes narrowed to what lowers: + * createServer with an optional connection handler; listen(port[, cb]) + * with the real port discoverable as address().port (the composed read + * is the lowered form — bare address() has no lowering); close([cb]) + * firing once connections drain; socket write/end/destroy/pipe and the + * data/end/close/error/connect/drain events (on and once — listeners + * take at most the declared parameter). write returns Node's + * backpressure boolean; end returns void here (Node returns the socket); + * on/once return void (chaining is fenced). connect's host is a numeric IP or "localhost" (pinned to + * 127.0.0.1 — SEMANTICS.md); every failure is the async 'error' event, + * and an 'error' with no listener exits 1 like an unhandled EventEmitter + * 'error'. */ +declare module "net" { + export interface Socket { + /* Whether the socket keeps the event loop alive (ref'd by default). */ + ref(): void; + unref(): void; + readonly remoteAddress: string | undefined; + /* The peer's fields are cached at first read, like remoteAddress; the + * local ones are undefined while connecting and after close. */ + readonly remotePort: number | undefined; + readonly remoteFamily: string | undefined; + readonly localAddress: string | undefined; + readonly localPort: number | undefined; + readonly localFamily: string | undefined; + readonly bytesRead: number; + readonly connecting: boolean; + readonly pending: boolean; + readonly readyState: "opening" | "open" | "readOnly" | "writeOnly" | "closed"; + /* true once the fd is gone (destroy() or full close) — Node's flag. */ + readonly destroyed: boolean; + /* the write half is open: no end() yet, no FIN sent, fd alive. */ + readonly writable: boolean; + /* true until the read half is done (peer FIN / destroy). */ + readonly readable: boolean; + /* every byte the write paths accepted (buffered included — Node + * counts those too; plaintext on TLS sockets). */ + readonly bytesWritten: number; + /* write's callback runs once the data is flushed, with null; end's + * once the socket finishes. */ + /* false when the bytes this write left unsent reach the 65536-byte + * high-water mark, as in Node; 'drain' fires when they are all sent. */ + write(data: string | Uint8Array, callback?: (err?: Error | null) => void): boolean; + /* the bytes written and not yet sent */ + readonly writableLength: number; + /* a write answered false and 'drain' has not fired yet */ + readonly writableNeedDrain: boolean; + end(callback?: () => void): void; + end(data: string | Uint8Array, callback?: () => void): void; + destroy(): void; + /* Flow control: pause holds reads off (kernel/TCP backpressure is + * the buffer); resume flows — and discards without listeners, so + * 'end' stays reachable. Both chain, Node's shape. */ + pause(): Socket; + resume(): Socket; + /* TCP_NODELAY on the live fd; missing means true. Chains. */ + setNoDelay(noDelay?: boolean): Socket; + /* SO_KEEPALIVE with the initial delay in ms. Chains. */ + setKeepAlive(enable?: boolean, initialDelay?: number): Socket; + /* end() now, destroy once the FIN actually flushed. */ + destroySoon(): void; + /* An RST instead of a FIN; a connecting socket resets once it + * connects. Throws ERR_INVALID_HANDLE_TYPE on a TLS socket. */ + resetAndDestroy(): Socket; + /* The local address, or {} while connecting and once destroyed. */ + address(): AddressInfo | {}; + /* setEncoding('utf8'): 'data' delivers strings — the IncomingMessage + * twin's contract. */ + setEncoding(encoding: string): void; + /* socket→socket, and socket→response (the extended-CONNECT bridge + * leg: raw chunks become response body writes; EOF ends it). */ + pipe( + destination: Socket | import("http").ServerResponse | import("http2").Http2ServerResponse, + ): void; + /* The callback is a once('timeout') listener (removed again by 0). */ + setTimeout(ms: number, callback?: () => void): void; + /* The paused-mode demux surface: once('readable') + read(1) + + * unshift — read answers exactly n buffered bytes or null. */ + read(size?: number): Buffer | null; + unshift(chunk: Uint8Array): void; + /* secureConnect/secure/session are TLS-socket events declared on the + * shared socket surface (the lowering collapses TLSSocket onto this kind): + * the runtime gates on the transport, so a plain socket's + * registration never fires — Node's exact split. 'session' fires + * once with the serialized session (the received-ticket event). */ + on(event: "data", listener: (chunk: Buffer) => void): void; + /* After setEncoding the chunk is a string. */ + on(event: "data", listener: (chunk: string) => void): void; + on( + event: + | "end" + | "connect" + | "ready" + | "timeout" + | "drain" + | "readable" + | "finish" + | "secureConnect" + | "secure", + listener: () => void, + ): void; + on(event: "close", listener: (hadError: boolean) => void): void; + on(event: "error", listener: (err: Error) => void): void; + on(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + on( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; + /* addListener IS on (Node aliases them) — the suite spells both. */ + addListener(event: "data", listener: (chunk: Buffer) => void): void; + /* After setEncoding the chunk is a string. */ + addListener(event: "data", listener: (chunk: string) => void): void; + addListener( + event: + | "end" + | "connect" + | "ready" + | "timeout" + | "drain" + | "readable" + | "finish" + | "secureConnect" + | "secure", + listener: () => void, + ): void; + addListener(event: "close", listener: (hadError: boolean) => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + addListener( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; + once(event: "data", listener: (chunk: Buffer) => void): void; + /* After setEncoding the chunk is a string. */ + once(event: "data", listener: (chunk: string) => void): void; + once( + event: + | "end" + | "connect" + | "ready" + | "timeout" + | "drain" + | "readable" + | "finish" + | "secureConnect" + | "secure", + listener: () => void, + ): void; + once(event: "close", listener: (hadError: boolean) => void): void; + once(event: "error", listener: (err: Error) => void): void; + once(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + once( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; + } + export interface Server { + /* Whether the server keeps the event loop alive (ref'd by default). */ + ref(): void; + unref(): void; + readonly listening: boolean; + /* Connections past this many are closed with a 'drop' event; + * undefined (the default) or null lifts the cap. */ + maxConnections: number | undefined; + /* Node reads it only in cluster mode: a stored value here. */ + dropMaxConnection: boolean | undefined; + /* cb(null, count) on the next tick. */ + getConnections(callback: (error: Error | null, count: number) => void): Server; + /* Node answers the server itself (`return this` chaining). */ + listen(port: number, callback?: () => void): Server; + /* The positional bind address (the options form's host in argument + * spelling). */ + listen(port: number, host: string, callback?: () => void): Server; + /* A Unix-socket server; the socket file is removed when it closes. */ + listen(path: string, callback?: () => void): Server; + /* The explicit-interface bind: host is an IP literal (absent = the + * host-less dual-stack any); ipv6Only sets IPV6_V6ONLY and reusePort + * requests kernel connection distribution where Node supports it. */ + listen( + options: { port: number; host?: string; ipv6Only?: boolean; reusePort?: boolean }, + callback?: () => void, + ): Server; + close(callback?: () => void): void; + /* The bound AddressInfo (Node answers null before listen — this + * surface answers the record with port 0 there, the serverPort + * stance). */ + address(): import("node:dgram").AddressInfo; + /* The demux route: hand an accepted socket to another server. */ + emit(event: "connection", socket: Socket): boolean; + on(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; + /* tls and https servers: a handshake that failed */ + on( + event: "tlsClientError", + listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, + ): void; + on(event: "close" | "listening", listener: () => void): void; + on(event: "drop", listener: (data?: DropArgument) => void): void; + on(event: "timeout", listener: (socket: Socket) => void): void; + on(event: "error", listener: (err: Error) => void): void; + /* The WebSocket handover: fires INSTEAD of 'request' for + * Connection: upgrade requests, with the raw socket + head bytes; + * 'connect' is its twin for CONNECT requests (HTTP tunneling). */ + on( + event: "upgrade" | "connect", + listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + /* http.createServer(handler)'s event twin (a no-parser net server + * never fires it, like Node). */ + on( + event: "request" | "checkContinue" | "checkExpectation", + listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, + ): void; + /* addListener IS on (Node aliases them) — the suite spells both. */ + addListener(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; + /* tls and https servers: a handshake that failed */ + addListener( + event: "tlsClientError", + listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, + ): void; + addListener(event: "close" | "listening", listener: () => void): void; + addListener(event: "drop", listener: (data?: DropArgument) => void): void; + addListener(event: "timeout", listener: (socket: Socket) => void): void; + addListener(event: "error", listener: (err: Error) => void): void; + addListener( + event: "upgrade" | "connect", + listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + addListener( + event: "request" | "checkContinue" | "checkExpectation", + listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, + ): void; + once(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; + /* tls and https servers: a handshake that failed */ + once( + event: "tlsClientError", + listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, + ): void; + once(event: "close" | "listening", listener: () => void): void; + once(event: "drop", listener: (data?: DropArgument) => void): void; + once(event: "timeout", listener: (socket: Socket) => void): void; + once(event: "error", listener: (err: Error) => void): void; + once( + event: "upgrade" | "connect", + listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, + ): void; + once( + event: "request" | "checkContinue" | "checkExpectation", + listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, + ): void; + } + /* The 'drop' payload: the turned-away connection's addresses. */ + export interface DropArgument { + localAddress?: string; + localPort?: number; + localFamily?: string; + remoteAddress?: string; + remotePort?: number; + remoteFamily?: string; + } + export type AddressInfo = import("node:dgram").AddressInfo; + export type IPVersion = "ipv4" | "ipv6"; + export interface SocketAddressInitOptions { + address?: string; + family?: IPVersion; + flowlabel?: number; + port?: number; + } + /* A native handle (scr_net.c); the getters read its parsed address. */ + export class SocketAddress { + constructor(options?: SocketAddressInitOptions); + readonly address: string; + readonly family: IPVersion; + readonly port: number; + readonly flowlabel: number; + toJSON(): { address: string; port: number; family: IPVersion; flowlabel: number }; + /* undefined when the input is not "[:]" */ + static parse(input: string): SocketAddress | undefined; + static isSocketAddress(value: unknown): value is SocketAddress; + } + /* Address, range and subnet rules; check() matches IPv4 addresses + * against IPv6 rules through their IPv4-mapped form, as Node does. */ + export class BlockList { + constructor(); + readonly rules: readonly string[]; + readonly size: number; + addAddress(address: string | SocketAddress, type?: IPVersion): void; + addAddresses(addresses: readonly (string | SocketAddress)[], type?: IPVersion): void; + addRange(start: string | SocketAddress, end: string | SocketAddress, type?: IPVersion): void; + addSubnet(net: string | SocketAddress, prefix: number, type?: IPVersion): void; + addCIDR(cidr: string): void; + addCIDRs(cidrs: readonly string[]): void; + removeAddress(address: string | SocketAddress, type?: IPVersion): void; + removeRange(start: string | SocketAddress, end: string | SocketAddress, type?: IPVersion): void; + removeSubnet(net: string | SocketAddress, prefix: number, type?: IPVersion): void; + removeCIDR(cidr: string): void; + clear(): void; + check(address: string | SocketAddress, type?: IPVersion): boolean; + toJSON(): string[]; + fromJSON(data: string | readonly string[]): void; + static isBlockList(value: unknown): value is BlockList; + static readonly PRIVATE_RANGES: readonly string[]; + } + /* Node's IPv4Reg and IPv6Reg tests: 4, 6, or 0 for isIP. */ + export function isIP(input: string): number; + export function isIPv4(input: string): boolean; + export function isIPv6(input: string): boolean; + /* The Socket VALUE — the `x instanceof net.Socket` narrowing target + * (the h2 compat 'connect' listener's test). Constructing bare sockets + * has no lowering; the construct signature exists for instanceof. */ + export const Socket: abstract new () => Socket; + export function createServer(connectionListener?: (socket: Socket) => void): Server; + export function connect(port: number, host?: string, connectListener?: () => void): Socket; + export function createConnection( + port: number, + host?: string, + connectListener?: () => void, + ): Socket; + /* The caller-resolver dial (@types/node's net.LookupFunction): the + * runtime invokes it as Node's autoSelectFamily dial does (options + * { hints, all: true }, so the callback answers an address list) and + * dials the answered addresses in order. */ + export type LookupFunction = ( + hostname: string, + options: import("node:dns").LookupOptions, + callback: ( + err: NodeJS.ErrnoException | null, + address: string | import("node:dns").LookupAddress[], + family?: number, + ) => void, + ) => void; + export function connect( + options: { + port: number; + host?: string; + autoSelectFamily?: boolean; + lookup?: LookupFunction; + }, + connectListener?: () => void, + ): Socket; + export function createConnection( + options: { + port: number; + host?: string; + autoSelectFamily?: boolean; + lookup?: LookupFunction; + }, + connectListener?: () => void, + ): Socket; + /* The happy-eyeballs attempt budget (Node's process-wide knob; the + * autoSelectFamily dial consults it): one runtime int, default 250ms + * like Node's. */ + export function getDefaultAutoSelectFamilyAttemptTimeout(): number; + export function setDefaultAutoSelectFamilyAttemptTimeout(value: number): void; +} + +declare module "node:net" { + export * from "net"; +} diff --git a/packages/compiler/ambient/node-fallback/os.d.ts b/packages/compiler/ambient/node-fallback/os.d.ts new file mode 100644 index 000000000..d58592df5 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/os.d.ts @@ -0,0 +1,288 @@ +/* node:os — the slice a CLI needs: platform() (same implementation as + * process.platform), homedir(), tmpdir() (Node's env cascade), EOL + * (always "\n" — POSIX pinned, like path.sep), and networkInterfaces() + * (getifaddrs(3); the interface shapes mirror @types/node's exactly so + * both type worlds map to the same IR structure). */ +declare module "os" { + export function platform(): string; + export function homedir(): string; + export function tmpdir(): string; + /* uname(2)'s release field — Node's own implementation. */ + export function release(): string; + /* uname(2)'s sysname field ("Darwin", "Linux") — Node's os.type(). */ + export function type(): string; + /* Total system memory in bytes (sysctl hw.memsize / sysconf). */ + export function totalmem(): number; + export function availableParallelism(): number; + /* The passwd-entry snapshot (uv_os_get_passwd): shell is `string | + * null` to match @types/node (POSIX always answers the string arm); + * homedir is pw_dir — NOT os.homedir()'s $HOME-first cascade. The type + * is interned explicitly in type-mapper.ts (the AddressInfo pattern — this + * concrete form and @types/node's UserInfo both), and the call + * assembles field-by-field in lowerOsUserInfoCall. */ + export interface UserInfo { + username: string; + uid: number; + gid: number; + shell: string | null; + homedir: string; + } + export function userInfo(): UserInfo; + /* The TARGET platform's line terminator (a compile-time constant: + * "\r\n" under a win32 triple, "\n" everywhere else). */ + export const EOL: "\r\n" | "\n"; + export interface NetworkInterfaceBase { + address: string; + netmask: string; + mac: string; + internal: boolean; + cidr: string | null; + scopeid?: number; + } + export interface NetworkInterfaceInfoIPv4 extends NetworkInterfaceBase { + family: "IPv4"; + } + export interface NetworkInterfaceInfoIPv6 extends NetworkInterfaceBase { + family: "IPv6"; + scopeid: number; + } + export type NetworkInterfaceInfo = NetworkInterfaceInfoIPv4 | NetworkInterfaceInfoIPv6; + export function networkInterfaces(): { [name: string]: NetworkInterfaceInfo[] | undefined }; + /* Host facts, libuv's answers: model/speed/times per logical CPU, the + * host name, the 1/5/15-minute load averages, uptime in seconds, free + * memory in bytes, process.arch, uname's machine and version fields, + * the byte order, and a process's scheduling priority (an omitted pid + * is the current process). Shapes mirror @types/node's. */ + export interface CpuInfo { + model: string; + speed: number; + times: { + user: number; + nice: number; + sys: number; + idle: number; + irq: number; + }; + } + export function cpus(): CpuInfo[]; + export function hostname(): string; + export function loadavg(): number[]; + export function uptime(): number; + export function freemem(): number; + export function arch(): string; + export function machine(): string; + export function version(): string; + export function endianness(): "BE" | "LE"; + export function getPriority(pid?: number): number; + export function setPriority(priority: number): void; + export function setPriority(pid: number, priority: number): void; + /* The TARGET platform's null device ("\\\\.\\nul" under a win32 triple). */ + export const devNull: string; + /* The platform's constants: null-prototype objects with read-only + * members, `signals` frozen (Node's internalBinding('constants').os). */ + export type SignalConstants = { + [ + key in + | "SIGABRT" + | "SIGALRM" + | "SIGBUS" + | "SIGCHLD" + | "SIGCONT" + | "SIGFPE" + | "SIGHUP" + | "SIGILL" + | "SIGINT" + | "SIGIO" + | "SIGIOT" + | "SIGKILL" + | "SIGPIPE" + | "SIGPOLL" + | "SIGPROF" + | "SIGPWR" + | "SIGQUIT" + | "SIGSEGV" + | "SIGSTKFLT" + | "SIGSTOP" + | "SIGSYS" + | "SIGTERM" + | "SIGTRAP" + | "SIGTSTP" + | "SIGTTIN" + | "SIGTTOU" + | "SIGUNUSED" + | "SIGURG" + | "SIGUSR1" + | "SIGUSR2" + | "SIGVTALRM" + | "SIGWINCH" + | "SIGXCPU" + | "SIGXFSZ" + | "SIGBREAK" + | "SIGLOST" + | "SIGINFO" + ]: number; + }; + export namespace constants { + const UV_UDP_REUSEADDR: number; + namespace signals {} + const signals: SignalConstants; + namespace errno { + const E2BIG: number; + const EACCES: number; + const EADDRINUSE: number; + const EADDRNOTAVAIL: number; + const EAFNOSUPPORT: number; + const EAGAIN: number; + const EALREADY: number; + const EBADF: number; + const EBADMSG: number; + const EBUSY: number; + const ECANCELED: number; + const ECHILD: number; + const ECONNABORTED: number; + const ECONNREFUSED: number; + const ECONNRESET: number; + const EDEADLK: number; + const EDESTADDRREQ: number; + const EDOM: number; + const EDQUOT: number; + const EEXIST: number; + const EFAULT: number; + const EFBIG: number; + const EHOSTUNREACH: number; + const EIDRM: number; + const EILSEQ: number; + const EINPROGRESS: number; + const EINTR: number; + const EINVAL: number; + const EIO: number; + const EISCONN: number; + const EISDIR: number; + const ELOOP: number; + const EMFILE: number; + const EMLINK: number; + const EMSGSIZE: number; + const EMULTIHOP: number; + const ENAMETOOLONG: number; + const ENETDOWN: number; + const ENETRESET: number; + const ENETUNREACH: number; + const ENFILE: number; + const ENOBUFS: number; + const ENODATA: number; + const ENODEV: number; + const ENOENT: number; + const ENOEXEC: number; + const ENOLCK: number; + const ENOLINK: number; + const ENOMEM: number; + const ENOMSG: number; + const ENOPROTOOPT: number; + const ENOSPC: number; + const ENOSR: number; + const ENOSTR: number; + const ENOSYS: number; + const ENOTCONN: number; + const ENOTDIR: number; + const ENOTEMPTY: number; + const ENOTSOCK: number; + const ENOTSUP: number; + const ENOTTY: number; + const ENXIO: number; + const EOPNOTSUPP: number; + const EOVERFLOW: number; + const EPERM: number; + const EPIPE: number; + const EPROTO: number; + const EPROTONOSUPPORT: number; + const EPROTOTYPE: number; + const ERANGE: number; + const EROFS: number; + const ESPIPE: number; + const ESRCH: number; + const ESTALE: number; + const ETIME: number; + const ETIMEDOUT: number; + const ETXTBSY: number; + const EWOULDBLOCK: number; + const EXDEV: number; + const WSAEINTR: number; + const WSAEBADF: number; + const WSAEACCES: number; + const WSAEFAULT: number; + const WSAEINVAL: number; + const WSAEMFILE: number; + const WSAEWOULDBLOCK: number; + const WSAEINPROGRESS: number; + const WSAEALREADY: number; + const WSAENOTSOCK: number; + const WSAEDESTADDRREQ: number; + const WSAEMSGSIZE: number; + const WSAEPROTOTYPE: number; + const WSAENOPROTOOPT: number; + const WSAEPROTONOSUPPORT: number; + const WSAESOCKTNOSUPPORT: number; + const WSAEOPNOTSUPP: number; + const WSAEPFNOSUPPORT: number; + const WSAEAFNOSUPPORT: number; + const WSAEADDRINUSE: number; + const WSAEADDRNOTAVAIL: number; + const WSAENETDOWN: number; + const WSAENETUNREACH: number; + const WSAENETRESET: number; + const WSAECONNABORTED: number; + const WSAECONNRESET: number; + const WSAENOBUFS: number; + const WSAEISCONN: number; + const WSAENOTCONN: number; + const WSAESHUTDOWN: number; + const WSAETOOMANYREFS: number; + const WSAETIMEDOUT: number; + const WSAECONNREFUSED: number; + const WSAELOOP: number; + const WSAENAMETOOLONG: number; + const WSAEHOSTDOWN: number; + const WSAEHOSTUNREACH: number; + const WSAENOTEMPTY: number; + const WSAEPROCLIM: number; + const WSAEUSERS: number; + const WSAEDQUOT: number; + const WSAESTALE: number; + const WSAEREMOTE: number; + const WSASYSNOTREADY: number; + const WSAVERNOTSUPPORTED: number; + const WSANOTINITIALISED: number; + const WSAEDISCON: number; + const WSAENOMORE: number; + const WSAECANCELLED: number; + const WSAEINVALIDPROCTABLE: number; + const WSAEINVALIDPROVIDER: number; + const WSAEPROVIDERFAILEDINIT: number; + const WSASYSCALLFAILURE: number; + const WSASERVICE_NOT_FOUND: number; + const WSATYPE_NOT_FOUND: number; + const WSA_E_NO_MORE: number; + const WSA_E_CANCELLED: number; + const WSAEREFUSED: number; + } + namespace dlopen { + const RTLD_LAZY: number; + const RTLD_NOW: number; + const RTLD_GLOBAL: number; + const RTLD_LOCAL: number; + const RTLD_DEEPBIND: number; + } + namespace priority { + const PRIORITY_LOW: number; + const PRIORITY_BELOW_NORMAL: number; + const PRIORITY_NORMAL: number; + const PRIORITY_ABOVE_NORMAL: number; + const PRIORITY_HIGH: number; + const PRIORITY_HIGHEST: number; + } + } +} + +declare module "node:os" { + export * from "os"; +} diff --git a/packages/compiler/ambient/node-fallback/path-posix.d.ts b/packages/compiler/ambient/node-fallback/path-posix.d.ts new file mode 100644 index 000000000..66db6a876 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/path-posix.d.ts @@ -0,0 +1,22 @@ +/// + +declare module "path/posix" { + export function join(...paths: string[]): string; + export function resolve(...paths: string[]): string; + export function normalize(path: string): string; + export function dirname(path: string): string; + export function basename(path: string, suffix?: string): string; + export function extname(path: string): string; + export function isAbsolute(path: string): boolean; + export function relative(from: string, to: string): string; + export function toNamespacedPath(path: string): string; + export function matchesGlob(path: string, pattern: string): boolean; + export function parse(path: string): import("path").ParsedPath; + export function format(pathObject: import("path").FormatInputPathObject): string; + export const sep: "/"; + export const delimiter: ":"; +} + +declare module "node:path/posix" { + export * from "path/posix"; +} diff --git a/packages/compiler/ambient/node-fallback/path-win32.d.ts b/packages/compiler/ambient/node-fallback/path-win32.d.ts new file mode 100644 index 000000000..b889a34e0 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/path-win32.d.ts @@ -0,0 +1,22 @@ +/// + +declare module "path/win32" { + export function join(...paths: string[]): string; + export function resolve(...paths: string[]): string; + export function normalize(path: string): string; + export function dirname(path: string): string; + export function basename(path: string, suffix?: string): string; + export function extname(path: string): string; + export function isAbsolute(path: string): boolean; + export function relative(from: string, to: string): string; + export function toNamespacedPath(path: string): string; + export function matchesGlob(path: string, pattern: string): boolean; + export function parse(path: string): import("path").ParsedPath; + export function format(pathObject: import("path").FormatInputPathObject): string; + export const sep: "\\"; + export const delimiter: ";"; +} + +declare module "node:path/win32" { + export * from "path/win32"; +} diff --git a/packages/compiler/ambient/node-fallback/path.d.ts b/packages/compiler/ambient/node-fallback/path.d.ts new file mode 100644 index 000000000..a57e40bb7 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/path.d.ts @@ -0,0 +1,55 @@ +/// +/// + +/* node:path — the TARGET platform's implementation (Node's rule): posix + * on posix triples, path.win32 under a win32 triple, with the posix and + * win32 namespaces answering their own platform anywhere. Every function + * is a pure string algorithm matching Node's implementations + * exactly; join/resolve are variadic like Node's. */ +declare module "path" { + export function join(...paths: string[]): string; + export function resolve(...paths: string[]): string; + export function normalize(path: string): string; + export function dirname(path: string): string; + export function basename(path: string, suffix?: string): string; + export function extname(path: string): string; + export function isAbsolute(path: string): boolean; + export function relative(from: string, to: string): string; + export function toNamespacedPath(path: string): string; + /* matchesGlob() is Node's glob matcher: brace expansion, extglobs, POSIX + * classes and the ** and dot rules. */ + export function matchesGlob(path: string, pattern: string): boolean; + /* parse() splits a path into its root, directory, base, extension and + * name; format() joins those fields back (Node's `||` defaults). */ + export interface ParsedPath { + root: string; + dir: string; + base: string; + ext: string; + name: string; + } + export interface FormatInputPathObject { + root?: string | undefined; + dir?: string | undefined; + base?: string | undefined; + ext?: string | undefined; + name?: string | undefined; + } + export function parse(path: string): ParsedPath; + export function format(pathObject: FormatInputPathObject): string; + /* The union @types/node declares: the bare module answers the TARGET + * platform's value (a compile-time constant — "\\"/";" under a win32 + * triple, "/"/":" everywhere else). */ + export const sep: "\\" | "/"; + export const delimiter: ";" | ":"; + /* The platform-specific namespaces (real Node submodules too — + * "node:path/posix" resolves). Each carries the FULL surface and + * answers ITS platform's rules on any target, Node's own contract; + * the bare module IS the target platform's implementation. */ + export const posix: typeof import("path/posix"); + export const win32: typeof import("path/win32"); +} + +declare module "node:path" { + export * from "path"; +} diff --git a/packages/compiler/ambient/node-fallback/process.d.ts b/packages/compiler/ambient/node-fallback/process.d.ts new file mode 100644 index 000000000..7782a1b2d --- /dev/null +++ b/packages/compiler/ambient/node-fallback/process.d.ts @@ -0,0 +1,11 @@ +/* Node's process module default export is the global process object. These + * declaration aliases let the ESM spelling typecheck against the same + * deliberately narrow fallback surface when @types/node is absent. */ +declare module "process" { + export = process; +} + +declare module "node:process" { + import process = require("process"); + export = process; +} diff --git a/packages/compiler/ambient/node-fallback/punycode.d.ts b/packages/compiler/ambient/node-fallback/punycode.d.ts new file mode 100644 index 000000000..fc2725c5d --- /dev/null +++ b/packages/compiler/ambient/node-fallback/punycode.d.ts @@ -0,0 +1,18 @@ +/* node:punycode (deprecated, DEP0040) — punycode.js 2.1.0: decode and + * toUnicode throw its RangeErrors on malformed input; ucs2 converts + * between strings and UTF-16 code point arrays. */ +declare module "punycode" { + export function decode(string: string): string; + export function encode(string: string): string; + export function toUnicode(domain: string): string; + export function toASCII(domain: string): string; + export namespace ucs2 { + function decode(string: string): number[]; + function encode(codePoints: readonly number[]): string; + } + export const version: string; +} + +declare module "node:punycode" { + export * from "punycode"; +} diff --git a/packages/compiler/ambient/node-fallback/querystring.d.ts b/packages/compiler/ambient/node-fallback/querystring.d.ts new file mode 100644 index 000000000..4ee4346bb --- /dev/null +++ b/packages/compiler/ambient/node-fallback/querystring.d.ts @@ -0,0 +1,55 @@ +/* node:querystring — Node's legacy query-string codec (NOT + * URLSearchParams: '+' means space on the parse side and escape encodes + * spaces as %20). parse answers the null-prototype dictionary as a pure + * index-signature record (repeated keys become string[] buckets; + * @types/node's Dict adds an undefined arm the lowering tolerates); + * stringify serializes string/number/boolean values and arrays of those + * (Node's rules: arrays expand to repeated keys, null/undefined and + * anything else serialize as the empty value). The maxKeys option lowers + * (0 removes the cap, Node's rule); the custom encoder/decoder options + * typecheck under the options-record stance and fence by name at the + * call. decode/encode are Node's own aliases of parse/stringify. */ +declare module "querystring" { + export interface ParseOptions { + maxKeys?: number; + decodeURIComponent?: (str: string) => string; + [option: string]: unknown; + } + export interface StringifyOptions { + encodeURIComponent?: (str: string) => string; + [option: string]: unknown; + } + export interface ParsedUrlQuery { + [key: string]: string | string[] | undefined; + } + export interface ParsedUrlQueryInput { + [key: string]: + | string + | number + | boolean + | bigint + | ReadonlyArray + | null + | undefined; + } + export function parse( + str: string, + sep?: string | null, + eq?: string | null, + options?: ParseOptions, + ): ParsedUrlQuery; + export function stringify( + obj?: ParsedUrlQueryInput, + sep?: string | null, + eq?: string | null, + options?: StringifyOptions, + ): string; + export const decode: typeof parse; + export const encode: typeof stringify; + export function escape(str: string): string; + export function unescape(str: string): string; +} + +declare module "node:querystring" { + export * from "querystring"; +} diff --git a/packages/compiler/ambient/node-fallback/stream-consumers.d.ts b/packages/compiler/ambient/node-fallback/stream-consumers.d.ts new file mode 100644 index 000000000..9e220ea17 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/stream-consumers.d.ts @@ -0,0 +1,21 @@ +declare module "stream/consumers" { + /* The promise consumers over a readable: accumulate every chunk and + * settle — text (the utf8 decode), json (the text through JSON.parse: + * `unknown`, validated with a checked cast; malformed input rejects + * with the parse's SyntaxError), buffer (the concatenated bytes; + * string chunks contribute their utf8 bytes, Node's Blob rule). + * Stream errors reject; an early close rejects with + * ERR_STREAM_PREMATURE_CLOSE. arrayBuffer answers an ArrayBuffer over + * the bytes; blob fences per site. */ + function text(stream: unknown): Promise; + function json(stream: unknown): Promise; + function buffer(stream: unknown): Promise; + function arrayBuffer(stream: unknown): Promise; + function blob(stream: unknown): Promise; + export { arrayBuffer, blob, buffer, json, text }; +} + +declare module "node:stream/consumers" { + import streamConsumers = require("stream/consumers"); + export = streamConsumers; +} diff --git a/packages/compiler/ambient/node-fallback/stream-promises.d.ts b/packages/compiler/ambient/node-fallback/stream-promises.d.ts new file mode 100644 index 000000000..bf771d571 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/stream-promises.d.ts @@ -0,0 +1,28 @@ +/// + +declare module "stream/promises" { + /* The promise forms of finished/pipeline: a void promise the stream's + * terminal point settles (rejected with the error, or + * ERR_STREAM_PREMATURE_CLOSE on an early close). Stream arguments are + * the lowered surface — iterables/generators/functions fence per site. + * pipeline's trailing { signal, end } object and finished's options + * (readable, writable, error, cleanup, signal) are supported. A last + * stage that is an async function resolves the promise with its value. */ + function pipeline(...streams: T): import("stream").PipelinePromise; + function finished( + stream: unknown, + options?: { + readable?: boolean; + writable?: boolean; + error?: boolean; + cleanup?: boolean; + signal?: AbortSignal; + } | null, + ): Promise; + export { pipeline, finished }; +} + +declare module "node:stream/promises" { + import streamPromises = require("stream/promises"); + export = streamPromises; +} diff --git a/packages/compiler/ambient/node-fallback/stream-web.d.ts b/packages/compiler/ambient/node-fallback/stream-web.d.ts new file mode 100644 index 000000000..f947e667d --- /dev/null +++ b/packages/compiler/ambient/node-fallback/stream-web.d.ts @@ -0,0 +1,17 @@ +/* node:stream/web — the module exports the web stream globals themselves + * (require("stream/web").ReadableStream === globalThis.ReadableStream). + * ReadableStream is the implemented class; its reader and controller are + * exported as types. */ +declare module "stream/web" { + export const ReadableStream: typeof globalThis.ReadableStream; + export type ReadableStream = globalThis.ReadableStream; + export type ReadableStreamDefaultReader = globalThis.ReadableStreamDefaultReader; + export type ReadableStreamDefaultController = + globalThis.ReadableStreamDefaultController; + export type UnderlyingSource = globalThis.UnderlyingSource; + export type QueuingStrategy = globalThis.QueuingStrategy; +} + +declare module "node:stream/web" { + export * from "stream/web"; +} diff --git a/packages/compiler/ambient/node-fallback/stream.d.ts b/packages/compiler/ambient/node-fallback/stream.d.ts new file mode 100644 index 000000000..24f290fd9 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/stream.d.ts @@ -0,0 +1,644 @@ +/// + +/* node:stream — the static stream classes (runtime-provided, the + * EventEmitter-hierarchy precedent). Phase 1 is the OPTIONS-OBJECT + * constructor surface: `new Readable({ read() {} })`, `new Writable({ + * write(chunk, enc, cb) {} })`, Duplex/Transform/PassThrough, push/read, + * flowing vs paused, write/end backpressure + 'drain', pipe/unpipe, and + * destroy — with Node's event orderings. Chunks are BYTES (Buffer), with + * strings converted at push/write like Node, and any value on an + * object-mode side (objectMode, readableObjectMode, writableObjectMode — + * the object-mode constructor overloads come first). The runtime-fired + * events carry per-class FORCED argument tuples ('data' is one chunk, + * 'error' one Error, 'pipe'/'unpipe' one Readable, the rest empty) — the + * overloads below hand tsc the same contract so unannotated listener + * parameters contextually type in .js sources. */ +declare module "stream" { + import { EventEmitter } from "events"; + + interface ReadableOptions { + highWaterMark?: number; + encoding?: string; + objectMode?: boolean; + read?(this: Readable, size: number): void; + destroy?(this: Readable, error: Error | null, callback: (error?: Error | null) => void): void; + construct?(this: Readable, callback: (error?: Error | null) => void): void; + autoDestroy?: boolean; + emitClose?: boolean; + captureRejections?: boolean; + defaultEncoding?: string; + signal?: AbortSignal; + } + interface WritableOptions { + highWaterMark?: number; + decodeStrings?: boolean; + defaultEncoding?: string; + objectMode?: boolean; + write?( + this: Writable, + chunk: Buffer, + encoding: string, + callback: (error?: Error | null) => void, + ): void; + writev?( + this: Writable, + chunks: Array<{ chunk: any; encoding: string }>, + callback: (error?: Error | null) => void, + ): void; + final?(this: Writable, callback: (error?: Error | null) => void): void; + destroy?(this: Writable, error: Error | null, callback: (error?: Error | null) => void): void; + construct?(this: Writable, callback: (error?: Error | null) => void): void; + autoDestroy?: boolean; + emitClose?: boolean; + captureRejections?: boolean; + signal?: AbortSignal; + } + interface DuplexOptions { + highWaterMark?: number; + readableHighWaterMark?: number; + writableHighWaterMark?: number; + encoding?: string; + decodeStrings?: boolean; + objectMode?: boolean; + readableObjectMode?: boolean; + writableObjectMode?: boolean; + allowHalfOpen?: boolean; + read?(this: Duplex, size: number): void; + write?( + this: Duplex, + chunk: Buffer, + encoding: string, + callback: (error?: Error | null) => void, + ): void; + final?(this: Duplex, callback: (error?: Error | null) => void): void; + destroy?(this: Duplex, error: Error | null, callback: (error?: Error | null) => void): void; + construct?(this: Duplex, callback: (error?: Error | null) => void): void; + autoDestroy?: boolean; + emitClose?: boolean; + captureRejections?: boolean; + readable?: boolean; + writable?: boolean; + defaultEncoding?: string; + writev?( + this: Duplex, + chunks: Array<{ chunk: any; encoding: string }>, + callback: (error?: Error | null) => void, + ): void; + signal?: AbortSignal; + } + interface TransformOptions { + highWaterMark?: number; + readableHighWaterMark?: number; + writableHighWaterMark?: number; + encoding?: string; + decodeStrings?: boolean; + objectMode?: boolean; + readableObjectMode?: boolean; + writableObjectMode?: boolean; + allowHalfOpen?: boolean; + transform?( + this: Transform, + chunk: Buffer, + encoding: string, + callback: (error?: Error | null, data?: Buffer | string) => void, + ): void; + flush?(this: Transform, callback: (error?: Error | null, data?: Buffer | string) => void): void; + destroy?(this: Transform, error: Error | null, callback: (error?: Error | null) => void): void; + construct?(this: Transform, callback: (error?: Error | null) => void): void; + read?(this: Transform, size: number): void; + write?( + this: Transform, + chunk: Buffer, + encoding: string, + callback: (error?: Error | null) => void, + ): void; + final?(this: Transform, callback: (error?: Error | null) => void): void; + writev?(this: Transform, chunks: unknown[], callback: (error?: Error | null) => void): void; + autoDestroy?: boolean; + emitClose?: boolean; + captureRejections?: boolean; + readable?: boolean; + writable?: boolean; + defaultEncoding?: string; + signal?: AbortSignal; + } + + /* Object-mode options (objectMode: true, or a Duplex's or Transform's + * readableObjectMode / writableObjectMode): chunks and completion data + * are any values, and an object-mode write's encoding argument reaches + * write/transform as passed (undefined when absent, null when a + * callback took its place). The constructors take these before the + * byte-mode options, so a byte-mode stream's callbacks keep Buffer + * chunks. */ + type ObjectModeEncoding = string | null | undefined; + type ObjectModeFlag = + | { objectMode: true } + | { readableObjectMode: true } + | { writableObjectMode: true }; + interface ObjectWritableOptions extends Omit { + objectMode: true; + write?( + this: Writable, + chunk: any, + encoding: ObjectModeEncoding, + callback: (error?: Error | null) => void, + ): void; + } + type ObjectDuplexOptions = Omit & { + write?( + this: Duplex, + chunk: any, + encoding: ObjectModeEncoding, + callback: (error?: Error | null) => void, + ): void; + } & ObjectModeFlag; + type ObjectTransformOptions = Omit & { + transform?( + this: Transform, + chunk: any, + encoding: ObjectModeEncoding, + callback: (error?: Error | null, data?: any) => void, + ): void; + flush?(this: Transform, callback: (error?: Error | null, data?: any) => void): void; + write?( + this: Transform, + chunk: any, + encoding: ObjectModeEncoding, + callback: (error?: Error | null) => void, + ): void; + } & ObjectModeFlag; + + /* decodeStrings: false — a string write reaches write/transform as the + * string with its encoding (Buffers keep 'buffer'). */ + interface StringWritableOptions extends Omit { + decodeStrings: false; + write?( + this: Writable, + chunk: string | Buffer, + encoding: string, + callback: (error?: Error | null) => void, + ): void; + } + type StringDuplexOptions = Omit & { + decodeStrings: false; + write?( + this: Duplex, + chunk: string | Buffer, + encoding: string, + callback: (error?: Error | null) => void, + ): void; + }; + type StringTransformOptions = Omit & { + decodeStrings: false; + transform?( + this: Transform, + chunk: string | Buffer, + encoding: string, + callback: (error?: Error | null, data?: Buffer | string) => void, + ): void; + }; + + /* The operators' options: concurrency and highWaterMark bound map's + * in-flight callbacks and queued results. */ + interface ArrayOptions { + concurrency?: number; + highWaterMark?: number; + signal?: AbortSignal; + } + + class Readable extends EventEmitter { + constructor(opts?: ReadableOptions); + /* The underscore-method surface (@types/node declares these on the + * classes; assigning them post-construction shadows the prototype + * method and the machinery calls through the assignment — lowered + * onto the option-callback slots). */ + _read(size: number): void; + _destroy(error: Error | null, callback: (error?: Error | null) => void): void; + [Symbol.asyncIterator](): AsyncIterableIterator; + iterator(options?: { destroyOnReturn?: boolean }): AsyncIterableIterator; + /* compose(this, stage), destroyed when options.signal aborts. */ + compose(stage: ComposeStage, options?: { signal: AbortSignal }): Duplex; + push(chunk: Buffer | string | null, encoding?: string): boolean; + /* An object-mode stream's values. */ + push(chunk: any, encoding?: string): boolean; + unshift(chunk: Buffer | string, encoding?: string): void; + unshift(chunk: any, encoding?: string): void; + /* A Buffer, a string once encoded, an object-mode value, or null (as + * @types/node types it). */ + read(size?: number): any; + pause(): this; + resume(): this; + isPaused(): boolean; + setEncoding(encoding: string): this; + pipe(destination: T, options?: { end?: boolean }): T; + unpipe(destination?: NodeJS.WritableStream): this; + destroy(error?: Error): this; + readonly readable: boolean; + readonly readableEnded: boolean; + readonly readableFlowing: boolean | null; + readonly readableLength: number; + readonly readableHighWaterMark: number; + readonly readableObjectMode: boolean; + readonly closed: boolean; + readonly destroyed: boolean; + readonly errored: Error | null; + static from(iterable: unknown, options?: ReadableOptions): Readable; + /* A Readable over a web ReadableStream's reader. */ + static fromWeb( + readableStream: globalThis.ReadableStream, + options?: { + encoding?: string; + highWaterMark?: number; + objectMode?: boolean; + signal?: AbortSignal; + }, + ): Readable; + /* The Readable operators: chunks are strings or Buffers, map and + * flatMap callbacks return strings or Buffers (flatMap: arrays of + * them, or strings), and callbacks that declare the options argument + * are refused. */ + map( + fn: (data: any, options?: Pick) => any, + options?: ArrayOptions, + ): Readable; + filter( + fn: (data: any, options?: Pick) => boolean | Promise, + options?: ArrayOptions, + ): Readable; + forEach( + fn: (data: any, options?: Pick) => void | Promise, + options?: ArrayOptions, + ): Promise; + toArray(options?: Pick): Promise; + some( + fn: (data: any, options?: Pick) => boolean | Promise, + options?: ArrayOptions, + ): Promise; + find( + fn: (data: any, options?: Pick) => boolean | Promise, + options?: ArrayOptions, + ): Promise; + every( + fn: (data: any, options?: Pick) => boolean | Promise, + options?: ArrayOptions, + ): Promise; + flatMap( + fn: (data: any, options?: Pick) => any, + options?: ArrayOptions, + ): Readable; + drop(limit: number, options?: Pick): Readable; + take(limit: number, options?: Pick): Readable; + reduce( + fn: (previous: any, data: any, options?: Pick) => T, + initial?: undefined, + options?: Pick, + ): Promise; + reduce( + fn: (previous: T, data: any, options?: Pick) => T, + initial: T, + options?: Pick, + ): Promise; + on(event: "data", listener: (chunk: any) => void): this; + on(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: string, listener: (...args: any[]) => void): this; + once(event: "data", listener: (chunk: any) => void): this; + once(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + addListener(event: "data", listener: (chunk: any) => void): this; + addListener( + event: "end" | "close" | "readable" | "pause" | "resume", + listener: () => void, + ): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: "data", listener: (chunk: any) => void): this; + prependListener( + event: "end" | "close" | "readable" | "pause" | "resume", + listener: () => void, + ): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: "data", listener: (chunk: any) => void): this; + prependOnceListener( + event: "end" | "close" | "readable" | "pause" | "resume", + listener: () => void, + ): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + off(event: "data", listener: (chunk: any) => void): this; + off(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; + off(event: "error", listener: (err: Error) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + removeListener(event: "data", listener: (chunk: any) => void): this; + removeListener( + event: "end" | "close" | "readable" | "pause" | "resume", + listener: () => void, + ): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + } + + class Writable extends EventEmitter { + constructor(opts: ObjectWritableOptions); + constructor(opts: StringWritableOptions); + constructor(opts?: WritableOptions); + /* The underscore-method surface (see Readable). */ + _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void; + _writev( + chunks: Array<{ chunk: any; encoding: string }>, + callback: (error?: Error | null) => void, + ): void; + _final(callback: (error?: Error | null) => void): void; + _destroy(error: Error | null, callback: (error?: Error | null) => void): void; + write(chunk: Buffer | string, callback?: (error?: Error | null) => void): boolean; + write( + chunk: Buffer | string, + encoding: string, + callback?: (error?: Error | null) => void, + ): boolean; + /* An object-mode stream's values (the encoding argument reaches + * _write as passed). */ + write(chunk: any, callback?: (error?: Error | null) => void): boolean; + write( + chunk: any, + encoding: ObjectModeEncoding, + callback?: (error?: Error | null) => void, + ): boolean; + end(callback?: () => void): this; + end(chunk: Buffer | string, callback?: () => void): this; + end(chunk: Buffer | string, encoding: string, callback?: () => void): this; + end(chunk: any, callback?: () => void): this; + end(chunk: any, encoding: ObjectModeEncoding, callback?: () => void): this; + cork(): void; + uncork(): void; + setDefaultEncoding(encoding: string): this; + destroy(error?: Error): this; + readonly writable: boolean; + readonly writableEnded: boolean; + readonly writableFinished: boolean; + readonly writableLength: number; + readonly writableHighWaterMark: number; + readonly writableObjectMode: boolean; + readonly writableNeedDrain: boolean; + readonly writableCorked: number; + readonly closed: boolean; + readonly destroyed: boolean; + readonly errored: Error | null; + on(event: "drain" | "finish" | "close", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + on(event: string, listener: (...args: any[]) => void): this; + once(event: "drain" | "finish" | "close", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + addListener(event: "drain" | "finish" | "close", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: "drain" | "finish" | "close", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: "drain" | "finish" | "close", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + off(event: "drain" | "finish" | "close", listener: () => void): this; + off(event: "error", listener: (err: Error) => void): this; + off(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + removeListener(event: "drain" | "finish" | "close", listener: () => void): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + } + + /* Duplex is Readable + Writable in one object. TS single inheritance + * mirrors @types/node: extends Readable, the writable half declared + * again. The lowering treats both halves as first-class. */ + class Duplex extends Readable { + /* Duplex.from: an async generator function, an iterable, a string or + * a stream (duplexify.js). */ + static from(body: unknown): Duplex; + constructor(opts: ObjectDuplexOptions); + constructor(opts: StringDuplexOptions); + constructor(opts?: DuplexOptions); + /* The underscore-method surface (see Readable). */ + _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void; + _writev( + chunks: Array<{ chunk: any; encoding: string }>, + callback: (error?: Error | null) => void, + ): void; + _final(callback: (error?: Error | null) => void): void; + write(chunk: Buffer | string, callback?: (error?: Error | null) => void): boolean; + write( + chunk: Buffer | string, + encoding: string, + callback?: (error?: Error | null) => void, + ): boolean; + /* An object-mode stream's values (the encoding argument reaches + * _write as passed). */ + write(chunk: any, callback?: (error?: Error | null) => void): boolean; + write( + chunk: any, + encoding: ObjectModeEncoding, + callback?: (error?: Error | null) => void, + ): boolean; + end(callback?: () => void): this; + end(chunk: Buffer | string, callback?: () => void): this; + end(chunk: Buffer | string, encoding: string, callback?: () => void): this; + end(chunk: any, callback?: () => void): this; + end(chunk: any, encoding: ObjectModeEncoding, callback?: () => void): this; + cork(): void; + uncork(): void; + setDefaultEncoding(encoding: string): this; + readonly writable: boolean; + readonly writableEnded: boolean; + readonly writableFinished: boolean; + readonly writableLength: number; + readonly writableHighWaterMark: number; + readonly writableObjectMode: boolean; + readonly writableNeedDrain: boolean; + readonly writableCorked: number; + readonly allowHalfOpen: boolean; + on(event: "data", listener: (chunk: any) => void): this; + on( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + on(event: "error", listener: (err: Error) => void): this; + on(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + on(event: string, listener: (...args: any[]) => void): this; + once(event: "data", listener: (chunk: any) => void): this; + once( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + once(event: "error", listener: (err: Error) => void): this; + once(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + addListener(event: "data", listener: (chunk: any) => void): this; + addListener( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: "data", listener: (chunk: any) => void): this; + prependListener( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: "data", listener: (chunk: any) => void): this; + prependOnceListener( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + off(event: "data", listener: (chunk: any) => void): this; + off( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + off(event: "error", listener: (err: Error) => void): this; + off(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + removeListener(event: "data", listener: (chunk: any) => void): this; + removeListener( + event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", + listener: () => void, + ): this; + removeListener(event: "error", listener: (err: Error) => void): this; + removeListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + } + + class Transform extends Duplex { + constructor(opts: ObjectTransformOptions); + constructor(opts: StringTransformOptions); + constructor(opts?: TransformOptions); + /* The underscore-method surface (see Readable). */ + _transform( + chunk: any, + encoding: string, + callback: (error?: Error | null, data?: any) => void, + ): void; + _flush(callback: (error?: Error | null, data?: any) => void): void; + } + class PassThrough extends Transform { + constructor(opts: ObjectTransformOptions); + constructor(opts: StringTransformOptions); + constructor(opts?: TransformOptions); + } + + /* addAbortSignal(signal, stream): the stream, destroyed with an + * AbortError (cause: signal.reason) when the signal aborts. */ + function addAbortSignal(signal: AbortSignal, stream: T): T; + /* pipeline(...stages, callback): the last stream, or the object-mode + * PassThrough that a function destination feeds. */ + type PipelineResult = T extends [ + ...unknown[], + infer D, + (...args: any[]) => unknown, + ] + ? D extends (...args: any[]) => unknown + ? PassThrough + : D + : unknown; + function pipeline(...streams: T): PipelineResult; + /* util.promisify's pipeline overload matches only pipeline itself: a + * generic signature alone admits every function. */ + namespace pipeline { + const __promisify__: typeof promises.pipeline; + } + /* A compose stage: a stream, an async generator function over the + * previous stage, or (first) an iterable, async iterable or web + * ReadableStream. */ + type ComposeStage = + | Readable + | Writable + | Iterable + | AsyncIterable + | globalThis.ReadableStream + | ((source: any, options?: { signal: AbortSignal }) => unknown); + /* compose(...streams): a Duplex that writes to the first stage and + * reads from the last, with the stages running as a pipeline. */ + function compose(...streams: ComposeStage[]): Duplex; + /* finished's options: readable/writable choose which ends count, + * error: false ignores 'error' events, signal aborts the wait. */ + interface FinishedOptions { + readable?: boolean; + writable?: boolean; + error?: boolean; + signal?: AbortSignal; + } + function finished(stream: unknown, callback: (err?: Error | null) => void): () => void; + function finished( + stream: unknown, + options: FinishedOptions | null, + callback: (err?: Error | null) => void, + ): () => void; + /* Folds to the TARGET platform's default at compile time (win32 16384, + * else 65536 — Node's own state.js split); literal argument only. */ + function getDefaultHighWaterMark(objectMode: boolean): number; + /* The promise pipeline's result: a last stage that is an async + * function resolves the promise with its value (before the options + * argument, when one follows). */ + type PipelinePromise = T extends [ + ...unknown[], + (...args: any[]) => Promise, + ] + ? Promise

+ : T extends [ + ...unknown[], + (...args: any[]) => Promise, + { signal?: AbortSignal; end?: boolean }, + ] + ? Promise

+ : Promise; + namespace promises { + function pipeline(...streams: T): PipelinePromise; + function finished( + stream: unknown, + options?: FinishedOptions & { cleanup?: boolean }, + ): Promise; + } + class Stream extends EventEmitter {} + + export { + Readable, + Writable, + Duplex, + Transform, + PassThrough, + Stream, + addAbortSignal, + pipeline, + compose, + finished, + getDefaultHighWaterMark, + promises, + }; + export type { + ReadableOptions, + WritableOptions, + DuplexOptions, + TransformOptions, + FinishedOptions, + PipelinePromise, + }; +} + +declare module "node:stream" { + import stream = require("stream"); + export = stream; +} diff --git a/packages/compiler/ambient/node-fallback/string_decoder.d.ts b/packages/compiler/ambient/node-fallback/string_decoder.d.ts new file mode 100644 index 000000000..617a7871c --- /dev/null +++ b/packages/compiler/ambient/node-fallback/string_decoder.d.ts @@ -0,0 +1,20 @@ +/* node:string_decoder — StringDecoder over Node's whole encoding set: + * write() decodes complete sequences and buffers a trailing partial one + * across chunk boundaries (utf8's truncated sequence, base64's mod-3 + * remainder, utf16le's odd byte / trailing lead surrogate; latin1, ascii + * and hex are stateless); end() flushes the buffered partial. `encoding` + * answers the normalized name. Node's exact algorithms (SEMANTICS.md); + * the encoding must be a literal, and end(buffer) fences — write the + * buffer, then end(). */ +declare module "string_decoder" { + export class StringDecoder { + constructor(encoding?: BufferEncoding); + readonly encoding: string; + write(buffer: Uint8Array): string; + end(buffer?: Uint8Array): string; + } +} + +declare module "node:string_decoder" { + export * from "string_decoder"; +} diff --git a/packages/compiler/ambient/node-fallback/test.d.ts b/packages/compiler/ambient/node-fallback/test.d.ts new file mode 100644 index 000000000..2ca1c0fe7 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/test.d.ts @@ -0,0 +1,236 @@ +/// + +/* node:assert — the static assertion surface. The module object IS a + * callable function (`assert(x)` is `assert.ok(x)`), so the declaration + * is the callable-namespace `export =` shape and BOTH `import assert + * from "node:assert"` and `const assert = require("assert")` bind the + * callable; named imports take the members. Failures throw + * AssertionError (name "AssertionError", code "ERR_ASSERTION" — catch and + * read .name/.message/.code). strictEqual/deepStrictEqual compare with + * Object.is over scalars; deepStrictEqual compares composites + * structurally per their static types. The messages here are plain + * strings (Node accepts Errors — that form fences per site), and the + * shallow legacy pair (equal/notEqual) follows Node's == coercions for + * primitive and primitive-union operands, including Node v24's NaN + * exception. deepEqual/notDeepEqual remain declared-but-fenced; + * assert/strict's loose names alias the strict forms and lower. */ +/* node:test — the in-process test runner (prefix-only in Node too: + * require("test") is MODULE_NOT_FOUND). test/it register tests (sync and + * async bodies, skip/todo/only options and method twins), describe/suite + * group them (bodies run at registration, Node's collection phase), + * before/after/beforeEach/afterEach hook the enclosing suite, and the + * TestContext argument carries subtests (t.test), t.skip/t.todo, + * t.diagnostic, and t.assert. mock/run/snapshot are DECLARED so real + * suites typecheck but fence at their use sites (no lowering). */ +declare module "node:test" { + type TestFn = (t: TestContext) => void | Promise; + type SuiteFn = (s: SuiteContext) => void; + type HookFn = (c: TestContext | SuiteContext) => void | Promise; + /* The describe/suite callback's context, and the before/after hook + * context of a suite. */ + interface SuiteContext { + readonly name: string; + readonly fullName: string; + readonly filePath: string | undefined; + readonly attempt: number; + readonly passed: boolean; + diagnostic(message: string): void; + } + interface TestOptions { + skip?: boolean | string; + todo?: boolean | string; + only?: boolean; + concurrency?: number | boolean; + timeout?: number; + plan?: number; + } + interface TestContext { + readonly name: string; + readonly fullName: string; + readonly filePath: string | undefined; + readonly attempt: number; + readonly passed: boolean; + readonly workerId: number | undefined; + readonly tags: readonly string[]; + readonly assert: TestContextAssert; + readonly mock: MockTracker; + test(name?: string, fn?: TestFn): Promise; + test(name?: string, options?: TestOptions, fn?: TestFn): Promise; + test(options?: TestOptions, fn?: TestFn): Promise; + test(fn?: TestFn): Promise; + skip(message?: string): void; + todo(message?: string): void; + diagnostic(message: string): void; + plan(count: number): void; + after(fn: HookFn): void; + before(fn: HookFn): void; + beforeEach(fn: HookFn): void; + afterEach(fn: HookFn): void; + } + interface AssertSnapshotOptions { + readonly serializers?: ReadonlyArray<(value: any) => any> | undefined; + } + interface TestContextAssert { + snapshot(value: any, options?: AssertSnapshotOptions): void; + fileSnapshot(value: any, path: string, options?: AssertSnapshotOptions): void; + ok(value: unknown, message?: string): void; + strictEqual(actual: unknown, expected: unknown, message?: string): void; + notStrictEqual(actual: unknown, expected: unknown, message?: string): void; + deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; + equal(actual: unknown, expected: unknown, message?: string): void; + notEqual(actual: unknown, expected: unknown, message?: string): void; + deepEqual(actual: unknown, expected: unknown, message?: string): void; + notDeepEqual(actual: unknown, expected: unknown, message?: string): void; + fail(message?: string): never; + throws(fn: () => unknown, expected?: unknown, message?: string): void; + match(value: string, regExp: RegExp, message?: string): void; + doesNotMatch(value: string, regExp: RegExp, message?: string): void; + } + /* The name, options and function are each optional, as in Node: a test + * without a name takes the function's name, or "". */ + function test(name?: string, fn?: TestFn): Promise; + function test(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function test(options?: TestOptions, fn?: TestFn): Promise; + function test(fn?: TestFn): Promise; + namespace test { + function skip(name?: string, fn?: TestFn): Promise; + function skip(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function skip(options?: TestOptions, fn?: TestFn): Promise; + function skip(fn?: TestFn): Promise; + function todo(name?: string, fn?: TestFn): Promise; + function todo(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function todo(options?: TestOptions, fn?: TestFn): Promise; + function todo(fn?: TestFn): Promise; + function only(name?: string, fn?: TestFn): Promise; + function only(name?: string, options?: TestOptions, fn?: TestFn): Promise; + function only(options?: TestOptions, fn?: TestFn): Promise; + function only(fn?: TestFn): Promise; + } + const it: typeof test; + function describe(name?: string, fn?: SuiteFn): void; + function describe(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function describe(options?: TestOptions, fn?: SuiteFn): void; + function describe(fn?: SuiteFn): void; + namespace describe { + function skip(name?: string, fn?: SuiteFn): void; + function skip(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function skip(options?: TestOptions, fn?: SuiteFn): void; + function skip(fn?: SuiteFn): void; + function todo(name?: string, fn?: SuiteFn): void; + function todo(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function todo(options?: TestOptions, fn?: SuiteFn): void; + function todo(fn?: SuiteFn): void; + function only(name?: string, fn?: SuiteFn): void; + function only(name?: string, options?: TestOptions, fn?: SuiteFn): void; + function only(options?: TestOptions, fn?: SuiteFn): void; + function only(fn?: SuiteFn): void; + } + const suite: typeof describe; + function before(fn: HookFn): void; + function after(fn: HookFn): void; + function beforeEach(fn: HookFn): void; + function afterEach(fn: HookFn): void; + /* The mock function surface: mock.fn, mock.method and their t.mock + * twins, the tracker's restoreAll and reset, and the calls log and the + * implementation controls on fn.mock. */ + interface MockFunctionCall< + F extends Function, + ReturnType = F extends (...args: any) => infer T ? T : unknown, + Args = F extends (...args: infer Y) => any ? Y : unknown[], + > { + arguments: Args; + error: unknown | undefined; + result: ReturnType | undefined; + this: unknown; + } + interface MockFunctionContext { + readonly calls: MockFunctionCall[]; + callCount(): number; + mockImplementation(implementation: F): void; + mockImplementationOnce(implementation: F, onCall?: number): void; + resetCalls(): void; + restore(): void; + } + type Mock = F & { mock: MockFunctionContext }; + interface MockFunctionOptions { + times?: number; + } + type FunctionPropertyNames = { + [K in keyof T]: T[K] extends Function ? K : never; + }[keyof T]; + interface MockTracker { + fn undefined>(original?: F): Mock; + fn undefined, Implementation extends Function = F>( + original?: F, + implementation?: Implementation, + ): Mock; + method>( + object: MockedObject, + methodName: MethodName, + options?: MockFunctionOptions, + ): MockedObject[MethodName] extends Function ? Mock : never; + method< + MockedObject extends object, + MethodName extends FunctionPropertyNames, + Implementation extends Function, + >( + object: MockedObject, + methodName: MethodName, + implementation: Implementation, + options?: MockFunctionOptions, + ): MockedObject[MethodName] extends Function + ? Mock + : never; + reset(): void; + restoreAll(): void; + readonly timers: MockTimers; + } + interface MockTimersOptions { + apis: ReadonlyArray<"setInterval" | "setTimeout" | "setImmediate" | "Date">; + now?: number | Date | undefined; + } + interface MockTimers { + enable(options?: MockTimersOptions): void; + setTime(milliseconds?: number): void; + reset(): void; + tick(milliseconds?: number): void; + runAll(): void; + [Symbol.dispose](): void; + } + const mock: MockTracker; + /* The context of the test, suite or hook running in the current async + * context; undefined outside one. */ + function getTestContext(): TestContext | SuiteContext | undefined; + /* Declared for typechecking only — every use fences (no lowering). + * `assert` is the module-level custom-assertion registry (v24's + * `const { assert } = require('node:test')`), not TestContext.assert. */ + const assert: { register(name: string, fn: (...args: unknown[]) => unknown): void }; + function run(options?: unknown): unknown; + function snapshot(...args: unknown[]): unknown; + export { + test, + it, + describe, + suite, + before, + after, + beforeEach, + afterEach, + mock, + assert, + getTestContext, + run, + snapshot, + TestContext, + SuiteContext, + TestOptions, + Mock, + MockFunctionContext, + MockFunctionCall, + MockTracker, + MockTimers, + MockTimersOptions, + }; + export default test; +} diff --git a/packages/compiler/ambient/node-fallback/timers-promises.d.ts b/packages/compiler/ambient/node-fallback/timers-promises.d.ts new file mode 100644 index 000000000..619b92f79 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/timers-promises.d.ts @@ -0,0 +1,31 @@ +/* node:timers/promises — the promisified pair the Node test harness leans + * on (`await setTimeout(ms)` as a sleep). setTimeout and setImmediate + * settle with their resolve value (undefined without one) from the shared + * timer heap and the immediate queue; setInterval(delay, value) lowers to a + * typed async iterator. The options object takes `ref`; AbortSignal + * options remain declared surface that fences per site. */ +declare module "node:timers/promises" { + export interface TimerOptions { + ref?: boolean; + signal?: AbortSignal; + } + export function setTimeout( + delay?: number, + value?: T, + options?: TimerOptions, + ): Promise; + export function setImmediate(value?: T, options?: TimerOptions): Promise; + export function setInterval( + delay?: number, + value?: T, + options?: unknown, + ): AsyncGenerator; + export const scheduler: { + wait(delay?: number, options?: TimerOptions): Promise; + yield(): Promise; + }; +} + +declare module "timers/promises" { + export * from "node:timers/promises"; +} diff --git a/packages/compiler/ambient/node-fallback/timers.d.ts b/packages/compiler/ambient/node-fallback/timers.d.ts new file mode 100644 index 000000000..f6a1d3946 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/timers.d.ts @@ -0,0 +1,28 @@ +/* node:timers — the module twins of the timer globals (Node's timers + * module re-exports them: require('timers').setTimeout IS setTimeout). + * Named imports, destructured requires, and namespace calls all lower to + * the same libCalls as the globals; the surface beyond these six + * (enroll/unenroll, promises) fences per member. */ +declare module "node:timers" { + export function setTimeout(callback: () => void, ms?: number): Timeout; + export function setTimeout( + callback: (...args: never[]) => void, + ms?: number, + ...args: unknown[] + ): Timeout; + export function clearTimeout(handle?: Timeout | number | null | undefined): void; + export function setInterval(callback: () => void, ms?: number): Timeout; + export function setInterval( + callback: (...args: never[]) => void, + ms?: number, + ...args: unknown[] + ): Timeout; + export function clearInterval(handle?: Timeout | number | null | undefined): void; + export function setImmediate(callback: () => void): Immediate; + export function setImmediate(callback: (...args: never[]) => void, ...args: unknown[]): Immediate; + export function clearImmediate(handle?: Immediate | null | undefined): void; +} + +declare module "timers" { + export * from "node:timers"; +} diff --git a/packages/compiler/ambient/node-fallback/tls.d.ts b/packages/compiler/ambient/node-fallback/tls.d.ts new file mode 100644 index 000000000..6ddc08b35 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/tls.d.ts @@ -0,0 +1,169 @@ +/// +/// + +/* node:tls — the TLS server slice (scr_tls.c over scr_net.c with the + * vendored mbedTLS; linked only into using binaries). + * createServer({ cert, key }, handler) returns a net.Server; the handler + * is Node's 'secureConnection' — it fires post-handshake with a socket + * that behaves exactly like a net socket (write/end/destroy/pipe and the + * data/end/close/error events are the same lowered surface). cert/key + * are PEM strings or Buffers. Handshake failures tear the socket down + * without emitting an event, Node's default for 'tlsClientError' with no listener. */ +declare module "tls" { + import { Server as NetServer, Socket } from "net"; + /** tls.Server: a net server whose connections handshake first. */ + export interface Server extends NetServer { + /* serve `context` to clients whose SNI name matches `hostname` ('*' + * matches within one label); the newest match wins */ + addContext(hostname: string, context: TlsOptions | SecureContext): void; + /* new connections serve a context made from `options` */ + setSecureContext(options: TlsOptions): void; + } + export interface TlsOptions { + /* Optional in the DECLARATION only (real Node programs build these + * literals with every TLS knob): the lowering still requires both — + * a literal without them fences at the call site. */ + cert?: string | Uint8Array; + key?: string | Uint8Array; + /* The options-record stance (see http.RequestOptions): every key + * typechecks; the option walk fences documented-but-unlowered keys + * by name and drops undocumented ones like Node. */ + [option: string]: unknown; + } + export function createServer( + options: TlsOptions, + secureConnectionListener?: (socket: TLSSocket) => void, + ): Server; + /** TLSSocket — post-handshake it IS a net socket to every lowered + * member (the mapping collapses it onto the socket kind; the + * secureConnect/session events ride the shared socket declarations). + * The TLS extras: authorized/authorizationError answer Node's verify + * split — authorizationError is the verify-failure CODE STRING + * (DEPTH_ZERO_SELF_SIGNED_CERT, ...), or null. */ + export interface TLSSocket extends Socket { + readonly encrypted: true; + authorized: boolean; + authorizationError: string | null; + /* null before the handshake, then the negotiated protocol or false */ + readonly alpnProtocol: string | false | null; + /* "TLSv1.2" or "TLSv1.3"; null before the handshake and after close */ + getProtocol(): string | null; + /* OpenSSL's suite name, the IANA name and the suite's version */ + getCipher(): CipherNameAndProtocol; + /* whether the handshake resumed a cached session */ + isSessionReused(): boolean; + /* the peer's certificate: {} before the handshake or without one, + * null once the socket is destroyed; detailed adds issuerCertificate */ + getPeerCertificate(detailed: true): DetailedPeerCertificate; + getPeerCertificate(detailed?: false): PeerCertificate; + getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate; + /* the certificate this socket serves, the same way */ + getCertificate(): PeerCertificate | object | null; + /* a new X509Certificate on each call, or undefined without one */ + getPeerX509Certificate(): import("crypto").X509Certificate | undefined; + getX509Certificate(): import("crypto").X509Certificate | undefined; + } + /* A certificate as getPeerCertificate() and X509Certificate's + * toLegacyObject() answer it: subject and issuer map attribute short + * names to values, infoAccess its " - " keys to values. */ + export interface PeerCertificate { + subject: { [attribute: string]: string | string[] }; + issuer: { [attribute: string]: string | string[] }; + subjectaltname?: string; + infoAccess?: { [key: string]: string[] }; + ca: boolean; + modulus?: string; + exponent?: string; + pubkey?: Buffer; + bits?: number; + valid_from: string; + valid_to: string; + fingerprint: string; + fingerprint256: string; + fingerprint512: string; + ext_key_usage?: string[]; + serialNumber: string; + raw: Buffer; + asn1Curve?: string; + nistCurve?: string; + } + /* getPeerCertificate(true): each certificate links to its issuer; a + * self-issued root is its own issuerCertificate */ + export interface DetailedPeerCertificate extends PeerCertificate { + issuerCertificate: DetailedPeerCertificate; + } + + /** new tls.TLSSocket(socket, options): the server-mode wrap of an open + * socket (a server connection or an http CONNECT socket). The options + * record reads at runtime; isServer, cert, key, secureContext, + * ALPNProtocols, minVersion and maxVersion are implemented. */ + export interface TLSSocketOptions { + isServer?: boolean; + cert?: string | Uint8Array; + key?: string | Uint8Array; + secureContext?: SecureContext; + ALPNProtocols?: string[] | Uint8Array; + minVersion?: string; + maxVersion?: string; + [option: string]: unknown; + } + export const TLSSocket: new (socket: Socket, options: TLSSocketOptions) => TLSSocket; + export interface CipherNameAndProtocol { + name: string; + standardName: string; + version: string; + } + export const DEFAULT_MIN_VERSION: string; + export const DEFAULT_MAX_VERSION: string; + /** tls.connect options — the RUNTIME options record (members read at + * runtime; port/host/socket/rejectUnauthorized/ca/servername are the + * honestly-implemented set, every other documented member throws the + * runtime fence — the options-record stance's divergence-66 shape). */ + export interface ConnectionOptions { + port?: number; + host?: string; + /* an open socket to run the client handshake over */ + socket?: Socket; + rejectUnauthorized?: boolean; + ca?: string | Uint8Array | ReadonlyArray; + servername?: string; + [option: string]: unknown; + } + export function connect( + options: ConnectionOptions, + secureConnectListener?: () => void, + ): TLSSocket; + export function connect( + port: number, + options?: ConnectionOptions, + secureConnectListener?: () => void, + ): TLSSocket; + export function connect( + port: number, + host?: string, + options?: ConnectionOptions, + secureConnectListener?: () => void, + ): TLSSocket; + /** An opaque parsed cert/key pair — what an SNI callback answers with + * (the value is a runtime handle; the members are runtime-internal). */ + export interface SecureContext {} + /* @types/node's name for createSecureContext's options */ + export type SecureContextOptions = TlsOptions; + export function createSecureContext(options: TlsOptions): SecureContext; + /** The CA-store introspection surface (scr_tls_ca.c): per-type cached + * PEM string arrays. The host bundle stands in for Node's compiled-in + * Mozilla roots ('bundled', and rootCertificates) AND the platform + * store ('system') — the Windows certificate stores or POSIX bundle probe; + * 'extra' reads NODE_EXTRA_CA_CERTS. An unknown type string throws + * Node's ERR_INVALID_ARG_VALUE TypeError. */ + export function getCACertificates(type?: string): string[]; + export const rootCertificates: readonly string[]; + /** Replaces the 'default' set (deduped) and the trust anchors the TLS + * client verifies against; a certificate-free non-empty array throws + * Node's ERR_CRYPTO_OPERATION_FAILED Error. */ + export function setDefaultCACertificates(certs: readonly string[]): void; +} + +declare module "node:tls" { + export * from "tls"; +} diff --git a/packages/compiler/ambient/node-fallback/url.d.ts b/packages/compiler/ambient/node-fallback/url.d.ts new file mode 100644 index 000000000..c690c52b1 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/url.d.ts @@ -0,0 +1,95 @@ +/// + +/* node:url — the file-URL bridge pair. fileURLToPath accepts a URL value + * or a URL string and throws Node's TypeErrors (non-file scheme, encoded + * slashes, non-empty host); pathToFileURL resolves the path and percent- + * encodes it into a file: URL. The legacy API: parse() answers a Url + * object, format() serializes a Url-shaped object, a string, or a URL, + * resolve() resolves one URL string against another, the domain pair + * converts a hostname to and from its Punycode form, and urlToHttpOptions + * builds http.request options from a URL. */ +declare module "url" { + export type URL = globalThis.URL; + export type URLSearchParams = globalThis.URLSearchParams; + export const URL: typeof globalThis.URL; + export const URLSearchParams: typeof globalThis.URLSearchParams; + export function fileURLToPath(url: string | URL, options?: { windows?: boolean }): string; + export function fileURLToPathBuffer(url: string | URL, options?: { windows?: boolean }): Buffer; + export function pathToFileURL(path: string, options?: { windows?: boolean }): URL; + export interface UrlObject { + auth?: string | null | undefined; + hash?: string | null | undefined; + host?: string | null | undefined; + hostname?: string | null | undefined; + href?: string | null | undefined; + pathname?: string | null | undefined; + protocol?: string | null | undefined; + search?: string | null | undefined; + slashes?: boolean | null | undefined; + port?: string | number | null | undefined; + query?: string | null | import("querystring").ParsedUrlQueryInput | undefined; + } + export interface Url { + auth: string | null; + hash: string | null; + host: string | null; + hostname: string | null; + href: string; + path: string | null; + pathname: string | null; + protocol: string | null; + search: string | null; + slashes: boolean | null; + port: string | null; + query: string | null | import("querystring").ParsedUrlQuery; + } + export interface UrlWithParsedQuery extends Url { + query: import("querystring").ParsedUrlQuery; + } + export interface UrlWithStringQuery extends Url { + query: string | null; + } + export interface URLFormatOptions { + auth?: boolean | undefined; + fragment?: boolean | undefined; + search?: boolean | undefined; + unicode?: boolean | undefined; + } + export function parse( + urlString: string, + parseQueryString?: false, + slashesDenoteHost?: boolean, + ): UrlWithStringQuery; + export function parse( + urlString: string, + parseQueryString: true, + slashesDenoteHost?: boolean, + ): UrlWithParsedQuery; + export function parse( + urlString: string, + parseQueryString: boolean, + slashesDenoteHost?: boolean, + ): Url; + export function format(urlObject: URL, options?: URLFormatOptions): string; + export function format(urlObject: UrlObject | string): string; + export function resolve(from: string, to: string): string; + export function domainToASCII(domain: string): string; + export function domainToUnicode(domain: string): string; + /** urlToHttpOptions's null-prototype request options. */ + export interface UrlHttpOptions { + protocol: string; + hostname: string; + hash: string; + search: string; + pathname: string; + path: string; + href: string; + port?: number; + auth?: string; + } + export function urlToHttpOptions(url: URL): UrlHttpOptions; +} + +declare module "node:url" { + export * from "url"; +} diff --git a/packages/compiler/ambient/node-fallback/util-types.d.ts b/packages/compiler/ambient/node-fallback/util-types.d.ts new file mode 100644 index 000000000..3af1b6f00 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/util-types.d.ts @@ -0,0 +1,50 @@ +/* node:util/types — native runtime brands. Other forms retain their + * per-member lowering boundaries. */ +declare module "util/types" { + export function isAnyArrayBuffer(value: unknown): boolean; + export function isArrayBuffer(value: unknown): value is ArrayBuffer; + export function isArrayBufferView(value: unknown): value is ArrayBufferView; + export function isDataView(value: unknown): value is DataView; + export function isTypedArray(value: unknown): boolean; + export function isUint8Array(value: unknown): value is Uint8Array; + export function isUint8ClampedArray(value: unknown): value is Uint8ClampedArray; + export function isUint16Array(value: unknown): value is Uint16Array; + export function isUint32Array(value: unknown): value is Uint32Array; + export function isInt8Array(value: unknown): value is Int8Array; + export function isInt16Array(value: unknown): value is Int16Array; + export function isInt32Array(value: unknown): value is Int32Array; + export function isFloat16Array(value: unknown): value is Float16Array; + export function isFloat32Array(value: unknown): value is Float32Array; + export function isFloat64Array(value: unknown): value is Float64Array; + export function isMap(value: unknown): value is Map; + export function isSet(value: unknown): value is Set; + export function isWeakMap(value: unknown): value is WeakMap; + export function isWeakSet(value: unknown): value is WeakSet; + export function isRegExp(value: unknown): value is RegExp; + export function isNativeError(value: unknown): value is Error; + export function isPromise(value: unknown): value is Promise; + export function isProxy(value: unknown): boolean; + export function isAsyncFunction(value: unknown): boolean; + export function isGeneratorFunction(value: unknown): boolean; + export function isGeneratorObject(value: unknown): boolean; + export function isModuleNamespaceObject(value: unknown): boolean; + export function isDate(value: unknown): value is Date; + export function isSharedArrayBuffer(value: unknown): value is SharedArrayBuffer; + export function isMapIterator(value: unknown): boolean; + export function isSetIterator(value: unknown): boolean; + export function isKeyObject(value: unknown): boolean; + export function isNumberObject(value: unknown): boolean; + export function isStringObject(value: unknown): boolean; + export function isBooleanObject(value: unknown): boolean; + export function isBigIntObject(value: unknown): boolean; + export function isSymbolObject(value: unknown): boolean; + export function isBoxedPrimitive(value: unknown): boolean; + export function isBigInt64Array(value: unknown): value is BigInt64Array; + export function isBigUint64Array(value: unknown): value is BigUint64Array; + export function isCryptoKey(value: unknown): boolean; + export function isExternal(value: unknown): boolean; +} + +declare module "node:util/types" { + export * from "util/types"; +} diff --git a/packages/compiler/ambient/node-fallback/util.d.ts b/packages/compiler/ambient/node-fallback/util.d.ts new file mode 100644 index 000000000..5ff4c38ad --- /dev/null +++ b/packages/compiler/ambient/node-fallback/util.d.ts @@ -0,0 +1,270 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// + +/* node:util — compile-time projections for child_process.execFile, + * child_process.exec and fs.readFile. The first two retain Node's custom + * { stdout, stderr } result; the last resolves the callback's data + * argument. Other targets and bare + * promisify values fence per site. */ +declare module "util" { + export * as types from "util/types"; + export function getSystemErrorName(err: number): string; + export function getSystemErrorMessage(err: number): string; + export function getSystemErrorMap(): Map; + export function stripVTControlCharacters(str: string): string; + export function toUSVString(string: string): string; + export function isDeepStrictEqual(val1: unknown, val2: unknown, skipPrototype?: boolean): boolean; + export function styleText( + format: string | readonly string[], + text: string, + options?: { validateStream?: boolean; stream?: unknown }, + ): string; + export function parseEnv(content: string): { [key: string]: string | undefined }; + export function inherits(constructor: unknown, superConstructor: unknown): void; + export interface DeprecateOptions { + modifyPrototype?: boolean; + } + export function deprecate( + fn: T, + msg: string, + code?: string, + options?: DeprecateOptions, + ): T; + /* The callback receives null on success and the rejection reason + * otherwise (falsy reasons arrive as ERR_FALSY_VALUE_REJECTION). */ + export function callbackify( + fn: () => Promise, + ): (callback: (err: NodeJS.ErrnoException | null) => void) => void; + export function callbackify( + fn: () => Promise, + ): (callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void; + export function callbackify( + fn: (arg1: T1) => Promise, + ): (arg1: T1, callback: (err: NodeJS.ErrnoException | null) => void) => void; + export function callbackify( + fn: (arg1: T1) => Promise, + ): (arg1: T1, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2) => Promise, + ): (arg1: T1, arg2: T2, callback: (err: NodeJS.ErrnoException | null) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2) => Promise, + ): ( + arg1: T1, + arg2: T2, + callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, + ) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3) => Promise, + ): (arg1: T1, arg2: T2, arg3: T3, callback: (err: NodeJS.ErrnoException | null) => void) => void; + export function callbackify( + fn: (arg1: T1, arg2: T2, arg3: T3) => Promise, + ): ( + arg1: T1, + arg2: T2, + arg3: T3, + callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, + ) => void; + export type DebugLoggerFunction = (msg: string, ...param: unknown[]) => void; + export interface DebugLogger extends DebugLoggerFunction { + enabled: boolean; + } + export function debuglog( + section: string, + callback?: (fn: DebugLoggerFunction) => void, + ): DebugLogger; + export { debuglog as debug }; + export type TextEncoder = globalThis.TextEncoder; + export type TextDecoder = globalThis.TextDecoder; + export var TextEncoder: typeof globalThis.TextEncoder; + export var TextDecoder: typeof globalThis.TextDecoder; + /* stream.pipeline's overload comes first: its __promisify__ member keeps + * every other target out of it. */ + export function promisify( + fn: typeof import("node:stream").pipeline, + ): typeof import("node:stream/promises").pipeline; + export function promisify( + fn: typeof import("node:fs").readFile, + ): (path: string, encoding: "utf8" | "utf-8") => Promise; + export function promisify( + fn: (file: string, args?: string[] | null, options?: object) => unknown, + ): ( + file: string, + args?: string[], + options?: { + encoding?: "utf8" | "utf-8"; + cwd?: string; + env?: { [k: string]: string | undefined }; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + windowsHide?: boolean; + }, + ) => Promise<{ stdout: string; stderr: string }>; + export function promisify( + fn: typeof globalThis.setTimeout, + ): typeof import("node:timers/promises").setTimeout; + export function promisify( + fn: typeof globalThis.setImmediate, + ): typeof import("node:timers/promises").setImmediate; + export function promisify(fn: typeof import("node:child_process").exec): ( + command: string, + options?: { + encoding?: "utf8" | "utf-8"; + cwd?: string; + env?: { [k: string]: string | undefined }; + timeout?: number; + maxBuffer?: number; + killSignal?: string; + windowsHide?: boolean; + }, + ) => Promise<{ stdout: string; stderr: string }>; + /* The zlib callback one-shots (deflate, inflate, deflateRaw, inflateRaw, + * gzip, gunzip, unzip) share one type. */ + export function promisify( + fn: typeof import("node:zlib").gzip, + ): (data: string | Uint8Array, options?: import("node:zlib").ZlibOptions) => Promise; + export function promisify( + fn: typeof import("node:stream").finished, + ): typeof import("node:stream/promises").finished; + /* The crypto callback functions: the promise settles with the callback's + * value. */ + export function promisify( + fn: typeof import("node:crypto").randomBytes, + ): (size: number) => Promise; + export function promisify( + fn: typeof import("node:crypto").pbkdf2, + ): ( + password: string | Uint8Array, + salt: string | Uint8Array, + iterations: number, + keylen: number, + digest: string, + ) => Promise; + export function promisify( + fn: typeof import("node:crypto").scrypt, + ): ( + password: import("node:crypto").BinaryLike, + salt: import("node:crypto").BinaryLike, + keylen: number, + options?: import("node:crypto").ScryptOptions, + ) => Promise; + export function promisify( + fn: typeof import("node:crypto").hkdf, + ): ( + digest: string, + ikm: import("node:crypto").BinaryLike | import("node:crypto").KeyObject, + salt: import("node:crypto").BinaryLike, + info: import("node:crypto").BinaryLike, + keylen: number, + ) => Promise; + /* A callback-only function (`obj.close.bind(obj)`): before the targets + * whose leading parameters would otherwise capture it. Builtins that take + * arguments before their callback never match these. */ + export function promisify( + fn: (callback: (err: any, result: TResult) => void) => void, + ): () => Promise; + export function promisify(fn: (callback: (err?: any) => void) => void): () => Promise; + export function promisify( + fn: typeof import("node:crypto").randomFill, + ): ( + buffer: T, + offset?: number, + size?: number, + ) => Promise; + export function promisify(fn: typeof import("node:crypto").randomInt): { + (max: number): Promise; + (min: number, max: number): Promise; + }; + export function promisify( + fn: typeof import("node:crypto").generateKey, + ): ( + type: "hmac" | "aes", + options: { length: number }, + ) => Promise; + /* dns.lookup's promise settles with { address, family } (the address + * array with options.all), and lookupService's with { hostname, service }. */ + export function promisify(fn: typeof import("node:dns").lookup): { + ( + hostname: string, + options: import("node:dns").LookupAllOptions, + ): Promise; + ( + hostname: string, + options?: number | import("node:dns").LookupOneOptions, + ): Promise; + }; + export function promisify( + fn: typeof import("node:dns").lookupService, + ): (address: string, port: number) => Promise<{ hostname: string; service: string }>; + /* Declared surface without full lowering (the type-level tolerance + * story: guarded/diagnostic-path code must TYPECHECK; a reached use + * without a lowering fences at its site). inspect's options carry the + * documented knobs through an index signature. */ + export function inspect( + object: unknown, + options?: { depth?: number | null; colors?: boolean; [k: string]: unknown }, + ): string; + export function format(format?: unknown, ...args: unknown[]): string; + export function formatWithOptions( + inspectOptions: { depth?: number | null; colors?: boolean; [k: string]: unknown }, + format?: unknown, + ...args: unknown[] + ): string; + export type ParseArgsOptionsType = "boolean" | "string"; + export interface ParseArgsOptionDescriptor { + type: ParseArgsOptionsType; + multiple?: boolean; + short?: string; + default?: string | boolean | string[] | boolean[]; + } + export interface ParseArgsOptionsConfig { + [longOption: string]: ParseArgsOptionDescriptor; + } + export interface ParseArgsConfig { + args?: readonly string[]; + options?: ParseArgsOptionsConfig; + strict?: boolean; + allowPositionals?: boolean; + allowNegative?: boolean; + tokens?: boolean; + } + export type ParseArgsToken = + | { + kind: "option"; + index: number; + name: string; + rawName: string; + value: string | undefined; + inlineValue: boolean | undefined; + } + | { kind: "positional"; index: number; value: string } + | { kind: "option-terminator"; index: number }; + export interface ParseArgsResult { + values: { [longOption: string]: undefined | string | boolean | Array }; + positionals: string[]; + tokens?: ParseArgsToken[]; + } + export function parseArgs(config?: ParseArgsConfig): ParseArgsResult; + /* util.getCallSites (Node ≥22.9): the captured-frame slice harness + * code reads. No stack bookkeeping exists in a compiled binary — every + * reached call fences per site. */ + export interface CallSite { + readonly functionName: string; + readonly scriptName: string; + readonly lineNumber: number; + readonly column: number; + } + export function getCallSites(frameCount?: number): CallSite[]; +} + +declare module "node:util" { + export * from "util"; +} diff --git a/packages/compiler/ambient/node-fallback/v8.d.ts b/packages/compiler/ambient/node-fallback/v8.d.ts new file mode 100644 index 000000000..16b112b1c --- /dev/null +++ b/packages/compiler/ambient/node-fallback/v8.d.ts @@ -0,0 +1,43 @@ +/* node:v8 — importable for the packages that require it at top level. + * getHeapStatistics answers allocator-derived figures in Node's shape + * (approximate: the runtime heap's total and used bytes, the machine's + * memory as the limit; V8-only figures are 0). The other members observe + * V8 itself and fence at their use with the by-design reason; they are + * declared so programs that type-check under their own tsc reach that + * fence. */ +declare module "v8" { + type DoesZapCodeSpaceFlag = 0 | 1; + export interface HeapInfo { + total_heap_size: number; + total_heap_size_executable: number; + total_physical_size: number; + total_available_size: number; + used_heap_size: number; + heap_size_limit: number; + malloced_memory: number; + peak_malloced_memory: number; + does_zap_garbage: DoesZapCodeSpaceFlag; + number_of_native_contexts: number; + number_of_detached_contexts: number; + total_global_handles_size: number; + used_global_handles_size: number; + external_memory: number; + } + export function getHeapStatistics(): HeapInfo; + export function cachedDataVersionTag(): number; + export function getHeapSpaceStatistics(): unknown[]; + export function getHeapCodeStatistics(): unknown; + export function getHeapSnapshot(options?: unknown): unknown; + export function writeHeapSnapshot(fileName?: string, options?: unknown): string; + export function setFlagsFromString(flags: string): void; + export function setHeapSnapshotNearHeapLimit(limit: number): void; + export function serialize(value: unknown): Buffer; + export function deserialize(buffer: Uint8Array): unknown; + export function takeCoverage(): void; + export function stopCoverage(): void; + export function isStringOneByteRepresentation(content: string): boolean; +} + +declare module "node:v8" { + export * from "v8"; +} diff --git a/packages/compiler/ambient/node-fallback/worker_threads.d.ts b/packages/compiler/ambient/node-fallback/worker_threads.d.ts new file mode 100644 index 000000000..81873f115 --- /dev/null +++ b/packages/compiler/ambient/node-fallback/worker_threads.d.ts @@ -0,0 +1,113 @@ +/// + +/* node:worker_threads — native workers use statically compiled entry points. + * Lowering and runtime diagnostics define the supported options and methods. */ +declare module "worker_threads" { + export const isMainThread: boolean; + export const isInternalThread: boolean; + export const threadId: number; + export interface ResourceLimits { + maxYoungGenerationSizeMb?: number; + maxOldGenerationSizeMb?: number; + codeRangeSizeMb?: number; + stackSizeMb?: number; + } + /* {} in the main thread */ + export const resourceLimits: ResourceLimits; + /* the Worker's name option, trimmed ("WorkerThread" by default); "" in the main thread */ + export const threadName: string | null; + export interface WorkerOptions { + workerData?: unknown; + transferList?: readonly unknown[]; + argv?: readonly unknown[]; + name?: string; + resourceLimits?: ResourceLimits; + stdout?: boolean; + stderr?: boolean; + stdin?: boolean; + [option: string]: unknown; + } + export class Worker { + constructor(filename: string | URL, options?: WorkerOptions); + readonly threadId: number; + readonly resourceLimits?: ResourceLimits; + readonly threadName: string | null; + /* with the stdout/stderr options: the worker's writes to those streams */ + readonly stdout: import("stream").Readable; + readonly stderr: import("stream").Readable; + /* with the stdin option: what the worker reads from process.stdin */ + readonly stdin: import("stream").Writable | null; + /* terminate(), resolving with undefined once the worker exited */ + [Symbol.asyncDispose](): Promise; + on(event: "online", listener: () => void): this; + on(event: "message", listener: (value: any) => void): this; + on(event: "error", listener: (error: any) => void): this; + on(event: "exit", listener: (code: number) => void): this; + on(event: string, listener: (...args: any[]) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + postMessage(value: unknown, transferList?: readonly unknown[]): void; + ref(): void; + unref(): void; + terminate(): Promise; + removeAllListeners(event?: string): this; + listenerCount(event: string, listener?: (...args: any[]) => void): number; + } + export class MessagePort { + on(event: "message", listener: (value: any) => void): this; + on(event: "close", listener: () => void): this; + on(event: string, listener: (...args: any[]) => void): this; + once(event: string, listener: (...args: any[]) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + removeListener(event: string, listener: (...args: any[]) => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependOnceListener(event: string, listener: (...args: any[]) => void): this; + postMessage(value: unknown, transferList?: readonly unknown[]): void; + start(): void; + close(callback?: () => void): void; + ref(): void; + unref(): void; + hasRef(): boolean; + removeAllListeners(event?: string): this; + listenerCount(event: string, listener?: (...args: any[]) => void): number; + } + /* Two entangled ports; a port moves to another thread through a + * postMessage or workerData transferList. */ + export class MessageChannel { + readonly port1: MessagePort; + readonly port2: MessagePort; + } + export function receiveMessageOnPort(port: MessagePort): { message: any } | undefined; + /* This thread's environment data; each new Worker starts from a clone. */ + export function setEnvironmentData(key: any, value?: any): void; + export function getEnvironmentData(key: any): any; + /* A marked object cannot be listed in a transfer list. */ + export function markAsUntransferable(object: any): void; + /* structuredClone and postMessage throw DataCloneError for a marked + * object, error or class instance */ + export function markAsUncloneable(object: any): void; + export function isMarkedAsUntransferable(object: any): boolean; + /* Delivers each message to every other open channel with the name, in + * any thread, as a 'message' MessageEvent. */ + export class BroadcastChannel extends EventTarget { + constructor(name: string); + readonly name: string; + onmessage: ((event: MessageEvent) => void) | null; + onmessageerror: ((event: MessageEvent) => void) | null; + postMessage(message: unknown): void; + close(): void; + ref(): this; + unref(): this; + } + export const parentPort: MessagePort | null; + export const workerData: any; +} + +declare module "node:worker_threads" { + export * from "worker_threads"; +} diff --git a/packages/compiler/ambient/node-fallback/zlib.d.ts b/packages/compiler/ambient/node-fallback/zlib.d.ts new file mode 100644 index 000000000..c601596fe --- /dev/null +++ b/packages/compiler/ambient/node-fallback/zlib.d.ts @@ -0,0 +1,422 @@ +/// + +/* node:zlib — the one-shot zlib/raw/gzip codecs lower for string/Buffer + * inputs with Node's default options; libz links only into zlib-using + * binaries. Callback forms use the executable worker pool. Options stay + * declared so reached uses receive a named SC2020 refusal. The Deflate, + * Inflate, Gzip, Gunzip, DeflateRaw, InflateRaw, Unzip, BrotliCompress + * and BrotliDecompress streams are runtime Transform streams; Zstd remains + * outside the lowered surface. */ +declare module "zlib" { + import { Transform } from "stream"; + export interface ZlibOptions { + [option: string]: unknown; + } + export type ZlibCallback = (error: NodeJS.ErrnoException | null, result: Buffer) => void; + export function deflate(data: string | Uint8Array, callback: ZlibCallback): void; + export function deflate( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function deflateSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function inflate(data: string | Uint8Array, callback: ZlibCallback): void; + export function inflate( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function inflateSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function deflateRaw(data: string | Uint8Array, callback: ZlibCallback): void; + export function deflateRaw( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function deflateRawSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function inflateRaw(data: string | Uint8Array, callback: ZlibCallback): void; + export function inflateRaw( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function inflateRawSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function gzip(data: string | Uint8Array, callback: ZlibCallback): void; + export function gzip( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function gzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function gunzip(data: string | Uint8Array, callback: ZlibCallback): void; + export function gunzip( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function gunzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function unzip(data: string | Uint8Array, callback: ZlibCallback): void; + export function unzip( + data: string | Uint8Array, + options: ZlibOptions, + callback: ZlibCallback, + ): void; + export function unzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; + export function crc32(data: string | Uint8Array, value?: number): number; + /* Brotli over the vendored brotli 1.2.0 (scr_brotli.c): `params` keys + * are the numeric BROTLI_PARAM_* and BROTLI_DECODER_PARAM_* constants. */ + export interface BrotliOptions { + [option: string]: unknown; + } + export function brotliCompress(data: string | Uint8Array, callback: ZlibCallback): void; + export function brotliCompress( + data: string | Uint8Array, + options: BrotliOptions, + callback: ZlibCallback, + ): void; + export function brotliCompressSync(data: string | Uint8Array, options?: BrotliOptions): Buffer; + export function brotliDecompress(data: string | Uint8Array, callback: ZlibCallback): void; + export function brotliDecompress( + data: string | Uint8Array, + options: BrotliOptions, + callback: ZlibCallback, + ): void; + export function brotliDecompressSync(data: string | Uint8Array, options?: BrotliOptions): Buffer; + /* The stream classes: runtime Transform streams over the zlib engine + * (scr_zlib_stream.c). createGzip(options) and new Gzip(options) build + * the same stream; options are validated at runtime with Node's rules. + * Brotli streams have no params(), as in Node. */ + class ZlibBase extends Transform { + readonly bytesWritten: number; + flush(kind?: number, callback?: () => void): void; + flush(callback: () => void): void; + close(callback?: () => void): void; + reset(): void; + } + class Zlib extends ZlibBase { + params(level: number, strategy: number, callback?: () => void): void; + } + export class BrotliCompress extends ZlibBase { + constructor(options?: BrotliOptions); + } + export class BrotliDecompress extends ZlibBase { + constructor(options?: BrotliOptions); + } + export function createBrotliCompress(options?: BrotliOptions): BrotliCompress; + export function createBrotliDecompress(options?: BrotliOptions): BrotliDecompress; + export class Deflate extends Zlib { + constructor(options?: ZlibOptions); + } + export class Inflate extends Zlib { + constructor(options?: ZlibOptions); + } + export class Gzip extends Zlib { + constructor(options?: ZlibOptions); + } + export class Gunzip extends Zlib { + constructor(options?: ZlibOptions); + } + export class DeflateRaw extends Zlib { + constructor(options?: ZlibOptions); + } + export class InflateRaw extends Zlib { + constructor(options?: ZlibOptions); + } + export class Unzip extends Zlib { + constructor(options?: ZlibOptions); + } + export function createDeflate(options?: ZlibOptions): Deflate; + export function createInflate(options?: ZlibOptions): Inflate; + export function createGzip(options?: ZlibOptions): Gzip; + export function createGunzip(options?: ZlibOptions): Gunzip; + export function createDeflateRaw(options?: ZlibOptions): DeflateRaw; + export function createInflateRaw(options?: ZlibOptions): InflateRaw; + export function createUnzip(options?: ZlibOptions): Unzip; + /* Node v24 zlib.constants, literal-typed (the CryptoConstants stance): + * every access bakes as its literal; the object never materializes. The + * members outside BROTLI_* are also module properties (zlib.Z_SYNC_FLUSH). */ + export interface ZlibConstants { + readonly Z_NO_FLUSH: 0; + readonly Z_PARTIAL_FLUSH: 1; + readonly Z_SYNC_FLUSH: 2; + readonly Z_FULL_FLUSH: 3; + readonly Z_FINISH: 4; + readonly Z_BLOCK: 5; + readonly Z_OK: 0; + readonly Z_STREAM_END: 1; + readonly Z_NEED_DICT: 2; + readonly Z_ERRNO: -1; + readonly Z_STREAM_ERROR: -2; + readonly Z_DATA_ERROR: -3; + readonly Z_MEM_ERROR: -4; + readonly Z_BUF_ERROR: -5; + readonly Z_VERSION_ERROR: -6; + readonly Z_NO_COMPRESSION: 0; + readonly Z_BEST_SPEED: 1; + readonly Z_BEST_COMPRESSION: 9; + readonly Z_DEFAULT_COMPRESSION: -1; + readonly Z_FILTERED: 1; + readonly Z_HUFFMAN_ONLY: 2; + readonly Z_RLE: 3; + readonly Z_FIXED: 4; + readonly Z_DEFAULT_STRATEGY: 0; + readonly ZLIB_VERNUM: 4897; + readonly DEFLATE: 1; + readonly INFLATE: 2; + readonly GZIP: 3; + readonly GUNZIP: 4; + readonly DEFLATERAW: 5; + readonly INFLATERAW: 6; + readonly UNZIP: 7; + readonly BROTLI_DECODE: 8; + readonly BROTLI_ENCODE: 9; + readonly ZSTD_DECOMPRESS: 11; + readonly ZSTD_COMPRESS: 10; + readonly Z_MIN_WINDOWBITS: 8; + readonly Z_MAX_WINDOWBITS: 15; + readonly Z_DEFAULT_WINDOWBITS: 15; + readonly Z_MIN_CHUNK: 64; + readonly Z_MAX_CHUNK: number; + readonly Z_DEFAULT_CHUNK: 16384; + readonly Z_MIN_MEMLEVEL: 1; + readonly Z_MAX_MEMLEVEL: 9; + readonly Z_DEFAULT_MEMLEVEL: 8; + readonly Z_MIN_LEVEL: -1; + readonly Z_MAX_LEVEL: 9; + readonly Z_DEFAULT_LEVEL: -1; + readonly BROTLI_OPERATION_PROCESS: 0; + readonly BROTLI_OPERATION_FLUSH: 1; + readonly BROTLI_OPERATION_FINISH: 2; + readonly BROTLI_OPERATION_EMIT_METADATA: 3; + readonly BROTLI_PARAM_MODE: 0; + readonly BROTLI_MODE_GENERIC: 0; + readonly BROTLI_MODE_TEXT: 1; + readonly BROTLI_MODE_FONT: 2; + readonly BROTLI_DEFAULT_MODE: 0; + readonly BROTLI_PARAM_QUALITY: 1; + readonly BROTLI_MIN_QUALITY: 0; + readonly BROTLI_MAX_QUALITY: 11; + readonly BROTLI_DEFAULT_QUALITY: 11; + readonly BROTLI_PARAM_LGWIN: 2; + readonly BROTLI_MIN_WINDOW_BITS: 10; + readonly BROTLI_MAX_WINDOW_BITS: 24; + readonly BROTLI_LARGE_MAX_WINDOW_BITS: 30; + readonly BROTLI_DEFAULT_WINDOW: 22; + readonly BROTLI_PARAM_LGBLOCK: 3; + readonly BROTLI_MIN_INPUT_BLOCK_BITS: 16; + readonly BROTLI_MAX_INPUT_BLOCK_BITS: 24; + readonly BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING: 4; + readonly BROTLI_PARAM_SIZE_HINT: 5; + readonly BROTLI_PARAM_LARGE_WINDOW: 6; + readonly BROTLI_PARAM_NPOSTFIX: 7; + readonly BROTLI_PARAM_NDIRECT: 8; + readonly BROTLI_DECODER_RESULT_ERROR: 0; + readonly BROTLI_DECODER_RESULT_SUCCESS: 1; + readonly BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 2; + readonly BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 3; + readonly BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: 0; + readonly BROTLI_DECODER_PARAM_LARGE_WINDOW: 1; + readonly BROTLI_DECODER_NO_ERROR: 0; + readonly BROTLI_DECODER_SUCCESS: 1; + readonly BROTLI_DECODER_NEEDS_MORE_INPUT: 2; + readonly BROTLI_DECODER_NEEDS_MORE_OUTPUT: 3; + readonly BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE: -1; + readonly BROTLI_DECODER_ERROR_FORMAT_RESERVED: -2; + readonly BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE: -3; + readonly BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET: -4; + readonly BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME: -5; + readonly BROTLI_DECODER_ERROR_FORMAT_CL_SPACE: -6; + readonly BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE: -7; + readonly BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT: -8; + readonly BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1: -9; + readonly BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2: -10; + readonly BROTLI_DECODER_ERROR_FORMAT_TRANSFORM: -11; + readonly BROTLI_DECODER_ERROR_FORMAT_DICTIONARY: -12; + readonly BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS: -13; + readonly BROTLI_DECODER_ERROR_FORMAT_PADDING_1: -14; + readonly BROTLI_DECODER_ERROR_FORMAT_PADDING_2: -15; + readonly BROTLI_DECODER_ERROR_FORMAT_DISTANCE: -16; + readonly BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET: -19; + readonly BROTLI_DECODER_ERROR_INVALID_ARGUMENTS: -20; + readonly BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES: -21; + readonly BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS: -22; + readonly BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP: -25; + readonly BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1: -26; + readonly BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2: -27; + readonly BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES: -30; + readonly BROTLI_DECODER_ERROR_UNREACHABLE: -31; + readonly ZSTD_e_continue: 0; + readonly ZSTD_e_flush: 1; + readonly ZSTD_e_end: 2; + readonly ZSTD_fast: 1; + readonly ZSTD_dfast: 2; + readonly ZSTD_greedy: 3; + readonly ZSTD_lazy: 4; + readonly ZSTD_lazy2: 5; + readonly ZSTD_btlazy2: 6; + readonly ZSTD_btopt: 7; + readonly ZSTD_btultra: 8; + readonly ZSTD_btultra2: 9; + readonly ZSTD_c_compressionLevel: 100; + readonly ZSTD_c_windowLog: 101; + readonly ZSTD_c_hashLog: 102; + readonly ZSTD_c_chainLog: 103; + readonly ZSTD_c_searchLog: 104; + readonly ZSTD_c_minMatch: 105; + readonly ZSTD_c_targetLength: 106; + readonly ZSTD_c_strategy: 107; + readonly ZSTD_c_enableLongDistanceMatching: 160; + readonly ZSTD_c_ldmHashLog: 161; + readonly ZSTD_c_ldmMinMatch: 162; + readonly ZSTD_c_ldmBucketSizeLog: 163; + readonly ZSTD_c_ldmHashRateLog: 164; + readonly ZSTD_c_contentSizeFlag: 200; + readonly ZSTD_c_checksumFlag: 201; + readonly ZSTD_c_dictIDFlag: 202; + readonly ZSTD_c_nbWorkers: 400; + readonly ZSTD_c_jobSize: 401; + readonly ZSTD_c_overlapLog: 402; + readonly ZSTD_d_windowLogMax: 100; + readonly ZSTD_CLEVEL_DEFAULT: 3; + readonly ZSTD_error_no_error: 0; + readonly ZSTD_error_GENERIC: 1; + readonly ZSTD_error_prefix_unknown: 10; + readonly ZSTD_error_version_unsupported: 12; + readonly ZSTD_error_frameParameter_unsupported: 14; + readonly ZSTD_error_frameParameter_windowTooLarge: 16; + readonly ZSTD_error_corruption_detected: 20; + readonly ZSTD_error_checksum_wrong: 22; + readonly ZSTD_error_literals_headerWrong: 24; + readonly ZSTD_error_dictionary_corrupted: 30; + readonly ZSTD_error_dictionary_wrong: 32; + readonly ZSTD_error_dictionaryCreation_failed: 34; + readonly ZSTD_error_parameter_unsupported: 40; + readonly ZSTD_error_parameter_combination_unsupported: 41; + readonly ZSTD_error_parameter_outOfBound: 42; + readonly ZSTD_error_tableLog_tooLarge: 44; + readonly ZSTD_error_maxSymbolValue_tooLarge: 46; + readonly ZSTD_error_maxSymbolValue_tooSmall: 48; + readonly ZSTD_error_stabilityCondition_notRespected: 50; + readonly ZSTD_error_stage_wrong: 60; + readonly ZSTD_error_init_missing: 62; + readonly ZSTD_error_memory_allocation: 64; + readonly ZSTD_error_workSpace_tooSmall: 66; + readonly ZSTD_error_dstSize_tooSmall: 70; + readonly ZSTD_error_srcSize_wrong: 72; + readonly ZSTD_error_dstBuffer_null: 74; + readonly ZSTD_error_noForwardProgress_destFull: 80; + readonly ZSTD_error_noForwardProgress_inputEmpty: 82; + } + export const constants: ZlibConstants; + export const Z_NO_FLUSH: 0; + export const Z_PARTIAL_FLUSH: 1; + export const Z_SYNC_FLUSH: 2; + export const Z_FULL_FLUSH: 3; + export const Z_FINISH: 4; + export const Z_BLOCK: 5; + export const Z_OK: 0; + export const Z_STREAM_END: 1; + export const Z_NEED_DICT: 2; + export const Z_ERRNO: -1; + export const Z_STREAM_ERROR: -2; + export const Z_DATA_ERROR: -3; + export const Z_MEM_ERROR: -4; + export const Z_BUF_ERROR: -5; + export const Z_VERSION_ERROR: -6; + export const Z_NO_COMPRESSION: 0; + export const Z_BEST_SPEED: 1; + export const Z_BEST_COMPRESSION: 9; + export const Z_DEFAULT_COMPRESSION: -1; + export const Z_FILTERED: 1; + export const Z_HUFFMAN_ONLY: 2; + export const Z_RLE: 3; + export const Z_FIXED: 4; + export const Z_DEFAULT_STRATEGY: 0; + export const ZLIB_VERNUM: 4897; + export const DEFLATE: 1; + export const INFLATE: 2; + export const GZIP: 3; + export const GUNZIP: 4; + export const DEFLATERAW: 5; + export const INFLATERAW: 6; + export const UNZIP: 7; + export const ZSTD_DECOMPRESS: 11; + export const ZSTD_COMPRESS: 10; + export const Z_MIN_WINDOWBITS: 8; + export const Z_MAX_WINDOWBITS: 15; + export const Z_DEFAULT_WINDOWBITS: 15; + export const Z_MIN_CHUNK: 64; + export const Z_MAX_CHUNK: number; + export const Z_DEFAULT_CHUNK: 16384; + export const Z_MIN_MEMLEVEL: 1; + export const Z_MAX_MEMLEVEL: 9; + export const Z_DEFAULT_MEMLEVEL: 8; + export const Z_MIN_LEVEL: -1; + export const Z_MAX_LEVEL: 9; + export const Z_DEFAULT_LEVEL: -1; + export const ZSTD_e_continue: 0; + export const ZSTD_e_flush: 1; + export const ZSTD_e_end: 2; + export const ZSTD_fast: 1; + export const ZSTD_dfast: 2; + export const ZSTD_greedy: 3; + export const ZSTD_lazy: 4; + export const ZSTD_lazy2: 5; + export const ZSTD_btlazy2: 6; + export const ZSTD_btopt: 7; + export const ZSTD_btultra: 8; + export const ZSTD_btultra2: 9; + export const ZSTD_c_compressionLevel: 100; + export const ZSTD_c_windowLog: 101; + export const ZSTD_c_hashLog: 102; + export const ZSTD_c_chainLog: 103; + export const ZSTD_c_searchLog: 104; + export const ZSTD_c_minMatch: 105; + export const ZSTD_c_targetLength: 106; + export const ZSTD_c_strategy: 107; + export const ZSTD_c_enableLongDistanceMatching: 160; + export const ZSTD_c_ldmHashLog: 161; + export const ZSTD_c_ldmMinMatch: 162; + export const ZSTD_c_ldmBucketSizeLog: 163; + export const ZSTD_c_ldmHashRateLog: 164; + export const ZSTD_c_contentSizeFlag: 200; + export const ZSTD_c_checksumFlag: 201; + export const ZSTD_c_dictIDFlag: 202; + export const ZSTD_c_nbWorkers: 400; + export const ZSTD_c_jobSize: 401; + export const ZSTD_c_overlapLog: 402; + export const ZSTD_d_windowLogMax: 100; + export const ZSTD_CLEVEL_DEFAULT: 3; + export const ZSTD_error_no_error: 0; + export const ZSTD_error_GENERIC: 1; + export const ZSTD_error_prefix_unknown: 10; + export const ZSTD_error_version_unsupported: 12; + export const ZSTD_error_frameParameter_unsupported: 14; + export const ZSTD_error_frameParameter_windowTooLarge: 16; + export const ZSTD_error_corruption_detected: 20; + export const ZSTD_error_checksum_wrong: 22; + export const ZSTD_error_literals_headerWrong: 24; + export const ZSTD_error_dictionary_corrupted: 30; + export const ZSTD_error_dictionary_wrong: 32; + export const ZSTD_error_dictionaryCreation_failed: 34; + export const ZSTD_error_parameter_unsupported: 40; + export const ZSTD_error_parameter_combination_unsupported: 41; + export const ZSTD_error_parameter_outOfBound: 42; + export const ZSTD_error_tableLog_tooLarge: 44; + export const ZSTD_error_maxSymbolValue_tooLarge: 46; + export const ZSTD_error_maxSymbolValue_tooSmall: 48; + export const ZSTD_error_stabilityCondition_notRespected: 50; + export const ZSTD_error_stage_wrong: 60; + export const ZSTD_error_init_missing: 62; + export const ZSTD_error_memory_allocation: 64; + export const ZSTD_error_workSpace_tooSmall: 66; + export const ZSTD_error_dstSize_tooSmall: 70; + export const ZSTD_error_srcSize_wrong: 72; + export const ZSTD_error_dstBuffer_null: 74; + export const ZSTD_error_noForwardProgress_destFull: 80; + export const ZSTD_error_noForwardProgress_inputEmpty: 82; +} + +declare module "node:zlib" { + export * from "zlib"; +} diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index 058b262ea..dff6b6b05 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -416,17 +416,6 @@ declare var process: { once(event: "disconnect", listener: () => void): void; }; -/* Node's process module default export is the global process object. These - * declaration aliases let the ESM spelling typecheck against the same - * deliberately narrow fallback surface when @types/node is absent. */ -declare module "process" { - export = process; -} -declare module "node:process" { - import process = require("process"); - export = process; -} - /* ── globals a real CLI's sources reference (all @types/node or dyn-lib * territory; the fallback declares the slice so projects PREFLIGHT and * every reached use lands on the SC2020 fence — or a real lowering where @@ -1292,150 +1281,6 @@ declare function setInterval( ): Timeout; declare function clearInterval(handle?: Timeout | number | null | undefined): void; -/* node:timers — the module twins of the timer globals (Node's timers - * module re-exports them: require('timers').setTimeout IS setTimeout). - * Named imports, destructured requires, and namespace calls all lower to - * the same libCalls as the globals; the surface beyond these six - * (enroll/unenroll, promises) fences per member. */ -declare module "node:timers" { - export function setTimeout(callback: () => void, ms?: number): Timeout; - export function setTimeout( - callback: (...args: never[]) => void, - ms?: number, - ...args: unknown[] - ): Timeout; - export function clearTimeout(handle?: Timeout | number | null | undefined): void; - export function setInterval(callback: () => void, ms?: number): Timeout; - export function setInterval( - callback: (...args: never[]) => void, - ms?: number, - ...args: unknown[] - ): Timeout; - export function clearInterval(handle?: Timeout | number | null | undefined): void; - export function setImmediate(callback: () => void): Immediate; - export function setImmediate(callback: (...args: never[]) => void, ...args: unknown[]): Immediate; - export function clearImmediate(handle?: Immediate | null | undefined): void; -} -declare module "timers" { - export * from "node:timers"; -} - -/* node:timers/promises — the promisified pair the Node test harness leans - * on (`await setTimeout(ms)` as a sleep). setTimeout and setImmediate - * settle with their resolve value (undefined without one) from the shared - * timer heap and the immediate queue; setInterval(delay, value) lowers to a - * typed async iterator. The options object takes `ref`; AbortSignal - * options remain declared surface that fences per site. */ -declare module "node:timers/promises" { - export interface TimerOptions { - ref?: boolean; - signal?: AbortSignal; - } - export function setTimeout( - delay?: number, - value?: T, - options?: TimerOptions, - ): Promise; - export function setImmediate(value?: T, options?: TimerOptions): Promise; - export function setInterval( - delay?: number, - value?: T, - options?: unknown, - ): AsyncGenerator; - export const scheduler: { - wait(delay?: number, options?: TimerOptions): Promise; - yield(): Promise; - }; -} -declare module "timers/promises" { - export * from "node:timers/promises"; -} - -/* node:diagnostics_channel — the in-process pub/sub core: named channels, - * subscribe/unsubscribe (module-level and channel-level), publish with any - * dyn-convertible message. Channel values are f64 handles into the - * runtime's channel registry (the readline.Interface precedent — channels - * are process-lived in Node too, its WeakRef machinery aside). - * The store-binding surface (bindStore/unbindStore/runStores) lowers onto - * the AsyncLocalStorage integration (dc.chan* libCalls). - * - * TracingChannel (tracingChannel) is the five-event-channel collection: - * an f64 handle into the runtime's tracing registry (type-mapper.ts maps the - * name like Channel). subscribe/unsubscribe walk a handlers object; - * traceSync/traceCallback run Node's publish choreography in the runtime - * (context defaults to a fresh {}); tracePromise is declared (the member - * exists in Node) but fences at the call site — the traced function's - * promise cannot cross the checked-dynamic tree boundary yet. */ -declare module "node:diagnostics_channel" { - export type ChannelListener = (message: unknown, name: string) => void; - export interface Channel { - readonly name: string; - readonly hasSubscribers: boolean; - publish(message: unknown): void; - subscribe(onMessage: ChannelListener): void; - unsubscribe(onMessage: ChannelListener): boolean; - bindStore( - store: import("async_hooks").AsyncLocalStorage, - transform?: (context: any) => any, - ): void; - unbindStore(store: import("async_hooks").AsyncLocalStorage): boolean; - runStores(context: unknown, fn: (...args: any[]) => any, thisArg?: any, ...args: any[]): any; - } - export function channel(name: string): Channel; - export function subscribe(name: string, onMessage: ChannelListener): void; - export function unsubscribe(name: string, onMessage: ChannelListener): boolean; - export function hasSubscribers(name: string): boolean; - export interface TracingChannelSubscribers { - start?: (message: any) => void; - end?: (message: any) => void; - asyncStart?: (message: any) => void; - asyncEnd?: (message: any) => void; - error?: (message: any) => void; - } - export interface TracingChannelCollection { - start: Channel; - end: Channel; - asyncStart: Channel; - asyncEnd: Channel; - error: Channel; - } - export interface TracingChannel { - start: Channel; - end: Channel; - asyncStart: Channel; - asyncEnd: Channel; - error: Channel; - readonly hasSubscribers: boolean; - subscribe(subscribers: TracingChannelSubscribers): void; - /** True when every present handler was found and removed (Node's - * all-found conjunction; @types/node under-declares this as void). */ - unsubscribe(subscribers: TracingChannelSubscribers): boolean; - traceSync( - fn: (this: ThisArg, ...args: Args) => Result, - context?: object, - thisArg?: ThisArg, - ...args: Args - ): Result; - tracePromise( - fn: (this: ThisArg, ...args: Args) => Promise, - context?: object, - thisArg?: ThisArg, - ...args: Args - ): Promise; - traceCallback( - fn: (this: ThisArg, ...args: Args) => Result, - position?: number, - context?: object, - thisArg?: ThisArg, - ...args: Args - ): Result; - } - export function tracingChannel(nameOrChannels: string | TracingChannelCollection): TracingChannel; -} -declare module "diagnostics_channel" { - export * from "node:diagnostics_channel"; -} - /* node:perf_hooks — performance.now() over the runtime's process-start- * anchored monotonic clock (Node's timeOrigin for a compiled program), * fractional milliseconds. The .bind(performance) spelling lowers to the @@ -1525,15 +1370,6 @@ declare module "perf_hooks" { * performance.now() either way. */ declare var performance: import("node:perf_hooks").Performance; -declare module "node:module" { - export function createRequire(filename: string | URL): ScriptcRequire; - export const builtinModules: string[]; - export function isBuiltin(moduleName: string): boolean; - export function syncBuiltinESMExports(): void; -} -declare module "module" { - export * from "node:module"; -} /* import.meta: module-loader metadata with no value representation — * direct file-backed fields lower to constants, while the object and * remaining members fence (SC1090) EXCEPT as createRequire's base, where it @@ -1548,1188 +1384,6 @@ interface ImportMeta { resolve(specifier: string, parent?: string | URL): string; } -/* The synchronous node:fs surface — utf8-only, no options objects, no - * Buffers. Importing "node:fs" resolves here (preflight allowlists exactly - * the ambient-declared node: modules); every function lowers to a `libCall` - * with a scr_fs_* runtime implementation. Failures throw CATCHABLE string - * values formatted like Node's error messages ("ENOENT: no such file or - * directory, open 'x'"). `rmSync` removes FILES only (like Node without - * `recursive`); empty directories go through `rmdirSync`. */ -declare module "node:fs" { - import { Readable, Writable } from "stream"; - export function readFileSync(path: string | number | URL, encoding: BufferEncoding): string; - export function readFileSync( - path: string | number | URL, - options: { encoding: BufferEncoding }, - ): string; - export function readFileSync(path: string | number | URL): Buffer; - export function readFileSync(path: string | URL, options: unknown): string | Buffer; - /* Error-first callbacks select string or Buffer data from their encoding. - * Unknown options retain that union until the caller validates the value. */ - export function readFile( - path: string, - options: unknown, - callback: (error: Error | null, data: string | Buffer) => void, - ): void; - /* The options form carries the mode (applied at CREATION only, like - * Node — an existing file keeps its permissions) and/or the utf8 - * encoding spelling. */ - export function writeFileSync( - path: string | URL, - data: string, - /* The options-record stance (see http.RequestOptions): flag fences - * by name at the call site; undocumented keys drop like Node. */ - options?: { - mode?: number; - encoding?: "utf8" | "utf-8"; - flag?: string; - [option: string]: unknown; - }, - ): void; - /* The bare-encoding spelling — the options record's encoding key alone. */ - export function writeFileSync(path: string | URL, data: string, encoding: "utf8" | "utf-8"): void; - export function writeFileSync(path: string | URL, data: string | Uint8Array): void; - export function appendFileSync(path: string | URL, data: string | Uint8Array): void; - export function existsSync(path: string | URL): boolean; - export function mkdirSync(path: string | URL): void; - export function mkdirSync( - path: string | URL, - options: { recursive?: boolean; mode?: number }, - ): void; - export function unlinkSync(path: string | URL): void; - export function chmodSync(path: string | URL, mode: number): void; - export function fchmodSync(fd: number, mode: number): void; - export function fsyncSync(fd: number): void; - export function linkSync( - existingPath: string | Uint8Array | URL, - newPath: string | Uint8Array | URL, - ): void; - export function symlinkSync( - target: string | Uint8Array | URL, - path: string | Uint8Array | URL, - type?: string | null, - ): void; - /* fs.cpSync / fs.cp: copies a file, symlink or (with recursive) a - * directory tree. filter decides per path; the callback and promise - * forms complete the copy before reporting. */ - export interface CopySyncOptions { - dereference?: boolean; - errorOnExist?: boolean; - filter?: (source: string, destination: string) => boolean; - force?: boolean; - mode?: number; - preserveTimestamps?: boolean; - recursive?: boolean; - verbatimSymlinks?: boolean; - } - export interface CopyOptions { - dereference?: boolean; - errorOnExist?: boolean; - filter?: (source: string, destination: string) => boolean | Promise; - force?: boolean; - mode?: number; - preserveTimestamps?: boolean; - recursive?: boolean; - verbatimSymlinks?: boolean; - } - export function cpSync( - source: string | URL, - destination: string | URL, - options?: CopySyncOptions, - ): void; - export function cp( - source: string | URL, - destination: string | URL, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function cp( - source: string | URL, - destination: string | URL, - options: CopyOptions, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function symlink( - target: string | Uint8Array | URL, - path: string | Uint8Array | URL, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function symlink( - target: string | Uint8Array | URL, - path: string | Uint8Array | URL, - type: string | null | undefined, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function link( - existingPath: string | Uint8Array | URL, - newPath: string | Uint8Array | URL, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function readlink( - path: string | Uint8Array | URL, - callback: (err: NodeJS.ErrnoException | null, target: string) => void, - ): void; - export function readlink( - path: string | Uint8Array | URL, - options: "buffer" | { encoding: "buffer" }, - callback: (err: NodeJS.ErrnoException | null, target: Buffer) => void, - ): void; - export function readlink( - path: string | Uint8Array | URL, - options: BufferEncoding | { encoding?: BufferEncoding | null } | null, - callback: (err: NodeJS.ErrnoException | null, target: string) => void, - ): void; - export function readlink( - path: string | Uint8Array | URL, - options: string | { encoding?: string | null } | null, - callback: (err: NodeJS.ErrnoException | null, target: string | Buffer) => void, - ): void; - export function readlinkSync( - path: string | Uint8Array | URL, - options: "buffer" | { encoding: "buffer" }, - ): Buffer; - export function readlinkSync( - path: string | Uint8Array | URL, - options?: BufferEncoding | { encoding?: BufferEncoding | null } | null, - ): string; - export function readlinkSync( - path: string | Uint8Array | URL, - options: string | { encoding?: string | null } | null, - ): string | Buffer; - export function chownSync(path: string | URL, uid: number, gid: number): void; - export function lchownSync(path: string | URL, uid: number, gid: number): void; - export function fchownSync(fd: number, uid: number, gid: number): void; - export function truncateSync(path: string | URL, len?: number): void; - /* The 2-argument form only (Node's mode flags have no lowering). The - * destination is created or truncated carrying the SOURCE's mode. */ - export function copyFileSync(src: string | URL, dest: string | URL): void; - export function rename( - oldPath: string, - newPath: string, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function renameSync(oldPath: string | URL, newPath: string | URL): void; - export function rmSync(path: string | URL): void; - /* maxRetries/retryDelay are accepted no-ops (Node retries around - * EBUSY-class races; the synchronous lowering has no re-entrancy that - * needs them). */ - export function rmSync( - path: string | URL, - options: { recursive?: boolean; force?: boolean; maxRetries?: number; retryDelay?: number }, - ): void; - export function rmdirSync(path: string | URL): void; - export function readdirSync(path: string | URL): string[]; - /* The withFileTypes form: Dirent rows (name + parentPath + the type - * probes — the honest subset of Node's Dirent surface). The type is - * interned explicitly in type-mapper.ts (the record's hidden %dtype field - * carries the entry kind; isFile/isDirectory/isSymbolicLink lower as - * reads of it in builtins/process.ts). */ - export interface Dirent { - name: string; - parentPath: string; - isFile(): boolean; - isDirectory(): boolean; - isSymbolicLink(): boolean; - } - export function readdirSync( - path: string | URL, - /* The options-record stance (see http.RequestOptions): recursive - * fences by name at the call site; undocumented keys drop like Node. */ - options: { withFileTypes: true; recursive?: boolean; [option: string]: unknown }, - ): Dirent[]; - /* fs.globSync / fs.glob: Node's glob walk over the file system. Patterns - * are minimatch globs (case-insensitive on macOS and Windows); results - * are relative to cwd unless the pattern is absolute. withFileTypes must - * be a literal so the result type is known. */ - export interface GlobOptions { - cwd?: string | URL; - exclude?: readonly string[] | ((fileName: string) => boolean); - followSymlinks?: boolean; - withFileTypes?: false; - } - export interface GlobOptionsWithFileTypes { - cwd?: string | URL; - exclude?: readonly string[]; - followSymlinks?: boolean; - withFileTypes: true; - } - export function globSync(pattern: string | readonly string[], options?: GlobOptions): string[]; - export function globSync( - pattern: string | readonly string[], - options: GlobOptionsWithFileTypes, - ): Dirent[]; - export function glob( - pattern: string | readonly string[], - callback: (err: NodeJS.ErrnoException | null, matches?: string[]) => void, - ): void; - export function glob( - pattern: string | readonly string[], - options: GlobOptions, - callback: (err: NodeJS.ErrnoException | null, matches?: string[]) => void, - ): void; - /* fs.Dir: an open directory stream (opendirSync, fs.promises.opendir) - * read one Dirent at a time in OS order; utf8 names, no recursive walk. */ - export interface Dir { - readonly path: string; - readSync(): Dirent | null; - read(): Promise; - closeSync(): void; - close(): Promise; - [Symbol.asyncIterator](): AsyncIterableIterator; - } - export interface OpenDirOptions { - encoding?: "utf8" | "utf-8"; - bufferSize?: number; - } - export function opendirSync(path: string | URL, options?: OpenDirOptions): Dir; - /* fs.ReadStream / fs.WriteStream: runtime Readable and Writable streams - * over a file (scr_fs_stream.c). The options are checked at runtime - * with Node's rules; flags and mode are checked when the file opens, so - * their errors arrive as 'error' events. Instances also emit 'open' - * (with the fd) and 'ready'. */ - export interface StreamOptions { - flags?: string; - encoding?: BufferEncoding; - fd?: number; - mode?: number | string; - autoClose?: boolean; - emitClose?: boolean; - start?: number; - highWaterMark?: number; - } - export interface ReadStreamOptions extends StreamOptions { - end?: number; - } - export interface WriteStreamOptions extends StreamOptions { - flush?: boolean; - } - export class ReadStream extends Readable { - readonly bytesRead: number; - readonly path: string | Buffer; - readonly pending: boolean; - close(callback?: () => void): void; - } - export class WriteStream extends Writable { - readonly bytesWritten: number; - readonly path: string | Buffer; - readonly pending: boolean; - close(callback?: () => void): void; - } - export function createReadStream( - path: string | Buffer | URL, - options?: BufferEncoding | ReadStreamOptions, - ): ReadStream; - export function createWriteStream( - path: string | Buffer | URL, - options?: BufferEncoding | WriteStreamOptions, - ): WriteStream; - export function accessSync(path: string | URL, mode?: number): void; - export function mkdtempSync(prefix: string | URL): string; - export const constants: { - readonly F_OK: number; - readonly R_OK: number; - readonly W_OK: number; - readonly X_OK: number; - readonly O_RDONLY: number; - readonly O_WRONLY: number; - readonly O_RDWR: number; - readonly O_CREAT: number; - readonly O_EXCL: number; - readonly O_NOFOLLOW: number; - readonly O_NONBLOCK: number; - readonly O_TRUNC: number; - readonly O_APPEND: number; - readonly COPYFILE_EXCL: number; - readonly COPYFILE_FICLONE: number; - readonly COPYFILE_FICLONE_FORCE: number; - readonly UV_FS_SYMLINK_DIR: number; - readonly UV_FS_SYMLINK_JUNCTION: number; - readonly UV_DIRENT_UNKNOWN: number; - readonly UV_DIRENT_FILE: number; - readonly UV_DIRENT_DIR: number; - readonly UV_DIRENT_LINK: number; - readonly UV_DIRENT_FIFO: number; - readonly UV_DIRENT_SOCKET: number; - readonly UV_DIRENT_CHAR: number; - readonly UV_DIRENT_BLOCK: number; - readonly UV_FS_O_FILEMAP: number; - readonly UV_FS_O_TEMPORARY: number; - readonly UV_FS_O_SHORT_LIVED: number; - readonly UV_FS_O_SEQUENTIAL: number; - readonly UV_FS_O_RANDOM: number; - readonly UV_FS_COPYFILE_EXCL: number; - readonly UV_FS_COPYFILE_FICLONE: number; - readonly UV_FS_COPYFILE_FICLONE_FORCE: number; - readonly S_IFMT: number; - readonly S_IFREG: number; - readonly S_IFDIR: number; - readonly S_IFCHR: number; - readonly S_IFBLK: number; - readonly S_IFIFO: number; - readonly S_IFLNK: number; - readonly S_IFSOCK: number; - readonly O_NOCTTY: number; - readonly O_DIRECTORY: number; - readonly O_NOATIME: number; - readonly O_SYNC: number; - readonly O_DSYNC: number; - readonly O_SYMLINK: number; - readonly O_DIRECT: number; - readonly S_IRWXU: number; - readonly S_IRUSR: number; - readonly S_IWUSR: number; - readonly S_IXUSR: number; - readonly S_IRWXG: number; - readonly S_IRGRP: number; - readonly S_IWGRP: number; - readonly S_IXGRP: number; - readonly S_IRWXO: number; - readonly S_IROTH: number; - readonly S_IWOTH: number; - readonly S_IXOTH: number; - }; - /* A stat(2) snapshot (statSync follows symlinks, lstatSync does not — - * Node's split) — immutable; the supported surface is exactly these - * members. dev/ino identify the filesystem entry, blocks is the allocated - * size in 512-byte units, and the time fields are milliseconds with their - * sub-second fractions. */ - export interface Stats { - isFile(): boolean; - isDirectory(): boolean; - isSymbolicLink(): boolean; - isBlockDevice(): boolean; - isCharacterDevice(): boolean; - isFIFO(): boolean; - isSocket(): boolean; - readonly dev: number; - readonly mode: number; - readonly nlink: number; - readonly uid: number; - readonly gid: number; - readonly rdev: number; - readonly blksize: number; - readonly ino: number; - readonly size: number; - readonly blocks: number; - readonly atimeMs: number; - readonly mtimeMs: number; - readonly ctimeMs: number; - readonly birthtimeMs: number; - readonly atime: Date; - readonly mtime: Date; - readonly ctime: Date; - readonly birthtime: Date; - } - /* throwIfNoEntry: false answers undefined for a missing entry (ENOENT or - * ENOTDIR) instead of throwing. BigIntStats (bigint: true) has no - * lowering. */ - export interface StatSyncOptions { - bigint?: boolean; - throwIfNoEntry?: boolean; - } - export function statSync(path: string | URL, options?: undefined): Stats; - export function statSync( - path: string | URL, - options: StatSyncOptions & { bigint?: false; throwIfNoEntry: false }, - ): Stats | undefined; - export function statSync( - path: string | URL, - options: StatSyncOptions & { bigint?: false; throwIfNoEntry?: true }, - ): Stats; - /* bigint: true type-checks so the lowering can name it as unsupported. */ - export function statSync(path: string | URL, options: StatSyncOptions & { bigint: true }): Stats; - export function statSync(path: string | URL, options: StatSyncOptions): Stats | undefined; - export function lstatSync(path: string | URL, options?: undefined): Stats; - export function lstatSync( - path: string | URL, - options: StatSyncOptions & { bigint?: false; throwIfNoEntry: false }, - ): Stats | undefined; - export function lstatSync( - path: string | URL, - options: StatSyncOptions & { bigint?: false; throwIfNoEntry?: true }, - ): Stats; - /* bigint: true type-checks so the lowering can name it as unsupported. */ - export function lstatSync(path: string | URL, options: StatSyncOptions & { bigint: true }): Stats; - export function lstatSync(path: string | URL, options: StatSyncOptions): Stats | undefined; - /* The fd pair behind spawn's fd-stdio form: openSync(path, flags) → - * the raw fd. Numeric flags use an inline OR of the O_* constants. */ - export function openSync(path: string | URL, flags: string): number; - export function openSync(path: string | URL, flags: number, mode?: number): number; - export function closeSync(fd: number): void; - export function fdatasyncSync(fd: number): void; - export function ftruncateSync(fd: number, len?: number): void; - export function utimesSync( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - ): void; - export function futimesSync( - fd: number, - atime: number | string | Date, - mtime: number | string | Date, - ): void; - export function lutimesSync( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - ): void; - export function utimes( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function futimes( - fd: number, - atime: number | string | Date, - mtime: number | string | Date, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function lutimes( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - callback: (err: NodeJS.ErrnoException | null) => void, - ): void; - export function readvSync(fd: number, buffers: Uint8Array[], position?: number | null): number; - export function writevSync(fd: number, buffers: Uint8Array[], position?: number | null): number; - /* Read into a caller buffer from the fd's current position when position - * is omitted/null, or from a numeric byte position without advancing the - * fd. Answers the byte count, 0 at EOF. */ - export interface ReadSyncOptions { - offset?: number; - length?: number; - position?: number | null; - } - export function readSync(fd: number, buffer: Uint8Array, options?: ReadSyncOptions): number; - export function readSync( - fd: number, - buffer: Uint8Array, - offset: number, - length: number, - position?: number | null, - ): number; - /* Write a caller-buffer window, or a utf8 string, at the fd's current - * position when position is omitted/null (advancing it), or at a numeric - * byte position without advancing it. Answers the byte count. */ - export interface WriteSyncOptions { - offset?: number; - length?: number; - position?: number | null; - } - export function writeSync(fd: number, buffer: Uint8Array, options: WriteSyncOptions): number; - export function writeSync( - fd: number, - buffer: Uint8Array, - offset?: number, - length?: number, - position?: number | null, - ): number; - export function writeSync( - fd: number, - string: string, - position?: number | null, - encoding?: "utf8" | "utf-8", - ): number; - /* statSync over an open fd — the same Stats snapshot. */ - export function fstatSync(fd: number): Stats; - /* realpath(3) — resolves symlinks, `.`/`..`, throwing Node's fs error - * shapes for missing paths. */ - export function realpathSync(path: string | URL): string; - export namespace realpathSync { - function native(path: string | URL): string; - } - /* statfs(2)/statvfs(3) — the filesystem-capacity snapshot (the fields - * Node's statfsSync reports; bavail × bsize is the free-space probe). */ - export interface StatsFsBase { - type: T; - bsize: T; - blocks: T; - bfree: T; - bavail: T; - files: T; - ffree: T; - } - export interface StatsFs extends StatsFsBase {} - export interface BigIntStatsFs extends StatsFsBase {} - export interface StatFsOptions { - bigint?: boolean; - } - export function statfsSync( - path: string | Uint8Array | URL, - options?: StatFsOptions & { bigint?: false }, - ): StatsFs; - export function statfsSync( - path: string | Uint8Array | URL, - options: StatFsOptions & { bigint: true }, - ): BigIntStatsFs; - export function statfsSync( - path: string | Uint8Array | URL, - options?: StatFsOptions, - ): StatsFs | BigIntStatsFs; - export function statfs( - path: string | Uint8Array | URL, - callback: (err: NodeJS.ErrnoException | null, stats: StatsFs) => void, - ): void; - export function statfs( - path: string | Uint8Array | URL, - options: (StatFsOptions & { bigint?: false }) | undefined, - callback: (err: NodeJS.ErrnoException | null, stats: StatsFs) => void, - ): void; - export function statfs( - path: string | Uint8Array | URL, - options: StatFsOptions & { bigint: true }, - callback: (err: NodeJS.ErrnoException | null, stats: BigIntStatsFs) => void, - ): void; - export function statfs( - path: string | Uint8Array | URL, - options: StatFsOptions | undefined, - callback: (err: NodeJS.ErrnoException | null, stats: StatsFs | BigIntStatsFs) => void, - ): void; - /* fs.watch — the file-watching slice (scr_watch.c, kqueue EVFILT_VNODE): - * "rename"/"change" events on ONE path, watcher.close() to stop. The - * listener takes zero parameters or the eventType string (the filename - * parameter has no lowering); an open watcher keeps the loop alive. A - * path that cannot be opened THROWS Node's fs error synchronously. */ - export interface FSWatcher { - close(): void; - } - /* The options-record stance (see http.RequestOptions): persistent: true, - * recursive: false, and encoding: "utf8" state the lowered behavior and - * are accepted; persistent: false, recursive: true, and signal fence by - * name at the call site; undocumented keys drop like Node. */ - export interface WatchOptions { - persistent?: boolean; - recursive?: boolean; - encoding?: string; - [option: string]: unknown; - } - export function watch(path: string, listener?: (eventType: string) => void): FSWatcher; - export function watch( - path: string, - options: WatchOptions, - listener?: (eventType: string) => void, - ): FSWatcher; - /* fs.promises IS the fs/promises module (Node's rule) — the namespace- - * import form `fs.promises.readFile(...)` lowers through the same - * fs/promises table as `import { readFile } from "node:fs/promises"`. */ - export const promises: typeof import("node:fs/promises"); -} -/* Every builtin answers to BOTH specifier forms, like in Node: the bare - * module re-exports its node:-prefixed twin (or holds the declarations - * itself — one of the pair forwards). The lowerer recognizes members by - * their import SPECIFIER, so both forms lower identically. */ -declare module "fs" { - export * from "node:fs"; -} -/* graceful-fs — served as node:fs (it monkeypatches fs and retries on - * EMFILE/ENFILE; compiled programs call fs directly, without the retry - * queue). gracefulify is a no-op statement. The @types/graceful-fs shape. */ -declare module "graceful-fs" { - export * from "node:fs"; - export function gracefulify(fsModule: T): T; -} - -/* fs/promises — the SAME synchronous operations behind already-settled - * promises: success fulfills, failure REJECTS with the would-be thrown - * error (catchable at the await, like Node). DOCUMENTED DIVERGENCE - * (SEMANTICS.md): the syscall blocks the event loop, so I/O never - * interleaves with timers or other fibers — observable only in concurrent - * code. readFile is utf8-only, like readFileSync. */ -declare module "fs/promises" { - export interface FileReadResult { - bytesRead: number; - buffer: T; - } - export interface FileWriteResult { - bytesWritten: number; - buffer: T; - } - export interface FileReadvResult { - bytesRead: number; - buffers: T; - } - export interface FileWritevResult { - bytesWritten: number; - buffers: T; - } - export interface FileReadOptions { - buffer?: T; - offset?: number | null; - length?: number | null; - position?: number | null; - } - /* The streams over a FileHandle (createReadStream, createWriteStream, - * readLines): fs streams that hold the handle until they close; closing - * the stream closes the handle unless autoClose is false. */ - export interface CreateReadStreamOptions { - encoding?: BufferEncoding | null; - autoClose?: boolean; - emitClose?: boolean; - start?: number; - end?: number; - highWaterMark?: number; - } - export interface CreateWriteStreamOptions { - encoding?: BufferEncoding | null; - autoClose?: boolean; - emitClose?: boolean; - start?: number; - highWaterMark?: number; - flush?: boolean; - } - export interface FileHandle extends AsyncDisposable { - readonly fd: number; - close(): Promise; - createReadStream(options?: CreateReadStreamOptions): import("node:fs").ReadStream; - createWriteStream(options?: CreateWriteStreamOptions): import("node:fs").WriteStream; - readLines(options?: CreateReadStreamOptions): import("node:readline").Interface; - sync(): Promise; - datasync(): Promise; - truncate(len?: number): Promise; - chmod(mode: number): Promise; - utimes(atime: number | string | Date, mtime: number | string | Date): Promise; - readv( - buffers: T, - position?: number | null, - ): Promise>; - writev( - buffers: T, - position?: number | null, - ): Promise>; - read( - options?: FileReadOptions | null, - ): Promise>; - read( - buffer: T, - options: Omit, "buffer">, - ): Promise>; - read( - buffer: T, - offset?: number | null, - length?: number | null, - position?: number | null, - ): Promise>; - write( - buffer: T, - offset?: number | null, - length?: number | null, - position?: number | null, - ): Promise>; - write( - buffer: T, - options: Omit, "buffer">, - ): Promise>; - write( - data: string, - position?: number | null, - encoding?: "utf8" | "utf-8" | null, - ): Promise>; - readFile(options?: null): Promise; - readFile(encoding: "utf8" | "utf-8"): Promise; - writeFile(data: string | Uint8Array, encoding?: "utf8" | "utf-8" | null): Promise; - appendFile(data: string | Uint8Array, encoding?: "utf8" | "utf-8" | null): Promise; - stat(): Promise; - [Symbol.asyncDispose](): Promise; - } - export function open(path: string | URL, flags?: string, mode?: number): Promise; - export function link( - existingPath: string | Uint8Array | URL, - newPath: string | Uint8Array | URL, - ): Promise; - export function symlink( - target: string | Uint8Array | URL, - path: string | Uint8Array | URL, - type?: string | null, - ): Promise; - export function cp( - source: string | URL, - destination: string | URL, - options?: import("node:fs").CopyOptions, - ): Promise; - export function readlink( - path: string | Uint8Array | URL, - options: "buffer" | { encoding: "buffer" }, - ): Promise; - export function readlink( - path: string | Uint8Array | URL, - options?: BufferEncoding | { encoding?: BufferEncoding | null } | null, - ): Promise; - export function readlink( - path: string | Uint8Array | URL, - options: string | { encoding?: string | null } | null, - ): Promise; - export function utimes( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - ): Promise; - export function lutimes( - path: string | Uint8Array | URL, - atime: number | string | Date, - mtime: number | string | Date, - ): Promise; - export function readFile(path: string | URL, encoding: "utf8" | "utf-8"): Promise; - export function readFile( - path: string | URL, - options: BufferEncoding | { encoding: BufferEncoding }, - ): Promise; - export function readFile(path: string | URL): Promise; - export function writeFile( - path: string | URL, - data: string, - options?: { - mode?: number; - encoding?: "utf8" | "utf-8"; - flag?: string; - [option: string]: unknown; - }, - ): Promise; - export function writeFile( - path: string | URL, - data: string, - encoding: "utf8" | "utf-8", - ): Promise; - export function mkdir( - path: string | URL, - options?: { recursive?: boolean; mode?: number }, - ): Promise; - export function readdir(path: string | URL): Promise; - export function readdir( - path: string | URL, - options: { - encoding?: "utf8" | "utf-8"; - withFileTypes: true; - recursive?: boolean; - [option: string]: unknown; - }, - ): Promise; - /* The async iterator form of fs.globSync: iterate it with for await. */ - export function glob( - pattern: string | readonly string[], - options?: import("node:fs").GlobOptions, - ): AsyncIterableIterator; - export function glob( - pattern: string | readonly string[], - options: import("node:fs").GlobOptionsWithFileTypes, - ): AsyncIterableIterator; - export function rm(path: string | URL): Promise; - export function rm( - path: string | URL, - options: { recursive?: boolean; force?: boolean; maxRetries?: number; retryDelay?: number }, - ): Promise; - export function stat(path: string | URL): Promise; - export function statfs( - path: string | Uint8Array | URL, - options?: import("node:fs").StatFsOptions & { bigint?: false }, - ): Promise; - export function statfs( - path: string | Uint8Array | URL, - options: import("node:fs").StatFsOptions & { bigint: true }, - ): Promise; - export function statfs( - path: string | Uint8Array | URL, - options?: import("node:fs").StatFsOptions, - ): Promise; - export function realpath(path: string | URL): Promise; - export function lstat(path: string | URL): Promise; - export function unlink(path: string | URL): Promise; - export function chmod(path: string | URL, mode: number): Promise; - export function chown(path: string | URL, uid: number, gid: number): Promise; - export function lchown(path: string | URL, uid: number, gid: number): Promise; - export function access(path: string | URL, mode?: number): Promise; - export function truncate(path: string | URL, len?: number): Promise; - export function appendFile(path: string | URL, data: string | Uint8Array): Promise; - export function copyFile(src: string | URL, dest: string | URL): Promise; - export function mkdtemp(prefix: string | URL): Promise; - export function rmdir(path: string | URL): Promise; - export function rename(oldPath: string | URL, newPath: string | URL): Promise; - export function opendir( - path: string | URL, - options?: import("node:fs").OpenDirOptions, - ): Promise; -} -declare module "node:fs/promises" { - export * from "fs/promises"; -} - -/* node:path — the TARGET platform's implementation (Node's rule): posix - * on posix triples, path.win32 under a win32 triple, with the posix and - * win32 namespaces answering their own platform anywhere. Every function - * is a pure string algorithm matching Node's implementations - * exactly; join/resolve are variadic like Node's. */ -declare module "path" { - export function join(...paths: string[]): string; - export function resolve(...paths: string[]): string; - export function normalize(path: string): string; - export function dirname(path: string): string; - export function basename(path: string, suffix?: string): string; - export function extname(path: string): string; - export function isAbsolute(path: string): boolean; - export function relative(from: string, to: string): string; - export function toNamespacedPath(path: string): string; - /* matchesGlob() is Node's glob matcher: brace expansion, extglobs, POSIX - * classes and the ** and dot rules. */ - export function matchesGlob(path: string, pattern: string): boolean; - /* parse() splits a path into its root, directory, base, extension and - * name; format() joins those fields back (Node's `||` defaults). */ - export interface ParsedPath { - root: string; - dir: string; - base: string; - ext: string; - name: string; - } - export interface FormatInputPathObject { - root?: string | undefined; - dir?: string | undefined; - base?: string | undefined; - ext?: string | undefined; - name?: string | undefined; - } - export function parse(path: string): ParsedPath; - export function format(pathObject: FormatInputPathObject): string; - /* The union @types/node declares: the bare module answers the TARGET - * platform's value (a compile-time constant — "\\"/";" under a win32 - * triple, "/"/":" everywhere else). */ - export const sep: "\\" | "/"; - export const delimiter: ";" | ":"; - /* The platform-specific namespaces (real Node submodules too — - * "node:path/posix" resolves). Each carries the FULL surface and - * answers ITS platform's rules on any target, Node's own contract; - * the bare module IS the target platform's implementation. */ - export const posix: typeof import("path/posix"); - export const win32: typeof import("path/win32"); -} -declare module "node:path" { - export * from "path"; -} -declare module "path/posix" { - export function join(...paths: string[]): string; - export function resolve(...paths: string[]): string; - export function normalize(path: string): string; - export function dirname(path: string): string; - export function basename(path: string, suffix?: string): string; - export function extname(path: string): string; - export function isAbsolute(path: string): boolean; - export function relative(from: string, to: string): string; - export function toNamespacedPath(path: string): string; - export function matchesGlob(path: string, pattern: string): boolean; - export function parse(path: string): import("path").ParsedPath; - export function format(pathObject: import("path").FormatInputPathObject): string; - export const sep: "/"; - export const delimiter: ":"; -} -declare module "node:path/posix" { - export * from "path/posix"; -} -declare module "path/win32" { - export function join(...paths: string[]): string; - export function resolve(...paths: string[]): string; - export function normalize(path: string): string; - export function dirname(path: string): string; - export function basename(path: string, suffix?: string): string; - export function extname(path: string): string; - export function isAbsolute(path: string): boolean; - export function relative(from: string, to: string): string; - export function toNamespacedPath(path: string): string; - export function matchesGlob(path: string, pattern: string): boolean; - export function parse(path: string): import("path").ParsedPath; - export function format(pathObject: import("path").FormatInputPathObject): string; - export const sep: "\\"; - export const delimiter: ";"; -} -declare module "node:path/win32" { - export * from "path/win32"; -} - -/* node:os — the slice a CLI needs: platform() (same implementation as - * process.platform), homedir(), tmpdir() (Node's env cascade), EOL - * (always "\n" — POSIX pinned, like path.sep), and networkInterfaces() - * (getifaddrs(3); the interface shapes mirror @types/node's exactly so - * both type worlds map to the same IR structure). */ -declare module "os" { - export function platform(): string; - export function homedir(): string; - export function tmpdir(): string; - /* uname(2)'s release field — Node's own implementation. */ - export function release(): string; - /* uname(2)'s sysname field ("Darwin", "Linux") — Node's os.type(). */ - export function type(): string; - /* Total system memory in bytes (sysctl hw.memsize / sysconf). */ - export function totalmem(): number; - export function availableParallelism(): number; - /* The passwd-entry snapshot (uv_os_get_passwd): shell is `string | - * null` to match @types/node (POSIX always answers the string arm); - * homedir is pw_dir — NOT os.homedir()'s $HOME-first cascade. The type - * is interned explicitly in type-mapper.ts (the AddressInfo pattern — this - * concrete form and @types/node's UserInfo both), and the call - * assembles field-by-field in lowerOsUserInfoCall. */ - export interface UserInfo { - username: string; - uid: number; - gid: number; - shell: string | null; - homedir: string; - } - export function userInfo(): UserInfo; - /* The TARGET platform's line terminator (a compile-time constant: - * "\r\n" under a win32 triple, "\n" everywhere else). */ - export const EOL: "\r\n" | "\n"; - export interface NetworkInterfaceBase { - address: string; - netmask: string; - mac: string; - internal: boolean; - cidr: string | null; - scopeid?: number; - } - export interface NetworkInterfaceInfoIPv4 extends NetworkInterfaceBase { - family: "IPv4"; - } - export interface NetworkInterfaceInfoIPv6 extends NetworkInterfaceBase { - family: "IPv6"; - scopeid: number; - } - export type NetworkInterfaceInfo = NetworkInterfaceInfoIPv4 | NetworkInterfaceInfoIPv6; - export function networkInterfaces(): { [name: string]: NetworkInterfaceInfo[] | undefined }; - /* Host facts, libuv's answers: model/speed/times per logical CPU, the - * host name, the 1/5/15-minute load averages, uptime in seconds, free - * memory in bytes, process.arch, uname's machine and version fields, - * the byte order, and a process's scheduling priority (an omitted pid - * is the current process). Shapes mirror @types/node's. */ - export interface CpuInfo { - model: string; - speed: number; - times: { - user: number; - nice: number; - sys: number; - idle: number; - irq: number; - }; - } - export function cpus(): CpuInfo[]; - export function hostname(): string; - export function loadavg(): number[]; - export function uptime(): number; - export function freemem(): number; - export function arch(): string; - export function machine(): string; - export function version(): string; - export function endianness(): "BE" | "LE"; - export function getPriority(pid?: number): number; - export function setPriority(priority: number): void; - export function setPriority(pid: number, priority: number): void; - /* The TARGET platform's null device ("\\\\.\\nul" under a win32 triple). */ - export const devNull: string; - /* The platform's constants: null-prototype objects with read-only - * members, `signals` frozen (Node's internalBinding('constants').os). */ - export type SignalConstants = { - [ - key in - | "SIGABRT" - | "SIGALRM" - | "SIGBUS" - | "SIGCHLD" - | "SIGCONT" - | "SIGFPE" - | "SIGHUP" - | "SIGILL" - | "SIGINT" - | "SIGIO" - | "SIGIOT" - | "SIGKILL" - | "SIGPIPE" - | "SIGPOLL" - | "SIGPROF" - | "SIGPWR" - | "SIGQUIT" - | "SIGSEGV" - | "SIGSTKFLT" - | "SIGSTOP" - | "SIGSYS" - | "SIGTERM" - | "SIGTRAP" - | "SIGTSTP" - | "SIGTTIN" - | "SIGTTOU" - | "SIGUNUSED" - | "SIGURG" - | "SIGUSR1" - | "SIGUSR2" - | "SIGVTALRM" - | "SIGWINCH" - | "SIGXCPU" - | "SIGXFSZ" - | "SIGBREAK" - | "SIGLOST" - | "SIGINFO" - ]: number; - }; - export namespace constants { - const UV_UDP_REUSEADDR: number; - namespace signals {} - const signals: SignalConstants; - namespace errno { - const E2BIG: number; - const EACCES: number; - const EADDRINUSE: number; - const EADDRNOTAVAIL: number; - const EAFNOSUPPORT: number; - const EAGAIN: number; - const EALREADY: number; - const EBADF: number; - const EBADMSG: number; - const EBUSY: number; - const ECANCELED: number; - const ECHILD: number; - const ECONNABORTED: number; - const ECONNREFUSED: number; - const ECONNRESET: number; - const EDEADLK: number; - const EDESTADDRREQ: number; - const EDOM: number; - const EDQUOT: number; - const EEXIST: number; - const EFAULT: number; - const EFBIG: number; - const EHOSTUNREACH: number; - const EIDRM: number; - const EILSEQ: number; - const EINPROGRESS: number; - const EINTR: number; - const EINVAL: number; - const EIO: number; - const EISCONN: number; - const EISDIR: number; - const ELOOP: number; - const EMFILE: number; - const EMLINK: number; - const EMSGSIZE: number; - const EMULTIHOP: number; - const ENAMETOOLONG: number; - const ENETDOWN: number; - const ENETRESET: number; - const ENETUNREACH: number; - const ENFILE: number; - const ENOBUFS: number; - const ENODATA: number; - const ENODEV: number; - const ENOENT: number; - const ENOEXEC: number; - const ENOLCK: number; - const ENOLINK: number; - const ENOMEM: number; - const ENOMSG: number; - const ENOPROTOOPT: number; - const ENOSPC: number; - const ENOSR: number; - const ENOSTR: number; - const ENOSYS: number; - const ENOTCONN: number; - const ENOTDIR: number; - const ENOTEMPTY: number; - const ENOTSOCK: number; - const ENOTSUP: number; - const ENOTTY: number; - const ENXIO: number; - const EOPNOTSUPP: number; - const EOVERFLOW: number; - const EPERM: number; - const EPIPE: number; - const EPROTO: number; - const EPROTONOSUPPORT: number; - const EPROTOTYPE: number; - const ERANGE: number; - const EROFS: number; - const ESPIPE: number; - const ESRCH: number; - const ESTALE: number; - const ETIME: number; - const ETIMEDOUT: number; - const ETXTBSY: number; - const EWOULDBLOCK: number; - const EXDEV: number; - const WSAEINTR: number; - const WSAEBADF: number; - const WSAEACCES: number; - const WSAEFAULT: number; - const WSAEINVAL: number; - const WSAEMFILE: number; - const WSAEWOULDBLOCK: number; - const WSAEINPROGRESS: number; - const WSAEALREADY: number; - const WSAENOTSOCK: number; - const WSAEDESTADDRREQ: number; - const WSAEMSGSIZE: number; - const WSAEPROTOTYPE: number; - const WSAENOPROTOOPT: number; - const WSAEPROTONOSUPPORT: number; - const WSAESOCKTNOSUPPORT: number; - const WSAEOPNOTSUPP: number; - const WSAEPFNOSUPPORT: number; - const WSAEAFNOSUPPORT: number; - const WSAEADDRINUSE: number; - const WSAEADDRNOTAVAIL: number; - const WSAENETDOWN: number; - const WSAENETUNREACH: number; - const WSAENETRESET: number; - const WSAECONNABORTED: number; - const WSAECONNRESET: number; - const WSAENOBUFS: number; - const WSAEISCONN: number; - const WSAENOTCONN: number; - const WSAESHUTDOWN: number; - const WSAETOOMANYREFS: number; - const WSAETIMEDOUT: number; - const WSAECONNREFUSED: number; - const WSAELOOP: number; - const WSAENAMETOOLONG: number; - const WSAEHOSTDOWN: number; - const WSAEHOSTUNREACH: number; - const WSAENOTEMPTY: number; - const WSAEPROCLIM: number; - const WSAEUSERS: number; - const WSAEDQUOT: number; - const WSAESTALE: number; - const WSAEREMOTE: number; - const WSASYSNOTREADY: number; - const WSAVERNOTSUPPORTED: number; - const WSANOTINITIALISED: number; - const WSAEDISCON: number; - const WSAENOMORE: number; - const WSAECANCELLED: number; - const WSAEINVALIDPROCTABLE: number; - const WSAEINVALIDPROVIDER: number; - const WSAEPROVIDERFAILEDINIT: number; - const WSASYSCALLFAILURE: number; - const WSASERVICE_NOT_FOUND: number; - const WSATYPE_NOT_FOUND: number; - const WSA_E_NO_MORE: number; - const WSA_E_CANCELLED: number; - const WSAEREFUSED: number; - } - namespace dlopen { - const RTLD_LAZY: number; - const RTLD_NOW: number; - const RTLD_GLOBAL: number; - const RTLD_LOCAL: number; - const RTLD_DEEPBIND: number; - } - namespace priority { - const PRIORITY_LOW: number; - const PRIORITY_BELOW_NORMAL: number; - const PRIORITY_NORMAL: number; - const PRIORITY_ABOVE_NORMAL: number; - const PRIORITY_HIGH: number; - const PRIORITY_HIGHEST: number; - } - } -} -declare module "node:os" { - export * from "os"; -} - /* The WHATWG URL class (a Node global; the es2023 lib doesn't declare it), * typed as exactly the supported surface: construction from an input and * optional base (invalid input throws a catchable TypeError, like Node), the @@ -2791,1937 +1445,6 @@ declare var URLSearchParams: { new (init?: string | string[][] | { [key: string]: string } | URLSearchParams): URLSearchParams; }; -/* node:url — the file-URL bridge pair. fileURLToPath accepts a URL value - * or a URL string and throws Node's TypeErrors (non-file scheme, encoded - * slashes, non-empty host); pathToFileURL resolves the path and percent- - * encodes it into a file: URL. The legacy API: parse() answers a Url - * object, format() serializes a Url-shaped object, a string, or a URL, - * resolve() resolves one URL string against another, the domain pair - * converts a hostname to and from its Punycode form, and urlToHttpOptions - * builds http.request options from a URL. */ -declare module "url" { - export type URL = globalThis.URL; - export type URLSearchParams = globalThis.URLSearchParams; - export const URL: typeof globalThis.URL; - export const URLSearchParams: typeof globalThis.URLSearchParams; - export function fileURLToPath(url: string | URL, options?: { windows?: boolean }): string; - export function fileURLToPathBuffer(url: string | URL, options?: { windows?: boolean }): Buffer; - export function pathToFileURL(path: string, options?: { windows?: boolean }): URL; - export interface UrlObject { - auth?: string | null | undefined; - hash?: string | null | undefined; - host?: string | null | undefined; - hostname?: string | null | undefined; - href?: string | null | undefined; - pathname?: string | null | undefined; - protocol?: string | null | undefined; - search?: string | null | undefined; - slashes?: boolean | null | undefined; - port?: string | number | null | undefined; - query?: string | null | import("querystring").ParsedUrlQueryInput | undefined; - } - export interface Url { - auth: string | null; - hash: string | null; - host: string | null; - hostname: string | null; - href: string; - path: string | null; - pathname: string | null; - protocol: string | null; - search: string | null; - slashes: boolean | null; - port: string | null; - query: string | null | import("querystring").ParsedUrlQuery; - } - export interface UrlWithParsedQuery extends Url { - query: import("querystring").ParsedUrlQuery; - } - export interface UrlWithStringQuery extends Url { - query: string | null; - } - export interface URLFormatOptions { - auth?: boolean | undefined; - fragment?: boolean | undefined; - search?: boolean | undefined; - unicode?: boolean | undefined; - } - export function parse( - urlString: string, - parseQueryString?: false, - slashesDenoteHost?: boolean, - ): UrlWithStringQuery; - export function parse( - urlString: string, - parseQueryString: true, - slashesDenoteHost?: boolean, - ): UrlWithParsedQuery; - export function parse( - urlString: string, - parseQueryString: boolean, - slashesDenoteHost?: boolean, - ): Url; - export function format(urlObject: URL, options?: URLFormatOptions): string; - export function format(urlObject: UrlObject | string): string; - export function resolve(from: string, to: string): string; - export function domainToASCII(domain: string): string; - export function domainToUnicode(domain: string): string; - /** urlToHttpOptions's null-prototype request options. */ - export interface UrlHttpOptions { - protocol: string; - hostname: string; - hash: string; - search: string; - pathname: string; - path: string; - href: string; - port?: number; - auth?: string; - } - export function urlToHttpOptions(url: URL): UrlHttpOptions; -} -declare module "node:url" { - export * from "url"; -} - -/* node:crypto: the static hashing/MAC/random/key-derivation utility slice. */ -declare module "crypto" { - import { Transform } from "stream"; - export type KeyObjectType = "secret" | "public" | "private"; - export interface JsonWebKey { - crv?: string; - d?: string; - dp?: string; - dq?: string; - e?: string; - k?: string; - kty?: string; - n?: string; - p?: string; - q?: string; - qi?: string; - x?: string; - y?: string; - [key: string]: unknown; - } - export interface JwkKeyExportOptions { - format: "jwk"; - } - export interface KeyExportOptions { - format?: "buffer" | undefined; - } - export type KeyFormat = "pem" | "der" | "jwk"; - export interface AsymmetricKeyExportOptions { - type: "pkcs1" | "spki" | "pkcs8" | "sec1"; - format: T; - cipher?: string | undefined; - passphrase?: string | Buffer | undefined; - } - export interface AsymmetricKeyDetails { - modulusLength?: number; - publicExponent?: bigint; - namedCurve?: string; - } - /* Secret keys, and RSA and EC private and public keys. */ - export class KeyObject { - private constructor(); - type: KeyObjectType; - symmetricKeySize?: number; - asymmetricKeyType?: string; - asymmetricKeyDetails?: AsymmetricKeyDetails; - export(options: AsymmetricKeyExportOptions<"pem">): string | Buffer; - export(options: AsymmetricKeyExportOptions<"der">): Buffer; - export(options?: KeyExportOptions): Buffer; - export(options: JwkKeyExportOptions): JsonWebKey; - equals(otherKeyObject: KeyObject): boolean; - } - export interface PrivateKeyInput { - key: string | Buffer; - format?: "pem" | "der" | undefined; - type?: "pkcs1" | "pkcs8" | "sec1" | undefined; - encoding?: string | undefined; - passphrase?: string | Buffer | undefined; - } - export interface PublicKeyInput { - key: string | Buffer; - format?: "pem" | "der" | undefined; - type?: "pkcs1" | "spki" | undefined; - encoding?: string | undefined; - } - export interface JsonWebKeyInput { - key: JsonWebKey; - format: "jwk"; - } - export function createPrivateKey( - key: PrivateKeyInput | string | Buffer | JsonWebKeyInput, - ): KeyObject; - export function createPublicKey( - key: PublicKeyInput | string | Buffer | KeyObject | JsonWebKeyInput, - ): KeyObject; - export interface KeyPairKeyObjectResult { - publicKey: KeyObject; - privateKey: KeyObject; - } - export interface KeyPairSyncResult { - publicKey: T1; - privateKey: T2; - } - export interface RSAKeyPairKeyObjectOptions { - modulusLength: number; - publicExponent?: number | undefined; - } - export interface ECKeyPairKeyObjectOptions { - namedCurve: string; - } - export interface RSAKeyPairOptions { - modulusLength: number; - publicExponent?: number | undefined; - publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; - privateKeyEncoding: { - type: "pkcs1" | "pkcs8"; - format: PrivF; - cipher?: string | undefined; - passphrase?: string | undefined; - }; - } - export interface ECKeyPairOptions { - namedCurve: string; - publicKeyEncoding: { type: "pkcs1" | "spki"; format: PubF }; - privateKeyEncoding: { - type: "sec1" | "pkcs8"; - format: PrivF; - cipher?: string | undefined; - passphrase?: string | undefined; - }; - } - export function generateKeyPairSync( - type: "rsa", - options: RSAKeyPairOptions<"pem", "pem">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "rsa", - options: RSAKeyPairOptions<"der", "der">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "rsa", - options: RSAKeyPairKeyObjectOptions, - ): KeyPairKeyObjectResult; - export function generateKeyPairSync( - type: "ec", - options: ECKeyPairOptions<"pem", "pem">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "ec", - options: ECKeyPairOptions<"der", "der">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "ec", - options: ECKeyPairKeyObjectOptions, - ): KeyPairKeyObjectResult; - export interface ED25519KeyPairOptions { - publicKeyEncoding: { type: "spki"; format: PubF }; - privateKeyEncoding: { - type: "pkcs8"; - format: PrivF; - cipher?: string | undefined; - passphrase?: string | undefined; - }; - } - export function generateKeyPairSync( - type: "ed25519", - options: ED25519KeyPairOptions<"pem", "pem">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "ed25519", - options: ED25519KeyPairOptions<"der", "der">, - ): KeyPairSyncResult; - export function generateKeyPairSync(type: "ed25519", options?: {}): KeyPairKeyObjectResult; - export interface X25519KeyPairOptions { - publicKeyEncoding: { type: "spki"; format: PubF }; - privateKeyEncoding: { - type: "pkcs8"; - format: PrivF; - cipher?: string | undefined; - passphrase?: string | undefined; - }; - } - export function generateKeyPairSync( - type: "x25519", - options: X25519KeyPairOptions<"pem", "pem">, - ): KeyPairSyncResult; - export function generateKeyPairSync( - type: "x25519", - options: X25519KeyPairOptions<"der", "der">, - ): KeyPairSyncResult; - export function generateKeyPairSync(type: "x25519", options?: {}): KeyPairKeyObjectResult; - export function generateKeyPair( - type: "x25519", - options: {} | undefined, - callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, - ): void; - export function generateKeyPair( - type: "ed25519", - options: {} | undefined, - callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, - ): void; - export function generateKeyPair( - type: "rsa", - options: RSAKeyPairKeyObjectOptions, - callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, - ): void; - export function generateKeyPair( - type: "ec", - options: ECKeyPairKeyObjectOptions, - callback: (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => void, - ): void; - export interface SigningOptions { - padding?: number | undefined; - saltLength?: number | undefined; - dsaEncoding?: "der" | "ieee-p1363" | undefined; - } - export interface SignPrivateKeyInput extends PrivateKeyInput, SigningOptions {} - export interface SignKeyObjectInput extends SigningOptions { - key: KeyObject; - } - export interface VerifyPublicKeyInput extends PublicKeyInput, SigningOptions {} - export interface VerifyKeyObjectInput extends SigningOptions { - key: KeyObject; - } - export function diffieHellman(options: { privateKey: KeyObject; publicKey: KeyObject }): Buffer; - export function diffieHellman( - options: { privateKey: KeyObject; publicKey: KeyObject }, - callback: (err: Error | null, secret: Buffer) => void, - ): void; - export function sign( - algorithm: string | null | undefined, - data: ArrayBufferView, - key: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, - ): Buffer; - export function verify( - algorithm: string | null | undefined, - data: ArrayBufferView, - key: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, - signature: ArrayBufferView, - ): boolean; - /* Sign and Verify accumulate update() data; they are not usable as - * writable streams. */ - export class Sign { - private constructor(); - update(data: string | ArrayBufferView): this; - update(data: string, inputEncoding: Encoding): this; - sign( - privateKey: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, - ): Buffer; - sign( - privateKey: KeyObject | string | Buffer | SignKeyObjectInput | SignPrivateKeyInput, - outputFormat: BinaryToTextEncoding, - ): string; - } - export class Verify { - private constructor(); - update(data: string | ArrayBufferView): this; - update(data: string, inputEncoding: Encoding): this; - verify( - object: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, - signature: ArrayBufferView, - ): boolean; - verify( - object: KeyObject | string | Buffer | VerifyKeyObjectInput | VerifyPublicKeyInput, - signature: string, - signatureEncoding?: BinaryToTextEncoding, - ): boolean; - } - export function createSign(algorithm: string): Sign; - export function createVerify(algorithm: string): Verify; - export function createSecretKey(key: ArrayBufferView | ArrayBufferLike): KeyObject; - export function createSecretKey(key: string, encoding: BufferEncoding): KeyObject; - export function generateKeySync(type: "hmac" | "aes", options: { length: number }): KeyObject; - export function generateKey( - type: "hmac" | "aes", - options: { length: number }, - callback: (err: Error | null, key: KeyObject) => void, - ): void; - export interface RandomUUIDOptions { - disableEntropyCache?: boolean | undefined; - } - export function randomUUID(options?: RandomUUIDOptions): string; - export function randomUUIDv7(options?: RandomUUIDOptions): string; - export function randomBytes(size: number): Buffer; - export type BinaryToTextEncoding = "base64" | "base64url" | "hex" | "binary"; - export type CharacterEncoding = "utf8" | "utf-8" | "utf16le" | "utf-16le" | "latin1"; - export type LegacyCharacterEncoding = "ascii" | "binary" | "ucs2" | "ucs-2"; - export type Encoding = BinaryToTextEncoding | CharacterEncoding | LegacyCharacterEncoding; - export interface HashOptions { - outputLength?: number | undefined; - } - /* Hash and Hmac are Transforms: written chunks update the digest, which - * the readable side delivers once the writable side ends. */ - export interface Hash extends Transform { - copy(options?: HashOptions): Hash; - update(data: string | ArrayBufferView): Hash; - update(data: string, inputEncoding: Encoding): Hash; - digest(): Buffer; - digest(encoding: BinaryToTextEncoding): string; - } - export interface Hmac extends Transform { - update(data: string | ArrayBufferView): Hmac; - update(data: string, inputEncoding: Encoding): Hmac; - digest(): Buffer; - digest(encoding: BinaryToTextEncoding): string; - } - export function createHash(algorithm: string, options?: HashOptions): Hash; - export function createHmac( - algorithm: string, - key: BinaryLike | KeyObject, - options?: { encoding?: BufferEncoding | undefined }, - ): Hmac; - export interface OneShotDigestOptions { - outputEncoding?: BinaryToTextEncoding | "buffer" | undefined; - outputLength?: number | undefined; - } - export interface OneShotDigestOptionsWithStringEncoding extends OneShotDigestOptions { - outputEncoding?: BinaryToTextEncoding | undefined; - } - export interface OneShotDigestOptionsWithBufferEncoding extends OneShotDigestOptions { - outputEncoding: "buffer"; - } - export function hash( - algorithm: string, - data: string | ArrayBufferView, - options?: OneShotDigestOptionsWithStringEncoding | BinaryToTextEncoding, - ): string; - export function hash( - algorithm: string, - data: string | ArrayBufferView, - options: OneShotDigestOptionsWithBufferEncoding | "buffer", - ): Buffer; - export function hash( - algorithm: string, - data: string | ArrayBufferView, - options: OneShotDigestOptions | BinaryToTextEncoding | "buffer", - ): string | Buffer; - export function timingSafeEqual( - a: ArrayBufferView | ArrayBufferLike, - b: ArrayBufferView | ArrayBufferLike, - ): boolean; - /* WebCrypto's getRandomValues (also the webcrypto object's): integer - * typed arrays, filled in place and returned. */ - type RandomValuesArray = - | Int8Array - | Uint8Array - | Uint8ClampedArray - | Int16Array - | Uint16Array - | Int32Array - | Uint32Array - | BigInt64Array - | BigUint64Array; - export function getRandomValues(typedArray: T): T; - /* The WebCrypto global's object and its SubtleCrypto. */ - export const webcrypto: Crypto; - export const subtle: SubtleCrypto; - export function randomFillSync( - buffer: T, - offset?: number, - size?: number, - ): T; - export function randomFill( - buffer: T, - callback: (err: Error | null, buf: T) => void, - ): void; - export function randomFill( - buffer: T, - offset: number, - callback: (err: Error | null, buf: T) => void, - ): void; - export function randomFill( - buffer: T, - offset: number, - size: number, - callback: (err: Error | null, buf: T) => void, - ): void; - export function randomInt(max: number): number; - export function randomInt(min: number, max: number): number; - export function randomInt( - max: number, - callback: (err: Error | null, value: number) => void, - ): void; - export function randomInt( - min: number, - max: number, - callback: (err: Error | null, value: number) => void, - ): void; - export function pbkdf2Sync( - password: string | Uint8Array, - salt: string | Uint8Array, - iterations: number, - keylen: number, - digest: string, - ): Buffer; - export function pbkdf2( - password: string | Uint8Array, - salt: string | Uint8Array, - iterations: number, - keylen: number, - digest: string, - callback: (error: Error | null, derivedKey: Buffer) => void, - ): void; - export function randomBytes( - size: number, - callback: (error: Error | null, buffer: Buffer) => void, - ): void; - export type BinaryLike = string | ArrayBuffer | ArrayBufferView; - /* Cipheriv/Decipheriv: aes-128/192/256 in cbc, ctr, gcm, ccm, ecb, cfb, - * cfb8, cfb1, and ofb modes and their key wraps, des-ede3 and des-ede, - * chacha20, and chacha20-poly1305. Both are Transforms: written chunks go - * through update(), and the end pushes final(). */ - export type CipherCCMTypes = "aes-128-ccm" | "aes-192-ccm" | "aes-256-ccm"; - export type CipherGCMTypes = "aes-128-gcm" | "aes-192-gcm" | "aes-256-gcm"; - export type CipherChaCha20Poly1305Types = "chacha20-poly1305"; - export type CipherKey = BinaryLike | KeyObject; - export interface CipherCCMOptions { - authTagLength: number; - } - export interface CipherGCMOptions { - authTagLength?: number; - } - export interface CipherChaCha20Poly1305Options { - authTagLength?: number; - } - export interface Cipheriv extends Transform { - update(data: string | ArrayBufferView): Buffer; - update(data: string, inputEncoding: BufferEncoding): Buffer; - update(data: ArrayBufferView, inputEncoding: undefined, outputEncoding: BufferEncoding): string; - update( - data: string, - inputEncoding: BufferEncoding | undefined, - outputEncoding: BufferEncoding, - ): string; - final(): Buffer; - final(outputEncoding: BufferEncoding): string; - setAutoPadding(autoPadding?: boolean): this; - } - export interface CipherCCM extends Cipheriv { - setAAD(buffer: ArrayBufferView, options: { plaintextLength: number }): this; - getAuthTag(): Buffer; - } - export interface CipherGCM extends Cipheriv { - setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; - getAuthTag(): Buffer; - } - export interface CipherChaCha20Poly1305 extends Cipheriv { - setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; - getAuthTag(): Buffer; - } - export interface Decipheriv extends Transform { - update(data: ArrayBufferView): Buffer; - update(data: string, inputEncoding: BufferEncoding): Buffer; - update(data: ArrayBufferView, inputEncoding: undefined, outputEncoding: BufferEncoding): string; - update( - data: string, - inputEncoding: BufferEncoding | undefined, - outputEncoding: BufferEncoding, - ): string; - final(): Buffer; - final(outputEncoding: BufferEncoding): string; - setAutoPadding(autoPadding?: boolean): this; - } - export interface DecipherCCM extends Decipheriv { - setAuthTag(buffer: ArrayBufferView): this; - setAAD(buffer: ArrayBufferView, options: { plaintextLength: number }): this; - } - export interface DecipherGCM extends Decipheriv { - setAuthTag(buffer: ArrayBufferView): this; - setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; - } - export interface DecipherChaCha20Poly1305 extends Decipheriv { - setAuthTag(buffer: ArrayBufferView): this; - setAAD(buffer: ArrayBufferView, options?: { plaintextLength: number }): this; - } - export function createCipheriv( - algorithm: CipherCCMTypes, - key: CipherKey, - iv: BinaryLike, - options: CipherCCMOptions, - ): CipherCCM; - export function createCipheriv( - algorithm: CipherGCMTypes, - key: CipherKey, - iv: BinaryLike, - options?: CipherGCMOptions, - ): CipherGCM; - export function createCipheriv( - algorithm: CipherChaCha20Poly1305Types, - key: CipherKey, - iv: BinaryLike, - options?: CipherChaCha20Poly1305Options, - ): CipherChaCha20Poly1305; - export function createCipheriv( - algorithm: string, - key: CipherKey, - iv: BinaryLike | null, - options?: { authTagLength?: number }, - ): Cipheriv; - export function createDecipheriv( - algorithm: CipherCCMTypes, - key: CipherKey, - iv: BinaryLike, - options: CipherCCMOptions, - ): DecipherCCM; - export function createDecipheriv( - algorithm: CipherGCMTypes, - key: CipherKey, - iv: BinaryLike, - options?: CipherGCMOptions, - ): DecipherGCM; - export function createDecipheriv( - algorithm: CipherChaCha20Poly1305Types, - key: CipherKey, - iv: BinaryLike, - options?: CipherChaCha20Poly1305Options, - ): DecipherChaCha20Poly1305; - export function createDecipheriv( - algorithm: string, - key: CipherKey, - iv: BinaryLike | null, - options?: { authTagLength?: number }, - ): Decipheriv; - export type ECDHKeyFormat = "compressed" | "uncompressed" | "hybrid"; - export class ECDH { - private constructor(); - static convertKey( - key: BinaryLike, - curve: string, - inputEncoding?: BinaryToTextEncoding, - outputEncoding?: "latin1" | "hex" | "base64" | "base64url", - format?: ECDHKeyFormat, - ): Buffer | string; - generateKeys(): Buffer; - generateKeys(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; - computeSecret(otherPublicKey: ArrayBufferView): Buffer; - computeSecret(otherPublicKey: string, inputEncoding: BinaryToTextEncoding): Buffer; - computeSecret(otherPublicKey: ArrayBufferView, outputEncoding: BinaryToTextEncoding): string; - computeSecret( - otherPublicKey: string, - inputEncoding: BinaryToTextEncoding, - outputEncoding: BinaryToTextEncoding, - ): string; - getPrivateKey(): Buffer; - getPrivateKey(encoding: BinaryToTextEncoding): string; - getPublicKey(encoding?: null, format?: ECDHKeyFormat): Buffer; - getPublicKey(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string; - setPrivateKey(privateKey: ArrayBufferView): void; - setPrivateKey(privateKey: string, encoding: BinaryToTextEncoding): void; - } - export function createECDH(curveName: string): ECDH; - export class DiffieHellman { - private constructor(); - generateKeys(): Buffer; - generateKeys(encoding: BinaryToTextEncoding): string; - computeSecret( - otherPublicKey: ArrayBufferView, - inputEncoding?: null, - outputEncoding?: null, - ): Buffer; - computeSecret( - otherPublicKey: string, - inputEncoding: BinaryToTextEncoding, - outputEncoding?: null, - ): Buffer; - computeSecret( - otherPublicKey: ArrayBufferView, - inputEncoding: null, - outputEncoding: BinaryToTextEncoding, - ): string; - computeSecret( - otherPublicKey: string, - inputEncoding: BinaryToTextEncoding, - outputEncoding: BinaryToTextEncoding, - ): string; - getPrime(): Buffer; - getPrime(encoding: BinaryToTextEncoding): string; - getGenerator(): Buffer; - getGenerator(encoding: BinaryToTextEncoding): string; - getPublicKey(): Buffer; - getPublicKey(encoding: BinaryToTextEncoding): string; - getPrivateKey(): Buffer; - getPrivateKey(encoding: BinaryToTextEncoding): string; - setPublicKey(publicKey: ArrayBufferView): void; - setPublicKey(publicKey: string, encoding: BufferEncoding): void; - setPrivateKey(privateKey: ArrayBufferView): void; - setPrivateKey(privateKey: string, encoding: BufferEncoding): void; - verifyError: number; - } - export type DiffieHellmanGroup = Omit; - export function createDiffieHellman(primeLength: number, generator?: number): DiffieHellman; - export function createDiffieHellman( - prime: ArrayBuffer | ArrayBufferView, - generator?: number | ArrayBuffer | ArrayBufferView, - ): DiffieHellman; - export function createDiffieHellman( - prime: ArrayBuffer | ArrayBufferView, - generator: string, - generatorEncoding: BinaryToTextEncoding, - ): DiffieHellman; - export function createDiffieHellman( - prime: string, - primeEncoding: BinaryToTextEncoding, - generator?: number | ArrayBuffer | ArrayBufferView, - ): DiffieHellman; - export function createDiffieHellman( - prime: string, - primeEncoding: BinaryToTextEncoding, - generator: string, - generatorEncoding: BinaryToTextEncoding, - ): DiffieHellman; - export function getDiffieHellman(groupName: string): DiffieHellmanGroup; - export function createDiffieHellmanGroup(name: string): DiffieHellmanGroup; - export type LargeNumberLike = ArrayBufferView | SharedArrayBuffer | ArrayBuffer | bigint; - export interface GeneratePrimeOptions { - add?: LargeNumberLike | undefined; - rem?: LargeNumberLike | undefined; - safe?: boolean | undefined; - bigint?: boolean | undefined; - } - export interface GeneratePrimeOptionsBigInt extends GeneratePrimeOptions { - bigint: true; - } - export interface GeneratePrimeOptionsArrayBuffer extends GeneratePrimeOptions { - bigint?: false | undefined; - } - export function generatePrime( - size: number, - callback: (err: Error | null, prime: ArrayBuffer) => void, - ): void; - export function generatePrime( - size: number, - options: GeneratePrimeOptionsBigInt, - callback: (err: Error | null, prime: bigint) => void, - ): void; - export function generatePrime( - size: number, - options: GeneratePrimeOptionsArrayBuffer, - callback: (err: Error | null, prime: ArrayBuffer) => void, - ): void; - export function generatePrime( - size: number, - options: GeneratePrimeOptions, - callback: (err: Error | null, prime: ArrayBuffer | bigint) => void, - ): void; - export function generatePrimeSync(size: number): ArrayBuffer; - export function generatePrimeSync(size: number, options: GeneratePrimeOptionsBigInt): bigint; - export function generatePrimeSync( - size: number, - options: GeneratePrimeOptionsArrayBuffer, - ): ArrayBuffer; - export function generatePrimeSync( - size: number, - options: GeneratePrimeOptions, - ): ArrayBuffer | bigint; - export interface CheckPrimeOptions { - checks?: number | undefined; - } - export function checkPrime( - value: LargeNumberLike, - callback: (err: Error | null, result: boolean) => void, - ): void; - export function checkPrime( - value: LargeNumberLike, - options: CheckPrimeOptions, - callback: (err: Error | null, result: boolean) => void, - ): void; - export function checkPrimeSync(candidate: LargeNumberLike, options?: CheckPrimeOptions): boolean; - export interface ScryptOptions { - N?: number; - r?: number; - p?: number; - cost?: number; - blockSize?: number; - parallelization?: number; - maxmem?: number; - } - export function scryptSync( - password: BinaryLike, - salt: BinaryLike, - keylen: number, - options?: ScryptOptions, - ): Buffer; - export function scrypt( - password: BinaryLike, - salt: BinaryLike, - keylen: number, - callback: (error: Error | null, derivedKey: Buffer) => void, - ): void; - export function scrypt( - password: BinaryLike, - salt: BinaryLike, - keylen: number, - options: ScryptOptions, - callback: (error: Error | null, derivedKey: Buffer) => void, - ): void; - export function hkdfSync( - digest: string, - ikm: BinaryLike | KeyObject, - salt: BinaryLike, - info: BinaryLike, - keylen: number, - ): ArrayBuffer; - export function hkdf( - digest: string, - ikm: BinaryLike | KeyObject, - salt: BinaryLike, - info: BinaryLike, - keylen: number, - callback: (error: Error | null, derivedKey: ArrayBuffer) => void, - ): void; - /* The lowered X509Certificate surface is the data-record slice: the - * fields below, all computed at construction (raw decodes the input, - * and the others read raw). validFrom/validTo are Node's - * ASN1_TIME_print strings ("Jul 1 00:00:00 2026 GMT"). Unparseable - * input throws Node's ERR_OSSL_PEM_NO_START_LINE Error. */ - export class X509Certificate { - constructor(buffer: Uint8Array | string); - readonly ca: boolean; - readonly fingerprint: string; - readonly fingerprint256: string; - readonly fingerprint512: string; - readonly subject: string; - readonly subjectAltName: string | undefined; - readonly infoAccess: string | undefined; - readonly issuer: string; - readonly raw: Buffer; - readonly serialNumber: string; - readonly signatureAlgorithm: string | undefined; - readonly signatureAlgorithmOid: string; - readonly validFrom: string; - readonly validFromDate: Date; - readonly validTo: string; - readonly validToDate: Date; - /* the certificate's public key; every read answers the same KeyObject */ - readonly publicKey: KeyObject; - verify(publicKey: KeyObject): boolean; - checkPrivateKey(privateKey: KeyObject): boolean; - checkIssued(otherCert: X509Certificate): boolean; - checkHost(name: string, options?: X509CheckOptions): string | undefined; - checkEmail(email: string, options?: X509CheckOptions): string | undefined; - checkIP(ip: string): string | undefined; - toLegacyObject(): import("tls").PeerCertificate; - } - export interface X509CheckOptions { - subject?: "always" | "default" | "never"; - wildcards?: boolean; - partialWildcards?: boolean; - multiLabelWildcards?: boolean; - singleLabelSubdomains?: boolean; - } - /* The crypto introspection statics: getFips() answers 0 (a compiled - * binary is never a FIPS build), the three name lists bake as fresh - * string[] literals of Node v24 answers, and constants (below) bakes - * per member like http2.constants. */ - export function getFips(): 1 | 0; - export function getCiphers(): string[]; - export type CipherMode = - | "cbc" - | "ccm" - | "cfb" - | "ctr" - | "ecb" - | "gcm" - | "ocb" - | "ofb" - | "stream" - | "wrap" - | "xts"; - export interface CipherInfoOptions { - keyLength?: number | undefined; - ivLength?: number | undefined; - } - export interface CipherInfo { - name: string; - nid: number; - blockSize?: number | undefined; - ivLength?: number | undefined; - keyLength: number; - mode: CipherMode; - } - export function getCipherInfo( - nameOrNid: string | number, - options?: CipherInfoOptions, - ): CipherInfo | undefined; - export function getHashes(): string[]; - export function getCurves(): string[]; - /* Node v24 crypto.constants, literal-typed (the Http2Constants - * stance). Every ACCESS bakes as its literal at lowering; the object - * itself never materializes (bare crypto.constants value uses fence). */ - export interface CryptoConstants { - readonly OPENSSL_VERSION_NUMBER: 810549376; - readonly SSL_OP_ALL: 2147485776; - readonly SSL_OP_ALLOW_NO_DHE_KEX: 1024; - readonly SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: 262144; - readonly SSL_OP_CIPHER_SERVER_PREFERENCE: 4194304; - readonly SSL_OP_CISCO_ANYCONNECT: 32768; - readonly SSL_OP_COOKIE_EXCHANGE: 8192; - readonly SSL_OP_CRYPTOPRO_TLSEXT_BUG: 2147483648; - readonly SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: 2048; - readonly SSL_OP_LEGACY_SERVER_CONNECT: 4; - readonly SSL_OP_NO_COMPRESSION: 131072; - readonly SSL_OP_NO_ENCRYPT_THEN_MAC: 524288; - readonly SSL_OP_NO_QUERY_MTU: 4096; - readonly SSL_OP_NO_RENEGOTIATION: 1073741824; - readonly SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: 65536; - readonly SSL_OP_NO_SSLv2: 0; - readonly SSL_OP_NO_SSLv3: 33554432; - readonly SSL_OP_NO_TICKET: 16384; - readonly SSL_OP_NO_TLSv1: 67108864; - readonly SSL_OP_NO_TLSv1_1: 268435456; - readonly SSL_OP_NO_TLSv1_2: 134217728; - readonly SSL_OP_NO_TLSv1_3: 536870912; - readonly SSL_OP_PRIORITIZE_CHACHA: 2097152; - readonly SSL_OP_TLS_ROLLBACK_BUG: 8388608; - readonly ENGINE_METHOD_RSA: 1; - readonly ENGINE_METHOD_DSA: 2; - readonly ENGINE_METHOD_DH: 4; - readonly ENGINE_METHOD_RAND: 8; - readonly ENGINE_METHOD_EC: 2048; - readonly ENGINE_METHOD_CIPHERS: 64; - readonly ENGINE_METHOD_DIGESTS: 128; - readonly ENGINE_METHOD_PKEY_METHS: 512; - readonly ENGINE_METHOD_PKEY_ASN1_METHS: 1024; - readonly ENGINE_METHOD_ALL: 65535; - readonly ENGINE_METHOD_NONE: 0; - readonly DH_CHECK_P_NOT_SAFE_PRIME: 2; - readonly DH_CHECK_P_NOT_PRIME: 1; - readonly DH_UNABLE_TO_CHECK_GENERATOR: 4; - readonly DH_NOT_SUITABLE_GENERATOR: 8; - readonly RSA_PKCS1_PADDING: 1; - readonly RSA_SSLV23_PADDING: 2; - readonly RSA_NO_PADDING: 3; - readonly RSA_PKCS1_OAEP_PADDING: 4; - readonly RSA_X931_PADDING: 5; - readonly RSA_PKCS1_PSS_PADDING: 6; - readonly RSA_PSS_SALTLEN_DIGEST: -1; - readonly RSA_PSS_SALTLEN_MAX_SIGN: -2; - readonly RSA_PSS_SALTLEN_AUTO: -2; - readonly defaultCoreCipherList: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA"; - readonly TLS1_VERSION: 769; - readonly TLS1_1_VERSION: 770; - readonly TLS1_2_VERSION: 771; - readonly TLS1_3_VERSION: 772; - readonly POINT_CONVERSION_COMPRESSED: 2; - readonly POINT_CONVERSION_UNCOMPRESSED: 4; - readonly POINT_CONVERSION_HYBRID: 6; - readonly defaultCipherList: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA"; - } - export const constants: CryptoConstants; -} -declare module "node:crypto" { - export * from "crypto"; -} - -/* node:child_process — the synchronous slice: spawnSync(command, args?) - * runs the child to completion (posix_spawn + waitpid) with stdout/stderr - * captured as utf8 strings. `status` is the exit code, or null when the - * child died to a signal OR could not be spawned at all (nonexistent - * binary, permission denied) — Node reports spawn failure through the - * result's `error` property, which this surface does not carry, and types - * stdout/stderr null there where scriptc returns "" (SEMANTICS.md). - * No shell, no options object: the command runs directly (PATH-searched, - * like Node). */ -declare module "child_process" { - export interface SpawnSyncReturns { - readonly status: number | null; - readonly stdout: string; - readonly stderr: string; - /* The termination signal's name ("SIGTERM", ...) when a signal - * killed the child (a timeout's killSignal included), null otherwise - * — Node's shape. */ - readonly signal: string | null; - /* Node's spawn-failure carrier: a real Error ("spawnSync - * ENOENT", `code` stamped) when the spawn itself failed — status is - * null and both outputs "" then — or ETIMEDOUT after a timeout kill; - * undefined otherwise. */ - readonly error?: Error; - } - /* scriptc always captures utf8 strings; pass { encoding: "utf8" } - * whenever stdout/stderr is READ so plain Node agrees (without it Node - * hands Buffers). The other supported options: timeout (ms — sends - * killSignal at the deadline and the result carries error: ETIMEDOUT + - * the signal, never a throw), killSignal (a signal NAME literal), - * stdio ("ignore"/"inherit"/"pipe" or a 3-tuple of those — captures - * read "" for non-piped streams), env (replaces the child's - * environment), and windowsHide (a POSIX no-op). */ - export function spawnSync( - command: string, - args?: string[], - options?: { - encoding?: "utf8" | "utf-8"; - timeout?: number; - killSignal?: string; - stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; - env?: { [k: string]: string | undefined }; - windowsHide?: boolean; - }, - ): SpawnSyncReturns; - - /* The asynchronous slice: spawn with stdio "ignore"/"inherit"/fd/"pipe" - * tuples (piped stdin is writable through child.stdin; piped stdout/stderr - * deliver through child.stdout/stderr) and the terminal events. "exit" - * fires once with the exit code, or - * null when the child died to a signal; "error" fires ONLY when the - * child could not be spawned at all (Node's split: a spawn failure - * emits "error" and never "exit"). "close" fires after exit/error and - * every piped stdio handle reaches EOF. Exit/close listeners may take - * Node's code and signal parameters. `on`/`once` return void here (Node - * returns the child; chaining is fenced). The event loop keeps the process - * alive until every spawned child is reaped, like Node — reaping polls at - * loop quiescence (SEMANTICS.md documents the divergence). An "error" - * event with no registered listener prints the error and exits 1, - * exactly the unhandled-'error' EventEmitter behavior. */ - export interface ChildStdin { - readonly writable: boolean; - write(chunk: string | Uint8Array): boolean; - end(): void; - destroy(): void; - on(event: "drain" | "finish", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: string, listener: (...args: any[]) => void): void; - once(event: "drain" | "finish", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: string, listener: (...args: any[]) => void): void; - } - - export interface ChildProcess extends Disposable { - ref(): void; - on(event: "spawn", listener: () => void): this; - once(event: "spawn", listener: () => void): this; - /* The exit listener may also take Node's second parameter — the - * terminating signal's name, null for a normal exit. */ - on(event: "exit", listener: (code: number | null, signal: string | null) => void): this; - on(event: "close", listener: (code: number | null, signal: string | null) => void): this; - on(event: "error", listener: (err: Error) => void): this; - once(event: "exit", listener: (code: number | null, signal: string | null) => void): this; - once(event: "close", listener: (code: number | null, signal: string | null) => void): this; - once(event: "error", listener: (err: Error) => void): this; - on(event: "message", listener: (message: any) => void): this; - on(event: "disconnect", listener: () => void): this; - once(event: "message", listener: (message: any) => void): this; - once(event: "disconnect", listener: () => void): this; - /* The lifecycle members, Node's exact shapes: pid is undefined exactly - * when the spawn failed; exitCode is null while running, the code - * after a normal exit, null for a signal death, and -errno once a - * spawn failure's "error" event has fired; kill resolves signal names - * through Node's table (unknown names throw the Unknown signal - * TypeError), answers true when the signal was sent, false once the - * child was reaped (or never spawned), and sets `killed` on success; - * unref() drops the child from the event loop's keep-alive set (it is - * still reaped while the loop runs for other reasons — SEMANTICS.md - * has the reap story). */ - readonly pid: number | undefined; - readonly exitCode: number | null; - readonly killed: boolean; - kill(signal?: string | number): boolean; - unref(): void; - readonly connected: boolean; - send(message: object, callback?: (error: Error | null) => void): boolean; - disconnect(): void; - [Symbol.dispose](): void; - /* The piped streams — non-null exactly when the matching stdio slot - * was "pipe" (Node's shape). */ - readonly stdin: ChildStdin | null; - readonly stdout: NodeJS.ReadableStream | null; - readonly stderr: NodeJS.ReadableStream | null; - } - export interface SpawnOptions { - /* The 3-tuple form admits number fds in the stdout/stderr slots — - * openSync results dup2'd into the child (the daemon-log idiom - * ["ignore", logFd, logFd]) — and "pipe" there too (child.stdout/ - * child.stderr streams); "pipe" in the stdin slot exposes - * child.stdin. */ - stdio?: "ignore" | "inherit" | "pipe" | ("ignore" | "inherit" | "pipe" | number)[]; - /* detached gives the child its own session and process group - * (POSIX_SPAWN_SETSID); env REPLACES the child environment; cwd - * sets its working directory; windowsHide is a POSIX no-op; shell - * runs the complete command string through /bin/sh or cmd.exe. */ - detached?: boolean; - env?: { [k: string]: string | undefined }; - cwd?: string; - windowsHide?: boolean; - shell?: boolean; - } - export function spawn(command: string, args?: string[], options?: SpawnOptions): ChildProcess; - export function spawn(command: string, options?: SpawnOptions): ChildProcess; - - export function fork( - modulePath: string | URL, - args?: string[], - options?: { - cwd?: string; - env?: { [k: string]: string | undefined }; - execArgv?: string[]; - silent?: boolean; - stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit" | "ipc")[]; - serialization?: "json" | "advanced"; - windowsHide?: boolean; - }, - ): ChildProcess; - - /* The synchronous exec pair: execFileSync runs a file directly (no - * shell), execSync runs a command through /bin/sh -c. Both capture - * stdout as a utf8 string (pass { encoding: "utf8" } so plain Node - * agrees — without it Node returns a Buffer) and THROW on a non-zero - * exit / signal death — the Error's message is Node's "Command failed: - * " (with the captured stderr appended). Supported options: - * encoding ("utf8"), cwd, env (REPLACES the child environment), input - * (fed to stdin), timeout (ms — SIGTERMs the child, then throws - * ETIMEDOUT), stdio ("pipe"/"ignore" or a 3-tuple of those — the - * default captures stderr AND echoes it to the parent, like Node), and - * maxBuffer (accepted, not enforced). Node's status/stdout/stderr error - * properties are not carried (SEMANTICS.md). */ - interface ExecSyncOptions { - encoding?: "utf8" | "utf-8"; - cwd?: string; - env?: { [k: string]: string | undefined }; - input?: string; - timeout?: number; - stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; - maxBuffer?: number; - killSignal?: string; - windowsHide?: boolean; - shell?: boolean; - } - /* The named options interface for the string-encoding exec form — - * @types/node's ExecFileSyncOptionsWithStringEncoding, narrowed to the - * honestly-implemented members. VALUES of this type are a real record - * (type-mapper.ts interns the shape), so a typed const or a runner function's - * options parameter flows to execFileSync at runtime — the windows-ca - * command-runner idiom. */ - export interface ExecFileSyncOptionsWithStringEncoding { - encoding: "utf8" | "utf-8"; - cwd?: string; - input?: string; - timeout?: number; - stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[]; - maxBuffer?: number; - windowsHide?: boolean; - } - export function execFileSync(file: string, args?: string[], options?: ExecSyncOptions): string; - export function execSync(command: string, options?: ExecSyncOptions): string; - export type ExecFileCallback = (error: Error | null, stdout: string, stderr: string) => void; - /* The callback form starts an asynchronous child and captures utf8 - * stdout/stderr. The no-callback overload remains for util.promisify's - * static target recognition; reached no-callback calls still fence. */ - export function execFile(file: string, callback: ExecFileCallback): ChildProcess; - export function execFile( - file: string, - args: string[] | null, - callback: ExecFileCallback, - ): ChildProcess; - export function execFile( - file: string, - args: string[] | null, - options: ExecSyncOptions, - callback: ExecFileCallback, - ): ChildProcess; - export function execFile( - file: string, - args?: string[] | null, - options?: ExecSyncOptions, - ): unknown; - /* The async shell form. The callback forms lower like execFile's, with - * the command line run by /bin/sh -c, and util.promisify recognizes it - * statically, as it does execFile; reached calls without a callback - * fence at their site. */ - export function exec( - command: string, - options?: ExecSyncOptions | ((error: Error | null, stdout: string, stderr: string) => void), - callback?: (error: Error | null, stdout: string, stderr: string) => void, - ): ChildProcess; -} -declare module "node:child_process" { - export * from "child_process"; -} - -/* node:util — compile-time projections for child_process.execFile, - * child_process.exec and fs.readFile. The first two retain Node's custom - * { stdout, stderr } result; the last resolves the callback's data - * argument. Other targets and bare - * promisify values fence per site. */ -declare module "util" { - export * as types from "util/types"; - export function getSystemErrorName(err: number): string; - export function getSystemErrorMessage(err: number): string; - export function getSystemErrorMap(): Map; - export function stripVTControlCharacters(str: string): string; - export function toUSVString(string: string): string; - export function isDeepStrictEqual(val1: unknown, val2: unknown, skipPrototype?: boolean): boolean; - export function styleText( - format: string | readonly string[], - text: string, - options?: { validateStream?: boolean; stream?: unknown }, - ): string; - export function parseEnv(content: string): { [key: string]: string | undefined }; - export function inherits(constructor: unknown, superConstructor: unknown): void; - export interface DeprecateOptions { - modifyPrototype?: boolean; - } - export function deprecate( - fn: T, - msg: string, - code?: string, - options?: DeprecateOptions, - ): T; - /* The callback receives null on success and the rejection reason - * otherwise (falsy reasons arrive as ERR_FALSY_VALUE_REJECTION). */ - export function callbackify( - fn: () => Promise, - ): (callback: (err: NodeJS.ErrnoException | null) => void) => void; - export function callbackify( - fn: () => Promise, - ): (callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void; - export function callbackify( - fn: (arg1: T1) => Promise, - ): (arg1: T1, callback: (err: NodeJS.ErrnoException | null) => void) => void; - export function callbackify( - fn: (arg1: T1) => Promise, - ): (arg1: T1, callback: (err: NodeJS.ErrnoException | null, result: TResult) => void) => void; - export function callbackify( - fn: (arg1: T1, arg2: T2) => Promise, - ): (arg1: T1, arg2: T2, callback: (err: NodeJS.ErrnoException | null) => void) => void; - export function callbackify( - fn: (arg1: T1, arg2: T2) => Promise, - ): ( - arg1: T1, - arg2: T2, - callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, - ) => void; - export function callbackify( - fn: (arg1: T1, arg2: T2, arg3: T3) => Promise, - ): (arg1: T1, arg2: T2, arg3: T3, callback: (err: NodeJS.ErrnoException | null) => void) => void; - export function callbackify( - fn: (arg1: T1, arg2: T2, arg3: T3) => Promise, - ): ( - arg1: T1, - arg2: T2, - arg3: T3, - callback: (err: NodeJS.ErrnoException | null, result: TResult) => void, - ) => void; - export type DebugLoggerFunction = (msg: string, ...param: unknown[]) => void; - export interface DebugLogger extends DebugLoggerFunction { - enabled: boolean; - } - export function debuglog( - section: string, - callback?: (fn: DebugLoggerFunction) => void, - ): DebugLogger; - export { debuglog as debug }; - export type TextEncoder = globalThis.TextEncoder; - export type TextDecoder = globalThis.TextDecoder; - export var TextEncoder: typeof globalThis.TextEncoder; - export var TextDecoder: typeof globalThis.TextDecoder; - /* stream.pipeline's overload comes first: its __promisify__ member keeps - * every other target out of it. */ - export function promisify( - fn: typeof import("node:stream").pipeline, - ): typeof import("node:stream/promises").pipeline; - export function promisify( - fn: typeof import("node:fs").readFile, - ): (path: string, encoding: "utf8" | "utf-8") => Promise; - export function promisify( - fn: (file: string, args?: string[] | null, options?: object) => unknown, - ): ( - file: string, - args?: string[], - options?: { - encoding?: "utf8" | "utf-8"; - cwd?: string; - env?: { [k: string]: string | undefined }; - timeout?: number; - maxBuffer?: number; - killSignal?: string; - windowsHide?: boolean; - }, - ) => Promise<{ stdout: string; stderr: string }>; - export function promisify( - fn: typeof globalThis.setTimeout, - ): typeof import("node:timers/promises").setTimeout; - export function promisify( - fn: typeof globalThis.setImmediate, - ): typeof import("node:timers/promises").setImmediate; - export function promisify(fn: typeof import("node:child_process").exec): ( - command: string, - options?: { - encoding?: "utf8" | "utf-8"; - cwd?: string; - env?: { [k: string]: string | undefined }; - timeout?: number; - maxBuffer?: number; - killSignal?: string; - windowsHide?: boolean; - }, - ) => Promise<{ stdout: string; stderr: string }>; - /* The zlib callback one-shots (deflate, inflate, deflateRaw, inflateRaw, - * gzip, gunzip, unzip) share one type. */ - export function promisify( - fn: typeof import("node:zlib").gzip, - ): (data: string | Uint8Array, options?: import("node:zlib").ZlibOptions) => Promise; - export function promisify( - fn: typeof import("node:stream").finished, - ): typeof import("node:stream/promises").finished; - /* The crypto callback functions: the promise settles with the callback's - * value. */ - export function promisify( - fn: typeof import("node:crypto").randomBytes, - ): (size: number) => Promise; - export function promisify( - fn: typeof import("node:crypto").pbkdf2, - ): ( - password: string | Uint8Array, - salt: string | Uint8Array, - iterations: number, - keylen: number, - digest: string, - ) => Promise; - export function promisify( - fn: typeof import("node:crypto").scrypt, - ): ( - password: import("node:crypto").BinaryLike, - salt: import("node:crypto").BinaryLike, - keylen: number, - options?: import("node:crypto").ScryptOptions, - ) => Promise; - export function promisify( - fn: typeof import("node:crypto").hkdf, - ): ( - digest: string, - ikm: import("node:crypto").BinaryLike | import("node:crypto").KeyObject, - salt: import("node:crypto").BinaryLike, - info: import("node:crypto").BinaryLike, - keylen: number, - ) => Promise; - /* A callback-only function (`obj.close.bind(obj)`): before the targets - * whose leading parameters would otherwise capture it. Builtins that take - * arguments before their callback never match these. */ - export function promisify( - fn: (callback: (err: any, result: TResult) => void) => void, - ): () => Promise; - export function promisify(fn: (callback: (err?: any) => void) => void): () => Promise; - export function promisify( - fn: typeof import("node:crypto").randomFill, - ): ( - buffer: T, - offset?: number, - size?: number, - ) => Promise; - export function promisify(fn: typeof import("node:crypto").randomInt): { - (max: number): Promise; - (min: number, max: number): Promise; - }; - export function promisify( - fn: typeof import("node:crypto").generateKey, - ): ( - type: "hmac" | "aes", - options: { length: number }, - ) => Promise; - /* dns.lookup's promise settles with { address, family } (the address - * array with options.all), and lookupService's with { hostname, service }. */ - export function promisify(fn: typeof import("node:dns").lookup): { - ( - hostname: string, - options: import("node:dns").LookupAllOptions, - ): Promise; - ( - hostname: string, - options?: number | import("node:dns").LookupOneOptions, - ): Promise; - }; - export function promisify( - fn: typeof import("node:dns").lookupService, - ): (address: string, port: number) => Promise<{ hostname: string; service: string }>; - /* Declared surface without full lowering (the type-level tolerance - * story: guarded/diagnostic-path code must TYPECHECK; a reached use - * without a lowering fences at its site). inspect's options carry the - * documented knobs through an index signature. */ - export function inspect( - object: unknown, - options?: { depth?: number | null; colors?: boolean; [k: string]: unknown }, - ): string; - export function format(format?: unknown, ...args: unknown[]): string; - export function formatWithOptions( - inspectOptions: { depth?: number | null; colors?: boolean; [k: string]: unknown }, - format?: unknown, - ...args: unknown[] - ): string; - export type ParseArgsOptionsType = "boolean" | "string"; - export interface ParseArgsOptionDescriptor { - type: ParseArgsOptionsType; - multiple?: boolean; - short?: string; - default?: string | boolean | string[] | boolean[]; - } - export interface ParseArgsOptionsConfig { - [longOption: string]: ParseArgsOptionDescriptor; - } - export interface ParseArgsConfig { - args?: readonly string[]; - options?: ParseArgsOptionsConfig; - strict?: boolean; - allowPositionals?: boolean; - allowNegative?: boolean; - tokens?: boolean; - } - export type ParseArgsToken = - | { - kind: "option"; - index: number; - name: string; - rawName: string; - value: string | undefined; - inlineValue: boolean | undefined; - } - | { kind: "positional"; index: number; value: string } - | { kind: "option-terminator"; index: number }; - export interface ParseArgsResult { - values: { [longOption: string]: undefined | string | boolean | Array }; - positionals: string[]; - tokens?: ParseArgsToken[]; - } - export function parseArgs(config?: ParseArgsConfig): ParseArgsResult; - /* util.getCallSites (Node ≥22.9): the captured-frame slice harness - * code reads. No stack bookkeeping exists in a compiled binary — every - * reached call fences per site. */ - export interface CallSite { - readonly functionName: string; - readonly scriptName: string; - readonly lineNumber: number; - readonly column: number; - } - export function getCallSites(frameCount?: number): CallSite[]; -} -declare module "node:util" { - export * from "util"; -} - -/* node:util/types — native runtime brands. Other forms retain their - * per-member lowering boundaries. */ -declare module "util/types" { - export function isAnyArrayBuffer(value: unknown): boolean; - export function isArrayBuffer(value: unknown): value is ArrayBuffer; - export function isArrayBufferView(value: unknown): value is ArrayBufferView; - export function isDataView(value: unknown): value is DataView; - export function isTypedArray(value: unknown): boolean; - export function isUint8Array(value: unknown): value is Uint8Array; - export function isUint8ClampedArray(value: unknown): value is Uint8ClampedArray; - export function isUint16Array(value: unknown): value is Uint16Array; - export function isUint32Array(value: unknown): value is Uint32Array; - export function isInt8Array(value: unknown): value is Int8Array; - export function isInt16Array(value: unknown): value is Int16Array; - export function isInt32Array(value: unknown): value is Int32Array; - export function isFloat16Array(value: unknown): value is Float16Array; - export function isFloat32Array(value: unknown): value is Float32Array; - export function isFloat64Array(value: unknown): value is Float64Array; - export function isMap(value: unknown): value is Map; - export function isSet(value: unknown): value is Set; - export function isWeakMap(value: unknown): value is WeakMap; - export function isWeakSet(value: unknown): value is WeakSet; - export function isRegExp(value: unknown): value is RegExp; - export function isNativeError(value: unknown): value is Error; - export function isPromise(value: unknown): value is Promise; - export function isProxy(value: unknown): boolean; - export function isAsyncFunction(value: unknown): boolean; - export function isGeneratorFunction(value: unknown): boolean; - export function isGeneratorObject(value: unknown): boolean; - export function isModuleNamespaceObject(value: unknown): boolean; - export function isDate(value: unknown): value is Date; - export function isSharedArrayBuffer(value: unknown): value is SharedArrayBuffer; - export function isMapIterator(value: unknown): boolean; - export function isSetIterator(value: unknown): boolean; - export function isKeyObject(value: unknown): boolean; - export function isNumberObject(value: unknown): boolean; - export function isStringObject(value: unknown): boolean; - export function isBooleanObject(value: unknown): boolean; - export function isBigIntObject(value: unknown): boolean; - export function isSymbolObject(value: unknown): boolean; - export function isBoxedPrimitive(value: unknown): boolean; - export function isBigInt64Array(value: unknown): value is BigInt64Array; - export function isBigUint64Array(value: unknown): value is BigUint64Array; - export function isCryptoKey(value: unknown): boolean; - export function isExternal(value: unknown): boolean; -} -declare module "node:util/types" { - export * from "util/types"; -} - -/* node:assert — the static assertion surface. The module object IS a - * callable function (`assert(x)` is `assert.ok(x)`), so the declaration - * is the callable-namespace `export =` shape and BOTH `import assert - * from "node:assert"` and `const assert = require("assert")` bind the - * callable; named imports take the members. Failures throw - * AssertionError (name "AssertionError", code "ERR_ASSERTION" — catch and - * read .name/.message/.code). strictEqual/deepStrictEqual compare with - * Object.is over scalars; deepStrictEqual compares composites - * structurally per their static types. The messages here are plain - * strings (Node accepts Errors — that form fences per site), and the - * shallow legacy pair (equal/notEqual) follows Node's == coercions for - * primitive and primitive-union operands, including Node v24's NaN - * exception. deepEqual/notDeepEqual remain declared-but-fenced; - * assert/strict's loose names alias the strict forms and lower. */ -/* node:test — the in-process test runner (prefix-only in Node too: - * require("test") is MODULE_NOT_FOUND). test/it register tests (sync and - * async bodies, skip/todo/only options and method twins), describe/suite - * group them (bodies run at registration, Node's collection phase), - * before/after/beforeEach/afterEach hook the enclosing suite, and the - * TestContext argument carries subtests (t.test), t.skip/t.todo, - * t.diagnostic, and t.assert. mock/run/snapshot are DECLARED so real - * suites typecheck but fence at their use sites (no lowering). */ -declare module "node:test" { - type TestFn = (t: TestContext) => void | Promise; - type SuiteFn = (s: SuiteContext) => void; - type HookFn = (c: TestContext | SuiteContext) => void | Promise; - /* The describe/suite callback's context, and the before/after hook - * context of a suite. */ - interface SuiteContext { - readonly name: string; - readonly fullName: string; - readonly filePath: string | undefined; - readonly attempt: number; - readonly passed: boolean; - diagnostic(message: string): void; - } - interface TestOptions { - skip?: boolean | string; - todo?: boolean | string; - only?: boolean; - concurrency?: number | boolean; - timeout?: number; - plan?: number; - } - interface TestContext { - readonly name: string; - readonly fullName: string; - readonly filePath: string | undefined; - readonly attempt: number; - readonly passed: boolean; - readonly workerId: number | undefined; - readonly tags: readonly string[]; - readonly assert: TestContextAssert; - readonly mock: MockTracker; - test(name?: string, fn?: TestFn): Promise; - test(name?: string, options?: TestOptions, fn?: TestFn): Promise; - test(options?: TestOptions, fn?: TestFn): Promise; - test(fn?: TestFn): Promise; - skip(message?: string): void; - todo(message?: string): void; - diagnostic(message: string): void; - plan(count: number): void; - after(fn: HookFn): void; - before(fn: HookFn): void; - beforeEach(fn: HookFn): void; - afterEach(fn: HookFn): void; - } - interface AssertSnapshotOptions { - readonly serializers?: ReadonlyArray<(value: any) => any> | undefined; - } - interface TestContextAssert { - snapshot(value: any, options?: AssertSnapshotOptions): void; - fileSnapshot(value: any, path: string, options?: AssertSnapshotOptions): void; - ok(value: unknown, message?: string): void; - strictEqual(actual: unknown, expected: unknown, message?: string): void; - notStrictEqual(actual: unknown, expected: unknown, message?: string): void; - deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - equal(actual: unknown, expected: unknown, message?: string): void; - notEqual(actual: unknown, expected: unknown, message?: string): void; - deepEqual(actual: unknown, expected: unknown, message?: string): void; - notDeepEqual(actual: unknown, expected: unknown, message?: string): void; - fail(message?: string): never; - throws(fn: () => unknown, expected?: unknown, message?: string): void; - match(value: string, regExp: RegExp, message?: string): void; - doesNotMatch(value: string, regExp: RegExp, message?: string): void; - } - /* The name, options and function are each optional, as in Node: a test - * without a name takes the function's name, or "". */ - function test(name?: string, fn?: TestFn): Promise; - function test(name?: string, options?: TestOptions, fn?: TestFn): Promise; - function test(options?: TestOptions, fn?: TestFn): Promise; - function test(fn?: TestFn): Promise; - namespace test { - function skip(name?: string, fn?: TestFn): Promise; - function skip(name?: string, options?: TestOptions, fn?: TestFn): Promise; - function skip(options?: TestOptions, fn?: TestFn): Promise; - function skip(fn?: TestFn): Promise; - function todo(name?: string, fn?: TestFn): Promise; - function todo(name?: string, options?: TestOptions, fn?: TestFn): Promise; - function todo(options?: TestOptions, fn?: TestFn): Promise; - function todo(fn?: TestFn): Promise; - function only(name?: string, fn?: TestFn): Promise; - function only(name?: string, options?: TestOptions, fn?: TestFn): Promise; - function only(options?: TestOptions, fn?: TestFn): Promise; - function only(fn?: TestFn): Promise; - } - const it: typeof test; - function describe(name?: string, fn?: SuiteFn): void; - function describe(name?: string, options?: TestOptions, fn?: SuiteFn): void; - function describe(options?: TestOptions, fn?: SuiteFn): void; - function describe(fn?: SuiteFn): void; - namespace describe { - function skip(name?: string, fn?: SuiteFn): void; - function skip(name?: string, options?: TestOptions, fn?: SuiteFn): void; - function skip(options?: TestOptions, fn?: SuiteFn): void; - function skip(fn?: SuiteFn): void; - function todo(name?: string, fn?: SuiteFn): void; - function todo(name?: string, options?: TestOptions, fn?: SuiteFn): void; - function todo(options?: TestOptions, fn?: SuiteFn): void; - function todo(fn?: SuiteFn): void; - function only(name?: string, fn?: SuiteFn): void; - function only(name?: string, options?: TestOptions, fn?: SuiteFn): void; - function only(options?: TestOptions, fn?: SuiteFn): void; - function only(fn?: SuiteFn): void; - } - const suite: typeof describe; - function before(fn: HookFn): void; - function after(fn: HookFn): void; - function beforeEach(fn: HookFn): void; - function afterEach(fn: HookFn): void; - /* The mock function surface: mock.fn, mock.method and their t.mock - * twins, the tracker's restoreAll and reset, and the calls log and the - * implementation controls on fn.mock. */ - interface MockFunctionCall< - F extends Function, - ReturnType = F extends (...args: any) => infer T ? T : unknown, - Args = F extends (...args: infer Y) => any ? Y : unknown[], - > { - arguments: Args; - error: unknown | undefined; - result: ReturnType | undefined; - this: unknown; - } - interface MockFunctionContext { - readonly calls: MockFunctionCall[]; - callCount(): number; - mockImplementation(implementation: F): void; - mockImplementationOnce(implementation: F, onCall?: number): void; - resetCalls(): void; - restore(): void; - } - type Mock = F & { mock: MockFunctionContext }; - interface MockFunctionOptions { - times?: number; - } - type FunctionPropertyNames = { - [K in keyof T]: T[K] extends Function ? K : never; - }[keyof T]; - interface MockTracker { - fn undefined>(original?: F): Mock; - fn undefined, Implementation extends Function = F>( - original?: F, - implementation?: Implementation, - ): Mock; - method>( - object: MockedObject, - methodName: MethodName, - options?: MockFunctionOptions, - ): MockedObject[MethodName] extends Function ? Mock : never; - method< - MockedObject extends object, - MethodName extends FunctionPropertyNames, - Implementation extends Function, - >( - object: MockedObject, - methodName: MethodName, - implementation: Implementation, - options?: MockFunctionOptions, - ): MockedObject[MethodName] extends Function - ? Mock - : never; - reset(): void; - restoreAll(): void; - readonly timers: MockTimers; - } - interface MockTimersOptions { - apis: ReadonlyArray<"setInterval" | "setTimeout" | "setImmediate" | "Date">; - now?: number | Date | undefined; - } - interface MockTimers { - enable(options?: MockTimersOptions): void; - setTime(milliseconds?: number): void; - reset(): void; - tick(milliseconds?: number): void; - runAll(): void; - [Symbol.dispose](): void; - } - const mock: MockTracker; - /* The context of the test, suite or hook running in the current async - * context; undefined outside one. */ - function getTestContext(): TestContext | SuiteContext | undefined; - /* Declared for typechecking only — every use fences (no lowering). - * `assert` is the module-level custom-assertion registry (v24's - * `const { assert } = require('node:test')`), not TestContext.assert. */ - const assert: { register(name: string, fn: (...args: unknown[]) => unknown): void }; - function run(options?: unknown): unknown; - function snapshot(...args: unknown[]): unknown; - export { - test, - it, - describe, - suite, - before, - after, - beforeEach, - afterEach, - mock, - assert, - getTestContext, - run, - snapshot, - TestContext, - SuiteContext, - TestOptions, - Mock, - MockFunctionContext, - MockFunctionCall, - MockTracker, - MockTimers, - MockTimersOptions, - }; - export default test; -} - -declare module "assert" { - function assert(value: unknown, message?: string): void; - namespace assert { - interface AssertionErrorOptions { - message?: string | undefined; - actual?: unknown; - expected?: unknown; - operator?: string | undefined; - stackStartFn?: Function | undefined; - diff?: "simple" | "full" | undefined; - } - class AssertionError extends Error { - constructor(options: AssertionErrorOptions); - actual: unknown; - expected: unknown; - generatedMessage: boolean; - code: "ERR_ASSERTION"; - operator: string; - } - function ok(value: unknown, message?: string): void; - function strictEqual(actual: unknown, expected: unknown, message?: string): void; - function notStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function partialDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function equal(actual: unknown, expected: unknown, message?: string): void; - function notEqual(actual: unknown, expected: unknown, message?: string): void; - function deepEqual(actual: unknown, expected: unknown, message?: string): void; - function notDeepEqual(actual: unknown, expected: unknown, message?: string): void; - function fail(message?: string): never; - function throws(fn: () => unknown, expected?: unknown, message?: string): void; - function doesNotThrow(fn: () => unknown, expected?: unknown, message?: string): void; - function rejects( - fn: (() => Promise) | Promise, - expected?: unknown, - message?: string, - ): Promise; - function doesNotReject( - fn: (() => Promise) | Promise, - expected?: unknown, - message?: string, - ): Promise; - function match(value: string, regExp: RegExp, message?: string): void; - function doesNotMatch(value: string, regExp: RegExp, message?: string): void; - function ifError(value: unknown): void; - const strict: typeof import("assert/strict"); - interface AssertOptions { - diff?: "simple" | "full" | undefined; - strict?: boolean | undefined; - skipPrototype?: boolean | undefined; - } - type AssertMethodNames = - | "deepEqual" - | "deepStrictEqual" - | "doesNotMatch" - | "doesNotReject" - | "doesNotThrow" - | "equal" - | "fail" - | "ifError" - | "match" - | "notDeepEqual" - | "notDeepStrictEqual" - | "notEqual" - | "notStrictEqual" - | "ok" - | "partialDeepStrictEqual" - | "rejects" - | "strictEqual" - | "throws"; - interface Assert extends Pick {} - interface AssertStrict extends Pick {} - const Assert: { - new (options?: AssertOptions & { strict?: true | undefined }): AssertStrict; - new (options: AssertOptions): Assert; - }; - } - export = assert; -} -declare module "node:assert" { - import assert = require("assert"); - export = assert; -} -/* node:assert/strict — the same surface with the loose names bound to the - * strict comparisons (equal IS strictEqual, ...), exactly Node's aliasing; - * the module object is callable like assert's. */ -declare module "assert/strict" { - function strict(value: unknown, message?: string): void; - namespace strict { - function ok(value: unknown, message?: string): void; - function equal(actual: unknown, expected: unknown, message?: string): void; - function notEqual(actual: unknown, expected: unknown, message?: string): void; - function deepEqual(actual: unknown, expected: unknown, message?: string): void; - function notDeepEqual(actual: unknown, expected: unknown, message?: string): void; - function strictEqual(actual: unknown, expected: unknown, message?: string): void; - function notStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function deepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function notDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function partialDeepStrictEqual(actual: unknown, expected: unknown, message?: string): void; - function fail(message?: string): never; - function throws(fn: () => unknown, expected?: unknown, message?: string): void; - function doesNotThrow(fn: () => unknown, expected?: unknown, message?: string): void; - function rejects( - fn: (() => Promise) | Promise, - expected?: unknown, - message?: string, - ): Promise; - function doesNotReject( - fn: (() => Promise) | Promise, - expected?: unknown, - message?: string, - ): Promise; - function match(value: string, regExp: RegExp, message?: string): void; - function doesNotMatch(value: string, regExp: RegExp, message?: string): void; - function ifError(value: unknown): void; - const strict: typeof import("assert/strict"); - type AssertOptions = import("assert").AssertOptions; - type Assert = import("assert").Assert; - type AssertStrict = import("assert").AssertStrict; - const Assert: typeof import("assert").Assert; - } - export = strict; -} -declare module "node:assert/strict" { - import strict = require("assert/strict"); - export = strict; -} - -/* node:string_decoder — StringDecoder over Node's whole encoding set: - * write() decodes complete sequences and buffers a trailing partial one - * across chunk boundaries (utf8's truncated sequence, base64's mod-3 - * remainder, utf16le's odd byte / trailing lead surrogate; latin1, ascii - * and hex are stateless); end() flushes the buffered partial. `encoding` - * answers the normalized name. Node's exact algorithms (SEMANTICS.md); - * the encoding must be a literal, and end(buffer) fences — write the - * buffer, then end(). */ -declare module "string_decoder" { - export class StringDecoder { - constructor(encoding?: BufferEncoding); - readonly encoding: string; - write(buffer: Uint8Array): string; - end(buffer?: Uint8Array): string; - } -} -declare module "node:string_decoder" { - export * from "string_decoder"; -} - -/* node:querystring — Node's legacy query-string codec (NOT - * URLSearchParams: '+' means space on the parse side and escape encodes - * spaces as %20). parse answers the null-prototype dictionary as a pure - * index-signature record (repeated keys become string[] buckets; - * @types/node's Dict adds an undefined arm the lowering tolerates); - * stringify serializes string/number/boolean values and arrays of those - * (Node's rules: arrays expand to repeated keys, null/undefined and - * anything else serialize as the empty value). The maxKeys option lowers - * (0 removes the cap, Node's rule); the custom encoder/decoder options - * typecheck under the options-record stance and fence by name at the - * call. decode/encode are Node's own aliases of parse/stringify. */ -declare module "querystring" { - export interface ParseOptions { - maxKeys?: number; - decodeURIComponent?: (str: string) => string; - [option: string]: unknown; - } - export interface StringifyOptions { - encodeURIComponent?: (str: string) => string; - [option: string]: unknown; - } - export interface ParsedUrlQuery { - [key: string]: string | string[] | undefined; - } - export interface ParsedUrlQueryInput { - [key: string]: - | string - | number - | boolean - | bigint - | ReadonlyArray - | null - | undefined; - } - export function parse( - str: string, - sep?: string | null, - eq?: string | null, - options?: ParseOptions, - ): ParsedUrlQuery; - export function stringify( - obj?: ParsedUrlQueryInput, - sep?: string | null, - eq?: string | null, - options?: StringifyOptions, - ): string; - export const decode: typeof parse; - export const encode: typeof stringify; - export function escape(str: string): string; - export function unescape(str: string): string; -} -declare module "node:querystring" { - export * from "querystring"; -} - -/* node:punycode (deprecated, DEP0040) — punycode.js 2.1.0: decode and - * toUnicode throw its RangeErrors on malformed input; ucs2 converts - * between strings and UTF-16 code point arrays. */ -declare module "punycode" { - export function decode(string: string): string; - export function encode(string: string): string; - export function toUnicode(domain: string): string; - export function toASCII(domain: string): string; - export namespace ucs2 { - function decode(string: string): number[]; - function encode(codePoints: readonly number[]): string; - } - export const version: string; -} -declare module "node:punycode" { - export * from "punycode"; -} - -/* node:constants (deprecated, DEP0008) — one frozen object: os.constants' - * dlopen, errno, priority and signals groups, fs.constants, and - * crypto.constants. A platform's missing constant reads as undefined. */ -declare module "constants" { - const constants: { readonly [name: string]: number } & { - readonly defaultCoreCipherList: string; - readonly defaultCipherList: string; - }; - export = constants; -} -declare module "node:constants" { - import constants = require("constants"); - export = constants; -} - /* node:readline — the question/close slice: createInterface over exactly * { input: process.stdin, output: process.stdout }, question(query, cb) * (the query writes to stdout, the callback gets the next line's text), @@ -4919,2484 +1642,6 @@ declare module "node:readline/promises" { export * from "readline/promises"; } -/* node:zlib — the one-shot zlib/raw/gzip codecs lower for string/Buffer - * inputs with Node's default options; libz links only into zlib-using - * binaries. Callback forms use the executable worker pool. Options stay - * declared so reached uses receive a named SC2020 refusal. The Deflate, - * Inflate, Gzip, Gunzip, DeflateRaw, InflateRaw, Unzip, BrotliCompress - * and BrotliDecompress streams are runtime Transform streams; Zstd remains - * outside the lowered surface. */ -declare module "zlib" { - import { Transform } from "stream"; - export interface ZlibOptions { - [option: string]: unknown; - } - export type ZlibCallback = (error: NodeJS.ErrnoException | null, result: Buffer) => void; - export function deflate(data: string | Uint8Array, callback: ZlibCallback): void; - export function deflate( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function deflateSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function inflate(data: string | Uint8Array, callback: ZlibCallback): void; - export function inflate( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function inflateSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function deflateRaw(data: string | Uint8Array, callback: ZlibCallback): void; - export function deflateRaw( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function deflateRawSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function inflateRaw(data: string | Uint8Array, callback: ZlibCallback): void; - export function inflateRaw( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function inflateRawSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function gzip(data: string | Uint8Array, callback: ZlibCallback): void; - export function gzip( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function gzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function gunzip(data: string | Uint8Array, callback: ZlibCallback): void; - export function gunzip( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function gunzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function unzip(data: string | Uint8Array, callback: ZlibCallback): void; - export function unzip( - data: string | Uint8Array, - options: ZlibOptions, - callback: ZlibCallback, - ): void; - export function unzipSync(data: string | Uint8Array, options?: ZlibOptions): Buffer; - export function crc32(data: string | Uint8Array, value?: number): number; - /* Brotli over the vendored brotli 1.2.0 (scr_brotli.c): `params` keys - * are the numeric BROTLI_PARAM_* and BROTLI_DECODER_PARAM_* constants. */ - export interface BrotliOptions { - [option: string]: unknown; - } - export function brotliCompress(data: string | Uint8Array, callback: ZlibCallback): void; - export function brotliCompress( - data: string | Uint8Array, - options: BrotliOptions, - callback: ZlibCallback, - ): void; - export function brotliCompressSync(data: string | Uint8Array, options?: BrotliOptions): Buffer; - export function brotliDecompress(data: string | Uint8Array, callback: ZlibCallback): void; - export function brotliDecompress( - data: string | Uint8Array, - options: BrotliOptions, - callback: ZlibCallback, - ): void; - export function brotliDecompressSync(data: string | Uint8Array, options?: BrotliOptions): Buffer; - /* The stream classes: runtime Transform streams over the zlib engine - * (scr_zlib_stream.c). createGzip(options) and new Gzip(options) build - * the same stream; options are validated at runtime with Node's rules. - * Brotli streams have no params(), as in Node. */ - class ZlibBase extends Transform { - readonly bytesWritten: number; - flush(kind?: number, callback?: () => void): void; - flush(callback: () => void): void; - close(callback?: () => void): void; - reset(): void; - } - class Zlib extends ZlibBase { - params(level: number, strategy: number, callback?: () => void): void; - } - export class BrotliCompress extends ZlibBase { - constructor(options?: BrotliOptions); - } - export class BrotliDecompress extends ZlibBase { - constructor(options?: BrotliOptions); - } - export function createBrotliCompress(options?: BrotliOptions): BrotliCompress; - export function createBrotliDecompress(options?: BrotliOptions): BrotliDecompress; - export class Deflate extends Zlib { - constructor(options?: ZlibOptions); - } - export class Inflate extends Zlib { - constructor(options?: ZlibOptions); - } - export class Gzip extends Zlib { - constructor(options?: ZlibOptions); - } - export class Gunzip extends Zlib { - constructor(options?: ZlibOptions); - } - export class DeflateRaw extends Zlib { - constructor(options?: ZlibOptions); - } - export class InflateRaw extends Zlib { - constructor(options?: ZlibOptions); - } - export class Unzip extends Zlib { - constructor(options?: ZlibOptions); - } - export function createDeflate(options?: ZlibOptions): Deflate; - export function createInflate(options?: ZlibOptions): Inflate; - export function createGzip(options?: ZlibOptions): Gzip; - export function createGunzip(options?: ZlibOptions): Gunzip; - export function createDeflateRaw(options?: ZlibOptions): DeflateRaw; - export function createInflateRaw(options?: ZlibOptions): InflateRaw; - export function createUnzip(options?: ZlibOptions): Unzip; - /* Node v24 zlib.constants, literal-typed (the CryptoConstants stance): - * every access bakes as its literal; the object never materializes. The - * members outside BROTLI_* are also module properties (zlib.Z_SYNC_FLUSH). */ - export interface ZlibConstants { - readonly Z_NO_FLUSH: 0; - readonly Z_PARTIAL_FLUSH: 1; - readonly Z_SYNC_FLUSH: 2; - readonly Z_FULL_FLUSH: 3; - readonly Z_FINISH: 4; - readonly Z_BLOCK: 5; - readonly Z_OK: 0; - readonly Z_STREAM_END: 1; - readonly Z_NEED_DICT: 2; - readonly Z_ERRNO: -1; - readonly Z_STREAM_ERROR: -2; - readonly Z_DATA_ERROR: -3; - readonly Z_MEM_ERROR: -4; - readonly Z_BUF_ERROR: -5; - readonly Z_VERSION_ERROR: -6; - readonly Z_NO_COMPRESSION: 0; - readonly Z_BEST_SPEED: 1; - readonly Z_BEST_COMPRESSION: 9; - readonly Z_DEFAULT_COMPRESSION: -1; - readonly Z_FILTERED: 1; - readonly Z_HUFFMAN_ONLY: 2; - readonly Z_RLE: 3; - readonly Z_FIXED: 4; - readonly Z_DEFAULT_STRATEGY: 0; - readonly ZLIB_VERNUM: 4897; - readonly DEFLATE: 1; - readonly INFLATE: 2; - readonly GZIP: 3; - readonly GUNZIP: 4; - readonly DEFLATERAW: 5; - readonly INFLATERAW: 6; - readonly UNZIP: 7; - readonly BROTLI_DECODE: 8; - readonly BROTLI_ENCODE: 9; - readonly ZSTD_DECOMPRESS: 11; - readonly ZSTD_COMPRESS: 10; - readonly Z_MIN_WINDOWBITS: 8; - readonly Z_MAX_WINDOWBITS: 15; - readonly Z_DEFAULT_WINDOWBITS: 15; - readonly Z_MIN_CHUNK: 64; - readonly Z_MAX_CHUNK: number; - readonly Z_DEFAULT_CHUNK: 16384; - readonly Z_MIN_MEMLEVEL: 1; - readonly Z_MAX_MEMLEVEL: 9; - readonly Z_DEFAULT_MEMLEVEL: 8; - readonly Z_MIN_LEVEL: -1; - readonly Z_MAX_LEVEL: 9; - readonly Z_DEFAULT_LEVEL: -1; - readonly BROTLI_OPERATION_PROCESS: 0; - readonly BROTLI_OPERATION_FLUSH: 1; - readonly BROTLI_OPERATION_FINISH: 2; - readonly BROTLI_OPERATION_EMIT_METADATA: 3; - readonly BROTLI_PARAM_MODE: 0; - readonly BROTLI_MODE_GENERIC: 0; - readonly BROTLI_MODE_TEXT: 1; - readonly BROTLI_MODE_FONT: 2; - readonly BROTLI_DEFAULT_MODE: 0; - readonly BROTLI_PARAM_QUALITY: 1; - readonly BROTLI_MIN_QUALITY: 0; - readonly BROTLI_MAX_QUALITY: 11; - readonly BROTLI_DEFAULT_QUALITY: 11; - readonly BROTLI_PARAM_LGWIN: 2; - readonly BROTLI_MIN_WINDOW_BITS: 10; - readonly BROTLI_MAX_WINDOW_BITS: 24; - readonly BROTLI_LARGE_MAX_WINDOW_BITS: 30; - readonly BROTLI_DEFAULT_WINDOW: 22; - readonly BROTLI_PARAM_LGBLOCK: 3; - readonly BROTLI_MIN_INPUT_BLOCK_BITS: 16; - readonly BROTLI_MAX_INPUT_BLOCK_BITS: 24; - readonly BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING: 4; - readonly BROTLI_PARAM_SIZE_HINT: 5; - readonly BROTLI_PARAM_LARGE_WINDOW: 6; - readonly BROTLI_PARAM_NPOSTFIX: 7; - readonly BROTLI_PARAM_NDIRECT: 8; - readonly BROTLI_DECODER_RESULT_ERROR: 0; - readonly BROTLI_DECODER_RESULT_SUCCESS: 1; - readonly BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 2; - readonly BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 3; - readonly BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION: 0; - readonly BROTLI_DECODER_PARAM_LARGE_WINDOW: 1; - readonly BROTLI_DECODER_NO_ERROR: 0; - readonly BROTLI_DECODER_SUCCESS: 1; - readonly BROTLI_DECODER_NEEDS_MORE_INPUT: 2; - readonly BROTLI_DECODER_NEEDS_MORE_OUTPUT: 3; - readonly BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE: -1; - readonly BROTLI_DECODER_ERROR_FORMAT_RESERVED: -2; - readonly BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE: -3; - readonly BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET: -4; - readonly BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME: -5; - readonly BROTLI_DECODER_ERROR_FORMAT_CL_SPACE: -6; - readonly BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE: -7; - readonly BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT: -8; - readonly BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1: -9; - readonly BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2: -10; - readonly BROTLI_DECODER_ERROR_FORMAT_TRANSFORM: -11; - readonly BROTLI_DECODER_ERROR_FORMAT_DICTIONARY: -12; - readonly BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS: -13; - readonly BROTLI_DECODER_ERROR_FORMAT_PADDING_1: -14; - readonly BROTLI_DECODER_ERROR_FORMAT_PADDING_2: -15; - readonly BROTLI_DECODER_ERROR_FORMAT_DISTANCE: -16; - readonly BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET: -19; - readonly BROTLI_DECODER_ERROR_INVALID_ARGUMENTS: -20; - readonly BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES: -21; - readonly BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS: -22; - readonly BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP: -25; - readonly BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1: -26; - readonly BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2: -27; - readonly BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES: -30; - readonly BROTLI_DECODER_ERROR_UNREACHABLE: -31; - readonly ZSTD_e_continue: 0; - readonly ZSTD_e_flush: 1; - readonly ZSTD_e_end: 2; - readonly ZSTD_fast: 1; - readonly ZSTD_dfast: 2; - readonly ZSTD_greedy: 3; - readonly ZSTD_lazy: 4; - readonly ZSTD_lazy2: 5; - readonly ZSTD_btlazy2: 6; - readonly ZSTD_btopt: 7; - readonly ZSTD_btultra: 8; - readonly ZSTD_btultra2: 9; - readonly ZSTD_c_compressionLevel: 100; - readonly ZSTD_c_windowLog: 101; - readonly ZSTD_c_hashLog: 102; - readonly ZSTD_c_chainLog: 103; - readonly ZSTD_c_searchLog: 104; - readonly ZSTD_c_minMatch: 105; - readonly ZSTD_c_targetLength: 106; - readonly ZSTD_c_strategy: 107; - readonly ZSTD_c_enableLongDistanceMatching: 160; - readonly ZSTD_c_ldmHashLog: 161; - readonly ZSTD_c_ldmMinMatch: 162; - readonly ZSTD_c_ldmBucketSizeLog: 163; - readonly ZSTD_c_ldmHashRateLog: 164; - readonly ZSTD_c_contentSizeFlag: 200; - readonly ZSTD_c_checksumFlag: 201; - readonly ZSTD_c_dictIDFlag: 202; - readonly ZSTD_c_nbWorkers: 400; - readonly ZSTD_c_jobSize: 401; - readonly ZSTD_c_overlapLog: 402; - readonly ZSTD_d_windowLogMax: 100; - readonly ZSTD_CLEVEL_DEFAULT: 3; - readonly ZSTD_error_no_error: 0; - readonly ZSTD_error_GENERIC: 1; - readonly ZSTD_error_prefix_unknown: 10; - readonly ZSTD_error_version_unsupported: 12; - readonly ZSTD_error_frameParameter_unsupported: 14; - readonly ZSTD_error_frameParameter_windowTooLarge: 16; - readonly ZSTD_error_corruption_detected: 20; - readonly ZSTD_error_checksum_wrong: 22; - readonly ZSTD_error_literals_headerWrong: 24; - readonly ZSTD_error_dictionary_corrupted: 30; - readonly ZSTD_error_dictionary_wrong: 32; - readonly ZSTD_error_dictionaryCreation_failed: 34; - readonly ZSTD_error_parameter_unsupported: 40; - readonly ZSTD_error_parameter_combination_unsupported: 41; - readonly ZSTD_error_parameter_outOfBound: 42; - readonly ZSTD_error_tableLog_tooLarge: 44; - readonly ZSTD_error_maxSymbolValue_tooLarge: 46; - readonly ZSTD_error_maxSymbolValue_tooSmall: 48; - readonly ZSTD_error_stabilityCondition_notRespected: 50; - readonly ZSTD_error_stage_wrong: 60; - readonly ZSTD_error_init_missing: 62; - readonly ZSTD_error_memory_allocation: 64; - readonly ZSTD_error_workSpace_tooSmall: 66; - readonly ZSTD_error_dstSize_tooSmall: 70; - readonly ZSTD_error_srcSize_wrong: 72; - readonly ZSTD_error_dstBuffer_null: 74; - readonly ZSTD_error_noForwardProgress_destFull: 80; - readonly ZSTD_error_noForwardProgress_inputEmpty: 82; - } - export const constants: ZlibConstants; - export const Z_NO_FLUSH: 0; - export const Z_PARTIAL_FLUSH: 1; - export const Z_SYNC_FLUSH: 2; - export const Z_FULL_FLUSH: 3; - export const Z_FINISH: 4; - export const Z_BLOCK: 5; - export const Z_OK: 0; - export const Z_STREAM_END: 1; - export const Z_NEED_DICT: 2; - export const Z_ERRNO: -1; - export const Z_STREAM_ERROR: -2; - export const Z_DATA_ERROR: -3; - export const Z_MEM_ERROR: -4; - export const Z_BUF_ERROR: -5; - export const Z_VERSION_ERROR: -6; - export const Z_NO_COMPRESSION: 0; - export const Z_BEST_SPEED: 1; - export const Z_BEST_COMPRESSION: 9; - export const Z_DEFAULT_COMPRESSION: -1; - export const Z_FILTERED: 1; - export const Z_HUFFMAN_ONLY: 2; - export const Z_RLE: 3; - export const Z_FIXED: 4; - export const Z_DEFAULT_STRATEGY: 0; - export const ZLIB_VERNUM: 4897; - export const DEFLATE: 1; - export const INFLATE: 2; - export const GZIP: 3; - export const GUNZIP: 4; - export const DEFLATERAW: 5; - export const INFLATERAW: 6; - export const UNZIP: 7; - export const ZSTD_DECOMPRESS: 11; - export const ZSTD_COMPRESS: 10; - export const Z_MIN_WINDOWBITS: 8; - export const Z_MAX_WINDOWBITS: 15; - export const Z_DEFAULT_WINDOWBITS: 15; - export const Z_MIN_CHUNK: 64; - export const Z_MAX_CHUNK: number; - export const Z_DEFAULT_CHUNK: 16384; - export const Z_MIN_MEMLEVEL: 1; - export const Z_MAX_MEMLEVEL: 9; - export const Z_DEFAULT_MEMLEVEL: 8; - export const Z_MIN_LEVEL: -1; - export const Z_MAX_LEVEL: 9; - export const Z_DEFAULT_LEVEL: -1; - export const ZSTD_e_continue: 0; - export const ZSTD_e_flush: 1; - export const ZSTD_e_end: 2; - export const ZSTD_fast: 1; - export const ZSTD_dfast: 2; - export const ZSTD_greedy: 3; - export const ZSTD_lazy: 4; - export const ZSTD_lazy2: 5; - export const ZSTD_btlazy2: 6; - export const ZSTD_btopt: 7; - export const ZSTD_btultra: 8; - export const ZSTD_btultra2: 9; - export const ZSTD_c_compressionLevel: 100; - export const ZSTD_c_windowLog: 101; - export const ZSTD_c_hashLog: 102; - export const ZSTD_c_chainLog: 103; - export const ZSTD_c_searchLog: 104; - export const ZSTD_c_minMatch: 105; - export const ZSTD_c_targetLength: 106; - export const ZSTD_c_strategy: 107; - export const ZSTD_c_enableLongDistanceMatching: 160; - export const ZSTD_c_ldmHashLog: 161; - export const ZSTD_c_ldmMinMatch: 162; - export const ZSTD_c_ldmBucketSizeLog: 163; - export const ZSTD_c_ldmHashRateLog: 164; - export const ZSTD_c_contentSizeFlag: 200; - export const ZSTD_c_checksumFlag: 201; - export const ZSTD_c_dictIDFlag: 202; - export const ZSTD_c_nbWorkers: 400; - export const ZSTD_c_jobSize: 401; - export const ZSTD_c_overlapLog: 402; - export const ZSTD_d_windowLogMax: 100; - export const ZSTD_CLEVEL_DEFAULT: 3; - export const ZSTD_error_no_error: 0; - export const ZSTD_error_GENERIC: 1; - export const ZSTD_error_prefix_unknown: 10; - export const ZSTD_error_version_unsupported: 12; - export const ZSTD_error_frameParameter_unsupported: 14; - export const ZSTD_error_frameParameter_windowTooLarge: 16; - export const ZSTD_error_corruption_detected: 20; - export const ZSTD_error_checksum_wrong: 22; - export const ZSTD_error_literals_headerWrong: 24; - export const ZSTD_error_dictionary_corrupted: 30; - export const ZSTD_error_dictionary_wrong: 32; - export const ZSTD_error_dictionaryCreation_failed: 34; - export const ZSTD_error_parameter_unsupported: 40; - export const ZSTD_error_parameter_combination_unsupported: 41; - export const ZSTD_error_parameter_outOfBound: 42; - export const ZSTD_error_tableLog_tooLarge: 44; - export const ZSTD_error_maxSymbolValue_tooLarge: 46; - export const ZSTD_error_maxSymbolValue_tooSmall: 48; - export const ZSTD_error_stabilityCondition_notRespected: 50; - export const ZSTD_error_stage_wrong: 60; - export const ZSTD_error_init_missing: 62; - export const ZSTD_error_memory_allocation: 64; - export const ZSTD_error_workSpace_tooSmall: 66; - export const ZSTD_error_dstSize_tooSmall: 70; - export const ZSTD_error_srcSize_wrong: 72; - export const ZSTD_error_dstBuffer_null: 74; - export const ZSTD_error_noForwardProgress_destFull: 80; - export const ZSTD_error_noForwardProgress_inputEmpty: 82; -} -declare module "node:zlib" { - export * from "zlib"; -} - -/* node:net — the inbound-networking slice (scr_net.c links only into - * net-using binaries). TCP only, Node's shapes narrowed to what lowers: - * createServer with an optional connection handler; listen(port[, cb]) - * with the real port discoverable as address().port (the composed read - * is the lowered form — bare address() has no lowering); close([cb]) - * firing once connections drain; socket write/end/destroy/pipe and the - * data/end/close/error/connect/drain events (on and once — listeners - * take at most the declared parameter). write returns Node's - * backpressure boolean; end returns void here (Node returns the socket); - * on/once return void (chaining is fenced). connect's host is a numeric IP or "localhost" (pinned to - * 127.0.0.1 — SEMANTICS.md); every failure is the async 'error' event, - * and an 'error' with no listener exits 1 like an unhandled EventEmitter - * 'error'. */ -declare module "net" { - export interface Socket { - /* Whether the socket keeps the event loop alive (ref'd by default). */ - ref(): void; - unref(): void; - readonly remoteAddress: string | undefined; - /* The peer's fields are cached at first read, like remoteAddress; the - * local ones are undefined while connecting and after close. */ - readonly remotePort: number | undefined; - readonly remoteFamily: string | undefined; - readonly localAddress: string | undefined; - readonly localPort: number | undefined; - readonly localFamily: string | undefined; - readonly bytesRead: number; - readonly connecting: boolean; - readonly pending: boolean; - readonly readyState: "opening" | "open" | "readOnly" | "writeOnly" | "closed"; - /* true once the fd is gone (destroy() or full close) — Node's flag. */ - readonly destroyed: boolean; - /* the write half is open: no end() yet, no FIN sent, fd alive. */ - readonly writable: boolean; - /* true until the read half is done (peer FIN / destroy). */ - readonly readable: boolean; - /* every byte the write paths accepted (buffered included — Node - * counts those too; plaintext on TLS sockets). */ - readonly bytesWritten: number; - /* write's callback runs once the data is flushed, with null; end's - * once the socket finishes. */ - /* false when the bytes this write left unsent reach the 65536-byte - * high-water mark, as in Node; 'drain' fires when they are all sent. */ - write(data: string | Uint8Array, callback?: (err?: Error | null) => void): boolean; - /* the bytes written and not yet sent */ - readonly writableLength: number; - /* a write answered false and 'drain' has not fired yet */ - readonly writableNeedDrain: boolean; - end(callback?: () => void): void; - end(data: string | Uint8Array, callback?: () => void): void; - destroy(): void; - /* Flow control: pause holds reads off (kernel/TCP backpressure is - * the buffer); resume flows — and discards without listeners, so - * 'end' stays reachable. Both chain, Node's shape. */ - pause(): Socket; - resume(): Socket; - /* TCP_NODELAY on the live fd; missing means true. Chains. */ - setNoDelay(noDelay?: boolean): Socket; - /* SO_KEEPALIVE with the initial delay in ms. Chains. */ - setKeepAlive(enable?: boolean, initialDelay?: number): Socket; - /* end() now, destroy once the FIN actually flushed. */ - destroySoon(): void; - /* An RST instead of a FIN; a connecting socket resets once it - * connects. Throws ERR_INVALID_HANDLE_TYPE on a TLS socket. */ - resetAndDestroy(): Socket; - /* The local address, or {} while connecting and once destroyed. */ - address(): AddressInfo | {}; - /* setEncoding('utf8'): 'data' delivers strings — the IncomingMessage - * twin's contract. */ - setEncoding(encoding: string): void; - /* socket→socket, and socket→response (the extended-CONNECT bridge - * leg: raw chunks become response body writes; EOF ends it). */ - pipe( - destination: Socket | import("http").ServerResponse | import("http2").Http2ServerResponse, - ): void; - /* The callback is a once('timeout') listener (removed again by 0). */ - setTimeout(ms: number, callback?: () => void): void; - /* The paused-mode demux surface: once('readable') + read(1) + - * unshift — read answers exactly n buffered bytes or null. */ - read(size?: number): Buffer | null; - unshift(chunk: Uint8Array): void; - /* secureConnect/secure/session are TLS-socket events declared on the - * shared socket surface (the lowering collapses TLSSocket onto this kind): - * the runtime gates on the transport, so a plain socket's - * registration never fires — Node's exact split. 'session' fires - * once with the serialized session (the received-ticket event). */ - on(event: "data", listener: (chunk: Buffer) => void): void; - /* After setEncoding the chunk is a string. */ - on(event: "data", listener: (chunk: string) => void): void; - on( - event: - | "end" - | "connect" - | "ready" - | "timeout" - | "drain" - | "readable" - | "finish" - | "secureConnect" - | "secure", - listener: () => void, - ): void; - on(event: "close", listener: (hadError: boolean) => void): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: "session", listener: (session: Buffer) => void): void; - /* The host name's resolution, before the connect. */ - on( - event: "lookup", - listener: (err: Error | null, address: string, family: number, host: string) => void, - ): void; - /* addListener IS on (Node aliases them) — the suite spells both. */ - addListener(event: "data", listener: (chunk: Buffer) => void): void; - /* After setEncoding the chunk is a string. */ - addListener(event: "data", listener: (chunk: string) => void): void; - addListener( - event: - | "end" - | "connect" - | "ready" - | "timeout" - | "drain" - | "readable" - | "finish" - | "secureConnect" - | "secure", - listener: () => void, - ): void; - addListener(event: "close", listener: (hadError: boolean) => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener(event: "session", listener: (session: Buffer) => void): void; - /* The host name's resolution, before the connect. */ - addListener( - event: "lookup", - listener: (err: Error | null, address: string, family: number, host: string) => void, - ): void; - once(event: "data", listener: (chunk: Buffer) => void): void; - /* After setEncoding the chunk is a string. */ - once(event: "data", listener: (chunk: string) => void): void; - once( - event: - | "end" - | "connect" - | "ready" - | "timeout" - | "drain" - | "readable" - | "finish" - | "secureConnect" - | "secure", - listener: () => void, - ): void; - once(event: "close", listener: (hadError: boolean) => void): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: "session", listener: (session: Buffer) => void): void; - /* The host name's resolution, before the connect. */ - once( - event: "lookup", - listener: (err: Error | null, address: string, family: number, host: string) => void, - ): void; - } - export interface Server { - /* Whether the server keeps the event loop alive (ref'd by default). */ - ref(): void; - unref(): void; - readonly listening: boolean; - /* Connections past this many are closed with a 'drop' event; - * undefined (the default) or null lifts the cap. */ - maxConnections: number | undefined; - /* Node reads it only in cluster mode: a stored value here. */ - dropMaxConnection: boolean | undefined; - /* cb(null, count) on the next tick. */ - getConnections(callback: (error: Error | null, count: number) => void): Server; - /* Node answers the server itself (`return this` chaining). */ - listen(port: number, callback?: () => void): Server; - /* The positional bind address (the options form's host in argument - * spelling). */ - listen(port: number, host: string, callback?: () => void): Server; - /* A Unix-socket server; the socket file is removed when it closes. */ - listen(path: string, callback?: () => void): Server; - /* The explicit-interface bind: host is an IP literal (absent = the - * host-less dual-stack any); ipv6Only sets IPV6_V6ONLY and reusePort - * requests kernel connection distribution where Node supports it. */ - listen( - options: { port: number; host?: string; ipv6Only?: boolean; reusePort?: boolean }, - callback?: () => void, - ): Server; - close(callback?: () => void): void; - /* The bound AddressInfo (Node answers null before listen — this - * surface answers the record with port 0 there, the serverPort - * stance). */ - address(): import("node:dgram").AddressInfo; - /* The demux route: hand an accepted socket to another server. */ - emit(event: "connection", socket: Socket): boolean; - on(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; - /* tls and https servers: a handshake that failed */ - on( - event: "tlsClientError", - listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, - ): void; - on(event: "close" | "listening", listener: () => void): void; - on(event: "drop", listener: (data?: DropArgument) => void): void; - on(event: "timeout", listener: (socket: Socket) => void): void; - on(event: "error", listener: (err: Error) => void): void; - /* The WebSocket handover: fires INSTEAD of 'request' for - * Connection: upgrade requests, with the raw socket + head bytes; - * 'connect' is its twin for CONNECT requests (HTTP tunneling). */ - on( - event: "upgrade" | "connect", - listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - /* http.createServer(handler)'s event twin (a no-parser net server - * never fires it, like Node). */ - on( - event: "request" | "checkContinue" | "checkExpectation", - listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, - ): void; - /* addListener IS on (Node aliases them) — the suite spells both. */ - addListener(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; - /* tls and https servers: a handshake that failed */ - addListener( - event: "tlsClientError", - listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, - ): void; - addListener(event: "close" | "listening", listener: () => void): void; - addListener(event: "drop", listener: (data?: DropArgument) => void): void; - addListener(event: "timeout", listener: (socket: Socket) => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener( - event: "upgrade" | "connect", - listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - addListener( - event: "request" | "checkContinue" | "checkExpectation", - listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, - ): void; - once(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void; - /* tls and https servers: a handshake that failed */ - once( - event: "tlsClientError", - listener: (err: Error, tlsSocket: import("tls").TLSSocket) => void, - ): void; - once(event: "close" | "listening", listener: () => void): void; - once(event: "drop", listener: (data?: DropArgument) => void): void; - once(event: "timeout", listener: (socket: Socket) => void): void; - once(event: "error", listener: (err: Error) => void): void; - once( - event: "upgrade" | "connect", - listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - once( - event: "request" | "checkContinue" | "checkExpectation", - listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void, - ): void; - } - /* The 'drop' payload: the turned-away connection's addresses. */ - export interface DropArgument { - localAddress?: string; - localPort?: number; - localFamily?: string; - remoteAddress?: string; - remotePort?: number; - remoteFamily?: string; - } - export type AddressInfo = import("node:dgram").AddressInfo; - export type IPVersion = "ipv4" | "ipv6"; - export interface SocketAddressInitOptions { - address?: string; - family?: IPVersion; - flowlabel?: number; - port?: number; - } - /* A native handle (scr_net.c); the getters read its parsed address. */ - export class SocketAddress { - constructor(options?: SocketAddressInitOptions); - readonly address: string; - readonly family: IPVersion; - readonly port: number; - readonly flowlabel: number; - toJSON(): { address: string; port: number; family: IPVersion; flowlabel: number }; - /* undefined when the input is not "[:]" */ - static parse(input: string): SocketAddress | undefined; - static isSocketAddress(value: unknown): value is SocketAddress; - } - /* Address, range and subnet rules; check() matches IPv4 addresses - * against IPv6 rules through their IPv4-mapped form, as Node does. */ - export class BlockList { - constructor(); - readonly rules: readonly string[]; - readonly size: number; - addAddress(address: string | SocketAddress, type?: IPVersion): void; - addAddresses(addresses: readonly (string | SocketAddress)[], type?: IPVersion): void; - addRange(start: string | SocketAddress, end: string | SocketAddress, type?: IPVersion): void; - addSubnet(net: string | SocketAddress, prefix: number, type?: IPVersion): void; - addCIDR(cidr: string): void; - addCIDRs(cidrs: readonly string[]): void; - removeAddress(address: string | SocketAddress, type?: IPVersion): void; - removeRange(start: string | SocketAddress, end: string | SocketAddress, type?: IPVersion): void; - removeSubnet(net: string | SocketAddress, prefix: number, type?: IPVersion): void; - removeCIDR(cidr: string): void; - clear(): void; - check(address: string | SocketAddress, type?: IPVersion): boolean; - toJSON(): string[]; - fromJSON(data: string | readonly string[]): void; - static isBlockList(value: unknown): value is BlockList; - static readonly PRIVATE_RANGES: readonly string[]; - } - /* Node's IPv4Reg and IPv6Reg tests: 4, 6, or 0 for isIP. */ - export function isIP(input: string): number; - export function isIPv4(input: string): boolean; - export function isIPv6(input: string): boolean; - /* The Socket VALUE — the `x instanceof net.Socket` narrowing target - * (the h2 compat 'connect' listener's test). Constructing bare sockets - * has no lowering; the construct signature exists for instanceof. */ - export const Socket: abstract new () => Socket; - export function createServer(connectionListener?: (socket: Socket) => void): Server; - export function connect(port: number, host?: string, connectListener?: () => void): Socket; - export function createConnection( - port: number, - host?: string, - connectListener?: () => void, - ): Socket; - /* The caller-resolver dial (@types/node's net.LookupFunction): the - * runtime invokes it as Node's autoSelectFamily dial does (options - * { hints, all: true }, so the callback answers an address list) and - * dials the answered addresses in order. */ - export type LookupFunction = ( - hostname: string, - options: import("node:dns").LookupOptions, - callback: ( - err: NodeJS.ErrnoException | null, - address: string | import("node:dns").LookupAddress[], - family?: number, - ) => void, - ) => void; - export function connect( - options: { - port: number; - host?: string; - autoSelectFamily?: boolean; - lookup?: LookupFunction; - }, - connectListener?: () => void, - ): Socket; - export function createConnection( - options: { - port: number; - host?: string; - autoSelectFamily?: boolean; - lookup?: LookupFunction; - }, - connectListener?: () => void, - ): Socket; - /* The happy-eyeballs attempt budget (Node's process-wide knob; the - * autoSelectFamily dial consults it): one runtime int, default 250ms - * like Node's. */ - export function getDefaultAutoSelectFamilyAttemptTimeout(): number; - export function setDefaultAutoSelectFamilyAttemptTimeout(value: number): void; -} -declare module "node:net" { - export * from "net"; -} - -/* node:http — the SERVER and CLIENT slices (scr_http.c layers a - * hand-written HTTP/1.1 parser + serializer on scr_net.c; both link only - * into using binaries). createServer(handler) returns a net.Server — - * listen/close/address().port/'error' are the same lowered surface. The - * request carries url/method (always present on server requests), - * statusCode (a number on client responses, undefined on server - * requests), the underlying socket, and lowercased headers read as - * `string | undefined`; the body streams through on("data"/"end"). The - * response is setHeader/writeHead/write/end/destroy with Node's framing - * (Content-Length for a single end(body), chunked after writeHead/write) - * and the auto Date/Connection headers; writeHead's headers argument is - * an object literal or a Record. request/get dial one - * connection per call (no agent pooling) with Node's exact wire head. */ -declare module "http" { - import { Server as NetServer, Socket } from "net"; - export const METHODS: string[]; - export const STATUS_CODES: { [status: number]: string | undefined }; - export const maxHeaderSize: number; - export interface Server extends NetServer { - timeout: number; - setTimeout(msecs: number, callback?: (socket: Socket) => void): this; - closeAllConnections(): void; - closeIdleConnections(): void; - keepAliveTimeout: number; - keepAliveTimeoutBuffer: number; - headersTimeout: number; - requestTimeout: number; - } - export type RequestListener = (req: IncomingMessage, res: ServerResponse) => void; - /* The Server VALUE — Node's constructor works with and without `new` - * (test/parallel's http.Server(fn) spelling); both route to the - * createServer lowering. */ - export const Server: { - new (requestListener?: (req: IncomingMessage, res: ServerResponse) => void): Server; - new ( - options: ServerOptions, - requestListener?: (req: IncomingMessage, res: ServerResponse) => void, - ): Server; - (requestListener?: (req: IncomingMessage, res: ServerResponse) => void): Server; - ( - options: ServerOptions, - requestListener?: (req: IncomingMessage, res: ServerResponse) => void, - ): Server; - }; - /* The options-record stance (the RequestOptions precedent): every - * documented key typechecks; the lowering's option walk decides per - * key — requireHostHeader: false, joinDuplicateHeaders, and - * keepAliveTimeoutBuffer lower, - * documented-but-unlowered keys fence by name, unknown keys drop. */ - export interface ServerOptions { - requireHostHeader?: boolean; - joinDuplicateHeaders?: boolean; - keepAliveTimeoutBuffer?: number; - maxHeaderSize?: number; - [option: string]: unknown; - } - /* The outgoing-header shape, @types/node's matrix: numbers format via - * String(n), arrays write one line per element (set-cookie's shape). - * Incoming headers stay `string | undefined` — what the parser answers - * (under @types/node the two SLOTS unify so `{ ...req.headers }` - * merges into outgoing literals as a copy; the fallback keeps reads - * simple and spreads target the same string | undefined slot). */ - export type OutgoingHttpHeaders = { [name: string]: number | string | string[] | undefined }; - export interface IncomingMessage { - readonly url: string; - readonly method: string; - readonly httpVersion: string; - readonly complete: boolean; - readonly aborted: boolean; - readonly statusCode: number | undefined; - readonly statusMessage: string | undefined; - readonly socket: Socket; - readonly connection: Socket; - readonly headers: { [name: string]: string | undefined }; - readonly headersDistinct: { [name: string]: string[] | undefined }; - readonly rawHeaders: string[]; - readonly trailers: { [name: string]: string | undefined }; - readonly trailersDistinct: { [name: string]: string[] | undefined }; - readonly rawTrailers: string[]; - resume(): void; - pause(): void; - destroy(): void; - setTimeout(msecs: number, callback?: () => void): this; - /* setEncoding('utf8'): 'data' delivers strings (other real encodings - * throw a "not supported yet" Error at runtime; unknown names throw - * ERR_UNKNOWN_ENCODING). */ - setEncoding(encoding: string): void; - /* The proxy legs: the body streams into a ServerResponse, a - * ClientRequest, or a raw Socket; natural end ends the destination. */ - pipe( - destination: ServerResponse | import("http2").Http2ServerResponse | ClientRequest | Socket, - ): void; - on(event: "data", listener: (chunk: any) => void): void; - on(event: "end" | "close" | "aborted", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - /* addListener IS on (Node aliases them) — the suite spells both. */ - addListener(event: "data", listener: (chunk: any) => void): void; - addListener(event: "end" | "close" | "aborted", listener: () => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - once(event: "data", listener: (chunk: any) => void): void; - once(event: "end" | "close" | "aborted", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - } - export interface ServerResponse { - readonly headersSent: boolean; - readonly writableEnded: boolean; - readonly finished: boolean; - readonly writableFinished: boolean; - readonly writableCorked: number; - readonly req: IncomingMessage; - readonly socket: Socket | null; - readonly connection: Socket | null; - sendDate: boolean; - strictContentLength: boolean; - /* Node's writable head properties: the implicit head reads them. */ - statusCode: number; - statusMessage: string | undefined; - /* The value keeps its type: getHeader answers the number, string or - * array that was set. */ - setHeader(name: string, value: number | string | readonly string[]): void; - appendHeader(name: string, value: string | readonly string[]): void; - getHeader(name: string): number | string | string[] | undefined; - getHeaderNames(): string[]; - getRawHeaderNames(): string[]; - getHeaders(): OutgoingHttpHeaders; - hasHeader(name: string): boolean; - removeHeader(name: string): void; - /* Both overloads answer the response (`return this` chaining); the - * headers argument also takes Node's flat [name, value, ...] array. */ - writeHead(statusCode: number, headers?: OutgoingHttpHeaders | string[]): ServerResponse; - writeHead( - statusCode: number, - statusMessage: string, - headers?: OutgoingHttpHeaders | string[], - ): ServerResponse; - writeContinue(callback?: () => void): void; - writeProcessing(callback?: () => void): void; - writeEarlyHints(hints: Record, callback?: () => void): void; - write(data: string | Uint8Array): void; - flushHeaders(): void; - cork(): void; - uncork(): void; - addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void; - setTimeout(msecs: number, callback?: () => void): this; - /* end's callback forms fire once the body went out (the 'finish' - * emit, deferred past the handler's synchronous tail). */ - end(data?: string | Uint8Array, callback?: () => void): void; - end(callback: () => void): void; - destroy(): void; - on(event: "close" | "finish", listener: () => void): void; - /* addListener IS on (Node aliases them) — the suite spells both. */ - addListener(event: "close" | "finish", listener: () => void): void; - once(event: "close" | "finish", listener: () => void): void; - } - /* The CLIENT slice (http.request/http.get): the options-object form - * with hostname/host, port, path, method, timeout, and headers; the - * callback receives the response as an IncomingMessage (statusCode, - * headers, body via on("data"/"end")). */ - export interface RequestOptions { - hostname?: string | null; - host?: string | null; - /* A string port (a URL's .port) is accepted, as by Node. */ - port?: number | string | null; - path?: string | null; - method?: string; - timeout?: number; - headers?: OutgoingHttpHeaders; - /* The caller's own dialer (the proxy's loopback dial): invoked once, - * synchronously; its socket carries the exchange. */ - createConnection?: () => Socket; - /* agent: false is a one-shot dial with Connection: close — exactly - * the compiled client's model; null/undefined are the default; an - * Agent VALUE threads through (getName-keyed maxSockets accounting - * over one-dial-per-request connections). */ - agent?: Agent | boolean | null; - /* Aborting destroys the request with an AbortError. */ - signal?: AbortSignal; - /* HTTP over the Unix socket at this path instead of host:port. */ - socketPath?: string; - /* The options-record stance: Node ignores an options record's - * unknown keys, so every key typechecks here — the option WALK in - * the lowering decides per key (lower, fence by name, or drop - * undocumented keys exactly like Node). */ - [option: string]: unknown; - } - /* The Agent lowers to a checked-dynamic HANDLE: option parsing at the - * literal construction site, getName, destroy, the sockets/requests/ - * freeSockets snapshots, and REAL maxSockets accounting (over-limit - * requests defer their dial and queue). keepAlive: true keeps finished - * connections in freeSockets for the next request to the same name, - * taken in `scheduling` order. */ - export interface AgentOptions { - keepAlive?: boolean; - keepAliveMsecs?: number; - maxSockets?: number; - maxFreeSockets?: number; - maxTotalSockets?: number; - scheduling?: "fifo" | "lifo"; - timeout?: number; - port?: number; - [option: string]: unknown; - } - export class Agent { - constructor(options?: AgentOptions); - destroy(): void; - /* Node's exact key shape: host:port:localAddress[:family][:socketPath]. */ - getName(options?: { - host?: string; - port?: number; - localAddress?: string; - family?: number; - socketPath?: string; - [option: string]: unknown; - }): string; - maxSockets: number; - maxFreeSockets: number; - keepAliveMsecs: number; - readonly keepAlive: boolean; - readonly protocol: string; - /* Settable — a portless request through this agent dials it (Node's - * option merge). */ - defaultPort: number; - readonly totalSocketCount: number; - readonly sockets: { [key: string]: unknown[] }; - readonly freeSockets: { [key: string]: unknown[] }; - readonly requests: { [key: string]: unknown[] }; - } - /* Node's keepAlive global agent for requests without an agent option. */ - export const globalAgent: Agent; - export interface ClientRequest { - readonly destroyed: boolean; - readonly aborted: boolean; - readonly socket: Socket; - readonly connection: Socket; - readonly writableCorked: number; - readonly method: string; - readonly path: string; - readonly host: string; - readonly protocol: string; - readonly headersSent: boolean; - readonly writableEnded: boolean; - readonly writableFinished: boolean; - readonly finished: boolean; - readonly reusedSocket: boolean; - setHeader(name: string, value: string): void; - getHeader(name: string): string | undefined; - hasHeader(name: string): boolean; - removeHeader(name: string): void; - getHeaderNames(): string[]; - getRawHeaderNames(): string[]; - getHeaders(): OutgoingHttpHeaders; - flushHeaders(): void; - addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void; - cork(): void; - uncork(): void; - setNoDelay(noDelay?: boolean): void; - setSocketKeepAlive(enable?: boolean, initialDelay?: number): void; - setTimeout(timeout: number, callback?: () => void): this; - write(data: string | Uint8Array): void; - end(data?: string | Uint8Array): void; - destroy(error?: Error): void; - abort(): void; - on(event: "response", listener: (res: IncomingMessage) => void): void; - on(event: "socket", listener: (socket: Socket) => void): void; - on( - event: "upgrade", - listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - on(event: "timeout" | "close" | "finish" | "abort" | "continue", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - /* addListener IS on (Node aliases them) — the suite spells both. */ - addListener(event: "response", listener: (res: IncomingMessage) => void): void; - addListener(event: "socket", listener: (socket: Socket) => void): void; - addListener( - event: "upgrade", - listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - addListener(event: "timeout" | "close" | "finish" | "abort", listener: () => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - once(event: "response", listener: (res: IncomingMessage) => void): void; - once(event: "socket", listener: (socket: Socket) => void): void; - once( - event: "upgrade", - listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void, - ): void; - once(event: "timeout" | "close" | "finish" | "abort" | "continue", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - } - export function createServer( - requestListener?: (req: IncomingMessage, res: ServerResponse) => void, - ): Server; - export function createServer( - options: ServerOptions, - requestListener?: (req: IncomingMessage, res: ServerResponse) => void, - ): Server; - /* One signature with a UNION first parameter rather than Node's two - * overloads: `const requestFn = tls ? https.request : http.request` - * (the portless isProxyRunning shape) unions two function types, and - * resolving a call on that union collapses each constituent to a - * single signature — with an overload SET on both sides the options - * literal ends up checked against URL, in either declaration order. - * A union parameter has nothing to collapse. The lowering dispatches - * on the argument's own type, so it reads the same either way. - * - * `target` is a URL string or URL object (the spelling a from-scratch - * client reaches for first — dialed directly, with a non-http scheme - * an ERR_INVALID_PROTOCOL at runtime rather than a type error), or - * the options record. */ - export function request( - target: RequestOptions | string | URL, - callback?: (res: IncomingMessage) => void, - ): ClientRequest; - export function get( - target: RequestOptions | string | URL, - callback?: (res: IncomingMessage) => void, - ): ClientRequest; - export function validateHeaderName(name: string, label?: string): void; - export function validateHeaderValue(name: string, value: unknown): void; -} -declare module "node:http" { - export * from "http"; -} - -/* node:tls — the TLS server slice (scr_tls.c over scr_net.c with the - * vendored mbedTLS; linked only into using binaries). - * createServer({ cert, key }, handler) returns a net.Server; the handler - * is Node's 'secureConnection' — it fires post-handshake with a socket - * that behaves exactly like a net socket (write/end/destroy/pipe and the - * data/end/close/error events are the same lowered surface). cert/key - * are PEM strings or Buffers. Handshake failures tear the socket down - * without emitting an event, Node's default for 'tlsClientError' with no listener. */ -declare module "tls" { - import { Server as NetServer, Socket } from "net"; - /** tls.Server: a net server whose connections handshake first. */ - export interface Server extends NetServer { - /* serve `context` to clients whose SNI name matches `hostname` ('*' - * matches within one label); the newest match wins */ - addContext(hostname: string, context: TlsOptions | SecureContext): void; - /* new connections serve a context made from `options` */ - setSecureContext(options: TlsOptions): void; - } - export interface TlsOptions { - /* Optional in the DECLARATION only (real Node programs build these - * literals with every TLS knob): the lowering still requires both — - * a literal without them fences at the call site. */ - cert?: string | Uint8Array; - key?: string | Uint8Array; - /* The options-record stance (see http.RequestOptions): every key - * typechecks; the option walk fences documented-but-unlowered keys - * by name and drops undocumented ones like Node. */ - [option: string]: unknown; - } - export function createServer( - options: TlsOptions, - secureConnectionListener?: (socket: TLSSocket) => void, - ): Server; - /** TLSSocket — post-handshake it IS a net socket to every lowered - * member (the mapping collapses it onto the socket kind; the - * secureConnect/session events ride the shared socket declarations). - * The TLS extras: authorized/authorizationError answer Node's verify - * split — authorizationError is the verify-failure CODE STRING - * (DEPTH_ZERO_SELF_SIGNED_CERT, ...), or null. */ - export interface TLSSocket extends Socket { - readonly encrypted: true; - authorized: boolean; - authorizationError: string | null; - /* null before the handshake, then the negotiated protocol or false */ - readonly alpnProtocol: string | false | null; - /* "TLSv1.2" or "TLSv1.3"; null before the handshake and after close */ - getProtocol(): string | null; - /* OpenSSL's suite name, the IANA name and the suite's version */ - getCipher(): CipherNameAndProtocol; - /* whether the handshake resumed a cached session */ - isSessionReused(): boolean; - /* the peer's certificate: {} before the handshake or without one, - * null once the socket is destroyed; detailed adds issuerCertificate */ - getPeerCertificate(detailed: true): DetailedPeerCertificate; - getPeerCertificate(detailed?: false): PeerCertificate; - getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate; - /* the certificate this socket serves, the same way */ - getCertificate(): PeerCertificate | object | null; - /* a new X509Certificate on each call, or undefined without one */ - getPeerX509Certificate(): import("crypto").X509Certificate | undefined; - getX509Certificate(): import("crypto").X509Certificate | undefined; - } - /* A certificate as getPeerCertificate() and X509Certificate's - * toLegacyObject() answer it: subject and issuer map attribute short - * names to values, infoAccess its " - " keys to values. */ - export interface PeerCertificate { - subject: { [attribute: string]: string | string[] }; - issuer: { [attribute: string]: string | string[] }; - subjectaltname?: string; - infoAccess?: { [key: string]: string[] }; - ca: boolean; - modulus?: string; - exponent?: string; - pubkey?: Buffer; - bits?: number; - valid_from: string; - valid_to: string; - fingerprint: string; - fingerprint256: string; - fingerprint512: string; - ext_key_usage?: string[]; - serialNumber: string; - raw: Buffer; - asn1Curve?: string; - nistCurve?: string; - } - /* getPeerCertificate(true): each certificate links to its issuer; a - * self-issued root is its own issuerCertificate */ - export interface DetailedPeerCertificate extends PeerCertificate { - issuerCertificate: DetailedPeerCertificate; - } - - /** new tls.TLSSocket(socket, options): the server-mode wrap of an open - * socket (a server connection or an http CONNECT socket). The options - * record reads at runtime; isServer, cert, key, secureContext, - * ALPNProtocols, minVersion and maxVersion are implemented. */ - export interface TLSSocketOptions { - isServer?: boolean; - cert?: string | Uint8Array; - key?: string | Uint8Array; - secureContext?: SecureContext; - ALPNProtocols?: string[] | Uint8Array; - minVersion?: string; - maxVersion?: string; - [option: string]: unknown; - } - export const TLSSocket: new (socket: Socket, options: TLSSocketOptions) => TLSSocket; - export interface CipherNameAndProtocol { - name: string; - standardName: string; - version: string; - } - export const DEFAULT_MIN_VERSION: string; - export const DEFAULT_MAX_VERSION: string; - /** tls.connect options — the RUNTIME options record (members read at - * runtime; port/host/socket/rejectUnauthorized/ca/servername are the - * honestly-implemented set, every other documented member throws the - * runtime fence — the options-record stance's divergence-66 shape). */ - export interface ConnectionOptions { - port?: number; - host?: string; - /* an open socket to run the client handshake over */ - socket?: Socket; - rejectUnauthorized?: boolean; - ca?: string | Uint8Array | ReadonlyArray; - servername?: string; - [option: string]: unknown; - } - export function connect( - options: ConnectionOptions, - secureConnectListener?: () => void, - ): TLSSocket; - export function connect( - port: number, - options?: ConnectionOptions, - secureConnectListener?: () => void, - ): TLSSocket; - export function connect( - port: number, - host?: string, - options?: ConnectionOptions, - secureConnectListener?: () => void, - ): TLSSocket; - /** An opaque parsed cert/key pair — what an SNI callback answers with - * (the value is a runtime handle; the members are runtime-internal). */ - export interface SecureContext {} - /* @types/node's name for createSecureContext's options */ - export type SecureContextOptions = TlsOptions; - export function createSecureContext(options: TlsOptions): SecureContext; - /** The CA-store introspection surface (scr_tls_ca.c): per-type cached - * PEM string arrays. The host bundle stands in for Node's compiled-in - * Mozilla roots ('bundled', and rootCertificates) AND the platform - * store ('system') — the Windows certificate stores or POSIX bundle probe; - * 'extra' reads NODE_EXTRA_CA_CERTS. An unknown type string throws - * Node's ERR_INVALID_ARG_VALUE TypeError. */ - export function getCACertificates(type?: string): string[]; - export const rootCertificates: readonly string[]; - /** Replaces the 'default' set (deduped) and the trust anchors the TLS - * client verifies against; a certificate-free non-empty array throws - * Node's ERR_CRYPTO_OPERATION_FAILED Error. */ - export function setDefaultCACertificates(certs: readonly string[]): void; -} -declare module "node:tls" { - export * from "tls"; -} - -/* node:https — http over the TLS transport (scr_tls.c). - * createServer({ cert, key }, handler) is the http server behind a - * handshake; request/get are the http client with port 443 default and - * the local-CA knobs: `ca` (PEM string/Buffer) replaces the trust - * anchors, rejectUnauthorized: false disables verification (the - * self-signed probe shape). With neither, /etc/ssl/cert.pem stands in - * for Node's bundled roots. */ -declare module "https" { - import { Server } from "http"; - import { Agent, ClientRequest, IncomingMessage, ServerResponse } from "http"; - export { Agent, Server }; - /* Node's keepAlive global agent for requests without an agent option. */ - export const globalAgent: Agent; - export interface ServerOptions { - cert: string | Uint8Array; - key: string | Uint8Array; - /* The options-record stance (see http.RequestOptions). */ - [option: string]: unknown; - } - export interface RequestOptions { - hostname?: string; - host?: string; - port?: number; - path?: string; - method?: string; - timeout?: number; - headers?: { [name: string]: string }; - rejectUnauthorized?: boolean; - ca?: string | Uint8Array; - agent?: Agent | boolean | null; - /* Aborting destroys the request with an AbortError. */ - signal?: AbortSignal; - socketPath?: string; - /* The options-record stance (see http.RequestOptions). */ - [option: string]: unknown; - } - export function createServer( - options: ServerOptions, - requestListener: (req: IncomingMessage, res: ServerResponse) => void, - ): Server; - /* One signature with a union first parameter, for the request-fn - * ternary's sake (see the http block). A URL target takes no options, - * which means Node's defaults — the certificate is verified against - * the default trust anchors. */ - export function request( - target: RequestOptions | string | URL, - callback?: (res: IncomingMessage) => void, - ): ClientRequest; - export function get( - target: RequestOptions | string | URL, - callback?: (res: IncomingMessage) => void, - ): ClientRequest; -} -declare module "node:https" { - export * from "https"; -} - -/* node:http2 — the compatibility surface: allowHTTP1 servers negotiate - * h2 or HTTP/1.1 and expose the shared request/response API. HTTP/2 - * requests retain their backing stream; server session failures surface - * through sessionError. */ -declare module "http2" { - import { Socket } from "net"; - export interface Http2Stream { - on(event: "error" | "close", listener: (err: Error) => void): void; - once(event: "error" | "close", listener: (err: Error) => void): void; - addListener(event: "error" | "close", listener: (err: Error) => void): void; - destroy(): void; - } - export interface Http2ServerRequest { - readonly url: string; - readonly method: string; - readonly statusCode: number | undefined; - readonly socket: Socket; - readonly headers: { [name: string]: string | undefined }; - readonly stream: Http2Stream; - /* The h2 compat request's version triple ("2.0" over real h2 streams; - * the allowHTTP1 lowering's HTTP/1.1 connections answer 1.x). */ - readonly httpVersion: string; - readonly httpVersionMajor: number; - readonly httpVersionMinor: number; - readonly aborted: boolean; - readonly complete: boolean; - resume(): void; - destroy(): void; - setEncoding(encoding: string): void; - pipe(destination: Socket | import("http").ClientRequest): void; - on(event: "data", listener: (chunk: Buffer) => void): void; - on(event: "end" | "close" | "aborted", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - /* addListener IS on (Node aliases them) — and the compat req IS the - * http.IncomingMessage surface, so the alias mirrors here too. */ - addListener(event: "data", listener: (chunk: Buffer) => void): void; - addListener(event: "end" | "close" | "aborted", listener: () => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - once(event: "data", listener: (chunk: Buffer) => void): void; - once(event: "end" | "close" | "aborted", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - } - export interface Http2ServerResponse { - readonly headersSent: boolean; - readonly writableEnded: boolean; - readonly writableCorked: number; - /* The same lowered surface as http.ServerResponse — the allowHTTP1 - * lowering serves every connection as HTTP/1.1, where the compat - * response IS this parser's response handle. */ - statusCode: number; - statusMessage: string; - setHeader(name: string, value: string | number): void; - getHeader(name: string): string | undefined; - getHeaderNames(): string[]; - getRawHeaderNames(): string[]; - getHeaders(): import("http").OutgoingHttpHeaders; - hasHeader(name: string): boolean; - removeHeader(name: string): void; - writeHead( - statusCode: number, - headers?: import("http").OutgoingHttpHeaders | string[], - ): Http2ServerResponse; - writeHead( - statusCode: number, - statusMessage: string, - headers?: import("http").OutgoingHttpHeaders | string[], - ): Http2ServerResponse; - writeContinue(): void; - writeEarlyHints(hints: Record): void; - write(data: string | Uint8Array): void; - flushHeaders(): void; - cork(): void; - uncork(): void; - addTrailers( - headers: import("http").OutgoingHttpHeaders | ReadonlyArray<[string, string]>, - ): void; - end(data?: string | Uint8Array, callback?: () => void): void; - end(callback: () => void): void; - destroy(): void; - on(event: "close", listener: () => void): void; - /* addListener IS on (Node aliases them) — and the compat res IS the - * http.ServerResponse surface (assignability into ServerResponse - * unions — IncomingMessage.pipe's destination — needs the alias). */ - addListener(event: "close", listener: () => void): void; - once(event: "close", listener: () => void): void; - } - export interface Http2SecureServer { - listen(port: number, callback?: () => void): void; - close(callback?: () => void): void; - /* The bound AddressInfo (Node answers null before listen — this - * surface answers the record with port 0 there, the serverPort - * stance). */ - address(): import("node:dgram").AddressInfo; - emit(event: "connection", socket: Socket): void; - on(event: "connection", listener: (socket: Socket) => void): void; - on( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: "close", listener: () => void): void; - on(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; - /* The ALPN=h2 server's surface (createSecureServer WITHOUT - * allowHTTP1 — the real h2-over-TLS stack): per-stream and - * per-session listeners, exactly the h2c server's. */ - on( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - on(event: "session", listener: (session: ServerHttp2Session) => void): void; - /* HTTP CONNECT (and, in Node, the h2 extended CONNECT — which the - * allowHTTP1 lowering never sees): the second argument is the raw - * socket on HTTP/1.1 connections; narrow with instanceof. */ - on( - event: "connect", - listener: ( - req: Http2ServerRequest | import("http").IncomingMessage, - resOrSocket: Http2ServerResponse | Socket, - ) => void, - ): void; - once(event: "connection", listener: (socket: Socket) => void): void; - once( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: "close", listener: () => void): void; - once(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; - once( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - once(event: "session", listener: (session: ServerHttp2Session) => void): void; - once( - event: "connect", - listener: ( - req: Http2ServerRequest | import("http").IncomingMessage, - resOrSocket: Http2ServerResponse | Socket, - ) => void, - ): void; - addListener(event: "connection", listener: (socket: Socket) => void): void; - addListener( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener(event: "close", listener: () => void): void; - addListener( - event: "sessionError", - listener: (err: Error, session: ServerHttp2Session) => void, - ): void; - addListener( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener(event: "session", listener: (session: ServerHttp2Session) => void): void; - addListener( - event: "connect", - listener: ( - req: Http2ServerRequest | import("http").IncomingMessage, - resOrSocket: Http2ServerResponse | Socket, - ) => void, - ): void; - } - export interface SecureServerOptions { - allowHTTP1?: boolean; - cert?: string | Uint8Array; - key?: string | Uint8Array; - SNICallback?: ( - servername: string, - cb: (err: Error | null, ctx?: import("tls").SecureContext) => void, - ) => void; - /* The options-record stance (see http.RequestOptions): h2 session- - * tuning knobs are accepted as literals and ignored (no h2 session - * ever exists under the allowHTTP1 lowering); other documented keys - * fence by name; undocumented keys drop like Node. */ - [option: string]: unknown; - } - /* The full Node v24 constants table (240 members), literal-typed so - * computed header keys ([HTTP2_HEADER_PATH]: value) stay literal keys. - * Every ACCESS bakes as its literal at lowering; the object itself - * never materializes (bare http2.constants value uses fence). */ - export interface Http2Constants { - readonly NGHTTP2_ERR_FRAME_SIZE_ERROR: -522; - readonly NGHTTP2_SESSION_SERVER: 0; - readonly NGHTTP2_SESSION_CLIENT: 1; - readonly NGHTTP2_STREAM_STATE_IDLE: 1; - readonly NGHTTP2_STREAM_STATE_OPEN: 2; - readonly NGHTTP2_STREAM_STATE_RESERVED_LOCAL: 3; - readonly NGHTTP2_STREAM_STATE_RESERVED_REMOTE: 4; - readonly NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL: 5; - readonly NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: 6; - readonly NGHTTP2_STREAM_STATE_CLOSED: 7; - readonly NGHTTP2_FLAG_NONE: 0; - readonly NGHTTP2_FLAG_END_STREAM: 1; - readonly NGHTTP2_FLAG_END_HEADERS: 4; - readonly NGHTTP2_FLAG_ACK: 1; - readonly NGHTTP2_FLAG_PADDED: 8; - readonly NGHTTP2_FLAG_PRIORITY: 32; - readonly DEFAULT_SETTINGS_HEADER_TABLE_SIZE: 4096; - readonly DEFAULT_SETTINGS_ENABLE_PUSH: 1; - readonly DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS: 4294967295; - readonly DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 65535; - readonly DEFAULT_SETTINGS_MAX_FRAME_SIZE: 16384; - readonly DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: 65535; - readonly DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL: 0; - readonly MAX_MAX_FRAME_SIZE: 16777215; - readonly MIN_MAX_FRAME_SIZE: 16384; - readonly MAX_INITIAL_WINDOW_SIZE: 2147483647; - readonly NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: 1; - readonly NGHTTP2_SETTINGS_ENABLE_PUSH: 2; - readonly NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: 3; - readonly NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: 4; - readonly NGHTTP2_SETTINGS_MAX_FRAME_SIZE: 5; - readonly NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: 6; - readonly NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: 8; - readonly PADDING_STRATEGY_NONE: 0; - readonly PADDING_STRATEGY_ALIGNED: 1; - readonly PADDING_STRATEGY_MAX: 2; - readonly PADDING_STRATEGY_CALLBACK: 1; - readonly NGHTTP2_NO_ERROR: 0; - readonly NGHTTP2_PROTOCOL_ERROR: 1; - readonly NGHTTP2_INTERNAL_ERROR: 2; - readonly NGHTTP2_FLOW_CONTROL_ERROR: 3; - readonly NGHTTP2_SETTINGS_TIMEOUT: 4; - readonly NGHTTP2_STREAM_CLOSED: 5; - readonly NGHTTP2_FRAME_SIZE_ERROR: 6; - readonly NGHTTP2_REFUSED_STREAM: 7; - readonly NGHTTP2_CANCEL: 8; - readonly NGHTTP2_COMPRESSION_ERROR: 9; - readonly NGHTTP2_CONNECT_ERROR: 10; - readonly NGHTTP2_ENHANCE_YOUR_CALM: 11; - readonly NGHTTP2_INADEQUATE_SECURITY: 12; - readonly NGHTTP2_HTTP_1_1_REQUIRED: 13; - readonly NGHTTP2_DEFAULT_WEIGHT: 16; - readonly HTTP2_HEADER_STATUS: ":status"; - readonly HTTP2_HEADER_METHOD: ":method"; - readonly HTTP2_HEADER_AUTHORITY: ":authority"; - readonly HTTP2_HEADER_SCHEME: ":scheme"; - readonly HTTP2_HEADER_PATH: ":path"; - readonly HTTP2_HEADER_PROTOCOL: ":protocol"; - readonly HTTP2_HEADER_ACCEPT_ENCODING: "accept-encoding"; - readonly HTTP2_HEADER_ACCEPT_LANGUAGE: "accept-language"; - readonly HTTP2_HEADER_ACCEPT_RANGES: "accept-ranges"; - readonly HTTP2_HEADER_ACCEPT: "accept"; - readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS: "access-control-allow-credentials"; - readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS: "access-control-allow-headers"; - readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS: "access-control-allow-methods"; - readonly HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN: "access-control-allow-origin"; - readonly HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS: "access-control-expose-headers"; - readonly HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS: "access-control-request-headers"; - readonly HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD: "access-control-request-method"; - readonly HTTP2_HEADER_AGE: "age"; - readonly HTTP2_HEADER_AUTHORIZATION: "authorization"; - readonly HTTP2_HEADER_CACHE_CONTROL: "cache-control"; - readonly HTTP2_HEADER_CONNECTION: "connection"; - readonly HTTP2_HEADER_CONTENT_DISPOSITION: "content-disposition"; - readonly HTTP2_HEADER_CONTENT_ENCODING: "content-encoding"; - readonly HTTP2_HEADER_CONTENT_LENGTH: "content-length"; - readonly HTTP2_HEADER_CONTENT_TYPE: "content-type"; - readonly HTTP2_HEADER_COOKIE: "cookie"; - readonly HTTP2_HEADER_DATE: "date"; - readonly HTTP2_HEADER_ETAG: "etag"; - readonly HTTP2_HEADER_FORWARDED: "forwarded"; - readonly HTTP2_HEADER_HOST: "host"; - readonly HTTP2_HEADER_IF_MODIFIED_SINCE: "if-modified-since"; - readonly HTTP2_HEADER_IF_NONE_MATCH: "if-none-match"; - readonly HTTP2_HEADER_IF_RANGE: "if-range"; - readonly HTTP2_HEADER_LAST_MODIFIED: "last-modified"; - readonly HTTP2_HEADER_LINK: "link"; - readonly HTTP2_HEADER_LOCATION: "location"; - readonly HTTP2_HEADER_RANGE: "range"; - readonly HTTP2_HEADER_REFERER: "referer"; - readonly HTTP2_HEADER_SERVER: "server"; - readonly HTTP2_HEADER_SET_COOKIE: "set-cookie"; - readonly HTTP2_HEADER_STRICT_TRANSPORT_SECURITY: "strict-transport-security"; - readonly HTTP2_HEADER_TRANSFER_ENCODING: "transfer-encoding"; - readonly HTTP2_HEADER_TE: "te"; - readonly HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS: "upgrade-insecure-requests"; - readonly HTTP2_HEADER_UPGRADE: "upgrade"; - readonly HTTP2_HEADER_USER_AGENT: "user-agent"; - readonly HTTP2_HEADER_VARY: "vary"; - readonly HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS: "x-content-type-options"; - readonly HTTP2_HEADER_X_FRAME_OPTIONS: "x-frame-options"; - readonly HTTP2_HEADER_KEEP_ALIVE: "keep-alive"; - readonly HTTP2_HEADER_PROXY_CONNECTION: "proxy-connection"; - readonly HTTP2_HEADER_X_XSS_PROTECTION: "x-xss-protection"; - readonly HTTP2_HEADER_ALT_SVC: "alt-svc"; - readonly HTTP2_HEADER_CONTENT_SECURITY_POLICY: "content-security-policy"; - readonly HTTP2_HEADER_EARLY_DATA: "early-data"; - readonly HTTP2_HEADER_EXPECT_CT: "expect-ct"; - readonly HTTP2_HEADER_ORIGIN: "origin"; - readonly HTTP2_HEADER_PURPOSE: "purpose"; - readonly HTTP2_HEADER_TIMING_ALLOW_ORIGIN: "timing-allow-origin"; - readonly HTTP2_HEADER_X_FORWARDED_FOR: "x-forwarded-for"; - readonly HTTP2_HEADER_PRIORITY: "priority"; - readonly HTTP2_HEADER_ACCEPT_CHARSET: "accept-charset"; - readonly HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE: "access-control-max-age"; - readonly HTTP2_HEADER_ALLOW: "allow"; - readonly HTTP2_HEADER_CONTENT_LANGUAGE: "content-language"; - readonly HTTP2_HEADER_CONTENT_LOCATION: "content-location"; - readonly HTTP2_HEADER_CONTENT_MD5: "content-md5"; - readonly HTTP2_HEADER_CONTENT_RANGE: "content-range"; - readonly HTTP2_HEADER_DNT: "dnt"; - readonly HTTP2_HEADER_EXPECT: "expect"; - readonly HTTP2_HEADER_EXPIRES: "expires"; - readonly HTTP2_HEADER_FROM: "from"; - readonly HTTP2_HEADER_IF_MATCH: "if-match"; - readonly HTTP2_HEADER_IF_UNMODIFIED_SINCE: "if-unmodified-since"; - readonly HTTP2_HEADER_MAX_FORWARDS: "max-forwards"; - readonly HTTP2_HEADER_PREFER: "prefer"; - readonly HTTP2_HEADER_PROXY_AUTHENTICATE: "proxy-authenticate"; - readonly HTTP2_HEADER_PROXY_AUTHORIZATION: "proxy-authorization"; - readonly HTTP2_HEADER_REFRESH: "refresh"; - readonly HTTP2_HEADER_RETRY_AFTER: "retry-after"; - readonly HTTP2_HEADER_TRAILER: "trailer"; - readonly HTTP2_HEADER_TK: "tk"; - readonly HTTP2_HEADER_VIA: "via"; - readonly HTTP2_HEADER_WARNING: "warning"; - readonly HTTP2_HEADER_WWW_AUTHENTICATE: "www-authenticate"; - readonly HTTP2_HEADER_HTTP2_SETTINGS: "http2-settings"; - readonly HTTP2_METHOD_ACL: "ACL"; - readonly HTTP2_METHOD_BASELINE_CONTROL: "BASELINE-CONTROL"; - readonly HTTP2_METHOD_BIND: "BIND"; - readonly HTTP2_METHOD_CHECKIN: "CHECKIN"; - readonly HTTP2_METHOD_CHECKOUT: "CHECKOUT"; - readonly HTTP2_METHOD_CONNECT: "CONNECT"; - readonly HTTP2_METHOD_COPY: "COPY"; - readonly HTTP2_METHOD_DELETE: "DELETE"; - readonly HTTP2_METHOD_GET: "GET"; - readonly HTTP2_METHOD_HEAD: "HEAD"; - readonly HTTP2_METHOD_LABEL: "LABEL"; - readonly HTTP2_METHOD_LINK: "LINK"; - readonly HTTP2_METHOD_LOCK: "LOCK"; - readonly HTTP2_METHOD_MERGE: "MERGE"; - readonly HTTP2_METHOD_MKACTIVITY: "MKACTIVITY"; - readonly HTTP2_METHOD_MKCALENDAR: "MKCALENDAR"; - readonly HTTP2_METHOD_MKCOL: "MKCOL"; - readonly HTTP2_METHOD_MKREDIRECTREF: "MKREDIRECTREF"; - readonly HTTP2_METHOD_MKWORKSPACE: "MKWORKSPACE"; - readonly HTTP2_METHOD_MOVE: "MOVE"; - readonly HTTP2_METHOD_OPTIONS: "OPTIONS"; - readonly HTTP2_METHOD_ORDERPATCH: "ORDERPATCH"; - readonly HTTP2_METHOD_PATCH: "PATCH"; - readonly HTTP2_METHOD_POST: "POST"; - readonly HTTP2_METHOD_PRI: "PRI"; - readonly HTTP2_METHOD_PROPFIND: "PROPFIND"; - readonly HTTP2_METHOD_PROPPATCH: "PROPPATCH"; - readonly HTTP2_METHOD_PUT: "PUT"; - readonly HTTP2_METHOD_REBIND: "REBIND"; - readonly HTTP2_METHOD_REPORT: "REPORT"; - readonly HTTP2_METHOD_SEARCH: "SEARCH"; - readonly HTTP2_METHOD_TRACE: "TRACE"; - readonly HTTP2_METHOD_UNBIND: "UNBIND"; - readonly HTTP2_METHOD_UNCHECKOUT: "UNCHECKOUT"; - readonly HTTP2_METHOD_UNLINK: "UNLINK"; - readonly HTTP2_METHOD_UNLOCK: "UNLOCK"; - readonly HTTP2_METHOD_UPDATE: "UPDATE"; - readonly HTTP2_METHOD_UPDATEREDIRECTREF: "UPDATEREDIRECTREF"; - readonly HTTP2_METHOD_VERSION_CONTROL: "VERSION-CONTROL"; - readonly HTTP_STATUS_CONTINUE: 100; - readonly HTTP_STATUS_SWITCHING_PROTOCOLS: 101; - readonly HTTP_STATUS_PROCESSING: 102; - readonly HTTP_STATUS_EARLY_HINTS: 103; - readonly HTTP_STATUS_OK: 200; - readonly HTTP_STATUS_CREATED: 201; - readonly HTTP_STATUS_ACCEPTED: 202; - readonly HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION: 203; - readonly HTTP_STATUS_NO_CONTENT: 204; - readonly HTTP_STATUS_RESET_CONTENT: 205; - readonly HTTP_STATUS_PARTIAL_CONTENT: 206; - readonly HTTP_STATUS_MULTI_STATUS: 207; - readonly HTTP_STATUS_ALREADY_REPORTED: 208; - readonly HTTP_STATUS_IM_USED: 226; - readonly HTTP_STATUS_MULTIPLE_CHOICES: 300; - readonly HTTP_STATUS_MOVED_PERMANENTLY: 301; - readonly HTTP_STATUS_FOUND: 302; - readonly HTTP_STATUS_SEE_OTHER: 303; - readonly HTTP_STATUS_NOT_MODIFIED: 304; - readonly HTTP_STATUS_USE_PROXY: 305; - readonly HTTP_STATUS_TEMPORARY_REDIRECT: 307; - readonly HTTP_STATUS_PERMANENT_REDIRECT: 308; - readonly HTTP_STATUS_BAD_REQUEST: 400; - readonly HTTP_STATUS_UNAUTHORIZED: 401; - readonly HTTP_STATUS_PAYMENT_REQUIRED: 402; - readonly HTTP_STATUS_FORBIDDEN: 403; - readonly HTTP_STATUS_NOT_FOUND: 404; - readonly HTTP_STATUS_METHOD_NOT_ALLOWED: 405; - readonly HTTP_STATUS_NOT_ACCEPTABLE: 406; - readonly HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED: 407; - readonly HTTP_STATUS_REQUEST_TIMEOUT: 408; - readonly HTTP_STATUS_CONFLICT: 409; - readonly HTTP_STATUS_GONE: 410; - readonly HTTP_STATUS_LENGTH_REQUIRED: 411; - readonly HTTP_STATUS_PRECONDITION_FAILED: 412; - readonly HTTP_STATUS_PAYLOAD_TOO_LARGE: 413; - readonly HTTP_STATUS_URI_TOO_LONG: 414; - readonly HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: 415; - readonly HTTP_STATUS_RANGE_NOT_SATISFIABLE: 416; - readonly HTTP_STATUS_EXPECTATION_FAILED: 417; - readonly HTTP_STATUS_TEAPOT: 418; - readonly HTTP_STATUS_MISDIRECTED_REQUEST: 421; - readonly HTTP_STATUS_UNPROCESSABLE_ENTITY: 422; - readonly HTTP_STATUS_LOCKED: 423; - readonly HTTP_STATUS_FAILED_DEPENDENCY: 424; - readonly HTTP_STATUS_TOO_EARLY: 425; - readonly HTTP_STATUS_UPGRADE_REQUIRED: 426; - readonly HTTP_STATUS_PRECONDITION_REQUIRED: 428; - readonly HTTP_STATUS_TOO_MANY_REQUESTS: 429; - readonly HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: 431; - readonly HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: 451; - readonly HTTP_STATUS_INTERNAL_SERVER_ERROR: 500; - readonly HTTP_STATUS_NOT_IMPLEMENTED: 501; - readonly HTTP_STATUS_BAD_GATEWAY: 502; - readonly HTTP_STATUS_SERVICE_UNAVAILABLE: 503; - readonly HTTP_STATUS_GATEWAY_TIMEOUT: 504; - readonly HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED: 505; - readonly HTTP_STATUS_VARIANT_ALSO_NEGOTIATES: 506; - readonly HTTP_STATUS_INSUFFICIENT_STORAGE: 507; - readonly HTTP_STATUS_LOOP_DETECTED: 508; - readonly HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED: 509; - readonly HTTP_STATUS_NOT_EXTENDED: 510; - readonly HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: 511; - } - export const constants: Http2Constants; - /* The eager handler is the first COMPAT 'request' listener — Node's - * route on either flavor (the allowHTTP1 server's HTTP/1.1 handles, - * the ALPN=h2 server's compat handles over streams). */ - export function createSecureServer( - options: SecureServerOptions, - onRequestHandler?: ( - req: import("http").IncomingMessage, - res: import("http").ServerResponse, - ) => void, - ): Http2SecureServer; - - /* ── the REAL h2c surface (scr_http2.c: frame codec + HPACK over the - * net loop) — createServer/connect and the session/stream handles the - * Node suite's http2 family exercises. Headers cross as the canonical - * header record (the IncomingHttpHeaders index-signature shape); the - * response :status reads back as a number. Members declared here - * without a lowering fence by name at their use sites. */ - - /** The http2 module's own header shapes: pure index-signature records - * (the HEADER-FAMILY canonicalization — string[] arm included). */ - export interface IncomingHttpHeaders { - [name: string]: number | string | string[] | undefined; - } - export interface OutgoingHttpHeaders { - [name: string]: number | string | string[] | undefined; - } - - export interface Http2Session { - close(callback?: () => void): void; - destroy(): void; - readonly closed: boolean; - readonly destroyed: boolean; - readonly encrypted: boolean; - readonly type: number; - readonly alpnProtocol: string; - readonly socket: import("net").Socket; - readonly pendingSettingsAck: boolean; - ref(): void; - unref(): void; - setTimeout(msecs: number, callback?: () => void): void; - ping(callback: (err: Error | null, duration: number, payload: Uint8Array) => void): boolean; - /* The settings surface rides the checked-dynamic machinery (records - * cross as dyn values — the suite's mustCall listeners are dyn). */ - settings(settings?: any, callback?: any): void; - readonly localSettings: any; - readonly remoteSettings: any; - goaway(code?: number, lastStreamID?: number, opaqueData?: Uint8Array): void; - on(event: "close" | "timeout", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - on( - event: "connect", - listener: (session: Http2Session, socket: import("net").Socket) => void, - ): void; - on( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - on( - event: "goaway", - listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, - ): void; - on(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; - on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - on(event: "ping", listener: (payload: Uint8Array) => void): void; - once(event: "close" | "timeout", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - once( - event: "connect", - listener: (session: Http2Session, socket: import("net").Socket) => void, - ): void; - once( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - once( - event: "goaway", - listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, - ): void; - once(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; - once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - once(event: "ping", listener: (payload: Uint8Array) => void): void; - addListener(event: "close" | "timeout", listener: () => void): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener( - event: "connect", - listener: (session: Http2Session, socket: import("net").Socket) => void, - ): void; - addListener( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener( - event: "goaway", - listener: (errorCode: number, lastStreamID: number, opaqueData?: Uint8Array) => void, - ): void; - addListener(event: "localSettings" | "remoteSettings", listener: (settings: any) => void): void; - addListener( - event: "frameError", - listener: (type: number, code: number, id: number) => void, - ): void; - addListener(event: "ping", listener: (payload: Uint8Array) => void): void; - } - - export interface ClientHttp2Session extends Http2Session { - request( - headers?: OutgoingHttpHeaders, - options?: { - endStream?: boolean; - exclusive?: boolean; - parent?: number; - weight?: number; - waitForTrailers?: boolean; - }, - ): ClientHttp2Stream; - } - - export interface ServerHttp2Session extends Http2Session { - altsvc(alt: string, originOrStream: number | string): void; - origin(...origins: string[]): void; - } - - /** The shared Http2Stream core (both roles). 'data' chunks are Buffers - * (strings once setEncoding('utf8') is in force — the IncomingMessage - * chunk:any stance keeps both working). */ - interface Http2StreamCore { - readonly id: number | undefined; - readonly rstCode: number; - readonly closed: boolean; - readonly destroyed: boolean; - readonly pending: boolean; - readonly aborted: boolean; - readonly session: Http2Session; - readonly sentHeaders: OutgoingHttpHeaders; - readonly state: Record; - write(chunk: string | Uint8Array, callback?: (err?: Error | null) => void): boolean; - end(data?: string | Uint8Array, callback?: () => void): void; - end(callback: () => void): void; - close(code?: number, callback?: () => void): void; - destroy(error?: Error): void; - setEncoding(encoding: string): this; - resume(): void; - pause(): void; - setTimeout(msecs: number, callback?: () => void): void; - priority(options: Record): void; - sendTrailers(headers: OutgoingHttpHeaders): void; - } - - export interface ServerHttp2Stream extends Http2StreamCore { - on(event: "data", listener: (chunk: any) => void): void; - on( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - on(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; - once(event: "data", listener: (chunk: any) => void): void; - once( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - once(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; - addListener(event: "data", listener: (chunk: any) => void): void; - addListener( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener( - event: "frameError", - listener: (type: number, code: number, id: number) => void, - ): void; - addListener( - event: "trailers", - listener: (trailers: IncomingHttpHeaders, flags: number) => void, - ): void; - respond( - headers?: OutgoingHttpHeaders, - options?: { endStream?: boolean; waitForTrailers?: boolean }, - ): void; - respondWithFile( - path: string, - headers?: OutgoingHttpHeaders, - options?: Record, - ): void; - respondWithFD( - fd: number, - headers?: OutgoingHttpHeaders, - options?: Record, - ): void; - pushStream( - headers: OutgoingHttpHeaders, - callback: ( - err: Error | null, - pushStream: ServerHttp2Stream, - headers: OutgoingHttpHeaders, - ) => void, - ): void; - readonly headersSent: boolean; - readonly pushAllowed: boolean; - additionalHeaders(headers: OutgoingHttpHeaders): void; - } - - export interface ClientHttp2Stream extends Http2StreamCore { - on(event: "response", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - on(event: "push", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - on(event: "headers", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - on(event: "data", listener: (chunk: any) => void): void; - on( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - on(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; - once(event: "response", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - once(event: "push", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - once(event: "headers", listener: (headers: IncomingHttpHeaders, flags: number) => void): void; - once(event: "data", listener: (chunk: any) => void): void; - once( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: "frameError", listener: (type: number, code: number, id: number) => void): void; - once(event: "trailers", listener: (trailers: IncomingHttpHeaders, flags: number) => void): void; - addListener( - event: "response", - listener: (headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener( - event: "push", - listener: (headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener( - event: "headers", - listener: (headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener(event: "data", listener: (chunk: any) => void): void; - addListener( - event: - | "end" - | "close" - | "aborted" - | "ready" - | "timeout" - | "drain" - | "finish" - | "wantTrailers", - listener: () => void, - ): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener( - event: "frameError", - listener: (type: number, code: number, id: number) => void, - ): void; - addListener( - event: "trailers", - listener: (trailers: IncomingHttpHeaders, flags: number) => void, - ): void; - } - - export interface Http2Server { - listen(port?: number, callback?: () => void): void; - listen(port: number, host: string, callback?: () => void): void; - close(callback?: () => void): void; - address(): import("node:dgram").AddressInfo; - setTimeout(msecs?: number, callback?: () => void): void; - ref(): void; - unref(): void; - on( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - on(event: "session", listener: (session: ServerHttp2Session) => void): void; - on( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - on(event: "error", listener: (err: Error) => void): void; - on(event: "close" | "listening" | "timeout", listener: () => void): void; - on(event: "connection", listener: (socket: import("net").Socket) => void): void; - on(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; - once( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - once(event: "session", listener: (session: ServerHttp2Session) => void): void; - once( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - once(event: "error", listener: (err: Error) => void): void; - once(event: "close" | "listening" | "timeout", listener: () => void): void; - once(event: "connection", listener: (socket: import("net").Socket) => void): void; - once(event: "sessionError", listener: (err: Error, session: ServerHttp2Session) => void): void; - addListener( - event: "stream", - listener: (stream: ServerHttp2Stream, headers: IncomingHttpHeaders, flags: number) => void, - ): void; - addListener(event: "session", listener: (session: ServerHttp2Session) => void): void; - addListener( - event: "request", - listener: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): void; - addListener(event: "error", listener: (err: Error) => void): void; - addListener(event: "close" | "listening" | "timeout", listener: () => void): void; - addListener(event: "connection", listener: (socket: import("net").Socket) => void): void; - addListener( - event: "sessionError", - listener: (err: Error, session: ServerHttp2Session) => void, - ): void; - } - - export interface ServerOptions { - [option: string]: unknown; - } - - export function createServer( - onRequestHandler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): Http2Server; - export function createServer( - options: ServerOptions, - onRequestHandler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void, - ): Http2Server; - - export function connect( - authority: string, - listener?: (session: ClientHttp2Session, socket: import("net").Socket) => void, - ): ClientHttp2Session; - export function connect( - authority: string, - options: Record, - listener?: (session: ClientHttp2Session, socket: import("net").Socket) => void, - ): ClientHttp2Session; - - export function getDefaultSettings(): any; - export function getPackedSettings(settings: Record): Uint8Array; - export const sensitiveHeaders: unique symbol; -} -declare module "node:http2" { - export * from "http2"; -} - -/* node:dgram — UDP sockets over the event loop (scr_dgram.c, linked only - * into using binaries). createSocket takes "udp4" or the - * { type: "udp4", reuseAddr? } literal; bind/connect bind NOW and defer - * 'listening'/'connect' to the next loop turn; send takes one string or - * Buffer datagram with an explicit port + address (the connected-send - * and callback forms have no lowering); address() returns the real - * {address, family, port} record and THROWS "Not running" before bind, - * like Node. The socket options throw Node's errors (EBADF before bind); - * add/dropMembership before bind is refused at run time (SC2020). 'error' - * with no listener exits 1 (the unhandled EventEmitter 'error' behavior). */ -declare module "dgram" { - export interface AddressInfo { - address: string; - family: string; - port: number; - } - export interface RemoteInfo { - address: string; - family: string; - port: number; - size: number; - } - export interface Socket { - bind(port: number, address?: string, callback?: () => void): void; - connect(port: number, address?: string, callback?: () => void): void; - send(msg: string | Uint8Array, port: number, address: string): void; - address(): AddressInfo; - close(callback?: () => void): void; - disconnect(): void; - unref(): void; - ref(): void; - setBroadcast(flag: boolean): void; - setTTL(ttl: number): number; - setMulticastTTL(ttl: number): number; - setMulticastLoopback(flag: boolean): boolean; - setMulticastInterface(multicastInterface: string): void; - addMembership(multicastAddress: string, multicastInterface?: string): void; - dropMembership(multicastAddress: string, multicastInterface?: string): void; - getRecvBufferSize(): number; - getSendBufferSize(): number; - setRecvBufferSize(size: number): void; - setSendBufferSize(size: number): void; - getSendQueueSize(): number; - getSendQueueCount(): number; - on(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): void; - on(event: "listening" | "close" | "connect", listener: () => void): void; - on(event: "error", listener: (err: Error) => void): void; - once(event: "message", listener: (msg: Buffer, rinfo: RemoteInfo) => void): void; - once(event: "listening" | "close" | "connect", listener: () => void): void; - once(event: "error", listener: (err: Error) => void): void; - } - export function createSocket(type: "udp4"): Socket; - export function createSocket(options: { type: "udp4"; reuseAddr?: boolean }): Socket; -} -declare module "node:dgram" { - export * from "dgram"; -} - -/* node:dns — the lookup slice (scr_dgram.c hosts it; getaddrinfo runs AT - * CALL TIME and the callback defers to the next loop turn — SEMANTICS.md - * documents the blocking divergence). Only the { family: 4 } options - * form lowers; failures deliver Node's error shape ("getaddrinfo - * ENOTFOUND ") as the callback's Error argument, with "" for - * the address where Node passes undefined. */ -declare module "dns" { - export interface LookupAddress { - address: string; - family: number; - } - export interface LookupOptions { - family?: number | "IPv4" | "IPv6"; - hints?: number; - all?: boolean; - order?: "ipv4first" | "ipv6first" | "verbatim"; - verbatim?: boolean; - } - export interface LookupOneOptions extends LookupOptions { - all?: false; - } - export interface LookupAllOptions extends LookupOptions { - all: true; - } - /* The callback comes with the other lookup deliveries (literal - * addresses on the next tick); the options follow lib/dns.js. */ - export function lookup( - hostname: string, - callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, - ): void; - export function lookup( - hostname: string, - family: number, - callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, - ): void; - export function lookup( - hostname: string, - options: LookupOneOptions, - callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void, - ): void; - export function lookup( - hostname: string, - options: LookupAllOptions, - callback: (err: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void, - ): void; - /* getnameinfo with NI_NAMEREQD: the host's name and the port's service - * name (the number when it has none). */ - export function lookupService( - address: string, - port: number, - callback: (err: NodeJS.ErrnoException | null, hostname: string, service: string) => void, - ): void; - /* The nameservers /etc/resolv.conf lists. */ - export function getServers(): string[]; - export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; - export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; - /* The promise forms (node:dns/promises). */ - export const promises: typeof import("dns/promises"); -} -declare module "node:dns" { - export * from "dns"; -} -/* node:dns/promises — lookup and lookupService as promises over the same - * resolution as the callback forms: argument errors throw synchronously, - * failures reject with the callbacks' errors. */ -declare module "dns/promises" { - import type { LookupAddress, LookupAllOptions, LookupOneOptions, LookupOptions } from "dns"; - export function lookup(hostname: string, family: number): Promise; - export function lookup(hostname: string, options: LookupOneOptions): Promise; - export function lookup(hostname: string, options: LookupAllOptions): Promise; - export function lookup( - hostname: string, - options: LookupOptions, - ): Promise; - export function lookup(hostname: string): Promise; - export function lookupService( - address: string, - port: number, - ): Promise<{ hostname: string; service: string }>; - export function getServers(): string[]; - export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; - export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; -} -declare module "node:dns/promises" { - export * from "dns/promises"; -} - -/* node:v8 — importable for the packages that require it at top level. - * getHeapStatistics answers allocator-derived figures in Node's shape - * (approximate: the runtime heap's total and used bytes, the machine's - * memory as the limit; V8-only figures are 0). The other members observe - * V8 itself and fence at their use with the by-design reason; they are - * declared so programs that type-check under their own tsc reach that - * fence. */ -declare module "v8" { - type DoesZapCodeSpaceFlag = 0 | 1; - export interface HeapInfo { - total_heap_size: number; - total_heap_size_executable: number; - total_physical_size: number; - total_available_size: number; - used_heap_size: number; - heap_size_limit: number; - malloced_memory: number; - peak_malloced_memory: number; - does_zap_garbage: DoesZapCodeSpaceFlag; - number_of_native_contexts: number; - number_of_detached_contexts: number; - total_global_handles_size: number; - used_global_handles_size: number; - external_memory: number; - } - export function getHeapStatistics(): HeapInfo; - export function cachedDataVersionTag(): number; - export function getHeapSpaceStatistics(): unknown[]; - export function getHeapCodeStatistics(): unknown; - export function getHeapSnapshot(options?: unknown): unknown; - export function writeHeapSnapshot(fileName?: string, options?: unknown): string; - export function setFlagsFromString(flags: string): void; - export function setHeapSnapshotNearHeapLimit(limit: number): void; - export function serialize(value: unknown): Buffer; - export function deserialize(buffer: Uint8Array): unknown; - export function takeCoverage(): void; - export function stopCoverage(): void; - export function isStringOneByteRepresentation(content: string): boolean; -} -declare module "node:v8" { - export * from "v8"; -} - -/* node:worker_threads — native workers use statically compiled entry points. - * Lowering and runtime diagnostics define the supported options and methods. */ -declare module "worker_threads" { - export const isMainThread: boolean; - export const isInternalThread: boolean; - export const threadId: number; - export interface ResourceLimits { - maxYoungGenerationSizeMb?: number; - maxOldGenerationSizeMb?: number; - codeRangeSizeMb?: number; - stackSizeMb?: number; - } - /* {} in the main thread */ - export const resourceLimits: ResourceLimits; - /* the Worker's name option, trimmed ("WorkerThread" by default); "" in the main thread */ - export const threadName: string | null; - export interface WorkerOptions { - workerData?: unknown; - transferList?: readonly unknown[]; - argv?: readonly unknown[]; - name?: string; - resourceLimits?: ResourceLimits; - stdout?: boolean; - stderr?: boolean; - stdin?: boolean; - [option: string]: unknown; - } - export class Worker { - constructor(filename: string | URL, options?: WorkerOptions); - readonly threadId: number; - readonly resourceLimits?: ResourceLimits; - readonly threadName: string | null; - /* with the stdout/stderr options: the worker's writes to those streams */ - readonly stdout: import("stream").Readable; - readonly stderr: import("stream").Readable; - /* with the stdin option: what the worker reads from process.stdin */ - readonly stdin: import("stream").Writable | null; - /* terminate(), resolving with undefined once the worker exited */ - [Symbol.asyncDispose](): Promise; - on(event: "online", listener: () => void): this; - on(event: "message", listener: (value: any) => void): this; - on(event: "error", listener: (error: any) => void): this; - on(event: "exit", listener: (code: number) => void): this; - on(event: string, listener: (...args: any[]) => void): this; - once(event: string, listener: (...args: any[]) => void): this; - off(event: string, listener: (...args: any[]) => void): this; - addListener(event: string, listener: (...args: any[]) => void): this; - removeListener(event: string, listener: (...args: any[]) => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - postMessage(value: unknown, transferList?: readonly unknown[]): void; - ref(): void; - unref(): void; - terminate(): Promise; - removeAllListeners(event?: string): this; - listenerCount(event: string, listener?: (...args: any[]) => void): number; - } - export class MessagePort { - on(event: "message", listener: (value: any) => void): this; - on(event: "close", listener: () => void): this; - on(event: string, listener: (...args: any[]) => void): this; - once(event: string, listener: (...args: any[]) => void): this; - off(event: string, listener: (...args: any[]) => void): this; - addListener(event: string, listener: (...args: any[]) => void): this; - removeListener(event: string, listener: (...args: any[]) => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - postMessage(value: unknown, transferList?: readonly unknown[]): void; - start(): void; - close(callback?: () => void): void; - ref(): void; - unref(): void; - hasRef(): boolean; - removeAllListeners(event?: string): this; - listenerCount(event: string, listener?: (...args: any[]) => void): number; - } - /* Two entangled ports; a port moves to another thread through a - * postMessage or workerData transferList. */ - export class MessageChannel { - readonly port1: MessagePort; - readonly port2: MessagePort; - } - export function receiveMessageOnPort(port: MessagePort): { message: any } | undefined; - /* This thread's environment data; each new Worker starts from a clone. */ - export function setEnvironmentData(key: any, value?: any): void; - export function getEnvironmentData(key: any): any; - /* A marked object cannot be listed in a transfer list. */ - export function markAsUntransferable(object: any): void; - /* structuredClone and postMessage throw DataCloneError for a marked - * object, error or class instance */ - export function markAsUncloneable(object: any): void; - export function isMarkedAsUntransferable(object: any): boolean; - /* Delivers each message to every other open channel with the name, in - * any thread, as a 'message' MessageEvent. */ - export class BroadcastChannel extends EventTarget { - constructor(name: string); - readonly name: string; - onmessage: ((event: MessageEvent) => void) | null; - onmessageerror: ((event: MessageEvent) => void) | null; - postMessage(message: unknown): void; - close(): void; - ref(): this; - unref(): this; - } - export const parentPort: MessagePort | null; - export const workerData: any; -} -declare module "node:worker_threads" { - export * from "worker_threads"; -} - -/* node:buffer — the module spelling of the Buffer/atob/btoa globals. - * Buffer re-exports the GLOBAL declaration, so an imported binding - * resolves to the same symbol the lowerings already answer for (and - * third-party .d.ts files that `import { Buffer } from "node:buffer"` — - * file-entry-cache in a typed-JS source graph — typecheck). */ -declare module "buffer" { - export function isAscii(input: ArrayBuffer | ArrayBufferView): boolean; - export function isUtf8(input: ArrayBuffer | ArrayBufferView): boolean; - export function transcode(source: Uint8Array, fromEncoding: string, toEncoding: string): Buffer; - export function atob(data: string): string; - export function btoa(data: string): string; - export const kMaxLength: number; - export const kStringMaxLength: number; - export const Buffer: BufferConstructor; - export type Blob = globalThis.Blob; - export type File = globalThis.File; - export const Blob: typeof globalThis.Blob; - export const File: typeof globalThis.File; -} -declare module "node:buffer" { - export * from "buffer"; -} - -/* node:cluster — the primary-probe slice. A compiled binary never runs - * as a cluster worker (cluster forks the node binary itself — the - * mechanism is na for compiled programs), so isPrimary lowers to `true` - * and isWorker to `false`; everything else is undeclared. */ -declare module "cluster" { - const cluster: { - readonly isPrimary: boolean; - readonly isMaster: boolean; - readonly isWorker: boolean; - }; - export = cluster; -} -declare module "node:cluster" { - import cluster = require("cluster"); - export = cluster; -} - /* node:tty — the fd probe (the same isatty(3) behind process.*.isTTY) * and the supported structural view of a process output stream. */ declare module "tty" { @@ -7418,808 +1663,6 @@ declare module "node:tty" { export * from "tty"; } -/* node:async_hooks — declared surface without lowering: async-hook - * instrumentation is guarded behind env flags in harness code; a reached - * use fences at its site. */ -declare module "async_hooks" { - /* AsyncLocalStorage: fiber-carried context snapshots — run/getStore/ - * exit/enterWith/disable lower onto the runtime's active-slot machinery - * (als.* libCalls); values cross as dyn values. bind and snapshot bind - * functions to the active context; the options carry the store's - * default value and name. */ - export interface AsyncLocalStorageOptions { - defaultValue?: any; - name?: string | undefined; - } - export class AsyncLocalStorage { - constructor(options?: AsyncLocalStorageOptions); - static bind any>(fn: Func): Func; - static snapshot(): (fn: (...args: TArgs) => R, ...args: TArgs) => R; - readonly name: string; - run(store: T, callback: (...args: any[]) => R, ...args: any[]): R; - exit(callback: (...args: any[]) => R, ...args: any[]): R; - getStore(): T | undefined; - enterWith(store: T): void; - disable(): void; - } - export interface AsyncHook { - enable(): AsyncHook; - disable(): AsyncHook; - } - export function createHook(callbacks: { - init?: (asyncId: number, type: string, triggerAsyncId: number, resource: unknown) => void; - before?: (asyncId: number) => void; - after?: (asyncId: number) => void; - destroy?: (asyncId: number) => void; - }): AsyncHook; - export function executionAsyncId(): number; - export function triggerAsyncId(): number; -} -declare module "node:async_hooks" { - export * from "async_hooks"; -} -/* node:events — the EventEmitter class (the module object IS the class, - * like Node's `module.exports = EventEmitter`). Listener/emit signatures - * mirror @types/node's untyped `...args: any[]` surface; the compiler's - * lowering unifies each event name's argument tuple program-wide and - * checks annotated listener parameters against it (unannotated non-empty - * parameter lists have no static types and fence at the registration). */ -declare module "events" { - class EventEmitter { - constructor(); - static defaultMaxListeners: number; - /* Listeners registered under this symbol observe 'error' emits first - * (an emit with no 'error' listener still throws). */ - static readonly errorMonitor: unique symbol; - static addAbortListener(signal: AbortSignal, listener: (event: Event) => void): Disposable; - on(eventName: string | symbol, listener: (...args: any[]) => void): this; - addListener(eventName: string | symbol, listener: (...args: any[]) => void): this; - once(eventName: string | symbol, listener: (...args: any[]) => void): this; - prependListener(eventName: string | symbol, listener: (...args: any[]) => void): this; - prependOnceListener(eventName: string | symbol, listener: (...args: any[]) => void): this; - off(eventName: string | symbol, listener: (...args: any[]) => void): this; - removeListener(eventName: string | symbol, listener: (...args: any[]) => void): this; - removeAllListeners(eventName?: string | symbol): this; - emit(eventName: string | symbol, ...args: any[]): boolean; - listenerCount(eventName: string | symbol, listener?: (...args: any[]) => void): number; - /* Declared as zero-parameter closures so the RESULT is statically - * consumable (.length, indexing, identity): the lowering only admits - * these calls for events whose argument tuple is empty — a listener - * pulled from the array is then safely callable too. Events with - * arguments fence (their element type would lie). */ - listeners(eventName: string): Array<() => void>; - rawListeners(eventName: string): Array<() => void>; - eventNames(): string[]; - setMaxListeners(n: number): this; - getMaxListeners(): number; - } - import internal = require("node:events"); - namespace EventEmitter { - // The class under its own name, so `import { EventEmitter }` resolves - // (the @types/node namespace-alias pattern). - export { internal as EventEmitter }; - // The event's arguments, as checked-dynamic values. - export function once( - emitter: EventEmitter | NodeJS.ReadableStream | NodeJS.WritableStream, - eventName: string, - options?: { signal?: AbortSignal }, - ): Promise; - /* The emitter's own listenerCount/getMaxListeners/setMaxListeners/ - * listeners, over EventEmitter instances. */ - export function listenerCount(emitter: EventEmitter, eventName: string | symbol): number; - export function getMaxListeners(emitter: EventEmitter): number; - export function setMaxListeners(n?: number, ...eventTargets: EventEmitter[]): void; - export function getEventListeners(emitter: EventEmitter, name: string): Array<() => void>; - } - export = EventEmitter; -} -declare module "node:events" { - import events = require("events"); - export = events; -} - -/* node:stream — the static stream classes (runtime-provided, the - * EventEmitter-hierarchy precedent). Phase 1 is the OPTIONS-OBJECT - * constructor surface: `new Readable({ read() {} })`, `new Writable({ - * write(chunk, enc, cb) {} })`, Duplex/Transform/PassThrough, push/read, - * flowing vs paused, write/end backpressure + 'drain', pipe/unpipe, and - * destroy — with Node's event orderings. Chunks are BYTES (Buffer), with - * strings converted at push/write like Node, and any value on an - * object-mode side (objectMode, readableObjectMode, writableObjectMode — - * the object-mode constructor overloads come first). The runtime-fired - * events carry per-class FORCED argument tuples ('data' is one chunk, - * 'error' one Error, 'pipe'/'unpipe' one Readable, the rest empty) — the - * overloads below hand tsc the same contract so unannotated listener - * parameters contextually type in .js sources. */ -declare module "stream" { - import { EventEmitter } from "events"; - - interface ReadableOptions { - highWaterMark?: number; - encoding?: string; - objectMode?: boolean; - read?(this: Readable, size: number): void; - destroy?(this: Readable, error: Error | null, callback: (error?: Error | null) => void): void; - construct?(this: Readable, callback: (error?: Error | null) => void): void; - autoDestroy?: boolean; - emitClose?: boolean; - captureRejections?: boolean; - defaultEncoding?: string; - signal?: AbortSignal; - } - interface WritableOptions { - highWaterMark?: number; - decodeStrings?: boolean; - defaultEncoding?: string; - objectMode?: boolean; - write?( - this: Writable, - chunk: Buffer, - encoding: string, - callback: (error?: Error | null) => void, - ): void; - writev?( - this: Writable, - chunks: Array<{ chunk: any; encoding: string }>, - callback: (error?: Error | null) => void, - ): void; - final?(this: Writable, callback: (error?: Error | null) => void): void; - destroy?(this: Writable, error: Error | null, callback: (error?: Error | null) => void): void; - construct?(this: Writable, callback: (error?: Error | null) => void): void; - autoDestroy?: boolean; - emitClose?: boolean; - captureRejections?: boolean; - signal?: AbortSignal; - } - interface DuplexOptions { - highWaterMark?: number; - readableHighWaterMark?: number; - writableHighWaterMark?: number; - encoding?: string; - decodeStrings?: boolean; - objectMode?: boolean; - readableObjectMode?: boolean; - writableObjectMode?: boolean; - allowHalfOpen?: boolean; - read?(this: Duplex, size: number): void; - write?( - this: Duplex, - chunk: Buffer, - encoding: string, - callback: (error?: Error | null) => void, - ): void; - final?(this: Duplex, callback: (error?: Error | null) => void): void; - destroy?(this: Duplex, error: Error | null, callback: (error?: Error | null) => void): void; - construct?(this: Duplex, callback: (error?: Error | null) => void): void; - autoDestroy?: boolean; - emitClose?: boolean; - captureRejections?: boolean; - readable?: boolean; - writable?: boolean; - defaultEncoding?: string; - writev?( - this: Duplex, - chunks: Array<{ chunk: any; encoding: string }>, - callback: (error?: Error | null) => void, - ): void; - signal?: AbortSignal; - } - interface TransformOptions { - highWaterMark?: number; - readableHighWaterMark?: number; - writableHighWaterMark?: number; - encoding?: string; - decodeStrings?: boolean; - objectMode?: boolean; - readableObjectMode?: boolean; - writableObjectMode?: boolean; - allowHalfOpen?: boolean; - transform?( - this: Transform, - chunk: Buffer, - encoding: string, - callback: (error?: Error | null, data?: Buffer | string) => void, - ): void; - flush?(this: Transform, callback: (error?: Error | null, data?: Buffer | string) => void): void; - destroy?(this: Transform, error: Error | null, callback: (error?: Error | null) => void): void; - construct?(this: Transform, callback: (error?: Error | null) => void): void; - read?(this: Transform, size: number): void; - write?( - this: Transform, - chunk: Buffer, - encoding: string, - callback: (error?: Error | null) => void, - ): void; - final?(this: Transform, callback: (error?: Error | null) => void): void; - writev?(this: Transform, chunks: unknown[], callback: (error?: Error | null) => void): void; - autoDestroy?: boolean; - emitClose?: boolean; - captureRejections?: boolean; - readable?: boolean; - writable?: boolean; - defaultEncoding?: string; - signal?: AbortSignal; - } - - /* Object-mode options (objectMode: true, or a Duplex's or Transform's - * readableObjectMode / writableObjectMode): chunks and completion data - * are any values, and an object-mode write's encoding argument reaches - * write/transform as passed (undefined when absent, null when a - * callback took its place). The constructors take these before the - * byte-mode options, so a byte-mode stream's callbacks keep Buffer - * chunks. */ - type ObjectModeEncoding = string | null | undefined; - type ObjectModeFlag = - | { objectMode: true } - | { readableObjectMode: true } - | { writableObjectMode: true }; - interface ObjectWritableOptions extends Omit { - objectMode: true; - write?( - this: Writable, - chunk: any, - encoding: ObjectModeEncoding, - callback: (error?: Error | null) => void, - ): void; - } - type ObjectDuplexOptions = Omit & { - write?( - this: Duplex, - chunk: any, - encoding: ObjectModeEncoding, - callback: (error?: Error | null) => void, - ): void; - } & ObjectModeFlag; - type ObjectTransformOptions = Omit & { - transform?( - this: Transform, - chunk: any, - encoding: ObjectModeEncoding, - callback: (error?: Error | null, data?: any) => void, - ): void; - flush?(this: Transform, callback: (error?: Error | null, data?: any) => void): void; - write?( - this: Transform, - chunk: any, - encoding: ObjectModeEncoding, - callback: (error?: Error | null) => void, - ): void; - } & ObjectModeFlag; - - /* decodeStrings: false — a string write reaches write/transform as the - * string with its encoding (Buffers keep 'buffer'). */ - interface StringWritableOptions extends Omit { - decodeStrings: false; - write?( - this: Writable, - chunk: string | Buffer, - encoding: string, - callback: (error?: Error | null) => void, - ): void; - } - type StringDuplexOptions = Omit & { - decodeStrings: false; - write?( - this: Duplex, - chunk: string | Buffer, - encoding: string, - callback: (error?: Error | null) => void, - ): void; - }; - type StringTransformOptions = Omit & { - decodeStrings: false; - transform?( - this: Transform, - chunk: string | Buffer, - encoding: string, - callback: (error?: Error | null, data?: Buffer | string) => void, - ): void; - }; - - /* The operators' options: concurrency and highWaterMark bound map's - * in-flight callbacks and queued results. */ - interface ArrayOptions { - concurrency?: number; - highWaterMark?: number; - signal?: AbortSignal; - } - - class Readable extends EventEmitter { - constructor(opts?: ReadableOptions); - /* The underscore-method surface (@types/node declares these on the - * classes; assigning them post-construction shadows the prototype - * method and the machinery calls through the assignment — lowered - * onto the option-callback slots). */ - _read(size: number): void; - _destroy(error: Error | null, callback: (error?: Error | null) => void): void; - [Symbol.asyncIterator](): AsyncIterableIterator; - iterator(options?: { destroyOnReturn?: boolean }): AsyncIterableIterator; - /* compose(this, stage), destroyed when options.signal aborts. */ - compose(stage: ComposeStage, options?: { signal: AbortSignal }): Duplex; - push(chunk: Buffer | string | null, encoding?: string): boolean; - /* An object-mode stream's values. */ - push(chunk: any, encoding?: string): boolean; - unshift(chunk: Buffer | string, encoding?: string): void; - unshift(chunk: any, encoding?: string): void; - /* A Buffer, a string once encoded, an object-mode value, or null (as - * @types/node types it). */ - read(size?: number): any; - pause(): this; - resume(): this; - isPaused(): boolean; - setEncoding(encoding: string): this; - pipe(destination: T, options?: { end?: boolean }): T; - unpipe(destination?: NodeJS.WritableStream): this; - destroy(error?: Error): this; - readonly readable: boolean; - readonly readableEnded: boolean; - readonly readableFlowing: boolean | null; - readonly readableLength: number; - readonly readableHighWaterMark: number; - readonly readableObjectMode: boolean; - readonly closed: boolean; - readonly destroyed: boolean; - readonly errored: Error | null; - static from(iterable: unknown, options?: ReadableOptions): Readable; - /* A Readable over a web ReadableStream's reader. */ - static fromWeb( - readableStream: globalThis.ReadableStream, - options?: { - encoding?: string; - highWaterMark?: number; - objectMode?: boolean; - signal?: AbortSignal; - }, - ): Readable; - /* The Readable operators: chunks are strings or Buffers, map and - * flatMap callbacks return strings or Buffers (flatMap: arrays of - * them, or strings), and callbacks that declare the options argument - * are refused. */ - map( - fn: (data: any, options?: Pick) => any, - options?: ArrayOptions, - ): Readable; - filter( - fn: (data: any, options?: Pick) => boolean | Promise, - options?: ArrayOptions, - ): Readable; - forEach( - fn: (data: any, options?: Pick) => void | Promise, - options?: ArrayOptions, - ): Promise; - toArray(options?: Pick): Promise; - some( - fn: (data: any, options?: Pick) => boolean | Promise, - options?: ArrayOptions, - ): Promise; - find( - fn: (data: any, options?: Pick) => boolean | Promise, - options?: ArrayOptions, - ): Promise; - every( - fn: (data: any, options?: Pick) => boolean | Promise, - options?: ArrayOptions, - ): Promise; - flatMap( - fn: (data: any, options?: Pick) => any, - options?: ArrayOptions, - ): Readable; - drop(limit: number, options?: Pick): Readable; - take(limit: number, options?: Pick): Readable; - reduce( - fn: (previous: any, data: any, options?: Pick) => T, - initial?: undefined, - options?: Pick, - ): Promise; - reduce( - fn: (previous: T, data: any, options?: Pick) => T, - initial: T, - options?: Pick, - ): Promise; - on(event: "data", listener: (chunk: any) => void): this; - on(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; - on(event: "error", listener: (err: Error) => void): this; - on(event: string, listener: (...args: any[]) => void): this; - once(event: "data", listener: (chunk: any) => void): this; - once(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; - once(event: "error", listener: (err: Error) => void): this; - once(event: string, listener: (...args: any[]) => void): this; - addListener(event: "data", listener: (chunk: any) => void): this; - addListener( - event: "end" | "close" | "readable" | "pause" | "resume", - listener: () => void, - ): this; - addListener(event: "error", listener: (err: Error) => void): this; - addListener(event: string, listener: (...args: any[]) => void): this; - prependListener(event: "data", listener: (chunk: any) => void): this; - prependListener( - event: "end" | "close" | "readable" | "pause" | "resume", - listener: () => void, - ): this; - prependListener(event: "error", listener: (err: Error) => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - prependOnceListener(event: "data", listener: (chunk: any) => void): this; - prependOnceListener( - event: "end" | "close" | "readable" | "pause" | "resume", - listener: () => void, - ): this; - prependOnceListener(event: "error", listener: (err: Error) => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - off(event: "data", listener: (chunk: any) => void): this; - off(event: "end" | "close" | "readable" | "pause" | "resume", listener: () => void): this; - off(event: "error", listener: (err: Error) => void): this; - off(event: string, listener: (...args: any[]) => void): this; - removeListener(event: "data", listener: (chunk: any) => void): this; - removeListener( - event: "end" | "close" | "readable" | "pause" | "resume", - listener: () => void, - ): this; - removeListener(event: "error", listener: (err: Error) => void): this; - removeListener(event: string, listener: (...args: any[]) => void): this; - } - - class Writable extends EventEmitter { - constructor(opts: ObjectWritableOptions); - constructor(opts: StringWritableOptions); - constructor(opts?: WritableOptions); - /* The underscore-method surface (see Readable). */ - _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void; - _writev( - chunks: Array<{ chunk: any; encoding: string }>, - callback: (error?: Error | null) => void, - ): void; - _final(callback: (error?: Error | null) => void): void; - _destroy(error: Error | null, callback: (error?: Error | null) => void): void; - write(chunk: Buffer | string, callback?: (error?: Error | null) => void): boolean; - write( - chunk: Buffer | string, - encoding: string, - callback?: (error?: Error | null) => void, - ): boolean; - /* An object-mode stream's values (the encoding argument reaches - * _write as passed). */ - write(chunk: any, callback?: (error?: Error | null) => void): boolean; - write( - chunk: any, - encoding: ObjectModeEncoding, - callback?: (error?: Error | null) => void, - ): boolean; - end(callback?: () => void): this; - end(chunk: Buffer | string, callback?: () => void): this; - end(chunk: Buffer | string, encoding: string, callback?: () => void): this; - end(chunk: any, callback?: () => void): this; - end(chunk: any, encoding: ObjectModeEncoding, callback?: () => void): this; - cork(): void; - uncork(): void; - setDefaultEncoding(encoding: string): this; - destroy(error?: Error): this; - readonly writable: boolean; - readonly writableEnded: boolean; - readonly writableFinished: boolean; - readonly writableLength: number; - readonly writableHighWaterMark: number; - readonly writableObjectMode: boolean; - readonly writableNeedDrain: boolean; - readonly writableCorked: number; - readonly closed: boolean; - readonly destroyed: boolean; - readonly errored: Error | null; - on(event: "drain" | "finish" | "close", listener: () => void): this; - on(event: "error", listener: (err: Error) => void): this; - on(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - on(event: string, listener: (...args: any[]) => void): this; - once(event: "drain" | "finish" | "close", listener: () => void): this; - once(event: "error", listener: (err: Error) => void): this; - once(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - once(event: string, listener: (...args: any[]) => void): this; - addListener(event: "drain" | "finish" | "close", listener: () => void): this; - addListener(event: "error", listener: (err: Error) => void): this; - addListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - addListener(event: string, listener: (...args: any[]) => void): this; - prependListener(event: "drain" | "finish" | "close", listener: () => void): this; - prependListener(event: "error", listener: (err: Error) => void): this; - prependListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - prependOnceListener(event: "drain" | "finish" | "close", listener: () => void): this; - prependOnceListener(event: "error", listener: (err: Error) => void): this; - prependOnceListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - off(event: "drain" | "finish" | "close", listener: () => void): this; - off(event: "error", listener: (err: Error) => void): this; - off(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - off(event: string, listener: (...args: any[]) => void): this; - removeListener(event: "drain" | "finish" | "close", listener: () => void): this; - removeListener(event: "error", listener: (err: Error) => void): this; - removeListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - removeListener(event: string, listener: (...args: any[]) => void): this; - } - - /* Duplex is Readable + Writable in one object. TS single inheritance - * mirrors @types/node: extends Readable, the writable half declared - * again. The lowering treats both halves as first-class. */ - class Duplex extends Readable { - /* Duplex.from: an async generator function, an iterable, a string or - * a stream (duplexify.js). */ - static from(body: unknown): Duplex; - constructor(opts: ObjectDuplexOptions); - constructor(opts: StringDuplexOptions); - constructor(opts?: DuplexOptions); - /* The underscore-method surface (see Readable). */ - _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void; - _writev( - chunks: Array<{ chunk: any; encoding: string }>, - callback: (error?: Error | null) => void, - ): void; - _final(callback: (error?: Error | null) => void): void; - write(chunk: Buffer | string, callback?: (error?: Error | null) => void): boolean; - write( - chunk: Buffer | string, - encoding: string, - callback?: (error?: Error | null) => void, - ): boolean; - /* An object-mode stream's values (the encoding argument reaches - * _write as passed). */ - write(chunk: any, callback?: (error?: Error | null) => void): boolean; - write( - chunk: any, - encoding: ObjectModeEncoding, - callback?: (error?: Error | null) => void, - ): boolean; - end(callback?: () => void): this; - end(chunk: Buffer | string, callback?: () => void): this; - end(chunk: Buffer | string, encoding: string, callback?: () => void): this; - end(chunk: any, callback?: () => void): this; - end(chunk: any, encoding: ObjectModeEncoding, callback?: () => void): this; - cork(): void; - uncork(): void; - setDefaultEncoding(encoding: string): this; - readonly writable: boolean; - readonly writableEnded: boolean; - readonly writableFinished: boolean; - readonly writableLength: number; - readonly writableHighWaterMark: number; - readonly writableObjectMode: boolean; - readonly writableNeedDrain: boolean; - readonly writableCorked: number; - readonly allowHalfOpen: boolean; - on(event: "data", listener: (chunk: any) => void): this; - on( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - on(event: "error", listener: (err: Error) => void): this; - on(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - on(event: string, listener: (...args: any[]) => void): this; - once(event: "data", listener: (chunk: any) => void): this; - once( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - once(event: "error", listener: (err: Error) => void): this; - once(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - once(event: string, listener: (...args: any[]) => void): this; - addListener(event: "data", listener: (chunk: any) => void): this; - addListener( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - addListener(event: "error", listener: (err: Error) => void): this; - addListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - addListener(event: string, listener: (...args: any[]) => void): this; - prependListener(event: "data", listener: (chunk: any) => void): this; - prependListener( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - prependListener(event: "error", listener: (err: Error) => void): this; - prependListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - prependOnceListener(event: "data", listener: (chunk: any) => void): this; - prependOnceListener( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - prependOnceListener(event: "error", listener: (err: Error) => void): this; - prependOnceListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - off(event: "data", listener: (chunk: any) => void): this; - off( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - off(event: "error", listener: (err: Error) => void): this; - off(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - off(event: string, listener: (...args: any[]) => void): this; - removeListener(event: "data", listener: (chunk: any) => void): this; - removeListener( - event: "end" | "close" | "readable" | "pause" | "resume" | "drain" | "finish", - listener: () => void, - ): this; - removeListener(event: "error", listener: (err: Error) => void): this; - removeListener(event: "pipe" | "unpipe", listener: (src: Readable) => void): this; - removeListener(event: string, listener: (...args: any[]) => void): this; - } - - class Transform extends Duplex { - constructor(opts: ObjectTransformOptions); - constructor(opts: StringTransformOptions); - constructor(opts?: TransformOptions); - /* The underscore-method surface (see Readable). */ - _transform( - chunk: any, - encoding: string, - callback: (error?: Error | null, data?: any) => void, - ): void; - _flush(callback: (error?: Error | null, data?: any) => void): void; - } - class PassThrough extends Transform { - constructor(opts: ObjectTransformOptions); - constructor(opts: StringTransformOptions); - constructor(opts?: TransformOptions); - } - - /* addAbortSignal(signal, stream): the stream, destroyed with an - * AbortError (cause: signal.reason) when the signal aborts. */ - function addAbortSignal(signal: AbortSignal, stream: T): T; - /* pipeline(...stages, callback): the last stream, or the object-mode - * PassThrough that a function destination feeds. */ - type PipelineResult = T extends [ - ...unknown[], - infer D, - (...args: any[]) => unknown, - ] - ? D extends (...args: any[]) => unknown - ? PassThrough - : D - : unknown; - function pipeline(...streams: T): PipelineResult; - /* util.promisify's pipeline overload matches only pipeline itself: a - * generic signature alone admits every function. */ - namespace pipeline { - const __promisify__: typeof promises.pipeline; - } - /* A compose stage: a stream, an async generator function over the - * previous stage, or (first) an iterable, async iterable or web - * ReadableStream. */ - type ComposeStage = - | Readable - | Writable - | Iterable - | AsyncIterable - | globalThis.ReadableStream - | ((source: any, options?: { signal: AbortSignal }) => unknown); - /* compose(...streams): a Duplex that writes to the first stage and - * reads from the last, with the stages running as a pipeline. */ - function compose(...streams: ComposeStage[]): Duplex; - /* finished's options: readable/writable choose which ends count, - * error: false ignores 'error' events, signal aborts the wait. */ - interface FinishedOptions { - readable?: boolean; - writable?: boolean; - error?: boolean; - signal?: AbortSignal; - } - function finished(stream: unknown, callback: (err?: Error | null) => void): () => void; - function finished( - stream: unknown, - options: FinishedOptions | null, - callback: (err?: Error | null) => void, - ): () => void; - /* Folds to the TARGET platform's default at compile time (win32 16384, - * else 65536 — Node's own state.js split); literal argument only. */ - function getDefaultHighWaterMark(objectMode: boolean): number; - /* The promise pipeline's result: a last stage that is an async - * function resolves the promise with its value (before the options - * argument, when one follows). */ - type PipelinePromise = T extends [ - ...unknown[], - (...args: any[]) => Promise, - ] - ? Promise

- : T extends [ - ...unknown[], - (...args: any[]) => Promise, - { signal?: AbortSignal; end?: boolean }, - ] - ? Promise

- : Promise; - namespace promises { - function pipeline(...streams: T): PipelinePromise; - function finished( - stream: unknown, - options?: FinishedOptions & { cleanup?: boolean }, - ): Promise; - } - class Stream extends EventEmitter {} - - export { - Readable, - Writable, - Duplex, - Transform, - PassThrough, - Stream, - addAbortSignal, - pipeline, - compose, - finished, - getDefaultHighWaterMark, - promises, - }; - export type { - ReadableOptions, - WritableOptions, - DuplexOptions, - TransformOptions, - FinishedOptions, - PipelinePromise, - }; -} -declare module "node:stream" { - import stream = require("stream"); - export = stream; -} -declare module "stream/promises" { - /* The promise forms of finished/pipeline: a void promise the stream's - * terminal point settles (rejected with the error, or - * ERR_STREAM_PREMATURE_CLOSE on an early close). Stream arguments are - * the lowered surface — iterables/generators/functions fence per site. - * pipeline's trailing { signal, end } object and finished's options - * (readable, writable, error, cleanup, signal) are supported. A last - * stage that is an async function resolves the promise with its value. */ - function pipeline(...streams: T): import("stream").PipelinePromise; - function finished( - stream: unknown, - options?: { - readable?: boolean; - writable?: boolean; - error?: boolean; - cleanup?: boolean; - signal?: AbortSignal; - } | null, - ): Promise; - export { pipeline, finished }; -} -declare module "node:stream/promises" { - import streamPromises = require("stream/promises"); - export = streamPromises; -} -declare module "stream/consumers" { - /* The promise consumers over a readable: accumulate every chunk and - * settle — text (the utf8 decode), json (the text through JSON.parse: - * `unknown`, validated with a checked cast; malformed input rejects - * with the parse's SyntaxError), buffer (the concatenated bytes; - * string chunks contribute their utf8 bytes, Node's Blob rule). - * Stream errors reject; an early close rejects with - * ERR_STREAM_PREMATURE_CLOSE. arrayBuffer answers an ArrayBuffer over - * the bytes; blob fences per site. */ - function text(stream: unknown): Promise; - function json(stream: unknown): Promise; - function buffer(stream: unknown): Promise; - function arrayBuffer(stream: unknown): Promise; - function blob(stream: unknown): Promise; - export { arrayBuffer, blob, buffer, json, text }; -} -declare module "node:stream/consumers" { - import streamConsumers = require("stream/consumers"); - export = streamConsumers; -} -/* node:stream/web — the module exports the web stream globals themselves - * (require("stream/web").ReadableStream === globalThis.ReadableStream). - * ReadableStream is the implemented class; its reader and controller are - * exported as types. */ -declare module "stream/web" { - export const ReadableStream: typeof globalThis.ReadableStream; - export type ReadableStream = globalThis.ReadableStream; - export type ReadableStreamDefaultReader = globalThis.ReadableStreamDefaultReader; - export type ReadableStreamDefaultController = - globalThis.ReadableStreamDefaultController; - export type UnderlyingSource = globalThis.UnderlyingSource; - export type QueuingStrategy = globalThis.QueuingStrategy; -} -declare module "node:stream/web" { - export * from "stream/web"; -} interface ErrorConstructor { stackTraceLimit: number; } diff --git a/packages/compiler/src/frontend/dts-paths.ts b/packages/compiler/src/frontend/dts-paths.ts index 009d1c333..638e58641 100644 --- a/packages/compiler/src/frontend/dts-paths.ts +++ b/packages/compiler/src/frontend/dts-paths.ts @@ -20,7 +20,9 @@ function declarationPath(name: string): string { ? require.resolve("@scriptc/compiler/scriptc.d.ts") : name === "scriptc-overrides.d.ts" ? require.resolve("@scriptc/compiler/scriptc-overrides.d.ts") - : require.resolve("@scriptc/compiler/scriptc-node-fallback.d.ts") + : name === "scriptc-node-fallback.d.ts" + ? require.resolve("@scriptc/compiler/scriptc-node-fallback.d.ts") + : join(dirname(require.resolve("@scriptc/compiler/scriptc-node-fallback.d.ts")), name) : join(installedDeclarationRoot, name), ); } @@ -56,6 +58,9 @@ export function compilerOwnedFileName(file: string): string | null { for (const declaration of [ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]) { if (path === declaration) return `scriptc:${path.slice(path.lastIndexOf("/") + 1)}`; } + const modules = fallbackModuleRoot() + "/"; + if (path.startsWith(modules)) + return `scriptc:${FALLBACK_MODULE_DIRECTORY}/${path.slice(modules.length)}`; return null; } @@ -75,14 +80,138 @@ export function overridesDtsPath(): string { return declarationPath("scriptc-overrides.d.ts"); } -/** Path of the shipped FALLBACK declarations (console, process, node:fs) — - * part of the program only when the target project has no @types/node. - * With @types/node, the project's real Node types stand in and this file - * stands down (its declaration forms would collide). */ +/** Path of the shipped FALLBACK declarations (the console and process + * globals and the modules they name) — part of the program only when the + * target project has no @types/node. With @types/node, the project's real + * Node types stand in and this file stands down (its declaration forms + * would collide). */ export function fallbackDtsPath(): string { return declarationPath("scriptc-node-fallback.d.ts"); } +const FALLBACK_MODULE_DIRECTORY = "node-fallback"; + +/** The fallback declarations of the other Node modules, one file per module + * under node-fallback/, by specifier (both spellings). Each file references + * the files of the modules its declarations name. */ +const FALLBACK_MODULE_FILES: Readonly> = { + "assert": "assert.d.ts", + "assert/strict": "assert-strict.d.ts", + "async_hooks": "async_hooks.d.ts", + "buffer": "buffer.d.ts", + "child_process": "child_process.d.ts", + "cluster": "cluster.d.ts", + "constants": "constants.d.ts", + "crypto": "crypto.d.ts", + "dgram": "dgram.d.ts", + "diagnostics_channel": "diagnostics_channel.d.ts", + "dns": "dns.d.ts", + "dns/promises": "dns-promises.d.ts", + "events": "events.d.ts", + "fs": "fs.d.ts", + "fs/promises": "fs-promises.d.ts", + "graceful-fs": "graceful-fs.d.ts", + "http": "http.d.ts", + "http2": "http2.d.ts", + "https": "https.d.ts", + "module": "module.d.ts", + "net": "net.d.ts", + "node:assert": "assert.d.ts", + "node:assert/strict": "assert-strict.d.ts", + "node:async_hooks": "async_hooks.d.ts", + "node:buffer": "buffer.d.ts", + "node:child_process": "child_process.d.ts", + "node:cluster": "cluster.d.ts", + "node:constants": "constants.d.ts", + "node:crypto": "crypto.d.ts", + "node:dgram": "dgram.d.ts", + "node:diagnostics_channel": "diagnostics_channel.d.ts", + "node:dns": "dns.d.ts", + "node:dns/promises": "dns-promises.d.ts", + "node:events": "events.d.ts", + "node:fs": "fs.d.ts", + "node:fs/promises": "fs-promises.d.ts", + "node:http": "http.d.ts", + "node:http2": "http2.d.ts", + "node:https": "https.d.ts", + "node:module": "module.d.ts", + "node:net": "net.d.ts", + "node:os": "os.d.ts", + "node:path": "path.d.ts", + "node:path/posix": "path-posix.d.ts", + "node:path/win32": "path-win32.d.ts", + "node:process": "process.d.ts", + "node:punycode": "punycode.d.ts", + "node:querystring": "querystring.d.ts", + "node:stream": "stream.d.ts", + "node:stream/consumers": "stream-consumers.d.ts", + "node:stream/promises": "stream-promises.d.ts", + "node:stream/web": "stream-web.d.ts", + "node:string_decoder": "string_decoder.d.ts", + "node:test": "test.d.ts", + "node:timers": "timers.d.ts", + "node:timers/promises": "timers-promises.d.ts", + "node:tls": "tls.d.ts", + "node:url": "url.d.ts", + "node:util": "util.d.ts", + "node:util/types": "util-types.d.ts", + "node:v8": "v8.d.ts", + "node:worker_threads": "worker_threads.d.ts", + "node:zlib": "zlib.d.ts", + "os": "os.d.ts", + "path": "path.d.ts", + "path/posix": "path-posix.d.ts", + "path/win32": "path-win32.d.ts", + "process": "process.d.ts", + "punycode": "punycode.d.ts", + "querystring": "querystring.d.ts", + "stream": "stream.d.ts", + "stream/consumers": "stream-consumers.d.ts", + "stream/promises": "stream-promises.d.ts", + "stream/web": "stream-web.d.ts", + "string_decoder": "string_decoder.d.ts", + "timers": "timers.d.ts", + "timers/promises": "timers-promises.d.ts", + "tls": "tls.d.ts", + "url": "url.d.ts", + "util": "util.d.ts", + "util/types": "util-types.d.ts", + "v8": "v8.d.ts", + "worker_threads": "worker_threads.d.ts", + "zlib": "zlib.d.ts", +}; + +function fallbackModuleRoot(): string { + return declarationPath(FALLBACK_MODULE_DIRECTORY); +} + +/** The fallback declaration file of the Node module `specifier`, or null. */ +export function fallbackModulePath(specifier: string): string | null { + return Object.hasOwn(FALLBACK_MODULE_FILES, specifier) + ? declarationPath(`${FALLBACK_MODULE_DIRECTORY}/${FALLBACK_MODULE_FILES[specifier]}`) + : null; +} + +const QUOTED_SPECIFIER = /["'`]((?:node:)?[a-z][a-z0-9_]*(?:[-/][a-z0-9_]+)?)["'`]/g; + +/** The fallback declaration files of the Node modules `text` names in a + * string literal: an over-approximation of the modules its imports, requires, + * import types and JSDoc types can name. */ +export function fallbackModulesNamedIn(text: string): string[] { + const files = new Set(); + for (const match of text.matchAll(QUOTED_SPECIFIER)) { + const file = fallbackModulePath(match[1]!); + if (file !== null) files.add(file); + } + return [...files]; +} + +/** True for the fallback declarations: the core file and the module files. */ +export function isFallbackDtsPath(file: string): boolean { + const path = tsgoPath(file); + return path === fallbackDtsPath() || path.startsWith(fallbackModuleRoot() + "/"); +} + /** True for files belonging to the adopted Node type surface: the * @types/node package itself and undici-types (its dependency — the * web-platform globals: fetch/Response/AbortSignal/ReadableStream/...). diff --git a/packages/compiler/src/frontend/fork-target.test.ts b/packages/compiler/src/frontend/fork-target.test.ts index ffe01ee47..23982976a 100644 --- a/packages/compiler/src/frontend/fork-target.test.ts +++ b/packages/compiler/src/frontend/fork-target.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test, vi } from "vitest"; import { forkTargetPaths } from "./fork-target.js"; -import { fallbackDtsPath } from "./dts-paths.js"; +import { fallbackDtsPath, fallbackModulePath } from "./dts-paths.js"; import { Ts7Host } from "./ts7/program-host.js"; import { Ts7Api } from "./ts7/rpc-api.js"; import { ModuleKind, ModuleResolutionKind } from "./ts7/enums.js"; @@ -35,12 +35,15 @@ test("fork discovery batches only candidate bindings and preserves aliases and s `, ); const host = new Ts7Host((options) => new Ts7Api(options), { cwd: directory }); - const program = host.createProgram([entry, declarations], { - noLib: true, - types: [], - module: ModuleKind.ESNext, - moduleResolution: ModuleResolutionKind.Bundler, - }); + const program = host.createProgram( + [entry, declarations, fallbackModulePath("node:child_process")!], + { + noLib: true, + types: [], + module: ModuleKind.ESNext, + moduleResolution: ModuleResolutionKind.Bundler, + }, + ); const checker = program.getTypeChecker(); const batch = vi.spyOn(checker, "prefetchSymbolNodesExact"); const whole = vi.spyOn(checker.raw, "getTypeOfSymbol"); @@ -73,12 +76,15 @@ function discover(files: Record): { targets: string[]; queried: for (const [name, text] of Object.entries(files)) writeFileSync(join(directory, name), text); const entry = join(directory, "main.ts"); const host = new Ts7Host((options) => new Ts7Api(options), { cwd: directory }); - const program = host.createProgram([entry, fallbackDtsPath()], { - noLib: true, - types: [], - module: ModuleKind.ESNext, - moduleResolution: ModuleResolutionKind.Bundler, - }); + const program = host.createProgram( + [entry, fallbackDtsPath(), fallbackModulePath("node:child_process")!], + { + noLib: true, + types: [], + module: ModuleKind.ESNext, + moduleResolution: ModuleResolutionKind.Bundler, + }, + ); const batch = vi.spyOn(program.getTypeChecker(), "prefetchSymbolNodesExact"); try { const targets = forkTargetPaths(program, [program.getSourceFile(entry)!]).map((path) => diff --git a/packages/compiler/src/frontend/fork-target.ts b/packages/compiler/src/frontend/fork-target.ts index 93fc7c244..398e1b0aa 100644 --- a/packages/compiler/src/frontend/fork-target.ts +++ b/packages/compiler/src/frontend/fork-target.ts @@ -3,7 +3,7 @@ import { fileURLToPath, pathToFileURL } from "node:url"; import * as ts from "./ts7/adapter.js"; import { ambientDtsPath, - fallbackDtsPath, + isFallbackDtsPath, isNodeTypesPath, overridesDtsPath, tsgoPath, @@ -22,7 +22,7 @@ export function isNodeGlobal(program: ts.Program, expression: ts.Identifier): bo const source = ts.getSourceFileOfNode(declaration); return ( source.isDeclarationFile && - (tsgoPath(source.fileName) === fallbackDtsPath() || isNodeTypesPath(source.fileName)) + (isFallbackDtsPath(source.fileName) || isNodeTypesPath(source.fileName)) ); }) ); @@ -333,7 +333,7 @@ const SPELLED_CHILD_PROCESS = new RegExp( function namesChildProcess(program: ts.Program): boolean { const known = program.analysis.namesChildProcess; if (known !== null) return known; - const own = new Set([ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]); + const own = new Set([ambientDtsPath(), overridesDtsPath()]); // Implementation files are already fetched; any other file is read as // the host serves it, without fetching its syntax tree. const texts = new Map(); @@ -343,7 +343,7 @@ function namesChildProcess(program: ts.Program): boolean { let names = false; for (const fileName of program.getSourceFileNames()) { const path = tsgoPath(fileName); - if (isNodeTypesPath(fileName) || own.has(path)) continue; + if (isNodeTypesPath(fileName) || isFallbackDtsPath(fileName) || own.has(path)) continue; const text = texts.get(path) ?? program.getSourceText(fileName) ?? ""; if ( text.includes("child_process") || diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index e9e351429..106ffb9a7 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -274,12 +274,12 @@ import { cjsExportsAssignedInFunctions, cjsReexportedModules, cjsNamespaceExportNames, - fallbackDtsPath, isCheckedJsSourceFile, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isNodeEsmFile, + isFallbackDtsPath, isNodeTypesPath, locOf, orderedImportsOf, @@ -3325,7 +3325,6 @@ export class Lowerer { readonly typeMemo = new Map(); readonly ambient = ambientDtsPath(); readonly overridesAmbient = overridesDtsPath(); - readonly fallbackAmbient = fallbackDtsPath(); /** The one mapType context: registries + hooks, assembled in the * constructor (typeParamResolver reads the CURRENT instantiation bindings * through `this`, so the same ctx serves generic bodies too). */ @@ -13439,7 +13438,7 @@ export class Lowerer { readonly isStdlibFile = (sf: ts.SourceFile): boolean => sf.fileName === this.ambient || sf.fileName === this.overridesAmbient || - sf.fileName === this.fallbackAmbient || + isFallbackDtsPath(sf.fileName) || this.program.isSourceFileDefaultLibrary(sf) || (sf.isDeclarationFile && isNodeTypesPath(sf.fileName)); diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index b93e3f217..f64733fc0 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -104,6 +104,8 @@ import { cjsLexedExportsOfFile, cjsVisibleNames } from "./cjs-syntax.js"; import { ambientDtsPath, fallbackDtsPath, + fallbackModulesNamedIn, + isFallbackDtsPath, isNodeTypesPath, overridesDtsPath, tsgoPath, @@ -613,6 +615,27 @@ function externalTypeFileClosure7( return new Map([...ownersByFile].map(([file, owners]) => [file, [...owners]] as const)); } +/** The fallback declaration files of the Node modules the program's files + * name (fallbackModulesNamedIn) that the program does not hold yet. Each + * file is read as tsgo reads it; the TypeScript libraries and scriptc's own + * declarations name no module that needs a file. */ +function fallbackModuleRoots7(program: ts.Program, host: ts.Ts7Host): string[] { + const names = program.getSourceFileNames(); + const held = new Set(names.filter(isFallbackDtsPath).map((name) => tsgoPath(name))); + const ambient = ambientDtsPath(); + const overrides = overridesDtsPath(); + const roots = new Set(); + for (const name of names) { + const path = tsgoPath(name); + if (held.has(path) || path === ambient || path === overrides) continue; + if (/\/lib\.[^/]*\.d\.ts$/.test(path) && program.getSourceFileMetadata(name)?.isDefaultLibrary) + continue; + for (const file of fallbackModulesNamedIn(host.sourceText(name) ?? "")) + if (!held.has(file)) roots.add(file); + } + return [...roots]; +} + /** Program roots hidden behind canonical createRequire bindings. tsgo * discovers ordinary import/require edges itself, but a source binding * returned by createRequire is intentionally just a function to the type @@ -889,12 +912,23 @@ function loadProgram7( } options = { ...options, paths }; } - const coreRoots = [entryPath, ambientDtsPath(), nodeTypes ?? fallbackDtsPath()]; + // Without @types/node the fallback declarations stand in: the core file, + // and a Node module's file once a program file names the module + // (fallbackModuleRoots7). The modules the entry names join the first + // program. + const coreRoots = [ + entryPath, + ambientDtsPath(), + ...(nodeTypes === null + ? [fallbackDtsPath(), ...fallbackModulesNamedIn(host.sourceText(entryPath) ?? "")] + : [nodeTypes]), + ]; const programRoots = [...coreRoots]; let program = host.createProgram([...programRoots, overridesDtsPath()], options); try { for (let pass = 0; pass < 32; pass++) { const candidates = [ + ...(nodeTypes === null ? fallbackModuleRoots7(program, host) : []), ...entryPackageProgramRoots7(program, entryPath, rootNpmStaticEdges), ...createRequireProgramRoots7(program), ...forkTargetPaths(program, program.getImplementationSourceFiles()), @@ -5014,7 +5048,7 @@ function laterJobCallee7(checker: ts.TypeChecker, callee: ts.Expression): boolea // package declarations describe user code. return decls.every((d) => { const file = ts.getSourceFileOfNode(d).fileName; - return isNodeTypesPath(file) || file === fallbackDtsPath(); + return isNodeTypesPath(file) || isFallbackDtsPath(file); }); } @@ -7097,7 +7131,13 @@ const nodeBuiltinNames: ReadonlySet = new Set(builtinModules); * established (the phase-2 port kept every spelling); the world-neutral * helpers re-export from their focused modules. */ -export { ambientDtsPath, fallbackDtsPath, isNodeTypesPath, overridesDtsPath } from "./dts-paths.js"; +export { + ambientDtsPath, + fallbackDtsPath, + isFallbackDtsPath, + isNodeTypesPath, + overridesDtsPath, +} from "./dts-paths.js"; export { builtinDefaultImportModule, diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 9e8f51bb3..0b2c3f6b7 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -47,7 +47,7 @@ import { } from "../input-tracker.js"; import { packageShimDeclarationText } from "../package-shims.js"; import { typePatchedText } from "../type-patches/layer.js"; -import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../dts-paths.js"; +import { ambientDtsPath, isFallbackDtsPath, overridesDtsPath } from "../dts-paths.js"; import { reusedLibDiagnostics } from "./lib-diagnostics.js"; import { ts7Diagnostic } from "./diagnostic.js"; import type { Ts7DiagnosticData } from "./session-schema.generated.js"; @@ -425,12 +425,13 @@ export class Ts7Program { reusedLibDiagnostics(): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { this.snapshot.ensureActive(); const program = this.project.program; - const declarations = new Set([ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]); + const declarations = new Set([ambientDtsPath(), overridesDtsPath()]); return reusedLibDiagnostics( program, this.getSourceFileNames(), (name) => declarations.has(tsgoPath(name)) || + isFallbackDtsPath(name) || program.getSourceFileMetadataByPath(name)?.isDefaultLibrary === true, (name) => this.getSourceFile(name), ); diff --git a/packages/compiler/src/frontend/worker-target.test.ts b/packages/compiler/src/frontend/worker-target.test.ts index 7125bc309..e68fc798a 100644 --- a/packages/compiler/src/frontend/worker-target.test.ts +++ b/packages/compiler/src/frontend/worker-target.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; import { workerTargetPaths } from "./worker-target.js"; -import { fallbackDtsPath } from "./dts-paths.js"; +import { fallbackDtsPath, fallbackModulePath } from "./dts-paths.js"; import { Ts7Host } from "./ts7/program-host.js"; import { Ts7Api } from "./ts7/rpc-api.js"; import { ModuleKind, ModuleResolutionKind } from "./ts7/enums.js"; @@ -17,12 +17,15 @@ function discover(source: string): string[] { 'export { Worker as Task } from "node:worker_threads";', ); const host = new Ts7Host((options) => new Ts7Api(options), { cwd: directory }); - const program = host.createProgram([entry, fallbackDtsPath()], { - noLib: true, - types: [], - module: ModuleKind.ESNext, - moduleResolution: ModuleResolutionKind.Bundler, - }); + const program = host.createProgram( + [entry, fallbackDtsPath(), fallbackModulePath("node:worker_threads")!], + { + noLib: true, + types: [], + module: ModuleKind.ESNext, + moduleResolution: ModuleResolutionKind.Bundler, + }, + ); try { return workerTargetPaths(program, [program.getSourceFile(entry)!]).map((file) => file.replace(directory + "/", ""), diff --git a/packages/compiler/test/node-fallback-modules.test.ts b/packages/compiler/test/node-fallback-modules.test.ts new file mode 100644 index 000000000..60a2e0bc9 --- /dev/null +++ b/packages/compiler/test/node-fallback-modules.test.ts @@ -0,0 +1,43 @@ +import { existsSync, readdirSync, readFileSync } from "node:fs"; +import { basename, dirname, join } from "node:path"; +import { expect, test } from "vitest"; +import { + fallbackDtsPath, + fallbackModulePath, + fallbackModulesNamedIn, +} from "../src/frontend/dts-paths.js"; + +const moduleDirectory = join(dirname(fallbackDtsPath()), "node-fallback"); +const declaredModules = (text: string): string[] => + [...text.matchAll(/^declare module "([^"]+)" \{/gm)].map((m) => m[1]!); + +test("every fallback module file declares the specifiers that map to it, and only those", () => { + const files = readdirSync(moduleDirectory).filter((name) => name.endsWith(".d.ts")); + expect(files.length).toBeGreaterThan(0); + for (const file of files) { + const text = readFileSync(join(moduleDirectory, file), "utf8"); + const declared = declaredModules(text); + expect(declared.length, file).toBeGreaterThan(0); + for (const specifier of declared) expect(basename(fallbackModulePath(specifier)!)).toBe(file); + // Every reference names another module file. + for (const [, reference] of text.matchAll(/^\/\/\/ $/gm)) + expect(existsSync(join(moduleDirectory, reference!)), `${file} -> ${reference}`).toBe(true); + } +}); + +test("the core fallback file declares no module that has a file of its own", () => { + for (const specifier of declaredModules(readFileSync(fallbackDtsPath(), "utf8"))) + expect(fallbackModulePath(specifier), specifier).toBeNull(); +}); + +test("a module named in a string literal selects its file, under either spelling", () => { + const files = fallbackModulesNamedIn( + `import { readFileSync } from "node:fs";\nconst os = require('os');\ntype T = import(\`node:stream/web\`);\nconst label = "not-a-module";`, + ).map((file) => basename(file)); + expect(files.sort()).toEqual(["fs.d.ts", "os.d.ts", "stream-web.d.ts"]); + expect(fallbackModulesNamedIn('console.log("hi")')).toEqual([]); + // Module names with digits. + expect( + fallbackModulesNamedIn(`require("http2"); import("node:v8");`).map((file) => basename(file)), + ).toEqual(["http2.d.ts", "v8.d.ts"]); +}); diff --git a/packages/compiler/test/node-pin.test.ts b/packages/compiler/test/node-pin.test.ts index 4d4c4a7e8..f13c5558e 100644 --- a/packages/compiler/test/node-pin.test.ts +++ b/packages/compiler/test/node-pin.test.ts @@ -33,7 +33,7 @@ test("the Node pins agree", () => { }); test("the crypto.constants declaration matches the baked table", () => { - const dts = read("packages/compiler/ambient/scriptc-node-fallback.d.ts"); + const dts = read("packages/compiler/ambient/node-fallback/crypto.d.ts"); const start = dts.indexOf("export interface CryptoConstants {"); const body = dts.slice(start, dts.indexOf("\n }\n", start)); const declared = Object.fromEntries( @@ -46,7 +46,7 @@ test("the crypto.constants declaration matches the baked table", () => { }); test("the zlib.constants declaration matches the baked table", () => { - const dts = read("packages/compiler/ambient/scriptc-node-fallback.d.ts"); + const dts = read("packages/compiler/ambient/node-fallback/zlib.d.ts"); const start = dts.indexOf("export interface ZlibConstants {"); const body = dts.slice(start, dts.indexOf("\n }\n", start)); const declared = Object.fromEntries( diff --git a/packages/compiler/test/ts7/bench.test.ts b/packages/compiler/test/ts7/bench.test.ts index 13e34bca1..c8de220c9 100644 --- a/packages/compiler/test/ts7/bench.test.ts +++ b/packages/compiler/test/ts7/bench.test.ts @@ -28,6 +28,8 @@ import type { } from "../../src/frontend/ts7/semantic-types.js"; import { CheckerFacade } from "../../src/frontend/ts7/checker.js"; import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../../src/frontend/program.js"; +import { fallbackModulesNamedIn } from "../../src/frontend/dts-paths.js"; +import { readFileSync } from "node:fs"; import { ad, options7 } from "./harness.js"; const repoRoot = fileURLToPath(new URL("../../../..", import.meta.url)); @@ -230,7 +232,13 @@ for (const workload of WORKLOADS) { { retry: 2 }, () => { const program = createProgram( - [workload.entry, ambientDtsPath(), fallbackDtsPath(), overridesDtsPath()], + [ + workload.entry, + ambientDtsPath(), + fallbackDtsPath(), + ...fallbackModulesNamedIn(readFileSync(workload.entry, "utf8")), + overridesDtsPath(), + ], options7(), host, ); diff --git a/packages/compiler/test/ts7/harness.ts b/packages/compiler/test/ts7/harness.ts index 484add55d..073de1421 100644 --- a/packages/compiler/test/ts7/harness.ts +++ b/packages/compiler/test/ts7/harness.ts @@ -18,6 +18,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import ts5 from "typescript5"; import { ambientDtsPath, fallbackDtsPath } from "../../src/frontend/program.js"; +import { fallbackModulesNamedIn } from "../../src/frontend/dts-paths.js"; import * as ad from "../../src/frontend/ts7/adapter.js"; export { ad, ts5 }; @@ -78,7 +79,8 @@ export function buildTwoWorlds(sources: Record, host?: ad.Ts7Hos writeFileSync(path, text); files.push(path); } - const roots = [...files, ambientDtsPath(), fallbackDtsPath()]; + const modules = Object.values(sources).flatMap((text) => fallbackModulesNamedIn(text)); + const roots = [...files, ambientDtsPath(), fallbackDtsPath(), ...new Set(modules)]; const p5 = ts5.createProgram(roots, options5()); const p7 = createProgram(roots, options7(), host); return { diff --git a/scripts/native-cli-assets.mts b/scripts/native-cli-assets.mts index bdecc1da2..58d16fb69 100644 --- a/scripts/native-cli-assets.mts +++ b/scripts/native-cli-assets.mts @@ -74,6 +74,11 @@ export function stageNativeCliAssets(options: { for (const name of ["scriptc.d.ts", "scriptc-overrides.d.ts", "scriptc-node-fallback.d.ts"]) { cpSync(join(repository, "packages/compiler/ambient", name), join(declarations, name)); } + cpSync( + join(repository, "packages/compiler/ambient/node-fallback"), + join(declarations, "node-fallback"), + { recursive: true }, + ); // Package shims (packageShimRoot): the built-in npm package implementations. cpSync(join(repository, "packages/compiler/shims"), join(declarations, "shims"), { recursive: true, From 11de08b93116e4758c4d66d114fd859d4ca2f606 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:46:27 -0500 Subject: [PATCH 116/230] Skip worker cancellation polls in short loops inside another loop In a worker program, a loop inside another loop of the same function needs less than its own budget, because the enclosing loop polls at least once every 64 of its iterations: - A loop whose counters' proven ranges bound its runs, and those of the loops in its body, to 64 x 64 emits no poll. This covers loops with constant bounds, such as mandelbrot's lane and iteration loops. The bound uses the counted and two-counter loop ranges, or the range of a counter that the update alone steps by a constant where the condition reads it. Loops whose counted form needs a run-time guard don't count. - An innermost counted loop is strip-mined like a counted loop over typed arrays, whatever its body. A run of at most 64 iterations never polls, and the budget's countdown leaves the body. A run of at most 16384 iterations is one block and polls once as it leaves. A longer run polls between blocks of 64, but not after its last block. Between two polls there are then at most 64 iterations of the enclosing loop, and in each of them at most 16384 unpolled iterations of each strip-mined inner loop and 64 x 64 runs of each bounded nest. A loop that no other loop of its function encloses polls as before. New corpus program: native-worker-terminate-bounded-loops.ts. --- .../src/backend/llvm/emit-statements.ts | 142 +++++++++++++++++- packages/compiler/src/backend/loop-polls.ts | 4 +- packages/compiler/src/ir/integer-loops.ts | 22 +++ .../test/counted-loop-emission.test.ts | 91 +++++++++++ ...ive-worker-terminate-bounded-loops.ts.json | 6 + .../native-worker-terminate-bounded-loops.ts | 91 +++++++++++ ...ive-worker-terminate-bounded-loops.ts.json | 1 + 7 files changed, 349 insertions(+), 8 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-bounded-loops.ts.json create mode 100644 tests/corpus/native-worker-terminate-bounded-loops.ts create mode 100644 tests/harness/corpus-costs/native-worker-terminate-bounded-loops.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index a43cf3534..e0db764e8 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -50,6 +50,7 @@ import { matchIntegerBytesForLoop, matchIntegerCountedForLoop, matchIntegerPairedForLoop, + steppedLoopCounter, } from "../../ir/integer-loops.js"; import { emitByteWindowGuard, matchByteWindow } from "./byte-windows.js"; import { @@ -1218,7 +1219,24 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { // has no exit besides its own bound, which LLVM can vectorize. A typed // array's length is fixed, so each block can take it as its limit when // the loop does not assign the array's binding. + // + // Inside another loop of this function, which polls at least once + // every WORKER_POLL_BLOCK of its own iterations, a loop needs less: + // - a loop whose counters' ranges bound its runs, and those of the + // loops in its body, to WORKER_POLL_BLOCK^2 needs no poll; + // - an innermost counted loop is strip-mined, typed arrays or not, + // and does not poll after its last block. A run of at most + // WORKER_POLL_BLOCK iterations then never polls, and the countdown + // leaves its body. A run of at most WORKER_UNPOLLED_RUN iterations + // is one block, polled once as it leaves. + const enclosed = + emitter.mod.workers === true && + emitter.jumpTargets.some((target) => target.kind === "loop"); + const bounded = + enclosed && boundedLoopRuns(emitter, s) <= WORKER_POLL_BLOCK * WORKER_POLL_BLOCK; + const innermost = enclosed && !containsLoop(s.body); const stripMined = + !bounded && capturedLets.length === 0 && integerSlot !== null && ((countedLoop !== null && @@ -1226,16 +1244,21 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { Math.abs(countedLoop.step) * WORKER_POLL_BLOCK <= 2 ** 52) || (integerLoop !== null && integerArrayLoop === null && integerLoop.stableReceiver)) && emitter.workerLoopPolls(s) && - loopAccessesTypedArrays(s); - const workerBudget = stripMined ? null : emitter.workerLoopBudget(s); + (loopAccessesTypedArrays(s) || (countedLoop !== null && innermost)); + const workerBudget = stripMined || bounded ? null : emitter.workerLoopBudget(s); // One slot for every copy of the loop: a speculative copy resumes an // iteration in another copy's body, which then reads this bound. let blockBound: string | null = null; + let runSlot: string | null = null; if (stripMined) { blockBound = B.slot(); B.entryAllocas.push( `${blockBound} = alloca ${countedLoop ? "i64" : emitter.sizeType} ; cancellation poll block bound`, ); + if (innermost) { + runSlot = B.slot(); + B.entryAllocas.push(`${runSlot} = alloca i1 ; cancellation poll after the run`); + } } freshenBindings(); emitter.emitLoopCopies(s, capturedLets.length === 0, le, (exit) => { @@ -1271,7 +1294,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { return len; }, }; - emitStripMinedCountedLoop(emitter, s, counter, blockBound!, exit); + emitStripMinedCountedLoop(emitter, s, counter, blockBound!, exit, runSlot); return; } const lc = B.newLabel("loop.c"); @@ -2628,6 +2651,51 @@ function emitInt32KindLoop( /** Iterations between cancellation polls of a strip-mined counted loop. */ const WORKER_POLL_BLOCK = 64; +/** The longest run of a strip-mined loop inside another loop that polls + * only as it leaves. */ +const WORKER_UNPOLLED_RUN = 16384; + +/** At most how many times a `for` loop and the loops in its body run their + * bodies per entry, from the counters' proven ranges: its iterations times + * one plus the nested loops' runs. Infinity when a count has no bound, or + * depends on a run-time guard (the guard's slow copy steps a double). */ +function boundedLoopRuns(emitter: LlEmitter, loop: IrStmt): number { + if (loop.kind !== "for") return Infinity; + const counted = matchIntegerCountedForLoop( + loop, + emitter.numericLocals, + emitter.integerRanges, + LOOPS.of(emitter).unaliasedArrays, + ); + let trips = Infinity; + if (counted && !counted.guarded) + trips = Math.floor((counted.range.max - counted.range.min) / Math.abs(counted.step)) + 1; + else if (!counted) { + // Each iteration closes the gap between the two counters by at least two. + const paired = matchIntegerPairedForLoop(loop, emitter.numericLocals, emitter.integerRanges); + if (paired && !paired.guarded) + trips = Math.floor((paired.range.max - paired.range.min) / 2) + 1; + } + if (trips === Infinity) { + // A counter with a proven range where the condition reads it, such as + // one compared with a module constant. + const stepped = steppedLoopCounter(loop, emitter.currentLocals); + const range = stepped && emitter.integerRanges.get(stepped.ref); + if (stepped && range) trips = Math.floor((range.max - range.min) / Math.abs(stepped.step)) + 1; + } + if (!(trips <= WORKER_POLL_BLOCK * WORKER_POLL_BLOCK)) return Infinity; + let nested = 0; + const visit = (s: IrStmt): boolean => { + if (s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf") { + nested += boundedLoopRuns(emitter, s); + return nested !== Infinity; + } + return everyStmtChild(s, () => true, visit); + }; + if (!loop.body.every(visit)) return Infinity; + return trips * (1 + nested); +} + /** The counter of a strip-mined loop: its slot, the LLVM integer type and * signedness of its values, its step, the counters stepped beside it, and * the limit, emitted in the outer header before each block. */ @@ -2645,13 +2713,20 @@ interface StripMinedCounter { * tests the counter against the loop's limit, each block runs the body up * to the nearer of the limit and WORKER_POLL_BLOCK steps on, and the poll * follows the block. It polls as often as the budget it replaces, and the - * inner loop's only exit besides `break` and `return` is its bound. */ + * inner loop's only exit besides `break` and `return` is its bound. + * + * With `runSlot` (for a loop that another loop of the function encloses), a + * block that ends at the limit leaves the loop without polling, and a run + * of at most WORKER_UNPOLLED_RUN iterations is one block, polled once as + * it leaves when it took more than WORKER_POLL_BLOCK iterations. `runSlot` + * holds whether the current block is such a run. */ function emitStripMinedCountedLoop( emitter: LlEmitter, s: Extract, counter: StripMinedCounter, boundSlot: string, exit: string, + runSlot: string | null, ): void { const B = emitter.B; const { slot, type, signed, step } = counter; @@ -2684,7 +2759,27 @@ function emitStripMinedCountedLoop( `${stepped} = call ${type} @${saturating}(${type} ${start}, ${type} ${step * WORKER_POLL_BLOCK})`, ); B.line(`${bound} = call ${type} @${nearer}(${type} ${stepped}, ${type} ${limit})`); - B.line(`store ${type} ${bound}, ptr ${boundSlot}`); + if (runSlot === null) B.line(`store ${type} ${bound}, ptr ${boundSlot}`); + else { + // The steps left to the limit: positive, as the header tested. + const left = B.tmp(), + whole = B.tmp(), + long = B.tmp(), + polled = B.tmp(), + end = B.tmp(); + const stride = Math.abs(step); + B.line( + ascending + ? `${left} = sub ${wrap} ${type} ${limit}, ${start}` + : `${left} = sub ${wrap} ${type} ${start}, ${limit}`, + ); + B.line(`${whole} = icmp ule ${type} ${left}, ${stride * WORKER_UNPOLLED_RUN}`); + B.line(`${long} = icmp ugt ${type} ${left}, ${stride * WORKER_POLL_BLOCK}`); + B.line(`${polled} = and i1 ${whole}, ${long}`); + B.line(`${end} = select i1 ${whole}, ${type} ${limit}, ${type} ${bound}`); + B.line(`store ${type} ${end}, ptr ${boundSlot}`); + B.line(`store i1 ${polled}, ptr ${runSlot}`); + } B.br(bc); B.startBlock(bc); const index = B.tmp(); @@ -2693,7 +2788,8 @@ function emitStripMinedCountedLoop( B.line(`${index} = load ${type}, ptr ${slot}`); B.line(`${end} = load ${type}, ptr ${boundSlot}`); B.line(`${inBlock} = icmp ${compare} ${type} ${index}, ${end}`); - B.condBr(inBlock, lb, poll); + const after = runSlot === null ? poll : B.newLabel("loop.end"); + B.condBr(inBlock, lb, after); B.startBlock(lb); emitter.jumpTargets.push({ kind: "loop", @@ -2716,11 +2812,45 @@ function emitStripMinedCountedLoop( B.line(`store ${type} ${next}, ptr ${stepSlot}`); } B.br(bc); + if (runSlot !== null) { + // A speculative copy may enter the body without passing the header, so + // the limit is read again here. + B.startBlock(after); + const at = B.tmp(); + const more = B.tmp(); + const run = B.tmp(); + const done = B.newLabel("loop.done"); + const last = B.newLabel("loop.poll.run"); + B.line(`${at} = load ${type}, ptr ${slot}`); + B.line(`${more} = icmp ${compare} ${type} ${at}, ${counter.limit()}`); + B.condBr(more, poll, done); + B.startBlock(done); + B.line(`${run} = load i1, ptr ${runSlot}`); + B.condBr(run, last, exit); + B.startBlock(last); + emitter.emitWorkerPoll(); + B.br(exit); + } B.startBlock(poll); emitter.emitWorkerPoll(); B.br(lc); } +/** Whether `body` holds a loop outside its closures. */ +function containsLoop(body: IrStmt[]): boolean { + const inStmt = (s: IrStmt): boolean => + s.kind === "while" || + s.kind === "doWhile" || + s.kind === "for" || + s.kind === "forOf" || + !everyStmtChild( + s, + () => true, + (child) => !inStmt(child), + ); + return body.some(inStmt); +} + function enterLoopHeader(emitter: LlEmitter, header: string): void { const dispatch = emitter.speculationHeader; emitter.speculationHeader = null; diff --git a/packages/compiler/src/backend/loop-polls.ts b/packages/compiler/src/backend/loop-polls.ts index c582a4974..866711fb7 100644 --- a/packages/compiler/src/backend/loop-polls.ts +++ b/packages/compiler/src/backend/loop-polls.ts @@ -110,8 +110,8 @@ export function loopTestsEveryIteration(loop: Loop, tested: (call: IrExpr) => bo /** True when the body of `loop`, outside its nested loops and closures, * reads or writes a typed-array element: the loops whose cancellation poll * the emitter moves between blocks of iterations, so that LLVM can - * vectorize them. Other loops keep the budget in their body, which costs - * less in a loop of a few iterations. */ + * vectorize them. Outside another loop, other loops keep the budget in + * their body, which costs less in a loop of a few iterations. */ export function loopAccessesTypedArrays(loop: Loop): boolean { const inExpr = (e: IrExpr): boolean => { if (e.kind === "bytesIntrinsic" && e.method === "get") return true; diff --git a/packages/compiler/src/ir/integer-loops.ts b/packages/compiler/src/ir/integer-loops.ts index ea866a134..427f6879c 100644 --- a/packages/compiler/src/ir/integer-loops.ts +++ b/packages/compiler/src/ir/integer-loops.ts @@ -618,6 +618,28 @@ function writesLocal(body: IrStmt[], localId: string): boolean { }); } +/** The counter of a `for` loop that its condition reads and its update + * steps by a constant, when nothing else in the loop writes it: each test + * of the condition then sees a different value. The local must not be + * boxed (a closure could write it). */ +export function steppedLoopCounter( + stmt: IrStmt & { kind: "for" }, + locals: ReadonlyMap, +): { ref: IrExpr & { kind: "varRef" }; step: number } | null { + const cond = stmt.cond; + if (cond?.kind !== "bin") return null; + for (const side of [cond.left, cond.right]) { + if (side.kind !== "varRef") continue; + const local = locals.get(side.localId); + if (local === undefined || local.boxed) continue; + const step = constantStep(stmt.update, side.localId); + if (step === null || writesLocal(stmt.body, side.localId)) continue; + if (writesLocal([{ kind: "exprStmt", expr: cond, loc: cond.loc }], side.localId)) continue; + return { ref: side, step }; + } + return null; +} + function isUnitIncrement(update: IrStmt | null, localId: string): boolean { // The frontend normally lowers `i++` to `i = i + 1` before backend // emission. Accept the incDec form too so this analysis remains valid for diff --git a/packages/compiler/test/counted-loop-emission.test.ts b/packages/compiler/test/counted-loop-emission.test.ts index f617c6ab4..794edd1af 100644 --- a/packages/compiler/test/counted-loop-emission.test.ts +++ b/packages/compiler/test/counted-loop-emission.test.ts @@ -136,3 +136,94 @@ test("worker programs poll counted loops over typed arrays between blocks of ite await rm(dir, { recursive: true, force: true }); } }); + +test("worker programs poll no loop whose proven ranges bound its runs inside another loop", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-bounded-loop-polls-")); + try { + const entry = join(dir, "bounded.ts"); + await writeFile( + entry, + [ + 'import { isMainThread, Worker } from "node:worker_threads";', + "const LIMIT = 50;", + "export function rows(cr: Float64Array, n: number): number {", + " let t = 0;", + " for (let r = 0; r < n; r++)", + " for (let i = 0; i < 8; i += 2) {", + " const c = cr[i]!;", + " for (let j = 0; j < LIMIT; j++) {", + " t = (t * 31 + j + c) % 1000003;", + " if (t === 7) break;", + " }", + " }", + " return t;", + "}", + "export function open(cr: Float64Array, n: number, k: number): number {", + " let t = 0;", + " for (let r = 0; r < n; r++)", + " for (let i = 0; i < 8; i += 2) {", + " const c = cr[i]!;", + " for (let j = 0; j < k; j++) {", + " t = (t * 31 + j + c) % 1000003;", + " if (t === 7) break;", + " }", + " }", + " return t;", + "}", + "export function wide(n: number): number {", + " let t = 0;", + " for (let r = 0; r < n; r++) for (let j = 0; j < 5000; j++) t = (t * 31 + j) % 1000003;", + " return t;", + "}", + "export function single(): number {", + " let t = 0;", + " for (let j = 0; j < 50; j++) t = (t * 31 + j) % 1000003;", + " return t;", + "}", + "const cr = new Float64Array(8).fill(0.5);", + 'const k = Number(process.argv[2] ?? "50");', + "console.log(rows(cr, 3), open(cr, 3, k), wide(2), single());", + "if (!isMainThread) new Worker(new URL(import.meta.url));", + ].join("\n"), + ); + const outPath = join(dir, "bounded.ir.json"); + const result = await compile(entry, { outPath, outDir: dir, outputKind: "ir" }); + if (!result.ok) throw new Error(result.diagnostics.map((d) => d.message).join("\n")); + const ll = emitLlvmModule(deserializeModule(await readFile(outPath, "utf8"))); + const body = (name: string): string => + [ + ...ll.matchAll( + new RegExp( + `define internal [^\\n]+ @sc_b?f_${name}(?:\\.i32)?\\([^\\n]*\\n([\\s\\S]*?)\\n\\}`, + "g", + ), + ), + ] + .map((match) => match[1]!) + .join("\n"); + const polls = (text: string): number => text.match(/@scr_exc_flag/g)?.length ?? 0; + // The i and j loops of `rows` run at most 4 × (1 + 50) bodies: only the + // r loop polls, and the i loop over the typed array is not strip-mined. + const rows = body("rows"); + expect(rows).not.toMatch(/^loop\.poll\d+:$/m); + expect(polls(rows)).toBeGreaterThan(0); + // With a limit known only at run time the inner loops keep their polls. + // The innermost one is strip-mined, and a block that ends at the limit + // leaves it without polling: only a block that ends short of it polls. + const open = body("open"); + expect(polls(open)).toBeGreaterThan(polls(rows)); + expect(open).toMatch(/^loop\.poll\d+:$/m); + const ends = [...open.matchAll(/^loop\.end\d+:$[\s\S]*?^\S/gm)].map((match) => match[0]); + expect(ends.length).toBeGreaterThan(0); + for (const end of ends) expect(end).toMatch(/br i1 %\w+, label %loop\.poll\d+, label %/); + // A run of up to 16384 iterations is one block, polled as it leaves when + // it took more than 64. + expect(open).toMatch(/^loop\.poll\.run\d+:$/m); + // A nest over the bound, or a loop no other loop encloses, polls. + expect(polls(body("wide"))).toBeGreaterThan(0); + expect(body("wide")).toMatch(/^loop\.poll\d+:$/m); + expect(polls(body("single"))).toBeGreaterThan(0); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-bounded-loops.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-bounded-loops.ts.json new file mode 100644 index 000000000..2e52a519c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/native-worker-terminate-bounded-loops.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/native-worker-terminate-bounded-loops.ts" + ], + "diags": [] +} diff --git a/tests/corpus/native-worker-terminate-bounded-loops.ts b/tests/corpus/native-worker-terminate-bounded-loops.ts new file mode 100644 index 000000000..d244e021d --- /dev/null +++ b/tests/corpus/native-worker-terminate-bounded-loops.ts @@ -0,0 +1,91 @@ +// worker.terminate() stops a worker whose loop runs short inner loops: +// counted loops of a few iterations, a two-counter loop, an inner loop +// whose length grows past 64 iterations, an inner loop too long to finish, +// and a nest of loops with constant bounds. +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +function counted(values: number[]): number { + let acc = 0; + for (let round = 0; ; round++) { + for (let i = 0; i < values.length; i++) acc = (acc + values[i]! * i) % 1000003; + for (let i = values.length - 1; i >= 0; i -= 2) acc = (acc * 7 + values[i]!) % 1000003; + if (round === 200) parentPort!.postMessage("ready"); + } + return acc; +} + +function paired(values: number[]): number { + let flips = 0; + for (let round = 0; ; round++) { + for (let i = 0, j = values.length - 1; i < j; i++, j--) { + const t = values[i]!; + values[i] = values[j]!; + values[j] = t; + flips++; + } + if (round === 200) parentPort!.postMessage("ready"); + } + return flips; +} + +function growing(): number { + let acc = 0; + for (let n = 1; ; n++) { + const limit = n % 200; + for (let i = 0; i < limit; i++) acc = (acc + i) % 1000003; + if (n === 200) parentPort!.postMessage("ready"); + } + return acc; +} + +function long(limit: number): number { + let acc = 0; + for (let round = 0; ; round++) { + for (let i = 0; i < limit; i++) { + acc = (acc * 7 + i) % 1000003; + if (i === 100000) parentPort!.postMessage("ready"); + } + } + return acc; +} + +const ROUNDS = 50; + +function constant(row: Float64Array): number { + let acc = 0; + for (let y = 0; ; y++) { + for (let i = 0; i < 8; i += 2) { + const c = row[i]!; + for (let j = 0; j < ROUNDS; j++) { + acc = (acc * 3 + j + c) % 1000003; + if (acc === -1) break; + } + } + if (y === 200) parentPort!.postMessage("ready"); + } + return acc; +} + +if (isMainThread) { + for (const kind of ["counted", "paired", "growing", "long", "constant"]) { + await new Promise((resolve) => { + const worker = new Worker(new URL(import.meta.url), { workerData: kind }); + worker.once("message", () => { + void worker.terminate().then((code: number) => console.log(kind, "terminate", code)); + }); + worker.once("exit", (code: number) => { + console.log(kind, "exit", code); + resolve(); + }); + }); + } +} else { + const values = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8]; + let result = 0; + if (workerData === "counted") result = counted(values); + else if (workerData === "paired") result = paired(values); + else if (workerData === "growing") result = growing(); + else if (workerData === "long") result = long(1e15); + else result = constant(new Float64Array(8).fill(0.25)); + console.log("unexpected result", result); +} diff --git a/tests/harness/corpus-costs/native-worker-terminate-bounded-loops.ts.json b/tests/harness/corpus-costs/native-worker-terminate-bounded-loops.ts.json new file mode 100644 index 000000000..bb744e4ed --- /dev/null +++ b/tests/harness/corpus-costs/native-worker-terminate-bounded-loops.ts.json @@ -0,0 +1 @@ +{ "plain": 4034, "san": 5956 } From 7be83d8e114c353faa92ffef8352f2e780fb0e31 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:47:57 -0500 Subject: [PATCH 117/230] Fix the merge and address review feedback --- .gitignore | 2 ++ packages/compiler/node_modules | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 120000 packages/compiler/node_modules diff --git a/.gitignore b/.gitignore index 81b92ddd5..88dfdc35b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ +# A symlinked node_modules is a file to git, which node_modules/ does not match. +/packages/*/node_modules .pnpm-store/ # test-fixture packages are TEST DATA, committed on purpose !tests/diagnostics/npm-imports/node_modules/ diff --git a/packages/compiler/node_modules b/packages/compiler/node_modules deleted file mode 120000 index 6b01b0eda..000000000 --- a/packages/compiler/node_modules +++ /dev/null @@ -1 +0,0 @@ -../../../shape-reg/packages/compiler/node_modules \ No newline at end of file From 829f35bf851bd691078b1fa3ed8226e56c7543a0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:55:57 -0500 Subject: [PATCH 118/230] Pass sanitizeAttributes where the serialized-IR test emits directly The test emits each corpus program from its serialized IR with its own emitter options and compares the text with compile()'s. compile() now marks functions sanitize_address only in sanitized builds, so the test passes the same setting as objectAudit. --- .../compiler/src/backend/llvm/serialized-ir-emission.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts b/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts index 6d2457c57..1d35caa05 100644 --- a/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts +++ b/packages/compiler/src/backend/llvm/serialized-ir-emission.test.ts @@ -44,6 +44,7 @@ test("sort-heavy programs emit equal LLVM from memory and from serialized IR", a // compile() emits Linux textual LLVM with the unwinding ABI. unwindAbi: process.platform === "linux", objectAudit: sanitize, + sanitizeAttributes: sanitize, ...inlineRcOption(options.optimization, sanitize), }); const text = typeof emitted === "string" ? emitted : emitted.join(""); From e5778cafbb9d48f457f861f43394d52af93843d4 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:57:51 -0500 Subject: [PATCH 119/230] Remove the committed node_modules symlink --- .gitignore | 2 ++ packages/compiler/node_modules | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 120000 packages/compiler/node_modules diff --git a/.gitignore b/.gitignore index 81b92ddd5..88dfdc35b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ +# A symlinked node_modules is a file to git, which node_modules/ does not match. +/packages/*/node_modules .pnpm-store/ # test-fixture packages are TEST DATA, committed on purpose !tests/diagnostics/npm-imports/node_modules/ diff --git a/packages/compiler/node_modules b/packages/compiler/node_modules deleted file mode 120000 index 6b01b0eda..000000000 --- a/packages/compiler/node_modules +++ /dev/null @@ -1 +0,0 @@ -../../../shape-reg/packages/compiler/node_modules \ No newline at end of file From d1f0113e49ce58cb2ea00c38b6521041c4d2a3bd Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:58:14 -0500 Subject: [PATCH 120/230] Check only the files whose diagnostics the tsc gate keeps The tsc gate (preflight's errorsOf, and the original program of a JSX or declaration-projection build) asked for every file's semantic diagnostics, then dropped all of those in an opted-in package's files, in unselected package JavaScript and in package shims. With --npm-static that is nearly the whole check: on three.js's nodes fixture the 82 package files took 1.17 s and reported 13,022 diagnostics, the program's own files 0.1 s. getPreEmitDiagnostics takes the files whose diagnostics its caller keeps. When some file is left out, the server checks only the kept files (getDiagnosticsOfCheckedFiles), and their semantic diagnostics come in the full check's order. When a stored answer for the supplied declaration files applies to the program, it stands in for checking those files, as in the full check; the storage rule is unchanged. A global diagnostic can come from checking any file, so when the partial check reports one, the full check answers as before. At load time, before workspace packages are known, the gate leaves out only package and shim files. The kept diagnostics are unchanged. --- packages/compiler/src/frontend/program.ts | 21 +++++- .../src/frontend/ts7/lib-diagnostics.test.ts | 40 ++++++++++ .../src/frontend/ts7/lib-diagnostics.ts | 37 +++++++++- .../src/frontend/ts7/program-host.test.ts | 55 +++++++++++++- .../compiler/src/frontend/ts7/program-host.ts | 74 ++++++++++++++++--- 5 files changed, 208 insertions(+), 19 deletions(-) diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index b93e3f217..284b2ce06 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -665,6 +665,14 @@ function createRequireProgramRoots7(program: ts.Program): string[] { return [...roots].sort(); } +/** Whether the tsc gate can keep a file's diagnostics when the program + * loads: it drops all of an opted-in package's files and of the package + * shims (keptErrors in preflight also drops unselected package JavaScript, + * which is not known yet here, so those files are still checked). */ +function tscGateFileKept(fileName: string): boolean { + return npmStaticPackageOfPath(fileName) === null && !isPackageShimFile(fileName); +} + /** Classify the explicitly selected installed package's own sources. */ export function entryPackageFilePredicate(entryPath: string): (file: string) => boolean { const entryFile = tsgoPath(entryPath); @@ -934,7 +942,7 @@ function loadProgram7( const original = program; // The project world must read the original text too, so it is built // before the rewritten files are served, and only when it is needed. - const diagnostics = ts.getPreEmitDiagnostics(original); + const diagnostics = ts.getPreEmitDiagnostics(original, tscGateFileKept); const errors = diagnostics.some((d) => d.category === ts.DiagnosticCategory.Error); tscWorld = { program: original, @@ -982,7 +990,7 @@ function loadProgram7( } else { tscWorld = { program: original, - diagnostics: ts.getPreEmitDiagnostics(original), + diagnostics: ts.getPreEmitDiagnostics(original, tscGateFileKept), projectWorld: null, declWorldFallback: true, }; @@ -5438,7 +5446,14 @@ function preflight7(load: LoadResult): { const sf = p.getSourceFile(d.fileName); return sf !== undefined && isCheckedJsSourceFile(sf, projectCheckJs); }; - const errorsOf = (p: ts.Program): ts.Diagnostic[] => keptErrors(p, ts.getPreEmitDiagnostics(p)); + // keptErrors drops every diagnostic of these files: the gate does not ask + // the checker for them. + const diagnosticsKept = (fileName: string): boolean => + npmStaticPackageOfPath(fileName) === null && + !unselectedPackageJsFile(fileName) && + !isPackageShimFile(fileName); + const errorsOf = (p: ts.Program): ts.Diagnostic[] => + keptErrors(p, ts.getPreEmitDiagnostics(p, diagnosticsKept)); const keptErrors = (p: ts.Program, all: readonly ts.Diagnostic[]): ts.Diagnostic[] => { // First pass: every comment-side 2300's (file, name) — the partners // the second pass forgives. diff --git a/packages/compiler/src/frontend/ts7/lib-diagnostics.test.ts b/packages/compiler/src/frontend/ts7/lib-diagnostics.test.ts index e87db5f1a..83abaeddc 100644 --- a/packages/compiler/src/frontend/ts7/lib-diagnostics.test.ts +++ b/packages/compiler/src/frontend/ts7/lib-diagnostics.test.ts @@ -4,6 +4,7 @@ import { join } from "node:path"; import { afterAll, afterEach, expect, test } from "vitest"; import type { SourceFile } from "./ast-types.js"; import { + reusedKeptLibDiagnostics, reusedLibDiagnostics, setLibDiagnosticsStore, type LibDiagnosticsStore, @@ -118,6 +119,45 @@ test("a stored answer stands in for checking the supplied files, merged by file expect(full?.semantic).toEqual(reused?.semantic); }); +test("with kept files, a stored answer needs checking only the kept other files", () => { + const store = memoryStore(); + setLibDiagnosticsStore(store, "checker"); + const pkg = join(dir, "node_modules", "pkg", "index.d.ts"); + const semantic = new Map([ + [lib, [diagnostic(lib, 1)]], + [fallback, []], + [main, [diagnostic(main, 5)]], + [pkg, [diagnostic(pkg, 3)]], + ]); + const withPackage = files({ + [main]: "export const n: number = 'x';\n", + [pkg]: "export declare const p: number;\n", + }); + const all = [lib, fallback, main, pkg]; + const keep = new Set([lib, fallback, main]); + // Without a stored answer nothing is checked or stored. + const cold = sessionProgram({ semantic }); + expect(reusedKeptLibDiagnostics(cold, all, supplied, withPackage, keep)).toBeNull(); + expect(cold.asked).toEqual([]); + expect(store.entries.size).toBe(0); + reusedLibDiagnostics(sessionProgram({ semantic }), all, supplied, withPackage); + expect(store.entries.size).toBe(1); + const warm = sessionProgram({ semantic }); + expect(reusedKeptLibDiagnostics(warm, all, supplied, withPackage, keep)).toEqual({ + semantic: [diagnostic(lib, 1), diagnostic(main, 5)], + global: [], + }); + expect(warm.asked).toEqual([`checked ${main}`]); + // A left-out file that can declare something global still prevents reuse. + const augmenting = files({ + [main]: "export const n: number = 'x';\n", + [pkg]: "export {};\ndeclare global {\n var NaN: number;\n}\n", + }); + const blocked = sessionProgram({ semantic }); + expect(reusedKeptLibDiagnostics(blocked, all, supplied, augmenting, keep)).toBeNull(); + expect(blocked.asked).toEqual([]); +}); + test("files merge in code point order, which differs from UTF-16 order", () => { const store = memoryStore(); setLibDiagnosticsStore(store, "checker"); diff --git a/packages/compiler/src/frontend/ts7/lib-diagnostics.ts b/packages/compiler/src/frontend/ts7/lib-diagnostics.ts index 30c6e81f0..c3e563fef 100644 --- a/packages/compiler/src/frontend/ts7/lib-diagnostics.ts +++ b/packages/compiler/src/frontend/ts7/lib-diagnostics.ts @@ -64,6 +64,31 @@ export function reusedLibDiagnostics( fileNames: readonly string[], supplied: (fileName: string) => boolean, sourceFile: (fileName: string) => SourceFile | undefined, +): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { + return reuse(program, fileNames, supplied, sourceFile, () => true, false); +} + +/** reusedLibDiagnostics for a caller that keeps only the diagnostics of the + * `kept` files: a stored answer then needs checking only the kept other + * files, and without a stored answer the result is null (nothing is checked + * or stored). */ +export function reusedKeptLibDiagnostics( + program: Ts7SessionProgram, + fileNames: readonly string[], + supplied: (fileName: string) => boolean, + sourceFile: (fileName: string) => SourceFile | undefined, + kept: ReadonlySet, +): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { + return reuse(program, fileNames, supplied, sourceFile, (name) => kept.has(name), true); +} + +function reuse( + program: Ts7SessionProgram, + fileNames: readonly string[], + supplied: (fileName: string) => boolean, + sourceFile: (fileName: string) => SourceFile | undefined, + keep: (fileName: string) => boolean, + storedOnly: boolean, ): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { if (configured === null || process.env["SCRIPTC_LIB_DIAGNOSTICS"] === "0") return null; const { store, checker } = configured; @@ -104,12 +129,16 @@ export function reusedLibDiagnostics( if (globals !== null) writeEntry(store, key, globals, Object.fromEntries(stored.files)); } if (stored !== null && (scripts.length === 0 || (globals !== null && !collides(globals)))) { - const checked = program.checkedFileDiagnosticData(own); + const checkedNames = storedOnly ? own.filter(keep) : own; + const checked = program.checkedFileDiagnosticData(checkedNames); if (checked === null) return null; - const byFile = new Map(stored.files); - own.forEach((name, index) => byFile.set(name, checked.files[index] ?? [])); + const byFile = new Map( + storedOnly ? [...stored.files].filter(([name]) => keep(name)) : stored.files, + ); + checkedNames.forEach((name, index) => byFile.set(name, checked.files[index] ?? [])); return { semantic: inFileOrder(byFile), global: checked.global }; } + if (storedOnly) return null; // The full check, as before. Its answers for the supplied files are stored // when it reports no global diagnostics and the program's scripts declare // none of the supplied files' globals (so nothing of the program reached @@ -339,7 +368,7 @@ function readEntry( /** Each file's diagnostics (already in the checker's order) in the order the * checker sorts files: by file name, compared by code point. */ -function inFileOrder(byFile: ReadonlyMap): Ts7DiagnosticData[] { +export function inFileOrder(byFile: ReadonlyMap): Ts7DiagnosticData[] { const names = [...byFile.keys()].sort(compareCodePoints); const out: Ts7DiagnosticData[] = []; for (const name of names) for (const d of byFile.get(name)!) out.push(d); diff --git a/packages/compiler/src/frontend/ts7/program-host.test.ts b/packages/compiler/src/frontend/ts7/program-host.test.ts index 80c4d2bcf..2ab6a0293 100644 --- a/packages/compiler/src/frontend/ts7/program-host.test.ts +++ b/packages/compiler/src/frontend/ts7/program-host.test.ts @@ -3,7 +3,12 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, expect, test, vi } from "vitest"; import { Ts7Api } from "./rpc-api.js"; -import { Ts7Host, type Ts7ApiOptions, type Ts7CompilerOptions } from "./program-host.js"; +import { + getPreEmitDiagnostics, + Ts7Host, + type Ts7ApiOptions, + type Ts7CompilerOptions, +} from "./program-host.js"; import { ModuleDetectionKind, ModuleKind, ModuleResolutionKind, ScriptTarget } from "./enums.js"; import { tsgoPath } from "./session-path.js"; @@ -70,6 +75,54 @@ test("implementation discovery leaves declaration trees lazy without skipping th expect(() => program.getSourceFileNames()).toThrow("disposed"); }); +test("pre-emit diagnostics for kept files check only those, unless a global diagnostic appears", () => { + const { directory, entry } = fixture(); + const other = join(directory, "other.ts"); + const later = join(directory, "later.ts"); + writeFileSync( + entry, + 'import "./other.js";\nimport "./later.js";\nexport const n: number = "x";\n', + ); + writeFileSync(other, "export const s: string = 1;\n"); + writeFileSync(later, "export const b: boolean = 0;\n"); + const { host } = makeHost(directory); + const program = host.createProgram([entry], options); + try { + const full = getPreEmitDiagnostics(program); + const keep = (name: string): boolean => name !== tsgoPath(other); + const checked = vi.spyOn(program.project.program, "checkedFileDiagnosticData"); + const kept = getPreEmitDiagnostics(program, keep); + // The full answer without the left-out file's diagnostics, in the same order. + expect(full.some((diagnostic) => diagnostic.fileName === tsgoPath(other))).toBe(true); + expect(kept.map((diagnostic) => [diagnostic.fileName, diagnostic.code])).toEqual( + full + .filter((diagnostic) => diagnostic.fileName === undefined || keep(diagnostic.fileName)) + .map((diagnostic) => [diagnostic.fileName, diagnostic.code]), + ); + expect(kept).toHaveLength(2); + expect(checked).toHaveBeenCalledTimes(1); + expect(checked.mock.calls[0]![0]).not.toContain(tsgoPath(other)); + // Keeping every file is the full check. + expect(getPreEmitDiagnostics(program, () => true)).toEqual(full); + expect(checked).toHaveBeenCalledTimes(1); + // A global diagnostic from the partial check, or a server without it: the full check. + for (const answer of [ + { + files: [], + global: [{ fileName: undefined, pos: 0, end: 0, code: 2318, category: 1, text: "global" }], + }, + null, + ]) { + checked.mockReturnValueOnce( + answer as ReturnType, + ); + expect(getPreEmitDiagnostics(program, keep)).toEqual(full); + } + } finally { + program.dispose(); + } +}); + test("the factory receives one connection with the host's filesystem and timing", () => { const { directory, entry } = fixture(); const factory = vi.fn((connection: Ts7ApiOptions) => new Ts7Api(connection)); diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 9e8f51bb3..bd6921afc 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -48,7 +48,7 @@ import { import { packageShimDeclarationText } from "../package-shims.js"; import { typePatchedText } from "../type-patches/layer.js"; import { ambientDtsPath, fallbackDtsPath, overridesDtsPath } from "../dts-paths.js"; -import { reusedLibDiagnostics } from "./lib-diagnostics.js"; +import { inFileOrder, reusedKeptLibDiagnostics, reusedLibDiagnostics } from "./lib-diagnostics.js"; import { ts7Diagnostic } from "./diagnostic.js"; import type { Ts7DiagnosticData } from "./session-schema.generated.js"; @@ -423,17 +423,31 @@ export class Ts7Program { /** The semantic and global diagnostics with the stored diagnostics of the * declaration files scriptc supplies (lib-diagnostics.ts), or null. */ reusedLibDiagnostics(): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { + return this.libDiagnostics(null); + } + + /** reusedLibDiagnostics for the `kept` files only: null without a stored + * answer. */ + reusedKeptLibDiagnostics( + kept: ReadonlySet, + ): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { + return this.libDiagnostics(kept); + } + + private libDiagnostics( + kept: ReadonlySet | null, + ): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { this.snapshot.ensureActive(); const program = this.project.program; const declarations = new Set([ambientDtsPath(), overridesDtsPath(), fallbackDtsPath()]); - return reusedLibDiagnostics( - program, - this.getSourceFileNames(), - (name) => - declarations.has(tsgoPath(name)) || - program.getSourceFileMetadataByPath(name)?.isDefaultLibrary === true, - (name) => this.getSourceFile(name), - ); + const names = this.getSourceFileNames(); + const supplied = (name: string): boolean => + declarations.has(tsgoPath(name)) || + program.getSourceFileMetadataByPath(name)?.isDefaultLibrary === true; + const sourceFile = (name: string): SourceFile | undefined => this.getSourceFile(name); + return kept === null + ? reusedLibDiagnostics(program, names, supplied, sourceFile) + : reusedKeptLibDiagnostics(program, names, supplied, sourceFile, kept); } isSourceFileDefaultLibrary(file: SourceFile): boolean { @@ -464,10 +478,35 @@ export class Ts7Program { /** 5.9.3's getPreEmitDiagnostics, composed from 7.0.2's split surface (the * package dropped the aggregate). Order mirrors 5.9.3: config, options/ - * program, global, syntactic, semantic. */ -export function getPreEmitDiagnostics(program: Ts7Program): readonly Diagnostic[] { + * program, global, syntactic, semantic. + * + * With `keep`, a caller that discards every diagnostic of the other files + * asks for semantic diagnostics of the kept files only: the server checks + * only those (getDiagnosticsOfCheckedFiles), or only those not answered by + * stored diagnostics of the supplied declaration files, and their + * diagnostics come in the full check's order. A global diagnostic can come + * from checking any file, so when that partial check reports one, the full + * check answers. */ +export function getPreEmitDiagnostics( + program: Ts7Program, + keep?: (fileName: string) => boolean, +): readonly Diagnostic[] { const config = program.getConfigFileParsingDiagnostics(); const options = program.getProgramDiagnostics(); + if (keep !== undefined) { + const kept = program.getSourceFileNames().filter((name) => keep(name)); + if (kept.length < program.getSourceFileNames().length) { + const partial = + program.reusedKeptLibDiagnostics(new Set(kept)) ?? keptFileDiagnostics(program, kept); + if (partial !== null && partial.global.length === 0) + return [ + ...config, + ...options, + ...program.getSyntacticDiagnostics(), + ...partial.semantic.map(ts7Diagnostic), + ]; + } + } const reused = program.reusedLibDiagnostics(); if (reused !== null) return [ @@ -486,6 +525,19 @@ export function getPreEmitDiagnostics(program: Ts7Program): readonly Diagnostic[ ]; } +/** The semantic diagnostics of the kept files, in the full check's order, + * and the global diagnostics of checking them; null when the server cannot + * check only some files. */ +function keptFileDiagnostics( + program: Ts7Program, + kept: readonly string[], +): { semantic: Ts7DiagnosticData[]; global: Ts7DiagnosticData[] } | null { + const checked = program.project.program.checkedFileDiagnosticData(kept); + if (checked === null) return null; + const byFile = new Map(kept.map((name, index) => [name, checked.files[index] ?? []])); + return { semantic: inFileOrder(byFile), global: checked.global }; +} + /** ts.findConfigFile: nearest configName walking up from searchPath. */ export function findConfigFile( searchPath: string, From ac003fdf07d80408e0182535239e9d26cd12c974 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:01:45 -0500 Subject: [PATCH 121/230] Remove the committed node_modules symlink --- .gitignore | 2 ++ packages/compiler/node_modules | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 120000 packages/compiler/node_modules diff --git a/.gitignore b/.gitignore index 81b92ddd5..88dfdc35b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ +# A symlinked node_modules is a file to git, which node_modules/ does not match. +/packages/*/node_modules .pnpm-store/ # test-fixture packages are TEST DATA, committed on purpose !tests/diagnostics/npm-imports/node_modules/ diff --git a/packages/compiler/node_modules b/packages/compiler/node_modules deleted file mode 120000 index 6b01b0eda..000000000 --- a/packages/compiler/node_modules +++ /dev/null @@ -1 +0,0 @@ -../../../shape-reg/packages/compiler/node_modules \ No newline at end of file From 4217aa12bf806794cb9eba0ac1d36fd9c59f06ce Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:03:13 -0500 Subject: [PATCH 122/230] Remove the committed node_modules symlink --- .gitignore | 2 ++ packages/compiler/node_modules | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 120000 packages/compiler/node_modules diff --git a/.gitignore b/.gitignore index 81b92ddd5..88dfdc35b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ +# A symlinked node_modules is a file to git, which node_modules/ does not match. +/packages/*/node_modules .pnpm-store/ # test-fixture packages are TEST DATA, committed on purpose !tests/diagnostics/npm-imports/node_modules/ diff --git a/packages/compiler/node_modules b/packages/compiler/node_modules deleted file mode 120000 index 6b01b0eda..000000000 --- a/packages/compiler/node_modules +++ /dev/null @@ -1 +0,0 @@ -../../../shape-reg/packages/compiler/node_modules \ No newline at end of file From a96fbcfb641ec841f1ff5e98f5b8cd855782734e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:14:48 -0500 Subject: [PATCH 123/230] Refuse static member reads of MIMEType and MIMEParams in JavaScript JavaScript sources hold node:util's MIMEType and MIMEParams as identity tokens, so a read such as MIMEType.prototype or MIMEType.name answered undefined where Node returns the class's member. Those reads now report SC2020, as the TypeScript spelling does at compile time; in JavaScript the refusal throws when the read runs. The node24-mime-errors fixture reads MIMEType.prototype to take the getters' descriptors, so its expected refusal is now SC2020. --- .../compiler/src/frontend/lowering/builtins/mime.ts | 11 +++++++++++ .../frontend/lowering/expressions/property-access.ts | 2 ++ tests/harness/npm.test.ts | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/builtins/mime.ts b/packages/compiler/src/frontend/lowering/builtins/mime.ts index 27f95226c..4c36d6cc5 100644 --- a/packages/compiler/src/frontend/lowering/builtins/mime.ts +++ b/packages/compiler/src/frontend/lowering/builtins/mime.ts @@ -134,6 +134,17 @@ export function lowerMimeCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr }; } +/** Reports SC2020 when `access` reads a static member of MIMEType or + * MIMEParams (`MIMEType.prototype`, `MIMEType.name`); null otherwise. + * JavaScript sources hold either class as an identity token, which has no + * members, so the read would answer undefined. MIMEType.parse calls lower + * in lowerMimeCall and never reach the member read. */ +export function fenceMimeStaticRead(lowerer: Lowerer, access: ts.PropertyAccessExpression): null { + const cls = mimeClassOf(lowerer, access.expression); + if (cls !== null) lowerer.noLowering(`util.${cls}.${access.name.text}`, access); + return null; +} + /** `v instanceof MIMEType` / `v instanceof MIMEParams`. */ export function lowerMimeInstanceof(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr | null { const cls = mimeClassOf(lowerer, expr.right); diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index df62da2c8..cfd7ae297 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -63,6 +63,7 @@ import { } from "../../../ir/ir.js"; import { lowerNavigatorMember } from "../builtins/navigator.js"; import { lowerX509PublicKey } from "../builtins/x509.js"; +import { fenceMimeStaticRead } from "../builtins/mime.js"; import { NODE_COMPAT_VERSION } from "../builtins/process.js"; import { inlineNamespaceRequireOf, @@ -942,6 +943,7 @@ export function lowerPropertyAccessExpr( // prototype that no lowering above claimed keeps the SC2020 fence // rather than reading undefined through the field and dynamic reads. fenceBuiltinConstructorStaticRead(lowerer, expr) ?? + fenceMimeStaticRead(lowerer, expr) ?? lowerer.lowerFieldRead(expr); // A union-typed field read narrows like an identifier when the // checker has narrowed this use to one arm. diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index d51a2e242..8e133f6e2 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -74,7 +74,7 @@ const KNOWN: Record = { "node24-file-url-conversion": { build: "SC1090" }, "node24-fs-descriptors": { run: "SC2020" }, "node24-mime": { run: "SC2020" }, - "node24-mime-errors": { run: "SC1090" }, + "node24-mime-errors": { run: "SC2020" }, "node24-no-error-assertions": { run: "SC2020" }, "node24-parseargs": { run: "SC1090" }, "node24-promise-timers": { run: "SC2020" }, From a9ea71df7ddec01a9e0c9984fe3981f4d4b68cf9 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:22:25 -0500 Subject: [PATCH 124/230] Compare unions that hold promises as untyped values A promise converted into a union's promise arm is a new promise, so two optional slots set to one promise each hold their own conversion, and a value narrowed out of such a slot may be one too. Union equality compared those arm payloads by pointer and answered false where Node answers true. Any comparison between a union with a promise arm and a promise or another such union now compares both sides as untyped values, where a converted promise answers with its origin's identity. --- .../frontend/lowering/expressions/binary.ts | 27 ++++++++++--------- .../union-promise-function-arm-widening.ts | 9 +++++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index 01b48554e..4e46b4d5f 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -2388,19 +2388,22 @@ export function lowerStrictEquality( loc, }; } - // A plain promise against a union whose promise arm has a different - // payload (`chains.get(id) === tail` with a `Map>`): - // wrapping would convert the promise into that arm, and the converted - // promise is a new object. Promises are objects to ===, so compare both - // sides as untyped values, where a converted promise answers with its - // origin's identity (the plain promise-vs-promise rule below). - const plainPromise = - left.type.kind === "promise" ? left : right.type.kind === "promise" ? right : null; + // A promise against a union with a promise arm, or two such unions: + // converting a promise into a union's promise arm of another payload + // (`chains.get(id) === tail` with a `Map>`, or + // `a.pending === b.pending` after both were set to one promise) builds a + // new promise, so either side may hold a conversion of the other's + // promise, and a value narrowed out of the union may be one too. + // Promises are objects to ===, so compare both sides as untyped values, + // where a converted promise answers with its origin's identity (the plain + // promise-vs-promise rule below). + const holdsPromise = (type: IrType): boolean => + type.kind === "promise" || + (type.kind === "union" && + (lowerer.unions.get(type.unionId)?.arms ?? []).some((arm) => arm.kind === "promise")); if ( - !bothUnion && - plainPromise !== null && - lowerer.armTag(ut.unionId, plainPromise.type) < 0 && - unionArms.some((arm) => arm.kind === "promise") && + holdsPromise(left.type) && + holdsPromise(right.type) && lowerer.dynConvertible(left.type) && lowerer.dynConvertible(right.type) ) { diff --git a/tests/corpus/union-promise-function-arm-widening.ts b/tests/corpus/union-promise-function-arm-widening.ts index 751af6f41..d3b8f618d 100644 --- a/tests/corpus/union-promise-function-arm-widening.ts +++ b/tests/corpus/union-promise-function-arm-widening.ts @@ -58,6 +58,15 @@ async function main(): Promise { console.log(store.pending === other); console.log(await store.pending); + // One promise in two optional slots: each holds its own conversion. + const second: Store = {}; + second.pending = work; + console.log(store.pending === second.pending, store.pending !== second.pending); + const slots: Store[] = [store, second, { pending: other }]; + console.log(slots.map((slot) => slot.pending === slots[0]!.pending).join(",")); + second.lookup = findName; + console.log(store.lookup === second.lookup); + const lookup = store.lookup; if (lookup) console.log(await lookup("abcd"), await lookup("ab")); console.log(store.scale?.(21)); From e3f02c73c9cde475b83fae2db150f2cc5f0af083 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:32:27 -0500 Subject: [PATCH 125/230] Probe hashed number-key maps without normalizing integer keys A number-key Map or Set read whose key has an integer view (sc_map_entry_f64_int) fell back to the same hashed probe as any double key, which tests for NaN and -0 before hashing. The fallback is now sc_map_entry_f64_hashed_int, which takes the integer and converts it to the key word directly: an integer is never NaN, and zero converts to +0. Both hashed probes test the absent-key filter only before scanning a map that keeps no bucket table. The filter keeps two of its 64 bits per key, so it rejects few absent keys once a map has a bucket table, and the probe's first bucket read answers those. The map layout and the runtime lookup are unchanged. Corpus 8331 covers integers the compiler tracks as integers on hashed maps and absent keys on small bucket tables, and matches Node's output. --- .../compiler/src/backend/llvm/emit-module.ts | 254 ++++++++++-------- .../backend/llvm/map-number-lookup.test.ts | 22 +- .../src/backend/llvm/map-number-lookup.ts | 7 +- ...331-map-number-hashed-integer-keys.ts.json | 6 + .../8331-map-number-hashed-integer-keys.ts | 102 +++++++ ...331-map-number-hashed-integer-keys.ts.json | 1 + 6 files changed, 276 insertions(+), 116 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8331-map-number-hashed-integer-keys.ts.json create mode 100644 tests/corpus/8331-map-number-hashed-integer-keys.ts create mode 100644 tests/harness/corpus-costs/8331-map-number-hashed-integer-keys.ts.json diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index af00ebc2c..66e419fb9 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -1210,6 +1210,126 @@ export function emitModuleParts(emitter: LlEmitter): string[] { return out; } +/** The out-of-line hashed probe of a number-key map (scr_map_find_f64): + * `keyLines` define the normalized key word %kb from the parameter, and + * `runtimeKey` is the double passed to the runtime lookup. A map with at + * most SCR_MAP_LINEAR_LIMIT entries keeps no bucket table: the absent-key + * filter is tested before its entries are scanned. A bucket table is + * probed without the filter: the filter keeps two of its 64 bits per key, + * so past a few dozen keys it rejects few absent keys, and the probe's + * first bucket read answers those. Compact tables tag each 24-bit entry + * index (+1) with the hash's top byte; wider tables take the runtime + * lookup. */ +function hashedNumberProbe( + name: string, + param: string, + sz: string, + keyLines: string[], + runtimeKey: string, +): string[] { + return [ + `define internal ptr @${name}(ptr %m, ${param}) noinline ${FN_ATTRS} {`, + `entry:`, + ...keyLines, + ` %x1s = lshr i64 %kb, 30`, + ` %x1 = xor i64 %kb, %x1s`, + ` %x2 = mul i64 %x1, -4658895280553007687`, + ` %x3s = lshr i64 %x2, 27`, + ` %x3 = xor i64 %x2, %x3s`, + ` %x4 = mul i64 %x3, -7723592293110705685`, + ` %x5s = lshr i64 %x4, 31`, + ` %x5 = xor i64 %x4, %x5s`, + ` %hz = icmp eq i64 %x5, 0`, + ` %h = select i1 %hz, i64 1, i64 %x5`, + ` %nbp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, + ...(sz === "i64" + ? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`] + : [` %nb = load i32, ptr %nbp`]), + ` %eps = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, + ` %ents = load ptr, ptr %eps`, + ` %linear = icmp eq ${sz} %nb, 0`, + ` br i1 %linear, label %filter, label %buckets`, + `filter:`, + ` %f1s = lshr i64 %h, 44`, + ` %f1a = and i64 %f1s, 63`, + ` %f1 = shl i64 1, %f1a`, + ` %f2s = lshr i64 %h, 50`, + ` %f2a = and i64 %f2s, 63`, + ` %f2 = shl i64 1, %f2a`, + ` %fb = or i64 %f1, %f2`, + ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, + ` %filter.bits = load i64, ptr %fp`, + ` %fm = and i64 %filter.bits, %fb`, + ` %fin = icmp eq i64 %fm, %fb`, + ` br i1 %fin, label %scan, label %miss`, + // A number-key entry has no cached hash; a dead entry's key matches no key. + `scan:`, + ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, + ...(sz === "i64" + ? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`] + : [` %ne = load i32, ptr %nep`]), + ` br label %scan.loop`, + `scan.loop:`, + ` %si = phi ${sz} [ 0, %scan ], [ %si.next, %scan.next ]`, + ` %sdone = icmp eq ${sz} %si, %ne`, + ` br i1 %sdone, label %miss, label %scan.entry`, + `scan.entry:`, + ` %se = getelementptr inbounds %ScrMapNumEntry, ptr %ents, ${sz} %si`, + ` %sk = load i64, ptr %se`, + ` %skq = icmp eq i64 %sk, %kb`, + ` br i1 %skq, label %scan.found, label %scan.next`, + `scan.found:`, + ` ret ptr %se`, + `scan.next:`, + ` %si.next = add ${sz} %si, 1`, + ` br label %scan.loop`, + `buckets:`, + ` %wide = icmp ugt ${sz} %nb, 16777216`, + ` br i1 %wide, label %runtime, label %probe`, + `probe:`, + ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 14`, + ` %bk = load ptr, ptr %bp`, + ` %mask = sub ${sz} %nb, 1`, + ` %ht = lshr i64 %h, 32`, + ` %ht32 = trunc i64 %ht to i32`, + ` %tag = and i32 %ht32, -16777216`, + sz === "i64" ? ` %hi = add i64 %h, 0` : ` %hi = trunc i64 %h to ${sz}`, + ` %i0 = and ${sz} %hi, %mask`, + ` br label %probe.loop`, + `probe.loop:`, + ` %pi = phi ${sz} [ %i0, %probe ], [ %pi.next, %probe.next ]`, + ` %bep = getelementptr inbounds i32, ptr %bk, ${sz} %pi`, + ` %b = load i32, ptr %bep`, + ` %bempty = icmp eq i32 %b, 0`, + ` br i1 %bempty, label %miss, label %probe.tag`, + `probe.tag:`, + ` %bt = and i32 %b, -16777216`, + ` %btq = icmp eq i32 %bt, %tag`, + ` br i1 %btq, label %probe.entry, label %probe.next`, + `probe.entry:`, + ` %bix = and i32 %b, 16777215`, + ` %bix1 = sub i32 %bix, 1`, + sz === "i32" ? ` %bi = add i32 %bix1, 0` : ` %bi = zext i32 %bix1 to ${sz}`, + ` %pe = getelementptr inbounds %ScrMapNumEntry, ptr %ents, ${sz} %bi`, + ` %pk = load i64, ptr %pe`, + ` %pkq = icmp eq i64 %pk, %kb`, + ` br i1 %pkq, label %probe.found, label %probe.next`, + `probe.found:`, + ` ret ptr %pe`, + `probe.next:`, + ` %pi.inc = add ${sz} %pi, 1`, + ` %pi.next = and ${sz} %pi.inc, %mask`, + ` br label %probe.loop`, + `miss:`, + ` ret ptr null`, + `runtime:`, + ` %r = call ptr @scr_map_entry_f64(ptr %m, double ${runtimeKey}) memory(read) nounwind willreturn`, + ` ret ptr %r`, + `}`, + ``, + ]; +} + /** The shared abort helpers (emitted only when referenced): the OOM * abort of untraced shape allocation and the invalid-union-tag abort — * both print the runtime ABI’s exact message on fd 2 and abort. */ @@ -1349,118 +1469,34 @@ export function helperDefs(emitter: LlEmitter): string[] { ` %dense.any = icmp ne i64 %nd, 0`, ` br i1 %dense.any, label %miss, label %slow`, `slow:`, - ` %hashed = call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)`, + ` %hashed = call ptr @sc_map_entry_f64_hashed_int(ptr %m, i64 %ki)`, ` ret ptr %hashed`, `}`, ``, - // Hashing (scr_map_find_f64): the SameValueZero key word, its - // scr_key_mix hash (zero remapped to one) and the absent-key filter. - `define internal ptr @sc_map_entry_f64_hashed(ptr %m, double %k) noinline ${FN_ATTRS} {`, - `entry:`, - ` %nan = fcmp uno double %k, %k`, - ` %zero = fcmp oeq double %k, 0.0`, - ` %kz = select i1 %zero, double 0.0, double %k`, - ` %kzb = bitcast double %kz to i64`, - ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, - ` %x1s = lshr i64 %kb, 30`, - ` %x1 = xor i64 %kb, %x1s`, - ` %x2 = mul i64 %x1, -4658895280553007687`, - ` %x3s = lshr i64 %x2, 27`, - ` %x3 = xor i64 %x2, %x3s`, - ` %x4 = mul i64 %x3, -7723592293110705685`, - ` %x5s = lshr i64 %x4, 31`, - ` %x5 = xor i64 %x4, %x5s`, - ` %hz = icmp eq i64 %x5, 0`, - ` %h = select i1 %hz, i64 1, i64 %x5`, - ` %f1s = lshr i64 %h, 44`, - ` %f1a = and i64 %f1s, 63`, - ` %f1 = shl i64 1, %f1a`, - ` %f2s = lshr i64 %h, 50`, - ` %f2a = and i64 %f2s, 63`, - ` %f2 = shl i64 1, %f2a`, - ` %fb = or i64 %f1, %f2`, - ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, - ` %filter = load i64, ptr %fp`, - ` %fm = and i64 %filter, %fb`, - ` %fin = icmp eq i64 %fm, %fb`, - ` br i1 %fin, label %table, label %miss`, - `table:`, - ` %nbp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, - ...(sz === "i64" - ? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`] - : [` %nb = load i32, ptr %nbp`]), - ` %eps = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, - ` %ents = load ptr, ptr %eps`, - ` %linear = icmp eq ${sz} %nb, 0`, - ` br i1 %linear, label %scan, label %buckets`, - // Small maps keep no bucket table: scan the entries' keys. A number-key - // entry has no cached hash; a dead entry's key matches no key. - `scan:`, - ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, - ...(sz === "i64" - ? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`] - : [` %ne = load i32, ptr %nep`]), - ` br label %scan.loop`, - `scan.loop:`, - ` %si = phi ${sz} [ 0, %scan ], [ %si.next, %scan.next ]`, - ` %sdone = icmp eq ${sz} %si, %ne`, - ` br i1 %sdone, label %miss, label %scan.entry`, - `scan.entry:`, - ` %se = getelementptr inbounds %ScrMapNumEntry, ptr %ents, ${sz} %si`, - ` %sk = load i64, ptr %se`, - ` %skq = icmp eq i64 %sk, %kb`, - ` br i1 %skq, label %scan.found, label %scan.next`, - `scan.found:`, - ` ret ptr %se`, - `scan.next:`, - ` %si.next = add ${sz} %si, 1`, - ` br label %scan.loop`, - // Compact tables tag each 24-bit entry index (+1) with the hash's - // top byte; wider tables take the runtime lookup. - `buckets:`, - ` %wide = icmp ugt ${sz} %nb, 16777216`, - ` br i1 %wide, label %runtime, label %probe`, - `probe:`, - ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 14`, - ` %bk = load ptr, ptr %bp`, - ` %mask = sub ${sz} %nb, 1`, - ` %ht = lshr i64 %h, 32`, - ` %ht32 = trunc i64 %ht to i32`, - ` %tag = and i32 %ht32, -16777216`, - sz === "i64" ? ` %hi = add i64 %h, 0` : ` %hi = trunc i64 %h to ${sz}`, - ` %i0 = and ${sz} %hi, %mask`, - ` br label %probe.loop`, - `probe.loop:`, - ` %pi = phi ${sz} [ %i0, %probe ], [ %pi.next, %probe.next ]`, - ` %bep = getelementptr inbounds i32, ptr %bk, ${sz} %pi`, - ` %b = load i32, ptr %bep`, - ` %bempty = icmp eq i32 %b, 0`, - ` br i1 %bempty, label %miss, label %probe.tag`, - `probe.tag:`, - ` %bt = and i32 %b, -16777216`, - ` %btq = icmp eq i32 %bt, %tag`, - ` br i1 %btq, label %probe.entry, label %probe.next`, - `probe.entry:`, - ` %bix = and i32 %b, 16777215`, - ` %bix1 = sub i32 %bix, 1`, - sz === "i32" ? ` %bi = add i32 %bix1, 0` : ` %bi = zext i32 %bix1 to ${sz}`, - ` %pe = getelementptr inbounds %ScrMapNumEntry, ptr %ents, ${sz} %bi`, - ` %pk = load i64, ptr %pe`, - ` %pkq = icmp eq i64 %pk, %kb`, - ` br i1 %pkq, label %probe.found, label %probe.next`, - `probe.found:`, - ` ret ptr %pe`, - `probe.next:`, - ` %pi.inc = add ${sz} %pi, 1`, - ` %pi.next = and ${sz} %pi.inc, %mask`, - ` br label %probe.loop`, - `miss:`, - ` ret ptr null`, - `runtime:`, - ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k) memory(read) nounwind willreturn`, - ` ret ptr %r`, - `}`, - ``, + // Hashing (scr_map_find_f64): the SameValueZero key word and its + // scr_key_mix hash. sc_map_entry_f64_int's probe converts the integer + // view to the key word, which is never NaN or -0, and so skips the + // normalization. + ...hashedNumberProbe( + "sc_map_entry_f64_hashed", + "double %k", + sz, + [ + ` %nan = fcmp uno double %k, %k`, + ` %zero = fcmp oeq double %k, 0.0`, + ` %kz = select i1 %zero, double 0.0, double %k`, + ` %kzb = bitcast double %kz to i64`, + ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, + ], + "%k", + ), + ...hashedNumberProbe( + "sc_map_entry_f64_hashed_int", + "i64 %ki", + sz, + [` %kf = sitofp i64 %ki to double`, ` %kb = bitcast double %kf to i64`], + "%kf", + ), ); } // Map/Set iteration steps read the entries array inline: an index is diff --git a/packages/compiler/src/backend/llvm/map-number-lookup.test.ts b/packages/compiler/src/backend/llvm/map-number-lookup.test.ts index 7457db42d..f7bd87af1 100644 --- a/packages/compiler/src/backend/llvm/map-number-lookup.test.ts +++ b/packages/compiler/src/backend/llvm/map-number-lookup.test.ts @@ -168,8 +168,10 @@ test("hashed number-key lookups probe inline and call the runtime only for wide expect(hashed).toContain("select i1 %nan, i64 9221120237041090560, i64 %kzb"); expect(hashed).toContain("mul i64 %x1, -4658895280553007687"); expect(hashed).toContain("mul i64 %x3, -7723592293110705685"); - // Filter (field 10), small-map scan of the entry count (field 8), and - // compact buckets (fields 13 and 14) tagged with the hash's top byte. + // Filter (field 10) before the small-map scan of the entry count (field + // 8) only, and compact buckets (fields 13 and 14) tagged with the hash's + // top byte. + expect(hashed).toContain("br i1 %linear, label %filter, label %buckets"); expect(hashed).toContain("getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10"); expect(hashed).toContain(`load i32, ptr %nep`); expect(hashed).toContain(`load i32, ptr %nbp`); @@ -192,11 +194,23 @@ test("a key with an integer view tests the direct index on the integer", () => { integral.module, )![0]; // An unsigned compare also rejects negative keys; the hashed probe takes - // the double. + // the integer. expect(helper).toContain("icmp ult i64 %ki, %nd"); expect(helper).toContain(`getelementptr inbounds i32, ptr %dense, ${sz} %i`); - expect(helper).toContain("call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)"); + expect(helper).toContain("call ptr @sc_map_entry_f64_hashed_int(ptr %m, i64 %ki)"); expect(helper).not.toContain("fptosi"); + // Its key word is the integer's double, which needs no NaN or -0 + // normalization; the runtime lookup gets that double. + const hashed = + /^define internal ptr @sc_map_entry_f64_hashed_int\(ptr %m, i64 %ki\) noinline [^]*?^}/m.exec( + integral.module, + )![0]; + expect(hashed).toContain("sitofp i64 %ki to double"); + expect(hashed).not.toContain("fcmp"); + expect(hashed).toContain("mul i64 %x1, -4658895280553007687"); + expect(hashed).toContain( + "call ptr @scr_map_entry_f64(ptr %m, double %kf) memory(read) nounwind willreturn", + ); // A fractional key and a key without a proof keep the double lookup. for (const key of [{ kind: "numLit", value: 0.5, type: F64, loc } as IrExpr, ref("k", F64)]) { const { work } = emitted(lookupFixture(key), bits); diff --git a/packages/compiler/src/backend/llvm/map-number-lookup.ts b/packages/compiler/src/backend/llvm/map-number-lookup.ts index afc8ec606..17a1563c3 100644 --- a/packages/compiler/src/backend/llvm/map-number-lookup.ts +++ b/packages/compiler/src/backend/llvm/map-number-lookup.ts @@ -4,9 +4,10 @@ * direct index in the runtime (scr_map.c). Reads go through the internal * sc_map_entry_f64 helper (emitter.ts helperDefs), which LLVM inlines: it * resolves directly indexed maps without a call and other maps through - * the internal sc_map_entry_f64_hashed probe, which hashes the key as the - * runtime does and defers to scr_map_entry_f64 only for wide bucket - * tables. Both only read memory. The generated code then reads the entry's + * the internal sc_map_entry_f64_hashed probe (sc_map_entry_f64_hashed_int + * for a key with an integer view), which hashes the key as the runtime + * does and defers to scr_map_entry_f64 only for wide bucket tables. Both + * only read memory. The generated code then reads the entry's * value slot itself and retains a reference value with the value type's own * retain, which is the adapter the collection was constructed with. * String-keyed reads use the same entry contract through the internal diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8331-map-number-hashed-integer-keys.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8331-map-number-hashed-integer-keys.ts.json new file mode 100644 index 000000000..86822b641 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8331-map-number-hashed-integer-keys.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8331-map-number-hashed-integer-keys.ts" + ], + "diags": [] +} diff --git a/tests/corpus/8331-map-number-hashed-integer-keys.ts b/tests/corpus/8331-map-number-hashed-integer-keys.ts new file mode 100644 index 000000000..8af7293e9 --- /dev/null +++ b/tests/corpus/8331-map-number-hashed-integer-keys.ts @@ -0,0 +1,102 @@ +// Number-keyed Map and Set lookups whose keys are integers the compiler +// tracks as integers (loop counters, literals, integer arithmetic) on +// hashed storage, and lookups of absent keys on small bucket tables. + +// Keys spread past the direct index's range, so the maps hash them. +const spread = new Map(); +for (let i = 0; i < 500; i++) spread.set(i * 104729 - 26000000, i); +let found = 0, + absent = 0, + total = 0; +for (let i = -10; i < 520; i++) { + const v = spread.get(i * 104729 - 26000000); + if (v === undefined) absent++; + else { + found++; + total += v; + } +} +console.log("spread", spread.size, found, absent, total); +console.log("spread literals", spread.get(-26000000), spread.get(-25895271), spread.get(0), spread.has(-1)); + +// Zero stored through -0 and through an integer; read back through both. +const zeros = new Map(); +for (let i = 1; i <= 20; i++) zeros.set(i * 1000003, "k" + i); +zeros.set(-0, "minus zero"); +let z = 0; +console.log("zeros", zeros.size, zeros.get(z), zeros.get(0), zeros.get(-0), zeros.get(z * -1)); +zeros.set(z, "zero"); +console.log("zeros overwrite", zeros.size, zeros.get(-0), [...zeros.keys()].includes(0)); + +// Large and negative integer keys. +const wide = new Map(); +for (let i = 0; i < 64; i++) wide.set(2 ** 40 + i * 3, i); +for (let i = 0; i < 64; i++) wide.set(-(2 ** 33) - i, -i); +let wideUp = 0, + wideDown = 0, + wideMissing = 0; +for (let i = 0; i < 200; i++) { + const a = wide.get(2 ** 40 + i); + const b = wide.get(-(2 ** 33) - i); + if (a === undefined) wideMissing++; + else wideUp += a; + if (b === undefined) wideMissing++; + else wideDown += b; +} +console.log("wide", wide.size, wideUp, wideDown, wideMissing, wide.get(1099511627776), wide.get(-8589934592)); + +// Absent keys on bucket tables just past the linear scan, and as they grow. +for (const size of [9, 12, 16, 33, 64, 100, 257, 400]) { + const m = new Map(); + for (let i = 0; i < size; i++) m.set(i * 7919 + 1000000, i); + let hits = 0, + misses = 0; + for (let i = 0; i < size * 4; i++) { + if (m.has(i * 7919 + 1000000)) hits++; + else misses++; + if (m.get(i * 7919 + 1000001) !== undefined) hits += 1000; + } + for (let i = 0; i < size; i += 2) m.delete(i * 7919 + 1000000); + let after = 0; + for (let i = 0; i < size; i++) if (m.has(i * 7919 + 1000000)) after++; + console.log("size", size, hits, misses, m.size, after, m.get(1000000), m.get(1007919)); +} + +// Small maps that scan their entries, with integer keys. +const tiny = new Map(); +for (let i = 0; i < 8; i++) tiny.set(i * 5000011, "t" + i); +let tinyHits = 0; +for (let i = 0; i < 40; i++) if (tiny.has(i * 5000011)) tinyHits++; +console.log("tiny", tiny.size, tinyHits, tiny.get(0), tiny.get(35000077), tiny.has(5000012)); + +// Sets with integer keys. +const seen = new Set(); +let dupes = 0; +for (let i = 0; i < 3000; i++) { + const k = ((i * 2654435761) % 1000003) - 500000; + if (seen.has(k)) dupes++; + else seen.add(k); +} +let present = 0; +for (let i = -500000; i < 500003; i += 997) if (seen.has(i)) present++; +console.log("set", seen.size, dupes, present, seen.has(-500000), seen.has(500002)); + +// A counting loop like a k-mer histogram: get, then update the found entry. +class Count { + n = 0; +} +const counts = new Map(); +let key = 0; +for (let i = 0; i < 20000; i++) { + key = ((key * 4) % 4194304) + (i % 7 === 0 ? 3 : i % 3); + const c = counts.get(key); + if (c === undefined) { + const fresh = new Count(); + fresh.n = 1; + counts.set(key, fresh); + } else c.n++; +} +let most = 0, + mostKey = -1; +for (const [k, c] of counts) if (c.n > most) [most, mostKey] = [c.n, k]; +console.log("counts", counts.size, most, mostKey); diff --git a/tests/harness/corpus-costs/8331-map-number-hashed-integer-keys.ts.json b/tests/harness/corpus-costs/8331-map-number-hashed-integer-keys.ts.json new file mode 100644 index 000000000..961fe0dad --- /dev/null +++ b/tests/harness/corpus-costs/8331-map-number-hashed-integer-keys.ts.json @@ -0,0 +1 @@ +{ "plain": 3441, "san": 1752 } From 3a900ed9b6d8b43cdb7b764228dcd134df7f76d7 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:38:27 -0500 Subject: [PATCH 126/230] Keep the poll in nested loops whose counter can stop changing at 2^53 The range analysis caps a counter that steps by 1 at 2^53, where `j + 1 === j`, so a loop such as `for (let j = 2 ** 53 - 2; j < 2 ** 53 + 10; j++)` has a range of three values at its condition and was taken for a loop of three runs. It never ends, and without its poll worker.terminate() could not stop it. A loop's runs now count as bounded only when a step from every value in its counter's range gives a different value. --- .../src/backend/llvm/emit-statements.ts | 15 ++++++++++++--- .../test/counted-loop-emission.test.ts | 19 ++++++++++++++++++- .../native-worker-terminate-bounded-loops.ts | 15 +++++++++++++-- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts index e0db764e8..cc0af8ebb 100644 --- a/packages/compiler/src/backend/llvm/emit-statements.ts +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -2655,6 +2655,14 @@ const WORKER_POLL_BLOCK = 64; * only as it leaves. */ const WORKER_UNPOLLED_RUN = 16384; +/** Whether a step of `step` from every value in `range` gives a different + * value. Beyond 2^53 a double can stop changing (`j + 1 === j` from 2^53), + * so a counter there can run without end inside a range of a few values. */ +function exactSteps(range: { min: number; max: number }, step: number): boolean { + const size = Math.abs(step); + return range.min >= size - 2 ** 53 && range.max <= 2 ** 53 - size; +} + /** At most how many times a `for` loop and the loops in its body run their * bodies per entry, from the counters' proven ranges: its iterations times * one plus the nested loops' runs. Infinity when a count has no bound, or @@ -2668,12 +2676,12 @@ function boundedLoopRuns(emitter: LlEmitter, loop: IrStmt): number { LOOPS.of(emitter).unaliasedArrays, ); let trips = Infinity; - if (counted && !counted.guarded) + if (counted && !counted.guarded && exactSteps(counted.range, counted.step)) trips = Math.floor((counted.range.max - counted.range.min) / Math.abs(counted.step)) + 1; else if (!counted) { // Each iteration closes the gap between the two counters by at least two. const paired = matchIntegerPairedForLoop(loop, emitter.numericLocals, emitter.integerRanges); - if (paired && !paired.guarded) + if (paired && !paired.guarded && exactSteps(paired.range, 1)) trips = Math.floor((paired.range.max - paired.range.min) / 2) + 1; } if (trips === Infinity) { @@ -2681,7 +2689,8 @@ function boundedLoopRuns(emitter: LlEmitter, loop: IrStmt): number { // one compared with a module constant. const stepped = steppedLoopCounter(loop, emitter.currentLocals); const range = stepped && emitter.integerRanges.get(stepped.ref); - if (stepped && range) trips = Math.floor((range.max - range.min) / Math.abs(stepped.step)) + 1; + if (stepped && range && exactSteps(range, stepped.step)) + trips = Math.floor((range.max - range.min) / Math.abs(stepped.step)) + 1; } if (!(trips <= WORKER_POLL_BLOCK * WORKER_POLL_BLOCK)) return Infinity; let nested = 0; diff --git a/packages/compiler/test/counted-loop-emission.test.ts b/packages/compiler/test/counted-loop-emission.test.ts index 794edd1af..d7ebc97fa 100644 --- a/packages/compiler/test/counted-loop-emission.test.ts +++ b/packages/compiler/test/counted-loop-emission.test.ts @@ -175,6 +175,18 @@ test("worker programs poll no loop whose proven ranges bound its runs inside ano " for (let r = 0; r < n; r++) for (let j = 0; j < 5000; j++) t = (t * 31 + j) % 1000003;", " return t;", "}", + "export function exact(n: number): number {", + " let t = 0;", + " for (let r = 0; r < n; r++)", + " for (let j = 9007199254740900; j < 9007199254740950; j++) t = (t + (j % 7)) % 1000003;", + " return t;", + "}", + "export function stalled(n: number): number {", + " let t = 0;", + " for (let r = 0; r < n; r++)", + " for (let j = 9007199254740990; j < 9007199254741002; j++) t = (t + (j % 7)) % 1000003;", + " return t;", + "}", "export function single(): number {", " let t = 0;", " for (let j = 0; j < 50; j++) t = (t * 31 + j) % 1000003;", @@ -182,7 +194,7 @@ test("worker programs poll no loop whose proven ranges bound its runs inside ano "}", "const cr = new Float64Array(8).fill(0.5);", 'const k = Number(process.argv[2] ?? "50");', - "console.log(rows(cr, 3), open(cr, 3, k), wide(2), single());", + "console.log(rows(cr, 3), open(cr, 3, k), wide(2), exact(2), stalled(0), single());", "if (!isMainThread) new Worker(new URL(import.meta.url));", ].join("\n"), ); @@ -219,6 +231,11 @@ test("worker programs poll no loop whose proven ranges bound its runs inside ano // A run of up to 16384 iterations is one block, polled as it leaves when // it took more than 64. expect(open).toMatch(/^loop\.poll\.run\d+:$/m); + // Steps of 1 below 2^53 are exact, so 50 runs near it are bounded. From + // 2^53, `j + 1 === j`: a counter ranging up to it can run without end + // and keeps its poll. + expect(polls(body("exact"))).toBe(polls(body("single"))); + expect(polls(body("stalled"))).toBeGreaterThan(polls(body("exact"))); // A nest over the bound, or a loop no other loop encloses, polls. expect(polls(body("wide"))).toBeGreaterThan(0); expect(body("wide")).toMatch(/^loop\.poll\d+:$/m); diff --git a/tests/corpus/native-worker-terminate-bounded-loops.ts b/tests/corpus/native-worker-terminate-bounded-loops.ts index d244e021d..ec1e4c736 100644 --- a/tests/corpus/native-worker-terminate-bounded-loops.ts +++ b/tests/corpus/native-worker-terminate-bounded-loops.ts @@ -1,7 +1,8 @@ // worker.terminate() stops a worker whose loop runs short inner loops: // counted loops of a few iterations, a two-counter loop, an inner loop // whose length grows past 64 iterations, an inner loop too long to finish, -// and a nest of loops with constant bounds. +// a nest of loops with constant bounds, and an inner loop whose counter +// stops changing at 2 ** 53 (`j + 1 === j` there), so it never ends. import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; function counted(values: number[]): number { @@ -66,8 +67,17 @@ function constant(row: Float64Array): number { return acc; } +function stalled(): number { + let acc = 0; + for (let round = 0; ; round++) { + if (round === 0) parentPort!.postMessage("ready"); + for (let j = 9007199254740990; j < 9007199254741002; j++) acc = (acc + (j % 7)) % 1000003; + } + return acc; +} + if (isMainThread) { - for (const kind of ["counted", "paired", "growing", "long", "constant"]) { + for (const kind of ["counted", "paired", "growing", "long", "constant", "stalled"]) { await new Promise((resolve) => { const worker = new Worker(new URL(import.meta.url), { workerData: kind }); worker.once("message", () => { @@ -86,6 +96,7 @@ if (isMainThread) { else if (workerData === "paired") result = paired(values); else if (workerData === "growing") result = growing(); else if (workerData === "long") result = long(1e15); + else if (workerData === "stalled") result = stalled(); else result = constant(new Float64Array(8).fill(0.25)); console.log("unexpected result", result); } From 3c057b501f277a46fc6d798eddae936ff13685bc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:44:46 -0500 Subject: [PATCH 127/230] Lay out the native compiler's functions by the code each kind of run executes A warm rebuild ran about 3.5 MB of the native compiler's code, but the code was spread over its 69 MB text, so 64 KB fault-around mapped about 20 MB of it. The emitter can now take a function layout: a list of functions in tiers. It writes the listed definitions, generated helpers included, first and by tier, each in a `layout-` partition group. The LLVM helper compiles each partition's definitions in text order, so the listed ones come first in their partitions, and a helper that places functions by group keeps a tier's definitions in the same partitions. The rest keep their order and their source-file groups. - compile() takes a `functionLayout` file (` ` lines, gzip-compressed when the name ends in .gz). Keys leave out module numbers. - build-native-cli.mts uses the committed packages/compiler/native/cli.layout.gz, or the file SCRIPTC_NATIVE_LAYOUT names; an empty value and instrumented builds use none. - scripts/refresh-cli-layout.mjs builds an instrumented compiler, builds the runtime workloads and a corpus slice cold and then unchanged, and reads the functions each run executed from the raw profiles. Tier 1 is `--version` and rebuilds that hit both caches; tier 2 is the other builds. - The release workflow's cli-profile job also collects a layout on the release commit, and the CLI builds use it. Names of closures and generated helpers change with nearby edits, so a committed layout matches fewer functions as main moves on. - Forked lowering and emission workers reset the profile counters after fork and write their profile before _exit, so instrumented builds also count the work done in workers. This applies to refresh-cli-profile.mjs too. Executables the compiler produces are unchanged. --- .github/workflows/release.yml | 29 +- RELEASING.md | 2 + packages/compiler/native/cli.layout.gz | Bin 0 -> 216519 bytes packages/compiler/native/host.c | 25 +- .../compiler/src/backend/llvm/emit-module.ts | 89 +++++-- packages/compiler/src/backend/llvm/emitter.ts | 3 + .../src/backend/llvm/function-layout.ts | 41 +++ packages/compiler/src/compile-types.ts | 5 + packages/compiler/src/index.ts | 18 ++ .../compiler/test/function-layout.test.ts | 147 +++++++++++ scripts/build-native-cli.mts | 10 + scripts/refresh-cli-layout.mjs | 248 ++++++++++++++++++ 12 files changed, 591 insertions(+), 26 deletions(-) create mode 100644 packages/compiler/native/cli.layout.gz create mode 100644 packages/compiler/src/backend/llvm/function-layout.ts create mode 100644 packages/compiler/test/function-layout.test.ts create mode 100644 scripts/refresh-cli-layout.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 799337e0a..ef0b764ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -100,11 +100,11 @@ jobs: if (receiptRun) appendFileSync(process.env.GITHUB_OUTPUT, `receipt_run=${receiptRun}\n`); NODE - # Collects the native compiler's profile on this commit - # (scripts/refresh-cli-profile.mjs), so every platform's CLI build optimizes - # with a profile of the code it compiles. The committed profile falls behind - # main within a day or two. If this job fails, the CLI builds use the - # committed profile. + # Collects the native compiler's profile and function layout on this commit + # (scripts/refresh-cli-profile.mjs, scripts/refresh-cli-layout.mjs), so every + # platform's CLI build optimizes and lays out the code it compiles. The + # committed ones fall behind main within a day or two. If this job fails, + # the CLI builds use the committed profile and layout. cli-profile: name: Collect the native compiler profile needs: check-release @@ -138,11 +138,17 @@ jobs: - name: Collect the profile run: >- node scripts/refresh-cli-profile.mjs --tsc-ts "$RUNNER_TEMP/tsc-ts" --jobs 4 - --output "$RUNNER_TEMP/cli-profile/cli.profdata.gz" + --output "$RUNNER_TEMP/cli-profile/cli.profdata.gz" --work "$RUNNER_TEMP/cli-profile-work" + - name: Collect the function layout + run: >- + node scripts/refresh-cli-layout.mjs --jobs 4 --instrumented "$RUNNER_TEMP/cli-profile-work/cli" + --output "$RUNNER_TEMP/cli-profile/cli.layout.gz" - uses: actions/upload-artifact@v4 with: name: cli-profile - path: ${{ runner.temp }}/cli-profile/cli.profdata.gz + path: | + ${{ runner.temp }}/cli-profile/cli.profdata.gz + ${{ runner.temp }}/cli-profile/cli.layout.gz retention-days: 7 if-no-files-found: error @@ -300,8 +306,8 @@ jobs: with: name: cli-profile path: ${{ runner.temp }}/cli-profile - # Without the artifact, SCRIPTC_NATIVE_PROFILE_USE stays unset and - # build-native-cli.mts uses the committed profile. + # Without the artifact, SCRIPTC_NATIVE_PROFILE_USE and SCRIPTC_NATIVE_LAYOUT + # stay unset and build-native-cli.mts uses the committed profile and layout. - name: Select the native compiler profile if: matrix.cli != '' shell: bash @@ -311,6 +317,11 @@ jobs: if [ -n "$COLLECTED" ]; then echo "SCRIPTC_NATIVE_PROFILE_USE=$COLLECTED" >> "$GITHUB_ENV" echo "The native CLI builds with the profile collected on $GITHUB_SHA" + layout="$(dirname "$COLLECTED")/cli.layout.gz" + if [ -f "$layout" ]; then + echo "SCRIPTC_NATIVE_LAYOUT=$layout" >> "$GITHUB_ENV" + echo "The native CLI builds with the function layout collected on $GITHUB_SHA" + fi else echo "::warning::No profile was collected on this commit; the native CLI builds with the committed profile" fi diff --git a/RELEASING.md b/RELEASING.md index 42bfe75ad..42c253568 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -18,6 +18,8 @@ The release job builds each pinned LLVM helper, precompiled runtime pack, and na Release builds of the native CLI optimize the compiler with a profile collected on the release commit. The `cli-profile` job runs `node scripts/refresh-cli-profile.mjs` on Linux x64, which builds an instrumented compiler, runs it on tsc-ts, a slice of the corpus, and the runtime benchmark workloads, and writes the merged sparse profile. Every platform's CLI build uses that profile (`SCRIPTC_NATIVE_PROFILE_USE`), and the GitHub release attaches it as `scriptc--cli.profdata.gz`. Code the profile does not describe, or describes from older compiler sources, is optimized as without a profile. When the profile job fails, the CLI builds use the committed profile `packages/compiler/native/cli.profdata.gz`, which local and CI builds of the native compiler also use. Refresh the committed profile when compiler code has changed substantially since the last refresh: run `node scripts/refresh-cli-profile.mjs --tsc-ts ` on a quiet Linux x64 host and commit the new profile on its own. +Builds of the native compiler also order its functions by the committed layout `packages/compiler/native/cli.layout.gz`: the functions that `--version` and rebuilds of unchanged programs run come first, then the ones other builds run, so a run maps fewer pages of the executable. Functions the layout does not list keep their usual order. Function names of closures and generated helpers change with nearby edits, so the `cli-profile` job also collects a layout on the release commit (`scripts/refresh-cli-layout.mjs`, reusing the job's instrumented compiler), and the CLI builds use it (`SCRIPTC_NATIVE_LAYOUT`). Refresh the committed layout the same way as the profile, with `node scripts/refresh-cli-layout.mjs`, and commit it on its own. + Publishing uses npm trusted publishing (OIDC) — there is no npm token secret. Each published package must have a GitHub Actions trusted publisher for `release.yml` and the `Release` environment. A missing configuration fails diff --git a/packages/compiler/native/cli.layout.gz b/packages/compiler/native/cli.layout.gz new file mode 100644 index 0000000000000000000000000000000000000000..ff1f8b97e93e958cd850eb6991d489661b1d678e GIT binary patch literal 216519 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 literal 0 HcmV?d00001 diff --git a/packages/compiler/native/host.c b/packages/compiler/native/host.c index 24d330cc0..f4393c5f7 100644 --- a/packages/compiler/native/host.c +++ b/packages/compiler/native/host.c @@ -80,9 +80,30 @@ void scriptc_native_unlock(void) {} #include #include -int32_t scriptc_native_fork(void) { return (int32_t)fork(); } +#if defined(__linux__) +/* LLVM's profile runtime, linked into instrumented builds of the compiler + * (SCRIPTC_NATIVE_PROFILE_GENERATE); other builds leave these null. A worker + * starts with zeroed counters and writes its profile before _exit, which + * skips the runtime's atexit writer, so the profile counts the worker's work + * once. */ +extern void __llvm_profile_reset_counters(void) __attribute__((weak)); +extern int __llvm_profile_write_file(void) __attribute__((weak)); +#endif -void scriptc_native_exit(int32_t code) { _exit(code); } +int32_t scriptc_native_fork(void) { + pid_t pid = fork(); +#if defined(__linux__) + if (pid == 0 && __llvm_profile_reset_counters != NULL) __llvm_profile_reset_counters(); +#endif + return (int32_t)pid; +} + +void scriptc_native_exit(int32_t code) { +#if defined(__linux__) + if (__llvm_profile_write_file != NULL) (void)__llvm_profile_write_file(); +#endif + _exit(code); +} /* The worker's exit status, 128 plus the signal for a worker a signal * ended, or -1 when waitpid fails. */ diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index af00ebc2c..24c32d2e9 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -66,6 +66,7 @@ import { emitLibraryIdentityLines } from "../library-identity-markers.js"; import { InternalCompilerError } from "../../errors.js"; import { targetCpuAttribute } from "../target-cpu.js"; import type { IrFunction } from "../../ir/ir.js"; +import { type FunctionLayout, layoutTierOf } from "./function-layout.js"; /** The function bodies emitted in `chunks` contiguous ranges, each by its * own emitter (LlEmitter.chunkEmitter), then merged in function order: the @@ -228,6 +229,61 @@ function partitionGroupLines(functions: readonly IrFunction[]): string[] { /** Ends the group of the definitions that follow. */ const PARTITION_GROUP_END = "; scriptc-group"; +/** Writes the generated helpers, then the function definitions with their + * group lines. With a layout, the definitions it lists, helpers included, + * move after the unlisted helpers and before the other functions, by tier, + * each in its tier's group (`layout-`). The helper compiles each + * partition's definitions in text order, so listed ones come first in their + * partitions, and it places a group's definitions in the same partitions. + * Everything else keeps its order and its source-file group. */ +function writeDefinitions( + out: string[], + layout: FunctionLayout | undefined, + helperTexts: readonly (readonly string[])[], + fnDefs: readonly string[], + groups: readonly string[], +): void { + const tierLine = (tier: number) => `${PARTITION_GROUP_END} layout-${tier}`; + const listed = new Map(); + const listIn = (tier: number, lines: readonly string[]) => { + const list = listed.get(tier) ?? []; + if (!listed.has(tier)) listed.set(tier, list); + for (const line of lines) list.push(line); + }; + if (layout === undefined || layout.size === 0) { + for (const texts of helperTexts) for (const text of texts) out.push(text); + } else { + // A helper's text can span several entries (one per line), so the + // definitions are found in the joined text. A listed definition moves + // through its closing brace; what follows it stays in place. + const joined: string[] = []; + for (const texts of helperTexts) for (const text of texts) joined.push(text); + for (const part of joined.join("\n").split(/\n(?=define )/)) { + const tier = part.startsWith("define ") ? layoutTierOf(layout, part) : undefined; + const close = tier === undefined ? -1 : part.search(/\n}(\n|$)/); + if (tier === undefined || close < 0) { + out.push(part); + continue; + } + listIn(tier, [tierLine(tier), part.slice(0, close + 2)]); + const rest = part.slice(close + 3); + if (rest !== "") out.push(rest); + } + } + const unlisted: number[] = []; + fnDefs.forEach((text, i) => { + const tier = layout === undefined || layout.size === 0 ? undefined : layoutTierOf(layout, text); + if (tier === undefined) unlisted.push(i); + else listIn(tier, ["", tierLine(tier), text]); + }); + for (const tier of [...listed.keys()].sort((a, b) => a - b)) + for (const line of listed.get(tier)!) out.push(line); + for (const [k, i] of unlisted.entries()) { + if (k > 0 || listed.size > 0) out.push(""); + out.push(groups[i] ?? PARTITION_GROUP_END, fnDefs[i]!); + } +} + export function emitModuleParts(emitter: LlEmitter): string[] { // Function bodies first (the literal/unit/fn-value tables fill as they // emit), then the file assembles around them — the runtime ABI’s order. @@ -942,21 +998,24 @@ export function emitModuleParts(emitter: LlEmitter): string[] { ); } if (globals.length > 0) out.push(``); - for (const line of helpers) out.push(line); - for (const line of ffiCallbacks.defs) out.push(line); - for (const line of shapes.defs) out.push(line); - for (const line of classShapes.defs) out.push(line); - for (const line of classObjDefs) out.push(line); - for (const line of emitter.walkers.defs) out.push(line); - for (const line of emitter.dyn.defs) out.push(line); - for (const line of wrappers) out.push(line); - for (const line of asyncDefs) out.push(line); - for (const line of emitter.resolveThunkDefs) out.push(line); - const groups = partitionGroupLines(emitter.mod.functions); - for (let i = 0; i < fnDefs.length; i++) { - if (i > 0) out.push(""); - out.push(groups[i] ?? PARTITION_GROUP_END, fnDefs[i]!); - } + writeDefinitions( + out, + emitter.targetOptions.functionLayout, + [ + helpers, + ffiCallbacks.defs, + shapes.defs, + classShapes.defs, + classObjDefs, + emitter.walkers.defs, + emitter.dyn.defs, + wrappers, + asyncDefs, + emitter.resolveThunkDefs, + ], + fnDefs, + partitionGroupLines(emitter.mod.functions), + ); if (fnDefs.length === 0) out.push(""); out.push(PARTITION_GROUP_END, ""); // WebAssembly modules translated into this program, and the registry. diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 5569599ee..0b0bf11e0 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1,4 +1,5 @@ import type { TargetCpu } from "../target-cpu.js"; +import type { FunctionLayout } from "./function-layout.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; import { wasmBoundsChecks } from "./wasm-modules.js"; import { jsTruthy } from "./jsval.js"; @@ -487,6 +488,8 @@ export interface LlvmTargetOptions { /** The host's process primitives, where it can fork (the native * compiler's). */ forkPrimitives?: ForkPrimitives | undefined; + /** Functions to write first, by tier (function-layout.ts). */ + functionLayout?: FunctionLayout; /** Exact frontend sources for dev-build locations and variable metadata. */ debugSources?: ReadonlyMap; /** Pointer width of the target C ABI. Native targets are 64-bit today. */ diff --git a/packages/compiler/src/backend/llvm/function-layout.ts b/packages/compiler/src/backend/llvm/function-layout.ts new file mode 100644 index 000000000..00cf233db --- /dev/null +++ b/packages/compiler/src/backend/llvm/function-layout.ts @@ -0,0 +1,41 @@ +/** Function layouts: the functions that runs of a program execute, in tiers + * (scripts/refresh-cli-layout.mjs writes the native compiler's). The emitter + * writes a listed function before the others, in a partition group of its + * tier, so the code that runs together sits together in the executable. */ + +/** Layout key → tier (1 first). */ +export type FunctionLayout = ReadonlyMap; + +/** The layout key of an LLVM symbol: module numbers (`%m12.`, `%init.12`), + * which change when modules are added, are left out. */ +export function functionLayoutKey(symbol: string): string { + return symbol.replace(/_x25_m\d+_/g, "_x25_m_").replace(/_x25_init_\d+$/, "_x25_init_"); +} + +/** Reads a layout's lines, each ` `. A function listed in two + * tiers keeps the first. */ +export function parseFunctionLayout(text: string): FunctionLayout { + const layout = new Map(); + for (const line of text.split("\n")) { + const space = line.indexOf(" "); + if (space < 1) continue; + const tier = Number(line.slice(0, space)); + const key = functionLayoutKey(line.slice(space + 1).trim()); + if (Number.isInteger(tier) && tier > 0 && !layout.has(key)) layout.set(key, tier); + } + return layout; +} + +/** The lowest tier of the functions `text` defines, or undefined when the + * layout lists none of them. */ +export function layoutTierOf(layout: FunctionLayout, text: string): number | undefined { + let tier: number | undefined; + for (let at = text.indexOf("define "); at >= 0; at = text.indexOf("\ndefine ", at + 1)) { + const name = text.indexOf("@", at); + const open = text.indexOf("(", name); + if (name < 0 || open < 0) break; + const listed = layout.get(functionLayoutKey(text.slice(name + 1, open))); + if (listed !== undefined && (tier === undefined || listed < tier)) tier = listed; + } + return tier; +} diff --git a/packages/compiler/src/compile-types.ts b/packages/compiler/src/compile-types.ts index 8ec7b5ffd..2703e008b 100644 --- a/packages/compiler/src/compile-types.ts +++ b/packages/compiler/src/compile-types.ts @@ -54,6 +54,11 @@ export interface CompileBaseOptions { * merge-profiles`). Code the profile does not describe, or describes * from an older version of the program, is optimized as without one. */ profileUse?: string; + /** A function layout (scripts/refresh-cli-layout.mjs; gzip-compressed when + * the name ends in .gz): the listed functions are written first, by tier, + * so the code that runs together sits together in the executable. The + * native compiler's build uses it. Executables and LLVM output. */ + functionLayout?: string; /** Eligible packages compile automatically. Explicit names override automatic selection heuristics. */ npmStatic?: readonly string[] | "auto"; diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index ffd56a1bf..ace268af7 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -48,6 +48,7 @@ export type { import { compilePackedLibrary } from "./backend/library-pack.js"; import { node24FetchCompatProfile } from "./compat/fetch-profile.js"; import { libFnSignatures } from "./ir/builtin-signatures.js"; +import { type FunctionLayout, parseFunctionLayout } from "./backend/llvm/function-layout.js"; import { InternalCompilerError } from "./errors.js"; import { frontendProcessExecArgv } from "./frontend-process-args.js"; import { type ChildProcess, spawn } from "node:child_process"; @@ -57,6 +58,7 @@ import { copyFile, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node: import { tmpdir } from "node:os"; import { basename, dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { gunzipSync } from "node:zlib"; import { clearCcCaches, configuredTargetPlatform, @@ -774,6 +776,20 @@ async function readNativeProfile( return { use: { path, digest: createHash("sha256").update(bytes).digest("hex") } }; } +/** The request's function layout. */ +async function readFunctionLayout( + opts: CompileRequestOptions, +): Promise<{ functionLayout?: FunctionLayout }> { + if (opts.functionLayout === undefined) return {}; + const path = resolve(opts.functionLayout); + const bytes = await readFile(path); + return { + functionLayout: parseFunctionLayout( + (path.endsWith(".gz") ? gunzipSync(bytes) : bytes).toString("utf8"), + ), + }; +} + function usesPrecompiledRuntimePack(opts: CompileRequestOptions, backend: "llvm"): boolean { if (backend !== "llvm" || opts.sanitize === true) return false; return nativeCodegenTarget() !== null; @@ -847,6 +863,7 @@ async function prepareExecutableInput( llvm = emitLlvmModuleSource(mod, { targetTriple: process.env["SCRIPTC_TARGET"] ?? "", ...debugOptions, + ...(await readFunctionLayout(opts)), pointerBits: buildPlatform === "wasi" ? 32 : 64, wasi: buildPlatform === "wasi", runtimeAbiMarker: outputKind === "obj", @@ -920,6 +937,7 @@ async function prepareExecutableInput( llvmSource = emitLlvmModuleSource(mod, { targetTriple: process.env["SCRIPTC_TARGET"] ?? "", ...debugOptions, + ...(await readFunctionLayout(opts)), pointerBits: buildPlatform === "wasi" ? 32 : 64, wasi: buildPlatform === "wasi", runtimeAbiMarker: useRuntimePack, diff --git a/packages/compiler/test/function-layout.test.ts b/packages/compiler/test/function-layout.test.ts new file mode 100644 index 000000000..8619c5b34 --- /dev/null +++ b/packages/compiler/test/function-layout.test.ts @@ -0,0 +1,147 @@ +import { execFileSync } from "node:child_process"; +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { gzipSync } from "node:zlib"; +import { expect, test } from "vitest"; +import { compile } from "../src/index.js"; +import { + functionLayoutKey, + layoutTierOf, + parseFunctionLayout, +} from "../src/backend/llvm/function-layout.js"; + +const source = ` +function first(n: number): number { return n + 1; } +function second(n: number): number { return n * 2; } +function third(n: number): number { return n - 3; } +console.log(first(Number(process.argv.length)), second(4), third(5)); +`; + +async function emitLlvm(layout?: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-function-layout-")); + try { + const entry = join(dir, "main.ts"); + const outPath = join(dir, "main.ll"); + await writeFile(entry, source); + let functionLayout: string | undefined; + if (layout !== undefined) { + functionLayout = join(dir, "layout.gz"); + await writeFile(functionLayout, gzipSync(layout)); + } + const result = await compile(entry, { + outDir: dir, + outPath, + outputKind: "llvm", + ...(functionLayout === undefined ? {} : { functionLayout }), + }); + expect(result.ok, JSON.stringify(result.diagnostics)).toBe(true); + return await readFile(outPath, "utf8"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +/** Each defined function's name with the partition group line it follows. */ +function definitions(llvm: string): { name: string; group: string }[] { + const out: { name: string; group: string }[] = []; + let group = ""; + for (const line of llvm.split("\n")) { + if (line.startsWith("; scriptc-group")) group = line.slice("; scriptc-group".length).trim(); + const name = /^define [^@]*@([\w.$]+)\(/.exec(line)?.[1]; + if (name !== undefined) out.push({ name, group }); + } + return out; +} + +test("a layout writes the functions it lists first, by tier, in their tier's group", async () => { + const plain = definitions(await emitLlvm()).filter((d) => /_(first|second|third)$/.test(d.name)); + expect(plain.map((d) => d.name.replace(/^sc_b?f_/, ""))).toEqual(["first", "second", "third"]); + expect(plain.every((d) => !d.group.startsWith("layout-"))).toBe(true); + + const names = new Map(plain.map((d) => [d.name.replace(/^sc_b?f_/, ""), d.name])); + const laid = definitions(await emitLlvm(`2 ${names.get("first")}\n1 ${names.get("third")}\n`)); + const index = (fn: string) => laid.findIndex((d) => d.name === names.get(fn)); + expect(index("third")).toBeLessThan(index("first")); + expect(index("first")).toBeLessThan(index("second")); + expect(laid[index("third")]!.group).toBe("layout-1"); + expect(laid[index("first")]!.group).toBe("layout-2"); + expect(laid[index("second")]!.group).not.toMatch(/^layout-/); + // Listed functions precede every unlisted definition in the function section. + const firstUnlisted = laid.findIndex( + (d, i) => i > index("third") && !d.group.startsWith("layout-"), + ); + expect(firstUnlisted).toBeGreaterThan(index("first")); +}); + +test("layout keys leave out module numbers", () => { + expect(functionLayoutKey("sc_bf__x25_m447_lowerCall")).toBe( + functionLayoutKey("sc_bf__x25_m12_lowerCall"), + ); + expect(functionLayoutKey("sc_f__x25_init_253")).toBe(functionLayoutKey("sc_f__x25_init_7")); + expect(functionLayoutKey("sc_bf__x25_m3_Lexer_scan")).not.toBe( + functionLayoutKey("sc_bf__x25_m3_Lexer_peek"), + ); +}); + +test("a function listed twice keeps its first tier, and malformed lines are skipped", () => { + const layout = parseFunctionLayout("1 sc_f_a\n2 sc_f_a\n2 sc_f_b\nnot a line\n0 sc_f_c\n\n"); + expect([...layout]).toEqual([ + ["sc_f_a", 1], + ["sc_f_b", 2], + ]); + const text = + "define internal void @sc_f_b() {\n ret void\n}\n\ndefine void @sc_f_a() {\n ret void\n}"; + expect(layoutTierOf(layout, text)).toBe(1); + expect(layoutTierOf(layout, "define void @sc_f_z() {\n ret void\n}")).toBeUndefined(); +}); + +test("an executable built with a layout that lists helpers and functions runs unchanged", async () => { + const program = ` +class Point { constructor(public x: number, public y: number) {} } +function norm(p: Point): number { return Math.sqrt(p.x * p.x + p.y * p.y); } +const points = [new Point(3, 4), new Point(6, 8)]; +console.log(points.map(norm).join(","), points[0]!.x === 3 ? "three" : "other"); +`; + const dir = await mkdtemp(join(tmpdir(), "scriptc-function-layout-exe-")); + try { + const entry = join(dir, "main.ts"); + await writeFile(entry, program); + const llvmPath = join(dir, "main.ll"); + const emitted = await compile(entry, { outDir: dir, outPath: llvmPath, outputKind: "llvm" }); + expect(emitted.ok, JSON.stringify(emitted.diagnostics)).toBe(true); + // The generated helpers written before the functions, and one function, + // in two tiers. + const names = definitions(await readFile(llvmPath, "utf8")).map((d) => d.name); + const helpers = names.slice( + 0, + names.findIndex((name) => /^sc_b?f_/.test(name)), + ); + expect(helpers.length).toBeGreaterThan(0); + const norm = names.find((name) => /^sc_b?f_norm$/.test(name))!; + const layout = join(dir, "layout"); + await writeFile(layout, [`1 ${norm}`, ...helpers.map((name) => `2 ${name}`)].join("\n")); + const laid = join(dir, "laid.ll"); + const laidOut = await compile(entry, { + outDir: dir, + outPath: laid, + outputKind: "llvm", + functionLayout: layout, + }); + expect(laidOut.ok, JSON.stringify(laidOut.diagnostics)).toBe(true); + const order = definitions(await readFile(laid, "utf8")); + expect(order.filter((d) => d.group === "layout-2").map((d) => d.name)).toEqual(helpers); + expect(order.find((d) => d.name === norm)!.group).toBe("layout-1"); + const exe = join(dir, "main"); + const built = await compile(entry, { + outputKind: "exe", + outDir: dir, + outPath: exe, + functionLayout: layout, + }); + expect(built.ok, JSON.stringify(built.diagnostics)).toBe(true); + expect(execFileSync(exe, { encoding: "utf8" })).toBe("5,10 three\n"); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/scripts/build-native-cli.mts b/scripts/build-native-cli.mts index 4d3f00ff5..a26c50209 100644 --- a/scripts/build-native-cli.mts +++ b/scripts/build-native-cli.mts @@ -35,6 +35,15 @@ const useProfile = !sanitize && requestedProfile !== "" && (requestedProfile !== undefined || existsSync(committedProfile)); +// Builds lay out the compiler's functions with the committed layout +// (scripts/refresh-cli-layout.mjs), or the one SCRIPTC_NATIVE_LAYOUT names; +// an empty SCRIPTC_NATIVE_LAYOUT, and instrumented builds, use none. +const committedLayout = join(root, "packages/compiler/native/cli.layout.gz"); +const requestedLayout = process.env["SCRIPTC_NATIVE_LAYOUT"]; +const functionLayout = + profileGenerate !== undefined || requestedLayout === "" + ? undefined + : (requestedLayout ?? (existsSync(committedLayout) ? committedLayout : undefined)); let target = nativeCodegenTarget(); if (target === null || target.platform === "wasi") throw new Error("a supported native host is required to build the compiler"); @@ -124,6 +133,7 @@ const result = await compile(join(root, "packages/compiler/src/native/cli.ts"), sanitize, ...(profileGenerate === undefined ? {} : { profileGenerate }), ...(profileUse === undefined ? {} : { profileUse }), + ...(functionLayout === undefined ? {} : { functionLayout }), emitIr: process.env["SCRIPTC_NATIVE_EMIT_IR"] === "1", }); if (!result.ok) diff --git a/scripts/refresh-cli-layout.mjs b/scripts/refresh-cli-layout.mjs new file mode 100644 index 000000000..d401ac62b --- /dev/null +++ b/scripts/refresh-cli-layout.mjs @@ -0,0 +1,248 @@ +#!/usr/bin/env node +/* Refreshes packages/compiler/native/cli.layout.gz, the function layout that + * builds of the native compiler use (scripts/build-native-cli.mts). The + * layout lists, in two tiers, the functions the compiler runs: tier 1 for + * `--version` and rebuilds of unchanged programs, tier 2 for the other + * builds. The emitter writes the listed functions first, so the code a build + * runs sits together in the compiler's executable and a run maps fewer of its + * pages. + * + * Builds an instrumented native compiler for this Linux host (or reuses one + * with --instrumented), builds each training program cold and then again + * unchanged, and reads the functions each kind of run executed from the raw + * profiles. The training programs are the runtime benchmark workloads at + * `speed` and every seventh corpus program at the default optimization. A + * refresh takes about 15 minutes on 16 cores, most of it for the instrumented + * build. A stale layout lists some functions that no longer exist and misses + * new ones, which then keep their usual order. + * + * node scripts/refresh-cli-layout.mjs [--jobs 8] [--instrumented ] + */ +import { spawn, spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { + appendFileSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, + statSync, + writeFileSync, +} from "node:fs"; +import { cpus, tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; +import { constants, gzipSync, inflateSync } from "node:zlib"; + +const root = fileURLToPath(new URL("..", import.meta.url)); +const { values } = parseArgs({ + options: { + jobs: { type: "string", default: String(Math.max(1, Math.floor(cpus().length / 2))) }, + output: { type: "string", default: join(root, "packages/compiler/native/cli.layout.gz") }, + work: { type: "string" }, + instrumented: { type: "string" }, + help: { type: "boolean", default: false }, + }, +}); +if (values.help) { + console.log( + "Usage: node scripts/refresh-cli-layout.mjs [--jobs N] [--output ] [--work

] [--instrumented ]", + ); + process.exit(0); +} +if (process.platform !== "linux" || process.arch !== "x64") + throw new Error("the compiler layout is collected on Linux x64"); +const jobs = Number(values.jobs); +const output = resolve(values.output); +mkdirSync(dirname(output), { recursive: true }); +const work = resolve(values.work ?? mkdtempSync(join(tmpdir(), "scriptc-cli-layout-"))); +const raw = join(work, "raw"); +const builds = join(work, "builds"); +rmSync(raw, { recursive: true, force: true }); +for (const dir of [raw, builds]) mkdirSync(dir, { recursive: true }); +const log = join(work, "training.log"); + +const cli = resolve(values.instrumented ?? join(work, "cli")); +if (values.instrumented === undefined) { + console.log(`building the instrumented compiler in ${cli}`); + const built = spawnSync( + process.execPath, + [ + "--max-old-space-size=8192", + "--import", + "tsx", + join(root, "scripts/build-native-cli.mts"), + cli, + ], + { + cwd: root, + stdio: "inherit", + env: { ...process.env, SCRIPTC_NATIVE_PROFILE_GENERATE: join(raw, "build-%4m.profraw") }, + }, + ); + if (built.status !== 0) throw new Error("the instrumented compiler did not build"); +} +const scriptc = join(cli, "bin/scriptc"); +// Phase timing shows whether a rebuild hit both caches. It is on for every +// run, so the rebuild's environment matches the first build's. +const baseEnv = { ...process.env, SCRIPTC_TIMING: "1" }; +delete baseEnv["SCRIPTC_LLVM_HELPER"]; + +/** Runs the instrumented compiler, writing its profile to `options.profile`. */ +function run(args, options) { + return new Promise((done) => { + const child = spawn(scriptc, args, { + cwd: options.cwd, + stdio: ["ignore", "pipe", "pipe"], + env: { + ...baseEnv, + SCRIPTC_CACHE_DIR: options.cache, + LLVM_PROFILE_FILE: options.profile, + }, + }); + let text = ""; + child.stdout.on("data", (chunk) => (text += chunk)); + child.stderr.on("data", (chunk) => (text += chunk)); + child.on("close", (code) => { + appendFileSync(log, `$ scriptc ${args.join(" ")}\n${text}exit ${code}\n`); + done({ ok: code === 0, text }); + }); + }); +} + +async function pool(tasks) { + let next = 0; + let passed = 0; + await Promise.all( + Array.from({ length: jobs }, async () => { + while (next < tasks.length) if (await tasks[next++]()) passed++; + }), + ); + return passed; +} + +const programNumber = (name) => Number.parseInt(name, 10); +const training = [ + ...readdirSync(join(root, "benchmarks/runtime/workloads")) + .filter((name) => name.endsWith(".ts")) + .map((name) => ({ + file: join(root, "benchmarks/runtime/workloads", name), + optimization: "speed", + })), + ...readdirSync(join(root, "tests/corpus")) + .filter((name) => /^\d+-.*\.ts$/.test(name) && programNumber(name) % 7 === 0) + .map((name) => ({ file: join(root, "tests/corpus", name), optimization: "release" })), +]; + +// Tier 1 collects each run that took the warm path from its own profile; a +// rebuild that missed a cache (it shows no `frontend-cache-hit` or +// `executable-cache-hit` phase) is left out. Tier 2 pools the first builds. +const warm = new Set(); +const cold = new Set(); +let skipped = 0; +async function warmRun(args, cache, name, cacheHits) { + const profile = join(raw, `warm-${name}.profraw`); + const { ok, text } = await run(args, { cwd: root, cache, profile }); + if (ok && cacheHits.every((phase) => text.includes(`"phase":"${phase}"`))) + for (const fn of executedFunctions(profile)) warm.add(fn); + else skipped++; + rmSync(profile, { force: true }); +} + +const started = Date.now(); +await warmRun(["--version"], join(work, "cache-version"), "version", []); +console.log(`training: ${training.length} programs, cold then unchanged, ${jobs} at a time`); +const passed = await pool( + training.map((program, index) => async () => { + const cache = join(work, `cache-${index}`); + const out = join(builds, `program-${index}`); + const args = ["build", program.file, `--optimization=${program.optimization}`, "-o", out]; + const { ok } = await run(args, { cwd: root, cache, profile: join(raw, "cold-%4m.profraw") }); + if (ok) + await warmRun(args, cache, String(index), ["frontend-cache-hit", "executable-cache-hit"]); + rmSync(cache, { recursive: true, force: true }); + rmSync(out, { force: true }); + return ok; + }), +); +console.log( + `trained in ${Math.round((Date.now() - started) / 1000)} s; ${passed} of ${training.length} programs built, ${skipped} rebuilds left out of tier 1 (see ${log})`, +); + +/** Functions a raw profile (LLVM's format version 10) records as run: the + * names whose counters are not all zero, without the file prefix of + * internal functions. */ +function executedFunctions(path) { + const buf = readFileSync(path); + const u64 = (offset) => Number(buf.readBigUInt64LE(offset)); + if (buf.readBigUInt64LE(0) !== 0xff6c70726f667281n) + throw new Error(`${path} is not a 64-bit raw profile`); + const version = Number(buf.readBigUInt64LE(8) & 0xffffffffn); + if (version !== 10) throw new Error(`${path} has raw profile version ${version}, expected 10`); + const [binaryIdsBytes, records, beforeCounters, counters, afterCounters] = [2, 3, 4, 5, 6].map( + (field) => u64(field * 8), + ); + const [bitmapBytes, afterBitmap, namesBytes] = [7, 8, 9].map((field) => u64(field * 8)); + const valueKinds = u64(15 * 8) + 1; + const recordBytes = Math.ceil((6 * 8 + 4 + 2 * valueKinds + 4) / 8) * 8; + const dataStart = 16 * 8 + binaryIdsBytes; + const countersStart = dataStart + records * recordBytes + beforeCounters; + const namesStart = countersStart + counters * 8 + afterCounters + bitmapBytes + afterBitmap; + const countersDelta = buf.readBigInt64LE(10 * 8); + const names = new Map(); + let at = namesStart; + const uleb = () => { + let value = 0; + let scale = 1; + for (;;) { + const byte = buf[at++]; + value += (byte & 0x7f) * scale; + if ((byte & 0x80) === 0) return value; + scale *= 128; + } + }; + while (at < namesStart + namesBytes) { + const plain = uleb(); + const packed = uleb(); + if (plain === 0) break; + const bytes = + packed === 0 ? buf.subarray(at, at + plain) : inflateSync(buf.subarray(at, at + packed)); + at += packed === 0 ? plain : packed; + for (const name of bytes.toString("latin1").split("\x01")) + names.set(createHash("md5").update(name, "latin1").digest().readBigUInt64LE(0), name); + } + const ran = []; + for (let index = 0; index < records; index++) { + const record = dataStart + index * recordBytes; + const counterOffset = Number( + buf.readBigInt64LE(record + 16) - (countersDelta - BigInt(index * recordBytes)), + ); + const count = buf.readUInt32LE(record + 48); + for (let c = 0; c < count; c++) { + if (buf.readBigUInt64LE(countersStart + counterOffset + c * 8) !== 0n) { + const name = names.get(buf.readBigUInt64LE(record)); + if (name !== undefined) ran.push(name.slice(name.indexOf(";") + 1)); + break; + } + } + } + return ran; +} + +for (const name of readdirSync(raw)) + if (name.startsWith("cold-")) for (const fn of executedFunctions(join(raw, name))) cold.add(fn); +const lines = [ + ...[...warm].sort().map((name) => `1 ${name}`), + ...[...cold] + .filter((name) => !warm.has(name)) + .sort() + .map((name) => `2 ${name}`), +]; +writeFileSync(output, gzipSync(`${lines.join("\n")}\n`, { level: constants.Z_BEST_COMPRESSION })); +console.log( + `wrote ${output}: ${warm.size} tier-1 and ${lines.length - warm.size} tier-2 functions (${statSync(output).size} bytes)`, +); +rmSync(builds, { recursive: true, force: true }); +if (values.work === undefined) rmSync(work, { recursive: true, force: true }); From 5a3d6d79037bf839e50f70adc828e7c6625b658f Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sat, 24 Oct 2026 14:53:54 -0700 Subject: [PATCH 128/230] Call function tables without boxing or reference counting Values typed `F | undefined` for a function type F are now plain closure pointers (nullable-pointer unions), so a keyed read of a `Record` or a Map.get no longer allocates a union box. Keyed record reads, string-keyed Map.get and checked extractions lend the stored closure as a receiver or callee when ReferenceWrites proves the call cannot write the locations it was read from, and optional-chain receivers lend under the same proof. Owned record overflow reads use the inline entry lookup. Indirect closure calls skip the ambient receiver bracket for closures whose bodies never observe it (new ScrClosure.function_kind bit SCR_FN_RECEIVER_FREE; signature adapters that only forward the receiver copy it from their original), and a callValue that forwards `dyn.this` needs no bracket at all. Co-Authored-By: Claude Opus 5.5 --- benchmarks/runtime/README.md | 1 + .../runtime/workloads/handler-tables.json | 6 + .../runtime/workloads/handler-tables.ts | 108 ++++++++++++++++ .../src/backend/llvm/checked-narrows.ts | 9 +- packages/compiler/src/backend/llvm/common.ts | 4 + .../backend/llvm/constant-callbacks.test.ts | 41 +++++++ packages/compiler/src/backend/llvm/emitter.ts | 61 ++++++++-- .../src/backend/llvm/expr-callbacks.ts | 20 ++- .../compiler/src/backend/llvm/expr-calls.ts | 115 ++++++++++++++++-- .../compiler/src/backend/llvm/expr-control.ts | 33 +++-- .../compiler/src/backend/llvm/expr-dynamic.ts | 6 +- .../compiler/src/backend/llvm/expr-records.ts | 107 ++++++++++++---- .../compiler/src/backend/llvm/lib-http2.ts | 5 +- .../compiler/src/backend/llvm/lib-network.ts | 2 +- .../src/backend/llvm/map-read-lifetimes.ts | 64 ++++++++++ .../src/backend/llvm/nullable-unions.test.ts | 21 +++- .../src/backend/llvm/nullable-unions.ts | 22 +++- .../compiler/src/backend/llvm/receivers.ts | 40 +++++- .../src/backend/llvm/reference-writes.test.ts | 43 +++++++ .../src/backend/llvm/reference-writes.ts | 13 ++ .../src/backend/llvm/stack-callbacks.ts | 6 +- packages/compiler/src/ir/receiver-readers.ts | 92 ++++++++++---- .../8230-function-record-dispatch.ts.json | 6 + .../corpus/8231-function-table-shapes.ts.json | 6 + .../8232-optional-function-values.ts.json | 6 + packages/runtime/src/scr_closure.h | 9 +- packages/runtime/src/scr_json.c | 9 +- tests/corpus/8230-function-record-dispatch.ts | 107 ++++++++++++++++ tests/corpus/8231-function-table-shapes.ts | 108 ++++++++++++++++ tests/corpus/8232-optional-function-values.ts | 75 ++++++++++++ .../8230-function-record-dispatch.ts.json | 1 + .../8231-function-table-shapes.ts.json | 1 + .../8232-optional-function-values.ts.json | 1 + 33 files changed, 1042 insertions(+), 106 deletions(-) create mode 100644 benchmarks/runtime/workloads/handler-tables.json create mode 100644 benchmarks/runtime/workloads/handler-tables.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-function-record-dispatch.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-function-table-shapes.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-optional-function-values.ts.json create mode 100644 tests/corpus/8230-function-record-dispatch.ts create mode 100644 tests/corpus/8231-function-table-shapes.ts create mode 100644 tests/corpus/8232-optional-function-values.ts create mode 100644 tests/harness/corpus-costs/8230-function-record-dispatch.ts.json create mode 100644 tests/harness/corpus-costs/8231-function-table-shapes.ts.json create mode 100644 tests/harness/corpus-costs/8232-optional-function-values.ts.json diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index af3d3d239..4c6cf45ed 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -16,6 +16,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `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 | | `dynamic-properties` | cpu | untyped JavaScript reading and writing properties of values it does not type: records built by object literals in two key orders, nested records, and instances of a class and its subclass | | `functional-pipeline` | cpu | `filter`/`map`/`reduce`/`flatMap` chains, closures, small records | +| `handler-tables` | cpu | dispatch tables of functions: `Record` read as `t[k]!(…)`, `t[k]?.(…)` and through a checked local, a `Map`, an array of handlers, and an object literal of methods; some handlers declare fewer parameters than the table type | | `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 | | `json-unions` | cpu | `JSON.parse` into discriminated unions: an event log with eight kinds and nested expression trees with nine, evaluated after the cast | diff --git a/benchmarks/runtime/workloads/handler-tables.json b/benchmarks/runtime/workloads/handler-tables.json new file mode 100644 index 000000000..1408ae91e --- /dev/null +++ b/benchmarks/runtime/workloads/handler-tables.json @@ -0,0 +1,6 @@ +{ + "entry": "workloads/handler-tables.ts", + "args": [ + "4" + ] +} diff --git a/benchmarks/runtime/workloads/handler-tables.ts b/benchmarks/runtime/workloads/handler-tables.ts new file mode 100644 index 000000000..b0a61fcfe --- /dev/null +++ b/benchmarks/runtime/workloads/handler-tables.ts @@ -0,0 +1,108 @@ +// Dispatch tables of functions: a `Record` of opcode handlers +// called as `ops[name]!(…)`, `ops[name]?.(…)` and through a checked local, +// a `Map` of the same handlers, an array of handlers indexed by +// opcode number, and an object literal of methods. Some handlers declare +// fewer parameters than the table's function type. +type Op = (acc: number, arg: number) => number; + +const ops: Record = { + add: (acc, arg) => acc + arg, + sub: (acc, arg) => acc - arg, + mul: (acc, arg) => (acc * arg) % 1000003, + neg: (acc) => -acc, + half: (acc) => Math.floor(acc / 2), + mix: (acc, arg) => (acc ^ arg) & 0xffff, +}; + +const byName = new Map(Object.entries(ops)); +const byCode: Op[] = Object.values(ops); +const names = Object.keys(ops); + +const methods = { + add(acc: number, arg: number): number { + return acc + arg; + }, + sub(acc: number, arg: number): number { + return acc - arg; + }, + mul(acc: number, arg: number): number { + return (acc * arg) % 1000003; + }, +}; + +interface Insn { + name: string; + code: number; + arg: number; +} + +function program(length: number): Insn[] { + const out: Insn[] = []; + let seed = 12345; + for (let i = 0; i < length; i++) { + seed = (seed * 1103515245 + 12345) % 2147483648; + const code = seed % (names.length + 1); + // One in seven instructions names an opcode no table holds. + out.push({ name: code < names.length ? names[code]! : "nop", code, arg: (seed >> 8) % 97 }); + } + return out; +} + +function runAsserted(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) if (insn.name !== "nop") acc = ops[insn.name]!(acc, insn.arg); + return acc; +} + +function runOptional(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) acc = ops[insn.name]?.(acc, insn.arg) ?? acc + 1; + return acc; +} + +function runChecked(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) { + const handler = ops[insn.name]; + if (handler) acc = handler(acc, insn.arg); + } + return acc; +} + +function runMap(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) { + const handler = byName.get(insn.name); + if (handler !== undefined) acc = handler(acc, insn.arg); + } + return acc; +} + +function runArray(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) if (insn.code < byCode.length) acc = byCode[insn.code]!(acc, insn.arg); + return acc; +} + +function runMethods(insns: Insn[]): number { + let acc = 1; + for (const insn of insns) { + if (insn.code === 0) acc = methods.add(acc, insn.arg); + else if (insn.code === 1) acc = methods.sub(acc, insn.arg); + else if (insn.code === 2) acc = methods["mul"](acc, insn.arg); + } + return acc; +} + +const rounds = Number(process.argv[2] ?? "4"); +const insns = program(200_000); +const totals = [0, 0, 0, 0, 0, 0]; +for (let round = 0; round < rounds * 10; round++) { + totals[0] = (totals[0]! + runAsserted(insns)) % 1e9; + totals[1] = (totals[1]! + runOptional(insns)) % 1e9; + totals[2] = (totals[2]! + runChecked(insns)) % 1e9; + totals[3] = (totals[3]! + runMap(insns)) % 1e9; + totals[4] = (totals[4]! + runArray(insns)) % 1e9; + totals[5] = (totals[5]! + runMethods(insns)) % 1e9; +} +console.log(totals.join(" ")); diff --git a/packages/compiler/src/backend/llvm/checked-narrows.ts b/packages/compiler/src/backend/llvm/checked-narrows.ts index c1514df1e..5fbaf6d08 100644 --- a/packages/compiler/src/backend/llvm/checked-narrows.ts +++ b/packages/compiler/src/backend/llvm/checked-narrows.ts @@ -101,6 +101,13 @@ export class CheckedNarrows { } } +/** A call to a checked extraction whose helper borrows its argument, so + * emitCheckedNarrowReceiver can lend the payload instead of calling it. */ +export function borrowsCheckedNarrow(host: LlvmEmitterContext, e: IrExpr): boolean { + if (e.kind !== "call" || host.checkedNarrows.get(e) === null) return false; + return host.callLifetimes.borrowed.get(e.callee)?.has(0) === true && host.mayThrow.has(e.callee); +} + /** A checked extraction consumed at once by a field or tag load. The * successful arm borrows the payload from the union, which itself borrows * from its source: a dense array read keeps the element in the array slot, @@ -113,7 +120,7 @@ export function emitCheckedNarrowReceiver( narrow: CheckedNarrowCall, ): LlValue | null { const callee = narrow.call.callee; - if (!host.callLifetimes.borrowed.get(callee)?.has(0) || !host.mayThrow.has(callee)) return null; + if (!borrowsCheckedNarrow(host, narrow.call)) return null; const B = host.B; const read = host.optionalArrayReads.get(narrow.value); const union = read diff --git a/packages/compiler/src/backend/llvm/common.ts b/packages/compiler/src/backend/llvm/common.ts index cca1b603b..f1bdeb557 100644 --- a/packages/compiler/src/backend/llvm/common.ts +++ b/packages/compiler/src/backend/llvm/common.ts @@ -277,6 +277,10 @@ export function emitFieldAbsentTest(B: BlockBuilder, value: string, undefinedTag return absent; } +/** ScrClosure.function_kind's SCR_FN_RECEIVER_FREE bit (scr_closure.h): + * the body never observes the ambient receiver. */ +export const CLOSURE_RECEIVER_FREE = 16; + /** The right-hand side of a JS function-identity comparison: two closures * are one function when their identity roots match (a signature adapter * stands for the function it adapts). Both operands must be closures. */ diff --git a/packages/compiler/src/backend/llvm/constant-callbacks.test.ts b/packages/compiler/src/backend/llvm/constant-callbacks.test.ts index e8ca5571f..8bdab0408 100644 --- a/packages/compiler/src/backend/llvm/constant-callbacks.test.ts +++ b/packages/compiler/src/backend/llvm/constant-callbacks.test.ts @@ -299,6 +299,47 @@ test("receiver readers propagate backwards through direct calls and recursion", ).toBe(false); }); +test("signature adapters that only forward the receiver ignore it with their original", () => { + const forward = (receiver: IrExpr): IrStmt => ({ + kind: "return", + value: { + kind: "callValue", + callee: ref("f.0"), + receiver, + args: [{ kind: "varRef", localId: "a.0", type: F64, loc }], + type: BOOL, + loc, + }, + loc, + }); + const ambient: IrExpr = { kind: "libCall", fn: "dyn.this", args: [], type: { kind: "dyn" }, loc }; + const adapter: IrFunction = { + name: "%fn.adapt.x.impl", + params: [{ localId: "a.0", name: "a", type: F64 }], + captures: [{ localId: "f.0", name: "f", type: callback }], + locals: [ + { id: "f.0", name: "f", type: callback, mutable: false, boxed: true }, + { id: "a.0", name: "a", type: F64, mutable: false }, + ], + returnType: BOOL, + body: [forward(ambient)], + loc, + }; + const readers = (): AmbientReceiverReaders => + new AmbientReceiverReaders(new Map([[adapter.name, adapter]])); + // The adapter itself reads the receiver, only to forward it. + expect(readers().ignores(adapter)).toBe(false); + expect(readers().forwardsOnly(adapter)).toBe(true); + // Any other read of the receiver, or forwarding something else, does not. + adapter.body = [thisRead, forward(ambient)]; + expect(readers().forwardsOnly(adapter)).toBe(false); + adapter.body = [forward(ref("a.0", F64))]; + expect(readers().forwardsOnly(adapter)).toBe(false); + adapter.body = [forward(ambient)]; + adapter.captures = []; + expect(readers().forwardsOnly(adapter)).toBe(false); +}); + test("virtual calls admit every same-named method", () => { const call: IrStmt = { kind: "exprStmt", diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 1a2c8bef8..4c15ae61d 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -183,14 +183,14 @@ import { unionBoxBits, } from "./inline-unions.js"; import { OptionalArrayReads, type LocalArrayRead } from "./local-array-reads.js"; -import { CheckedNarrows } from "./checked-narrows.js"; +import { borrowsCheckedNarrow, CheckedNarrows } from "./checked-narrows.js"; import { emitDenseArraySet, emitDenseSlotUnlink, SCR_ELEM_I32, type ArrayAccess, } from "./dense-array-access.js"; -import { matchMapRead, type MapReadLifetimes } from "./map-read-lifetimes.js"; +import { matchMapRead, pointerMapGet, type MapReadLifetimes } from "./map-read-lifetimes.js"; import { type MapEntryFusion } from "./map-entry-fusion.js"; import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js"; import { emitBorrowedInputs } from "./borrowed-inputs.js"; @@ -213,7 +213,13 @@ import { } from "./string-slices.js"; import { LazyCaptures } from "./lazy-captures.js"; import { StackCaptures } from "./stack-captures.js"; -import { emitFieldAbsentTest, f64Lit, SCR_ARR_FIELD, SCR_ARR_FROZEN } from "./common.js"; +import { + CLOSURE_RECEIVER_FREE, + emitFieldAbsentTest, + f64Lit, + SCR_ARR_FIELD, + SCR_ARR_FROZEN, +} from "./common.js"; import { ffiExtendsNarrowIntegers } from "../targets.js"; import { emitLiteralExpr, @@ -285,7 +291,12 @@ import { emitBytesSet, emitBytesIntrinsic, } from "./expr-bytes.js"; -import { emitRegexIntrinsic, emitRecordKeyGet, keyedRecordReadInto } from "./expr-records.js"; +import { + emitRecordKeyGet, + emitRegexIntrinsic, + keyedRecordReadInto, + pointerRecordKeyGet, +} from "./expr-records.js"; import { dynPromiseAdapter, streamTypedRefCommitAdapter, @@ -2540,7 +2551,7 @@ export class LlEmitter { ret === "void" ? ` ${call}` : ` %r = ${call}`, ret === "void" ? ` ret void` : ` ret ${ret} %r`, `}`, - `@${mangleFnClosure(name)} = internal ${this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : ""}global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null, i32 ${(fn.generator ? 1 : 0) + (fn.async ? 2 : 0) + (fn.ownsPrototype ? 4 : 0)}, i32 0, ptr null, ptr ${lending ? `@${mangleBorrowingWrapper(name)}` : "null"} }`, + `@${mangleFnClosure(name)} = internal ${this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : ""}global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null, i32 ${this.closureFunctionKind(fn)}, i32 0, ptr null, ptr ${lending ? `@${mangleBorrowingWrapper(name)}` : "null"} }`, ``, ); if (lending) @@ -5715,7 +5726,10 @@ export class LlEmitter { unwrittenRead(e: IrExpr, writes: Writes): boolean { const rw = this.referenceWrites; if (rw === null) return false; - const keys = rw.readKeys(e, (call) => this.borrowedReturnKeys(call.callee)); + // A checked extraction reads nothing besides the union it narrows. + const keys = rw.readKeys(e, (call) => + borrowsCheckedNarrow(this, call) ? [] : this.borrowedReturnKeys(call.callee), + ); return keys !== null && !rw.conflicts(keys, writes); } @@ -5992,7 +6006,21 @@ export class LlEmitter { } case "fieldGet": case "recordGet": + case "recordKeyGet": return this.borrowsWithoutOwning(e) && this.unownedWalk(e.obj); + case "mapIntrinsic": { + // A Map binding's own pointer (maps are not plainReference types). + if (!this.borrowsWithoutOwning(e) || e.receiver.kind !== "varRef") return false; + const binding = this.binding(e.receiver.localId); + return ( + binding.kind === "global" || + (binding.kind === "local" && + binding.local !== undefined && + !binding.local.boxed && + !binding.local.tdz && + !this.localUnionStorage.has(e.receiver.localId)) + ); + } case "unionNarrow": case "downcast": case "upcast": @@ -7244,6 +7272,11 @@ export class LlEmitter { return this.nullableFieldGet(e) === null; case "recordGet": return true; + // A keyed read whose hits are all the result's own pointer. + case "recordKeyGet": + return pointerRecordKeyGet(this, e); + case "mapIntrinsic": + return pointerMapGet(this, e); // emitReadReceiver lends a required element of a borrowed array when // computing the index removes no edge. case "arrayGet": @@ -7389,8 +7422,20 @@ export class LlEmitter { return wrapEmitterListener(this, target, adapterFn); } - unwrapNullableClosure(u: string, funcTag: number): string { - return unwrapNullableClosure(this, u, funcTag); + /** ScrClosure.function_kind for a closure over `fn`: generator 1, async + * 2, own prototype 4, and SCR_FN_RECEIVER_FREE (16) when its body never + * observes the ambient receiver, so indirect callers skip the bracket. */ + closureFunctionKind(fn: IrFunction): number { + return ( + (fn.generator ? 1 : 0) + + (fn.async ? 2 : 0) + + (fn.ownsPrototype ? 4 : 0) + + (this.receiverReaders.ignores(fn) ? CLOSURE_RECEIVER_FREE : 0) + ); + } + + unwrapNullableClosure(u: string, unionId: string, funcTag: number): string { + return unwrapNullableClosure(this, u, unionId, funcTag); } closeBindThunkFor(cbUnion: IrType, retServer: boolean): string { diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index 398aebb21..ad827c4bf 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -974,10 +974,20 @@ export function wrapEmitterListener( export function unwrapNullableClosure( host: LlvmEmitterContext, u: string, + unionId: string, funcTag: number, ): string { const B = host.B; host.declare(`declare ptr @scr_closure_retain_v(ptr)`); + const nullable = host.nullableUnions.get(unionId); + if (nullable !== null) { + // The union is the closure pointer itself (NULL for the unit arm). + if (nullable.repr !== "single" || nullable.nullTag >= 0 || nullable.refTag !== funcTag) + throw new InternalCompilerError("llvm emitter bug: nullable callback union shape"); + const r = B.tmp(); + B.line(`${r} = call ptr ${retainSym(host.shapeHost, { kind: "union", unionId })}(ptr ${u})`); + return r; + } const slot = B.slot(); B.entryAllocas.push(`${slot} = alloca ptr`); B.line(`store ptr null, ptr ${slot}`); @@ -1026,9 +1036,9 @@ export function closeBindThunkFor( "llvm emitter bug: bound-close callback union lacks its func arm", ); const oneParam = funcArm.params.length === 1; + const cbNullable = host.nullableUnions.get(cbUnion.unionId); host.declare(`declare ptr @scr_box_get_ref(ptr)`); host.declare(`declare void @scr_closure_release(ptr)`); - host.declare(`declare void @scr_union_release(ptr)`); host.declare(`declare ptr @scr_closure_retain_v(ptr)`); host.declare(`declare void @scr_net_server_close_direct(ptr, ptr)`); let trampoline: string | null = null; @@ -1072,13 +1082,11 @@ export function closeBindThunkFor( ` %capsp = getelementptr inbounds %ScrClosure, ptr %self, i64 1`, ` %bx = load ptr, ptr %capsp`, ` %srv = call ptr @scr_box_get_ref(ptr %bx) ; +1`, - ` %tagp = getelementptr inbounds %ScrUnion, ptr %cbu, i64 0, i32 1`, - ` %tag = load i32, ptr %tagp`, + ...unionTagLines(cbNullable, "%cbu", "%tag"), ` %isfn = icmp eq i32 %tag, ${funcTag}`, ` br i1 %isfn, label %fn, label %go`, `fn:`, - ` %pp = getelementptr inbounds %ScrUnion, ptr %cbu, i64 0, i32 5`, - ` %pv = load ptr, ptr %pp`, + ...unionPeekLines(cbNullable, "%cbu", "%pv"), ]; if (oneParam) { d.push( @@ -1097,7 +1105,7 @@ export function closeBindThunkFor( ` br label %go`, `go:`, ` %regv = load ptr, ptr %regslot`, - ` call void @scr_union_release(ptr %cbu) ; the callee owns its +1 param`, + ` call void ${releaseSym(host.shapeHost, cbUnion)}(ptr %cbu) ; the callee owns its +1 param`, ` call void @scr_net_server_close_direct(ptr %srv, ptr %regv) ; reg moves`, ); if (retServer) { diff --git a/packages/compiler/src/backend/llvm/expr-calls.ts b/packages/compiler/src/backend/llvm/expr-calls.ts index d411d9172..3ecf33a79 100644 --- a/packages/compiler/src/backend/llvm/expr-calls.ts +++ b/packages/compiler/src/backend/llvm/expr-calls.ts @@ -42,7 +42,13 @@ import { } from "./classes.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext, ExprOf, LlValue } from "./expr-context.js"; -import { f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl } from "./common.js"; +import { + CLOSURE_RECEIVER_FREE, + f64Lit, + ffiNativeTypeLl, + ffiNativeParamLl, + ffiNativeReturnLl, +} from "./common.js"; import { canStackUnion, emitStackUnion } from "./stack-unions.js"; import { emitStackRecordKeyGet, scalarRecordKeyGet } from "./expr-records.js"; import { emitCallArrayRead } from "./local-array-reads.js"; @@ -761,8 +767,7 @@ export function emitCallExpr( `${c} = call ptr @scr_closure_new(ptr @${host.callTarget(e.fnName)}, ${host.sizeType} ${e.captures.length})`, ); const out = host.own({ name: c, type: e.type }); - const functionKind = - (target.generator ? 1 : 0) + (target.async ? 2 : 0) + (target.ownsPrototype ? 4 : 0); + const functionKind = host.closureFunctionKind(target); if (functionKind) { const kindPtr = B.tmp(); B.line(`${kindPtr} = getelementptr inbounds %ScrClosure, ptr ${c}, i64 0, i32 4`); @@ -800,6 +805,25 @@ export function emitCallExpr( : 0; host.declare(`declare void @scr_closure_adopt_identity(ptr, ptr, i32)`); B.line(`call void @scr_closure_adopt_identity(ptr ${c}, ptr ${original}, i32 ${length})`); + if (host.receiverReaders.forwardsOnly(target)) { + // The adapter forwards the ambient receiver to its original, + // so it ignores the receiver exactly when the original does. + const fromPtr = B.tmp(), + from = B.tmp(), + bit = B.tmp(), + toPtr = B.tmp(), + to = B.tmp(), + kind = B.tmp(); + B.line( + `${fromPtr} = getelementptr inbounds %ScrClosure, ptr ${original}, i64 0, i32 4`, + ); + B.line(`${from} = load i32, ptr ${fromPtr}`); + B.line(`${bit} = and i32 ${from}, ${CLOSURE_RECEIVER_FREE}`); + B.line(`${toPtr} = getelementptr inbounds %ScrClosure, ptr ${c}, i64 0, i32 4`); + B.line(`${to} = load i32, ptr ${toPtr}`); + B.line(`${kind} = or i32 ${to}, ${bit}`); + B.line(`store i32 ${kind}, ptr ${toPtr}`); + } } }); return out; @@ -813,7 +837,18 @@ export function emitCallExpr( const ft = e.callee.type; if (e.args.length !== ft.params.length) throw new LlvmUnsupportedError("callValue:arity", e.loc); - const callee = host.stackCallbacks.emit(host, e.callee) ?? emitBorrowedInput(host, e.callee); + // A receiver that reads the ambient `this` binds what the callee + // would see without a binding: the call forwards it unchanged and + // needs no receiver bracket (signature adapters forward this way). + const forwardsReceiver = + e.receiver?.kind === "libCall" && + e.receiver.fn === "dyn.this" && + e.receiver.args.length === 0; + const callee = + host.stackCallbacks.emit(host, e.callee) ?? + (lendsCallee(host, e, forwardsReceiver) + ? host.emitReadReceiver(e.callee) + : emitBorrowedInput(host, e.callee)); // A class instance bound as the callee's `this` (`h.fn(x)`) is boxed // only if the callee reads it: the receiver window records the // instance (owned by this frame for the whole call) with its typed-ref @@ -825,7 +860,10 @@ export function emitCallExpr( !host.shapeHost.isErrorClass(e.receiver.value.type.className) ? host.emitExpr(e.receiver.value) : null; - const receiver = e.receiver === undefined || lazyReceiver ? null : host.emitExpr(e.receiver); + const receiver = + e.receiver === undefined || lazyReceiver || forwardsReceiver + ? null + : host.emitExpr(e.receiver); const pushReceiver = (): void => { if (lazyReceiver) { const t = lazyReceiver.type; @@ -865,7 +903,7 @@ export function emitCallExpr( ? borrowableInputs(host, e.args, false, host.referenceWrites?.summary(known!.name)) : []; let dynamic = false; - if (!direct && e.receiver === undefined) { + if (!direct && (e.receiver === undefined || forwardsReceiver)) { const references = new Set(); ft.params.forEach((param, index) => { if (isRefCounted(param)) references.add(index); @@ -898,16 +936,51 @@ export function emitCallExpr( lent.has(i) ? { name: host.retainFrame(a.name, a.type), type: a.type } : a, ) : args; - const invoke = (target: string, needsReceiver: boolean, values = args): string => { + // An entry through the closure's own pointer brackets the call only + // when the closure may observe the receiver (SCR_FN_RECEIVER_FREE). + const invoke = ( + target: string, + bracket: boolean, + values = args, + indirect = false, + ): string => { + const needsReceiver = bracket && !forwardsReceiver; + let observes: string | null = null; if (needsReceiver) { host.declare("declare void @scr_dyn_this_pop()"); - pushReceiver(); + if (indirect) { + const kindPtr = B.tmp(), + kind = B.tmp(), + free = B.tmp(); + observes = B.tmp(); + B.line( + `${kindPtr} = getelementptr inbounds %ScrClosure, ptr ${callee.name}, i64 0, i32 4`, + ); + B.line(`${kind} = load i32, ptr ${kindPtr}`); + B.line(`${free} = and i32 ${kind}, ${CLOSURE_RECEIVER_FREE}`); + B.line(`${observes} = icmp eq i32 ${free}, 0`); + const push = B.newLabel("receiver.push"), + pushed = B.newLabel("receiver.call"); + B.condBr(observes, push, pushed); + B.startBlock(push); + pushReceiver(); + B.br(pushed); + B.startBlock(pushed); + } else pushReceiver(); } const result = e.type.kind === "void" ? "" : B.tmp(); B.line( `${result ? `${result} = ` : ""}call ${host.llType(e.type)} ${target}(${argListOf(values)})`, ); - if (needsReceiver) B.line("call void @scr_dyn_this_pop()"); + if (observes !== null) { + const pop = B.newLabel("receiver.pop"), + popped = B.newLabel("receiver.done"); + B.condBr(observes, pop, popped); + B.startBlock(pop); + B.line("call void @scr_dyn_this_pop()"); + B.br(popped); + B.startBlock(popped); + } else if (needsReceiver) B.line("call void @scr_dyn_this_pop()"); return result; }; const lending = @@ -953,11 +1026,11 @@ export function emitCallExpr( B.line(`${has} = icmp ne ptr ${entry}, null`); B.condBr(has, borrowing, owning); B.startBlock(borrowing); - const lentResult = invoke(entry, true); + const lentResult = invoke(entry, true, args, true); const borrowingEnd = B.currentLabel(); B.br(done); B.startBlock(owning); - const ownedResult = invoke(fn, true, ownedArgs()); + const ownedResult = invoke(fn, true, ownedArgs(), true); const owningEnd = B.currentLabel(); B.br(done); B.startBlock(done); @@ -966,7 +1039,7 @@ export function emitCallExpr( B.line( `${t} = phi ${host.llType(e.type)} [ ${lentResult}, %${borrowingEnd} ], [ ${ownedResult}, %${owningEnd} ]`, ); - } else if (!direct) t = invoke(fn, true, ownedArgs()); + } else if (!direct) t = invoke(fn, true, ownedArgs(), true); else { // A global initializer may not have run, or a callback may enter // through a boundary the census cannot prove. Validate the actual @@ -984,7 +1057,7 @@ export function emitCallExpr( const fastEnd = B.currentLabel(); B.br(done); B.startBlock(fallback); - const indirect = invoke(fn, true, ownedArgs()); + const indirect = invoke(fn, true, ownedArgs(), true); const fallbackEnd = B.currentLabel(); B.br(done); B.startBlock(done); @@ -1358,6 +1431,22 @@ function ctorArguments( * walk consumes without owning it (LlEmitter.canBorrowReceiver proved every * reference argument borrowable and every argument edge-preserving). The * result stays reachable from the borrowed arguments until the consumer. */ +/** A closure value read from a location the call cannot write (a keyed + * table entry, a field, a checked extraction of either) is entered without + * a reference of its own: the location keeps the closure alive until the + * call returns. Plain stable bindings already lend (emitBorrowedInput). */ +function lendsCallee( + host: LlvmEmitterContext, + e: ExprOf<"callValue">, + forwardsReceiver: boolean, +): boolean { + const writes = host.referenceWrites; + if (writes === null || host.canBorrowCallArgument(e.callee) || !host.canBorrowReceiver(e.callee)) + return false; + const later = e.receiver === undefined || forwardsReceiver ? e.args : [e.receiver, ...e.args]; + return borrowableInputs(host, [e.callee, ...later], false, writes.ownWrites(e))[0] === true; +} + export function emitBorrowedResultCall(host: LlvmEmitterContext, e: ExprOf<"call">): LlValue { const callee = host.fnByName.get(e.callee); if (!callee || !host.borrowedReturns.has(e.callee)) diff --git a/packages/compiler/src/backend/llvm/expr-control.ts b/packages/compiler/src/backend/llvm/expr-control.ts index 2f7c31813..9ce5540a6 100644 --- a/packages/compiler/src/backend/llvm/expr-control.ts +++ b/packages/compiler/src/backend/llvm/expr-control.ts @@ -491,10 +491,7 @@ export function emitOptionalChainReceiver( // needs no reference of its own while the body cannot remove an edge // (so nothing can overwrite the projection before the body finishes). const borrowReceiver = - !multiple && - isRefCounted(narrowed) && - host.canBorrowReceiver(e.receiver) && - host.referenceEffects.preserves(e.body); + !multiple && isRefCounted(narrowed) && borrowsChainReceiver(host, e.receiver, e.body); // An optional array read of a nullable-pointer union is the element // pointer itself, read inline and owned by the statement frame until // the body finishes; the bind slot borrows it like an emitExpr value. @@ -554,6 +551,16 @@ export function emitOptionalChainReceiver( }; } +/** An optional chain's receiver can lend its payload to the bind slot when + * the body leaves the receiver's locations alone: it removes no edge at + * all, or it writes none of the locations the receiver reads. */ +function borrowsChainReceiver(host: LlvmEmitterContext, receiver: IrExpr, body: IrExpr): boolean { + if (!host.canBorrowReceiver(receiver)) return false; + if (host.referenceEffects.preserves(body)) return true; + const writes = host.referenceWrites; + return writes !== null && host.unwrittenRead(receiver, writes.expr(body)); +} + /** `a?.b ?? d` narrowed to the chain body's present arm. The body wraps a * value of the result type in a non-unit arm, so the default runs exactly * when the receiver is nullish and the intermediate result box is never @@ -580,16 +587,21 @@ function emitNullishOptionalChain(host: LlvmEmitterContext, e: ExprOf<"nullish"> if (unitTags.length === 0 || present.length !== 1) return null; const B = host.B; const narrowed = receiverDef.arms[present[0]!]!; - const r = host.canStackReceiver(chain.receiver) - ? host.emitReadReceiver(chain.receiver) - : host.emitExpr(chain.receiver); + // A receiver read from a location the body cannot write lends its + // payload to the bind slot for the whole body (`table[k]?.(…) ?? d`). + const borrowed = + isRefCounted(narrowed) && borrowsChainReceiver(host, chain.receiver, chain.body.value); + const r = + borrowed || host.canStackReceiver(chain.receiver) + ? host.emitReadReceiver(chain.receiver) + : host.emitExpr(chain.receiver); const bindTy = host.llType(narrowed); const bind = B.slot(); B.entryAllocas.push(`${bind} = alloca ${bindTy}`); B.line( `store ${bindTy} ${bindTy === "ptr" ? "null" : bindTy === "double" ? f64Lit(0) : "false"}, ptr ${bind}`, ); - host.ownSlot(bind, narrowed); + if (!borrowed) host.ownSlot(bind, narrowed); const isUnit = host.tagInSet(r.name, receiverDef.id, unitTags); const ty = host.llType(e.type); const slot = B.slot(); @@ -602,7 +614,10 @@ function emitNullishOptionalChain(host: LlvmEmitterContext, e: ExprOf<"nullish"> host.emitBranchInto(slot, e.right); B.br(lj); B.startBlock(lb); - B.line(`store ${bindTy} ${host.unionExtract(r.name, receiverDef.id, narrowed)}, ptr ${bind}`); + const payload = borrowed + ? host.unionPeek(r.name, receiverDef.id) + : host.unionExtract(r.name, receiverDef.id, narrowed); + B.line(`store ${bindTy} ${payload}, ptr ${bind}`); host.chainSlots.set(chain.id, { name: bind, type: narrowed, slot: true }); host.emitBranchInto(slot, chain.body.value); host.chainSlots.delete(chain.id); diff --git a/packages/compiler/src/backend/llvm/expr-dynamic.ts b/packages/compiler/src/backend/llvm/expr-dynamic.ts index 55bbb828b..7f4b7e48a 100644 --- a/packages/compiler/src/backend/llvm/expr-dynamic.ts +++ b/packages/compiler/src/backend/llvm/expr-dynamic.ts @@ -11,7 +11,7 @@ import { bindLiveArrayOps } from "./expr-stream-bridges.js"; /* Focused LLVM expression emission extracted from emitter.ts. */ import { InternalCompilerError } from "../../errors.js"; import { emitNullableIsTag, emitNullablePresent } from "./union-repr.js"; -import { isObjectArm } from "./nullable-unions.js"; +import { isPointerIdentityArm } from "./nullable-unions.js"; import { streamTypedRefEligible } from "../../ir/analysis.js"; import { DYN, @@ -908,13 +908,13 @@ export function emitDynamicExpr( if (!def) throw new InternalCompilerError(`llvm emitter bug: equality of unknown union ${e.unionId}`); const nullable = host.nullableUnions.get(def.id); - if (nullable && isObjectArm(nullable)) { + if (nullable && isPointerIdentityArm(nullable)) { // Object identity, and NULL === NULL for the unit arm: one compare. const eq = B.tmp(); B.line(`${eq} = icmp ${e.negated ? "ne" : "eq"} ptr ${l.name}, ${r.name}`); return { name: eq, type: e.type }; } - if (nullable) { + if (nullable && nullable.repr === "single" && nullable.arm.kind === "string") { // String arm: identical pointers are equal (the same string or the // same unit encoding); otherwise equal only when both hold strings // with the same bytes. diff --git a/packages/compiler/src/backend/llvm/expr-records.ts b/packages/compiler/src/backend/llvm/expr-records.ts index 59ab625e5..597cda630 100644 --- a/packages/compiler/src/backend/llvm/expr-records.ts +++ b/packages/compiler/src/backend/llvm/expr-records.ts @@ -9,6 +9,7 @@ import { borrowableInputs, emitBorrowedInputs, emitBorrowedInput } from "./borro import { regexLiteralTemplate } from "./expr-primitives.js"; import { isScalarUnionDef, loadInlineUnion, presentBits } from "./inline-unions.js"; import { preservesRegexInputs } from "./reference-effects.js"; +import { emitNumberMapSlot, emitStringMapEntry } from "./map-number-lookup.js"; /** Widen one tagged read. The source is borrowed unless the overflow map * supplied an owned value. Each selected reference payload is retained @@ -343,8 +344,8 @@ export function emitRecordKeyGet( e: IrExpr & { kind: "recordKeyGet" }, ): LlValue { const B = host.B; - const obj = host.emitExpr(e.obj); - const k = host.emitExpr(e.key); + // The read runs no user code: both inputs lend for its duration. + const [obj, k] = emitBorrowedInputs(host, [e.obj, e.key]) as [LlValue, LlValue]; const ty = host.llType(e.type); const slot = B.slot(); B.entryAllocas.push(`${slot} = alloca ${ty}`); @@ -366,6 +367,80 @@ export function emitRecordKeyGet( return host.own({ name: t, type: e.type }); } +/** A keyed read whose every hit is the result's own pointer: the result + * is a nullable-pointer union (nullable-unions.ts) with an undefined arm, + * and each declared field and the overflow value hold exactly the result + * type or its reference arm in a plain (non-inline) slot. A miss is the + * immortal undefined encoding. Such a read can lend the stored pointer: + * borrowedRecordKeyGet reads it without a reference. */ +export function pointerRecordKeyGet(host: LlvmEmitterContext, e: IrExpr): boolean { + if (e.kind !== "recordKeyGet" || e.missNaN === true || e.type.kind !== "union") return false; + const resultType = e.type; + const nullable = host.nullableUnions.single(resultType.unionId); + if (nullable === null || undefinedArmTag(resultType, host.unionsById) < 0) return false; + const exact = (t: IrType): boolean => typeEquals(t, resultType) || typeEquals(t, nullable.arm); + const shape = host.recordShape(e.shapeId); + if (e.overflowOnly !== true) { + for (const f of shape.fields) + if (host.recordFieldInline(e.shapeId, f.name) || !exact(f.type)) return false; + } + return shape.indexValue === undefined || typeEquals(shape.indexValue, nullable.arm); +} + +/** A pointerRecordKeyGet read without a reference: the declared field or + * overflow entry the key names, or the undefined encoding for a miss. The + * record (and through it the stored value) must stay unchanged and alive + * until the consumer is done with the pointer (canBorrowReceiver). */ +export function emitBorrowedRecordKeyGet( + host: LlvmEmitterContext, + e: IrExpr & { kind: "recordKeyGet" }, +): LlValue { + const B = host.B; + if (e.type.kind !== "union") + throw new InternalCompilerError("llvm emitter bug: borrowed keyed read of a non-union"); + const resultType = e.type; + const obj = host.emitReadReceiver(e.obj); + const key = host.canBorrowReceiver(e.key) ? host.emitReadReceiver(e.key) : host.emitExpr(e.key); + const slot = B.slot(); + B.entryAllocas.push(`${slot} = alloca ptr`); + const join = B.newLabel("rkb.j"); + const shape = host.recordShape(e.shapeId); + for (const f of e.overflowOnly === true ? [] : shape.fields) { + const hit = B.tmp(); + B.line( + `${hit} = call zeroext i1 ${host.literalEquals(f.name)}(ptr ${key.name}) ; ${llvmCommentText(f.name)}`, + ); + const lh = B.newLabel("rkb.h"); + const ln = B.newLabel("rkb.n"); + B.condBr(hit, lh, ln); + B.startBlock(lh); + const { ptr, type } = host.recordFieldPtr(obj.name, e.shapeId, f.name); + B.line(`store ptr ${host.loadRecordField(ptr, type, false)}, ptr ${slot}`); + B.br(join); + B.startBlock(ln); + } + if (shape.indexValue) { + const ovf = host.recordOvfPtr(obj.name, e.shapeId); + const entry = emitStringMapEntry(host, ovf, key.name); + const found = B.tmp(); + B.line(`${found} = icmp ne ptr ${entry}, null`); + const lh = B.newLabel("rkb.h"); + const ln = B.newLabel("rkb.n"); + B.condBr(found, lh, ln); + B.startBlock(lh); + B.line(`store ptr ${emitNumberMapSlot(host, entry, "ptr")}, ptr ${slot}`); + B.br(join); + B.startBlock(ln); + } + const undefTag = undefinedArmTag(resultType, host.unionsById); + B.line(`store ptr ${host.unitInstanceRef(resultType.unionId, undefTag)}, ptr ${slot}`); + B.br(join); + B.startBlock(join); + const t = B.tmp(); + B.line(`${t} = load ptr, ptr ${slot}`); + return { name: t, type: e.type }; +} + /** A keyed read whose result is a scalar union (`r[k]` on a * `Record`, at `number | undefined`) and whose every hit * is a number or boolean arm of it, or an inline field of the same union. @@ -660,27 +735,17 @@ export function keyedRecordReadInto( B.br(join); B.startBlock(ln); } else { - host.declare(`declare ptr @scr_map_get_str_ref(ptr, ptr)`); + // The entry lookup filters absent keys inline; a hit retains the + // stored value with its own type's retain (map-number-lookup.ts). A + // nullable value's NULL is a stored unit arm: presence decides. const lh = B.newLabel("rkg.h"); const ln = B.newLabel("rkg.n"); - let raw: string; - if (iv.kind === "union" && host.nullableUnions.has(iv.unionId)) { - // NULL is a stored unit arm of a nullable value: presence decides. - host.declare(`declare zeroext i1 @scr_map_has_str(ptr, ptr)`); - const has = B.tmp(); - B.line(`${has} = call zeroext i1 @scr_map_has_str(ptr ${ovf}, ptr ${keyName})`); - B.condBr(has, lh, ln); - B.startBlock(lh); - raw = B.tmp(); - B.line(`${raw} = call ptr @scr_map_get_str_ref(ptr ${ovf}, ptr ${keyName})`); - } else { - raw = B.tmp(); - const isnull = B.tmp(); - B.line(`${raw} = call ptr @scr_map_get_str_ref(ptr ${ovf}, ptr ${keyName})`); - B.line(`${isnull} = icmp eq ptr ${raw}, null`); - B.condBr(isnull, ln, lh); - B.startBlock(lh); - } + const entry = emitStringMapEntry(host, ovf, keyName); + const found = B.tmp(); + B.line(`${found} = icmp ne ptr ${entry}, null`); + B.condBr(found, lh, ln); + B.startBlock(lh); + const raw = host.retainHeap(emitNumberMapSlot(host, entry, "ptr"), iv); B.line(`store ${ty} ${surface(iv, raw, true)}, ptr ${slot}`); B.br(join); B.startBlock(ln); diff --git a/packages/compiler/src/backend/llvm/lib-http2.ts b/packages/compiler/src/backend/llvm/lib-http2.ts index d750fb7c5..e9f60cdfa 100644 --- a/packages/compiler/src/backend/llvm/lib-http2.ts +++ b/packages/compiler/src/backend/llvm/lib-http2.ts @@ -95,11 +95,12 @@ export function sniCallbackArgs(host: LlvmEmitterContext, cb: LlValue): AbiArgum let type = cb.type, value = cb.name; if (type.kind === "union") { - const arms = host.unionsById.get(type.unionId)?.arms ?? []; + const unionId = type.unionId; + const arms = host.unionsById.get(unionId)?.arms ?? []; const tag = arms.findIndex((arm) => arm.kind === "func"); if (tag < 0) throw new InternalCompilerError("SNI callback union lacks its function arm"); type = arms[tag]!; - value = host.unwrapNullableClosure(value, tag); + value = host.unwrapNullableClosure(value, unionId, tag); } else host.toHeap(cb); if (type.kind !== "func" || type.params[1] === undefined) throw new InternalCompilerError("SNI callback lacks its answer function"); diff --git a/packages/compiler/src/backend/llvm/lib-network.ts b/packages/compiler/src/backend/llvm/lib-network.ts index 35eb885cd..63661ab4c 100644 --- a/packages/compiler/src/backend/llvm/lib-network.ts +++ b/packages/compiler/src/backend/llvm/lib-network.ts @@ -165,7 +165,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr) const funcTag = def ? def.arms.findIndex((a) => a.kind === "func") : -1; if (funcTag < 0) throw new InternalCompilerError(`llvm emitter bug: ${e.fn} union lacks its func arm`); - cb = host.unwrapNullableClosure(args[cbIndex]!.name, funcTag); + cb = host.unwrapNullableClosure(args[cbIndex]!.name, cbT.unionId, funcTag); } } const valueCount = reusePort ? 5 : 4; diff --git a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts index ae741463d..fbb21dcd2 100644 --- a/packages/compiler/src/backend/llvm/map-read-lifetimes.ts +++ b/packages/compiler/src/backend/llvm/map-read-lifetimes.ts @@ -12,13 +12,18 @@ import type { CallLifetimes } from "./call-lifetimes.js"; import type { ReferenceEffects } from "./reference-effects.js"; import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; import { + emitCompactMapSlot, emitNumberMapGetRef, emitNumberMapGetScalar, + emitNumberMapSlot, + emitStringMapEntry, emitStringMapGetScalar, emitStringMapGetRef, compactMapValues, noteNumberMapKey, } from "./map-number-lookup.js"; +import { InternalCompilerError } from "../../errors.js"; +import { undefinedArmTag } from "../../ir/analysis.js"; import { mapKeyAccess, mapKeyParamType } from "./shapes.js"; import { rememberMapEntry } from "./map-entry-fusion.js"; @@ -490,3 +495,62 @@ export function emitStackMapRead(host: LlvmEmitterContext, read: LocalMapRead): B.line(`store i32 ${selected}, ptr ${tag}`); return { value: { name: box, type: read.type }, owner }; } + +/** A string-keyed Map.get whose result is a nullable-pointer union over the + * map's own value type (`Map` read as `F | undefined`): a hit is + * the stored pointer itself and a miss the immortal undefined encoding, so + * the read can lend the stored value (emitBorrowedMapGet). */ +export function pointerMapGet(host: LlvmEmitterContext, e: IrExpr): boolean { + if ( + e.kind !== "mapIntrinsic" || + e.method !== "get" || + e.args.length !== 1 || + e.type.kind !== "union" || + e.receiver.type.kind !== "map" || + e.receiver.type.key.kind !== "string" || + !borrowsMapReadInputs(e) + ) + return false; + const nullable = host.nullableUnions.single(e.type.unionId); + return ( + nullable !== null && + typeEquals(nullable.arm, e.receiver.type.value) && + undefinedArmTag(e.type, host.unionsById) >= 0 + ); +} + +/** A pointerMapGet read without a reference: the map (and through it the + * stored value) must stay unchanged and alive until the consumer is done + * with the pointer (canBorrowReceiver). */ +export function emitBorrowedMapGet( + host: LlvmEmitterContext, + e: IrExpr & { kind: "mapIntrinsic" }, +): LlValue { + const B = host.B; + const mapType = e.receiver.type; + if (e.type.kind !== "union" || mapType.kind !== "map") + throw new InternalCompilerError("llvm emitter bug: borrowed Map.get shape"); + const receiver = host.emitReadReceiver(e.receiver); + const keyExpr = e.args[0]!; + const key = host.canBorrowReceiver(keyExpr) + ? host.emitReadReceiver(keyExpr) + : host.emitExpr(keyExpr); + const compact = compactMapValues(host, mapType.key, mapType.value); + const entry = emitStringMapEntry(host, receiver.name, key.name, compact); + const found = B.tmp(); + B.line(`${found} = icmp ne ptr ${entry}, null`); + const hit = B.newLabel("mapb.hit"), + miss = B.newLabel("mapb.miss"), + join = B.newLabel("mapb.join"); + B.condBr(found, hit, miss); + B.startBlock(hit); + const value = compact ? emitCompactMapSlot(host, entry) : emitNumberMapSlot(host, entry, "ptr"); + B.br(join); + B.startBlock(miss); + B.br(join); + B.startBlock(join); + const absent = host.unitInstanceRef(e.type.unionId, undefinedArmTag(e.type, host.unionsById)); + const result = B.tmp(); + B.line(`${result} = phi ptr [ ${value}, %${hit} ], [ ${absent}, %${miss} ]`); + return { name: result, type: e.type }; +} diff --git a/packages/compiler/src/backend/llvm/nullable-unions.test.ts b/packages/compiler/src/backend/llvm/nullable-unions.test.ts index bf0941652..f4e6a6866 100644 --- a/packages/compiler/src/backend/llvm/nullable-unions.test.ts +++ b/packages/compiler/src/backend/llvm/nullable-unions.test.ts @@ -7,7 +7,7 @@ import { type IrType, type IrUnionDef, } from "../../ir/ir.js"; -import { NullableUnions } from "./nullable-unions.js"; +import { isObjectArm, isPointerIdentityArm, NullableUnions } from "./nullable-unions.js"; const loc = { file: "nullable-unions.ts", start: 0, end: 0 }; const a: IrType = { kind: "record", shapeId: "a" }; @@ -111,3 +111,22 @@ test("library, lifetime-region and debug builds keep record unions boxed", () => expect(new NullableUnions(module(unions, { lifetimeRegions: true })).get("ab")).toBeNull(); expect(new NullableUnions(module(unions)).shapes("ab")).not.toBeNull(); }); + +test("function arms are nullable pointers compared by identity root", () => { + const fn: IrType = { kind: "func", params: [F64], ret: F64 }; + const unions = new NullableUnions( + module([ + { id: "fu", arms: [fn, UNDEFINED_T] }, + { id: "fnu", arms: [NULL_T, fn, UNDEFINED_T] }, + { id: "ff", arms: [fn, F64] }, + ]), + ); + expect(unions.single("fu")).toMatchObject({ refTag: 0, unitTag: 1, nullTag: -1, arm: fn }); + expect(unions.single("fnu")).toMatchObject({ refTag: 1, unitTag: 2, nullTag: 0 }); + expect(unions.get("ff")).toBeNull(); + const single = unions.single("fu")!; + // Always truthy, but `===` compares adapters by the function they adapt. + expect(isObjectArm(single)).toBe(true); + expect(isPointerIdentityArm(single)).toBe(false); + expect(unions.armTypes().get("fu")).toEqual(fn); +}); diff --git a/packages/compiler/src/backend/llvm/nullable-unions.ts b/packages/compiler/src/backend/llvm/nullable-unions.ts index c51f3b5f6..eedb922b3 100644 --- a/packages/compiler/src/backend/llvm/nullable-unions.ts +++ b/packages/compiler/src/backend/llvm/nullable-unions.ts @@ -89,21 +89,30 @@ export const NULLABLE_ABSENT = "@sc_nullable_absent"; /** The reference arms whose instances are never NULL. Class and record * entry points all tolerate NULL; strings, Stats snapshots, arrays, maps and - * sets retain through a NULL-skipping wrapper (shapes.ts). Object arms are - * always truthy and compare by identity; string arms are truthy when - * non-empty and compare bytes. */ + * sets retain through a NULL-skipping wrapper (shapes.ts), as do closures + * (function arms). Object arms are always truthy and compare by identity + * (function arms by identity root); string arms are truthy when non-empty + * and compare bytes. */ export type NullableRefArm = | { kind: "object"; className: string } | { kind: "record"; shapeId: string } | { kind: "string" } | { kind: "stats" } - | (IrType & { kind: "array" | "map" | "set" }); + | (IrType & { kind: "array" | "map" | "set" | "func" }); -/** The arm is a JS object: always truthy, compared by identity. */ +/** The arm is a JS object: always truthy. */ export function isObjectArm(nullable: PointerUnion): boolean { return nullable.repr === "shapes" || nullable.arm.kind !== "string"; } +/** The arm compares by pointer identity. Function arms compare identity + * roots instead: a signature adapter is the function it adapts. */ +export function isPointerIdentityArm(nullable: PointerUnion): boolean { + return ( + nullable.repr === "shapes" || (nullable.arm.kind !== "string" && nullable.arm.kind !== "func") + ); +} + /** The representation decision for every union of one module. It is a * pure function of the module, so serialized-IR and in-memory emission * agree. */ @@ -264,7 +273,8 @@ function nullableShape( return { ...base, arm: { kind: "record", shapeId: arm.shapeId } }; if (arm.kind === "string") return { ...base, arm: { kind: "string" } }; if (arm.kind === "stats") return { ...base, arm: { kind: "stats" } }; - if (arm.kind === "array" || arm.kind === "map" || arm.kind === "set") return { ...base, arm }; + if (arm.kind === "array" || arm.kind === "map" || arm.kind === "set" || arm.kind === "func") + return { ...base, arm }; return null; } diff --git a/packages/compiler/src/backend/llvm/receivers.ts b/packages/compiler/src/backend/llvm/receivers.ts index 2fe9dd66b..8524a51fb 100644 --- a/packages/compiler/src/backend/llvm/receivers.ts +++ b/packages/compiler/src/backend/llvm/receivers.ts @@ -11,14 +11,24 @@ import type { LlValue } from "./expr-context.js"; import { guardedValue } from "./borrowed-receivers.js"; import { InternalCompilerError } from "../../errors.js"; import { emitBorrowedArrayRead, emitProjectedArrayRead } from "./local-array-reads.js"; -import { emitStackMapRead, matchMapRead } from "./map-read-lifetimes.js"; +import { + emitBorrowedMapGet, + emitStackMapRead, + matchMapRead, + pointerMapGet, +} from "./map-read-lifetimes.js"; import { emitBorrowedResultCall, emitCallExpr } from "./expr-calls.js"; -import { emitCheckedNarrowReceiver } from "./checked-narrows.js"; +import { borrowsCheckedNarrow, emitCheckedNarrowReceiver } from "./checked-narrows.js"; import { canStackUnion, emitStackUnion } from "./stack-unions.js"; import { emitDowncastGuard, emitUnionNarrowGuard } from "./narrow-guards.js"; import { emitScalarOptional, isScalarOptionalSource } from "./local-union-storage.js"; import { borrowableInputs } from "./borrowed-inputs.js"; -import { emitStackRecordKeyGet, scalarRecordKeyGet } from "./expr-records.js"; +import { + emitBorrowedRecordKeyGet, + emitStackRecordKeyGet, + pointerRecordKeyGet, + scalarRecordKeyGet, +} from "./expr-records.js"; import { type WalkBorrowHost, analyzeWalks } from "./walk-borrows.js"; /** Read a receiver whose value is consumed immediately by a field or tag @@ -92,6 +102,10 @@ export function emitReadReceiver(emitter: LlEmitter, e: IrExpr): LlValue { return emitBorrowedResultCall(emitter, e); const arrayRead = emitter.optionalArrayReads.get(e); if (arrayRead) return emitProjectedArrayRead(emitter, arrayRead); + if (e.kind === "recordKeyGet" && emitter.canBorrowReceiver(e)) + return emitBorrowedRecordKeyGet(emitter, e); + if (e.kind === "mapIntrinsic" && emitter.canBorrowReceiver(e)) + return emitBorrowedMapGet(emitter, e); const narrow = emitter.checkedNarrows.get(e); const narrowed = narrow ? emitCheckedNarrowReceiver(emitter, narrow) : null; if (narrowed) return narrowed; @@ -294,7 +308,27 @@ export function canBorrowReceiver(emitter: LlEmitter, e: IrExpr): boolean { // snapshot, so neither the callee nor a later argument may replace // what it was read from: a callee that preserves every edge cannot, // and any other one must leave the argument's locations unwritten. + // A keyed read lends the stored pointer while the record keeps it. + case "recordKeyGet": + return ( + pointerRecordKeyGet(emitter, e) && + emitter.canBorrowReceiver(e.obj) && + emitter.referenceEffects.preserves(e.key) + ); + // So does a Map.get while the map keeps it. + case "mapIntrinsic": + return ( + pointerMapGet(emitter, e) && + emitter.canBorrowReceiver(e.receiver) && + emitter.referenceEffects.preserves(e.args[0]!) + ); case "call": { + // A checked extraction lends the payload of the union it narrows + // (emitCheckedNarrowReceiver). + if (borrowsCheckedNarrow(emitter, e)) { + const narrow = emitter.checkedNarrows.get(e)!; + return emitter.canBorrowReceiver(narrow.value); + } if (!emitter.borrowedReturns.has(e.callee)) return false; if (!e.args.every((arg) => emitter.referenceEffects.preserves(arg))) return false; const preserving = emitter.referenceEffects.functions.has(e.callee); diff --git a/packages/compiler/src/backend/llvm/reference-writes.test.ts b/packages/compiler/src/backend/llvm/reference-writes.test.ts index cbbe49d3b..db575babe 100644 --- a/packages/compiler/src/backend/llvm/reference-writes.test.ts +++ b/packages/compiler/src/backend/llvm/reference-writes.test.ts @@ -97,3 +97,46 @@ test("an array that is read back or escapes keeps its element writes", () => { const input = fill("input", [store("input", element("input"))]); expect(writes(input).conflicts(["a"], writes(input).summary("input"))).toBe(true); }); + +test("a keyed record read depends on every field and keyed entry of records", () => { + const handler: IrType = { kind: "func", params: [F64], ret: F64 }; + const table: IrType = { kind: "record", shapeId: "t" }; + const key: IrExpr = { kind: "strLit", value: "k", type: { kind: "string" }, loc }; + const read: IrExpr = { + kind: "recordKeyGet", + obj: ref("table", table), + shapeId: "t", + key, + type: { kind: "union", unionId: "hu" }, + loc, + }; + const keyedWrite: IrStmt = { + kind: "recordKeySet", + obj: ref("table", table), + shapeId: "t", + key, + value: ref("h", handler), + loc, + }; + const fn = (name: string, body: IrStmt[]): IrFunction => ({ + name, + loc, + returnType: F64, + params: [{ localId: "table", name: "table", type: table }], + locals: [ + { id: "table", name: "table", type: table, mutable: false }, + { id: "h", name: "h", type: handler, mutable: false }, + ], + body, + }); + const records = [{ id: "t", fields: [{ name: "add", type: handler }], indexValue: handler }]; + const quiet = fn("quiet", []); + const writer = fn("writer", [keyedWrite]); + const functions = new Map([quiet, writer].map((f) => [f.name, f])); + const rw = new ReferenceWrites(functions, [], () => false, new Set(), { records }); + rw.enter(quiet); + const keys = rw.readKeys(read); + expect(keys).toEqual(expect.arrayContaining(["r:*", "r:add"])); + expect(rw.conflicts(keys!, rw.summary("quiet"))).toBe(false); + expect(rw.conflicts(keys!, rw.summary("writer"))).toBe(true); +}); diff --git a/packages/compiler/src/backend/llvm/reference-writes.ts b/packages/compiler/src/backend/llvm/reference-writes.ts index 89132a715..d64beafc5 100644 --- a/packages/compiler/src/backend/llvm/reference-writes.ts +++ b/packages/compiler/src/backend/llvm/reference-writes.ts @@ -92,6 +92,8 @@ const INERT_LIB_PREFIXES = [ ]; const INERT_LIB_CALLS = new Set([ "dyn.compare", + // Reads the ambient receiver (a fresh reference to the bound value). + "dyn.this", "error.new", "error.nodeThrow", "dyn.typedRefIs", @@ -922,6 +924,17 @@ export class ReferenceWrites { case "arrayGet": out.push(ARRAY_KEY); return this.expr(node.index) === null && walk(node.arr); + case "recordKeyGet": + // Any declared field or overflow entry the key may name. + out.push(ANY_RECORD_KEY); + for (const f of this.records.get(node.shapeId)?.fields ?? []) out.push(`r:${f.name}`); + return this.expr(node.key) === null && walk(node.obj); + case "mapIntrinsic": + // A typed Map.get: any entry of any map. + if (node.method !== "get" || node.args.length !== 1 || !borrowsMapReadInputs(node)) + return false; + out.push(MAP_KEY); + return this.expr(node.args[0]!) === null && walk(node.receiver); case "unionNarrow": case "upcast": case "downcast": diff --git a/packages/compiler/src/backend/llvm/stack-callbacks.ts b/packages/compiler/src/backend/llvm/stack-callbacks.ts index 5c404896a..20de50372 100644 --- a/packages/compiler/src/backend/llvm/stack-callbacks.ts +++ b/packages/compiler/src/backend/llvm/stack-callbacks.ts @@ -32,10 +32,12 @@ export class StackCallbacks { const count = B.tmp(); B.line(`${count} = getelementptr inbounds %ScrClosure, ptr ${closure}, i32 0, i32 2`); B.line(`store ${host.sizeType} ${value.captures.length}, ptr ${count}`); - if (this.functions.get(value.fnName)?.ownsPrototype) { + const target = this.functions.get(value.fnName); + const functionKind = target ? host.closureFunctionKind(target) : 0; + if (functionKind !== 0) { const kind = B.tmp(); B.line(`${kind} = getelementptr inbounds %ScrClosure, ptr ${closure}, i32 0, i32 4`); - B.line(`store i32 4, ptr ${kind}`); + B.line(`store i32 ${functionKind}, ptr ${kind}`); } value.captures.forEach((localId, index) => { const box = host.retainBox(host.captureBox(localId)); diff --git a/packages/compiler/src/ir/receiver-readers.ts b/packages/compiler/src/ir/receiver-readers.ts index f5270aa7d..067c474ca 100644 --- a/packages/compiler/src/ir/receiver-readers.ts +++ b/packages/compiler/src/ir/receiver-readers.ts @@ -1,6 +1,7 @@ import { isBorrowSafeMath } from "./analysis.js"; import type { IrExpr, IrFunction, IrLibFn, IrStmt } from "./ir.js"; import { eachNodeOfKinds, type IrNodeIndex } from "./node-index.js"; +import { everyExprChild, everyStmtChild } from "./traverse.js"; /** Expression kinds that run no user code of their own and never consult * the ambient receiver stack. Calls are admitted here and resolved through @@ -151,6 +152,7 @@ function opaqueOperand(e: IrExpr): boolean { * this set needs no receiver bracket. */ export class AmbientReceiverReaders { private readers: Set | null = null; + private byMethod: Map | null = null; constructor( private readonly functions: ReadonlyMap, @@ -163,8 +165,63 @@ export class AmbientReceiverReaders { return !this.compute().has(fn.name); } - private compute(): Set { - if (this.readers) return this.readers; + /** A signature adapter body that observes the ambient receiver only by + * forwarding it, unchanged, to the original it captures (a callValue on + * that capture whose receiver is the plain `this` read). Such an adapter + * ignores the receiver exactly when its original does. */ + forwardsOnly(fn: IrFunction): boolean { + if (fn.async || fn.generator) return false; + const original = fn.captures?.[0]?.localId; + if (original === undefined) return false; + const readers = this.compute(); + const edge = (name: string): boolean => this.functions.has(name) && !readers.has(name); + const stmt = (node: IrStmt): boolean => + RECEIVER_FREE_STMTS.has(node.kind) && everyStmtChild(node, expr, stmt); + const expr = (node: IrExpr): boolean => { + if (node.kind === "callValue" && node.receiver !== undefined) { + const receiver = node.receiver; + return ( + receiver.kind === "libCall" && + receiver.fn === "dyn.this" && + receiver.args.length === 0 && + node.callee.kind === "varRef" && + node.callee.localId === original && + node.args.every(expr) + ); + } + return this.admits(node, edge) && everyExprChild(node, expr, stmt); + }; + return fn.body.every(stmt); + } + + /** Whether one expression node, apart from its children, leaves the + * ambient receiver alone; `edge` admits a direct callee. */ + private admits(expr: IrExpr, edge: (name: string) => boolean): boolean { + if (!RECEIVER_FREE_EXPRS.has(expr.kind)) return false; + switch (expr.kind) { + case "call": + return edge(expr.callee); + case "new": + return edge(`%${expr.className}.constructor`); + case "virtualCall": { + // Any class's override may run: admit only when every + // same-named method is itself receiver-free. + const targets = this.methods().get(expr.method) ?? []; + return targets.length > 0 && targets.every(edge); + } + case "libCall": + return isBorrowSafeMath(expr.fn) || RECEIVER_FREE_LIB_FNS.has(expr.fn); + case "toString": + return !opaqueOperand(expr.operand); + default: { + const operands = dataIntrinsicOperands(expr); + return operands === null || !operands.some(opaqueOperand); + } + } + } + + private methods(): Map { + if (this.byMethod) return this.byMethod; const byMethod = new Map(); for (const name of this.functions.keys()) { const dot = name.lastIndexOf("."); @@ -174,6 +231,12 @@ export class AmbientReceiverReaders { list.push(name); byMethod.set(method, list); } + this.byMethod = byMethod; + return byMethod; + } + + private compute(): Set { + if (this.readers) return this.readers; const readers = new Set(); const callers = new Map(); const scan = this.nodes?.scanExcept(TRIVIALLY_RECEIVER_FREE); @@ -185,34 +248,11 @@ export class AmbientReceiverReaders { callees.add(name); return true; }; - const expr = (expr: IrExpr): boolean => { - if (!RECEIVER_FREE_EXPRS.has(expr.kind)) return false; - switch (expr.kind) { - case "call": - return edge(expr.callee); - case "new": - return edge(`%${expr.className}.constructor`); - case "virtualCall": { - // Any class's override may run: admit only when every - // same-named method is itself receiver-free. - const targets = byMethod.get(expr.method) ?? []; - return targets.length > 0 && targets.every(edge); - } - case "libCall": - return isBorrowSafeMath(expr.fn) || RECEIVER_FREE_LIB_FNS.has(expr.fn); - case "toString": - return !opaqueOperand(expr.operand); - default: { - const operands = dataIntrinsicOperands(expr); - return operands === null || !operands.some(opaqueOperand); - } - } - }; if (!reads) eachNodeOfKinds(fn, scan, (node, statement) => { const free = statement ? RECEIVER_FREE_STMTS.has((node as IrStmt).kind) - : expr(node as IrExpr); + : this.admits(node as IrExpr, edge); if (!free) reads = true; return free; }); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-function-record-dispatch.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-function-record-dispatch.ts.json new file mode 100644 index 000000000..1670b97b4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8230-function-record-dispatch.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8230-function-record-dispatch.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-function-table-shapes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-function-table-shapes.ts.json new file mode 100644 index 000000000..8f1963ef5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8231-function-table-shapes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8231-function-table-shapes.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-optional-function-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-optional-function-values.ts.json new file mode 100644 index 000000000..671364bc6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8232-optional-function-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8232-optional-function-values.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_closure.h b/packages/runtime/src/scr_closure.h index 9644722ce..cfca90313 100644 --- a/packages/runtime/src/scr_closure.h +++ b/packages/runtime/src/scr_closure.h @@ -98,7 +98,7 @@ typedef struct ScrClosure { * (like the dyn→closure edge): a cycle through it is merely never * collected. */ ScrBox *props; - uint32_t function_kind; /* low bits: 0 ordinary, 1 generator, 2 async, 3 async generator; bit 2: ordinary own prototype; bit 3: checked callable adapter in caps[0] */ + uint32_t function_kind; /* low bits: 0 ordinary, 1 generator, 2 async, 3 async generator; bit 2: ordinary own prototype; bit 3: checked callable adapter in caps[0]; bit 4: SCR_FN_RECEIVER_FREE */ /* The root's `length` as boxed values report it; meaningful only when * identity is set (the root's own boxes carry their arity). */ uint32_t identity_length; @@ -117,6 +117,13 @@ typedef struct ScrClosure { ScrBox *caps[]; } ScrClosure; +/* function_kind bit 4: the body never observes the ambient receiver (the + * `this` an unbound call binds), so compiled callers skip the receiver + * bracket around an indirect call. Set only by the compiler; the kind + * proper is function_kind & SCR_FN_KIND_MASK. */ +#define SCR_FN_RECEIVER_FREE 16u +#define SCR_FN_KIND_MASK 15u + ScrClosure *scr_closure_new(void *fn, size_t ncaps); /* +1; caller fills caps with +1 box refs */ static inline ScrClosure *scr_closure_retain(ScrClosure *c) { diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index cd9eeecde..cef3be673 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -6880,7 +6880,7 @@ ScrDyn *scr_dyn_class_base_prototype(ScrDyn *constructor) { default: break; } if (constructor->kind != SCR_DYN_FUNC || constructor->v.fn.class_obj || - scr_closure_identity(constructor->v.fn.clo)->function_kind != 4) { + (scr_closure_identity(constructor->v.fn.clo)->function_kind & SCR_FN_KIND_MASK) != 4) { static const char message[] = "Class extends value is not a native ordinary constructor"; scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); return NULL; @@ -7028,7 +7028,8 @@ void scr_dyn_name_class_prototype(ScrDyn *prototype, const ScrStr *name) { /* Ordinary native function constructors retain their live prototype table. * A constructor may replace the fresh receiver with another object. */ ScrDyn *scr_dyn_construct(const ScrDyn *callee, const ScrDyn *args, const ScrStr *what) { - if (callee->kind != SCR_DYN_FUNC || callee->v.fn.class_obj || scr_closure_identity(callee->v.fn.clo)->function_kind != 4) + if (callee->kind != SCR_DYN_FUNC || callee->v.fn.class_obj || + (scr_closure_identity(callee->v.fn.clo)->function_kind & SCR_FN_KIND_MASK) != 4) return scr_bytes_construct(callee, args, what); ScrDyn *prototype = scr_dyn_fn_get(callee, "prototype", 9); if (!prototype) return NULL; @@ -9144,7 +9145,9 @@ bool scr_dyn_is_callable(const ScrDyn *value) { int scr_dyn_function_kind(const ScrDyn *value) { while (value->kind == SCR_DYN_PROXY) value = value->v.proxy.target; - return value->kind == SCR_DYN_FUNC ? scr_closure_identity(value->v.fn.clo)->function_kind : 0; + return value->kind == SCR_DYN_FUNC + ? (int)(scr_closure_identity(value->v.fn.clo)->function_kind & SCR_FN_KIND_MASK) + : 0; } void scr_dyn_adopt_identity(ScrClosure *adapter, const ScrDyn *value) { diff --git a/tests/corpus/8230-function-record-dispatch.ts b/tests/corpus/8230-function-record-dispatch.ts new file mode 100644 index 000000000..3acde5bd7 --- /dev/null +++ b/tests/corpus/8230-function-record-dispatch.ts @@ -0,0 +1,107 @@ +// Records of functions used as dispatch tables: keyed reads consumed at once +// (`t[k]!(…)`, `t[k]?.(…)`, a checked local), missing keys, handlers that +// declare fewer parameters than the table's type, handlers that replace, +// delete or re-add table entries (their own included) while they run, and +// key order after such mutation. +type Handler = (x: number, y: number) => number; + +let log: string[] = []; + +function make(tag: string): Handler { + let calls = 0; + return (x, y) => { + calls++; + log.push(tag + ":" + calls); + return x * 10 + y; + }; +} + +const table: Record = { + add: (x, y) => x + y, + neg: (x) => -x, + pow: (x, y) => x ** y, + made: make("made"), +}; + +function keys(): string[] { + return process.argv.length > 99 ? [] : ["add", "neg", "pow", "made", "missing", ""]; +} + +for (const k of keys()) { + const h = table[k] as Handler | undefined; + console.log(JSON.stringify(k), typeof h, h === undefined, h !== undefined && h === table[k]); + console.log(" optional", table[k]?.(3, 4) ?? "none"); + if (h) console.log(" checked", h(2, 5)); + try { + console.log(" asserted", table[k]!(5, 2)); + } catch (e) { + console.log(" asserted threw", (e as Error).name); + } +} + +// A handler that replaces its own entry while it runs: the replaced closure +// keeps running with its captured state. +function selfReplacing(): Handler { + const state = { hits: 0, label: "first" }; + return (x, y) => { + table["swap"] = make("second"); + state.hits++; + log.push(state.label + " still running after replace, hits " + state.hits); + return x + y + state.hits; + }; +} +table["swap"] = selfReplacing(); +console.log("swap 1", table["swap"]!(1, 2)); +console.log("swap 2", table["swap"]?.(1, 2)); +console.log("swap 3", table["swap"]!(1, 2)); + +// A handler that deletes its own entry. +function selfDeleting(name: string): Handler { + const captured = [name, "captured"]; + return (x) => { + delete table[name]; + log.push(captured.join(" ") + " after delete"); + return x; + }; +} +table["gone"] = selfDeleting("gone"); +const goneKey = keys()[0] === "add" ? "gone" : "add"; +console.log("gone", table[goneKey]?.(7, 0) ?? "none", table[goneKey]?.(7, 0) ?? "none"); + +// A checked local keeps its closure after the entry changes. +table["kept"] = make("kept-old"); +const kept = table["kept"] as Handler | undefined; +table["kept"] = make("kept-new"); +delete table["kept"]; +if (kept) console.log("kept", kept(4, 4)); +console.log("kept entry", table["kept"] === undefined); + +// Iteration while handlers add and delete keys: key order follows inserts. +const order: Record = { + b: (x) => x + 1, + a: (x) => { + delete order["c"]; + order["z"] = (v) => v * 2; + return x + 2; + }, + c: (x) => x + 3, + "1": (x) => x + 4, +}; +for (const k in order) console.log("in", k, order[k]?.(1, 0)); +console.log("keys", Object.keys(order).join(",")); +for (const k of Object.keys(order)) { + order[k + k] = (x) => x; + console.log("of", k, order[k]!(10, 0)); +} +console.log("keys", Object.keys(order).join(",")); + +// Reassigning the table binding itself from inside a handler. +let current: Record = { + go: (x) => { + current = { go: (y) => y * 100 }; + const local = [x, x]; + return local[0]! + local[1]!; + }, +}; +console.log("rebind", current["go"]!(3, 0), current["go"]!(3, 0)); +console.log(log.join("\n")); diff --git a/tests/corpus/8231-function-table-shapes.ts b/tests/corpus/8231-function-table-shapes.ts new file mode 100644 index 000000000..108c4cee0 --- /dev/null +++ b/tests/corpus/8231-function-table-shapes.ts @@ -0,0 +1,108 @@ +// Function dispatch through other table shapes: Map, arrays of +// handlers, object literals of methods (with and without `this`), handler +// mutation of the Map during calls, and adapters for handlers that declare +// fewer parameters than the table type. +type Op = (acc: number, arg: number) => number; + +const trace: string[] = []; +const byName = new Map(); +byName.set("add", (acc, arg) => acc + arg); +byName.set("half", (acc) => acc / 2); +byName.set("reset", () => { + byName.delete("reset"); + byName.set("reset", (acc) => acc - 1); + trace.push("reset replaced itself"); + return 0; +}); +byName.set("clear", (acc) => { + const keep = [acc, "before clear"]; + byName.clear(); + trace.push(keep.join(" ") + ", size " + byName.size); + return acc; +}); + +function program(): string[] { + return process.argv.length > 99 ? [] : ["add", "half", "nop", "reset", "reset", "add", "clear", "add"]; +} + +let acc = 8; +for (const name of program()) { + const handler = byName.get(name); + if (handler !== undefined) acc = handler(acc, 3); + else trace.push("no " + name); + console.log(name, acc, byName.get(name)?.(1, 1) ?? "-"); +} + +const byCode: Op[] = [(a, b) => a - b, (a) => a * 3, (a, b) => Math.max(a, b)]; +let total = 1; +for (let i = 0; i < 9; i++) { + total = byCode[i % 4]?.(total, i) ?? total + 100; + if (i % 4 < byCode.length) total = byCode[i % 4]!(total, 1); +} +console.log("array", total); + +const counter = { + base: 5, + bump(by: number): number { + this.base += by; + return this.base; + }, + twice(by: number): number { + return this.bump(by) + this.bump(by); + }, + plain(by: number): number { + return by * 2; + }, +}; +console.log("methods", counter.bump(1), counter.twice(2), counter.plain(3), counter["plain"](4)); + +// Methods that read `this`, called through computed keys and through a +// signature adapter (a method declaring fewer parameters than its type). +interface Account { + balance: number; + deposit: (amount: number, note: string) => number; + withdraw: (amount: number, note: string) => number; + peek: (amount: number, note: string) => number; +} +const account: Account = { + balance: 10, + deposit(amount) { + this.balance += amount; + return this.balance; + }, + withdraw(amount, note) { + this.balance -= amount; + trace.push("withdraw " + note); + return this.balance; + }, + peek: (amount) => amount, +}; +for (const op of ["deposit", "withdraw", "peek", "deposit"] as const) { + console.log(op, account[op](5, "n-" + op), account.balance); +} +const detached = account.peek; +console.log("detached", detached(3, "x")); + +interface Machine { + state: string; + on: Record string>; +} +const machine: Machine = { + state: "idle", + on: { + idle: (m, input) => (input > 0 ? "running" : m.state), + running: (m, input) => { + if (input < 0) { + m.on["stopped"] = () => "idle"; + return "stopped"; + } + return m.state; + }, + }, +}; +for (const input of [0, 1, 2, -1, 5, 3]) { + const next = machine.on[machine.state]?.(machine, input) ?? machine.state; + console.log("machine", machine.state, input, "->", next); + machine.state = next; +} +console.log(trace.join("\n")); diff --git a/tests/corpus/8232-optional-function-values.ts b/tests/corpus/8232-optional-function-values.ts new file mode 100644 index 000000000..5035c5f51 --- /dev/null +++ b/tests/corpus/8232-optional-function-values.ts @@ -0,0 +1,75 @@ +// Values typed `F | undefined` for a function type F: identity and +// equality (signature adapters compare as the function they adapt), typeof, +// truthiness, storage in arrays, records, class fields and Maps, optional +// parameters and returns. +type Unary = (x: number) => number; +type Binary = (x: number, y: number) => number; + +const double: Unary = (x) => x * 2; +const asBinary: Binary = double; +const maybe = (flag: boolean): Unary | undefined => (flag ? double : undefined); + +const a = maybe(true); +const b = maybe(true); +const none = maybe(false); +console.log(a === b, a === none, none === undefined, typeof a, typeof none, !!a, !!none); +console.log(a === double, (asBinary as unknown) === (double as unknown)); + +const slots: (Unary | undefined)[] = [double, undefined, (x) => x + 1]; +console.log(slots.map((f) => (f ? f(5) : "empty")).join(" "), slots.length); +console.log(slots.indexOf(double), slots.includes(undefined), slots.filter((f) => f !== undefined).length); + +interface Hooks { + before?: Unary; + after?: Unary; +} +const hooks: Hooks = { after: (x) => x - 1 }; +console.log(hooks.before?.(1) ?? "no before", hooks.after?.(1) ?? "no after", "before" in hooks); + +class Button { + onClick: Unary | undefined; + clicks = 0; + click(): number { + this.clicks++; + return this.onClick?.(this.clicks) ?? -1; + } +} +const button = new Button(); +console.log(button.click()); +button.onClick = (n) => n * 100; +console.log(button.click(), button.click()); +button.onClick = undefined; +console.log(button.click(), button.onClick === undefined); + +const registry = new Map([ + ["set", double], + ["unset", undefined], +]); +console.log(registry.get("set")?.(4), registry.get("unset")?.(4), registry.has("unset"), registry.get("other") === undefined); + +function run(x: number, transform?: Unary, combine?: Binary): number { + const t = transform ? transform(x) : x; + return combine === undefined ? t : combine(t, x); +} +console.log(run(3), run(3, double), run(3, undefined, (p, q) => p * q), run(3, (x) => -x, asBinary)); + +function pick(name: string): Binary | undefined { + switch (name) { + case "mul": + return (x, y) => x * y; + case "first": + return (x) => x; + default: + return undefined; + } +} +for (const name of ["mul", "first", "none"]) { + const f = pick(name); + console.log(name, f?.(6, 7), f === undefined ? "undefined" : typeof f); +} + +let late: Unary | undefined; +const callLate = (): number => late?.(9) ?? 0; +console.log(callLate()); +late = (x) => x + 0.5; +console.log(callLate()); diff --git a/tests/harness/corpus-costs/8230-function-record-dispatch.ts.json b/tests/harness/corpus-costs/8230-function-record-dispatch.ts.json new file mode 100644 index 000000000..1dd4b287a --- /dev/null +++ b/tests/harness/corpus-costs/8230-function-record-dispatch.ts.json @@ -0,0 +1 @@ +{ "plain": 4700, "san": 5800 } diff --git a/tests/harness/corpus-costs/8231-function-table-shapes.ts.json b/tests/harness/corpus-costs/8231-function-table-shapes.ts.json new file mode 100644 index 000000000..1dd4b287a --- /dev/null +++ b/tests/harness/corpus-costs/8231-function-table-shapes.ts.json @@ -0,0 +1 @@ +{ "plain": 4700, "san": 5800 } diff --git a/tests/harness/corpus-costs/8232-optional-function-values.ts.json b/tests/harness/corpus-costs/8232-optional-function-values.ts.json new file mode 100644 index 000000000..1dd4b287a --- /dev/null +++ b/tests/harness/corpus-costs/8232-optional-function-values.ts.json @@ -0,0 +1 @@ +{ "plain": 4700, "san": 5800 } From 3d886579b2f25b213a33eb9ea6de82fdb9e01745 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:50:35 -0700 Subject: [PATCH 129/230] Remove the committed node_modules symlink Co-Authored-By: Claude Opus 5.5 --- packages/compiler/node_modules | 1 - 1 file changed, 1 deletion(-) delete mode 120000 packages/compiler/node_modules diff --git a/packages/compiler/node_modules b/packages/compiler/node_modules deleted file mode 120000 index 6b01b0eda..000000000 --- a/packages/compiler/node_modules +++ /dev/null @@ -1 +0,0 @@ -../../../shape-reg/packages/compiler/node_modules \ No newline at end of file From 5f76d41a7181348e2e14d60a2ec3377f825bc3d5 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:57:11 -0500 Subject: [PATCH 130/230] Keep U+11A7 after a Hangul LV syllable in NFC and NFKC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libunicode composes an LV syllable with U+11A7 as if it were a trailing consonant (U+11A8..U+11C2), which drops it: "가ᆧ".normalize() gave "가" where Node gives "가ᆧ". U+11A7 is a class-0 vowel that composes with nothing, so for the composing forms the code points between occurrences of it normalize on their own and it is kept as it is. The guard sits in front of the vendored library (scr_unicode_normalize), for String.prototype.normalize and the NFC step of IDNA mapping. 8009-normalize-hangul-jungseong-o-yae prints the code points of all four forms for U+11A7 after LV and LVT syllables, conjoining jamo, other starters and combining marks. --- ...9-normalize-hangul-jungseong-o-yae.ts.json | 6 ++ packages/runtime/src/scr_regex.c | 55 ++++++++++++++++++- .../8009-normalize-hangul-jungseong-o-yae.ts | 28 ++++++++++ ...9-normalize-hangul-jungseong-o-yae.ts.json | 1 + 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts.json create mode 100644 tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts create mode 100644 tests/harness/corpus-costs/8009-normalize-hangul-jungseong-o-yae.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts.json new file mode 100644 index 000000000..872a85396 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_regex.c b/packages/runtime/src/scr_regex.c index 2a98e92c6..37f94bd65 100644 --- a/packages/runtime/src/scr_regex.c +++ b/packages/runtime/src/scr_regex.c @@ -2418,6 +2418,57 @@ static void *scr_normalize_realloc(void *opaque, void *ptr, size_t size) { return realloc(ptr, size); } +/* unicode_normalize, except that U+11A7 never composes. libunicode composes + * an LV syllable with U+11A7 as a trailing consonant (those are U+11A8 to + * U+11C2), which drops it: "\uAC00\u11A7" became "\uAC00". U+11A7 is a + * class-0 vowel that composes with nothing, so for the composing forms the + * code points between occurrences of it normalize on their own and it is + * kept as it is, as in Node. */ +static int scr_unicode_normalize(uint32_t **pdst, const uint32_t *src, int len, + UnicodeNormalizationEnum mode) { + int at = 0; + if (mode == UNICODE_NFC || mode == UNICODE_NFKC) + while (at < len && src[at] != 0x11A7) at++; + else + at = len; + if (at == len) return unicode_normalize(pdst, src, len, mode, NULL, scr_normalize_realloc); + uint32_t *out = NULL; + size_t count = 0, cap = 0; + int start = 0; + for (int i = 0; i <= len; i++) { + if (i < len && src[i] != 0x11A7) continue; + uint32_t *part = NULL; + int n = 0; + if (i > start) { + n = unicode_normalize(&part, src + start, i - start, mode, NULL, scr_normalize_realloc); + if (n < 0) { + free(out); + return -1; + } + } + size_t need = count + (size_t)n + 1; + if (need > cap) { + size_t grown = cap ? cap * 2 : 16; + if (grown < need) grown = need; + uint32_t *bigger = realloc(out, grown * sizeof *out); + if (!bigger) { + free(part); + free(out); + return -1; + } + out = bigger; + cap = grown; + } + if (n > 0) memcpy(out + count, part, (size_t)n * sizeof *out); + count += (size_t)n; + free(part); + if (i < len) out[count++] = 0x11A7; + start = i + 1; + } + *pdst = out; + return (int)count; +} + ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) { UnicodeNormalizationEnum mode; if (form->len == 3 && !memcmp(form->data, "NFC", 3)) mode = UNICODE_NFC; @@ -2442,7 +2493,7 @@ ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) { offset += step; } uint32_t *output = NULL; - int count = unicode_normalize(&output, input, length, mode, NULL, scr_normalize_realloc); + int count = scr_unicode_normalize(&output, input, length, mode); free(input); if (count < 0) scr_regex_oom(); ScrJsonBuf buffer; @@ -2455,7 +2506,7 @@ ScrStr *scr_str_normalize(const ScrStr *s, const ScrStr *form) { uint32_t *scr_nfc_code_points(const uint32_t *input, size_t length, size_t *out_len) { if (length > INT32_MAX) scr_regex_oom(); uint32_t *output = NULL; - int count = unicode_normalize(&output, input, (int)length, UNICODE_NFC, NULL, scr_normalize_realloc); + int count = scr_unicode_normalize(&output, input, (int)length, UNICODE_NFC); if (count < 0) scr_regex_oom(); *out_len = (size_t)count; return output; diff --git a/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts b/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts new file mode 100644 index 000000000..b7cb5418a --- /dev/null +++ b/tests/corpus/8009-normalize-hangul-jungseong-o-yae.ts @@ -0,0 +1,28 @@ +// U+11A7 (HANGUL JUNGSEONG O-YAE) is a vowel that composes with nothing: +// after an LV syllable, after a leading consonant, and around trailing +// consonants it stays as it is in every normalization form, as in Node. + +const samples = [ + "가ᆧ", + "가ᆧ", + "가ᆧᆨ", + "각ᆧ", + "ᄀᆧ", + "ᆧ", + "ᆧᆧ", + "x가ᆧy각z", + "café 가ᆧ é", + "ガᆧ가", +]; + +function points(text: string): string { + return Array.from(text, (ch) => ch.codePointAt(0)!.toString(16)).join("."); +} + +for (const sample of samples) { + console.log( + points(sample), + ["NFC", "NFD", "NFKC", "NFKD"].map((form) => points(sample.normalize(form))).join(" "), + ); +} +console.log("가ᆧ".normalize() === "가ᆧ", "가ᆧ".normalize().length); diff --git a/tests/harness/corpus-costs/8009-normalize-hangul-jungseong-o-yae.ts.json b/tests/harness/corpus-costs/8009-normalize-hangul-jungseong-o-yae.ts.json new file mode 100644 index 000000000..6c20e6ad7 --- /dev/null +++ b/tests/harness/corpus-costs/8009-normalize-hangul-jungseong-o-yae.ts.json @@ -0,0 +1 @@ +{ "plain": 2503, "san": 2110 } From e7a088f90420ca6de0d81675e6e95989a60eb215 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 16:58:11 -0500 Subject: [PATCH 131/230] Key the executable cache by the function layout's contents The early executable cache key covered the profile but not the function layout, so a build that added, changed or edited a layout could restore the executable built with the previous order. The key now includes the digest of the layout file's bytes. An unreadable layout fails the build with SC3002 before the cache lookup. --- .../src/executable/executable-cache.test.ts | 3 +- .../src/executable/executable-cache.ts | 4 ++ packages/compiler/src/index.ts | 20 ++++++++++ .../compiler/test/function-layout.test.ts | 38 ++++++++++++++++++- 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/executable/executable-cache.test.ts b/packages/compiler/src/executable/executable-cache.test.ts index afae8b2c1..68aedbc33 100644 --- a/packages/compiler/src/executable/executable-cache.test.ts +++ b/packages/compiler/src/executable/executable-cache.test.ts @@ -220,7 +220,7 @@ test("early executable cache misses on source and resolution changes", async () expect(await readEarlyExecutableCache(f.root, f.options)).toBeNull(); }); -test("early executable keys isolate compile modes, paths, implementation, checker, and FFI manifests", async () => { +test("early executable keys isolate compile modes, paths, implementation, checker, function layouts, and FFI manifests", async () => { const f = await fixture(); const tracker = new FrontendInputTracker(); tracker.run(() => trackedReadFile(f.source)); @@ -241,6 +241,7 @@ test("early executable keys isolate compile modes, paths, implementation, checke { ...f.options, targetCpu: "x86-64-v4" }, { ...f.options, profile: "profile-generate:/tmp/%p.profraw" }, { ...f.options, profile: "profile-use:digest" }, + { ...f.options, functionLayout: "layout-digest" }, { ...f.options, checker: "tsc-ts-executable-identity" }, { ...f.options, npmStatic: "auto" }, { ...f.options, npmStatic: ["commander"] }, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index f26c4c89a..ed351e809 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -148,6 +148,8 @@ export interface EarlyExecutableCacheOptions { /** Omitted without --profile-generate/--profile-use; otherwise the * pattern, or the digest of the profile's bytes. */ profile?: string; + /** Omitted without a function layout; otherwise the digest of its bytes. */ + functionLayout?: string; /** Omitted for the default tsgo checker; with SCRIPTC_CHECKER=tsc-ts, the * tsc-ts executable's file identity. */ checker?: string; @@ -290,6 +292,7 @@ function cacheKey(options: EarlyExecutableCacheOptions): string { ...(options.detachedExit ? ["detached-exit"] : []), ...targetCpuKeyParts(options.targetCpu), ...(options.profile !== undefined ? [options.profile] : []), + ...(options.functionLayout !== undefined ? [`layout:${options.functionLayout}`] : []), ...(options.checker !== undefined ? [`checker:${options.checker}`] : []), ...(options.youngNursery ? ["young-nursery"] : []), options.npmStatic === null @@ -333,6 +336,7 @@ function routeKey(options: Omit { + if (opts.functionLayout === undefined) return undefined; + const path = resolve(opts.functionLayout); + let bytes: Buffer; + try { + bytes = await readFile(path); + } catch (err) { + throw new Error( + `cannot read function layout ${path}: ${err instanceof Error ? err.message : String(err)}`, + ); + } + return createHash("sha256").update(bytes).digest("hex"); +} + /** The request's function layout. */ async function readFunctionLayout( opts: CompileRequestOptions, @@ -1546,9 +1563,11 @@ async function compileTracked( platform: buildPlatform, }); let profile: NativeProfileOptions | undefined; + let functionLayoutDigest: string | undefined; try { if (profileRefusal !== null) throw new Error(profileRefusal); profile = await readNativeProfile(opts); + functionLayoutDigest = await functionLayoutKey(opts); } catch (err) { return { ok: false, @@ -1583,6 +1602,7 @@ async function compileTracked( ...(opts.detachedExit === true ? { detachedExit: true as const } : {}), ...targetCpuField(opts.targetCpu), ...(profile === undefined ? {} : { profile: nativeProfileKey(profile) }), + ...(functionLayoutDigest === undefined ? {} : { functionLayout: functionLayoutDigest }), ...checkerField(), ...(youngNurseryRequested() ? { youngNursery: true as const } : {}), npmStatic: opts.npmStatic ?? null, diff --git a/packages/compiler/test/function-layout.test.ts b/packages/compiler/test/function-layout.test.ts index 8619c5b34..ecf4af7d9 100644 --- a/packages/compiler/test/function-layout.test.ts +++ b/packages/compiler/test/function-layout.test.ts @@ -3,7 +3,7 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { gzipSync } from "node:zlib"; -import { expect, test } from "vitest"; +import { expect, test, vi } from "vitest"; import { compile } from "../src/index.js"; import { functionLayoutKey, @@ -145,3 +145,39 @@ console.log(points.map(norm).join(","), points[0]!.x === 3 ? "three" : "other"); await rm(dir, { recursive: true, force: true }); } }); + +test("a changed function layout misses the executable cache", async () => { + vi.stubEnv("SCRIPTC_NO_CACHE", undefined); + const dir = await mkdtemp(join(tmpdir(), "scriptc-function-layout-cache-")); + try { + vi.stubEnv("SCRIPTC_CACHE_DIR", join(dir, "cache")); + const entry = join(dir, "main.ts"); + await writeFile(entry, source); + const exe = join(dir, "main"); + const llvm = join(dir, "main.ll"); + const build = async (functionLayout?: string) => { + const built = await compile(entry, { + outputKind: "exe", + outDir: dir, + outPath: exe, + ...(functionLayout === undefined ? {} : { functionLayout }), + }); + expect(built.ok, JSON.stringify(built.diagnostics)).toBe(true); + return definitions(await readFile(llvm, "utf8")); + }; + const plain = await build(); + const third = plain.find((d) => /^sc_b?f_third$/.test(d.name))!.name; + const first = plain.find((d) => /^sc_b?f_first$/.test(d.name))!.name; + const layout = join(dir, "layout"); + await writeFile(layout, `1 ${third}\n`); + expect((await build(layout)).find((d) => d.name === third)!.group).toBe("layout-1"); + // The same path with other contents is another layout. + await writeFile(layout, `1 ${first}\n`); + const edited = await build(layout); + expect(edited.find((d) => d.name === first)!.group).toBe("layout-1"); + expect(edited.find((d) => d.name === third)!.group).not.toMatch(/^layout-/); + } finally { + vi.unstubAllEnvs(); + await rm(dir, { recursive: true, force: true }); + } +}); From 52abff7c147f5429310136513d17fb72880dd3f8 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 13:33:51 -0700 Subject: [PATCH 132/230] Clone union records whose override is an empty array literal Spreading a union of records and replacing a shared field keeps each value's variant (lowerUnionRecordClone), but only when every override's type lifts into the field. An empty array literal is typed `never[]`, which maps to nothing, so `{ ...entry, content: [] }` over a union fell back to the ordinary literal path. That path cannot build a union of spread records, and the literal was refused (SC2001). An empty array literal now builds an empty array of each arm's own element type. This covers a field that is an array and a field that is a union with exactly one array arm (`string | Part[]`). pi's session-manager repairs messages with missing content this way (`return [{ ...message, content: [] }]`). The refusal rejected the whole SessionManager class. Co-Authored-By: Claude Opus 5.5 --- .../lowering/expressions/object-literals.ts | 19 +++- .../union-clone-empty-array-overrides.ts.json | 6 ++ .../union-clone-empty-array-overrides.ts | 92 +++++++++++++++++++ .../union-clone-empty-array-overrides.ts.json | 1 + 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-clone-empty-array-overrides.ts.json create mode 100644 tests/corpus/union-clone-empty-array-overrides.ts create mode 100644 tests/harness/corpus-costs/union-clone-empty-array-overrides.ts.json diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 70766949a..ae82133bf 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -2513,6 +2513,20 @@ export function lowerObjectLiteral( : { kind: "seqExpr", stmts: spreadStmts, result, type, loc }; } +/** The field type is an array, or a union with exactly one array arm. */ +function singleArrayArm(lowerer: Lowerer, type: IrType): boolean { + if (type.kind === "array") return true; + if (type.kind !== "union") return false; + const arms = lowerer.unions.get(type.unionId)?.arms ?? []; + return arms.filter((arm) => arm.kind === "array").length === 1; +} + +function isEmptyArrayLiteral(node: ts.Node): boolean { + let inner = node; + while (ts.isParenthesizedExpression(inner)) inner = inner.expression; + return ts.isArrayLiteralExpression(inner) && inner.elements.nodes.length === 0; +} + /** A union of plain records can preserve its existing variant while * replacing shared fields. Dispatch before constructing the copy: only * the selected arm evaluates overrides, and recordClone snapshots fields @@ -2561,8 +2575,11 @@ function lowerUnionRecordClone(lowerer: Lowerer, expr: ts.ObjectLiteralExpressio for (let index = 0; index < props.length; index++) { const prop = props[index]!; const node = ts.isPropertyAssignment(prop) ? prop.initializer : prop.name; - const valueType = lowerer.mapTypeOf(lowerer.typeOf(node)); const fieldType = shape.fields.find((field) => field.name === names[index])!.type; + // An empty array literal (`content: []`, typed `never[]`) builds a + // fresh array of each arm's own element type. + if (isEmptyArrayLiteral(node) && singleArrayArm(lowerer, fieldType)) continue; + const valueType = lowerer.mapTypeOf(lowerer.typeOf(node)); const lift = valueType ? lowerer.widthLiftPlan(valueType, fieldType) : null; // The inferred result must retain an override's own fields and // aliases. A structural copy into the source field's narrower shape diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-clone-empty-array-overrides.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-clone-empty-array-overrides.ts.json new file mode 100644 index 000000000..cb11c5098 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/union-clone-empty-array-overrides.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/union-clone-empty-array-overrides.ts" + ], + "diags": [] +} diff --git a/tests/corpus/union-clone-empty-array-overrides.ts b/tests/corpus/union-clone-empty-array-overrides.ts new file mode 100644 index 000000000..98ddac59c --- /dev/null +++ b/tests/corpus/union-clone-empty-array-overrides.ts @@ -0,0 +1,92 @@ +// Spreading a union of records and overriding a shared field with an empty +// array literal keeps each value's own variant: the `[]` (typed `never[]`) +// becomes an empty array of the selected arm's element type, also when that +// arm's field is a union with one array arm (`string | Part[]`). + +interface TextPart { + type: "text"; + text: string; +} +interface ImagePart { + type: "image"; + data: string; +} +interface UserEntry { + role: "user"; + content: string | (TextPart | ImagePart)[]; + timestamp: number; +} +interface AssistantEntry { + role: "assistant"; + content: TextPart[]; + model: string; + timestamp: number; +} +interface ToolEntry { + role: "tool"; + toolCallId: string; + content: (TextPart | ImagePart)[]; + isError: boolean; + timestamp: number; +} +interface SystemEntry { + role: "system"; + content: string; + timestamp: number; +} +interface NoteEntry { + role: "note"; + text: string; + timestamp: number; +} +type Entry = UserEntry | AssistantEntry | ToolEntry | SystemEntry | NoteEntry; + +function repair(entry: Entry, clear: boolean): Entry[] { + if (entry.role === "system" && clear) return [{ ...entry, content: "" }]; + if ((entry.role === "user" || entry.role === "assistant" || entry.role === "tool") && clear) { + return [{ ...entry, content: [] }]; + } + return [entry]; +} + +interface Circle { + kind: "circle"; + tags: string[]; + radius: number; +} +interface Square { + kind: "square"; + tags: string[]; + side: number; +} +type Shape = Circle | Square; + +function cleared(shape: Shape): Shape { + return { ...shape, tags: [] }; +} + +const entries: Entry[] = [ + { role: "user", content: [{ type: "text", text: "hello" }], timestamp: 1 }, + { role: "assistant", content: [{ type: "text", text: "hi" }], model: "m", timestamp: 2 }, + { role: "tool", toolCallId: "t1", content: [{ type: "image", data: "AA==" }], isError: false, timestamp: 3 }, + { role: "note", text: "hi", timestamp: 4 }, + { role: "system", content: "be brief", timestamp: 5 }, + { role: "user", content: "kept", timestamp: 6 }, +]; +entries.forEach((entry, index) => { + for (const fixed of repair(entry, index % 2 === 0 || entry.role === "system")) { + console.log(fixed.role, JSON.stringify(fixed), fixed === entry); + } +}); +console.log(JSON.stringify(entries[0])); + +const shapes: Shape[] = [ + { kind: "circle", tags: ["round", "red"], radius: 2 }, + { kind: "square", tags: ["blue"], side: 3 }, +]; +for (const shape of shapes) { + const copy = cleared(shape); + copy.tags.push("fresh"); + const area = copy.kind === "circle" ? copy.radius * copy.radius * 3 : copy.side * copy.side; + console.log(copy.kind, area, copy.tags.join("+"), shape.tags.join("+"), copy !== shape); +} diff --git a/tests/harness/corpus-costs/union-clone-empty-array-overrides.ts.json b/tests/harness/corpus-costs/union-clone-empty-array-overrides.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/union-clone-empty-array-overrides.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 605e312e788131d8a3067227a0abdd1a2fbfb0fe Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:00:29 -0700 Subject: [PATCH 133/230] Let boxable function arms ride a checked-dynamic union A union with a checked-dynamic arm (`unknown`, fetch's AbortSignal) maps to the checked-dynamic value when every other arm is one the checked-dynamic tree holds faithfully. Function arms were excluded, so `Progress | AbortSignal | undefined` was refused (SC2009). The tree boxes a function whose parameters check in and whose result converts out by reference (canBoxFuncIntoDyn), and `typeof x === "function"` narrows it back out to the typed function. Such arms now join the collapse. The corpus program checks calls, identity (`===` against the original), and the other arm's members against Node. pi's SessionManager.listAll takes `onProgressOrSignal?: SessionListProgress | AbortSignal`. The refusal rejected the whole SessionManager class. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.ts | 11 +++++ .../corpus/dyn-union-function-arms.ts.json | 6 +++ tests/corpus/dyn-union-function-arms.ts | 48 +++++++++++++++++++ .../dyn-union-function-arms.ts.json | 1 + 4 files changed, 66 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-union-function-arms.ts.json create mode 100644 tests/corpus/dyn-union-function-arms.ts create mode 100644 tests/harness/corpus-costs/dyn-union-function-arms.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..03d85ba7d 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -10,6 +10,7 @@ import { isDynTypedRefType, BOOL, bytesOf, + canBoxFuncIntoDyn, canConvertToDyn, canDynCheckTo, CHILD_T, @@ -8341,6 +8342,16 @@ function dynSubsumableUnionArm(arm: IrType, ctx: TypeMapperCtx): boolean { // `string | URL | Request`, fetch's own input type under @types/node. case "url": return true; + // A function the checked-dynamic tree can box (each parameter checks + // in, the result converts out) is held by reference there, and + // `typeof x === "function"` narrows it back out: `Progress | AbortSignal` + // beside fetch's handles. + case "func": + return canBoxFuncIntoDyn( + arm, + (id) => ctx.shapes.get(id), + (id) => ctx.unions.get(id), + ); case "record": case "array": // `T | Promise` with T bound to `unknown`: the checked-dynamic value diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-union-function-arms.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-union-function-arms.ts.json new file mode 100644 index 000000000..80675e5e6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-union-function-arms.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-union-function-arms.ts" + ], + "diags": [] +} diff --git a/tests/corpus/dyn-union-function-arms.ts b/tests/corpus/dyn-union-function-arms.ts new file mode 100644 index 000000000..e85cc2e6b --- /dev/null +++ b/tests/corpus/dyn-union-function-arms.ts @@ -0,0 +1,48 @@ +// A union of a function type with a value the checked-dynamic tree holds +// (AbortSignal, `unknown`) rides that representation: the function is held +// by reference, `typeof x === "function"` narrows it back out, and calls, +// identity and the other arm's members behave as in Node. + +type Progress = (loaded: number, total: number) => void; + +function listAll( + dirOrProgress?: string | Progress, + progressOrSignal?: Progress | AbortSignal, + signal?: AbortSignal, +): string { + const dir = typeof dirOrProgress === "string" ? dirOrProgress : "/default"; + const progress = + typeof dirOrProgress === "function" + ? dirOrProgress + : typeof progressOrSignal === "function" + ? progressOrSignal + : undefined; + const abortSignal = + typeof dirOrProgress === "string" || typeof progressOrSignal === "function" + ? signal + : (progressOrSignal ?? signal); + progress?.(1, 3); + progress?.(3, 3); + return `${dir} aborted=${abortSignal?.aborted ?? "none"}`; +} + +const seen: string[] = []; +const report: Progress = (loaded, total) => seen.push(`${loaded}/${total}`); +const controller = new AbortController(); +console.log(listAll()); +console.log(listAll("/x", report)); +console.log(listAll(report, controller.signal)); +controller.abort(); +console.log(listAll("/y", undefined, controller.signal)); +console.log(listAll(report, controller.signal), seen.join(" ")); + +function same(handler: Progress | AbortSignal | undefined): boolean { + return handler === report; +} +console.log(same(report), same(controller.signal), same(undefined), same((a, b) => void (a + b))); + +function describe(value: unknown | ((n: number) => string)): string { + if (typeof value === "function") return `function -> ${(value as (n: number) => string)(7)}`; + return `value ${JSON.stringify(value)}`; +} +console.log(describe((n: number) => `n=${n}`), describe({ a: 1 }), describe("s")); diff --git a/tests/harness/corpus-costs/dyn-union-function-arms.ts.json b/tests/harness/corpus-costs/dyn-union-function-arms.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/dyn-union-function-arms.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From be0e825ac1e2a1f8e2c88ce9bfafbb8d2788635a Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:35:56 -0700 Subject: [PATCH 134/230] Map `any` parameters of interface method and call signatures as checked values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A parameter a TypeScript program types `any` maps to the checked-dynamic slot for function declarations and spelled function types, but not for method or call signatures of interfaces and type literals. So `interface Tool { execute(id: string, params: any): Promise }` was refused (SC2009), along with every value of the interface, whenever no class implementing it gave it a class view. Method and call signatures in program source now take the same rule. pi's ToolDefinition declares `execute(toolCallId: string, params: Static, …)`. Under --node-islands, typebox's types resolve to `any`, so the parameter is `any`. The refusal rejected the AgentSession class through its `_customTools: ToolDefinition[]` field, and CreateAgentSessionRuntimeResult with it. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/frontend/type-mapper.ts | 12 ++++-- .../method-signature-any-params.ts.json | 6 +++ tests/corpus/method-signature-any-params.ts | 39 +++++++++++++++++++ .../method-signature-any-params.ts.json | 1 + 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/method-signature-any-params.ts.json create mode 100644 tests/corpus/method-signature-any-params.ts create mode 100644 tests/harness/corpus-costs/method-signature-any-params.ts.json diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 17f9d0d11..aa3af2b14 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -5218,10 +5218,12 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null // A parameter a TypeScript program types `any` takes whatever the // caller passes: the checked-dynamic slot, as an `any`-typed binding // already is (dynFallbackType). Declaration files keep their paths. - // Only function declarations, spelled function types and lambda - // parameters whose default is the only source of their type (`(n = - // lookup() ?? 5) => ...` with an `any` lookup): other lambdas and - // methods in object literals keep their existing checked-dynamic story. + // Only function declarations, spelled function types, method and call + // signatures of interfaces and type literals (`execute(params: any): + // Promise`), and lambda parameters whose default is the only source + // of their type (`(n = lookup() ?? 5) => ...` with an `any` lookup): + // other lambdas and methods in object literals keep their existing + // checked-dynamic story. if ( !pt && !rest && @@ -5230,6 +5232,8 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null decl.parent !== undefined && (ts.isFunctionDeclaration(decl.parent) || ts.isFunctionTypeNode(decl.parent) || + ts.isMethodSignatureDeclaration(decl.parent) || + ts.isCallSignatureDeclaration(decl.parent) || ((ts.isArrowFunction(decl.parent) || ts.isFunctionExpression(decl.parent)) && decl.initializer !== undefined && decl.type === undefined && diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/method-signature-any-params.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/method-signature-any-params.ts.json new file mode 100644 index 000000000..b3f7ed165 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/method-signature-any-params.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/method-signature-any-params.ts" + ], + "diags": [] +} diff --git a/tests/corpus/method-signature-any-params.ts b/tests/corpus/method-signature-any-params.ts new file mode 100644 index 000000000..e505ae78c --- /dev/null +++ b/tests/corpus/method-signature-any-params.ts @@ -0,0 +1,39 @@ +// A method or call signature of an interface whose parameter is typed `any` +// (directly, or through an alias that resolves to `any`) takes whatever the +// caller passes, as a function type's `any` parameter already does: the +// interface compiles, and implementations receive the checked-dynamic value. + +type Loose = any; + +interface Tool { + name: string; + execute(id: string, params: any, extra?: Loose): Promise; + describe(value: Loose): string; +} + +interface Formatter { + (value: any): string; +} + +const tools: Tool[] = [ + { + name: "count", + execute: async (id, params) => `${id} has ${Object.keys(params ?? {}).length} keys`, + describe: (value) => `count(${Array.isArray(value) ? "array" : typeof value})`, + }, +]; + +const format: Formatter = (value) => (typeof value === "number" ? value.toFixed(1) : String(value)); + +async function main(): Promise { + const inputs: unknown[] = [{ a: 1, b: [2, 3] }, "text", 42, null, [1, 2]]; + for (const tool of tools) { + for (const input of inputs) { + console.log(tool.name, await tool.execute("call", input), tool.describe(input)); + } + console.log(tool.name, await tool.execute("extra", { k: true }, { more: "yes" })); + } + console.log(format(3), format("x"), format(undefined)); +} + +main().catch((e: unknown) => console.log("failed", e instanceof Error ? e.message : String(e))); diff --git a/tests/harness/corpus-costs/method-signature-any-params.ts.json b/tests/harness/corpus-costs/method-signature-any-params.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/method-signature-any-params.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 3375304945e1b6e548d501b3c9d403f1c5d155fa Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:40:32 -0700 Subject: [PATCH 135/230] Erase lambdas that implement a generic method through their context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A lambda with no type parameters of its own, contextually typed by a generic signature (`send: (m) => …` for an interface's `send(m: Message): string`), takes that signature's type parameters: its parameter is `Message`. A generic lambda written with its own type parameters already lowers once, erased, with them bound to `unknown`, but this one tried to map `Message` with `T` unbound and was refused (SC2009). It now lowers the same way, erased, binding the contextual signature's type parameters to `unknown`. Parameters bound in the enclosing scope keep their bindings. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-functions.ts | 42 +++++++++++++++++++ .../generic-member-lambda-impls.ts.json | 6 +++ tests/corpus/generic-member-lambda-impls.ts | 40 ++++++++++++++++++ .../generic-member-lambda-impls.ts.json | 1 + 4 files changed, 89 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-lambda-impls.ts.json create mode 100644 tests/corpus/generic-member-lambda-impls.ts create mode 100644 tests/harness/corpus-costs/generic-member-lambda-impls.ts.json diff --git a/packages/compiler/src/frontend/lowering/lower-functions.ts b/packages/compiler/src/frontend/lowering/lower-functions.ts index bd9b5f55d..7f4c8dfb7 100644 --- a/packages/compiler/src/frontend/lowering/lower-functions.ts +++ b/packages/compiler/src/frontend/lowering/lower-functions.ts @@ -167,6 +167,23 @@ function declareFunctionArguments( } } +/** The type parameters a lambda without its own takes from a generic + * contextual signature, unbound in the current scope. */ +function contextualTypeParameterSymbols( + lowerer: Lowerer, + node: ts.ArrowFunction | ts.FunctionExpression, +): ts.Symbol[] { + const signatures = lowerer.checker.getCallSignatures(lowerer.typeOf(node)); + if (signatures.length !== 1) return []; + const symbols: ts.Symbol[] = []; + for (const parameter of signatures[0]!.getTypeParameters()) { + const symbol = parameter.getSymbol(); + if (!symbol || lowerer.typeParamBindings?.has(symbol)) return []; + symbols.push(symbol); + } + return symbols; +} + /** Lifts an arrow function / function expression / nested declaration / * object-literal shorthand method to a module-level function and yields * the `closure` expression creating it. */ @@ -202,6 +219,31 @@ export function lowerLambda( lowerer.typeParamBindings = previous; } } + // A lambda contextually typed by a GENERIC signature (`send: (m) => …` + // for `send(m: Msg): string`) takes that signature's type + // parameters as its own; it lowers once, erased, like a generic lambda. + if ( + (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && + !node.typeParameters?.nodes.length && + !lowerer.erasingLambdas.has(node) + ) { + const contextual = contextualTypeParameterSymbols(lowerer, node); + if (contextual.length > 0) { + const previous = lowerer.typeParamBindings; + const bindings = new Map(previous ?? []); + for (const symbol of contextual) bindings.set(symbol, DYN); + lowerer.typeParamBindings = bindings; + lowerer.erasingLambdas.add(node); + lowerer.erasingDepth++; + try { + return lowerLambda(lowerer, node, checkedReceiver); + } finally { + lowerer.erasingDepth--; + lowerer.erasingLambdas.delete(node); + lowerer.typeParamBindings = previous; + } + } + } const prepared = ts.isArrowFunction(node) || ts.isFunctionExpression(node) ? lowerer.preparedClassFactories.get(node) diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-lambda-impls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-lambda-impls.ts.json new file mode 100644 index 000000000..bc87c6ea0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-member-lambda-impls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/generic-member-lambda-impls.ts" + ], + "diags": [] +} diff --git a/tests/corpus/generic-member-lambda-impls.ts b/tests/corpus/generic-member-lambda-impls.ts new file mode 100644 index 000000000..fd25bae96 --- /dev/null +++ b/tests/corpus/generic-member-lambda-impls.ts @@ -0,0 +1,40 @@ +// An object literal implements an interface's generic method with a plain +// lambda (`send: (m) => …` for `send(m: Message): string`). The +// lambda takes the contextual signature's type parameters as its own and +// lowers once, erased, like a generic lambda: its parameters hold whatever +// each call passes. + +interface Message { + content: string; + details?: T; +} + +interface Sender { + name: string; + send(message: Message): string; + notify(message: Message): void; + pick(keys: K[], fallback: K): K; +} + +const log: string[] = []; + +function makeSender(name: string): Sender { + return { + name, + send: (message) => `${name} sent ${message.content} (${message.details === undefined ? "no details" : JSON.stringify(message.details)})`, + notify: (message) => { + log.push(`${name} notified ${message.content}`); + }, + pick: (keys, fallback) => keys[0] ?? fallback, + }; +} + +const senders: Sender[] = [makeSender("first"), makeSender("second")]; +for (const sender of senders) { + console.log(sender.send({ content: "hi", details: { n: 3 } })); + console.log(sender.send({ content: "bare" })); + console.log(sender.send({ content: "list", details: [1, 2] })); + sender.notify({ content: "ping", details: true }); + console.log(sender.pick(["a", "b"], "z"), sender.pick([], "z")); +} +console.log(log.join("; ")); diff --git a/tests/harness/corpus-costs/generic-member-lambda-impls.ts.json b/tests/harness/corpus-costs/generic-member-lambda-impls.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/generic-member-lambda-impls.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From 171214b2c214e71c6dda67b9401859dbf8b0e967 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:40:32 -0700 Subject: [PATCH 136/230] Discard the result of void generic methods called through class-backed interfaces A generic method called through an interface value that classes implement dispatches by name on the checked-dynamic receiver, and the result converts to the call's instantiated return type. For a void method, that conversion (`unknown` to `void`) was refused (SC1100). The result is now discarded, as JavaScript does. The dispatch helper selected overriding subclasses' erased instances with a ternary, which has no void form (an internal compiler error once the call got that far). A void method now selects them with if statements. Co-Authored-By: Claude Opus 5.5 --- .../lowering/class-dynamic-dispatch.ts | 50 ++++++++++++------- .../src/frontend/lowering/lower-calls.ts | 13 ++++- .../generic-interface-void-calls.ts.json | 6 +++ tests/corpus/generic-interface-void-calls.ts | 45 +++++++++++++++++ .../generic-interface-void-calls.ts.json | 1 + 5 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-interface-void-calls.ts.json create mode 100644 tests/corpus/generic-interface-void-calls.ts create mode 100644 tests/harness/corpus-costs/generic-interface-void-calls.ts.json diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index 2af84f8fb..7992f7fec 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -6793,24 +6793,41 @@ export class ClassDynamicDispatch { type: result, loc, }; + // A void method dispatches its overrides with statements: a ternary + // has no void form. + let voidDispatch: IrStmt[] = [{ kind: "exprStmt", expr: call, loc }]; for (const override of overrides.toReversed()) { const subType: IrType = { kind: "object", className: override.cls.def.name }; + const isOverride: IrExpr = { + kind: "instanceOf", + value: receiver, + className: override.cls.def.name, + type: BOOL, + loc, + }; + const overrideCall: IrExpr = { + kind: "call", + callee: override.callee, + args: [{ kind: "downcast", value: receiver, type: subType, loc }, ...args], + type: result, + loc, + }; + if (result.kind === "void") { + voidDispatch = [ + { + kind: "if", + cond: isOverride, + then: [{ kind: "exprStmt", expr: overrideCall, loc }], + else_: voidDispatch, + loc, + }, + ]; + continue; + } call = { kind: "ternary", - cond: { - kind: "instanceOf", - value: receiver, - className: override.cls.def.name, - type: BOOL, - loc, - }, - then: { - kind: "call", - callee: override.callee, - args: [{ kind: "downcast", value: receiver, type: subType, loc }, ...args], - type: result, - loc, - }, + cond: isOverride, + then: overrideCall, else_: call, type: result, loc, @@ -6818,10 +6835,7 @@ export class ClassDynamicDispatch { } let body: IrStmt[]; if (result.kind === "void") - body = [ - { kind: "exprStmt", expr: call, loc }, - { kind: "return", value: dynUndefinedExpr(loc), loc }, - ]; + body = [...voidDispatch, { kind: "return", value: dynUndefinedExpr(loc), loc }]; else { const boxed = lowerer.coerceToExpected(call, DYN); if (boxed.type.kind !== "dyn") return fence(); diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index c8f02562e..e2f7ade90 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -10073,7 +10073,18 @@ function lowerObjLitGenericMethodCall( type: DYN, loc, }; - return lowerer.coerceInto(call, invoked, lowerer.irTypeOf(call)); + const resultType = lowerer.irTypeOf(call); + // A void method's runtime result is discarded, as JavaScript does. + if (resultType.kind === "void") { + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: invoked, loc }], + result: { kind: "libCall", fn: "console.noop", args: [], type: VOID, loc }, + type: VOID, + loc, + }; + } + return lowerer.coerceInto(call, invoked, resultType); } } if (onInterface) { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-interface-void-calls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-interface-void-calls.ts.json new file mode 100644 index 000000000..a5c69b714 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/generic-interface-void-calls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/generic-interface-void-calls.ts" + ], + "diags": [] +} diff --git a/tests/corpus/generic-interface-void-calls.ts b/tests/corpus/generic-interface-void-calls.ts new file mode 100644 index 000000000..d810383f7 --- /dev/null +++ b/tests/corpus/generic-interface-void-calls.ts @@ -0,0 +1,45 @@ +// Calling a void generic method through an interface value that classes +// implement (the receiver is a checked-dynamic object, and the call +// dispatches by name to the class's erased method) discards the result, as +// JavaScript does, instead of converting it to `void`. + +interface Message { + content: string; + details?: T; +} + +interface Channel { + name: string; + publish(message: Message): void; + count(items: T[]): number; +} + +const delivered: string[] = []; + +class LogChannel implements Channel { + name: string; + constructor(name: string) { + this.name = name; + } + publish(message: Message): void { + delivered.push(`${this.name}:${message.content}:${message.details === undefined ? "-" : JSON.stringify(message.details)}`); + } + count(items: T[]): number { + return items.length; + } +} + +class LoudChannel extends LogChannel { + publish(message: Message): void { + super.publish({ content: message.content.toUpperCase(), details: message.details }); + } +} + +const channels: Channel[] = [new LogChannel("quiet"), new LoudChannel("loud")]; +for (const channel of channels) { + channel.publish({ content: "hello", details: [1, 2] }); + channel.publish({ content: "bare" }); + const result = channel.publish({ content: "typed", details: "x" }); + console.log(channel.name, result === undefined, channel.count(["a", "b", "c"])); +} +console.log(delivered.join(" | ")); diff --git a/tests/harness/corpus-costs/generic-interface-void-calls.ts.json b/tests/harness/corpus-costs/generic-interface-void-calls.ts.json new file mode 100644 index 000000000..1254aa6e4 --- /dev/null +++ b/tests/harness/corpus-costs/generic-interface-void-calls.ts.json @@ -0,0 +1 @@ +{ "plain": 4000 } From f4936d742cb0b99a4911571b42615f15716e6a61 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:02:53 -0500 Subject: [PATCH 137/230] Report kept direct references before the checks that skip them silently The direct-reference rewrite tested pureReceiver and runsAfter before the effectFree and createdBefore checks that report why a reference is kept, so a receiver with effects or a read that may run before the member is stored was skipped without its report line. The reporting checks now come first; every check still keeps the reference as written. --- .../src/frontend/shape-inference/static-objects.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/frontend/shape-inference/static-objects.ts b/packages/compiler/src/frontend/shape-inference/static-objects.ts index 6fb9c9b73..bd79941ab 100644 --- a/packages/compiler/src/frontend/shape-inference/static-objects.ts +++ b/packages/compiler/src/frontend/shape-inference/static-objects.ts @@ -1189,10 +1189,6 @@ export function planStaticObjectRewrites( if (parent === undefined || ts.isDeleteExpression(parent)) continue; if (!ts.isPropertyAccessExpression(node)) continue; if (topStatement(node) === null) continue; - // The receiver's evaluation is dropped, and the binding exists from the - // store on (a function) or from its declaration on (a class). - if (!pureReceiver(node.expression, node)) continue; - if (!runsAfter(node, storeOf.get(field.values[0]!)!)) continue; // The reference replaces the whole member expression: its receiver // must be one that evaluating runs no code. if (!effectFree(node.expression)) { @@ -1203,6 +1199,10 @@ export function planStaticObjectRewrites( kept.push(`reference ${node.getText()}: it may run before the member is stored`); continue; } + // The receiver's evaluation is dropped, and the binding exists from the + // store on (a function) or from its declaration on (a class). + if (!pureReceiver(node.expression, node)) continue; + if (!runsAfter(node, storeOf.get(field.values[0]!)!)) continue; if (node.getSourceFile() !== field.values[0]!.node!.getSourceFile()) { const text = crossFileReference(node, field.values[0]!.node!.getSourceFile(), name); if (text === null) continue; From c7fc9a580ec7698dec6669e7145474118bfc4a3c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:03:23 -0500 Subject: [PATCH 138/230] Fix CI --- .../src/frontend/shape-inference/static-objects.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/frontend/shape-inference/static-objects.ts b/packages/compiler/src/frontend/shape-inference/static-objects.ts index 424e51ba8..99df9fd19 100644 --- a/packages/compiler/src/frontend/shape-inference/static-objects.ts +++ b/packages/compiler/src/frontend/shape-inference/static-objects.ts @@ -1207,10 +1207,6 @@ export function planStaticObjectRewrites( if (parent === undefined || ts.isDeleteExpression(parent)) continue; if (!ts.isPropertyAccessExpression(node)) continue; if (topStatement(node) === null) continue; - // The receiver's evaluation is dropped, and the binding exists from the - // store on (a function) or from its declaration on (a class). - if (!pureReceiver(node.expression, node)) continue; - if (!runsAfter(node, storeOf.get(field.values[0]!)!)) continue; // The reference replaces the whole member expression: its receiver // must be one that evaluating runs no code. if (!effectFree(node.expression)) { @@ -1221,6 +1217,10 @@ export function planStaticObjectRewrites( kept.push(`reference ${node.getText()}: it may run before the member is stored`); continue; } + // The receiver's evaluation is dropped, and the binding exists from the + // store on (a function) or from its declaration on (a class). + if (!pureReceiver(node.expression, node)) continue; + if (!runsAfter(node, storeOf.get(field.values[0]!)!)) continue; if (node.getSourceFile() !== field.values[0]!.node!.getSourceFile()) { const text = crossFileReference(node, field.values[0]!.node!.getSourceFile(), name); if (text === null) continue; From 0b772d69eaa7797ad64bc7a87da648a29b7224f7 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:04:56 -0500 Subject: [PATCH 139/230] Fix CI --- .../src/frontend/shape-inference/static-objects.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/compiler/src/frontend/shape-inference/static-objects.ts b/packages/compiler/src/frontend/shape-inference/static-objects.ts index 6fb9c9b73..bd79941ab 100644 --- a/packages/compiler/src/frontend/shape-inference/static-objects.ts +++ b/packages/compiler/src/frontend/shape-inference/static-objects.ts @@ -1189,10 +1189,6 @@ export function planStaticObjectRewrites( if (parent === undefined || ts.isDeleteExpression(parent)) continue; if (!ts.isPropertyAccessExpression(node)) continue; if (topStatement(node) === null) continue; - // The receiver's evaluation is dropped, and the binding exists from the - // store on (a function) or from its declaration on (a class). - if (!pureReceiver(node.expression, node)) continue; - if (!runsAfter(node, storeOf.get(field.values[0]!)!)) continue; // The reference replaces the whole member expression: its receiver // must be one that evaluating runs no code. if (!effectFree(node.expression)) { @@ -1203,6 +1199,10 @@ export function planStaticObjectRewrites( kept.push(`reference ${node.getText()}: it may run before the member is stored`); continue; } + // The receiver's evaluation is dropped, and the binding exists from the + // store on (a function) or from its declaration on (a class). + if (!pureReceiver(node.expression, node)) continue; + if (!runsAfter(node, storeOf.get(field.values[0]!)!)) continue; if (node.getSourceFile() !== field.values[0]!.node!.getSourceFile()) { const text = crossFileReference(node, field.values[0]!.node!.getSourceFile(), name); if (text === null) continue; From 56e84177467483c007ac218a04bd248c885ccc48 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:35:07 -0700 Subject: [PATCH 140/230] Name the package declaration and its patch when a declared-type check fails A value that contradicts a type an npm package declares used to throw "expected number at $, got undefined" and nothing else. Such a check now names the declaration and the type patch that admits the value: profilekit@3.1.0 declares Profile.email as string (index.d.ts:7) if that declaration is wrong, patch it in package.json: ... Origins are found for values a package function returns under a written return type, for properties of records typed by a package-declared type (record projections into such a type are keyed by it), and for declaration-boundary casts, whose origin the projection records from the declaration-world checker. Generic declarations get none: their types come from the program's own instantiation. Checked conversions carry dynCheck.origin, an index into IrModule.declOrigins; the site's existing cold exception block calls scr_type_origin_note (new runtime unit behind the typeOrigins feature), which appends the note to the pending TypeError and derives the selector suffix and the admitted kind from the failure. Union extractions get the note per arm at compile time. The hot path is unchanged. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/dependencies.mdx | 8 + .../src/backend/executable-features.ts | 1 + packages/compiler/src/backend/llvm/emitter.ts | 5 + .../compiler/src/backend/llvm/expr-async.ts | 19 +- .../src/backend/native-link-info-core.ts | 1 + .../compiler/src/backend/native/contracts.ts | 1 + .../compiler/src/backend/native/executable.ts | 1 + .../compiler/src/backend/runtime-pack-core.ts | 2 + .../src/executable/executable-cache.ts | 1 + .../compiler/src/frontend/decl-projection.ts | 61 +++ .../src/frontend/lowering/coercions/state.ts | 9 +- .../frontend/lowering/coercions/structural.ts | 31 +- .../src/frontend/lowering/coercions/unions.ts | 36 +- .../src/frontend/lowering/decl-origin.ts | 392 ++++++++++++++++++ .../lowering/expressions/narrowing.ts | 54 ++- .../compiler/src/frontend/lowering/lowerer.ts | 100 ++++- .../src/frontend/lowering/union-narrow.ts | 17 +- packages/compiler/src/frontend/program.ts | 7 +- packages/compiler/src/index.ts | 1 + .../src/ir/function-references.test.ts | 1 + packages/compiler/src/ir/ir.ts | 10 + packages/compiler/src/ir/runtime-features.ts | 4 + packages/compiler/src/ir/validate.ts | 13 + .../compiler/src/native/sanitizer.test.ts | 1 + .../runtime-pack-matrix.mjs | 1 + packages/runtime/src/scr_type_origin.c | 154 +++++++ tests/fixtures/type-patches/unpatched/main.ts | 13 + .../type-patches/unpatched/package.json | 1 + tests/harness/type-patches.test.ts | 32 +- 29 files changed, 961 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/decl-origin.ts create mode 100644 packages/runtime/src/scr_type_origin.c create mode 100644 tests/fixtures/type-patches/unpatched/main.ts create mode 100644 tests/fixtures/type-patches/unpatched/package.json diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index 9b660c76f..58b0a0a1d 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -145,6 +145,14 @@ A JavaScript package can contain operations scriptc can't compile, often in func scriptc trusts declared types: a package's `.d.ts` files, its `@types/` package, and the JSDoc types in its JavaScript. A value that contradicts its declared type throws a `TypeError` instead of flowing on, so a package whose declarations are wrong can stop a program that runs under Node.js. A common case is a member declared `string` or `number` that is sometimes `undefined`. +When the type comes from a package's declarations, the error names the declaration and the patch that would admit the value: + +```text +Uncaught TypeError: expected string at $, got undefined + profilekit@3.1.0 declares Profile.email as string (index.d.ts:7) + if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } } +``` + Fix such a declaration in your project with a type patch in `package.json`: ```json:package.json diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index 2a6c6959e..b2deb374d 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -31,6 +31,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { ...(features.streamDyn ? { streamDyn: true } : {}), ...(features.handleProps ? { handleProps: true } : {}), ...(features.declAdopt ? { declAdopt: true } : {}), + ...(features.typeOrigins ? { typeOrigins: true } : {}), ...(features.dynForAwait ? { dynForAwait: true } : {}), ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), ...(features.dynBuffer ? { dynBuffer: true } : {}), diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index f1f1fa7d6..f3b2f44fe 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -3700,6 +3700,9 @@ export class LlEmitter { passThrough?: { name: string; type: IrType }, poll = false, guard?: string, + /** Runs at the start of the cold unwind block, before the unwind (a + * package-declared check's origin note). */ + onUnwind?: () => void, ): void { const through = passThrough !== undefined && @@ -3724,6 +3727,7 @@ export class LlEmitter { const lk = B.newLabel("exc.k"); B.condBr(p, lu, lk); B.startBlock(lu); + onUnwind?.(); this.emitUnwind(through); B.startBlock(lk); if (skip !== undefined) { @@ -3800,6 +3804,7 @@ export class LlEmitter { B.condBr(cold, lu, lk); } B.startBlock(lu); + onUnwind?.(); this.emitUnwind(through); B.startBlock(lk); if (skip !== undefined) { diff --git a/packages/compiler/src/backend/llvm/expr-async.ts b/packages/compiler/src/backend/llvm/expr-async.ts index ee9ff2983..28a4cf57f 100644 --- a/packages/compiler/src/backend/llvm/expr-async.ts +++ b/packages/compiler/src/backend/llvm/expr-async.ts @@ -372,6 +372,16 @@ export function emitSerializationExpr( // the dummy harmlessly. // `JSON.parse(text) as T` fuses into one schema-directed parse that // falls back to exactly this route whenever the fast path declines. + // A package-declared target: the cold path names the declaration in + // the pending TypeError (scr_type_origin.c). + const origin = e.origin === undefined ? undefined : host.mod.declOrigins?.[e.origin]; + const note = + origin === undefined + ? undefined + : (): void => { + host.declare("declare void @scr_type_origin_note(ptr)"); + B.line(`call void @scr_type_origin_note(ptr ${host.cstr(origin)})`); + }; const parsed = e.value; if (!e.preserveRefs && parsed.kind === "libCall" && parsed.fn === "json.parse") { const fused = host.dyn.jsonParseHelper(e.type); @@ -380,7 +390,7 @@ export function emitSerializationExpr( const r = B.tmp(); B.line(`${r} = call ptr @${fused}(ptr ${text.name})`); const fusedOut = host.own({ name: r, type: e.type }); - host.emitPendingCheck(); + host.emitPendingCheck(undefined, false, undefined, note); return fusedOut; } } @@ -394,14 +404,14 @@ export function emitSerializationExpr( } const dynV = emitBorrowedInput(host, e.value); const helper = host.dyn.dynCheckHelper(e.type, e.preserveRefs); - if (isDynTypedRefType(e.type)) return emitClassCheck(host, e.type, dynV.name, helper); + if (isDynTypedRefType(e.type)) return emitClassCheck(host, e.type, dynV.name, helper, note); const ty = host.llType(e.type); const t = B.tmp(); B.line( `${t} = call ${ty === "i1" ? "zeroext i1" : ty} @${helper}(ptr ${dynV.name}, ptr null)`, ); const out = host.own({ name: t, type: e.type }); - host.emitPendingCheck(); + host.emitPendingCheck(undefined, false, undefined, note); return out; } default: { @@ -420,6 +430,7 @@ function emitClassCheck( type: IrType & { kind: "object" }, value: string, helper: string, + note?: () => void, ): LlValue { const B = host.B; const slot = B.slot(); @@ -460,7 +471,7 @@ function emitClassCheck( B.line(`${result} = call ptr @${helper}(ptr ${value}, ptr null)`); B.line(`store ptr ${result}, ptr ${slot}`); // A throwing check returns null, so the unwind has nothing to release. - host.emitPendingCheck(); + host.emitPendingCheck(undefined, false, undefined, note); B.br(done); B.startBlock(done); const out = B.tmp(); diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index f250a2ac6..de92ca584 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -55,6 +55,7 @@ export interface NativeLinkFeatures { * Absent (false) for programs without them. */ handleProps?: boolean; declAdopt?: boolean; + typeOrigins?: 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 e3f97710f..9aeff6138 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -126,6 +126,7 @@ export interface CcOptions { /** Own properties JavaScript adds to children: compiles scr_dyn_handle_props.c. */ handleProps?: boolean; declAdopt?: boolean; + typeOrigins?: 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 7f989a5e4..2fba9ca4b 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -300,6 +300,7 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [ 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"), + unit((u) => u.opts.typeOrigins === true, "scr_type_origin.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-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 22bba85d2..8457b7295 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -87,6 +87,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures { streamDyn: boolean; handleProps: boolean; declAdopt: boolean; + typeOrigins: boolean; dynForAwait: boolean; eventsOnceSignal: boolean; dynBuffer: boolean; @@ -287,6 +288,7 @@ export function effectiveRuntimeFeatures( streamDyn: features.streamDyn === true, handleProps: features.handleProps === true, declAdopt: features.declAdopt === true, + typeOrigins: features.typeOrigins === true, dynForAwait: features.dynForAwait === true, eventsOnceSignal: features.eventsOnceSignal === true, dynBuffer: features.dynBuffer === true, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index f26c4c89a..498ee7e19 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -74,6 +74,7 @@ export interface EarlyExecutableNativeFeatures { streamDyn?: boolean; handleProps?: boolean; declAdopt?: boolean; + typeOrigins?: boolean; dynForAwait?: boolean; eventsOnceSignal?: boolean; dynBuffer?: boolean; diff --git a/packages/compiler/src/frontend/decl-projection.ts b/packages/compiler/src/frontend/decl-projection.ts index a999559c2..eebe1f773 100644 --- a/packages/compiler/src/frontend/decl-projection.ts +++ b/packages/compiler/src/frontend/decl-projection.ts @@ -27,6 +27,59 @@ * scriptc-perf/results/design-decl-boundary/DESIGN.md. */ import * as ts from "./ts7/adapter.js"; +import { declarationSelector, originAt } from "./lowering/decl-origin.js"; + +/** Per user file, the origin (lowering/decl-origin.ts) of each boundary + * cast in source order: the package declaration the cast's type comes + * from, so a failing cast names it. Set by planDeclProjection, reset per + * program load. */ +const boundaryOrigins = new Map(); + +export function resetDeclBoundaryOrigins(): void { + boundaryOrigins.clear(); +} + +/** The origin of the `index`-th boundary cast of a file, or null. */ +export function declBoundaryOrigin(fileName: string, index: number): string | null { + return boundaryOrigins.get(fileName)?.[index] ?? null; +} + +/** The package declaration a projected expression's type comes from, in + * the declaration world. */ +function castOrigin(checker: ts.TypeChecker, node: ts.Node, type: ts.Type): string | null { + let inner = node as ts.Expression; + while ( + ts.isParenthesizedExpression(inner) || + ts.isAwaitExpression(inner) || + ts.isNonNullExpression(inner) + ) + inner = inner.expression; + let decl: ts.Node | undefined; + let suffix = ""; + try { + if (ts.isCallExpression(inner) || ts.isNewExpression(inner)) { + decl = checker.getResolvedSignature(inner)?.declaration; + suffix = "()"; + } else { + const name = ts.isPropertyAccessExpression(inner) + ? inner.name + : ts.isIdentifier(inner) + ? inner + : null; + if (name === null) return null; + let symbol = checker.getSymbolAtLocation(name); + if (symbol !== undefined && symbol.flags & ts.SymbolFlags.Alias) + symbol = checker.getAliasedSymbol(symbol); + decl = symbol?.declarations[0]; + } + if (decl === undefined) return null; + const selector = declarationSelector(decl); + if (selector === null) return null; + return originAt(decl, `${selector}${suffix}`, checker.typeToString(type)); + } catch { + return null; + } +} /** Alias names of projected types start with this prefix; the lowering * recognizes boundary casts and parameters by it. */ @@ -573,6 +626,7 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | const declSf = input.declProgram.getSourceFile(file); if (jsSf === undefined || declSf === undefined || jsSf.text !== declSf.text) continue; const fileEdits: Edit[] = []; + const fileOrigins: (string | null)[] = []; /** Candidate expressions (by js node) with their declared type, named * only for the casts that survive suppression. */ const exprCandidates = new Map(); @@ -946,6 +1000,12 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | const candidate = exprCandidates.get(e)!; const alias = candidate === "any" ? "any" : printer.project(candidate); if (alias === null) continue; + // Only casts to a projected alias are boundary casts the lowering + // counts (an `as any` cast is not one), so only they take a slot. + if (candidate !== "any") { + const twin = twins.get(e); + fileOrigins.push(twin === undefined ? null : castOrigin(declChecker, twin, candidate)); + } const start = ts.tokenStartOfNode(e); const stmt = e.parent; let prefix = "("; @@ -980,6 +1040,7 @@ export function planDeclProjection(input: DeclProjectionInput): DeclProjection | if (fileEdits.length === 0) continue; edits.set(file, fileEdits); + if (fileOrigins.some((o) => o !== null)) boundaryOrigins.set(file, fileOrigins); } if (edits.size === 0) return null; diff --git a/packages/compiler/src/frontend/lowering/coercions/state.ts b/packages/compiler/src/frontend/lowering/coercions/state.ts index 76e5ac434..9fa815081 100644 --- a/packages/compiler/src/frontend/lowering/coercions/state.ts +++ b/packages/compiler/src/frontend/lowering/coercions/state.ts @@ -16,7 +16,14 @@ export class CoercionState { // The extracted arm of each checked (non-defaulting) extraction helper. readonly checkedNarrowArms = new Map< string, - { unionId: string; tag: number; target: IrType; widens?: true; message?: string } + { + unionId: string; + tag: number; + target: IrType; + widens?: true; + message?: string; + origin?: string; + } >(); readonly retags = new Map(); readonly narrows = new Map(); diff --git a/packages/compiler/src/frontend/lowering/coercions/structural.ts b/packages/compiler/src/frontend/lowering/coercions/structural.ts index 93ae1c231..76c6c9c3b 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural.ts @@ -1,3 +1,4 @@ +import { fieldOrigin } from "../decl-origin.js"; import { keyedHelperName } from "../stable-names.js"; import { constructedValue } from "../constructor-returns.js"; import { countedFor, dynUndefinedExpr, varRef } from "../../../ir/build.js"; @@ -394,7 +395,13 @@ export function recordWidthHelper( // orphan one). const plan = lowerer.recordWidthPlan(fromId, toId); if (!plan) return null; - const key = `rec:${fromId}:${toId}`; + // Package code converting into a package-declared record type (an + // object literal returned under `@returns {HelpContext}`): its field + // extractions name the declared fields (decl-origin.ts), so the helper + // is keyed by that type too. Every other projection is shared as before. + const declared = lowerer.coercionRecordOrigin(); + const key = + declared === null ? `rec:${fromId}:${toId}` : `rec:${fromId}:${toId}:origin:${declared.name}`; const existing = lowerer.valueHelpers.get(key); if (existing) return existing; const name = keyedHelperName(lowerer, "%rec.width", key); @@ -402,7 +409,21 @@ export function recordWidthHelper( // (self-referential shapes) resolves to this helper itself. lowerer.valueHelpers.set(key, name); const fromT: IrType = { kind: "record", shapeId: fromId }; - lowerer.liftedFns.push(buildRecordProjection(lowerer, name, fromT, to, plan, loc)); + const outerNode = lowerer.coercionOriginNode; + const outerField = lowerer.coercionFieldOrigin; + // Nested conversions see only their own field's origin. + lowerer.coercionOriginNode = null; + lowerer.coercionFieldOrigin = null; + try { + lowerer.liftedFns.push( + buildRecordProjection(lowerer, name, fromT, to, plan, loc, (field) => + declared === null ? null : fieldOrigin(lowerer.checker, declared, field), + ), + ); + } finally { + lowerer.coercionOriginNode = outerNode; + lowerer.coercionFieldOrigin = outerField; + } return name; } @@ -799,6 +820,7 @@ function buildRecordProjection( target: IrRecordShape, plan: Map, loc: SrcLoc, + originOf: (field: string) => string | null = () => null, ): IrFunction { const parameter = source.kind === "record" ? "r" : "o"; const localId = parameter + ".0"; @@ -855,7 +877,12 @@ function buildRecordProjection( type: conversion.src, loc, }; + // Only the field's own extraction (a plain narrow) carries the + // declared field's origin; nested conversions are shared. + lowerer.coercionFieldOrigin = + conversion.lift.how === "narrow" ? originOf(field.name) : null; const lifted = lowerer.applyWidthLift(conversion.lift, read, field.type, loc); + lowerer.coercionFieldOrigin = null; return { name: field.name, value: diff --git a/packages/compiler/src/frontend/lowering/coercions/unions.ts b/packages/compiler/src/frontend/lowering/coercions/unions.ts index 6bd72e989..316b1ecad 100644 --- a/packages/compiler/src/frontend/lowering/coercions/unions.ts +++ b/packages/compiler/src/frontend/lowering/coercions/unions.ts @@ -1,3 +1,4 @@ +import { renderOriginNote } from "../decl-origin.js"; import { keyedHelperName } from "../stable-names.js"; import { buildUnionNarrow, unionNarrowFailureMessage } from "../union-narrow.js"; import { isSafeToRepeat } from "../expressions/evaluation-safety.js"; @@ -408,7 +409,24 @@ export function narrowedArmHelper( if (!from || isUnitType(target)) return null; const tag = lowerer.armTag(fromId, target); if (tag < 0) return null; - const key = message === undefined ? `${fromId}:${tag}` : `${fromId}:${tag}:${message}`; + // A slot in package code typed by a package declaration: the TypeError + // names the declaration (decl-origin.ts). Other extractions keep their + // shared helper. + const originNode = + message === undefined && lowerer.coercionOriginUnion === fromId + ? lowerer.coercionOriginNode + : null; + const origin = + message !== undefined + ? null + : (lowerer.coercionFieldOrigin ?? + (originNode === null ? null : lowerer.slotOriginOf(originNode))); + const key = + message !== undefined + ? `${fromId}:${tag}:${message}` + : origin !== null + ? `${fromId}:${tag}:origin:${origin}` + : `${fromId}:${tag}`; const existing = lowerer.coercions.narrows.get(key); if (existing) { lowerer.coercions.checkedNarrows.add(existing); @@ -425,6 +443,8 @@ export function narrowedArmHelper( (type) => lowerer.fmt(type), null, message ?? null, + null, + origin, ); if (!fn) throw new InternalCompilerError("lowerer bug: invalid checked union extraction"); lowerer.coercions.checkedNarrowArms.set(name, { @@ -432,6 +452,7 @@ export function narrowedArmHelper( tag, target: from.arms[tag]!, ...(message === undefined ? {} : { message }), + ...(origin === null ? {} : { origin }), }); lowerer.liftedFns.push(fn); return name; @@ -516,8 +537,19 @@ export function checkedNarrowCall( let failure: IrExpr | null = null; for (let tag = from.arms.length - 1; tag >= 0; tag--) { if (tag === arm.tag) continue; + const failing = from.arms[tag]!; const message = - arm.message ?? unionNarrowFailureMessage(from.arms[tag]!, (t) => lowerer.fmt(t)); + (arm.message ?? unionNarrowFailureMessage(failing, (t) => lowerer.fmt(t))) + + (arm.origin === undefined + ? "" + : renderOriginNote( + arm.origin, + isUnitType(failing) + ? failing.kind === "undefinedT" + ? "undefined" + : "null" + : lowerer.fmt(failing), + )); const thrown = nodeThrowExpr(1, "", message, type, loc); failure = failure === null diff --git a/packages/compiler/src/frontend/lowering/decl-origin.ts b/packages/compiler/src/frontend/lowering/decl-origin.ts new file mode 100644 index 000000000..60510ecec --- /dev/null +++ b/packages/compiler/src/frontend/lowering/decl-origin.ts @@ -0,0 +1,392 @@ +/* Origins of package-declared types at checked conversions. + * + * scriptc enforces declared types: a value that contradicts the type an + * npm package declares (its .d.ts, @types package or JSDoc) throws a + * TypeError where Node runs on. When the declaration is what is wrong, the + * fix is a type patch (package.json scriptc.typePatches), so the error + * names the declaration and the patch that would admit the value: + * + * TypeError: expected number at $, got undefined + * termctx@1.2.0 declares Terminal.columns() as number (index.js:14) + * if that declaration is wrong, patch it in package.json: "scriptc": + * { "typePatches": { "termctx@^1.2.0": { "declarations": + * { "Terminal.columns()": "| undefined" } } } } + * + * The origin is found for three kinds of slots: a value a package function + * returns under a written return type, a property of an object literal + * typed by a package-declared type, and a declaration-boundary cast (a + * value user code receives from a package; decl-projection.ts records + * those with originAt). Generic declarations get none: their types come + * from how the program instantiates them. Checks in + * code that names no package declaration carry no origin, so programs + * without npm packages lower exactly as before. + * + * The origin string's fields (separated by \x1f) are read by the runtime + * (scr_type_origin.c) for checked conversions, whose path and actual kind + * are only known when they fail, and rendered here for union extractions, + * whose failing arm is known statically. */ + +import type { IrExpr, IrModule } from "../../ir/ir.js"; +import { everyModuleNode } from "../../ir/traverse.js"; +import { trackedReadFile } from "../input-tracker.js"; +import * as ts from "../ts7/adapter.js"; + +export const ORIGIN_SEPARATOR = "\x1f"; + +interface PackageFile { + pkg: string; + version: string; + /** The file relative to the package root. */ + rel: string; +} + +const versions = new Map(); + +function packageFileOf(fileName: string): PackageFile | null { + const path = fileName.split("\\").join("/"); + const marker = "/node_modules/"; + const at = path.lastIndexOf(marker); + if (at < 0) return null; + const parts = path.slice(at + marker.length).split("/"); + const nameParts = parts[0]!.startsWith("@") ? 2 : 1; + if (parts.length <= nameParts) return null; + let pkg = parts.slice(0, nameParts).join("/"); + const root = path.slice(0, at + marker.length) + pkg; + let versionRoot = root; + if (pkg.startsWith("@types/")) { + // A twin describes the runtime package installed beside it. + const bare = pkg.slice("@types/".length); + const i = bare.indexOf("__"); + pkg = i > 0 ? `@${bare.slice(0, i)}/${bare.slice(i + 2)}` : bare; + versionRoot = path.slice(0, at + marker.length) + pkg; + } + let version = versions.get(versionRoot); + if (version === undefined) { + version = ""; + try { + const parsed = JSON.parse(trackedReadFile(`${versionRoot}/package.json`) ?? "{}") as { + version?: unknown; + }; + if (typeof parsed.version === "string") version = parsed.version; + } catch { + version = ""; + } + versions.set(versionRoot, version); + } + return { pkg, version, rel: parts.slice(nameParts).join("/") }; +} + +/** True for a node in an installed package's file. */ +export function inPackageFile(node: ts.Node): boolean { + return packageFileOf(ts.getSourceFileOfNode(node).fileName) !== null; +} + +function lineOf(node: ts.Node): number { + const sf = ts.getSourceFileOfNode(node); + return ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(node)).line + 1; +} + +function encode(file: PackageFile, selector: string, where: string, declared: string): string { + const range = file.version === "" ? "*" : `^${file.version}`; + return [file.pkg, file.version, range, selector, where, declared.replace(/\s+/g, " ")].join( + ORIGIN_SEPARATOR, + ); +} + +function nameOf(name: ts.Node | undefined): string | null { + if (name === undefined) return null; + if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isPrivateIdentifier(name)) + return name.text; + return null; +} + +/** `module.exports` → "export="; `exports.x` / `module.exports.x` → "x". */ +function exportTarget(left: ts.Node): string | null { + if (!ts.isPropertyAccessExpression(left)) return null; + const obj = left.expression; + const prop = nameOf(left.name); + if (prop === null) return null; + if (ts.isIdentifier(obj) && obj.text === "module" && prop === "exports") return "export="; + if (ts.isIdentifier(obj) && obj.text === "exports") return prop; + if ( + ts.isPropertyAccessExpression(obj) && + ts.isIdentifier(obj.expression) && + obj.expression.text === "module" && + nameOf(obj.name) === "exports" + ) + return prop; + return null; +} + +/** The selector naming a function-like declaration (without `()`), or + * null when it has no name a selector can reach. */ +function functionSelector(fn: ts.Node): string | null { + if (ts.isFunctionDeclaration(fn)) return nameOf(fn.name); + if (ts.isMethodDeclaration(fn) || ts.isGetAccessor(fn)) { + const method = nameOf(fn.name); + const holder = fn.parent; + if (method === null || holder === undefined) return null; + if (ts.isClassDeclaration(holder) || ts.isClassExpression(holder)) { + const cls = nameOf(holder.name); + return cls === null ? null : `${cls}.${method}`; + } + if (ts.isObjectLiteralExpression(holder)) { + const owner = valueSelector(holder); + return owner === null ? null : `${owner}.${method}`; + } + return null; + } + if (ts.isFunctionExpression(fn) || ts.isArrowFunction(fn)) return valueSelector(fn); + return null; +} + +/** The selector of the declaration an expression is the value of. */ +function valueSelector(value: ts.Node): string | null { + let e = value; + while (e.parent !== undefined && ts.isParenthesizedExpression(e.parent)) e = e.parent; + const p = e.parent; + if (p === undefined) return null; + if (ts.isVariableDeclaration(p) && p.initializer === e) return nameOf(p.name); + if ( + ts.isBinaryExpression(p) && + p.right === e && + p.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) + return exportTarget(p.left); + if (ts.isPropertyAssignment(p) && p.initializer === e && p.parent !== undefined) { + const name = nameOf(p.name); + const owner = ts.isObjectLiteralExpression(p.parent) ? valueSelector(p.parent) : null; + return name === null || owner === null ? null : `${owner}.${name}`; + } + return null; +} + +/** True when a function-like declaration writes its return type: a type + * annotation, or a JSDoc @returns tag. */ +function writesReturnType(fn: ts.Node): boolean { + if ((fn as ts.FunctionDeclaration).type !== undefined) return true; + const sf = ts.getSourceFileOfNode(fn); + let owner: ts.Node = fn; + // The JSDoc of `exports.f = function`, `const f = () =>` sits on the + // statement. + while ( + owner.parent !== undefined && + !ts.isSourceFile(owner.parent) && + !ts.isBlock(owner.parent) && + !ts.isClassDeclaration(owner.parent) && + !ts.isClassExpression(owner.parent) && + !ts.isObjectLiteralExpression(owner.parent) + ) + owner = owner.parent; + const leading = sf.text.slice(owner.pos, ts.tokenStartOfNode(owner)); + const doc = leading.lastIndexOf("/**"); + return doc >= 0 && /@returns?\s*\{/.test(leading.slice(doc)); +} + +function enclosingFunction(node: ts.Node): ts.Node | null { + for (let p = node.parent; p !== undefined; p = p.parent) { + if ( + ts.isFunctionDeclaration(p) || + ts.isFunctionExpression(p) || + ts.isArrowFunction(p) || + ts.isMethodDeclaration(p) || + ts.isGetAccessor(p) + ) + return p; + if (ts.isSourceFile(p) || ts.isClassDeclaration(p)) return null; + } + return null; +} + +function typeText(checker: ts.TypeChecker, type: ts.Type | undefined): string { + if (type === undefined) return ""; + try { + return checker.typeToString(type); + } catch { + return ""; + } +} + +/** The origin of the slot a value expression in a package file flows + * into, or null when no package declaration types that slot. */ +export function slotOrigin(checker: ts.TypeChecker, node: ts.Node): string | null { + let value = node; + while (value.parent !== undefined && ts.isParenthesizedExpression(value.parent)) + value = value.parent; + const parent = value.parent; + if (parent === undefined) return null; + // A property of an object literal typed by a declared type. + if ( + (ts.isPropertyAssignment(parent) && parent.initializer === value) || + ts.isShorthandPropertyAssignment(value) || + (ts.isShorthandPropertyAssignment(parent) && parent.name === value) + ) { + const property = ts.isShorthandPropertyAssignment(value) ? value : parent; + const literal = property.parent; + const name = nameOf((property as ts.PropertyAssignment).name); + if (literal === undefined || !ts.isObjectLiteralExpression(literal) || name === null) + return null; + const contextual = checker.getContextualType(literal); + if (contextual === undefined) return null; + const typeSymbol = contextual.getAliasSymbol() ?? contextual.getSymbol(); + const typeName = typeSymbol === undefined ? "" : ts.symbolName(typeSymbol); + if (!/^[A-Za-z_$][\w$]*$/.test(typeName)) return null; + const symbol = checker.getPropertyOfType(contextual, name); + const decl = symbol?.declarations[0]; + if (symbol === undefined || decl === undefined || genericDeclaration(decl)) return null; + const file = packageFileOf(ts.getSourceFileOfNode(decl).fileName); + if (file === null) return null; + return encode( + file, + `${typeName}.${name}`, + `${file.rel}:${lineOf(decl)}`, + typeText(checker, checker.getTypeOfSymbol(symbol)), + ); + } + // A value a package function returns under its written return type. + let fn: ts.Node | null = null; + if (ts.isReturnStatement(parent) && parent.expression === value) fn = enclosingFunction(parent); + else if (ts.isArrowFunction(parent) && parent.body === value) fn = parent; + if (fn === null || !writesReturnType(fn)) return null; + const file = packageFileOf(ts.getSourceFileOfNode(fn).fileName); + const selector = functionSelector(fn); + if (file === null || selector === null) return null; + let declared = ""; + try { + const signature = checker.getSignatureFromDeclaration(fn as ts.SignatureDeclaration); + declared = + signature === undefined ? "" : typeText(checker, checker.getReturnTypeOfSignature(signature)); + } catch { + declared = ""; + } + return encode(file, `${selector}()`, `${file.rel}:${lineOf(fn)}`, declared); +} + +/** A package-declared record type a value is converted to (the + * contextual type of a value in package code, such as an object literal + * returned under `@returns {HelpContext}`), or null. */ +export interface RecordOrigin { + type: ts.Type; + name: string; +} + +export function recordOrigin(checker: ts.TypeChecker, node: ts.Node): RecordOrigin | null { + const contextual = checker.getContextualType(node); + if (contextual === undefined) return null; + const symbol = contextual.getAliasSymbol() ?? contextual.getSymbol(); + const name = symbol === undefined ? "" : ts.symbolName(symbol); + if (!/^[A-Za-z_$][\w$]*$/.test(name)) return null; + const decl = symbol?.declarations[0]; + if ( + decl === undefined || + genericDeclaration(decl) || + packageFileOf(ts.getSourceFileOfNode(decl).fileName) === null + ) + return null; + return { type: contextual, name }; +} + +/** The origin of one field of a package-declared record type. */ +export function fieldOrigin( + checker: ts.TypeChecker, + record: RecordOrigin, + field: string, +): string | null { + const symbol = checker.getPropertyOfType(record.type, field); + const decl = symbol?.declarations[0]; + if (symbol === undefined || decl === undefined || genericDeclaration(decl)) return null; + const file = packageFileOf(ts.getSourceFileOfNode(decl).fileName); + if (file === null) return null; + return encode( + file, + `${record.name}.${field}`, + `${file.rel}:${lineOf(decl)}`, + typeText(checker, checker.getTypeOfSymbol(symbol)), + ); +} + +/** The origin of a declaration in a package file named by `selector`. */ +export function originAt(decl: ts.Node, selector: string, declared: string): string | null { + const file = packageFileOf(ts.getSourceFileOfNode(decl).fileName); + if (file === null) return null; + return encode(file, selector, `${file.rel}:${lineOf(decl)}`, declared); +} + +/** True when a declaration or the class, interface or type alias holding + * it has type parameters: its declared type then comes from how the + * program instantiates it (a lying `z.custom()`), not from the package. */ +function genericDeclaration(decl: ts.Node): boolean { + for (let n: ts.Node | undefined = decl; n !== undefined && !ts.isSourceFile(n); n = n.parent) { + const params = (n as ts.FunctionDeclaration).typeParameters; + if (params !== undefined && params.nodes.length > 0) return true; + } + return false; +} + +/** The selector of a declaration in a declaration file: a function, a + * member of a class, interface or named object type, or null (also for + * generic declarations, whose types the program supplies). */ +export function declarationSelector(decl: ts.Node): string | null { + if (genericDeclaration(decl)) return null; + const own = nameOf((decl as ts.FunctionDeclaration).name); + if (own === null) return null; + if (ts.isFunctionDeclaration(decl) || ts.isVariableDeclaration(decl)) return own; + let holder = decl.parent; + if (holder !== undefined && ts.isTypeLiteralNode(holder)) holder = holder.parent; + if ( + holder !== undefined && + (ts.isClassDeclaration(holder) || + ts.isInterfaceDeclaration(holder) || + ts.isTypeAliasDeclaration(holder)) + ) { + const owner = nameOf(holder.name); + return owner === null ? null : `${owner}.${own}`; + } + return null; +} + +/** The note a union extraction's TypeError carries for a failing `arm` + * (the same text the runtime renders for checked conversions). */ +export function renderOriginNote(origin: string, arm: string): string { + const [pkg = "", version = "", range = "", selector = "", where = "", declared = ""] = + origin.split(ORIGIN_SEPARATOR); + return ( + `\n ${pkg}${version === "" ? "" : `@${version}`} declares ${selector}` + + `${declared === "" ? "" : ` as ${declared}`} (${where})` + + `\n if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "${pkg}@${range}": { "declarations": { "${selector}": "| ${arm}" } } } }` + ); +} + +/** Keeps only the origins the module's checks still reference (a coercion + * whose check was later replaced leaves its origin behind), renumbered in + * first-use order; drops the table when none is left, so the runtime unit + * is not linked for nothing. */ +export function compactDeclOrigins(mod: IrModule): void { + const table = mod.declOrigins ?? []; + const remap = new Map(); + const used: string[] = []; + // A node the IR shares at several places is renumbered once. + const checks = new Set(); + everyModuleNode(mod, { + expr: (e) => { + if (e.kind === "dynCheck" && e.origin !== undefined) { + // An exact capsule check never throws: nothing to name. + if (e.exact === true) { + delete e.origin; + return true; + } + checks.add(e); + if (!remap.has(e.origin)) { + remap.set(e.origin, used.length); + used.push(table[e.origin]!); + } + } + return true; + }, + stmt: () => true, + type: () => true, + }); + for (const check of checks) check.origin = remap.get(check.origin!)!; + if (used.length === 0) delete mod.declOrigins; + else mod.declOrigins = used; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 9f0559672..5c1b276f7 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -1,4 +1,5 @@ /* Type narrowing of lowered values, conditional and as-expressions, and runtime-optional receiver reads. */ +import { declBoundaryOrigin } from "../../decl-projection.js"; import * as ts from "../../ts7/adapter.js"; import { type Lowerer, dynFallbackType } from "../lowerer.js"; import { @@ -1531,7 +1532,7 @@ export function lowerAsExpression( // 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)); + const adopted = declBoundaryCast(lowerer, inner, target, locOf(expr), expr); if (adopted) return adopted; } // An all-`unknown`-fields record target (`err as { code?: unknown }` @@ -1639,11 +1640,44 @@ function holdsReference(lowerer: Lowerer, t: IrType, seen = new Set()): * 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. */ +const boundaryCastOrder = new WeakMap>(); + +/** The origin the projection recorded for a boundary cast (the package + * declaration its type comes from, decl-projection.ts), found by the + * cast's position among the file's boundary casts in source order. */ +function recordedBoundaryOrigin(cast: ts.Node): string | null { + const sf = ts.getSourceFileOfNode(cast); + let order = boundaryCastOrder.get(sf); + if (order === undefined) { + const found = new Map(); + const visit = (node: ts.Node): void => { + if (ts.isAsExpression(node) && isDeclBoundaryTypeNode(node.type)) found.set(node, found.size); + ts.forEachChild( + node, + (child) => { + visit(child); + return undefined; + }, + (list) => { + for (const child of list.nodes) visit(child); + return undefined; + }, + ); + }; + visit(sf); + order = found; + boundaryCastOrder.set(sf, order); + } + const index = order.get(cast); + return index === undefined ? null : declBoundaryOrigin(sf.fileName, index); +} + function declBoundaryCast( lowerer: Lowerer, inner: IrExpr, target: IrType, loc: SrcLoc, + cast: ts.Node, ): IrExpr | null { if ( !canDynCheckTo( @@ -1654,8 +1688,23 @@ function declBoundaryCast( !(target.kind === "union" && lowerer.dynConvertible(target)) ) return null; + // The package call or read the value comes from (decl-origin.ts): a + // failing check names it and the patch admitting the value. + let originText: string | null = null; + try { + originText = recordedBoundaryOrigin(cast); + } catch { + originText = null; + } + const originId = originText === null ? undefined : lowerer.declOriginIndex(originText); if (!holdsReference(lowerer, target)) { - return { kind: "dynCheck", value: inner, type: target, loc }; + return { + kind: "dynCheck", + value: inner, + type: target, + loc, + ...(originId === undefined ? {} : { origin: originId }), + }; } const source = lowerer.declareHiddenLocal("%boundarySource", DYN); const value = lowerer.declareHiddenLocal("%boundaryValue", target); @@ -1672,6 +1721,7 @@ function declBoundaryCast( adopt: true, type: target, loc, + ...(originId === undefined ? {} : { origin: originId }), }, loc, }, diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index feca34427..5b5ae3471 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,10 @@ +import { + compactDeclOrigins, + inPackageFile, + recordOrigin, + slotOrigin, + type RecordOrigin, +} from "./decl-origin.js"; import { isDeclBoundaryFile } from "../decl-projection.js"; import { relieveHeapPressure } from "../../heap-pressure.js"; import { moduleNamespaceObject, moduleNamespaceValue } from "./module-namespace-elements.js"; @@ -3761,6 +3768,73 @@ export class Lowerer { * (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. */ + /** Origins of package-declared types checked in this program + * (decl-origin.ts), by IrModule.declOrigins index. */ + readonly declOrigins: string[] = []; + private readonly declOriginIds = new Map(); + private readonly packageFileMemo = new Map(); + private readonly slotOriginMemo = new Map(); + /** The value whose slot coercion is being lowered, while that value is + * in a package file: union extractions built for it carry the slot's + * origin in their TypeError. */ + coercionOriginNode: ts.Node | null = null; + /** The union type of that value: only its own extraction is the slot's. */ + coercionOriginUnion: string | null = null; + /** While a record projection into a package-declared type builds, the + * origin of the field being converted. */ + coercionFieldOrigin: string | null = null; + private readonly recordOriginMemo = new Map(); + + /** The package-declared record type the current coercion converts to. */ + coercionRecordOrigin(): RecordOrigin | null { + const node = this.coercionOriginNode; + if (node === null) return null; + let origin = this.recordOriginMemo.get(node); + if (origin === undefined) { + try { + origin = recordOrigin(this.checker, node); + } catch { + origin = null; + } + this.recordOriginMemo.set(node, origin); + } + return origin; + } + + declOriginIndex(origin: string): number { + let id = this.declOriginIds.get(origin); + if (id === undefined) { + id = this.declOrigins.length; + this.declOrigins.push(origin); + this.declOriginIds.set(origin, id); + } + return id; + } + + /** The origin of the package-declared slot a value flows into. */ + slotOriginOf(node: ts.Node): string | null { + let origin = this.slotOriginMemo.get(node); + if (origin === undefined) { + try { + origin = slotOrigin(this.checker, node); + } catch { + origin = null; + } + this.slotOriginMemo.set(node, origin); + } + return origin; + } + + private inPackageFileOf(node: ts.Node): boolean { + const sf = ts.getSourceFileOfNode(node); + let inside = this.packageFileMemo.get(sf); + if (inside === undefined) { + inside = inPackageFile(node); + this.packageFileMemo.set(sf, inside); + } + return inside; + } + private declBoundaryMemo: boolean | null = null; get declBoundary(): boolean { this.declBoundaryMemo ??= this.program.getSourceFileNames().some(isDeclBoundaryFile); @@ -8327,6 +8401,7 @@ export class Lowerer { ...(this.foreignElementKeys.size > 0 ? { foreignElementKeys: [...this.foreignElementKeys].sort() } : {}), + ...(this.declOrigins.length > 0 ? { declOrigins: [...this.declOrigins] } : {}), ...(this.threadSlotKeys.size > 0 ? { threadSlotKeys: [...this.threadSlotKeys].sort() } : {}), @@ -8338,6 +8413,7 @@ export class Lowerer { ...(this.ffiImports.length > 0 ? { ffiImports: [...this.ffiImports] } : {}), }; if (module) sanitizeUnregisteredClassTypes(module, (name) => this.classes.has(name)); + if (module?.declOrigins !== undefined) compactDeclOrigins(module); if (module) checkCycleBindingAccesses(this, module); if (module) bindCompletionCallbacks(module); // Execution tiers from the final bodies (ir/tiers.ts). Assigned with or @@ -10956,8 +11032,30 @@ export class Lowerer { * wrap implicitly (coerceToExpected), then the exact-type fence runs * (SC2002 for record shapes, SC2003 for unions). Every slot-directed * lowering goes through here (via lowerExprExpecting) or calls this - * directly when the expression was already lowered. */ + * directly when the expression was already lowered. In package files, a + * check this coercion creates names the package declaration that types + * the slot (decl-origin.ts). */ coerceInto(node: ts.Node, expr: IrExpr, expected: IrType): IrExpr { + if (!this.inPackageFileOf(node)) return this.coerceIntoSlot(node, expr, expected); + const outer = this.coercionOriginNode; + const outerUnion = this.coercionOriginUnion; + this.coercionOriginNode = node; + this.coercionOriginUnion = expr.type.kind === "union" ? expr.type.unionId : null; + let out: IrExpr; + try { + out = this.coerceIntoSlot(node, expr, expected); + } finally { + this.coercionOriginNode = outer; + this.coercionOriginUnion = outerUnion; + } + if (out !== expr && out.kind === "dynCheck" && out.origin === undefined) { + const origin = this.slotOriginOf(node); + if (origin !== null) return { ...out, origin: this.declOriginIndex(origin) }; + } + return out; + } + + private coerceIntoSlot(node: ts.Node, expr: IrExpr, expected: IrType): IrExpr { refuseInferredCallbackDrops(this, node, expr.type, expected); // An assertion's scalar check whose value goes straight back into a // checked-dynamic slot (`process.loadEnvFile({} as unknown as string)`) diff --git a/packages/compiler/src/frontend/lowering/union-narrow.ts b/packages/compiler/src/frontend/lowering/union-narrow.ts index 99cca9941..d224d3e9e 100644 --- a/packages/compiler/src/frontend/lowering/union-narrow.ts +++ b/packages/compiler/src/frontend/lowering/union-narrow.ts @@ -1,4 +1,5 @@ import { BOOL, STRING, isUnitType, typeEquals } from "../../ir/ir.js"; +import { renderOriginNote } from "./decl-origin.js"; import type { IrExpr, IrFunction, IrStmt, IrType, IrUnionDef, SrcLoc } from "../../ir/ir.js"; /** The TypeError message for a checked extraction that finds `arm`. */ @@ -35,6 +36,9 @@ export function buildUnionNarrow( /** Converts another arm's payload into the target (a record arm that is * a width subtype of a record target), or null to throw. */ convert: ((tag: number, payload: IrExpr) => IrExpr | null) | null = null, + /** The target's package-declared origin (decl-origin.ts): each arm's + * message names the declaration and the patch admitting that arm. */ + origin: string | null = null, ): IrFunction | null { if (isUnitType(target) || target.kind === "void") return null; const targetTag = from.arms.findIndex((arm) => typeEquals(arm, target)); @@ -87,7 +91,18 @@ export function buildUnionNarrow( args: [ { kind: "strLit", - value: failureMessage ?? unionNarrowFailureMessage(arm, formatType), + value: + (failureMessage ?? unionNarrowFailureMessage(arm, formatType)) + + (origin === null + ? "" + : renderOriginNote( + origin, + isUnitType(arm) + ? arm.kind === "undefinedT" + ? "undefined" + : "null" + : formatType(arm), + )), type: STRING, loc, }, diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 284b2ce06..44e048916 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -139,7 +139,11 @@ import { typeOnlyReachedFiles, } from "./type-only-imports.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; -import { planDeclProjection, type DeclProjection } from "./decl-projection.js"; +import { + planDeclProjection, + resetDeclBoundaryOrigins, + type DeclProjection, +} from "./decl-projection.js"; import { configureTypePatches, packageSourceText } from "./type-patches/layer.js"; import { reportShapes } from "./shape-inference/index.js"; import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js"; @@ -1077,6 +1081,7 @@ export function loadProgram( // JavaScript, even when named. A registry entry's `sourceVersions` // (chalk 6) compile from their own source. configurePackageShims(entryPath); + resetDeclBoundaryOrigins(); // The project's type patches (type-patches/layer.ts), read by every host // this load creates; nothing at all without `scriptc.typePatches`. configureTypePatches(entryPath, (path, text, kind) => services.parse(path, text, kind)); diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 11b0f6d13..e2d14ebde 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -648,6 +648,7 @@ async function compileExecutableNative( ...(features.streamDyn ? { streamDyn: true } : {}), ...(features.handleProps ? { handleProps: true } : {}), ...(features.declAdopt ? { declAdopt: true } : {}), + ...(features.typeOrigins ? { typeOrigins: true } : {}), ...(features.dynForAwait ? { dynForAwait: true } : {}), ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), ...(features.dynBuffer ? { dynBuffer: true } : {}), diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index e97025e82..4101ae2fd 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -223,6 +223,7 @@ const MODULE_FIELDS: Record>, Role> = { "threadSlotKeys[]": "other", "foreignElementKeys[]": "other", "freezableArrays[]": "other", + "declOrigins[]": "other", }; const loc = { file: "references.ts", start: 0, end: 0 }; diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index c946d799f..f82e71d4a 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1063,6 +1063,11 @@ export interface IrModule { * closures boxed for package code return records and arrays as live * capsules, so a value that comes back keeps its identity. */ declBoundary?: true; + /** Origins of package-declared types checked by `dynCheck.origin` + * (frontend/lowering/decl-origin.ts): fields separated by \x1f — + * package, version, suggested range, selector, where it is declared, + * declared type. */ + declOrigins?: string[]; /** The program constructs LifetimeRegion (@scriptc/experimental): every * ProgramLifetime instance lives in a counted region (the region.* * intrinsics; backend/llvm/regions.ts). */ @@ -4040,12 +4045,17 @@ export type IrExpr = * 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. */ + /** `origin`: the target type comes from an npm package's declaration; + * an index into `IrModule.declOrigins`. A failing check's TypeError then + * names the declaration and the type patch that would admit the value + * (scr_type_origin.c). */ | { kind: "dynCheck"; value: IrExpr; preserveRefs?: true; adopt?: true; exact?: true; + origin?: number; type: IrType; loc: SrcLoc; }; diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 10e0a9e38..832329672 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -953,6 +953,9 @@ const RUNTIME_FEATURE_RULES = { * casts, and closures boxed in a boundary program, which adopt their * arguments. */ declAdopt: rule({ fns: ["dyn.declAdopt"], initial: (mod) => mod.declBoundary === true }), + // Checked conversions into package-declared types name the declaration + // in their TypeError (scr_type_origin.c). + typeOrigins: rule({ initial: (mod) => (mod.declOrigins?.length ?? 0) > 0 }), /** 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 @@ -1237,6 +1240,7 @@ function noRuntimeFeatures(): RuntimeFeatures { streamDyn: false, handleProps: false, declAdopt: false, + typeOrigins: false, dynForAwait: false, dynBuffer: false, builtinValues: false, diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 45ebf5c79..b42bca72b 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -127,6 +127,12 @@ export function validateModule(mod: IrModule): IrValidationError[] { 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 }); + const declOrigins = mod.declOrigins; + if ( + declOrigins !== undefined && + (!Array.isArray(declOrigins) || declOrigins.some((o) => typeof o !== "string")) + ) + errors.push({ message: "declOrigins must be an array of strings", loc: moduleLoc }); if (mod.untypedIntegrity !== undefined && mod.untypedIntegrity !== true) errors.push({ message: "untypedIntegrity must be true when present", loc: moduleLoc }); if (mod.lifetimeRegions !== undefined && mod.lifetimeRegions !== true) @@ -1001,6 +1007,7 @@ export function validateModule(mod: IrModule): IrValidationError[] { mod.workers === true, mod.lifetimeRegions === true, new Set(mod.threadSlotKeys ?? []), + mod.declOrigins?.length ?? 0, ); } return errors; @@ -1020,6 +1027,7 @@ function validateFunction( workers: boolean, lifetimeRegions: boolean, threadSlotKeys: ReadonlySet, + declOriginCount: number, ): void { const locals = new Map(fn.locals.map((l) => [l.id, l])); const err = (message: string, loc: SrcLoc) => @@ -6154,6 +6162,11 @@ function validateFunction( case "dynCheck": { checkExpr(e.value); expectType(e.value, DYN, "dynCheck operand"); + if ( + e.origin !== undefined && + (!Number.isInteger(e.origin) || e.origin < 0 || e.origin >= declOriginCount) + ) + err(`dynCheck origin ${e.origin} is not an index of declOrigins`, e.loc); // The target drives the emitted validator/builder: non-dyn, // non-void, JSON-representable (closures/class instances can never // be found inside a JSON dyn — the frontend rejects those casts). diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 771e2a426..3786542cc 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -81,6 +81,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( streamDyn: false, handleProps: false, declAdopt: false, + typeOrigins: false, dynForAwait: false, eventsOnceSignal: false, dynBuffer: false, diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index b4868d5cb..82a0f8451 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -134,6 +134,7 @@ export const OPTIONAL_RUNTIME_SOURCES = [ // Own properties JavaScript adds to children. ["scr_dyn_handle_props.c", "handleProps"], ["scr_decl_adopt.c", "declAdopt"], + ["scr_type_origin.c", "typeOrigins"], // 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/src/scr_type_origin.c b/packages/runtime/src/scr_type_origin.c new file mode 100644 index 000000000..c52a09164 --- /dev/null +++ b/packages/runtime/src/scr_type_origin.c @@ -0,0 +1,154 @@ +/* Declared-type origins on checked-conversion TypeErrors, linked only by + * programs that check values against types an npm package declares + * (runtime feature typeOrigins). + * + * A checked conversion into a type that comes from a package's + * declarations (its .d.ts, @types package or JSDoc) throws the usual + * `expected at , got ` TypeError when the value does + * not fit. The compiler passes the declaration's origin to the conversion + * site's cold exception path, which calls scr_type_origin_note: the + * pending TypeError's message gains two lines naming the declaration and + * the type patch (package.json scriptc.typePatches) that would make the + * declaration admit the value. The hot path is unchanged, and so is every + * other error. + * + * The origin is one string of fields separated by \x1f: package, version, + * suggested range, the selector of the declared slot, where it is + * declared, and the declared type. */ +#include "scr_runtime.h" + +#include +#include + +typedef struct { + char *data; + size_t len, cap; +} ScrOriginBuf; + +static void scr_origin_put(ScrOriginBuf *b, const char *s, size_t n) { + if (b->len + n + 1 > b->cap) { + size_t cap = b->cap ? b->cap : 256; + while (b->len + n + 1 > cap) cap *= 2; + char *grown = realloc(b->data, cap); + if (!grown) scr_trap("scriptc: out of memory\n"); + b->data = grown; + b->cap = cap; + } + memcpy(b->data + b->len, s, n); + b->len += n; + b->data[b->len] = 0; +} + +static void scr_origin_puts(ScrOriginBuf *b, const char *s) { scr_origin_put(b, s, strlen(s)); } + +/* Field `index` of the origin string (not terminated: [*out, *out+len)). */ +static size_t scr_origin_field(const char *origin, int index, const char **out) { + const char *p = origin; + for (int i = 0; i < index; i++) { + const char *sep = strchr(p, '\x1f'); + if (!sep) { + *out = ""; + return 0; + } + p = sep + 1; + } + const char *end = strchr(p, '\x1f'); + *out = p; + return end ? (size_t)(end - p) : strlen(p); +} + +/* The TypeScript type a `got` kind of the check message suggests adding. */ +static const char *scr_origin_arm(const char *kind, size_t len) { + if (len == 5 && memcmp(kind, "array", 5) == 0) return "unknown[]"; + if (len == 8 && memcmp(kind, "function", 8) == 0) return "Function"; + return NULL; /* the kind itself (undefined, null, string, number, ...) */ +} + +void scr_type_origin_note(const char *origin) { + ScrExcCell *cell = scr_exc_current_cell(); + if (cell->kind != SCR_EXC_OBJ || cell->payload == NULL) return; + ScrError *e = (ScrError *)cell->payload; + if (e->vt != &scr_error_vts[SCR_ERR_TYPE] || e->message == NULL) return; + const char *msg = e->message->data; + size_t mlen = e->message->len; + if (memchr(msg, '\n', mlen) != NULL) return; /* already noted */ + /* `expected at , got `: the path extends the + * declared slot's selector, the kind is what the patch admits. */ + const char *path = NULL, *got = NULL; + size_t path_len = 0, got_len = 0; + if (mlen > 9 && memcmp(msg, "expected ", 9) == 0) { + for (size_t i = mlen; i-- > 7;) { + if (memcmp(msg + i - 6, ", got ", 6) == 0) { + got = msg + i; + got_len = mlen - i; + for (size_t j = i - 6; j-- > 4;) { + if (memcmp(msg + j - 4, " at $", 5) == 0) { + path = msg + j; + path_len = i - 6 - j; + break; + } + } + break; + } + } + } + const char *pkg, *version, *range, *selector, *where, *declared; + size_t pkg_len = scr_origin_field(origin, 0, &pkg); + size_t version_len = scr_origin_field(origin, 1, &version); + size_t range_len = scr_origin_field(origin, 2, &range); + size_t selector_len = scr_origin_field(origin, 3, &selector); + size_t where_len = scr_origin_field(origin, 4, &where); + size_t declared_len = scr_origin_field(origin, 5, &declared); + + ScrOriginBuf b = {0}; + scr_origin_put(&b, msg, mlen); + scr_origin_puts(&b, "\n "); + scr_origin_put(&b, pkg, pkg_len); + if (version_len > 0) { + scr_origin_puts(&b, "@"); + scr_origin_put(&b, version, version_len); + } + scr_origin_puts(&b, " declares "); + scr_origin_put(&b, selector, selector_len); + if (declared_len > 0) { + scr_origin_puts(&b, " as "); + scr_origin_put(&b, declared, declared_len); + } + scr_origin_puts(&b, " ("); + scr_origin_put(&b, where, where_len); + scr_origin_puts(&b, ")\n if that declaration is wrong, patch it in package.json: \"scriptc\": { \"typePatches\": { \""); + scr_origin_put(&b, pkg, pkg_len); + scr_origin_puts(&b, "@"); + scr_origin_put(&b, range, range_len); + scr_origin_puts(&b, "\": { \"declarations\": { \""); + scr_origin_put(&b, selector, selector_len); + /* $[3].email extends the selector as [].email: elements of an array + * share one declared type. */ + for (size_t i = 1; path != NULL && i < path_len; i++) { + if (path[i] == '[') { + scr_origin_puts(&b, "[]"); + while (i < path_len && path[i] != ']') i++; + } else { + scr_origin_put(&b, path + i, 1); + } + } + scr_origin_puts(&b, "\": \"| "); + if (got != NULL) { + const char *arm = scr_origin_arm(got, got_len); + if (arm != NULL) scr_origin_puts(&b, arm); + else scr_origin_put(&b, got, got_len); + } else { + scr_origin_puts(&b, "undefined"); + } + scr_origin_puts(&b, "\" } } } }"); + + ScrStr *next = scr_str_new(b.data, b.len); + free(b.data); + scr_str_release(e->message); + e->message = next; + if (e->stack != NULL) { + /* The cached `stack` starts with the message. */ + scr_str_release(e->stack); + e->stack = NULL; + } +} diff --git a/tests/fixtures/type-patches/unpatched/main.ts b/tests/fixtures/type-patches/unpatched/main.ts new file mode 100644 index 000000000..89418d2d0 --- /dev/null +++ b/tests/fixtures/type-patches/unpatched/main.ts @@ -0,0 +1,13 @@ +// termctx's lying JSDoc without the project's type patches: each failing +// check's TypeError names the declaration and the patch that admits the +// value (native only; Node never checks). +import { Terminal } from "termctx"; + +const term = new Terminal(false); +for (const read of [() => term.columns(), () => term.outputContext(false).width]) { + try { + console.log(read()); + } catch (e) { + console.log((e as Error).message); + } +} diff --git a/tests/fixtures/type-patches/unpatched/package.json b/tests/fixtures/type-patches/unpatched/package.json new file mode 100644 index 000000000..b2d19d710 --- /dev/null +++ b/tests/fixtures/type-patches/unpatched/package.json @@ -0,0 +1 @@ +{ "name": "type-patches-unpatched", "private": true, "type": "module" } diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index 816418a96..cfd8b4dd6 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -7,7 +7,9 @@ * apply as written warn (SC8030-SC8033). The `.d.ts`, `@types`, replaced * file and supplied entry cases run in npm.test.ts (type-patch-dts, * type-patch-types-twin, type-patch-files, type-patch-types-entry); here - * their unpatched copies show the patches are what makes them compile. And + * their unpatched copies show the patches are what makes them compile, and + * a failing check names the declaration and the patch (scr_type_origin.c, + * frontend/lowering/decl-origin.ts). And * commander-calc carries the HelpContext.helpWidth patch. */ import { execFile } from "node:child_process"; import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; @@ -115,7 +117,11 @@ describe("type patches", () => { if (result.ok) { const native = await run(result.binaryPath, []); expect(native.exitCode).not.toBe(0); - expect(native.stderr).toContain("TypeError: expected string at $, got undefined"); + expect(native.stderr).toBe( + "Uncaught TypeError: expected string at $, got undefined\n" + + " profilekit@3.1.0 declares Profile.email as string (index.d.ts:7)\n" + + ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } }\n`, + ); } } finally { rmSync(copy, { recursive: true, force: true }); @@ -165,6 +171,28 @@ describe("type patches", () => { }, ); + test( + "a failing check names the package declaration and the patch", + { timeout: 600_000 }, + async () => { + const result = await build(join(fixture, "unpatched/main.ts"), "unpatched", ["termctx"]); + const native = await run(result.binaryPath, []); + const patch = (selector: string) => + ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "termctx@^1.2.0": { "declarations": { "${selector}": "| undefined" } } } }`; + expect(native.stdout).toBe( + [ + "expected number at $, got undefined", + " termctx@1.2.0 declares Terminal.columns() as number (index.js:16)", + patch("Terminal.columns()"), + "undefined is not representable in the target union (a value narrowed or asserted past it still held it)", + " termctx@1.2.0 declares OutputContext.width as number (index.js:24)", + patch("OutputContext.width"), + "", + ].join("\n"), + ); + }, + ); + test( "patches that do not apply as written warn and the build goes on", { timeout: 600_000 }, From 5e843317aff77bf1f029014fc7c4ede8cece26db Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:35:16 -0700 Subject: [PATCH 141/230] Add scriptc types patch and scriptc types list `scriptc types patch [--range] [--dry-run]` finds the installed package from the working directory, shows every declaration the selector matches (declaration files, JSDoc, @types package) with its type before and after, and adds the patch to the nearest package.json: into an entry whose range covers the installed version, or under ^. Nothing is written when the selector matches nothing. `scriptc types list` shows each patch and what it matches. Both CLI hosts run the same implementation with their own parser. The TypeError note for a wrong declaration now names the command. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/cli.mdx | 27 +- docs/content/docs/dependencies.mdx | 13 +- packages/cli/src/main.ts | 2 + packages/compiler/src/cli/command.ts | 34 ++ packages/compiler/src/cli/host.ts | 6 + packages/compiler/src/cli/usage.ts | 9 + .../src/frontend/lowering/decl-origin.ts | 4 +- .../src/frontend/type-patches/command.ts | 382 ++++++++++++++++++ packages/compiler/src/index.ts | 15 + packages/compiler/src/native/cli.ts | 14 + .../test/type-patches-command.test.ts | 120 ++++++ packages/runtime/src/scr_type_origin.c | 20 +- tests/harness/type-patches.test.ts | 4 +- 13 files changed, 632 insertions(+), 18 deletions(-) create mode 100644 packages/compiler/src/frontend/type-patches/command.ts create mode 100644 packages/compiler/test/type-patches-command.test.ts diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index 4c1b7e7c6..7166d3f10 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -2,7 +2,7 @@ title: "CLI Reference" --- -The `scriptc` command has five subcommands: `build`, `run`, `coverage`, `cache warm`, and `merge-profiles`. `scriptc --help` prints this summary and the options below, and `scriptc --version` prints the installed version. +The `scriptc` command has six subcommands: `build`, `run`, `coverage`, `types`, `cache warm`, and `merge-profiles`. `scriptc --help` prints this summary and the options below, and `scriptc --version` prints the installed version. ```console $ scriptc --help @@ -21,6 +21,13 @@ Usage: exporting the profile symbols; a profile with a sidecar section also gets the contract sidecar JSON beside the archive + scriptc types patch [--range ] [--dry-run] + fix a wrong declaration of an installed + package: shows what the selector + matches and adds the type patch to + package.json ("| undefined" widens) + scriptc types list the project's type patches and what + they match scriptc cache warm [runtime|tls…] verify and load precompiled runtime families for the current compiler/SDK/target scriptc merge-profiles -o [--sparse] @@ -88,6 +95,24 @@ $ scriptc run fib.ts Analyzes the program without building it and reports what compiles and what blocks the rest. See [Coverage Reports](/docs/coverage). +## scriptc types + +Writes and lists [type patches](/docs/dependencies#fix-a-packages-types), the project's fixes for wrong declarations of npm packages. + +
+
scriptc types patch <package> <selector> <type>
+
Finds the package installed for the working directory, prints every declaration the selector matches in its declaration files, its JSDoc and its @types/ package with the type before and after, and adds the patch to the nearest package.json. The patch joins an entry whose range covers the installed version, or gets the range ^<installed version>. Nothing is written when the selector matches nothing. --range <range> chooses the range, and --dry-run only prints.
+
scriptc types list
+
Prints each type patch in the nearest package.json, whether it applies to the installed version, and what it matches.
+
+ +```console +$ scriptc types list +commander@^15.0.0 + HelpContext.helpWidth: | undefined + node_modules/commander/lib/command.js:2481 number -> number | undefined +``` + ## scriptc cache warm Verifies and loads the installed precompiled runtime objects and vendored libraries, and prints the cache directory. Pass `runtime`, `tls`, or both to select which to warm. Ordinary builds don't need this step. With `--sanitize`, it builds the instrumented runtime objects using a C toolchain. diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index 58b0a0a1d..2288aeb83 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -150,10 +150,19 @@ When the type comes from a package's declarations, the error names the declarati ```text Uncaught TypeError: expected string at $, got undefined profilekit@3.1.0 declares Profile.email as string (index.d.ts:7) - if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } } + if that declaration is wrong, patch it: scriptc types patch profilekit "Profile.email" "| undefined" ``` -Fix such a declaration in your project with a type patch in `package.json`: +Fix such a declaration in your project with a type patch. `scriptc types patch` shows what a declaration path matches in the installed package and adds the patch to your `package.json`: + +```console +$ scriptc types patch commander "HelpContext.helpWidth" "| undefined" +commander 15.0.0 (node_modules/commander) + lib/command.js:2481 number -> number | undefined +wrote package.json scriptc.typePatches["commander@^15.0.0"].declarations["HelpContext.helpWidth"] +``` + +The patch it writes looks like this: ```json:package.json { diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index 8203b959e..5deb4ca29 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -10,6 +10,7 @@ import { mergeProfiles, resolveProvenanceSources, sourceTargetPlatform, + typePatches, warmNativeCaches, } from "@scriptc/compiler"; import { runCli } from "./command.js"; @@ -39,6 +40,7 @@ process.exitCode = await runCli(process.argv.slice(2), { sourceTargetPlatform, warmNativeCaches, mergeProfiles, + typePatches, run: (binary) => new Promise((resolveExit) => { let child; diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index 8b2e4c480..1064b18ad 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -1,3 +1,4 @@ +import type { TypePatchCommandRequest } from "../frontend/type-patches/command.js"; import { rmSync, statSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { parseArgs } from "node:util"; @@ -205,6 +206,39 @@ async function main(args: string[], host: CliHost): Promise { process.stdout.write(`${outputPath}\n`); return 0; } + if (command === "types") { + const sub = positionals[1]; + const rest = positionals.slice(2); + let request: TypePatchCommandRequest; + if (sub === "patch" && rest.length === 3) { + request = { + action: "patch", + cwd: process.cwd(), + pkg: rest[0]!, + selector: rest[1]!, + type: rest[2]!, + range: values.range ?? null, + dryRun: values["dry-run"], + }; + } else if ( + sub === "list" && + rest.length === 0 && + values.range === undefined && + !values["dry-run"] + ) { + request = { action: "list", cwd: process.cwd() }; + } else { + fail( + `scriptc types takes "patch " (with --range or --dry-run) or "list"\n\n${USAGE}`, + ); + } + const result = await host.typePatches(request); + process.stdout.write(result.stdout); + process.stderr.write(result.stderr); + return result.exitCode; + } + if (values.range !== undefined || values["dry-run"]) + fail(`--range and --dry-run are scriptc types patch options\n\n${USAGE}`); if (values.sparse) fail(`--sparse is a merge-profiles option\n\n${USAGE}`); if (command !== "build" && command !== "run" && command !== "coverage") { fail(`unknown command "${command}"\n\n${USAGE}`); diff --git a/packages/compiler/src/cli/host.ts b/packages/compiler/src/cli/host.ts index 7a927ee39..eafe1882b 100644 --- a/packages/compiler/src/cli/host.ts +++ b/packages/compiler/src/cli/host.ts @@ -1,3 +1,7 @@ +import type { + TypePatchCommandRequest, + TypePatchCommandResult, +} from "../frontend/type-patches/command.js"; import type { AnalyzeOptions, AnalyzeResult, @@ -30,5 +34,7 @@ export interface CliHost { /** Merges raw or indexed profiles into one indexed profile; a sparse one * leaves out functions that never ran. */ mergeProfiles: (outputPath: string, inputPaths: string[], sparse: boolean) => Promise; + /** `scriptc types patch|list` (frontend/type-patches/command.ts). */ + typePatches: (request: TypePatchCommandRequest) => Promise; run: (binary: string) => Promise; } diff --git a/packages/compiler/src/cli/usage.ts b/packages/compiler/src/cli/usage.ts index f8b6a4869..2d4b101fd 100644 --- a/packages/compiler/src/cli/usage.ts +++ b/packages/compiler/src/cli/usage.ts @@ -13,6 +13,13 @@ Usage: exporting the profile symbols; a profile with a sidecar section also gets the contract sidecar JSON beside the archive + scriptc types patch [--range ] [--dry-run] + fix a wrong declaration of an installed + package: shows what the selector + matches and adds the type patch to + package.json ("| undefined" widens) + scriptc types list the project's type patches and what + they match scriptc cache warm [runtime|tls…] verify and load precompiled runtime families for the current compiler/SDK/target scriptc merge-profiles -o [--sparse] @@ -157,6 +164,8 @@ export const CLI_OPTIONS = { "profile-generate": { type: "string" }, "profile-use": { type: "string" }, sparse: { type: "boolean", default: false }, + range: { type: "string" }, + "dry-run": { type: "boolean", default: false }, "keep-llvm": { type: "boolean", default: true }, "emit-ir": { type: "boolean", default: false }, sanitize: { type: "boolean", default: false }, diff --git a/packages/compiler/src/frontend/lowering/decl-origin.ts b/packages/compiler/src/frontend/lowering/decl-origin.ts index 60510ecec..a44c54eca 100644 --- a/packages/compiler/src/frontend/lowering/decl-origin.ts +++ b/packages/compiler/src/frontend/lowering/decl-origin.ts @@ -348,12 +348,12 @@ export function declarationSelector(decl: ts.Node): string | null { /** The note a union extraction's TypeError carries for a failing `arm` * (the same text the runtime renders for checked conversions). */ export function renderOriginNote(origin: string, arm: string): string { - const [pkg = "", version = "", range = "", selector = "", where = "", declared = ""] = + const [pkg = "", version = "", , selector = "", where = "", declared = ""] = origin.split(ORIGIN_SEPARATOR); return ( `\n ${pkg}${version === "" ? "" : `@${version}`} declares ${selector}` + `${declared === "" ? "" : ` as ${declared}`} (${where})` + - `\n if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "${pkg}@${range}": { "declarations": { "${selector}": "| ${arm}" } } } }` + `\n if that declaration is wrong, patch it: scriptc types patch ${pkg} "${selector}" "| ${arm}"` ); } diff --git a/packages/compiler/src/frontend/type-patches/command.ts b/packages/compiler/src/frontend/type-patches/command.ts new file mode 100644 index 000000000..d7fb817ae --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/command.ts @@ -0,0 +1,382 @@ +/* `scriptc types patch` and `scriptc types list`: write and inspect the + * project's type patches (package.json scriptc.typePatches). + * + * scriptc types patch [--range ] [--dry-run] + * scriptc types list + * + * `patch` finds the installed package from the working directory, shows + * every declaration the selector matches (in the package's declaration + * files, its JSDoc and its @types package) with its type before and after, + * and adds the patch to the nearest package.json under a range covering + * the installed version (`^` unless --range says otherwise, or an + * existing entry's range when one covers it). Nothing is written when the + * selector matches nothing. `list` shows each configured patch, the + * installed copies it applies to and what it matches. */ + +import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { dirname, join, relative } from "node:path"; +import { parseProjectConfig, type TypePatchEntry } from "../../project-config.js"; +import type { TypePatchParse } from "./layer.js"; +import { patchFile, type PatchMatch } from "./locate.js"; +import { parseSelector, parseTypeText, type Selector, type TypePatchOp } from "./selector.js"; +import { isValidRange, satisfies } from "./semver.js"; + +export type TypePatchCommandRequest = + | { + action: "patch"; + cwd: string; + pkg: string; + selector: string; + type: string; + range: string | null; + dryRun: boolean; + } + | { action: "list"; cwd: string }; + +export interface TypePatchCommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +interface PackageCopy { + root: string; + version: string | null; + /** True for an `@types/` twin. */ + twin: boolean; +} + +const PATCHABLE = /\.(?:[cm]?js|d\.[cm]?ts|[cm]?ts)$/; + +function nearestManifest(cwd: string): string | null { + for (let dir = cwd; ;) { + const manifest = join(dir, "package.json"); + if (existsSync(manifest)) return manifest; + const parent = dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + +function versionOf(root: string): string | null { + try { + const parsed = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { + version?: unknown; + }; + return typeof parsed.version === "string" ? parsed.version : null; + } catch { + return null; + } +} + +function typesName(pkg: string): string { + return pkg.startsWith("@") ? `@types/${pkg.slice(1).replace("/", "__")}` : `@types/${pkg}`; +} + +/** The copy of `pkg` (and of its @types twin) Node resolution finds from + * `cwd`. A twin is versioned by the runtime package beside it. */ +function installedCopies(cwd: string, pkg: string): PackageCopy[] { + const out: PackageCopy[] = []; + let runtimeVersion: string | null = null; + let twinFound = false; + for (let dir = cwd; ;) { + const modules = join(dir, "node_modules"); + if (runtimeVersion === null && existsSync(join(modules, pkg, "package.json"))) { + const root = join(modules, pkg); + runtimeVersion = versionOf(root); + out.push({ root, version: runtimeVersion, twin: false }); + } + if (!twinFound && existsSync(join(modules, typesName(pkg), "package.json"))) { + twinFound = true; + out.push({ root: join(modules, typesName(pkg)), version: null, twin: true }); + } + const parent = dirname(dir); + if (parent === dir) break; + dir = parent; + } + for (const copy of out) if (copy.twin) copy.version = runtimeVersion ?? versionOf(copy.root); + return out; +} + +/** The files of a package copy a patch can apply to (nested packages + * excluded), package-relative. */ +function packageFiles(root: string): string[] { + const out: string[] = []; + const walk = (dir: string): void => { + let names: string[]; + try { + names = readdirSync(dir).sort(); + } catch { + return; + } + for (const name of names) { + if (name === "node_modules" || name.startsWith(".")) continue; + const path = join(dir, name); + let isDir = false; + try { + isDir = statSync(path).isDirectory(); + } catch { + continue; + } + if (isDir) walk(path); + else if (PATCHABLE.test(name)) out.push(relative(root, path).split("\\").join("/")); + } + }; + walk(root); + return out; +} + +interface FileMatch extends PatchMatch { + file: string; +} + +/** Every declaration a selector matches in a copy, and the problems where + * it cannot apply. */ +function matchCopy( + copy: PackageCopy, + selector: Selector, + op: TypePatchOp, + parse: TypePatchParse, +): { matches: FileMatch[]; problems: string[] } { + const words = + selector.root === "export=" + ? ["exports", "export"] + : selector.root === "default" + ? ["default"] + : [selector.root]; + const matches: FileMatch[] = []; + const problems: string[] = []; + for (const rel of packageFiles(copy.root)) { + if (selector.file !== null && selector.file !== rel) continue; + const path = join(copy.root, rel); + let text: string; + try { + text = readFileSync(path, "utf8"); + } catch { + continue; + } + if (!words.some((w) => text.includes(w))) continue; + let result; + try { + result = patchFile(rel, text, parse(path, text, /\.[cm]?ts$/.test(rel) ? "ts" : "js"), [ + { id: 0, selector, op }, + ]); + } catch { + continue; + } + for (const m of result.matches) matches.push({ ...m, file: rel }); + for (const p of result.problems) problems.push(`${p.message} (${rel})`); + } + return { matches, problems }; +} + +function shownCopy(copy: PackageCopy, base: string, pkg: string): string { + const name = copy.twin ? typesName(pkg) : pkg; + return `${name} ${copy.version ?? "(no version)"} (${relative(base, copy.root).split("\\").join("/")})`; +} + +function describe(m: FileMatch): string { + if (m.redundant) return ` ${m.file}:${m.line} ${m.before} (already declared so)`; + return ` ${m.file}:${m.line} ${m.before} -> ${m.after}`; +} + +/** The indentation a JSON text uses (two spaces when it cannot tell). */ +function indentOf(text: string): string { + const m = /^[{[]\s*\n([ \t]+)\S/.exec(text); + return m === null ? " " : m[1]!; +} + +function isObject(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +/** JSON text of `value` with `path` set to the string `leaf` (objects + * created along the way, the leaf replaced if present), keeping key order + * and indenting like JSON.stringify(value, null, indent). The parsed value + * itself is only read. */ +function renderJson( + value: unknown, + indent: string, + at: string, + path: readonly string[], + leaf: string, +): string { + const inner = at + indent; + if (path.length > 0 && !isObject(value)) return renderJson({}, indent, at, path, leaf); + if (isObject(value)) { + const keys = Object.keys(value); + const parts: string[] = []; + const head = path[0]; + for (const k of keys) { + const child = + head === k + ? path.length === 1 + ? JSON.stringify(leaf) + : renderJson(value[k], indent, inner, path.slice(1), leaf) + : renderJson(value[k], indent, inner, [], leaf); + parts.push(`${inner}${JSON.stringify(k)}: ${child}`); + } + if (head !== undefined && !keys.includes(head)) { + const child = + path.length === 1 + ? JSON.stringify(leaf) + : renderJson({}, indent, inner, path.slice(1), leaf); + parts.push(`${inner}${JSON.stringify(head)}: ${child}`); + } + return parts.length === 0 ? "{}" : `{\n${parts.join(",\n")}\n${at}}`; + } + if (Array.isArray(value)) { + const items: unknown[] = value; + if (items.length === 0) return "[]"; + return `[\n${items.map((item) => `${inner}${renderJson(item, indent, inner, [], leaf)}`).join(",\n")}\n${at}]`; + } + return JSON.stringify(value) ?? "null"; +} + +function patchCommand( + req: Extract, + parse: TypePatchParse, +): TypePatchCommandResult { + const fail = (message: string): TypePatchCommandResult => ({ + stdout: "", + stderr: `scriptc: ${message}\n`, + exitCode: 1, + }); + const selector = parseSelector(req.selector); + if ("error" in selector) return fail(`'${req.selector}' is not a selector: it ${selector.error}`); + const op = parseTypeText(req.type); + if ("error" in op) return fail(`the type '${req.type}' ${op.error}`); + if (req.range !== null && !isValidRange(req.range)) + return fail(`'${req.range}' is not a semver range`); + const manifest = nearestManifest(req.cwd); + if (manifest === null) return fail("no package.json found from the working directory"); + const base = dirname(manifest); + const copies = installedCopies(req.cwd, req.pkg); + const runtime = copies.find((c) => !c.twin) ?? copies[0]; + if (runtime === undefined) return fail(`${req.pkg} is not installed here`); + const version = runtime.version; + const lines: string[] = []; + let matched = 0; + let changed = 0; + const problems: string[] = []; + for (const copy of copies) { + const found = matchCopy(copy, selector, op, parse); + problems.push(...found.problems); + if (found.matches.length === 0) continue; + lines.push(shownCopy(copy, base, req.pkg)); + for (const m of found.matches) { + lines.push(describe(m)); + matched++; + if (!m.redundant) changed++; + } + } + if (matched === 0) { + const why = + problems.length > 0 ? problems.join("\n") : `'${req.selector}' matches no declaration`; + return fail(`${why}\nnothing was written`); + } + if (changed === 0) { + lines.push("the declarations already say this; nothing was written"); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + const text = readFileSync(manifest, "utf8"); + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch { + return fail(`${relative(req.cwd, manifest) || "package.json"} is not valid JSON`); + } + // An existing entry whose range covers the installed version takes the + // patch; otherwise a new one under the range asked for. + const existing = parseProjectConfig(manifest, text).typePatches.find( + (e: TypePatchEntry) => + e.pkg === req.pkg && + (req.range === null + ? version !== null && satisfies(version, e.range) + : e.range === req.range), + ); + const key = + existing?.key ?? `${req.pkg}@${req.range ?? (version === null ? "*" : `^${version}`)}`; + const where = `scriptc.typePatches["${key}"].declarations["${req.selector}"]`; + if (req.dryRun) { + lines.push(`would write ${relative(req.cwd, manifest) || "package.json"} ${where}`); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + const trailing = text.endsWith("\n") ? "\n" : ""; + writeFileSync( + manifest, + renderJson( + parsed, + indentOf(text), + "", + ["scriptc", "typePatches", key, "declarations", req.selector], + req.type, + ) + trailing, + ); + lines.push(`wrote ${relative(req.cwd, manifest) || "package.json"} ${where}`); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; +} + +function listCommand(cwd: string, parse: TypePatchParse): TypePatchCommandResult { + const manifest = nearestManifest(cwd); + if (manifest === null) + return { + stdout: "", + stderr: "scriptc: no package.json found from the working directory\n", + exitCode: 1, + }; + const base = dirname(manifest); + const config = parseProjectConfig(manifest, readFileSync(manifest, "utf8")); + const lines: string[] = []; + for (const d of config.diagnostics) + if (d.message.includes("typePatches")) lines.push(`warning ${d.code}: ${d.message}`); + if (config.typePatches.length === 0) { + lines.push("no type patches"); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + for (const entry of config.typePatches) { + lines.push(entry.key); + const copies = installedCopies(cwd, entry.pkg); + const covered = copies.filter((c) => c.version !== null && satisfies(c.version, entry.range)); + if (copies.length === 0) lines.push(` not installed`); + else if (covered.length === 0) + lines.push( + ` does not apply to the installed ${entry.pkg} ${copies[0]!.version ?? "(no version)"}`, + ); + for (const decl of entry.declarations) { + const selector = parseSelector(decl.selector) as Selector; + const op = parseTypeText(decl.type) as TypePatchOp; + const found: FileMatch[] = []; + for (const copy of covered) { + for (const m of matchCopy(copy, selector, op, parse).matches) + found.push({ + ...m, + file: `${relative(base, copy.root).split("\\").join("/")}/${m.file}`, + }); + } + const status = + covered.length === 0 + ? "" + : found.length === 0 + ? " (matches nothing)" + : found.every((m) => m.redundant) + ? " (already declared so)" + : ""; + lines.push(` ${decl.selector}: ${decl.type}${status}`); + for (const m of found) + if (!m.redundant) lines.push(` ${m.file}:${m.line} ${m.before} -> ${m.after}`); + } + for (const [target, local] of entry.files) + lines.push(` file ${target}: ${relative(base, local).split("\\").join("/")}`); + if (entry.types !== null) + lines.push(` types: ${relative(base, entry.types).split("\\").join("/")}`); + } + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; +} + +export function runTypePatchCommand( + req: TypePatchCommandRequest, + parse: TypePatchParse, +): TypePatchCommandResult { + return req.action === "patch" ? patchCommand(req, parse) : listCommand(req.cwd, parse); +} diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index e2d14ebde..92b39e78b 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -1,3 +1,9 @@ +import { nodeFrontendServices } from "./frontend/services-node.js"; +import { + runTypePatchCommand, + type TypePatchCommandRequest, + type TypePatchCommandResult, +} from "./frontend/type-patches/command.js"; import { optimizationField, type NativeOptimization } from "./backend/optimization.js"; import { targetCpuField, targetCpuTargetRefusal } from "./backend/target-cpu.js"; import { compilationTiming, type CompilationTiming, type TimingClock } from "./timing.js"; @@ -197,6 +203,15 @@ export { } from "./backend/external-c.js"; export { warmNativeCaches } from "./backend/warm-cache.js"; export { mergeNativeProfiles as mergeProfiles } from "./backend/native-codegen.js"; + +/** `scriptc types patch|list` with the Node frontend's parser. */ +export async function typePatches( + request: TypePatchCommandRequest, +): Promise { + return runTypePatchCommand(request, (path, text, kind) => + nodeFrontendServices().parse(path, text, kind), + ); +} export { collectGarbageUnderHeapPressure } from "./heap-collection.js"; export { ANDROID_MIN_API, diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index b3de05846..60f3d28da 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -12,6 +12,7 @@ import { mergeNativeProfiles } from "../backend/native-tools.js"; import { setDeclarationRoot } from "../frontend/dts-paths.js"; import { setFrontendDisposedHook } from "../frontend/dispose-hook.js"; import { FrontendServices } from "../frontend/services.js"; +import { runTypePatchCommand } from "../frontend/type-patches/command.js"; import { createNativeTs7Api } from "../frontend/ts7/native-api.js"; import { resolveProvenanceSourcesWithParser } from "../frontend/provenance-core.js"; import { setCompilerReleaseVersion } from "../library/sidecar.js"; @@ -169,6 +170,19 @@ async function main(): Promise { } return { cacheRoot: cache.root, profiles }; }, + typePatches: async (request) => { + const toolchain = getCompiler().toolchain; + const services = new FrontendServices((options) => + createNativeTs7Api({ ...options, executable: toolchain.ts7Executable }), + ); + try { + return runTypePatchCommand(request, (path, source, kind) => + services.parse(path, source, kind), + ); + } finally { + services.close(); + } + }, mergeProfiles: async (outputPath, inputPaths, sparse) => { const toolchain = getCompiler().toolchain; mergeNativeProfiles({ diff --git a/packages/compiler/test/type-patches-command.test.ts b/packages/compiler/test/type-patches-command.test.ts new file mode 100644 index 000000000..7fc8a0d6c --- /dev/null +++ b/packages/compiler/test/type-patches-command.test.ts @@ -0,0 +1,120 @@ +/* `scriptc types patch` and `scriptc types list` + * (frontend/type-patches/command.ts) on a copy of the type patch fixture: + * matches are shown before anything is written, the patch lands in the + * nearest package.json under a range covering the installed version (or + * an existing entry that covers it), and nothing is written when the + * selector matches nothing. */ +import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, expect, test } from "vitest"; +import { nodeFrontendServices } from "../src/frontend/services-node.js"; +import { runTypePatchCommand } from "../src/frontend/type-patches/command.js"; + +const fixture = join(import.meta.dirname, "../../../tests/fixtures/type-patches"); +const parse = (path: string, text: string, kind: "ts" | "js") => + nodeFrontendServices().parse(path, text, kind); +let root: string; + +beforeEach(() => { + root = mkdtempSync(join(tmpdir(), "scriptc-types-command-")); + cpSync(fixture, root, { recursive: true }); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ name: "app", private: true, type: "module" }, null, 2) + "\n", + ); +}); +afterEach(() => rmSync(root, { recursive: true, force: true })); + +const patch = (pkg: string, selector: string, type: string, extra: object = {}) => + runTypePatchCommand( + { action: "patch", cwd: root, pkg, selector, type, range: null, dryRun: false, ...extra }, + parse, + ); + +test("patch shows the matches and writes the entry", () => { + const result = patch("termctx", "OutputContext.width", "| undefined"); + expect(result).toEqual({ + stdout: + "termctx 1.2.0 (node_modules/termctx)\n" + + " index.js:24 number -> number | undefined\n" + + `wrote package.json scriptc.typePatches["termctx@^1.2.0"].declarations["OutputContext.width"]\n`, + stderr: "", + exitCode: 0, + }); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8"))).toEqual({ + name: "app", + private: true, + type: "module", + scriptc: { + typePatches: { + "termctx@^1.2.0": { declarations: { "OutputContext.width": "| undefined" } }, + }, + }, + }); + // A second patch joins the entry whose range covers the installed copy. + expect(patch("termctx", "Terminal.columns()", "| undefined").exitCode).toBe(0); + const text = readFileSync(join(root, "package.json"), "utf8"); + expect(text.endsWith("}\n")).toBe(true); + expect(JSON.parse(text).scriptc.typePatches).toEqual({ + "termctx@^1.2.0": { + declarations: { "OutputContext.width": "| undefined", "Terminal.columns()": "| undefined" }, + }, + }); +}); + +test("--range and --dry-run", () => { + const dry = patch("ms", "index.js:parse()", "| undefined", { dryRun: true, range: "2.1.x" }); + expect(dry.stdout).toBe( + "ms 2.1.3 (node_modules/ms)\n" + + " index.js:44 Number -> Number | undefined\n" + + `would write package.json scriptc.typePatches["ms@2.1.x"].declarations["index.js:parse()"]\n`, + ); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8")).scriptc).toBeUndefined(); +}); + +test("nothing is written for a selector that matches nothing or is already so", () => { + const missing = patch("termctx", "OutputContext.height", "| undefined"); + expect(missing.exitCode).toBe(1); + expect(missing.stderr).toBe( + "scriptc: 'OutputContext.height' matches no declaration\nnothing was written\n", + ); + const redundant = patch("ms", "index.js:fmtShort(ms)", "| Number"); + expect(redundant).toMatchObject({ exitCode: 0 }); + expect(redundant.stdout).toContain("the declarations already say this; nothing was written\n"); + expect(patch("absent-package", "A.b", "| undefined").stderr).toBe( + "scriptc: absent-package is not installed here\n", + ); + expect(patch("termctx", "1bad", "| undefined").stderr).toContain("is not a selector"); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8")).scriptc).toBeUndefined(); +}); + +test("list shows each patch and what it matches", () => { + writeFileSync(join(root, "package.json"), readFileSync(join(fixture, "package.json"))); + const listed = runTypePatchCommand({ action: "list", cwd: root }, parse); + expect(listed.stdout).toBe( + [ + "termctx@^1.0.0", + " OutputContext.width: | undefined", + " node_modules/termctx/index.js:24 number -> number | undefined", + " Terminal.columns(): | undefined", + " node_modules/termctx/index.js:14 number -> number | undefined", + "ms@^2.1.0", + " index.js:parse(): | undefined", + " node_modules/ms/index.js:44 Number -> Number | undefined", + " export=(): | undefined", + " node_modules/ms/index.js:22 String|Number -> String|Number | undefined", + "", + ].join("\n"), + ); + writeFileSync(join(root, "stale.json"), ""); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { typePatches: { "termctx@^2.0.0": { declarations: { "A.b": "| undefined" } } } }, + }), + ); + expect(runTypePatchCommand({ action: "list", cwd: root }, parse).stdout).toBe( + "termctx@^2.0.0\n does not apply to the installed termctx 1.2.0\n A.b: | undefined\n", + ); +}); diff --git a/packages/runtime/src/scr_type_origin.c b/packages/runtime/src/scr_type_origin.c index c52a09164..cf9c6fc49 100644 --- a/packages/runtime/src/scr_type_origin.c +++ b/packages/runtime/src/scr_type_origin.c @@ -9,12 +9,13 @@ * site's cold exception path, which calls scr_type_origin_note: the * pending TypeError's message gains two lines naming the declaration and * the type patch (package.json scriptc.typePatches) that would make the - * declaration admit the value. The hot path is unchanged, and so is every + * declaration admit the value, as the `scriptc types patch` command that + * writes it. The hot path is unchanged, and so is every * other error. * * The origin is one string of fields separated by \x1f: package, version, - * suggested range, the selector of the declared slot, where it is - * declared, and the declared type. */ + * suggested range (which the command chooses itself), the selector of the + * declared slot, where it is declared, and the declared type. */ #include "scr_runtime.h" #include @@ -92,10 +93,9 @@ void scr_type_origin_note(const char *origin) { } } } - const char *pkg, *version, *range, *selector, *where, *declared; + const char *pkg, *version, *selector, *where, *declared; size_t pkg_len = scr_origin_field(origin, 0, &pkg); size_t version_len = scr_origin_field(origin, 1, &version); - size_t range_len = scr_origin_field(origin, 2, &range); size_t selector_len = scr_origin_field(origin, 3, &selector); size_t where_len = scr_origin_field(origin, 4, &where); size_t declared_len = scr_origin_field(origin, 5, &declared); @@ -116,11 +116,9 @@ void scr_type_origin_note(const char *origin) { } scr_origin_puts(&b, " ("); scr_origin_put(&b, where, where_len); - scr_origin_puts(&b, ")\n if that declaration is wrong, patch it in package.json: \"scriptc\": { \"typePatches\": { \""); + scr_origin_puts(&b, ")\n if that declaration is wrong, patch it: scriptc types patch "); scr_origin_put(&b, pkg, pkg_len); - scr_origin_puts(&b, "@"); - scr_origin_put(&b, range, range_len); - scr_origin_puts(&b, "\": { \"declarations\": { \""); + scr_origin_puts(&b, " \""); scr_origin_put(&b, selector, selector_len); /* $[3].email extends the selector as [].email: elements of an array * share one declared type. */ @@ -132,7 +130,7 @@ void scr_type_origin_note(const char *origin) { scr_origin_put(&b, path + i, 1); } } - scr_origin_puts(&b, "\": \"| "); + scr_origin_puts(&b, "\" \"| "); if (got != NULL) { const char *arm = scr_origin_arm(got, got_len); if (arm != NULL) scr_origin_puts(&b, arm); @@ -140,7 +138,7 @@ void scr_type_origin_note(const char *origin) { } else { scr_origin_puts(&b, "undefined"); } - scr_origin_puts(&b, "\" } } } }"); + scr_origin_puts(&b, "\""); ScrStr *next = scr_str_new(b.data, b.len); free(b.data); diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index cfd8b4dd6..7b8aaf7f5 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -120,7 +120,7 @@ describe("type patches", () => { expect(native.stderr).toBe( "Uncaught TypeError: expected string at $, got undefined\n" + " profilekit@3.1.0 declares Profile.email as string (index.d.ts:7)\n" + - ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } }\n`, + ` if that declaration is wrong, patch it: scriptc types patch profilekit "Profile.email" "| undefined"\n`, ); } } finally { @@ -178,7 +178,7 @@ describe("type patches", () => { const result = await build(join(fixture, "unpatched/main.ts"), "unpatched", ["termctx"]); const native = await run(result.binaryPath, []); const patch = (selector: string) => - ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "termctx@^1.2.0": { "declarations": { "${selector}": "| undefined" } } } }`; + ` if that declaration is wrong, patch it: scriptc types patch termctx "${selector}" "| undefined"`; expect(native.stdout).toBe( [ "expected number at $, got undefined", From 062700bdde6125d6f38192d6d636dc9484c17b61 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 10:08:00 -0700 Subject: [PATCH 142/230] Convert asserted values that no ordinary conversion reaches `x as T` erases (or, record to record, converts), and the value then meets its slot with x's representation. Where no conversion reaches the slot the program was refused: - a union whose arms only partly convert into T (`api as Partial>` on `S | Partial> | undefined`) failed at the next typed slot with SC2003; - a record or array whose member types the assertion narrows (`models as Model<"one">[]` on a union-element array, `item as Narrow` with a narrower field) failed with SC2002 or SC2003. The lowerer now remembers the asserted type of an erased value, and a slot that would refuse it converts it as asserted: the arms T excludes throw the catchable TypeError of a lying assertion and the others convert, and records and arrays take the checked deep conversion, which validates the value member by member and names the deepest failing path, as a cast of an unknown value does. The conversions run only where the slot would otherwise refuse the value, so everything that already lowered keeps its code. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/coercions/asserted.ts | 87 +++++++++++++++++++ .../lowering/expressions/narrowing.ts | 10 +++ .../compiler/src/frontend/lowering/lowerer.ts | 9 +- .../checked-union-record-assertions.ts.json | 6 ++ .../corpus/checked-union-record-assertions.ts | 69 +++++++++++++++ .../checked-union-record-assertions.ts.json | 1 + tests/harness/dyncheck.test.ts | 26 ++++++ 7 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 packages/compiler/src/frontend/lowering/coercions/asserted.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/checked-union-record-assertions.ts.json create mode 100644 tests/corpus/checked-union-record-assertions.ts create mode 100644 tests/harness/corpus-costs/checked-union-record-assertions.ts.json diff --git a/packages/compiler/src/frontend/lowering/coercions/asserted.ts b/packages/compiler/src/frontend/lowering/coercions/asserted.ts new file mode 100644 index 000000000..43c447cb0 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/coercions/asserted.ts @@ -0,0 +1,87 @@ +/* Slots reached only through what a type assertion claims. */ +import type { IrExpr, IrType, SrcLoc } from "../../../ir/ir.js"; +import { canDynCheckTo, DYN, isUnitType, typeEquals } from "../../../ir/ir.js"; +import { lowerRecordOvfCaptureHelper } from "../containers/indexed-objects.js"; +import type { Lowerer } from "../lowerer.js"; +import { unionRetagHelper } from "./unions.js"; + +/** A value an erased assertion produced (`x as T`, lowerer.assertedValues) + * flowing into a slot that no ordinary conversion reaches. Runs only where + * the slot would otherwise refuse the value (SC2002/SC2003), so every + * conversion that already lowered keeps its code. + * + * - A union whose arms only partly convert (`api as Partial>` on `S | Partial> | undefined`): the arms that convert + * into T convert into the slot, and an arm the assertion excludes (no + * conversion into T) throws the catchable TypeError of a lying assertion. + * - A record or array whose member types the assertion narrows (`models as + * Model<"one">[]` on a union-element array): the checked deep conversion + * a cast of an unknown value takes, which validates the value member by + * member and names the deepest failing path. */ +export function assertedConversion( + lowerer: Lowerer, + expr: IrExpr, + expected: IrType, +): IrExpr | null { + const asserted = lowerer.assertedValues.get(expr); + if (!asserted) return null; + const loc = expr.loc; + if (expr.type.kind === "union") { + const from = lowerer.unions.get(expr.type.unionId); + if (!from) return null; + const values = from.arms.filter((arm) => !isUnitType(arm)); + const kept = values.filter((arm) => convertsInto(lowerer, arm, asserted, loc)); + if (kept.length === 0 || kept.length === values.length) return null; + if (asserted.kind === "union" && expected.kind === "union") { + // The arms T excludes trap; the others (units included) re-tag. + const trappable = new Set(); + from.arms.forEach((arm, tag) => { + if (!isUnitType(arm) && !kept.includes(arm)) trappable.add(tag); + }); + const helper = unionRetagHelper(lowerer, from.id, expected.unionId, loc, trappable); + return helper ? { kind: "call", callee: helper, args: [expr], type: expected, loc } : null; + } + // A record or array T holds one arm's values: extract that arm (every + // other arm, a unit too, contradicts the assertion) and convert it. + if (asserted.kind === "union" || kept.length !== 1) return null; + const arm = kept[0]!; + const helper = lowerer.narrowedArmHelper(from.id, arm, loc); + if (!helper) return null; + const narrowed = lowerer.checkedNarrowCall(helper, expr, arm, loc); + if (typeEquals(arm, expected)) return narrowed; + const converted = lowerer.coerceToExpected(narrowed, expected); + return typeEquals(converted.type, expected) ? converted : null; + } + if (expected.kind !== "record" && expected.kind !== "array") return null; + if (expected.kind === "record" && lowerer.shapes.get(expected.shapeId)?.indexValue !== undefined) + return null; + if ( + !lowerer.dynConvertible(expr.type) || + !canDynCheckTo( + expected, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) + ) + return null; + return { kind: "dynCheck", value: lowerer.coerceToExpected(expr, DYN), type: expected, loc }; +} + +/** True when a value of `arm` has a structural conversion into `target`: + * the same type, one of its arms, a width lift, or (into an + * index-signature record) the overflow capture. */ +function convertsInto(lowerer: Lowerer, arm: IrType, target: IrType, loc: SrcLoc): boolean { + if (typeEquals(arm, target)) return true; + if ( + target.kind === "union" && + lowerer.unions.get(target.unionId)?.arms.some((candidate) => typeEquals(candidate, arm)) + ) + return true; + if (lowerer.widthLiftPlan(arm, target) !== null) return true; + return ( + arm.kind === "record" && + target.kind === "record" && + lowerer.shapes.get(target.shapeId)?.indexValue !== undefined && + lowerRecordOvfCaptureHelper(lowerer, arm.shapeId, target.shapeId, loc) !== null + ); +} diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 9f0559672..19f879bcf 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -1481,6 +1481,7 @@ export function lowerAsExpression( target?.kind === "record" && inner.type.shapeId !== target.shapeId ) { + lowerer.assertedValues.set(inner, target); return lowerer.coerceInto(expr, inner, target); } // `u as Arm` on a UNION value is `u!`'s spelling with a named arm @@ -1515,6 +1516,15 @@ export function lowerAsExpression( const asserted = checkedClassUnionAssertion(lowerer, inner, target, locOf(expr)); if (asserted) return asserted; } + // The erased value still answers to the assertion: a slot it cannot + // reach by an ordinary conversion converts it as asserted + // (assertedConversion). + if ( + target && + (target.kind === "union" || target.kind === "record" || target.kind === "array") && + (inner.type.kind === "union" || inner.type.kind === "array") + ) + lowerer.assertedValues.set(inner, target); return inner; // erasure, unchanged } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index feca34427..491ca7c11 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -118,6 +118,7 @@ import { deferredReadHelper, widthNarrowHelper, } from "./coercions/unions.js"; +import { assertedConversion } from "./coercions/asserted.js"; import { lowerUtilTypeValue, pathModuleValue, @@ -2247,6 +2248,10 @@ export class Lowerer { ts.Type, { tsBindings: Map; instance: ts.Type }[] >(); + /** Values an erased type assertion produced, with the asserted type + * (`x as T` keeps x's representation): a slot that no ordinary + * conversion reaches converts them as asserted (assertedConversion). */ + readonly assertedValues = new WeakMap(); /** Non-null while an IMPLICIT-ANY instance body lowers (npm-static JS; * see generic-functions.ts): bound param symbol → * the call site's checker type, consulted by typeOf for identifier @@ -10599,6 +10604,8 @@ export class Lowerer { if (!typeEquals(expr.type, expected)) { const w = this.widthCoerce(expr, expected); if (w) return w; + const asserted = assertedConversion(this, expr, expected); + if (asserted) return asserted; // A UNIT (null/undefined) flowing into a plain non-nullable slot // the checker approved (`null!` casts, non-strict assignments): // the stranded-source stance (divergence 38) without a union in @@ -10617,7 +10624,7 @@ export class Lowerer { if (helper) { return { kind: "call", callee: helper, args: [expr], type: expected, loc: expr.loc }; } - return expr; + return assertedConversion(this, expr, expected) ?? expr; } if (expr.type.kind === "void") { // A void CALL RESULT flowing into a union with an undefined arm diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/checked-union-record-assertions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/checked-union-record-assertions.ts.json new file mode 100644 index 000000000..ff77d1a95 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/checked-union-record-assertions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/checked-union-record-assertions.ts" + ], + "diags": [] +} diff --git a/tests/corpus/checked-union-record-assertions.ts b/tests/corpus/checked-union-record-assertions.ts new file mode 100644 index 000000000..f5d43cee1 --- /dev/null +++ b/tests/corpus/checked-union-record-assertions.ts @@ -0,0 +1,69 @@ +// Assertions whose target no static conversion reaches: a union asserted to +// the one layout some of its arms convert into, and records or arrays whose +// member types the assertion narrows. The value is checked against the +// asserted type; these programs assert truthfully, so they run as in Node. + +type Api = "alpha" | "beta" | (string & {}); + +interface Streams { + stream: (x: number) => string; + fetch?: (x: number) => string; +} + +interface Options { + name: string; + api?: Streams | Partial>; +} + +function isStreams(value: unknown): value is Streams { + return typeof value === "object" && value !== null && "stream" in value && typeof value.stream === "function"; +} + +function run(input: Options): string[] { + const single = input.api && isStreams(input.api) ? input.api : undefined; + const byApi = single || !input.api ? undefined : (input.api as Partial>); + const all = single ? [single] : Object.values(byApi ?? {}).filter((entry) => entry !== undefined); + return all.map((entry) => `${input.name}:${entry.stream(all.length)}`); +} + +const shared: Streams = { stream: (x) => `shared${x}` }; +console.log(run({ name: "single", api: shared })); +console.log(run<"alpha" | "beta">({ name: "map", api: { alpha: { stream: (x) => `alpha${x}` }, beta: shared } })); +console.log(run<"alpha">({ name: "none" })); + +type KnownKind = "alpha" | "beta" | "gamma"; + +interface AlphaOpts { + fast?: boolean; +} +interface BetaOpts { + depth?: number; +} + +interface Item { + id: string; + kind: TKind; + opts?: TKind extends "alpha" ? AlphaOpts : TKind extends "beta" ? BetaOpts : never; +} + +interface Other { + id: string; + kind: string; + size: number; +} + +const alpha: Item<"alpha"> = { id: "a1", kind: "alpha", opts: { fast: true } }; +const gamma: Item<"gamma"> = { id: "g1", kind: "gamma" }; +const other: Other = { id: "o1", kind: "other", size: 2 }; + +const everything: (Item | Other)[] = [alpha, gamma, other, { id: "g2", kind: "gamma" }]; +const gammas = everything.filter((entry) => entry.kind === "gamma") as Item<"gamma">[]; +console.log(gammas.length, gammas.map((entry) => `${entry.id}:${entry.opts === undefined}`).join(",")); + +const items: Item[] = [gamma, alpha]; +const first = items[0]! as Item<"gamma">; +console.log(first.id, first.kind, first.opts === undefined); + +const stored: { list: (Item | Other)[] } = { list: [gamma] }; +const restored = stored.list as Item<"gamma">[]; +console.log(JSON.stringify(restored)); diff --git a/tests/harness/corpus-costs/checked-union-record-assertions.ts.json b/tests/harness/corpus-costs/checked-union-record-assertions.ts.json new file mode 100644 index 000000000..fc3725628 --- /dev/null +++ b/tests/harness/corpus-costs/checked-union-record-assertions.ts.json @@ -0,0 +1 @@ +{ "plain": 14493, "san": 16666 } diff --git a/tests/harness/dyncheck.test.ts b/tests/harness/dyncheck.test.ts index e1a869048..dfd037570 100644 --- a/tests/harness/dyncheck.test.ts +++ b/tests/harness/dyncheck.test.ts @@ -114,6 +114,32 @@ describe(`dynamic-boundary checks (scriptc-only${sanitize ? ", sanitized" : ""}) expect(r.stderr).toBe(""); }); + test("assertions narrowing record members or union arms check the value", async () => { + const r = await compileAndRun( + "narrowing-record-assertions", + ` + interface AlphaOpts { fast?: boolean } + interface Item { id: string; kind: K; opts?: K extends "alpha" ? AlphaOpts : never } + const items: Item<"alpha" | "gamma">[] = [{ id: "g1", kind: "gamma" }, { id: "a1", kind: "alpha", opts: { fast: true } }]; + try { const all = items as Item<"gamma">[]; console.log(all.length); } + catch (error) { console.log(error instanceof TypeError, (error as Error).message); } + try { const one = items[1]! as Item<"gamma">; console.log(one.id); } + catch (error) { console.log(error instanceof TypeError, (error as Error).message); } + interface S { stream: () => string } + function keys(u: S | { a?: S }): string[] { const m = u as Partial>; return Object.keys(m); } + try { console.log(keys({ a: { stream: () => "a" } }).join()); console.log(keys({ stream: () => "s" })); } + catch (error) { console.log(error instanceof TypeError); } + `, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout).toBe( + "true expected null | undefined at $[1].opts, got object\n" + + "true expected null | undefined at $.opts, got object\n" + + "a\ntrue\n", + ); + expect(r.stderr).toBe(""); + }); + test("typed Promise exits validate fulfillment and preserve rejection", async () => { const result = await compileAndRun( "promise-exit-payload", From 1733a4c84bf815fa283ddcb8672d4ef3df18f5fe Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 15:17:09 -0700 Subject: [PATCH 143/230] Name new corpus programs instead of numbering them Parallel changes kept taking the same next corpus number (6992 twice, the Node island programs renumbered several times). The harness never needed the numbers: it discovers every entry by glob, and 818 programs already have descriptive names. AGENTS.md now makes a name, without a number, the convention for new programs and keeps the numbered ones as they are. The two scripts that sampled programs by number skipped named programs. They now key a named program by a stable hash of its name, so they sample it at the same rate; the numbered programs they pick are unchanged. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 ++ scripts/lowering-order.mjs | 24 ++++++++++++++++++++---- scripts/refresh-cli-profile.mjs | 29 ++++++++++++++++++++++------- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2350d7337..55ccb2eab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,6 +46,8 @@ SCRIPTC_TEST_WORKERS=4 SCRIPTC_SAN=1 pnpm test # sanitized lane Corpus programs are differential tests against Node: every program runs under Node and as a compiled native binary, and stdout, stderr, and exit codes must match byte-for-byte. A new feature lands with corpus programs that pin its behavior both ways. +Name a new corpus program after what it tests, without a number: lowercase words joined by hyphens, such as `tests/corpus/intersection-restated-members.ts`, or a directory such as `tests/corpus//main.ts` for a multi-file program. Parallel changes kept taking the same next number; with names, two changes collide only when they add the same name, which git reports as a conflict. Leave existing numbered programs as they are: never rename or renumber them, and name a new program even when it extends a numbered family. + Record a new or remeasured program's CI cost (used to balance the test shards) in `tests/harness/corpus-costs/.json`, not in `tests/harness/corpus-costs.json`; see the README there. Before opening a PR that adds or changes corpus, diagnostic, or frontend fixtures, run `pnpm test:ts7 --baselines-only`. This fast completeness check uses the same fixture discovery as the parity suite; it does not type-check or replace behavioral parity. Generate missing preflight/order baselines, review their diagnostics and module order, and run the affected `order-parity.test.ts` batches before pushing. Keep existing baseline entries unchanged unless the corresponding behavior intentionally changes. The full `pnpm test:ts7` command also checks completeness before starting its sweep. diff --git a/scripts/lowering-order.mjs b/scripts/lowering-order.mjs index 81611e6a4..6d68530cb 100644 --- a/scripts/lowering-order.mjs +++ b/scripts/lowering-order.mjs @@ -16,13 +16,16 @@ * [--seeds 1,2,3] [--jobs 8] [--entry ]... [--out ] * [--fail-on names|content] * + * --stride and --offset sample the top-level corpus programs by number, or by + * a hash of the name of a program without one. + * * Needs a built compiler (pnpm build). Writes /results.json and prints * a ranking of fields by the programs whose IR they change. With --fail-on, * it exits with status 1 when some program's IR differs between orders: * in any way (names), or beyond names and ids (content). */ import { spawn } from "node:child_process"; import { createHash } from "node:crypto"; -import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs"; import { cpus, tmpdir } from "node:os"; import { basename, join, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; @@ -306,12 +309,25 @@ if (values["fail-on"] !== undefined && !["names", "content"].includes(values["fa const out = resolve(values.out); mkdirSync(out, { recursive: true }); const seeds = values.seeds.split(",").filter(Boolean); +/** A top-level corpus program's sampling key: its number, or, for a program + * named without one, a stable hash of its name (FNV-1a). */ +const programKey = (name) => + /^\d+-/.test(name) + ? Number.parseInt(name, 10) + : [...name].reduce((h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619) >>> 0, 2166136261); +/** Numbered programs by number, then the named ones by name. */ +const programOrder = (a, b) => + /^\d+-/.test(a) !== /^\d+-/.test(b) + ? /^\d+-/.test(a) + ? -1 + : 1 + : programKey(a) - programKey(b) || (a < b ? -1 : a > b ? 1 : 0); const corpus = join(root, "tests/corpus"); const programs = [ ...readdirSync(corpus) - .filter((name) => /^\d+-.*\.ts$/.test(name)) - .filter((name) => Number.parseInt(name, 10) % Number(values.stride) === Number(values.offset)) - .sort((a, b) => Number.parseInt(a, 10) - Number.parseInt(b, 10)) + .filter((name) => /^[\w-]+\.ts$/.test(name) && statSync(join(corpus, name)).isFile()) + .filter((name) => programKey(name) % Number(values.stride) === Number(values.offset)) + .sort(programOrder) .slice(0, Number(values.limit)) .map((name) => join(corpus, name)), ...values.entry.map((entry) => resolve(entry)), diff --git a/scripts/refresh-cli-profile.mjs b/scripts/refresh-cli-profile.mjs index 1285b5503..dde4e21b9 100644 --- a/scripts/refresh-cli-profile.mjs +++ b/scripts/refresh-cli-profile.mjs @@ -7,8 +7,9 @@ * Builds an instrumented native compiler for this Linux host, runs it on the * training builds below, and merges the raw profiles it writes into one * sparse indexed profile. The training builds are tsc-ts (a cold `speed` - * build, then a rebuild after an edit), every seventh corpus program at the - * default optimization and a further slice in `dev`, and the runtime + * build, then a rebuild after an edit), every seventh top-level corpus + * program (by number, or by a hash of the name of a program without one) at + * the default optimization and a further slice in `dev`, and the runtime * benchmark workloads. Run it on a quiet Linux x64 host after changes that * move much compiler code; a stale profile only loses part of its gain. A * refresh takes about 11 minutes on 16 cores. @@ -126,16 +127,30 @@ async function pool(tasks) { return passed; } -const programNumber = (name) => Number.parseInt(name, 10); +/** A top-level corpus program's sampling key: its number, or, for a program + * named without one, a stable hash of its name (FNV-1a). */ +const programKey = (name) => + /^\d+-/.test(name) + ? Number.parseInt(name, 10) + : [...name].reduce((h, c) => Math.imul(h ^ c.charCodeAt(0), 16777619) >>> 0, 2166136261); +/** Numbered programs by number, then the named ones by name. */ +const programOrder = (a, b) => + /^\d+-/.test(a) !== /^\d+-/.test(b) + ? /^\d+-/.test(a) + ? -1 + : 1 + : programKey(a) - programKey(b) || (a < b ? -1 : a > b ? 1 : 0); const corpus = readdirSync(join(root, "tests/corpus")) - .filter((name) => /^\d+-.*\.ts$/.test(name)) - .sort((a, b) => programNumber(a) - programNumber(b)); + .filter( + (name) => /^[\w-]+\.ts$/.test(name) && statSync(join(root, "tests/corpus", name)).isFile(), + ) + .sort(programOrder); const training = [ ...corpus - .filter((name) => programNumber(name) % 7 === 0) + .filter((name) => programKey(name) % 7 === 0) .map((name) => ({ file: join(root, "tests/corpus", name), optimization: "release" })), ...corpus - .filter((name) => programNumber(name) % 14 === 7) + .filter((name) => programKey(name) % 14 === 7) .map((name) => ({ file: join(root, "tests/corpus", name), optimization: "dev" })), ...readdirSync(join(root, "benchmarks/runtime/workloads")) .filter((name) => name.endsWith(".ts")) From d33b083a3b4f49c258e4ff2a911235bcd3abda8a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:30:44 -0500 Subject: [PATCH 144/230] Cache the zone offset at UTC midnights for local-time conversions Local setters and the field constructor convert a local time value to UTC by bracketing it with the zone offset a day before and after, then checking the candidate instants: three to five localtime(3) calls per conversion. In glibc each call takes the zone lock and, for dates past the zone file's last transition, parses the zone's rule string again. The offsets now come from a per-thread cache of offsets at UTC midnights: a day whose two midnights agree has that offset throughout, given at most one transition per day, which the conversion already assumes. A day with a transition still asks localtime for the instant. A change of process.env.TZ clears the cache. --- packages/runtime/src/scr_lib.c | 58 ++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index a9fd25915..1a625cdf1 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -11588,8 +11588,8 @@ double scr_date_get_timezone_offset(double ms) { * from local fields reads those fields back. */ /* The host zone's offset (local - UTC) in ms at UTC instant `ms` (an - * integral time value within about ±8.65e15). */ -static bool scr_date_offset_at(double ms, double *out) { + * integral time value within about ±8.65e15), from the host's localtime. */ +static bool scr_date_offset_at_uncached(double ms, double *out) { long long cms = (long long)ms; double secd = (double)(cms / 1000 - (cms % 1000 < 0)); struct tm tmv; @@ -11611,6 +11611,60 @@ static bool scr_date_offset_at(double ms, double *out) { return true; } +/* Offsets at UTC midnights, for scr_date_offset_at. A day whose two + * midnights have the same offset has that offset throughout, given at most + * one transition per day (scr_date_local_to_utc already takes transitions + * to be at least a day apart). Each local setter asks for three to five + * offsets around one time value, and consecutive ones land on the same or + * neighbouring days, so most answers come from here instead of a + * localtime(3) call: glibc takes its zone lock in each and, for dates past + * the zone file's last transition, parses the zone's rule string again. A + * change of process.env.TZ starts a new generation. */ +#define SCR_DATE_MIDNIGHTS 64 +typedef struct { + long long day; + double offset; + bool valid; +} ScrDateMidnight; +typedef struct { + bool valid; + unsigned generation; + ScrDateMidnight entries[SCR_DATE_MIDNIGHTS]; +} ScrDateMidnightCache; +static SCR_TL ScrDateMidnightCache scr_date_midnight_cache; + +static bool scr_date_midnight_offset(ScrDateMidnightCache *c, long long day, double *out) { + ScrDateMidnight *e = &c->entries[(unsigned long long)day % SCR_DATE_MIDNIGHTS]; + if (e->valid && e->day == day) { + *out = e->offset; + return true; + } + if (!scr_date_offset_at_uncached((double)day * 86400000.0, out)) return false; + e->valid = true; + e->day = day; + e->offset = *out; + return true; +} + +static bool scr_date_offset_at(double ms, double *out) { + ScrDateMidnightCache *c = &scr_date_midnight_cache; + unsigned generation = scr_date_tz_generation; + if (!c->valid || c->generation != generation) { + memset(c, 0, sizeof *c); + c->valid = true; + c->generation = generation; + } + long long cms = (long long)ms; + long long day = cms / 86400000 - (cms % 86400000 < 0); + double start, end; + if (scr_date_midnight_offset(c, day, &start) && scr_date_midnight_offset(c, day + 1, &end) && + start == end) { + *out = start; + return true; + } + return scr_date_offset_at_uncached(ms, out); +} + static volatile const double scr_date_day_ms = 86400000.0; static double scr_date_local_to_utc(double t) { From 46785f8ebac9826fcc59ff3a87617e986c5f5f9b Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:35:38 -0500 Subject: [PATCH 145/230] Address review feedback --- .../frontend/lowering/coercions/structural.ts | 6 ++- .../src/frontend/lowering/decl-origin.ts | 22 +++++--- packages/compiler/src/frontend/program.ts | 2 + .../node_modules/gridctx/index.js | 43 +++++++++++++++ .../node_modules/gridctx/package.json | 1 + .../node_modules/panectx/index.js | 43 +++++++++++++++ .../node_modules/panectx/package.json | 1 + tests/fixtures/type-patches/same-name/main.ts | 15 ++++++ .../type-patches/same-name/package.json | 1 + tests/harness/type-patches.test.ts | 53 ++++++++++++++++++- 10 files changed, 177 insertions(+), 10 deletions(-) create mode 100644 tests/fixtures/type-patches/node_modules/gridctx/index.js create mode 100644 tests/fixtures/type-patches/node_modules/gridctx/package.json create mode 100644 tests/fixtures/type-patches/node_modules/panectx/index.js create mode 100644 tests/fixtures/type-patches/node_modules/panectx/package.json create mode 100644 tests/fixtures/type-patches/same-name/main.ts create mode 100644 tests/fixtures/type-patches/same-name/package.json diff --git a/packages/compiler/src/frontend/lowering/coercions/structural.ts b/packages/compiler/src/frontend/lowering/coercions/structural.ts index 76c6c9c3b..863472910 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural.ts @@ -398,10 +398,12 @@ export function recordWidthHelper( // Package code converting into a package-declared record type (an // object literal returned under `@returns {HelpContext}`): its field // extractions name the declared fields (decl-origin.ts), so the helper - // is keyed by that type too. Every other projection is shared as before. + // is keyed by that type's declaration too (two packages may declare + // records of the same name and shape). Every other projection is shared + // as before. const declared = lowerer.coercionRecordOrigin(); const key = - declared === null ? `rec:${fromId}:${toId}` : `rec:${fromId}:${toId}:origin:${declared.name}`; + declared === null ? `rec:${fromId}:${toId}` : `rec:${fromId}:${toId}:origin:${declared.key}`; const existing = lowerer.valueHelpers.get(key); if (existing) return existing; const name = keyedHelperName(lowerer, "%rec.width", key); diff --git a/packages/compiler/src/frontend/lowering/decl-origin.ts b/packages/compiler/src/frontend/lowering/decl-origin.ts index 60510ecec..72a135b67 100644 --- a/packages/compiler/src/frontend/lowering/decl-origin.ts +++ b/packages/compiler/src/frontend/lowering/decl-origin.ts @@ -40,8 +40,15 @@ interface PackageFile { rel: string; } +/** Installed package versions by package root, read once per program load + * (resetDeclOriginVersions): a dependency reinstalled at another version + * between two builds of one process is read again. */ const versions = new Map(); +export function resetDeclOriginVersions(): void { + versions.clear(); +} + function packageFileOf(fileName: string): PackageFile | null { const path = fileName.split("\\").join("/"); const marker = "/node_modules/"; @@ -268,6 +275,9 @@ export function slotOrigin(checker: ts.TypeChecker, node: ts.Node): string | nul export interface RecordOrigin { type: ts.Type; name: string; + /** The declaration's identity (package, version, file, line and name): + * records of the same shape and name declared by two packages differ. */ + key: string; } export function recordOrigin(checker: ts.TypeChecker, node: ts.Node): RecordOrigin | null { @@ -277,13 +287,11 @@ export function recordOrigin(checker: ts.TypeChecker, node: ts.Node): RecordOrig const name = symbol === undefined ? "" : ts.symbolName(symbol); if (!/^[A-Za-z_$][\w$]*$/.test(name)) return null; const decl = symbol?.declarations[0]; - if ( - decl === undefined || - genericDeclaration(decl) || - packageFileOf(ts.getSourceFileOfNode(decl).fileName) === null - ) - return null; - return { type: contextual, name }; + if (decl === undefined || genericDeclaration(decl)) return null; + const file = packageFileOf(ts.getSourceFileOfNode(decl).fileName); + if (file === null) return null; + const key = [file.pkg, file.version, file.rel, lineOf(decl), name].join(ORIGIN_SEPARATOR); + return { type: contextual, name, key }; } /** The origin of one field of a package-declared record type. */ diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index 44e048916..44979ca68 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -145,6 +145,7 @@ import { type DeclProjection, } from "./decl-projection.js"; import { configureTypePatches, packageSourceText } from "./type-patches/layer.js"; +import { resetDeclOriginVersions } from "./lowering/decl-origin.js"; import { reportShapes } from "./shape-inference/index.js"; import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js"; import { @@ -1082,6 +1083,7 @@ export function loadProgram( // (chalk 6) compile from their own source. configurePackageShims(entryPath); resetDeclBoundaryOrigins(); + resetDeclOriginVersions(); // The project's type patches (type-patches/layer.ts), read by every host // this load creates; nothing at all without `scriptc.typePatches`. configureTypePatches(entryPath, (path, text, kind) => services.parse(path, text, kind)); diff --git a/tests/fixtures/type-patches/node_modules/gridctx/index.js b/tests/fixtures/type-patches/node_modules/gridctx/index.js new file mode 100644 index 000000000..44c282ad7 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/gridctx/index.js @@ -0,0 +1,43 @@ +"use strict"; +// termctx's Terminal under another name: panectx and gridctx declare the +// same OutputContext (one name, one shape) in two packages. + +class Terminal { + constructor(tty) { + this.tty = tty; + this.settings = { + columns: () => (this.tty ? 80 : undefined), + }; + } + + /** + * @returns {number} + */ + columns() { + return this.settings.columns(); + } + + /** + * @typedef OutputContext + * @type {object} + * @property {boolean} error + * @property {number} width + * + * @param {boolean} error + * @returns {OutputContext} + */ + outputContext(error) { + const width = this.settings.columns(); + return { error, width }; + } + + /** + * @param {OutputContext} context + * @returns {string} + */ + describe(context) { + return `width ${context.width}${context.error ? " (stderr)" : ""}`; + } +} + +module.exports = { Terminal }; diff --git a/tests/fixtures/type-patches/node_modules/gridctx/package.json b/tests/fixtures/type-patches/node_modules/gridctx/package.json new file mode 100644 index 000000000..36f6aba11 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/gridctx/package.json @@ -0,0 +1 @@ +{ "name": "gridctx", "version": "4.1.0", "main": "index.js" } diff --git a/tests/fixtures/type-patches/node_modules/panectx/index.js b/tests/fixtures/type-patches/node_modules/panectx/index.js new file mode 100644 index 000000000..44c282ad7 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/panectx/index.js @@ -0,0 +1,43 @@ +"use strict"; +// termctx's Terminal under another name: panectx and gridctx declare the +// same OutputContext (one name, one shape) in two packages. + +class Terminal { + constructor(tty) { + this.tty = tty; + this.settings = { + columns: () => (this.tty ? 80 : undefined), + }; + } + + /** + * @returns {number} + */ + columns() { + return this.settings.columns(); + } + + /** + * @typedef OutputContext + * @type {object} + * @property {boolean} error + * @property {number} width + * + * @param {boolean} error + * @returns {OutputContext} + */ + outputContext(error) { + const width = this.settings.columns(); + return { error, width }; + } + + /** + * @param {OutputContext} context + * @returns {string} + */ + describe(context) { + return `width ${context.width}${context.error ? " (stderr)" : ""}`; + } +} + +module.exports = { Terminal }; diff --git a/tests/fixtures/type-patches/node_modules/panectx/package.json b/tests/fixtures/type-patches/node_modules/panectx/package.json new file mode 100644 index 000000000..e73708257 --- /dev/null +++ b/tests/fixtures/type-patches/node_modules/panectx/package.json @@ -0,0 +1 @@ +{ "name": "panectx", "version": "2.0.0", "main": "index.js" } diff --git a/tests/fixtures/type-patches/same-name/main.ts b/tests/fixtures/type-patches/same-name/main.ts new file mode 100644 index 000000000..3cbfffc57 --- /dev/null +++ b/tests/fixtures/type-patches/same-name/main.ts @@ -0,0 +1,15 @@ +// panectx and gridctx each declare an OutputContext of the same shape: +// each failing check's TypeError names its own package's declaration +// (native only; Node never checks). +import { Terminal as PaneTerminal } from "panectx"; +import { Terminal as GridTerminal } from "gridctx"; + +const pane = new PaneTerminal(false); +const grid = new GridTerminal(false); +for (const read of [() => pane.outputContext(false).width, () => grid.outputContext(false).width]) { + try { + console.log(read()); + } catch (e) { + console.log((e as Error).message); + } +} diff --git a/tests/fixtures/type-patches/same-name/package.json b/tests/fixtures/type-patches/same-name/package.json new file mode 100644 index 000000000..fdf221cba --- /dev/null +++ b/tests/fixtures/type-patches/same-name/package.json @@ -0,0 +1 @@ +{ "name": "type-patches-same-name", "private": true, "type": "module" } diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index cfd8b4dd6..7afb4433d 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -9,7 +9,8 @@ * type-patch-types-twin, type-patch-files, type-patch-types-entry); here * their unpatched copies show the patches are what makes them compile, and * a failing check names the declaration and the patch (scr_type_origin.c, - * frontend/lowering/decl-origin.ts). And + * frontend/lowering/decl-origin.ts), its own package's also when two + * packages declare a record of one name and shape. And * commander-calc carries the HelpContext.helpWidth patch. */ import { execFile } from "node:child_process"; import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; @@ -193,6 +194,56 @@ describe("type patches", () => { }, ); + test( + "records of one name and shape from two packages name their own declarations", + { timeout: 600_000 }, + async () => { + const result = await build(join(fixture, "same-name/main.ts"), "same-name", [ + "panectx", + "gridctx", + ]); + const native = await run(result.binaryPath, []); + const failure = (pkg: string, version: string) => [ + "undefined is not representable in the target union (a value narrowed or asserted past it still held it)", + ` ${pkg}@${version} declares OutputContext.width as number (index.js:24)`, + ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "${pkg}@^${version}": { "declarations": { "OutputContext.width": "| undefined" } } } }`, + ]; + expect(native.stdout).toBe( + [...failure("panectx", "2.0.0"), ...failure("gridctx", "4.1.0"), ""].join("\n"), + ); + }, + ); + + test( + "a rebuild after the package's version changes names the installed version", + { timeout: 600_000 }, + async () => { + const copy = mkdtempSync(join(tmpdir(), "scriptc-reinstalled-")); + try { + cpSync(fixture, copy, { recursive: true }); + const entry = join(copy, "unpatched/main.ts"); + const declares = async (name: string) => { + const native = await run((await build(entry, name, ["termctx"])).binaryPath, []); + return native.stdout.split("\n").filter((line) => line.includes(" declares ")); + }; + expect(await declares("reinstalled-1.2.0")).toEqual([ + " termctx@1.2.0 declares Terminal.columns() as number (index.js:16)", + " termctx@1.2.0 declares OutputContext.width as number (index.js:24)", + ]); + writeFileSync( + join(copy, "node_modules/termctx/package.json"), + '{ "name": "termctx", "version": "1.3.0", "main": "index.js" }\n', + ); + expect(await declares("reinstalled-1.3.0")).toEqual([ + " termctx@1.3.0 declares Terminal.columns() as number (index.js:16)", + " termctx@1.3.0 declares OutputContext.width as number (index.js:24)", + ]); + } finally { + rmSync(copy, { recursive: true, force: true }); + } + }, + ); + test( "patches that do not apply as written warn and the build goes on", { timeout: 600_000 }, From b372816337ced999adcc8653539a9fc08b7ea41e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:39:01 -0500 Subject: [PATCH 146/230] Address review feedback --- .../src/frontend/type-patches/command.ts | 53 ++++++++++++++----- .../test/type-patches-command.test.ts | 50 ++++++++++++++++- 2 files changed, 88 insertions(+), 15 deletions(-) diff --git a/packages/compiler/src/frontend/type-patches/command.ts b/packages/compiler/src/frontend/type-patches/command.ts index d7fb817ae..99c321710 100644 --- a/packages/compiler/src/frontend/type-patches/command.ts +++ b/packages/compiler/src/frontend/type-patches/command.ts @@ -131,13 +131,15 @@ interface FileMatch extends PatchMatch { } /** Every declaration a selector matches in a copy, and the problems where - * it cannot apply. */ + * it cannot apply (`always`: a problem even where the selector matched + * elsewhere, such as an ambiguous replacement, which leaves those + * declarations as they are). */ function matchCopy( copy: PackageCopy, selector: Selector, op: TypePatchOp, parse: TypePatchParse, -): { matches: FileMatch[]; problems: string[] } { +): { matches: FileMatch[]; problems: { message: string; always: boolean }[] } { const words = selector.root === "export=" ? ["exports", "export"] @@ -145,7 +147,7 @@ function matchCopy( ? ["default"] : [selector.root]; const matches: FileMatch[] = []; - const problems: string[] = []; + const problems: { message: string; always: boolean }[] = []; for (const rel of packageFiles(copy.root)) { if (selector.file !== null && selector.file !== rel) continue; const path = join(copy.root, rel); @@ -165,7 +167,8 @@ function matchCopy( continue; } for (const m of result.matches) matches.push({ ...m, file: rel }); - for (const p of result.problems) problems.push(`${p.message} (${rel})`); + for (const p of result.problems) + problems.push({ message: `${p.message} (${rel})`, always: p.always === true }); } return { matches, problems }; } @@ -258,7 +261,7 @@ function patchCommand( const lines: string[] = []; let matched = 0; let changed = 0; - const problems: string[] = []; + const problems: { message: string; always: boolean }[] = []; for (const copy of copies) { const found = matchCopy(copy, selector, op, parse); problems.push(...found.problems); @@ -272,9 +275,16 @@ function patchCommand( } if (matched === 0) { const why = - problems.length > 0 ? problems.join("\n") : `'${req.selector}' matches no declaration`; + problems.length > 0 + ? problems.map((p) => p.message).join("\n") + : `'${req.selector}' matches no declaration`; return fail(`${why}\nnothing was written`); } + // A declaration the patch cannot apply to where it also matched (an + // ambiguous replacement) would stay as it is. + const unapplied = problems.filter((p) => p.always); + if (unapplied.length > 0) + return fail(`${unapplied.map((p) => p.message).join("\n")}\nnothing was written`); if (changed === 0) { lines.push("the declarations already say this; nothing was written"); return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; @@ -286,15 +296,30 @@ function patchCommand( } catch { return fail(`${relative(req.cwd, manifest) || "package.json"} is not valid JSON`); } - // An existing entry whose range covers the installed version takes the - // patch; otherwise a new one under the range asked for. - const existing = parseProjectConfig(manifest, text).typePatches.find( - (e: TypePatchEntry) => - e.pkg === req.pkg && - (req.range === null - ? version !== null && satisfies(version, e.range) - : e.range === req.range), + // The compiler applies every entry whose range covers the installed + // version, and a later entry wins on the same selector. Without a range, + // the patch goes to the last covering entry that patches the selector + // already, else to the first covering entry; with one, to the entry of + // that range. Otherwise a new entry, which comes last, takes it. + const entries = parseProjectConfig(manifest, text).typePatches.filter( + (e: TypePatchEntry) => e.pkg === req.pkg, ); + const covers = (e: TypePatchEntry): boolean => version !== null && satisfies(version, e.range); + const declares = (e: TypePatchEntry): boolean => + e.declarations.some((d) => d.selector === req.selector); + const existing = + req.range === null + ? (entries.findLast((e) => covers(e) && declares(e)) ?? entries.find(covers)) + : entries.find((e) => e.range === req.range); + if (existing !== undefined && covers(existing)) { + const later = entries + .slice(entries.indexOf(existing) + 1) + .find((e) => covers(e) && declares(e)); + if (later !== undefined) + return fail( + `scriptc.typePatches["${later.key}"] also patches '${req.selector}' for ${req.pkg} ${version} and takes precedence over scriptc.typePatches["${existing.key}"]\nnothing was written`, + ); + } const key = existing?.key ?? `${req.pkg}@${req.range ?? (version === null ? "*" : `^${version}`)}`; const where = `scriptc.typePatches["${key}"].declarations["${req.selector}"]`; diff --git a/packages/compiler/test/type-patches-command.test.ts b/packages/compiler/test/type-patches-command.test.ts index 7fc8a0d6c..12f8a97bf 100644 --- a/packages/compiler/test/type-patches-command.test.ts +++ b/packages/compiler/test/type-patches-command.test.ts @@ -4,7 +4,7 @@ * nearest package.json under a range covering the installed version (or * an existing entry that covers it), and nothing is written when the * selector matches nothing. */ -import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, expect, test } from "vitest"; @@ -118,3 +118,51 @@ test("list shows each patch and what it matches", () => { "termctx@^2.0.0\n does not apply to the installed termctx 1.2.0\n A.b: | undefined\n", ); }); + +test("nothing is written when a replacement is ambiguous in one of the files it matches", () => { + const pkg = join(root, "node_modules/overloads"); + mkdirSync(pkg, { recursive: true }); + writeFileSync( + join(pkg, "package.json"), + JSON.stringify({ name: "overloads", version: "1.0.0", types: "index.d.ts" }), + ); + writeFileSync(join(pkg, "index.d.ts"), "export declare function pick(a: string): string;\n"); + writeFileSync( + join(pkg, "more.d.ts"), + "export declare function pick(a: string): string;\nexport declare function pick(a: number): string;\n", + ); + const result = patch("overloads", "pick()", "string | undefined"); + expect(result.exitCode).toBe(1); + expect(result.stderr).toBe( + "scriptc: 'pick()' matches 2 declarations in more.d.ts; a replacement must name one " + + "(widen them, or replace the declaration file) (more.d.ts)\nnothing was written\n", + ); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8")).scriptc).toBeUndefined(); +}); + +test("the patch goes to the covering entry the compiler applies", () => { + const manifest = join(root, "package.json"); + const entries = { + "termctx@^1.0.0": { declarations: { "OutputContext.width": "| null" } }, + "termctx@1.x": { declarations: { "OutputContext.width": "| null" } }, + }; + writeFileSync(manifest, JSON.stringify({ name: "app", scriptc: { typePatches: entries } })); + // Both ranges cover termctx 1.2.0, and the later entry wins on the same + // selector: it takes the patch. + const result = patch("termctx", "OutputContext.width", "| undefined"); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain( + `wrote package.json scriptc.typePatches["termctx@1.x"].declarations["OutputContext.width"]\n`, + ); + expect(JSON.parse(readFileSync(manifest, "utf8")).scriptc.typePatches).toEqual({ + "termctx@^1.0.0": { declarations: { "OutputContext.width": "| null" } }, + "termctx@1.x": { declarations: { "OutputContext.width": "| undefined" } }, + }); + // Asked for the earlier range, the later entry would still win. + const earlier = patch("termctx", "OutputContext.width", "| undefined", { range: "^1.0.0" }); + expect(earlier.exitCode).toBe(1); + expect(earlier.stderr).toBe( + `scriptc: scriptc.typePatches["termctx@1.x"] also patches 'OutputContext.width' for termctx 1.2.0 ` + + `and takes precedence over scriptc.typePatches["termctx@^1.0.0"]\nnothing was written\n`, + ); +}); From cc08abcbe72c4082a81a195d2627eb258c49119d Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:42:53 -0500 Subject: [PATCH 147/230] Match the pending-flag fallback edge after the callback's receiver block An indirect callback call now gives the closure an undefined receiver when it reads one, so the fallback's edge into the pending-flag phi comes from the receiver block instead of callback.indirect. The flag is still false on the direct call and true on the fallback. --- packages/compiler/test/callback-pending-emission.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/compiler/test/callback-pending-emission.test.ts b/packages/compiler/test/callback-pending-emission.test.ts index 3b2ed3823..7e7996c83 100644 --- a/packages/compiler/test/callback-pending-emission.test.ts +++ b/packages/compiler/test/callback-pending-emission.test.ts @@ -40,9 +40,11 @@ try { const module = deserializeModule(readFileSync(output, "utf8")); expect(validateModule(module)).toEqual([]); const llvm = emitLlvmModule(module, {}); + // The fallback calls the closure's entry, after giving it an undefined + // receiver when it reads one, so its edge comes from receiver.done then. const guards = [ ...llvm.matchAll( - /(%t\d+) = phi i1 \[ false, %callback\.direct\d+ \], \[ true, %callback\.indirect\d+ \]/g, + /(%t\d+) = phi i1 \[ false, %callback\.direct\d+ \], \[ true, %(?:callback\.indirect|receiver\.done)\d+ \]/g, ), ]; // The quiet every and map callbacks; not the throwing one. From 5b40498c22c8ac7f4096e05e724a0b0bff177568 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 17:52:20 -0500 Subject: [PATCH 148/230] Keep asserted values in a map the lowerer owns compiler-weak-collections.test.ts refuses weak collections in compiler sources: the compiled compiler must not depend on which of its values can serve as weak keys. The lowerer owns the assertedValues cache for its pass, so an ordinary Map holds it. --- packages/compiler/src/frontend/lowering/lowerer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 491ca7c11..d665cc3ff 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2251,7 +2251,7 @@ export class Lowerer { /** Values an erased type assertion produced, with the asserted type * (`x as T` keeps x's representation): a slot that no ordinary * conversion reaches converts them as asserted (assertedConversion). */ - readonly assertedValues = new WeakMap(); + readonly assertedValues = new Map(); /** Non-null while an IMPLICIT-ANY instance body lowers (npm-static JS; * see generic-functions.ts): bound param symbol → * the call site's checker type, consulted by typeOf for identifier From e1780421a12b0528384d829dd492fcf5e151603e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 15:32:59 -0700 Subject: [PATCH 149/230] Declare the origin note, skip it in library builds, and keep the cast order on the lowerer --- packages/compiler/src/backend/llvm/expr-async.ts | 7 ++++++- .../src/frontend/lowering/expressions/narrowing.ts | 10 ++++------ packages/compiler/src/frontend/lowering/lowerer.ts | 3 +++ packages/runtime/src/scr_dyn.h | 5 +++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/backend/llvm/expr-async.ts b/packages/compiler/src/backend/llvm/expr-async.ts index 28a4cf57f..037179d8e 100644 --- a/packages/compiler/src/backend/llvm/expr-async.ts +++ b/packages/compiler/src/backend/llvm/expr-async.ts @@ -374,7 +374,12 @@ export function emitSerializationExpr( // falls back to exactly this route whenever the fast path declines. // A package-declared target: the cold path names the declaration in // the pending TypeError (scr_type_origin.c). - const origin = e.origin === undefined ? undefined : host.mod.declOrigins?.[e.origin]; + // Library archives and reactors link a fixed runtime set without the + // note unit: their checks keep the plain message. + const origin = + e.origin === undefined || host.mod.lib !== undefined + ? undefined + : host.mod.declOrigins?.[e.origin]; const note = origin === undefined ? undefined diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 5c1b276f7..7043367da 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -1640,14 +1640,12 @@ function holdsReference(lowerer: Lowerer, t: IrType, seen = new Set()): * 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. */ -const boundaryCastOrder = new WeakMap>(); - /** The origin the projection recorded for a boundary cast (the package * declaration its type comes from, decl-projection.ts), found by the * cast's position among the file's boundary casts in source order. */ -function recordedBoundaryOrigin(cast: ts.Node): string | null { +function recordedBoundaryOrigin(lowerer: Lowerer, cast: ts.Node): string | null { const sf = ts.getSourceFileOfNode(cast); - let order = boundaryCastOrder.get(sf); + let order = lowerer.boundaryCastOrder.get(sf); if (order === undefined) { const found = new Map(); const visit = (node: ts.Node): void => { @@ -1666,7 +1664,7 @@ function recordedBoundaryOrigin(cast: ts.Node): string | null { }; visit(sf); order = found; - boundaryCastOrder.set(sf, order); + lowerer.boundaryCastOrder.set(sf, order); } const index = order.get(cast); return index === undefined ? null : declBoundaryOrigin(sf.fileName, index); @@ -1692,7 +1690,7 @@ function declBoundaryCast( // failing check names it and the patch admitting the value. let originText: string | null = null; try { - originText = recordedBoundaryOrigin(cast); + originText = recordedBoundaryOrigin(lowerer, cast); } catch { originText = null; } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 5b5ae3471..45e0067fc 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -3778,6 +3778,9 @@ export class Lowerer { * in a package file: union extractions built for it carry the slot's * origin in their TypeError. */ coercionOriginNode: ts.Node | null = null; + /** Per user file, each declaration-boundary cast's index in source + * order (narrowing.ts recordedBoundaryOrigin). */ + readonly boundaryCastOrder = new Map>(); /** The union type of that value: only its own extraction is the slot's. */ coercionOriginUnion: string | null = null; /** While a record projection into a package-declared type builds, the diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 1004a93c2..cce1f4b53 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -391,6 +391,11 @@ const ScrDynTypedRecordOps *scr_dyn_typed_ref_live_record(const ScrDyn *d); * 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); +/* A checked conversion into a type an npm package declares failed + * (scr_type_origin.c, linked by programs with such checks): the pending + * TypeError's message gains the declaration and the type patch that would + * admit the value. `origin` is the compiler's origin string. */ +void scr_type_origin_note(const char *origin); /* Prototype members of a compiled class seen as `unknown`, emitted per * class by the compiler: the string names of methods, getters and setters From 7675b7d6538a86e4299439d54c57385aeb5b5471 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 14:35:16 -0700 Subject: [PATCH 150/230] Add scriptc types patch and scriptc types list `scriptc types patch [--range] [--dry-run]` finds the installed package from the working directory, shows every declaration the selector matches (declaration files, JSDoc, @types package) with its type before and after, and adds the patch to the nearest package.json: into an entry whose range covers the installed version, or under ^. Nothing is written when the selector matches nothing. `scriptc types list` shows each patch and what it matches. Both CLI hosts run the same implementation with their own parser. The TypeError note for a wrong declaration now names the command. Co-Authored-By: Claude Opus 5.5 --- docs/content/docs/cli.mdx | 27 +- docs/content/docs/dependencies.mdx | 13 +- packages/cli/src/main.ts | 2 + packages/compiler/src/cli/command.ts | 34 ++ packages/compiler/src/cli/host.ts | 6 + packages/compiler/src/cli/usage.ts | 9 + .../src/frontend/lowering/decl-origin.ts | 4 +- .../src/frontend/type-patches/command.ts | 382 ++++++++++++++++++ packages/compiler/src/index.ts | 15 + packages/compiler/src/native/cli.ts | 14 + .../test/type-patches-command.test.ts | 120 ++++++ packages/runtime/src/scr_type_origin.c | 20 +- tests/harness/type-patches.test.ts | 4 +- 13 files changed, 632 insertions(+), 18 deletions(-) create mode 100644 packages/compiler/src/frontend/type-patches/command.ts create mode 100644 packages/compiler/test/type-patches-command.test.ts diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index 4c1b7e7c6..7166d3f10 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -2,7 +2,7 @@ title: "CLI Reference" --- -The `scriptc` command has five subcommands: `build`, `run`, `coverage`, `cache warm`, and `merge-profiles`. `scriptc --help` prints this summary and the options below, and `scriptc --version` prints the installed version. +The `scriptc` command has six subcommands: `build`, `run`, `coverage`, `types`, `cache warm`, and `merge-profiles`. `scriptc --help` prints this summary and the options below, and `scriptc --version` prints the installed version. ```console $ scriptc --help @@ -21,6 +21,13 @@ Usage: exporting the profile symbols; a profile with a sidecar section also gets the contract sidecar JSON beside the archive + scriptc types patch [--range ] [--dry-run] + fix a wrong declaration of an installed + package: shows what the selector + matches and adds the type patch to + package.json ("| undefined" widens) + scriptc types list the project's type patches and what + they match scriptc cache warm [runtime|tls…] verify and load precompiled runtime families for the current compiler/SDK/target scriptc merge-profiles -o [--sparse] @@ -88,6 +95,24 @@ $ scriptc run fib.ts Analyzes the program without building it and reports what compiles and what blocks the rest. See [Coverage Reports](/docs/coverage). +## scriptc types + +Writes and lists [type patches](/docs/dependencies#fix-a-packages-types), the project's fixes for wrong declarations of npm packages. + +
+
scriptc types patch <package> <selector> <type>
+
Finds the package installed for the working directory, prints every declaration the selector matches in its declaration files, its JSDoc and its @types/ package with the type before and after, and adds the patch to the nearest package.json. The patch joins an entry whose range covers the installed version, or gets the range ^<installed version>. Nothing is written when the selector matches nothing. --range <range> chooses the range, and --dry-run only prints.
+
scriptc types list
+
Prints each type patch in the nearest package.json, whether it applies to the installed version, and what it matches.
+
+ +```console +$ scriptc types list +commander@^15.0.0 + HelpContext.helpWidth: | undefined + node_modules/commander/lib/command.js:2481 number -> number | undefined +``` + ## scriptc cache warm Verifies and loads the installed precompiled runtime objects and vendored libraries, and prints the cache directory. Pass `runtime`, `tls`, or both to select which to warm. Ordinary builds don't need this step. With `--sanitize`, it builds the instrumented runtime objects using a C toolchain. diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index 58b0a0a1d..2288aeb83 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -150,10 +150,19 @@ When the type comes from a package's declarations, the error names the declarati ```text Uncaught TypeError: expected string at $, got undefined profilekit@3.1.0 declares Profile.email as string (index.d.ts:7) - if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } } + if that declaration is wrong, patch it: scriptc types patch profilekit "Profile.email" "| undefined" ``` -Fix such a declaration in your project with a type patch in `package.json`: +Fix such a declaration in your project with a type patch. `scriptc types patch` shows what a declaration path matches in the installed package and adds the patch to your `package.json`: + +```console +$ scriptc types patch commander "HelpContext.helpWidth" "| undefined" +commander 15.0.0 (node_modules/commander) + lib/command.js:2481 number -> number | undefined +wrote package.json scriptc.typePatches["commander@^15.0.0"].declarations["HelpContext.helpWidth"] +``` + +The patch it writes looks like this: ```json:package.json { diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index 8203b959e..5deb4ca29 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -10,6 +10,7 @@ import { mergeProfiles, resolveProvenanceSources, sourceTargetPlatform, + typePatches, warmNativeCaches, } from "@scriptc/compiler"; import { runCli } from "./command.js"; @@ -39,6 +40,7 @@ process.exitCode = await runCli(process.argv.slice(2), { sourceTargetPlatform, warmNativeCaches, mergeProfiles, + typePatches, run: (binary) => new Promise((resolveExit) => { let child; diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index 8b2e4c480..1064b18ad 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -1,3 +1,4 @@ +import type { TypePatchCommandRequest } from "../frontend/type-patches/command.js"; import { rmSync, statSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { parseArgs } from "node:util"; @@ -205,6 +206,39 @@ async function main(args: string[], host: CliHost): Promise { process.stdout.write(`${outputPath}\n`); return 0; } + if (command === "types") { + const sub = positionals[1]; + const rest = positionals.slice(2); + let request: TypePatchCommandRequest; + if (sub === "patch" && rest.length === 3) { + request = { + action: "patch", + cwd: process.cwd(), + pkg: rest[0]!, + selector: rest[1]!, + type: rest[2]!, + range: values.range ?? null, + dryRun: values["dry-run"], + }; + } else if ( + sub === "list" && + rest.length === 0 && + values.range === undefined && + !values["dry-run"] + ) { + request = { action: "list", cwd: process.cwd() }; + } else { + fail( + `scriptc types takes "patch " (with --range or --dry-run) or "list"\n\n${USAGE}`, + ); + } + const result = await host.typePatches(request); + process.stdout.write(result.stdout); + process.stderr.write(result.stderr); + return result.exitCode; + } + if (values.range !== undefined || values["dry-run"]) + fail(`--range and --dry-run are scriptc types patch options\n\n${USAGE}`); if (values.sparse) fail(`--sparse is a merge-profiles option\n\n${USAGE}`); if (command !== "build" && command !== "run" && command !== "coverage") { fail(`unknown command "${command}"\n\n${USAGE}`); diff --git a/packages/compiler/src/cli/host.ts b/packages/compiler/src/cli/host.ts index 7a927ee39..eafe1882b 100644 --- a/packages/compiler/src/cli/host.ts +++ b/packages/compiler/src/cli/host.ts @@ -1,3 +1,7 @@ +import type { + TypePatchCommandRequest, + TypePatchCommandResult, +} from "../frontend/type-patches/command.js"; import type { AnalyzeOptions, AnalyzeResult, @@ -30,5 +34,7 @@ export interface CliHost { /** Merges raw or indexed profiles into one indexed profile; a sparse one * leaves out functions that never ran. */ mergeProfiles: (outputPath: string, inputPaths: string[], sparse: boolean) => Promise; + /** `scriptc types patch|list` (frontend/type-patches/command.ts). */ + typePatches: (request: TypePatchCommandRequest) => Promise; run: (binary: string) => Promise; } diff --git a/packages/compiler/src/cli/usage.ts b/packages/compiler/src/cli/usage.ts index f8b6a4869..2d4b101fd 100644 --- a/packages/compiler/src/cli/usage.ts +++ b/packages/compiler/src/cli/usage.ts @@ -13,6 +13,13 @@ Usage: exporting the profile symbols; a profile with a sidecar section also gets the contract sidecar JSON beside the archive + scriptc types patch [--range ] [--dry-run] + fix a wrong declaration of an installed + package: shows what the selector + matches and adds the type patch to + package.json ("| undefined" widens) + scriptc types list the project's type patches and what + they match scriptc cache warm [runtime|tls…] verify and load precompiled runtime families for the current compiler/SDK/target scriptc merge-profiles -o [--sparse] @@ -157,6 +164,8 @@ export const CLI_OPTIONS = { "profile-generate": { type: "string" }, "profile-use": { type: "string" }, sparse: { type: "boolean", default: false }, + range: { type: "string" }, + "dry-run": { type: "boolean", default: false }, "keep-llvm": { type: "boolean", default: true }, "emit-ir": { type: "boolean", default: false }, sanitize: { type: "boolean", default: false }, diff --git a/packages/compiler/src/frontend/lowering/decl-origin.ts b/packages/compiler/src/frontend/lowering/decl-origin.ts index 60510ecec..a44c54eca 100644 --- a/packages/compiler/src/frontend/lowering/decl-origin.ts +++ b/packages/compiler/src/frontend/lowering/decl-origin.ts @@ -348,12 +348,12 @@ export function declarationSelector(decl: ts.Node): string | null { /** The note a union extraction's TypeError carries for a failing `arm` * (the same text the runtime renders for checked conversions). */ export function renderOriginNote(origin: string, arm: string): string { - const [pkg = "", version = "", range = "", selector = "", where = "", declared = ""] = + const [pkg = "", version = "", , selector = "", where = "", declared = ""] = origin.split(ORIGIN_SEPARATOR); return ( `\n ${pkg}${version === "" ? "" : `@${version}`} declares ${selector}` + `${declared === "" ? "" : ` as ${declared}`} (${where})` + - `\n if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "${pkg}@${range}": { "declarations": { "${selector}": "| ${arm}" } } } }` + `\n if that declaration is wrong, patch it: scriptc types patch ${pkg} "${selector}" "| ${arm}"` ); } diff --git a/packages/compiler/src/frontend/type-patches/command.ts b/packages/compiler/src/frontend/type-patches/command.ts new file mode 100644 index 000000000..d7fb817ae --- /dev/null +++ b/packages/compiler/src/frontend/type-patches/command.ts @@ -0,0 +1,382 @@ +/* `scriptc types patch` and `scriptc types list`: write and inspect the + * project's type patches (package.json scriptc.typePatches). + * + * scriptc types patch [--range ] [--dry-run] + * scriptc types list + * + * `patch` finds the installed package from the working directory, shows + * every declaration the selector matches (in the package's declaration + * files, its JSDoc and its @types package) with its type before and after, + * and adds the patch to the nearest package.json under a range covering + * the installed version (`^` unless --range says otherwise, or an + * existing entry's range when one covers it). Nothing is written when the + * selector matches nothing. `list` shows each configured patch, the + * installed copies it applies to and what it matches. */ + +import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { dirname, join, relative } from "node:path"; +import { parseProjectConfig, type TypePatchEntry } from "../../project-config.js"; +import type { TypePatchParse } from "./layer.js"; +import { patchFile, type PatchMatch } from "./locate.js"; +import { parseSelector, parseTypeText, type Selector, type TypePatchOp } from "./selector.js"; +import { isValidRange, satisfies } from "./semver.js"; + +export type TypePatchCommandRequest = + | { + action: "patch"; + cwd: string; + pkg: string; + selector: string; + type: string; + range: string | null; + dryRun: boolean; + } + | { action: "list"; cwd: string }; + +export interface TypePatchCommandResult { + stdout: string; + stderr: string; + exitCode: number; +} + +interface PackageCopy { + root: string; + version: string | null; + /** True for an `@types/` twin. */ + twin: boolean; +} + +const PATCHABLE = /\.(?:[cm]?js|d\.[cm]?ts|[cm]?ts)$/; + +function nearestManifest(cwd: string): string | null { + for (let dir = cwd; ;) { + const manifest = join(dir, "package.json"); + if (existsSync(manifest)) return manifest; + const parent = dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + +function versionOf(root: string): string | null { + try { + const parsed = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as { + version?: unknown; + }; + return typeof parsed.version === "string" ? parsed.version : null; + } catch { + return null; + } +} + +function typesName(pkg: string): string { + return pkg.startsWith("@") ? `@types/${pkg.slice(1).replace("/", "__")}` : `@types/${pkg}`; +} + +/** The copy of `pkg` (and of its @types twin) Node resolution finds from + * `cwd`. A twin is versioned by the runtime package beside it. */ +function installedCopies(cwd: string, pkg: string): PackageCopy[] { + const out: PackageCopy[] = []; + let runtimeVersion: string | null = null; + let twinFound = false; + for (let dir = cwd; ;) { + const modules = join(dir, "node_modules"); + if (runtimeVersion === null && existsSync(join(modules, pkg, "package.json"))) { + const root = join(modules, pkg); + runtimeVersion = versionOf(root); + out.push({ root, version: runtimeVersion, twin: false }); + } + if (!twinFound && existsSync(join(modules, typesName(pkg), "package.json"))) { + twinFound = true; + out.push({ root: join(modules, typesName(pkg)), version: null, twin: true }); + } + const parent = dirname(dir); + if (parent === dir) break; + dir = parent; + } + for (const copy of out) if (copy.twin) copy.version = runtimeVersion ?? versionOf(copy.root); + return out; +} + +/** The files of a package copy a patch can apply to (nested packages + * excluded), package-relative. */ +function packageFiles(root: string): string[] { + const out: string[] = []; + const walk = (dir: string): void => { + let names: string[]; + try { + names = readdirSync(dir).sort(); + } catch { + return; + } + for (const name of names) { + if (name === "node_modules" || name.startsWith(".")) continue; + const path = join(dir, name); + let isDir = false; + try { + isDir = statSync(path).isDirectory(); + } catch { + continue; + } + if (isDir) walk(path); + else if (PATCHABLE.test(name)) out.push(relative(root, path).split("\\").join("/")); + } + }; + walk(root); + return out; +} + +interface FileMatch extends PatchMatch { + file: string; +} + +/** Every declaration a selector matches in a copy, and the problems where + * it cannot apply. */ +function matchCopy( + copy: PackageCopy, + selector: Selector, + op: TypePatchOp, + parse: TypePatchParse, +): { matches: FileMatch[]; problems: string[] } { + const words = + selector.root === "export=" + ? ["exports", "export"] + : selector.root === "default" + ? ["default"] + : [selector.root]; + const matches: FileMatch[] = []; + const problems: string[] = []; + for (const rel of packageFiles(copy.root)) { + if (selector.file !== null && selector.file !== rel) continue; + const path = join(copy.root, rel); + let text: string; + try { + text = readFileSync(path, "utf8"); + } catch { + continue; + } + if (!words.some((w) => text.includes(w))) continue; + let result; + try { + result = patchFile(rel, text, parse(path, text, /\.[cm]?ts$/.test(rel) ? "ts" : "js"), [ + { id: 0, selector, op }, + ]); + } catch { + continue; + } + for (const m of result.matches) matches.push({ ...m, file: rel }); + for (const p of result.problems) problems.push(`${p.message} (${rel})`); + } + return { matches, problems }; +} + +function shownCopy(copy: PackageCopy, base: string, pkg: string): string { + const name = copy.twin ? typesName(pkg) : pkg; + return `${name} ${copy.version ?? "(no version)"} (${relative(base, copy.root).split("\\").join("/")})`; +} + +function describe(m: FileMatch): string { + if (m.redundant) return ` ${m.file}:${m.line} ${m.before} (already declared so)`; + return ` ${m.file}:${m.line} ${m.before} -> ${m.after}`; +} + +/** The indentation a JSON text uses (two spaces when it cannot tell). */ +function indentOf(text: string): string { + const m = /^[{[]\s*\n([ \t]+)\S/.exec(text); + return m === null ? " " : m[1]!; +} + +function isObject(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +/** JSON text of `value` with `path` set to the string `leaf` (objects + * created along the way, the leaf replaced if present), keeping key order + * and indenting like JSON.stringify(value, null, indent). The parsed value + * itself is only read. */ +function renderJson( + value: unknown, + indent: string, + at: string, + path: readonly string[], + leaf: string, +): string { + const inner = at + indent; + if (path.length > 0 && !isObject(value)) return renderJson({}, indent, at, path, leaf); + if (isObject(value)) { + const keys = Object.keys(value); + const parts: string[] = []; + const head = path[0]; + for (const k of keys) { + const child = + head === k + ? path.length === 1 + ? JSON.stringify(leaf) + : renderJson(value[k], indent, inner, path.slice(1), leaf) + : renderJson(value[k], indent, inner, [], leaf); + parts.push(`${inner}${JSON.stringify(k)}: ${child}`); + } + if (head !== undefined && !keys.includes(head)) { + const child = + path.length === 1 + ? JSON.stringify(leaf) + : renderJson({}, indent, inner, path.slice(1), leaf); + parts.push(`${inner}${JSON.stringify(head)}: ${child}`); + } + return parts.length === 0 ? "{}" : `{\n${parts.join(",\n")}\n${at}}`; + } + if (Array.isArray(value)) { + const items: unknown[] = value; + if (items.length === 0) return "[]"; + return `[\n${items.map((item) => `${inner}${renderJson(item, indent, inner, [], leaf)}`).join(",\n")}\n${at}]`; + } + return JSON.stringify(value) ?? "null"; +} + +function patchCommand( + req: Extract, + parse: TypePatchParse, +): TypePatchCommandResult { + const fail = (message: string): TypePatchCommandResult => ({ + stdout: "", + stderr: `scriptc: ${message}\n`, + exitCode: 1, + }); + const selector = parseSelector(req.selector); + if ("error" in selector) return fail(`'${req.selector}' is not a selector: it ${selector.error}`); + const op = parseTypeText(req.type); + if ("error" in op) return fail(`the type '${req.type}' ${op.error}`); + if (req.range !== null && !isValidRange(req.range)) + return fail(`'${req.range}' is not a semver range`); + const manifest = nearestManifest(req.cwd); + if (manifest === null) return fail("no package.json found from the working directory"); + const base = dirname(manifest); + const copies = installedCopies(req.cwd, req.pkg); + const runtime = copies.find((c) => !c.twin) ?? copies[0]; + if (runtime === undefined) return fail(`${req.pkg} is not installed here`); + const version = runtime.version; + const lines: string[] = []; + let matched = 0; + let changed = 0; + const problems: string[] = []; + for (const copy of copies) { + const found = matchCopy(copy, selector, op, parse); + problems.push(...found.problems); + if (found.matches.length === 0) continue; + lines.push(shownCopy(copy, base, req.pkg)); + for (const m of found.matches) { + lines.push(describe(m)); + matched++; + if (!m.redundant) changed++; + } + } + if (matched === 0) { + const why = + problems.length > 0 ? problems.join("\n") : `'${req.selector}' matches no declaration`; + return fail(`${why}\nnothing was written`); + } + if (changed === 0) { + lines.push("the declarations already say this; nothing was written"); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + const text = readFileSync(manifest, "utf8"); + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch { + return fail(`${relative(req.cwd, manifest) || "package.json"} is not valid JSON`); + } + // An existing entry whose range covers the installed version takes the + // patch; otherwise a new one under the range asked for. + const existing = parseProjectConfig(manifest, text).typePatches.find( + (e: TypePatchEntry) => + e.pkg === req.pkg && + (req.range === null + ? version !== null && satisfies(version, e.range) + : e.range === req.range), + ); + const key = + existing?.key ?? `${req.pkg}@${req.range ?? (version === null ? "*" : `^${version}`)}`; + const where = `scriptc.typePatches["${key}"].declarations["${req.selector}"]`; + if (req.dryRun) { + lines.push(`would write ${relative(req.cwd, manifest) || "package.json"} ${where}`); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + const trailing = text.endsWith("\n") ? "\n" : ""; + writeFileSync( + manifest, + renderJson( + parsed, + indentOf(text), + "", + ["scriptc", "typePatches", key, "declarations", req.selector], + req.type, + ) + trailing, + ); + lines.push(`wrote ${relative(req.cwd, manifest) || "package.json"} ${where}`); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; +} + +function listCommand(cwd: string, parse: TypePatchParse): TypePatchCommandResult { + const manifest = nearestManifest(cwd); + if (manifest === null) + return { + stdout: "", + stderr: "scriptc: no package.json found from the working directory\n", + exitCode: 1, + }; + const base = dirname(manifest); + const config = parseProjectConfig(manifest, readFileSync(manifest, "utf8")); + const lines: string[] = []; + for (const d of config.diagnostics) + if (d.message.includes("typePatches")) lines.push(`warning ${d.code}: ${d.message}`); + if (config.typePatches.length === 0) { + lines.push("no type patches"); + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; + } + for (const entry of config.typePatches) { + lines.push(entry.key); + const copies = installedCopies(cwd, entry.pkg); + const covered = copies.filter((c) => c.version !== null && satisfies(c.version, entry.range)); + if (copies.length === 0) lines.push(` not installed`); + else if (covered.length === 0) + lines.push( + ` does not apply to the installed ${entry.pkg} ${copies[0]!.version ?? "(no version)"}`, + ); + for (const decl of entry.declarations) { + const selector = parseSelector(decl.selector) as Selector; + const op = parseTypeText(decl.type) as TypePatchOp; + const found: FileMatch[] = []; + for (const copy of covered) { + for (const m of matchCopy(copy, selector, op, parse).matches) + found.push({ + ...m, + file: `${relative(base, copy.root).split("\\").join("/")}/${m.file}`, + }); + } + const status = + covered.length === 0 + ? "" + : found.length === 0 + ? " (matches nothing)" + : found.every((m) => m.redundant) + ? " (already declared so)" + : ""; + lines.push(` ${decl.selector}: ${decl.type}${status}`); + for (const m of found) + if (!m.redundant) lines.push(` ${m.file}:${m.line} ${m.before} -> ${m.after}`); + } + for (const [target, local] of entry.files) + lines.push(` file ${target}: ${relative(base, local).split("\\").join("/")}`); + if (entry.types !== null) + lines.push(` types: ${relative(base, entry.types).split("\\").join("/")}`); + } + return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 }; +} + +export function runTypePatchCommand( + req: TypePatchCommandRequest, + parse: TypePatchParse, +): TypePatchCommandResult { + return req.action === "patch" ? patchCommand(req, parse) : listCommand(req.cwd, parse); +} diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index e2d14ebde..92b39e78b 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -1,3 +1,9 @@ +import { nodeFrontendServices } from "./frontend/services-node.js"; +import { + runTypePatchCommand, + type TypePatchCommandRequest, + type TypePatchCommandResult, +} from "./frontend/type-patches/command.js"; import { optimizationField, type NativeOptimization } from "./backend/optimization.js"; import { targetCpuField, targetCpuTargetRefusal } from "./backend/target-cpu.js"; import { compilationTiming, type CompilationTiming, type TimingClock } from "./timing.js"; @@ -197,6 +203,15 @@ export { } from "./backend/external-c.js"; export { warmNativeCaches } from "./backend/warm-cache.js"; export { mergeNativeProfiles as mergeProfiles } from "./backend/native-codegen.js"; + +/** `scriptc types patch|list` with the Node frontend's parser. */ +export async function typePatches( + request: TypePatchCommandRequest, +): Promise { + return runTypePatchCommand(request, (path, text, kind) => + nodeFrontendServices().parse(path, text, kind), + ); +} export { collectGarbageUnderHeapPressure } from "./heap-collection.js"; export { ANDROID_MIN_API, diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index b3de05846..60f3d28da 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -12,6 +12,7 @@ import { mergeNativeProfiles } from "../backend/native-tools.js"; import { setDeclarationRoot } from "../frontend/dts-paths.js"; import { setFrontendDisposedHook } from "../frontend/dispose-hook.js"; import { FrontendServices } from "../frontend/services.js"; +import { runTypePatchCommand } from "../frontend/type-patches/command.js"; import { createNativeTs7Api } from "../frontend/ts7/native-api.js"; import { resolveProvenanceSourcesWithParser } from "../frontend/provenance-core.js"; import { setCompilerReleaseVersion } from "../library/sidecar.js"; @@ -169,6 +170,19 @@ async function main(): Promise { } return { cacheRoot: cache.root, profiles }; }, + typePatches: async (request) => { + const toolchain = getCompiler().toolchain; + const services = new FrontendServices((options) => + createNativeTs7Api({ ...options, executable: toolchain.ts7Executable }), + ); + try { + return runTypePatchCommand(request, (path, source, kind) => + services.parse(path, source, kind), + ); + } finally { + services.close(); + } + }, mergeProfiles: async (outputPath, inputPaths, sparse) => { const toolchain = getCompiler().toolchain; mergeNativeProfiles({ diff --git a/packages/compiler/test/type-patches-command.test.ts b/packages/compiler/test/type-patches-command.test.ts new file mode 100644 index 000000000..7fc8a0d6c --- /dev/null +++ b/packages/compiler/test/type-patches-command.test.ts @@ -0,0 +1,120 @@ +/* `scriptc types patch` and `scriptc types list` + * (frontend/type-patches/command.ts) on a copy of the type patch fixture: + * matches are shown before anything is written, the patch lands in the + * nearest package.json under a range covering the installed version (or + * an existing entry that covers it), and nothing is written when the + * selector matches nothing. */ +import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, expect, test } from "vitest"; +import { nodeFrontendServices } from "../src/frontend/services-node.js"; +import { runTypePatchCommand } from "../src/frontend/type-patches/command.js"; + +const fixture = join(import.meta.dirname, "../../../tests/fixtures/type-patches"); +const parse = (path: string, text: string, kind: "ts" | "js") => + nodeFrontendServices().parse(path, text, kind); +let root: string; + +beforeEach(() => { + root = mkdtempSync(join(tmpdir(), "scriptc-types-command-")); + cpSync(fixture, root, { recursive: true }); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ name: "app", private: true, type: "module" }, null, 2) + "\n", + ); +}); +afterEach(() => rmSync(root, { recursive: true, force: true })); + +const patch = (pkg: string, selector: string, type: string, extra: object = {}) => + runTypePatchCommand( + { action: "patch", cwd: root, pkg, selector, type, range: null, dryRun: false, ...extra }, + parse, + ); + +test("patch shows the matches and writes the entry", () => { + const result = patch("termctx", "OutputContext.width", "| undefined"); + expect(result).toEqual({ + stdout: + "termctx 1.2.0 (node_modules/termctx)\n" + + " index.js:24 number -> number | undefined\n" + + `wrote package.json scriptc.typePatches["termctx@^1.2.0"].declarations["OutputContext.width"]\n`, + stderr: "", + exitCode: 0, + }); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8"))).toEqual({ + name: "app", + private: true, + type: "module", + scriptc: { + typePatches: { + "termctx@^1.2.0": { declarations: { "OutputContext.width": "| undefined" } }, + }, + }, + }); + // A second patch joins the entry whose range covers the installed copy. + expect(patch("termctx", "Terminal.columns()", "| undefined").exitCode).toBe(0); + const text = readFileSync(join(root, "package.json"), "utf8"); + expect(text.endsWith("}\n")).toBe(true); + expect(JSON.parse(text).scriptc.typePatches).toEqual({ + "termctx@^1.2.0": { + declarations: { "OutputContext.width": "| undefined", "Terminal.columns()": "| undefined" }, + }, + }); +}); + +test("--range and --dry-run", () => { + const dry = patch("ms", "index.js:parse()", "| undefined", { dryRun: true, range: "2.1.x" }); + expect(dry.stdout).toBe( + "ms 2.1.3 (node_modules/ms)\n" + + " index.js:44 Number -> Number | undefined\n" + + `would write package.json scriptc.typePatches["ms@2.1.x"].declarations["index.js:parse()"]\n`, + ); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8")).scriptc).toBeUndefined(); +}); + +test("nothing is written for a selector that matches nothing or is already so", () => { + const missing = patch("termctx", "OutputContext.height", "| undefined"); + expect(missing.exitCode).toBe(1); + expect(missing.stderr).toBe( + "scriptc: 'OutputContext.height' matches no declaration\nnothing was written\n", + ); + const redundant = patch("ms", "index.js:fmtShort(ms)", "| Number"); + expect(redundant).toMatchObject({ exitCode: 0 }); + expect(redundant.stdout).toContain("the declarations already say this; nothing was written\n"); + expect(patch("absent-package", "A.b", "| undefined").stderr).toBe( + "scriptc: absent-package is not installed here\n", + ); + expect(patch("termctx", "1bad", "| undefined").stderr).toContain("is not a selector"); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8")).scriptc).toBeUndefined(); +}); + +test("list shows each patch and what it matches", () => { + writeFileSync(join(root, "package.json"), readFileSync(join(fixture, "package.json"))); + const listed = runTypePatchCommand({ action: "list", cwd: root }, parse); + expect(listed.stdout).toBe( + [ + "termctx@^1.0.0", + " OutputContext.width: | undefined", + " node_modules/termctx/index.js:24 number -> number | undefined", + " Terminal.columns(): | undefined", + " node_modules/termctx/index.js:14 number -> number | undefined", + "ms@^2.1.0", + " index.js:parse(): | undefined", + " node_modules/ms/index.js:44 Number -> Number | undefined", + " export=(): | undefined", + " node_modules/ms/index.js:22 String|Number -> String|Number | undefined", + "", + ].join("\n"), + ); + writeFileSync(join(root, "stale.json"), ""); + writeFileSync( + join(root, "package.json"), + JSON.stringify({ + scriptc: { typePatches: { "termctx@^2.0.0": { declarations: { "A.b": "| undefined" } } } }, + }), + ); + expect(runTypePatchCommand({ action: "list", cwd: root }, parse).stdout).toBe( + "termctx@^2.0.0\n does not apply to the installed termctx 1.2.0\n A.b: | undefined\n", + ); +}); diff --git a/packages/runtime/src/scr_type_origin.c b/packages/runtime/src/scr_type_origin.c index c52a09164..cf9c6fc49 100644 --- a/packages/runtime/src/scr_type_origin.c +++ b/packages/runtime/src/scr_type_origin.c @@ -9,12 +9,13 @@ * site's cold exception path, which calls scr_type_origin_note: the * pending TypeError's message gains two lines naming the declaration and * the type patch (package.json scriptc.typePatches) that would make the - * declaration admit the value. The hot path is unchanged, and so is every + * declaration admit the value, as the `scriptc types patch` command that + * writes it. The hot path is unchanged, and so is every * other error. * * The origin is one string of fields separated by \x1f: package, version, - * suggested range, the selector of the declared slot, where it is - * declared, and the declared type. */ + * suggested range (which the command chooses itself), the selector of the + * declared slot, where it is declared, and the declared type. */ #include "scr_runtime.h" #include @@ -92,10 +93,9 @@ void scr_type_origin_note(const char *origin) { } } } - const char *pkg, *version, *range, *selector, *where, *declared; + const char *pkg, *version, *selector, *where, *declared; size_t pkg_len = scr_origin_field(origin, 0, &pkg); size_t version_len = scr_origin_field(origin, 1, &version); - size_t range_len = scr_origin_field(origin, 2, &range); size_t selector_len = scr_origin_field(origin, 3, &selector); size_t where_len = scr_origin_field(origin, 4, &where); size_t declared_len = scr_origin_field(origin, 5, &declared); @@ -116,11 +116,9 @@ void scr_type_origin_note(const char *origin) { } scr_origin_puts(&b, " ("); scr_origin_put(&b, where, where_len); - scr_origin_puts(&b, ")\n if that declaration is wrong, patch it in package.json: \"scriptc\": { \"typePatches\": { \""); + scr_origin_puts(&b, ")\n if that declaration is wrong, patch it: scriptc types patch "); scr_origin_put(&b, pkg, pkg_len); - scr_origin_puts(&b, "@"); - scr_origin_put(&b, range, range_len); - scr_origin_puts(&b, "\": { \"declarations\": { \""); + scr_origin_puts(&b, " \""); scr_origin_put(&b, selector, selector_len); /* $[3].email extends the selector as [].email: elements of an array * share one declared type. */ @@ -132,7 +130,7 @@ void scr_type_origin_note(const char *origin) { scr_origin_put(&b, path + i, 1); } } - scr_origin_puts(&b, "\": \"| "); + scr_origin_puts(&b, "\" \"| "); if (got != NULL) { const char *arm = scr_origin_arm(got, got_len); if (arm != NULL) scr_origin_puts(&b, arm); @@ -140,7 +138,7 @@ void scr_type_origin_note(const char *origin) { } else { scr_origin_puts(&b, "undefined"); } - scr_origin_puts(&b, "\" } } } }"); + scr_origin_puts(&b, "\""); ScrStr *next = scr_str_new(b.data, b.len); free(b.data); diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index cfd8b4dd6..7b8aaf7f5 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -120,7 +120,7 @@ describe("type patches", () => { expect(native.stderr).toBe( "Uncaught TypeError: expected string at $, got undefined\n" + " profilekit@3.1.0 declares Profile.email as string (index.d.ts:7)\n" + - ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "profilekit@^3.1.0": { "declarations": { "Profile.email": "| undefined" } } } }\n`, + ` if that declaration is wrong, patch it: scriptc types patch profilekit "Profile.email" "| undefined"\n`, ); } } finally { @@ -178,7 +178,7 @@ describe("type patches", () => { const result = await build(join(fixture, "unpatched/main.ts"), "unpatched", ["termctx"]); const native = await run(result.binaryPath, []); const patch = (selector: string) => - ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "termctx@^1.2.0": { "declarations": { "${selector}": "| undefined" } } } }`; + ` if that declaration is wrong, patch it: scriptc types patch termctx "${selector}" "| undefined"`; expect(native.stdout).toBe( [ "expected number at $, got undefined", From 20a4a92aa604ee1a6159cdd78d8bd38c1c9d3146 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:05:55 -0700 Subject: [PATCH 151/230] Reserve the rc word's high half for layout ids The single object model keeps an object's layout id in the high half of its rc word, beside the program-lifetime and frozen bits. Class ids already live there; this names the encoding and moves the runtime's tests that may see an object with an id onto the count: - scr_rc_layout_id reads the 30-bit layout id; scr_rc_class_id is now that id. - scr_rc_immortal tests the count, and scr_rc_frozen and Object.freeze of a record use it instead of comparing the whole word with SIZE_MAX. - Publication writes only the count of any object with a layout id, so an immortal object keeps its id, as a class instance keeps its class id. - classIdRcWord refuses a class id outside the layout id range. No object other than a class instance carries an id yet, so behavior and emitted code are unchanged. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/backend/llvm/classes.ts | 13 +++- packages/runtime/src/scr_json.c | 2 +- packages/runtime/src/scr_publish.c | 6 +- packages/runtime/src/scr_rc.h | 68 ++++++++++++++----- packages/runtime/src/scr_rc.test.c | 47 +++++++++++++ packages/runtime/src/scr_rc.test.ts | 36 ++++++++++ 6 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 packages/runtime/src/scr_rc.test.c create mode 100644 packages/runtime/src/scr_rc.test.ts diff --git a/packages/compiler/src/backend/llvm/classes.ts b/packages/compiler/src/backend/llvm/classes.ts index 699755157..f71f96a3d 100644 --- a/packages/compiler/src/backend/llvm/classes.ts +++ b/packages/compiler/src/backend/llvm/classes.ts @@ -573,13 +573,20 @@ export function classIdLoadLines( * 64-bit word. The count is the i32 at offset 0. */ export const CLASS_ID_OFFSET = 4; +/** The bound on layout ids (scr_rc.h SCR_RC_LAYOUT_ID_LIMIT): the high + * half of a 64-bit rc word keeps the layout id in its low 30 bits, beside + * the program-lifetime and frozen bits. A class id is the layout id of its + * class's canonical layout. */ +export const LAYOUT_ID_LIMIT = 0x3fffffff; + /** The rc word an LlClassMeta.idInRc instance is created with: the class * id, its preorder number plus 1, in the high 32 bits, and in the low 32 bits * a count of 1, or for a program-lifetime instance the midpoint of the 32-bit - * count (as programLifetimeRc is of the 32-bit word). The id is never 0, - * 0x80000000 or 0xffffffff, the high halves of other objects' rc words, so - * the runtime can tell an object with a class id (scr_rc.h scr_rc_class_id). */ + * count (as programLifetimeRc is of the 32-bit word). The id is a layout id + * (LAYOUT_ID_LIMIT), so it never sets the program-lifetime or frozen bit + * and the runtime reads it back (scr_rc.h scr_rc_layout_id). */ export function classIdRcWord(meta: LlClassMeta, lifetime: boolean): string { + if (meta.pre + 1 >= LAYOUT_ID_LIMIT) throw new Error(`class id ${meta.pre + 1} is not a layout id`); return (BigInt(meta.pre + 1) * 2n ** 32n + (lifetime ? 2n ** 31n : 1n)).toString(); } diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index e19ea65cc..c4c9b0ba9 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -2564,7 +2564,7 @@ static void scr_dyn_record_view_freeze(ScrDyn *view) { void scr_record_freeze(void *record) { size_t *rc = record; - if (*rc != SIZE_MAX) *rc |= SCR_RC_FROZEN; + if (!scr_rc_immortal(record)) *rc |= SCR_RC_FROZEN; } void scr_record_throw_frozen(ScrStr *key, ScrStr *owner, double operation) { diff --git a/packages/runtime/src/scr_publish.c b/packages/runtime/src/scr_publish.c index afbe4d6af..8d11d3806 100644 --- a/packages/runtime/src/scr_publish.c +++ b/packages/runtime/src/scr_publish.c @@ -128,8 +128,10 @@ bool scr_pub_mark(ScrPub *ctx, void *obj, bool cyc, int kind) { size_t *rc = obj; if (SCR_RC_PUBLISHED(obj) || ctx->refusal) return false; bool compact = (kind & SCR_PUB_COMPACT) != 0; - /* Both log the low 32 bits: a count, or a compact header word. */ - bool class_id = compact || (kind & SCR_PUB_CLASS_ID) != 0; + /* Both log the low 32 bits: a count, or a compact header word. An + * object with a layout id in its rc word (scr_rc_layout_id) keeps it, as + * a class instance keeps its class id. */ + bool class_id = compact || (kind & SCR_PUB_CLASS_ID) != 0 || scr_rc_layout_id(obj) != 0; kind &= ~(SCR_PUB_CLASS_ID | SCR_PUB_COMPACT); if (ctx->logged == ctx->log_cap) ctx->log = scr_pub_grow(ctx->log, &ctx->log_cap, sizeof *ctx->log, ctx->log_inline); diff --git a/packages/runtime/src/scr_rc.h b/packages/runtime/src/scr_rc.h index 171e420c2..34c9fdc1d 100644 --- a/packages/runtime/src/scr_rc.h +++ b/packages/runtime/src/scr_rc.h @@ -8,13 +8,27 @@ /* ── reference counts ───────────────────────────────────────────────── * Every heap object's first member is its `size_t rc` word. On 64-bit - * targets the reference count is the word's low 32 bits: an emitted - * instance of a class in a program-rooted hierarchy keeps its class id (its - * class's preorder number + 1) in the high 32 bits (llvm/classes.ts - * classIdRcWord), and no other object's count reaches 2^32. A count of SCR_RC_IMMORTAL marks - * an immortal object; for every object but those class instances the whole - * word is then SIZE_MAX. Code that reads or writes the count of an object of - * any kind goes through SCR_RC_COUNT. */ + * targets the word is + * + * bits 0-31 the reference count (SCR_RC_COUNT) + * bit 62 F: a frozen record (SCR_RC_FROZEN) + * bit 63 P: a program-lifetime record (llvm/classes.ts + * programLifetimeRc) + * bits 32-61 the layout id (scr_rc_layout_id) + * + * and no object's count reaches 2^32. The layout id names the object's + * layout in the single object model (scr_layout.h); 0 means the object + * never needed one. An emitted instance of a class in a program-rooted + * hierarchy keeps its class id (its class's preorder number + 1) there + * (llvm/classes.ts classIdRcWord): a class id is the layout id of its + * class's canonical layout. A count of SCR_RC_IMMORTAL marks an immortal + * object. Publication and the runtime's own immortal objects write the + * whole word as SIZE_MAX when the object has no layout id, and write only + * the count when it has one, so an immortal object keeps its layout id + * (scr_rc_immortal tests the count; a high half of all ones reads as no + * layout id). Code that reads or writes the count of an object of any + * kind goes through SCR_RC_COUNT; code that may see an object with a layout + * id tests immortality with scr_rc_immortal, never against SIZE_MAX. */ #if SIZE_MAX > UINT32_MAX typedef uint32_t ScrRcCount; #ifdef __BYTE_ORDER__ @@ -58,25 +72,47 @@ typedef size_t ScrRcCount; #define SCR_RC_FROZEN ((size_t)1 << 30) #define SCR_RC_REFS(obj) (SCR_RC_COUNT(obj) == SCR_RC_IMMORTAL ? SCR_RC_IMMORTAL : SCR_RC_COUNT(obj) & ~SCR_RC_FROZEN) #endif -static inline bool scr_rc_frozen(const void *obj) { - size_t word = *(const size_t *)obj; - return word != SIZE_MAX && (word & SCR_RC_FROZEN) != 0; +/* Whether `obj`, an object whose rc word is a count (not a compact class + * instance), is immortal: its count is SCR_RC_IMMORTAL, whatever its high + * half holds. */ +static inline bool scr_rc_immortal(const void *obj) { + return SCR_RC_COUNT(obj) == SCR_RC_IMMORTAL; } -/* The class id in the high half of `obj`'s rc word, or 0 for an object - * without one: the high half of any other object's word is 0, 0xffffffff - * (immortal), 0x80000000 (program-lifetime) or 0x40000000 (a frozen record), - * and a class id is never one of these. */ -static inline uint32_t scr_rc_class_id(const void *obj) { +static inline bool scr_rc_frozen(const void *obj) { + return !scr_rc_immortal(obj) && (*(const size_t *)obj & SCR_RC_FROZEN) != 0; +} + +/* The high half of the rc word on 64-bit targets: the program-lifetime + * bit, the frozen bit and the layout id (see the layout above). 32-bit + * targets have no high half; their objects keep a layout id elsewhere when + * they need one (scr_layout.h). */ +#define SCR_RC_HIGH_LIFETIME UINT32_C(0x80000000) +#define SCR_RC_HIGH_FROZEN UINT32_C(0x40000000) +#define SCR_RC_LAYOUT_ID_MASK UINT32_C(0x3fffffff) +/* Layout ids are below this bound: 2^30 - 1 is the id bits of an all-ones + * high half, which reads as no id. */ +#define SCR_RC_LAYOUT_ID_LIMIT SCR_RC_LAYOUT_ID_MASK + +/* The layout id in the high half of `obj`'s rc word, or 0 for an object + * without one. The whole-word immortal encoding (SIZE_MAX) has no id. */ +static inline uint32_t scr_rc_layout_id(const void *obj) { #if SIZE_MAX > UINT32_MAX uint32_t high = (uint32_t)(*(const size_t *)obj >> 32); - return high == UINT32_MAX || high == UINT32_C(0x80000000) || high == UINT32_C(0x40000000) ? 0 : high; + return high == UINT32_MAX ? 0 : high & SCR_RC_LAYOUT_ID_MASK; #else (void)obj; return 0; #endif } +/* The class id in the high half of `obj`'s rc word, or 0 for an object + * without one. A class id is the layout id of the class's canonical + * layout, and no object of any other kind has a layout id yet. */ +static inline uint32_t scr_rc_class_id(const void *obj) { + return scr_rc_layout_id(obj); +} + /* ── cycle collection (scr_cycle.c) ─────────────────────────────────── * Reference counting alone cannot free cycles, so every object that can * participate in one — capture boxes, heap closures, unions, promises, and diff --git a/packages/runtime/src/scr_rc.test.c b/packages/runtime/src/scr_rc.test.c new file mode 100644 index 000000000..159235ce9 --- /dev/null +++ b/packages/runtime/src/scr_rc.test.c @@ -0,0 +1,47 @@ +#undef NDEBUG +#include "scr_rc.h" +#include +#include + +/* The rc word's encodings (scr_rc.h): the count in the low half, and the + * program-lifetime bit, the frozen bit and the layout id in the high half. + * Immortality is a count of all ones; a layout id survives it. */ +static size_t word(uint32_t high, uint32_t count) { + return ((size_t)high << 32) | count; +} + +int main(void) { +#if SIZE_MAX > UINT32_MAX + /* An ordinary object: no layout id, not frozen, not immortal. */ + size_t plain = word(0, 3); + assert(scr_rc_layout_id(&plain) == 0 && !scr_rc_frozen(&plain) && !scr_rc_immortal(&plain)); + /* The whole-word immortal encoding has no layout id and is not frozen. */ + size_t immortal = SIZE_MAX; + assert(scr_rc_layout_id(&immortal) == 0 && scr_rc_immortal(&immortal) && !scr_rc_frozen(&immortal)); + /* A program-lifetime record (llvm/classes.ts programLifetimeRc) and a + * frozen record have no layout id. */ + size_t lifetime = word(SCR_RC_HIGH_LIFETIME, 0); + assert(scr_rc_layout_id(&lifetime) == 0 && !scr_rc_immortal(&lifetime)); + size_t frozen = word(SCR_RC_HIGH_FROZEN, 2); + assert(scr_rc_layout_id(&frozen) == 0 && scr_rc_frozen(&frozen)); + /* A class instance keeps its class id, which is its layout id, beside + * its count, also once immortal, frozen or program-lifetime. */ + size_t instance = word(7, 1); + assert(scr_rc_layout_id(&instance) == 7 && scr_rc_class_id(&instance) == 7); + SCR_RC_COUNT(&instance) = SCR_RC_IMMORTAL; + assert(scr_rc_immortal(&instance) && scr_rc_layout_id(&instance) == 7 && !scr_rc_frozen(&instance)); + size_t frozen_id = word(SCR_RC_HIGH_FROZEN | 9, 1); + assert(scr_rc_layout_id(&frozen_id) == 9 && scr_rc_frozen(&frozen_id)); + size_t lifetime_id = word(SCR_RC_HIGH_LIFETIME | 11, UINT32_C(0x80000000)); + assert(scr_rc_layout_id(&lifetime_id) == 11 && !scr_rc_immortal(&lifetime_id)); + /* The largest layout id. */ + size_t top = word(SCR_RC_LAYOUT_ID_LIMIT - 1, 1); + assert(scr_rc_layout_id(&top) == SCR_RC_LAYOUT_ID_LIMIT - 1); +#else + size_t plain = 3, immortal = SIZE_MAX; + assert(scr_rc_layout_id(&plain) == 0 && !scr_rc_immortal(&plain)); + assert(scr_rc_immortal(&immortal) && !scr_rc_frozen(&immortal)); +#endif + puts("rc word encodings"); + return 0; +} diff --git a/packages/runtime/src/scr_rc.test.ts b/packages/runtime/src/scr_rc.test.ts new file mode 100644 index 000000000..181d0beba --- /dev/null +++ b/packages/runtime/src/scr_rc.test.ts @@ -0,0 +1,36 @@ +import { execFile } from "node:child_process"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; + +const exec = promisify(execFile); + +/* The rc word helpers are inline in scr_rc.h, so the test links alone. */ +test.skipIf(process.platform === "win32")( + "the rc word keeps the count, the lifetime and frozen bits and the layout id apart", + async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-rc-")); + try { + const bin = join(dir, "rc"); + await exec("clang", [ + "-std=c11", + "-O1", + "-g", + "-fsanitize=address,undefined", + join(import.meta.dirname, "scr_rc.test.c"), + "-o", + bin, + ]); + const result = await exec(bin, [], { + timeout: 60000, + env: { ...process.env, UBSAN_OPTIONS: "halt_on_error=1" }, + }); + expect(result.stdout).toBe("rc word encodings\n"); + expect(result.stderr).toBe(""); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }, +); From 30cf205e90108ea02cd109b71ad0cc6c439655ef Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:10:32 -0500 Subject: [PATCH 152/230] Run untyped pipeline function stages and later iterable stages A pipeline stage that arrives as an untyped value now follows Node's stage ladder for more shapes. As the first stage, a function is a source function: the pipeline calls it with { signal } while it is set up and pumps its result, which must be iterable (ERR_INVALID_RETURN_VALUE otherwise), and a { readable, writable } pair pumps its readable side. After the first stage, the pipeline call is lowered once per combination of its first three untyped stages being functions or not, and a typeof test picks the branch at run time. A function stage is called with the previous stage and { signal }; before the last stage its result must be async iterable, and as the last stage a promise resolves the pipeline's value and an async iterable feeds the PassThrough pipeline() returns (SCR_PIPE_DEST_DYN). Any other value that is not a stream becomes Duplex.from of it, which the next stage reads from without the previous stage being piped into it, and Duplex.from's ERR_INVALID_ARG_TYPE for the values it rejects. An untyped stage followed by an untyped function stage hands its value to that stage through a cell created per pipeline call, so the function receives the previous stage's own iterator, as Node's makeAsyncIterable answers it, instead of an async generator over it. Untyped web streams, pairs after the first stage, and function stages that return a stream throw the not-supported error. RUNTIME_ABI_VERSION goes from 45 to 46 for the new stream exports. --- .../builtin-apis/streams-and-readline.mdx | 8 +- packages/cli/test/native-link-info.test.ts | 4 +- .../compiler/src/backend/llvm/lib-dispatch.ts | 12 +- .../compiler/src/backend/llvm/lib-shared.ts | 3 + .../compiler/src/backend/llvm/lib-stream.ts | 2 +- packages/compiler/src/backend/runtime-abi.ts | 4 +- .../src/backend/runtime-pack-targets.test.ts | 2 +- .../compiler/src/backend/runtime-pack.test.ts | 2 +- .../src/frontend/lowering/lower-stream.ts | 667 +++++++++++++++++- packages/compiler/src/ir/builtin-effects.ts | 3 + .../compiler/src/ir/builtin-signatures.ts | 30 +- packages/compiler/src/ir/runtime-features.ts | 4 + packages/compiler/src/ir/validate.ts | 6 +- .../test/native-codegen-integration.test.ts | 4 +- ...pipeline-untyped-source-functions.mjs.json | 6 + ...-pipeline-untyped-function-stages.mjs.json | 6 + ...-pipeline-untyped-later-iterables.mjs.json | 6 + .../7511-pipeline-untyped-stages.cjs.json | 6 + .../corpus/7512-pipeline-any-stages.ts.json | 6 + ...513-pipeline-untyped-stage-errors.mjs.json | 6 + .../runtime-darwin-arm64/scripts/build.mjs | 2 +- .../runtime-pack-common/scripts/build.mjs | 2 +- packages/runtime/src/scr_console.c | 2 +- packages/runtime/src/scr_core.h | 2 +- packages/runtime/src/scr_dyn.h | 3 + packages/runtime/src/scr_dyn_builtin_stream.c | 8 + packages/runtime/src/scr_stream.c | 272 ++++++- packages/runtime/src/scr_stream.h | 12 + ...7508-pipeline-untyped-source-functions.mjs | 85 +++ .../7509-pipeline-untyped-function-stages.mjs | 112 +++ .../7510-pipeline-untyped-later-iterables.mjs | 64 ++ tests/corpus/7511-pipeline-untyped-stages.cjs | 50 ++ tests/corpus/7512-pipeline-any-stages.ts | 45 ++ .../7513-pipeline-untyped-stage-errors.mjs | 86 +++ ...pipeline-untyped-source-functions.mjs.json | 1 + ...-pipeline-untyped-function-stages.mjs.json | 1 + ...-pipeline-untyped-later-iterables.mjs.json | 1 + .../7511-pipeline-untyped-stages.cjs.json | 1 + .../7512-pipeline-any-stages.ts.json | 1 + ...513-pipeline-untyped-stage-errors.mjs.json | 1 + 40 files changed, 1466 insertions(+), 72 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7508-pipeline-untyped-source-functions.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7509-pipeline-untyped-function-stages.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7510-pipeline-untyped-later-iterables.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7511-pipeline-untyped-stages.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7512-pipeline-any-stages.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7513-pipeline-untyped-stage-errors.mjs.json create mode 100644 tests/corpus/7508-pipeline-untyped-source-functions.mjs create mode 100644 tests/corpus/7509-pipeline-untyped-function-stages.mjs create mode 100644 tests/corpus/7510-pipeline-untyped-later-iterables.mjs create mode 100644 tests/corpus/7511-pipeline-untyped-stages.cjs create mode 100644 tests/corpus/7512-pipeline-any-stages.ts create mode 100644 tests/corpus/7513-pipeline-untyped-stage-errors.mjs create mode 100644 tests/harness/corpus-costs/7508-pipeline-untyped-source-functions.mjs.json create mode 100644 tests/harness/corpus-costs/7509-pipeline-untyped-function-stages.mjs.json create mode 100644 tests/harness/corpus-costs/7510-pipeline-untyped-later-iterables.mjs.json create mode 100644 tests/harness/corpus-costs/7511-pipeline-untyped-stages.cjs.json create mode 100644 tests/harness/corpus-costs/7512-pipeline-any-stages.ts.json create mode 100644 tests/harness/corpus-costs/7513-pipeline-untyped-stage-errors.mjs.json diff --git a/docs/content/limitations/builtin-apis/streams-and-readline.mdx b/docs/content/limitations/builtin-apis/streams-and-readline.mdx index 561840268..25427a307 100644 --- a/docs/content/limitations/builtin-apis/streams-and-readline.mdx +++ b/docs/content/limitations/builtin-apis/streams-and-readline.mdx @@ -4,7 +4,13 @@ When an untyped value reaches `NodeJS.ReadableStream` or `NodeJS.WritableStream`, those two interfaces hold any value, so JavaScript stream objects (constructor-function streams, plain objects with `on` and `pipe`) pass through TypeScript functions typed with them and come back to JavaScript as the same objects. Member calls on such a value use the native stream when it is one and call the object's own members otherwise; `readable.pipe()` into a JavaScript destination follows Node's pipe protocol. The first `pipeline` stage may arrive as an untyped value that is a native stream, a `Cipheriv` or `Decipheriv` value, or an iterable: a string, an array, a generator object, or any value with a `Symbol.asyncIterator` or `Symbol.iterator` method, which the pipeline pumps into the next stage as Node does. - Later `pipeline` stages and `for await` sources that arrive as untyped values must be native streams, or `Cipheriv` and `Decipheriv` values, at run time; other values throw a not-supported error. + It may also be a source function, which the pipeline calls with `{ signal }` and whose iterable result it pumps, or a `{ readable, writable }` pair, whose readable side it pumps. + Later `pipeline` stages that arrive as untyped values may be native streams, `Cipheriv` and `Decipheriv` values, functions, and iterables. + A function is called with the previous stage and `{ signal }` as a function stage: before the last stage it must return an async iterable, and as the last stage it may return a promise, whose value resolves the pipeline, or an async iterable, which feeds the PassThrough that `pipeline()` returns. + An untyped source function, function stage, or destination that returns a stream is unsupported. + An iterable becomes `Duplex.from` of it, which the next stage reads from while the stages before it are not read, as in Node. + A pipeline branches at run time on whether each of its first three untyped stages after the first is a function; a function in a later untyped stage, an untyped web stream, and a `{ readable, writable }` pair after the first stage throw a not-supported error. + `for await` sources that arrive as untyped values must be native streams, or `Cipheriv` and `Decipheriv` values, at run time; other values throw a not-supported error. Native streams expose no `emit` to JavaScript, so a JavaScript stream whose own `pipe()` emits on its destination (the legacy `Stream` pipe) cannot pipe into one. `Readable.from` accepts strings, Buffers, arrays, Sets, Maps, Map and Set iterators, generators, async generators, and class iterables and async iterables, and in JavaScript any iterable or async iterable. In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported. diff --git a/packages/cli/test/native-link-info.test.ts b/packages/cli/test/native-link-info.test.ts index f63cc790c..24db2c4c2 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: 45, marker: "scr_runtime_abi_v45" }, + runtime_abi: { version: 46, marker: "scr_runtime_abi_v46" }, 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_v45"); + expect(error?.stderr).toContain("scr_runtime_abi_v46"); }); }); diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 8cefda8f0..30eef4b99 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -671,11 +671,13 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll // which also takes those streams. e.fn === "stream.unboxDyn" && host.usesDynBuiltinStream ? "scr_dyn_builtin_stream_unbox" - : // for await over a checked-dynamic value iterates a native - // stream behind it (scr_dyn_for_await_stream.c) - e.fn === "dyn.asyncIteratorOpen" && host.dynForAwaitStreams - ? "scr_stream_dyn_async_iterator_open" - : LIB_FN_SYMS[e.fn]); + : e.fn === "stream.unboxDynStage" && host.usesDynBuiltinStream + ? "scr_dyn_builtin_stream_unbox_stage" + : // for await over a checked-dynamic value iterates a native + // stream behind it (scr_dyn_for_await_stream.c) + e.fn === "dyn.asyncIteratorOpen" && host.dynForAwaitStreams + ? "scr_stream_dyn_async_iterator_open" + : LIB_FN_SYMS[e.fn]); if (sym === undefined) throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc); // The runtime's Date functions take and answer time values: a Date // operand passes its time value, read at once from the borrowed object, diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 4b3f34b80..016815819 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -818,6 +818,9 @@ export const LIB_FN_SYMS: Record = { "stream.stdioWritable": "scr_stream_stdio_writable", "stream.unboxDyn": "scr_stream_unbox_dyn", "stream.dynIterableSource": "scr_stream_dyn_iterable_source", + "stream.dynSourceValue": "scr_stream_dyn_source_value", + "stream.unboxDynStage": "scr_stream_unbox_dyn_stage", + "stream.dynTransformResult": "scr_stream_dyn_transform_result", "stream.handleStream": "scr_stream_handle", "stream.pipeDyn": "scr_stream_pipe_dyn", "stream.unpipeDyn": "scr_stream_unpipe_dyn", diff --git a/packages/compiler/src/backend/llvm/lib-stream.ts b/packages/compiler/src/backend/llvm/lib-stream.ts index fdbf13d4f..53f455964 100644 --- a/packages/compiler/src/backend/llvm/lib-stream.ts +++ b/packages/compiler/src/backend/llvm/lib-stream.ts @@ -743,7 +743,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV const helpers = packed(5 + n + k, helperCount); let at = 5 + n + k + helperCount; const take = (present: boolean): string => (present ? args[at++]!.name : "null"); - const box = take(((flags >> 1) & 3) === 2); + const box = take(((flags >> 1) & 3) >= 2); const signal = take((flags & 32) !== 0); const abort = take((flags & 32) !== 0); const head = diff --git a/packages/compiler/src/backend/runtime-abi.ts b/packages/compiler/src/backend/runtime-abi.ts index fe13879a2..892e9281e 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 = 45 as const; -export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v45" as const; +export const RUNTIME_ABI_VERSION = 46 as const; +export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v46" 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-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index 492b71217..9c5b61f58 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: 45, marker: "scr_runtime_abi_v45" }, + runtime_abi: { version: 46, marker: "scr_runtime_abi_v46" }, 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 fdb0ba29e..812fd9531 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -129,7 +129,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { object_format: target.objectFormat, minimum_os: target.minimumOs, }, - runtime_abi: { version: 45, marker: "scr_runtime_abi_v45" }, + runtime_abi: { version: 46, marker: "scr_runtime_abi_v46" }, compiler: { command: "clang", identity: "fixture clang", diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index c8699d856..2ae952aaa 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -2837,20 +2837,24 @@ export function streamClassAliasDecl( const PIPELINE_STAGE_EXPECTED = "of type function or an instance of Blob, ReadableStream, WritableStream, Stream, Iterable, AsyncIterable, or Promise or { readable, writable } pair"; -/** A checked-dynamic pipeline stage (an untyped source or destination): - * a boxed native stream unwraps at run time; other values (functions, - * pairs) throw the not-supported Error there. */ +/** A checked-dynamic pipeline stage taken as a stream: a boxed native + * stream unwraps at run time, and a value that is not stream-like or a + * function (an iterable after the first stage) becomes Duplex.from of it, + * as pipelineImpl's last branch does. Stream-like objects, web streams and + * { readable, writable } pairs throw the not-supported Error there, and so + * do functions, which reach this lowering only past + * MAX_DYN_FUNCTION_STAGES. */ function unboxDynStage(lowerer: Lowerer, v: IrExpr, node: ts.Expression): IrExpr { noteUntypedStreamFlow(lowerer, null); return { kind: "libCall", - fn: "stream.unboxDyn", + fn: "stream.unboxDynStage", args: [ v, strLit( lowerer.dynNodeStreams - ? "pipeline stages other than native streams (JavaScript stream objects, iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet" - : "pipeline stages other than native streams (iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet", + ? "pipeline stages other than native streams, iterables and functions (JavaScript stream objects, web streams, { readable, writable } pairs after the first stage, and functions after the first three untyped stages) are not supported yet" + : "pipeline stages other than native streams, iterables and functions (web streams, { readable, writable } pairs after the first stage, and functions after the first three untyped stages) are not supported yet", locOf(node), ), ], @@ -2930,6 +2934,102 @@ const BOXED_SOURCE_KINDS: ReadonlySet = new Set([ "string", ]); +/** The most checked-dynamic stages after the first that a pipeline + * branches on as functions; each one doubles the lowered pipeline calls. */ +const MAX_DYN_FUNCTION_STAGES = 3; + +/** A pipeline's arguments for one of the run-time branches + * lowerDynStagePipeline lowers. */ +interface DynStagePlan { + /** the first stage when it is checked-dynamic */ + source: DynPipelineSource | null; + /** argument values evaluated before the branches, in argument order */ + bound: ReadonlyMap; + /** a typed iterable first stage's source, lowered before the branches */ + preSource?: { closure: IrExpr; out: SyncGenType } | undefined; + /** the checked-dynamic stages this branch takes as functions */ + fns: ReadonlySet; +} + +/** pipeline() whose stages after the first include untyped values: + * pipelineImpl calls a stage that is a function with the previous stage + * and { signal }, and pipes into or takes any other value, so the call is + * lowered once per combination of those stages being functions or not, + * up to MAX_DYN_FUNCTION_STAGES of them, and a typeof test on each + * picks the branch at run time. The arguments other than function stages + * are evaluated first, in argument order. */ +function lowerDynStagePipeline( + lowerer: Lowerer, + stageArgs: readonly ts.Expression[], + src: DynPipelineSource | null, + loc: SrcLoc, + build: (plan: DynStagePlan) => IrExpr, +): IrExpr { + const candidates: number[] = []; + for (let j = 1; j < stageArgs.length; j++) { + const arg = stageArgs[j]!; + if (pipelineStageFunction(lowerer, arg) !== null || isValueStream(lowerer, arg)) continue; + if ((lowerer.typeOf(arg).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) { + candidates.push(j); + } + } + if (candidates.length === 0) return build({ source: src, bound: new Map(), fns: new Set() }); + const stmts: IrStmt[] = []; + const bind = (init: IrExpr): IrExpr => { + const local = lowerer.declareHiddenLocal("%pipelineArg", init.type); + stmts.push({ kind: "varDecl", localId: local.id, init, loc: init.loc }); + return varRef(local.id, init.type, init.loc); + }; + const source = src !== null && !src.iterable ? { expr: bind(src.expr), iterable: false } : src; + const bound = new Map(); + let preSource: DynStagePlan["preSource"]; + for (let j = src !== null ? 1 : 0; j < stageArgs.length; j++) { + const arg = stageArgs[j]!; + if (pipelineStageFunction(lowerer, arg) !== null) continue; + if (j === 0 && iterableSourceType(lowerer, arg) !== null) { + const lowered = lowerIterableSource(lowerer, arg, null, loc); + if (lowered === null) lowerer.noLowering("pipeline over this source", arg); + preSource = { closure: bind(lowered!.closure), out: lowered!.out }; + continue; + } + let value = lowerer.lowerExpr(arg); + // an untyped array, string, Set, Map or generator object that keeps + // its static type is taken through its checked-dynamic value + if ( + candidates.includes(j) && + BOXED_SOURCE_KINDS.has(value.type.kind) && + lowerer.dynConvertible(value.type) + ) { + value = { kind: "dynFrom", value, type: DYN, loc: value.loc }; + } + bound.set(j, bind(value)); + } + const dynStages = candidates + .filter((j) => bound.get(j)!.type.kind === "dyn") + .slice(0, MAX_DYN_FUNCTION_STAGES); + const branch = (i: number, fns: ReadonlySet): IrExpr => { + if (i === dynStages.length) return build({ source, bound, preSource, fns }); + const j = dynStages[i]!; + const asStream = branch(i + 1, fns); + const asFunction = branch(i + 1, new Set([...fns, j])); + // a function destination's promise resolves to its value + const type = + asFunction.type.kind === "promise" && asFunction.type.inner.kind === "dyn" + ? asFunction.type + : asStream.type; + return { + kind: "ternary", + cond: { kind: "dynTest", test: "function", value: bound.get(j)!, type: BOOL, loc }, + then: lowerer.coerceToExpected(asFunction, type), + else_: lowerer.coerceToExpected(asStream, type), + type, + loc, + }; + }; + const result = branch(0, new Set()); + return { kind: "seqExpr", stmts, result, type: result.type, loc }; +} + /** lowerPipelineStages' source arguments for a checked-dynamic first stage. */ function stagesSource( src: DynPipelineSource | null, @@ -2938,6 +3038,22 @@ function stagesSource( return src.iterable ? [undefined, src.expr] : [new Map([[0, src.expr]]), undefined]; } +/** A pipeline stage taken as a stream under a DynStagePlan: the + * checked-dynamic first stage's stream, a value bound before the + * branches, or the argument lowered here. */ +function planStreamArg( + lowerer: Lowerer, + plan: DynStagePlan, + node: ts.Expression, + i: number, +): IrExpr { + if (i === 0 && plan.source !== null) return plan.source.expr; + const bound = plan.bound.get(i); + return bound !== undefined + ? streamArgOf(lowerer, node, bound, "pipeline") + : lowerStreamArg(lowerer, node, "pipeline"); +} + /** A stream-rooted argument, lowered — or the pointed fence (pipeline's * iterable/generator/function stages have no lowering yet). `what` picks * the validation ladder: finished() rejects EVERY non-stream through @@ -3304,7 +3420,9 @@ export function lowerStreamModuleCall( : null; const stageArgs = opts ? args.slice(0, -1) : args; const build = (src: DynPipelineSource | null): IrExpr => - lowerPromisePipeline(lowerer, stageArgs, opts, src, loc); + lowerDynStagePipeline(lowerer, stageArgs, src, loc, (plan) => + lowerPromisePipeline(lowerer, stageArgs, opts, plan, loc), + ); return lowerDynSourcePipeline(lowerer, stageArgs[0]!, loc, build) ?? build(null); } return null; @@ -3312,17 +3430,23 @@ export function lowerStreamModuleCall( return lowerStreamModuleCallbackCall(lowerer, call, bi, loc); } -/** stream/promises pipeline(...stages[, options]), with the first stage - * pre-lowered when it is checked-dynamic (lowerDynSourcePipeline). */ +/** stream/promises pipeline(...stages[, options]), for one branch of + * lowerDynStagePipeline's plan. */ function lowerPromisePipeline( lowerer: Lowerer, stageArgs: readonly ts.Expression[], opts: ReturnType | null, - src: DynPipelineSource | null, + plan: DynStagePlan, loc: SrcLoc, ): IrExpr { - if (src?.iterable === true || hasPipelineFunctionStage(lowerer, stageArgs)) { - const staged = lowerPipelineStages(lowerer, stageArgs, loc, ...stagesSource(src)); + const src = plan.source; + if ( + src?.iterable === true || + plan.fns.size > 0 || + plan.preSource !== undefined || + hasPipelineFunctionStage(lowerer, stageArgs) + ) { + const staged = lowerPipelineStages(lowerer, stageArgs, loc, ...stagesSource(src), plan); const type = staged.destPromise ?? { kind: "promise", inner: VOID }; // A promise destination's value settles the result (same type). if (!opts) { @@ -3346,9 +3470,7 @@ function lowerPromisePipeline( } let streams: IrExpr[]; try { - streams = stageArgs.map((a, i) => - i === 0 && src !== null ? src.expr : lowerStreamArg(lowerer, a, "pipeline"), - ); + streams = stageArgs.map((a, i) => planStreamArg(lowerer, plan, a, i)); } catch (e) { if (e instanceof StreamArgTypeThrow) return e.expr; throw e; @@ -3530,7 +3652,9 @@ function lowerStreamModuleCallbackCall( ); } const build = (src: DynPipelineSource | null): IrExpr => - lowerCallbackPipeline(lowerer, call, args, src, loc); + lowerDynStagePipeline(lowerer, args.slice(0, -1), src, loc, (plan) => + lowerCallbackPipeline(lowerer, call, args, plan, loc), + ); return lowerDynSourcePipeline(lowerer, args[0]!, loc, build) ?? build(null); } @@ -3544,23 +3668,32 @@ function lowerStreamModuleCallbackCall( return null; } -/** pipeline(...stages, callback), with the first stage pre-lowered when it - * is checked-dynamic (lowerDynSourcePipeline). */ +/** pipeline(...stages, callback), for one branch of lowerDynStagePipeline's + * plan. */ function lowerCallbackPipeline( lowerer: Lowerer, call: ts.CallExpression, args: readonly ts.Expression[], - src: DynPipelineSource | null, + plan: DynStagePlan, loc: SrcLoc, ): IrExpr { - if (src?.iterable === true || hasPipelineFunctionStage(lowerer, args.slice(0, -1))) { - const staged = lowerPipelineStages(lowerer, args.slice(0, -1), loc, ...stagesSource(src)); - const cbNode = args[args.length - 1]!; - const cbParams = - ts.isArrowFunction(cbNode) || ts.isFunctionExpression(cbNode) - ? cbNode.parameters.nodes.length - : 0; - if (staged.dest === 2 || (staged.dest === 1 && cbParams > 1)) { + const src = plan.source; + const cbNode = args[args.length - 1]!; + const cbParams = + ts.isArrowFunction(cbNode) || ts.isFunctionExpression(cbNode) + ? cbNode.parameters.nodes.length + : 0; + // A branch over untyped stages whose destination is a stream: the + // callback still receives (err, value), value undefined, as in Node. + const dynCallback = plan.bound.size > 0 && cbParams > 1; + if ( + src?.iterable === true || + plan.fns.size > 0 || + plan.preSource !== undefined || + hasPipelineFunctionStage(lowerer, args.slice(0, -1)) + ) { + const staged = lowerPipelineStages(lowerer, args.slice(0, -1), loc, ...stagesSource(src), plan); + if (staged.dest >= 2 || (staged.dest === 1 && cbParams > 1) || dynCallback) { // callback(err, value): the destination's value (undefined for // an async generator) reaches the callback through the // checked-dynamic path. @@ -3595,15 +3728,15 @@ function lowerCallbackPipeline( } let streams: IrExpr[]; try { - streams = args - .slice(0, -1) - .map((a, i) => (i === 0 && src !== null ? src.expr : lowerStreamArg(lowerer, a, "pipeline"))); + streams = args.slice(0, -1).map((a, i) => planStreamArg(lowerer, plan, a, i)); } catch (e) { if (e instanceof StreamArgTypeThrow) return e.expr; throw e; } const last = streams[streams.length - 1]!; - const { cb, dyn } = lowerEosCallback(lowerer, "pipeline", args[args.length - 1]!, last.type); + const { cb, dyn } = dynCallback + ? { cb: lowerer.lowerExprExpecting(cbNode, DYN), dyn: true } + : lowerEosCallback(lowerer, "pipeline", cbNode, last.type); return { kind: "libCall", fn: dyn ? "stream.pipelineDyn" : "stream.pipeline", @@ -4213,6 +4346,397 @@ function lowerDynIterableSource( }; } +/** The async generator a checked-dynamic stage's iterable is pumped + * through. */ +function dynStageGenType(): SyncGenType { + return { kind: "generator", async: true, yieldT: DYN, retT: VOID, nextT: UNDEFINED_T }; +} + +/** A checked-dynamic pipeline stage after the first that is a function at + * run time, as the stage closure the runtime calls with the previous + * stage and { signal } (pipelineImpl's `typeof stream === 'function'` + * branch): it calls the function with the previous stream or generator + * as an untyped value. Before the last stage the result must be async + * iterable (ERR_INVALID_RETURN_VALUE otherwise) and is pumped on through + * lowerDynIterableSource's generator; the last stage answers the result, + * which the runtime classifies (SCR_PIPE_DEST_DYN). A previous stage + * whose type has no untyped form throws the not-supported Error. */ +function lowerDynFunctionStage( + lowerer: Lowerer, + fnValue: IrExpr, + srcT: IrType, + j: number, + isLast: boolean, + loc: SrcLoc, + handoff?: DynStageHandoff, +): { closure: IrExpr; out: IrType } { + const out: IrType = isLast ? DYN : dynStageGenType(); + const stageT = funcOf([srcT, DYN], out); + const take = handoff?.take === true; + const give = handoff?.give === true && !isLast; + const key = `dynFunctionStage:${isLast ? "dest" : "transform"}:${typeKey(srcT)}:${take ? 1 : 0}${give ? 1 : 0}`; + let maker = lowerer.valueHelpers.get(key); + if (!maker) { + maker = `%stream.dynFunctionStage.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, maker); + const stageName = `${maker}.stage`; + const frame = new LiftedFrame(loc); + const fnId = frame.local("fn", DYN, false); + const labelId = frame.local("label", STRING, false); + const cellId = frame.local("cell", DYN, false); + const srcId = frame.param("src", srcT); + const optsId = frame.param("opts", DYN); + const inputId = frame.local("input", DYN, false); + const resultId = frame.local("result", DYN, false); + const nextId = frame.local("next", DYN, false); + const cell = (): IrExpr => varRef(cellId, DYN, loc); + const body: IrStmt[] = []; + if (take) { + // the previous untyped stage's value, handed over as it is + // (makeAsyncIterable answers an iterable itself) + body.push( + { kind: "varDecl", localId: inputId, init: handoffTake(cell(), loc), loc }, + handoffPut(cell(), dynUndefinedExpr(loc), loc), + ); + } + if (!take && !lowerer.dynConvertible(srcT)) { + body.push({ + kind: "return", + value: nodeThrowExpr( + 0, + "SC2020", + `pipeline function stages after a stage of type '${lowerer.fmt(srcT)}' are not supported yet`, + out, + loc, + ), + loc, + }); + } else { + body.push({ + kind: "varDecl", + localId: resultId, + init: { + kind: "dynCall", + callee: varRef(fnId, DYN, loc), + receiver: dynUndefinedExpr(loc), + calleeName: "transform", + args: [ + take + ? varRef(inputId, DYN, loc) + : { kind: "dynFrom", value: varRef(srcId, srcT, loc), type: DYN, loc }, + varRef(optsId, DYN, loc), + ], + type: DYN, + loc, + }, + loc, + }); + if (isLast) { + body.push({ kind: "return", value: varRef(resultId, DYN, loc), loc }); + } else { + body.push({ + kind: "varDecl", + localId: nextId, + init: { + kind: "libCall", + fn: "stream.dynTransformResult", + args: [varRef(resultId, DYN, loc), varRef(labelId, STRING, loc)], + type: DYN, + loc, + }, + loc, + }); + // the next untyped function stage takes the result itself; the + // generator returned for the runtime is then never read + if (give) body.push(handoffPut(cell(), varRef(nextId, DYN, loc), loc)); + const iterable = lowerDynIterableSource(lowerer, varRef(nextId, DYN, loc), loc); + if (!iterable) + throw new InternalCompilerError("dyn iterable generator without a result record"); + body.push({ + kind: "return", + value: { kind: "callValue", callee: iterable.closure, args: [], type: out, loc }, + loc, + }); + } + } + const captured = take || give ? [fnId, labelId, cellId] : [fnId, labelId]; + for (const id of captured) frame.locals.find((l) => l.id === id)!.boxed = true; + lowerer.liftedFns.push({ + name: stageName, + params: frame.params, + returnType: out, + captures: [ + { localId: fnId, name: "fn", type: DYN }, + { localId: labelId, name: "label", type: STRING }, + ...(take || give ? [{ localId: cellId, name: "cell", type: DYN }] : []), + ], + locals: frame.locals, + body, + loc, + }); + const makerFn: IrLocal = { id: "fn.0", name: "fn", type: DYN, mutable: false, boxed: true }; + const makerLabel: IrLocal = { + id: "label.1", + name: "label", + type: STRING, + mutable: false, + boxed: true, + }; + const makerCell: IrLocal = { + id: "cell.2", + name: "cell", + type: DYN, + mutable: false, + boxed: true, + }; + const makerLocals = take || give ? [makerFn, makerLabel, makerCell] : [makerFn, makerLabel]; + lowerer.liftedFns.push({ + name: maker, + params: makerLocals.map((l) => ({ localId: l.id, name: l.name, type: l.type })), + returnType: stageT, + locals: makerLocals, + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: stageName, + captures: makerLocals.map((l) => l.id), + type: stageT, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { + closure: { + kind: "call", + callee: maker, + args: [fnValue, strLit(`transform[${j - 1}]`, loc), ...(take || give ? [handoff!.cell] : [])], + type: stageT, + loc, + }, + out, + }; +} + +/** The cell that carries an untyped stage's value to the untyped function + * stage after it, created per pipeline call. A stage that gives stores its + * value; the next stage takes it instead of the generator the runtime + * passes, as Node's makeAsyncIterable answers an iterable itself. */ +interface DynStageHandoff { + cell: IrExpr; + take: boolean; + give: boolean; +} + +function handoffPut(cell: IrExpr, value: IrExpr, loc: SrcLoc): IrStmt { + return { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.keySet", + args: [cell, strLit("v", loc), value], + type: VOID, + loc, + }, + loc, + }; +} + +function handoffTake(cell: IrExpr, loc: SrcLoc): IrExpr { + return { kind: "dynKeyGet", value: cell, key: strLit("v", loc), type: DYN, loc }; +} + +/** `(value) => generator over value`: the async generator an untyped + * destination function's async iterable result is pumped through + * (SCR_PIPE_DEST_DYN's box), interned. */ +function dynStageIterHelper(lowerer: Lowerer, loc: SrcLoc): IrExpr { + const genT = dynStageGenType(); + const type = funcOf([DYN], genT); + const key = "dynStageIter"; + let name = lowerer.valueHelpers.get(key); + if (!name) { + name = `%stream.dynStageIter.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, name); + const v: IrLocal = { id: "value.0", name: "value", type: DYN, mutable: false }; + const iterable = lowerDynIterableSource(lowerer, varRef(v.id, DYN, loc), loc); + if (!iterable) + throw new InternalCompilerError("dyn iterable generator without a result record"); + lowerer.liftedFns.push({ + name, + params: [{ localId: v.id, name: v.name, type: DYN }], + returnType: genT, + locals: [v], + body: [ + { + kind: "return", + value: { kind: "callValue", callee: iterable.closure, args: [], type: genT, loc }, + loc, + }, + ], + loc, + }); + } + return { kind: "closure", fnName: name, captures: [], type, loc }; +} + +/** A checked-dynamic first pipeline stage pumped as an iterable + * (pipelineImpl's i === 0 branch), as the stage closure the runtime calls + * with { signal } while it sets the pipeline up: a function is called + * with { signal } and its result must be iterable, a { readable, writable } + * pair pumps its readable side, and any other value is pumped itself, + * through lowerDynIterableSource's generator. */ +function lowerDynSourceStage( + lowerer: Lowerer, + value: IrExpr, + loc: SrcLoc, + cell?: IrExpr, +): { closure: IrExpr; out: SyncGenType } | null { + const out: SyncGenType = { + kind: "generator", + async: true, + yieldT: DYN, + retT: VOID, + nextT: UNDEFINED_T, + }; + const stageT = funcOf([DYN], out); + const give = cell !== undefined; + const key = give ? "dynSourceStage:give" : "dynSourceStage"; + let maker = lowerer.valueHelpers.get(key); + if (!maker) { + maker = `%stream.dynSourceStage.${lowerer.valueHelpers.size}`; + const stageName = `${maker}.stage`; + const frame = new LiftedFrame(loc); + const srcId = frame.local("src", DYN, false); + const cellId = frame.local("cell", DYN, false); + const optsId = frame.param("opts", DYN); + const valueId = frame.local("value", DYN, false); + const genId = frame.local("gen", out, false); + const src = (): IrExpr => varRef(srcId, DYN, loc); + const sourceValue = (v: IrExpr, returned: boolean): IrExpr => ({ + kind: "libCall", + fn: "stream.dynSourceValue", + args: [v, boolLit(returned, loc)], + type: DYN, + loc, + }); + const iterable = lowerDynIterableSource(lowerer, varRef(valueId, DYN, loc), loc); + if (!iterable) return null; + lowerer.valueHelpers.set(key, maker); + const body: IrStmt[] = [ + { + kind: "varDecl", + localId: valueId, + init: { + kind: "ternary", + cond: { kind: "dynTest", test: "function", value: src(), type: BOOL, loc }, + then: sourceValue( + { + kind: "dynCall", + callee: src(), + receiver: dynUndefinedExpr(loc), + calleeName: "source", + args: [varRef(optsId, DYN, loc)], + type: DYN, + loc, + }, + true, + ), + else_: sourceValue(src(), false), + type: DYN, + loc, + }, + loc, + }, + { + kind: "varDecl", + localId: genId, + init: { kind: "callValue", callee: iterable.closure, args: [], type: out, loc }, + loc, + }, + ]; + if (give) { + // the untyped function stage after the source takes the iterable + // itself, as Node's makeAsyncIterable answers it; a stream (a pair's + // readable side) goes as the generator over it, as Node iterates a + // stream there + const v = varRef(valueId, DYN, loc); + body.push( + handoffPut( + varRef(cellId, DYN, loc), + { + kind: "ternary", + cond: { kind: "libCall", fn: "stream.dynIterableSource", args: [v], type: BOOL, loc }, + then: v, + else_: { kind: "dynFrom", value: varRef(genId, out, loc), type: DYN, loc }, + type: DYN, + loc, + }, + loc, + ), + ); + } + body.push({ kind: "return", value: varRef(genId, out, loc), loc }); + const captured = give ? [srcId, cellId] : [srcId]; + for (const id of captured) frame.locals.find((l) => l.id === id)!.boxed = true; + lowerer.liftedFns.push({ + name: stageName, + params: frame.params, + returnType: out, + captures: [ + { localId: srcId, name: "src", type: DYN }, + ...(give ? [{ localId: cellId, name: "cell", type: DYN }] : []), + ], + locals: frame.locals, + body, + loc, + }); + const makerSrc: IrLocal = { id: "src.0", name: "src", type: DYN, mutable: false, boxed: true }; + const makerCell: IrLocal = { + id: "cell.1", + name: "cell", + type: DYN, + mutable: false, + boxed: true, + }; + const makerLocals = give ? [makerSrc, makerCell] : [makerSrc]; + lowerer.liftedFns.push({ + name: maker, + params: makerLocals.map((l) => ({ localId: l.id, name: l.name, type: l.type })), + returnType: stageT, + locals: makerLocals, + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: stageName, + captures: makerLocals.map((l) => l.id), + type: stageT, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { + closure: { + kind: "call", + callee: maker, + args: give ? [value, cell!] : [value], + type: stageT, + loc, + }, + out, + }; +} + function lowerIterableSource( lowerer: Lowerer, node: ts.Expression, @@ -4356,7 +4880,7 @@ function lowerIterableSource( interface PipelineStagesLowering { args: IrExpr[]; last: IrType; - dest: 0 | 1 | 2; + dest: 0 | 1 | 2 | 3; destPromise: (IrType & { kind: "promise" }) | null; wrap: (call: IrExpr) => IrExpr; } @@ -4377,6 +4901,7 @@ function lowerPipelineStages( loc: SrcLoc, preLowered?: ReadonlyMap, dynSource?: IrExpr, + plan?: DynStagePlan, ): PipelineStagesLowering { if (stageArgs.length > 52) { lowerer.noLowering( @@ -4390,7 +4915,7 @@ function lowerPipelineStages( const helperArgs: IrExpr[] = []; let positions = 0; let mask = 0; - let dest: 0 | 1 | 2 = 0; + let dest: 0 | 1 | 2 | 3 = 0; let destPromise: (IrType & { kind: "promise" }) | null = null; // What the next function stage receives: null before the first stage, // else the previous stream's type or the previous stage's generator. @@ -4399,17 +4924,69 @@ function lowerPipelineStages( // before all stages, so when a stage precedes a stream the arguments // are bound to locals first, keeping JavaScript's evaluation order. const argOrder: { list: IrExpr[]; index: number }[] = []; + // An untyped stage followed by an untyped function stage hands its value + // over through one cell per call (DynStageHandoff). + let handoffCell: IrLocal | null = null; + const cellRef = (): IrExpr => { + handoffCell ??= lowerer.declareHiddenLocal("%pipelineHandoff", DYN); + return varRef(handoffCell.id, DYN, loc); + }; + let takes = false; for (let j = 0; j < stageArgs.length; j++) { const arg = stageArgs[j]!; + const givesToNext = plan?.fns.has(j + 1) === true; if (j === 0 && dynSource !== undefined) { - const source = lowerDynIterableSource(lowerer, dynSource, loc); + const source = lowerDynSourceStage( + lowerer, + dynSource, + loc, + givesToNext ? cellRef() : undefined, + ); + takes = givesToNext; if (source === null) lowerer.noLowering("pipeline over this source", arg); + // a source function takes { signal } + mask |= 1 << stages.length; positions += 1; argOrder.push({ list: stages, index: stages.length }); stages.push(source!.closure); srcT = source!.out; continue; } + if (plan?.fns.has(j) === true) { + // a checked-dynamic stage that is a function at run time + const isLast = j === stageArgs.length - 1; + const take = takes; + const give = givesToNext && !isLast; + takes = give; + const lowered = lowerDynFunctionStage( + lowerer, + plan.bound.get(j)!, + srcT!, + j, + isLast, + loc, + take || give ? { cell: cellRef(), take, give } : undefined, + ); + mask |= 1 << stages.length; + positions += 2 ** j; + argOrder.push({ list: stages, index: stages.length }); + stages.push(lowered.closure); + if (isLast) { + // its result is classified at run time: a promise settles the + // pipeline's value, and an async iterable is pumped into the + // PassThrough + const genT = dynStageGenType(); + const helpers = fromGenHelpers(lowerer, genT, loc); + if (!helpers) lowerer.noLowering("a pipeline destination with this generator type", arg); + helperArgs.push(helpers![0]!, helpers![1]!, helpers![2]!); + dest = 3; + destPromise = { kind: "promise", inner: DYN }; + srcT = null; + } else { + srcT = lowered.out; + } + continue; + } // compose's head and tail functions arrive as Duplex.from streams const fn = preLowered?.has(j) ? null : pipelineStageFunction(lowerer, arg); const syncSourceFn = @@ -4423,7 +5000,9 @@ function lowerPipelineStages( !preLowered?.has(j) && (syncSourceFn || (fn === null && iterableSourceType(lowerer, arg) !== null)) ) { - const source = lowerIterableSource(lowerer, arg, syncSourceFn ? fn : null, loc); + const source = + (syncSourceFn ? undefined : plan?.preSource) ?? + lowerIterableSource(lowerer, arg, syncSourceFn ? fn : null, loc); if (source === null) lowerer.noLowering("pipeline over this source", arg); positions += 1; argOrder.push({ list: stages, index: stages.length }); @@ -4448,7 +5027,12 @@ function lowerPipelineStages( } let stream: IrExpr; try { - stream = preLowered?.get(j) ?? lowerStreamArg(lowerer, arg, "pipeline"); + const bound = plan?.bound.get(j); + stream = + preLowered?.get(j) ?? + (bound !== undefined + ? streamArgOf(lowerer, arg, bound, "pipeline") + : lowerStreamArg(lowerer, arg, "pipeline")); } catch (e) { if (e instanceof StreamArgTypeThrow) lowerer.noLowering("pipeline over this value", arg); throw e; @@ -4527,6 +5111,8 @@ function lowerPipelineStages( ); } helperArgs.push(pipelineBoxHelper(lowerer, destPromise!, loc)); + } else if (dest === 3) { + helperArgs.push(dynStageIterHelper(lowerer, loc)); } const argDecls: IrStmt[] = []; const firstStage = argOrder.findIndex((a) => a.list === stages); @@ -4538,6 +5124,15 @@ function lowerPipelineStages( a.list[a.index] = varRef(local.id, init.type, init.loc); } } + if (handoffCell !== null) { + const cell: IrLocal = handoffCell; + argDecls.unshift({ + kind: "varDecl", + localId: cell.id, + init: { kind: "dynObjLit", fields: [], type: DYN, loc }, + loc, + }); + } const bindArgs = (call: IrExpr): IrExpr => argDecls.length === 0 ? call diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index b0cc96687..86f9ad233 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -92,6 +92,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "child.stdioStream", "stream.unboxDyn", "stream.dynIterableSource", + "stream.dynSourceValue", + "stream.unboxDynStage", + "stream.dynTransformResult", "stream.handleStream", "stream.pipeDyn", "stream.unpipeDyn", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e230932ba..b7ef7d660 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -2870,9 +2870,27 @@ function buildLibFnSignatures() { result: { kind: "object", className: "%Duplex" }, }, /** Whether pipeline() pumps a checked-dynamic first stage as an - * iterable (a string, or a value with an iterator method that is not a - * stream with pipe() and on()) instead of piping it as a stream. */ + * iterable (a string, a value with an iterator method that is not a + * stream with pipe() and on(), a { readable, writable } pair, or a + * source function's result) instead of piping it as a stream. */ "stream.dynIterableSource": { argTypes: [DYN], result: BOOL }, + /** The value pipeline() pumps for a checked-dynamic first stage: a + * source function's result (the flag set), which must be iterable, else + * a { readable, writable } pair's readable side or the value itself. */ + "stream.dynSourceValue": { argTypes: [DYN, BOOL], result: DYN }, + /** An untyped pipeline function stage's result before the last stage, + * which must be async iterable: ERR_INVALID_RETURN_VALUE naming the + * stage ("transform[i]") otherwise. Answers the value (+1). */ + "stream.dynTransformResult": { argTypes: [DYN, STRING], result: DYN }, + /** A checked-dynamic pipeline() stage taken as a stream: a native stream + * (+1), or Duplex.from of a value that is not stream-like or a function + * (an iterable), which the pipeline uses without piping into it. Other + * values throw: Duplex.from's ERR_INVALID_ARG_TYPE, or the given + * not-supported message. */ + "stream.unboxDynStage": { + argTypes: [DYN, STRING], + result: { kind: "object", className: "%Duplex" }, + }, /** The Transform a Cipheriv or Decipheriv reads and writes as a stream * (+1, created on first use and owned by the cipher). */ "stream.handleStream": { @@ -5237,10 +5255,12 @@ export type IrLibFn = keyof ReturnType; /** The function-stage runs of a pipeline of `total` stages that a stream * follows (positions bit j: stage j is a function), and an async * generator destination's run (flags bits 1-2 = 1, pumped into the - * PassThrough pipeline() answers): each pumps its last generator through - * one (next, read, return) triple. */ + * PassThrough pipeline() answers, or 3, an untyped destination whose + * result may be pumped there): each pumps its last generator through one + * (next, read, return) triple. */ export function pipelinePumpedRuns(total: number, positions: number, flags: number): number { - let runs = ((flags >> 1) & 3) === 1 ? 1 : 0; + const dest = (flags >> 1) & 3; + let runs = dest === 1 || dest === 3 ? 1 : 0; for (let j = 0; j + 1 < total; j++) { if (Math.floor(positions / 2 ** j) % 2 === 1 && Math.floor(positions / 2 ** (j + 1)) % 2 === 0) runs++; diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 494c4e3e8..630ebdc67 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -278,6 +278,9 @@ const STREAM_LIB_FNS: ReadonlySet = new Set([ "stream.stdioWritable", "stream.unboxDyn", "stream.dynIterableSource", + "stream.dynSourceValue", + "stream.dynTransformResult", + "stream.unboxDynStage", "stream.handleStream", "stream.unpipeDyn", "stream.workerStdio", @@ -314,6 +317,7 @@ const READABLE_DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ "stream.duplexFromAsyncFn", "stream.duplexFromGenSignal", "stream.duplexFromAsyncFnSignal", + "stream.unboxDynStage", ]); /** libCall name prefixes of the Readable entry points that await through the dyn-async unit. */ diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 45ebf5c79..84751cc06 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -5761,7 +5761,7 @@ function validateFunction( } const lastIsFunction = Math.floor(positions! / 2 ** (total - 1)) % 2 === 1; const helperCount = 3 * pipelinePumpedRuns(total, positions!, flags!); - const groups = helperCount + (destKind === 2 ? 1 : 0) + (flags! & 32 ? 2 : 0); + const groups = helperCount + (destKind >= 2 ? 1 : 0) + (flags! & 32 ? 2 : 0); const cbArgs = e.fn === "sp.pipelineStagesOpts" ? 1 + (flags! & 64 ? 1 : 0) @@ -5776,7 +5776,7 @@ function validateFunction( total > 52 || positions! >= 2 ** total || functions !== k || - destKind === 3 || + (destKind === 3 && e.fn === "stream.composeStages") || (destKind !== 0) !== lastIsFunction || total < 2 || e.args.length !== 5 + n! + k! + groups + cbArgs @@ -5795,7 +5795,7 @@ function validateFunction( err(`libCall ${e.fn} stage function ${i} must be a func`, e.loc); } at2 += k!; - const helpers = helperCount + (destKind === 2 ? 1 : 0); + const helpers = helperCount + (destKind >= 2 ? 1 : 0); for (let i = at2; i < at2 + helpers; i++) { if (e.args[i]?.type.kind !== "func") err(`libCall ${e.fn} helper ${i} must be a func`, e.loc); diff --git a/packages/compiler/test/native-codegen-integration.test.ts b/packages/compiler/test/native-codegen-integration.test.ts index a57d96f01..bdad62203 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_v45"); + expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v46"); 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_v45"); + expect(undefinedSymbols).toContain("_scr_runtime_abi_v46"); }); test("helper and clang object paths retain the same outbound FFI C ABI", async () => { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7508-pipeline-untyped-source-functions.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7508-pipeline-untyped-source-functions.mjs.json new file mode 100644 index 000000000..4027063d4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7508-pipeline-untyped-source-functions.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7508-pipeline-untyped-source-functions.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7509-pipeline-untyped-function-stages.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7509-pipeline-untyped-function-stages.mjs.json new file mode 100644 index 000000000..028a5c1c6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7509-pipeline-untyped-function-stages.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7509-pipeline-untyped-function-stages.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7510-pipeline-untyped-later-iterables.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7510-pipeline-untyped-later-iterables.mjs.json new file mode 100644 index 000000000..be18c801a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7510-pipeline-untyped-later-iterables.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7510-pipeline-untyped-later-iterables.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7511-pipeline-untyped-stages.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7511-pipeline-untyped-stages.cjs.json new file mode 100644 index 000000000..2353cc44c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7511-pipeline-untyped-stages.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7511-pipeline-untyped-stages.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7512-pipeline-any-stages.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7512-pipeline-any-stages.ts.json new file mode 100644 index 000000000..acea0e98d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7512-pipeline-any-stages.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7512-pipeline-any-stages.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7513-pipeline-untyped-stage-errors.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7513-pipeline-untyped-stage-errors.mjs.json new file mode 100644 index 000000000..39a5dba57 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7513-pipeline-untyped-stage-errors.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7513-pipeline-untyped-stage-errors.mjs" + ], + "diags": [] +} diff --git a/packages/runtime-darwin-arm64/scripts/build.mjs b/packages/runtime-darwin-arm64/scripts/build.mjs index 3305d69de..f8f5d0bf1 100644 --- a/packages/runtime-darwin-arm64/scripts/build.mjs +++ b/packages/runtime-darwin-arm64/scripts/build.mjs @@ -366,7 +366,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: RUNTIME_PACK_MATRIX.target, - runtime_abi: { version: 45, marker: "scr_runtime_abi_v45" }, + runtime_abi: { version: 46, marker: "scr_runtime_abi_v46" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs index 8c0863b04..ed32f43fb 100644 --- a/packages/runtime-pack-common/scripts/build.mjs +++ b/packages/runtime-pack-common/scripts/build.mjs @@ -469,7 +469,7 @@ async function build() { package: packageManifest.name, version: packageManifest.version, target: matrix.target, - runtime_abi: { version: 45, marker: "scr_runtime_abi_v45" }, + runtime_abi: { version: 46, marker: "scr_runtime_abi_v46" }, compiler: { command: compiler, identity: compilerVersion, diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c index 67d4aca15..18c715cb2 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_v45(void) {} +void scr_runtime_abi_v46(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 206463e18..92392897a 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_v45(void); +void scr_runtime_abi_v46(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_dyn.h b/packages/runtime/src/scr_dyn.h index 2d916a825..63dc0d209 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1548,6 +1548,9 @@ ScrDyn *scr_dyn_builtin_stream_object(struct ScrStream *s); ScrDyn *scr_dyn_builtin_stream_view(struct ScrStream *s); struct ScrStream *scr_dyn_builtin_stream_check(const ScrDyn *value, double kind); struct ScrStream *scr_dyn_builtin_stream_unbox(const ScrDyn *value, const ScrStr *message); +/* stream.unboxDynStage in those programs (scr_stream_unbox_dyn_stage's + * contract, also taking constructor-function streams). */ +struct ScrStream *scr_dyn_builtin_stream_unbox_stage(const ScrDyn *value, const ScrStr *message); /* stream.dynNative in those programs: scr_stream_dyn_native's contract, * also taking constructor-function streams that keep the runtime's write * and end (+1 or NULL). */ diff --git a/packages/runtime/src/scr_dyn_builtin_stream.c b/packages/runtime/src/scr_dyn_builtin_stream.c index 2875cfa06..52c1e7269 100644 --- a/packages/runtime/src/scr_dyn_builtin_stream.c +++ b/packages/runtime/src/scr_dyn_builtin_stream.c @@ -1752,3 +1752,11 @@ ScrStream *scr_dyn_builtin_stream_unbox(const ScrDyn *value, const ScrStr *messa } return scr_stream_unbox_dyn(value, message); } + +/* stream.unboxDynStage in programs that link this unit: a + * constructor-function stream as scr_dyn_builtin_stream_unbox takes it, + * any other value as scr_stream_unbox_dyn_stage does. */ +ScrStream *scr_dyn_builtin_stream_unbox_stage(const ScrDyn *value, const ScrStr *message) { + if (bcs_of(value) && value->kind == SCR_DYN_OBJ) return scr_dyn_builtin_stream_unbox(value, message); + return scr_stream_unbox_dyn_stage(value, message); +} diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 201d6e34b..a7593b551 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -93,6 +93,10 @@ struct ScrStreamState { bool is_transform; /* the writable side routes through transform/flush */ bool allow_half_open; bool auto_destroy, emit_close; + /* Duplex.from of a pipeline() argument that is not a stream: the + * pipeline takes it as that stage's stream without wiring the stage + * (scr_stream_unbox_dyn_stage) */ + bool pipeline_body; /* shared lifecycle */ bool destroyed, destroy_calling, error_emitted, close_emitted, error_scheduled, close_scheduled; @@ -3850,7 +3854,9 @@ typedef ScrPromise *(*ScrPipeNextFn)(ScrClosure *, ScrGen *); typedef ScrDyn *(*ScrPipeReadFn)(ScrClosure *, void *); typedef ScrDyn *(*ScrPipeBoxFn)(ScrClosure *, ScrPromise *); -enum { SCR_PIPE_DEST_STREAM, SCR_PIPE_DEST_GEN, SCR_PIPE_DEST_PROMISE }; +/* SCR_PIPE_DEST_DYN: an untyped destination function's result, a + * checked-dynamic value the pipeline classifies (scr_pipe_dest_dyn_kind) */ +enum { SCR_PIPE_DEST_STREAM, SCR_PIPE_DEST_GEN, SCR_PIPE_DEST_PROMISE, SCR_PIPE_DEST_DYN }; /* flags bit 7: the callback is typed code's (stream.pipelineStagesDyn) */ enum { SCR_PIPE_TYPED_CB = 128 }; @@ -3868,7 +3874,9 @@ typedef struct { * async generator destination's PassThrough included), in pipeline * order, or NULL */ ScrClosure **helpers; - ScrClosure *box; /* SCR_PIPE_DEST_PROMISE: boxes its promise as a dyn promise */ + /* SCR_PIPE_DEST_PROMISE: boxes its promise as a dyn promise; + * SCR_PIPE_DEST_DYN: answers the async generator over a dyn iterable */ + ScrClosure *box; } ScrPipeStages; static bool scr_pipe_is_stage(const ScrPipeStages *fs, size_t j) { @@ -4234,6 +4242,71 @@ static void scr_pipe_dest_promise(ScrStreamPipeCtx *ctx, ScrPromise *p /*moves*/ scr_promise_release(scr_async_spawn_after(d->boxed->v.promise, &scr_pipe_dest_entry, d)); } +/* An untyped destination's promise: scr_pipe_dest_promise over a promise + * that already holds dyn values. */ +static void scr_pipe_dest_dyn_promise(ScrStreamPipeCtx *ctx, ScrDyn *boxed /*moves*/, size_t pi, + bool end) { + ScrPipeDest *d = malloc(sizeof *d); + if (!d) scr_stream_oom(); + d->ctx = scr_stream_pipectx_retain_v(ctx); + d->typed = scr_promise_retain(boxed->v.promise); + d->boxed = boxed; + d->pi = pi; + d->end = end; + scr_promise_release(scr_async_spawn_after(d->boxed->v.promise, &scr_pipe_dest_entry, d)); +} + +typedef ScrGen *(*ScrPipeIterFn)(ScrClosure *, ScrDyn *); +static bool scr_stream_dyn_has_method(const ScrDyn *value, const char *key); +static bool scr_stream_dyn_transform_like(const ScrDyn *value); +static void scr_stream_throw_web_stage(void); +static bool scr_stream_dyn_stage_returned_stream(const ScrDyn *value); +static void scr_stream_throw_invalid_return(const char *expected, const char *name, + const ScrDyn *got); + +/* What pipelineImpl does with an untyped destination function's result: + * a promise settles the pipeline's value (the `then` branch), and an + * async iterable, or a { readable, writable } pair's readable side, is + * pumped into the PassThrough. *out answers the promise or that + * iterable's async generator (+1). Other values throw + * ERR_INVALID_RETURN_VALUE, and thenables that are not promises and web + * streams throw the not-supported Error; -1 then. */ +enum { SCR_PIPE_DYN_PROMISE, SCR_PIPE_DYN_ITER }; +static int scr_pipe_dest_dyn_kind(const ScrPipeStages *fs, ScrDyn *r, void **out) { + if (r->kind == SCR_DYN_PROMISE) { + *out = scr_dyn_retain(r); + return SCR_PIPE_DYN_PROMISE; + } + if (r->kind == SCR_DYN_OBJ && scr_stream_dyn_has_method(r, "then")) { + static const char msg[] = + "pipeline destinations that return a thenable other than a promise are not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "SC2020"); + return -1; + } + if (scr_exc_pending() || scr_stream_dyn_stage_returned_stream(r)) return -1; + ScrDyn *read = NULL; + int kind = scr_dyn_iterable_kind(r); + if (kind < 0) return -1; + if (r->kind == SCR_DYN_HANDLE && + (r->v.handle.tag == SCR_DYNH_WEB_STREAM || r->v.handle.tag == SCR_DYNH_WEB_TRANSFORM)) { + scr_stream_throw_web_stage(); + return -1; + } + if (kind == 2) { + read = scr_dyn_retain(r); + } else if (scr_stream_dyn_transform_like(r)) { + read = scr_dyn_obj_read(r, "readable", 8); + } + if (!read) { + if (!scr_exc_pending()) scr_stream_throw_invalid_return("AsyncIterable or Promise", "destination", r); + return -1; + } + ScrGen *g = ((ScrPipeIterFn)fs->box->fn)(fs->box, read); + if (!g) return -1; + *out = g; + return SCR_PIPE_DYN_ITER; +} + static ScrStream *scr_stream_pipeline_ex(size_t n, ScrStream **streams, ScrClosure *cb, ScrStreamErrInv inv, bool end_opt, ScrDyn *signal, ScrAbortAddFn add, const ScrPipeStages *fs, @@ -4294,8 +4367,18 @@ static ScrStream *scr_stream_pipeline_ex(size_t n, ScrStream **streams, ScrClosu : (void *)scr_stream_retain(ret); ret_gen = NULL; void *out = scr_pipe_call_stage(fs, k++, src); + int dyn_kind = -1; + if (!scr_exc_pending() && dest && fs->dest == SCR_PIPE_DEST_DYN) { + ScrDyn *r = out ? out : scr_dyn_retain(scr_dyn_undefined()); + out = NULL; + dyn_kind = scr_pipe_dest_dyn_kind(fs, r, &out); + scr_dyn_release(r); + } if (scr_exc_pending()) { - if (out && dest) scr_promise_release(out); + if (out && dest && fs->dest == SCR_PIPE_DEST_DYN) { + if (dyn_kind == SCR_PIPE_DYN_ITER) scr_gen_release(out); + else scr_dyn_release(out); + } else if (out && dest) scr_promise_release(out); else if (out) scr_gen_release(out); scr_stream_pipectx_drop(ctx); scr_stream_pipectx_release_v(ctx); @@ -4315,9 +4398,12 @@ static ScrStream *scr_stream_pipeline_ex(size_t n, ScrStream **streams, ScrClosu size_t pi = ctx->n++; ctx->streams[pi] = pt; ctx->finish_count++; - if (fs->dest == SCR_PIPE_DEST_GEN) { + if (fs->dest == SCR_PIPE_DEST_GEN || + (fs->dest == SCR_PIPE_DEST_DYN && dyn_kind == SCR_PIPE_DYN_ITER)) { /* pumpToNode(ret, pt, finish, { end }) */ scr_pump_start(ctx, out, fs->helpers + 3 * h++, pt, end); + } else if (fs->dest == SCR_PIPE_DEST_DYN) { + scr_pipe_dest_dyn_promise(ctx, out, pi, end); } else { scr_pipe_dest_promise(ctx, out, fs->box, pi, end); } @@ -4332,6 +4418,20 @@ static ScrStream *scr_stream_pipeline_ex(size_t n, ScrStream **streams, ScrClosu continue; } ScrStream *s = streams[i]; + if (s->st->pipeline_body) { + /* ret = Duplex.from(stream) for an argument that is not a stream: + * no destroyer or 'error' listener, and the previous stage is + * neither piped nor pumped into it */ + s->st->pipeline_body = false; + if (ret_gen) { + scr_gen_release(ret_gen); + ret_gen = NULL; + h++; + } + ret = s; + i++; + continue; + } bool writing = j > 0; if (reading && !scr_pipe_is_stage(fs, j + 1) && (streams[i + 1]->st->close_emitted || streams[i + 1]->st->destroyed)) { @@ -8593,10 +8693,90 @@ ScrStream *scr_stream_unbox_dyn(const ScrDyn *value, const ScrStr *message) { return NULL; } -/* pipeline.js's first stage: a value with pipe() and on() methods is a - * readable node stream, which the stage ladder tests before isIterable; - * a string, or a value with a Symbol.asyncIterator or Symbol.iterator - * method, is an iterable the pipeline pumps into the next stage. */ +/* Whether a checked-dynamic object's member is of type "object". */ +static bool scr_stream_dyn_member_object(const ScrDyn *value, const char *key) { + ScrDyn *m = scr_dyn_obj_read(value, key, strlen(key)); + if (!m) return false; + ScrStr *t = scr_dyn_typeof(m); + bool object = t->len == 6 && memcmp(t->data, "object", 6) == 0; + scr_str_release(t); + scr_dyn_release(m); + return object; +} + +/* pipeline.js's isTransformStream for a value that is not a stream: an + * object whose readable and writable members are both of type "object". */ +static bool scr_stream_dyn_transform_like(const ScrDyn *value) { + if (value->kind == SCR_DYN_HANDLE) return value->v.handle.tag == SCR_DYNH_WEB_TRANSFORM; + return value->kind == SCR_DYN_OBJ && scr_stream_dyn_member_object(value, "readable") && + !scr_exc_pending() && scr_stream_dyn_member_object(value, "writable"); +} + +/* Whether a checked-dynamic object has a function member `key`. */ +static bool scr_stream_dyn_has_method(const ScrDyn *value, const char *key) { + ScrDyn *m = scr_dyn_obj_read(value, key, strlen(key)); + if (!m) return false; + bool fn = m->kind == SCR_DYN_FUNC; + scr_dyn_release(m); + return fn; +} + +/* pipeline.js's isNodeStream and isWebStream for a checked-dynamic value + * that is not a native stream: a stream-like object or a web stream, which + * the pipeline would pipe or pump into and has no lowering for. */ +static bool scr_stream_dyn_stream_like(const ScrDyn *value) { + if (value->kind == SCR_DYN_HANDLE) { + int tag = value->v.handle.tag; + return tag == SCR_DYNH_WEB_STREAM || tag == SCR_DYNH_WEB_TRANSFORM || tag == SCR_DYNH_WEB_WRITABLE; + } + if (value->kind != SCR_DYN_OBJ) return false; + static const char *const state_keys[] = {"_readableState", "_writableState"}; + for (size_t i = 0; i < 2; i++) { + ScrDyn *v = scr_dyn_obj_read(value, state_keys[i], strlen(state_keys[i])); + bool set = v && scr_dyn_truthy(v); + if (v) scr_dyn_release(v); + if (set) return true; + } + bool on = scr_stream_dyn_has_method(value, "on"); + if (on && (scr_stream_dyn_has_method(value, "write") || scr_stream_dyn_has_method(value, "pipe"))) + return true; + if (scr_stream_dyn_has_method(value, "pipeThrough") && + scr_stream_dyn_has_method(value, "getReader") && scr_stream_dyn_has_method(value, "cancel")) + return true; + if (scr_stream_dyn_has_method(value, "getWriter") && scr_stream_dyn_has_method(value, "abort")) + return true; + return scr_stream_dyn_transform_like(value); +} + +/* A checked-dynamic pipeline() stage that the pipeline takes as a stream + * (the stream.unboxDynStage libCall): a boxed native stream or a stream + * handle answers its stream (+1). Any other value that is not stream-like + * or a function takes pipelineImpl's last branch, Duplex.from(stream): + * an iterable answers a readable Duplex the pipeline uses without piping + * the previous stage into it, and values Duplex.from rejects throw its + * ERR_INVALID_ARG_TYPE. Stream-like objects, web streams and functions + * throw `message` as the not-supported Error. */ +ScrStream *scr_stream_unbox_dyn_stage(const ScrDyn *value, const ScrStr *message) { + ScrStream *stream = scr_stream_of_handle(value); + if (!stream) stream = scr_stream_boxed(value); + if (stream) return stream; + if (value->kind == SCR_DYN_FUNC || scr_stream_dyn_stream_like(value)) { + if (!scr_exc_pending()) + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + return NULL; + } + if (scr_exc_pending()) return NULL; + ScrStream *d = scr_stream_duplex_from_dyn((ScrDyn *)value); + if (d) d->st->pipeline_body = true; + return d; +} + +/* pipeline.js's first stage: a function is a source function, which the + * pipeline calls; a value with pipe() and on() methods is a readable node + * stream, which the stage ladder tests before isIterable; a string, a + * value with a Symbol.asyncIterator or Symbol.iterator method, or a + * { readable, writable } pair is an iterable the pipeline pumps into the + * next stage. */ bool scr_stream_dyn_iterable_source(const ScrDyn *value) { ScrStream *stream = scr_stream_of_handle(value); if (!stream) stream = scr_stream_boxed(value); @@ -8604,7 +8784,8 @@ bool scr_stream_dyn_iterable_source(const ScrDyn *value) { scr_stream_release(stream); return false; } - if (value->kind == SCR_DYN_STR) return true; + if (value->kind == SCR_DYN_STR || value->kind == SCR_DYN_FUNC) return true; + if (value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_WEB_TRANSFORM) return true; if (value->kind == SCR_DYN_OBJ) { ScrDyn *pipe = scr_dyn_obj_read(value, "pipe", 4); if (!pipe) return false; @@ -8617,6 +8798,79 @@ bool scr_stream_dyn_iterable_source(const ScrDyn *value) { scr_dyn_release(on); if (streamish) return false; } + if (scr_stream_dyn_transform_like(value)) return true; + if (scr_exc_pending()) return false; } return scr_dyn_iterable_kind(value) > 0; } + +/* Untyped web streams as pipeline stages have no lowering. */ +static void scr_stream_throw_web_stage(void) { + static const char msg[] = "pipeline stages that are untyped web streams are not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "SC2020"); +} + +/* A native stream returned by an untyped pipeline function, which Node + * pipes or passes on as the stream: throws the not-supported Error. */ +static bool scr_stream_dyn_stage_returned_stream(const ScrDyn *value) { + ScrStream *stream = scr_stream_of_handle(value); + if (!stream) stream = scr_stream_boxed(value); + if (!stream) return false; + scr_stream_release(stream); + static const char msg[] = "pipeline function stages that return a stream are not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "SC2020"); + return true; +} + +/* Node's ERR_INVALID_RETURN_VALUE TypeError: "Expected to be + * returned from the "" function but got ." */ +static void scr_stream_throw_invalid_return(const char *expected, const char *name, + const ScrDyn *got) { + char detail[160]; + const char *type = scr_dyn_specific_type(got, detail, sizeof detail); + char msg[384]; + int n = snprintf(msg, sizeof msg, "Expected %s to be returned from the \"%s\" function but got %s.", + expected, name, type); + if (n < 0) n = 0; + if ((size_t)n >= sizeof msg) n = (int)sizeof msg - 1; + scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_INVALID_RETURN_VALUE"); +} + +/* An untyped pipeline function stage's result before the last stage + * (pipelineImpl's `isIterable(ret, true)` check): an async iterable + * answers itself (+1); other values throw ERR_INVALID_RETURN_VALUE naming + * the stage, as `label` ("transform[i]") does. */ +ScrDyn *scr_stream_dyn_transform_result(const ScrDyn *value, const ScrStr *label) { + if (scr_stream_dyn_stage_returned_stream(value)) return NULL; + int kind = scr_dyn_iterable_kind(value); + if (kind < 0) return NULL; + if (kind != 2) { + scr_stream_throw_invalid_return("AsyncIterable", label->data, value); + return NULL; + } + return scr_dyn_retain((ScrDyn *)value); +} + +/* The value a checked-dynamic first stage pumps (pipelineImpl's i === 0 + * branch): for `returned`, a source function's result, which must be + * iterable; otherwise a { readable, writable } pair's readable side, or + * the value itself. +1, or NULL with an exception pending. */ +ScrDyn *scr_stream_dyn_source_value(const ScrDyn *value, bool returned) { + if (!returned) { + if (value->kind == SCR_DYN_HANDLE && value->v.handle.tag == SCR_DYNH_WEB_TRANSFORM) { + scr_stream_throw_web_stage(); + return NULL; + } + if (scr_stream_dyn_transform_like(value)) return scr_dyn_obj_read(value, "readable", 8); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain((ScrDyn *)value); + } + if (scr_stream_dyn_stage_returned_stream(value)) return NULL; + int kind = scr_dyn_iterable_kind(value); + if (kind < 0) return NULL; + if (kind == 0) { + scr_stream_throw_invalid_return("Iterable, AsyncIterable or Stream", "source", value); + return NULL; + } + return scr_dyn_retain((ScrDyn *)value); +} diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h index 7acad0eeb..c10978298 100644 --- a/packages/runtime/src/scr_stream.h +++ b/packages/runtime/src/scr_stream.h @@ -149,6 +149,18 @@ ScrStream *scr_stream_unbox_dyn(const struct ScrDyn *value, const ScrStr *messag * to pump rather than as a stream (false with an exception pending when * reading the value's members throws). */ bool scr_stream_dyn_iterable_source(const struct ScrDyn *value); +/* A checked-dynamic pipeline() stage the pipeline takes as a stream: a + * native stream (+1), or Duplex.from of a value that is not stream-like + * or a function, which the pipeline uses without piping into it; NULL + * with an exception otherwise (`message` for the unsupported shapes). */ +ScrStream *scr_stream_unbox_dyn_stage(const struct ScrDyn *value, const ScrStr *message); +/* The iterable pipeline() pumps for a checked-dynamic first stage: a + * source function's result when `returned`, else a { readable, writable } + * pair's readable side or the value (+1, or NULL with an exception). */ +struct ScrDyn *scr_stream_dyn_source_value(const struct ScrDyn *value, bool returned); +/* An untyped function stage's result before the last pipeline stage: the + * async iterable itself (+1), or NULL with ERR_INVALID_RETURN_VALUE. */ +struct ScrDyn *scr_stream_dyn_transform_result(const struct ScrDyn *value, const ScrStr *label); /* Handles that are Transforms (ScrDynHandleOps.transform: Cipheriv and * Decipheriv). scr_stream_of_handle: the handle's Transform (+1), built on * first use, or NULL when the value is not such a handle. diff --git a/tests/corpus/7508-pipeline-untyped-source-functions.mjs b/tests/corpus/7508-pipeline-untyped-source-functions.mjs new file mode 100644 index 000000000..987221ffd --- /dev/null +++ b/tests/corpus/7508-pipeline-untyped-source-functions.mjs @@ -0,0 +1,85 @@ +// pipeline() over untyped first stages that are functions or +// { readable, writable } pairs: a source function is called with +// { signal } while the pipeline is set up and its iterable result is +// pumped; a result that is not iterable throws ERR_INVALID_RETURN_VALUE +// from the callback form and rejects the promise form; a pair pumps its +// readable side. +import { pipeline, Writable, Readable, PassThrough } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function pick(v) { + return v; +} +function sink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + console.log(label, "write", JSON.stringify(String(chunk))); + cb(); + }, + final(cb) { + console.log(label, "final"); + cb(); + }, + }); +} + +const arraySource = pick(function (opts) { + console.log("array source called", typeof opts, typeof opts.signal, opts.signal.aborted); + return ["a1", "a2"]; +}); +const before = pipelineP(arraySource, sink("array")); +console.log("after pipeline() returned"); +await before; +console.log("array resolved"); + +await pipelineP(pick(() => "xy"), sink("string")); + +await pipelineP( + pick(async function* ({ signal }) { + console.log("generator source", typeof signal.aborted); + yield "g1"; + await new Promise((r) => setTimeout(r, 1)); + yield "g2"; + }), + pick(async function* (source) { + for await (const chunk of source) yield `[${chunk}]`; + }), + sink("generator"), +); + +await new Promise((resolve) => { + pipeline(pick(() => new Set(["s1", "s2"])), sink("set"), (err) => { + console.log("set done", err); + resolve(undefined); + }); +}); + +// a result that is not iterable +try { + pipeline(pick(() => 5), sink("five"), () => console.log("five callback")); +} catch (err) { + console.log("five threw", err.name, err.code, err.message); +} +try { + await pipelineP(pick(() => ({ value: 1 })), sink("object")); +} catch (err) { + console.log("object rejected", err.name, err.code, err.message); +} + +// a source function that throws +try { + await pipelineP( + pick(() => { + throw new Error("source failed"); + }), + sink("throws"), + ); +} catch (err) { + console.log("throws rejected", err.message); +} + +// a { readable, writable } pair pumps its readable side +const side = new PassThrough(); +await pipelineP(pick({ readable: Readable.from(["p1", "p2"]), writable: side }), sink("pair")); +console.log("done"); diff --git a/tests/corpus/7509-pipeline-untyped-function-stages.mjs b/tests/corpus/7509-pipeline-untyped-function-stages.mjs new file mode 100644 index 000000000..18749e55a --- /dev/null +++ b/tests/corpus/7509-pipeline-untyped-function-stages.mjs @@ -0,0 +1,112 @@ +// pipeline() with untyped stages after the first that are functions: a +// transform is called with the previous stream or generator and +// { signal } while the pipeline is set up, and must return an async +// iterable; an async function destination's value resolves the promise +// form and reaches the callback; an async generator destination feeds +// the PassThrough that pipeline() returns. Results of the wrong kind throw +// ERR_INVALID_RETURN_VALUE naming the stage. A stage after an untyped +// source function or transform receives that stage's own iterator, as +// Node's makeAsyncIterable answers an iterable itself. +import { pipeline, Writable, Readable, PassThrough } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function pick(v) { + return v; +} +function sink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + console.log(label, "write", JSON.stringify(String(chunk))); + cb(); + }, + }); +} +const upper = pick(async function* (source, { signal }) { + console.log("upper called", source instanceof Readable, typeof signal.aborted); + for await (const chunk of source) yield String(chunk).toUpperCase(); +}); +const join = pick(async (source) => { + let text = ""; + for await (const chunk of source) text += chunk; + return text; +}); + +await pipelineP(Readable.from(["a", "b"]), upper, sink("transform")); +console.log("joined", await pipelineP(Readable.from(["x", "y"]), upper, join)); +console.log("two transforms", await pipelineP(pick(["m", "n"]), upper, upper, join)); + +await new Promise((resolve) => { + pipeline(Readable.from(["c1", "c2"]), upper, join, (err, value) => { + console.log("callback", err, value); + resolve(undefined); + }); +}); + +// an async generator destination +const wrap = pick(async function* (source) { + for await (const chunk of source) yield `<${chunk}>`; +}); +const out = pipeline(Readable.from(["p1", "p2"]), wrap, (err, value) => { + console.log("generator destination done", err, value); +}); +console.log("returns a PassThrough", out instanceof PassThrough); +for await (const chunk of out) console.log("passthrough", chunk); + +// a destination whose promise rejects +try { + await pipelineP( + Readable.from(["r"]), + pick(async (source) => { + for await (const chunk of source) console.log("rejecting after", chunk); + throw new Error("destination failed"); + }), + ); +} catch (err) { + console.log("rejected", err.message); +} + +// results of the wrong kind +try { + await pipelineP(Readable.from(["i"]), pick(() => 5), sink("number")); +} catch (err) { + console.log("transform result", err.name, err.code, err.message); +} +try { + await pipelineP(Readable.from(["i"]), upper, pick(() => ["sync"]), sink("array")); +} catch (err) { + console.log("second transform result", err.code, err.message); +} +try { + pipeline(Readable.from(["i"]), pick(() => 7), () => console.log("never")); +} catch (err) { + console.log("destination result", err.name, err.code, err.message); +} +// the iterators handed from stage to stage +let transformIter = null; +const handed = await pipelineP( + pick(function* () { + yield "s1"; + yield "s2"; + }), + pick((source) => { + // a sync generator, not an async iterator over it + console.log( + "transform gets the source's iterator", + typeof source[Symbol.asyncIterator], + typeof source[Symbol.iterator], + ); + transformIter = (async function* () { + for await (const chunk of source) yield `${chunk}!`; + })(); + return transformIter; + }), + pick(async (source) => { + console.log("destination gets the transform's iterator", source === transformIter); + let text = ""; + for await (const chunk of source) text += chunk; + return text; + }), +); +console.log("handed", handed); +console.log("done"); diff --git a/tests/corpus/7510-pipeline-untyped-later-iterables.mjs b/tests/corpus/7510-pipeline-untyped-later-iterables.mjs new file mode 100644 index 000000000..7fe6e27b3 --- /dev/null +++ b/tests/corpus/7510-pipeline-untyped-later-iterables.mjs @@ -0,0 +1,64 @@ +// pipeline() with an untyped stage after the first that is not a stream or +// a function: Node takes Duplex.from of it, so an iterable becomes the +// readable stage the next stream is piped from, the stages before it are +// not read and are destroyed when the pipeline finishes, and a value +// Duplex.from rejects throws ERR_INVALID_ARG_TYPE. +import { pipeline, Writable, Readable, Duplex } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function pick(v) { + return v; +} +function sink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + console.log(label, "write", JSON.stringify(String(chunk))); + cb(); + }, + }); +} +function watched(label, values) { + const source = Readable.from(values); + source.on("error", (err) => console.log(label, "error", err.code)); + source.on("close", () => console.log(label, "close", source.destroyed)); + return source; +} + +await pipelineP(watched("first", ["unread"]), pick(["z1", "z2"]), sink("array")); +console.log("array resolved"); +await new Promise((r) => setTimeout(r, 5)); + +await new Promise((resolve) => { + pipeline(watched("second", ["unread"]), pick("text"), sink("string"), (err) => { + console.log("string done", err); + resolve(undefined); + }); +}); +await new Promise((r) => setTimeout(r, 5)); + +// a generator stage before the iterable is never read +await pipelineP( + Readable.from(["s"]), + pick(async function* (source) { + console.log("generator stage called"); + for await (const chunk of source) yield chunk; + }), + pick(new Set(["set1", "set2"])), + sink("set"), +); +console.log("set resolved"); + +// the iterable as the last stage is what pipeline() returns +const last = pipeline(Readable.from(["u"]), pick(["tail"]), () => console.log("never")); +console.log(last instanceof Duplex, last.readable, last.writable); +for await (const chunk of last) console.log("last", chunk); + +for (const value of [42, { plain: true }, null]) { + try { + await pipelineP(Readable.from(["i"]), pick(value), sink("rejected")); + } catch (err) { + console.log(err.name, err.code, err.message); + } +} +console.log("done"); diff --git a/tests/corpus/7511-pipeline-untyped-stages.cjs b/tests/corpus/7511-pipeline-untyped-stages.cjs new file mode 100644 index 000000000..124372e12 --- /dev/null +++ b/tests/corpus/7511-pipeline-untyped-stages.cjs @@ -0,0 +1,50 @@ +// A CommonJS helper that passes its untyped parameters to pipeline(): the +// same call runs over streams, function stages and iterables, whichever +// each argument is at run time. +const { pipeline, Transform, Writable, Readable } = require("node:stream"); + +function run(label, source, middle, destination) { + return new Promise((resolve) => { + pipeline(source, middle, destination, (err, value) => { + console.log(label, "done", err ? err.code || err.message : err, value); + resolve(undefined); + }); + }); +} +function sink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + console.log(label, "write", String(chunk)); + cb(); + }, + }); +} +const double = new Transform({ + objectMode: true, + transform(chunk, _enc, cb) { + cb(null, String(chunk) + String(chunk)); + }, +}); +async function* exclaim(source) { + for await (const chunk of source) yield `${chunk}!`; +} +async function count(source) { + let n = 0; + for await (const _ of source) n++; + return n; +} + +(async () => { + await run("streams", Readable.from(["a", "b"]), double, sink("streams")); + await run("function middle", Readable.from(["c"]), exclaim, sink("function middle")); + await run("function ends", () => ["d", "e", "f"], exclaim, count); + await run("iterable middle", Readable.from(["skipped"]), ["g"], sink("iterable middle")); + await run("generator into stream", ["h"], exclaim, new Transform({ + objectMode: true, + transform(chunk, _enc, cb) { + console.log("transform saw", chunk); + cb(null, chunk); + }, + }).on("data", (d) => console.log("data", d))); +})(); diff --git a/tests/corpus/7512-pipeline-any-stages.ts b/tests/corpus/7512-pipeline-any-stages.ts new file mode 100644 index 000000000..a2039a533 --- /dev/null +++ b/tests/corpus/7512-pipeline-any-stages.ts @@ -0,0 +1,45 @@ +// pipeline() in TypeScript with stages after the first typed any or +// unknown: functions run as function stages, streams are piped, and an +// iterable is taken as Duplex.from of it. +import { pipeline, Readable, Writable, PassThrough } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function sink(label: string): Writable { + return new Writable({ + objectMode: true, + write(chunk: unknown, _enc, cb) { + console.log(label, JSON.stringify(chunk)); + cb(); + }, + }); +} + +async function main(): Promise { + const shout: any = async function* (source: any) { + for await (const chunk of source) yield String(chunk).toUpperCase(); + }; + await pipelineP(Readable.from(["a", "b"]), shout, sink("function")); + + const total: unknown = async (source: any) => { + let sum = 0; + for await (const chunk of source) sum += Number(chunk); + return sum; + }; + const sum = await pipelineP(Readable.from(["1", "2", "3"]), total as any); + console.log("sum", sum); + + const relay: any = new PassThrough({ objectMode: true }); + await pipelineP(Readable.from([{ k: 1 }]), relay, sink("stream")); + + const values: any = ["v1", "v2"]; + await pipelineP(Readable.from(["unread"]), values, sink("iterable")); + + await new Promise((resolve) => { + pipeline(Readable.from(["x"]), shout, sink("callback"), (err: NodeJS.ErrnoException | null) => { + console.log("callback done", err == null); + resolve(); + }); + }); +} + +main().then(() => console.log("done")); diff --git a/tests/corpus/7513-pipeline-untyped-stage-errors.mjs b/tests/corpus/7513-pipeline-untyped-stage-errors.mjs new file mode 100644 index 000000000..ade4370b4 --- /dev/null +++ b/tests/corpus/7513-pipeline-untyped-stage-errors.mjs @@ -0,0 +1,86 @@ +// Untyped pipeline function stages next to typed stages: an untyped stage +// after a typed generator stage or a Transform, the { signal } an untyped +// stage receives aborting when the pipeline fails, and an untyped stage +// that throws while the pipeline is set up. +import { pipeline, Writable, Readable, Transform } from "node:stream"; +import { pipeline as pipelineP } from "node:stream/promises"; + +function pick(v) { + return v; +} +function sink(label) { + return new Writable({ + objectMode: true, + write(chunk, _enc, cb) { + console.log(label, "write", JSON.stringify(String(chunk))); + cb(); + }, + }); +} + +await pipelineP( + Readable.from(["a", "b"]), + async function* (source) { + for await (const chunk of source) yield `${chunk}1`; + }, + pick(async function* (source) { + for await (const chunk of source) yield `${chunk}2`; + }), + sink("after generator"), +); + +await pipelineP( + Readable.from(["q"]), + new Transform({ + transform(chunk, _enc, cb) { + cb(null, chunk); + }, + }), + pick(async (source) => { + for await (const chunk of source) console.log("destination saw", String(chunk)); + }), +); + +let seen; +try { + await pipelineP( + Readable.from(["x", "y"]), + pick(async function* (source, { signal }) { + seen = signal; + signal.addEventListener("abort", () => console.log("signal abort event")); + for await (const chunk of source) yield chunk; + }), + new Writable({ + write(_chunk, _enc, cb) { + cb(new Error("write failed")); + }, + }), + ); +} catch (err) { + console.log("rejected", err.message, seen.aborted); +} + +try { + pipeline( + Readable.from(["t"]), + pick(() => { + throw new Error("stage threw"); + }), + sink("never"), + () => console.log("never called"), + ); +} catch (err) { + console.log("callback form threw", err.message); +} +try { + await pipelineP( + Readable.from(["t"]), + pick(() => { + throw new Error("stage threw again"); + }), + sink("never"), + ); +} catch (err) { + console.log("promise form rejected", err.message); +} +console.log("done"); diff --git a/tests/harness/corpus-costs/7508-pipeline-untyped-source-functions.mjs.json b/tests/harness/corpus-costs/7508-pipeline-untyped-source-functions.mjs.json new file mode 100644 index 000000000..805df1394 --- /dev/null +++ b/tests/harness/corpus-costs/7508-pipeline-untyped-source-functions.mjs.json @@ -0,0 +1 @@ +{ "plain": 4900, "san": 6400 } diff --git a/tests/harness/corpus-costs/7509-pipeline-untyped-function-stages.mjs.json b/tests/harness/corpus-costs/7509-pipeline-untyped-function-stages.mjs.json new file mode 100644 index 000000000..93a0d0f3b --- /dev/null +++ b/tests/harness/corpus-costs/7509-pipeline-untyped-function-stages.mjs.json @@ -0,0 +1 @@ +{ "plain": 6100, "san": 8500 } diff --git a/tests/harness/corpus-costs/7510-pipeline-untyped-later-iterables.mjs.json b/tests/harness/corpus-costs/7510-pipeline-untyped-later-iterables.mjs.json new file mode 100644 index 000000000..e1920ef92 --- /dev/null +++ b/tests/harness/corpus-costs/7510-pipeline-untyped-later-iterables.mjs.json @@ -0,0 +1 @@ +{ "plain": 5100, "san": 8600 } diff --git a/tests/harness/corpus-costs/7511-pipeline-untyped-stages.cjs.json b/tests/harness/corpus-costs/7511-pipeline-untyped-stages.cjs.json new file mode 100644 index 000000000..eafd03265 --- /dev/null +++ b/tests/harness/corpus-costs/7511-pipeline-untyped-stages.cjs.json @@ -0,0 +1 @@ +{ "plain": 5300, "san": 6900 } diff --git a/tests/harness/corpus-costs/7512-pipeline-any-stages.ts.json b/tests/harness/corpus-costs/7512-pipeline-any-stages.ts.json new file mode 100644 index 000000000..ac0e8497d --- /dev/null +++ b/tests/harness/corpus-costs/7512-pipeline-any-stages.ts.json @@ -0,0 +1 @@ +{ "plain": 4500, "san": 6100 } diff --git a/tests/harness/corpus-costs/7513-pipeline-untyped-stage-errors.mjs.json b/tests/harness/corpus-costs/7513-pipeline-untyped-stage-errors.mjs.json new file mode 100644 index 000000000..f363efcf0 --- /dev/null +++ b/tests/harness/corpus-costs/7513-pipeline-untyped-stage-errors.mjs.json @@ -0,0 +1 @@ +{ "plain": 4700, "san": 6300 } From d74cc3b8bdd7e140bc1c81d3341da680d7bb540e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:16:41 -0700 Subject: [PATCH 153/230] Report which type failures localized failure would turn into dynamic slots Stage L0 of the localized-failure design, report-only: no lowering changes. A type with no static lowering poisons the slot that holds it, and the poison rejects the declaration around it (a class, a signature, a declaration statement); each later use then fails on its own line. This records, on the committed diagnostics, where each type failure sits, whether a checked dynamic slot would keep the program's meaning, and which rejected declarations each failing statement used. coverage --explain-dynamic turns that into the would-be dynamic slot list with the cascade each slot clears, and the counts of errors that would disappear through stages L2 and L4. Co-Authored-By: Claude Opus 5.5 --- packages/compiler/src/coverage/report.ts | 60 +++++- .../compiler/src/diagnostics/diagnostic.ts | 58 ++++++ packages/compiler/src/diagnostics/envelope.ts | 21 ++ .../src/frontend/lowering/lower-classes.ts | 9 +- .../src/frontend/lowering/lower-stmts.ts | 41 +++- .../compiler/src/frontend/lowering/lowerer.ts | 187 ++++++++++++++--- .../src/frontend/lowering/type-failure.ts | 195 ++++++++++++++++++ .../compiler/src/frontend/tier-analysis.ts | 183 +++++++++++++++- tests/coverage-fixtures/localized-failure.ts | 53 +++++ .../coverage-explain-localized-failure.txt | 49 +++++ tests/harness/coverage.test.ts | 32 +++ 11 files changed, 842 insertions(+), 46 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/type-failure.ts create mode 100644 tests/coverage-fixtures/localized-failure.ts create mode 100644 tests/harness/__snapshots__/coverage-explain-localized-failure.txt diff --git a/packages/compiler/src/coverage/report.ts b/packages/compiler/src/coverage/report.ts index 58a2cafa6..dabbc9617 100644 --- a/packages/compiler/src/coverage/report.ts +++ b/packages/compiler/src/coverage/report.ts @@ -24,7 +24,11 @@ import { import { renderDiagnostics } from "../diagnostics/render.js"; import type { LowerStats } from "../frontend/lowering/lowerer.js"; import type { ProvenanceSources } from "../frontend/provenance-registry.js"; -import type { DemotionUnit, TierReport } from "../frontend/tier-analysis.js"; +import type { + DemotionUnit, + LocalizedFailureReport, + TierReport, +} from "../frontend/tier-analysis.js"; import type { WasmModuleReport } from "../wasm/discover.js"; import { describe as describeReason, @@ -668,6 +672,26 @@ function tiersEnvelope(tiers: TierReport, locator: SourceLocator): TiersEnvelope ...at(r.loc), })), units: tiers.units.map((u) => ({ ...unit(u), requests: u.requests, sites: u.sites })), + localizedFailure: { + errors: tiers.localizedFailure.errors, + typeFailures: tiers.localizedFailure.typeFailures, + cascades: tiers.localizedFailure.cascades, + clearedThroughL2: tiers.localizedFailure.clearedThroughL2, + clearedThroughL4: tiers.localizedFailure.clearedThroughL4, + remainingByCode: tiers.localizedFailure.remainingByCode, + slots: tiers.localizedFailure.slots.map((slot) => ({ + ...at(slot.loc), + slot: slot.slot, + name: slot.name, + declaredType: slot.declaredType, + family: slot.family, + code: slot.code, + message: slot.message, + stage: slot.stage, + ...(slot.rejects !== null ? { rejects: slot.rejects } : {}), + cascade: slot.cascade, + })), + }, }; } @@ -695,6 +719,39 @@ function unitMatches(u: DemotionUnit, query: string): boolean { ); } +const SLOTS_SHOWN = 40; + +/** The localized-failure section of --explain-dynamic (report-only): the + * committed type failures that would become checked dynamic slots, and + * the cascade each one causes today. */ +function renderLocalizedFailure( + report: LocalizedFailureReport, + out: string[], + c: (code: string, s: string) => string, + where: (loc: { file: string; start: number }) => string, +): void { + if (report.typeFailures.degradable === 0 && report.typeFailures.refused === 0) return; + out.push(""); + out.push( + ` ${report.slots.length} would-be dynamic slot${report.slots.length === 1 ? "" : "s"} ${c(DIM, "(localized failure, report-only: type failures a checked dynamic slot would hold)")}`, + ); + out.push( + ` ${c(DIM, `${report.errors} errors: ${report.clearedThroughL2} clear with declaration slots (L1-L2), ${report.clearedThroughL4} with member and expression slots too (L3-L4); ${report.cascades} cascade${report.cascades === 1 ? "" : "s"} attributed; ${report.typeFailures.refused} type failure${report.typeFailures.refused === 1 ? " stays" : "s stay"} refused`)}`, + ); + for (const slot of report.slots.slice(0, SLOTS_SHOWN)) { + const cascade = slot.cascade > 0 ? `, clears ${slot.cascade}` : ""; + out.push( + ` ${slot.stage} ${slot.slot} ${slot.name}: ${capMessage(slot.declaredType)} ${c(DIM, `${where(slot.loc)} (${slot.code} ${slot.family}${cascade})`)}`, + ); + if (slot.rejects !== null) + out.push(` ${c(DIM, `rejects ${slot.rejects.split(" @")[0]}`)}`); + } + if (report.slots.length > SLOTS_SHOWN) + out.push( + ` ${c(DIM, `${report.slots.length - SLOTS_SHOWN} more (--print=diagnostics lists all)`)}`, + ); +} + /** `scriptc coverage --explain-dynamic[=]`: the dynamic tier today * and the demotion chains the report-only analysis computed. With a query, * only units whose name matches it (`x`, `Point.x`, `parse:x`). */ @@ -735,6 +792,7 @@ export function renderExplainDynamic( (excluded > 0 ? c(DIM, `, ${excluded} excluded by policy`) : ""), ); } + if (query === undefined) renderLocalizedFailure(tiers.localizedFailure, out, c, where); const units = tiers.units.filter((u) => query === undefined || unitMatches(u, query)); if (query !== undefined && units.length === 0) { out.push(` no demoted unit matches '${query}'`); diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 8010cbb17..534e03c07 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -110,6 +110,64 @@ export interface ScrDiagnostic { * and class member body: it throws when the module initializes, which is * program startup for a module the entry imports. */ startup?: true; + /** Localized failure, report-only (stage L0): set where a type with no + * static lowering poisoned a slot (Lowerer.badType and the dyn class + * field policy). Never part of the rendered message. */ + typeFailure?: TypeFailure; + /** Localized failure, report-only: the declaration this diagnostic + * rejected, as a failure-group key. Set when a reference flushes a + * deferred declaration's diagnostics, and on the diagnostics of a + * declaration statement whose bindings were blocked. */ + rejects?: string; + /** Localized failure, report-only: the failure groups (rejected + * declarations, blocked statements) that the statement committing this + * diagnostic used. A cascade clears when every one of them would + * degrade instead of failing. */ + cause?: readonly string[]; +} + +/** Where a type failure sits (localized-failure design §3.6): a + * declaration slot, a member of a declared type, or an expression's own + * type. */ +export type TypeFailureSlot = + | "field" + | "param" + | "return" + | "local" + | "global" + | "loop" + | "member" + | "expression"; + +/** Which badType branch (or policy fence) refused the type. */ +export type TypeFailureFamily = + | "node-types" + | "fetch-ambient" + | "enum-object" + | "stdlib-index" + | "index-signature" + | "package" + | "generic-signature" + | "overloads" + | "intersection" + | "component" + | "stdlib-nominal" + | "record-member" + | "unsupported" + | "dyn-field-policy"; + +/** A type failure's structured payload: what the commit points of the + * localized-failure design inspect (PoisonError.failure carries the same + * object). `degradable` is the §3.7 classification: a checked-dynamic slot + * would keep the program's meaning. */ +export interface TypeFailure { + slot: TypeFailureSlot; + /** The slot's name (`_customTools`, `listAll`, `x`), or the expression + * text for expression slots. */ + name: string; + declaredType: string; + family: TypeFailureFamily; + degradable: boolean; } /** SC3002–SC3004 — native-helper target, installation, and execution diff --git a/packages/compiler/src/diagnostics/envelope.ts b/packages/compiler/src/diagnostics/envelope.ts index bdb545ff3..e64f35824 100644 --- a/packages/compiler/src/diagnostics/envelope.ts +++ b/packages/compiler/src/diagnostics/envelope.ts @@ -114,6 +114,27 @@ export interface TiersEnvelope { message: string; })[]; units: (TierUnitPosition & { requests: number; sites: number })[]; + /** Localized failure (report-only): committed type failures that would + * become checked dynamic slots, and what they would clear. */ + localizedFailure: { + errors: number; + typeFailures: { degradable: number; refused: number }; + cascades: number; + clearedThroughL2: number; + clearedThroughL4: number; + remainingByCode: Record; + slots: (SourcePosition & { + slot: string; + name: string; + declaredType: string; + family: string; + code: string; + message: string; + stage: string; + rejects?: string; + cascade: number; + })[]; + }; } export interface DiagnosticsEnvelope { diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index d941d7b60..2c0145c34 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -2542,8 +2542,7 @@ export function collectClassShapeInner( (member.type !== undefined || (lowerer.typeOf(member.name).flags & ts.TypeFlags.Any) === 0) ) { - lowerer.unsupported( - "SC1090", + lowerer.dynFieldPolicyFence( member.name, `${dynFieldTypeSpelling(lowerer, member.name)}-typed static fields`, ); @@ -3059,8 +3058,7 @@ export function collectClassShapeInner( !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) ) { - lowerer.unsupported( - "SC1090", + lowerer.dynFieldPolicyFence( member.name, `${dynFieldTypeSpelling(lowerer, member.name)}-typed class fields`, ); @@ -3197,8 +3195,7 @@ export function collectClassShapeInner( if (type.kind === "void") lowerer.badType(p.name, lowerer.typeOf(p.name)); // The class-field dyn rule verbatim (KEEP NARROW). if (type.kind === "dyn" && !lowerer.classFieldStoresDyn(lowerer.typeOf(p))) { - lowerer.unsupported( - "SC1090", + lowerer.dynFieldPolicyFence( p.name, `${dynFieldTypeSpelling(lowerer, p)}-typed class fields`, ); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 84196476a..4cf3a342c 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -41,6 +41,8 @@ import { isTimeValueDateDeclaration } from "./date-values.js"; import { bindingSource } from "../binding-source.js"; import { lowerConstructorReturn } from "./constructor-returns.js"; import type { Lowerer } from "./lowerer.js"; +import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; +import { statementGroupKey } from "./type-failure.js"; import { cjsExportValueByName, cjsNamespaceDestructureOf, @@ -750,6 +752,8 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir if (!lowerer.suppressStats) lowerer.countStatement(stmt, sf?.fileName); const diagsBefore = lowerer.diags.length; const sinkMark = lowerer.diagSinkMark(); + const touched = new Set(); + lowerer.failureTouches.push(touched); try { let lowered: IrStmt | IrStmt[] | null; let appended = false; @@ -813,7 +817,17 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir throw e; } if (!lowerer.suppressStats) lowerer.countStatementFailed(stmt, sf?.fileName); - lowerer.noteBlockedBindings(stmt); + // Localized failure (report-only): the statement's committed + // diagnostics that are not themselves type failures or rejected + // declarations are cascades of the rejected declarations it used. + const committed = + lowerer.diagSink === null && !deferFences ? lowerer.diags.slice(diagsBefore) : []; + if (touched.size > 0) { + const cause = [...touched]; + for (const d of committed) + if (d.typeFailure === undefined && d.rejects === undefined) d.cause ??= cause; + } + lowerer.noteBlockedBindings(stmt, committed); // The runtime-fence conversion (JS sources, direct diagnostics // only — a capture wrapper in force means collection owns the // report). The statement's diagnostics move OFF the build and @@ -837,6 +851,8 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir const fence = lowerer.capturedRuntimeFence(sinkMark, stmt); if (fence) out.push(fence); } + } finally { + lowerer.failureTouches.pop(); } } // Each acquisition protects the remainder of its lexical statement @@ -1522,7 +1538,11 @@ function noteEarlyReadVar(lowerer: Lowerer, symbol: ts.Symbol, local: IrLocal): * lowerVarDecl's salvage path declares the local when it can, and * resolution checks locals/globals before the fallthroughs consult * blockedBindings. */ -export function noteBlockedBindings(lowerer: Lowerer, stmt: ts.Statement): void { +export function noteBlockedBindings( + lowerer: Lowerer, + stmt: ts.Statement, + diags: readonly ScrDiagnostic[] = [], +): void { let list: ts.VariableDeclarationList | null = null; if (ts.isVariableStatement(stmt)) list = stmt.declarationList; else if ( @@ -1533,12 +1553,20 @@ export function noteBlockedBindings(lowerer: Lowerer, stmt: ts.Statement): void list = stmt.initializer; } if (!list) return; + // Localized failure (report-only): the bindings form one failure group, + // the statement, whose diagnostics are the group's. + const group = statementGroupKey(stmt); + let bound = false; for (const decl of list.declarations.nodes) { for (const nameNode of boundIdentifiersOf(decl.name)) { const symbol = lowerer.checker.getSymbolAtLocation(nameNode); - if (symbol) lowerer.blockedBindings.add(symbol); + if (!symbol) continue; + lowerer.blockedBindings.add(symbol); + if (!lowerer.rejectedGroups.has(symbol)) lowerer.rejectedGroups.set(symbol, group); + bound = true; } } + if (bound) for (const d of diags) d.rejects ??= group; } /** True when a reference/assignment fell through resolution because the @@ -1548,12 +1576,13 @@ export function noteBlockedBindings(lowerer: Lowerer, stmt: ts.Statement): void * flushed by the emit pass). */ export function isBlockedBinding(lowerer: Lowerer, symbol: ts.Symbol | null): boolean { if (!symbol) return false; - return ( + const blocked = lowerer.blockedBindings.has(symbol) || lowerer.deferredDiags.has(symbol) || lowerer.flushedSymbols.has(symbol) || - lowerer.alreadyFlushed.has(symbol) - ); + lowerer.alreadyFlushed.has(symbol); + if (blocked) lowerer.touchRejected(symbol); + return blocked; } /** Lowers a statement in a fresh lexical scope (if/while/for bodies). */ diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 634c361c2..65b66c298 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -204,7 +204,12 @@ import { isBundledShimForwarder } from "../package-shims.js"; import { erasedTypeOnlyImport } from "../type-only-imports.js"; import { literalValues } from "../literal-values.js"; import { discriminantOwners, literalUnionArm, type UnionLiteral } from "../union-discriminants.js"; -import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; +import type { + ScrDiagnostic, + TypeFailure, + TypeFailureFamily, +} from "../../diagnostics/diagnostic.js"; +import { declarationGroupKey, typeFailureOf } from "./type-failure.js"; import { unsupportedAnyOperationDiag, blockedBindingUseDiag, @@ -713,7 +718,16 @@ interface GenericDemandOwner { classDemands: ClassInfo[]; } -export class PoisonError extends Error {} +export class PoisonError extends Error { + /** Set when a type with no static lowering poisoned a slot (badType and + * the dyn class field policy): what failed, for the localized-failure + * commit points. Report-only in stage L0. */ + readonly failure: TypeFailure | undefined; + constructor(failure?: TypeFailure) { + super(); + this.failure = failure; + } +} /** A function-shaped value that exists only in the compiler's static * projection graph. Declared-function aliases point at the original @@ -3697,6 +3711,15 @@ export class Lowerer { * the fallthroughs report the inherited-blocker cascade (SC2004) * instead of misattributing the reference. */ readonly blockedBindings = new Set(); + /** Localized failure, report-only: the failure group (type-failure.ts) + * of each declaration whose rejection reached the build — a flushed + * deferred declaration, or a binding of a poisoned declaration + * statement. */ + readonly rejectedGroups = new Map(); + /** One frame per statement lowerStmts is lowering: the failure groups + * its lowering touched. A statement that then poisons attributes its + * diagnostics to them (ScrDiagnostic.cause). */ + readonly failureTouches: Set[] = []; /** True while collectProgram runs: resolution helpers must not flush * deferred diagnostics (collection itself resolves symbols — extends * clauses — and collection order must not decide what reports). */ @@ -9177,7 +9200,11 @@ export class Lowerer { this.diagSink.push(d); return; } - if (pending) this.commitFlush(pending.symbol, pending.source); + if (pending) { + this.commitFlush(pending.symbol, pending.source); + const group = this.rejectedGroups.get(pending.symbol); + if (group !== undefined) d.rejects ??= group; + } // One site, one report: some declarations map a type twice (module // globals pre-register before their initializers lower) — an exact // duplicate (code + span + message) adds noise, not information. @@ -9232,6 +9259,35 @@ export class Lowerer { return (this.typeOf(node).flags & ts.TypeFlags.Any) !== 0; } + /** badType's commit: the diagnostic carries the structured type + * failure (report-only in stage L0), and so does the poison. */ + private typeFail( + node: ts.Node, + type: ts.Type, + family: TypeFailureFamily, + diag: ScrDiagnostic, + ): never { + const failure = typeFailureOf(node, this.checker.typeToString(type), family); + diag.typeFailure = failure; + this.pushDiag(diag); + throw new PoisonError(failure); + } + + /** The SC1090 refusal of checked-dynamic class fields in TypeScript + * (classFieldStoresDyn): a policy fence, not a missing representation, + * so its failure is recorded as degradable. */ + dynFieldPolicyFence(node: ts.Node, feature: string): never { + const diag = unsupportedDiag("SC1090", locOf(node), feature); + const failure = typeFailureOf( + node, + this.checker.typeToString(this.typeOf(node)), + "dyn-field-policy", + ); + diag.typeFailure = failure; + this.pushDiag(diag); + throw new PoisonError(failure); + } + badType(node: ts.Node, type: ts.Type): never { const widened = this.checker.getBaseTypeOfLiteralType(type); // Types declared by the ADOPTED @types/node (Buffer, NodeJS.Timeout, @@ -9243,8 +9299,12 @@ export class Lowerer { // applies to node-typed values. const typeSym = widened.getAliasSymbol() ?? widened.getSymbol(); if (this.nodeTypesOnlySymbol(typeSym)) { - this.pushDiag(noLoweringDiag(this.checker.typeToString(type), locOf(node), undefined, true)); - throw new PoisonError(); + this.typeFail( + node, + type, + "node-types", + noLoweringDiag(this.checker.typeToString(type), locOf(node), undefined, true), + ); } // Engine-backed ambient TYPES in a STATIC build report the per-site // SC2012 rather than the generic supported-types recitation. Native @@ -9255,10 +9315,12 @@ export class Lowerer { (FETCH_AMBIENT_TYPES as readonly string[]).includes(ts.symbolName(typeSym)) && this.isStdlibSymbol(typeSym) ) { - this.pushDiag( + this.typeFail( + node, + type, + "fetch-ambient", unsupportedNativeApiDiag(`a value of type '${ts.symbolName(typeSym)}'`, locOf(node)), ); - throw new PoisonError(); } // ENUM OBJECTS (`typeof e` — a numeric enum's type carries the // reverse-map index signature, a string enum's just its members): @@ -9267,14 +9329,16 @@ export class Lowerer { // as the per-site identifier fence — never the index-signature // recitation. if (typeSym && (typeSym.flags & ts.SymbolFlags.Enum) !== 0) { - this.pushDiag( + this.typeFail( + node, + type, + "enum-object", unsupportedDiag( "SC1090", locOf(node), `enum objects as values ('${ts.symbolName(typeSym)}' — member reads like '${ts.symbolName(typeSym)}.X' compile to constants; the object itself has no runtime representation)`, ), ); - throw new PoisonError(); } // Would-be records with INDEX SIGNATURES (`Record`, // `{ [k: string]: T }`) get the index-signature fence (SC2006) naming @@ -9304,19 +9368,26 @@ export class Lowerer { this.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { - this.pushDiag(noLoweringDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "stdlib-index", + noLoweringDiag(this.checker.typeToString(type), locOf(node)), + ); } - this.pushDiag(indexSignatureTypeDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "index-signature", + indexSignatureTypeDiag(this.checker.typeToString(type), locOf(node)), + ); } // Attribute an unsupported package type to its package. { const pkg = this.npmPackageOf(widened); if (pkg) { - this.pushDiag(unsupportedPackageValueDiag(pkg, locOf(node))); - throw new PoisonError(); + this.typeFail(node, type, "package", unsupportedPackageValueDiag(pkg, locOf(node))); } } // A type that keeps a GENERIC call signature (`(x: T) => T` slots, @@ -9332,8 +9403,12 @@ export class Lowerer { this.checker.getCallSignatures(p).some((s) => s.getTypeParameters().length > 0), ) ) { - this.pushDiag(genericSignatureTypeDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "generic-signature", + genericSignatureTypeDiag(this.checker.typeToString(type), locOf(node)), + ); } } // STANDARD-LIBRARY nominal provenance, decided once: interface/class @@ -9353,16 +9428,24 @@ export class Lowerer { // generic overload set keeps the monomorphization story: a value of a // multi-signature type has no single compiled signature to hold. if (!stdlibNominal && this.checker.getCallSignatures(widened).length > 1) { - this.pushDiag(overloadedSignatureTypeDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "overloads", + overloadedSignatureTypeDiag(this.checker.typeToString(type), locOf(node)), + ); } // INTERSECTIONS that resolved to no lowering (SC2008). The ones that // compile never get here: member intersections intern through the // record path, callable hybrids map to '%call' records, and pinned // mixin instantiations resolve by chain structure. if (widened.isIntersectionType()) { - this.pushDiag(intersectionTypeDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "intersection", + intersectionTypeDiag(this.checker.typeToString(type), locOf(node)), + ); } // SUPPORTED shapes over a component outside its slot (SC2009): the // Map/Set domains, array/tuple elements, union arms, function @@ -9374,8 +9457,12 @@ export class Lowerer { { const detail = describeComponentBlocker(widened, this.typeCtx); if (detail !== null) { - this.pushDiag(componentTypeDiag(this.checker.typeToString(type), detail, locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "component", + componentTypeDiag(this.checker.typeToString(type), detail, locOf(node)), + ); } } // STANDARD-LIBRARY nominal types with no lowering at all: the SC2020 @@ -9405,26 +9492,36 @@ export class Lowerer { TextDecoder: "TextDecoder instances support recognized labels, fatal/ignoreBOM option literals, and streaming UTF-8/UTF-16/single-byte decode; runtime option objects remain unsupported", }; - this.pushDiag( + this.typeFail( + node, + type, + "stdlib-nominal", noLoweringDiag( this.checker.typeToString(type), locOf(node), typeHints[ts.symbolNameOf(stdlibOwnSym) ?? ""], ), ); - throw new PoisonError(); } // USER record shapes blocked by ONE member (SC2009's record arm): // name the member and its type. { const detail = describeRecordMemberBlocker(widened, this.typeCtx); if (detail !== null) { - this.pushDiag(componentTypeDiag(this.checker.typeToString(type), detail, locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "record-member", + componentTypeDiag(this.checker.typeToString(type), detail, locOf(node)), + ); } } - this.pushDiag(unsupportedTypeDiag(this.checker.typeToString(type), locOf(node))); - throw new PoisonError(); + this.typeFail( + node, + type, + "unsupported", + unsupportedTypeDiag(this.checker.typeToString(type), locOf(node)), + ); } /** The lib fence (SC2020): a reached use of standard-library surface @@ -12067,12 +12164,35 @@ export class Lowerer { this.reportDeferred(symbol); } + /** A statement's lowering used a declaration: if that declaration was + * rejected, the statement's own failure (if any) is its cascade. */ + touchRejected(symbol: ts.Symbol): void { + const group = this.rejectedGroups.get(symbol); + if (group === undefined) return; + const frame = this.failureTouches[this.failureTouches.length - 1]; + if (frame !== undefined) frame.add(group); + } + + /** Records a declaration whose deferred diagnostics reached the build: + * its failure group, on the group's diagnostics too. */ + private noteRejected(symbol: ts.Symbol, diags: readonly ScrDiagnostic[]): void { + let group = this.rejectedGroups.get(symbol); + if (group === undefined) { + group = declarationGroupKey(symbol); + this.rejectedGroups.set(symbol, group); + } + for (const d of diags) d.rejects ??= group; + } + /** flushDeferred without the collection-pass guard: for declarations * whose diagnostics must report eagerly (class statements with * declaration-time code, extended bases — collectClassShapeDeferring). */ reportDeferred(symbol: ts.Symbol): void { const diags = this.deferredDiags.get(symbol); - if (!diags) return; + if (!diags) { + this.touchRejected(symbol); + return; + } if (this.alreadyFlushed.has(symbol)) { this.deferredDiags.delete(symbol); // the emit pass reported these return; @@ -12080,7 +12200,9 @@ export class Lowerer { if (this.diagSink === null) { this.deferredDiags.delete(symbol); this.flushedSymbols.add(symbol); + this.noteRejected(symbol, diags); for (const d of diags) this.pushDiag(d); + this.touchRejected(symbol); return; } // A capture buffer is active (a probe, or another declaration's @@ -12097,6 +12219,7 @@ export class Lowerer { * (the declaration counts as flushed for the coverage remainder). */ private commitFlush(symbol: ts.Symbol, source: ScrDiagnostic): void { this.flushedSymbols.add(symbol); + this.noteRejected(symbol, [source]); const list = this.deferredDiags.get(symbol); if (!list) return; const index = list.indexOf(source); @@ -13222,8 +13345,8 @@ export class Lowerer { return lowerStmts(this, stmts); } - noteBlockedBindings(stmt: ts.Statement): void { - return noteBlockedBindings(this, stmt); + noteBlockedBindings(stmt: ts.Statement, diags?: readonly ScrDiagnostic[]): void { + return noteBlockedBindings(this, stmt, diags); } isBlockedBinding(symbol: ts.Symbol | null): boolean { diff --git a/packages/compiler/src/frontend/lowering/type-failure.ts b/packages/compiler/src/frontend/lowering/type-failure.ts new file mode 100644 index 000000000..3502a9bbf --- /dev/null +++ b/packages/compiler/src/frontend/lowering/type-failure.ts @@ -0,0 +1,195 @@ +/* Localized failure, stage L0 (report-only). + * + * A type with no static lowering poisons the slot that holds it (a field, + * a parameter, a return, a local), and today the poison rejects the whole + * declaration around the slot: a class, a signature, a declaration + * statement. Every later use of that declaration then fails on its own + * line. The localized-failure design turns such slots into checked + * dynamic slots at the points where the build would otherwise commit the + * error. + * + * This module holds the bookkeeping that stage measures without changing + * any lowering: where a failing type sits (its slot), whether a dynamic + * slot would keep the program's meaning (design §3.7), and the keys that + * tie a cascade diagnostic to the rejected declaration it inherits from. + * The data rides the committed diagnostics as report-only fields + * (ScrDiagnostic.typeFailure, .rejects, .cause); tier-analysis.ts turns it + * into the would-be dynamic slot report of `--explain-dynamic`. */ +import type { + TypeFailure, + TypeFailureFamily, + TypeFailureSlot, +} from "../../diagnostics/diagnostic.js"; +import * as ts from "../ts7/adapter.js"; + +/** Families a checked-dynamic slot can hold (design §3.7). The others + * name values the compiler cannot produce (opaque stdlib and @types/node + * types, engine-backed ambient types), package value types that scope B + * routes to Node islands, enum objects with no runtime value, and generic + * implementations without an erased instance. */ +const DEGRADABLE_FAMILIES: ReadonlySet = new Set([ + "index-signature", + "overloads", + "intersection", + "component", + "record-member", + "unsupported", + "dyn-field-policy", +]); + +function nameText(name: ts.Node | undefined): string | null { + if (name === undefined) return null; + if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name)) return name.text; + if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text; + return ts.getTextOfNode(name); +} + +function isParameterProperty(param: ts.ParameterDeclaration): boolean { + return ( + ts.isConstructorDeclaration(param.parent) && + (param.modifiers?.nodes ?? []).some( + (m) => + m.kind === ts.SyntaxKind.PublicKeyword || + m.kind === ts.SyntaxKind.PrivateKeyword || + m.kind === ts.SyntaxKind.ProtectedKeyword || + m.kind === ts.SyntaxKind.ReadonlyKeyword, + ) + ); +} + +function variableSlot(decl: ts.VariableDeclaration): TypeFailureSlot { + const list = decl.parent; + const holder = list?.parent; + if ( + holder !== undefined && + (ts.isForOfStatement(holder) || ts.isForInStatement(holder) || ts.isForStatement(holder)) + ) + return "loop"; + if (holder !== undefined && ts.isVariableStatement(holder) && ts.isSourceFile(holder.parent)) + return "global"; + return "local"; +} + +function functionLabel(fn: ts.Node): string { + if (ts.isConstructorDeclaration(fn)) return "constructor"; + if (ts.isFunctionLikeDeclaration(fn)) { + const own = nameText(fn.name); + if (own !== null) return own; + const parent = fn.parent; + if ( + parent !== undefined && + (ts.isVariableDeclaration(parent) || + ts.isPropertyAssignment(parent) || + ts.isPropertyDeclaration(parent)) + ) { + const bound = nameText(parent.name); + if (bound !== null) return bound; + } + } + return ""; +} + +/** The slot a badType blame node denotes. A declaration's own node, its + * name and its type annotation name the declaration's slot; anything + * reached through an expression is an expression slot (the type of a + * cast, a literal, a temporary). */ +export function typeFailureSlot(node: ts.Node): { slot: TypeFailureSlot; name: string } { + let current: ts.Node = node; + for (let depth = 0; depth < 64; depth++) { + if (ts.isParameter(current)) { + const name = nameText(current.name) ?? ""; + return { slot: isParameterProperty(current) ? "field" : "param", name }; + } + if (ts.isPropertyDeclaration(current)) + return { slot: "field", name: nameText(current.name) ?? "" }; + if (ts.isVariableDeclaration(current)) + return { slot: variableSlot(current), name: nameText(current.name) ?? "" }; + if (ts.isBindingElement(current)) { + let owner: ts.Node | undefined = current.parent; + while (owner !== undefined && !ts.isVariableDeclaration(owner) && !ts.isParameter(owner)) + owner = owner.parent; + const slot: TypeFailureSlot = + owner === undefined + ? "local" + : ts.isParameter(owner) + ? "param" + : variableSlot(owner as ts.VariableDeclaration); + return { slot, name: nameText(current.name) ?? "" }; + } + if (ts.isPropertySignature(current) || ts.isMethodSignatureDeclaration(current)) + return { slot: "member", name: nameText(current.name) ?? "" }; + if (ts.isFunctionLikeDeclaration(current)) + return { slot: "return", name: functionLabel(current) }; + if (ts.isCatchClause(current)) return { slot: "local", name: "" }; + const parent: ts.Node | undefined = current.parent; + if (parent === undefined || ts.isSourceFile(parent)) break; + // Climb from a declaration's name or type annotation, or from inside a + // type node; any other position is an expression's own type. + const declared = + (ts.isParameter(parent) || + ts.isPropertyDeclaration(parent) || + ts.isVariableDeclaration(parent) || + ts.isBindingElement(parent) || + ts.isPropertySignature(parent) || + ts.isMethodSignatureDeclaration(parent) || + ts.isFunctionLikeDeclaration(parent)) && + ((parent as { name?: ts.Node }).name === current || + (parent as { type?: ts.Node }).type === current); + if (!declared && !ts.isTypeNode(current) && !ts.isTypeNode(parent)) break; + current = parent; + } + const text = ts.getTextOfNode(node); + return { slot: "expression", name: text.length > 60 ? `${text.slice(0, 57)}...` : text }; +} + +/** Program code outside node_modules, in TypeScript: the code holes apply + * to (design §3.3, §3.8). JavaScript keeps its dynamic fallback and its + * runtime fences; npm packages keep island routing. */ +function programTypeScript(node: ts.Node): boolean { + const sf = ts.getSourceFileOfNode(node); + const file = sf.fileName; + if (file.includes("/node_modules/")) return false; + if (sf.isDeclarationFile) return false; + return !/\.(?:[cm]?js|jsx)$/.test(file); +} + +export function typeFailureOf( + node: ts.Node, + declaredType: string, + family: TypeFailureFamily, +): TypeFailure { + const { slot, name } = typeFailureSlot(node); + return { + slot, + name, + declaredType, + family, + degradable: DEGRADABLE_FAMILIES.has(family) && programTypeScript(node), + }; +} + +function locKey(node: ts.Node): string { + const sf = ts.getSourceFileOfNode(node); + return `${sf.fileName}:${ts.tokenStartOfNode(node)}`; +} + +/** The failure-group key of a deferred declaration (a class shape or a + * function signature whose collection poisoned). */ +export function declarationGroupKey(symbol: ts.Symbol): string { + const decl = symbol.declarations[0]; + const name = ts.symbolName(symbol); + if (decl === undefined) return `declaration ${name}`; + const kind = + ts.isClassDeclaration(decl) || ts.isClassExpression(decl) + ? "class" + : ts.isFunctionLikeDeclaration(decl) + ? "function" + : "declaration"; + return `${kind} ${name} @${locKey(decl)}`; +} + +/** The failure-group key of a poisoned declaration statement whose + * bindings are blocked. */ +export function statementGroupKey(stmt: ts.Statement): string { + return `statement @${locKey(stmt)}`; +} diff --git a/packages/compiler/src/frontend/tier-analysis.ts b/packages/compiler/src/frontend/tier-analysis.ts index b75e491a8..0baedd7dd 100644 --- a/packages/compiler/src/frontend/tier-analysis.ts +++ b/packages/compiler/src/frontend/tier-analysis.ts @@ -29,7 +29,12 @@ * Attribution is syntactic and approximate: the site's innermost node, the * operand it acts on, and that operand's declaration. Stage S6 replaces it * with requests the failing lowering names exactly. */ -import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; +import type { + ScrDiagnostic, + TypeFailureFamily, + TypeFailureSlot, +} from "../diagnostics/diagnostic.js"; +import { isWarningCode } from "../diagnostics/render.js"; import type { IrFunction, SrcLoc } from "../ir/ir.js"; import { dynamicOperationSites, type DynamicFunction } from "../ir/tiers.js"; import * as ts from "./ts7/adapter.js"; @@ -85,8 +90,70 @@ export interface TierDynamicFunction extends DynamicFunction { statements: number; } +/** The localized-failure stage whose slot positions include a slot kind + * (design §3.6): locals and globals degrade first, declarations next, + * members of declared types with push-down, expression types last. */ +export type LocalizedFailureStage = "L1" | "L2" | "L3" | "L4"; + +export function slotStage(slot: TypeFailureSlot): LocalizedFailureStage { + switch (slot) { + case "local": + case "global": + case "loop": + return "L1"; + case "field": + case "param": + case "return": + return "L2"; + case "member": + return "L3"; + case "expression": + return "L4"; + } +} + +/** A committed type failure that would become a checked dynamic slot + * (SC6104) instead of an error. */ +export interface WouldBeSlot { + loc: SrcLoc; + slot: TypeFailureSlot; + name: string; + declaredType: string; + family: TypeFailureFamily; + code: string; + message: string; + stage: LocalizedFailureStage; + /** The declaration the failure rejects today (a failure-group key), if + * any: a class, a signature, or a declaration statement. */ + rejects: string | null; + /** Other committed diagnostics that inherit from this slot's group and + * clear once every group they inherit from degrades (with push-down and + * expression slots, the full design). */ + cascade: number; +} + +/** Localized failure, report-only (stage L0): what the committed errors + * would become. Two scenarios: through stage L2 (declaration slots) and + * through L4 (members and expressions too). */ +export interface LocalizedFailureReport { + /** Reached errors (warnings excluded). */ + errors: number; + /** Type failures, by whether a checked dynamic slot keeps meaning. */ + typeFailures: { degradable: number; refused: number }; + slots: WouldBeSlot[]; + /** Diagnostics attributed to a rejected declaration they used. */ + cascades: number; + /** Errors that disappear (degraded slots plus cleared cascades). */ + clearedThroughL2: number; + clearedThroughL4: number; + /** The errors that remain through L4, by code. */ + remainingByCode: Record; +} + export interface TierReport { mode: TierMode; + /** Localized failure (report-only). */ + localizedFailure: LocalizedFailureReport; /** Reached source statements that perform a dynamic operation. */ statementsDynamic: number; dynamicFunctions: TierDynamicFunction[]; @@ -576,6 +643,119 @@ function demotedUnits(requests: readonly DemotionRequest[]): DemotedUnit[] { ); } +/* ── localized failure ───────────────────────────────────────────────── */ + +/** A diagnostic's failure-group bookkeeping (lowerer: badType, deferred + * flushes, blocked statements) turned into the would-be report. A group + * (a rejected declaration or blocked statement) clears in a scenario when + * each of its diagnostics either degrades (a degradable type failure at a + * slot the scenario covers) or is a cascade whose every cause clears. */ +export function localizedFailureReport(reached: readonly ScrDiagnostic[]): LocalizedFailureReport { + const errors = reached.filter((d) => !isWarningCode(d.code)); + const groups = new Map(); + for (const d of errors) { + if (d.rejects === undefined) continue; + const list = groups.get(d.rejects); + if (list === undefined) groups.set(d.rejects, [d]); + else list.push(d); + } + const stages: Record<"L2" | "L4", ReadonlySet> = { + L2: new Set(["L1", "L2"]), + L4: new Set(["L1", "L2", "L3", "L4"]), + }; + const clears = (scenario: "L2" | "L4") => { + const covered = stages[scenario]; + const memo = new Map(); + const degrades = (d: ScrDiagnostic): boolean => + d.typeFailure !== undefined && + d.typeFailure.degradable && + covered.has(slotStage(d.typeFailure.slot)); + const causeClears = (d: ScrDiagnostic): boolean => + d.cause !== undefined && d.cause.length > 0 && d.cause.every((g) => groupClears(g)); + const groupClears = (group: string): boolean => { + const known = memo.get(group); + if (known !== undefined) return known; + memo.set(group, false); // a cycle never clears + const members = groups.get(group) ?? []; + const ok = members.length > 0 && members.every((d) => degrades(d) || causeClears(d)); + memo.set(group, ok); + return ok; + }; + return { degrades, causeClears, groupClears }; + }; + const l2 = clears("L2"); + const l4 = clears("L4"); + const cleared = (c: ReturnType, d: ScrDiagnostic) => + c.degrades(d) || c.causeClears(d); + // Each slot's cascade: diagnostics whose causes (transitively) include + // the slot's group and that clear in the full scenario. + const reachesGroup = (d: ScrDiagnostic, group: string, seen: Set): boolean => { + for (const g of d.cause ?? []) { + if (g === group) return true; + if (seen.has(g)) continue; + seen.add(g); + for (const m of groups.get(g) ?? []) if (reachesGroup(m, group, seen)) return true; + } + return false; + }; + const slots: WouldBeSlot[] = []; + let degradable = 0; + let refused = 0; + for (const d of errors) { + const f = d.typeFailure; + if (f === undefined) continue; + if (!f.degradable) { + refused++; + continue; + } + degradable++; + const group = d.rejects ?? null; + let cascade = 0; + if (group !== null) + for (const e of errors) + if ( + e !== d && + e.typeFailure === undefined && + cleared(l4, e) && + reachesGroup(e, group, new Set()) + ) + cascade++; + slots.push({ + loc: d.loc, + slot: f.slot, + name: f.name, + declaredType: f.declaredType, + family: f.family, + code: d.code, + message: d.message, + stage: slotStage(f.slot), + rejects: group, + cascade, + }); + } + slots.sort( + (a, b) => + b.cascade - a.cascade || a.loc.file.localeCompare(b.loc.file) || a.loc.start - b.loc.start, + ); + const remainingByCode: Record = {}; + let clearedThroughL2 = 0; + let clearedThroughL4 = 0; + for (const d of errors) { + if (cleared(l2, d)) clearedThroughL2++; + if (cleared(l4, d)) clearedThroughL4++; + else remainingByCode[d.code] = (remainingByCode[d.code] ?? 0) + 1; + } + return { + errors: errors.length, + typeFailures: { degradable, refused }, + slots, + cascades: errors.filter((d) => d.cause !== undefined && d.typeFailure === undefined).length, + clearedThroughL2, + clearedThroughL4, + remainingByCode, + }; +} + export interface TierAnalysisInput { mode: TierMode; /** The dynamic-tier IR functions of the reached program. */ @@ -624,6 +804,7 @@ export function analyzeTiers(program: ts.Program, input: TierAnalysisInput): Tie const dynamic = dynamicFunctionReports(program, input.dynamicFunctions); return { mode: input.mode, + localizedFailure: localizedFailureReport(input.reached), statementsDynamic: dynamic.statements, dynamicFunctions: dynamic.functions, requests, diff --git a/tests/coverage-fixtures/localized-failure.ts b/tests/coverage-fixtures/localized-failure.ts new file mode 100644 index 000000000..ed4d30e8c --- /dev/null +++ b/tests/coverage-fixtures/localized-failure.ts @@ -0,0 +1,53 @@ +// The localized-failure report of `scriptc coverage --explain-dynamic`: +// type failures that would become checked dynamic slots, and the uses +// each one rejects today. +interface Tool { + name: string; + execute(id: string, params: any): Promise; +} +type Progress = (done: number, total: number) => void; +type Callbacks = Progress | AbortSignal | Map string> | Set; + +// A field: rejects the class, so every use of an instance fails. +class Session { + private tools: Tool[] = []; + count(): number { + return this.tools.length; + } + label(): string { + return "s"; + } +} + +// A parameter: rejects the function, so every call fails. +function listAll(dir: string, progress?: Callbacks): number { + return dir.length; +} + +// The dyn class field policy. +class Holder { + state: any = {}; + get(): number { + return 1; + } +} + +// A local: rejects its declaration statement, so its uses fail. +function pick(n: number): boolean { + const callbacks: Callbacks | undefined = n > 1 ? new Set() : undefined; + return callbacks === undefined; +} + +// Refused: a value the compiler cannot produce stays an error. +class Cache { + tokens: WeakRef = new WeakRef({}); + size(): number { + return 0; + } +} + +const s = new Session(); +console.log(s.count(), s.label()); +console.log(listAll("abc")); +console.log(new Holder().get()); +console.log(pick(1), new Cache().size()); diff --git a/tests/harness/__snapshots__/coverage-explain-localized-failure.txt b/tests/harness/__snapshots__/coverage-explain-localized-failure.txt new file mode 100644 index 000000000..ed727b604 --- /dev/null +++ b/tests/harness/__snapshots__/coverage-explain-localized-failure.txt @@ -0,0 +1,49 @@ + dynamic tier (tier assignment, report-only; --tier=auto) + 0 functions already operate on dynamic values (0 statements) + + 11 demotion requests (refusals and fences the dynamic tier would replace) + + 4 would-be dynamic slots (localized failure, report-only: type failures a checked dynamic slot would hold) + 11 errors: 9 clear with declaration slots (L1-L2), 9 with member and expression slots too (L3-L4); 6 cascades attributed; 1 type failure stays refused + L2 field tools: Tool[] tests/coverage-fixtures/localized-failure.ts:13:11 (SC2009 component, clears 2) + rejects class Session + L2 param progress: Callbacks | undefined tests/coverage-fixtures/localized-failure.ts:23:31 (SC2009 component, clears 1) + rejects function listAll + L2 field state: any tests/coverage-fixtures/localized-failure.ts:29:3 (SC1090 dyn-field-policy, clears 1) + rejects class Holder + L1 local callbacks: Callbacks | undefined tests/coverage-fixtures/localized-failure.ts:37:9 (SC2009 component, clears 1) + rejects statement + class Session tests/coverage-fixtures/localized-failure.ts:12:1 (1 request) + <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:49:11: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + -> function turns dynamic + class Holder tests/coverage-fixtures/localized-failure.ts:28:1 (1 request) + <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:52:13: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + -> function turns dynamic + class Cache tests/coverage-fixtures/localized-failure.ts:42:1 (1 request) + <- SC1090 reflection at tests/coverage-fixtures/localized-failure.ts:53:22: constructing through a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic) is not supported yet + -> function turns dynamic + field Session.tools tests/coverage-fixtures/localized-failure.ts:13:3 (1 request) + <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:13:11: values of type 'Tool[]' cannot be compiled: the array shape is supported, but its element type 'Tool' does not compile + -> function turns dynamic + field Holder.state tests/coverage-fixtures/localized-failure.ts:29:3 (1 request) + <- SC1090 operation at tests/coverage-fixtures/localized-failure.ts:29:3: 'any'-typed class fields are not supported yet + -> function turns dynamic + field Cache.tokens tests/coverage-fixtures/localized-failure.ts:43:3 (1 request) + <- SC2020 operation at tests/coverage-fixtures/localized-failure.ts:43:3: 'WeakRef' is part of the standard library types but has no scriptc lowering yet + -> function turns dynamic + function tests/coverage-fixtures/localized-failure.ts:1:1 (8 sites inside) + function listAll tests/coverage-fixtures/localized-failure.ts:23:1 (1 request, 1 site inside) + <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:51:13: uses of 'listAll' inherit the blocker on its declaration + -> function turns dynamic + function pick tests/coverage-fixtures/localized-failure.ts:36:1 (2 sites inside) + binding progress in listAll tests/coverage-fixtures/localized-failure.ts:23:31 (1 request) + <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:23:31: values of type 'Callbacks | undefined' cannot be compiled: the union shape is supported, and unions in the 'string | object' family ride the checked-dynamic representation wholesale, but 'Map function listAll turns dynamic + binding callbacks in pick tests/coverage-fixtures/localized-failure.ts:37:9 (2 requests) + <- SC2009 type at tests/coverage-fixtures/localized-failure.ts:37:9: values of type 'Callbacks | undefined' cannot be compiled: the union shape is supported, and unions in the 'string | object' family ride the checked-dynamic representation wholesale, but 'Map function pick turns dynamic + <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:38:10: uses of 'callbacks' inherit the blocker on its declaration + -> function pick turns dynamic + binding s tests/coverage-fixtures/localized-failure.ts:49:7 (1 request) + <- SC2004 cascade at tests/coverage-fixtures/localized-failure.ts:50:13: uses of 's' inherit the blocker on its declaration + -> function turns dynamic \ No newline at end of file diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index fde16a901..3f9a60bce 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -428,6 +428,38 @@ test("--explain-dynamic attributes each refusal to the unit the dynamic tier wou ); }); +test("--explain-dynamic lists the type failures localized failure would turn into dynamic slots", async () => { + const { coverage } = analyze(fixture("localized-failure.ts"), { explainDynamic: true }); + const report = coverage.tiers!.localizedFailure; + expect(report.errors).toBe(11); + expect(report.typeFailures).toEqual({ degradable: 4, refused: 1 }); + expect( + report.slots.map((s) => [ + s.stage, + s.slot, + s.name, + s.family, + s.rejects?.split(" @")[0], + s.cascade, + ]), + ).toEqual([ + ["L2", "field", "tools", "component", "class Session", 2], + ["L2", "param", "progress", "component", "function listAll", 1], + ["L2", "field", "state", "dyn-field-policy", "class Holder", 1], + ["L1", "local", "callbacks", "component", "statement", 1], + ]); + // The WeakRef field stays refused, so its class and the construction + // that uses it stay errors. + expect(report.clearedThroughL2).toBe(9); + expect(report.remainingByCode).toEqual({ SC1090: 1, SC2020: 1 }); + const relative = JSON.parse( + JSON.stringify(coverage.tiers).replaceAll(repoRoot + "/", ""), + ) as typeof coverage.tiers; + await expect(renderExplainDynamic(relative!)).toMatchFileSnapshot( + "__snapshots__/coverage-explain-localized-failure.txt", + ); +}); + test("CLI: --tier and --explain-dynamic", () => { const scriptcCli = join(repoRoot, "packages/cli/src/main.ts"); const outDir = mkdtempSync(join(tmpdir(), "scriptc-tier-")); From 693b038b15432b25d446b1352391858e7c545e9f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:17:32 -0500 Subject: [PATCH 154/230] Keep the result of void generic methods called through class-backed interfaces A class may implement a void interface method with a method that returns a value, and JavaScript passes that value on (`console.log(c.publish(m))`). The call now keeps the dispatch's untyped result, as a call through a non-generic void member already does, instead of replacing it with undefined: printing or interpolating it shows the value, and binding it to a void-typed name checks it. --- .../src/frontend/lowering/lower-calls.ts | 15 +++++--------- tests/corpus/generic-interface-void-calls.ts | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index e2f7ade90..265b7553b 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -10074,16 +10074,11 @@ function lowerObjLitGenericMethodCall( loc, }; const resultType = lowerer.irTypeOf(call); - // A void method's runtime result is discarded, as JavaScript does. - if (resultType.kind === "void") { - return { - kind: "seqExpr", - stmts: [{ kind: "exprStmt", expr: invoked, loc }], - result: { kind: "libCall", fn: "console.noop", args: [], type: VOID, loc }, - type: VOID, - loc, - }; - } + // A void method's result stays the runtime's untyped value, as a + // call through a non-generic void member's dispatch keeps it: a class + // may implement the member with a method that returns a value, which + // JavaScript passes on (`console.log(c.publish(m))`). + if (resultType.kind === "void") return invoked; return lowerer.coerceInto(call, invoked, resultType); } } diff --git a/tests/corpus/generic-interface-void-calls.ts b/tests/corpus/generic-interface-void-calls.ts index d810383f7..63c137c10 100644 --- a/tests/corpus/generic-interface-void-calls.ts +++ b/tests/corpus/generic-interface-void-calls.ts @@ -43,3 +43,23 @@ for (const channel of channels) { console.log(channel.name, result === undefined, channel.count(["a", "b", "c"])); } console.log(delivered.join(" | ")); + +// A class may implement the void member with a method that returns a value; +// JavaScript passes that value on. +class CountingChannel implements Channel { + name = "counting"; + sent = 0; + publish(message: Message): number { + this.sent++; + return message.content.length; + } + count(items: T[]): number { + return items.length * 2; + } +} + +const counted: Channel[] = [new CountingChannel(), new LogChannel("plain")]; +for (const channel of counted) { + console.log(channel.name, channel.publish({ content: "four" })); + console.log(`sent ${channel.publish({ content: "again", details: 2 })}`); +} From c7ac4d7001feb1a4312ea92bcdb107cabd1c1dc5 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:25:10 -0700 Subject: [PATCH 155/230] Expect the types patch command in the same-name origin test --- tests/harness/type-patches.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/harness/type-patches.test.ts b/tests/harness/type-patches.test.ts index dfd084572..c7975a36f 100644 --- a/tests/harness/type-patches.test.ts +++ b/tests/harness/type-patches.test.ts @@ -206,7 +206,7 @@ describe("type patches", () => { const failure = (pkg: string, version: string) => [ "undefined is not representable in the target union (a value narrowed or asserted past it still held it)", ` ${pkg}@${version} declares OutputContext.width as number (index.js:24)`, - ` if that declaration is wrong, patch it in package.json: "scriptc": { "typePatches": { "${pkg}@^${version}": { "declarations": { "OutputContext.width": "| undefined" } } } }`, + ` if that declaration is wrong, patch it: scriptc types patch ${pkg} "OutputContext.width" "| undefined"`, ]; expect(native.stdout).toBe( [...failure("panectx", "2.0.0"), ...failure("gridctx", "4.1.0"), ""].join("\n"), From f8e069daa9957d586ea66e0e1c6de220616a540a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:25:29 -0500 Subject: [PATCH 156/230] Fix CI --- .../src/frontend/shape-inference/analyze.ts | 99 ++++++++++--------- tests/harness/npm-suite.ts | 5 +- 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/packages/compiler/src/frontend/shape-inference/analyze.ts b/packages/compiler/src/frontend/shape-inference/analyze.ts index 9a0af7965..0d13e64cd 100644 --- a/packages/compiler/src/frontend/shape-inference/analyze.ts +++ b/packages/compiler/src/frontend/shape-inference/analyze.ts @@ -317,6 +317,15 @@ export function analyzeShapes(program: ts.Program, checker: ts.TypeChecker): Sha } class Analyzer { + /** locLabel, once per node of this analysis: sinks and escapes label + * their site each time the solver reaches them. */ + private readonly labels = new Map(); + private loc(node: Node): string { + let label = this.labels.get(node); + if (label === undefined) this.labels.set(node, (label = locLabel(node))); + return label; + } + readonly atoms = new AtomTable(); readonly nodes: ValueNode[] = []; readonly functions = new Map(); @@ -830,7 +839,7 @@ class Analyzer { if (this.traceTop) this.nodeLabel.set( id, - `binding ${(decl as { name?: { text?: string } }).name?.text ?? "?"}@${locLabel(decl)}${this.ctx !== "" ? ` [${this.ctx}]` : ""}`, + `binding ${(decl as { name?: { text?: string } }).name?.text ?? "?"}@${this.loc(decl)}${this.ctx !== "" ? ` [${this.ctx}]` : ""}`, ); } return id; @@ -908,7 +917,7 @@ class Analyzer { const nameNode = (fn as { name?: Node }).name; const name = nameNode !== undefined ? (propertyNameText(nameNode) ?? "") : null; const owner = ownerClass !== null ? this.className(ownerClass) : null; - const label = `${owner !== null ? `${owner}.` : ""}${name ?? (ts.isConstructorDeclaration(node) ? "constructor" : "")}@${locLabel(node)}`; + const label = `${owner !== null ? `${owner}.` : ""}${name ?? (ts.isConstructorDeclaration(node) ? "constructor" : "")}@${this.loc(node)}`; const info: FunctionInfo = { node, ctx, @@ -952,7 +961,7 @@ class Analyzer { if (p.dotDotDotToken !== undefined) { const arr = this.atoms.intern(`rest:${this.nodes.length}`, () => ({ kind: "array", - label: `rest@${locLabel(p)}`, + label: `rest@${this.loc(p)}`, node: p, })); this.add(this.binding(p), arr); @@ -998,7 +1007,7 @@ class Analyzer { const p = decl.parent; if (p !== undefined && ts.isVariableDeclaration(p) && ts.isIdentifier(p.name)) return p.name.text; - return ``; + return ``; } classAtoms(decl: Node): { cls: Atom; inst: Atom } { @@ -1039,7 +1048,7 @@ class Analyzer { ctx: "", root: null, walked: true, - label: `${label}.${suffix}@${locLabel(decl)}`, + label: `${label}.${suffix}@${this.loc(decl)}`, params: [], rest: -1, thisNode: this.newNode(), @@ -1187,7 +1196,7 @@ class Analyzer { if (this.isFactory(info.node)) { const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({ kind: "dictionary", - label: `Object.create@${locLabel(site)}`, + label: `Object.create@${this.loc(site)}`, node: site, })); this.add(dst, d); @@ -1253,7 +1262,7 @@ class Analyzer { syntheticArray(site: Node, tag: string): Atom { return this.atoms.intern(`arr:${tag}:${this.nodeKey(site)}`, () => ({ kind: "array", - label: `${tag}[]@${locLabel(site)}`, + label: `${tag}[]@${this.loc(site)}`, node: site, })); } @@ -1361,7 +1370,7 @@ class Analyzer { return; } if (ts.isThrowStatement(s)) { - this.sink(this.expr(s.expression), `thrown at ${locLabel(s)}`); + this.sink(this.expr(s.expression), `thrown at ${this.loc(s)}`); return; } if ( @@ -1437,7 +1446,7 @@ class Analyzer { Symbol.iterator member have a computed key; they are G sites. */ } else { this.add(dst, this.top); - if (a.kind !== "top") this.escape(a, `iterated at ${locLabel(site)}`); + if (a.kind !== "top") this.escape(a, `iterated at ${this.loc(site)}`); } }); } @@ -1453,7 +1462,7 @@ class Analyzer { if (this.traceTop && !this.nodeLabel.has(id)) this.nodeLabel.set( id, - `expr ${e.getText().slice(0, 40).replace(/\s+/g, " ")}@${locLabel(e)}`, + `expr ${e.getText().slice(0, 40).replace(/\s+/g, " ")}@${this.loc(e)}`, ); return id; } @@ -1586,7 +1595,7 @@ class Analyzer { if (ts.isTaggedTemplateExpression(e)) return this.taggedTemplate(e); if (ts.isYieldExpression(e)) { if (e.expression !== undefined) - this.sink(this.expr(e.expression), `yielded at ${locLabel(e)}`); + this.sink(this.expr(e.expression), `yielded at ${this.loc(e)}`); return this.constNode(this.top); } if (ts.isMetaProperty(e)) return this.constNode(this.top); @@ -1841,7 +1850,7 @@ class Analyzer { const spreads: number[] = []; const inPackage = isPackageJsFile(e.getSourceFile()); const label = (): string => - `{${keys.slice(0, 6).join(",")}${keys.length > 6 ? ",…" : ""}${keysKnown ? "" : ",…"}}@${locLabel(e)}`; + `{${keys.slice(0, 6).join(",")}${keys.length > 6 ? ",…" : ""}${keysKnown ? "" : ",…"}}@${this.loc(e)}`; // keys first, so the label is complete for (const p of list(e.properties)) { if (ts.isSpreadAssignment(p)) { @@ -2053,7 +2062,7 @@ class Analyzer { arrayLiteral(e: ts.ArrayLiteralExpression): number { const atom = this.atoms.intern(`arr:${this.nodeKey(e)}`, () => ({ kind: "array", - label: `[]@${locLabel(e)}`, + label: `[]@${this.loc(e)}`, node: e, })); const n = this.newNode(); @@ -2175,7 +2184,7 @@ class Analyzer { if (ts.isIdentifier(t)) { const decl = this.resolve(t); if (decl === undefined || decl.getSourceFile().isDeclarationFile) { - this.sink(value, `global write at ${locLabel(t)}`); + this.sink(value, `global write at ${this.loc(t)}`); return; } this.edge(value, this.binding(decl)); @@ -2421,7 +2430,7 @@ class Analyzer { switch (a.kind) { case "top": this.noteTopWrite(name, site); - this.sink(value, `stored into ⊤.${name} at ${locLabel(site)}`); + this.sink(value, `stored into ⊤.${name} at ${this.loc(site)}`); return; case "instance": { const member = this.lookupMember(a.node!, name, false); @@ -2448,7 +2457,7 @@ class Analyzer { case "prim": return; default: - this.sink(value, `stored into ${a.label} at ${locLabel(site)}`); + this.sink(value, `stored into ${a.label} at ${this.loc(site)}`); } }); } @@ -2497,7 +2506,7 @@ class Analyzer { switch (a.kind) { case "top": this.noteTopWrite(null, site); - this.sink(value, `stored into ⊤[k] at ${locLabel(site)}`); + this.sink(value, `stored into ⊤[k] at ${this.loc(site)}`); return; case "array": // An index, or a named property under an unknown key. @@ -2515,7 +2524,7 @@ class Analyzer { return; default: // As a store under a name: a builtin, promise or collection keeps it where it isn't tracked. - this.sink(value, `stored into ${a.label}[k] at ${locLabel(site)}`); + this.sink(value, `stored into ${a.label}[k] at ${this.loc(site)}`); } }); } @@ -2591,7 +2600,7 @@ class Analyzer { if (inst.kind === "instance") this.callConstructorChain(base, inst, args, spread, e); }); } else if (base === undefined) - for (const a of args) this.sink(a, `super(...) at ${locLabel(e)}`); + for (const a of args) this.sink(a, `super(...) at ${this.loc(e)}`); return dst; } // CommonJS `require("")` of an analyzed module: its exports (the @@ -2687,9 +2696,9 @@ class Analyzer { case "prim": return; default: - for (const x of args) this.sink(x, `argument of ⊤ call at ${locLabel(site)}`); + for (const x of args) this.sink(x, `argument of ⊤ call at ${this.loc(site)}`); if (recv !== null && a.kind === "top") - this.sink(recv, `receiver of ⊤ call at ${locLabel(site)}`); + this.sink(recv, `receiver of ⊤ call at ${this.loc(site)}`); this.add(dst, this.top); } }); @@ -2732,7 +2741,7 @@ class Analyzer { this.constructBuiltin(a.path ?? "", args, dst, e); return; default: - for (const x of args) this.sink(x, `argument of ⊤ constructor at ${locLabel(e)}`); + for (const x of args) this.sink(x, `argument of ⊤ constructor at ${this.loc(e)}`); this.add(dst, this.top); } }); @@ -2747,7 +2756,7 @@ class Analyzer { case "WeakSet": { const c = this.atoms.intern(`coll:${this.nodeKey(site)}`, () => ({ kind: "collection", - label: `${path}@${locLabel(site)}`, + label: `${path}@${this.loc(site)}`, node: site, })); this.add(dst, c); @@ -2761,7 +2770,7 @@ class Analyzer { this.add(dst, this.builtin("Error.instance")); return; case "Promise": { - const p = this.promiseAtom(site, locLabel(site)); + const p = this.promiseAtom(site, this.loc(site)); this.add(this.field(p, "value"), this.top); this.add(dst, p); if (args[0] !== undefined) { @@ -2794,11 +2803,11 @@ class Analyzer { // The handler's traps run on every operation with the target. if (args[0] !== undefined) this.ops.push({ kind: "proxy", recv: args[0], name: null, node: site }); - for (const x of args) this.sink(x, `argument of new Proxy at ${locLabel(site)}`); + for (const x of args) this.sink(x, `argument of new Proxy at ${this.loc(site)}`); this.add(dst, this.top); return; default: - for (const x of args) this.sink(x, `argument of new ${path} at ${locLabel(site)}`); + for (const x of args) this.sink(x, `argument of new ${path} at ${this.loc(site)}`); this.add(dst, this.top); } } @@ -2825,13 +2834,13 @@ class Analyzer { this.callFunction(this.infoOfAtom(f)!, null, cbArgs, false, ret ?? this.newNode(), site); this.forceLoop = saved; } else if (f.kind === "top" || f.kind === "builtin") { - for (const x of cbArgs) this.sink(x, `argument of ⊤ callback at ${locLabel(site)}`); + for (const x of cbArgs) this.sink(x, `argument of ⊤ callback at ${this.loc(site)}`); if (ret !== null) this.add(ret, this.top); } }); }; const escapeAll = (): void => { - for (const x of args) this.sink(x, `argument of ${path} at ${locLabel(site)}`); + for (const x of args) this.sink(x, `argument of ${path} at ${this.loc(site)}`); this.add(dst, this.top); }; // Methods of builtin receivers. @@ -2949,7 +2958,7 @@ class Analyzer { return; } escapeAll(); - this.escape(recv, `array.${method} at ${locLabel(site)}`); + this.escape(recv, `array.${method} at ${this.loc(site)}`); return; } if (path.startsWith("string.") || path.startsWith("number.") || path.startsWith("boolean.")) { @@ -2978,7 +2987,7 @@ class Analyzer { return; } if (path.startsWith("promise.")) { - const out = this.promiseAtom(site, locLabel(site)); + const out = this.promiseAtom(site, this.loc(site)); const value = this.field(recv, "value"); const ret = this.newNode(); if (method === "then") { @@ -3048,7 +3057,7 @@ class Analyzer { if (info !== undefined && args.length > 1) { this.activate(info); for (const p of info.params) this.add(p, this.top); - for (const x of args.slice(1)) this.sink(x, `bound argument at ${locLabel(site)}`); + for (const x of args.slice(1)) this.sink(x, `bound argument at ${this.loc(site)}`); } this.add(dst, recv); return; @@ -3146,7 +3155,7 @@ class Analyzer { for (const s of args.slice(1)) this.copyFields(s, t, site); if (t.kind === "top") for (const s of args.slice(1)) - this.sink(s, `Object.assign into ⊤ at ${locLabel(site)}`); + this.sink(s, `Object.assign into ⊤ at ${this.loc(site)}`); }); this.edge(arg(0), dst); return; @@ -3155,20 +3164,20 @@ class Analyzer { case "Object.defineProperties": case "Reflect.defineProperty": this.ops.push({ kind: "defineProperty", recv: arg(0), name: null, node: site }); - this.sink(arg(2), `property descriptor at ${locLabel(site)}`); - this.sink(arg(1), `property descriptor at ${locLabel(site)}`); + this.sink(arg(2), `property descriptor at ${this.loc(site)}`); + this.sink(arg(1), `property descriptor at ${this.loc(site)}`); this.edge(arg(0), dst); return; case "Object.setPrototypeOf": case "Reflect.setPrototypeOf": this.ops.push({ kind: "setPrototypeOf", recv: arg(0), name: null, node: site }); - this.sink(arg(1), `prototype at ${locLabel(site)}`); + this.sink(arg(1), `prototype at ${this.loc(site)}`); this.edge(arg(0), dst); return; case "Object.create": { const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({ kind: "dictionary", - label: `Object.create@${locLabel(site)}`, + label: `Object.create@${this.loc(site)}`, node: site, })); this.add(dst, d); @@ -3177,7 +3186,7 @@ class Analyzer { case "Object.fromEntries": { const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({ kind: "dictionary", - label: `fromEntries@${locLabel(site)}`, + label: `fromEntries@${this.loc(site)}`, node: site, })); const pairs = this.newNode(); @@ -3226,7 +3235,7 @@ class Analyzer { site, ); else if (f.kind === "top") - this.sink(revived, `JSON.parse reviver at ${locLabel(site)}`); + this.sink(revived, `JSON.parse reviver at ${this.loc(site)}`); }); this.edge(revived, dst); return; @@ -3253,7 +3262,7 @@ class Analyzer { site, ); else if (f.kind === "top") - this.sink(serialized, `JSON.stringify replacer at ${locLabel(site)}`); + this.sink(serialized, `JSON.stringify replacer at ${this.loc(site)}`); }); prim("string"); prim("undefined"); @@ -3282,7 +3291,7 @@ class Analyzer { if (c.kind === "builtin" && c.path === "Proxy") this.ops.push({ kind: "proxy", recv: listed, name: null, node: site }); }); - this.sink(listed, `argument of ${path} at ${locLabel(site)}`); + this.sink(listed, `argument of ${path} at ${this.loc(site)}`); escapeAll(); return; } @@ -3311,23 +3320,23 @@ class Analyzer { prim("bigint"); return; case "Promise.resolve": { - const p = this.promiseAtom(site, locLabel(site)); + const p = this.promiseAtom(site, this.loc(site)); if (args[0] !== undefined) this.awaitInto(args[0], this.field(p, "value")); else this.add(this.field(p, "value"), this.prim("undefined")); this.add(dst, p); return; } case "Promise.reject": { - const p = this.promiseAtom(site, locLabel(site)); + const p = this.promiseAtom(site, this.loc(site)); this.add(dst, p); - if (args[0] !== undefined) this.sink(args[0], `rejection at ${locLabel(site)}`); + if (args[0] !== undefined) this.sink(args[0], `rejection at ${this.loc(site)}`); return; } case "Promise.all": case "Promise.allSettled": case "Promise.race": case "Promise.any": { - const p = this.promiseAtom(site, locLabel(site)); + const p = this.promiseAtom(site, this.loc(site)); const items = this.newNode(); if (args[0] !== undefined) this.iterateInto(args[0], items, site); if (path === "Promise.all") { @@ -3341,7 +3350,7 @@ class Analyzer { return; } case "promise.resolver": - for (const x of args) this.sink(x, `promise resolution at ${locLabel(site)}`); + for (const x of args) this.sink(x, `promise resolution at ${this.loc(site)}`); return; } if (path.startsWith("console.")) { diff --git a/tests/harness/npm-suite.ts b/tests/harness/npm-suite.ts index ac9cb0e99..5fbbf9c9a 100644 --- a/tests/harness/npm-suite.ts +++ b/tests/harness/npm-suite.ts @@ -120,8 +120,11 @@ const KNOWN: Record = { * types module, and the zod 4 cases (zod4-boundary, zod4-record, * hono-zod-validator, hono-zod-default-hook) * compile zod 4's core, classic API and locales. A sanitized build of any - * of them takes minutes on a CI runner. */ + * of them takes minutes on a CI runner. commander-calc-async runs four + * command lines, and with the shape overlay its build analyzes commander + * for most of a minute. */ const TIMEOUTS: Record = { + "commander-calc-async": 300_000, "hono-zod-default-hook": 900_000, "hono-zod-validator": 900_000, undici5: 600_000, From a848a7b349338d91ec3000ccebd3d5bd5d1242b0 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:44:18 -0500 Subject: [PATCH 157/230] Resolve the pipeline promise to undefined for an untyped iterable destination When an untyped destination function returned an async iterable, the promise form settled its result without a payload, and code that read the awaited value as an untyped value dereferenced NULL. The promise form for an untyped destination now resolves to undefined when the destination returned no promise, or its promise carries no value, as Node does. --- packages/runtime/src/scr_stream.c | 24 +++++++++++++++++-- .../7509-pipeline-untyped-function-stages.mjs | 5 ++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index a7593b551..fb416e5c3 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -5312,13 +5312,31 @@ static void scr_sp_value_inv(ScrClosure *cb, ScrError *err, ScrDyn *value, ScrPr scr_promise_release(p); } +/* scr_sp_value_inv for an untyped destination (SCR_PIPE_DEST_DYN), whose + * result promise carries a dyn value: when the destination returned an + * async iterable (no promise), or its promise carries no payload, the + * result is undefined, as in Node. */ +static void scr_sp_dyn_value_inv(ScrClosure *cb, ScrError *err, ScrDyn *value, + ScrPromise *value_p) { + if (err != NULL || (value_p != NULL && scr_promise_payload_kind(value_p) != SCR_EXC_NONE)) { + scr_sp_value_inv(cb, err, value, value_p); + return; + } + ScrPromise *p = scr_box_get_ref(cb->caps[0]); /* +1 */ + scr_promise_fulfill_ref(p, scr_dyn_retain(scr_dyn_undefined()), scr_dyn_retain_v, + scr_dyn_release_v, NULL); + scr_promise_release(p); +} + ScrPromise *scr_sp_pipeline_stages(double n, ScrStream **streams, double count, ScrClosure **stages, double positions, double flags, double mask, ScrClosure **helpers, ScrClosure *box, ScrDyn *signal, ScrClosure *abort) { ScrPromise *p = scr_promise_new(); ScrPipeStages fs = scr_pipe_stages_of(count, stages, positions, flags, mask, helpers, box, signal); - ScrPipeValInv vi = fs.dest != SCR_PIPE_DEST_STREAM ? &scr_sp_value_inv : NULL; + ScrPipeValInv vi = fs.dest == SCR_PIPE_DEST_STREAM ? NULL + : fs.dest == SCR_PIPE_DEST_DYN ? &scr_sp_dyn_value_inv + : &scr_sp_value_inv; ScrStream *dst = scr_stream_pipeline_ex((size_t)n, streams, scr_sp_watcher(p), &scr_sp_settle_inv, true, NULL, NULL, &fs, abort, vi); if (dst) scr_stream_release(dst); @@ -5370,7 +5388,9 @@ ScrPromise *scr_sp_pipeline_stages_opts(double n, ScrStream **streams, double co ScrPromise *p = scr_promise_new(); if (!scr_sp_opts_signal(p, &outer)) return p; ScrPipeStages fs = scr_pipe_stages_of(count, stages, positions, flags, mask, helpers, box, signal); - ScrPipeValInv vi = fs.dest != SCR_PIPE_DEST_STREAM ? &scr_sp_value_inv : NULL; + ScrPipeValInv vi = fs.dest == SCR_PIPE_DEST_STREAM ? NULL + : fs.dest == SCR_PIPE_DEST_DYN ? &scr_sp_dyn_value_inv + : &scr_sp_value_inv; ScrStream *dst = scr_stream_pipeline_ex((size_t)n, streams, scr_sp_watcher(p), &scr_sp_settle_inv, end, outer, add, &fs, abort, vi); if (dst) scr_stream_release(dst); diff --git a/tests/corpus/7509-pipeline-untyped-function-stages.mjs b/tests/corpus/7509-pipeline-untyped-function-stages.mjs index 18749e55a..590334f90 100644 --- a/tests/corpus/7509-pipeline-untyped-function-stages.mjs +++ b/tests/corpus/7509-pipeline-untyped-function-stages.mjs @@ -53,6 +53,11 @@ const out = pipeline(Readable.from(["p1", "p2"]), wrap, (err, value) => { console.log("returns a PassThrough", out instanceof PassThrough); for await (const chunk of out) console.log("passthrough", chunk); +// an async generator destination in the promise form: the PassThrough +// takes its values and the promise resolves to undefined +const promised = await pipelineP(Readable.from(["q1", "q2"]), wrap); +console.log("generator destination promise", promised, typeof promised); + // a destination whose promise rejects try { await pipelineP( From 68e148add9b6dc5182e79592031ddcc27ccff577 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:51:56 -0500 Subject: [PATCH 158/230] Wait for every released package to resolve on npm before the GitHub release The release workflow publishes each package and moves on once npm accepts the upload. A package whose processing on npm stalls was visible only on npmjs.com. A new npm-available job runs after "Publish to npm". It checks each package of the release with `npm view @ version --prefer-online` every 2 minutes (scripts/wait-npm-available.mjs) until every package resolves, and reports each package's time from upload to available in the log and the job summary. When a package still does not resolve after 2 hours, the job fails and names each missing package and version. It only reads from the registry. The publish job lists every package of the release and records the time npm accepted each upload, keeps those times with its upload receipts, and passes both to the new job. Create GitHub Release now waits for the new job, so the release is created and marked complete only once every package resolves; a later release run checks again and skips uploads npm already accepted. --- .github/workflows/release.yml | 68 +++++++++++-- scripts/wait-npm-available.mjs | 150 ++++++++++++++++++++++++++++ tests/harness/npm-available.test.ts | 114 +++++++++++++++++++++ 3 files changed, 325 insertions(+), 7 deletions(-) create mode 100644 scripts/wait-npm-available.mjs create mode 100644 tests/harness/npm-available.test.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 799337e0a..73f000431 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,6 +83,7 @@ jobs: spawnSync('git', ['diff', '--quiet', run.head_sha, 'HEAD', '--', '.', ':(exclude).github/workflows/release.yml', ':(exclude)scripts/verify-native-cli.mjs', ':(exclude)scripts/package-native-cli.mjs', ':(exclude)tests/harness/native-cli-packaging.test.ts', + ':(exclude)scripts/wait-npm-available.mjs', ':(exclude)tests/harness/npm-available.test.ts', ]).status !== 0) continue; const artifacts = api(`repos/${repo}/actions/runs/${run.id}/artifacts?per_page=100`).artifacts .filter((artifact) => !artifact.expired && artifact.size_in_bytes > 0); @@ -394,6 +395,8 @@ jobs: contents: read actions: read id-token: write + outputs: + uploads: ${{ steps.publish.outputs.uploads }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -413,7 +416,7 @@ jobs: VERSION: ${{ needs.check-release.outputs.version }} RECEIPT_RUN: ${{ needs.check-release.outputs.receipt_run }} run: | - touch "$RUNNER_TEMP/npm-accepted.txt" + touch "$RUNNER_TEMP/npm-accepted.txt" "$RUNNER_TEMP/npm-accepted-times.txt" # Include this run so re-running only failed jobs restores its receipts. for run in "$GITHUB_RUN_ID" "$RECEIPT_RUN"; do if [ -z "$run" ]; then continue; fi @@ -422,6 +425,7 @@ jobs: if [ "$count" -eq 0 ]; then continue; fi gh run download "$run" --repo "$GITHUB_REPOSITORY" --pattern "npm-accepted-$VERSION-*" --dir "$RUNNER_TEMP/npm-receipts/$run" find "$RUNNER_TEMP/npm-receipts/$run" -name npm-accepted.txt -exec cat {} + >> "$RUNNER_TEMP/npm-accepted.txt" + find "$RUNNER_TEMP/npm-receipts/$run" -name npm-accepted-times.txt -exec cat {} + >> "$RUNNER_TEMP/npm-accepted-times.txt" done - name: Setup pnpm @@ -471,6 +475,7 @@ jobs: done - name: Publish to npm + id: publish shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -490,10 +495,14 @@ jobs: # Upload acceptance and public availability are separate states. # Save acceptance immediately so retries never depend on registry lag. + # npm-release.txt lists every package of the release, and + # npm-accepted-times.txt the time npm accepted each upload, for the + # npm-available job. publish_dir() { dir="$1" packed="${2:-}" name=$(node -p "require('./$dir/package.json').name") + echo "$name@$VERSION" >> "$RUNNER_TEMP/npm-release.txt" if grep -Fxq "$name@$VERSION" "$RUNNER_TEMP/npm-accepted.txt"; then echo "$name@$VERSION upload already accepted, skipping" return 0 @@ -510,6 +519,7 @@ jobs: publish_log="$RUNNER_TEMP/npm-publish.log" if npm publish "$packed" $PROVENANCE --access public 2>&1 | tee "$publish_log"; then echo "$name@$VERSION" >> "$RUNNER_TEMP/npm-accepted.txt" + echo "$name@$VERSION $(date +%s)" >> "$RUNNER_TEMP/npm-accepted-times.txt" return 0 else publish_status=${PIPESTATUS[0]} @@ -533,7 +543,8 @@ jobs: # pnpm pack resolves workspace dependencies; npm handles OIDC. # Publish dependencies first. Registry propagation after an accepted - # upload must not block the remaining packages or the GitHub release. + # upload must not block the remaining packages; the npm-available + # job waits for it. publish_dir packages/runtime publish_dir packages/runtime-darwin-arm64 publish_dir packages/llvm-darwin-arm64 @@ -561,23 +572,66 @@ jobs: publish_dir packages/cli echo "All package uploads accepted; npm registry propagation may continue after this job." >> "$GITHUB_STEP_SUMMARY" + # Each package of the release with the time its upload was accepted, + # when a run recorded it. + { + echo "uploads<> "$GITHUB_OUTPUT" + - name: Save accepted upload receipts if: always() uses: actions/upload-artifact@v4 with: name: npm-accepted-${{ needs.check-release.outputs.version }}-${{ github.run_attempt }} - path: ${{ runner.temp }}/npm-accepted.txt + path: | + ${{ runner.temp }}/npm-accepted.txt + ${{ runner.temp }}/npm-accepted-times.txt if-no-files-found: ignore retention-days: 90 - # Publish standalone native distributions and the surface manifest after - # npm publishing succeeds. The release body comes from the marked changelog. - github-release: - name: Create GitHub Release + # Waits until every package of the release resolves on the npm registry, + # and reports each package's time from upload to available. Fails with the + # packages that are still missing after 2 hours. It only reads from the + # registry; the next release run retries it, and publishing skips uploads + # npm already accepted. + npm-available: + name: Wait for npm availability needs: [check-release, publish] if: >- !cancelled() && needs.check-release.outputs.should_release == 'true' && needs.publish.result == 'success' runs-on: ubuntu-latest + timeout-minutes: 135 + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + + - name: Wait for every package to resolve + env: + UPLOADS: ${{ needs.publish.outputs.uploads }} + run: | + printf '%s\n' "$UPLOADS" > "$RUNNER_TEMP/npm-uploads.txt" + node scripts/wait-npm-available.mjs "$RUNNER_TEMP/npm-uploads.txt" + + # Publish standalone native distributions and the surface manifest after + # every npm package resolves. The release body comes from the marked + # changelog. + github-release: + name: Create GitHub Release + needs: [check-release, publish, npm-available] + if: >- + !cancelled() && needs.check-release.outputs.should_release == 'true' && needs.npm-available.result == 'success' + runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: write diff --git a/scripts/wait-npm-available.mjs b/scripts/wait-npm-available.mjs new file mode 100644 index 000000000..26d7db391 --- /dev/null +++ b/scripts/wait-npm-available.mjs @@ -0,0 +1,150 @@ +#!/usr/bin/env node +// Waits until every package of a release resolves on the npm registry. +// +// usage: wait-npm-available.mjs [--interval ] [--timeout ] +// +// Each line of the uploads file is `@`, optionally followed by +// the Unix time (seconds) at which npm accepted the upload. Every interval +// (120 seconds by default), each package that has not resolved yet is asked +// for with `npm view @ version --prefer-online`. A package +// resolves when npm prints its version. Each package is reported once it +// resolves, with the time from upload to the check that found it. +// +// Exits with 0 once every package resolves. When packages are still missing +// at the timeout (2 hours by default), prints their names and versions and +// exits with 1. The script only reads from the registry. +import { execFile } from "node:child_process"; +import { appendFileSync, readFileSync } from "node:fs"; +import { setTimeout as delay } from "node:timers/promises"; + +function usage() { + throw new Error( + "usage: wait-npm-available.mjs [--interval ] [--timeout ]", + ); +} + +const args = process.argv.slice(2); +let file; +let intervalSeconds = 120; +let timeoutSeconds = 2 * 60 * 60; +for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === "--interval" || arg === "--timeout") { + const value = Number(args[++i]); + if (!Number.isFinite(value) || value <= 0) usage(); + if (arg === "--interval") intervalSeconds = value; + else timeoutSeconds = value; + } else if (file === undefined) { + file = arg; + } else { + usage(); + } +} +if (file === undefined) usage(); + +const packages = []; +for (const line of readFileSync(file, "utf8").split("\n")) { + const fields = line.trim().split(/\s+/); + if (fields[0] === "") continue; + const spec = fields[0]; + const at = spec.lastIndexOf("@"); + if (at <= 0 || at === spec.length - 1 || fields.length > 2) { + throw new Error(`malformed upload line: ${line}`); + } + const acceptedAt = fields[1] === undefined ? null : Number(fields[1]) * 1000; + if (acceptedAt !== null && !Number.isFinite(acceptedAt)) { + throw new Error(`malformed upload time: ${line}`); + } + packages.push({ spec, version: spec.slice(at + 1), acceptedAt, availableAt: null }); +} +if (packages.length === 0) throw new Error(`${file} names no packages`); + +function resolves(pkg) { + return new Promise((resolve) => { + execFile( + "npm", + ["view", pkg.spec, "version", "--prefer-online"], + { encoding: "utf8", timeout: 60_000 }, + (error, stdout, stderr) => { + if (error === null && stdout.trim() === pkg.version) { + resolve(true); + return; + } + const code = /npm error code (\S+)/.exec(stderr)?.[1]; + if (error !== null && code !== "E404") { + const reason = code ?? error.code ?? error.message.split("\n")[0]; + console.log(`${pkg.spec}: npm view failed (${reason}); checking again`); + } + resolve(false); + }, + ); + }); +} + +function duration(ms) { + const seconds = Math.max(0, Math.round(ms / 1000)); + const minutes = Math.floor(seconds / 60); + return minutes === 0 ? `${seconds}s` : `${minutes}m ${String(seconds % 60).padStart(2, "0")}s`; +} + +function uploadToAvailable(pkg) { + return pkg.acceptedAt === null + ? "upload time not recorded" + : duration(pkg.availableAt - pkg.acceptedAt); +} + +function summary(missing) { + const summaryFile = process.env.GITHUB_STEP_SUMMARY; + if (!summaryFile) return; + const rows = packages.map( + (pkg) => + `| \`${pkg.spec}\` | ${pkg.availableAt === null ? "missing" : uploadToAvailable(pkg)} |`, + ); + const heading = + missing.length === 0 + ? `Every package resolves on npm (${packages.length}).` + : `${missing.length} of ${packages.length} packages do not resolve on npm after ${duration(timeoutSeconds * 1000)}.`; + appendFileSync( + summaryFile, + [heading, "", "| Package | Upload to available |", "| --- | --- |", ...rows, ""].join("\n"), + ); +} + +const started = Date.now(); +const deadline = started + timeoutSeconds * 1000; +for (;;) { + const pending = packages.filter((pkg) => pkg.availableAt === null); + const checked = []; + // A few checks at a time keep each round short without flooding the registry. + for (let i = 0; i < pending.length; i += 8) { + const batch = pending.slice(i, i + 8); + const results = await Promise.all(batch.map(resolves)); + const now = Date.now(); + batch.forEach((pkg, j) => { + if (results[j]) { + pkg.availableAt = now; + checked.push(pkg); + } + }); + } + for (const pkg of checked) console.log(`${pkg.spec} is available (${uploadToAvailable(pkg)})`); + const missing = packages.filter((pkg) => pkg.availableAt === null); + if (missing.length === 0) { + console.log(`Every package resolves on npm (${packages.length}).`); + summary(missing); + process.exit(0); + } + const now = Date.now(); + if (now >= deadline) { + console.log( + `${missing.length} of ${packages.length} packages do not resolve on npm after ${duration(now - started)}:`, + ); + for (const pkg of missing) console.log(`::error::${pkg.spec} does not resolve on npm`); + summary(missing); + process.exit(1); + } + console.log( + `${missing.length} of ${packages.length} packages do not resolve yet; checking again in ${duration(Math.min(intervalSeconds * 1000, deadline - now))}`, + ); + await delay(Math.min(intervalSeconds * 1000, deadline - now)); +} diff --git a/tests/harness/npm-available.test.ts b/tests/harness/npm-available.test.ts new file mode 100644 index 000000000..0970d1f98 --- /dev/null +++ b/tests/harness/npm-available.test.ts @@ -0,0 +1,114 @@ +import { spawnSync } from "node:child_process"; +import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { delimiter, join } from "node:path"; +import { expect, test } from "vitest"; + +const repoRoot = join(import.meta.dirname, "../.."); +const script = join(repoRoot, "scripts", "wait-npm-available.mjs"); + +// A stand-in for `npm view version`: each spec resolves on the check +// numbered in registry.json (0 never resolves). Every call is logged. +const fakeNpm = `#!/usr/bin/env node +const { appendFileSync, readFileSync } = require("node:fs"); +const { join } = require("node:path"); +const dir = process.env.FAKE_NPM_DIR; +const args = process.argv.slice(2); +appendFileSync(join(dir, "calls.log"), JSON.stringify(args) + "\\n"); +const spec = args[1]; +const calls = readFileSync(join(dir, "calls.log"), "utf8").trim().split("\\n") + .filter((line) => JSON.parse(line)[1] === spec).length; +const registry = JSON.parse(readFileSync(join(dir, "registry.json"), "utf8")); +const resolvesOn = registry[spec]; +if (resolvesOn === undefined) { + process.stderr.write("npm error code E404\\n"); + process.exit(1); +} +if (resolvesOn !== 0 && calls >= resolvesOn) { + process.stdout.write(spec.slice(spec.lastIndexOf("@") + 1) + "\\n"); +} +`; + +function run(uploads: string, registry: Record, extra: string[]) { + const dir = mkdtempSync(join(tmpdir(), "scriptc-npm-available-")); + try { + writeFileSync(join(dir, "npm"), fakeNpm); + chmodSync(join(dir, "npm"), 0o755); + writeFileSync(join(dir, "registry.json"), JSON.stringify(registry)); + writeFileSync(join(dir, "calls.log"), ""); + writeFileSync(join(dir, "uploads.txt"), uploads); + writeFileSync(join(dir, "summary.md"), ""); + const result = spawnSync(process.execPath, [script, join(dir, "uploads.txt"), ...extra], { + encoding: "utf8", + env: { + ...process.env, + PATH: `${dir}${delimiter}${process.env["PATH"] ?? ""}`, + FAKE_NPM_DIR: dir, + GITHUB_STEP_SUMMARY: join(dir, "summary.md"), + }, + }); + const calls = readFileSync(join(dir, "calls.log"), "utf8") + .trim() + .split("\n") + .filter((line) => line !== "") + .map((line) => JSON.parse(line) as string[]); + return { + status: result.status, + output: result.stdout + result.stderr, + calls, + summary: readFileSync(join(dir, "summary.md"), "utf8"), + }; + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +test.skipIf(process.platform === "win32")( + "waits until every package resolves and reports each package's time from upload", + () => { + const accepted = Math.floor(Date.now() / 1000) - 90; + const result = run( + `@scriptc/runtime@1.2.3 ${accepted}\nscriptc@1.2.3\n`, + { "@scriptc/runtime@1.2.3": 1, "scriptc@1.2.3": 3 }, + ["--interval", "0.05", "--timeout", "30"], + ); + expect(result.status, result.output).toBe(0); + expect(result.output).toMatch(/@scriptc\/runtime@1\.2\.3 is available \(1m 3\ds\)/); + expect(result.output).toContain("scriptc@1.2.3 is available (upload time not recorded)"); + expect(result.output).toContain("Every package resolves on npm (2)."); + // A package that resolved is not asked again. + expect(result.calls.filter((args) => args[1] === "@scriptc/runtime@1.2.3")).toHaveLength(1); + expect(result.calls.filter((args) => args[1] === "scriptc@1.2.3")).toHaveLength(3); + for (const args of result.calls) + expect(args).toEqual(["view", args[1], "version", "--prefer-online"]); + expect(result.summary).toContain("| `scriptc@1.2.3` | upload time not recorded |"); + }, +); + +test.skipIf(process.platform === "win32")( + "fails with the names and versions of the packages that do not resolve by the timeout", + () => { + const result = run( + "@scriptc/runtime@1.2.3\n@scriptc/compiler@1.2.3\nscriptc@1.2.3\n", + { "@scriptc/runtime@1.2.3": 1, "@scriptc/compiler@1.2.3": 0 }, + ["--interval", "0.1", "--timeout", "0.5"], + ); + expect(result.status, result.output).toBe(1); + expect(result.output).toContain("2 of 3 packages do not resolve on npm after"); + expect(result.output).toContain("::error::@scriptc/compiler@1.2.3 does not resolve on npm"); + expect(result.output).toContain("::error::scriptc@1.2.3 does not resolve on npm"); + expect(result.output).not.toContain("::error::@scriptc/runtime@1.2.3"); + expect( + result.calls.filter((args) => args[1] === "@scriptc/compiler@1.2.3").length, + ).toBeGreaterThan(1); + expect(result.summary).toContain("| `@scriptc/compiler@1.2.3` | missing |"); + expect(result.summary).toContain("| `scriptc@1.2.3` | missing |"); + }, +); + +test("rejects a malformed uploads file", () => { + const result = run("scriptc 1.2.3\n", {}, []); + expect(result.status).toBe(1); + expect(result.output).toContain("malformed upload line: scriptc 1.2.3"); + expect(result.calls).toHaveLength(0); +}); From e884e53001b0a1a79b94807eddf070699750ce34 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 25 Oct 2026 16:53:05 -0700 Subject: [PATCH 159/230] Keep one identity through checked casts of dynamic values A checked cast of a dynamic value to a record or array (value as T) built a separate native copy: writes through the result missed the source and every other holder, and === was false, where Node's as does nothing at run time. Every such cast now runs the declaration boundary's adoption leniently (dyn.adoptShared): each source object or array something else still holds becomes, in place, a live capsule of the native value built from it. A node that is not plain data, or that the value holds twice, keeps the copy. A source nothing else can hold converts outright, which keeps the fused JSON.parse(text) as T: a fresh JSON.parse or response.json(), workerData read at one site, the payload of the program's one message listener, and a cast whose result serves one primitive member read. Data-only object and array literals in untyped slots the program declares (unknown or any variables, fields, parameters, returns, unknown[] elements), thrown literals and their members are built as dynamic objects, so their later casts adopt them too. A boxed native value cast to another native layout keeps its cached copy (object model decision D3); the harness test pins it. Ported from the fix/dyn-record-identity prototype. Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/backend/llvm/lib-shared.ts | 1 + .../lowering/expressions/message-listeners.ts | 22 +++ .../lowering/expressions/narrowing.ts | 158 +++++++++++++++++- .../lowering/expressions/object-literals.ts | 3 +- .../src/frontend/lowering/lower-stmts.ts | 11 +- .../compiler/src/frontend/lowering/lowerer.ts | 97 +++++++++++ .../compiler/src/ir/builtin-signatures.ts | 6 + packages/compiler/src/ir/runtime-features.ts | 5 +- .../corpus/dyn-cast-identity-fresh.ts.json | 6 + .../corpus/dyn-cast-identity-holders.ts.json | 6 + .../corpus/dyn-cast-identity-workers.ts.json | 6 + .../tests/corpus/dyn-cast-identity.ts.json | 6 + .../tests/corpus/dyn-literal-identity.ts.json | 6 + packages/runtime/src/scr_decl_adopt.c | 49 ++++-- packages/runtime/src/scr_dyn.h | 1 + tests/corpus/dyn-cast-identity-fresh.ts | 21 +++ tests/corpus/dyn-cast-identity-holders.ts | 62 +++++++ tests/corpus/dyn-cast-identity-workers.ts | 23 +++ tests/corpus/dyn-cast-identity.ts | 71 ++++++++ tests/corpus/dyn-literal-identity.ts | 83 +++++++++ .../dyn-cast-identity-fresh.ts.json | 1 + .../dyn-cast-identity-holders.ts.json | 1 + .../dyn-cast-identity-workers.ts.json | 1 + .../corpus-costs/dyn-cast-identity.ts.json | 1 + .../corpus-costs/dyn-literal-identity.ts.json | 1 + tests/harness/dyncheck.test.ts | 27 +++ 26 files changed, 659 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/src/frontend/lowering/expressions/message-listeners.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-fresh.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-holders.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-workers.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-literal-identity.ts.json create mode 100644 tests/corpus/dyn-cast-identity-fresh.ts create mode 100644 tests/corpus/dyn-cast-identity-holders.ts create mode 100644 tests/corpus/dyn-cast-identity-workers.ts create mode 100644 tests/corpus/dyn-cast-identity.ts create mode 100644 tests/corpus/dyn-literal-identity.ts create mode 100644 tests/harness/corpus-costs/dyn-cast-identity-fresh.ts.json create mode 100644 tests/harness/corpus-costs/dyn-cast-identity-holders.ts.json create mode 100644 tests/harness/corpus-costs/dyn-cast-identity-workers.ts.json create mode 100644 tests/harness/corpus-costs/dyn-cast-identity.ts.json create mode 100644 tests/harness/corpus-costs/dyn-literal-identity.ts.json diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 4b3f34b80..0bbdff1d8 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1683,6 +1683,7 @@ export const LIB_FN_SYMS: Record = { "dyn.objEntries": "scr_dyn_obj_entries", "dyn.structuredClone": "scr_structured_clone", "dyn.declAdopt": "scr_dyn_decl_adopt", + "dyn.adoptShared": "scr_dyn_adopt_shared", "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/frontend/lowering/expressions/message-listeners.ts b/packages/compiler/src/frontend/lowering/expressions/message-listeners.ts new file mode 100644 index 000000000..698bf4dc3 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/expressions/message-listeners.ts @@ -0,0 +1,22 @@ +import * as ts from "../../ts7/adapter.js"; + +const LISTENER_METHODS = new Set([ + "on", + "once", + "addListener", + "prependListener", + "prependOnceListener", +]); + +/** `.on("message", …)` and the other listener registrations. */ +export function isMessageListenerCall(call: ts.CallExpression): boolean { + const callee = call.expression; + const event = call.arguments.nodes[0]; + return ( + ts.isPropertyAccessExpression(callee) && + LISTENER_METHODS.has(callee.name.text) && + event !== undefined && + ts.isStringLiteral(event) && + event.text === "message" + ); +} diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 9f0559672..d4407538b 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -50,6 +50,7 @@ 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"; +import { isMessageListenerCall } from "./message-listeners.js"; /** `c ? a : b`. An explicit destination constructs each branch at that * layout before joining it. Without one, retain the expression's inferred @@ -1575,7 +1576,7 @@ export function lowerAsExpression( // contains undefined, so casts over parse results keep failing on // non-string values with the usual path-annotated TypeError. if (target.kind === "union" && lowerer.dynConvertible(target)) { - return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) }; + return checkedDynCast(lowerer, expr, inner, target); } // Uint8Array targets: the checked-dynamic tree carries a bytes kind now (converted // stdin chunks) — the extraction validates the kind and copies out. @@ -1619,9 +1620,145 @@ export function lowerAsExpression( `number, string, boolean, records, arrays, and unions of those)`, ); } + return checkedDynCast(lowerer, expr, inner, target); +} + +/** The checked conversion of a dynamic value. A record or array built from + * a value another holder may still reference keeps one identity with it + * (adoptingCast); only a source nothing else can hold (freshDynSource) + * converts outright, which keeps the fused `JSON.parse(text) as T`. */ +function checkedDynCast( + lowerer: Lowerer, + expr: ts.AsExpression | ts.TypeAssertion, + inner: IrExpr, + target: IrType, +): IrExpr { + if ( + holdsReference(lowerer, target) && + !freshDynSource(lowerer, expr, inner) && + !resultOnlyRead(lowerer, expr) + ) { + return adoptingCast(lowerer, inner, target, locOf(expr), "dyn.adoptShared"); + } return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) }; } +/** A cast whose result only serves one read of a primitive member + * (`(value as T).name`, `(value as T[]).length`): the converted value is + * never stored, compared, written or called through, and the read yields no + * reference into it, so whether it is the source itself or a copy cannot be + * observed. */ +function resultOnlyRead(lowerer: Lowerer, expr: ts.AsExpression | ts.TypeAssertion): boolean { + let node: ts.Node = expr; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + const access = node.parent; + if ( + access === undefined || + !( + (ts.isPropertyAccessExpression(access) || ts.isElementAccessExpression(access)) && + access.expression === node + ) + ) + return false; + const primitive = + ts.TypeFlags.StringLike | + ts.TypeFlags.NumberLike | + ts.TypeFlags.BooleanLike | + ts.TypeFlags.BigIntLike | + ts.TypeFlags.Undefined | + ts.TypeFlags.Null; + const read = lowerer.typeOf(access); + const parts = read.isUnionType() ? ts.constituentTypes(read) : [read]; + if (!parts.every((part) => (part.flags & primitive) !== 0)) return false; + let use: ts.Node = access; + while (use.parent !== undefined && ts.isParenthesizedExpression(use.parent)) use = use.parent; + const parent = use.parent; + if (parent === undefined) return true; + if (ts.isCallExpression(parent) && parent.expression === use) return false; + if ( + ts.isBinaryExpression(parent) && + parent.left === use && + ts.isAssignmentOperator(parent.operatorToken.kind) + ) + return false; + if ( + (ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent)) && + (parent.operator === ts.SyntaxKind.PlusPlusToken || + parent.operator === ts.SyntaxKind.MinusMinusToken) + ) + return false; + if (ts.isDeleteExpression(parent)) return false; + return true; +} + +/** A cast operand nothing else can hold: a value this expression creates + * (`JSON.parse(text)`, `await response.json()`), or a structured clone the + * runtime delivers once and the program reads at this site only (`workerData` + * read nowhere else; the parameter of the program's one `"message"` + * listener, read nowhere else in it). Anything else is adopted. */ +function freshDynSource( + lowerer: Lowerer, + expr: ts.AsExpression | ts.TypeAssertion, + inner: IrExpr, +): boolean { + if (inner.kind === "libCall" && inner.fn === "json.parse") return true; + if ( + inner.kind === "awaitExpr" && + inner.value.kind === "libCall" && + inner.value.fn === "fetch.responseJson" + ) + return true; + const facts = lowerer.structuredCloneReads(); + if (inner.kind === "libCall" && inner.fn === "worker.data") return facts.workerData === 1; + let operand: ts.Expression = expr.expression; + while (ts.isParenthesizedExpression(operand)) operand = operand.expression; + if (!ts.isIdentifier(operand) || inner.kind !== "varRef" || facts.messageListeners !== 1) + return false; + const symbol = lowerer.checker.getSymbolAtLocation(operand); + const param = symbol?.valueDeclaration; + if (!param || !ts.isParameter(param) || !ts.isIdentifier(param.name)) return false; + const fn = param.parent; + if ( + !(ts.isArrowFunction(fn) || ts.isFunctionExpression(fn)) || + fn.parameters.nodes[0] !== param || + !isMessageListener(fn) + ) + return false; + let reads = 0; + const name = param.name.text; + const visit = (node: ts.Node): void => { + if (ts.isIdentifier(node) && node.text === name && node !== param.name) reads++; + ts.forEachChild(node, visit); + }; + ts.forEachChild(fn, visit); + return reads === 1; +} + +/** The listener argument of `.on("message", fn)` (also once, + * addListener, prependListener, prependOnceListener) registered once: at + * module level, outside any function, class or loop, so one emitter can + * never hand the same message to two registrations of it. */ +function isMessageListener(fn: ts.Node): boolean { + const call = fn.parent; + if (!call || !ts.isCallExpression(call) || call.arguments.nodes[1] !== fn) return false; + if (!isMessageListenerCall(call)) return false; + for (let p = call.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) { + if ( + ts.isFunctionLike(p) || + ts.isClassDeclaration(p) || + ts.isClassExpression(p) || + ts.isClassStaticBlockDeclaration(p) || + ts.isForStatement(p) || + ts.isForInStatement(p) || + ts.isForOfStatement(p) || + ts.isWhileStatement(p) || + ts.isDoStatement(p) + ) + return false; + } + return true; +} + /** 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 { @@ -1657,6 +1794,21 @@ function declBoundaryCast( if (!holdsReference(lowerer, target)) { return { kind: "dynCheck", value: inner, type: target, loc }; } + return adoptingCast(lowerer, inner, target, loc, "dyn.declAdopt"); +} + +/** The checked conversion of `inner` to `target` followed by adoption + * (`dyn.declAdopt` at the boundary, the lenient `dyn.adoptShared` + * elsewhere): every source object or array that something else still + * references becomes a live capsule of the native value built from it. */ +function adoptingCast( + lowerer: Lowerer, + inner: IrExpr, + target: IrType, + loc: SrcLoc, + fn: "dyn.declAdopt" | "dyn.adoptShared", +): IrExpr { + const boundary = fn === "dyn.declAdopt"; const source = lowerer.declareHiddenLocal("%boundarySource", DYN); const value = lowerer.declareHiddenLocal("%boundaryValue", target); return { @@ -1669,7 +1821,7 @@ function declBoundaryCast( init: { kind: "dynCheck", value: varRef(source.id, DYN, loc), - adopt: true, + ...(boundary ? { adopt: true as const } : {}), type: target, loc, }, @@ -1679,7 +1831,7 @@ function declBoundaryCast( kind: "exprStmt", expr: { kind: "libCall", - fn: "dyn.declAdopt", + fn, args: [ varRef(source.id, DYN, loc), { diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index f57e52159..6e4ff8a33 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -419,7 +419,8 @@ export function lowerDynObjectLiteral( : !boxValue && (ts.isObjectLiteralExpression(valueExpr) || (ts.isArrayLiteralExpression(valueExpr) && - holdsEvolvingArray(lowerer, valueExpr))) + (holdsEvolvingArray(lowerer, valueExpr) || + lowerer.untypedLiteralContext(valueExpr)))) ? lowerer.lowerExprExpecting(valueExpr, DYN) : lowerer.lowerExpr(valueExpr as ts.Expression); raw = fenceClosureProbe(lowerer, valueExpr, undefined, lowerValue) ?? lowerValue(); diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 84196476a..37e14ec0c 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1878,7 +1878,16 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt } // Exception-representable values move into the runtime's exception // cell. `throw f()` of a void call has no value. - let value = lowerer.lowerExpr(stmt.expression); + // A thrown object or array literal has no declared type: it is built as + // a dynamic object, so the catch binding's checked casts adopt it + // (lowerer.untypedLiteralContext). + let literal: ts.Expression = stmt.expression; + while (ts.isParenthesizedExpression(literal)) literal = literal.expression; + let value = + (ts.isObjectLiteralExpression(literal) || ts.isArrayLiteralExpression(literal)) && + lowerer.untypedLiteralContext(literal) + ? lowerer.lowerExprExpecting(stmt.expression, DYN) + : lowerer.lowerExpr(stmt.expression); if (value.type.kind === "void") lowerer.badType(stmt.expression, lowerer.typeOf(stmt.expression)); // Preserve primitive reasons and readable record identity across the diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 634c361c2..7c2d32f17 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -460,6 +460,7 @@ import { genericLocalClassBinding, } from "./generic-functions.js"; import { bindingNeverReassigned } from "./binding-analysis.js"; +import { isMessageListenerCall } from "./expressions/message-listeners.js"; import type { InstantiationPath } from "./instantiation-path.js"; import { bodyReadsArguments } from "../arguments-usage.js"; import { completeArgs, wrappedUndefined, undefinedArgFor } from "./call-arguments.js"; @@ -2477,6 +2478,69 @@ export class Lowerer { return fallback ?? null; } + /** An object or array literal in an untyped context: its contextual type + * is `unknown` or `any` from a slot the program declares (a variable, + * field, parameter, return type or `unknown[]` element; a library + * parameter typed `any` only through a type argument), it is thrown, or + * it is a member of such a literal, and it holds data only (no methods, + * accessors or function members). Its value is built as a dynamic + * object: no type the program wrote gives it a native layout, and a later + * checked cast adopts it (dyn.adoptShared), so every holder keeps one + * identity. A library parameter the declarations type `any` or `unknown` + * itself (`console.log`, `JSON.stringify`) only reads the value and keeps + * the native literal. */ + untypedLiteralContext(x: ts.Expression): boolean { + if (isJsSourceFile(ts.getSourceFileOfNode(x))) return false; + // Data only: a literal with methods, accessors or function members is an + // object with behavior, which keeps its native layout (and boxed form). + if ( + ts.isObjectLiteralExpression(x) && + x.properties.nodes.some((p) => { + if (!ts.isPropertyAssignment(p)) + return !ts.isShorthandPropertyAssignment(p) && !ts.isSpreadAssignment(p); + let init: ts.Expression = p.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + return ( + ts.isFunctionExpression(init) || ts.isArrowFunction(init) || ts.isClassExpression(init) + ); + }) + ) + return false; + let node: ts.Node = x; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) + node = node.parent; + const parent = node.parent; + if (parent === undefined) return false; + if (ts.isThrowStatement(parent)) return true; + if (ts.isPropertyAssignment(parent) && parent.initializer === node) { + const literal = parent.parent; + if (ts.isObjectLiteralExpression(literal)) return this.untypedLiteralContext(literal); + } + if (ts.isArrayLiteralExpression(parent) && this.untypedLiteralContext(parent)) return true; + const contextual = this.checker.getContextualType(x); + if (!contextual || !(contextual.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown))) + return false; + if (ts.isCallExpression(parent) || ts.isNewExpression(parent)) { + const signature = this.checker.getResolvedSignature(parent); + const decl = signature?.declaration; + if (decl && this.isStdlibFile(ts.getSourceFileOfNode(decl))) { + const index = (parent.arguments?.nodes ?? []).indexOf(node as ts.Expression); + const params = (decl as { parameters?: { nodes: readonly ts.ParameterDeclaration[] } }) + .parameters?.nodes; + const param = params?.[Math.min(index, params.length - 1)]; + let annotation = param?.type; + if (annotation && ts.isArrayTypeNode(annotation)) annotation = annotation.elementType; + if ( + !annotation || + annotation.kind === ts.SyntaxKind.AnyKeyword || + annotation.kind === ts.SyntaxKind.UnknownKeyword + ) + return false; + } + } + return true; + } + /** 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 @@ -9858,6 +9922,38 @@ export class Lowerer { ); }; + private structuredCloneReadCounts: { workerData: number; messageListeners: number } | null = null; + + /** How often the program reads `workerData` (every expression spelling it, + * counted conservatively) and registers a `"message"` listener: a + * structured clone read at one site only has no other holder + * (narrowing.ts freshDynSource). Computed once per lowering pass. */ + structuredCloneReads(): { workerData: number; messageListeners: number } { + if (this.structuredCloneReadCounts) return this.structuredCloneReadCounts; + const counts = { workerData: 0, messageListeners: 0 }; + const visit = (node: ts.Node): void => { + if (ts.isIdentifier(node) && node.text === "workerData") { + const parent = node.parent; + const declaresName = + parent !== undefined && + (ts.isImportSpecifier(parent) || + ts.isExportSpecifier(parent) || + ((ts.isPropertyAssignment(parent) || + ts.isPropertyDeclaration(parent) || + ts.isPropertySignature(parent) || + ts.isMethodDeclaration(parent)) && + parent.name === node)); + if (!declaresName) counts.workerData++; + } else if (ts.isCallExpression(node) && isMessageListenerCall(node)) { + counts.messageListeners++; + } + ts.forEachChild(node, visit); + }; + for (const sf of this.moduleOrder) ts.forEachChild(sf, visit); + this.structuredCloneReadCounts = counts; + return counts; + } + irTypeOf(node: ts.Node): IrType { const t = this.typeOf(node); // JS inference can leave mutable fields, accessor parameters and @@ -11458,6 +11554,7 @@ export class Lowerer { !this.mapTypeOf(this.typeOf(x)) || this.excessJsContextualProperty(x, parts) || this.reshapeableContextualLiteral(x, parts) || + this.untypedLiteralContext(x) || holdsEvolvingArray(this, x) ) { return lowerDynObjectLiteral(this, x); diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e230932ba..405dc5765 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -3967,6 +3967,12 @@ function buildLibFnSignatures() { * 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.declAdopt for every other checked cast of a dynamic value to a + * record or array (`value as T`): a shared source object or array + * becomes a live capsule of the value built from it. A node that is not + * plain data, or that the value holds twice, keeps its separate copy. + * Never throws: the conversion already succeeded. */ + "dyn.adoptShared": { 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/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 494c4e3e8..cd6eb55e9 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -963,7 +963,10 @@ const RUNTIME_FEATURE_RULES = { /** 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 }), + declAdopt: rule({ + fns: ["dyn.declAdopt", "dyn.adoptShared"], + 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 diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-fresh.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-fresh.ts.json new file mode 100644 index 000000000..e1643bcab --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-fresh.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-cast-identity-fresh.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-holders.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-holders.ts.json new file mode 100644 index 000000000..e1a06ecc5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-holders.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-cast-identity-holders.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-workers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-workers.ts.json new file mode 100644 index 000000000..e8ecb066e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity-workers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-cast-identity-workers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity.ts.json new file mode 100644 index 000000000..fdf360d21 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-cast-identity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-cast-identity.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-literal-identity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-literal-identity.ts.json new file mode 100644 index 000000000..41fde2c81 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dyn-literal-identity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dyn-literal-identity.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_decl_adopt.c b/packages/runtime/src/scr_decl_adopt.c index 7372826ab..fc1c4456c 100644 --- a/packages/runtime/src/scr_decl_adopt.c +++ b/packages/runtime/src/scr_decl_adopt.c @@ -15,7 +15,11 @@ * 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). */ + * (scr_dyn_decl_become, scr_json.c). + * + * Every other checked cast of a dynamic value to a record or array runs the + * same pass leniently (scr_dyn_adopt_shared): a node that cannot become a + * capsule keeps the cast's copy. */ #include "scr_runtime.h" #include @@ -31,6 +35,9 @@ typedef struct { typedef struct { ScrDeclAdopt *items; size_t len, cap; + /* A checked cast outside the boundary (scr_dyn_adopt_shared): a node + * that cannot become a capsule keeps the copy instead of throwing. */ + bool lenient; } ScrDeclAdoptList; static void scr_decl_adopt_push(ScrDeclAdoptList *list, ScrDyn *node, ScrDyn *capsule) { @@ -44,7 +51,7 @@ static void scr_decl_adopt_push(ScrDeclAdoptList *list, ScrDyn *node, ScrDyn *ca list->len++; } -static bool scr_decl_adopt_plain(const ScrDyn *n, const ScrDynPath *path) { +static bool scr_decl_adopt_plain(const ScrDyn *n, const ScrDynPath *path, bool lenient) { const char *want = NULL; if (n->kind == SCR_DYN_OBJ) { if (n->prototype || n->null_proto || n->v.obj.class_name) @@ -72,7 +79,7 @@ static bool scr_decl_adopt_plain(const ScrDyn *n, const ScrDynPath *path) { want = "a plain array (this value is frozen, sealed or not extensible)"; } if (!want) return true; - scr_dyn_check_fail(path, want, n); + if (!lenient) scr_dyn_check_fail(path, want, n); return false; } @@ -82,6 +89,14 @@ static ScrDyn *scr_decl_capsule_ptr_holder(const ScrDeclAdoptList *list, const S return NULL; } +/* Whether the walk follows a child: plain objects and arrays. A boxed + * native record or array is already one object with its native value; a + * cast to another layout copies it (the copy keeps a cached identity per + * capsule, scr_dyn_typed_ref_cache_cast). */ +static bool scr_decl_adopt_follows(const ScrDyn *child) { + return child && (child->kind == SCR_DYN_OBJ || child->kind == SCR_DYN_ARR); +} + /* `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) { @@ -90,15 +105,16 @@ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsu scr_dyn_release(capsule); return true; } - if (!scr_decl_adopt_plain(n, path)) { + if (!scr_decl_adopt_plain(n, path, list->lenient)) { scr_dyn_release(capsule); - return false; + return list->lenient; } 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 && list->lenient) return true; 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; @@ -112,7 +128,7 @@ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsu 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; + if (!scr_decl_adopt_follows(child)) 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); @@ -135,7 +151,7 @@ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsu 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; + if (!element || !scr_decl_adopt_follows(child)) continue; ScrDynPath p = { path, NULL, i }; if (!scr_decl_adopt_walk(list, child, scr_dyn_retain(element), &p, depth + 1)) { scr_dyn_release(view); @@ -150,7 +166,7 @@ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsu 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; + if (!scr_decl_adopt_follows(child)) 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); @@ -162,8 +178,8 @@ static bool scr_decl_adopt_walk(ScrDeclAdoptList *list, ScrDyn *n, ScrDyn *capsu return true; } -void scr_dyn_decl_adopt(ScrDyn *source, ScrDyn *capsule) { - ScrDeclAdoptList list = { NULL, 0, 0 }; +static void scr_decl_adopt_run(ScrDyn *source, ScrDyn *capsule, bool lenient) { + ScrDeclAdoptList list = { NULL, 0, 0, lenient }; 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]; @@ -172,3 +188,16 @@ void scr_dyn_decl_adopt(ScrDyn *source, ScrDyn *capsule) { } free(list.items); } + +void scr_dyn_decl_adopt(ScrDyn *source, ScrDyn *capsule) { + scr_decl_adopt_run(source, capsule, false); +} + +/* Any other checked cast of a dynamic value to a record or array + * (`value as T`): the same adoption, so a source that something else still + * holds keeps one identity with the built value. A node that is not plain + * data, or that the value holds in two places, keeps the separate copy the + * cast built (the conversion already succeeded; nothing throws here). */ +void scr_dyn_adopt_shared(ScrDyn *source, ScrDyn *capsule) { + scr_decl_adopt_run(source, capsule, true); +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 2d916a825..eb752a52b 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -393,6 +393,7 @@ const ScrDynTypedRecordOps *scr_dyn_typed_ref_live_record(const ScrDyn *d); * 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); +void scr_dyn_adopt_shared(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 diff --git a/tests/corpus/dyn-cast-identity-fresh.ts b/tests/corpus/dyn-cast-identity-fresh.ts new file mode 100644 index 000000000..f0ef0fd53 --- /dev/null +++ b/tests/corpus/dyn-cast-identity-fresh.ts @@ -0,0 +1,21 @@ +// Casts of values nothing else holds yet: a parse result and a response +// body. They convert outright; the results are ordinary records. + +type Point = { x: number; y: number }; +type Doc = { title: string; points: Point[] }; + +const parsed = JSON.parse('{"title":"t","points":[{"x":1,"y":2},{"x":3,"y":4}]}') as Doc; +parsed.points[0]!.x = 10; +parsed.points.push({ x: 5, y: 6 }); +console.log("parse", parsed.title, parsed.points.length, JSON.stringify(parsed.points[0])); + +const again = JSON.parse('{"x":1,"y":2}') as Point; +const other = JSON.parse('{"x":1,"y":2}') as Point; +console.log("separate parses", again === other, again.x === other.x); + +async function main(): Promise { + const body = (await new Response('{"title":"r","points":[{"x":7,"y":8}]}').json()) as Doc; + body.points[0]!.y = 80; + console.log("response", body.title, JSON.stringify(body.points)); +} +main(); diff --git a/tests/corpus/dyn-cast-identity-holders.ts b/tests/corpus/dyn-cast-identity-holders.ts new file mode 100644 index 000000000..55b311462 --- /dev/null +++ b/tests/corpus/dyn-cast-identity-holders.ts @@ -0,0 +1,62 @@ +// Checked casts of dynamic values held by parameters, fields, closures and +// containers keep one identity with the holder's value. + +type Point = { x: number; y: number }; + +// A parameter: the caller's value and the cast result are one object. +function asPoint(value: unknown): Point { + return value as Point; +} +const shared: unknown = JSON.parse('{"x":1,"y":2}'); +const fromParam = asPoint(shared); +fromParam.x = 5; +console.log("parameter", (shared as Point).x, asPoint(shared) === fromParam); + +// A field of a class instance. +class Holder { + data: unknown; + constructor(data: unknown) { + this.data = data; + } +} +const holder = new Holder(JSON.parse('{"x":3,"y":4}')); +const fromField = holder.data as Point; +fromField.y = 40; +console.log("field", JSON.stringify(holder.data), (holder.data as Point) === fromField); + +// A value captured by a closure. +const captured: unknown = JSON.parse('{"x":0,"y":0}'); +const bump = (): void => { + (captured as Point).x++; +}; +bump(); +bump(); +console.log("closure", (captured as Point).x); + +// Elements of an unknown[] container. +const list: unknown[] = [JSON.parse('{"x":1,"y":1}'), JSON.parse('{"x":2,"y":2}')]; +const second = list[1] as Point; +second.x = 20; +console.log("container", JSON.stringify(list), (list[1] as Point) === second); + +// A union target with a record arm. +const maybe: unknown = JSON.parse('{"x":7,"y":8}'); +const viaUnion = maybe as Point | undefined; +if (viaUnion) viaUnion.x = 70; +console.log("union", (maybe as Point).x); + +// A record whose field is an array, mutated through both casts. +type Bag = { items: number[] }; +const bagRaw: unknown = JSON.parse('{"items":[1]}'); +const bag = bagRaw as Bag; +const items = (bagRaw as { items: unknown }).items as number[]; +items.push(2); +bag.items.push(3); +console.log("record array", bag.items.join(","), items.length, bag.items === items); + +// A native value boxed as unknown and cast back to its own type. +const nativeList: number[] = [1, 2]; +const boxedList: unknown = nativeList; +const backList = boxedList as number[]; +backList.push(3); +console.log("native array", nativeList.length, backList === nativeList); diff --git a/tests/corpus/dyn-cast-identity-workers.ts b/tests/corpus/dyn-cast-identity-workers.ts new file mode 100644 index 000000000..9ee2e42b9 --- /dev/null +++ b/tests/corpus/dyn-cast-identity-workers.ts @@ -0,0 +1,23 @@ +// Structured clones a worker receives: workerData and message payloads. +// Two casts of workerData are one object; a message payload converts once. +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +type Job = { name: string; point: { x: number; y: number }; tags: string[] }; + +if (isMainThread) { + const worker = new Worker(new URL(import.meta.url), { + workerData: { name: "job", point: { x: 1, y: 2 }, tags: ["a"] }, + }); + worker.on("message", (message: unknown) => { + const reply = message as Job; + reply.tags.push("main"); + console.log("reply", reply.name, reply.point.x, reply.tags.join(",")); + }); +} else { + const job = workerData as Job; + const same = workerData as Job; + job.point.x = 10; + same.tags.push("worker"); + console.log("worker", same.point.x, job.tags.join(","), job === same); + parentPort!.postMessage(job); +} diff --git a/tests/corpus/dyn-cast-identity.ts b/tests/corpus/dyn-cast-identity.ts new file mode 100644 index 000000000..5ac30c52f --- /dev/null +++ b/tests/corpus/dyn-cast-identity.ts @@ -0,0 +1,71 @@ +// A checked cast of a dynamic value to a record or array (`value as T`) +// keeps the value's identity: every cast of the same source, and the source +// itself, read and write one object. + +type Point = { x: number; y: number }; + +// Two casts of one parsed object alias each other and the source. +const raw: unknown = JSON.parse('{"x":1,"y":2}'); +const a = raw as Point; +const b = raw as Point; +a.x = 10; +b.y = 20; +console.log("aliasing", a.x, a.y, b.x, b.y); +console.log("identity", a === b, (raw as Point) === a); +console.log("source", JSON.stringify(raw)); + +// Nested objects keep their identity through the outer and inner casts. +type Outer = { name: string; inner: { v: number } }; +const nestedRaw: unknown = JSON.parse('{"name":"o","inner":{"v":1}}'); +const outer = nestedRaw as Outer; +const inner = (nestedRaw as { inner: unknown }).inner as { v: number }; +inner.v = 2; +console.log("nested", outer.inner.v, outer.inner === inner); +outer.inner.v = 3; +console.log("nested back", inner.v); + +// Arrays: pushes and element writes show through every cast. +const listRaw: unknown = JSON.parse("[1,2]"); +const first = listRaw as number[]; +const second = listRaw as number[]; +first.push(3); +second[0] = 9; +console.log("array", first.length, second.length, first[0], first === second, JSON.stringify(listRaw)); + +// Arrays of records. +type Item = { id: number; tags: string[] }; +const itemsRaw: unknown = JSON.parse('[{"id":1,"tags":["a"]},{"id":2,"tags":[]}]'); +const items = itemsRaw as Item[]; +const again = itemsRaw as Item[]; +items[0]!.tags.push("b"); +again[1]!.id = 7; +console.log("array of records", again[0]!.tags.join(","), items[1]!.id, items[0] === again[0]); + +// The source stays an ordinary object for untyped reads and writes. +const bagRaw: unknown = JSON.parse('{"y":2,"x":1,"extra":"e"}'); +const point = bagRaw as Point; +const bag = bagRaw as Record; +bag.z = 3; +console.log("added", JSON.stringify(bagRaw), Object.keys(bag).join(",")); +delete bag.extra; +console.log("deleted", JSON.stringify(bagRaw), JSON.stringify(point)); +for (const k in bag) console.log("key", k, bag[k]); +point.x = 5; +console.log("typed write", bag.x); + +// A native record boxed as unknown comes back as itself. +const native: Point = { x: 1, y: 2 }; +const boxed: unknown = native; +const unboxed = boxed as Point; +unboxed.x = 10; +console.log("native", native.x, unboxed === native); + +// A fresh parse result has no other holder. +const fresh = JSON.parse('{"x":4,"y":5}') as Point; +console.log("fresh", fresh.x + fresh.y); + +// A frozen source keeps working as a copy. +const frozen: unknown = Object.freeze(JSON.parse('{"x":1,"y":2}')); +const held = [frozen]; +const thawed = frozen as Point; +console.log("frozen", thawed.x, held.length, Object.isFrozen(frozen)); diff --git a/tests/corpus/dyn-literal-identity.ts b/tests/corpus/dyn-literal-identity.ts new file mode 100644 index 000000000..915b8f73e --- /dev/null +++ b/tests/corpus/dyn-literal-identity.ts @@ -0,0 +1,83 @@ +// Object and array literals in untyped contexts (an `unknown` or `any` +// slot, an `unknown[]` element, a thrown value, an `unknown`/`any` +// parameter) keep one identity through later checked casts. + +type Counter = { count: number }; +type Item = { name: string; tags: string[] }; + +// The original example: a write through the cast shows through the holder. +const holder: any = { count: 1 }; +const item = holder as Counter; +item.count++; +console.log("any holder", holder.count, item === holder); + +const box: unknown = { count: 10 }; +const viewA = box as Counter; +const viewB = box as Counter; +viewA.count += 5; +console.log("unknown holder", viewB.count, viewA === viewB, JSON.stringify(box)); + +// Elements of an unknown[] array literal. +const values: unknown[] = [ + { name: "a", tags: ["x"] }, + { name: "b", tags: [] }, +]; +const second = values[1] as Item; +second.tags.push("y"); +console.log("unknown[] element", JSON.stringify(values), (values[1] as Item) === second); + +// Nested literals inside an untyped literal. +const tree: unknown = { name: "root", children: [{ name: "leaf", tags: ["t"] }] }; +const root = tree as { name: string; children: Item[] }; +const leaf = (tree as { children: unknown[] }).children[0] as Item; +leaf.tags.push("u"); +console.log("nested", root.children[0]!.tags.join(","), root.children[0] === leaf); + +// A thrown literal. +try { + throw { code: "E_BUSY", attempts: [1] }; +} catch (error) { + const failure = error as { code: string; attempts: number[] }; + failure.attempts.push(2); + console.log("thrown", failure.code, (error as { attempts: number[] }).attempts.length, failure === error); +} + +// Literals passed to unknown and any parameters. +function remember(value: unknown): Counter { + const counter = value as Counter; + counter.count *= 2; + return value as Counter; +} +console.log("unknown parameter", remember({ count: 4 }).count); + +function relabel(value: any): Item { + const target = value as Item; + target.name = target.name.toUpperCase(); + return value; +} +const relabeled = relabel({ name: "pkg", tags: [] }); +console.log("any parameter", relabeled.name, relabeled.tags.length); + +// A literal returned as unknown, and one stored in an unknown field. +function make(): unknown { + return { count: 0 }; +} +const made = make(); +(made as Counter).count = 7; +console.log("returned", (made as Counter).count); + +class Slot { + value: unknown = { name: "slot", tags: ["s"] }; +} +const slot = new Slot(); +(slot.value as Item).tags.push("t"); +console.log("field", JSON.stringify(slot.value)); + +// Untyped literals keep JavaScript's own key order and extra keys. +const extra: unknown = { name: "e", z: 1, tags: [], a: 2 }; +const typed = extra as Item; +typed.tags.push("k"); +console.log("extra keys", JSON.stringify(extra), Object.keys(typed).join(",")); + +// Literals handed to library calls are unchanged. +console.log(JSON.stringify({ count: 3 }), [{ n: 1 }].length); diff --git a/tests/harness/corpus-costs/dyn-cast-identity-fresh.ts.json b/tests/harness/corpus-costs/dyn-cast-identity-fresh.ts.json new file mode 100644 index 000000000..8c0e66c3c --- /dev/null +++ b/tests/harness/corpus-costs/dyn-cast-identity-fresh.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } diff --git a/tests/harness/corpus-costs/dyn-cast-identity-holders.ts.json b/tests/harness/corpus-costs/dyn-cast-identity-holders.ts.json new file mode 100644 index 000000000..8c0e66c3c --- /dev/null +++ b/tests/harness/corpus-costs/dyn-cast-identity-holders.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } diff --git a/tests/harness/corpus-costs/dyn-cast-identity-workers.ts.json b/tests/harness/corpus-costs/dyn-cast-identity-workers.ts.json new file mode 100644 index 000000000..8c0e66c3c --- /dev/null +++ b/tests/harness/corpus-costs/dyn-cast-identity-workers.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } diff --git a/tests/harness/corpus-costs/dyn-cast-identity.ts.json b/tests/harness/corpus-costs/dyn-cast-identity.ts.json new file mode 100644 index 000000000..8c0e66c3c --- /dev/null +++ b/tests/harness/corpus-costs/dyn-cast-identity.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } diff --git a/tests/harness/corpus-costs/dyn-literal-identity.ts.json b/tests/harness/corpus-costs/dyn-literal-identity.ts.json new file mode 100644 index 000000000..8c0e66c3c --- /dev/null +++ b/tests/harness/corpus-costs/dyn-literal-identity.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3600 } diff --git a/tests/harness/dyncheck.test.ts b/tests/harness/dyncheck.test.ts index e1a869048..57bf9ecf9 100644 --- a/tests/harness/dyncheck.test.ts +++ b/tests/harness/dyncheck.test.ts @@ -143,6 +143,33 @@ describe(`dynamic-boundary checks (scriptc-only${sanitize ? ", sanitized" : ""}) ).toBe(""); }); + test("a boxed native value cast to another layout is copied (decision D3)", async () => { + // Node's `as` is a no-op, so these writes would show through every + // holder. scriptc copies a boxed native value into a cast's different + // layout for now (the object model's decision D3: copy, throw, or + // share storage); the copy keeps one identity per boxed value. + const r = await compileAndRun( + "native-layout-casts", + `type Point = { x: number; y: number }; +const dict: Record = { x: 1, y: 2 }; +const v: unknown = dict; +const p = v as Point; +p.x = 5; +console.log(dict.x, p.x, (v as Point) === p); +const nums: number[] = [1, 2]; +const u: unknown = nums; +console.log((u as number[]) === nums); +const wide = u as (number | string)[]; +wide.push("s"); +console.log(nums.length, wide.length); +console.log(JSON.stringify({ ...(v as Record) })); +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout).toBe(["1 5 true", "true", "2 3", '{"x":1,"y":2}', ""].join("\n")); + expect(r.stderr).toBe(""); + }); + test("wrong-typed field throws with the path", async () => { const r = await compileAndRun( "wrong-type", From 4c3a1d9c98b87320c0271c9d1b5b50489141e8f6 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 18:53:25 -0500 Subject: [PATCH 160/230] Link the Linux release helpers against LLVM built with ThinLTO and a helper profile The Linux helpers linked LLVM 22.1.8's release archive. The helper's code generation is LLVM's code, so building that LLVM from its release sources with ThinLTO and with a profile of the helper's own work makes code generation 5-9% faster than the helper as released, with identical objects. - native/llvm-codegen/llvm.profdata.gz is the profile, collected by scripts/refresh-llvm-profile.mjs: it builds LLVM with IR instrumentation and an instrumented helper, and runs that helper on tsc-ts, five Effect fixtures, the runtime benchmark workloads and every 50th corpus program (scripts/llvm-helper-inputs.mjs). It follows LLVM's sources, so it needs a refresh when LLVM is bumped. - scripts/build-llvm-dist.mjs builds LLVM with that profile and ThinLTO, and packs it with a checksum. The llvm-dist workflow, run by hand, builds it for x64 and arm64, requires a helper linked against it to give the same objects as one linked against the release archive (scripts/check-llvm-dist.mjs), prints both helpers' times and the profile's mismatch count, and attaches the archive to the LLVM_DIST_TAG release of this repository. - The release workflow's Linux jobs, and the wasm32-wasi job, which also uploads the x64 GNU helper, download that archive and build the helper with SCRIPTC_LLVM_THINLTO=1: clang, lld and ThinLTO, and the profile for the helper's own sources. Without the archive, or if that build fails, the helper links the release archive as before. - The four Linux helper packages share their build in native/llvm-codegen/linux-helper.mjs. Without SCRIPTC_LLVM_THINLTO it builds the same executable as before. --- .github/workflows/llvm-dist.yml | 93 ++++++++++++++ .github/workflows/release.yml | 37 +++++- native/llvm-codegen/CMakeLists.txt | 33 +++++ native/llvm-codegen/README.md | 17 +++ native/llvm-codegen/linux-helper.mjs | 79 ++++++++++++ native/llvm-codegen/llvm-dist.mjs | 96 +++++++++++++++ native/llvm-codegen/llvm.profdata.gz | Bin 0 -> 6404815 bytes .../llvm-linux-arm64-gnu/scripts/build.mjs | 40 ++---- .../llvm-linux-arm64-musl/scripts/build.mjs | 42 ++----- packages/llvm-linux-x64-gnu/scripts/build.mjs | 39 ++---- .../llvm-linux-x64-musl/scripts/build.mjs | 41 ++----- scripts/build-llvm-dist.mjs | 96 +++++++++++++++ scripts/check-llvm-dist.mjs | 105 ++++++++++++++++ scripts/llvm-helper-inputs.mjs | 114 ++++++++++++++++++ scripts/refresh-llvm-profile.mjs | 112 +++++++++++++++++ tests/harness/llvm-dist.test.ts | 74 ++++++++++++ 16 files changed, 893 insertions(+), 125 deletions(-) create mode 100644 .github/workflows/llvm-dist.yml create mode 100644 native/llvm-codegen/linux-helper.mjs create mode 100644 native/llvm-codegen/llvm-dist.mjs create mode 100644 native/llvm-codegen/llvm.profdata.gz create mode 100644 scripts/build-llvm-dist.mjs create mode 100644 scripts/check-llvm-dist.mjs create mode 100644 scripts/llvm-helper-inputs.mjs create mode 100644 scripts/refresh-llvm-profile.mjs create mode 100644 tests/harness/llvm-dist.test.ts diff --git a/.github/workflows/llvm-dist.yml b/.github/workflows/llvm-dist.yml new file mode 100644 index 000000000..233e97e98 --- /dev/null +++ b/.github/workflows/llvm-dist.yml @@ -0,0 +1,93 @@ +name: LLVM distribution + +# Builds the LLVM that the Linux release helpers link +# (native/llvm-codegen/llvm-dist.mjs): LLVM from its release sources with +# ThinLTO and native/llvm-codegen/llvm.profdata.gz, for each Linux +# architecture. Each job builds a helper against it and one against LLVM's +# release archive, requires the two to give identical objects on this +# checkout's helper inputs (scripts/check-llvm-dist.mjs), prints both +# helpers' times, and attaches the archive to the LLVM_DIST_TAG release. +# The arm64 build uses the profile collected on x64; its job log reports how +# many functions the profile no longer matches. Run it after a profile refresh +# or an LLVM version change, with a new LLVM_DIST_TAG. +on: + workflow_dispatch: + +permissions: + contents: read + +concurrency: ${{ github.workflow }} + +jobs: + build: + name: Build LLVM (${{ matrix.arch }}) + if: github.repository == 'vercel-labs/scriptc' + strategy: + fail-fast: false + matrix: + include: + - arch: x64 + runner: ubuntu-24.04 + llvm_asset: LLVM-22.1.8-Linux-X64 + - arch: arm64 + runner: ubuntu-24.04-arm + llvm_asset: LLVM-22.1.8-Linux-ARM64 + runs-on: ${{ matrix.runner }} + # About an hour for LLVM on 4 cores, and 5 minutes for each helper link. + timeout-minutes: 240 + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 11.1.3 + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: pnpm + - name: Install LLVM's release archive and sources + run: | + sudo apt-get update + sudo apt-get install --yes ninja-build zlib1g-dev libzstd-dev + base=https://github.com/llvm/llvm-project/releases/download/llvmorg-22.1.8 + curl -fL --retry 3 -o "$RUNNER_TEMP/llvm.tar.xz" "$base/${{ matrix.llvm_asset }}.tar.xz" + tar -xJf "$RUNNER_TEMP/llvm.tar.xz" -C "$RUNNER_TEMP" + mv "$RUNNER_TEMP/${{ matrix.llvm_asset }}" "$RUNNER_TEMP/llvm-22.1.8" + curl -fL --retry 3 -o "$RUNNER_TEMP/llvm-src.tar.xz" "$base/llvm-project-22.1.8.src.tar.xz" + tar -xJf "$RUNNER_TEMP/llvm-src.tar.xz" -C "$RUNNER_TEMP" + rm "$RUNNER_TEMP/llvm.tar.xz" "$RUNNER_TEMP/llvm-src.tar.xz" + echo "$RUNNER_TEMP/llvm-22.1.8/bin" >> "$GITHUB_PATH" + - run: pnpm install --frozen-lockfile + - run: pnpm --filter @scriptc/compiler --filter scriptc build + - name: Build the distribution + run: >- + node scripts/build-llvm-dist.mjs --source "$RUNNER_TEMP/llvm-project-22.1.8.src" + --prefix "$RUNNER_TEMP/dist/scriptc-llvm-22.1.8-linux-${{ matrix.arch }}" + --archive "$RUNNER_TEMP/scriptc-llvm-22.1.8-linux-${{ matrix.arch }}.tar.xz" + --work "$RUNNER_TEMP/llvm-dist-work" + - name: Build a helper against each LLVM + env: + HELPER: llvm-linux-${{ matrix.arch }}-gnu + run: | + LLVM_DIR="$RUNNER_TEMP/llvm-22.1.8/lib/cmake/llvm" pnpm --filter "@scriptc/$HELPER" build:native + cp "packages/$HELPER/bin/scriptc-llvm-codegen" "$RUNNER_TEMP/helper-release-archive" + LLVM_DIR="$RUNNER_TEMP/dist/scriptc-llvm-22.1.8-linux-${{ matrix.arch }}/lib/cmake/llvm" \ + SCRIPTC_LLVM_THINLTO=1 pnpm --filter "@scriptc/$HELPER" build:native + cp "packages/$HELPER/bin/scriptc-llvm-codegen" "$RUNNER_TEMP/helper-profiled" + - name: Compare the helpers + run: >- + node scripts/check-llvm-dist.mjs --baseline "$RUNNER_TEMP/helper-release-archive" + --candidate "$RUNNER_TEMP/helper-profiled" --rounds 3 --work "$RUNNER_TEMP/llvm-check" + - name: Attach the archive to the release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ARCHIVE: ${{ runner.temp }}/scriptc-llvm-22.1.8-linux-${{ matrix.arch }}.tar.xz + run: | + tag=$(node --input-type=module -e "import { LLVM_DIST_TAG } from './native/llvm-codegen/llvm-dist.mjs'; console.log(LLVM_DIST_TAG)") + # Both architectures' jobs create the release if it is missing. + gh release view "$tag" > /dev/null 2>&1 || + gh release create "$tag" --target "$GITHUB_SHA" --title "$tag" --latest=false \ + --notes "LLVM 22.1.8 built with ThinLTO and the helper profile, for the Linux LLVM helpers (scripts/build-llvm-dist.mjs)." || + gh release view "$tag" > /dev/null + gh release upload "$tag" "$ARCHIVE" "$ARCHIVE.sha256" --clobber diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 799337e0a..3f19a4858 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -173,6 +173,7 @@ jobs: runtime: runtime-linux-x64-gnu cli: cli-linux-x64-gnu llvm_asset: LLVM-22.1.8-Linux-X64 + llvm_dist: x64 use_zig: true - platform: linux-arm64 runner: ubuntu-24.04-arm @@ -180,6 +181,7 @@ jobs: runtime: runtime-linux-arm64-gnu cli: cli-linux-arm64-gnu llvm_asset: LLVM-22.1.8-Linux-ARM64 + llvm_dist: arm64 use_zig: true - platform: windows-x64 runner: windows-2022 @@ -195,6 +197,7 @@ jobs: seed_helper: llvm-linux-x64-gnu cli_target: x86_64-linux-musl llvm_asset: LLVM-22.1.8-Linux-X64 + llvm_dist: x64 use_zig: true - platform: linux-arm64-musl runner: ubuntu-24.04-arm @@ -204,12 +207,14 @@ jobs: seed_helper: llvm-linux-arm64-gnu cli_target: aarch64-linux-musl llvm_asset: LLVM-22.1.8-Linux-ARM64 + llvm_dist: arm64 use_zig: true - platform: wasm32-wasi runner: ubuntu-24.04 helper: llvm-linux-x64-gnu runtime: runtime-wasm32-wasi llvm_asset: LLVM-22.1.8-Linux-X64 + llvm_dist: x64 use_zig: true runs-on: ${{ matrix.runner }} # The Intel macOS build took 24 minutes for 0.3.2 and passed 30 for 0.3.3. @@ -240,6 +245,28 @@ jobs: mv "$RUNNER_TEMP/${{ matrix.llvm_asset }}" "$RUNNER_TEMP/llvm-22.1.8" echo "LLVM_DIR=$RUNNER_TEMP/llvm-22.1.8/lib/cmake/llvm" >> "$GITHUB_ENV" echo "$RUNNER_TEMP/llvm-22.1.8/bin" >> "$GITHUB_PATH" + # The Linux helpers link LLVM built with ThinLTO and a profile of the + # helper's code generation (native/llvm-codegen/llvm-dist.mjs), from a + # release of this repository. Without that archive they link LLVM's + # release archive, which gives the same objects more slowly. The + # wasm32-wasi job also uploads the x64 GNU helper, so it selects the same. + - name: Select the profiled LLVM distribution (Linux) + if: matrix.llvm_dist != '' + env: + ARCH: ${{ matrix.llvm_dist }} + run: | + tag=$(node --input-type=module -e "import { LLVM_DIST_TAG } from './native/llvm-codegen/llvm-dist.mjs'; console.log(LLVM_DIST_TAG)") + asset=$(node --input-type=module -e "import { llvmDistAsset } from './native/llvm-codegen/llvm-dist.mjs'; console.log(llvmDistAsset(process.env.ARCH))") + base="https://github.com/$GITHUB_REPOSITORY/releases/download/$tag" + if curl -fsSL --retry 3 -o "$RUNNER_TEMP/$asset" "$base/$asset" && + curl -fsSL --retry 3 -o "$RUNNER_TEMP/$asset.sha256" "$base/$asset.sha256" && + (cd "$RUNNER_TEMP" && sha256sum --check "$asset.sha256"); then + tar -xJf "$RUNNER_TEMP/$asset" -C "$RUNNER_TEMP" + echo "SCRIPTC_HELPER_LLVM_DIR=$RUNNER_TEMP/${asset%.tar.xz}/lib/cmake/llvm" >> "$GITHUB_ENV" + echo "The helper links $asset from release $tag" + else + echo "::warning::$asset is not available from release $tag; the helper links LLVM's release archive" + fi - uses: vercel-labs/setup-zig@v1 if: matrix.use_zig == true with: @@ -278,7 +305,15 @@ jobs: - name: Build native packages (POSIX) if: matrix.platform != 'windows-x64' run: | - pnpm --filter @scriptc/${{ matrix.helper }} build:native + if [ -n "${SCRIPTC_HELPER_LLVM_DIR:-}" ]; then + LLVM_DIR="$SCRIPTC_HELPER_LLVM_DIR" SCRIPTC_LLVM_THINLTO=1 pnpm --filter @scriptc/${{ matrix.helper }} build:native || { + echo "::warning::the helper did not build against the profiled LLVM; it links LLVM's release archive" + rm -rf node_modules/.cache/scriptc-${{ matrix.helper }} + pnpm --filter @scriptc/${{ matrix.helper }} build:native + } + else + pnpm --filter @scriptc/${{ matrix.helper }} build:native + fi if [ "${{ matrix.use_zig }}" = true ]; then CC=zig AR=zig pnpm --filter @scriptc/${{ matrix.runtime }} build:native else diff --git a/native/llvm-codegen/CMakeLists.txt b/native/llvm-codegen/CMakeLists.txt index 07abb35ec..7ce20086f 100644 --- a/native/llvm-codegen/CMakeLists.txt +++ b/native/llvm-codegen/CMakeLists.txt @@ -17,6 +17,14 @@ set(SCRIPTC_HELPER_ARCH "arm64" CACHE STRING # Linux packages ship this process-isolated helper as a fully static host # executable so it runs independently of the destination libc and libstdc++. option(SCRIPTC_STATIC_LINUX_BUILD "Build a static Linux helper executable" OFF) +# The release Linux helpers link an LLVM built with ThinLTO and a profile of +# the helper's code generation (llvm-dist.mjs). Its archives hold bitcode, so +# the helper compiles and links with ThinLTO too, which needs clang and lld. +option(SCRIPTC_LLVM_THINLTO "Compile and link the helper with ThinLTO" OFF) +set(SCRIPTC_LLVM_PROFILE_USE "" CACHE FILEPATH + "Indexed profile the helper's own sources are optimized with") +option(SCRIPTC_LLVM_PROFILE_GENERATE + "Instrument the helper's sources to collect that profile" OFF) # These must be cache variables before project() enables the Apple toolchain. # A target property named OSX_DEPLOYMENT_TARGET is not recognized by CMake and @@ -219,6 +227,31 @@ else() endif() target_link_libraries(scriptc-llvm-codegen PRIVATE ${SCRIPTC_LLVM_LIBS} ${SCRIPTC_LLVM_SYSTEM_LIBS}) +if(SCRIPTC_LLVM_THINLTO) + target_compile_options(scriptc-llvm-codegen PRIVATE -flto=thin) + # The thin link optimizes and compiles all of LLVM's code the helper + # reaches; the cache makes a relink after a helper edit incremental. + target_link_options(scriptc-llvm-codegen PRIVATE + -flto=thin + -fuse-ld=lld + "LINKER:--thinlto-jobs=all" + "LINKER:--thinlto-cache-dir=${CMAKE_BINARY_DIR}/thinlto-cache" + ) +endif() +if(SCRIPTC_LLVM_PROFILE_USE) + # The profile covers LLVM's sources and the helper's. Functions it does + # not cover, or whose control flow changed since, are optimized without it. + target_compile_options(scriptc-llvm-codegen PRIVATE + "-fprofile-use=${SCRIPTC_LLVM_PROFILE_USE}" + -Wno-profile-instr-unprofiled + -Wno-profile-instr-out-of-date + ) +endif() +if(SCRIPTC_LLVM_PROFILE_GENERATE) + target_compile_options(scriptc-llvm-codegen PRIVATE -fprofile-generate) + target_link_options(scriptc-llvm-codegen PRIVATE -fprofile-generate) +endif() + if(APPLE) # Homebrew's static LLVM archives contain broad component object files. # Export only the process entry point so archive-global LLVM symbols do not diff --git a/native/llvm-codegen/README.md b/native/llvm-codegen/README.md index 70c118202..02ac6e3ef 100644 --- a/native/llvm-codegen/README.md +++ b/native/llvm-codegen/README.md @@ -58,3 +58,20 @@ scriptc-llvm-codegen prune-import --input unit.opt.bc --output unit.bc ``` Both commands keep only what a program build can import: the importable bodies within the size limit at their call distance from any public function, and the constants they copy. Every other definition becomes a declaration, so a program imports exactly what it would from the whole unit while reading a fraction of it. Unpromoted unit-local symbols stay local, so release bitcode offers only bodies that need none of them. + +## Linux release builds + +The Linux helper packages are released linked against LLVM 22.1.8 built from its release sources with ThinLTO and with `llvm.profdata.gz`, a profile of the helper's own code generation (`llvm-dist.mjs`). The helper's code generation is 5-9% faster with it than when the helper links LLVM's release archive, and its objects are identical. The release workflow downloads that build from the `LLVM_DIST_TAG` release of this repository; when the archive is missing, the helper links LLVM's release archive instead. + +To build and use it locally, with clang, lld and llvm-profdata 22.1.8 on `PATH` (LLVM's release archive for the host has them): + +```console +$ node scripts/build-llvm-dist.mjs --source llvm-project-22.1.8.src --prefix +$ LLVM_DIR=/lib/cmake/llvm SCRIPTC_LLVM_THINLTO=1 pnpm --filter @scriptc/llvm-linux-x64-gnu build:native +``` + +`SCRIPTC_LLVM_THINLTO=1` compiles and links the helper with clang, lld and ThinLTO, since that LLVM's archives hold bitcode, and optimizes the helper's own sources with the same profile. The link takes about 4 minutes on 4 cores. + +The `llvm-dist` workflow (run by hand) builds the archive for x64 and arm64, checks that a helper linked against it gives the same objects as one linked against LLVM's release archive (`scripts/check-llvm-dist.mjs`), and attaches it to the release. The arm64 build uses the profile collected on x64, and its log reports how many functions the profile does not match there. + +The profile depends on LLVM's sources and the kind of code scriptc emits, not on scriptc's commits. `scripts/refresh-llvm-profile.mjs` collects a new one after an LLVM version change: it builds LLVM with IR instrumentation and an instrumented helper, runs that helper on tsc-ts, five Effect fixtures, the runtime benchmark workloads and every 50th corpus program (`scripts/llvm-helper-inputs.mjs`), and merges the result. A new profile or LLVM version gets a new `LLVM_DIST_TAG`. diff --git a/native/llvm-codegen/linux-helper.mjs b/native/llvm-codegen/linux-helper.mjs new file mode 100644 index 000000000..68d738af6 --- /dev/null +++ b/native/llvm-codegen/linux-helper.mjs @@ -0,0 +1,79 @@ +/* The static Linux helper build shared by the four Linux helper packages, + * scripts/refresh-llvm-profile.mjs and scripts/check-llvm-dist.mjs. */ +import { execFileSync } from "node:child_process"; +import { + chmodSync, + copyFileSync, + existsSync, + mkdirSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { helperLlvmDistArgs } from "./llvm-dist.mjs"; +import { nativeTargetCmakeArgs } from "./targets.mjs"; + +const DATA_LAYOUTS = { + x64: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + arm64: + "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32", +}; + +/** Configures and builds the helper for `arch` (`x64` or `arm64`) and `libc` + * (`gnu` or `musl`) in `build` against LLVM_DIR, and writes the stripped + * executable to `output`. musl builds take the compilers from CC and CXX. */ +export function buildLinuxHelper({ arch, libc, version, build, output }) { + const triple = `${arch === "x64" ? "x86_64" : "aarch64"}-unknown-linux-${libc}`; + // CMake drops the other definitions when a reconfigure changes the + // compiler, so a build directory configured for another LLVM or build + // kind starts over. + const kind = JSON.stringify({ + llvm: process.env.LLVM_DIR ?? null, + thinLTO: process.env.SCRIPTC_LLVM_THINLTO === "1", + profileGenerate: process.env.SCRIPTC_LLVM_PROFILE_GENERATE === "1", + cc: process.env.CC ?? null, + cxx: process.env.CXX ?? null, + }); + const stamp = join(build, "scriptc-helper-build.json"); + if (existsSync(build) && (!existsSync(stamp) || readFileSync(stamp, "utf8") !== kind)) + rmSync(build, { recursive: true, force: true }); + mkdirSync(build, { recursive: true }); + writeFileSync(stamp, kind); + execFileSync( + "cmake", + [ + "-S", + fileURLToPath(new URL(".", import.meta.url)), + "-B", + build, + "-G", + "Ninja", + ...(libc === "musl" + ? [ + `-DCMAKE_C_COMPILER=${process.env.CC ?? "cc"}`, + `-DCMAKE_CXX_COMPILER=${process.env.CXX ?? "c++"}`, + ] + : []), + `-DLLVM_DIR=${process.env.LLVM_DIR ?? "/usr/lib/llvm-22/lib/cmake/llvm"}`, + `-DSCRIPTC_PACKAGE_VERSION=${version}`, + `-DSCRIPTC_DEFAULT_TARGET=${triple}`, + `-DSCRIPTC_DEFAULT_DATA_LAYOUT=${DATA_LAYOUTS[arch]}`, + ...nativeTargetCmakeArgs, + "-DSCRIPTC_STATIC_LINUX_BUILD=ON", + "-DCMAKE_EXE_LINKER_FLAGS=-static", + "-DCMAKE_BUILD_TYPE=Release", + // Later definitions win, so these can select clang for musl builds. + ...helperLlvmDistArgs(build), + ], + { stdio: "inherit" }, + ); + execFileSync("cmake", ["--build", build, "--target", "scriptc-llvm-codegen"], { + stdio: "inherit", + }); + mkdirSync(dirname(output), { recursive: true }); + copyFileSync(join(build, "scriptc-llvm-codegen"), output); + execFileSync("strip", [output], { stdio: "inherit" }); + chmodSync(output, 0o755); +} diff --git a/native/llvm-codegen/llvm-dist.mjs b/native/llvm-codegen/llvm-dist.mjs new file mode 100644 index 000000000..aea90dd54 --- /dev/null +++ b/native/llvm-codegen/llvm-dist.mjs @@ -0,0 +1,96 @@ +/* The LLVM distribution the Linux helpers link in releases: LLVM built from + * the release sources with ThinLTO and with a profile of the helper's own + * code generation (llvm.profdata.gz). scripts/build-llvm-dist.mjs builds it, + * the llvm-dist workflow attaches it to the LLVM_DIST_TAG release of this + * repository, and scripts/refresh-llvm-profile.mjs collects the profile. */ +import { execFileSync } from "node:child_process"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { gunzipSync } from "node:zlib"; + +export const LLVM_VERSION = "22.1.8"; +/** The release of this repository that holds the distribution archives. A + * new profile or LLVM version gets a new tag. */ +export const LLVM_DIST_TAG = "llvm-22.1.8-pgo.1"; +export const llvmProfile = fileURLToPath(new URL("./llvm.profdata.gz", import.meta.url)); + +/** The archive name for a Linux architecture (`x64` or `arm64`). */ +export function llvmDistAsset(arch) { + return `scriptc-llvm-${LLVM_VERSION}-linux-${arch}.tar.xz`; +} + +/** Writes the uncompressed profile into `dir` and returns its path. */ +export function writeLlvmProfile(dir, profile = llvmProfile) { + mkdirSync(dir, { recursive: true }); + const path = join(dir, "llvm.profdata"); + writeFileSync(path, gunzipSync(readFileSync(profile))); + return path; +} + +/** CMake arguments for an LLVM build with the helper's targets, static, + * without tools or tests. `profile` builds with that indexed profile and + * ThinLTO; `instrumented` builds the IR-instrumented libraries the profile + * is collected with. clang and lld come from PATH and must be LLVM_VERSION, + * since the profile's format and hashes follow the compiler. */ +export function llvmCmakeArgs({ source, build, prefix, profile, instrumented }) { + const version = execFileSync("clang", ["--version"], { encoding: "utf8" }); + if (!version.includes(`clang version ${LLVM_VERSION}`)) + throw new Error(`the LLVM distribution is built with clang ${LLVM_VERSION}; found ${version}`); + return [ + "-S", + join(source, "llvm"), + "-B", + build, + "-G", + "Ninja", + "-DCMAKE_BUILD_TYPE=Release", + `-DCMAKE_INSTALL_PREFIX=${prefix}`, + "-DCMAKE_C_COMPILER=clang", + "-DCMAKE_CXX_COMPILER=clang++", + "-DLLVM_USE_LINKER=lld", + "-DLLVM_TARGETS_TO_BUILD=X86;AArch64;WebAssembly", + "-DLLVM_ENABLE_PROJECTS=", + "-DLLVM_ENABLE_ASSERTIONS=OFF", + "-DLLVM_ENABLE_ZLIB=FORCE_ON", + "-DLLVM_ENABLE_ZSTD=FORCE_ON", + "-DLLVM_USE_STATIC_ZSTD=ON", + "-DLLVM_ENABLE_TERMINFO=OFF", + "-DLLVM_ENABLE_LIBXML2=OFF", + "-DLLVM_ENABLE_LIBEDIT=OFF", + "-DLLVM_ENABLE_LIBPFM=OFF", + "-DLLVM_INCLUDE_TESTS=OFF", + "-DLLVM_INCLUDE_EXAMPLES=OFF", + "-DLLVM_INCLUDE_BENCHMARKS=OFF", + "-DLLVM_INCLUDE_DOCS=OFF", + "-DLLVM_BUILD_TOOLS=OFF", + ...(instrumented ? ["-DLLVM_BUILD_INSTRUMENTED=IR", "-DLLVM_BUILD_RUNTIME=OFF"] : []), + ...(profile ? ["-DLLVM_ENABLE_LTO=Thin", `-DLLVM_PROFDATA_FILE=${profile}`] : []), + ]; +} + +/** Extra CMake arguments for a Linux helper package build. With + * SCRIPTC_LLVM_THINLTO=1, LLVM_DIR names a distribution from + * scripts/build-llvm-dist.mjs: its archives hold ThinLTO bitcode, so the + * helper compiles and links with clang, lld and ThinLTO, and its own sources + * use the same profile. SCRIPTC_LLVM_PROFILE_GENERATE=1 instruments the + * helper's sources against an instrumented LLVM. */ +export function helperLlvmDistArgs(build) { + const args = []; + if (process.env.SCRIPTC_LLVM_THINLTO === "1") + args.push( + "-DCMAKE_C_COMPILER=clang", + "-DCMAKE_CXX_COMPILER=clang++", + "-DSCRIPTC_LLVM_THINLTO=ON", + `-DSCRIPTC_LLVM_PROFILE_USE=${writeLlvmProfile(build)}`, + ); + else args.push("-DSCRIPTC_LLVM_THINLTO=OFF", "-DSCRIPTC_LLVM_PROFILE_USE="); + if (process.env.SCRIPTC_LLVM_PROFILE_GENERATE === "1") + args.push( + "-DCMAKE_C_COMPILER=clang", + "-DCMAKE_CXX_COMPILER=clang++", + "-DSCRIPTC_LLVM_PROFILE_GENERATE=ON", + ); + else args.push("-DSCRIPTC_LLVM_PROFILE_GENERATE=OFF"); + return args; +} diff --git a/native/llvm-codegen/llvm.profdata.gz b/native/llvm-codegen/llvm.profdata.gz new file mode 100644 index 0000000000000000000000000000000000000000..5ebb5076ffb548be48ed7012ccd255fe287abbd8 GIT binary patch literal 6404815 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

2kd&c#&BqIOm{g-B|a`> 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

V;aFX_om|2xYYe+RC_t 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

-cKkZsic_}124oYhJG5p&?`rG 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 u`Pu=c{m$S$dZv5_eNkLL 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%0DJbr}RkVp;STh_zUd_6A%g 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

7C!x5L%7#+-V#g+=-Ukra;5j)kR8tn^4ca(y+D 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%0An;Dt9Ht> z3#wUm_Wfbm6&apoR?T=|S?Ru`b;d=jEEQ*sC8ahV@EW-0tIxT3@-SuTAn(t6o_Toh z2TLpu&ndW2|0l&`KK!+EK-V=E{>7x; zeRN2#Bc|v^fZ5g5X$t<;#M!-#ZhoS}<87>Q+v-(5kXe+}?>mc|a`EFU1|E2E!+$e< zCoMEDZY8tN)a>;LjTMrHH%HN!QH10V-5D}?uwQ3FEzCwQ|HBvW%$>6GNtvuXV2t|f zU8XTqN?<4a&+$$wX@= z29gn6mf%vLChp~rT)k_-4a*Pf+4lg4GkVC>62q&^1&)=@wtbx50Fb`|vLEdy0s{^3 zQ%$_Re0hiF%c}spm_9rSD#;xw*Sw{3iRPNqY3vGMf!yZY#w$ zAr(MnwF*#LmpqAhr5SehIhn_$uuK{t@r?=9w!uo_J@nLbtiC65NKO8j*x65tl`z!b-XAX027sV=vvL z`sA^i>7M-k@|Di}ZWz4v)kAN(2|xE9({t}B7ajfK+>9H;lb}s1R_LriqK$7(wzP;! zj-HFSGb0KTm!HxJl$0q0wi<{Nh~-W^m6(eedHJka!Lub@$mXc~^bYk#X5CK>M_Ha+ zkT+wi-afwGPRJr!mWbxEm!GG~A^b@$8GvLiL;DE!*-SRr#{ifMNaS;QO>a8SvG?0w zG-|Qoedfz=HXe7~uLL5$TYFVmU6PpeM_ut~vnYF*)A3})){+qA=D6w$^F~{wgA2}? z8LIeZWbqM(|2H0E6+uO*2G*$zHr{7?zA-T|JLV&IgGOharj-O-h*1K{w2~S`hAKp) z?@cyC;HJtSE0|Vg=eT95i~NV2RMqEL!=I1Bm`fiqrIU`i;@{_u)-cC)ZiI6w zG!j(SU{@0>(3~U=>aI+C<+> zko8nY;|=00By+_D@fB}T8`6R5p&D7BacYJF-e-?ralt7MG9_dFyl%TGo88Q~DiY_^ z4NB!L`{498g8w0^GNN{sBxSLR$*^ZfkU206P1SN-MSV0SB8#(xnXd%C;MLQnIVoC- zg#rm2GQm*}I67KTfR2y}Mpb059`C>)$+gEsUqbXdva}2>cEQ&s`l0?H?yP3%7jmd} zhvFgcEbu8{Re`TS<8L;A1>z5t0sc@G@9;DN=?}2r5WPN~-J>bd;S7SSImiLleGtKv zQy$957w%fKm1+N_ddr4?UXZO*BQtt&I*U00=x(bLTf9gWMfUk70A@g$zr0?G77^Ya z76~1-ZX0FdL6bN`^u$Qin3VwgLTi^>nGkOrP<5A}hxM{bNIf?Yr_G)K$O!8 zSrAo@%BkNmabX?0tUWL zJ>a@ahQzo+ffg07IxSl2%@$uUr#GJW#>4<&eC5GjRu=&r?#5&yq>u~nBq$4y)onn% z1_b0ebu?U*8NChY@YTel$ynFrG=RM_`A$}U6#oehQl)&~0Z|ni2l5j^AphLFsT+*c zHG}@!Z0Ah*<1d+?s5^(fe8Xftq@)QM2_k1%IMo$Sli*B11-TG3)rt*E_5!6Gt)%D~+p;I+P1{QHJdpF}FM92li)%Bl4+Rye3D8}dL!v}kP&eMy z)*(Uox~Rz`Gr8+0SDp6bA=~=O?UON!R>7P5scH$Xi$QFfFchsf2Rpw8j=wRfGZcNz zO#jY*=o_2X?7xNC{VX9;n?>c?;uWbf>Pdr0&-3u(frRo+8;X6YI!75I!=1-|lJ3VcRw zFj^@q@L>r)C-14_7oPf>tz_G80}m8RVFz<&2{(p&;;Am66cC&v8$}@8736OiV>rT? zSJMglz+>u8jo%`}PUeH7#(cVQ_mP?i0|e?-4y@dSlsi0P0^E%GM=#$phu{9MXPmSz z@7|Z}w1T6WKyw`CYAw;U6_kV(MaY#4)XOs}t)Knw{ZHhcPl`x!&wCmQp{nt6`)TDl zIz-cV15IG6jjdG^CV|QMISEgeST&Fc;NIsNcDp6 zn1Oe_xoAZ{q9%E6aYoBjZo;MQ(cxcS_v8k<&CBI#jZB_=F}7M za&&<|!JK1VZ~uXs&(pr{zwKxL=cCX|c=wkNEbaC7Yk9XXNh=TZXpqu^;?J~wlbPp} zk=CRT_ltfWB(XvqPg)Auf9?1Z%o5m`_$DEg;l)&z{!GdO@H%pJE$Z(8ZDKh`_LlKO zuI_(K?{mK{Js7}mbppJLc`E+fvcqPbtXC)jL%$XEAsf6M+2sAT6T@@=m}tnJU4GA8 zRh@jy`N>H-aWM@Yh69m$ptKdy zb0E?pDxf`p8`_v?6S071I+fmV)AJ&@o?zm(D`V?IE-zs#R|f#h*CN zpxU1KP4728JxlDj2y$2xP@T02Z6WuHP8jo?#{&MOj7Y~Q5EDr) zSVNV_f-zK|GUOAKe|i5$=Gtp-`)&+VrduDSm3FXnSwLz6nvej5<%tPC5s(?VVBW0O zADKN%8|Kc~iVegwJyF!I(N>f=O^wDgJR8!@s^UQ>UvkOTcrS7x{2jCP8!7d7Xwx*j zOYjRmyyBsSpRF)%z7;zjoVumgZBMY*zkLe+(t~GEuaEh1_LYde{YWgZo7r8}e9EKuIy2Epo&-hnRic!LM>}{S*$%4_oR#POXd5wh zrD;GR0WD<_o1Vo<6%%v=%>0DMM}?KNjIMV=e_@&?d z3I6~Tby&~UQ$%4V?4}1ItxXfrHnKPB zjRKJ*P?eWDB&y647UGHeWJ1J8ljF$Ltx>tpKl9$hY-qUoVzb?ynY`lD1N)yybq$)# zxkUj4Cje*>4V;5|LA+LFA}WPJ5Z=kB@n+ z;byz0s*o{3PG-d1_0saK%8IZg77d^k1@y|7VBb<88 zevi?esn=3g!2d@lumIUwg|10?0gC6pGh>FF_4WPtu$hNDyO0xQ0{knWX$b0nO%nl8 zIemk;JZ1cMAI-np-1minTCj0=E%G#ZeQPQFUK_7JBt3ub>tJ}b~e9LeACuIMj3 z>&c!Hv`>gk3HZf`Gb;FrBQf|JnWrO=S~efgwKK1I>)45R%D!E)lQLdvx93_Kbki#lHgQFx0CMMs4ByIRIOa;kZ89~P07K06kghC0!8yaGGOH!M zpy&!RT`Ec~nprFVh_ZxTaoKCjZsyL+%sO-{rLWnuIt)7=c-y zYw~i1B7{oVn;!mT>3Pq+s~2hJBO^!|u~8*zFmj4KDZ|x7Dbb9jnCU7bO0)=7CO+F8?7^;0HUIS33)ak=h9nmCcAJF8M`txuJVMswSh^jCN%eM89BXcE zyP^M)dw%UvqUpu!_2koEr8m29%HYd>SlF8PoB%w9q%fkP%R`+B_ERD=g;o)COfVU! zA&UY%jdueTKZ$$o&>IF_@Z}oA!8UD7AAtUB)v>4l^b&KDUXllRPw=Y~Ft!`ix@o8kzpX)`-y*f%RkzXH}WBy=%6ZH$gO_fB^l%V;SS^_X}E*SYr6gEdmN=?=2={e zx_|w#MoItiXnhYZuaafkmnh12f7w&#_zhy2i4Wa)=#WXer^**HMq`+c2m)H%H??6a zC2Z;@Yso^@3Cau$3rP3@-f`jlPw8NG4gLDsse`W8^bJ7m-&n@;A+bH+i?)IecEE@0 z_d*<3aKOY~IL^ycu1|Rh5$plSH4}adfI3k;^Zkk+#*K&$JQ%fqHDLY_Hn{89!xxSq zPOr-{ij*pJrqakB2lo~7HY7HC<)qi|eD}3^rGpKBtVjB?7{Z==X`cuEwR|pQoib*l zO#!J!jEHd|fjB+o9~nYnC1sdi@uj6eo|ND68fKWs(&i zWeEvqkb<>Rnus`o76cgn38uq@N0JE82FQz^ z4h)va*h9dyn~K!y?Rj4VhqLkE;;nN%$LQjhZMKG}Eff$W*!hBG!~Ca{V#5*aD*p}B zr^Q$3Oeb0E0ZJubYb_FfM>Z^CUizioQHF&lRXHmXcK)xlMBiOI6e&v`C zno5fD?KH@+5S1f@io&4%LFj%g&%lvrx+)w;Q9fL%;9DY~XUkJY)yF_>(t07{2Qz91 zr0l6}6y4cR2mLvo2{;P?{sElZLj_qO4`VCuz2K#X64Uc;*J!Me(;-T> zu1(gR^6%XYXAk4d7ksrTppyuaFd(R%@IkM|PCIICMljmBpuDSWzCqA2F*?CRTA8q#3vuFF^ ztl@RvpSY{Xz(3IU>!GR{k6@=h`Ofb@oj;%`qoa}MWU5S^vt!*WfY&x0VpK%G3|^Co zr^^Vx#2$@xfXp1$qqC8lDtX;0)!O-#YUMFA3jGC;u(gFb7_QZ%ru}r5+IwmQd*=H$ zzxSj>7}pszWUcQ8T`#oP2O>(-4W-2f2LDoYG8~9N+%OM>+q-VO@x&Fahg|XL@rDrv zUSA>q|86isv0)_p(g){`{JH#866qIdqh1pp`EiLH^h_LnF_Hkekv9r-3(NQ!+_|UE zIlguv8P%`ch2Caq9S{Ftk3`E8uHFa}$>v`F1D9`Gu7=Bb7>hUc8uYrGpZMWWusqGU zHEXPK=YLRQdfhCve>=YNI%>~@muOcrrZeJda`kigjmY<#r-L;d^XNl|cD7$?#hjvS z^=$I^Z21R=DwhUDpS3tk&~#t)%c%GFL_7H$lEIPZ$*&;Kk7SwioXPVa);H?4dpOjH zBzaq7B-BxevV4)@XmE&2j6>_4RNz#}11f{ch**F7Ksflodf5hV;wX@F1_ znkTREfj~nZZ29sgm&_xrK-3y%u-pU(*IGE;HMvw8SXdaX3=Y9$57<~jUllo}G`ZYj zECg*%@n)D+pwXHrHG-YMb1;rx@#u*^PIYW5I`C3p$FgUdFX*}F#nA?xaG(NZ03=NS zmz^1?`6Rg1_~eIEfF)`<6sN+9LE~G(OYVYJSr^+QU zF)q{J|W{oujP z?=9~*LT8EtT45CdX-cJ|p{Xi=*Mc#u^Y+reoH+bFJ+MPAP7uMK7`adj?`iYLdECjQ zp*J_EsX3f5;}awPfDnlQYeR}7LSMnI#Q8xSKk4bA%)g_T@O2hXclH zyvM<}?L6bO{ZaHC!!k2nO*aicQ?IiuYlRR{97Q#QKY*lAJBA#XtE5C@4FBzk19-Pq z%_`qccah;lcEv^8zn!!79UaVQp8XqD%`}V4At0_ggLK!LIpuOQr#ug}S!{I*z}@?; zGbp}6Cvyif3d#c;I{0$*i5Cw2CXdM&q;*=NR`Z)tjo(u-v>P%A-> zDvOI!U^bF;L4CngcteDKNQuZCxBF-)N$kI72K+Yn{)CJ>%-8OLH{Fdx^}fL|YN&M7wyHgvlq6pfN?ak$0JNuDLYaFw zwD#vz);B`^S*_3|$I-Ol_h&K0a-eRDN@5V(@&Q>{H+nj)Uj_KjF)V)E8qtmF<1gpjp@TB#sM9@;&AnY?eJ5@mI#Z$w*pKTC4wQ&9W<9=7WXT zgf|N4-ySgmGDB0kTZcK^smQZvSn82|y zva&>Ll47DS&=T;agb)v13$hd!(wN#HWri`6vSwR{j0s#`{{EBhSl7={UQ9BIxXvBA z51T{17sO^#ct5gC1dF_X z7BL+vt$3ei^N|rqDX^XuZ9g=7ZJAQq@xbaJ zi6jxR8h{Z8RH@bo`ep$Sz~X?cX@ns1|BlUA|5k8&pMxnAue|lk*tc%zZG3-*S(gTZU{^Z$;MZ5cb+GWq)VuOjTnDg{4KZ%#-4MZqF;9e5}!dAOo zh$WJ7n7dF4^*%Y~Q_9-sd*V#&J;UxAJM^-Lx{y|aKqDwcBUZ#TsuRIfNGl0p>d;Fa zq34RiOmY-vlB2Lrdboub1sHbwTT_~Y4|Z{4hRd^MJoN+=WO$w@M4?lqt(YMg z;gr)Eyx zl{fz9QVlvT_c;WrX_(mnw28+z32|rC2X0M27#8HL-IFi@ysb7z2W(9F1Mvn=`#41IHQ$Q1QLzoK~bp+ZM?2@;F`cidGdg5syNJ{!la; zD@3C)l!sFPG*~P%2c#MS$P{?mk}WME(U0U^Wt=N~J0Sh9Qf*VK)?%5%By^LN_X$AE zTv-;j70gJ3&75g8Pnj^0*oplC8os`__2%)v^gY;+siCF$&Xd)wf4%XpfBY2G#LQ`E zJdGQ7vI*lv#L0#3gH2{YVXX?p;WH9S^~>t*NEjIakUfctD8#}Eh&g3ayqyPVgpA>i z8&iABo_>Fn;)gPHQx>SI+2*&u>N%)&v3@R=ha(&$yrg@DSWZLjET`mY=#_pkw@e*g z0mw5@PhKTWd%1g9>=W5h=N9+b^X*xgKiZR^X}%GkT0SOpgrb6tFm~~i9`mw zj{}w9)HQ#36qpg^`f9C3xo)~fcK6WRz2}~KPG(%E=A+P*T#>OyrGzID7ZPG?M|M66 z>^q}T46B!e+G7=h?!hRgalt*V9{I*#jcnVE)L;7j<8p!nC@!>wN=!(|mK?4nltsHA zWkl{JAb$gv1E~7}pxbVAI=!G|kw2-!&0hOI(bH#N5-j8k(Pdx~ z_avfmKvMJv&|r>LK2(}9s+yJw!pKdwL^K@mHOBq${qoLg-Rw`Ftv!Ez^jbr{Ns5Gbiwom_`if$V3nXk1^wK`+dWg|LJw`-I6`* zlKM|?om@+#&d=nJJcC^AgI1Nef0mJYdqNlADMC{}`Gy)qDKjFEDJJsfkzML&ec zWzz3o%IpA?|uxU>wVa*>g!Q^-Ti$W z`erZV`q#@j>R>%I%fa}EP1x*Kjl<_0qdbCcO8TJ5(~Zl0@2)AZ*#PcjyZ z#uMY+4v)Rb;jv9l1VWOG^8#!*u00_}fI5gN+~!Bgu8W>eVZKc2Cm z|Gd?jdSbq8Tcc%SAm&N{aeL9(+VV0s5+1kV3<`t1~US8dJuAA2?FAr-8Ou*MY^ zeWG8C_(URTM_UN!+5`IkLP&{~M6iBkS*3ZFaH{dXl_ez1_;oV^s!i&T;L!aZMjgV} zhZkl1nrx1xXMFw%weRDi^S}9-#?PI_ZrPQ3`8}pVuhXYI_#uhw+GU!5YvLR>_F?en z*%uKjp=BBQK^Z8MVtX}sF`#t}LdmKl0Eo(ESkOda<(v|BKO{~+?4^N!6zXNBT;B|~ z+VWKNq=jL;Ijv)m)x*jqyHhJ`b9t4e0g|FB8itnN1a9mR!+{374L9BZLom}C+(Qwg zWlKC6sjBusNaQr&WJQ^)AQ96o3cfa!M|y0$3q4f{a27~X645?U*&2TZklK*9=Qxjp zN5&-BnWlX`056(hEvuAyDB_ZWbm<@ zO4e=;FSuG~@K+iQqDH&I#UYK`rvYx?Ertke-wg&e?tLfp>am}9TIMkCXYXIpt76z?4`^b8JPf`a2n(V0_|`*8(1gWa z57vQ}8!C^Ydr-SkTYSWYQbo}hVn)oc1j#kO7yGTRH}zo2$p2#{v2-9KvOT}9`B(Ay z&6>JRSHWm%K7%Zr5D_O!qWlsFQ#14AFZd6`4DM{jP_=a z&;mfCva(l@9NT6uzh_7f=Hs)+$(ju3<1Xm8e?!Z!@bVJ89DrHJkIzKQH8b@_)fRU4 zh=&Is`^xgnao&@t;N8u9A})#!wdEI>fD%?7(Z&JLk>)wliHCm^XPh#=p4dYrTV=1{o(&S@%{l=TiY;eOp)QwM^^^H09zYh*ne2K zLMN@ym02kOE>I`81{iD=J;kwdXH%E|Nn&|(R zL%*%eHYAw+;(1AVJV5w+l@V4fDYfw}@n|Fg)#GbXLp24(luz&-_G#CGznSc)Cnh|2 z^W*KA-$`!sQfAab$3l^@4u`86@g;~D3us$_p%{ZJtV%E7w3txv;A)_1)7u)^_bY$JvWkjbdgwJMb2L56Zf{ zvdByaS_IPbLJ>M!;l_~%dzdUTB-x5*E?M29jv!aHVmroEi$s^ZMSma;@F0;^=%(Hw zB}CHtxA@>y6p*Aqq_s}$K$ky6G$MMWTMKtpl&V0WXsr`FJP9F@l(K-0B!<~s`L=e@Jo%jfO(J@ctA6XYYymqx_6rey1iN839bS&O+Ug}_>buP1fl zT8^{0MJXAALg@vodK=o=`(J#F+xNJ?Nbh84p|;UwErAdispO{uvOank5I_A&e^s8M z{4J^t#lV)SW19HXW^A5u-QFe zr(um3_^z;dK$SH^FdmQ_a*$`07`!sxfjoe?D#2anErV4v3(xUxi2j_`;Nf_Ory9ta zfn9*4+2oMN$#cA}9WKY2o5j{ZBokhKZ*lgs)XpyY@$%v#OhF^6WvS?Y? zh?ZCXh7?4UzQ77_Y9kksw3umEuWM zkoG1@Z;hjQ3YB}pXo>~b zY}UJV#pb22UuVeosCPTomPSch6$;2xqKBL45mI8cTWp=m;X<0H9`I4-KK?(4RsXi2 zS690>nW578?3`!UUR8AQDLSDNFeHNmBd*aCH=sZ)B+5O%}Pf7 z2MZeYU5(=r9SSDbu6Q&iv??WMGT1%#CYMu4BmzFzKc&o&?}fKC2_bm<8`C>AK_F&k zubBIKz;}a6A8YUXN7dmif>UbxF^}EyPt)VCo?NTfmCK;iz?K$bKf(F{N72N=vKd7R zuzSgoiGRtP-@SCi%9{rjp1NPkK6Uv;|M`3FIE3K3iqlwCt+ojzX};;HM(wEe7z2e%!iIZG}qa z+Q8<6Crh$^mHF&5PyO`8sYjiZyBb@L(8~63Y^txV6-)_*P^jm#eXHL&`R&dB(v6^6 z#RL%YNW=rUHbI+ZD6L=PMTbfmpna?IpsSO`K`_`>?X!sd9n&G1nMbbxzII*HBdvzf zNOSW!{eHQ(_3^QN$*;Y;RK0vhgVGyZs$PG)M7^GQ#Vlm!UqRnK{hIdNC(4K`mq0W} zj60?ZekYIO06F_bT3&!Nl;SwN1L#@N65bAiacpuSbTo@-Ux1_l#j;o;|MR|(9X<4+ zi#I!N*ZJPGFJu;Cu@F+nfF;m?w(1G96dP5}`)adj!|Q9vS2QHUjXqy89tS`t36Oc< z6SS|{k-|G69eOB*=;dK%3Weq=K3`G%h3xJ<<7&2Vxl?a&&Lu6Vs%0wDgznQU)2EgD z9{b{>u8sSP*mmlR1#OqejoRYE!d&Qxt|tee*ab{y$l;Ro=uo}p9RGLbD}VdwPvX*_ zj{^#HD>7WkF5PwWuOoW1I%Mza|II&p^tzYjH6rnGase|;DFKPH*aTCaDbbvnVT7EPfaSQ-U7m@ug#>Z1Ss$y4}y9K+4r@9=1Dedqb$43_-guVFAsMXk%5Z11&uAv z9@FLiEw`|V;q7Pr^WN)phJ@VF9*w31Br3#ZoZ!MkKC>M1$$E}xya^UQ;@!%A_UYIT zCQ3+bXO?L0HOw2Q&0C8H?dsVa%#<`9rFedFgX z6beNceU23gU*v`io4}^ z7#AT-#_wxNyY+p^CdG549|o;UTLAM1V!A+Ku!67W z#W-ZGdlHmU1;>PYXor^ju16z9-MSt+wc^{@dq<9c>mN_tmkB+&f2_Y-Ypns!7|LI^6wkXMlBkcpJd#v!?`gjI}yK?@*@rbZG{+$bgdoHZcvk`Na8 zl*5Zu+ESb;0fOd6kBy(Z%d{F)?)Mkma2xABe*8C6&m%F-lDsOq<&|+E;%jroqs>Bd zAQVV+aGc=b8a*_BP1SX;e)z(=G|L+ zRqG<(%BG0TczWo7aR`|CAPo_=2Z&`=R;h>lW4XK8+KV<%?ipqD3Q=V!zu?*vAV;bb zp{*n=OHLG|HZ+|fyorN{(E$syz*Nfx zcR?B`k{E_-CXTMBsvBVS68UVPoO8&S9em$=2A3RY!=*=9|BG&i|JlWwODkuduejlr zf5T=67eF^|*V{u>s9_H-M&wl8?=BCmE zciF9CkF5Fe>%ZMpm3i6W&>2(FO!mg=Y83th%qC?G3o-b6u&@{OwpZr`#e+^h?DD_< zy`;Nneno~y*{^?Ro_Kv5Y1=I+DIh0Hixr~3FpfZ{nrl?;L|K6Vv`dVKlL@i0r7{qS z#-wzcR(Tqq==sb~PfuOcBbwt^QUxBBK7>D)+bFd$|j_3ZqZ{}FH zn!S7L16N=90&#Jhn(x4m4Oyz9VIdH4i%7Q+RGl=Q-}*%qlG6l&84kRfai;=~CCI-y zfi<|Y@pLR%c{+I*X;@_mXDg2_x^UrT?}vlFp3(0-L-&i!KF-cRWccGRUP*$A_Kf5V%c9M)&h!58v+ zlD&J-#r;Rh+UwdtiYf~WGsT>!Y8MhuRoUQjN=RFEvm5ej05zmJfQc1Qn^S{3hKEfH z=k|hER)PzmHun4jH~uK~@2<12XVOz-c#7>Sid*{zNnVkmJ)0po0rQz!uLH;UL1?W| zZznXU=C@C~YV<1ZSOe)tJ^Ow3@3i)N_0piTzuxQjKZ1Fdt$Nqpw0;4B(8RRA9scx2 z$_#o#frRJ-;_@Cb;Y%q60sxvoxdSKLXr?Aa4*3_LEcZP~7kY_dE&KbB;|$wdXX&Jg zIfZU$WN&Korvg$`CPrXc^n65aP^TS;)p^W$ODC>ngMn?Mr<_YjC8J6-*{ubX=1I_q z`U_Dh0e=Bi;fsa7W|L zWu;t_$31xeI5`729lQ{)pQbq$!^skFUxg#|q(QwThw3cN`Y_GT&l`f*&6=e_bAHL=N6q>zIlh;6{VYTF z->Fw6Xw)qAjzeWp_l_NF*_RGIamcqn&D0q=atY{U)z8slld#GIWweE62^|t>#ETI> z=K>LsC1bSM@X>ocvlm8QU?05s#*K9gZ_K+ND0@68n7heP8k(J}xvF7~n84P0G29rl zhms)f0b2E7ShB*65HYLl+duK<^qK1wZN&XOTQ9JW4PA2FrvB41r)?a>AwXv!8mY3? zP!<4fMH6;oOA9coh61--lNYzwtf-}E^ZNk_4@>SK+giNRyJSS*>m6_Q8=&*eMhGZa zx#Vv>56B#+r~0+-$+usBcaZJudH8dS4eQu3!viNZ#}{r^cO_c}E(Fgkotc;UnM@6RfFtgZ=OcY>{ z2`@@b3Gd9(yK9c=(%D*8ND!-doqO@{o9F`yUW0S-zTh!@6oZBe!l zThBHxzV51D|9!H~G*VG5NC}9#FKZE`1Q!k@_yCMN>zlmb77P|3MDPmRG;G^Z*I$GM zJ{5d2AlAq8FsW-30dRu&!-0Mf%;WPAj#FoLE^v?Ij2+6SRY5J8pI45WU|sF(+a01^ z>eTF2_LDs;eCIqTWtM}p$t+~)vf|;WE=>AUu9`-irzM3DfI5NdF-JT`#WsvO8NFMTQORzyW8!dG%Mv>GslU91xHOM>4Km^a0nd!W`FLXWzTB z_#Z!vH0mL3EDDpsIC2e3#5-yLXQGu>(gK>bkfkSHJtW76zrj|yZocrnORmgJF7xsY znJ>Uzbd{Slj^x8rpE;V`GCvRLk@z;Zvq81t6VW%~2ue)UQ z9h$RJDBUN^fS+#k1QhO(^_)GDko>aoukxf5nSJ8$fvh88NJQ9qo@8?ZwRMXJo$ReD z2)FhwyZ!`e-VYt+dO>x0t*5cU5L&4-Z`Et4gY^S6o;ms*<#l@S>nI9v zdj&~?(3VvGfzW2Qa^51_VK0&Ie{fuv!CZ~_6-FFvqZS|M0*p!(3l^?mOzKj>O=VQmwnHr|Iq6;%fuMZTn&TPZ|l!a_=gBGSWCeIbo-y7P&H zmK));q7kl~II8&}kqg^D82)6@!OK%VXYW|Dd%W~6t6TE2R*C4gtajB4i8df~CsIbT z5w2R3kw7#8nX?{92!3FRIBnStXePi1VaVpRoKpZY&|=1i9Xx ze_g#CI#|6NYbjDKwfk3N9Y_)se{L&MEy;Ghp!uHxJSz=RFSXypqS2cq>tt4YeY(2s zN7l!3o%fRn=3Xm!Qlzszs9BF_5OG`ZRP__?CzNaU{~0e2KM$h-Gyw&#gS69oU%mG+ zXwI`i=RD}g4cpkui`lxZW&hLFTP-afVS;m!8cmIFL>V?+_`O&m--~eW@l+LhFIIJS zFV4OU9S+~X!8&8nc6P$k>xcX^Bm+eYgnE&Ru?{$tQtUz-<)ZS`3X8*2#ZUB1uH@r! z%v3=|W)Q4X3J8-ZD+^s`R$B;&yZB`+(IrcZL+7hoVqK! zqs&ftA*I!&5v>;pq`6%l8-MA7Wv`U33=JYbXzYKEfXivr=ctfcg`T->(ra)z;bRmo~OTIzbrrdSn-!LbrQg|tVy|Rq64Y31D#4N362g;5Jln2qm_S*xq}_^%E%E@ zKRQXL-9wJ31&FWW;1|N|{Y@O~9Deq@_hNJ37X}vE+1bH9vFh`+)$fecD=*SE$5gHz z$gKdx42|1tKyM5c8}t5=C5D}>`{r9Ozi1q%S9YLa2`nXqpjsag=d&Q8P{nrO?$Q_& zBS@x?!`veyKjf7_^_p%h3+!ZPU-H1^0duj#x9M3pN` zJDpd9=<+P2cDdlwNwo*u2$(+@jdvLlS8Vu&y~jS-Uqlli(EN<>Ee}8`7{&pq)ui;`G4aj2e>r2CW!siP1OCf1-7jp*R@a#DZ;g86OSxxO zAkr!WcR-)o2=&Rq@A8NVOmb6IGr^C=Pjg6=c=WvSFI{!^ZzbK=$=u5x{jgLv+czmQ z{;ShWK(rZ&NjRNN^@@x-t+$7|@URKRQASWug>lS^{xS!)ZW?l`exA4KPh`a|099mt6rg*3TZ{|5? z5IO}=mPEXRCmMthj}2<7C@KNFyFAtVlQjOuo__A|tLRZRI%9}QArOYg3wwtz1P$J> zSoZ(N-gm$^QTP9+rR=GI;CRpn5D`&wms!0-)AUM1(>5ldARtG^wY`$%-K9|XK-?P@ z6*o@Y6A|a*e%zw+s3+lY ztU>*GNNbz@l$$@lIKL+psqq9AngC}j5oFTPvbZC;<4Cn1lwBg4Od$uK(`$05hvt3; zO*$MRZ#nAY<9GQ*!@_!60Pzd8Jb`Yg6)%Sr-JdMXa_1B0#r`qI;9}+s4Gk59GZE=X zXBDpfwIP3;`|H0iueNANylbDTxqWg}jYI;BSDJ@}yE} zH0SMT?01EwoEf|P!Ji+#NHoKOU}2M}k<~BCah#X-P50PoK9-~6%ct{;s>)_s+CM-g z519IjCh99^rjI&w-1GO>nvAEa0*f0xNRlq7Cw18N72=IkL0qM98K*s$d<>dS^imHVF zcR;R9?4vfA}C0zA{r7!jYyK z1u=#+!WgE679b6)6>W9-0rH#~}eCSRz)k0MTIlj!vJ4?)_WL&}7FI zA~)jL*J7NVxcy8tr*B(~+V?2@{Rl=))L?AUrz6bF&pynU-zP-&oN$aO?9{H$qf(A` zpukWckrxPYC>w(eDJ?h^xs#4iHK0M)Ix{-#1w`Rv&JSQll7GccN`08cd)->QVe)3Y z{#@5F%Up50H!Y>JikK46pU^2&p=mUtA7~I3da8Uu0*WQGFoe{^(o)(HYK>$BV<5l; zb)~nQ9bhyys?E`myGHO-`3ffP0Hy%ITfx|iWEGMt&WN5r`rqZliWntI`M9@p6tikZ zoBjnr-1q*g?%TlfyT-WHj@y@cYW%npPW$mPlN500wZcU6L=VDfYvT%{F}sY$x8YcP z8_tRWthFSj7Vr^3qnHo?Lv^2r!*709M0VKR@$1Jtmz;9OgLjzvG7z<8!>L?VT^v|Q zh)*AtgK(THiL<>>&4KncLi4WJUeu3y`IhFZKRk*vn+YfxlD;8*?}F~Ty)_{xTvcKU zFtY<8Sp>gs4D&agF>>7KZV?X;V%BEYAMo&|qlkmmr5Z9Zd8fdkhUnd^ecB<)VF~CD z;E@|+=)B1c=&P0LcFvGm-B2;?m{%`;yJA9tSX*bGyVj$d+5b1xL^dCWmd{|GiKf;1 z^F4?EaAI|Z&++kOJ85X-dmHioiH6@DV^}96|1^v$8rsDTJas5KuTlAQ+$#P0nTHl` z`sH0V{J~7kC3~M9ZJKP>*NyGVO?Y5Xh7F{EFZy)eLM=Gs!s+^7yJ}qFlp(o_!OZbv z-alz%q{=Ktimm}L)eDL?1qdTlAdah0PL21%Cqx-SU%AIlJaWu!1N&8UH?Es|PD2>$ zy>HyUIXWjlSY0s2;Pysjg}DA*5ySf1?CyM;*DY<~hDmcm6$GPn<0xcsG%x5p^w?yw zpWMt4=IF;KufFPH!m*O+l15ygUIckVVt}YU+MNJfn(VVE+KrthLI#{OJzzG`=qzbj zlytGyN*=S1&2l?)nDPP!PUfeN2bT@qpthTxpPiqc#zXhgjjF>K`!y$3pZ!M6EOq4w z0^MX;2&84DQHl$aDieqT#s%TDdfh)9?(nJO8X|LBT)WC;-TlelM&#n;^}Pz>Qf@aK zXj~_w!Z7C1YiABS>p;GE+@fArC=ijrg~C9NIx^_#f(sl{Y$U17GK!i0(i<;a!Y(wk zjq*8E%yj)=@H;mEi=4mB#&OUgf^KNOb`O$vfSk;gh2?uH49jxUsbnTfNeN}RcGLcjqreNY?3?><{q(zRtBiI?q=?UN4N9}Xu z(tbTB7`8ie$@DGnPd${)$4BCe*3N1wYXJ1{Ya~H9nq-EpXblE(`i$Fk3J6*dgyapo z>X()xw|DIX{Dave@DIMX(EXsZ&6O8)?n+iB%9#h9N0gae>OYyos*h=>`|Mp-IPC1^ z7G;E(BE%uKHYZlQ!zoUQ|&Z;Y<>^>GONZ- z%IrE|f?3oRj|eVETGcL4Rv8k5@PLV>gzD-E%rfVQ8`mHAd^f^sNz;)4xJ*eQnUT`X z6zvyN0;m{)tviW_hy2ke+1c2W#C4NNO@S#5g7rDbB80%gR?2apq!hH7&@{gFn?u>i zZx3}l3|D5^k6A_!UQ@Q`t%mWW>*nMa+Yv)8YVA{y5F>byaWH(F$y;g;^H(=fZcz?p z#6&_nNDw#Hz=9nBS-pHae$?tiTL3Vx=L?JWXXZWi!?f@(v{||uwNlzKQ%uFv3+137 zr?XN_0MxfLC6MTFECdU#t@tK8nOS@4FPnuC1X)pl>~5CZ2_m2*AV6DTAfN_Tba6jCtZZ;$vB;O#v3UgOiygaqv`^nmVf7eR7++(#e zoA+IM)!;`tvzfuIE7_kZroc-i_)FRa@Ry!oz0(f%)c8mnlu9b~Dznf`&62z9pKN-u zJ3xG-`|Lw;Zvu7Fjw&|dT5!n>oXr94CB)Q`8YeIa*~!=x0_AEVhC^#X(_L1=*m|-O zrp#hve(1f+{+BgBgdbpDshF!Nkcgyo(LOrK`_nCAERyh~;(2jCS5rVM0Yy2XIRbc7 z0Hp%2k|G9$n3xf;Nu*7r06QoS;GvQb$Hox|8oDTG7fgqA15E+FWy8V#6LRtR1eBOk zbXF8ai85eA0j89UrwZuf`rB+)XiAY2&lKZA3SAt0jjpCZP{<@;gdT4O&~^@sJUDn? z?zh7r?+VUYi7yr%nL8j<2aM2$kQYO+96$^L8-hR}3uo8BDI9KmifMePmd95DFAfA- z4ObGZ3)S(#x{#k=@bV#nFIJ7PbX5s=?XEr5zn#p+CyyU*8BM74@_900xYH#^;&DFR z4zAV82csu^Z=bg0^)FRW_u|`7DcxEeV_?>yD?IB~w0$*RZhcdKi5Qm%&z&niV`OyM~*(Ob0dBmnmyL5<4FCfozkX@>SOgpiI+*Pp2 zL-8@LbT61;E^Iwued~E28y;!V@mx^gQy*x|sEq884w2Znd@B+Ygm4xd%k?OVBt`+j z7tD|Y?MT8(L3RloXLNUHtH)C(NGTx!nyE@pZOuIcP!Yq;NPwak2hZ_bB_wWaPcMMU z2c?qtq&oO`M2UoGzA@zBEzzbS+P2aCDf2L9^;z^ELyw$d=7iH!SXiWBcMs5OAkj(4 za{yK$UttNxuZMB|`hybYDYF4Tp`+7f?Z7!%jg6=Vm{Wof52vezM5F`N zC|Gq~m^B|Rg$Qqe8CN$W0ZlhlRKUj)Txc3>L9qEi@Le7FS2FnS*b-aPRfgqBhW&lI zY!F^%!!>U`zWCKcOmcdZL#-RC6%rXiqG;sm2ExG^4PHEB>S6?wGR*5h3$y2g0n?Zf zD}LJA{J~_CWEdSwkdd$uwi?1VuDIfc5#7N9m0N0=i4V9Bn0o1}W~sO?97j@Diipx+ z<0`5M7vMd*Q!#@px*I~3yjr&Fg@1-P)!XFwPz{J;aV|-a1*u)&+5%y_M@u03&PW6S z&Eg?GTU+=E)iN(!(fi7K-hRP!lGU+?0+pqDA*pie!AH|(fNi=%)lFeYmt%;M4n24$%;f(Y42`y_)-nF; zq9=|VbeY){QfOKzP!bJVCWOsYi_OhYE)lXw*{dN=TSo4b46%yc;#q=r4VW!Ay;*-< zcPIzSEcMLh_16!2c)~oqr3lGBy?=n!t1}u*Jm6f8NCNDifjX6|nN&W6Z2YNfW%qR_ z?>?l@4tjxQmPRJQ{Ju}0moDm#6;P)&1A54Z27U}=j#?CnDad_BQYLM&2R?gmRJWkd zL1t-c*I|*xy54VyA(@yRj-UV-!>WZy98Kzl3(f)>O#_abe^1BUyBl6q8M)HsXTw%8 zecc4za`z@Ql|6T85-zvQ+ZS$smPbqZr)vs^f8VVyrT#c3AF>rvY)M!;lGBLl27_P(vFU-)fM`&-kp=}}bq91#;1Et+1dX_t z4?Jn~J4TvN{ERPzckYOE-9qdLWHI*JuP>{0;lvK9zZuCu5yJ!W5sa8}6 zZ*;?Js*!}FvO+4>kvkj^eWaz1P{@O6MIGqwx%PA-i!y1p@jQhE3>rWjBTVsOy5zj# zeCFJLoxfnucZ=urBFF~qm_4Q1GmXS%RqUr7ClbR_Hica_6zmD8xI(m!26YXui2+AH zF}!qdZ{n14Bd6EZxD}mi;%gWa?tM)nVcqXn5oTWh2Yb!@bw7g%mBS%eiS1JppzNXy zO@KrP8sFkZU+IP~`xX456ZflV>yBVZnI+16YuT;3^{q8Ml2iEKB#|t$#F^s{eD&r{ z1E&{_2J?p-!GC5l#@=a~0BmVqZ!@!T!qZFZ?)aC<8_~i(3*~e)2u`{ng0oBCp$h~j zIKZ5#1!Dc-%cVyd%zJzzZSK8PwiFdMBD__U*M49 zv3G6E*cE-A=l(pb$O)4$IUxW%V%KSb!*owgoeP-p2MwI?hqX2@A`@;+*{!^qj%W&? zH;UtH$k3u(Sy_;4L`haau|s1j(ShP1s?iJ+-5lz@lp+ zd;Gqvn^AN_vO(+)MWu+N-y+t!DaDCC>|InXeSD~^J|#29PQL8OcTb#@f2Tw~g5by! zSpgu`8`9CNm{7!2O{!XG&X2y_e;F#oeB^SpoDm8X{B4=KG(V z&-DF1T|$_yGNhy>RGk9iaovr)L4><~fe1vGx*vSYGP_odd1ELQF#}y!lKIQi(jwIf z9k9Lwz5}uY4O_6#7)qL%he@>5Bk%2U!LM3Qg4$2ea@~mHF1?p!A#?nb&Z>`AJ+NJ& zCk=nC&JHoWDMa>%Ir!b6v3OE(4p}5sumlbO0B9;B3Nc}!C<~yL1bJz^lEW%OI3~x1 zINmoM!SF(ER4-=?XZ?uZ<=G~*ZVQ>4AG97j+!-^O|0o-9keWncAq-B?b_vq#!Pf_* zWopt=wNInUS*>Oq-GIlxT@)5pst2pbKsAYCC%%PDblC_ww991EPV(|uT2lLS#=Rf6)S@W@aDEjO7Bf%lc1v9_I1VG9|Ir+zt(igjC zmapkQsEbscS?aF0$$IZ1`(y_*HGR-|KO{&`Tx5K)c{H80Eg+?nX+@|>q$3KT>a6mG zy`E5*)5azzUpvvqe|`F(9TkN$FYG&p*=ymF`2ClY!I9Jqc3#*ARN-OYgj%0kqKk2- z{P^~ZZyZ}j5_WgtF7gSUXvYuHx0d5JOxCTF{p5QS4x+nt$K?BWjp_Wj&qnIcPp)&0 zSHIotJeAiMZflqQQ$IG`$d(pSnEtK)Iejq@Eq^9&4pT~w@1Yhl-moN}Irw+U$ZzlB zJO)Jq%dE1(>k~}6-08Thoog7EzB7>oKi;tYT*Lm8|3^$~(=x<$Fp(@@+b!W&Mdud{)*~5#^)pcuv=SApi=aN&9ER_qyik^7RXQqEjExvQsTw+rd-3xJ0u1S!@W--_K41LdGI1Jzj z?#)y$*)T}&$}JZ#RU@wc{i`2p&3v>1Tsvd}KuX6=0hmgG%JBg(YQWr7Bn0XR zoIF3I`J43eHT?$?i?~RWs@4zya^v-3Mc&y3%!!WbFF!wimRV%3i~GVOMxw0%MGy>q z1QSU(g4hd>AO!8Wm+6zgh=~r~x^m*_HF!&2^=Tdo-I0n&QhFiETi0h3g@EV)b?Ntq zxnaV02LP1)e@hhbBId0Pk6ifmehr4KcaGUOD6|A5L6)-;Vu=KfaWtFE08~=7&@AVn zohf@$P-p>+w~Z1+qjH?)rBCKg!!gD80JCsFBA|7C=w9$EIx;HF?0OE{;zf) zV9Pfy^H9pPt65Bma%&D)TrD)iDyDQw=Gq^5v-(sZS+f1CkC!kNHk;+Lr*6r|h#9RB zLK{Q_+MuA2Agn=@jy1@trwy`V2?_KF353b4u+~{zVJ)tAfCJEj=X`6s_ly4JJ!Ph9 z(V*I5f^1MIsOo9y|+e+ zv;Y$hLh32rRqs`gL93XVyEPfT_vPTawHszyJjC;@(a(L849c#TK2dmRcy4OpzW40i z#TjhJ-6+BnT-z7X-;?Er^U=HBBZRK-nPG?OmY)~|E!PhmZ`t|T5pMZ1=7+=Y7SB%i z!7uu*Qow!yUtTXH15&yfPe9e897#sSmMnBn3b%LK3OHY6Q4#Q$AOs&4+TaPR(0w!t&yr${-jC3D7U6VGca z+x36ueO|#FdqdqzXTLbj6zt}j<#y_QU)tItC@2_+$4*TNjyAc@wJsh1#BtZJe6nA^ z9d-lDnV)+vbzNy+R5aem&>(Z=&1yBXsp`4I;{k0A*ex=caw!Lh;w=ENHBnZ?m|O~I z>`G?wls6np&+3Zfu+3O*-bRYeDf41;Gp!x58#U)wGQmgt4tsp_6tlFp16oT!k5Q0i z09pryT~+BcP={Bu)QP3<1W(_#+vQ!+kawbQcoox>x_Gx?3&b7qu8mPvkEbCGxG!NI zP)xOyhElqbX}|o#UmdsYVTSBN;jX5DFBoVQOG6vp$~3GPf6}yl-_AD_OO+riKzOL9 z>M^A~1P8`+tcqHSaiUPhfuT~zqZqRD=oJ1o%BEvXtTUrbT(hkuz57Rfc67zVt<00R zuAVqE)~ih4%!@!S`Xfgp_%E0Xx*%4_Aqvn#4MtKe0#cd8IVL^9BFx1dCIh|-n zJTPzvd8rsnZsb(Sm&^s=aw|t!`FcKyGBjLJOS2HH3wimFLb-XIz~R&IWMVoscFhqh z02+%Jz;8zTmGo%KEi0L4etYq#LvFmqB)OCiQ_4g8)D;Xhm@t5uAb~Kf*^x&CmP0c; zu@X1u1_eui+?Wf7s`!Z?-#qo9fqz!?RQ<+E#@g}9+#%ytZ{1iZOZ$Rw^h$u&N!wcl zB?Q2_f?B!Vn2Eu9wx=gI?zQ(vuMe%-X{YY|6lF8r#vC-OwZk|1Ak#=uy_W!pXawmG zK^9W1pAcRsrngWSB7vDLUDCu6sa5M3;*G!i1>>RNiTg%Db08rj}dHv%~ulujIN%;&6)F`u2_R z-K}~VZNL9TR7)}9fq7f)WUs4A*|&&5sS*e^a^_33)6qKsU(x^V141&%^Pr z_xkF$uODo7yLU5_&%5ZTJzt%ZZ(!?4fhjwn=4^z2*N6#$a=@@rG}{uWu0nNYaF)z< z8Mspw+tWy74Qm}!_x*OLSe04sXEs!9n05A~m`Mz^po$ejo`E^MkwVh*&5@V@?gdCH zH^A^B$Vi5(swrzMo#{YV%;_)wrZYwkA3FB9-lY;^B{z>J-k9Jp`W1!jR@BO!>| z&I-d_AY-fxRx>J*R6HWZ$x)<^5R^QC8dz8c z%n6|`Y=p)8Ppg?p{~9r7-|SvyFUDT)#g);sK&JpH&8L^n{c6c?uA$pc5cV*$SFPi$ z@8>4*mQ7u1kokgBbOt>sn6z01B;hJl5$QxvDh?wlRq1Rht_Exk{y>Q4N3MPDn%>Ge z11s@GT(K7#XMe2y2V9b_d(mg;>W=-W?0?!m8Qw$Jzw6)ZIb1nVzszrLYnYnFd+l{p zhs`7sgy?YKh))4JW;7DYFjrBj_5;vnqQ&>HN0|-ZzIoaD6Wsa6n0XzQ+ao8#=>wgYr=PT9(mD2hY#s#%+(W2?ROh?UHomcS=7(rOQi(KFu4Sz^r8-5!D&awQZ*e_ zfs#b-bdGbPi3b+G6|b=R1oQgzK?gaHT~qA5NPe*dnk=d^C>)g)C4n5S?r=Ax8#CyH zWFLpZBhxYMbLli%o*tY+pJwj)EW2vJYLA)!MBAnT;W`B8OE-%NHS_I=#32ESuR18S zG-Q*Uj�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

8!h(^(Ilu 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%0AO(MYsWk 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 z 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 `+%dg$`}+g;y^i@i z{?J|XUtQHOUK8e{P6;Hc6IQx9Q9{$!P)vwAYRl+EBKP*O)jxT~yvy{DI7@p0PHgKlqIMsjd=O(GU9YMSfz}(Mh-jUw45se5G9m=fOPFQ ztD0{dzo#ED3=YyOO&RhDgvs|O5QI%JFnf*w2=%Is?q zqJZj-j|SahEC5ok_ek?v#vvTI*FvQfAypI}HvslBekB9xsD zHuI&e0D2?k9t=U@cCUf;c&%)%!|liZ4)>26-Whym&Ru{693c_9TjT-ECYjTs0$e!I z8b)c61Yg?Dgrq7S1yy@4oqFwUD^GhdDe^UXGn(vLUsk%3!(YtoT52h3 z-Whpd!=qQ6aK$A2y-)p~J}-^izx}*|VCz}3H!3y(gE=WqMT93ke4NpGet#-wkE5$HZ7nP-npzZx8mV@$EOU`s4P331q6r~`fWgwm zL8y)+jVmWY)1j-bo$dekA=UcsD<|6Se{1FXZzeuXCcR7YdEv+&9Zz#&xYOySGe0UD z*-e?DJBDm$SmIzv$=J$K6rk-JSO6CW)@}0l-~c)Hp(}2g_~muUav`^&4o*}Toriku zrxnP^d+`di>~lYwv2H!DM_y0p%{$QdKAU!h#*_QXKk~ANdNwSbn~qz_5g-={flN}q zIu;PZ6A58}oV=RS3e?iyf9AJ?29RN56-JNNAFfNY)d^!w6tnU7Q4RY#>O74yE0V?vOjou77y=@bPO09QO}k`7fQwzxQuzThjdJ z%mI33x;R%1y<6mcLKr|%nuO;31T%9f2~&+B<{B69rZua?ve%MZleOesrBjWHbn3px zzWv}Y`*qsj31b`6gCE%CClNp_u4^cP>3S&E3{_7!Ae-zmp1u`t*_}5C0x;%i#qQ+CKvoPpCrLtc6aXW9 zF?UNR0D@^3K$fsptOA4R-sQ3{gErl==dhnP4Ls!kXxBbCgRhBR*-!o@)raWE6<4cV zo#bF{z(cvSrqV46F~9^+DMS+nD++a}HkWqRf>5(E(zW=h5#di4KG1K_-{dKz3%c#{ zm%sJMxz&$9qtV$6<_!63XDku{b=^sFxcmznWc+b-8*Ma*yu^!be0fYn>e10)1)l)A zRv_|=SrNNn$#<~H!s;rog9QO6Z#f$&RK8TN`^9gNw!K+>fLm)2%20f1pd9gdmpBJ+zFWJ^uSQ2CtYuINlE}QD46&cVg9{uUL$$?m}asQZNe{sh4 zSO1c4PuMj3m1$aumW%~ukfaHAbJ)S5Xv#jWG5gKG`(gUo9z%$l#d1||vk}HU3bP}W z*YeFc{{hV&w=A8kd*igzKX{uTIQVb)etIb0&!7G_%P(t3pVo=DWD$wY7HlWs##n0t z0K`OlirPm8{5C=0H)835>{CcuwhS1^YH8X0&D$d8g9Wv3-noIue=p3_$p9}Ij4`n? zM+G(*(=0~wben}70~-a32bKy6%xXho3}ghNYpaY$vgSY-T8PO>;hYy{+DAVU>G|Kb zUs6x62eY#1>94-G&B-|ymsD+$Gg7N+FRZEBiwuW#Pw7PuT_0xNq8BzV{L!W{V_PNU z&m_ex<6^8$2=XEeB0XG!<$_!sq<}mvdDXb}VQwye@7o=t=B0I_TnS5*lx*EmVX(e?KRWbHu3%&q;SfA#F?e_X0E7IH`Jq%b&$6i-VyD(yhZ7=}y?G9Wja$&y>VCN|>snH+_=JN=lF zb3YmI#sd*8cPAt5AxhXlY;Y)ZS#x%-Q!p@?W(k0~@XI%AZYwPO(x3V1$5}%UdG*eM z8?`cM(p5;VJGhwSqn~rH>Hdmz1DPGZL5rLEKb-E16;dvmXOSpm7T`sZ54Le(&0+gnM*~tj7MOW-%mIzujew5+3}u9+<3fJwSL#wd=o4o<)s9`S;6#7 zl9mU^UTf&;rE|J@LY_vr*#zloQN4N%*V)KB!a{SD_Q*y^NVNc6ama{}Zv~uQH;5U# z=afI$4=+`9gdEJHn3V81h@iA66O&FW$s`A%ulUNqu~dl!CQsB&NCGQE4{B~%{NDMW zi+zgna0!%jQiGYVn#WJ<2+(<3fK|?NSLS;%fnAwQDs49QA&jlxpnLyyoI`WOgfUkT z8=`|k!eFWKxY*iQg0JPx#U(TLHn1L>j>cgBhzB!a`pUAK}DVbVLAy3Vqxu zqqd(Ed1k=h$4xkZS#t9HVZRKXkGIIUyycWEp(MXb2){MR5~9$+kgU~Z^&kXkcUQXV zxv)DXrL(43SQ$(ec#mMvH<;KOwwX_`NCTNAPojD4*ly=!@1YyU?0)yA zhKc;L^t;bZ>B8y=w{x8mVy?>xXx$y9S5WoiRsSKAr@nEAK35}PtZaZ##&(qJ!am#R0m3Zi$?{d&@PxUK@LK zcPdP4>>I|6J@?Ai?M{-G>?lf0jJd;HG=)Vlm;$g2IN^&Cm?dIEKy*#$a16PscROQ% zU=fzW!)@03ax{F6ZN7PNE&D-#SC;Eu)K~Yn{H)jO{wF?V-E>@80qyPY68=-xKOe;O zJj8b8XTQ{IL`(>EWTToovXM|n8f=-UBOB>lcp?wfocs@FMqKjpCg&Y@77TMqo2a)*4OFp+|jloNN34*>mrySPo@=+V7I` z69&vkx3H1K7nt$@TNx|B93BbA0K|)zYvL17@WlfD@pWe8Cz)o+U8f_P%K~Jn5PUPSMC^K7w(tLO-&l+e3$vqT6 zZu8n%MC+U|fYO9^P7EdwWD{KTtw*2I+tF6hW7z)@5X}f?*tBuY7jAT<_gPHFCQFmT z{0T%f1`q-YFs~;S{#dzyKpsWvqnUcXfv~Ae?QmrX4_8QL;BcyIJ0oX zVfNwfRR#JPfWj$R^$-pt?}C^9Pxu>$GpkNKe&m3;v7Eycxr4@WDQbF&r%Nj1xgY_I z=aVDS>yBXRo6mh}$EC09^9>zk;J(&W`AIThoA9@iI;Z|>gAM4_0SAl_w3Ww158qtL zhvVqwqs+Q8R*U13X>gnp)=<(UghBCCav!up+&iRo;5Q8Ea#4?_pzbOc!F&!ylWm z42WnJp2R+qiSFq)>(o!@Y8mEcS2W6ts{ZR1V;u-Rk%{?}GFEeQIzbc)lDHYYPA5It z0}U`*Hg2R!n=CA>3UDFa)HM5`N|WaOXlDB2hvu1nAo%{(`Pv6^9RmEf6QMt(BzLy4 zDHaX_nULu*ar$(eJ5K3_AUwFy3!0?R)H$V<7dz1?W|5s0Ryok??|Y}^7)OIgXUWCfUb&1^PuK?7?j zQC~HfBPlCPeR2{{_DV!@QdLKjsnXMD(9AE)_Gm>G$1|R~r?(b=a^!(HrcC6`r z#6OtOJTvSD=BytLFD#u;D9l+|g{XT86xadZon$D zW`|hAM=FCza~5R(`GkjSujZsgO)Lnik@BEBH#0WR^$q{NjmmOilzqChuDskQasj>( za(gv~dAlI_>YO0en9mP31=j1Qhg&p%FbCSkCSD{%Ht3ZR||qtocXR&B^FG;vIElt&($SR>={3l~J{>SFL9sknGIZqrSeP^P{ka zg`J&PE2Ai49;_1PoLMF8%%C+7&pBxtVUkhHGa5?*u`rn0kl9)X8$T$RLBSe$Q7JZ) zLduQ6yYX6B|A9x1p0RoTp#Q$E;zTC?;Un%t`F5=yNtuxe0S8skRH+h_d#D9?)9$vO zbA6K&naO`1K61|=v+{;iDa1=t9Liwa?+Q-^d?gzKMVXQHmTwA3uREEk-g5Jl3vWJO zBlU0h@(KBTR)T1>u)L>Qm(jphhb**QP}Q9f0n!!jp)81t!vc(0iBGUnYa>j&yMPif zI>tofB2*vC7ndyhW%~vF`f1$6e~ikdF3024$EPO6Q<&pl+c9=$qAVRMshkvTWqg?H zbVUFn9kIlLkX00FrZz!oJG2iM>Cu8GvhlkWOillbbcM#1PGQ3F=$>o-JwmVHPjpRJ zEsO1>5nRm~acTTYoy7moOeKuIBR$q%eGYd(8 zg84(<`jc`_23UCqv$b9;NPXH^?}-mz{o)DQ@vH9s%q!4#l>50#rl(1a?-o9OpYwK& zOhsQlUm}MZ$a4Roas$&6Humii6&|;zN;1$wg9t0CXo~Qyaq=Ra`Eng3#UlX$N%@NXn0MdCS-gK{Dj2c)VqWg}F+ZuO=LQl+f#E0xwK6Nv#kts8mKr zfI?gikeYGP1Z-fzUrHG%YrR`WYD|dB8cXQO7>5YUrO{~-7`5K*;juodax*;A$w>*> zW@EiuDLw`5F%#^tj6pyRz58?&7~*T(^vA_#xhY z@3lnpV|scmM>cR-ikLyGTFV9~1>uY?uM4Ktuokb0O~L&^=`)PdatSUYrll0VO_OGw zm$u1dXT3C<&EQ|&3Sb-2FulQY5)xs8(az*qlbuHP1vunEJ{PyobnD|QT|LR1_aOdb zD}JV1dnwU|WO=x1nLZ{=X1-c`+S2v&4I1Ng(!-`K5}$DMq6AXJLZY)gig5MGf-QCT z{BSYiU}<`>IOA_mW;VQa{Lphk<1`8m$fLD*!?E_V&L|fV0x{_)BrkZMm6?X*NgaB`_i;#YPY7`6=k$8d+!rj0}2l>pl>rZ_0^uh|&s+s*K zj@TBX&&?fHkuef4=gN1ByvL!%$7j;|0!1|gOH7h#^9*s-F#F&9S*I~JQq%P>nQpUS z2n6u60C7(q=dWY#uPh$__MGF=EqsNCN7Udg$Q8FE#^y#|q}%RTzPeP;-{0f!E=#6O z9N=Yi%WgQ}l~+e-rKU_Ze1ZcwiRu^#^n%E<+U7Y78!ifx>ZSZ(In6B~XCt5Svjk3}u2g zR1FuGg#u-T>*;APh2x6k&kz@%Die>Irngm!^UmdUpEX=S02cygm%2+=x_jl0t3Qod zYKx3qGU-EI{;@h4aYFi|twJxuN!L4qK@T5*+zVPs#T?47?a45S9n@X}L$m9Mn+fJR zQsFkbt8m+Cz0o_fv}Arzr!k|;JA*IRU!#$xq3p_JFdVatwszpz_+r(-c7Z8JX=)U+ zgYdz+L<=vr3lg9j%9MNtOk{ayp20klyX@Ia=DT^LZoaeWpf2!THbJU)d+L0pxg)%0 zG7tNnzjVKG*Jv4<>MEL?2`MPkG@hFUGO4qKXmfp~mZOqggU~_})oQu|Z#lZXdfYaS z(<~o4ohg~NW1uTANvrRqlZYAUjyNdOk0t<%n49(bJgzcYDWnw2KQ$6$HvIHUbMs9% zCh%9=+XpV@!5ZeQ___R!5@zPj|PS&M%X z3Qm?AK_Vg!pxACPmWTly@4m>E&tjf4S#Ofo57Fo?=4fj;EEUeHF^jqFk;87Rxb0ol z`L&H4muI}!>%c{%S)kx;gic9;+NXi#4EQH7ds^yr&|*wYtF##w0KoV~i88#7?3XT) z(M}Cy^o5?@%M0_7<4oy@!RN2Iy;`f)sKkdUX!!wZ08n{@1XF=g1WgmD)wVZcZL^tY z)}HkJsn*N$K?ley!0jG>cB>$!Yjl>BO?Ooj-zlj^;We;X+BS_)+A{orB@fF|WJ-jR z*Tx*tn7_U4@;JF@EDChiz(|_Xz$IS3)Y{0pSgqr7S5{9ji;i#{(a`ig-jbqUNY3>c z02c;BLb0tzh}MZUTn9?kD`TxSTt|%%O^H9G(BvShEcd2Tx^Y$(h5Mxl*TKd`9vnd@ z$x=ZoG|iSp<%$lLuC1CH25hP_)++~$wZVUBHV{^9K>r@L%<*)6<&uGaA2NtGX4C6; zTv~rejaKq5O%Ru|Iz^s{xg$6V9f8vuq-<%#DLolZpyjFC?_7P`-5ZOurP-XbP)iy+ z87)t3Mces@iZm|&BBuO{twZh|aBt4TFlqY^$Px`k6=L|1{;5IjVoCo7fveiNV6d#Q zxr}cDHb)67lc*&w21%p4$_NK$N$Py`%f)w}aq!(Y4$gmQy_hLIXGqHr9|u*wU6=C^ z;Rx5zUm&|F158R1XaFfsN@4|unLHO<^qY0?-e;38VJ6PqR$Xzz_c^~tS?xkSF>mo- zxI83PyI^A32q4Ktr3>vsqJ?dQ+G{R1aSQ|j4%4Y(2JMmqRb`n`@uQR#RJI^2vbUwR zsR;nFGf7r7f{ECK%MQwL*+KOA8SNoP>XUCXxP;kUTJveynfc4iL#Yi{lM_;5faqGL z;XmU+xsLrsx=We!yo_(!MR_r^&r5@uk{4h2{1J zV@mlg8i)lU;|kO;3r%k2SA2W+yMz9(a?__qxOq(Rvav@mPS$=*%3wS~q6-)X1g*o{ z8i{aXC-&gg^cSdTjPcKoA)k=EnOMEj(x4BTw}pxVtk)EALjle!w8*^3{I%J++>?1jH6womK_WFmniut z$710Ow4O-!DhcWJU|lpiG~|}fVYkg2_;*p3P-oy4Fb`hjwyo}8rkQ_}INkVAPN{FmPBT{W&UZ=;NyH{d4;(aGV>Q6d&nmv$7;C4hLSQd7LW5m z@_T@iM6v<|U9>fxyW?G?yNa24_7j%|w=5t%tY?l5PR8!7st~I-m@!djB`^W&;(*l$ z8(u0J?+j&PSa)i=&5|k+8?q&O{E_p<7GF}}OJAp2uEEQPay&lU{&n4SUHj@oz#o<@ z$NBNHeji;2k^Y#d`{(n|=kH_CUd3GW?Z7)9W*j=Dn~(~)s3{I8n}mrFAmt#G@j`g1 zWQfaaU>kq>`uMNCZL50_R7YQIhLx)fp}GmkUoXb|6c>&|bA=U~7dut3zGBRqt`=a) zBpL!ff8q^Rm4ZfjkM4Ppdd-<^5ff~C)PL-nH90426eSWVlm#)+Lm5gq3nKC+Jl6&p z&vk}6IfJZDt~9Mq?t`yrc<&u2y$0Xd=cVXTJlruK{&CkCh|6jI+4J?eP5q0Rn@i^Q zJeIyn^=$V-ZbNPCrO9m=EmBLYH5_!b#e|?M65)d&vJ0MVIf&+z$3(9%hgWNG%zf^F zVGBRFeZYwSQtv&#{PF*?8+;wJ>Qd`VgGL!O%ru+FQ6chtu#=+r=T*+bTOHGcGZ=GV>7-E!->muetK3XyIM6;fy#!eyo*OyOw81swloFDUPh z5TstWgxS#F_4dq$$?2diZ^9S>72Ybp-IT zdf(mV;_gCP73pqb)~`9`q)%NIjU=ZXSi?>((89YEP-dX8W@MrDYp;sN+M}gHLZVBS zNY`hIK7MuMAzfa_P0X&#Vpm*zj6W^6Q>!grZLkRv-2pHgz@>oBUaKhbQNofgyWI2| zp;3>WJZg7%YcJy9ZN-0Jay#8+3sBQGo{pCO6VUe8kHt2jUb((ar(CMfr(REQJvTGI znqJ)1=Y1mhR-#wqRt+FtK)e^qD)LfmI3Yo1q=W$ZI$&17B0mgf5HjP?(AIPckrLMG z5HV8-KBW;pMwD#;lbeG*X~o^I^fv9%AO4S7mgDGf3-jL3uU)U3GCSu8dmb|9>8NU? zsZh$daBV_NB(f6uan>zmR^Hcf;l_{_KQKi>Dls3b2FF#PWfe&Bin5fIRaEc^G~dH* z@CZv;1+0$nQY=0mtmaW9t=HYkJX5~l_QzYtXvG^S6zzbWfR42Ks=YAepU17=&WOJp zQ?jCLv(~gl1^IZQg>4crj_do-%sm%rOs)nB)doP~5g_w{-(4DH0M>5Vg|HwcQm?-e z9Eyr^VCzG>0ZU#qk>L^3G$=vvc1C<$BklH@_o86sF6Q6oe_3VYw--8;)$kF(`HTBR znILhIMxnVCVuX}XZ8L1s(?oFButm75(gah$d?yJ&Hy54`egh>wa7Z)L0koncrlT}4 z1A*-4QJqv?(r`t(<;=sg>uxz?z|=x0_0(L+?ut6qroCs^>zZoW>aM7R!*iyBG*D0} zOG#NF?H(3Aw`HTZU1=%iD6@k50KYAjc247k=9UEAMo$fU zs=yCbv9O>GWWH;&6Ygofp5=^TXGPfocfVf%$XZQ^Aj{H#UC>}c@C^-|CaOBm#q+l& zJ%20l{Iil8C?M9QT|unB#;A0^CSr3X^Idi2^GA)1<_r)~`uH_Y&}!jioTBf9xl@vd zN#a5r?(%SqOJo@H8^?xs{<5YU3XfMZ2Z}G=ymR7N1-x{(f?TCr+)xuJA^_D#Pb10NA zksj>6p%5tDvUE8Nhm=FGMLq;iDp|8>;vwc<`?`uyue*n7d$RZl{gTVH4Brw^mLEhm zkGs-^O7xuuXb@T5l`iPFfeFpS2U2+z$$ZL zS`Zfb4}Ap8h20kie27_o!so)^FWB@>uR)k*Fkp&}Qjv=WTAYO1T{Eu4rjyd=?DIdw z%pX4P_a8?RklUm}8Dh!Rr;`88z(wa0d<6&UzN)z>k5S=CJ!(brCiC67y(KIxDE4Tl zgff{6)~?M3D~rdQIp-{@)DiI;*3Hb6iXa{vPC^iIRHDXYm#HxkO)rNJ z@--(>n+eg;nrO)~?k3eBrouQDq-Zj*v=zVT>|-#3E+d)`IM5a8uKR7n+KUEuw}rTY zS+;qo@$F?+t%QooYj+x;y5Prck#AnNB))tt-V)dEMWo9ZPQ;AW zzBs$<_CfKcqjzLQT zh`$`u7LlUkFAi3|$~Q430J-ji`YFvB0u3OZ!A1B*6GA5fO(#*b1YY)3d0#zLMpTh* z6LX-o_{DW?Zx%9KD@N-y8Z?$Nx z|AAigrM2h*UX0i2ap(nK_{V-Z4FsE*m!3V~E!|lY@Vc6^o3fShKnB@W>!m%uDXtn$ znni~~f{@w$5NLYcjxC*cga`KiZ*y<=&^^nnPgGCrGh&iP>J$$q$6pHBnoYUuhv;iC zwPt1}?kaD>K*a?# zbAW*<1Pfg(?m+~khUd|PvX>U0T z^RHGY(&`FA-x9EFd#hNok3yPT_26`He>K=&o7zvgIgyJX*9;y|$C5Jx0FFR$zmY|{`sS+>3_h za7BZB2Sgjn)gGu!ak)mwiAJflpML*p;P8_o&kXo)PcnMlX6E63b4M>6)u{ym!WEqn zb?s(^NSE8Oa(PJ`cw8*hDo0HVRdiMg!5|-%37MzsoC~zJy1DkHzsQ``@rUkUZmB9^ zku{b%jc;^F5+7*{cgp`R=cELHMQ3gils&_t2164kCFqEdU;){2smtedczvvhTMHDk zg0+T}Y!a*Kchu7752@Dg>#RkQ?p3Ct?~Kh4I!Q6-!Rq8@2@dD7HBQP3P@tXBnB)+J zL<_h>;v~VeGrhFs!qb*dx_j`c|3pXs`N*2ttIVpMr;Icug{J~?IfHFUZP!) zpx8Y`YWZSNZZrewQi#^mhx5dCl#QpNIGug^OzcT&Y7-4-S~;f99Wb|>-yOr+!SA;!}klM)^W z;e_9JsEIAiOYdx)v19p@x?FLIDu!J(0gxsQ9fo#c`dK>7J~69KXh1q9s;A><0wzcs z;+_K;Dk0hyn}sMkQ`B=_V*&bA?=Y_&Q%6mE=polFgY8EGN6a!fhssWrGba1 zGI1nBw=z8l2of@290EvO5EHub#ybv_&&~JZ4u8*4XgRdER;PWJ`Tfg{=Huoaq%kz& z(V(WT-aEaF4lS5xz7$k8b$3SKU1sjL```KHh^O)Tb@||foiQ=inh>JAG`@yQ@FMg} zATD@nLI_JrBsCR}$wjqQy|Bc?N4OL+S}DB-Rj=*v2pHK(l@p|w9l*qj(?HjG9qb7& zjQba07#f`8Ls_m`>NM81KE9lr_4~|i_kS>K<}GKd&;Z(xy~a5ns)ZuDI)n(9jeT8U zs3`;dB8hQkYSt8P&YSC2Uv&C!Yx@_T_}|7%*uHb(iwj5v-8ngr>+%Y_@nixjb5wXd zApNaTXk_;A%_;ntD8xh|(K!W{j>61bOJ}1f1lh2}iXsH2)3h-ji_>kRN1QqA?ng{L z2K?7XwojP3?^J9$;^hw2lLCP^JIg|G6rsuGPU_Fg6~@w7-Yf{=&d+Y;!V=rYgtminrAl!Cc_ObbMM*lU1w~tI-=78tGw&J4!{zh0je^Ukrl6e9 z(ld9%X0!9!8{txPoef9DU5|Cb^9ZjX516x`qA?_X#=Nm5`cc!Q30gL{!HC-h6zPz? zY*Vl`632F|tm))zPk+Xo!k+rtM_*TJjS&|KCJykEGsHviiJYsug8~J7- znor%==ggwvmdTr6S)!F3u()Foh*!gPmhw(6o@f>MU@6}e6M03+DJHR0nDv-XyDWz3 zcOZp!BT8YTGl62xSQKb(WuljBL=`9q`~c$dFv1y9dso@-@(bp>e;v25eSAP;^2-YG zrMw)7gU#VZXRM7Ex#pbIZ9(bG`={KvdgW;^4(<-?UWpa@irE+$HlpQo!ils+o#VDT zxhT@n0l{Ohkl>MfnBw7ri3l4yMu8p~bm+Uj<@Q;F&HrV?Z2B@KBUPPh-GkR{csXHN zW(KRFs_oC*Rv6RO3KPzM9FOwQ_M7nUFGb5g#`fE%&!c+TpuAd5I>PL0=C#F_&pGRv zy`h$br7cJp`~L*Bq+a(8v-!1?`L8^av?5pLx;P)LaVHtm?0|Hw9F&+4a#}l4KrG_{ z;~0#o)6Ff52ORqA@4fO5ZGFq!+5XXkXH6mmr3b6a;|(Mz3~7c*{kx$AT$rC1W1y2y zmH42CM~c_>QUR0kY05nxaru8{j(^KJaMV9hQ$iZ&~B|jV;5i$c^2I+ zanWF7tV0mfgyhwVFh(-7B7JDO)@uRDJ`0O>s`X;9P;_QllkE9pte5AI>E#(rAom0K zFz600kU-cG$SRvY1!sGLKQf!oy4H5Z%5$|09wVrsNhV}rXfp|ssf)srOpt5G>`nIM zK3b-$qZGt*tMl$&8=EomsIZiN-rQTCxkLWMEb+B1KJ3WrR1o9r)eg>BB*I05)hUK$ zWzJ6RKT(x@IF4&Lpg@&8pVtVPSW&#GE)akWAORkN?d9X8mECgx0jEzK$PMn-uv*qi_SJbF!^1NB(CEVJHk*lj-u4r9M)_-M&oiXON{a4hcPxMMPL%O=WTnHomU9bWY zWUgvBrts0Az(u0~r$HfMWSXW*1gS=dOBNGdO}Cw89x|~Is)3%m-h!9bhK$64^eyhbGI-l0|Y{#kB?>#8D`((ys9`aL-Tn{gKKlSgZ?&o*r z{>Hnb=Wf4Ar$!v+Sa+kaA`uoA{_O|`0Z^>lN^Q)%$RQOz6MKNZd`9&{%ON&)0trJ__c-)E(P1_KC1r;zG!_5v0IJlO5EvjBePEn{20KlNm8uaXD)h6fLs z_SR2#&R2mN@~*PYK-L_mv8ds^ z?d$8uFFs%8gXK_Lm?SswI-M!StMtl3qq73WjZg@P0&3Ib98)E(IVJ33RBp~4htj*@ z#Dj)^ao0zav>Zu8k}S^4CxGG^4mSaqlcK1IqQH@a0AGCtD~3In@d|o33>!49eNEMW zh1HHk;qswm>fJE&{U2Jw!>%r%+ozyTO79Y|LWBw#^+I^e3xPo|tqgd|Jo_wpHgLlX zwA}7cE?+9rYU0HWb2r&GeEHN2t!zd~X=@}7s_c@=7{t~{tHG#_m`d!H)PgeD08Y%C z;KW2N-|h`_jp-WSUtP^1TVidIhI`bnVdT3TA8cHIj79@Cm}{J6(?Lt+0Gl-$a8UX+ zpV!{6;fIFX$5f6@q~o=6EcyuLBvWij)p^*#%cM~v3Xl?40HPnj1hVDpp|y{61Cx-VOh$U$fQAY0wVoLtZ_qSITpl=T zgh4~3j6|!Y9)@x~vZR|hb=2x_U2A)HpOrAEVYltT8yD|bqS0=YB#rJ2`MoY_8{HXM z57R4Tx^hplt4uGtS5qyHI)G?VJ>$jMF$N z0s{ZxGCmOQ+W+=c72<)fS+K+Sz}J0?x(kRScNL2i;k`o}hVA-yvAoHS9Uwvd^^wN1DSq&CJLO<_q5tZ~ zB)x_aZcRIove%Tel`xp@Yyq%eki4Tcm~c>q5QXY2M0ljS*?oE9NR4}2J>dH#BM!JX zceC*?5RQ`IU%-t8nck^UDZqvKpwW+O^he1v{%>$5}uxMNWNzZy#=&y2{O&LZ8YhBsFH zddU}8mT4i0?6fGxAQ2NHhJvpHAmib7I64}Whd}?DHVTr1c30(W@r`O2vf$Xc*B?p< zNb;>$M*~=fTJ@5t(IDT11I&N7LiH_6P$d6u9MLCiBTwOob@K}4diMOq=!WP?SL}J! z{)h@7k*Bb#x7wXtN%qb(7@YyZ=%RcxpaMuHL#1%ME%{|@X>4lTTkSTO006rcYB~#% z)(EP673Kg|OMnwM(19?3?|BU=z2-(AdyfB+z;qe=+h?jf|D?d-dUV643pO{OFs%u% zrud4#6hfWMKd)AXT6B~NL}R31b0l#Qnx zvwzs9*Ltmk=c4|y=fGL8tDcTUKt8X_Oel2PHAtyo8_p=KJ$eINp1T|^C+f9Ex-kud z{=I)i{X1(5?01=f7|5>1+Y$7yur6j+#n&8JyO=|h3!?j=sE0SKi)N3!}(`R6MBT0udlK%tiSK;Fus@4dB5m6%ugUrX4rV@Ot~~wd#nLn4w;GM8gMP zKXdi#KNAV@)#^55_qB*T7fhlKK~xRZy(pYmPiEv51(8?FkAAYRGkkv0{{JJXW4-R^ zhO*1AcRc^bff^nNs8KKrQgxX(_i`n@?wE!{&-3glpH6Ea-V8{4yj(=JTb-b9O%u6z z96fdlJomb&;+d!3!0>0b-9GK^M+cD6bu1E`($&sW#siMQ+sSgQYCU}(8r$&A8yB2+ z^;zVyUr|2{!b)Tc3(;8~zPUBbiPgS38s*bc8-VyEPf?CwkPl)=YvUj1d@|>~@$dBg zUr&6J$2GR$*WzJg4xTbuD=ch6qbM=2GJ~o!*5#Pmg*cxN1^)PkHzwXa_bJ!P+zZfRd^4({_HZ7|%#l`2u|L(9xukd6}pWaC=l>uD2VQ>zfB+uCHvnTH|f+tlpa2 z$z|q;5Jh=(V0e+PZcfHpM=CcxaDyQOWYvA%8vpT(pVpst{DY=JLrqr50IfHjzNS-@ zi%q4#C&5QZG9dJ_89v3zobn0vn8Sw%dU9fjlUYz1sholZXLQB7f-H$PNlsB7UScFR z%Q3xy7z(*5Rl1;>>-cZ%_cs4_#;9R+O!s<*aU%ZF@+Uq$!+A`-Nh8Q64FY2tS3hWT z6}dU0kj+JR8fct$A<3!146p-1!Kt{ZyK-e)6`*JDz++Jht=LnoUJreV5-mP0p7ooov;sNuDb^9SF0&nN`Lx}2|@CT+(7*Qe3 z>4-l&<+~#bS9ndDy@(@5uAYyC)4t1>0k4E#i;BfVJ5GI8+wzY1$lpFY>!fe@F}VOt z3kwZvz?%RZIzKPTypV*vSJe+5j`;Ase9yf(aef^+9jG?yMC=t*tsckg8S4?; zRz`(eMT$c{z%P2#O*i-pQrz2ZsP*9Vk8NLMA~of5K(!p%1<+gdCL}n*s9yu=Q(>UU z>ut-;5axkzr2ItnUEsj@cRtriw%b}vr!8#0=`uzG*TjKbLKtd8)ndCH9PgX3|GqDr z95br}AwUcU(?hwHzfmp&@^?WrP%(_rLz(0+dMIjPWaN$YfJ6_DU;NLdOJCeiX{}kt zx-WZ27`zZFzq6eex;PPK1;BykoQQxtN`YQSSew+f~m%wpxLr$Vr_J>2_ziu)7%V%coh3|QBWekv%4Z|ee z)L;<(p=xjuP6miHh&iahl>zmcl~S9E@~-k-gbQW>+i_qo!gaxfA6YizM`|hiK)w*s z+2E;7L(kxb%3LVCTtSu#RMM2W2H*lf>J3BzAHbShZR>CCzI^IWV>kH^b%PIyKeAw- zwj177l29iYJD8FViWU+wG}u5D{_2(F7mTDsx6$*O@%_*cqE$V@)=zb>Z^9HHrDoT1D+2zBdt~o8$u$N_M8Ql2m z;qi0RKV81!q-(16;|+DeReJJvK*L|iut1TG>f@5I-@`)U`doE)MOVGGu{u}1!Qze2 z?;5t{C0l3AOi^4PMA)bx=0>TaOvs8La-edLmoPC(2QSJ6HiKz4Q5w7px=z)5$G-Sx z%W40bH&`<-t#UE9D^hgeI=EzpPgKKdQ2ixR@l8^$8Q9qYQ zbWCnjj2DRgxvE2XAr%IM^4jz3+vy8d95HdQNAyNZOZ+c$&%flcFTbxkfTEPGPLH;? znI^8y6i)4FDJKwi2|XVS28E`pjd7y)10uG@R*fZ_>$yaa?-XF2P?H_WD;nJKe1-wx3BMdglV|UTme~&fSgWI z4mlT=MU)D5qFjIRqcw~X)8mt0*=^0bDUa7#_dzNkAXTI!mqV=E|0ildLh+V|W?3&@ z^uVAXvjCl8kn2W#$qFY!d2`4{4)hr?5!kE}Cl~V5uwYv%%&8RsC?2DPc1zM$)dxJ< z;$5$;UD**zmU;{d8nTHOSg{{Pb|YMu(o0AifOUY*XgGmFiJ3A7Daw-)b9u70KT z3=yw-nc*BMkVX%Lqf8}UI2Q51DKx9V6Mf?xD_alsaHAy>7k)bF{68itNQ!*LQO+p9 z07K?zzBg!D5YGC*H+FSkLsfpBir^a$0N-5RH~YtNV>gn~ABivJ_j&ej7kbQQhf;JB zX=78VXaO?1WkN|nK;?(%>dX2u8#o4U2TPI zh?SE)00UQjpPv!W-|^c{o18h`{sIz__$=;j+ z5~+f(d0Mr!Z`D^c9vR=_v;+V7{mCA)?oK8%N}UAZnjs{6xa3?e6--5CaJiK79-#PT z4e!B`@$dKEbD?9>DYZ0ASsG`XvJ*+;g0X;2@EKn2|FgxgkBl$(9P-BYU0IXi3(-IX z+6il3%z*)`mFo}aGBkiyNaV7tI9GCe#qI)dSSS`^-#8HiP z8Y06ibK?pDjd z>xn-!=+@J3Me9c}fFO(K1>5lQ4|Goa`$MvSv@cie(M>ipB)%h@jIdC!0u==btqdI4 z%E6t$IYzKk5$z78y%p5cAN#((!_Ma)KJm~-%h2*)wEXSeZHF3RBP>_HG1SgOi}`ZX zQ2!WOhL)jaXc=0DmZ4>68Cr&xp=FSlm$0Al!Jp)B6hl$-PyC%(JKPkx;tcsv4osf6kPiq9*N~y>j6c8s4}JRzN`ZuFa4=n!OpjXeTqMMs zA;U0z-9bX?8dR~n?hAN={XBhlwS*4?^U|l z_{jV_cV9oojNA{1H_zl#2;hj4C1_Nz1CryCF~R?2XJh#v;^Ov8PySWjO2HYGWH4Q! z0BTH3-J`Ur`Z|!E_2j4mfd~uByKEA~}FG_Pd zl_B}HiuW3bfQkw|Xu<%Ku!sE3JeNr+g&-tU&+!Lf`a;#WSF=#yU2ysXpft**oOHze z#W&uy?8p)St$LhVc%Aks@$myJW4CyY<#|o6^KBqbv55vds6* z1<8c(-lH5A342#T;(V--g4O8~)jMIrxBh>>`j3gG(sV=1&@!|PEkn!DGPL~9ELM#v zRSw}>O3Iv%H-BWma;IAq9VlJdk}b>T>tkcqGl-Rtg*ky_T~3H_l2Vl#mF*c$k|5hB zFgkIeR4WL5K-MF^k7|!Pm{qM~$9`~I5iuT034pdYPdRSR|;`=!>iM*-ydvtvknl#@Z}`AgO_-~r0?hj ze>966Hy?&KP;kJi+0i&@-PwR? z`f{q;y2NJl87?p8x(J6_<4@_-~ef5{1~kc52oNz}puW%y)2 z`Z`zf)|BxDIC1DdsEG$EI=Xunt(^VhC`)6V?|pB4lO6Ye;FR4J{;3i|7FWR63?<6g zE>4ErjU<72a`n4$pbdt6A3g2gbG3CQp}WvuvE9&b&-mQTb)Ofv{@yv}$Eyn_5kAs^ zSgditG{JeLY!jQD%LyqIo^r-;T&fkJseNjw3eoemqTrlQsXx)|b%cEA!wS^9`W(VB zad+cP?q7>9D{024Op-8(04gw*Iy(l`Z&oDpP`Yl6&vHQRqmU;E=+PsNUu`+2olznc zC|04bBF!iX2E?}E0~v_qJs-cdbseQZ4VGi$|7>x-eB*h_BN|SG4dz`e9m%c zz0L9H%@B9kQ@5Ut*T!Y!Y?L>clpd{He|Zy{aqoEqeP1X3onP?&9=y$u(gUx5y-7b) zPjzn?`gt`s+~lzw7ms}|Ej#?99ZM}4mE4uE@luDcBNmJL$!H)-rO!UoaYlNMG-|Mm zLWWsRh#$Abg30R-D41lB1}%hDjaKgmzsJt&j=wN+L#YN&i1+^X=s!Qa`6RRagF$4y zx0j*_BFSblpd^#&5&*9WL%2ZccQl#Hb4n1JBw!F4*#_-hT|mnPP!$;N!~uZ#o))gJ zt&mNC*Lg;!7br{jW~v>kPKY1)_S#);e)CYXN!=Y`=c(-ZNH`#nr93?>kQ~agvPzSL zl(Pz5U0m%nNez}0;|oUJGWFii$28~%%-Z6I^EffY$|Bzv5I`eId{WXYKvz^@(h&I> z<-v%u-|t2{V*QFNlN(r9ULi$gAI?_~lNHFTVPzpRXIHkb0B|E|paub>KojjbyRDyMx*iuh0+Mg>azBlgSAj z!-KDY-0NYK8g7p<$9^#J!LAnC6xwx6+wE% zZbI`f|r z|BzZ^z4+B9HwYC#xG{$+rmujGfjQN|!03TNr#pE2pgBv`%wskb7)VX9A7m^p0}DAdwP zIQ)Cv)@NSjeYWv{|FR&?=6emiU1@*4=l$96h123+^wEdjb)JGe$(J+G2wv(41=>R1 zSAbt=?eyYkVo0oHO|=vHA>f)%u&5H?>r`wJjInk_8du_%fL& zFLSj_J`I+0<6Er#rsc^qZZ}Ey4iuF^VHgPx2ts#9OQ1x9k*bY`1xIKcCF7Fa5Sa1m z1f}bO)4umosQ& zHxo%PvdD2*%@7uFw-XK!wcuqgDHlZ8i+~iN>|p{aPLO%IAMzNep!}-UqK^|EjI=E{n&WeRI}dkDH0NpRYKUD%|SLAY8=H=QL!GL6=w% zfbDEbO0@g(^xnJfa{mOQY}@hScN@xWcdifWws%RL57*ma>ql81w-=KB+6_yoOzqW& zPG%Wo+Zq~>Zu}8@+&O9SezPy!9e<8l760vy8_&O1`JTSTAMgI_F6=40;_dCfynK~? z^zyHi-)*p572i%eZkan7C>>ywm3M}xUFTsh#+Tje0L8ZjZ1ZNRHix#R)8Pv=Hw<$lG}2EFDG{sHUQ%!E#OfT;{RecX_+1)S$<9tP@Zji)@xtd}qQYa9m3A&tt_dUvogs=ose6 z*YEq{M-Wx3pFL|yyshzw(2YM#GYJ9Pi6X>xmfPviWm#T`aI9Li5`=L#$k>BkzB)m! z*TsK7!a4K$)@MvEq#By$?TlO5+iKHMnpC(gmu#iE4d`KE+}*q;WdBU1;y>GD)ixm8+<} zJeQQY6hOY?4?^d>{fwK#BdX8iFN-H$`{J{`+WV^xEGRdoRDYSk`E9zBkT4R`L;_Da z!^!QC)sqrZltMZj0#z;5cCBUcL;t?y!lM?nDPhxL1}~QkPiw~sEM+v($w?}mX+SZ_ zuZK-p_U6lH4(EsFi=ky`8T7@x*y8T^d&mBG^0BkcCUlpC_`30YO4{;bu9r`7BGUyE zq70j2^T1z1qEjhgf!IN=v^Ju`yW>xc?Dqs>7gQg3En9Y|#kJ^SE4M3H`iZy_22QHo`#BsoQ(tn zcvNPSbD=CxKmZm5%Kr=9UwsVgxoSlXfMR1cjwQKuL)vRTNb)@belKXq4Xrb(PjYSF%@*<>qqb zRbWB>-b7R?>R|?wl?pc}BCX&9NPhsti-K4|YQ)RmuLQB&4X-`DH-7g?XJ7JNA#(E8ac(fXR}(Rvm>etyrUaDB+vPs8szZ*EYJbHp=YNJ=!Hu%Xeho{7r(JfP3FT)K5zIhG9iXT2qmV67nBTAl}M!sVv=uV0@Goj zHYyV?<(`V81Nv#wrXGRcr9Td_P0xJHe(~0veN-jF@n@s;gtN;x^YK`NbAQw3zd!!U zh^0I5BNeazT($Z293F;(_R=W06^%7{kxPML2HrPh?(l$r)i8J5JYr39()j;}bLl9Z zdAl+mxp<3Io2rRe^0t#s0C&#v^P%MzfmF?klS;S{BE_4T$Mz#wCe{1POP=T6*OZ_r zBcyR%=~|6Y^p)}6E8d#;z;)TuY>$$?&Pq&*%d$X=nIr)j2G1hB76hCGFNrf(kn;sx zdf?<`R^SU+^-~1d4i978BGQIJyHI7n{kMsi9Xx@pS$M)+@#=y2jc-4<*u&)XTBg`f@*>Ke#n!Ya&#t-a$X6lrO z+e?uGnmTxVeQLQxA)95zexOn&vRNKQZbp{V%@Ob>P zH?Qr?z8tUGB}8;dQ=ts98P^QSAmI}Riz2f!*PRpl?E;(SYLW6?9lz_n9|HRxKO65k z%J{k#HMEVoc7RJk3p)ich$>>njo0ja!~OF6V`_ajr)YeB{ky5(UL8-3Y&+zLPp>m` z2P#&;kwLzf3jjcOFI1lUlW;?XQ|b|UVJ>vylNk=kTTc1<@EMnHdwJvFBl(|-zw_Dd z_x^A~U)3S$4TOY|4zD+ylK~_K*Lg_InH&r9&HxxzE7<(0_>#^qAKc-+^@eOxW$y`P z450oL80L(6Q_u=h`Z0sb5X3-p0#nG8!8lQ~+gf4o*M1#Ic589MP(4~MhyrvStQy2H z%8wIPCd6dXyF^8RdE*uO8Gwt4v;uxH85E+S!%Pm44}q)Ob>)#ux849frKjQ-&%5?7 zBThfj?A35XnJ)OSVj`QI3+~H<>%>}x+&rN<3^7^FIk7n$6G1?}oZd2up?SjrK}Mbh z{#cAtstHN$w^8Zu{g179Fd&Q=eBjfXc;>Km?Mr4Izp;>zK(Gu~Rd79QFP{??I3(&1 zT&fmWMNAcTgA&)jJ~M`#aumd%LbG&Vj>HI3T!4YnJFptSS-0$cWoXlNV<(PLQZI&k zTV}Ghn4D)mwLTrcZ^f4{be%ojB*H|vlv+SF{zJNR&@4lJs#O70KUGok_j zJJfG(&&w(g8PM8+qXRtB)Gu?W7z%^loYUZbI1&^MyNIb-w#c5p{<-X`(OdkN;(Hn` z&>2`TL!#A6aGMJ>ZNvFe88tcR|R3&^?!8vKZW~Eqo?}yubxnIVtWW_N8rfmbR0Pqo;-3MC-1z|(wmS6Y+aUy(qRYTdevP?D% zq>~%Wn(&|T;eopY-#1=RqpNqMHx!)epXrS>gP1{)^H3Qb;304l;R0zjn9afhk&>af zG?Ad%fPv{|7+&bj%~k0hqai=fb_+R4=95&o2IaLE<0IES*mvlbiZpUxEfX8+wfuYz z(!{A-6=3A$75TKmXrRFdD#{F*3x_XCX&thG!_vhXGgM~guBbfeX1?xS&`fR<7&8vH zg2J&l(XeeXIZheY3#)uTUyOga>Vgv+&wI*JYw`#gK;;t3CZ12Pf=$=C?bK}K@#hhB zK{u8FwF13YWfP06x=Vi&dp#Z|RP2^orzc7>@?PG5-@9(Rc-CRB0}odT zzwKY)%8tY1*AXUT@+c{TQ8lz#*j(XB}A4?a#@O8leQ2 zNJ6>?pxLd$5NnMuzZxGcemrINvdLAOi>gx$v`O{Pu{`4gbVl4@{6j2Z>M$57;m9h> z4Pp1U$Dm>?R<1`gn7=&+hFdfslGbj(dkzeDnFBeGAe-PaR2akvn?TwVG#k2<+NsMT z>2$FaqZgnWq7LTu+izIG*JT^N9(NP{qd%KZnL)V~8E(fcgATW&o0GArf|;42ZRZDp zF0Ko*WBl>QA3MMKYtw|rx_jjt@%LWu`|$1qAF=2=gduIKxHe_Tq^!y`7)ViB7^c%KZ zZ|vRmNPWK%f9saxU*xu0ViFlC^0w68w@O}VnBMo1f$3hGi%Eh*_7o>2G2JqvVQS%1 zDNevozX;hD175<)B%$%{&jTx~dg%OHarf-P<2&r@EPd*zQ-W%G!j`=`MYH;Ro?fMMl) z|3y!~`RGw&$8F>(u)zT0YP5V5e|GVLQv&an2|AjaKm?%PDZ(K!d!Pw4vs&NmqxiWm z44coNwm|844OJR9iiQAisI(4&-lnnprZq)Kt5^`)g;JQNN%`vI&$gU#j@Q$;M~&^h zH!dIDi~8Rt11}D5AII;$ZSPGc+rO@RSV7rLo0Q4-Dvr14YRb*XM}uJx0J|ZA$|69h z1O>(i(*#LT6vB&P0wyqorW2GIrHgZ5IGB?`H9-{mD@sB9UoE0A^*8C4@%XZ-e=v7$ z->?BAbqUdgF`&f|L^#JwIiW~Jp|=-dpc{TD$jUOGR3<007Ih8WqAmvGErV&2YKwXp zH4W{9e?}=a_e?R-W+pmqplQGkr-5l`8fYD~X`sRKRlMW-2~&1j*=A;vcr;oPXrP+O z7E6K%SQ4a+Gq4llY`sFp3eK(}Ve@VL?t_o{q3!TTtA4+VIDs;}V`=lpTIP{D27aUt zl>yeFVSqJ%Xoy6s2|cvtl7|+0Mlbr)Y8YgX-fU9YM*22>&$pAl{_Dx2nYTwufW(XM z>IZLbdp^QJYd1w{+65Hr6PV+}7R9F@QQ*XWDglR;vh%-@yv67uo_;#IH~PRWOuE~5 z@k`G7=F4~2SWB%#gdP+jMouDaDsT%hQ$#rtTc%K@jOzX5<lm}2o)$G@isY&Q6N{AO=Fgk*c8DAS-Prc9z5<+Lmr+KgX#5sJdAqu^r z%MPZa{15T-3-4XrmV4G@$f$@)cl!fDu8&W$8Q8@^5TODD%ZhN-iZ4_>pPW4dWBu)T zBn=314h^mW=%!DH5NX^+q8=sz@PWW9x7Lh^Y_$9sZ$9q4?x#M!(PS_vTj%Z8)^`)0 z_C(s}jrf4m3i=Af+--ysZb)4J!t8CQ&v@y&hH8hwYSDT>#@*-d9B%t|p{3ebUikoR zSv@p2&O=5!D9FaA7R7O?W+efvQ1YeANTAir-q~RJDZb>gr{36V#?fX@BI!c+p$=iN z`)2bHBSsRu1Svl-nLL07fNO+mjL7tX&r!>voJnNTr88%y`aIvy@%*;eoVNK|C2w*^ z$t4rGtqpvK4)|AC7QF?z2VD}rZqT`kvU8>Fo0@;s$NzYyFnqKB5~u9v_|2`2J7o^z zD@4`O#t>Or36K#s6d=>bz8WhPDhXL(Bi(Vix~vu>2B#db1Usj;`xUb0JmY%XQ;+ zx*j7vjZO44Cc&%Z&TFZ04`F{UEw!kP_!(2wDqg-6?g2U zoPW;K4*sB#0=N<=cE-I9fo$1YHfY z8cxuL3NS>DaI&(@CVSerc|cMH1c4(_JA)!I_-BAUjOg`2bN~LVl>wZqN~@%HLvByQ zPv5}XyZ!7R;QB?p{o|DK!KC4q2D8eY2?Jf0zG!^7BVU>DVfCMad3{1RggGD)A zY?6a+p_*!SBF~#HK`_8I&tVtV2MH{Tw)dq+bJ@gRgT1}=0} zvHfLZ4>kuc;lM*rp{pyyr3?WGm-DH^<1y7+@vJA2n zB#P4s1Y1pJ0^!y$j5Z}=%oDwO z7-4v&YycjIgDyFdaB=8QuotvQ4%O`dQ4{TX$^hi%U?n1@ zngdZz27ApCC={w(1f#gPe8&qejIABHWXLKTT85UPWoW4|0lTmJ^@#^AX*Y4uNUKhK z4nz+9Y&VAd>Ox!?+<;7cjHhnT%R#P}%K){c_@?8(ZLAlz)?jI*KWNV%yfb@4$%O(k z4~8iS-33-m0ZT~XU$0he4pLgyk!&ACh};D9t@ z^+8w?AT(Q8DJ*g+4sb!_#1PvTmASlBi+aZ>`upMAMfMyQFiRDiYIwoCC#bmZU_~BS zl9H=~MMVR!wUF*<)>too7A9xeeVkd!Wi8d}wCP$K%U|bc`jUfxJAoAx)ug%4}4m6<_}T{qcZNP0A{Whb?emvts1&Ns!!$w znB#D?z|fPQ7s1fi%ayR+*>qn?#e`6dX-<@A*(r=&O54~Nn+;}pqEw6e<&==p(-s#I zJ&ADC%An2vzix)Kb&ZN@S1EVI= zvn=bbzwgPQ*|>>p30~mZ0WpP;a==(+nHVJscb6*%tM+Op(%YQ5;w*k_T~+m1U{XnJ zwd8n7))0Rf%6FZ{^NJMj<*MqtO{+jRp?CW9xgAfN*K9Hsx_u}sEjnef-|wT!sW+Gx z!`G_4-C)_2&cC$w!?tsF7^t(5ywov`iiNywF(6r1{lvc+eUIP>z4X0rN@C|e( zwxdTre$jb<`*ItLKJ2J{${wH_+#U*mCv2>=+i<}04)k~5k3VtJuTC?&(~V?IDpqc&DVA+{=9`NgxcU`$)1I7pimM$9b8idjuXR!H{1YSLhu7(~iaJqmK_HCHzR z?^k($9^2(D*h3Fby$f6AQ|~Z%_sRVb=x&=?C@r$*PUB4a@ow~ktv~eby6B`5Pe9Lp zX@D!1Ml64T3O0lS1fQeOUm$kUJW#sc1Jm`*C|y4tr0XZ# zHK@vFd(s^XgQ=AdE1j&TTn-NRFw(9W(_o_jk|f|ysEUTL$Wi2=SA+4ju*eZsp7mDb zp@x-d6e@MR_w281%RU=3Xw-LF1c!F+*pt5F+2rc4K3ibsqQWcy$3Qs0G)gFY00tbA z-k@YpAc9k{u!q?dixGsDP~{$JS5Lv^&FJC6q`qeCf@SYT7qPo%(fI9t{&hs{K*PQ0 z2WR~)x^Md`8(_1kiCq#$Uy9XUyZ&9E^}^exrnG?2);l~)Zs zDiS4eRe&ojauO%@au^>0sBxk$=C_vnKL7))l6SuIuE}t{w}=ecLoV1_*CtOsZ&nI0 zh2Fi;`&4+E;>mAf)Gxr*%fNXDu@yvFLj{`w)DkM45>`$~@vI;KP$Pk*1t_5M`nV+E=to^eH-q8wHXZZg0e`o6(BQD}~55eb%db zM4gk~=E~J?J-B#D=_J%zjdnLGU@{UmRc70*rSRRE$^o`YT<6}33*EbYp~)vr%1a@z;C=Du}X`z&1!ifl$W^poSJ19}=aV{$M{2Pit3>!S5p7 zF!T(~O;C{uo6?7Ao=_^-9S~%(Ao0CigzNGNa#)6Lp{=(|BCKFSj`mA3m&H6x4v_bc z+Tgg!)JlHdXgQR2U;5#+Nq>n{myI-xZIFE&hQz`UAZ(>J13ev_+d3m2QE8e*&@uF!jN|fS!3^ zpzw_ghHnBWd=nZp@`PcQ!|1L0zu)45ql9vg-{uBbp`zKH(#ZcL3SCT!m-3Jb6~0pr zhAAYW2$(|q5mu1~+7uMe^YFe=Ik6i=3Ew!!%GSe1br}7@l@}kibg!CnM26y5jI1yw zhD=LHq9juxIRqF;YRnNGM&JCvE}Kn}pENP^Dn+iHs8kt+f7B$s6C;l63RFAQKy{HP zqNc*;rVX*@8Z3v?7vB8)L0#j2GvqI-X_9QQ52kUhSW_B>g=#2csR#gBsb+E(hukGL7CY(^kEP%~#SV@pkBc=vrdNv6`pYGe2Umci{1c7^>;lHur_)(_yt!hzz0j=Ev7iG2nDSpd!gkkMWm zy!DF-!!|JH)L`+`Up)2lPNTYx#Ctv|wQe!KGvos|3VmpjfNnE#$1tLS3N~YN)&O;Y zZ*I|f3w?adCVl4|GV%X9@oR&nnSN}u@jX{(?=>W+2BA(L&jFC@yDOmUHatbm4%%FK zP6S-F@Kp_bIgsAXpe!J%$U*I_zgOZj(@t{dS_&K z)oP$BiD{6X->*hYbdBh)g*v8!3aDH6GqB=?aKO)^>KDu%RyxMIm^yQ=&Y?7ZKMU}?=a zqXH%gRcQ_K{lRCziG&Rj>8VCd@9&G(o%8Jri$+fV-zK~pT85UPWoQ{%hL)jaXn`>m zCHzpXkj2O#)oskQoRbzUkt`1Ayq5Xemb{we= zF-jkG4B-ku#a9SMZY~G{2ddFfn#!$0Gm;z>K;@(W=y5K}S41`&n%_Hc z^Ltg6XZRNw7pAMtza(_U`L_iw9kH>v^NWu$MlU#h%2k`JOyNBxTvc8^8h z39E&*5O!3$sX~g;(}yK=DWJayMejRUsQH(s20*x&J$6#1>m@aGy+){u7mie|U9KO- zFTZTK(hGjv60Q9&Z35Skuzr8{0ky5obC!mNFiqHXdV%o4DOZ3ddI|{$?zr%$ z2cFzE*f8c#es5;cNB!lpskhIWiMNelcTW@*9C9)W^95lFT2W-v3bMgmiMkof1%wpW zNA;+LNfAI+yzc$Y_PkRAGWR`(U)BU#f3Xc3gg?XY!LWpWZM^*9mf7^4m;GaM*Ii*# z#6lUAiOmYkpq#`-3yBEV4KFpw3LJn-z%)e~-7G6`O}v~1Ac|_!cC+cD_RhvOpOmgU z|42Ih5JkX>{CxN>OjI-<~%nATb?`DEQt5r)#imk9 z($pP|w0q&DgEvF=Z~_>JKyk?|L6$_DB&R43FEJ9E<(S?;459RUDZ31^LG4jD&?UOJ z`L{DvwVZ7YDQ;u6mRZhbHvPo%gMRt$i@OYY?W*Sa88V-drUv;0m}#YGkH`yiXNqjz zr0&jLdL}_4Dv)*{cny*}<PSIASQ*Mh-3@@N&>1uN6kwCB9_&QZm+U=e&ELK@vTK@m2&je%1`wp6ivEvO`ivq3#Vjt3a`?$luI! znG~L1+|U*d2r|GI^yz>#TbBlbf&j?eU5KZL0R#!E3ax zgCuEjh0AHvEFbewWIIp}gu4MsdPbagfR@6YCM&R;DEJ?DeVEClv{;L&e(yAe<3d$3H;i+H<{W~6ejnmg#&fPJ8-U9M z3C0oi1LBng)z%6KM3b;uJ$3#c-XF)kJ8Hw_sk-T&O+Wek#$7%!3sqF4^)9I?hL$$y zZ`e~KZT>{JaWW%=)TpHzfvdy}P9dynV1t>#8!bKb z#ebRiTL0L8)MC33HUKkGD`9O>pRW=Zh|Q)<4u(g6bg5eH@%K)m|q|=K0Cu!4sK?|Gd0xV_7SVC1Qz2R_-Nr%(n*7WFOMyK~& zF?!6O%?MU}kJ5DTdC#M(Zc=W4dG)v^O&nTb+E9o%2%Dlb*2D|#`Lw=f$4p=>O=ya4|sxOJ` zqYobc#~03QLt-1x+9I}+L6D4G3a_n3MI?>SreJ_;GDN}5qsvClNf{C^% z|3YBa*mAA->HV#bPE>Ljic>uouNATYoZGS}!^4G!M|FuI`k^qgc=7RxhFNe}$s4^st8>`Z*gh+@0T|%sjImS!1k7zX3%y+9>6fl zTid(3Bn|~+N>TwZG)o|EP)(>NTkVrsKyP-@gj-&{c8^lbQFY0813IV-00|(45(6QP z90MVZ99pE2Zi220ztN=PH<|!bI4C?VvWt7o*~K*1G zbvQ~!fczEed0&&^~?>F;&YzSv-exoIwi6D{ag2v;`;sKW40 z$g$sxq;8OdGaWlPF|y`d!}0X|KeE5>PC3j>N4K6625E>&oE+i>K3m8(@iGXU`m%Yc z7E+*-==@DTwSI7W-P)`nAKC$k7L=V{DGNqJPDq(0=UgRc5>Ci^-dD(jp#}~k@-c^R z_IrKWtS8gEzTUrP#=-lTw5AQ~L*US6Smg4o$Q9XuYvBSumELCayB3bz_oVtojw94-mDDaR2(G*ibqlQ-5I-M0HHk_(>Q70-7grM9aDdR=PcTP&@q>$YoiNCsK~!4qPSpAUp%<_7%#(JVQpLfF>2*B!w4lGG6b9*Qa3z zb5HERDfZVYd_6tA`oQxlLoU72qI_;I!~V~4xIJb#&qyb*9FM_{Ty?*9PNV*F^)7$G z4LHt(#ru?T`n7TS!*qr{mk#fH`S`1?3#v9!l$0gVdO)r++||Kz^B|VU1ernjIzm1# zuuFk&?M^zXX1_XP`Fp70p>i440r{6m+cGhMWy@U6hJHin5ubrwC%6>wXABgC_H=ML<|fB!qdY_k(Q_r6j}(JAV)K~c{oEuwio1l zK}IrjHF!Ul)5|aVboU!>IJ)X*Qw+ZmHYH>a*Fo zYv9=B^#z2kr2Y52`Nr6Nk1+8kNm4_arL9yWVQ@Rbb+@ruu1pDs_BL9!D zB8=Ybb@Ug%@4eUddmL;Urc+X(AXx$@DX4B5rgY4MML#l4)i8qIK+h%);3vGH@biyWJ37IRrV7lvDVww)dO?TB^z40<|knB_+LSMsgfJqs6?CJ2-_ zN#zw6!6<@CKJ&Sv#Q`9(){Zb7vZFnK%XeN(WP3SLVl$w~Ezc>1{`e#W1uQd!0`3Gi z=3XvN8`@+jfW~q{3JTWg^40zdcX}heH2lCj&jq_oIztsDm?Y#=LsB7sQu*bL^b^~9 zrvzTR+YDw=5d^Lu%EBfSYhCDWr;6^@FuelS-|cA}gZ2v3!h04eH&n2Ldz>7m?2NAAD$x!ooxfwpsN zNdqB^f}hX9Opv;zAdXSQ42971d?wdj;FR>LQYNbQPE9Z$T4A~`1S|#;l?atC z+^KG%cYS!mE{;Yea{f@M;{x6p#cY7Uczdc}V3MpPw{hJaY(^`fb>55bANKPh4@|1I zdW!2PA6dR0&rUCFi;gS0Gxu+!-&PX?+vvZ?(^%ige+%v0@2xMc_~ux=-D7;m2=L&G zPZW=9dI>GPGPbFh)&O`}y-L@mr#!ZYb=yZqD5$h4RQUnZW!S$r-kxYc$xSq!oo}Vb z{&CF>wt3Sm)uKp>RRR&RgDWeoR6JswozAS-J#VAG`L=J_0V5T^I&bWrpt=esJM~Zt z!Uq3}$~loENH-Y4)zYOhEVOTa!*6@yit8tCta^;w=!xH6OkBM9YP|hPt*K?aZf1cW z&SlIbZ4IZ{Y+bA*gPb7~i29ih$`_&hGkg@&9164rLNh{)i0iy;p^g~hY9}-Rsy8-& zX}8ahfAfEUSNV4Oz9~O^)$t&K_wL-RLM+bpsN9ofoso8DqYP0Of_z{$ZEqlyy1W# zBYBBn+JVVxG(0p0#a2KO?~5@8k7R`pmO&zq0&uRQdZ~Mo<3z%fjRD zu!a#s?T}W91+sm4G1p}yqwsIsMG+8=>XT6|5-^boCv>I?LQ;)(B#;=bLuE;ZUm;N+ zJ(}O&wI?^~|NcAwcRI3JI<#g?!Nk6H^{^6)X638J<=0Eh@IBSz$s1reyuFg-A| z;=%_4>FH%eY>|vD{(=-xcu8XEGiYjCYhZj>PQP~LR=*T>f4bCFtrjQ^V|zGtU`PZ5 za7Y!2Y#^jNAjY~iDw9N>=aSq!Ug9)LC~{GQAkqp;Wuw!W0IbfZB2jOBFzg1)ee^lY z=91?g-mAEfRpSrLbo@Yb=)DkRKLErG-`AWIQRSCH2!xM95O(5)MW^sj{Jco5KiC6X zW*s=Dn8(_uN>RF!fmnp?&(p_ktWoxUI{ZfKdGOo0k*k0^|&cg<~omM=-~x@IL6kRzEB~LI?N%R-ACntyK#t7z$4?VjCw2 z{k+6x6MT084xm*NM{g(l3WVBKD7+#FNx@=IkYx+N3JzGxCxIAK zl+AMJhw6%7h1B>}h>5l{(FjBFeIP?)XD)j+y1iEGx&FEJQ9AfY=v}ULPt*LU%j*q` zd@n2W^Fh5qSQY_tA7=1sD2slaetH@;?>*c3hTj>Cawj7q-VTX90A`D4GfK=HzUh*Z zKUK&+2!{;H=~UL>8c@iMmeurQZ(XoHa?G_R(a#F}Ev3}5MItpoa6t_QO^aPi(_%+7 zEuK=EmTEi9YI^m)yN%1y#~Oy(YE)Qqxjcm1DtlC}0;l4hiF_UgV-wG0M2rAl7<*;BJlg#D&!Qh1)wOm04Q=;hcu|f(3 znZh7lj7lzDAo851vYTorkyK{zt&2#iCR~Ampe4*r~@(mj&ctq%eN6}a$ z;EQP@M+1kBqBiFCdv3q*c*n9y`wZn~HRVf?jG&`yW8fbc6=W)A^yS>!u`bBZNAkyh|h z0)QKKt(Y97N#=9Ht}BmR3Q~^s1BG6r?>T?Yw&(5KW@^3}0apOU9oT^VjiD8XlwRMRbIyk&ZKeXS+D^I(n$JDgZOJbzW@3Opb1tLQwjQ5Gs?o`@n zC++e*`s?<6%*O4v==dwYkFfkOqa1kCkph~!Mk#5%E1E7MW=Rgk;g|#p!~ycOw3g!F zJo>~LTJ_+a)BEW<)^L+%eT(k9ea$!TeiJlHkywkq6ycP5a)nGPpu!FygOb|pyUBOOugfpz5%R@Nfh_A+p;fxFV3LG6doOLJ$K_Q=YF->7*TpeL0QvoaDgJ}-| zGZNEL;Z8?bpN*h^&IPnirvul(1r0HP_>h^r$lZeAl0^EKRg#qo_IfIKZH`J$o6Z!Lb?>V zSVn*Gn#~4&UT@P&XMgnIi0fah-t8ZtR5zTHWVN)`z?f-n3bfW^NKpo>8xa$j*Xg=y zNDRD3uiWFxf4#S`Um5!w26{mOH@N7774}|lGs3SC1b9PzY+f#iunM&`#u#5xj8gU{ zBoN||)u;(VS^K#>lv0WHQ;Ecd_kD1j^UL!aHu(=mN@~+@-yOMa>$iJ-Gh*U@(DjYb zP4Dm1UoBXF-D38%Qn(6+N7$5FaE+}6CM`E`1Nf%yy@V1T-f3`Bm`*i1T3m&`Z;2EZ9O%HAct+EAS}$sy$C@M#Y*N2O*!}$io8^-jYDQR#urn&$nyiaa z<@`NHZ@D2sp|$h}7vHgfSTwh4IFKojFOcGMSp%9+4-+bXR;`es2FpkES09~lk~g?d zsqxgRjC-J7hy~Lo;o?!bE!u@7fFJqdru(%JP2!aO5m*hc&7btg^o=j1|GNJOxfVEW z`7Y;~<#r;Dx3I(&Qn^1(GvH(T_XVQwu&zaB=J7x-0vtM!hk&w5wYdmbeA-^(A|L?) ztRLWl6v}n-TsB%hqwkyc+uCtAY-fhB)N`*+>yukVg?H|P;5wS+UH;GOO6 zk%PRz4MclV(ND7)aLk6Nzaj4FH2+;qkQu?w2TQ3BWD|h~M3MCxdMF8idWeLh&CJ3x z7Oxq8OJ>sOKMmsX1>Ir!xc~F#?=fVzH@@z;jVuUx_%|N+(Eubhaa}o)!zwprJUgxQ z&BUqewwX3E?YP5A!v@M28ikhxf+;q4GAGC^FGwx9Y_6LV zxLiSk-5bcLp=1$|Z=rhILR@mvuE&m0=A&7U>DQGmw4o$J{}tW!>|s|4Ywj{LfGTR3 z!QhX;&5$_lE<5mGK)Y84k1_7qR3AJS^DO{BNb6YjvKgm6fbwI(H0jX6$zT{20LboI zv%|}vmgUd3l^QHx(-SjmmyCY@Ak$!;2Tw?PYpItL`L6yM5(l^|^~`$J(>i`jKh^Wr z=|7&meaVkwHc6E60)9udUr5$69DhrnKlhnkFa6B)zKlv10LGnxVt-h>q9~Ar9&ft( zpZ;~h;hyWinr=450AWPbB>)mHFFFyI8S&6**W_EdY!pvnLJln(EacE&`HAk@ zau|8E94r?VBpeJQaPt@z*|88gLRh697KD8AvKUZeolU$9av$DALgadZA`kuqz#z8v ze*M4~4{58lU%PsjDzENmx@Q~r!<+o=+ERFKbq^4w-gR)P!KucoV0Sc`+8w)VrXTHg zgJHXJ|294O5M$RQxWc;Oq7(fJLMH~W>o zYUR)S&09Uy#0Bt3tdL6N`goCWdF5P|Px`X?S;?MmFzdM_P6mY0RN{|6{%Akr=J1H> zQJTNgFYSK=wSIK0>bRYfw}Xi$DqDyJqh`cVnp0|-Mj9-C&^x>}{O$8{kJalD8RfJw zpxoR6GV9PbMoE^yzd)b%3@D}slvL11ih63E4bxK_*15j*!@w=Joy}Uqg@LaHAi4}7 zPMnXVdTaO7Ms(gWxp2olD?hKgGlYCoL%uMa(Wm-ZaUL&#!Uedk$Awr!KJ^IICxfXj zClHPn7`^rLB6!CDtE~|NVfa!iCWK;4bD~7{3{TS3EH)Jc!?BIAkys2C#nhCaXiubl zzKj&%x+EycjfSg4IBI3kX0^R^L}&QU*%w`~-9?tF;gy2{0wJwB5XcOFprZ=p$GEpb z0l-wT;W)xEow1|8e)s*&k1Xws8bpIelNIOV35SX+M$D}+K}MEIkRcil5JZ95v0C4k zM$6dFyPtbGIQ!@Ie+C56kS!p*UIk(ZD&vi?Ln)y2f3FwLsR~Ll0x(Sou3hd3oK|3x z$OSM^-JA$_!*?5^{Da`2Dm}`%!C%mZw$@hIJ+?FdM&kA3d!K8l8W4th@hrC!{HJgM z7aw$mG!1qyR6;ypBo&~tAu^Jnyi>@B^m;(&m7zQ}Ae&`UT!xc5B{l_d0~%9Z0erYi zuPIOx5-5A=Xli%Pay!vYVkZnBny&B%b6hZz`v5;Xy!Z2$b;i-360;4!_7?`&+^*R|s&)LI27hX=nP0>43%!lkTNNP(L_es65GU~BX6-{zgKI43xT2g=p zMjdmJP15QIc*Yc$YT{){Icln_Sr^+*gJtW^r#in24gd0(>hr#3oeP~EV={^=vJ&79 zkfPYP>o1wjOk9N>JA>~hU58BV zsJV0(1T)1{7ikUm^hHK5FCw$>)xvGb(DOXA$)L#DV%okpxP z!gWP`K2?tll(}ie0j-Q3R=(-!Q5q-h8}rxnTzBf+WT*Yk8h8DECYJ@!6!ttT_YfpN zxPkrySyYlptyu2Qa8&KRX>zA!?p0GJA9Y}r$psrC7gRO&U~T~ju84ddK{Ts>9e3&c z?zkUEo*Y?OXFRId+KPRcPS+QVav0L`-T{MAkmYeYvJwN~sCUESP`h=ye?08|J4cCl z&tk3XZ(}-0H^YcftGc2(QMwo_NJm*kpEm{r#^|3+Isptnmt|PU^`m~6k=gF};X8kw z&8-~%pMnc(Fc@%+mOVN@nZt;SJej)k+~IZ|1FB>mG2yuv&=5i?nI}om4YIkfaX<$?!||~tlWbkj~eP2!lwH?m3aO38&>dT z5ZRR*aG%aCcE35CS?f2;iECJfNJj;9;>0j_U&9S>xECZ>YQsB z35ze5P0HkZ7099JYRb*XM}uJxRP_P$*EUBFE4Af>nLW9Tiil_z+j2q}TyuLDU^g2` zF6h17P3LJv{~moEHD&;BAA`5A$0OMrm*^Y&b;EY?=JxNr@z?Wy+;mOE1mO)C58F6N zCP6$4$JfO4rNy{tOJX%^Uq#X~F-~5#L8k8t2T~yKK^%%Cd!|RbI>(bkt z+L;;i*o1pVU0_y#W)B5)_i39^0?@K~g%p~;w6|BxFd@*D$JfIp=b~seWLL2lQC1Np z5D8z>GUz5?n0~gXFAmr)1}jL?Xt8!4b(Z(Ug#|@?cXh=|ZI#9VK08H&S*0B!c?l0X zv3V$*2&K9dqJaO8z4w4~qFUpIvrAP#5f!@^v7p;x_%_1b&8h}gSwRle^zC7GFIc9UgM-}fayadsw?Hgn2z zp66e3ZqgBAQO0z=iFsUn`L>g9J!G4CgX{)he4C1aY2^C3VqInL`^?;Ma38NTVx$f3 z=l2?@$Dn>*kN$>>=1}XlySl#_88BK8+8f0rnS*HVjYuGiNnZ*QXNnXBoi0Vp3h7Qc zD&imn7exuzA>0!o$NI5O=0Z<&-?V3ZN~UZ?ho`2tGc3m%(y8`%GQ*m@)smMg)}8@T zeRC=*viRHr2S}fmd-zhdiYrW((2L`5zT%m-u||0!Qd{v@_-3JWUA9V-Eo46JIt`fqRp8l6N9vB2O%N6F{3Nz;|;+--EME7XkDw#<0PJU`2`2 zOy@a-BZz8I$u?o9vbKa2%}S|ucprsl<4^OK^F=zC7Bd%QxnD+XXr!%pA>9wrEhCETOm9%NuD|YAkzZy* zmmM^C2nI4Rb~pn`?xCxtxOG^o6Ll1>J#@o^Tle~H3|_yF?s?X5S-Ekv?)^sTK5vNb z^}FfL-$M6!mAcng=su6_+d@k&+w0&r?c)tPlEv(m)0vDIYYod1v<4`9DcG8*NftcZs-y3R*|g&a67*jurOd16N{k zg${OKGk5$yn48Jd1;f0$|6tEhJnQ>qF@{&n#84nC@Xdl1UEsZF_1lln$_cktzeTdr zdp7m{eg3g1Y7EFn_ZqmgrD2$53bCZVSIaL!DJlut@x9=gCGEIltJBqf`Ft{X+5G=^I| zd5Se~?0}WV>1d@;*A|zfBWAt}06#dP!j4eqY7h>?V{|RL_|ukqzn^N^k`0B|c9X7r z9o_2pS3OV86Li<|$2+w9Kiy%}ISvi2elq>}0gZ4Q580=#8Jes(EQlgv zyOb(tBswf~s(G03@}ZLqzJS$EApSMs#O$=(7?x*45j@8107RucGwKS{;i1!eetg*X z{7jQ}T+X*IzM+O7henPU!V~qerRd_4SzgbMdQs_}iro(tVxR*Yhjh zC=PgJ*XPfA=&$E~Iv_av`}^@X?tUNd+lHNAVVV;9?aaeJz9j4@jdI}EEkpYt7kpgu zROksE_|n0v-u+*9IknuPyjm-Zj5YS%o5;7O(&P;td+l{ge^5@>UBJE<0e(@;K*o_O z+8NUeDau6XjL2epK*ZPy*+l{Azd-j-FI9t1 zW!X2W(L)YGSycVR^stgt4`c@Vn-*s;ui-3MfGUxW;*1cJdd!b*Sl~7{@KL>6YomuE zw&DHw$nfSFr$ly4jyPy@=JGF1msXl;LXP+D+WDH*Q;o_ojz$j<>nxnu6;5;lbVi{Q zA1pO6>L7RXsj?N20&wdo!eIPvY-2^SZ&DLlzU1fb*IxXI?%&>b!!P7~L%4<|C0kNT zx0H=`&=x+k!D5p|4EDt~Gi_N>W~T(yTBREGV85RJ%@d3Tk+!6cMMa`d!3+$wqUnCV zsouI5vPKiUz#-MksUum=A$-ip^oZ{(zZ_-R+*JRM=tqN0O`-A1i!I8^+xmkg!BAs? zc!0+HgBp%g%y7H}CX&Ra*mF$0^%gyfC+(> z2Bdu$(b0yuDn-mr)t?f9J6h)_RMdz!6EvesCqDK~!_l4*{h=pRXsT&5LW2*T_t;O> zJ_8Vp)$jLM%xzKvR7<5;urnok{4H3uqIf8zr6}-xe?c!ZLg(!FijukY%YFhiIV)KZ z@pG~aA!OXWEN0Eg^bD@7 zzzE+gco74KLia3pYMv1qTVK8Y&>f~2l?}8Ge~g-Eae<<{JJ9H`O_x%h8Xtxsrg{Qm z`!xRyJ}#BMNIxR9uyx6p#5iYZSq|FMY7*&g8gKWW<|wuG91(hNH~(VS!jS-&-t-X+cs6-3P-&VK8hr7tx3_)rabexD7+T9> z%hA0Q5foR#8YQVBGE^3XRe6m;TvS=g7F9q{N(->XLnx@1G#QpL>M)Xs+V%ThTjz=nO_@#Q}bIda)xmg~N>p`zDqWgi!FR_OM#Kf3n% z1()<^n4=jKUkZxI4Bo`zDpy-N5##4bS--~Er64~NF^mE@8z`BuIH`3iXx;{GSMw_x zMh@0h>y5UBoi7@?eW%t>ySQr%;X3+nqV^8M#3=k590{>K9PP1K*=kEo00&BF5w-6U zZTFx%!8Fcn_niF|KZu2P-udA3A6UN7AU-V|X!Ppqg@Z{Pa3TE}OX74SiPM5TT{Q(4 z-~GJW)v8jrH5u&de{C#u^Hy~~q~eDggrOjw05KLF3b5R$T~csNN^Kq#l8R(ArWFZR zbi94^p=!uFEtwW<3opJH`smR;?|9^&@97|=1LAquCp%Uxn?QN77MIZ*dzx!%I2^$d z3PsL_6qf#KY6Zap84F@t(5#X6|1LlJxvd961yOxz#Zcwxq2FI!d22uB%(&WK1#u1r z$4_U&2|=1GV)YT|bVgp`v$$uV<@!Z?p?G$ z?aD*@`pHtr|Hq8iW}K5Kd*oHiu4{I3wso$WbRvc)2+$6|>B~4PXHsFYh+sjH`=)(* zz%x5FR16#N9v%<9^6w+IxZ)l*nRn=JsH>rY=woqK%z(`e<^jw;&FvD0X@_5wRg$<_ zksov26f|$`y*c3F5$9{a$^BIEaMC-)zv+E_JfHt1vqSPjJMKDeH%6aYtj`=-F%OAK zM!>ghQAAmBE98g=>osT%Z*_GzPW4&{rFLvNaNK0;h^_x-g`f&kGBoSK$?FEk7a1Ta zEp9hS{V7UkQjGZmpfrLVpo__q*f`$627;&IwpytKdqfucz33ey0+EQ{4`$f1yky8g zIs#Rgl+ZKJKRc`Yc$3aBtaROA4r%^pv^3)+1vw%=ZSmLB=0&*_Rp?vwb28jUa& zl`U<+2{wO6O!lQ>B6y+mP~jlSj9$&-%`~%7zQPNU&4G8v3zZ-uhdjsz--ECEQoh)? zYUrpP|1YcL8;ds03sufKZ1Jx*8X;n>);yC2OVNAMi8Nf~2Ao#FX$Df|?v+T>Zn*qB zHe~vC`CXjlT!Xd0u_%RBsJN(dbBh9Y1e}8}o>#Q=9_d=Bb~BI-x-K~IZZJjJSTV|D zL*q}};^X74IZt=JOMs!1=t1aACc|=%-ly2^$loVlGD(OtnpfaXHeCk1IEE|^3=?XQ zX@1Cj&DslHdn64pR;0V}}B8Z9U;+!a-~uIV&h9r#h2t;ZgdB>~ry=fbWx){jU9M@{au}{YPl&Khig3 z2D>K?AZVu2v>X*60g(!l`r$w3eVy23;&i%eBj* z673M7i#CXJ((NFJ1SckX@>hwudFXWB=w%|oP7(T?S_G~JPO>=EZ&2L{X}ElSmZN3a zE}l<(eA_cujp-WtSF6;h%}TYh+58I>YL;zCX*pp4s`O7u`ia}A=a+>~L3>Fm&pzbtrn z#hSND#(VeMh)}1aW(j99iJoRTn64K5Inf$zYa_z1MfvaKsX~RrfL1?7CF|@WbLxf- z8%{X!C{$Td=C!IcofG=u<(*ExV$orS1zoMAmeXI`+$xFPo^)p_s}Q1p|7Eq#39SjN zI`pv)7W>{H(of7R9-uFNKZbwK1}0WM}YM{q1wq$mzL|`-=Ie`j|SR zio0kf;yNX-E6hgg3Mb+nWE|#k7WC{#M*r3@`IF&)SFhvzs^#3!qsO1R{^4hzG7L|O zvOu&dbymUSA^vIs@t96YE>Kvs=E`e2l%l6r@Ob=kdQJ~ZQWxSFt>9~lMJ&O4H9+Kr z;dL!oxHTXuoe69cB)Cu=P2lskR5&S?Ln^r-G}QBKbHn4)jEZa=k$M63p^PsXZHRLu zJ!4lN$7)4TGqmBVU~cMlWo1#Ufuv6IqVr4#{W{|PVH-Q`UJ#mk@Ef;0vHl{XFWhSd zRAmQJSf1@y#j}|dJgX7>NemWv0G#4j%e{%!UrH^3+16ZP)L<_R1?oI!IDb2? z?80vxl1lM-|0}w29u%>Bbm z=~OH(fL-KRc^aG`UidirKAkHcyhIg8S4r!hW=QC>l$KBA1YN zW}^@rD@)euQ>jo`1*1YI%D-G??A8$HtmKVVXqnt^AtNUR%%msIaqM&7@GaQAWmo>E z9SD-&u_M_wkI+8B|`s=Qa2|jYiHNs#9QW;_N;J z{>~?8i^rILVmHoS4F$WA_gfzP_Srj!Z^wrJ*(59X9aZv3(kt}_&B9ujlkJgIx#GN$ zWaL~*B|k5q`*Z30u=DR)vV-P0d$8x(R{t7+n zH1WD?gXw&EQqRfc>@!>rb`G4+Int>Fmki9poVg|~ zgI!QKsbGvblPq-m;&24wim^AySl5@yv z%PRm;6&POW`lD5IQhCkfrjY-e=IO_rvb0xL6~~dsuPR=%ObO!NFjD#~^ADTmulT4K&>qD-#a^3Zv^Z1eOMzaCxk)z^-PMda)7)L0f1#w$HbMUTSD`9cz?OP&Pcn(nN->{;pl7!;ca;=Wc5s2TW4FFC_`jl zYD>E%^xYGmOuQgo>TOL?&|eJwFhPw1s>g!yvH2FbL7{`7}9% z;9>YbObrRai_>1cDAT!-g~Pv^fCs>i$CF5>L=2gBqul2(>j4K%szDykUoX@n{N;2x zv}VYm@#@nFsIvhz34TN&nhryZW&_D6v?K!c@N+m0jI)ST5@*Bzf@UOecF?uM8w;wi zzo!hVT(rdjrp=E(H>-GCXx8gLuIl6uHW&+3j7lnN!#NZXQ;vdwOglA-#`PPzAT2$S zcSmUH?0;3)U2ulMXBSOqs<3ChJH_|{f|QgJVOb@2*0)oPih=DcnSp&r$b931;r9-m zZvdmg#aN54nKNhPG{|28TM;kNAwGk?2mv(qzU3_bL^um$AC9pa;#MeUCPYOcYjm31 z#`*7@_a5J+&A9}EC=v*oQRUVLoP6>cd006G9c@bJuF#FYoV&+9Lmo8(2ys$|=&dmd ziABNk9tdl#`HHcoX#iv42*4FPkOMoFYLn4>!8;U~W@+M6E9%y#nj5HT=JngmP!G(j zw*>?TI^n+8pHKt6%I`S;483#SR^B~k)|iYSbS{{*Bo%C-EzP+kB{wmEy86^~RxidD z@z)2@u!*xrs%baxEh}Efd{=io|NZU`?e}B$=__nS>}%?a5lb0K!D`FMkQ*YvE~U9k zl&MXdTb8mNNii!$v62+u8sy6_sjmz*+`RQUb%UKHRJ}MG!7q5yN)|`a{V#W|488Zr z^p)>^#2cjnof<)`++t6SUm&X^Gx5R16SrUB0B~RWhQpHFWc@q~QYq1~% zKsS^W0YieaY1Agk(1-dAV<=~t+S@16SqzbZ5WuQe?NG-WP8@3x418gnkzl?rWa_S= z{{_v+yyV|?JFZ?jYV_Z9R=Ynm^tkgs?bxxl7;M9c&}=b74x6~6K*tSW@j#w;#zZA6 zr+WZg!1G=HAX+xT`lgyd)&D^J!O+@+eGgoC@L3i8D8iN)8P6;=tSvjLwdkW-iz_3i zGvW4dR`hn#H4=RjUyJ~tEb4$VzY9}foTL~E*g!llxDh;O6mM3Oc#{&a9fd)!3t=>p z>KWX7o1jD_G?HMqIf&`nzJMUWZ5HZ~;739Ly?)tAkNo%jZ7Ton_2CCYO%;z+zhZj5 zpGaOCltHR+1m{nu6PS|gYV;=SjP`QY5HgYsaY~Xpm3SNX~v|eGrF2vb$ zXL<0+TM7}igpJaiH(92&%cc@I_7XOUikz9i2% zvCoQZ4L8euq^{?+= z>GT~?*lB3cYiIn98b3Bo=dA4_c708EW__tPzUz=zCXapb$ahT}JA1AQE&KOn+qUk0 z)u!#gRvZ^&MSB1_yd12<$t6jQkRz9Ky3Zayl8df@x1=*0prsZpO@O;3{ zIZ%B^!0kdE*9|^o#)j3Sw;1)8rzD;TneH3)*bnd9jbe3c4xb0(5l5A#eM*fV*UNbo zYNlX;P-*zFPlcX5|H3_9w;C-GtW|knIvvegMOkW-L^)3(*jO4th3V*K|&)l zLq41~8P4vPG9n1`QDFicJa+L`i!3T&5LT0xasASriq(o)YPrTHJ>!y^;6seh(dDpmK&k_uQ8xEYt3I=`5ppztKSptw8=Xt9c$L|RV zOcH|~vZ!>V6S2n5L>98VqFhdO`MJ=o zIJ+&mx;LyM`4a4!F#gJnlUF!>#?#W18!}hR*B)N=%ZT^;ZO+uGn~Js(>4+(ma! zynyAF&Qs@>w3Hy6_2=xsuppBmmAO$j*0yz9cdHa-qg_49TznLU0_c!r%#u8F}d)$4|!a zv+OCqEG0!Gi*O8OK>DMHyqz=uTNn|Tl{2V@nT5j=L0HqW6GL+m6h!!B(vRPPTn&5- zein&TlGEQ@><2DrfA7Idp)+TUx&M$q*uXPGUp#E})2Pd0B|$o2AOf(oSq{n|65&?^ zG4k%ff(?;DIMUhPkdE?o2fu&T{znRfnY_n%gLq#5sO)o~`cG)WwcD=s9CmC$p~S#! zE;mN`87!Jef&+*ou@NIwb>8c(TxH`5(<`AJTdtZRPw^SzoB@{DlM!=-D$YS{nSH5j zKva;Y(NAR7sK7aP3}7Hd!B$3*7tts=e#!i6%rmbz%$+Q{)xg zENiUyDZ<$V{3@@Q$ub;`x=7RBzL!VsHat)P#A~5z7F_yM;OUGmT~>~4Uz6Bf+uCFR z9d(dr0oe#Jn_${oBQ$j;bLN6{LA&dI+h1|f)Zd1W{EGsq3ey{*!(M){{xRV~gO+BA zChVAuN6c%}nXE4kqZF1kqm?~O%5dAPC`yo7mS$Yfn<~X(VhW#Wi-}5<6uYeqb%;0SagA6iZ>Ie4|1q$wTU!j&IXK#?YL_B-oU#xJ}fT*MsU1H1|X>ZQt{pS2cJOKt^ z2U>Q`2m20RaL*QlHZp9gG`$_#ZeGUivUlFH|ET3!xTy8l^E0 zMM|f}KX}A4HMry4+qKteMSWj+?sq~@-u&uWul{4Pt~`Ka0xP0oj0E%&3FMj~`#}K^ zs}QpsmkiG#W52IWOJ$ERH3;360bda_A{VbEfik)rVSeL7p$BrHJ9x8o>^z_ed^kBz->i6+YGt7HZwh&nxx^zAHWO=J}JSJ zk;ABu%fn<9FV>jnsP8Y;Jur6FnaqLE(gG}yuQ|ezjQ=V zh3Wm!lY@4+=AJixF`!>mHHI@x!v|Xj5s47J0t_}{D?uena3L)x-i3;p5V?A<7rKZn zaDmDY8@{QoK|>sWDCfw^J%~|oiOs5uXsk>{KEL4GIDM55La)@-47T3Is<4li>=CY- z-3h8tD>|U1faB(wLq!!x4|pTFcOlUch>8~Fi(?27?_whlX9lh~RXGMI7)FB)J+9~G zkym}c)n-$$zxTsv;Qk=Au=eYz?_Q@EKm!a&Np2+xZA8-yEztyC{}hOo&?HcEs=2KV zcX0LMh$M<(5%JhTuk3f>*NcX1s%gE#^l|9kVTC0) zi4%d=NaW?U@D7c$gMob>R{eQ%Nc-f=XK(peOZ4Q2^iM*WBiCIyH*$}zs;TYjc`(g?nR8rvt6S3tiYGO@cjb5vztr@gC8seOpMo~^Gr>&km zuB+_irtf&#h)qwk|1{L`{`n&_ADmwhFtZ=2jTpL;5n#c9NE8S=pth6`G}BR`#G0*? z$q`VwfY0&BVxr&*@-2yE1Slw7iDYEH<+PtSB}S&gv^I3q^vb7pT62jm{bk&SYGg=v zrDBtsGh(XIKdHIVpF=q08j%_Foq}Oyc8+W+i3;PO+sP|cx<&N{9uH>3=&D`<-Miq6g|#}5DUw3UT5H%O`nA_z~P{aQA#d_J*bV(MZ!sT3Eo3yqLA z=CQnDK5g$4yhuZ+fUe_3Jnuq{NR)wx=f_Jt`t4}O+>3a9d9LRzf?zs-PqJTo%c!E) zVO{9?EuIK}=bdSoyubikQa!#WKLr4Am z?k@u%TQkV?d1%=qH%yv$-eHBcAC{W(`gnIFB8y#un6<&GgXLs_bV~q_c#5dXSqt+U zD>L?aX#CuRUOw>i{frWj4&9Wc(uypWnlr-Ud>)D%dwfsgk3)@8>h>rWu*Xw5Xk+`h zD(bQn&W7EoR60v!G4ehpWuyha2-&uN^vU1XPc~d#cy+avV71#VU^I3x+>UejIG<0e z6M_XLBjtwZUxe1IM>{(8TYKG!-M8#s$9g-Brlec8pGnDegS>1^Qdtq}b zj|+lFr0cCcHq;K;(La3C!&trw&9z@UYW)itgF==ICYLTr?B=QnX^F4{8pD|kpimrL z;2Tz-N_VFyZ2;@Zxje08myWySrI^XWQ^h%JP!1=d)Rz*4o+Ll#q-B#p>3g6Dic zFi$VI1Z%6RT+l~Ykpy1{rWs^0VX@~<@X%wCa%2r7ES+tfdNt+($7j!nmAj5Vg4g zMQzrQ8q5ZgoHW0WPglXB%_ZQXXA)Uly^xE=CDZhd>J8ZFqF^*5lbBK`}8IPPHdQ&WgJ>(7fS!Th6J}5=LBo*J7V&_!R^<eSIimKP50@k&!Kf z#p;#9?Wweq1p~XbGzMFXZY5csbl3kvYp?a)eUo=@gBjKqSJEkmq_hNUT)G+3pPX!) z9JqHtC;Z<-rnT2^KR?Yp5XY5V$J6+~G57>!*Ysp^RlNqz=}9Nx)aHy5OcPNq7H6s7 zgRanYnij^s_c??kMqUt63mDC-&6YBm|IuNiw zx!!{E7>Q`z9A}IkwOZQzpb(BD@KsL^R^-3e@BG$ioab0y?-Mw9ft6QGv%U zp!;*m!G(f^L*5rIM|Dt&GoY-T+l?D5Gk%7MOOUdnTW;@!h|QG@r=&Jf$=TCuT<4~R zuKnz;V|Ey|sj_KCOQj8=2i^bq=hCy5(fv%xXfz!bM#v;Es3kWhaFXIlCt^5Q%T?2g zn26vg#=@XA_*jiB9@8nNqIls!YM4djdidj}yt};ev){Mc@=xkim8J@G;D%lInb&kq zIj|vDNpK;H6aR&mmC#(mYL)-e5kh6nROqV3w;k}+ACoFdt*5GbhbJwkJF`+sB!A-D z-zNx>GgJBlR`es2VKgnLoe<)?t9?)pvN6^Tk@6woT>zpCmx@U!Sdd-l2!l#1!7EyG z-vUd!b9zJ{QGV9yJ(6P@pp6&#K(eGE4&i2`FN0^iXn*)*dDz|a?;kO26L-}LQze@E zpA{F(+HR5|Y_+g|K|d5aqo8X>%q`6Yqvr$DCRP>?9R{IWJGiyAQ+R{Sdy%-+=5O}u z4ARWVhKn2xNf*yMX_bLb?&1F}eWm=;kd5`mF3boFK`Vtz2d#|ED|t}s)jkEAKO2qv zoHMFFm8XkX7mIcIK;*@ad2d~@)-m|c8mlTy!_YqW-FVvmR|-ZeOB=S61?hvVsALt+ zQQZTxl^pJ&IRh6}VIs}m3c`X(R-H{k0FdPQWFQ&nuz&H#0oSfrHGJb2pIe}FZrkp~ z>mJJ*weZq`nHJj&m!l@EWc{)jl(3~zo79dAeSHk2WqO`en6^ag5B@5A=j-)Gl~@-@%7&$s0)3W4U3Yr#-#AH~5h!u#b@N}kkt-RepWuNLs9>wV zOu|RayOPhREO$T|jNR>_XKhRHfmdj=C68TE*~eEJiKc#9+xp1qD&M0*8bgqS2*nZGpJ;mpuE68$h)Lz=-Tho30 zas6ZTUi}^Iwn9g>C2u_{)nGJ8yGY%+6QKK^np)@rL`YK;oCJCkUP&B2;=5^|O&B%s z{@tTdZ1j(Z{rcny25mrB>MRyO;u5IJH;hJFWxH@!oyJH`PNp7fn#uygC+??)d5H z+g__2G?Il@j6-n=ov@jv(&hBhZ$n4FRw}bNMz>`v&Fko;KAHBMujmzKuodQ`@-AgW z+&MzqiCL?1(Cr2E{q{82Z!_3u4^~_sKU(|w`mIT~zlvUmeRa?8JV<-JOuFl)f80Al zdp+59$<{_K^u@`^XAW&{(+v-OuTPmEUac&Ou^uB!5L>`aP9Q2l90y*}VMS>w8Uj3D zX*7)Oh{oNt{q);oO~v4JMyi&a6BmmCUja)buheHWzCJM8)zpJ-Dcga$^1eDUT;)Vh zVZ_eAGsDp3oIoS`0wE>|1-??|ez>4&)Icu`DowkfIcF)O1D<;fVOu#;NU{LWn{0?X zsD4pyW=R$yc_e$AXOfiQ!J*t3pM+q;n9`A~Bc^?V8#o=+8^h_?1-)|Q-Qyl=Xf*iw zoCS*;L<#7@R~N@#*o|ItgLtyXbS2~DT2#@O#6%t^=lbE4vKu;d!Yz;Ov}8_U_*|^- z6hVNp*^&ZHiQE>^K-H{8vBoJW?E{SjKk^c?ZYm*r$}7q*Y7hH2yV+b{+ArD-Wmg@v zTid_C)cqdgF{>fi4F9w&867BFr`uHs&=!z2kxg40g3VZXDIm6YCc<(-p;4KhNS+S%H(EyDTd9z?Q6sCEeq)YfnLg3JiOPZI&RTr`R z$nYQNwOviaW}R{!L!0O`*cf!}o$e0Q*xk{nNVLjHIH(4=k^)=7c_daad%+k9la;j4 z^kP>ApX*5{Gg3lqNlDqFSEdSInOlTCcUwE|z^!*Q{onO4Caa7?FU}ei`F^ZuRB+(P z5(=Bp2Gi5i@@yW;LlX%a?+ZwGkP51{xtF$q}K;TC3W@t}AB=GPbcynYe4u~1jNfgFu zJq@Dazdw#zrc9Mb{#6PTd!XT=1Kzy;UppJL0xXMXmXuciRJYeV-CGW&b5FGU@ROhT zdF1g%I9WZ>f~#s5v-P5!5)*E@T_LjobCV{Eg#5E)oY1OQ(v^ZPpUJ{A5)Ygj+A36v zYL)T)w--7S-MoDMu10APMoq;sP0jdk7@f;W_3wptzw_G1zFane?mt~{>84AmSh`!F zPVq9wG1GW|hr~;P_(#7_eEg^P*Hmu*e|TlI(zFlqPx#-Kod*~TvN173wE%?03A0Ur zf#>D+fY=TtK3>ca#xi+2 zPX`B#_O(~9;Ky>uZZ&)p&Extt(gZZWb8Ox1(;hWkBJ|Ehu;-=1^5u_y31{00G?wTw z5;t;-Es;*o?#wh(9)d5GO%rN@58K7psg|C8!M$O-DCPokT@WZ(Pk*yN z34sI9^P?`AzoKJ_A?$D<^tm}_U1u@_Qk7xoi36(4-Ir3YQJ`9BIuQNjx9suGeft~S zSW6XQY!y+o#lIlZU-;R9XvfcXIrH&x9^(p(HtC5~!oixBp92h3eojW_Rp|7*9$)-_ zuT;ISW)+S)mhc_#dVw4{ZXr$I-9qw^XB=2z(CiOFmp}N{cOUz=uPAl#s+)>1tP8$X z5UPs0C2jA6M?Sew=!>F#TjGUc#}!Pzi+2QlFs$@npr0pyK|#N|H`0?8lb)&x>8YlG z^i*yzau9NyyNi@rxY!7M*~(dhEphU`@!clje$(ga&_Zhe3^I=Cn?gM5ZhEtg|}Zk`WsGoE;CwG_sXa z?JgGC`e;#`xPseQ?8~RJQnrV|O4!L{L=QkM7DUN-u8N%xd{RgXgk=#bH>5pv2`&_< zFTe_@Y7Ppvl%*nEF7Uj;`BuQV1`+nlq1N&0wXynplB6+w7>Xv~b%LL~&M(xX2=>c? z7g)^f1)g-K2ZmkBE%%0n0E+x8d-z~jFf!oTK5WH6w+Y>LpMEy=i70g5FNd^L{?jP1 zurs}Wfr$B8%s49p7X?#PYr;}OjFGw4=8d#Bgd-wldh&ICz0#y#GiPBLQ?C!dglb}V zeL`m>Luzp}XXN$eyq1Y=QI|Ts1zjFzIL&3JI4ew((8Y^B-SO;;4m5&`leh^B#zX0$ zS#~R|HeknyOup+~XU|9Hs_O3juesV#r6?|)x62`=6)PAggQEg;a9IbcGEr%Ap!xR< zIsWJuE-JagL+Lxy#fV!`#AGDVqyD>y@o~Xy!l};3a50Pxv2x*949Z4|BDJU7saQb7 z%zY~0CB{l$Z#&SG(WfQuZMRn#)a3i3^l~i1suz3s!dxU9zpK6UPMlZ^E8uHuq3`I(y(0^b)h}UD zZV*+P7XMGJ8vFR&PvY$>r<{Q=Z^Id6yYr=IXzt5ycaigs`R9`c9l?ptk&kYA!ty{r zRy&26p?qXUl9Ii7Z53Qk2f3ck0=F$1pEwvr&;#orSeS!v4?iay&5}UHlkQAq`M7?e z^V^iZ01PfPerVVG7mtsYg}KtTupH`{;7j@mvHH)iV~T!B7pna4ywNjXT5l?~J8>jI zP;6~lG^J+{JXB#s0(MR#**>N|R(6pLX-nXjI^?%wT8e|Ja23e#aI>-qWZrvpYl^|=9pv*45fnD%6|GG_8r<+79oDbi7f z-b;t0d*9gi;PX#d+0Vh+LaDQKpzIGs3>WWV3yf`NUkNFTE+OUYEg^M;Zd({AO+iZ^ zd)9qMO~4raU7}5{qYFaq9#SE9lDL}$z71lRm?$}IJq6kBI$#OcPd#iCUho&2oli1Q`OKf{cqz*xyDt5Lo$EN?CB&gN&=I<#wryytWO zJF9T&QtNCnTpVZ55o!q2MWv=?MNqj?VRH)Pvz#jVEGx<>&sl7!5w(k1od-oGtzcAP z?Ce0c{l5LS9B6Q79eQ!dozlY9R~aSRdbKRGXFD?q(N|YQ!cdZ=0Wk%$sZGIb#1yQm z&=jon-}~}6hX-5l8gEnqw?NpY5(p#AIHepka|2qoyQ2`kDg+u*qBrtqAlMoPutZvl$iwoIqe0qcv&7fk)? zog*ih1`1&BN6TVub5?adQf_jKIWX2@y9c%K8M!}X(of3Y8uMCy5G1|>$L`Ln1$L-bt0>HlEGqc?o_Gf)7i=|r|0ze ziq2!IiLQ>s?L=wEhG-_DAOS`uNWp>vf(u>Q`rOD<|L4pZRjhk^jApx*Zhua0VbI(I z=*(*uKmOX>^9!(M214l08f@krB&qY zS5y`_^y!%BFMq5ay~SUJ8VjKL?+pLy(Vg{Tv?YuS>IP18P`RaqL!n1%8aoqkqE?Ak z;4rD$3M9eCaJGX8s}LxUmAx6U{(viJY0Gdg#G%1Vs>EO;a zG_*F-`2fD1oj{a=sTt4SJK4VVpK0qpR1+q&pdI7Q2k*9BlTi_bGlP~D@w>D*+tPBB zTGC-tXJxh+6QWp(hW3xV6bb&F?u68y;=gX(W~Xowji2%}95YaA{Cme=@atZ>fHU8H zRg6u{90{f~q`P(UVY-$Ag?Sgy}rkb<}6RQgyJ zo_EC35J6BG)Own3Z1*(2H12-T(yJpbD- zadL4!jTgOGN2BXI&h$KW0jrVq?RvmiP^6XxX)mlHCZ*ch$Z1d-a}>UU3H5Nccv?#3 zpXaGfl38^~u%e2m4m}&v37byDeHM~$-tGF(YuEJyHbEMHF&Zh=*eF}W(VlA^DxjKi zfhM~0(|7vkBpgOBuiN+i!T+)uB>*k<+!7?FhZ2EwTz-L4ka18(7=5aU*3J>ymY;OI z!T8ckeuZT|`>MSZQPU(KH?9icCp2%X)THHFfNc5&7kcCK?VCU*iw#467;M>NC&OW= z4ZXE;N$V|+uXRaGU4kx8X)9kODb5RK7@!1JgQcutbQ1HUE?5_kla zMi@_ZpvYk1GLje-yCp?L(KNk;D4S26HRJ4`(1d(-B+FHVI^{vcnQPqGvCj=FVy<`7 zVC~kVY6it55OX)AQX2xW8^VT0CU)J1F24E8qt}f%ujJ6L1i9W+tXZCs&TA3bqv2GI zEQ*O9A5Mqm)X7vYB0+@;zp8DP`bGR3w0?F0-M+M|Q3LBh>kr%aF#COH^s{bJMF?47 z5uB%gCHqspNYe5}l9n$B0y?huvz_uVao73ZcEx{E2fA_STi4yO=4iTqp8f|p+z}-! zhof1BDCg|p3`^N+*q8=K*)BMg=UEgFRIDuu%;q~he&utMF2B4_JDN}bAW+BOUL*U5 zZy@`}O(5IZ2QDmW*xh-;!am}c6uNoP7yIA54a@9bRqEQF>dwN-o=vSeBM}&;`58gJ z$%|TfPp~|<`{DzKEKZh%BvQ3Ei+O4~3#x%cO^_k(`)GN!dct9t(Vb;+Eo zoU(+s1{@B-pv4xt(C{(Ou7CGK zPhnVXWD8v+Non(kvmJJtj|dBd^wM$Z%k^~^uHJG}Yh_mGidU7U9+a5W;y$&bPj+j- zgJ&*R~{{{IP6%Xj0}f4YeKk9 z%=S>sKCmb()N+hWw;E9prmmHL9R?U_72Je(N~&|MXz1b z^VKEa-BOs```hGWdiNvr6#5bBvnST?8jIfCW6|!@PP+PWp{>}DHyE{lsZuIQpcgeZ>)N!b62Oq#K@Vw-dd*0;2@bSuh<4l zd-yT|%+K-GGB3Q+>&qvgRMI*2t%r9tSV3BSf){MU@S+jh7HnQO_Knk+kIrfQ^?y&! zxX38MZp)FEvF{2g#AMQWnzP?XmX)S|DUQ>KIh`ju7ZReesU~)W06K)bfCK&J7i87> z`WLrciJYE`3$^0IiIElN>3#q8HPj6LUozT6x{kDT$`giAI- zY+YemfR-u;Jb!Wh_c~aG@#4i7m;`I7pp>MV2pZ%JOBuRaFf^3iFr1L=s?)}nkb82c zdT61%&|?x{2(Wg?mux0JCnzOF_;gX3O2oi9C-W#$856+*&{aeQJ%0*ay>$7zFh&D& zb~o0rbg=1U^u!l?uYCFksY0KNfEgqio-Ir}7JWODU1RB1GNZRlO3%{Xi&WR5XU@GQhbnFpdO{-gQcsa8u7+D|TZfvM(N@oXl zdOR7O`Dy3M-5k3c#A{Bf3dNbD9Wa^-0AuL7^S065vjzaia5DOB-*wH z1vyl6|M+#Q-rq22=l^FW{!>u!+mk)<3lAvlIV6KurG!(lNP3PWb1pg&wkRSat0g6z z@i^hcqfliQ)cm$fqfzpZvy=Ks&i>WnloYq3q@z+eD|JED56Aq$eEAI*iuzIH^(MXg&vN*=V}(45H|IpI%o=TLceG?;AcBe2stu&CN-2Io zz-edBa^yXU;;Q6@XqBU5xymXJpzR~gk=c21P^fLgJ!gR?cr5w zp~l1tQWpPaOA>e5C~s@cVa5C2LiBX&n$(;Z*`j!HsWSq@^3!dN98QY5!oS{3%qA&~ z5M@CAf`5zCnW!_95oHtsP`l61 z5;k+qD%f)zu9NN9rpu((f-*T zMG#YPpx%ab?+q<}y)Htg zS1ud7hx0arnW5JPXyGu%BOH+uQkGDSaAPWDW0s{QWG7A0Q9xv%P<4V+$D%Su7|_0l zwu{loAvNOI*OTR7%ZfX-ajn=22|PlmXn%hJXU47i5_D(%{ufU8;rw!Pf}8~}m_)fH z1*^F_GLLYoG%pkYH>METx5vxQJCt@uE$fJ=c&1)Fv%hP?55M{PE~|cI=PyAGpN020cW1feL}8A* z3cW#Pn$}>g$_uVHcm%<%mga#I;a_tqD(04caDZG3xrZ-(FuV*Mw&MwpUvbcjy^r1b zzE1jRAH#UB4#xQA6*-}|QC>S9VCf>@HPb^PVQL#yAtAM2>J_;Ry<$J0a_)i&^u!y> z36_arEEX%xlaK#|vZ}?&K9dheJ9L+|p9R)HDMb2WRoLHto#E+u{n;)5F8Ky^>T8fD zKerT3IeYxjnGao9a_PL$GpW(zR+0s&{9auMEYSp8%js)h7B894np4efZP2F48jnMzgU<)}g8iOY+ zW<;+Nc`}BdWl#BKDJddZ#O7m}bV7>ukhgQ@e-ewf;EIxe9JW|919*!#td+>43*b7W z;77i?fFIUk(@?WF)g0mJdd`PG+J*qWlwTPMPQ*SMobCkd43rhyWt=1-l8CVG=Xk=v zd-I*H?)iuQQt?-bFjSbXN3XQ*(lmKS-;#b-Sp;TgK+IxIKkZ0Xda3FL)LOaYw&y>> zg7NtU(Sa5Fb}?ya&f=BC07e`^m|(`Sxgtg(VFb$DM||IX{VVq!GwQFqgg2p@8?QTN zhi}YA2yqI2E9Om2EGY5Prwm1~<>=<54Ld zz})oLbGXuU3tB#5yWhvW#heycx_@I7(>R-?w4`EUn=ge61GG|+1CC2PACLd{$5G1w zD9q{to(0;kcvv4l;X;Zf(7&$KpMDD(7rHC$ZL!e(0{Dj&t0;&uPc1NQ98<+G5an%P zoDAlx)tF-k22n>@-8X)>V*8CCklc>$eDIieUYa|__`5JQEhuGj77s31G__23$6{)Y zL{@yK{+N|pA24{#M(}VeO?RQ0Z=Tos+*M75&9WFoD=#lfZh50e=t)M>35*oskfYIq z4U-3o#d#MRe9f@2ryTbz-7gDYRZozMYTOv6CpD!#HNLzw>VR;OdiA8T>2OWe8)K4Q zVOohQKmD-l%*A|v5a{3*-6$sga=MLTM7@#{PDZ5m&LS9N%{9n-Ks6gz=0aEsN!FFB5$juk32_f~Kt3Qq3}mgy$9(_8?{bd$TtBi=zMH&D%VqaQy% zCo>R=ZiZc|V#sxn+mj z&UTgj3oW*$bc)!~6W9+{P4J4*M0r2m5HkMow4dAz@mB?cYyQRJGHz4S}o zXRt!zFxtIUl)-~aO0`?8;Z!P}1xjp6!NUJdX?H>jD@B3{KOmL*yNIeVJ%-*`cEBE) zN0@ng-@=+|9e$V3lgz-k0qy}2)U>1KQQuEVJho`)pCPEJB}+e!9v-EPU--c^qwDPO zx@)0*GLEopvGJJB?n`9@qJrGP=0=|fH8i`uMBR=mxdn8b_Wob*f1!}|`ucl^FsS=D zoiZn9_I8KmHjKr!a4+!I_Hw)Ee$Pzw{-gwu# zlj$g5Vf2Fq76{-woIsgyPa++Tp@`Zrg|h|3Xs0ZTsVD}zVp%ziTaJlJR!;ZuU0cuo z?g;Qd8EDVm3e&&QxMOP)8?K(OyW}M=>6y28TF$wAv%n+Rk#d=lUhKf?c6&uxY!mSh z#$0*o8xat*opMTyl^0X5Mv1GBfA8dP9{OumyPVVCA>oNf`5P{DD@@O!)(b4Z$RkfJ zygc|K(3BjE$T*J4n|*|I6kQ|UmTkDk4Yy4KASLV zQ(1_;fTrBUZ^NC*GR*}JHWpt1k7^0*rCF7we~S%aZ1d<3#|Tf$)&jTDz>DIWa82Vo zJ8fGz@-LiDwGNCgqWH4oKD_q&9(ts2|6Lr`!rhvLi5TSnG#6?>52y-uK;5DxrG(o= z_4xv(YHbb~?Gu&AhnE(hvMwTUwJf(3$WNd_|349HISrHlyIpW5ik z;ck>xva%RXlCI6<@{ugI`D%2A8@Pt^(#)OeT3)%XqSS7*pK8IQ4bt=}!H>}VCp`_~ z(`V?awgW8ZQ(r`Fp@6;WuWfEoL?-4FO<=|A)y=n`I%^}84L?F3HSaay@>^mR+6biY z_Dbj70uieOAO-XyRz;-h479BBT6JAuC9ii_=WMFag$$sj7Mu16X&Kx%J8`m|dNP~! zDnn2@Cq+ZuWMn~w($J8Gj@xjYWK)+9_2nb8vGE@k%pk;Va}m9(Q@2}i?x8!`hip2> z){oJlxAwGUzP-NGC7IfUti%PGmGjQP+^S3^DAT{SmumpmUZ0?&4j)(VoH*ZL zR_fbKHJPMB2rl4=WBYGh+^7ymXP0UVn&jzt1p*4VmQnuvx(40+?Ikz(_rFOOarE6^ z)$xF4hsMop%AvZY{G3do`C)ISheSX@r-DisDmXbzG}srt6?mg#%k4&$AM&h4i_yWu zR-gQ8f5u{>7Lgx*@ID)Ufg_ICY=%0BR=%&Mi%qMVIf1deiUyWwm@Y4Zm^u1zQQZ&9G{k;k`A}Q6Co(*E?>h9j$523{h z)0b%Ol?NYn>@ZXa9IV%vNp%%hre!l&JkH1CSl%<|$O-8xNVG>26zmd@lS6_X4LQB> zt!*|6SNIiLHTvuY)xS?LNC~Jk?ahKH#;QBv?l=`O+oFguJ|2&}FnqktQF<@Rh* zXLsWp`ZY3Lv-ckHr^=GV(^Oo{D!**qlv6BFwe}IHvU1D7`BMFWp4nylw|??oWwZ`2 zL#5D?L&OxM{H+`_+Tyz;{0euhOH$Hut^mMq8WYnB(~oHF6GuFI#yiZ+>S=xBu1}@A zQ{X9U3rAv|FcO%NU_p~^+3@_hAHN=Zi z(Kw+qlBEt3ZD}jj*2T)|!8DH-1^%>>4Y&ocTklMWa_RTsCv^GNO9#&1-f|<&xj{e}m{V%%X zAH$i`$HJ#buH_yt>17X%Bm?e!kCWQ#l9Pxjyfm$NFFf{jnz30((%t6Iw$*HXBahN- zUpnP@e2wBK9I1JqTj=w@^vuNDYiS~P#lhtJT|=M$xt+Z361siz0i=1KmGt~=E}o9J zpDxtmw0@$mW7}ss$n)=LjrmLukJL^)^K`#|gYJFU=e_aS_69xnXEgQOYWwl)FDe;o zs!tQdBgNCr@!pcnaU2oGBSf#w7)Kfh6sd!tNZkb}(*AbSL8f2OKD)WbTrv6B!lf_h zqaqYdXL=B*O63&0gsjB@4m0T_3gcFvmk7>=+j$pzOOHjyC>Qw=n=S(Yy* zrm|ADrw`dQyOC^~-B>owZY-N-H;_%^c=iugRt_9ZV!u1;hK8eZp$_49Fe5zwHDL$L zu#X;tccreFRz%+kLz>NqoKhf~MouHAVrl{q69V-?4s|boVy_GDo3_=a%2NG;T!+6h z>X(VW!nF^bN1QWU4!yZbM_3_Qdn|+Hm(vl^$it`M*GC$zlwZo(oJ~xVMYWl3cS@JG z$TkL!u19MY?)ltv)5B#a{aG@TO?|~{dt6mRIvn#PIw5Lr>?FD4a=;nuQQ+EJJiW@3qrkM%eQLy`NZ`jk>i2(}ciXO{YHe?ub|UUd42 za*I*btXB{xBVa+Mg0nDmbA)67p-(zVa?eJRdwN>WSBFwI+aRN$asz66@P+_(_`SUfk21yI69BbpOK&-1tq`NURjN>|? z)6Q;DL~jl8PlRy@e4)s?#@V8X#1fc);FH=l(>5U(VUVd})(215{QS+LbLbIf{$46C z5|jiX_h8UzIn^l^S6f)g@<}O+Frm_mTB`%iZm-8J1d+_s3)a{g#AOj`CQKIT+a0>* z#Ru=-e|WcP^JDX4^W*>YM{WR}weZ^8PrUQsGs>QeV9d`Et^H~wA6~R5i41Y`goL!S zI^B{H8vL9YLI{Nn1E1KeEu5V=MS2@^qL<}J-FTo0#raM;9No`Az?y+ zz&G%oAo6%N5uKoN)(4~afBLe=?1foWx`bhwU^2!xrBk96AX9#oOB_EZ3*Vo=VaoFl z&e|wD!6CCg7`)AAF~kazvr&)ev72uUf@@D;M`A{KkBYe`aR z270>dZPk1n=WI(SVx%}Kbab}0K|PEuNYzNU2owzL)C1Rx`auOBI_t1|Us|{O+C7b$ zwX@YfU5be*==}jPDrH1_G?7k;eAgknI;zwr6FZgNh)2vi*fNO!e*N<$*P?-QE+s^W z2`BjowX+d;ol>gZ6HcX5fD40x!Khhh$m=~YBGwskM5}N}3F9MlUJD|81i^|TktV^0 zY<^h;6BxcLfPD}hHR%L|%7Tbl<~}cv&#ISFG2WXAqSU{S=#C$$3@d-k*=p9%tzN62 zd%~dx0kXxMvr40-2ng3XtK6AtPUV97fR-fxZ2j%&ojw}6=`bUsXI;E*YP;+FK;gVZ z*W+f0O{tv%&W4eF>J6p&x;)Q2+sTEc(hI6uMdj891ifY9`?j6+;-E7cUVpz)2c*S` zAZrqGbUJwoJ5zb!a_DX$YfAtlH;z^G&B#|DC-o0$4i9tS^9Rk}{*D#);rdki2fA&k zmvq}%OV66n`SQPlbpOUa=S7PX&x@^M6sE>j-VOC<@!qpKcHVlnK{_^H9drg|$V%AZI@GzYvlgybj=lquU_r3=lr&bSa zE-BD<(hI_NZ9|pmPNOm|JoMjPO`?nKr+wl;)y03kY&-1_KxS7WB7nB~fz2=05 zo+FE>9Ar^1z=-zaJBA%ZP5MN2DHmo>j}9&v;uJ{`dL)?#K_N;eQ-U-N)rmBPCQ>+s z(pIjTYZer3#)3;GWL)un?9GG!5~-Y$R*T%-OLoX!Tj9Dt-}L+VOaJldXoE-SOi#vc zx|nGA0sOC1Bgfu+8EeXuszMeD88Tpb1)8ALsT>)jR_J5^NeF^=m{LOxo0bAX#f++* zVuNZ12cWdO@onM6=eoXJTCv}l=f_1wF_O^v~+yuWoTW#pHZJbh~KvMu2Vj! z><;A@)4z)`J<3n}28xUk;19pEhOeh_mBpd_UelJHK7CugQ5MFD@RMS^*&{Tm@p|YD z5PdLx#w6GkO1p3<%M{-GuT|DrhuMv?4t9@^sgH{a!fv>wge;SK$e<7fupAP40`SQS z#YyNK&IjtE_LeWt%(dpx_gXtaENX}UiSq2v9LO#q^2gE2WY-gfkY}bG6xJ&yR{8)-VYs;2Z2Rc-5e`$`J zM+{070YVR$5@k1Z4kBVAMxL-1yE*N;2~YfW>w43OKbZ+sYO?Sb+TP#t-lIy-9}Qim zrKc-GgSclM%EJG2=A+k?jlR_g0n{7SFf{CE5H&BjQb3F%FnkBB>Iczi=WZ>BX)Y<$ zUA6J_KWZKdZ5c#%H}){b8i<0prjUZfhhQ*+INU_+y&-^uk8q~Iv^-YU%``BK)vf9_ zI!UC`7guT}Z_*c!w2?^$eh^k&aQ0NP4SpR>L0BRDd`D99CZvV{fy2TJ+tn?kyIB^H zgb0*!6(V?MU{Z-_8zF>B%O^9|$sA>2ka#HszHFTM+u+BtRNhyRbxPOd6S~>XeQz9m z!-Q|o3$1;K>`8WuLUZJ=na1rG$SYY+nbiPE!%Uw(KcoW(JGw5F#^k2Z0YHDT9wg~Z znF%9`3)W9RdhVT9!grJX$_wTY59sJBQ2Asa*fm);Y z*dfY(F$BjN=&qwUKYrTk3r&hB2&hO5NdPOrqZ%jmqn}+g^xB_>yf;9_;GH=-|N4u4 zwd>reVFo|^&hht89C@Qrs;_I7%Q9EM7gw14mUZ5m#4bt_tvYT?C#0^0hA(7KV>&=s z!Qe!9qAjj4t;KWG9>;J0@VX9q=718d+pr7O9N8p#-4<)*5<(c1c2G7{+QAAPN^Mh} z1Rr@2N95ER(^^=K6_3E8txTN-^1-?GN*s&JUQDh&J_^xW>&xv)qU%(Mi`Xg=Ls`PQJkz!+<{#= zoUBgVZ=LEWwSm7wSy@ju1rRJsi36##HW|+1{;{~?5nrY1n}#8b z6CK3vsu>OS9OMULaM%XpI7d015C-It z(!N?)6_!-V#Nfb58Zj%*4%?COdrxpx`zb2|sj`qAHx7vz)NolI=aXSlT1OX-Jqhwn z(4P&~nYo@5mQ?d+O}}%mb9WxrohOPXN-T}-wSjoJB_S0P2Mh4eJr``-{L%%vgH(1X zQZ;F!t%$2j6Jew{Ml~p5n2~D|C8%r-e3+Nbp3#$5k-zOfIumW!jIyF* zp+bKY%bT}u&h`T7x7eiC7BTJWnZHvW#ly63ib!E`Sh&cx_{b}RR zf&Nt4?a^Ow&UAB(Et2qx@d)88uVUS(+~m!l*|GLZCXY~QXa|;Pc>et3Umw@|$zsF&LfHMsU=l_g#I3P@AVJb>k*B@dJ_|3sOpm z;1@%H?zaPmgAk%_4l;e*EwE%GC?p|=2p+k8LD*yJP!Pc5hJPbk>oSQX_tfA6yb?^f z%Gld3MxmXsj{nK&s{77&jV`lLY}ymr*AQ@!@VF|B z$_>b=>S-88h5=30ZBa$UF@S1RG}LNkFi%Q~KrYmTMr&*9q_Nj*8RvVnboZfEPT*Jn zC~UmrRCUIlQd&eP!+^6J-)OoT(3(MW!KvQsjwS07UJz1?0b%trJm@23;T&blx)_3P zGSw&iHBhs5!eCb~HNdc6aGL!7?b>wtd!!ddf7I?jteflU&+qMJ+m5|=>-9(O=Ih^G z;Pof{y0>PnF6WcapKoxT%=C96KXBzmp}pQY!pK5Xol_OB8b~DY98$EEb|OFa(o+Vm z-gKVss|qMv@@opON!7JB3Ro6KwUekQ4gvOZ02b;s*l+@I#bBC0D+-bpVK8v~JZ11J z@9d-vV0N1L7+r=6kxA7h@?Bj`iXeG}CMH$heCN}P#zoV^m6N;_mAdcN2fCbx6gFIO z{MN+gzE6E>$+aU4Am2FV!+H?&A&TK4PGk#rndgcZ-g<%`{GZ6A_t6BC0dGrAy6J@} zHyfpiyAo*14Gy>Vgf6-jnK~8@(N}b-)?&#~YdIF|5|TT_Z@%$^op(%MUGPh^s~}kN zc{s<0qdp@Ptc6AcgapL65Xq~Uf~M>|32ftgjqqOEq6b6DVMeBT2DImZF+2F)A&Lws zT-RBQ&mr=`pAvIMtX^HBkCdJVsE`jG99%}pAao2@L(wbq%rRt!3HWOLuO_Ke*Qy>yx{K{$4b;@AK;wxjXjc@kIB zcNfMPZPIEkzx|a}lbY!{24PQQ4mV6IDF9hcghikT_Qr%%n~X1?>u476r;!=jfd;L03l?zMf>2y2$@h% z$|mFazu)qc8&A>2FVw#uzj@$>x1FNw4c2&kHFbptwp08E-h=%oR;U4+ znFZo;3FCX6BqpOmS|W);@T>+Q9!W?wIu2#V_vGP^-@oj^Qk6>VhvXhuYQA=unDzee zmFN6Erg{A9Hy>LiEIQRx*bJK{)=-`NYV70}GQT2ZV|EOe%oOFY;%Q!eJN?`^e@UUl zMeE3nGVzZ5y>T9Y$nJt7|vMj_x(a!9@RYn3;6T`qNv+2((hD2c= zKlm*Ams{&p97bb7enA$0noc#djKFUbae)(&ZGg>QL_t$q$33#>#i>_q+J8`QOQ@BY zPUlZqdG`N0Zb`Aw< z%>F<%HoPQ)ae#+V=(IP;sVlf5fS9GGGx-Ms;bXu2_($D4Sx`|Z3ev&9(!gbC$92bp zs8YOA7W^@#b5@%mb*dz%HvRS7l$s-_8y&VZvzNNlmS$=pRszbDTe{AaGX|OR{Wl(d z6~tetBg2iO@VcGM>_-~trnL{r-@s?`)@MK4vE`b%rb2-U1;}7iXpc8oTVLbBP1zMk z?yI*>x@Om8q|f3Xcy3ByzuMyrtq2G5d%TixYMT%bBUlRsvm@!<1pShYB+;`{-r4pc zP62d&Xz)Ac^V^39C|CQTh8Ucn&VZtr+ zd4c)5_9%iB7)ectO`Te;83G&Ejq_!Qw~o0JJdcChn- ze_Wg!Ng>ZRXUI#Bo&^njNCv_UAlVS!iGX+ZJV}cz0>wa09cRO*GeMSu45p`DHaM2n z4tds#N47920~R{8YyEwm%b#*9Gd@~zvVrH(jUSgi69%#|(ba;Q{bMqfa*P$ybu%%2 z2S0p3cMoF$f7j95hKlX`bp-`6i*lg4wqZ`=>{*4|%M17qs$M_nY424!kU^0Y-s-rF z+oCqD8Icoa#V4ePaf=x%IkLWr8jfkKV`@BViK(w9$W$=8G)~Vs7Z&o{w$2!I%kiHX z1!lX83{njNG(MrgAWGHsfU-I*C(GF3X+a^}GetTAKtg}Pd@92+RGA-XzGniBXdpm1 znqYusxbqfW|9syy(GhB!yh(ho1fdQ6f$YD6bjSIn+U@S!coF|pxNpr(H#He`Q%hJ9 zfI{#bbqUo5fbYC30+Ix(LRq}g1W;%nQSLx(LEF3d6y%5t6&}&MfPejyb*9=yCly#s zM48pvF$HU1rHx&i3!AxlBr{O!8y6Q9Kf7GW&!hi&Wa{cCbhBJffYO;G+B}KQghYg* zwuS&wxN3!p&|#85=9=O-VO^%X9u0wvD!WGjrkY*xNPRq+&P(GD5L=81!H8Y!*4L`n zHlOs*{<~WITxzkJ-K$>N?ErfwowJAYr z5kQ2%U&*up;Ty+>_ubH4b+FXxr?w0m5{ zzdhRLnfU%>qjD~Drs$la;XFjlVsb!lNqQQWfnCa*z_{O6uKu@Chsp6Ki?7p7z}mQI;S{ z(R#H5-}gRG;{6`-GJirS3_gDd6Phd?fz`#=lfs;VB)wbz2_B_iy5FePF6S?J_fXd@ zhgBH4W4RF|OjNByx-&M&Lj#qYs<24}<8%)0rj_p>HgNuZgZk`pfM^N-kmty0ukvic zDR6D1i*_fXQ3Mc{!xH5rvjxfCpq(T|Zv7Lb$gkkvz1aS;!}0~$)6sp77|609Pg7kv zop+|SAc{f7!9mXz@5}+g(N?-E1IINpxGG|j!Zjc=H;whKk_T~lErM)%G zW~W{H=+Z9QE_Wq=+5s!8)?M#4nn82Q)eR(bCb_IhcXb5;( z@7=Gzb=Qo{rM#nd=gFyQ=N0V_VgPBiAT_6-x`7u++jQr`het@KC<+lykAkZZycjkUm#X=~Ff^q|o$2z1c2Q|Tgz`Rw_r?SNw?;{FRI6x$Fv)Juu5IsbCUly>q|r z`t!~Fzy%l8ygB-^+>TR~=1h%(xTX@5#t2nXhMCdy#K=K8DTKu)F`R;vTPMm4*;W(#ipGVLR^r4qR$l zy5rF-LY3Yc%5LRf|Mkx67S2$5lKzJRaGAhp+67fz=(?7;!_4XZHOl1*BotVqOiDM= zB<7(INVh0y&s%x=_@{oeIvqyg;9S1^GMu`F6gf%GT^-PbA__O@Zs(6&@%oB>Vug_@ zNm(iyYM?DH$TExpIU0Z@Vk#qbpAGqPvWomMXO0x{L+;?;e}3QV+uu_4`z|T!@o8gE zcpDc-WDq*~Y&eb1LZBVUDBMhlP6SEiuOlukO{FjAjzhXGSwUH7g+H#(ciSH;{iEH~ z?5u(oWqcQZ;Ylx~?*HPhg2P97bCj$pft50DN)<&tkir>Ag-?8CVt!^#u9Ph)C6WN< z4Q2~sa|?9tSc*IRN6WW|Ep9E@=f4CHtTxVCeo}PBAf@H19u{X2$Ehd^7c#k6b0}Oa zx;e$kF%Ia@V3VSXb-_t<${`1TJ+QQgZdZxvZvLUNm7!4dJ_B#H2wHKoYKRHY5rCz? zzDS3!5k&kPArvx0W4Ig*4iv5{jEE9WPAD*UPtJu+Q6}pT=W6#NXlek;F!wq|d8ug~ zKl)qo=Bs8sNcQ|x>`l?toFgbfo z735Y5M-del+3QRv>u~;&5MurPv*n)sU!OB{e{w7FqwxA`vYz?}zjOqjY6|Vt{to@` zIac?%TaMCh&m-H<8W26NTX(rBa7}J!IR;UJQ@i z!(S3k?Q`vo|LO@M2qO}v)d=ks4K+FxP&ebSs`sr2_qh}CW(fj)1(bO2nXzw656o%L z2jeDNS4_0Su3qvR0HGpiob%`$!!n&eGLZdMNg&4Xme<1~fGX_YU;?S?8^5jSzcGr}Zxu2x7j zJ$8TF&_hgnmR;!=_wjc*su!={_`E@Q_P>OQs4L3J@V%eBo$UxbLpuI|Zo``E`E6IN zIApKwv(z%U65YI_7M5BVzc*7>ip)Jo)&}QHpC5(sgm^>*@i_Ddu_!iCGJ+kQNlm(Y z^3zwGajj|Mo{pLKQLN|vr!IfvfHUV8oMl%-pwn<6G{kNiAQ|Y)8PNPjA4IW{L>$fe zWJe@R;4aI?Zae&`S8n`mKso{Sx@(c<4&3803GI)^j48^fSl67i3b@uTFyEc z$yH+tn7s|w2+mhOAI)S*T6QSrnbw$)7ClWkf+sXff1Gk`*E>ohVOA z#HkQ@YMDdF*{NrFWWhf_p zvFb5ND-M7Y!BFkKU4O|RIi%#W)V%X=pW=`e-bKhzp~!BI?UG|G%J3S} zAXzjgQ8pnaD)oM*oT43RF#v*Obi>>PQ+z$4-{-~ z8Tg8-G>O{iIY2%$2J$5(A>uO>HnEHHEOeuA450Ou7nnRfj3MZB9t=9f1zw&U`^jrN z-XGfgAjhZpvrc|v@uYRP7K44z()BvZ6K(N`jOqx)cymFDCA|P;a1{e(c#5yP`hY82 z-kNF<2&d`lHd!ebm<}P_rU(%=ftxAPzGnH4SN9I+_Gx~>mG@t><@e`zA!vp|7}Y?L z#13B^wuvl;$-yrZs@>6$Cb&TamD-BJxSRRwqqX*e5qBEIt+?dS2SRxD}^~-^Fc~nJ%yBb(m^!D-w#>JjEVZJh_S?n ztH`Q%xr(fh=~;fm7xkab32xU#%?tKP5yYwRs4Z+*RAhmxm-Q68(0?r#;zEYW8OoyFU3f6%MgFYP9gZER7{zXg zny`dI36#0Q%_M{xdv%!-kDndh9iG2M;Gd@#=%M>m$f|;{+{8@Z|}`y(zzzcD7X?+VQk2`>0HQ7o)@Q78J4iAR_k8f*-WvSEXa1r8 zc@^Z1d@yi4Th-sGMzKqD_eH_X5b5DI9PQG%sM+!~F=p&*Pu{TPo+E}+dvYOv%EcIy zv5EJ8_0NA_vh-3l>sEw$+yHBP(qkuO5oI5Y-3u}VVyJN#0@JR_iDhT$Fk+&P9HB-y z4#DH7bZLX!cGQ%`ttC3OJ<|;i?Rp#G+L`gVc3Zz(e2NxTxt*MMXE+ya*^_1a*!-i) zCVnB`9GUk1`X2ast%3YpSekalELgif`gYCpQI0({Z!b`(zrr_mcprY`$%KJ1L|ZbL z+=L`S@o6&J#uzu9&((G2`G6X|&pTg1>%Z{e=Ii{xDGMteKSUMtJgd80k*epGl`Ep? z+PF|J3)#Dh%qgkmM_f}f1)>mbkx3w7-6wx51-B_pLMi=gNRk5Ql%8E|J`v`WRDkoO zPQ11el-R;ZP5ie92R>FaZv!)vlD=@<+A1gFs<{QQt`QOj zlLS;zOK5C;mAKj{l@-sV>>L+|QP^lk9W0;sWS?fHQ5F5fuliCeE|4i}tUP~Aee*XI>G+L9TO9}x*wITo7=&ZBW&fHGF4Xu$ zoOhTaQA_`tH&#~Gb*!u`4cS6}v2LWc+I1~l8Al`reF4_nRUO>yg}86jPa9~^reE`w zXFN9L;v=ez%m7F|Tp`4RgqNlq#OEU3H;Dd4a{+w(P&Waqb_9R~i?zs3U!?EK%*#>Al=mt~_gfPbCEi{ATj^)_GuWi;^zT-oBrI3`aW&r9cS&FHO+B4 zc`_TjwR^l0bb8{EX|+iqULBZLTOCO2qwA`7%J3=a!xV}8;O54YCx-So)hwf0gRbVs zE$I)#r;of+K~j*|8k|$IW2A_PAGti~%Ofv8Pc|H%XeP558udqF#D^1ON#Jd!vE8i zN^qJi<8B_Y{@+_aX&F4~zc?hM<*WRTpD4WY&+k7qoAkyFZI6Mu8K${Jq%$jZLXskx z_Mds4^MHKFo2f4uoL1;hIqATIf8P6p8;vY)YnuBK74Twb=?9c8gNn6yvZ)ibC{yirAJYzgzcbHzZ_wql3{i}I=S3QUpKlQRakq|3(o=z3>PK|7VD30|A5 zC4Ne_4tQ=lhP852Kkm0xIO@uNe0qQ-iD+}ygKodN{bNcSkByS;kz{m_ z#kAL*ULczGjK12`*A9}AS*~kKb^m+hMlRG{N8R3Uu68|4cb=Mu*GCQS>y!WU`nCQ# z#~a+2+Aha;bN?fCmg+%0oNd(0=-W+i2&>0SODEr&ac4KbU*AsY z=QlV1w!Xde?KVoc&V3d8fFq9wr%b(}F4rtmC6KD!=&e%iE7}|bovx-PA*{f}o|K4s zqltE$aIoQXN%p~7?N&pE$i;m2ju%gx5UMTdO`e0kny1$nob}fDhQ{wzSWhzFoL~{< z25+^?x!wf;8zIm?dtz+Fuvl$!d1m#W`A>&+|jJObRI5HBf3iLysZB$J3I zAa&jG=UtT;_d$R45r{eq`@-=6{jviV;5YO|_hQX?my_-DpT+az;`{NMt6Y@#XZ8b4 zbNF9>{&a}}6u=rNpA{&>_BB#(R;%Nv^j~Zun!ieWt*v#OgL!%QuBG|=15@DYGmTdxNfM2$+AuTVGy(bA`dP$}}fs1CfZD z9?ASJi^s>{VV`gXq40vu<3qUeLLcl9uy73baOv`Y7-gcu(K3)sMsb=4>jn0p=s8DC z5VMWAXfqck;v|qQK_umsE}9Dy=m(8?glCG8h!DrS%qiFiTttw=XonU31;G!HI=8CM z%z2W*syd1*v`BFX*z6sesJdti+1}VK4@q9ee2=R_wV(~A1uE+l%FYTv3b%M+$!`QQ zw#j)&NhmI_GA>jbh94m(GMZCK(ln!ri;ktGx7CZY7Vd^`rqa_&7YtNEpOagT&@_+= zT)3K;Gso%}1JtSJo%68@%*@m9dbX~iPx&0L|M`RV$O{dPn(d1k8}s(o=fA|i>*ff) ze*2f&->F{p$rJGL8@|I&(Dn&lA37WFzk?_>9Mf0(UW{9+{l41>3V%9Dmm~;O=*SfQ z-gTtsTu#pWSl4caF6!_8$6ng?c5)y3WRD;}>Y*bz{-~@5%QadpdrRl=ui7{g{Q% ze~ql|Wg`uy_+j9KWhcz{q$V382gqcXg&u&eKr!KhQqx{w)jpi~+MU;$@?$NzhJ|Ru!bWM70nL2~vjM zr2s9B_v;_9{Sn%CW5CSD&5oHR2j%7wh*$5+1S(OmjfT(O%q^XL(#Q+4RAKz3+O^44 z*tWV5A-42!{dVp%83%q?^uTN9fBvwFTq*6=J?^&J$Q6mOZJ{*w&=Q7)-VREo`aZvyT??_OE@1XYv^H!)JWw~m>xY1o734^+8Mwh|D> zArmYbM8QzZ4CywK&oZnlk<@w#oF?rwE^CGs#4eUL zcSS?^VBI|T{X=&40+e|o*ml!`(a*owW*pSUuS1$Zg*J;a($Q&yP&ULi0y!D;^>sts zL@+-HW8eGM)zWvY>7!21mQSwKJlILG!_fA=lMgP@cfpc8-HvU;=Mx!7=rwe|Uw-YUzKn~$A6H;!I0SrV zjlB8z=}+dydSO9QhJXtyLQxhDzA0RcSok@T$HBHJ?BFm7(m_y?QH2FD!6o}pZKj%6 z$2?vz)S+kbVp?07`Y0^>;t(enM*BfbHw2lag4xtuwRIdU5P*Ymvjn2J3;-gRL&(qA zb(*uoK*--J+;+Up2q<8#6XJ40Vv|Ccq6lCRrTt?h4UCVYfvboV@D%XHqmj6!q+f~l zdwWK&mer4N3@jmaL$a1qZ9Ql9%S{Y8$h20a*Yh;~Dw>kfUu=umq+ls7=;#s$K+m=m z!gNf=Y-}`FxNCek=-6w_5x)!VId;BQyjc~FR0-bmNI2WF6eDFW1){zxBmjnWnGxEn zTV3Y1hl2ZM>oPxiCoWRkhrG5bgMfamDj0qaaN1}_7_VJc+ysY{#P(v+B0d9thr>a1 z&bMQhKXX`aZ`ShB!Z;I5Yrr83;=^$gpm1J?slfYozbk9RqjCWvxSbh45|pDYX-qj0 z!yrir7$MN(1q+?vrh?b|uwOlY_A-Naws$BYX?2Q&0TQ8FP$V%d7fuIhk=o*@_&&(m z3+~WU!Rzvr2d$3LU8Iccq8#wD_2tDBl^hMm`+nGY;cC%T@Mu%8YEhnKt55a_AOfE} zD=I>9*qCWx)wi?1drCeiAJE4_t3V%%H;`2g0o9NS{|Ynsuza0qgsZ|RN0lGyZ*7wW zFOU_t8^vX?`YOChtSFK0S37s^JoD_6xKj0pY@~B->wJa{*!=F2s%e|vR?janQDFLa z(^9{DN#Z)Dr8(h6ZS56i%_d|J8lex0mIWn!GFCRsSG@K4gEOQnX8t;GL@)UfKR<&d z7Y;etkJjrS!)BoWdt8vC?29o_?l186z3&AK9vTg{J-lY0$ZbX$I@+PhE6x_m|hgrzxxKVA2i0pLe``LBS2D z_k~+C*=tnaSu~RaO_&@|Q9GuZc+>ldEyRQwVgVblY#rEmfXwU|9I7*V{ zSt$-8KtNl(2`Q|~5zI^|5X+0qdKi}1q{}TTEBIqay*lOcub1@OUCK2jrenZ2Tfh48 z@iC_xWktFpwzCms+}55@*tGzLKzY9s0y{*`;Y^FP7}eJpKNuId*S+D@h}=nWLYvO63XHqIdj@>Tmzpu*;GN=)!Mu#nX!l5B736(I5@n*IyDM&$~8A}S`2otfv)X(5Utc7d+n41`C zJy9N@29(zP9mD2U|9xv?sJ`1L%Z?mZe`!m0)3cSFMJL|AJr*|JowYZc-ObiFBXCo;%XxzD?#F}8xC=R!Z zcz%BH@$EOv@IG;d5o(b(SGzqzI1v#d0a<8^Bmz<*Bp{%iDvyClZb$*G4kVMsC6Lw2 zS{Z_wqRkccJ`cL2Ix1I#B=g?R+uk3)HC6gYAyZ3CK`{E9uP1yI8MK@9m?>I@HTD80 z5{AP(1czFFLmj;#z?2D~l(eF3aP1d4oc8QC?SxsR2};eJD41+f9Wd zBPqK}j?I=}d=7JE)^+9pAF(O#qRR3D9|@aeUfiQK4S1v|TnL39 zdF{Os3ydYBVpvpa+M-cJwJ#T9ZDj@e%8IB5o&%x_FFN+(e{|?#JH=7dCz60>$xgnnGL z{Nol|_jXY>vkj~QX2s?>6z0n0mZOC}p_72L+J4{u?e`mbEQU4?9?R2(qtPagZHY)6 z3Cz)j2*2TaYl>Fcvk;o566Q&0Dk%zKp-Cl<`ba0;uGDQ(H9;H#`fA7v{zUnROy zkaZ!yrD(V1BryEg{jOLas4y^+DF;Rg;gKGXvy{0Vu2KHehcM$@86!yvNt+g};}~YX z=aSp`T`21)HJuC^mi_hb&%4&>z-z{DP`Ri;3w9d*vFaw?GKgk7u@|N}m2#rH^!SKA zD7Ht^>Tg!v0R!B4jA2?gOq==4zsJ5ibkJ@#Y)=NSZ~v@gP~ew>ORmzqbqQ#$c6bDY zhlvUxMf69CQj^v3uj(4vV$J(4aA!gm6+~6`=hc}#1>AD+>9;?+@q%2GcV5D-eDs@n zXgUq)Hyt2zni{TSV8G6zjvi%csW;5tC49GI*?phhG)M(q?w7>rI*0eo}N+$ID9(gb)`q$MAXErRBL&A1? z1`lNMQ5Ho45Vb0}Tf}IDN!?Jo>74A*Xd0Pp?k7*P!K&S_Zwdg`eOtE5G8Y&@{T*uh z6NeX(Xptl|L0UGZHjkfZ)t;Z%%Y)(8=2_G4g_H_tZAc;+);mBI1JB_kHhOD4U>XR8y^E`HH=VYd5JOTpPcKYz8{kFvm_K$RT zMNm@d+w#*C2C!fpbTf=*!BYY{d{;JgyU1rc?%5%D__}wVlTIj{$fPF-`4`JdGoyX z!Gl*Cdu6>>q=tB~+_bOKMLW2#-UrhZtWg$&iZvLwhfZ0+K3S=QQKEkW ztU}p1PJ}h!azawLC}a!|1dO|FG#7PJhEXTsL@bGhJKr2UBM_EOl@g6&g_v05b`iE4 z$TI0kKy?VHJO$4~s#6q4W&oSi7Wa5rAKaW%!}IZk0gI?w(XCb_qMJg!0He1o znr_BqIOt&4@uBZcTPLC1hZcaCsXB_Q`QrBG<zaP*;3<^=~AVM)-{6R)TFqq>%F)Asd z&cj4j-y`4MQ&wJU?ti;!&$4IPv+P;+EPr$mbD##t5d}xC5FcN??J1*dmm>(;)8ZjP z0l-2#Bb5=7G9)#XMlx6=jh;u5gwcd3t;opqdAx!WZb8L-b`JZinNJ{ZL4)p3Wny~_ zq~RDe=C@P&_LK@c7xxka$wr^+nErn+xUxr=h*f1*bM!~^$kxz3Rd{ryb0R1q0tH92 z)1^#Ky0qu^Ndh$fH1gG35A*3@hg?pHhJ_s0BrF9!cK`G3O%InFC0cWUjg-AvP%7JE$vWuUV4pW%?NZ_C znGP_~lUn{rg_xU(Q#pe$unK;C8c;{$eI(YToZlVVveJCKL1_CHE4g?hH<&QGd#XKEsXg{L%dDryZf(BNh$~Z1A zv~wWMXqAEEL#c#_8_~WvRP_%g7N|;n`|wwfPUumL>P)c8_vE0;hbO77h^}3-M2Iq@ zO)Z3Uf4K9@AOB+hs?X3rP4ez6F#T)WlMl2mF_`ygmy5E&kd-XA#e@i^?=)uaq>WfP zVb&S5a;7U4Aj;j!;bFk+3kF~=L1jxedxRv)*VlD2!B8j=fa!WkTGkKhkN5ATSL`e> zy=+=CH!_!znW+Qp06SEN6=eF$FYdb~hzdv2Y7P+hUq*CV;aZGwz=l>kV1; zv%tBB%zXXHtJMJ5`=$CRIp|~E4L%k{spvBFveqEQD{-C>fdCOaT5hDfr(JEWQqz1ey8SKc%7F`1v!F!ZTJ}bVO-~;v z%EFE+9y~Z@i#2!57vBM-5yWY$L-%Q?{l0FuHVOg&i6x(GlGxkMRru#7npDG zCZq}`Ksc={m_V9|rj?0}+?Eym4}XYTjsLK;z<2RH@Z236+Aq0Los`$-8ultDAoinZ z3reR$rW;qa7;lr^R3O?WBf_9OOdwh(8QCN@^^9P;zMA3J*x2VfKQH~WYCRguF)aWC zuZW&~`8{H;BdV=2&f(1ODqc>h;03HP(isaSqUaUG*}ZJO{4Gj}?Vt&K-~V)_+5#}@ zf|8p@zH)&<&Yze#a3IwhJOqIV&9^j1F2m52B6X(q5|M0M3y@oUl|fvDo-M~`&srNddzdTx*A>OHnLTHX&6GAI7EdmP{zwphbgD*B} z^ls~jj5KEGvMh}mTx*oBbZ2R<8w4zjSX45M6^iE-Q-^&a*nIqfdOJoZ}bg3UHiM;Z06r#5r`=K<1$n z>UJ^^1&0u0R;Kr=L<}{%(E|vuRamvVHb=GFOh{e7Rf8S-ch_w$23v=1m%bmX52g1a ze|%0_-i*`oP*KxY0KL2e`M&8;89dD3eo@9&`x9XXbD)HyzCe|e=}?3?DonA7l__LpVL&{)D*yEk7@YssE&(g2zjXb_+g>Bv1yJJ^&h?R3JP8p! zah7^nV1jvMlys#+WiFRmpO3gVn-bn=0w`j1k`a8~_(&M%Zv7DL`8K z0G2|>m~^x^Ls|de;>~lQ=KP31Sp$@sE(i0+{a@*T2Nrj29i)o|;UtO;1E0Uu0#kYr zi=;R?Uqpy2qSA=~v#N2BLdF3Mc~CaQLyOAjyR&uQv`K!sc8w=e#)6Y zET4Xi(Ql$WJ}j}@nG~RZMoP;4{)TE5(%P!^af;rZ`rmix$FKfyzs{pE9;Xp;H@t&sp4qOT z7ynP%-=HqjS=S;7AW|QfL6eYuK4Ke$F^qKcW*vl&w|Kzr#L=T)95DaBL49`7Vkt3Q z1vajH^zf(eJUzEFunI+U9+Kt&Z5;^W$~ZZWq&|EB9bSS}Y?!%`j1=~m+?)jAxpq$? zj0#KOIAG)Q*HPpAK76e_+{PDv>bGqdaHl1vrC|G4uYMN3wY@PNV_3|td zmy|UW%zk6(H*1Cr%%M|lT8ynj*NfUYZ_&R-l;j^CmxIkOojGpPabvnLpdF&@^|@=S z1BIK-%fZ`s9VNZ~%N%mzs%{4W)dipCRyrU)N8RqyI!oa&r9DtlOFpK)<+-LB^z(Js zw$R~36srtIkUs-wqJWf$CE!<}5f;&(!QHUiqAl7CzYTv)8;Fux7R)2g3PdkBljUR` zoK8SH;}@&v&gA^9W`c@U3zOmAJRN@~?D){ZMFBe0r$Xa-2pPB&W+XN*DcW>ZZc1OI6y;O`di{b7gdg}0REL;$hL zfF-rk5E-4+7Q@g`DApy3N=r;o#4sA-pnMokzF0{Rn9Iw_r>eNa^ASJ*6|UO|)o9wm zfa>X5Uf!zgsyf2ApwZ6UGCW>$*ee5ld(xPDmfiTKuLKvYx@rVB3>Zu;PL~UdxyWT1 z_5urfvD70ZWm27pz@Cljcd9f4GHc_ox$bfy24-}Q{zX5x)aVCJ2~L{vr9-7kx?Ue9 zK_u=8$%fVf6AEQb!8rS+*Ro}7S59t!`?-nzRFo2l0Gi(X5dWID$oiuXOA2aC_49}7 zPS19?F!;)#9RBXh%dY~fUt8Jm<$!DQLF6(Hc_vR)ybICFux8E&HF$K| zYdLX#cgB^6g@MqBhonc8nRB%%2^87)&`j!A-|6eKL3p?IE$nVrflKUDFZt={v&r@| z+4rh*DAHP70GG&7Wn48zV3DwbFDsr***PwbFi~06%@8<=pK6MAaCOk#WEI-nl#tqi z6wxqDx9|0@n{4&ow2;JJ720)o%Y=&jpDXn9yOKiG3Q61sMmcXdqH@&*1xJmGh*RpQ zR4o>lDm7UlnHMc?9G^z|D3g<$Z<(cK8lh#+?vZQ_sQqW;qa=Xh6|B^RX(SePM z^b}dau|AIV7aQ2{7ee^?{EQld#~Yn;Tff%E`6`r3kNfR{b)K>ZT13nzmf@Q34MI;u zg+jgl=+h41#vPB;L_~^hLpz*YK&*>vy+QUF%CQiUAjs&Wl09c*2 zx59`$CJMe2JTv2lQJ1{nH-gF%bQx`igI8^RT!b_(Ac`4uXbO?ahFfY0@GK$W+`Hyo z_~Q1H!MnRbF}1|>cQF0)As=5g)vn9ErXz)^CYh#!0*<7K@n(pOV>@LB@lIvlq8_akpYhmoh!E67A@nDfFTZ)z*fq;X6kpV{9vnSy%1@wHHFsC_ zrn##Z4sf|2Or76{9I%9KQLo7=}?oR)IVk&7C-CQqva0g=2Ctdb|&Uz@fj*^gTG$VE8R-2t+}g zf&|yKk}DGNiz&eu&(2NgQ?%EUp{TxZ{_lJce75?=CCQ81cZ07~*AjP_xjclfxX9QK z4LLE9?ncF_xjY0u4D~zw{Bu|44ChFDj0-1{og7T8a)9!|;sTW@rHqB32AIOnmTICX zj>|$|$`WniI8o+uCu<*)^R;ta3j5};hR*|4PO>C_DWvopKv_N;$05WfF1}>Tx$rT2 z#U7e8xwN;jvn8g7!N88r=O>@}0NK+~q8t01X=Q?>5GI^PloOgx=^X%fTJ2=snxA_c zfen*F7&EVe+@) zIQmLVPl8b|xjsGR)iEVnFmLy)jsZbPwj`ng{DHAt;n!|8lh8=1XQQwLg=V%b1X=EZ zDCLlpI?$j5sZZxZ)f1G&gJ}HTa=yC}qjEtpsuQ0d`@%34s;p+YUwB4~DQ+X%XOATT zuYI*^pYFW2X9gE($^Qd9FpxXqhasmMgvz>#WFW+0xk%6?y`WZA7LE$AAo1H(SKnzK z_xNsOtuHk_117IK`s$OP_`Jx*p81dVsN z8}KG3mNVmzB3uMR==>ZQ4+xF=D6FYtJ7dhscPk%<+_$^R5AY1AdC2?R)+5yNq-2q~ z2&kRH98dOionbC0)mBu_9Npw%InFhNDdI1J`T zQlgx|wayG>1JFZ-PEiz((ok+XrHz=O+MQf=gKG`Tqh0_fT{iot<-cE<54^35yaYi+ z?5IOnZbW^Wj=KYe%MuECXA3)iDTKLY_!Q2-ivvNH#sT-J3thX79Q#U$n*|2pZ>6k``t+q{RPKfbl)G96{j44RPSm~vvjUam3 z1Ibk%uQxLCx}C~%h?olE4zrR{x*k18d-^#d8A%9X$eM)Sz3EeS&>w9qZvIfWD4aMAgoCdq$Us+U<@DCqLTUxJx4+DGsY_*HRyu7?tARvo&v>p7hOoVNmNmj zMRn|oY+|YDHSk%fGUJVX4>1aEP!y_Ks3n@_%>rT?s41!p`8BX1yklhNwMObPlvxI_ zBs?i4nrH`${w8>4%+knoC^-34TvXu=@YT=OSznBd>!J$XyDU1?x1XZpLWdF*03u8X zcIDGzG%Ya(p|5qqu-HXiNw7DtM!)|p&>9N-`<)}yI;O2TEG|h`%qlRp8ydQk3yRn@fAWbgZ4UEB_SbT4AxMh zu%TG^>-{cx^}ScG+U(t;OMLVwLKmp0)$P(oDe_FodR>%2MQy!|^v?3k$H<(-qDD-l zSzvONEb;8YwuQrKQm8zjd6-D)T=e7PjlTCwdK-UQVtNmJ^EETMJaT7GZ0;&e*gHUg z$rybHnb|?OMWAT)gtXmKG&O=fO^uMY=&~(y&n_1ZmYUuN_s)H=@4W|4HH`VI#J;nE z9Lq59*hSf9EfWY>8 zsBpxCEA}#97Z!5Mk@Dm+FyM=T5b>u2Kmsut6%5g+csR;hbKZDiI&IKBw*cNZp>@nV z6O0Uroc>mv!SE57y#Bs{J7@mIC{&iWaM*^j5YaXPvQA*V2OB^R7846X%Fqkm{JgW@ z*ucQu7wq~Jm`AOuPzFpX%#4F+a{?4)AylE@Ci)aiKkl&kT*+ld4Wtp2wF;1SBNCYD zL--QhPx9E!xP}3z@D)Kyfv6iq!?=_U?!}+R<ht4<$ZDH)RX< zuH@v8ul*}pzcR?EOMMQGyX5JEpPM}`UwDb4NTxpoZ!N|GD78n~QI)@%Xy9TX$;68F zA3q0^e*Qu@^yMiAtx8++kgXH3KZqVGwkW>D^d(p}c;ZzHQ!{dNk17v@s{$JJY4UH% ziFx(Jc|wB_4hk~o^=VD*GRwj@dX{$l3T$Y7dHw4lHQ=~j_iGSu3vD69e(|;#G!-;_ z*eYv_quLi>IUnm{QVc~2nNzb!URVp4Sa!$KuRk^Q`?JZ$Ow2N2^D$?91%CbP;>U&^ zwNZzH?cTY|_JF2&Tor2JlsbhXHVSo83PdNLAY8@nNZ zrXpm{>^3du9wT&)b?QCxdWXnFoAyq*HlvorRDs)2LS#l*PILVOYH{)+Db09wDO4L;v z0<*=45XZj=kAe+>ppcA;VbpTjyV05Obn7k{~P!D#6lO zcim8S%!xPk?JaSTexYUGowsyx$zOFpyGR#7l_U1JDuO}C#$$kB_Y0kN(H%n{DqYdjfNkD7 zSHeeJbY!+HZS!V;ayGd|6613@5RY&q@818xy4Q9^UbfWKKg93Ih&9Imi(u=zeQfE-wJj@hO)Q$arT4tF zV26E#YB$~pv}=fyofDf5!n&!yf zfY^Ba5uE+Nk6N>bLBJL@?OJ#`WN=7X(Ro4Oe3zj)?Ihh8BGH9vL6O9;>|o-u5DP^G zlwAu*2^d07v`JxMrXV)AC~`3l=+F><_FtH*uIm>0%Fgu9F8so!rs1J^&&;d#464bQ zNoXr3h(vy#*wl&ROt2@xK{dTk$WamqK;0w4uNuHFZm}-R@X&+}FV{|AaG_zHNj~Tt zg+P+%h^Mx$OT-+7QuH%1?5JmuxrZOVZt?}!(8K;{SYfGYL}=LuPfyuv%y^^xs1<|P zRmn*>i51!-Mugt{+cP62aDdUZ%PJEg}Bh4!BI&dT5iX*Q^#yARn; zL1a5N@in2oDs`mI-up_wJFe+)ujqeSiD}QWXW6stS@tY@mOaa!WzVu_*|Y3f{uE0# zUMipGSz;O)I`YJ0mkza`X%HLkN-JHX_kq~c#28Hcg1#kaO_yIlaIpWqBT1z7=sZp3dV6D$tI!Xu7YXZ`e|>UxxZmdG9( zVM$?K>i)sXslVNL*Mff2_KX65iY3!?f0^{*FwgrlOSB=Q!c`=Rjs@CFBB|Y=G*iL+ z;c{~p9?smg)G9i`PS{Ei7|}1Jgear3JBaaG3DP9`>uLinFd@&(L4aCwzMo4wB!ZHF zzlVrZyGfCSOeiC$tYZikN*TjKSsPc&+Bq11FEqNgcc}D6+koefIhky4%?aHha7AX= zpl!H4>*L`>TU>#80VH$+QwBupt|5?>Fh+#b5Viy_!(fJL;hw!)Ze2Wez_|bMPF`Z# zCp3wT9em{#YssEsi3+l?bGxSoz*soef`nt8SUA>`BOF_NTZ{{B?0?#hm0{Y*#wZhI zq>Gk>+PN9^Rz*SVGR`#X7*jluf4|U^0eS6x&StV_?C>H&$Qi-fMu$x=R_li-2_@X3 z0-j*`b53?+)N?bhSaaOQevj-46z^H~EPIwc%bsP=vS-<|>{<3KdzL-R|Cdp)Qq%sS zweqD?m%Y}Xc!oxTJyl}&ialKjzm{>7W3~`-$p~H8nNfeWi7_aKCa^7SO-)e&vXw%v zlxj?(P-dX}N@bmnPRo!jhzyJZm2s4rBnu!SG$;mH2FATbRqLk`{`=Ew{p=qK+*^L1 z^V_ZRoA$;cvL{y#q^!105hPt$H6SHAI(>QZ$*BCo59W2Ip_vxe?Pe;2I6b5R@m4a7 z4c8|k^&#|4>B_<4q(#Pu{7((IzGL{3+#Asp;KHJaY9FfTags0<F{tPl>qE zr}1tqN9K-VIChccyW>Oi_g?tMFZZgfO~o)g$8c3+76BhcsP`i22ZPuOa{G0xt6m)= z<-4&=@op|>b}Yk~?Szck&-Yyu9kIJua!O1SLQ`&SSh@8@x52ZgEWRL#!o!B5>~4f} z#$y}{)1g=}Tb16;l94JL=z3si@FQPbkSte~>mJSgh)jux%7d9vJ*D%io+3F~NG05z z`YDak`*pD9$Vm6}y7K7y2}fN&{Ld;*G%>4gB45!;iD_bJIJ5qOqwZE8>{vZvqBYVX zMk$+$w@0N#@uoynVGvQ%9S_{&`}Ez|@B{Z)FFF(EpwMvo(W%#8?>8F%RU5)9ftaB3 zF3@INRKlVXz}XV!Rwqmf-4+WQ{hEV9qi6KFbZxaNnxNOqrkpiE>I@1%mJ?wSD5x}n zwkn!^6->ZI$f7WvYlWaNQ33Lg1fax#tbNy6E0?Lp|HqdXnl3uKz2ETv@Z|F<( z6J^@ZF1_18M8%GP3YkgSL=nj*v%6ti$Sg~-j5Cwj-3e*VOu{Z=Iom0Uf}mn=a28I* zj@WxSI~G*L-Z%@`4i)wLy)DU1GMmDp-go3D&dy|#OnK*hpZ9tGWu(!=CjIyR!l(=0 z-gUw_d!gHvHikt+Z*;pnT0AO?h+maEvhHo-J+r^?f=KVB*M$xcHr;pOlJ!#xnQWMAZrH4* zV|zI=9D&|ukJ*8O5UyVN!bcb2ErbMCn6ZqP0Yysiie#n(4w`R3iAUpDGAgF>*tkxW zG-A0D$VYhC;-z1r6p6HM{W~C=C7b+PN(Yf@Yz26@VFm$5&G8P=%kWBK34# z5uKx5km9Bn5z6fH~oz!^yjMZ;68 zlRDw7R8Ou1Yjsj*Q!*+@%624K9xOD(U%Nh~5y{w&g3nieL`onI&&iY2I?|TjHYqA= z0gspMiZ-{ksS*_Om`OQ*!Y z=zyzndP8+7B+l`|UCO3sE}S<8Z#g?>xV8cZ2h=}PHd$?yq7Klv2c32%b?6zV-tzVy z_g4)0)4PO2gf}`4s|b58G0bgBfhziAZIVL7NfW#%WklHBvWnlxIH{ATD|=2xS%L2k zRvihL%XaO#W!wcHM-J<=+ka_TUST;*Sa9IoOIT3-EX=XKB6gq5`&T<7afAr;UCMrmw5D4X1%^s0d)eiu$VAhHs_*h{&(uZcq2ts>SyoJotHe$e;ZHR9YqommUAvfIXg^mpdlr=r`n% zNduRRB62+Fk{v-9BZ3WA}{BFJ8r ziNe+&7B2CPtIxgUdZ})pC6ZC^yFE5!CYkNk7rJ%5^B%bL-AM%a?cZm*3W+%`&E$0apg$k+R}JE5Y$!S z%pn$Z23mJ=teurKkU;eN^Z-8>_zTw-^g7OzjcC*$;b(?SLEZIEMgBn6w;<6(gULkExulO>f&m8wVT5DxD%|MdPty3^@s#~vBr zk@V7~*tAs%%O?!l{mi{SH%r77Z{ksAU|70xoF}au=Yb`Pnr?M5Svw?aWMnaGwnY}R zN_a|La@B-8PcR#Xsah!}DYEE`M*-yt(~jT(Vb^a%T8?^x6p}BEtD1f@16>&KcDSVk zwG@|7J^GcvC0`$I$P;E8VW?Vmm*s|3x0;|7MGunElIP+I;ab}-vHnDW?i>S$kgOY* zxyR-fro@CL$6rs3SEm%@tEC-H(iKM_R?VxbiI|JcTbnhqQIwL5HODZ!2h)ED*=PQ( zKhFN!s^~`%TZu3o$Kw8#5tLkrB|Rg*{#?re5GZQAY9h@*{im-MR&YL zRmx0;EW7g54Kt`A?8?{SI#7q}%wY#%g-${Z0_4>0`dJ6+XKBtKV5__9V<+I{XAARR zoJqpZyRkDafAXQ+>0+@Un=IjFc;#B;SXV$(NWxHNddyAAoG)dEd?_#DOV#!OU+O4f zw_o=e(*JH^z_+DRe_dP+4Ty8Cb&M=#eX2B9EDwvElCp92stK_C=AbOInYL_$=KVaeInh4*Hjr`z5LnrWDr~!I~ zd;Y%HvM)~anl!-dqjMwN2=spl$s$aqnH(j^f^F|aQia7Q>~u%ND`(6iUXJTb62w&? z>m#QmhqptHgxu|tR-J5XlN2Vk<=OY<``HtF4gW9gC9|(hU){B-7tUYtKP5{U+ z4A1iD%m7^#P%r^#bC8l;^2u?v*=pR2cG53Dz^7hsX!V_jmP4-4)Dc5Q*Sj?3V*G`@ z9=-Lu5i|Dt4_Tkw`p;2op7>@O`M!OXg%A1Oy7zIiKL3{eV~YFsKJEC+_x$D2Mekhq z$+cv?($Xj#`u!`_H{U$6o0_s)R%=*)led{IzM$MFBo<7+{gl!1q9;!TMTFDc#K<7c zf2ZlBD&g)UdmmECcV}<(#$%e)sWHW8teS;a9sDuPm;bxm*+G@h55Xo(u2PYHe8=U5 zkkl$mb77{Na(Y{l7&Ni0!5$y^+MJs!bC%&v&oaCm%W!O+-m})f&7QT*!aeWqzxdS{ zfg!EVNn+)c8^N*DRU#h|q4vKVYlqW{Iw`5hswRtSo=Nw z&b(^YWK&2a4P2|CDRpx~qnyAa757-vRi4L5D#a$pey~0KN^pB$!TK)KJdP3q-G&uz1gB>vzvW z9TcC{n=YKeeD;13c-jL9z-`?R22et zu^n+mL*HWn+G=2O41R^OqW9rM8R|&c(FUMx1UIra4(Uq~3yZFVq%wQ-Uhz+HpZWpY z+FmMh8uBb*N^8@&_xBYHHLY%O4xXeIpPpz}_>h!I7$nh#nJ#BzGNQ?eNS7xRs_xPx z700WY0%thLQrw~mOMf{2m-_~ldrsuW&k|mHin(HLg}b7RHV;BAJL_dynY=+OfgJl| zD3g!&hdwS1VH$x?45EvWZmyrtMUIvv`&3_pa;lfaL#J`N?k z=yJ=vb?Gp;4$^}gOM3!O#kN-W;PtmnpTOBdZ0P95OZH9~x*zk##3mD<`A_sJyxIY5 zGE$P#g{LxRbc%fYf?_L0A+`d1pOK^X1!HRWDa#K0SRwJ{uK)1VPB4SnQFIzn#Y!6G zhK@1_Iml~L2tLWFGlPBm$8X+HK_0lFA0dF^`6~t#bsWbElV;XCf8KnXVRBIrjgv)3 z&;Gkc?({1PkYJUC2=L$4lz5~j5}#{Q@CC6r8&@BXgM5X7&sAYLPI&b=udjc7@#Ro5qerto48=1+JW;O9UXp^K zV~}Valuk&>3ZQVr6QBep0aBp#;8Xf< zhd|RNn7$5WpzF|hkM&{TI_yugkB||BU=|oU+sm4=!7oD(4`3=H%}_>YZUK4=)kp6? zXUPS&zW-l959H31swPP`+-g`dBneS;pvDWQ{d&wtKT(&MRE=mW#1a5pYY7Xk-Q z_SOItcEzz9<&;%~h=Zcf?Rd*1QRK)ymh;mLG9 zZE^&N|D=Z?8Tq1elp_jUJTvkQDA65i(`~R>TLUosVLL!PIJZUNQ<9?c5m-Kz5Xy+6 z@JUqRLk4zz)@D|S|`q1E8r)zRVrK|$@TWyX-kQj^& z_*nyhYntzvGtR>D4#W8PV10`fs0x7w#G!K0>?YR7fx-`43u-?+I~DHGz%i!U7J&B0 zHi8KU+r&M&^q8-*&34Pbg&o4CtL=5`>yNYKzlA0S!?YD^E5XrQ)#NBRP8%Nz!S0me zvK)vZsZp=JbjE{!8v&jaa{NZShpSDt=Z0vqu*>s*s~SG!QnRFZ3FfSHn&Tj4lPO?J zq=lJfW1V#?x{^cs|*UD!V<#M@xXn%0lqL*rdakgz)+`6~;agPPMUxETpZyg4geRBX>5(9~M@#l6oeD9}y^xOBh==UFL^0`~_ z_kK}goqTWnxi9hcsmIggSMmEN4%A=AS-a-{eKVrMpvMlpYu7P{sN}dSEMcMHx23VI z1IFd_ysSpqIF1j6ld8sbNFXsoYMgndl@VZ+o+H`jK$2}@Jc?!perOMnhKghU0!6V> zRBrNm-~fsyt~!J`a$cdZycgqCk}+Qm>j$fW8UcyyGYk`EX)fGi<-&pb3>z9^NonAN zZ8aOJt>$AjbixKbD*6pbI&1-$e71&bp|}>ByZN z9A^Y=WKJU{SoZm5>Do8#W~UKB`3xHwtg?MU05Jm8d65rx(W6T*OI$cytv+GQuak1i z8%ClpO+%Tf5JKa%LN^V_%pz=GFAZRWHqOX$EcRB$h1TO2w13rNH_H*a2+PA}?cmiw zTvp>TNNIsZe@`qH*NBYXDfhkk1jx9OvE~#EI=u4-3NHV%5iPfVMwuMT*Zkm7(nk)n zfPxky1Ox^fPOHyT3zL+H595>2tP|$ui>C`Dgr?*rW555|Y7&RJRlZG9HC~(pREKzR z4#EmjHqc2YH3>rpQ^MV+{4+bUD);HCw45OPeA-2uHr+X`n+pLkb2LpnnR#(eovea7 z4tU1$L+0Q`LR7RFa!BhazRjK>oU!_@JTR|C*><|;-s0xcFgx+4a?C+N!Xm)Jb09RnP<&8%Hy`z~eg*25E>^F}5;e+ol zE(c|VC+t{SI) zb-5XE#6X!tI#)g%(&yqos~UYR$pnUsQ~_H{M3(YFNTl_0g zA4&qZoenIn-JC4=hORzHO^{%z(?I^B9BIjt848$5(srp+5|f$)Xu~LGsg`3a9L=Dcl4{f^Z5AM$GdAGi6+^u(tN&IN;)t(kOU(ZHP&Y#t4h`YIy8hgb(~aywPU zheF_ar~rLHg1@fD+6*d_dTHZ_FZD!zqSA7PP(6p+>7Y^V-OrNs2~w9XF>3QkQIKW~ zfq@9CA;S4)fs|({A~9zOkKJAW+LV^R8Df&6A^)wC0(CgdvMCP3ev}$;qZUIxU>3=8QI9)8Xze&7uGRDy1fWAl%apYPF?q2)r%wM zw%<2)Ao;O9diYs5NBHuh^>k~vszQJ0#j(}{%@bKD!;WQ?!1PLw0W2x91a+7O%8h5Z zOdY7cz;{%A6GaN*!4_!Ync+MJTCDj6H8CZ2{wSd3CBUjVw47EHHQY`2>;Y5&IqGc z26b^WXjO(OA|VXZj8>qO$F<3d3Lyb0=)f6lv2wG9L>Fd<6UCm+xx(_*x9cn%isI3y(azM?*#5 z9&0e?3$Oit!?1P3k24_Q(od+KO`LVa;9{BiVC2-(rZQ=uB?vHmhuwed>$e<~o;8td zsBvlbGOLW7=v@BLYu6neBcC@?=(ZJKa=u`Bp=n4Ujn|x6a%7NpWyM>J&a64(l%0ro zA+c1OO;*`fCau%uQQ=4+DM6{dxecb!{0*`dOag4+*~N4=pUk(%;(!E|{v?y?H(}0s zR}Oipe@{c@D=ZfX1DBjSbMuGSn&rO?VqdAK05azymbd2GVt$A_BN%a;PU_GPVBA2? z3g)5Vgw86(LNXtV0V!20Ru5*VDpevs65#}?U5+7SI0e|%p!N*ygr-NL0;mb4B2l6E z-e&HR12e^Xmz=FP%l!uZ;nTc}gxxMZ z$3N=Yw(@5}bR$1Us!j3$_1Kw+$fB(E^hBt_a*6QV4a;XQX(+&|u5MtOd;wo6Ri&6HQ z&OtJb{KPtD4R_?O{df7V$$?xZjQ{6E>whYzm}M>981aS8q+klt3wb*(*4NqsGOtV+ z70@yg{`IoMcJ^DNvzQ~Sx7Z`PTsU0&X7Ty=epYH$W~R#t{ss6Dq>+&axU`9AK34-H zKBE;V5=x=!>h#dqgH?|!NAKLxGZ??CgzASn_P=;kOW9)^7(~2)alP#Gt=`WkUDrjKO*wB4u=N zyVYo{!OH_S^)qQ3w+YAJIqddt50B&m=CM;mfY@yq2m#g{$|}V<(SX7?1tNa{`?iPy;q@e5T{?kq<5lU<709%+Q`S+ zBXX?W%g5p|SwwRZ{fOw4(XLzwWc%AA5>0yqRa1CTBcBcs+!M%)88;fZrL^v+uo5_A zn1KsOp)m$<$pVTT<&`cb6(4y-?3DQ6-pB5!&Fy&o4;LhRSyl-*@3(A3%Kn;3w@=%$ zx+;JOi0~SQTjO4drMUc19Sr`@$I-9i{Fxd7>);|`LX}5)QYg(vxj=mCiG>*Ov!YbZ zi*um2i$+rkSrVnWvMPb18bXF}8tdZV`(SKT=H=BvJZL>9Y-pChw3lT$U`f4JTX1+ zu&oka9reI1FWkA>q*z;6*E!Ej5%JRSc9`WYE4}7Sr`OWaRDtwbwQ0%mjB&^|=|DW6 zyc@5dwlgAG3uDl_grkdKFSD5R4&nR@wG%cxc%W&T*4F3+4+BODxWS{xYd%S2O0XBX zQ&|1@@=Kcz8Eg70E(G6alhG*5PKTsU7kI!RhpB`Z`z~Spl!s5_7T24hYTY=VBFF)d z@v|vO^prc(vi*5efE0{xx78%7!g8uLknjMdLXKxTRu98S9VA&ON;)CQM-Vxa0irph-frLN`#O5yz(1&` zSuI?%_da6}z4pU$P`CBD)3khbSIP;75-EfyoF-|hvU;BG=W+H@=5d8nANLC*pZ)2p z`wu(TFpXB`!*;S}#FjF7dnpOOCB;!6g@HH9%B7fdQRPQYzTO2>`ZG zIqZ-#Zm^v&xRj@~x9%x@P?%SD;aBTc>{n5A%2E%X@dt@6G7d#pjdF6>K@Su*RiKMi z$Ul#?Cx4KYqcT7;Yh5&zz2@a(AxLy(vQ%>I9C%nzx*JL&n6m+Cj$|wHw2NQR*+>- zFuzjXegB^Zz1OdNKZ!?$PiH+Ce}41tW}|Td?S~whF}Ts=@kt3t^@n#3%xA*gX^Gjp z(-O0D;PbeTGpmkUi^XCB7c;EO|n3ZJ0HwZN;r7 zrHA4*1E?y zzMDp)|KvEm{gsDd7SJ2UA@4zsO)Nl94fkpSwzf5xR8R4FjJx@IDjhfA@O+k3p;y`!+ z_9kJG6l8Xe$}8C*ykZ~WXN2#EcC5SiowcQ4cJg=CZY&09)OMlVY$UG1`~iUSV*DEr z#^}By2N6II_2*DrzxYe?_HVt8@B97LWe(&NO)d)|8FVw6yR-KNlPol)EHI+ONuw@; zapy!ZuicFg{~6R(M0P>TVSUYp?SBa$AHCOu-=4Up0PZor6|x@4H~!p*U!&!jw++($-rrVs4}6^UxPJW+Lzqk81_69r z#m<H|n4F!|=0kmf`n@6#4sP zzwnbdz}PE4Fo(Q9@jrd@e*f$z(9-sw;_CRv{jP7;O@s8;AvZRCNw8KQb@}R3@DEu| z&7$tOIS#^+^xssR2NbiiN}ghcuqK#~<8;ynSl z4-#WlCAf*|%?%fUkn6k-gYoO8N^Ev2->C=h_Ay-<~3Zd9%N1iip*K#?A9;F?6;|7#?G+r{Y z(?vd>$2$?v_#%ceu*#zVl#9k-8Sb)>p6+yjgt9zLcLYK>%VM@We{ikv`Oq7ZTi>{+ zjFLnc#*z-79~5G`^R)lC zpR+?%nc|xBl6MM#o$oLS-`^3I&D(Ipg1c%ANcN0!6$-|+t~6Osb!%Xn9RriGFen0L z9Ss+eRhX~0H8iv^j8P1BGj@>Or0Lhm>w~^#J@b0uk&3<_T(Kr>ni(j_kO5uVTuEW) z%9_|wR65iOCQeADzxY9Fz3}RS=YRj_??;-V1Dp_!(bD1vqk9@0Yn|zG;%z|m1*$c4 zwgTdwRLThXuApsh8};=<)>7;cW~TYrt>hz|BDy;lpZvqG{d>}0RBz3&L2%9O^~|oL zh&99NoNGx3cwprggG$qsT~xIcAK?;;6ymflto$jfT$JxrA!G{L3ZUVYP#-}U2`Pb; z74oOkO3(z4#t4BHZtQihjk*2G8J1lQ2cLKe1(NSzyN~F zX5Uj`vOTv&yeGW6^N5{CMhH;5v(%twMn@b9CbTPj)F-86MJqb0F*YJ=D-`etS*^U( zh=Q?77)L5$JR%o}Q(S)f$mq2j1$JJy1oCd|7ByQF+uQw@ZnRRJn`d9w!|zFrmzS7thd$A zLcw1Gl?0G0XskfwQ?QXj>B*i?gk_1&n@^i`Pwpt04GYSYWpF(|3wH;(tf27X=KLWk zrU6|?2-i12_d$6VD(@j+1+3P<;t0jTkdBK%NB{#>Bk&Jrm3QnM6M)KmAhbkSl?25Rcyr$*QQ{+xR@Lr0kvZ$Km zI;p%nQ#5KOSla_PJUnoL3jV}}9FOB%6INF}x30Fb05Wnj`v3>~5-Un>= zPIIGh-37xZT-DH$113c3;Xp5jQz4*^JZ4pFBk;V^%*A0MBnO0GJx=gniZ1mn&b21#JLlESD~E3b zg5cjmW#93US8v}^bO1;q;EB*4JWIkk<4`Tf#JG}(E4(H(Npo4RH;4?Iu+vA5kCT<7 zoC%0e$?BW}^~ggkkBK>kb^${MIC7(wOVCVUEBs_`ux{t3Vw?|(^>Q7~H~JFK3|@U% zZ*qmsG%QKk;$o9!;VZsL2tU13^Qxa7E&7TZvqqS=9#_%~cGX0Kg|>S<@aZOxFuD=Z zM8xKn)mBi&@oJq}IIEht(wx*(Ig~Cgri&xi%t5tCjQAkm=0LBYWg#}8#2$ZD64qo9jgpLxy0|lnRX>4s3 z*T`YdK0KsC#s?%frwXAJ5d;X#sheB4dmjJlm&H@3^&0dCM@mM=x^IQnbJxA`?!rR~ z70D5gFjFS4l2xa@TGoo6PJSzlU)3))tSx0J7BmX+F<|VTNQ~eo0u-MdOF~36lHpQ`NTosDLzthiYzJh=w*R~h-fsQo# zM+h^NZMsST*Q}&v;c0ykPm3%^VeMop{!g~vwe|Wdud(d(7v;o!CoH(w@n+2dBrdoy zr+PwWP_4Yu5EoVGfXW1E+OQqB!Dl}>Hj%vG!OFj?ZDnSZ`CfSAJnu*E#mveoZea5Y z(S8mIy2+K0H2OjK?W#fS3r&e!)M2-@hb*8-4%NYKq&k2hp0;z1T)U)^*$u&<#Z}0A z2rUe1KtrT18QG_)|L+uL%mHnc&J z##K;}=Fd#gREh&%oN|5QZd>mBwO${;yV$O%#tUpmEYyh+WYA}lc;=>zO7I1LTzt9i z=TH8#R5m;{UiPcA*?-P+pal|7-jSE(Ca`-p

Gd^FugN7*6{!q?aIHR}J-c>i(mH`@o_Qv*D13K;B%(VSFqdV>W;D#W9~aO}ZG`N(NQ})`B|rRxeZ! z4odB6mTIuj@6F|(3A<10LyqgRYmt##5-)v@Lv_dDHLSY~jjjT(%((CLV;}D|+j{c~ zwEq`}C*B&_eth(|<%i;##vt)G@5N7j^VGPU!~&*m0oa_YAuhrR6;_W*t3|OiXH<(S z_FfJW_j>o%DUQA3WtReJJ1zyffK!1eCKW}BiINKFq-7Tb7nmOI%+%A*_?{!mJ~;-b zb~NUbOh#F=GscO;r3oSy45k?wj;V;h5@4&i95x(E*WGpYq05{5kYXXP5?58+^xZI-2>`_m1hiwyYk;>1=m>et9@Ii1_Fg_6-k+P@2ZVyYUCr8UQ`D*R|diMM5domUn>=bEXK<^^q=hJjf+r8dslV=irG zr?->XUw(hu%{4!ojuqLaY;ds6&A*u*UJ$ydF;iJPl9I@iU}G5Q5|D8=zn;@?TXO^zmYv0vZL2u>$NkLuZ3^{aaM<%hk?hhaKoyqV#MQ3T?|gIA zDQ20RZYU!((QwFRj?mW3TY3TsZO8BA;gM8@Bt=e@f^?{`j1WJc{l=o9lai%SAoOT= zSI(i7;NpO!4p^ECvJS3znBVT=b1SM|J7Ue$+*p9sC-zdlfpx~vumWvng?2u?l_9F+|SCJwvb;N^IH4(H)(QHO)q4m_04#j}EMaYR8T z353|k;pH8smu8f>{%c{{-m!2gk5PUmkt%rladh-D<7aG-J>0@(t5mV_whoKR3>D$R!v*2Ce7othDv@*!?| z=|(R5ilY}^efyUCmE2LEAfslwoEbH8tE}Q2fE;VDjwb;-s0*1&8b~sx%0X!7+*Wt{ z4KD!4KeIf-viB~AH+j|vX!!(Kpk+T99uo;{qXaozIWvQbmyK%B9K|lS(}@OhFeQ>NNx^L@X(hSStEoY# zi-0GClvO4ZJ_*XQqMo%Xph(dB5wkYY>>4N^R&!G!57$7mpB@3v7AQ~i%`G6#@jY_g zE4!BgXsNIqEWXcu`SzC2s!3;TU>nfxfCMHtMJK1bn2WBj&PlMl4yC$1@$= zwnYex^V3MqGTtQ|yy<)%8via&oSJa^iOCmT8t^E2@WCpJI}`CQadPMzG=7|7ZS zI=7Oiak|Q9)yr-A&Bb;qW5wX;fqkyJa$Olt zBYo!CHcrxb@#8h~$IU&0Hl;SWvqMSZBxvVDA>xh$VzF?f1WMjv;>gi`9vaBB<=&O- z(lnkZ&5-yxt#v^7R1dVJvVgV}0@{K}qKj+fBn4_`L3m*{Xcq2aR@GZ?q5*Q4DD)q& z-{QCal^cEFD^%4Qi10BTYXO+l%L$2t?1L=*ETmwX^z}$Uq%{zffH@3I-=#O(S_9d@FNQ%*azvvGdSv;!NBzrQ zmBgT=a;*Wbq1of(YHE;vqP;Z$61z1uSQrxA|0>pp`hV8WjePpYix(aJYp-pWs&tB1 z+~GXwv=PMb?)}O_M^3rmScB$Q*dP+Ak>P_4pE2g7+q%A;Uoq3N6@T>eez@4tHQM&W$2aD}D9!Wu&Wv;=O`*RVr6?}M zEB{Yq8;=m}7v8+;jzLkgVk%AKpOknc-ky}0s0<=xK#R$nbq;oyq^7O-+iDa(DiK^# z(W7H8%OvsRnd4gbKBs*8zKzbY+$@5~Z;IA=%0%*hlK4c!MR(6Q_l2V8ta+h!CQcmP zL+eVjj_Y2d+g8d%b@c=2k)PIVc2O$D`tQSYDASA58t?}jC5;DG))b|J;03KPVI-i5 zhAiU`lVYg3|Lu|D%`2BaH~(LU7fPYJXL_4!YFZRtlt2XuvZAS~4qgRQmzWAv?I7;4 zgv#7xai;D2cW%7$GSlBKx{OHE=|p1=j>AUEMb!!)6FUM*T;K&6C`6>e9894e%O2yS zC7oNfIB)-F*^!eq13atf99dml^8()#5M61eigJ`Q$}4lol>i?glc)@6LGYe+)TzgB zgUD|$i&xzD`HLshYa0znu5R3fX<2Qou4ef*sU?oJ&5?YeSw9$l^O`pgz3Ovk@4pHR z%PR(t+PYiyhFX)SvReeczD65v2val>0moqaQc2xj@$?zfB#h8JeCjLJ_;x75*xVhVg`(wnj$Jb3iX$50e_j3jLxv-^V(0%?e zLsMWzB}D;iGXma$osG({nkxxM$;eGpWx1gv-ykpr5$1h)gTG2^i z0(3{!g+?jrjV4MIYo@b5o5?Vjr`23xsTGe)wJbSm;sP^7d+rlRLCx?qfEZ5SV8Qkf)JsK)XMX0}gw@0`Bj$u8u zn^_jFP8{93IJWlU=_U`5jiAtAyK9s*MHbZ=3ZF=zsFt=W$yjqNZ(x@*CY|=>#v_=O z4TH&3bTFg8X=L5Fog8kRm>PY;=7-+B+-xRZRGdV3^<@`*jp`*dWT%1D6w}Fz8j6R! z@nlS6!uPk#^WHb;_ugcB^KwNC?AxZlgxpBaw4+l!-muJVy?$}%C(4@H&zy+2bY!Q{ z2_?`7yK0o8WEM|sNzJ&lba7l->{kr|FZV9oWV`d<11fup=w%M_d(kPy>@AAX=_sy`zl$(@bKwsexLL;$KaC?=OCh^DT z_FDg}yT&B^vMHgqs-V;cCO&Re(t;9944`Q$ap{F545Z-zU>h}&_*@8bC|OdcNlYvp z|K(+Ch_F`6KCT33EZSHlcZJ*##?wKwpijh!tZ2RfqHH%lAGUU+{ZwQzjHA$vG%c2%u zju6`-tt%oi#taJ@Z72s!d1Vo6qs2c80rACa-uO&j_pKpKSBi8`P~ufJF3P+nb!Uf_ z?)H_;{>~a6iuIVv)nwUOHPEyej{v@BV=|)2iAWdvu!{9ia!E-PM_Eyr0s*TG!4SdA zmgQ|#0{}yZyxaQi#jox;pid7ByV=)0ARbP!2QJ*{21934ga%Ec6pbrgEbGC927}a` zK8Y!SXc34UfF?nS*s7Fkivc4rj&d5m%#Ci3Piljmh#S2RRt0=*ga}K6>|66WHwXHV z-YkqC*;@vgHK}Q0&A3O-nlf~JZb_Qd{W1C((hE^x(hAFbYBCBQC+ijJrQxI1ex+h= zris-@rEc_AA5AWO!Asrfomkskg8_U-BfVLivb z3yK40wOrBnmg?NO1glXr=aW=TiFa{)veOUeJ8q5(p>1gusHhYL$QuXD`7%W5>E-9Q z^x3xQ_iXX1wR^Q6f8;s2$3~Jh-Gc%t2dHB505XU0fBu3l4^~c3l!3`nSvvgMNoHA)d?e?pMsJPWnM{EAPmxr49uz6^BT#oL z`eKZ}JsWf2d37=>(%>a|X?Xo`(=k*O=La`EHTtqkbE~KIg&82>QW+jF7ZZgy-N`Xk zkblOjfYbp)hx9j*1P0j^oj7O#m6m0{JaXSzLySob51u8C-$lRNeNb_?NfcYpy?UW< z?cz#(zQ%k=Z)j--|L`!Lqa8`Jk{FMLf`InZ(jmuyPo@+L(SSY))*D)y@urpzMdCvs zM@#}Oe+@2xFr5iW)TEF<$2*SZfVVFg8vq>u)4=mtz@f5W7}RKK4vVpZ%T04`@JaX` zKo*$hC|k9xiSdw>v9PAOe74#Q`m&60t{)`4H6X%+{%YhCEnRpo7Xj14gLcC(8M{W& zIeld_=qtgEW;I(Bv@p06mBs(0=mt49hi`}WoN5|i8ii9!qLdV6XFz$vtOG!dC3Hr) zbW)~?4RE;a(x&tz$_1|_ye{rWK9R0d(7LjIE!V^Xk|ayjabfty;4k>yR5jkr;Co;& zoS2Q?W@|a7A#6o#m8SaX5#SENi4lfnxT%T9ZhL6oM8BTS;^o5sLL&Y5M;qm};gX9- z>Cvg4qM#wB2QolGDiVo)=k<^U!=D}5$2t&2uXNF1NxbK>T_#qXb)@N>z~1O3nH7k5 zbIYnn(E!+2<|9F=9gO4*oTqrnTIJF5*Y+%H^LN~+o80Sh#E-o9vFA+=r$hX>{rp{z z{>o_zJtSDJ#v~%=tCU0RfS%+L2{86s;!v|2O8Xnbnu~jzT?Sd4bj};IZ(d1E*S8d1 z|MA8FbiUIkL6~bq;#1hi)I=iJnfC)QK}A)`5DVVcKnAbx!Dw{p; z*?^64Rn{a3Q0RN>-t%8N`1oOa{*NVOds(95sv|$|7=QF+(^(nDl(I=d!?pCbIPF4; z5M#Jn4eA>VVyf~G^K*eBEcSAWEHxx$)dK++Xc5|Ls7YD#Q%_U^q6XK5#etE&< zoBmxipd_84f};pPVq_361ujKK$P*?HnN&QIgj2>8ARSsc+L`s8pq*xwNhupFlV;aP zt=Vqq@RC5m_$bODFv09=608TFPCyVIrx>_6sOo{w?*G87<$x_%erhBoUJ+V)YGU$p zBx0{HqJ)hJILe)bf7HYkZzLX*LeRJpGcmy5Fq5wa1I>E>+8mS`il{gjY#FdVn`Jf+ zi|v341WM0y)B?L`$|ifc+a6*=iRfkE;k%B7oEwLG-%{SCGLj*2Z2gYNzQbMlg6Jn;Nb&_ z;{G$1oQD`gP>4T(jS|2g0X~G{ZBUa0e9|@?C#vY#bof-Q89&?(RuP*M%=N8r*=CKa zt^98^E6E;HdRgX)`(8We%%MJ0DrB|}3K-3*K^94axnO@RBrB4rfvk4%RpNQ#1H;2B zUVXO#ub-V;VgL?OFxDZEXWF55#FLQ_O4xd|cvKdHjWhf)e-KqsZCTq?jk#{csn@@D z-w8v8Z;V5x?_BN9l1fAX$ z!ZgV6URtI7L~+$?A6TwUQ`EKvhf9GnI1CQd5@fN;E7YDS)?9Yn7uTM3gh_VLofdvX zW|ewxHRJpTf*(^C9(p&ZzHB=oX}-8&uWLqcyrEA;nE+t$3`!vw00fkHf^X+@>^W(h zM^H6|7qtdn(_}GyE8zXH2cVyY;*TLNwhkvxGdZ<0f1tTmi6;|)c>r%E{(OTx2Zp#U zND~t#pOJEt{`)ba41711#DfwnHg9VHA6_dTL6>>V2dBIEEJSjtmb4X?Q^d}G!z_=@A`M;L0>+p z<)XV0(uM}mfXsQTpk@Q3VuYy2s+#$68#x7Vvx|w6t!>xt_PgheTl)?E69>-hfMkI< zc+ZU|QZJ=N;0Bcry#Uh$e@Ko;ftf^mOr9s1l&@X6d(sq~O*O@1B0yP!HVJ1Ssms8(*UBj3}c6~;dNgnJ~uf9F>yFGQ>#ePrU?Q}hi?X#zjdwbv4a=-tNZg1PW9ryHW zuZsU)=d^C0Ft3`8!UaNL5l<|H z#h3t!qzWeoN=;^i1U43mb3+|$MxW5G;eKNGxqHi?_xf#nYG$dpe9))+ro7`!ik_6C zS|-}h5curxXy~9EOhbpYH1X+PmW#x(-!T7~edgPw;AHrzyqPuV?1D%S;4c-y1 za1Y=<13e&_6e_qNL#S!@AKBQQag|x7kZdnClekO_&hZ^HruBt#ZXj4oOVQ;c{o5Jl zuc${tUMr_pnDkqhipMM+`r6C)-DT26(qhEHClV2a{l{#m(ruM;xKaot2*qfomZ(PK zp*ZN&#H0dPpc-OLsG`9pmx}AF`_!KI+7V{Mg03AqR4K~GG+9*Zc(sXdBGUA25sfA9 z6rB*hRQz`G)f?YDaxGq8U9eb?_SWOuP`Ran?}{h2G_{MSIW7uJS-H8g))7x?oRDm5 zlhU+lx5~FkD)=Bq8KTeQ6*R~*kzyEM2>l8+BsB?r3zNzwZh63ez|^9eYoWN|Z?%k= z@_b_Oe@jI*6E<8fe*EI6Uk!P*Cbu3=;NJDI_}o}cz#9Q_0Zv(k>rGNhQX1mzvdBl8 zr1sg85>IyukaG@QH$Q*9@j6EqPI9k*piJ`#b`e5uCMovBn`fa4wpB~IQ6ADC5 z=jr^ay6^m|5Uv6JLN@mtz#PVQeLsKgq=}V7{^Z6`X}MDDJ^zfy64Es$QFiy@Fm~*L zL`yz(<3Uh}J1L2%QPKp(d`>f-nH^UDo}3c+=`H zmKRN+{MuTX_X=DoYEPfL*Wkkm)PJNAN#$x*LXskdybzEZ0_YjVA<;PWp3{&F^m^#a;kifZ>oo zV_VAL`CCInYa=O6Z~pb2^M88cxM7DF6TJp;xVNWue<@r&bV++1q+cfrbf@FeukD=f zKYu3PFI?bv!hiBuah2HnmVFaz?|;s0;#z2mn1vC|^?lI|ks0HclIM=D^xP4O^s~Nh zIE9dC5@3CZRanP$d~?*1{5I&zRamYTuX+B;FaKjd%4`-^;&^DE3xwiJx45=kC!Y1z zkCTqMX;E(HSzoL{U^h~Pw9NphE?$AKJc!hF;)jo(v&${>B{qtUvHc|wmN_VTWm894-PtFsOc@<_dp`|Z6yS$96Vja`~T#Bv9IHXaTz)%BglJa%>LA$3mPKkw81|dwDibFE-r!*wAwPT=g0gH;7N&KXFXMdvz7%oVeb$c4Do!xfMw?XS76Fjkc34 z0YG@cQ~}8o6jVnukZSc;($0k1rM#3{Fm_}fJYTy^kf@|o$<$j?hXlwK^x6(jMcC) zR&$v4nAI5VTq_^ZXT97&9}{ylAvH(^NeefZ{(9ng;LM_N2kP0W9N1*+aGMBuyNPY! z{NX0HnS&w_41=0*HOJ0GO}K?)DJ%R3lbkIS_xKAJJ@w+}AN2e4jQDO5SKk{RRJGG7 zhOxP*RyJLeBkDw0r;*#bOn~k-Z-*q#Av+;jJ!G39d}Nw+uQQM-UD4w>&Ph}3&Vj*A}}0=)m$>8ujXTOAe);eY%>S}&oMMt z70M~A9Qaze4Y2k6EgZu*QY4IU^8E z*Ri?T*L-ZGOw5O)Bb_WQG!aTCItme!p6OHEL9P|Brc*N33MB8LE)A6k^>LhzRVb7byFuOlaxL>#iO<#k{NlOQ2b!gq?8s%_ zTLY3zp@w*@JseGxreC~Dd}HxPcbxlo!lqeNd^tplAn1b6Q#+CHq#vwuVFo|xmG$@P7tgH= z3_s`Ij!)3%DlE5){SWE)*>hK)mfIiX`po69ON%v}qwHKGhwdmcnVX8t%1!L&;M8XG z`m=8YrHt~ahj)qxZk%<_UBl;^G_q2R0oX_!+4xDNFp>a?nF&I5PDjz+-*<`&ete_# z*=O6$(inMsx+z-Kct!I@;({P4nF?+J8}NKa*rh9e+$9Q|*qh!NQe`&WQO;}}44heJ zVPPm*Ry|sc+?fE?p;Gk8?-p-<&Hvf=ou`w>p@i8i^pHwOA}hD&Eie_-3=3n;urQv- zQdTU@=K*?f(IZDZ{OlFG4BcrvMNPAZZd^)4m>4)Z=yN2CU@g;=!3^Vgl z6|XuTk4StBzJUNnd5l#-^Y>B3-_6zH89(oT+lwz4saOTMk276P@Xr^I@^TF7lgjNe zFl99{RHdu(?v2#)Dc~Lep6sT)P95zPW=-)v@rmFQk1Sf!WYX8zyZC5?vf;1@li)zC z1K~xpJdF*MHINhtWR9td;s6Q?AJG10Cdk^j;vr*e#QtAB`Jcr<5PEZm@tGvTk(3M6 z&X76~n4*C-NY`azAOZ^^s1vJIrGP4NOauO$*?6re!=!%hczXUG!}|`{&NGBH;-)tf zCnSD4+9VB`C5s1+F|B=Ex-uRdoWNANF)^;QC*Xo>#OIcGZaDnMQ*v)dPV$GM5Jbo0 zsX-Vtz|RHQV0isOO$(RN!2sHVU9_$KU;qf@fX0KFhJ@9b&Q~Fad!Lkm$byzGMtET1 zPMeS3_qSf#U&-ozakezxb=66ymJ4yq!jpEC6RvjPL}LfmIWWPr6&~##5TE=0$y2}h z?BSwksU)n#4y3FUnTRx}w61_6g`g{KAuScacq9_x__NcJ5>PsZQYw@y_ZwX*AC;iv ze7KN=4C-1+ga|(>4zB5&xc)$|NxljkX(xcIuwpzRsk9BuSrzPNzSbkBJR+U)L@F`((2cvK=WQI#9~VE|`|FnmPJX)wV%=^`uz(~!B)bO`Ij#Wn zt58Z-<4Oq?s2+d_dqMYH>{$4?c-%(mrfW9tYgY1bfZ!C^Cd>0+GRR9a&8!uaR*uE^DG8KRc2V*3V?poLF{c3&Qh+-E~vN;RK?nXlLadC0@(*p za?hvAY#z@(C(eKD**o4?>&YFjv*;Yer2|Yxu{r}T-=?Sj?nF}U;Gn!fu%4xIP{br? z+`RKJlS6r4oV5N-&#@1mSu(6OYrw_MG)kQm$z00@it6Ci#<(I)S0yz)!s_S4VD@f~ zVcO@H=^tGg?HKgm21Kp2yda)t9Ta_Yb)@)~nOBnlDWH2|p{aZ{qf$v)sG1ZwKWDaI zv7qV~MCaUF#-22jGDmg@?Lh572?cnCk7BG-mCxV63sMA#3^IXwBpP2*a--7nk~s8l zoul?+&nV|IfznrVLW(s8niHn;QLD$}_qM{Unxv4C*ab}zSWT&RKrnpTNh!NZT_>Pf}r5Ua%%pv^azSHMV&4bC%d`XgSS;YYzW$nyW<<*EF0f1fnjJx%Vf zS*65jG6%n6-pdu)aNUo%SMoj;!%V*C^IrLn!>r6ZxWMuJP^sUqp(6kH{nK#Vdm8pX z$MAlk*G@&AZ>g`(du`wLU_8#949~goyv&||a@~vuBc(#u&DxKp!0%pg)3lO)r`qr< zeN4WWEPLmfLc!@;MRC{1xc`&_XGxA{Jd=ONadG**49(xmWU@)wy?jDTDiRJfKz7qO%wGS=0FxN}6>;e<)$Eql?;48I+o((k0(ES8 z-%WIsyE6?uqynC#))DXqrATuEK@dp zC^D&BE*)xQ?+zQ6$J&tyAUFVjl=glI~UASBcqkF_gFDW>^b{V|aVg1LEB zpbnMqSQ{5e)4Ea0%%+<+Gx-w%`BZ@!W_X{w_8sEs>TSgFUu|--`BMgWycKS&Zt+-Lup%HI=xd`K{xlgIUXt*N)d;|78Y%uC%-+ zj-0Ua&<>LF(`T}>94Sy$RDp_hmy0nkRdWHkfZm%-)~$anh^ z0M!P-9~i{uOEj}iu5#ERExr;m5U+_fH8;<5{``%h=xN?vpaMK7DNke?Y7E1xzNc_8 z4K+xc1P`2X_}MC^p$0I({0+!h6Tp}FSWHq?#+pvSx}h^7?q48SHKt+1A{!HF=vBEcj+v?6%|l0jcRh)e#5y8o50gPpZ|CxZn}RWHcFpy1ILxwcB?WWch8C zUkQ3$9DmV|y{&uZ@vOvsAxaxI0z^CL4-tqb%iQq9H%_h@Sw>t?X?a6D=FSP%eL8tX znb}|fSOFJAPDxCsBtr5JD~BYetxXa&Hklw=@Fvle-CoLz${tk z_WNqQd_+XI)`tqVPH%@C2`N&nRs!v{rOR)ue?S%uC?Yb!dSI(=%p|WB&=CyzR8O18 z-1;}gS35@UNe_P23=~1p0UrE%tdZBm4xr!@YfgZw3>O6(piD)T04&oqbbLDE9Cj;l z!hO@2Sy;ojav=UzHL!d7yJ%>4i5WGpaORrLbi#cQkAWcq6)LB8$ZZ-IPoS&dAYZ{z z_G&rCD_yW`iCCOMpoKf`#6bgwtiG%_iRXh`cZbXAcu8I_{;rSs?|U4r2N|@UI$6J- z3p80LDr@fDvQHpJ5^(8jiU9o`5})8y2&nW2ND{LORVr|;0G@CWA`yWa0VZ)V%`gSc z2w5*6V*GvC)2`QVIjCQs9vD8doAo*|EZ%zS@gL4J>nvOVQb4W0czOcP0RsbR7p!pB z27;_~0)QW=5O0AC%l?M2(+>4TAT}>OQ>jPwSi=+~w>tX!@4x@P%RL+VRg^dot`jf3 z>w`)ADJ${1QHqDQHu{5z+am#t2?98JX{H=S7RZwN|iJgJCKY(5Q3U}>%Z zE&;UJsig_CRaia}E92kpyT?c}OS;kcP&s9lFaVpCG#3z|(t^3z+inVt-z->g832^- z{1*+tGZ*C(@s88hAF#()Cz`ff6T4}YJN|0KwE|jwugc_}I2E zV=644i5q9GIJNIy1i-p1r^MPI3!u}$LV~iSPznveN|NAT^zf)-c4B=|CIDhc|t8ZL%3<*0b43`yVo|@fA z{*IM2$_n%%(rS2Cj^anQTJb}JhBjP2^0*lNirNq46Tv^$09SdhG93lDh2-P zs!gjPdE>DZ$n?ZS2t>|@09!o&l_h}U-*af>A4TUJw)LG=SDNOaa^Rep(G|@R2&Eh~ zb3;L?9Ri;~Fo5)X07w#NEn!e!q|``r6q45cQ=5?ybe0u#`Q@-J;>TBy`qbWbi)ni; z0RGP*-e!ViP}PzeB~6h<70+?JU1CI1%M&DVrUglyP{gR(T>Ta!P5x17cG^FQmRIh6 z?6HX}O=88ur%k1GVH;`~C{)#)=-i8d z0{|%5>q_#W5U27`y3fiDdhY5q$7lWS8*d@}-Ces?=%(qFrh&&+QFx5{<$+tGCT$K9 zvS@!ZMMe2UGpPb(R|Isj27~6;J;5)nWXv!wERV zP&c;CbUB@}8ko-3G2uJMe|gw~E9fDkcZ4DM;zf2+`&>lLGJD*zRRXIrDuKXToL3~K z6S=-OCzvdcI~)WUl>=EDCjf~2KJRR;9#SqtV5@lH!7Da?Vp&mk|DdM_P#iRLE#Cze zWbIt=OMp}~?le5G)2P-9>3g@?8Mc?@H&MB*z3ZI5p$h$stEl7}8vNvW(nPi)kX|8S zKu#Rst^@DKV;Ua`N+C2ehj7&Nnm4X$X^<^8QBVIKoMS_|hiq$RDr@qV&(I#@N^EK* zkxCz7RdsxNf^7&e2%C~;)({B zd{QlB3E(xvrJIK`Z`(%9OXHVx-|3~`Z3VMe{B}C+&oK4_IlHTSZ}VysZ}T&QeQ(T` ze=hc6{7qc`T0=|ILHn42$FjvO8vVeXHD981EK^}p__T!1yJvUqp!?4*y6_1UPJ?h- zf~BS;~Gslp=)&7kEPi_Bn0&=oBFF z{20Rr8h$y<6G;k92QoYzTrq|E<7IhtIMW^-#tG+@qj&B&>zArw+YWi@9}31pKiqfi zjfU1x)^%oiAYD06QV{TDr0D^nsV2ZdGg8G1h;zHc;sE7+u+6Sv>%9~HF;&EW_LHHm2G&) zK|X#6@2Y1A^$>Rbw(#-7iwEeKs4#{EX~1hMJU+X#bVBYj3_XNlcSROBR;(u5D_WE( zP@@;2i$$Abssg(h(O=e@%dBQ!eM2Y|0jYVw=XA$|YT5&l3{FJnOWBBhHZo+h5aXC~ zr?Rx|Ck>MpcX;To9>zn>=sy%f(o1+bbWzVOmZSCaBMA_M^xA$Iz-$%tB^EEfy?4J> zA&^2()Uyim?R!F}j83 zRBz@iYZ}5^5DL@dtPVy(ZexPyxUGZM&wK5OA^#Rh74;-}gs{hd^SbkP8TDLs6MT@h zBA9{rfDvZ~A=ZMVNv>FFQxeGepuzeGfoUIKxcBi;?m?)Q_=~COL5&TKd2MlnMo4dP zV9md;aM5p-BkDI)>pUst??Y$Fi>uYRnIfdMht`i@5ZuV`jo?fQ9H+BninEYluF=h& z^%GXe7l7BcoS`!Yl*s;=5QxtdC988RtdL_Eh{ch4g8M?@HA@O7)r7H;|Nr;n_FXUc z%mqGMq?%U;2rsX;%=*``#k#OIgg_u50j-`#78n6TVFbl~L7<=n#)f1uJW@P~LdGZx zlMzLTC1uGZCk3tqLc>uo!BvejeKdXmF`RRLH2<^guZ0~C)Z!c<437Pd_pQ>%9P+rNu+Cpx~NlZq_KLd&q*oLbxS>|iNJC9P3ke={VBr@ zZI{YFTKI?`WBzbilU@>sLG)6M{s>_mElE+yk}6IXhEwTD9K+yo1m;f@T6_969=6g*X>_%&tpbu*kVebw_n6?m*SXdNS*Z@l&XNFfEZUXTx<6=xK3 z0JPrMP&4|`_U?{d{>Hmh*C-4YR!_4GaD3+_d(KgXdW(4|Vibot!<2Tbf*l%Lw?rvC zThb6Fg&}*Aub7Xl8hqBp#{N&Y?{v`Y{vpDpCww)a^M*~Du$}(M1IyLoQWEleMg1|s zEyjS>cFK?#)>cbHPA~$1BZ(s}TaX9CO-*#v ztFpu26*(+03=@`wZVY@ngG!2T$t&dBg5FUUjs`oIw+K&-9&k*n$?vgzxDWbIuqzALa3(q^jjwBmN}?GMGTRlAn904%1JFw zRbWaPiQ>4&rae3n&I;7&XYT$7s(843;bX}1yyqu?hJy9rQ*_E%&%cGv`+0B)%Jv*y ze=1r(HP?#Prjp&N_<>C`8x)X@eG7(5Hnw`eY1i4%=LU^<@dWO<#l{MQ=Kk{6{kCt&p86KAR+04_Ni50=KSV{w>Ky)9 zJO;U)6>uzyjS%svya1)`>mq<u&M7<0W}E{myH>J@P#?N18FKU9!? zXtxTZLQfCv+i8kU&X+A23t~VLgEcV`_B6;|O&(N=D5;p7;`7;w|C603o5%y~KI7T_ z*O&j<>F=`Ztinx`eky$QsxmzweP(*p!4h7m3?j-51gSR7UR8miwA0wLbY|0H;gVZ3&S5d z`O;N?m~{GMNvdfwR^%keAee-ah6*u;tbQ9j0ac#m&Rbd{oIS03tvraTuk6tLsLk!h z!ybcO5RB9!>Do-qW*qIbDu5|iou$Egrm6q!O$8KCE^DrgZfn)Bg)Yv0PX#Zfw9j|7MXuXQIAMH{%*72t$;=qGHdmc3r9I=D(@)*( z{jN*C*o?gcvk#Gvf1xIMcviFhi_YhK1%HoS{pa6gzhBkWebrOeQv=*L2KwLVY8hL_4mXn+> zX#?jr_#=EW?g~QyE))QDg}F*!F%RSDae<(2i8v`bz#4fc3E`uI0y;_$c_c9kcnKgW z(L_8V2AlBL6{jzrS`~DxCd_6mlLq^e^g#?!k48P{8}RKDwL%MzNBQW4w{P+s$U{F< zSn_xe^AraqTGEz&F-E%_rDeJnn7b$H25`bx%>Z7dHak8nzrJ^n>(>#6{{N$%w^0!f z8IZ&I1%^^#O!b1R-hAm?O-d8dR#|!S{$Y*^#@ZAya}|C(oC|Fg3am2_Q_jmE7>@~D z%5<$Z^0R7YurKy^r=*U$-*IfSm;M_ugDi8LI-S=k7;bv|&!z9)qvv{MbueiXfM-jT zes!4y00cw8qjK|-mx#iz7@p`$`eTA$66jgjzl3@0wEt;zdn%~d3}nTCXJNsT+(Q>m zq1~=z`^lF!7;=uerYC7!@iWn8^++I&fAM>nFygJ3x_0~QRy{K) z1GdhXkYS_-ud9@N-C@SsDj<4*z|aFe3nEx!&0)J)xy${eNF)R#Jt7{yY8s|?6vOI z8rW49eH%EVu|`jQlMMTWl9b6-;g_eUNVK3bH&e`4q?n3eDk&y#(J{{ye^MH`xUl=b zIWAdfa0~9sdL6eq_=+A_q2)q+m2z7K83AgeDqI!B9V_3HJIoM~u)GV|ZK;}68!Ah6 z@*HE8>m@NZo%hECJ{SQo+tA_h>NkHlfSK4W?Amgs_wJuh%S@<}IHuD+7Ft?qfQk(X zdDVhoRf4amR{uNmSwAMIme#8^J@2}Z@p5ua{Ar3{Bp(? zy`Wf2)j!Gs6umXr#|dmJ?|L3fs-DLJWr5)>vt}&lSw)BzT(M*vkEbs0cpRHWpGw&n zK06=as%Tx6UXn|{n|p+Be_ip%p0ieF7&pZ2u-jq9f^3)NhA0qRUKqfFWWRhm)|C!w zm9V&-aGe-B@L_Q z<(>vtx$$L^KMJ&dyqOKb`tC{qgz9)H4JiQuN0_SXK{b#_ax^U}mZfmzxU!Re2`t^z zzK_mLJlF>}`SY(*1NbJ9?LM3EacVGbCFeh=S+5}!FWu&ErzHbD zk!xKD$fiZvk_7f`rd@+WE64vvzn9jf4<*|Zg=>HN=jzvf?_SXMc~zo%uCe%Sb~{a~ zLh1|pH*)`EVj!{>yWJCq{O7PDfs%ZpGq6LYdl2`R%p z*#>D6y~r=|J5}g%3UioezuTZjmZhw3*60Paq74@PNn!$GPD_-;8C3~Ky>(}5mZHIeRuY3z zb*-p34+^6kBbqiY9Hlceu*OuV)r~ARL?{bMJfn$*Bq+!6#pbfp`oAp*cl8?l;rc=I z=~HMip+GV8WbKgvq$$#gOrQ!Sj}(u_VV^{!o*xTILe)*g>g>UcBPFUc?RYwP=fp34 zKOJW4L4Bq%OGxN{vHE`ENAy!|@6itA9q|+~XUh-L))4>9RV1kzUU2O-3)rKE^d)j- zukL*jT#zjx73UCO3)n*A01y@}nob?Iacy6Erqd1?0M3#B>d z(ce4f=W8B0_vw2drN8GnZ^0;-z-?MyOFOmFegfpr&by$ZTw{!=)S*5u%1Ne} zPMi*?mJP#(G^@a=9<7hfN`S>S|1eohfRz!>pdK0I2`T0|ci4!C6|G6BiKpa>h{zYQ zOe03nvAoOUD?bqbt-v4&*W19(oyQ)hGfrWXz`>#-a}`0Sc078gqn_8?B!u?e+jH}Y z!*ny~Fb^eEpa2GA0g_)vh$ zDJu`{hV^2Mrt4heykRu-ai{=-((|N=dC<2tL)>HL5h##<Gwaa}i zw4V!wN6){f;DU;yb^07C%{Pg1t*55a;}~Clmh-IKtMe}uE*LfCl}j3j>vd>LsR%<) zpbkjby9m3(7KXv2T7HhhMZ)L(M*RM-`>)T2`%*{G6~vj0o3Lhz&~-w*b?$gDO{K}D z-z==IqTv7>+l?ohT#95HWKoL2oOeVaB``kC;Og<2`Diuol+SkTl91iW~)p6+gzz%JP5PdS6Su5jJ33#9q$jMY$C(l}RKL)3ezz<DgS=&>WjXR5uO;>01luY(z z%8yKfoSDHq57MN9RszQ%hVv4B-TFNj-L`avp@+^D)py1d{Or!hHGAbV9>e0wW_R}E zQ)1n6y7PrXzlo8Ddj90p%MoNGHdrGr;lM?^RSDi#UFAjKX=o@x)pU~J2!B|+?4y5v zv8wZdlT}?NblKq_J?iasnr4(o6+N0pNOhl_LSHcuG(q+f0wuQ?K==q0u0Y@!l|h9d z(@;FesB{1&6>bI5cwQmdS%=UVfu&n>R&vd=rsWY-8Dr2h;SpZ0| zNmX}`HjyGuMV-V2JaB3Sk`$Maf1T@?f0Zz>m!Z#(Gs-eUMWoMofnS#6LD8QCa13J^ z#SU&J)s(|+M=M%(sVKY}UR&JJO1}JU^2d-`y#AQ%Us|JHk0EOs>YcP3N1m@E`yV)G zpz8ZtidNF&v_Jho0@=&R_h>(V>p26ne$O5PLixS=sAi8-)Nl0X=C6zzAQaNMPGf*v z-GXn?(s6n{^f}ROx}M!zy{6B9dUf`=Zh@vQsh7@u^h!PVs|EKvt6!h;%ISJRB9+1q zkv~@GBtc9*!$3?HC4lgm@}}P|FkCB~Gw!)Qmu;EWeC|NsRk1kYQrr;%{#ZjI7n44l z-K8h2SYWtL=-X*e-(Lruk`Z)Z5TiTFxVz)Q=CV#pV{gO{ZWZqhLS=_0%ZhndW`;&u zC$p%KNQ5FgL6|k<(-Md(k)4(_6LCz$Q_b%UJ?(@7XY{)7eHSMQr?127)tdXesj?{R zHviZ>okY|k;cVZOG2S7!W;2M=zEGt?2ZxZhs5#zI&&o?&=Y356kx&<=(w zjMUM=m__(`C+|TV1QbT+H#5l?>-}=AU$RSed5{s^D3tquopQsUMxEi5^(RYe{SpRN zIaoX^XBL09g8%m>Veb3=s_*@_PVbkCeKSPZ5s_eKJcKcTl(5+-0a&X6X}vH=Abm1p zg?m%nm6Y6tF~Z&Dh)9IvIPH)R3R=40TZDn$7k-*sQrps7u}ySTm6Z{Yb~$F#z!cw^ z2Vgk@Q=Fzah2VWe7?yi$SuS+A)$tk^e7B?pl5sw5jbpAq%m<6?MoL&s za3tEN*}6m!pVoP$O2^DLA*EDuEq zsx&-3)esilijilfFzcx+UEB80&~a~B3oey_>~2+|kPMO>q}+z12`rMtASH$Hg04%K z$&rKFrsCTZNttUqYV+HKF3~eOj2U^d9%@qq!8G!&2$9ExU`tdM^~u>Ylr#F_+wo7B zM&k8X@_h85m$c9RbZ--O9RGU1djfMbCS5-47GrBGePV374l_6EDj7r;$i|KU!Bg(y z8o6Ro=3t!&7WMR5_-6)(&$FuTEUS#5JkesPU zibGCyICqIbG3f*$Ij69HtHANAgw2jC2gQai(u?+J`RY_|Opa`I7RV6o1u%{BpFveu zXt+lh(*44tM$Mt}v#WE3{81?oSZ9MuZ|R=Y4juj*#!OBmG$GKm5;trD(cbKHOl@dfH2P{EUV7N~x zo3VRKyCPTS1V}tpBP7zUcEK*ouyr8m@CWMw9OH3fGK)?SG)RN>05fy$-@m_V;XR)A zxzA#2gwmzsYhT}3p9!T@@sPKsY92d@X;v#7t!9ieNRm=IuCethe-8^!6ux@KL$}Y> z7_9ls5x9Ct0REVJ&E64`#4@7M#h>j;kP}SeBqBQUSE8^-qTqx zTyWo#F2eP98*+s%V$mD4B9Z3Qv&bpeoPiLM#JV~lIV8VauN2E^Y5K`KN&c8%8++af zJ$m1%lO#v+>SFjm$g``)e3YaRSdO?Pfe5tDEFLKy0IB34itn(<+NSnbHklP$yFm}83sG|F3WhTeR z9}J2T7zJ~RSl%>AOUYGw4{HO9*SDp#=>}o&-aGHUq30<&qZh-XbTGMa264nC5)lBg zCUU|bB!tUzE}Y627UVhg7Z^4Qp2(d0e>h@_UhE+?cJd~ZUG~SE0J7{N#@!kj*h5Jm zVT3R2Pnp9dn>k$O?{qyOT=c@>CqDDZk(qvdDmZ3?^!%<$pN|V)zxdX2SP}8>a^njO z&j>&M)U)R35jH)fo3X-ylTScmO)$weL5w-dTs5&7Voa!IeM!HBTqKLH0uuT!Zy0{j z*TG{ezoKlX1;$jQY;_?QI8Dav6;5?v*cuf&yD_>@!{z)aErqZ7w`59`oqYuxNn zuk(FIFA2*od;9Ik3Yrx7z70zhGKjJv@1|H6D~rVaP$rKtHlXH;$_l($=r*O-2WGpn%ue-_ ztU3@WgBRQo3lfHj610JMrvn=X-Qn4|Yf;hhhssLwoUgVCTSvuSp15pgrYoWaDk86h zVSUM}WId2PA_#(1niZAxO8_Af2{nj|>L0cQN`#S0IN2aX>4`r)I1MJ9JGSwiZWI2s zZR5>be?32E?^09eN@ftGu0R{7sRWLVB7%a!b3DjUMoj4tAr@?c-eXlP2h`cVgmt3_8jkF_s5S8>Bv$HkaJ2_7DWeQKmF66FYS&G#RlX@CtG63Q zzrF7OqUn`j#@O1*++{AuXK)5;tO+arMwDYqI+}tJfGGr9Mb%K8dWgl4H`M3ITApH7 zl&3f{%2V2UGCA$-zL zj}jE&$LR5u@}Mz86edEwnbT}(q04XRL2P36^8h%o1>8t8tiAPjhg`y z9de3|qCRkSMb#@0w7;yb=*a&O%SlP3HZX_t3k~lJTkd%7ggef=G9SdGx+02A(IsS7 zc$wUOIq7YH6|KSPVyrGzZFq1ekbte1*Ad^-6Uov|e?0hk(4@~LAhG<+%y+HPF?=InEI%>p9^m&5Uh`s&Jp&CCi+ zm}SX7Gn?xre=5BAV(tCM&MMX!pR#1$85hc{II^CJ5CDJ3aVJ^%pw$vdXBM=ZcF#`OJK)?=%N;@z!!8j(15m}JM zaC~p?dFRIm-Fe2C)Ult0AFHqGaoba|oWm{}@m>tMg14ZDEh>QwtXeM#dxT%l z9QMq!cSLmn5!qx*b(ko^m5c=vHKIx%OciBAuzi#y!>CUV!sum)t$<&J^Inb78(u!C z9@hP|b?C93VXttX`_JwljkrbApIUBJUQkyf`|D5#aKs}Kbgw!LH^o^`#;u4oN)559 zSO&=%eZ1NO7k&NZY23Z;ZmqR#_X^KWvwU#RQFN5btRPO6&_Z!RSV$WXlR>|nBpUam zyu+IrRSMI{!V^!va9jQ7Rl3!~f44cdSJ>2l+fRSpMl1BnwH2UN6gVV|$SCSi5{gFz zLOB3?4ubJ|l{{gN4$G&Gc>cETl=YluVb?u}T_Zg*?@!74X3hGHAJkN-kCN|MPu8^Y zpOOWiqg*UH`ybI<7p;FBpt=4RHT&sAI4TQXLAhVMoS@mJ*R4n7uKk`4Eo|$3Pltol zm|oOu*Xo>Ckay$U@b;M5J%-HFGxKaT35Uv#kO9wPHWe?ZgC;T3R8lnK@RBr!Yarg3%zzu5!Qv+Xz<- zB^~?{u1YiVjE%q*6lcQn!-og#{j+`ET^-Et@W{HU({x*v38_>Ar*kZPhz^#k@)=vf zfY=Uv`@|_fS#9U!?i}Vuu3?fPC1|oTUK0~QC_^;%qkYvETDm;bgFctzbk><~s{>el zXWy63d_m4VvweV+uSUDw32+*}%v0e{*26(GpQu9z(C)bXXpt=1=iou>%$8(U9L2e zCnYiNLvs)E)>TOb{A`dOWv;j#tkq-ZeM#0Bic2&6l91L5O#16)j<|W~T?_WN>-KLl zQ3^CfTs^^_MH}~>-tI=Soj7=TW|K+5Y>k5va8~e%P*N5XWw!`P?N)i(!^y7T6dI#*9?L+9VWf^z(GaQhO~9!F%DqB(yGK0n z=rzZGIIZ*Wv}K2uY3;JFuhg7(+I{M6y``x~=VrfGZ<#`lZy}-jDLI}`*Ym6Ldvn^) z=er!=6W^yxC$-QMOnhyDeeuN0T7&XYaZR^L#p1ybs3|b?0;?8%vTN=`OLe9YRyl=K zqhv!8IO5T0JcgNdx$;zm)0FD6uyJ#ZoQWlZ7G-e>X)f7g!dQ^B3xY(7mvLr3%TJ(3 z>SKALq{n$+Ue;5MQt;6WhPDeIqeu!YtjYCu4+EPwzx8f3T7M91Ks`)->!j5Cf+gZR zi*7jm39@HFi>)uKq!&}`LGyYk;Xx@ucbTFY74KjpHAObMcc0tV{rL6Au*vaM?Z^XFZjiG)Z;akQONTXw9oa|}#mhK1EcgC@>hMpWI!2*4H5oVyH~9!R8-F@l&N`eV=@;h2J7w$V#72VIm5tx;zI1Uu z81S^Q9lsaId|8EkVr5N*N<;Y4j>ERU(BECq>7Y%r7SQ3{ zMPEF4;aM8{BApB}Q+N+SHI*PTg{nldRD!l&X_E_AlE>=cX0{ZT*W+XOL7yJC7@CI% z+itLcH~KVuGwbC`^fE29teo&GF^msH#Y%@AlMy0HT&4e%irJ{*GlW=zvHFuqf3Ut1 z%z%Iph!BQl`?#}`-+?a@$H<_^c;~`C$1C$PH;@@;Imz$+s@rp|VCwv#Teo++yFhCW zA-ZSYFN7Q9Bwr7p`IF7YjX;T{^rUFv`FKKREIcd#fovTKG&#B^quHZ{r)13fn0yRn z+^|XwF=v?%p=u^q`bv4$Mg;Y0ViNEN>mhPC^#wA*KzrbMjHMJdFFbH#nRPVy8M8f5 zhUYMslYY1YW^`OA*i{4XA7+JkjtfKeuWBP7#`<_#DKVp-mYA^xF)`JTph$=Z{;OvA zYL>s}jr;#`+LTr84qPr{6sT`FcguC>P0~xzHRm1^j48GeUGA{eOjzPLrUceJa}f{) zv7}rUm&QW01x$+V_^MCkx;9bzI0JnCXuIB@4;J+xE~(&*k!MVScsx=iQR1cS+)NJE zHO_7_^FjRHa44o+6a)}MXPX5e!*UA_D@f*Ep>7A#Nu8>Ge6mh2tDs~DCTzwjK@mJ< z7Nh@HqhfS(jSQp`MAS|uNFx$pk4^X20r)XlMSUDT-dpy)vgG`#&oMIkb23uU^{UPO zg6v~^Dd_Ue#$N``9#5T%b{RoFwLO8VdQnQCEH2f=rpMwlVrem0GS@TEJ|PFWs_Bnk ze%-&`>OpP${#Do%`K5z;d)h~8qpOi=mv#Lct|>9>%yXJJipvil(M7X;{yo22X!yUq{IA@!6y-Q4P%Rw5~Kq!k4_im$Jr-wE%pVnlUJ!`~y&we6g=tqN=M-KUIPS80KOa5xat%sczuD1P z$w&oCmpq}SRAj;|lRY$p3hft!pCw+@M~s7>ge`Erd9aY+qNVei7fbP%-ecCa9_mT^4BmQ|1uqbrl%Kg0l55 z9DC9|@k~RTc*jgs<^>S-$Lb=0H6tn|AlVy|^=CEsaodCLiZ$MlOf-;2W)+kEnAi|a zeU4$P(BaaCPzdpy!RQ^{RTlBrwVkX$fx!hrQJxnzf&OJ}U_y1!iqO<(e8wtMG1gp33A@9cT^l5Oxc~~yAyc&%3xjm^33ZG65kJ)SK*$g{Dzka%ZDfzwCOV!Vb zUWfL%b^AuW-Sr#wT5qBL^1DZIuAKYv#Aq~XwVXgRbTS`ID$mzRXXH$3}q3GjKqUgI*$ z_$R6jYR@f=TeVC80lO@NXdu$0X0Bw+=r30c&S$b1RAHNOMplWD=H9*S^oMr!aCaW? zKmHc`W%2Tn3kjaBcxqhl&QzF3OU}}g1-vgs?B^*x%VR;n zb{AA|fXFSB_hM3i`QnjAOc=s(B?#((eNj?(7v`de9f%mQ7fe36Uyoj^*J^SL{tMX_#dY3d6Ja+ZMj+-x|5bKsX1XAHL2l+KMx_AlDavHrR3JsVn479iq4-+QayeYzMDT;7JQtxxNOcCzH6yfD6S%fHgTLRTp$aK!?zx!Ib1A{k#d*NbrN|HjAN}>^wsmWCHQpH$ zoLT%y9>;rK=36WMgY`UXIMy~?n_1y;+|`~b73F3Ed*-|3Q?3n6lchsuHSD$geW>m8Nb}oL8H!a88Uy+b2 z-Tw2H?0Mfsz@*_4KDtp%ue3TQ*a5RfZtGIPSEMO$oVJ5nm{r zEQy)glt8&Pj|2q7VGegtb_T7oIVzA1ANI7zxVm5Go?ZWTBP&= zG-LDuAe1&hXar#&X{BDoba2x;cEKedPcBfWjdLb;t!hX@uZ{+uk-NmB;W!HAI9p~P zNis%UP{t>cRHDchC*R6g$@dGXk=B=F%mT)?@B*p~$8|A^vKku?i)D!2P{PCfpFZ$c zTNbamuVQj3aI`k1#(%HR0Shk1@=UQJds=~jHJmrh!Bh^%an(k?n)i9>zq24H*6-bR zc8AlR>eA&Pw=3m1+)32~qo7Ccn~wec!jLXrqlHKSV{(jzEI1<6Q9DpscYW&H7}&6D z<^187uWYU9r7Ct*lyVcR85Z`os{Fh-WqGAUf0S8#XU@t=yL9?x3=BR#`EdWL<8<6H zmc`tAjxrZ&e%R0hQtnT;z;G@YHekxG0sJ)?s0d$@P4{7#iRY7XJ|s$Vvc=w ztr1$)4C!xB=@0dMl$NKm5xT>O7`<8GxsBH{{k42UGB9q8Uh(QHWtlSvF~*Bigy%GT z%32bL&(a5lm1AB|R~14^d_)QhO@=-OlILs7H*$Z_4Pfx-A!7#~U9HoDkrW;4s$z@@ zDGmwD$AcyW*RYg>nI<;70=A~w(_4+ztP6XdJa_J=QpdtP#>GO_xHto>>*(T3KBP>P zcjbgz(BjFh(0_M12|tC2zt(xf%kMsOP8VuVJW1fsWi4E1)2YpG>n*knLJ54A6m5DD zQ6e~&T7(_w{m%rR4J*EUZKXq}TbPVa(O(yf%SjQT`IYRauy@$(Qe6XV%??&$jfUDU zs2J|dt7fkW>{<0w!6ykldZXt0sM?~?LW44i#Tg@p{5dNdq7)gVV`ZSOiy1ki_lidz z_j}h5I<_8-az2ol&raFupq!5@6k~7+&qHS(dbOVMv`&&j5i=^9SUifxKpjN7dddQ1 zpsj|plg|OiQD2g^LK?q#l=maG02RC2C%<$zcX%n-v$gDQeG1j4MdS+%Bf!Qv zv2%`ngMQ!n;LB~#Kl$lePiA`<-^wDIgTiA{WS9enFTdf2XS>~>>E>pYjy6}g&@| z)9$dvhMP21i?$K9=v`xKo~`Y7NmuGzchbDpb{e~)pp}nn4zNA2<<((S^YMd-S2TGS`eZ2POAFrMFarp6~zaY|nTytSgI|_4-!(q-!e&gp~ z4lLc&q3gloT+EDaTmXI^74P}SgXd|Y8#UK>e@_ov<21H(u1zrT41;cbX&7=>gt9uIy2UVF+TFtL1~`LII6C1A)S=e)>U_h}~K7Grk`5Qp;bEK1?JKzwIwF0xfqLWI9={|5O!@zgop`JhHS*U@!$?8?NX8QJ?fOB*qVRsHXh5d znWu}92-o=en-wdS?De^iU6+9;UhZyio%yB?xG2{e8@AS?Wc$txpqSfi#4kqfl^^q* zp>MUOzEweDmihoLM1k{xr`mk$#(P}tIvUhAS0W3ZF57mmjBdaPrsd_$*XnF?1%Erx1fM5~`uFN_>H1z@ro6Xq1g;`1na)DZY zd?@Cc>}JeReFJw{SX79$Rq&Ohz71*9`b==twebv*@j!~%NJVBm+|82R?W%&Axq*2M$P57S4QCO4V@>M_{4UX3S zsly8B(@p@(R^Z){U|+yDo`)k;Jen{Wt13Lsrr201-jGO0VxvC^%E6|mF@62!oWqr>m6N z)>udG*sN>8=k5Qvgm$l*SfRu_vWEL2kWTTTnje)7TxxG z_$so!Bj_au38DcDz={gkW-yDP?klAlZjOey82yEOR@kG_K&nu3 zhm%RN1WUNW!0yCYQkG0|Qs7ENnV0=hz&E<8u^dMuXHLRNp~`26Q^7ym0)w*%%shSf zzG=Vh&3Pcs+Pj?gv6LUJC9jQga+&EoOi12f1U6%|W)xq{FepIcjVEzJ%9zIXL$X7} z*Fd<<19;wS)^~H{=)0Yfx8-{i*#6J&PJa19lOA-|oazVg`N&wKt`u4yO=7Gr-AOW= z{2Gs*_VLYNocYGyZ*@6IZ#*s*<*FE{lB#0B2U80?2iV-x(0tUT`&bAFvs$e04_8v1}~0Y+3xpU54LI`ODlXT|E_3= zIK@UUh&F3kD-6=c44WDG8Cn5RThc!rxZ`y!>=)qxYS1hPHzK*-@M2JQ=lbF5am2od5m z!Z1QC2^9>;o7Kt}?<$n0{y@=#K!QvLPA(lFTp!$&Laj(Wt1^$ZL$? zQTOJnF~G8C=Ycv8ZW-u($_UQ@B77S~MDIv;z`-gr zMTn&iEDEz3z2#`GPiYw_ADEak`r{EgP0(b<7#;#=^u*<4iYJwh4O|{3`f^bI?XEi< zFLW%({(BnrABE|YEanlQhcQPW$F>^dqD0(grq@WPXBjmqu_GP}f(vpDy@*=&fr0z^|4iiEM5 zqE*c{h_uEO8_jhX8Z)K<^#3dTP1W@Xq?8dE*uQ`OoC~M&g#~#Uq$|LR{hvSev|OSW`ZoG}c0y>) z7+tByrRJSfAWiQ7=?;&wh;Hy{*uoh|2AeBS@6^K7u@Y=q{8Rb4Wv9_+)LwH*B^u`; zyMlnSi`fnD{WRdQE$yRilrnmXJwNM%u5Nlswf*QLtJ^eHRldA!zwgkJ-}3OfL(ji8 z%esOz)UOjBfe_czo_l)KEbeQ$^ET417nEFl{wdw(T>R%l7zMSdXziNqFDJ>#j7b?@JJ650NA>ebtdW{H;!BirkD2!XlZ zKewN5duDwz?Cfuj_YNi7<-tL<*Ti05M7J+K`TC{7i#G*MB-S6o-y5d>7{qT`Zdv1L0lo=K37q@#^v+~TS&)x2LS!F& z-YUD=CCY26HFch^-07pvwI*!IuERhKN5YHj21tlBF;( zw>TZu_*6@NF{w!H*)%^#(uVMN2!P9j4T-pfQobCu9Lq8Qya#*fmqif{zp8t{vkxr$ zcKN=0sr6K#HBFRZI+A{4&0JJWLR*Bwb1Bi9O6S6pJ?)GwZdY2ji}=ZkL^SLptU22c zj2EFc!gyuiX?fl9mnCPXZX*uOO8?eHze@97Fmp+7@YRkl$ex?>jhiV#-y?~Zs?QHC z_gM;hvJxSlK9`*3$q?$&ew>!Qt8rmie1c7fuvnDFZ3+M+a0*4RSl$Ll1*Gm0@LBml z7^Q}bHz}qkA+5JD@)hMAhMTYypu>GC3n}D2&r^~|3%9v2}3f?yi zxwA&h$LbWLAO?G~ObL-&!_L}Yc=5H_zx>syLmS?#G8TOw`1HyPZry&-XkA>eO8?Yc z2a?1sL+OQZHtkbwF7nx;=I6%yfbI21{}^bVpjU#x_>%tMbjW7nh)3dx8pAKk&=n+T z18thg+gzUM`ggpY5`P}mrLfgq_kOTs%8@^AI6K^$xS~SA2~NXYL_|U*DfSB5G3AB; z@8We%%Ux*V0kCRpWh`V{lL;8gijprz7*|-i2_s`XPSP4t{UwQ&xSSX`OR(~EGE$-H zvjFHBvnccaSQ9@BNO3-d!E;6$>Miq7*)b)|HU-6n_}aR@$GraDl;MVsZETtq8PqvKE|u*s*8qwS{z&E-YK&K>P}BF$NIzQ91CV(6bDZ-gq48-f1FOu;xxhUd)>+ zoR*Rbrv(QCQCbMe0R3*=@(pZwZI?EA1oS)phXGxNUZe|+G;4|ot>L(s!eV;-Qc{Ms z4!Rs{%3;}~G7Q7$MzKOQu02#dgm6ZT^PW3r>fFPlVC%gs+yUDq3e!BE;@dd00p_juSKe2mc zW`H(bNlRQ`=LEaAY?8ggZHBZRC`b;=njqqP@#5Qiw+=eC5u7t~=55oTkLhGBvL<q06*mMK!s+x6p7H*1HJB&CmQ%az94d|0>#-%AB*i7F^H$6`&^FHV zrVb(D2hJqQcFr?C5B%BFVEzT6@l$))^wO*W<>JLh)n zbI+4k&?7DLc(+)IpkWiK9Ip*PD9)$n!q2gcqaFmO6LTd+HNu5w&OUqe=Gm^IqyLXO znd#T{9AGPM8y>pzDP42EMR>S2}8}2oQba7OV9Fs&YMBbLDz<3EU8` zn$<&Rgnb?iIDGw+@uExg3}4n*;qb_yL5M?XTYyz=B^J6kt{9{uA?8gPN@WK~7zcg| zpDHLmtcQB0gpDf80|J`^PkwRv&5v}sxKmqOv(JMkw!C`DXIF%DrsZrGX-PIDBEU7y zlOaV3Z`f4pu}31l1~kRQB{>b+hpBsvI`R9U!b~yG_XvF#U$P;k(>`EGZdV`C1pUg% z_K=)g{tIBkbL}Qxmts~_>9kt}0F~4!nV(NJk+U`=^o;1QctCg}cz==)LC8NB%~z-KLOonHcT_jQ@O=#HNZ>UNP=3IHVh5~QSojx$D#L44O(&ZBbgto}%Z zkB86*Z%#c0#QTbdcqGBaWk5q&tN!;hMhV0v0Z3yx4~|Y3j|CJ{??L1^r1~Rk6C!?K z0Q2m9@=Nzs|K6AJGFbQ2hO75hT<~`RuuUer-3bsE3{<>?UMfY0r2_hS*k(nBS3&RN z-))qdj?uMAoq;+i5H1NNh_6iov1IN)@G4j~@|IhN$wFpWj!dAMBEq!9yrZhDjAfya z5Ozt+;?lT=XaafW*f&j^^+etae-#Pjao->7-*zAZG_QhD^F~D1&iXEA0Jetw4jG(u zD}@(J2Y6bR&tpvZV@aY|#-Rb6HD>!x2g6~cHDe`*q2e>TB91c;;Tdc5sO!>$R54p* zcnzGmc<;Dz?FQ>aUNAi~WGQxgU6tvm!nL#fZ$Ulcwu+g&25uQpTyfLHew2QpFwYlC z))g)f00~IIU6n|-@G6y&5o*bW!yFe4d!sPnnOWu4vR1 z6&>WJLGA8r^UlNYQbxmBDtBFLzut?GjC%qEBf)`_r@#(NLDVsu7w2GXy z=CAz|2JQA1jXRWrJGA@_^XY4#_iwugp7rzEoE^_JPB!exO0uAKH^a?2n_A;xD0$UG|o%r z#*Fk<#tc6HaGfjL23Ad7c9iw6$(gtwU(&?avNoQVQ0Y&~8(~at|EwlxnIMq`lh5J6 zR<@P?Yu3ts*gX9BP4SZpj`%NWz)BQ|@{Jc7UI$C2T+#K-J^`{P(V`83sLtT)9f@Mb z`2Qq|pi?+&Mdwrn!md~ah{mNR7*_7L@5mn)E@8WO`I|cL>tIaf&~HBd`smDjH)+1O zZ_0K%4za?EJn_Sehneobw}{#nA9lzdL!F|rGlyx?s`T#-zXi@|=)HVD{d|GpZBYIz z6JI^8CNoG+x|Ko>0m;i_-Ac&huNX{FAR{}Gi&{USRmkcB!#m)`)0ZCG@bfU8;4Evy z5V=wb_@_H0F$s+TPN)NNb~$MMappbHt?;t7Yabk?Ymt*oIYN}D#~yd8BUCt zwg$Otjcc5bopV6E{qKWkJFot3=980jlJ`2)le|TM`8o@h7R{G`@=nva-SSD_w^O~a zo#0aAB?HgfPF1y692gSMmt?FwFUFEmT#idgJ_7Z>JsCY6S1|c~5itn7V1@(-P1Y*m zzhHy}hwB?8AXGp`XAFugfw?{mlH@3AE)pQyP(K z0i;uA_{y*Et6Mzs_b$EuW24=>z?c8L`QukyK^is8A=atN_-2h%Fbs*AAy^%xRhED( z&O&8XSe1xtjJ_IUA5x0uT>Bu-0bX;ZIv05Dl|?Pw#7AJ=)4xvd*z1Z`SWZe{q4L(` zHY0Dr6rACFsd5VqyFsAK{X@_CXStqD#4^q}gtWwh0CB5`m=6WLmSSS@C>d$sgNI{0 zOy}n@YVHQL6(fH*{M9qb_SVccqUM!lSy4vPMAh+9VE4e-MpdYKMx0YnKm`KlpIE`p z9yy~&U-ESa+DR@f`g=c@*$zJjdpck8$ZxxVUXIZS%~H%ji(V3ow+R9`tD%apBN9kN z{6WCY6q6)unE7^dp94PuXLMb^VA<4{b>^Gqx{J!T&c?XNYkYBCIz_hmzQm^b607J- z`gDpcqEjrzbc$tdpr-RFIIe2I6N&w^S}~|Z;e)U`U9{>;bMMjS?mS8yv1r;6Yvl-1 zVE7E!<}H43-h#PgPkoEiG!8%_#e}MKFp^?n70Tu~5D7C9@b+oMjwq(%W~}%E0?UMC zLooXR(VW}eC!Yj||IP<_jw>{L2|h1)lZ`HI&`V?Huk+5Nb>5M6g`P%PT=F$Vll}mW zM4Grri1*bC5|3w2i17dVzG?oQ-M?!!!=auL{UsPU`;J!^UTVsDBKM3B<~A|6en9*h z3>iA4=8kqQP5WyWBgC-oco3HN;(Qm)F*3z`1y5^kvE(`zb@rWJIzhqxd+-dUZ$Ke1 ziXRJQI%B)pn68keO4m}ps(r`ppZ+?B)eHP8GO|G>{tJefZ! zsv+@TU8%m()dVR08=-f6GKSA@XouH(7vk@^s28?QHa^`>^*uvL%Io#c#CX_?*A+w6 zzvJDBpNWYWInPq41v$UH6JPh$>&UbJ1n>XsMf`h3=M<>63Gc08xh%fJdKQji!0Q}6N88iM&`?j0R#LA1?M^DBZE}bY%#Xlu~;EDGl>hnCkwAJJYSO9IN+Z zzk|W^|D5`1*8w_Xs%fIl3Ke;3Jrz~86W9taoV$^`7xb!}d;L{HW#-gF@XpyKf>R|C zDgC783L#!x8-91}MK^Y7D+rQ4SKnX2dE~LHw*#tAvO33fEFmJSmLMf#^KXAmY-*wYtGK4%vNl*tQUy^EX)Vu2$ z3+JvkX2-GWx2&SjwF`WAzYu20o}026HL2<^XFTc`35_8&WiZss%+J5!s+w229nqdz zFQ=QZY5I&856^RFR%E@FW8T1{>%cdf4{s~=Rp7v0pfMcFlNG9M`hA6l_MybBotqZi zbehiSOYM9c6%!;V1uqD(M#z~eO1L@M2>^x*xBvd-_rz8dF0>B~`HSm3>ss1@JuSm$ z)gTjPLRxzYxz^h3DeA z@>@b|_Sg^%wYTlZXMTT6JJq_JP)*bAd6%izzg>#gYy03Q^lD$c-K`W4E{7eh-p+O1 zr3`=HC6}pv;hBmdrUPmma4{G*pLNx-mmTI_^MM_Io^J2fY(J&h-lW-nTC@EO*)BA6 z4E?<8+lBp}yI3>5wziC06D4L9QDT{4(V7$(Gp0sS7L%#kZThxTJN8{ZZr=PqihI)0 zrM`o@YW~Z1eR68F9YZ}zuCF`utkdJ`s7|5EEBf6fcf3W<8Pw8D+FC;|$rStCo|L7IX3tjAM%prHBkKxU=LQ!TI)}#W zTkzCZ6Gt_dO)2&Tp*JtF7CGpTp#U0gSgsgC#K$3tn~63n{4Y~{KwTY|<&<1K${wNQ^skRP=ZmP$NZ%s3J*-1?-eP<& ztWV~CIAQ%f%Aj6nw)EbiVUHX?Y)QAzGFv*N0OQO3a(xmu#x(pWC8_FvWK_?}W$ibr$`p0zZid-ki>nQgCc==!5J8#~uA zx|o$L5)maqf2=A|E%_6;jxd`Xgud;0WTdl?U4qL4^H8!98P2Yet&aSr;X+rtD$SyiB3XLjae zL)BkBKFqkA(~I!7TEo@H^4s4Y)khipoxdm72}2glTYSCg|lQQ;U_;ee8-)Y8*&Bh2cv@`>G!2n{YO{j=K(x9Nd& zTNHWBGO&uR!HJ0kB_={>OG%58jwQO1j+NVSKx#IL)z^^i9!v3#aohXkIPMukfiI6L z-^f#xS1*liWnx6RR7^sIy#ptHaJ`zt0Sj!P8;9L01i{8+TyQMDdwOBJlbkg@N)A~7 z|C5VR0s8Nf)vQrdLlzoLq2nfX?|AcO3askYUh6h%nR{SHp%Tf33kr5eB_&VRIScK~ z;({|K^krgz)O3f}UvXaX^3az(u4v(2ZzmBzk}Nk+FZ?4yNIFP!{!g*4-3!{L@A}A^)2!+J=W0_G6{zp4wF`HEpW|BSM`OCH*Ds%z{XvQ+w9WI? zwx6*&=UuIc#&#!p+5+q=qSXz9yZrW7Jkgi-yUHcoj@Wv{cfZp1T6#mb9#w^JK1%za z-ZmYd==NorZF;??(5sJKd~5u!8XfEe!^**6s$>oOp*9Sc@HIrEh@lVHq`-P1SOP~m zjuEc~V;l>|^O;Fl7uR_7bMyC~xV}^8gAd^|htBA^=?k~5k?h&gJO8{Rnnx=9NlBcU zVsq5dvtgO6CtGS^3X*F)W3O$aLvE&tYKm?FTh3|VGEiLIgV{0m1F@A8v9cae1}d|ZrF*RXCICRb%y zzaY?ERIaTmA!jO^r^+U?=)KkVnD9XD(zn|k_8MkvenKdbD z^{YV?qDa!f5)hBzrO{@y$M`x=a`ee#Qiul=?zNM@$%QoD2n>6p~OS zPRMaA0Rgpkl~|M`9upjKf$mgjJwb^n1WZ9cmu3M&C=zA1eC5t@fwp9pEijxOip?Bw z+nQG>bg^ZP3wR4-L2RTG_yr|l3~4_RQKyW+WYqrzY?F)y5S=f8PIS>CugsVJ!1lM_ zoC3-^QO>@%E`1-ZZ|ileDxrLNt@^gt9D$F2zY{myr_Fs56N;TS_sutlxH7xdcnZcd z#S=*vW5&vQ5H|t2z2JZY8y-dKkpv$UfT%)DS`$PeS)V&XYfR``=RXYNx4llbDMBkx z!pK@6A+eSg*ohk(xfHZk^*NSlgnu>J6Nw0b!a#)X27WIQ(UF7XDyAWPu|Co8Plp#ZeLlW-_qLKH%w*rRhqiLREdJ9eX#FDaIMzgdlh^Y#=nF$a~0>P!4zpGJELo?(!KOBADxjnWRyG%V~ZXH_w4=w#P zh5B@{hk9QOdN&TV>W$0U;B71eRRF}87;T7_ipgpqCmqp57QVmI0mtb~=g!a&^Q0U5 zo^yeonN^I52cdZ?0nj|@AcGm~g8m2+!Uj6^cdC&BR>u85_TB@&Z8HBKchbFqmeR6U zp_EZec08DMY$bK#G_IATr3@k4k}TJ&lAJc>fU-*Am<5i}0%db-3X}rn*mtah0tL!A z_C6>Z3hlxF|14XQt=LWj$9;eJRZui+%hIFI^BM2Y`yCS{S7$B`iIstdAj8zqD94Dq ze!cTU7oR~~>=KSU7m&s3$q5A!0ou+i$N{a+j20184l~WeFoQtjBYG!_b#nLQ{{0s{ z^=q}Ttm0`-7oYw9u4kW|d#uHUF5Ayn_S(@dywRV{^@Npdj+giXR{qxzw5ziP#pSO_ zpPkio3*Iuj&nHj0W_w&s@`8LW#Q3Vx;G-Nlz}XU(a{|zr!WepmpU-7gL}~>+f1^OB zsY}EM#?R>d2NUH^*hNN}5mU5rmi@H3mL80X~*;lW+0! znK*1$W{UJWfMA!$jc7~?_>;YGIMW<$H=i;Xj+7gciSnvI_Nok2XQ!NmE`q+!s}EvhsX5#q4> zUd~9xzWoM7xQLB3AW=a~Dj=SLy;j{z_q+S+o3d1owihc#0Lm8qri z{V4I~nTOtgVT5!)cxrk6IyE_^!qiEK@5$yZ6cJqh1TSYg8sL-2gjqYM=cQt~1~991 zFysMxZs<+N_DzBJ81cyXgpMiC6UP6lX-&#XMCp<_sF5S9Sjn5ZtlvUYD*VeAk1Jrf zKqRsd4~Aq9)GN`Emt!6feNvOvJj7)Hdfqcw2S<7J$tFxB1j+fFFq(zOIPoz!!&kMV zdW^VkhpzUK_v~ghxq4f&Q8?NZe@YfIP#&;0x-vmr^yalU{PfJ;rj$yduPYq(Ly|o( z)cxU3Q!ni3TN|ySA)n2iBTSh$T(x+}%Gi^W1sLL?^AjybBR%az-pjHJXXsRYE z%!nKB^$k6u_I4i!_@cv*f9%M*V zWyLjnKatw~WV=Y%!Hq zmi{diL|V->NkHT^YC)Cc>fM=lVQ1dOMU9dI9T(sIte&lGchk~lv0gglbK=ssFMi(k z3L(~6WGkO4EMeAwsb1tXH>G$Kk6GR3hBCL=pG~K;8StBQbOSJ(;@1`citNJgFEYe> z;osn503Q*RIiP#l%{XZ68v?;=Ug6WeOuSiuzKEczFwYI;pe+ZSi$iq3AY^4-T)2aa zu#H@L(cS}-yNsAQfQ+DVb=8d^Rm`n@e#}KvND#eiE!>#}X1}Rj;S`^NzGs0z){}#z zzs-4G;WL^bsGW^rtO4Ne3Zo2J;5(^q_v^GC2Z1)O-Q&pg0Q;kkyV&6ozVgL zDgZT4O9kA24z?eLi#t z931#;OaV57b#j!$-xUt=nkL7DEjwAB_r*TEzTbdb`ZHQ6 zen0QveMVeD$cGkG8;V$xl+Fp!b`_=!<%NQd2B$!dt>?S}E<{^bM>#5o)kEfYJp1uG zLq`1O2KrCp8|&VDf8LqCy7IwuuQ_~Eo=S6bF#kz8fw+kYJ4O7ARsm3rn%z$9j?t7c z9p=wwVnR*>E>zX76;sw_ZGvL1e2{J7%BwXwP>AB=%)yC+V?&6m5dOu^;Mj0kJ{yOQ z&n8^LXLIvv8D8R9=RX9OgyP}hdZg6x+*wUn)W)eW1rm(08b!qtyh2!5W8gZ)U|FM__B6bTz!vvMKzf|G z=Uup_7)Q0QRb0|oRn)wG=9 za*6;%J23aIy)THn`_7WKKI^SLN{Eary;AZ2Z5Q_haoXckQ)lcr$26x{LrQ_Q!?^}$ zxVgP((sGnN+}z&S2#XYyv*6sIq6OAeXy6i;ywKWm{xidWEjkH(9&>=B@pjzl?45d@NUwzv5UI(ozk0o59Sbb!p&Xa=OfOyPud(c6!yT{n{hux>3+Wcvnc>Ay5(|UCyD#L zzw_|pfBZY%Qkip)phU}K8!S?t6gUiqOMvko)cAbeyqp4C3)xMh(S`^<{T+!QpdUup zkj*g4$GgmOugB|)E!^D=j~QtKN%rzOyc}I+o~+(>vN+v-=WI>A-6{`T!Q1f$ zf<8qG0qHhtvQd~dEgXpJa?TQtQAfe4Xu$f9y@)?Lp!lN)i9h_g4>Wcu4O`Y#3kNwL3+O4%>5S z)BJfBW5`O(x+KSsGR-b@otaRw0ZV1oB65AohsuyzM6UJ6o+2hQ^~+8ib!JsX34=Fp z$orS(kR56&*Ikg!a5rWfCKmwthqYje-p`^XMe|fD^XIM^Q(DKKH zRwns0@xYCP_x)}+tcR!X)iELr^)`@p#1cYmt}m5h^Ep8CD5-6{GFONrP>r`y#-yi4 zP@l#ys~^&yJ>uFGBggy>aWY1Q^V7vZAeUW6on>VM^w|^u6#&L4n9;IK$q;~vAQ=RG zh1r#x&qYZy3XOJ=+D``bcAz4)8f5J-Aq@WO2JR=E(aDxlJmf`ty@_)8eD|oQZ+`WIV=P8RDZr5O=$aI9O$S*# zT#v>zK`@vc0CuP^m64`K=HbVW{Gx9$`g!6>N4O>@rv)uc678NPM186%q@$^xW+B7o zqZ)Qn#jKx3r|LpVfM&*YCs#x7+W^~A@yO@TyyN5vUaJBkX&8!{JA^ox^UvJ5)4-aYo?)wW-sP4q%>@UoEC^MzHA=vDblJepk~D{dUC z(9V2D!EQ7wXq_t5oCM9$H~~RcgMk{*Y8Q!1{Iq>gHfEaHD=_W*)N}=+eTU&7%EhGR zLKFbib5gNnYTJWH@nZ&Vf!D47fftFJ<6n#k`OdXfizmgV4#1731~on-=hJO`MgrlJ zpU-47GVVgK8dmuIpa2ysAVOU-`Xnwcq``*??is>-7`ih|ceu5KtN!L(EQ;%bO;`IH z>#BwKg*w{7e~ln}m5EnqV2wsv3&Llm^t-6hvZzs&M&Yx;FH1vJDrOGYn{gEl3Ru;OZ%cvU6=*8rdEuqVJK7w6Zff zRs-64Xg8My09$Yo9&pJ$t3SDB*pPmHurC&O_4@`*>ioRwF{pDe5S|&rRV}g1R=vn1 zGg%&PT#A9F%YO}JFXEqZiNy>~23MW7ZcJwB8hXGk71wor`_u*{P<4VIM3e47pcxi@ z85a(m!>vdZEsP}3IzR;Q;F7BjyleBOQNKkh-x&Na6ITvh+P>;vjFq!h+CAuDb19hi zXwPxX?2d@f-@&n+ja`htqdj8iB3!%8HlIn|f32_Ncb(YJKlj96>G=A(-nU!rFAk#1 z#RJ?M@4q=pa)zr*l2Ejvde?xPGuw0e0-jUN2{BoeW8FZdvj;{UZcxtSG?-y5Fvo;T6t@&OgU7cq~4F(@ab9nu(V#({{4%t8mGD7VBhbHflpEJck2q zSBROXUKnv7H`C;N=`Nw(>vY&bhg4p}s0>)R1so(yN&xu=)#$WBeLpCB#Uw8o5<0hQ z!f7MOL-`QCyECdBEhm}QAF|K~yFy%*SH3^ujJx}ouqvPsSqImbjFqkcfoc%FXRT_> zSrKb9dbHJ#A9`$C{hh-u!RsqZtSXu}7)8NWbq*0$)D+;;z#UGFpDQ#rz-b#o^Wav7 z=}bjHrIA5rOmpQ@)aa8ji;iU`y6^+vJK~h3~@Bsov{Q^@eQWaLRd6G4;Ti*FJDjz&b-^o`tBc@Em4=7FM$9ETG@WXaaDm41MdcF5Gvd$kpPy zKOgYe!9VX|6(mz`T*D9ozUH=INbVM#@qD@_mDX#-+i!eg+p~_l)@Fgb@duLd!VNvz1<=2Tb4o=>({7IsNTGAX{O2pt+ zfq&xD$^nL>uw>x?cM6OcrJGNwOw(t3E*`o5kRksUTA+<(?%Tw-_s$G+{gY{dTt~07-6gKR;O#vxXP&D*nli$++k`Zj&u|7MAgg>jDogn? z;B6!{gY?Bg$-)#a!Q_f6YXbf#cytitU1*#;Dt7==!VzFZQ8Trr$m(o&i*LWW+a>(j zx0d>8IK$X1WCTTyVOdv1fP#8bDE!bGcfsfp_{s4wyCU3s=(0xA64j@k8C=J=I?QwGQiWkuG@=MV6(U>BH>^Cp1 z6DX_113x_qS}HHx=V;5^ep87nuz%{s-e}yta87&)dwdifBWeQn=vO@hA=`uQi~lv zY#Zphwcv@MDNX4qpb`NM2$$g1M6d-kz%s!5L`+e|FVO@AJr)hP`7LTHP9`A?F|t8j z_|8e*QCk{flzLNZMEa6#z59-wYzg&9tA$)JXdb~IwORiM#Mg#hmpfs_Osj8l z7u&O^qs$;hM+HgF0E3niG+b^j!?R@soK=5H4~d)a`A=@z6(lY%cK))O!_ILKqJTJE z_&A@_SH%Z-RXS+y zyX-4h=a#P-_TQscc~pG+?R`8OGBd5Lj;Fb624s|>9l#U{Il}M?0jCAZNR!i0+8n;R zVZ#OXiH{5#`m6nv$He=Rjn}D%Ut$#iS8Q!Bfi*#+0HQ5mMO{pvk}M5+l&2fqd#m2| zzW?+m#1~q3KV$T{2UxlL&KMkuxU|b^d~9w9FG+wUND(5w+L){AZBL1x?XcZnzv-H5 zajAP}cXrMa__;-fXEfPU9&k~7nK*;&Y9k_4hBH=pdJv`30kJZLh?NhHY%3;@P={>k z#d}J8>WxeHZ+!V1tL%O+;f#Lnz^PYY)_t0$$kDtec)(w1G6P;X9$!9H&8ML=1P4x= zoN411lp9;#-p7`gLk*G447QO2$trj>1OH^3(Wrxe0Y0Z-*@h*zu3;$%8Da59d+K#f8A%t-ePhZ#N?eb!v5CKtUxV%2hWI^*pILNxZ5<#oE09SnS2@n zG$|JW_ambn43x9v<}x83h!-HxP+FeN z$*B}19w^~mhS_R}LqR_T^X~#q4ouAlO<4QsH--mQkDPp7T(#YouG62rb&F<=1{R>_ z7}(d`*NLKBHok^-fPd*5ST?j6^_E*rU0FjK>nt9{b>h+6e){2(LQmm>l9&m%E;*P` zQ;{U40hprhn{#~bh}k*%Mpy= zay1lL_xRzg3UR~DOqk|eP90N#ZdORAQGuyrKFGRmDQv&a_L6wbj}QMcXw%CkugyB= zM55KH(7W-aWL_nkaE__)8_^LM_3Gilq0LN~*oI~%3_U;$eFw>Cg!*LVh1eBrD6q$Z z`Nz$nEVIuyGcPExlb>N`v;tT}ECrl>6JW(fK&H)17=b*Vz5R~IX9wS|BZKr%+I$e|lLwGBfGAx$h>dV5iPlHq$LAT4zTS}oEJb-GlwaeJ<%GCx&`MGLx2zX;9Y63>;e=V1LELPQ%h%DNV=JRkX~CaY zwQL%WFM`D|u$HBhTfkdZXM0oJYE#Vd^97gGl+mFaA%0;`A(i6*qTDds7!bN;1!#0f zgdBL1FznS&>QIZlpE=;BD&%x6NS^TuO#6zF*jeCFWEmrj) zXIs#ywb3YNc%?^&5TFngNskC9X4Y_Pk%M$Ozz+$g09L@|M2)|o=LT?a8Mj~rL4>ro zgI`V%JchQx_6~0HrDM~PPo|9e^`gAD#erKt!M^(qkw;lDyaWY6P(!ef$1STqRn5j^ z9>n!NAS{wj=QX&7Qw^p{QRjC{57_U6b4Rt-m73mi>(`q|E)M&^xc*Ls+Ctbg>^C+U zzhT@qy}$or^JDX^aaE~sZBI45zpH{hHB+rorgfqea?ijmGtUQ_)`<&KE10|1(#~Al zAFn@Nf4u&9{qg$a^?&yznAY=?jO%-v_CH$p$2F_w^)RNPe(wI8U|BtP>3YMJo4DJ$ zy7Ip5d*a9!Pha!>qC@Z&9U6|Npwa^gwpHl83kt@T6QU+}AP5gDfHady=K9mO-J@^R ztnZ6Sdtmaa#NRE-|FtlK^+uIZK2uhKFT)K0V?dn0B|mCwviA1cWW7JSLQ^KzTcpN5 z5T||7ero%u!z?1Sf`XhEbhpSFq{&W4g?L=fNabbZKv@3p{_7^6|HTD^`ip+tW~%;v zAl^D5_u{~R+bwP*NEx_b@D6iZs5_<%G_cmL^m^Mz;$OynddqRG2U1eG20v@%FK&-`4%-U9A8Bvt%vd&!#{#3E;EU;NML5 z`0F;V`Rhk-_Y26^n6mp!T)ErIN0Qgx$o7Wj+-w$H`;ru_B#;mey!?G4V@!{R4l$wgvIDUi}toUyEmfR+sqm4Y5* zjIH-)^BIki5*Ll!{-@1@h~YWh^vkX8-RI(wgQ;75ukCAD$tV@qr64J&!o$H*{V@4l z3=c@0FzNCpi-a%jZVNW%0Z0KSvx1o10vbG+JSgcra`W+SJ`)p|?&r=pe{Vgu@YU2- zhOHs(R`S zw0FRy8>WQ~Bhiwq2IhuFe-NdEDNxmcpc7I$RSBrS49z^v9eUsccL?WxGxE16;TeH6 zUx;tqdGy4W*6(bU8Fd9Ceh8nB#tPgXpA>CK-0Lnn{ z%FViT@AWCVKwd%X$SbTAw_ZR^2rHR7i=X+e_|S7yI`Px)R@TtY%Ah0&%n!?pf(Gjb zwT#Sq+xOy<6^AX`_tjfVFQuNWE^LV>*OAsCvP6dx7m?I5xWqn5QkW7VJGYL=jz&aw zpvq1;xi$`gFgQSe6Kp|Zbnv2vS&b@^)WO~0O%Uf$E9&ukF?8B53$JM;&7CDO2c|}| z(kwD)pl#0xovL6=EYn;u#Y{P4ss@_S4A?_TR=I^h<{97)q5nXV&7iLeRRDWwS)&>l zLJiM)0YS8B9h4k(Ep5pBlGDc}Db(cl^u-k&!gNtCPGK<408kLa5l!jr#d!P1$S==qHSG5;EdPUe?s?1JI(5}`r9MklL5X21 zp{T}2IR1cj=~L0(S%^sxsSk`v8+V%Zm860$e?zb%w@|)qo3J2(!UtgJ}25 za0r$Vdr_VQuX3y~FAtcxuzv>hs~ftZN$$psT(HBQ4z@E9(D4a)2OTHxciNBZKeP2m zlr+TPUj0Wre#4-P?CD8X4lmLRbxjxexS&9dqfYVx;r zhWz2_)g!S6*y^MSch9*XUeAR@fH;V$_N}QEd8xTo(<=&Os z2j*EhT!h368i*zZjSA>>QB*bqnqm-Q%MzWuc-9G&&jb#HWC zaqN<%{gHPAK3H666EsDRsZ4*(aTbpMwD%Bp^yWcUziog-A9TP6&$eAj#$iQ~6G-I~ zhMAG60bZFeXFvl1T(@$HOoveoFtO4lD5{*zw80eOmCZT{ln5Y1&9 zIq|IpUvgu1`hPU5U47^7Kw1* z!(W^ZVBW+(*kuRfGCs(-EFE!!q*HFJyY!fUktQT_J0j)PvqoTeA4Nm9|E6$O{H@}bc3e!bYx+uEsH(v-GU;fvh&+`EfFpt9y8z}Q31&( zO!%3eHD~b~5OsK2&7HDcr4R#Xk3p>;w?CZ&yS-sWh|6%c(+vWa0jB58cb@&{vWd&; ze=Tg$5Ggg8zWeTdJeH9~H^`#(5LUKU-6Rt($(Dzs??1R?dwt=>5k~31l>;gzfrd)! z#x33E{@lYBXVRO_nE@G}lNY_^w9G4-i7+TTAnwf-Nf!(B z7uEa0W2Aw{CVzhJ^&PFQsxvIZ(SymXKrXZP5NB)YW&VgI2OjjMRhGSC8!@XjX!%@9 z2(~~D{sQeFwhv(-P)wRP9mYZJ!g94t=>t$g3gZC!ErJk7DgUyRFaZt%CJQ~vloxal z5>BC}tkMi_Kj@~}!9HT#k0ah1(pP4I0k&-<#XmOuNnO&id{(J_*@^xRW&qoVGE3oe zx$0^F7LDTXTb%1Q((tRBr#!y;ER(Zd%TzUkS9&4>kFlCUdEbt1Fdc@7{-M;p19E1X zMShFU@cVadoP7Bq>Zt#fKwul`slZ`(OnO;K=n9Dyow2NvrQNEWnTx(p6;z+72?{uoMi81|u13mXq-Y2e zR5CscyBRRY3?J4uKBrI56#DG!=BJtF5P3i-=_yDrmC@dDgBCm(Qlr`vW@v=%xu7>U zf_roC81u~n&>lae-{ZTn(o*lNPRQ5`W6ZNGOcDlB*4+SAwAMgL_l;}wdMc;QJD$UHu1U&H>`JaqhaFCk5v_LT@Q z^@|^EUxbOzlGCaiA7q~R9vj&;CO>!1I3uiJ$0`LT+}PQ|sHB2ck8R5%GrM@59JlEzBndlmAX?vzz|in* z*vYiswhLOm?lRtomAjz}b=LdnbDn+}{ow2H`!syr{)~(7-`j)Vf59lUoVknf`wI8z&H;w^&&20DVM_VWC+|LS zlkxrJdXx9J;QbHc?;oXOm=uW%b$JhEj&y26{SGR|ZYck%*b5jn|rXRnTZ7lzJl}O#p zUY!%X9DkmYR!R3O939PtXB!)05aWf1>OM=$MFX#d> zf>3>hXFKV&fA!ol{E8zj5-0@^%PWD>Y&y=P!ba_#n?a5yID%||vilSXwAHH}J-U8om}Bt!Sq)_8uKf9nvFGd;AC>$A@BPQ?kJqoB3>aXW zAhmqBX5!Af?qLx%7PDd$)t=+$nxIW$)JD@Gc?&_h=Yp*5NOm+?k(^#pm~JA&YH4NS^%cP z3Ws2_5QIsAjnO+kci^$ZK3F+o)PLzFTft2yNFQ!H@UBy@51Vdz_4fmJL6?ulo&M&= zNV)ML)mf$CcaTmxcESv83+(^z3Y&^g;0Om|B_T)5lH)=Kohp(N z>Iz4MTnbjB@McK3L{sqWL=OH8PQba};G7w)Y!gSA7Exz8_z3V$NHNZIraHJJW9M2z zX*&~2Lqe1&kjaw~i8ZHUFitStGF#1c*dd)@E4O(2BYrq$)4e0M)HB;bn)bYF{mYB9 zR_L0FM58nfxx)}7w+2c~keg)?;5bi_@AtavxrxbJOTkgqgIN*Ul+VP`MkDSN zL8Z`+d#G;oX?&1yP^uhQ|E0;V2@^1@tmXGRg!Y^qy`L!ZBU{PR0^?sf0bVZXN6 zSiNm8>D{k$$Ncr|lgXZn$RMJvrpIqg4fvY<*>n`ty9(lm-Wb~A&t_sg*6nQw~rTV*9YpxdOZE=GX{l%c37*up`yVJ+P#Q=}Ca z58UU&X_j2nR6_?=GR00C-~t2_ZAZ2(Yr$0$wgO|Kg9)ayFoT;<_vB;CpY9at=2!07 z^)H8AN^%l)RXUzq*uCH>bT5EPJKGTn`a8I$rcir(q(b|`ZL4m(;Dm-d$4vPnWc*D` zL7i=1>5X+qJgUwmHUA|w1}7E92BkZMb{573tN;eM)((d4;FR9S1nb~FJ0qj2)0L5< z{>zuE-nPFqq515OP8hSVh41a4PSA zLfGXjO1dwC*J&(woyM?XPb{13X;QLjpvUI}E%=is?eWFXYKag_v8D$|lcj6_{OQ9n zQ;NG*L)8`x3qmfD1&zL1F^!4@W#Vx*9}T9{!88-?;F1y64&;T_pZd|I8%Mk~WXsu` zr%KD;dEkBK!GleXzYZ`9V(KEggQn7eS6qgkT%(fZ<1t>+_y}c-W%E5K?U# zX<8Pl4yG~0nf6brQ01Crs$P)g5R2-@1dHRVeObY0$IlRUZ+&*veIpAG93`#&d(OY$ z2~x+NTrsa+)}JJJJ4;LhZPy=94yQawh`6yc7z%I&i9~~^8S{T|N+Lgf^x-vLgGX>r zl-<-DH3K68w!&&!KbfAZCK^{#@y)q;v~iuh9^b;k9{Ju@3yr{|D!l*k3h%G>Mc#h} ze$@W+JDEdyi}-%|ul&824l`btXY0!^$++0Tv^3ujay{eq#-&YXjO%n&%DOtVMxgHG zzNl?4^z1TgR*-z2C-l;sZ=b#46tYf9FWmjYrpMicqJ2fJMjmO_;x$k&BdX>WPqYUg z+Md^>Ea0d|{R*H~CMaGW{^3(3hSb%Vt`3-@L~e)!x*_E&Y``z4fY^}kB-}nl!sR!r zwEh+WTMK2khR5-^)aepnp~d>YkNbvL9zVe%2O{<7ZB~W6X;E=3GG5KNx@F!e?G~@3Qs~bbe0h z+sUWx_t1`qTDX~%r$bnvA%=>GkjpAqdCMJ)fb3KU_}dFn@L(0VZ+@DIRv+dzNXPH+ z_!)l|iJMzO0#7-Cf1b^N5f5RzPSjOV&@=3=AG2>qpay_>gY@^Oj{fe0wyAX`N#ZKq zT?4yFiBhOb&ZT4((bm&#LOQGTpz0Q^9C&50lhb$!OBk`NZ@Q4m35rV5zElbw+QCbx z>QD|-?&r!jURU(4*(MJA*K89Md1&Prw+NHnDiZufjSu8gF?UT+{|3~i0h+PvZ5MBP z=-$Pnh%+$iofqK+UH|@E=s9)YfR@AeK}1Ms>v0AnQ+;Y0Nplw4ELRE3bNYV8165r&Q>E@~!`o9QjKPOyG136o<* z?IOIzJnQ(;tV3B%JAN+7;EKGd|H96py{Kf5`YP;L_6)XGbWq$^PtAMyvlCt)z18nD ztyyoICXGM5>!UrEoMTF!Rh!lfW%)R!lu4qj0N65}SK>lO7jd+faRkh6E+JBA=Hrrp z#afwI3aW5F(|-HnW41uIcAE5kaOdod4Nv0rMHPQ$K|utHJFtQV6}*Zy%aZYHN8=Za z*dW1OlJ`<}sJF+0at8yH1tNf7ifS-@HiI@h8)1_OmLyT}$cjOb5ZH~JvclBEfHYlx zO=NqJ`6UUVL8jn!ScT<6sqt=rODaamvU{1%X=EdBD_ z=YE`D8Pn##qQR^H@C?D(d^`jnBLML5Y1yuK&Rz%#CkMlA(OE59}{W4hFR+tAx5e{iPF3h`G31Pjb`f}#jwA`*zW$oMEo^5m zj86VCl%!^KTJei!NQ)oZadF#TgxmGvs)0EXL4q(NP{*}`0Kh8~&Zea%Pv$N`K!W`1*hBvL)z3#5D;{5yW5P*)q_mrO!!c0%Yyv07H)B>_{i=$bdeaMEVo+`Mn+$2RKdQNCGc3}p{{82yl? z%dC`2XGv$Y48QcVm#!znxW53SB~T>YA(TtZrsC)WI9 zaP_Z&4S;^Z?RWb375n-w8nh2S{VV36<)eY>p6L2NW}d1Z%~{gRH|{y{sO`d5=Au-~ zlJY<~47Nj#2vS)Y%jA~JW7OG>l#=mnSC2Sxz9l5oRnV+t{eD<2b0d_PzV)K?RQlG+ zT0O&QR9bz~p8PIr%S?GFY;OzT{4$M&=Q|pGJ{CqWz$8HHYt*hVc6jMH%m}v$fRY8y zzVw#9uqQ{Q9sP&gy89q2Pwzc>A=GeeMEWleaqRX3jKHp zhYmN+410*nOu4{fz7UTH5(+37R2r#4NXLVcK;9DCG7rlActF;N^#dB_-h^~m50cTL zxnP)OdX>9N+m(fp$b|sO<4QW;CV=cN-Zf(0`MXqB>D$#SkAI0Lo%bojAx7pxoCG3JRgm+ z#tNDW0FeP%O=f}35j2M6N(*!547RyAxx$cnItR8g9OXi!cHr~dgme_^ycTx5gTR6y zCf=reCL^RQ67z#z`hE?l{%(2AN|MgLe8$rUY|~sfLz(I@bXzjELQu0VzvwVIxzC3c9YY+LT#Zx=*yt4UHf|Rtu~P@|sXZAm9Rev18F7MXE-j%5eB8qYGu_#_f}U}Z#6%brQgFVTy1P-Ci|J~-Kpe1cnYC*8v^vSXdhwt+Bup#|?p-Nr+jI?;4!*{*5bFS3`K{U)jXhf08D6Gg? zMb>(J@orhoDq$ttEkpWsya5t%l?a?ca!kr82^vzYnkC1zL@h~nMLJ;3&_(+uI;<>7 zLkt#6U=huaN%0-t-ss2m6o?Gswx|tTUvE<-`pi|K9a~#1^KRipj|$rCNFze0%1e+) zlya$ph8`Y#k|dTkZmjkasZ!Tx%~yPLJt5Ytf_AZ3t3;qkP_ZP8qWVs8R-@b;F^U}G zXI6qu(Lz?vf!?O|%*+c)55qtfK+0wQJU>Nv|9~tvn~!S9cHoWRc;jyelT!oCgvjtw z1g1sva=uIt2WCIk!Hw+x_xiwu^+SJeHDvu$H`oxUY0_PnEqd?OJGZkKGMI>`D-60? zs+Stjq}h$lfg4WG;q}TLI9QZ$Y6MO&WEHJh1ix;>LBNNl9Kci-L0R4ZsXOkL7m}Y2 z8-c(7+ujM%O7@fWZ}zP2{nq9!FO56UmpJ3esiiY9Hg9&FbXf?%aX0GFi^REMKIlGb5uccLS0+Y=AUG8HDr#{!a~!g@2O%-lKfG z^ZnB;0}u#i!Y!~th*`v2P!v{%dmH$<6c@aVFXu?+vFYc0;`Ptx?ZU-r5EGCOPHN-$;Dqac8U&^mMed=A7!>plz)}0M#0RSV0icpgUatzkPMaptwph!9jn3ByF`$nYO!?WJFyQ=KJgz_r_;;R zO}7!PfNgF*9*23p68ycL^TNZ(-V*pc2tgyP)({63Zcq~eaVe;b z;C4Ht+p*7vTZ3o)bMJaWpS8^5S(#(8eTv1pd3#5d>%aF#b&pB!g$$rqf{}~Q*uC?# zIl6rV3{Kb?(&%MP4{rMSKD;Gd0WpG1FmX`0EV}7*1Cb+5TZ zFWS#CG3AH|QX`+4i_z~cXo$720wxhn7Svh->2sv5_CFzi?0v6RZH5bAPk3b^8dC>e zEo*Xt4QkC~=Vw6i7({Ip2d7Ds`3=z?kS)z?Lg8SL|0E_AdOV(jfkmbamC7S5#o=(7 zbUC2dGYQPF6S}M^0BCe@)qBt9O5-0ncI3x9uIfYjK3oQ&&@XNBy(QzFHEG&c`2FWf zm;aFY=VgoAEDAX#=}|8qkE0YBLMxUb3V<_bo1g7TN3$vLs~Q|jUe)I*y(+pb-{GFT z@3yfTx93TVTBIBHS^A&}qzp3tUXFvUPO=ybx0aPJsDwy{{vaKm9)$B4;FPC589|7v zWK#-GU|@6>ewp3aEXH)EK#0V^GJGS4{s9pIjdlUAF`MR)_g|0hgODG)`f2EDgf>l6~wkd+a&xa7**!L0K8nqYC{HkQ;|| zc8sdYfPsLWr)Uh6l#DY3OA)Dxc*gpHyg>SJ`}{L(V3Gw0f#x-5#Me0sjX!%Jh&pV8 z0Z^byGU=_qzG>S&qv9`+Ztk2>zwd$;6Vy$o?rnA)-5T+NSZ0A1EOguyd`DQ=fcwDO zUf(4z;s5s60i-{2H`CJms&>DvP@FX`Z5I1$ef?S-Y+HJsm5!TiZdArAW9@rQwKyF35Eo8uO(88h-XcJZv5xa(|xkzP3P?N`I&Z?c*fDyVHWupq-# zR%Y1QpDM$@g3@xm?ILOTHUDW?e(k=dtU5PmhFgs(a!!j;bPVMzLPEEYDwUHwXccp3 zTa+JZ))2_lZQ0WNnyz!s2k5%S>rCHQY5gK;!w=u>f9MSaQa8bD_YgTCWJm=AzB$h; z7!iY_t138rSJZ#{_jh062JZ3)G|4m@Uo1W2fA{jXTf?U8BWxjbbDWZewMstD=QO!n zD5^D;SK&=D9UMY2vQALiXB(hP(!dR0y!+V$ejZad;y1Q0btMInOQj7#XVU{OI4!(x zZy+kQ@ycAF-HO_~15qg=#Ij)2fk30U3V=H8r{5SJRISf@sq~?ISK!W_#^5o&ww9t< z^$mX5e}>5ol;nn{GF(lQAGDjgcj8*A7beMp6f;Q-2h-_1>`8_PmuoqY8D|0ON;RZG z^)s#mzqHO^eVh}0WD^_UB5X6qK#{xAdn`O-?v;)~zjw8?>UC+COMgo~`tSb^B$nqC zvaxC^*In4_Zp?P(*ie|FA%qKP*hPex3FNGw_i!_|2Rvl3UYbP8w^S?z(i`S-t1g07 zF)hN3*3J~$z$F(b8Z4GuRhClofKNX*Pi=G6RgtecAsxXHX>+wB~x3s z=k?OGJuefkJ7YJiPsTX@0=xm-=RlbztJZp}ued=P{McUd?NiS-8K+vb_eih~Ab-X3 zBS4WV5kcz6w(;s*l>j)3%6hFNbY~k-ehmZE%Mc--disji)t_85tiLQyz3nDx?QL71 z6?l7w#ie!j@aYugz+#3S2(k={)=a4Ba9>*pvX0CZ6a+D17P&X2iswHd9}Ct zCh3hmTVDC}fgyF}lMl5~36*Q;M)^yls5Uwd@al(TQPAYH;6l!iRd=_i_h|*}|qv!U-pcCY&}f;T*#2NC);^XuOOoD?UlLNG+}tx4B~7 zi&k^6lMU#L1GJ;q9R~cI_FN1n>|3BF1k!`B!;#E^iU5P@W+;1mMo^2y{e_NQSVxHI zn^V03+?rILY?u%Y4Et2yAH1_|E%09Eo^sf`h4lJR!r0XN8EO3v9RuKP{&Oe>T_C7dqTJ+e&H9g!ea17;$K6PO-qmpwn1&05}dS z1IGOV5~Hw_NIf&%WB1y?ZAr4R&UUx-=}hr{A#)Gja!jq}hBQY&aa4c-kCReOTL3mv z(0!#Aa}~^l3ka&PjT}QYgkV*oXedyD3>b$Hy6Zf-bNWe&wb07ISUyxbQ_E6^qVK{rs=uf$ZJ~5UwXY0 z_s_1cFr@F3R@};N^N$b6e4aB$5I_tF)&CICMD_W=<6P(gAoAtCb^ts+Wy~8_?BFWc zR`TeRCGI7)?NeoTm>|jv=&*X@`v=(8NEdIre*7EK-G09cNe5*Phx z>G1Thfqg#nM|K@?rR`v6bx0tp0U?3f@~Mar&&N;^7ROT6hcgdI-%eV`)Su3PV_5+iQe~$USurLi@UEm{2EYM>ATfYe4Ox-_~HSHrb}H2K>Gzlt)bowTFlOJ}17_6*~QX zZ`bvYS6?mVL(S$YS~(o;0bn@yMFaUsJL z6Zd^c#Xl^ij$l_$TRg)m-YHuiHc^|Aavu=?M9rCqiV!erG;r-5+=Hk8bIL_8>@aLg zdq7IdLyt&@p8Uj(_k~Wd!aSH#c}aaW$QzQog$Upi#yk;SGvFj{k-AHURgRm1Q{N7wGkJTBd}_uF^RyWwbyA}j5|>fucapB8ZA3Y_5$0a+=StJl!1 zc20mNByHc|(x=B>X&N~Fq2W`~jbH9q%F!*WvgQi}Shll~1Eco9K_O)u?AT+Z+* zCQ@}b<}=d93*PwgmL)sl11Hqi7&){FY2eo5!dF06`E*p4^6(y74QCjC$(n%QgkS(b zr?&yZ(8}#*h&e8dfqXj6E096NTX+{R#R^1Nz`_Lbn2Y29X~dBcp20RCa!c^>1-F{k zC6tqhc(ug*F$_DM`)KOLd-!KQULYd)~qpJ=X`bA zyA2na=DJ#4CqUOHfO(zG=U}1Bgkme<*)YvRPOH!9on*uM@qbBblK=C4oqL~BArgdM z$VQVfUWw6EV;&^a;t}AaWc(>!PDi>_nnqznqzlYt03htorm_n3b7%@F_7plY=5tXD zDxhezi+DVlY+T^7FhWvGd6-FkxEpX1n`{3JQ!F1B6gY~ROr5*Nx;R7uB4>KSno<;O zC|kMn_wJP;GA~dex!}mpLCe~pEq;t%k?z~&pbOO=stu<}Ad$Ab^Y zrq|~~Wo4By6qyqca*DY2q2>sH*?@khL7V{UrSbN_b|&&6#e zPdls8G>4KDe2UBQJ*g}ohfGdEGgVa)64WIqppgqY^pK?rQxS1(91x(z9J>I);NngS zoEz#7v7T2Y@v6JN=oxil^`^MQVF?BOZB`SE6G0!qA&>|Xw49I-91;u_H1Hh8&D9&- z*QEG{L$0M;jw#LH07a%ql+zSJ2&M|sPN_6(*DfkF!E6m7(n}Qx!f0^iB|*bdT^#A} z&Z@AA7C$SZN%u}3KBV7WfY+rNKQ-U!d48s?{4|1#PQVr*0}gC=c^y2Q2?2CiK0@UN z#qlDoYy-F9o2U2qu^+^MUzhCrEuQe<-v|JAeM$Ko3PY%^@)xpgxK6*7wCpjBP9&N_;6^8UtI8?^Y(u4p8A9S zaMu4H3u@n!qJtApPWv=znVLdgix*gU_;eeA3eX0@GAHW6hF*2~WGoGnu>jd%2Mqi3 z^0FetYoV3;NLuVUyZ*(i=2}!9%ARUzt{ApK&i1&lVk?w&t>(0AL0WwzecMnsZPj?O z>ZzA74ut{M6W~h8`C8=J93=;$*|^+UTEh@OmR_C`81NZ;m{op>G9uESaMwP^&WpOJt6&sUp{sPxH8Sl1>-_S zld<3pnfl@1*zclg$mItoek8|;rvD(T(GKz*TnrF)6dRL}$2No2yr|(2wE53g70?Z* z@@awVhEQD%fU1BNLIYf9x)~?f!~p05!}XOd`rp#g^i6HKORa!=w9n_T%W62QfnUz2 zQawIV6k?hXZ|rI06-6ffkoIB%!PFnps&5cM-~+cWs%l_jFXS3<-rjK0uX^E{w{JY^ zpJV!;0_}5Y;M)C0AN9CkWt()&pp-|iZY4~YGnz`d;p`ZN5Vn{A8=EaCK^tIXh@5&j zBc`i;(m$7!vHv*h4W`SYF5(T$=M@q39#alJ9`|K3pmmbetQ>(L^y6q}TL7wZK*TJ( zE0`A)%KZj{@UdbTJNv>{$9}xpHuP7U5WbKOxbH{rT{j$6TB7S0RG}x{li|~V{2j7! za6%@|bk|;?`$igf(va+ihc3rk7Ss@!>#L+rs0Wcm&4{(O$|Z55ToSj*l_@nf=t@mK zSZdmQqddgi?mFc{MBTa-rkUL5aEGx!1TRC|2P|)`@wR?1{pHJzZ)>Yx zHr>|B6Vfa>6VJ|P0S+;r(kY43GzS=qfhB|l=fz&&B2jU;Y>`ryWq8@D5*UT5o(-mx za0zhkX1evf;eN~;-a+?)1%9KH`uLJ*d(B_6g%w|*D+RD%R@zdg!zeqzC;He3rzp4z z=4gZRFBIn?WW8WsJK%bH;lGZUV(agKbbxJ>lza18E;ks^u)`w%#@=hYdFtj_TWOKX(v?7FbF01gVfZPoj{WCEOEhw}Ne+5M9 zq6ucqH-{|v;H|Ce$NrDS2b-jy-1aWvn^!FG8Wq!@1Y1k^E^kmL$V|nXj1ejD#wFVlqjcC z*d=AERg6!=ey6LFH*DR~b?ca|f2U)~3f{C?dS7^Dw-X`nRCy~K2G#JUn5@0aZ(4L5wj z9Q1y2t*`zHEs3=0Kk@uc+9Uq6hcQ$B1HJ%6`1kR`wHW!9vS#PgEkXA#S>rQuKF!62 zG!MGKXnO$pQOtsv0NqxQL#ri3t+&|{b0&ZK*cTTbS{iGW)q=+9K0;&&vXCs4a0DSo zK?Ztr@VM1|iT!Mem5Z7d|D&0ZI4?3=cM5j5L$d5t9=P;u4#3eO7Nl>$t9I$lGb+mk z$zxPT3lqAi5ip`;WXj7A!4e`~hOwV}>V_8&o?1WhzhtQEY;}pqqQtA-QwP>rMDiuS zMQ4;LXQ48KDowg3j!=?fqOeGbj|s_hVVHtDpP?y&5t!*Su=xh>$Gv;-o8V~}+d`2O z4i1DN0u4n>V*=D74Yq)sF-_%UAtub1RUDUt=#$fmo@P5}C1coD4*VH_-^N5h#0|tR zE`I*2C-h;r*}ivMJ|gvr8%K@!iOC$?7jdB8$s8(j0rp1qKvTH$7v3YaBO4GvP6m1) zPRJpX-x6pP8$hkL2u7vHoI)^FDCt41+!#UyT*fHy z%N1_o zEnYjU-RB}8&e>l)AA=IBm}9;gm>`+&62)0ZxD9wX_(VW3D5!$cEliiCgrGo{y+Rvi z5m2=8fTd6Bv!TmPX;HHNY<>CE{A5!$l>GfLeZ=nHf4*){^8Py8;KZ6!JSVlSZ7p6GvP^&w1CG>&oeKq8f($LU&$b+8B3%c$AldGEUEmxGraRG6Hx}ghH@UDCu z+XD83Gp8K+OcvnDgg6J3IH7Ec6G#++KN(i@KzqVzS+vf>1rtiWKW^ePmya6tU%#ni z5`P)=uQ!`)ds!tEKmokX?^9u8ck_%+e|uAt3y`H{m0CctTP4nxj#&ECV^OOFp*_vd z6*xgrP{du(Zp1lK^h`2fH`5aF(GIRP1~dLNIuN}R$Bqr?IFze1E5B5}CSwy{_dJ*F zI6vMOI9w(0wHaOG^FjC`h_q@Wb{m)ISlfE_w(YxE-8nZxEFf-Y_&k)Ob-Q`oAd4>; zpEz>!agRLx{KA@{3D}tf4l$~d5(nGiEz%~I90c4V@Lzi-CKQ_1zy{=4D?Lp04V~L1 z9$}yT;j9~ZQ^v~r+`$(KDqLW1C@klM6p)2MXHV{$nvZfp&gwWbpy7fCK=oEi3sH$l0@M&j4YVrDz>n_9CidXa)WL>j>95)$F`@b76Gjd^0&mHcWLDdALZ&TTV27b8tk?{?JXj&e z!&pLXY=B;n5qD$6xk5>Emgasq{>!h}?WgoR%)?5qxX!j?qOp4@|M78G^p35gyd>sD zltbkafi#u}3<#h`1Ind^NEz&PnD^@S1}y-10CUvMr{p*UIfM2Vrrfk=Z7p|7to!J^ z`(Gb)W+_gmY!F4a@eA7IOfg_8%4hT*Pq7UsO-HL~egRD8a$1ief-1wei6W|1@16DI z?BLsVzZSY@r^NU{2ORfPYTvqQr+z$rvKpU>^Ge(lTN!(@$Pn>#!VDrA5U?`b-D2WX zLIATc&Z)%FO&36O7K*h%h8pPNIh=)K{7ub%fM^D(IKydBZiS@pu&k2`Z6fUfr*Asg zct}|qE<#sSBqd_H!`+DQq1pkrQ)2qeJ8ryo{)MI~!qR9^E`S6<^F%9X7D*A=M=wda z1O%k@KtQY}CN7GQ7K0wAh@KGK&FTLc><=V%Xnx|t35N&YuIs;u1Kd>_XIh zE-vw6{d^{y0g}`Jw+H-0FNhX1@h%=y3mT=o%sDF~M+d{K`oT5shXZ#R{oe|`tObB~ zNu;)EzryuHCB78d?54>DH_ctnO_MQ$Y|VTrms{$H@0xh#%5m>~ap0w;tsWR~N#Sg} zQHqIf5ak8=Tv${1yp{;aV3?-m43bMLt+QImbVbV5R#_K^vJ7Q2_0vK()eT#&jM(n~ z7%+d=#ERRxU-;_~k0tP?Hw9&&b4PK#DJIvY5GTZgCl9oEf_F>IIq97R>}--=uH5ZT z!bH6zIyK;HCfZa5PClT%7t5!3O=#@V0B>Bk92XQ52Y52cYMw0avJ0k}- zpH#G*c?jBm?nbmUOYA;p5{-XwM_g*g$L+c^T7Iz?`re~Ap!M|5<eE??aiU9ZLa3@iIP$?vkB4E9Wji&qVN{mM$dzEOF`D!g{+>iiuLNTCUhs5cd(o?CT!I0uswIW`ejIE;{_D}cBWL<4{= z;NO%7u=n~haRxy)8mmWaL|PGKqnQh~V7P`4!8K~V)l(8Hem?4{7kB=&>a0+erpl^f z8YQ@9C&5DDz88S!E6oM!KA55*uTvMxgcyGZQllVEGga_kS`|l6jSRk_FN|FEwtW*% zsWaAoHr!JQ?(YN{;v(LkwhNG|h>H`hqP`*d_f4!@b;g{t&q-N~=dwLr#6bt~fj!*Z zZgTOPg$yF4CB9^ZrmoseizBhowD`p;c=CB}It=OzqaT1#i06L{|M`nteh0e}+3Ol{Uz9mZvD za6E}AjR^%10WvMD3yGWCL*cg0TpY-ETTq>^9ANbTUO=J0o7{=N?Y8#Nt54X3w|uz; zNjhVk%coLYRt!c;^b6!rl5#N+G66i?*&dhCPj+~TN^k(cIO77Qe`Qjd+LTq$!Gt~E zsD@Gy9EPOn$Wu0cc!6{nueawkBtz_Am}q+kD0Hhk%vdwvtt$p^Z~MRfDw;P>;@x?d z9Q)j5S5!n47`UKIVFWaS5o83=4gf3aB;DZ-syxg~gcTt!$G~nZ0n(X?3nEDiF$i-I z0RQ@oIXHns1U##ErVFMYUK%NxoBzw zisk-;5b)1khN8S_7!d#B&*<~KPOAs~z{HfVr=57><`%0gi_?&~U_wo-rU-mmP-q88 zydla(xm$oxqKp>yvnJW_YBPTaCKjAO`k0TV&$a4IY=C?j-Fyn78*(X6NZ>_a^#P5E zH=WI7RWRX)lBiZdJ9NiS=Tjm`9Kr#ff`u%E?E;eEOeC31rC6GK^o1kuy6j`uKy#bn zHD0uQtI90dr5M`7m^_U$2V32SgA&S+k;e-U-CLSvN1O^|v3>Vo710cf$cFUS&xiAGJ-A^}rE6aYDZnQj9A zqQ7_-oOi^oKX0+GHNk>&{h-9cJ#KyB3pY{rU17dpcGSt#sATzgj90a$Y$^^Fo*R20 zQ&KYd-N5MH^2hDAt$w!O+}b3!&UNoa8a0TC5(VG(y!LYYMzRdH)b|Ethoil+BuHXPE?YN|`~b zlg37Om2+Yymc(5@h_@6Y%7Qh)11#?^AvsrOs1mooy!MepZl&Iq^GZ=h#(p!HkO=)y zGg6Mg5rk(thZspYGp&@VY*A7*l!q#P%1KR!tTCTD`yUVQ%FOLXMe4?#iD$b<5aGj{ z$yqPdx!!wzo{etBUEgBTgz4kWXuXms!mXL1-hbhv&!G3;z6Td7n;bX*_*ul^*(QLO zw|p@kE$^?u@1NGv^zaV6RQ=Dzs)S8Fc#hI#7sx5wZXu;X)lgAQ4NkK1n$e*Z3_b2arTb@T2<>a*u` zYegUM2EHuA-5Oo2L&^KQf1h2jsomXyj@AZpT#)o&6K=xG`ga$V71KdYAjf}3e%~J+ z!N<8sW&B%1@Or`~mMK5~D1x%BF2m<<{j#e8dnt#zp>kR$I482LW8I{&@A z`j=jvO+Z`D?3xSpiBBGF{qE)gEk&c+7$De5|5u@Ib!rti7h7bE>%QoijEaw)jLE>y~NmKPsJ~`2eKK>}XCz!@g>d zGu2X;gi-~*Y7ZI~Y`$tN{PU0NL-l3vpWcn_;3VqFmtC|3H^U`lc`jLALs~<106$lo zkHyO&X>~{FyoC&lOVrJIyaVr-Bz5hBi5N0fqq&YGNmpx z6X2;AIp3qyhZ!zE1TPsva;Dd%UDDSvWdB*@a-!sYt!Bau&C|P>Tz^ng?m_R5N4Mh) zh^v1JFYWQ=DVm&=+}k+Zr#byqNEZrwyEa9kcr7a0C4>jG2MX0u42j-q5wGYs{ zl$z=3ZfFp<_z!&N#$Tyua-Jn2$*QoBmWdn|e5phlb?g%|!Bm*bT1<-_+y-yI&LUn7 zV)Z3getPJQ*OGOuiWxH2w!weE9F>-CfXDX&O6gji*wcopg<=j2l0~H=qlHx3>|#*j zv06chR<0M2X;WBqJNPDly^%L9IQIOXUfXHt3VAbSrBs7BBT|uk_@}}Re zXh9}AcSD>Q%HQrfBc%rM%fBsoqI~OjCFfUhpo>aVX^)4k7hORqEfrgrDyum7l|`wj zzEu6GtO*e;p%G;~470LO-Y4!Ci1oULh6IyLN=ykAwxJxX1IYtIIZGs1n@hn4Sa{Rs zo;#HJv#jG(C2u-Why(YR z>gqTaRmZtK-e8jul`R%*bb}wunQ7sV7{^5>UUcdMH%I$d{P*fiXNXmcR=s&pxka~u zYHKoR)dAly$<0-%BtVOAP)*{R3isM;U-&S8Sg0v3Z-h00^3t(*Vy%wptq?!;xH)fx z<+@I9%7$_^L4_EKRsK|2KU8r5=Fz}DxZJR6cOl#BJ^eMC#2vS$-aR=po>Do>I|@S` z*l{c%63(U>*XE(;5JFNX{*XHEgB@q~puD(82dQ(850mv{1{GeX2>OlttJmrBUd{eX zHCNV6b3ENoj`1|dpHWfxeZ80V{f;86{vAXFdKm-l^$#7`{{F?jh7Ro9aUFf{iUEc9 ztN4TaDcYX2?WCjMReRs_`@N7j=&d)mo!*1$%x>*)w5WBBt99YI^Ejsz!++(3*HNTa zEwt8!(m}28xb_+2j8b&#i*=VH z6M1)CJ#va8k-M*^Slc)R(T2ZXdna;f7LDVty9A7;u!G92MJ>Zd}-cGPmvAf0JywCMR}Lezz3yOGdAt<-`7(fUevjnKvop1!91z4?y*-85yWA?={(Sg-Zo?>ne4Cr1;Sb zuP-~Z<(%TLsMGFx(6*PPYl+SQf^v}s65cF?5kI2@>4&s9XvtmUqnB>U@5OZHL0SW7 ztjUD67buTZ$9Al+3p5r}g&Kld>oF6a^_XB0BxcKTtQr2Su*jKf4TjQ~Ge;6S_t(#H z-P`x~J@&BbZperSt_lA1+q7EU6jb4&G=@QcH3nQxFUNIIFaPA6ov#}Yez?cp$_iw} z=htri>iy{)S=ZJ>$}D?0F6BTV27`WBX;pT*W_X5y6d8(le%|}RYYx2jr=A1^A6}-XAUS>1%sqZzLIU`vG)+7I z8pnTAr7I&|v+eb=L1T6qN@dLIVx&2ERKOx_}Fo?o8dc-#6u@xQD7y(yVx-?E>G- zJ7|siDIGF1T)qhB zXx$!nCYHnyzl4eY497J^11A22Q`aB2-?mx(Px@<$@c;D7u6VePDB-xQ_@#5lma@-D zfvw@vJRCUB6v`1uG^dF%6yzvFXQ;`ZN%eev9be^B0mIY7fBWZSL%%Tm(LJ?Zm($JA zDo(eb^yVv*qxreoK$@}ed|a5#2W8UUB#jMDw3+uQDkvFM1*K74LdnP_l$@P>qQJ#` zHQBEo_+oo!RMT6Lb7RfP=ZnN zY`+i!$rRKc<(jmN!=W9<%X38gtTTV_|IfSizPAv~bdRLYP3=jYo3PZmg)gjPjzv}J zzzd~Sxh7QgYR6TtrF+(#EpB{k+we)=`*rU-NMc`UdhM(P{}g!SDUI!;mBP^zaY}?` zaLEH2-{6QwFCWk1ISY1mK{r!vT-~8#b%5m+9@R!Jt2Ct!RPuV3Fmhc-UiRo5@!RhQ zKmURE>Qbk%1y!Vd2`Mf2YTD{Q%@okYws#O__M`+-L1R(kMyLD5M%DQ`a6Cgxe&~O%QQpk>#;66N09N-EZw(4;PhJppMx`IvQ)iif4)eudZoTT=&-*OXd&t{Kjz;th7v%oV zTrde5X)gsDAw67#00B;(!>C)NR6nOO!+GN9Y1?LJo|PTNHDo!~O1+d4aq7H83;#;B zbFyaPfo)WJ%AFtzHMFS2qf>y0`=(!(InvHkj_Nz&uduRHy+t>}B602fQ6I7WD5{k! z3JIhcE6;}$(0U*a(#R0q14VdaiL9XTJtDzNV-=#qK`e@cj&D4yiMH@V@0t5~&(C~4 zdUkQVsMX3_B>JYGKH(g2V?G{LWR2GKG=Oj zbl~0sxfY3Q>NdZ3_9#A|VW>y2#?EX&2seTtW*O$+A{;lxlDQF7LHY>T_{pb#eD{n! zD8%hyLY`QxF&O3il*eSPb)mrZl!HFr zcJ0S?73se^xW8V1z5ZujlNDo`TL0BxVE5p2&lmq;pVoT+b2sSq{Oyg25ZtVF(fkIL zsfwwq@7u{$>5Y;mS>M5=(08!IpaNIR2i*MV_t$*^&X9U`cI-G`oZs@p_2XAg(wm~? zLuCy1ov(@}g2+I<%3lZBlS+t8rS2Rs^X@f``~TF1-Z$lj3&dU*zj8|Spf@$C#uDlu z11^^@kcej6F*H-_QjlBXg>tRUl!cviJ+JET)L?*g&c$(Q#3a`7!)0yxweeW_gI(CUwouqlLcyhyCvg-{kv zF51!Vhe1yZ*`KI{bo52y2ZxTk`{4_j-_shA@ zy<(-2xK?Qc%>wHfte?>w3<)TuY7BNxva<%$ z%e=HCu1Ajgto++o?I~KXC@+#mxSs&a`20dB0Vgub0=`a-UKS1IqOilfN#!uVsb|&2 zPz+?ipN5-vsrZKTs?W}?PIj7o#+s^9f}nwP_L?|Ax#2Q#;;!cpG31z1 zOk=aIt0Xor6W6vauYTo&`*e`d4nYq!ozQ}TQpxGWs(%bi-|oDYmsLhYtfl0+hUMacw{?5$%UBz1YZB155;7B4i6ehs^HX2%)ch`rkm6`^YJw0~ zI+(H!AR){=81-dR$%G`R1=ZWpS}zsODl=Rs`d&Kku1NV&Ivpt#ewKe(PaHoqaJ=lC z;PXvl#no*q*AIVC=j+;6F=dThYdRH#t7wOuAB2ewAa0sJ6Gyw?gdW_nAui1Z>5xdW zD0`xKYA>jG+KKy6Zn#;T*z43MANcVkz2upt62J#TXc1CSghud)UXihIUeU;LEQ1Sh zCkeBil9Y)FVLaCoi*1}Yi%U;_$LaWJV$oUo+;|m>6v~{(a3ll=@=j*T*NO8C=Pdj# zx~MbwgnIajb!Kpik)bI@pn4?;;miXe_9to#r`(C>2V+mfIgN{Y>s{Y^F}rd?>}lSq zd(Sn|6Jv8l6LCRp%TSYV)u6rA%Z|XBg5CS*oyrFB!WECcaL@P8uz zSAwMsPp0$K@0YvFyU^FT(Npa z2Y+;~H7SJBLKr5ajMNu9jWdr{R}dPCA*p&w@ng(kJWJ=pPz+h{1UQ^3#S*^tv*EYE zRfUGNU)MsDn^Xg#j8GKH5qbi3P>&EUIF4(GlDsy~?PAS0JaR@jJ}{Y&pqZwx6P?Fx zA{+kEYuM$|iS&?21H4k3mhZGmc`)y`!(3y3E(XlPT2Ec&wW;jMV#jD>?0Ay=^h z%x&VZvHLDr`-C3P09xoV43HWiL(Zkjr zJ2Jmj=)~Xaq>}!EhuarFl_+AwTtZQPNPLuy%fm%Ysu}p+qc++P5c`i29FqsSnt4R0KRIctV#`o0g zzaJKly!_2a;=PT!6qnqkvSu8)@)xbpqcuPuCju1<>74%KtA zK$y+iASBud2aSa6FeL>t zNg+jBr9`F#=318$xWQYk_!H1jN-RN^zu_N!)bL%s$~*alJt}^E`^cYWe9tQ{>x#adK5M+{XQVz;2CQM;W$W24;x!&(ZmgY)I=W5472ropceK3`0+0t z*!ZV--|l(unrFCl+nmWuO%0l&f@Et9qSj=Gf<-kT8s&72H|FpB^mwJ(--)%yqUE4Z za1eIF9L$BCB@EMvGoBJ}9Q&Vde`D9^#58OuY6&Kj(Kht#BDFil)({UNlh?2}96&O% zT3bv~iR?5P`Oriip*yuxCMnyX`geE-glb6`*+g^YdT8un#D0lP(b{-ViP@t!o_^`l zO+^zasL~h~xu>gmO^2puQtgC_%+cw?&JIFMuy4>AqhlO&g@DB;jBE#M<1$wisq;CTr^Q9@ zFS)tp$tsj_6IgP(PLwa8nmwv(6P8fMpezZ~0=37=dlbyVcysZhU zxux4ww%+iG!7+wSN(v#+4B)IMoef5xIg~DmsB(0+J;~8^GrTO;&p3bDN8SHMwrtO94p!6&*4KMrcPXboS>;xa zGb0z!Of(J4CUM$E_HW0Xtrs-m;6WlaM{LgF9d67Cig1bqn;c0YZE+%C0e45?tFmT1 z!j0Vb_St9MJgNVF|3lc7vio~ke0J8yDSvZHXC9zd|7|KH;%h}BzDne|eFd|vMWSv?hx~e9~CcHKf55an^ z7JQ=4l;BFllEE}+iVB71>S{+Ph)MYhx@`EAJ^Zi{Egzo|kqiNmRNAPJ&T{n%5r6Jv@6Q!~*WYd}6sbRdDV3<`m9D7>WB zH{08i&M9-}_oj+kOGc@s4nI%#P%S5sj=@hNpX-t= zPX#g+qym|EJ!&MS0tFm=yDZ5{nI&0?IR0FREJ-e}5-xc)W(NH7#Sm;@L!Pv|!pqST zEO-GeKNgl38G5}TF0S}ixMkRFI(Zs~(U45?YgP+0N*GFRv<1UqD#ORx$-xbA0Q*v; z@{JS*|>emZ;cI(6dsRnWX zEk-wP+faVj)chfm#$gofstm?wqHxHH#?0DaGD*tm@kAzH_=wEl%tSI=s>MX*;t8K` ziCKqu<%OS&(>ps#iC7TC{Q|Z` zgG@4=zT1xAEnGW|?yMvfl9boKF>i~9KK%A$ExnG`ON%+uAO>;bkN_b-5EW8YAPyuE zdw6l#S6&}~KxfinZ;Rvq?p%K8(kP8r6Ng=N)%Ii8yplfzs9Fdl_9FFuG>`0<<%?U9K&Y>f^+6gli%7$5G0YjJvf_%M z#zws_j8Gj8Oa8&Gz$RsqP}Y=mm?kEPJdAk!?YwgCDzv2F%)%Qm@kZz|hyI7JL;Ey) zO9f6u)zB~bSZABsng{-pfg+P7#oS&D`*b)6E%1-w3ag$6Qt&gv!w*elWa5kQ$iJMf@5QQW*9+G)1 z{O8;yt#7_EzBlDXQo+hw$<_DMOmJ(<)cf@&ZlT!&)$30ao371-YkvZH|AVo3c_-Qa z42i}1lF$9EoBDg{`~N0$&38#dctErMH97ubvOIS`_44jA^-}v9Uu64vS2wNoi8v&A z)i%2r>2z@cgR)LIN4Gc23d#(hiiXIH#Jt+!dFNU$#_dL)Lby)nBxs^W1TWI1$flGT zJ`<1qE_CLi1od#(l*c1LI&u@jO{lxU<=$v;#j0|_bP#b|kuCl6y`HRxq5Gd5a>xx> zUx@3v9k{&jM;978napZW0ybX(_B0TUVm=E5e*z#aFh-p9A@NF{d;nK!0`&{Aap`|n zbUT60E`3Fc0uUhpzbD{o^aPv=0Nu+tAiUk07UIw;ml);}&bq=F*~t4Nkf_1%O-%=l zSv5E`{M3pCe{r+^dj0kK>(#+)n1X0fD34KQ*d=~{=cwnZYb*07$Tcv-r?j)?Aw8s! zr!~izkn>J6NJ(iv(aia3La|>wXN%9B@o&SPlODekx4-uD_22)8s*vrI=jE7J-ETV$ zgoSvc5UNY^n2yW`8Y&w(SACsdRpZakIWdw#dS5u;Q7 zvHbj7SL@CBIuuq?(K&pWM1weA&`&Jo7Mi=PvXDq1`fCDae*dzn)1g~L*UdJ66u+JO zb+^Y76j{y*XT$U}kD5Ga?vOfD_)TsET z9s+9t4zbnJtQF!ci5MRYg#<}b6}kUbv8Kdp2f2i6c+BVE#$wjbuw}(RjFu z(eHLn(8w`(Sv$_kMC%jety&PDl!?^_TPI~=vVDO{SzaaO!4g~)bpch;V2e!lQfJP& zb6y_4`Jr8|ijJ`0O=M>D!OGH%lJ#fgzl!%AymRhp7cVOnoRUSTh$tpzJyN~X5*01j z!k^MvDU094NypC~ADP1y&22?fz(yYhB@yYG|1OztcKp&Xdt;76Tb z0jt!kx>ColOgh)|u!pl_Bh*i(jDOpU⁢K67S1af6ii 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

yu0dv@|z7yA!1LH1I##SD3(=6`^`DYW| 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�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

+}S)hp}C&Rl&)zdkH0`Zezk>g%^ypfpO%+a- z@^C)MY1**E6BCEH73NF|D71 zNL_$T)1ya?zX!ws6?3Id^Gj>+`ZW)4-^WES<8`lS>4&d=_VW{j&LB%iW}x|zae@di z#Oql;91}opm}*`kJjRuE3ZHJ`2T$UURXE5S%-`y*qMDSz>O#i2P{hnWtX9$ z{%y}tAEkN1y&dK^*Bq{QmE+_AUk$$F*?j1we2R_=NSLrekT9;r_Qqlnu^9By(emQe z|J^lqb!~1|4V!6p^m}S*NRkA4ZXC}=nTWTkRaCf7a!}?V;0~nd7d-e~aOL3N`)TuU zYrDJxwezyU(mYTN6SmQp6PU@gXV_F~hci*xP+uXfdOob7hu($xF3PKda_M=Fm zk(F@lVur{zFrzhA_;Dg+EjZs z?GpJmqZ&biPNR+B&gy2ETPLiN&`K+l8HY>krgC7etDrrAHK(1N$PVm4TPEnWkWIFm zuR7-WsyFm-(j8Tt63w2&>ewEA{(_`Tb}iQNa@aGw%ksmH&@(pyJE*lTA;l9CVS>6B zes)0utl*{SUz{kPfA+@apRc$o7q(g$oIh>a`b~666HErfpN=~13)+V+2B*C43-;Id zPdGZaWv68eR}i3lv-6^n0Rr+oe;-#_s9#co~SUO|4Ooj0L|;dp>6 zBkjnKNPhy+X8!p=A0VGbd3W7w*!l7EKZX}i*0acRG!zH|mS@+4sw^BYv$#d6E{^|p zdOdtG4DuE7HS0$oIZ{-aG*W&Zj{^2aVd04YHd06Tz5^5ro#dgrYvtp4<>z z2~4ADf^xu$g=B0#R9;k7&-h7Tj{GrT-8((XicI;I89eg)$bkC^uZFl)<>!+Vkg5$KXDptKN;*%J(@Z9C631_m_~LD;y8v zH`99o|FCLUnJe}cqkWw`c4N!bV;0|_Oh;WhA(e*%sEU%0mldK3iN$d?5U|(Cf+4HK z?*puvA$|GbJoyiAv;{Cmek8)gLC2ZTG? z!+}@C5`0*Mq(PVrtB@MgLf5A0^3n~=dk6PP2Zzc!nv>LYCLIu&IJ68VHB3l|rB!u& z+ANCQDMkaa`7EDKoCM$a>cYug&YklQs<{LdUv_P<{CgZq@0FHY^8)`y}a5 z6FbrZ8%hWwmt^TCXtvrE#zz=j#cQ+zdBx?X*^G}-Z^&5c!?C|DLN4A*Po7 zL#focFR!XcPU?j7l^FuEaO?A9oG<>Q05dyu?=TV_WM?OOB!=e_(He$tjBtFTi{WED z7eW!=Z1}#FLx<8~HVTerNzxesmESf zt3;_fTs8_YWvzHlp=JfBS~p!!xdL@Q+EeSJs&KGNDZa~RxWb7joBM~bxSdssyN!fU z^O&~}pZ4I918jGfj zNBCz)BP&%@sAN_0;J+@JC(kI2t!1Ws)7_g6|8{|1RRn2Ld9wkSU6oipWx`s*0)(ru zxGP|BwN}SMo^T6Q>nCZ#2={s1wTWoyDut+!9M(k=4p0h#q4J=Zy9yMP19E^xhd%h& z)*;npomUkxTV8Y9u6y@ea*rW9wo@>oLs`fHo+G;ktDPp+kndU3U=R-v;=KalK!5Nd z*3F6`fs2FL8a0k7%!-l_Yek}&lo#b?CT%$Ei$m`{t-BH=-+4DeklwQr%>nE1b_3oH z*m-$Yi|o{J@pbKrf({2fe;0eg^o1|aWRT?1`VIZ(jGnM<_lduu`23webMH|P_xpB; z30YE}${&`j{4x2;vcc&6odew8o7et);e7o4*iB#VKl`pjk0bH-%fG#6?zeLSzG3+L z-Fj{P>51^ezg~pDpZM>ZJI1~*f7yn=Kjid9JA2+xJ-t8ve*A#Rjq`46-aZ_Ezw+bJ z=gz*dy8oN_`{jl?^6(q#92>vBu%viUXrGams_T86^w|B~Aaoa!8T(#lh>AAEeR zUZjz%sqs0LiZ46ltZS@sV_^z_)l!7cKuA|9>dul+Fw;fklxTU^BiDTN+U9FZOkCy* z!ZnvNu{7g`Z{CN=%Q6Eazj4`Q>qoCv;XSz&%N8FCZ>Z7DHb4}tkrP4SHxL6X#Fa5& zAtQuw&^J2@?#+Ymz9y%U{SraaG?)qKC?Lo3bV!$k$7WO^ zWXkYlsYsiwv?BEYS>z#-Rfht#)^DN-BP4Zb01~(`S8JpLw10~7uL|&?Za#gwuwuxH z^3Ga%8)bX^rE#O*IyHB~nx-G9$|dasAaMfk%wko+q?Uz5ugpBC6-X9NS-{Da!@hd| zOYVshk}KL0pg)}wyK@P#Ir*s>Uz5vTR+Q@ccg=w>#F8u!kOL&s;AtS52yIYAzufLK z*TFBX9)DB$;(z>)c3*cqJiOh{?RDk`W}I$@h}`vh(I&>ZNQ5l{<{y#I``s|Db@-D?h9aL)q%g__xF{>YDrSO!YmSc% z#e{I0c{MLNB8mdT@8ZLKCE+fq!Z;R5rBNYg+*nsB64KB^vcXgi_Y?&M=Qtfj!96nu3d?o96g+= zxzafW=;2(~fgVmo-qT$1^{*!?TE+JhxJUV%+=v4jXc7!}Z+L`ORzmXC^8 zT|4s+Pi=PviRHsv#c*7Ok!fQw!0$ZlZM}t4F^VQ$wdSI`>pEXidBF5O?{2?DVKvGX z_en=yD_KaJFA_#)IE>$DWCDZNS-(8a@wltoUja7G&Y$tJ@%E!<9kW`|gfW(By-KrE z)fw(pZ3fD3qHlZZyXJW-MwcD-&nl{$%FITmEs(DpchS7H z?@rK*XAt#XI3$3<58=sx{s3r?XW|#hJI$y3IK0pFE=qji2fV#i^FFtWC*_Z(pZ4*w ztL=K;5@B-(8_`k%3DU4GH$l2RKp%}2Md%~)5VBVcj%=Y!CXTdq-&U!~cx6J}=vUJf z#YRv7jlhg3_mSZYRx}IYI}r1v&Up2?jpI)8{|U^Ot@2X?W;uVdJe#|w=Vt=R>uMIv z2|}j1@p4u;QVDGV%2Bq4l>{y%rWZS^M9L6n4rNH`dHY3jwc$a>em{)TNgg;r_za>H z2~!Ax72(x}sbt%cUEcr~u{T;UWm!R%Q{{Kg|77&6!|u!-d7W9d;#InV8eyGNzEY7} zz5?>0cDjaU1z4S(hoifKrrZ}^eP=0=#Z%>v_K*+0Sut0~$RZso;s=z8oXLpM^kB3? zbtlN-hly=6%As%+f}=GHV4-uvsq&zqpMThA?&W$Ts(pkhVVnVrd@3qD?&9Jg7=3EW ziGz?dhMlI7mAVyQrA{QL%6*T2?CX239I5v}jP97^YG`ydf$WTrh3tt!F~()N;WYWF z`{rN$#*&8%Zdh%?4rdaB4IGVI)#?!hHk=3ng8(KbZ5}~DERF!jnx~bm?MK$FbPzVS zl@4MAYkQIPhXadT8~Xf_b~+~ioE?jhy5`?%8|O4Rweii0&(HpMDaoANN@lB%Z2-m0 zxpZ$Jv55c=dNk-IO6HQFoF-G63H?C`uLsjPsLlj%CI1w<>8lfuXMWj`#{Ry%HM>PN zcj;)wTP&Zy?0}5}?$fs!Flj;BI!5dqXyw6cl$VRMz?>9{p#^D^-7B}=82DG0vi$2u z#}Mo+*tEN}i#`fFs{;FPM{_za<>Q$)leHX(S^5SLRjbjH%c+V+0 zW0v$XfP$K@0VO0X2)*GO&SrK>$h;b-g&7s*A`f}*Z6KBYarf*u`PSZ=P2gX$IMO>2bmuzec8%ju6T+UkWP2)WP-3CkT zH?jMUlfPWrcVCDCW?SKB%AF5BEA^60IrVbDxiCZe6jlVjCz=`+h-?lej4xL| z&)4h=!rf+w{6mvO4bvKA5!tp+%|28`LsA){R8pA>qtqrrO7gjITjG}^>L@_3EqN=x{U1~U{S>PI*BsXA$MBF%a zNmi(48zjX!Q+?Y}!SM{}-6E(l`hLwj;`&ck_EG`~C%uyY(f;x!I^ty9W}7g##n{VH z!_GJuwaMd$v(M#LaF!6 ziBD`Ze{#|5_qclegZlhyU!$n9c1P1ia`2p{PlMOos#*Aosu2dPfmy(Do$_Fy$*R*| zs5xM^bfnMZBB&%1$kVUzH5Cy$yhI+I+!d+YT3<|tD6167=5|q?U4B$BQOpW(K7$LVnUwH58Z?@9Fj_bq7qc3JlKdQFVvJ+JmSgXZ_OpTAx9=MIH|ophf5 z1!Aze8VbX7wQM~-Z|AsF-Z6jegPc{V7P(m4H5|2!(CT9uQH+H+Mq(lNxH%lFHUuWXJ-7%k~!Iy_2u%v=l*oajDyDM#J3f@GorO9BNU>$9rNB9 zV@-Fi?$Wi>F6m17khAW-FFtv#UP`v(m|w;+&$a7IhUHr{&@yL-{9;bPFSep(&X0Y1 z|4D$5GQ+iUU*|i=emeA1jVDnsiob@9f%707?tw^uo|~wyZhUUBK$1P&bsIYiI^la(*B zVc-@7c-*%FqGg5~<)zoW{n6>)n{*PuWGJG{60j@`DQQT+&5JUU6>X5EvSP(8152su zDL33CFCKBlGdI3DMlYnRx{4NxAd%ipf~_qI-+GfgVB6b09z5o*+@4TakUgvK=2B-U zB#$OY@@UQh~X9`R_jv(fQNnj%^BeE#wmOI$S zYMjE%kFJp`KX~Tlc@H<}MUph!?deie6RhF^yI@cUK5hqc5rJ?QD~h;w6~xb|`ar%K z)@F-o>Fa7tO5u~yUGkaFu?PAeh*i-a(0T-8jFjdX$#Mn`Gh9XfqrhmOAPUGmp6 zUs>>D?L~TUA8m!CnlTBnvT=N?R}wP$L~U6QrDv28?GHo(8)d)_-SbM#O_ttI?4Je{ zU5R|{{W`?V-6xOS`rok!Kea^j@H&}>s_*;@svGQ<7yL*+BKj@^7tJ`8WMND+dhYv@o6uWAbP*AfN8KdiDvriHwfp%4Y> zS(AwIP$G>DsJJVO2@=^jS<*%omO6`=df1(cFa9T{WyoxOB2|#%3{*) zL!N~Jrhx#A$rp=Bl-Ezt#ZRFR$mfSs)$D1r_l z?sa8)m%2_K{1Gwa?&XTJf4g?#)e{mT0KQL2lxBlxHf$R4=vBr&J^m`K`1Vqb+5Kjt zyestb{SV9^fp@p<3!kY#4~Zr%9zM*rwBsKU7G-J2qK>fP)87IV!mw&Q+{gxEwd_Ku z$)$)qOap$4*u;Q5DZ&s~Xo65GUV%CptYo(%li3b6rANciS{EDwE0OqOrw5yBT32$9r9V+EeW{k#s#HIE6-7N(-sl{6=UC z5-wLg6LE?n*T~yIZ9gU?H+lSS!srwlDsVIt??TFuY=eDPN_x;Nn{TaFC z!}qRyH*}8f+FS2+vjUf7!=8F1_~Rl``HP1SBL_J{@IaW-JEw7c7(+G9mEIagYC>{p zF^tox_j-JpSYMiu+3&~_1k$eFvpTftpOtq#A`c$iht>g0*u;2*lPW6U=y#-^ zS7}$qAp0yrBPfTsQTPXCHevZqP^GoA{Ek@t;B)%yYZ&;ma`z|bi-ax}de6(AD}4;B z-XRXcp0omOn1uP}(k!-O$_7|MtsEgfgO8_%EWT*n-v2+Lofl=aErp`@>Z| zbZ+-~x&Q2wfBNkM-I%*ABMAh|3RHN6J#66X{PfEgN2n#{-0ZIC-6-=A*HEOit$vFx^I4Ild8Dk|GHDfIBPI1urwaD4(z@wEc|nlXClzU$TUO z@PG#c+fn76Qj|!V=o*@57t$O@FSqUJWOTY?#_Ma=#C*vK-&%(EsC0|G(w7 zO^(KGq)@S3sF8cyO*EP#heo4No+fASD^w;1$n|gJa=r<{eeh948Wce}SJ{HJs zot|KYR;tggSO5Lm^xo;>JmuAZuO<08+HOywC)OK#rV2s-VR+bIk7HKdbW-`s>-!~W z3Oxn?U~_K-DYy-LB(pWg7n|`v`)qXn;b)J_K2O`YyvN_IxgJBFa13~Tuq6BYGc})I zyy>v4S$NP=^!_FZb(Eu)XCJTBEP?|r%j9k*UY*Li~G{+07BuNs%Pz1^>4 zNC+0y9oa!gQO-9C>n_jhll>ff;(o34|9(Wj2f8>>`aRy;md;`hq9sT{2X z6*+ZWQT_d2n*OA%iLKZ9-hMjA>mB#>F@@1@$gTOWe^&<~rPN+Rdyc@rdy3}wf9RxX zT6t~<MdJ+n@}(e9W}LBaf9J1$DZ{0DFTZar@OWbf=lQc=y$(dr_nLqF(5Xt$2GIo(Ej5?f0{J1@bWbtQi+$Rrkvp$4oSsPyQxzpD)Xw z(`N+VsgB@1ISPsff0dN;VSuvouE-qEG zNj5@JT11(*Yx~=Lcc~5)?>=q8vWY7mx<@&_-0+ILzE|y{pVR!w>(h4$k;AkQ1Cd;s z2{Un!sVjhqE)NvQ9!p5DT#x`7c;K&ahIb+l2nb{{Rp=r+t-zZo5X*`H)i4VG3lN~F z4#)syZff^p!H)+NLaoTk0FcMmhidi8|qfSLL()HDdD9cM8tn=K~$;=;b(I z_QuUMG=TUL;;uEj;d2*J6i_T3mS{}}tkMEH`jxdNSXo5Zg424_IeTWIgHV*ak(Vyux(oT60f5E~_&%Oljxu?kZL?r`=7ubkSy$qo` zgtRv>qC`bGiH3ylZo0nG>#Lhl>n3SIpi~M)kWz|J=PyjO|Efc}H%v-)8}L6S=kvP! z-F57s4;~kF!rASTd}owT(Acy$3bBPwK1`t+!3tO+2pY)&kAfMA>|D3H`mfmdce&v$ z`I!+{`7Zl%l_oi%<@G@GqKbz&P1-@we#sPN;(!blKmzP4>JBAItgv-Nap==CEvif& z8fp(W4X9E9z6KuZ?nsr^M29c%NsSxE_U>$i;ahUWLjz~YpPZlrp&%?OzGe`$f_y^? z(`Q8jRQ`bS_V#H#whq*h1F# z<$;?|8}!RN$11C+wq^u|-qE^^n4d$jNCeig3S5{C;}u+{OqTISU@{^ZK;W77hJ>>sL z_TeM>*xre2ryV*;Z>~~v%af#Pp7OWyA;^e`=zrRosfJI{o2fpQ@A199{Eu5#?UezW zPMQ&{FZFh=dj7*5qeop%2{ z2S2*&iY_A!|GWyB5pAzACxv! zKunPyihObc#mJ7vsgfZ$r!#QBj#^^PWvl)>wn~8}ypYopKDH5-dcbAJ8jp#RI^Rtt zhYWx2iEAcw>#HP@KEaJT;8(Q$1?yKIFf98D{?Lp(+L^th48NT{xh!jIP#NRpszjc0 z{3Dx3(POW=b%%1kXyg17-`n`Y+b84qTEt>mdG_y>8NQHrHf*_n`LL5Tpo>gSHpxT) zv_%BqlbZSfG#u?tL10=jsU`H_lp2^k94O|6j2N}mqtgIfaEau5Wri>1Ia{Ky#gBL- zH(rBHJHI=Jj|Y-cgj9SyCMO^EU?$(%86qp<&j7O+l5d-l`z5TZL<24#4tIc|s>$cvvvSHOv&tK!5sW$*L#d2xB@!YBs*n%ou846WYf(Q?+s{`O7y<)>g zJR%!(;qm{Km!EUqf3I8+%YDoW=j<%IHQRhP8A18lLa~Mu89*4shSgC^R!o;70RXZE zaSEv|=^8eQjR^}x98=!rCudd-Q`hm$7&Iy?uUhIdRc`oEHm<*N&~Nv4RI1RREmdex zN)-q@E>*~?MhIg%Z@u~j-BJZTzV07o`-7{V3cXUyfF%vHwBy z=s91bt!*Y+Pn$SYZ+G5|{C?n373CewNI%Q#k2q}p?%QHTMJ#YpHUz4YGn--&U?MgO zOw`RLIYHX{pv7+8uJ?<4PoHBhI3e)7W)kR_0!E!rnyAv6?1%F|} zs;U#)lAOp%bv_CP*%Ud<4tQef012y`ZacDT^9U8P@pa8s?}`2*FS`G$ew#1;qf}o8 zrrMNu>M;OYFQ(_wkXHS#5H=D11%Sbz3(fM?d8#=R2}6nY7#!+M#FKv#w>K!2z?De| zb|)Na&jn+oaZR8Ch|du@F1+;iH+i)2#ncCm^ObaBDK>Z}LlL2(r7*-sF+*$=r)boe zVi!IAJjZeOuD0~-NKxvIMazoeD zxVxP2jJ?7 z^_(Cjm`H}ALpQkNW7UT_V7wf-SZM;ftgt!A6agX-!l?QV#12CXqWQxz@1e~ni9L#U z!n&opPI>5><(D0fxR+n5bKd-c?qyoGMHrgCods(b3AH>ziS zKk~6$|4^O7N(*XYUQ>~Y#v^Q8h&2$#G(|+1io)Ej644L8f}|Eu z86{frP+9{P1}UaLVTP+R%vkY|7{D(P_AE|%$Y0}k(fQcw%`|oDB#S_05L zReOza;|0&`D7bg(7FH;|GXqA!LWU#~GUVAtMq)i_%Gk^|NeeD)OV^EZjXY9+0m~~% z6K1$Z`&mg%`khH*0bX8YN_yg%7LtIwZ(;RX%nzGv#ZgJxI%&T)E=$KkE2b4e}u zarjsUR8f+V7^BQRAUli`)6log9Iu3Bva`}kLOY5GYi(5dbuUetSiaw1yoi+<`lqJu zm?L!i#iBR!niYm&^gIb8^cDP4Ou#;)1O5$*Cysk>!sioi{>sq36Q5g`EF9$lsinu* zXFm7B4LWhDj+{WklqQ@h$oDhBB~f9>&yz}qW{+a|(*oPzsnlpH1LZO5#W_Q!&UpEj zvO*^kb&Y?S21#0$H3SEw2LJY%(|m5LjuisrbV2M0w+!h3?}0ch@cd|j4J8DTOR{tm zq|dkE58!-cSG1(5sUYdq+>9b{;QsJQ!Ymc_hJf_iv;a~nGZsu9Iw1Ak%h#T=epIVY z;8h>llI6W*q=&NNXzC}R=;XGmJnx|2dCY#{kA2?lQF>L}z!bUrsvd`)f4p8e&F1z{ zK�Cvd=gIGfm6V55i_)z~^rUjAB&VkzJ2HY~E!(yLG}~Qa_V{ssG-*>gHwrkI?Bj z)(G3kgjxxE7-ZVOXz|C{5ZAyJgWc$27?e8u$VpSL4&18~3neXi9>Hn#@?4!{MWk!5jvAp3DWBXB_U1Uyza;?Vvy znyF~2vwKx4nNJrUiaUm9GfS01InBF{W_Ss2J zb+(&&`m)g%-V@3bqSX@t+nu<>}a&IRci zaE2v&TvpMLq6gR;6NdCZ;fNPD_8jy-98{r4aCS(lf7d@+m)`WG4)nJ}1$qVZ^j9d* zLw?Rq)Sxdn3{9Q#z%4tts8Dc*EH?xSP@0h1F$A^S(xMbVQ?)K3u>xdJXyGL96px6* zX>2%~UV)&^b6;FOJtF<(-rLYrYSS&gp?9CC0gh#gnt&1->;v%Gg5r0h)FiM>7!qRT zAaFoNhoowF6nm!MjketzodP21dn(Fj2^jc&>dIW7nTK z0PSxJq1kH!uBos-h_)%se(m-njN13(J~N?52V2eea~Fl-sb>#;8BT_d#bnD^Wy6-4>NawA_(Lqh=sQi@#tY~LwgMp1vD(SK9 zY;SBYz;$RnKGEYxJuJ2COtJD>Hm36+?7-Dba_mCF3T%EiFz|6Ip0L7z#56v(kk9C> zgO#Qpy+yzo5`3?kWg1|S1BKy%Ca2I?&54Shd*O)mk*RMT-NPdrXXqv02!%lwJcfv0 zaztZ%Oa$e3_$iSl)e%#eNCOB=zy}5x=&dNYOGP4nf}Y|X+VGf%@1}%RZgpi};0nz| zw0JARvYr3*n7o%iGBs=Y0fK7SMWNrx{s5REs1qA0i{2ZpV;@gZ_uZAIl9#q>?xOtNXnZ>^fepxs{1V2ofzj0mL*K zOi~t(k0&HeCeoYq1-${Yb3_S&<|9(Kk6AqR-lEGMNfrm@En=4KJnB8k)ueRh$u-Q z$|WZs_OT)>1-K~3H{#OetWajZ!M~jG@|{QY=(hR3M1lS9=+rx#PPtq$o#I}pt+#fPb6`7ckdEe$I>A+@D? z=#qB^Zq9{$t8<~v>2yc4-$>&-)vmYTk4>dr04b=w>Hpl(@-A7kpmTh=dM+iSsIuQr6hhL{Lk z_gRgQRSph6)X-=}JdGZ(FcqT?QEsrO?3ehS+c`@~@njT_{1QpfGzgc{QC4VFaZ=!c zVFUdd#3_(ZnhOrFAaFn^7LCL4?D0#sMuBDEpXfpXJ-Y+aNjL$uM1Z6R4g2_xht@yQ z<WzNs#mp(Q%X27CFFP}6eckRf_`9WCS9wo|$!{}%#0+|yeIErW=1cXTc)pPp# z(&#QHrPfS(e3En18>IxjGH6DHu_YE~!&4ZsDFCjy9_T8Xff^zc&eR!oj^=y2kub9+ z66S8tTUNZhM|IoigjDZGdo&!_vUTsG2xiR_1r#2ol67Q)gK52v;(~+ry$KGM8BRn%D{oI_;za4^=w2{r|o zOQI)$^H5$@POA0T)y<$JXYYAi+^IdbyDD$I<(PakTpA~kLp;HtwF44D|NmL{`JSs! z9bA&BOr@5E-J|Y1{^3&RvAmV%rA$}Nc(MYPatA^CoQcY@ccZd0^^~>W_uq{?U#||T zltze_(g;!92vOV!HjELVnoEtmlo_g0_5<(i`qRpw-Xk_fnfWZu3Id9MC{iQ#Dwbyj zE`;g`Dey5h)#o%@=xHGUu_Z`!Ao?bSCK?Mm4n2wDnaHQ4&i=gLHs+aBjwM!1l_TMB zvkj~uuxR;2p8yMOFKQLbzZ-ShNvobVXic(i`^Y3KGfYjL{o||8t{8oF!Kq-SFb8)= z1rDY`W9T#SXz&+S{|ug!fXr|W3nk;*M<$W<&d(021O{{TQ{2)cB^vFg+_Pf(LTec|ZbF{O}sU{EGz{{hWM5S-|WMZypi zL`DDju+lWgOF~SH2@)LvIZdw;Pf$OoNC95c z98{g`jz!_~#x@e+XQN!L%L!*!G?;Vyg`ZG>QdS|syDR-3@ODnEzk()-Br9+Yt$r>D z%nwY<&VMtFX9gP%D$B+z6^?xu8WUiUdxcmurq)s_(0q?*W)z!K zX!J6Iq(inBX>W0iVB0;CvD^tl5?Ymzn`bIIAYI^)x57H}ZPX=#ly6!DM%KRn?7|z= z>~ozxpLVbW$yBC;=-lV^N0V0o7I3jzDW^o})GGIc7Kw zi!jG3_Qo9Zrfv_J)=lN>^=ucH_E8BEX=yJWM(cvV}+?;>3x38-SC&%VyOt7iTVbl!T$%+_^ zoH-0e-wyO--R46DKFltLK8wN)B`oj2hT!zhnj_5s=sz9$;4=upq%i0Qcft-lbS7SBL=zM*_(A{5ppD#^h! zJHts%j0wtt6mrWY2g`$)9?dm)X7Q3Jm_#W|IXICPnJ7yqJpm+tm!!-R2rh!>wB%d! zgKAmWQq$^XhS{m+vGmJBdOwmkif_(PM1Tw$VWPoGLPv3TQ#MPYvOp5t)3NlXQCx73KlkId_a&Gue4`ZbS)t>gIZ{w~0fvKruliEF2#KPGln;1h%$FCC9vX#_eY>8@Hcw z6%#tx8dM!!EeBPG-&HR^@urX7IEPh^Kf!+12Yq^PSvw&eUpOVT^6~i9>ozY^;PE*~ zlw86iGpONDFapC%EE}$kNr6}e3zXg(>I83$6Jva3Oi+omXBi|=s?q87@QSolwsjJ0 zuK0@F9VmD8w*O5rZ;HlB@4XbU7*gQ(i~Sv~R*YV1x7$9BFY1kyQ&QmwdDCxq9;KJI zw{jB0bBQP&W}^(>7-0#5;uFzo2vUg@Q)^RZU{Za5Joxwn)(qAAF_U|y(^m<(Gpl2v z;!Yk+dAG;5y>Y^4`IZ|ko3{-MsyWijN*1mK6?>>%Hykn+7(UeG6=Fdq$bp51!h~@- zNEUl8lEto|h19O9f)?^lgP&{GY*7Hh7ih7DA#Jcsdjd2?S>XHh%I%*@`=`=cn!)pX zWN#;R(QCX=YS;7!F1ph|>P?pp_+(}~YpTtl=n3-ZI)g!hO~TwYSX3%nJ#H$xV3hmJ zkyCEz_h(@k$_;GF`(&THg@e!3OFg#7K~I?LeQaY5BhIe_Nm2>fy!olJ z_);{8cJD@0(L1}#in5IwQfC_HZm)OTtg(y=V$cJoUmbArbR;N@HPv2$iwT_63Uz_7 zNeQe+&`&@!5P3WgC_s^RPNrhKGf45FpM>?vB30h+sL_}9j`k|u8f#2_%}ZMkeEEW0 z)S-f~%}Vc)bv!#mWYxJnvy$=4+9{%%?@*G7(6DbCX*-iSM6qoEs~PD~NK7r6kCm#x zpf^DO@}KGTAdRKK2(fCWVNO~0`@=O`rCgw|?(H|Fwrpo_JMyd2YODcCSm#z)o3c(> zOntQlf-`Vgb@~SakWplBYOKo)TksEi57tyVsE zA|y6p&fWo5Xse1Nla6pAgrmUH50DC*dqCg+q{G!$1+%vX)uYR&b~R@0TK;41);E;n z%MFoK;MP@dy?aN69_A;FJRyvDZEB6Pq@$6Q0z!fZHc1#~TQeU+_)D7T19 zaiL@L;00fs?*FV;d1p(gYg3V34XvqQudT23yW;kIRTw!cU9b)iIMy~l+X$yZSf-+8 zG$eCfb23w`p)N21NMMrIt==I!sNNxIDvt{&+Kz-(N(*AgalUxiRhPeU)C~=|cMo~wgpqZkYZ#(y(QEKSuI3AVhzv@CdI_6DX zbgu5__and8-MZ9JD!3#~kEr~`iaK76-RZ=%Nd7p0dO(7Jj0u-!0aZ~hQak5=)UxFJ z-+Fc?$D6u8wx+_zt(v+$sT(h|AO<&&PXQ|dp5NMfZ(!C;Mu>ad#e}p@Pu;ZpoO$D1 zN9rtcBv#~1)8infDbYnzTrd~}KXg=|DdpWe%{E$`I^{g)rs;<%WAWl*o<1hIr~w5v z9#=H3Ja)h20Aq$07lQO<2a`|^{cnYfaz*1xX96WP{~&hF zBfa+j+gO`zsS6+ZVdQh?U4-}aFYB;HHJV9ii^cAu(G2U4B((ZLg&H4X>l*5KR*d@e zKq=4~#b=^DH8lk3iAFuV$U|BtilV9pzFDzi`^i0q8~$&A;F zqz*=)X23P3q5_GJbrNkg1(!P|UPlpDu}R?g`830aX)G-?_M6EwRt#BD-lNl-e(;2}D&Vhx{aMsob<|}) zPB^fkry}6E1lzsS@%v{m$@yuG~!b?DVK`|I3K z_Zw=yjNNmB9(dkZs& z^mA7I`vje-?7UR}t7iRtepJEP4=!}MSEQ)H6`+M%i9=*~@hk!x1V(3C$UBb!rdCo1 z2gsZMup2I@-qNF|mfj)$H(P**b@KV*9j-nXr1+;Tzb@~k_+z`YL&6eR2#`58$i-a8 zv#usC685oiAuk`Zk+#4CP_6>x>L5nvtxdN(A3puY?*CiZ0=0g8x#8l}cdp)p2fc80 zp&=c6NueB@hX=5tK((iaiN_(AOlJjh8p}!x9SfQqi>4j%c_Uh!^Un%Z^p&?oMD>;j#O*K(knvN_NpOa$njd8R?y^E5|kKDdaL z6KU{uxDPbD8ZDAdjgEV|7f|X|&^Ns^x=Kwie(1Gm!aL}T`EYw`5Dn*3N9AY9yEOIn zt6RJGQ440+INlz?r{(hAv@R3gD`6VVazlMp6q!Fb^0heAJ6yF0-y{Mq; zvK74HKCQ$SsB6e6UxAB7nh+W_g0I$Zqp?ya2z%Bg04@c8c9{;q zdwk@@E9eF7Cz-O@ptIHh(wv3Sz=Su2P(qu81W$p)lh1D{A}4)iYRvcEHy+u3SHXFE zm9>Th;AYvaATOqXHFZ2r;2C=`{1jTuD1$E$7KmbJLi2GTl!``_sok!W`yz%1IB!7- zB#+-s7@Y!SRS*NMMX*pWPyK#=zpuI;eY7q* z^P)yH9}dHpt-OB|$A6y~IUw)dU7yWBu?09Zh$zm%T`5==!fwZt}x)X;ag?s#gkr^~?q zVYYc@ps!3-u6?Vm?@1l$&IYwXhhSQF)|O9qHdw0e?0w~WmwHQT&X1#CTy)>!!spQn%fmecEjTfdYD)>|}n-5UPK+I zxfn!nPJ}2UEumNhK%o&oHPlg8(y^#&7mz<%G4+0)V%>BxcLfcjNMJA6tsVRY9eArg;>%dzyhQhJcuPmlF8--ZCeybncm0 zcs+$>2G285HVlGt;PQ$qd$=z(@YA1fc$4?$I+xjjWWw$gqe#RAy1qabQ-^cQib7d% z4oB8hG&p;|=okE|cgjk4gYQq>zH9Y!`&FH&x9FJR)diY_Sfa5BWwt944Glosu920X zsAq%=DS=BEb)%xuLYwOTRNvr~IZw^KTVrk(DX{_wKGW~A(YO}SVo`1pH06*U9WYEJ zRxm?O&N*-pXPp2TJp>UKVxS5Qa)-kBEc^@1QYL7ldfkCgq@mgvrzxuJ!s}YqmTJGf zHU=I@Eq?Vn+H9qB!rgq~BbGueB}} zIppSRx7$aS<$42%Q6}3DXosQpui%yORt7Pl%b!UanR^x&3RqZSEtS*cZ=x%`{! zQ-|DS8SZXV3M}r)$pUHk4+uMpVrt$BPrygjQ$9b^u~N!W@-QFNEuwc^7`?vFfd3&@ zbGhN6)Xpsr9rV`qD|M4&7St@za{!N)G^0{u?-Y-l6(!J#amH}bT(7HT`8TT zvJkC)2mRz_bH6zI7h69i6n53%9woKHL#Y#{JUpT7;AI7uv6PJeBJJ1^Pv^5hBvi4& zJ4AwM5&gx(FCR$_|M0aZUwCIuj&;#qfC9&K{WXw{S zVKd2259NtPQPO9hs!I=pK8#W}ztNIFhK*2X5b5Ud;? z6ag;E0=B+H9N>aEUeS|L!R5@f^32gf)NWUG(Y}C@KIh}IK0rUPvvEX^rJk}ZW{$dm z!0&I+I?Q6LNLb)~z{qGoU_$w%Z@EVfzRvjdwF|raT@^aY44YHS=N|63#rTNMt#*JR z!@2t$5`LN>lqnN`p2NOxrm1$YW%_mqlsrHnGpMoVchMAK46~vn#9Ez^2$f_nT5-TD z+f!Y0TK$tF3TvJYj=KaKywl1{YxORM=TbWk7+?M?!|0%D3uNLTArl8gQt*Yba10$X z(AAlA8_%U4+Vl1&@2^Yf zKv_#7#04W@8I}YPLWwP`<0GvEpx6Zd3-DQ?$68Wu_qo)j9lw0@j+rYszL&0x1;kOl z1P>;F`5fP<>__*c+=ZA3(jK~qa-tVfs}8$ixnv!o7n!nQ z)XD#2?@QpECiC|@!zqV&3y6q-EQ(0eyPkA98R#5LX*&ag8ZvEWCPUlQBrP*|A*rb7 zE-J1kt{1u<;I*jWiLUpxqIlnmiYMN>9{B%!-}gvRxpbU_(I zAlQg3G>AVU>0|Q#dVW6Q`ByOx>cZ_Yd_LiEfX^o6Z73{m_csUYSy|?zATL29E4|3N zU)BA(Uxn6rA`IYX!ZZW%_3q5yyCVzz>jwXtLtd;OuYY9AP4>n|`rP9Ce!1!JbziA! z6FsY{;ywn_^sGF^*HAqz*rci+Hv{P}Z>XU+rae$;t~(*MrP%0vceM~DY6`~eN0@Qp zPj^la`x>fk&34LV{2~{o83y4A2AoKD^3$#AB_($JGGY_4lmi-n8 zE%p!}W6bEdi-a{u$c%EKw&|*_3C~DFbvYno!Vc!2Kh5N&lXvO+e_eX9!t%o63;KP% z|64;YH?b~Dp|Yn`SP$m02RtXO5nL41t_=eRV&uV z^6KK)&NVB){_FmR^#&S&X^FG*IXeo-0(OMy6j~XO0ffb|)D`E|#Z$kjdu!T-HE6gp1TJjmxUU-VrbDrXK;HC;U7(K;?)PdI> zI5*sP`xhd;zBo1K`ENJ$ooix=bc+_XTdR4t4b*V?DEwA!QU|W_sPyPMlxVp`PnDLp z7w`4l2`g4zSR_+OqRAAp#&MP$2^!Q3QD7~Rs%JG43YOA)+uMs@eC)<~cmMj3VI?h7 zm8p2d+KX_Q)qHC!iUlIM2nUwjk}zwtME6>}$(0jt%CVArqC%<*PzBJ1{mhED<|sFf zSp0`Cs1=rX7FQ<5W_I6ui&-|7ayJL#Y*M=dc>M_-sgVo)ox}gCRx#4@zrC4D?|!uSjEBxjJrv|jyz%b2`wS3iO~xw^6F1eZv2QosnvWKLK7aYp z$<>#Z3llcfaQnoB%R&2#;e|-{lesUgN+pYGnN?HP;c*>C{Edk1SI*5L}%18o- zPz#1@m>_x%+B4`Qj2b|tH^-L6*Umn3Sw^y(*x#tikb?TuS*(;m9Yy3?BV9#E8B2## zezEwFi#lcuOHC^@?bHHQJh*BEET;&2Y=Y#~1&IBNh+Z`@rw*DfH{|ri;>8!7)3(ZT zkD)H4)Ot>+5vcY}iwM=eX=JPy04trdJudYcxSn#LWu`e8p+ht38>gu?=qlEN$XwbP z7)Ln=4^n&%rk$Qq2W48(IJqoD$O1I~-ther=QlT6{ye24UoLj^Jx1Q=nk6RHH#ScL zmjK2i(x0k%iA}WdZMc4)vJ-q2p;rqTsMZVtAmsI#L%C1~%EyFhnx6blWPSY+n+E=Q z+Uu*ueTSbl@4hWJn}vEEx$-KkmxBhRwmu8_LCryA;D_6RE^0wUWHX#7v2hioiXBIa zT!nZ>=^pC0oZZ%MVM8C(EsLv4_8?Q_tk!O%Tp|bN4ER1l zRs=7!jJ5oy#rMtVjC}E~!z|)p$CAXVlsJWOpg2%R>*3)1hB+f@n>JokQR=!eQDa~0 zVK&CsKQE5o@ECtP?;}S#&7mxgE;bR*6}i@?6Dh<-?WB{k*p(waW5eJkI+>%se)%I{ z{~7$d56G>Hn{SoEwNrO90k$|pTpQO3ZcOhX*uO4*j7G)g&R( z)0o5=4|P5Zn4r{AASc2Ym&eWlK@>2YGbPZ!*j@d)_?rWEzV(NxFLfWH)7ShJrSj`e z4#lRH!s(*X8ZDF_ssmwx;FEHWL$g@fe!()K9}YM_Qu;`=D0{tmu5|WZPSY%;R_#oe zJaV84oFHbVivSM8C}uYD8GT4?IC&jC8~3UW7Ys1DtxDyJYnMNFaa~L}Z?WZ0e)d6oVtqm`gx~n?KEVf0;=k)aFBp+l-tYb!bPPx^*8(rUF zx8ko*sdhYsYG>K7F?~`a-tBK}*)0#?E_!DVEq7@Tuho|Ar~K{Ai*7#sf&)z=dTO0Y z%vh8Hs}C%V6W&oK+PAA((E#Ow)z^pLcyofuvTSo`;-fg$2XhH4NpL^Cl@YG6?4;cD z=KMRY7hF(oz-SiN;*%sU(Gu^{{vG7FRJ#z*m7~-dtXOZ6Ybh51d&rvDB(ZII6t!J{ zxOC@yuyV)4>{ZV$pKR)P;Hnjo>W^Jh(&<9yHz zZc@N7)VgI%W3lCLp8+whW<=qR+iV6&APVH~T00F*mLMWI%u(+^Ej5~4OeOXzjy=-DX9gp75&XRz%V6sSMrd<-g>{h7zhy=sb<9md~*BBj5rM+ zT8_|&qo~kGl_YJO&d4z3+NGDr(wib?ewQof{D!e!9_Vp|1rfwJ3ggyBjzA4*2Tg}g zdTH)CbARch&P4O9nCN^$a3{!)sP` zAV(uc)4^ycfs;NFX6TYtpT2VR>4SS~IGHg&?y1B-dAo7$UV?emL{D5ZcFZ+{Ib_UP zXFM~~iNz>6QtzdWA`FRkyUMj4?<*t$P##wcSt% z6_$OJ6{`mS+;92!hThYy1%UF+c)h^n5cyz`gJA$?#lW32EQRabgJIbj16uil<=&=E zbLV6=Kr%#Is7Nu4jGx}6o zMk`Yfyl?dxgRdyeb86kVM9yPNs2-gMGlgQ_qI4p`g8a47e{<&jw*-1VpypzO#jA~z zRTP27fn!pF)VI$Aj<{_blZzO|*~x~c9A5>-bI6WdZoMAVcLuslOsAKf=#}U?SnTAfwVzP9<%n;1J@v;Uv1{*gHOYgWnjk%&_d( zc5Hv#X4RP9#jx-d2I-5j%Ff?Tc=xl@jxRkA(Q7gnM=>%N2+NmKWLjw%r`)jDN&D|{ zv{wLV2-d)diwap{@F5kA)BFL8 z9{$VUr~f?g_mYXPupFQ~8~?SUbJCKMJG;7KhuhhhumF*bO!N1^#9FC4C_pa|vl^eN zdGK=ddi9el(Ei+&`1Q>{UDCh!Q=6oW{A=^150+h9X!;gK6}a_9K%nhLZ~$s?s5_8^ z7`ntq1J$G!6vrsl`m~-Tc~C+I@-py0O~ELoFA4&TK%Y2j&%xVjH&0T2Sn$Nt zw#e40 zYlC>C4a7VfML!~u3yaZuAsUvAP|PsJbaaF|#y>mzlaVjZ8S;N!K_=f^R%Kz};|rSR z&92awo|LCHneeB&NX)iJ-4qf|L6Zlv5i;0sr|h}UL5!U_L^(Q2;~EYteudr_!kW=M zlxya(&Q=j_&#ZTPD zYIP_l!z8%{43a^{k*plBxFgI$AqeduCpkJ1tjm0P@8|2aA^ia*H;G-@e8@RV=+EOO zQ(h?*ZYm5EC+VbkDwBimxvPYxRJcCq1yCeT?U7V;H^s^FfS&H!sf>BEf2HSzJ{9Fp z)EI3mN$CXY&VDx9&e`m~)>b4>hn;|2c&8ZF#>P-qTp)&EhGsSBn!r$5CB!}^T;1)& zUoh=gmon{->SxxN9_4N7tbTrBMPUhD&#NBRjln@9LYQ8VKQA9|;KU7r{sKgXkD)zk zT>%X-^@;CY_c=dT8qoW>;4sCu-!p@!o_kHXAh)9KO;C{JT)3>4)sdd=KC8F&k<*pk zq~jcu{Qof--`)n6&^-q_io;0s6Mct^ban$QrK6)bxTK@IN8QnZnSdxJ2W#noL@J&l zXv!svT{-9$l9=j1J>Spt)a!@!$D-6Tos}jHQ>(1nb9g1i))vF0<%lP>G!?l#zAg|) z=Ta5kR9b3B=sH}_AuOtYc6%^mRaokj6}P>-`o$Z%%ATaPXd#1ttSd;y*xTYLFj9tx za9~c111pzKqQWvmx#7_lHa~oIs(kmBZRL=^s>Yw~?tFGNcM=9nv@JvN)IwyJT+ex$8wzsTC)*^I9 zP)P$QhZ;d7FjDd`pWA!ZA?#1<2CMVumP^h?>%%*j;k|PC+5Niz9U4w z&gSMGs0L#(rP?=5Whv+>ccms19cqkDEhx1ybU|etrTJjOh1ndY>uG-ko5MNyNB63L zjZh;|&HQ}U#96ZgZ&z%8E|`e&H}RbJ_CLR~>~YZ$2m$Azs1+tMMAAI!HQOIeU16D{ ze16W_xs@B2nE>XyGaGW#Qk^Dy=49xWBG!VdduzePv>;o-#k8pGD;i6|&&*uvJ?z#S zodf%Cw?}rYvU%Kp=B&GSyjiRyCxHlYF7@vw2{L>;efRHcwULUe$?WnLDf;VgX!oo4 zG`oxAl*Ly}z4Ov9M^qHdBPg*xot4@VUsZl-&FSs3Nv?{Q6Rb2JTb;NN5hd}7$LD1N zF)k_dGOz)7xZ~4ooRLug1ME(K^^mtX8F1Bv7}0?w03dY2W@U)EgHOiLS@l#<c}pNPBLl~0((f9EL!7qq=LZCMjJa$P|r(EtXuStS$;;TMvUFRa>!TM+$i zF4FM8V_QBCzBRqyu7C1i(LCim@#DP@-8<52keSUz1kK9UF8fD?S$}4gMY|q2?}78q zR41~^a`J0>-g^8RNDlaT-u0=ctVsV|gjJ}@i2zo)a?IR2;EyUywW9SLLoH`DGv^Iw zPaW*vkvrY7Z4)G}RT<!-=9Gs`e)xtjPp3!MbP* z9kw$4&S*HOx-9>vO&Nda){CFq<9TxAqFw}xq1qz=%m>kSSU*a%m(_$yp{lFl^HfBb z&B;rcRT9>&w8%9jW1J|-f)JxKCD_X+9W-R#w=aHg8TtqJ0*is-yTa10Eb_npb?f!d z_a@S%sX(ARaf+viv_a1H!&lA_l3Y=R17~rCgPtpuJh@n*M~xLyE~#DQlk*`B$3){C zE7F<%cSm0Ma+|E3gtGGJ5B~G{?tU{goI4zC=O8-_#6K{XOO!Llv9W5dbvi4}5Sk>S zg;r3Pu$sDR%=RdPtFWY$+y4Cwmw3x8+l;VkGtvpf;aww$O_3T8-CnwrbG$NP^6U4{ zd&6&*$fbzvBWer85ka{{P7>l74ju9X-626`>EkbNJMXfz9kZdo+C{%vdOq5pdpo|Z z-OoB-cT*D$;UV>Jx!=e=CVta!&vNp<3X7zy+V!L80k3%r8-?7oM{k1RL^Ll&#B>sJ z3N;X{2h1Yf9D)2@>Fp&ep{Fivd$4MG$xdNW8c&_&+ySIK+asB(bqu`_LOv2Wz*Wak z-K)wVgNkJKyZ`YEl+TmF>nGgJnRLbyB*ZDKH4i_q4E^wOFiL$bUCOgd4>|k7=Trv;eOj1m%{+1kol-&Fw)*x)gf%TX68UOGICti_M5!5E#IXq88Cct zz-7wBNGxb*MC&X*2_P3dJ@H8-HBOs}N#X@FUzmXs_uBlmi+OizQIP`{8!!J+|6PE!(k3Z71!)d4sK9Lwk&?LOhy zlhfNJ0>1 z3FNke#}0m9cskR*<)|aHQC(wyt?aRlPEfuGY<=a3X`{^6Q^&OC23F=oQruLY1jk9r zs>5Ht>5j^!W+p{XPr$$oU}t*O#P(bh=d{wC#B6$k$<}Cf2z%e#PF6fq4mtbC|C-6k zI5Yx*z@XTbL_km$3$t)yKA}MZAH7UtoT8k6;lbXwubE@o=3zhGtbVJ_&P(L)m@gI! zaZ$Q6B?hMl(BO0jrw7pJEF2D4HyhQX4|~G@p8cofK(!5HWAuB`WM3FdTBLk_|IW?y z>G$>qk5!k^W$#+hW8Yp3-{s-5s!B=Fzt=phXfrHQuKj-K_XkCeGiw9~wi!TMP)ds& zWff9fvPzI;A_OHE>V7{&pdI@^QwyJEv4tyAQ#%erc@AZeqLS(Dl zslIQCGW?x29qU${+M5*!F#+Y6kc$F5dxW-h5MWCO%q8CKzwq?2oUVM}St!id^N>Pl z8S(zSvpue9TryY12e*zi%%?Aaff?pxI#W4p(i$!AvZ`ljrfaBv|LMv_H@w$0Y|zHS z*-Dj-2!wLOCs1z!cXm*Cs>TeagwYipsS<=Z$0pGU+C&S6a5+oyJlJ}4{iQdk$-1)a0z(=MGsuNmuTOZ9TohBy!uC3X=2+KVGg%h$zA16p~QD} zBpUa!u{hJhweiVloQPezV_YA0VXSG(%JF+))U=wh2&=Pa_z-&Dtu63s^Cx`y`^i z30oteV{4e_r|cGoa<$)|I(nIsyqkKz`TW0-BSzgOk55X}bDdCvRnK)!=Q>enu^#=L zINee|IQDJp;hBDayfo6e$`4YVXVIN^8Z5(dRZ@!39OY=RD4l|AuKLc4^KY2Oolmwk zn4ldH6&WHUYSC)rfi>Bp5l%o#3U{4;?%3@U$T&}lyno%eIrpm-0-Jkm7;xP<3GE1o z(QF#TENUwJL&VSqiE!0xVzBiW%zI|q8@uhi_y1s`T3}k_vSJ@F#8<4!!3(*|lsr=}RBNu(a zQXpQ2c0)XrVI`Gj91|O2tOy~$5I7>xMleL|VwxCz+8SkZDd+O*`Yu++w*EMETD;aYALu2bG$Nbe@ zHrXU`J}D>)aiJ}(W^;@1sFxlBzEpYn=vChxu_{{VostlPs!khDr$BO(qL4LI!}4)1 zriyxK=!EE6bS&qx&~ygfrZchtk>%Gx+$~fWu@>;2l|BWoP;9Sp>mCRs$dUCWHK5HJ z1IefapF{;k90!u%#OEwlmd<#qco`c?IKXNPac$u!o8gjBCO3?I*^>D7s=;cgWw!_M z4SxJeAKmMgG=2(9TK_c6UK@`uAp3rXy+!lHDsst0^mm0e)Dk0~ck%eXqn@B|`ES2m zGhU{gIDEGi4~C8@9Rs6d5cFjP9&^LjV5Hf6xDS@i{iRQ-*yc~4ZSA3_U!`PsJ7xXa%vmMJ$Bg;=M8ZVHJJH=lpMXLdI*}mZ z_HwkldpluQTCP!E{$bGS|9r19>7@!lVS!MXPw-%!$Xz*Ek9=M}H+8!-+D)bt_p5h> z-`V=qE<62RoD$b45AOM&=382iH3Oace3YA)P$?u(iLx<-Rw|5%)fwe{L*|;irLP3n zD)*clvilou=`~cO7!|Tf8Cuxcy`AACfmtWhLFx_7ldevbboJq+D-B84p|@!Y3~CI& z*f{g3YMM=^E%Q%h>?30r-niwWUZlM`f+bb)d5Wf|yD!!wd9aRr77W+#c1%CbyKgxb zNu}jF#c}+QOUDkqsIY6Ak?4{*h*x5hu@<3|7Y#%Zb1>aYHJ9R&F)kVHLM7S2T?e2K zug|B7Vj}_=cTQ`(2_)}nraMTZy<`B?b;{C*{;{dzns~`6rrrsCIYH(DiRt#}bL5vjs=$SR%}lsYCCI3nW1;A)ttz+qP!5-2xHg^WvZ_$ABY3WX(_!sezWpp&D?Lt9p7o@ zI+cLw^XBFxu80lMqxzSXmqOg}X%w5J87x;i{ zziVv|eRR;!-#dzXr!r&qvTxfG_ZSi&rNXi_$teeVNMTOa*dC512}xetdJBmwBF;fM z>kJyv^3e@5? zG^6FHb~8*;5EGjGLLdl*eh?``<@t+euJ70$`1-ZVJx9E7?qR2x1Ig`J`+|!hYYV!D zySjQBio}q==~x>_(+-JW$dx{QtyK>H^~xtMUOBfr8O7N)(yf zpcl6E#sO;~4p>Vs9I!XEy?w7W`rrMyUH|&656#-;UgfIOce#Gykh95=mkVOJ8l^Iy zlmJXKicX@aJG_!`&VrOW>IGQBvO5Fm%cLBrg=ujCD<#~`Y#d>?!AY@Wz8ouK_a(cC zkU&s~cO?Wd)y|_5PdahqQTvbGaN_O*_xztref(QF=f@d0ef-&~!t_I4P1g!o zN-UUY==z$YeGT=LqpHdmi-`?v3gwL5RaJSdU0iYnCA4%RC3odKKq!3o>PY`7Pdh6y zVPNx5MmeJY9ARkMnIU{mI0K7m)B=rkDC}ceLJ=!tR}Mb?zb}7k*&dXV19YwPkg_Bw zJi{D#PH(WVngF?G2-?vF0QZ9S=w@wps3*dltgg!HlI$d_a&LnH6_$sU^ImUzQ2K9k z$vk?hwLGPv9)oRsu?#QuD8yqd*CR_!4eEXfqahx$Ww$%N`e?p zanYRC9F9UDQKJwS=U9n@k0y7ABM+KTAyv%quO`@GEp!=Oo1+XZjYTi`8?os1u{@&0 zZ@hKIgv0-0nxeGUvoeHWf+8=7SYsf~IXTqJO11$WQC@!O$G$6{S8ae)g}%eP>+AEt zf@;2vmm5VSe9mV$I`hWv%!SW9G;L>9x7<@^UDA)_`X-b2sW$rQHM4ccU#T72?dL99 zR9Wkb^AHr{9I8|w|GR^ z_}5Wu7u^0x=^!ctd_t9BQ;!sgy%}Xg9ppk%RSIRxu$0EI>ru~3azi>U^Pu7gFjIGk zYeU#l8}dbf5(8x_>N~t%SOiESubOAul7b}j(Y%O3X22oySA8%jGWfRwSoN_ys@$+o z_Db&;LSc$AKku}M*+4tcI)R^n_*RIQ5Ugu2!AEFEs8t+}Gl3ReUKL+Edh!)J|5;(0 zV>H%7F-PrDW#9vA?t6Hc&D3M@XiT_BBmj{F$}QnsvlLJj`c*R; zO~28h@=#$J4Lc=rh=3)`12=Uwhxjib*QJ(6hYCXpWe|)#ro8^e#jid1>wn6r!%$Zx zM0iTVO$9pkPE;)blxuPq&cNqH?1}VAH*qYeqx@sU>>pRGhnzR!_1hM7d~&l@mkbSi9TkbuqPGI{XaJh zKR|Vkj8(xJawB*gxipNLNujJNYigicZ@y%TAXXs)1~ybxl?vA(ko~-$X=i%Rpl!~F zWJifB!iVJKMAW?DdS=;m?O#k-H*lxlTEP8DWzKi2LQg+?h#CIH6Ji(Sm~kK}%ZY&w znxfcPhD}B}ARI=80YkLa8Kw8br<6VZMeiFK{ZB*AyG+_XwUQS?Fm0TOTwfK0VWYyV zoED*QgO71g`V?rc3Ct!|n~GWt<s)w zlm5A}jw+lzjA1?(FWNF()JrE?xEO#_a*;hK=2-szaSOJEf9z@HuG6j=aMQ?ZO3s-Y zh~Mn>^$}Vfc}4WUz#UPaYY1e8IN5Gp0FRJ>lH#%y5%Av)d@>9Xy^an@pt=~^+D!(v z3<2Dyl_ia>8?OGbH>83ItP@>CYOY)`3UO}}`TAfFC=Fujh`O*BYB(nzFmB60)i&Q` z^0Ouy2#Ip(xJv!|DlN|_we!4V>Nf=n=={PmB2qWf;91Rh@T@%$;px5vl_7mIsjZ+YUOC6+=o*TLcx^fB(R#E1YZOzjS`QmN`5DO$Q)%RhjWbdWGY5bSU zKUA2EacGh;)dk6z?T_Bypq#u@)l(b(BNe8wh!yU{FmpO1#F3Rnxq;o7i?wkPc0Pv~ zS5T=5;LWtv>>%PDESi5LeD_d3pLZ2$`qW)d{bk77OZw(uScIGCK6Q!{aSzIpklB?%MRcLK~uZh^rv0ipO^0)Ik>wf7!_jW(3eu`)xuo${~OM7#+Ic;gLn} zc%$;;p(`d1ze0_}UtxSW?gU;rDXJk5!mB4IQtl|uCg+w@iO#I6owE0viRlA(`IACH zDl9K4+5Yv)ebc6ytzfREB=RnD#<1Upsok+^j4`2#^By_qU2^zdC$H5Q{x|OSqV7f} zSG=Hmy<*~HXg})IGfjHaE6P=@^~bIs(rSdt1O=pukK}S)zYzpzq%>U|@<)lDy`oI~ zyZ_23FLjy1(~Z*z#Cn?=_$2UTka+_UYJ}}f%PC(u+J@hF=`eM`oMbS64&2LZh`**h zw9np)M$dZGa3AKYjjDAuB_Oj&KAlis>lYdqkI&{bvTQaL#n}L_VbxZPOR%84{-BXR zfDrupRHYKUG|)DCI4Y#Ln94XzTwcYC zDyZq6*?(x92MrXk|CB#>y`hNjEZO&!uEMl?q&?}dGC3LCAm)gHjvnHoe2OcN2k0GT ztvUcYS5$>~WWr z41+ZkdJyy>jyJ{yImAJgtw|!l<|y_SB!ty#+Iw@z$j8d6lbcph5uX_}#YWN+$f?S6 zP1wA^5}AYSVlub+Nnt^f2A5RAco-}BJ!3IrET`y891ondqL?9 zJlEtp8DhU7p+~qJRW^z3_g*g-c=RT>AU{((e)=aK09=Kr^3v|L$@e%o`3qC8wZsrHC@2H)PvOYqH(20 zg}zc;k6yd`6T?(Qtr6dEb7}Bp;FW30dMG>MrPa};2 zy{Bh7Zc+@PmhHhA9BEH_tc)R~Zg=^7Sc6(C6$b&wFfVgCGGU6&{B!h6&Gp3WuSYNP;IXlF*o9!jZ-hT|^eI!)ZU`743+;e`u)>_D7{Td}hmiNAV^j62%d1 zfkqpZ5(UWB)p1<_R3%v-EIZr4cC~Q$2^Ouc$rx1KB21_b)Hd}zV)T1#1=zi_l$Cbx}VsWcEMQ*I#{@2UYVY2eF4!nr81?rsj^wt4^Cp^pAfMu!bCd`GF< zP{Y2|_fIcco!7_WrrL#t6_X?C=bZQXIHB|KH`>*4H~K}_+vDzf?!e02y3i*(VYB;- zmbNu!OW3xQ;Odp#!%n6*s>em5ls|K2c_i5jD#pUzM<8m@h_yRCV2*x-&#>28#s)S48nOwj_B&>`)d?u7g) z7lT&kli~JsYipc{`08n^S0BE7$(bYKmA@BkyVBAxd)}g>9;v$4j6uK=>uibQLQMgZ z#b6}L1jt@`F1BBG@#LxUOh)aRWlKugssq637e{azT0Im_x1>a&O;oF4%0-bl4b{0m zz4l!dXy1L#Z&N+Xfb8q%U;owAc4KN4d6o7Am_vldKtkL{DN$&ptg4kAh|!6#>;TaK z1dxYONhUQ&O#>Mq-${qUF6NoZi}u{U^6i7Np48;OEp9l{#1QuYZ)Aq&7TByJ$Hp*T z2wEWwP3NI{jMR?CcQu$HFX zY%G>5-LeIe88*%X;f}Ga%ZmZ?lFvueg~jkmnQ)zZd5MwO1jl3o5ll6rR>3!heatCe zIqt0a<;(%ws*qP<8Is-W7S4A3+OtdcxK+~~3Pv%n(iEMHo(ZKI3DZ2Yj;!oNSbhsiVsq{2>Ks*3o_A$s09 zJKTA6%b1z}F0eiT{>v~>?7+Z;!LSrU_XmS@>Tqspd;{AVhyt5rJ-+w4p@#YDjs&d~MR$7W;dh3I@HCB*q?7j2_4<3Nm;Qi8-4%F|3c zYd{YjZ@28L)#u*P?`y?$u8Xy5$``_nJCMw9qQnufy;X-*M^F#V8yFS1E^#tgd6?!B z>wW@hG;~oF$BC9mC(?cZyUxj6ziIw?H*N<%XSeL8#5~XX@z1L9QDc~Z|C;dA~=naqupZ93P%$W%;#@@JAb)+LWSO7?G8~-zIO_m+428XcKRNuW-=K%r|GMEpUsI&S# z;#xVRJ1n89Nn|=F!m=Iau$+yYfXO+5y>X4HoWRvsDAi;rnMXC(Ui8&XNnWNCrLo4$ zFky|^=h*`<{9*801Gb+TJ|a7$!8h&BGi`-S7wHtvX`D>QIY9LW0p6FN%qS}&NyVZP z8KYEX%ctQUzI&hS!F%kn`ElRn1{*UsM^n?dBq#DwQa6Q}DzV^(Lw}6MQD1G9Bvz#h z3X-fpOj9;Cm5O)acS?G4Fi=~MwzjzlDQ2*6wWI0ugKPQ$^{Sfjx$tY8Oc?SF-mkbH z*I&6vAaK<}{p;~$ZHrB-=wY26mHomq_2P5uPA>HB>PUW2NAYMTjjUGR+n5~Xv_~b9 z3J}hSVnJ{kQ&fARLQ-b=qy)hxiaD~+hC$lerzc63&J$d{N(6o)t-gol*scAQf zg#)*^f``qTo9qguJX|Q+Aqr`kPhu7GT3o2Z$t=uC-3fY>&-c1tcI&NQD5sqoC@oM= zS*sVYVk^3R%GJOp`2@d^1A@6gve~&ULHqr(!=F6!8_Pe`V*cyO3C|Ik!O#pu@S~NP zGg2hkv=<=-!p5|mO3O8ZAR`_Y+Dw9wDo{5^gV+uvu1jW28u0j(Lw5SJYdKa}#$<>8 z^t1E67x$_t5~cE}WzzxGyG0gSBjrNGqOEL{1N95zyhl9CmmyJ#yH|)OWb-m~-EW--hWnf)VEU zBPQ8^#Y(-^b#Lk;a$~mfr}wc;$e#0tc;imPA|@FxH7QG3^GR84{o&OXx5F1!oc&Ze*g z0+uSIX^M&x9yO*l2|PVc#zqyr3(79>$@%D(Q9L#jrT})z*eE{EYZiEB0n-98Pc$vy zQ);j;Da1Gir?@3d>|;e8D+3E`p$iH}vDQPV1(-sFS<`*MyZwn2oc88Pa!M}4oDp_2 zl#9Ts(Fnq0#mf|N!3EdK0oNb4rsDsKe2{t8&zg;Y`_lvW-_u~`rc#s!;7WP?LNY3H zG8Yy^8SF(NiD{$AijH`UXv)^c$urUkte99%)Z3mNc-(1w-#NXZ8?V_eq~rhvYR|Jo zn0T5o(|x21r(dj$>Q-Yy7>(yVi;v0e<=l=dn>j%m;qI7k#nS7z%q-?e$CKUizk9WMNb|{E&T1x6;=c=OFfZc70~5y z$mYmhqT<33feLb{FjGs!ie?iR<3(XwmFvSnX0hs2C_({y9S~0*OocF|{MI4~<#(+{Ud^ z0iGr-+Z+s&0Ny(WQ5P=Y<#whe-P+2DYF5k*E(=HyKs2~#-omi#ZWB2aj7MuB&W0gk zsR+d+A3u5l(DarI!Z{*)-|%N!p7CB}l1WtOH(2AXv-o68Sdf>10*YX|_$=qQ1!{ei zorz+r7teRo`B;o2Sa}Mt+dO#!M9SLAIAWX>B^ThC#UBS28XDw%ApU8bE0>x+oFlV` zc;D;)^5vA-)U5_!yt6^sCL*#?4kkIo7vklm`Vfc~mmxfMWOmM)vzzL?c9Sk=^Q1U# zexzL#(rxW@Ct{uor(5s@A?bYi@?|BbW;gwKNusXbWV4)-svI4Fj2-1gG7XXt&v0U{ z{sVq#J?0sjxM#o;&5H4^K&xLs7zbWRnjn?j_n>ioHJLmC09eJs+2G*=oU$BeB2moCi81!vaz+5rr{PZz4V$rhWDcWz?VJQ z`r!$*hXt>Hb=-)f5Kh*vb zDf+SK_l5p+O}6C@cK36?oKWbh;4W*YnJt#IL^IB z495ojaJ*M_t-L1RUz0uVzz5G#ZaSspzMC_p3PlZbNr;|$iYNcx2qz)j9Zh?424U%0 zl$z}8A5Q-F{vWO=jKlN{zr}zejK4)+%lC#4l_zYdJUG4H9=TAxzXKO)ZMJgUGwIW} z+}Vp(NkA?m(1HG+Nc2j?dq&vC5G7$J6X?jR_o3Gbl=lE`WgkPx*bAF1k#Df7-=V?k zvT@fBSKmMRYO^)VuCdisN<++rO!7hyzskF(sAGX2z~R?8T|s$3Ajy}E(Kz1&^s^=A zc|w>*Gwf3nJs_otQZ`0&t!pHXW1l#(pUID{&mLzx_^5jiJJ1x!XxEjVB0*YT6jXKa zob$_y8!NRLl}TUXFW-agD-9usO4D^|M)r~eU%C3<^7p-|Q6TZTFlwwc%<%r2X(iX{ z72qpSkB3gGpQ}8u=H?!1Z-Mx3W=7b{sNw?znqP$=Tz-ajM&leXihl6^_)p#&{^pS1 zt4Lx-Hgn#kLH2}kX5AhepG39TkMW6u2u#4`dI=Slnc1rzo8)OYa9%;T!~Etk0aBky zUPj>KKv<^i2uzn(uQ6oVY*uW_p-fv2Es5zz!5cuW>tYUHHzsk;++X@^d;Tw+9oV_@ z$eo_M*wmEnwXlk}o^OHhf|~Wg@9BlOO0Tqn!`Ww2A6+`hdXO28QjcJxoCFL5sEB$< z7?lybY0BE3dVA8rx6UuC5p{aXlxr5Bq^Ac(F2#!6ZzrK#X_=Kh`Nj2D4pNRQ*>1)u z>?t8m&F-r1g>GUZK36^E5b^17iWQ@F8=Xib67iX5|GYQ2J>?qBv$8jh89(ymBWIam ztDRarM~=W8vlY{n4NFO3U8w+(9oK=lfZf)CmY+;xq@XUe&olZ4RbTHyNRRtJZPq8| zWG&Vof@eQU_27L+jZQWwVuIO-$%)J9hR0-Ij68S$F%$1F3yhRdeF+2C=c8~q%9o6( z>8u%HJ40Eq{sgM(Do=8e6lS^$Bo|$|dQ42b@81T=#bdKA(=NW``mYC8l$x}MamGng z!ZHhb*D>sC0W^~>lrvC&tfvX(Gt|M0nle+X&-FnPAJk59hjRx-PU6H27sI2OgP;cr zsiF>xQ!uE=2hE-Oo1okx!Aht~L>N17^Ax!_e+dLtZ8V(;X2Lcm1S_+PaoW$|>yPyh z-u7~)dD&H0JvedyC04V^wSbYEa^+^bWK;m&`|`+@Te64T_uaU_A>++(Dn2QJg%h3i zAaEtHay?pQ<8(&;S7pc0wU_k$y-bh_3zxlCd}qn3Ga3v)xco@e0vV7os+y-YLDFli z#v>Yl2;Cf^TTZ?B&HMg7+S^C1FnGDW_-!qV;UToh(8_-y$4@S?KX?q?K^>SYXa0QD zJ=fj%W}1+Ij9E2i?dtlKCr2l!OHONc)24S|3U&r3||aF*-jj5I)ByiB=j7H??Zt=oG)O5Dzk}YecM~+l4b9bq4hkd?hh4bDs_= zK06br!9KdvKnp}8)O&EU`-Xmv6+?`Qd*|HgM@%-Jfr7#<-$K z|21-(z9)G_Kg%Dv{c`Ih-Rmjw+q=DfOApUu(H#7F<45EDVPt=bn=k9WoNtco_IuA= z*-zI${&M1hy7QMfv6#apryf+aKYhCMUL`%}Qv0JaX=2gi!JCdKdc5)md|!e8^ws^I z`@X}kpI=?xeMQFMarPyl+h4z|G&1d)w{bf^K)%2AP5e2jefsG3W0LszgC=wCh^PAY zs{bwD|F@-der#}MxBFjDDDV3p+ilzK_ti(bU$nM-hgX7up3>%p12=bnKSQsn>~=4I zsDJ<9{q_58-K>vo-{(8!VZ87Em(o7>ZX(cr{0qg;w-|o^i9Zy-Zq#nww|B_o`}e5c zzQ14n{)4RgeYx9;%kciP|8(CUYwzs7zp3w6`;qbdCc||-_1E8O^B>v$dE+=x=TY^2 z#`9*|e#e$JfAn_|Nhn_em&;K zgMQQB^9`Av*%5gE3_1QUQmwHcdA-luWE_?G`JHFr_PUkXw)@;<*jqGND78le9<-vO zLgy>LciR^SX7_pgAIFk@KU{vM>F>k$o~q85hrKlXnp1XPa&_2no+Ym>^BcX6aAR3k_D%#G9jEo_nQ7F`jqQV-r{>RA_ zmDsaqTBTeXAdqa}5`x%;sW6J5IFW$0u|#NEzC;LdF~9-Z=3p9_Hclo&?~MAzf<%YH zZsv$<1|1cvoj73d?=+~i>==4TX?wQ$%C8RY=bK)T$1WP*S1Yc($hT`?QxauE!t1<@ zb7n!vcxmGJ9du&XZBfsyu<+TtYR@|9yPKw&xz;uc*Xt&uK$Mh}c}~nBUyHD;p`@Mz z&~S7xOfjVgXy#`>ccm{}yywM*`B_xJ1sO&`WW_EODy605a5GF2RrF~xBe{qqZqjyE zYG;7829X!PIFGpUsKaj~M`mk#e6IK9SPco& zkY=oDiTE(vK{y?a2J%EPMtymVzoXUeOu3l`M&uBYHJM5SwYZ~adK_I|oV7*y_6c9! zeC+adGj~=|w=2J@(Ea>);QLc(lLRdLB7|<2=B3F>ax3$||mE#o?L{b`3 zi?ZHm$t86rUZ#Pa&p|Q(SQLSBT;d_p&EPy3YFR7u@cGAlwmm8})7h=lUOW1N11~E? zRFy{It0NDiC`8`W%mRG4D1%OqD1Z)<@$Vp7;*?8j7y0CThE2v8lKby}cjSdHw`qAh zDf`Hy{oWYy^f84gEzKBXRPHBRAQa{ke4G_|xywdrU*(hOs(igln(0lYrFI6=CcstV z02y>oc?ivuv;9}ybmGXG&4!tvXRsuh1N}m(3v|WVHk8IGhn(5l;16+ahz+HX3r< zW^(!(4~mQIOr3-F;me+^DAuwkXP3@evuwooX|qo6z$v!|HiZE0s6Ajk0SsgWt)|uf zYQneRiXw=0fUG?*Y32o9YeR?|m=5CV9(~y!9?5O(7v3}H`2m*7GOS5`*6ft*n=QXy zYQLk_6p;lv4>20z+5&A!0q(#rqOy`2#QMI_hEcY|BOYR#+Bg+iyQP_}#kG z4VkKP%X{-Qg=TQyf(t1t2vGRIN=VS^@`!pf{+&<{vd^eHrE`~z?K|vG;(wb_vsYNM z*^~R8bJx4|d=L5l4r9K*Lxt2kC=99B0_C+xdS2!C)gkUp;qwzKi$C)|mL=K0O?d9h z(|&rm&{QuGU|Jl5tf@5l&AjH8i8=-~5H1{yslJZMINU0hVILj|c*dhX_T#{$; zF%I~PFZ?}#OJqhF>+s>14dN$}E+xUFf zjfLI-@j<-B&>b!@okUvDswo;U(PP#;F0a-fgeplcB_ot)*u`u4hyjSrw{L;CD%A6d}$Zk5ObI3^BdV|8by2`Y)O@VbL$pRo3Ej5$D z)ob(?V2o$M#OXrShcVz{A*{#6!dS=-z+K3y(lgO>X1JxO z$fx9}QC|tFxcE2+b288<#em$ON9ZB6Q2QB*q7ytR0>HpQH(WehPijDTuj+QiTV#N&XVLiX+n z^yZil=#9PS=#?PFp^pE!p>FfGK0TSvXJ%`6|Lob@Kf6Iqd}|y;ZCPf}3|Qnq@yNm= zI~y*tOLA>s^vyxev^~d&9%Q5QQRzCxc;N#OwF=a)!{et&L8Kr4MXY5OME+iz+JBLjeX8E5?InZPZWHf#ukzk>NAQJ--YE_{~9sSW|fBSgj>H`LBZy~Ba zmZjOstxx-JduXY_Xe!xpYD)3|oaf~(L%mMD!=1OCOfQBB6#i>WMmbeO-3AA+5h`}k zr4}VF&7Qrcw(Yg|7aGF!zlR;|h50duM-*aRbTUR~ehlvX(p57K>pS=lFpafOd1>~T zrsHng@8SK4J?h!3%$Ctf-vI$-uDq8p1&M|lE$g%Zn%PjfC*8?KL8y#!bLcNFe=1OC zlqs`y_brGX0+wN@7~$jqo>?2hcBZaIAIdXgjI}5-OvmBXhDy~!a72iM3Skf>r$K&? zd{YMLlOUr``x!KpY_)(46&}FoS8`%lhKL7V%P1?f1sFn+t^h7Aj@TITZH1bC!Mb*0 zMn#APCCOsmj4JAX;x~Bk8;YE9smH3=cdZm z-B)SZvF+G)Y&*6c+m3C=wqx6|?bvo~JGLF$j%~-bW81Oq*mi6?wjJC5;AYh%6h@SA zeTC(3*^!feZvraB)TH*yMAv8!tQXlJyO=p#@5yzyr}bl0{QfBT~AB2{5@6rRKycEX%Xm;9HO1 zc}OEUVuXw%UVX~mz@}1svW-xd16w9tUvsptq27gMP-xntJ`7dH7Nt3CfkqmDd*{Cz zdbxej9z)BQDZVIsoV0&%`2v+nu#h**8V>u2DjMaQ&Ps>^rA81jgu8%otIglAKU8$6 zxzceXa3#~@q!RSqOEGQjpif7m{n!!xO>XUy?9@48?bN$ZEBO)XB}hQ#D=2ai#B!Bv z<#VbV4o5ySTI@PVd_}vqPg=CXa%uL4%eLG#+jX&N3%i>E1EG_au!XoNpW=vqM4mDZ z^O@Ng_)D)T;cgD9q}E7g&4X~;wP?6D2enWu^$IV(0>NOg;sofiIhZXCyN+OgT9SV7~J>JHB)4G9|`*6)XseJ7~xMBfGTa4F2s-MYC*K zN=NAD@x9$B2zHCnw&gI|HirCW6Dp;t@$1q4waY3^e#}+bSoq4zy|4VuWCis~Y%i*~`k^y4O|A{w7|J2(&l(>)MQP8{ zn2ERa_3-NK)C*5tL2V#MiJ$ zIb2AmdMLty=aesEpir3_rvHWlH#4$uwL4k!x8#ipJJy>WrPt8eYJ|{Fantr1;<$k}*EVY+Yg} z_-FPXbH1Ncv&&&-vywso&dLx*%|J3E%tts2y1v7PmJf=ifiiOW`^PQN7I1FdfZOvG zayLyD{QBaU*-c%Sy~N^UMqHsTE1mh9%6E=X5*Jn-NpXsP2@<>*h}mUT*9B~YD^)zHZbmyebHsV$J<`YE)&b2~on(WA(2shVveicDXV8&DPMK z$E{aOEYC2uN>Swxs<|cyxm3HajcLF5xw_irPvsPU&o?}jo?iE~P1JPXKBVKqwu|BO zDlKcWhmX4FGWI7QIb*DZP>VKfR8F7@K=ol+WM!_+L)4Uh&rYjZf@zKRMD~ zs=JZ{KuQTrd8vg$q620+uxe2+LCD!0yacT4M0cg0M-PgRaWN#e=?-yiNPfwNCUKBr zCi2Y$o?p$gZAn3r`Dk9#2{`a=(|?*Z=(n0`^9^`MR-VwZ&)}KIn%a+~1RkLg1GUDv z!Ht#oBswourPKO>=|WRQkBf$k&$-Vd4p^~J4wy!6krR->Xoj9q*A&mtOf*hsKz$4f6$yHqhJQVx zw}q{jFZ=M}7y76?P45v^nQO_u*|5;5^Pk!u>dC%JohQ}#)X-s6`ZA-rb%^2hB*Utc zE4C*d_AX0{_oH9x^$*HoxbKN3*O%Tu*JTfS@?YQldWPG?B>+FWD$XTi*mqaqwgg_< z9LyQi7?WDM*>+#HcFSw4FRA+6V7B!{SPTKADTw453NSQ?>TXL&h?E_M(dvS97)9$v zp9r7rz?G$QkRTIiS!lnjs%r+=9fkr9FvzXlms3J<`8i$0Gt~YkOP?lOs3&#rhUs%ZyYvvWsX1kLjz&a zsk<3s)nmpF#>rG)LQk|GGfR!~vG4QOLv zY*1Ac@mEN4@p>hj zD}O}|$I5AulhB(Na-z*6Bq6Cp$9Z6=Lb~y(lpxCKBrkWB6WQ62tzDnK`?woiMUo7Z z6Hkmt5*Mg2pb7;E6vR|Fh@voqPK=Lo5}o89uU zHlpYTl}~E@(R41J>d}bFsQH@0f*zVgWgYqtw7F60po*7Uc`hC!>U9tp2UVY3IgzH+ zD5NcDLbDw*YrISY!=p$U_>dZ_CTs(8f>TaChN-M5)6cE2JfHpU*Ms;Io=TM!aVlh& zLNmo|j|-`h;AB^-s}!A$G6P5zmW|nsC++**qE{OV!;IQW-CpqZu>myU77IMyN(itU z>+tKtNE)K0miIgCb*lSz+g+oUE z_;WvXIywG;Ar+;KjF+;9zF^<9s=li?<_!eoeX=a_El}B~{>4dVfdZ$`2gUSstc{Z( zHbuGGS*a;GKPfCoVnwTRp6|=qD-QU2){|cK`K~Q6V{)uMn7}_M2yvTL!|@LXFxxz` zRz2!*B&F?(^KD7``mxRZKRe~MomJWABM-%GyyObJpdLY9FD0+1T~3U8LJ*ZCskgpl zIK6(b{^xvZASRx>r~bIQ{~}o4oL6aiBfEBF-Ix0`w3hs2)j2Q3wIN2HVAaf^N%gA- z&NcH$5ZagS1T)2p(<=Nzj&AKJS!C~xtUP<4Cm-yy+RTn}VA>6&UGfm8)oNMf^|&Is`|8h=I`OAX47HGMd5XG4$A>H4sbnxtHBw0~V9hjiS?*K@HZ ziIbo_JkY8QCxs~-csJZ8X(Ek@8EfN;p<7zemG&l3l!ir#rs;QXXJYRkx_M_cN*f`+ zs^iCL724PT7=RN-pI778SCH2~euD*4?9e+eR%gTUm0wWavU}!iRus2r4xV|DZj!z5Mbhm zgk>m@Tu*E|E8(K&X0R&kj4m|TE7RYTGyO3+Pj$}pzijlb?OOZkT?*HrFS-(|SS;EF z2cP0kVhqg9HD(x?cd}J?ta$&UD`uLlQAMT59d1;v9Ou!%6(qTeOLDEetgi84Hp3^| zq=s}{=2LN=h%EKWKB=_4mmO=d?D_rU#}uTCU>#?$M07ic+%IjG5l2-5^&KuK`8E}a zj;jGntO@OALJ=$T`1D&UA6U0!=(c7T)P{a9d)o`UANt;bH<@B|N>h%~0@mzIc_dII zCgUt1fpX-a8!8VJ?TQICd@@F&FLhxb3cu8LG9Uaf_^kokZnkgAUh?$aH=Oj|BL=j9 zF~*(fnRY~Hq6TQ1M$$wSBBuZw8xHw`futNjNC*c*QNAF$23neON?ZcHa?!R1d(8|& z*ng+heJmj%$Q){n z5GOG;K_=ALG@}~La;|VSqJdd=QQN;>e_~9(p??rm)tu?%!|dU2ALe$wvX>d4X12%G z;HNuNWvO6)nC<`Fhqgt6Y=UTTCAfslwo&=8N2Fck*jT-gY(s&0Z_YhLUXvrIIVB*M z5h;`9E+JCbIQoe)?V44jqjK1!Z1w|jKxG+iq`mgT?2^-NXbvnpw|meo<<{bHi=%h# zj&Qq>j>l#Qawwft&tS1P4L_`@+h<_P((xrb3yAjo^=7KHo3o3qnYs1qU&opJB%2*` zQi52a&o@hCQz@V(lB9Cnk&m-0Pi?&}{K~0jIqYtr%xzAOo;CdRoUAH%%=>J(^@_Lp zHV;zqnvtdWOFkp^;hS##ZCp$mbgmD7DP!2rGrT^|r+-$*lGn!?j!)P8jJ~R50KEVF zk)OCGPJLSayw+=m4d`=P-v#RHQM>N6@w!L99<6?Fh2_)is;OV~@y)!MeA?_D@THC_ zKFNw*dU&ZLD#;l(j?$|bON)3XC5Da186kKKDjilu`3k~Hy$@_{$;O1Gfg}E^_Vuad_zMt-BVoKLI>tvh#3$Q&iW25*Q5`aR zPQ9H{@df$gQe%b_)z3ij!2D#3!-HPU)w2X~KBSdkZLl(4pHIbWg$U)gB&8E6ELMrw z>oE5nHskC07E#s8BOhJFz9*-Qz9*^(aB4~PfZSc2PcpLjhKdUKp))qBN@SDK_Mj-V zuq}L?mo+Dgs`(LGC6DG|s5E~iBzl`mO!OZ5i{2kSw|Q6(-TsSg>+|oV-|g%&HLo;x zjKErU2l5;F)-K`{aZM6e4KcqM7pn>SVUW|sSxYH$I>HgG8)d75xSIN|u#M^X$7{lQ zcf9pT#h-=6H{VoWW_Ou)>Fzr{kThMPyTegAP{zMT5EFRXL-QTw0WTP0=Lr-^se+_o zEmrW1Envk~2fP96-BVcazVyZZ>+Dmb*Nm;$IL)G;Q}Q4T2&R!3z98B>;Hxx>eq?6g zo-c^g1zwUNfm)Cd{A^U(Kj_8PLq`37R$`WK;BT`}eLAiA#_W+M31Z5TuLr71$LCX& zFBWTNMILd@>CK=#5X)&lwb<2}B*@P5KFhz=yV1q`e5nCD?mH}%0@XLJ*8<6xssz3!MeC5@TvvMY#sUATgQM6S>6zu|{=qoyBZW{V$sRIA9@JI9J%;H&1WPM4&GlNt_&JMBolTn!8 zaE7uP&)ke7BFkb6#$JR|L7|1ymyQsPbFa5C68E5kxqCt@-a7XAM@EeJi}bQz7IwTl z@AShSt}#NAS>T3JJ@x%&Dm2!2m23unyM#Mlpk?xy@;74|gZa-b8-&5}APICS$_ZQz^apj(x z_3d`q11)D=HV?PIyQZQG8@J0oMbEE$5buBRiK+d{xvueV;^o9QyFczaD}K(C$Z>RO z(=GL%uOuvFe+bz>XU{z}%fap1NIF%yx1NNz???9UN|xt#;pJ7o;^(#ExC1ojcg(`; z@1C`PxA*fYdCwbWY0r0_a6q@`?f*SqP8f*QH6M8tKkpC7^BG01bGYulCXo9-M7Ol` zuuPEl^6dTEb{IUozd@F|Uv}Ky-+%bnuSQxbE~sGW5QVucQw5kMfGYb0S&)cS3ghSy zWif;N&^jT}Bv3rJ&D)6A2}lrcbpViCv%OUFpkA{8yxy2darCT7$oqLqKrHf~o)01f{SPf%vl zcG=NijlFExGxKTkRPWAO@^cX5Xc02hmGyY4QY2;J@%T|_GK!5aNC<{`T(%huP^1#B zBX@e5WaJs=<}+MF=M&O6hS9|}HC^=J#|s?aUTEI_FE*{KBZ=xz=?}+_srmlEjRQvf zg|2UaCek@5d(e>wk9p{Ub4rX;vkggzKWU=WMMBqV`Ue%1!NR*DX;nm;q)DOZ%SDPK zX-pcK-4KEnzW;R@R7=w5n*IQLg$c6kAj`#<;AM)8%ukXVPVH~h!Utzpov>TvZyR?t z7|N_OTn@J=HwNnb_0y(uCF7U+nMP#4x&OQUs_ya|luFox;aYzs<0c@aTCph#Qnb)X zST+`s=8DPc2vn`gkZDJc9~(1{umll6ZilG01T5h{UO+9R%MoKCZ2{%}t@)`D0K-09DE}wkRYfa`|m{ODdeI6k5b2*KzY+ zUAyn43U6#6N>6h)U~9DLL!;{*{k40-gr^TPf`&QjytqOEtH=lvV*TrK2`;*GWFAPUHN^28Ym1|ZRs*R&@+UJX z9Nj``YkW$`#`^;yj&I@`sBocC>pRwLgc967d(GMp4|si1r2ABSH~d+Q6XkgXf*dFX zXI&2NVf*Z!HJ7|vb>?o}jH--9lp7I5Bor!rTs50^$Syl*{LmMUI=T>3L1rs!P-p=G z3nFuF2d@{ul0ZsKX57s*xrE148@l_KFS^>@wnYRpqq5H*(K4>%fV~Q*Q9R(I>;kZ9 zMa5)u3df}fClw&?4(df!Zp6t1mS!#rO3YS;e({bPyFC0Be+kF+Jv=)THBkO;{qNcLqeu5BF}b6LWdw_u!G_xg)3$J{+R6PFMc=E z_T0wa!t1M38E+~c3#H>?Cew35nO2<`r)%@?9V%umJZsVa1iX6;v{4H^2Mv^(o#-X^ zRC!|KODFyqo9+M6Ci4xm_AX$uz*s($U^GDn0%BBe(0f0P!NuAR4F+1tJmt@QZ*roFS$F8_Mcetm7Xg*>=F z=c>XP1{F>NU5FqPS@+shG)^{lae3isKqVc!;-@#aL_u@!>^aNs{MV+P_v&sYw2N}% zWKX!73kIqzQv<%zr^CIo$9y;H*K2lsz`%6su3k28A-Nzn3|omGZjmE^Qcp{%X2wp( zg%H{%bKPgrLm8Gyi$YYGE6M_ojDJ8Q@R>YGU;|3@g*YDmBqEo=kPD~U%MD2Hojo9N z`MGygU!%*Pl*+=WHj<(3=3>zdxC}udEoV}4kdkGva0ZnuC1?uuH(-Op9`XTk3uGli zK2ne2;_-hlcS77})Zq5tPC9Pkz(HFrcC$}*^;1XB@ZOy&G)MHk!&&F`Ho_%ShBL;= z*YT2oveCSVt=TDvcn0vzJlfOzvfVRnDxG+B^JKj~0B$#Om{adVh zx7=&1Fip(P|8DdJ{jMW1tsHG&xtRy9GaX6B0H%cm`^o23-+3j>KYnz@bw4#&VHunzG8Ga+qA(?|)1=_MPRQjrg+^PU+L1 zn2nnkudItwrmkxW9EGBsSFD|QdMXGdF{zP7tuC*$@tOx9a@=0OJb3&SlYSo3+qA@h zt^82TrbDvxf9ZGcsyQzdGnZX_u!+w|g5am)kIDvQYsDmR(M1TI3Z9K>NbZVZv1v%g zKoIS=@FeyE`7j3$r$C{cpN8FFfCm#Jno4*1F-so^ar3CWm``FHM*sm}$Yr68_`9n4 z07B-f_aI0Nl-_lWs=0Mbdal`YXg1z4_7>|^>&TY>TyYsoi_5}QR`!3RvW(5n3e7$F z>EO8rNs$tPiE0MzHW;;ggGg*6h{QI6NF>#<;mhuQv2WN#9NF3pvlgFsd{v==s<*(c z@l48E8*X6iL7}BR9+7Ip4G8On@T)?M1Y6Xesoql>*yHuMvTNrA&%fGxh7J|l2hbg> zEs{>hyO2hCG6us16eR$;qa02Vzzv)e z*BOP8*=U@yYsEOmu42~miOtQVxWIw8a4}qMqhj9j$2cx=%0}D3`k{)cx1&*i)8X08 z3ufHjcFVjTEcR09Rgi2p*%6Ai3ecoV3jITaL?)#^z{9goP5jb6u#N(uJ9|W{m6|=%XU9~Buq<*W zsyselRZ6O=^N<~MYIUL_Yh+$~Q^}0bnL&whHXkksz#1)@9nO9`_@(s|2gdrS55#5{ z-|iyg)5OEI%dP$C>G#UrzU{N^Lq~KvMh!uQoZ9_YeR%9{FWtDyJG33$S$nBCeC8id z(tdDw_8${&I;5GIS#pksjvaOWAU26fQCcyzo?^$8uG(@$c1_KUTd)3jjgjlylYoa^ zML}Z2k+8Z*8b!eU0mPHtD;*D4jtnA4I?@w2rw?;;Rs%QZks$0sC(DuZuRylVg?Hdg z__w5txTc?x(|vTd@|M<}cmAE4?iO=V4<~PYB|;H@g4235c=C5mZ%v_h(;`I@KA{6q z8p(v%$6ndxky;8p9ao{J0}q6S{8x*RA(2$RRZM31_M|wcUEtFxnG0puyq}}R%b+g^ zhHU)O{!7PwYTq2CN>_O@D1*ea?v255(KoHXK?n#2%XvSw5QpFc)Z<2(T zpfDHHp;}vnOuf*V!5g6JER<@OqL3&hMEp3=lZ&MwD=5_76ck#Z8$svE2De*bAF^&l zxz6mQCs?7U8{Bn+=&E-H*p>Ws_r4up2j4=6(4Q~XS4zaseXWb9?Mhoni?5Y@V9dq@(vPWY?+T?Hcrji|) zE-WXsWyuR|X%5N=QeTQ}RhWF)Lszbzc*C%(%Ch3~;dbZb_J|alF2;l;NyKwoMVX^_ zhQ2R*&rthfChjB$URExcp-xD^EJ%!DpD0HXO=3$s3OD6}4J8iX0ewfcK!{WpGXnYk z$x;O(*cM_-%xpei_9O7MoQ*h~#em|Fmws@Ksf1-wsFo()6bbCjxCx$`K-xu&){< zY2)E(LfF$E6OtKVT!4gRu1(wd9Oo&^B8(4e&k|Jlkyh=8ATCnjQsswHQnNmkaSKR` z3&N>0WC?P~awH+}9q{(~Ojla)he7-gt#cimErTE=)nF_ZR9uz}-Sua~cRes~@J>e? zCBR&m|4qpF)fs$6k% zP1d*fhk@AG`ob8rUc`;Hhs0z{T&S&WtW(W$R4ghF65<#za&w2tC_ESTm4Vu+$sQ4( za>%3K-J(P7-%mkotB1IpkTj*j{Ts(1z-> zyH#&ke&s_nhuw4icb#`qoi_)RQsb&@Kx$4*D|P-@Ey99|g#5T{mwpG~rmtz_IBxca zxgDDue;V{xF_6@9eCl^tpS`2z$wktJD~(!tsX?0JilaS8W9+$TOb&BYgm%NGqTTQS z?Z$KcZ;g+num5gpK#)75ywp_ocn95j&sJnOn+}h=`qeX5^(GC~NYnO{kScM0kB*X8 zPiG9zsWAYUC9VzZ_9#LA@Xbj-&=AB4lcgsaqmI8n86yN+LG&)*_Yk0&;kc$a@Fsz- z^&wTGhv^4_#vY!9OHZ*#Muer_;ro3a88MC~3`f@s`_#O6--Fxdp5659{9iIx9e&I> zqv2d5$_m56ims7L8mmnUf2?oa0l+QESn4ke2N)LlmtPJ)geFB+` zI|M}kmD4)p2tNkR6ty+AH63~ArtIN~r~c!`!!xt@Og!aHGcLQQHvxQv{Sz{g7!u?w zhsBvZ3|j$PJ+p}-0IbTP?Q zyAekuLZ*c>C@cU(xO_~A3mE}lFNGbI0`F@v_jJLL?8~nn`)&WG+AZ+TJU%a1%parM zOAjwLWgj{Dw42uLaic+BD;ix?Tg;erF-J>R5+sh7nKEttjplxRY(Y0= zZ#eFl_ph$HrI$TSry*wLdo{M?`xWk5uAcjq3->E;AM~qOw&UKq2`d(yTXJ=TKRAhc z$vJQj>jDEj5W-cHg1?yxrq$ti8bLMC^MI`bnf6=)lXI%|Q!HvV4!GZ$q2!?9tC#hs z7yVifV#3RFfgJ7nc7_jOc6QX5N3ZWUZjS;$pQ;4MD$0#jb)jjMEMtXw)QhgTwi>E3 z|8r2eMD~)8Di@6orndmCT5vxxrLw*$7$omZIB*&U@LPhC}edFg{|QL`&3#M+awNHPObQY!1x zK!(W5^l%=k<)`Tpc$Uy_%cZX=LMj%Vt?&nv>l;Z0hBSMTZwmB%uuB;Q3ez!n#Q@gOR&5YHVwhQ%RMx%7raF z62k~N`0BI&@up?W+5vrSLe0tkuzaT(^$%L!oXE zzBVv7t2}olCWOD?%6&CIU2WF-`8(-mq5F>?r@6%Gznck)4}ad8rE>Xzm_DF&q34r^ zP6Y-h5uPY2!?0jitqcC>FfH=4d7ySEymdP*wESAKPTk36-`;r_SbP z-2h7ENGPy|#u&}yyghX@Z{Ce<&))IL9-prj))%_5<-mY7tmg$zLvL}x#Y8i^FV(Ul zqo~X(t-DXEFzAJIvlp7oo^@}IE6h-+m}6j4uJv&<0;qhAYYcO~8ya`|dFTfN`zi%7 zH+$IXws9+G%rk4RqhtnjsU6G&GvLlH>1pH#X|qBSRPfeTc@grNDs0x#O3 z{}t9L=B@+k_JtU*m^4VCOiB`v&?_Mcw}91xpJVrd*m+);qi6E*S_AU&-Cv$?>83t>#TBN`Y{f3Sr>0KY zy%3s^w~iQ5u9^`Hg&TqnTj|Bgo!M7MR~(hyozmUu3-(ziEOcfFOaq=jR)MvF0t(l0 zRQZDVW{%<{`0@H-u>0!ih+H6!)DzL&&w`&=y3P%Uo18lt{7X$r-21H!h(de?@=`{ zz=C=;4>$nK9fGw`lQC!^_&}7$M_>TE>O?0R^mTY@6wy{2MlbC?roX!WaLX<9`PtRS z@n@tyZ8vERf!vL|8qz|tF3^z9cqfOvUNwOR&sb|CCw0NNfZ!(9kk^a17YFs=((8~N zqjsF=`!H??qk6a-mo9DjVdLk$PHEm=J;3x={)|&~w_*y+X48V~IMaD&+_PYT!AMr1 z&uBwhB9agjfvF-!jfuKQC$Jd^u@)g5nJpwSiv~)_U__RRg_wjRY4#b+6ff79`_8gB zX->@s_85BY>$#HA(h+WkV#MA^zGAfR6p0xt?u?z8hGwoaLv=Q^^`kJg7SkbSd(t5; z@oAlw{o#c-mbQ;Q%J^-2y#cruQtM4hywx*!uprD+`tP5fy=CFMzh8f2Mu&UQ4)Qg{ zbX=$geesdrTG~iY?9`$o{gKDvzm6bFd5*SkUofZpzJ8f*q#vZa`yKvf))@5tPBv>C zg|_X@8pHqXWm?B!-$w0#Is9PlJUU~P){)p_7(M)q>|;M~KH#^-r;`&_>fd^ggruy+ z<3SUGHp=n;9W7~v>CEhccRqe+&d?5{=}XW1=mbs-pa}_R(tX6wVy&vft0VDdnEX^! zkS^MI84H4*q4o}>?wBsbfAQjn&Rigx{(qG=IWv322Z!H$!*TUS;~5)(czc~cs5K)B zD*b}prC&=Q-_FV&udz0rte6m=*193W(jWKB>2$Qy=x)|d> zS@DgyMWl-7W>=aHe}2C+4>9l|os+@FX-GSf&|RvT4B1*+{F4oZ(p$SA|n)r-nX(%E8*wV;8qulY?EXP2${uwu7EE+{#EneTH6~ zso)IxjD9f~L!!HWm>?m`L_T0i01SEsdQ*=#*7(F@-;Z+JjAkL1uc-yg7UfVVj71hQ zB=DvtNhy7^`h)eRZGua*zr(rGwQ{OiQJVa!3AX)y$aXca^d=%I@6y3 zorjK?bL{;hR29oSpsW}hUt&7A>L;}@+UFc36B{%_!xssLJQZKj;C<2-G{jViLuC# zFR2x03y>QiYG-KZMHm|0kD%5IwMYy{29mOnT9vrndkc_SmAJWALh}t(K`jhtkH!Uv zYdy;^tm+HunhMjh?0H|Fw&_tDg)T2CUOtqQsZ}T9)Wvv-f|@vcVNINUOKYUf?-kkkk2O8EXw-(@r+aa5wJ2|! z8|i!@?p~rqq0(EHGn|29WseVz;zAuNTxj=sUaZ*rh^ZLSpFX?N6hPghI7DQy^>;i_J^m3 zZZKUw!N@Fi;OR3zF)}bQPDUx0VhEiUvr${UGJ8i=Dl)9!*9Jf3PDIkU!Vym`IexX= zHrCQC#(@Vew@W1X0mfb{b_hP9sU53^%24|+r{Wz#%-7V?kcN0WW5-E!K_aa3j6-z{ zI*nm)z&IvG;oi7u@gNL=i2SWdjL}(36)y(zH)zWc&xayr{rO-sSs-|DPIEn}BkB{4ah#0%pYDhLQiAp$rsINZ6U1*WpZ&7mrV z(io`DAa?-ee9fohxeFE^KJO8KvH+tbPwBUX^XPTiV>n5?GUeg8yy(fJL;x~$?k#+lm+ImMO~CaL#Bf44NJq`46(rtRst z76v{IU?fiFr=-{JwP5)tL;4yDparPu)9S14Ki<5qmo9IoCMAc8lbu@qvMiMMxjOO9NC!ol}w-q{MwyDdbQ zugI?0XH#nMLyJqthiSO?0g6RbncHxefGyP#3DcbPl(#1MBq`SH0vIerpx~J^ywfK% z6D?qFW+BYi)$>n7i(_n1Jr{|=zlacoD4i7KTWU1hvCuK7s?=Z^6|%ihmDa+uHqFjt z6e3H>j0lrbeGQ<*qH(}{zzhu&eD|2K`acabc6;_aVaYcaE$LL{i%XvL8=Bg@o}ia5 zA)pMoRU>Il-C;IBA5#hd8uA^#+rKZjA93w=bmF`8(w#L|ejsQ5k2rw0XRCf6chgV5 zxs4n^dk*MLkQJ6V=*x#ZJ|79BXcHL=?Axz7!|pNl>%};&)qK1oYZ`pYhwHAGVPKdr z4j-shc~gnB7#F4`#f;UhPN$QMc&NQ8jXBaNd5@oxhO<6@elK_J@`B$hD_$N+YoqDiQf4Vn2fBozWCTyN%@RK=P zmvQ6AKNGMwQl*%I9qO2S{xYxgAd%ri70?K@!N?W08MNhlv(>Bb3%)cz#6lx2ff82LIWV{QXQ1Wq<$et(4Q1TbB> z4IIuwVp>&qjmulp4?8E(;zHvLH1gR;3Ov z4`pW_{@6>aez6-tqOGK6MLbDpXl|ATn)Y!SyG?mgG-Z*&5uyh25OD$SG4dHnN3BWn! zM7a`Ok7l>O{{Lp4KdIIr#R)K+n9wQ2a(Z^S4uC<00GLaYYVYu@1@v>(hoW6`&wI%i z^ISBd{}z}5{WNC46WJA?AM@DA9qS9PT2J23*u9hC=>-sCA|oX7I$ErN<pO-G#J4Pi5;n-@NzB``*w6?Go;ZxUFB4fyY>9CxAfRSbTq<&hy-lT{$yNC8g>)Lr9MnRJ{IF9)^%ki-iq?Z#Rhg1 zUo5Vo+^>3{b3UwCHDa7;+o!Z#!7Evdl$bj;eOsY{t2eP+srC$dU2bnA8AFKee8HsN z$ZEqjhovXUJ4M-HFO_1lA+2G1v0Kj) zYmfI1?M)6q>*2hS?Z4^b>rcMW)k7O#EUFi81+aIpF4W!xp)vH@9brjK$IAhHzn4Az zuyr3N-82|@VnO9Js9^x0yj>Cq4#pXYbwG*?N46OUmSGSiGJeS1fF<)5-(lZx$oOq@ z?pAVP-p_todu1q7|6R#0U8!nn207j)YJ)j)E>M7U1IX-lr@`im;=iiu+T#sUl~3#d zm0SSlrslHP`Jp{3pKEP{Oe+hSR>LCYh7ikt_xd~4JN$FV2@R%c zA7o!&yWgIFOh2(OnggH{9`P{okgxF5`kn42KsgsZhXyXJ(_{f9nboXKYaV-Q;|%|Y zW(pX2_ z4@Wb(L|a=M)KNHmez-b^R~5!Ez*!V&LA3{^zlYDVvyQ)iFh6Oc(G|0# z^~5^FsKB@+6i=rvG`jpUT=>tkn;-f61*v%I!K3llqVR-}jERwC5=zn-Dh0zhWr6-Uf%S1%F+y#m z!W3YIDS+#MKT5y(Z?hYZ3~u`6Adzf2wUo%a+M9FYzw=pVIMNe~q-kkX57mn;t(VT) zVc7RMFW{G<+NDV~iKs0?>TUfp=SSDPi|?2}W&pi`jl(8i_Rd+9K)e6UWsB>Uj9o<6 zf4#8zv)jjxzlN??m^Ne`&m4XF6PL9VGu!iJBW<}7k+wXDi(#on=V@t?xb6x^+Hz$h zR74ym>B?RcA8yFLowsW!+fN}|R+emnah+*!vxDG+ z%LXB2tLC7*1{wp%i-x3%V?Za4_0~s`czrlHn9`-zo`c)Oegt`SfDVC`dI`UyDdaf1 zpCYyP*PXYYZo5g`~8FcTCvr z2AYEIIohZJFBai+uVtO0T-7c^NhYG1OCy>&VshkarO-xVdaCQE@zkxhl)1JbuIz+w zqhy~UmjAlrog>#CxO^l{pV#i9UEVab`(-ZG*{}HiQ*|j(x~^a9Kc#N(X}z}}qx;>` z>(6(;pR%2Mt{t$`(D!tqF5^~%sy!eGGzsPA@V|QfBO{1w2bY5=Y0hID7$F0xy=s&! z#}kZ+k2GH8-edhAl?7}hUxPCwGk&I-2PVV}?+^L-M$Q}LeAB!fAFA`$^AZs_wAM9- zabAewU33QKW_KR{#Q_ft`qOb@X4B^ExG4|ak%-ZXnG<^i$f-&sIY%U&7LqY66{Xe( z%VFZING*ix2m@e#!XN2ZfEa}S{K$Y=vy58%YnH3mn>EYb*WzVppi#^JEqli^htB@_ zgG!?q8{^0Y=?O(3O4?IPZ<-uwA;DFvc3Y{f@3-um?&^nrTEiCDWUN(`;m5-%Zxs?4 z&Dou_-xQ#akLZmot=VK!Rvghe{JC|D4E=&`3%Ko`fFH5Pmr*~>;^GNwLEN3iad(!_ zl}%8xj8dOye^Z5Wi}cf~r#4s(0}VRlAj@DKn>r!kO{9^P1xmDBQ`4x-$uFs^Yt_D( z{Pu(YQzZBm%BHpQqvy7a(WP^+^c*#+LAn@|CwTRAC)AEMWRX%{eYAPm-+y{aH`tUu zGRuhoas}v?+y(=Z`CP*wVQbbd%HXyVJbA4sXOPAZ0Di%fbq0kNq?^ogwQcaf1q4mu z54Rmk+K619PmHuAQ*uU(=D8<31|B;5wtox>4;{3%UXQ82!g?FOUA@X_IJ((=sRUHK zK+DUkO{)^B9Pn7Gel3vhdGCk2{_9`<~ zVH%+9`NmyG9X0Dt9lX)_5mt(sXeuU5nvQhN=_+0`61Bns4b7n}g9u7cafp;^&xlDJ z(aOy^j)Gl5WZM?Z`w5O~n-hEcL=b4|tJQmlG8YCe-QVXJY_c7&M^H60|ZROIRMjJ5-xS+*$h{9af0)^yaT=2FE(b;l) z0wTv{J)RNDnjz=DbMK+mM&&1VOcR|OO&A9#RfHq*?70zEq2<<+cYC!dd5Kdo>*EWIO8H z6M_VZw@@b1Bto!6ui#P3vOw41OSLz}HUCm;Nt5|1{WkVcmdtyF!`+aEC(e{UOV@bWZ+?|pSD-abpWeK&*qaq5<)Tu7+<&(f)QIniLhy_fH$)R25{ zzn@+={p?%f@9OtY?B(yjLlP{E!Ttxzq}!+c?gWGVv$n9G(Dr;9$dYy-+x+4+icoTX|tp*cbxE~LB$UZ zA~aUbK`aA~r?(OO8Gnq>pLPgbTeAmvN<6yVbSJ8O&{*tNml$ z(Nr8P0n|7|xF~p!0%1JJy>s+33mKtRl(ZxrgoqWuBz*UT&y_obqleL|or>MC&grBH z+Vwlyw89i$kyQK3#GInXe@2dbEwCe6E{Ndu-E{?qgFe)*-#Cf%q`mMmc_2Q2`9NIV z{{;Cydfq4Z;pMQG@p7|{ScUGV%OC-vpLHf)#>xBm;C;Mo(oK-`P42I}Z(cHS=U<*N z!myXXVcM1`O~9S)Ljm`m7@)nF25WKw%W3aB=>f_b>#h9RZyare)6508SP-59IC3Kl zIRa9k@klLlF{EG_@Ye&Ck2hp2TEKO*V<;d zs2n4rF~p^Fw4|y|NYIHVs7*b$jcxn;n^k*O4+w69-f#Q-g+E5gl>?R7f~wtTy?Cb% zBUHMY0jVzywKrwZA2brixNF5GfWy`TOBmpD>5j6Q1MNBHz?@`+3xJeZ+5o_m7rh-E zzyH494yq{eF%-sK4^`gXlWDl&%A<hb0&PG;sfI{c4M* zVT&@lVVALYPg&iY&ZiPXLUI+AQY0B|4M?db68rpb6sTa7S1-WJSZB(g7+T#kTW&DlIb>9sqEB zS`uRZJn&3^I%@}pM^tE!OKxzXeU8nPRhe%nv5)tt#_|x$8&&GNc@ASL87_(g)j{zCSr?N9qcE(NC8&LZRx0w9Gm}K5)4lszAK zXvOO{M~o(I#vUfF0U5j*wx-kH!No0l%Ah;D5u|*K0Cc} zx?Wt*7v4+g$8;Y#E%oo#u%iDuK`$}eLt+VpJr z?q2&}42B7+%5q%XoOZDz5*LzD0crjUGO`|=oHv(^EZ+&!=*xM)+|%gGaa*h}S7ACr z>HpDA=RE(;WU{3Yd{h9Z*J=?`M125R$2jer2hg8weSXj0XnGx?1Z$pLFAku2n6&<~ z+_;hjif6#?3`3)K3b?3+FhCS9cFDAAv>%|t)Xqr~5V&wlr@qBJn-2EtYe1|z5}M4dXetuKAU&!_%BlZ{wlx*v*{Qmu9ta6suAiX_)V$hGkXcWyu9LS?AAJPm3Mgc}hfUxEiuuRi&h`-WP!&AET6 zk442oI<%+(pr@ntC_nD{%d!)$Kg?jZH6~RDQV6V+qmh^_W2UcEuB+JL)b(Fwjph)S z%9BJY<$}awg4dwX0(p*5tJs|3Ix{elAf6J@VdH9}3~PaZ89QcTKog7?p{Cn#H@DY$ zwL#EAmMP0}`SP}2QL%fK?QS~xiI0DrQg~a~#$p+bHYW|by3rrraCoK`gH%|)+uM5d z3X@N%8u{ig%i8I+tt{)>s_LfI_H=CrJ~Za$C$3CZ?D${eRQr^3ZoRef^U<=-sw+EK z5)yLZ^PI*QgSJ!#y$$s~0<7rf(LLX=QaN#plyf+x^OrU6-yq&Xwyf@_H}Bo#a%qeh z#bV+4lJ_icMB2gJRz}jy&{$!f-qud#qez7}GlznQqI?zL`yv zmHxN9`Oznt7YtmW9%_G8Ff^U4ftCiaG6EWr%_)XO<91ynod#;9VuA(^p6jM!*vMaJ zQR0S_4x=zNFwm2FgNJKpJl~gfXlBzC<)BY@n6R)pN0F#bo-$4z1RqjTMC#(3+MAmN zh@ES(@Pv!34dM%&mj#Nq_zmNxo}sRHA3bFs&2p*1YkiOGf7g$fUy`N%viq58l=X+* zbn1Y41ZHG}fqH}Z(ZyCU0)a%9OE^V<|VOUy6f@MdQp7dlj7hCTl(sfGu;_U{vzD(gK zt{$U7C9)o<;AiNV~0P3q$u(wzcZ~xi>2G=}Qsocx+to-= zP1u5qE|`MrHzG9&DWldMVVm~Jw?^T*?hS6nMGa`Gl-^kFSEWq`MkL&QWj2>US;?2;3FT}`t0R;q$Gh%rG@gm4}b@c%T zwg>YEk{Se50?ndF(Gd*uFwOq(`TA3)-}=z7zAh=PFohM@4=Y>usQI|$@LADGNWD@j zcejmlcjuV9am&}#c3Nc+T^F0XaHtrL$jxE!#t|7?x9oMU64~oU9p%tGqkoLvvD;@C zR%-2g-antGb;#e9Udf^>DAL*?E+|k63WyDbh&<{|5yk<>0MijUPU1K_swwAGq-Pht z`{GycnJ*qnQv*%wv^PR)&lZy9ZF_3>Q*hEX1d6bUw7xLe{{8QG9#P1RuO~)ax{?mbo=T3ZQrl>{rB{lZ?Ii| zJuhI2^W66i+P-_M_M0-5sP-D6T^7zy)0DCIP8xjf@=J~9Cp0M`$T;P428L%jC81gj zJ&!FWMMF%sjEgYYYG`ZG0o_hm_#D(>Wi-VZv)dX?T+@_)B*wT`zImlCGorJR-B`8><@=ay{4^sZ9h2H6r+uoD!LkwhK@mLpPh z)p<$r4iuZIu!GOd*yX)Xc71=?)}jS9n@&*9+U22{$G&z_p?#`RG-aG>&C=r0F=wi& zEgF%cZXl}l1icZJ+j=MKt`}P5^WV& zOM6nH$`!d?q)(#_mW&Mg>4!?)A40`|OQf237P%hEV++`kPvm+g1+Q*p{4T5U+Fr zH!rj%hV>$dbX5ShaH^Oj%F5)#u6TQRE`)?pR1&wq99Z~|KmNG7|IEh+mkY^jQNkbI zyY{PVZZwDr_9X7ZxPUj*lT0OXZ9yr~eFkGF17cc$in^i_n*|e}7AKAbnYmYHj+ruT z{f*{QZ+g7y9hr4jmU_KKskD9hhVld<+cLeWA1f`mnl_hvX7PF$YmI4!df>=qBe7Vh zy@{0Is>^YkE^Zro*G?TvOuPJ#C6HQ_;OhCwG51%QOO4A`Nk9#fj7`F*|DOx$YqWoX(?)blquRozQ9%;V)wZjiE!UHi@njSzjIE>{aK}un$ zIM??5q*qKYzkkNa(IvjOM+W07dY_10JUd29yENs5-t|^W2YB9O?Rxp`DXFZu`lb&i z{nJ+-cH z5?#7uspVBnJF(nHo>3}?zxd?FU>8|mUEntAA^6lSL6|(at42|ni%>^Uj)22MKB2h? zyE?<;I@P>~4-9CvZCEff65<*{mHhZwXD4Ck{BI*q$SC(Ld;Ix(tIsz=FSr_k5_@NOGAn7W5n~_BI_@xx>^|_^;?Gn6{HhOGrkTXn|4sWo6Lj`}4LA0Y zPu6L+?=VI?2QDG|_4%Md^|*b#NT&WDF6`a?-q}Z{!(O_j$zZ*w^Yzcw{3pME`W_C_ zZU2Z=GSKB0yOw9GnCrJQ80t<^OtXclFUP2y-eqQ8ASf(>co*Z6g$$SM5T#TS)D);) zrE8%8CKIHq-?VF^WPlc0w2&mPDkr#{icb4X%d7 zlgjXX`z=BYkls)^J=hU#TzHXl#Py*s& zEd;Vqgjfn5p@YBR_vQZgjyuRiCsDzveNOuIo;{2O$`g0(@!jrko^A-V=z-UhFc)f#PFg#t(%BT7vsv$jS%c zI^bxZ0QInmutN-+4vQdB{*VVX8%ritB}71I zQm(XvE*Qf#H9fF)&GkEaP8+bTk^HYVl6z|G)=|5jZjkNoZnkODh+ei8&Ngiw#N05P zaN3Av4@<%mNbUM_```8WGh=GaeJ?t9y0Y@bpJzt@9WlD+o~3W=89T7VpfD+}1aQ=* zqW)MX2U?His^D(%ql@g9YN0YNpTAb<1PdFP(wNP!TL;I2 zjao`y0w%1jz`L>Pf&_mi`@Gq7mU8Nx3)fygd>;cVu&|QXE(s7iE91$Zt#mH@;ct(M zQw%X0HjPK4;+QRpON4}6_n^cVD~v4DbCmDisxVD?y3?%pQ$iWm&hzcbmiCAg6Jk6- zx*`&lAq~)K$s#^gA)mXAZ;ZqN<&j2~zaLjr+K9tPi-4GrkPz#=$d18Mvrf%(Kh2x@Eia#0HB zD6^*Dedes!orUpz{)0K$J2#Wamj*yg(Ybu;ir3`*nB!esfX{gbfRq;&-E;5v&QhbqX4jw@g9fJY~(A z`>u1YtSRBgy>n=qhl;_Hfw$=QPBO(haYh^z)ll-YrxhipRw+v~LlPXK0~ z!CTqus4$(cTzKYBp_L!f^1f3GVv~8;f5-_tUzyRh&(w3CI!X6(d&UOkQ6Mq$E-9gk0l2_Io(K?+u#}sL zu_HMuybFQLpHA+3_3+ip`fvS)ut<4-pPTG=pCK0x2cZnZsA7bePq)d!g)*!I-drld z14K`jb3s8q6KUaG^c>0$hsQ++LeHYfifU~EJst!NQ7qh9_6)j6>D+nb1uHL$8~KqI zPc$mT5u-Z_S6dK|yFI@ZQMz-^NGt|fQ~wmynyo%R$F(&fmG%(NT77dPQZr7b22waIU}ovVF`Uy z9T;_tT}}&8v004rVlvYaiK8GiXHURR4MAK}Ak2@vW76;$w?34sCmW?(_VgLm!{uM7 z+;Q=QyZ1WkR0GFK4}rF7A<%#-=7C(qU(*KDS;j3Sp_TDsvwwyxF%D7Y;S975pkDqk z4>TJTAEv@VbG8iP#3nQmeKSOSDtt6~#SM&_ur)#VpOu>98CFTmJ|$);3gEy3dTD|n8_ zQcuw6;~`rCV0`C^MpODy<=M{lt*5+fC0kbL@D;XXM@PbHs|tiuFwDa*LPl#XY0d}|UhghSfSq*Yp@;cUHT)O0@ZB3P?Q4R&$<@*@ zYKb1Edjq3;SXK`yz6_K7cHO%EMO?1jxBt>F*+of%AXd@hzd~wtuv>t+q)O~;M`HBh zb|B^{r4$e#>ZJ$7u2tB*&;7sGAEMGsPY*5P60=r9PnB#>5UQ+vU7nMxtvc3ISL@(v zt9Y)C>);qh$RDV7f~|a_D>44*%({M4%-hDaZJ%wQZJ%wQZJ%wQZJ%wQZJ)mToM_HR z9F z#ib4;j?nDSC%x<^iRm=(d^0>_J4sCQ)C&T7DYTUplQE%_CyXpXJ!L3|_>OtTfn4)& z*bmPvmyC5y7n(qPBheJ^BKsf`C(5C8Jd&x4$Ouv078JQ@almAN-`vy?@`2BM`oF$> zr#I?yH!2(F-!$pIj<3jiXIXJaloy6XC~^cR0cnsR%b^I6#hzruUdA%H{3_(A0x3~W zB?XCLAtez`g<2!A)Lf2gqtmC?ic-gkL=X{}IoRVmnrNX}SW?jR!YK>4R| z#*?9e>)7veKK^{b75)DVbd$Pu-l%+I{(ALJpPfP0AJgYXimLHy!aAITAVyGzF95Ne z9QK>xbFq2G)h-KCK8hj|z#is-MV#Y+^kE@$8|DESe&~HmUpVB9KTQwtnM0>;(W5mN zJld7V|MuYzx5N#y1jYsUZS|pyljjk(mtmXWQA2MQlP&%zB-^5`5sA;>&`1=rG>Dxa z*otIXY)J-%L@Fb24Iwwb^s^J@0xv@E;Zip%_grz=0b|djh(%TMEDu=7UpNW)=WuR- z4nuh)z;Q87zK)j!ROd}$MpJ?{7!cN>3J)?)o=>KvL?oWCBM4CV2Fj-$u;I?*mp(9c z1chjJ-lDw$ezCjRmoGX-b&O7?p-`e*i-9;;NW#NIUErU~qB*ZC466v^*mQu}( zg^nr!)SxVHA(l^|XJF?!l#}BLd_hI=#)UF2VNQD_&Sz3I4+m);^ml#8$pevmictLX zB)^1quoj*SEqG@P?WMZ{l9&M29h5Rkr{ZF?3xAz6eN^V25=kxJm9e_I+i^EkpYXR-~`pSfQ-^<&sb1OI(I_0FH}GU6}M} zvX}BC5cyY*bJ3<0D+}@ZH)U1u`E%|%L)86ppCMiuYc+nIm`aneAYy$qK5~JYXhMIf zpBi53ZHlAwv}c#^N(b?42A4CB1{x;S2?+q)lFB`wC`S@aVoQ5Otreh~M=gLMmc@)f zPMR!5(kLU#lB8U*{d8i@Ykby+SUoR>lTaye9bX1-$h(zjAm_6BP*?%>VMwG{3}=GKB&+iI zEdTf6e}AQU)+^ifhT2(s;VYH-e{7mI{iFQ~{RmR+g4BrwkDQ=|6b>lNPD$nX40kDS zM;?E5W$n~Lrv*{jX#paFothkb*4|g@|qewbX z4=SZY{GX`m6}6(E&@6KuLvG;?`u(sYw%fgjixF!+=u=1QWP|V9(%)HdAg;1Fh%*}w zU+S1A{d?;Eca6Me;>?RCkn_x@)yg+3-gqy2-n!m4c5Pt;;(-rMONyC3D^lvFh$Upz zm9TiaD`ENS7Tlxv*?eKq-qFwWfHJfG?_WN(|ILMCc~NM#6M?XE@Mfq{fxtFF>*cVp zejkqx#JX%YkJN&K_B@4f!^RxQyE)wviF26u*^0l)*#y|aOqT}j&E+uQP?C{IcxKH0{?!~mXtkks$H!2bUNSY1G1AHvkF@68j!b=xhx7+bAKCkC*F|+E) z(7HE{F`5i*xi~J2E;8eeNCKW8y{SYxk`Yk>W{n27T}0RRkr&4pjKph{r>^^Cmm_QA z2I()ICa1ea$j~f{uc;+3wE(k?R=kMy`MJ(C5;2DOQcb?q;OosXr?%;-Q%j#CA6BLu zeD7ELefS#L@iD?|Bqo%GzyTE>|cBnZvbwa00_kXE9-n1VJUO4iYX*;PW_xGolz zSW!Znylfp;Ut`^mw@2e>hVuZcEf<7BIxoNGurq!V-#=u~nEz2zH~&%}Q~ptLr{k*S zKk3poJr{Z5+5{v3aly6u%a-6xt21|!CnLn!3r)LBcj7|#>~FEDn9R|v6uni{$8v1ZK% z*4Y?9SP7pXq{C9ALy+W1oREHC2ZA9-fPaD2PFSnzTlg4B@ycNEJNVi*7snCNWS(JA zQjJ7a5NAAr_^{+#5luT|X@b9H(CNUu3ylGyhD4|P&o`Y%`OLIz^8?Z)BN`` zG�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®~QgGY&rO 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

0eng`_@uXFwI@yVSgt=Xp}aItmm6&XGrO?PvG z9#J3abi*9KjU>H5zYI1y2w%A>mIw6ogZg4L;n zGr>wtkX99pFptPun9L_Mk}3Q5NR z^L&ZYCWof9DNt0})bscbur)vKx(E)(w@*Ok(spZ4gZn?+;}N($WzA{U^2X{Da{P_@ z_deqfDV|@dk#=E>a3DoF1Q!%l5&#TyAdM>LBl`K#C*-AP4g2%^+pRN;9Q8>JrzA=Y zktsBdd0-8qmVzD`o* zP0_Lfdtlpw08FS;0gMM|U@>+!Q6#N0-PEIf=!6Cueb7!*P3i{;x0doAQUgIcVHmja z(7`Yj=__b69lIFcjV;~!Ps;hb){<{fN1DV|$(DCe9W>ANI$YBf9VP|RX zS2&IJK>+K*1h6hhF6E)gYD}F*Zh>CYbMhzdw_vvL|JkBrTiLlzr`9IL>PY_M|M!$9h0>^~HCU=(v2|_G7wL_oAPLtKRnMfdpX~c3mv~rDz9o&l}_KaA_832a` zIbJyOh9k#>S7l&IB!mXG2Nunusypz#BwEMa>TRhDwB{F;<-8fXMMy&ugaphYn!;{o z4gR|Po^2fS-22Cs9gkM{?m?h6%OpWnF4ZGSY^rn!+7n^XNp4YMq^wBhERi2WTc#=h zhJ4xeFUa?SM*dH^DE|MT}tiZMR)YJf}AtZH-MNY5z=cF&L zn*Q_X?f=W?);Hv(r|)v-vwI$55(!rC*KQYCDUfExrnKaPjPS~2YNk(R{w&^>=k0e} z*PQ8QgfezFpkOSX(!pF_O1jcDwDd2%EuTN_vh#ayyUF0_Y(3d=yWMaajP9ichYgC4 zD~M3&RUG$ z&58(H=TE0HX&&f9mk1hDw4H$YS{9$Jd_OFJ!~4V`+lv#WoZU;&oDVFr|bv!gbAlsas>ns`=-XF2a{WSRu+z zW89dN1|T$O32@y|(B=8V7VdQW&8|VqUu~4idd zayZP3T@AQ>;6m=i)Q%-qV)b;i4uh4q%5udoTfBVE zWGUhE^CD(biyN>WL4cd0An#`4s76dpNEXH2aXPLR0<2=Ft*a&+(h{g3dHSBbYs)TQ zzV*Tk6ZeU5!;~GcDlrxV4H~dBz_DN==UFx`h8cn7MG!Y?{O6n<1~VmHWqDs-{;xY) z#dRC;o_vvgW)CwUWyV8FEyC$fWgxp7odWNX3v6%Z*;bL&?sC>}JR|fpXVjez8j;UL z!Kjsbf_lPI8|`5V=PLDn04r*D;Yb%4#GUqP4Mh$3oj9l*64-bS#Qb{D+znbW=uc%3 z<`jhETi9VyY}mc2PTvCk-Y!?b-r7M z-rsi-2jSHNlu9G{2~VA0d9S05au)Vux3WwsDcH?`2&+;cfT}rA;!;(bLEi*{m(^vb z-#?P4o%!uqU({bdkQzyMAP6H17>yhVs&gKi^oT$-(FRCdqe<4RSklw?(u_Q7)2hkx zjpxthRm?9B-DjOzTw{J8v7nfsQeo(Fjj98>+X1i{~uB`{otJ>PdB^HCedX?o1 z`JR0U?~_f}mF@DVW`BUquzVcUJ{rw_l@N$

#c*F8%3DPf--fQ}~t^R+JF!b2TX1 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

;e>Snt8CW!1P!BS8S3f*)%u6c&ap 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 z%0A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 G)F^(z@DepND1YY6CPjp-bg> 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%0DF|!%_hD*K^A@u65N6IzGu>p{YQ{ zNO67Tw`mYslW#oO=xIhNzr7qe=hkO0FS3++SMUjGnxL|Rc5ij4KAVj2qJ-E(nEKjD ziB9C57&e*!7=}Z>`((=2nOvh4ah>bsU!U38T-x*O&8M}C*%T-=0W<3~Bx&V^RQtiL zRJ#KD!IKg|^9nuez*FJ(Yy5js~n7iyT6yRkX2SXzMoun&j@8RfL~%90ACBBJlw#hvF0xT ztx^fl`%G)UWv^Hlt)95eh(7mHHk{;1Ef{2|-=#^)QBE|Yf%z-1PooY6Xd(etHvhl! zN8UcltJkhOWw`XA&I#@2$ZN8QBhrfo(xDUS!9k*{rx~Gp`h>p+X z(eY#NqVHY%;(>6#3&x3$Isw`HKjN}5vLD14y@7p|>pj0c-ErGWeC8^%3T34$Y1OQg z`Jz@EC~8F<04O$ykO!e`G@94uLr$&Q3&vW0WmmmI5vq_(Tbj~h8z;u{m#{L|Jg+5o z=j-=eIe?&zSJrDEcg+^<@sZ2Xe#Zl~`yX^ja&o@A+n)0}|9rhFg3(W9dFRU%`)WY| zw_`(>+}^hpT2R21_?T1+{Aku@ojxJ28=t>YW%|RUxXykg=gk#5_uj0{j?LDCekusQ zn;h=147_djuJg`(Lg(ok5AZtAxH^wpN+NFayiW)uSga804HyYP@Q+Xos;P^Lp|vjf zV{qlMxTPeZH1NZ&lL)n^WNs!_V4L5o)je%uQF&Rh-zwpc4tNwk1IP463cmEnzUj0Suu)kNGMoL88{0zLm^;^{b zE9dRjE5A=g(P%M`L6}&Q5N(`V+C(P%m+AtsG*M5ZS{ptRtf2X&$YsT!_E5FlS zZu<1FziVC3|KD4Ut`wO}_CvVum&AYAW5|zfh*rIAhign%Jd*xk$_ms$SN(RvP0kxn z5!3m#9vMIieoaQBGzyEIV>iNQbn@=X>l-Mo6Tavl#3K(rI;^kOI&Z+R7&dtW*q( ze6uMT0(wf!!!#Qi(|*_RcSb*GCY5zVb^HDXds7#m4>k024pe5+FaG+$$+UrE;HvXr zhnuQw=4DSqH4%i;74DSHnQ)-;{*R}f{_kt%myKT3N0HIKRGEX7PT+)QsF4}Z$t}DX z05$z`v<1*NHu6!ua9goh+gJZOY+3Jc;vS^j{M*f}b(*0>VVC**Jl{Oc`m~fb@6y zr_l~p8V@#4-(@h-@Oybs?{yE_n&Cy{-+_KB2~iAyE!8V@h*Zj^!poviA55Q-6h%SV z&%gNT_p)oQsIZ&2FB#jn?c4Tk`?h`CzHQ&OZ~vsNV3QuIeEQpU(L*1*9iLe^pc}~w zMAtH|ju)GGfIiA|GdRR7;3OxRibAR(Xvia9+i9}G<#r>cKa{M|Cm^1fb)mGwk|q7V z>81Ahw43p!jRzrjbn?Q|pMQ>n-goYS{cQXKKI>sL==PgRdn1iyiffEvkOBYL9^PK< zSN-vGoxZYPum0{GL$&+f+OF=U?CXKJJ)M6uadYyX+k5z&ub4|eXSv%cxIgUL%g?i| zw`paz*TFr!p98u%548@|?#taAcz-DVUUp#V@w&C+{hzk=(cO2phtIKSz(4D`AH(x9 z8JG8L<^7M+35c8WcEu6O&7(JdcKdF};xk3kA{wqk7%+x9jV)2Nuj6KPv7 zTXxIHQOlQ&&WM>R(l}OeX(Vn;^03`~h0TC}In6GOL>q;nTR#UMkFwA*xzq4|)s`~0k z-QHwytbasGS-G68Bn2iJ_YH7D{-|5^jK5TcEZs}Q&l3D zinavAbcBlt2_T>iwSFiE}~#Kv&pJde}CoL z7aLt=OQ)5#SL%ELp3k(T6MO+qYeWUqN^rGWO)rwDj>nmJ9MNOOX(16RGn*=vbx%_BhW}0!87!-LD_?7~!8(Rbc}iNEQ~jv!J*g*2sYWhh*9gP~zz%%fX_2 z4M>-9lJDe6@SScc8BWjOQ%nGXd|~ao3oHfi-1+tMTT}a9VX`Z&FWXywoK6%Q*A^ZC zmnYa@Ee~M!YX^zVBHPZ&04znzEg^bax- z)SbbuT=l{A8D;m&dSD{Jx)!_F2Prp-?SOb^RzMuph%s@@vF9wM#}hB8@i@1+8c&7E zp?tIH(-AveFs-=jW47)oPP?}~h14++V*>Ky^7T5!zF4p`aBvzdC;~Jx^93xL{I31KH3-VXw76n?1-j-q5>cko0$=-(;}gOMADMv2&&iE0wN!SOb0QY7y=eN zfUIH9{(Jrp2VFIBc&>QPmAxnL_vn~M2}j|b-E_d~JRqO}gL~T44G@SdOdM7UUJ`JJJvHtsKf{Kn z1{g03*igf)$L;rHtd2HH5-cKy)&50hN?_cZYX|Q%=M`?bff9Ee(le6zM0<5Q5#wXQT0$pY3iGCW z^)6pB$;W`yGsr7I;ah(wI7#B2pdbu~D6A|+)$vJ09T{%N1+r?6fKVkHNz6DZ_S%ih zAI%r|o^?NJp8NkCs5yQFACvvLzxD0HrgAHDt~+FZ*H5JKx=0+&kebn#k{dE?DD3vs z*0}>3K8ARouP;CHnQ>zW{oMu8+)8lgt53ga6bU%782ax>-U|09g<)|G$GhbW7$H zbyDM`U9|h(j?(VG*j>9{fRjEM!{=d?E0&qrCqJ&nXRgvckJIl*ENMGl$3f+Mj6UxjVgZ8a^qy007p?$Uu8Q_A9_E+tO z&bxBjA+hu*L1?gvQYmXDab3_$|XSHF&Xv8DGE)2>hrK( z9#HR8LJUnlLQPqLe2+WU%B7-wIL#y{Ge4hcePO6+_}^sAm`xLuCi|ZI&7OW#@uOs8Vs|MVAui{+_|?|PaWDFT7Pvf^DDJXdUy*sS(mA?Ywvd-L4Gc36DOu>H3i>t%N4I^~$j z-;a28%37nKLMQo#JkxX`Dh+w)^lz$o*7L_{Y!~UE3bxLn3O1Hhy~2Ni!dH-8v`a0b zkeb1AsThl43;iDqzyAH!a5kIj75lpD9~i$f*Hf@Yp@cv!xf|&&+m||cr3T52fattYXU{0qT56uYJ)Ic>M`wMgYx8=H_E0hWagc%f7Hs5SHqy| zC;&obM22fqvF18p0ux?Yn1A7`Q-e~TdwBJ0@2%f;fWC%cmp9NVJreKF(-mgb!@hOX5eG1$<=z0#E z<6@Enr_G!Q*oyVlB-ee10kGeh%bb8FDRck4Y1eyipbTrQk26q#$r_8qZ}7O`e?&qi{il9+qa{VSfe8MG^90kDH0-yGs)sN_K^y|8<)rcnf` z*0at&k*zS7VH0Wjn@?7%UcP_x_^U{Edk3>|INwT_2rIO-pRY4%vIV^@IC2tBm^S?aOXocC4W(A59Q-fO^US*-ONHx-;BvZ^)p)m#dkV z@xRNaUSXBvUc0(s|A%H3PpyPTSAz*ec9hfG5LN}IFzS!yMNWc=c|IkBS1&tQ$mK^- zbOcOTtOQMEUlf3bqAi@r%IHUii zyZ;Pv`H+EmX`Tbun|pLSoO0aBSA6>D;FNBDF%HeaSrfd;ao)-9U^V(Lo>&cdOKss6 zk>_F|u8D^M1E{wGf2WR%ifN{G{p)o%_Kcf5qL_XhHht0VCmIcku0`QpeorXmE(c>- znc|WqJm9lW8fx&WdF6z=cl*|59*@ry!O3U}Lm739F^(oE2o;?XPJ7+s0caS{>l)9* zd-QJpi<8$48Tk*L(|9Ga*L#F$i?Jf~vr!7WP8XEnF)f;*vbj6v7}Fh@}Zx*~>S6f9%-bKI=u&grolv~a5CA56nQ$Dy7YUcMteg;_x+P?ul$f9a{xTTXXJop% zrxs;FYFE>Du_FxP&Q?p%?ehT?SQ=E@H$xcQ0@^2unJjq>p;gmk-6t7KX7JXO{z!3_i2tA1&2sm~ifZ&0v zP}}h=JGWZR6E=IyD7Zfi*9a3HUjfHHZvV9OBla;Rl#4Gr|JU{(rxuS9)W!}xDN{5Q zsRhPBh?gmcoCcav)jU+*0%WhcxE{l_1`AGEm?aL^-Y0!_;Wg{+`C+uJr-6iWy0X^; znY9ag80pAE=0aAsM*$9NKERO&=&A!8uA%@3O{4|ziF#}ad}{cFhwXjVC4cO<&LF@+ zTbhJ~%!>suxj?X+lmL`r3m>$x5nx)v=R_Zo#{#Z9i(+Bub8T1<1K3*u;Imlj8GTGa zjXM=fZUo6+K-hakz}_&3wP|7R9-0AxAqN}sum^o~&Z-}GbPgwNHDoSIFO0zZK6roM zIoh;?nsc)cByvVaPfomKzYBKs-#rR{j~xGgj`nyZKCUn&mDnd+(q~!U>`8yB)!AF! zH}-oJgLLO(kZ#b3fN+qeK{;_D0tqV}z*~WDBoL}*K}ah!fo<7%{IT5ct9u-eO)47> z&z*VVYxnhV{JHA*WLj(wvZ~!WurXYyF}z#d7-rL{%AaRD z|7bXEPXo=>Nb@-0Wu!Q;Zb3)mNI+EC%%`B|kpY6TDrHq9nkbMPW`PWXuS-gJBOcf! zQ<$+q>)+I^lrz z8VekC+1*-|hi|*D{)7E8MGp+|L2MGxDZyUDCerB)5h`#1I0P77A(~C7x|2~K2+59S z82%M~M3(^8A%S`%iVf3j_wT$-37cR2z!obZVp*#Jvs8;*k|)k0ZE4}8APVWmK%4-6 ziv7hRT+r6bp3hVk2akWBX}h)TkO2y0fRyey#$U}O1(10t9V>7${%RN_z#~;9YDaYJ zsA3^ISaAU>v2akao=ft8wwIS-EsAQqSByCP-raxuqhI$!4ed(JM}hvo?nc5#dSz7F zI+>5SC5cZ)678hD!2p0BfNSR25BpO6dQb6mmh#o4n{QjOfuMo*)rVy5z*@=E&H}!F zOH(=#3)BE5)bR|^IZQ*K2e9}8H9iOj`l9{l`E=#Ryqd#;=g|HM9__!xBH>42)ZU($ z6XXkg{z+cD|KV-z@di99cfz9PQ!xg63f?#1{RcWMi~jf;orwFgChhU6B-&5Mdy)ww z_o3fj&@r_<&KtG+X-ewrouAt6-ZsPV9pUxOyQo$xoO6(qDx{h{Tq=f8dHK3$on(y4 zjR5D<@SD8o%6~JT%$YH)eNykxC!emop=_Qr-AOV{9}d!WC~ICgr#muJs95cp>iJK3 zMt#vw&uskK>cxMH2ukaZNX+KbbFSVdzkl$W&YyD;i}oU>@*2AT=)d&&yZiSpn5;ei z?MdW~o1PlAP49E*@1HuBK_C?-M{Nb*wyyvW>}^5 z+49y`(jR)eUyM?z(5YR>3ey>ig}LDElRiG$D0M)of$&8dHvYJkW*83*)%&1ypPhYV z_DOrMP$juvT&v{`eHI@-^($({+s+$O(zZQA$thK?_6r}!$K63kY6v?@qeZoqTK+gy zI%Y|C9tENExJkNm+$8;%;wH_e*~;U0$L6o4&gp3YNgFp$_9Q@*#NnYNP73unICQqM ze%*_wo%q@_W^Ll2Z1K=KJ_&$L81Uc~BrX{dnzNiXKxt*!Y$n5tY$Tlpbs$|vCn<~6 z!ijv0&B&s!s4xvgbtD0hS-`&vaW*~>MWnIwRq6spPYxca57~7ugP|yb=!`YXIH-6%MUr8@Yrg8VUq|Duo!Ik^zBdBAwzPkGPx7|2z@LvE$AG=YZN!Eer2S z*Bu%_cVJ)ytB&=>>q7w+62dT`=q^|bZfz=*!3NU=MZy0EYf<3su@^3X^2PNZ4H)_t zPw0gd?i|H*``n9{*=HHdHm(patEUf(mL5nu!2m)LK+nqLhZSf!#Q#=uf?*JyE8%ep z0v3{q`;0p)_~l{4$UXkh56!5x6oNaB*az{Nuipt}D0jOJ?LWR42Txi|nq&Pl&r$rp zKk)49`=4kuzm}|)()GlxteKkG#r_rDerlIf#W~7sVegGszSQ5WjdSYI#3lF)EcxhP zEFU(nh3|}FDOJgxfu`9y7Lv_GdA3DJ$!r!fTM6;kb00u)~1~5G1832T%>?p*APc4WEE= z+g(e7*wFTV-;yfg`9-OMN{F7VPK!yHWCc(* zw*=}T2oEY;4ZS`;bT69Hp$s2|yr7gFTt!Yp3}bZ5e;-jU-Pmu)U-VYhPp!=m&Q~_w zxbO9^XO`hJZ|XB;Za1Xt@}&e>z{T^`X|W*^#~fuOFOn9CqK!Ku$s!k(^BEI(rLDm1 zg{bEOo$KGI`5Rz`j03JP;dW;Hyk{4UKI_K8yP5uPY=t3rzVfsE{R{bxL`t-4K3f&7 zNu38BczG#04$+tZR5G86k@Ri8^uU_V6cLS5PVk+C(40cld$6A(yp)B_*DCY?5(J8| zZ5cqQf(jYnJ5H91ay(zT;&s$#th<46^w^!}}&otFHxx6~(tuPH@c zrh|M`$ndDx*;jCr5yc5S3qo7~Jj245O0iH&8(!du2&ot=Ben_xmSz~}ru*YA3cj=n zaE56NXrdgL8yEf(h-KPgV2z@HJD&mZKlq~-ma+*FDGOpN^Gap&-Mc3uUmG)}xNr*sHYKq-<4eWRTSd1EMeggf zNna`ov=f`lFS@u=x&P{C-3NTRxCa4tRHuYR+1Sc!k60q9tyVgPqoC{}?DZXLr?#uSGkn(Zm6z_p6=!Taj> zv6CRd^<_65U_5};!*EPX;~F{9pN@hN7z2h%xxSd5P*e3Yes7~49U@l@O@7JCu0HX= z!Monw|9_43Qb%6;Xd*8+Ds#?z>ac8EebHzv$2GMe$aE;frLYTXjq#E!rrVLN%LGCc zDCzp;j3YjL;LDo__1-wTNg4aeP*?qN(+wBj&dMGuoq|NFLa{$LS&&;o(CiBGa#l<= zwDMvi&BfeuwZJE0Ok5~;4Y)~}^ID&ae!AfnT|&ut4WLjXYgU4yET5*W!bA&6`nV85agVfal%nu8RunLNp~6UXbxeV(6T{k-5(BSWhs{Y2QWev0w-SNLLrjAO1IdY1@1UoppQ~JO9eBm;C2A_ zCz{|SiTGmJw^e;!NjxF!j4;G!C@#xnm`EVZE*!h~r&E4kJ!ET9@ynG*e|r1f^cUxJ zs~49d>jyX`SXU0UcZ$eZ!~{7y*wzlYYZ^K zI-frPSrbszCP~9G@i+qa>q7zp!?*@skjt&A?DN;pFe{Y7$>{GdENQ?ku&_H&9`q2x zEPh3R>PB!5FiO8MD-Om$Lq|@RsLq z?1y7tWut>J$IUoMPCQP{c!L7*DBYYcJDu%P7tyLS+B#IL-S|(@lm!0;)Ve`19;;wu z*(!ai8*;AMx=L-A85g`)+4Ze`_xx^suzW;uk-{B}wu>ByNG61&kkXcv(iFf_AZVQ= z1GEnUR0Q;SY^s`z$_=8Yg$J3&u6fLWfF6(kAYZr#DX1?Wh1Hvs)tiQwrbQUVMkSq~ zeueP(+u0(HVRV|d0jzdXn8hK`RFV^C;P4{;UJ2a3ni$UyFdW^52lg@DuM8gh=WO{@ zLWx|I{jz&~j6WTvD9|Wb8R=Xh)NKq%_)->7paJFJC1%zqXMTRv^qc!`ZD#3yWzV~> zThMe)PdOqrWxaVFLLWHWYw)73I3nGMBa+!_j>!9!;qwkV==OaMFMdV1G5DFV{39Ty zBYa7L)Q6}2zTuJ*QYPKK6|-rTay9*$?X8jIW{M%ePH&{yElEOiDxU_W0Qnr4ctDi9 zwfnf^HOj10&)qL96ssGCTT+ybW-)$)&SMZY;6pFfoh-(~%J3CWz1?RZNl|~OZ*E&& zK@vfTXiEZ+4K^~Ka6^#wG}HxxOelnkb+oS`y<)*0M^`>SaMyqMdOJ!-o?BQ?|E9hTT@zusDtPXKGw{Kxfy)Qlg_4OTpNLE5+kNN4&JkQ^&x{vsoY4M z`MMKJQ!c=H-yKgyt>wU}t{9%zo;R1i<;RqfgYJ55>^y7nAj1UCfNC2p5}?wp1{w^m zY9WRCrLzhDt9Y<2?AulUe_R>a{_UNgz4;$}W?PUz6d5i?Bo5u?aG(u293Ym&#;bG^ zeKCn?^?v(?f1g#!zW=K%q}lYOvVZ)Rb8Lr@6#e{itxQ~S1!{MIooS#iLa8pXTADaX z26hEI#c2oGaZh8oo(Y8Ls%py4inv9sb&X*Lm*f}tUGAPg_JL1Ey+3r=|7g}J4Ctqn ztIj;|%{wM9FkBJ4LY*b!rD0{s&J2NM9A;60k{JN}(@;_hVCk5dTvSGu0GuV5_=DkD zp4CIP;+fgRvK!YtmWA;Cpf6+nbkf`OhFVk{{RTpyBMJk*0>g(lw= zNu-=DoWzFsQ?oqKYDQIbpu^i*A?m4>sgz-{CHOho*8o*2prEkzoFmTr_04Zg{d%GA z571bWuPODpip%<)HM0jUU^2dSGyArXq3vs0r(AXOSA!pVO)8$~RP+v{pcA>Q?u5We z@NLqD91nDP91k=?ME|8659^fc@A;u&PVTtkF-PiF1J#K%Cqv;wrv$X=Wg#jBL_R7= zFgSv|*`|aXA|Dowr|8$En|z%zYC!I?4^BVRRBqgf6j7j{nUGF--Q&lFqAffyLA~zr z!5|Q}F)*}%rIeo#0uv$jK9iMN82nCcAviSkF`E6V1^6K6o^yr4QyevK_NvfRF?_7A zTA-XT$_Vj^L~4L?;b={5D&3Z<4tNq6KwGfpkbA2CoTBhSn3rTG{>!_Y&RW%rwG3v{ zn~GR*%4JOp_cAhQP>DHkAVTd)nTvSnVkhP!d_T1nMt|lVg{DUR`NJVAdtf&qcrU?f z=Q*m16`bH1S4d8a(7*4Yx`QXL?%)B@qEO9N5cs{T?E1{Owj1tw&Qxx|I&U%n2P*(L zBXoQXdQ>5ju7r^LY7wm%?Rl%9;EDmqE6ODjpwO9ZO8JUq5A%f#jRN1sixeehQw=E< zT{@vo$5Bq8JQ1qwM%ej1<+6rX?mBGIp+XP0_Zlu*jl2jcYPwGnf8@vKo}0?OZzc=S(`N0y>%03v`OZ}_Yp+*NF_=j@ zuYW3{_Qa5Af1I-CSvjnjl>-F;nIsf+TJy?@GoBso@5Rc(kCaU}E!ptxs;7$E*&vld zMllw(w)*{HgbJrXT@4KA$?X%fyx0!%Dv*kfg!zKm1n_)lOOl(34eNvqGy&a`uSw@& zz(R0{8VCQffQlowj!mLB2?_`cDU@I>80|9UWbun;)4*u~KeYctQ*YjF`LYo`wKYCc zR)t=@ud2J zvIn|;dKM9G%Z5jT8*X~!?eahdNjUqLZtE$U#j2MW+uN?s_**z(5Hu;Y!F4HsZcbX z;bZk_=u*4gsGgO!;94~FsW+YEgcMHl*z3H6n3h{W(hvMmNYC$*%amciOqiox1O z7_0&0qxzSD3Bd47R`}KURUL#IKtG;XbLYo}p7y=+?rG6cKd+r?)YB|pAK}#T(0Kg? z`A8CA1i?2ad4CiI{9_K#NM?Abd0dzW0&omc^8fCa9L^b+F!xm#zdBdiRsje<%wd`SYTa`u$RNWI~_2s;6u^k`F*~l*sZv zZ+$k&)KfMT!RAF64gwq}giFkwicQQySBLS(D|-(LQ<;EFI}I2G1H|)CI?NZW1;hf7 zQ5##rAvYUg8p4pztow+iKNS0CYp*|I_@(%`qim~$;lxC{52U35;hZWLtxHAInhRxK z1b$$&owlI(gl-DU=TGz>tQ>pbm#dGxW+TlRYhIA<82% zz5;hCicC{>6%{E886+c9hb6yN2Q#{FBAId@|8iA|{{B?b5h`?UofvC!f^h;$Yaxe( zJCO+T@GUia`6dbFMko>xMP`zPb%He?V3mBB1D9`tRsh39BA6G5Cf{*4H;c@O+KRLN zN6zg#aXSuo`}Vi?Dk^f{w5|McPfEsTy3pRh@x8xBb)(N?X#eO3XdL%%MBdxiThRFf z?njaV%Q3WSB{qD>aq&XiC->qz!z*8yv8!48wB`7(61JjAK84J4y2yA}(monmqavtw z(O)5&#V$;A)0FO6M!}KKC_puHkYOmng;%NK!t-o%F1$XuQ6Kzv>yJ;~-Gg*4Y=l4> zMieHcK%jL3vb+fei!UW3Tb$05vdqkg(0nV=fR4@^(7{6g6zp;1sZBpGd&nmp8-(f7 z0J3x)iYo|Cz+#%aVX=+DA)|5FKj++k@x+5&V@knUD=jPuj$kxvpvH%Btd{P?ZwBPn zxVBhj?j<7-p<%SY5VQn17?g-67Ju<{06RJqLTO10fP1p=R+`di^XXz; z$fP@U4*+x%5c!x8bANe^GsWh z5Z@^mdFR3{i*~-oC|}*JX|sezT5rvXHXeBM3`|k&Y_6UPxM+GW(Y|lPOEQ}lV}$hD=1nI7mJ0N_yzl40eub(@j1X!?T-#x`h&a2LE8D>`4)7O&{afJF zMj>*P#hF{P%ApQ5^qxU7GPgFcepvOEy^9SJ&f@AJ3@hL^Bkqc5KMe{i}THur>; zd$)Zv^Z?ur8@7@+sLfpZxXs+N@AvfYb+u*9Ti1hj%dL3*!*70-zr@G;cHMsph;CFf zIK^oXhLq{+h_7oAS(mX zDNy_X(pM1uy_uSnxc3O1Kn3D~o>H4%u32j4) zMvfs~#t`DKSnG?13twwPnBDZny0a!Pe{kr4UZ42)$TfNQY*gP1&3#BN$%H>3&xLSeMjo{Xdutbs5^S2wsv?vS^Szwdx%t5K)1f`AuPZDL}A zOwrK7!D)6a{4e+O@;8qj#^oU<>=VuWQ)6_T$SII^Ug% zesRdb^-AvOGmp4;xf!?Z-npa3e!1g=Kh+cm??WGVfCvMU(GYgKyB;y|Jty&C;YE_=PVs3EO<0{Zx1}vfQAUPTeTH9 z&|XAjFgNwF64K`RY+}cwn^nG*FB17;-|4mP5tH;tFzHbtDJ&6)GhJLHnMd8dC& z7;?YdoA;l%>a)!5odB{Ziy#H+8XBifne6ROhH(GfAFT`598zO72yvDa?y-f$XhQ~w z>=saq6uaQIbN#0YmQRewNk+6fQ5YdL>$*)S?F&V>XIbPUJ&WG*FSlXYCGIU%H)JzOfC5~9dD zHma1w-v?tbnjo&2T|mFY#y|l=bAqQVRU#LKML;0N*czw}cLS9?CilkDq<>&+XWdi5 z3_8>}^ZKwcUG9(eF&&Z{-kzRXy=jG!Bdy~wuS6h=hK!1doT`jUGN2IGC@oQf#LnWy zG*nqC3c4b`+C1}lNiy@0ll8|b2eyIyamt4Ni8;z)WCpO3pZnUn3%~WP?!T?Z+ab9> z>iJ{pte5FB$C~OY+Sfo+88IE@C8?Hg2gRKjZl|rcBK*4$(4FG>7-Fn0pgBsEUA5|9 zwQALyFv~#cc34!ke%evboUx#`|NpAcR*iJ**9p?`kX&%epy1}OS`92G(xQ~5(4t0V zYXF|s4AQQ^2N-$@jiZsPo_KPK2pI&yN0uoqdB@&8oSGvY^s7B%VknZ?!+h z-!OgYCwJc3-|-jqw&pHE&IfEy;r7b>cX;mh`n~_X>EF|h;_^1Y3oo{E30OjzNjUR? zXp#_0B$$xwN~Tk32}Fkcaf$-KbuJdqN-{{0`S~U}Bt)1v&|ruQt6smsua=a05dY&# zJns79UjG!{5664A;p%y0?&1A@NpX8DEgLx1|CVbHdyS69z;V{-$|S3bUW(5bqpXCbc^3b~P(j7l@Ja1QuJ z(DH$XBPg_Fio}5eC6zWH?}xpr3fY*LoqjJcr|s;>J!JaLcly9l z_{>awJ39h(P(c{6F|{6Z#?L4Je8dC2Rxhr$Fe^H-Fdez8c9T*gA_QAkqnlD_S6x<; zt8nImj}aqrW+ssqff6!|n{m+@ZJZdJkQP!9MZy%PnU%i(k)NE;58ZBTY+Yib!sN#H-*<&TzqJUo;Ci+z|;<@8#vz zVLbxf9h}ZlxtdeXyz|Uk(*|}~o_B?^2zjA50BAs$zkOK;?9h&Y$V&W%}r*bmdk9fr?b zrH30r=MQiq@;mV8mBD_H*i97_Q@uSO${^^@tcR4MwHsQGr@d<*OE|8pMcoB5YDfyrmMn>90s)9~U zEDxQ+&?xeQl+EiNp9j(a*Am%{gk(FEjmYo|%)JX&U;E+3=|Q{w9}5{(n5uGf&wTvR z+gq$g4jE+!iC@?ZxMNZ-L8?!uGhi(bWwo?USDhvmCR+> zwR)^Du)KBI44N%m%_S0SI>k!>I*Qlmg1Blv#{u#e6Z%Z_0?NnUgOGZ1J22y>TyoEq zr4SQVC(CA2fPqqgNv)U)I33A0u`xl)!23kW7M9IMp@O^>c%R9TTnS|0eMt~%Pclpw z>=FQnP~fz%!>Uy~7NX4y$iIEDg&WnQnn-W%+BYA3{>69WhP}*26s#o~4n>;gfK5A# zuM)Xbv?Y&$MgzG7Xcke|k;zm4tw_0@)0>;Uu+M;LpH>lJWs0vYv1a#$w(2c zN^;u~P%ycrhXn3mOxQT_2B z!Va3_M_YE#Tz9m&s|WGr?tbL}bAxSZ@o>kbu@-C$){oMJmiF=pj}Y(hwkACC=faV%8s)%421)KN%UO!b zC^4utkVm!lRzZs~Ot2Ed1U<-*AHUTwL9?ko7u;~e#qVUCMk_W6^Ck6RKESE~gZ3!6 zH(7|uE!_#11#-`ZU+jPBxN$~)l*J2VNgz7{ZEX@#g4DvKWI=BC@=efON~a{IReEIf z40SBM=N%-JJ7ur+KhohPW!kHyus0=vGo72UdBfDejP=I>NLdZEv0|W2>X(XRRty=K zH&j148_^X$gN=(_@ctPclHy*&CJ5)=JyUq=$wxNon!woJfUgI1^>Hu=i2h5s5xY`F zYGv})mgajj)e8RA?#^TqygM4@6R4~qfN1_`crhVJ3Lxq~5U*g6d`enm0<}>jeAFpz zpT~fj!A4+iv>2hhhfDK~TQYb!eD>{B$%!YwIhzEYHOyCJwV*33)Y5j&Zb zTX>nWcI`WJbPh(@*jf_yPK)^fIH{4wH>eH!SZ!|iBv1#HXctn=2tY4^TMr2`ALQk% znBrsL32fH+Z}kMsJt=qG;C1sJ`{t&yqb!BlT1WJ%avnf#u3O-4JDR>7Mc{6X6F?vV zH!4HK1k=y5bE|u2MrzEx{nlgjH5b{8(j6{!>;u~r&Q?JZ%umRl?~nU4T!sbnP4uKss`=QAzogb^IfRpQd_a@$-vv*Mv+TK?!`-yT%| zKamm8lcut{{U5&cjhmD^iUUu7JDax?5tiuvU%cBBk}k99lw9Q%i&j0EXG`Sru10BZs~Es!^|m7%PfQ~z`RmiKn)-m%f^ z;&L@ZrcIc=;~ax2fu;dW0l*Sd&2kHrLEtqUl!So72#)|hp&jT)xKxZ2W8^Bt)G5I> zv9a(MEdD?pK@Gk#ZFH_DrlQ%@lw11s+9%H&LWo-C>!&$ATGVP4r14o!D0*9)|W$UxIw|dv-(nd+~n3 zh3NWq0W=y3Z|ZD3IKSI$F8TX~hi=Y=&#F7RqJ~h^7gb2%Yz}yNo^+~7Xtq*Nb~GtK zfE65pgnb!sWu{YAHpmUmw6cHv@?d)O$J?xg!)y|A!xx`;t#Xr*poo<&WJIu_Dj%rv zNr=M_l8K`D8~7)Rw2AFSHg2;aIc75VCLStczH&`~9$dmmyp4W2AD&UG?sS8Hu9}x)H{|(3FasxKJ?d zp)vcO>Qm>Qx6v&^y7Vn4XzSp6{ja^;Sy>?mKr@VH5Qul}Z_VW87e|N`< zzqViG-}jEFy{F=G0@AKIT|4WaTYtMx;C33WGhoR3)04$tt2=(AkM{38(uKXTXBj@1 zHfx*7jU4*Xi_wLz>DIQgqjALHKpY93s`x~b$WC#pGE<~ry;!;jT&Vr=?#S0TA^$H{ z@rfh=p>p{_B{}!8You_&gO!HR087Z@353##R$in*B9RC;A@buGx$kaiAGGCKYWm)G z(l=xho}K@zAQa4kGz*Z<3j|6P&=t$d+mWn1e`=OXNNg*Y@YN!2+%UB7T>w1+R4D<2 zQ##3VQBW+YQKig26x>~^5r>l5u;!VM;ZsZ*iTH&#-G&VFj|1pdgQuJU_h;^niZdVi zdOjQw^WJZR`}1x~he}8Gdl&WBJ8O40Xp+-%-_4zS+7(YsG{}Q>E*?aKATI${ zIox71A_oNN2^Y{vV?k#_%%BH-bdGcDI4sW0J$@kfR?G1v;3SY~B$Q#JAVR>$*o>Ud zRaz5r+T}}Svd}_d{ObH8&HB^P8Eht<5Tfl24Ki449ox*yY>ZFvP)@BjB)fn}A}get z(&hHFGjn6k+kE$jXDl*;lenZALIx*Gu%x7f#8ZTXAjyS3(#A$W6$xx>^bv6{Q|TDb zqC#Wzxu&y%R{;Ls3bID}xHpskm|;3@zw6(x|Nh}NAl*497ds_4Wz!qy8+jyMQGA4CJbEh>H`XF8J4kWHO3Vpe*#9z)wj@ zZYD{b;$94LGCrJp0k$`~Oi1Z2V;Fo##aCy``_XKvA7xbVLExmpbnIa(UT+3@A7Y>H zgN3T5?XEqZ-&eaoy|2-LT99kH=!tV?ZlsEx)B;ii2!>#xMcAGo5ei@#78aD@psCnq zo&U^$IS&jfH?(48+bqZ(Gk9Zc?o9zBubH-~vJyHJt_A)p#DqN_Foqo=uNp_ALGkJf z2Z9n*hyf4{+M=aRToERvonhz=segWbXLyv!tEoG<{;O{9p`QG8y{(NYd1 zN8PFy=H|b2Z)GNRy-}22Pt&WJHlYF@TuD%DgaDWjMW94mN}XnUGP%M?B?Kg((x_$2 zZW&n$C+dn-bTMaLlnY;bf4`@qM;b+cD3ve<;wzSLQ5Sj|Pl?Jb<6zIlxr!HlaNS)o z)u@4yGC41bmRBjr zVQyFV0bhS{)+6{#_fo|^H$3dFg)s;F(rA{5hwKeJ>X47T4 z_zr7wpI6(A;95?vkAVmwK;OelK+l4vqUmH(kb%{j7CEuKD%%92x~(rQSak5#nWC5H zhPt11oO{7UBin{bN_k=-mlyv-L|G*OJfR$F!9})J;M+>6E;5Y=9Pk04TG&>>v&(aR z_xt=vdGY%_Fwz2QK_CW0(4kY=2C~l?G2JY3$r#@%h%!|Pf}(Xi08B$4fgsfZbb`PJ zdqmOT&Id^$?jlrOGOJ=$WmT%TIV--|bX9KVOWgCzZn@52N+USBo@j6u>9N4X60EVM zthmP30u|m_SXW!9u*E2|d?;P?xhWPF-WpWiE--w+CAonIjQH33x)PJ9G3}$87YGtT z0OyC7P#qW@1e9LrB#ff=4iBW+v@G}Vsuxcla4FZtyNDoPsYK~AO`6q86qlfwHn^Zf zMRN~Y^XlBJV;=kL&sz;;;EJ%qlsy9ep+O+S6unoH8!{warT(~lAL5$a{@)+^<}S~+ z6|XuW*~tbYtSs`pFHUCn+IVRlR3;=-78bMIN>KkSKknmsGAJio$0Ex1KuD&nt=gL;mxy){>L)PL9?-)J>>%iR%K4FGQgSVH7D5#g{gF4LNZ=j6B_wOQ)@Y*~28#H$=H}vMW-h8&! zWfU8Q)By#GR<#;Up3MX>>%(*1;xAi~d-LpDz8*QS#>mVnk_aK?4InJb*YUC_L?x;c z9u?$nAbU9Q5f*)RavMlgk}*^ty&4(o_ykc?OAIb8QMn?wYjb$ydHqQg-&s*6s7_a4 z3Tl}^m4yMA&ZfJ}N;iRG$D|;$J^&{S2YsF}JIPlcX2T6^b$}HyO{}%9G0Z^8BuYif zEWWYF(BnJ{hI_U{)3$HjS}|7U-X1#feD{*GiUY~U#X0$fqDvj}kd+Poopi`6On2v6 zc3#!7{jxRQW@x4-Bfole=dO{S;gX8%WFA7l`EFQ%rt%WOaZ7I`JMs2g{p4>Y3ML$@t zxX~c6;Ux1JWmo5Iz^%C?!0eq!@C5K+0f9q5*PhOzJ1t04Gmx;Ll7L)PrLPSd5>U#i zjrc(waM~?4`}0OV=$sy)6?^lIA3FBwk#T>2ZoyGsuTI@F(J0kok+@_AdZSIWGX(#Q z`GA8w7vses*M=&0TrHeb4Z6LYuTtcb>3r1SurD^g_wMw=Oub)<_CW5S`%eGj-Ulw! z;nm8L6`A6+cd?jJs!M2Vejx*unjtS6V5WpwvGesSWM!YNJax%et45lJ{g0=_ex^0K zeQVc$dW-ovgBOnbBgrCZB@SiEl1&MxW_g$)1XDs-NP;JnsY*iQhEjhhWPoH(uoVub zPB-yr8HT20v7P0S<{8^2$SrIOC$;$0aW6Q;3pXV5A|l>&uqbyB(T$)uoL++)q*))$ zaShwrVo!a%eof9hdT#WtQ*P0vzsqKX!K@kMG>S#wRDq`>@35+_$8t4I=|l{@h$s%(QU>2W{gKw5MnsmI5UM5IN5Xpd378WuNZBMAYHb?<&0DBNXp-GYfP6p_F`8^z zZGqb^9vvZBn}7UeSh@8*%~*UQhWA78exQLUg>REZ^Z7{K1h#3YcCY`O9FMpZF{IZ1 zXz5l5=!0#h0hg5KdmP!XcZSA}{d#w(eT|>9S=YYg?+m(hj6tV)D0lbB>+c^KI!WiR z8&66ItUC_OFG4Fu9%vwCe##zIi7Es*xka}~ZGD;f!?~LuiwAx>V}FA=rE(Ie!EwaI zbOx$>H}N8nC6}1iaT^!(pQ@ss-s#h;p-D3Fk{jVE?z{V7AGqsSA2if<4qgtgdzF^Xb7er)?hc zSMiQLmOJi%%pE7C8_0Xtb28J7fkIe_s^k%oD$<*kQ0m&=7|1X0XhtrgWsWn+k8b+! zfIj3+KW|{4uh!qDS?i*VAV`_2S59u*Czm+_f7pLX8~^k0$F3rQ#d}`AVWVyO9cK{w z#ii7gQLnx_{528@Thp}U_2D~BeT$SNop$8riI!1UE*OiCBab<)-nnD1BkwYOZR;MJ zr^`QK0%p@5!Qp{b8Rs0*@2U@9bMn~Z_qUQdZ0g~6O~XgMED$j1>}$nGzp8q39kDrH zyzc4I$~m@YNqcw}ui~Dcb^cMLSx(;fq2osnnUb!^zoy*Fr?1)M*wb(&s`HF*sIDfo z-`!&veKNP{w}$IZJYupza@gf|;~E=VKAKEZh{&iu?M?`sM6SRLmB`^?nwmiUHu9MZ z%bq?A|Muhmg75XE>#wUQ?aV)$tGvtn_^fxX)j9KJSAzKgIV-R=B~OCqQrS#_LLl#G z>T0_xjlSv%^38Oeua3(=w-ewrDH};Wu$;{DsTcza8P_=9J{^?%cQwf$!k9aKLC@w) zhg>mm*)ya7cvUyux>6)~Fr^Jl>Z)La+v{fhys0Oxcx=ZHTKez&j|-GN zmpk40(A9LCP(H|7)7Y?0-2acg?|^Tc%-?sC(p@OKlvSX#r4(w(Gf;*TD~XdhaV;k; z1**!nlF0Ewa@?js*-$o=Qbv2MD=Rj8l)cw6O4-n3^ccOeH^=7xd6z87R%|Ef!TtW# zr=UodWsP@y$Mbz2?_3boFi;P(=?=@)9u-nQGJ>B}n%4IDwz+=SPZBgfVp>IE5OR~2 zhASU_Y)1JEfJXDon}?u#Y_m_15kYcN4yEt~1MXzt2HPbeEl9GUitsi0Aj*%x@_=`W zs7iF!a)5&roM!D*f$C!m7uo=)t}O}<$d$yv4j5#(P?TYPT##vtf;}J<$u@B4Us3dQ zhv|$*|NZ1^ReRxQ`#Bzm>hXB|5#Ig)uOG+95f+K-bpXy&@NuN-?pWQrFs#@8uElG@98Azb zV+^{-+P<5+!(0E_z#1i#=unj8ykSm9!8_F^BuR+Ete8`~4Mm0-%7SS6QU1FuEdEP< zw;tZ}VRmx8!Q5z7;S>-f7R!*#rb;fY)Y`w>D~&GXmA;m%7jARey3Y#1Dhp=2Su^L+ zbxwe)M6w+DS#O6v>kTrKG!5n+96ah0<0%dWraX-Ra!yl8Q3weOMOjesKc?7}E!C5Z zf`eF!V;I)M!Eh7&O^1>>SMxhEY>Lk1)Zw|bpTmgL=Wl*CT}=VILANe_AbzcHVwdBV zeXoAU$eb>USZkqdD!3mYX{XF^?QysC#F!S!M(T4>DToB*?vZ`&iict}1m{uj>_s2VuV1Zl zQ_78#>d*IP-|OcebLxxEuo3FAklJK56xQ(aNm*%*KpB{HaZm{3X+(0L$K+xo^Su+xdXb+Z>*f4yYRhr zelCf`ll314Esg9a>%FTs?|AS|QKrjx`Z`W{a@9`P>|Id+rk9)C@^fVr4K#k0maI&E z0QjA<#9(%X;wZCHKNx0WdQ7K<^E9z`F2)p?6x|#H%PVMfsCy`;M3CoZTZ&AH?jlp7 z8&8P?r7Ej1z1w%5d;NRoUFOUO(Wv}Zq{l<_CapC!fLI%Nr%GlNSjb@5JmQA`y!o{X zG*iBJ^j=@x59|n@V+~+EPT~d9^qhnQHM+cjxSuD<8zgN(tDQy4LdZ#*g|rZ5%;;hV zU*@-=?~$_t=k32{fk`_w=k!GYBoAQrELNoXOh@|uMW{5wMml}`3Ivz%Cifq`}wDf`_f2$fitH{TF(EjszLb-pu?Q3bPjBNGPd?UCMRgDZGB~lNEUMJYo z3Bt8X`7jFJTa;}W!@)Gus>5J=&?Ah_Jy|=+8L5X)+3MS)-;rP2^KyLrr+sJLe%>2P zy|)`cF@$6C%G0b7IT5c$Nhna(3mxYitDM$b)25w@Ic`?PakCP~4Y+8U(?Xv!03u5P ziVM(O0GP3C6KC4%)eAwH&Cr8;KI?mZkIU`RlddTlbK$L>>8-^@le*lTosai&3Fvp_ zoUjnXfJV$wll^m3EVw4gnK)h_VAwmp===3=huN1M(q&+_7;_WktU?uvBoaimGYsVf8m_K* zm_egr4H^_#3kMEifFTJ7z>K)_-an0bZ^S@tnJ@Z=|9aji`mkmrKhiPRX?FzzP;&t* zOVH5g@(Z0Ud`u|t&JcN2t%A)e-(UQ?Z~Z?XXgm4t#r2&F7fx$5$(-xNtv91tJ4eTG4!{1zC`o8ah z*MB;eT}E;PODl>+(R5{bs$vnPz|0D&t5j%V(6mUT2aLt}=r}ys(v+67uQm|}@*W-t z6f`V2(drN~1u=GYFyPO7*hY@&ul~=)6&UcFl>xt*V_P`Zmm^(nFvolMd#FzVM_u)g zZq53U|Dc?~J8vPL#MgdDDe9RRJVw|PHeoyObzRe(rb}>di~qhbc--y>JQ4ZsiQmxq z_^%FfpB?;mp9k@_cfY-^Uf%HBZ#zWMyH5Oa-anr`U?=+H2-@y_@x*K2II-`s^?2Jp z=Y47Vv2C@l;O(L zigVMelOOGk#xUnAvyOZ4?(27c2XD`uG4oRCg`=u>?Lp(m(c3*Y@tHeL7{4>#&fK)) zB^5VM`gupZeey5&k7?h2#(U%O_GK$JO}zB}tDoz66g`b=KmWAR65mbQTFH?7(C2Jg z-tm@&WI-p5C@=EkPQ-XI4hRWlu`{kF1TB!B%Rd+x$mP-?KlRnuyWKi@^oUotR3dCy z3M^BYia~vU>L9&h{O7gbZl*$zO7J@Y@1!jXx7DjmH+fPcm8hNw%cuAk$at`q2)js zdsJBD7N83m75KCm?*wVtlDX6K!h!mV1RDPa@}v>hzYmm8TdW0tUExB8>9_)hy{-R5 zyiK7?x2fpTQN@6bn=9|Np?$&`V>Wl~LzU^rzOCCSp4=iw^5>)SX?IWNYXBODT2!-v4Ape^>Kk--4GL;(r^qtazl;fruY~egb#_ zE}sXhrxHR4TowsYfqDA?qXpWD0(I#9Y4$Lbxw*OBTi+f?)VCw%I)EL+6@sNYxDRny5T!55x?OqFyOoy3%>U;kFwL*N4 zV~h}91*s)em=D_Ot8ewh!=faFU?HeaO2iThH&{QM5+H8x7mn!>B_W(2;`q?ul7H&k zsa1OSyQ`MtZG8m*?Q&7}E?#!Ya!*VMd;B2$0<)0SN+8Aw{(?9(3*xwRxPmg2{8A19 zAV#DtkPGAh+H@<2t6V*P9~h>{eVW}u5>mSadKrH(=<_uN%$y`$z7-hahpIm5r@n~o zW?$=?Cyc^^MMW-XRFV*dSZ)~khZ)WRsZ!V#RSKkDRZ}jrSIuCUy?*Ljvf{&TR|8{Y zOE^GCTS`HBcLeUJrI`qPdrk-u1DHFo!MfW6u(B8yJV7=1fv50)?rT5m;!W2OW(_9j6LGL zyH*)Y8KhZMYXf`*M+h4PII(<(km^Nq0r?VQq{;gj=9)?l5JH2h5R%$Bd-iKzc#3Ji z;rmW8$PZY1`B>D!hWL0~kSL2B;^TGE#6pCyNcViOoQfPN4>-F2Yv0CuuT~!b;)l} zGsowTJsPlU1YprUVF4;^0@$h=z#0L-U(U;gqKP=>X+jxu8%LWtztAHC_E*II1wRJH zVm`N1gkfEre?WAA6`7y+`fkpwB})C%s64Yw(XUs^9USnlnl?|fn`>uk1FimI#G{Ye zR@I-;bX&z_gSNO^iO>Ib-S%bGrG1ZYEU#(KG`P+KF);P<5)I}LiyYTl=5>8o;_p6Q zra`~g0KYfX*RjWVvgIBNMs6L{(Pmt1{M|z8ZCCVwO?x z%IvKrh24}zh>5A%Iu9d<_&nY$;IX3Ou{EjK%6Gn9;!xLTl=a4NB(x+^!hYgKxN>aBc;H87X6&==eH*1`@E#)tr&xZuG>yuo<3cB( z2GCLNdKiYOizfIKss38!GQ#X zd#r?UhiQw(7%T|IfPY0xnvFo0n|sMU;Zd2%_V2GaV7F6rA)^vV8$5o9V$>y{n)KwV zy5k`81c?x}$$Y1vKyxhG-KFE_#%8V(CySF787f^n+I>*W7IY27iUPGOxoW=<>Onze zh^Gd`d0sx%RTt$uywV_Uf+BjZW0(KY&8??1aDD>I#K~D8zh%M(%9j z!)$mgWsRYrqo>5UOtI3%LP}qR!=~&2 zqzmYcQw~w)WIiT<`4${62|zWA3#Ea*1&AZyrbI}Y03U=ED5NoDg$nz?8NtatjclqK zoDym-!^>cu99XxoDO*?w@x9OvBVUFh4Rv#x^$W$6@N+m zjp_g|ch^gao}^7q@(V$wXRcNpsz$-KT+MkoF`iCz3rG@=bH#f(p!>1aQt$e~s2x?B zw$H>5zU=PfzV9+JcPI}47Nq$oFiv7IT-i@sRJ$AaXh&;Oh(p^2)SFUfWRNNF1Doa~ z$*2Q-0x37k?VH!r+? zTq-KWkW5qoLsM0sQ7rXtS1eCE6nk;2=8vbmpY;nuk&3Z#*7sUMq6G7j)aB2l=~0wQT(t3Bh!4rt>~_ zj5d@;TITw`Doxwm+2H$o;PrHaP}W;`{8?h~{aGKQ^}OS>`-9#2<2+a<>887Tbmu$k zE3`h<;P=9VBg?7j?o@R>Av@MU)6wq&uGzqpS+K2$7FH>4ziHT>8$HC^)^( zF6g*fy5CRW=kU#*h$Q#LkI}%z?uuG+n=PU_{+oZI^|xDT=c{9WLMO6bgz6yQs4M*3 z>iu|LG3Qe*upRe#TH-TS0{{?UC& zx6*$nW;&kE9C5+C11n06t?6o=y+RCeJVFq+Ta@`&Q0(l11W>uaLJlP6YszDJfhaaH zCE#<^OMDWEiqjZLSm*#ouUiBQ3v?3W6otP6eWe^7m5YOL-aLM(x*X{uNGgFE_ck7!>$_KXE)FLocDF9?dCxLk1bTRID zN|r2gD$G>lxHgye1Q1(HT8S0AxD&pyuBrR&%n?Ks=tQZrcydTFxZhB^qNtdTD zo=mLZJn@>J@A9AA6-qt)m^$*poqfJW*JVObNzfT zC{747X@RhR=Ah@>t&g_!-22|>&0Tu8f2Qup2Tz{7@96^oK}7ni4bx6mk^X|t5e#;S zV6=miW2}HVh>4Vq6*A4pG+)`c=c^OV2E)Ev0CPBOJcL@B1ssP86J#yTw6a!CUo=?k zmuF<&zxwSZ@BOiFKF9@Eh6N-s85N-Z@f;v3+{(5Kk_@Ja^k5~AXJk60@UF2lEe1m& zWn*DhkVz+^Jy2kUf`u>`Vx$%?(vLZx1dQZ!56qaF#;v`4`&aOuNAlAiJ{}Oj#l7Hm za|&c!3~b{<$&j2%gI+BOVJ_9{!OH0}$eDAAxPYXVDRQac3X${^)&q|VZL*-8kmBG8 z96)tsLx#4Qn!))4y) z+!VwW0EIMhu@J5U%%lp@7pf!6^A&sn>(%4`-7;vcKz&Yt${gf6^!jfS?gJsUL zx?H(@BX}4JbygWgVN|B!{!Ld4N4j-shnzXDF7Uy$<+M31%3=zo-g1nJe(lT`xn$lU ze=L4!#}V89MMEe?ud>2KXQoWQAbqRDMJAsr?Nh0C&vE%N5iu~oAOHyv(8&e*7kDgY z&I(~cU9R?j)m`2(a{iV8)|Tb}<7Jwv%T?*3>RRv6k$K{pV_(|v%L9gXt$Ki>Xh2iH zDAk?}Vx^W@2g=r6H>Kc&{CF_7*q7%;_z650JfRF;# z&LLJwzBeeK%IU%sC^m$Ggg;pAa(N+cL1uz~{P9QYnSH*kN)I0#l$rSCmrLK=)tqmY z7Lc!KLFA^8_Z#p=0~-m^F<y%gW`7y}b^D5%jX$iOV|G8Eee z1Qh;!PKwf<>O(SJ_g08zv6fu2jvNx1Om@E@Cwioi02&Frs_Kv=001YF=3rrLRp(8= zJL;ofp1Eb*s6DrI={(y`i&{H0V_kRq@Gn2w$)Mc;N^6HGGjjv}*`?E2ADX%Q=Z5d5 z{TwhF9rX4eZO(}atGJ2~O+*0_BjAhRZd|S>Sz&TzR^7VwRPWeMyyt?VHX(tA`vrI< zoo+#i*(tT|&KTd@z^4UP3=3F#kl??yJ>;fohu$;c`76f#*-)PxNVhJt?#in!A7j5t z_dv8%x&x?E0#3iph-6n&IR$!cNrbzYQ3(?6&hs3-?w7R>R*wA>yOX-v)n!^%Jb3WK z6L7A+6k+mW@-SFmBMuk}5*;2to3inMG20_H$D9l^ubFa0!8h6|yT{3P^>lPZ1>H=V zqu3T1O)Nq)yVi1rlw}H#2>>9iMPb+OdB8vM&n@(*&pff!4obiR`{@TPG<6a2y<}CfcMS>AeXwG0rb~{YzY4R! zRx6)V)v(6T%ZH?dOX}djXCs7_yR zUr~32nknq!Rp_tzV*?PaHtjt$*a;KREuJ4j!{w!`jDp$CnYovbVn>{~+9=OjtYFuw zRGQ_*+Tlk&DUk&AwK$?Z6!c+)g#B6W)VkAuacnbK{g^6~H?zmIW5@2dZbhLa926sq zMhMy?3WsWf4~a&AN(;7}fU=DT?$PClWvNibk(C2dx3)Bz=V?&WR8UdO$^k8yT-i~@ z#Kp(#^~+^LmcjF87OnkavG@5s40G)&;SYPA7#HO(7%|n5i0K81jY!09-0FY_I^Os) zN{;ttB7Zz`?sdB+bcU69nFU(YP(9UQ zrrDWQ>wcg6$FL&}3?SN$3wi2=I0?ho@bPZwOxpm}8@MnppG<;69;CnQ<0YV3i+6g$ zi=ZJ1ZW4s_%l5fuXQto(+k#8>T4Z#I{V1U1AVayLeV7zTNh_k{gdIi-5+fnXJFnMOsh)8raRWGt?!7y7A-aO$u^dLQ-0n zUvDhJmxZ(d6uS_FwVV0gL=Uv#ZCpWqU>ir7b3X#Iad0Q%^a}>r8ZH8?eToT1AweR; z1OwcL9bdb7&Bv=p4OIi4omsGHtBtGo3m8rf9zT4VNWa;V!hZ|3iDWPGXRK{9$t>Aa zBxrWxcKdOHgqhy)MBmUzf%!8l?wqq~%`(;~AFU{}QA#ETFe9=l34pJ93rn+6c9;!B zU<`C8vEUKoVxPzR-mMsTaIh^iZhuepV1{R4$RG$|VgWN(Z~b`B2lk#^IaKT< zdRuR9=H2PSWv8qoH3~oGc+)5dP>de9Q1Vu9isUYI z$^6E?e~9LMFaUo2Kp319^2#(208aL#1hm&LbOOMP%%m6c+0zbL)jgWf9Gx)%g$%dB zJY(}-(H7f&eK&g1Etj>U{)0b1EPy)x^CzMCZo3|gO~L{m$4lDp=MNzBG8de2`{Kur z4;bV*jOTC2KA{jnz^9M2%c=o16vyzv^i~f5NkC6)SM!)12BTJ1Y3j_}F!i!Cei~VA zU~Sk%iB1b4%G{m^@xh)bFZI%Zyu&96VG&?s0c{E-CWb1MFzj7cVyH9IxBK-|k65&G zKS6b;D7X9A23Jk#QT0kwSLTXaz59K8R!x4((nIxBxo{^6v9@+}$U;hntQZO$i)R_g zh0Gm#$TIICAC4Sn`g0a?k6oFcZ@P55%Xe_*hoT8*0iM+xdtym=Fa4ruoVD?(?+t4o z{bz-UO*-1R3R8DxlWtU3mu>ng~7l8`w%n<=&SG0q1K*RrP=8QNw-)Bpm~{(t0!NR&BHJfQuT8tTZ)m z6Kw+(5lL|Uame4|l3QS|#1khH@g30=E5!JCN({-sFk509KTD1EXARc*SGG8led(Dg z&sPvRx!i*U94COWCM6{RPYoAMB$6HXqS58?m8CQiSt_68jiBLVaW$f-oe4o56W%OM>PPO*p>|QSYy}+ zf53(>0knK_dWK;=9McvB%JDP@=&&Lz%|!xcF5-hwqFlrZ>_-4fZ5WD1VJNdv9E#>5 z5Id><5lFr>hhv+7)a%i^)UF$hgnFgv_{?YBk>=rBA8#t~Dy^!j4UVS~8#Zox!46dx z4kD*;M9xeoGXUNXQKdCUoCHEjsoe@3cl3cZ5Uw?YI2-HY=8WE@cQEjW<1@?8fBC>W zF0+>i!!UDdzK-SyWru)Ox-b~ysfpWZhVc_#*r7;IUXtm1sAm2@Yub&%9oRQSQVjqO z0!Mes?iJ%=Ok6Cu!ePl1Z?`ddZX!b5n`I&8M)gcgx?JLyD$@y>4>x|0xcH0${vdHs z>f2jDm4YAx1T}4Sb#_XSa1K2(UqJ0)}7+?jhEJoW}7M=9^IQ^?WCjkK!pAR3TFCW4uj%9d)1V4HVpL4^vOK5=T%1d%=aDtmIn8VrA5JW+O~gCIPH5ah)FGK6fDBZ*x=$6R zb22aYtU77OyPQS?M*&ksAG?O3YwJQWDKP2sD;Uqu9Jk7QvCzHTFsm{I6^xeJdQhh* zMgv3$5-aj?V}<7890|Gur5EN?9BqTKn!HF7>ajxumwQ~Fa zLC}L9Caxa2uE<2rnQ}`{_3ilq{if151?XmBk*Fwy71YFYx`Um~d{BroVBQ}(IXpYE zx&R#?&9QghJ^l#KhKkK4eOF<+Dsx=d!))~R!;HrR0=vWm0qoqCU?LH1m54LI5Dh~+ z>hxtyWmjjOIq1F9HeM9Nd-NqoiiK-|X%?W86_7-M1``D<#yg_|w0yy=n$xZGkA_rM~@ZG9%^09mnkR{s8A5at78pgy?`Gn<(=LI_MTu|F50Z>Kqrk7WkuBK-e2=WRxpq{+{m)bsyXWgN%hq4%a9`4pkLevKm4Aksd~IFh2&~K}^n?bHB3O@#(0_5u0JCRyW}rGH*`(=9FE| zINM;NrK~QOTS!9F5sYIL1-2kc%H_f@$*R%HFJO&I0D^k{9&`$4HNbx_(^lf zIC1K}j6=iV`&dV$^hx{1O!GN6$%kG1FWq2N4loIXO#p34yff7W#RP;xELnQcktY@t z!Xl`{%8^)6oC3&TYngT?kIl--L6?NKGzq;>O91Daa_7SE^#1v+oca7#YuBUoZ>uT{ zntfkp-o_vH{N!-rs-KwGQNi#_Dj!h&8-!ui34E$YLcmg$sq*{q)uR{O;~nv5RX+4( z-o52~DWAQX%Me5nbt)SqmRjP%hd`kGH7L?h?!yX`Ag8emAx~&RYsA+s4XHKrZ zC1)Xl+puV&{7fNMptMg95uvPVQ5*p>`Li?^eEy}?$FKfvMAcC66R2D0&6&G@eB}kn;Xp^)3)aYC(OH zm;mHavwMUdfvT4Hg#hV=r3&0aG{vLTETW!?%x+xNcxv0ms_}nOZ$b|es5IS@nKJ## zC6{bZikhy{cb`x~O28;Q5=jlZ#S=1-%IZW~kE znhsbrFmmJ@bn9|Quv;=4&i38=>J!tAW@0KPb77Fe?u-hQ4Nse(e}m>&PPnGRbZf?4 z@m%H9XBmU~3kzKjs_)=f0fjHbcqa*tHVd76sMn&@!{w2eYKjhcvM-3HSSB2@0 z%%N8wUNI|hRmt@d`pk$=g@%Piw6%N^Ok=f)STYfZRb(JdJD_3-n^G*HB&jf=RN&IP z)~ahIINjQ0u$}+H?#xVko&I+3-z^4c2ur#{rX1i^iSbTQP0#kqDIsQ;Q+%oi7x&u& z3jsl#zJ#Fk*Kl`c-jzQ-UcXQ>XrT@;QHt?SLRML=o^m+}XfVlQTHv~bXi|_IWG?nW z;sG#g!O5yIf7*@tLaYOdK)?yuoENSAY<88wgIklyzO=_NSM76;QPXh}ynjyk0t0?2 zBzTT1Lba5f-~igGI>09jJ>dkZaZtXI$Fgb1Mz2-*QJ8;0x;Y%k;ZkNc#!D%0PfA$i zA?#61PKd7Km0#09-+b_x?fWvjU2&wfdf0%ufPgfM*Vmd%8B~GTngl5>K*4yVnPL~? z2vG>4aWPR&iJ>}iQ4$Pf=A{s^x6abtN zC3~e1CPJ7}BAQ}unk)VC9?q=2@6@}SuDvGTQb!VN&`5+REnX5-RwF4kQ5oc*QBfS6 zA6qFi6bBMuAQF9`$KlvGtS!SdM{K}yge55y3J)Q62Zmwmx#pOeX^vSL#tMo60ARJA zV;i|U*NnfuSK=xLYm?`*+dPta^|Z669sm6(9pKG)q)^PRnJ`|0{@*T0VuzT^N*9&0 zqAZ&4QO-2@s~1?YFrx)#Z9mLh!Q-HH>&K;oF*aq9fiF!e2xGvTjxj){OkGMC1JgBp ztf-QoGIL%ohVsAQWI0VNe-qotu_?;Q;q)+&#By<=-7T$ltiFLOo2`{ z6AklZ6K&yUmNzXqVVYBOsD*;_17`aPtEP35A5AkYO`!HG6jcH|F@o!K7C4>>zB2B{ zS1x_^tmP9I$s1e%Y zVWgz))G?-3;|HXsSuLj^;~$yJ@3S27&YE$$s{yz-7Nm@0VLny*>7LCDtGXfePWrk5 zWI>VRA;c0=Z!?Ahpk%QV!aPtx2$+)F`6vd(7YI|({f8-NB}_r*hbi>ggTtRf>s_jh z?)Ul3r0(p_C(Lmec?>qTw$=w+hIYWg3JR4W8LNVQ@N19|r9S8?(+ipB<;Bc3lhb)= z3$0RkQ!2l}!j1|oV6CjorTM5w#T%rwFQn^T4gM9?_14opO<0VC@@AG&eGx7C{?;75|P8Y^2*@cHb7R<^Ca!MN^*FukSxsiE!C^YR! zP-?;kX}Fj)V-I*h108sa1z75tS6^8Rus|pc2iny@odX;OZA>_%CnTnevG)LUaW{i>`P5hDn6!g%t|KabJ2G`?IwnY+0utJmL1`!2&$O~; zu24=01P&QVg7oCfUa$Y~@BOBJF=n^_&nErc1AissJMV<^hOb$b@2E8?BJ@!{t|6nR zkr+K@j?cvm^z`wH<~+u#TIHk=55#h zo`Et-kwr&evEY|`#|%DZ_-dwZ>EBn3Y&|Cb?(-^c>!OK;2+|GwZORB7fuz+-fk!|! z7z~d%dG;fbgO6N2Z1A`Add7a+Gb_LL?mkd~Efy*X5kR<<2nyOvV=+*t8vH)5h2@&5 zYFq)&G_=N}z3>xRTfGm_6KQ{d^DE~A!;~+8`>=MTbzv}>orT!|Q0i9-3bal^f!1*- z#Kee;5G7W*V5myd8<~a$>kj$$bt*rBL4ZN+zTA_bAw_aL6ionnU4Y3zP&y`knt%8W z1JP)DBlGs8V_IIhhKL?CRO%xvDsu%{<~kdONi#NR#bEPVtz>r7HT91+ZhymQl2H3} zB5F=ken(^b-}Xi%VTX30T+fcg0>JApzDCDSI0vn1-CZny2uZ>XKleGf-tSX17aoUW zwr_upa-+jeF3h9qTZ<$=XTN~X-zcDU08c%;v}upu{St(}ZkvmaTaZAO^4G7U=RWSX zXEeue`V|3{NOEXbmVUEWxHp zi{((b{%-!50ggv+W_Ek+y6s*)i10w?)1?&heJF=0`?`8%ztHJTgh9A8%pm&joMjvQ z^!kTSIiz>l@SQi)EcwZ!T61OWTiQQc27&U) zs7%1fO5&kaO&81+%4<-xOX>vd--Bd}S}H!`V1t zDvF!_JO!@z*&c20{t4P1*{iMBdE##TobmQ6HHb84*8GYhqxUoosjvE2MD7 z1(s%8p;2WWSe=ATM;z(m7$>L}2WBU^`wb1PW%~2vNxr_e(;M&_82p}s(b;&e`P-0{iN}awp^F*QfY9L z$hTC+Bw*B~syUL$gLa~#@JVrs>FB_sYN!aGb#Szq^9wz)z$GMH)&{_ku$(?0Aqh@Y z%lTmPM*Rs?=JE&3TzZG5%-|FU-p>S|9RF+Qlwt*J9r^;c4pP8I)2M(=TecQ1Shkic zbSt}nt#i+Eyv02jm5L%C0i}**2Su66BduY5PqlNs=^IK_uk1S-Hdq}&#`5{xk*IR69 zdZ_=;%?8_gP)9M4$AgvH-!qrkxGGD$9#fg;BlR8S%A$=a^1aF-ny8FHCm&_%Qd1s! zy6?f#;YUav01q-VAwrRCfua!rOoW3b>6?XdNfx@l@4)^H2t|65^+FundtEJLzU#T3Q7<4m!5 zWfKWl0-ojYQ9FLOY42|^7)2T6VM$WQKmgAj81CGG%PZ9` zkBgi3!3|%CN6I4xZc2Zhx#FDr`3-%B`m(SnB?{R4!_X9?5l1YcKCXr})o?f|eLVgq zv-q3d?SFpyX{&Tx)^sMeYufN7I)qp|>!4Nw&Ztanm}qYe=O zE;lUcgudw(Is&NgD}p7ekq)w~DfV=LL$b4;7S_di0%i^-5x}3I(H?m7tm?U*4HYAX zd|3E4V_tCGqAO-)3ORlS0e(6tHBJ}Ed^{XXED|MBH4SkOg+SC62%@Gm-~|WA_&_0C zzwo`9%>>`8F#V9Bhu?eM9b*WB_JRss(5@Qtnt|LQY$BMT0EU8U5Ib$u zkTxB-1!D7;NcB~ke$I@qWFI^J+*63pr8jhJ2$YP_?2CoE1XTD%bn;DwB2Z5FXPpF8 z!Q-J`I7C4vZiM&1h7iUOB6|pDbS%Dlwsm%oAfXzGy(Znd-EDuHqofhY^6hW$!g=X*~bb6#*$I7qqkWM zLOvo*c2vvjq?ZwCNTg~L(QpHRw!vr_q9vO0uT!R3Zpu^*U4dCe_T*KaA0Il0)LKr+ zgHaUFi_mFq*pW%@@gZajAS$(UtFPHGb>zuwMv*6f$LW8Ao))z`O2pmwt=93{q_g+N z>E4KM!QSwDgb9NF^dL>%?Q1VmY2S<7NDL81PQu#{Y%^9fOW)u|zfW$Qx&*CHBELID z>*Q7AxK0IrKwbF8D)4Q5rvn4>x`0BZvRReA=bfcj-Pk%3AK6e*M$01JrGWp!-n*hc zS>1_!_Z9&bu0Zx$)MFHbNzK%PtuYWx1e?%O^C6-vagh*Kse+)?Opd{Y;s|3>fnlZw zCEh{uydI|NDT`t)`;92NTbs9SDL0MmAK)2k3r+6yAVe z2eXWo%N>D&*_s?;KHWfI%5mtZQXZ+Dcn}{MYvV9+kJ|(F0?645(~wo8&f(XC zVO3^*3(IieKs4B*vm*RU4QK43RvoW`O1!`7j>z7hV(Hlqo?jtjni+g|j+v8TrV zRfK0NOe3>P)=!vm(u%Z!Q%~;KL4e@K+8z`xCG+^nvHd~FLEJFSN5wGALo_1LWoOqV zM{PUy+oF*{<5_idwr1X>s(q4s=i4?|yr7v+0Yg|rU;uvGW?A~yzzmkL*{&1-_1U|{hu!PW4EP_K(uuRGYfxjv_QJN!q5hyvQC2)@tlrXXZT;N~8%*{A= zH_uQY)+$ZovfBk0U+J!$l^-8z%JZd30;7JFdZWc}+NdswKu*x-Tbi}@|uVtx=3<-oi1G$BI? zJ|LH@;KYsO7{O`aK5&#rrNyW%`N!pjJ^-tz;jVdia{Z5kQo$LYJ$&(lQzN?)c8+AZ z=_W;knC!_DE!MLvlXN)la6l=n%@d0Oo+%;hX%;D_S+p|Eq6Kjt6K#QNx5#(K6QDX? zk>Fv{yYDdRwaIN8MhrEQ#HgHUeD*qN!upXl7pWW*okx_6;(`u|33m$lDugV>MNpy} z>qj7A2?-r(hv_vjRNEUuxgP|w$9q}Ug-L6*QyYUVbSKCWp$JLvKK7i;-h94tpa}`{lpyI3UlXvAy*cp@ANU6YG*sj<3Ga|y z9lYYk(TglbkT?sd`%`Qem=ZySgs`xzrl+F=6bE!L!BFS|*JGc1?j64QFljqxr*qug z$ye7KC2$OA;0vUxkvbZniS{QFh{l)&L8CWa*=_lZj0GN1)f*7>SXGC#CQ}w0fU&`5Ukm9?fZYqYae)~k z03Qgb$oSOd5(H|3SU8l}7MxlrtKxekpxi7Zrnf2w;T#yUabe-*VIMqf{z6W?$&o~wQas2UjY zory@Vo`0J5{CY6iDR@6|fzLHS*&JTt@7{iD;r$9f&6g+)K1>&#Am6L`X)n$1j~FEC z?eF;cZ>4LG`gTdyPnJBRPRdTY=ceY>OHM3#d?Rx0YB|H1ZdZN(*f`2QCn0qMtq{^J zDH=?=cq>eLznw*iCjEdmM3}i?8J3{U-W7ZaS$)vPJf}gS`pe#hEX>lXAF~c0TWHh znsN{|zSocHoBas%p*>-1GQ~uK=11;c8h_%lt?SEdlnXJWbBS*MT{tDQW>0k2U+Z2G zsXRC01uRDFJ6zY<$$3e^-;rdn)7=lR>zpS~8eP`?epm!B@kqM7^WAB1d;|+NA8`%3 zfj->e?kVy4@G?5@f+}quf-I*Q-1n3-wGV8{4(PaJb;mC%;{INbXBUDPdwErfgRE{e zCf{58USY@~iAoaKJn>X^m|vcmOYVo9Z{?YVkC$~1{!ZOMupf)w+r!(J_I&iqhym8R z-*+%tyLIh-neMp#+45%HsrWj_ROvFTfAV5J3f(9!d^Jti-Pf)A^#A=IF_wiX5+MH% zKJJREN*fs8!tG;ikqJUJ=)mOpuIef+axeMNbzNBGxQRtxJNbV7-16GTod);WXK?(Z zv(fpjpQANG6GoZANL72mewx6m{?l7DAy)^ z#^CqA!s`$4`9}}Y^ZQYUL8}}7=e|$9Q{&b>PyKzrYS6vhMYe}(ISrGVzn!Z5Q=i*l z`{CykH}88aHr^U-mwVo62HX0x7OS{ANIyT4{<4+ne|pis=Yt0+PIkOJuy(JXhX(st zpDA(N){g^wdMnDBANBpL;!l2`7Z%wj%Ri?6cdm4>$6Xb_@28ADgYcqf4>fP5jBLu-#AWVzMUwhp9w=bB7Z9>xBkuz2CUB zs_>e8PW%gEpFe=t>ngQ=ir!x#>yf(oYg(E9MT2S%8;05qJfi){jogn5xYr)X@8OPj zCv93aaqL;dAE?OVIh~w6^|)F4XN6~UGaE_wRD1cPN|98XDZnB{xs)5tMFv6+7-2A~ zP(z|l5_llGMstuY4e0aOQ&KNSGYdP4iKia^U2t zTi{WG49B8r^Ua(R?t7FJ8Q@+~I|Ibw)ZLJ5Ne$AqK__YlW6uf~O-YI-*O}C=` zHTZ-K-d=MR%605k3bohotzlc{9FJvfwrv^4|*&-B%|>UNb*zL!^`rAPniawD{_zO_vjYHA?v zs1Ov;Q5XtXTtdjDlpVQ%+A^Tt!`!@o+hf}?>yJv-IM%I4Yd+*L&MAUnh7JcsC z+L_*30(#S<-E;zo%I8!yB(`yj639EH1y?WuilxzT#d@0)A?D4UKe+Jvqi(Aj^ItMC zj1kJ_>~UkM{g3;YAg+`8M(@O_e$dEp#)&pV`~pBt;Y<&JGI)S|RCB1>31CXFY1zHC zkn1JlSJZ6Pa*72wx{utDnYj~n+}4{5e!8fHlRlSwC{J#aQU@(AxamS5r{2jA`N+3Sw@$5s0u zO~zF{1_|Khj&OOKDF;%(6&DLK79j%T>OfIK6(&daoT|uI58OPPBvkZmJAbA!A`Bs% z2OeJ&pkYk|D6$1IWP<_gwTTBjao5Dv!$xi9?wfCkIG3HOO(v9=QvaF(~*>%_J5D7IvOFXjOqV0rUAh#4n72xfqc{ zQLGRXTWVbxjtf%L`A9vM;xI7OF7?FSbu8p$n+I5BX){vj@$r%nPx&NJVV~+{pyD{w z7R6RM$1o9K!3WG-#0L?ddUwEVffg?;y$evBKR|Q8dXB$mAXOy?XB(D1 zxC1)Y|d52EK9?S)X687M2=;Z42ohQ9?-;&X_d9lhugqZhVw^uvU?EPfT zm|A>8>nGk3KiIrT*KbOIlK>+t~d^ zr)((ZRWT;MW*U_9uw6YJ9Z>-lCbjg$>ZL?aQr1_x`T((>BK!+$+mwZbL9tga^|o8?oGA|8e-vEnc434KsE(v76Y@VIt0YJAysU;`P|wwxgawfiC~n%^8~}B7{%O^dYon)Bj{l2}EET4v z?6NtXAIx9=aPcHTB;U~01nMFM5(JU{B5INTlLL`rsewo%D()VF+(4zNIs4{{WYq(= zWb&Pdaq@6+vL@Pl-0h2DD@z$4}(Q|mq)M}zjyP({5o6m@{+1Sb` zTXQ1O-IL69Yj{);bY;Jfb#bSidDU~nGSO`|U-_p$yXnOA@Bf~Cib2FPKk!(9cEN^2 zQ(*4xuRyeL(}WL(qIp>sJL7&KmPjF`;*~3}-C;szJPEc`2;zl0bP`)8PQmJ>C#-sdfq78INbd z{K3HwOa>I(^+|#(NNE9If*>;!M^3p(zUge!U=zVcS(ZvuAiH5h?>?`byT4)320kd3 z?+{Xu_K>o63Mu%0YZFmWbeQK~Or3dS!50 zPaqdE(CCnPm3&#`NO?rLVb?j?Ur$d@U3TN4MqSS8N~I(**pq?;>@sZ=<(5R9uQtld zGOS=tiNUC#DD|LFYx|KtFG#48UwP5h|JAS?wQ0(`SEKEFR*x}w@<(P5yvwxY%o9#2 zIl9XBB$Ej#HB()zZ@?--o7D$-y?hdqEs%*w{UMMA2K@+bz=(3mS^6WheQhT;uc;$` zOK~>j&B2z!s@YNyQ%Vt*l)zTM?0Vjs9mj1o1Y?-mKBzJsot?hl(!SSC3cN+n@QX<{ z6#5IO?Wkhc(2{E?gl~|QsacG7yJR*crT8Ew!9ZjktU}fSr?iURW40c@%ery9|9={| znx@0BsWG(b?W41w9Y5~$x5w!5SS6&9RRc9C>r|4-PKDblmSlx!EiVhC-_^#Fxn`)Q zkAT^zf?QV-#D_%$Vn$wcZW#C)WE$S@bgUqV*Behm>$9Vtb4McN$gKEDt~w~9C-%ER$+*8TZH^Fh$+8d&MtgM?&Spe(jX)COVxdNiCz6_6xaaH1 zQzC<4{TH&n+p1>$@$<5>Gm;>$MBIWrc5TviZ7NNze!l=7?ND)eh}j?e_FeMEgKz7C zfMsxaz&F4y6jPxtd@G3PNsOnYgq)C4TomwN6(cg#gSEiOg+(oLtcDhj2#qjIG@sTm z$MXcTi4Jgz+E|joX?8aVPzNZk<+E((%9%fn{;LKiD@~E?1@AXEhQv8W zhmaJDBh)2?y4`Y$?Fq^uNlap~9i*Uv{)B#>KPNI+BVyCO0L6QXifp?f$s9O>C$xKIQ9Mj=t#L*;^Ac z5N3J5?4e zV0JAVpB>MxXgu_j0~S;p43M-VH<#k~!g?83jR$6e5q@rfGiD+i-t+qN7P1GK@}X}r z5e#XRjdO$PT0ObtW8eVj6ueO;x_rI=&92cqj4PjHEty>_ZjAkHwKuP(L)AW0EDwmv zsA|yg5GAVeY&^cE6q+1uAa!f*NJtBLDXdA_o;>@J^}P}|WU^4nY@Cx8FZU3n)zrghnj3h{T zhk6yoMX0I`GBX&LhkRu8aSHp$>(4Xu1G%0`Q*U{Y>*ww!%ROo%= z^r0ifuxyGb05Nwk=bFPjCx@Cg1rnmRoL+PU8Ui+zS~LSizXnPse{A;oXI8m?AHBD+ zbFO->YA{RblkG{_OHWU&`uHW@Aja(^n&lpt_RrlaD11W}4#q5iM?>lfIm)hlkIa+k z`(QAGa0co#+xjscMkFD{Ka!@#1~@6X-Z8QHGa&)sI9AKJph%%I> zW?=CvYvY!F`n~6#^R6DV#}FC&zemveam@AhA-=~~=b`oVRSod*RA098wkx8uGxi3uMxfSPO*o2UiQNsOpmPF zflxvWX2{!&$RC<3Sqr8xyrPaEe;N&tKX^2DaRtWVW@Q|1=GYd%=1yu|8&0d@9p)uNN00T|LLCjBF z3@DjnJt>I4M&b8}UsH@mbGLc!oVEJ55mhAlS%268@LE3K1z{yTC1lqM-H5;StbMwl zd#3)ok7iUL%)#>PBPaQXPqFQ1Xc<}HNC8|e5O;(=VS~Vj1*ni&x`DJjd(Vp7UOMKr zYYoO@4dpb4Vjm>Srm#+Pv`sk-aCHayPu0EjIJA*dwAHJKlXq{&z6 zRptG_q8CtF@OSOH*eJL>+x+|?i=X}S3f&l3s>uLQ1;F@s`R3LoB)Rk|=_M3m6LwpU7pS;ZNcEDJD|6&He#*8Dr-1Y@cn>q@Al?G{UL$ zGiEA-qADJrhb2ZHeL&(v-5|-=;8DM&0%Mo6e@ha@yTJ$s|6<*sz_1YP&Z&ak)kE0b zuYKYOea(C3d$hiE7wz^iq}ym}tkCW!Yg5UOcSU%&vjdBghPbuXq!9P|lpaDmQPqXN zC!P{x0>&?CY;moKA$;0`l!Cz(b?#dwt7PR4A9h3izefkgY%bVgrRn1A4bLt+=;29% z?oRtv@NTbj)p*>P&_XNYt(Hfq1L}`>$Z4JmmJ&w2)((b3G2Y1vP(j1vhPn&N7o6ZPrQ#IbPq~&)rRkFF zRbMy1_C}T8XilK5c?o7bn$T1z5l;(J3bZoN2?GgG5wDbI;^04Nf~8I#mt?aOGxs$_ z&oKZf^f$7^$^q!c;DABvBZM(9O2m`^Aw${aE=i1cb9_9^g`xuB8<_U>;cY8-Sy?&s zfQU=8Vf%Y8?c|lZ&y#F*V$w*+DzneK6YY7KCI# z;?sg8^U*mH1Xy4*6(0fh-cZufqB;vl%t~2+fgHe~!9XVmez{)alUQ{A>IKL|tg9z1@#d4E&D4C~Tu9K-oxfM8w=>*fHo z9SrJ>n+FzdTNK!=hB16#L9kw+QN*@zGtRo0Q`5DG*gqp|V2@Rl`u;A<-pNp>)vi3P z+}WQ%@StBwwpz_TNl-$s{fhw5)%|`6fcEn&^`f_PdG^HN!%k`mA8usHxjn={x1k9L zp!L}#A3_s}qB5$Lj0xH-ORJeo^Vos=!7H=(z4uC0Wz8&uVS+X*N+nU~`KCoVAwiQC z{2f9uKfgxYaOVBXPXgpxq+3fbpy_?>3rNt}jo0-rj4ShM|CZgmE(-}WsGSIppigtV0o?uc+ zbV_`T$ybIizc$FBlvcph4gfdU@R^G{o$xrliONPF)+^h zdqtGfCCfcA7#+&J7|H_4iF{&G0FAW($q;-RdIdIhmqKGL_4*@E-Lx$_Y0i`eOWf;B)!5@ zmqvCoIfA%5pdNergw8L%U0$`h>y(Y;X>QG)KEM5}kL9xlFjhjX`C1;tqybx2uFJ;; z!oUg5Td>B%WwGJ0{F(W-?48nQw&R5}^5bQNAwmcbG)DsF8D$0hGsqSMe+@lKXwA7_ zS?>6B)R5Wk#ynTIXH%~&;C|bFQ|YLoer!Qa2A+%yY>IMlWXj~aco5W*JatOVG{$s; z`g2^Rq|em@+nM5MbD>|UrO21;_~Tr}h_O!Q>=XkaG-5oR=oTP$v6^$md!a@!YcGR9 z?yjtT;qsk=Gp8ECoHel_3WyrJQi+%t0-P31dinjCuh-3VSZT9<%VqAAK z{ckV1VdK&4<)^Ry;n>BC3WBC+&Mn9(DbdUEJ&PdeA5+b7VYH9nFsuE1SX>0WMOY`0 z7ku$Gg>bM;!_6T!Ma_D7u@<+3WUh>Jywa@NbVGz?VVgou!DHO?uwKrft znj%F{u=i>c%S|vYpgO=O3q9cko9K~3g7S^LT(J{#B3qRoO~gCIP7v?|Lx$WdQ)V{C zODS(pO2EwuE^G}~z$|U(-z;rVvNP~6wh?NFR|8Zoir%Jc>W{B36sbRP*)>|=6I0Re zeq$5h@$BoP@3Gy~aYBBqKF@5WJ$_bn z#E&@B93H>Qut5edd=dZ)4b9c^c`u&GMt^>D`R*R7LYrSNp(smC3lIa0$kI_&rC;a( z?J*4EWx8r6jFaU+NfV$o)zvFCyW3gTA2V!l((0=I*n22w~n8rYExp zo#`_le*QEgkiH$U2`iKeI4}>!Fc82kb*QN{J(a!hvg9tYQLh;QjcHo}NE84o0NC>& z6b|y~n;aZ~FoNgGSZGXRQ3q54BtGVfhwDK+7NK1+pMf!2i*<2ipz#56T3>+kvvpkT z#Hqs~dkz39`c!teE2nI%e0v}aF_7KL3UTxRD*+X6ZIJx|;8$P+1h52{t1BGN?4Jw^ z10N$f*kgXOI+9COSN|6nVxGxv@Fw4y`qBj@`?F+V@e7?U;JEuFF#!fi9F14%JZ-u9 z3xh++tc=j;LsjefT=uL>;@fZg$1kO4YgD2JzE)J}2~w{CK~|VG*%WQ#xSn`t4`5pi zb0DP4OC)GfELedpGN)Ha@nJY2Hk^1`<~rDn6~|k`V&TYrsuQf1D;yD;_FESAg3E7kyl7j zsSu>;Zx%XIj4u|6oN;3Nzt{cbRan@bJP9?a3+_hSE4D$~Gw}L9T2gEnB2~xLDk`<_ zRe!wYxeCqaKh=`!pSE&N^G}T6LL8-4J}@X ziioSrdMh4yBQkNl4|G*LKt)#98}Ytf*K1i974`pru4HDCX_HcP-}faSaXR<)%=7$? z@9+Bsqh5$l0LxmzegS|-fWO1nmP`p8*apXH@);m4F<$A<G>Gxio#BXt1`P;-q+^l$KixLIS8e3gbxU4NfBnVAxw_v?ByLHiC8RlGX-TEx zjRHp9U_h9OCABpuRc8k38dx?U%|#?G3cfJ1PZ{6YTk9I`T|4>AU=dQ z_+%8_aiETI!1CeRPWBTeDQ*2&^w-Px4pvRcaxyJ+?YFJ_Zu0HbZ>B5tT>E@M6C@1r zNSy#znnR)>JuJ|lxZvM@7`p}7pWb#S+twYkZr`2uZGYCwZrdO#5M_Ab>p*o@g+x9I z3?bSYw;R_YFh@t*IJ!^(4iQZQ3oTbRSy!lF37|oL+IsoHeUaquXLx(v)xK!etIzLJ zUJ2z(NR8lb@O|E1_sUU!pY>YcNId^yVZ{ew!h)5;l$8VhM^lndC1Gz8tYb{<+&6b@ zoc_*;VdMI!o4pZpHx574_`6sHZ?9WTjjb?EGL{`6MPYQ9l~?=svK@BB}!ZDoN zPzO`98Yk2$39uFhw~9er5*ApduvVHfv3@pz+7H+!pO_EU0jWZX zag;GJeFfz}!vaUR8lBZ#k`wrd;$U>?5!MgZy)(ReW9zXe z75bHA1VTw#LQ4RqP07Y|mA{JCc2CYGV0SjAMP(%h1V&edMadtQ+L0w%F2MhTb+5&j zgl16B>EL&J8YL-!{1>EL1&*dhXZI0e_r!AVMw}dU0Vl_xQD}NE9>h3fAwEsrMn7G5 z#s!04`XNMWyeACQJR>_u4|+jj3{y8)gbt)grC<;%%E(?)c9F3aCKt08neQDHYZ*aw z7-l|%rthACrW*%!Zv&I<>X)M1-+DKiZ=#Ej?dNYOzC3ZJ8YVS*CvNQcdX7I`y*-cX z!47z1tZMm@+thsuxsK!hg66kqirG)W$4PMLz0LEeKggx{dh2l!`dO6DA3XIRr9R&ee>bYG^EqsfTwb-0YP+|eAFG<)f-QmwzMda&RFWtP zX{Y2oWJ+$E-2dGNlx8!=Mi>7Y$!{B$xgCR>OB`?i(GK8ohDIZaweQ_&MB&f!=&MTg_ep-!A5y=cQP}s5;QL?mPWShB6d614JZTr%gVpVxOi40;w7-W@>3pvL zC>*UmTetjl!`6+h%lg!!LT^4(;CaS*(S{DiuGy(q8+QhE_}4?s7c5_t)QoK#F5<(E=^v2M)8i{G1j6Tv-~ z?pDxYA>QQ!CVmE)t;GXraS`KBEL0B130=o9B7+9(8Gc?AxhMdTgjj^*vwONCaZaSC zzJK=MyYH|K?knT|i*=xi>#?DodSM6(o7Y$p1e-Q6CPC%vloYEVh|5&0gbA*(=Ja_* zNxc>-()5-u%4qn2VflS+E5@9ui3gNkse*OYi{VWz zX-)v~VN6%oNqAjwLfek0bc#Vmr;fdD40YsT#=Z(L{QfD2?dw)vbJkI|W)cbeM(cN3 z8ODv9ePu^{q1hJXV2I0|jFNbeJ05QWN-sgwv#DDAmK*Q<>WDMf58L1ffMzf|6qOG0Kb>vr9py0j<&mmq-h#RY zCNbL90{)~MA^%=pDi2hXgGGu$^%#Wz6)Q-mbRx|OERSbqfe(y@@ZeD9Wue#}&0H@ZmTn#c&de$$jsbrTl zJiEg%U~HdiKPd;59qZOiIg2}PO6gI%;=_a|m5lP>Hc6Kzs5$U#5h$1l--ic2`@+nj z&VfMF2LYJkI=1>9^N;H$o%+YWj5>8eVXC4Vf>EnlS%Crtnivy=l#5-F6po9*U_Gu& z3lM}fx6(UG~TKD2fXG;S|>xwZ_FyW9FGgGG=baVqjYQYhXMsrol z_J&krn(PUmF)aCUaY>M%1zNjvl9DJ`L@7$Uc###u369P9La5FzLtA0#572Hq^E>MU zRX?9UXjjSQDhxZtU za#DMqoD|#+`2Io8)eJt#HcrAoKDM1tN-XJeQX(jvvXHA_VSC7<`oIB#jE%i;(|&6! zj(Kp<1f9FTX^eV)5hfn0)7X3?_EN7ue298p`|D}F-jD2e_>j`N`Hp$d7}s zhAT@Slzq)jA}46MOx45$Ey%xy%wFzjG{{9D*a46fMsVp0+nNM+73;vqGgIzp6#nIF z^?`T;CxWCyFh+J{ui#@GEPM?| zhhiZdeL6tB|8@A$HQ?zJ1Jvsatl$&$!Cw!q=;gpzAzymbvPbS{JwngJw#LL%(vCc9ClrwoyO% z$ZH;$Xk3%l>xCk5TR5GDluafUBY#*|b+gx94M)`i%#S69RJTSsF+$hEMvOGQL`Ysp zV>=0wI(gHG?`7Khu~&u2S~lL zVbK?;BeFx3J$KxkNu$^WRpo$;Y=z;c^sF5_>qnXjW8lTB zD=C-ANlhY$m1t0%Cf%YPZKl$CHt#5T$wxPyd&;QO3iAhwil-cZHX%1hR{?PsjO0wE z5l2OjRaL-(Vpz3Y9x|A`EMgO`W2)G{4Zh>l-T)x`8TOL@`1;Cu^RLY486coWO_Dev z=?6UnW%VS|Z~$asPk|t`R7ABz$#-jF9lXdc<^%|lboxArG z{~7ZO!2A+R=6+-GsowqUCAY3P?Y2*b#q?T($?riRc~C9|YVH!e1aog7YvD!QAsG%L zGnE`=6svk?fP2DgbNRhC1c!kzS`d;bUE_N%x$=}J{7r*j*VruOD06v*RJtjdorUTT$raR8ziDbIbAQ`+KPuPJ6b~XsGTYhbATNraprf{K@AEIxZGi8Z+T_i zOBUC5!l<{re@}0*l9xbH(##;FCnvLLPDf(RqS}bECqVKA6`i6N87n4A_HoU#j~sUI zut68%OJly7(0v!Xudh$L?4MW7htr!cM^J=)PC)L3?LTOY|Dh3Ckk`L<7@WR&`P^Qe z=fK&EdbQutiyPteJ|BW=tQv_riv#M=eE)4?lWL_ZK$5y!-g@2cvpFo&Eo8P@A?*lN-xA4~1h@j~!Wj z{X6(NCiX|$J^RCN3zF9R{r$P}wqO5E_hPaS{*_WT&J@<7>58w0H{cNh~-J9pB zrrO`he?aYFNgt`b=ex*R5$}W9jN`aj>O0 zl&+jZ=>o1Hu&?xlLVEQ1#>h+Nv~}He-(h&k63tD=#UfOxNGH$ht%gqVfqNf%^)a7a z*>Cvoh6NZa&mZ&Aqj$$PkTsfGTgt&Of%8i&eK^t=eGC3iZ=9vY^atrU ziYLx{HgvpFg1E1q@I=|!dO>A%qypc0dyhAVr6d5Q)Jz{rx%ol9jZd~I!%~`9b76MQ z>~0Yu7cbd6KuQY{pQL)B=5d)-r*f0DIx6;{t%>kL4jl-Q4d$iVLB6Oq2rHcsw;ZGu zbFtQdz(wHr;>M}AJ$mtzO9~g+Q!oSzA@< zVttIV_bPB3!=r2r1Yw=|s#6@Z4b(ACSR&Kr@xL_br>h2&7J2tSk#FU5AdK;~@F9_piKk`==x{u&Druz{FD$)b9b}0BIowJmK8Xl?h1u+e@Gv%k4yN2d}aT0PQCNgBXoi#7D|WHC?KRVe3UCo==%?H#ch%Osu&W@Y%hq@ zx%Erf90vwBm-1Y zUTP8adBB`aB{O->9FK>cm`a@)Mp5}1eEsA_w(ovs^5aJ0)dS!A8 zZLrol)TIzsgQA@t55P}zv{1FQUzcIM!DHC-+w&{OO`a)F`{m!mqaz$e+~gwIbjhVJ)+Ka@sB=ft|n*=S1nyC-CU{N1*S7X2| zE)o#3bsrG2;ee2hjbXD`pFzb|z@YN>(erNZPezd$6}|gZDhv+!qch%2pRy@YAYRHy zl!ay6xp0~lA&@J_bFBV8N~*>KLj|rJCgPJ6!zK6zj~hNKxP?PiFo_DsDMBA^7dn#O zDh8sF=Qc2vU~@R+HYEiv9BD@i^MGL-uC!GGd<`nsgvKHaOj0&{5gNZDwPr;TENvGwJiq#cs;%qhU0NI1y$AYrP>zr>2wz=ptCOK(=3~3rSUYSIp^y)BsJYLHXx`!F0CvNR3@91#hsA|J05KNiVKHL}K9!b6#tW(#mwx%h zTfHj`cgokT{?E&6kI8oLty2!A%3~2>5(liS$|1DFD)Gl%k|qH|2i80R4;hBJ6yCmr zEh59!vPJLvJxUIS>nu-fIkj-GE0Dl&;QF`94uF#Na;+gB{3>#wD8f`;Fo?PBY+VH5 zh*UxW78VA>;SY>-y^_B;N?x~r<;>fkJW^-8cKNwPO6c-(txjZQ`!SZ9@^S*7;b0L; z0nJwJ8%MyWcl+x{7>hTRqveY5o97?iWH0<$^y>tK0E5UbN26MXa{PM%~c-1+w1iNYDypAXUPph^XBmeZBU{dtX%`QsJgj9 zC%tAzYt1GkB@Fy~VAOM>h_c=2p@GFPy&XW>bY}EQ-4PH6p4rREAO2B)~dU* zw-o>+0AQSjKz_cpM|3Pn+0=)VTqQ3hV1Jqi;VMD13fTcW8k=EBk;rDkag0Evp$Zi0 ze1V!A=q6GLHVj{oN=@#Ufj195`9c4n-wT}Kl~*qOblt{zTQ$8!-=$Txlsaj&Kdj@a z1@#6>T@^ONbBl_JtB|%|=us2pEgA?9*jUOASO)oI8^cLpjn4~lJ64GF@TPksCq&Da zTdy`9T|Y_3sJAx*q7Cz1z`J?dLr`{I~7y1!J zVDCh%j}SsqtRvMDi-d&;WecU!-qfOGKoU?`GDlQE*;qfz31N|A!xE&92`~;e_4GAg z^o_u)Pae3c%KUu8apjD4nBvA&SqU;2{&2eQ#=KfNevX44bp@qksj3N)DHo)b;FE1Q z4ZG|7$F=g(*yY^yjepfiWcH|po+tdwi7@s`{@j)$(kv|Ig2tFmDVO!PE#<0{qvu5) zUh>R!g+@x9Cge$(Dl5r#g1SwVI)WThJjn6phm%EY3+OE2b3?1hV@jr?9E++B#6l8N zQjwZL3}@P%TQTMjpBy z=c=LMWulV+{ZBBDihLIKMc}rGX16iK4&Ce6X}#$SDhzYvtnW04+SRr}o= zfi>`QWbZ+Z09Ka2JPOk+(y?$lIY`-x$T}UY zgHb2O&TjbX_B%#SK0Rmgjxm;=%vWwYNB->2=ic|6_mifbmy|;Ed(e!R6C+@OTTD~} zxKc8zN@M8JBRat@alS2CDTI@ecG$4AEQ!5&!@#X~f#%#P31Q?WG=vj`2&^F9tZvKY zbWnQw)$$3kOh$?LvvnqYC0BcQI>XLt}51rj}YZ(SlrE) zORH!!%#$mI9ysNyzZ|32nOgi@!jtZ5OiRH0qwLV~QTFhKDEHD6@h8b^7C!g$kl~l> zb)+Tm{JVk(%&m+P!QwXx7Ehu?H0WF*S`HW{BsRpD*&d)BAnaAXF2G6hw8OWpd4O~1 zy?LN^j#SR!00iQ5$RP149CGks2kIEA7caF%zNkieF#PtELeLS$Qkv#C?bw$9aX!@G zgIpV(8PB~q>$!>b#@&thG#WTLJ>jN1rU$nZ*T<+${xqh!q(6|W2X{ew1cpyUQKNI@ zd_v%55S)R68BS3~h?I2|0b&~)>vbNWP==F`rrMyDNa*9RoNp?Tb~Or7PJo>l`d*0O z0FeA<>e;b_=l<~XpWYchWlwIf*BPaLs*F;reCeD0E1rBElhx!{c=S_?-DHp`D+A|o7tjD|M3@{Q5c z??19urxVJn3$YHr58j>&mlk8Qy^w2!GKpY3$8tjD2RmYN`wwHjbG))h2Y02KvR|%lck-C&%%jb(H-9wYo#DF=v6qymom>Cnb6ZH;+Dp@eJFA-; zYErx?Ifbx4P~+=ZgnB}eMnF$G05U+p#(_4P1y-2{_%sJff{N7e!GTX*a<8EhfSMugUmt(K;Zyu&Gd4|A z#TAvEvUYxvykyep|Gb!++#L|pjyUNk6PJwo!|60Ih%<*Q*dU#(5GE=A5igd1nRm0J zj(J$uKJPB9l!1{YCrUkHWv#x^n$uS)wXi4!sSqly>`thZ{2shP&;CA5o}3h1cMVvk z7kw^fep8nE@ZzoV9<5&@_aE9n`R1K^tZv=h3KWy93iuKUA8;7z zV`jSdcl7J8zc!w~HZY)cL4~vA;HFE$!%QdXc>WF%DO=!XX11hpK@|&9&S8lSN5g4= z84A|LXggX~e5_FnETy{8XUS`vn|^)Yw^_$}x7fTL={O&OkfB`ULG&eI|5M3Hi#QI4 zCX@kZhGGlD6%Z%`y~H9J1Ijv^3oj(0gXS8vjBM>mC0qG6kQV}ep4cT)CMFRUB!7p* zb^0(I3Q>jUQB@?(HF(=SjwgctB(ft^#JqyBj}AcIII;?6Mq0um z$JDTmAr=&MocYk}Q9x$9s~ykTa`WG|eR*Kzy**&3oK28HnFP$>u#~9<$R;l*N*8G+km;C)-f_OQT3+13uJL*M{<6!uS;r#a8N z&Nv{gML=`lP*_}8Eu=cq0AZH_FtSXbAgnfLgx)>RH9C$vjPDVA<7o~xc~3A0yyc*4 z&yl@D4{pA?YEk!3RAUNhdD~#23!za1y$0v!B!Q2J&78ou@}(7&m6wcKCVwN{chsc^ zU#Vk5mysUbJz|5-NGjb`C8Pj%2vZ5H)M>jyMr$;jFJJt{DamMkbzyiD-?5U;kg~OL z672qQaUT{aGOLz>_eM8|=y*%)-=Q}Sezx)ap}V6*S6$n@LT-NfIcnekynvVVEEA3> zc4q>Ug3oYB#5X4qgUpKRh9($A@pFk5P^Jv%xCq5&HzPB!G$ebZT}UNUlt}{VC}``# zqF)Q!Y^Ka&mnd-wHiE(S7SLZ@YV7Jp6vYf+&S#!ApRF*K*~u9Vf0DnGn~uC`^;Et0 z=5z~f5V;M@QyBPt1vv?jxE8*rtwRwJ#8*R)&}qIab% zb952GI}H?~fFR!PX-03iIV|w7s-ZqcAp@~M2*=6Ny$ce-P63b(WG+7ZzVGfBJ8ajT z)<4U$#C}&__SmU~VILe_bYQQQrR%Fu$T}~ZX12ogIdH#ti9zEmdJJS_MRRBmoGe3| z!GJvSX)WlmxNXxLM{GRhn4O!4^zWm=)o8d_KJfmnzuxp^zmm0)bkx{Dmk(Z6Zk)Bo zQ{iZkYXh$c*fo$v4Dhi8b7}+VP{aet`|J)9wN+KEneEEbeUxI#!N-b}_G1tke6pCF z9`tySeJ6Equ>G-{GuGd zHSlu@khlR!s6?0!2I|^}54m~3zzKoDyXy)o3|Go0_q*)G>Ha4RQH6D-F)&zR@4#?U zVYa!i$QN?(!k8YoFs6VOQo9*ic%}T&^EFM0YfsRNb=mz~!UwowlAv{y$|AFJDL}Wu zB%&xmbPwqFks;*|ceQYE8C^c&;VL^&_5!q@iT54+SDO$&%@m3M9d9!fSErMM=f1d0M!Jf z+h9!|j2RT;YVNs>tddVYf#2)Ce|=eY_+RNxs%Ef3rh150Ad^@GN{NCjMfRqg8D8Y2 z+$RDHb71eDFUE<{loJ8rs({vB94n^9oT#YJ%oaTe#H(O_>&6f$7^!%8QLd7uBss4<#Q%R^Td7xSR2#}%Qo$3EV6q+&1J~DQDnNWM{6vM>&lRU$AU0Lx)d0&6~n>oSO9OA3CjO-b;KYY~HkI>L&UxpHV}T7i8546KD^?x+@}IV=$3 zF%*!22|l`1?LMcXtOE-ERqK^?RO^>@RO^>@RCiO>k<;$8w!&Bm<7QpX2c8BTyuY1_ z+HLZUw{`tuI_#Iy>ps+DJ$C3ZUyPR*!-*EYtpoNrYLA107$QtY6R6Xo98JKE6?uun zhk{!}4h*vm;$(`gN3Iwd{c*)OEbnyX_5qLBRm~wN4~lnECY;CL` z(N`YFh~FvQU(FhM^!r_cXX!zjcL@s8yguxLcpxJH)6oLkFt9{Rz1e;SS>Ctqo+Ey~ zuS=7SE7Ps=x~scKyi$~66UqeLoys5!Q3hz#!a#$sUw!4^YZ?~}+1>d&S^gjtZ3@vV zdYgDvcXoVlYEpTeZRPP8<^)CJDA&rk*=ZlH`HG(>_8Q*1lAS1wZ}(wQZ%OuMpfvv1_y|MmFf z`(!JB(besrbrkk+1za}foE#rO6o8SK(qlFb)*+Iop1F-Fu-aE)6G9dAhS^TaiN`S?cZ;XxnskTpyMe$6D+1sUc}-k%(F7J#!(!QRz?24rhG#!l z7n>*MWaD4>0~&yCkKp!pZhxH@^oTre_3bqaUfEv{c&Ncpg0!d>R=9jL>Xcsn2F(wT z>X~Ve$<~fvANYRiAG&kQ9SBfX>2ouI8gpfplPTRle@vc{{^;X5mB;A7>nTevrw>d7 z7dFG%pxl%$0MZ^`x{CN9PMM8I@4fX@a^Q>x+mlf`#*iw zb%UA;v9<(Q!E}f~!^I<{L=lu%gPy_0i(amk2Sn+qq&osu;mrzhx3If{++R~32~Dq> zkRCQl6VSPrLO1m3Ks3E&0lqwE@##88@%b9fV<7Wn{W^zUYuF%fUL4uheonM3(?h`? zPdWXZ(3Z;}iKSQ_WmXn%kSjM@&s%x#EhXoHuz+l9Y9?euL;ojW#GVi+89LWcRr^t{ z4EU=H9=x7Ua>3Lhk=#g>v-VUq2%|m;Pr}`q#VlzMq~f>^w!0I(kxv^0ywk z*&U4rxd>Fm(M#HvzzpNcp&nfjyG5%l@haB-(frL1e17wga+RZ}+x6V8jq(^{&EC!_ z>w1;HhV%ji9#np!+JY0+xd3k|iE6U(?oU*2lt+g4p1ofOuhX%U+Si!2%ryr2G<>yg z%E@99McBb1kUOY2L8PLnptOdMM!6(hWQ_v+1pw+m9tQ3Ae0PQ3h0db>aq~^5Z0WrV zsZ5u^e%DE#(!poKaiFgPHIO;PPUN6HyTEgRW^jlHHVvf;K1x*omfGaazK*&N~%sMGhreSui23tH!T$qx%yZ)PdG z@<=r(r=gby(a(bDCqYWdfl!nrbg`J=*azg5YJ=$GVXQ1-t!6>US5bszXY>2F;0u78 zM1ZlQvtf@F)cT#ut8(k}FAn->!ipY%XT?dZi^9$*7r>I@D&U@ptP5zWd#OnEsyu(> zjB8$SRyi>w$jM*p0ZW) z$*|DXn2xXsuppOijCXKs3!=U=u@-kS>Vu5zc>P>XNTD5&`RMX;t(+imQM4mklV?Ii z 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

bi zw6sD+PK=PGynyj~+w`;zX!Ez^rkN<&v@Qt)gh4*ZMZ$%K(>1?jbB0y>RQ?c__UZeH zgtbyQy%vIl={zoaiP3gcIbtRJdmJ>-%A8Dqr(}>X5+}71uQkpgH*-;JbVW(6-r7if zI->V&sU;^;jbf6{!&-EoapdyTs_xS{O1^QTn~|#}AMAJUJykh2sN!c4{&f8`no2I$ z;(V1~tnQ8W>v}DyD=(Mz!qjvfjcmNEwdY8;yc_+a1D|wSb^X@zohaG&xVbH5l~UTo zWmXCEoc1R?XQlLdD~HNhR%%s8Cr zwgV*?h>xM;@Z6HX4kwbVH=$z|i z>9(Kqz*POzYoVG38Xb=bgKTbjzYm3&N$oQDau7Z&<2z&=+awVQoqXPTb7Aj$ykN-B z=Xh*!CFWm5G!F<9q3{RL0gmE0z!_{AF)SHG!(3=LeG{+*H(pS*9-Lyk45OJ2kg9V} z$Sg;rRMu!Vbp8AvgSK_KO!f#YO$x1+vgOEFY zQ5Zr%Cx)7mf&=m|$}I}Nv~c2gx%_sclg)YGd~sZBmEZGsp>Vr+9sbT5sBFTzz?_KA z@hnIm|8)X(m4@|7!j(SF`kxu~>SUjPnsF>@9mnPScXCWKgWn&)WhCKdT`DX@XsB%r zR#}sr#SP?e?>RDP#<+AbRq>GUvNcOC9Y^g1veeLo*n;2%om$56du#iCZh%D0NANLf z)Y3_&z8s%v6Y|)vvVnR;RjFVsE!CrYukcCc8u4hwe^sN{u?iaW|4`+mrG!k=MQ|92 z;*GdW#e-J(jbjp)Do9UV(Z4=vlccnt)iP?wU*-CI?`CIV*(WsjkQ?vZ$n>*dv!8m=_Pc0 z{b}u?+GmLrgyxOSCd~INOrQP0kBKw(#YyWd+&N%! z*isEaXp6W>^_BYS{JMudoR}KIe&6o-6oJBdaSPy}iXi-heL%3Dwz*Vd1Wim*A~c*J ztn}IrA=hWf2e~eQmetA;?gu>xjF!=!?;n7Nc*i)64Im4vq^zSNgsK|BW2c=m!Y6>PUIhFo&W|+2R zA9|fWIhyJJ9FXVEU{&38;aqQOcD6-Gih6E<8SLLL4u2C^A5o5yH|7-DXtZi-3iCOZ zGTYMsGS#CsDlI8 z^V`-ou}9gHJ!3*$tqS~8e|ORu?V@uvH!CX^1a#rZ7|kDGI)N~~9&ZnQ5x<|Fb_g!r znO#!G} zLYmtR1z0WiOmMP*#k-S#z_hz3YS*vFeHKV1}@rYY$hP2*n>6CLORSZ}l zv#C;}7HTQ5JLcD5aH0()2tGb(yZ$!d_K_O2Krb@AosiKT8E0mu+ecd}I7&7*+Js}3 z0N5-l?p1NjIa*6Fg5G~{PgwotIHxWy*JJro6`A5Z-e1S=x!reZ_E|fHXb0zaRAJ#1 zome9_vxdbl0S*6yZNB9k!z#Gi&!rtH*5ND zMClX5oko57ubCEUPlB$x*iai}y+!pw_pa)h#A(IKmBQ!##bhc?&oQ>o20y3^7~sDE zdO(H0$)jBLK868dEg~NPM7WyMrrx?O^yVr|4`d!4aN^x_o|s@5mtp)SYw^awhuMQN zV+px~s?#%0oZROzHM&J?lj7a6<$5R+oiK9g7hj&Jy9V79D2324w`LG%k!%cfvVkTy zzoKmr zuFSmaTW{|B&P7JJJ#Ft}K|t%;gzSn&H8G-6QlT|aB9!EGw03+29Ih5cm4%R+gLf2a zt$-43OH0f8BgO?HTZPd*ir%P$P45lR`XP&Kwn^z=JFwDB%@ge7LJJ4=XQR z6+Hb3qcTF5$k@{Qebq=b8RfBZpP@CWyke0)s;P#k28LA^U$&edhyMzCK%LV_MZ)C4 ziyaM26RJ$BGmA&{KIOWllZuB@Qk`Jy-Kd@fQkck_8e*Vf0mnePY zvf`bLDv;Qq5DGbHvsD#li&QZJ+7#MKY?T@Ofa7N!RjYRX_5HrnuQ{&o_No+X@0K

HRn+(LsDT{lW`tj?fme)nMThE!{Sj20N`^Au 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ⅈw~Gi1-;V8hf-p_03h0#D7gN*h8bMfS;~ip9CYeG2COz1J2V`- zRS@PM0>&_?Ye+$UHb_V4WJwYQzSC+hJ_o*dnvn7)fo<%UDQg&)A%yuTFC(?td|i*~ z-gKm}=?I*QV-KHnwYrYOXsIvn{uQ02yfboBbk-eHb27n35P?iJN1mEphT;6Ep1OJH zH&dfeF7CIxbG5md$g`Evx5qvAo|iB%1FT(KEJ}NtDKid6*7HD?Q9ZpvQ{-f4V?!mf z{)wP4j6u?HBQ4Gwh%(bfO4Rh)ucIcPUShnAa==&zm}LUIBuk|d@NYDv5WPg1 z_WY#3t-mXR_pHcGB!UQz*uh2d#AF8*zkq5w*TV*R5y<|a`br%aZEa5SaWd5aEoNlB zVMXGTHw_$d_;D@$M*L2(RAr`1mBq^Wmppr6ok4@f!!U8XX>+3s3){PAx{zW8Dn$kj z9Y7l)z7ZB0!%4N{>LVWlK3-3gYFk-PBhLcmYI(@X+=p{=)52=C`^;P#0c3X`z{8Q9 zi6VXu;!X5g_qMFoJ$Q{^asojj%FH@ba$=XM4HqcKFxNY$EGCyepiI{gF@HGF`YurT z8y8J-@vxW>!mP+gIf*3wEKIbO^|yyb7Ficy3^?8M@X@FLKt@xi-K;#XW7c z@bT?e8sVJ`&9hj(kR?SkhzApLB!qYtNJe<30sc{%N(y|4pDRc_s$d0U2`l^MN;#E` z@}6nUF$Uz~J=2fb|Ujy9Q(ms4V#4;Pafyjp2`@xb%;Ix%46-d2K1uhHxAVJ=^) zDis$K(I{{!>-dx?fEs89S&0S5v&627&<{od=8gQsnH?B^Gc&kgTr zsZ}T|?4o{N&hkr`Nx&Gv8-jqBtJzi1j(HZe!i#!GtNNAFhB+ zVF|BObnAcXcoACn&cC0=`+dW;`%gL2=wVH`{MS?ebTwlH7Na5h#pBauO=)v7k%U$8 zskob$LZXnAQK^8+DJ(qCeZBJ9%0D`vT3u_DpRqT(W(aXtb4^VKRbc~yB#1E#!!f)X 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