Node's constructNT reads _construct on the construction tick, so an
assignment after the constructor and before that tick replaces a scheduled
construct hook. The assignment was dropped, so the original hook ran. It
now lowers to stream.replaceConstruct, which swaps the hook while its tick
has not run and has no effect after it or on a stream without a hook.
Runtime decodeStrings and defaultEncoding values are read into locals
before the constructor evaluates the other option members, while Node
evaluates the members in order. A runtime value after a member whose value
has effects (a call or an assignment) now refuses instead of reading a
value that member could still change.
Lowering collects a function-local class with the body declaring it, so
a local subclass whose function lowers after its base's constructor was
missing from new.target's dispatch, which then answered with the base for
its instances. The refusal no longer depends on that order: the program's
function-local classes are found once, and one that extends the
constructor's class, directly or through other classes its extends
clauses name, refuses new.target as a collected one does.
A constructor's new.target dispatch skipped generic class families, so a
generic subclass instantiated only in a body lowered later answered with
the base's name, and `new.target === Base` held for its instances. A
family now has its own arm, tested by its instanceof interval, which holds
every instantiation: `new.target.name` reads the family's name and
comparisons with a program class are false. Under an instantiation, the
generic subclasses of its family are candidates too. `new.target` as a
value refuses generic subclasses, which have no common class value.
`new.target.name` reads a class's own static `name` field, which replaces
its name (a subclass has its own name); a static method `name` or a
non-string field is refused.
`C.call(...)` through a class value typed as `Base` may run a subclass's own
static `call`, which Node calls instead of the class constructor. The
constructor-call lowering now applies only when no class the receiver can
hold declares a static `call` or `apply`: the class and its bases (any
static member of that name, including accessors, fields without
initializers and computed names), and, for a receiver that may hold a
subclass, every subclass. Otherwise the call keeps its existing
diagnostic.
Since Iterable<T> values compile (#1875), two diagnostics fixtures changed:
- fromentries-aliased-entries.ts compiles and prints what Node prints:
the entries' one array stays shared by both fields. It moves to the run
corpus as 7440-fromentries-aliased-entries.ts.
- iterator-slot-fences.ts no longer reports its Iterable<number>
parameter (SC2020) and the call through it (SC2004). That part leaves
the fixture, which keeps the class-iterator fence; corpus 7380-7383
cover Iterable values.
Inside a class constructor new.target is the class of the instance under
construction. A compiled new allocates the instance with its concrete class
before the constructor chain runs, and the class hierarchy is closed, so
new.target lowers to a static dispatch over the constructor's class and its
descendants (deepest instanceof first). new.target.name reads the exact
class's name and new.target === C is an exact-class test, without a class
value, so subclasses may construct with other signatures. new.target as a
class value requires every subclass to share the constructor's signature.
Plain functions and function-local subclasses keep explicit refusals.
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>
A function expression stored in a function type with more parameters
folds `arguments.length` to that type's parameter count, the count every
call through the slot passes. A named expression that calls itself
through its own name passes only its declared parameters there, so no
constant answers both calls. Such a read now reports SC1090 with the
reason, and expressions that do not refer to their own name keep the
fold.
`.filter` with a written type predicate (`(p): p is Cat => ...`, or a
guard function passed as a value such as `filter(isDog)`) was refused with
SC1090, because TypeScript does not check the predicate. An inferred
predicate that keeps several union arms (`v => v !== undefined` over
`(string | number | undefined)[]`) was refused too.
The filter loop now keeps the receiver's element type, and the result
converts to the predicate's type element by element
(filter-predicates.ts):
- a union element to one of its arms, or to a union of some of its arms,
by its tag;
- a checked-dynamic element by dynCheck;
- a class instance to a subclass by its class;
- a tuple or record to one with the same fields, field by field.
An element the predicate's type does not describe, kept because the
predicate returned true for it, throws a catchable TypeError: "a kept
element is not a 'Cat' (a type predicate returned true for a value of
another type)". The diagnostics fixture filter-narrow.ts, which pinned
the three refusals, compiles now; corpus 7007 covers its cases.
Fixes#1680.
Generic arrow and function-expression bindings compile to shared
instances, and their declarations emit no store. Code that can run before
the declaration then diverged from Node.
- Inside a function, a nested function lowered before the declaration
reads the binding through a TDZ box that nothing initialized. That
covers closures above the declaration and hoisted function
declarations. Such a read threw a ReferenceError even after the
declaration ran. The declaration now stores the instance its declared
signature pins into that box.
- At module scope, calls and pinned value reads went straight to the
instances even when a hoisted function or an earlier closure ran them
before the declaration. A binding with such a use now has a flag that
its declaration sets.
- Uses that can run first check the flag.
- A let or const throws Node's ReferenceError before the arguments
run.
- A var runs the arguments and then throws "is not a function".
- Uses that follow the declaration stay direct calls: later statements,
closures created after it, its own initializer, and function
declarations that only such code calls.
A var binding inside a function that is called from a place that can run
before its declaration would need a per-call flag shared with its
closures. That shape now fails with SC1090 instead of calling the
instance.
The runtime call reads the cert and key options before the SNICallback,
so a cert or key initializer after the callback that reassigned the
callback's binding changed the callback the server used, and the server
could serve the wrong certificate. When SNICallback comes before a cert
or key entry, the callback is now saved just before the next of them is
evaluated. With the callback read in order, a callback expression with
side effects lowers instead of being refused.
tls.createServer({ cert, key, SNICallback }) hands each ClientHello's
servername to the callback and serves the SecureContext it answers,
synchronously or later, or the default cert and key when it answers none.
An error answer closes that connection. This uses the SNI handling that
http2.createSecureServer already has. A callback expression with side
effects, or SNICallback combined with ALPNProtocols, minVersion, maxVersion
or the other options the runtime walk reads, fails with SC2020.
A tls.connect or https.request client whose peer closes or resets the
connection before the handshake completes now fails with Node's ECONNRESET
"Client network socket disconnected before secure TLS connection was
established" instead of closing without an error.
Refs #1438
Co-authored-by: saboursepideh <25549001+saboursepideh@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>
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.
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>
A typed array or Buffer in a compiled program is always a built-in kind
(programs cannot subclass them, add own properties or change their
prototypes), so Node 24's prototype chains for them are known exactly.
- A read of a member the type does not declare (in JavaScript, or under
@ts-ignore) answers undefined when that chain lacks the name, as Node's
GetV does: the base64 and hex feature probes (`typeof bytes.toHex`) that
libraries such as @noble/hashes make. Names Node has, including the
Buffer prototype's members on a Uint8Array that may hold a Buffer, keep
the fence.
- `"name" in bytes` with a literal member name answers from the same
chains, and `bytes.BYTES_PER_ELEMENT` is the element size.
- Array destructuring assignment into typed-array elements
(`[high[i], low[i]] = [h, l]`) evaluates the receiver, the index and the
value in order and writes the element as `b[i] = v` does.
Refs #1437
Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
Math.max and Math.min with a spread and other arguments (`Math.max(...xs,
5)`) pack their arguments, in order and with each spread flattened, into
one number[] that the runtime folds, as Math.hypot already does. The fold
keeps NaN poisoning, -0 below +0 and the empty fold's infinities, and the
arguments evaluate left to right before it. The refusal hint now names
the supported forms.
Fixes#1621
Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
A generic arrow or function expression bound inside a function or method
compiles when it uses nothing from the enclosing function's frame: no
`this`, `super`, `arguments` or `new.target`, and no local, parameter or
type parameter of an enclosing function. Its instantiations are module
functions, as for a module-scope binding, and may call the binding itself
and read module-scope bindings. Instances of bindings in functions and
blocks take the declaration's position in their names, so two functions
may declare bindings of the same name. Bindings that use the enclosing
frame keep their SC1090 fence.
Fixes#1624
Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
Another module can import an exported const (`export const` or an
`export { name }` specifier) and reach its elements, so a spread of it
is not their only reference. Those spreads keep the element-layout
refusal instead of copying the elements.
A const initialized by an array literal and spread once can still run
that spread more than once: in a loop body, loop test or update, a
function, or a class field initializer. Each run inserts the same
elements in JavaScript, so copying them into the destination's layout
would break identity. Those spreads keep the element-layout refusal.