`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>
`module.exports.Agent = require('./lib/agent')`, where agent.js ends with
`module.exports = Agent`, is the same alias plumbing as
`module.exports.Agent = Agent`: importers resolve to the class declaration
and the class registry applies, instead of storing a class value (which
classes inheriting a runtime-provided constructor, such as EventEmitter
subclasses, do not have). The statement still evaluates the required
module at its position.
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>
An identifier-minified esbuild bundle keeps the __commonJS helper's
shape but loses the names the recognizers verify; an ES-module bundle of
CommonJS chunks has no require to route them through. Both now refuse
with a message naming the construct.
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>
The interop rewrite certifies a file whose esbuild export object is
installed before anything can run another module's code; the CommonJS
cycle rule then treats its tail table as settled at that point. A
top-level plain read of a cycle member's function-declaration export is
exact as well: Node and the compiled read see the same function.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Recognize esbuild's __commonJS helper (keyed and arrow forms, the
Object.keys spelling of 0.11) and move every chunk body into a virtual
sibling CommonJS module, so require_x() becomes a require of that module
and the ordinary CommonJS machinery compiles it. Also accept minified
helper declarations that share one var statement, counter-suffixed
helper names inside bundled bundles, and treat .cjs files as CommonJS
in the interop rewrite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test used `const { length } = [1, 2] as number[]` as its coverage
blocker. That compiles now, so no diagnostic was left to replay. The blocker
destructures a `number[] | string` union instead, which keeps the SC1031
object-pattern fence. The comment-only replay and code-edit relower
assertions are unchanged.
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.
A worker message round trip cost about 12 µs, almost all of it in two
blocked waits: the poster signaled the waiting thread while it still held
the mailbox lock, so the woken thread went back to sleep on the lock, and
every wait paid a sleep and a wakeup. scr_mailbox_post and close now
signal after the unlock, and scr_mailbox_wait spins for up to 20 µs (or
the wait's timeout, if shorter) on a flag the queue keeps before it
blocks. A process that may run on only one CPU never spins.
A new bench:runtime workload, worker-messages, posts 100,000 round trips
between the main thread and a worker: 1296 -> 360 ms.
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>
three.js's Color constructor ends with `return this.set(r, g, b)`, and
every reachable `set` (Color's, and Color4's override returning
`super.set(...)`) returns `this`. The return analysis classified it as
returning another instance and refused `class Color4 extends Color`.
A return that is `this`, or a `this.m(...)`/`super.m(...)` call whose
every reachable implementation returns only `this`, a primitive or
nothing, now keeps the ordinary lowering, so such classes need no
result slot and subclasses construct through super() as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage S4 of the two-tier design: dynamic-tier code reads, writes and
calls properties through per-site inline caches keyed on hidden-class
shapes.
- Runtime: scr_js_object.c (dynTier) attaches shapes to the existing
checked objects: a shape is a node of the thread's transition tree
over key sequences, computed on an object's first cache miss, and
objects past 64 keys stay unshaped (dictionary mode). Attributes and
the prototype are tested live on every hit. The dyn-tier build of
scr_json.c (SCR_JS_TIER) resets a shape on every key-set change;
typed programs keep linking the ordinary build.
- Sites: ir/js-ic.ts recognizes the lowering's property helpers by
structure; calls to them from dynamic-tier functions, and method
dispatch outside module initializers, become sites with a zeroed
thread-local ScrJsIc. backend/llvm/js-ic.ts tests entry 0 inline
(own property or one prototype link), probes out of line, and keeps
the helper as the noinline cold slow path. Array length and dense
number-key element reads are inline.
- Numbers: jsval reads of number properties are immediates, and a jsval
number stored over an unshared number cell updates it in place; the
jsval pass feeds cache stores jsvals and stops boxing discarded
compound-assignment results. `%` and bitwise operators get integer
paths.
Static functions are untouched: typed programs compile to byte-identical
LLVM and executables, and every static function of programs with
dynamic code is byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture builds and serializes an IR module by hand. Since IR version
17 serializes function tiers instead of deriving them on load, the loaded
module's main, which performs dynamic operations, validated as a static
function. Assign tiers as lowering does before serializing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>