`x.constructor` on a value typed as EventEmitter or a stream class
stopped at SC2020. The runtime now records each instance's Node class
name: fs streams as ReadStream and WriteStream, child process stdio as
Socket, worker stdio as ReadableWorkerStdio and WritableWorkerStdio, and
Hash/Hmac stream views as Hash and Hmac (zlib streams, the base classes
and program subclasses already did). The name is the one Node's leak
warning and argument-type errors print, so those messages now name the
same class as Node.
`x.constructor.name` reads that name (scr_emitter_class_name).
`x.constructor === C` for EventEmitter or a stream class tests the
instance's own class: C's interval, none of its direct subclasses', and
C's name, as fs, zlib, crypto, child process and worker streams share the
base classes' vtables. `x.constructor` as a value is the class object of
EventEmitter or the stream class the instance belongs to; for instances
of the other runtime classes and of program subclasses it reports SC1090
at run time.
Main split lower-exprs.ts into expression families; the typeof and __dirname changes move to expressions/typeof.ts and expressions/identifiers.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A thrown array reached its catch binding as a type-erased native array:
`console.log(e)` printed `{}` where Node prints the array, `String(e)`
gave "[object Object]", `Array.isArray(e)` was false and a typed view
such as `(e as number[]).length` threw a TypeError. Thrown records
already crossed the catch boundary as checked-dynamic values that keep
their identity; arrays, and unions over records and arrays, now take the
same conversion, so the catch binding is the thrown object.
node:test printed a failing test's thrown non-Error value with String():
`throw { code: 42 }` reported `[object Object]` where Node prints the
value as util.inspect renders it. The failing-tests section now inspects
thrown numbers, booleans and checked-dynamic values (records, arrays,
null and undefined); a thrown string is quoted as before. A thrown class
instance that is not an Error still prints `[object Object]` (the
limitations page says so).
Adds corpus 6341, extends the thrown-values node:test fixture, and
records 6341's cost.
Once generic wrappers compile (#1413), the code behind them is reachable.
This closes the general gaps found there:
- performance.eventLoopUtilization() and monitorEventLoopDelay()'s
IntervalHistogram (enable, disable, reset, percentile and the min, max,
mean, stddev, count and exceeds readings), under the fallback
declarations and @types/node. A new runtime unit, scr_perf_loop.c, links
only for programs that read them (the perfLoop feature): an unref'd
sampling interval records into HdrHistogram-shaped buckets, and
utilization measures active time as the main thread's CPU time since
the loop started.
- Catch bindings narrowed by a user type predicate or an assertion read
their members through the checked-dynamic value.
- `??` with a function default of a different signature takes the
optional callback's own function type and adapts the default.
- Optional calls on interface-typed receivers dispatch like the plain
protocol call.
- `any` parameters of function declarations and spelled function types
are checked-dynamic slots, and a generic instantiated at `any` takes
`unknown` there.
- `'key' in err` on Error receivers answers from the declaring subclasses.
- Object.entries over a generic record resolves
`keyof T extends never ? unknown : T[keyof T]` through the binding.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the conflicts with main's process.features value, the WebCrypto dynamic types, the NODE_COMPAT_VERSION import and the symbol.declarations API. Renumber this branch's corpus programs to 6420-6429, write the new limitations partial one sentence per line, and regenerate the compatibility outputs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lower-exprs.ts had grown to 17.1k lines, and its import block and the big
branches of lowerExprInner were edited by about half of all changes to
it, so concurrent changes conflicted there. Its declarations move, as is,
into files under frontend/lowering/expressions/: identifiers,
property-access, typeof, element-access, binary, relational, narrowing,
updates and array-literals. lowerExprInner's identifier, property-access
and typeof branches become functions of their own in those files, called
from the branch. Helpers move with their only users; helpers shared
across files stay in lower-exprs.ts, which re-exports every moved name it
exported, so other modules' imports are unchanged.
The move changes no behavior: the zero-cost gate compiles the benchmarks,
the self-hosted compiler and the corpus to identical IR, LLVM and
executables.
The net unit drained its poller every time the loop came back to the I/O
station, including the restarts that drain microtasks. I/O that became
ready during the iteration's own callbacks (a loopback connect started by
one of them) was delivered before the check phase, so setImmediate
callbacks queued earlier ran after it; Node delivers it in the next
iteration's poll phase. The poller now drains once per iteration (a full
batch keeps draining), reopening after the check phase. Back-to-back
undici requests now reuse connections like Node, so the fixtures drop
their setImmediate turn between requests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@types/node's fetch declarations (undici-types) spell the Headers methods
and the body mixin as function-typed properties (readonly append: (...) =>
void), where scriptc's own declarations use methods. The receiver call
lowering treated such a standard-library property as a stored callback:
Headers.forEach was refused at build time and append/get/set/... compiled
to a runtime 'is not a function'. Standard-library members now take the
shared receiver dispatch either way.
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>
ScrDynHandleTag listed its tags by name with explicit values, so two
changes that each took the next free value merged without a conflict and
produced two tags with one value; only dyn-handle-tags.test.ts noticed.
#1397 (CRYPTO_KEY) and #1259 (ASYNC_DISPOSABLE_STACK) both took 56.
SCR_DYNH_TAGS is now the one list of tags: X(NAME, value) rows, sorted
by name, generate the enum, a _Static_assert per tag that the value is
below SCR_DYNH_CAPACITY, and scr_dynh_tags_distinct, a never-called
switch with a case per tag. Two tags with one value are a duplicate case
value, so every runtime unit that includes scr_dyn.h fails to compile and
names both tags. The values, the names and the generated code are
unchanged. dyn-handle-tags.test.ts reads the rows.
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.
The bounds guard of a versioned typed-array loop runs before the loop's
initializer, and the stability scan covered the body, condition and
update only. An initializer that rebinds the indexed array
(`for (let i = (a = b, 0); i < n; i++) a[i]`) kept the bounds the guard
proved for the old array: the unchecked loop then read and wrote past a
shorter replacement. The scan now includes the initializer, so a binding
it writes is not invariant and the loop runs checked. Loops whose
initializer leaves the receiver alone version as before.
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>
The lowerer kept the tuple bindings that are only forwarded in one map
for the whole program, keyed by local id. Local ids are unique within
one function only: two wrappers that both bind `arguments` (or a
`const args = arguments as any` alias) declare the same id, so a wrapper
whose tuple exists found the other wrapper's entry, and reading an
element of its `arguments` failed with "forwarded-only 'arguments' read
as a value". Each function's context now keeps its own map.
The corpus program wraps functions with a forwarding-only wrapper and
with one that also reads an element of `arguments`, directly and through
an alias, in both orders.
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).