The native compiler failed on corpus programs that the Node-hosted
compiler compiles. Four compiler causes:
- A record that width-lifts into several members of a discriminated
union had no conversion plan. As a field of a larger record, that
removed the outer member it belonged to, and the outer record could
then lift into the only member left: a `recordSet` statement built with
a nested `recordGet` literal became a `break` statement, and a switch
on `kind` read it with the `recordSet` layout. The native compiler
stopped at the narrowing guard on 5585-record-results-inlined.ts and
similar programs. Such a record now converts through the discriminant:
each member it width-lifts into is a route, and the value's
discriminant selects the member at run time, as same-layout members
already do.
- Three compiler paths asserted a value that can be null, and the native
compiler threw "null is not representable in the target union":
stableCreateRequireBindingReason7 (`cache.get(decl)!` from a cache that
stores null for stable createRequire bindings; 2631-create-require,
2932-create-require-program), the Int32 contract check (`catchLocalId!`
for a bindingless `catch {}`; 4970-int32-call-abi) and
StringIndexBounds.binding (a memo that stores null). The caches are
read without assertions and the catch test checks for null.
- scriptc adopts alwaysStrict, esModuleInterop and
allowSyntheticDefaultImports from a project's tsconfig, but the TS 7
SDK schema no longer declares them. A native compiler keeps only
declared fields when it builds or parses a typed record, so these
options were dropped, and the default-import check refused
`import fs from "node:fs"` (SC1012, 2391-default-builtin-imports and the
3055 to 3057 module-setting variants). The options a project reports
are parsed with these fields declared, and Ts7CompilerOptions uses the
same type.
- An object-literal method kept its inferred return type where an arrow
function or function expression takes the return type of its
contextual signature. For a method that returns an array of statement
literals, a `varDecl` literal with `init: null` was converted to the
member whose `init` holds only expressions, and the conversion threw
(Array.fromAsync's array loop: 5854-array-from-async,
5855-array-from-async-js). A synchronous object-literal method now
takes its slot from the property of the same name on the literal's
contextual type and adopts its return the same way.
Adds corpus 6022 and 6024. 2391, 2631, 3055, 4970, 5585 and 5854 run in
the native-frontend self-host check, which now also links the dyn-async
unit when the program uses it, as the production build does.
node:test changes, matching Node 24.22:
- mock.fn() and t.mock.fn() without an original return undefined and
record the arguments of each call, whatever the callers pass. The
original and the initial implementation are a function that returns
undefined, so restore() brings that function back.
- mock.fn<F>() with an explicit function type records F's parameters
when F's return type is void or includes undefined. A return type
without undefined, such as mock.fn<() => number>(), throws SC2020.
mock.fn()'s default type, (...args: any[]) => undefined, maps to the
checked-dynamic rest ABI used for program-declared (...args: unknown[])
functions: the calls log records the argument array, and the mock's
wrapper spreads it into the implementation.
A function whose fixed parameters are all unknown or any, or that has
none, now converts to a checked-dynamic rest signature without fixed
parameters, such as (...args: unknown[]) => void: each call passes the
arguments in order, undefined for the ones the caller left out. This is
how () => {} reaches mockImplementation and mockImplementationOnce, and
it applies to any such slot in TypeScript programs, which threw SC1090
before.
The mock-fn-default fixture and corpus program 5816 compare with Node,
plain and with SCRIPTC_SAN=1.
paramShapes, which builds the parameters of an instantiated signature
(generic call instances and the erased instance that a method value or
a reflected prototype slot uses), refused an optional parameter unless
its type had an undefined union arm. An interface view implemented by a
class (`handle?: Handle | null`) and an erased type parameter map to the
checked-dynamic type, which holds undefined directly; paramShape, the
declared-signature path, already accepts it.
Direct calls of such a method failed with "optional parameters of type
'unknown'", and reflecting its class prototype from the dynamic-dispatch
fixpoint raised the same refusal outside any statement and crashed the
compiler with an uncaught PoisonError. paramShapes now treats the
parameter as omittable, so the instance lowers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The native CLI built from main aborted on the record-boundaries and
value-objects benchmarks with "a narrowed value does not have the type its
narrowing proved". record-results.ts builds recordSet statements in a map
callback whose value field is a recordGet literal. Converting that array
into IrStmt[] chose the break/continue arm: the literal also fits the
shorter varRef-like expression arms, so its conversion into IrExpr was
ambiguous, and an ambiguous field conversion made widthLiftPlan drop every
arm that needed it. The one arm left was break/continue, reached by
dropping obj, shapeId, field and value. The record kept kind "recordSet"
under the break/continue tag, so mapStmtChildren's narrowing guard aborted.
IR record shapes keep the discriminant as a plain string, so a record's
layout alone cannot pick its arm. When a record that carries a
discriminated union's discriminant field fits several arms, widthLiftPlan
now plans a route through every arm it converts into, selected at run time
by the discriminant's value, as JavaScript does (a value no arm names
throws the catchable TypeError). Conversions that resolved before are
unchanged; the zero-cost gate shows identical IR, LLVM and executables for
the benchmarks and the corpus, and the native CLI now compiles every
benchmark workload.
cjs-lexer-invisible and hoisted-workspace compiled only once their
untyped dependencies were selected; both now match Node in both lanes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An untyped package another package's JavaScript imports is selected
whether resolution answers no declarations or its JavaScript. A
DefinitelyTyped twin qualifies by identity (@types/<mangled name>):
the declarations only check the program, and the JavaScript compiles
with its inferred surface, so a twin that disagrees with the shipped
JavaScript fails the build at the use rather than compiling against it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Function-local classes whose private names are used only on `this` now
compile, so the two diagnostics fixtures test a brand check on another
object instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`new Agent({ keepAliveTimeout: 10 })` against undici's
`constructor({ factory = defaultFactory, maxOrigins = Infinity, ...options }
= {})` failed with SC2002: the literal was laid out against the record the
checker infers for the pattern, which lacks the properties the rest
element collects.
When a TypeScript object literal flows into a checked-value slot whose
contextual type is a JavaScript destructuring pattern with a rest element
(or that pattern's `= {}` default), and the literal names a property the
type does not declare, the literal is built as the plain object the
JavaScript side receives. Every other literal keeps its previous lowering.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Higher-order wrappers over `(...args: any) => any` that forward `arguments`
with `fn.apply(this, arguments)` and type their options with
`Parameters<F>` / `ReturnType<F>`, and their rest-tuple rewrite, did not
compile. Generic calls already monomorphize per call site; these pieces now
resolve to that site's concrete signature:
- Rest parameters typed as fixed tuples (`...args: [A, B]`, `...args: A`
bound to a tuple, `...args: Parameters<F>`) flatten to positional
parameters, so `(...args: [A, B]) => R` and `(a: A, b: B) => R` share one
function type. Trailing optional positions become `T | undefined` slots.
- `Parameters<F>`, `ReturnType<F>`, `Awaited<ReturnType<F>>` and the
`ReturnType<F> extends PromiseLike<infer U> ? U : …` spelling resolve
through F's binding.
- A parameterless function expression reading `arguments` lowers against
the fixed signature its value is asserted to (or contextually typed as);
`arguments` is that signature's tuple.
- `f.apply(thisArg, args)` and `f(...args)` over a forwarded tuple call
directly, passing `thisArg` as the receiver. A tuple that is only
forwarded is never allocated.
- `fn.name` on a function parameter folds to the call site's static name;
the instantiation key carries it.
Also: optional callback fields narrowed at the call (`o.cb ? o.cb(x) : …`)
compile, effect-free surplus arguments to fixed-arity function values are
dropped, and `arguments.length` no longer counts a `this` parameter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An __esm initializer becomes a run-once closure over its body in the
bundle's own scope. Interop bindings it assigns once from require()
become that require at the assignment, with their member reads respelled
as reads of the required module; a namespace read only through members
becomes an object literal of getters over the module's live bindings.
Asynchronous initializers, initializer results, and namespaces kept as
values (a memoized lazy loader) stay refused with a message naming them.
The default selection now also compiles a package typed by a
DefinitelyTyped twin that describes its installed release (same major,
or major.minor below 1.0), and untyped dependencies of compiled
packages. A twin for another release is refused with both versions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each evaluation of a class declared inside a function creates fresh
private names, while the compiled class shares one layout across
evaluations, so any private member refused the class. undici's webidl
createIterator declares such a class (FastIterableIterator) at module load.
The two agree when every private name is used only on `this` inside the
class's own instance members (`this.#x`, `#x in this`), never inside a
nested class or non-arrow function, and the class has no static members:
each instance then meets only the methods of its own evaluation. Those
classes now compile; everything else keeps the refusal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
undici keeps two key tables that both hold a `Symbol('close')`, and
MockClient inherits a slot under one while declaring a method under the
other. Symbol slots were named by their printable spelling, so the two
collided ("distinct symbol keys sharing the printable name").
A key-table key whose description an earlier symbol identity already holds
now takes an ordinal in its internal slot name; inspect drops the ordinal
and prints Node's spelling for both. Const keys keep their plain names, so
programs that compiled before are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Interop the rewrite cannot finish now refuses its package only when the
compiled program can load that file: the module order closed over every
literal require() and import() and, in package JavaScript, every import
and export declaration (a computed specifier keeps every file loadable).
A package reached through a leaf subpath and types from its main entry
stays static. Auto mode also no longer refuses a whitespace/syntax
minified bundle whose interop helpers keep their names: the recognizers
judge it instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A CommonJS function with parameters that reads `arguments` was refused
because sloppy-mode `arguments` aliases the parameters. Strict code (a
'use strict' prologue on the file or an enclosing function, or a class
body) and functions with non-simple parameter lists (defaults or
destructuring) get Node's unmapped object, whose elements never alias the
parameters, so they now take the hidden arguments array the way ES module
functions and class methods already do. undici's
`webidl.argumentLengthCheck(arguments, 1, 'getCookies')` compiles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`class TracingAgent extends Agent { dispatch(opts: DispatchOptions,
handler: DispatchHandler): boolean { ... super.dispatch(...) } }` over an
npm-static JavaScript Agent was refused: the override's typed parameters
had to match the base's untyped (checked-value) slots exactly.
A TypeScript override of a JavaScript method now keeps the base's ABI.
Each untyped base slot stays a checked value, and the override's typed
parameter narrows from it at entry with a checked conversion; an untyped
base return takes the override's value boxed; trailing base slots the
override omits are ignored. Calls through base-typed receivers dispatch
to the override, and calls on the subclass type see the same ABI.
The corpus program builds the undici dispatcher shape as an npm package:
an EventEmitter-rooted base, symbol-keyed fields from a key table, a
computed accessor, subclasses across modules re-exported through require,
and TypeScript subclasses overriding `dispatch`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A JavaScript package often keeps its private symbols in one module,
`module.exports = { kClosed: Symbol('closed'), ... }`, and its classes
declare `[kClosed] = false` fields and `[kDispatch]()` methods with keys
destructured from that table (undici's core/symbols.js). Those keys were
not stable, so every such class was rejected, and with it every subclass
in other modules ("extending classes not declared in the program").
A property of a JS module's effective top-level `module.exports = {...}`
literal initialized with `Symbol(<constant>)` or `Symbol.for(<constant>)`
is now a stable class key, as a module-scope const is, provided nothing
before the table can run code (a require there could close a cycle and
publish the empty initial exports object), no earlier property can run
code, and nothing writes the property or replaces it through `exports`.
The property's own declaration symbol is the identity every requirer
shares, and the table's export global supplies the runtime key for
dynamic lookups (a base method calling a subclass's symbol-keyed method).
The checker types such keys as plain `symbol`, so it answers `this[k]`
with the union of every symbol-keyed property of the class. Reads through
table keys in JS files now take the field's own layout type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
new WebAssembly.Memory, Table and Global construct the runtime objects an
instance exports, with Node 24's descriptor order, defaults, conversions
and messages (V8's EnforceUint32 errors, the v128 and shared-memory
errors; a valid shared memory is refused with a TypeError until threads),
so either kind can be imported. Imports of memories, tables and globals
now check limits, types and mutability with V8's LinkError messages, and
exports keep V8's identity (imported objects re-export as themselves, one
object per memory and table). The classes called without new throw Node's
TypeError.
WebAssembly.Module.imports, exports and customSections read the
descriptor (ABI v2). Custom sections are carried only by programs that
call customSections. Every function a module references reaches
JavaScript as a callable function: the translator emits one call thunk
per signature, so table.get and funcref globals return the module's
unexported functions too, and exported functions can be stored in tables
and globals from any instance. Discovery also finds .so WebAssembly
shared libraries at resolved paths.
Fixes to W3/W4 found on the way: externref Table.grow fills with
undefined; Memory.grow/Table index errors and funcref messages match V8;
an invalid funcref global import is a LinkError; global.valueOf() on a
WebAssembly.Global-typed receiver answers the value; explicit-bounds
memories keep the blocks grow replaced, so old ArrayBuffers never
dangle.
This unblocks quickjs-wasi's extension loader: quickjs-wasi with its url,
encoding, headers and structured-clone extensions runs through its own
glue identically to Node, and pi codemode's static coverage goes from 83%
to 84% with no WebAssembly blocker left.
Evidence: differential corpus programs for the API (webassembly-jsapi),
a 261-test port of WPT wasm/jsapi (webassembly-wpt-jsapi), quickjs-wasi's
dist/extensions.js run unmodified against dylink-shaped modules
(webassembly-dylink), and the 2.0 spec suite's modules that import the
spectest memory and table (new webassembly-spec-imports group plus the
memory and table groups); all match Node in the plain and sanitized
lanes. The zero-cost gate leaves every non-WebAssembly program
byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unit tests run each split bundle against its original under Node and
pin the refusal reasons; npm-static tests compile the new cases and
compare them with Node byte for byte.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Real esbuild output (0.25 plain and whitespace/syntax minified, 0.17)
of one bundle with lazily and eagerly required chunks, a chunk that is
itself an esbuild dist, an external through __toESM, a .cjs bundle in a
type-module package, and per-file esbuild CommonJS cycles entered from
either side. Split chunks are require-only modules: their whole export
table is their surface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jose 6.2.12 now compiles natively through its root entry (`import { jwtVerify,
errors } from "jose"`), from TypeScript and JavaScript, with chained SignJWT
builders and string durations:
- A discarded `a && b;` / `a || b;` statement whose value form fails on its own
result type (jose's secs(): `(!m || m[4] && m[1]) && invalid()`) lowers as
`if (a) b;` / `if (!a) b;`. The value form still lowers first, so statements
that compiled before are unchanged.
- In JavaScript, a number-keyed read on a `T[] | null | undefined` value outside
an optional chain (`m[2]` after a statement ruled `m` non-null) reads the
array arm like any element read: a group that did not participate is
undefined instead of a trap.
- A JavaScript fluent method whose returns all end in `this` (including comma
sequences) narrows a used call result back to the receiver's subclass. A
receiver lowered at an ancestor of the class a call needs is bridged with a
verified downcast instead of an upcast the validator rejected (the
"upcast JWTClaimsBuilder does not extend SignJWT" internal error).
- `typeof navigator` is "object" and `navigator.userAgent` is "Node.js/24", as
in Node 24, for the free name in JavaScript and under @types/node. Both fold
to literals; other members refuse by name.
- TypeScript object destructuring of an `any` result of a JavaScript function
(jose's jwtVerify) reads its members like the member reads that already
compiled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/coverage-fixtures/mixed.ts used `const { length: firstItem } = items`
as its SC1031 example. Object patterns of positions and length over arrays
compile now, so coverage counted 13/13 static. The example now destructures
a union source (array | string), which keeps the SC1031 object-pattern fence
at the same line and column; only the message's source kind changes, from
array-typed to union-typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A JavaScript `var` binding hoists to its function, and its slot was
created with the checker's type before the initializer lowered. When the
initializer was an existing checked-dynamic object (an element of an
array built with push, a find result, the object a function returns),
the record-typed slot held a converted copy: writes through the binding
missed the object that the array or the function's caller still held,
and `===` against it was false. `let` and `const` already lowered the
initializer first and kept such a value checked-dynamic.
A `var` initialized with an existing value now lowers the initializer
before creating the slot, and a checked-dynamic value gets a
checked-dynamic slot. The same applies to `for (var x of ...)`, to array
destructuring with `var`, `let` and `const`, and to file-scope bindings,
whose module-entry `undefined` store follows the slot's final type. A
literal holding such an object builds checked-dynamic for a `var` as it
already did for `let`. TypeScript files and annotated bindings are
unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fs-shims now gets past Buffer.isBuffer on a Uint8Array (fszoo/index.js:35),
which this branch lowers; its first deferred refusal is the existing
SC1101 for a closure passed into an unknown slot (fszoo/index.js:44), so
the expectation records SC1101.
The branch's new corpus programs (1112 and 6200 to 6210) get per-program
cost files in tests/harness/corpus-costs, taken from the differential and
llvm-differential case durations in this branch's Linux CI shards.
A class with a parameterless *[Symbol.iterator]() generator method can now
be iterated by for-of, array spread, Array.from, Math.max and Math.min
spreads, new Set, new Map, Object.fromEntries and Iterator.from. Each site
calls the method once and drives the generator it returns.
obj[Symbol.iterator]() and the other well-known symbol methods called
through element access now call the class's method, so iterator helpers
apply to the result instead of failing on an untyped value.
Writes that can replace a well-known symbol method (a symbol-keyed write,
Object.defineProperty, a prototype write) now enter the method census, and
those classes keep the existing checked or refused paths.
The zero-cost gate's I3 check (a fully typed bench workload defines no
dynamic-tier runtime symbol) runs only when someone runs the gate, and on
Darwin it never matched: Mach-O names carry a leading underscore, so no
symbol had a DYNAMIC_TIER_PREFIXES prefix.
DYNAMIC_WORKLOADS and the nm function-name reader move to
zero-cost-compare.mjs, which now drops the Mach-O underscore. A harness
test builds every bench program on Linux and fails when a workload outside
DYNAMIC_WORKLOADS links a dynamic-tier symbol, or when a listed workload
no longer links any, so the list shrinks as leaks close.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The event loop called scr_children_pending, scr_children_poll and the other
child hooks directly on native targets, so every async program linked the
child-process unit (the reap pass, IPC service, pipe readers and writers).
The loop now reaches them through a nullable ScrLoopChildren table that the
child unit installs with its first spawned child or IPC channel, the shape
the events, net, dgram and fs.watch hooks already use. The WASI stubs for
the old direct calls are removed.
rl[Symbol.dispose]() closes the interface, as Node's dispose method
calls close(). rl instanceof readline.Interface and
readlinePromises.Interface answer which module created the interface:
rl.create's mode marks readline/promises interfaces, and the fallback
declarations declare both Interface classes as values.
In JavaScript, createInterface's input and output can be checked-dynamic
values (a function's result, an object's member, an element that may be
undefined): rl.inputFromDyn and rl.outputFromDyn unbox the stream they
hold, an output of null or undefined means no output, and other values
are refused.
In JavaScript, question() and setPrompt() accept a value that is not a
string. When such a prompt is written, an interface that is not a
terminal throws ERR_INVALID_ARG_TYPE as Node's kWriteToOutput does, and
the readline/promises question rejects with it; the question stays
pending and the previous prompt returns when it is answered. A terminal
interface writes String(value) of a primitive.
Corpus 5876-5878 match Node v24.22.0.
symbol.declarations, symbol.valueDeclaration and signature.declaration
return nodes, as tsc-ts's Symbol and Signature do, instead of the
facade's declarationsOf, valueDeclarationOf and signatureDeclaration
resolving their handles. Each symbol and signature resolves its handles
in its own project on first read and keeps the nodes. The handles stay
available as declarationHandles, valueDeclarationHandle and
declarationHandle.
A program's checker and its symbols belong to the same project, so the
nodes are the ones the facade returned. The frontend's 432 calls of the
three facade methods become property reads, and the methods and their
caches go.
Each record shape's enumeration table now follows the key order its object
literals, spreads, rest bindings and record copies create, and appends
optional keys assigned after construction. Shapes whose values are built
in more than one order keep their declaration order and SC6002.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crypto.subtle.importKey('raw', ...) for HMAC keys, HMAC sign and verify,
and the SHA-1/SHA-2 digests compile through the crypto global, with
CryptoKey values as native handles. scr_webcrypto.c is only the WebCrypto
layer (Web IDL conversion, algorithm normalization, the CryptoKey handle,
promise wrapping and Node's TypeErrors and DOMExceptions) over node:crypto's
digest core and deferred promise queue. It links only into programs that
call these methods (the new webcrypto link feature). Other algorithms, the
jwk format and the other SubtleCrypto methods are SC2020 refusals.
The real jose@6.2.12 HS256 path (SignJWT, jwtVerify) now runs natively. It
also needed four compiler fixes: object patterns of positions and length
over arrays, the Uint8Array base64/hex feature probes, a compile-time
String.fromCharCode.apply, and structuredClone of a parameter whose default
is {}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>