A lone array callback in a local class's method could read a binding the
method reaches through its class's captures. Those bindings are shared by
every method of the class, so another method can assign one while the
loop runs, but the copied loop read the value once when the call started.
stableTarget now counts a method's class captures as inherited, so such
a callback keeps its helper and reads the current value on every call.
forkTargetPaths asked the checker for the symbol of every identifier
callee in every implementation file, on each root-discovery pass and twice
more in preflight, to find calls of child_process.fork. A call is a fork
only through a binding whose import or alias chain names the module in a
module specifier, or through a '#' specifier a package.json "imports"
field maps.
namesChildProcess reads every program file once per program, outside
Node's type declarations and scriptc's own declaration files, which
declare the module and alias it only as node:child_process. Implementation
files are read from their syntax trees, which discovery fetches anyway;
other files as the host serves them to the server (the new
Ts7Program.getSourceText), without fetching their syntax trees. When no
file holds "child_process" in its text, an escape or line continuation
that spells it, or an "imports" entry that names it, forkTargetPaths
answers no targets without querying the checker.
The discovered fork targets are unchanged.
In a speculative integer loop, `v - Math.floor(v / 2^n) * 2^n` computed
the floor division as an arithmetic shift, multiplied it back by 2^n with
overflow and -0 checks, and subtracted with range checks. For a safe
integer v and n in [0, 52] the result is the floor-mod, which is v's low
n bits: the fast copy now computes it as `v & (2^n - 1)`, which lies in
[0, 2^n) and needs no check. A product with a power of two (a literal or
`Math.pow(2, n)`) becomes a shift, with one test that the other factor's
magnitude is at most (2^53 - 1) >> n; its zero result is +0.
A getter or proxy trap that runs during a trusted build outside any
visit session now runs with the build's state set aside, as inside a
session. A conversion the getter starts, such as a recursive union
that opens and ends its own session, no longer clears the mark that
keeps the rest of the outer build from trusting its earlier match.
The receiver of a borrowed Map.get is read before its key. When the key's
evaluation replaces the field or local that holds the map
(`this.m.get(this.key())` where key() assigns this.m), the read runs on
the old map, which only the statement still holds, and the borrowed
value outlives it. A borrow now also requires the read itself, receiver
and key included, to preserve reference edges; otherwise the read keeps
its own reference.
Corpus 8181 and the emission test cover a key method that replaces the
field.
A lone array callback whose body has an early return is copied into an
exit block, and its statements are renamed by a separate path from the
single-return case. The new test covers labeled switches, a nested
labeled switch and breaks to both labels in map and some callbacks that
return early. Both calls run as loops, and the IR validator accepts
every renamed break.
Buffer.from(text, "base64" | "base64url") decoded each four-character
group with four lookups and a sign test, then stored its three bytes one
at a time. The decoder now first takes complete groups two at a time:
each character's value comes from a table that holds it already shifted
into place within its group's 24 bits, a single bit test covers all
eight characters, and each group's three bytes go out in one four-byte
store whose last byte the next store overwrites. A group with any other
character (padding, whitespace, an invalid character) takes the
previous path, and the two-group step resumes after it, so line-wrapped
text keeps most of the gain. The destination already holds two spare
bytes, which cover the extra store byte.
selectInt32Arrays proposed int32 array candidates, analyzed the marked
module, and when a candidate dropped, marked the module again without it and
analyzed the result from scratch, until a round kept every candidate it
marked. On tsc-ts the first analysis drops 6 of 8 candidates, and a second
full analysis confirms the other 2.
The analysis now revises the module as candidates drop: a candidate whose
element stores fail (and that keeps no checked stores) has its functions
replaced by the module's functions marked without it, which call the element
readers again, and the fixpoint goes on over the revised functions. The
selection takes the module of the functions the analysis ended with when
marking the module for the candidates it kept proposes exactly those
candidates, in those functions, and the emitter takes over the analysis with
that module's node index, initializer bindings and dynamic stores. A revision
that changes anything else in a function, such as a call of a function no
other call reaches, ends the analysis, and selection falls back to rounds.
SCRIPTC_INT32_VERIFY=1 also runs the rounds and throws unless the selected
module and every fact of its analysis are the same.
The workload reads its file through readline over fs.createReadStream.
The stream and readline runtime units define dynamic-tier symbols on
their own: a program that only reads a file with createReadStream links
the same 32, as log-summary and inventory-report do.
`a?.[i]` on a binding whose type admits undefined, such as a
`string[] | undefined` parameter, threw "undefined is not representable
in the target union" when the binding held undefined. Element access
replaced an identifier receiver whose storage has an undefined arm with
a checked narrow to its present arm before the optional chain ran, so
the chain saw a receiver that is never nullish and lowered `?.[` as a
plain element read.
Element access now leaves the receiver to the optional chain when the
access carries `?.`; the chain tests the undefined arm and lowers the
access again inside its guard. This applies to arrays, tuples and
record keys. Property access (`a?.length`, `a?.at(0)`) already lowered
through the chain.
Corpus 8330 matches Node's output.
readline split redirected input byte by byte, twice per chunk (once to
find whether the chunk ends a line, once to split it). Before splitting
it copied each chunk out of its Buffer and into a scratch buffer, decoded
it into a new string, and copied the unfinished line and the whole chunk
into a joined buffer. Now:
- one scan per chunk: memchr for a CR once, then LF to LF with memchr
when the chunk has none, and the LF, CR and CRLF split otherwise;
- the unfinished line joins only the first line of the chunk;
- an ASCII chunk with no partial UTF-8 sequence held from the previous
one is split where it is, and a chunk that is not shared memory is
read from its own storage. Lines are still copied into their strings
before the first is delivered.
Buffer.from(string, 'latin1' | 'ascii') encoded character by character;
an ASCII prefix, all of an ASCII string, is now copied with memcpy.
Line events, their order and the CRLF delay are unchanged. Corpus 8344
covers lines joined across chunks, a line longer than a chunk, empty
lines, mixed line endings and UTF-8 split across chunks. The new
line-reader workload reads a 67 MB log through readline over a file
stream.
- A guarded declaration of a name the CommonJS wrapper defines (`var
exports = exports || {}`, and `module`, `require`, `__filename`,
`__dirname`, `arguments`) reads the wrapper's value, not undefined, so
it keeps its text instead of becoming a fresh object.
- The write scan counts destructuring assignments, and destructuring
for-in/for-of targets, as writes, so a hoisted function that sets the
namespace before its guarded declaration keeps the declaration as it
is.
- An exported `var` that a later declaration of the same name sets
again is not settled at the export: importers would read the
redeclared value, where Node copies the first one into
module.exports. It stays refused (SC1090), as before. The npm case
var-export-redeclared pins it.
`JSON.parse(text) as T` for a discriminated union of records took the
checked-dynamic route: it parsed the text into a dynamic tree, matched
and copied that tree into records, then freed it. The one-pass schema
parser now builds these unions directly, as record members, array
elements and whole documents. For an object, a shallow scan of its
members finds the discriminant's value, which selects the one record arm
that holds that literal, and the object is parsed with that arm's
schema. An object with a missing, non-string or unknown discriminant, or
any member the arm rejects, declines, and the checked route then gives
the result or the error as before.
The compiler admits unions whose record arms each name their string
literals, every literal naming one arm, plus null and undefined arms.
Since 76735a8d9 (#1975), a `super.m()` call in a JavaScript class method
whose `this` is the call-site receiver reflected the base class's whole
prototype and read `m` from it. An instance's member lookup first tests
its property bag, which inherits from its class's prototype data, so once
the base's prototype was reflected, `child[k]` for a method the subclass
overrides found the base's method there: `value[name](arg)` on a subclass
instance called the base method.
While no code reads a prototype method by name, `super.m()` now calls the
declaring class's receiver-independent method value with the receiver, as
the method's [[HomeObject]] lookup answers, without reflecting the base
prototype. Programs that read prototype methods by name keep the
reflected lookup.
An unchanged rebuild that kept its output still selected the runtime pack's
artifacts first, reading the stored selection, and computed the identity of
every selected artifact to open the executable cache.
- The executable cache key names the pack by its package.json text, the
manifest's identity (its text on darwin), and the link features and
flavor. Together they fix which artifacts the selection names.
- The cache selects the artifacts only when it needs their identities, in
restore() and trace(). keep() checks the recorded entry, which lists them.
- A build that does not keep or restore its executable selects the pack
before code generation, as before.
A read site's cache only remembered properties it found on the receiver
or its first prototype. Reads of properties an object lacks, which
validation libraries make constantly (zod reads ctx.skipChecks,
payload.memo, x.aborted and _zod.optin on every check), walked the whole
shape chain and then repeated the lookup on the generic path every time:
7.9 million such reads in one zod benchmark run.
- Read sites call a new probe, scr_js_get_probe, that answers undefined
for a key the receiver and its chain of plain shaped objects (at most
one prototype, ending in the default prototype) lack, and caches that
answer. The entry holds while the receiver's shape, its prototype link,
and that prototype's shape are unchanged. Its tag and placeholder
holder make the inline entry-0 test in compiled code miss, so the
answer always comes from the probe. Method reads keep
scr_js_load_probe, so a missing method still reaches the slow path and
its "is not a function" error. Names of Object.prototype members are
never cached as absent.
- Receivers of one shape with different prototypes (zod's internals
objects) each keep an entry instead of replacing one another.
- A site whose four-way record is full consults a per-thread table keyed
by shape and key before walking shapes, and fills it.