Commit Graph
3145 Commits
Author SHA1 Message Date
Malte UblandClaude Opus 5.5 8c5d65212a Throw Node's ReferenceError for __dirname in a JavaScript ES module
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>
2026-10-19 23:11:39 -07:00
Chris Tate ba8849a269 Speed up native string character tests, optional-string switches, fromCharCode and long-string length
`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.
2026-10-20 01:11:36 -05:00
Malte UblandClaude Opus 5.5 655ba77eaa Compile the fs-extra shim under @types/node as well
Date-only utimes, literal mkdir recursion and promise-backed readFile
callbacks: the forms that compile natively with either type world.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:51:07 -07:00
Malte UblandClaude Opus 5.5 5b53ce7144 Hoist lazily defined classes over global constructors
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>
2026-10-19 22:51:07 -07:00
Malte Ubl 8320393e6a Merge branch 'opt/npm-esm-class-hoist' into opt/npm-fs-extra-shim 2026-10-19 22:44:53 -07:00
Malte UblandClaude Opus 5.5 78f713e572 Implement fs-extra 8-11 as a built-in package
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>
2026-10-19 22:44:48 -07:00
Malte UblandClaude Opus 5.5 3ddf9640e9 Format the npm case filter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:44:15 -07:00
Malte UblandClaude Opus 5.5 29f07e15f9 Compile import cycles whose class is reached only after initialization
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>
2026-10-19 22:42:10 -07:00
Chris Tate ddc5d50204 Refuse JSON and dynamic copies of object literals with accessors (#1490) 2026-10-20 00:37:25 -05:00
Chris Tate 88e032bf65 Keep counters a for loop bounds in integer shadows
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.
2026-10-20 00:32:42 -05:00
Chris Tate 88f2061f62 Refuse JSON and dynamic copies of object literals with accessors
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.
2026-10-20 00:31:25 -05:00
Malte UblandClaude Opus 5.5 7d1d440b7b Adapt method overrides across ABI differences, write Error codes, and close wrapper-reachable gaps
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>
2026-10-19 22:28:43 -07:00
Chris Tate 80c760f2b1 Format checked-dynamic arguments under %d and %i as Node does (#1488) 2026-10-20 00:25:46 -05:00
Malte UblandClaude Opus 5.5 4b82886458 Keep builtin constructor members refused instead of reading undefined
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>
2026-10-19 22:23:28 -07:00
Malte UblandClaude Opus 5.5 85a2567ecd Treat optional members an index signature covers as keys, erase 'as never', and keep fromEntries objects asserted to records
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>
2026-10-19 22:21:46 -07:00
Chris Tate 1d6bd1ea04 Shrink the array header to 48 bytes
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.
2026-10-20 00:21:45 -05:00
Chris Tate 12d0215c4d Link the child-process unit only in programs that spawn (#1403) 2026-10-20 00:18:21 -05:00
Chris Tate bae0570c2c Format checked-dynamic arguments under %d and %i as Node does
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.
2026-10-20 00:17:34 -05:00
Chris Tate b452a06425 Keep the class id of hierarchy instances in the rc word
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.
2026-10-20 00:15:31 -05:00
Chris Tate 8df5e8d48a Accept readline's dispose, instanceof, dynamic streams and non-string prompts (#1401) 2026-10-20 00:13:26 -05:00
Chris Tate 89625ef91c Borrow typed-array locals read from fields and parameters (#1481) 2026-10-20 00:12:50 -05:00
Chris Tate ac0ae29b18 Construct CommonJS constructor functions imported through a member alias (#1453) 2026-10-20 00:10:40 -05:00
Chris Tate 3944a70017 Compile module exports that pass this on with apply or call (#1444) 2026-10-20 00:09:54 -05:00
Chris Tate 619c857fcd List an error's own name where it was created
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.
2026-10-20 00:09:04 -05:00
Chris Tate 98a7d3b33d Merge main 2026-10-20 00:08:29 -05:00
Chris Tate 5cf51c46b0 Read x.constructor on EventEmitter and stream instances (#1442) 2026-10-20 00:06:32 -05:00
Chris Tate 226ba9672f Borrow typed-array locals read from fields and parameters
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.
2026-10-20 00:05:09 -05:00
Chris Tate 59dd6d1742 Read member aliases only while the binding holds the exported value
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.
2026-10-20 00:02:49 -05:00
Chris Tate 679227aeb1 Read an Error subclass's own code through NodeJS.ErrnoException (#1471) 2026-10-19 23:59:55 -05:00
Chris Tate 63cb11ff83 Keep DataView's own behavior through unknown (#1470) 2026-10-19 23:59:13 -05:00
Chris Tate b690f8f8f4 Count destructuring and for-of writes of a factory's parameter
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`.
2026-10-19 23:56:22 -05:00
Chris Tate d321ea5780 Leave the error marker out of own property names
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.
2026-10-19 23:52:46 -05:00
Chris Tate e134489edc Unroll the MD5 block function (#1465) 2026-10-19 23:51:35 -05:00
Chris Tate ee8d320fdb Widen an unjudged backlog threshold with its level's growth fraction (#1462) 2026-10-19 23:51:08 -05:00
Chris Tate 097b6b4c86 Cache zone offsets and UTC date fields, and return small integers' text without allocating
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.
2026-10-19 23:50:28 -05:00
Chris Tate e3f3dfd312 Copy repeated childless IR nodes one kind at a time (#1467) 2026-10-19 23:49:58 -05:00
Chris Tate b07a3bd5f2 Size encoded messages to their nodes and wake a channel's owner once per batch (#1468) 2026-10-19 23:48:19 -05:00
Chris Tate 21e5e2cd69 Answer in on typed arrays held as any (#1473) 2026-10-19 23:47:59 -05:00
Malte UblandClaude Opus 5.5 1a17bd3405 Merge branch 'opt/npm-polyfill-shims' into opt/npm-esm-class-hoist
Renumbers this branch's npm case 2572 to 2588, after the renumbered
polyfill cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:44:23 -07:00
Malte UblandClaude Opus 5.5 1b282f9c80 Merge branch 'opt/npm-cjs-exports' into opt/npm-polyfill-shims
Renumbers this branch's npm cases 2569-2571 to 2585-2587: #1411 now uses
2571.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:44:08 -07:00
Malte UblandClaude Opus 5.5 dde27c7e21 Merge branch 'opt/npm-lazy-loaders' into opt/npm-cjs-exports
Renumbers this branch's npm cases 2567-2568 to 2583-2584: #1422 now uses
2567.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:43:36 -07:00
Malte UblandClaude Opus 5.5 6fc85e848d Merge branch 'opt/npm-esm-wrappers' into opt/npm-lazy-loaders
Renumbers this branch's npm cases 2564-2566 to 2580-2582: #1411 now uses
2564-2566.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:42:59 -07:00
Chris Tate 9fc483b5c6 Box asserted class bindings into class-backed views (#1469) 2026-10-19 23:42:07 -05:00
Chris Tate 9548ab3663 Give the undici npm cases a 600 s timeout
The undici cases compile the built-in undici implementation's module
graph; a sanitized build of it on a CI runner outlasted the default
120 s per case.
2026-10-19 23:38:15 -05:00
Chris Tate cdd78b4502 Answer in on typed arrays held as any
`in` on a typed array, Buffer or DataView held as `any` or `unknown`
answered false for every key: indices, `length`, prototype methods and
Object.prototype names.

On a typed array, a canonical numeric key (one that ToString(ToNumber(key))
reproduces, or "-0") now answers by bounds: an integral index, not -0,
below the length, so "1.5", "-0", "NaN" and "Infinity" answer false. Other
keys answer true for members of %TypedArray%.prototype, Buffer.prototype on
a Buffer, the constructors' BYTES_PER_ELEMENT and Object.prototype. A
DataView has no indexed elements and answers DataView.prototype's members
and Object.prototype's.
2026-10-19 23:36:14 -05:00
Chris Tate 135e36ba7a Merge main 2026-10-19 23:35:07 -05:00
Malte UblandClaude Opus 5.5 1a65f234c9 Narrow unions of records by intersection refinements, and keep members an assertion attaches
'k' in u answers per value on arms that declare k as an optional slot.
A predicate or 'in' narrowing that intersects record arms views the
value through the stored arms that can hold it. An assertion
'r as R & { m: T }' gives R's arms m as an optional member, keeps the
object, and a binding initialized by it keeps the stored type, so
writes through it reach the original. Late writes through a union
receiver count as writes through the binding it was read from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:32:43 -07:00
Chris Tate 2259da3cf7 Give interfaces extending class-backed protocols checked identity (#1461) 2026-10-19 23:29:44 -05:00
Chris Tate 0b98a8c713 Read an Error subclass's own code through NodeJS.ErrnoException
`(err as NodeJS.ErrnoException).code` read the runtime error's errno
slot, which only runtime errors set, so an Error subclass with its own
`code` field answered undefined where Node reads the own property. When
a class in the program under the receiver's class has its own `code`
field or a dynamic property bag, the read goes through the error's
dynamic view, which answers the own property and the errno code alike.
2026-10-19 23:28:36 -05:00
Chris Tate bc9ef21b90 Keep DataView's own behavior through unknown
A DataView reached as a checked-dynamic value behaved like a Uint8Array
over its bytes: it printed as `Uint8Array(2) [ 0, 0 ]`, its string and
tag were the typed array's, `.length` and index reads answered from the
bytes, and typed array methods ran on it. It now inspects as Node's
`DataView { [byteLength], [byteOffset], [buffer] }`, answers
"[object DataView]", has no length or indexed elements, and throws
"is not a function" for typed array methods.
2026-10-19 23:28:21 -05:00