Commit Graph
1826 Commits
Author SHA1 Message Date
Malte UblandClaude Opus 5.5 8a02efaa91 Type process.getBuiltinModule results as module namespaces
Under @types/node, process.getBuiltinModule("node:os") types as the
module namespace, which lowered as an unchecked dynamic value and failed.
A string-literal id naming a supported builtin now lowers to the namespace
value an `import * as` binding has, including pi's cast-and-probe spelling
`(process as P).getBuiltinModule?.("node:os") ?? null`; a nullable
namespace binding narrowed present keys the same member tables, and a
namespace is truthy. process.features read as a whole value (`"sea" in
process.features`) is an object holding none of the flags, matching the
existing member reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:04 -07:00
Malte UblandClaude Opus 5.5 5e964965f6 Assign process.emitWarning
pi's CLI setup replaces process.emitWarning to silence warnings. The
assignment now stores the replacement: later process.emitWarning(...)
calls apply it to their arguments, and runtime-built warnings (Node's own
modules call process.emitWarning) reach it as (message, type[, code])
instead of the report and the 'warning' listeners. process.title writes
already lowered; the corpus program pins both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:15:03 -07:00
Chris Tate 42591b1eed Read zlib options held in typed objects (#1316) 2026-10-18 14:24:54 -05:00
Chris Tate 240f576805 Add execution tiers to the IR and a report-only tier analysis (#1315) 2026-10-18 14:08:12 -05:00
Chris Tate d9f0005ecb Represent unions of records as the record pointer itself (#1225) 2026-10-18 14:04:16 -05:00
Chris Tate a5548597e5 Read zlib options held in typed objects
A zlib options object that reaches the runtime as a typed reference (a
cast object literal such as `{ level: 0 } as zlib.ZlibOptions`, which the
checked-dynamic options slot holds by reference) was not a plain
checked-dynamic object, so the options reader found no keys: every option
was ignored and out-of-range values were not reported. The sync, callback
and promisified one-shots and the stream constructors compressed with the
defaults instead.

scr_zlib_options_parse and scr_zlib_stream_new now read a typed reference
through its materialized view, as the blob and fetch option readers do, so
the options apply and invalid ones throw Node's ERR_OUT_OF_RANGE.
2026-10-18 14:02:03 -05:00
Chris Tate 329ea4387e Convert a Date to local time once for its local getters and follow TZ changes (#1313) 2026-10-18 13:57:42 -05:00
Chris Tate e5c45f8253 Merge main 2026-10-18 13:41:18 -05:00
Chris Tate f80eb5fc39 Give every arm of record unions that share arms a collector header
A shape union's arms all carry a collector header when one of them does.
The emitter applied that rule in one pass over the unions in module order,
so a header that a later union gave a shared arm did not reach the other
arms of an earlier union. With First = A | B and Second = B | C, where only
C is cyclic, B got a header through Second, First's trace helper then
called A's trace function, and A had none: the module failed LLVM
verification with "use of undefined value '@sc_rtrace_r0'".

The rule now repeats until no union adds a header. Corpus
5919-record-unions-shared-arms chains Third = D | A, First = A | B and
Second = B | C, so the requirement passes from Second through First to
Third.
2026-10-18 13:39:12 -05:00
Chris Tate fe7b1cd199 Convert a Date to local time once for its local getters and follow TZ changes
Each local-time getter (getFullYear, getMonth, getDate, getHours, ...,
getTimezoneOffset) called localtime_r and walked the calendar for its
own field. The thread now keeps its last local conversion, keyed by the
instant and a timezone generation, so reading several local fields of
one Date converts it once. The UTC walk and the second computation use
integer division instead of floor.

Writing or deleting process.env.TZ calls tzset and starts a new
generation, so later local-time reads use the new zone, as in Node.
Before, the zone the process started with stayed in effect.
2026-10-18 13:33:41 -05:00
Chris Tate 761252e173 Expect the run-time refusals of node24-file-url-conversion and stream-shims
Both fixtures declare a function-local class whose base has no static
lowering (`class FileURL extends URL {}`, `class Counter extends
Readable`). The SC1090 for it used to come from the whole-program type
queries, which collected the class outside any statement, so the build
failed. Those queries now keep a refused local class out of their
results and drop its diagnostics; the class declaration reports the
refusal when it lowers, and in a JavaScript source a refused statement
becomes a runtime fence. Both programs therefore compile and throw the
fence when they reach a construct without a lowering, like the other
fixtures listed with a run refusal.
2026-10-18 13:20:51 -05:00
Chris Tate 16da839c62 Merge main 2026-10-18 13:16:06 -05:00
Chris Tate 09b0dffebc Walk dead cycles once in the cycle collector (#1286) 2026-10-18 13:09:16 -05:00
Chris Tate 7cfb6b16d2 Report unhandled rejections between timers and between immediates
Node runs runNextTicks after each timer and each immediate callback, which
reports the unhandled rejections the callback left before the next one runs.
The event loop now leaves the timer phase when a callback leaves the
rejection ledger non-empty, the check phase waits for that report, and the
check phase reports the ledger after each immediate's checkpoint.

When several timers expired in the same turn, a later timer ran before an
earlier timer's rejection was reported. On macOS the 2 ms and 3 ms timers in
5809-process-exit-and-rejections.cjs expire together, so its uncaught
'thrown string' was printed before the TypeError rejection.

5996-rejections-between-macrotasks.cjs makes five timers expire in the same
turn and checks the order across three immediates.
2026-10-18 12:47:01 -05:00
Chris Tate 4b8b5e3b81 Revert "Read fields as undefined before their assignment" (#1310) 2026-10-18 12:38:47 -05:00
Chris Tate debbd359d7 Merge main 2026-10-18 12:30:17 -05:00
Chris Tate 29f6d8e47f Merge main 2026-10-18 12:28:02 -05:00
Chris Tate f59049112f Compile chalk 5 through the dynamic object model (#1309) 2026-10-18 12:25:45 -05:00
Chris Tate cb3d7dbe7c Merge main 2026-10-18 12:24:21 -05:00
Chris Tate da1af7437f Revert "Read fields as undefined before their assignment (#1292)"
This reverts commit ff90245b27.
2026-10-18 12:20:36 -05:00
Chris Tate 71e34fa445 Merge main 2026-10-18 12:16:33 -05:00
Chris Tate 9ef9c038da Merge main 2026-10-18 12:01:19 -05:00
Chris Tate 3c638d5537 Add the zero-cost gate (#1304) 2026-10-18 11:57:52 -05:00
Chris Tate 65d849e6b8 Merge main 2026-10-18 11:56:36 -05:00
Chris Tate 29c8ad237c Throw when nm cannot list an executable's symbols in the zero-cost gate
The gate read an empty symbol list when nm could not start, was killed by
a signal, or exited with a non-zero status, so the dynamic-tier symbol
check and the executable comparison passed without any symbols. nmOutput
now throws with the binary, the reason and nm's stderr.
2026-10-18 11:36:13 -05:00
Malte UblandClaude Opus 5.5 cd376c14f1 Refresh the corpus costs from main's newest green Linux shards
Rerun scripts/ci-corpus-costs.mjs over the 120 green Linux corpus shard
logs (plain and san, 20 shards each) of main CI runs 39142848734,
39142619508 and 39142079655 (307387c8e, 18c50b55d, b85dd44cd). Their
Linux lanes were all green; only macOS jobs were still queued.

This measures the programs added since the previous refresh, including
5770-5773, 5960-5962, 5973, 6041-6043, the CommonJS require programs
6140-6145 and 5023/5024. Coverage of current main's corpus goes from
2992/3012 (0.9934) to 3011/3012 (0.9997); the one unmeasured program,
scratch-string-slices.ts, landed after those runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:33:01 -07:00
Malte UblandClaude Opus 5.5 23981f0c95 Merge origin/main into opt/shard-costs-refresh
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:32:57 -07:00
Chris Tate 4bba6c1e0b Keep this PR's refreshed corpus costs after the main merge 2026-10-18 11:30:08 -05:00
Chris Tate a0496cbe1e Merge main 2026-10-18 11:27:27 -05:00
Chris Tate 45aa58c433 Run module-level before and after hooks (#1305) 2026-10-18 11:26:46 -05:00
Chris Tate 20e5a2d7c6 Write borrowed string slices into stack buffers (#1203) 2026-10-18 11:24:42 -05:00
Chris Tate ab5fa64c90 Prove array lengths stable in index loops and version dense array loops (#1247) 2026-10-18 11:21:01 -05:00
Malte UblandClaude Opus 5.5 577b0135a9 Add execution tiers to the IR and a report-only tier analysis
Stage S2 of the two-tier design. IrFunction gains a tier (absent means
static); lowering assigns it from function bodies, placing functions that
use the dyn vocabulary in the dynamic tier, and the validator enforces
invariant I1: a static function performs no dynamic operation, only
boundary operations (dynFrom, dynCheck, kind tests) and moves. Serialized
IR keeps the field out in this stage and loaders derive it again, so every
IR, LLVM and executable artifact is unchanged.

Every committed refusal and runtime fence becomes a recorded demotion
request attributed to the binding, field, function instance, class or
module the dynamic tier would take over, while the compiler still refuses
and fences as before. `scriptc coverage` reports dynamic-tier statements
(statementsDynamic), `--explain-dynamic[=<name>]` prints the demotion
chains, and `--tier=auto|static-only` is wired through build, run and
coverage for stage S6; both modes compile identically today.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:12:27 -07:00
Malte UblandClaude Opus 5.5 571ffb10a9 Refresh the measured corpus costs from recent main runs
298 of the 3008 corpus programs had no measured cost, which left main
three unmeasured programs above the shard harness's 90% coverage floor:
any PR adding a few corpus programs failed shard.test.ts. Rerun
scripts/ci-corpus-costs.mjs over the 120 green Linux corpus shard logs
of main runs 39131161610, 39130670620 and 39126633135 (31b52e215,
aba3c3a97, 572b26ef8), and drop the entry of the renumbered
5942-local-class-observed-methods.ts. 2989 programs are now measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:03:08 -07:00
Chris Tate d3dba4c07f Run module-level before and after hooks
node:test changes, matching Node 24.22:

- before and after hooks outside any suite run, with the root's context,
  whose name is "<root>". They did not run before.
- A module-level before hook runs at its before() call, as Node runs the
  hooks of its already-started root at once: its synchronous part runs
  before the code after the call, and the first top-level test waits for
  an async hook's promise. Every module-level before hook runs, and an
  async hook's rejection is not reported as an unhandled rejection.
- Module-level after hooks run after the last top-level test or suite.
- A throwing root before hook fails each top-level test with the first
  failure; the test's body and beforeEach hooks do not run, and its
  afterEach hooks do. Each top-level suite fails with the error, and the
  tests and suites in it are cancelled before they start: they report
  without a duration and fail with "test did not finish before its parent
  and was cancelled", listed in the failing section in completion order.
- A throwing root after hook reports the entry file as a failed result,
  after the top-level results, and lists it in the failing section at the
  hook's location. It is not counted, and the exit code is unchanged.
- Hooks record the "file:line:col" of their call for that location.

The root-hooks, root-before-throws and root-after-throws fixtures compare
the reporter output, the order of the hooks against the module code, and
the exit codes with Node, plain and with SCRIPTC_SAN=1.
2026-10-18 10:59:27 -05:00
Chris Tate 307387c8ea Answer undefined for missing Record and index-signature keys (#1236) 2026-10-18 10:58:02 -05:00
Malte UblandClaude Opus 5.5 1090c05dfe Compile chalk 5 through the dynamic object model
chalk's callable builder (a function whose prototype chain is set with
Object.setPrototypeOf, with style getters from Object.defineProperties)
already ran on the checked-dynamic model. What failed was the code
around it. Each fix is general and sits on a path that used to end in a
refusal or a runtime fence, or is confined to JavaScript files:

- package.json "imports" inside a --npm-static package resolve to the
  shipped JS with the "node" condition hoisted, like the tsgo fs shadow;
- JavaScript object literals that the file reshapes at run time (keyed
  writes with runtime keys, delete, Object.defineProperty/ies) map to
  the checked-dynamic type, with the literals nested in them;
- a JavaScript class whose only member is a constructor that returns a
  value and never reads `this` answers that value from `new` (the return
  used to be discarded silently);
- `const { env } = process` binds the data member in JavaScript;
- `o && x` / `o || x` with an always-truthy object, array or function
  left operand;
- Number.parseInt/parseFloat over checked-dynamic values apply ToString;
- Function.prototype as a value is one compiler-emitted function;
- calls of String.prototype-only names (bold, italics, ...) on untyped
  receivers call the receiver's own or inherited member; a string
  receiver throws the refusal at run time.

Programs without these patterns compile to identical bytes: the runtime
benchmark workloads, the self-hosted compiler (LLVM and executable) and
the TypeScript corpus IR were compared against the baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:57:56 -07:00
Chris Tate 18c50b55db Pass the SuiteContext and hook contexts (#1302) 2026-10-18 10:54:53 -05:00
Chris Tate b85dd44cd6 Renumber the local-class corpus program to 5945 (#1301) 2026-10-18 10:47:37 -05:00
Chris Tate 888726a06c Select the discriminated member for asserted object literals (#1300) 2026-10-18 10:46:09 -05:00
Chris Tate 835ee0a08d Drop the cost entries of a corpus program this PR doesn't add 2026-10-18 10:36:41 -05:00
Chris Tate aee210e184 Read named groups through optional chains and keep their null prototype (#1299) 2026-10-18 10:34:42 -05:00
Malte UblandClaude Opus 5.5 5a6efaf010 Bind top-level this in CommonJS modules to the module's exports object
Top-level `this` in a CommonJS JavaScript file, also inside top-level arrow functions, read as undefined; Node binds it to the module's original exports object. It is now a per-module object created when the module starts: typeof, truthiness, identity and passing it along (the UMD wrapper `(function (root) { ... })(this)`, CoffeeScript's `.call(this)`) behave as in Node. Exports stay the static export table, so a property access through top-level `this` is a named refusal rather than a write no requirer would see. ES modules keep undefined.

The UMD host test `typeof module !== "undefined" && module.exports` read `module.exports` through the module handle as undefined inside functions, so such modules took the non-CommonJS branch (this is what broke ipaddr.js). `module.exports` used only for its truthiness now answers true when every `module.exports =` in the file assigns an object or function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:33:04 -07:00
Malte UblandClaude Opus 5.5 15096a47ed Answer typeof on builtin import bindings by the export's kind
`const { inspect } = require("util"); typeof inspect` printed "string", as did `import { inspect } from "node:util"` in an ES module and `typeof promises` from `fs`. JavaScript sources represent builtin values without their own lowering as identity tokens (strings), and typeof answered from that representation. typeof on a builtin import binding in a JavaScript source now answers "function" for exported functions and classes and "object" for exported objects; the binding read has no effects. TypeScript sources compile as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:32:58 -07: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
Malte UblandClaude Opus 5.5 ce87ba722d Read missing record keys as undefined before every absence guard
`let e = map[k]; if (e === undefined) { e = {}; map[k] = e } e[c] = v` on a `Record<string, Record<string, string>>` compiled, then aborted with "record has no key (typed slot…)" where Node prints the result. Without noUncheckedIndexedAccess a keyed record read keeps its typed trap unless the next statement tests the binding for absence, and only `if (!e)` counted. Comparisons with the missing value now count too (`=== undefined`, `== null`, `!== void 0`, `typeof e === "undefined"` and their mirrored forms) for index-signature record reads whose checker type lacks undefined. Module-scope declarations get the same treatment: their pre-registered global takes the undefined arm before any body reads it, and the initializer lowers as the absence probe. Array reads, annotated bindings and noUncheckedIndexedAccess programs compile as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:32:46 -07:00
Malte UblandClaude Opus 5.5 dcdca2b810 Read and write records through a binding that may hold a missing record
Under noUncheckedIndexedAccess, `let e = map[k]; if (e === undefined) { e = {}; map[k] = e } e[c] = v` failed with SC9001 "recordKeySet receiver: expected record, got union". A runtime-optional binding read with a computed key reaches the record read and write lowerings as its `record | undefined` storage. Those lowerings now hold the receiver, the key and, for writes, the value in temporaries and throw Node's TypeError for an undefined receiver ("Cannot read properties of undefined (reading 'k')", "Cannot set properties of undefined (setting 'k')") after evaluating them in Node's order. Every other receiver builds the same IR as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:32:40 -07:00
Chris Tate c73f363906 Pass the SuiteContext and hook contexts
node:test changes, matching Node 24.22:

- A describe or suite body may take its SuiteContext, with name,
  fullName, filePath, attempt, passed and diagnostic. Its passed is false
  until the suite has finished, after its after hooks.
- before and after hooks may take the suite's context, and beforeEach
  and afterEach hooks the test's. A test's t.before, t.after,
  t.beforeEach and t.afterEach hooks may take the test's context.
- SuiteContext maps to the runner node that TestContext maps to, so the
  TestContext members that SuiteContext shares lower the same way.

The suite-context fixture compares the reporter output and the order of
the bodies and hooks with Node, plain and with SCRIPTC_SAN=1.
2026-10-18 10:31:11 -05:00
Chris Tate 3c264cf0d9 Renumber the local-class corpus program to 5945
The program shared the number 5942 with 5942-callback-specialization-throws.ts.
Its cost entries and order-parity baseline move with it.
2026-10-18 10:26:31 -05:00