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.
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.
- 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.
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/.
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.
`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>
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>
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>
`.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>
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>
`''.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>
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.
- 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.
`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.
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.
- Worker programs run strip-mined typed-array loops in blocks of 128
iterations when the program's code is compiled for x86-64-v3 or above,
or with --cpu-dispatch. At that level the vectorized loop over bytes
takes 128 iterations a step, so in blocks of 64 its vector code never
ran.
- In --cpu-dispatch builds, the LLVM helper gives an x86-64-v3 copy the
optimized code of its baseline copy when the v3 code has vectors of at
most 128 bits, none wider, and no rounding or bit-count intrinsic in a
loop. Its direct calls go to the callees' x86-64-v3 copies. The text
partitioner places each x86-64-v3 copy with its baseline copy, so the
two are optimized in one partition.
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.
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.
With SCRIPTC_INFER_JS=1, shape inference writes its overlay for the program's own JavaScript files, with or without npm-static packages, and leaves package files as they are. The analysis and overlay keep a value untyped where an inferred type would be narrower than what the program holds: awaited values of for await, iterable object literals with computed keys, matchAll and other builtin results, fields an instance may lack, overridden methods and getters, class instances reached by reflection, constructor parameters stored into wider fields, and results the analysis cannot type. A computed member name no longer folds a read that runs before its binding is initialized. In every build, a JavaScript binding written with a value that may be a string, or with a union, keeps the dynamic representation.