Commit Graph
266 Commits
Author SHA1 Message Date
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
Chris Tate 309fa897ec Use EventEmitter, the stream classes and the error classes as values
`function make(C: typeof EventEmitter) { return new C(); }`,
`const kinds: ErrorConstructor[] = [TypeError, RangeError]` and
`(typeof Readable)[]` stopped at SC2020 (the type has no mapping) or
SC1090 (builtin classes as values). These classes are now class values:
the name in a TypeScript file (or a const alias of it) is the class
object, `typeof EventEmitter`, `typeof Readable` and ErrorConstructor-
style types map to class values, and parameters, arrays, Map values and
object fields hold them.

Construction through a value calls %C.construct, which builds the
instance with the runtime constructor the direct `new` uses (emitter.new,
a stream's newDyn without options, error.newOptions for Error and its
six standard subclasses). instanceof, typeof, .name and === answer as in
Node. A Duplex value widens into a `typeof Readable` slot, and stream
values widen into `typeof EventEmitter` slots, as the constructor ABIs
match; a union of such class values (`const kinds = [Readable,
Writable]`) is a value of their nearest common ancestor, and `new K()`
through it answers the checker's union of instances. `instanceof`
through a value that may be Writable keeps the Duplex rule at run time.

EventEmitter and stream values constructed with options, runtime class
values stored as untyped values or printed, statics read through a value,
and DOMException, AggregateError, AssertionError and SuppressedError
values report SC1090. The existing static lowerings
(`EventEmitter.setMaxListeners`, `Error.stackTraceLimit`, the events
module) are unchanged. JavaScript files keep the identity token.
2026-10-19 04:48:10 -05:00
Chris Tate 1b27822d64 Merge main 2026-10-19 00:47:00 -05:00
Chris Tate dc6a638e63 Lower string and typed array iterators, Iterator.from and helper return()
String iterators (s[Symbol.iterator]()) and typed array values(),
keys() and entries() compile as iterator objects: generators over the
code points and over the indices. The iterator helpers apply to them,
and for...of, spreads and Array.from consume them.

Iterator.from over an array, string, typed array, Set, Map or iterator
object answers the value's own iterator, as Node does for iterators
that already inherit from Iterator.prototype. Objects with their own
next method keep the Iterator fence. As the root of a helper chain,
Iterator.from(x) walks x itself: an array by index, a Set or Map live,
a generator directly.

h.return!() and h.return?.() compile when h is a helper call or a const
initialized with one: helper objects always have return(), so both
spellings are the call. Other iterator objects keep the fence, since the
array, string, Map and Set iterators have no return().

flatMap callbacks may return Sets (their values) and Maps (their
entries), walked live; a string result throws Node's TypeError and
closes the source.
2026-10-18 22:10:17 -05:00
Chris Tate d4d407bb07 Fix npm crashes, internal errors and false TDZ errors from the compatibility research (#1357) 2026-10-18 21:05:11 -05:00
Chris Tate 684f73d186 Add ThreadSlots, per-thread values on shared objects, to @scriptc/experimental (#1271) 2026-10-18 20:39:11 -05:00
Chris Tate 4e92ec16d8 Compile derived constructors whose super() call leads a comma statement (#1354) 2026-10-18 20:25:58 -05:00
Chris Tate 705c197a71 Format Dates with Node's local time zone names
Date.prototype.toString, toDateString and toTimeString, String(date),
template literals and string concatenation print local time with the GMT
offset and the zone's long name, as Node does. The runtime identifies the
host zone from TZ or /etc/localtime, again after each write to
process.env.TZ, and looks its name up in a table generated from Node's
ICU data. Dates held in untyped values convert to strings with toString()
and to numbers with their time value.
2026-10-18 20:20:05 -05:00
Malte UblandClaude Opus 5.5 18033dc42d Compile derived constructors whose super() call leads a comma statement
Minifiers join a derived constructor's statements into one comma
expression (`super(message, options), this.name = ..., ...;`), which jose
6.2.12 ships in its error classes. The statement lowering already runs
each operand of a statement-level comma as its own statement, so super()
is accepted when only parentheses and comma operators separate it from
its expression statement. Every other expression position keeps the
SC1090 refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:57:35 -07:00
Chris Tate 45d4105351 Compile maybe-promise unions, unknown[] rest parameters, and function-valued Maps and Sets (#1347) 2026-10-18 19:55:09 -05:00
Malte Ubl cdf731e4da Merge remote-tracking branch 'origin/main' into opt/npm-runtime-bugs
# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-modules.ts
#	packages/compiler/src/frontend/lowering/lower-stmts.ts
#	packages/compiler/src/frontend/program.ts
2026-10-18 17:50:35 -07:00
Chris Tate 6fd2dedb15 Merge main 2026-10-18 19:04:59 -05:00
Chris Tate 6ab49f33b2 Stringify class instances with JSON.stringify (#1338) 2026-10-18 18:59:22 -05:00
Chris Tate 8d94e5d993 Refuse RegExp and Promise class fields in JSON.stringify
A Date field reads through the class's checked-dynamic property view and
writes its toJSON text, so json-class-fields.ts compiled and matched Node
while the diagnostics test expected a refusal. The Date cases move to
6000-json-stringify-class-instances.ts.

RegExp and Promise fields read through the view but the runtime JSON
traversal has no writer for them: a RegExp field threw at run time where
Node writes {}, and a Promise field failed to link. Both are now refused
at compile time naming the field, also inside a field's arrays, records
and unions, and the fixture covers them.
2026-10-18 18:34:09 -05:00
Chris Tate cf11fbaaeb WebAssembly W0–W2: free handle tags, a cross-thread clone hook, the WebAssembly types and refusals, and a decoder that reports every module a program can load (#1339) 2026-10-18 18:12:42 -05:00
Malte UblandClaude Opus 5.5 f4a8511dca Compile maybe-promise unions, unknown[] rest parameters, and function-valued Maps and Sets
A union may now hold one promise beside data members (`T | Promise<T>`,
`Promise<R | void> | R | void`). The union tag is the `instanceof Promise`
test, and every operation keeps Node's microtask timing: `await` awaits the
promise arm or takes the one non-thenable turn, an async `return` (block
bodies, concise arrows, then handlers) adopts the promise arm after the
function's own finally blocks, `Promise.resolve` returns the promise arm as
is, and `typeof (u as any).then` reads the tag. Data arms that declare
`then`, two promise arms, and function arms beside a promise stay refused
with diagnostics. Inferred JavaScript literals that mix promises and values
keep the checked-dynamic array.

Rest parameters typed `unknown[]` or `any[]` in TypeScript use the
checked-dynamic variadic ABI that JavaScript rest parameters already use,
including spread forwarding (`(...args) => handler(...args)`).

Functions are now Map values, Map keys and Set elements, compared by
closure identity.

Two pre-existing bugs in the same area are fixed: an async lambda in a
`Promise<void> | void` slot got a `Promise<union>` ABI and threw when called
through the slot, and `return cb()` of such a callback in an async function
dropped the promise instead of adopting it.

The retired-engine 2251 and 2790 diagnostics fixtures (tagged templates
with `any[]` rest tags) now compile and match Node, so they move to the
corpus as 6248 and 6249.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:58:57 -07:00
Chris Tate b32f4e31f3 Merge main 2026-10-18 17:47:55 -05:00
Chris Tate 2f40bcb8d4 Lower iterator helpers on generators and Map and Set iterators
map, filter, take, drop, flatMap, toArray, forEach, reduce, some, every
and find compiled only as one chain from arr.values() or arr.entries()
that ended in a consuming method. Every other form was refused, and in
JavaScript the refusal was thrown at run time.

IteratorObject now maps to the generator type. A consuming method over
a chain of stages fuses into one loop over the source: a generator
driven like for-of, a Map or Set walked live, or an array by index. A
stage on its own builds a helper object: a generator over its source
that supports next(), for...of, spreads, Array.from and yield*. A
for...of whose head is a chain of stages without flatMap runs the
stages inside the loop.

The source is closed where Node closes it: when take reaches its count,
when some, every or find has its answer, when a callback or the loop
body throws, when the loop exits early, and before the RangeError of an
invalid take or drop budget. It is not closed when it is exhausted or
its own next() throws. return() on a helper object before its first
next() closes the source through a new runtime hook,
scr_gen_set_unstarted_return.

When closing an iterator throws while another error propagates, the
first error reaches the caller, as in Node. This applies to for...of
over a generator too, where the error from the close used to replace
the body's error. The try statement takes a dropFinallyErrors flag,
whose cleanup handler calls the new scr_exc_keep_first.

throw() lowers only on values typed as generators. Iterator helpers and
the built-in iterators have no throw method, and a value typed as an
iterator may hold one.

Callbacks in JavaScript bind their unannotated parameters to the
element type, and flatMap callbacks may return generators. A generator
whose record yields share checked storage with its return channel has
each value converted to the declared element type as it is pulled.
2026-10-18 17:46:31 -05:00
Chris Tate b119b4cf03 Stringify class instances through the runtime JSON traversal
JSON.stringify of a class instance, or of a record, array or union that
holds one, boxes the value and serializes it with the runtime JSON
traversal. The output matches Node: own enumerable properties in
creation order, properties added after construction, toJSON on the
instance or a base class, and no methods, accessors, #private fields or
static fields. A class whose declared field cannot be read through the
checked-dynamic property view, such as a Date field, is refused at
compile time with SC1090 naming the field.

Boxed Map and Set values now stringify as {}, as Node does, instead of
throwing.
2026-10-18 17:32:17 -05:00
Malte UblandClaude Opus 5.5 c7f3fa6564 Type-check programs that mention WebAssembly and refuse its API precisely
Packages whose declarations name WebAssembly types (quickjs-wasi,
photon-node, and therefore pi) failed scriptc's type check with SC0001
"Cannot find namespace 'WebAssembly'", because scriptc's ES-only type
world never loads lib.dom, so `scriptc coverage` could not analyze them.

- scriptc.d.ts declares lib.dom's WebAssembly namespace and BufferSource
  (admitting every ArrayBufferView, so Node's Buffer types fit).
- The type mapper gives every type declared in the namespace, and unions
  over one, the checked-dynamic representation, so declarations that only
  mention them compile.
- Every reached use of the API value refuses at its site with a named
  SC2020 diagnostic: `WebAssembly.<member>` reads and calls,
  `new WebAssembly.<Class>`, `instanceof WebAssembly.<Class>`, the bare
  namespace, `globalThis.WebAssembly`, and the global object viewed
  through a cast to a type with a WebAssembly member (pi codemode's
  spelling). TypeScript sources fail the build; JavaScript sources get a
  located runtime fence.
- `typeof WebAssembly` and `typeof WebAssembly.<member>` need no value and
  fold to Node's answers ("object", "function"), including inside
  `typeof x === "..."` tests, so feature probes in JavaScript glue behave
  as in Node. Before, a JavaScript probe answered "string" (the identity
  token) and silently took the non-wasm branch.
- The surface manifest projects the refusal (stdlib.webassembly), and the
  compatibility ledger's `WebAssembly` global row moves from
  not-implemented to refused; no other row changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:19:38 -07:00
Chris Tate 4c27ab8bd7 Compile classes that extend a generic class factory's result
`class Counter extends base(42) {}` with `function base<T>(initial: T) {
return class { value: T = initial; }; }` failed with SC1090 "extending
computed expressions": the call's result type is the factory's local class
instantiated with T = number, which only maps inside the factory's own
generic instance.

A call of a generic function whose result is one of its local classes (or
an instance of one) now keys the instance on its type bindings and maps the
result inside that instance, where the local class takes the instance's
name and bindings. The class value then works as a base (`extends
base(42)`), as a `const` binding, through `new`, and for method calls and
field reads on its instances. Each instantiation has its own class, so
`base(1) === base(1)` is false and `instanceof` tells them apart, as in
Node. Calls inside another generic instance bind through the declaration;
calls elsewhere also record the instantiated checker type, so later uses of
it name the same class.

Fixes #1241
Co-authored-by: BaoLei <50226652+blurname@users.noreply.github.com>
2026-10-18 17:17:22 -05:00
Chris Tate 356d968528 Merge main 2026-10-18 17:07:58 -05:00
Chris Tate 3b0ffb24e4 Compile overloaded methods on interfaces and object types (#1333) 2026-10-18 17:01:44 -05:00
Malte UblandClaude Opus 5.5 f4daefa6be Compile overloaded methods on interfaces and object types
An interface or type-literal member with several call signatures (the `on("event", handler)` registry shape, arity overloads, type overloads sharing one implementation) failed its whole record shape: the type mapper maps only single-signature function types, so the member mapped to nothing and every type mentioning the interface failed with it.

JavaScript stores one function under such a member; overloads only constrain call sites. The member now keeps a record slot holding that function. Its type joins the overloads per parameter position and result: equal types stay, scalar mixes form their union, and other mixes travel as checked `unknown` values when every joined type crosses that boundary both ways (differing callbacks become checked function values that any implementation's callback type adapts from). Positions some overloads omit are optional, and an overload's own type parameters erase to `unknown`. Rest parameters and a void result beside a value result keep the member fenced, now with a diagnostic naming the overload and parameter that block it.

Calls convert their arguments into the slot and the result back to the resolved overload's type, on record and union receivers, through optional chains, and on call results. Object-literal methods find their member slot through the literal's contextual type, so an implementation with optional parameters fills an arity overload set (and a single-signature member's slot). Value reads of the member keep SC2007. Library, declaration-file and class overload declarations are unchanged.

The zero-cost gate passes against the parent commit with all 2379 programs identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 14:40:40 -07:00
Malte UblandClaude Opus 5.5 beddc5f94a Keep typescript-go's tuple-literal panic with the statements that ask
`s.match(re) || []` contextually types the empty array by the tuple-like
RegExpMatchArray, so typescript-go builds an empty tuple target and
clones it as an array literal. The clone keeps ObjectFlagsTuple on
TypeReference data, and the API's newTypeResponse panics (interface
conversion to *TupleType) whenever it serializes the clone, for example
as a member of `RegExpMatchArray | []`. Whole-program analyses asked for
such union members first, so every program containing the pattern
(moment.js, or a two-line TypeScript file) failed at 1:1 with SC0004.

A union whose members typescript-go panics to serialize is now
unmappable at the top of mapType, the any-array probe answers false for
it, and the panic is remembered per type instead of re-requested.
Queries that still need the members keep the source-anchored SC0004 on
their own statement (a runtime fence in JavaScript). Other checker
panics are unchanged. The panic itself is an upstream bug in
internal/api/proto.go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 13:51:44 -07:00
Chris Tate ea7bcd9846 Resolve builtin classes through const aliases (#1327) 2026-10-18 15:42:15 -05:00
Chris Tate 3be66127c2 Fence refused statements inside capture buffers instead of dropping them (#1322) 2026-10-18 15:24:10 -05:00
Chris Tate 547e59529f Resolve builtin classes through const aliases
`const E = EventEmitter`, `const M = Map`, `const T = TypeError` and
`const R = Readable` stopped at SC2020, and so did `typeof EventEmitter`,
`Readable.name`, `Map.name`, `Error.name` and `===` between builtin
classes. The zlib class alias resolver now covers the error classes,
EventEmitter, the node:stream classes and Map/Set: a const binding of one
(an alias of an alias, or an export used through a named or namespace
import) constructs, extends and tests instanceof like the direct name,
and typeof, .name and === fold for aliases and direct names.

In TypeScript files the alias owns no runtime slot and other value uses
report SC1090 at the use. In JavaScript files a copy of a name or of a
globalThis property keeps its slot, which holds the same value as the
name it copies; builtin module members (`zlib.Gzip`) own none, as before.
2026-10-18 15:23:20 -05:00
Malte UblandClaude Opus 5.5 f649959609 Report refused local classes from speculative type queries again
The previous change dropped the diagnostics of a function-local class refused during a speculative whole-program type query, expecting the class's own declaration to report them. In JavaScript that declaration only defers them to a runtime fence, so two npm programs that the compile refused (stream-shims and node24-file-url-conversion, both SC1090) began to compile and diverged from Node at run time. The speculative query now leaves the refusal reported, as it was wherever such an analysis already caught the poison, and only stops the poison from escaping the compiler.

The JavaScript crash repro becomes a diagnostics fixture (local-class-type-query-js.js); the corpus program 6102 never crashed the base compiler and is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:05:25 -07:00
Malte UblandClaude Opus 5.5 b0ce48f168 Fence refused statements inside capture buffers instead of dropping them
A refused statement in a JavaScript function body was dropped without a diagnostic or runtime fence when the body lowered inside a capture buffer whose owner kept the result. tryLowerExpression lowers the receiver of `(function () { ... }).call(this)` or `.apply(...)` and a computed class base that way: the refused statement (for example `console.log(root === module.exports)`) was missing from the kept function, its diagnostic was handed back to the enclosing statement, and that statement, having lowered cleanly, filed it as an informational runtime-fence entry. The program printed nothing for that statement.

lowerStmts now emits the statement's runtime fence inside a buffer as well (Lowerer.capturedRuntimeFence, the same message deferToRuntimeFence would give), leaving the diagnostics with the buffer's owner. The three JavaScript class initializer sites that skipped their fence under a buffer (static field, instance field, static initializer block) do the same. An owner that discards the candidate discards the fence with it, so every program that compiled before keeps byte-identical IR unless it contained such a dropped statement: all 3006 corpus programs produce the same IR as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 11:47:04 -07:00
Chris Tate 29f6d8e47f Merge main 2026-10-18 12:28:02 -05:00
Chris Tate 2de6f617e6 Merge main 2026-10-18 11:08:54 -05:00
Malte UblandClaude Opus 5.5 95712ea590 Keep refused local classes out of speculative type queries
Whole-program analyses that query types before reachability is known (the computed-key write census for JavaScript method dispatch, and the runtime-optional array analysis) can name a function-local class whose collection is refused. No statement owns that refusal there, so it escaped as an uncaught PoisonError from collectClassShapeInner and crashed the compiler (the ts-api-utils probe, @typescript-eslint/typescript-estree, and small TypeScript programs). These analyses now run under Lowerer.speculativeTypeQueries: a refused local class is unmappable for the query and its diagnostics are dropped, and the class's own declaration collects it again and reports them if the program reaches it. Collection and body lowering keep their poison handling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:32:52 -07:00
Chris Tate 9638f0b4ef Close three TypeScript gaps: Error.cause redeclaration, match-result object patterns, union-stored string indexing (#1298) 2026-10-18 10:32:50 -05:00
Chris Tate b4ef932b72 Support zlib stream classes as const aliases and instanceof targets
`const C = zlib.Gzip` stopped at SC2020, and `instanceof zlib.Gzip`
(any zlib class) stopped at SC1090. A const binding of a zlib class is
now a compile-time alias with no runtime slot: `new C()`, `typeof C`,
`C.name` and `C === zlib.Gzip` lower at the use. `instanceof` against a
zlib class asks the runtime for the stream's mode when the operand is a
Transform, tests for a Transform first when it is typed as one of
Transform's bases, and answers false for other static types, as in Node.
Other uses of an alias as a value report SC1090 at the use.
2026-10-18 09:52:17 -05:00
Malte UblandClaude Opus 5.5 56269bdad8 Destructure regex match results with object patterns
`const { 0: path, index } = re.exec(s)` was refused with SC1031 because a
match result lowers to an honest string[] slice, and object patterns only
accepted records. Object patterns whose source the checker types as
RegExpExecArray or RegExpMatchArray now lower each element to the read its
member expression already has: numeric keys are the positional reads of the
equivalent array pattern (defaults included), `index` and `input` are the
result's own properties, and `groups` is the named-group projection when the
regex is statically known. Rest elements, defaults on `index` and `input`,
and other keys keep named refusals. This is path-to-regexp's
regexpToFunction shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 07:07:44 -07:00
Malte UblandClaude Opus 5.5 22cbd7623b Compile TypeScript redeclarations of Error.cause
A TypeScript subclass of Error that declared `cause` was refused with SC1090,
although the same field in a JavaScript class already compiled. The
declaration now lowers like the JavaScript field: it defines the error's own
enumerable `cause` after super() returns, undefined without an initializer,
replacing a cause passed through the options exactly as Node does. `declare`
and `abstract` declarations are erased and define nothing.

Reads of `.cause` on Error-rooted classes are always the checked-dynamic
property, so only declarations typed `unknown` or `object` are accepted.
Other declared types, parameter properties and accessors named `cause` keep
named refusals. This unblocks the source of @openfeature/core, whose
abstract error base declares `cause?: unknown`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 07:07:37 -07:00
Malte Ubl 8255315883 Merge origin/main into opt/cjs-interop 2026-10-18 06:46:23 -07:00
Chris Tate 1663d6d35e Merge main 2026-10-18 08:45:06 -05:00
Malte UblandClaude Opus 5.5 ae91df908e Admit require cycles whose modules settle their exports first
A require cycle was always refused (SC1016). Node answers a require of a
module that is still evaluating with its exports as they stand, while the
compiled bindings alias the final declarations; the two agree when every
module of the cycle assigns all of its exports before its first statement
that can run another module's code, every other use of its export object
is a plain member read, and no module's top-level code can reach its
binding of another member. Each module then initializes exactly as it
would outside the cycle. This admits semver's comparator/range cycle
("hoisted class for cyclic dependency"); other cycles keep SC1016 with a
reason naming the offending statement.

The require reachability scan no longer treats a declaration's own name
as a read (esbuild's `var X = class {...}` before its requires), treats a
plain write of an export member as running only a setter published for it
(on-finished's `module.exports.isFinished = isFinished`), and keeps the
conservative scan for writes of __proto__, __esModule and members of a
replaced export object.

Evidence: corpus 6145 (the cycle), 6142 gains the esbuild layout; the
npm-static harness compiles the published fs.realpath@1.0.0,
on-finished@2.4.1 (with ee-first@1.1.1) and statuses@2.0.1 statically;
program.test.ts pins the TDZ analysis through var bindings plus the cycle
admission and refusals. Preflight/order baselines recorded for the new
fixtures; a full re-record changed no other fixture. Dependencies and
limitations docs describe the supported require forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 06:24:04 -07:00
Chris Tate 4d95b28dde Add t.assert.snapshot and t.assert.fileSnapshot (#1274) 2026-10-18 06:55:39 -05:00
Chris Tate 85fe87ffbf Convert Date values into unknown slots as Dates
A TypeScript Date placed in an `unknown` slot (an annotated binding, an
object or array literal, a record or class field) becomes a Date holding
the same time value. Before, a Date member of an `unknown` object literal
became a function, and other conversions were refused. util.inspect prints
these Dates in their ISO form or as Invalid Date, and their methods can be
read as values through untyped references. Comparisons with a Date operand
keep their fence: each conversion creates a new Date.
2026-10-18 05:40:08 -05:00
Chris Tate 89b5baedd7 Add t.assert.snapshot and t.assert.fileSnapshot
node:test changes, matching Node 24.22:

- t.assert.snapshot(value[, options]) serializes the value with Node's
  default serializer, JSON.stringify(value, null, 2), or with an
  options.serializers array literal applied in order, and compares it
  with assert.strictEqual against the snapshot stored under
  "<test full name> <n>" in "<entry file>.snapshot".
- t.assert.fileSnapshot(value, path[, options]) compares the serialized
  value with the contents of path.
- Snapshot files are read in the form Node writes them, exports[`key`] =
  `value`; statements, with template escapes and CRLF line endings
  cooked as in a template literal.
- Missing snapshots, unreadable snapshot files and throwing serializers
  throw Node's ERR_INVALID_STATE errors, with Node's messages.
- Running the binary with Node's --test-update-snapshots flag as its
  first argument records the values and writes the snapshot files after
  the last test, keys sorted as Node sorts them. The flag is kept out of
  process.argv and appears in process.execArgv, which is otherwise [].
- Snapshot assertions count toward the test's plan.

The snapshot fixture compares the reporter output with Node, plain and
with SCRIPTC_SAN=1, against a snapshot file Node wrote. The update-mode
harness test runs Node and the binary with --test-update-snapshots and
compares the files they write. The node:test normalization also drops
the "... N lines matching cause stack trace ..." line Node prints for an
error cause, with the stack frames it summarizes.
2026-10-18 05:31:40 -05:00
Chris Tate ffd760bfa2 Merge main 2026-10-18 04:55:11 -05:00
Chris Tate a3feec9b35 Add ThreadSlots, per-thread values keyed by objects, to @scriptc/experimental
ThreadSlots<K, V> holds one value per key and thread, like a WeakMap that
each thread has its own copy of; under Node.js it is a WeakMap per name
and thread. Setting a value never writes the key, so values can be set on
published keys.

Classes used as keys carry a 32-bit slot word after their header words,
assigned on the first set for a key (with a compare-and-swap for a
published key) and never reused. Each family has one block per thread
that maps slot indices to values through pages of 1024 entries. get and
has are inline reads of the slot word, the page and the entry; set and
delete call scr_slots.c, which holds one reference to each value. A key
class's free paths release the key's values, and thread exit releases the
rest.
2026-10-18 04:11:02 -05:00
Chris Tate a0b1e93dec Merge main 2026-10-18 03:52:20 -05:00
Chris Tate 84916f25e4 Support the regex v flag
Regex literals and new RegExp calls accept the v (unicode sets) flag, which
the vendored engine implements: class subtraction and intersection, nested
classes, \q{...} string literals and properties of strings. The flag uses
unicode mode for pattern decoding and string iteration, and flags read in
getter order (dgimsuvy). Regex literals now also report their flags in
getter order, as Node does for /x/yg, and the flag getters (global,
unicodeSets and the rest) compile on statically typed regexes.

In v mode the engine rejected `-` and doubled punctuation outside a
character class, and the `\-` escape inside \q{...}. It now applies the
class-only checks inside classes and \q{...} only, and parses \q{...}
contents with the class escapes, matching Node. The d flag is still
refused.
2026-10-18 03:37:40 -05:00
Chris Tate 32061e8ba3 Add Float16Array
Float16Array is a native typed-array kind with IEEE 754 binary16 storage.
Stores round the number to nearest-even in one step from the double, as
Node does, including overflow to Infinity, subnormals, -0 and NaN; reads
widen exactly. The runtime converts with integer operations, so no target
needs half-precision hardware or compiler-rt support.

Construction, element reads and writes, ArrayBuffer views, fill, set,
subarray, slice, both sort orders, constructor values and checked-dynamic
storage work as for the other typed arrays. Math.f16round,
DataView.prototype.getFloat16 and setFloat16, and util.types.isFloat16Array
compile.
2026-10-17 22:45:32 -05:00
Malte UblandClaude Opus 5.5 43fb612438 Compile CommonJS requires placed after code that can reach them, and JSON requires
A top-level require binding that earlier code could reach was refused
(SC1013). The reachability scan now follows Node's evaluation order:
hoisted functions and classes run only when reachable code names them, a
class statement runs only its heritage, computed keys, decorators and
static initializers, timer and promise callbacks run after the module
body, and publishing a value through module.exports, exports.n,
Object.defineProperty(exports, ...) or F.prototype.m runs nothing until
reachable code reads it back. This admits the export-first layouts of
semver, glob@7, fs.realpath, on-finished, readable-stream and
tsc-emitted CommonJS.

A const or let binding that code can still reach before its require now
compiles with an initialization flag set right after the require and a
module.tdzCheck before the outermost expression of every reference that
can run earlier, so an early read throws Node's ReferenceError before
any argument evaluates. A reference whose lowering leaves no expression
to check, a var binding (Node reads undefined there) and createRequire
bindings keep SC1013.

require() of a JSON document now answers one parsed object per document,
created in the requiring module's %init prelude unless another module
already created it, so repeated requires return the same object and
mutations are visible to every requirer. Require bindings of JSON
documents are ordinary storage, so destructuring (nested patterns,
defaults, missing keys) copies at the require as in Node.

Nested destructuring of a builtin require binds the members of the
sub-module the key names (const { types: { isPromise } } =
require('util')).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:58:21 -07:00
Chris Tate a5c7c09ad3 Add mock.timers to node:test 2026-10-17 20:55:17 -05:00