Skip to content

Fix panic when serializing empty tuple array literal types - #4803

Open
artem1458 wants to merge 1 commit into
microsoft:mainfrom
artem1458:fix/api-empty-tuple-type-response
Open

Fix panic when serializing empty tuple array literal types#4803
artem1458 wants to merge 1 commit into
microsoft:mainfrom
artem1458:fix/api-empty-tuple-type-response

Conversation

@artem1458

@artem1458 artem1458 commented Jul 31, 2026

Copy link
Copy Markdown

Fixes #4804

Asking the API for the type of an array literal contextually typed by an empty tuple kills the tsgo server:

declare function f(t: readonly []): void;
const g = () => f([]);   // checker.getTypeAtLocation on `[]`
panic: interface conversion: checker.TypeData is *checker.TypeReference, not *checker.TupleType
github.com/microsoft/typescript-go/internal/checker.(*Type).AsTupleType(...)
	internal/checker/types.go:700
github.com/microsoft/typescript-go/internal/api.newTypeResponse(...)
	internal/api/proto.go:741
github.com/microsoft/typescript-go/internal/api.(*Session).handleGetTypeAtLocation(...)
	internal/api/session.go:1532

tsc --noEmit on the same file is clean, and the TypeScript 6 API returns [] for it, so this is specific to how the type is serialized.

Cause

newTypeResponse treats ObjectFlagsTuple as proof that the type holds *checker.TupleType data:

if objectFlags&checker.ObjectFlagsTuple != 0 {
	tuple := t.AsTupleType()

That holds for a synthesized tuple target, but not for a clone of one. createArrayLiteralType clones the array literal's type through cloneTypeReference, which allocates *checker.TypeReference data and then copies the source's object flags over it, ObjectFlagsTuple included — so the flags claim a tuple while the data is a type reference, contradicting the invariant documented above ObjectType in internal/checker/types.go.

Only empty tuples get there: createTupleTypeEx returns the tuple target itself at arity zero, while every other arity returns a plain type reference that never had ObjectFlagsTuple set. That is why [1] against readonly [number] is fine and [] against readonly [] is not, and why the same empty tuple type reached through a type node or an annotated identifier serializes fine — those are the target, not a clone.

The checker itself is unaffected, since isTupleType and friends test Target().objectFlags rather than the type's own flags.

Fix

Read the tuple shape off the target rather than casting the type itself. A tuple target's target is itself, so this works for both the target and a clone of it.

proto.go:760 (t.AsInterfaceType(), guarded by ObjectFlagsClassOrInterface) makes the same assumption and would break the same way if a class or interface target were ever cloned; both current cloneTypeReference call sites happen to be tuple paths, so I left it alone.

Tests

TestGetTypeAtLocationTupleTypes in internal/api/session_typeatlocation_test.go covers thirteen cases — the six array literal shapes that panicked, empty tuple targets reached through a type node and through an identifier, single element tuples, and arrays — asserting the printed type and the tuple metadata on the response. It panics without this change.

go test ./... is green, including an uncached go test -count=1 ./internal/testrunner/... (117111 subtests).

`newTypeResponse` cast a type with `ObjectFlagsTuple` set straight to
`*checker.TupleType`. That holds for a synthesized tuple target, but not for
a clone of one: `createArrayLiteralType` clones the type through
`cloneTypeReference`, which allocates `*checker.TypeReference` data and then
copies the source's object flags over it, `ObjectFlagsTuple` included.

Only empty tuples reach that path, because `createTupleTypeEx` returns the
tuple target itself at arity zero while every other arity returns a plain
type reference without `ObjectFlagsTuple`. So asking the API for the type of
an array literal contextually typed by an empty tuple killed the server:

    declare function f(t: readonly []): void;
    const g = () => f([]);   // getTypeAtLocation on `[]`

    panic: interface conversion: checker.TypeData is *checker.TypeReference,
    not *checker.TupleType

Read the tuple shape off the target instead, which works for both shapes
since a tuple target's target is itself.
Copilot AI review requested due to automatic review settings July 31, 2026 14:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes type serialization crashes for empty tuple array literals by reading tuple metadata from the tuple target.

Changes:

  • Serialize cloned tuple references without unsafe tuple casts.
  • Add coverage for empty tuples, non-empty tuples, and arrays.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
internal/api/proto.go Reads tuple shape from the reference target.
internal/api/session_typeatlocation_test.go Tests tuple type serialization and regression scenarios.

@artem1458

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

checker.getTypeAtLocation panics for an array literal contextually typed by an empty tuple

2 participants