Commit Graph
2786 Commits
Author SHA1 Message Date
Chris Tate 496cf577c7 Print iterators and iterator helper objects as Node does
console.log and util.inspect of a generator object, an iterator helper,
or an array or string iterator were refused, and printing a Map or Set
iterator threw "util.inspect of a dynamic Array Iterator is not
supported yet".

Compiled generators now carry the tag their object prints with:
"Generator" or "AsyncGenerator" by default, "Iterator Helper" for the
helpers, "Array Iterator" and "String Iterator" for the iterators the
compiler builds as generators. The spawn wrapper sets it through the new
scr_gen_set_tag; an adapter over another generator copies that
generator's tag (scr_gen_copy_tag), and generators that stand in for an
object of another class, such as a Set iterator adapted into an
iterator-typed slot, keep refusing to print. They print as Node's
Object [<tag>] {} at any depth, and Object.prototype.toString answers
[object <tag>].

The runtime's native iterators print as Node does: Object [Array
Iterator] {} and Object [String Iterator] {}, and for Map and Set
iterators the entries the next steps would produce, without advancing,
under [Set Iterator], [Set Entries], [Map Iterator] or [Map Entries],
as [Object [Set Iterator]] past the depth limit and {  } when none are
left. Object.prototype.toString answers [object Set Iterator] and the
other tags.
2026-10-19 01:10:39 -05:00
Chris Tate 96d3a0f5e4 Carry AsyncLocalStorage stores into timer, immediate, tick and microtask callbacks (#1365) 2026-10-19 01:01:02 -05:00
Chris Tate a7e30318a3 Sort ShapeHost's members and the lowerer's mode options by name (#1370) 2026-10-19 00:59:31 -05:00
Chris Tate d6952e4fe2 Walk Map and Set iterators inside iterator helper objects (#1367) 2026-10-19 00:43:25 -05:00
Chris Tate b1395256d2 Initialize the asyncContext runtime feature in declaration order
The runtime feature scanner's result is serialized as JSON, and the
self-hosted scanner lays its record out in the interface's field order.
asyncContext was initialized before dynAsync, while the interface declares
it after, so the self-hosted scanner's JSON put the key elsewhere than the
TypeScript one. The initializer now follows the interface order.
2026-10-19 00:17:31 -05:00
Chris Tate 86e8a09d86 Sort ShapeHost's members and the lowerer's mode options by name
Changes that add a backend host hook or a lowering option appended it at
the end of ShapeHost, LowererMode and the Lowerer constructor's option
assignments, so concurrent additions edited the same lines. ShapeHost's
and LowererMode's members are now sorted by name with their comments, and
the constructor assigns the plain mode options in name order, followed by
the values derived from them in their previous order. Type-only and
reordering changes: the emitted LLVM is unchanged.
2026-10-19 00:14:59 -05:00
Chris Tate 05f2dd5859 Walk Map and Set iterators inside iterator helper objects
A helper object over a Map or Set iterator that is stored and consumed
later (const it = set.values().map(f); for (const x of it) ...) stepped
the runtime's checked iterator through an adapter generator, boxing each
value and resuming two generators per element: 5.1x Node's time on a
100k-element Set.

A stage on m.keys(), m.values(), m.entries() (or a Set's), or on
Iterator.from(set) or Iterator.from(map), now walks the collection inside
the helper's own generator. The walk steps the runtime's native iterator
by entry index through the new scr_dyn_collection_iterator_index and
reads each key or value with the typed intrinsics. The native iterator
keeps the walk live and ends the collection's iteration when it is
exhausted or released, so an abandoned or returned helper leaves the
collection free to compact. Other helper sources over a Map or Set, and
Iterator.from over a Set or Map, use a typed generator over the
collection built the same way.
2026-10-19 00:03:25 -05:00
Chris Tate d4e614a51c Count each statement once in coverage however often it lowers (#1366) 2026-10-18 23:38:30 -05:00
Chris Tate 6b14ce8833 List bench/thread-slots among the zero-cost gate's dynamic workloads (#1364) 2026-10-18 23:36:46 -05:00
Chris Tate aef5b052b5 Sort the handle tags by name with explicit values (#1362) 2026-10-18 22:33:15 -05:00
Chris Tate ce03d9a1db Lower string and typed array iterators, Iterator.from and helper return() (#1363) 2026-10-18 22:32:46 -05:00
Chris Tate a1a475c76f Count each statement once in coverage however often it lowers
Speculative lowering lowers some function bodies several times: the
receiver of `(function () { ... }).call(this)` goes through four call
lowerings that each probe it with tryLowerExpression, and each probe lowered
and counted the body. A five-statement program with such a wrapper reported
14 statements, and a fenced statement inside it counted as four failures.

The Lowerer now records which statements and class field initializers are
in the totals and which are counted as failed, and counts each node once.
Corpus 6110's @deferred-fences directive stays 1, its one fenced statement
in a class base lowered once, and its header no longer ties the count to
how many times that body lowers.
2026-10-18 22:30:45 -05:00
Chris Tate 1f7f029a05 Carry AsyncLocalStorage stores into timer, immediate, tick and microtask callbacks
AsyncLocalStorage changes, matching Node 24.22:

- setTimeout, setInterval, setImmediate, process.nextTick and
  queueMicrotask callbacks run with the stores that were active when they
  were scheduled. Before, getStore() returned undefined in them.
- An interval keeps that context on every tick, and a refreshed timer
  keeps it too.
- enterWith inside one of these callbacks lasts until the callback
  returns. An enterWith in top-level code ends when the main module's body
  returns, and one inside a callback scheduled with no store active ends
  when that callback returns, so the store does not reach callbacks that
  were scheduled without it.

Programs that enter stores (store.run, exit, enterWith, or a
diagnostics_channel store binding) schedule through new *_als runtime
entry points, which wrap a callback scheduled with a store active in a
closure that installs the stores around the call. The emitter picks them
from the new asyncContext runtime feature; other programs keep the plain
entry points, so their scheduling is unchanged.

The limitations page notes that completion callbacks of callback-style
I/O APIs still run with no store.

5815-als-callback-context compares the stores each kind of callback sees
with Node, plain and with SCRIPTC_SAN=1.
2026-10-18 22:26:09 -05:00
Chris Tate 8121e32909 Derive the docs compatibility metadata at build time (#1360) 2026-10-18 22:20:36 -05:00
Chris Tate 15636ecf90 List bench/thread-slots among the zero-cost gate's dynamic workloads
The workload's release binary links dynamic-tier symbols through its
worker messages: node:worker_threads values (Worker, parentPort,
workerData) are dyn objects, and worker.on and postMessage lower to
dynamic method calls. ThreadSlots and InternTable link none on their
own. intern-table and typed-array-scan-workers are listed for the same
reason.
2026-10-18 22:18:10 -05:00
Chris Tate 708b1350bf Free errors that crossed into checked-dynamic code and attach Node's error properties (#1359) 2026-10-18 22:17:52 -05:00
Chris Tate dc6a638e63 Lower string and typed array iterators, Iterator.from and helper return()
String iterators (s[Symbol.iterator]()) and typed array values(),
keys() and entries() compile as iterator objects: generators over the
code points and over the indices. The iterator helpers apply to them,
and for...of, spreads and Array.from consume them.

Iterator.from over an array, string, typed array, Set, Map or iterator
object answers the value's own iterator, as Node does for iterators
that already inherit from Iterator.prototype. Objects with their own
next method keep the Iterator fence. As the root of a helper chain,
Iterator.from(x) walks x itself: an array by index, a Set or Map live,
a generator directly.

h.return!() and h.return?.() compile when h is a helper call or a const
initialized with one: helper objects always have return(), so both
spellings are the call. Other iterator objects keep the fence, since the
array, string, Map and Set iterators have no return().

flatMap callbacks may return Sets (their values) and Maps (their
entries), walked live; a string result throws Node's TypeError and
closes the source.
2026-10-18 22:10:17 -05:00
Chris Tate b9cc0ed820 Sort the handle tags by name with explicit values
Every change that added a checked-dynamic handle kind appended its tag
just before SCR_DYNH_COUNT in scr_dyn.h, so any two such changes in
flight conflicted there. The tags are now sorted by name, each with its
current value written out, so a new tag goes in its sorted place with the
next free value, and changes adding different tags edit different lines.
SCR_DYNH_COUNT, used only by the capacity check, is gone.

The values are unchanged and the runtime artifacts are identical. A new
test checks what the enum's order used to guarantee: values are unique and
below SCR_DYNH_CAPACITY, and the LLVM backend's hardcoded tag numbers
(DYN_HANDLE_TAG_NUM) match the header.
2026-10-18 22:06:48 -05:00
Chris Tate b6e9e42308 Derive the docs compatibility metadata at build time
node-v24-compatibility-meta.json held two values derived from the public
compatibility artifact: a content hash for the data URL and its row count.
Every compatibility change rewrote the hash line, so concurrent changes
conflicted on it. next.config.mjs now computes both from the committed
artifact and passes them to the page through Next's build-time env, with
the same values. The generator no longer writes the file, and its local
check compares the public artifact itself.
2026-10-18 21:56:43 -05:00
Chris Tate ecff232688 Close Next.js proxy gaps: fetch header and URL-error parity, JSON undefined, and twelve compile gaps (#1341) 2026-10-18 21:54:08 -05:00
Chris Tate 96f59b0f3f Free errors that crossed into checked-dynamic code and attach Node's error properties
The error identity cache (scr_dyn_from_error) keeps one checked-dynamic
node per error so a crossing error compares reference-equal and keeps the
properties set on it. It retained both sides for the whole process, so
every error that crossed a checked-dynamic boundary (a caught error read
through a cast, an error delivered to an untyped callback, a dns failure, a
warning) stayed alive until exit: 100,000 caught AssertionErrors read
through a cast grew the resident set by about 210 MiB.

The cache now sweeps itself. An entry whose error and node are referenced
only by the cache is unreachable (a node holds no reference to its error;
a registered subclass box holds one, and a spawnSync error's `error`
property refers to its own node), so a sweep releases both sides. Sweeps
run when the cache doubles past the entries the last sweep kept, so they
cost amortized constant time per crossing. The cache is per thread, and
objects shared across threads are published (immortal), so a sweep never
races another thread's reference. Live errors and live nodes keep their
entries: identity, rethrowing the same instance, isNativeError and kind
checks are unchanged.

scr_error_set_prop sets an own property on an error's node; dns, assert,
cipher, crypto, util.callbackify and warning errors use it for the
properties they attach. util.inspect of a runtime error renders through
its node: Node's own properties follow the bracket form in key order (a
system error's errno, code, syscall and path, and the extra properties a
throw site attached), and an AssertionError renders at depth 0 with long
string actual/expected values shortened, as its custom inspect does. A
node that contains itself renders as Node's <ref *1>/[Circular *1].

child_process errors carry Node's own properties:
- execSync and execFileSync: status, signal, output, pid, stdout and
  stderr (Buffers, or strings with an encoding option); a spawn failure
  or timeout adds errno, syscall, path, spawnargs and error first.
- execFile's callback and the promisified execFile/exec: code (the exit
  status, or null after a signal), killed, signal and cmd, with stdout and
  stderr when promisified; a spawn failure has errno, code, syscall, path,
  spawnargs and cmd.
The sync forms echo the captured stderr before a timeout throw too, as
Node's inheritStderr does. The cp.execSync libCall takes a flag for Buffer
outputs.

An error's own `stack` stays non-enumerable when assigned through its
node. A worker's record-source table is freed when the worker exits.
2026-10-18 21:53:14 -05:00
Chris Tate f629d03dd8 Merge main 2026-10-18 21:28:03 -05:00
Chris Tate a7e3a9d0aa Cancel running subtests when their parent test times out (#1350) 2026-10-18 21:24:04 -05:00
Chris Tate c3b1303e7a Convert class instances to strings and numbers through their own methods (#1358) 2026-10-18 21:20:19 -05:00
Chris Tate d4d407bb07 Fix npm crashes, internal errors and false TDZ errors from the compatibility research (#1357) 2026-10-18 21:05:11 -05:00
Chris Tate 82683b5b4d Clear timers through handles that may be undefined or null (#1356) 2026-10-18 21:02:08 -05:00
Chris Tate e8e432b6dd Clean up subtests cancelled by their parent's timeout
node:test changes, matching Node 24.22:

- A subtest cancelled because its parent timed out has its mocks
  restored and its mock timers reset when it is cancelled, as Node's
  postRun resets the test's mock tracker. Before, a mocked method stayed
  mocked for the tests that ran after it.
- Its afterEach chain and t.after hooks run at the next turn, once the
  runner has moved on to the next test, as Node runs them when the
  cancelled test's own run resumes. Before, they never ran. The chain is
  copied when the test is cancelled, since its ancestors drop their hooks
  when they finish.
- Its hooks are then dropped, so closures that capture its context do not
  keep it alive. The registration parent is still not restored when the
  cancelled body settles: it belongs to the test that runs then.

The timeout-cancel-cleanup fixture records the mocked method and the hook
order against the next test's body and compares them with Node, plain and
with SCRIPTC_SAN=1. It fails without this change.
2026-10-18 20:58:20 -05:00
Chris Tate 2ed5136923 Convert class instances to strings and numbers through their own methods
String(x), template literals, `+` with a string and Number(x) or unary
plus on a program class instance failed with SC2001 ("values of type
'Money' cannot be compiled yet"), even when the class defines toString.
They now run Node's ToPrimitive protocol through the class's methods:

- `[Symbol.toPrimitive](hint)` when the class defines it, with the hint
  "string" for String(x) and template literals, "default" for `+`, and
  "number" for Number(x) and unary plus.
- Otherwise toString (string hint) or valueOf and then toString (default
  and number hints), and "[object Object]" (NaN as a number) when the
  class defines neither.
- The method dispatches like a call of it. Subclasses that convert
  through a different method are tested first with instanceof, so an
  array of base-class values converts each element by its own class.
- Unions of class instances and primitives (`Box | undefined`) convert
  each arm as it would alone.

Conversions the program can change at run time keep a named refusal: a
toString or valueOf instance property or prototype write, a
Symbol.toStringTag, and conversion methods that can return an object.
Builtin classes and their subclasses keep their existing conversions.
2026-10-18 20:55:25 -05:00
Chris Tate 3b46ea1603 Return undefined from generic functions that read a missing element (#1307) 2026-10-18 20:48:26 -05:00
Chris Tate 684f73d186 Add ThreadSlots, per-thread values on shared objects, to @scriptc/experimental (#1271) 2026-10-18 20:39:11 -05:00
Chris Tate c48983889c Clear timers through handles that may be undefined or null
clearTimeout, clearInterval and clearImmediate refused a handle typed
`Timeout | undefined` or `Timeout | null`, the type of a timer variable
that starts empty (`let timer: ReturnType<typeof setTimeout> | undefined`)
and of `handles[i]!` reads. Such a handle now passes id 0 when it is
absent, which matches no timer, so the clear does nothing, as in Node.
2026-10-18 20:35:23 -05:00
Chris Tate 4e92ec16d8 Compile derived constructors whose super() call leads a comma statement (#1354) 2026-10-18 20:25:58 -05:00
Chris Tate d9ac08e57c Merge main 2026-10-18 20:25:10 -05:00
Chris Tate ee9a0e1c33 Pool keep-alive sockets in http Agents and use globalAgent for default requests (#1230) 2026-10-18 20:21:54 -05:00
Chris Tate c03479abe5 Merge main 2026-10-18 20:16:15 -05:00
Chris Tate 2d37d451e9 Run iterator helpers on untyped iterators (#1352) 2026-10-18 20:07:41 -05:00
Chris Tate ada2657873 Stop creating self-capture cycles and buffer cycle candidates on the release fast path (#1320) 2026-10-18 20:03:40 -05:00
Malte UblandClaude Opus 5.5 18033dc42d Compile derived constructors whose super() call leads a comma statement
Minifiers join a derived constructor's statements into one comma
expression (`super(message, options), this.name = ..., ...;`), which jose
6.2.12 ships in its error classes. The statement lowering already runs
each operand of a statement-level comma as its own statement, so super()
is accepted when only parentheses and comma operators separate it from
its expression statement. Every other expression position keeps the
SC1090 refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:57:35 -07:00
Chris Tate 45d4105351 Compile maybe-promise unions, unknown[] rest parameters, and function-valued Maps and Sets (#1347) 2026-10-18 19:55:09 -05:00
Chris Tate f761f7d75a Count idle pooled sockets in agent.totalSocketCount
agent.totalSocketCount counted only the requests an agent was serving,
so the idle sockets of a keepAlive agent in agent.freeSockets were left
out, and a second request that dialed a new socket while the first was
idle counted 1 instead of 2. As in Node, the count is now the number of
sockets the agent created that have not closed: a socket joins it when
its dial starts and leaves it when it closes, or before the request's
'close' when it does not go back to the pool. A request that takes a
free socket keeps the count.

Corpus 5593 covers reuse, a second socket, agent.destroy() and an agent
without keepAlive.
2026-10-18 19:54:32 -05:00
Malte Ubl cdf731e4da Merge remote-tracking branch 'origin/main' into opt/npm-runtime-bugs
# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-modules.ts
#	packages/compiler/src/frontend/lowering/lower-stmts.ts
#	packages/compiler/src/frontend/program.ts
2026-10-18 17:50:35 -07:00
Malte UblandClaude Opus 5.5 20f15f89fd Let implicit JS instances that fall through return undefined
A JavaScript function specialized per call site takes its declared
return type from JSDoc. bcryptjs's `hash` and `compare` document
`@returns {!Promise}` but return nothing when a callback is passed, so
their instances ended with the "the checker proved every path returns"
trap and every callback-style call aborted with SC9002. Non-generic
JavaScript functions already widen such a return with an undefined arm;
implicit instances now do the same.

The widened `Promise<unknown> | undefined` result is then awaited by
TypeScript callers, which was refused: an await of a promise-of-dynamic
or unit union now goes through the dynamic await, which adopts the
promise or hops once for the unit, as Node does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:48:01 -07:00
Malte UblandClaude Opus 5.5 f8fa60afec Link the promise adapter when a boxed class instance holds a promise
Boxing a class instance as a dynamic value (Object.keys, spread,
inspection) boxes its fields, and a promise field goes through
scr_dyn_new_promise_adapting in scr_async_dyn.c. The link-unit scan
followed promises through records, arrays, unions and functions but not
class instances, so `Object.keys(job)` over a class with an optional
`Promise` field failed to link.

The scan now follows a class instance's fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:48:01 -07:00
Malte UblandClaude Opus 5.5 b6637c882d Check record, array and string fields that construction can read early
A field without an initializer that the constructor assigns only after
handing `this` to other code is undefined in Node until that assignment.
Class-typed fields in that position already take the undefined-armed
deferred-init slot whose reads check it. Record, array and string fields
kept a plain pointer slot, so a method the constructor calls first could
read null and crash with a segfault (`this.internal.n` read from a
method before `this.internal = ...`).

Those field kinds now take the same checked slot when a method or
accessor the constructor calls through `this` (transitively, over the
class and its bases) reads them. Fields only exposed in other ways keep
their plain slot, so constructors whose early calls never touch the
field compile as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:48:00 -07:00
Chris Tate ec79cf2789 Run iterator helpers on untyped iterators
In JavaScript, a receiver without an inferred type, such as an untyped
parameter's xs.values(), is the runtime's checked iterator. Its only
method was next(), so xs.values().filter(f), .map(f) and the other
iterator helpers threw "TypeError: ... is not a function" where Node
runs them, and [].values().reduce(f) threw the same instead of Node's
"Reduce of a done iterator with no initial value".

The native array, string, Map and Set iterators now answer the
Iterator.prototype methods. map, filter, take, drop and flatMap make
helper objects (SCR_DYNH_ITERATOR_HELPER) that pull lazily and support
next(), return(), for...of and spreads, print as
"Object [Iterator Helper] {}" and tag as "[object Iterator Helper]".
toArray, forEach, reduce, some, every and find drive the iterator.

The helpers close the iterator they were made from where Node does:
when a callback throws, when take's budget is spent, on return(), and
before the error for an invalid argument. flatMap closes its inner
iterator first. An error from a close that runs because of another
error is dropped. Invalid arguments throw Node's messages, such as
"number 5 is not a function" and "x must be positive".
2026-10-18 19:42:27 -05:00
Malte UblandClaude Opus 5.5 6a2d43b462 Keep declaration-file rest callbacks on their existing paths
Library callback slots such as String#replace's (substring, ...args: any[])
have intrinsic lowerings that inspect the unmapped function type, so only
rest parameters declared in program source take the checked-dynamic
variadic ABI. Found by the zero-cost gate on corpus 6165 after merging main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:30:57 -07:00
Chris Tate 7b2b864bdd Place functions too large to inline by their own name (#1283) 2026-10-18 19:30:32 -05:00
Chris Tate 3de2fb56a0 Cancel running subtests when their parent test times out
node:test changes, matching Node 24.22:

- When a test's { timeout } elapses, its subtests that are still running
  are cancelled, the innermost first. Each reports its duration so far and
  fails with "test did not finish before its parent and was cancelled",
  before the parent's result.
- A cancelled subtest's body keeps running unobserved: when it settles it
  reports nothing and leaves the runner's state alone. Before, it reported
  its result after the run's summary.

The timeout-subtests fixture compares the results, counts and failing
section with Node, plain and with SCRIPTC_SAN=1.
2026-10-18 19:28:55 -05:00
Chris Tate 6fd2dedb15 Merge main 2026-10-18 19:04:59 -05:00
Chris Tate 6ab49f33b2 Stringify class instances with JSON.stringify (#1338) 2026-10-18 18:59:22 -05:00