A top-level return in a CommonJS module compiles when declarations follow
it. When the return is taken, only code that already exists can observe the
bindings it skips: functions and classes created before the return, and
function declarations after it. A module where one of them reads a binding
declared after the return, or that assigns exports after it, still stops
with SC1090.
assert.strictEqual and assert.notStrictEqual compare a nullable number,
string or boolean (`number | null`, `string | null`) with a scalar, null or
undefined through the checked-dynamic SameValue compare, which reports
Node's failure messages. notStrictEqual of another nullable value, such as
`child.stdin`, against null or undefined tests the value's tag.
An initializer-less JavaScript `let` whose writes all agree also takes
their type when the writes are calls declared in the standard library, such
as `spawn(...)`, and at file scope. A runtime handle that a closure
captures takes the handle or undefined. Inside a nested function, a member
read on it (`c.address()`, `child.stdin`) reads the handle and throws
Node's TypeError while the binding is still undefined; other reads there
take the checked-dynamic value.
process.memoryUsage() reported external and arrayBuffers as 0. arrayBuffers
is now the storage of the ArrayBuffers, typed arrays, Buffers, DataViews
and SharedArrayBuffers the program holds, and external the same storage
without SharedArrayBuffers, as Node splits them. A view over existing
storage adds nothing. v8.getHeapStatistics().external_memory reads the
same figure as external.
Each ScrBytes owner records the bytes it counts in the struct's padding,
so ScrBytes stays 64 bytes and the offsets compiled code reads hold; a
static assertion pins them. Owners are counted in a per-instance counter,
because their reference counts are not atomic and they are released on
their instance's thread. Shared storage moves its owner's count to a
process-wide atomic counter, since any thread may release it.
In TypeScript, tee(), pipeTo() and pipeThrough() on a ReadableStream were
refused with SC2020, and the WritableStream constructor and a
TransformStream with a transformer had no lowering, while JavaScript
programs reach all of them through the web stream classes
(scr_webstreams.c).
A TypeScript program that uses one of them now builds its ReadableStream,
WritableStream and TransformStream values with those classes, as
JavaScript does, and %main installs them, so a native stream such as a
Response body answers the three methods through its web view. A program
that uses only getReader(), cancel(), locked and the identity transform
keeps the native streams and builds to the same binary.
The fallback declarations gain UnderlyingSink and Transformer, the
WritableStream constructor takes a sink and the TransformStream
constructor a transformer and queuing strategies, and their controllers
map to checked-dynamic handles. The fetch profile lists the three methods
as static with the new static-web-streams fetch fixture as their
differential evidence, and keeps them in the set that links the classes.
The surface manifest is regenerated, and the surface-manifest probe that
kept tee() and pipeTo() fenced now fences values() and the async
iterator.
Adds the static-web-streams fixture and the corpus program
web-streams-typed-pipes.ts, with its corpus cost and order baseline.
fs.watchFile(path[, options], listener) polls the resolved path every
`interval` milliseconds (5007 by default, `persistent` by default) and
calls its listener with (current, previous) Stats when a poll sees a
difference. The first poll only records the baseline, a path that cannot
be stat'd polls as all-zero Stats, and a stat failure that repeats for
the same errno is not re-reported. One poller per RESOLVED path is
shared by every listener on it, and fs.unwatchFile(path[, listener])
removes the last listener registered with that callback, or every
listener when called without one. The call is a statement, since Node's
StatWatcher has no lowering, and bigint: true is refused.
The implementation is scr_watch_file.c, a new gated runtime unit
switched by moduleUsesFsWatchFile, so programs that do not poll link
none of it. The feature travels the usual chain (runtime-features,
executable-features, the native link info, the executable cache keys,
the runtime-pack matrix) and the validator learns that
fs.unwatchFile's trailing listener slot may be omitted.
Evidence:
- tests/corpus/fs-watch-file-names.ts and fs-watch-file-polling.ts match
Node on stdout, stderr and exit status in the plain and sanitized lanes
- 1564-fs-watch.ts, 1751-options-record-stance.ts and the `fs` corpus stay
green; the new order baselines and corpus cost files are recorded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The forms the first slice answers now lower: x.toLocaleString() (and an
explicit undefined locale) to icu.numFormatDefault, the argument-free
Date#toLocaleString/toLocaleTimeString to the ICU date formatters, and
Intl.Segmenter({ granularity: "word" }) to the ICU word iterator (a
literal language tag is the locale; the empty string is the default).
Sentence segmentation keeps its refusing construction in JavaScript
dependencies, the grapheme segmenter stays the engine-free fast path,
and the code notes the deletion I4 schedules.
The refusals that named these forms follow: the four stdlib hints, the
Intl identifier hint, the Date method hint and the Segmenter option
messages list what lowers and what still does not (locale negotiation,
options bags, toLocaleDateString, formatter values). The library mode
refuses the icu.* libCalls, whose ambient locale state and data file a
library archive cannot carry, and the ambient process.config comment says
what a compiled binary can honestly answer.
An argument that IS undefined (exactly undefined, or void) means the
absent form and lowers; one whose type merely includes undefined may hold
a locale or an options bag at run time, and the constructor cannot read
either, so it is refused rather than silently treated as absent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In JavaScript, a Response or Request body answered getReader, cancel and
locked, and every other ReadableStream member was undefined: tee,
pipeTo, pipeThrough, values and for await threw TypeErrors where Node's
body is a ReadableStream.
In programs that link the web stream classes, the body's handle now
stands for a ReadableStream once any other member is used
(sf_stream_web_view): a stream with a zero high-water mark whose native
source reads the body (scr_webstream_readable_over). From then on every
member goes to it, the class's method values and getters run on it
through the alias's view (scr_dyn_handle_class_receiver), and text(),
clone(), new Response(body) and fetch uploads read the body through it,
so its lock and bodyUsed follow it as in Node. A body locked when the
view is made keeps the view locked until the lock is released. Once
only the view's source holds the body, the body lets the view go.
The compiler links the classes into a program whose JavaScript reads a
ReadableStream member that the fetch profile lists as unsupported by the
native stream, or iterates a ReadableStream with for await, and that
names none of the classes: %main calls webstream.install first.
Programs without web streams build to the same binaries as before.
Adds corpus programs fetch-body-web-stream-linked.mjs,
fetch-body-web-stream-unlinked.mjs and fetch-body-web-stream-network.mjs,
with their corpus costs and preflight/order baselines.
Typed records checked from frozen untyped values stay frozen, as the
objects they copy are. A sloppy-mode function called with undefined or null
as this receives the global object that globalThis reads.
The record check reads the source's integrity with a query that answers for
every value: a proxy answers as its target, and values without a modeled
integrity level, which Object.freeze refuses, are not frozen. Object.isFrozen
keeps its refusal of those values.
In JavaScript sources, WritableStream with its default writer and
controller, TransformStream with its controller, pipeTo and pipeThrough,
and TextEncoderStream and TextDecoderStream join the native web stream
classes, following Node 24's webstreams step by step: sinks and
transformers, backpressure and the writers' ready and closed promises,
abort with the controller's signal, the pipe's shared write requests and
its abort signal, and UTF-8 encoding and decoding across chunks settle in
Node's order. The streams take own properties, which a pipe does not use
for its own reads and writes, and the global object answers the classes
by computed name in programs that name them.
A JavaScript class that extends one of these classes keeps the SC1090
refusal, and TextDecoderStream refuses encodings other than UTF-8.
The node24-web-stream lifecycle, errors, queues, transforms, pipes,
pipe-errors, pipe-abort, tee and encoding cases now match Node.
A JavaScript ReadableStream given to Response, Request or fetch as a body
was converted to the string "[object ReadableStream]". The fetch unit now
reads such a stream through a reader of its own. A locked or disturbed
stream gives Node's TypeError, and the chunks must be Uint8Array
when the body is consumed ("Received non-Uint8Array chunk").
instanceof against a web stream class held in a variable or passed as an
argument walked no prototype for handles and answered false. It now starts
at the class prototype of the handle. The fetch unit's own streams, readers
and controllers, such as Response.body, are instances of the JavaScript
classes, so `response.body instanceof ReadableStream` is true again.
These paths, and the fetch reader functions' hand-off to web stream
readers, are in SCR_HANDLE_CLASSES variants of scr_json.c and scr_fetch.c,
which only programs with JavaScript web streams link; other programs that
use fetch build to the same size as on main.
In JavaScript sources, ReadableStream, ReadableStreamDefaultReader,
ReadableStreamDefaultController, CountQueuingStrategy and
ByteLengthQueuingStrategy (globals and node:stream/web exports) are class
values over a new runtime unit, scr_webstreams.c, which follows Node 24's
webstreams step by step: underlying sources, queuing strategies, default
readers, tee, ReadableStream.from and async iteration settle their promises
in Node's order. scr_dyn_handle_class.c gives a dyn handle tag a
constructor value with a prototype, brand checks and Node's constructor
errors. Stream state carries cycle headers, so the collector reclaims
streams whose callbacks close over their controllers.
Promise steps run as native promise reactions, a variant of scr_async.c
that only programs with web streams link; every other program links the
default object. TypeScript sources keep their static web stream lowerings.
Byte streams, BYOB readers, pipeTo and pipeThrough throw SC2020.
node24-web-stream-readers and node24-web-stream-readable-lifecycle now
match Node.
A pipeline stage that arrives as an untyped value now follows Node's
stage ladder for more shapes. As the first stage, a function is a source
function: the pipeline calls it with { signal } while it is set up and
pumps its result, which must be iterable (ERR_INVALID_RETURN_VALUE
otherwise), and a { readable, writable } pair pumps its readable side.
After the first stage, the pipeline call is lowered once per combination
of its first three untyped stages being functions or not, and a typeof
test picks the branch at run time. A function stage is called with the
previous stage and { signal }; before the last stage its result must be
async iterable, and as the last stage a promise resolves the pipeline's
value and an async iterable feeds the PassThrough pipeline() returns
(SCR_PIPE_DEST_DYN). Any other value that is not a stream becomes
Duplex.from of it, which the next stage reads from without the previous
stage being piped into it, and Duplex.from's ERR_INVALID_ARG_TYPE for
the values it rejects.
An untyped stage followed by an untyped function stage hands its value to
that stage through a cell created per pipeline call, so the function
receives the previous stage's own iterator, as Node's makeAsyncIterable
answers it, instead of an async generator over it.
Untyped web streams, pairs after the first stage, and function stages
that return a stream throw the not-supported error.
RUNTIME_ABI_VERSION goes from 45 to 46 for the new stream exports.
A pipeline whose first stage is a checked-dynamic value now checks it at
run time, as Node's stage ladder does: a stream is piped as before, and a
string or a value with a Symbol.asyncIterator or Symbol.iterator method is
pumped into the next stage through an async generator over its iterator.
A typed Set, Map, generator or async generator object as the first stage
takes the same path. This covers the callback and stream/promises forms,
with or without function stages and pipeline options.
A write whose callback fails synchronously no longer sets needDrain, so no
'drain' follows the error and a pumped source stops at the failed write,
as in Node.
RUNTIME_ABI_VERSION goes from 37 to 42 for scr_stream_dyn_iterable_source.
Object.freeze, Object.seal and Object.preventExtensions of a class
instance held in an untyped value threw SC2020 at run time. Node changes
the instance itself: writes through its typed references then throw the
read-only TypeError in strict mode code, adding properties through any
reference fails, and Object.isFrozen, isSealed and isExtensible report
the state through either reference.
- In a program that freezes, seals or prevents extensions of an untyped
value that is not a fresh literal, every class hierarchy that can be held
there gets the integrity word that static Object.freeze already uses, and
with it the existing write checks.
- The runtime applies the operation to the instance's checked view and
commits the view. The commit of such a hierarchy stores the view's
integrity state in the word (scr_dyn_class_view_integrity) and makes the
instance's property bag not extensible with it. A hierarchy without the
word still throws SC2020.
- These commits replace and remove the bag's field entries whatever their
attributes, so a sealed or frozen view does not leave stale field values
in the bag.
- The analysis behind records and arrays now starts from these calls only,
rather than from every untyped freeze the lowering emits, such as the
frozen strings array of a tagged template.
- Programs without such calls compile as before. The runtime ABI moves to
v38.
Corpus 7407 covers freeze, seal and preventExtensions through unknown of
classes and subclasses, writes through typed and untyped references and
through methods, properties added before and after the operation, and the
integrity queries, and matches Node.
Refs #1374
Co-authored-by: blurname <50226652+blurname@users.noreply.github.com>