Commit Graph
766 Commits
Author SHA1 Message Date
Chris Tate 431a8d6874 Merge main 2026-10-19 21:39:39 -05:00
Malte Ubl ea14ae9bae Merge remote-tracking branch 'origin/opt/native-undici' into opt/npm-polyfill-shims
# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-modules.ts
#	tests/harness/npm.test.ts
2026-10-19 19:20:10 -07:00
Chris Tate 3ae4172f4d Keep thrown arrays' identity and inspect thrown values in test failures
A thrown array reached its catch binding as a type-erased native array:
`console.log(e)` printed `{}` where Node prints the array, `String(e)`
gave "[object Object]", `Array.isArray(e)` was false and a typed view
such as `(e as number[]).length` threw a TypeError. Thrown records
already crossed the catch boundary as checked-dynamic values that keep
their identity; arrays, and unions over records and arrays, now take the
same conversion, so the catch binding is the thrown object.

node:test printed a failing test's thrown non-Error value with String():
`throw { code: 42 }` reported `[object Object]` where Node prints the
value as util.inspect renders it. The failing-tests section now inspects
thrown numbers, booleans and checked-dynamic values (records, arrays,
null and undefined); a thrown string is quoted as before. A thrown class
instance that is not an Error still prints `[object Object]` (the
limitations page says so).

Adds corpus 6341, extends the thrown-values node:test fixture, and
records 6341's cost.
2026-10-19 21:06:20 -05:00
Malte UblandClaude Opus 5.5 c17eec0261 Compile event-loop metrics, predicate-narrowed catch bindings and other wrapper-reachable gaps
Once generic wrappers compile (#1413), the code behind them is reachable.
This closes the general gaps found there:

- performance.eventLoopUtilization() and monitorEventLoopDelay()'s
  IntervalHistogram (enable, disable, reset, percentile and the min, max,
  mean, stddev, count and exceeds readings), under the fallback
  declarations and @types/node. A new runtime unit, scr_perf_loop.c, links
  only for programs that read them (the perfLoop feature): an unref'd
  sampling interval records into HdrHistogram-shaped buckets, and
  utilization measures active time as the main thread's CPU time since
  the loop started.
- Catch bindings narrowed by a user type predicate or an assertion read
  their members through the checked-dynamic value.
- `??` with a function default of a different signature takes the
  optional callback's own function type and adapts the default.
- Optional calls on interface-typed receivers dispatch like the plain
  protocol call.
- `any` parameters of function declarations and spelled function types
  are checked-dynamic slots, and a generic instantiated at `any` takes
  `unknown` there.
- `'key' in err` on Error receivers answers from the declaring subclasses.
- Object.entries over a generic record resolves
  `keyof T extends never ? unknown : T[keyof T]` through the binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:04:07 -07:00
Malte UblandClaude Opus 5.5 20ab1c0531 Merge main into opt/bc-node-surface
Resolve the conflicts with main's process.features value, the WebCrypto dynamic types, the NODE_COMPAT_VERSION import and the symbol.declarations API. Renumber this branch's corpus programs to 6420-6429, write the new limitations partial one sentence per line, and regenerate the compatibility outputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:02:41 -07:00
Chris Tate 2119198c55 Merge main 2026-10-19 20:53:44 -05:00
Malte Ubl f5f8c4821c Merge remote-tracking branch 'origin/main' into opt/native-undici
# Conflicts:
#	packages/compiler/src/frontend/lowering/builtins/timers.ts
#	packages/compiler/src/frontend/type-mapper.ts
2026-10-19 18:08:01 -07:00
Chris Tate e8ba8e404a Merge main 2026-10-19 19:56:12 -05:00
Chris Tate d438cf685f Merge main 2026-10-19 19:43:23 -05:00
Malte UblandClaude Opus 5.5 13fa2c9505 Document built-in packages and unloaded optional dependencies
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:31:38 -07:00
Malte UblandClaude Opus 5.5 d9d3dc2517 Keep __dirname and __filename in TypeScript files that load as ES modules
A .ts or .tsx file with import syntax is an ES module under Node's strip-types rule, which has no __dirname or __filename. TypeScript projects are commonly shipped as CommonJS bundles where both exist, and @types/node declares them for every file. scriptc now compiles them in such files to the module's source path, the same compile-time constants as in CommonJS and the same strings as import.meta.dirname and import.meta.filename. JavaScript ES modules keep the fence. The limitations page documents the difference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:46:46 -07:00
Chris Tate 53af3dc930 Compare strings in UTF-16 code-unit order
Relational string operators (`<`, `<=`, `>`, `>=`) compared the UTF-8
bytes, which is code-point order. Node compares UTF-16 code units, so the
results differed for characters above U+FFFF against U+E000..U+FFFF and
for lone surrogates against surrogate pairs. The default `sort()`
comparator, `util.inspect` with `sorted: true` and dynamic comparisons
already used UTF-16 order; the default comparator of a dynamic array's
`sort()` did not.

Typed comparisons and the dynamic array sort now use `scr_str_cmp_u16`,
and the IR's `strCmp` node no longer has a `utf16` flag. The two orders
agree unless both of the first differing bytes are at least 0xED, so:

- the inline wrapper decides differing first words, or last words of
  equal lengths up to 16 bytes, in byte order when neither word has a
  byte of at least 0xED (a SWAR test). Other differing words, and the
  first bytes of shorter strings, decide by their first differing byte:
  in byte order when one of the two is below 0xED, and by plane when both
  are lead bytes from 0xEE to 0xF4 (a supplementary character orders
  below U+E000..U+FFFF);
- otherwise a proven-ASCII string on either side compares with `memcmp`
  (`scr_str_cmp`), and other pairs call `scr_str_cmp_u16_bytes`. It finds
  the first differing byte with words up to byte 32, then with 32-byte
  SSE2 or NEON blocks where the target has them. The call is marked as
  only reading its arguments, so repeated comparisons of one pair merge.
- `scr_str_cmp_u16` itself takes the same `memcmp` path for proven-ASCII
  strings.

The native compiler sorts record fields with `<` and checks the order
with `sort()`, so it reported SC9001 for 5888-inspect-sorted.ts, whose
object has the keys "\u{1F600}" and "\uffff". 5888 runs in the
native-frontend self-host check. Adds corpus 6023.
2026-10-19 18:10:54 -05:00
Chris Tate 305832a12d Merge main 2026-10-19 17:32:14 -05:00
Chris Tate 736085fbf1 Merge main 2026-10-19 17:19:52 -05:00
Chris Tate a31b1fe23e Merge main 2026-10-19 17:17:48 -05:00
Chris Tate 37f54ff157 Merge main 2026-10-19 17:14:32 -05:00
Chris Tate 0e02fc28ca Accept mock.fn() without an original
node:test changes, matching Node 24.22:

- mock.fn() and t.mock.fn() without an original return undefined and
  record the arguments of each call, whatever the callers pass. The
  original and the initial implementation are a function that returns
  undefined, so restore() brings that function back.
- mock.fn<F>() with an explicit function type records F's parameters
  when F's return type is void or includes undefined. A return type
  without undefined, such as mock.fn<() => number>(), throws SC2020.

mock.fn()'s default type, (...args: any[]) => undefined, maps to the
checked-dynamic rest ABI used for program-declared (...args: unknown[])
functions: the calls log records the argument array, and the mock's
wrapper spreads it into the implementation.

A function whose fixed parameters are all unknown or any, or that has
none, now converts to a checked-dynamic rest signature without fixed
parameters, such as (...args: unknown[]) => void: each call passes the
arguments in order, undefined for the ones the caller left out. This is
how () => {} reaches mockImplementation and mockImplementationOnce, and
it applies to any such slot in TypeScript programs, which threw SC1090
before.

The mock-fn-default fixture and corpus program 5816 compare with Node,
plain and with SCRIPTC_SAN=1.
2026-10-19 16:59:05 -05:00
Chris Tate cf87b359e7 Merge main 2026-10-19 16:50:17 -05:00
Malte UblandClaude Opus 5.5 4e0f71a7e5 Document the new package selection; record the 2563 order baseline
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:21:57 -07:00
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
Malte UblandClaude Opus 5.5 6e68a811f1 WebAssembly W5: the Memory, Table and Global constructors, Module.imports/exports/customSections, and function references from tables
new WebAssembly.Memory, Table and Global construct the runtime objects an
instance exports, with Node 24's descriptor order, defaults, conversions
and messages (V8's EnforceUint32 errors, the v128 and shared-memory
errors; a valid shared memory is refused with a TypeError until threads),
so either kind can be imported. Imports of memories, tables and globals
now check limits, types and mutability with V8's LinkError messages, and
exports keep V8's identity (imported objects re-export as themselves, one
object per memory and table). The classes called without new throw Node's
TypeError.

WebAssembly.Module.imports, exports and customSections read the
descriptor (ABI v2). Custom sections are carried only by programs that
call customSections. Every function a module references reaches
JavaScript as a callable function: the translator emits one call thunk
per signature, so table.get and funcref globals return the module's
unexported functions too, and exported functions can be stored in tables
and globals from any instance. Discovery also finds .so WebAssembly
shared libraries at resolved paths.

Fixes to W3/W4 found on the way: externref Table.grow fills with
undefined; Memory.grow/Table index errors and funcref messages match V8;
an invalid funcref global import is a LinkError; global.valueOf() on a
WebAssembly.Global-typed receiver answers the value; explicit-bounds
memories keep the blocks grow replaced, so old ArrayBuffers never
dangle.

This unblocks quickjs-wasi's extension loader: quickjs-wasi with its url,
encoding, headers and structured-clone extensions runs through its own
glue identically to Node, and pi codemode's static coverage goes from 83%
to 84% with no WebAssembly blocker left.

Evidence: differential corpus programs for the API (webassembly-jsapi),
a 261-test port of WPT wasm/jsapi (webassembly-wpt-jsapi), quickjs-wasi's
dist/extensions.js run unmodified against dylink-shaped modules
(webassembly-dylink), and the 2.0 spec suite's modules that import the
spectest memory and table (new webassembly-spec-imports group plus the
memory and table groups); all match Node in the plain and sanitized
lanes. The zero-cost gate leaves every non-WebAssembly program
byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:27:45 -07:00
Chris Tate b9fbe4b50b Merge main 2026-10-19 08:42:36 -05:00
Chris Tate edd3874925 Run iterator helpers and destructuring on generators passed through untyped values (#1389) 2026-10-19 07:57:24 -05:00
Chris Tate 809f2cbea1 Merge main 2026-10-19 07:55:06 -05:00
Chris Tate a57089aebf Accept readline's dispose, instanceof, dynamic streams and non-string prompts
rl[Symbol.dispose]() closes the interface, as Node's dispose method
calls close(). rl instanceof readline.Interface and
readlinePromises.Interface answer which module created the interface:
rl.create's mode marks readline/promises interfaces, and the fallback
declarations declare both Interface classes as values.

In JavaScript, createInterface's input and output can be checked-dynamic
values (a function's result, an object's member, an element that may be
undefined): rl.inputFromDyn and rl.outputFromDyn unbox the stream they
hold, an output of null or undefined means no output, and other values
are refused.

In JavaScript, question() and setPrompt() accept a value that is not a
string. When such a prompt is written, an interface that is not a
terminal throws ERR_INVALID_ARG_TYPE as Node's kWriteToOutput does, and
the readline/promises question rejects with it; the question stays
pending and the previous prompt returns when it is answered. A terminal
interface writes String(value) of a primitive.

Corpus 5876-5878 match Node v24.22.0.
2026-10-19 07:51:54 -05:00
Malte UblandClaude Opus 5.5 60e0b246e7 Choose record key order from the orders values are built in
Each record shape's enumeration table now follows the key order its object
literals, spreads, rest bindings and record copies create, and appends
optional keys assigned after construction. Shapes whose values are built
in more than one order keep their declaration order and SC6002.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:34:30 -07:00
Malte UblandClaude Opus 5.5 78184a9869 WebAssembly W3–W4: translate modules to native code at build time, the JavaScript API over them, and Module transfer to workers
A build now translates every WebAssembly module whose bytes it can see
(W2's discovery) into LLVM IR with a TypeScript translator
(packages/compiler/src/wasm/module.ts, translate.ts) and appends it to the
program module, so the existing LLVM helper optimizes, partitions and
caches it with the program. The program also emits scr_wasm_registry, a
portable table of module descriptors (scr_wasm_abi.h). At run time,
WebAssembly.compile, instantiate, validate, new Module and new Instance
find the translated module by the SHA-256 of its bytes; bytes the build did
not see reject with a CompileError. There is no interpreter and no JIT.

The runtime (scr_wasm.c, link-gated by the new `wasm` feature, handle tags
inside W0's headroom) implements the JavaScript API once, over
descriptors, with Node's semantics and messages: exports (functions,
memories, tables, globals), imports (functions, immutable globals, and
memories, tables and globals other instances export), every trap as
WebAssembly.RuntimeError, the CompileError, LinkError and RuntimeError
classes, compile and instantiate settling on a later turn, and
globalThis.WebAssembly as a value. On 64-bit Linux and macOS, memories sit
in 8 GiB guard reservations and a SIGSEGV/SIGBUS handler turns faults into
traps or stack exhaustion; async fibers get a guard page on first use.
Sanitizer builds and Windows check bounds and the stack explicitly. A
WebAssembly.Module crosses to workers through W0's transfer hook (W4).

W1's refusals become lowerings; the Memory, Table and Global constructors,
Module.imports/exports/customSections and the streaming functions still
refuse with SC2020. The globals: WebAssembly compatibility row moves to
partial.

Evidence: differential corpus programs for the API, TypeScript use and the
codemode worker shape, plus five programs executing about 9,300 commands
of the WebAssembly 2.0 spec suite; all match Node in the plain and
sanitized lanes. quickjs-wasi runs through its own glue, matching Node
(apart from a pre-existing non-wasm fence) and on par with or faster than
V8. The zero-cost gate leaves every non-wasm program byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:33:11 -07:00
Chris Tate e82686eab7 Add getTestContext to node:test
node:test changes, matching Node 24.22:

- getTestContext() returns the context of the test, suite or hook whose
  code is running: undefined at module top level, the root's context in
  module-level hooks, the suite's in a suite body and in the suite's
  hooks, and the test's in its body. In the afterEach and t.after hooks
  of a subtest cancelled by its parent's timeout, it returns each hook's
  owner.
- The context rides the AsyncLocalStorage snapshot under a reserved id,
  so it follows a test across its awaits, into the async functions,
  promise callbacks, timers and other callbacks it schedules, and into
  concurrent subtests, which each see their own.
- The root's fullName is "<root>", as its name is.
- Optional chains over a test context, such as getTestContext()?.name,
  lower.

AsyncLocalStorage enter and restore move into the always-linked async
core so the test runner can use them. The snapshot holds the node's
index in a table of live nodes, so a snapshot that outlives its node
answers undefined.

The get-test-context and get-test-context-cancelled fixtures compare
the contexts seen at each point with Node, plain and with SCRIPTC_SAN=1.
2026-10-19 06:29:28 -05:00
Chris Tate 6a8732f57a Run iterator helpers and destructuring on generators passed through untyped values
A compiled generator that reached an untyped value, as in
`function f(it) { return it.map(g) } f(gen())`, refused its iterator
helper calls with SC1071 at run time: the runtime's helpers could not
step a generator's capsule. Array destructuring of untyped values only
handled arrays, strings and bytes, so `const [a, b] = it` threw "is not
iterable" for generators, iterators, Maps and Sets.

Programs that box a synchronous generator and call a helper method or
destructure an untyped value now register, first thing in %main, the
checked getters that read a generator capsule's next and return methods
(scr_dyn_set_generator_members). With them:

- helper calls on a generator capsule run the runtime's helpers, which
  step and close the generator through its own next() and return();
- reads of a helper name answer the runtime's helper function, so
  `typeof it.map` is "function" and `it.reduce.call(it, f, init)` works;
- destructuring of any other iterable steps it through the iterator
  protocol (scr_dyn_iter_take, and rest elements through
  scr_dyn_iter_pack), closing an iterator that has values left.

A return() through an untyped value with no argument, or with undefined,
now completes with undefined as the typed .return() does, instead of
converting undefined to the generator's return type. This also fixes
for-of loops over untyped generators with a typed return value that exit
early.
2026-10-19 05:52:22 -05:00
Chris Tate 48259bd7cd Refuse literal accessor descriptors for declared class fields at compile time
Object.defineProperty with an object-literal descriptor that declares get
or set for a declared class field is refused at compile time with SC2020,
naming the field. JavaScript sources report it as a deferred fence, so
coverage lists the call. Other accessor descriptors still throw SC2020
when the call runs.

The native descriptor test now compiles a data descriptor change on a
declared field and compares the output with Node, and checks that an
accessor descriptor stays fenced.
2026-10-19 05:41:16 -05:00
Chris Tate 91c66e0eba Support Object.freeze, Object.seal, Object.preventExtensions and Object.defineProperty on class instances
Object.freeze, Object.seal and Object.preventExtensions accept a class
instance whose static type names the class, and Object.isFrozen,
Object.isSealed and Object.isExtensible answer for it as Node does.
Object.defineProperty with a string-literal key changes the value and
the writable, enumerable and configurable attributes of a declared
field, with Node's validation and errors.

Only hierarchies passed to these calls pay for them: they get a
per-instance integrity word and an attribute word per redefined field,
and each write to a declared field checks them first. A write to a
read-only field throws Node's TypeError in strict mode code and leaves
the field unchanged in sloppy mode code; a class field initializer on a
non-extensible instance throws "Cannot define property x, object is not
extensible". The instance's checked view carries the attributes, so
Object.keys, JSON.stringify, util.inspect and getOwnPropertyDescriptor
follow them. Readonly<C> over a class maps to the class.

Through an untyped value, freezing, sealing or preventing extensions of
a class instance throws SC2020 instead of doing nothing. The runtime's
"Cannot redefine property" and "Cannot add property" errors name the
property, as Node does.
2026-10-19 04:13:47 -05:00
Chris Tate 20a989e405 Format Dates with Node's local time zone names (#1355) 2026-10-19 04:12:27 -05:00
Chris Tate d79bcf9d79 Accept test and describe calls without a name, and settle skip and todo as Node does (#1371) 2026-10-19 03:49:07 -05:00
Chris Tate f75b7d8aab Use a computed test name as given when the function's name is unknown
A test name computed at run time took the function's name when it was
empty, which needs that name at compile time. For a function the
program received as a parameter, such as a helper that registers
`test(\`case ${label}\`, body)` for its callers, the call was refused,
where it compiled before. The name is now used as given in that case,
and the limitations page notes that an empty one stays empty, where
Node uses the function's name.

The test-name-forms fixture registers tests through such a helper and
compares them with Node, plain and with SCRIPTC_SAN=1. Without this
change it fails to compile.
2026-10-19 03:22:05 -05:00
Chris Tate 496cf577c7 Print iterators and iterator helper objects as Node does
console.log and util.inspect of a generator object, an iterator helper,
or an array or string iterator were refused, and printing a Map or Set
iterator threw "util.inspect of a dynamic Array Iterator is not
supported yet".

Compiled generators now carry the tag their object prints with:
"Generator" or "AsyncGenerator" by default, "Iterator Helper" for the
helpers, "Array Iterator" and "String Iterator" for the iterators the
compiler builds as generators. The spawn wrapper sets it through the new
scr_gen_set_tag; an adapter over another generator copies that
generator's tag (scr_gen_copy_tag), and generators that stand in for an
object of another class, such as a Set iterator adapted into an
iterator-typed slot, keep refusing to print. They print as Node's
Object [<tag>] {} at any depth, and Object.prototype.toString answers
[object <tag>].

The runtime's native iterators print as Node does: Object [Array
Iterator] {} and Object [String Iterator] {}, and for Map and Set
iterators the entries the next steps would produce, without advancing,
under [Set Iterator], [Set Entries], [Map Iterator] or [Map Entries],
as [Object [Set Iterator]] past the depth limit and {  } when none are
left. Object.prototype.toString answers [object Set Iterator] and the
other tags.
2026-10-19 01:10:39 -05:00
Chris Tate 96d3a0f5e4 Carry AsyncLocalStorage stores into timer, immediate, tick and microtask callbacks (#1365) 2026-10-19 01:01:02 -05:00
Chris Tate 1b27822d64 Merge main 2026-10-19 00:47:00 -05:00
Chris Tate 60535fd09c Accept test and describe calls without a name, and settle skip and todo as Node does
node:test changes, matching Node 24.22:

- test, it, describe, suite, their skip/todo/only twins and t.test take
  Node's (name?, options?, fn?) forms: test(fn), test(options, fn),
  test(options) and test() compile, as does describe(fn) and
  describe(options, fn). The fallback declarations add the overloads.
- A missing or empty name is the function's name, or "<anonymous>", as
  Node's Test constructor completes it. The name is the function
  expression's or declaration's own name, or the binding name of a
  const arrow or anonymous function; a name that is empty at run time
  takes the same default.
- With both skip and todo, the test is skipped, and the directive message
  is the skip string, else the todo string. The skip and todo method
  twins override the option of the same name, so test.skip("x",
  { skip: "msg" }) reports a bare SKIP and it.skip("x", { todo: "msg" })
  reports "msg". Before, the last option set the mode.

The limitations page notes that a thrown object or array that is not an
Error reports [object Object] in the failing section.

The test-name-forms fixture compares the names, directives and counts
with Node, plain and with SCRIPTC_SAN=1.
2026-10-19 00:21:03 -05:00
Chris Tate 9f2e9b00c9 Write the limitations partials one sentence per line
Each limitations partial held its whole text on one line (map-and-set.mdx
was a single 1,080-character line), so any two edits to the same partial
conflicted. The partials now put one sentence per line, with a bullet's
continuation lines indented two spaces. Markdown joins the lines, so the
rendered pages are unchanged.

scripts/limitations-partials.mjs reads a labeled bullet's indented
continuation lines as part of the bullet when it splits and joins the page,
and AGENTS.md states the convention.
2026-10-19 00:11:31 -05:00
Chris Tate ce03d9a1db Lower string and typed array iterators, Iterator.from and helper return() (#1363) 2026-10-18 22:32:46 -05:00
Chris Tate f8140efb56 Merge main 2026-10-18 22:31:49 -05:00
Chris Tate b3feb6761b Merge main 2026-10-18 22:29:57 -05:00
Chris Tate 1f7f029a05 Carry AsyncLocalStorage stores into timer, immediate, tick and microtask callbacks
AsyncLocalStorage changes, matching Node 24.22:

- setTimeout, setInterval, setImmediate, process.nextTick and
  queueMicrotask callbacks run with the stores that were active when they
  were scheduled. Before, getStore() returned undefined in them.
- An interval keeps that context on every tick, and a refreshed timer
  keeps it too.
- enterWith inside one of these callbacks lasts until the callback
  returns. An enterWith in top-level code ends when the main module's body
  returns, and one inside a callback scheduled with no store active ends
  when that callback returns, so the store does not reach callbacks that
  were scheduled without it.

Programs that enter stores (store.run, exit, enterWith, or a
diagnostics_channel store binding) schedule through new *_als runtime
entry points, which wrap a callback scheduled with a store active in a
closure that installs the stores around the call. The emitter picks them
from the new asyncContext runtime feature; other programs keep the plain
entry points, so their scheduling is unchanged.

The limitations page notes that completion callbacks of callback-style
I/O APIs still run with no store.

5815-als-callback-context compares the stores each kind of callback sees
with Node, plain and with SCRIPTC_SAN=1.
2026-10-18 22:26:09 -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 96f59b0f3f Free errors that crossed into checked-dynamic code and attach Node's error properties
The error identity cache (scr_dyn_from_error) keeps one checked-dynamic
node per error so a crossing error compares reference-equal and keeps the
properties set on it. It retained both sides for the whole process, so
every error that crossed a checked-dynamic boundary (a caught error read
through a cast, an error delivered to an untyped callback, a dns failure, a
warning) stayed alive until exit: 100,000 caught AssertionErrors read
through a cast grew the resident set by about 210 MiB.

The cache now sweeps itself. An entry whose error and node are referenced
only by the cache is unreachable (a node holds no reference to its error;
a registered subclass box holds one, and a spawnSync error's `error`
property refers to its own node), so a sweep releases both sides. Sweeps
run when the cache doubles past the entries the last sweep kept, so they
cost amortized constant time per crossing. The cache is per thread, and
objects shared across threads are published (immortal), so a sweep never
races another thread's reference. Live errors and live nodes keep their
entries: identity, rethrowing the same instance, isNativeError and kind
checks are unchanged.

scr_error_set_prop sets an own property on an error's node; dns, assert,
cipher, crypto, util.callbackify and warning errors use it for the
properties they attach. util.inspect of a runtime error renders through
its node: Node's own properties follow the bracket form in key order (a
system error's errno, code, syscall and path, and the extra properties a
throw site attached), and an AssertionError renders at depth 0 with long
string actual/expected values shortened, as its custom inspect does. A
node that contains itself renders as Node's <ref *1>/[Circular *1].

child_process errors carry Node's own properties:
- execSync and execFileSync: status, signal, output, pid, stdout and
  stderr (Buffers, or strings with an encoding option); a spawn failure
  or timeout adds errno, syscall, path, spawnargs and error first.
- execFile's callback and the promisified execFile/exec: code (the exit
  status, or null after a signal), killed, signal and cmd, with stdout and
  stderr when promisified; a spawn failure has errno, code, syscall, path,
  spawnargs and cmd.
The sync forms echo the captured stderr before a timeout throw too, as
Node's inheritStderr does. The cp.execSync libCall takes a flag for Buffer
outputs.

An error's own `stack` stays non-enumerable when assigned through its
node. A worker's record-source table is freed when the worker exits.
2026-10-18 21:53:14 -05:00
Chris Tate a7e3a9d0aa Cancel running subtests when their parent test times out (#1350) 2026-10-18 21:24:04 -05:00
Chris Tate 9dc45b6776 Check with tsgo by default on Windows 2026-10-18 21:00:33 -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 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
Chris Tate 6c07441c24 Check programs with tsc-ts by default
scriptc's frontend talks to a TypeScript 7 API server. tsc-ts serves the
same API as tsgo, and it is now the default checker; SCRIPTC_CHECKER=tsgo
selects tsgo, which scriptc still includes.

- @scriptc/compiler depends on tsc-ts 0.0.3. The native distribution
  ships its platform package as lib/tsc-ts, named by a new tsc_ts field
  of the toolchain manifest; a manifest without it takes the executable
  of an installed tsc-ts package.
- The executable cache key includes the tsc-ts executable's identity, in
  the compiler and in the CLI's startup route.
- tsc-ts keeps the syntax trees it builds for the life of its process,
  so the source parser starts a new server after 256 KB of parsed
  source instead of letting one server grow with every file.
- A CI job compiles every corpus program with both checkers, in release
  and dev, and requires the same LLVM IR and diagnostics
  (scripts/checker-identity.mjs).
- Tests that compare with the TypeScript 7 SDK, the checker-panic
  diagnostics, and the protocol tests run against tsgo.
2026-10-18 19:55:52 -05:00