From 2dcd4368f48593e7d11107984c6c907fb995bd24 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 28 Aug 2026 10:43:28 -0700 Subject: [PATCH 1/4] [api] Clear up the difference between tuple types and tuple type references --- packages/typescript/src/api/async/api.ts | 37 +++++++--- packages/typescript/src/api/async/types.ts | 10 ++- .../typescript/src/api/proto.generated.ts | 4 +- packages/typescript/src/api/sync/api.ts | 56 ++++++++++---- packages/typescript/src/api/sync/types.ts | 10 ++- packages/typescript/test/async/api.test.ts | 74 +++++++++++++++---- packages/typescript/test/sync/api.test.ts | 74 +++++++++++++++---- tsc/internal/api/proto.go | 17 ++--- tsc/internal/api/session.go | 18 ----- tsc/internal/checker/exports.go | 11 +++ 10 files changed, 223 insertions(+), 88 deletions(-) diff --git a/packages/typescript/src/api/async/api.ts b/packages/typescript/src/api/async/api.ts index ef6d7fb7ef10b..e97fb790a03b0 100644 --- a/packages/typescript/src/api/async/api.ts +++ b/packages/typescript/src/api/async/api.ts @@ -125,6 +125,7 @@ import type { TemplateLiteralType, ThisTypePredicate, TupleType, + TupleTypeReference, Type, TypeParameter, TypePredicate, @@ -187,6 +188,7 @@ export type { TimingAccumulators, TimingInfo, TupleType, + TupleTypeReference, Type, TypeAcquisition, TypeParameter, @@ -1820,12 +1822,12 @@ export class Checker { }); } + async isTupleTypeReference(type: Type): Promise { + return type.isTupleTypeReference(); + } + async isTupleType(type: Type): Promise { - return this.client.apiRequest("isTupleType", { - snapshot: this.snapshotId, - project: this.project.id, - type: type.id, - }); + return type.isTupleType(); } /** @@ -2334,6 +2336,7 @@ class TypeObject implements Type { readonly freshType!: number; readonly regularType!: number; readonly target!: number; + private readonly tupleTypeReference: boolean; readonly typeParameters!: readonly number[]; readonly outerTypeParameters!: readonly number[]; readonly localTypeParameters!: readonly number[]; @@ -2389,14 +2392,20 @@ class TypeObject implements Type { if (data.freshType !== undefined) this.freshType = data.freshType; if (data.regularType !== undefined) this.regularType = data.regularType; if (data.target !== undefined) this.target = data.target; + this.tupleTypeReference = data.isTupleTypeReference ?? false; this.typeParameters = data.typeParameters ?? []; this.outerTypeParameters = data.outerTypeParameters ?? []; this.localTypeParameters = data.localTypeParameters ?? []; this.aliasTypeArguments = data.aliasTypeArguments ?? []; if (data.aliasSymbol !== undefined) this.aliasSymbol = data.aliasSymbol; - if (data.elementFlags !== undefined) this.elementFlags = data.elementFlags; - if (data.fixedLength !== undefined) this.fixedLength = data.fixedLength; - if (data.readonly !== undefined) this.readonly = data.readonly; + if (data.fixedLength !== undefined) { + if (data.readonly === undefined) { + throw new Error("Tuple type response is missing readonly metadata"); + } + this.elementFlags = data.elementFlags ?? []; + this.fixedLength = data.fixedLength; + this.readonly = data.readonly; + } if (data.texts !== undefined) this.texts = data.texts; if (data.objectType !== undefined) this.objectType = data.objectType; if (data.indexType !== undefined) this.indexType = data.indexType; @@ -2660,8 +2669,12 @@ class TypeObject implements Type { return isTypeReference(this); } + isTupleTypeReference(): this is TupleTypeReference { + return this.tupleTypeReference; + } + isTupleType(): this is TupleType { - return isTupleType(this); + return this.fixedLength !== undefined; } isIndexType(): this is IndexType { @@ -2747,8 +2760,12 @@ export function isTypeReference(type: Type): type is TypeReference { return isObjectType(type) && (type.objectFlags & ObjectFlags.Reference) !== 0; } +export function isTupleTypeReference(type: Type): type is TupleTypeReference { + return type.isTupleTypeReference(); +} + export function isTupleType(type: Type): type is TupleType { - return isObjectType(type) && (type.objectFlags & ObjectFlags.Tuple) !== 0; + return type.isTupleType(); } export function isIndexType(type: Type): type is IndexType { diff --git a/packages/typescript/src/api/async/types.ts b/packages/typescript/src/api/async/types.ts index 79c392aed6b63..efba5d5d327af 100644 --- a/packages/typescript/src/api/async/types.ts +++ b/packages/typescript/src/api/async/types.ts @@ -107,7 +107,9 @@ export interface Type { isBooleanLiteralType(): this is BooleanLiteralType; /** Whether this type is a type reference */ isTypeReference(): this is TypeReference; - /** Whether this type is a tuple type */ + /** Whether this type is a reference to a tuple type */ + isTupleTypeReference(): this is TupleTypeReference; + /** Whether this type owns tuple metadata */ isTupleType(): this is TupleType; /** Whether this type is an index type (`keyof T`) */ isIndexType(): this is IndexType; @@ -177,6 +179,12 @@ export interface TypeReference extends ObjectType { getTarget(): Promise; } +/** References to tuple types */ +export interface TupleTypeReference extends TypeReference { + /** Get the tuple type that describes this reference's shape */ + getTarget(): Promise; +} + /** Interface types — classes and interfaces (ObjectFlags.ClassOrInterface) */ export interface InterfaceType extends TypeReference { /** Get all type parameters (outer + local, excluding thisType) */ diff --git a/packages/typescript/src/api/proto.generated.ts b/packages/typescript/src/api/proto.generated.ts index 6528b59b0f3f9..d202326a03e19 100644 --- a/packages/typescript/src/api/proto.generated.ts +++ b/packages/typescript/src/api/proto.generated.ts @@ -115,7 +115,6 @@ export interface APIMethodInfo { getJsDocTags: APIMethod; getDocumentationComment: APIMethod; isArrayType: APIMethod; - isTupleType: APIMethod; isReadonlySymbol: APIMethod; getReferencesToSymbolInFile: APIMethod; getReferencedSymbolsForNode: APIMethod; @@ -365,6 +364,7 @@ export interface TypeResponse { id: number; flags: number; objectFlags?: number; + isTupleTypeReference?: boolean; /** * Value is literal type data. BigInt literals are encoded as signed decimal * strings because JSON cannot represent bigint; absent values are null. @@ -1002,7 +1002,6 @@ export interface BatchRequest { | "isArrayType" | "isContextSensitive" | "isReadonlySymbol" - | "isTupleType" | "isTypeAssignableTo" | "parseCommandLine" | "parseConfigFile" @@ -1149,7 +1148,6 @@ export interface BatchResponse { | "isArrayType" | "isContextSensitive" | "isReadonlySymbol" - | "isTupleType" | "isTypeAssignableTo" | "parseCommandLine" | "parseConfigFile" diff --git a/packages/typescript/src/api/sync/api.ts b/packages/typescript/src/api/sync/api.ts index b92a3fa26fb72..49d1be3ac53e7 100644 --- a/packages/typescript/src/api/sync/api.ts +++ b/packages/typescript/src/api/sync/api.ts @@ -142,6 +142,7 @@ import type { TemplateLiteralType, ThisTypePredicate, TupleType, + TupleTypeReference, Type, TypeParameter, TypePredicate, @@ -204,6 +205,7 @@ export type { TimingAccumulators, TimingInfo, TupleType, + TupleTypeReference, Type, TypeAcquisition, TypeParameter, @@ -3892,6 +3894,23 @@ export class Checker { ); } + get isTupleTypeReference(): { + (type: Type): boolean; + gen(type: Type): Generator; + } { + const owner = this; + return cacheGeneratorMethod( + owner, + "isTupleTypeReference", + function (type: Type): boolean { + return type.isTupleTypeReference(); + }, + function* (type: Type): Generator { + return type.isTupleTypeReference(); + }, + ); + } + get isTupleType(): { (type: Type): boolean; gen(type: Type): Generator; @@ -3901,18 +3920,10 @@ export class Checker { owner, "isTupleType", function (type: Type): boolean { - return owner.client.apiRequest("isTupleType", { - snapshot: owner.snapshotId, - project: owner.project.id, - type: type.id, - }); + return type.isTupleType(); }, function* (type: Type): Generator { - return yield* apiRequest("isTupleType", { - snapshot: owner.snapshotId, - project: owner.project.id, - type: type.id, - }); + return type.isTupleType(); }, ); } @@ -5088,6 +5099,7 @@ class TypeObject implements Type { readonly freshType!: number; readonly regularType!: number; readonly target!: number; + private readonly tupleTypeReference: boolean; readonly typeParameters!: readonly number[]; readonly outerTypeParameters!: readonly number[]; readonly localTypeParameters!: readonly number[]; @@ -5143,14 +5155,20 @@ class TypeObject implements Type { if (data.freshType !== undefined) this.freshType = data.freshType; if (data.regularType !== undefined) this.regularType = data.regularType; if (data.target !== undefined) this.target = data.target; + this.tupleTypeReference = data.isTupleTypeReference ?? false; this.typeParameters = data.typeParameters ?? []; this.outerTypeParameters = data.outerTypeParameters ?? []; this.localTypeParameters = data.localTypeParameters ?? []; this.aliasTypeArguments = data.aliasTypeArguments ?? []; if (data.aliasSymbol !== undefined) this.aliasSymbol = data.aliasSymbol; - if (data.elementFlags !== undefined) this.elementFlags = data.elementFlags; - if (data.fixedLength !== undefined) this.fixedLength = data.fixedLength; - if (data.readonly !== undefined) this.readonly = data.readonly; + if (data.fixedLength !== undefined) { + if (data.readonly === undefined) { + throw new Error("Tuple type response is missing readonly metadata"); + } + this.elementFlags = data.elementFlags ?? []; + this.fixedLength = data.fixedLength; + this.readonly = data.readonly; + } if (data.texts !== undefined) this.texts = data.texts; if (data.objectType !== undefined) this.objectType = data.objectType; if (data.indexType !== undefined) this.indexType = data.indexType; @@ -5897,8 +5915,12 @@ class TypeObject implements Type { return isTypeReference(this); } + isTupleTypeReference(): this is TupleTypeReference { + return this.tupleTypeReference; + } + isTupleType(): this is TupleType { - return isTupleType(this); + return this.fixedLength !== undefined; } isIndexType(): this is IndexType { @@ -5984,8 +6006,12 @@ export function isTypeReference(type: Type): type is TypeReference { return isObjectType(type) && (type.objectFlags & ObjectFlags.Reference) !== 0; } +export function isTupleTypeReference(type: Type): type is TupleTypeReference { + return type.isTupleTypeReference(); +} + export function isTupleType(type: Type): type is TupleType { - return isObjectType(type) && (type.objectFlags & ObjectFlags.Tuple) !== 0; + return type.isTupleType(); } export function isIndexType(type: Type): type is IndexType { diff --git a/packages/typescript/src/api/sync/types.ts b/packages/typescript/src/api/sync/types.ts index 43a650ef67621..3b4d99f0edccb 100644 --- a/packages/typescript/src/api/sync/types.ts +++ b/packages/typescript/src/api/sync/types.ts @@ -165,7 +165,9 @@ export interface Type { isBooleanLiteralType(): this is BooleanLiteralType; /** Whether this type is a type reference */ isTypeReference(): this is TypeReference; - /** Whether this type is a tuple type */ + /** Whether this type is a reference to a tuple type */ + isTupleTypeReference(): this is TupleTypeReference; + /** Whether this type owns tuple metadata */ isTupleType(): this is TupleType; /** Whether this type is an index type (`keyof T`) */ isIndexType(): this is IndexType; @@ -244,6 +246,12 @@ export interface TypeReference extends ObjectType { }; } +/** References to tuple types */ +export interface TupleTypeReference extends TypeReference { + /** Get the tuple type that describes this reference's shape */ + getTarget(): TupleType; +} + /** Interface types — classes and interfaces (ObjectFlags.ClassOrInterface) */ export interface InterfaceType extends TypeReference { /** Get all type parameters (outer + local, excluding thisType) */ diff --git a/packages/typescript/test/async/api.test.ts b/packages/typescript/test/async/api.test.ts index b40091c90d6c9..ccaa20ebd602c 100644 --- a/packages/typescript/test/async/api.test.ts +++ b/packages/typescript/test/async/api.test.ts @@ -2726,15 +2726,59 @@ export const tuple: readonly [number, string?, ...boolean[]] = [1]; } }); - test("TupleType properties", async () => { - const { type, api } = await getTypeAtName(spawnAPI(typeFiles), "tuple:"); + test("tuple metadata is owned by tuple targets", async () => { + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": ` +declare function empty(value: readonly []): void; +declare function nonempty(value: readonly [number]): void; +declare function array(value: readonly number[]): void; +empty([]); +nonempty([1]); +array([]); +`, + }); try { - assert.ok(type.flags & TypeFlags.Object); - const ref = type as TypeReference; - assert.ok(ref.objectFlags & ObjectFlags.Reference); - const target = await ref.getTarget(); - assert.ok(target); - assert.ok(target.flags & TypeFlags.Object); + 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 arrayLiterals: Node[] = []; + sourceFile.forEachChild(function visit(node) { + if (node.kind === SyntaxKind.ArrayLiteralExpression) { + arrayLiterals.push(node); + } + node.forEachChild(visit); + }); + assert.equal(arrayLiterals.length, 3); + + for (const [index, expectedFixedLength] of [0, 1].entries()) { + const type = await project.checker.getTypeAtLocation(arrayLiterals[index]); + assert.equal(await project.checker.isTupleTypeReference(type), true); + assert.equal(await project.checker.isTupleType(type), false); + assert.equal(type.isTupleTypeReference(), true); + assert.equal(type.isTupleType(), false); + assert.ok(type.isTupleTypeReference()); + assert.equal(Reflect.get(type, "fixedLength"), undefined); + + const target = await type.getTarget(); + assert.ok(target.objectFlags & ObjectFlags.Tuple); + assert.equal(await project.checker.isTupleTypeReference(target), true); + assert.equal(await project.checker.isTupleType(target), true); + assert.equal(target.isTupleTypeReference(), true); + assert.equal(target.isTupleType(), true); + assert.ok(target.isTupleType()); + assert.equal(target.fixedLength, expectedFixedLength); + assert.equal(target.elementFlags.length, expectedFixedLength); + assert.equal(target.readonly, false); + } + + const arrayType = await project.checker.getTypeAtLocation(arrayLiterals[2]); + assert.equal(await project.checker.isTupleTypeReference(arrayType), false); + assert.equal(await project.checker.isTupleType(arrayType), false); + assert.equal(arrayType.isTupleTypeReference(), false); + assert.equal(arrayType.isTupleType(), false); } finally { await api.close(); @@ -3271,7 +3315,7 @@ describe("readFile callback semantics", () => { }); }); -describe("Checker - isArrayType / isTupleType", () => { +describe("Checker - isArrayType / isTupleTypeReference", () => { test("number[] is array, not tuple", async () => { const api = spawnAPI({ "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), @@ -3287,7 +3331,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), true); - assert.equal(await project.checker.isTupleType(type), false); + assert.equal(await project.checker.isTupleTypeReference(type), false); } finally { await api.close(); @@ -3309,7 +3353,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), true); - assert.equal(await project.checker.isTupleType(type), false); + assert.equal(await project.checker.isTupleTypeReference(type), false); } finally { await api.close(); @@ -3331,7 +3375,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), true); - assert.equal(await project.checker.isTupleType(type), false); + assert.equal(await project.checker.isTupleTypeReference(type), false); } finally { await api.close(); @@ -3353,7 +3397,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), false); - assert.equal(await project.checker.isTupleType(type), true); + assert.equal(await project.checker.isTupleTypeReference(type), true); } finally { await api.close(); @@ -3375,7 +3419,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), false); - assert.equal(await project.checker.isTupleType(type), true); + assert.equal(await project.checker.isTupleTypeReference(type), true); } finally { await api.close(); @@ -3397,7 +3441,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), false); - assert.equal(await project.checker.isTupleType(type), false); + assert.equal(await project.checker.isTupleTypeReference(type), false); } finally { await api.close(); diff --git a/packages/typescript/test/sync/api.test.ts b/packages/typescript/test/sync/api.test.ts index d534bc20a6e4e..91943155616b4 100644 --- a/packages/typescript/test/sync/api.test.ts +++ b/packages/typescript/test/sync/api.test.ts @@ -2642,15 +2642,59 @@ export const tuple: readonly [number, string?, ...boolean[]] = [1]; } }); - test("TupleType properties", () => { - const { type, api } = getTypeAtName(spawnAPI(typeFiles), "tuple:"); + test("tuple metadata is owned by tuple targets", () => { + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": ` +declare function empty(value: readonly []): void; +declare function nonempty(value: readonly [number]): void; +declare function array(value: readonly number[]): void; +empty([]); +nonempty([1]); +array([]); +`, + }); try { - assert.ok(type.flags & TypeFlags.Object); - const ref = type as TypeReference; - assert.ok(ref.objectFlags & ObjectFlags.Reference); - const target = ref.getTarget(); - assert.ok(target); - assert.ok(target.flags & TypeFlags.Object); + 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 arrayLiterals: Node[] = []; + sourceFile.forEachChild(function visit(node) { + if (node.kind === SyntaxKind.ArrayLiteralExpression) { + arrayLiterals.push(node); + } + node.forEachChild(visit); + }); + assert.equal(arrayLiterals.length, 3); + + for (const [index, expectedFixedLength] of [0, 1].entries()) { + const type = project.checker.getTypeAtLocation(arrayLiterals[index]); + assert.equal(project.checker.isTupleTypeReference(type), true); + assert.equal(project.checker.isTupleType(type), false); + assert.equal(type.isTupleTypeReference(), true); + assert.equal(type.isTupleType(), false); + assert.ok(type.isTupleTypeReference()); + assert.equal(Reflect.get(type, "fixedLength"), undefined); + + const target = type.getTarget(); + assert.ok(target.objectFlags & ObjectFlags.Tuple); + assert.equal(project.checker.isTupleTypeReference(target), true); + assert.equal(project.checker.isTupleType(target), true); + assert.equal(target.isTupleTypeReference(), true); + assert.equal(target.isTupleType(), true); + assert.ok(target.isTupleType()); + assert.equal(target.fixedLength, expectedFixedLength); + assert.equal(target.elementFlags.length, expectedFixedLength); + assert.equal(target.readonly, false); + } + + const arrayType = project.checker.getTypeAtLocation(arrayLiterals[2]); + assert.equal(project.checker.isTupleTypeReference(arrayType), false); + assert.equal(project.checker.isTupleType(arrayType), false); + assert.equal(arrayType.isTupleTypeReference(), false); + assert.equal(arrayType.isTupleType(), false); } finally { api.close(); @@ -3187,7 +3231,7 @@ describe("readFile callback semantics", () => { }); }); -describe("Checker - isArrayType / isTupleType", () => { +describe("Checker - isArrayType / isTupleTypeReference", () => { test("number[] is array, not tuple", () => { const api = spawnAPI({ "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), @@ -3203,7 +3247,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), true); - assert.equal(project.checker.isTupleType(type), false); + assert.equal(project.checker.isTupleTypeReference(type), false); } finally { api.close(); @@ -3225,7 +3269,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), true); - assert.equal(project.checker.isTupleType(type), false); + assert.equal(project.checker.isTupleTypeReference(type), false); } finally { api.close(); @@ -3247,7 +3291,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), true); - assert.equal(project.checker.isTupleType(type), false); + assert.equal(project.checker.isTupleTypeReference(type), false); } finally { api.close(); @@ -3269,7 +3313,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), false); - assert.equal(project.checker.isTupleType(type), true); + assert.equal(project.checker.isTupleTypeReference(type), true); } finally { api.close(); @@ -3291,7 +3335,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), false); - assert.equal(project.checker.isTupleType(type), true); + assert.equal(project.checker.isTupleTypeReference(type), true); } finally { api.close(); @@ -3313,7 +3357,7 @@ describe("Checker - isArrayType / isTupleType", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), false); - assert.equal(project.checker.isTupleType(type), false); + assert.equal(project.checker.isTupleTypeReference(type), false); } finally { api.close(); diff --git a/tsc/internal/api/proto.go b/tsc/internal/api/proto.go index 84b3673425fce..7224a753761e7 100644 --- a/tsc/internal/api/proto.go +++ b/tsc/internal/api/proto.go @@ -176,7 +176,6 @@ const ( MethodGetJSDocTags Method = "getJsDocTags" MethodGetDocumentationComment Method = "getDocumentationComment" MethodIsArrayType Method = "isArrayType" - MethodIsTupleType Method = "isTupleType" MethodIsReadonlySymbol Method = "isReadonlySymbol" // Reference methods @@ -516,7 +515,6 @@ var unmarshalers = map[Method]func([]byte) (any, error){ MethodGetJSDocTags: unmarshallerFor[CheckerSymbolParams], MethodGetDocumentationComment: unmarshallerFor[CheckerSymbolParams], MethodIsArrayType: unmarshallerFor[CheckerTypeParams], - MethodIsTupleType: unmarshallerFor[CheckerTypeParams], MethodIsReadonlySymbol: unmarshallerFor[CheckerSymbolParams], MethodGetReferencesToSymbolInFile: unmarshallerFor[GetReferencesToSymbolInFileParams], MethodGetReferencedSymbolsForNode: unmarshallerFor[GetReferencedSymbolsForNodeParams], @@ -826,9 +824,10 @@ type GetTypesOfSymbolsParams struct { } type TypeResponse struct { - Id TypeID `json:"id"` - Flags uint32 `json:"flags"` - ObjectFlags uint32 `json:"objectFlags,omitempty"` + Id TypeID `json:"id"` + Flags uint32 `json:"flags"` + ObjectFlags uint32 `json:"objectFlags,omitempty"` + IsTupleTypeReference bool `json:"isTupleTypeReference,omitempty"` // Value is literal type data. BigInt literals are encoded as signed decimal // strings because JSON cannot represent bigint; absent values are null. @@ -911,19 +910,17 @@ func newTypeResponse(t *checker.Type, id TypeID) *TypeResponse { } case flags&checker.TypeFlagsObject != 0: resp.ObjectFlags = uint32(t.ObjectFlags()) + resp.IsTupleTypeReference = checker.IsTupleTypeReference(t) objectFlags := t.ObjectFlags() if objectFlags&checker.ObjectFlagsReference != 0 { - var ref *checker.TypeReference - if objectFlags&checker.ObjectFlagsTuple != 0 { + ref := t.AsTypeReference() + if checker.IsTupleTarget(t) && ref.Target() == t { tuple := t.AsTupleType() - ref = tuple.AsTypeReference() resp.ElementFlags = tuple.ElementFlags() fixedLen := tuple.FixedLength() resp.FixedLength = &fixedLen isReadonly := tuple.IsReadonly() resp.TupleReadonly = &isReadonly - } else { - ref = t.AsTypeReference() } if ref.Target() != nil { resp.Target = TypeHandle(ref.Target()) diff --git a/tsc/internal/api/session.go b/tsc/internal/api/session.go index 03c28a6d665e1..dcbb317a07ea6 100644 --- a/tsc/internal/api/session.go +++ b/tsc/internal/api/session.go @@ -822,8 +822,6 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetDocumentationComment(ctx, parsed.(*CheckerSymbolParams)) case string(MethodIsArrayType): return s.handleIsArrayType(ctx, parsed.(*CheckerTypeParams)) - case string(MethodIsTupleType): - return s.handleIsTupleType(ctx, parsed.(*CheckerTypeParams)) case string(MethodIsReadonlySymbol): return s.handleIsReadonlySymbol(ctx, parsed.(*CheckerSymbolParams)) case string(MethodGetAnyType): @@ -3005,22 +3003,6 @@ func (s *Session) handleIsArrayType(ctx context.Context, params *CheckerTypePara return setup.checker.IsArrayType(t), nil } -// handleIsTupleType returns whether a type is a tuple type. -func (s *Session) handleIsTupleType(ctx context.Context, params *CheckerTypeParams) (bool, error) { - setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) - if err != nil { - return false, err - } - defer setup.done() - - t, err := setup.resolveTypeHandle(params.Type) - if err != nil { - return false, err - } - - return checker.IsTupleType(t), nil -} - // handleIsReadonlySymbol returns whether a symbol is a readonly symbol. func (s *Session) handleIsReadonlySymbol(ctx context.Context, params *CheckerSymbolParams) (bool, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) diff --git a/tsc/internal/checker/exports.go b/tsc/internal/checker/exports.go index f58d4a3ab6050..fd79860b086c7 100644 --- a/tsc/internal/checker/exports.go +++ b/tsc/internal/checker/exports.go @@ -225,6 +225,17 @@ func IsTupleType(t *Type) bool { return isTupleType(t) } +// IsTupleTypeReference reports whether the provided type is a type reference +// whose target is a tuple type. +func IsTupleTypeReference(t *Type) bool { + return isTupleType(t) +} + +// IsTupleTarget reports whether the provided type owns tuple metadata. +func IsTupleTarget(t *Type) bool { + return t != nil && t.objectFlags&ObjectFlagsTuple != 0 +} + func (c *Checker) IsArrayType(t *Type) bool { return c.isArrayType(t) } From 9df1613c5c689cf3829bb5839abf7819e84aaef6 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 28 Aug 2026 10:53:46 -0700 Subject: [PATCH 2/4] Adapt tuple reference API to generator-based sync methods Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/typescript/src/api/sync/types.ts | 5 ++++- packages/typescript/test/sync/api-generators.test.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/typescript/src/api/sync/types.ts b/packages/typescript/src/api/sync/types.ts index 3b4d99f0edccb..55f9afdfcd3e1 100644 --- a/packages/typescript/src/api/sync/types.ts +++ b/packages/typescript/src/api/sync/types.ts @@ -249,7 +249,10 @@ export interface TypeReference extends ObjectType { /** References to tuple types */ export interface TupleTypeReference extends TypeReference { /** Get the tuple type that describes this reference's shape */ - getTarget(): TupleType; + getTarget: { + (): TupleType; + gen(): Generator; + }; } /** Interface types — classes and interfaces (ObjectFlags.ClassOrInterface) */ diff --git a/packages/typescript/test/sync/api-generators.test.ts b/packages/typescript/test/sync/api-generators.test.ts index 9016e6c75e7de..45c8c4892ddbc 100644 --- a/packages/typescript/test/sync/api-generators.test.ts +++ b/packages/typescript/test/sync/api-generators.test.ts @@ -813,6 +813,7 @@ describe("API - generator batching", () => { parityCase("Checker", "typeToString", checker.typeToString, assertDeepEquivalent, interfaceType, interfaceDeclaration), parityCase("Checker", "isContextSensitive", checker.isContextSensitive, assertDeepEquivalent, boxDeclaration.initializer!), parityCase("Checker", "isArrayType", checker.isArrayType, assertDeepEquivalent, arrayType), + parityCase("Checker", "isTupleTypeReference", checker.isTupleTypeReference, assertDeepEquivalent, tupleType), parityCase("Checker", "isTupleType", checker.isTupleType, assertDeepEquivalent, tupleType), parityCase("Checker", "getReturnTypeOfSignature", checker.getReturnTypeOfSignature, assertTypesEquivalent, signature), parityCase("Checker", "getRestTypeOfSignature", checker.getRestTypeOfSignature, assertOptionalTypesEquivalent, signature), From 93ecad4be0b819ccff3eed5f32b9f7dc3c29e31b Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 28 Aug 2026 11:49:47 -0700 Subject: [PATCH 3/4] Undo renaming --- packages/typescript/src/api/async/api.ts | 26 +++++++------- packages/typescript/src/api/async/types.ts | 10 +++--- .../typescript/src/api/proto.generated.ts | 2 +- packages/typescript/src/api/sync/api.ts | 34 +++++++++---------- packages/typescript/src/api/sync/types.ts | 13 ++++--- packages/typescript/test/async/api.test.ts | 34 +++++++++---------- .../test/sync/api-generators.test.ts | 2 +- packages/typescript/test/sync/api.test.ts | 34 +++++++++---------- tsc/internal/api/proto.go | 12 +++---- tsc/internal/checker/exports.go | 11 ++---- 10 files changed, 89 insertions(+), 89 deletions(-) diff --git a/packages/typescript/src/api/async/api.ts b/packages/typescript/src/api/async/api.ts index e97fb790a03b0..5e6351ee06f33 100644 --- a/packages/typescript/src/api/async/api.ts +++ b/packages/typescript/src/api/async/api.ts @@ -1822,14 +1822,14 @@ export class Checker { }); } - async isTupleTypeReference(type: Type): Promise { - return type.isTupleTypeReference(); - } - async isTupleType(type: Type): Promise { return type.isTupleType(); } + async isTupleTypeTarget(type: Type): Promise { + return type.isTupleTypeTarget(); + } + /** * The following symbols are considered read-only: * - Properties with a `readonly` modifier @@ -2336,7 +2336,7 @@ class TypeObject implements Type { readonly freshType!: number; readonly regularType!: number; readonly target!: number; - private readonly tupleTypeReference: boolean; + private readonly tupleType: boolean; readonly typeParameters!: readonly number[]; readonly outerTypeParameters!: readonly number[]; readonly localTypeParameters!: readonly number[]; @@ -2392,7 +2392,7 @@ class TypeObject implements Type { if (data.freshType !== undefined) this.freshType = data.freshType; if (data.regularType !== undefined) this.regularType = data.regularType; if (data.target !== undefined) this.target = data.target; - this.tupleTypeReference = data.isTupleTypeReference ?? false; + this.tupleType = data.isTupleType ?? false; this.typeParameters = data.typeParameters ?? []; this.outerTypeParameters = data.outerTypeParameters ?? []; this.localTypeParameters = data.localTypeParameters ?? []; @@ -2669,11 +2669,11 @@ class TypeObject implements Type { return isTypeReference(this); } - isTupleTypeReference(): this is TupleTypeReference { - return this.tupleTypeReference; + isTupleType(): this is TupleTypeReference { + return this.tupleType; } - isTupleType(): this is TupleType { + isTupleTypeTarget(): this is TupleType { return this.fixedLength !== undefined; } @@ -2760,12 +2760,12 @@ export function isTypeReference(type: Type): type is TypeReference { return isObjectType(type) && (type.objectFlags & ObjectFlags.Reference) !== 0; } -export function isTupleTypeReference(type: Type): type is TupleTypeReference { - return type.isTupleTypeReference(); +export function isTupleType(type: Type): type is TupleTypeReference { + return type.isTupleType(); } -export function isTupleType(type: Type): type is TupleType { - return type.isTupleType(); +export function isTupleTypeTarget(type: Type): type is TupleType { + return type.isTupleTypeTarget(); } export function isIndexType(type: Type): type is IndexType { diff --git a/packages/typescript/src/api/async/types.ts b/packages/typescript/src/api/async/types.ts index efba5d5d327af..316484c68ddc8 100644 --- a/packages/typescript/src/api/async/types.ts +++ b/packages/typescript/src/api/async/types.ts @@ -107,10 +107,10 @@ export interface Type { isBooleanLiteralType(): this is BooleanLiteralType; /** Whether this type is a type reference */ isTypeReference(): this is TypeReference; - /** Whether this type is a reference to a tuple type */ - isTupleTypeReference(): this is TupleTypeReference; + /** Whether this type is a tuple type reference */ + isTupleType(): this is TupleTypeReference; /** Whether this type owns tuple metadata */ - isTupleType(): this is TupleType; + isTupleTypeTarget(): this is TupleType; /** Whether this type is an index type (`keyof T`) */ isIndexType(): this is IndexType; /** Whether this type is an indexed access type (`T[K]`) */ @@ -195,8 +195,10 @@ export interface InterfaceType extends TypeReference { getLocalTypeParameters(): Promise; } -/** Tuple types (ObjectFlags.Tuple) */ +/** Tuple type targets (ObjectFlags.Tuple) */ export interface TupleType extends InterfaceType { + /** Get this tuple target */ + getTarget(): Promise; /** Per-element flags (Required, Optional, Rest, Variadic) */ readonly elementFlags: readonly ElementFlags[]; /** Number of initial required or optional elements */ diff --git a/packages/typescript/src/api/proto.generated.ts b/packages/typescript/src/api/proto.generated.ts index d202326a03e19..9faeb652e4fd0 100644 --- a/packages/typescript/src/api/proto.generated.ts +++ b/packages/typescript/src/api/proto.generated.ts @@ -364,7 +364,7 @@ export interface TypeResponse { id: number; flags: number; objectFlags?: number; - isTupleTypeReference?: boolean; + isTupleType?: boolean; /** * Value is literal type data. BigInt literals are encoded as signed decimal * strings because JSON cannot represent bigint; absent values are null. diff --git a/packages/typescript/src/api/sync/api.ts b/packages/typescript/src/api/sync/api.ts index 49d1be3ac53e7..48798db15ba7f 100644 --- a/packages/typescript/src/api/sync/api.ts +++ b/packages/typescript/src/api/sync/api.ts @@ -3894,36 +3894,36 @@ export class Checker { ); } - get isTupleTypeReference(): { + get isTupleType(): { (type: Type): boolean; gen(type: Type): Generator; } { const owner = this; return cacheGeneratorMethod( owner, - "isTupleTypeReference", + "isTupleType", function (type: Type): boolean { - return type.isTupleTypeReference(); + return type.isTupleType(); }, function* (type: Type): Generator { - return type.isTupleTypeReference(); + return type.isTupleType(); }, ); } - get isTupleType(): { + get isTupleTypeTarget(): { (type: Type): boolean; gen(type: Type): Generator; } { const owner = this; return cacheGeneratorMethod( owner, - "isTupleType", + "isTupleTypeTarget", function (type: Type): boolean { - return type.isTupleType(); + return type.isTupleTypeTarget(); }, function* (type: Type): Generator { - return type.isTupleType(); + return type.isTupleTypeTarget(); }, ); } @@ -5099,7 +5099,7 @@ class TypeObject implements Type { readonly freshType!: number; readonly regularType!: number; readonly target!: number; - private readonly tupleTypeReference: boolean; + private readonly tupleType: boolean; readonly typeParameters!: readonly number[]; readonly outerTypeParameters!: readonly number[]; readonly localTypeParameters!: readonly number[]; @@ -5155,7 +5155,7 @@ class TypeObject implements Type { if (data.freshType !== undefined) this.freshType = data.freshType; if (data.regularType !== undefined) this.regularType = data.regularType; if (data.target !== undefined) this.target = data.target; - this.tupleTypeReference = data.isTupleTypeReference ?? false; + this.tupleType = data.isTupleType ?? false; this.typeParameters = data.typeParameters ?? []; this.outerTypeParameters = data.outerTypeParameters ?? []; this.localTypeParameters = data.localTypeParameters ?? []; @@ -5915,11 +5915,11 @@ class TypeObject implements Type { return isTypeReference(this); } - isTupleTypeReference(): this is TupleTypeReference { - return this.tupleTypeReference; + isTupleType(): this is TupleTypeReference { + return this.tupleType; } - isTupleType(): this is TupleType { + isTupleTypeTarget(): this is TupleType { return this.fixedLength !== undefined; } @@ -6006,12 +6006,12 @@ export function isTypeReference(type: Type): type is TypeReference { return isObjectType(type) && (type.objectFlags & ObjectFlags.Reference) !== 0; } -export function isTupleTypeReference(type: Type): type is TupleTypeReference { - return type.isTupleTypeReference(); +export function isTupleType(type: Type): type is TupleTypeReference { + return type.isTupleType(); } -export function isTupleType(type: Type): type is TupleType { - return type.isTupleType(); +export function isTupleTypeTarget(type: Type): type is TupleType { + return type.isTupleTypeTarget(); } export function isIndexType(type: Type): type is IndexType { diff --git a/packages/typescript/src/api/sync/types.ts b/packages/typescript/src/api/sync/types.ts index 55f9afdfcd3e1..43a7b1720db22 100644 --- a/packages/typescript/src/api/sync/types.ts +++ b/packages/typescript/src/api/sync/types.ts @@ -165,10 +165,10 @@ export interface Type { isBooleanLiteralType(): this is BooleanLiteralType; /** Whether this type is a type reference */ isTypeReference(): this is TypeReference; - /** Whether this type is a reference to a tuple type */ - isTupleTypeReference(): this is TupleTypeReference; + /** Whether this type is a tuple type reference */ + isTupleType(): this is TupleTypeReference; /** Whether this type owns tuple metadata */ - isTupleType(): this is TupleType; + isTupleTypeTarget(): this is TupleType; /** Whether this type is an index type (`keyof T`) */ isIndexType(): this is IndexType; /** Whether this type is an indexed access type (`T[K]`) */ @@ -274,8 +274,13 @@ export interface InterfaceType extends TypeReference { }; } -/** Tuple types (ObjectFlags.Tuple) */ +/** Tuple type targets (ObjectFlags.Tuple) */ export interface TupleType extends InterfaceType { + /** Get this tuple target */ + getTarget: { + (): TupleType; + gen(): Generator; + }; /** Per-element flags (Required, Optional, Rest, Variadic) */ readonly elementFlags: readonly ElementFlags[]; /** Number of initial required or optional elements */ diff --git a/packages/typescript/test/async/api.test.ts b/packages/typescript/test/async/api.test.ts index ccaa20ebd602c..0dbe366e50c95 100644 --- a/packages/typescript/test/async/api.test.ts +++ b/packages/typescript/test/async/api.test.ts @@ -2755,30 +2755,30 @@ array([]); for (const [index, expectedFixedLength] of [0, 1].entries()) { const type = await project.checker.getTypeAtLocation(arrayLiterals[index]); - assert.equal(await project.checker.isTupleTypeReference(type), true); - assert.equal(await project.checker.isTupleType(type), false); - assert.equal(type.isTupleTypeReference(), true); - assert.equal(type.isTupleType(), false); - assert.ok(type.isTupleTypeReference()); + assert.equal(await project.checker.isTupleType(type), true); + assert.equal(await project.checker.isTupleTypeTarget(type), false); + assert.equal(type.isTupleType(), true); + assert.equal(type.isTupleTypeTarget(), false); + assert.ok(type.isTupleType()); assert.equal(Reflect.get(type, "fixedLength"), undefined); const target = await type.getTarget(); assert.ok(target.objectFlags & ObjectFlags.Tuple); - assert.equal(await project.checker.isTupleTypeReference(target), true); assert.equal(await project.checker.isTupleType(target), true); - assert.equal(target.isTupleTypeReference(), true); + assert.equal(await project.checker.isTupleTypeTarget(target), true); assert.equal(target.isTupleType(), true); - assert.ok(target.isTupleType()); + assert.equal(target.isTupleTypeTarget(), true); + assert.ok(target.isTupleTypeTarget()); assert.equal(target.fixedLength, expectedFixedLength); assert.equal(target.elementFlags.length, expectedFixedLength); assert.equal(target.readonly, false); } const arrayType = await project.checker.getTypeAtLocation(arrayLiterals[2]); - assert.equal(await project.checker.isTupleTypeReference(arrayType), false); assert.equal(await project.checker.isTupleType(arrayType), false); - assert.equal(arrayType.isTupleTypeReference(), false); + assert.equal(await project.checker.isTupleTypeTarget(arrayType), false); assert.equal(arrayType.isTupleType(), false); + assert.equal(arrayType.isTupleTypeTarget(), false); } finally { await api.close(); @@ -3315,7 +3315,7 @@ describe("readFile callback semantics", () => { }); }); -describe("Checker - isArrayType / isTupleTypeReference", () => { +describe("Checker - isArrayType / isTupleType", () => { test("number[] is array, not tuple", async () => { const api = spawnAPI({ "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), @@ -3331,7 +3331,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), true); - assert.equal(await project.checker.isTupleTypeReference(type), false); + assert.equal(await project.checker.isTupleType(type), false); } finally { await api.close(); @@ -3353,7 +3353,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), true); - assert.equal(await project.checker.isTupleTypeReference(type), false); + assert.equal(await project.checker.isTupleType(type), false); } finally { await api.close(); @@ -3375,7 +3375,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), true); - assert.equal(await project.checker.isTupleTypeReference(type), false); + assert.equal(await project.checker.isTupleType(type), false); } finally { await api.close(); @@ -3397,7 +3397,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), false); - assert.equal(await project.checker.isTupleTypeReference(type), true); + assert.equal(await project.checker.isTupleType(type), true); } finally { await api.close(); @@ -3419,7 +3419,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), false); - assert.equal(await project.checker.isTupleTypeReference(type), true); + assert.equal(await project.checker.isTupleType(type), true); } finally { await api.close(); @@ -3441,7 +3441,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = await project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(await project.checker.isArrayType(type), false); - assert.equal(await project.checker.isTupleTypeReference(type), false); + assert.equal(await project.checker.isTupleType(type), false); } finally { await api.close(); diff --git a/packages/typescript/test/sync/api-generators.test.ts b/packages/typescript/test/sync/api-generators.test.ts index 45c8c4892ddbc..3d557db4e1f5d 100644 --- a/packages/typescript/test/sync/api-generators.test.ts +++ b/packages/typescript/test/sync/api-generators.test.ts @@ -813,8 +813,8 @@ describe("API - generator batching", () => { parityCase("Checker", "typeToString", checker.typeToString, assertDeepEquivalent, interfaceType, interfaceDeclaration), parityCase("Checker", "isContextSensitive", checker.isContextSensitive, assertDeepEquivalent, boxDeclaration.initializer!), parityCase("Checker", "isArrayType", checker.isArrayType, assertDeepEquivalent, arrayType), - parityCase("Checker", "isTupleTypeReference", checker.isTupleTypeReference, assertDeepEquivalent, tupleType), parityCase("Checker", "isTupleType", checker.isTupleType, assertDeepEquivalent, tupleType), + parityCase("Checker", "isTupleTypeTarget", checker.isTupleTypeTarget, assertDeepEquivalent, tupleType), parityCase("Checker", "getReturnTypeOfSignature", checker.getReturnTypeOfSignature, assertTypesEquivalent, signature), parityCase("Checker", "getRestTypeOfSignature", checker.getRestTypeOfSignature, assertOptionalTypesEquivalent, signature), parityCase("Checker", "getTypePredicateOfSignature", checker.getTypePredicateOfSignature, assertOptionalTypePredicatesEquivalent, predicateSignature), diff --git a/packages/typescript/test/sync/api.test.ts b/packages/typescript/test/sync/api.test.ts index 91943155616b4..7eb0a5e0b510e 100644 --- a/packages/typescript/test/sync/api.test.ts +++ b/packages/typescript/test/sync/api.test.ts @@ -2671,30 +2671,30 @@ array([]); for (const [index, expectedFixedLength] of [0, 1].entries()) { const type = project.checker.getTypeAtLocation(arrayLiterals[index]); - assert.equal(project.checker.isTupleTypeReference(type), true); - assert.equal(project.checker.isTupleType(type), false); - assert.equal(type.isTupleTypeReference(), true); - assert.equal(type.isTupleType(), false); - assert.ok(type.isTupleTypeReference()); + assert.equal(project.checker.isTupleType(type), true); + assert.equal(project.checker.isTupleTypeTarget(type), false); + assert.equal(type.isTupleType(), true); + assert.equal(type.isTupleTypeTarget(), false); + assert.ok(type.isTupleType()); assert.equal(Reflect.get(type, "fixedLength"), undefined); const target = type.getTarget(); assert.ok(target.objectFlags & ObjectFlags.Tuple); - assert.equal(project.checker.isTupleTypeReference(target), true); assert.equal(project.checker.isTupleType(target), true); - assert.equal(target.isTupleTypeReference(), true); + assert.equal(project.checker.isTupleTypeTarget(target), true); assert.equal(target.isTupleType(), true); - assert.ok(target.isTupleType()); + assert.equal(target.isTupleTypeTarget(), true); + assert.ok(target.isTupleTypeTarget()); assert.equal(target.fixedLength, expectedFixedLength); assert.equal(target.elementFlags.length, expectedFixedLength); assert.equal(target.readonly, false); } const arrayType = project.checker.getTypeAtLocation(arrayLiterals[2]); - assert.equal(project.checker.isTupleTypeReference(arrayType), false); assert.equal(project.checker.isTupleType(arrayType), false); - assert.equal(arrayType.isTupleTypeReference(), false); + assert.equal(project.checker.isTupleTypeTarget(arrayType), false); assert.equal(arrayType.isTupleType(), false); + assert.equal(arrayType.isTupleTypeTarget(), false); } finally { api.close(); @@ -3231,7 +3231,7 @@ describe("readFile callback semantics", () => { }); }); -describe("Checker - isArrayType / isTupleTypeReference", () => { +describe("Checker - isArrayType / isTupleType", () => { test("number[] is array, not tuple", () => { const api = spawnAPI({ "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), @@ -3247,7 +3247,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), true); - assert.equal(project.checker.isTupleTypeReference(type), false); + assert.equal(project.checker.isTupleType(type), false); } finally { api.close(); @@ -3269,7 +3269,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), true); - assert.equal(project.checker.isTupleTypeReference(type), false); + assert.equal(project.checker.isTupleType(type), false); } finally { api.close(); @@ -3291,7 +3291,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), true); - assert.equal(project.checker.isTupleTypeReference(type), false); + assert.equal(project.checker.isTupleType(type), false); } finally { api.close(); @@ -3313,7 +3313,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), false); - assert.equal(project.checker.isTupleTypeReference(type), true); + assert.equal(project.checker.isTupleType(type), true); } finally { api.close(); @@ -3335,7 +3335,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), false); - assert.equal(project.checker.isTupleTypeReference(type), true); + assert.equal(project.checker.isTupleType(type), true); } finally { api.close(); @@ -3357,7 +3357,7 @@ describe("Checker - isArrayType / isTupleTypeReference", () => { const type = project.checker.getTypeOfSymbol(symbol); assert.ok(type); assert.equal(project.checker.isArrayType(type), false); - assert.equal(project.checker.isTupleTypeReference(type), false); + assert.equal(project.checker.isTupleType(type), false); } finally { api.close(); diff --git a/tsc/internal/api/proto.go b/tsc/internal/api/proto.go index 7224a753761e7..a7c15ddef7bee 100644 --- a/tsc/internal/api/proto.go +++ b/tsc/internal/api/proto.go @@ -824,10 +824,10 @@ type GetTypesOfSymbolsParams struct { } type TypeResponse struct { - Id TypeID `json:"id"` - Flags uint32 `json:"flags"` - ObjectFlags uint32 `json:"objectFlags,omitempty"` - IsTupleTypeReference bool `json:"isTupleTypeReference,omitempty"` + Id TypeID `json:"id"` + Flags uint32 `json:"flags"` + ObjectFlags uint32 `json:"objectFlags,omitempty"` + IsTupleType bool `json:"isTupleType,omitempty"` // Value is literal type data. BigInt literals are encoded as signed decimal // strings because JSON cannot represent bigint; absent values are null. @@ -910,11 +910,11 @@ func newTypeResponse(t *checker.Type, id TypeID) *TypeResponse { } case flags&checker.TypeFlagsObject != 0: resp.ObjectFlags = uint32(t.ObjectFlags()) - resp.IsTupleTypeReference = checker.IsTupleTypeReference(t) + resp.IsTupleType = checker.IsTupleType(t) objectFlags := t.ObjectFlags() if objectFlags&checker.ObjectFlagsReference != 0 { ref := t.AsTypeReference() - if checker.IsTupleTarget(t) && ref.Target() == t { + if checker.IsTupleTypeTarget(t) { tuple := t.AsTupleType() resp.ElementFlags = tuple.ElementFlags() fixedLen := tuple.FixedLength() diff --git a/tsc/internal/checker/exports.go b/tsc/internal/checker/exports.go index fd79860b086c7..e26b4038fae8a 100644 --- a/tsc/internal/checker/exports.go +++ b/tsc/internal/checker/exports.go @@ -225,15 +225,8 @@ func IsTupleType(t *Type) bool { return isTupleType(t) } -// IsTupleTypeReference reports whether the provided type is a type reference -// whose target is a tuple type. -func IsTupleTypeReference(t *Type) bool { - return isTupleType(t) -} - -// IsTupleTarget reports whether the provided type owns tuple metadata. -func IsTupleTarget(t *Type) bool { - return t != nil && t.objectFlags&ObjectFlagsTuple != 0 +func IsTupleTypeTarget(t *Type) bool { + return isTupleType(t) && t.Target() == t } func (c *Checker) IsArrayType(t *Type) bool { From 7cba9aa055ff5f0d699cbc1ef9d44106e8a6d404 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 28 Aug 2026 12:05:48 -0700 Subject: [PATCH 4/4] Fix wire shape --- packages/typescript/src/api/async/api.ts | 5 +---- packages/typescript/src/api/sync/api.ts | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/typescript/src/api/async/api.ts b/packages/typescript/src/api/async/api.ts index 5e6351ee06f33..f46b9bf16d3c0 100644 --- a/packages/typescript/src/api/async/api.ts +++ b/packages/typescript/src/api/async/api.ts @@ -2399,13 +2399,10 @@ class TypeObject implements Type { this.aliasTypeArguments = data.aliasTypeArguments ?? []; if (data.aliasSymbol !== undefined) this.aliasSymbol = data.aliasSymbol; if (data.fixedLength !== undefined) { - if (data.readonly === undefined) { - throw new Error("Tuple type response is missing readonly metadata"); - } this.elementFlags = data.elementFlags ?? []; this.fixedLength = data.fixedLength; - this.readonly = data.readonly; } + if (data.readonly !== undefined) this.readonly = data.readonly; 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/api.ts b/packages/typescript/src/api/sync/api.ts index 48798db15ba7f..d6c538d1895c4 100644 --- a/packages/typescript/src/api/sync/api.ts +++ b/packages/typescript/src/api/sync/api.ts @@ -5162,13 +5162,10 @@ class TypeObject implements Type { this.aliasTypeArguments = data.aliasTypeArguments ?? []; if (data.aliasSymbol !== undefined) this.aliasSymbol = data.aliasSymbol; if (data.fixedLength !== undefined) { - if (data.readonly === undefined) { - throw new Error("Tuple type response is missing readonly metadata"); - } this.elementFlags = data.elementFlags ?? []; this.fixedLength = data.fixedLength; - this.readonly = data.readonly; } + if (data.readonly !== undefined) this.readonly = data.readonly; if (data.texts !== undefined) this.texts = data.texts; if (data.objectType !== undefined) this.objectType = data.objectType; if (data.indexType !== undefined) this.indexType = data.indexType;