copyFileSync/copyFile took exactly two arguments; the third is libuv's
copyfile flag bits (COPYFILE_EXCL 1, COPYFILE_FICLONE 2, FICLONE_FORCE 4 —
fs.constants.* reads bake to literals). The surface row now carries the
F64, and both forms lower to new flag-aware runtime entries that reuse the
existing recursive-copy helper (reflink via ioctl, EXCL through open) after
Node's own range check: a mode outside [0, 7] throws the RangeError
"mode is out of range: >= 0 && <= 7".
writeFileSync/writeFile fenced every `flag` option although "w" (the
default truncate-or-create) and "wx" (O_EXCL: EEXIST when the path exists)
write the bytes the lowered path already writes. Both literal flags now
lower; every other flag still fences by name because it writes different
bytes. The exclusive form is a separate runtime entry so the open flags
stay compile-time known.
Corpus 8427 pins the sync and promise forms of both against Node,
including the EEXIST and range errors, the mode-plus-flag combination and
the async twins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`class Sink extends EventEmitter<Map>` now compiles: the type argument of
an EventEmitter base is the listener MAP, a pure typing aid (Node erases
it too), so the base is the same emitter class whatever the map says. A
stream base's argument IS the element layout and still refuses, as does
every other generic base.
The bundled fallback `events` module declares the class with an
unconstrained map so interfaces typecheck against it, and its members stay
the untyped `...args: any[]` surface either way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
process.stdin/stdout/stderr.fd answers Node's compile-time descriptor
(0/1/2, on a TTY, a pipe, and a redirected file alike), and
process.stdin.isRaw answers the TTY's raw-mode flag through
scr_process_stdin_is_raw (1/0), or Node's undefined on a pipe-backed
stdin through the module's interned `boolean | undefined` union.
The declared type of isRaw is `boolean` (as @types/node has it), so the
read is added to isOptionalProcessStreamProperty: without that entry
maybeNarrow bridges the union to the declared arm and the runtime guard
traps on the undefined a pipe actually answers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
process.prependListener now registers at the FRONT of the signal, 'exit',
and 'uncaughtException' listener lists instead of refusing: the runtime
gained scr_signal_prepend and scr_process_prepend_exit (both memmove the
existing entries down one slot), and the uncaught-exception registry
shares one prepend implementation with the append path (scr_rej_push_at).
Families with an append-only registry ('message', 'disconnect',
'unhandledRejection', 'rejectionHandled', 'warning') and
prependOnceListener still refuse rather than reordering silently.
Server.off/removeListener unregister the most recently added matching
listener from each of the server's lists (error, close/listening,
connection/secureConnection, timeout, drop) through scr_net_server_off,
releasing the callback and its once-marker; the emit forms keep their
existing lowering.
A child handle that is a `ChildProcess | undefined` field or parameter now
takes `child?.kill(...)` / `child?.on(...)`: both were gated on a
questionDotToken that the optional-chain lowering has already consumed, so
the chain-blocked check (lowerer.chainBlocked) replaces it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under the synchronous output model nothing is ever queued: Node emits
'drain' only after a write() returned false, and these writes never
return false. The process streams therefore describe a stream whose
writes complete synchronously, and the members a flush idiom probes are
constants:
- writableHighWaterMark is 65536 (Node's default for a pipe, a file and
a TTY);
- writableNeedDrain is false;
- writableLength stays 0.
`on`/`once`/`addListener`/`off`/`removeListener`/`prependListener`/
`prependOnceListener` of "drain" evaluate their receiver and listener and
register nothing: the event cannot fire, and the listener table is not
observable (listenerCount/listeners/eventNames on the process streams
stay refused).
`events.once(process.stdout | process.stderr, "drain")` lowers to
`events.pendingForever`, a fresh promise that never settles, which is
Node's behavior for a stream no write asked to drain; it holds no loop
handle, so a program awaiting it still exits.
The fallback declarations gain writableHighWaterMark, writableNeedDrain
and the event methods, so typed programs can ask.
Corpus: `tests/corpus/process-stream-drain.ts`; the diagnostics fixture
`process-stream-listener-count.ts` pins that reading the listener table
stays refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
An fs.watch listener may now take (), (eventType), or
(eventType, filename). On a directory watch the filename is the entry
that changed; a file watch names the watched file's basename. The
parameter must admit null (Node passes null when the platform names
nothing). macOS directory watches that ask for the name are refused with
SC2020 until the FSEvents slice lands.
FSWatcher gains its EventEmitter surface for "change", "error" and
"close" (on, once, addListener, prependListener, prependOnceListener,
off, removeListener). The name and error adapters are emitted per
program because each builds the listener's own parameter shapes, and the
erased `string | null` nullable union takes the payload directly instead
of a union box. The runtime keeps one listener list per event with the
once flag and prepend ordering; a once listener's registration reference
leaves when it fires.
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>
Node answers a BigIntStats when the stat call is passed `bigint: true`: the
same snapshot, read through a bigint member table. Every number member of
Stats reads as a bigint — the integers, the `*Ms` millisecond members (the
truncated nanosecond quotient, as the binding computes them) and the
BigIntStats-only `*Ns` nanosecond members (sec * 1e9 + nsec, exactly) —
while only the four time members stay Dates. scriptc refused the option
outright.
The representation is static, chosen by the option literal: the lowerer
validates `bigint` against the call's result type (so
`{ bigint: true, throwIfNoEntry: false }` still narrows
`BigIntStats | undefined`), refuses a runtime-valued option and an option
that disagrees with the destination, and keeps every other option key
fenced. fstatSync joins statSync/lstatSync on the same path, with the
descriptor evaluated before the options.
The IR gains a `bigintStats` handle kind beside `stats`: the same RC
family, the same non-traced leaf and non-dynamic handle treatment, so no
shape, tracing or dyn-check behavior moves for any program that never asks
for one. Its member table is a `bigstats.*` lib-call family whose Date
members reuse the existing stat reads.
The runtime support is its own unit, scr_stats_bigint.c: the snapshot
layout moved into scr_stats_private.h so the bigint getters can read the
same fields without scr_lib.c carrying their code. The unit is gated on the
existing `bigint` feature in both the runtime-pack matrix and the
compiler's from-source unit table (the sanitized lane links from that one),
and `-ffunction-sections` keeps a bigint program that never reads a member
from linking any of the getters. No typed program's IR or LLVM changes.
tests/corpus/7018-bigint-stats.ts pins the member table, the Date/bigint
split, ms/ns agreement, arithmetic on a member, lstat over a symlink,
fstatSync over a descriptor, the throwIfNoEntry union, and the plain Stats
answers of the un-optioned and `bigint: false` forms against Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve tests/harness/npm.test.ts by taking main's npm-suite split and
porting the island path normalisation and the two island KNOWN entries
into tests/harness/npm-suite.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Response.clone() and Request.clone() on a value typed Response or Request
were refused: in TypeScript the fallback declarations had no clone, and
the fetch profile listed both as unsupported, so the call compiled to an
SC2020 refusal, also in JavaScript where the checker types the value. The
checked-dynamic handles already implement them (the body tees, and the
clone gets a copy of the headers with the same guard), so typed calls now
dispatch to those implementations, as text() and json() do.
The declarations gain clone(), and the fetch profile lists both as static
methods with the new static-clone fetch fixture as their differential
evidence. The surface manifest is regenerated, and the surface-manifest
probes that kept clone fenced now fence blob() and formData().
Adds the static-clone fixture and corpus programs fetch-clone.ts and
fetch-clone-untyped.mjs, with their corpus costs and preflight/order
baselines.
`import { win32 } from "node:path"` binds the win32 sub-namespace itself,
so `win32.normalize(...)` must key the win32 table on every platform —
Node answers each namespace its own platform's semantics anywhere.
`builtinNamespaceModuleOf` now resolves a named `win32`/`posix` binding
of `path`, `path/posix` or `path/win32` to that sub-namespace module
instead of falling through to a dynamic receiver.
`process.stdout.writableLength` / `stderr.writableLength` are now
accepted and answer 0: the compiled binary writes fd 1/2 synchronously,
so nothing is queued when the property is read, and `write()` answers
true for the same reason. Backpressure probes stay correct. The ambient
`tty.WriteStream` declares the property.
Corpus 8414 pins the value and its type before and after writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three shapes from pi's process/child helpers:
1. A cleanup arrow such as `() => process.off(signal, handler)` sits in
@types/node's `Process`-returning slot. The lambda's ABI was decided
from its inferred return type before the contextual void slot was
consulted, so the arrow promised a `Process` value the lowering cannot
produce and the registration fenced. `lambdaSignature` now takes the
contextual void answer first; the adoption block further down already
computes it, so every program that compiles today keeps its IR.
2. `child.off`/`removeListener` were unimplemented. They join the
`child.on` family, are limited to the removable literals `spawn`,
`exit`, `close` and `error`, and require the listener value that was
registered — removal is by closure identity, scanning the registry
from the end as Node does. New C runtime entry points
`scr_child_off_spawn/_exit/_close/_error` release the entry and shift
the tail.
3. `child.signalCode` reads `string | null` from the recorded exit
signal, with `scr_child_has_signal_code`/`scr_child_signal_code`
behind the same typed-union wrap as `spawnRes.signal`; the IR
validator pins the `string | null` result.
The process listener registration paths (stdin events, IPC message and
disconnect, uncaughtException and uncaughtExceptionMonitor,
unhandledRejection, warning, signals, exit) now accept any discarded
expression, matching `child.on` — `resultIsDiscarded` rather than a bare
expression statement — so `handlers.push(() => process.off(…))` compiles.
Evidence: corpus 8412 (removal through `off` and `removeListener` for
all four events, `signalCode` while running, after a normal exit, after
a fatal signal and after a failed spawn, idempotent double removal) and
the `node-types` fixture plus project-config test that pins pi's exact
`() => process.off(...)` cleanup closure. Ambient declarations gain the
`signalCode` property and the four `off`/`removeListener` overloads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Node's writeFileSync accepts an open file descriptor where it accepts a
path: it writes the data at the descriptor's current offset and leaves the
descriptor open, since closeSync owns its lifetime. The static fs surface
declared only the path forms, so `fs.writeFileSync(fd, text)` failed type
mapping — the compiler was missing a documented overload, not a
conversion.
Declare the (fd, data) and (fd, data, "utf8" | "utf-8") forms in the
node-fallback typings, route the call by the first argument's static f64
type to a new fs.writeFileSyncFd libfn, and implement it as
scr_fs_write_file_fd: a write loop over the whole string through the
descriptor-validating scr_fs_write_bytes, resuming short writes, with the
descriptor operation's errno shape on failure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Without @types/node, every program loaded scriptc-node-fallback.d.ts whole:
the console and process globals and 47 Node modules, about 8,100 lines,
parsed, bound and checked in every cold build whether or not the program
imported them.
- The fallback file keeps the globals and the modules they name (console,
readline, readline/promises, perf_hooks, tty). Every other module, under
both spellings, has its own file in ambient/node-fallback/, which
references the files of the modules its declarations name.
- A module's file joins the program when one of the program's files names
the module in a string literal: imports, requires, import types and JSDoc
types all name modules that way. The entry is read before the first
program; the program-root discovery passes add modules that other files
name. Files are read as tsgo reads them, and the TypeScript libraries and
scriptc's own declarations are skipped.
- The checks that recognized the fallback file by its path recognize every
fallback file.
A TCP socket's write() answered nothing in typed code and true through
untyped values, and a 'drain' listener never fired. Node 24 answers
false when the bytes the write leaves unsent reach the socket's
65536-byte high-water mark (writeOrBuffer computes it after the socket
writes), sets needDrain, and emits 'drain' when the write buffer
empties; writes to an ended or destroyed socket answer false.
The runtime's write entry points now return that answer, record
needDrain, and fire the socket's 'drain' listeners from the flush that
empties the buffer, unless the write half is ending or the socket is
gone. writableLength reads the unsent bytes and writableNeedDrain the
flag. on('drain') and once('drain') register on the typed path and
through untyped values, and socket.write() may be used as a value.
The fallback node:net declarations type write() as boolean, add the
'drain' event and both properties.
RUNTIME_ABI_VERSION goes from 43 to 44: the socket write entry points
return a boolean.