Commit Graph
2624 Commits
Author SHA1 Message Date
Malte UblandClaude Opus 5.5 3149e7be91 Regenerate the surface manifest
Date.prototype.toUTCString and the static Response.json operation are
static surfaces now (pnpm manifest). pnpm node-compat leaves the Node
ledger and the public artifact unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:19 -07:00
Malte UblandClaude Opus 5.5 b831d840d7 Accept getters that never return a value
A getter that only throws (`get page(): never`, or Next.js's unannotated
removed-API getters) or has no return statement declared a VOID property
type, so the class was refused (SC2001). No read of such a getter produces
a value other than undefined; the property now takes the checked-dynamic
slot, and each read throws the getter's error or answers undefined.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:19 -07:00
Malte UblandClaude Opus 5.5 cd63b935e0 Instantiate generics with a literal undefined argument
`id(undefined)` binds T = undefined, which the type mapper answers as VOID
(the return convention), so the instance's parameter was refused (SC2001)
although a declared `v: undefined` parameter compiles. Instantiated
parameters are value positions and now take the unit-only slot, as
declared ones do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:18 -07:00
Malte UblandClaude Opus 5.5 dd0c255fa7 Stringify class instances that declare toJSON
`JSON.stringify(new Url(...))` was refused (SC1090) for every program
class, although JSON.stringify of a class with a toJSON hook serializes
whatever toJSON(key) returns. A class whose chain declares toJSON now
crosses into the runtime JSON walker through its checked-dynamic class
view, which calls the hook with the root key "" and handles nested hooks;
an undefined hook result keeps the dyn-root undefined rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:18 -07:00
Malte UblandClaude Opus 5.5 a8b7bafefb Lower ...(cond && { k: v }) spreads as conditional fields
Spreading a falsy primitive copies nothing, so `...(cond && { k: v })` is
the optional-field idiom `...(cond ? { k: v } : {})`. The `&&` spelling
(the @edge-runtime/cookies parseSetCookie shape) was refused with a record
shape mismatch (SC2002).

When the spread's type mixes the record with primitive arms, the literal
lowering now uses the existing conditional-spread desugar: cond evaluates
once, v only when taken, and the absent arm leaves the optional field
undefined. Record and record-or-unit spreads keep the source-copy path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:18 -07:00
Malte UblandClaude Opus 5.5 e6dbad9567 Destructure Maps and Sets with array patterns
`const [[k, v], ...rest] = map` was refused (SC1031). The source now
snapshots once into its iteration order (Map entries as pairs, Set
values), the spread's own machinery, and the pattern lowers as an array
pattern over that snapshot, as string sources already do. Element defaults
keep a fence because they run between iterator steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:17 -07:00
Malte UblandClaude Opus 5.5 30e23af63f Store fetch Headers, Request, and Response handles in class fields
A class field typed `Headers`, `Request`, or `Response` (or an array or
nullable form of them) maps to checked-dynamic storage, and the class-field
rule refused it (SC1090) although `unknown`, Map values, and closures
already store the same native handles.

Such fields now take the checked-dynamic slot, like the worker handles;
reads keep the handle's identity and the static fetch member lowering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:17 -07:00
Malte UblandClaude Opus 5.5 6c1af69d03 Add static Response.json
`Response.json(data, init)` was refused (SC2020). It now builds a native
Response the way undici does: ResponseInit converts, the data serializes
through the checked-dynamic JSON serializer (toJSON hooks included;
TypeError "Value is not JSON serializable" when JSON.stringify answers
undefined), then the response initializes like the constructor with
"application/json" as the default content type, so status, statusText,
header, and null-body-status errors keep their order.

The fetch compatibility profile moves Response.json from unsupported to a
static operation with the static-response-json differential fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:17 -07:00
Malte UblandClaude Opus 5.5 1fd405c033 Add Date.prototype.toUTCString
`new Date(ms).toUTCString()` was refused (SC2020). It now lowers to a
runtime formatter over the stored time value with V8's output: "Www, DD
Mmm YYYY HH:mm:ss GMT" in UTC, a leading "-" and at least four digits for
negative years, and "Invalid Date" for an invalid time value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:16 -07:00
Malte UblandClaude Opus 5.5 58d3981476 Accept initializer-less symbol-keyed fields assigned in the constructor
A computed TypeScript field without an initializer (`[INTERNALS]: T`)
was refused even with a stable Symbol()/Symbol.for() or literal key, which
contradicts the documented rule that TypeScript fields need an initializer
or an unconditional constructor assignment.

Such a field now takes its declared type and the identifier fields'
definite-assignment rule: an unconditional top-level `this[key] = ...` in
the constructor, otherwise the checked deferred-initialization slot. Types
that admit undefined (optional fields) start undefined.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:16 -07:00
Malte UblandClaude Opus 5.5 4d3dde55a7 Return undefined from JSON.stringify of unknown roots where it is observed
JSON.stringify answers undefined for an undefined, function, or symbol
root. Through `unknown` (or a generic instantiated with it) the dyn-root
lowering converted the runtime result to a string, so the result was the
text "undefined": `text === undefined` was false and `typeof text` was
"string".

Where the result reaches a use that distinguishes undefined from that text
(undefined/null comparisons, typeof, ??, ?., truth tests, a return from a
function declaring undefined, or an unannotated local binding used that way), the
call now keeps the checked `string | undefined` result the replacer path
already produces; later string uses of such a binding narrow by the
checker's flow type. Other uses keep the existing string ABI, so programs
that cannot observe the difference compile unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:15 -07:00
Malte UblandClaude Opus 5.5 def4626396 Lower optional calls of always-present library methods as plain calls
`h.getSetCookie?.()` on a native Headers handle failed with SC2020: the
optional-chain lowering first reads the callee as a value, and native
handle methods have no first-class value. A required standard-library
method on a never-nullish receiver always exists, so `?.()` is the plain
call.

When that method read has no lowering, the optional chain now re-dispatches
the plain call. Reads that lower keep the existing path unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:15 -07:00
Malte UblandClaude Opus 5.5 43cefeb56b Retag flow-narrowed unions before primitive toString()
`headers.host.toString()` after `!Array.isArray(headers.host)` narrows a
`string | number | string[]` slot to `string | number`, and the union
toString lowering accepted the narrowed checker type but emitted the toString
node over the wider storage union. The validator rejected the non-stringable
arm as an internal compiler error (SC9001).

The lowering now retags the read to the proven union (the same checked
coercion other narrowed reads use) before the per-union ToString helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:15 -07:00
Malte UblandClaude Opus 5.5 2957a9c042 Report unparsable fetch and Request URLs as "Failed to parse URL from"
undici's Request constructor wraps the URL parser's failure: TypeError
"Failed to parse URL from <input>" with the parser's TypeError as its
non-enumerable cause. fetch() rejected, and new Request() threw, with the
bare "Invalid URL" error instead.

Both paths now replace the pending parser error with the wrapping
TypeError (sf_throw_url_parse_failure).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:14 -07:00
Malte UblandClaude Opus 5.5 46d238fc0a Send repeated Set-Cookie request headers as one combined line
Headers stores each Set-Cookie value separately, but undici dispatches its
header list's combined value: one line with the values joined by ", " at
the position of the first one. fetch() wrote one line per value.

The request serializer now joins later Set-Cookie values into the first
entry and skips the rest, for every HeadersInit form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:14 -07:00
Malte UblandClaude Opus 5.5 77b137268d Visit each Set-Cookie value separately in Headers#forEach
Fetch's "sort and combine" never combines Set-Cookie: Node's Headers
iterator yields one entry per Set-Cookie value, in insertion order, while
get() still joins them with ", ". The native forEach walked the sorted
unique names and read the combined value, so repeated cookies reached the
callback as one joined string.

forEach now indexes the expanded list per step (sf_headers_sorted_entry),
keeping the live re-sort so callback mutations shift later entries as in
Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:14 -07:00
Chris Tate ea7bcd9846 Resolve builtin classes through const aliases (#1327) 2026-10-18 15:42:15 -05:00
Chris Tate 3be66127c2 Fence refused statements inside capture buffers instead of dropping them (#1322) 2026-10-18 15:24:10 -05:00
Chris Tate 547e59529f Resolve builtin classes through const aliases
`const E = EventEmitter`, `const M = Map`, `const T = TypeError` and
`const R = Readable` stopped at SC2020, and so did `typeof EventEmitter`,
`Readable.name`, `Map.name`, `Error.name` and `===` between builtin
classes. The zlib class alias resolver now covers the error classes,
EventEmitter, the node:stream classes and Map/Set: a const binding of one
(an alias of an alias, or an export used through a named or namespace
import) constructs, extends and tests instanceof like the direct name,
and typeof, .name and === fold for aliases and direct names.

In TypeScript files the alias owns no runtime slot and other value uses
report SC1090 at the use. In JavaScript files a copy of a name or of a
globalThis property keeps its slot, which holds the same value as the
name it copies; builtin module members (`zlib.Gzip`) own none, as before.
2026-10-18 15:23:20 -05:00
Malte UblandClaude Opus 5.5 c20c21b22c Declare the deferred fences of 6110 for the coverage contract
The corpus program deliberately carries refused statements on untaken paths; the `// @deferred-fences: 9` header is the coverage sweep's exemption for such programs (9 failed statements across the speculative and final lowerings), so it still runs against Node in the differential oracle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 13:05:47 -07:00
Chris Tate b866ae3236 Report refused local classes from speculative type queries again (#1321) 2026-10-18 15:04:40 -05:00
Chris Tate 1c1400272f Keep the compiler's kind and code methods direct (#1317) 2026-10-18 14:43:02 -05:00
Chris Tate 42591b1eed Read zlib options held in typed objects (#1316) 2026-10-18 14:24:54 -05:00
Chris Tate 470d317703 Keep class methods named kind and code direct in the compiler
resolveInferredReturn turns a return statement into a runtime fence
through a Record<string, unknown> view with element writes
(`mutable["kind"] = ...`). The class method census treats a literal
element write as a possible write to every method of that name, so in the
native compiler every method named `kind` or `code` became a dynamic
callback: AstWireFile.kind, called for each AST node access, went through
a property bag and a method value.

Property writes go through the receiver check instead, and no class
instance is assignable to Record<string, unknown>, so those methods are
called directly again. Output of the compiler is unchanged.
2026-10-18 14:21:32 -05:00
Chris Tate 240f576805 Add execution tiers to the IR and a report-only tier analysis (#1315) 2026-10-18 14:08:12 -05:00
Malte UblandClaude Opus 5.5 5746dc30c5 Expect the compile to refuse stream-shims and node24-file-url-conversion again
With the local-class refusal reported again, both npm fixtures fail to compile with SC1090 as they did before #1303; restore their build expectations. The deleted corpus program 6102 also leaves the measured cost table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:05:40 -07:00
Malte UblandClaude Opus 5.5 f649959609 Report refused local classes from speculative type queries again
The previous change dropped the diagnostics of a function-local class refused during a speculative whole-program type query, expecting the class's own declaration to report them. In JavaScript that declaration only defers them to a runtime fence, so two npm programs that the compile refused (stream-shims and node24-file-url-conversion, both SC1090) began to compile and diverged from Node at run time. The speculative query now leaves the refusal reported, as it was wherever such an analysis already caught the poison, and only stops the poison from escaping the compiler.

The JavaScript crash repro becomes a diagnostics fixture (local-class-type-query-js.js); the corpus program 6102 never crashed the base compiler and is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:05:25 -07:00
Malte UblandClaude Opus 5.5 8531a45509 Record baselines for the capture-buffer fence fixtures
The diagnostics snapshot for speculative-function-refusal.ts and the ts7 order-parity baselines for it and 6110-cjs-speculative-wrapper-fences.cjs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:04:28 -07:00
Chris Tate d9f0005ecb Represent unions of records as the record pointer itself (#1225) 2026-10-18 14:04:16 -05:00
Chris Tate a5548597e5 Read zlib options held in typed objects
A zlib options object that reaches the runtime as a typed reference (a
cast object literal such as `{ level: 0 } as zlib.ZlibOptions`, which the
checked-dynamic options slot holds by reference) was not a plain
checked-dynamic object, so the options reader found no keys: every option
was ignored and out-of-range values were not reported. The sync, callback
and promisified one-shots and the stream constructors compressed with the
defaults instead.

scr_zlib_options_parse and scr_zlib_stream_new now read a typed reference
through its materialized view, as the blob and fetch option readers do, so
the options apply and invalid ones throw Node's ERR_OUT_OF_RANGE.
2026-10-18 14:02:03 -05:00
Chris Tate 329ea4387e Convert a Date to local time once for its local getters and follow TZ changes (#1313) 2026-10-18 13:57:42 -05:00
Malte UblandClaude Opus 5.5 b0ce48f168 Fence refused statements inside capture buffers instead of dropping them
A refused statement in a JavaScript function body was dropped without a diagnostic or runtime fence when the body lowered inside a capture buffer whose owner kept the result. tryLowerExpression lowers the receiver of `(function () { ... }).call(this)` or `.apply(...)` and a computed class base that way: the refused statement (for example `console.log(root === module.exports)`) was missing from the kept function, its diagnostic was handed back to the enclosing statement, and that statement, having lowered cleanly, filed it as an informational runtime-fence entry. The program printed nothing for that statement.

lowerStmts now emits the statement's runtime fence inside a buffer as well (Lowerer.capturedRuntimeFence, the same message deferToRuntimeFence would give), leaving the diagnostics with the buffer's owner. The three JavaScript class initializer sites that skipped their fence under a buffer (static field, instance field, static initializer block) do the same. An owner that discards the candidate discards the fence with it, so every program that compiled before keeps byte-identical IR unless it contained such a dropped statement: all 3006 corpus programs produce the same IR as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 11:47:04 -07:00
Malte UblandClaude Opus 5.5 fa64c19fdc Build the coverage result without a conditional spread over its tiers
The self-hosted compiler refuses a conditional spread of `tiers` over a
LowerResult that already declares the field, so assign it instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 11:45:52 -07:00
Chris Tate 2d9604eff5 Fix get-or-create records, local-class type queries, builtin typeof and CommonJS top-level this (#1303) 2026-10-18 13:44:35 -05:00
Chris Tate e5c45f8253 Merge main 2026-10-18 13:41:18 -05:00
Chris Tate f80eb5fc39 Give every arm of record unions that share arms a collector header
A shape union's arms all carry a collector header when one of them does.
The emitter applied that rule in one pass over the unions in module order,
so a header that a later union gave a shared arm did not reach the other
arms of an earlier union. With First = A | B and Second = B | C, where only
C is cyclic, B got a header through Second, First's trace helper then
called A's trace function, and A had none: the module failed LLVM
verification with "use of undefined value '@sc_rtrace_r0'".

The rule now repeats until no union adds a header. Corpus
5919-record-unions-shared-arms chains Third = D | A, First = A | B and
Second = B | C, so the requirement passes from Second through First to
Third.
2026-10-18 13:39:12 -05:00
Chris Tate fe7b1cd199 Convert a Date to local time once for its local getters and follow TZ changes
Each local-time getter (getFullYear, getMonth, getDate, getHours, ...,
getTimezoneOffset) called localtime_r and walked the calendar for its
own field. The thread now keeps its last local conversion, keyed by the
instant and a timezone generation, so reading several local fields of
one Date converts it once. The UTC walk and the second computation use
integer division instead of floor.

Writing or deleting process.env.TZ calls tzset and starts a new
generation, so later local-time reads use the new zone, as in Node.
Before, the zone the process started with stayed in effect.
2026-10-18 13:33:41 -05:00
Chris Tate 359bd29b1b Stop repeating two frontend scans (#1312) 2026-10-18 13:30:45 -05:00
Chris Tate 761252e173 Expect the run-time refusals of node24-file-url-conversion and stream-shims
Both fixtures declare a function-local class whose base has no static
lowering (`class FileURL extends URL {}`, `class Counter extends
Readable`). The SC1090 for it used to come from the whole-program type
queries, which collected the class outside any statement, so the build
failed. Those queries now keep a refused local class out of their
results and drop its diagnostics; the class declaration reports the
refusal when it lowers, and in a JavaScript source a refused statement
becomes a runtime fence. Both programs therefore compile and throw the
fence when they reach a construct without a lowering, like the other
fixtures listed with a run refusal.
2026-10-18 13:20:51 -05:00
Chris Tate 16da839c62 Merge main 2026-10-18 13:16:06 -05:00
Chris Tate 09b0dffebc Walk dead cycles once in the cycle collector (#1286) 2026-10-18 13:09:16 -05:00
Chris Tate f3b5696d5d Stop repeating two frontend scans
Class dynamic dispatch decides whether a class family keeps a fixed
field layout from the property names a delete can remove. It cached that
set with ??=, but the scan returns null when the program reflects on
properties, so a program that does recomputed it, walking every source
file, for each class in every dispatch wave. Only an undefined value now
means the set has not been computed.

npm-static re-export pruning read and parsed a package's package.json
for every file whose purity it checked, and for every package boundary
an import crossed. Each plan now parses each package.json once.

Output is unchanged. three.js nodes lowering takes 6 s less and an
Effect fixture's frontend load 4 s less.
2026-10-18 13:08:49 -05:00
Chris Tate 13f44cac9c Report unhandled rejections after each timer and immediate callback (#1311) 2026-10-18 13:06:56 -05:00
Chris Tate a08aada5f5 Name 5980-cycle-components.ts in its order baseline
The program moved from 5973 with its baseline, but the baseline still
recorded the old path as the module order.
2026-10-18 12:48:38 -05:00
Chris Tate 7cfb6b16d2 Report unhandled rejections between timers and between immediates
Node runs runNextTicks after each timer and each immediate callback, which
reports the unhandled rejections the callback left before the next one runs.
The event loop now leaves the timer phase when a callback leaves the
rejection ledger non-empty, the check phase waits for that report, and the
check phase reports the ledger after each immediate's checkpoint.

When several timers expired in the same turn, a later timer ran before an
earlier timer's rejection was reported. On macOS the 2 ms and 3 ms timers in
5809-process-exit-and-rejections.cjs expire together, so its uncaught
'thrown string' was printed before the TypeError rejection.

5996-rejections-between-macrotasks.cjs makes five timers expire in the same
turn and checks the order across three immediates.
2026-10-18 12:47:01 -05:00
Chris Tate 4b8b5e3b81 Revert "Read fields as undefined before their assignment" (#1310) 2026-10-18 12:38:47 -05:00
Chris Tate debbd359d7 Merge main 2026-10-18 12:30:17 -05:00
Chris Tate 29f6d8e47f Merge main 2026-10-18 12:28:02 -05:00
Chris Tate f59049112f Compile chalk 5 through the dynamic object model (#1309) 2026-10-18 12:25:45 -05:00
Chris Tate cb3d7dbe7c Merge main 2026-10-18 12:24:21 -05:00