[clang] dd8297b - PR42513: Fix handling of function definitions lazily instantiated from
Richard Smith via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 30 18:37:00 PDT 2020
Author: Richard Smith
Date: 2020-10-30T18:35:12-07:00
New Revision: dd8297b0669f8e69b03ba40171b195b5acf0f963
URL: https://github.com/llvm/llvm-project/commit/dd8297b0669f8e69b03ba40171b195b5acf0f963
DIFF: https://github.com/llvm/llvm-project/commit/dd8297b0669f8e69b03ba40171b195b5acf0f963.diff
LOG: PR42513: Fix handling of function definitions lazily instantiated from
friends.
When determining whether a function has a template instantiation
pattern, look for other declarations of that function that were
instantiated from a friend function definition, rather than assuming
that checking for member specialization information on whichever
declaration name lookup found will be sufficient.
Added:
Modified:
clang/include/clang/AST/Decl.h
clang/lib/AST/Decl.cpp
clang/lib/Sema/SemaDecl.cpp
clang/lib/Sema/SemaDeclCXX.cpp
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
clang/test/SemaTemplate/friend.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 5d4f18806ff4..71896c0db086 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2050,7 +2050,14 @@ class FunctionDecl : public DeclaratorDecl,
///
/// The variant that accepts a FunctionDecl pointer will set that function
/// declaration to the declaration that is a definition (if there is one).
- bool isDefined(const FunctionDecl *&Definition) const;
+ ///
+ /// \param CheckForPendingFriendDefinition If \c true, also check for friend
+ /// declarations that were instantiataed from function definitions.
+ /// Such a declaration behaves as if it is a definition for the
+ /// purpose of redefinition checking, but isn't actually a "real"
+ /// definition until its body is instantiated.
+ bool isDefined(const FunctionDecl *&Definition,
+ bool CheckForPendingFriendDefinition = false) const;
bool isDefined() const {
const FunctionDecl* Definition;
@@ -2096,6 +2103,11 @@ class FunctionDecl : public DeclaratorDecl,
willHaveBody() || hasDefiningAttr();
}
+ /// Determine whether this specific declaration of the function is a friend
+ /// declaration that was instantiated from a function definition. Such
+ /// declarations behave like definitions in some contexts.
+ bool isThisDeclarationInstantiatedFromAFriendDefinition() const;
+
/// Returns whether this specific declaration of the function has a body.
bool doesThisDeclarationHaveABody() const {
return (!FunctionDeclBits.HasDefaultedFunctionInfo && Body) ||
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index b4f92d77dd5d..5da0f5a337bb 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2886,10 +2886,55 @@ bool FunctionDecl::hasTrivialBody() const {
return false;
}
-bool FunctionDecl::isDefined(const FunctionDecl *&Definition) const {
- for (auto I : redecls()) {
- if (I->isThisDeclarationADefinition()) {
- Definition = I;
+bool FunctionDecl::isThisDeclarationInstantiatedFromAFriendDefinition() const {
+ if (!getFriendObjectKind())
+ return false;
+
+ // Check for a friend function instantiated from a friend function
+ // definition in a templated class.
+ if (const FunctionDecl *InstantiatedFrom =
+ getInstantiatedFromMemberFunction())
+ return InstantiatedFrom->getFriendObjectKind() &&
+ InstantiatedFrom->isThisDeclarationADefinition();
+
+ // Check for a friend function template instantiated from a friend
+ // function template definition in a templated class.
+ if (const FunctionTemplateDecl *Template = getDescribedFunctionTemplate()) {
+ if (const FunctionTemplateDecl *InstantiatedFrom =
+ Template->getInstantiatedFromMemberTemplate())
+ return InstantiatedFrom->getFriendObjectKind() &&
+ InstantiatedFrom->isThisDeclarationADefinition();
+ }
+
+ return false;
+}
+
+bool FunctionDecl::isDefined(const FunctionDecl *&Definition,
+ bool CheckForPendingFriendDefinition) const {
+ for (const FunctionDecl *FD : redecls()) {
+ if (FD->isThisDeclarationADefinition()) {
+ Definition = FD;
+ return true;
+ }
+
+ // If this is a friend function defined in a class template, it does not
+ // have a body until it is used, nevertheless it is a definition, see
+ // [temp.inst]p2:
+ //
+ // ... for the purpose of determining whether an instantiated redeclaration
+ // is valid according to [basic.def.odr] and [class.mem], a declaration that
+ // corresponds to a definition in the template is considered to be a
+ // definition.
+ //
+ // The following code must produce redefinition error:
+ //
+ // template<typename T> struct C20 { friend void func_20() {} };
+ // C20<int> c20i;
+ // void func_20() {}
+ //
+ if (CheckForPendingFriendDefinition &&
+ FD->isThisDeclarationInstantiatedFromAFriendDefinition()) {
+ Definition = FD;
return true;
}
}
@@ -3639,7 +3684,13 @@ FunctionDecl::getTemplateInstantiationPattern(bool ForDefinition) const {
return getDefinitionOrSelf(getPrimaryTemplate()->getTemplatedDecl());
}
- if (MemberSpecializationInfo *Info = getMemberSpecializationInfo()) {
+ // Check for a declaration of this function that was instantiated from a
+ // friend definition.
+ const FunctionDecl *FD = nullptr;
+ if (!isDefined(FD, /*CheckForPendingFriendDefinition=*/true))
+ FD = this;
+
+ if (MemberSpecializationInfo *Info = FD->getMemberSpecializationInfo()) {
if (ForDefinition &&
!clang::isTemplateInstantiation(Info->getTemplateSpecializationKind()))
return nullptr;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 10c9db073a25..1dcf7a16a897 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2637,8 +2637,11 @@ static const NamedDecl *getDefinition(const Decl *D) {
return Def;
return VD->getActingDefinition();
}
- if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D))
- return FD->getDefinition();
+ if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+ const FunctionDecl *Def = nullptr;
+ if (FD->isDefined(Def, true))
+ return Def;
+ }
return nullptr;
}
@@ -13860,69 +13863,23 @@ Sema::CheckForFunctionRedefinition(FunctionDecl *FD,
const FunctionDecl *EffectiveDefinition,
SkipBodyInfo *SkipBody) {
const FunctionDecl *Definition = EffectiveDefinition;
- if (!Definition && !FD->isDefined(Definition) && !FD->isCXXClassMember()) {
- // If this is a friend function defined in a class template, it does not
- // have a body until it is used, nevertheless it is a definition, see
- // [temp.inst]p2:
- //
- // ... for the purpose of determining whether an instantiated redeclaration
- // is valid according to [basic.def.odr] and [class.mem], a declaration that
- // corresponds to a definition in the template is considered to be a
- // definition.
- //
- // The following code must produce redefinition error:
- //
- // template<typename T> struct C20 { friend void func_20() {} };
- // C20<int> c20i;
- // void func_20() {}
- //
- for (auto I : FD->redecls()) {
- if (I != FD && !I->isInvalidDecl() &&
- I->getFriendObjectKind() != Decl::FOK_None) {
- if (FunctionDecl *Original = I->getInstantiatedFromMemberFunction()) {
- if (FunctionDecl *OrigFD = FD->getInstantiatedFromMemberFunction()) {
- // A merged copy of the same function, instantiated as a member of
- // the same class, is OK.
- if (declaresSameEntity(OrigFD, Original) &&
- declaresSameEntity(cast<Decl>(I->getLexicalDeclContext()),
- cast<Decl>(FD->getLexicalDeclContext())))
- continue;
- }
+ if (!Definition &&
+ !FD->isDefined(Definition, /*CheckForPendingFriendDefinition*/ true))
+ return;
- if (Original->isThisDeclarationADefinition()) {
- Definition = I;
- break;
- }
- }
+ if (Definition->getFriendObjectKind() != Decl::FOK_None) {
+ if (FunctionDecl *OrigDef = Definition->getInstantiatedFromMemberFunction()) {
+ if (FunctionDecl *OrigFD = FD->getInstantiatedFromMemberFunction()) {
+ // A merged copy of the same function, instantiated as a member of
+ // the same class, is OK.
+ if (declaresSameEntity(OrigFD, OrigDef) &&
+ declaresSameEntity(cast<Decl>(Definition->getLexicalDeclContext()),
+ cast<Decl>(FD->getLexicalDeclContext())))
+ return;
}
}
}
- if (!Definition)
- // Similar to friend functions a friend function template may be a
- // definition and do not have a body if it is instantiated in a class
- // template.
- if (FunctionTemplateDecl *FTD = FD->getDescribedFunctionTemplate()) {
- for (auto I : FTD->redecls()) {
- auto D = cast<FunctionTemplateDecl>(I);
- if (D != FTD) {
- assert(!D->isThisDeclarationADefinition() &&
- "More than one definition in redeclaration chain");
- if (D->getFriendObjectKind() != Decl::FOK_None)
- if (FunctionTemplateDecl *FT =
- D->getInstantiatedFromMemberTemplate()) {
- if (FT->isThisDeclarationADefinition()) {
- Definition = D->getTemplatedDecl();
- break;
- }
- }
- }
- }
- }
-
- if (!Definition)
- return;
-
if (canRedefineFunction(Definition, getLangOpts()))
return;
@@ -14040,9 +13997,10 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
FD->setInvalidDecl();
}
- // See if this is a redefinition. If 'will have body' is already set, then
- // these checks were already performed when it was set.
- if (!FD->willHaveBody() && !FD->isLateTemplateParsed()) {
+ // See if this is a redefinition. If 'will have body' (or similar) is already
+ // set, then these checks were already performed when it was set.
+ if (!FD->willHaveBody() && !FD->isLateTemplateParsed() &&
+ !FD->isThisDeclarationInstantiatedFromAFriendDefinition()) {
CheckForFunctionRedefinition(FD, nullptr, SkipBody);
// If we're skipping the body, we're done. Don't enter the scope.
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index f340b5229d47..2b1481efc385 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -694,6 +694,17 @@ bool Sema::MergeCXXFunctionDecl(FunctionDecl *New, FunctionDecl *Old,
Invalid = true;
}
+ // C++11 [temp.friend]p4 (DR329):
+ // When a function is defined in a friend function declaration in a class
+ // template, the function is instantiated when the function is odr-used.
+ // The same restrictions on multiple declarations and definitions that
+ // apply to non-template function declarations and definitions also apply
+ // to these implicit definitions.
+ const FunctionDecl *OldDefinition = nullptr;
+ if (New->isThisDeclarationInstantiatedFromAFriendDefinition() &&
+ Old->isDefined(OldDefinition, true))
+ CheckForFunctionRedefinition(New, OldDefinition);
+
return Invalid;
}
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index dc7c23cfce8f..458572cb6fa3 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2040,8 +2040,11 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(
Function->setInstantiationOfMemberFunction(D, TSK_ImplicitInstantiation);
}
- if (isFriend)
+ if (isFriend) {
Function->setObjectOfFriendDecl();
+ if (FunctionTemplateDecl *FT = Function->getDescribedFunctionTemplate())
+ FT->setObjectOfFriendDecl();
+ }
if (InitFunctionInstantiation(Function, D))
Function->setInvalidDecl();
@@ -2124,63 +2127,33 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(
SemaRef.CheckFunctionDeclaration(/*Scope*/ nullptr, Function, Previous,
IsExplicitSpecialization);
- NamedDecl *PrincipalDecl = (TemplateParams
- ? cast<NamedDecl>(FunctionTemplate)
- : Function);
-
- // If the original function was part of a friend declaration,
- // inherit its namespace state and add it to the owner.
- if (isFriend) {
- Function->setObjectOfFriendDecl();
- if (FunctionTemplateDecl *FT = Function->getDescribedFunctionTemplate())
- FT->setObjectOfFriendDecl();
- DC->makeDeclVisibleInContext(PrincipalDecl);
-
- bool QueuedInstantiation = false;
-
- // C++11 [temp.friend]p4 (DR329):
- // When a function is defined in a friend function declaration in a class
- // template, the function is instantiated when the function is odr-used.
- // The same restrictions on multiple declarations and definitions that
- // apply to non-template function declarations and definitions also apply
- // to these implicit definitions.
- if (D->isThisDeclarationADefinition()) {
- SemaRef.CheckForFunctionRedefinition(Function);
- if (!Function->isInvalidDecl()) {
- for (auto R : Function->redecls()) {
- if (R == Function)
- continue;
-
- // If some prior declaration of this function has been used, we need
- // to instantiate its definition.
- if (!QueuedInstantiation && R->isUsed(false)) {
- if (MemberSpecializationInfo *MSInfo =
- Function->getMemberSpecializationInfo()) {
- if (MSInfo->getPointOfInstantiation().isInvalid()) {
- SourceLocation Loc = R->getLocation(); // FIXME
- MSInfo->setPointOfInstantiation(Loc);
- SemaRef.PendingLocalImplicitInstantiations.push_back(
- std::make_pair(Function, Loc));
- QueuedInstantiation = true;
- }
- }
- }
- }
+ // Check the template parameter list against the previous declaration. The
+ // goal here is to pick up default arguments added since the friend was
+ // declared; we know the template parameter lists match, since otherwise
+ // we would not have picked this template as the previous declaration.
+ if (isFriend && TemplateParams && FunctionTemplate->getPreviousDecl()) {
+ SemaRef.CheckTemplateParameterList(
+ TemplateParams,
+ FunctionTemplate->getPreviousDecl()->getTemplateParameters(),
+ Function->isThisDeclarationADefinition()
+ ? Sema::TPC_FriendFunctionTemplateDefinition
+ : Sema::TPC_FriendFunctionTemplate);
+ }
+
+ // If we're introducing a friend definition after the first use, trigger
+ // instantiation.
+ // FIXME: If this is a friend function template definition, we should check
+ // to see if any specializations have been used.
+ if (isFriend && D->isThisDeclarationADefinition() && Function->isUsed(false)) {
+ if (MemberSpecializationInfo *MSInfo =
+ Function->getMemberSpecializationInfo()) {
+ if (MSInfo->getPointOfInstantiation().isInvalid()) {
+ SourceLocation Loc = D->getLocation(); // FIXME
+ MSInfo->setPointOfInstantiation(Loc);
+ SemaRef.PendingLocalImplicitInstantiations.push_back(
+ std::make_pair(Function, Loc));
}
}
-
- // Check the template parameter list against the previous declaration. The
- // goal here is to pick up default arguments added since the friend was
- // declared; we know the template parameter lists match, since otherwise
- // we would not have picked this template as the previous declaration.
- if (TemplateParams && FunctionTemplate->getPreviousDecl()) {
- SemaRef.CheckTemplateParameterList(
- TemplateParams,
- FunctionTemplate->getPreviousDecl()->getTemplateParameters(),
- Function->isThisDeclarationADefinition()
- ? Sema::TPC_FriendFunctionTemplateDefinition
- : Sema::TPC_FriendFunctionTemplate);
- }
}
if (D->isExplicitlyDefaulted()) {
@@ -2190,7 +2163,13 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(
if (D->isDeleted())
SemaRef.SetDeclDeleted(Function, D->getLocation());
- if (Function->isLocalExternDecl() && !Function->getPreviousDecl())
+ NamedDecl *PrincipalDecl =
+ (TemplateParams ? cast<NamedDecl>(FunctionTemplate) : Function);
+
+ // If this declaration lives in a
diff erent context from its lexical context,
+ // add it to the corresponding lookup table.
+ if (isFriend ||
+ (Function->isLocalExternDecl() && !Function->getPreviousDecl()))
DC->makeDeclVisibleInContext(PrincipalDecl);
if (Function->isOverloadedOperator() && !DC->isRecord() &&
@@ -4672,8 +4651,7 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
bool Recursive,
bool DefinitionRequired,
bool AtEndOfTU) {
- if (Function->isInvalidDecl() || Function->isDefined() ||
- isa<CXXDeductionGuideDecl>(Function))
+ if (Function->isInvalidDecl() || isa<CXXDeductionGuideDecl>(Function))
return;
// Never instantiate an explicit specialization except if it is a class scope
@@ -4683,6 +4661,20 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
if (TSK == TSK_ExplicitSpecialization)
return;
+ // Don't instantiate a definition if we already have one.
+ const FunctionDecl *ExistingDefn = nullptr;
+ if (Function->isDefined(ExistingDefn,
+ /*CheckForPendingFriendDefinition=*/true)) {
+ if (ExistingDefn->isThisDeclarationADefinition())
+ return;
+
+ // If we're asked to instantiate a function whose body comes from an
+ // instantiated friend declaration, attach the instantiated body to the
+ // corresponding declaration of the function.
+ assert(ExistingDefn->isThisDeclarationInstantiatedFromAFriendDefinition());
+ Function = const_cast<FunctionDecl*>(ExistingDefn);
+ }
+
// Find the function body that we'll be substituting.
const FunctionDecl *PatternDecl = Function->getTemplateInstantiationPattern();
assert(PatternDecl && "instantiating a non-template");
diff --git a/clang/test/SemaTemplate/friend.cpp b/clang/test/SemaTemplate/friend.cpp
index 283c7732ccff..a1dcd866b9e8 100644
--- a/clang/test/SemaTemplate/friend.cpp
+++ b/clang/test/SemaTemplate/friend.cpp
@@ -141,3 +141,10 @@ namespace PR37556 {
inline namespace N { int z1, z2; }
template struct Z<int>;
}
+
+namespace PR42513_comment3 {
+ template<typename X> struct T { friend auto f(X*) { return nullptr; } };
+ struct X1 { friend auto f(X1*); };
+ template struct T<X1>;
+ int n = f((X1*)nullptr); // expected-error {{cannot initialize a variable of type 'int' with an rvalue of type 'nullptr_t'}}
+}
More information about the cfe-commits
mailing list