The backlog threshold of a level backs off by doublings as backlog passes
find little to free, but only passes that had candidates count. A program
whose mature passes have no candidates while its heap grows keeps the base
threshold, a quarter of the live heap, while those same passes widen the
level's growth fraction. When it then reads the structure it built, the
mature buffer fills with live objects and the first backlog pass walks a
quarter of the live heap to find nothing. In tsc-ts's program build on
Sentry, that pass ran inside the loader walk and took 9 to 18 ms.
Until a pass at a level has had candidates, the backlog threshold now uses
the level's growth fraction doublings. The first pass with candidates
judges the level, and the threshold follows its own yield from then on.
A null-valued JSON field bakes as null, so the fixture's document now
compiles. It keeps its purpose with a value no literal spells (1e400 parses
to Infinity), and the mixed-table corpus program covers null elements.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The exemption for `f.apply(this, ...)` and `f.call(this, ...)` dropped
SC1090 for every module export that passes `this` on. When the function
it reaches reads `this`, Node gives it the module object, and the direct
module call has none, so the result differed from Node.
A `this` that is passed on now counts as unread only when the function
it goes to provably ignores its `this`: an arrow, a function that uses
`this` only to pass it on to such functions, or what a known function
returns, with its parameters bound to the call's arguments and never
assigned. The analysis follows at most six calls and functions, and
refuses otherwise. Effect 4's provideService still compiles: it passes
`this` to what dual(3, body) returns, which passes it on only to the
arrow it was given.
Passing `this` on to a function that reads it now refuses, directly,
through such a factory, or when the factory reassigns its parameter.
An interface that extends a class-backed protocol, such as
`interface TupleTypeReference extends TypeReference { getTarget():
TupleType }` over a `Type` that class instances flow into, mapped as a
record when no class declared itself assignable to it. Asserting a base
value to it copied the instance into that record, which has no methods:
the next method call threw "expected function at $.getTarget, got
undefined", or the assertion reported a conversion diagnostic.
Such an interface holds values of its base protocol, so it now keeps the
same checked identity: the assertion keeps the instance, and its methods
and fields read as on the base type.
Inside a generic body Object.entries(t) over T extends Record<string,
unknown> types its values 'keyof T extends never ? unknown : T[keyof T]'.
An instance whose T maps to the checked-dynamic value now reads both
branches as unknown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Message types from SDK declarations often use shapes scriptc refused:
- `{ [key: string]: any }` and `Dictionary<any>` (also a bare
`Dictionary<T = any>`): the overflow values are checked-dynamic, like
`Record<string, unknown>`'s.
- `any[]` members an interface, type literal or class declares, also
optional or nullable: the checked-dynamic array, as `unknown[]` maps.
An object literal's inferred `any[]` member keeps its existing story.
- A class a types-only package declares with public fields only (aws-sdk's
`declare class PresignedPost { url: string; fields: Fields }`, with a
merged namespace of types): it describes JSON data and is never
constructed, so it maps structurally like an interface.
- `Pick<C, K>` and `Omit<C, K>` over a program class, when the view exposes
a method: like an interface the class matches, it keeps a live receiver,
so calls through the view run on the instance.
A record blocked by a member deep inside it now names that member's path
(`member 'poll().jobs[].handle' has type ...`) instead of the outermost
member only; a member holding `any` is named itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
emitter.ts had grown to 11.6k lines, and its import block and its large
emission methods were edited by most changes to the LLVM backend, so
concurrent changes conflicted there: 17 merges into main resolved it in
two weeks, more than any other compiler file. 26 LlEmitter methods move,
as is, into functions of the emitter in four files next to it:
emit-module.ts (module parts, helper and library definitions),
emit-statements.ts (the statement switch, try/catch, switch, loop copies),
emit-function.ts (function bodies and generator and async scaffolding) and
receivers.ts (receiver reads and the borrow analyses). Each method keeps
a one-line stub in the class, so callers are unchanged, and imports that
only the moved bodies use move with them.
The move changes no behavior: the zero-cost gate compiles the benchmarks,
the self-hosted compiler and the corpus to identical IR, LLVM and
executables.
Each evaluation of a function-local class creates its own private names,
while one compiled class serves every evaluation. A JavaScript class's
prototype methods are closures per evaluation whose `this` is the
call-site value, so a method taken from one evaluation could be called
with another evaluation's instance (`a.next.call(b)`, a prototype read,
Reflect.get with a receiver). Those calls read and wrote the other
instance's fields, and `#x in this` answered true. Reads and writes of
`this.#x` inside the closures also went through the property bag under
the key "#x" instead of the native field.
Private fields on a checked `this` now go through a brand check: the
receiver must be an instance of the class or a subclass whose class
environment is the closure's own evaluation, which the class's own
binding names. A matching receiver reads and writes the native field;
any other receiver throws Node's TypeError for the read or the write
(after the assigned value evaluates), and `#x in this` answers false.
Method values and accessors taken from typed instances of these classes
resolve to the instance's own evaluation's prototype functions instead of
a native thunk shared by every evaluation.
The classes this applies to are narrowed to the shapes the check covers:
JavaScript class declarations whose private members are instance fields
used only on `this`. Private methods and accessors, class expressions,
and TypeScript classes keep the "function-local classes with private
brands" refusal.
A checked conversion to a union of object types tests each member's
fields with the members' matchers. For a field of type `C | undefined`
holding a class instance, the union's matcher found the instance's
capsule, which is not a capsule of the union itself, and answered no
(or matched the instance's plain-object view, which no class arm
accepts), so no member matched and the conversion threw a TypeError.
workerData read as such a union failed this way in every worker. A union
now matches capsules of other types through its arms, where the class
arm accepts the instance.
`const [hit] = re.exec(s) || ""` destructures `RegExpExecArray | string`:
the string arm answers its code point at each position, or undefined past
its end, as the string iterator does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A class whose module the program reaches only through `import type`
never registers, and its instance type stayed unmapped: a field such as
`items: Emitted | undefined` reported SC2009, and the class holding it
SC1090. The program holds no instance of such a class, so its instance
type now maps as the structural type it describes, like an interface:
fields of the type hold undefined, object literals and arrays of them.
A JavaScript class outside the program keeps the unmapped type, so its
JSDoc-annotated slots still take the dynamic value.
A class a bundled ES module's __esm initializer stores into a module
binding (`Multi = class extends import_stream.Readable { … }`) becomes a
class declaration at the same text position: the initializer's body splits
around it into run-once pieces, so a class over a runtime-provided base no
longer has to exist as a stored class value. Only definitions whose
evaluation runs no code (no static fields, static blocks or computed names;
heritage absent, a builtin module member, or a hoisted class), declared
once, assigned only there and read only inside functions after the
assignment move.
npm selection now also scans the program modules a literal import()
reaches, with their static graphs: a workspace package loaded on demand
had its npm dependencies refused as "not part of the compiled module
graph".
`new import_x.C()` respelled `new require("x").C()`, which constructs the
require call; the interop binding now becomes `(require("x"))` there.
util.inherits onto a runtime-provided class (stream.Readable, the from2
and multistream shape) fences where it is written instead of failing in
Node's argument validation at run time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A holey array converted to a checked-dynamic value keeps its holes (the
conversion records them in the array's presence map), but the runtime
inspector printed every element: `const u: unknown = a` for
`a[2] = 1` printed `[ undefined, undefined, 1 ]`, where Node prints
`[ <2 empty items>, 1 ]`, and `new Array(3)` through `unknown` printed
`[ undefined, undefined, undefined ]`.
The inspector now prints each run of holes as one `<n empty items>`
entry that counts toward the 100-entry limit, as Node's
formatSpecialArray does and as the typed array inspector already does.
- TLSSocket.isSessionReused() is declared in the fallback types and lowers
to false: the TLS client never offers a cached session (no session
resumption is a documented divergence), so no handshake resumes one.
- `o[k] ??= v`, `||=` and `&&=` through a pure index-signature record with
a string key use the keyed read and write `o[k]` and `o[k] = v` lower to.
- Object.assign(instance, record) writes the record's fields into the
class instance's declared fields (same or width-compatible types;
optional source fields only when present) and answers the instance.
- Object.assign({}, instance) whose result the checker types as the class
is refused at compile time: the copy is a plain object, so the checked
cast into the class threw at run time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In Node every Error has an own, non-enumerable `stack` accessor, shared by
all errors, that formats the stack on first access. Errors that reached
checked-dynamic code read `stack` as undefined: the error encoding carried
a stack only when a typed read had already formatted one. Reads on
instances of Error subclasses found no class arm for it, and assigning
`stack` on a subclass instance, typed or through `any`, did not change
what a typed read answered.
The encoding now lists `stack` first among its own properties as a
non-enumerable accessor. Its getter formats the native error's stack on
first access, so the heading is the name and message at that point, and
its setter stores a string in the native error, which typed reads share.
Any other value replaces the accessor with a data property. Objects that
inherit from an error read the error's stack, and a getter called on any
other receiver answers undefined.
A `stack` read on a checked-dynamic value counts as a stack observation,
so such programs capture frames as they do for typed reads. Dynamic reads
and writes of `stack` on Error subclass instances use the native stack;
non-string values are kept with the instance's own properties.
A thrown Map, Set, typed array, Buffer, function or RegExp reached its
catch binding as a type-erased reference, so inspection printed `{}`,
String() gave "[object Object]", instanceof was false and typeof of a
thrown function was "object". These values now take the checked-dynamic
conversion that thrown records and arrays use, so the catch binding is
the thrown object, as in Node.
The darwin-arm64 runtime pack matrix repeated the common matrix's base
and optional runtime source lists and its variantsFor table, so a new
runtime source needed the same line in both files. The common matrix
now exports BASE_RUNTIME_SOURCES, OPTIONAL_RUNTIME_SOURCES and
variantsFor, and the darwin-arm64 matrix imports them. A comment on the
lists notes that their order is the link order of an executable's
runtime units. Both matrices evaluate to the same objects as before.
The undici5 and undici7 npm cases now also run peers.ts against Node with
the vendored undici: header values with CR or LF, chunk-size lines that
are valid, malformed, overflowing or 1 MiB long, and fetch bodies that
are endless (read once, then cancelled), gzipped, truncated, empty,
corrupt, cut off, aborted after the headers, redirected while endless,
or carry six content codings. The undici5 case leaves out the Host
values, which undici 5.12 writes into the request head, and the two
fetch cases where undici 5.12 differs (six codings, and an endless
redirect, which fails with a TypeError in 5.12).
package-shims.test.ts compiles two programs in copies of the cases: one
reads 64 MiB chunk-size lines and 64 MiB plain and gzipped fetch bodies
from a separate Node peer and checks that RSS stays under 48 MiB (plain
lane), the other checks that the 5.x profile rejects a Host value with
CR or LF with undici 5.29.0's error.
compileCInternal built the program's command line from a chain of
conditional spreads, one per optional runtime unit, and wrote the
optional -D flags out twice (the program build and the runtime-object
flags). RUNTIME_UNITS now lists the optional runtime inputs in link
order, one entry per unit: the sources and the condition that selects
them, or a small function for the entries that add vendor objects,
include directories or platform libraries. RUNTIME_DEFINES lists the
optional definitions once, and both flag lists use it. The command
lines keep their order, so the executables do not change.
The comment fragment before the process-events unit, left from a
removed zlib entry, is dropped.
Two fixes for the CommonJS cycle check and the bundled-chunk splitter:
- During a cycle's initialization, a read of a partner's exported
function is safe only where the value neither runs nor stays: an
expression statement, typeof, void, !, an equality operand, or the test
of if, while, do and ?:. A const alias (`const f = binding.fn`), an
object or array literal, a return, or an argument can be called before
the cycle settles, so these keep SC1016 (Node throws when the function
reads a binding in its temporal dead zone). A top-level const that only
publishes the function in an export statement, as the static rewrite
spells an export-table re-export, stays a read.
- A copied helper whose initializer is a named function expression
reaches itself through its own name. Each chunk gets its own copy, so
that name must also be used only as a callee; otherwise the chunk keeps
the package refused with the existing reason.
ir/runtime-features.ts kept three parallel lists that every new runtime
feature extended at the same place: the RuntimeFeatures interface fields,
the all-false initializer, and the chain of libCall checks in the scan.
They are now one table, RUNTIME_FEATURE_RULES, with one entry per
feature, sorted by name. An entry lists what sets the feature: a
module-level initial check, libCall names and name prefixes, libCall and
expression predicates, and IR type kinds. A libCall or type that needs
several units appears in each unit's entry. RuntimeFeatures, the scan and
its early exit derive from the table; noRuntimeFeatures lists every
feature once, and TypeScript rejects it when it misses one.
Every corpus program gives the same features as before (3217 programs
compared field by field on their IR), and the module still lowers
statically when the compiler compiles itself.
JSON documents whose rows mix strings and numbers (`(string | number)[][]`,
`(string | number[])[][]`, the character-set and code-point tables of
iconv-lite and tr46) now bake: comptime literals take union-typed elements
and pick each value's arm from its JSON kind and contents.
Array destructuring of a union of array and tuple types
(`const [m, a, b] = re.exec(s) || ["", "", ""]`) reads each position from
the arm the value holds: a tuple arm answers its field or undefined past
its end, an array arm its element or undefined past its end.
Unions of tuple types no longer pick the checker's `length` literal as a
discriminant: tuple shapes have no such slot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A package that exports a constructor function whole and also as a named member (`exports = module.exports = Client; exports.StatsD = Client`, the statsd-client shape) could not be constructed from TypeScript: `new StatsD(...)` was refused as "constructing values other than classes declared in the program", and a bare reference had "no lowering".
- `exports = module.exports = value` is now the module's whole export, and it rebinds `exports` too, so members attached through `exports` afterwards are exported. The fresh-table reset `exports = module.exports = {}` keeps its own handling.
- An `import { name }` or a top-level `const { name } = require(...)` of a member written once as `exports.<name> = <binding or primitive literal>` on such a module reads that value, following `module.exports = require("./x")` re-exports. The checker gives these bindings a synthesized property symbol, so the module's statements answer.
- Construction then takes the existing checked path for JavaScript functions: prototype methods, `util.inherits` between two constructor functions, and `Parent.call(this, ...)` behave as in Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fetch's input under @types/node is string | URL | Request. A union with a
checked-dynamic arm collapses to the checked-dynamic representation only
when every sibling survives the collapse; URL did not qualify, so the type
(and anything typed as typeof globalThis.fetch, such as a custom fetch cast
through unknown) did not compile. URL's handle boxes by reference, keeps its
accessors and setters, answers instanceof URL and checks back out to the
typed value, so URL arms now join the collapse. A fetch() input typed
string | URL also converts through the checked-dynamic input path instead
of being narrowed to a string, which threw on the URL arm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`util.promisify(client.close.bind(client))` over `close(callback?: (error?: Error) => void)` was refused because the callback parameter is `callback | undefined`. The promisified function now takes the union's function arm and passes the callback into the union.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A condition built from typeof tests whose answers are static (an
undeclared global is "undefined", the global object and process are
"object") folds, so the untaken arm of `typeof window < "u" ? window : …`
never lowers, and a detection chain used as a receiver resolves to the
global object it selects. JavaScript files no longer report a name the
file feature-tests with typeof as undeclared. `typeof global.setTimeout`
answers "function" instead of the identity token's "string".
`var pna = process` (process-nextick-args once it compiles as process)
and `var Transform = require("stream").Transform` alias like their const
spellings when nothing reassigns them; before, the first silently lost
process's members.
Also reads symbol declarations directly in the CommonJS accessor
snapshot path, after the checker helper's removal on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isarray, safe-buffer, process-nextick-args, inherits and util-deprecate
evaluate to a Node builtin on Node 24 at their checked major versions, so
a require of one compiles as that builtin expression at the require's
position: from an opted-in package's files (the installed version where
Node's resolution finds it), from the program's own CommonJS files, and
through an esbuild __commonJS chunk whose pnpm path key names the package
and its version. The package's files never join the program.
readable-stream 2.x and 4.x compile as node:stream, the package's own
READABLE_STREAM=disable behavior (a documented divergence). A use of the
entry's export picks the builtin module (member reads of the carried
classes) or stream.Readable (value uses); a mixture keeps the package.
3.x stays as is: its disable branch throws on Node 24.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>