Commit Graph
3145 Commits
Author SHA1 Message Date
Chris Tate 899375b9fe Accept optional checked-dynamic parameters in generic method instances (#1421) 2026-10-19 18:05:41 -05:00
Malte UblandClaude Opus 5.5 f7f239ad53 Lower memoized lazy loaders of bundled ES-module namespaces
A loader `L = () => M ??= NS` over a lazily initialized module's
namespace (optionally through __toESM) is respelled at every `L().name`
as the module's initializer call followed by the export's live read;
loaders whose result escapes as a value stay refused. The canonical
export table keeps a getter it cannot chase as a live accessor, so a
lazy re-export still initializes its module at the first read. An
ES-module importer copies those accessors once when it links the
CommonJS module, in the lexer's order, exactly like Node's loader;
require() keeps reading them live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:55:37 -07:00
Malte UblandClaude Opus 5.5 6255c9351b Keep the global object in type assertions of globalThis
`globalThis as { [KEY]?: State }` is the usual way for several modules
to share process-wide state. globalThis lowers to a checked-dynamic
value, and asserting a checked-dynamic value to a record or array type
took the checked conversion, which builds a new native value. A write
through the asserted value went to that copy, so other modules never
saw it; string-keyed and index-signature views threw "expected object".

- An assertion of the global object (globalThis, global, also through
  `as unknown as`) keeps the checked-dynamic value when the conversion
  would copy, so member reads and writes reach the global object.
- File-scope and local bindings of such an assertion hold the global
  object (collectGlobals, lowerVarDecl), so an exported
  `const state = globalThis as ...` works across modules.
- The divergence scan no longer reports these assertions as SC6001
  width copies.
- Runtime: one global-object box per thread (scr_global_object), which
  globalThis, global and globalThis.globalThis all answer, and direct
  `globalThis[symbol]` accesses use that box's symbol-keyed properties,
  so every spelling of the global object sees one property.

Other checked-dynamic values keep the checked conversion: a native
value held as unknown unwraps by identity there, and a checked-dynamic
tree is copied at the documented boundary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:52:52 -07:00
Chris Tate 305832a12d Merge main 2026-10-19 17:32:14 -05:00
Chris Tate ca01eabd12 Read generic class narrowings through every class they can hold
In a generic function, `node instanceof Identifier || node instanceof
PrivateIdentifier` narrows `node` to `T & PrivateIdentifier` when
Identifier has every member PrivateIdentifier has: TypeScript reduces the
union of the two intersections by structural subtyping. The read was
lowered as a downcast to PrivateIdentifier, and the narrowing guard
aborted when it held an Identifier. tsc-ts's NodeFactory.cloneNode has
this pattern, so declaration emit aborted.

When the checker types a read of a type-parameter intersection as a
subclass, and another class under the value's class is assignable to that
subclass without extending it, the read is now a class union over those
classes, and member reads and method calls on it dispatch over the union.
When this instantiation's class is unrelated to the narrowed class but
assignable to it (`T = Identifier`), member calls use the instantiation's
class. A union method helper whose body lowering is refused is no longer
left registered without a body.
2026-10-19 17:25:49 -05:00
Chris Tate c04f770104 Flatten the last value a take delivers in full (#1390) 2026-10-19 17:20:54 -05:00
Chris Tate 736085fbf1 Merge main 2026-10-19 17:19:52 -05:00
Chris Tate a31b1fe23e Merge main 2026-10-19 17:17:48 -05:00
Chris Tate 0df30d4a56 Choose record key order from the orders values are built in (#1398) 2026-10-19 17:15:29 -05:00
Malte UblandClaude Opus 5.5 46d12eb1e1 Probe forwarded tuples without interning types
Recognizing the empty-tuple representation interned the unit-only union,
and a rest parameter's IR type was mapped before checking that it is a
tuple at all. Both shifted union ids in programs with ordinary array rest
parameters that read `.length`. Probe through the checker and registry
lookups only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:15:20 -07:00
Chris Tate 37f54ff157 Merge main 2026-10-19 17:14:32 -05:00
Chris Tate 9886b570a3 Merge main 2026-10-19 17:11:27 -05:00
Chris Tate 79ecacfb5d Merge main 2026-10-19 17:08:37 -05:00
Chris Tate c82b6d8188 Merge main 2026-10-19 17:06:58 -05:00
Chris Tate 1711c0110e Inspect WebAssembly objects as Node does and declare the wasm entry points in scr_runtime.h
util.inspect prints a handle whose Symbol.toStringTag ends with "." and its class name in place of the name, as Node does: WebAssembly.Module {} rather than Module [WebAssembly.Module] {}. The same holds for Instance, Memory, Table and Global.

The WebAssembly API entry points move to scr_wasm.h, which scr_runtime.h includes with scr_wasm_abi.h, so every scr_* symbol the backend can emit has a header declaration. The emitted registry array is renamed sc_wasm_registry_modules, following the compiler-emitted symbol prefix.
2026-10-19 17:05:27 -05:00
Chris Tate 302f0f9fa9 Compile the remaining corpus programs with the native compiler
The native compiler failed on corpus programs that the Node-hosted
compiler compiles. Four compiler causes:

- A record that width-lifts into several members of a discriminated
  union had no conversion plan. As a field of a larger record, that
  removed the outer member it belonged to, and the outer record could
  then lift into the only member left: a `recordSet` statement built with
  a nested `recordGet` literal became a `break` statement, and a switch
  on `kind` read it with the `recordSet` layout. The native compiler
  stopped at the narrowing guard on 5585-record-results-inlined.ts and
  similar programs. Such a record now converts through the discriminant:
  each member it width-lifts into is a route, and the value's
  discriminant selects the member at run time, as same-layout members
  already do.
- Three compiler paths asserted a value that can be null, and the native
  compiler threw "null is not representable in the target union":
  stableCreateRequireBindingReason7 (`cache.get(decl)!` from a cache that
  stores null for stable createRequire bindings; 2631-create-require,
  2932-create-require-program), the Int32 contract check (`catchLocalId!`
  for a bindingless `catch {}`; 4970-int32-call-abi) and
  StringIndexBounds.binding (a memo that stores null). The caches are
  read without assertions and the catch test checks for null.
- scriptc adopts alwaysStrict, esModuleInterop and
  allowSyntheticDefaultImports from a project's tsconfig, but the TS 7
  SDK schema no longer declares them. A native compiler keeps only
  declared fields when it builds or parses a typed record, so these
  options were dropped, and the default-import check refused
  `import fs from "node:fs"` (SC1012, 2391-default-builtin-imports and the
  3055 to 3057 module-setting variants). The options a project reports
  are parsed with these fields declared, and Ts7CompilerOptions uses the
  same type.
- An object-literal method kept its inferred return type where an arrow
  function or function expression takes the return type of its
  contextual signature. For a method that returns an array of statement
  literals, a `varDecl` literal with `init: null` was converted to the
  member whose `init` holds only expressions, and the conversion threw
  (Array.fromAsync's array loop: 5854-array-from-async,
  5855-array-from-async-js). A synchronous object-literal method now
  takes its slot from the property of the same name on the literal's
  contextual type and adopts its return the same way.

Adds corpus 6022 and 6024. 2391, 2631, 3055, 4970, 5585 and 5854 run in
the native-frontend self-host check, which now also links the dyn-async
unit when the program uses it, as the production build does.
2026-10-19 17:02:31 -05:00
Chris Tate 0e02fc28ca Accept mock.fn() without an original
node:test changes, matching Node 24.22:

- mock.fn() and t.mock.fn() without an original return undefined and
  record the arguments of each call, whatever the callers pass. The
  original and the initial implementation are a function that returns
  undefined, so restore() brings that function back.
- mock.fn<F>() with an explicit function type records F's parameters
  when F's return type is void or includes undefined. A return type
  without undefined, such as mock.fn<() => number>(), throws SC2020.

mock.fn()'s default type, (...args: any[]) => undefined, maps to the
checked-dynamic rest ABI used for program-declared (...args: unknown[])
functions: the calls log records the argument array, and the mock's
wrapper spreads it into the implementation.

A function whose fixed parameters are all unknown or any, or that has
none, now converts to a checked-dynamic rest signature without fixed
parameters, such as (...args: unknown[]) => void: each call passes the
arguments in order, undefined for the ones the caller left out. This is
how () => {} reaches mockImplementation and mockImplementationOnce, and
it applies to any such slot in TypeScript programs, which threw SC1090
before.

The mock-fn-default fixture and corpus program 5816 compare with Node,
plain and with SCRIPTC_SAN=1.
2026-10-19 16:59:05 -05:00
Malte UblandClaude Opus 5.5 76e5a1c48f Accept optional checked-dynamic parameters in generic method instances
paramShapes, which builds the parameters of an instantiated signature
(generic call instances and the erased instance that a method value or
a reflected prototype slot uses), refused an optional parameter unless
its type had an undefined union arm. An interface view implemented by a
class (`handle?: Handle | null`) and an erased type parameter map to the
checked-dynamic type, which holds undefined directly; paramShape, the
declared-signature path, already accepts it.

Direct calls of such a method failed with "optional parameters of type
'unknown'", and reflecting its class prototype from the dynamic-dispatch
fixpoint raised the same refusal outside any statement and crashed the
compiler with an uncaught PoisonError. paramShapes now treats the
parameter as omittable, so the instance lowers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:54:50 -07:00
Chris Tate 65034c6d14 Merge main 2026-10-19 16:53:40 -05:00
Chris Tate 4986a928c7 Merge main 2026-10-19 16:51:05 -05:00
Chris Tate cf87b359e7 Merge main 2026-10-19 16:50:17 -05:00
Malte UblandClaude Opus 5.5 493e876328 Keep brand checks on TypeScript unknown receivers refused
The checked-receiver brand check was meant for JavaScript methods, whose
`this` is the actual call-site value. It also accepted TypeScript
`unknown` receivers, which the private-members diagnostics fixture pins as
refused (narrow first). It now applies to JavaScript sources only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:48:19 -07:00
Chris Tate c7329845b1 Merge main 2026-10-19 16:48:01 -05:00
Chris Tate bdefcb0141 Route records into discriminated unions by their discriminant
The native CLI built from main aborted on the record-boundaries and
value-objects benchmarks with "a narrowed value does not have the type its
narrowing proved". record-results.ts builds recordSet statements in a map
callback whose value field is a recordGet literal. Converting that array
into IrStmt[] chose the break/continue arm: the literal also fits the
shorter varRef-like expression arms, so its conversion into IrExpr was
ambiguous, and an ambiguous field conversion made widthLiftPlan drop every
arm that needed it. The one arm left was break/continue, reached by
dropping obj, shapeId, field and value. The record kept kind "recordSet"
under the break/continue tag, so mapStmtChildren's narrowing guard aborted.

IR record shapes keep the discriminant as a plain string, so a record's
layout alone cannot pick its arm. When a record that carries a
discriminated union's discriminant field fits several arms, widthLiftPlan
now plans a route through every arm it converts into, selected at run time
by the discriminant's value, as JavaScript does (a value no arm names
throws the catchable TypeError). Conversions that resolved before are
unchanged; the zero-cost gate shows identical IR, LLVM and executables for
the benchmarks and the corpus, and the native CLI now compiles every
benchmark workload.
2026-10-19 16:47:28 -05:00
Chris Tate 706f473559 Keep existing objects in JavaScript var and destructured bindings (#1406) 2026-10-19 16:45:13 -05:00
Chris Tate fa8001358c Merge main 2026-10-19 16:44:05 -05:00
Chris Tate d3c2c75720 Merge main 2026-10-19 16:37:26 -05:00
Chris Tate acf5bafe9c Merge main 2026-10-19 16:36:46 -05:00
Chris Tate cdde537adf Run String/Number methods on dynamic values; add crypto.getRandomValues, fs.constants, Buffer brand tests (#1345) 2026-10-19 16:36:21 -05:00
Malte UblandClaude Opus 5.5 7f836d276d Answer private brand checks on checked receivers
`#target in this` inside a JavaScript method reads the actual call-site
receiver, a checked value, so the brand check fenced at run time
(undici's FastIterableIterator, and the corpus program that pins it,
which coverage counted as one deferred site). The brand is present
exactly when the value is a class instance at or below the declarer:
a typed reference boxed under the declarer or a descendant answers true,
and one boxed under an ancestor answers with an instanceof test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:32:28 -07:00
Malte UblandClaude Opus 5.5 4e0f71a7e5 Document the new package selection; record the 2563 order baseline
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:21:57 -07:00
Malte UblandClaude Opus 5.5 0d9a6da8c9 Select untyped dependencies and @types twins by identity
An untyped package another package's JavaScript imports is selected
whether resolution answers no declarations or its JavaScript. A
DefinitelyTyped twin qualifies by identity (@types/<mangled name>):
the declarations only check the program, and the JavaScript compiles
with its inferred surface, so a twin that disagrees with the shipped
JavaScript fails the build at the use rather than compiling against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:19:07 -07:00
Malte UblandClaude Opus 5.5 afdb7706c1 Record TypeScript order baselines for the undici class-shape corpus programs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:09:32 -07:00
Malte UblandClaude Opus 5.5 cadcaae85a Refuse prototype reflection of members without a lowering at run time
Reflecting a class prototype (the checked view's prototype helper) lowers
each member's value and key from emitReachable. A member whose generic
method instance or computed key had no lowering threw a PoisonError out of
ClassDynamicDispatch and crashed the compiler (seen once undici's classes
compile). The walk now contains those errors per member, drops the
member's diagnostic, and refuses the reflection at run time like its other
unsupported cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:09:32 -07:00
Malte UblandClaude Opus 5.5 3c76868fca Pass TypeScript option literals to open JavaScript option parameters
`new Agent({ keepAliveTimeout: 10 })` against undici's
`constructor({ factory = defaultFactory, maxOrigins = Infinity, ...options }
= {})` failed with SC2002: the literal was laid out against the record the
checker infers for the pattern, which lacks the properties the rest
element collects.

When a TypeScript object literal flows into a checked-value slot whose
contextual type is a JavaScript destructuring pattern with a rest element
(or that pattern's `= {}` default), and the literal names a property the
type does not declare, the literal is built as the plain object the
JavaScript side receives. Every other literal keeps its previous lowering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:09:31 -07:00
Malte UblandClaude Opus 5.5 cfb50d16e2 Specialize variadic generic wrappers to the wrapped function's signature
Higher-order wrappers over `(...args: any) => any` that forward `arguments`
with `fn.apply(this, arguments)` and type their options with
`Parameters<F>` / `ReturnType<F>`, and their rest-tuple rewrite, did not
compile. Generic calls already monomorphize per call site; these pieces now
resolve to that site's concrete signature:

- Rest parameters typed as fixed tuples (`...args: [A, B]`, `...args: A`
  bound to a tuple, `...args: Parameters<F>`) flatten to positional
  parameters, so `(...args: [A, B]) => R` and `(a: A, b: B) => R` share one
  function type. Trailing optional positions become `T | undefined` slots.
- `Parameters<F>`, `ReturnType<F>`, `Awaited<ReturnType<F>>` and the
  `ReturnType<F> extends PromiseLike<infer U> ? U : …` spelling resolve
  through F's binding.
- A parameterless function expression reading `arguments` lowers against
  the fixed signature its value is asserted to (or contextually typed as);
  `arguments` is that signature's tuple.
- `f.apply(thisArg, args)` and `f(...args)` over a forwarded tuple call
  directly, passing `thisArg` as the receiver. A tuple that is only
  forwarded is never allocated.
- `fn.name` on a function parameter folds to the call site's static name;
  the instantiation key carries it.

Also: optional callback fields narrowed at the call (`o.cb ? o.cb(x) : …`)
compile, effect-free surplus arguments to fixed-arity function values are
dropped, and `arguments.length` no longer counts a `this` parameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:03:44 -07:00
Malte UblandClaude Opus 5.5 f42fd146d9 Lower esbuild's lazy ES modules; select @types-typed packages by default
An __esm initializer becomes a run-once closure over its body in the
bundle's own scope. Interop bindings it assigns once from require()
become that require at the assignment, with their member reads respelled
as reads of the required module; a namespace read only through members
becomes an object literal of getters over the module's live bindings.
Asynchronous initializers, initializer results, and namespaces kept as
values (a memoized lazy loader) stay refused with a message naming them.

The default selection now also compiles a package typed by a
DefinitelyTyped twin that describes its installed release (same major,
or major.minor below 1.0), and untyped dependencies of compiled
packages. A twin for another release is refused with both versions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:00:32 -07:00
Malte UblandClaude Opus 5.5 7b1556e0f6 Compile function-local classes whose private names are used only on this
Each evaluation of a class declared inside a function creates fresh
private names, while the compiled class shares one layout across
evaluations, so any private member refused the class. undici's webidl
createIterator declares such a class (FastIterableIterator) at module load.

The two agree when every private name is used only on `this` inside the
class's own instance members (`this.#x`, `#x in this`), never inside a
nested class or non-arrow function, and the class has no static members:
each instance then meets only the methods of its own evaluation. Those
classes now compile; everything else keeps the refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:43:29 -07:00
Malte UblandClaude Opus 5.5 a953203d36 Give key-table symbols that share a description their own slots
undici keeps two key tables that both hold a `Symbol('close')`, and
MockClient inherits a slot under one while declaring a method under the
other. Symbol slots were named by their printable spelling, so the two
collided ("distinct symbol keys sharing the printable name").

A key-table key whose description an earlier symbol identity already holds
now takes an ordinal in its internal slot name; inspect drops the ordinal
and prints Node's spelling for both. Const keys keep their plain names, so
programs that compiled before are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:43:29 -07:00
Malte UblandClaude Opus 5.5 2eb05d6e4f Record TypeScript order baselines for the class hierarchy corpus programs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:43:24 -07:00
Malte UblandClaude Opus 5.5 40ca92fa13 Format
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:31:49 -07:00
Malte UblandClaude Opus 5.5 506b7994ba Type chunk records once so the compiler compiles itself
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:31:49 -07:00
Malte UblandClaude Opus 5.5 bab3b98001 Record order baselines for the new npm cases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:31:48 -07:00
Malte UblandClaude Opus 5.5 405f643742 Judge bundled packages by the files their imports can load
Interop the rewrite cannot finish now refuses its package only when the
compiled program can load that file: the module order closed over every
literal require() and import() and, in package JavaScript, every import
and export declaration (a computed specifier keeps every file loadable).
A package reached through a leaf subpath and types from its main entry
stays static. Auto mode also no longer refuses a whitespace/syntax
minified bundle whose interop helpers keep their names: the recognizers
judge it instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:31:48 -07:00
Malte UblandClaude Opus 5.5 796b1a9740 Read arguments as a value in strict CommonJS functions
A CommonJS function with parameters that reads `arguments` was refused
because sloppy-mode `arguments` aliases the parameters. Strict code (a
'use strict' prologue on the file or an enclosing function, or a class
body) and functions with non-simple parameter lists (defaults or
destructuring) get Node's unmapped object, whose elements never alias the
parameters, so they now take the hidden arguments array the way ES module
functions and class methods already do. undici's
`webidl.argumentLengthCheck(arguments, 1, 'getCookies')` compiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:18:25 -07:00
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