Commit Graph
2626 Commits
Author SHA1 Message Date
Chris Tate 05b63df116 Check Buffer.concat's length before the elements of an untyped list
Node's Buffer.concat(list, length) validates length (after returning early
for an empty list) before it checks that every element is a Uint8Array.
The untyped-list path checked the elements first, so a bad element and a
bad length threw ERR_INVALID_ARG_TYPE where Node throws the length's
ERR_OUT_OF_RANGE.
2026-10-18 18:05:09 -05:00
Malte UblandClaude Opus 5.5 b8ac5e3f6b Merge origin/main into opt/builtins-npm2
fs.constants as a value now builds its object from the fs group fill that
node:constants added (scr_fs_constants_object, defined after
scr_node_constants so the shared static keeps its place); the fallback
declarations gain the UV_FS_O_* Windows-only flags. Compatibility files
regenerated with pnpm manifest and pnpm node-compat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:03:19 -07:00
Chris Tate 52303baf4b Keep the promise of async generic functions whose body reads a missing element
The instance result of an async generic function whose body returns an
element or index-signature read that can miss is Promise<T | undefined>:
the promise is always present and settles with undefined. The call site
widened the whole promise instead, so the instance and its calls had
different signatures and every call failed to compile.
2026-10-18 18:01:49 -05:00
Chris Tate dd2444fbb6 Keep React Flight's development-only stack parsing out of production builds, and make stack mode cheaper (#1330) 2026-10-18 18:01:06 -05:00
Malte UblandClaude Opus 5.5 f4a8511dca Compile maybe-promise unions, unknown[] rest parameters, and function-valued Maps and Sets
A union may now hold one promise beside data members (`T | Promise<T>`,
`Promise<R | void> | R | void`). The union tag is the `instanceof Promise`
test, and every operation keeps Node's microtask timing: `await` awaits the
promise arm or takes the one non-thenable turn, an async `return` (block
bodies, concise arrows, then handlers) adopts the promise arm after the
function's own finally blocks, `Promise.resolve` returns the promise arm as
is, and `typeof (u as any).then` reads the tag. Data arms that declare
`then`, two promise arms, and function arms beside a promise stay refused
with diagnostics. Inferred JavaScript literals that mix promises and values
keep the checked-dynamic array.

Rest parameters typed `unknown[]` or `any[]` in TypeScript use the
checked-dynamic variadic ABI that JavaScript rest parameters already use,
including spread forwarding (`(...args) => handler(...args)`).

Functions are now Map values, Map keys and Set elements, compared by
closure identity.

Two pre-existing bugs in the same area are fixed: an async lambda in a
`Promise<void> | void` slot got a `Promise<union>` ABI and threw when called
through the slot, and `return cb()` of such a callback in an async function
dropped the promise instead of adopting it.

The retired-engine 2251 and 2790 diagnostics fixtures (tagged templates
with `any[]` rest tags) now compile and match Node, so they move to the
corpus as 6248 and 6249.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:58:57 -07:00
Malte Ubl f18e804ffd Merge remote-tracking branch 'origin/main' into opt/tier-s3-jsval
# Conflicts:
#	packages/compiler/src/backend/llvm/shapes.ts
2026-10-18 15:58:49 -07:00
Malte UblandClaude Opus 5.5 5737483a04 Answer typeof for Web constructor globals and their static methods
ky probes `typeof globalThis.AbortSignal === 'function' && typeof
globalThis.AbortSignal.any === 'function'`. In JavaScript sources these
values are identity tokens, so typeof answered "string", and the static
method read refused. Node 24's Web-platform constructor globals and the
static methods of AbortSignal, Response and URL now answer "function",
also inside typeof comparisons, where a fallback-typed receiver narrows to
never after the first guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:55:24 -07:00
Malte UblandClaude Opus 5.5 ffd13d9751 Read fs.constants from the target libc
Only the access-mode and COPYFILE bits of fs.constants lowered; every other
member and the object itself refused. The runtime now builds
node_constants.cc's fs group in its order under the target libc's #ifdef
guards (O_NOATIME and O_DIRECT on Linux, O_SYMLINK on macOS) as a cached
null-prototype object with read-only members. `fs.constants` as a value
reads it, and members beyond the baked literals read from it (undefined
for a constant the target lacks, in JavaScript sources). The fallback
declarations list the full set. The table keeps each name inside its
entry, so the whole table is one data object section GC drops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:55:21 -07:00
Malte UblandClaude Opus 5.5 9e63a937db Test the Buffer brand at run time and alias the buffer module's Buffer
`Buffer.isBuffer` over a Uint8Array-typed value (safe-buffer's
`Buffer.from(...)` result, etag's input) refused at compile time because
the bytes IR shares storage between Buffer and Uint8Array. The view keeps
its Buffer brand at run time, so the test now reads it.

`require('buffer').Buffer`, `const { Buffer } = require('buffer')` and
`buffer.Buffer` through a namespace binding alias the global Buffer (the
module member is the same constructor), so their statics lower like the
global's. A file-scope `var` of the namespace form aliases too when nothing
reassigns it and the require-binding early-reference analysis finds no
earlier reader. `Buffer.from` over a JS-inferred `string | Uint8Array |
undefined` value takes the checked runtime form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:55:18 -07:00
Malte UblandClaude Opus 5.5 8edd2bce8e Add crypto.getRandomValues
nanoid fills its id bytes with `crypto.getRandomValues(buffer.subarray(...))`,
which had no lowering. The WebCrypto global, node:crypto's export and
`webcrypto.getRandomValues` now fill typed views in place and return them
at their own type. Float views, DataViews and other values throw Node's
TypeMismatchError DOMException, views over 65536 bytes QuotaExceededError,
with Node's messages and codes. The fallback declarations gain the
node:crypto export and webcrypto's member (the crypto global stays
unknown in builds without @types/node).

The runtime entry points are new functions reached only from these
calls; the size check is integer arithmetic so no constant joins the
pool every binary links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:55:15 -07:00
Malte UblandClaude Opus 5.5 ba17241220 Run String and Number methods on dynamic values and add substr, locale case and toPrecision
string-width's `character.codePointAt(0)`, camelcase's
`string.toLocaleLowerCase(locale)`, toml's `input.substr(...)` and
pretty-bytes' `number.toPrecision(3)` threw "x.y is not a function": the
dynamic method dispatch had no answer for these names on strings and
numbers.

A checked-dynamic receiver calling a String or Number method the runtime
dispatch cannot answer (codePointAt, substr, toLocaleLowerCase,
toLocaleUpperCase, isWellFormed, toWellFormed, trimLeft, trimRight,
toPrecision, toExponential(digits)) now evaluates the receiver and
arguments once and dispatches on the runtime kind: a string or number runs
the typed lowering, every other kind keeps the runtime dispatch's answer.
Names other dyn kinds share (at, concat, slice) and the Annex B HTML names
chalk-style builders define (bold, link, ...) keep their existing
dispatch.

Typed receivers gain substr, the locale case pair, the HTML methods,
toPrecision and toExponential(fractionDigits), all previously compile-time
refusals. toPrecision/toExponential digits come from exact big-integer
arithmetic over the double (ties up on the exact binary value). Locale
case mapping follows V8: the first requested locale decides, Turkic
dotted/dotless I is implemented, and Lithuanian, Greek uppercasing,
objects and malformed tags throw a 'not supported yet' Error.

A checker-untyped JS binding holding `string | undefined` boxes into the
dyn receiver path for String methods (string-width's baseVisible result).
tests/diagnostics/retired-engine-1112-number-methods.ts compiles now and
moves back to tests/corpus/1112-number-methods.ts.

New runtime functions are reached only from the new lowerings; the
existing runtime objects' sections are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:55:12 -07:00
Chris Tate b32f4e31f3 Merge main 2026-10-18 17:47:55 -05:00
Chris Tate 2f40bcb8d4 Lower iterator helpers on generators and Map and Set iterators
map, filter, take, drop, flatMap, toArray, forEach, reduce, some, every
and find compiled only as one chain from arr.values() or arr.entries()
that ended in a consuming method. Every other form was refused, and in
JavaScript the refusal was thrown at run time.

IteratorObject now maps to the generator type. A consuming method over
a chain of stages fuses into one loop over the source: a generator
driven like for-of, a Map or Set walked live, or an array by index. A
stage on its own builds a helper object: a generator over its source
that supports next(), for...of, spreads, Array.from and yield*. A
for...of whose head is a chain of stages without flatMap runs the
stages inside the loop.

The source is closed where Node closes it: when take reaches its count,
when some, every or find has its answer, when a callback or the loop
body throws, when the loop exits early, and before the RangeError of an
invalid take or drop budget. It is not closed when it is exhausted or
its own next() throws. return() on a helper object before its first
next() closes the source through a new runtime hook,
scr_gen_set_unstarted_return.

When closing an iterator throws while another error propagates, the
first error reaches the caller, as in Node. This applies to for...of
over a generator too, where the error from the close used to replace
the body's error. The try statement takes a dropFinallyErrors flag,
whose cleanup handler calls the new scr_exc_keep_first.

throw() lowers only on values typed as generators. Iterator helpers and
the built-in iterators have no throw method, and a value typed as an
iterator may hold one.

Callbacks in JavaScript bind their unannotated parameters to the
element type, and flatMap callbacks may return generators. A generator
whose record yields share checked storage with its return channel has
each value converted to the declared element type as it is pulled.
2026-10-18 17:46:31 -05:00
Chris Tate 6dff370714 Compile inline require(), computed and chained CommonJS exports, and a module's own exports object (#1329) 2026-10-18 17:45:15 -05:00
Malte UblandClaude Opus 5.5 8a89024e7b Refuse module exports that read this when called through the module binding
`m.f()` where `m` is `const m = require("./x")` or `import * as m from
"./x"` lowers to a direct call of the exported function, so a function
that reads `this` saw no receiver (or another frame's) where Node passes
the module object: `exports.hello = function () { return this.name; }`
threw a TypeError natively. Such calls now refuse with SC1090 (deferred
in JavaScript); exports that do not read this compile as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:41:11 -07:00
Malte UblandClaude Opus 5.5 815ce664e5 Let writes initialize a module let declared without an initializer
`let f: T; f = (n) => ... f(n - 1)` at module scope threw "Cannot access
'f' before initialization": a function-typed (pointer-guarded) binding
kept the null-pointer TDZ test on its writes, but the declaration
already initialized it (to undefined) when it ran. Such bindings now
carry the existing initialization flag: the declaration sets it, writes
that can run earlier check it, and writes no longer test the empty
pointer. A write that really precedes the declaration still throws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:41:11 -07:00
Malte UblandClaude Opus 5.5 8514c45b53 Make builtin module functions real function values in JavaScript
uuid 11 threw `TypeError: native.randomUUID is not a function` where
Node works. Root cause: a builtin module function used as a JavaScript
value without a function-value adapter lowered to a string identity
token ("[builtin crypto.randomUUID]"); uuid's native.js stores it in an
exported record (`export default { randomUUID }`), so the call hit a
string and typeof answered "string".

crypto.randomUUID and randomUUIDv7 get value adapters (randomUUID's
through its options-validating entry point, the new valueFn surface
field), boxed adapters carry the builtin's name, and every other
builtin module function value is one function per builtin that throws
the named SC1090 fence when called, keeping identity, typeof and
inspection Node's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:41:10 -07:00
Malte UblandClaude Opus 5.5 e250214e03 Name objects built by plain constructor functions in util.inspect
console.log(new F(2)) printed `{ a: 2 }` where Node prints `F { a: 2 }`.
A construction through a plain function now stamps the first prototype
on the receiver's chain whose own `constructor` data property holds a
named function with that name, as Node's getConstructorName finds it.
The dyn printer already reads such runtime class names, so its code is
unchanged; stamped prototypes stay alive and are cleared before release
at exit. `new F()` boxes F under its name so the prototype's constructor
carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:40:50 -07:00
Chris Tate 5b0c0f66b9 Compile classes that extend a generic class factory's result (#1335) 2026-10-18 17:38:32 -05:00
Chris Tate b119b4cf03 Stringify class instances through the runtime JSON traversal
JSON.stringify of a class instance, or of a record, array or union that
holds one, boxes the value and serializes it with the runtime JSON
traversal. The output matches Node: own enumerable properties in
creation order, properties added after construction, toJSON on the
instance or a base class, and no methods, accessors, #private fields or
static fields. A class whose declared field cannot be read through the
checked-dynamic property view, such as a Date field, is refused at
compile time with SC1090 naming the field.

Boxed Map and Set values now stringify as {}, as Node does, instead of
throwing.
2026-10-18 17:32:17 -05:00
Malte UblandClaude Opus 5.5 daf4ff6a2b Decode and validate the WebAssembly modules a program can load, and report them in coverage
`scriptc coverage` now lists every WebAssembly module whose bytes the
build can see, with its size, whether it validates, its function, import
and export counts, and the WebAssembly proposals it uses. Nothing is
translated yet; builds are unchanged.

- packages/compiler/src/wasm/binary.ts decodes and validates a module in
  plain TypeScript (the native compiler compiles it too) at the feature
  level scriptc means to translate: the MVP plus sign-extension,
  non-trapping float-to-int, bulk memory, multi-value, mutable globals,
  reference types and extended constant expressions. Where WebAssembly
  3.0 and V8 relaxed a 2.0 rule (global initializers reading defined
  globals, memory indices), it follows V8, the behavior oracle. Modules
  that use SIMD, threads, exception handling, tail calls, function
  references or GC, memory64 or multiple memories still decode, and are
  reported as needing that proposal instead of as invalid.
- packages/compiler/src/wasm/discover.ts finds modules from four sources:
  paths the compiler resolves from the source text (require.resolve,
  createRequire(...).resolve, import.meta.resolve,
  new URL(..., import.meta.url), __dirname joins), inline base64 and byte-array
  literals, the .wasm files of reached packages whose code mentions
  WebAssembly, and a `scriptc.wasm` list in package.json or the new
  coverage-only `--wasm <file>` flag. Modules are reported once, by
  SHA-256, with every source that found them; module files are input
  probes of the coverage verdict cache. The JSON envelope
  (--print=diagnostics) carries the same records under `wasmModules`.

Evidence: packages/compiler/test/wasm-spec.test.ts runs the decoder over
the 3,017 distinct binary modules of the WebAssembly 2.0 core test suite
(without SIMD) and the extended-const proposal's tests, generated by
gen-wasm-spec-cases.mjs with wabt's wast2json, and requires V8's verdict
for each; 8,000 seeded mutations of those modules must also agree with
V8, as must undici's inline llhttp modules (scalar valid, SIMD reported)
and es-module-lexer's. src/wasm/binary.test.ts checks each feature and
each error class against V8. On the survey's real modules the decoder
agrees with V8 for quickjs-wasi (637 KB, 36 ms) and photon (1.9 MB,
121 ms). tests/coverage-fixtures/wasm pins the four discovery sources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:19:39 -07:00
Malte UblandClaude Opus 5.5 c7f3fa6564 Type-check programs that mention WebAssembly and refuse its API precisely
Packages whose declarations name WebAssembly types (quickjs-wasi,
photon-node, and therefore pi) failed scriptc's type check with SC0001
"Cannot find namespace 'WebAssembly'", because scriptc's ES-only type
world never loads lib.dom, so `scriptc coverage` could not analyze them.

- scriptc.d.ts declares lib.dom's WebAssembly namespace and BufferSource
  (admitting every ArrayBufferView, so Node's Buffer types fit).
- The type mapper gives every type declared in the namespace, and unions
  over one, the checked-dynamic representation, so declarations that only
  mention them compile.
- Every reached use of the API value refuses at its site with a named
  SC2020 diagnostic: `WebAssembly.<member>` reads and calls,
  `new WebAssembly.<Class>`, `instanceof WebAssembly.<Class>`, the bare
  namespace, `globalThis.WebAssembly`, and the global object viewed
  through a cast to a type with a WebAssembly member (pi codemode's
  spelling). TypeScript sources fail the build; JavaScript sources get a
  located runtime fence.
- `typeof WebAssembly` and `typeof WebAssembly.<member>` need no value and
  fold to Node's answers ("object", "function"), including inside
  `typeof x === "..."` tests, so feature probes in JavaScript glue behave
  as in Node. Before, a JavaScript probe answered "string" (the identity
  token) and silently took the non-wasm branch.
- The surface manifest projects the refusal (stdlib.webassembly), and the
  compatibility ledger's `WebAssembly` global row moves from
  not-implemented to refused; no other row changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:19:38 -07:00
Malte UblandClaude Opus 5.5 d3bfa69323 Coerce the value passed to a JavaScript setter that overrides Error.name or message
A JavaScript class that extends Error and declares `set name(v)` or
`set message(v)` (quickjs-wasi's JSException) made the Error property
write dispatcher, which exists once any program writes `.name` or
`.message` on an Error, pass a string where the untyped setter takes a
dynamic value. IR validation failed with SC9001 "expected dyn, got
string". The dispatcher now coerces the value to the setter's parameter
type, which leaves TypeScript setters (string parameters) unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:19:37 -07:00
Chris Tate 4c27ab8bd7 Compile classes that extend a generic class factory's result
`class Counter extends base(42) {}` with `function base<T>(initial: T) {
return class { value: T = initial; }; }` failed with SC1090 "extending
computed expressions": the call's result type is the factory's local class
instantiated with T = number, which only maps inside the factory's own
generic instance.

A call of a generic function whose result is one of its local classes (or
an instance of one) now keys the instance on its type bindings and maps the
result inside that instance, where the local class takes the instance's
name and bindings. The class value then works as a base (`extends
base(42)`), as a `const` binding, through `new`, and for method calls and
field reads on its instances. Each instantiation has its own class, so
`base(1) === base(1)` is false and `instanceof` tells them apart, as in
Node. Calls inside another generic instance bind through the declaration;
calls elsewhere also record the instantiated checker type, so later uses of
it name the same class.

Fixes #1241
Co-authored-by: BaoLei <50226652+blurname@users.noreply.github.com>
2026-10-18 17:17:22 -05:00
Chris Tate 238d782fdc Keep the CommonJS caches on their owners and recount 6110's fences
The exports-assignment cache moves from a module-level WeakMap onto the
Lowerer (cjsExportsAssignments), and the inline require() calls that close
a require cycle move from a module-level WeakSet into preflight's result:
checkPreflight stores them on the LoadResult, and lowering receives them
with the other preflight facts (LowerOptions.inlineRequireCycleCalls).

6110's call and apply wrappers now compile `root === module.exports` and
`typeof module.exports`, which print what Node prints when the wrappers'
untaken branches run. The class base's `console.log(module.exports)` is the
one statement left as a runtime fence, and its body is lowered once, so
the program's deferred-fence count is 1.
2026-10-18 17:17:00 -05:00
Malte UblandClaude Opus 5.5 d84e410981 Upcast devirtualized calls whose override returns a subclass
A base method calling `this.set()` where only a subclass is constructed
devirtualizes to the subclass override. With a polymorphic `this` return
type, the override's lowered return type is the subclass object while the
site keeps the base method's type, and the validator reported an internal
compiler error (SC9001: "call %Sub.set type object != return object"). The
Next.js extract hit this once its placeholder ResponseCookies instance was
gone: MiddlewareResponseCookies became the only constructed class.

The direct call now carries the callee's call-site return type and upcasts
to the site's class; any other mismatch keeps the virtual call. Calls whose
types already matched are emitted exactly as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:16:20 -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
Malte Ubl d68db45c93 Merge origin/main into opt/pi-small-fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	packages/runtime/src/scr_async.h
2026-10-18 15:15:21 -07:00
Malte UblandClaude Opus 5.5 c600e8daa5 Delete V8's own stack on Error subclasses with a stack accessor
quickjs-wasi's JSException constructor runs `delete this.stack` so V8's
own stack property stops shadowing the class's stack accessor; scriptc
fenced the delete, so every guest exception reaching the host threw the
fence error. Compiled errors keep the trace internally and class dispatch
already reads the accessor, so the delete now evaluates its receiver and
nothing else, for Error subclasses whose chain declares a stack getter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:05 -07:00
Malte UblandClaude Opus 5.5 50235c42c5 Support pi-tui's terminal setup on process streams
Three gaps on pi-tui's terminal start path:
- `if (process.stdin.setRawMode)` read the method as a value (SC2020).
  The read now answers the stream object's member: the method on a TTY,
  undefined otherwise, as on Node.
- process.stdin.setEncoding had no runtime method, and a string 'data'
  listener was fenced. stdin now decodes with the shared StringDecoder
  once per read (split multibyte sequences stay whole, end() flushes the
  tail before 'end'), string listeners receive the text, and dynamic
  listeners receive what Node delivers. A string listener with no
  encoding set throws the setEncoding-state TypeError, as child streams
  do.
- process.stdout.on("resize", cb) (once/off/removeListener, stderr too)
  registers on the stream object, which listens for SIGWINCH and emits
  when a TTY's size changed; the listener never keeps the loop alive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:05 -07:00
Malte UblandClaude Opus 5.5 98b3ced80c Resolve bare core-module names to the builtin over npm packages
Node checks its builtins before node_modules for a bare core-module name,
so an installed npm package named string_decoder (a transitive dependency
of pi) never answers `import ... from "string_decoder"`. scriptc
attributed the import to the package and refused it. The npm resolution
now answers null for bare builtin names (prefix-only builtins such as
node:test keep their bare names for packages), for ES imports and CommonJS
requires alike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:05 -07:00
Malte UblandClaude Opus 5.5 bcc35ddf1a Call chalk.bold from TypeScript
#1309 let a checker-untyped receiver call a member named like a
String.prototype-only method (`bold`, `italics`, ...), refusing only a
string receiver at run time. TypeScript importing chalk's JavaScript
source types the default export as a plain function, so `chalk.bold` is
an `any` member on a typed receiver and still fenced; pi calls it in
nine places. A checker-untyped member now takes the same path. The chalk 5
fixture pins root, aliased, and Chalk-instance `bold` calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:04 -07:00
Malte UblandClaude Opus 5.5 71b99913a2 Allow WeakMap- and WeakSet-typed class fields
Weak collections already lower to checked-dynamic storage as locals, but a
class field of those types was fenced as a dynamic-typed field (pi's
AgentSession keys entry ids by message in a WeakMap field). Declared,
optional, and constructor-assigned fields of those types now store the
native collection like the locals do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:04 -07:00
Malte UblandClaude Opus 5.5 0bb9b750fe Iterate class instances whose async iterator is an async generator
pi-ai consumes every provider stream with `for await` over an EventStream
whose `async *[Symbol.asyncIterator](): AsyncIterator<T>` is an async
generator method; that was SC1070. The loop now opens the iterator with
the method call and drives the returned async generator like any other
(break and throw close it). An `async *` declaration annotated with the
protocol interfaces (AsyncIterator, AsyncIterableIterator, AsyncIterable)
maps like AsyncGenerator. Boxing a class instance whose fields hold a
promise now links the dynamic async unit (the materializer boxes the
promise), which EventStream's own `this.isComplete(event)` calls need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:04 -07:00
Malte UblandClaude Opus 5.5 8a02efaa91 Type process.getBuiltinModule results as module namespaces
Under @types/node, process.getBuiltinModule("node:os") types as the
module namespace, which lowered as an unchecked dynamic value and failed.
A string-literal id naming a supported builtin now lowers to the namespace
value an `import * as` binding has, including pi's cast-and-probe spelling
`(process as P).getBuiltinModule?.("node:os") ?? null`; a nullable
namespace binding narrowed present keys the same member tables, and a
namespace is truthy. process.features read as a whole value (`"sea" in
process.features`) is an object holding none of the flags, matching the
existing member reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:04 -07:00
Malte UblandClaude Opus 5.5 5e964965f6 Assign process.emitWarning
pi's CLI setup replaces process.emitWarning to silence warnings. The
assignment now stores the replacement: later process.emitWarning(...)
calls apply it to their arguments, and runtime-built warnings (Node's own
modules call process.emitWarning) reach it as (message, type[, code])
instead of the report and the 'warning' listeners. process.title writes
already lowered; the corpus program pins both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:03 -07:00