Commit Graph
4679 Commits
Author SHA1 Message Date
Malte UblandClaude Opus 5.5 312d7be280 See Pick and Omit views of classes through type aliases
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>
2026-10-29 05:37:46 -07:00
Malte UblandClaude Opus 5.5 b0bbae15ac Spread an empty-object default like its source
`...(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>
2026-10-29 05:37:45 -07:00
Malte UblandClaude Opus 5.5 980a1af682 Build nested literals at the enclosing literal's own layout
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>
2026-10-29 05:37:45 -07:00
Malte UblandClaude Opus 5.5 5a6f2c0902 Destructure options ?? {} over records with required members
`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>
2026-10-29 05:37:44 -07:00
Malte UblandClaude Opus 5.5 e86800de67 Spread unions with several record arms
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>
2026-10-29 05:37:44 -07:00
Malte UblandClaude Opus 5.5 b1b7aff25c Renumber the new corpus programs to 8514 and 8515
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>
2026-10-29 05:34:49 -07:00
Malte UblandClaude Opus 5.5 48d524a321 Lower a JavaScript read of an undeclared name to Node's ReferenceError
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>
2026-10-29 05:34:48 -07:00
Malte UblandClaude Opus 5.5 ff93a5a4de Refuse sloppy implicit-global writes by name, and relax undeclared JS names
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>
2026-10-29 05:34:47 -07:00
Malte UblandClaude Opus 5.5 b234e06f20 Enforce the ECMAScript early errors the checker does not model
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>
2026-10-29 05:34:47 -07:00
Malte UblandClaude Opus 5.5 c6db09c8e4 Relax the async-iterator protocol demand in untyped JavaScript
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>
2026-10-29 05:34:46 -07:00
Malte UblandClaude Opus 5.5 a4c19ae4b4 Host the bare compareArray from the upstream assert.js harness
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>
2026-10-29 05:34:44 -07:00
Chris Tate f96217e289 Count loop-body var initializers as writes, and skip overload signatures
- 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.
2026-10-29 07:24:45 -05:00
Chris Tate 2473207af2 Name the emitter corpus programs instead of numbering them
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.
2026-10-29 07:18:44 -05:00
Chris Tate b421cf6a38 Pass every argument to emitter listeners that read arguments, and deliver the leak warning at exit 2026-10-29 07:12:59 -05:00
Chris Tate d478dc9ec5 Send a zero from an inline RWF_NOWAIT read to the pool
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.
2026-10-29 07:05:55 -05:00
Chris Tate bbec52d459 Give class methods and function values jsval entries for untyped calls 2026-10-29 06:54:29 -05:00
Chris Tate 1acdda7328 Read and write cached regular files on the loop thread in fs streams
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.
2026-10-29 06:33:43 -05:00
Chris Tate 96739536d2 Read fs.read's position option once and check a bigint against length |= 0
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.
2026-10-29 06:22:18 -05:00
Chris Tate 64f9481fb8 Link the web stream classes for an element-access tee, pipeTo or pipeThrough
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.
2026-10-29 04:41:05 -05:00
Chris Tate 32745afc07 Bind generic arrow captures before parameter defaults, and count var redeclarations as writes
- 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.
2026-10-29 04:30:06 -05:00
Chris Tate 60be717d92 Merge main 2026-10-29 04:24:50 -05:00
Chris Tate 442c06fde3 Throw Node's TypeError when a nullable destructuring source is null
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.
2026-10-29 04:20:01 -05:00
Chris Tate c533750aa3 Test a jammed loop's exit every iteration when its counter may not advance
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.
2026-10-29 04:17:06 -05:00
Chris Tate eff8ef743d Read at the exact bigint position in fs.readSync
Co-authored-by: MathurAditya724 <57684218+MathurAditya724@users.noreply.github.com>
Fixes #2404
2026-10-29 04:11:04 -05:00
Chris TateandMathurAditya724 7f33f37384 Run for-in over a receiver that may be null or undefined
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>
2026-10-29 03:58:21 -05:00
Chris Tate 82fbe022b4 Deliver the lines a chunk split after a line listener closes the interface 2026-10-29 03:51:59 -05:00
Chris Tate 6d170bf082 Check every path namespace key now that format and parse have function values 2026-10-29 03:36:13 -05:00
Chris Tate 5f7a9cc8c7 Merge main 2026-10-29 03:34:56 -05:00
Chris Tate a7b673e4ff Keep path module objects and bound path functions Node's values in untyped code
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.
2026-10-29 03:32:36 -05:00
Chris Tate 37128308c3 Match Node's emitWarning, kill, chdir and userInfo behavior
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.
2026-10-29 03:22:35 -05:00
Chris Tate 7921e020c2 Deliver a line to a single line listener without the listener snapshot 2026-10-29 03:13:45 -05:00
Chris Tate b319186a68 Count the arguments a replaced stdio write receives
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.
2026-10-29 03:01:39 -05:00
Chris Tate b7bcbc68d8 Pass a Buffer to a patched stdio write from an encoded data listener
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.
2026-10-29 02:58:50 -05:00
Chris Tate 7329f07d36 Match V8's default locale and calendar in the ICU formatters, and build Intl programs without Zig from the runtime pack
- 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.
2026-10-29 02:42:35 -05:00
Chris Tate 8537c41171 Keep the receiver bracket around stream completion calls
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.
2026-10-29 02:05:14 -05:00
Chris Tate 089a8e1799 fs.watch/fs.watchFile: fix listener removal, adapter ownership and argument checks
- 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.
2026-10-29 01:47:31 -05:00
Chris Tate 2a39a97699 Test the direct index once in number-key lookups, and inline the probe in loops 2026-10-29 01:32:04 -05:00
Chris Tate 85fdf4ce74 Trim the per-chunk work of compiled object-mode stream callbacks
- 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.
2026-10-29 00:40:53 -05:00
Chris Tate b260a13dda Make fewer syscalls per fs thread-pool completion and pool-thread write (#2519) 2026-10-29 00:20:58 -05:00
Chris Tate 50aa42883f Cache the pool-thread signal check per OS thread
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.
2026-10-29 00:18:11 -05:00
Chris Tate 4cfd4f3320 Records: nested-arm filter narrowing, async object-method returns, untyped Map layouts (#2509) 2026-10-28 23:56:59 -05:00
Chris Tate c0ef82865d Run short constant-count loops together when their inner exits saturate (#2493) 2026-10-28 23:49:18 -05:00
Chris Tate 5f27c4d233 Exit cleanly with Node loaded and keep nested entries on Node's stack (NI-7c, NI-3b) (#1905) 2026-10-28 23:43:21 -05:00
Chris Tate 8c79a0f6b8 Pass only the line to a patched stdio write without ignoreErrors
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.
2026-10-28 23:42:43 -05:00
Chris Tate f909bdf6c8 Make fewer syscalls per fs thread-pool completion and pool-thread write
- 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.
2026-10-28 23:34:34 -05:00
Chris Tate 0faade48ae Test that receivers are evaluated once and before effectful keys (#2514) 2026-10-28 23:25:09 -05:00
Chris Tate 6d07a49cbf Return freed memory sooner: chunked shared writes, a bounded string spare, idle trims
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.
2026-10-28 23:24:06 -05:00
Chris Tate 5f7a5dc883 Keep an async method's promise in non-promise slots; convert filtered arms beside an exact one
- 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.
2026-10-28 23:10:05 -05:00
Chris Tate a64b5b0820 Keep saturating loops whose copies cannot share the inner loop
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.
2026-10-28 23:01:16 -05:00
Chris Tate 686c34bd25 Store tty.isatty and process.cwd as function values (#2494) 2026-10-28 22:46:19 -05:00