Commit Graph
4353 Commits
Author SHA1 Message Date
Malte UblandClaude Opus 5.5 4f7c48b4e3 reflection: Reflect.has, and Object reflection over records
Reflect.has had no lowering. It is [[HasProperty]] — own and inherited
names (Array.prototype's, Object.prototype's, a class's compiled methods),
proxy `has` traps, symbol and property-key conversion — which is exactly
the `in` operator's checked-dynamic probe, so it lowers to dyn.hasKeyComputed
over the target's dyn view (and the target is fenced when it has no such
view). A primitive target is Node's TypeError, which the probe answers
false for instead: fenced by name, never silently divergent.

Object.getOwnPropertyNames/Symbols/Descriptors only accepted values that
already were checked-dynamic; a record, array or class instance now boxes
into its own dyn view (own fields and elements, in property order) like
the function and error targets already did.

Corpus 8426 pins both against Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:38:28 -07:00
Malte UblandClaude Opus 5.5 1a3caa88eb web/text singletons: URLSearchParams ToString, match reads, Array.from
URLSearchParams crossing an implicit ToString (`${sp}`, "" + sp, String(sp))
had no lowering: ensureString now answers sp.toString, the serialized pair
list. Record inits also accept shorthand members (`new URLSearchParams({ model })`)
like the explicit `{ model: model }` spelling.

`re.exec(s)?.index` keeps the optional chain: the read is the match row's
index/input member (the chain lowering binds the guarded receiver first),
so the questionDotToken guard no longer diverts it.

Array.from(s) over a string whose binding the checker narrowed through a
guard read as the narrowed union and fenced; the site type now decides, and
the read is coerced to that string (code points, fresh array).

Corpus 8423-8425 pin all three against Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:38:28 -07:00
Malte UblandClaude Opus 5.5 e59c117e3b Record CI costs for the new process and child-process corpus programs
The six new differential programs' plain and sanitized measurements, in
tests/harness/corpus-costs/<program>.json so the shard balancer weighs
them without touching the shared table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:38:27 -07:00
Malte UblandClaude Opus 5.5 b8b4a4251a Take spawn's detached option as a runtime boolean
`spawn(cmd, args, { detached: process.platform !== "win32" })`, the
portable daemon idiom, no longer refuses: a non-literal detached value
lowers into scr_spawn_opts's bool slot, the same POSIX_SPAWN_SETSID flag
the literal forms reach, so the child leads its own process group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:38:26 -07:00
Malte UblandClaude Opus 5.5 19dbebda67 Adopt an EventEmitter's type argument as the typing aid it is
`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>
2026-10-29 05:38:26 -07:00
Malte UblandClaude Opus 5.5 dda9f585e0 Expose the process streams' fd and stdin's raw-mode flag
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>
2026-10-29 05:38:25 -07:00
Malte UblandClaude Opus 5.5 e77e04c046 Lower process prependListener registrations, server.off, and optional child chains
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>
2026-10-29 05:38:25 -07:00
Malte UblandClaude Opus 5.5 55cbeac6c6 Pin the native-shim monkeypatching refusal boundary
pi's compatibility shims patch host objects — `process.stdout.write = fn`
to capture output, `.bind()` on a native function, `Object.defineProperty`
on a module export. A compiled binary has no host object to patch: the
natives are direct calls, so these sites keep an explicit refusal instead
of compiling to something that silently diverges from Node.

tests/diagnostics/native-shim-monkeypatching.ts pins the three shapes
(SC1090 assignment to a non-variable, SC2007 on a valued overload set,
SC2020 for defineProperty on an import), so the boundary is visible and
any future process-object model has to update it deliberately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:38:24 -07:00
Malte UblandClaude Opus 5.5 d414d518b3 Fold constant module specifiers in dynamic import() and import.meta.resolve
Module resolution is a build-time artifact, but only a string literal
counted as a static specifier: `const FS = "node:" + "fs"; import(FS)`
and `import("node:" + "path")` fell into the computed-specifier refusals.
pi's `packages/ai` env loader writes exactly that shape.

`staticString` now folds a literal, a cast or parenthesized form of one,
a `+` concatenation of those, and a `const` binding whose initializer
folds to one (depth-bounded); anything only known at run time still
returns null, so genuinely computed specifiers keep their refusal.
`import()`, `import.meta.resolve` and `createRequire`'s `resolve` share
it. A folded specifier carries no module symbol on its argument node, so
the own-module test and the import target fall back to the checker's
program import resolution.

Corpus 8502 pins both folded forms and the builtins they name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:38:23 -07:00
Malte UblandClaude Opus 5.5 1d7ddf7391 Lower ?. method calls on dynamic receivers and declared-optional capture receivers
`this.cache?.warm()` refused whenever the receiver lowered to a dynamic
value: the checked-dynamic receiver path treated any `?.` step as
unhandled chain work and bailed, even inside a chain body whose guard had
already proven the receiver present. Those steps are now gated on
`chainBlocked`, the canonical "is this `?.` still unhandled" test, so the
guarded body is the plain read-and-call it already is, with `dynCall`
throwing Node's catchable "x.y is not a function" for a present but
non-callable member. The string-only and primitive-method branches drop
their raw `questionDotToken` checks the same way.

A capture the checker itself types with an absent arm (`T | undefined`,
an optional property or parameter) also refused as a "complex" use in a
logical or conditional receiver, even though the union value flows into
the surrounding operator, which handles the absent arm itself. The use
now lowers to a plain read when both the checked type and the recorded
storage carry an undefined arm; a hidden absence keeps its refusal.

Corpus 8500 and 8501 pin optional method-call receivers and
`(entries ?? []).map(...)`-shaped capture receivers against Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:38:23 -07:00
Chris Tate 2ae08e4002 Route more packages and values through Node islands (NI-4c–4g) (#1904) 2026-10-28 21:53:40 -05: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 d4445ec628 Merge remote-tracking branch 'origin/ni/i6-subclasses' into ni/i7-routing
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:57:10 -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