[clang] [Clang][AST] Fix crash in ExplicitInstantiationDecl printing for variable templates with tag types (PR #197856)
via cfe-commits
cfe-commits at lists.llvm.org
Fri May 15 08:21:52 PDT 2026
https://github.com/16bit-ykiko updated https://github.com/llvm/llvm-project/pull/197856
>From 96e8c1d67b64498e1ef77f1448ae267ae298ffeb Mon Sep 17 00:00:00 2001
From: ykiko <ykiko at clice.io>
Date: Fri, 15 May 2026 11:13:32 +0800
Subject: [PATCH] [Clang][AST] Fix ExplicitInstantiationDecl accessors
confusing declared type with class encoding
---
clang/include/clang/AST/DeclTemplate.h | 38 ++++-----
clang/lib/AST/DeclTemplate.cpp | 50 ++++++------
clang/lib/Sema/SemaTemplate.cpp | 6 +-
.../AST/ast-print-explicit-instantiation.cpp | 56 +++++++++++++
.../explicit-instantiation-source-info.cpp | 81 +++++++++++++++++++
5 files changed, 183 insertions(+), 48 deletions(-)
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index 9fb41c87da732..efc9021797148 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3449,9 +3449,19 @@ class ExplicitInstantiationDecl final
return hasTrailingQualifier() ? 1 : 0;
}
- /// Raw access to the internal TypeSourceInfo. For class templates this is
- /// a TemplateSpecializationTypeLoc; for nested classes a TagTypeLoc.
- /// Public getTypeAsWritten() returns null for those cases.
+ /// For class templates / nested classes, returns the TypeLoc encoding the
+ /// entity (TemplateSpecializationTypeLoc or TagTypeLoc). For function /
+ /// variable templates — where TypeSourceInfo holds the declared type
+ /// rather than the entity — returns std::nullopt.
+ std::optional<TypeLoc> getClassTypeLoc() const {
+ if (!isa<RecordDecl>(getSpecialization()))
+ return std::nullopt;
+ if (auto *TSI = TypeAndFlags.getPointer())
+ return TSI->getTypeLoc();
+ return std::nullopt;
+ }
+
+ /// Raw TypeSourceInfo pointer, needed by the serializer.
TypeSourceInfo *getRawTypeSourceInfo() const {
return TypeAndFlags.getPointer();
}
@@ -3493,13 +3503,10 @@ class ExplicitInstantiationDecl final
SourceLocation getTemplateLoc() const { return getLocation(); }
SourceLocation getNameLoc() const { return NameLoc; }
- /// For class templates / nested classes, the tag keyword location is
- /// stored inside TypeSourceInfo; otherwise returns an invalid location.
+ /// The tag keyword (struct/class/union) location for class templates /
+ /// nested classes; invalid for function / variable templates.
SourceLocation getTagKWLoc() const;
- /// Whether the qualifier is stored as a trailing object (function / variable
- /// templates) rather than inside TypeSourceInfo (class templates / nested
- /// classes).
bool hasTrailingQualifier() const {
return TypeAndFlags.getInt() & HasQualifierFlag;
}
@@ -3508,24 +3515,17 @@ class ExplicitInstantiationDecl final
}
/// Returns the qualifier regardless of where it is stored.
- /// For class templates / nested classes, it is extracted from TypeSourceInfo
- /// (TemplateSpecializationTypeLoc or TagTypeLoc).
- /// For function / variable templates, it comes from a trailing object.
+ /// For class templates / nested classes, extracted from the class TypeLoc;
+ /// for function / variable templates, from a trailing object.
NestedNameSpecifierLoc getQualifierLoc() const;
- /// Number of explicit template arguments, regardless of storage.
- /// For class templates they come from TemplateSpecializationTypeLoc;
- /// for function / variable templates from trailing
- /// ASTTemplateArgumentListInfo.
unsigned getNumTemplateArgs() const;
TemplateArgumentLoc getTemplateArg(unsigned I) const;
SourceLocation getTemplateArgsLAngleLoc() const;
SourceLocation getTemplateArgsRAngleLoc() const;
- /// For function / variable templates, returns the declared type (return type
- /// or variable type). For class templates and nested classes returns null —
- /// the qualifier, tag keyword, and template arguments are accessible via
- /// getQualifierLoc(), getTagKWLoc(), and getTemplateArg().
+ /// The declared type (return type or variable type) for function / variable
+ /// templates. Null for class templates and nested classes.
TypeSourceInfo *getTypeAsWritten() const;
TemplateSpecializationKind getTemplateSpecializationKind() const {
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 08e6512a1c74d..e1e3088cdf1e5 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1843,11 +1843,11 @@ ExplicitInstantiationDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID,
}
SourceLocation ExplicitInstantiationDecl::getTagKWLoc() const {
- if (auto *TSI = getRawTypeSourceInfo()) {
- if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
- return TL.getElaboratedKeywordLoc();
- if (auto TL = TSI->getTypeLoc().getAs<TagTypeLoc>())
- return TL.getElaboratedKeywordLoc();
+ if (auto TL = getClassTypeLoc()) {
+ if (auto TST = TL->getAs<TemplateSpecializationTypeLoc>())
+ return TST.getElaboratedKeywordLoc();
+ if (auto Tag = TL->getAs<TagTypeLoc>())
+ return Tag.getElaboratedKeywordLoc();
}
return SourceLocation();
}
@@ -1855,29 +1855,24 @@ SourceLocation ExplicitInstantiationDecl::getTagKWLoc() const {
NestedNameSpecifierLoc ExplicitInstantiationDecl::getQualifierLoc() const {
if (hasTrailingQualifier())
return *getTrailingObjects<NestedNameSpecifierLoc>();
- if (auto *TSI = getRawTypeSourceInfo())
- return TSI->getTypeLoc().getPrefix();
+ if (auto TL = getClassTypeLoc())
+ return TL->getPrefix();
return NestedNameSpecifierLoc();
}
TypeSourceInfo *ExplicitInstantiationDecl::getTypeAsWritten() const {
- auto *TSI = getRawTypeSourceInfo();
- if (!TSI)
+ // For class-like entities, TSI encodes the class itself, not a declared type.
+ if (getClassTypeLoc())
return nullptr;
- TypeLoc TL = TSI->getTypeLoc();
- // For class templates and nested classes, the "type" is fully described by
- // the unified accessors (getQualifierLoc, getTemplateArg, getTagKWLoc).
- if (TL.getAs<TemplateSpecializationTypeLoc>() || TL.getAs<TagTypeLoc>())
- return nullptr;
- return TSI;
+ return getRawTypeSourceInfo();
}
unsigned ExplicitInstantiationDecl::getNumTemplateArgs() const {
if (const auto *Args = getTrailingArgsInfo())
return Args->NumTemplateArgs;
- if (auto *TSI = getRawTypeSourceInfo())
- if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
- return TL.getNumArgs();
+ if (auto TL = getClassTypeLoc())
+ if (auto TST = TL->getAs<TemplateSpecializationTypeLoc>())
+ return TST.getNumArgs();
return 0;
}
@@ -1885,25 +1880,28 @@ TemplateArgumentLoc
ExplicitInstantiationDecl::getTemplateArg(unsigned I) const {
if (const auto *Args = getTrailingArgsInfo())
return (*Args)[I];
- auto *TSI = getRawTypeSourceInfo();
- return TSI->getTypeLoc().castAs<TemplateSpecializationTypeLoc>().getArgLoc(I);
+ // Must be a class template — args live in the TemplateSpecializationTypeLoc.
+ return getRawTypeSourceInfo()
+ ->getTypeLoc()
+ .castAs<TemplateSpecializationTypeLoc>()
+ .getArgLoc(I);
}
SourceLocation ExplicitInstantiationDecl::getTemplateArgsLAngleLoc() const {
if (const auto *Args = getTrailingArgsInfo())
return Args->getLAngleLoc();
- if (auto *TSI = getRawTypeSourceInfo())
- if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
- return TL.getLAngleLoc();
+ if (auto TL = getClassTypeLoc())
+ if (auto TST = TL->getAs<TemplateSpecializationTypeLoc>())
+ return TST.getLAngleLoc();
return SourceLocation();
}
SourceLocation ExplicitInstantiationDecl::getTemplateArgsRAngleLoc() const {
if (const auto *Args = getTrailingArgsInfo())
return Args->getRAngleLoc();
- if (auto *TSI = getRawTypeSourceInfo())
- if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>())
- return TL.getRAngleLoc();
+ if (auto TL = getClassTypeLoc())
+ if (auto TST = TL->getAs<TemplateSpecializationTypeLoc>())
+ return TST.getRAngleLoc();
return SourceLocation();
}
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 71c2928b22d53..707a700c9244f 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -10790,6 +10790,7 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
VarDecl *Prev = Previous.getAsSingle<VarDecl>();
VarTemplateDecl *PrevTemplate = Previous.getAsSingle<VarTemplateDecl>();
+ const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
if (!PrevTemplate) {
if (!Prev || !Prev->isStaticDataMember()) {
@@ -10858,6 +10859,8 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
// Ignore access control bits, we don't need them for redeclaration
// checking.
Prev = cast<VarDecl>(Res.get());
+ ArgsAsWritten =
+ ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
}
// C++0x [temp.explicit]p2:
@@ -10912,9 +10915,6 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
return true;
}
- const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
- if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(Prev))
- ArgsAsWritten = VTSD->getTemplateArgsAsWritten();
addExplicitInstantiationDecl(
Context, CurContext, Prev, ExternLoc, TemplateLoc,
D.getCXXScopeSpec().getWithLocInContext(Context), ArgsAsWritten,
diff --git a/clang/test/AST/ast-print-explicit-instantiation.cpp b/clang/test/AST/ast-print-explicit-instantiation.cpp
index 6c794e9575039..a8a49bac0df15 100644
--- a/clang/test/AST/ast-print-explicit-instantiation.cpp
+++ b/clang/test/AST/ast-print-explicit-instantiation.cpp
@@ -25,19 +25,35 @@ template int ns::var<int>;
// CHECK: extern template float ns::var<float>;
extern template float ns::var<float>;
+namespace ns {
+// CHECK: template struct S<short>;
+template struct S<short>;
+// CHECK: template void foo<short>(short);
+template void foo<short>(short);
+// CHECK: template short var<short>;
+template short var<short>;
+}
+
+
template <typename T> struct X { struct Inner {}; };
// CHECK: template struct X<int>::Inner;
template struct X<int>::Inner;
+// CHECK: extern template struct X<float>::Inner;
+extern template struct X<float>::Inner;
template <typename T> struct Outer {
void method();
template <typename U> void f(U);
template <typename U> static U var;
template <typename U> struct Inner {};
+ static T sval;
+ static T arr[1];
};
template <typename T> void Outer<T>::method() {}
template <typename T> template <typename U> void Outer<T>::f(U) {}
template <typename T> template <typename U> U Outer<T>::var = U{};
+template <typename T> T Outer<T>::sval = T{};
+template <typename T> T Outer<T>::arr[1] = {};
// CHECK: template void Outer<int>::method();
template void Outer<int>::method();
@@ -47,6 +63,21 @@ template void Outer<int>::f<double>(double);
template double Outer<int>::var<double>;
// CHECK: template struct Outer<int>::Inner<double>;
template struct Outer<int>::Inner<double>;
+// CHECK: template int Outer<int>::sval;
+template int Outer<int>::sval;
+// CHECK: template int Outer<int>::arr[1];
+template int Outer<int>::arr[1];
+
+// CHECK: extern template void Outer<float>::method();
+extern template void Outer<float>::method();
+// CHECK: extern template void Outer<float>::f<double>(double);
+extern template void Outer<float>::f<double>(double);
+// CHECK: extern template double Outer<float>::var<double>;
+extern template double Outer<float>::var<double>;
+// CHECK: extern template struct Outer<float>::Inner<double>;
+extern template struct Outer<float>::Inner<double>;
+// CHECK: extern template int Outer<float>::sval;
+extern template int Outer<float>::sval;
template <typename T> struct A {
template <typename U> struct B {
@@ -58,3 +89,28 @@ void A<T>::B<U>::g(V) {}
// CHECK: template void A<int>::B<double>::g<float>(float);
template void A<int>::B<double>::g<float>(float);
+// CHECK: extern template void A<float>::B<double>::g<int>(int);
+extern template void A<float>::B<double>::g<int>(int);
+
+namespace GH197797 {
+struct S {};
+enum E { X };
+template <typename T> struct Wrap {};
+
+// Variable templates with tag / class-template-specialization declared types.
+template <typename T> T var = T{};
+// CHECK: extern template S var<S>;
+extern template S var<S>;
+// CHECK: extern template E var<E>;
+extern template E var<E>;
+// CHECK: extern template Wrap<int> var<Wrap<int>>;
+extern template Wrap<int> var<Wrap<int>>;
+
+// Variable declared type has a qualifier but the variable itself does not:
+// the type's qualifier must not leak into the variable name.
+template <typename T> T var2 = T{};
+// CHECK: extern template ns::S<int> var2<ns::S<int>>;
+extern template ns::S<int> var2<ns::S<int>>;
+// CHECK: extern template ns::S<float> var2<ns::S<float>>;
+extern template ns::S<float> var2<ns::S<float>>;
+} // namespace GH197797
diff --git a/clang/test/AST/explicit-instantiation-source-info.cpp b/clang/test/AST/explicit-instantiation-source-info.cpp
index 3086fd261dd25..cc9aa6c5054a6 100644
--- a/clang/test/AST/explicit-instantiation-source-info.cpp
+++ b/clang/test/AST/explicit-instantiation-source-info.cpp
@@ -1,5 +1,9 @@
// RUN: %clang_cc1 -fsyntax-only -ast-dump %s | FileCheck %s
+struct Plain {};
+enum Color { Red };
+template <typename T> struct Wrap {};
+
namespace ns {
template <typename T> void foo(T x) {}
template <typename T> T bar = T{};
@@ -158,6 +162,43 @@ extern template struct ns::S<double>::Inner;
// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<double>'
// CHECK-NEXT: CXXRecord {{.*}} 'Inner'
+// extern template: member variable template
+extern template double ns::S<double>::mvar<double>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:50> col:8 explicit_instantiation_declaration extern 'mvar'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<double>'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'mvar' 'double'
+// CHECK-NEXT: BuiltinTypeLoc <col:17> 'double'
+// CHECK-NEXT: TemplateArgument <col:44> type 'double'
+// CHECK-NEXT: BuiltinType {{.*}} 'double'
+
+// extern template: member class template
+extern template struct ns::S<double>::Nested<double>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:52> col:8 explicit_instantiation_declaration extern 'Nested'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<double>'
+// CHECK-NEXT: ClassTemplateSpecialization {{.*}} 'Nested'
+// CHECK-NEXT: TemplateArgument <col:46> type 'double'
+// CHECK-NEXT: BuiltinType {{.*}} 'double'
+
+// extern template: static data member (array)
+extern template double ns::S<double>::arr[1];
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:44> col:8 explicit_instantiation_declaration extern 'arr'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<double>'
+// CHECK-NEXT: Var {{.*}} 'arr' 'double[1]'
+// CHECK-NEXT: ConstantArrayTypeLoc <col:17, col:44> 'double[1]' 1
+// CHECK-NEXT: BuiltinTypeLoc <col:17> 'double'
+
+// extern template: deeply nested
+extern template void ns::A<int>::B<float>::deep<double>(double);
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:63> col:8 explicit_instantiation_declaration extern 'deep'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::A<int>::B<float>'
+// CHECK-NEXT: CXXMethod {{.*}} 'deep' 'void (double)'
+// CHECK-NEXT: FunctionProtoTypeLoc <col:17, col:63> 'void (double)' cdecl
+// CHECK-NEXT: ParmVarDecl {{.*}} <col:57> col:63 'double'
+// CHECK-NEXT: BuiltinTypeLoc <col:57> 'double'
+// CHECK-NEXT: BuiltinTypeLoc <col:17> 'void'
+// CHECK-NEXT: TemplateArgument <col:49> type 'double'
+// CHECK-NEXT: BuiltinType {{.*}} 'double'
+
// member variable template
template double ns::S<long>::mvar<double>;
// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:41> col:1 explicit_instantiation_definition 'mvar'
@@ -212,3 +253,43 @@ namespace ns {
// CHECK-NEXT: TemplateArgument <col:21> type 'short'
// CHECK-NEXT: BuiltinType {{.*}} 'short'
}
+
+// GH197797: variable template with tag declared type
+extern template Plain ns::bar<Plain>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:36> col:8 explicit_instantiation_declaration extern 'bar'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'Plain'
+// CHECK-NEXT: RecordTypeLoc <col:17> 'Plain'
+// CHECK: TemplateArgument <col:31> type 'Plain'
+
+// Definition after declaration — template arg locations must be independent.
+template Plain ns::bar<Plain>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:29> col:1 explicit_instantiation_definition 'bar'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'Plain'
+// CHECK-NEXT: RecordTypeLoc <col:10> 'Plain'
+// CHECK: TemplateArgument <col:24> type 'Plain'
+
+// Variable template with enum declared type.
+extern template Color ns::bar<Color>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:36> col:8 explicit_instantiation_declaration extern 'bar'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'Color'
+// CHECK-NEXT: EnumTypeLoc <col:17> 'Color'
+// CHECK: TemplateArgument <col:31> type 'Color'
+
+// Variable template with class template specialization declared type.
+extern template Wrap<int> ns::bar<Wrap<int>>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:44> col:8 explicit_instantiation_declaration extern 'bar'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'Wrap<int>'
+// CHECK-NEXT: TemplateSpecializationTypeLoc <col:17, col:25> 'Wrap<int>'
+
+// Variable template: type has qualifier, variable does not — no leaking.
+namespace ns {
+ extern template Wrap<int> bar<Wrap<int>>;
+ // CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:3, col:42> col:10 explicit_instantiation_declaration extern 'bar'
+ // CHECK-NOT: NestedNameSpecifier
+ // CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'Wrap<int>'
+ // CHECK-NEXT: TemplateSpecializationTypeLoc <col:19, col:27> 'Wrap<int>'
+}
More information about the cfe-commits
mailing list