Prevent getTypeAtLocation crash on type-only import clause - #64468
Andrew Branch (andrewbranch) merged 3 commits into
Conversation
|
@microsoft-github-policy-service agree |
| // In this case, we call getSymbolOfDeclaration instead of getSymbolAtLocation because it is a declaration | ||
| symbol := c.getSymbolOfDeclaration(node) | ||
| return c.getDeclaredTypeOfSymbol(symbol) | ||
| if symbol != nil { |
There was a problem hiding this comment.
This seems like a band-aid. Why is this nil in the first place?
There was a problem hiding this comment.
Thanks, you're right. The nil symbol itself is expected: the binder only declares a symbol for
an ImportClause that has a default binding (bindImportClause checks Name() != nil). For
import type { U } and import type * as ns, the declarations are the ImportSpecifier and
NamespaceImport, and the clause itself declares nothing.
The actual problem is that ast.IsTypeDeclaration still treats such a clause (import type { U }) as a type
declaration. I've changed it to require a default binding and removed the nil check, so these
clauses now take the same path as a regular import clause. The other callers of
IsTypeDeclaration only see symbol declarations or named nodes, so they aren't affected.
I also extended the test to cover import type D to make sure a default binding still
resolves as before.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Fixes #64467
Context
I'm building syscript, a compiler that compiles TypeScript syntax to C for systems
programming. It uses the TypeScript 7 API to type-check the program and then walks
every node, requesting its type in a batch with
getTypeAtLocation. That walk crashedthe API server on the first file that used
import type.Problem
checker.GetTypeAtLocationpanics with a nil pointer dereference when called on theImportClauseof a type-only import without a default binding:It reproduces with the latest nightly (
typescript@7.1.0-dev.20260926.1).Cause
ast.IsTypeDeclarationreturns true for a type-onlyImportClause, sogetTypeOfNodetakes the type-declaration branch and passes the result of
getSymbolOfDeclarationstraight to
getDeclaredTypeOfSymbol. An import clause only has a symbol when it hasa default binding (
import type X from "..."), so forimport type { U }andimport type * as nsthe symbol is nil.The missing check looks historical rather than intentional. When this branch was
written,
isTypeDeclarationonly covered type parameters, classes, interfaces, typealiases and enums, which always have a symbol. #35200 (type-only imports and exports)
added
ImportClause,ImportSpecifierandExportSpecifier; the specifiers alwayshave a symbol, but a clause without a default binding does not. The same unguarded
branch is still in Strada's
getTypeOfNode, and this code was ported from it.The nil symbol is expected: the binder only declares a symbol for an import clause with a
default binding. The bug is that
IsTypeDeclarationclassifies a type-only clause as a typedeclaration even when it has no default binding and therefore declares nothing.
Fix
Make
ast.IsTypeDeclarationreturn true for a type-onlyImportClauseonly when it has adefault binding. A clause without one now takes the same path in
getTypeOfNodeas a regularimport clause, which already handles a missing symbol.
The other callers of
IsTypeDeclaration(addUndefinedToGlobalsOrErrorOnRedeclaration, thedeprecation check,
reportUnusedLocalandIsTypeDeclarationName) only look at symboldeclarations or named nodes, so their behavior doesn't change.
Tests
Added
TestGetTypeAtLocationOfTypeOnlyImportClauseininternal/checker. It requeststhe type of a named and a namespace type-only import clause and checks that both match
the type of an equivalent regular import clause, and that
import type D(a defaultbinding) still resolves to the same type as a reference to
D. It panics without the fixand passes with it.
Ran the pre-submission checklist from CONTRIBUTING.md. Everything passes except
internal/vfs/osvfsTestOS/Realpath, which also fails onmainin my environmentbecause my home directory is a symlink (
/home→/var/home, Fedora Atomic).I used Claude Code for this change; I've reviewed it and will handle the review.