Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions packages/typescript/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ import type {
TemplateLiteralType,
ThisTypePredicate,
TupleType,
TupleTypeReference,
Type,
TypeParameter,
TypePredicate,
Expand Down Expand Up @@ -187,6 +188,7 @@ export type {
TimingAccumulators,
TimingInfo,
TupleType,
TupleTypeReference,
Type,
TypeAcquisition,
TypeParameter,
Expand Down Expand Up @@ -1821,11 +1823,11 @@ export class Checker {
}

async isTupleType(type: Type): Promise<boolean> {
return this.client.apiRequest("isTupleType", {
snapshot: this.snapshotId,
project: this.project.id,
type: type.id,
});
return type.isTupleType();
}

async isTupleTypeTarget(type: Type): Promise<boolean> {
return type.isTupleTypeTarget();
}

/**
Expand Down Expand Up @@ -2334,6 +2336,7 @@ class TypeObject implements Type {
readonly freshType!: number;
readonly regularType!: number;
readonly target!: number;
private readonly tupleType: boolean;
readonly typeParameters!: readonly number[];
readonly outerTypeParameters!: readonly number[];
readonly localTypeParameters!: readonly number[];
Expand Down Expand Up @@ -2389,13 +2392,16 @@ 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.tupleType = data.isTupleType ?? 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.fixedLength !== undefined) {
this.elementFlags = data.elementFlags ?? [];
this.fixedLength = data.fixedLength;
}
if (data.readonly !== undefined) this.readonly = data.readonly;
if (data.texts !== undefined) this.texts = data.texts;
if (data.objectType !== undefined) this.objectType = data.objectType;
Expand Down Expand Up @@ -2660,8 +2666,12 @@ class TypeObject implements Type {
return isTypeReference(this);
}

isTupleType(): this is TupleType {
return isTupleType(this);
isTupleType(): this is TupleTypeReference {
return this.tupleType;
}

isTupleTypeTarget(): this is TupleType {
return this.fixedLength !== undefined;
}

isIndexType(): this is IndexType {
Expand Down Expand Up @@ -2747,8 +2757,12 @@ export function isTypeReference(type: Type): type is TypeReference {
return isObjectType(type) && (type.objectFlags & ObjectFlags.Reference) !== 0;
}

export function isTupleType(type: Type): type is TupleType {
return isObjectType(type) && (type.objectFlags & ObjectFlags.Tuple) !== 0;
export function isTupleType(type: Type): type is TupleTypeReference {
return type.isTupleType();
}

export function isTupleTypeTarget(type: Type): type is TupleType {
return type.isTupleTypeTarget();
}

export function isIndexType(type: Type): type is IndexType {
Expand Down
16 changes: 13 additions & 3 deletions packages/typescript/src/api/async/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +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 tuple type */
isTupleType(): this is TupleType;
/** Whether this type is a tuple type reference */
isTupleType(): this is TupleTypeReference;
/** Whether this type owns tuple metadata */
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]`) */
Expand Down Expand Up @@ -177,6 +179,12 @@ export interface TypeReference extends ObjectType {
getTarget(): Promise<Type>;
}

/** References to tuple types */
export interface TupleTypeReference extends TypeReference {
/** Get the tuple type that describes this reference's shape */
getTarget(): Promise<TupleType>;
}

/** Interface types — classes and interfaces (ObjectFlags.ClassOrInterface) */
export interface InterfaceType extends TypeReference {
/** Get all type parameters (outer + local, excluding thisType) */
Expand All @@ -187,8 +195,10 @@ export interface InterfaceType extends TypeReference {
getLocalTypeParameters(): Promise<readonly TypeParameter[]>;
}

/** Tuple types (ObjectFlags.Tuple) */
/** Tuple type targets (ObjectFlags.Tuple) */
export interface TupleType extends InterfaceType {
/** Get this tuple target */
getTarget(): Promise<TupleType>;
/** Per-element flags (Required, Optional, Rest, Variadic) */
readonly elementFlags: readonly ElementFlags[];
/** Number of initial required or optional elements */
Expand Down
4 changes: 1 addition & 3 deletions packages/typescript/src/api/proto.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ export interface APIMethodInfo {
getJsDocTags: APIMethod<CheckerSymbolParams, JSDocTagInfo[] | null>;
getDocumentationComment: APIMethod<CheckerSymbolParams, string>;
isArrayType: APIMethod<CheckerTypeParams, boolean>;
isTupleType: APIMethod<CheckerTypeParams, boolean>;
isReadonlySymbol: APIMethod<CheckerSymbolParams, boolean>;
getReferencesToSymbolInFile: APIMethod<GetReferencesToSymbolInFileParams, string[]>;
getReferencedSymbolsForNode: APIMethod<GetReferencedSymbolsForNodeParams, ReferencedSymbolEntry[] | null>;
Expand Down Expand Up @@ -365,6 +364,7 @@ export interface TypeResponse {
id: number;
flags: number;
objectFlags?: number;
isTupleType?: boolean;
/**
* Value is literal type data. BigInt literals are encoded as signed decimal
* strings because JSON cannot represent bigint; absent values are null.
Expand Down Expand Up @@ -1002,7 +1002,6 @@ export interface BatchRequest {
| "isArrayType"
| "isContextSensitive"
| "isReadonlySymbol"
| "isTupleType"
| "isTypeAssignableTo"
| "parseCommandLine"
| "parseConfigFile"
Expand Down Expand Up @@ -1149,7 +1148,6 @@ export interface BatchResponse {
| "isArrayType"
| "isContextSensitive"
| "isReadonlySymbol"
| "isTupleType"
| "isTypeAssignableTo"
| "parseCommandLine"
| "parseConfigFile"
Expand Down
55 changes: 39 additions & 16 deletions packages/typescript/src/api/sync/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ import type {
TemplateLiteralType,
ThisTypePredicate,
TupleType,
TupleTypeReference,
Type,
TypeParameter,
TypePredicate,
Expand Down Expand Up @@ -204,6 +205,7 @@ export type {
TimingAccumulators,
TimingInfo,
TupleType,
TupleTypeReference,
Type,
TypeAcquisition,
TypeParameter,
Expand Down Expand Up @@ -3901,18 +3903,27 @@ 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<ProtocolRequest, boolean, ProtocolResponse["result"]> {
return yield* apiRequest("isTupleType", {
snapshot: owner.snapshotId,
project: owner.project.id,
type: type.id,
});
return type.isTupleType();
},
);
}

get isTupleTypeTarget(): {
(type: Type): boolean;
gen(type: Type): Generator<ProtocolRequest, boolean, ProtocolResponse["result"]>;
} {
const owner = this;
return cacheGeneratorMethod(
owner,
"isTupleTypeTarget",
function (type: Type): boolean {
return type.isTupleTypeTarget();
},
function* (type: Type): Generator<ProtocolRequest, boolean, ProtocolResponse["result"]> {
return type.isTupleTypeTarget();
},
);
}
Expand Down Expand Up @@ -5088,6 +5099,7 @@ class TypeObject implements Type {
readonly freshType!: number;
readonly regularType!: number;
readonly target!: number;
private readonly tupleType: boolean;
readonly typeParameters!: readonly number[];
readonly outerTypeParameters!: readonly number[];
readonly localTypeParameters!: readonly number[];
Expand Down Expand Up @@ -5143,13 +5155,16 @@ 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.tupleType = data.isTupleType ?? 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.fixedLength !== undefined) {
this.elementFlags = data.elementFlags ?? [];
this.fixedLength = data.fixedLength;
}
if (data.readonly !== undefined) this.readonly = data.readonly;
if (data.texts !== undefined) this.texts = data.texts;
if (data.objectType !== undefined) this.objectType = data.objectType;
Expand Down Expand Up @@ -5897,8 +5912,12 @@ class TypeObject implements Type {
return isTypeReference(this);
}

isTupleType(): this is TupleType {
return isTupleType(this);
isTupleType(): this is TupleTypeReference {
return this.tupleType;
}

isTupleTypeTarget(): this is TupleType {
return this.fixedLength !== undefined;
}

isIndexType(): this is IndexType {
Expand Down Expand Up @@ -5984,8 +6003,12 @@ export function isTypeReference(type: Type): type is TypeReference {
return isObjectType(type) && (type.objectFlags & ObjectFlags.Reference) !== 0;
}

export function isTupleType(type: Type): type is TupleType {
return isObjectType(type) && (type.objectFlags & ObjectFlags.Tuple) !== 0;
export function isTupleType(type: Type): type is TupleTypeReference {
return type.isTupleType();
}

export function isTupleTypeTarget(type: Type): type is TupleType {
return type.isTupleTypeTarget();
}

export function isIndexType(type: Type): type is IndexType {
Expand Down
22 changes: 19 additions & 3 deletions packages/typescript/src/api/sync/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +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 tuple type */
isTupleType(): this is TupleType;
/** Whether this type is a tuple type reference */
isTupleType(): this is TupleTypeReference;
/** Whether this type owns tuple metadata */
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]`) */
Expand Down Expand Up @@ -244,6 +246,15 @@ 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;
gen(): Generator<ProtocolRequest, TupleType, ProtocolResponse["result"]>;
};
}

/** Interface types — classes and interfaces (ObjectFlags.ClassOrInterface) */
export interface InterfaceType extends TypeReference {
/** Get all type parameters (outer + local, excluding thisType) */
Expand All @@ -263,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<ProtocolRequest, TupleType, ProtocolResponse["result"]>;
};
/** Per-element flags (Required, Optional, Rest, Variadic) */
readonly elementFlags: readonly ElementFlags[];
/** Number of initial required or optional elements */
Expand Down
60 changes: 52 additions & 8 deletions packages/typescript/test/async/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.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.isTupleType(target), true);
assert.equal(await project.checker.isTupleTypeTarget(target), true);
assert.equal(target.isTupleType(), true);
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.isTupleType(arrayType), false);
assert.equal(await project.checker.isTupleTypeTarget(arrayType), false);
assert.equal(arrayType.isTupleType(), false);
assert.equal(arrayType.isTupleTypeTarget(), false);
}
finally {
await api.close();
Expand Down
1 change: 1 addition & 0 deletions packages/typescript/test/sync/api-generators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,7 @@ describe("API - generator batching", () => {
parityCase("Checker", "isContextSensitive", checker.isContextSensitive, assertDeepEquivalent, boxDeclaration.initializer!),
parityCase("Checker", "isArrayType", checker.isArrayType, assertDeepEquivalent, arrayType),
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),
Expand Down
Loading