From e2567b26617c50eccf59af246f0b771901730dc2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:07:26 +0000 Subject: [PATCH 1/3] Initial plan From 29a603fe07dea74b6b8e0f718b17b915fb8dc4c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:20:46 +0000 Subject: [PATCH 2/3] Expose labeled tuple element declarations Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com> --- packages/typescript/src/api/async/api.ts | 12 ++++++ packages/typescript/src/api/async/types.ts | 8 +++- .../typescript/src/api/proto.generated.ts | 1 + packages/typescript/src/api/sync/api.ts | 10 +++++ packages/typescript/src/api/sync/types.ts | 8 +++- packages/typescript/test/async/api.test.ts | 42 +++++++++++++++++++ packages/typescript/test/sync/api.test.ts | 42 +++++++++++++++++++ tsc/internal/api/proto.go | 7 ++-- tsc/internal/api/session.go | 14 ++++++- 9 files changed, 138 insertions(+), 6 deletions(-) diff --git a/packages/typescript/src/api/async/api.ts b/packages/typescript/src/api/async/api.ts index 35a82de3af8f2..b323cc19cc322 100644 --- a/packages/typescript/src/api/async/api.ts +++ b/packages/typescript/src/api/async/api.ts @@ -22,7 +22,9 @@ import { type Identifier, type IndexSignatureDeclaration, ModifierFlags, + type NamedTupleMember, type Node, + type ParameterDeclaration, type Path, type SourceFile, type SyntaxKind, @@ -724,6 +726,10 @@ class ProjectObjectRegistry { return this.types.get(id); } + createNodeHandle(handle: string): NodeHandle { + return new NodeHandle(handle, this.project); + } + getOrCreateSignature(data: SignatureResponse): Signature { let sig = this.signatures.get(data.id); if (!sig) { @@ -2427,6 +2433,7 @@ class TypeObject implements Type { readonly elementFlags!: readonly ElementFlags[]; readonly fixedLength!: number; readonly readonly!: boolean; + readonly labeledElementDeclarations?: readonly (NodeHandle | undefined)[]; readonly texts!: readonly string[]; readonly objectType!: number; readonly indexType!: number; @@ -2485,6 +2492,11 @@ class TypeObject implements Type { this.fixedLength = data.fixedLength; } if (data.readonly !== undefined) this.readonly = data.readonly; + if (data.labeledElementDeclarations !== undefined) { + this.labeledElementDeclarations = data.labeledElementDeclarations.map(handle => + handle ? objectRegistry.createNodeHandle(handle) : undefined + ); + } if (data.texts !== undefined) this.texts = data.texts; if (data.objectType !== undefined) this.objectType = data.objectType; if (data.indexType !== undefined) this.indexType = data.indexType; diff --git a/packages/typescript/src/api/async/types.ts b/packages/typescript/src/api/async/types.ts index 316484c68ddc8..8df18f408c5b7 100644 --- a/packages/typescript/src/api/async/types.ts +++ b/packages/typescript/src/api/async/types.ts @@ -3,7 +3,11 @@ import type { ElementFlags } from "#enums/elementFlags"; import type { ObjectFlags } from "#enums/objectFlags"; import type { TypeFlags } from "#enums/typeFlags"; import type { TypePredicateKind } from "#enums/typePredicateKind"; -import type { IndexSignatureDeclaration } from "../../ast/ast.ts"; +import type { + IndexSignatureDeclaration, + NamedTupleMember, + ParameterDeclaration, +} from "../../ast/ast.ts"; import type { Diagnostic } from "../proto.ts"; import type { NodeHandle, @@ -205,6 +209,8 @@ export interface TupleType extends InterfaceType { readonly fixedLength: number; /** Whether the tuple is readonly */ readonly readonly: boolean; + /** Declarations providing tuple element names */ + readonly labeledElementDeclarations?: readonly (NodeHandle | undefined)[]; } /** Union or intersection types (TypeFlags.Union | TypeFlags.Intersection) */ diff --git a/packages/typescript/src/api/proto.generated.ts b/packages/typescript/src/api/proto.generated.ts index 63f8d74f81069..b920cda9a7ef3 100644 --- a/packages/typescript/src/api/proto.generated.ts +++ b/packages/typescript/src/api/proto.generated.ts @@ -400,6 +400,7 @@ export interface TypeResponse { elementFlags?: number[]; fixedLength?: number; readonly?: boolean; + labeledElementDeclarations?: string[]; /** IndexedAccessType data */ objectType?: number; indexType?: number; diff --git a/packages/typescript/src/api/sync/api.ts b/packages/typescript/src/api/sync/api.ts index a8712274ab5c1..5da50411058c3 100644 --- a/packages/typescript/src/api/sync/api.ts +++ b/packages/typescript/src/api/sync/api.ts @@ -39,7 +39,9 @@ import { type Identifier, type IndexSignatureDeclaration, ModifierFlags, + type NamedTupleMember, type Node, + type ParameterDeclaration, type Path, type SourceFile, type SyntaxKind, @@ -1212,6 +1214,10 @@ class ProjectObjectRegistry { return this.types.get(id); } + createNodeHandle(handle: string): NodeHandle { + return new NodeHandle(handle, this.project); + } + getOrCreateSignature(data: SignatureResponse): Signature { let sig = this.signatures.get(data.id); if (!sig) { @@ -5245,6 +5251,7 @@ class TypeObject implements Type { readonly elementFlags!: readonly ElementFlags[]; readonly fixedLength!: number; readonly readonly!: boolean; + readonly labeledElementDeclarations?: readonly (NodeHandle | undefined)[]; readonly texts!: readonly string[]; readonly objectType!: number; readonly indexType!: number; @@ -5303,6 +5310,9 @@ class TypeObject implements Type { this.fixedLength = data.fixedLength; } if (data.readonly !== undefined) this.readonly = data.readonly; + if (data.labeledElementDeclarations !== undefined) { + this.labeledElementDeclarations = data.labeledElementDeclarations.map(handle => handle ? objectRegistry.createNodeHandle(handle) : undefined); + } if (data.texts !== undefined) this.texts = data.texts; if (data.objectType !== undefined) this.objectType = data.objectType; if (data.indexType !== undefined) this.indexType = data.indexType; diff --git a/packages/typescript/src/api/sync/types.ts b/packages/typescript/src/api/sync/types.ts index 43a7b1720db22..8a6987adf024b 100644 --- a/packages/typescript/src/api/sync/types.ts +++ b/packages/typescript/src/api/sync/types.ts @@ -16,7 +16,11 @@ import type { ElementFlags } from "#enums/elementFlags"; import type { ObjectFlags } from "#enums/objectFlags"; import type { TypeFlags } from "#enums/typeFlags"; import type { TypePredicateKind } from "#enums/typePredicateKind"; -import type { IndexSignatureDeclaration } from "../../ast/ast.ts"; +import type { + IndexSignatureDeclaration, + NamedTupleMember, + ParameterDeclaration, +} from "../../ast/ast.ts"; import type { Diagnostic } from "../proto.ts"; import type { NodeHandle, @@ -287,6 +291,8 @@ export interface TupleType extends InterfaceType { readonly fixedLength: number; /** Whether the tuple is readonly */ readonly readonly: boolean; + /** Declarations providing tuple element names */ + readonly labeledElementDeclarations?: readonly (NodeHandle | undefined)[]; } /** Union or intersection types (TypeFlags.Union | TypeFlags.Intersection) */ diff --git a/packages/typescript/test/async/api.test.ts b/packages/typescript/test/async/api.test.ts index b52a3b2fcf988..6f0db6774f0ff 100644 --- a/packages/typescript/test/async/api.test.ts +++ b/packages/typescript/test/async/api.test.ts @@ -3058,6 +3058,48 @@ array([]); await api.close(); } }); + + test("tuple targets expose labeled element declarations", async () => { + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": ` +export function gh1449(a: T): T { + return a; +} +`, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const sourceFile = await project.program.getSourceFile("/src/main.ts"); + assert.ok(sourceFile); + const functionDeclaration = sourceFile.statements.find(isFunctionDeclaration); + assert.ok(functionDeclaration); + const constraint = functionDeclaration.typeParameters?.[0].constraint; + assert.ok(constraint); + + const type = await project.checker.getTypeFromTypeNode(constraint); + assert.ok(type.isTupleType()); + const target = await type.getTarget(); + const declarations = target.labeledElementDeclarations; + assert.ok(declarations); + assert.equal(declarations.length, 2); + + const names: string[] = []; + for (const handle of declarations) { + assert.ok(handle); + assert.equal(handle.kind, SyntaxKind.NamedTupleMember); + const declaration = await handle.resolve(); + assert.ok(declaration); + assert.ok(isIdentifier(declaration.name)); + names.push(declaration.name.text); + } + assert.deepEqual(names, ["foo", "bar"]); + } + finally { + await api.close(); + } + }); }); describe("Checker - intrinsic type getters", () => { diff --git a/packages/typescript/test/sync/api.test.ts b/packages/typescript/test/sync/api.test.ts index d5f12c2ca57f8..846b9052109c0 100644 --- a/packages/typescript/test/sync/api.test.ts +++ b/packages/typescript/test/sync/api.test.ts @@ -2974,6 +2974,48 @@ array([]); api.close(); } }); + + test("tuple targets expose labeled element declarations", () => { + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": ` +export function gh1449(a: T): T { + return a; +} +`, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const sourceFile = project.program.getSourceFile("/src/main.ts"); + assert.ok(sourceFile); + const functionDeclaration = sourceFile.statements.find(isFunctionDeclaration); + assert.ok(functionDeclaration); + const constraint = functionDeclaration.typeParameters?.[0].constraint; + assert.ok(constraint); + + const type = project.checker.getTypeFromTypeNode(constraint); + assert.ok(type.isTupleType()); + const target = type.getTarget(); + const declarations = target.labeledElementDeclarations; + assert.ok(declarations); + assert.equal(declarations.length, 2); + + const names: string[] = []; + for (const handle of declarations) { + assert.ok(handle); + assert.equal(handle.kind, SyntaxKind.NamedTupleMember); + const declaration = handle.resolve(); + assert.ok(declaration); + assert.ok(isIdentifier(declaration.name)); + names.push(declaration.name.text); + } + assert.deepEqual(names, ["foo", "bar"]); + } + finally { + api.close(); + } + }); }); describe("Checker - intrinsic type getters", () => { diff --git a/tsc/internal/api/proto.go b/tsc/internal/api/proto.go index d5cb20b175f9d..c93a0a5b1ac9f 100644 --- a/tsc/internal/api/proto.go +++ b/tsc/internal/api/proto.go @@ -867,9 +867,10 @@ type TypeResponse struct { LocalTypeParameters []TypeID `json:"localTypeParameters,omitempty"` // TupleType data - ElementFlags []checker.ElementFlags `json:"elementFlags,omitempty"` - FixedLength *int `json:"fixedLength,omitempty"` - TupleReadonly *bool `json:"readonly,omitempty"` + ElementFlags []checker.ElementFlags `json:"elementFlags,omitempty"` + FixedLength *int `json:"fixedLength,omitempty"` + TupleReadonly *bool `json:"readonly,omitempty"` + LabeledElementDeclarations []NodeHandle `json:"labeledElementDeclarations,omitempty"` // IndexedAccessType data ObjectType TypeID `json:"objectType,omitzero"` diff --git a/tsc/internal/api/session.go b/tsc/internal/api/session.go index 26893c729229d..3cc6a23a0babd 100644 --- a/tsc/internal/api/session.go +++ b/tsc/internal/api/session.go @@ -212,7 +212,19 @@ func (sd *snapshotData) newTypeResponse(projectID ProjectID, t *checker.Type) *T if t == nil { return nil } - return newTypeResponse(t, sd.registerType(projectID, t)) + resp := newTypeResponse(t, sd.registerType(projectID, t)) + if checker.IsTupleTypeTarget(t) { + elementInfos := t.AsTupleType().ElementInfos() + for i := range elementInfos { + if declaration := elementInfos[i].LabeledDeclaration(); declaration != nil { + if resp.LabeledElementDeclarations == nil { + resp.LabeledElementDeclarations = make([]NodeHandle, len(elementInfos)) + } + resp.LabeledElementDeclarations[i] = sd.nodeHandleFrom(declaration) + } + } + } + return resp } func (sd *snapshotData) registerType(projectID ProjectID, t *checker.Type) TypeID { From 4ef60d87ad0b860963e03a6d9d08176d2ebe8196 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:28:16 +0000 Subject: [PATCH 3/3] Format tuple declaration mapping Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com> --- packages/typescript/src/api/async/api.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/typescript/src/api/async/api.ts b/packages/typescript/src/api/async/api.ts index b323cc19cc322..dc265882bfbca 100644 --- a/packages/typescript/src/api/async/api.ts +++ b/packages/typescript/src/api/async/api.ts @@ -2493,9 +2493,7 @@ class TypeObject implements Type { } if (data.readonly !== undefined) this.readonly = data.readonly; if (data.labeledElementDeclarations !== undefined) { - this.labeledElementDeclarations = data.labeledElementDeclarations.map(handle => - handle ? objectRegistry.createNodeHandle(handle) : undefined - ); + this.labeledElementDeclarations = data.labeledElementDeclarations.map(handle => handle ? objectRegistry.createNodeHandle(handle) : undefined); } if (data.texts !== undefined) this.texts = data.texts; if (data.objectType !== undefined) this.objectType = data.objectType;