[api] Fix crash accessing tuple type reference data (#64080) · microsoft/TypeScript@444de10 · GitHub
Skip to content

Commit 444de10

Browse files
[api] Fix crash accessing tuple type reference data (#64080)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 54d81cf commit 444de10

11 files changed

Lines changed: 210 additions & 77 deletions

File tree

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

Lines changed: 25 additions & 11 deletions

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,10 @@ export interface Type {
107107
isBooleanLiteralType(): this is BooleanLiteralType;
108108
/** Whether this type is a type reference */
109109
isTypeReference(): this is TypeReference;
110-
/** Whether this type is a tuple type */
111-
isTupleType(): this is TupleType;
110+
/** Whether this type is a tuple type reference */
111+
isTupleType(): this is TupleTypeReference;
112+
/** Whether this type owns tuple metadata */
113+
isTupleTypeTarget(): this is TupleType;
112114
/** Whether this type is an index type (`keyof T`) */
113115
isIndexType(): this is IndexType;
114116
/** Whether this type is an indexed access type (`T[K]`) */
@@ -177,6 +179,12 @@ export interface TypeReference extends ObjectType {
177179
getTarget(): Promise<Type>;
178180
}
179181

182+
/** References to tuple types */
183+
export interface TupleTypeReference extends TypeReference {
184+
/** Get the tuple type that describes this reference's shape */
185+
getTarget(): Promise<TupleType>;
186+
}
187+
180188
/** Interface types — classes and interfaces (ObjectFlags.ClassOrInterface) */
181189
export interface InterfaceType extends TypeReference {
182190
/** Get all type parameters (outer + local, excluding thisType) */
@@ -187,8 +195,10 @@ export interface InterfaceType extends TypeReference {
187195
getLocalTypeParameters(): Promise<readonly TypeParameter[]>;
188196
}
189197

190-
/** Tuple types (ObjectFlags.Tuple) */
198+
/** Tuple type targets (ObjectFlags.Tuple) */
191199
export interface TupleType extends InterfaceType {
200+
/** Get this tuple target */
201+
getTarget(): Promise<TupleType>;
192202
/** Per-element flags (Required, Optional, Rest, Variadic) */
193203
readonly elementFlags: readonly ElementFlags[];
194204
/** Number of initial required or optional elements */

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ export interface APIMethodInfo {
123123
getJsDocTags: APIMethod<CheckerSymbolParams, JSDocTagInfo[] | null>;
124124
getDocumentationComment: APIMethod<CheckerSymbolParams, string>;
125125
isArrayType: APIMethod<CheckerTypeParams, boolean>;
126-
isTupleType: APIMethod<CheckerTypeParams, boolean>;
127126
isReadonlySymbol: APIMethod<CheckerSymbolParams, boolean>;
128127
getReferencesToSymbolInFile: APIMethod<GetReferencesToSymbolInFileParams, string[]>;
129128
getReferencedSymbolsForNode: APIMethod<GetReferencedSymbolsForNodeParams, ReferencedSymbolEntry[] | null>;
@@ -385,6 +384,7 @@ export interface TypeResponse {
385384
id: number;
386385
flags: number;
387386
objectFlags?: number;
387+
isTupleType?: boolean;
388388
/**
389389
* Value is literal type data. BigInt literals are encoded as signed decimal
390390
* strings because JSON cannot represent bigint; absent values are null.
@@ -1023,7 +1023,6 @@ export interface BatchRequest {
10231023
| "isArrayType"
10241024
| "isContextSensitive"
10251025
| "isReadonlySymbol"
1026-
| "isTupleType"
10271026
| "isTypeAssignableTo"
10281027
| "parseCommandLine"
10291028
| "parseConfigFile"
@@ -1171,7 +1170,6 @@ export interface BatchResponse {
11711170
| "isArrayType"
11721171
| "isContextSensitive"
11731172
| "isReadonlySymbol"
1174-
| "isTupleType"
11751173
| "isTypeAssignableTo"
11761174
| "parseCommandLine"
11771175
| "parseConfigFile"

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

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ import type {
147147
TemplateLiteralType,
148148
ThisTypePredicate,
149149
TupleType,
150+
TupleTypeReference,
150151
Type,
151152
TypeParameter,
152153
TypePredicate,
@@ -211,6 +212,7 @@ export type {
211212
TimingAccumulators,
212213
TimingInfo,
213214
TupleType,
215+
TupleTypeReference,
214216
Type,
215217
TypeAcquisition,
216218
TypeParameter,
@@ -4038,18 +4040,27 @@ export class Checker {
40384040
owner,
40394041
"isTupleType",
40404042
function (type: Type): boolean {
4041-
return owner.client.apiRequest("isTupleType", {
4042-
snapshot: owner.snapshotId,
4043-
project: owner.project.id,
4044-
type: type.id,
4045-
});
4043+
return type.isTupleType();
40464044
},
40474045
function* (type: Type): Generator<ProtocolRequest, boolean, ProtocolResponse["result"]> {
4048-
return yield* apiRequest("isTupleType", {
4049-
snapshot: owner.snapshotId,
4050-
project: owner.project.id,
4051-
type: type.id,
4052-
});
4046+
return type.isTupleType();
4047+
},
4048+
);
4049+
}
4050+
4051+
get isTupleTypeTarget(): {
4052+
(type: Type): boolean;
4053+
gen(type: Type): Generator<ProtocolRequest, boolean, ProtocolResponse["result"]>;
4054+
} {
4055+
const owner = this;
4056+
return cacheGeneratorMethod(
4057+
owner,
4058+
"isTupleTypeTarget",
4059+
function (type: Type): boolean {
4060+
return type.isTupleTypeTarget();
4061+
},
4062+
function* (type: Type): Generator<ProtocolRequest, boolean, ProtocolResponse["result"]> {
4063+
return type.isTupleTypeTarget();
40534064
},
40544065
);
40554066
}
@@ -5225,6 +5236,7 @@ class TypeObject implements Type {
52255236
readonly freshType!: number;
52265237
readonly regularType!: number;
52275238
readonly target!: number;
5239+
private readonly tupleType: boolean;
52285240
readonly typeParameters!: readonly number[];
52295241
readonly outerTypeParameters!: readonly number[];
52305242
readonly localTypeParameters!: readonly number[];
@@ -5280,13 +5292,16 @@ class TypeObject implements Type {
52805292
if (data.freshType !== undefined) this.freshType = data.freshType;
52815293
if (data.regularType !== undefined) this.regularType = data.regularType;
52825294
if (data.target !== undefined) this.target = data.target;
5295+
this.tupleType = data.isTupleType ?? false;
52835296
this.typeParameters = data.typeParameters ?? [];
52845297
this.outerTypeParameters = data.outerTypeParameters ?? [];
52855298
this.localTypeParameters = data.localTypeParameters ?? [];
52865299
this.aliasTypeArguments = data.aliasTypeArguments ?? [];
52875300
if (data.aliasSymbol !== undefined) this.aliasSymbol = data.aliasSymbol;
5288-
if (data.elementFlags !== undefined) this.elementFlags = data.elementFlags;
5289-
if (data.fixedLength !== undefined) this.fixedLength = data.fixedLength;
5301+
if (data.fixedLength !== undefined) {
5302+
this.elementFlags = data.elementFlags ?? [];
5303+
this.fixedLength = data.fixedLength;
5304+
}
52905305
if (data.readonly !== undefined) this.readonly = data.readonly;
52915306
if (data.texts !== undefined) this.texts = data.texts;
52925307
if (data.objectType !== undefined) this.objectType = data.objectType;
@@ -6034,8 +6049,12 @@ class TypeObject implements Type {
60346049
return isTypeReference(this);
60356050
}
60366051

6037-
isTupleType(): this is TupleType {
6038-
return isTupleType(this);
6052+
isTupleType(): this is TupleTypeReference {
6053+
return this.tupleType;
6054+
}
6055+
6056+
isTupleTypeTarget(): this is TupleType {
6057+
return this.fixedLength !== undefined;
60396058
}
60406059

60416060
isIndexType(): this is IndexType {
@@ -6121,8 +6140,12 @@ export function isTypeReference(type: Type): type is TypeReference {
61216140
return isObjectType(type) && (type.objectFlags & ObjectFlags.Reference) !== 0;
61226141
}
61236142

6124-
export function isTupleType(type: Type): type is TupleType {
6125-
return isObjectType(type) && (type.objectFlags & ObjectFlags.Tuple) !== 0;
6143+
export function isTupleType(type: Type): type is TupleTypeReference {
6144+
return type.isTupleType();
6145+
}
6146+
6147+
export function isTupleTypeTarget(type: Type): type is TupleType {
6148+
return type.isTupleTypeTarget();
61266149
}
61276150

61286151
export function isIndexType(type: Type): type is IndexType {

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,10 @@ export interface Type {
165165
isBooleanLiteralType(): this is BooleanLiteralType;
166166
/** Whether this type is a type reference */
167167
isTypeReference(): this is TypeReference;
168-
/** Whether this type is a tuple type */
169-
isTupleType(): this is TupleType;
168+
/** Whether this type is a tuple type reference */
169+
isTupleType(): this is TupleTypeReference;
170+
/** Whether this type owns tuple metadata */
171+
isTupleTypeTarget(): this is TupleType;
170172
/** Whether this type is an index type (`keyof T`) */
171173
isIndexType(): this is IndexType;
172174
/** Whether this type is an indexed access type (`T[K]`) */
@@ -244,6 +246,15 @@ export interface TypeReference extends ObjectType {
244246
};
245247
}
246248

249+
/** References to tuple types */
250+
export interface TupleTypeReference extends TypeReference {
251+
/** Get the tuple type that describes this reference's shape */
252+
getTarget: {
253+
(): TupleType;
254+
gen(): Generator<ProtocolRequest, TupleType, ProtocolResponse["result"]>;
255+
};
256+
}
257+
247258
/** Interface types — classes and interfaces (ObjectFlags.ClassOrInterface) */
248259
export interface InterfaceType extends TypeReference {
249260
/** Get all type parameters (outer + local, excluding thisType) */
@@ -263,8 +274,13 @@ export interface InterfaceType extends TypeReference {
263274
};
264275
}
265276

266-
/** Tuple types (ObjectFlags.Tuple) */
277+
/** Tuple type targets (ObjectFlags.Tuple) */
267278
export interface TupleType extends InterfaceType {
279+
/** Get this tuple target */
280+
getTarget: {
281+
(): TupleType;
282+
gen(): Generator<ProtocolRequest, TupleType, ProtocolResponse["result"]>;
283+
};
268284
/** Per-element flags (Required, Optional, Rest, Variadic) */
269285
readonly elementFlags: readonly ElementFlags[];
270286
/** Number of initial required or optional elements */

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

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3000,15 +3000,59 @@ export const tuple: readonly [number, string?, ...boolean[]] = [1];
30003000
}
30013001
});
30023002

3003-
test("TupleType properties", async () => {
3004-
const { type, api } = await getTypeAtName(spawnAPI(typeFiles), "tuple:");
3003+
test("tuple metadata is owned by tuple targets", async () => {
3004+
const api = spawnAPI({
3005+
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
3006+
"/src/main.ts": `
3007+
declare function empty(value: readonly []): void;
3008+
declare function nonempty(value: readonly [number]): void;
3009+
declare function array(value: readonly number[]): void;
3010+
empty([]);
3011+
nonempty([1]);
3012+
array([]);
3013+
`,
3014+
});
30053015
try {
3006-
assert.ok(type.flags & TypeFlags.Object);
3007-
const ref = type as TypeReference;
3008-
assert.ok(ref.objectFlags & ObjectFlags.Reference);
3009-
const target = await ref.getTarget();
3010-
assert.ok(target);
3011-
assert.ok(target.flags & TypeFlags.Object);
3016+
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
3017+
const project = snapshot.getProject("/tsconfig.json")!;
3018+
const sourceFile = await project.program.getSourceFile("/src/main.ts");
3019+
assert.ok(sourceFile);
3020+
3021+
const arrayLiterals: Node[] = [];
3022+
sourceFile.forEachChild(function visit(node) {
3023+
if (node.kind === SyntaxKind.ArrayLiteralExpression) {
3024+
arrayLiterals.push(node);
3025+
}
3026+
node.forEachChild(visit);
3027+
});
3028+
assert.equal(arrayLiterals.length, 3);
3029+
3030+
for (const [index, expectedFixedLength] of [0, 1].entries()) {
3031+
const type = await project.checker.getTypeAtLocation(arrayLiterals[index]);
3032+
assert.equal(await project.checker.isTupleType(type), true);
3033+
assert.equal(await project.checker.isTupleTypeTarget(type), false);
3034+
assert.equal(type.isTupleType(), true);
3035+
assert.equal(type.isTupleTypeTarget(), false);
3036+
assert.ok(type.isTupleType());
3037+
assert.equal(Reflect.get(type, "fixedLength"), undefined);
3038+
3039+
const target = await type.getTarget();
3040+
assert.ok(target.objectFlags & ObjectFlags.Tuple);
3041+
assert.equal(await project.checker.isTupleType(target), true);
3042+
assert.equal(await project.checker.isTupleTypeTarget(target), true);
3043+
assert.equal(target.isTupleType(), true);
3044+
assert.equal(target.isTupleTypeTarget(), true);
3045+
assert.ok(target.isTupleTypeTarget());
3046+
assert.equal(target.fixedLength, expectedFixedLength);
3047+
assert.equal(target.elementFlags.length, expectedFixedLength);
3048+
assert.equal(target.readonly, false);
3049+
}
3050+
3051+
const arrayType = await project.checker.getTypeAtLocation(arrayLiterals[2]);
3052+
assert.equal(await project.checker.isTupleType(arrayType), false);
3053+
assert.equal(await project.checker.isTupleTypeTarget(arrayType), false);
3054+
assert.equal(arrayType.isTupleType(), false);
3055+
assert.equal(arrayType.isTupleTypeTarget(), false);
30123056
}
30133057
finally {
30143058
await api.close();

‎packages/typescript/test/sync/api-generators.test.ts‎

Lines changed: 1 addition & 0 deletions

0 commit comments

Comments
 (0)