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>
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>
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 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>
This branch lowers toPrecision statically, but STATIC_NUMBER_METHODS still
lacked it, so the regenerated manifest kept stdlib.number.toPrecision as
unsupported/SC2012 and a determinism fence on it had no reach detector.
List it with its num.toPrecision witness and 0 to 1 arguments, like
toFixed, and regenerate the manifest; the compatibility outputs are
unchanged.
library-profile's remediation test used that id as its SC2012 fence;
it now names the SC2012 code family (diagnostic.sc2012), so the explicit
remediations key still competes with fence-supplied SC2012 text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A build now translates every WebAssembly module whose bytes it can see
(W2's discovery) into LLVM IR with a TypeScript translator
(packages/compiler/src/wasm/module.ts, translate.ts) and appends it to the
program module, so the existing LLVM helper optimizes, partitions and
caches it with the program. The program also emits scr_wasm_registry, a
portable table of module descriptors (scr_wasm_abi.h). At run time,
WebAssembly.compile, instantiate, validate, new Module and new Instance
find the translated module by the SHA-256 of its bytes; bytes the build did
not see reject with a CompileError. There is no interpreter and no JIT.
The runtime (scr_wasm.c, link-gated by the new `wasm` feature, handle tags
inside W0's headroom) implements the JavaScript API once, over
descriptors, with Node's semantics and messages: exports (functions,
memories, tables, globals), imports (functions, immutable globals, and
memories, tables and globals other instances export), every trap as
WebAssembly.RuntimeError, the CompileError, LinkError and RuntimeError
classes, compile and instantiate settling on a later turn, and
globalThis.WebAssembly as a value. On 64-bit Linux and macOS, memories sit
in 8 GiB guard reservations and a SIGSEGV/SIGBUS handler turns faults into
traps or stack exhaustion; async fibers get a guard page on first use.
Sanitizer builds and Windows check bounds and the stack explicitly. A
WebAssembly.Module crosses to workers through W0's transfer hook (W4).
W1's refusals become lowerings; the Memory, Table and Global constructors,
Module.imports/exports/customSections and the streaming functions still
refuse with SC2020. The globals: WebAssembly compatibility row moves to
partial.
Evidence: differential corpus programs for the API, TypeScript use and the
codemode worker shape, plus five programs executing about 9,300 commands
of the WebAssembly 2.0 spec suite; all match Node in the plain and
sanitized lanes. quickjs-wasi runs through its own glue, matching Node
(apart from a pre-existing non-wasm fence) and on par with or faster than
V8. The zero-cost gate leaves every non-wasm program byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frontend's node lists become node lists, { pos, end, nodes }, the
shape of tsc-ts's NodeList, so that the frontend can later read tsc-ts's
own nodes. AstNode's list getters return an AstNodeList, ts.NodeArray<T>
is that class, and the frontend reads a list's elements through nodes.
The lists already carried their positions on the wire.
The class is the type, not a structural interface, because native code
represents a class and a record differently.
A decoded file keeps each list's nodes array, as before, and each read
of a list field returns a new AstNodeList over it, so the file holds no
more objects than before. nodes keeps its identity; a list object is not
the same object on each read.
The change is mechanical: a type-aware rewrite of each list field read,
plus hand edits where lists are read through casts, generic accessors
or test mocks. The generated getters and types follow from
generate-ts7-ast-schema.mjs. AstFile and Ts7Session drop their unused
listMetadata hook.
node:test changes, matching Node 24.22:
- getTestContext() returns the context of the test, suite or hook whose
code is running: undefined at module top level, the root's context in
module-level hooks, the suite's in a suite body and in the suite's
hooks, and the test's in its body. In the afterEach and t.after hooks
of a subtest cancelled by its parent's timeout, it returns each hook's
owner.
- The context rides the AsyncLocalStorage snapshot under a reserved id,
so it follows a test across its awaits, into the async functions,
promise callbacks, timers and other callbacks it schedules, and into
concurrent subtests, which each see their own.
- The root's fullName is "<root>", as its name is.
- Optional chains over a test context, such as getTestContext()?.name,
lower.
AsyncLocalStorage enter and restore move into the always-linked async
core so the test runner can use them. The snapshot holds the node's
index in a table of live nodes, so a snapshot that outlives its node
answers undefined.
The get-test-context and get-test-context-cancelled fixtures compare
the contexts seen at each point with Node, plain and with SCRIPTC_SAN=1.
unshareNodes gives every occurrence of a repeated node its own object.
Nodes with children are copied by mapExprChildren and mapStmtChildren,
which spread the node and keep its type and location objects. Childless
nodes were copied by reparseExpr and reparseStmt, which stringify the
node and parse the text, copying the type and location objects as well;
on the three.js nodes fixture that took about 6 s.
Childless nodes are now spread too. Analyses key their facts by node
identity, which each copy still has of its own. The type and location
objects are shared as they already are in every copied node with
children and in every function without a repeated node. The emitted IR
is unchanged for every corpus program.
unshareNodes takes about 2 s on the three.js nodes fixture instead of
8 s, and compiling it to LLVM text takes 171 s instead of 178 s (median
of five interleaved runs).
everyExprChild, everyStmtChild, mapExprChildren and mapStmtChildren
switch over the node kind: 96 expression and 26 statement string cases,
which the engine tests one after another in source order. Every analysis
and rewrite walks the IR through them, and some of the most common kinds
were near the end: in the three.js nodes module, dynFrom, libCall and
dynCheck make up 27% of expression nodes and were the 64th, 94th and
96th cases.
The cases for the most common kinds now come first, in the order of how
often they occur in the three.js nodes and Effect fixtures. All other
cases keep their order, and no case changes. In those fixtures, seven
expression kinds make up 89% of expression nodes and four statement
kinds make up 88% of statements.
A full child walk of the three.js nodes module takes about 108 ns per
node instead of 154 ns. Compiling it to LLVM text takes 161 s instead
of 180 s (median of five interleaved runs), and the Effect schema
fixture 64 s instead of 69 s. The emitted IR is unchanged.
In a fused helper chain, take(n) sets the chain's done flag as it
delivers its nth value, so nothing is pulled after it. A flatMap later in
the chain tested the same flag in its inner walk and stopped after the
first flattened value: gen().take(2).flatMap((x) => [x, x + 10]) gave
[0, 10] where Node gives [0, 10, 1, 11].
A flatMap stage after a take now holds the flag while it walks its inner
iterable, and sets it again afterwards. A terminal that short-circuits or
a take later in the chain still stops the inner walk.
A compiled generator that reached an untyped value, as in
`function f(it) { return it.map(g) } f(gen())`, refused its iterator
helper calls with SC1071 at run time: the runtime's helpers could not
step a generator's capsule. Array destructuring of untyped values only
handled arrays, strings and bytes, so `const [a, b] = it` threw "is not
iterable" for generators, iterators, Maps and Sets.
Programs that box a synchronous generator and call a helper method or
destructure an untyped value now register, first thing in %main, the
checked getters that read a generator capsule's next and return methods
(scr_dyn_set_generator_members). With them:
- helper calls on a generator capsule run the runtime's helpers, which
step and close the generator through its own next() and return();
- reads of a helper name answer the runtime's helper function, so
`typeof it.map` is "function" and `it.reduce.call(it, f, init)` works;
- destructuring of any other iterable steps it through the iterator
protocol (scr_dyn_iter_take, and rest elements through
scr_dyn_iter_pack), closing an iterator that has values left.
A return() through an untyped value with no argument, or with undefined,
now completes with undefined as the typed .return() does, instead of
converting undefined to the generator's return type. This also fixes
for-of loops over untyped generators with a typed return value that exit
early.
Object.defineProperty with an object-literal descriptor that declares get
or set for a declared class field is refused at compile time with SC2020,
naming the field. JavaScript sources report it as a deferred fence, so
coverage lists the call. Other accessor descriptors still throw SC2020
when the call runs.
The native descriptor test now compiles a data descriptor change on a
declared field and compares the output with Node, and checks that an
accessor descriptor stays fenced.
`function make(C: typeof EventEmitter) { return new C(); }`,
`const kinds: ErrorConstructor[] = [TypeError, RangeError]` and
`(typeof Readable)[]` stopped at SC2020 (the type has no mapping) or
SC1090 (builtin classes as values). These classes are now class values:
the name in a TypeScript file (or a const alias of it) is the class
object, `typeof EventEmitter`, `typeof Readable` and ErrorConstructor-
style types map to class values, and parameters, arrays, Map values and
object fields hold them.
Construction through a value calls %C.construct, which builds the
instance with the runtime constructor the direct `new` uses (emitter.new,
a stream's newDyn without options, error.newOptions for Error and its
six standard subclasses). instanceof, typeof, .name and === answer as in
Node. A Duplex value widens into a `typeof Readable` slot, and stream
values widen into `typeof EventEmitter` slots, as the constructor ABIs
match; a union of such class values (`const kinds = [Readable,
Writable]`) is a value of their nearest common ancestor, and `new K()`
through it answers the checker's union of instances. `instanceof`
through a value that may be Writable keeps the Duplex rule at run time.
EventEmitter and stream values constructed with options, runtime class
values stored as untyped values or printed, statics read through a value,
and DOMException, AggregateError, AssertionError and SuppressedError
values report SC1090. The existing static lowerings
(`EventEmitter.setMaxListeners`, `Error.stackTraceLimit`, the events
module) are unchanged. JavaScript files keep the identity token.