diff --git a/packages/compiler/src/frontend/lowering/structural-instances.ts b/packages/compiler/src/frontend/lowering/structural-instances.ts index 0d21c30cb..c321bacd7 100644 --- a/packages/compiler/src/frontend/lowering/structural-instances.ts +++ b/packages/compiler/src/frontend/lowering/structural-instances.ts @@ -249,13 +249,20 @@ export function layoutReturnType( /** The initializer type of a const the argument names when the const kept * that initializer's layout (keepsInitializerLayout): the checker types the - * binding by its annotation, but its value has the initializer's layout. */ + * binding by its annotation, but its value has the initializer's layout. A + * declaration lowers once per function instance, and only some instances + * keep a layout, so the recorded type applies only while it maps to the + * layout of the binding the current lowering resolves. */ function preservedLayoutOf(lowerer: Lowerer, arg: ts.Expression): ts.Type | undefined { let e = arg; while (ts.isParenthesizedExpression(e) || ts.isNonNullExpression(e)) e = e.expression; if (!ts.isIdentifier(e)) return undefined; const symbol = lowerer.resolveValueSymbol(e); - return symbol ? lowerer.preservedLayoutTypes.get(symbol) : undefined; + const recorded = symbol ? lowerer.preservedLayoutTypes.get(symbol) : undefined; + if (recorded === undefined) return undefined; + const binding = lowerer.peekLocal(e)?.type ?? lowerer.globalOf(e)?.type; + const layout = lowerer.mapTypeOf(recorded); + return binding && layout && typeEquals(binding, layout) ? recorded : undefined; } /** Whether a `const` binding typed `declared` keeps the record or array diff --git a/tests/corpus/width-layout-identity.ts b/tests/corpus/width-layout-identity.ts index 7cf5ea72a..fcae6faac 100644 --- a/tests/corpus/width-layout-identity.ts +++ b/tests/corpus/width-layout-identity.ts @@ -53,3 +53,16 @@ const moduleSource: Entry[] = [{ key: "m", hits: 1 }]; const moduleView: Counted[] = moduleSource; track(moduleView, "n"); console.log(moduleView.length, moduleSource.length, moduleView === moduleSource); + +// The same function body lowered for a wider argument and for its own type: +// each lowering passes its own binding on. +function addTwice(list: Counted[], key: string): number { + const alias: Counted[] = list; + track(alias, key); + track(alias, `${key}2`); + return list.length; +} +const wideList: Entry[] = [{ key: "w", hits: 1, note: "n" }]; +const plainList: Counted[] = [{ key: "p", hits: 1 }]; +console.log(addTwice(wideList, "a"), addTwice(plainList, "b"), addTwice(wideList, "c")); +console.log(wideList.length, plainList.length);