Stage L3 of the localized-failure design.
A degraded type used to become the checked dynamic as a whole slot. Its
lenient mapping now keeps the compilable shape: the type maps again with
every nested component whose mapping fails as the checked dynamic, so a
record keeps its static members with one dynamic member, an array of
records keeps its records, and a Map, Promise or function keeps its static
parts. When even that shape does not map, or a unit still fails with it,
the type falls back to the whole-slot checked dynamic (one more round).
Lenient forms are IR types of one lowerer's shape tables, so the coverage
remainder re-derives them. A pushed-down type is one SC6104, at the slot
where it degraded, marked as keeping its shape.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A function expression's or arrow's parameter and return types commit at the
statement that holds it, and a generic instance's at the instance drain.
Both are now localized-failure commit points for declaration slots: the
failing type degrades and the statement or instance lowers again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A flush inside a capture buffer cannot retry (a probe that later declines
must see today's answer), so its copies of the declaration's diagnostics
reach the build when the statement commits. That statement commit point
now reopens such a declaration: when its diagnostics are retriable, they
leave the build, the declaration retries, and the statement lowers again.
Probe copies of a declaration that already registered are dropped.
The 'any'-typed class field refusal stays a fence at collection and is
lifted by the flush retry, so only reached classes change; the coverage
remainder registers the declarations reachable emit registered, and maps
the degraded types alike.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage L2 of the localized-failure design.
A class whose field (or parameter property) type has no static lowering,
or a function or method whose parameter or return type has none, used to
be rejected, and every use of it failed on its own line. Its collection
deferred the diagnostics until a reachable reference flushed them into the
build. That flush is now a commit point: when every deferred diagnostic is
a degradable type failure at one of the declaration's slots, the failing
types degrade and the collection runs again, up to 16 rounds. The
declaration registers with checked dynamic slots (one SC6104 each), the
reference proceeds, and emitReachable (or run) lowers the bodies it had
skipped. A derived class whose base registers this way retries with it.
The 'any'-typed class field refusal is lifted: a TypeScript class field
whose type maps to the checked dynamic stores it, as JavaScript classes
already do.
A program that compiles today flushes no deferred diagnostics into its
build, so nothing degrades and its output is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its programs pin today's refusals; localized failure turns several of them
into checked dynamic slots in the default mode. Under static-only every
snapshot stays byte-identical, which is the stage's proof that the switch
restores the refusals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage L1 of the localized-failure design.
A local, loop variable or module global whose declared TypeScript type has
no static lowering used to reject its declaration statement, and every
later use of the binding failed with it. Where that failure is about to
enter the build (the statement's poison in a TypeScript file, or the
module global's registration), the failing type now joins the lowerer's
degraded types and the unit lowers again: the type maps to the checked
dynamic wherever its strict mapping fails, so the slot holds values by
reference (typed capsules, live records and arrays, function thunks) and
typed reads out of it are checked. Each such slot is an SC6104
performance warning, listed under coverage's statement counts and in the
JSON envelope; coverage --fail-on=dynamic-slots fails on them, and
--tier=static-only keeps today's refusals.
A program that compiles today reaches neither commit point, so nothing
degrades and its output is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage L0 of the localized-failure design, report-only: no lowering changes.
A type with no static lowering poisons the slot that holds it, and the
poison rejects the declaration around it (a class, a signature, a
declaration statement); each later use then fails on its own line. This
records, on the committed diagnostics, where each type failure sits, whether
a checked dynamic slot would keep the program's meaning, and which rejected
declarations each failing statement used. coverage --explain-dynamic turns
that into the would-be dynamic slot list with the cascade each slot clears,
and the counts of errors that would disappear through stages L2 and L4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A union with a checked-dynamic arm (`unknown`, fetch's AbortSignal)
maps to the checked-dynamic value when every other arm is one the
checked-dynamic tree holds faithfully. Function arms were excluded, so
`Progress | AbortSignal | undefined` was refused (SC2009).
The tree boxes a function whose parameters check in and whose result
converts out by reference (canBoxFuncIntoDyn), and `typeof x ===
"function"` narrows it back out to the typed function. Such arms now
join the collapse. The corpus program checks calls, identity (`===`
against the original), and the other arm's members against Node.
pi's SessionManager.listAll takes `onProgressOrSignal?: SessionListProgress
| AbortSignal`. The refusal rejected the whole SessionManager class.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libunicode composes an LV syllable with U+11A7 as if it were a trailing
consonant (U+11A8..U+11C2), which drops it: "가ᆧ".normalize()
gave "가" where Node gives "가ᆧ". U+11A7 is a class-0 vowel
that composes with nothing, so for the composing forms the code points
between occurrences of it normalize on their own and it is kept as it
is. The guard sits in front of the vendored library (scr_unicode_normalize),
for String.prototype.normalize and the NFC step of IDNA mapping.
8009-normalize-hangul-jungseong-o-yae prints the code points of all four
forms for U+11A7 after LV and LVT syllables, conjoining jamo, other
starters and combining marks.
The range analysis caps a counter that steps by 1 at 2^53, where
`j + 1 === j`, so a loop such as `for (let j = 2 ** 53 - 2;
j < 2 ** 53 + 10; j++)` has a range of three values at its condition and
was taken for a loop of three runs. It never ends, and without its poll
worker.terminate() could not stop it. A loop's runs now count as bounded
only when a step from every value in its counter's range gives a
different value.
A number-key Map or Set read whose key has an integer view
(sc_map_entry_f64_int) fell back to the same hashed probe as any double
key, which tests for NaN and -0 before hashing. The fallback is now
sc_map_entry_f64_hashed_int, which takes the integer and converts it to
the key word directly: an integer is never NaN, and zero converts to +0.
Both hashed probes test the absent-key filter only before scanning a map
that keeps no bucket table. The filter keeps two of its 64 bits per key,
so it rejects few absent keys once a map has a bucket table, and the
probe's first bucket read answers those.
The map layout and the runtime lookup are unchanged. Corpus 8331 covers
integers the compiler tracks as integers on hashed maps and absent keys on
small bucket tables, and matches Node's output.
A promise converted into a union's promise arm is a new promise, so two
optional slots set to one promise each hold their own conversion, and a
value narrowed out of such a slot may be one too. Union equality compared
those arm payloads by pointer and answered false where Node answers true.
Any comparison between a union with a promise arm and a promise or another
such union now compares both sides as untyped values, where a converted
promise answers with its origin's identity.
JavaScript sources hold node:util's MIMEType and MIMEParams as identity
tokens, so a read such as MIMEType.prototype or MIMEType.name answered
undefined where Node returns the class's member. Those reads now report
SC2020, as the TypeScript spelling does at compile time; in JavaScript
the refusal throws when the read runs.
The node24-mime-errors fixture reads MIMEType.prototype to take the
getters' descriptors, so its expected refusal is now SC2020.
In a worker program, a loop inside another loop of the same function
needs less than its own budget, because the enclosing loop polls at
least once every 64 of its iterations:
- A loop whose counters' proven ranges bound its runs, and those of the
loops in its body, to 64 x 64 emits no poll. This covers loops with
constant bounds, such as mandelbrot's lane and iteration loops. The
bound uses the counted and two-counter loop ranges, or the range of a
counter that the update alone steps by a constant where the condition
reads it. Loops whose counted form needs a run-time guard don't count.
- An innermost counted loop is strip-mined like a counted loop over
typed arrays, whatever its body. A run of at most 64 iterations never
polls, and the budget's countdown leaves the body. A run of at most
16384 iterations is one block and polls once as it leaves. A longer
run polls between blocks of 64, but not after its last block.
Between two polls there are then at most 64 iterations of the enclosing
loop, and in each of them at most 16384 unpolled iterations of each
strip-mined inner loop and 64 x 64 runs of each bounded nest. A loop
that no other loop of its function encloses polls as before.
New corpus program: native-worker-terminate-bounded-loops.ts.
The paired nest reads its bounds masked to [0, 2^25 - 1], which turns a
-0 start into +0, and a term can tell the two apart (1 / i on the first
row). The bounds test now requires 1 / b > 0, which -0 fails, so such a
nest runs as written. Corpus 8355 adds row and column starts of -0 with
terms that see the sign.
- The project configuration records the local files a type patch names,
with whether each existed. A configuration read again from an unchanged
package.json is reused only while every answer still holds, so creating
or deleting a local declaration file between builds in one process
validates again.
- A types patch supplies the declaration TypeScript probes for a .jsx
JavaScript entry (x.d.ts for x.jsx), as it does for .js, .mjs and .cjs.
- Remove the two tests/fixtures/effect4/node_modules links the previous
commit added; that directory is ignored and pnpm creates them.
A paired row-sum nest now also takes two columns per step: each row adds
the first column's term and then the second's, and an odd column count
runs its last column on its own, so every sum keeps its order of
additions. The nest runs paired when all four bounds are integers in
[0, 2^25 - 1]; it reads them masked to that range, which proves both
counters' ranges, so its counted loops need no run-time guard or second
copy of the body.
Corpus 8355 covers the column counts and bounds, and corpus 8356 covers
worker loops over shared views that run past a view's end or write
overlapping views of one buffer.
A scriptc.typePatches entry can now replace a declaration file of the
package (or of its @types twin, written @types/<name>/<path>) with a
local .d.ts ("files"), or give the package a declaration entry ("types").
A supplied entry answers the package's types file and the declarations
TypeScript probes beside its JavaScript entries, and exists for the
program host, the resolver and npm-static's untyped-package test, so a
package without declarations becomes a typed package. Declaration patches
still apply on top. A files target the installed copy does not ship is
SC8031.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records inherit toString, toLocaleString, valueOf, hasOwnProperty,
isPrototypeOf and propertyIsEnumerable. Calls of these methods by name
run an own callable entry when the record's type allows one and the
record holds it, and otherwise the built-in with the record as receiver;
toLocaleString, valueOf and isPrototypeOf on records were refused, and
toString ignored an own entry. Keyed reads of records of functions answer
the built-in as a native function value when the key misses: bound to the
record when called at once, with an undefined `this` otherwise. The
backend fuses the read with its fallback, so hits cost nothing extra.
Records of other value types keep reading undefined for these names; the
limitations page says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a fresh key and IV each run, Node's OpenSSL now and then accepts
the padding of a wrong-passphrase decryption and rejects the decrypted
structure instead: ERR_OSSL_UNSUPPORTED rather than ERR_OSSL_BAD_DECRYPT,
in about 1 run in 100. The program now prints both as one decryption
error.