The copy of a dense-array, byte-window, counted, callback or typed-array
versioned loop that runs when its guard fails no longer gives its loops
speculative integer or int32 kind copies, which emitted each loop again for
a path that rarely runs. Proven integer loops, which emit one copy, are kept
in it.
m.set(k, f(m.get(k))) and a get followed by a set of the same key, with a
number or boolean value, now reuse the entry the read resolved, as
string-keyed maps already do: the write overwrites the value in place
when the map is the same and the key has the read's bits, and takes the
ordinary set otherwise. Before, the write probed the table again: the two
lookups were not merged, because each sits behind its own direct-index
test.
A mapped type with no members was refused (SC2001) to keep `Partial<T>`
inside a generic body, where keyof T is still unknown, from interning a
wrong empty record. That also refused mapped types the checker resolved
from concrete inputs whose key filter keeps no key: `Record<never, T>`,
`Partial<{}>`, and a catalog type such as
`{ [K in KeyOf<Data, "image"> as Id<K>]: Model }` for data without image
models. When the mapped type comes through an alias whose arguments mention
no type parameter (or a module-level alias without arguments), it is the
empty object type and lowers like `{}`.
pi-ai declares its per-provider image and classifier model catalogs this
way; most providers have none, which refused 105 declaration groups.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`value instanceof Boolean` (or Number, String, BigInt, Symbol) died at run
time in JavaScript with Node's TypeError for a function without a prototype
object, because those constructors are compiled as plain conversion
closures; in TypeScript the right-hand side was refused (SC1090). A
compiled program never creates a boxed primitive (`new Boolean(...)` has
no lowering) and a primitive is never an instance, so the test is false
for every value. It now folds to false after evaluating the operand.
typebox's value guards (IsBoolean, IsNumber, IsString) test this way, so
pi's tool-schema construction and validation died at its first guard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A class field typed AbortController or AbortSignal (plain, optional,
nullable, or in an array) maps to checked-dynamic storage, and the
class-field rule refused it (SC1090) although the same handles already
live in locals, Map values, and closures. They now take the checked-dynamic
slot, like the fetch Headers, Request, and Response handles.
controller?.abort() on a nullable controller was refused as a method call
(also for locals): the static abort lowering ran only without an optional
chain. It now runs again once the chain has narrowed the receiver.
Found by pi's TUI Editor (autocompleteAbort: AbortController | undefined).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JSON.parse's SyntaxError messages were shaped like V8's but differed from
Node's in several ways:
- Every positional message now ends with V8's "(line L column C)", where
\n, \r and \r\n end lines.
- Positions are UTF-16 offsets, as in Node, not byte offsets.
- An object or array cut off after '{', a property name, a value or an
array element reports the token V8 expected ("Expected property name or
'}'", "Expected ':' after property name", "Expected ',' or '}' after
property value", "Expected ',' or ']' after array element") instead of
"Unexpected end of JSON input".
- After a comma in an object the message is "Expected double-quoted
property name".
- An unterminated string reports the end of the input, and a truncated
\u escape is a "Bad Unicode escape" there.
- A leading zero followed by a digit is an "Unexpected number".
- A misspelled literal names the first differing character, or the end of
the input.
- An unexpected character is named with the source as V8 does: whole when
it is under 21 units, otherwise ten units of context on each side; the
sources "NaN", "Infinity", "undefined" and "[object Object]" name only
themselves; a quote or a number there is an "Unexpected string" or
"Unexpected number".
The new corpus program 7993-json-parse-error-messages pins 76 inputs
against Node, including non-ASCII text before the error, line breaks, and
long sources with split surrogate pairs at the context edges.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reader that starts after the second chunk arrives reads both chunks in
one 'readable' event. On loaded test workers Node started that late while
the compiled program did not, so the two outputs differed. The first chunk
now waits 500 ms.
Reapplies #2017, which #2038 reverted after effect's observability-otlp
fixture crashed in plain builds. The crash was the candidate-buffer
corruption that the previous commit fixes: the cached property sites
change when the collector runs, and with enough environment variables a
pass buffered a string box that was later pooled while still buffered.
TypedArray.prototype.set with a SharedArrayBuffer view as its source or
target converted every element through a double, even when both arrays
had the same element kind. That cost about 5 ns per byte, and it changed
NaN payloads: a Float32Array signaling NaN came back quieted (7fa00001
became 7fe00001), where Node copies the bytes.
The same element kind now copies the source's bytes with the relaxed
shared stores. A source that overlaps the target range, a view of the
same buffer in a program without workers, is copied out first. Different
element kinds still convert each element.
An arm's field check can run a getter that changes the discriminant. The
checked cast now reads the discriminant again after each failed arm;
when its case changed, the tests continue arm by arm after the failed
arm, so the cast picks the arm the arm-by-arm tests pick.
The sanitized self-hosting native-frontend build needs about 15 GB by
itself: its clang compile of the whole frontend under ASan peaks at
10.5 GB beside a 3.8 GB compile in Node. In the 14 GB scope, with the
shard's other work running beside it, the kernel killed that clang
intermittently, so SC5004 reported clang's warnings and no error (SAN
16/20 and 19/20). The scope now caps RAM at 15 GB, still below the
runner's 16 GB, and may still swap past it.
A union parameter's proof covered the union, not its payload: narrowing it
and storing, returning or pushing the result, a narrowed nullish read, and a
store into a nullable-pointer field all counted as projections. That holds
for a boxed union, whose payload is a separate object. A record passed
through a nullable-pointer union is that payload, so forwarding it to such a
callee let a record in its caller's frame escape the frame.
A pointer-wrapped argument now needs a second fact from the callee's union
parameter: its payload stays in place too. The payload is in place when it
is only tested, narrowed for a field read (directly or through the checked
narrowing a field read of a `T | undefined` parameter compiles to), or passed
on to a parameter that is proved in turn. The facts that other arguments use
are unchanged.
Corpus 7663 has callees that push (directly and through a non-null
assertion), default, store in a global record field or a class field, or
pass on the narrowed record, and one that only reads it.
A JavaScript module binding whose initializer joins an untyped value with
a string, number or boolean through `||`, `&&`, `??` or `?:`
(`export const APP_NAME = pkg.appConfig?.name || "app"`) stayed a local of
the module's initializer, so reads from other modules and functions
failed with "a binding form with no lowering". Such initializers now
register the binding in checked-dynamic module storage, like the untyped
initializers they combine.
pi-coding-agent's config.js declares APP_NAME, APP_TITLE and isBundledNode
this way.
Refs #1777.
An untyped call of an array method went through the whole dispatch ladder
on every call: the receiver-kind tests for handles, proxies, typed refs,
objects, functions and primitives, then the array section's prologue
(the hole scan and the frozen, sealed and extensibility tables) before
the method itself.
A dense array without holes, named properties or a prototype of its
own, whose length and elements are writable, now runs push, pop, shift,
at, includes and indexOf directly from the call site's cached method id.
An argument that could run user code (a non-number index, a fromIndex)
still takes the ladder, as does every other receiver. A statement-position
push calls an entry that does not box the new length.
Calls on arrays that the direct path refuses now also follow Node:
- an array's own method, or one on its custom prototype chain
(Object.setPrototypeOf), runs instead of the builtin;
- push on a sealed or non-extensible array throws "Cannot add property
N, object is not extensible", and pop or shift on a sealed array
"Cannot delete property 'N' of [object Array]".
An `export * from` declaration binds each re-exported name to the binding
of the module that declares it, like a named re-export, so it has no
initialization state of its own. Import cycles whose modules re-export
another member of the cycle this way now compile with the exact cycle
initialization: re-exported names read the same values, and throw the
same temporal dead zone errors, as in Node. `export * as ns` of a cycle
member keeps the SC1016 refusal.
pi-coding-agent's sdk.js (sdk.js → agent-session-runtime.js →
agent-session-services.js → sdk.js) has this shape.
Refs #1777.
Object.getPrototypeOf of an Error answers Error.prototype when the error's view was built from its fields and its prototype is still the object the view was built with. After Object.setPrototypeOf(error, {}), it answers the object that was set.
A quotient x / Math.pow(2, n) divided on every evaluation, and a
division's latency bounds loops that carry the quotient, such as a key
that keeps its low bits with k - Math.floor(k / Math.pow(2, n)) *
Math.pow(2, n). A divisor that is a positive power of two from 2^-1022
to 2^1022 has a reciprocal that is a power of two too, whose bits are
0x7FE0000000000000 minus the divisor's, and the product with it is the
quotient for every dividend. The quotient now multiplies by that
reciprocal when the divisor's bits are such a power, and divides
otherwise.
Corpus 8180 compares the quotient with an ordinary division for every
integer exponent from -1080 to 1030, including divisors that are
subnormal, zero or infinite, and for exponents that are not integers.