[llvm-branch-commits] [clang] deb63e7 - [clang] Track function template instantiation from definition (#125266) (#127777)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Feb 21 10:49:13 PST 2025
Author: Matheus Izvekov
Date: 2025-02-21T10:49:10-08:00
New Revision: deb63e72d6c9ed98a2fbf4f8249ca6911bd189b8
URL: https://github.com/llvm/llvm-project/commit/deb63e72d6c9ed98a2fbf4f8249ca6911bd189b8
DIFF: https://github.com/llvm/llvm-project/commit/deb63e72d6c9ed98a2fbf4f8249ca6911bd189b8.diff
LOG: [clang] Track function template instantiation from definition (#125266) (#127777)
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
Added:
clang/test/SemaTemplate/GH55509.cpp
Modified:
clang/docs/ReleaseNotes.rst
clang/include/clang/AST/Decl.h
clang/include/clang/AST/DeclBase.h
clang/include/clang/AST/DeclTemplate.h
clang/lib/AST/Decl.cpp
clang/lib/Sema/SemaTemplateDeduction.cpp
clang/lib/Sema/SemaTemplateInstantiate.cpp
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
clang/lib/Serialization/ASTReaderDecl.cpp
clang/lib/Serialization/ASTWriterDecl.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index e716efa46a1f0..a2518042cb5b0 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -1057,6 +1057,7 @@ Bug Fixes to C++ Support
- Fix that some dependent immediate expressions did not cause immediate escalation (#GH119046)
- Fixed a substitution bug in transforming CTAD aliases when the type alias contains a non-pack template argument
corresponding to a pack parameter (#GH124715)
+- Clang is now better at keeping track of friend function template instance contexts. (#GH55509)
Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 9593bab576412..362a2741a0cdd 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2298,6 +2298,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 3bb82c1572ef9..648dae2838e03 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -1780,6 +1780,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)
@@ -1830,7 +1832,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
@@ -1841,12 +1843,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;
@@ -1860,7 +1862,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 03c43765206b1..472b079c8f728 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -1011,6 +1011,26 @@ class FunctionTemplateDecl : public RedeclarableTemplateDecl {
return getTemplatedDecl()->isThisDeclarationADefinition();
}
+ bool isCompatibleWithDefinition() const {
+ return getTemplatedDecl()->isInstantiatedFromMemberTemplate() ||
+ isThisDeclarationADefinition();
+ }
+
+ // This bit closely tracks 'RedeclarableTemplateDecl::InstantiatedFromMember',
+ // except this is per declaration, while the redeclarable field is
+ // per chain. This indicates a template redeclaration which
+ // is compatible with the definition, in the non-trivial case
+ // where this is not already a definition.
+ // This is only really needed for instantiating the definition of friend
+ // function templates, which can have redeclarations in
diff erent template
+ // contexts.
+ // The bit is actually stored in the FunctionDecl for space efficiency
+ // reasons.
+ 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 0bd4d64b54a0f..ba77c748815d5 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3069,6 +3069,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 425c41f0f6236..5304b5a2155b4 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4072,22 +4072,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 eec56b7493bad..cf29d8a101b43 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -479,9 +479,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);
@@ -495,8 +492,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 b4f4469ed4e48..89ad2a0a9b7bb 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/DependentDiagnostic.h"
@@ -5245,9 +5246,31 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
RebuildTypeSourceInfoForDefaultSpecialMembers();
SetDeclDefaulted(Function, PatternDecl->getLocation());
} else {
+ NamedDecl *ND = Function;
+ DeclContext *DC = ND->getLexicalDeclContext();
+ std::optional<ArrayRef<TemplateArgument>> Innermost;
+ if (auto *Primary = Function->getPrimaryTemplate();
+ Primary &&
+ !isGenericLambdaCallOperatorOrStaticInvokerSpecialization(Function) &&
+ Function->getTemplateSpecializationKind() !=
+ TSK_ExplicitSpecialization) {
+ 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");
+ if (FunctionDecl *Def = cast<FunctionTemplateDecl>(*It)
+ ->getTemplatedDecl()
+ ->getDefinition())
+ DC = Def->getLexicalDeclContext();
+ else
+ DC = (*It)->getLexicalDeclContext();
+ Innermost.emplace(Function->getTemplateSpecializationArgs()->asArray());
+ }
MultiLevelTemplateArgumentList TemplateArgs = getTemplateInstantiationArgs(
- Function, Function->getLexicalDeclContext(), /*Final=*/false,
- /*Innermost=*/std::nullopt, false, PatternDecl);
+ Function, DC, /*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 8921b92178ee2..b4ff71c8958a4 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -1064,6 +1064,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 3505db441e829..a5d8a3f05bc99 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -679,7 +679,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");
@@ -785,6 +785,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 0000000000000..773a84305a0cd
--- /dev/null
+++ b/clang/test/SemaTemplate/GH55509.cpp
@@ -0,0 +1,112 @@
+// 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
+namespace regression2 {
+ template <class> struct A {
+ template <class T> static void f() {
+ A<int>::f<T>();
+ }
+ };
+ template <> template <class T> void A<int>::f() {
+ static_assert(__is_same(T, long));
+ }
+ template void A<void>::f<long>();
+} // namespace regression2
More information about the llvm-branch-commits
mailing list