https://github.com/16bit-ykiko updated https://github.com/llvm/llvm-project/pull/197856
>From a527caae1fe28ef5ca27cf38c01c9a8196d1fac0 Mon Sep 17 00:00:00 2001 From: ykiko <[email protected]> Date: Fri, 15 May 2026 11:13:32 +0800 Subject: [PATCH] [Clang][AST] Fix ExplicitInstantiationDecl accessors confusing declared type with class encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several ExplicitInstantiationDecl accessors (getTypeAsWritten, getQualifierLoc, getTagKWLoc, getNumTemplateArgs, and the angle-loc getters) used TypeLoc kind checks to distinguish class template / nested class instantiations from function / variable template instantiations. This was incorrect: a variable template's declared type can be a TagType or TemplateSpecializationType, causing: - getTypeAsWritten() returning nullptr → crash in DeclPrinter (GH#197797) - getQualifierLoc() returning the type's qualifier instead of the variable's → wrong output (e.g. "ns::var" instead of "var") - getTagKWLoc() returning the type's elaborated keyword - getNumTemplateArgs/AngleLocs extracting from the type instead of the variable's template arguments Add a private isClassOrNestedClassSpec() helper that checks isa<RecordDecl>(getSpecialization()), and guard all TypeSourceInfo fallback paths with it. Fixes #197797 --- clang/include/clang/AST/DeclTemplate.h | 7 +++ clang/lib/AST/DeclTemplate.cpp | 38 +++++++++------- clang/lib/Sema/SemaTemplate.cpp | 5 +-- .../AST/ast-print-explicit-instantiation.cpp | 24 ++++++++++ .../explicit-instantiation-source-info.cpp | 44 +++++++++++++++++++ 5 files changed, 99 insertions(+), 19 deletions(-) diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h index 9fb41c87da732..4e9f348f28847 100644 --- a/clang/include/clang/AST/DeclTemplate.h +++ b/clang/include/clang/AST/DeclTemplate.h @@ -3449,6 +3449,13 @@ class ExplicitInstantiationDecl final return hasTrailingQualifier() ? 1 : 0; } + /// Whether the specialization is a class template or nested class. + /// When true, TypeSourceInfo encodes the class itself (qualifier, tag + /// keyword, template arguments) rather than a declared type. + bool isClassOrNestedClassSpec() const { + return isa<RecordDecl>(getSpecialization()); + } + /// 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. diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp index 08e6512a1c74d..0786c3ba56518 100644 --- a/clang/lib/AST/DeclTemplate.cpp +++ b/clang/lib/AST/DeclTemplate.cpp @@ -1843,6 +1843,8 @@ ExplicitInstantiationDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID, } SourceLocation ExplicitInstantiationDecl::getTagKWLoc() const { + if (!isClassOrNestedClassSpec()) + return SourceLocation(); if (auto *TSI = getRawTypeSourceInfo()) { if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>()) return TL.getElaboratedKeywordLoc(); @@ -1855,8 +1857,9 @@ SourceLocation ExplicitInstantiationDecl::getTagKWLoc() const { NestedNameSpecifierLoc ExplicitInstantiationDecl::getQualifierLoc() const { if (hasTrailingQualifier()) return *getTrailingObjects<NestedNameSpecifierLoc>(); - if (auto *TSI = getRawTypeSourceInfo()) - return TSI->getTypeLoc().getPrefix(); + if (isClassOrNestedClassSpec()) + if (auto *TSI = getRawTypeSourceInfo()) + return TSI->getTypeLoc().getPrefix(); return NestedNameSpecifierLoc(); } @@ -1864,20 +1867,21 @@ TypeSourceInfo *ExplicitInstantiationDecl::getTypeAsWritten() const { auto *TSI = getRawTypeSourceInfo(); if (!TSI) 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; + if (isClassOrNestedClassSpec()) { + TypeLoc TL = TSI->getTypeLoc(); + if (TL.getAs<TemplateSpecializationTypeLoc>() || TL.getAs<TagTypeLoc>()) + return nullptr; + } return TSI; } 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 (isClassOrNestedClassSpec()) + if (auto *TSI = getRawTypeSourceInfo()) + if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>()) + return TL.getNumArgs(); return 0; } @@ -1892,18 +1896,20 @@ ExplicitInstantiationDecl::getTemplateArg(unsigned I) const { 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 (isClassOrNestedClassSpec()) + if (auto *TSI = getRawTypeSourceInfo()) + if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>()) + return TL.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 (isClassOrNestedClassSpec()) + if (auto *TSI = getRawTypeSourceInfo()) + if (auto TL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>()) + return TL.getRAngleLoc(); return SourceLocation(); } diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 71c2928b22d53..798efe94d82d1 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,7 @@ 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 +10914,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..40e71881e9fdf 100644 --- a/clang/test/AST/ast-print-explicit-instantiation.cpp +++ b/clang/test/AST/ast-print-explicit-instantiation.cpp @@ -25,6 +25,7 @@ template int ns::var<int>; // CHECK: extern template float ns::var<float>; extern template float ns::var<float>; + template <typename T> struct X { struct Inner {}; }; // CHECK: template struct X<int>::Inner; template struct X<int>::Inner; @@ -58,3 +59,26 @@ 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); + +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..b304bb6114946 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{}; @@ -212,3 +216,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>' +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
