Commit Graph
4679 Commits
Author SHA1 Message Date
Chris Tate e665913200 Print accessor properties of arrays and fence number keys in defineProperty
- An enumerable getter or setter on an array printed as undefined: the
  property loop rendered the accessor's unused data slot. It now prints
  [Getter], [Setter] or [Getter/Setter], as util.inspect does.
- Object.defineProperty on a live native array with a number key that is
  not an array index (-3, 1.5) skipped the numeric-property fence and
  lost the property on commit. A number key now takes the same fence as
  its string spelling.
2026-10-22 21:39:37 -05:00
Malte UblandClaude Opus 5.5 47f44c3dfd npm harness: cases that stay native under the fence rule keep their deferred-refusal expectations
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:38:49 -07:00
Chris Tate 17826afb23 Renumber the new.target corpus programs to 7560 and 7561
7362 and 7363 are taken on main by other programs. The costs and the
order-parity baselines move with them.
2026-10-22 21:37:39 -05:00
Chris Tate fe406429eb Keep int32 slots for long number arrays with checked integer stores
A local number[] candidate whose stores are exact integers but not all
proven int32s (a distance array updated with dist[v] = dist[u]! + 1), and
that is only ever allocated by new Array(n).fill(v), now holds SCR_ELEM_I32
slots when n is at least 4096. A shorter fill creates doubles, which fit a
typical L1 data cache. The first store of a value that is not an int32
widens the array to doubles in place (scr_arr_widen_i32).

An innermost loop that accesses such arrays tests their kinds once on
entry (backend/llvm/int32-kind-loops.ts). Its fast copy reads and writes
them as int32 arrays; a store that would widen one leaves for a copy that
re-executes it with kind tests and finishes the loop on doubles. When the
arrays already hold doubles, a copy accesses them as double arrays. Other
accesses test the kind each time.

Only these arrays take kind tests: every other array, and every program
without one, emits the same code as before.
2026-10-22 21:37:12 -05:00
Chris Tate 53dc4df990 Keep object and array literals that hold an evolving empty array on that array (#1929) 2026-10-22 21:35:22 -05:00
Chris Tate 1501559d4a Read and write the "undefined" property for element indexes that are undefined
An element index the checker types as a number can hold undefined at run
time (an element read from a holey array). JavaScript then names the
array's "undefined" property, but the index converted with ToNumber, so
such reads and writes used the "NaN" property: Node prints `10 0,7,0,7`
for a program that sets `values[NaN] = 10` and then writes through two
holes, the compiled program printed `7 0,7,0,7`.

The runtime's noncanonical property table, which holds the properties
named by non-index numbers, now spells one NaN payload
(scr_arr_undefined_key) "undefined". At array element reads, writes,
compound and update writes, destructuring targets and writes in value
position, an index that can be undefined is that key when it is
undefined. The property stays a typed entry of the array, so the Int32[]
contract checks cover it like any element store.

A typed array cannot hold the property: a write through an undefined
index throws the catchable SC1090 error where Node adds it.

Fixes #1640.
2026-10-22 21:34:06 -05:00
Chris Tate 1940e06304 Cancel streamed bodies on disconnect and type Response.json headers
A response whose client disconnects while its body streams now cancels the
body's stream when the response closes, as the package does, so the
producer stops instead of being read until it ends. A response that fails
closes too and is cancelled the same way.

Response.json(data, { headers }) with init headers is written from its
Headers, as the package's Response.json passes them on as a Headers object
with the JSON content type added when they lack one. Before, the cached
path wrote only the init record and sent no Content-Type.

The hono-node-server case covers both: Response.json with and without an
init content type, and a client that destroys the response after the first
chunk of an endless stream.
2026-10-22 19:32:49 -07:00
Malte UblandClaude Opus 5.5 9bf76af974 Build @hono/node-server 2.x into scriptc
@hono/node-server replaces the global Request and Response with
lightweight objects and reparents prototypes, tricks that only pay off
under V8 and that native compilation cannot follow. scriptc now compiles
the package from a built-in implementation (shims/hono-node-server/v2.ts)
over node:http and the native Request/Response/Headers: serve,
createAdaptorServer, getRequestListener and RequestError.

Responses are written the way the package writes them, byte for byte on
the wire: the general path (header record in Headers order, Set-Cookie
gathered, default content type, content length for bodies that finish
within the first reads, streamed bodies, error responses) and the
package's cached-response path for a Response that is still exactly its
constructor arguments. The runtime tracks that lightweight state on each
constructed Response (header form, raw plain-record headers, body length;
cleared by body and metadata reads, turned into Headers form by a headers
read) and exposes it to the shim through scriptcResponseLight.

The declaration entry of a shimmed package may now be a .d.mts/.d.cts
file. The npm case hono-node-server drives buffered, streamed, cookie,
header-form, HEAD, error-handler and URL cases against the real package
under Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:32:49 -07:00
Chris Tate a2213edc6d Map unresolved JSDoc results of generic JavaScript calls as checked-dynamic (#1925) 2026-10-22 21:31:04 -05:00
Chris Tate e470edc111 Merge main 2026-10-22 21:24:30 -05:00
Chris Tate 33cf76d36e Keep object and array literals that hold an evolving empty array on that array
An object or array literal that held an evolving `const xs = []` between
writes of different element types (`const box = { items: xs }` or
`const rows = [xs]` after `xs.push(1)`, before `xs.push("two")`) took the
element type the checker gives the array at that point, so it held a copy.
`box.items.length` and `box.items === xs` printed `1 false`, where Node
prints `2 true`. Such a literal now builds as a checked-dynamic literal that
keeps the array, and an unannotated binding of it takes the checked-dynamic
type. This covers shorthand properties (`{ xs }`), nested literals, spreads
of a literal holder, aliases of a holder and names destructured from one.
Element reads through an alias or holder (`rows[0]`, `alias[1]`) read the
checked-dynamic value instead of checking it against the checker's type,
which lists only the elements written before the alias or holder was taken.

A shorthand property now counts as a read of the array when its reads are
compared, so `{ xs }` before a write of another element type makes the
array checked-dynamic too.

Fixes #1641.
2026-10-22 21:23:57 -05:00
Chris Tate 8b5e861770 Give each class one prototype in identity comparisons of unknown values
`Object.getPrototypeOf(a) === Object.getPrototypeOf(b)`, for two instances
held as unknown, compared fresh objects when their class has no prototype
data (no methods, or a subclass of Error), so it was false for instances of
one class. Each such class now has one stand-in object, created on first
use.

A subclass instance boxed at its base type answered with the base's
prototype, so instances of two sibling subclasses compared true. The answer
for a value boxed at a class now tests the class's subclasses, most
derived first, and answers with the prototype of the one the instance
belongs to.
2026-10-22 21:21:09 -05:00
Chris Tate 6115135d96 Dispatch new.target over generic subclasses and own static names
A constructor's new.target dispatch skipped generic class families, so a
generic subclass instantiated only in a body lowered later answered with
the base's name, and `new.target === Base` held for its instances. A
family now has its own arm, tested by its instanceof interval, which holds
every instantiation: `new.target.name` reads the family's name and
comparisons with a program class are false. Under an instantiation, the
generic subclasses of its family are candidates too. `new.target` as a
value refuses generic subclasses, which have no common class value.

`new.target.name` reads a class's own static `name` field, which replaces
its name (a subclass has its own name); a static method `name` or a
non-string field is refused.
2026-10-22 21:18:48 -05:00
Malte UblandClaude Opus 5.5 45c8c292af Native plain objects inherit Object.prototype directly in Node islands
A plain object a program passes into an island is an object-template
instance, which inherits from its implicit constructor's prototype object;
island code that checks for plain objects (jose's isObject:
Object.getPrototypeOf(o) === Object.prototype) rejected it. The instance now
inherits Object.prototype directly. Corpus 6826 checks it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:15:49 -07:00
Malte UblandClaude Opus 5.5 74f2c9754b Dispatch String-only method names on untyped receivers by runtime kind
A call of a String.prototype-only method name (trim, toLowerCase,
startsWith, ...) on a checker-untyped receiver was lowered as a string
call that throws unless the receiver is a string. A checked-dynamic
object can carry its own member of that name: zod's schemas assign
`trim` in their constructors, so `z.string().trim().min(1)` typed the
`trim()` result as a string and refused `.min` with SC1090. Untyped
receivers now take the runtime-kind dispatch that object-typed receivers
missing the member already use: a string runs the string lowering, any
other value calls its own member as Node does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:15:44 -07:00
Malte UblandClaude Opus 5.5 a9b1d15eea Map unresolved JSDoc results of generic JavaScript calls as checked-dynamic
A published bundle's JSDoc can name a type that only its .d.ts declares:
hono's bearerAuth carries `@template E` and `@returns {MiddlewareHandler<E>}`
without importing MiddlewareHandler. The checker types that result as its
error type, and a call of such a generic JavaScript function was refused
with SC2001 ("values of type 'MiddlewareHandler<E>' cannot be compiled").
The unresolved annotation is documentation, not a contract, so the call's
result is now checked-dynamic, as an `any` result of a TypeScript generic
already is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:15:42 -07:00
Chris Tate 107d773382 Pass zlib's flush sentinels to _transform as Buffers
flush() on a zlib stream writes a zero-length chunk that _transform
recognizes by identity and compresses with that flush kind. Since
writes of a typed array that is not a Buffer reach _write as a Buffer
over the same bytes (as Node's Stream._uint8ArrayToBuffer does), the
sentinel, created as a plain Uint8Array, reached _transform as a new
Buffer. _transform then used the default NO_FLUSH, so flush() emitted
nothing: corpus 5374-zlib-stream-flush-params decoded an empty prefix
where Node decodes "hello ".

The sentinels are Buffers, as Node's kFlushBuffers are (Buffer.alloc(0)),
so write() passes them through unchanged.

The npm fixture stream-shims, whose test expected a deferred SC1090, now
runs to a later deferred refusal, SC2020 (write completion callbacks
taking the error argument); its expectation names that code.
2026-10-22 21:14:00 -05:00
Malte UblandClaude Opus 5.5 b388156593 Make packages islands only for fences that surely fire; keep jose and run-node native without pins
A gap fence inside a function body may never run, so it no longer makes its
package a Node island under --node-islands=auto: only fences in top-level
module code and fences that need the JavaScript runtime do. The package's
island class still decides after a failed build. Typed packages like jose
stay native (DECISIONS.md, 2026-10-21), so the corpus pins of jose and
run-node are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:08:34 -07:00
Malte UblandClaude Opus 5.5 4b34943b1a Adopt closure arguments, add adoption, @types and failing-check cases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:03:00 -07:00
Chris Tate 4780e7ef22 Compare optional string index reads as code units
`s[i]` with an index the frontend cannot prove in bounds reads the
character or undefined: temps for the string and the index, a range and
integer test, and a string-or-undefined union. A lexer that tests
`src[pos] === " "` or `c < "a"` on `const c = src[pos]` compared that
union with a union-wrapped literal, or boxed it for a dynamic
comparison, on every character.

Tests of such a read against a literal of one UTF-16 code unit now
compare `i === Math.floor(i) ? s.charCodeAt(i) : NaN` instead.
Undefined, like NaN, equals no literal and fails every ordered
comparison, and charCodeAt reads NaN out of range. A read of plain
operands is evaluated in place; others keep the read's temps.

An immutable local initialized from such a read, and used only in
those tests, in tests for undefined and in a switch over one-unit
cases, holds the code unit. Tests for undefined become NaN tests.
Copies of the local, such as a switch's discriminant temp, follow it.
The code-unit reads are charCodeAt reads, so a loop version can cover
them.
2026-10-22 21:01:18 -05:00
Malte UblandClaude Opus 5.5 3041df387d Spread untyped iterables into Math.min and Math.max
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:59:56 -07:00
Malte UblandClaude Opus 5.5 3fff00e5f7 Fold navigator member reads under optional chains
zod 4 probes `navigator?.userAgent?.includes("Cloudflare")`. Node's
navigator global is never nullish and has no value form here, so the
optional step is the plain member read: `navigator?.userAgent` folds to
the same `Node.js/<major>` literal as `navigator.userAgent`, instead of
lowering the navigator object as a value and refusing at run time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:59:34 -07:00
Malte UblandClaude Opus 5.5 7f7e11a1a7 Read and write Error.stackTraceLimit through the Error constructor value
zod 4 saves and restores the limit through an alias,
`const E = "captureStackTrace" in Error ? Error : null`. The interned
Error constructor value now answers `stackTraceLimit` from the same limit
the static member and stack capture use, and numeric writes set it, so
`E.stackTraceLimit` reads 10 instead of undefined and `E.stackTraceLimit = 0`
reaches `Error.stackTraceLimit`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:59:34 -07:00
Chris Tate f3bb5d2cb1 Borrow the array of an inline Atomics operation from its local
emitInlineAtomicsOp read its typed array with emitExpr, so every Atomics
call on a local array retained the array before the atomic instruction and
released it after, two out-of-line calls around one instruction. The
array now goes through emitBorrowedInput: an unchanged, unboxed local owns
it across the later operands and the operation, and the instruction and
scr_atomics_op only read it. Fields, globals, captures and bindings an
operand can reassign keep their own reference.
2026-10-22 20:57:07 -05:00
Malte UblandClaude Opus 5.5 17bef8c03b Type user code from npm package declarations at a checked, identity-preserving boundary
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:54:55 -07:00
Chris Tate 5ed872b843 Index arrays with exact i64 products of int32 values (#1857) 2026-10-22 20:53:08 -05:00
Chris Tate 7b79d73a55 Store local number arrays of proven int32s in 4-byte slots
A local number[] that only compiled element operations reach, and whose
every stored element the int32 slot analysis proves an int32 (never -0),
is now stored as SCR_ELEM_I32: 4-byte int32 slots, with state bytes once
it holds a hole or undefined, as for every array. Element reads give
consumers an exact int32, so indexing with an element skips the double
round trip, and the slots take half the memory.

The runtime's dense slot accessors convert between the int32 slot and the
double, so its double-valued entry points serve int32 arrays unchanged;
growth, copies, fill and length changes use the 4-byte width.

ir/int32-arrays.ts proposes the candidates and inlines their element-reader
helper calls; the int32 slot analysis keeps the proven ones, and
selectInt32Arrays rewrites the module for exactly those, so a program
without an int32 array emits the same LLVM IR as before.
2026-10-22 20:52:38 -05:00
Chris Tate dcd86a17da Merge main 2026-10-22 20:52:05 -05:00
Chris Tate fc05167a29 Merge main 2026-10-22 20:50:35 -05:00
Malte UblandClaude Opus 5.5 d646fa70d0 Collect garbage cycles across the scriptc and V8 heaps (NI-8)
Node islands stack, part 11 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:56 -07:00
Malte UblandClaude Opus 5.5 00e88c2dfd Islands: CommonJS island requires, builtin-class handles, JavaScript widened bindings
Node islands stack, part 10 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:56 -07:00
Malte UblandClaude Opus 5.5 ec88372eba Defer island modules whose recorded load is deterministic (NI-7b/7d/7e/7f)
Node islands stack, part 9 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:55 -07:00
Malte UblandClaude Opus 5.5 55a69232b7 Exit cleanly with Node loaded and keep nested entries on Node's stack (NI-7c, NI-3b)
Node islands stack, part 8 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:55 -07:00
Malte UblandClaude Opus 5.5 0f41b7bc67 Island handle operations, native subclasses of island classes, arrays and collections by reference (NI-5b/5c/5d)
Node islands stack, part 6 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:54 -07:00
Malte UblandClaude Opus 5.5 0dcf0e95d3 Route more packages and values through Node islands (NI-4c–4g)
Node islands stack, part 7 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:54 -07:00
Malte UblandClaude Opus 5.5 5fa44d9243 Share modules between a program and its Node islands through loader hooks (NI-6)
Node islands stack, part 5 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:53 -07:00
Malte UblandClaude Opus 5.5 2f8e8b2b92 Defer evaluating Node island modules until the program uses them (NI-7)
Node islands stack, part 4 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:53 -07:00
Malte UblandClaude Opus 5.5 a1b6fda11c Run npm packages in Node islands: object handles and compiler routing (NI-4)
Node islands stack, part 2 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:52 -07:00
Malte UblandClaude Opus 5.5 106eccd4fd Pass native closures, promises and objects into Node islands by reference (NI-5)
Node islands stack, part 3 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:52 -07:00
Malte UblandClaude Opus 5.5 993486e4a4 Bound V8's stack scan by Node's own stack in Node islands
Node islands stack, part 1 of 11 (scriptc-perf/results/islands-prs/PLAN.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:49:51 -07:00
Chris Tate 3991a49094 Hold any later value in JavaScript var bindings
A JavaScript `var` took the checker's type of its initializer, which no
later write is checked against in a JavaScript file. js-yaml 4.1.1 failed
on every timestamp with "a 'string' value is not representable in the
target union": its `var fraction = 0` holds the fraction's digits as a
string while it pads them (`fraction = match[7].slice(0, 3)`). Every YAML
error also threw the "assignment to non-variables" fence instead of the
YAMLException, because its error mark is a `var` object literal that
gains a member (`mark.snippet = snippet(mark)`).

- A `var` whose writes the declared type cannot hold is checked-dynamic,
  by the rules `let` already uses (jsBindingHasOpenWrites), and so is a
  `var` whose object literal gains members or carries methods
  (jsDeclarationExpandsObject, shared with `let` and `const`).
- `+=` of a string onto a number or boolean binding (`width += "px"`)
  counts as such a write, for `var` and `let`.
- A JavaScript function that returns such a binding returns a
  checked-dynamic value (an async one a promise of one) instead of the
  initializer's type. A string binding that is only appended to stays a
  string.
2026-10-22 20:40:56 -05:00
Chris Tate 961aad6ac9 Keep the identity of records and class instances converted to dictionaries
A record or class instance converted to a string-keyed dictionary type
(`Record<string, V>`, `{ [key: string]: V }`) was copied field by field
into a native dictionary record, directly, through a dictionary parameter,
or through a `{}`, `object` or `unknown` binding or parameter. Writes
through the dictionary missed the original object, and the dictionary was
never `===` to it, where under Node it is the same object.

ClassProtocols now marks a dictionary value type when a dictionary of that
value type receives an existing object (a binding, member, element or
`this` whose type is a program class instance, a record with declared
properties, or a `{}`, `object` or `unknown` binding or parameter that
holds one), and the program writes through, compares or passes to a
whole-object observer a dictionary of that value type. usesCheckedIdentity
answers true for those dictionaries, so they lower to checked-dynamic
storage, which holds the original object. Dictionaries of `unknown` or
`any` values are checked-dynamic already and are not marked.

The dictionary case of SC6001 is removed, since these conversions now keep
the object.
2026-10-22 20:37:10 -05:00
Chris Tate fa843548a5 Keep Dates as time values where no use observes their identity 2026-10-22 20:36:52 -05:00
Chris Tate 8c67f04062 Throw Node's TypeError for class constructors called through call/apply (#1892) 2026-10-22 20:36:01 -05:00
Chris Tate 5966f23bd3 Keep the class-value call fence when a subclass declares a static call
`C.call(...)` through a class value typed as `Base` may run a subclass's own
static `call`, which Node calls instead of the class constructor. The
constructor-call lowering now applies only when no class the receiver can
hold declares a static `call` or `apply`: the class and its bases (any
static member of that name, including accessors, fields without
initializers and computed names), and, for a receiver that may hold a
subclass, every subclass. Otherwise the call keeps its existing
diagnostic.
2026-10-22 20:33:33 -05:00
Chris Tate 8d5b3377aa Represent Dates as objects with identity
A Date is a refcounted object holding its time value, shared with the
checked-dynamic Date handle, so it keeps its identity through unknown.
Dates compile in arrays (includes, indexOf, lastIndexOf, join, default
sort by toString() text), in Date | undefined and Date | null slots, as
Map and Set keys and values, in index signatures, and in records, tuples,
class fields, closures, promises and generators. === compares identity,
relational operators compare time values, new Date(date) makes a new
Date, and JSON.stringify writes toJSON().

Closes #225
Co-authored-by: Max B <max@tfq.at>
2026-10-22 20:33:09 -05:00
Chris Tate 210dee9eb0 Read the global object without rebuilding its name table (#1895) 2026-10-22 20:26:51 -05:00
Chris Tate a9ee38611e Register the global object's names again after a library re-init 2026-10-22 20:20:56 -05:00
Chris Tate 6bbabd5fc9 Convert dynamic values into nested unions in one pass
Converting a dynamic value into a union (`JSON.parse(text) as T`, a cast
from `any`) tries the arms in order and builds the first arm whose literal
discriminator and whole structure match. The builder of that arm converts
its members, and a member typed as another union matched its arm's
structure again. Every value was matched once per union around it, so
converting a tree of depth d cost about d times as much as a flat one.

The arm a union matched now builds as a trusted conversion
(scr_dyn_trust_enter). Inside it, a nested union picks an arm without
matching the arm's structure, which the enclosing match already checked,
when the value's arm is known: a plain object's arm by its literal, when
the arms' literals are disjoint and no arm without a literal takes plain
objects, or the only arm that takes plain objects (a record) or arrays (an
array), as in an optional or nullable member. Other unions, and values that
are not plain objects or arrays, match as before. Getters and proxy traps
run outside the trusted conversion. Failed conversions throw the same
TypeError with the same path.
2026-10-22 20:15:02 -05:00
Chris Tate 8807c24526 Compile zod 3 natively (#1835) 2026-10-22 20:13:04 -05:00