Commit Graph
2536 Commits
Author SHA1 Message Date
Chris Tate 307387c8ea Answer undefined for missing Record and index-signature keys (#1236) 2026-10-18 10:58:02 -05: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
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
Chris Tate 9d0012ddab Lower child_process.exec's callback forms
exec(command, callback) and exec(command, { encoding, maxBuffer },
callback) lower like execFile's callback slice. The command line runs
with /bin/sh -c, as execSync runs it, the call returns the live
ChildProcess, and the error-first callback receives the captured utf8
stdout and stderr. A non-zero exit, including the shell's 127 for a
missing command, reports Node's "Command failed: <command>\n<stderr>":
scr_exec_file takes a shell flag so the message shows the command line.

Under @types/node, child_process.ExecException maps to Error, as
ExecFileException does. Its code member is the child's numeric exit
status, which the runtime error's string code slot does not hold, so
reading it, testing it with `in`, or testing its typeof is a named
refusal.
2026-10-18 09:05:09 -05:00
Malte UblandClaude Opus 5.5 04297f3064 Keep the preflight batching test on a fenced require binding
The test asserts that the TDZ analysis batches symbol queries over the
roots it scans, observed through an SC1013 refusal. Its const binding now
compiles with initialization checks instead of a refusal, so the test
uses a var binding, which keeps the fence and runs the same scan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 06:56:24 -07:00
Chris Tate 8c1d94e72e Compile large closure environments in linear time (#1284) 2026-10-18 08:51:59 -05:00
Chris Tate 4ed75111a7 Carry lifetime regions across await and reuse freed region chunks (#1194) 2026-10-18 08:47:04 -05:00
Malte Ubl 8255315883 Merge origin/main into opt/cjs-interop 2026-10-18 06:46:23 -07:00
Chris Tate f2e03bdb37 Keep records, tuples and spread updates that own references in frame storage (#1205) 2026-10-18 08:44:15 -05:00
Chris Tate b143778641 Read events.defaultMaxListeners from the ES module namespace copy (#1280) 2026-10-18 08:34:13 -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
Malte UblandClaude Opus 5.5 9118cbc258 Record corpus 5973's preflight baseline and skip the scaling test under ASan
The order-parity baseline for the new corpus program records its module
order and empty preflight diagnostics. Uncached sanitized builds rebuild
the runtime for every compile, which takes longer than either measured
size, so the compile-time growth test runs in the plain lane only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 06:23:52 -07:00
Malte UblandClaude Opus 5.5 ed5aa1043f Merge origin/main into opt/02-value-records
Resolve the emitter.ts import conflict by keeping both RESULT_DST_ID
(frame-stored result slots) and specializeCallbackCalls (#1206).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 06:23:47 -07:00
Malte UblandClaude Opus 5.5 10c5e269d7 Merge origin/main into opt/06-regions
Resolve the scr_resume_fiber conflict with stackless async coroutines
(#1204): the region the fiber carries is made current before either
resume path and restored after both. scr_async_spawn_stackless now
captures the spawning region like the fiber spawns, so stackless bodies
(every async body in 5013 and 5023 after the merge) keep allocating in
their region after an await and keep it alive while they exist.

5024-lifetime-region-await-forms pins regions carried by stackless and
fiber (for await) bodies that spawn and await each other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 06:23:44 -07:00
Chris Tate 31b52e2152 Remove the last element of a packed array inline on pop (#1282) 2026-10-18 08:21:05 -05:00
Chris Tate aba3c3a97b Read the class base after the callable-base case in super calls (#1281) 2026-10-18 08:14:23 -05:00
Chris Tate fc68a582d0 Remove the last element of a packed array inline on pop
pop and a discarded pop (the length - 1 store) branch on the array's
length and hole-free prefix bound. When the bound equals the length, the
last element is a present dense value: the emitted code clears its slot
and state, lowers the bound and the length, and moves the slot out (or
releases it for a discarded pop). An empty array answers undefined
without a call. Holes, present undefined and sparse storage keep the
runtime call.
2026-10-18 07:59:03 -05:00
Chris Tate 201857eb6c Read the class base after the callable-base case in super calls
superCallStmt asserted the class base before checking for a callable
base. A class that extends an ordinary function constructor has no base
class info, so the assertion held null. Node ignores the unused value;
the native compiler throws the TypeError of a non-null assertion that
held null, and failed to compile programs with such classes (for example
the Effect stream fixtures). The base is now read after that case
returns.
2026-10-18 07:54:21 -05:00
Chris Tate 1cd06fda4c Read events.defaultMaxListeners from the ES module namespace copy
Reads of defaultMaxListeners through an ES module namespace import or a
named import answer the namespace's copy of the export, which keeps its
value from program start until module.syncBuiltinESMExports() refreshes it,
as in Node. The default import, the EventEmitter class and CommonJS module
bindings keep reading the current default. A destructured CommonJS binding
of defaultMaxListeners, which held a placeholder string in JavaScript
files, is refused.
2026-10-18 07:44:23 -05:00