https://github.com/matthiasgoergens created https://github.com/llvm/llvm-project/pull/214989
`TemplateDiff` stores template-template arguments as `TemplateDecl *`, but a dependent template name such as `PartialApply<B, F>::template R` has no declaration: `GetTemplateDecl` returned null for both sides, and `PrintTemplateTemplate` hit `assert((FromTD || ToTD))` as soon as a diagnostic printed a template tree containing such arguments — a crash on valid code. Carry `TemplateName` through the diff tree instead. Equality uses `ASTContext::hasSameTemplateName`, which matches the previous canonical-declaration comparison for resolved names and additionally identifies equal dependent names; printing uses `TemplateName::print` when no declaration exists and is unchanged otherwise. The test pins the printed tree for two differing dependent names rather than merely the absence of the crash. Fixes #213068. ## Tool use Per the [LLVM AI Tool Use Policy](https://llvm.org/docs/AIToolPolicy.html): AI tools were involved throughout the preparation of this change. I am the author and accountable for the contribution. Assisted-by: OpenAI Codex Assisted-by: Claude Code Assisted-by: Kimi Assisted-by: DeepSeek From 12f5d4b591132b1af8c2483724a97bf145c542ff Mon Sep 17 00:00:00 2001 From: Matthias Goergens <[email protected]> Date: Sun, 2 Aug 2026 22:53:04 +0800 Subject: [PATCH] [clang][AST] Preserve dependent names in template diffs Template-template argument diagnostics reduced TemplateName to TemplateDecl. Dependent template names have no declaration, so comparing two of them stored two null pointers and later asserted while printing.\n\nRetain TemplateName in the diagnostic tree, compare names through ASTContext, and print unresolved names directly. This both avoids the crash and reports the actual dependent arguments that differ. --- clang/lib/AST/ASTDiagnostic.cpp | 81 ++++++++++++++++++---------- clang/test/SemaTemplate/gh213068.cpp | 23 ++++++++ 2 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 clang/test/SemaTemplate/gh213068.cpp diff --git a/clang/lib/AST/ASTDiagnostic.cpp b/clang/lib/AST/ASTDiagnostic.cpp index f7888f58985db..0a76457cf4bb2 100644 --- a/clang/lib/AST/ASTDiagnostic.cpp +++ b/clang/lib/AST/ASTDiagnostic.cpp @@ -616,6 +616,7 @@ class TemplateDiff { bool IsValidInt = false; Expr *ArgExpr = nullptr; TemplateDecl *TD = nullptr; + TemplateName TN; ValueDecl *VD = nullptr; bool NeedAddressOf = false; bool IsNullPtr = false; @@ -694,12 +695,12 @@ class TemplateDiff { SetDefault(FromDefault, ToDefault); } - void SetTemplateTemplateDiff(TemplateDecl *FromTD, TemplateDecl *ToTD, + void SetTemplateTemplateDiff(TemplateName FromTN, TemplateName ToTN, bool FromDefault, bool ToDefault) { assert(FlatTree[CurrentNode].Kind == Invalid && "Node is not empty."); FlatTree[CurrentNode].Kind = TemplateTemplate; - FlatTree[CurrentNode].FromArgInfo.TD = FromTD; - FlatTree[CurrentNode].ToArgInfo.TD = ToTD; + FlatTree[CurrentNode].FromArgInfo.TN = FromTN; + FlatTree[CurrentNode].ToArgInfo.TN = ToTN; SetDefault(FromDefault, ToDefault); } @@ -853,10 +854,10 @@ class TemplateDiff { ToExpr = FlatTree[ReadNode].ToArgInfo.ArgExpr; } - void GetTemplateTemplateDiff(TemplateDecl *&FromTD, TemplateDecl *&ToTD) { + void GetTemplateTemplateDiff(TemplateName &FromTN, TemplateName &ToTN) { assert(FlatTree[ReadNode].Kind == TemplateTemplate && "Unexpected kind."); - FromTD = FlatTree[ReadNode].FromArgInfo.TD; - ToTD = FlatTree[ReadNode].ToArgInfo.TD; + FromTN = FlatTree[ReadNode].FromArgInfo.TN; + ToTN = FlatTree[ReadNode].ToArgInfo.TN; } void GetIntegerDiff(llvm::APSInt &FromInt, llvm::APSInt &ToInt, @@ -1218,12 +1219,13 @@ class TemplateDiff { /// template template difference. void DiffTemplateTemplates(const TSTiterator &FromIter, const TSTiterator &ToIter) { - TemplateDecl *FromDecl = GetTemplateDecl(FromIter); - TemplateDecl *ToDecl = GetTemplateDecl(ToIter); - Tree.SetTemplateTemplateDiff(FromDecl, ToDecl, FromIter.isEnd() && FromDecl, - ToIter.isEnd() && ToDecl); - Tree.SetSame(FromDecl && ToDecl && - FromDecl->getCanonicalDecl() == ToDecl->getCanonicalDecl()); + TemplateName FromName = GetTemplateName(FromIter); + TemplateName ToName = GetTemplateName(ToIter); + Tree.SetTemplateTemplateDiff(FromName, ToName, + FromIter.isEnd() && !FromName.isNull(), + ToIter.isEnd() && !ToName.isNull()); + Tree.SetSame(!FromName.isNull() && !ToName.isNull() && + Context.hasSameTemplateName(FromName, ToName)); } /// InitializeNonTypeDiffVariables - Helper function for DiffNonTypes @@ -1513,14 +1515,14 @@ class TemplateDiff { return QualType(); } - /// GetTemplateDecl - Retrieves the template template arguments, including + /// GetTemplateName - Retrieves the template template arguments, including /// default arguments. - static TemplateDecl *GetTemplateDecl(const TSTiterator &Iter) { + static TemplateName GetTemplateName(const TSTiterator &Iter) { if (!Iter.isEnd()) - return Iter->getAsTemplate().getAsTemplateDecl(); + return Iter->getAsTemplateOrTemplatePattern(); if (Iter.hasDesugaredTA()) - return Iter.getDesugaredTA().getAsTemplate().getAsTemplateDecl(); - return nullptr; + return Iter.getDesugaredTA().getAsTemplateOrTemplatePattern(); + return {}; } /// IsEqualExpr - Returns true if the expressions are the same in regards to @@ -1571,9 +1573,9 @@ class TemplateDiff { return; } case DiffTree::TemplateTemplate: { - TemplateDecl *FromTD, *ToTD; - Tree.GetTemplateTemplateDiff(FromTD, ToTD); - PrintTemplateTemplate(FromTD, ToTD, Tree.FromDefault(), + TemplateName FromTN, ToTN; + Tree.GetTemplateTemplateDiff(FromTN, ToTN); + PrintTemplateTemplate(FromTN, ToTN, Tree.FromDefault(), Tree.ToDefault(), Tree.NodeIsSame()); return; } @@ -1801,20 +1803,41 @@ class TemplateDiff { /// PrintTemplateTemplate - Handles printing of template template arguments, /// highlighting argument differences. - void PrintTemplateTemplate(TemplateDecl *FromTD, TemplateDecl *ToTD, + void PrintTemplateTemplate(TemplateName FromTN, TemplateName ToTN, bool FromDefault, bool ToDefault, bool Same) { - assert((FromTD || ToTD) && "Only one template argument may be missing."); + assert((!FromTN.isNull() || !ToTN.isNull()) && + "Only one template argument may be missing."); - std::string FromName = - std::string(FromTD ? FromTD->getName() : "(no argument)"); - std::string ToName = std::string(ToTD ? ToTD->getName() : "(no argument)"); - if (FromTD && ToTD && FromName == ToName) { - FromName = FromTD->getQualifiedNameAsString(); - ToName = ToTD->getQualifiedNameAsString(); + auto GetName = [this](TemplateName TN, TemplateName::Qualified Qual) { + if (TN.isNull()) + return std::string("(no argument)"); + std::string Name; + llvm::raw_string_ostream Out(Name); + TN.print(Out, Policy, Qual); + return Name; + }; + TemplateDecl *FromTD = FromTN.getAsTemplateDecl(); + TemplateDecl *ToTD = ToTN.getAsTemplateDecl(); + std::string FromName; + std::string ToName; + if (FromTD && ToTD) { + FromName = std::string(FromTD->getName()); + ToName = std::string(ToTD->getName()); + if (FromName == ToName) { + FromName = FromTD->getQualifiedNameAsString(); + ToName = ToTD->getQualifiedNameAsString(); + } + } else { + FromName = GetName(FromTN, TemplateName::Qualified::AsWritten); + ToName = GetName(ToTN, TemplateName::Qualified::AsWritten); } if (Same) { - OS << "template " << FromTD->getDeclName(); + OS << "template "; + if (FromTD) + OS << FromTD->getDeclName(); + else + FromTN.print(OS, Policy); } else if (!PrintTree) { OS << (FromDefault ? "(default) template " : "template "); Bold(); diff --git a/clang/test/SemaTemplate/gh213068.cpp b/clang/test/SemaTemplate/gh213068.cpp new file mode 100644 index 0000000000000..8d6c89cbde850 --- /dev/null +++ b/clang/test/SemaTemplate/gh213068.cpp @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s +// RUN: not %clang_cc1 -std=c++17 -fsyntax-only -fdiagnostics-show-template-tree %s 2>&1 | FileCheck %s + +template <template <template <typename> class, typename> class T, + template <typename> class V> +struct PartialApply { + template <template <template <typename> class, typename> class A, + template <template <typename> class, typename> class B, + template <typename> class F, typename X> + using Mul = A<PartialApply<B, F>::template R, X>; // expected-note {{previous definition is here}} + template <template <template <typename> class, typename> class T_ffl, + template <typename> class V_ffl> + struct PartialApply_ffl {}; + template <template <template <typename> class, typename> class A_ffl, + template <template <typename> class, typename> class B_ffl, + template <typename> class F_ffl, typename X> + using Mul = // expected-error {{type alias template redefinition with different types}} + A_ffl<PartialApply_ffl<B_ffl, F_ffl>::template R, X>; +}; + +// CHECK: error: type alias template redefinition with different types +// CHECK: [template PartialApply_ffl<B_ffl, F_ffl>::template R != template PartialApply<B, F>::template R], +// CHECK-NEXT: [...] _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
