Commit Graph
2098 Commits
Author SHA1 Message Date
Chris Tate 59dd6d1742 Read member aliases only while the binding holds the exported value
A member export resolves to its binding only when the binding is declared
before the write and never reassigned, so a consumer reads the value the
member stored. A module that re-exports another module's exports through
module.exports = require("./x") now counts its own writes after the
re-exported module's: a member it adds resolves, and a member both modules
write stays refused. The exports rebinding refusal and its test now cover
the form that stays refused, exports = value without module.exports.
2026-10-20 00:02:49 -05:00
Malte UblandClaude Opus 5.5 f176de2124 Construct CommonJS constructor functions imported through a member alias
A package that exports a constructor function whole and also as a named member (`exports = module.exports = Client; exports.StatsD = Client`, the statsd-client shape) could not be constructed from TypeScript: `new StatsD(...)` was refused as "constructing values other than classes declared in the program", and a bare reference had "no lowering".

- `exports = module.exports = value` is now the module's whole export, and it rebinds `exports` too, so members attached through `exports` afterwards are exported. The fresh-table reset `exports = module.exports = {}` keeps its own handling.
- An `import { name }` or a top-level `const { name } = require(...)` of a member written once as `exports.<name> = <binding or primitive literal>` on such a module reads that value, following `module.exports = require("./x")` re-exports. The checker gives these bindings a synthesized property symbol, so the module's statements answer.
- Construction then takes the existing checked path for JavaScript functions: prototype methods, `util.inherits` between two constructor functions, and `Parent.call(this, ...)` behave as in Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:58:56 -07:00
Chris Tate 4bbf31b61f Check the bench workloads' dynamic-tier symbols in CI (#1436) 2026-10-19 21:32:51 -05: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
Chris Tate 8d4737dfdb Merge main 2026-10-19 20:27:03 -05:00
Chris Tate bb6270efc1 Fix silent divergences: constructor returns, arr && arr[0], ES5 class names, builtin function values, uninitialized let writes, module-export this, 1 ** Infinity (#1351) 2026-10-19 19:56:36 -05:00
Chris Tate 950ec9ee85 Give prototype name stamps their own copy of the name
A construction through a plain function stamps the prototype with the
name of the function in its `constructor` property, for util.inspect.
The stamp pointed at the function's name. A bound function's name
("bound " and the target's name) is a string the bound function owns,
so after the `constructor` property was replaced and the bound function
released, inspecting an instance read freed memory. The stamp now holds
a copy of the name, freed with the stamp at exit.

The corpus program constructs through functions whose prototype's
`constructor` is a bound function (and a bound function of one), then
replaces the property and inspects the instances.
2026-10-19 19:51:44 -05:00
Chris Tate 4770b3ffa7 Fix pi's small compile gaps (#1348) 2026-10-19 19:50:07 -05:00
Chris Tate adb879f678 Compile the real jose root entry: string durations, chained builders and navigator (#1407) 2026-10-19 19:45:13 -05:00
Chris Tate 2edc7a2c2a Read declarations from symbols and signatures (#1399) 2026-10-19 19:33:42 -05:00
Chris Tate 85d1c423d3 Read generic class narrowings through every class they can hold (#1419) 2026-10-19 19:32:55 -05:00
Chris Tate 31475715a0 Compare strings in UTF-16 code-unit order (#1424) 2026-10-19 19:04:23 -05:00
Chris Tate 68f930bf53 Keep the global object in type assertions of globalThis (#1423) 2026-10-19 18:42:22 -05: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 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 ca01eabd12 Read generic class narrowings through every class they can hold
In a generic function, `node instanceof Identifier || node instanceof
PrivateIdentifier` narrows `node` to `T & PrivateIdentifier` when
Identifier has every member PrivateIdentifier has: TypeScript reduces the
union of the two intersections by structural subtyping. The read was
lowered as a downcast to PrivateIdentifier, and the narrowing guard
aborted when it held an Identifier. tsc-ts's NodeFactory.cloneNode has
this pattern, so declaration emit aborted.

When the checker types a read of a type-parameter intersection as a
subclass, and another class under the value's class is assignable to that
subclass without extending it, the read is now a class union over those
classes, and member reads and method calls on it dispatch over the union.
When this instantiation's class is unrelated to the narrowed class but
assignable to it (`T = Identifier`), member calls use the instantiation's
class. A union method helper whose body lowering is refused is no longer
left registered without a body.
2026-10-19 17:25:49 -05: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 79ecacfb5d Merge main 2026-10-19 17:08:37 -05:00
Chris Tate c82b6d8188 Merge main 2026-10-19 17:06:58 -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 c7329845b1 Merge main 2026-10-19 16:48:01 -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 fa8001358c Merge main 2026-10-19 16:44:05 -05:00
Malte UblandClaude Opus 5.5 cf198952e2 Compile the real jose root entry: string durations, chained builders, navigator
jose 6.2.12 now compiles natively through its root entry (`import { jwtVerify,
errors } from "jose"`), from TypeScript and JavaScript, with chained SignJWT
builders and string durations:

- A discarded `a && b;` / `a || b;` statement whose value form fails on its own
  result type (jose's secs(): `(!m || m[4] && m[1]) && invalid()`) lowers as
  `if (a) b;` / `if (!a) b;`. The value form still lowers first, so statements
  that compiled before are unchanged.
- In JavaScript, a number-keyed read on a `T[] | null | undefined` value outside
  an optional chain (`m[2]` after a statement ruled `m` non-null) reads the
  array arm like any element read: a group that did not participate is
  undefined instead of a trap.
- A JavaScript fluent method whose returns all end in `this` (including comma
  sequences) narrows a used call result back to the receiver's subclass. A
  receiver lowered at an ancestor of the class a call needs is bridged with a
  verified downcast instead of an upcast the validator rejected (the
  "upcast JWTClaimsBuilder does not extend SignJWT" internal error).
- `typeof navigator` is "object" and `navigator.userAgent` is "Node.js/24", as
  in Node 24, for the free name in JavaScript and under @types/node. Both fold
  to literals; other members refuse by name.
- TypeScript object destructuring of an `any` result of a JavaScript function
  (jose's jwtVerify) reads its members like the member reads that already
  compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:30:48 -07:00
Malte UblandClaude Opus 5.5 f2b8162a6d Keep the coverage fixture's blocker refused now that array position patterns compile
tests/coverage-fixtures/mixed.ts used `const { length: firstItem } = items`
as its SC1031 example. Object patterns of positions and length over arrays
compile now, so coverage counted 13/13 static. The example now destructures
a union source (array | string), which keeps the SC1031 object-pattern fence
at the same line and column; only the message's source kind changes, from
array-typed to union-typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 10:19:59 -07:00
Malte Ubl 643c0f5daa Merge commit '26946d6b2' into opt/webcrypto-hmac 2026-10-19 08:21:02 -07:00
Malte Ubl 675e70cc24 Merge commit '26946d6b2' into opt/pi-small-fixes 2026-10-19 08:21:01 -07:00
Malte Ubl 13efc5ffcd Merge commit '26946d6b2' into opt/tier-s3-jsval 2026-10-19 08:20:59 -07: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 1f07166fbf Merge main 2026-10-19 08:56:48 -05:00
Malte UblandClaude Opus 5.5 7ea35785ee Check the bench workloads' dynamic-tier symbols in CI
The zero-cost gate's I3 check (a fully typed bench workload defines no
dynamic-tier runtime symbol) runs only when someone runs the gate, and on
Darwin it never matched: Mach-O names carry a leading underscore, so no
symbol had a DYNAMIC_TIER_PREFIXES prefix.

DYNAMIC_WORKLOADS and the nm function-name reader move to
zero-cost-compare.mjs, which now drops the Mach-O underscore. A harness
test builds every bench program on Linux and fails when a workload outside
DYNAMIC_WORKLOADS links a dynamic-tier symbol, or when a listed workload
no longer links any, so the list shrinks as leaks close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 06:44:29 -07:00
Chris Tate 178646baf0 Merge main 2026-10-19 08:31:43 -05:00
Chris Tate 3d4f181cb7 Merge main 2026-10-19 08:15:47 -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 c80f9e62a1 Record new corpus program costs in per-program files (#1361) 2026-10-19 07:48:44 -05:00
Malte Ubl a10ce583d7 Merge remote-tracking branch 'origin/main' into opt/tier-s3-jsval 2026-10-19 05:43:49 -07:00
Chris Tate 9b79eb8d32 Read declarations from symbols and signatures
symbol.declarations, symbol.valueDeclaration and signature.declaration
return nodes, as tsc-ts's Symbol and Signature do, instead of the
facade's declarationsOf, valueDeclarationOf and signatureDeclaration
resolving their handles. Each symbol and signature resolves its handles
in its own project on first read and keeps the nodes. The handles stay
available as declarationHandles, valueDeclarationHandle and
declarationHandle.

A program's checker and its symbols belong to the same project, so the
nodes are the ones the facade returned. The frontend's 432 calls of the
three facade methods become property reads, and the methods and their
caches go.
2026-10-19 07:41:28 -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 UblandClaude Opus 5.5 2b8cd22423 Add WebCrypto HMAC: crypto.subtle importKey, sign, verify and digest
crypto.subtle.importKey('raw', ...) for HMAC keys, HMAC sign and verify,
and the SHA-1/SHA-2 digests compile through the crypto global, with
CryptoKey values as native handles. scr_webcrypto.c is only the WebCrypto
layer (Web IDL conversion, algorithm normalization, the CryptoKey handle,
promise wrapping and Node's TypeErrors and DOMExceptions) over node:crypto's
digest core and deferred promise queue. It links only into programs that
call these methods (the new webcrypto link feature). Other algorithms, the
jwk format and the other SubtleCrypto methods are SC2020 refusals.

The real jose@6.2.12 HS256 path (SignJWT, jwtVerify) now runs natively. It
also needed four compiler fixes: object patterns of positions and length
over arrays, the Uint8Array base64/hex feature probes, a compile-time
String.fromCharCode.apply, and structuredClone of a parameter whose default
is {}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:34:26 -07:00
Malte Ubl 2fc52c47c9 Merge remote-tracking branch 'origin/opt/silent-bugs' into opt/silent-bugs 2026-10-19 05:34:13 -07:00
Malte UblandClaude Opus 5.5 9290f32e0d Treat constructor returns of chainable this-calls as returning this
three.js's Color constructor ends with `return this.set(r, g, b)`, and
every reachable `set` (Color's, and Color4's override returning
`super.set(...)`) returns `this`. The return analysis classified it as
returning another instance and refused `class Color4 extends Color`.
A return that is `this`, or a `this.m(...)`/`super.m(...)` call whose
every reachable implementation returns only `this`, a primitive or
nothing, now keeps the ordinary lowering, so such classes need no
result slot and subclasses construct through super() as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:34:05 -07:00
Malte Ubl 800b298b6a Merge remote-tracking branch 'origin/opt/tier-s3-jsval' into opt/tier-s3-jsval 2026-10-19 05:33:53 -07:00