A Pick or Omit view of a program class is held as a live class protocol,
and generic methods called through it monomorphize against the class.
Both checks read the view from the type's alias symbol, but a view named
by a type alias (type View = Pick<Registry, "m">) carries the user's
alias instead, so it mapped to a record of erased method fields that no
instance converts to. classViewOf reads the alias declaration and sees
the view through non-generic aliases, alias chains included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`...(x ?? {})` and `...(x || {})` spread exactly what `...x` spreads, but
the defaulted source lowered as a `??` expression into a checked-dynamic
value, which failed for records with function fields. When x is a record
or an optional record, the spread now lowers as a spread of x.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An object literal whose context has no layout builds at its own type,
but its nested literals still chose their own contextual layout, so the
nested record could not convert into the field the outer literal had
built, and SC2002 named the source literal's type as the expected one.
A nested literal now builds at the layout the enclosing literal asked
for when its contextual layout cannot reach it, and the outer coercion
lifts the whole tree into the real destination.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`const { a, ...rest } = options ?? {}` destructures the options record or
an empty object. Where the record had a required member, the `{}`
fallback was coerced into the record and failed with SC2002. The source
now lowers to the union of the record and the empty record, so the
union pattern lowering reads each key from the arm that holds it, the
empty arm answers undefined and the rest element packs nothing. A
record arm without a literal key now answers undefined in unionKeyGet
too, which the frontend already asked for and the validator rejected
as an internal error. All-optional options keep their lowering.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An object spread of a union with several record arms was refused. Two
cases are now lowered. Where the checker narrowed the source to fewer
arms than its stored union holds (an optional-chain discriminant test
on an element read, a type predicate), the source converts through the
checked narrowing, so an excluded arm throws the catchable TypeError,
and the narrowed record spreads as before. A union spread into one
declared record copies each arm's own keys behind its tag test: a key
the running arm lacks keeps the earlier value or stays absent. One
record arm emits the same code as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
A path family read by untyped code is a plain object holding Node's
members in Node's order, built once per family and kept in the
builtin-module registry. Object.keys, Object.entries, Object.assign,
spreads, for-in and own-property checks over it see those members.
path.posix.posix and the cross-family members refer to the registered
objects; the registry releases their members at exit through the objects'
own storage.
Every builtin function value that becomes untyped boxes as its checked
twin, under the builtin's name and length, after lowering: a module
object's member, an assertion's argument, and a read of a binding whose
only stores are that builtin (a const local, or a module-level binding).
`ns.join === path.posix.join` holds for an untyped path namespace, and
`const join = path.join` passed to an untyped function compares equal to
path.join and validates its arguments with Node's errors.
The lowerer journals the checked-twin table (each builtin's data, display
name and location), so a unit replayed from the journal keeps its twins.
process.emitWarning checks its arguments in Node's order: an options
object replaces type with `options.type || "Warning"` and code with
`options.code`, type and code are checked before the warning and for
Error warnings too, an empty type names the warning "Warning", and a
DeprecationWarning type returns at once under process.noDeprecation.
Wrong kinds throw ERR_INVALID_ARG_TYPE with Node's "Received ..." tail.
An instance of an Error subclass is an Error warning, and a typed record
in the options position is read as the options object. The default
report reads the warning's message, code and detail as property reads
do, so an Error subclass's own code is reported. A deprecation thrown
under process.throwDeprecation is thrown as the Error it encodes, so the
uncaught report names it.
process.kill throws ERR_UNKNOWN_SIGNAL for an unknown signal name, as
subprocess.kill does, and a failed kill(2) is Node's errnoException with
errno, code and syscall. A failed process.chdir names the working
directory as path and the target as dest.
os.userInfo() reads the passwd entry of the effective uid: uid and gid
are the entry's pw_uid and pw_gid (-1 on Windows), not the process ids.
os.homedir() answers $HOME whenever it is set, an empty value included.
The minimal test/common for Node's test/parallel files gains isAIX,
isIBMi and isSunOS.
A JavaScript function assigned to process.stdout.write or
process.stderr.write takes its parameters from the call, not from Node's
overloads, but the lowering returned before giving the function its
arguments slot. `arguments.length` then folded to the declared parameter
count (3 for `function (chunk, encoding, callback)` called with one
argument, 0 for a function without parameters), and reading
`arguments[i]` was refused at run time.
The replacement now receives the call's arguments, so `arguments` holds
what the caller passed, as in Node.
A byte write to a replaced process.stdout.write or process.stderr.write
was boxed with the 'data' chunk helper, which answers a string while a
listener of a socket or HTTP response with setEncoding("utf8") runs. A
`process.stdout.write(buffer)` from such a listener reached the
replacement as a string. Node passes the Buffer it was given.
The stdio hook now boxes a byte write as a Buffer whatever listener is
running.
- scr_intl.c resolves the default locale as V8 does: ICU's POSIX locale
(LC_ALL=C, C.UTF-8, POSIX) becomes en-US, so numbers keep their grouping
separators, and other defaults round-trip through their BCP 47 tag. The
number formatter, the date pattern generator, the date formatter and the
default word segmenter use that locale.
- The date formatter moves the Gregorian calendar's Julian cutover to the
start of ECMAScript time, so dates before October 1582 print their
proleptic Gregorian fields, as in Node.
- icu.numFormatDefault, icu.dateLocaleString and icu.dateLocaleTimeString
are in the may-throw set: a missing ICU data file throws an Error into the
surrounding catch.
- `x.toLocaleString(undefined)` reads the receiver before the argument runs.
- The linux-x64-gnu runtime pack ships the ICU headers scr_intl.c includes
beside its ICU archive (manifest `headers`, verified like the other
artifacts). The native compiler's sanitized build compiles scr_intl.c
against them and refuses with SC3003 when they are missing or changed.
The C-source path (sanitized builds) links the pack's ICU archive and
uses those headers, instead of building ICU with Zig's C++ driver.
- The macOS pack omits scr_intl.c, scr_intl.h declares ScrDyn, and the
library-mode teachings test uses a locale argument, which still refuses
with SC2020.
Corpus programs pin the C and C.UTF-8 locales, pre-1582 dates and the
receiver order against Node. intl-missing-data.test.ts covers the throw.
sanitizer.test.ts covers the header lookup.
The stream completion closures carried SCR_CLOSURE_KIND_RECEIVER_FREE, so
compiled callers skipped the receiver bracket around cb(). A completion
runs user code synchronously (the write(chunk, callback) callback, the
next queued _write), and that code reads the ambient receiver, so the
flag is removed and the completions bind `this` as any unbound call
does. Corpus programs 8531 and 8532 call a saved completion from inside
another object's method, from JavaScript and from a TypeScript class
method that JavaScript calls, and check `this` in the callbacks it runs
against Node.
- watcher.off/removeListener of a listener that is not the last one moves
the later listeners down instead of up, so the removed closure leaves
the list.
- The watchFile adapters for zero- and one-parameter listeners release
the Stats their listener has no parameter for, and the fs.watch adapter
releases the event string once a dynamic value holds it.
- A one-parameter fs.watch listener whose eventType is dynamic receives a
boxed string through the emitted adapter.
- fs.watch, fs.watchFile and fs.unwatchFile throw ERR_INVALID_ARG_VALUE
for a path holding a null byte, and a new watchFile poller throws
ERR_OUT_OF_RANGE when interval is not a uint32, as Node does. Both
watchFile libCalls check for the pending exception.
- fs.watchFile and fs.unwatchFile have surface-manifest entries, so a
fence can deny them.
- The self-hosted feature records list watchFile, and the node-types
fence fixture uses fs.openAsBlob in place of the now-lowered
fs.watchFile.
Tests: tests/corpus/fs-watch-listener-shapes.ts (listener shapes, off of
a middle listener, null-byte path) with its order baseline and cost file,
the validation cases in fs-watch-file-polling.ts, and
tests/harness/fs-watch-leaks.test.ts, which runs both programs under
LeakSanitizer in the sanitized lane.
- A typed write or transform callback whose encoding parameter is a
pointer union with an undefined arm receives the null pointer directly
when an object-mode write passed no encoding, instead of converting the
encoding value through a dyn box
(scr_stream_chunk_encoding_undefined).
- The stream completion callbacks carry SCR_CLOSURE_KIND_RECEIVER_FREE:
their thunks never read `this`, so compiled callers skip the receiver
bracket.
- The completion thunks borrow the stream from the closure's box instead
of retaining and releasing it on every call; the closure, alive for the
call, holds the box.
- settle_next, entry_convert and clear_buffer_each test their early-out
conditions in inline wrappers before calling the full functions.
A corpus program covers the encoding argument in object mode, byte mode
and transforms.
scr_fs_write_once cached whether the calling thread blocks SIGPIPE and
SIGXFSZ in an SCR_TL variable. SCR_TL is empty in builds without thread
instances, so a pool thread's answer applied to the main thread too, and
a later writeSync to a closed pipe ended the process with SIGPIPE
instead of throwing EPIPE. The cache is _Thread_local now. The
fs-write-sync fixture covers a write stream followed by writeSync on a
closed stdout.
A Console instance over process stdio passed a patched write the line and
an error handler, and discarded what the write threw, whatever its
ignoreErrors setting. Node's kWriteToConsole does that only with
ignoreErrors (the default, and the global console). With ignoreErrors
false, from the options or the third constructor argument, it calls
stream.write(line) alone and lets a throw propagate out of the console
method.
The console write path now takes the instance's ignoreErrors, so such an
instance passes only the line and keeps the write's exception pending.
Instances bound to other streams already followed the setting.
- A worker writes the completion wake byte only when the pipe has none.
The loop reads it, with the queue lock held, when it takes the last
waiting completion. The pipe holds a byte exactly while completions
wait, so a burst of completions costs one write and one read instead of
one write per completion and a read loop per dispatch.
- The fs pool's threads keep SIGPIPE and SIGXFSZ blocked for their
lifetime. scr_fs_write_once checks once per thread whether both are
blocked; on such a thread a write makes no signal-mask calls, and a
write that fails with EPIPE or EFBIG consumes the signal it raised.
Other threads keep the per-write guard.
fs-write-sync.test.ts covers EPIPE and EFBIG from write streams, whose
writes run on the pool's threads, and matches Node's errors.
Three runtime changes that lower peak memory without changing behavior:
- In programs with workers, process.stdout.write and process.stderr.write
of a view into a SharedArrayBuffer read it through a 64 KiB buffer
instead of copying the whole view first, so writing a 32 MB shared view
no longer holds a second 32 MB. Programs without workers keep the direct
write.
- The one-slot string spare keeps freed blocks of 512 bytes to 1 MiB only.
A larger block stayed resident until a string of about its size came
along, which a program that replaces its input with an edited copy never
does.
- The main thread trims its arena when its event loop waits with nothing
ready, once it has carved 16 MB more since its last trim, as a parking
thread does: pages that hold only free blocks go back to the system while
the program waits on its workers.
- An async object-literal method whose contextual slot returns a non-promise
(`() => void`) keeps its own promise return, and one whose slot returns a
union takes only the union's promise arm (`Promise<T> | void`): adopting
the whole return type gave the method a non-promise type, refused with
SC2001.
- A type predicate whose record target is itself an arm of the element
union keeps that arm's elements as they are and converts the other arms
with its fields, instead of refusing the filter with SC1090.
The jam of a short constant-count loop now leaves the loop as it is when:
- the inner loop's body assigns the accumulator other than by
`acc = acc | C`. An iteration that runs past its exit could clear bits
after the accumulator was full.
- the inner loop's body assigns the inner counter. The copies share the
counter, so one copy's write changed the iterations of the others.
- the inner loop's start or bound reads the outer counter or a local of the
outer iteration. The copies share the inner loop's start and bound, which
read neither the counter's value nor the copies' renamed locals.
The unit test rejects each shape, and the corpus program adds a function
for each that matches Node's output.