Commit Graph
2969 Commits
Author SHA1 Message Date
Luis Alvarez D 04583b419f Merge branch 'main' of https://github.com/vercel-labs/scriptc into spli-commonjs-require-bindings 2026-10-22 23:05:49 -05:00
Luis Alvarez D c009b582e2 Support split CommonJS require bindings 2026-10-22 23:05:28 -05:00
Chris Tate 608c6202d4 Spread untyped iterables into Math.min and Math.max (#1928) 2026-10-22 23:02:05 -05:00
Chris Tate ac3cb1c98f Merge main 2026-10-22 22:59:19 -05:00
Chris Tate 7d424368ce Dispatch splice on arrays of unknown elements (#1935) 2026-10-22 22:57:55 -05:00
Chris Tate e3e955eda0 Register Math.min and Math.max spreads as generator steps
dyn.mathFold steps the iterator of an untyped iterable, so the generator
dispatch pass now counts it among the runtime entry points that step
iterators. A generator whose only consumer is Math.min or Math.max then has
its step hooks registered, and the spread folds its values instead of
throwing "Reading this native iterator result has no lowering".
2026-10-22 22:55:26 -05:00
Chris Tate 97921c2764 Read and write Error.stackTraceLimit through the Error constructor value (#1932) 2026-10-22 22:21:42 -05:00
Chris Tate 143f2e11e9 Keep an own stackTraceLimit on the Error value ahead of the limit 2026-10-22 22:15:57 -05:00
Chris Tate 75cf8f0e52 Run zod 4's core constructor factory in the dynamic tier (#1918) 2026-10-22 22:08:46 -05:00
Chris Tate 6a1e5103f0 Array keys and properties: non-index number keys, Object.keys, console.log (#1769) 2026-10-22 22:00:27 -05:00
Chris Tate 77c0636826 Include star re-exports in namespace objects, and read causes and instanceof Error like Node under a runtime Error parent
- A module namespace converted to an untyped object, and a computed
  namespace read, use the module's resolved exports, which include the
  members of `export *` declarations.
- `super(message, options)` through a runtime Error parent installs the
  cause as Error does: an inherited `cause` counts, the value is read
  through its getter, and the getter's exception propagates.
- `value instanceof Error` on an instance of a class whose heritage is
  evaluated at run time tests that class's prototype chain for
  Error.prototype. A program with such a class checks prototype chains
  for `instanceof Error` on untyped values.
- The cache of names written on the global object is a Lowerer field
  instead of a WeakMap keyed by the Lowerer.
- errors.test.ts: `"prototype" in Map` answers from the builtin
  constructor's keys now, so it leaves the refused reads and joins a
  check of `in` probes against Node's answers.
2026-10-22 22:00:01 -05:00
Chris Tate 12166956b8 Spread protocol iterables into Math.min and Math.max
Math.min(...src) and Math.max(...src) over an untyped source now take its
values the way a spread does: arrays, strings, bytes and collections pack,
a native iterator (a Map's keys()) drains, and other iterables (a generator,
an object with a Symbol.iterator method) step their iterator to the end.
Before, only the packing kinds were accepted and every other iterable threw
the spread TypeError.
2026-10-22 21:57:29 -05:00
Chris Tate 09c132fb2e Merge main 2026-10-22 21:55:35 -05:00
Chris Tate 9dbb16d7f8 Support new.target in class constructors (#1917) 2026-10-22 21:52:42 -05:00
Chris Tate ee79ce16e3 Give the zod3-infer npm case the long per-case timeout
A sanitized build of zod3-infer compiles zod 3's types module, as zod3's
does, and takes about as long as the default 120 s on a CI runner.
2026-10-22 21:49:03 -05:00
Chris Tate df9f83bbc1 Refuse new.target over function-local subclasses lowered later
Lowering collects a function-local class with the body declaring it, so
a local subclass whose function lowers after its base's constructor was
missing from new.target's dispatch, which then answered with the base for
its instances. The refusal no longer depends on that order: the program's
function-local classes are found once, and one that extends the
constructor's class, directly or through other classes its extends
clauses name, refuses new.target as a collected one does.
2026-10-22 21:47:37 -05:00
Chris Tate e0f6f094bf Keep int32 slots for long number arrays with checked integer stores (#1931) 2026-10-22 21:46:03 -05:00
Chris Tate bbc9a4e5f9 Read and write the "undefined" property for element indexes that are undefined (#1930) 2026-10-22 21:39:58 -05:00
Chris Tate e665913200 Print accessor properties of arrays and fence number keys in defineProperty
- An enumerable getter or setter on an array printed as undefined: the
  property loop rendered the accessor's unused data slot. It now prints
  [Getter], [Setter] or [Getter/Setter], as util.inspect does.
- Object.defineProperty on a live native array with a number key that is
  not an array index (-3, 1.5) skipped the numeric-property fence and
  lost the property on commit. A number key now takes the same fence as
  its string spelling.
2026-10-22 21:39:37 -05:00
Chris Tate 17826afb23 Renumber the new.target corpus programs to 7560 and 7561
7362 and 7363 are taken on main by other programs. The costs and the
order-parity baselines move with them.
2026-10-22 21:37:39 -05:00
Chris Tate fe406429eb Keep int32 slots for long number arrays with checked integer stores
A local number[] candidate whose stores are exact integers but not all
proven int32s (a distance array updated with dist[v] = dist[u]! + 1), and
that is only ever allocated by new Array(n).fill(v), now holds SCR_ELEM_I32
slots when n is at least 4096. A shorter fill creates doubles, which fit a
typical L1 data cache. The first store of a value that is not an int32
widens the array to doubles in place (scr_arr_widen_i32).

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

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

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

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

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

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

Fixes #1641.
2026-10-22 21:23:57 -05:00
Chris Tate 6115135d96 Dispatch new.target over generic subclasses and own static names
A constructor's new.target dispatch skipped generic class families, so a
generic subclass instantiated only in a body lowered later answered with
the base's name, and `new.target === Base` held for its instances. A
family now has its own arm, tested by its instanceof interval, which holds
every instantiation: `new.target.name` reads the family's name and
comparisons with a program class are false. Under an instantiation, the
generic subclasses of its family are candidates too. `new.target` as a
value refuses generic subclasses, which have no common class value.

`new.target.name` reads a class's own static `name` field, which replaces
its name (a subclass has its own name); a static method `name` or a
non-string field is refused.
2026-10-22 21:18:48 -05:00
Malte UblandClaude Opus 5.5 a9b1d15eea Map unresolved JSDoc results of generic JavaScript calls as checked-dynamic
A published bundle's JSDoc can name a type that only its .d.ts declares:
hono's bearerAuth carries `@template E` and `@returns {MiddlewareHandler<E>}`
without importing MiddlewareHandler. The checker types that result as its
error type, and a call of such a generic JavaScript function was refused
with SC2001 ("values of type 'MiddlewareHandler<E>' cannot be compiled").
The unresolved annotation is documentation, not a contract, so the call's
result is now checked-dynamic, as an `any` result of a TypeScript generic
already is.

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

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

The npm fixture stream-shims, whose test expected a deferred SC1090, now
runs to a later deferred refusal, SC2020 (write completion callbacks
taking the error argument); its expectation names that code.
2026-10-22 21:14:00 -05:00
Malte UblandClaude Opus 5.5 3041df387d Spread untyped iterables into Math.min and Math.max
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:59:56 -07:00
Malte UblandClaude Opus 5.5 7f7e11a1a7 Read and write Error.stackTraceLimit through the Error constructor value
zod 4 saves and restores the limit through an alias,
`const E = "captureStackTrace" in Error ? Error : null`. The interned
Error constructor value now answers `stackTraceLimit` from the same limit
the static member and stack capture use, and numeric writes set it, so
`E.stackTraceLimit` reads 10 instead of undefined and `E.stackTraceLimit = 0`
reaches `Error.stackTraceLimit`.

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

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

ir/int32-arrays.ts proposes the candidates and inlines their element-reader
helper calls; the int32 slot analysis keeps the proven ones, and
selectInt32Arrays rewrites the module for exactly those, so a program
without an int32 array emits the same LLVM IR as before.
2026-10-22 20:52:38 -05:00
Chris Tate dcd86a17da Merge main 2026-10-22 20:52:05 -05:00
Chris Tate fc05167a29 Merge main 2026-10-22 20:50:35 -05:00
Chris Tate 3991a49094 Hold any later value in JavaScript var bindings
A JavaScript `var` took the checker's type of its initializer, which no
later write is checked against in a JavaScript file. js-yaml 4.1.1 failed
on every timestamp with "a 'string' value is not representable in the
target union": its `var fraction = 0` holds the fraction's digits as a
string while it pads them (`fraction = match[7].slice(0, 3)`). Every YAML
error also threw the "assignment to non-variables" fence instead of the
YAMLException, because its error mark is a `var` object literal that
gains a member (`mark.snippet = snippet(mark)`).

- A `var` whose writes the declared type cannot hold is checked-dynamic,
  by the rules `let` already uses (jsBindingHasOpenWrites), and so is a
  `var` whose object literal gains members or carries methods
  (jsDeclarationExpandsObject, shared with `let` and `const`).
- `+=` of a string onto a number or boolean binding (`width += "px"`)
  counts as such a write, for `var` and `let`.
- A JavaScript function that returns such a binding returns a
  checked-dynamic value (an async one a promise of one) instead of the
  initializer's type. A string binding that is only appended to stays a
  string.
2026-10-22 20:40:56 -05:00
Chris Tate 8c67f04062 Throw Node's TypeError for class constructors called through call/apply (#1892) 2026-10-22 20:36:01 -05:00
Chris Tate 5966f23bd3 Keep the class-value call fence when a subclass declares a static call
`C.call(...)` through a class value typed as `Base` may run a subclass's own
static `call`, which Node calls instead of the class constructor. The
constructor-call lowering now applies only when no class the receiver can
hold declares a static `call` or `apply`: the class and its bases (any
static member of that name, including accessors, fields without
initializers and computed names), and, for a receiver that may hold a
subclass, every subclass. Otherwise the call keeps its existing
diagnostic.
2026-10-22 20:33:33 -05:00
Chris Tate 210dee9eb0 Read the global object without rebuilding its name table (#1895) 2026-10-22 20:26:51 -05:00
Chris Tate a9ee38611e Register the global object's names again after a library re-init 2026-10-22 20:20:56 -05:00
Chris Tate 8807c24526 Compile zod 3 natively (#1835) 2026-10-22 20:13:04 -05: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 636ffa21be Merge main 2026-10-22 19:59:51 -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 7a96b0de57 Spread untyped sources into typed object literals (#1893) 2026-10-22 19:48:13 -05:00