diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index a2eda839a..1769e8562 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -1041,11 +1041,12 @@ function unifySignatureBindings( iAliasArgs.length === dAliasArgs.length ) { if (tsBindings && declared !== inst) { - let pairs = lowerer.boundAliasInstances.get(tsBindings); - if (!pairs) lowerer.boundAliasInstances.set(tsBindings, (pairs = new Map())); + let entries = lowerer.boundAliasInstances.get(declared); + if (!entries) lowerer.boundAliasInstances.set(declared, (entries = [])); // A body forwarding its own alias (`inner(g)` returning `Catalog` inside an instance) resolves through the enclosing one. - if (!pairs.has(declared)) pairs.set(declared, lowerer.boundAliasTsResolver(inst) ?? inst); + if (!entries.some((entry) => entry.tsBindings === tsBindings)) + entries.push({ tsBindings, instance: lowerer.boundAliasTsResolver(inst) ?? inst }); } dAliasArgs.forEach((da, i) => { const ia = iAliasArgs[i]; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 216851c9c..c77741997 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -2233,16 +2233,19 @@ export class Lowerer { * the body needs — indexed accesses (`T[K]` needs K's literal key) and * keyed record reads (`o[k]` where k's type is a literal-bound K). */ typeParamTsBindings: Map | null = null; - /** Generic alias references of a call-keyed instance's declared signature - * (`Catalog`), each paired with the same alias as the call's - * resolved signature instantiates it (`Catalog<{ g1: … }, "acme">`), - * keyed by the instance's ts-level bindings (typeParamTsBindings). The - * checker keeps the alias symbolic inside the generic body and offers no - * way to instantiate it with the bound arguments, but alias - * instantiations are interned: the body's `x as Catalog` is the - * very type the signature declares, so the pair recorded during binding - * unification is that re-instantiation. */ - readonly boundAliasInstances = new Map, Map>(); + /** Generic alias references of call-keyed instances' declared signatures + * (`Catalog`), each with the same alias as every call's resolved + * signature instantiates it (`Catalog<{ g1: … }, "acme">`), together with + * that instance's ts-level bindings map (typeParamTsBindings), which every + * scope that lowers the instance installs. The checker keeps the alias + * symbolic inside the generic body and offers no way to instantiate it + * with the bound arguments, but alias instantiations are interned: the + * body's `x as Catalog` is the very type the signature declares, so + * the pair recorded during unification is that re-instantiation. */ + readonly boundAliasInstances = new Map< + ts.Type, + { tsBindings: Map; instance: ts.Type }[] + >(); /** Non-null while an IMPLICIT-ANY instance body lowers (npm-static JS; * see generic-functions.ts): bound param symbol → * the call site's checker type, consulted by typeOf for identifier @@ -9563,10 +9566,15 @@ export class Lowerer { /** The concrete instantiation of a generic alias reference (`Catalog`) the current call-keyed instance's signature declares - * (boundAliasInstances). Inert outside generic function instantiation. */ + * (boundAliasInstances), or null when its signature never instantiated + * that reference. Inert outside generic function instantiation. */ readonly boundAliasTsResolver = (t: ts.Type): ts.Type | null => { if (!this.typeParamTsBindings || t.getAliasTypeArguments().length === 0) return null; - return this.boundAliasInstances.get(this.typeParamTsBindings)?.get(t) ?? null; + const tsBindings = this.typeParamTsBindings; + return ( + this.boundAliasInstances.get(t)?.find((entry) => entry.tsBindings === tsBindings)?.instance ?? + null + ); }; irTypeOf(node: ts.Node): IrType {