Commit Graph
6258 Commits
Author SHA1 Message Date
Malte UblandClaude Opus 5.5 2acd199fbe Give an async object-literal method its contextual return type
An async method in an object literal with no return annotation and a
contextual signature inferred its own (narrower) literal type, while its
`return` literal was built at the contextually typed record: the return
statement then had to convert the wider record into the narrower literal,
which fails with SC2002. The method now takes its return type from the
contextual property's call signature, as the arrow form does. Sync
methods are unchanged; they already adopt their slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:55:50 -07:00
Malte UblandClaude Opus 5.5 9eaa210e1f Narrow filtered union elements into a record with an arm's fields
A written type predicate whose target is no arm of the element union
but the same fields as one, with a nested union field narrowed
(Model<"a"> from the Model<Api> arm), was refused. The kept elements of
that arm now convert field by field, the nested union by its tag; any
other kept element still throws the predicate TypeError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:55:50 -07:00
Chris Tate d1abf69149 Refresh the runtime profile and convert ASCII case eight bytes at a time (#2492) 2026-10-28 20:15:03 -05:00
Ben SabicandBen Sabic 1e48ebe187 chore(docs): add Technical Writer agent skill (#2447)
Adds the technical-writer skill from vercel/chat to .agents/skills, with a
.claude symlink to .agents so Claude Code finds it. AGENTS.md now asks agents
to use the skill for prose in this repo.

CONTRIBUTING.md gets a matching "Writing with AI coding agents" section.

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-10-29 11:35:20 +11:00
Chris Tate 84644dfe9d Send the writeHead head with the response's first write (#2488) 2026-10-28 19:18:26 -05:00
Chris Tate 7f0c987140 Key Sets and Maps of identity-arm unions by their pointer representation (#2487) 2026-10-28 19:08:20 -05:00
Chris Tate 61c24c08e9 Refresh the runtime profile and convert ASCII case eight bytes at a time
scr_str_case_conv scans ASCII text for the first letter to convert eight
bytes at a time, then converts the rest eight bytes at a time; the tail
and big-endian targets take the byte loop. With a refreshed profile, the
byte-at-a-time scan compiled worse for short strings with nothing to
convert, which made validate-errors slower.

The runtime profile is collected again on this change with
scripts/refresh-runtime-profile.mjs (326 raw profiles). Changed
functions hold 0.0% of its counts, against 3.7% for the previous one.

tests/corpus/string-case-ascii-words.ts covers ASCII strings of every
length from 0 to 40, with letters at block boundaries and next to the
bytes just outside A-Z and a-z.
2026-10-28 18:43:37 -05:00
Chris Tate 18ab17848e Keep the held head across a cork() and uncork() with no write
An uncork() flushed a head writeHead stored even when nothing was
written while corked. Node keeps the stored head until write(), end() or
flushHeaders(), so the flush now sends it only when a write() or
flushHeaders() ran while corked. The http-held-head fixture covers the
three cases.
2026-10-28 18:23:49 -05:00
Chris Tate 33799052e5 Keep the source stack-box tests' Set element unions boxed through the emission option
The call-lifetime and string-and-array lifetime tests kept their optional element union a box with a `Set<Item | undefined>`, which no longer boxes a union of identity arms. They now emit with keepSetElementsBoxed, which keeps the unions the module's Sets hold boxed.
2026-10-28 18:22:49 -05:00
Chris Tate 94e921728c Island handle operations, native subclasses of island classes, arrays and collections by reference (NI-5b/5c/5d) (#1903) 2026-10-28 18:09:56 -05:00
Chris Tate f233fb5c1b Keep child-process executables byte-identical when the new listener APIs are unused (#2491) 2026-10-28 18:01:26 -05:00
Chris Tate 7409c85216 Compile .length of computed tuple expressions (#2482) 2026-10-28 17:51:45 -05:00
Chris Tate 9f3fb0fb17 Keep glibc's memset, memcpy, memcmp and bcmp in zig-linked executables (#2490) 2026-10-28 17:47:23 -05:00
Chris Tate 3919eec758 Visit only sockets with deferred work in the net sweep (#2489) 2026-10-28 17:32:13 -05:00
Chris Tate 3a1ba89194 Throw for .length of a missing tuple element
An unchecked element read of a tuple array (`rows[i].length` with
`rows: [number, string][]`) answers the element's optional value, and the
computed `.length` path discarded it, so a read past the end printed the
tuple's arity. It now holds the receiver and throws Node's TypeError,
"Cannot read properties of undefined (reading 'length')", when the element
is missing, before answering the arity.

The corpus program tuple-length-computed.ts covers a missing element read
directly and through a field holding the array.
2026-10-28 17:04:51 -05:00
Malte UblandClaude Opus 5.5 1962a28fd7 Keep child-process executables byte-identical without the new listener APIs
#2468 shipped child.off()/removeListener() and child.signalCode in the gated
scr_child_lifecycle.c unit, but it also restructured the always-linked
scr_child.c terminal dispatch loops to detach their listener arrays before
emitting. scr_child.c is compiled once per program and linked into all of
them, so its object moved and every child-process executable grew: the
zero-cost gate reported 55 runtime-only corpus programs.

Restore scr_child.c byte for byte and move the emit-snapshot discipline into
the gated unit instead. The base loops already bracket each terminal emit
with a state flag that only the emitter writes -- `settled` for 'exit' and
'error' (set after the stream drain, immediately before the terminal branch)
and `close_done` for 'close' (set at the top of the close emit) -- and run no
user code in between. A removal inside that window is therefore a no-op: the
entry stays, the in-flight emit still delivers it, which is exactly Node's
answer (emit copies the listener array), and the emitter's own drop releases
it. Outside the window no emit of that event is in flight, so removal in
place is Node-exact, and the base already releases late registrations once
the window has closed. 'spawn' detaches its array before invoking its
listeners, so removal during it finds an empty registry.

scr_child_private.h repeats the structure layout for the gated unit only;
scr_child.c keeps its own definitions untouched.

Validation: zero-cost gate against 68980c241 reports no runtime-only program
and no scr_child symbol in any IR, LLVM or executable difference; scr_child.o
is byte-identical to the base in all six shipped flavors, and the
scr_child_off_*/scr_child_signal_code entries live in scr_child_lifecycle.o.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 15:02:54 -07:00
Chris Tate 94215d6f0e Keep glibc's memset, memcpy, memcmp and bcmp in zig-linked executables
The BigInt reciprocal divided a two-limb value by one limb, which compiles
to a __udivti3 call. zig provides that builtin from its compiler-rt, a
single object that also defines memset, memcpy, memmove, memcmp, bcmp,
strlen and the _chk variants. Linking it for __udivti3 made those
definitions take precedence over libc's in every glibc executable built
with SCRIPTC_CC=zigcc, and zig's bcmp and memcmp compare one byte per
iteration and its memset stores one byte at a time.

The reciprocal now divides in 32-bit halves with one-limb operations, so
the runtime references no compiler-rt builtin and glibc executables import
the four functions from libc.
2026-10-28 17:01:19 -05:00
Chris Tate 5c49604e41 Visit only the sockets with deferred work in the net sweep
The sweep and the dispatch loop's pending check walked every registered
socket on each loop pass, so a server with many keep-alive connections
paid for all of them on every request. A socket now joins a work queue
when it may have deferred work (bytes buffered for a consumer, write
callbacks waiting for the drain, 'finish', an error or 'close') and
leaves it when a sweep visit leaves it none. The queue is ordered by
registration, so the sweep visits sockets in registry order as before.
With SCR_RC_AUDIT, the dispatch loop checks that no registered socket
outside the queue has deferred work.
2026-10-28 16:49:22 -05:00
Chris Tate 7384ff616e Keep in-loop dyn releases inline in bodies over the release budget (#2485) 2026-10-28 16:49:02 -05:00
Chris Tate 2814b57e20 Send the head writeHead stores with the response's first write or end()
res.writeHead() builds the head and keeps it until the first write(),
end() or flushHeaders(), which sends it with that call's data in one
socket write, as Node's _storeHeader does. A response with an Expect
header sends the head at once, as in Node. The http-held-head server
fixture compares the wire timing with Node's.
2026-10-28 16:35:39 -05:00
Chris Tate 6cc5345a41 Key Sets and Maps of identity-arm unions by their pointer representation
A union that is a Set element or a Map key was a tagged box in the whole
program, because the C map hashes and compares union keys by tag and
payload. One `Set<Node | undefined>` made every `Node | undefined` value
of the program a box, at every read.

A union whose arms all compare by identity (records, class instances,
arrays, functions), beside at most one undefined and one null arm, now
keeps its pointer representation in collections too. The map keys such a
union by reference: the pointer carries the identity, and NULL and the
null sentinel are two more distinct keys. Unions with a string or another
value arm keep their boxes, and library builds keep the earlier rule.

The tests of the box paths keep their unions boxed through a new
`boxedUnions` emission option instead of a Set of each union.
2026-10-28 16:22:38 -05:00
Chris Tate 3a15562085 Follow a redirect whose chunked body has not ended (#2484) 2026-10-28 16:06:56 -05:00
Chris Tate 4d1e3fbbcb Keep small listener snapshots of readline and EventEmitter emits on the stack (#2483) 2026-10-28 15:51:22 -05:00
Chris Tate 7f950bbbf9 Keep in-loop dyn releases inline in bodies over the release budget
A function with more than INLINE_DYN_RELEASES_PER_FUNCTION inline dyn
releases called scr_dyn_release at every site. A script's top-level loop
runs in its module initializer, which often has more sites than that, so
its hottest releases became calls. The emitter now marks inline dyn
releases emitted inside loops, and a body over the budget keeps up to the
budget of those inline, calling the runtime at the rest.
2026-10-28 15:48:47 -05:00
Chris Tate fb5df61367 Reuse lib diagnostics across options that cannot change them (#2472) 2026-10-28 15:41:23 -05:00
Chris Tate 361e660a72 Give the entry module's top-level code a partition of its own (#2480) 2026-10-28 15:31:45 -05:00
Chris Tateandlinguofeng 46058c67a2 Report an aborted client response's error to its 'error' listeners only
A client response cut off mid-body emitted 'error' ("aborted") whether or
not it had an 'error' listener, so a response without one ended the
process with "Unhandled 'error' event". Node's IncomingMessage reports its
destroy error only to 'error' listeners; 'aborted' and 'close' still fire.

fetch leaves a redirect response by destroying its request, so following
a redirect whose chunked body had not ended crashed the process before
fetch settled. It now follows the redirect.

Fixes #2395

Co-authored-by: linguofeng <460328+linguofeng@users.noreply.github.com>
2026-10-28 15:19:07 -05:00
Chris Tate 061020b2f8 Add --exceptions=rare to unwind through recursive functions (#2481) 2026-10-28 15:17:22 -05:00
Chris Tate ee30477142 Keep small listener snapshots of readline and EventEmitter emits on the stack
readline's emit and EventEmitter's emit copy the listener list before
running it, so that listeners added or removed during the emit don't
change who runs. The copy was a malloc and a free per emitted event: per
line for a readline 'line' listener. A list of at most four listeners is
now copied into a stack buffer; longer lists still allocate.

A new corpus program pins the snapshot semantics for lists below and above
four listeners: a listener added during an emit runs from the next emit, a
removed one still runs in that emit, and a once listener runs once.
2026-10-28 15:07:25 -05:00
Chris Tate d83a5dc929 Fix census WP-2 stdlib/@types sites: void-slot lambda ABI, ChildProcess.off/signalCode, writeFileSync const options, path sub-namespaces, stdout writableLength (#2468) 2026-10-28 15:04:48 -05:00
Chris Tate e80b5f1447 Compile .length of computed tuple expressions
`.length` of a tuple is its arity, and the lowering folded it to that
constant only when the receiver was rooted at a binding or `this`. Any
other receiver (a call result, a ternary, an `as` assertion, an `as
const` literal) was refused with SC1090.

The receiver is now evaluated once, for its side effects and exceptions,
and its value discarded; the expression is the arity. A binding, `this`,
or a chain of plain property reads from one still folds. A chain through
an accessor is evaluated, so an accessor that returns a tuple runs, as in
Node.
2026-10-28 14:57:21 -05:00
Chris Tate 1edb46545a Lower more pi constructs: tuple and keyed reads, mixed union fields, spreads, assertion probes (#2476) 2026-10-28 14:51:13 -05:00
Chris Tate 4066cf1d1a Lower Object.getOwnPropertyNames over records and Array.prototype.join with a dynamic separator (#2475) 2026-10-28 14:44:07 -05:00
Chris Tate 377af617d3 Add --exceptions=rare to unwind through recursive functions
With --exceptions=rare, recursive functions and functions up to three
calls above a throw also propagate exceptions by unwinding on Linux, so
calls to them need no pending-flag test. Functions with a throw of their
own keep the flag. The default, common, keeps the current rules, and
builds without the flag emit the same code as before.

The option is part of the executable cache key; the bootstrap's cached
route leaves builds with it to the full compiler, and coverage and
library builds refuse it.
2026-10-28 14:28:28 -05:00
Chris Tate 7e2e47aa8d Report unhandled rejections in programs without async work (#2479) 2026-10-28 14:13:38 -05:00
Malte UblandClaude Opus 5.5 cbd26d3a11 Merge remote-tracking branch 'origin/main' into fix/pi-lowering-constructs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:01:49 -07:00
Malte UblandClaude Opus 5.5 d25f3ba44b Evaluate effectful receivers and keys once in the new lowerings
The keyed and tuple-index dispatches read the receiver and key inside a
discard statement, which re-evaluated an effectful receiver expression
(an `in` test over a call result) and ignored a key call that rebinds the
receiver binding. Both now settle the receiver into a hidden local before
the key evaluates, and the inherited-key probe returns its answer
directly instead of wrapping it in a discard statement.

Narrow the union field write's no-static-supertype path to unions that
have a dynamic arm, so a union of unrelated static storage types keeps
its refusal, matching the read path; a runtime-keyed spread into a record
is covered by the checked extraction test instead of the refusal table.

Review threads:
- 4403396631 inherited-key probe evaluated an effectful receiver twice
- 4403396650 key/index calls that reassign the receiver binding

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:01:33 -07:00
Chris Tate 31a9be23a4 Give the entry module's top-level code a partition of its own
The emitter writes "; scriptc-entry" before the entry module's top-level
function (its %init). In modules of fewer than 32 partitions, the helper
places the roots that hold a marked function first, and their partitions
take no other root. A root that finds no partition with room goes to the
first open partition from its own.

An edit of the program's top level then recompiles only the top-level code
and the chain that follows it, not the other roots that balancing placed
beside it.
2026-10-28 13:57:13 -05:00
Malte UblandClaude Opus 5.5 de7f40e87a Merge remote-tracking branch 'origin/main' into fix/pi-wp6b-stdlib-tail
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:57:03 -07:00
Malte UblandClaude Opus 5.5 81580f44ad Merge remote-tracking branch 'origin/main' into ni/i6-subclasses
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:56:30 -07:00
Malte Ubl 7dcf1376b6 Merge remote-tracking branch 'origin/main' into fix/pi-lowering-constructs 2026-10-28 11:40:29 -07:00
Chris Tate ad368e353d Lower Response.clone and Request.clone on typed values (#2473) 2026-10-28 13:37:52 -05:00
Chris Tate f61c40e4e7 Space the timer delays in two order-sensitive tests and raise the static-spatial timeout (#2478) 2026-10-28 13:36:39 -05:00
Malte UblandClaude Opus 5.5 031b46d2b7 Evaluate the stream receiver before the writableLength constant
A procStream-typed receiver that is a call or an accessor read now runs
for its effects, and propagates its throw, before the fold answers 0: the
constant sits in a seqExpr behind an exprStmt receiver, the same
evaluate-for-effect shape objectIsDisjointFalse uses. A pure alias still
folds to the bare constant. The corpus program covers a call receiver, an
accessor receiver and a throwing receiver against Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:33:39 -07:00
Malte UblandClaude Opus 5.5 753532a3da Merge origin/main into fix/pi-wp2-stdlib-node
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:33:39 -07:00
Chris Tate 911857561e Read typed-array constructors, NaN and Infinity in shorthand properties (#2477) 2026-10-28 13:26:13 -05:00
Chris Tate 7bb87229c2 Report unhandled rejections in programs without async work
A program with no async functions, generators or timers never ran the
event loop, so a promise rejection nothing handled went unreported:
`Promise.reject(new Error('x'))` alone exited 0, where Node prints the
error and exits 1. The loop now also runs when the program makes a
promise (a Promise static, `new Promise` or Promise.withResolvers), and
its end reports the rejection, runs the 'exit' listeners with code 1 and
exits 1, as for async programs. Programs that make no promise keep their
code unchanged.
2026-10-28 13:23:10 -05:00
Chris Tate 55a803c733 Read the array's length before converting a join separator
Array.prototype.join reads the length before it converts the separator.
The lowering converted the separator first and let the join read the
length afterwards, so a separator whose toString changed the array joined
the wrong number of elements. When ToString of the separator value can run
code of the program (a checked-dynamic value, an object or class instance
with toString), the join now reads the length first: an array that grew
joins its first elements, and the missing elements of one that shrank join
as empty strings. Primitive separators keep the direct join.
2026-10-28 13:07:14 -05:00
Chris Tate 577f086658 Space the timer delays in two order-sensitive tests and raise the static-spatial timeout
mock-timers' "named and namespace imports keep the loop's timers" and
6811-node-island-uncaught-listener check the order in which real timers
fire, with delays 1 to 5 ms apart that are scheduled one after another.
On a busy runner more time than that can pass between scheduling two of
them, and the order flips in either lane: the sanitized node:test run saw
Node's reference print 'named promise' first, and the sanitized
llvm-differential run saw 'island timer 1' before 'native timer after'.
The delays are now 100 ms apart in mock-timers (100, 200, 300) and the
island's timers (150, 250) start 100 ms after the native ones (10, 50).
Node prints the same output as before.

static-spatial bundles three.js and builds it with the sanitizer, which
passed the default 300 s test timeout on a busy runner. Its cases take
three.test.ts's 1200 s timeout.
2026-10-28 12:49:21 -05:00
Chris Tate 5a1021dcd1 Fence Response.formData in the node-types fenced fixture
Response.clone lowers now, so the fixture's unlowered Response
operation is formData, which keeps the same fence and hint.
2026-10-28 12:42:13 -05:00