A generic arrow declared inside a function compiles to shared module
instances that took the enclosing values they read as trailing
parameters: a copy, so any binding written in the file was refused,
including a counter the arrow itself increments. When one of its
captures is reassigned, the instance is now a lifted function that
receives every capture through a closure environment, and each call site
calls a closure over its own boxes, the representation ordinary closures
use. Writes from the instance, the frame and other closures meet in the
same box, as in Node. Arrows whose captures never change keep the
trailing parameters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A generic arrow or function-expression binding declared inside a function
used to be refused (SC1090) whenever its body read anything from the
enclosing frame, because its monomorphized instances are module functions
with no closure.
Give each instance the frame it needs: every value the binding reads from
the enclosing function becomes a TRAILING parameter of the instance, and
every call site (which sits in the declaring frame or in a closure over it)
passes the current value after the declared arguments. The instance body
binds the same symbol, so it resolves the reference like any parameter.
Sound only while the original binding never changes, so the fence now
covers exactly what a copy cannot stand in for: `this`, `super`,
`new.target`, `arguments`, an enclosing type parameter, an enclosing
generic scope (per-instantiation types), and any binding with a write
anywhere in the file or declared after the binding (temporal dead zone).
Corpus: withClientIdentity/withIdentity, physicalModels (captured Set),
listTwo (captured local function), runTracked (async, call from a nested
closure) and twice (two instances, different capture values). The
generic-value-bindings diagnostics fixture now pins the fences that remain
(a capture the file reassigns, and an enclosing generic scope) instead of
the never-reassigned parameter it used to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5b23e6ccf110c66e96fad472265d7b53db0f342a)
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.
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.
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>
Generic arrow properties and generic methods now have an erased runtime
value, their instance with every type parameter bound to unknown. Calls
through a round trip via any, a function result, an array element or a
class-backed interface call that value and convert the result to the
call's type, as JavaScript does. Direct calls on a known definition keep
their monomorphized path.
Fixes#905
Co-authored-by: Konstantin Mikheev <8560582+konmik@users.noreply.github.com>
- ambientUndefVarRootOf walks calls, `new`, optional chains, assertions, instantiation expressions, and tagged templates, and accepts `declare function` and trap-binding roots: Node erases the declaration, so the root read IS the crash — lowerCall, member reads, assignments, and variable declarations lower the whole statement to the catchable ReferenceError, arguments never evaluate, and the never-initialized binding registers as a trap whose references can never run
- never-read unmappable bindings with value-only initializers and writes emit nothing: Node builds the value and drops it, so `var xs2: typeof Array;` and the write-only `var f2: { <T, U>(x: T, y: U): T }` compile instead of fencing on a type the program never consumes
- null!/as-any bindings whose types keep generic signatures (or intern empty record shapes) register no storage; generic-method calls and member reads through them compile to Node's exact TypeError, nullish-to-nullish flows to nothing, heritage-clause uses decline the family so mixin resolution keeps its fence, and checked-dynamic (`any`) bindings keep their dyn story untouched
- assertion-shaped generic arrows (`< <T>(x: T) => T >((x) => x)`) register like annotated bindings and monomorphize per call; stdlib generic members (Promise.then, Object.defineProperty) take the SC2020 lib fence, and Function.prototype.apply/call/bind on compiled function values name the direct-call spelling
- diagnostics fixtures that used `declare` to conjure unconstructible values now conjure them by cast so each fence family keeps its pin; snapshots re-recorded, the deferred checker-panic diagnostic now flushes at its first reference as designed
- Instantiations carry ts-level bindings alongside the mapped IrTypes: a `K extends keyof T` parameter keys its instance on the bound literal (no cross-literal sharing — the bodies read different fields), and literals forward through generic-to-generic calls.
- `T[K]` inside instance bodies resolves through the bound checker types (mapBoundIndexedAccess via the new resolveTypeParamTs hook), and `o[k]` reads whose key TYPE proves one literal — a keyof-bound parameter or a literal-typed const — lower to static field reads.
- `const h = id` registers the target's GenericFnInfo under the alias's symbol (chains, block scope, pinned values, and calls all resolve like the target's own name); non-const/reassigned aliases fence by name, and unpinned VALUE uses keep their fence at the reference.
- Fixtures 2550 (keyof/pick shapes, forwarding, runtime keys, literal-typed key vars) and 2551 (alias chains, contextual pinning, annotated generic aliases), byte-identical vs Node on both backends.