Skip to content

Commit c7fbe21

Browse files
committed
Fix rejects-valid when referencing an implicit operator== from within a
templated class. When a defaulted operator<=> results in the injection of a defaulted operator==, that operator== can be named by unqualified name within the same class, even if the class is templated. To make this work, perform the transform from defaulted operator<=> to defaulted operator== in the template definition context instead of the template instantiation context. This results in our substituting into a declaration from a context where we don't have a full list of template arguments (or indeed any), for which we are now more careful to not spuriously instantiate declarations that are not dependent on the arguments we're substituting.
1 parent 771b788 commit c7fbe21

File tree

8 files changed

+163
-74
lines changed

8 files changed

+163
-74
lines changed

clang/include/clang/AST/DeclBase.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class SourceManager;
6666
class Stmt;
6767
class StoredDeclsMap;
6868
class TemplateDecl;
69+
class TemplateParameterList;
6970
class TranslationUnitDecl;
7071
class UsingDirectiveDecl;
7172

@@ -862,6 +863,10 @@ class alignas(8) Decl {
862863
// within the scope of a template parameter).
863864
bool isTemplated() const;
864865

866+
/// Determine the number of levels of template parameter surrounding this
867+
/// declaration.
868+
unsigned getTemplateDepth() const;
869+
865870
/// isDefinedOutsideFunctionOrMethod - This predicate returns true if this
866871
/// scoped decl is defined outside the current function or method. This is
867872
/// roughly global variables and functions, but also handles enums (which
@@ -1038,8 +1043,16 @@ class alignas(8) Decl {
10381043

10391044
/// If this is a declaration that describes some template, this
10401045
/// method returns that template declaration.
1046+
///
1047+
/// Note that this returns nullptr for partial specializations, because they
1048+
/// are not modeled as TemplateDecls. Use getDescribedTemplateParams to handle
1049+
/// those cases.
10411050
TemplateDecl *getDescribedTemplate() const;
10421051

1052+
/// If this is a declaration that describes some template or partial
1053+
/// specialization, this returns the corresponding template parameter list.
1054+
const TemplateParameterList *getDescribedTemplateParams() const;
1055+
10431056
/// Returns the function itself, or the templated function if this is a
10441057
/// function template.
10451058
FunctionDecl *getAsFunction() LLVM_READONLY;

clang/include/clang/Sema/Template.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ class VarDecl;
9595
return TemplateArgumentLists.size();
9696
}
9797

98+
unsigned getNumRetainedOuterLevels() const {
99+
return NumRetainedOuterLevels;
100+
}
101+
98102
/// Determine how many of the \p OldDepth outermost template parameter
99103
/// lists would be removed by substituting these arguments.
100104
unsigned getNewDepth(unsigned OldDepth) const {
@@ -159,6 +163,9 @@ class VarDecl;
159163
void addOuterRetainedLevel() {
160164
++NumRetainedOuterLevels;
161165
}
166+
void addOuterRetainedLevels(unsigned Num) {
167+
NumRetainedOuterLevels += Num;
168+
}
162169

163170
/// Retrieve the innermost template argument list.
164171
const ArgList &getInnermost() const {

clang/lib/AST/DeclBase.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,16 @@ TemplateDecl *Decl::getDescribedTemplate() const {
240240
return nullptr;
241241
}
242242

243+
const TemplateParameterList *Decl::getDescribedTemplateParams() const {
244+
if (auto *TD = getDescribedTemplate())
245+
return TD->getTemplateParameters();
246+
if (auto *CTPSD = dyn_cast<ClassTemplatePartialSpecializationDecl>(this))
247+
return CTPSD->getTemplateParameters();
248+
if (auto *VTPSD = dyn_cast<VarTemplatePartialSpecializationDecl>(this))
249+
return VTPSD->getTemplateParameters();
250+
return nullptr;
251+
}
252+
243253
bool Decl::isTemplated() const {
244254
// A declaration is dependent if it is a template or a template pattern, or
245255
// is within (lexcially for a friend, semantically otherwise) a dependent
@@ -248,7 +258,29 @@ bool Decl::isTemplated() const {
248258
if (auto *AsDC = dyn_cast<DeclContext>(this))
249259
return AsDC->isDependentContext();
250260
auto *DC = getFriendObjectKind() ? getLexicalDeclContext() : getDeclContext();
251-
return DC->isDependentContext() || isTemplateDecl() || getDescribedTemplate();
261+
return DC->isDependentContext() || isTemplateDecl() ||
262+
getDescribedTemplateParams();
263+
}
264+
265+
unsigned Decl::getTemplateDepth() const {
266+
if (auto *DC = dyn_cast<DeclContext>(this))
267+
if (DC->isFileContext())
268+
return 0;
269+
270+
if (auto *TPL = getDescribedTemplateParams())
271+
return TPL->getDepth() + 1;
272+
273+
// If this is a dependent lambda, there might be an enclosing variable
274+
// template. In this case, the next step is not the parent DeclContext (or
275+
// even a DeclContext at all).
276+
auto *RD = dyn_cast<CXXRecordDecl>(this);
277+
if (RD && RD->isDependentLambda())
278+
if (Decl *Context = RD->getLambdaContextDecl())
279+
return Context->getTemplateDepth();
280+
281+
const DeclContext *DC =
282+
getFriendObjectKind() ? getLexicalDeclContext() : getDeclContext();
283+
return cast<Decl>(DC)->getTemplateDepth();
252284
}
253285

254286
const DeclContext *Decl::getParentFunctionOrMethod() const {

clang/lib/Sema/SemaDecl.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17170,10 +17170,10 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
1717017170
I.setAccess((*I)->getAccess());
1717117171
}
1717217172

17173-
if (!CXXRecord->isDependentType()) {
17174-
// Add any implicitly-declared members to this class.
17175-
AddImplicitlyDeclaredMembersToClass(CXXRecord);
17173+
// Add any implicitly-declared members to this class.
17174+
AddImplicitlyDeclaredMembersToClass(CXXRecord);
1717617175

17176+
if (!CXXRecord->isDependentType()) {
1717717177
if (!CXXRecord->isInvalidDecl()) {
1717817178
// If we have virtual base classes, we may end up finding multiple
1717917179
// final overriders for a given virtual function. Check for this

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 72 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9839,86 +9839,95 @@ static void findImplicitlyDeclaredEqualityComparisons(
98399839
/// [special]p1). This routine can only be executed just before the
98409840
/// definition of the class is complete.
98419841
void Sema::AddImplicitlyDeclaredMembersToClass(CXXRecordDecl *ClassDecl) {
9842-
if (ClassDecl->needsImplicitDefaultConstructor()) {
9843-
++getASTContext().NumImplicitDefaultConstructors;
9842+
// Don't add implicit special members to templated classes.
9843+
// FIXME: This means unqualified lookups for 'operator=' within a class
9844+
// template don't work properly.
9845+
if (!ClassDecl->isDependentType()) {
9846+
if (ClassDecl->needsImplicitDefaultConstructor()) {
9847+
++getASTContext().NumImplicitDefaultConstructors;
98449848

9845-
if (ClassDecl->hasInheritedConstructor())
9846-
DeclareImplicitDefaultConstructor(ClassDecl);
9847-
}
9849+
if (ClassDecl->hasInheritedConstructor())
9850+
DeclareImplicitDefaultConstructor(ClassDecl);
9851+
}
98489852

9849-
if (ClassDecl->needsImplicitCopyConstructor()) {
9850-
++getASTContext().NumImplicitCopyConstructors;
9853+
if (ClassDecl->needsImplicitCopyConstructor()) {
9854+
++getASTContext().NumImplicitCopyConstructors;
98519855

9852-
// If the properties or semantics of the copy constructor couldn't be
9853-
// determined while the class was being declared, force a declaration
9854-
// of it now.
9855-
if (ClassDecl->needsOverloadResolutionForCopyConstructor() ||
9856-
ClassDecl->hasInheritedConstructor())
9857-
DeclareImplicitCopyConstructor(ClassDecl);
9858-
// For the MS ABI we need to know whether the copy ctor is deleted. A
9859-
// prerequisite for deleting the implicit copy ctor is that the class has a
9860-
// move ctor or move assignment that is either user-declared or whose
9861-
// semantics are inherited from a subobject. FIXME: We should provide a more
9862-
// direct way for CodeGen to ask whether the constructor was deleted.
9863-
else if (Context.getTargetInfo().getCXXABI().isMicrosoft() &&
9864-
(ClassDecl->hasUserDeclaredMoveConstructor() ||
9865-
ClassDecl->needsOverloadResolutionForMoveConstructor() ||
9866-
ClassDecl->hasUserDeclaredMoveAssignment() ||
9867-
ClassDecl->needsOverloadResolutionForMoveAssignment()))
9868-
DeclareImplicitCopyConstructor(ClassDecl);
9869-
}
9856+
// If the properties or semantics of the copy constructor couldn't be
9857+
// determined while the class was being declared, force a declaration
9858+
// of it now.
9859+
if (ClassDecl->needsOverloadResolutionForCopyConstructor() ||
9860+
ClassDecl->hasInheritedConstructor())
9861+
DeclareImplicitCopyConstructor(ClassDecl);
9862+
// For the MS ABI we need to know whether the copy ctor is deleted. A
9863+
// prerequisite for deleting the implicit copy ctor is that the class has
9864+
// a move ctor or move assignment that is either user-declared or whose
9865+
// semantics are inherited from a subobject. FIXME: We should provide a
9866+
// more direct way for CodeGen to ask whether the constructor was deleted.
9867+
else if (Context.getTargetInfo().getCXXABI().isMicrosoft() &&
9868+
(ClassDecl->hasUserDeclaredMoveConstructor() ||
9869+
ClassDecl->needsOverloadResolutionForMoveConstructor() ||
9870+
ClassDecl->hasUserDeclaredMoveAssignment() ||
9871+
ClassDecl->needsOverloadResolutionForMoveAssignment()))
9872+
DeclareImplicitCopyConstructor(ClassDecl);
9873+
}
98709874

9871-
if (getLangOpts().CPlusPlus11 && ClassDecl->needsImplicitMoveConstructor()) {
9872-
++getASTContext().NumImplicitMoveConstructors;
9875+
if (getLangOpts().CPlusPlus11 &&
9876+
ClassDecl->needsImplicitMoveConstructor()) {
9877+
++getASTContext().NumImplicitMoveConstructors;
98739878

9874-
if (ClassDecl->needsOverloadResolutionForMoveConstructor() ||
9875-
ClassDecl->hasInheritedConstructor())
9876-
DeclareImplicitMoveConstructor(ClassDecl);
9877-
}
9879+
if (ClassDecl->needsOverloadResolutionForMoveConstructor() ||
9880+
ClassDecl->hasInheritedConstructor())
9881+
DeclareImplicitMoveConstructor(ClassDecl);
9882+
}
98789883

9879-
if (ClassDecl->needsImplicitCopyAssignment()) {
9880-
++getASTContext().NumImplicitCopyAssignmentOperators;
9884+
if (ClassDecl->needsImplicitCopyAssignment()) {
9885+
++getASTContext().NumImplicitCopyAssignmentOperators;
98819886

9882-
// If we have a dynamic class, then the copy assignment operator may be
9883-
// virtual, so we have to declare it immediately. This ensures that, e.g.,
9884-
// it shows up in the right place in the vtable and that we diagnose
9885-
// problems with the implicit exception specification.
9886-
if (ClassDecl->isDynamicClass() ||
9887-
ClassDecl->needsOverloadResolutionForCopyAssignment() ||
9888-
ClassDecl->hasInheritedAssignment())
9889-
DeclareImplicitCopyAssignment(ClassDecl);
9890-
}
9887+
// If we have a dynamic class, then the copy assignment operator may be
9888+
// virtual, so we have to declare it immediately. This ensures that, e.g.,
9889+
// it shows up in the right place in the vtable and that we diagnose
9890+
// problems with the implicit exception specification.
9891+
if (ClassDecl->isDynamicClass() ||
9892+
ClassDecl->needsOverloadResolutionForCopyAssignment() ||
9893+
ClassDecl->hasInheritedAssignment())
9894+
DeclareImplicitCopyAssignment(ClassDecl);
9895+
}
98919896

9892-
if (getLangOpts().CPlusPlus11 && ClassDecl->needsImplicitMoveAssignment()) {
9893-
++getASTContext().NumImplicitMoveAssignmentOperators;
9897+
if (getLangOpts().CPlusPlus11 && ClassDecl->needsImplicitMoveAssignment()) {
9898+
++getASTContext().NumImplicitMoveAssignmentOperators;
98949899

9895-
// Likewise for the move assignment operator.
9896-
if (ClassDecl->isDynamicClass() ||
9897-
ClassDecl->needsOverloadResolutionForMoveAssignment() ||
9898-
ClassDecl->hasInheritedAssignment())
9899-
DeclareImplicitMoveAssignment(ClassDecl);
9900-
}
9900+
// Likewise for the move assignment operator.
9901+
if (ClassDecl->isDynamicClass() ||
9902+
ClassDecl->needsOverloadResolutionForMoveAssignment() ||
9903+
ClassDecl->hasInheritedAssignment())
9904+
DeclareImplicitMoveAssignment(ClassDecl);
9905+
}
99019906

9902-
if (ClassDecl->needsImplicitDestructor()) {
9903-
++getASTContext().NumImplicitDestructors;
9907+
if (ClassDecl->needsImplicitDestructor()) {
9908+
++getASTContext().NumImplicitDestructors;
99049909

9905-
// If we have a dynamic class, then the destructor may be virtual, so we
9906-
// have to declare the destructor immediately. This ensures that, e.g., it
9907-
// shows up in the right place in the vtable and that we diagnose problems
9908-
// with the implicit exception specification.
9909-
if (ClassDecl->isDynamicClass() ||
9910-
ClassDecl->needsOverloadResolutionForDestructor())
9911-
DeclareImplicitDestructor(ClassDecl);
9910+
// If we have a dynamic class, then the destructor may be virtual, so we
9911+
// have to declare the destructor immediately. This ensures that, e.g., it
9912+
// shows up in the right place in the vtable and that we diagnose problems
9913+
// with the implicit exception specification.
9914+
if (ClassDecl->isDynamicClass() ||
9915+
ClassDecl->needsOverloadResolutionForDestructor())
9916+
DeclareImplicitDestructor(ClassDecl);
9917+
}
99129918
}
99139919

99149920
// C++2a [class.compare.default]p3:
99159921
// If the member-specification does not explicitly declare any member or
99169922
// friend named operator==, an == operator function is declared implicitly
9917-
// for each defaulted three-way comparison operator function defined in the
9918-
// member-specification
9923+
// for each defaulted three-way comparison operator function defined in
9924+
// the member-specification
99199925
// FIXME: Consider doing this lazily.
9920-
if (getLangOpts().CPlusPlus20) {
9921-
llvm::SmallVector<FunctionDecl*, 4> DefaultedSpaceships;
9926+
// We do this during the initial parse for a class template, not during
9927+
// instantiation, so that we can handle unqualified lookups for 'operator=='
9928+
// when parsing the template.
9929+
if (getLangOpts().CPlusPlus20 && !inTemplateInstantiation()) {
9930+
llvm::SmallVector<FunctionDecl *, 4> DefaultedSpaceships;
99229931
findImplicitlyDeclaredEqualityComparisons(Context, ClassDecl,
99239932
DefaultedSpaceships);
99249933
for (auto *FD : DefaultedSpaceships)

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3719,6 +3719,7 @@ FunctionDecl *Sema::SubstSpaceshipAsEqualEqual(CXXRecordDecl *RD,
37193719
// access and function-definition and in the same class scope as the
37203720
// three-way comparison operator function
37213721
MultiLevelTemplateArgumentList NoTemplateArgs;
3722+
NoTemplateArgs.addOuterRetainedLevels(RD->getTemplateDepth());
37223723
TemplateDeclInstantiator Instantiator(*this, RD, NoTemplateArgs);
37233724
Decl *R;
37243725
if (auto *MD = dyn_cast<CXXMethodDecl>(Spaceship)) {
@@ -5605,6 +5606,20 @@ DeclContext *Sema::FindInstantiatedContext(SourceLocation Loc, DeclContext* DC,
56055606
} else return DC;
56065607
}
56075608

5609+
/// Determine whether the given context is dependent on template parameters at
5610+
/// level \p Level or below.
5611+
///
5612+
/// Sometimes we only substitute an inner set of template arguments and leave
5613+
/// the outer templates alone. In such cases, contexts dependent only on the
5614+
/// outer levels are not effectively dependent.
5615+
static bool isDependentContextAtLevel(DeclContext *DC, unsigned Level) {
5616+
if (!DC->isDependentContext())
5617+
return false;
5618+
if (!Level)
5619+
return true;
5620+
return cast<Decl>(DC)->getTemplateDepth() > Level;
5621+
}
5622+
56085623
/// Find the instantiation of the given declaration within the
56095624
/// current instantiation.
56105625
///
@@ -5635,6 +5650,10 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D,
56355650
const MultiLevelTemplateArgumentList &TemplateArgs,
56365651
bool FindingInstantiatedContext) {
56375652
DeclContext *ParentDC = D->getDeclContext();
5653+
// Determine whether our parent context depends on any of the tempalte
5654+
// arguments we're currently substituting.
5655+
bool ParentDependsOnArgs = isDependentContextAtLevel(
5656+
ParentDC, TemplateArgs.getNumRetainedOuterLevels());
56385657
// FIXME: Parmeters of pointer to functions (y below) that are themselves
56395658
// parameters (p below) can have their ParentDC set to the translation-unit
56405659
// - thus we can not consistently check if the ParentDC of such a parameter
@@ -5651,15 +5670,14 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D,
56515670
// - as long as we have a ParmVarDecl whose parent is non-dependent and
56525671
// whose type is not instantiation dependent, do nothing to the decl
56535672
// - otherwise find its instantiated decl.
5654-
if (isa<ParmVarDecl>(D) && !ParentDC->isDependentContext() &&
5673+
if (isa<ParmVarDecl>(D) && !ParentDependsOnArgs &&
56555674
!cast<ParmVarDecl>(D)->getType()->isInstantiationDependentType())
56565675
return D;
56575676
if (isa<ParmVarDecl>(D) || isa<NonTypeTemplateParmDecl>(D) ||
56585677
isa<TemplateTypeParmDecl>(D) || isa<TemplateTemplateParmDecl>(D) ||
5659-
((ParentDC->isFunctionOrMethod() ||
5660-
isa<OMPDeclareReductionDecl>(ParentDC) ||
5661-
isa<OMPDeclareMapperDecl>(ParentDC)) &&
5662-
ParentDC->isDependentContext()) ||
5678+
(ParentDependsOnArgs && (ParentDC->isFunctionOrMethod() ||
5679+
isa<OMPDeclareReductionDecl>(ParentDC) ||
5680+
isa<OMPDeclareMapperDecl>(ParentDC))) ||
56635681
(isa<CXXRecordDecl>(D) && cast<CXXRecordDecl>(D)->isLambda())) {
56645682
// D is a local of some kind. Look into the map of local
56655683
// declarations to their instantiations.
@@ -5813,7 +5831,7 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D,
58135831
// anonymous unions in class templates).
58145832
}
58155833

5816-
if (!ParentDC->isDependentContext())
5834+
if (!ParentDependsOnArgs)
58175835
return D;
58185836

58195837
ParentDC = FindInstantiatedContext(Loc, ParentDC, TemplateArgs);

clang/test/PCH/cxx2a-defaulted-comparison.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RxN: %clang_cc1 -std=c++2a -verify -Wno-defaulted-function-deleted -include %s %s
1+
// RUN: %clang_cc1 -std=c++2a -verify -Wno-defaulted-function-deleted -include %s %s
22
//
33
// RUN: %clang_cc1 -std=c++2a -emit-pch %s -o %t.pch
44
// RUN: %clang_cc1 -std=c++2a -include-pch %t.pch %s -verify
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: %clang_cc1 -std=c++20 -verify %s
2+
// expected-no-diagnostics
3+
4+
namespace SpaceshipImpliesEq {
5+
template<typename T> struct A {
6+
int operator<=>(const A&) const = default;
7+
constexpr bool f() { return operator==(*this); }
8+
};
9+
static_assert(A<int>().f());
10+
}

0 commit comments

Comments
 (0)