A structural type that receives class instances became a checked view
for all of its values, even when nothing could tell a copy from the
original. In tsc-ts this turned ResolvedProjectReference, the redirect
parameter of every module resolution, into a checked-dynamic value.
markIdentityFlows still finds every flow, then keeps a type marked only
when the program can observe the difference:
- values of the type are written through, compared by identity (also
inside generic functions), used as Set or Map keys or array search
needles, or passed to whole-object observers or any-value parameters;
- a field the type exposes is written on the source class after
construction, or is an accessor;
- its values flow into another kept view or class-backed protocol.
Other types stay native records.
The int32 field analysis keeps every field of a class whose instances
reach checked-dynamic code as a double when the program contains a
dynamic helper call that may store a property through a live view.
The dynamic operator helpers counted as such calls, so a single `+`,
`*`, `|`, `++` or unary `+` with a dynamic operand kept the fields of
every exposed class as doubles.
dyn.add, dyn.arithmetic, dyn.bitwise, dyn.toNumeric and
dyn.toNumberCoerce convert their operands with the ToPrimitive and
coercion helpers of dyn.compare, dyn.numberConstructor and
dyn.toStringCoerce, which are already on the read-only list, and return
a new number, string or bigint. dyn.increment steps the number or bigint
dyn.toNumeric returns. All six are now on the read-only list.
Node sets a test's concurrency to Infinity for `true` whenever the test
has a parent, and to os.availableParallelism() - 1 only for the root
test. Every test and suite a program declares has a parent, so `true`
now runs all of its subtests at once instead of capping them at the CPU
count minus one.
The concurrency fixture adds a suite of 64 concurrent tests that checks
all of them run at once.
copy() of a Hash whose stream was destroyed before a digest, by
destroy() or an error, carried over the closed-stream flag, so the
copy's first stream use threw the SC2020 error. The copy now starts
without a stream, as in Node, and gets one on first stream use.
Corpus 5761 covers a copy written and ended after the original's
'close' event.
Object.groupBy now builds its result record with a null prototype,
stored as a flag on the record's overflow map. Inspection prints
"[Object: null prototype]", Object.getPrototypeOf answers null, `in`
finds no inherited names, and String(), template literals, toString(),
hasOwnProperty() and propertyIsEnumerable() throw Node's TypeErrors.
Spread copies and structuredClone keep Object.prototype, as in Node.
Index-signature records converted from an Object.create(null) value
keep the null prototype too.
- JavaScript calls of Promise.withResolvers' resolve/reject through the
record no longer fail with an internal compiler error.
- new RegExp with the d or v flag is refused (SC1120) for literal flags
and throws a named error for computed flags, instead of SyntaxError.
- JavaScript .groups reads on matches whose regex is not traceable are
refused like TypeScript's, instead of reading undefined.
- Iterator helpers on the string iterator are refused instead of
failing at run time.
node:test changes, matching Node 24.22:
- { concurrency } on describe, suite, test, it and t.test sets how many
subtests run at once: a number, true for os.availableParallelism() - 1,
or false for 1. Subtests inherit the parent's value.
- Above 1, each subtest runs on its own fiber. Subtests start in
declaration order as slots free up, and t.test and test() calls inside a
concurrent test start the subtest at once; t.test's promise settles when
it finishes. The parent finishes after all of them.
- Results, diagnostics and failures are reported in declaration order.
- A concurrent subtest waits after its beforeEach hooks, and a concurrent
suite before its children, for as many turns as Node's runner does, so
sibling bodies interleave as they do there.
- A concurrency that is not an integer from 1 to 4294967295 throws Node's
ERR_OUT_OF_RANGE RangeError when the test or suite is created.
- Nested test() calls and hooks inside a concurrent subtest attach to the
subtest that runs on the calling fiber.
The concurrency fixture compares the reporter output, the order of the
bodies' side effects, and the range errors with Node, plain and with
SCRIPTC_SAN=1.
promisify(zlib.deflate) and the other callback one-shots (inflate,
deflateRaw, inflateRaw, gzip, gunzip, unzip) over a named or namespace
import of node:zlib now lower. A call through the binding,
gzipAsync(data, options?), runs the callback form's work on the worker
pool and returns a promise that settles where the callback would run:
fulfilled with the Buffer, or rejected with the callback form's Error
(errno, code and message). An invalid option or input rejects the promise
instead of throwing, as the throw inside Node's promisify wrapper does.
The runtime adds scr_zlib_codec_promise and its checked-dynamic twin,
which share the callback forms' options ladder and worker-pool operation
and settle a pending promise in place of the callback. The fallback
declarations gain the matching util.promisify overload.
Node's util.promisify returns the custom promisified forms of
stream.finished and stream.pipeline, which are stream/promises' functions
of the same name. A const binding of promisify over the node:stream named
imports or namespace members now registers that stream/promises member
as the projection, so calls through the binding lower as the
stream/promises call and accept the stream arguments and options it
accepts.
The fallback declarations gain the matching util.promisify overloads.
pipeline's overload comes first: its (...streams: unknown[]) => unknown
type is assignable to every other target's parameter type.
The ESNext array lib is now part of the type world, and Array.fromAsync(source,
mapFn?) compiles when the source is an array of values or of promises. Each
call lowers to an async helper with the spec's loop: elements are awaited
through the async-from-sync iterator, with the same microtask turns as a
`for await` loop over an array, and a mapping function's result is awaited
before it is stored. A throwing or rejecting mapping function and a rejected
element close the iterator before the returned promise rejects, on the turn
Node rejects it. Async generator sources, a thisArg, and arrays that mix
values and promises are refused.
The SuppressedError hook was installed by a load-time constructor in
scr_json.c, which kept the checked-dynamic conversion of caught values in
every program that linked that unit. scr_exc_suppress now records the
disposal and body snapshots in a side table in scr_exception.c, the error's
dyn view reads them when scr_json.c builds it, and the SuppressedError's
teardown releases them.
`using` and `await using` now accept object literals and other values typed
Disposable, AsyncDisposable or unknown. The declaration reads the value's
[Symbol.dispose] method ([Symbol.asyncDispose] first for `await using`) and
throws Node's TypeErrors for values that are not objects and for missing or
non-callable methods; null and undefined dispose nothing. Scope exit calls the
method with the value as `this`, and `await using` awaits the result of an
async method but not of the sync fallback. A class instance reached through
such a value throws an SC2020 error, because its dynamic view has no methods.
SuppressedError is its own error class: a failing disposal over a pending
error throws one whose `error` is the disposal error and whose `suppressed` is
the pending error, both non-enumerable as in Node. `new SuppressedError(error,
suppressed, message?)` and `instanceof SuppressedError` compile, and
`extends SuppressedError` is refused.
std::filesystem::copy_file reports a destination that exists but is not
a regular file as invalid_argument in libstdc++ and as not_supported in
libc++, which Node's macOS builds use. cpSync on Apple platforms now
throws ENOTSUP there, as Node does, and the fs errno names include
ENOTSUP.
Corpus 5571 copies a 400-level directory chain instead of 1500 levels,
so its deepest path stays under macOS's 1024-byte PATH_MAX.
5628-promisify-timers.ts compared a 1 ms timer with an immediate armed
at the same time. Their order depends on how much time has passed when
the loop reaches its timers phase: once the delay has elapsed, the timer
fires before the check phase. Node prints either order under load. The
test now orders three timers armed together (1, 10 and 30 ms), which
settle in deadline order. The node-types fixture does the same with two
timers.
fs.cpSync, fs.cp and fs/promises cp compile with every option:
dereference, errorOnExist, filter, force, mode, preserveTimestamps,
recursive and verbatimSymlinks. They follow Node's two implementations.
cpSync follows cp-sync.js over the fs binding: copy errors are plain
Errors with a code, std::error_code failures throw an ErrnoException,
and links inside a copied directory point at their canonical targets.
fs.cp and fs/promises cp follow cp.js, whose copy errors are
SystemErrors with code, info, errno, syscall and path.
Options and paths validate in Node's order with Node's errors. filter
runs for the top-level paths and each entry in Node's order; an async
filter must return a promise that is already settled, and a pending one
throws SC2020. The callback and promise forms complete the copy before
the callback runs or the promise settles. Windows builds throw SC2020.
The copy lives in the new scr_fs_cp.c. Directory walks keep their frames
on the heap, so deep trees also copy on a fiber's stack. File contents
copy with sendfile on Linux, and COPYFILE_FICLONE uses FICLONE
where it exists. ScrError gains the SystemError context and the
ErrnoException key order, with matching fields in the compiler's Error
layout. The stored fs.cp callback uses the same implementation.
Corpus 5567-5571 cover the sync, callback and promise forms, errors,
symlinks and 1500-level trees.