From bad6712894132ce94f94a977a7d5afc26931921c Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Fri, 29 May 2026 13:01:00 +0200 Subject: [PATCH 1/7] TS GO API: add support for `isThisType` boolean property. --- _packages/native-preview/src/api/async/api.ts | 2 + .../native-preview/src/api/async/types.ts | 2 + _packages/native-preview/src/api/proto.ts | 1 + _packages/native-preview/src/api/sync/api.ts | 2 + .../native-preview/src/api/sync/types.ts | 2 + .../native-preview/test/async/api.test.ts | 49 +++++++++++++++++++ .../native-preview/test/sync/api.test.ts | 49 +++++++++++++++++++ internal/api/proto.go | 5 ++ 8 files changed, 112 insertions(+) diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index f3947d5222..8e22676290 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -980,6 +980,7 @@ class TypeObject implements Type { readonly flags: TypeFlags; readonly objectFlags!: ObjectFlags; readonly value!: string | number | boolean; + readonly isThisType!: boolean; readonly target!: number; readonly typeParameters!: readonly number[]; readonly outerTypeParameters!: readonly number[]; @@ -1004,6 +1005,7 @@ class TypeObject implements Type { this.flags = data.flags; if (data.objectFlags !== undefined) this.objectFlags = data.objectFlags; if (data.value !== undefined) this.value = data.value; + if (data.isThisType !== undefined) this.isThisType = data.isThisType; if (data.target !== undefined) this.target = data.target; if (data.typeParameters !== undefined) this.typeParameters = data.typeParameters; if (data.outerTypeParameters !== undefined) this.outerTypeParameters = data.outerTypeParameters; diff --git a/_packages/native-preview/src/api/async/types.ts b/_packages/native-preview/src/api/async/types.ts index 7e44ae8e39..b91ec935f2 100644 --- a/_packages/native-preview/src/api/async/types.ts +++ b/_packages/native-preview/src/api/async/types.ts @@ -81,6 +81,8 @@ export interface IntersectionType extends UnionOrIntersectionType { /** Type parameters (TypeFlags.TypeParameter) */ export interface TypeParameter extends Type { + /** True if this is the synthetic `this` type of an interface, class, or tuple */ + readonly isThisType?: boolean; } /** Index types — keyof T (TypeFlags.Index) */ diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts index 593cbe41f8..a0f78a8af0 100644 --- a/_packages/native-preview/src/api/proto.ts +++ b/_packages/native-preview/src/api/proto.ts @@ -154,6 +154,7 @@ export interface TypeResponse { baseType?: number; substConstraint?: number; texts?: string[]; + isThisType?: boolean; symbol?: number; } diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index 2be2762147..1814979c18 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -988,6 +988,7 @@ class TypeObject implements Type { readonly flags: TypeFlags; readonly objectFlags!: ObjectFlags; readonly value!: string | number | boolean; + readonly isThisType!: boolean; readonly target!: number; readonly typeParameters!: readonly number[]; readonly outerTypeParameters!: readonly number[]; @@ -1012,6 +1013,7 @@ class TypeObject implements Type { this.flags = data.flags; if (data.objectFlags !== undefined) this.objectFlags = data.objectFlags; if (data.value !== undefined) this.value = data.value; + if (data.isThisType !== undefined) this.isThisType = data.isThisType; if (data.target !== undefined) this.target = data.target; if (data.typeParameters !== undefined) this.typeParameters = data.typeParameters; if (data.outerTypeParameters !== undefined) this.outerTypeParameters = data.outerTypeParameters; diff --git a/_packages/native-preview/src/api/sync/types.ts b/_packages/native-preview/src/api/sync/types.ts index 51b8032918..5e4c2869e4 100644 --- a/_packages/native-preview/src/api/sync/types.ts +++ b/_packages/native-preview/src/api/sync/types.ts @@ -89,6 +89,8 @@ export interface IntersectionType extends UnionOrIntersectionType { /** Type parameters (TypeFlags.TypeParameter) */ export interface TypeParameter extends Type { + /** True if this is the synthetic `this` type of an interface, class, or tuple */ + readonly isThisType?: boolean; } /** Index types — keyof T (TypeFlags.Index) */ diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index e3cbe21daa..4e7c2b542a 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -44,6 +44,7 @@ import { type TemplateLiteralType, TypeFlags, TypePredicateKind, + type TypeParameter, type TypeReference, type UnionOrIntersectionType, } from "@typescript/native-preview/unstable/async"; // @sync: } from "@typescript/native-preview/unstable/sync"; @@ -1966,6 +1967,54 @@ describe("Checker - getTypeArguments", () => { }); }); +describe("TypeParameter - isThisType", () => { + test("isThisType is true for the polymorphic 'this' type in a class method", async () => { + const src = `\nexport class Builder {\n setName(name: string): this { return this; }\n}\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + // ": this {" — offset 2 past ': ' lands on 't' in the return-type 'this' + const pos = src.indexOf(": this {") + 2; + const type = await project.checker.getTypeAtPosition("/src/main.ts", pos); + assert.ok(type, "Expected a type at the 'this' position"); + assert.ok(type.flags & TypeFlags.TypeParameter, "Expected TypeParameter"); + const typeParam = type as TypeParameter; + assert.equal(typeParam.isThisType, true); + } + finally { + await api.close(); + } + }); + + test("isThisType is absent for a regular generic type parameter", async () => { + const src = `\nexport function identity(x: T): T { return x; }\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + // Point to 'T' in the type parameter declaration '' — getTypeAtPosition + // on a type annotation reference doesn't resolve to TypeParameter, but + // the declaration position does. + const pos = src.indexOf("") + 1; + const type = await project.checker.getTypeAtPosition("/src/main.ts", pos); + assert.ok(type, "Expected a type at the 'T' position"); + assert.ok(type.flags & TypeFlags.TypeParameter, "Expected TypeParameter"); + const typeParam = type as TypeParameter; + assert.ok(!typeParam.isThisType, "Expected isThisType to be absent/false for a regular type parameter"); + } + finally { + await api.close(); + } + }); +}); + describe("Checker - isContextSensitive", () => { test("arrow function with no type annotation is context sensitive", async () => { const api = spawnAPI({ diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index ff666225bc..48b396e133 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -53,6 +53,7 @@ import { SymbolFlags, type TemplateLiteralType, TypeFlags, + type TypeParameter, TypePredicateKind, type TypeReference, type UnionOrIntersectionType, @@ -1974,6 +1975,54 @@ describe("Checker - getTypeArguments", () => { }); }); +describe("TypeParameter - isThisType", () => { + test("isThisType is true for the polymorphic 'this' type in a class method", () => { + const src = `\nexport class Builder {\n setName(name: string): this { return this; }\n}\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + // ": this {" — offset 2 past ': ' lands on 't' in the return-type 'this' + const pos = src.indexOf(": this {") + 2; + const type = project.checker.getTypeAtPosition("/src/main.ts", pos); + assert.ok(type, "Expected a type at the 'this' position"); + assert.ok(type.flags & TypeFlags.TypeParameter, "Expected TypeParameter"); + const typeParam = type as TypeParameter; + assert.equal(typeParam.isThisType, true); + } + finally { + api.close(); + } + }); + + test("isThisType is absent for a regular generic type parameter", () => { + const src = `\nexport function identity(x: T): T { return x; }\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + // Point to 'T' in the type parameter declaration '' — getTypeAtPosition + // on a type annotation reference doesn't resolve to TypeParameter, but + // the declaration position does. + const pos = src.indexOf("") + 1; + const type = project.checker.getTypeAtPosition("/src/main.ts", pos); + assert.ok(type, "Expected a type at the 'T' position"); + assert.ok(type.flags & TypeFlags.TypeParameter, "Expected TypeParameter"); + const typeParam = type as TypeParameter; + assert.ok(!typeParam.isThisType, "Expected isThisType to be absent/false for a regular type parameter"); + } + finally { + api.close(); + } + }); +}); + describe("Checker - isContextSensitive", () => { test("arrow function with no type annotation is context sensitive", () => { const api = spawnAPI({ diff --git a/internal/api/proto.go b/internal/api/proto.go index 260947b1ef..ea98ab7492 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -507,6 +507,9 @@ type TypeResponse struct { // TemplateLiteralType text segments Texts []string `json:"texts,omitempty"` + // TypeParameter data + IsThisType bool `json:"isThisType,omitempty"` + // Symbol associated with structured types Symbol SymbolID `json:"symbol,omitempty"` } @@ -572,6 +575,8 @@ func newTypeData(t *checker.Type) *TypeResponse { // types omitted; fetched via separate request case flags&checker.TypeFlagsStringMapping != 0: resp.Target = TypeHandle(t.AsStringMappingType().Target()) + case flags&checker.TypeFlagsTypeParameter != 0: + resp.IsThisType = t.AsTypeParameter().IsThisType() } return resp From 71fc24360930b7f46667a27960a6386488726426 Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Fri, 29 May 2026 14:06:21 +0200 Subject: [PATCH 2/7] TS GO API: add support for `aliasTypeArguments` property. --- _packages/native-preview/src/api/async/api.ts | 6 ++ .../native-preview/src/api/async/types.ts | 3 + _packages/native-preview/src/api/proto.ts | 1 + _packages/native-preview/src/api/sync/api.ts | 6 ++ .../native-preview/src/api/sync/types.ts | 3 + .../native-preview/test/async/api.test.ts | 71 +++++++++++++++++++ .../native-preview/test/sync/api.test.ts | 71 +++++++++++++++++++ internal/api/proto.go | 9 +++ internal/api/session.go | 11 +++ internal/checker/types.go | 4 ++ 10 files changed, 185 insertions(+) diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index 8e22676290..594ce03ca2 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -985,6 +985,7 @@ class TypeObject implements Type { readonly typeParameters!: readonly number[]; readonly outerTypeParameters!: readonly number[]; readonly localTypeParameters!: readonly number[]; + readonly aliasTypeArguments!: readonly number[]; readonly elementFlags!: readonly ElementFlags[]; readonly fixedLength!: number; readonly readonly!: boolean; @@ -1010,6 +1011,7 @@ class TypeObject implements Type { if (data.typeParameters !== undefined) this.typeParameters = data.typeParameters; if (data.outerTypeParameters !== undefined) this.outerTypeParameters = data.outerTypeParameters; if (data.localTypeParameters !== undefined) this.localTypeParameters = data.localTypeParameters; + if (data.aliasTypeArguments !== undefined) this.aliasTypeArguments = data.aliasTypeArguments; if (data.elementFlags !== undefined) this.elementFlags = data.elementFlags; if (data.fixedLength !== undefined) this.fixedLength = data.fixedLength; if (data.readonly !== undefined) this.readonly = data.readonly; @@ -1060,6 +1062,10 @@ class TypeObject implements Type { return this.fetchTypes("getLocalTypeParametersOfType"); } + async getAliasTypeArguments(): Promise { + return this.fetchTypes("getAliasTypeArgumentsOfType"); + } + async getObjectType(): Promise { return this.fetchType(this.objectType, "getObjectTypeOfType"); } diff --git a/_packages/native-preview/src/api/async/types.ts b/_packages/native-preview/src/api/async/types.ts index b91ec935f2..ff45c6eff2 100644 --- a/_packages/native-preview/src/api/async/types.ts +++ b/_packages/native-preview/src/api/async/types.ts @@ -25,6 +25,9 @@ export interface Type { /** Get the symbol associated with this type, if any */ getSymbol(): Promise; + + /** Get the type arguments of the type alias this type was instantiated from, if any */ + getAliasTypeArguments(): Promise; } /** Literal types: StringLiteral, NumberLiteral, BigIntLiteral, BooleanLiteral */ diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts index a0f78a8af0..0155c9f367 100644 --- a/_packages/native-preview/src/api/proto.ts +++ b/_packages/native-preview/src/api/proto.ts @@ -155,6 +155,7 @@ export interface TypeResponse { substConstraint?: number; texts?: string[]; isThisType?: boolean; + aliasTypeArguments?: number[]; symbol?: number; } diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index 1814979c18..7a5024637f 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -993,6 +993,7 @@ class TypeObject implements Type { readonly typeParameters!: readonly number[]; readonly outerTypeParameters!: readonly number[]; readonly localTypeParameters!: readonly number[]; + readonly aliasTypeArguments!: readonly number[]; readonly elementFlags!: readonly ElementFlags[]; readonly fixedLength!: number; readonly readonly!: boolean; @@ -1018,6 +1019,7 @@ class TypeObject implements Type { if (data.typeParameters !== undefined) this.typeParameters = data.typeParameters; if (data.outerTypeParameters !== undefined) this.outerTypeParameters = data.outerTypeParameters; if (data.localTypeParameters !== undefined) this.localTypeParameters = data.localTypeParameters; + if (data.aliasTypeArguments !== undefined) this.aliasTypeArguments = data.aliasTypeArguments; if (data.elementFlags !== undefined) this.elementFlags = data.elementFlags; if (data.fixedLength !== undefined) this.fixedLength = data.fixedLength; if (data.readonly !== undefined) this.readonly = data.readonly; @@ -1068,6 +1070,10 @@ class TypeObject implements Type { return this.fetchTypes("getLocalTypeParametersOfType"); } + getAliasTypeArguments(): readonly Type[] { + return this.fetchTypes("getAliasTypeArgumentsOfType"); + } + getObjectType(): Type { return this.fetchType(this.objectType, "getObjectTypeOfType"); } diff --git a/_packages/native-preview/src/api/sync/types.ts b/_packages/native-preview/src/api/sync/types.ts index 5e4c2869e4..9b78054f3e 100644 --- a/_packages/native-preview/src/api/sync/types.ts +++ b/_packages/native-preview/src/api/sync/types.ts @@ -33,6 +33,9 @@ export interface Type { /** Get the symbol associated with this type, if any */ getSymbol(): Symbol | undefined; + + /** Get the type arguments of the type alias this type was instantiated from, if any */ + getAliasTypeArguments(): readonly Type[]; } /** Literal types: StringLiteral, NumberLiteral, BigIntLiteral, BooleanLiteral */ diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index 4e7c2b542a..48bd0d99e9 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -2015,6 +2015,77 @@ describe("TypeParameter - isThisType", () => { }); }); +describe("Type - getAliasTypeArguments", () => { + test("returns the type arguments of a single-param generic type alias", async () => { + const src = `\ntype Box = { value: T };\nexport const x: Box = { value: "hi" };\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("x:"); + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasArgs = await type.getAliasTypeArguments(); + assert.equal(aliasArgs.length, 1, "Expected 1 alias type argument"); + assert.ok(aliasArgs[0].flags & TypeFlags.String, `Expected string, got flags ${aliasArgs[0].flags}`); + } + finally { + await api.close(); + } + }); + + test("returns multiple type arguments for a multi-param generic type alias", async () => { + const src = `\ntype Pair = [A, B];\nexport const p: Pair = ["hello", 42];\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("p:"); + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasArgs = await type.getAliasTypeArguments(); + assert.equal(aliasArgs.length, 2, "Expected 2 alias type arguments"); + assert.ok(aliasArgs[0].flags & TypeFlags.String, `Expected first arg to be string, got flags ${aliasArgs[0].flags}`); + assert.ok(aliasArgs[1].flags & TypeFlags.Number, `Expected second arg to be number, got flags ${aliasArgs[1].flags}`); + } + finally { + await api.close(); + } + }); + + test("returns empty array for a non-alias generic type", async () => { + const src = `\nexport const arr: Array = ["hello"];\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("arr:"); + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasArgs = await type.getAliasTypeArguments(); + assert.equal(aliasArgs.length, 0, "Expected no alias type arguments for a direct generic reference"); + } + finally { + await api.close(); + } + }); +}); + describe("Checker - isContextSensitive", () => { test("arrow function with no type annotation is context sensitive", async () => { const api = spawnAPI({ diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index 48b396e133..7a7e81a191 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -2023,6 +2023,77 @@ describe("TypeParameter - isThisType", () => { }); }); +describe("Type - getAliasTypeArguments", () => { + test("returns the type arguments of a single-param generic type alias", () => { + const src = `\ntype Box = { value: T };\nexport const x: Box = { value: "hi" };\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("x:"); + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasArgs = type.getAliasTypeArguments(); + assert.equal(aliasArgs.length, 1, "Expected 1 alias type argument"); + assert.ok(aliasArgs[0].flags & TypeFlags.String, `Expected string, got flags ${aliasArgs[0].flags}`); + } + finally { + api.close(); + } + }); + + test("returns multiple type arguments for a multi-param generic type alias", () => { + const src = `\ntype Pair = [A, B];\nexport const p: Pair = ["hello", 42];\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("p:"); + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasArgs = type.getAliasTypeArguments(); + assert.equal(aliasArgs.length, 2, "Expected 2 alias type arguments"); + assert.ok(aliasArgs[0].flags & TypeFlags.String, `Expected first arg to be string, got flags ${aliasArgs[0].flags}`); + assert.ok(aliasArgs[1].flags & TypeFlags.Number, `Expected second arg to be number, got flags ${aliasArgs[1].flags}`); + } + finally { + api.close(); + } + }); + + test("returns empty array for a non-alias generic type", () => { + const src = `\nexport const arr: Array = ["hello"];\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("arr:"); + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasArgs = type.getAliasTypeArguments(); + assert.equal(aliasArgs.length, 0, "Expected no alias type arguments for a direct generic reference"); + } + finally { + api.close(); + } + }); +}); + describe("Checker - isContextSensitive", () => { test("arrow function with no type annotation is context sensitive", () => { const api = spawnAPI({ diff --git a/internal/api/proto.go b/internal/api/proto.go index ea98ab7492..d162f8c70d 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -87,6 +87,7 @@ const ( MethodGetTypeParametersOfType Method = "getTypeParametersOfType" MethodGetOuterTypeParametersOfType Method = "getOuterTypeParametersOfType" MethodGetLocalTypeParametersOfType Method = "getLocalTypeParametersOfType" + MethodGetAliasTypeArgumentsOfType Method = "getAliasTypeArgumentsOfType" MethodGetObjectTypeOfType Method = "getObjectTypeOfType" MethodGetIndexTypeOfType Method = "getIndexTypeOfType" MethodGetCheckTypeOfType Method = "getCheckTypeOfType" @@ -316,6 +317,7 @@ var unmarshalers = map[Method]func([]byte) (any, error){ MethodGetTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], MethodGetOuterTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], MethodGetLocalTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetAliasTypeArgumentsOfType: unmarshallerFor[GetTypePropertyParams], MethodGetObjectTypeOfType: unmarshallerFor[GetTypePropertyParams], MethodGetIndexTypeOfType: unmarshallerFor[GetTypePropertyParams], MethodGetCheckTypeOfType: unmarshallerFor[GetTypePropertyParams], @@ -510,6 +512,9 @@ type TypeResponse struct { // TypeParameter data IsThisType bool `json:"isThisType,omitempty"` + // TypeAlias data + AliasTypeArguments []TypeID `json:"aliasTypeArguments,omitempty"` + // Symbol associated with structured types Symbol SymbolID `json:"symbol,omitempty"` } @@ -524,6 +529,10 @@ func newTypeData(t *checker.Type) *TypeResponse { resp.Symbol = SymbolHandle(t.Symbol()) } + if t.Alias() != nil { + resp.AliasTypeArguments = typeHandles(t.Alias().TypeArguments()) + } + switch flags := t.Flags(); { case flags&checker.TypeFlagsLiteral != 0: resp.Value = literalValueToJSON(t.AsLiteralType().Value()) diff --git a/internal/api/session.go b/internal/api/session.go index 817eeece89..17dceb2063 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -421,6 +421,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetOuterTypeParametersOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetLocalTypeParametersOfType): return s.handleGetLocalTypeParametersOfType(ctx, parsed.(*GetTypePropertyParams)) + case string(MethodGetAliasTypeArgumentsOfType): + return s.handleGetAliasTypeArgumentsOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetObjectTypeOfType): return s.handleGetObjectTypeOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetIndexTypeOfType): @@ -1286,6 +1288,15 @@ func (s *Session) handleGetLocalTypeParametersOfType(_ context.Context, params * }) } +func (s *Session) handleGetAliasTypeArgumentsOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { + return s.resolveTypeArrayProperty(params, func(t *checker.Type) []*checker.Type { + if t.Alias() == nil { + return nil + } + return t.Alias().TypeArguments() + }) +} + func (s *Session) handleGetObjectTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { return t.AsIndexedAccessType().ObjectType() diff --git a/internal/checker/types.go b/internal/checker/types.go index 83106fcdc4..ded70120da 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -777,6 +777,10 @@ func (t *Type) Symbol() *ast.Symbol { return t.symbol } +func (t *Type) Alias() *TypeAlias { + return t.alias +} + func (t *Type) IsUnion() bool { return t.flags&TypeFlagsUnion != 0 } From 2b2699da43646a60d36185ce99549279661e6aff Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Fri, 29 May 2026 14:18:22 +0200 Subject: [PATCH 3/7] TS GO API: add support for `FreshableType`. --- _packages/native-preview/src/api/async/api.ts | 23 +++- .../native-preview/src/api/async/types.ts | 12 +- _packages/native-preview/src/api/proto.ts | 2 + _packages/native-preview/src/api/sync/api.ts | 23 +++- .../native-preview/src/api/sync/types.ts | 12 +- .../native-preview/test/async/api.test.ts | 125 ++++++++++++++++++ .../native-preview/test/sync/api.test.ts | 125 ++++++++++++++++++ internal/api/proto.go | 21 ++- internal/api/session.go | 22 +++ internal/checker/types.go | 8 ++ 10 files changed, 367 insertions(+), 6 deletions(-) diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index 594ce03ca2..09bf047d0e 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -67,6 +67,7 @@ import type { AssertsThisTypePredicate, ConditionalType, Diagnostic, + FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, @@ -91,7 +92,7 @@ import type { export { DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdentifier, DocumentPosition, LSPConnectionOptions }; -export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; +export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; /** Type alias for the snapshot-scoped object registry */ @@ -981,6 +982,8 @@ class TypeObject implements Type { readonly objectFlags!: ObjectFlags; readonly value!: string | number | boolean; readonly isThisType!: boolean; + readonly freshType!: number; + readonly regularType!: number; readonly target!: number; readonly typeParameters!: readonly number[]; readonly outerTypeParameters!: readonly number[]; @@ -1007,6 +1010,8 @@ class TypeObject implements Type { if (data.objectFlags !== undefined) this.objectFlags = data.objectFlags; if (data.value !== undefined) this.value = data.value; if (data.isThisType !== undefined) this.isThisType = data.isThisType; + if (data.freshType !== undefined) this.freshType = data.freshType; + if (data.regularType !== undefined) this.regularType = data.regularType; if (data.target !== undefined) this.target = data.target; if (data.typeParameters !== undefined) this.typeParameters = data.typeParameters; if (data.outerTypeParameters !== undefined) this.outerTypeParameters = data.outerTypeParameters; @@ -1046,6 +1051,22 @@ class TypeObject implements Type { return this.fetchType(this.target, "getTargetOfType"); } + async getFreshType(): Promise { + if (!this.freshType) return undefined; + const cached = this.objectRegistry.getType(this.freshType); + if (cached) return cached as FreshableType; + const data = await this.client.apiRequest("getFreshTypeOfType", { snapshot: this.snapshotId, type: this.id }); + return data ? this.objectRegistry.getOrCreateType(data) as FreshableType : undefined; + } + + async getRegularType(): Promise { + if (!this.regularType) return undefined; + const cached = this.objectRegistry.getType(this.regularType); + if (cached) return cached as FreshableType; + const data = await this.client.apiRequest("getRegularTypeOfType", { snapshot: this.snapshotId, type: this.id }); + return data ? this.objectRegistry.getOrCreateType(data) as FreshableType : undefined; + } + async getTypes(): Promise { return this.fetchTypes("getTypesOfType"); } diff --git a/_packages/native-preview/src/api/async/types.ts b/_packages/native-preview/src/api/async/types.ts index ff45c6eff2..4476b1d365 100644 --- a/_packages/native-preview/src/api/async/types.ts +++ b/_packages/native-preview/src/api/async/types.ts @@ -30,8 +30,18 @@ export interface Type { getAliasTypeArguments(): Promise; } +/** + * Freshable types (TypeFlags.Freshable) - literal types (TypeFlags.Literal) and computed enum types (TypeFlags.Enum). + */ +export interface FreshableType extends Type { + /** Get the fresh version of this type, if any */ + getFreshType(): Promise; + /** Get the regular (non-fresh) version of this type, if any */ + getRegularType(): Promise; +} + /** Literal types: StringLiteral, NumberLiteral, BigIntLiteral, BooleanLiteral */ -export interface LiteralType extends Type { +export interface LiteralType extends FreshableType { /** The literal value */ readonly value: string | number | boolean; } diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts index 0155c9f367..42d0a28876 100644 --- a/_packages/native-preview/src/api/proto.ts +++ b/_packages/native-preview/src/api/proto.ts @@ -140,6 +140,8 @@ export interface TypeResponse { flags: number; objectFlags?: number; value?: string | number | boolean; + freshType?: number; + regularType?: number; target?: number; typeParameters?: number[]; outerTypeParameters?: number[]; diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index 7a5024637f..41d7dcc865 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -75,6 +75,7 @@ import type { AssertsThisTypePredicate, ConditionalType, Diagnostic, + FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, @@ -99,7 +100,7 @@ import type { export { DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdentifier, DocumentPosition, LSPConnectionOptions }; -export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; +export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; /** Type alias for the snapshot-scoped object registry */ @@ -989,6 +990,8 @@ class TypeObject implements Type { readonly objectFlags!: ObjectFlags; readonly value!: string | number | boolean; readonly isThisType!: boolean; + readonly freshType!: number; + readonly regularType!: number; readonly target!: number; readonly typeParameters!: readonly number[]; readonly outerTypeParameters!: readonly number[]; @@ -1015,6 +1018,8 @@ class TypeObject implements Type { if (data.objectFlags !== undefined) this.objectFlags = data.objectFlags; if (data.value !== undefined) this.value = data.value; if (data.isThisType !== undefined) this.isThisType = data.isThisType; + if (data.freshType !== undefined) this.freshType = data.freshType; + if (data.regularType !== undefined) this.regularType = data.regularType; if (data.target !== undefined) this.target = data.target; if (data.typeParameters !== undefined) this.typeParameters = data.typeParameters; if (data.outerTypeParameters !== undefined) this.outerTypeParameters = data.outerTypeParameters; @@ -1054,6 +1059,22 @@ class TypeObject implements Type { return this.fetchType(this.target, "getTargetOfType"); } + getFreshType(): FreshableType | undefined { + if (!this.freshType) return undefined; + const cached = this.objectRegistry.getType(this.freshType); + if (cached) return cached as FreshableType; + const data = this.client.apiRequest("getFreshTypeOfType", { snapshot: this.snapshotId, type: this.id }); + return data ? this.objectRegistry.getOrCreateType(data) as FreshableType : undefined; + } + + getRegularType(): FreshableType | undefined { + if (!this.regularType) return undefined; + const cached = this.objectRegistry.getType(this.regularType); + if (cached) return cached as FreshableType; + const data = this.client.apiRequest("getRegularTypeOfType", { snapshot: this.snapshotId, type: this.id }); + return data ? this.objectRegistry.getOrCreateType(data) as FreshableType : undefined; + } + getTypes(): readonly Type[] { return this.fetchTypes("getTypesOfType"); } diff --git a/_packages/native-preview/src/api/sync/types.ts b/_packages/native-preview/src/api/sync/types.ts index 9b78054f3e..cb4de75bc1 100644 --- a/_packages/native-preview/src/api/sync/types.ts +++ b/_packages/native-preview/src/api/sync/types.ts @@ -38,8 +38,18 @@ export interface Type { getAliasTypeArguments(): readonly Type[]; } +/** + * Freshable types (TypeFlags.Freshable) - literal types (TypeFlags.Literal) and computed enum types (TypeFlags.Enum). + */ +export interface FreshableType extends Type { + /** Get the fresh version of this type, if any */ + getFreshType(): FreshableType | undefined; + /** Get the regular (non-fresh) version of this type, if any */ + getRegularType(): FreshableType | undefined; +} + /** Literal types: StringLiteral, NumberLiteral, BigIntLiteral, BooleanLiteral */ -export interface LiteralType extends Type { +export interface LiteralType extends FreshableType { /** The literal value */ readonly value: string | number | boolean; } diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index 48bd0d99e9..a9c4f74500 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -34,8 +34,10 @@ import { API, type ConditionalType, DiagnosticCategory, + type FreshableType, type IndexedAccessType, type IndexType, + type LiteralType, ModifierFlags, ObjectFlags, SignatureKind, @@ -2086,6 +2088,129 @@ describe("Type - getAliasTypeArguments", () => { }); }); +describe("FreshableType - getFreshType and getRegularType", () => { + test("LiteralType.value is accessible via the FreshableType hierarchy", async () => { + const src = `\nexport const greeting: "hello" = "hello";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("greeting:"); + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.ok(type.flags & TypeFlags.StringLiteral, "Expected StringLiteral"); + const literal = type as LiteralType; + assert.equal(literal.value, "hello"); + } + finally { + await api.close(); + } + }); + + test("getFreshType() returns a fresh twin with matching value", async () => { + const src = `\nexport const greeting: "hello" = "hello";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("greeting:"); + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.ok(type.flags & TypeFlags.Freshable, "Type should be a freshable type"); + const fresh = await (type as FreshableType).getFreshType(); + assert.ok(fresh, "Expected getFreshType() to return non-undefined for a literal type"); + assert.ok(fresh.flags & TypeFlags.StringLiteral, "Fresh type should be a StringLiteral"); + assert.equal((fresh as LiteralType).value, "hello", "Fresh type should carry the same value"); + assert.notEqual(fresh.id, type.id, "Fresh type should not be the original type"); + const freshFresh = await fresh.getFreshType(); + assert.ok(freshFresh, "Expected getFreshType() to return non-undefined for a fresh type"); + assert.equal(freshFresh.id, fresh.id, "Fresh type of a fresh type should be the fresh type"); + } + finally { + await api.close(); + } + }); + + test("getRegularType() on a fresh literal returns the regular twin", async () => { + // The initial type response from getTypeOfSymbol does not always include the + // regularType handle, so getRegularType() is tested via the fresh twin which + // always includes its regularType in its own response. + const src = `\nexport const greeting: "hello" = "hello";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("greeting:"); + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.ok(type.flags & TypeFlags.Freshable, "Type should be a freshable type"); + const fresh = await (type as FreshableType).getFreshType(); + assert.ok(fresh, "Need fresh type for this test"); + assert.ok(fresh.flags & TypeFlags.Freshable, "Fresh type should be a freshable type"); + const regular = await fresh.getRegularType(); + assert.ok(regular, "Expected getRegularType() on the fresh twin to return non-undefined"); + assert.ok(regular.flags & TypeFlags.StringLiteral, "Regular type should be a StringLiteral"); + assert.equal((regular as LiteralType).value, "hello", "Regular type should carry the same value"); + assert.equal(regular.id, type.id, "Regular type should be the original type"); + } + finally { + await api.close(); + } + }); + + test("getFreshType() and getRegularType() work for computed enum types (TypeFlags.Enum)", async () => { + // getTypeOfSymbol on an ambient enum member returns the FRESH computed enum type. + // For fresh types: getFreshType() returns self, getRegularType() returns the regular twin. + const src = `\ndeclare enum Status { Pending }\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("Pending"); + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.ok(type.flags & TypeFlags.Enum, `Expected TypeFlags.Enum, got ${type.flags}`); + assert.ok(type.flags & TypeFlags.Freshable, "Enum type should be freshable"); + // The returned type IS the fresh type: getFreshType() returns itself + const fresh = await (type as FreshableType).getFreshType(); + assert.ok(fresh, "Expected getFreshType() to return non-undefined"); + assert.equal(fresh.id, type.id, "getFreshType() on a fresh enum type returns itself"); + // getRegularType() returns the regular twin (a different type) + const regular = await (type as FreshableType).getRegularType(); + assert.ok(regular, "Expected getRegularType() to return non-undefined"); + assert.ok(regular.flags & TypeFlags.Enum, "Regular enum type should also have TypeFlags.Enum"); + assert.notEqual(regular.id, type.id, "Regular type should be distinct from the fresh type"); + // Round-trip: regular → getFreshType() → back to the original fresh type + const backToFresh = await (regular as FreshableType).getFreshType(); + assert.ok(backToFresh); + assert.equal(backToFresh.id, type.id, "Round-trip through regular/fresh returns the original type"); + } + finally { + await api.close(); + } + }); +}); + describe("Checker - isContextSensitive", () => { test("arrow function with no type annotation is context sensitive", async () => { const api = spawnAPI({ diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index 7a7e81a191..a82ccdf922 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -44,8 +44,10 @@ import { API, type ConditionalType, DiagnosticCategory, + type FreshableType, type IndexedAccessType, type IndexType, + type LiteralType, ModifierFlags, ObjectFlags, SignatureKind, @@ -2094,6 +2096,129 @@ describe("Type - getAliasTypeArguments", () => { }); }); +describe("FreshableType - getFreshType and getRegularType", () => { + test("LiteralType.value is accessible via the FreshableType hierarchy", () => { + const src = `\nexport const greeting: "hello" = "hello";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("greeting:"); + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.ok(type.flags & TypeFlags.StringLiteral, "Expected StringLiteral"); + const literal = type as LiteralType; + assert.equal(literal.value, "hello"); + } + finally { + api.close(); + } + }); + + test("getFreshType() returns a fresh twin with matching value", () => { + const src = `\nexport const greeting: "hello" = "hello";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("greeting:"); + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.ok(type.flags & TypeFlags.Freshable, "Type should be a freshable type"); + const fresh = (type as FreshableType).getFreshType(); + assert.ok(fresh, "Expected getFreshType() to return non-undefined for a literal type"); + assert.ok(fresh.flags & TypeFlags.StringLiteral, "Fresh type should be a StringLiteral"); + assert.equal((fresh as LiteralType).value, "hello", "Fresh type should carry the same value"); + assert.notEqual(fresh.id, type.id, "Fresh type should not be the original type"); + const freshFresh = fresh.getFreshType(); + assert.ok(freshFresh, "Expected getFreshType() to return non-undefined for a fresh type"); + assert.equal(freshFresh.id, fresh.id, "Fresh type of a fresh type should be the fresh type"); + } + finally { + api.close(); + } + }); + + test("getRegularType() on a fresh literal returns the regular twin", () => { + // The initial type response from getTypeOfSymbol does not always include the + // regularType handle, so getRegularType() is tested via the fresh twin which + // always includes its regularType in its own response. + const src = `\nexport const greeting: "hello" = "hello";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("greeting:"); + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.ok(type.flags & TypeFlags.Freshable, "Type should be a freshable type"); + const fresh = (type as FreshableType).getFreshType(); + assert.ok(fresh, "Need fresh type for this test"); + assert.ok(fresh.flags & TypeFlags.Freshable, "Fresh type should be a freshable type"); + const regular = fresh.getRegularType(); + assert.ok(regular, "Expected getRegularType() on the fresh twin to return non-undefined"); + assert.ok(regular.flags & TypeFlags.StringLiteral, "Regular type should be a StringLiteral"); + assert.equal((regular as LiteralType).value, "hello", "Regular type should carry the same value"); + assert.equal(regular.id, type.id, "Regular type should be the original type"); + } + finally { + api.close(); + } + }); + + test("getFreshType() and getRegularType() work for computed enum types (TypeFlags.Enum)", () => { + // getTypeOfSymbol on an ambient enum member returns the FRESH computed enum type. + // For fresh types: getFreshType() returns self, getRegularType() returns the regular twin. + const src = `\ndeclare enum Status { Pending }\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("Pending"); + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.ok(type.flags & TypeFlags.Enum, `Expected TypeFlags.Enum, got ${type.flags}`); + assert.ok(type.flags & TypeFlags.Freshable, "Enum type should be freshable"); + // The returned type IS the fresh type: getFreshType() returns itself + const fresh = (type as FreshableType).getFreshType(); + assert.ok(fresh, "Expected getFreshType() to return non-undefined"); + assert.equal(fresh.id, type.id, "getFreshType() on a fresh enum type returns itself"); + // getRegularType() returns the regular twin (a different type) + const regular = (type as FreshableType).getRegularType(); + assert.ok(regular, "Expected getRegularType() to return non-undefined"); + assert.ok(regular.flags & TypeFlags.Enum, "Regular enum type should also have TypeFlags.Enum"); + assert.notEqual(regular.id, type.id, "Regular type should be distinct from the fresh type"); + // Round-trip: regular → getFreshType() → back to the original fresh type + const backToFresh = (regular as FreshableType).getFreshType(); + assert.ok(backToFresh); + assert.equal(backToFresh.id, type.id, "Round-trip through regular/fresh returns the original type"); + } + finally { + api.close(); + } + }); +}); + describe("Checker - isContextSensitive", () => { test("arrow function with no type annotation is context sensitive", () => { const api = spawnAPI({ diff --git a/internal/api/proto.go b/internal/api/proto.go index d162f8c70d..577f2c1b1b 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -83,6 +83,8 @@ const ( // Type sub-property methods MethodGetTargetOfType Method = "getTargetOfType" + MethodGetFreshTypeOfType Method = "getFreshTypeOfType" + MethodGetRegularTypeOfType Method = "getRegularTypeOfType" MethodGetTypesOfType Method = "getTypesOfType" MethodGetTypeParametersOfType Method = "getTypeParametersOfType" MethodGetOuterTypeParametersOfType Method = "getOuterTypeParametersOfType" @@ -313,6 +315,8 @@ var unmarshalers = map[Method]func([]byte) (any, error){ MethodGetTypesAtPositions: unmarshallerFor[GetTypesAtPositionsParams], MethodGetTargetOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetFreshTypeOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetRegularTypeOfType: unmarshallerFor[GetTypePropertyParams], MethodGetTypesOfType: unmarshallerFor[GetTypePropertyParams], MethodGetTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], MethodGetOuterTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], @@ -509,6 +513,10 @@ type TypeResponse struct { // TemplateLiteralType text segments Texts []string `json:"texts,omitempty"` + // FreshableType data (LiteralType and computed enum types) + FreshType TypeID `json:"freshType,omitempty"` + RegularType TypeID `json:"regularType,omitempty"` + // TypeParameter data IsThisType bool `json:"isThisType,omitempty"` @@ -534,8 +542,17 @@ func newTypeData(t *checker.Type) *TypeResponse { } switch flags := t.Flags(); { - case flags&checker.TypeFlagsLiteral != 0: - resp.Value = literalValueToJSON(t.AsLiteralType().Value()) + case flags&checker.TypeFlagsFreshable != 0: + lit := t.AsLiteralType() + if flags&checker.TypeFlagsLiteral != 0 { + resp.Value = literalValueToJSON(lit.Value()) + } + if lit.FreshType() != nil { + resp.FreshType = TypeHandle(lit.FreshType()) + } + if lit.RegularType() != nil { + resp.RegularType = TypeHandle(lit.RegularType()) + } case flags&checker.TypeFlagsObject != 0: resp.ObjectFlags = uint32(t.ObjectFlags()) objectFlags := t.ObjectFlags() diff --git a/internal/api/session.go b/internal/api/session.go index 17dceb2063..e8f0149b7d 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -413,6 +413,10 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetTypesAtPositions(ctx, parsed.(*GetTypesAtPositionsParams)) case string(MethodGetTargetOfType): return s.handleGetTargetOfType(ctx, parsed.(*GetTypePropertyParams)) + case string(MethodGetFreshTypeOfType): + return s.handleGetFreshTypeOfType(ctx, parsed.(*GetTypePropertyParams)) + case string(MethodGetRegularTypeOfType): + return s.handleGetRegularTypeOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetTypesOfType): return s.handleGetTypesOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetTypeParametersOfType): @@ -1266,6 +1270,24 @@ func (s *Session) handleGetTargetOfType(_ context.Context, params *GetTypeProper return s.resolveTypeProperty(params, (*checker.Type).Target) } +func (s *Session) handleGetFreshTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { + if t.Flags()&checker.TypeFlagsFreshable == 0 { + return nil + } + return t.AsLiteralType().FreshType() + }) +} + +func (s *Session) handleGetRegularTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { + if t.Flags()&checker.TypeFlagsFreshable == 0 { + return nil + } + return t.AsLiteralType().RegularType() + }) +} + func (s *Session) handleGetTypesOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { return s.resolveTypeArrayProperty(params, (*checker.Type).Types) } diff --git a/internal/checker/types.go b/internal/checker/types.go index ded70120da..eeb17751cd 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -881,6 +881,14 @@ func (t *LiteralType) Value() any { return t.value } +func (t *LiteralType) FreshType() *Type { + return t.freshType +} + +func (t *LiteralType) RegularType() *Type { + return t.regularType +} + func (t *LiteralType) String() string { return ValueToString(t.value) } From 38cf26e9dadaeff2795237b9ed38895262826599 Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Fri, 29 May 2026 14:25:26 +0200 Subject: [PATCH 4/7] TS GO API: add support for `aliasSymbol` property. --- _packages/native-preview/src/api/async/api.ts | 10 +++ .../native-preview/src/api/async/types.ts | 3 + .../native-preview/src/api/objectRegistry.ts | 4 ++ _packages/native-preview/src/api/proto.ts | 1 + _packages/native-preview/src/api/sync/api.ts | 10 +++ .../native-preview/src/api/sync/types.ts | 3 + .../native-preview/test/async/api.test.ts | 71 +++++++++++++++++++ .../native-preview/test/sync/api.test.ts | 71 +++++++++++++++++++ internal/api/proto.go | 6 ++ internal/api/session.go | 20 ++++++ 10 files changed, 199 insertions(+) diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index 09bf047d0e..5c31f272d7 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -989,6 +989,7 @@ class TypeObject implements Type { readonly outerTypeParameters!: readonly number[]; readonly localTypeParameters!: readonly number[]; readonly aliasTypeArguments!: readonly number[]; + readonly aliasSymbol!: number; readonly elementFlags!: readonly ElementFlags[]; readonly fixedLength!: number; readonly readonly!: boolean; @@ -1017,6 +1018,7 @@ class TypeObject implements Type { if (data.outerTypeParameters !== undefined) this.outerTypeParameters = data.outerTypeParameters; if (data.localTypeParameters !== undefined) this.localTypeParameters = data.localTypeParameters; if (data.aliasTypeArguments !== undefined) 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; @@ -1034,6 +1036,14 @@ class TypeObject implements Type { return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } + async getAliasSymbol(): Promise { + if (!this.aliasSymbol) return undefined; + const cached = this.objectRegistry.getSymbol(this.aliasSymbol); + if (cached) return cached; + const data = await this.client.apiRequest("getAliasSymbolOfType", { snapshot: this.snapshotId, type: this.id }); + return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; + } + private async fetchType(handle: number | undefined, method: string): Promise { const cached = handle !== undefined ? this.objectRegistry.getType(handle) : undefined; if (cached) return cached as Type; diff --git a/_packages/native-preview/src/api/async/types.ts b/_packages/native-preview/src/api/async/types.ts index 4476b1d365..41cda52a7d 100644 --- a/_packages/native-preview/src/api/async/types.ts +++ b/_packages/native-preview/src/api/async/types.ts @@ -28,6 +28,9 @@ export interface Type { /** Get the type arguments of the type alias this type was instantiated from, if any */ getAliasTypeArguments(): Promise; + + /** Get the symbol of the type alias this type was instantiated from, if any */ + getAliasSymbol(): Promise; } /** diff --git a/_packages/native-preview/src/api/objectRegistry.ts b/_packages/native-preview/src/api/objectRegistry.ts index 78e90bc854..3913c39396 100644 --- a/_packages/native-preview/src/api/objectRegistry.ts +++ b/_packages/native-preview/src/api/objectRegistry.ts @@ -70,6 +70,10 @@ export class ObjectRegistry< return this.types.get(id); } + getSymbol(id: number): TSymbol | undefined { + return this.symbols.get(id); + } + getOrCreateSignature(data: SignatureResponse): TSignature { let signature = this.signatures.get(data.id); if (signature) { diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts index 42d0a28876..7ee5184500 100644 --- a/_packages/native-preview/src/api/proto.ts +++ b/_packages/native-preview/src/api/proto.ts @@ -158,6 +158,7 @@ export interface TypeResponse { texts?: string[]; isThisType?: boolean; aliasTypeArguments?: number[]; + aliasSymbol?: number; symbol?: number; } diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index 41d7dcc865..35652d0cb1 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -997,6 +997,7 @@ class TypeObject implements Type { readonly outerTypeParameters!: readonly number[]; readonly localTypeParameters!: readonly number[]; readonly aliasTypeArguments!: readonly number[]; + readonly aliasSymbol!: number; readonly elementFlags!: readonly ElementFlags[]; readonly fixedLength!: number; readonly readonly!: boolean; @@ -1025,6 +1026,7 @@ class TypeObject implements Type { if (data.outerTypeParameters !== undefined) this.outerTypeParameters = data.outerTypeParameters; if (data.localTypeParameters !== undefined) this.localTypeParameters = data.localTypeParameters; if (data.aliasTypeArguments !== undefined) 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; @@ -1042,6 +1044,14 @@ class TypeObject implements Type { return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; } + getAliasSymbol(): Symbol | undefined { + if (!this.aliasSymbol) return undefined; + const cached = this.objectRegistry.getSymbol(this.aliasSymbol); + if (cached) return cached; + const data = this.client.apiRequest("getAliasSymbolOfType", { snapshot: this.snapshotId, type: this.id }); + return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; + } + private fetchType(handle: number | undefined, method: string): Type { const cached = handle !== undefined ? this.objectRegistry.getType(handle) : undefined; if (cached) return cached as Type; diff --git a/_packages/native-preview/src/api/sync/types.ts b/_packages/native-preview/src/api/sync/types.ts index cb4de75bc1..0d0fee9a2a 100644 --- a/_packages/native-preview/src/api/sync/types.ts +++ b/_packages/native-preview/src/api/sync/types.ts @@ -36,6 +36,9 @@ export interface Type { /** Get the type arguments of the type alias this type was instantiated from, if any */ getAliasTypeArguments(): readonly Type[]; + + /** Get the symbol of the type alias this type was instantiated from, if any */ + getAliasSymbol(): Symbol | undefined; } /** diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index a9c4f74500..0ec2184657 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -2088,6 +2088,77 @@ describe("Type - getAliasTypeArguments", () => { }); }); +describe("Type - getAliasSymbol", () => { + test("returns the symbol for a non-generic type alias", async () => { + // Object-type aliases preserve aliasSymbol; primitive aliases (type Foo = string) do not. + const src = `\ntype Point = { x: number; y: number };\nexport const p: Point = { x: 1, y: 2 };\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("p:"); + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasSymbol = await type.getAliasSymbol(); + assert.ok(aliasSymbol, "Expected alias symbol to exist"); + assert.equal(aliasSymbol.name, "Point"); + } + finally { + await api.close(); + } + }); + + test("returns the symbol for a generic type alias", async () => { + const src = `\ntype Container = { item: T };\nexport const c: Container = { item: 42 };\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("c:"); + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasSymbol = await type.getAliasSymbol(); + assert.ok(aliasSymbol, "Expected alias symbol for generic alias"); + assert.equal(aliasSymbol.name, "Container"); + } + finally { + await api.close(); + } + }); + + test("returns undefined for a non-alias type", async () => { + const src = `\nexport const str: string = "test";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("str:"); + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasSymbol = await type.getAliasSymbol(); + assert.equal(aliasSymbol, undefined, "Expected no alias symbol for primitive type"); + } + finally { + await api.close(); + } + }); +}); + describe("FreshableType - getFreshType and getRegularType", () => { test("LiteralType.value is accessible via the FreshableType hierarchy", async () => { const src = `\nexport const greeting: "hello" = "hello";\n`; diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index a82ccdf922..71e05f690a 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -2096,6 +2096,77 @@ describe("Type - getAliasTypeArguments", () => { }); }); +describe("Type - getAliasSymbol", () => { + test("returns the symbol for a non-generic type alias", () => { + // Object-type aliases preserve aliasSymbol; primitive aliases (type Foo = string) do not. + const src = `\ntype Point = { x: number; y: number };\nexport const p: Point = { x: 1, y: 2 };\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("p:"); + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasSymbol = type.getAliasSymbol(); + assert.ok(aliasSymbol, "Expected alias symbol to exist"); + assert.equal(aliasSymbol.name, "Point"); + } + finally { + api.close(); + } + }); + + test("returns the symbol for a generic type alias", () => { + const src = `\ntype Container = { item: T };\nexport const c: Container = { item: 42 };\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("c:"); + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasSymbol = type.getAliasSymbol(); + assert.ok(aliasSymbol, "Expected alias symbol for generic alias"); + assert.equal(aliasSymbol.name, "Container"); + } + finally { + api.close(); + } + }); + + test("returns undefined for a non-alias type", () => { + const src = `\nexport const str: string = "test";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("str:"); + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const aliasSymbol = type.getAliasSymbol(); + assert.equal(aliasSymbol, undefined, "Expected no alias symbol for primitive type"); + } + finally { + api.close(); + } + }); +}); + describe("FreshableType - getFreshType and getRegularType", () => { test("LiteralType.value is accessible via the FreshableType hierarchy", () => { const src = `\nexport const greeting: "hello" = "hello";\n`; diff --git a/internal/api/proto.go b/internal/api/proto.go index 577f2c1b1b..41e8b21b0b 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -90,6 +90,7 @@ const ( MethodGetOuterTypeParametersOfType Method = "getOuterTypeParametersOfType" MethodGetLocalTypeParametersOfType Method = "getLocalTypeParametersOfType" MethodGetAliasTypeArgumentsOfType Method = "getAliasTypeArgumentsOfType" + MethodGetAliasSymbolOfType Method = "getAliasSymbolOfType" MethodGetObjectTypeOfType Method = "getObjectTypeOfType" MethodGetIndexTypeOfType Method = "getIndexTypeOfType" MethodGetCheckTypeOfType Method = "getCheckTypeOfType" @@ -322,6 +323,7 @@ var unmarshalers = map[Method]func([]byte) (any, error){ MethodGetOuterTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], MethodGetLocalTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], MethodGetAliasTypeArgumentsOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetAliasSymbolOfType: unmarshallerFor[GetTypePropertyParams], MethodGetObjectTypeOfType: unmarshallerFor[GetTypePropertyParams], MethodGetIndexTypeOfType: unmarshallerFor[GetTypePropertyParams], MethodGetCheckTypeOfType: unmarshallerFor[GetTypePropertyParams], @@ -522,6 +524,7 @@ type TypeResponse struct { // TypeAlias data AliasTypeArguments []TypeID `json:"aliasTypeArguments,omitempty"` + AliasSymbol SymbolID `json:"aliasSymbol,omitempty"` // Symbol associated with structured types Symbol SymbolID `json:"symbol,omitempty"` @@ -539,6 +542,9 @@ func newTypeData(t *checker.Type) *TypeResponse { if t.Alias() != nil { resp.AliasTypeArguments = typeHandles(t.Alias().TypeArguments()) + if t.Alias().Symbol() != nil { + resp.AliasSymbol = SymbolHandle(t.Alias().Symbol()) + } } switch flags := t.Flags(); { diff --git a/internal/api/session.go b/internal/api/session.go index e8f0149b7d..8e2bcbaff0 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -427,6 +427,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetLocalTypeParametersOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetAliasTypeArgumentsOfType): return s.handleGetAliasTypeArgumentsOfType(ctx, parsed.(*GetTypePropertyParams)) + case string(MethodGetAliasSymbolOfType): + return s.handleGetAliasSymbolOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetObjectTypeOfType): return s.handleGetObjectTypeOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetIndexTypeOfType): @@ -1319,6 +1321,24 @@ func (s *Session) handleGetAliasTypeArgumentsOfType(_ context.Context, params *G }) } +func (s *Session) handleGetAliasSymbolOfType(_ context.Context, params *GetTypePropertyParams) (*SymbolResponse, error) { + sd, err := s.getSnapshotData(params.Snapshot) + if err != nil { + return nil, err + } + + t, err := sd.resolveTypeHandle(params.Type) + if err != nil { + return nil, err + } + + if t.Alias() == nil || t.Alias().Symbol() == nil { + return nil, nil + } + + return sd.registerSymbol(t.Alias().Symbol()), nil +} + func (s *Session) handleGetObjectTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { return t.AsIndexedAccessType().ObjectType() From 23c59f760f106c2450104addb64e6a25831c0213 Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Fri, 29 May 2026 15:33:58 +0200 Subject: [PATCH 5/7] TS GO API: add support for `intrinsicName` property. --- _packages/native-preview/src/api/async/api.ts | 5 ++- .../native-preview/src/api/async/types.ts | 6 ++++ _packages/native-preview/src/api/proto.ts | 1 + _packages/native-preview/src/api/sync/api.ts | 5 ++- .../native-preview/src/api/sync/types.ts | 6 ++++ .../native-preview/test/async/api.test.ts | 31 +++++++++++++++++++ .../native-preview/test/sync/api.test.ts | 31 +++++++++++++++++++ internal/api/proto.go | 5 +++ 8 files changed, 88 insertions(+), 2 deletions(-) diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index 5c31f272d7..71bb36ebf8 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -74,6 +74,7 @@ import type { IndexType, InterfaceType, IntersectionType, + IntrinsicType, LiteralType, ObjectType, StringMappingType, @@ -92,7 +93,7 @@ import type { export { DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdentifier, DocumentPosition, LSPConnectionOptions }; -export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; +export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; /** Type alias for the snapshot-scoped object registry */ @@ -981,6 +982,7 @@ class TypeObject implements Type { readonly flags: TypeFlags; readonly objectFlags!: ObjectFlags; readonly value!: string | number | boolean; + readonly intrinsicName!: string; readonly isThisType!: boolean; readonly freshType!: number; readonly regularType!: number; @@ -1010,6 +1012,7 @@ class TypeObject implements Type { this.flags = data.flags; if (data.objectFlags !== undefined) this.objectFlags = data.objectFlags; if (data.value !== undefined) this.value = data.value; + if (data.intrinsicName !== undefined) this.intrinsicName = data.intrinsicName; if (data.isThisType !== undefined) this.isThisType = data.isThisType; if (data.freshType !== undefined) this.freshType = data.freshType; if (data.regularType !== undefined) this.regularType = data.regularType; diff --git a/_packages/native-preview/src/api/async/types.ts b/_packages/native-preview/src/api/async/types.ts index 41cda52a7d..0d344ba17b 100644 --- a/_packages/native-preview/src/api/async/types.ts +++ b/_packages/native-preview/src/api/async/types.ts @@ -143,6 +143,12 @@ export interface StringMappingType extends Type { getTarget(): Promise; } +/** Intrinsic types — any, unknown, string, number, bigint, symbol, void, undefined, null, never, object (TypeFlags.Intrinsic) */ +export interface IntrinsicType extends Type { + /** The intrinsic type name (e.g. "any", "string", "never") */ + readonly intrinsicName: string; +} + /** Base for all type predicates */ export interface TypePredicateBase { readonly kind: TypePredicateKind; diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts index 7ee5184500..f76102aa8f 100644 --- a/_packages/native-preview/src/api/proto.ts +++ b/_packages/native-preview/src/api/proto.ts @@ -156,6 +156,7 @@ export interface TypeResponse { baseType?: number; substConstraint?: number; texts?: string[]; + intrinsicName?: string; isThisType?: boolean; aliasTypeArguments?: number[]; aliasSymbol?: number; diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index 35652d0cb1..05792e1f2d 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -82,6 +82,7 @@ import type { IndexType, InterfaceType, IntersectionType, + IntrinsicType, LiteralType, ObjectType, StringMappingType, @@ -100,7 +101,7 @@ import type { export { DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdentifier, DocumentPosition, LSPConnectionOptions }; -export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; +export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; /** Type alias for the snapshot-scoped object registry */ @@ -989,6 +990,7 @@ class TypeObject implements Type { readonly flags: TypeFlags; readonly objectFlags!: ObjectFlags; readonly value!: string | number | boolean; + readonly intrinsicName!: string; readonly isThisType!: boolean; readonly freshType!: number; readonly regularType!: number; @@ -1018,6 +1020,7 @@ class TypeObject implements Type { this.flags = data.flags; if (data.objectFlags !== undefined) this.objectFlags = data.objectFlags; if (data.value !== undefined) this.value = data.value; + if (data.intrinsicName !== undefined) this.intrinsicName = data.intrinsicName; if (data.isThisType !== undefined) this.isThisType = data.isThisType; if (data.freshType !== undefined) this.freshType = data.freshType; if (data.regularType !== undefined) this.regularType = data.regularType; diff --git a/_packages/native-preview/src/api/sync/types.ts b/_packages/native-preview/src/api/sync/types.ts index 0d0fee9a2a..e788818d35 100644 --- a/_packages/native-preview/src/api/sync/types.ts +++ b/_packages/native-preview/src/api/sync/types.ts @@ -151,6 +151,12 @@ export interface StringMappingType extends Type { getTarget(): Type; } +/** Intrinsic types — any, unknown, string, number, bigint, symbol, void, undefined, null, never, object (TypeFlags.Intrinsic) */ +export interface IntrinsicType extends Type { + /** The intrinsic type name (e.g. "any", "string", "never") */ + readonly intrinsicName: string; +} + /** Base for all type predicates */ export interface TypePredicateBase { readonly kind: TypePredicateKind; diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index 0ec2184657..43583a5ae6 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -36,6 +36,7 @@ import { DiagnosticCategory, type FreshableType, type IndexedAccessType, + type IntrinsicType, type IndexType, type LiteralType, ModifierFlags, @@ -2159,6 +2160,36 @@ describe("Type - getAliasSymbol", () => { }); }); +describe("IntrinsicType - intrinsicName", () => { + test("intrinsicName matches the primitive type name", async () => { + const src = `\nexport const x: string = "hello";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const stringType = await project.checker.getStringType(); + assert.equal((stringType as IntrinsicType).intrinsicName, "string"); + const anyType = await project.checker.getAnyType(); + assert.equal((anyType as IntrinsicType).intrinsicName, "any"); + const neverType = await project.checker.getNeverType(); + assert.equal((neverType as IntrinsicType).intrinsicName, "never"); + const pos = src.indexOf("x:"); + const sym = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(sym); + const litType = await project.checker.getTypeOfSymbol(sym); + assert.ok(litType); + assert.ok(litType.flags & TypeFlags.Intrinsic); + assert.equal((litType as IntrinsicType).intrinsicName, "string"); + } + finally { + await api.close(); + } + }); +}); + describe("FreshableType - getFreshType and getRegularType", () => { test("LiteralType.value is accessible via the FreshableType hierarchy", async () => { const src = `\nexport const greeting: "hello" = "hello";\n`; diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index 71e05f690a..743606f1d1 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -47,6 +47,7 @@ import { type FreshableType, type IndexedAccessType, type IndexType, + type IntrinsicType, type LiteralType, ModifierFlags, ObjectFlags, @@ -2167,6 +2168,36 @@ describe("Type - getAliasSymbol", () => { }); }); +describe("IntrinsicType - intrinsicName", () => { + test("intrinsicName matches the primitive type name", () => { + const src = `\nexport const x: string = "hello";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const stringType = project.checker.getStringType(); + assert.equal((stringType as IntrinsicType).intrinsicName, "string"); + const anyType = project.checker.getAnyType(); + assert.equal((anyType as IntrinsicType).intrinsicName, "any"); + const neverType = project.checker.getNeverType(); + assert.equal((neverType as IntrinsicType).intrinsicName, "never"); + const pos = src.indexOf("x:"); + const sym = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(sym); + const litType = project.checker.getTypeOfSymbol(sym); + assert.ok(litType); + assert.ok(litType.flags & TypeFlags.Intrinsic); + assert.equal((litType as IntrinsicType).intrinsicName, "string"); + } + finally { + api.close(); + } + }); +}); + describe("FreshableType - getFreshType and getRegularType", () => { test("LiteralType.value is accessible via the FreshableType hierarchy", () => { const src = `\nexport const greeting: "hello" = "hello";\n`; diff --git a/internal/api/proto.go b/internal/api/proto.go index 41e8b21b0b..b7990d5eae 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -522,6 +522,9 @@ type TypeResponse struct { // TypeParameter data IsThisType bool `json:"isThisType,omitempty"` + // IntrinsicType data + IntrinsicName string `json:"intrinsicName,omitempty"` + // TypeAlias data AliasTypeArguments []TypeID `json:"aliasTypeArguments,omitempty"` AliasSymbol SymbolID `json:"aliasSymbol,omitempty"` @@ -609,6 +612,8 @@ func newTypeData(t *checker.Type) *TypeResponse { resp.Target = TypeHandle(t.AsStringMappingType().Target()) case flags&checker.TypeFlagsTypeParameter != 0: resp.IsThisType = t.AsTypeParameter().IsThisType() + case flags&checker.TypeFlagsIntrinsic != 0: + resp.IntrinsicName = t.AsIntrinsicType().IntrinsicName() } return resp From 06d8615fd4e4b0b9dc044ca219c0a609dde69578 Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Fri, 29 May 2026 20:16:50 +0200 Subject: [PATCH 6/7] TS GO API: add support for `isTypeAssignableTo` method --- _packages/native-preview/src/api/async/api.ts | 9 +++ _packages/native-preview/src/api/sync/api.ts | 9 +++ .../native-preview/test/async/api.test.ts | 65 +++++++++++++++++++ .../native-preview/test/sync/api.test.ts | 65 +++++++++++++++++++ internal/api/proto.go | 12 +++- internal/api/session.go | 22 +++++++ 6 files changed, 181 insertions(+), 1 deletion(-) diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index 71bb36ebf8..272d9f3cbe 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -691,6 +691,15 @@ export class Checker { }); } + async isTypeAssignableTo(source: Type, target: Type): Promise { + return this.client.apiRequest("isTypeAssignableTo", { + snapshot: this.snapshotId, + project: this.projectId, + source: source.id, + target: target.id, + }); + } + async getShorthandAssignmentValueSymbol(node: Node): Promise { const data = await this.client.apiRequest("getShorthandAssignmentValueSymbol", { snapshot: this.snapshotId, diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index 05792e1f2d..f79c0c236d 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -699,6 +699,15 @@ export class Checker { }); } + isTypeAssignableTo(source: Type, target: Type): boolean { + return this.client.apiRequest("isTypeAssignableTo", { + snapshot: this.snapshotId, + project: this.projectId, + source: source.id, + target: target.id, + }); + } + getShorthandAssignmentValueSymbol(node: Node): Symbol | undefined { const data = this.client.apiRequest("getShorthandAssignmentValueSymbol", { snapshot: this.snapshotId, diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index 43583a5ae6..7fca0df81e 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -2342,6 +2342,71 @@ describe("Checker - isContextSensitive", () => { }); }); +describe("Checker - isTypeAssignableTo", () => { + test("returns true when source is assignable to target", async () => { + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": `export {};`, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const stringType = await project.checker.getStringType(); + const anyType = await project.checker.getAnyType(); + const neverType = await project.checker.getNeverType(); + assert.ok(await project.checker.isTypeAssignableTo(stringType, stringType), "string assignable to string"); + assert.ok(await project.checker.isTypeAssignableTo(stringType, anyType), "string assignable to any"); + assert.ok(await project.checker.isTypeAssignableTo(neverType, stringType), "never assignable to string (bottom type)"); + } + finally { + await api.close(); + } + }); + + test("returns false when source is not assignable to target", async () => { + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": `export {};`, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const stringType = await project.checker.getStringType(); + const numberType = await project.checker.getNumberType(); + assert.ok(!await project.checker.isTypeAssignableTo(numberType, stringType), "number not assignable to string"); + assert.ok(!await project.checker.isTypeAssignableTo(stringType, numberType), "string not assignable to number"); + } + finally { + await api.close(); + } + }); + + test("a string literal type is assignable to string but not number", async () => { + const src = `\nexport const x: "hello" = "hello";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("x:"); + const sym = await project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(sym); + const litType = await project.checker.getTypeOfSymbol(sym); + assert.ok(litType); + assert.ok(litType.flags & TypeFlags.StringLiteral); + const stringType = await project.checker.getStringType(); + const numberType = await project.checker.getNumberType(); + assert.ok(await project.checker.isTypeAssignableTo(litType, stringType)); + assert.ok(!await project.checker.isTypeAssignableTo(litType, numberType)); + } + finally { + await api.close(); + } + }); +}); + describe("Emitter - printNode", () => { const emitterFiles = { "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index 743606f1d1..0965cf4ff5 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -2350,6 +2350,71 @@ describe("Checker - isContextSensitive", () => { }); }); +describe("Checker - isTypeAssignableTo", () => { + test("returns true when source is assignable to target", () => { + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": `export {};`, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const stringType = project.checker.getStringType(); + const anyType = project.checker.getAnyType(); + const neverType = project.checker.getNeverType(); + assert.ok(project.checker.isTypeAssignableTo(stringType, stringType), "string assignable to string"); + assert.ok(project.checker.isTypeAssignableTo(stringType, anyType), "string assignable to any"); + assert.ok(project.checker.isTypeAssignableTo(neverType, stringType), "never assignable to string (bottom type)"); + } + finally { + api.close(); + } + }); + + test("returns false when source is not assignable to target", () => { + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": `export {};`, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const stringType = project.checker.getStringType(); + const numberType = project.checker.getNumberType(); + assert.ok(!project.checker.isTypeAssignableTo(numberType, stringType), "number not assignable to string"); + assert.ok(!project.checker.isTypeAssignableTo(stringType, numberType), "string not assignable to number"); + } + finally { + api.close(); + } + }); + + test("a string literal type is assignable to string but not number", () => { + const src = `\nexport const x: "hello" = "hello";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("x:"); + const sym = project.checker.getSymbolAtPosition("/src/main.ts", pos); + assert.ok(sym); + const litType = project.checker.getTypeOfSymbol(sym); + assert.ok(litType); + assert.ok(litType.flags & TypeFlags.StringLiteral); + const stringType = project.checker.getStringType(); + const numberType = project.checker.getNumberType(); + assert.ok(project.checker.isTypeAssignableTo(litType, stringType)); + assert.ok(!project.checker.isTypeAssignableTo(litType, numberType)); + } + finally { + api.close(); + } + }); +}); + describe("Emitter - printNode", () => { const emitterFiles = { "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), diff --git a/internal/api/proto.go b/internal/api/proto.go index b7990d5eae..1475dd4da7 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -106,6 +106,7 @@ const ( MethodGetWidenedType Method = "getWidenedType" MethodGetParameterType Method = "getParameterType" MethodIsArrayLikeType Method = "isArrayLikeType" + MethodIsTypeAssignableTo Method = "isTypeAssignableTo" MethodGetShorthandAssignmentValueSymbol Method = "getShorthandAssignmentValueSymbol" MethodGetTypeOfSymbolAtLocation Method = "getTypeOfSymbolAtLocation" MethodTypeToTypeNode Method = "typeToTypeNode" @@ -337,6 +338,7 @@ var unmarshalers = map[Method]func([]byte) (any, error){ MethodGetWidenedType: unmarshallerFor[GetWidenedTypeParams], MethodGetParameterType: unmarshallerFor[GetParameterTypeParams], MethodIsArrayLikeType: unmarshallerFor[IsArrayLikeTypeParams], + MethodIsTypeAssignableTo: unmarshallerFor[IsTypeAssignableToParams], MethodGetShorthandAssignmentValueSymbol: unmarshallerFor[GetTypeAtLocationParams], MethodGetTypeOfSymbolAtLocation: unmarshallerFor[GetTypeOfSymbolAtLocationParams], MethodTypeToTypeNode: unmarshallerFor[TypeToTypeNodeParams], @@ -527,7 +529,7 @@ type TypeResponse struct { // TypeAlias data AliasTypeArguments []TypeID `json:"aliasTypeArguments,omitempty"` - AliasSymbol SymbolID `json:"aliasSymbol,omitempty"` + AliasSymbol SymbolID `json:"aliasSymbol,omitempty"` // Symbol associated with structured types Symbol SymbolID `json:"symbol,omitempty"` @@ -767,6 +769,14 @@ type IsArrayLikeTypeParams struct { Type TypeID `json:"type"` } +// IsTypeAssignableToParams checks assignability between two types. +type IsTypeAssignableToParams struct { + Snapshot SnapshotID `json:"snapshot"` + Project ProjectID `json:"project"` + Source TypeID `json:"source"` + Target TypeID `json:"target"` +} + type GetSignaturesOfTypeParams struct { Snapshot SnapshotID `json:"snapshot"` Project ProjectID `json:"project"` diff --git a/internal/api/session.go b/internal/api/session.go index 8e2bcbaff0..42ee08abd3 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -455,6 +455,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetParameterType(ctx, parsed.(*GetParameterTypeParams)) case string(MethodIsArrayLikeType): return s.handleIsArrayLikeType(ctx, parsed.(*IsArrayLikeTypeParams)) + case string(MethodIsTypeAssignableTo): + return s.handleIsTypeAssignableTo(ctx, parsed.(*IsTypeAssignableToParams)) case string(MethodGetShorthandAssignmentValueSymbol): return s.handleGetShorthandAssignmentValueSymbol(ctx, parsed.(*GetTypeAtLocationParams)) case string(MethodGetTypeOfSymbolAtLocation): @@ -1527,6 +1529,26 @@ func (s *Session) handleIsArrayLikeType(ctx context.Context, params *IsArrayLike return setup.checker.IsArrayLikeType(t), nil } +// handleIsTypeAssignableTo returns whether source is assignable to target. +func (s *Session) handleIsTypeAssignableTo(ctx context.Context, params *IsTypeAssignableToParams) (bool, error) { + setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) + if err != nil { + return false, err + } + defer setup.done() + + source, err := setup.sd.resolveTypeHandle(params.Source) + if err != nil { + return false, err + } + target, err := setup.sd.resolveTypeHandle(params.Target) + if err != nil { + return false, err + } + + return setup.checker.IsTypeAssignableTo(source, target), nil +} + // handleGetShorthandAssignmentValueSymbol returns the value symbol of a shorthand property assignment. func (s *Session) handleGetShorthandAssignmentValueSymbol(ctx context.Context, params *GetTypeAtLocationParams) (*SymbolResponse, error) { setup, err := s.setupChecker(ctx, params.Snapshot, params.Project) From 7ee944507dad6e456ec233ac038380425001fa5e Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Fri, 5 Jun 2026 21:05:57 +0200 Subject: [PATCH 7/7] TS GO API: fix formatting in tests --- _packages/native-preview/test/async/api.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index 7fca0df81e..92ae31105c 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -36,8 +36,8 @@ import { DiagnosticCategory, type FreshableType, type IndexedAccessType, - type IntrinsicType, type IndexType, + type IntrinsicType, type LiteralType, ModifierFlags, ObjectFlags, @@ -46,8 +46,8 @@ import { SymbolFlags, type TemplateLiteralType, TypeFlags, - TypePredicateKind, type TypeParameter, + TypePredicateKind, type TypeReference, type UnionOrIntersectionType, } from "@typescript/native-preview/unstable/async"; // @sync: } from "@typescript/native-preview/unstable/sync";