A class without a constructor that extends a stream class or
EventEmitter inherits the runtime constructor, which takes no arguments
here. Direct construction with arguments already reported that, but
construction through the class's value (`new registry.upper({ ... })`)
completed the arguments against the empty inherited signature and
dropped them. Both paths now share refuseInheritedBuiltinCtorArgs, so
the class-value path reports at the first argument with the same
message.
A const annotated with the asserted type (const view: Route & { order: number } = route as Route & { order: number }) took the annotated layout, so the initializer was coerced into it: a width copy that read members the writes had not created yet, and writes through the binding no longer reached the original object. Local and module-level consts now keep the stored type when it hosts the annotation, as unannotated ones do.
A Pick or Omit view of a program class that holds the instance (its view
exposes a method) now carries the class's identity in the flow analysis,
and its values count as instances of that class. Assigning such a view
to another structural type keeps the instance, so reads through the
second type see the instance's later writes instead of a copy's.
- A function adapter whose source returns `any`/`unknown` (a generic
wrapper's result handed back `as F`) converts the result through the
checked-dynamic bridge when the slot's result is checkable: a record
checks its shape, and a Promise keeps its identity and checks its
payload when it settles. Before, these adapters threw "the call has
no exact lowering" when called.
- In a class constructor, `Object.setPrototypeOf(this, new.target.prototype)`
(directly, through a const, or the `if (Object.setPrototypeOf) ... else
this.__proto__ = ...` form), and `Object.setPrototypeOf(this,
C.prototype)` in C's own constructor when no class extends C, are
no-ops: a compiled instance already has that prototype.
- Object.assign between records copies a source field into a wider
target field (`{ bail: true }` into `bail?: boolean`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
util.promisify(fn) over a function typed any (an npm package method typed
by its JavaScript, a bound method of an untyped value) was refused,
because the promisified signature is only known at run time. A native
wrapper now calls the function with the call's receiver, its arguments
and a callback, and answers a promise: a truthy error rejects, otherwise
the first value fulfills, the first settlement wins, and a synchronous
throw rejects. util.promisify.custom functions are not consulted.
The wrapper lives in its own runtime unit (scr_util_promisify.c), linked
by the new utilPromisify feature, so other programs are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `Promise.all([p, q, r])` over different promise types (the tuple
overload) lowers in TypeScript through one interned async helper that
awaits the runtime combinator over the same promises (widened to
Promise<void>, a type-only view), so settlement order and rejection
handling are Node's, then reads each fulfilled entry into the tuple.
Entries keep their identity; plain scalar entries pass through, and a
checked-dynamic entry joins through the promise it holds.
- The ambient `Boolean` as an `every`/`some`/`find`/`findIndex`/`map`
callback in TypeScript is the interned one-parameter truthiness
closure `filter(Boolean)` already used, and `filter(Boolean)` accepts
checked-dynamic elements (runtime truthiness).
- `E[key]` with a runtime string key answers the named member's constant,
or undefined for a name no member carries; a numeric member's reverse
key and inherited Object.prototype names throw a named runtime refusal.
- Regex-argument `replace`/`replaceAll`/`split`/`search`/`matchAll` on a
`string | undefined` receiver take the string lowering and throw Node's
"Cannot read properties of undefined (reading 'replace')".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The read used to defer a refusal, which kept the corpus program from
compiling statically. It now throws Node's catchable ReferenceError
("__dirname is not defined"), and typeof answers "undefined" as in
Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`s.charAt(i) === "c"`, and `s[i] === "c"` where the index is proven in
bounds, built a one-character string to compare with the literal. When
the literal is one UTF-16 code unit, the comparison now reads
`s.charCodeAt(i)` and compares the number. charCodeAt converts the index
the same way charAt does, and an index out of range answers NaN, which
equals no unit, as "" equals no one-unit literal. A switch on
`s.charAt(i)` whose case tests are all one-unit literals switches on the
code unit the same way.
A switch on a `string | undefined` value with string literal cases, such
as an array element or an optional field, lowered to a chain of union
equalities. It now tests for undefined, which matches no case, and
dispatches the string through the string switch: by byte length, then by
the literal's leading bytes.
`String.fromCharCode(a, b, ...)` with up to sixteen arguments passes the
codes to the runtime in a stack array instead of a heap f64[], and up to
64 code units encode into a stack buffer instead of a heap scratch block.
`.length` of a string that is not ASCII counts its UTF-16 units. The
string index keeps four entries, so a program that read the lengths of
more long texts than that in turn (the text of every source file) walked
each whole string on every read. Lengths of non-ASCII strings longer than
64 bytes now go into a 1024-slot table keyed by address, allocated by
the first length it records. A slot is dropped when the string is freed
or reallocated and when an append changes it in place, which is where
the index drops its facts.
A bundled package's error class defined inside an __esm initializer
(`NowBuildError = class extends Error { … }`) now hoists to a declaration
like the classes over builtin module members, so a program class can
extend it and `new` and `instanceof` work against both. Reading a standard
global constructor the bundle never rebinds runs no code. References in
esbuild's `0 && (module.exports = { … })` annotation for Node's CommonJS
lexer never run and no longer block the move.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fs-extra builds its exports at load time (computed keys over graceful-fs,
universalify wrappers), which the static CommonJS model cannot follow, so
programs reading its members through the default import failed. The
package-shim registry now maps fs-extra 8.x-11.x to a TypeScript
implementation over node:fs: fs's members re-exported, the wrapped methods
following universalify's callback-or-promise rule over fs.promises, and
the extras (copy, move, remove, emptyDir, mkdirs, ensureFile, pathExists,
outputFile, the JSON helpers and their aliases) with fs-extra's messages.
Its declarations stand in for @types/fs-extra, including the default
import, and a namespace import resolves to the shim. What it does not
implement is not declared, so using it is a type error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A class of an import cycle that code in the other module can reach before the class statement was refused (SC1016). It now gets the initialization flag a module's own early-reached classes already had: references from code that can run earlier check it, so Node's ReferenceError appears only when the class is actually reached early. The reference scan resolves other modules' import bindings. An `extends` clause in another module that can run early keeps the refusal, since a compiled subclass is static.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Linux test jobs set ASAN_OPTIONS=detect_leaks=0 for both flavors and
left leak detection to the RC audit, which does not see runtime tables
allocated with calloc. A worker thread's record-source table leaked that
way, and only local runs reported it. The san shards' case-sharded corpus
task now sets detect_leaks=1 and an LSAN_OPTIONS suppressions file,
tests/harness/lsan.supp, which starts empty. Plain shards, the side queue,
the Effect jobs and macOS, whose ASan has no LeakSanitizer, keep leak
detection off.
A loop that counts with number locals (`lines++`, `run = 0`,
`longest = run`) kept each counter a double, so every increment was a
dependent floating-point add, and conditional increments compiled to
branches. The speculative integer copy only covered loops with products
or variable steps.
The range analysis now bounds for-loop cursors that are reset to an
integer literal or set to another bounded binding, besides those it
already bounded by their increments. A cursor's writes take its loop
range, which holds every value it takes in the loop, so `changes + 1`
stays proven at the limit of a typed array's length. The analysis
reports these loop ranges per for loop.
Where an exception leaves the function (no enclosing try or finally,
no coroutine), the emitter keeps such cursors in i64 shadows for the
loop: one copy of the loop, no checks, and each double is written back
when the loop exits. A loop with a labeled break or continue keeps its
doubles.
An object literal with get/set accessors keeps the accessors' closures in
reserved slots of its record. Since function values became convertible to
checked-dynamic values, these records passed the JSON and conversion
checks: JSON.stringify omitted the getter's property, as it omits
function-valued ones, and a copy held as `any` or `unknown` read the
getter's property as undefined and listed the slot's internal name among
its keys. Node invokes the getter in both cases.
Records with accessor slots are now outside the JSON-safe and
dyn-convertible domains, so JSON.stringify of such a value and its
conversion to `unknown` or `any` are refused at compile time. A literal
written directly into an `any` slot keeps its accessors and is unchanged.
Overrides whose parameters widen and whose results convert into the
inherited ones keep the inherited slot, which converts around the
override's own body; Error values take code writes, `in` tests and
Object.assign; and a few gaps behind generic wrappers close:
- Method overrides: each inherited argument converts into the
override's parameter (equal, wider, or a checked extraction from an
`any` slot) and the override's result converts into the inherited one
(narrower, a convertible union, a widening Promise payload, anything
convertible over an `any` base). The slot %C.m keeps the inherited
ABI and forwards to %C.m$own; TypeScript callers typed with the
subclass call $own directly when nothing overrides below. Async
overrides of sync and async bases, and JavaScript overrides of
builtin or TypeScript bases (zod's ZodError.toString) are included;
a toString slot converts its result with ToString.
- `err.code = value` on Error values in TypeScript: strings set the
runtime code slot (and the error's checked-dynamic node, if any); other
values clear it and keep the dynamic own property.
- `'key' in err` with any receiver: declared members answer true;
otherwise a class test over the declaring subclasses, a runtime lookup
of the error's own properties, or an Object.prototype member.
- `Object.assign(err, ...sources)` onto an Error target copies onto the
error, re-reads name, message, stack, cause and code into the typed
slots, and returns the error.
- `Error & { ... }` intersections whose extra members have checkable
types are Error values; those members read and write the error's own
properties, each read checked at its declared type.
- Catch bindings typed `any` read as checked-dynamic values (member
reads and writes, assertions); a multi-statement type guard with an
`unknown` parameter takes the caught value as an ordinary argument;
Error members of a binding narrowed to an Error-extending interface
read through the same value.
- Lambda parameters typed `any` by their default are checked-dynamic
slots; forwarded-only rest tuples with optional positions forward
their slots; `value as never` from an untyped value into a typed
parameter takes the checked extraction to the parameter's type.
- Lexical `this` stops at generic instance frames, so an instance no
longer sees the receiver of the caller that triggered it. The fence
for `this` captured through a plain function now points at the
`this` keyword.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading Object, Date, RegExp, Map, Set, Promise, WeakMap and WeakSet as
interned function values made every use of those globals a dynamic value,
including the receivers of member reads. The interned function models only
name, length and constructor, so `Map.prototype`, `Date.prototype.getYear`,
`Object.isFrozen` as a value, `Map["prototype"]` and `"prototype" in Map`
stopped reaching their SC2020/SC1090 fences and read undefined through the
dynamic paths instead (769 Test262 variants went from a refusal to a wrong
result).
A builtin global read for its members now keeps its member lowerings and
fences; only name, length and constructor read through the value. A member
read through an alias typed as the constructor's static side (`const D =
Date; D.prototype`) reports SC2020 before the generic field read. Passing
the value to Object or Reflect statics that reflect on or redefine its
members (`Object.hasOwn(Object, ...)`, `Object.defineProperty(Promise,
...)`, `hasOwnProperty.call(Map, ...)`) is refused; identity and the empty
enumerable key lists still compile. The interned functions answer Node's
lengths (Date 7, RegExp 2, Promise 1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A record type whose declared members are all optional and typed as its
index signature's slot (ProcessEnv's TZ?, '{ CI?: string } &
Dictionary<string>') interns as the pure index-signature record, its
slot admitting undefined: spread, delete and enumeration then follow one
insertion order. 'value as never' erases, so the consuming slot checks
an unknown value like any other. 'Object.fromEntries(...) as T' for a
declared record T goes through the checked-dynamic bridge, which keeps
the entries' keys and order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ScrArr stores len and cap as 32-bit fields and no longer stores the
state pointer. The dense states share the values' allocation and run
backward from its end: the state of slot i is at
data + cap * (slot width + 1) - 1 - i. Removing slots from the front
(shift, splice(0, n)) advances data and lowers cap, which leaves every
remaining state at its address; the head room size, needed only to free
or reallocate the block, is kept in the vacated slot before data. On
64-bit targets the header goes from 64 to 48 bytes, and from 36 to 32
bytes on wasm32. Array lengths were already limited to 2^32 - 1.
The emitted code loads len and cap as i32 and zero-extends them, and
computes state addresses from data and cap. Runtime loops compute the
end of the states once per call. Local array reads load data once for
both the state and value paths. A local or strict element read whose
index is a loop's induction variable reads slots above the hole-free
prefix through the runtime: an inline state check there makes LLVM keep
-1 - i as a second induction variable, which costs a register in the
loop.
Bulk operations do not read back states they just wrote: an append to
an array whose hole-free prefix reaches its length moves the prefix bound
to the new length, a copy from below the source's prefix writes value
states without reading the source's, and releasing a string array skips
the state checks below the prefix. Copies of up to eight slots and of
short state runs use overlapping word moves instead of a call. Emitted
element access computes the state address only on the state-checked
path. Without these, programs that build many small arrays with split or
slice ran 2-4% slower than with the separate state pointer.
Integer loops over an array's length compare the counter with the i32
length field in 32 bits; the counter never exceeds 2^32 - 1 there. The
array-length stability proof accepts Map and Set reads (get, has, size)
and checked union extractions (`x!`), which run no user code, so loops
that look up maps keep the dense fast copy, which tests the hole-free
prefix once before the loop.
Small constructions with the small allocator request 6 slots, so their
blocks stay 112 bytes, now with 7 inline slots (6 after a collector
header). With 96-byte blocks of 5 slots, programs that free and reuse
many small arrays ran 15-20% slower.
RUNTIME_ABI_VERSION goes from 22 to 23.
util.format's %d validated a checked-dynamic argument as a number and
threw "expected number" for anything else, where Node prints
Number(arg), and %i of such an argument had no lowering. Both now follow
Node: a bigint prints as `${n}n`, a symbol as NaN, and any other value
as Number(arg) under %d or parseInt(String(arg)) under %i.
On 64-bit targets, an instance of a class in a program-rooted hierarchy
has no vtable word. The rc word holds the class id (the class's preorder
number + 1) in its high 32 bits and the reference count in its low 32
bits. Retain and release update the count as an i32 at offset 0;
instanceof and the other preorder tests read the id at offset 4; virtual
calls and the last release through a base-typed reference index the
hierarchy's constant vtable array by the id. The runtime reads every
object's count through SCR_RC_COUNT.
Runtime-rooted hierarchies (Error, EventEmitter, EventTarget, Event and
the streams), standalone classes and 32-bit targets keep the vtable word.
Node lists an object's own properties in creation order. An Error's own
`name` is created by an assignment after construction defines `message`
and `cause`, or during construction by a `name` field or a constructor's
`this.name = ...`. Errors listed an own `name` before `message`, and
instances of Error subclasses listed it before every field, so
Object.keys, Object.getOwnPropertyNames and JSON.stringify answered a
different order than Node's: `{"name":"E","extra":1}` for `{"extra":1,
"name":"E"}`.
An error's own-property view now lists `name` after the properties its
construction defines. For an Error subclass, the class records which
field `name` follows: a `name` field takes its place among the fields,
parameter properties first, and `this.name = ...` in a constructor follows
the fields created before it, including the ones that constructor assigns
in JavaScript sources. When no class of the chain sets `name` during
construction, an own `name` follows every other own property.
A local initialized from a typed-array field or parameter, such as
`const chars = this.chars`, took a reference and released it at the end
of its block, like any owned binding. Class instances, records, arrays
and strings already borrow such a projection when the rest of the block
writes none of the locations it reads, and walk and rebound parameters
use the same test. Typed arrays are one plain pointer as well, so they
now take the same paths: the field or the caller's binding keeps the
array alive while the local reads it, and a block that may replace the
field still retains its own reference.
A member export resolves to its binding only when the binding is declared
before the write and never reassigned, so a consumer reads the value the
member stored. A module that re-exports another module's exports through
module.exports = require("./x") now counts its own writes after the
re-exported module's: a member it adds resolves, and a member both modules
write stays refused. The exports rebinding refusal and its test now cover
the form that stays refused, exports = value without module.exports.
A factory's parameter counts as the argument it was called with only
while the factory can't change it. The check saw assignments and updates
of the name, but not a write through a destructuring pattern
(`[body] = ...`, `({ body } = ...)`) or a for-in or for-of target, so a
factory could replace its callback with one that reads `this` and the
call still compiled without the module object.
The parameter now counts as changed by any assignment, update,
destructuring or for-in/for-of target that writes it, by a declaration
of the same name, by a write to an element of `arguments`, and by a call
of `eval`.
Object.getOwnPropertyNames, Reflect.ownKeys and
Object.getOwnPropertyDescriptors on errors held as `any` or `unknown`
listed "%error", the key that marks the checked-dynamic error encoding.
Node lists only the error's properties. The marker is now left out of
own property names; the runtime's own checks still read it.
Local-time getters converted every new Date with a localtime call. The
runtime now keeps the zone's offset over an interval of instants: when
the offset and DST flag at an instant equal those 19 days later, every
instant between has them too (zones do not change offset twice within
19 days; V8's date cache relies on the same span), so consecutive Dates
convert without a localtime call each. The interval grows as nearby
instants confirm the same offset and restarts on a change of
process.env.TZ.
UTC getters keep the thread's last decomposition, like the local ones,
so reading several fields of one Date decomposes it once.
Converting an integer from 0 to 99 to a string returns an immortal
static string instead of formatting and allocating one.