diff --git a/packages/compiler/src/frontend/lowering/class-protocols.ts b/packages/compiler/src/frontend/lowering/class-protocols.ts index a3f2ffb4d..1108bf0fa 100644 --- a/packages/compiler/src/frontend/lowering/class-protocols.ts +++ b/packages/compiler/src/frontend/lowering/class-protocols.ts @@ -414,6 +414,11 @@ export class ClassProtocols { for (const part of ts.constituentTypes(type)) add(part); return; } + // A Pick or Omit view's values are instances of the class it views. + if (this.pickOfProgramClass(type)) { + add(type.getAliasTypeArguments()[0]!); + return; + } const symbol = type.getSymbol(); if (symbol === undefined) return; if (this.isProgramClassInstance(type)) { @@ -603,6 +608,8 @@ export class ClassProtocols { return element !== undefined && this.carriesIdentity(element, marked); } if (this.isProgramClassInstance(type)) return true; + // A live Pick or Omit view of a program class holds the instance itself. + if (this.pickOfProgramClass(type) && this.usesCheckedIdentity(type)) return true; const symbol = type.getSymbol(); if (symbol === undefined) return false; if (marked.has(symbol)) return true; diff --git a/tests/corpus/6414-pick-class-method-views.ts b/tests/corpus/6414-pick-class-method-views.ts index a2d61c836..26e89a9fe 100644 --- a/tests/corpus/6414-pick-class-method-views.ts +++ b/tests/corpus/6414-pick-class-method-views.ts @@ -37,3 +37,19 @@ void run({ client, name: "ctx" }).then(() => { finish(client); console.log(count(client), client.calls); }); + +// A live view that flows into another structural type keeps the instance: +// reads through the second view see the instance's later writes. +interface Counter { + calls: number; +} +function counted(view: Pick): Counter { + void view.request("/z"); + return view; +} +const second = new ApiClient("https://k"); +const counter = counted(second); +second.calls += 10; +const holder: { counter: Counter } = { counter: counted(second) }; +second.calls = 100; +console.log("counter", counter.calls, holder.counter.calls, second.calls);