Commit Graph
2626 Commits
Author SHA1 Message Date
Chris Tate 85936f9035 Merge main 2026-10-20 17:44:58 -05:00
Chris Tate 1abce1868c Build JavaScript default-export objects that hold class values (#1551) 2026-10-20 17:42:08 -05:00
Malte UblandClaude Opus 5.5 1ef31a1b74 Continue a failed checked cast with its value in a dynamic tail
`JSON.parse(text) as T`, `const x: T = anyValue` and `await res.json()
as T` threw `TypeError: expected X at $.path, got Y` when the value did
not fit `T`. Node never checks an assertion and continues with the value
it has, so the build-container listener rejected an order Node accepts
and a JWT with a string `exp` became a 500.

A failed cast now keeps its value when it initializes a `const`/`let`
binding declared in a function or block and only the code after the cast
reads the binding. That code (the statements after the cast in each
enclosing statement list, up to the declaration) lowers twice: the
static copy, unchanged, runs while the value fits; a dynamic copy, in
which the binding and everything derived from it is typed `any`, runs
after a failure, so missing members read undefined, wrong-typed members
keep their values, extra members stay visible in source order, and
objects keep their identity. The cast sets the flag that selects the
copy: the fused JSON.parse helper's slow path and the plain check call
scr_dyn_check_recover, which takes back the TypeError and parks the
value for checkedCast.fallbackTaken/fallbackTake.

A failed value that flows into a typed slot outside the region is
checked there, as any `any` value is: it continues when it fits and
throws at that use when it does not. A statement of the dynamic copy
with no `any` lowering becomes a runtime fence that throws only when a
failed value reaches it. Module-level bindings, regions with hoisted
declarations or super() calls, union targets and targets holding class instances, and
--tier=static-only (and library mode) keep the throwing cast.

`a?.b.c` on an `any` value now short-circuits only on `a`, so `.c`
throws when `a.b` is undefined, as in Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:25:33 -07:00
Malte UblandClaude Opus 5.5 e060eeae25 Iterate untyped values with for await, and delegate with yield* in async generators
- A JavaScript generator whose inferred return is `any` (it delegates to
  itself) is a generator with checked-dynamic channels.
- for await over a checked-dynamic value: a compiled async generator, the
  value's Symbol.asyncIterator result, or its synchronous iterator with
  awaited values; break and throws close it through an awaited return().
  A value that is not iterable throws V8's TypeError.
- yield* in async generators delegates through the same protocol and
  completes with the delegate's return value.

The runtime part is a new unit, scr_dyn_for_await.c, linked only by
programs that use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:17:52 -07:00
Malte UblandClaude Opus 5.5 a0e850a9f1 Share one listener list between typed and untyped uses of an EventEmitter
An emitter used both through its static type and through an untyped
reference aborted: a typed `on` followed by an untyped `emit` (or a typed
`emit` from a class body reaching a listener added through `any`) hit
"computed event collided with a fixed event", and an untyped
`emit('error', ...)` trapped outright. Listener entries held either a
typed adapter or a checked-dynamic thunk, and the emit core trapped when
an entry lacked the form the emit carried.

Entries may now carry both forms. Listeners registered through
scr_emitter_on_dyn keep their thunk beside the typed adapter; typed
emits call the adapter and untyped emits call the thunk with the
arguments as given. The class dispatch of compiled emitters registers
untyped listeners under an event typed code fixes with a typed adapter
for its tuple, and converts the arguments of an untyped emit of such an
event to the tuple for listeners with typed parameters
(scr_emitter_emit_mixed; a mismatch throws the checked conversion's
TypeError). Registrations and emits through untyped values otherwise go
through scr_emitter_on_any/emit_any: runtime adapters under 'error' and
the meta events, and Node's unhandled-'error' contract. The emit core
throws instead of trapping when an entry has no usable form. The
dynamic half of the registry sits behind a hook only on_any installs,
and on_flex/emit_flex keep their plain behavior, so programs without
untyped emitter calls link none of it.

listenerCount, eventNames, removeAllListeners, listeners, rawListeners,
setMaxListeners and getMaxListeners through an untyped value on a
compiled emitter answered "is not a function"; they now dispatch to the
native registry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:13:04 -07:00
Malte UblandClaude Opus 5.5 23db1f70fd Adopt thenables in await of untyped values, and read Promise.prototype as a value
- await of a checked-dynamic value is PromiseResolve then await, so an
  object with a callable then is adopted. The rewrite applies only when a
  program source can define a thenable; other programs keep the plain
  checked await.
- promise.constructor answers the Promise constructor; its prototype and
  Promise.prototype answer an object holding the runtime's then, catch
  and finally as non-enumerable own properties.
- Reflect.getOwnPropertyDescriptor and Reflect.defineProperty over checked
  objects.

Together these run execa 5's Promise mixin over checked objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:02:02 -07:00
Chris Tate 445b5a2ced Merge main 2026-10-20 17:01:18 -05:00
Malte Ubl 48ab597d3f Merge branch 'opt/untyped-async-promises' of github.com:vercel-labs/scriptc into opt/untyped-async-promises 2026-10-20 14:15:07 -07:00
Chris Tate db7df36656 Check an early read of a cycle class's name
- `C.name` on a class of an import cycle reads the name from the class
  object, so the reference gets the class's initialization check: an
  early read throws Node's ReferenceError instead of returning the name.
  Other classes keep the compile-time name.
- The static field storage of a flag without a class is a shared empty
  set, so the self-hosted frontend lowers cycle-initialization.ts.
2026-10-20 16:13:15 -05:00
Chris Tate 3d280218b3 Keep constructor-function stream methods on stream slot member calls; order 6471's file reads
Only pipe and unpipe destinations now take the constructor-function
stream probe (stream.dynNativePipe). Member calls on a checked-dynamic
stream slot use the boxed-native probe again, so a constructor-function
stream's own pause(), resume() and on() still run
(5820-node-stream-dyn-constructor-overrides).

6471 started its second file read on the first stream's 'end', so that
stream's 'close' could land after the second stream's data. The second
read now starts on 'close'.
2026-10-20 15:58:30 -05:00
Malte UblandClaude Opus 5.5 be9c18f0b3 Bind builtin static functions and read Buffer.alloc, fs.realpathSync and String methods as values
- bind/call/apply on builtin function values held as checked-dynamic
  functions (String.fromCharCode, JSON.stringify and the forms below).
- Function values for Buffer.byteLength (Node's runtime encoding ladder),
  Buffer.compare, Buffer.alloc (size check, then fill), JSON.parse,
  fs.realpathSync and fs.realpathSync.native (Node's options handling).
- A JavaScript string method read as a value answers String.prototype's
  function (smol-toml's `!!(''.isWellFormed)` probe).

The runtime entries live in a new unit, scr_builtin_values.c, linked only
by programs that read these values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:50:09 -07:00
Malte UblandClaude Opus 5.5 af214de7f6 Destructure runtime computed keys from records and checked-dynamic values
`const { [name]: v } = source` was refused unless the key folded to one
property name. The key now evaluates once, in element order, and the
element reads:
- the live property of a checked-dynamic value or a JavaScript record
  (its checked view), undefined when absent, so a default applies;
- the entry of a pure index-signature record, undefined when absent;
- the declared field a literal-union key names, when every name the key
  allows is a declared field stored with one type.

Nested patterns and parameters share the path (`const { signals: {
[name]: n } } = os.constants`). Rest elements beside runtime keys,
class-instance and string sources keep their refusals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:45:56 -07:00
Malte UblandClaude Opus 5.5 d1e39f3d8c Build JavaScript default-export objects that hold class values
`export default { parse, stringify, DateType, ParseError }` failed at
module initialization: the literal's width check against the default
binding's type found class members missing.

- A class name in a shorthand property (`{ ParseError }`) now evaluates
  to the class object, exactly like the long form `{ ParseError:
  ParseError }`; it had no lowering, so the JavaScript literal dropped
  the member.
- A class the program could not lower (one extending Date) is still
  dropped from the literal, its diagnostic deferred to the reads that
  reach it. The default binding of a JavaScript object literal now
  registers its type without such members, so the module initializes
  and the named exports work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:45:28 -07:00
Chris Tate c9c19d8fdc Keep the generator result of untyped async generator methods
- `%dyn.class.call` dispatch answers a promise for an untyped async method whose signature keeps the promise's payload. An async generator method also has the async flag, but its signature already returns the generator object, so the call now keeps that type. Before, a call from untyped code (`schema.issues(values)` over an `async *issues` method) failed IR validation with "type promise != return generator".
- thunkAsyncSlot keeps an async generator method's slot type the same way.
- Corpus 6419 adds an async generator method that the package's own untyped helper iterates with `next()`.
2026-10-20 15:39:16 -05:00
Malte UblandClaude Opus 5.5 b723155820 Run buffer-crc32's and yazl's capability probes as Node does
- A JavaScript binding written with a value whose representation differs
  from its initializer's (buffer-crc32's Int32Array table) stores a
  checked-dynamic value.
- A top-level JavaScript function declaration its module reassigns
  (yazl's self-redefining bufferFrom) lives in a mutable module global,
  assigned when the module starts; reads, calls and writes use it.
- Buffer.from with a non-literal encoding follows Node's fromString rules
  at run time, and copies arrays crossing by reference; array and byte
  sources ignore the extra argument.
- `new Buffer(...)` in dependency sources, where Node never warns, is
  Node's construction.
- Fixed-width read/write methods on Buffers held as checked-dynamic
  values dispatch through a separate runtime unit (scr_bytes_dyn.c),
  linked only by programs that call them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:26:02 -07:00
Malte UblandClaude Opus 5.5 60f2823faf Box methods answering a result or a promise of it as dynamic values
A method whose result is either a status record or a promise of one
(a parse step that hands off to a static async merge helper in async
mode) has a union result such as `Promise<A | B> | A | B`. The dynamic
conversion accepted promise arms in a union only when every other arm
was JSON-safe, so records holding dynamic, Map or Set members were
rejected even though the composite converter boxes them. Such methods
had no checked-dynamic form: no prototype data slot, and reads through
untyped code answered undefined ("this._parse is not a function" in a
validation library's object and record schemas).

The union conversion now also accepts composite-boxable arms; the
union converter already boxes each arm through its own converter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:22:05 -07:00
Malte UblandClaude Opus 5.5 b5e99ac67b Name the deepest failing path when a checked cast fails inside an optional record
A checked cast such as `JSON.parse(text) as Partial<T>` makes every member
an `X | undefined` union. When a present object failed X, the error named
only the union's own path ("expected object | undefined at $.order, got
object"), not the missing or mistyped member inside it.

When no arm of a union matches and the value is the one container kind
exactly one arm can take (an object for the union's only record arm, an
array for its only array arm, beside unit and scalar arms), the failure
block now runs that arm's builder, which throws at the deepest failing
path with its reason ("expected string at $.order.owner.id, got
number"). Other values keep the union's message.

Only the failure block of union dynCheck builders changes; the matching
path is untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:13:35 -07:00
Malte UblandClaude Opus 5.5 556120a00c Convert Promise and record results and record arguments in function adapters
A function value assigned to a slot whose signature is structurally
compatible but differently shaped used to compile to an adapter that
threw "the checker's loose function compatibility admitted the
assignment, but the call has no exact lowering" when called. The adapter
now converts:

- a Promise result whose payload widens into the slot's payload
  (`() => Promise<Message>` into `() => Promise<Message | undefined>`,
  or records with extra, optional and nested members): the promise keeps
  its identity, and its payload converts when it settles;
- a record result, and a record argument, that the other side's shape
  is a structural width of: the record width conversion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:10:37 -07:00
Malte UblandClaude Opus 5.5 8b4dc59819 Convert booleans with ToNumber in bitwise operators
&, |, ^, <<, >> and >>> accept boolean operands, converted as ToNumber
does (true 1, false 0), as JavaScript dependencies write them
(async-sema's `isFn(pauseFn) ^ isFn(resumeFn)`). Hoisted JavaScript vars
that may also hold undefined or null convert those to 0, which is what
ToInt32 makes of NaN and 0. Number operands lower exactly as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:58:11 -07:00
Malte UblandClaude Opus 5.5 fec779abbb Use the events module and EventEmitter as untyped constructor values
In JavaScript, `ReleaseEmitter = EventEmitter` over `const EventEmitter =
require("events")` was refused with SC1090 ("a runtime-provided class value
as an untyped value"), and `class Channel extends require("events")` failed
on the require call as a value. Bundled npm code does both, for example a
small semaphore that aliases EventEmitter and constructs its release channel
through the alias.

- The events module binding and the EventEmitter or stream class itself
  turn untyped as the runtime's untyped constructor that constructor
  functions already use (scr_dyn_builtin_class.c). `new` through it builds
  an emitter object with Node's listener API, and `===` against the class
  compares identity.
- `x instanceof EventEmitter` on an untyped value also recognizes a boxed
  compiled emitter (an instance of a program class extending EventEmitter),
  which the prototype test alone answered false for. The same holds through
  an untyped binding narrowed to the class.
- `class X extends require("events")` extends the runtime emitter, as
  through a require binding.
- A typed array of the class built from such values (`[EventEmitter]`) is
  refused with SC1090 rather than failing its class check at run time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:41:53 -07:00
Chris Tate 32fce771f3 Return undefined from overload calls whose generic body misses an index
A call that selects an overload signature of a generic function now
gives its instance the same result type as an implementation-signature
call: when the body can return an indexed read that misses, the result
includes undefined, as in JavaScript. Before, such a call failed at the
missing read instead of returning undefined.
2026-10-20 14:38:45 -05:00
Chris Tate a601cba268 Merge main 2026-10-20 14:27:36 -05:00
Chris Tate 5a987359ff Keep rest, default and async forms in runtime-option dns.lookup callbacks
- A dns.lookup callback whose options are known only at run time binds its parameters as checked-dynamic values. Rest parameters now pack the remaining arguments, optional and defaulted parameters stay omittable so a default applies when Node passes fewer arguments, and an async callback returns its promise instead of being refused with SC2001.
- Corpus 6453 covers an async arrow, an async function expression with a default, a default `family` with `all: true`, and a rest parameter.
2026-10-20 14:18:52 -05:00
Chris Tate 7b510efacd Merge main 2026-10-20 14:13:21 -05:00
Chris Tate 9758a56b7b Merge main 2026-10-20 14:10:57 -05:00
Chris Tate c90b321a5b Print an error's non-enumerable cause and an AggregateError's errors in its checked-dynamic rendering
An error rendered through its checked-dynamic node (a runtime error class,
or an error held as a subclass's `cause`) prints a non-enumerable `cause`
as `[cause]: value` and an AggregateError's `errors` as `[errors]: [...]`,
after its enumerable properties, as Node's formatError does. Before, a
subclass's cause that was an Error constructed with a `cause` option
printed without it: `{ cause: [Error: inner] }` where Node prints
`{ cause: [Error: inner] { [cause]: 'deep' } }`.
2026-10-20 13:59:33 -05:00
Chris Tate af724271c7 Return the Set from add() in arrows that return it
An arrow whose expression body is `s.add(n)` lowered the add as a discarded
statement, so `(n) => s.add(n)`, `(n): Set<number> => s.add(n)` and
`xs.map((n) => s.add(n))` were refused with "'void' values where 'Set<number>'
is expected". The body now counts as discarded only when the arrow's lowered
result is void and its contextual signature returns void, as in
`xs.forEach((x) => s.add(x))`.
2026-10-20 13:53:18 -05:00
Chris Tate 77da2ea474 Count every class a Pick or Omit view accepts among its values' origins
A Pick or Omit view of a program class also accepts other program
classes that have the picked members. When such a view flows into
another structural type, the classes behind that type now include those
classes, so writes to their instances keep the second type a live view
instead of a copy.
2026-10-20 13:50:32 -05:00
Chris Tate 3306c78914 Check the frontend's types against tsc-ts/api (#1538) 2026-10-20 13:49:45 -05:00
Chris Tate f3c4ba0c20 Run macOS CI jobs nightly and for pull requests that change darwin code
Pushes to main now run every lane except macos, so a merged commit no
longer queues the macOS native build, the darwin test slices and the
macOS LLVM artifact jobs. The nightly sweep and manual runs still run
every lane. Pull requests keep running the macOS lane when
scripts/ci-lanes.mjs selects it.

- The iOS runtime pack builds move from mobile_runtime_packs (linux lane)
  to mobile_runtime_packs_ios (macos lane), since they run on macOS
  runners. The Android pack stays in the linux lane.
- The macos_full dispatch input is replaced by macos_suite (full or
  darwin, default full). Manual runs run the complete macOS suite unless
  macos_suite is darwin.
- ci-lanes.mjs also selects the macos lane for paths naming kqueue, the
  runtime's platform header, and every file of the darwin suite in
  scripts/ci-test-plan.mjs (adding tests/harness/differential.test.ts
  and the darwin runtime pack's archive test).
- The required test check counts a skipped macOS job as passing when the
  macos lane is off, as it already did for pull requests.
2026-10-20 13:43:22 -05:00
Chris Tate 5765d89227 Check the frontend's types against tsc-ts/api
The frontend calls tokenStartOfNode, getTextOfNode, getSourceFileOfNode and
forEachChild instead of AstNode's position and traversal methods. Node lists
answer hasTrailingComma(), and a declaration's modifiers are a modifier list
with their flags.

Symbol, Type, Signature, IndexInfo, TypePredicate, Program, TypeChecker and
Diagnostic have tsc-ts/api's members and types. Compiler code reads symbol
names with symbolName(symbol).

tsc-ts goes to 0.0.5. api-conformance.ts asserts that AstNode, AstNodeList,
SourceFile and each node interface are assignable to tsc-ts/api's interface of
the same name, and names the members where the wire format differs.
2026-10-20 13:41:39 -05:00
Chris Tate bb1452ca5a Expect SC2020 from three web-stream npm programs
The fixture's launch() helper calls run().then, which used to be the
first statement these programs fenced at (SC1090). It compiles since
#1503, so node24-web-stream-pipe-abort and
node24-web-stream-readable-lifecycle now stop at 'new ReadableStream'
and node24-web-stream-tee at 'ReadableStream.tee', which have no
lowering (SC2020).
2026-10-20 13:28:32 -05:00
Chris Tate a0894cc488 Give constructor functions native stream state over the stream classes (#1533) 2026-10-20 13:21:25 -05:00
Malte UblandClaude Opus 5.5 836a58b912 Give require declarators beside other declarators no storage
A top-level var statement that mixes require() declarators with other
bindings, the shape bundlers emit (`var EventEmitter = require("events"),
Base = class extends EventEmitter {}`), registered a module global for each
require declarator, because the whole-statement skip only applied when every
declarator was a require. The statement lowering treats each require
declarator as alias plumbing and never assigns it, so a read that resolved
to that global saw null: `new EventEmitter` in a class constructor called
through a null class object and crashed with SIGSEGV. A builtin require in a
nested block (`if (c) { var fs = require("fs"); }`) had the same mismatch.

Collection now skips storage for each such declarator, with the same
registrations a whole require statement makes, so reads resolve to the
module as they do for a statement of requires only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:17:15 -07:00
Chris Tate 5344367219 Read constructor-function stream hooks when they run
A constructor-function stream now reads `_final` and `_flush` when its
writable side finishes, and `_transform` for each chunk, as Node does. A hook
assigned after `Writable.call(this)` or `Transform.call(this)` runs, and an
override of a PassThrough's `_transform` replaces the pass-through. Without
`_final` or `_flush` the finish continues as Node's does.

A runtime stream passed to a constructor-function stream's listeners, such
as the source of a 'pipe' event, is the view whose pipe and unpipe accept
constructor-function streams, so `src.unpipe(this)` in a 'pipe' listener
unpipes instead of throwing.
2026-10-20 13:16:29 -05:00
Chris Tate 178e524662 Compile events.once with an AbortSignal and over streams, and combine Promise.race entries around it (#1531) 2026-10-20 13:04:20 -05:00
Chris Tate 3751afa51f Merge main 2026-10-20 13:02:01 -05:00
Chris Tate 24c73ac7fb Compile any crossings: destructuring, namespace patterns, any parameters and async callbacks (#1503) 2026-10-20 12:58:51 -05:00
Chris Tate e0d14fefaa Merge main 2026-10-20 12:55:55 -05:00
Chris Tate e181687405 Merge main 2026-10-20 12:55:10 -05:00
Malte UblandClaude Opus 5.5 a12827cc35 Read absent process.versions entries as undefined under @types/node
@types/node declares http_parser, v8, uv, zlib, ares and modules as plain
strings, but the runtime's versions object holds only node and openssl. The
dyn read was narrowed to the declared string with a checked extraction, so
comparisons with undefined folded to false and other uses threw. Such reads
now keep the undefined arm, like the optional stream properties, and
unannotated bindings initialized from them store the same union.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:48:12 -07:00
Malte UblandClaude Opus 5.5 eef68a7a97 Answer promises from thunked and dispatched untyped async methods
An untyped JavaScript async method whose result the compiler keeps as
`dyn` (its storage passes through untyped helpers) has a `dyn` return
on its signature: the promise's payload, not the promise. Two call paths
took that as the call's own type and failed the IR check ("call … type
dyn != return promise"):

- thunkAsyncSlot's `$own` adapter, made when a TypeScript override of
  such a method keeps the inherited slot (an async-sema `acquire`
  override). The slot's ABI is now the body's promise.
- `%dyn.class.call` dispatch from untyped callers (zod's `parseAsync`
  and `safeParseAsync` through checked-dynamic receivers), which now
  answers the promise.

Prototype data slots also skip method values that have no checked-
dynamic form (an async method's promise-returning signature) instead of
building an invalid slot.

From the int/bc round-5 build of the build-container entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:33:40 -07:00
Chris Tate 73a595692f Let NodeJS stream interface slots hold JavaScript stream objects (#1524) 2026-10-20 12:32:16 -05:00
Chris Tate f284f94827 Merge main 2026-10-20 12:31:50 -05:00
Chris Tate 834a2bfc29 Merge main 2026-10-20 12:31:03 -05:00
Chris Tate 28f1646461 Unpipe JavaScript destinations on unpipe() without a destination
With checked-dynamic stream slots, readable.unpipe() also unpipes every
JavaScript destination stream.pipeDyn registered from the source, oldest
pipe first. Each destination hears 'unpipe' and the source pauses, as in
Node's unpipe(), which empties every pipe. Before, those pipes stayed
attached and later chunks still reached them.
2026-10-20 12:29:12 -05:00
Chris Tate 19835975ea Bind generic overload calls by the overload's own type parameters 2026-10-20 12:25:57 -05:00
Chris Tate cf003870ee Write invisible characters in the WebAssembly spec data as escapes 2026-10-20 12:18:55 -05:00
Chris Tate 8ac2d854cf WebAssembly W5: the Memory, Table and Global constructors, Module.imports/exports/customSections, and function references from tables (#1410) 2026-10-20 12:12:19 -05:00
Chris Tate ae3642d1bb Merge main 2026-10-20 12:08:37 -05:00