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
13 changes: 13 additions & 0 deletions packages/typescript/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1787,6 +1787,11 @@ export class Checker {
return type.getApparentType();
}

/** Get the reduced type of a type. Always returns a type. */
async getReducedType(type: Type): Promise<Type> {
return type.getReducedType();
}

async getPropertiesOfType(type: Type): Promise<readonly Symbol[]> {
return type.getProperties();
}
Expand Down Expand Up @@ -2237,6 +2242,7 @@ class TypeObject implements Type {
private default: number | false;
private nonNullableType: number | false;
private apparentType: number | false;
private reducedType: number | false;
private properties: readonly Symbol[] | false;
private apparentProperties: readonly Symbol[] | false;
private callSignatures: readonly Signature[] | false;
Expand Down Expand Up @@ -2286,6 +2292,7 @@ class TypeObject implements Type {
this.default = false;
this.nonNullableType = false;
this.apparentType = false;
this.reducedType = false;
this.properties = false;
this.apparentProperties = false;
this.callSignatures = false;
Expand Down Expand Up @@ -2368,6 +2375,12 @@ class TypeObject implements Type {
return result;
}

async getReducedType(): Promise<Type> {
const result = await this.objectRegistry.fetchType(this, "getReducedType", this.reducedType);
this.reducedType = result.id;
return result;
}

async getIndexInfos(): Promise<readonly IndexInfo[]> {
if (this.indexInfos === false) {
this.indexInfos = await this.objectRegistry.fetchIndexInfosOfType(this);
Expand Down
3 changes: 3 additions & 0 deletions packages/typescript/src/api/async/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ export interface Type {
/** Get the apparent type of this type. */
getApparentType(): Promise<Type>;

/** Get the reduced type of this type. */
getReducedType(): Promise<Type>;

/** Get the call signatures of this type. */
getCallSignatures(): Promise<readonly Signature[]>;

Expand Down
1 change: 1 addition & 0 deletions packages/typescript/src/api/proto.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export interface APIMethodInfo {
getPropertiesOfType: APIMethod<CheckerTypeParams, SymbolResponse[] | null>;
getApparentPropertiesOfType: APIMethod<GetTypePropertyParams, SymbolResponse[]>;
getApparentType: APIMethod<GetTypePropertyParams, TypeResponse>;
getReducedType: APIMethod<GetTypePropertyParams, TypeResponse>;
getPropertyOfType: APIMethod<GetPropertyOfTypeParams, SymbolResponse | null>;
getIndexInfosOfType: APIMethod<CheckerTypeParams, IndexInfoResponse[] | null>;
getConstraintOfTypeParameter: APIMethod<GetTypePropertyParams, TypeResponse | null>;
Expand Down
13 changes: 13 additions & 0 deletions packages/typescript/src/api/sync/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1795,6 +1795,11 @@ export class Checker {
return type.getApparentType();
}

/** Get the reduced type of a type. Always returns a type. */
getReducedType(type: Type): Type {
return type.getReducedType();
}

getPropertiesOfType(type: Type): readonly Symbol[] {
return type.getProperties();
}
Expand Down Expand Up @@ -2245,6 +2250,7 @@ class TypeObject implements Type {
private default: number | false;
private nonNullableType: number | false;
private apparentType: number | false;
private reducedType: number | false;
private properties: readonly Symbol[] | false;
private apparentProperties: readonly Symbol[] | false;
private callSignatures: readonly Signature[] | false;
Expand Down Expand Up @@ -2294,6 +2300,7 @@ class TypeObject implements Type {
this.default = false;
this.nonNullableType = false;
this.apparentType = false;
this.reducedType = false;
this.properties = false;
this.apparentProperties = false;
this.callSignatures = false;
Expand Down Expand Up @@ -2376,6 +2383,12 @@ class TypeObject implements Type {
return result;
}

getReducedType(): Type {
const result = this.objectRegistry.fetchType(this, "getReducedType", this.reducedType);
this.reducedType = result.id;
return result;
}

getIndexInfos(): readonly IndexInfo[] {
if (this.indexInfos === false) {
this.indexInfos = this.objectRegistry.fetchIndexInfosOfType(this);
Expand Down
3 changes: 3 additions & 0 deletions packages/typescript/src/api/sync/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export interface Type {
/** Get the apparent type of this type. */
getApparentType(): Type;

/** Get the reduced type of this type. */
getReducedType(): Type;

/** Get the call signatures of this type. */
getCallSignatures(): readonly Signature[];

Expand Down
41 changes: 41 additions & 0 deletions packages/typescript/test/async/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,47 @@ describe("Checker - getApparentType", () => {
});
});

describe("Checker - getReducedType", () => {
test("returns the reduced type", async () => {
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
"/src/main.ts": `
declare const TaggedError: <Tag extends string>(
tag: Tag,
) => new <A extends Record<string, any> = {}>(
args: { readonly [P in keyof A]: A[P] },
) => { readonly _tag: Tag } & Readonly<A>;

class RateLimitError extends TaggedError("RateLimitError")<{
readonly retryAfter: number;
}> {}

class QuotaExceededError extends TaggedError("QuotaExceededError")<{
readonly limit: number;
}> {}

export type Result = RateLimitError | (RateLimitError & QuotaExceededError);`,
});
try {
const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const sourceFile = await project.program.getSourceFile("/src/main.ts");
assert.ok(sourceFile);
const typeAlias = sourceFile.statements.find(isTypeAliasDeclaration);
assert.ok(typeAlias);
const type = await project.checker.getTypeAtLocation(typeAlias);
assert.equal(type.isUnionType(), true);
assert.equal(type.isObjectType(), false);
const reducedType = await project.checker.getReducedType(type);
assert.equal(reducedType.isUnionType(), false);
assert.equal(reducedType.isObjectType(), true);
}
finally {
await api.close();
}
});
});

describe("Checker - getMemberInModuleExports", () => {
test("returns a named export when present", async () => {
const api = spawnAPI({
Expand Down
41 changes: 41 additions & 0 deletions packages/typescript/test/sync/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,47 @@ describe("Checker - getApparentType", () => {
});
});

describe("Checker - getReducedType", () => {
test("returns the reduced type", () => {
const api = spawnAPI({
"/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }),
"/src/main.ts": `
declare const TaggedError: <Tag extends string>(
tag: Tag,
) => new <A extends Record<string, any> = {}>(
args: { readonly [P in keyof A]: A[P] },
) => { readonly _tag: Tag } & Readonly<A>;

class RateLimitError extends TaggedError("RateLimitError")<{
readonly retryAfter: number;
}> {}

class QuotaExceededError extends TaggedError("QuotaExceededError")<{
readonly limit: number;
}> {}

export type Result = RateLimitError | (RateLimitError & QuotaExceededError);`,
});
try {
const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" });
const project = snapshot.getProject("/tsconfig.json")!;
const sourceFile = project.program.getSourceFile("/src/main.ts");
assert.ok(sourceFile);
const typeAlias = sourceFile.statements.find(isTypeAliasDeclaration);
assert.ok(typeAlias);
const type = project.checker.getTypeAtLocation(typeAlias);
assert.equal(type.isUnionType(), true);
assert.equal(type.isObjectType(), false);
const reducedType = project.checker.getReducedType(type);
assert.equal(reducedType.isUnionType(), false);
assert.equal(reducedType.isObjectType(), true);
}
finally {
api.close();
}
});
});

describe("Checker - getMemberInModuleExports", () => {
test("returns a named export when present", () => {
const api = spawnAPI({
Expand Down
2 changes: 2 additions & 0 deletions tsc/internal/api/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const (
MethodGetPropertiesOfType Method = "getPropertiesOfType"
MethodGetApparentPropertiesOfType Method = "getApparentPropertiesOfType"
MethodGetApparentType Method = "getApparentType"
MethodGetReducedType Method = "getReducedType"
MethodGetPropertyOfType Method = "getPropertyOfType"
MethodGetIndexInfosOfType Method = "getIndexInfosOfType"
MethodGetConstraintOfTypeParameter Method = "getConstraintOfTypeParameter"
Expand Down Expand Up @@ -487,6 +488,7 @@ var unmarshalers = map[Method]func([]byte) (any, error){
MethodGetPropertiesOfType: unmarshallerFor[CheckerTypeParams],
MethodGetApparentPropertiesOfType: unmarshallerFor[GetTypePropertyParams],
MethodGetApparentType: unmarshallerFor[GetTypePropertyParams],
MethodGetReducedType: unmarshallerFor[GetTypePropertyParams],
MethodGetPropertyOfType: unmarshallerFor[GetPropertyOfTypeParams],
MethodGetIndexInfosOfType: unmarshallerFor[CheckerTypeParams],
MethodGetConstraintOfTypeParameter: unmarshallerFor[GetTypePropertyParams],
Expand Down
18 changes: 18 additions & 0 deletions tsc/internal/api/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json.
return s.handleGetApparentPropertiesOfType(ctx, parsed.(*GetTypePropertyParams))
case string(MethodGetApparentType):
return s.handleGetApparentType(ctx, parsed.(*GetTypePropertyParams))
case string(MethodGetReducedType):
return s.handleGetReducedType(ctx, parsed.(*GetTypePropertyParams))
case string(MethodGetPropertyOfType):
return s.handleGetPropertyOfType(ctx, parsed.(*GetPropertyOfTypeParams))
case string(MethodGetIndexInfosOfType):
Expand Down Expand Up @@ -3062,6 +3064,22 @@ func (s *Session) handleGetApparentType(ctx context.Context, params *GetTypeProp
return setup.newTypeResponse(setup.checker.GetApparentType(t)), nil
}

// handleGetReducedType returns the reduced type of a type.
func (s *Session) handleGetReducedType(ctx context.Context, params *GetTypePropertyParams) (*TypeResponse, error) {
setup, err := s.setupChecker(ctx, params.Snapshot, params.Project)
if err != nil {
return nil, err
}
defer setup.done()

t, err := setup.resolveTypeHandle(params.Type)
if err != nil {
return nil, err
}

return setup.newTypeResponse(setup.checker.GetReducedType(t)), nil
}

// handleGetIndexInfosOfType returns the index infos of a type.
// @gen-proto-nullable
func (s *Session) handleGetIndexInfosOfType(ctx context.Context, params *CheckerTypeParams) ([]*IndexInfoResponse, error) {
Expand Down
4 changes: 4 additions & 0 deletions tsc/internal/checker/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@ func (c *Checker) GetApparentType(t *Type) *Type {
return c.getApparentType(t)
}

func (c *Checker) GetReducedType(t *Type) *Type {
return c.getReducedType(t)
}

// GetFullyQualifiedName returns the fully qualified name of a symbol, walking up
// its parent chain (e.g. `"/path/to/module".Namespace.Name`).
func (c *Checker) GetFullyQualifiedName(symbol *ast.Symbol) string {
Expand Down