Commit Graph
3145 Commits
Author SHA1 Message Date
Chris Tate fc9a8650af Keep an Error's code slot in step with dynamic writes and own properties
Writes and deletes of `code` through an error's checked-dynamic node (a
`catch (e: any)` binding, a reflective write) changed only the node, so
typed `err.code` reads kept the old value. The slot sync that already
covered name, message, stack and cause now covers code: a string takes
the slot, any other value or a delete clears it. A write, delete or
Object.assign through an Error subclass's typed view now syncs the
slots the subclass inherits from Error after the class commit, which
writes only declared fields.

`'code' in err` read the code slot, so an own `code` set to undefined
answered false. It now asks the runtime: the slot holds a string, or the
error has an own `code` property on its node.
2026-10-20 03:05:32 -05:00
Malte UblandClaude Opus 5.5 c70d9d7e28 Refuse returns a generic callback's inferred result would drop members of
A callback passed to a generic call without a return annotation can
return values the checker unifies into a narrower result type ('return
items' beside 'return items.map((i) => ({ ...i, extra }))' infers
Item[]). Converting the extended objects into that type copied them
and dropped the added members, which Node keeps and a later wider view
reads back. Such a return is now refused by name, pointing at the
annotation that keeps the members.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:53:19 -07:00
Chris Tate ea611bec02 Convert object literals with accessors to unknown again
#1490 also took records with accessor slots out of the dyn-convertible
domain, which refused passing such a literal where `unknown` is
expected. Native APIs read these values through their accessor slots:
fileURLToPath, fileURLToPathBuffer and pathToFileURL call the `windows`
getter of an options object, so tests/corpus/node24-file-url-options.ts
stopped compiling.

Records with accessor slots convert to checked-dynamic values again.
JSON.stringify of their typed values stays refused, since the typed
serializer cannot invoke the getter.
2026-10-20 02:52:38 -05:00
Malte UblandClaude Opus 5.5 b74224f31c Throw checked-dynamic primitives as primitives so catch typeof tests see them
A value thrown from `unknown` storage (`throw values[i]`) that holds a
number, string or boolean now throws as that primitive, like a statically
typed `throw 5`. Before, it threw as a checked-dynamic reference, so
`typeof e === "number"` (and "string"/"boolean") on the catch
binding answered false and the narrowed reads were unreachable. A
primitive has no identity to keep, and reading the binding as `unknown`
converts it back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:50:32 -07:00
Chris Tate eda2c3104e Merge main 2026-10-20 02:49:54 -05:00
Malte UblandClaude Opus 5.5 a64d7f8ce1 Run JavaScript promise executors passed by name, and read catch (e: any) bindings dynamically
- `new Promise(run)` where `run` is a JavaScript function value whose
  parameters are untyped (the retry-helper shape `function run(resolve,
  reject) {...}; return new Promise(run)`) gets an adapter executor: it
  receives the native resolve/reject pair and calls the original with
  both boxed as checked-dynamic function values. This was a deferred
  runtime refusal inside such packages.
- A catch binding annotated `any` reads the caught value through its
  checked-dynamic face, as an unannotated `unknown` one does: member
  reads, expando writes, `delete` and `in` act on the thrown object, and
  a rethrow keeps its identity. It was refused (SC1063).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:49:51 -07:00
Malte UblandClaude Opus 5.5 1fb1d9c829 Let constructor functions inherit from EventEmitter and the stream classes
Bundled npm code (glob, yazl, from2, multistream) subclasses Node's
classes with constructor functions: util.inherits(Ctor, EventEmitter) and
EventEmitter.call(this). util.inherits received an identity token and
threw ERR_INVALID_ARG_TYPE, EventEmitter.call(this) and
EventEmitter.prototype were refused, and `this instanceof Ctor` against
a function was refused.

A new runtime unit (scr_dyn_builtin_class.c, linked by the new
dynBuiltinClass feature) provides EventEmitter, the legacy Stream and
the stream classes as untyped constructor functions with Node's
prototype chain. EventEmitter's prototype ports lib/events.js over the
object's own _events, _eventsCount and _maxListeners properties; Stream
adds the legacy pipe. util.inherits, Base.call/apply(this, ...),
Base.prototype, instanceof on untyped values and JavaScript value reads
of these classes use them. Constructing over a stream class throws
SC2020 where it runs. instanceof against a compiled constructor function
compares its prototype with the value's prototype chain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:49:36 -07:00
Chris Tate c8b22e3435 Merge main 2026-10-20 02:40:17 -05:00
Malte Ubl 7e6da3a488 Merge remote-tracking branch 'origin/opt/npm-polyfill-shims' into opt/npm-fs-extra-shim 2026-10-20 00:40:16 -07:00
Malte UblandClaude Opus 5.5 37551e41b4 Compile any crossings: destructuring, namespace patterns, any parameters and async callbacks
- Destructuring an `any` value in TypeScript (a function typed `any`, an
  awaited `any`, `JSON.parse`, an `any` table read) now goes through the
  checked-dynamic value, as JavaScript sources and #1407's JS-declared
  results already did. Object sources get V8's RequireObjectCoercible
  TypeError for the spellings the assignment form reproduces.
- A method call or member read on a checker-`any` receiver whose value is
  a fresh typed call result (`out.split("\n").map(...)`, `.pop()`,
  `.length`) boxes that temporary and dispatches on the runtime kind,
  only after every other lowering declined.
- `const { a, b: c } = ns` and `= await import("./m")` over a program
  module bind each export the way `ns.a` reads it.
- A defaulted parameter of a program-source function, arrow or function
  expression that the checker types `any` (a default under an
  `(...args: any[]) => any` wrapper slot, where the contextual `any`
  wins) is a dyn ABI slot in the callable's type.
- An async lambda whose signature answered a bare dyn result (a library
  slot `A | Promise<A>` with A = any) produces Promise<unknown>.
- `761-any-objects` compiles and moves back to the corpus; the
  retired-engine 2083/2104 snapshots shrink to their remaining fences.
- JavaScript `Promise.resolve(value)` (in a JavaScript file) over a dyn value holding a promise
  answers that promise instead of wrapping it, so a TypeScript async
  callback's result is adopted by JavaScript retry helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:30:58 -07:00
Malte UblandClaude Opus 5.5 47ecfdfa8c Merge opt/trace-reachable-blockers (with main) into opt/override-variance-error-expando
Brings main into the stack (thrown-value fixes, the lower-exprs split,
the checker helper removal). This branch's lower-exprs.ts hunks move to
expressions/relational.ts (`in` on errors), expressions/property-access.ts
(stdlib members of a structurally narrowed catch binding) and
expressions/narrowing.ts (assertions on an `any` catch binding); the
`this` blame, the `any` catch-binding read and the setPrototypeOf
feature test stay in lower-exprs.ts. The Error member read keeps both
this branch's intersection members and #1433's subclass-owned `code`.
Corpus 6407 and 6408 renumber to 6412 and 6413 (main has its own 6407
and 6408), and 6414 adds the thrown-value round trip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:29:46 -07:00
Chris Tate 95837b91c5 Check static members a cycle class inherits from its base classes 2026-10-20 02:26:01 -05:00
Malte UblandClaude Opus 5.5 2553818b0c Compile the round-2 index-signature and union-of-records shapes
- An object literal that spreads an index-signature record (the
  metric-tags idiom '{ ...tags, bucket, status }') maps to the pure
  index-signature record joining the spread slots and member types: the
  checker drops the signature, but the object holds every spread key.
  Declared members read and write as keys of it.
- A conditional spread into an index-signature record adds no key when
  it does not apply.
- '{ ...unknownValue }' into an index-signature record copies the
  value's own keys through the checked extraction; null, undefined,
  numbers and booleans spread nothing.
- 'return Promise.reject(e)' in an async function is typed by the
  function's own result.
- A union of record layouts flowing into one record slot converts each
  arm; fields the checker types 'undefined' alone lift into
  undefined-armed slots.
- '(x as { m: T }).m' on a union of records reads the arms that declare
  m and answers undefined for the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:19:07 -07:00
Chris Tate d5d9a1185a Compile process.send presence, process.exitCode reads, wider stream hooks, void race entries and promisify of callbacks (#1429) 2026-10-20 02:18:40 -05:00
Chris Tate 391b9ac00e Merge main 2026-10-20 02:15:13 -05:00
Chris Tate d3a567f830 Pass rest-parameter functions into slots with fixed parameters (#1480) 2026-10-20 02:13:34 -05:00
Malte UblandClaude Opus 5.5 f4c6441cee Merge main (through opt/variadic-generic-wrappers) into opt/trace-reachable-blockers
Main replaced the runtime-feature scan with a rule table, the executable
unit list with a unit table and the module emission with emit-module.ts:
perfLoop becomes a rule (perf.idleTime, perf.loop*, perf.eld*), a unit
(scr_perf_loop.c) and emit-module's loop-start mark (readsLoopMetrics is
now public). The feature lists take both perfLoop and main's webcrypto.
This branch's lower-exprs.ts hunks move to expressions/property-access.ts
(histogram readings), expressions/binary.ts (`??` with a function
default) and expressions/relational.ts (`in` over class members); the
caughtRead fallback stays in lower-exprs.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:10:52 -07:00
Malte UblandClaude Opus 5.5 77437eae3c Merge main into opt/variadic-generic-wrappers
Main split lower-exprs.ts into expressions/*; this branch's hunks move to
expressions/identifiers.ts (typed 'arguments') and
expressions/property-access.ts (static fn.name, forwarded tuple length,
'this'-parameter arity). Main removed the checker's declarationsOf,
valueDeclarationOf and signatureDeclaration helpers; the branch's uses
read symbol.declarations, symbol.valueDeclaration and
signature.declaration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:10:27 -07:00
Chris Tate b6ef49d39d Promisify callback-last functions that return a value
The promisified executor typed its call of the function as void, so a
function with a non-void return type failed to compile with SC9001. The
call now has the function's return type, and its value is discarded, as
in Node.
2026-10-20 02:09:48 -05:00
Chris Tate 069545a126 Address review: monitorEventLoopDelay spellings and validation, Error 'in' keys, event-loop metrics in libraries
- monitorEventLoopDelay is lowered from the builtin call dispatch, by module and member, so aliased named imports, namespace members and destructured requires all reach it. Shorthand and possibly undefined resolutions take Node's default.
- The runtime validates the resolution as Node does (an integer of at least 1) and throws a catchable RangeError with ERR_OUT_OF_RANGE.
- `'stack' in err` and Object.prototype's keys are true for every Error. The declared-member fold applies only to keys some program class declares and that built-in errors don't carry in runtime slots; other keys stay fenced.
- Library mode's async_free gate refuses the event-loop metrics, which library links leave out. They join the perf_hooks fence family.
2026-10-20 02:07:46 -05:00
Chris Tate 9febfe8ca3 Place a quoted name field among an error's fields
The position of an Error subclass's own `name` among its fields was found
from identifier keys only, so a quoted `"name"` field, other quoted
fields, and `this["name"] = ...` in a constructor were not seen, and
`name` was listed last. Field keys and constructor assignments now read
any statically spelled key: an identifier, a quoted string, a number in
its canonical spelling, or a computed string literal.
2026-10-20 02:07:18 -05:00
Chris Tate c2ae71a6fa Bound full-batch poller drains to 48 per poll phase
A full batch of 64 events polled again without returning to the event
loop. When synchronous socket callbacks kept 64 events ready on every
drain, timers and setImmediate callbacks never ran. The poll phase now
drains at most 48 full batches, the count libuv's uv__io_poll uses, and
the budget carries across the reopen that lets microtasks run, so the
remaining events wait for the next loop iteration.
2026-10-20 02:07:00 -05:00
Chris Tate bdf1bb9734 Pass rest-parameter functions into nullable and optional fixed-parameter slots
Structural conversion lifts a function into a union's function arm only
when cleanFuncAdaptable accepts the pair, and it refused every rest
signature. A typed-rest function assigned to `((m: string) => void) |
null`, an optional parameter of that type, or a `| undefined` binding
therefore failed with SC2003. cleanFuncAdaptable now accepts the pairs
the rest packing adapter serves, so these slots use the same adapter as
a plain fixed-parameter slot.

6445 covers the nullable, optional and undefined slots, and 6445 and
6446 get their corpus costs.
2026-10-20 02:06:21 -05:00
Chris Tate f6f4ad1b00 Give errors held as any their own stack accessor (#1450) 2026-10-20 02:05:17 -05:00
Chris Tate 45431aec32 Choose comptime union arms by discriminant and refuse recursive records
A comptime record value whose union has a discriminant takes the arm its
discriminant field names, so a value that fits two arms with overlapping
shapes gets that arm's tag and layout instead of the first arm it fits.
comptimeBakeable reports a record type reachable from its own fields as
not bakeable instead of recursing without end, so such a comptime result
reports SC1090, and the message names unions and that limit.
2026-10-20 02:01:48 -05:00
Chris Tate d45a542221 Compile esbuild's bundled CommonJS chunks and CommonJS cycles statically (#1411) 2026-10-20 02:00:58 -05:00
Chris Tate 0c3a7ae73e Keep error stacks through computed keys, clones and throwing getters
A read through a computed key now counts as a stack read when the program
contains the string "stack", so `e[key]` captures frames like `e.stack`
does. A key the program assembles from other strings or from data it
reads keeps the static answer.

structuredClone of an error held as `any` copies a stack that was already
formatted as a non-enumerable data property, as it did before the stack
became an accessor.

When a name or message getter throws while the stack is formatted, the
stack accessor propagates the exception instead of reading the missing
string.
2026-10-20 02:00:21 -05:00
Chris Tate 7551434bd7 Support freezing and redefining fields of class instances (#1385) 2026-10-20 01:57:15 -05:00
Chris Tate a16a53d33c Return parked workers' memory while the event loop waits (#1501) 2026-10-20 01:55:58 -05:00
Chris Tate 2fbc08e36d Check every early reference to a cycle class
The references of a cycle class were found by their spelling, so a class
imported under another name (`import { Sink as Alias }`) was never seen as
reached early, and neither was a let or const imported under another
name. An `extends` clause only counted when its expression was the bare
class name. A static method read as a value (`const f = Sink.m`) built a
closure that no check covered.

The early-binding scan and the class reference scan now also match each
file's local import names that resolve to the binding, and record them:
Node's ReferenceError names the binding the access spells, so the check
names the alias. The heritage check walks the whole `extends` expression.
A closure of a flagged class's static method checks the class like a
static call does.
2026-10-20 01:55:18 -05:00
Chris Tate 5a38f414dd Resolve parameter-list references without the function body's vars
A function's parameter initializers run in their own scope, which sees
the parameters and a function expression's own name but not the body's
`var`s. Chunk splitting resolved them against the body's names too, so a
helper that a default value reads was not copied into the chunk when the
body declared a `var` of the same name, and the chunk read an undefined
binding. A computed method name now resolves outside the method.

A named function expression whose body declares a `var` of its own name
still exposes the shared function to its parameter initializers, so those
reads must be calls for the function to travel with a chunk.
2026-10-20 01:54:30 -05:00
Chris Tate 78dbc31e43 Find dynamic imports with a comment before their parenthesis
The on-demand import scan tested each file's text for `import(` before
reading its calls, so `import /* note */ ("pkg")` was never followed:
the workspace package it loads and that package's npm dependencies were
left out of the module graph, and the dependency was refused with
SC2013. A file is now skipped only when it does not contain the keyword,
and its literal import() calls are read from the syntax. The
workspace-dynamic-import case loads its package with such a comment.
2026-10-20 01:51:57 -05:00
Chris Tate 9791aa587e Return parked workers' memory while the event loop waits, after a delay that grows
The trim helper returned parked workers' memory once no worker had run for
100 ms. A compiler in watch mode starts its workers again for each rebuild
and faulted the returned pages back in, and a rebuild that runs on the main
thread alone had the discards run alongside it.

- The delay starts at 1 s (SCR_SA_DISCARD_DELAY_MS) and doubles each time
  workers start within ten delays of a discard, up to
  SCR_SA_DISCARD_DELAY_MAX_MS (10 s by default).
- Once the event loop of the thread that is not a worker runs, the helper
  discards one arena at a time, and only while that loop has been in one
  wait with nothing ready for a step (scr_sa_loop_waiting, called by the
  event loop around its sleep). A loop that does not wait gets the
  discards SCR_SA_DISCARD_DELAY_MAX_MS after the delay.
- Programs without a running event loop discard after the delay, as before.
2026-10-20 01:50:55 -05:00
Malte Ubl b664590eca Merge commit '12d0215c4' into opt/builtin-base-class-values 2026-10-19 23:50:41 -07:00
Malte UblandClaude Opus 5.5 44f4e2e74c Refuse inline stream completion callbacks that name the error
Node passes write and end callbacks an error argument (null on success),
but the native callbacks receive none, so an inline callback that names
the parameter read undefined. It now refuses where it is written. The
stream-shims npm case compiles now that stream subclasses are values;
its remaining scenarios are deferred refusals, so it is recorded as one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:50:20 -07:00
Chris Tate b8ee250c4a Narrow unions of records by intersection refinements, and keep members an assertion attaches (#1484) 2026-10-20 01:50:19 -05:00
Chris Tate f77298ea82 Merge main 2026-10-20 01:49:45 -05:00
Chris Tate 6197a45ab0 Refuse arguments through class values of ctor-less stream subclasses
A class without a constructor that extends a stream class or
EventEmitter inherits the runtime constructor, which takes no arguments
here. Direct construction with arguments already reported that, but
construction through the class's value (`new registry.upper({ ... })`)
completed the arguments against the empty inherited signature and
dropped them. Both paths now share refuseInheritedBuiltinCtorArgs, so
the class-value path reports at the first argument with the same
message.
2026-10-20 01:48:22 -05:00
Chris Tate b3bcbaaf7c Decode memargs the same way in validation and translation
Validation, translation and instruction skipping now read memarg flags
through one function, readMemargFlags in module.ts. Flags with bit 6 set
are followed by an explicit memory index, which may be 0; flags of 128 or
more are malformed.

The translator refuses a module (WasmTranslateError) when a memarg,
memory.size, memory.grow, memory.init, memory.copy or memory.fill names a
memory other than 0, and when the operand of local.set, local.tee,
global.set, table.set, a load or a store does not have the type of its
destination. Locals are stored with their declared type.
2026-10-20 01:46:36 -05:00
Chris Tate 85d420540d Keep the stored type for annotated asserted bindings
A const annotated with the asserted type (const view: Route & { order: number } = route as Route & { order: number }) took the annotated layout, so the initializer was coerced into it: a width copy that read members the writes had not created yet, and writes through the binding no longer reached the original object. Local and module-level consts now keep the stored type when it hosts the annotation, as unannotated ones do.
2026-10-20 01:46:11 -05:00
Chris Tate 6054b5b5f6 Version typed-array loops on one bounds guard and tag typed-array memory for alias analysis (#1126) 2026-10-20 01:45:49 -05:00
Chris Tate f4a7c4629d Keep a live Pick or Omit class view's identity when it flows into another structural type
A Pick or Omit view of a program class that holds the instance (its view
exposes a method) now carries the class's identity in the flow analysis,
and its values count as instances of that class. Assigning such a view
to another structural type keeps the instance, so reads through the
second type see the instance's later writes instead of a copy's.
2026-10-20 01:45:28 -05:00
Chris Tate 5a3bd5302b Merge main 2026-10-20 01:39:43 -05:00
Malte UblandClaude Opus 5.5 0e8b4cf990 Hand back untyped wrapper results at the slot's type, and no-op transpiled prototype resets
- A function adapter whose source returns `any`/`unknown` (a generic
  wrapper's result handed back `as F`) converts the result through the
  checked-dynamic bridge when the slot's result is checkable: a record
  checks its shape, and a Promise keeps its identity and checks its
  payload when it settles. Before, these adapters threw "the call has
  no exact lowering" when called.
- In a class constructor, `Object.setPrototypeOf(this, new.target.prototype)`
  (directly, through a const, or the `if (Object.setPrototypeOf) ... else
  this.__proto__ = ...` form), and `Object.setPrototypeOf(this,
  C.prototype)` in C's own constructor when no class extends C, are
  no-ops: a compiled instance already has that prototype.
- Object.assign between records copies a source field into a wider
  target field (`{ bail: true }` into `bail?: boolean`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:36:12 -07:00
Chris Tate 40f6dd25c8 Revert "Give interfaces extending class-backed protocols checked identity" 2026-10-20 01:35:23 -05:00
Chris Tate ca65f57626 Speed up native string character tests, optional-string switches, fromCharCode and long-string length (#1498) 2026-10-20 01:20:11 -05:00
Malte UblandClaude Opus 5.5 e71a1763bb Promisify untyped functions at run time
util.promisify(fn) over a function typed any (an npm package method typed
by its JavaScript, a bound method of an untyped value) was refused,
because the promisified signature is only known at run time. A native
wrapper now calls the function with the call's receiver, its arguments
and a callback, and answers a promise: a truthy error rejects, otherwise
the first value fulfills, the first settlement wins, and a synchronous
throw rejects. util.promisify.custom functions are not consulted.

The wrapper lives in its own runtime unit (scr_util_promisify.c), linked
by the new utilPromisify feature, so other programs are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:17:06 -07:00
Malte UblandClaude Opus 5.5 071024ff56 Hoist classes of an initializer declared last in a shared var statement
build-utils declares its error module's bindings and initializer in one
statement (`var NowBuildError, mapTypoToSuggestion, init_errors = __esm(…)`).
The split pieces follow the statement, so the initializer only needs to be
its last declarator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:17:04 -07:00
Malte Ubl 25e40afa83 Merge remote-tracking branch 'origin/main' into opt/bc-node-surface 2026-10-19 23:16:41 -07:00
Malte UblandClaude Opus 5.5 5e65642fcf Compile mixed-tuple Promise.all, Boolean callbacks, enum key lookups and nullable regex receivers
- `Promise.all([p, q, r])` over different promise types (the tuple
  overload) lowers in TypeScript through one interned async helper that
  awaits the runtime combinator over the same promises (widened to
  Promise<void>, a type-only view), so settlement order and rejection
  handling are Node's, then reads each fulfilled entry into the tuple.
  Entries keep their identity; plain scalar entries pass through, and a
  checked-dynamic entry joins through the promise it holds.
- The ambient `Boolean` as an `every`/`some`/`find`/`findIndex`/`map`
  callback in TypeScript is the interned one-parameter truthiness
  closure `filter(Boolean)` already used, and `filter(Boolean)` accepts
  checked-dynamic elements (runtime truthiness).
- `E[key]` with a runtime string key answers the named member's constant,
  or undefined for a name no member carries; a numeric member's reverse
  key and inherited Object.prototype names throw a named runtime refusal.
- Regex-argument `replace`/`replaceAll`/`split`/`search`/`matchAll` on a
  `string | undefined` receiver take the string lowering and throw Node's
  "Cannot read properties of undefined (reading 'replace')".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:16:02 -07:00