Commit Graph
2164 Commits
Author SHA1 Message Date
Chris Tate f288ddf707 Merge main 2026-10-20 08:43:43 -05:00
Chris Tate b8ee250c4a Narrow unions of records by intersection refinements, and keep members an assertion attaches (#1484) 2026-10-20 01:50:19 -05:00
Chris Tate 85d420540d Keep the stored type for annotated asserted bindings
A const annotated with the asserted type (const view: Route & { order: number } = route as Route & { order: number }) took the annotated layout, so the initializer was coerced into it: a width copy that read members the writes had not created yet, and writes through the binding no longer reached the original object. Local and module-level consts now keep the stored type when it hosts the annotation, as unannotated ones do.
2026-10-20 01:46:11 -05:00
Chris Tate 6054b5b5f6 Version typed-array loops on one bounds guard and tag typed-array memory for alias analysis (#1126) 2026-10-20 01:45:49 -05:00
Chris Tate 5a3bd5302b Merge main 2026-10-20 01:39:43 -05:00
Chris Tate 40f6dd25c8 Revert "Give interfaces extending class-backed protocols checked identity" 2026-10-20 01:35:23 -05:00
Chris Tate ca65f57626 Speed up native string character tests, optional-string switches, fromCharCode and long-string length (#1498) 2026-10-20 01:20:11 -05:00
Chris Tate ba8849a269 Speed up native string character tests, optional-string switches, fromCharCode and long-string length
`s.charAt(i) === "c"`, and `s[i] === "c"` where the index is proven in
bounds, built a one-character string to compare with the literal. When
the literal is one UTF-16 code unit, the comparison now reads
`s.charCodeAt(i)` and compares the number. charCodeAt converts the index
the same way charAt does, and an index out of range answers NaN, which
equals no unit, as "" equals no one-unit literal. A switch on
`s.charAt(i)` whose case tests are all one-unit literals switches on the
code unit the same way.

A switch on a `string | undefined` value with string literal cases, such
as an array element or an optional field, lowered to a chain of union
equalities. It now tests for undefined, which matches no case, and
dispatches the string through the string switch: by byte length, then by
the literal's leading bytes.

`String.fromCharCode(a, b, ...)` with up to sixteen arguments passes the
codes to the runtime in a stack array instead of a heap f64[], and up to
64 code units encode into a stack buffer instead of a heap scratch block.

`.length` of a string that is not ASCII counts its UTF-16 units. The
string index keeps four entries, so a program that read the lengths of
more long texts than that in turn (the text of every source file) walked
each whole string on every read. Lengths of non-ASCII strings longer than
64 bytes now go into a 1024-slot table keyed by address, allocated by
the first length it records. A slot is dropped when the string is freed
or reallocated and when an append changes it in place, which is where
the index drops its facts.
2026-10-20 01:11:36 -05:00
Chris Tate 9d80738297 Run LeakSanitizer over the sanitized corpus on Linux CI (#1492) 2026-10-20 00:47:53 -05:00
Chris Tate a495dc35a6 Run LeakSanitizer over the sanitized corpus on Linux CI
The Linux test jobs set ASAN_OPTIONS=detect_leaks=0 for both flavors and
left leak detection to the RC audit, which does not see runtime tables
allocated with calloc. A worker thread's record-source table leaked that
way, and only local runs reported it. The san shards' case-sharded corpus
task now sets detect_leaks=1 and an LSAN_OPTIONS suppressions file,
tests/harness/lsan.supp, which starts empty. Plain shards, the side queue,
the Effect jobs and macOS, whose ASan has no LeakSanitizer, keep leak
detection off.
2026-10-20 00:38:58 -05:00
Chris Tate ddc5d50204 Refuse JSON and dynamic copies of object literals with accessors (#1490) 2026-10-20 00:37:25 -05:00
Chris Tate 88f2061f62 Refuse JSON and dynamic copies of object literals with accessors
An object literal with get/set accessors keeps the accessors' closures in
reserved slots of its record. Since function values became convertible to
checked-dynamic values, these records passed the JSON and conversion
checks: JSON.stringify omitted the getter's property, as it omits
function-valued ones, and a copy held as `any` or `unknown` read the
getter's property as undefined and listed the slot's internal name among
its keys. Node invokes the getter in both cases.

Records with accessor slots are now outside the JSON-safe and
dyn-convertible domains, so JSON.stringify of such a value and its
conversion to `unknown` or `any` are refused at compile time. A literal
written directly into an `any` slot keeps its accessors and is unchanged.
2026-10-20 00:31:25 -05:00
Chris Tate 80c760f2b1 Format checked-dynamic arguments under %d and %i as Node does (#1488) 2026-10-20 00:25:46 -05:00
Malte UblandClaude Opus 5.5 85a2567ecd Treat optional members an index signature covers as keys, erase 'as never', and keep fromEntries objects asserted to records
A record type whose declared members are all optional and typed as its
index signature's slot (ProcessEnv's TZ?, '{ CI?: string } &
Dictionary<string>') interns as the pure index-signature record, its
slot admitting undefined: spread, delete and enumeration then follow one
insertion order. 'value as never' erases, so the consuming slot checks
an unknown value like any other. 'Object.fromEntries(...) as T' for a
declared record T goes through the checked-dynamic bridge, which keeps
the entries' keys and order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:21:46 -07:00
Chris Tate 12d0215c4d Link the child-process unit only in programs that spawn (#1403) 2026-10-20 00:18:21 -05:00
Chris Tate bae0570c2c Format checked-dynamic arguments under %d and %i as Node does
util.format's %d validated a checked-dynamic argument as a number and
threw "expected number" for anything else, where Node prints
Number(arg), and %i of such an argument had no lowering. Both now follow
Node: a bigint prints as `${n}n`, a symbol as NaN, and any other value
as Number(arg) under %d or parseInt(String(arg)) under %i.
2026-10-20 00:17:34 -05:00
Chris Tate 8df5e8d48a Accept readline's dispose, instanceof, dynamic streams and non-string prompts (#1401) 2026-10-20 00:13:26 -05:00
Chris Tate 89625ef91c Borrow typed-array locals read from fields and parameters (#1481) 2026-10-20 00:12:50 -05:00
Chris Tate ac0ae29b18 Construct CommonJS constructor functions imported through a member alias (#1453) 2026-10-20 00:10:40 -05:00
Chris Tate 3944a70017 Compile module exports that pass this on with apply or call (#1444) 2026-10-20 00:09:54 -05:00
Chris Tate 98a7d3b33d Merge main 2026-10-20 00:08:29 -05:00
Chris Tate 5cf51c46b0 Read x.constructor on EventEmitter and stream instances (#1442) 2026-10-20 00:06:32 -05:00
Chris Tate 226ba9672f Borrow typed-array locals read from fields and parameters
A local initialized from a typed-array field or parameter, such as
`const chars = this.chars`, took a reference and released it at the end
of its block, like any owned binding. Class instances, records, arrays
and strings already borrow such a projection when the rest of the block
writes none of the locations it reads, and walk and rebound parameters
use the same test. Typed arrays are one plain pointer as well, so they
now take the same paths: the field or the caller's binding keeps the
array alive while the local reads it, and a block that may replace the
field still retains its own reference.
2026-10-20 00:05:09 -05:00
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
Chris Tate 679227aeb1 Read an Error subclass's own code through NodeJS.ErrnoException (#1471) 2026-10-19 23:59:55 -05:00
Chris Tate 63cb11ff83 Keep DataView's own behavior through unknown (#1470) 2026-10-19 23:59:13 -05:00
Chris Tate b690f8f8f4 Count destructuring and for-of writes of a factory's parameter
A factory's parameter counts as the argument it was called with only
while the factory can't change it. The check saw assignments and updates
of the name, but not a write through a destructuring pattern
(`[body] = ...`, `({ body } = ...)`) or a for-in or for-of target, so a
factory could replace its callback with one that reads `this` and the
call still compiled without the module object.

The parameter now counts as changed by any assignment, update,
destructuring or for-in/for-of target that writes it, by a declaration
of the same name, by a write to an element of `arguments`, and by a call
of `eval`.
2026-10-19 23:56:22 -05:00
Chris Tate e134489edc Unroll the MD5 block function (#1465) 2026-10-19 23:51:35 -05:00
Chris Tate 21e5e2cd69 Answer in on typed arrays held as any (#1473) 2026-10-19 23:47:59 -05:00
Chris Tate 9fc483b5c6 Box asserted class bindings into class-backed views (#1469) 2026-10-19 23:42:07 -05:00
Chris Tate cdd78b4502 Answer in on typed arrays held as any
`in` on a typed array, Buffer or DataView held as `any` or `unknown`
answered false for every key: indices, `length`, prototype methods and
Object.prototype names.

On a typed array, a canonical numeric key (one that ToString(ToNumber(key))
reproduces, or "-0") now answers by bounds: an integral index, not -0,
below the length, so "1.5", "-0", "NaN" and "Infinity" answer false. Other
keys answer true for members of %TypedArray%.prototype, Buffer.prototype on
a Buffer, the constructors' BYTES_PER_ELEMENT and Object.prototype. A
DataView has no indexed elements and answers DataView.prototype's members
and Object.prototype's.
2026-10-19 23:36:14 -05:00
Malte UblandClaude Opus 5.5 1a65f234c9 Narrow unions of records by intersection refinements, and keep members an assertion attaches
'k' in u answers per value on arms that declare k as an optional slot.
A predicate or 'in' narrowing that intersects record arms views the
value through the stored arms that can hold it. An assertion
'r as R & { m: T }' gives R's arms m as an optional member, keeps the
object, and a binding initialized by it keeps the stored type, so
writes through it reach the original. Late writes through a union
receiver count as writes through the binding it was read from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:32:43 -07:00
Chris Tate 2259da3cf7 Give interfaces extending class-backed protocols checked identity (#1461) 2026-10-19 23:29:44 -05:00
Chris Tate 0b98a8c713 Read an Error subclass's own code through NodeJS.ErrnoException
`(err as NodeJS.ErrnoException).code` read the runtime error's errno
slot, which only runtime errors set, so an Error subclass with its own
`code` field answered undefined where Node reads the own property. When
a class in the program under the receiver's class has its own `code`
field or a dynamic property bag, the read goes through the error's
dynamic view, which answers the own property and the errno code alike.
2026-10-19 23:28:36 -05:00
Chris Tate bc9ef21b90 Keep DataView's own behavior through unknown
A DataView reached as a checked-dynamic value behaved like a Uint8Array
over its bytes: it printed as `Uint8Array(2) [ 0, 0 ]`, its string and
tag were the typed array's, `.length` and index reads answered from the
bytes, and typed array methods ran on it. It now inspects as Node's
`DataView { [byteLength], [byteOffset], [buffer] }`, answers
"[object DataView]", has no length or indexed elements, and throws
"is not a function" for typed array methods.
2026-10-19 23:28:21 -05:00
Chris Tate c0048af88b Box asserted class bindings into class-backed views
`const node = stack.pop()!` keeps the value's undefined at run time, so
the binding holds `Base | undefined`. Asserting it to a class-backed
protocol view (`node as unknown as View`) reported SC1090, while the same
cast of a plain instance boxed the instance. A union of class instances
and undefined or null now boxes the same way: class arms give the live
instance, and undefined stays undefined until a property read throws
Node's TypeError.
2026-10-19 23:26:01 -05:00
Chris Tate 95bdd7305c Unroll the MD5 block function
scr_md5_block ran its 64 steps as one loop that chose the round
function, message index and rotation per step, and the update path
called it once per block. scr_md5_blocks unrolls the steps with the
message word, constant and rotation inlined, processes every whole block
of an update in one call, and keeps each step's critical path to the
round function of b, an add, the rotate and the add of b: a + m + K is
summed first, G is written as (b & d) + (c & ~d) so that its c & ~d half
joins that sum, and an empty asm statement keeps the compiler from moving
the constant's add after the round function. MD5, HMAC-MD5 and md5-sha1
use it.

Corpus 5775 compares MD5, md5-sha1 and HMAC-MD5 digests over every
length from 0 to 520 bytes in five update sizes and over 1 MiB with
Node; tests/harness/corpus-costs/ records its plain and sanitized costs.
2026-10-19 23:22:51 -05:00
Chris Tate 783870dec3 Compile TLSSocket.isSessionReused, keyed short-circuit assignment and Object.assign into class instances (#1460) 2026-10-19 23:10:10 -05:00
Chris Tate 751d22ae6e Pass this on to module exports only where every function that gets it ignores it
The exemption for `f.apply(this, ...)` and `f.call(this, ...)` dropped
SC1090 for every module export that passes `this` on. When the function
it reaches reads `this`, Node gives it the module object, and the direct
module call has none, so the result differed from Node.

A `this` that is passed on now counts as unread only when the function
it goes to provably ignores its `this`: an arrow, a function that uses
`this` only to pass it on to such functions, or what a known function
returns, with its parameters bound to the call's arguments and never
assigned. The analysis follows at most six calls and functions, and
refuses otherwise. Effect 4's provideService still compiles: it passes
`this` to what dual(3, body) returns, which passes it on only to the
arrow it was given.

Passing `this` on to a function that reads it now refuses, directly,
through such a factory, or when the factory reassigns its parameter.
2026-10-19 23:09:08 -05:00
Chris Tate ae83a53dad Give interfaces extending class-backed protocols checked identity
An interface that extends a class-backed protocol, such as
`interface TupleTypeReference extends TypeReference { getTarget():
TupleType }` over a `Type` that class instances flow into, mapped as a
record when no class declared itself assignable to it. Asserting a base
value to it copied the instance into that record, which has no methods:
the next method call threw "expected function at $.getTarget, got
undefined", or the assertion reported a conversion diagnostic.

Such an interface holds values of its base protocol, so it now keeps the
same checked identity: the assertion keeps the instance, and its methods
and fields read as on the base type.
2026-10-19 23:06:52 -05:00
Chris Tate 6c742a7264 Compile class hierarchies across the CommonJS modules of npm packages (#1412) 2026-10-19 22:53:32 -05:00
Chris Tate 13cdc77ad0 Match class capsules in union fields of checked unions (#1456) 2026-10-19 22:48:20 -05:00
Chris Tate a1b077e16f Check private fields of function-local classes per class evaluation
Each evaluation of a function-local class creates its own private names,
while one compiled class serves every evaluation. A JavaScript class's
prototype methods are closures per evaluation whose `this` is the
call-site value, so a method taken from one evaluation could be called
with another evaluation's instance (`a.next.call(b)`, a prototype read,
Reflect.get with a receiver). Those calls read and wrote the other
instance's fields, and `#x in this` answered true. Reads and writes of
`this.#x` inside the closures also went through the property bag under
the key "#x" instead of the native field.

Private fields on a checked `this` now go through a brand check: the
receiver must be an instance of the class or a subclass whose class
environment is the closure's own evaluation, which the class's own
binding names. A matching receiver reads and writes the native field;
any other receiver throws Node's TypeError for the read or the write
(after the assigned value evaluates), and `#x in this` answers false.
Method values and accessors taken from typed instances of these classes
resolve to the instance's own evaluation's prototype functions instead of
a native thunk shared by every evaluation.

The classes this applies to are narrowed to the shapes the check covers:
JavaScript class declarations whose private members are instance fields
used only on `this`. Private methods and accessors, class expressions,
and TypeScript classes keep the "function-local classes with private
brands" refusal.
2026-10-19 22:45:03 -05:00
Chris Tate 064b072f94 Match class capsules in union fields of checked unions
A checked conversion to a union of object types tests each member's
fields with the members' matchers. For a field of type `C | undefined`
holding a class instance, the union's matcher found the instance's
capsule, which is not a capsule of the union itself, and answered no
(or matched the instance's plain-object view, which no class arm
accepts), so no member matched and the conversion threw a TypeError.
workerData read as such a union failed this way in every worker. A union
now matches capsules of other types through its arms, where the class
arm accepts the instance.
2026-10-19 22:44:28 -05:00
Chris Tate 27017ef0af Map classes reached only through import type as their structural types
A class whose module the program reaches only through `import type`
never registers, and its instance type stayed unmapped: a field such as
`items: Emitted | undefined` reported SC2009, and the class holding it
SC1090. The program holds no instance of such a class, so its instance
type now maps as the structural type it describes, like an interface:
fields of the type hold undefined, object literals and arrays of them.
A JavaScript class outside the program keeps the unmapped type, so its
JSDoc-annotated slots still take the dynamic value.
2026-10-19 22:37:25 -05:00
Chris Tate 60f5c852e0 Print holes in arrays reached through unknown as Node does
A holey array converted to a checked-dynamic value keeps its holes (the
conversion records them in the array's presence map), but the runtime
inspector printed every element: `const u: unknown = a` for
`a[2] = 1` printed `[ undefined, undefined, 1 ]`, where Node prints
`[ <2 empty items>, 1 ]`, and `new Array(3)` through `unknown` printed
`[ undefined, undefined, undefined ]`.

The inspector now prints each run of holes as one `<n empty items>`
entry that counts toward the 100-entry limit, as Node's
formatSpecialArray does and as the typed array inspector already does.
2026-10-19 22:31:09 -05:00
Malte UblandClaude Opus 5.5 762718a254 Compile TLSSocket.isSessionReused, keyed short-circuit assignment and Object.assign into class instances
- TLSSocket.isSessionReused() is declared in the fallback types and lowers
  to false: the TLS client never offers a cached session (no session
  resumption is a documented divergence), so no handshake resumes one.
- `o[k] ??= v`, `||=` and `&&=` through a pure index-signature record with
  a string key use the keyed read and write `o[k]` and `o[k] = v` lower to.
- Object.assign(instance, record) writes the record's fields into the
  class instance's declared fields (same or width-compatible types;
  optional source fields only when present) and answers the instance.
- Object.assign({}, instance) whose result the checker types as the class
  is refused at compile time: the copy is a plain object, so the checked
  cast into the class threw at run time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:26:47 -07:00
Chris Tate 62fb17bf8c Keep thrown Maps, Sets, typed arrays and functions as the thrown objects (#1448) 2026-10-19 22:17:41 -05:00
Chris Tate d23942ecfa Keep thrown Maps, Sets, typed arrays and functions as the thrown objects
A thrown Map, Set, typed array, Buffer, function or RegExp reached its
catch binding as a type-erased reference, so inspection printed `{}`,
String() gave "[object Object]", instanceof was false and typeof of a
thrown function was "object". These values now take the checked-dynamic
conversion that thrown records and arrays use, so the catch binding is
the thrown object, as in Node.
2026-10-19 22:11:54 -05:00
Chris Tate 2c6744e97d Merge main 2026-10-19 22:02:31 -05:00