Skip to content

Commit f52473a

Browse files
Expose labeled tuple element declarations in the TypeScript 7 API (#64109)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com>
1 parent ed2b6a9 commit f52473a

9 files changed

Lines changed: 136 additions & 6 deletions

File tree

packages/typescript/src/api/async/api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import {
2222
type Identifier,
2323
type IndexSignatureDeclaration,
2424
ModifierFlags,
25+
type NamedTupleMember,
2526
type Node,
27+
type ParameterDeclaration,
2628
type Path,
2729
type SourceFile,
2830
type SyntaxKind,
@@ -724,6 +726,10 @@ class ProjectObjectRegistry {
724726
return this.types.get(id);
725727
}
726728

729+
createNodeHandle<T extends Node>(handle: string): NodeHandle<T> {
730+
return new NodeHandle<T>(handle, this.project);
731+
}
732+
727733
getOrCreateSignature(data: SignatureResponse): Signature {
728734
let sig = this.signatures.get(data.id);
729735
if (!sig) {
@@ -2427,6 +2433,7 @@ class TypeObject implements Type {
24272433
readonly elementFlags!: readonly ElementFlags[];
24282434
readonly fixedLength!: number;
24292435
readonly readonly!: boolean;
2436+
readonly labeledElementDeclarations?: readonly (NodeHandle<NamedTupleMember | ParameterDeclaration> | undefined)[];
24302437
readonly texts!: readonly string[];
24312438
readonly objectType!: number;
24322439
readonly indexType!: number;
@@ -2485,6 +2492,9 @@ class TypeObject implements Type {
24852492
this.fixedLength = data.fixedLength;
24862493
}
24872494
if (data.readonly !== undefined) this.readonly = data.readonly;
2495+
if (data.labeledElementDeclarations !== undefined) {
2496+
this.labeledElementDeclarations = data.labeledElementDeclarations.map(handle => handle ? objectRegistry.createNodeHandle<NamedTupleMember | ParameterDeclaration>(handle) : undefined);
2497+
}
24882498
if (data.texts !== undefined) this.texts = data.texts;
24892499
if (data.objectType !== undefined) this.objectType = data.objectType;
24902500
if (data.indexType !== undefined) this.indexType = data.indexType;

packages/typescript/src/api/async/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import type { ElementFlags } from "#enums/elementFlags";
33
import type { ObjectFlags } from "#enums/objectFlags";
44
import type { TypeFlags } from "#enums/typeFlags";
55
import type { TypePredicateKind } from "#enums/typePredicateKind";
6-
import type { IndexSignatureDeclaration } from "../../ast/ast.ts";
6+
import type {
7+
IndexSignatureDeclaration,
8+
NamedTupleMember,
9+
ParameterDeclaration,
10+
} from "../../ast/ast.ts";
711
import type { Diagnostic } from "../proto.ts";
812
import type {
913
NodeHandle,
@@ -205,6 +209,8 @@ export interface TupleType extends InterfaceType {
205209
readonly fixedLength: number;
206210
/** Whether the tuple is readonly */
207211
readonly readonly: boolean;
212+
/** Declarations providing tuple element names */
213+
readonly labeledElementDeclarations?: readonly (NodeHandle<NamedTupleMember | ParameterDeclaration> | undefined)[];
208214
}
209215

210216
/** Union or intersection types (TypeFlags.Union | TypeFlags.Intersection) */

packages/typescript/src/api/proto.generated.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ export interface TypeResponse {
400400
elementFlags?: number[];
401401
fixedLength?: number;
402402
readonly?: boolean;
403+
labeledElementDeclarations?: string[];
403404
/** IndexedAccessType data */
404405
objectType?: number;
405406
indexType?: number;

packages/typescript/src/api/sync/api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ import {
3939
type Identifier,
4040
type IndexSignatureDeclaration,
4141
ModifierFlags,
42+
type NamedTupleMember,
4243
type Node,
44+
type ParameterDeclaration,
4345
type Path,
4446
type SourceFile,
4547
type SyntaxKind,
@@ -1212,6 +1214,10 @@ class ProjectObjectRegistry {
12121214
return this.types.get(id);
12131215
}
12141216

1217+
createNodeHandle<T extends Node>(handle: string): NodeHandle<T> {
1218+
return new NodeHandle<T>(handle, this.project);
1219+
}
1220+
12151221
getOrCreateSignature(data: SignatureResponse): Signature {
12161222
let sig = this.signatures.get(data.id);
12171223
if (!sig) {
@@ -5245,6 +5251,7 @@ class TypeObject implements Type {
52455251
readonly elementFlags!: readonly ElementFlags[];
52465252
readonly fixedLength!: number;
52475253
readonly readonly!: boolean;
5254+
readonly labeledElementDeclarations?: readonly (NodeHandle<NamedTupleMember | ParameterDeclaration> | undefined)[];
52485255
readonly texts!: readonly string[];
52495256
readonly objectType!: number;
52505257
readonly indexType!: number;
@@ -5303,6 +5310,9 @@ class TypeObject implements Type {
53035310
this.fixedLength = data.fixedLength;
53045311
}
53055312
if (data.readonly !== undefined) this.readonly = data.readonly;
5313+
if (data.labeledElementDeclarations !== undefined) {
5314+
this.labeledElementDeclarations = data.labeledElementDeclarations.map(handle => handle ? objectRegistry.createNodeHandle<NamedTupleMember | ParameterDeclaration>(handle) : undefined);
5315+
}
53065316
if (data.texts !== undefined) this.texts = data.texts;
53075317
if (data.objectType !== undefined) this.objectType = data.objectType;
53085318
if (data.indexType !== undefined) this.indexType = data.indexType;

packages/typescript/src/api/sync/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import type { ElementFlags } from "#enums/elementFlags";
1616
import type { ObjectFlags } from "#enums/objectFlags";
1717
import type { TypeFlags } from "#enums/typeFlags";
1818
import type { TypePredicateKind } from "#enums/typePredicateKind";
19-
import type { IndexSignatureDeclaration } from "../../ast/ast.ts";
19+
import type {
20+
IndexSignatureDeclaration,
21+
NamedTupleMember,
22+
ParameterDeclaration,
23+
} from "../../ast/ast.ts";
2024
import type { Diagnostic } from "../proto.ts";
2125
import type {
2226
NodeHandle,
@@ -287,6 +291,8 @@ export interface TupleType extends InterfaceType {
287291
readonly fixedLength: number;
288292
/** Whether the tuple is readonly */
289293
readonly readonly: boolean;
294+
/** Declarations providing tuple element names */
295+
readonly labeledElementDeclarations?: readonly (NodeHandle<NamedTupleMember | ParameterDeclaration> | undefined)[];
290296
}
291297

292298
/** Union or intersection types (TypeFlags.Union | TypeFlags.Intersection) */

packages/typescript/test/async/api.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3058,6 +3058,48 @@ array([]);
30583058
await api.close();
30593059
}
30603060
});
3061+
3062+
test("tuple targets expose labeled element declarations", async () => {
3063+
const api = spawnAPI({
3064+
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
3065+
"/src/main.ts": `
3066+
export function gh1449<T extends [foo: any, bar?: any]>(a: T): T {
3067+
return a;
3068+
}
3069+
`,
3070+
});
3071+
try {
3072+
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
3073+
const project = snapshot.getProject("/tsconfig.json")!;
3074+
const sourceFile = await project.program.getSourceFile("/src/main.ts");
3075+
assert.ok(sourceFile);
3076+
const functionDeclaration = sourceFile.statements.find(isFunctionDeclaration);
3077+
assert.ok(functionDeclaration);
3078+
const constraint = functionDeclaration.typeParameters?.[0].constraint;
3079+
assert.ok(constraint);
3080+
3081+
const type = await project.checker.getTypeFromTypeNode(constraint);
3082+
assert.ok(type.isTupleType());
3083+
const target = await type.getTarget();
3084+
const declarations = target.labeledElementDeclarations;
3085+
assert.ok(declarations);
3086+
assert.equal(declarations.length, 2);
3087+
3088+
const names: string[] = [];
3089+
for (const handle of declarations) {
3090+
assert.ok(handle);
3091+
assert.equal(handle.kind, SyntaxKind.NamedTupleMember);
3092+
const declaration = await handle.resolve();
3093+
assert.ok(declaration);
3094+
assert.ok(isIdentifier(declaration.name));
3095+
names.push(declaration.name.text);
3096+
}
3097+
assert.deepEqual(names, ["foo", "bar"]);
3098+
}
3099+
finally {
3100+
await api.close();
3101+
}
3102+
});
30613103
});
30623104

30633105
describe("Checker - intrinsic type getters", () => {

packages/typescript/test/sync/api.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2974,6 +2974,48 @@ array([]);
29742974
api.close();
29752975
}
29762976
});
2977+
2978+
test("tuple targets expose labeled element declarations", () => {
2979+
const api = spawnAPI({
2980+
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
2981+
"/src/main.ts": `
2982+
export function gh1449<T extends [foo: any, bar?: any]>(a: T): T {
2983+
return a;
2984+
}
2985+
`,
2986+
});
2987+
try {
2988+
const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
2989+
const project = snapshot.getProject("/tsconfig.json")!;
2990+
const sourceFile = project.program.getSourceFile("/src/main.ts");
2991+
assert.ok(sourceFile);
2992+
const functionDeclaration = sourceFile.statements.find(isFunctionDeclaration);
2993+
assert.ok(functionDeclaration);
2994+
const constraint = functionDeclaration.typeParameters?.[0].constraint;
2995+
assert.ok(constraint);
2996+
2997+
const type = project.checker.getTypeFromTypeNode(constraint);
2998+
assert.ok(type.isTupleType());
2999+
const target = type.getTarget();
3000+
const declarations = target.labeledElementDeclarations;
3001+
assert.ok(declarations);
3002+
assert.equal(declarations.length, 2);
3003+
3004+
const names: string[] = [];
3005+
for (const handle of declarations) {
3006+
assert.ok(handle);
3007+
assert.equal(handle.kind, SyntaxKind.NamedTupleMember);
3008+
const declaration = handle.resolve();
3009+
assert.ok(declaration);
3010+
assert.ok(isIdentifier(declaration.name));
3011+
names.push(declaration.name.text);
3012+
}
3013+
assert.deepEqual(names, ["foo", "bar"]);
3014+
}
3015+
finally {
3016+
api.close();
3017+
}
3018+
});
29773019
});
29783020

29793021
describe("Checker - intrinsic type getters", () => {

tsc/internal/api/proto.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -867,9 +867,10 @@ type TypeResponse struct {
867867
LocalTypeParameters []TypeID `json:"localTypeParameters,omitempty"`
868868

869869
// TupleType data
870-
ElementFlags []checker.ElementFlags `json:"elementFlags,omitempty"`
871-
FixedLength *int `json:"fixedLength,omitempty"`
872-
TupleReadonly *bool `json:"readonly,omitempty"`
870+
ElementFlags []checker.ElementFlags `json:"elementFlags,omitempty"`
871+
FixedLength *int `json:"fixedLength,omitempty"`
872+
TupleReadonly *bool `json:"readonly,omitempty"`
873+
LabeledElementDeclarations []NodeHandle `json:"labeledElementDeclarations,omitempty"`
873874

874875
// IndexedAccessType data
875876
ObjectType TypeID `json:"objectType,omitzero"`

tsc/internal/api/session.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,19 @@ func (sd *snapshotData) newTypeResponse(projectID ProjectID, t *checker.Type) *T
212212
if t == nil {
213213
return nil
214214
}
215-
return newTypeResponse(t, sd.registerType(projectID, t))
215+
resp := newTypeResponse(t, sd.registerType(projectID, t))
216+
if checker.IsTupleTypeTarget(t) {
217+
elementInfos := t.AsTupleType().ElementInfos()
218+
for i := range elementInfos {
219+
if declaration := elementInfos[i].LabeledDeclaration(); declaration != nil {
220+
if resp.LabeledElementDeclarations == nil {
221+
resp.LabeledElementDeclarations = make([]NodeHandle, len(elementInfos))
222+
}
223+
resp.LabeledElementDeclarations[i] = sd.nodeHandleFrom(declaration)
224+
}
225+
}
226+
}
227+
return resp
216228
}
217229

218230
func (sd *snapshotData) registerType(projectID ProjectID, t *checker.Type) TypeID {

0 commit comments

Comments
 (0)