Main's FinalizationRegistry took handle tag 72, so the Node island
handle moves to 73. Island handles keep their instanceof path in the
new scr_bytes_instanceof_at.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records are width-tolerant, and a union's arms were tried in canonical
order, which sorts record arms by shape id. When a `{}` arm, or an arm
whose fields are all optional, came first, it took every object, so a
value converted to the union lost the fields only a wider arm has:
const pairs = [["a", { publicKeyEncoding: 5 }], ["b", {}]];
for (const [, v] of pairs) console.log(Object.keys(v));
printed [] twice, where Node prints [ 'publicKeyEncoding' ] and [].
A checked cast to `{ p: number; q: string } | { p?: number }` dropped
`q` the same way.
The checked conversion now tries record arms with more required fields
first, then those with more fields, and keeps the canonical order
otherwise. The union's tags are unchanged.
The test refused unchecked-dependency only because its one-line index.js
looked minified. A dependency another package's JavaScript imports needs
no declarations of its own (npmStaticIneligibleReason's untypedDependency),
so with the minified refusal gone it compiles; the test now builds it and
checks the library's result.
The key-order witness treats records JavaScript builds as having a run-time
order. Two cases this change narrowed are counted again:
- A native callback that a checked JavaScript function finds inside a
record or array it receives is called with records JavaScript builds. The
walk of a checked function's parameters reset to the built direction at
the fields and elements of the records it passes out, so the callback's
own parameters counted as passed out. Children of a container passed out
now count in both directions (written by JavaScript, and read out of it),
and class instances keep the direction too.
- A replaceable method's checked result builds a record when a replacement
returns one. It is left out only while every site the census found for
the method name reads it or assigns a function whose return type is
primitive, as the handlers a router class installs through a computed key
do. The census now records every such site (Lowerer.methodAccessSites).
Refs #1777
An identifier-minified esbuild bundle renames the __commonJS helper
(var j=(u,t)=>()=>(t||u((t={exports:{}}).exports,t),t.exports)). The
recognizer already compared the helper's shape independently of its
parameter names, but only used a renamed match to refuse the package.
A stable top-level declaration with the helper's shape now splits the
bundle's chunks like the named helper, so lru-cache's CommonJS build is
no longer refused.
ToNumber and ToString of a foreign element are reached through a table
that scr_arr_set_foreign installs, so a program that never stores a
foreign element no longer links the dynamic tier through the array
runtime's joins, sorts and numeric reads.
Only number[] and string[] store a value written through a dynamic view
that their element type cannot hold. Other element types keep the
checked conversion's TypeError at the write. Counting every viewed
element type as possibly undefined made callbacks over records whose
arrays reach a package (zod3-infer) take optional elements and refuse
their spreads.
The EventTarget method values are cached for the program, and exit
released them only when an EventTarget, an event or a listener had
registered the unit's teardown. A program that read an AbortSignal's
addEventListener and never added a listener or made a target left the
cached functions live, and RC-audit builds exited with 99. Caching a
method value now registers the teardown too.
Refs #1777
npm packages are no longer refused up front for minified JavaScript. A
bundle whose interop helpers lost their names is still refused by the
bundle recognizers, and a package whose attempt fails is still refused on
its own.
A `var` read in a later declarator of the statement that stores it
(`var a = {...}, b = {...a}`) runs after the store. A read in a function
body is now located at the body's own statement instead of the body, so
JavaScript `var` bindings read after their store in a function keep their
type instead of gaining undefined.
`replace` and `replaceAll` with an untyped search value and a callback
call the regex callback replacement for a RegExp and the string-search
helper otherwise. That helper now accepts untyped callback parameters,
passes undefined to parameters after the subject, and converts number,
boolean and untyped results with ToString.
In JavaScript, `||` and `&&` over primitives of different types whose
result tsc types `any` evaluate in the checked domain.
An AbortSignal answered addEventListener, removeEventListener and
dispatchEvent read as values with functions of its own, which only ran on
AbortSignal handles: calling one on a plain EventTarget threw
ERR_INVALID_THIS. In Node they are EventTarget.prototype's methods,
shared with every target. The signal's property read now answers the
EventTarget method values (scr_event_target_method_value), which run on
any EventTarget receiver, AbortSignal included, so
signal.addEventListener === new EventTarget().addEventListener holds.
The signal's own method table keeps throwIfAborted.
The declaration boundary projected a user-declared interface as plain
data even when it extends a package interface. effect's Request.tagged
constructors create such values (interface Double extends
Request.Request<number>) with a prototype, so the boundary cast threw
"expected a plain object (this value has a prototype)". A user
interface now projects only when every interface it extends is
user-declared too; other values keep their JavaScript-world typing, so
the package finds its prototype members when the value goes back to it.
- With 'readable' listeners the stream stays paused, as in Node: 'data'
listeners, resume() and a parked for-await read no longer keep the loop
reading once the held input reaches the 64 KiB mark (or a larger
read(size)); reading resumes when a read drains it.
- Held input is a list of chunks with a running length, so an arrival
appends without copying what is already held, and read() joins only the
bytes it returns.
- New event-loop case: 'readable', 'data' and resume() with no read() for
400 ms while 1 MiB arrives.
A child spawned from a listener that runs during a poll pass, such as a
'close' listener, can exit before the next pass fires its 'spawn'
listeners. The same pass's waitpid loop then settled it with 'spawn' never
emitted. Settling an exited child now fires its pending 'spawn' listeners
first, before its stream data and 'exit', as Node emits them.
Object.freeze of an array that another reference may still hold stopped at
compile time with SC2020, and freezing an array, Map or Set held in an
untyped value threw SC2020 at run time. Node freezes the object itself: a
frozen array refuses every later write through any reference with the
TypeError of the operation's first write, and a frozen Map or Set only
stops being extensible, since its entries are not properties.
- An array a program may freeze keeps a frozen bit in its storage flags.
An array type is freezable when a typed Object.freeze names it, or, in a
program that freezes untyped values, when a value of it can be held as a
live untyped reference. The writes of those types test the bit with the
guard published arrays already use; other array types are unchanged.
- The guard's errors follow Node for every operation: splice, fill,
copyWithin, sort and reverse throw at their first write, and do not
throw when they would write nothing. Published arrays get the same
errors, and a statement splice of a published array is now guarded.
- A Map or Set records that it is not extensible, for Object.freeze, seal
and preventExtensions, typed or untyped. Object.isFrozen, isSealed and
isExtensible report it, and its entries stay writable.
- Writes to a frozen array through an untyped value are refused with the
same errors, as are writes to frozen checked arrays and objects, which
now name the property and the object as Node does.
- Programs that neither freeze nor publish arrays compile to the same IR
and LLVM.
Corpus 7406 covers typed and untyped freezes of arrays, Maps and Sets,
every array write on frozen arrays of zero, one and three elements,
comparator sorts, nested arrays, and frozen arrays that are released, and
matches Node.
Refs #1374
Co-authored-by: blurname <50226652+blurname@users.noreply.github.com>
`other.#x ??= value` (and `||=`, `&&=`) on a receiver JavaScript leaves
untyped read and wrote an ordinary property named "#x" instead of the
brand-checked private field, so the field kept its old value. Hono's
RegExpRouter numbers its parameter nodes with `next.#varIndex ??=
context.varIndex++`; without the stored index its regular expression lost
every parameter capture, and `c.req.param("id")` answered undefined.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A literal that starts by spreading a value of its own type, such as
`{ ...current, ...patch, updatedAt }`, made the key-order witness give up
on the type: the copy's source order was the type's own order, seeded
with the declared table, so it conflicted with the literals that build
the values. While no checked conversion builds the type, every value has
the one chosen order and the copy adds no witness.
Checked conversions also counted as builders of records they cannot
create: a class instance's field records, a checked function's record
parameters, a boxed primitive (the undefined a dynamic construction
passes for a missing argument), and the result of a typed call of a
method the program may replace on its instance, which without a
replacement is the method's own native value boxed and checked back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
generateKeyPair tested its callback, and a function in the options
position, by the checked-dynamic kind, so a Proxy whose target is a
function threw ERR_INVALID_ARG_TYPE. Node checks typeof, which answers
"function" for such a Proxy. Both tests now ask whether the value is
callable.
A JavaScript class's prototype method called with a receiver that is not an
instance of its class (`C.prototype.m.call(other)`, or an object that
inherits the method) now behaves as in Node when it uses private members or
calls super methods, instead of throwing "expected C" for the receiver.
- Private fields keep their brand check: reading or writing `this.#x` throws
"Cannot read private member #x from an object whose class did not declare
it" (or the write message), and `#x in this` answers false.
- `this.#m()` brand-checks the receiver through a new `call` use of the
private brand helpers and throws "Receiver must be an instance of class C"
(V8's wording: the class's own identifier, "anonymous" without one, and
"Cannot read properties of undefined (reading 'C')" for a nullish
receiver). It also applies to private method calls on other
checked-dynamic values.
- `super.m(...)` looks the method up on the base class's complete prototype
object and calls it with the receiver, as `super` reads
[[HomeObject]].[[Prototype]].
Methods that use `super` in other positions, static methods with private
names, and classes whose base chain includes a function-local, generic,
mixin, Date or runtime-provided class keep the native receiver check. The
receiver-independent method lowering and the prototype reflection now mark
their frames as `this` boundaries, so lowering one class's members on top of
another method's frames (a super call reflecting the base) never reaches the
caller's `this`.
- TypedArray indexOf, lastIndexOf and includes on every element kind, typed
and checked-dynamic: strict equality (SameValueZero for includes) and
fromIndex as ToIntegerOrInfinity. A Buffer reached through a Uint8Array
value keeps Buffer's search.
- process.stdin 'readable' with read() and read(size), including sizes
above the read-ahead mark and input a once('readable') listener leaves
for a later 'data' listener. for await over process.stdin yields Buffers.
- Module bindings holding `new F()` of a constructor function can be read
and reassigned from other functions.
child_process and http2 events ran with no store, or with the context of
whichever socket dispatch delivered them. Node runs them in the context
of the handle or request that produces them.
child_process: a child, its piped stdout, stderr and stdin, and its IPC
channel keep the context of the spawn() or fork() call. 'spawn',
'error', 'exit', 'close', stdout's and stderr's 'data' and 'end', and the
channel's 'message' and 'disconnect' run in it, in the POSIX and the
Windows implementations. stdin's 'drain' and 'error' run in the context
of the latest write() call, its 'finish' in the context of its end()
call, and a send() callback in the context of its send() call.
http2: a session keeps the context it was created in, the
http2.connect() call for a client and the server's listen() context for
a server session, and the session's socket callbacks and deferred emits
run in it. A client stream keeps the context of its request() call, and
the frames that drive a stream's events (DATA, WINDOW_UPDATE, RST_STREAM,
and a client's HEADERS) and its deferred emits run in the stream's
context. A session remembers the stream its last lookup by id found, so
the context lookup and the frame's own handling share one search.
A server stream and its compatibility request and response run with no
store, as Node's do. An accepted h2c socket's own events run with no
store, as an accepted net socket's do, so a server.close() that waits for
its sessions calls back with no store; a TLS server's sockets keep the
listen() context, as before.
Corpus 7385 to 7389.
Untyped code calling a method on an instance of a compiled JavaScript class
whose own properties live in a property bag materialized the instance's
whole checked view on every call (copying the bag, boxing each field and
attaching the prototype) only to read one member. Hono's Context declares
its methods as arrow-function fields, so every c.header() or c.json() from
middleware rebuilt an 18-property view.
The materializer of such a class gives the view exactly the bag's
string-keyed properties, the declared fields among them with their native
slot values, and the class's prototype. The compiler now emits those parts
as ScrDynTypedBagClassOps (bag, prototype, declared field names, field
getter) and binds them to the class's capsules, and scr_dyn_invoke resolves
a member that is an ordinary data property, own or inherited, from them,
calling it with the capsule as this. Accessors, read-only fields, capsule
prototype links and everything else still build the view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>