Commit Graph
2562 Commits
Author SHA1 Message Date
Chris Tate cb3d7dbe7c Merge main 2026-10-18 12:24:21 -05:00
Chris Tate 71e34fa445 Merge main 2026-10-18 12:16:33 -05:00
Chris Tate 3c638d5537 Add the zero-cost gate (#1304) 2026-10-18 11:57:52 -05:00
Chris Tate bc46dbb239 Refresh the measured corpus costs from recent main runs (#1306) 2026-10-18 11:53:42 -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 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 4128e6f6f5 Count instructions for differing corpus executables in the zero-cost gate
--cachegrind now also runs differing corpus executables (without
arguments, accepting intentional non-zero exits), so emitter changes that
only touch corpus programs get per-function instruction deltas. AGENTS.md
mentions the dynamic-tier symbol rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:57:34 -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
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 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
Chris Tate 4d417da7b8 Select the discriminated member for asserted object literals
An object literal asserted to a discriminated union keeps its own record
layout, and that layout can equal another member's layout: in
`{ kind: "return", value, loc } as Stmt`, a non-null `value` gives the
fields of a `throw` member. The value was wrapped as that member, so a
switch on `kind` read it with the `return` member's layout. The compiled
program aborted with an invalid union tag or read past the record; the
native compiler crashed this way while lowering three.js.

The literal's discriminant now selects the member through type
assertions too, as it already did through parentheses and `satisfies`.
2026-10-18 10:22:24 -05:00
Chris Tate cd61e0fb46 Add the TestContext filePath, attempt, passed, workerId and tags values (#1297) 2026-10-18 10:21:30 -05:00
Chris Tate 675388805c Read named groups through optional chains and keep their null prototype
`m?.groups?.year` compiles on exec and match results, including a null
match, where the read was refused before. The matchAll row callbacks also
cover findIndex and findLastIndex.

Reshaping an index-signature record into another index-signature type
keeps the source's null prototype, since the two are one object in
JavaScript. A match's groups read through `m?.groups`, or assigned to a
declared type such as `{ [k: string]: string } | undefined`, now prints
"[Object: null prototype]" as in Node, where it printed a plain object.
2026-10-18 10:18:12 -05:00
Malte UblandClaude Opus 5.5 8e6986c561 Add the zero-cost gate
scripts/zero-cost-gate.mjs compiles a fixed good-code set (the runtime
benchmark workloads, the self-hosted compiler, and the TypeScript corpus)
with a baseline and a candidate checkout. It requires byte-identical IR,
LLVM and release executables, and names the first differing IR function,
LLVM function or executable symbol when something changes. Programs that
match the change's pattern signature (--allow) may differ. It also checks
compile time (paired builds, bootstrap interval), binary size, optional
per-function cachegrind instruction counts, and the two-tier I3 symbol
rule: no executable gains dynamic-tier runtime symbols, and fully typed
benchmark workloads define none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:17:51 -07:00
Chris Tate 2e2a29426d Support zlib stream classes as const aliases and instanceof targets (#1296) 2026-10-18 10:15:42 -05:00
Chris Tate 910ed5e027 Widen nested record fields with the records that hold them (#1293) 2026-10-18 10:08:00 -05:00
Chris Tate 8cfb5f789f Lower util.promisify over the crypto callback functions and dns lookups (#1291) 2026-10-18 10:00:38 -05:00
Chris Tate acc920ab85 Add the TestContext filePath, attempt, passed, workerId and tags values
node:test changes, matching Node 24.22:

- t.filePath is the entry file's path, as Node's entryFile, fixed when the
  program is compiled.
- t.attempt is 0, since a test runs once.
- t.passed is false while the body runs and the test's outcome once the
  body ends, so afterEach and t.after hooks see it, as Node's pass() and
  fail() set it before them.
- t.workerId is Number(process.env.NODE_TEST_WORKER_ID) || undefined.
- t.tags is [], since the tags option has no lowering.
- A test's t.before, t.after, t.beforeEach and t.afterEach hooks are
  released when the test ends. Hooks whose closures capture the test's
  context no longer keep it alive in a cycle, which the SCRIPTC_SAN=1
  audit reported as live closures at exit.

The ctx-members fixture compares the reporter output with Node, plain and
with SCRIPTC_SAN=1.
2026-10-18 09:57:26 -05:00
Chris Tate 0bed1421b0 Verify checker-proven narrowings at run time (#1295) 2026-10-18 09:54:12 -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
Chris Tate 2fa4b19b45 Keep object-literal computed keys out of the method replacement census (#1294) 2026-10-18 09:46:31 -05:00
Chris Tate a52c713868 Merge main 2026-10-18 09:45:58 -05:00
Chris Tate ff90245b27 Read fields as undefined before their assignment (#1292) 2026-10-18 09:38:26 -05:00
Chris Tate 8dc0071e92 Merge main 2026-10-18 09:37:20 -05:00
Malte UblandClaude Opus 5.5 23e445f3a0 Say that a lying type predicate can fail a narrowing guard
A narrowing guard fails on a compiler bug, and also on a program whose
hand-written type predicate or assertion function (`x is T`,
`asserts x is T`) lied about a value's type: TypeScript trusts those
claims without checking them. The trap messages no longer call the
failure an internal error:

  scriptc: a narrowed value does not have the type its narrowing proved
  (a type predicate or assertion may be wrong)

with matching wording for downcasts and catch bindings. The guard header
and the IR comments say the same. A native test pins that a lying
predicate and a lying assertion function print their earlier output and
then abort with the message, where they used to crash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 07:31:43 -07:00
Malte UblandClaude Opus 5.5 e12db2974f Verify checker-proven narrowings at run time
unionNarrow, downcast and caughtNarrow extracted a payload as the arm,
class or caught kind TypeScript's narrowing proved, with no runtime
check. A wrong proof read the payload as another type. Each extraction
now verifies its proof once: the union tag (for a two-arm union, that
the value does not hold the other arm, which LLVM folds under either
dominating test), the target's vtable preorder intervals (shared with
instanceof), or the caught kind or class (shared with caughtTest). A
mismatch calls scr_trap with an internal-error message from a cold
block. A correct program never reaches it: narrowings the frontend
cannot prove already lower to checked extraction, which throws the
catchable TypeError, and analyses treat these expressions as
non-throwing, so a failing guard aborts instead of unwinding.

The guards found a stale narrowing the stability analysis missed: a
binding initialized or assigned from a narrowed read whose narrowing a
callback invalidated inherits that narrowing (`const held: Expr =
current`). main segfaults on that program where Node throws a
TypeError. NarrowingStability now treats such bindings like captured
mutable ones, so their narrowed reads use checked extraction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 07:31:42 -07:00
Chris Tate 0bdcccef80 Lower child_process.exec's callback forms (#1290) 2026-10-18 09:26:38 -05:00
Chris Tate 6c4e4c39b4 Keep object-literal computed keys out of the method replacement census
An object literal with a computed key that does not fold to a constant,
such as `{ [key]: 1 }`, entered the method replacement census as a write
with an unknown key and no receiver. That marked every class method name
in the program as replaceable, so method calls went through the checked
property lookup. A computed key in an object literal defines a property of
the new object, which is not a class instance, so the census now skips it.
Literals passed to Object.assign or Object.defineProperties are still
judged with their receiver.

Closes #962
Co-authored-by: Quim T. <26749475+espetro@users.noreply.github.com>
2026-10-18 09:23:31 -05:00
Chris Tate 33c9d91796 Widen nested record fields with the records that hold them
A record held in another record's field (`state.parsed`) and passed to a
function that stores a possibly missing array element in it kept its
declared layout, so the store threw where Node stores undefined. The field
read opened the record flow class of the function's parameter.

Such a field read now gets a field slot in the record flow classes. Field
slots join the same field of every member of the owner's class, take the
values literals and assignments store in the field, and widen only when
the owner's class can take the new layout too. Every member of the owner's
class then holds the widened record, so writes and identity are kept. An
owner that is spread, destructured or passed to Object.values or
Object.entries keeps the declared layout.
2026-10-18 09:17:29 -05:00
Chris Tate 0d4e6a0cc1 Compile classic CommonJS packages: late requires, JSON requires, nested builtin destructuring, settled require cycles (#1287) 2026-10-18 09:16:05 -05:00
Chris Tate 27f78a981d Read fields as undefined before their assignment
A constructor that let other code read a field before assigning it (a base
constructor calling an override, or the constructor calling its own method)
read an empty slot, which crashed the executable for string fields. Node
reads undefined.

Fields assigned after construction can expose the instance now admit
undefined. Allocation writes undefined into them. String, number, boolean,
bigint and union fields read as runtime-optional values, and class, array,
record and collection fields use the deferred-init reads, whose member
access throws a TypeError. Classes whose construction cannot expose the
instance before their assignments keep their layouts.
2026-10-18 09:14:01 -05:00
Chris Tate 1a007e9a2b Lower util.promisify over the crypto callback functions and dns lookups
promisify(crypto.randomBytes), pbkdf2, scrypt, hkdf, randomFill, randomInt
and generateKey, and promisify(dns.lookup) and dns.lookupService, over a
named or namespace import, now lower. A call through the binding runs the
callback form with a runtime callback that settles the returned promise
where the callback would run: fulfilled with the callback's value, or
rejected with its error. An argument error the callback form throws rejects
the promise instead, as the throw inside Node's promisify wrapper does.

The crypto promise's value follows the declared result type: a Buffer,
the filled view (the same object), a number, or a checked-dynamic value
where @types/node declares unknown (scrypt, randomFill, randomInt) and for
ArrayBuffer and KeyObject results. The runtime adds a callback closure that
owns the promise and settle adapters for each representation, next to the
existing callback forms in scr_crypto_async.c.

dns.lookup's promise fulfills with { address, family }, or the address
array with options.all, and lookupService's with { hostname, service }, as
Node's custom promisified forms do. The runtime builds the checked-dynamic
result; a declared record type is reached through an async helper that
awaits it and checks the value. The type mapper maps the promisified
lookupService result literal like the promisified exec result.

The fallback declarations gain the matching util.promisify overloads.
2026-10-18 09:09:03 -05:00
Malte UblandClaude Opus 5.5 542f6573bc Name the declared type in refused class field diagnostics
TypeScript class fields declared `unknown` or `object` compile since #1147,
but every other field type without a typed representation was still
reported as "'unknown'-typed class fields": `any`, `Array<any>`, and
`WeakMap<object, string>` alike. The refusal now names the declared type,
for example "'any'-typed class fields", so the blocker reads as what it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 07:07:45 -07:00
Malte UblandClaude Opus 5.5 07b44cd4b1 Index strings whose binding still stores a union
`const w = words[i]; w[w.length - 1]` and the narrowed form
`typeof t === "string" ? t[t.length - 1] : ...` were refused with SC1090
("string indexing with this index/result shape"), in `if` statements as well
as in conditional expressions. A binding read from an array keeps an
undefined arm in its storage, and a typeof guard narrows only the checker's
type, so the receiver reached the string-index lowering as the storage
union rather than a string.

The refused path now captures the union and the index and reads the string
arm. A missing value performs the checked-dynamic keyed read on the unit,
which throws Node's "Cannot read properties of undefined (reading '<key>')"
with the evaluated key; a value arm the checker excluded fails the checked
extraction. Receivers that compiled before take the paths they took before.
This is path-to-regexp's tokensToRegexp shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 07:07:44 -07: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