Commit Graph
6750 Commits
Author SHA1 Message Date
Chris Tate 68f930bf53 Keep the global object in type assertions of globalThis (#1423) 2026-10-19 18:42:22 -05:00
Chris Tate 53af3dc930 Compare strings in UTF-16 code-unit order
Relational string operators (`<`, `<=`, `>`, `>=`) compared the UTF-8
bytes, which is code-point order. Node compares UTF-16 code units, so the
results differed for characters above U+FFFF against U+E000..U+FFFF and
for lone surrogates against surrogate pairs. The default `sort()`
comparator, `util.inspect` with `sorted: true` and dynamic comparisons
already used UTF-16 order; the default comparator of a dynamic array's
`sort()` did not.

Typed comparisons and the dynamic array sort now use `scr_str_cmp_u16`,
and the IR's `strCmp` node no longer has a `utf16` flag. The two orders
agree unless both of the first differing bytes are at least 0xED, so:

- the inline wrapper decides differing first words, or last words of
  equal lengths up to 16 bytes, in byte order when neither word has a
  byte of at least 0xED (a SWAR test). Other differing words, and the
  first bytes of shorter strings, decide by their first differing byte:
  in byte order when one of the two is below 0xED, and by plane when both
  are lead bytes from 0xEE to 0xF4 (a supplementary character orders
  below U+E000..U+FFFF);
- otherwise a proven-ASCII string on either side compares with `memcmp`
  (`scr_str_cmp`), and other pairs call `scr_str_cmp_u16_bytes`. It finds
  the first differing byte with words up to byte 32, then with 32-byte
  SSE2 or NEON blocks where the target has them. The call is marked as
  only reading its arguments, so repeated comparisons of one pair merge.
- `scr_str_cmp_u16` itself takes the same `memcmp` path for proven-ASCII
  strings.

The native compiler sorts record fields with `<` and checks the order
with `sort()`, so it reported SC9001 for 5888-inspect-sorted.ts, whose
object has the keys "\u{1F600}" and "\uffff". 5888 runs in the
native-frontend self-host check. Adds corpus 6023.
2026-10-19 18:10:54 -05:00
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 e29807f17b Route records into discriminated unions by their discriminant (#1414) 2026-10-19 17:12:52 -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 769e9e58cf Run the two npm cases the default selection now compiles
cjs-lexer-invisible and hoisted-workspace compiled only once their
untyped dependencies were selected; both now match Node in both lanes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:29:23 -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 d204ef71d6 Keep the private-brand diagnostics on shapes that still refuse
Function-local classes whose private names are used only on `this` now
compile, so the two diagnostics fixtures test a brand check on another
object instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:09:32 -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