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.
Compiler sources keep caches on their owners, not in weak collections
(compiler-weak-collections.test.ts).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A generic body reading a member typed by a distributive conditional over
its type parameter (`opts?: T extends "alpha" ? AlphaOpts : T extends
"beta" ? BetaOpts : never`) refused it with SC2009 when the instance bound
T to a union of literals (`"alpha" | "gamma"`, `"a" | (string & {})`), and
mapped it as `number | undefined` (never's placeholder) when the binding
selected only never, while the concrete instantiation maps that member as
`null | undefined`; values crossing between the two failed with SC2003.
The conditional now distributes over the binding as the checker does: each
member evaluates, the non-never results form the union, an open `string &
{}` member checks as `string`, and an all-never result leaves an optional
member that holds only undefined.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the key is a parameter of a function that never escapes, every call
passes a string literal key of the table, and the body neither assigns the
parameter nor uses arguments, the dispatch needs no arm for other keys. In
nsbench the fallback boxed the Vec probe into a dynamic call, so every Vec
carried the property bag and lived on the heap; without it the workload
inlines into one loop. SCRIPTC_SHAPE_EXHAUSTIVE_TABLES=0 opts out.
nsbench (n=20000, overlay on): 36 -> 8.2 ms (Node about 21 ms).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- static-objects: a primitive member stored once by an unconditional
top-level statement of the object's own file (app.math.EPSILON = 1e-9)
reads as its literal where the read provably runs after the store: inside
functions when the statements before the store run no code, and in later
top-level statements of that file. SCRIPTC_SHAPE_FOLD_STORES=0 opts out.
- JavaScript functions read the ambient receiver at entry only when this
occurs in their parameters or body outside nested non-arrow functions.
- npm case static-stores pins both, with the overlay on and off.
nsbench (n=20000, overlay on): 64.4 -> 36 ms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- analyze.ts keeps argsFrom in a Map the analyzer owns (compiler sources keep
caches on their owners, not in weak collections).
- constant-graphs.test.ts: lib.math is stored after lib was created, so the
report shows it as undefined|{...} with a graph of its own.
- maybe-promise-fences.ts: `record instanceof Promise` lowers to false since
the base's neverPromise lowering; the snapshot no longer lists the refusal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A computed read through a snapshot of the global object taken in a
function (`const g = globalThis; return g[name];`) threw SC2020 for
builtin globals: the scan that decides whether a module installs the
builtin-global lookup runs before function bodies are lowered, when the
snapshot's alias is not registered yet. The scan now also recognizes a
variable whose only declaration initializes it with the global object.
navigator.language and navigator.languages cache their answers, but
only taking the navigator object registered their release at exit, so a
program that read just those members left them live and a sanitized
build exited with status 99. The first cached value now registers the
release.
Corpus 8161 reads through a function-local snapshot, and 8168 reads
navigator.language, navigator.languages and navigator.platform without
the navigator object.
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.
An initializer-less JavaScript `let` whose writes all agree gets a scalar
slot when every read already has that flow type (inferredEvolvingLetType).
The read scan resolved each identifier with getSymbolAtLocation, which
answers the property symbol for a shorthand property name, so the
`width` in `return { width }` was not seen as a read. A function that
assigns the let on one path only (`let width; if (tty) width = 80;
return { width };`) then stored it in a number slot: the path that never
assigns returned 80 or 0 instead of undefined. The capture scan missed a
closure that reads the let through a shorthand the same way.
Both scans now resolve a shorthand name to the binding it reads, so such
a let keeps its checked-dynamic slot and reads undefined until assigned,
as in Node. TypeScript lets are unaffected (the inference applies to
JavaScript only).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An opted-in package's JavaScript types its own values. The lowering types
a consumer's view of such a value from that JavaScript, and the
declaration boundary (decl-projection.ts) casts to the declared type only
where the JavaScript world answers `any`. A function that returns an
object literal has a precise JavaScript type, so it never crossed, even
where that literal type lacks members the declarations promise:
- `{ [key: string]: string }` over `{ size: "12px" }`: the consumer read
`settings["font"] ?? "x"` through the literal's fixed key set. The
checker types the miss `any` (a JavaScript literal), but the keyed read
took the declared fields' common type, so the miss trapped with
"record has no key" and aborted the process.
- an expando `out.max = ...` on a non-empty literal, which the JavaScript
checker leaves out of the literal's type: reading `stats(xs).max` was
refused with SC1090.
A package value whose JavaScript type is an object literal that lacks a
declared property or string index signature now crosses at its declared
type, so missing keys read undefined through the index signature and
expando members are read from the object. An index signature whose values
do not project (a package interface) keeps the value untyped (`as any`),
so its reads are the JavaScript reads. Only programs compiled with
--npm-static whose consumers read such values are rewritten.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 8211 program resolved a package written to a fresh directory under
os.tmpdir() and printed the result relative to that directory. On macOS
the temporary directory is a symbolic link (/var to /private/var), so the
resolved real path printed relative to the linked name included the
random directory suffix, which differs between the Node run and the
compiled run. The fixture now names the directory by its real path, and
the result is lib/entry.js on every host.
A global replace or replaceAll of a regex literal called the compiled
matcher once per match: each call set up its frame and seek again, and the
runtime then copied the gap and the replacement. A substitution of a
literal inside the compiled subset with an ASCII replacement without $
patterns now gets a replacer built for that pattern and replacement. It
appends each gap and the replacement to the output and continues the
search after the match, as the runtime's loop does (one past an empty
match), without returning. Gaps of up to 16 bytes are one 16-byte copy,
and the search after a match is a 16-byte scan instead of a memchr call.
scr_regex_replace_with runs the replacer on an ASCII subject; a subject
outside ASCII keeps the runtime's loop. Plain-text patterns, which the
runtime searches as strings, get no replacer.
New corpus program: 8351-regex-literal-replacers.ts.