`...(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>
Add the TS7 preflight/order baselines for the new diagnostics and corpus
fixtures so the completeness check and the parity batches cover them.
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.
The scheduled shape overlay lanes for npm and npm-static failed: on Linux
x64 CI the npm suites' Node island cases require SCRIPTC_LIBNODE
(tests/harness/libnode.ts), and this job did not run
.github/actions/libnode. The job now runs it for those two suites, as the
other Linux x64 and macOS test lanes do.
The nightly macOS lanes failed these tests on Linux-specific expectations:
- native-install: installNativeCli resolves the installation directory,
and on macOS /tmp is a link to /private/tmp. The fixture root is now
resolved too.
- counted-loop-emission: two tests counted polls of @scr_exc_flag, the
thread's pending word that ELF executables read. Other targets poll the
process-wide alert word. The tests now emit for x86_64 Linux, as the
file's other ELF tests do.
- native-partitions: Mach-O objects define an ltmp<N> label at the start
of each section, which the function lists now leave out.
- native-profile: profile temperature appears in ELF section names
(.text.hot.); Mach-O sections carry no such prefix. The section name
check, and the check that recovered block counts change the objects (they
move f4 into the hot section), now run on ELF objects only.
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 exit-time cleanup of the path module objects swapped each member
value out of the object's storage by reading v.obj.slots and
v.obj.entries directly. It now goes through scr_dyn_obj_count and a new
scr_dyn.h accessor, scr_dyn_obj_exchange_value_at, which stores a value at
an index without reading the keys, so the cleanup still runs after the
exit-time layout reset.
A changed name now also changes the names that hold it under another
name: a renamed or default import, a renamed or default export, an
`import x = A.b` and a renamed `require()` destructuring. A unit that
spells only the local name of such an import lowers again when the
imported declaration changes.
Regenerate codec.generated.ts for the bigintStats IR type, and round-trip
a BigIntStats program in the codec tests.
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.
Mach-O and COFF objects keep every function in one text section, so the
test compares the objects built with and without the block counts on
every target and checks the hot and unlikely sections on ELF targets.
The LLVM helper's emit reports the sums of the profile's counters on the
functions it annotated and on the functions whose control flow changed since
the profile was collected (annotated_counts and mismatched_counts), and the
partition cache keeps them with each partition's match. The annotated ones
are listed as the profile annotation leaves them, before block counts
recover any. Builds of the native compiler print the share of the profile's
counts that changed functions hold.
- 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.
A worker thread's large blocks come from glibc heaps, and the first large
block of each heap made the allocator check /proc/self/maps before
advising the heap huge pages. The check was skipped only for the heap the
thread advised last, so threads that switched between heaps read the maps
again at every switch: 9,000 to 11,000 reads in a type check of T3 web with
four checkers. A process-wide table now records every heap decided, and
each heap is read once.
A profile merged with --sparse has no record of the functions its
training never ran, so a build with it optimized them as without a
profile, and on tsc-ts they are 72% of the program's IR. The block-count
file a recording build writes now also names those functions, with an
entry count of zero and no blocks. A --profile-use build that reads the
file gives such a function an entry count of 0 and the cold attribute,
as it does for a mismatched function the file records as never run, so
LLVM optimizes it for size and places it with the other unlikely code.
A function the file does not name, such as one added since the profile
was collected, keeps no counts.
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.