Commit Graph
4591 Commits
Author SHA1 Message Date
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 794ede51db Read the callee before the arguments in calls through untyped receivers (#2510) 2026-10-29 19:53:04 -05:00
Chris Tate 7943812231 Keep omitted optional fields in the partial view's layout; save the receiver
A partial record assertion's literal now keeps the asserted type's
optional fields it omits, in their absent state, as an ordinary record
of that type does: a read yields undefined, in answers false and a write
adds the property, with no guard. Only an omitted required field is
missing from the view, and only its writes are guarded, so a write to an
absent optional field of an ordinary record of the same type no longer
throws. The receiver of a guarded write is saved whenever its value is,
so a right side that reassigns the receiver's variable no longer
redirects the check and the store.
2026-10-29 19:47:53 -05:00
Chris Tate 02ff0eb16b Read the getter first for classes that a later lowering wave reaches
A call rewritten for a getter-only name records the classes it tests. When a later wave reaches another class with the same getter, the fallback call is rewritten again for that class, instead of being skipped, so its instances also read the getter before the arguments. Before, `target.step(arg)` on such an instance threw "target.step is not a function".
2026-10-29 19:02:23 -05:00
Chris Tate d8f3565ac4 Call untyped write completion callbacks with the error argument
A write completion callback that a JavaScript module returned reaches
the stream lowering as an untyped value. Since the error-argument form
of write callbacks, the lowering refused it with SC2020, so
tests/corpus/8532-stream-saved-callback-receiver failed to compile.

Such a callback is now converted to a function of the error argument.
The stream calls it unbound with null after a successful write or with
the write's error, and ignores what it returns, as Node does.
2026-10-29 18:55:44 -05:00
Chris Tate 7b27410698 Intl over ICU 78.3: Segmenter (grapheme, word) and default-locale toLocaleString (linux-x64-gnu) (#2504) 2026-10-29 16:42:38 -05:00
Chris Tate 78dbd98b1a Merge main 2026-10-29 16:39:31 -05:00
Chris Tate 016ee41146 Check untyped destructuring sources in JavaScript files before reading keys (#2577) 2026-10-29 16:01:39 -05:00
Chris Tate 4e67c5ce2c Host stdio: typed process.stdout.write slot, write-callback errors, drain, run-time-typed chunks (#2546) 2026-10-29 15:59:21 -05:00
Chris Tate 0725682c87 Check untyped destructuring sources in JavaScript files before reading keys
A JavaScript object pattern over an untyped source (the result of an untyped function, a parameter, a property of an untyped object) now checks the source first when V8 can spell it, as the `any` path in TypeScript files does: a null or undefined source throws "Cannot destructure property 'size' of 'lookup(...)' as it is null." instead of "Cannot read properties of null (reading 'size')". When the first element has a default and a static key (`{ size = 0 }`), the source is not checked first and the read throws, as in V8; this also applies to `any` sources in TypeScript files and to nullable union sources.
2026-10-29 15:58:44 -05:00
Chris Tate 3b1a2c41b6 Merge main 2026-10-29 15:55:05 -05:00
Chris Tate 6f019151b7 Test only the exponent of a masked value's shift and sum in speculative integers 2026-10-29 15:45:58 -05:00
Chris Tate f1825611fb Collect garbage cycles across the scriptc and V8 heaps (NI-8) (#1908) 2026-10-29 15:33:24 -05:00
Chris Tate 0e6dec51d4 Test262: private static members with brand checks (#2549) 2026-10-29 15:31:06 -05:00
Chris Tate 714385609e Call private static fields through the general value-call lowering
A call through a function-valued private static field (this.#fn(...))
completed its arguments against the field's fixed parameter list, so a
rest-parameter function lost the arguments for its rest parameter, and a
function that reads arguments was refused. The call now takes the
ordinary call-through-value lanes (typed rests, runtime-arity closures,
checked-dynamic values) and binds this to the checked receiver.
2026-10-29 15:27:36 -05:00
Chris Tate b8a6d3805a Islands: CommonJS island requires, builtin-class handles, JavaScript widened bindings (#1907) 2026-10-29 15:27:09 -05:00
Chris Tate 0117f39da0 pi wave 5: union layouts and reads, provider and tool records (#2547) 2026-10-29 15:25:45 -05:00
Chris Tate 87c8d14510 Test262: String/Number/Boolean wrapper objects in JavaScript sources (#2550) 2026-10-29 15:24:04 -05:00
Chris Tate 97fc2a9ac1 Avoid call-stack overflow from spread pushes over large compiler lists (#2553) 2026-10-29 15:22:36 -05:00
Chris Tate d972a6bc13 Defer island modules whose recorded load is deterministic (NI-7b/7d/7e/7f) (#1906) 2026-10-29 15:18:28 -05:00
Chris Tate 43bafa67d1 Test262 group C: early errors, undeclared reads, async-iterator relaxation, compareArray harness (#2500) 2026-10-29 15:17:08 -05:00
Chris Tate ffa0753a82 Generic arrows: enclosing-frame captures (incl. mutable, boxed), optional-call arity, declared-arity refusal (#2506) 2026-10-29 15:14:11 -05:00
Chris Tate 145f8fcd5e fs.watch names the changed entry; fs.watchFile polls in its own gated unit (#2507) 2026-10-29 15:13:12 -05:00
Chris Tate 2de17f95df Pass every argument to emitter listeners that read arguments, and deliver the leak warning at exit (#2544) 2026-10-29 15:12:50 -05:00
Chris Tate b9587f361d Reuse lowered units across builds behind SCRIPTC_INCR_LOWERING=1 (#2521) 2026-10-29 15:11:51 -05:00
Chris Tate f2e82c68e1 Pack jammed saturating loops one copy per lane and test their exit every fourth iteration (#2522) 2026-10-29 15:11:29 -05:00
Chris Tate 16f3c16752 Match Node's node:path behavior from Node 24's path tests (#2495) 2026-10-29 15:10:50 -05:00
Chris Tate 6b2af671e5 Weigh program profile matches by the profile's counts (#2528) 2026-10-29 15:10:28 -05:00
Chris Tate 2d2cd25d26 Count the arguments a replaced stdio write receives (#2530) 2026-10-29 15:10:03 -05:00
Chris Tate 3c9d162a99 Test the direct index once in number-key lookups, and inline the probe in loops (#2525) 2026-10-29 15:09:43 -05:00
Chris Tate 7eda6fa880 Trim the per-chunk work of compiled object-mode stream callbacks (#2523) 2026-10-29 15:09:24 -05:00
Chris Tate 59eced7a6c Lower ReadableStream tee, pipeTo and pipeThrough in TypeScript (#2513) 2026-10-29 15:09:05 -05:00
Chris Tate 771b9a96ba Throw Node's TypeError when a nullable destructuring source is null (#2537) 2026-10-29 15:08:26 -05:00
Chris Tate 08e2b4596a Run for-in over a receiver that may be null or undefined (#2534) 2026-10-29 15:06:00 -05:00
Chris Tate 5dc4bfd6d8 Read builtins into untyped TypeScript slots as Node's values (#2556) 2026-10-29 15:05:19 -05:00
Chris Tate d19fad3999 Match Node's emitWarning, kill, chdir and userInfo behavior (#2532) 2026-10-29 15:04:59 -05:00
Chris Tate ce6d9d0f1a Skip the receiver bracket for adapters of receiver-free callbacks (#2564) 2026-10-29 15:03:34 -05:00
Chris Tate 0f6c3ae700 Count ArrayBuffer storage in process.memoryUsage() (#2569) 2026-10-29 15:03:14 -05:00
Chris Tate b8e678b765 Evaluate a drain name expression, and discard a write callback's return value
events.once(process.stdout, name) with a name typed "drain" returned the
pending promise without evaluating the name, so a call or comma
expression producing "drain" never ran. A name that is not a string
literal now runs before the promise is created.

A write completion callback that returns a value (`() => count++`, a
stored function typed `(): number`) reported SC2020. The stream ignores
the return value, so such a callback is adapted to return nothing, as
before the error-argument form was added.

The stream-shims npm case compiles and matches Node now that write
callbacks take the error argument, so its SC2020 expectation is removed.
2026-10-29 14:48:45 -05:00
Chris Tate f9b024dbab Count ArrayBuffer storage in process.memoryUsage()
process.memoryUsage() reported external and arrayBuffers as 0. arrayBuffers
is now the storage of the ArrayBuffers, typed arrays, Buffers, DataViews
and SharedArrayBuffers the program holds, and external the same storage
without SharedArrayBuffers, as Node splits them. A view over existing
storage adds nothing. v8.getHeapStatistics().external_memory reads the
same figure as external.

Each ScrBytes owner records the bytes it counts in the struct's padding,
so ScrBytes stays 64 bytes and the offsets compiled code reads hold; a
static assertion pins them. Owners are counted in a per-instance counter,
because their reference counts are not atomic and they are released on
their instance's thread. Shared storage moves its owner's count to a
process-wide atomic counter, since any thread may release it.
2026-10-29 13:36:41 -05:00
Chris Tate 62943d7460 Search wrapper graphs once, brand the wrapper prototypes, read call members once
- The wrapper check before JSON, util.inspect, structuredClone and deep
  equality searched the value's graph recursively with only a depth
  limit, so an object with two references to itself took exponentially
  many calls, and a wrapper more than 64 levels deep was not found. The
  search now keeps a set of the objects it reached and an explicit stack:
  each object and array is searched once, at any depth.
- String.prototype, Number.prototype and Boolean.prototype are wrappers of
  "", 0 and false, as in Node, so their valueOf and toString answer the
  primitive instead of throwing the brand TypeError. String.prototype has
  its own `length` of 0.
- A string-named method call on a dynamic receiver in a program with
  wrappers read the member twice: once to check for the builtin
  String.prototype method and once for the call. The call now reads it
  once, and wrapper.isStringMethod checks that value.
- createsWrappers is listed with the lowerer's first-use caches that the
  unit journal leaves out.

The wrapper corpus program covers the prototypes' values, a getter read
once per call and console.log of a 40-level shared graph. The refusal
test covers a wrapper 100 levels deep, a wrapper under a shared graph and
util.inspect of a cyclic value.
2026-10-29 13:31:39 -05:00
Chris Tate 8ccaea7e5c Keep builtin methods of untyped receivers whose name a class declares as a getter
A call whose name some class declares only as a getter reads the member first on instances of those classes, and keeps the runtime's method dispatch for every other receiver, so n.toFixed(2) still formats a number when a class declares get toFixed(). This replaces the list of builtin method names.
2026-10-29 13:14:45 -05:00
Chris Tate 7a9ad0140f Read omitted optional fields of partial assertions as undefined
A partial record assertion's view has no slot for any field the literal
omits. An omitted optional field now reads as undefined and answers false
to in, as in Node, instead of reaching a slot the view does not have. The
in test's join takes its predecessor from the block the field load ends
in. A write of an omitted field evaluates the receiver and the value
before the presence check, so the right side's side effects run before
the TypeError. The lowerer unit tests' program lists its source files,
which the partial-assertion scan reads.
2026-10-29 11:15:45 -05:00
Chris Tate 14babd6231 Call private static function fields and update private static fields through this
A call of a function-valued private static field through a receiver other
than the class's own name (this.#fn(a) in a static accessor) checks the
receiver and calls the stored function with this bound to the receiver,
instead of reading a public "#fn" property. this.#x++, --this.#x and
this.#x op= v check the receiver where the old value is read and update
the field's native storage, instead of a public "#x" property.
2026-10-29 11:13:10 -05:00
Chris Tate c12f2d64f3 Keep the runtime pack's verified artifacts in a record beside the pack
Each build stages the runtime artifacts it links from the runtime pack and
hashes each one with SHA-256 unless the build cache records that it was
verified with the file identity it still has. A build with an empty cache
therefore hashed every artifact again: on Linux x64, 40 objects and 4.3 MB
for a Benchmarks Game program, about 25 ms of a 300-1100 ms build.

Staging now also reads and writes runtime-pack.verified.json beside the
pack. It lists each verified artifact with its digest and file identity
(device, inode, size, mtime and ctime), as the cache's record does:

- An artifact is trusted only when one of the two records lists it with
  the manifest's digest and size and the identity its file has during the
  read. Any change to the file means hashing it again.
- A missing, unreadable or malformed record, or a malformed entry, is
  treated as absent.
- The record is written to a new file and renamed over the old one, so a
  reader sees a whole record. A write merges the record as it is then.
- When the pack directory cannot be written, the cache's record is the only
  one, as before.

The runtime pack packages do not publish the file. The native CLI build
removes the record its probe builds leave in the bundled pack, and
verify-native-cli.mjs rejects a distribution that has one.
2026-10-29 11:11:35 -05:00
Chris Tate 51c9e31533 Skip the receiver bracket for adapters of receiver-free callbacks
A signature adapter forwards the ambient receiver to its original, and
its closure carries the original's RECEIVER_FREE bit. Calls through the
closure's own pointer tested that bit, but the direct entry of a known
adapter target bracketed every call, because the adapter itself counts
as a receiver reader. The direct entry now tests the bit too.

The receiver readers analysis also admits more node kinds that run no
user code: optional chains, record presence and absent fields, dynamic
boxing, array for-of loops, typed array writes, typed array methods and
constructors over typed operands, the module binding check and region
intrinsics, performance.now, Atomics operations, string and number
library calls over primitives, and new Error(message, options) without
an options object.
2026-10-29 10:42:30 -05:00
Chris Tate 7aaf98b53c Check nullable destructuring sources through their unit arm
The TypeError check for a null or undefined source now reads the unit arm the value holds as a dyn null or undefined, instead of converting the whole union to dyn. A union whose data arm has no dyn form, such as a record holding a generator, compiles again instead of failing with SC9001.
2026-10-29 10:24:12 -05:00