Commit Graph
2870 Commits
Author SHA1 Message Date
Chris Tate 68f930bf53 Keep the global object in type assertions of globalThis (#1423) 2026-10-19 18:42:22 -05:00
Chris Tate 899375b9fe Accept optional checked-dynamic parameters in generic method instances (#1421) 2026-10-19 18:05:41 -05:00
Malte UblandClaude Opus 5.5 6255c9351b Keep the global object in type assertions of globalThis
`globalThis as { [KEY]?: State }` is the usual way for several modules
to share process-wide state. globalThis lowers to a checked-dynamic
value, and asserting a checked-dynamic value to a record or array type
took the checked conversion, which builds a new native value. A write
through the asserted value went to that copy, so other modules never
saw it; string-keyed and index-signature views threw "expected object".

- An assertion of the global object (globalThis, global, also through
  `as unknown as`) keeps the checked-dynamic value when the conversion
  would copy, so member reads and writes reach the global object.
- File-scope and local bindings of such an assertion hold the global
  object (collectGlobals, lowerVarDecl), so an exported
  `const state = globalThis as ...` works across modules.
- The divergence scan no longer reports these assertions as SC6001
  width copies.
- Runtime: one global-object box per thread (scr_global_object), which
  globalThis, global and globalThis.globalThis all answer, and direct
  `globalThis[symbol]` accesses use that box's symbol-keyed properties,
  so every spelling of the global object sees one property.

Other checked-dynamic values keep the checked conversion: a native
value held as unknown unwraps by identity there, and a checked-dynamic
tree is copied at the documented boundary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:52:52 -07:00
Chris Tate c04f770104 Flatten the last value a take delivers in full (#1390) 2026-10-19 17:20:54 -05:00
Chris Tate 0df30d4a56 Choose record key order from the orders values are built in (#1398) 2026-10-19 17:15:29 -05:00
Chris Tate e29807f17b Route records into discriminated unions by their discriminant (#1414) 2026-10-19 17:12:52 -05:00
Malte UblandClaude Opus 5.5 76e5a1c48f Accept optional checked-dynamic parameters in generic method instances
paramShapes, which builds the parameters of an instantiated signature
(generic call instances and the erased instance that a method value or
a reflected prototype slot uses), refused an optional parameter unless
its type had an undefined union arm. An interface view implemented by a
class (`handle?: Handle | null`) and an erased type parameter map to the
checked-dynamic type, which holds undefined directly; paramShape, the
declared-signature path, already accepts it.

Direct calls of such a method failed with "optional parameters of type
'unknown'", and reflecting its class prototype from the dynamic-dispatch
fixpoint raised the same refusal outside any statement and crashed the
compiler with an uncaught PoisonError. paramShapes now treats the
parameter as omittable, so the instance lowers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:54:50 -07:00
Chris Tate 4986a928c7 Merge main 2026-10-19 16:51:05 -05:00
Chris Tate cf87b359e7 Merge main 2026-10-19 16:50:17 -05:00
Chris Tate bdefcb0141 Route records into discriminated unions by their discriminant
The native CLI built from main aborted on the record-boundaries and
value-objects benchmarks with "a narrowed value does not have the type its
narrowing proved". record-results.ts builds recordSet statements in a map
callback whose value field is a recordGet literal. Converting that array
into IrStmt[] chose the break/continue arm: the literal also fits the
shorter varRef-like expression arms, so its conversion into IrExpr was
ambiguous, and an ambiguous field conversion made widthLiftPlan drop every
arm that needed it. The one arm left was break/continue, reached by
dropping obj, shapeId, field and value. The record kept kind "recordSet"
under the break/continue tag, so mapStmtChildren's narrowing guard aborted.

IR record shapes keep the discriminant as a plain string, so a record's
layout alone cannot pick its arm. When a record that carries a
discriminated union's discriminant field fits several arms, widthLiftPlan
now plans a route through every arm it converts into, selected at run time
by the discriminant's value, as JavaScript does (a value no arm names
throws the catchable TypeError). Conversions that resolved before are
unchanged; the zero-cost gate shows identical IR, LLVM and executables for
the benchmarks and the corpus, and the native CLI now compiles every
benchmark workload.
2026-10-19 16:47:28 -05:00
Chris Tate 706f473559 Keep existing objects in JavaScript var and destructured bindings (#1406) 2026-10-19 16:45:13 -05:00
Chris Tate acf5bafe9c Merge main 2026-10-19 16:36:46 -05:00
Chris Tate cdde537adf Run String/Number methods on dynamic values; add crypto.getRandomValues, fs.constants, Buffer brand tests (#1345) 2026-10-19 16:36:21 -05:00
Malte UblandClaude Opus 5.5 2f4edcc358 Keep existing objects in JavaScript var and destructured bindings
A JavaScript `var` binding hoists to its function, and its slot was
created with the checker's type before the initializer lowered. When the
initializer was an existing checked-dynamic object (an element of an
array built with push, a find result, the object a function returns),
the record-typed slot held a converted copy: writes through the binding
missed the object that the array or the function's caller still held,
and `===` against it was false. `let` and `const` already lowered the
initializer first and kept such a value checked-dynamic.

A `var` initialized with an existing value now lowers the initializer
before creating the slot, and a checked-dynamic value gets a
checked-dynamic slot. The same applies to `for (var x of ...)`, to array
destructuring with `var`, `let` and `const`, and to file-scope bindings,
whose module-entry `undefined` store follows the slot's final type. A
literal holding such an object builds checked-dynamic for a `var` as it
already did for `let`. TypeScript files and annotated bindings are
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:59:01 -07:00
Chris Tate 9ac4551de5 Expect SC1101 from fs-shims and record the branch's corpus costs
fs-shims now gets past Buffer.isBuffer on a Uint8Array (fszoo/index.js:35),
which this branch lowers; its first deferred refusal is the existing
SC1101 for a closure passed into an unknown slot (fszoo/index.js:44), so
the expectation records SC1101.

The branch's new corpus programs (1112 and 6200 to 6210) get per-program
cost files in tests/harness/corpus-costs, taken from the differential and
llvm-differential case durations in this branch's Linux CI shards.
2026-10-19 09:13:55 -05:00
Chris Tate 26946d6b22 Revert "Copy repeated childless IR nodes with a spread" (#1404) 2026-10-19 08:57:17 -05:00
Chris Tate 1f07166fbf Merge main 2026-10-19 08:56:48 -05:00
Chris Tate 0abf9a6395 Revert "Copy repeated childless IR nodes with a spread"
This reverts commit c836f93998. The
bootstrap seed build cannot compile the spread copies (SC1090).
2026-10-19 08:34:08 -05:00
Chris Tate 7f907d23e3 Read getTestContext's arguments through their nodes (#1402) 2026-10-19 08:18:09 -05:00
Chris Tate 720b6f6117 Merge main 2026-10-19 08:11:47 -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 c6e16d365b Read getTestContext's arguments through their nodes
lowerGetTestContext (#1393) read expr.arguments as an array. Since #1394
an AST list's elements are in nodes, so the compiler did not type-check.
2026-10-19 07:56:11 -05:00
Chris Tate 9e628720ae Test the common IR node kinds first in child traversals (#1391) 2026-10-19 07:50:45 -05:00
Chris Tate c80f9e62a1 Record new corpus program costs in per-program files (#1361) 2026-10-19 07:48:44 -05:00
Chris Tate 56850331b6 Copy repeated childless IR nodes with a spread (#1392) 2026-10-19 07:43:33 -05:00
Chris Tate 8e0af5fc59 Add getTestContext to node:test (#1393) 2026-10-19 07:37:01 -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 Ubl 0b00819e40 Merge remote-tracking branch 'origin/opt/builtins-npm2' into opt/builtins-npm2 2026-10-19 05:33:31 -07:00
Malte UblandClaude Opus 5.5 e819e2af3d Record Number.prototype.toPrecision as static in the surface manifest
This branch lowers toPrecision statically, but STATIC_NUMBER_METHODS still
lacked it, so the regenerated manifest kept stdlib.number.toPrecision as
unsupported/SC2012 and a determinism fence on it had no reach detector.
List it with its num.toPrecision witness and 0 to 1 arguments, like
toFixed, and regenerate the manifest; the compatibility outputs are
unchanged.

library-profile's remediation test used that id as its SC2012 fence;
it now names the SC2012 code family (diagnostic.sc2012), so the explicit
remediations key still competes with fence-supplied SC2012 text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:33:16 -07:00
Malte UblandClaude Opus 5.5 f74c2eaf03 Use a Brotli refusal for the K14 manifest-id teaching and fence tests
toPrecision lowers statically on this branch, so the two K14 tests that
needed a surface whose own refusal fires compiled instead. Use
zlib.brotliCompressSync: it has its own manifest id, the fallback types
declare it, and it refuses with SC2020 because the static runtime vendors
no Brotli codec. The id-keyed teaching and the fence teaching ride that
refusal as the note, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:33:15 -07:00
Chris Tate 098425ec78 Add an experimental young-in nursery for compiled classes and records (#1319) 2026-10-19 07:32:01 -05:00
Chris Tate 05f6c9c6ad Read AST lists through their nodes (#1394) 2026-10-19 07:30:01 -05:00
Chris Tate 3c749a2895 Read AST lists through their nodes
The frontend's node lists become node lists, { pos, end, nodes }, the
shape of tsc-ts's NodeList, so that the frontend can later read tsc-ts's
own nodes. AstNode's list getters return an AstNodeList, ts.NodeArray<T>
is that class, and the frontend reads a list's elements through nodes.
The lists already carried their positions on the wire.

The class is the type, not a structural interface, because native code
represents a class and a record differently.

A decoded file keeps each list's nodes array, as before, and each read
of a list field returns a new AstNodeList over it, so the file holds no
more objects than before. nodes keeps its identity; a list object is not
the same object on each read.

The change is mechanical: a type-aware rewrite of each list field read,
plus hand edits where lists are read through casts, generic accessors
or test mocks. The generated getters and types follow from
generate-ts7-ast-schema.mjs. AstFile and Ts7Session drop their unused
listMetadata hook.
2026-10-19 06:33:33 -05: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 c836f93998 Copy repeated childless IR nodes with a spread
unshareNodes gives every occurrence of a repeated node its own object.
Nodes with children are copied by mapExprChildren and mapStmtChildren,
which spread the node and keep its type and location objects. Childless
nodes were copied by reparseExpr and reparseStmt, which stringify the
node and parse the text, copying the type and location objects as well;
on the three.js nodes fixture that took about 6 s.

Childless nodes are now spread too. Analyses key their facts by node
identity, which each copy still has of its own. The type and location
objects are shared as they already are in every copied node with
children and in every function without a repeated node. The emitted IR
is unchanged for every corpus program.

unshareNodes takes about 2 s on the three.js nodes fixture instead of
8 s, and compiling it to LLVM text takes 171 s instead of 178 s (median
of five interleaved runs).
2026-10-19 06:18:17 -05:00
Chris Tate 60084fcf48 Test the common IR node kinds first in child traversals
everyExprChild, everyStmtChild, mapExprChildren and mapStmtChildren
switch over the node kind: 96 expression and 26 statement string cases,
which the engine tests one after another in source order. Every analysis
and rewrite walks the IR through them, and some of the most common kinds
were near the end: in the three.js nodes module, dynFrom, libCall and
dynCheck make up 27% of expression nodes and were the 64th, 94th and
96th cases.

The cases for the most common kinds now come first, in the order of how
often they occur in the three.js nodes and Effect fixtures. All other
cases keep their order, and no case changes. In those fixtures, seven
expression kinds make up 89% of expression nodes and four statement
kinds make up 88% of statements.

A full child walk of the three.js nodes module takes about 108 ns per
node instead of 154 ns. Compiling it to LLVM text takes 161 s instead
of 180 s (median of five interleaved runs), and the Effect schema
fixture 64 s instead of 69 s. The emitted IR is unchanged.
2026-10-19 06:14:15 -05:00
Chris Tate 26676e9df2 Flatten the last value a take delivers in full
In a fused helper chain, take(n) sets the chain's done flag as it
delivers its nth value, so nothing is pulled after it. A flatMap later in
the chain tested the same flag in its inner walk and stopped after the
first flattened value: gen().take(2).flatMap((x) => [x, x + 10]) gave
[0, 10] where Node gives [0, 10, 1, 11].

A flatMap stage after a take now holds the flag while it walks its inner
iterable, and sets it again afterwards. A terminal that short-circuits or
a take later in the chain still stops the inner walk.
2026-10-19 06:09:53 -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 ac21611925 Use EventEmitter, the stream classes and the error classes as values (#1387) 2026-10-19 05:09:04 -05:00
Chris Tate 309fa897ec Use EventEmitter, the stream classes and the error classes as values
`function make(C: typeof EventEmitter) { return new C(); }`,
`const kinds: ErrorConstructor[] = [TypeError, RangeError]` and
`(typeof Readable)[]` stopped at SC2020 (the type has no mapping) or
SC1090 (builtin classes as values). These classes are now class values:
the name in a TypeScript file (or a const alias of it) is the class
object, `typeof EventEmitter`, `typeof Readable` and ErrorConstructor-
style types map to class values, and parameters, arrays, Map values and
object fields hold them.

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

EventEmitter and stream values constructed with options, runtime class
values stored as untyped values or printed, statics read through a value,
and DOMException, AggregateError, AssertionError and SuppressedError
values report SC1090. The existing static lowerings
(`EventEmitter.setMaxListeners`, `Error.stackTraceLimit`, the events
module) are unchanged. JavaScript files keep the identity token.
2026-10-19 04:48:10 -05:00
Chris Tate 4cb690db0c Name the destination of every ownership transfer in the LLVM emitter (#1384) 2026-10-19 04:32:54 -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 ef394d0de9 Name the destination of every ownership transfer in the LLVM emitter
moveTemp, retainValue and releaseValue are replaced by forms that state
where a reference goes or comes from: toHeap and toFrame, retainHeap and
retainFrame, releaseHeap and releaseFrame. A heap reference is one that
heap memory holds: a field, an array or map element, a capture box, a
union box, a module global, a promise or generator slot, the exception
cell or another runtime structure. A frame reference is one that a
local, a parameter, a return value, a stack object or a temporary holds.
Sites whose destination depends on the binding (a local or a module
global) or on the function (a coroutine's return fulfills its promise)
choose at emission time.

adoptHeap marks the runtime results whose +1 the runtime made from heap
memory and that become frame values: capture box reads, generator in and
out slots, and await results. The statement-frame release is renamed
releaseTemps.

Both forms of each operation emit what the old call emitted, so the
generated IR is unchanged.
2026-10-19 04:09:13 -05:00
Chris Tate ee5008c1e5 Inspect class instances as Node does (#1382) 2026-10-19 04:03:05 -05:00
Chris Tate 5e9815759c Commit default-region memory as ProgramLifetime instances use it (#1381) 2026-10-19 03:55:18 -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 b9a54a65df Destroy an idle pooled http socket that receives data (#1380) 2026-10-19 03:43:51 -05:00
Chris Tate d823931318 Give matchAll rows and exec results their index, input and groups (#1323) 2026-10-19 03:41:07 -05:00
Chris Tate 0523806107 Inspect class instances with added properties as Node does
console.log and util.inspect of a class instance list the properties
added to it after construction, after its declared fields. JavaScript
classes print their own properties in creation order: a field first
assigned in a method or under a condition appears only once it exists.
Such instances render through their checked-dynamic view when the class
has a property bag or is a JavaScript class; other classes keep the
generated inspect helper.

The runtime inspector detects cycles through the class instances it
reaches through views, so a cycle prints [Circular *N] instead of
expanding to the depth limit. A circular target's <ref *N> prefix counts
toward the 80-column line width, as in Node.

Capsules of a recursive class type carry the class's view operations,
so a nested instance prints with its class name.
2026-10-19 03:37:40 -05:00
Chris Tate d8d48e1637 Commit default-region memory as ProgramLifetime instances use it
A thread's default region asked for huge pages from its first chunk and
gave every class a 16 KiB segment, so in a program that only references
LifetimeRegion each thread committed whole 2 MiB pages and per-class
segment space for a few instances of each class. Default regions now use
small pages, and a class's segments in a default region start at 1 KiB
and double up to 16 KiB. Freeable regions keep their page and segment
policy.
2026-10-19 03:31:48 -05:00