A generic binding inside a function that a nested function read before
the declaration lowered stores its instance into a TDZ box when the
declaration runs. That value was the instance's closure, which the
backend interns for module functions, so every evaluation of the
declaration stored the same function object. JavaScript makes a new one
each time. The stored value is now a closure over a lifted forwarder that
calls the instance, so each evaluation allocates a new closure; the
forwarder is created once per instance. Async and rest-parameter
bindings read this way report SC1090.
Corpus 7180 checks the identities.
When an event-loop callback calls an async function or steps a generator
that enters a store before its first await, the store becomes the
callback's at the end of the prefix. On the main slot it now ends with
that callback, as an enterWith there does: the copy schedules the tick
that clears the main slot, so the store does not reach later callbacks.
new tls.TLSSocket(socket, { isServer: true, secureContext, requestCert:
true }) skipped the options walk that refuses requestCert, so the server
asked for no client certificate and accepted every client, even with
rejectUnauthorized: true. With a secureContext, Node still reads the
socket's own options, so a truthy requestCert, SNICallback and
ALPNCallback now throw the fence there too.
A TLS socket's transport kept its 'secure' and 'session' listeners until
the socket was freed. A handshake that fails never fires 'secure', so a
listener that captures the socket kept the socket and its TLS state alive
after it closed. The transport now drops those listeners when the socket
settles, and ignores listeners added after that.
The key and signature errors formatted into fixed stack buffers and
passed snprintf's return value on as the message length. A message
longer than the buffer, such as "Invalid digest: " with a long algorithm
name from crypto.sign or crypto.verify, then copied bytes past the end of
the buffer into the error. The messages are now sized to their whole
text, so a long name appears in full, as in Node.
The received values in these messages also follow Node: strings as
util.inspect quotes them, cut at 128 characters, and large integers with
ERR_OUT_OF_RANGE's separators. A fractional, NaN or infinite
modulusLength reports "It must be an integer", as validateUint32 does.
The private copy of the LLVM text had no extension. Builds that compile it
with the C driver (sanitize builds, targets without the LLVM helper) passed
it to clang directly when the native cache was off, and clang read it as a
linker input, so the build failed. The copy is now named with `.ll`.
Those builds also record the output directory in their native cache proof.
Renaming the private copies to the shared names after the native build
changed that directory, so every unchanged repeat missed the proof and
relinked the executable. The shared names are now installed before the
native build, which compiles and caches copies in a private directory
outside the output directory.
Checker replies name every type, symbol and signature they reach in
full, and most of them are objects the client already holds: building
tsc-ts, 96% of the types, 79% of the symbols and 80% of the signatures
in replies are cached by id. Object replies are now read as bytes, each
element's extent is found by a scan that skips strings, and an element
whose leading "id" is cached resolves to the cached object without its
text being decoded. The other elements are parsed as before.
Narrowing stability looked for writes to a binding by walking the whole
file that declares it. For globals such as `console` and `JSON` that file
is a declaration file (scriptc-node-fallback.d.ts, lib.es5.d.ts), so every
build that read one of them materialized about 46,000 AST nodes and kept
them until the end of the build. A declaration file holds no code that
runs, so none of its writes happen: the walk now returns no writes for it.
The dense path of an element read that may be undefined (the
`%arr.idxOr` reads behind `a[i]` as `T | undefined`, the store
`a[x] = a[y]!`, and the element reads of the lowered array methods)
tested the index against the capacity, then against the hole-free prefix
(`filled`), and only above the prefix against the length and the slot's
state. `filled` is at most the length and the capacity (scr_array.h), so
an index below it needs no other test: the read now tests `filled` first
and takes the value path on that test alone. Above the prefix it tests
the capacity, the length and the state as before.
A double index first branches to the runtime lookup when it is not an
exact integer, then takes the same tests on its i64 value, which a 32-bit
target compares before truncating it.
`for (let lo = a, hi = b; lo < hi; lo++, hi--)` kept both counters as
doubles: the counted-loop match takes a single counter, so every
`a[lo]`/`a[hi]` converted and tested its index. A loop whose two counters
close in on each other by positive literal steps, are not written by the
body and index something now stores both as i64: on every body entry both
lie between the first start and the second. A start without a proven
integer range is tested at run time on a versioned loop, as a counted
loop's is.
The helpers of the typed-array callback methods (forEach, map, filter,
some, every, find, findIndex, findLast, findLastIndex, reduce,
reduceRight) called their callback through the closure's code pointer on
every element, and one helper served every call with the same signature,
so the callback was never inlined. A literal callback now gets a helper
of its own, as for the array methods, and the constant-callback census
accepts the typed-array helpers, so the helper calls that function
directly.
The visit is now a loop on its own counter, `for (let i = 0; i < a.length;
i++)` or down from the last index, instead of a loop on a counter
declared before it, so the counter is an integer and element reads need no
index test. reduce and reduceRight without an initial value read their
seed at the first visited index and start one past it.
Adds reduce, some and map callbacks to the typed-array-kernels workload.
Readable, Writable, Duplex, Transform and PassThrough take the
objectMode option, and a Duplex, Transform or PassThrough the
readableObjectMode and writableObjectMode options, as true or false
literals in an options object literal or in an options value. An
object-mode side holds any value that converts to a checked-dynamic
value: push, unshift, write, end, pipe, pipeline, for await, 'data'
listeners, Readable.from and the transform callback carry values in
TypeScript and JavaScript, and constructor-function streams and options
held in variables do the same. The object-mode high-water mark counts
values and defaults to 16. A readable buffer that mixes strings, Buffers
and other values holds them all, and push(undefined) and other values on
a byte-mode stream report ERR_INVALID_ARG_TYPE through 'error', as in
Node.
In typed code a write or transform callback, or a _write or _transform
method, may declare its chunk with any such type, which converts when
the callback runs. Writes pass the encoding argument to _write and
_transform as Node does: undefined when absent, null when a callback
took its place, otherwise the value unchanged. A parameter declared
string | null | undefined receives that value and one declared string
receives "" for undefined and null. write(chunk, encoding) on a
byte-mode stream encodes a string chunk with any known encoding and
throws ERR_UNKNOWN_ENCODING for an unknown one. Readable.from takes
typed Maps and Sets of any such value, and arrays that mix strings and
Buffers.
for await over a readable-only stream that will emit 'close' now ends
after 'close', as Node's eos does. Buffered writes that complete inside
clearBuffer run their callbacks in order, and 'finish' waits for a
deferred afterWrite that carries a callback, as Node's pendingcb does,
unless a _final callback schedules it.
The 'data' listener adapters take three slots and write and transform
callbacks read their chunk and encoding through runtime accessors, so
RUNTIME_ABI_VERSION is now 26.
Lowering named helpers by the size of their registry at first use
(%union.retag.N, %arr.map.N, ...), lifted closures and optional-chain
locals by compiler-wide counters (%fnN, chain.N), generic instances by
their instance count, and records and unions by creation order (rN, uN).
Class expressions, mixin instances, function-local generics, non-exported
namespace segments and some module globals carried the byte offset of their
declaration. A name therefore depended on which unit lowered first and on
the positions of unrelated code.
Helpers, instances, records and unions are now named by a hash of the key
they are interned by (keyedHelperName). Closures and optional-chain ids are
named by their lowering unit, their structural position and a count per
key within the unit. Offset-based names use the structural position: the
chain of enclosing named declarations (each with its kind, name and
ordinal) and the node's ordinal among same-kind nodes inside the nearest
one, so an edit outside that declaration leaves the name unchanged.
Interfaces and type aliases anchor the positions inside them, and default
library files are named by their base name, so tsgo and tsc-ts, which
install them in different directories, give the same positions.
Recursive record and union placeholders are named by the printed type, the
structural position of the declaration and the same for up to three levels
of type arguments; a description that two types still share gets a
numbered suffix for the later one. A generic class instance keyed by a
literal type argument is named by the literal's text or the argument's
description instead of its checker type id. Two different keys with the
same name throw an InternalCompilerError.
The SC6101 performance warning recognizes the new closure names
(fn.<hash>, with _<name> for a named function expression) and lists the
functions that rank alike in source order. The module lists its records
and unions by id. The generated program is unchanged apart from these
names and that order, and test:lowering-order now fails on any name
difference.
A module `let` declared without an initializer whose type uses its null
pointer as the TDZ sentinel (a function, for example) is initialized when
its declaration runs, while its slot is still empty. markUninitializedWrites
marked the assignment statements that run after the declaration as
initializing, but not assignments in expression position. Those ran the
pointer check and threw "ReferenceError: Cannot access 'g' before
initialization" where Node stores the value, so the deferred-resolver
pattern `let resolve!: ...; new Promise((r) => (resolve = r))` failed, as
did `(h) => (g = h)` and `const r = (g = h)`.
An assignment expression to such a binding now becomes an initializing
`assign` whose value a temp holds and yields. Writes that can run before
the declaration keep the flag check, so they still throw Node's
ReferenceError.
A class defined inside a function whose base resolves to a known class
lowered to its class value alone, so the `extends` expression never ran.
Node evaluates it each time the definition runs, so its calls and other
effects were missing (`class Local extends select(2) {}` never called
select). The class value now follows the heritage expression's
evaluation when that expression can have effects: a call, construction,
assignment, update, delete, await or yield outside nested functions.
Module-scope classes already evaluated it.
The pretty-printed IR of the self-hosted LLVM emitter is close to the
JavaScript string length limit on CI checkouts, so any growth of the
emitter fails the test with "Invalid string length".