A class a bundled ES module's __esm initializer stores into a module
binding (`Multi = class extends import_stream.Readable { … }`) becomes a
class declaration at the same text position: the initializer's body splits
around it into run-once pieces, so a class over a runtime-provided base no
longer has to exist as a stored class value. Only definitions whose
evaluation runs no code (no static fields, static blocks or computed names;
heritage absent, a builtin module member, or a hoisted class), declared
once, assigned only there and read only inside functions after the
assignment move.
npm selection now also scans the program modules a literal import()
reaches, with their static graphs: a workspace package loaded on demand
had its npm dependencies refused as "not part of the compiled module
graph".
`new import_x.C()` respelled `new require("x").C()`, which constructs the
require call; the interop binding now becomes `(require("x"))` there.
util.inherits onto a runtime-provided class (stream.Readable, the from2
and multistream shape) fences where it is written instead of failing in
Node's argument validation at run time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two fixes for the CommonJS cycle check and the bundled-chunk splitter:
- During a cycle's initialization, a read of a partner's exported
function is safe only where the value neither runs nor stays: an
expression statement, typeof, void, !, an equality operand, or the test
of if, while, do and ?:. A const alias (`const f = binding.fn`), an
object or array literal, a return, or an argument can be called before
the cycle settles, so these keep SC1016 (Node throws when the function
reads a binding in its temporal dead zone). A top-level const that only
publishes the function in an export statement, as the static rewrite
spells an export-table re-export, stays a read.
- A copied helper whose initializer is a named function expression
reaches itself through its own name. Each chunk gets its own copy, so
that name must also be used only as a callee; otherwise the chunk keeps
the package refused with the existing reason.
A condition built from typeof tests whose answers are static (an
undeclared global is "undefined", the global object and process are
"object") folds, so the untaken arm of `typeof window < "u" ? window : …`
never lowers, and a detection chain used as a receiver resolves to the
global object it selects. JavaScript files no longer report a name the
file feature-tests with typeof as undeclared. `typeof global.setTimeout`
answers "function" instead of the identity token's "string".
`var pna = process` (process-nextick-args once it compiles as process)
and `var Transform = require("stream").Transform` alias like their const
spellings when nothing reassigns them; before, the first silently lost
process's members.
Also reads symbol declarations directly in the CommonJS accessor
snapshot path, after the checker helper's removal on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isarray, safe-buffer, process-nextick-args, inherits and util-deprecate
evaluate to a Node builtin on Node 24 at their checked major versions, so
a require of one compiles as that builtin expression at the require's
position: from an opted-in package's files (the installed version where
Node's resolution finds it), from the program's own CommonJS files, and
through an esbuild __commonJS chunk whose pnpm path key names the package
and its version. The package's files never join the program.
readable-stream 2.x and 4.x compile as node:stream, the package's own
READABLE_STREAM=disable behavior (a documented divergence). A use of the
entry's export picks the builtin module (member reads of the carried
classes) or stream.Readable (value uses); a mixture keeps the package.
3.x stays as is: its disable branch throws on Node 24.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A chunk whose whole body is `module.exports = require("<builtin>")`
(readable-stream's internal/streams/stream.js) is the builtin module:
its calls become that require, and no chunk module is emitted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A CommonJS module whose export object is one of its own top-level
functions (semver 5/6's `exports = module.exports = SemVer`) reaches
its members through that function after the statement: member reads
and writes through module.exports (and, after the rebinding, every
`exports` reference) become members of the function, the expando form
both resolvers model, and the rebinding itself drops. Constructor
functions exported whole with members attached afterwards no longer
lose those members at runtime.
Replacing module.exports or rebinding exports inside a branch (an
environment-dependent export object, readable-stream 2.x) now names the
construct instead of the generic assignment fence, and a required
builtin module object is truthy in the LLVM backend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1399 removed CheckerFacade.declarationsOf and valueDeclarationOf. #1407 and
#1351 merged after it with calls to them, so main does not type-check. Read
symbol.declarations and symbol.valueDeclaration instead, as the rest of the
compiler does.
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.
A TypeScript implementation over node:net, node:tls and node:events:
Dispatcher, Client (HTTP/1.1 with keep-alive), Pool, Agent,
buildConnector, request(), errors, the global dispatcher, fetch, Headers
and the diagnostics_channel publications, with per-version behavior.
Differential npm cases run the vendored real undici under Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
package-shims.ts registers implementation modules shipped with the
compiler per package and major version (chosen by the importer's
installed version) and reads the project's never-loaded packages from
"scriptc": { "packages": { "<name>": "unloaded" } }. Module edges go
through resolveProjectImport; the npm resolution, the npm-edge walk and
the --npm-static set skip such packages; the tsgo host reads a shimmed
package's index.d.ts as a re-export of its implementation; an unloaded
package's require() throws Node's not-installed MODULE_NOT_FOUND.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@types/node 20 and older declare NodeJS.Timeout and NodeJS.Immediate as
classes; 22+ (and scriptc's own declarations) declare interfaces. Accept
both as the native timer handle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A statement-level require() of a package nothing installed threw a plain
Error; the expression form already carried Node's MODULE_NOT_FOUND code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A call through a bundled ES module's namespace (`ns.fn()`, `(ns.fn)()`,
a tagged template) passes the namespace as `this`. Node's namespace is
the __toCommonJS object (`__esModule` first and non-enumerable,
non-configurable getters); the lowered object of getters differs. The
namespace lowers only when every such call targets a top-level function,
never reassigned, that reads no `this` (an arrow inside it included);
otherwise the package stays refused.
Three fixes for the bundled-chunk splitter and the CommonJS cycle check:
- A reference counts as local only when a scope between it and the chunk
factory (or the copied helper) declares its name. A nested function,
block, loop or catch binding with the name of a top-level helper no
longer hides the chunk's read of that helper. Before, the helper was not
copied and the chunk failed at runtime on the unresolved reference.
- A top-level function is copied into a chunk only when the bundle calls
it and uses it in no other way. Each chunk gets its own copy, so a
function that is exported, compared, constructed, or given properties
would lose its shared identity. Such a chunk now keeps the package
refused.
- During a cycle's initialization, a read of a partner's exported
function is safe only as a plain value read. A call through
esbuild's `(0, binding.fn)()` spelling, `new`, a tagged template, a
member of the function, or passing it to a call can run the function,
so these uses keep SC1016 (Node throws when the function reads a binding
in its temporal dead zone).
- A star re-export of a lazily initialized module into the bundle's
exports (__reExport(main, (init_x(), __toCommonJS(x_exports))))
initializes the module at the statement and adds its names to the
export table after the explicit ones, without default.
- The rewrite keeps the original's lexer answer whenever its export
table holds accessors (Node's lexer stops at the first one).
- An npm alias directory of a selected package ("x-updated":
"npm:x@6.3.0") resolves to that package's JavaScript in both resolvers.
- "use strict" code may read arguments in functions with parameters.
- A comma expression ending in null/undefined coerces its result.
- A void conditional return lowers as control flow.
- A detached method of a local class drops its empty capture list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
A loader `L = () => M ??= NS` over a lazily initialized module's
namespace (optionally through __toESM) is respelled at every `L().name`
as the module's initializer call followed by the export's live read;
loaders whose result escapes as a value stay refused. The canonical
export table keeps a getter it cannot chase as a live accessor, so a
lazy re-export still initializes its module at the first read. An
ES-module importer copies those accessors once when it links the
CommonJS module, in the lexer's order, exactly like Node's loader;
require() keeps reading them live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`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>
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.