From 6f9ee02ba093df306991d6d0ecfe398a3e537ceb Mon Sep 17 00:00:00 2001 From: uditDewan Date: Tue, 23 Jun 2026 15:40:14 -0400 Subject: [PATCH] Fix panic in checker.GetTypeArguments for non-reference types (#4338) The public API `Checker.GetTypeArguments` forwarded straight to the internal `getTypeArguments`, which assumes a type reference and immediately dereferences `t.AsTypeReference()`. Passing a non-reference type (e.g. the intrinsic `string`) yielded a nil `*TypeReference` and crashed with a nil pointer dereference. Guard the exported method so non-reference types return no type arguments, matching tsc and the native-preview `getTypeArguments(type): readonly Type[]` contract. Internal callers already gate on `ObjectFlagsReference` before calling `getTypeArguments`, so the hot path is unaffected. Adds sync and async API regression tests that reproduce the original panic. Claude (Opus 4.8) helped debug this issue. --- .../native-preview/test/async/api.test.ts | 19 +++++++++++++++++++ .../native-preview/test/sync/api.test.ts | 19 +++++++++++++++++++ internal/checker/exports.go | 6 ++++++ 3 files changed, 44 insertions(+) diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index 015cd521e36..06ec733f992 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -2332,6 +2332,25 @@ describe("Checker - getTypeArguments", () => { await api.close(); } }); + + test("does not panic for a non-reference type (issue #4338)", async () => { + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": `export const s: string = "";`, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", `export const `.length); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.deepEqual(await project.checker.getTypeArguments(type), []); + } + finally { + await api.close(); + } + }); }); describe("TypeParameter - isThisType", () => { diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index 74041ca1e85..c6d1c4d2e88 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -2340,6 +2340,25 @@ describe("Checker - getTypeArguments", () => { api.close(); } }); + + test("does not panic for a non-reference type (issue #4338)", () => { + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": `export const s: string = "";`, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", `export const `.length); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.deepEqual(project.checker.getTypeArguments(type), []); + } + finally { + api.close(); + } + }); }); describe("TypeParameter - isThisType", () => { diff --git a/internal/checker/exports.go b/internal/checker/exports.go index 516f5f149b6..ae48373b9b6 100644 --- a/internal/checker/exports.go +++ b/internal/checker/exports.go @@ -275,6 +275,12 @@ func (c *Checker) GetRestTypeOfSignature(sig *Signature) *Type { } func (c *Checker) GetTypeArguments(t *Type) []*Type { + // getTypeArguments assumes a type reference; non-reference types (e.g. the + // intrinsic `string`) would nil-deref through the public API. Match tsc's + // behavior of having no type arguments for them. + if t.objectFlags&ObjectFlagsReference == 0 { + return nil + } return c.getTypeArguments(t) }