Commit Graph
498 Commits
Author SHA1 Message Date
Chris Tate 1716899871 pi decisions: per-arm union spreads, checked-dynamic union merges, EventEmitter with any event name, optional reads of always-defined globals (#2596) 2026-10-30 05:53:18 -05:00
Chris Tate 7ae0a0b322 Merge main 2026-10-30 05:03:19 -05:00
Chris Tate 895dd44bd7 Keep evaluation order and listener signatures with any-name emitters and union spreads
A distributed union spread evaluates its source before the dispatch, so
the properties written before the spread run after it. When the source
may have effects (a call that changes a value an earlier property reads),
those properties must now be constants; otherwise the literal takes the
checked-dynamic merge, which keeps Node's order.

A listener registered under an unrestricted event name was not recorded
in the program's event scan. A prefixed or suffixed emit with no matching
pattern listener was then lowered to `false` without dispatching, so the
any-name listener never ran. The scan now records such registrations, and
computed-name emits dispatch whenever one exists.

The same registrations can place a listener of any signature in the
bucket of a literal event. listeners(), rawListeners() and
events.getEventListeners() of a literal event returned that bucket typed
by the event's tuple, so calling an element passed the listener arguments
of other types. These calls are now refused once the program registers a
listener under an unrestricted name, as they already are for pattern
registrations.
2026-10-30 04:09:40 -05:00
Chris Tate 834bccdcd0 Keep records cast from unknown[] elements to dictionary types as live views 2026-10-30 04:01:39 -05:00
Chris Tate 422a02e225 Compile three Node test shapes: a top-level return before declarations, nullable strictEqual operands, and a branch-assigned let (#2590) 2026-10-30 01:04:18 -05:00
Chris Tate 8a5d2e14a2 Match Node's socket encodings, rejected writes and event loop liveness (#2599) 2026-10-30 00:09:13 -05:00
Chris Tate d80b34d92d Match Node's socket encodings, rejected writes and event loop liveness 2026-10-29 22:18:16 -05:00
Chris Tate f521dc91b1 Compile three Node test shapes: a top-level return before declarations, nullable strictEqual operands, and a branch-assigned let
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.
2026-10-29 20:51:09 -05:00
Chris Tate 48dd898db6 Support non-Error 'error' payloads and checked-dynamic listeners in listeners() and meta events 2026-10-29 19:16:06 -05:00
Chris Tate 78dbd98b1a Merge main 2026-10-29 16:39:31 -05:00
Chris Tate 4e67c5ce2c Host stdio: typed process.stdout.write slot, write-callback errors, drain, run-time-typed chunks (#2546) 2026-10-29 15:59:21 -05:00
Chris Tate 3b1a2c41b6 Merge main 2026-10-29 15:55:05 -05:00
Chris Tate f1825611fb Collect garbage cycles across the scriptc and V8 heaps (NI-8) (#1908) 2026-10-29 15:33:24 -05:00
Chris Tate 145f8fcd5e fs.watch names the changed entry; fs.watchFile polls in its own gated unit (#2507) 2026-10-29 15:13:12 -05:00
Chris Tate 16f3c16752 Match Node's node:path behavior from Node 24's path tests (#2495) 2026-10-29 15:10:50 -05:00
Chris Tate 59eced7a6c Lower ReadableStream tee, pipeTo and pipeThrough in TypeScript (#2513) 2026-10-29 15:09:05 -05:00
Chris Tate f9b024dbab Count ArrayBuffer storage in process.memoryUsage()
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.
2026-10-29 13:36:41 -05:00
Chris Tate 1941a3e353 Merge main 2026-10-29 09:27:38 -05:00
Malte UblandClaude Opus 5.5 bd06b809f1 Answer drain probes and drain listeners on the process streams (W1)
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>
2026-10-29 05:37:57 -07:00
Malte UblandClaude Opus 5.5 2b03f66ff3 Host patching H1: typed process output write slot
Reading `process.stdout.write` as a value, `write.bind(stream)`, and
`process.stdout.write = fn` now reach the runtime stream object's write
slot in TypeScript, and a program that replaces an output write method
calls its typed writes through that slot (SC8020 refuses the combination
with Node islands until H2 shares the stream object with them).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:37:56 -07:00
Chris Tate 60be717d92 Merge main 2026-10-29 04:24:50 -05:00
Chris Tate 5f7a9cc8c7 Merge main 2026-10-29 03:34:56 -05:00
Chris Tate a7b673e4ff Keep path module objects and bound path functions Node's values in untyped code
A path family read by untyped code is a plain object holding Node's
members in Node's order, built once per family and kept in the
builtin-module registry. Object.keys, Object.entries, Object.assign,
spreads, for-in and own-property checks over it see those members.
path.posix.posix and the cross-family members refer to the registered
objects; the registry releases their members at exit through the objects'
own storage.

Every builtin function value that becomes untyped boxes as its checked
twin, under the builtin's name and length, after lowering: a module
object's member, an assertion's argument, and a read of a binding whose
only stores are that builtin (a const local, or a module-level binding).
`ns.join === path.posix.join` holds for an untyped path namespace, and
`const join = path.join` passed to an untyped function compares equal to
path.join and validates its arguments with Node's errors.

The lowerer journals the checked-twin table (each builtin's data, display
name and location), so a unit replayed from the journal keeps its twins.
2026-10-29 03:32:36 -05:00
Chris Tate 22357e8bed Lower ReadableStream tee, pipeTo and pipeThrough in TypeScript
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.
2026-10-28 22:18:11 -05:00
Malte UblandClaude Opus 5.5 7176257136 fs.watchFile: poll a path from its own gated runtime unit
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>
2026-10-28 19:49:44 -07:00
Malte UblandClaude Opus 5.5 77faf0c9e6 Accept the ICU-answered Intl forms and narrow their refusals
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>
2026-10-28 19:49:10 -07:00
Chris Tate 7a7fd8ce1d Match Node's node:path behavior from Node 24's path tests
path.posix, path.win32 and the bare path module are one module object
each, the handle process.getBuiltinModule answers, so require('path/posix'),
path.posix and the target platform's bare path compare as Node's do, a
namespace beside other values in one slot is untyped, and path[family]
reads that object. Functions read through them take checked arguments:
each string parameter passes Node's validateString under its own name
(basename's suffix first), resolve validates paths[i] only as far as its
last-first walk reads them, parse answers an untyped { root, dir, base,
ext, name }, format follows _format over any object, and
toNamespacedPath answers a non-string as it is. A JavaScript call whose
arguments are not strings, or omit one, goes through the same checks.
Typed slots keep the typed function values.

path.win32.relative lowercases both resolved paths with
String.prototype.toLowerCase and walks them by UTF-16 unit, comparing
segments instead when lowercasing changed a length, as Node does. It
folded ASCII only, so a path with İ or Ⱥ in a different case gave a
longer relative path. The function moves into its own runtime unit,
scr_path_win32_relative.c, which links with the regex unit whose case
mapping it uses. scr_path.c stays free of it, so library and
cross-target builds that link the path functions without the regex unit
still link.

A JavaScript binding whose inferred type holds a union of functions and
arrays (a test table of [fn, cases] rows, or one destructured row) takes
the checked-dynamic kind that dynFallbackType gives the unmappable
spellings of such tables. Its rows keep their functions' names, and
for-of, forEach and destructuring read the items as untyped values.
TypeScript bindings keep their typed layout.
2026-10-28 19:45:36 -05:00
Chris Tate 44a4cd9db1 Merge main 2026-10-28 13:19:00 -05:00
Malte Ubl d1566d2746 Merge branch 'ni/i10-js-island-requires' into ni/i11-cross-heap-gc 2026-10-28 08:48:18 -07:00
Malte UblandClaude Opus 5.5 08ec7fd291 Merge ni/i10-js-island-requires into ni/i11-cross-heap-gc
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:43:05 -07:00
Chris Tate 17efed329f Give JavaScript fetch bodies the members of a web ReadableStream
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.
2026-10-28 03:44:57 -05:00
Chris Tate 41e7950ea9 Keep frozen checked records frozen, and give sloppy-mode this the global object
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.
2026-10-27 23:07:13 -05:00
Malte UblandClaude Opus 5.5 78fc4be1b4 Document the event-order limitation between native code and islands
Native timers and an island's Node timers run on the hosted libuv loop but are not merged into one order, so callbacks from the two sides can run in a different order than in Node and the order can vary between runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:39:16 -07:00
Chris Tate 919b79b62a Revert "Keep frozen checked records frozen, and give sloppy-mode this the global object" 2026-10-27 19:20:37 -05:00
Chris Tate e941d5d659 Run JavaScript WritableStream, TransformStream and pipes natively (#2437) 2026-10-27 18:43:35 -05:00
Chris Tate f945efecee Run JavaScript WritableStream, TransformStream and pipes natively
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.
2026-10-27 15:55:31 -05:00
Chris Tate 62d4cecd3a Keep frozen checked records frozen, and give sloppy-mode this the global object 2026-10-27 15:22:08 -05:00
Chris Tate f12a2b2a23 Take JavaScript ReadableStreams as fetch bodies and in instanceof by value
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.
2026-10-27 10:00:00 -05:00
Chris Tate 9ca9c7af9d Run JavaScript web streams on a native ReadableStream
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.
2026-10-27 06:53:26 -05:00
Chris Tate 7e225c5d87 Merge main 2026-10-26 17:33:06 -05:00
Chris Tate 2f13edd058 Merge main 2026-10-26 11:36:06 -05:00
Malte Ubl 572e7e3eac Merge remote-tracking branch 'origin/lf/l4-expressions' into lf/l5-polish
# Conflicts:
#	packages/compiler/src/frontend/lowering/lowerer.ts
2026-10-26 05:37:00 -07:00
Chris Tate fe244085d4 Run native async generators as stackless coroutines
An async generator whose body suspends only at its own awaits and yields
now compiles to an LLVM switched coroutine, as async functions of that
kind already do, instead of running on a fiber with its own stack. Its
steps run on the stack that resumes it (the loop's after an await), its
awaits suspend through scr_coro_await_prepare and scr_coro_hop_prepare,
and its yields store OUT through scr_coro_yield_* before suspending.

- scr_async_gen_new_stackless creates the generator. The stackless hop
  settles an async generator's state as the fiber hop does, and a step
  that would start below the stack guard throws a RangeError, which
  rejects its request.
- Calling an async generator's next, return or throw no longer counts as
  parking the caller: they queue a request and run the body to its next
  suspension, which returns to the caller. Bodies that consume async
  generators can now be stackless too.
- An untyped await (async.awaitDyn) has a native coroutine form,
  extracted by scr_coro_await_dyn_value, so async bodies that await
  untyped values can be stackless.
- An async generator's return through a finally region takes its await
  turn through the coroutine when the body is one.

Debug builds, libraries, worker programs and Windows executables keep
fibers, as for stackless async functions.
2026-10-26 07:28:50 -05:00
Chris Tate b8aaa5985a Merge main 2026-10-26 05:23:00 -05:00
Chris Tate acf19e2652 Merge main 2026-10-26 02:53:26 -05:00
Chris Tate 21bce9e4fa Merge main 2026-10-26 00:21:24 -05:00
Malte UblandClaude Opus 5.5 1ae5f105ca Rank dynamic slots by their dynamic operations, name them in SC6101, and document them
Stage L5 of the localized-failure design.

- SC6101 names a dynamic slot as the cause of a likely-hot untyped function
  ('tool' is a dynamic slot, declared 'ToolDefinition', which does not
  compile).
- Coverage counts the dynamic operations each slot's values take part in,
  lists the costly slots first, and puts the count in the JSON envelope.
- A build whose only performance warnings are dynamic slots closes with a
  line about declared types instead of function types.
- Docs: a Dynamic slots section in the coverage guide, the limitations
  page bullet, and --fail-on=dynamic-slots in the CLI reference and usage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:48:18 -07:00
Chris Tate 80ae660be6 Merge main 2026-10-25 21:22:05 -05:00
Chris Tate 8981b5c0a2 Resolve computed createRequire specifiers at run time (#2079) 2026-10-25 20:29:51 -05:00
Chris Tate 5dfa62b9fc Merge main 2026-10-25 18:54:10 -05:00