Commit Graph
6750 Commits
Author SHA1 Message Date
Malte UblandClaude Opus 5.5 c698667bf0 Let TypeScript classes override methods of JavaScript base classes
`class TracingAgent extends Agent { dispatch(opts: DispatchOptions,
handler: DispatchHandler): boolean { ... super.dispatch(...) } }` over an
npm-static JavaScript Agent was refused: the override's typed parameters
had to match the base's untyped (checked-value) slots exactly.

A TypeScript override of a JavaScript method now keeps the base's ABI.
Each untyped base slot stays a checked value, and the override's typed
parameter narrows from it at entry with a checked conversion; an untyped
base return takes the override's value boxed; trailing base slots the
override omits are ignored. Calls through base-typed receivers dispatch
to the override, and calls on the subclass type see the same ABI.

The corpus program builds the undici dispatcher shape as an npm package:
an EventEmitter-rooted base, symbol-keyed fields from a key table, a
computed accessor, subclasses across modules re-exported through require,
and TypeScript subclasses overriding `dispatch`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:18:25 -07:00
Malte UblandClaude Opus 5.5 e2d4092b16 Re-export classes through module.exports members that require them
`module.exports.Agent = require('./lib/agent')`, where agent.js ends with
`module.exports = Agent`, is the same alias plumbing as
`module.exports.Agent = Agent`: importers resolve to the class declaration
and the class registry applies, instead of storing a class value (which
classes inheriting a runtime-provided constructor, such as EventEmitter
subclasses, do not have). The statement still evaluates the required
module at its position.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:18:18 -07:00
Malte UblandClaude Opus 5.5 eb998d4f4d Key class fields and methods by symbols from CommonJS key tables
A JavaScript package often keeps its private symbols in one module,
`module.exports = { kClosed: Symbol('closed'), ... }`, and its classes
declare `[kClosed] = false` fields and `[kDispatch]()` methods with keys
destructured from that table (undici's core/symbols.js). Those keys were
not stable, so every such class was rejected, and with it every subclass
in other modules ("extending classes not declared in the program").

A property of a JS module's effective top-level `module.exports = {...}`
literal initialized with `Symbol(<constant>)` or `Symbol.for(<constant>)`
is now a stable class key, as a module-scope const is, provided nothing
before the table can run code (a require there could close a cycle and
publish the empty initial exports object), no earlier property can run
code, and nothing writes the property or replaces it through `exports`.
The property's own declaration symbol is the identity every requirer
shares, and the table's export global supplies the runtime key for
dynamic lookups (a base method calling a subclass's symbol-keyed method).

The checker types such keys as plain `symbol`, so it answers `this[k]`
with the union of every symbol-keyed property of the class. Reads through
table keys in JS files now take the field's own layout type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:18:13 -07:00
Malte UblandClaude Opus 5.5 bda5970343 Name identifier-minified and ES-module chunk bundles in refusals
An identifier-minified esbuild bundle keeps the __commonJS helper's
shape but loses the names the recognizers verify; an ES-module bundle of
CommonJS chunks has no require to route them through. Both now refuse
with a message naming the construct.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:32:22 -07:00
Malte UblandClaude Opus 5.5 6e68a811f1 WebAssembly W5: the Memory, Table and Global constructors, Module.imports/exports/customSections, and function references from tables
new WebAssembly.Memory, Table and Global construct the runtime objects an
instance exports, with Node 24's descriptor order, defaults, conversions
and messages (V8's EnforceUint32 errors, the v128 and shared-memory
errors; a valid shared memory is refused with a TypeError until threads),
so either kind can be imported. Imports of memories, tables and globals
now check limits, types and mutability with V8's LinkError messages, and
exports keep V8's identity (imported objects re-export as themselves, one
object per memory and table). The classes called without new throw Node's
TypeError.

WebAssembly.Module.imports, exports and customSections read the
descriptor (ABI v2). Custom sections are carried only by programs that
call customSections. Every function a module references reaches
JavaScript as a callable function: the translator emits one call thunk
per signature, so table.get and funcref globals return the module's
unexported functions too, and exported functions can be stored in tables
and globals from any instance. Discovery also finds .so WebAssembly
shared libraries at resolved paths.

Fixes to W3/W4 found on the way: externref Table.grow fills with
undefined; Memory.grow/Table index errors and funcref messages match V8;
an invalid funcref global import is a LinkError; global.valueOf() on a
WebAssembly.Global-typed receiver answers the value; explicit-bounds
memories keep the blocks grow replaced, so old ArrayBuffers never
dangle.

This unblocks quickjs-wasi's extension loader: quickjs-wasi with its url,
encoding, headers and structured-clone extensions runs through its own
glue identically to Node, and pi codemode's static coverage goes from 83%
to 84% with no WebAssembly blocker left.

Evidence: differential corpus programs for the API (webassembly-jsapi),
a 261-test port of WPT wasm/jsapi (webassembly-wpt-jsapi), quickjs-wasi's
dist/extensions.js run unmodified against dylink-shaped modules
(webassembly-dylink), and the 2.0 spec suite's modules that import the
spectest memory and table (new webassembly-spec-imports group plus the
memory and table groups); all match Node in the plain and sanitized
lanes. The zero-cost gate leaves every non-WebAssembly program
byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:27:45 -07:00
Malte UblandClaude Opus 5.5 22edb6deb0 Test bundled chunks, cycles and refusals
Unit tests run each split bundle against its original under Node and
pin the refusal reasons; npm-static tests compile the new cases and
compare them with Node byte for byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:20:28 -07:00
Malte UblandClaude Opus 5.5 17647d8bfd Add esbuild chunk and CommonJS cycle npm fixtures
Real esbuild output (0.25 plain and whitespace/syntax minified, 0.17)
of one bundle with lazily and eagerly required chunks, a chunk that is
itself an esbuild dist, an external through __toESM, a .cjs bundle in a
type-module package, and per-file esbuild CommonJS cycles entered from
either side. Split chunks are require-only modules: their whole export
table is their surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:17:13 -07:00
Malte UblandClaude Opus 5.5 6b1f1c5991 Admit require cycles through esbuild's live export tables
The interop rewrite certifies a file whose esbuild export object is
installed before anything can run another module's code; the CommonJS
cycle rule then treats its tail table as settled at that point. A
top-level plain read of a cycle member's function-declaration export is
exact as well: Node and the compiled read see the same function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:07:43 -07:00
Malte UblandClaude Opus 5.5 ee8011cea5 Split esbuild CommonJS chunks into their own modules
Recognize esbuild's __commonJS helper (keyed and arrow forms, the
Object.keys spelling of 0.11) and move every chunk body into a virtual
sibling CommonJS module, so require_x() becomes a require of that module
and the ordinary CommonJS machinery compiles it. Also accept minified
helper declarations that share one var statement, counter-suffixed
helper names inside bundled bundles, and treat .cjs files as CommonJS
in the interop rewrite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:03:34 -07:00
Malte Ubl dc66837001 Merge remote-tracking branch 'origin/opt/webcrypto-hmac' into opt/jose-gaps 2026-10-19 10:51:59 -07:00
Malte UblandClaude Opus 5.5 1a61c4daa0 Keep the verdict-cache test's blocker refused now that array position patterns compile
The test used `const { length } = [1, 2] as number[]` as its coverage
blocker. That compiles now, so no diagnostic was left to replay. The blocker
destructures a `number[] | string` union instead, which keeps the SC1031
object-pattern fence. The comment-only replay and code-edit relower
assertions are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:51:12 -07:00
Malte UblandClaude Opus 5.5 cf198952e2 Compile the real jose root entry: string durations, chained builders, navigator
jose 6.2.12 now compiles natively through its root entry (`import { jwtVerify,
errors } from "jose"`), from TypeScript and JavaScript, with chained SignJWT
builders and string durations:

- A discarded `a && b;` / `a || b;` statement whose value form fails on its own
  result type (jose's secs(): `(!m || m[4] && m[1]) && invalid()`) lowers as
  `if (a) b;` / `if (!a) b;`. The value form still lowers first, so statements
  that compiled before are unchanged.
- In JavaScript, a number-keyed read on a `T[] | null | undefined` value outside
  an optional chain (`m[2]` after a statement ruled `m` non-null) reads the
  array arm like any element read: a group that did not participate is
  undefined instead of a trap.
- A JavaScript fluent method whose returns all end in `this` (including comma
  sequences) narrows a used call result back to the receiver's subclass. A
  receiver lowered at an ancestor of the class a call needs is bridged with a
  verified downcast instead of an upcast the validator rejected (the
  "upcast JWTClaimsBuilder does not extend SignJWT" internal error).
- `typeof navigator` is "object" and `navigator.userAgent` is "Node.js/24", as
  in Node 24, for the free name in JavaScript and under @types/node. Both fold
  to literals; other members refuse by name.
- TypeScript object destructuring of an `any` result of a JavaScript function
  (jose's jwtVerify) reads its members like the member reads that already
  compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:30:48 -07:00
Malte UblandClaude Opus 5.5 f2b8162a6d Keep the coverage fixture's blocker refused now that array position patterns compile
tests/coverage-fixtures/mixed.ts used `const { length: firstItem } = items`
as its SC1031 example. Object patterns of positions and length over arrays
compile now, so coverage counted 13/13 static. The example now destructures
a union source (array | string), which keeps the SC1031 object-pattern fence
at the same line and column; only the message's source kind changes, from
array-typed to union-typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:19:59 -07:00
Malte Ubl 643c0f5daa Merge commit '26946d6b2' into opt/webcrypto-hmac 2026-10-19 08:21:02 -07:00
Malte Ubl 675e70cc24 Merge commit '26946d6b2' into opt/pi-small-fixes 2026-10-19 08:21:01 -07:00
Malte Ubl 13efc5ffcd Merge commit '26946d6b2' into opt/tier-s3-jsval 2026-10-19 08:20:59 -07:00
Malte UblandClaude Opus 5.5 2f4edcc358 Keep existing objects in JavaScript var and destructured bindings
A JavaScript `var` binding hoists to its function, and its slot was
created with the checker's type before the initializer lowered. When the
initializer was an existing checked-dynamic object (an element of an
array built with push, a find result, the object a function returns),
the record-typed slot held a converted copy: writes through the binding
missed the object that the array or the function's caller still held,
and `===` against it was false. `let` and `const` already lowered the
initializer first and kept such a value checked-dynamic.

A `var` initialized with an existing value now lowers the initializer
before creating the slot, and a checked-dynamic value gets a
checked-dynamic slot. The same applies to `for (var x of ...)`, to array
destructuring with `var`, `let` and `const`, and to file-scope bindings,
whose module-entry `undefined` store follows the slot's final type. A
literal holding such an object builds checked-dynamic for a `var` as it
already did for `let`. TypeScript files and annotated bindings are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:59:01 -07:00
Chris Tate 9ac4551de5 Expect SC1101 from fs-shims and record the branch's corpus costs
fs-shims now gets past Buffer.isBuffer on a Uint8Array (fszoo/index.js:35),
which this branch lowers; its first deferred refusal is the existing
SC1101 for a closure passed into an unknown slot (fszoo/index.js:44), so
the expectation records SC1101.

The branch's new corpus programs (1112 and 6200 to 6210) get per-program
cost files in tests/harness/corpus-costs, taken from the differential and
llvm-differential case durations in this branch's Linux CI shards.
2026-10-19 09:13:55 -05:00
Chris Tate a405f856bc Iterate class instances whose Symbol.iterator is a generator method
A class with a parameterless *[Symbol.iterator]() generator method can now
be iterated by for-of, array spread, Array.from, Math.max and Math.min
spreads, new Set, new Map, Object.fromEntries and Iterator.from. Each site
calls the method once and drives the generator it returns.

obj[Symbol.iterator]() and the other well-known symbol methods called
through element access now call the class's method, so iterator helpers
apply to the result instead of failing on an untyped value.

Writes that can replace a well-known symbol method (a symbol-keyed write,
Object.defineProperty, a prototype write) now enter the method census, and
those classes keep the existing checked or refused paths.
2026-10-19 09:06:15 -05:00
Chris Tate 26946d6b22 Revert "Copy repeated childless IR nodes with a spread" (#1404) 2026-10-19 08:57:17 -05:00
Chris Tate 1f07166fbf Merge main 2026-10-19 08:56:48 -05:00
Malte UblandClaude Opus 5.5 7ea35785ee Check the bench workloads' dynamic-tier symbols in CI
The zero-cost gate's I3 check (a fully typed bench workload defines no
dynamic-tier runtime symbol) runs only when someone runs the gate, and on
Darwin it never matched: Mach-O names carry a leading underscore, so no
symbol had a DYNAMIC_TIER_PREFIXES prefix.

DYNAMIC_WORKLOADS and the nm function-name reader move to
zero-cost-compare.mjs, which now drops the Mach-O underscore. A harness
test builds every bench program on Linux and fails when a workload outside
DYNAMIC_WORKLOADS links a dynamic-tier symbol, or when a listed workload
no longer links any, so the list shrinks as leaks close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 06:44:29 -07:00
Chris Tate b9fbe4b50b Merge main 2026-10-19 08:42:36 -05:00
Chris Tate 0abf9a6395 Revert "Copy repeated childless IR nodes with a spread"
This reverts commit c836f93998. The
bootstrap seed build cannot compile the spread copies (SC1090).
2026-10-19 08:34:08 -05:00
Chris Tate 37bac645aa Read call arguments through NodeArray.nodes in the readline dispose and getTestContext checks 2026-10-19 08:32:43 -05:00
Chris Tate 178646baf0 Merge main 2026-10-19 08:31:43 -05:00
Chris Tate 7f907d23e3 Read getTestContext's arguments through their nodes (#1402) 2026-10-19 08:18:09 -05:00
Chris Tate 3d4f181cb7 Merge main 2026-10-19 08:15:47 -05:00
Chris Tate 720b6f6117 Merge main 2026-10-19 08:11:47 -05:00
Chris Tate 88a2c557d1 Link the child-process unit only in programs that spawn
The event loop called scr_children_pending, scr_children_poll and the other
child hooks directly on native targets, so every async program linked the
child-process unit (the reap pass, IPC service, pipe readers and writers).
The loop now reaches them through a nullable ScrLoopChildren table that the
child unit installs with its first spawned child or IPC channel, the shape
the events, net, dgram and fs.watch hooks already use. The WASI stubs for
the old direct calls are removed.
2026-10-19 07:59:51 -05:00
Chris Tate edd3874925 Run iterator helpers and destructuring on generators passed through untyped values (#1389) 2026-10-19 07:57:24 -05:00
Chris Tate c6e16d365b Read getTestContext's arguments through their nodes
lowerGetTestContext (#1393) read expr.arguments as an array. Since #1394
an AST list's elements are in nodes, so the compiler did not type-check.
2026-10-19 07:56:11 -05:00
Chris Tate 809f2cbea1 Merge main 2026-10-19 07:55:06 -05:00
Chris Tate a57089aebf Accept readline's dispose, instanceof, dynamic streams and non-string prompts
rl[Symbol.dispose]() closes the interface, as Node's dispose method
calls close(). rl instanceof readline.Interface and
readlinePromises.Interface answer which module created the interface:
rl.create's mode marks readline/promises interfaces, and the fallback
declarations declare both Interface classes as values.

In JavaScript, createInterface's input and output can be checked-dynamic
values (a function's result, an object's member, an element that may be
undefined): rl.inputFromDyn and rl.outputFromDyn unbox the stream they
hold, an output of null or undefined means no output, and other values
are refused.

In JavaScript, question() and setPrompt() accept a value that is not a
string. When such a prompt is written, an interface that is not a
terminal throws ERR_INVALID_ARG_TYPE as Node's kWriteToOutput does, and
the readline/promises question rejects with it; the question stays
pending and the previous prompt returns when it is answered. A terminal
interface writes String(value) of a primitive.

Corpus 5876-5878 match Node v24.22.0.
2026-10-19 07:51:54 -05:00
Chris Tate 9e628720ae Test the common IR node kinds first in child traversals (#1391) 2026-10-19 07:50:45 -05:00
Chris Tate c80f9e62a1 Record new corpus program costs in per-program files (#1361) 2026-10-19 07:48:44 -05:00
Chris Tate 8320cc2289 Spin briefly before a mailbox wait blocks, and signal after the unlock
A worker message round trip cost about 12 µs, almost all of it in two
blocked waits: the poster signaled the waiting thread while it still held
the mailbox lock, so the woken thread went back to sleep on the lock, and
every wait paid a sleep and a wakeup. scr_mailbox_post and close now
signal after the unlock, and scr_mailbox_wait spins for up to 20 µs (or
the wait's timeout, if shorter) on a flag the queue keeps before it
blocks. A process that may run on only one CPU never spins.

A new bench:runtime workload, worker-messages, posts 100,000 round trips
between the main thread and a worker: 1296 -> 360 ms.
2026-10-19 07:46:06 -05:00
Malte UblandClaude Opus 5.5 1af8cfea7b Bump the IR version in main's new classes and thread-slots tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:43:59 -07:00
Malte Ubl a10ce583d7 Merge remote-tracking branch 'origin/main' into opt/tier-s3-jsval 2026-10-19 05:43:49 -07:00
Chris Tate 56850331b6 Copy repeated childless IR nodes with a spread (#1392) 2026-10-19 07:43:33 -05:00
Chris Tate 9b79eb8d32 Read declarations from symbols and signatures
symbol.declarations, symbol.valueDeclaration and signature.declaration
return nodes, as tsc-ts's Symbol and Signature do, instead of the
facade's declarationsOf, valueDeclarationOf and signatureDeclaration
resolving their handles. Each symbol and signature resolves its handles
in its own project on first read and keeps the nodes. The handles stay
available as declarationHandles, valueDeclarationHandle and
declarationHandle.

A program's checker and its symbols belong to the same project, so the
nodes are the ones the facade returned. The frontend's 432 calls of the
three facade methods become property reads, and the methods and their
caches go.
2026-10-19 07:41:28 -05:00
Chris Tate 8e0af5fc59 Add getTestContext to node:test (#1393) 2026-10-19 07:37:01 -05:00
Malte UblandClaude Opus 5.5 60e0b246e7 Choose record key order from the orders values are built in
Each record shape's enumeration table now follows the key order its object
literals, spreads, rest bindings and record copies create, and appends
optional keys assigned after construction. Shapes whose values are built
in more than one order keep their declaration order and SC6002.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:34:30 -07:00
Malte UblandClaude Opus 5.5 2b8cd22423 Add WebCrypto HMAC: crypto.subtle importKey, sign, verify and digest
crypto.subtle.importKey('raw', ...) for HMAC keys, HMAC sign and verify,
and the SHA-1/SHA-2 digests compile through the crypto global, with
CryptoKey values as native handles. scr_webcrypto.c is only the WebCrypto
layer (Web IDL conversion, algorithm normalization, the CryptoKey handle,
promise wrapping and Node's TypeErrors and DOMExceptions) over node:crypto's
digest core and deferred promise queue. It links only into programs that
call these methods (the new webcrypto link feature). Other algorithms, the
jwk format and the other SubtleCrypto methods are SC2020 refusals.

The real jose@6.2.12 HS256 path (SignJWT, jwtVerify) now runs natively. It
also needed four compiler fixes: object patterns of positions and length
over arrays, the Uint8Array base64/hex feature probes, a compile-time
String.fromCharCode.apply, and structuredClone of a parameter whose default
is {}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:34:26 -07:00
Malte Ubl 2fc52c47c9 Merge remote-tracking branch 'origin/opt/silent-bugs' into opt/silent-bugs 2026-10-19 05:34:13 -07:00
Malte UblandClaude Opus 5.5 9290f32e0d Treat constructor returns of chainable this-calls as returning this
three.js's Color constructor ends with `return this.set(r, g, b)`, and
every reachable `set` (Color's, and Color4's override returning
`super.set(...)`) returns `this`. The return analysis classified it as
returning another instance and refused `class Color4 extends Color`.
A return that is `this`, or a `this.m(...)`/`super.m(...)` call whose
every reachable implementation returns only `this`, a primitive or
nothing, now keeps the ordinary lowering, so such classes need no
result slot and subclasses construct through super() as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:34:05 -07:00
Malte UblandClaude Opus 5.5 a4131dfdb9 Read and write dynamic-tier properties through shapes and inline caches
Stage S4 of the two-tier design: dynamic-tier code reads, writes and
calls properties through per-site inline caches keyed on hidden-class
shapes.

- Runtime: scr_js_object.c (dynTier) attaches shapes to the existing
  checked objects: a shape is a node of the thread's transition tree
  over key sequences, computed on an object's first cache miss, and
  objects past 64 keys stay unshaped (dictionary mode). Attributes and
  the prototype are tested live on every hit. The dyn-tier build of
  scr_json.c (SCR_JS_TIER) resets a shape on every key-set change;
  typed programs keep linking the ordinary build.
- Sites: ir/js-ic.ts recognizes the lowering's property helpers by
  structure; calls to them from dynamic-tier functions, and method
  dispatch outside module initializers, become sites with a zeroed
  thread-local ScrJsIc. backend/llvm/js-ic.ts tests entry 0 inline
  (own property or one prototype link), probes out of line, and keeps
  the helper as the noinline cold slow path. Array length and dense
  number-key element reads are inline.
- Numbers: jsval reads of number properties are immediates, and a jsval
  number stored over an unshared number cell updates it in place; the
  jsval pass feeds cache stores jsvals and stops boxing discarded
  compound-assignment results. `%` and bitwise operators get integer
  paths.

Static functions are untouched: typed programs compile to byte-identical
LLVM and executables, and every static function of programs with
dynamic code is byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:34:01 -07:00
Malte Ubl 800b298b6a Merge remote-tracking branch 'origin/opt/tier-s3-jsval' into opt/tier-s3-jsval 2026-10-19 05:33:53 -07:00
Malte UblandClaude Opus 5.5 05d200ae13 Assign tiers in the union-conversions fixture before serializing its IR
The fixture builds and serializes an IR module by hand. Since IR version
17 serializes function tiers instead of deriving them on load, the loaded
module's main, which performs dynamic operations, validated as a static
function. Assign tiers as lowering does before serializing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:33:41 -07:00
Malte Ubl 0b00819e40 Merge remote-tracking branch 'origin/opt/builtins-npm2' into opt/builtins-npm2 2026-10-19 05:33:31 -07:00