Commit Graph
4679 Commits
Author SHA1 Message Date
Malte Ubl f593e17e1d Merge remote-tracking branch 'origin/main' into feat/decl-boundary 2026-10-22 18:12:28 -07:00
Malte UblandClaude Opus 5.5 982572c173 Dispatch splice on arrays of unknown elements
Arrays the checker types with unknown elements are stored checked-dynamic,
and their methods dispatch on the runtime array. splice was missing from
that dispatch table, so unknown[] receivers refused it, including generic
bodies instantiated at unknown (pool.splice(i, 1) in a sample<T> helper
gets such an instantiation once a program has a dynamic tier). The
runtime's dynInvoke already implements splice; the table now names it,
and arrays of unknown elements take the dispatch from the array-method
lowering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:10:08 -07:00
Chris Tate ae5c7d7780 Map interfaces that extend an any-typed base (zod z.infer) to checked-dynamic values (#1912) 2026-10-22 20:08:05 -05:00
Chris Tate 1660531c7b Support FinalizationRegistry with bookkeeping and no cleanup callbacks (#1854) 2026-10-22 20:03:31 -05:00
Chris Tate 43917080c9 Reach a class's methods from 'this' in its static initializers
The cycle walk resolved only identifiers, so a static field initializer or static block that called a method through 'this' did not reach the method, and its reads of a cycle binding declared later lost their temporal-dead-zone checks. 'this' in static code now reaches the class's stored unit, as naming the class does.
2026-10-22 20:01:47 -05:00
Chris Tate 636ffa21be Merge main 2026-10-22 19:59:51 -05:00
Chris Tate 0d016b7554 util: runtime format strings, inspect.custom and colors, untyped promisify 2026-10-22 19:57:46 -05:00
Chris Tate fbaa71a66f Run the fromEntries aliasing program and drop compiled Iterable entries from the iterator fences
Since Iterable<T> values compile (#1875), two diagnostics fixtures changed:

- fromentries-aliased-entries.ts compiles and prints what Node prints:
  the entries' one array stays shared by both fields. It moves to the run
  corpus as 7440-fromentries-aliased-entries.ts.
- iterator-slot-fences.ts no longer reports its Iterable<number>
  parameter (SC2020) and the call through it (SC2004). That part leaves
  the fixture, which keeps the class-iterator fence; corpus 7380-7383
  cover Iterable values.
2026-10-22 19:54:17 -05:00
Chris Tate b19a589c12 Keep the global object behind a typeof globalThis intersection cast (#1894) 2026-10-22 19:53:35 -05:00
Chris Tate b7d355ed13 Stringify live typed records and arrays without a key list (#1897) 2026-10-22 19:52:19 -05:00
Chris Tate b386e91958 Compile instanceof against constructors held as values 2026-10-22 19:50:12 -05:00
Chris Tate 7a96b0de57 Spread untyped sources into typed object literals (#1893) 2026-10-22 19:48:13 -05:00
Malte UblandClaude Opus 5.5 f210b1102a Run zod 4's core constructor factory in the dynamic tier
zod 4 builds every schema through `$constructor`, a factory of JavaScript
patterns the dynamic tier refused or answered wrongly. Each is fixed
generally:

- `"key" in BuiltinCtor` (`"captureStackTrace" in Error`) folds to Node's
  answer from a table of the builtin constructor globals' keys.
- A class whose heritage evaluates to Object or Error at runtime
  (`class D extends (params?.Parent ?? Object)`): Object means no
  heritage; Error links Error.prototype and super() installs message,
  cause and stack.
- `Object.defineProperty` on a class value boxes to its constructor, and
  a JavaScript class targeted by defineProperty gets a live static table.
- A Symbol.hasInstance defined on a function's own table answers
  untyped `instanceof`; an error constructor held as a value answers by
  error kind.
- A builtin error constructor stored in a slot of its class value type
  (`{ Parent: Error }`) is the class value, not a checked cast that fails.
- A function hoisted above a local class it references captures the
  class binding as a TDZ box the class statement initializes.
- Names a program stores on the global object read back from it instead
  of folding to undefined.
- A module namespace whose exports never change converts to an untyped
  object (`{ ...ns }`).
- An untyped call of a String-only method name (`x.trim()`) dispatches on
  the receiver's runtime kind, so objects defining that member work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:43:27 -07:00
Malte UblandClaude Opus 5.5 ac940ed936 Support new.target in class constructors
Inside a class constructor new.target is the class of the instance under
construction. A compiled new allocates the instance with its concrete class
before the constructor chain runs, and the class hierarchy is closed, so
new.target lowers to a static dispatch over the constructor's class and its
descendants (deepest instanceof first). new.target.name reads the exact
class's name and new.target === C is an exact-class test, without a class
value, so subclasses may construct with other signatures. new.target as a
class value requires every subclass to share the constructor's signature.
Plain functions and function-local subclasses keep explicit refusals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:43:09 -07:00
Chris Tate 3cace3e785 Throw Node's TypeError for class constructors called through call/apply
`C.call(thisArg, ...)` and `C.apply(thisArg, args)` on a program class that
declares no static `call` or `apply` now lower as a call through the boxed
class value. It evaluates the arguments in order, applies apply's
argument-list checks, and throws `TypeError: Class constructor C cannot be
invoked without 'new'` with the runtime class name, as Node does. Before,
TypeScript reported SC2020 at compile time and JavaScript threw the
standard-library fence's Error at run time. This covers ES5 constructors that
call a class as their base (`function Sub() { Base.call(this); }`).

Runtime-provided classes (Error, EventEmitter, streams, EventTarget), classes
with runtime statics, spread arguments, and receivers other than a name,
`this` or a namespace member keep their existing lowering.
2026-10-22 19:36:04 -05:00
Chris Tate 2442872d62 Version charAt loops and compare characters as code units (#1889) 2026-10-22 19:28:24 -05:00
Chris Tate ba8889104a Free arrays whose elements need no release without a teardown (#1890) 2026-10-22 19:27:12 -05:00
Chris Tate 36209f6d17 Keep the unreached JavaScript coverage fixture failing to compile
The fixture's unreached statement pushed values onto `{ items: [] }`, which
mapped to a `number[]` and could not hold them. Since #1881 a JavaScript
`[]` literal field is a checked-dynamic array, so the push compiles and the
test "a failed unreached JavaScript statement is always explained" found no
failed statement. The fixture now starts `items` as `[0]`, a `number[]`, so
the push of a boolean onto it still does not compile and reports SC1090 at
the same line.
2026-10-22 19:24:07 -05:00
Chris Tate d4a7b82dd8 Free arrays whose elements need no release without a teardown
When the last reference to an array dropped, the runtime ran the generic
teardown: scr_rc_destroy's depth bookkeeping, an indirect call to the array
destroy, and a loop over the element slots. Arrays of numbers, booleans and
compact ProgramLifetime references release nothing, and neither do arrays of
other ProgramLifetime instances, whose release does nothing.

- scr_arr_release frees an acyclic array without an ext directly when its
  elements are numbers, booleans or compact references, or its descriptor
  has SCR_ARR_OPS_RELEASE_NOTHING. Every other array keeps the full
  teardown.
- The backend sets SCR_ARR_OPS_RELEASE_NOTHING in the descriptor's flags
  word on 64-bit targets for ProgramLifetime element types that are not
  compact, plain or as the pointer arm of a nullable union, in programs
  without lifetime regions. Registration copies it into a table indexed by
  the descriptor's registry id before the id is published. Descriptors
  without the flag read 0 and keep the full teardown.

Corpus 7401 drops arrays of ProgramLifetime instances, numbers and booleans
through every array operation, with holes and far indices, next to arrays of
ordinary objects, and matches Node.
2026-10-22 19:22:55 -05:00
Chris Tate 93d2797bac Version charAt loops and compare characters as code units
`for (let i = 0; i < s.length; i++) if (s[i] === " ") n++` and the same
loop over `s.charAt(i)` got no loop version: `s[i]` lowers to charAt,
and the version covered only charCodeAt reads. Each character paid the
inline helper's ASCII and bounds tests and kept its opaque fallback.
`const c = s[i]; if (c >= "a" && c <= "z") n++` also built the
character string and compared strings twice.

- The typed-array loop version covers `s.charAt(index)` like
  `s.charCodeAt(index)`, and a counted loop that reads either at its
  counter takes an integer counter. In the fast copy, a charAt used as a
  string is the immortal one-character string for its byte, and the
  equality and switch rewrites to charCodeAt read the byte.
- A comparison of `s.charAt(i)` with a one-unit literal compares the
  code unit, as the equality does. An index out of range reads "",
  which is below every one-unit literal; its unit read is NaN, which the
  unordered predicates count as below.
- A const string local initialized from a one-argument charAt and used
  only in one-unit equalities, comparisons and switches holds the code
  unit instead (char-unit-locals.ts).
2026-10-22 19:22:20 -05:00
Chris Tate 3024e57eeb Revert "Enumerate each record in the key order its object literal wrote (#1802)" 2026-10-22 19:02:41 -05:00
Chris Tate 22d136e6ac Read records by keyof keys when their fields differ in type (#1883) 2026-10-22 18:54:16 -05:00
Chris Tate 3a23e98c7d Merge main 2026-10-22 18:45:27 -05:00
Chris Tate 53c43dd140 Merge main 2026-10-22 18:44:10 -05:00
Malte UblandClaude Opus 5.5 481f9a5e31 Stringify live typed records and arrays without a key list
JSON.stringify of a value reached through `unknown` walks native records
and arrays through their live capsules. The protocol walk built a
key-string array for every record and formatted every array index with
snprintf into a fresh string, only to look the member up again by that
key. A JSON API response of a few records spent most of its
serialization time there.

Without a replacer, walk a live record by field index over a snapshot of
its present fields (its keys are the immortal field names) and a live
array by element index, forming an index string only for a toJSON call
that receives it. Members are still read as scr_json_member reads them,
so callbacks that mutate or materialize the container see the same
results. Index keys elsewhere use the integer formatter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:43:54 -07:00
Malte UblandClaude Opus 5.5 cbafabe610 Keep the global object behind a typeof globalThis intersection cast
`globalThis as typeof globalThis & { key?: T }` (the usual global
singleton spelling) matched the WebAssembly cast lowering, because the
target inherits the global's own WebAssembly member, and produced a fresh
object holding only WebAssembly on every evaluation. Writes through the
view were lost, so a `g[key] ??= new Repo()` singleton was rebuilt on
every call and its state never persisted.

Take the WebAssembly view only when the target's member is not the
global's own, and map an intersection that contains the intrinsic
`typeof globalThis` to the global object (dyn), as `typeof globalThis`
alone already maps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:43:46 -07:00
Chris Tate f132cf5f39 Compile Iterable<T> values (#1875) 2026-10-22 18:30:45 -05:00
Chris Tate e3e37940f1 Iterate iterable unions without an array arm and adapt into optional Iterables
for-of over an iterable union read its array arm without checking that it
had one, so iterating a `Set<number> | Generator<number, void, undefined>`
crashed the compiler; a union without an array arm now runs the generator
loop only. A generator with a non-void return channel adapted into a
required Iterable<T> slot but not into an optional or nullable one, where
the conversion failed with SC2003; the slot conversion now accepts
iterable unions with undefined or null arms beside the iterable arms.
2026-10-22 18:27:44 -05:00
Chris Tate de28aeca20 Map JavaScript [] literal fields dynamically and keep union-added fields unit-only
Two JavaScript object literal mappings from #1799 broke packages.

A property initialized to `[]` has the checker type `never[]`, and never's
element maps to f64. A literal with such a property builds as a
checked-dynamic object when it lowers on its own. A literal nested in one
that maps to a record (`{ common: { issues: [] }, path: params?.path || [] }`,
zod 3's parse context) lowers against its record type, so the property was a
`number[]`. `ctx.common.issues.push(issue)` then threw "TypeError: expected
number at $, got object", where Node pushes the record. Such a property now
maps to the checked-dynamic array, as a property initialized to `undefined`
or `null` maps to the checked-dynamic value.

That `undefined`/`null` rule also matched the properties the checker adds
when it normalizes a union of literals: `{ _tag, value } | { _tag, error }`
gains an optional `error?: undefined` and `value?: undefined`. Mapping those
to the checked-dynamic value let `{ _tag, value }` fit both arms, and Effect
4's CLI parser (`consumeFlagValue`) failed with SC2003 "union conversion has
no unambiguous layout mapping". The rule now skips optional properties, so
they keep their unit-only type and each literal converts to its own arm.
2026-10-22 18:24:10 -05:00
Chris Tate e0c70c0c01 Enumerate each record in the key order its object literal wrote (#1802) 2026-10-22 18:23:26 -05:00
Chris Tate 81891d55f7 Fix boxed record field hooks and moving key writes, and merge main 2026-10-22 18:19:14 -05:00
Chris Tate aaca1f1e2b Count the arguments a function receives when it is called through a value
`arguments.length` in a typed function folds to a constant. In a function
declaration it was the declared parameter count even when the function is
called through a value with more arguments: Node prints 4 for
`[1].map(f)` with `function f(v) { return v + arguments.length; }`, the
compiled program printed 2.

A top-level function declaration that reads `arguments` and that the
program uses as a value now gets a count slot. Its value is an adapter
that stores the parameter count of the function type the value is passed
as (3 for map, 4 for reduce, 2 for a sort comparator) and forwards; the
function reads the slot on entry and clears it, so a direct call sees the
declared count.

Function expressions and declarations passed where the expected function
type has optional or rest parameters (a replace callback) are called with
a varying count and are refused with SC1090 instead of folding to a wrong
count, as are methods, nested functions, and arrows inside a function
used as a value.
2026-10-22 18:17:48 -05:00
Chris Tate a0c207cc25 Merge main 2026-10-22 18:12:20 -05:00
Malte UblandClaude Opus 5.5 f769a973df Map an interface that extends an any-typed base to a checked-dynamic value
In a compiled program an opted-in package is typed from its JavaScript, so a
type computed from members only its declarations have (zod's
z.infer<typeof Schema>) is any. An interface extending it mapped to a record
of its own declared fields, and reads of inherited fields and spreads into it
were refused. It is now checked-dynamic, like the base.

The zod3-infer npm case uses z.infer, z.input and z.output across objects,
interfaces extending inferred types, spreads, optional and nullable fields,
unions, discriminated unions, transforms, defaults, .extend and arrays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:04:47 -07:00
Chris Tate dc2b238df7 Revert "Dispatch checked-dynamic method calls on method ids (#1851)" 2026-10-22 18:02:51 -05:00
Malte UblandClaude Opus 5.5 089ec6739b Compile Iterable<T> values
Iterable<T> maps to a union of the sources that keep their own
representation (T[], Set<T>, Map<K, V> for [K, V] entries, string) and a
generator over T that holds every iterator. An array stays the same
multi-pass object; a generator stays single-pass. for-of gives the array
arm its own array loop, so iterating an Iterable that holds an array runs
at array speed. Spread, Array.from, the Set/Map constructors and
Object.fromEntries consume it; optional Iterables narrow into it;
generator functions may declare an Iterable return. Class iterables and
typed arrays are refused at the conversion with a named diagnostic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:00:41 -07:00
Chris Tate 78c9317a2c Construct builtins through stored constructors and fix function-local class names and dispatch (#1807) 2026-10-22 17:56:42 -05:00
Chris Tate db7bd1111f Find Buffer search candidates with memchr (#1830) 2026-10-22 17:56:21 -05:00
Chris Tate a147bb5b52 Read the characters of ASCII strings in versioned scan loops as bytes (#1846) 2026-10-22 17:50:54 -05:00
Chris Tate 5372c69459 Pass { signal } to stream operator callbacks and Duplex.from bodies 2026-10-22 17:50:29 -05:00
Chris Tate 98712c364a Merge main 2026-10-22 17:49:58 -05:00
Chris Tate 8bc8fc54ca Bound string reads of while loops by their counter tests
With while-loop versions, `while (pos < end && s.charCodeAt(pos) === 32)
pos++` took a version whose guard could never hold. The counter scan
records only typed-array accesses, so the string read fell back to the
statically ranged check. For a counter held in an int32 slot, that check
asks for a length above 2^31 - 2. The loop paid for the guard and ran its
checked copy.

The counter scan now records `s.charCodeAt(i)` and `s.charAt(i)` of a
string like typed-array reads. Their guard compares the counter's bound
with the string's length, as for a typed array. Corpus 7302 adds while
loops over counters below a string's length.
2026-10-22 17:47:26 -05:00
Chris Tate edb2755a9b Merge main 2026-10-22 17:46:30 -05:00
Malte UblandClaude Opus 5.5 05e0a19ff4 Spread untyped sources into typed object literals
An object literal that spreads an `any` value into a typed slot
(service.list({ ...c.req.valid("query"), offset: 0 }) where the validated
value is untyped) was refused, because only known record shapes spread.
TypeScript types such a literal `any` and the copy holds whatever own
properties the source has at runtime, so it now builds as a
checked-dynamic object and checks into the expected record like any other
`any` value assigned to that slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:43:33 -07:00
Malte UblandClaude Opus 5.5 268194e18e Read records by keyof keys when their fields differ in type
A keyed read of a record whose declared fields have different types, with
a key the checker types as keyof the record (row[key] in a loop over
Object.keys(patch) as (keyof Row)[]), was refused: the read needed one
common field type. The checker already types such a read as the union of
the field types; when every declared field surfaces in that union, the
existing per-field dispatch wraps each hit into its arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:43:28 -07:00
Malte UblandClaude Opus 5.5 fd4f14936f Check cycle bindings only where early code can reach the reading function
The import-cycle analysis treated every deferred reference (a function
body, parameter default or instance field) to a cycle binding as early
once any user code could run before the declaration completed. With a
static evaluation order it now walks the cycle's top-level code in
evaluation order, following stored callables (function and class
declarations, functions and methods stored by initializers) across the
cycle's modules through import aliases, and counts a deferred reference
as early only when that walk reaches it before the declaration ran.
Top-level reads, unknown orders, top-level await and default-exported
declarations keep the previous rule.

This removes the SC1016 refusal on zod 4's `fn[CONSTANT_CATCH] = true`
(a computed key folded from a const's literal type inside a function
nothing calls during the cycle's evaluation), and drops initialization
flags and checks that can never fire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:39:40 -07:00
Chris Tate 289a6f619d Merge main 2026-10-22 17:39:40 -05:00
Chris Tate d8d74dab97 Keep declaration order and override placeholders in record spreads (#1834) 2026-10-22 17:32:44 -05:00
Chris Tate 6132fcf7c2 Dispatch checked-dynamic method calls on method ids (#1851) 2026-10-22 17:25:35 -05:00
Chris Tate 4f6c55a8e6 Narrow filter results through written type predicates with checked conversions (#1819) 2026-10-22 17:21:41 -05:00