Author: Richard Smith Date: 2019-12-10T17:24:27-08:00 New Revision: bc24014b9765a454cb5214f4871451a41ffb7d29
URL: https://github.com/llvm/llvm-project/commit/bc24014b9765a454cb5214f4871451a41ffb7d29 DIFF: https://github.com/llvm/llvm-project/commit/bc24014b9765a454cb5214f4871451a41ffb7d29.diff LOG: [c++20] Implement P1185R2 (as modified by P2002R0). For each defaulted operator<=> in a class that doesn't explicitly declare any operator==, also inject a matching implicit defaulted operator==. Added: clang/test/CXX/class/class.compare/class.compare.default/p4.cpp Modified: clang/include/clang/Basic/DiagnosticSemaKinds.td clang/include/clang/Sema/Sema.h clang/include/clang/Sema/Template.h clang/lib/Frontend/FrontendActions.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaOverload.cpp clang/lib/Sema/SemaTemplateInstantiate.cpp clang/lib/Sema/SemaTemplateInstantiateDecl.cpp clang/test/CodeGenCXX/cxx2a-three-way-comparison.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index aeeff2b9a76e..a5f35996cfdc 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3840,6 +3840,7 @@ def select_ovl_candidate_kind : TextSubstitution< "constructor (the implicit move constructor)|" "function (the implicit copy assignment operator)|" "function (the implicit move assignment operator)|" + "function (the implicit 'operator==' for this 'operator<=>)'|" "inherited constructor}0%select{| template| %2}1">; def note_ovl_candidate : Note< @@ -8207,7 +8208,8 @@ def warn_defaulted_comparison_deleted : Warning< "explicitly defaulted %sub{select_defaulted_comparison_kind}0 is implicitly " "deleted">, InGroup<DefaultedFunctionDeleted>; def err_non_first_default_compare_deletes : Error< - "defaulting this %sub{select_defaulted_comparison_kind}0 " + "defaulting %select{this %sub{select_defaulted_comparison_kind}1|" + "the corresponding implicit 'operator==' for this defaulted 'operator<=>'}0 " "would delete it after its first declaration">; def note_defaulted_comparison_union : Note< "defaulted %0 is implicitly deleted because " @@ -8237,14 +8239,19 @@ def note_defaulted_comparison_cannot_deduce : Note< def note_defaulted_comparison_cannot_deduce_callee : Note< "selected 'operator<=>' for %select{|member|base class}0 %1 declared here">; def err_incorrect_defaulted_comparison_constexpr : Error< - "defaulted definition of %sub{select_defaulted_comparison_kind}0 " - "cannot be declared %select{constexpr|consteval}1 because it invokes " - "a non-constexpr comparison function">; + "defaulted definition of %select{%sub{select_defaulted_comparison_kind}1|" + "three-way comparison operator}0 " + "cannot be declared %select{constexpr|consteval}2 because " + "%select{it|the corresponding implicit 'operator=='}0 " + "invokes a non-constexpr comparison function">; def note_defaulted_comparison_not_constexpr : Note< "non-constexpr comparison function would be used to compare " "%select{|member %1|base class %1}0">; def note_defaulted_comparison_not_constexpr_here : Note< "non-constexpr comparison function declared here">; +def note_in_declaration_of_implicit_equality_comparison : Note< + "while declaring the corresponding implicit 'operator==' " + "for this defaulted 'operator<=>'">; def ext_implicit_exception_spec_mismatch : ExtWarn< "function previously declared with an %select{explicit|implicit}0 exception " diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 1cdaab3dc28c..5a1ee507218c 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -6524,6 +6524,8 @@ class Sema final { bool CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *MD, DefaultedComparisonKind DCK); + void DeclareImplicitEqualityComparison(CXXRecordDecl *RD, + FunctionDecl *Spaceship); void DefineDefaultedComparison(SourceLocation Loc, FunctionDecl *FD, DefaultedComparisonKind DCK); @@ -7838,6 +7840,10 @@ class Sema final { /// We are declaring an implicit special member function. DeclaringSpecialMember, + /// We are declaring an implicit 'operator==' for a defaulted + /// 'operator<=>'. + DeclaringImplicitEqualityComparison, + /// We are defining a synthesized function (such as a defaulted special /// member). DefiningSynthesizedFunction, @@ -8468,6 +8474,11 @@ class Sema final { Decl *SubstDecl(Decl *D, DeclContext *Owner, const MultiLevelTemplateArgumentList &TemplateArgs); + /// Substitute the name and return type of a defaulted 'operator<=>' to form + /// an implicit 'operator=='. + FunctionDecl *SubstSpaceshipAsEqualEqual(CXXRecordDecl *RD, + FunctionDecl *Spaceship); + ExprResult SubstInitializer(Expr *E, const MultiLevelTemplateArgumentList &TemplateArgs, bool CXXDirectInit); diff --git a/clang/include/clang/Sema/Template.h b/clang/include/clang/Sema/Template.h index 102e525e2e16..4c1cfecd4de6 100644 --- a/clang/include/clang/Sema/Template.h +++ b/clang/include/clang/Sema/Template.h @@ -473,13 +473,21 @@ class VarDecl; #include "clang/AST/DeclNodes.inc" + enum class RewriteKind { None, RewriteSpaceshipAsEqualEqual }; + + void adjustForRewrite(RewriteKind RK, FunctionDecl *Orig, QualType &T, + TypeSourceInfo *&TInfo, + DeclarationNameInfo &NameInfo); + // A few supplemental visitor functions. Decl *VisitCXXMethodDecl(CXXMethodDecl *D, TemplateParameterList *TemplateParams, Optional<const ASTTemplateArgumentListInfo *> - ClassScopeSpecializationArgs = llvm::None); + ClassScopeSpecializationArgs = llvm::None, + RewriteKind RK = RewriteKind::None); Decl *VisitFunctionDecl(FunctionDecl *D, - TemplateParameterList *TemplateParams); + TemplateParameterList *TemplateParams, + RewriteKind RK = RewriteKind::None); Decl *VisitDecl(Decl *D); Decl *VisitVarDecl(VarDecl *D, bool InstantiatingVarTemplate, ArrayRef<BindingDecl *> *Bindings = nullptr); diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp index 4d47c768ad3c..aeea63ca323f 100644 --- a/clang/lib/Frontend/FrontendActions.cpp +++ b/clang/lib/Frontend/FrontendActions.cpp @@ -413,6 +413,8 @@ class DefaultTemplateInstCallback : public TemplateInstantiationCallback { return "ExceptionSpecInstantiation"; case CodeSynthesisContext::DeclaringSpecialMember: return "DeclaringSpecialMember"; + case CodeSynthesisContext::DeclaringImplicitEqualityComparison: + return "DeclaringImplicitEqualityComparison"; case CodeSynthesisContext::DefiningSynthesizedFunction: return "DefiningSynthesizedFunction"; case CodeSynthesisContext::RewritingOperatorAsSpaceship: diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 7bbd0114b1e2..72ac81776aae 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6481,40 +6481,38 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) { bool HasMethodWithOverrideControl = false, HasOverridingMethodWithoutOverrideControl = false; - for (auto *M : Record->methods()) { - // FIXME: We could do this check for dependent types with non-dependent - // bases. - if (!Record->isDependentType()) { - // See if a method overloads virtual methods in a base - // class without overriding any. - if (!M->isStatic()) - DiagnoseHiddenVirtualMethods(M); - if (M->hasAttr<OverrideAttr>()) - HasMethodWithOverrideControl = true; - else if (M->size_overridden_methods() > 0) - HasOverridingMethodWithoutOverrideControl = true; - } + for (auto *D : Record->decls()) { + if (auto *M = dyn_cast<CXXMethodDecl>(D)) { + // FIXME: We could do this check for dependent types with non-dependent + // bases. + if (!Record->isDependentType()) { + // See if a method overloads virtual methods in a base + // class without overriding any. + if (!M->isStatic()) + DiagnoseHiddenVirtualMethods(M); + if (M->hasAttr<OverrideAttr>()) + HasMethodWithOverrideControl = true; + else if (M->size_overridden_methods() > 0) + HasOverridingMethodWithoutOverrideControl = true; + } - if (!isa<CXXDestructorDecl>(M)) - CompleteMemberFunction(M); + if (!isa<CXXDestructorDecl>(M)) + CompleteMemberFunction(M); + } else if (auto *F = dyn_cast<FriendDecl>(D)) { + CheckForDefaultedFunction( + dyn_cast_or_null<FunctionDecl>(F->getFriendDecl())); + } } if (HasMethodWithOverrideControl && HasOverridingMethodWithoutOverrideControl) { // At least one method has the 'override' control declared. - // Diagnose all other overridden methods which do not have 'override' specified on them. + // Diagnose all other overridden methods which do not have 'override' + // specified on them. for (auto *M : Record->methods()) DiagnoseAbsenceOfOverrideControl(M); } - // Process any defaulted friends in the member-specification. - if (!Record->isDependentType()) { - for (FriendDecl *D : Record->friends()) { - CheckForDefaultedFunction( - dyn_cast_or_null<FunctionDecl>(D->getFriendDecl())); - } - } - // Check the defaulted secondary comparisons after any other member functions. for (FunctionDecl *FD : DefaultedSecondaryComparisons) CheckExplicitlyDefaultedFunction(S, FD); @@ -7868,7 +7866,9 @@ static void lookupOperatorsForDefaultedComparison(Sema &Self, Scope *S, Lookup(ExtraOp); // For 'operator<=>', we also form a 'cmp != 0' expression, and might - // synthesize a three-way comparison from '<' and '=='. + // synthesize a three-way comparison from '<' and '=='. In a dependent + // context, we also need to look up '==' in case we implicitly declare a + // defaulted 'operator=='. if (Op == OO_Spaceship) { Lookup(OO_ExclaimEqual); Lookup(OO_Less); @@ -7904,9 +7904,13 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, for (const ParmVarDecl *Param : FD->parameters()) { if (!Param->getType()->isDependentType() && !Context.hasSameType(Param->getType(), ExpectedParmType)) { - Diag(FD->getLocation(), diag::err_defaulted_comparison_param) - << (int)DCK << Param->getType() << ExpectedParmType - << Param->getSourceRange(); + // Don't diagnose an implicit 'operator=='; we will have diagnosed the + // corresponding defaulted 'operator<=>' already. + if (!FD->isImplicit()) { + Diag(FD->getLocation(), diag::err_defaulted_comparison_param) + << (int)DCK << Param->getType() << ExpectedParmType + << Param->getSourceRange(); + } return true; } } @@ -7918,8 +7922,12 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, SourceLocation InsertLoc; if (FunctionTypeLoc Loc = MD->getFunctionTypeLoc()) InsertLoc = getLocForEndOfToken(Loc.getRParenLoc()); - Diag(MD->getLocation(), diag::err_defaulted_comparison_non_const) - << (int)DCK << FixItHint::CreateInsertion(InsertLoc, " const"); + // Don't diagnose an implicit 'operator=='; we will have diagnosed the + // corresponding defaulted 'operator<=>' already. + if (!MD->isImplicit()) { + Diag(MD->getLocation(), diag::err_defaulted_comparison_non_const) + << (int)DCK << FixItHint::CreateInsertion(InsertLoc, " const"); + } // Add the 'const' to the type to recover. const auto *FPT = MD->getType()->castAs<FunctionProtoType>(); @@ -7980,7 +7988,7 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, // This is really just a consequence of the general rule that you can // only delete a function on its first declaration. Diag(FD->getLocation(), diag::err_non_first_default_compare_deletes) - << (int)DCK; + << FD->isImplicit() << (int)DCK; DefaultedComparisonAnalyzer(*this, RD, FD, DCK, DefaultedComparisonAnalyzer::ExplainDeleted) .visit(); @@ -7988,7 +7996,7 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, } SetDeclDeleted(FD, FD->getLocation()); - if (!inTemplateInstantiation()) { + if (!inTemplateInstantiation() && !FD->isImplicit()) { Diag(FD->getLocation(), diag::warn_defaulted_comparison_deleted) << (int)DCK; DefaultedComparisonAnalyzer(*this, RD, FD, DCK, @@ -8028,7 +8036,7 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, !Info.Constexpr) { Diag(FD->getBeginLoc(), diag::err_incorrect_defaulted_comparison_constexpr) - << (int)DCK << FD->isConsteval(); + << FD->isImplicit() << (int)DCK << FD->isConsteval(); DefaultedComparisonAnalyzer(*this, RD, FD, DCK, DefaultedComparisonAnalyzer::ExplainConstexpr) .visit(); @@ -8051,6 +8059,20 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, return false; } +void Sema::DeclareImplicitEqualityComparison(CXXRecordDecl *RD, + FunctionDecl *Spaceship) { + Sema::CodeSynthesisContext Ctx; + Ctx.Kind = Sema::CodeSynthesisContext::DeclaringImplicitEqualityComparison; + Ctx.PointOfInstantiation = Spaceship->getEndLoc(); + Ctx.Entity = Spaceship; + pushCodeSynthesisContext(Ctx); + + if (FunctionDecl *EqualEqual = SubstSpaceshipAsEqualEqual(RD, Spaceship)) + EqualEqual->setImplicit(); + + popCodeSynthesisContext(); +} + void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD, DefaultedComparisonKind DCK) { assert(FD->isDefaulted() && !FD->isDeleted() && @@ -9330,6 +9352,44 @@ void Sema::ActOnFinishCXXMemberSpecification( CheckCompletedCXXClass(S, cast<CXXRecordDecl>(TagDecl)); } +/// Find the equality comparison functions that should be implicitly declared +/// in a given class definition, per C++2a [class.compare.default]p3. +static void findImplicitlyDeclaredEqualityComparisons( + ASTContext &Ctx, CXXRecordDecl *RD, + llvm::SmallVectorImpl<FunctionDecl *> &Spaceships) { + DeclarationName EqEq = Ctx.DeclarationNames.getCXXOperatorName(OO_EqualEqual); + if (!RD->lookup(EqEq).empty()) + // Member operator== explicitly declared: no implicit operator==s. + return; + + // Traverse friends looking for an '==' or a '<=>'. + for (FriendDecl *Friend : RD->friends()) { + FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Friend->getFriendDecl()); + if (!FD) continue; + + if (FD->getOverloadedOperator() == OO_EqualEqual) { + // Friend operator== explicitly declared: no implicit operator==s. + Spaceships.clear(); + return; + } + + if (FD->getOverloadedOperator() == OO_Spaceship && + FD->isExplicitlyDefaulted()) + Spaceships.push_back(FD); + } + + // Look for members named 'operator<=>'. + DeclarationName Cmp = Ctx.DeclarationNames.getCXXOperatorName(OO_Spaceship); + for (NamedDecl *ND : RD->lookup(Cmp)) { + // Note that we could find a non-function here (either a function template + // or a using-declaration). Neither case results in an implicit + // 'operator=='. + if (auto *FD = dyn_cast<FunctionDecl>(ND)) + if (FD->isExplicitlyDefaulted()) + Spaceships.push_back(FD); + } +} + /// AddImplicitlyDeclaredMembersToClass - Adds any implicitly-declared /// special functions, such as the default constructor, copy /// constructor, or destructor, to the given C++ class (C++ @@ -9407,6 +9467,20 @@ void Sema::AddImplicitlyDeclaredMembersToClass(CXXRecordDecl *ClassDecl) { ClassDecl->needsOverloadResolutionForDestructor()) DeclareImplicitDestructor(ClassDecl); } + + // C++2a [class.compare.default]p3: + // If the member-specification does not explicitly declare any member or + // friend named operator==, an == operator function is declared implicitly + // for each defaulted three-way comparison operator function defined in the + // member-specification + // FIXME: Consider doing this lazily. + if (getLangOpts().CPlusPlus2a) { + llvm::SmallVector<FunctionDecl*, 4> DefaultedSpaceships; + findImplicitlyDeclaredEqualityComparisons(Context, ClassDecl, + DefaultedSpaceships); + for (auto *FD : DefaultedSpaceships) + DeclareImplicitEqualityComparison(ClassDecl, FD); + } } unsigned Sema::ActOnReenterTemplateScope(Scope *S, Decl *D) { diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 344e54b7f3fc..5b1394d7b34f 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -9723,6 +9723,7 @@ enum OverloadCandidateKind { oc_implicit_move_constructor, oc_implicit_copy_assignment, oc_implicit_move_assignment, + oc_implicit_equality_comparison, oc_inherited_constructor }; @@ -9751,6 +9752,9 @@ ClassifyOverloadCandidate(Sema &S, NamedDecl *Found, FunctionDecl *Fn, }(); OverloadCandidateKind Kind = [&]() { + if (Fn->isImplicit() && Fn->getOverloadedOperator() == OO_EqualEqual) + return oc_implicit_equality_comparison; + if (CRK & CRK_Reversed) return oc_reversed_binary_operator; diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 1451fe4bb4d1..6db8eb3e5ed6 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -203,6 +203,7 @@ bool Sema::CodeSynthesisContext::isInstantiationRecord() const { case DefaultTemplateArgumentChecking: case DeclaringSpecialMember: + case DeclaringImplicitEqualityComparison: case DefiningSynthesizedFunction: case ExceptionSpecEvaluation: case ConstraintSubstitution: @@ -671,6 +672,11 @@ void Sema::PrintInstantiationStack() { << cast<CXXRecordDecl>(Active->Entity) << Active->SpecialMember; break; + case CodeSynthesisContext::DeclaringImplicitEqualityComparison: + Diags.Report(Active->Entity->getLocation(), + diag::note_in_declaration_of_implicit_equality_comparison); + break; + case CodeSynthesisContext::DefiningSynthesizedFunction: { // FIXME: For synthesized functions that are not defaulted, // produce a note. @@ -772,6 +778,7 @@ Optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const { return Active->DeductionInfo; case CodeSynthesisContext::DeclaringSpecialMember: + case CodeSynthesisContext::DeclaringImplicitEqualityComparison: case CodeSynthesisContext::DefiningSynthesizedFunction: case CodeSynthesisContext::RewritingOperatorAsSpaceship: // This happens in a context unrelated to template instantiation, so diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 71399ff35908..0bff0747df34 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -1794,8 +1794,9 @@ static QualType adjustFunctionTypeForInstantiation(ASTContext &Context, /// 1) instantiating function templates /// 2) substituting friend declarations /// 3) substituting deduction guide declarations for nested class templates -Decl *TemplateDeclInstantiator::VisitFunctionDecl(FunctionDecl *D, - TemplateParameterList *TemplateParams) { +Decl *TemplateDeclInstantiator::VisitFunctionDecl( + FunctionDecl *D, TemplateParameterList *TemplateParams, + RewriteKind FunctionRewriteKind) { // Check whether there is already a function template specialization for // this declaration. FunctionTemplateDecl *FunctionTemplate = D->getDescribedFunctionTemplate(); @@ -1865,6 +1866,9 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(FunctionDecl *D, DeclarationNameInfo NameInfo = SemaRef.SubstDeclarationNameInfo(D->getNameInfo(), TemplateArgs); + if (FunctionRewriteKind != RewriteKind::None) + adjustForRewrite(FunctionRewriteKind, D, T, TInfo, NameInfo); + FunctionDecl *Function; if (auto *DGuide = dyn_cast<CXXDeductionGuideDecl>(D)) { Function = CXXDeductionGuideDecl::Create( @@ -2101,8 +2105,8 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(FunctionDecl *D, Decl *TemplateDeclInstantiator::VisitCXXMethodDecl( CXXMethodDecl *D, TemplateParameterList *TemplateParams, - Optional<const ASTTemplateArgumentListInfo *> - ClassScopeSpecializationArgs) { + Optional<const ASTTemplateArgumentListInfo *> ClassScopeSpecializationArgs, + RewriteKind FunctionRewriteKind) { FunctionTemplateDecl *FunctionTemplate = D->getDescribedFunctionTemplate(); if (FunctionTemplate && !TemplateParams) { // We are creating a function template specialization from a function @@ -2181,13 +2185,17 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl( if (!DC) return nullptr; } + DeclarationNameInfo NameInfo + = SemaRef.SubstDeclarationNameInfo(D->getNameInfo(), TemplateArgs); + + if (FunctionRewriteKind != RewriteKind::None) + adjustForRewrite(FunctionRewriteKind, D, T, TInfo, NameInfo); + // Build the instantiated method declaration. CXXRecordDecl *Record = cast<CXXRecordDecl>(DC); CXXMethodDecl *Method = nullptr; SourceLocation StartLoc = D->getInnerLocStart(); - DeclarationNameInfo NameInfo - = SemaRef.SubstDeclarationNameInfo(D->getNameInfo(), TemplateArgs); if (CXXConstructorDecl *Constructor = dyn_cast<CXXConstructorDecl>(D)) { Method = CXXConstructorDecl::Create( SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo, @@ -3523,6 +3531,73 @@ Decl *Sema::SubstDecl(Decl *D, DeclContext *Owner, return SubstD; } +void TemplateDeclInstantiator::adjustForRewrite(RewriteKind RK, + FunctionDecl *Orig, QualType &T, + TypeSourceInfo *&TInfo, + DeclarationNameInfo &NameInfo) { + assert(RK == RewriteKind::RewriteSpaceshipAsEqualEqual); + + // C++2a [class.compare.default]p3: + // the return type is replaced with bool + auto *FPT = T->castAs<FunctionProtoType>(); + T = SemaRef.Context.getFunctionType( + SemaRef.Context.BoolTy, FPT->getParamTypes(), FPT->getExtProtoInfo()); + + // Update the return type in the source info too. The most straightforward + // way is to create new TypeSourceInfo for the new type. Use the location of + // the '= default' as the location of the new type. + // + // FIXME: Set the correct return type when we initially transform the type, + // rather than delaying it to now. + TypeSourceInfo *NewTInfo = + SemaRef.Context.getTrivialTypeSourceInfo(T, Orig->getEndLoc()); + auto OldLoc = TInfo->getTypeLoc().getAsAdjusted<FunctionProtoTypeLoc>(); + assert(OldLoc && "type of function is not a function type?"); + auto NewLoc = NewTInfo->getTypeLoc().castAs<FunctionProtoTypeLoc>(); + for (unsigned I = 0, N = OldLoc.getNumParams(); I != N; ++I) + NewLoc.setParam(I, OldLoc.getParam(I)); + TInfo = NewTInfo; + + // and the declarator-id is replaced with operator== + NameInfo.setName( + SemaRef.Context.DeclarationNames.getCXXOperatorName(OO_EqualEqual)); +} + +FunctionDecl *Sema::SubstSpaceshipAsEqualEqual(CXXRecordDecl *RD, + FunctionDecl *Spaceship) { + if (Spaceship->isInvalidDecl()) + return nullptr; + + // C++2a [class.compare.default]p3: + // an == operator function is declared implicitly [...] with the same + // access and function-definition and in the same class scope as the + // three-way comparison operator function + MultiLevelTemplateArgumentList NoTemplateArgs; + TemplateDeclInstantiator Instantiator(*this, RD, NoTemplateArgs); + Decl *R; + if (auto *MD = dyn_cast<CXXMethodDecl>(Spaceship)) { + R = Instantiator.VisitCXXMethodDecl( + MD, nullptr, None, + TemplateDeclInstantiator::RewriteKind::RewriteSpaceshipAsEqualEqual); + } else { + assert(Spaceship->getFriendObjectKind() && + "defaulted spaceship is neither a member nor a friend"); + + R = Instantiator.VisitFunctionDecl( + Spaceship, nullptr, + TemplateDeclInstantiator::RewriteKind::RewriteSpaceshipAsEqualEqual); + if (!R) + return nullptr; + + FriendDecl *FD = + FriendDecl::Create(Context, RD, Spaceship->getLocation(), + cast<NamedDecl>(R), Spaceship->getBeginLoc()); + FD->setAccess(AS_public); + RD->addDecl(FD); + } + return cast_or_null<FunctionDecl>(R); +} + /// Instantiates a nested template parameter list in the current /// instantiation context. /// diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p4.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p4.cpp new file mode 100644 index 000000000000..1ab77075277f --- /dev/null +++ b/clang/test/CXX/class/class.compare/class.compare.default/p4.cpp @@ -0,0 +1,146 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +// This test is for [class.compare.default]p3 as modified and renumbered to p4 +// by P2002R0. + +namespace std { + struct strong_ordering { + int n; + constexpr operator int() const { return n; } + static const strong_ordering less, equal, greater; + }; + constexpr strong_ordering strong_ordering::less = {-1}; + constexpr strong_ordering strong_ordering::equal = {0}; + constexpr strong_ordering strong_ordering::greater = {1}; +} + +namespace N { + struct A { + friend constexpr std::strong_ordering operator<=>(const A&, const A&) = default; + }; + + constexpr bool (*test_a_not_found)(const A&, const A&) = &operator==; // expected-error {{undeclared}} + + constexpr bool operator==(const A&, const A&); + constexpr bool (*test_a)(const A&, const A&) = &operator==; + static_assert((*test_a)(A(), A())); +} + +struct B1 { + virtual std::strong_ordering operator<=>(const B1&) const = default; +}; +bool (B1::*test_b)(const B1&) const = &B1::operator==; + +struct C1 : B1 { + // OK, B1::operator== is virtual. + bool operator==(const B1&) const override; +}; + +struct B2 { + std::strong_ordering operator<=>(const B2&) const = default; +}; + +struct C2 : B2 { + bool operator==(const B2&) const override; // expected-error {{only virtual member functions}} +}; + +struct D { + std::strong_ordering operator<=>(const D&) const; + virtual std::strong_ordering operator<=>(const struct E&) const = 0; +}; +struct E : D { + // expected-error@+2 {{only virtual member functions}} + // expected-note@+1 {{while declaring the corresponding implicit 'operator==' for this defaulted 'operator<=>'}} + std::strong_ordering operator<=>(const E&) const override = default; +}; + +struct F { + [[deprecated("oh no")]] std::strong_ordering operator<=>(const F&) const = default; // expected-note 4{{deprecated}} +}; +void use_f(F f) { + void(f <=> f); // expected-warning {{oh no}} + void(f < f); // expected-warning {{oh no}} + void(f == f); // expected-warning {{oh no}} + void(f != f); // expected-warning {{oh no}} +} + +class G { + // expected-note@+2 {{implicitly declared private here}} + // expected-note-re@+1 {{{{^}}declared private here}} + std::strong_ordering operator<=>(const G&) const = default; +public: +}; +void use_g(G g) { + void(g <=> g); // expected-error {{private}} + void(g == g); // expected-error {{private}} +} + +struct H { + bool operator==(const H&) const; // expected-note {{here}} + constexpr std::strong_ordering operator<=>(const H&) const { return std::strong_ordering::equal; } +}; + +struct I { + H h; // expected-note {{used to compare}} + // expected-error@+1 {{defaulted definition of three-way comparison operator cannot be declared constexpr because the corresponding implicit 'operator==' invokes a non-constexpr comparison function}} + constexpr std::strong_ordering operator<=>(const I&) const = default; +}; + +struct J { + std::strong_ordering operator<=>(const J&) const & = default; // expected-note {{candidate function (the implicit 'operator==' for this 'operator<=>)'}} + friend std::strong_ordering operator<=>(const J&, const J&) = default; // expected-note {{candidate function (the implicit 'operator==' for this 'operator<=>)'}} +}; +void use_j(J j) { + void(j == j); // expected-error {{ambiguous}} +} + +namespace DeleteAfterFirstDecl { + bool operator==(const struct Q&, const struct Q&); + struct Q { + struct X { + friend std::strong_ordering operator<=>(const X&, const X&); + } x; // expected-note {{no viable comparison}} + // expected-error@+1 {{defaulting the corresponding implicit 'operator==' for this defaulted 'operator<=>' would delete it after its first declaration}} + friend std::strong_ordering operator<=>(const Q&, const Q&) = default; + }; +} + +// Note, substitution here results in the second parameter of 'operator==' +// referring to the first parameter of 'operator==', not to the first parameter +// of 'operator<=>'. +// FIXME: Find a case where this matters (attribute enable_if?). +struct K { + friend std::strong_ordering operator<=>(const K &k, decltype(k)) = default; +}; +bool test_k = K() == K(); + +namespace NoInjectionIfOperatorEqualsDeclared { + struct A { + void operator==(int); // expected-note 2{{not viable}} + std::strong_ordering operator<=>(const A&) const = default; + }; + bool test_a = A() == A(); // expected-error {{invalid operands}} + + struct B { + friend void operator==(int, struct Q); // expected-note {{not viable}} + std::strong_ordering operator<=>(const B&) const = default; + }; + bool test_b = B() == B(); // expected-error {{invalid operands}} + + struct C { + void operator==(int); // expected-note 2{{not viable}} + friend std::strong_ordering operator<=>(const C&, const C&) = default; + }; + bool test_c = C() == C(); // expected-error {{invalid operands}} + + struct D { + void f() { + void operator==(const D&, int); + } + struct X { + friend void operator==(const D&, int); + }; + friend std::strong_ordering operator<=>(const D&, const D&) = default; + }; + bool test_d = D() == D(); +} diff --git a/clang/test/CodeGenCXX/cxx2a-three-way-comparison.cpp b/clang/test/CodeGenCXX/cxx2a-three-way-comparison.cpp index e3c1535815f0..e6be640a1f7f 100644 --- a/clang/test/CodeGenCXX/cxx2a-three-way-comparison.cpp +++ b/clang/test/CodeGenCXX/cxx2a-three-way-comparison.cpp @@ -1,6 +1,41 @@ -// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple | FileCheck %s --check-prefix=ITANIUM -// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple x86_64-pc-win32 2>&1 | FileCheck %s --check-prefix=MSABI -// RUN: not %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple -DBUILTIN 2>&1 | FileCheck %s --check-prefix=BUILTIN +// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple | FileCheck %s --check-prefixes=CHECK,ITANIUM +// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple x86_64-pc-win32 2>&1 | FileCheck %s --check-prefixes=CHECK,MSABI + +namespace std { + struct strong_ordering { + int n; + constexpr operator int() const { return n; } + static const strong_ordering less, equal, greater; + }; + constexpr strong_ordering strong_ordering::less = {-1}; + constexpr strong_ordering strong_ordering::equal = {0}; + constexpr strong_ordering strong_ordering::greater = {1}; +} + +struct Primary { + virtual void f(); + std::strong_ordering operator<=>(const Primary&) const = default; +}; +struct X { + virtual struct Y &operator=(Y&&); + virtual struct Y &operator=(const Y&); + std::strong_ordering operator<=>(const X&) const = default; +}; +// The vtable for Y should contain the following entries in order: +// - Primary::f +// - Y::operator<=> +// - Y::operator=(const Y&) (implicit) +// - Y::operator=(Y&&) (implicit) +// - Y::operator==(const Y&) const (implicit) +// See: +// https://github.com/itanium-cxx-abi/cxx-abi/issues/83 for assignment operator +// https://github.com/itanium-cxx-abi/cxx-abi/issues/88 for equality comparison +// FIXME: What rule does MSVC use? +struct Y : Primary, X { + virtual std::strong_ordering operator<=>(const Y&) const = default; +}; +Y y; +// ITANIUM: @_ZTV1Y = {{.*}}constant {{.*}} null, {{.*}} @_ZTI1Y {{.*}} @_ZN7Primary1fEv {{.*}} @_ZNK1YssERKS_ {{.*}} @_ZN1YaSERKS_ {{.*}} @_ZN1YaSEOS_ {{.*}} @_ZNK1YeqERKS_ {{.*}} -{{4|8}} {{.*}} @_ZTI1Y {{.*}} @_ZThn8_N1YaSERKS_ struct A { void operator<=>(int); @@ -26,8 +61,11 @@ int f(A a) { return a <=> a; } -#ifdef BUILTIN -void builtin(int a) { - a <=> a; // BUILTIN: cannot compile this scalar expression yet +// CHECK-LABEL: define {{.*}}builtin_cmp +void builtin_cmp(int a) { + // CHECK: icmp slt + // CHECK: select + // CHECK: icmp eq + // CHECK: select + a <=> a; } -#endif _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits