A class declared `extends Array` (or `extends Array<T>`) was refused with
"extending classes not declared in the program ('Array')", which blocked
lru-cache at module load. An instance is now an array of the class's
element type: its struct begins with the runtime's array header, whose
reference count it shares, and the class's own fields follow it. The
class's TypeScript type maps to its element array type, so every array
operation applies to an instance, and passing it where an array is
expected passes the same object. The class's own fields, methods,
accessors and statics read the array as the class object.
super() follows the Array constructor: one number argument sets the
length (a RangeError for an invalid one), other arguments become the
elements, and the implicit constructor forwards its arguments the same
way. The runtime finds the class through the array's ext (ScrArrClass:
name, destroy, the own fields, the species constructor), so instanceof
the class (or a binding that holds it) works on static, array-typed and
dyn values, util.inspect
prints `Name(length) [ ..., field: value ]`, Object.keys lists the own
fields, and a release through any array path reaches the class's
teardown. Instances whose elements or fields can form cycles carry the
collector header and are traced.
map, filter, slice, splice, concat, flat and flatMap construct their
result through the class as ArraySpeciesCreate does: `new C(length)` with
the specification's argument, then the elements, then the length where
the specification sets it. A map to another element type retypes the
new instance when its constructor added no elements. Array.of and
Array.from called on the class construct through it. Programs that
declare no Array subclass compile as before.
A generic subclass, a subclass of such a class and `static
[Symbol.species]` are refused at compile time; a species result the
class's constructor cannot construct throws a TypeError.
String() of an array with checked-dynamic elements now uses the
runtime's String() instead of being refused.
Corpus 7720-7726, diagnostics fixture array-subclass-fences.ts.
An executable's LLVM text was divided into one partition per 4 MiB, so a
program under 4 MiB was optimized and compiled on one thread, and one of
up to 32 MiB used fewer than eight cores. A module over 1 MiB now gets at
least eight partitions of 512 KiB or more: four up to 2 MiB, and eight up
to 32 MiB. Larger modules keep one partition per 4 MiB. The count still
depends only on the module's size.
Refs #1777
An identifier-minified esbuild bundle renames the __commonJS helper
(var j=(u,t)=>()=>(t||u((t={exports:{}}).exports,t),t.exports)). The
recognizer already compared the helper's shape independently of its
parameter names, but only used a renamed match to refuse the package.
A stable top-level declaration with the helper's shape now splits the
bundle's chunks like the named helper, so lru-cache's CommonJS build is
no longer refused.
Refs #1777
npm packages are no longer refused up front for minified JavaScript. A
bundle whose interop helpers lost their names is still refused by the
bundle recognizers, and a package whose attempt fails is still refused on
its own.
A `var` read in a later declarator of the statement that stores it
(`var a = {...}, b = {...a}`) runs after the store. A read in a function
body is now located at the body's own statement instead of the body, so
JavaScript `var` bindings read after their store in a function keep their
type instead of gaining undefined.
`replace` and `replaceAll` with an untyped search value and a callback
call the regex callback replacement for a RegExp and the string-search
helper otherwise. That helper now accepts untyped callback parameters,
passes undefined to parameters after the subject, and converts number,
boolean and untyped results with ToString.
In JavaScript, `||` and `&&` over primitives of different types whose
result tsc types `any` evaluate in the checked domain.
tsc-ts 0.0.8's API server serves on the named pipe the Node host
connects over on Windows (tsc-ts --api --pipe), and the native compiler
already starts it over its standard streams there, so tsc-ts is the
default checker on every platform. SCRIPTC_CHECKER=tsgo still selects
tsgo.
The compiler depends on tsc-ts 0.0.8, which the release-age exclusions
admit with its platform packages and @scriptc/threads 0.4.2.
The Windows CLI smoke test runs with both checkers, and
verify-native-cli.mjs --run checks that a distribution's tsc-ts runs on
its platform (tsc-ts.exe on Windows).
@hono/node-server replaces the global Request and Response with
lightweight objects and reparents prototypes, tricks that only pay off
under V8 and that native compilation cannot follow. scriptc now compiles
the package from a built-in implementation (shims/hono-node-server/v2.ts)
over node:http and the native Request/Response/Headers: serve,
createAdaptorServer, getRequestListener and RequestError.
Responses are written the way the package writes them, byte for byte on
the wire: the general path (header record in Headers order, Set-Cookie
gathered, default content type, content length for bodies that finish
within the first reads, streamed bodies, error responses) and the
package's cached-response path for a Response that is still exactly its
constructor arguments. The runtime tracks that lightweight state on each
constructed Response (header form, raw plain-record headers, body length;
cleared by body and metadata reads, turned into Headers form by a headers
read) and exposes it to the shim through scriptcResponseLight.
The declaration entry of a shimmed package may now be a .d.mts/.d.cts
file. The npm case hono-node-server drives buffered, streamed, cookie,
header-form, HEAD, error-handler and URL cases against the real package
under Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Iterable<T> maps to a union of the sources that keep their own
representation (T[], Set<T>, Map<K, V> for [K, V] entries, string) and a
generator over T that holds every iterator. An array stays the same
multi-pass object; a generator stays single-pass. for-of gives the array
arm its own array loop, so iterating an Iterable that holds an array runs
at array speed. Spread, Array.from, the Set/Map constructors and
Object.fromEntries consume it; optional Iterables narrow into it;
generator functions may declare an Iterable return. Class iterables and
typed arrays are refused at the conversion with a named diagnostic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wasm-bindgen output constructs a FinalizationRegistry per exported class
when its module loads, so every wasm-bindgen package stopped at load with
SC2020. `new FinalizationRegistry(cleanup)` now creates a native registry:
- register(target, held, token) and unregister(token) check their
arguments and answer as Node does, with Node's TypeError messages;
- the unregister tokens are keys of an internal weak map, so the registry
keeps no token alive;
- the cleanup callback never runs, which ECMAScript allows, so the
registry keeps neither the callback nor the held values.
The global answers typeof "function", instanceof, Object.prototype.toString
and util.inspect as Node does. Symbol targets and tokens are refused with
SC2020.
`"encodeInto" in encoder` on a TextEncoder, which wasm-bindgen's loaders
test at load, answered false because the encoder is a brand record without
fields for its methods. `in` over such a builtin brand record now answers
from the members the standard library declares, plus Object.prototype's.
The limitations page says that memory a package frees only from a cleanup
callback, such as the WebAssembly memory behind wasm-bindgen wrappers the
program drops without free(), is not reclaimed until the program exits.
Fixes#1678.
Record shapes are interned by field set, and their key order was one table
per shape. When object literals of one shape wrote the keys in different
orders (`{ id, name }` and `{ name, id }`), every value enumerated in the
first type's order, and anonymous literal types got no warning.
- The key-order witness pass gives such a shape its orders
(IrRecordShape.orderVariants, the declared table first) and a hidden
number field, %order, that each value sets to the index of its order:
a literal of explicit keys stores its own order, a copy from a shape
with one order stores that order, and spreads, rest bindings and
copies of values with orders read their source's index. Allocation
zeroes the field, so checked conversions and runtime producers keep the
table as before, and clones copy it.
- A write of an optional key a value lacks moves the key to the end of
that value's order, as in Node: the orders include each order such
writes reach, and the write site updates the index first, evaluating a
receiver other than a binding once.
- Object.keys, Object.values, Object.entries, for-in, util.inspect,
JSON.stringify, the record-to-dynamic conversion (structuredClone,
postMessage, untyped views) and live record views branch on the index.
The dynamic conversion and checked conversions leave the field out.
- A shape keeps its table, and SC6002 still reports its literals, when a
construction's order cannot be traced: a spread of a value of unknown
order, a clone that adds an optional key, a copy whose source is not a
binding, or more than 16 orders.
Corpus 7122 to 7128 check each surface against Node.
- An empty `[]` whose own type is `never[]` (a `return []` in an async
function, whose contextual type is `T[] | PromiseLike<T[]>`, or an
object literal's `files: []` field) builds as the unit-only element
array, like the empty tuple, so typed array slots receive it through
the checked empty-array lift. It was `number[]`, so `string[]` slots
refused it with SC1090 or SC2002.
- The refusal "instanceof against this native constructor" names its
site, and coverage lists the sites whose right-hand side is `Object` or
`Function`, which always reach it. An arrow function or method on the
right of an untyped `instanceof` throws Node's "Function has non-object
prototype" TypeError for an object.
- Coverage lists the diagnostics of a function whose signature did not
compile. A use of a parameter whose type did not compile reports SC2004.
- Coverage lists deferred fences in a module's top-level code under "runs
at startup", and the diagnostics envelope marks them `startup: true`.
A `for` loop's initializer and a `for...of` loop's iterable run once, but
SC6101 counted any dynamic operation inside a loop statement's span, so
untyped setup there produced the warning and failed --fail-on=dynamic.
Loop work is now found by walking the IR: a loop's condition, update and
body repeat, its initializer and iterable do not. Locations could not
tell them apart, because lowering gives the index loop of a `for...of`
the whole statement's span.
A build now names the functions of the program's own files that compile
to the dynamic tier and likely run often, so a slow program with a little
untyped code says where the time goes:
warning SC6101: 3 functions that likely run often use untyped values, which runs slower than typed code:
advance (nbody.js:22): untyped JavaScript file, in a loop
...
- The functions come from the tier assignment lowering already makes
(Lowerer.dynamicFunctions). Without a profile, a function counts as
likely hot when a dynamic operation runs inside one of its loops, when
a loop calls it, or when it calls itself (lowering/dynamic-tier.ts).
- A function is named only when its source says why its values are
untyped: an `any` (for a for...of variable, the value it iterates), an
untyped JSON.parse result, or an untyped JavaScript file. Generated
%dyn.* helpers, module top levels and functions that are dynamic for an
operation without a static lowering are left out.
- At most five functions are named, most likely hot first, with the
total; dynamic-tier code in npm packages is counted per package on one
line. All of the warning's text is in diagnostics/dynamic-tier.ts.
- SC6101 opens the SC61xx band, performance notes, with a new
"performance" category in the diagnostics envelope; SC60xx stay the
divergences.
- scriptc build prints it after the divergence warnings, and the
executable cache stores it with the entry, so a cached build (including
the CLI's routed fast path) repeats it. scriptc coverage reports it, and
a comment-only edit no longer replays a verdict that carries it, since
its text holds line numbers.
- --no-dynamic-tier-warning leaves it out of build and coverage;
coverage --fail-on=dynamic fails on it.
- Docs: coverage.mdx (Dynamic tier, Gating builds and CI, the envelope
categories) and cli.mdx.
How It Works gains a Build parallelism section: the frontend runs on one thread, executables divide into
partitions by the size of their LLVM text, the LLVM helper compiles partitions on a thread per hardware thread,
what dev and optimized builds run in parallel and in order, what a rebuild compiles again, and what
profile-guided optimization adds to build time. The CLI reference links to it from --emit, --optimization,
--profile-use, SCRIPTC_CACHE_DIR, and Profile-guided optimization.
Fixes#1340
Co-authored-by: Quino Terrasa <26749475+espetro@users.noreply.github.com>
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>
`JSON.parse(text) as T`, `const x: T = anyValue` and `await res.json()
as T` threw `TypeError: expected X at $.path, got Y` when the value did
not fit `T`. Node never checks an assertion and continues with the value
it has, so the build-container listener rejected an order Node accepts
and a JWT with a string `exp` became a 500.
A failed cast now keeps its value when it initializes a `const`/`let`
binding declared in a function or block and only the code after the cast
reads the binding. That code (the statements after the cast in each
enclosing statement list, up to the declaration) lowers twice: the
static copy, unchanged, runs while the value fits; a dynamic copy, in
which the binding and everything derived from it is typed `any`, runs
after a failure, so missing members read undefined, wrong-typed members
keep their values, extra members stay visible in source order, and
objects keep their identity. The cast sets the flag that selects the
copy: the fused JSON.parse helper's slow path and the plain check call
scr_dyn_check_recover, which takes back the TypeError and parks the
value for checkedCast.fallbackTaken/fallbackTake.
A failed value that flows into a typed slot outside the region is
checked there, as any `any` value is: it continues when it fits and
throws at that use when it does not. A statement of the dynamic copy
with no `any` lowering becomes a runtime fence that throws only when a
failed value reaches it. Module-level bindings, regions with hoisted
declarations or super() calls, union targets and targets holding class instances, and
--tier=static-only (and library mode) keep the throwing cast.
`a?.b.c` on an `any` value now short-circuits only on `a`, so `.c`
throws when `a.b` is undefined, as in Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Externrefs a guest holds are borrowed pointers. An import's result was
kept only until the outermost call returned, which was before the export
call converted its results to JavaScript, so a result created by an
import was freed before conversion. A table or global slot released the
reference it replaced at once, although the guest may still hold it in a
local after reading the slot. The kept references are now released after
the outermost caller has converted its results (scr_wasm_leave), and a
table or global store during a call keeps the reference it replaces until
then.
Checked memories (explicit bounds checks: sanitized builds, Windows)
grew with realloc, which can move the storage that memory.buffer and its
views alias. They now reserve the address space for their largest size
(mmap on POSIX hosts, VirtualAlloc on Windows) and commit pages as they
grow, so the base never moves. Hosts without address-space reservation
grow in place only until a buffer has been handed out; after that,
growth that would move the storage fails.
Tests: webassembly-externref-lifetimes.mjs (import results through an
export, table and global references replaced by the guest and by
JavaScript), webassembly-memory-growth.test.ts (views written after
growth from JavaScript and from the guest), and translate.test.ts cases
for memargs naming memory 0 explicitly and for local.tee operand types,
which b3bcbaaf7 made consistent with validation. Under SCRIPTC_SAN=1 the
two runtime tests report heap-use-after-free without these changes.
Merges main; internal/compatibility/generated/node-v24-backlog.json is
regenerated with pnpm node-compat.
readable-stream 3.x is a copy of Node 10's streams. Like 2.x and 4.x it
now compiles as node:stream: readable.js as stream.Readable carrying the
stream classes, Stream, finished and pipeline, and lib/_stream_*.js as the
matching builtin classes. Unlike them, this is not the package's own
READABLE_STREAM=disable mode, which throws on Node 24 (it Object.assigns
every stream export onto stream.Readable, which inherits the getter-only
`promises` accessor). The limitations and dependencies pages and the
compatibility notes say so.
The corpus program readable-stream-3 runs the vendored 3.6.2 under Node and
keeps to behavior its streams share with the builtin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>