From f4a7c4629d0a4f6a8a2bed1432b2e6fbd5bf589c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 01:45:28 -0500 Subject: [PATCH] Keep a live Pick or Omit class view's identity when it flows into another structural type A Pick or Omit view of a program class that holds the instance (its view exposes a method) now carries the class's identity in the flow analysis, and its values count as instances of that class. Assigning such a view to another structural type keeps the instance, so reads through the second type see the instance's later writes instead of a copy's. --- .../src/frontend/lowering/class-protocols.ts | 7 +++++++ tests/corpus/6414-pick-class-method-views.ts | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) 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);