- The inspector throws the 'not supported yet' Error for checked-dynamic
values it does not render (a promise, a URL, a RegExp reached through
`unknown`). Its calls were not marked as throwing, so
`console.log("x", p)` printed "x " and the program went on; records and
arrays with such a field printed `{ a: }` and `[ ]`. insp.dyn and
insp.dynS are now may-throw library calls, so the Error propagates from
the call that inspects the value, before anything is written.
- A class reached through `unknown` prints `[class C]`,
`[class C extends B]` or `[class (anonymous)]` instead of
`[Function: C]`. Functions and classes reached as dynamic values print
their enumerable own properties (`[Function: f] { x: 1 }`,
`[class S extends A] { count: 3 }`), cycles through them as
`[Circular *N]`, and `[Function]` past the depth limit. Class objects
carry their base class's name. A compiled class's static fields are
module globals that the class object does not carry, so inspecting a
class that declares static fields through `unknown` throws the
'not supported yet' Error.
- util.format's %o renders a checked-dynamic argument with showHidden: an
array's `[length]` entry and an object's non-enumerable own properties,
string- and symbol-keyed, in brackets. An array with more than 100
output entries keeps the last one out of the grid, as Node's
groupArrayElements does. %o of a function, an Error, a typed array or a
native handle other than Map, Set and Date throws the
'not supported yet' Error.
ScrClassObj has two new fields, so RUNTIME_ABI_VERSION is now 27.
tsgo has no getContextualTypes or getResolvedSignatures. Each prefetch
asked for them anyway, and a failed chunk was split and retried down to
single nodes, so a tsgo build paid about two failed requests per
prefetched node. The checker facade now remembers a batch method the
server answers with "unknown API method" and asks those queries node by
node from then on.
The runtime fired every socket, server, request and response event from
the event loop with no store. Node runs them in the context of the
resource or request that produced them, so getStore() answered
undefined in listeners where Node answers the caller's store.
Handles now keep the context Node uses and the runtime enters it around
each dispatch:
- a socket the context of its creation (net.connect, tls.connect, an
http request's socket), for 'connect', 'data', 'end', 'close',
'error' and 'timeout';
- a server the contexts of its listen() call ('listening', 'connection',
listen errors) and its close() call ('close' and close callbacks when
no connection is open; a close that waits for the last connection
settles in that connection's dispatch, as in Node);
- socket write() callbacks and end() callbacks with 'finish' the
contexts of their calls;
- http server requests and responses, which are created in their
connection's dispatch, the server's listen() context, and client
requests and their responses the http.request() context, including a
keep-alive socket an agent hands to a later request.
An accepted net socket runs its own events with no store, as in Node;
the sockets of an HTTP or TLS server run in the server's listen()
context. Events and callbacks of other streams are unchanged.
The compiler is unchanged. Programs without net or http link the same
runtime code; net and http programs gain the capture and dispatch
helpers (about 1.7 KB of text in a net program, 2.2 KB in an http
server), and bench:runtime http-api is neutral.
Corpus 7119 and 7380.
The merge helper for a fixed-shape spread into an index-signature record
wrote the source's keys in the shape's declared order when it was
lowered. Key-order witnesses can replace that order afterwards with the
order the program builds the source's values in, so a source declared
`{ start, end }` but built as `{ end, start }` merged as `start,end`
where Node gives `end,start`. The source's writes now go into one block
that a shape-order finalizer fills in the settled order, as the other
order-snapshotting helpers do. Corpus program 6916 covers sources built
in another order than declared, with an absent optional field and with
two spreads.
`for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) === 32) n++`
tested the string's ASCII bit and the index against the length on every
character, and its fallback is an opaque runtime call, so LLVM could not
hoist either test or vectorize the loop.
A typed-array loop version now also covers `s.charCodeAt(index)` of a
string binding the loop does not write, at a counter index plus a
constant or another index the version proves. A string is immutable, so
its bytes, its length and its ASCII bit are fixed when it is built. The
guard requires the ASCII bit, so each UTF-16 unit is one byte and the
length is the byte length, and the fast copy reads each unit with one
byte load. Other strings and indices run the checked loop.
A typed record encoded from its field hooks queued a plain-object field on
the visit stack and went on to the next field, so getters on later fields
ran before the properties of earlier ones were read. Node's serializer
finishes each field, nested properties included, before the next.
The record path now drains the visit stack down to where it started after
each field, so properties are read in Node's order. Corpus 7139 posts a
record whose fields hold getter-bearing objects and prints the read order.
A record conversion behind a written type predicate built the copy in the
target shape's field order and wrote every optional field, so a source
object without an optional key came out with that key present and holding
undefined, and Object.keys listed the target's order. The copy now keeps
the source's key order, as a width copy does, and an optional field the
source lacks stays absent.
spawn and execFileSync with an `env` option searched the parent's PATH
for a command without a '/'. They now resolve it the way spawnSync does,
as Node (libuv) does: on the replacement environment's PATH, or the
default path when it has none, with relative entries read from the cwd
option's directory.
An element read that may be undefined, lowered inline from `%arr.idxOr`
(`a[i]` as `T | undefined`, and the read in `a[x] = a[y]!`), took an
integer index only from a loop's integer slot. Any other index, such as a
double counter with a proven integer range or `k | 0`, was converted and
tested for an exact integer on every read. The read now takes the same
exact integer as a borrowed read does (`exactInteger`), on 64-bit targets.
The loop-induction treatment above the hole-free prefix still applies only
to a loop's integer slot.
The evolving-array check made the binding checked-dynamic for any spread
write, so `xs.push(...values)` of an array whose elements match the
binding's agreed element type turned a typed `number[]` into a
checked-dynamic array, and the spread call through it was refused with
SC1090 "spread arguments in calls through 'unknown' values". A spread now
stores the source array's element type, or each of a tuple's element
types, and the binding stays typed when they match. A spread of another
iterable still makes it checked-dynamic.
- An arrow function or method on the right of an untyped `instanceof`
has no prototype. Node throws "Function has non-object prototype
'undefined' in instanceof check" for any object, but the runtime threw
only for plain objects, arrays and functions, and answered false for
typed arrays, native handles, promises and typed references. It now
answers false only for primitives.
- Coverage lowers the body of a signature-blocked function for its
diagnostics. A declared `this` parameter now gives that body its
receiver, as lowerFunction does, so `this.label` reports nothing.
Before, each use reported SC1080.
A function expression stored in a function type with more parameters
folds `arguments.length` to that type's parameter count, the count every
call through the slot passes. A named expression that calls itself
through its own name passes only its declared parameters there, so no
constant answers both calls. Such a read now reports SC1090 with the
reason, and expressions that do not refer to their own name keep the
fold.
The witness skipped every libCall whose signature result is VOID. VOID is
also the signature of runtime calls that take their record type from the
use site and build the record at run time, such as net.serverAddress
(`server.address()`). A literal of the same shape then set the shape's key
order, and the runtime record enumerated in the literal's order instead of
Node's (`port, family, address` for `address, family, port`).
The witness now skips only the calls in ALWAYS_THROW_LIB_FNS, which never
produce a value. The set moves from validate.ts's result-type check to
builtin-effects.ts, and both use it.
new AsyncLocalStorage(options) read defaultValue and name as own data
properties, so inherited values and getters were ignored, and a name
that was not a string reached the compiled code unconverted. The options
are now read with a full property get (prototypes, getters, proxies,
the snapshot of a typed record or class instance) in Node's order:
defaultValue once, name to see whether it is defined and again for its
value. The name is kept as String(name); a Symbol throws Node's
TypeError, and an exception from a getter propagates. A class instance
whose class itself declares one of the two members, which a capsule does
not expose to the runtime, is refused with a "not supported yet" error.
A snapshot's runner called its callback with the runner's own receiver;
Node applies the callback to an undefined receiver.
test_dh.c includes scr_dh.c and still called sd_generate_safe_prime
without its new top_two argument, so dh.test.ts failed to compile. It
now passes false, as the DiffieHellman paths do, and a new case checks
generatePrime's search: the sizes and add forms with one possible
result, top-two-bit safe-prime starts, and checks above OpenSSL's round
count.
The trip count that bounds a follower's range was the driver's span over
its step, computed in doubles. A span above 2^53 - 1 can round down: from
-(2^53 - 1) to below 2^52 - 1 the span 3 * 2^52 - 3 becomes 3 * 2^52 - 4,
so a follower that reaches 2^53 got the range limit 2^53 - 1 and ran as an
unchecked integer. Such a span now bounds no trip count, and the follower
is tested at run time (or the loop is refused when every value is known).
Below it the quotient rounds to at least its floor, and the follower's
products and sums are exact within the safe range.
With an `env` option, spawnSync searched the parent's PATH for a command
without a '/'. Node (libuv) searches the PATH of the child's environment,
or the default path when that environment has none, so a command only on
the child's PATH ran as ENOENT and one only on the parent's PATH ran
where Node reports ENOENT. The lookup now follows libuv: each PATH entry
in order, an empty entry as the current directory, and EACCES when a
candidate exists but cannot run.
A callback loop version emits its loop twice. A loop inside either copy
could be versioned again, so a nest of loops that each call a callback
built just before them (`const g = adder(i)`) doubled at every level: a
three-deep nest was emitted seven times per function.
A loop inside a callback version, or the versioned loop itself emitted
again for each copy, is no longer versioned, so a loop nest is emitted at
most twice. The outer loop keeps its version; inner loops keep their
calls as they are.
The union-returning flatMap helper was interned by element and result
types alone, so two callbacks with one union result type, one with an
empty `never[]` arm and one with a populated array arm, shared the first
helper and the second dropped its elements. The key now includes the
per-arm plan. Array arms copy their elements as stored, a present
undefined included, so an array arm's element type must be the result's,
and a unit-typed result (`undefined`) appends itself.
tsc-ts 0.0.7 answers getContextualTypes and getResolvedSignatures, so
NODE_BATCH_QUERIES is on: lowering asks the checker for the contextual
types of identity-flow sites, array literals and conditionals, and the
resolved signatures of calls, in batches instead of node by node.
The release-age exclusions move to tsc-ts 0.0.7 and its platform
packages, and add @scriptc/threads 0.4.1, which tsc-ts 0.0.7 depends on.
`Promise.all([a, b])` whose entries have different types (the checker's
tuple overload) ran through an async helper that awaited the combinator
and then each fulfilled entry, so its awaits resumed three microtask
turns after Node's. The combinator now stores the entry promises, and an
adapter run inside its settlement reads them into the tuple, so the
result settles on the combinator's turn: awaiting it, storing it first,
chaining it with then and returning it from an async function all land
on Node's turns. Plain entries join the combinator through
Promise.resolve, as in Node, and unit-typed entries compile.
- Once a 'message' listener without a typed entry has decoded the
message, the listeners after it read that decoded value, so a typed
listener sees what an earlier untyped listener changed, as in Node.
- A union with a literal discriminator gets no typed message entry: its
checked conversion also tests the discriminator, which the message
readers do not.
Corpus 7361 checks a typed listener after an untyped one that changes
the message, and a listener on a discriminated union.
Typed-array loop versions applied only to for loops, so a scanner written
with while loops, such as `while (pos < end && bytes[pos] === 32) pos++`,
kept one bounds test per access. A while loop counter is now a number
local that the loop changes only by positive integer increments (`c = c +
k`, `c++`, `c = (c + k) | 0`) written as statements of its body, blocks,
branches and nested while loops. An access at `counter + d` after a
condition compared the counter below a loop-invariant bound, with no
increment since, is in bounds for the whole loop when the guard finds the
counter's entry value an exact integer of at least -d and the bound at
most the length minus the access's extent. A counter with a `| 0`
increment also needs its loops' bounds at most 2^31 minus its increments,
so it never wraps.
A fixed check (a statically ranged index) now needs an end below 2^31.
The counter of a loop to a number[]'s length whose length the loop may
change reaches 2^32 - 2, and its typed-array accesses got a guard no
typed array passes and a second copy of the loop.
Structural positions started with a file's absolute path, so lowered
names (closures, recursive records and unions, and every name keyed by a
type that holds one) changed with the directory the program was compiled
in. Union arms are ordered by their types' keys, so a program compiled in
a new temporary directory could also get its union arms in another order.
union-retype.test.ts compiles each program in a fresh temporary
directory, and in about one run in ten the arms of the visitor's unions
came out in an order where no retag keeps its tags, which failed "lowered
identity retags share the box".
Positions now name a file by its path relative to the entry's directory
(default library files keep their base name), so the same program gets
the same names wherever it sits.
`u === 0`, `u !== true` and `Object.is(u, x)` against a scalar arm tested
the union's tag inline but read its payload through scr_union_get_f64 or
scr_union_get_bool. The call passed the union's stack box to the runtime,
so LLVM could not promote the box to registers, and an element read of a
`number[]` compared with a number kept its value, tag and box in memory.
The comparison now loads the payload slot directly, as other union
extractions already do, and the box no longer escapes.
The lowering accepts on, once and addListener alike for each server,
session and stream kind, but the fallback Node types (used when a project
has no @types/node) declared fewer events for once than for on on the
http2 servers, sessions and streams, and no addListener on any of them.
`http2Server.once("connection", ...)` and `server.addListener("stream",
...)` failed to type-check (SC0001) where Node runs them.
once and addListener on Http2Server, Http2SecureServer, Http2Session,
Http2Stream, ServerHttp2Stream and ClientHttp2Stream now declare the same
overloads as on.
Corpus 7281 runs an HTTP CONNECT proxy on node:http, which #1791's
declaration of the http.Server 'connect' event made compile: it tunnels
to a local target and pipes both ways, with a non-empty head, once and
addListener registrations, and an 'upgrade' handover. Corpus 7282
registers http2 server, session and stream listeners with once and
addListener.
Closes#1439