Commit Graph
4679 Commits
Author SHA1 Message Date
Chris Tate 04435a70ac Address the second round of review comments on prototype toString conversions 2026-10-21 11:44:13 -05:00
Chris Tate 86aee1e81a Support TypedArray.of (#1658) 2026-10-21 11:42:08 -05:00
Chris Tate 5484c1254b Answer catch-binding instanceof for values thrown as unknown (#1674) 2026-10-21 11:27:19 -05:00
Chris Tate cdcf69c728 Give ProgramLifetime class instances a 32-bit header and packed fields (#1651) 2026-10-21 11:21:22 -05:00
Chris Tate 9a504b6f0d Share one listener list between typed and untyped uses of an EventEmitter (#1563) 2026-10-21 11:18:16 -05:00
Chris Tate b104643a95 Accept defaulted callbacks in literals under an erased generic member (#1666) 2026-10-21 11:15:39 -05:00
Chris Tate e34e5edfd5 Run Buffer methods on Buffers held as checked-dynamic values (#1659) 2026-10-21 11:15:06 -05:00
Chris Tate c8193b9486 Unwrap tsc's interop helpers around builtin modules in packages (#1683) 2026-10-21 11:14:39 -05:00
Chris Tate f2f0617c79 Support fs.promises.rm options (#1673) 2026-10-21 11:08:38 -05:00
Chris Tate cd38fd2248 Bridge events whose typed listeners take no parameters
An event that typed code only listened to with a listener taking no
parameters had no bridge entry: the scan marked a listener as typed only
when one of its parameters was not a checked-dynamic value. Emitting the
event through an `any` reference then threw instead of calling the
listener. A TypeScript listener with no parameters now marks the event as
typed. A JavaScript function without parameters still registers as before,
since it can read `arguments`.
2026-10-21 11:03:19 -05:00
Chris Tate aea358eed5 Freeze, seal and query functions through their property tables (#1657) 2026-10-21 11:01:40 -05:00
Chris Tate 4312b78ab5 Name the reason when a discovered WebAssembly module is not translated (#1660) 2026-10-21 10:59:44 -05:00
Malte UblandClaude Opus 5.5 9a0a3f1562 Load Node.js in-process on first use: libnode bridge and runtime loader
Scope-B milestone NI-1, runtime side. Programs whose IR has a
`nodeIsland.*` operation link scr_node_island.c and the SCR_NODE_ISLANDS
build of scr_async.c; every other program links neither, byte for byte.

- scr_node_island.c dlopens the bridge of a libnode pack on the first call
  (RTLD_NOW | RTLD_GLOBAL), checks its bridge ABI and that it links exactly
  the Node version recorded at build time, starts one Node environment and
  runs the embedded bootstrap (scr_node_island_host.cjs). A run that never
  calls into Node never maps libnode. The pack directory is SCRIPTC_LIBNODE
  at run time, else the recorded path; a `$ORIGIN/` path resolves against
  the executable's directory (the beside packaging, and the hook for
  shipping Node with the program).
- scr_node_bridge.h is the C ABI between the runtime and the bridge; the
  runtime includes no Node or V8 header. The bridge
  (packages/libnode-common/bridge/scr_node_bridge.cc) initializes Node once
  with no default signal handling, no debug-signal handler or inspector,
  no stdio reinitialization and one platform worker, creates the
  environment from the built-in snapshot, routes Node's process exit to
  the runtime's exit sequence, promotes libnode to the global scope for
  native addons, and runs calls in a CallbackScope with a TryCatch.
  build-pack.mjs assembles a pack (libnode, the bridge, manifest.json) from
  a Node --shared build and the matching headers.
- The SCR_NODE_ISLANDS build of scr_async.c adds a loop slot through which
  the runtime's loop drives Node's until NI-3 hosts the runtime's queues on
  Node's libuv: it keeps the program alive while Node has ref'd work,
  sleeps on Node's backend fd up to its next timer, and runs one
  non-blocking turn per loop turn.
- The `nodeIsland` runtime feature (rule, link features, unit table, pack
  matrix row and scr_async.c variant), the nodeIsland.call/callSync
  libCalls, the install call in main, and the build-time record of the
  pack (node-islands/libnode.ts: --libnode=<pack>|beside or
  SCRIPTC_LIBNODE, SC8005 when none is usable, Node >=24.13.1).
- The zero-cost gate and the bench nm test require that no good-code
  executable defines a Node island symbol.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:54:47 -07:00
Chris Tate 858328703b Name typed functions' property tables and keep runtime freezes data-only
The first Object.freeze, Object.seal or Object.preventExtensions of a typed
function creates its property table from the function's dynamic box. The
box had no name, so later `f.name` reads answered "". The box now carries
the function's JS name, as a closure flowing into any checked-dynamic slot
does.

The runtime's own callers (published values, os and node constants, the
private-range block list, Wasm exports) freeze data they built, and now
call scr_dyn_freeze_data, the freeze for values that are not functions.
Object.isFrozen, Object.isSealed and Object.isExtensible read a
function's existing table and answer false, false and true when it has
none. Fully typed programs therefore link no dynamic-tier object code
for these calls again.
2026-10-21 10:34:21 -05:00
Malte UblandClaude Opus 5.5 f793009ee5 Unwrap tsc's interop helpers around builtin modules in packages
tsc wraps every CommonJS import in its helpers: `const events_1 =
__importDefault(require("events"))`, `const path =
__importStar(require("path"))`. cjs-tsc-interop.ts reads such bindings of
program modules as plain require bindings, so the helper declarations go
dead, but a builtin module is not a program module: its binding kept the
helpers live, and their probe of the module's top-level `this`
(`(this && this.__importDefault) ||`) failed at load ("property access
through top-level 'this'"). That is the shape of hundreds of tsc-compiled
files in a typical dependency tree.

A builtin carries no `__esModule`, so `events_1.default` is the module and
the namespace copy reads the module's members. When every reference has
that form, a package-file stage now respells the binding as the plain
require() of the builtin and `x.default` as `x`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:11:47 -07:00
Chris Tate 96c82719b6 Deliver queued worker messages before immediates, as Node does
A thread's event loop delivered one message from its workers or its
parent per loop turn, so timers and setImmediate callbacks ran between
messages that were already queued. Node's MessagePort delivers, in one
callback, the messages queued when it starts and those that arrive
meanwhile until it has taken 1000, with a microtask checkpoint after
each, before the loop goes on to the check phase.

- scr_worker_dispatch starts a batch when it delivers a message: the
  messages queued then, and at least 1000 in all.
- scr_workers_more continues the loop pass while the next event in the
  thread's mailbox is a message and the batch has room; other events
  (online, exit, error, stdio) keep their order and their turns.
- scr_mailbox_head_kind and scr_mailbox_count_kind read the mailbox's
  first event kind and the number of queued events of a kind.
- Corpus 6211 checks the order of messages, nextTick, microtasks and
  setImmediate for worker-to-parent and parent-to-worker messages
  against Node.
2026-10-21 10:07:56 -05:00
Malte Ubl c568803e71 Merge remote-tracking branch 'origin/main' into opt/npm-unloaded-dynamic
# Conflicts:
#	packages/compiler/src/frontend/lowering/lowerer.ts
#	packages/compiler/src/frontend/resolve.ts
#	tests/harness/package-shims.test.ts
2026-10-21 07:58:01 -07:00
Chris Tate 94f040b3df Address review comments on Buffer methods for checked-dynamic values 2026-10-21 09:57:56 -05:00
Malte Ubl d3d39af3fe Merge remote-tracking branch 'origin/main' into opt/bc-satisfies-key-order
# Conflicts:
#	packages/compiler/src/ir/traverse.ts
2026-10-21 07:57:00 -07:00
Malte Ubl f798f6736c Merge remote-tracking branch 'origin/main' into opt/emitter-mixed-events 2026-10-21 07:53:10 -07:00
Malte UblandClaude Opus 5.5 3f3e90b846 Drop the EventEmitter method-values follow-up
Revert 3a27a92cb ("Forward at most four emit arguments through
EventEmitter method values") and 0cf62ebec ("Answer EventEmitter methods
read as values off compiled emitters"). Under the scope-B plan,
EventEmitter methods read as values, `EventEmitter.prototype.on` in
JavaScript and constructor-function emitters belong to Node islands, not
the native compiler. The typed/untyped listener sharing and the
catchable throw in place of the trap stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:53:10 -07:00
Chris Tate 77aab9c0fd Address review comments on prototype toString conversions 2026-10-21 09:41:55 -05:00
Chris Tate 5a56cc2564 Answer catch-binding instanceof for values thrown as unknown
A union, unknown or any value is converted to a dynamic value before it
is thrown, and promise rejections and other runtime throws carry dynamic
values too. A catch binding then holds a dynamic payload, which the
native class test does not recognize: `e instanceof C` was false for
every class of a hierarchy, and a read narrowed by it trapped. A test
through a parameter of type unknown was already correct.

When a class or one of its subclasses crosses into unknown, the class
dynamic-dispatch pass now routes the catch-binding tests of that class
(`e instanceof C`, the reads it narrows and `e as C`) through helpers
that keep the native test and answer a dynamic payload with the dynamic
class test, as standalone classes already do. The new "dynamic" caught
test selects that payload, so native payloads are not converted.
Programs whose classes never cross into unknown compile as before.
2026-10-21 09:40:20 -05:00
Chris Tate d78bd871f6 Clear recycled blocks for compact instances and keep TypeScript 7 parity 2026-10-21 09:16:06 -05:00
Chris Tateandjlucaso1 973884eabe Support fs.promises.rm options
fs.promises.rm(path, options) accepts the { recursive, force, maxRetries,
retryDelay } literal options that rmSync accepts. It runs the same removal
and rejects with its errors.

Removing a directory without recursive now throws Node's ERR_FS_EISDIR
SystemError from rmSync and fs.promises.rm. A retryDelay outside int32 >= 0
or a maxRetries outside uint32 throws ERR_OUT_OF_RANGE. rmSync checks these
options before the path, and fs.promises.rm checks the path first, as Node
does.

Fixes #1615

Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
2026-10-21 09:13:12 -05:00
Chris Tateandblurname 237a511174 Resolve conditional member types over literal class type arguments
A generic class instance bound its type parameters only to their mapped
IR types, so a literal argument widened (K = "required" became string)
and a member typed by a conditional over it, such as
`get(): Value<T, K>` with `type Value<T, K> = K extends "required" ? T :
T | undefined`, refused with SC2001.

Class instantiation now also keeps the type arguments as checker types
for the members it collects and lowers, as generic functions do. A
literal argument for a type parameter that a conditional type in the
class depends on is part of the instance key, so Field<string,
"required"> and Field<string, "optional"> are separate instances. The
conditional evaluation decides `X extends Y` by assignability when both
sides are primitive or literal types.

Fixes #1240

Co-authored-by: blurname <50226652+blurname@users.noreply.github.com>
2026-10-21 09:05:05 -05:00
Chris Tateandblurname a7faf91515 Accept defaulted callbacks in literals under an erased generic member
A generic arrow or method held by an object literal is also lowered with
its type parameters erased, for the literal's own value. In that lowering
the object it returns builds as a checked-dynamic value, so a callback
member with an optional or defaulted parameter, such as
`decode(value, at = [])`, met a slot type that maps to a dynamic value.
The completed-signature check refused it with SC1090, although the same
code in a standalone generic function compiled.

A slot whose type maps to a dynamic value now takes the function the same
way an 'any' slot does: the value boxes into a checked-dynamic function,
which completes omitted arguments with undefined.

Fixes #1373

Co-authored-by: blurname <50226652+blurname@users.noreply.github.com>
2026-10-21 08:59:12 -05:00
Chris Tateandejc3 e41858c7df Compile assignment and update targets wrapped in parentheses or non-null assertions
TypeScript accepts parentheses and non-null assertions around the
reference an assignment, a `++`/`--` update or a for-in/of head writes
(`(x) = 1`, `h.sums[k]! += 10`), and both are erased at run time. The
compiler now reads its source files with the AstFile writeTargets option:
the writer's `left`, `operand` or `initializer` is the reference itself
and the reference's `parent` is the writer, so lowering and every analysis
of a binding's or member's writes see one shape. The reference keeps its
own source span. Files read without the option, such as type nodes from
the checker and the AST conformance tests, keep TypeScript's exact tree.

Fixes #1625

Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
2026-10-21 08:58:51 -05:00
Chris Tateandsaboursepideh 9b70fb5e7b Negotiate ALPN and bound the TLS version in tls.connect and tls.createServer
tls.connect and tls.createServer accept ALPNProtocols as an array of
strings, a Buffer or a Uint8Array in the wire format. The server answers
with the first of its own protocols that the client offers, and both
sockets report it as alpnProtocol. A client that offers none of the
server's protocols fails with ERR_SSL_TLSV1_ALERT_NO_APPLICATION_PROTOCOL.

minVersion and maxVersion bound the handshake, and getProtocol() reports
the version it picked. Ranges that do not overlap fail with Node's
ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION or ERR_SSL_NO_PROTOCOLS_AVAILABLE,
and invalid versions throw Node's TypeError. tls.connect's timeout option
arms the socket's inactivity timeout before it connects.

Refs #1438

Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
2026-10-21 08:58:29 -05:00
Chris Tateandespetro fd8ac00e63 Convert global isFinite's argument with ToNumber
Global isFinite now converts any argument with ToNumber before the test,
the same way global isNaN does, and only Number.isFinite stays strict.
Before, an argument that wasn't typed as a number, such as a checker-unknown
or dynamic value, refused with SC2012.

The shared ToNumber conversion for these arguments (isNaN, isFinite, the
Math functions, String.fromCharCode, DataView offsets) now handles every
value kind: strings parse as numeric literals, booleans and null convert
to numbers, undefined is NaN, and dynamic values call valueOf/toString or
throw Node's TypeError for a Symbol or BigInt. Before, a dynamic value
went through a checked number cast and threw a TypeError for a string,
and a string or boolean in a JavaScript file reached a run-time SC1090.

Fixes #1386

Co-authored-by: espetro <26749475+espetro@users.noreply.github.com>
2026-10-21 08:53:09 -05:00
Chris Tateandjlucaso1 f168402e1f Name the reason in the CompileError for untranslated WebAssembly modules
A WebAssembly module the build finds but does not translate (one that uses
a proposal outside the translated feature set, an invalid one, or one the
translator rejects) now rejects with a CompileError that says the module
was found and names the reason, for example "it uses simd, outside the
translated WebAssembly feature set". Bytes the build did not see keep the
existing message.

The build emits the hash, length and reason of each such module in a
second table, scr_wasm_untranslated, next to scr_wasm_registry; the runtime
reads it when a lookup misses. WebAssembly.validate still answers false for
these modules, and the error stays a catchable CompileError.

Fixes #1617

Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
2026-10-21 08:52:18 -05:00
Chris Tate f2fd1c85a2 Run Buffer methods on Buffers held as checked-dynamic values 2026-10-21 08:50:54 -05:00
Chris Tateandsaboursepideh e50454caf3 Support TypedArray.of
TypedArray.of(...items) on the numeric typed-array constructors creates a
new array of its arguments. Each argument converts with ToNumber and then
to the element type, in argument order after all arguments are
evaluated, as in Node. Arguments that are all numbers copy directly;
other values and spreads collect into an array that goes through
TypedArray.from's conversion.

Spreading a dynamic iterable into a call's items (Array.of, push,
TypedArray.of) now drains it through the iterator protocol with the
spread call's TypeErrors, and a typed iterable spread into dynamic items
boxes and drains the same way. Before, both reached a run-time SC2020.

Fixes #1440

Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
2026-10-21 08:48:03 -05:00
Chris Tateandsaboursepideh 726c2ce3d7 Freeze, seal and query functions through their property tables
A plain function's own properties (name, length, prototype, and the ones a
program assigns, as in `hash.outputLen = 32`) live in its property table.
Object.freeze, Object.seal and Object.preventExtensions of a function now
act on that table and answer the function, and Object.isFrozen,
Object.isSealed and Object.isExtensible answer from it, as in Node:

- Object.freeze of a function was refused as a possibly-aliased value,
  and Object.isFrozen of one threw SC2020;
- Object.isExtensible answered false and Object.isSealed true for every
  function.

Adding a property to a frozen or non-extensible function throws Node's
TypeError; assigning a read-only one throws the runtime's existing
read-only TypeError. Class constructors keep their fences. This lets
@noble/hashes' createHasher return its frozen hasher, so the sha256
program in the issue prints the digest Node prints.

Fixes #1437

Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
2026-10-21 08:47:37 -05:00
Chris Tateandjlucaso1 d858d840f9 Accept file URL paths in synchronous fs functions
readFileSync, writeFileSync, appendFileSync, statSync, lstatSync,
readdirSync, mkdirSync, rmSync, rmdirSync, mkdtempSync, accessSync,
unlinkSync, chmodSync, chownSync, lchownSync, truncateSync, copyFileSync,
renameSync, realpathSync, realpathSync.native, openSync and opendirSync
accept a file: URL wherever they accept a path. The URL converts as
fileURLToPath converts it, with Node's errors for other schemes, hosts,
encoded separators and null bytes.

Arguments after a URL path that have side effects are evaluated into
hidden locals before the conversion, so they run even when it throws. A
URL path after a string path, or with options that are not literals,
fails with SC2020, because Node checks those arguments before it converts
the URL. existsSync and the fs.promises functions also fail with SC2020
for URL paths: existsSync answers false for a URL it cannot convert, and
fs.promises reports conversion errors as rejections.

Fixes #1616

Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
2026-10-21 08:46:44 -05:00
Chris Tate 13790b88c7 Call an object's own or inherited toString in template literals and join 2026-10-21 08:44:31 -05:00
Chris Tate f58c321c07 Sort by several keys without calling a chained comparator
A comparator that chains key differences with `||`, such as
`(b.score ?? 0) - (a.score ?? 0) || a.id - b.id`, ran through the
comparator sort: TimSort called the closure once per comparison. Each
term orders one number field, ascending or descending, and a term that is
0 or NaN falls through to the next, so the chain orders elements by their
keys with the first deciding. With no key NaN or -0 that order is
consistent, and a stable sort by the keys gives the comparator sort's
permutation.

The recognizer now accepts such chains of up to eight terms over number
fields of records and class instances, and a key may read an optional
number field with a number default (`x.k ?? 0`), also as a single key. The
runtime reads and checks every key of every element before it moves a
slot, then runs stable keyed passes from the last key to the first. A NaN
or -0 key, or a chain with any other term, keeps the comparator sort.
2026-10-21 08:31:35 -05:00
Chris Tate c1bb553964 Give ProgramLifetime class instances a 32-bit header and packed fields 2026-10-21 08:30:09 -05:00
Chris Tate 1ee5c84cad Read BYTES_PER_ELEMENT on narrowed-away and untyped typed arrays (#1639) 2026-10-21 04:24:36 -05:00
Chris Tate 8b4e74d050 Type empty arrays filled by later writes inside functions by their reads (#1632) 2026-10-21 04:23:15 -05:00
Chris Tateandsaboursepideh 916303beff Read BYTES_PER_ELEMENT on narrowed-away and untyped typed arrays
`a instanceof Uint8Array || (... a.BYTES_PER_ELEMENT === 1)`, the
@noble/hashes isBytes guard, narrows `a` to never on the right side when
the guard is specialized for a Uint8Array argument, so the read missed
the typed-array member lowering; it now answers the element size from
the receiver's storage. The checked-dynamic read of BYTES_PER_ELEMENT on
a typed array or Buffer (an untyped argument) answered undefined; it now
answers the element size, as each kind's prototype does in Node.

Refs #1437

Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
2026-10-21 04:21:00 -05:00
Chris Tate d244b84931 Keep the split scripts' reads and writes inside the worktree 2026-10-21 03:47:21 -05:00
Chris Tate cb75c51eda Fail the zero-cost gate when it compares nothing
A benchmark workload or the self-hosted compiler that fails to build
with both checkouts now fails the gate, and so does a run in which no
program built with both. Before this, a run whose builds all failed the
same way, such as with a helper of another version, reported PASS. A
build error now prints in full under its program; its first line is
often a warning.
2026-10-21 03:45:36 -05:00
Chris Tate 71e3dc5b80 Keep shorthand-property imports in split methods and treat extends as a value use in split modules 2026-10-21 03:21:11 -05:00
Chris Tateandejc3 b237cf1ff0 Convert possibly undefined indexes of element writes with ToNumber
An element write whose index the checker types as a number but whose
storage may hold undefined (a for...of variable over an array that may
have holes) converted the index with ToNumber only on plain-array writes.
Typed-array writes, compound and update writes, element assignments used
as values and element targets in destructuring assignment now convert it
the same way, as the element reads already do, instead of refusing it as
a non-number key.

Fixes #1622

Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
2026-10-21 03:04:59 -05:00
Chris Tate 1f554fbc75 Compile the typed-array callback methods (#1633) 2026-10-21 03:01:01 -05:00
Chris Tateandejc3 2078392a52 Compile the typed-array callback methods
forEach, map, filter, some, every, find, findIndex, findLast,
findLastIndex, reduce and reduceRight compile on every typed-array kind
through one shared helper per method shape. The receiver, the callback and
reduce's initial value evaluate in order before the helper runs; the
helper reads the length once and each element when it visits it, so the
callback's writes show in later elements. Callbacks receive the element,
its index and the typed array (reduce's receive the accumulator first).
map and filter return a new typed array of the receiver's kind, map
storing each result with that kind's element conversion, and a Buffer's
map and filter return a Buffer. reduce without an initial value throws
Node's TypeError on an empty typed array. A thisArg keeps the fence, as
for arrays.

Fixes #1620

Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
2026-10-21 02:55:08 -05:00
Chris Tate 095acffb46 Compile generic arrow bindings inside functions that use no enclosing frame (#1627) 2026-10-21 02:54:32 -05:00
Chris Tate 3e08632467 Refuse values of generic bindings declared inside functions
Each call of a function makes a new function object for a generic arrow
or function expression bound inside it, so `make() !== make()` when make
returns the binding, and such values are distinct Map and Set keys. The
compiled instances are shared module functions, which cannot give that
identity, so a value use of such a binding keeps an SC1090 fence that
names the reason. Calls of the binding still compile.
2026-10-21 02:51:36 -05:00
Chris Tateandejc3 009a01d074 Type empty arrays filled by later writes inside functions by their reads
An unannotated `const xs = []` (or `let`) inside a function, which
TypeScript types by the elements written before each read, takes the array
type its settled reads have: records pushed in a loop give a record array,
numbers a number[], strings a string[]. Reads inside closures, which the
checker leaves unsettled, do not count. When settled reads disagree or the
binding is reassigned, the binding is a dynamic array, as the same binding
already is at module scope. Before, such a binding was always number[] and
pushes of other values were refused.

Fixes #1623

Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
2026-10-21 02:40:20 -05:00