Commit Graph
6750 Commits
Author SHA1 Message Date
Chris Tate 2bc82f97dc Merge main 2026-10-30 03:40:32 -05:00
Chris Tate 422a02e225 Compile three Node test shapes: a top-level return before declarations, nullable strictEqual operands, and a branch-assigned let (#2590) 2026-10-30 01:04:18 -05:00
Chris Tate 13c5fc6748 pi wave 5: node process APIs, stdlib singletons, misc lowering (#2548) 2026-10-30 01:00:51 -05:00
Chris Tate 8e07b89461 Keep typed records unboxed in object-mode streams (#2601) 2026-10-30 00:41:54 -05:00
Chris Tate 8a5d2e14a2 Match Node's socket encodings, rejected writes and event loop liveness (#2599) 2026-10-30 00:09:13 -05:00
Chris Tate 3ddebf4c31 Read a captured handle let as the checked-dynamic value unless the checker types the read as the handle
Inside a nested function, a read of a captured handle `let` narrowed to
the handle whenever it was a member receiver. A function declared before
the first write reads the binding as `any`, so the narrowed handle reached
member reads typed `any`, and an optional read (`c?.remoteAddress`)
compiled to an unconditional SC1090 throw. js-dyn-server, whose listener
reads `server.listening` inside the callback passed to createServer, hit
the same path.

The read now narrows only when the checker types it as the handle. An
optional member read keeps the tagged value, so undefined short-circuits
and the handle arm narrows; other reads take the checked-dynamic value.
The lowerer's closureDynLets collection is a checked journal table.

Corpus evolving-let-socket-optional-reads.cjs reads and calls members
through `?.` on a captured socket before and after its first write.
2026-10-30 00:07:34 -05:00
Chris Tate c36c5d99ce Support non-Error 'error' payloads and checked-dynamic listeners in listeners() and meta events (#2586) 2026-10-30 00:04:33 -05:00
Chris Tate e36a442468 Throw for primitive Reflect.has targets, record isRaw, and match StringDecoder chunks
Reflect.has over a target held as any, unknown or a union answered a
presence result for a primitive. It now checks the target at run time
after both arguments are evaluated and throws Node's TypeError
("Reflect.has called on non-object"). The stdlib-fence snapshot no
longer lists Reflect.has, which lowers.

process.stdin.isRaw read a flag that only the dynamic setRawMode path
set, so a direct process.stdin.setRawMode(true) on a TTY left it false.
The flag now lives with scr_process_stdin_set_raw_mode, which every
path calls, readline's raw mode included.

StringDecoder.end(chunk) with a dynamic chunk that is undefined threw
ERR_INVALID_ARG_TYPE. It now only flushes, as Node's end skips the
write for undefined. A dynamic ArrayBuffer or SharedArrayBuffer chunk
decoded its contents; write and end now throw Node's
ERR_INVALID_ARG_TYPE for it, as it is not an ArrayBufferView.
2026-10-29 23:59:44 -05:00
Chris Tate bcda45ab7f Merge main 2026-10-29 23:58:03 -05:00
Chris Tate 6d496cd0cf Emit each loop once in the body a parameter version leaves (#2587) 2026-10-29 23:47:08 -05:00
Chris Tate 2353a04540 Keep typed records unboxed in object-mode streams 2026-10-29 23:46:36 -05:00
Chris Tate 8d92f6a886 Link bench:runtime layout variants with zig cc on the musl lane (#2600) 2026-10-29 23:45:47 -05:00
Chris Tate e6cea05db9 Ship ICU data without the categories no Intl form reads (#2598) 2026-10-29 23:29:51 -05:00
Chris Tate f67678b422 Pipe decoded socket strings and give lane jobs their own tmpdirs
After socket.setEncoding(enc), socket.pipe(destination) wrote the raw
bytes it read to the destination. It now writes the decoded strings, as
Node's pipe writes each string chunk: the destination receives their
UTF-8 bytes, and a partial character the decoder holds at EOF is written
before the destination ends.

tests/node-parallel/run.mjs runs each job's files with its own
TEST_THREAD_ID, as Node's test runner does, so test/common/tmpdir gives
concurrent files different directories instead of one shared .tmp.0.
2026-10-29 23:16:13 -05:00
Chris Tate 755b7d997b Read untyped listener arrays for tuple-less events, and route typed meta listeners beside checked-dynamic ones
- listeners() and rawListeners() in TypeScript of an event that no literal
  emit or listener gives a tuple (one registered only under computed names)
  read the untyped array, so each returned listener takes every argument a
  call passes. The array was checked into zero-argument functions, which
  called the listener without its arguments.
- A typed 'newListener' or 'removeListener' listener in a program with a
  checked-dynamic listener of the other meta event registers through the
  adapter. The meta lowering's check looked only at the listener's own
  event and refused it with SC2020.
2026-10-29 23:11:27 -05:00
Chris Tate a9c4647e0c Link bench:runtime layout variants with zig cc on the musl lane
The --layouts wrapper always forwarded to clang. A musl target links with
`zig cc`, and SCRIPTC_LINKER replaces the driver together with its `cc`
argument, so every layout build failed. The wrapper now forwards to
`zig cc` when SCRIPTC_TARGET names a musl triple.
2026-10-29 22:57:02 -05:00
Chris Tate 2d3361ddc1 pi wave 6: records and unions (#2597) 2026-10-29 22:55:28 -05:00
Chris Tate b47a1bd2cf Merge main 2026-10-29 22:54:58 -05:00
Chris Tate 01c36171e0 Regenerate the IR codec for partial record views (#2595) 2026-10-29 22:42:50 -05:00
Chris Tate 2cc51b1427 Reject import() of unresolvable specifiers with Node's ERR_MODULE_NOT_FOUND (#2563) 2026-10-29 22:35:34 -05:00
Chris Tate 52111b86c6 Merge main 2026-10-29 22:27:36 -05:00
Chris Tate aa912769f7 Records and literals: multi-arm spreads, nullish-empty destructuring, nested literal layouts, aliased Pick views, checked method-field adapters (#2545) 2026-10-29 22:27:07 -05:00
Chris Tate 671e7d8bc5 Read and write record fields through literal-union keys of one field type (#2580) 2026-10-29 22:18:36 -05:00
Chris Tate d80b34d92d Match Node's socket encodings, rejected writes and event loop liveness 2026-10-29 22:18:16 -05:00
Chris Tate 902e8b4396 Ship ICU data without the categories no Intl form reads
The runtime pack shipped ICU's full icudt78l.dat (33.1 MB), and every
Intl executable gets a copy beside it. scr_intl.c answers its Intl forms
with ICU's break iterators, number and date formatters, calendars and
locale IDs, which never read the charset converters or the collation,
language, region, time zone name, unit, spell-out and transliterator
trees: 21.5 MB of the file.

- The pack build writes icudt78l.dat without those items
  (ICU_DATA_DROPPED in icu-inputs.mjs). filterIcuData rewrites the data
  file's table of contents to the remaining items, and the result is
  checked against a pinned sha256 and size (11,653,984 B).
- tests/harness/icu-data-filter.test.ts runs every @requires-icu corpus
  program under 47 locales and 3 time zones with the pack's data, with
  the full data and in Node, and fails when the outputs differ, so an
  Intl form that reads a dropped category fails there.
- intl-word-segmenter-dictionaries.ts segments Chinese, Japanese, Thai,
  Lao, Khmer and Burmese text, which ICU breaks with the dictionaries in
  brkitr/.
2026-10-29 22:17:47 -05:00
Chris Tate 7befe5d506 Regenerate the IR codec for partial record views
codec.generated.ts was not regenerated when partialView was added to
IR expressions, so IR read through the binary codec dropped the field
and the stale-codec check failed.
2026-10-29 21:52:03 -05:00
Chris Tate 33018e9d8b Compare a held generic function with scalars without the identity check
A generic declaration held at several signatures and read through
unknown was refused with SC1090 when compared with a string, number or
boolean. A function is unequal to such a value whichever instance it is,
so a comparison with an operand the checker types as a string, number,
boolean, bigint or symbol is not recorded for the identity check.
2026-10-29 21:47:27 -05:00
Chris Tate 141464a6f7 Encode plainLoops in the generated IR codec
`IrFunction.plainLoops` was missing from codec.generated.ts, so IR read
back through the binary codec lost it. Regenerated with
scripts/generate-ir-codec.mjs.
2026-10-29 21:43:38 -05:00
Chris Tate 9e261bfa2a Keep x86-64-v3 code from running slower than the baseline (#2588) 2026-10-29 21:40:37 -05:00
Chris Tate dd47d1b884 Merge main 2026-10-29 21:34:09 -05:00
Chris Tate ee621414c6 Resolve relative import() specifiers as URLs for the not-found rejection
probeNodeFileImportRefusal resolved a relative or absolute specifier as a
filesystem path, so a percent escape, a query or a fragment became part of
the path: "./a%20b.mjs" was looked up as "a%20b.mjs", and "./x.mjs?v=1" of
an existing file rejected. It now resolves the specifier as a URL against
the importer's file URL, as Node's ESM resolution does: the message names
the decoded path without the query or fragment, and the error's url is the
resolved URL with them. A path ending in "/", an encoded "/" or "\" and a
malformed escape, which Node refuses with other errors, keep the current
lowering.

An absolute specifier that CommonJS resolution finds (for example a file
named without its extension) gets Node's "Did you mean to import" hint.
Node resolves the hint of a relative specifier against the process's
working directory, so relative specifiers get none.

npm-file-import.test.ts compares the probe with Node's import() rejections
for 29 specifiers, and the dynamic-import-not-found corpus program covers
an escaped specifier with a query and fragment.
2026-10-29 21:26:24 -05:00
Chris Tate 98f5f22a7b Forward a configured libnode pack into test:sandbox lanes (#2589) 2026-10-29 21:25:58 -05:00
Chris Tate 6a05a2588f Merge main 2026-10-29 21:25:26 -05:00
Chris Tate 43c72a8e9b Save the receiver and key locals of a literal-union keyed write 2026-10-29 21:18:04 -05:00
Chris Tate 2a1f7ac212 Give class methods and function values jsval entries for untyped calls (#2543) 2026-10-29 21:17:19 -05:00
Chris Tate 37511819d6 Infer types for the program's own JavaScript behind SCRIPTC_INFER_JS (#2581) 2026-10-29 21:12:13 -05:00
Malte UblandClaude Opus 5.5 eabcbdb533 State the empty-array arm rule once
The three sites that give an empty `[]` literal a home — a union slot, a
contextual union, and the `??` default — each carried their own copy of
the rule and each tested it slightly differently (element count here, a
`never[]` own type there), so a later fix could drift one from the
others. emptyArrayLiteralArm states it once: an empty literal has no
element to select an arm by, so it builds as the destination's first
array arm. Every site keeps the lowering it had.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:11:36 -07:00
Malte UblandClaude Opus 5.5 887aa774e0 Build an empty array literal in a union of array slots at its first arm
`const message: M = { content: [] }` with `content: TextContent[] |
ImageContent[]` threw at run time, `a 'number[]' value is not
representable in the target union`. tsc types an empty literal `never[]`,
whose element has no representation of its own: the literal mapped through
never's f64 element as `number[]`, then the union slot's retag found no
width lift into either array arm and took the stranded-source trap.

An empty array carries no element to select an arm by, and it is the same
value whichever array family stores it, so the literal is built as the
union's FIRST array arm — the rule a lone array slot already states, and
the one the ternary arm and the `?? []` default apply. Both the IR slot
(lowerExprExpecting) and the literal's own type mapping (lowerArrayLiteral)
now state it for any number of array arms; a single array arm keeps the
identical lowering it had. A `never[]` VALUE (an explicitly declared
variable) still maps to `number[]` and keeps its refusal: mapping never's
element globally is a different change.

Corpus: tests/corpus/empty-array-union-slot.ts pins the nested record
field, declarations and reassignment, declared and async returns, ternary
arms (one and both empty), arguments and default parameters, deeper
nesting, a class field, an optional arm, array/Set/Map element slots, a
callback argument, an assertion, and a fresh array per evaluation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:11:34 -07:00
Malte UblandClaude Opus 5.5 830ee3ddf3 Build a fresh object literal at an async record result
An unannotated async method inside a contextually typed object literal is
checked against the contextual signature's result, while its own inferred
return type stays the returned literal's narrower record. The async return
lowered the literal untyped and then coerced that wider record into the
declared result, which refused on `content`: the wider content-part union
array does not lift into the inferred text-record array. A ToolDefinition
execute reached through a Record<Name, ToolDefinition> hit the same
SC2002.

A fresh object literal returned into a record result is not a thenable,
so when the ordinary construction cannot convert it is now built at the
result slot, in both the synchronous and the promise-adopting return
paths. The field-set comparison also considers the literal's own checker
type, not only the contextual type it was checked against. Both the
return-path rule and the object-literal rule ask the same question first —
does the record the ordinary path builds have a width plan into the
result? — so a literal that converts today, and every value that is not a
fresh literal, keeps its exact lowering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:11:25 -07:00
Malte UblandClaude Opus 5.5 0c6a6d1db5 Lift a dynamic record field into a union destination
A record field the checker types `any`/`unknown` lifts into a non-union
destination through a dynCheck, but the union branch of widthLiftPlan
searched only for a source arm, and a dyn source has no arm family. SC2002
therefore refused `details: any` into `Details | undefined`, a tool
result's `structuredContent: unknown` into a JSON-value union, and the
same field carried through a spread into an index-signature union member.

The union branch now ends with that destination check: when no arm was
selected, a dyn source takes the same canDynCheckTo extraction
coerceToExpected already emits for a dyn value in a union slot. It sits
after every arm rule, so every conversion that exists today (including a
union that has its own dyn arm) keeps that plan unchanged; only the case
that returned null before converts now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:11:19 -07:00
Malte UblandClaude Opus 5.5 c294d129d0 Filter a union of arrays arm by arm
`.filter` on `A[] | B[]` refused with SC2020: `lowerArrayUnionHofCall`
admitted only `map` and `forEach`. Under TS 5.2's callable-union rule the
predicate runs over `A | B`, and the result element is the one the
predicate names — `(b): b is TextContent` over a union of block arrays
answers `TextContent[]` — which no single arm's element need equal.

`filter` joins the same arm dispatch: each arm filters its own concrete
array through the existing per-arm helper, and its survivors then pass
through the checked conversion to the checker's result element, so a
predicate that kept an element it does not describe throws instead of
reinterpreting a value. An arm with no such conversion (a boolean
predicate that names no narrower element) leaves the SC2020 fence in place.

Typed programs are untouched: the gate only widens, and `map`/`forEach`
keep their exact helper key, so the helpers a program already emitted stay
byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:11:10 -07:00
Malte UblandClaude Opus 5.5 b3d69baae8 Narrow a stored union to the stdlib slot's arm instead of refusing
A checked-dynamic JSON stringify (any record or array carrying an `unknown`
field, so the runtime walker serializes it) answers `string | undefined` in
the IR — its live walker can return undefined for a dyn root — while the
checker types the call `string`. Reading a string member of that result
(`JSON.stringify(toolsAdded).length` in a token estimator) then met the
lib-boundary pass, which refused every union at a concrete slot with SC2003
"expected 'string', got 'string | undefined'".

The slot's own type IS the site's proven type: tsc approved the call, so the
stored union is the declaration's wider one (an absence arm the compiler
itself carries, a stale capture, a widened inferred return). Coerce it with
coerceToExpected's checked arm rule — the proven arm's payload comes out and
any other arm throws the existing catchable TypeError — and keep the refusal
for a union that does not carry the slot type as an arm. No compiling program
changes IR: every program on this path refused before.

Corpus: tests/corpus/lib-slot-union-arm-extraction.ts pins string members
(length, slice, toUpperCase, indexOf, template interpolation, comparison and
concatenation) of dyn-root JSON stringify results over arrays, nested
records and empty collections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:10:47 -07:00
Malte UblandClaude Opus 5.5 677710075a Let an empty ?? array default adopt the left's array arm
`''.match(re) ?? []` typed the literal uninhabited (`never[]`), whose element
still mapped through never's f64 value representation, so the whole
expression was `RegExpMatchArray | number[]` — a result no single array slot
can receive. A union return such as `"all" | readonly string[]` refused with
SC2003 "union conversion has no unambiguous layout mapping" (`getScriptReach`
in a coding agent's tool-discovery path).

An EMPTY array-literal default has no element of its own: it adopts the
left's present array arm, exactly the rule the union slot and the ternary arm
already state for an empty literal. The left's nullish test and the lazy
default are unchanged, and a default whose own type already matches the left
keeps the identical lowering it had.

Corpus: tests/corpus/nullish-empty-array-default.ts pins the union return,
string element reads through the default, the declared optional-array slot
with a fresh empty value per evaluation, and a parenthesized/template default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:10:46 -07:00
Chris Tate 919151d339 Lower partial record assertions to record views (#2555) 2026-10-29 20:56:21 -05:00
Chris Tate f521dc91b1 Compile three Node test shapes: a top-level return before declarations, nullable strictEqual operands, and a branch-assigned let
A top-level return in a CommonJS module compiles when declarations follow
it. When the return is taken, only code that already exists can observe the
bindings it skips: functions and classes created before the return, and
function declarations after it. A module where one of them reads a binding
declared after the return, or that assigns exports after it, still stops
with SC1090.

assert.strictEqual and assert.notStrictEqual compare a nullable number,
string or boolean (`number | null`, `string | null`) with a scalar, null or
undefined through the checked-dynamic SameValue compare, which reports
Node's failure messages. notStrictEqual of another nullable value, such as
`child.stdin`, against null or undefined tests the value's tag.

An initializer-less JavaScript `let` whose writes all agree also takes
their type when the writes are calls declared in the standard library, such
as `spawn(...)`, and at file scope. A runtime handle that a closure
captures takes the handle or undefined. Inside a nested function, a member
read on it (`c.address()`, `child.stdin`) reads the handle and throws
Node's TypeError while the binding is still undefined; other reads there
take the checked-dynamic value.
2026-10-29 20:51:09 -05:00
Chris Tate f30f622a94 Keep x86-64-v3 copies' code in instrumented and colder-profiled builds
- In a module that counts executions for a profile (--profile-generate,
  also the context-sensitive second phase), every x86-64-v3 copy keeps
  its code. A copy that took its baseline copy's body would increment the
  baseline copy's counters, and a --profile-use build would then treat the
  x86-64-v3 copy as never run.
- With a profile, a copy keeps its code when its baseline copy ran fewer
  times. A profile collected on an x86-64-v3 machine gives the baseline
  copies no runs, and their bodies are optimized as cold code.
2026-10-29 20:47:16 -05:00
Chris Tate 4b8894a8bf Check strip-mining in the int32 parameter version of the bounded-loop test
`open` in the bounded-loop poll test is versioned on its loop bounds, so its
strip-mined loops are in its int32 parameter version. The test now checks
them there, and checks that the body the version leaves polls its loops
without strip-mining. Also records the CI cost of the
loop-bound-parameter-plain-loops corpus program.
2026-10-29 20:36:01 -05:00
Chris Tate ee5e659648 Regenerate the IR codec for IrFunction.jsEntry
The binary IR coders now write and read a closure body's jsval entry, so
a function read back from the binary encoding (reused lowering units,
lowering journal records, binary IR files) keeps it. The codec
round-trip test covers a program with jsval entries and a function with
jsEntry set.
2026-10-29 20:26:34 -05:00
Chris Tate b9f5486c9c Type fields read before the constructor stores them, and key cached builds by the inference settings
With SCRIPTC_INFER_JS=1, a field counts as always present only when it is defined before any code can observe the instance: construction runs from the base class down, field initializers and then the constructor body, and a statement that reads or passes on `this` (a helper called with `this`, a local holding it, a method call, a base constructor calling an overridden method) or an early `return` ends the scan. Otherwise the field's overlay type includes undefined, so `inspect(this); this.x = x` reads undefined in `inspect` as Node does. The early executable cache and the shape overlay memo now key their entries by SCRIPTC_INFER_JS and SCRIPTC_SHAPE_OVERLAY, so toggling either rebuilds instead of restoring a build made with the other setting.
2026-10-29 20:25:14 -05:00
Malte UblandClaude Opus 5.5 f5a677f086 Forward a configured libnode pack into test:sandbox lanes
The lanes run with CI=1, so the Node island tests require a libnode pack and
fail rather than skip without one. A lane Sandbox prepared from a snapshot
that carries a pack had no way to be told where it is, because the lane
environment pinned only CI, SCRIPTC_SAN and the shard variables. Forward an
explicitly configured SCRIPTC_LIBNODE — a directory inside the Sandbox, never
the host — through sandboxLaneEnv, which every remote lane command already
uses, and document it in the runner's help.

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