From 5ae6705e38b78d39a0820aa961788349fcd7c710 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sat, 19 Sep 2026 21:49:21 -0500 Subject: [PATCH] Add static builtin function values (#353) --- docs/src/app/limitations/page.mdx | 2 +- .../generated/node-v24-backlog.json | 97 ++++++--- .../generated/node-v24-internal.json | 103 ++++++--- internal/compatibility/static-support.json | 6 +- .../src/frontend/lowering/lower-builtins.ts | 19 ++ .../src/frontend/lowering/lower-calls.ts | 196 ++++++++---------- .../src/frontend/lowering/lower-exprs.ts | 13 +- .../compiler/src/frontend/lowering/lowerer.ts | 86 ++++++-- .../src/frontend/lowering/surfaces.ts | 68 +++--- packages/compiler/src/frontend/type-mapper.ts | 32 +-- packages/compiler/src/ir/ir.test.ts | 16 +- packages/compiler/src/ir/ir.ts | 29 +-- packages/compiler/src/ir/serialize.ts | 2 +- packages/compiler/src/ir/validate.ts | 2 +- .../compiler/src/library/int-infer.test.ts | 8 +- .../src/library/library-cache.test.ts | 10 +- .../test/bytes-element-emission.test.ts | 8 +- packages/compiler/test/emit-c.test.ts | 6 +- packages/compiler/test/fixtures/fib-ir.ts | 2 +- packages/compiler/test/ir.test.ts | 4 +- .../compiler/test/llvm-lib-dispatch.test.ts | 2 +- .../test/string-accumulation-emission.test.ts | 2 +- .../test/ts7/baselines/order-parity.json | 12 ++ tests/corpus/2917-builtin-callable-widths.ts | 53 +++++ .../2918-builtin-callable-widths-cjs.ts | 17 ++ tests/diagnostics/component-fences.ts | 3 +- tests/diagnostics/function-forms.ts | 11 +- tests/diagnostics/stdlib.ts | 7 +- .../__snapshots__/component-fences.ts.txt | 125 ++++------- .../__snapshots__/function-forms.ts.txt | 24 +-- tests/harness/__snapshots__/stdlib.ts.txt | 64 +++--- 31 files changed, 604 insertions(+), 425 deletions(-) create mode 100644 tests/corpus/2917-builtin-callable-widths.ts create mode 100644 tests/corpus/2918-builtin-callable-widths-cjs.ts diff --git a/docs/src/app/limitations/page.mdx b/docs/src/app/limitations/page.mdx index 38ce098d..ac697b7e 100644 --- a/docs/src/app/limitations/page.mdx +++ b/docs/src/app/limitations/page.mdx @@ -12,7 +12,7 @@ These are rejected at compile time with an `SC` code, a code frame, and usually - Block- and function-scoped `using` and `await using` compile for statically represented classes with zero-parameter disposal methods and for the supported FileHandle, timer, immediate, child-process, and readline handles. Array `for (using ... of ...)` loops compile. Top-level and switch-clause declarations, disposal through an engine-held package value, and the `DisposableStack` constructors remain fenced; a caught `SuppressedError` exposes its Node-compatible `name` and `message`, while its `error` and `suppressed` payload properties remain outside the static catch surface. - Typed async generator functions and methods compile with lazy execution, `await`, direct `yield`, queued `.next()`/`.return()`/`.throw()` requests, and `for await`. `for await` also consumes Node `Readable` streams, Web `ReadableStream` values, and statically represented class iterators whose zero-parameter `next()` returns a promise of a `{ value, done? }` record; abrupt completion runs the iterator's cleanup, including literal `destroyOnReturn` and `preventCancel` options. Async `yield*` delegation, stored Web/Node iterator handles, and other structural async-iterator objects remain fenced. The `node:timers/promises` `setInterval(delay, value)` async iterator compiles, while its AbortSignal options remain fenced. - Generics monomorphize when the target resolves statically. The remaining edges include generic functions declared inside another function, generic class expressions, generic classes whose base depends on their own type parameters, and generic methods that would require dynamic dispatch. -- Generic function values must be pinned at use to a concrete signature and come from a never-reassigned binding; unpinned or rebound values remain fenced. Immutable aliases of overloaded program functions retain per-call overload resolution and function identity. Exact-arity `node:path` functions such as `extname`, `isAbsolute`, and `relative` are first-class static values and can be stored, returned, and passed to higher-order functions; optional and variadic builtins such as `basename`, `join`, and `resolve` remain call-only. Other immutable aliases of table-backed Node builtins can be called directly, while escaping them remains fenced; `util.promisify` has compile-time projections for `child_process.execFile` and utf8 `fs.readFile`. +- Generic function values must be pinned at use to a concrete signature and come from a never-reassigned binding; unpinned or rebound values remain fenced. Immutable aliases of overloaded program functions retain per-call overload resolution and function identity. Typed rest-parameter functions are first-class values: indirect calls pack their surplus arguments into the function's typed array slot. Lowered `node:path` functions, including optional `basename` and variadic `join`/`resolve`, are first-class static values across the bare, POSIX, and win32 modules; the exact zero-argument `node:os` functions and `querystring.escape`/`unescape` are first-class too. Other immutable aliases of table-backed Node builtins can be called directly, while escaping them remains fenced; `util.promisify` has compile-time projections for `child_process.execFile` and utf8 `fs.readFile`. - Spread arguments compile when their arity is static: non-empty fixed tuples flatten into fixed signatures with left-to-right, evaluate-once semantics, while arrays, Sets, and statically represented class iterables spread into typed rest parameters. Runtime-length spreads into fixed signatures remain on the checked-dynamic or island paths in JavaScript and otherwise receive a compile-time diagnostic. **Types and shapes** diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index 9b4a6bb2..2db329cb 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -65368,7 +65368,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.os.homedir", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -65510,7 +65511,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.os.networkInterfaces", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -65547,7 +65549,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.os.platform", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -65584,7 +65587,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.os.release", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -65654,7 +65658,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.os.tmpdir", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -65691,7 +65696,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.os.totalmem", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -65728,7 +65734,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.os.type", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -65800,7 +65807,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.os.userInfo", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -65872,7 +65880,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.os.EOL", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -65979,7 +65988,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66018,7 +66029,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66057,7 +66070,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66164,7 +66179,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66203,7 +66220,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66242,7 +66261,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66316,7 +66337,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66355,7 +66378,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66394,7 +66419,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66433,7 +66460,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66472,7 +66501,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66511,7 +66542,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -66550,7 +66583,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -73611,7 +73646,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.querystring.decode", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -73648,7 +73684,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.querystring.encode", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -73685,7 +73722,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.querystring.escape", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -73722,7 +73760,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.querystring.parse", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -73759,7 +73798,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.querystring.stringify", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -73796,7 +73836,8 @@ "confidence": "high", "source": "surface-manifest:node-builtin.querystring.unescape", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 46e8989b..eba13e11 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -81395,7 +81395,8 @@ "status": "partial", "evidence": "derived:descendants", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -81821,7 +81822,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.os.homedir", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -81944,7 +81946,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.os.networkInterfaces", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -81977,7 +81980,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.os.platform", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -82010,7 +82014,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.os.release", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -82070,7 +82075,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.os.tmpdir", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -82103,7 +82109,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.os.totalmem", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -82136,7 +82143,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.os.type", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -82199,7 +82207,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.os.userInfo", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -82262,7 +82271,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.os.EOL", "tests": [ - "tests/corpus/1480-os-network-interfaces.ts" + "tests/corpus/1480-os-network-interfaces.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -82354,6 +82364,8 @@ "tests": [ "tests/corpus/2915-path-function-values.ts", "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts", "tests/corpus/957-builtins-namespace.ts" ] }, @@ -82416,7 +82428,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82451,7 +82465,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82486,7 +82502,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82578,7 +82596,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82613,7 +82633,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82648,7 +82670,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82713,7 +82737,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82748,7 +82774,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82783,7 +82811,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82818,7 +82848,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82853,7 +82885,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82888,7 +82922,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -82923,7 +82959,9 @@ "tests": [ "tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", - "tests/corpus/2916-path-function-values-cjs.ts" + "tests/corpus/2916-path-function-values-cjs.ts", + "tests/corpus/2917-builtin-callable-widths.ts", + "tests/corpus/2918-builtin-callable-widths-cjs.ts" ] }, "dynamic": { @@ -89406,6 +89444,7 @@ "status": "partial", "evidence": "derived:descendants", "tests": [ + "tests/corpus/2917-builtin-callable-widths.ts", "tests/corpus/957-builtins-namespace.ts" ] }, @@ -89439,7 +89478,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.querystring.decode", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -89472,7 +89512,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.querystring.encode", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -89505,7 +89546,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.querystring.escape", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -89538,7 +89580,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.querystring.parse", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -89571,7 +89614,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.querystring.stringify", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { @@ -89604,7 +89648,8 @@ "status": "partial", "evidence": "surface-manifest:node-builtin.querystring.unescape", "tests": [ - "tests/corpus/957-builtins-namespace.ts" + "tests/corpus/957-builtins-namespace.ts", + "tests/corpus/2917-builtin-callable-widths.ts" ] }, "dynamic": { diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index e6c58f4c..dec59b2d 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -50,11 +50,11 @@ "node-builtin.https": ["tests/corpus/2645-https-client-url-argument.ts", "tests/corpus/2690-https-server-timeout-option.ts"], "node-builtin.module": ["tests/corpus/1598-cjs-builtin-require/main.js"], "node-builtin.net": ["tests/corpus/2500-net-autosel-timeout.ts", "tests/corpus/2596-net-arg-ladders.cjs"], - "node-builtin.os": ["tests/corpus/1480-os-network-interfaces.ts"], - "node-builtin.path": ["tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", "tests/corpus/2916-path-function-values-cjs.ts"], + "node-builtin.os": ["tests/corpus/1480-os-network-interfaces.ts", "tests/corpus/2917-builtin-callable-widths.ts"], + "node-builtin.path": ["tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", "tests/corpus/2916-path-function-values-cjs.ts", "tests/corpus/2917-builtin-callable-widths.ts", "tests/corpus/2918-builtin-callable-widths-cjs.ts"], "node-builtin.perf_hooks": ["tests/corpus/957-builtins-namespace.ts"], "node-builtin.process": ["tests/corpus/990-process-basics.ts", "tests/corpus/2314-process-introspection.ts"], - "node-builtin.querystring": ["tests/corpus/957-builtins-namespace.ts"], + "node-builtin.querystring": ["tests/corpus/957-builtins-namespace.ts", "tests/corpus/2917-builtin-callable-widths.ts"], "node-builtin.readline": ["tests/corpus/1475-readline-closed-stdin.ts"], "node-builtin.stream": ["tests/corpus/1685-stream-readable-basics.ts", "tests/corpus/1746-stream-for-await.ts", "tests/corpus/1814-stream-pipeline.cjs", "tests/corpus/2629-stream-consumers.ts", "tests/corpus/2861-readable-iterator-close.ts"], "node-builtin.string_decoder": ["tests/corpus/1662-string-decoder-encodings.ts"], diff --git a/packages/compiler/src/frontend/lowering/lower-builtins.ts b/packages/compiler/src/frontend/lowering/lower-builtins.ts index b90ef361..64a0b7dd 100644 --- a/packages/compiler/src/frontend/lowering/lower-builtins.ts +++ b/packages/compiler/src/frontend/lowering/lower-builtins.ts @@ -2047,6 +2047,25 @@ function optionMember(p: ts.ObjectLiteralElementLike): { name: string; value: ts ); } } + // A value-enabled builtin with defaults uses its generated adapter for + // direct calls too. That keeps explicit undefined identical to omission: + // completeArgs wraps the undefined arm, then the adapter selects the + // descriptor's default before entering the fixed runtime libCall ABI. + // The call has already passed this member's ordinary arity/shape gates. + if (fn.defaults && fn.valueParams) { + const callee = lowerer.lowerBuiltinCallableValue(bi, loc); + if (!callee || callee.type.kind !== "func") { + throw new InternalCompilerError(`missing callable adapter for ${bi.module}.${bi.member}`); + } + const funcType = callee.type; + const shapes: ParamShape[] = fn.valueParams.map((param, index) => { + const type = funcType.params[index]; + if (!type) throw new InternalCompilerError(`missing callable parameter ${index} for ${bi.module}.${bi.member}`); + return { type, mode: param.mode === "optional" ? "omittable" : "required" }; + }); + const args = lowerer.completeArgs(expr.arguments, shapes, loc, expr); + return { kind: "callValue", callee, args, type: callee.type.ret, loc }; + } let args: IrExpr[]; if (hasSpread) { const shapes = fn.params.map((type, i): ParamShape => diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index d8bdb824..ab82c40d 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -74,6 +74,31 @@ export interface FnSig { generator?: { yieldT: IrType; nextT: IrType; resultType: IrType & { kind: "record" } }; } +/** The one func-type projection of completed parameter shapes. Typed rest + * parameters keep their packed array as the trailing native ABI slot and + * mark the value variadic so indirect call sites run completeArgs before + * callValue. Dynamic rest remains the historical hidden ScrDyn slot, while + * island rest spells its engine-array slot directly. */ +export function funcTypeFromParamShapes( + shapes: readonly ParamShape[], + ret: IrType, +): IrType & { kind: "func" } { + const typedRest = shapes.some((shape) => shape.mode === "rest"); + const dynRest = shapes.some((shape) => shape.mode === "dynRest"); + const islandRest = shapes.some((shape) => shape.mode === "islandRest"); + return { + kind: "func", + params: shapes.filter((shape) => shape.mode !== "dynRest").map((shape) => shape.type), + ret, + ...(typedRest || dynRest || islandRest ? { rest: true as const } : {}), + ...(typedRest + ? { restAbi: "typed" as const } + : islandRest + ? { restAbi: "jsval" as const } + : {}), + }; +} + /** Registers `const alias = overloadedDeclaration` as a compile-time * callable projection. The source function's one implementation ABI is * already collected in fnSigsBySymbol; each direct call still uses the @@ -695,6 +720,41 @@ export interface GenericInstance { return lowerer.wrappedUndefined(type, loc); } +/** Complete an indirect static call against its func value ABI. Ordinary + * fixed-width values retain the historical optional-suffix completion. + * Typed-rest values reinterpret their final array slot as a rest ParamShape + * and reuse completeArgs, including fixed-tuple and same-element array + * spreads with source-order/evaluate-once semantics. */ +function completeFuncValueArgs( + lowerer: Lowerer, + call: ts.CallExpression, + funcType: IrType & { kind: "func" }, + loc: SrcLoc, +): IrExpr[] { + if (funcType.rest === true && funcType.restAbi === "typed") { + const rest = funcType.params[funcType.params.length - 1]; + if (!rest || rest.kind !== "array") { + throw new InternalCompilerError("typed-rest function value has no trailing array ABI slot"); + } + const shapes: ParamShape[] = funcType.params.slice(0, -1).map((type) => ({ + type, + mode: omittedArgFor(lowerer, type, loc) ? "omittable" : "required", + })); + shapes.push({ type: rest, mode: "rest" }); + return completeArgs(lowerer, call.arguments, shapes, loc, call); + } + const args = call.arguments.map((arg, index) => + lowerer.lowerExprExpecting(arg, funcType.params[index])); + for (let i = args.length; i < funcType.params.length; i++) { + const absent = omittedArgFor(lowerer, funcType.params[i]!, loc); + if (!absent) { + lowerer.unsupported("SC1090", call, "calls omitting a non-optional parameter of the callee's type"); + } + args.push(absent); + } + return args; +} + export function undefinedArgFor(lowerer: Lowerer, type: IrType, loc: SrcLoc, blame: ts.Node): IrExpr { if (type.kind === "dyn") return dynUndefinedExpr(loc); // An omitted argument for an ISLAND-typed omittable param (`f()` where @@ -709,13 +769,12 @@ export interface GenericInstance { return wrapped; } -/** DECISION (docs/ir.md): function VALUES keep exact-arity semantics — a - * func-typed IrType spells one completed signature, so a function whose - * declaration has optional/default/rest parameters can become a value only - * where the target type spells that exact signature with required - * parameters (`x?: T` / `x: T = e` params appear as literal `T | undefined` - * unions; a rest signature is never spellable without `...`, which func - * types reject). Direct calls get the full feature. */ +/** DECISION (docs/ir.md): function VALUES carry one completed native ABI. + * Optional/default parameters appear as literal `T | undefined` slots; + * typed rest parameters appear as one trailing typed-array slot plus the + * typed-rest marker that makes indirect call sites pack source arguments. + * A value is admitted only where its target type projects to that same + * completed signature. */ export function requireExactArityValue(lowerer: Lowerer, blame: ts.Node, contextual: ts.Expression | null, shapes: readonly ParamShape[], @@ -738,9 +797,6 @@ export interface GenericInstance { ) { return; } - if (shapes.some((s) => s.mode === "rest")) { - lowerer.unsupported("SC1090", blame, "functions with rest parameters as values (call them directly)"); - } // The type the value FLOWS under must spell the completed ABI: the // contextual (target) type when one exists, otherwise the expression's // OWN inferred type — the unannotated-const case (`const f = (x = 5) => @@ -1739,12 +1795,7 @@ export function genericFnOf(lowerer: Lowerer, ident: ts.Identifier): GenericFnIn // closure per function, so `f === f` holds like any declaration). if (info.implicitParams) { const inst = implicitDefaultInstance(lowerer, ref, info); - const funcType: IrType = { - kind: "func", - params: inst.params.filter((p) => p.mode !== "dynRest").map((p) => p.type), - ret: inst.returnType, - ...(inst.params.some((p) => p.mode === "dynRest") ? { rest: true as const } : {}), - }; + const funcType: IrType = funcTypeFromParamShapes(inst.params, inst.returnType); lowerer.requireExactArityValue(ref, ref, inst.params, funcType); lowerer.noteEdge(inst.name); return { kind: "closure", fnName: inst.name, captures: [], type: funcType, loc }; @@ -1791,15 +1842,9 @@ export function genericFnOf(lowerer: Lowerer, ident: ts.Identifier): GenericFnIn // nothing: mapType answers null for an unsubstituted parameter. if (info.typeParams.some((tp) => !bindings.get(tp))) fenceUnpinned(); const inst = genericValueInstance(lowerer, ref, info, bindings); - // The value's type is the completed ABI signature — exact-arity, the - // declared-function value rule (dynRest slots stay out of the param - // list; the rest marker carries the trailing dyn-array ABI). - const funcType: IrType = { - kind: "func", - params: inst.params.filter((p) => p.mode !== "dynRest").map((p) => p.type), - ret: inst.returnType, - ...(inst.params.some((p) => p.mode === "dynRest") ? { rest: true as const } : {}), - }; + // The value's type is the completed ABI signature. Typed rest keeps its + // packed-array slot; dynRest stays hidden behind the rest marker. + const funcType: IrType = funcTypeFromParamShapes(inst.params, inst.returnType); lowerer.requireExactArityValue(ref, ref, inst.params, funcType); lowerer.noteEdge(inst.name); return { kind: "closure", fnName: inst.name, captures: [], type: funcType, loc }; @@ -4380,15 +4425,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const callee = expandoMemberRead(lowerer, expr.expression); if (callee) { if (callee.type.kind !== "func") lowerer.badType(expr.expression, lowerer.typeOf(expr.expression)); - const params = callee.type.params; - const args = expr.arguments.map((a, i) => lowerer.lowerExprExpecting(a, params[i])); - for (let i = args.length; i < params.length; i++) { - const absent = omittedArgFor(lowerer, params[i]!, loc); - if (!absent) { - lowerer.unsupported("SC1090", expr, "calls omitting a non-optional parameter of the callee's type"); - } - args.push(absent); - } + const args = completeFuncValueArgs(lowerer, expr, callee.type, loc); return { kind: "callValue", callee, args, type: callee.type.ret, loc }; } } @@ -4425,15 +4462,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { let callee = lowerer.lowerExpr(nsMember); if (callee.type.kind === "record") callee = lowerer.hybridCallUnwrap(callee); if (callee.type.kind !== "func") lowerer.badType(expr.expression, lowerer.typeOf(expr.expression)); - const params = callee.type.params; - const args = expr.arguments.map((a, i) => lowerer.lowerExprExpecting(a, params[i])); - for (let i = args.length; i < params.length; i++) { - const absent = omittedArgFor(lowerer, params[i]!, loc); - if (!absent) { - lowerer.unsupported("SC1090", expr, "calls omitting a non-optional parameter of the callee's type"); - } - args.push(absent); - } + const args = completeFuncValueArgs(lowerer, expr, callee.type, loc); return { kind: "callValue", callee, args, type: callee.type.ret, loc }; } // An AMBIENT namespace callee (`M.f()` where only `declare @@ -4485,15 +4514,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { let callee = lowerer.lowerExpr(nameId); if (callee.type.kind === "record") callee = lowerer.hybridCallUnwrap(callee); if (callee.type.kind !== "func") lowerer.badType(expr.expression, lowerer.typeOf(expr.expression)); - const params = callee.type.params; - const args = expr.arguments.map((a, i) => lowerer.lowerExprExpecting(a, params[i])); - for (let i = args.length; i < params.length; i++) { - const absent = omittedArgFor(lowerer, params[i]!, loc); - if (!absent) { - lowerer.unsupported("SC1090", expr, "calls omitting a non-optional parameter of the callee's type"); - } - args.push(absent); - } + const args = completeFuncValueArgs(lowerer, expr, callee.type, loc); return { kind: "callValue", callee, args, type: callee.type.ret, loc }; } if (ts.isPropertyAccessExpression(expr.expression)) { @@ -4781,6 +4802,14 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if (callee.type.kind !== "func") { lowerer.badType(expr.expression, lowerer.typeOf(expr.expression)); } + // Typed-rest closures already have a fixed native ABI: complete the + // source-level variadic call into their trailing typed-array slot. + // Claim before the runtime-arity spread lane; completeArgs handles + // typed spreads without boxing the function. + if (callee.type.rest === true && callee.type.restAbi === "typed") { + const args = completeFuncValueArgs(lowerer, expr, callee.type, loc); + return { kind: "callValue", callee, args, type: callee.type.ret, loc }; + } // A SPREAD argument on a func-typed callee — the rest-forwarding // idiom (`(...args) => from(...args)`): the runtime-arity lane boxes // or marshals the callee and applies through a runtime-built argument @@ -4834,26 +4863,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const boxed: IrExpr = { kind: "dynFrom", value: callee, type: DYN, loc }; return { kind: "dynCall", callee: boxed, calleeName, args, type: DYN, loc }; } - const params = callee.type.params; - const args = expr.arguments.map((a, i) => lowerer.lowerExprExpecting(a, params[i])); - // Optional-param func TYPES map their `x?: T` slots as `T | undefined` - // ABI unions, and tsc admits calls that omit the optional suffix — - // complete the missing trailing args with the interned undefined arm, - // exactly what completeArgs does for direct calls (the ABI stays - // count-exact). A missing arg whose param has no undefined arm means - // the callee value's type spelled a required param tsc let the caller - // skip — not a shape this surface models; fence. - for (let i = args.length; i < params.length; i++) { - // A missing argument completes with the slot's absent value — the - // interned undefined arm, the dyn undefined for checked-dynamic - // slots (a JS-inferred wrapper like mustCall's, called short), or - // the engine undefined for island slots. - const absent = omittedArgFor(lowerer, params[i]!, loc); - if (!absent) { - lowerer.unsupported("SC1090", expr, "calls omitting a non-optional parameter of the callee's type"); - } - args.push(absent); - } + const args = completeFuncValueArgs(lowerer, expr, callee.type, loc); return { kind: "callValue", callee, args, type: callee.type.ret, loc }; } @@ -6265,10 +6275,9 @@ function loweredTemplateStrings( } /** Signature checks + param shapes + IR func type for any lambda-like - * node. The func type's params are the ABI types, so a lambda with - * optional/default params has the same IR type as one spelling the - * `T | undefined` unions with required params — exactly the exact-arity - * value rule (requireExactArityValue decides who may become a value). */ + * node. The func type's params are the ABI types: optional/default params + * become `T | undefined`, and typed rest becomes one packed-array slot. + * requireExactArityValue decides whether that completed ABI may escape. */ export function lambdaSignature(lowerer: Lowerer, node: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | ts.MethodDeclaration | ts.GetAccessorDeclaration | ts.SetAccessorDeclaration,): { shapes: ParamShape[]; funcType: IrType & { kind: "func" } } { if (!node.body) lowerer.unsupported("SC1090", node, "function overload signatures"); if (node.typeParameters) { @@ -6381,7 +6390,6 @@ function loweredTemplateStrings( // use a rest parameter. Arrows never claim it (JS: an arrow's // `arguments` is the enclosing function's). const hasDynRest = shapes.some((s) => s.mode === "dynRest"); - const hasIslandRest = shapes.some((s) => s.mode === "islandRest"); const usesArguments = !hasDynRest && !ts.isArrowFunction(node) && @@ -6394,20 +6402,9 @@ function loweredTemplateStrings( "'arguments' in functions with declared parameters (use a rest parameter: (...args))", ); } - return { - shapes, - funcType: { - kind: "func", - // dynRest is EXCLUDED (the boxed thunk fills the trailing dyn - // array — no spelled slot); islandRest is INCLUDED (the trailing - // jsval param IS the engine arguments array, the REST host-call - // adapter's one uniform shape). - params: shapes.filter((s) => s.mode !== "dynRest").map((s) => s.type), - ret, - ...(hasDynRest || usesArguments || hasIslandRest ? { rest: true as const } : {}), - ...(hasIslandRest ? { restAbi: "jsval" as const } : {}), - }, - }; + const funcType = funcTypeFromParamShapes(shapes, ret); + if (usesArguments && !hasDynRest) funcType.rest = true; + return { shapes, funcType }; } /** Does this function's OWN body read `arguments`? Nested plain functions @@ -6443,7 +6440,7 @@ function loweredTemplateStrings( export function lowerLambda(lowerer: Lowerer, node: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration | ts.MethodDeclaration | ts.GetAccessorDeclaration | ts.SetAccessorDeclaration,): IrExpr { const loc = locOf(node); const { shapes, funcType } = lowerer.lambdaSignature(node); - // A lambda IS a value: the exact-arity rule applies at birth. The + // A lambda IS a value: the completed-ABI rule applies at birth. The // contextual (target) type decides — `(x?: number) => void` may flow // into a slot annotated `(x: number | undefined) => void` (same ABI // signature), anything else is fenced. Nested function declarations and @@ -6593,7 +6590,7 @@ function loweredTemplateStrings( // closure-signature check trips (SC9001). Island rest types SPELL // their trailing engine-array param, so funcType.params already // covers those. - if (funcType.rest === true && funcType.restAbi !== "jsval") { + if (funcType.rest === true && funcType.restAbi === undefined) { params.push({ localId: "%pfrest", name: "%pfrest", type: DYN }); } const fence = lowerer.deferToRuntimeFence(diagsBefore, node, { @@ -8790,8 +8787,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I // `.bold` (the chalk shape). if (callee.type.kind === "record") callee = lowerer.hybridCallUnwrap(callee); if (callee.type.kind !== "func") lowerer.badType(access, lowerer.typeOf(access)); - const params = callee.type.params; - const args = call.arguments.map((a, i) => lowerer.lowerExprExpecting(a, params[i])); + const args = completeFuncValueArgs(lowerer, call, callee.type, locOf(call)); return { kind: "callValue", callee, args, type: callee.type.ret, loc: locOf(call) }; } @@ -9740,15 +9736,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I } } if (callee?.type.kind === "func") { - const params = callee.type.params; - const args = call.arguments.map((a, i) => lowerer.lowerExprExpecting(a, params[i])); - for (let i = args.length; i < params.length; i++) { - const absent = omittedArgFor(lowerer, params[i]!, locOf(call)); - if (!absent) { - lowerer.unsupported("SC1090", call, "calls omitting a non-optional parameter of the callee's type"); - } - args.push(absent); - } + const args = completeFuncValueArgs(lowerer, call, callee.type, locOf(call)); return { kind: "callValue", callee, args, type: callee.type.ret, loc: locOf(call) }; } if (callee?.type.kind === "dyn") { diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index d1364600..e43ebb40 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -24,7 +24,7 @@ import { ambientNsRootOf, ambientUndefReadType, ambientUndefVarRootOf, ambientUn import { expandoMemberRead, expandoWritableTarget } from "./lower-expando.js"; import { lowerSocketInstanceOf, lowerTlsRootCertificates } from "./lower-server.js"; import { findGenericMethodOn, lowerStaticFieldRead } from "./lower-classes.js"; -import { bindingNeverReassigned, implicitMonoFile, lowerTaggedTemplate, nullishGenericBindingUnitOf, objLitGenericFnInfoOf, objLitGenericFnNodeOf, requireObjLitGenericReceiver } from "./lower-calls.js"; +import { bindingNeverReassigned, funcTypeFromParamShapes, implicitMonoFile, lowerTaggedTemplate, nullishGenericBindingUnitOf, objLitGenericFnInfoOf, objLitGenericFnNodeOf, requireObjLitGenericReceiver } from "./lower-calls.js"; import { mixinFnOfCallee } from "./lower-mixins.js"; import { isConstAssertionTypeNode, isGenericCallableMemberType, isParseArgsDynTypeName, underConstAssertion, unitOnlyUnion } from "../type-mapper.js"; import { lowerYield } from "./lower-generators.js"; @@ -889,17 +889,12 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (sig && lowerer.isTopLevelFnSymbol(expr)) { // A declared function used as a value: a zero-capture closure. The // backend interns one per function so `f === f` holds (JS identity). - // The value's type is the completed ABI signature — exact-arity, so - // optional/default/rest declarations pass the value fence first. + // The value's type is the completed ABI signature; optional/default + // slots and typed rest arrays pass the value fence first. lowerer.noteEdge(sig.name); // dynRest slots stay out of the VALUE type's param list (fn.length // semantics); the rest marker carries the trailing dyn-array ABI. - const funcType: IrType = { - kind: "func", - params: sig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type), - ret: sig.returnType, - ...(sig.params.some((p) => p.mode === "dynRest") ? { rest: true as const } : {}), - }; + const funcType: IrType = funcTypeFromParamShapes(sig.params, sig.returnType); lowerer.requireExactArityValue(expr, expr, sig.params, funcType); return { kind: "closure", fnName: sig.name, captures: [], type: funcType, loc }; } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 13cfaf99..2a7b32e2 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -95,7 +95,7 @@ import { FileParts, splitFiles, collectProgram, collectNpmImports, collectJsonIm import { prepareCjsModuleGraph } from "./lower-node-module.js"; import { ClassInfo, ClassIteratorInfo, GenericClassInfo, registerBuiltinErrorClasses, registerBuiltinEmitterClass, registerBuiltinStreamClasses, builtinErrorInfoOf, builtinEmitterInfoOf, builtinStreamInfoOf, analyzeClassDecoration, classIteratorDrainCall, classIteratorNextCall, classIteratorOf, classIteratorOpenCall, classIteratorRestDrainCall, classMemberNameOf, classValueRef, collectClassShape, exactClassOfReceiver, collectClassShapeInner, ctorAbiEquals, findMethodOn, findStaticOn, findGenericMethodOn, findGenericStaticOn, genericClassInstanceType, isSubclassOf, inHierarchy, overrideBelow, staticShadowBelow, upcastTo, lowerClassMembers, lowerClassCtor, lowerClassExpression, lowerClassExpressionInfo, lowerClassMethodMember, lowerClassValueProperty, lowerStaticMethod, throwingSetterFn, fieldInitStmts, lowerStaticFieldInits, lowerStaticFieldRead, lowerDerivedCtorBody, superCallStmt, lowerSuperMethodCall, superThisRef, lowerSuperAccessorRead, lowerSuperAccessorWrite, inheritsBuiltinErrorCtor, inheritsBuiltinEmitterCtor, errorMessageArg, lowerNew, accessorCall } from "./lower-classes.js"; import { MixinFnShape, mixinCallClassInfoOf, mixinIntersectionInstanceType } from "./lower-mixins.js"; -import { ParamShape, FnSig, GenericFnInfo, GenericInstance, bindingNeverReassigned, bodyReadsArguments, implicitMonoFile, isThisParameter, paramShape, paramShapes, checkDefaultParamBodyType, completeArgs, wrappedUndefined, undefinedArgFor, requireExactArityValue, bodyReturnType, declaredReturnType, collectSignature, collectSignatureInner, collectGenericSignature, genericFnOf, lowerGenericCall, lowerGenericFnValue, inferTypeParamBindings, lowerGenericInstance, lowerCall, lowerFfiCall, lowerTimersMemberCall, lowerPromiseMethodCall, lowerFilterNarrowCall, isTopLevelFnSymbol, lowerNestedFunctionDecl, lambdaSignature, lowerLambda, lowerFunction, validateFfiImports } from "./lower-calls.js"; +import { ParamShape, FnSig, GenericFnInfo, GenericInstance, bindingNeverReassigned, bodyReadsArguments, funcTypeFromParamShapes, implicitMonoFile, isThisParameter, paramShape, paramShapes, checkDefaultParamBodyType, completeArgs, wrappedUndefined, undefinedArgFor, requireExactArityValue, bodyReturnType, declaredReturnType, collectSignature, collectSignatureInner, collectGenericSignature, genericFnOf, lowerGenericCall, lowerGenericFnValue, inferTypeParamBindings, lowerGenericInstance, lowerCall, lowerFfiCall, lowerTimersMemberCall, lowerPromiseMethodCall, lowerFilterNarrowCall, isTopLevelFnSymbol, lowerNestedFunctionDecl, lambdaSignature, lowerLambda, lowerFunction, validateFfiImports } from "./lower-calls.js"; import { lowerArrayMethodCall, lowerMapMethodCall, lowerMapForEachCall, buildMapForEachFn, lowerRecordOvfCaptureHelper, lowerEnvToPairsHelper, lowerSetMethodCall, lowerSetForEachCall, buildSetForEachFn } from "./lower-containers.js"; import { lowerBufferStaticCall, lowerBytesMethodCall, lowerBytesNew } from "./containers/bytes.js"; import { lowerRegexMethodCall, lowerStringMethodCall } from "./containers/string-and-regexp.js"; @@ -3514,7 +3514,7 @@ export class Lowerer { this.diags.length > 0 ? null : { - irVersion: 9, + irVersion: 10, sourceFile: this.entry.fileName, functions, classes: artifacts.classes, @@ -9971,7 +9971,7 @@ export class Lowerer { /** Materialize one explicitly value-callable table entry as an interned * synthetic module function. Direct calls never use this path: they keep - * their existing validated libCall lowering. The descriptor opt-in is + * their existing validated libCall lowering. The value descriptor is * deliberately narrower than table membership because many table rows * are only dispatch sentinels for call-site-specific lowering. */ lowerBuiltinCallableValue( @@ -9979,29 +9979,79 @@ export class Lowerer { loc: SrcLoc, ): IrExpr | null { const fn = builtinModuleFnOf(this, bi.module, bi.member); - if (fn?.valueCallable !== true) return null; - if (fn.variadicPack || fn.defaults !== undefined) { - throw new InternalCompilerError( - `builtin callable value '${bi.module}.${bi.member}' has a variable-width descriptor`, - ); - } - const funcType = { kind: "func" as const, params: fn.params, ret: fn.result }; - const key = `${fn.fn}:${typeKey(funcType)}`; + const valueParams = fn?.valueParams; + if (!fn || !valueParams) return null; + const shapes: ParamShape[] = valueParams.map((param): ParamShape => { + if (param.mode === "rest") return { mode: "rest", type: arrayOf(param.type) }; + if (param.mode === "optional") { + return { mode: "omittable", type: this.withUndefinedArm(param.type), bodyType: param.type }; + } + return { mode: "required", type: param.type }; + }); + const funcType = funcTypeFromParamShapes(shapes, fn.result); + const adapterShape = valueParams + .map((param) => param.mode === "optional" ? `${param.mode}:${param.defaultValue}` : param.mode) + .join(","); + const key = `${fn.fn}:${typeKey(funcType)}:${adapterShape}`; let fnName = this.builtinCallableValueFns.get(key); if (!fnName) { fnName = `%builtin.value.${fn.fn}.${this.builtinCallableValueFns.size}`; this.builtinCallableValueFns.set(key, fnName); - const params = fn.params.map((type, index) => ({ + const params = funcType.params.map((type, index) => ({ localId: `arg.${index}`, name: `arg${index}`, type, })); - const args: IrExpr[] = params.map((param) => ({ - kind: "varRef", - localId: param.localId, - type: param.type, - loc, - })); + const args: IrExpr[] = params.map((param, index) => { + const valueParam = valueParams[index]; + if (!valueParam) { + throw new InternalCompilerError( + `builtin callable value '${bi.module}.${bi.member}' has a missing value parameter`, + ); + } + const ref: IrExpr = { + kind: "varRef", + localId: param.localId, + type: param.type, + loc, + }; + if (valueParam.mode !== "optional") return ref; + if (valueParam.type.kind !== "string" || param.type.kind !== "union") { + throw new InternalCompilerError( + `builtin callable value '${bi.module}.${bi.member}' has an unsupported optional default`, + ); + } + const undefTag = this.armTag(param.type.unionId, UNDEFINED_T); + const valueTag = this.armTag(param.type.unionId, valueParam.type); + if (undefTag < 0 || valueTag < 0) { + throw new InternalCompilerError( + `builtin callable value '${bi.module}.${bi.member}' has an invalid optional ABI`, + ); + } + return { + kind: "ternary", + cond: { + kind: "unionIsTag", + unionId: param.type.unionId, + tag: undefTag, + negated: false, + value: ref, + type: BOOL, + loc, + }, + then: { kind: "strLit", value: valueParam.defaultValue, type: STRING, loc }, + else_: { + kind: "unionNarrow", + unionId: param.type.unionId, + tag: valueTag, + value: ref, + type: valueParam.type, + loc, + }, + type: valueParam.type, + loc, + }; + }); const call: IrExpr = { kind: "libCall", fn: fn.fn, args, type: fn.result, loc }; const body: IrStmt[] = fn.result.kind === "void" ? [ diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index ac3cfefd..ef632b75 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -569,13 +569,19 @@ export interface BuiltinModuleFn { result: IrType; variadicPack?: boolean; defaults?: string[]; - /** This exact fixed-width table entry may materialize as an interned - * zero-capture closure when the builtin escapes call position. Entries - * with call-site-specific validation, optional completion, or rest - * packing stay call-only until they have an equally exact adapter. */ - valueCallable?: true; + /** The source-level function-value signature. Each admitted entry + * materializes as an interned zero-capture adapter over the fixed runtime + * libCall ABI. Optional parameters name the string default selected for + * omission or explicit undefined; rest parameters pack into one typed + * array slot. Entries with call-site-specific validation remain absent. */ + valueParams?: BuiltinValueParam[]; } +export type BuiltinValueParam = + | { mode: "required"; type: IrType } + | { mode: "optional"; type: IrType; defaultValue: string } + | { mode: "rest"; type: IrType }; + /** The lowerable surface of the supported node builtin modules, keyed by * CANONICAL module name (both "fs" and "node:fs" land on "fs" — see * canonicalBuiltinModule). Like STR_METHODS, membership is only half the @@ -589,30 +595,30 @@ export interface BuiltinModuleFn { * platform's rules on any host). toNamespacedPath is the posix identity * (Node: a non-op on posix systems). */ const PATH_MODULE_FNS: Record = { - join: { fn: "path.join", params: [STRING], result: STRING, variadicPack: true }, - resolve: { fn: "path.resolve", params: [STRING], result: STRING, variadicPack: true }, - normalize: { fn: "path.normalize", params: [STRING], result: STRING, valueCallable: true }, - dirname: { fn: "path.dirname", params: [STRING], result: STRING, valueCallable: true }, - basename: { fn: "path.basename", params: [STRING, STRING], result: STRING, defaults: [""] }, - extname: { fn: "path.extname", params: [STRING], result: STRING, valueCallable: true }, - isAbsolute: { fn: "path.isAbsolute", params: [STRING], result: BOOL, valueCallable: true }, - relative: { fn: "path.relative", params: [STRING, STRING], result: STRING, valueCallable: true }, - toNamespacedPath: { fn: "path.toNamespacedPath", params: [STRING], result: STRING, valueCallable: true }, + join: { fn: "path.join", params: [STRING], result: STRING, variadicPack: true, valueParams: [{ mode: "rest", type: STRING }] }, + resolve: { fn: "path.resolve", params: [STRING], result: STRING, variadicPack: true, valueParams: [{ mode: "rest", type: STRING }] }, + normalize: { fn: "path.normalize", params: [STRING], result: STRING, valueParams: [{ mode: "required", type: STRING }] }, + dirname: { fn: "path.dirname", params: [STRING], result: STRING, valueParams: [{ mode: "required", type: STRING }] }, + basename: { fn: "path.basename", params: [STRING, STRING], result: STRING, defaults: [""], valueParams: [{ mode: "required", type: STRING }, { mode: "optional", type: STRING, defaultValue: "" }] }, + extname: { fn: "path.extname", params: [STRING], result: STRING, valueParams: [{ mode: "required", type: STRING }] }, + isAbsolute: { fn: "path.isAbsolute", params: [STRING], result: BOOL, valueParams: [{ mode: "required", type: STRING }] }, + relative: { fn: "path.relative", params: [STRING, STRING], result: STRING, valueParams: [{ mode: "required", type: STRING }, { mode: "required", type: STRING }] }, + toNamespacedPath: { fn: "path.toNamespacedPath", params: [STRING], result: STRING, valueParams: [{ mode: "required", type: STRING }] }, }; /** The win32 twins (scr_path.c's Node-v24 path.win32 port, byte-for-byte): * "path/win32" everywhere, and the bare-module table when the build * TARGETS win32 — Node on Windows is path.win32. */ const PATH_WIN32_MODULE_FNS: Record = { - join: { fn: "path.win32Join", params: [STRING], result: STRING, variadicPack: true }, - resolve: { fn: "path.win32Resolve", params: [STRING], result: STRING, variadicPack: true }, - normalize: { fn: "path.win32Normalize", params: [STRING], result: STRING, valueCallable: true }, - dirname: { fn: "path.win32Dirname", params: [STRING], result: STRING, valueCallable: true }, - basename: { fn: "path.win32Basename", params: [STRING, STRING], result: STRING, defaults: [""] }, - extname: { fn: "path.win32Extname", params: [STRING], result: STRING, valueCallable: true }, - isAbsolute: { fn: "path.win32IsAbsolute", params: [STRING], result: BOOL, valueCallable: true }, - relative: { fn: "path.win32Relative", params: [STRING, STRING], result: STRING, valueCallable: true }, - toNamespacedPath: { fn: "path.win32ToNamespacedPath", params: [STRING], result: STRING, valueCallable: true }, + join: { fn: "path.win32Join", params: [STRING], result: STRING, variadicPack: true, valueParams: [{ mode: "rest", type: STRING }] }, + resolve: { fn: "path.win32Resolve", params: [STRING], result: STRING, variadicPack: true, valueParams: [{ mode: "rest", type: STRING }] }, + normalize: { fn: "path.win32Normalize", params: [STRING], result: STRING, valueParams: [{ mode: "required", type: STRING }] }, + dirname: { fn: "path.win32Dirname", params: [STRING], result: STRING, valueParams: [{ mode: "required", type: STRING }] }, + basename: { fn: "path.win32Basename", params: [STRING, STRING], result: STRING, defaults: [""], valueParams: [{ mode: "required", type: STRING }, { mode: "optional", type: STRING, defaultValue: "" }] }, + extname: { fn: "path.win32Extname", params: [STRING], result: STRING, valueParams: [{ mode: "required", type: STRING }] }, + isAbsolute: { fn: "path.win32IsAbsolute", params: [STRING], result: BOOL, valueParams: [{ mode: "required", type: STRING }] }, + relative: { fn: "path.win32Relative", params: [STRING, STRING], result: STRING, valueParams: [{ mode: "required", type: STRING }, { mode: "required", type: STRING }] }, + toNamespacedPath: { fn: "path.win32ToNamespacedPath", params: [STRING], result: STRING, valueParams: [{ mode: "required", type: STRING }] }, }; export const BUILTIN_MODULE_FNS: Record | undefined> = { @@ -698,18 +704,18 @@ export const BUILTIN_MODULE_FNS: Record shape, verified // structurally there — this entry only routes the dispatch. @@ -797,8 +803,8 @@ export const BUILTIN_MODULE_FNS: Record void` (Promise's resolve) is callable with // no arguments — a void param is dropped, not a mapping failure. if (pt.kind === "void") continue; @@ -2608,7 +2617,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // like declaredReturnType does for declarations. const ret = retT.flags & ts.TypeFlags.Never ? VOID : mapType(retT, ctx); if (!ret) return null; - return funcOf(params, ret); + return typedRest + ? { kind: "func", params, ret, rest: true, restAbi: "typed" } + : funcOf(params, ret); } // Records: object types whose members are all data properties (shorthand // methods in type position count — they're func-typed fields) with @@ -3909,8 +3920,9 @@ export function describeComponentBlocker(widened: ts.Type, ctx: TypeMapperCtx): return null; } - // Single-signature, non-generic function types: rest parameters, a - // parameter type, or the return type carries the failure. (Generic and + // Single-signature, non-generic function types: a parameter type or the + // return type carries the failure. Typed rest parameters use their array + // type here like any other completed ABI slot. (Generic and // overloaded signatures have their own fences — SC2005/SC2007 — and // badType runs those first.) const callSigs = checker.getCallSignatures(widened); @@ -3927,10 +3939,6 @@ export function describeComponentBlocker(widened: ts.Type, ctx: TypeMapperCtx): return `the function shape is supported, but its signature is variadic ('arguments'-reading), and a compiled signature is fixed-arity`; } for (const p of sig.getParameters()) { - const decl = checker.valueDeclarationOf(p); - if (decl !== undefined && ts.isParameter(decl) && decl.dotDotDotToken !== undefined) { - return `the function shape is supported, but its rest parameter '${p.name}' has no compiled calling convention yet (a compiled signature is fixed-arity)`; - } const pTs = checker.getTypeOfSymbol(p); if (!mapType(pTs, ctx)) { return `the function shape is supported, but its parameter '${p.name}' has type '${text(pTs)}', which does not compile`; diff --git a/packages/compiler/src/ir/ir.test.ts b/packages/compiler/src/ir/ir.test.ts index f441227b..32d3b1bf 100644 --- a/packages/compiler/src/ir/ir.test.ts +++ b/packages/compiler/src/ir/ir.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { HANDLE_KINDS, POINTER_KINDS } from "./ir.js"; +import { HANDLE_KINDS, POINTER_KINDS, STRING, arrayOf, typeEquals, typeKey } from "./ir.js"; describe("IR kind sets", () => { test("keeps procStream as the scalar handle exception", () => { @@ -14,4 +14,18 @@ describe("IR kind sets", () => { expect(POINTER_KINDS.has("record")).toBe(true); expect(POINTER_KINDS.has("date")).toBe(false); }); + + test("distinguishes typed-rest closure ABIs", () => { + const packed = arrayOf(STRING); + const typedRest = { + kind: "func" as const, + params: [packed], + ret: STRING, + rest: true as const, + restAbi: "typed" as const, + }; + const fixed = { kind: "func" as const, params: [packed], ret: STRING }; + expect(typeEquals(typedRest, fixed)).toBe(false); + expect(typeKey(typedRest)).toBe("func(array,...typed[])=>string"); + }); }); diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 296f6274..259a4a86 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -227,16 +227,16 @@ export type IrType = * digest state owns no script values and cannot participate in a cycle. */ | { kind: "cryptoHash" } | { kind: "cryptoHmac" } - /** Heap, refcounted closure. `rest` marks a VARIADIC JS function (a - * `...args` rest parameter, or a zero-param function body reading - * `arguments` — test/common's mustCall wrapper): the lifted function - * takes one extra trailing `ScrDyn *` param — a dyn ARRAY carrying the - * call's arguments from index params.length on — which the dyn call - * thunk builds per call. `params` stays the DECLARED (non-rest) list - * (fn.length semantics). Rest-marked values are only ever CALLED - * through the dyn boundary (boxed thunks); direct static calls box - * first (lower-calls). */ - | { kind: "func"; params: IrType[]; ret: IrType; rest?: true; restAbi?: "jsval" } + /** Heap, refcounted closure. `rest` marks a variadic function value. + * `restAbi: "typed"` spells one trailing typed array in `params`; static + * call sites pack their surplus arguments into it before `callValue`. + * `restAbi: "jsval"` similarly spells one trailing engine array for an + * island host callback. An absent `restAbi` is the legacy checked-dynamic + * JS form: `params` stays the declared non-rest list and the lifted + * function has one hidden trailing ScrDyn array filled by its boxed call + * thunk. The backends therefore still see one fixed native closure ABI; + * only the frontend's call completion observes variadic source arity. */ + | { kind: "func"; params: IrType[]; ret: IrType; rest?: true; restAbi?: "jsval" | "typed" } | { kind: "object"; className: string } // heap, refcounted class instance /** The class STATIC side as a value — `typeof C`, the type of the class * name itself and of `new (…) => T` constructor-typed slots. Runtime @@ -727,7 +727,7 @@ export function typeKey(t: IrType): string { case "set": return `set<${typeKey(t.elem)}>`; case "func": - return `func(${[...t.params.map(typeKey), ...(t.rest ? [t.restAbi === "jsval" ? "...jsval[]" : "...dyn[]"] : [])].join(",")})=>${typeKey(t.ret)}`; + return `func(${[...t.params.map(typeKey), ...(t.rest ? [t.restAbi === "jsval" ? "...jsval[]" : t.restAbi === "typed" ? "...typed[]" : "...dyn[]"] : [])].join(",")})=>${typeKey(t.ret)}`; case "object": return `object:${t.className}`; case "classval": @@ -760,6 +760,7 @@ export function typeEquals(a: IrType, b: IrType): boolean { b.kind === "func" && a.params.length === b.params.length && (a.rest === true) === (b.rest === true) && + a.restAbi === b.restAbi && a.params.every((p, i) => typeEquals(p, b.params[i]!)) && typeEquals(a.ret, b.ret) ); @@ -797,7 +798,7 @@ export function isRefCounted(t: IrType): boolean { export interface IrModule { /** Bumped on any breaking IR change; serialize.ts refuses mismatches. */ - irVersion: 9; + irVersion: 10; sourceFile: string; functions: IrFunction[]; /** Class shapes. Constructors and methods are ordinary module functions @@ -5956,6 +5957,10 @@ export function canBoxFuncIntoDyn( ): boolean { return ( t.kind === "func" && + // Only the legacy hidden-dyn rest ABI has a checked-dynamic call thunk. + // Typed rest stays static (its trailing array is compiler-packed), and + // island rest has its separate engine host-callback adapter. + (t.rest !== true || t.restAbi === undefined) && // A jsval (island) param converts through scr_jsval_from_dyn in the // thunk (wrapped cells unwrap by reference, dyn data deep-copies) — // the checker-'any' callback params of the routed-dispatch lane diff --git a/packages/compiler/src/ir/serialize.ts b/packages/compiler/src/ir/serialize.ts index 65c0882c..e0c49330 100644 --- a/packages/compiler/src/ir/serialize.ts +++ b/packages/compiler/src/ir/serialize.ts @@ -6,7 +6,7 @@ import { InternalCompilerError } from "../errors.js"; */ import type { IrModule } from "./ir.js"; -export const IR_VERSION = 9 as const; +export const IR_VERSION = 10 as const; export function serializeModule(mod: IrModule): string { return JSON.stringify(mod, (_key, value) => { diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 9b4dc4d5..c87c640a 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -2911,7 +2911,7 @@ function validateFunction( // ISLAND-REST types (restAbi jsval) SPELL their trailing engine // array param — the lifted signature matches directly, no // hidden slot. - const hiddenRest = e.type.rest === true && e.type.restAbi !== "jsval"; + const hiddenRest = e.type.rest === true && e.type.restAbi === undefined; const declared = hiddenRest ? target.params.slice(0, -1) : target.params; const restOk = !hiddenRest || diff --git a/packages/compiler/src/library/int-infer.test.ts b/packages/compiler/src/library/int-infer.test.ts index 8be5cb5c..d124f3fe 100644 --- a/packages/compiler/src/library/int-infer.test.ts +++ b/packages/compiler/src/library/int-infer.test.ts @@ -65,7 +65,7 @@ const sink = (name: string): IrFunction => ({ /** A module holding the case function plus the two declared sinks. */ function caseModule(params: string[], locals: string[], body: IrStmt[]): IrModule { return { - irVersion: 9, + irVersion: 10, sourceFile: "corpus.ts", functions: [ sink("send"), @@ -136,7 +136,7 @@ const RECORD_CFG: IntSlotConfig = { function recordCase(body: IrStmt[], names = ["m"], extraFns: IrFunction[] = []): IrModule { return { - irVersion: 9, + irVersion: 10, sourceFile: "fields.ts", functions: [ ...extraFns, @@ -177,7 +177,7 @@ const classCountRead = (): IrExpr => ({ function onlyOrdinaryClass(body: IrStmt[]): IntVerdict { const mod: IrModule = { - irVersion: 9, + irVersion: 10, sourceFile: "class-fields.ts", functions: [ sink("send"), @@ -391,7 +391,7 @@ describe("the domain's edges beyond the corpus", () => { loc, }; const mod: IrModule = { - irVersion: 9, + irVersion: 10, sourceFile: "optional.ts", functions: [{ name: "normalize", diff --git a/packages/compiler/src/library/library-cache.test.ts b/packages/compiler/src/library/library-cache.test.ts index 5323e5d6..dee881d8 100644 --- a/packages/compiler/src/library/library-cache.test.ts +++ b/packages/compiler/src/library/library-cache.test.ts @@ -250,7 +250,7 @@ test("semantic library cache restores and rebases IR after a comment-only edit", const sourceBefore = await readFile(f.source, "utf8"); const returnStart = sourceBefore.indexOf("return"); const semanticMod = { - irVersion: 9, + irVersion: 10, sourceFile: f.source, functions: [{ name: "__main", @@ -308,7 +308,7 @@ test("semantic library cache refuses token and directive edits", async () => { const f = await fixture(); const sourceBefore = await readFile(f.source, "utf8"); const semanticMod = { - irVersion: 9, + irVersion: 10, sourceFile: f.source, functions: [{ name: "__main", @@ -353,7 +353,7 @@ test("semantic C cache accepts only line-preserving single-source edits", async const f = await fixture(); const sourceBefore = await readFile(f.source, "utf8"); const semanticMod = { - irVersion: 9, + irVersion: 10, sourceFile: f.source, functions: [{ name: "__main", @@ -424,7 +424,7 @@ test("semantic C cache refuses non-LF separator normalization", async () => { frontend: tracker.snapshot(), semantic: { mod: { - irVersion: 9, + irVersion: 10, sourceFile: f.source, functions: [], entry: "__main", @@ -445,7 +445,7 @@ test("semantic C cache refuses comment-only edits in multi-source graphs", async const importedSource = "export function helper(): number { return 1; }\n"; await writeFile(imported, importedSource); const semanticMod = { - irVersion: 9, + irVersion: 10, sourceFile: f.source, functions: [{ name: "__main", diff --git a/packages/compiler/test/bytes-element-emission.test.ts b/packages/compiler/test/bytes-element-emission.test.ts index 6b29aa66..7e4ae222 100644 --- a/packages/compiler/test/bytes-element-emission.test.ts +++ b/packages/compiler/test/bytes-element-emission.test.ts @@ -66,7 +66,7 @@ function fixture(): IrModule { ); return { - irVersion: 9, + irVersion: 10, sourceFile: loc.file, entry: "__main", functions: [{ name: "__main", params: [], returnType: VOID, locals, body, loc }], @@ -136,7 +136,7 @@ function receiverReassignmentFixture(): IrModule { ]; return { - irVersion: 9, + irVersion: 10, sourceFile: loc.file, entry: "__main", functions: [{ name: "__main", params: [], returnType: VOID, locals, body, loc }], @@ -201,7 +201,7 @@ function integerLoopFixture(mutatesIndex = false): IrModule { { kind: "bytesSet", arr: bytesRef(), index: indexRef(), value: ref("sum"), loc }, ); return { - irVersion: 9, + irVersion: 10, sourceFile: loc.file, entry: "__main", functions: [{ @@ -328,7 +328,7 @@ test("large record clones stay outlined while small clones remain inlineable", ( const fields = Array.from({ length: count }, (_, i) => ({ name: `f${i}`, type: F64 })); const type = { kind: "record", shapeId: id } as const; return { - irVersion: 9, + irVersion: 10, sourceFile: "record-clone.ts", entry: "__main", records: [{ id, fields }], diff --git a/packages/compiler/test/emit-c.test.ts b/packages/compiler/test/emit-c.test.ts index 2d969905..c25cbb93 100644 --- a/packages/compiler/test/emit-c.test.ts +++ b/packages/compiler/test/emit-c.test.ts @@ -35,7 +35,7 @@ test("strings: literals, concat in a loop, toString, RC-clean under audit", asyn // while (i < 3) { acc = acc + ("-" + i); i = i + 1; } // console.log(acc, acc === "x-0-1-2", "α∂" < "β"); const mod: IrModule = { - irVersion: 9, + irVersion: 10, sourceFile: "s.ts", entry: "__main", functions: [ @@ -113,7 +113,7 @@ test("short-circuit: right operand of && only evaluates when left is true", asyn // if (false && sideEffect()) {} ; if (true || sideEffect()) {} // console.log("done") const mod: IrModule = { - irVersion: 9, + irVersion: 10, sourceFile: "l.ts", entry: "__main", functions: [ @@ -161,7 +161,7 @@ test("string params: callee owns and releases; returns transfer ownership", asyn // function greet(who: string): string { return "hi " + who; } // console.log(greet("world")); const mod: IrModule = { - irVersion: 9, + irVersion: 10, sourceFile: "p.ts", entry: "__main", functions: [ diff --git a/packages/compiler/test/fixtures/fib-ir.ts b/packages/compiler/test/fixtures/fib-ir.ts index 7edf588d..2c6f683c 100644 --- a/packages/compiler/test/fixtures/fib-ir.ts +++ b/packages/compiler/test/fixtures/fib-ir.ts @@ -18,7 +18,7 @@ const n = (localId: string): IrExpr => ({ kind: "varRef", localId, type: F64, lo const num = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc }); export const fibModule: IrModule = { - irVersion: 9, + irVersion: 10, sourceFile: "fib.ts", entry: "__main", functions: [ diff --git a/packages/compiler/test/ir.test.ts b/packages/compiler/test/ir.test.ts index b7f47eb2..d898cc1f 100644 --- a/packages/compiler/test/ir.test.ts +++ b/packages/compiler/test/ir.test.ts @@ -22,7 +22,7 @@ test("fib module JSON round-trips", () => { test("validator rejects type mismatches and bad references", () => { const loc = { file: "t.ts", start: 0, end: 0 }; const bad: IrModule = { - irVersion: 9, + irVersion: 10, sourceFile: "t.ts", entry: "__main", functions: [ @@ -83,7 +83,7 @@ test("serializer round-trips ±Infinity and refuses NaN", () => { }); test("deserializer rejects the previous IR version", () => { - const json = serializeModule(fibModule).replace('"irVersion": 9', '"irVersion": 8'); + const json = serializeModule(fibModule).replace('"irVersion": 10', '"irVersion": 9'); expect(() => deserializeModule(json)).toThrow(/version mismatch/); }); diff --git a/packages/compiler/test/llvm-lib-dispatch.test.ts b/packages/compiler/test/llvm-lib-dispatch.test.ts index cf8a125a..509ae61c 100644 --- a/packages/compiler/test/llvm-lib-dispatch.test.ts +++ b/packages/compiler/test/llvm-lib-dispatch.test.ts @@ -19,7 +19,7 @@ const dyn = (): IrExpr => ({ kind: "dynFrom", value: str("value"), type: DYN, lo function libCallModule(fn: IrLibFn, args: IrExpr[], type: IrType): IrModule { return { - irVersion: 9, + irVersion: 10, sourceFile: loc.file, entry: "__main", functions: [{ diff --git a/packages/compiler/test/string-accumulation-emission.test.ts b/packages/compiler/test/string-accumulation-emission.test.ts index 5cd333bc..a6e141a8 100644 --- a/packages/compiler/test/string-accumulation-emission.test.ts +++ b/packages/compiler/test/string-accumulation-emission.test.ts @@ -58,7 +58,7 @@ function fixture(): IrModule { { kind: "varDecl", localId: "other", init: str("other"), loc }, ); return { - irVersion: 9, + irVersion: 10, sourceFile: loc.file, entry: "__main", globals: [{ id: "%g.e.acc", name: "globalAccumulator", type: STRING, mutable: true }], diff --git a/packages/compiler/test/ts7/baselines/order-parity.json b/packages/compiler/test/ts7/baselines/order-parity.json index 62937fdd..2b6d3028 100644 --- a/packages/compiler/test/ts7/baselines/order-parity.json +++ b/packages/compiler/test/ts7/baselines/order-parity.json @@ -6343,6 +6343,18 @@ ], "diags": [] }, + "/tests/corpus/2917-builtin-callable-widths.ts": { + "order": [ + "/tests/corpus/2917-builtin-callable-widths.ts" + ], + "diags": [] + }, + "/tests/corpus/2918-builtin-callable-widths-cjs.ts": { + "order": [ + "/tests/corpus/2918-builtin-callable-widths-cjs.ts" + ], + "diags": [] + }, "/tests/corpus/300-if-else.ts": { "order": [ "/tests/corpus/300-if-else.ts" diff --git a/tests/corpus/2917-builtin-callable-widths.ts b/tests/corpus/2917-builtin-callable-widths.ts new file mode 100644 index 00000000..5f92cc3a --- /dev/null +++ b/tests/corpus/2917-builtin-callable-widths.ts @@ -0,0 +1,53 @@ +// Optional and variadic builtin functions materialize as ordinary static +// closures. Their native ABI remains fixed-width: optional arguments arrive +// as undefined-armed slots and typed rest arguments arrive as one packed +// array. The same typed-rest ABI serves program function values. +import { basename, join, resolve } from "node:path"; +import * as path from "node:path"; +import * as posix from "node:path/posix"; +import * as win32 from "node:path/win32"; +import { platform } from "node:os"; +import * as os from "node:os"; +import { escape, unescape } from "node:querystring"; +import * as querystring from "node:querystring"; + +const base = basename; +console.log(base("/one/two/file.ts")); +console.log(base("/one/two/file.test.ts", ".ts")); +console.log(base("/one/two/file.ts", undefined)); +console.log(base === path.basename, typeof base); + +const joiner = join; +const parts = ["one", "two", "file.ts"]; +console.log(joiner()); +console.log(joiner("one", "two", "file.ts")); +console.log(joiner(...parts)); +console.log(joiner === path.join, typeof joiner); + +const resolver = resolve; +console.log(resolver() === process.cwd()); +console.log(basename(resolver("one", "..", "two", "file.ts"))); + +type PathJoin = (...parts: string[]) => string; +const joins: PathJoin[] = [path.join, posix.join, win32.join]; +for (const fn of joins) console.log(fn("alpha", "beta", "gamma")); + +function chooseJoin(windows: boolean): PathJoin { + return windows ? win32.join : posix.join; +} +console.log(chooseJoin(false)("left", "right")); +console.log(chooseJoin(true)("left", "right")); +console.log(path.posix.join === posix.join, path.win32.join === win32.join); + +function gather(head: string, ...tail: string[]): string { + return `${head}:${tail.join(",")}`; +} +const gatherValue: (head: string, ...tail: string[]) => string = gather; +const gatherers = [gatherValue]; +for (const fn of gatherers) console.log(fn("head", "a", "b", "c")); + +const platformFn = platform; +console.log(platformFn() === process.platform, platformFn === os.platform); +const esc = escape; +const unesc = unescape; +console.log(unesc(esc("a b&c")), esc === querystring.escape, unesc === querystring.unescape); diff --git a/tests/corpus/2918-builtin-callable-widths-cjs.ts b/tests/corpus/2918-builtin-callable-widths-cjs.ts new file mode 100644 index 00000000..6be6b4ad --- /dev/null +++ b/tests/corpus/2918-builtin-callable-widths-cjs.ts @@ -0,0 +1,17 @@ +// CommonJS require bindings share the optional/rest builtin value adapters +// and function identities of namespace and destructured reads. +import { createRequire } from "node:module"; + +const require = createRequire(import.meta.url); +const path = require("node:path") as typeof import("node:path"); +const { basename, join, resolve } = require("node:path") as typeof import("node:path"); + +const base = basename; +const joiner = join; +const resolver = resolve; +const parts = ["cjs", "callable", "value.ts"]; + +console.log(base("/tmp/cjs-value.ts"), base("/tmp/cjs-value.ts", ".ts")); +console.log(joiner(), joiner(...parts)); +console.log(base === path.basename, joiner === path.join, resolver === path.resolve); +console.log(resolver() === process.cwd()); diff --git a/tests/diagnostics/component-fences.ts b/tests/diagnostics/component-fences.ts index 512b8f28..e9ce2939 100644 --- a/tests/diagnostics/component-fences.ts +++ b/tests/diagnostics/component-fences.ts @@ -23,7 +23,8 @@ function report(maybe: Map | undefined): number { } console.log(report(undefined)); -// A rest parameter has no compiled calling convention. +// Typed rest values now compile; retained here as working context for the +// component-type failures below. const sum = (...xs: number[]): number => xs.length; const storedSum = sum; console.log(storedSum(1, 2)); diff --git a/tests/diagnostics/function-forms.ts b/tests/diagnostics/function-forms.ts index 5d588f5b..eb90f3b6 100644 --- a/tests/diagnostics/function-forms.ts +++ b/tests/diagnostics/function-forms.ts @@ -12,8 +12,9 @@ function defaulted_tp(x: T): T { return x; } console.log(defaulted_tp(1)); -// Optional/default/rest parameters compile for DIRECT calls; the fences -// below are the exact-arity value rule and the default-param type limits. +// Optional/default/rest parameters compile for direct calls. Typed rest +// functions are first-class values too; the remaining fence below is for a +// nested defaulted declaration with no contextual completed signature. function optional(a?: number): number { return a === undefined ? 0 : a; } @@ -23,9 +24,9 @@ function defaulted(a: number = 1): number { function rest(...args: number[]): number { return args.length; } -// Optional/defaulted functions as values compile now (the inferred -// binding spells the completed `T | undefined` signature — corpus 1535); -// REST signatures are never spellable as exact-arity func types. +// Optional/defaulted functions as values compile when the inferred binding +// spells the completed `T | undefined` signature (corpus 1535); typed rest +// signatures carry a packed-array ABI (corpus 2917). const restRef = rest; // Union-typed parameter defaults compile now (corpus 1535); kept here as // working context for the fences below. diff --git a/tests/diagnostics/stdlib.ts b/tests/diagnostics/stdlib.ts index 5f13f765..6a192dd9 100644 --- a/tests/diagnostics/stdlib.ts +++ b/tests/diagnostics/stdlib.ts @@ -1,14 +1,11 @@ // tsc-clean misuses of the standard-library surface: each line below is // valid TypeScript against the ambient declarations but outside the // supported lowering (most library, island-backed, Math, string, and -// number functions have no value form; exact-arity path functions are the -// explicit exception; `process` itself is not a first-class value). +// number functions have no value form; descriptor-backed Node functions +// are the explicit exception; `process` itself is not a first-class value). import { readFileSync } from "node:fs"; -import { basename, join } from "node:path"; const read = readFileSync; console.log(read); -const base = basename; console.log(base); -const joiner = join; console.log(joiner); const cwd = process.cwd; const p = process; const env = process.env; diff --git a/tests/harness/__snapshots__/component-fences.ts.txt b/tests/harness/__snapshots__/component-fences.ts.txt index 0adc7aaf..cba204ba 100644 --- a/tests/harness/__snapshots__/component-fences.ts.txt +++ b/tests/harness/__snapshots__/component-fences.ts.txt @@ -109,117 +109,74 @@ component-fences.ts:24:13 - error SC2004: uses of 'report' inherit the blocker o hint: the declaration of 'report' did not compile — fix the diagnostic reported there and these sites clear with it -component-fences.ts:27:7 - error SC2009: values of type '(...xs: number[]) => number' cannot be compiled: the function shape is supported, but its rest parameter 'xs' has no compiled calling convention yet (a compiled signature is fixed-arity) - - 26 | // A rest parameter has no compiled calling convention. - 27 | const sum = (...xs: number[]): number => xs.length; - | ^~~ - 28 | const storedSum = sum; - - hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') - -component-fences.ts:27:13 - error SC1090: functions with rest parameters as values (call them directly) are not supported yet - - 26 | // A rest parameter has no compiled calling convention. - 27 | const sum = (...xs: number[]): number => xs.length; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 28 | const storedSum = sum; - -component-fences.ts:28:7 - error SC2009: values of type '(...xs: number[]) => number' cannot be compiled: the function shape is supported, but its rest parameter 'xs' has no compiled calling convention yet (a compiled signature is fixed-arity) - - 27 | const sum = (...xs: number[]): number => xs.length; - 28 | const storedSum = sum; - | ^~~~~~~~~ - 29 | console.log(storedSum(1, 2)); - - hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') - -component-fences.ts:28:19 - error SC2004: uses of 'sum' inherit the blocker on its declaration - - 27 | const sum = (...xs: number[]): number => xs.length; - 28 | const storedSum = sum; - | ^~~ - 29 | console.log(storedSum(1, 2)); - - hint: the declaration of 'sum' did not compile — fix the diagnostic reported there and these sites clear with it - -component-fences.ts:29:13 - error SC2004: uses of 'storedSum' inherit the blocker on its declaration - - 28 | const storedSum = sum; - 29 | console.log(storedSum(1, 2)); - | ^~~~~~~~~ - 30 | - - hint: the declaration of 'storedSum' did not compile — fix the diagnostic reported there and these sites clear with it - -component-fences.ts:34:7 - error SC2009: values of type '() => WeakMap' cannot be compiled: the function shape is supported, but its return type 'WeakMap' does not compile - - 33 | // Node's ReferenceError at the root instead). - 34 | const makeWeak = (0 as unknown) as () => WeakMap; - | ^~~~~~~~ - 35 | const storedMake = makeWeak; - - hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') - component-fences.ts:35:7 - error SC2009: values of type '() => WeakMap' cannot be compiled: the function shape is supported, but its return type 'WeakMap' does not compile - 34 | const makeWeak = (0 as unknown) as () => WeakMap; - 35 | const storedMake = makeWeak; - | ^~~~~~~~~~ - 36 | console.log(storedMake()); + 34 | // Node's ReferenceError at the root instead). + 35 | const makeWeak = (0 as unknown) as () => WeakMap; + | ^~~~~~~~ + 36 | const storedMake = makeWeak; hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') -component-fences.ts:35:20 - error SC2004: uses of 'makeWeak' inherit the blocker on its declaration +component-fences.ts:36:7 - error SC2009: values of type '() => WeakMap' cannot be compiled: the function shape is supported, but its return type 'WeakMap' does not compile - 34 | const makeWeak = (0 as unknown) as () => WeakMap; - 35 | const storedMake = makeWeak; + 35 | const makeWeak = (0 as unknown) as () => WeakMap; + 36 | const storedMake = makeWeak; + | ^~~~~~~~~~ + 37 | console.log(storedMake()); + + hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') + +component-fences.ts:36:20 - error SC2004: uses of 'makeWeak' inherit the blocker on its declaration + + 35 | const makeWeak = (0 as unknown) as () => WeakMap; + 36 | const storedMake = makeWeak; | ^~~~~~~~ - 36 | console.log(storedMake()); + 37 | console.log(storedMake()); hint: the declaration of 'makeWeak' did not compile — fix the diagnostic reported there and these sites clear with it -component-fences.ts:36:13 - error SC2004: uses of 'storedMake' inherit the blocker on its declaration +component-fences.ts:37:13 - error SC2004: uses of 'storedMake' inherit the blocker on its declaration - 35 | const storedMake = makeWeak; - 36 | console.log(storedMake()); + 36 | const storedMake = makeWeak; + 37 | console.log(storedMake()); | ^~~~~~~~~~ - 37 | + 38 | hint: the declaration of 'storedMake' did not compile — fix the diagnostic reported there and these sites clear with it -component-fences.ts:43:7 - error SC2009: values of type 'Holder' cannot be compiled: the record shape is supported, but its member 'cache' has type 'WeakMap', which does not compile - - 42 | } - 43 | const held = (0 as unknown) as Holder; - | ^~~~ - 44 | const kept = held; - - hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') - component-fences.ts:44:7 - error SC2009: values of type 'Holder' cannot be compiled: the record shape is supported, but its member 'cache' has type 'WeakMap', which does not compile - 43 | const held = (0 as unknown) as Holder; - 44 | const kept = held; + 43 | } + 44 | const held = (0 as unknown) as Holder; | ^~~~ - 45 | console.log(kept.label); + 45 | const kept = held; hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') -component-fences.ts:44:14 - error SC2004: uses of 'held' inherit the blocker on its declaration +component-fences.ts:45:7 - error SC2009: values of type 'Holder' cannot be compiled: the record shape is supported, but its member 'cache' has type 'WeakMap', which does not compile - 43 | const held = (0 as unknown) as Holder; - 44 | const kept = held; + 44 | const held = (0 as unknown) as Holder; + 45 | const kept = held; + | ^~~~ + 46 | console.log(kept.label); + + hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') + +component-fences.ts:45:14 - error SC2004: uses of 'held' inherit the blocker on its declaration + + 44 | const held = (0 as unknown) as Holder; + 45 | const kept = held; | ^~~~ - 45 | console.log(kept.label); + 46 | console.log(kept.label); hint: the declaration of 'held' did not compile — fix the diagnostic reported there and these sites clear with it -component-fences.ts:45:13 - error SC2004: uses of 'kept' inherit the blocker on its declaration +component-fences.ts:46:13 - error SC2004: uses of 'kept' inherit the blocker on its declaration - 44 | const kept = held; - 45 | console.log(kept.label); + 45 | const kept = held; + 46 | console.log(kept.label); | ^~~~~~~~~~ - 46 | + 47 | hint: the declaration of 'kept' 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/__snapshots__/function-forms.ts.txt b/tests/harness/__snapshots__/function-forms.ts.txt index 8f065833..c49a3fac 100644 --- a/tests/harness/__snapshots__/function-forms.ts.txt +++ b/tests/harness/__snapshots__/function-forms.ts.txt @@ -1,22 +1,6 @@ -function-forms.ts:29:7 - error SC2009: values of type '(...args: number[]) => number' cannot be compiled: the function shape is supported, but its rest parameter 'args' has no compiled calling convention yet (a compiled signature is fixed-arity) +function-forms.ts:38:3 - error SC1090: functions with optional or defaulted parameters as values, except where the target type spells the completed signature with required parameters (a '(x?: T) => R' function flows into a '(x: T | undefined) => R' slot, and a package/'any' slot takes any signature that can cross the island boundary; otherwise call the function directly) are not supported yet - 28 | // REST signatures are never spellable as exact-arity func types. - 29 | const restRef = rest; - | ^~~~~~~ - 30 | // Union-typed parameter defaults compile now (corpus 1535); kept here as - - hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') - -function-forms.ts:29:17 - error SC1090: functions with rest parameters as values (call them directly) are not supported yet - - 28 | // REST signatures are never spellable as exact-arity func types. - 29 | const restRef = rest; - | ^~~~ - 30 | // Union-typed parameter defaults compile now (corpus 1535); kept here as - -function-forms.ts:37:3 - error SC1090: functions with optional or defaulted parameters as values, except where the target type spells the completed signature with required parameters (a '(x?: T) => R' function flows into a '(x: T | undefined) => R' slot, and a package/'any' slot takes any signature that can cross the island boundary; otherwise call the function directly) are not supported yet - - 36 | function outer(): number { - 37 | function inner(n = 2): number { + 37 | function outer(): number { + 38 | function inner(n = 2): number { | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 38 | return n; \ No newline at end of file + 39 | return n; \ No newline at end of file diff --git a/tests/harness/__snapshots__/stdlib.ts.txt b/tests/harness/__snapshots__/stdlib.ts.txt index 49c9396e..d334d17f 100644 --- a/tests/harness/__snapshots__/stdlib.ts.txt +++ b/tests/harness/__snapshots__/stdlib.ts.txt @@ -1,55 +1,41 @@ -stdlib.ts:9:40 - error SC1090: a builtin function alias as an escaping value (call 'read' directly) is not supported yet +stdlib.ts:8:40 - error SC1090: a builtin function alias as an escaping value (call 'read' directly) is not supported yet - 8 | - 9 | const read = readFileSync; console.log(read); - | ^~~~ - 10 | const base = basename; console.log(base); + 7 | + 8 | const read = readFileSync; console.log(read); + | ^~~~ + 9 | const cwd = process.cwd; -stdlib.ts:10:36 - error SC1090: a builtin function alias as an escaping value (call 'base' directly) is not supported yet +stdlib.ts:9:13 - error SC1090: process methods as values (call 'cwd' directly) are not supported yet - 9 | const read = readFileSync; console.log(read); - 10 | const base = basename; console.log(base); - | ^~~~ - 11 | const joiner = join; console.log(joiner); - -stdlib.ts:11:34 - error SC1090: a builtin function alias as an escaping value (call 'joiner' directly) is not supported yet - - 10 | const base = basename; console.log(base); - 11 | const joiner = join; console.log(joiner); - | ^~~~~~ - 12 | const cwd = process.cwd; - -stdlib.ts:12:13 - error SC1090: process methods as values (call 'cwd' directly) are not supported yet - - 11 | const joiner = join; console.log(joiner); - 12 | const cwd = process.cwd; + 8 | const read = readFileSync; console.log(read); + 9 | const cwd = process.cwd; | ^~~~~~~~~~~ - 13 | const p = process; + 10 | const p = process; -stdlib.ts:15:13 - error SC1090: Math methods as values (call 'floor' directly) are not supported yet +stdlib.ts:12:13 - error SC1090: Math methods as values (call 'floor' directly) are not supported yet - 14 | const env = process.env; - 15 | const flo = Math.floor; + 11 | const env = process.env; + 12 | const flo = Math.floor; | ^~~~~~~~~~ - 16 | const upper = "abc".toUpperCase; + 13 | const upper = "abc".toUpperCase; -stdlib.ts:16:15 - error SC1090: string methods as values (call 'toUpperCase' directly) are not supported yet +stdlib.ts:13:15 - error SC1090: string methods as values (call 'toUpperCase' directly) are not supported yet - 15 | const flo = Math.floor; - 16 | const upper = "abc".toUpperCase; + 12 | const flo = Math.floor; + 13 | const upper = "abc".toUpperCase; | ^~~~~~~~~~~~~~~~~ - 17 | const fix = (1.5).toFixed; + 14 | const fix = (1.5).toFixed; -stdlib.ts:17:13 - error SC1090: number methods as values (call 'toFixed' directly) are not supported yet +stdlib.ts:14:13 - error SC1090: number methods as values (call 'toFixed' directly) are not supported yet - 16 | const upper = "abc".toUpperCase; - 17 | const fix = (1.5).toFixed; + 13 | const upper = "abc".toUpperCase; + 14 | const fix = (1.5).toFixed; | ^~~~~~~~~~~~~ - 18 | const pf = parseFloat; + 15 | const pf = parseFloat; -stdlib.ts:18:12 - error SC1090: library functions as values (call 'parseFloat' directly) are not supported yet +stdlib.ts:15:12 - error SC1090: library functions as values (call 'parseFloat' directly) are not supported yet - 17 | const fix = (1.5).toFixed; - 18 | const pf = parseFloat; + 14 | const fix = (1.5).toFixed; + 15 | const pf = parseFloat; | ^~~~~~~~~~ - 19 | \ No newline at end of file + 16 | \ No newline at end of file