Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
01f15bc
Compile nested generic function declarations and give a generic funct…
cramforce Nov 8, 2026
0e0f577
Refuse at compile time a Proxy that enters a class or record slot
cramforce Nov 8, 2026
2cfa61b
Answer process.stdin where a stream class is declared; join spawn's a…
cramforce Nov 8, 2026
5dbf8f1
Compile eight forms of typed code that were refused
cramforce Nov 8, 2026
5d90484
Keep a dictionary's identity inside a recursive union; treat a dictio…
cramforce Nov 8, 2026
8ff5883
Store union members that share a discriminant literal as one record; …
cramforce Nov 8, 2026
faed53a
Keep `left ?? right` refused where taking the left side would copy it
cramforce Nov 8, 2026
8a02130
Measure the weak-key memory test in bytes per additional key
cramforce Nov 8, 2026
d113c99
Report a dictionary copied where its identity is observed
cramforce Nov 8, 2026
1247c1b
Regenerate the island conformance list: four entries this branch moves
cramforce Nov 8, 2026
3497a48
Make the dictionary-copy scan compile with the free layoutPairs
cramforce Nov 8, 2026
dcbdc0b
Refuse a written-through copy of a record that holds a dictionary
cramforce Nov 8, 2026
0a62811
Merge main: the child-process limit, the gated runtime units and the …
cramforce Nov 8, 2026
fba7f06
Bound the search for a copied dictionary by what the target can hold
cramforce Nov 8, 2026
441ac37
Build a value that only looks like a union member into the union as i…
cramforce Nov 8, 2026
cd40117
Take the build-time Proxy refusal back out
cramforce Nov 8, 2026
e73c08c
Merge main: function members, callable namespaces and the download re…
cramforce Nov 8, 2026
c7a39d4
Report the stdin iteration test's bytes together, not per piece
cramforce Nov 8, 2026
fbe3a4f
Merge main: array methods on checked-dynamic receivers, the island an…
cramforce Nov 8, 2026
3599842
Record that `??` over a stale-narrowed binding builds on this branch
cramforce Nov 8, 2026
c909fcd
Merge main: option values a child launch does not read, with the shel…
cramforce Nov 8, 2026
1375238
Give process.stdin and the Readable it is handed on as one flowing state
cramforce Nov 8, 2026
cc2d401
End process.stdin handed on as a Readable the way Node's stream for t…
cramforce Nov 8, 2026
d815246
Merge main: Headers iteration, union arms by their own discriminant, …
cramforce Nov 8, 2026
e7e605e
Say in the narrowing test that `??` over a stale-narrowed binding builds
cramforce Nov 8, 2026
1c406f8
Merge main: the Node library train and the element-read default
cramforce Nov 8, 2026
12912c9
Move the file-backed stdin end out of the stream unit
cramforce Nov 8, 2026
f6d05db
Merge main: function values printed by name, arena groups and const e…
cramforce Nov 8, 2026
cd0bd7c
Merge main: CLI train 21, the hardened runtime build, timer programs …
cramforce Nov 8, 2026
ce3c3e0
Merge main: Node train 4 and CLI train 24
cramforce Nov 8, 2026
53e1c31
Merge main: Node API, coverage, and fallback type fixes
cramforce Nov 8, 2026
83489c4
Withhold process.stdin as a Readable until both names answer one state
cramforce Nov 8, 2026
e5da0f4
Withhold process.stdout as a Writable until both names answer one state
cramforce Nov 8, 2026
d57ec3e
Merge main: trains lang-13 and perf-7, the CommonJS re-export wrapper…
cramforce Nov 9, 2026
20e0536
Merge main into train/pi-1b
cramforce Nov 9, 2026
6ad5580
Merge main into train/pi-1b
cramforce Nov 9, 2026
dd8031f
Merge main into train/pi-1b
cramforce Nov 9, 2026
067bd10
Merge main into train/pi-1b
cramforce Nov 9, 2026
859d769
Merge main into train/pi-1b
cramforce Nov 9, 2026
33d8ebd
Merge main into train/pi-1b
cramforce Nov 9, 2026
0e6a32a
Merge main into train/pi-1b
cramforce Nov 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion docs/content/docs/coverage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ Coverage and `scriptc build` report the same diagnostic codes. Common ones inclu

Some code compiles but can behave differently than under Node.js. Coverage reports the cases it can detect as `SC6xxx` warnings, and `scriptc build` prints the same warnings after a successful build. They never fail a build, but `--fail-on=divergences` can fail a CI job on them.

- `SC6001`: a conversion to a record, array, or class type with another layout copies a value something else can still reach, and the program writes a field or element of either type, compares values of the converted type by identity, or passes either to code scriptc cannot follow.
- `SC6001`: a conversion to a record, array, or class type with another layout, or of a dictionary that keeps its identity to a dictionary type with another value type, copies a value something else can still reach, and the program writes a field, element, or key of either type, compares values of the converted type by identity, or passes either to code scriptc cannot follow.
- `SC6002`: values of one record type are built in key orders scriptc cannot track per value, so the type keeps its declaration order, an object literal's key order differs from it, and the program observes the order through `JSON.stringify`, `Object.keys`, `Object.values`, `Object.entries`, `for...in`, or console output.
- `SC6003`: `localeCompare` compares a string literal outside the built-in Latin-script collation.
- `SC6004`: a call passes a class instance to a function that casts it to an unrelated class.
Expand Down
9 changes: 9 additions & 0 deletions docs/content/limitations/console-and-process-streams.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,12 @@ The record is a separate value: it is not identical to the stream, and it expose
A record type that also declares a data member, such as `isTTY`, throws `SC2002`.
An instance of a stream class such as `Writable` throws `SC2002` in a record type of methods as well.
For those, pass an object literal that wraps the stream: `const sink: Sink = { write: (text: string) => { process.stdout.write(text); } }`.
Passing, storing or returning `process.stdout` or `process.stderr` where `stream.Writable` is declared does not by itself make them a `Writable`: without another use that needs the stream class, the build stops with `SC1090`.
A process stream held as an untyped value does not convert to a stream class: asserting or passing it as `Readable` or `Writable` throws a TypeError.
Passing `process.stdin` where `Readable`, `Duplex` or `EventEmitter` is declared throws the same TypeError.
The bundled declarations declare `process.stdin` by its own members, so these conversions need `@types/node`.
After `process.stdin.setEncoding`, a `'data'` listener registered on `process.stdin` whose parameter is declared `string` receives the decoded chunks, with a multi-byte sequence split across reads kept whole.
A listener whose parameter is a `Buffer` or `Uint8Array`, which includes a parameter with no annotation, throws a TypeError with code `SC2020` at the first chunk, because Node delivers a string there.
`process.stdin.read()` and `for await` over `process.stdin` answer bytes, so after `setEncoding` they throw the same kind of TypeError at the first chunk they would hand out, where Node answers a string.
A `read()` that hands out no chunk (`read(0)`, or a `read()` with nothing held) answers `null` and an iteration that gets no input ends, as in Node.
To read text that way, leave the encoding unset and decode the Buffers with a `TextDecoder`.
4 changes: 3 additions & 1 deletion docs/content/limitations/language-features/generics.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
- **Generics:** Generic functions declared inside other functions, generic class expressions, and generic methods requiring dynamic dispatch are unsupported.
- **Generics:** A generic `function` declared inside another function compiles when the enclosing function only calls it: captured values travel as the instance's parameters, or through a shared closure environment when one of them is reassigned.
Taking such a declaration as a value, or capturing a value whose type is the enclosing function's own type parameter, remains unsupported.
Generic class expressions and generic methods requiring dynamic dispatch are unsupported.
Generic classes can extend generic bases when each specialization has a concrete layout.
Independent concrete uses have no specialization-count limit; recursively expanding demands and type layouts produce diagnostics.
Other generic calls compile when the target resolves statically.
Expand Down
10 changes: 10 additions & 0 deletions docs/content/limitations/record-identity-and-property-order.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@ Assigning an existing value to a binding that keeps its initializer's layout, to
The refusal covers a short-circuit assignment that stores the value (`&&=`, or `??=` on a binding that can be nullish) like the plain assignment it stands for.
A member of another binding that kept its initializer's layout carries that layout into the store, so no refusal is reported when the two layouts already match.
Bracket writes such as `alias["k"] = v` write the field in the kept layout, like their dotted spelling.
The members of a union type that share one value of its discriminant property, such as two `{ type: "retry" }` members with different other properties, are stored as one record that has the properties of all of them, and a value of one of those member types is that record: it keeps its identity when it is stored in the union, narrowed, or passed as the member type.
A value whose type is not one of those members but has the same properties as one, such as an object held in a binding whose type was inferred, a value declared with a second spelling of the member's type, or a parsed value asserted to such a type, is that record too and keeps its identity in the same way.
A value with other properties, such as one property more than the member it is stored as, is copied into the record like any record converted to another record type.
Two unions that both write such a member type share one record for it, so a value keeps its identity in both.
When the other member types of the two unions declare one property name with different types, no one record holds them all, and the members of both unions keep separate records instead.
Reading a property that one of those members requires, from a value that an assertion claimed to have that member's properties and that lacks the property, throws a `TypeError` where Node.js answers `undefined`.

Typed records with declared fields, indexed records, and arrays retain identity and mutations through native checked storage.
An empty array literal with no element type of its own, such as the `files` of `{ files: [] }` or `const NONE = [] as const`, takes its element type from the declared types its value reaches and is the same array under every name.
`scriptc build` reports `SC2015` where one such array is used at two element types or reaches a typed array through a computed key; writing the type at the literal (`[] as string[]`) compiles.
A record or class instance converted to a string-keyed dictionary type such as `Record<string, string>`, directly, through a `{}`, `object` or `unknown` binding or parameter, or as an element of a constant `unknown[]` array that holds such records, keeps its identity when the program writes through, compares or inspects values of that dictionary type.
Every dictionary type with the same value type then uses checked dynamic storage, which is slower than a native dictionary.
The value type is compared by structure, so a dictionary type is the same type whether it is written inline, through an alias, as a generic instantiation, as `Record<string, V>`, under `Readonly`, in another module or in a declaration file; a `readonly` modifier does not make another type, and arrays, tuples and class instances are the same only as instantiations of one type with the same type arguments.
A dictionary that keeps its identity is copied where it is converted to a dictionary type with another value type, such as a `Record<string, number>` stored as a `Record<string, number | string>`, and `SC6001` reports the conversion where the program writes or deletes a key of the copy or of a dictionary that keeps its identity, or compares values of the copy's type by identity.
A union type that refers to itself and holds such a dictionary, such as a JSON value type `string | number | Json[] | { [key: string]: Json }`, uses checked dynamic storage as a whole.
`scriptc build` reports `SC2009` for such a union when another of its members, such as a `Map`, cannot be held in checked dynamic storage.
Class instances retain identity at their exact type or a base class, and at any interface or object type that a class instance is passed, stored, returned, or asserted as when the program could tell a copy from the instance: values of such a type are checked views of the original object, so writes, identity comparisons, Set and Map keys, `util.inspect`, and `JSON.stringify` through them reach the instance, as in Node.js.
These types use checked dynamic storage, which is slower than native record fields, for all of their values, including plain objects.
A type whose values are only read field by field, from fields the class does not write after construction and does not implement as accessors, stays a native record and holds a copy.
Expand Down
7 changes: 7 additions & 0 deletions docs/content/limitations/runtime-type-checks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ Any other value throws a `TypeError`, such as `expected null | undefined at $, g
Casts between program classes are checked too: casting an instance to a class it does not extend throws a `TypeError`, where Node.js reads the members structurally.
`SC6004` reports calls that pass such an instance to a function that casts it.

An object whose methods are typed by a generic interface can be stored as another instantiation of that interface, such as a `Parser<"csv" | "json">` stored in a `Parser<Format>[]`.
Each such method then checks its arguments against its own parameter types when it is called.
An argument that holds a union member the parameter type does not include throws a `TypeError`, where Node.js passes the object through.
When that member is an object type with only optional properties, the argument is read as the member of the parameter type that declares the properties the object has, as in Node.js.
It throws a `TypeError` only when no single member declares all of them.
The method receives a copy of an argument record whose type differs between the two instantiations, so a write to that argument in the method does not reach the caller's object, and `SC6001` does not report it.

A Promise converted from `unknown` to a typed result validates its fulfilled value.
A mismatch rejects with a `TypeError`.
Rejection reasons and reference equality are preserved across conversions.
Expand Down
2 changes: 2 additions & 0 deletions internal/compatibility/generated/node-v24-internal.json
Original file line number Diff line number Diff line change
Expand Up @@ -53784,6 +53784,7 @@
"tests/corpus/fetch-headers-copy-cache.ts",
"tests/corpus/fetch-headers-set-cookie-iteration.ts",
"tests/corpus/headers-static-accessors.ts",
"tests/corpus/headers-typed-iteration.ts",
"tests/corpus/js-abort-portable.js",
"tests/corpus/node24-url-factories.ts",
"tests/corpus/node24-url-mutation.ts",
Expand Down Expand Up @@ -54556,6 +54557,7 @@
"tests/corpus/fetch-headers-copy-cache.ts",
"tests/corpus/fetch-headers-set-cookie-iteration.ts",
"tests/corpus/headers-static-accessors.ts",
"tests/corpus/headers-typed-iteration.ts",
"tests/corpus/7735-untyped-headers-iterators.js"
]
},
Expand Down
2 changes: 1 addition & 1 deletion internal/compatibility/static-support.json
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@
{ "chapter": "globals", "symbols": ["URL", "URLSearchParams"], "status": "partial", "evidence": ["tests/corpus/1355-url-parse.ts", "tests/corpus/1791-searchparams-core.ts", "tests/corpus/1792-searchparams-encoding.ts", "tests/corpus/1793-searchparams-iteration.ts", "tests/corpus/1794-searchparams-url-live.ts", "tests/corpus/node24-url-mutation.ts", "tests/corpus/node24-url-factories.ts"] },
{ "chapter": "globals", "symbols": ["structuredClone"], "status": "partial", "evidence": ["tests/corpus/2283-structured-clone.cjs", "tests/corpus/5324-structured-clone-documents.ts", "tests/corpus/9917-structured-clone-errors.ts"] },
{ "chapter": "globals", "signatures": ["Class: `TextEncoder`", "Class: `TextDecoder`"], "status": "partial", "evidence": ["tests/corpus/1423-text-codec.ts", "tests/corpus/text-codec-values/main.ts", "tests/corpus/3133-text-decoder-streaming.ts", "tests/corpus/7747-text-codec-untyped-slot.js"] },
{ "chapter": "globals", "signatures": ["Class: `Headers`"], "status": "partial", "evidence": ["tests/corpus/fetch-headers-copy-cache.ts", "tests/corpus/fetch-headers-set-cookie-iteration.ts", "tests/corpus/headers-static-accessors.ts", "tests/corpus/7735-untyped-headers-iterators.js"] },
{ "chapter": "globals", "signatures": ["Class: `Headers`"], "status": "partial", "evidence": ["tests/corpus/fetch-headers-copy-cache.ts", "tests/corpus/fetch-headers-set-cookie-iteration.ts", "tests/corpus/headers-static-accessors.ts", "tests/corpus/headers-typed-iteration.ts", "tests/corpus/7735-untyped-headers-iterators.js"] },
{ "chapter": "globals", "signatures": ["Class: `Blob`", "Class: `File`"], "status": "partial", "evidence": ["tests/corpus/5285-blob-construct.ts", "tests/corpus/5286-blob-readers.ts", "tests/corpus/5287-blob-inspect.ts", "tests/corpus/5288-blob-validation.js", "tests/corpus/5290-blob-dynamic.js"] },
{ "chapter": "buffer", "signatures": ["Class: `Blob`", "Class: `File`"], "status": "partial", "evidence": ["tests/corpus/5285-blob-construct.ts", "tests/corpus/5288-blob-validation.js", "tests/fixtures/node-types/blob-file.ts"] },
{ "chapter": "buffer", "signatures": ["`blob.arrayBuffer()`", "`blob.bytes()`", "`blob.text()`", "`blob.slice([start[, end[, type]]])`", "`blob.size`", "`type` Type: {string}", "`name` Type: {string}", "`lastModified` Type: {number}"], "status": "supported", "evidence": ["tests/corpus/5285-blob-construct.ts", "tests/corpus/5286-blob-readers.ts", "tests/corpus/5288-blob-validation.js", "tests/corpus/5289-blob-read-order.ts", "tests/corpus/5290-blob-dynamic.js", "tests/fixtures/node-types/blob-file.ts"] },
Expand Down
25 changes: 13 additions & 12 deletions packages/compiler/ambient/node-fallback/child_process.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ declare module "child_process" {
* pipes an empty stdin), shell (the complete command line in
* `command`, with a statically empty args list, run by /bin/sh -c),
* and windowsHide (a POSIX no-op).
* The shell form takes no separate args: Node warns DEP0190 because it
* concatenates them unescaped, and a native build has no warning
* channel, so the compiler REFUSES `shell: true` with a non-empty args
* list. */
* The shell form joins a non-empty args list onto the command, as Node
* does: the arguments are concatenated unescaped, never quoted, and
* the first such call of a process reports the DEP0190 warning on
* stderr. */
export interface SpawnSyncOptions {
encoding?: "utf8" | "utf-8";
timeout?: number;
Expand All @@ -61,8 +61,8 @@ declare module "child_process" {
/* Node's shell form: the complete command line in `command`, with
* an empty args list, runs through /bin/sh -c, so quoting and
* operators follow the shell's rules. A non-empty args list with
* shell: true is a compile error (Node warns DEP0190 for unescaped
* shell arguments). */
* shell: true joins onto the command unescaped, and Node's DEP0190
* warning is shown. */
shell?: boolean;
}
export function spawnSync(
Expand Down Expand Up @@ -92,9 +92,9 @@ declare module "child_process" {
stdio?: "pipe" | "ignore" | "inherit" | ("pipe" | "ignore" | "inherit")[];
timeout?: number;
windowsHide?: boolean;
/* Read at the call. The call then takes the command-line form only:
* a statically non-empty args list is a compile error (Node warns
* DEP0190 for unescaped shell arguments). */
/* Read at the call. When it is on, a non-empty args list joins onto
* the command unescaped and Node's DEP0190 warning is issued; when
* off, the list is the arguments passed. */
shell?: boolean;
}

Expand Down Expand Up @@ -202,9 +202,10 @@ declare module "child_process" {
/* detached gives the child its own session and process group
* (POSIX_SPAWN_SETSID); env REPLACES the child environment; cwd
* sets its working directory; windowsHide is a POSIX no-op; shell
* runs the complete command string through /bin/sh or cmd.exe (with
* an empty args list — a non-empty one is a compile error, because
* Node warns DEP0190 for unescaped shell arguments). */
* (a literal or a boolean decided at run time) runs the command line
* through /bin/sh or cmd.exe. A non-empty args list joins onto the
* command unescaped where the shell is on, and Node's DEP0190 warning
* is reported for the first such call. */
detached?: boolean;
env?: { [k: string]: string | undefined };
cwd?: string;
Expand Down
12 changes: 10 additions & 2 deletions packages/compiler/ambient/scriptc-node-fallback.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,10 @@ declare var process: {
* resume, isPaused, read, readableLength), destroy() (full teardown:
* nothing fires after, the loop stops waiting), and async iteration
* (`for await (const chunk of process.stdin)` — chunks are Uint8Array
* values, ending at EOF). setEncoding has no lowering (chunks stay
* bytes; decode with TextDecoder). */
* values, ending at EOF). setEncoding decodes the chunks a 'data'
* listener with a string parameter receives. read() and async iteration
* answer bytes, so once an encoding is set they throw (Node answers
* strings there): leave the encoding unset and decode with TextDecoder. */
stdin: {
readonly isTTY: boolean;
/* The descriptor the stream is built over (Node reports 0 here and
Expand All @@ -407,6 +409,11 @@ declare var process: {
* undefined on a non-TTY stdin (the `x || false` idiom). */
readonly isRaw: boolean;
on(event: "data", listener: (chunk: Uint8Array) => void): void;
/* After setEncoding the chunks are strings: a listener that declares
* its parameter string receives the decoded text. A listener compiled
* for bytes (the overload above, which an unannotated parameter takes)
* throws at the first chunk once an encoding is set. */
on(event: "data", listener: (chunk: string) => void): void;
on(event: "end", listener: () => void): void;
on(event: "error", listener: (err: Error) => void): void;
/* Paused mode, as in Node: input stays buffered for read(), and the
Expand All @@ -419,6 +426,7 @@ declare var process: {
listener: (str: string | undefined, key: import("readline").Key) => void,
): void;
once(event: "data", listener: (chunk: Uint8Array) => void): void;
once(event: "data", listener: (chunk: string) => void): void;
once(event: "end", listener: () => void): void;
once(event: "error", listener: (err: Error) => void): void;
once(event: "readable", listener: () => void): void;
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler/src/backend/executable-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ export function executableLinkFeatures(
...(features.promiseGeneric ? { promiseGeneric: true } : {}),
...(features.dynTier ? { dynTier: true } : {}),
...(features.streamDyn ? { streamDyn: true } : {}),
...(features.streamStdin ? { streamStdin: true } : {}),
...(features.handleProps ? { handleProps: true } : {}),
...(features.childLifecycle ? { childLifecycle: true } : {}),
...(features.childShellLine ? { childShellLine: true } : {}),
...(features.processFeatures ? { processFeatures: true } : {}),
// A child process an island made, in a native ChildProcess slot
// (scr_node_island_child.c): a program with both islands and child_process.
Expand Down
4 changes: 3 additions & 1 deletion packages/compiler/src/backend/llvm/dyn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5037,7 +5037,9 @@ export class LlDyn {
const flags = B.tmp();
const np = B.tmp();
const alloc = B.tmp();
B.line(`${ovfp} = getelementptr inbounds %${struct}, ptr %v, i64 0, i32 1`);
// The map follows the record's header, which has a second word
// when the record is an arm of a pointer-represented union.
B.line(`${ovfp} = getelementptr inbounds %${struct}, ptr %v, i64 0, i32 ${base}`);
B.line(`${ovf} = load ptr, ptr ${ovfp} ; overflow map`);
B.line(
`${flagsp} = getelementptr inbounds i8, ptr ${ovf}, i64 ${mapFlagsOffset(host.sizeType)}`,
Expand Down
Loading
Loading