[clang] [clang] Track function template instantiation from definition (PR #110387)
Matheus Izvekov via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 6 19:31:20 PDT 2024
https://github.com/mizvekov updated https://github.com/llvm/llvm-project/pull/110387
>From 226b1bea7b287f31f4dc8d57466c8af5c6012c4e Mon Sep 17 00:00:00 2001
From: Matheus Izvekov <mizvekov at gmail.com>
Date: Sat, 28 Sep 2024 14:28:58 -0300
Subject: [PATCH] [clang] Track function template instantiation from definition
This fixes instantiation of definition for friend function templates,
when the declaration found and the one containing the definition
have different template contexts.
In these cases, the the function declaration corresponding to the
definition is not available; it may not even be instantiated at all.
So this patch adds a bit which tracks which function template
declaration was instantiated from the member template.
It's used to find which primary template serves as a context
for the purpose of obtainining the template arguments needed
to instantiate the definition.
Fixes #55509
---
clang/docs/ReleaseNotes.rst | 1 +
clang/include/clang/AST/Decl.h | 7 ++
clang/include/clang/AST/DeclBase.h | 10 +-
clang/include/clang/AST/DeclTemplate.h | 9 ++
clang/lib/AST/Decl.cpp | 1 +
clang/lib/Sema/SemaTemplateDeduction.cpp | 17 +--
clang/lib/Sema/SemaTemplateInstantiate.cpp | 9 +-
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 22 +++-
clang/lib/Serialization/ASTReaderDecl.cpp | 1 +
clang/lib/Serialization/ASTWriterDecl.cpp | 3 +-
clang/test/SemaTemplate/GH55509.cpp | 101 ++++++++++++++++++
11 files changed, 153 insertions(+), 28 deletions(-)
create mode 100644 clang/test/SemaTemplate/GH55509.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 44d5f348ed2d54..552c9f524a7ae6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -468,6 +468,7 @@ Bug Fixes to C++ Support
- Fixed an assertion failure in debug mode, and potential crashes in release mode, when
diagnosing a failed cast caused indirectly by a failed implicit conversion to the type of the constructor parameter.
- Fixed an assertion failure by adjusting integral to boolean vector conversions (#GH108326)
+- Clang is now better at keeping track of friend function template instance contexts. (#GH55509)
- Fixed an issue deducing non-type template arguments of reference type. (#GH73460)
- Fixed an issue in constraint evaluation, where type constraints on the lambda expression
containing outer unexpanded parameters were not correctly expanded. (#GH101754)
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 7ff35d73df5997..6afc86710a8137 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2299,6 +2299,13 @@ class FunctionDecl : public DeclaratorDecl,
FunctionDeclBits.IsLateTemplateParsed = ILT;
}
+ bool isInstantiatedFromMemberTemplate() const {
+ return FunctionDeclBits.IsInstantiatedFromMemberTemplate;
+ }
+ void setInstantiatedFromMemberTemplate(bool Val = true) {
+ FunctionDeclBits.IsInstantiatedFromMemberTemplate = Val;
+ }
+
/// Whether this function is "trivial" in some specialized C++ senses.
/// Can only be true for default constructors, copy constructors,
/// copy assignment operators, and destructors. Not meaningful until
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index ee662ed73d7e0e..eb67dc03157e64 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -1763,6 +1763,8 @@ class DeclContext {
uint64_t HasImplicitReturnZero : 1;
LLVM_PREFERRED_TYPE(bool)
uint64_t IsLateTemplateParsed : 1;
+ LLVM_PREFERRED_TYPE(bool)
+ uint64_t IsInstantiatedFromMemberTemplate : 1;
/// Kind of contexpr specifier as defined by ConstexprSpecKind.
LLVM_PREFERRED_TYPE(ConstexprSpecKind)
@@ -1813,7 +1815,7 @@ class DeclContext {
};
/// Number of inherited and non-inherited bits in FunctionDeclBitfields.
- enum { NumFunctionDeclBits = NumDeclContextBits + 31 };
+ enum { NumFunctionDeclBits = NumDeclContextBits + 32 };
/// Stores the bits used by CXXConstructorDecl. If modified
/// NumCXXConstructorDeclBits and the accessor
@@ -1824,12 +1826,12 @@ class DeclContext {
LLVM_PREFERRED_TYPE(FunctionDeclBitfields)
uint64_t : NumFunctionDeclBits;
- /// 20 bits to fit in the remaining available space.
+ /// 19 bits to fit in the remaining available space.
/// Note that this makes CXXConstructorDeclBitfields take
/// exactly 64 bits and thus the width of NumCtorInitializers
/// will need to be shrunk if some bit is added to NumDeclContextBitfields,
/// NumFunctionDeclBitfields or CXXConstructorDeclBitfields.
- uint64_t NumCtorInitializers : 17;
+ uint64_t NumCtorInitializers : 16;
LLVM_PREFERRED_TYPE(bool)
uint64_t IsInheritingConstructor : 1;
@@ -1843,7 +1845,7 @@ class DeclContext {
};
/// Number of inherited and non-inherited bits in CXXConstructorDeclBitfields.
- enum { NumCXXConstructorDeclBits = NumFunctionDeclBits + 20 };
+ enum { NumCXXConstructorDeclBits = NumFunctionDeclBits + 19 };
/// Stores the bits used by ObjCMethodDecl.
/// If modified NumObjCMethodDeclBits and the accessor
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index 687715a22e9fd3..f4a94c6cf20df2 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -1010,6 +1010,15 @@ class FunctionTemplateDecl : public RedeclarableTemplateDecl {
return getTemplatedDecl()->isThisDeclarationADefinition();
}
+ bool isCompatibleWithDefinition() const {
+ return getTemplatedDecl()->isInstantiatedFromMemberTemplate() ||
+ isThisDeclarationADefinition();
+ }
+ void setInstantiatedFromMemberTemplate(FunctionTemplateDecl *D) {
+ getTemplatedDecl()->setInstantiatedFromMemberTemplate();
+ RedeclarableTemplateDecl::setInstantiatedFromMemberTemplate(D);
+ }
+
/// Return the specialization with the provided arguments if it exists,
/// otherwise return the insertion point.
FunctionDecl *findSpecialization(ArrayRef<TemplateArgument> Args,
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 84ef9f74582ef6..e997ee301ac83f 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3059,6 +3059,7 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC,
FunctionDeclBits.IsIneligibleOrNotSelected = false;
FunctionDeclBits.HasImplicitReturnZero = false;
FunctionDeclBits.IsLateTemplateParsed = false;
+ FunctionDeclBits.IsInstantiatedFromMemberTemplate = false;
FunctionDeclBits.ConstexprKind = static_cast<uint64_t>(ConstexprKind);
FunctionDeclBits.BodyContainsImmediateEscalatingExpression = false;
FunctionDeclBits.InstantiationIsPending = false;
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index ce3317db5a8202..3b0dd9c3d0e588 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4034,22 +4034,7 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
if (FunctionTemplate->getFriendObjectKind())
Owner = FunctionTemplate->getLexicalDeclContext();
FunctionDecl *FD = FunctionTemplate->getTemplatedDecl();
- // additional check for inline friend,
- // ```
- // template <class F1> int foo(F1 X);
- // template <int A1> struct A {
- // template <class F1> friend int foo(F1 X) { return A1; }
- // };
- // template struct A<1>;
- // int a = foo(1.0);
- // ```
- const FunctionDecl *FDFriend;
- if (FD->getFriendObjectKind() == Decl::FriendObjectKind::FOK_None &&
- FD->isDefined(FDFriend, /*CheckForPendingFriendDefinition*/ true) &&
- FDFriend->getFriendObjectKind() != Decl::FriendObjectKind::FOK_None) {
- FD = const_cast<FunctionDecl *>(FDFriend);
- Owner = FD->getLexicalDeclContext();
- }
+
MultiLevelTemplateArgumentList SubstArgs(
FunctionTemplate, CanonicalDeducedArgumentList->asArray(),
/*Final=*/false);
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 898255ff7c6a32..9030d87c1bdb0c 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -476,9 +476,6 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
using namespace TemplateInstArgsHelpers;
const Decl *CurDecl = ND;
- if (!CurDecl)
- CurDecl = Decl::castFromDeclContext(DC);
-
if (Innermost) {
Result.addOuterTemplateArguments(const_cast<NamedDecl *>(ND), *Innermost,
Final);
@@ -492,8 +489,10 @@ MultiLevelTemplateArgumentList Sema::getTemplateInstantiationArgs(
// has a depth of 0.
if (const auto *TTP = dyn_cast<TemplateTemplateParmDecl>(CurDecl))
HandleDefaultTempArgIntoTempTempParam(TTP, Result);
- CurDecl = Response::UseNextDecl(CurDecl).NextDecl;
- }
+ CurDecl = DC ? Decl::castFromDeclContext(DC)
+ : Response::UseNextDecl(CurDecl).NextDecl;
+ } else if (!CurDecl)
+ CurDecl = Decl::castFromDeclContext(DC);
while (!CurDecl->isFileContextDecl()) {
Response R;
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 1c35c7d288e325..7dc8b97d8bc0d7 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -12,6 +12,7 @@
#include "TreeTransform.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTLambda.h"
#include "clang/AST/ASTMutationListener.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/DeclVisitor.h"
@@ -5185,9 +5186,26 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
RebuildTypeSourceInfoForDefaultSpecialMembers();
SetDeclDefaulted(Function, PatternDecl->getLocation());
} else {
+ NamedDecl *ND = Function;
+ std::optional<ArrayRef<TemplateArgument>> Innermost;
+ if (auto *Primary = Function->getPrimaryTemplate();
+ Primary &&
+ !isGenericLambdaCallOperatorOrStaticInvokerSpecialization(Function)) {
+ auto It = llvm::find_if(Primary->redecls(),
+ [](const RedeclarableTemplateDecl *RTD) {
+ return cast<FunctionTemplateDecl>(RTD)
+ ->isCompatibleWithDefinition();
+ });
+ assert(It != Primary->redecls().end() &&
+ "Should't get here without a definition");
+ ND = *It;
+ if (Function->getTemplateSpecializationKind() !=
+ TSK_ExplicitSpecialization)
+ Innermost.emplace(Function->getTemplateSpecializationArgs()->asArray());
+ }
MultiLevelTemplateArgumentList TemplateArgs = getTemplateInstantiationArgs(
- Function, Function->getLexicalDeclContext(), /*Final=*/false,
- /*Innermost=*/std::nullopt, false, PatternDecl);
+ Function, ND->getLexicalDeclContext(), /*Final=*/false, Innermost,
+ false, PatternDecl);
// Substitute into the qualifier; we can get a substitution failure here
// through evil use of alias templates.
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 7cead2728ca938..28b63bb5d40588 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -1087,6 +1087,7 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) {
FD->setHasImplicitReturnZero(FunctionDeclBits.getNextBit());
FD->setIsMultiVersion(FunctionDeclBits.getNextBit());
FD->setLateTemplateParsed(FunctionDeclBits.getNextBit());
+ FD->setInstantiatedFromMemberTemplate(FunctionDeclBits.getNextBit());
FD->setFriendConstraintRefersToEnclosingTemplate(
FunctionDeclBits.getNextBit());
FD->setUsesSEHTry(FunctionDeclBits.getNextBit());
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index b71684569609ac..4b2474c6321567 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -626,7 +626,7 @@ void ASTDeclWriter::VisitDeclaratorDecl(DeclaratorDecl *D) {
}
void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
- static_assert(DeclContext::NumFunctionDeclBits == 44,
+ static_assert(DeclContext::NumFunctionDeclBits == 45,
"You need to update the serializer after you change the "
"FunctionDeclBits");
@@ -732,6 +732,7 @@ void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
FunctionDeclBits.addBit(D->hasImplicitReturnZero());
FunctionDeclBits.addBit(D->isMultiVersion());
FunctionDeclBits.addBit(D->isLateTemplateParsed());
+ FunctionDeclBits.addBit(D->isInstantiatedFromMemberTemplate());
FunctionDeclBits.addBit(D->FriendConstraintRefersToEnclosingTemplate());
FunctionDeclBits.addBit(D->usesSEHTry());
Record.push_back(FunctionDeclBits);
diff --git a/clang/test/SemaTemplate/GH55509.cpp b/clang/test/SemaTemplate/GH55509.cpp
new file mode 100644
index 00000000000000..f95833fbed7b19
--- /dev/null
+++ b/clang/test/SemaTemplate/GH55509.cpp
@@ -0,0 +1,101 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++26 %s
+
+namespace t1 {
+ template<int N> struct A {
+ template<class C> friend auto cica(const A<N-1>&, C) {
+ return N;
+ }
+ };
+
+ template<> struct A<0> {
+ template<class C> friend auto cica(const A<0>&, C);
+ // expected-note at -1 {{declared here}}
+ };
+
+ void test() {
+ cica(A<0>{}, 0);
+ // expected-error at -1 {{function 'cica<int>' with deduced return type cannot be used before it is defined}}
+
+ (void)A<1>{};
+ cica(A<0>{}, 0);
+ }
+} // namespace t1
+namespace t2 {
+ template<int N> struct A {
+ template<class C> friend auto cica(const A<N-1>&, C) {
+ return N;
+ }
+ };
+
+ template<> struct A<0> {
+ template<class C> friend auto cica(const A<0>&, C);
+ };
+
+ template <int N, class = decltype(cica(A<N>{}, nullptr))>
+ void MakeCica();
+ // expected-note at -1 {{candidate function}}
+
+ template <int N> void MakeCica(A<N+1> = {});
+ // expected-note at -1 {{candidate function}}
+
+ void test() {
+ MakeCica<0>();
+
+ MakeCica<0>();
+ // expected-error at -1 {{call to 'MakeCica' is ambiguous}}
+ }
+} // namespace t2
+namespace t3 {
+ template<int N> struct A {
+ template<class C> friend auto cica(const A<N-1>&, C) {
+ return N-1;
+ }
+ };
+
+ template<> struct A<0> {
+ template<class C> friend auto cica(const A<0>&, C);
+ };
+
+ template <int N, class AT, class = decltype(cica(AT{}, nullptr))>
+ static constexpr bool MakeCica(int);
+
+ template <int N, class AT>
+ static constexpr bool MakeCica(short, A<N+1> = {});
+
+ template <int N, class AT = A<N>, class Val = decltype(MakeCica<N, AT>(0))>
+ static constexpr bool has_cica = Val{};
+
+ constexpr bool cica2 = has_cica<0> || has_cica<0>;
+} // namespace t3
+namespace t4 {
+ template<int N> struct A {
+ template<class C> friend auto cica(const A<N-1>&, C);
+ };
+
+ template<> struct A<0> {
+ template<class C> friend auto cica(const A<0>&, C) {
+ C a;
+ }
+ };
+
+ template struct A<1>;
+
+ void test() {
+ cica(A<0>{}, 0);
+ }
+} // namespace t4
+namespace regression1 {
+ template <class> class A;
+
+ template <class T> [[gnu::abi_tag("TAG")]] void foo(A<T>);
+
+ template <class> struct A {
+ friend void foo <>(A);
+ };
+
+ template struct A<int>;
+
+ template <class T> [[gnu::abi_tag("TAG")]] void foo(A<T>) {}
+
+ template void foo<int>(A<int>);
+} // namespace regression1
More information about the cfe-commits
mailing list