A Response body's stream gets a web ReadableStream view when a member the
fetch stream lacks is read (for example `body.tee`). The view's source holds
the stream, and the stream released the view only when that source was its
last holder. While the transfer runs it holds the stream too, so a dropped
body with a view kept a reference count of 2: the unobserved-response
discard never ran, the paused socket was never resumed, and a compressed
response dropped before HTTP EOF kept the process alive.
The stream now releases the view when only the view's source and the live
transfer hold it, and then takes the unobserved-response discard, as a
body without a view does.
The static-abandon-view fixture reads `body.tee` and `body.locked` of a
4 MiB incompressible gzip response from the new /gzip-incompressible route
and drops it. It hung until the test's timeout before this change and now
exits as Node does.
process.memoryUsage() reported external and arrayBuffers as 0. arrayBuffers
is now the storage of the ArrayBuffers, typed arrays, Buffers, DataViews
and SharedArrayBuffers the program holds, and external the same storage
without SharedArrayBuffers, as Node splits them. A view over existing
storage adds nothing. v8.getHeapStatistics().external_memory reads the
same figure as external.
Each ScrBytes owner records the bytes it counts in the struct's padding,
so ScrBytes stays 64 bytes and the offsets compiled code reads hold; a
static assertion pins them. Owners are counted in a per-instance counter,
because their reference counts are not atomic and they are released on
their instance's thread. Shared storage moves its owner's count to a
process-wide atomic counter, since any thread may release it.
A signature adapter forwards the ambient receiver to its original, and
its closure carries the original's RECEIVER_FREE bit. Calls through the
closure's own pointer tested that bit, but the direct entry of a known
adapter target bracketed every call, because the adapter itself counts
as a receiver reader. The direct entry now tests the bit too.
The receiver readers analysis also admits more node kinds that run no
user code: optional chains, record presence and absent fields, dynamic
boxing, array for-of loops, typed array writes, typed array methods and
constructors over typed operands, the module binding check and region
intrinsics, performance.now, Atomics operations, string and number
library calls over primitives, and new Error(message, options) without
an options object.
The TypeError check for a null or undefined source now reads the unit arm the value holds as a dyn null or undefined, instead of converting the whole union to dyn. A union whose data arm has no dyn form, such as a record holding a generator, compiles again instead of failing with SC9001.
- A function body or class static block treats its top-level function
declarations as var-scoped: duplicates are legal in strict code, and a
let/const/class of the same name is SC0006.
- Strictness is the code around a node: a function declaration's own
"use strict" no longer makes its declaring context strict.
- Every part of a class is strict; object literal methods and accessors
are strict only by their own prologue or their surroundings, and an
arrow function's block body can carry "use strict".
- The bare compareArray reads b.length, then a.length, and a.length again
before every element pair, converting it as the upstream `i < a.length`
does, so a string length compares its elements.
- Tests that relied on the old handling of undeclared names now use
constructs that are still refused or still compile: the installed-entry
diagnostics fixture reports a syntax error (a read of an undeclared name
is now Node's run-time ReferenceError), and static-objects-order's
loops.js stores from its for-in head into `undefined` instead of an
undeclared name (a store into an undeclared name refuses its package with
SC1091).
In a TypeScript source, a builtin global used as an object literal member
(`const o: any = { j: JSON }`) failed to lower, and the member silently
became a closure that threw when called. Node reads the global itself, so
`typeof o.j` answered "function" instead of "object", two reads were never
equal, and `o.j.stringify(x)` threw "is not a function". There was no
diagnostic.
A builtin read into a slot the checker types as any or unknown now holds
the value a JavaScript source reads: the JSON, Math and Reflect namespace
objects, Buffer, and functions such as structuredClone, atob and
queueMicrotask. This covers object and array
literal members, arguments, initializers and return values. A member of a
literal whose own slot is any takes the literal's slot. Array, setTimeout
and setImmediate keep their refusal: their values lack Array's statics and
the timers' util.promisify.custom getter.
Object literal members of TypeScript sources no longer defer a failed
lowering to a runtime closure. A builtin with no value (Intl, Proxy,
parseInt, process, a typed slot) reports SC2020 at compile time.
util.inspect now prints a plain object's Symbol.toStringTag as Node does:
`Object [Math] {}`, `[Object [Y]]` past the depth limit, and
`[Object: null prototype] [N] {}`. The tag is read through the prototype
chain and getters, and it is skipped when it is empty, not a string, the
name "Object", listed as an own enumerable key, or its getter throws.
A shared view written to stdout or stderr went out in 64 KiB pieces through
scr_stdio_write, so a replaced process.stdout.write received the first
piece alone, and a false return, which only reports backpressure, ended the
loop: a view larger than 64 KiB lost the rest. With a write hook, the view
is now copied whole and passed in one call, as Node passes it; the bounded
pieces go to the stream itself through scr_stdio_write_raw, which never
stops early.
tests/corpus/shared-bytes-stdio-write.ts replaces process.stdout.write with
a function that records each chunk's length and returns false, and writes a
150,000-byte shared view through it.
Group C adds two corpus programs whose 8423 prefix another group also
proposes; main's highest corpus number is 8513, so take the next two free
numbers and move the matching per-program cost files with them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
D-2: Node resolves a name nothing declares at run time, and the read always
throws the catchable `ReferenceError: x is not defined` at the access. A
`.js` read of such a name now compiles to exactly that throw instead of
refusing the program, with Node's message and Node's catchability.
The read's value is checked-dynamic (the checker's error-any after the
relaxation in the previous commit, narrowed by the use site where one
provides context), and every path through it unwinds before a use observes
the value, so the dynamic slot never carries a wrong value. Shorthand
position (`{ x }`) needs its own declaration test: there the checker
answers the PROPERTY symbol, so the name is bound when
`getShorthandAssignmentValueSymbol` has no value for it.
`typeof` is the one operator that never touches its operand value, and Node
answers "undefined" for an undeclared name, so `lowerDynTypeofTest` folds
the test to a literal before the read is ever lowered — `typeof x ===
"undefined"` stays true and `typeof x` alone prints "undefined".
No name that Node serves from the global object reaches this branch: the
compiler models those as declared (that is how the corpus reads `process`,
`console` and friends), so this is the name Node itself would report
missing.
The corpus program pins every shape: bare read, call, member read,
arithmetic, comparison, shorthand, destructuring default, typeof test, a
guarded read that keeps running after the throw, and a declared control.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Node resolves a name nothing declares at run time. In sloppy mode a WRITE
creates a property on the process-wide global object, which a native binary
has no object for, so those programs belong on the Node island route (plan
D-2/D-5). Until that routing exists, an undeclared write in a `.js` source
is refused at build time under a dedicated code, SC1091, whose text names
the island route instead of failing with a generic no-lowering message.
Reaching that refusal at all means the checker has to stop treating an
undeclared name in a JavaScript source as a compile error, so 2304, its
did-you-mean spelling 2552, and shorthand position's 18004 join the JS
relaxation set. The relaxation stays `isJsSourceFileName`-gated: TypeScript
input keeps the compile error, where an undeclared name is a bug rather
than a runtime path.
The refusal is a preflight walk over every JavaScript file, so it reports
once per write, before any code is emitted, at the span the checker used.
`isJsWriteTarget7` is shared with the lowerer's unresolved-reference fence,
which supplies the same refusal for a write that reaches lowering by a path
preflight does not model. An empty identifier is skipped: it is a parse
recovery artifact, never a name a program writes.
The JS-read half of D-2 (the name lowers to Node's catchable
`ReferenceError`) is the next commit; this one only changes what the build
says about a write.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Static semantics reject a program before it runs. Until now the compiler
borrowed that rejection from whatever tsc happened to report, and Test262's
negative-parse suites show where the borrowing stops: programs the spec
makes SyntaxErrors compile and run. This adds the two largest families the
grab-bag misses (plan §7.7 D-4, option c), each under its own preflight
code, so a checker relaxation can never remove the only rejection a
parse-negative program had:
SC0005 a statement list declares one lexical name twice
(`{ class f {} async function f() {} }`)
SC0006 a lexical name is `var`-declared in the same statement list
(`switch (1) { case 1: let x; case 2: var x; }`)
SC0007 a declaration where the grammar admits only a Statement
(`while (false) function f() {}`, `l: class c {}`,
`if (x) async function f() {}`)
frontend/early-errors.ts is a preflight walk over JavaScript sources, so a
rejected program fails where a tsc parse error fails, before any lowering.
A case block is one lexical scope across its clauses; a list's
VarDeclaredNames reach into nested blocks but never into a function or a
class body; and Annex B's allowances are honored exactly: two plain
function declarations may repeat in a sloppy block (B.3.3.1), and a plain
function may be an if/else body or a labelled statement in sloppy code
(B.3.2) — an async function, a generator or a class never gets either
allowance, and neither does any function as a loop or 'with' body.
Strictness comes from Node's own module classification (isNodeEsmFile7),
not from the checker's external-module bit: moduleDetection=force gives
every file module scope, CommonJS included, and taking that bit as
strictness cost the Annex B exemption on the annexB
function-redeclaration tests. Typed sources are untouched — their checker
diagnostics (TS2451 and friends) remain the gate, and re-reporting a
collision under a second code would only duplicate them.
tests/test262/support.mjs counts one of these codes at a location inside
the test's own source as the parse-negative match, the same standard the
helper already applies to tsc's early errors. The diagnostics fixtures pin
both families, including the strict half of SC0007 where Node refuses the
two Annex B shapes as well.
The pass only reads the AST and only refuses programs, so no typed
program's IR changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TS2504 demands a `[Symbol.asyncIterator]()` method that returns an async
iterator. In untyped JavaScript, whether a value is async-iterable is a
runtime fact of the value rather than a static one, exactly like the sync
demand (2488) relaxed just above. `for await` over a non-async-iterable
and `yield*` in an async generator both lower through the runtime
protocol openers, which read the method, call it, and throw the same
TypeError Node throws when it is missing, uncallable, or returns a
non-object.
Only diagnostics change, so only variants that previously held a 2504
refusal can move: the relaxed set is consulted only when JavaScript
strictness is suppressed, and 2504 is the sole code this adds. The sync
sibling 2505 stays enforced on the typed lanes, and TypeScript sources
keep 2504 because the relaxation applies to `.js`/`.mjs`/`.cjs`/`.jsx`
only.
Corpus 8423-async-iterator-protocol-shapes.js pins the seven protocol
shapes Node rejects and the two it accepts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
harness/assert.js defines `compareArray(a, b)` beside `assert`: a plain
function that RETURNS a boolean, whose throwing counterpart is
`assert.compareArray` (already hosted). The adapter now injects the bare
name into every prepared test and implements the upstream comparison:
read both `.length`s, so a nullish operand throws the same TypeError,
return false on a length mismatch, otherwise compare elements by
SameValue.
That also admits the compareArray.js include when the only use is a
direct call. A value reference or property read stays excluded, because
it would need the upstream function object and its `format` expando.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A `var` with an initializer inside a loop body of its own function runs
once per iteration, so the binding counts as written, and generic
instances share it instead of copying an earlier iteration's value.
- Only a function declaration with a body initializes its binding:
overload signatures no longer make an overloaded function count as
redeclared, so it is still a function that nothing reassigns (for
example as a pipeline() function stage).
Adds the corpus program pipeline-overloaded-stage.ts, with its corpus cost
and order baseline, and a loop-body case to
generic-arrow-mutable-captures.ts.
Rename 8640-emitter-arguments-listeners.cjs and
8641-emitter-leak-warning-exit.cjs to emitter-arguments-listeners.cjs and
emitter-leak-warning-exit.cjs, with their cost files and ts7 baselines, as
AGENTS.md asks for new corpus programs.
Linux 5.9 and 5.10 answer 0 instead of EAGAIN for an RWF_NOWAIT read of
data that is not cached. The inline read took that 0 as end of file and
ended the ReadStream early. A zero from the inline read now goes to the
pool, whose read returns the data or confirms end of file; this costs one
pool round trip at the end of each file.
fs-stream-nowait.test.ts preloads a preadv2 shim that answers 0 for every
RWF_NOWAIT read and checks that a ReadStream still reads the whole file,
as Node does.
On Linux, an fs stream's read or write of a regular file first tries
preadv2/pwritev2 with RWF_NOWAIT on the loop thread. When the page cache
holds the data, the step completes there and its completion waits for the
next loop turn (scr_work_submit_done), as a pool job's completion does,
so timers and I/O still run between steps. EAGAIN, a kernel or file
system without RWF_NOWAIT, and other files take the pool as before.
- A read the cache holds only part of finishes on the pool, so each chunk
is the one a single read() returns.
- A partial write completes with the bytes written; the stream submits
the rest, as after any partial write.
- Writes stay on the pool when RLIMIT_FSIZE is finite, so SIGXFSZ cannot
reach the loop thread.
scr_work_submit_done queues a completed job on a list the loop moves to
the done queue at its next sleep step, which is skipped while jobs wait.
Corpus 8530 covers a positioned read with a small highWaterMark, a write
stream with a start position into an existing file, and an append stream
over cached files, against Node.
fs.read's options forms read the position option a second time when it
was not a bigint, so a getter ran twice and a stateful one could move the
read. The option is now read once.
A bigint position was checked against the length before `length |= 0`.
A fractional or infinite length made the range check build its bound
from a null BigInt and crash. The check and the read now take the
converted length, so such a read completes with zero or the truncated
count of bytes, as in Node.
The scan that decides whether a program uses a web stream operation the
native streams lack only matched property accesses, so a TypeScript
program whose one such operation was `body["tee"]()` kept the native
streams and threw a TypeError at the call. Element accesses with a string
literal key now count the same way, in the TypeScript scan and in the
JavaScript one.
Adds the corpus program web-streams-typed-element-tee.ts, with its corpus
cost and order baseline.
- A generic arrow instance binds its trailing capture locals before it
lowers the declared parameters, so a parameter default that reads an
immutable enclosing binding resolves it instead of failing with SC1090.
The captures stay trailing parameters.
- A binding is written when one of its own declarations writes it: a
`var` in a for-in or for-of head (every iteration), or a redeclaration
that initializes (`var x = 1; ... var x = 2;`, a parameter or function
declaration that a `var` redeclares). Such a binding is now shared with
generic instances through the environment form, so a closure an
instance returns reads the current value instead of a copy.
In JavaScript files, a destructuring declaration whose source is one object or array type or null/undefined (`text.match(re)`, `items.find(...)`) threw a plain Error when the source was null or undefined. It now throws Node's TypeError with V8's message: "Cannot destructure property 'index' of 'text.match(...)' as it is null." for an object pattern, and "found is not iterable" or "object null is not iterable (cannot read property Symbol(Symbol.iterator))" for an array pattern. An object pattern whose source V8 spells in a form the compiler does not reproduce keeps the runtime fence.
The jammed loop tests its exit only when `(j & 3) === 3`. When the inner
counter starts at NaN, an infinity, or a value that `j + 1` leaves unchanged,
`j & 3` never becomes 3, and the loop ran past the point where every
accumulator was full, without end when its bound never ends it.
The every-fourth test is now used only when the inner loop starts at an
int32 literal: the counter then takes consecutive integer values, so its low
bits reach 3 within four iterations. From any other start, the jammed loop
tests its exit every iteration.
The unit test checks which starts get each test, and the corpus program adds
loops starting at -Infinity and -(2 ** 54) that end once both pixels escape.
for-in over a value whose type adds null or undefined to one enumerable
type (a record, an index-signature record, an array) was refused with
SC1052. JavaScript runs such a loop zero times for null and undefined. The
loop now evaluates the receiver once, skips it when it is null or
undefined, and otherwise walks the value as its own type.
Fixes#2403
Co-authored-by: MathurAditya724 <57684218+MathurAditya724@users.noreply.github.com>