Commit Graph
4322 Commits
Author SHA1 Message Date
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 cf369f70af Fix write callbacks that take the error argument (W3)
`writable.write(chunk, cb)` with `cb` passed as a function VALUE lost the
completion's error: `lowerWriteCb` checked only the callback's syntax and
lowered every other callback through `lowerExprExpecting(cbArg,
funcOf([], VOID))`, which adapts a one-parameter function down to zero
parameters. The native binary therefore printed `undefined` where Node
prints `null`, or the write's own error (`ERR_STREAM_WRITE_AFTER_END` /
`ERR_STREAM_DESTROYED` for late and destroyed writes).

Lower such callbacks through a new `writable.writeEcb` libCall
`(recv, mode, chunk, encoding, cb)`: a zero-parameter callback keeps the
existing synchronous path, and a callback that declares the error (a dyn
value, or `null | Error`) is wrapped by `scr_stream_ecb_wrap` with the
compiler-built adapter that delivers the error the write completion
carries. The stream unit publishes that error only in its
`SCR_STREAM_WRITE_ERRORS` build, selected by the new `streamWriteErrors`
runtime feature (rule: any `writable.writeEcb`). Every other build
compiles `SCR_WCB_CALL` to the same plain call, so their objects are
unchanged.

The destruction path also matched Node: `afterWrite` calls `cb(null)`
even for a stream destroyed after the write was queued (Node 24
`internal/streams/writable`), so the destroyed `SCR_ST_AFTER_WRITE` tick
passes NULL rather than the stream's stored error.

Corpus: `tests/corpus/writable-error-callbacks.ts` covers success, an
erroring write, a write after end, and a write on a destroyed stream.

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
Malte Ubl db617bd594 Merge remote-tracking branch 'origin/main' into fix/pi-conversions-layouts 2026-10-28 18:30:14 -07:00
Chris Tate e0c692593c Evaluate a for..of source beside never[] once, keep collection views through a second view, and combine typed promises in untyped JavaScript
- lowerForOfEmptyArrayArm binds the `Set | Map | string | never[]` source
  to a hidden local before the tag test, so a call or getter in the loop
  head runs once and the loop walks the value the test saw.
- markTarget marks a structural view as a collection view when its source
  is already one, and reruns the fixpoint when a view marked earlier
  becomes a collection view, so a Map or Set handed through two views keeps
  answering through the collection.
- Promise.all in a JavaScript source whose call types as Promise<any[]>
  (an unannotated parameter) runs the engine's Promise.all over the entries
  as checked-dynamic values when they are typed promises. A merged
  `Promise<number | string>[]` argument reaches that call since union
  promise arms merge.

Corpus 8419 and 6956 and the js-own-tostring npm fixture cover each case.
2026-10-28 20:22:14 -05:00
Chris Tate d1abf69149 Refresh the runtime profile and convert ASCII case eight bytes at a time (#2492) 2026-10-28 20:15:03 -05:00
Chris Tate 84644dfe9d Send the writeHead head with the response's first write (#2488) 2026-10-28 19:18:26 -05:00
Chris Tate 7f0c987140 Key Sets and Maps of identity-arm unions by their pointer representation (#2487) 2026-10-28 19:08:20 -05:00
Chris Tate 61c24c08e9 Refresh the runtime profile and convert ASCII case eight bytes at a time
scr_str_case_conv scans ASCII text for the first letter to convert eight
bytes at a time, then converts the rest eight bytes at a time; the tail
and big-endian targets take the byte loop. With a refreshed profile, the
byte-at-a-time scan compiled worse for short strings with nothing to
convert, which made validate-errors slower.

The runtime profile is collected again on this change with
scripts/refresh-runtime-profile.mjs (326 raw profiles). Changed
functions hold 0.0% of its counts, against 3.7% for the previous one.

tests/corpus/string-case-ascii-words.ts covers ASCII strings of every
length from 0 to 40, with letters at block boundaries and next to the
bytes just outside A-Z and a-z.
2026-10-28 18:43:37 -05:00
Chris Tate 18ab17848e Keep the held head across a cork() and uncork() with no write
An uncork() flushed a head writeHead stored even when nothing was
written while corked. Node keeps the stored head until write(), end() or
flushHeaders(), so the flush now sends it only when a write() or
flushHeaders() ran while corked. The http-held-head fixture covers the
three cases.
2026-10-28 18:23:49 -05:00
Chris Tate 94e921728c Island handle operations, native subclasses of island classes, arrays and collections by reference (NI-5b/5c/5d) (#1903) 2026-10-28 18:09:56 -05:00
Chris Tate 7409c85216 Compile .length of computed tuple expressions (#2482) 2026-10-28 17:51:45 -05:00
Chris Tate 3a1ba89194 Throw for .length of a missing tuple element
An unchecked element read of a tuple array (`rows[i].length` with
`rows: [number, string][]`) answers the element's optional value, and the
computed `.length` path discarded it, so a read past the end printed the
tuple's arity. It now holds the receiver and throws Node's TypeError,
"Cannot read properties of undefined (reading 'length')", when the element
is missing, before answering the arity.

The corpus program tuple-length-computed.ts covers a missing element read
directly and through a field holding the array.
2026-10-28 17:04:51 -05:00
Chris Tate 94215d6f0e Keep glibc's memset, memcpy, memcmp and bcmp in zig-linked executables
The BigInt reciprocal divided a two-limb value by one limb, which compiles
to a __udivti3 call. zig provides that builtin from its compiler-rt, a
single object that also defines memset, memcpy, memmove, memcmp, bcmp,
strlen and the _chk variants. Linking it for __udivti3 made those
definitions take precedence over libc's in every glibc executable built
with SCRIPTC_CC=zigcc, and zig's bcmp and memcmp compare one byte per
iteration and its memset stores one byte at a time.

The reciprocal now divides in 32-bit halves with one-limb operations, so
the runtime references no compiler-rt builtin and glibc executables import
the four functions from libc.
2026-10-28 17:01:19 -05:00
Chris Tate 2814b57e20 Send the head writeHead stores with the response's first write or end()
res.writeHead() builds the head and keeps it until the first write(),
end() or flushHeaders(), which sends it with that call's data in one
socket write, as Node's _storeHeader does. A response with an Expect
header sends the head at once, as in Node. The http-held-head server
fixture compares the wire timing with Node's.
2026-10-28 16:35:39 -05:00
Chris Tate 6cc5345a41 Key Sets and Maps of identity-arm unions by their pointer representation
A union that is a Set element or a Map key was a tagged box in the whole
program, because the C map hashes and compares union keys by tag and
payload. One `Set<Node | undefined>` made every `Node | undefined` value
of the program a box, at every read.

A union whose arms all compare by identity (records, class instances,
arrays, functions), beside at most one undefined and one null arm, now
keeps its pointer representation in collections too. The map keys such a
union by reference: the pointer carries the identity, and NULL and the
null sentinel are two more distinct keys. Unions with a string or another
value arm keep their boxes, and library builds keep the earlier rule.

The tests of the box paths keep their unions boxed through a new
`boxedUnions` emission option instead of a Set of each union.
2026-10-28 16:22:38 -05:00
Chris Tate 3a15562085 Follow a redirect whose chunked body has not ended (#2484) 2026-10-28 16:06:56 -05:00
Chris Tateandlinguofeng 46058c67a2 Report an aborted client response's error to its 'error' listeners only
A client response cut off mid-body emitted 'error' ("aborted") whether or
not it had an 'error' listener, so a response without one ended the
process with "Unhandled 'error' event". Node's IncomingMessage reports its
destroy error only to 'error' listeners; 'aborted' and 'close' still fire.

fetch leaves a redirect response by destroying its request, so following
a redirect whose chunked body had not ended crashed the process before
fetch settled. It now follows the redirect.

Fixes #2395

Co-authored-by: linguofeng <460328+linguofeng@users.noreply.github.com>
2026-10-28 15:19:07 -05:00
Malte UblandClaude Opus 5.5 a148177432 Check recursive records coinductively, not by refusing the back-edge
canDynCheckTo answered false at every recursion back-edge in a record or
array, so a type whose checked graph re-enters itself had no checked
extraction at all. That is ordinary in a callback-options API: a context's
method takes an options object whose callback receives the same context, so
passing an unknown value into the record raised SC1100 even though every
leaf is checkable — pi's LoadExtensionsResult graph (extension commands
whose handlers take the extension context) is the census shape.

Answer the back-edge coinductively, as the composite dynFrom walk already
does for recursive shapes: mark the in-progress key (the wrapper's own
success key) for the duration of the frame and verify every reachable field
at its first visit. A field that fails anywhere still fails the frame, and
the existing failure path keeps dropping dependent conversion results, so a
cycle cannot hide an unsupported sibling. The `unsafe` unit fixture now
carries a genuinely unsupported sibling; its former Map sibling is inside
the check domain, so the back-edge refusal was what made it look unsafe.

Only the static conversion domain widens: a program that compiled before
compiles to the same code, and the runtime extraction still walks the
actual value, throwing a catchable TypeError on a mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:17:19 -07:00
Malte UblandClaude Opus 5.5 d4041d6b25 Box promises nested in records, arrays and unions as dyn values
canBoxDynCompositeAt, the composite extension of the dynFrom domain, had no
promise case: it fell through to the runtime-handle test and answered false,
so a typed record with a Promise-typed field could not convert into an
unknown slot (SC1101). That is the shape of a model store's read state
(`reload?: { readers: number; promise: Promise<StoredModels> }`), and the
same fence blocked a promise in an array element or a union arm.

The backend already knew how to box one: toDynHelper emits
scr_dyn_new_promise for a dyn payload and scr_dyn_new_promise_adapting for
any other inner type, exactly as it does for a bare promise. Mirror that
conversion domain in the predicate — dyn and void inners, or an inner that
converts to dyn — so the promise boxes by REFERENCE wherever the walker
meets it and identity survives the round trip, as in Node.

The corpus program pins the round trip both ways: the payload, the promise
identity, and the absent-field case, under Node and natively.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:17:19 -07:00
Malte UblandClaude Opus 5.5 54cd19e52a Walk a for..of over an iterable beside never[]
`for (const name of registry.get(key) ?? [])` (mcp's server-tool registry)
types the head `Set<string> | never[]` and had no lowering: the iterable
union rule knows Iterable/generator unions and the indexed numeric arms, so
the loop fell through to the generic SC2001 fence on the union type.

`never[]` is the empty array literal's own type: its only value is the empty
array, which yields no iterations. `T[] | never[]` reduces in the checker
(never is assignable to T), so the reachable shape is exactly one inhabited
non-array iterable beside `never[]`. That loop is the existing container walk
on the arm the runtime tag selects:

    if (unionIsTag(head, setArm)) { <lowerForOfSet on unionNarrow(head)> }

for Set, Map and string — no new representation, and the empty-array arm is
an empty `else`. A union of two inhabited iterables
(`Set<string> | string[]`) still fences: both arms hold values and their
walks yield different element kinds through different protocols.

Corpus 8416-iterable-never-array-arm pins Set/Map/string registries with
present and missing keys, a live set mutated between loops, and an explicit
`Set<string> | never[]` annotation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:17:18 -07:00
Malte UblandClaude Opus 5.5 c3954d4dd4 Give a .map callback that answers a closure an ordinary function element
`entries.map((entry) => async () => …)` (package-manager's concurrency task
lists, `(() => Promise<…>)[]`) was refused: `.map` rejected a `func` return
with the same badType as a `void` return.

A closure is a supported array element. The closure array literal `[() => 1]`
already lowers to a `func`-element array, where the value is one ScrClosure
for its whole life, stored by reference with the element kind's `_v` adapter
and `scr_closure_trace_v`. The map helper builds the same array, so the
`func` half of the fence goes at both `.map` sites (plain receiver and
union-of-arrays receiver).

The `void` fence stays: `void[]` has no element, because a void return value
is erased before it could be written.

Corpus 8415-map-closure-elements pins the `.map`-produced arrays of closures:
`Array<() => Promise<string>>` and `Array<() => string>`, closure identity
through `indexOf`/`includes`, an inline `.map`, and
`Promise.all(tasks.map((task) => task()))`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:17:13 -07:00
Malte UblandClaude Opus 5.5 ea23feeb21 Merge a union's promise arms into one promise of their payload union
`void | Promise<A> | Promise<void> | Decision` (the agent-loop `finishTurn`
hook, codemode's remote client) had no representation: a union arm's index is
its runtime tag and a promise arm's tag IS the `instanceof Promise` test, so a
second promise arm had no tag. The union was refused (SC2009) and every use
site inherited the blocker.

JavaScript cannot tell the arms apart: `instanceof Promise` answers true for
both and `await` settles to either payload. The union therefore maps to one
promise arm `Promise<A | undefined>`: payloads join like union arms (`void`
becomes the undefined unit, an already-union payload flattens, identical
payloads dedupe, a checked-dynamic payload absorbs the arms the dynamic
representation holds faithfully). A payload with no union arm home (a RegExp
payload, a promise-of-a-promise) keeps the whole union fenced, and the
refusal now says why.

A `Promise<A>` value flowing into the merged arm converts with the existing
checked promise-payload widening (promisePayloadWidens / promiseWiden): the
promise keeps its identity and its settlement turn, and the settled payload
is checked against the payload slot.

Cost: the merge only runs for a union that already has two promise arms,
which is refused today. Every other union interns the same arms in the same
order, so a fully typed program without such a union compiles to identical
IR.

Corpus 8414-promise-union-arms pins identity through the union slot,
`instanceof Promise` narrowing, `await`, `.then`, the void arm, the rejection
path and a non-promise data arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:17:05 -07:00
Malte UblandClaude Opus 5.5 ab5b603e1f Keep the error itself when an interface extends Error
`interface HttpError extends Error { status: number }` receives `new Error(...)`
asserted to it. The error's fields are writable and later writes must show
through the view, so the view is the error object, not a record built from it.

A lib Error, TypeError, ... instance now carries identity into the structural
view, the same way a program class instance does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:16:21 -07:00
Malte UblandClaude Opus 5.5 b716775730 Keep a Map or Set handed to a record view as the collection
A Map or Set is a live collection: a record copy answers the members it copied
once and misses every later write, so `function freeId(seen: { has(k: string):
boolean })` called with a Map must answer through the Map.

The collection now carries identity into the record view, and the view is
registered as a collection view: it keeps checked identity without protocol
dispatch, because there is no stored member to read. Its method calls dispatch
on the collection at runtime (`scr_native_map_invoke`), and `size` reads the
collection's own member.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:14:47 -07:00
Malte UblandClaude Opus 5.5 8e495686e1 Resolve a named Pick or Omit alias to the class it views
`type Readable = Pick<Session, "read">` is the same live view of a class
instance as the inline `Pick<Session, "read">` spelling. Only the inline form
was recognized, so a view named through an alias became a record copy: it
dropped the class's methods and lost the instance's identity.

The resolved type of a program alias is the alias itself, so the reference is
read from the alias's declaration instead: `Pick`/`Omit` keep their first type
argument, and another alias is followed through its own declaration, bounded
in depth. `originsOf` resolves the view the same way, so the class family
behind a named view is found too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:14:12 -07:00
Chris Tate ee30477142 Keep small listener snapshots of readline and EventEmitter emits on the stack
readline's emit and EventEmitter's emit copy the listener list before
running it, so that listeners added or removed during the emit don't
change who runs. The copy was a malloc and a free per emitted event: per
line for a readline 'line' listener. A list of at most four listeners is
now copied into a stack buffer; longer lists still allocate.

A new corpus program pins the snapshot semantics for lists below and above
four listeners: a listener added during an emit runs from the next emit, a
removed one still runs in that emit, and a once listener runs once.
2026-10-28 15:07:25 -05:00
Chris Tate d83a5dc929 Fix census WP-2 stdlib/@types sites: void-slot lambda ABI, ChildProcess.off/signalCode, writeFileSync const options, path sub-namespaces, stdout writableLength (#2468) 2026-10-28 15:04:48 -05:00
Chris Tate e80b5f1447 Compile .length of computed tuple expressions
`.length` of a tuple is its arity, and the lowering folded it to that
constant only when the receiver was rooted at a binding or `this`. Any
other receiver (a call result, a ternary, an `as` assertion, an `as
const` literal) was refused with SC1090.

The receiver is now evaluated once, for its side effects and exceptions,
and its value discarded; the expression is the arity. A binding, `this`,
or a chain of plain property reads from one still folds. A chain through
an accessor is evaluated, so an accessor that returns a tuple runs, as in
Node.
2026-10-28 14:57:21 -05:00
Chris Tate 1edb46545a Lower more pi constructs: tuple and keyed reads, mixed union fields, spreads, assertion probes (#2476) 2026-10-28 14:51:13 -05:00
Chris Tate 4066cf1d1a Lower Object.getOwnPropertyNames over records and Array.prototype.join with a dynamic separator (#2475) 2026-10-28 14:44:07 -05:00
Chris Tate 7e2e47aa8d Report unhandled rejections in programs without async work (#2479) 2026-10-28 14:13:38 -05:00
Malte UblandClaude Opus 5.5 cbd26d3a11 Merge remote-tracking branch 'origin/main' into fix/pi-lowering-constructs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:01:49 -07:00
Malte UblandClaude Opus 5.5 d25f3ba44b Evaluate effectful receivers and keys once in the new lowerings
The keyed and tuple-index dispatches read the receiver and key inside a
discard statement, which re-evaluated an effectful receiver expression
(an `in` test over a call result) and ignored a key call that rebinds the
receiver binding. Both now settle the receiver into a hidden local before
the key evaluates, and the inherited-key probe returns its answer
directly instead of wrapping it in a discard statement.

Narrow the union field write's no-static-supertype path to unions that
have a dynamic arm, so a union of unrelated static storage types keeps
its refusal, matching the read path; a runtime-keyed spread into a record
is covered by the checked extraction test instead of the refusal table.

Review threads:
- 4403396631 inherited-key probe evaluated an effectful receiver twice
- 4403396650 key/index calls that reassign the receiver binding

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:01:33 -07:00
Malte UblandClaude Opus 5.5 de7f40e87a Merge remote-tracking branch 'origin/main' into fix/pi-wp6b-stdlib-tail
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:57:03 -07:00
Malte UblandClaude Opus 5.5 81580f44ad Merge remote-tracking branch 'origin/main' into ni/i6-subclasses
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:56:30 -07:00
Malte Ubl 7dcf1376b6 Merge remote-tracking branch 'origin/main' into fix/pi-lowering-constructs 2026-10-28 11:40:29 -07:00
Chris Tate ad368e353d Lower Response.clone and Request.clone on typed values (#2473) 2026-10-28 13:37:52 -05:00
Chris Tate f61c40e4e7 Space the timer delays in two order-sensitive tests and raise the static-spatial timeout (#2478) 2026-10-28 13:36:39 -05:00
Malte UblandClaude Opus 5.5 031b46d2b7 Evaluate the stream receiver before the writableLength constant
A procStream-typed receiver that is a call or an accessor read now runs
for its effects, and propagates its throw, before the fold answers 0: the
constant sits in a seqExpr behind an exprStmt receiver, the same
evaluate-for-effect shape objectIsDisjointFalse uses. A pure alias still
folds to the bare constant. The corpus program covers a call receiver, an
accessor receiver and a throwing receiver against Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:33:39 -07:00
Malte UblandClaude Opus 5.5 753532a3da Merge origin/main into fix/pi-wp2-stdlib-node
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:33:39 -07:00
Chris Tate 911857561e Read typed-array constructors, NaN and Infinity in shorthand properties (#2477) 2026-10-28 13:26:13 -05:00
Chris Tate 7bb87229c2 Report unhandled rejections in programs without async work
A program with no async functions, generators or timers never ran the
event loop, so a promise rejection nothing handled went unreported:
`Promise.reject(new Error('x'))` alone exited 0, where Node prints the
error and exits 1. The loop now also runs when the program makes a
promise (a Promise static, `new Promise` or Promise.withResolvers), and
its end reports the rejection, runs the 'exit' listeners with code 1 and
exits 1, as for async programs. Programs that make no promise keep their
code unchanged.
2026-10-28 13:23:10 -05:00
Chris Tate 55a803c733 Read the array's length before converting a join separator
Array.prototype.join reads the length before it converts the separator.
The lowering converted the separator first and let the join read the
length afterwards, so a separator whose toString changed the array joined
the wrong number of elements. When ToString of the separator value can run
code of the program (a checked-dynamic value, an object or class instance
with toString), the join now reads the length first: an array that grew
joins its first elements, and the missing elements of one that shrank join
as empty strings. Primitive separators keep the direct join.
2026-10-28 13:07:14 -05:00
Chris Tate 577f086658 Space the timer delays in two order-sensitive tests and raise the static-spatial timeout
mock-timers' "named and namespace imports keep the loop's timers" and
6811-node-island-uncaught-listener check the order in which real timers
fire, with delays 1 to 5 ms apart that are scheduled one after another.
On a busy runner more time than that can pass between scheduling two of
them, and the order flips in either lane: the sanitized node:test run saw
Node's reference print 'named promise' first, and the sanitized
llvm-differential run saw 'island timer 1' before 'native timer after'.
The delays are now 100 ms apart in mock-timers (100, 200, 300) and the
island's timers (150, 250) start 100 ms after the native ones (10, 50).
Node prints the same output as before.

static-spatial bundles three.js and builds it with the sanitizer, which
passed the default 300 s test timeout on a busy runner. Its cases take
three.test.ts's 1200 s timeout.
2026-10-28 12:49:21 -05:00
Chris Tate 5a1021dcd1 Fence Response.formData in the node-types fenced fixture
Response.clone lowers now, so the fixture's unlowered Response
operation is formData, which keeps the same fence and hint.
2026-10-28 12:42:13 -05:00
Chris Tate 2f3b926de8 Share modules between a program and its Node islands through loader hooks (NI-6) (#1902) 2026-10-28 12:41:52 -05:00
Chris Tate 68980c2414 Box user module namespaces as dynamic values (#2469) 2026-10-28 12:40:34 -05:00
Chris Tate 857b4ad3e0 Read typed-array constructors, NaN and Infinity in shorthand properties
A shorthand property's name carries the property's symbol, so the
identifier lowering's typed-array and NaN/Infinity cases, which checked
that symbol's provenance, did not match `{ Uint8Array }` or `{ NaN }`. A
JavaScript source stored the opaque identity token there, and TypeScript
stored a runtime fence; the long form `{ Uint8Array: Uint8Array }` read the
constructor. Both cases now resolve the name as the other builtin cases do
(isStdlibGlobal), which reads the shorthand's value symbol.

In JavaScript sources, `globalThis.NaN` and `globalThis.Infinity`, and
destructuring them from the global object, read the identity token too.
They now read the numbers.
2026-10-28 12:38:39 -05:00
Malte UblandClaude Opus 5.5 a989612ce7 Record the island case's unresolved import() fence as a divergence
The stale i6 branch marked lazy-traps island-only, which held while the
whole package was refused and ran in a Node island. main compiles the
package statically with a throwing stub per blocked edge, so its require()
edges still match Node through an island while an import() whose literal
specifier resolves nowhere is a deferred SC2012 fence. Record that
remaining gap the way main does instead of claiming Node parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:38:22 -07:00