Skip to content

[clang] Track function template instantiation from definition #112241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ Bug Fixes to Attribute Support
Bug Fixes to C++ Support
^^^^^^^^^^^^^^^^^^^^^^^^

- Clang is now better at keeping track of friend function template instance contexts. (#GH55509)

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions clang/include/clang/AST/DeclBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -1777,6 +1777,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)
Expand Down Expand Up @@ -1827,7 +1829,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
Expand All @@ -1838,12 +1840,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;

Expand All @@ -1857,7 +1859,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
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/AST/DeclTemplate.h
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,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,
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 1 addition & 16 deletions clang/lib/Sema/SemaTemplateDeduction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4074,22 +4074,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);
Expand Down
9 changes: 4 additions & 5 deletions clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down
22 changes: 20 additions & 2 deletions clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -5245,9 +5246,26 @@ 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");
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.
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Serialization/ASTWriterDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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);
Expand Down
101 changes: 101 additions & 0 deletions clang/test/SemaTemplate/GH55509.cpp
Original file line number Diff line number Diff line change
@@ -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@-1 {{declared here}}
};

void test() {
cica(A<0>{}, 0);
// expected-error@-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@-1 {{candidate function}}

template <int N> void MakeCica(A<N+1> = {});
// expected-note@-1 {{candidate function}}

void test() {
MakeCica<0>();

MakeCica<0>();
// expected-error@-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