[clang] bc24014 - [c++20] Implement P1185R2 (as modified by P2002R0).

Richard Smith via cfe-commits cfe-commits at lists.llvm.org
Tue Dec 10 17:24:36 PST 2019


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 at +2 {{only virtual member functions}}
+  // expected-note at +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 at +2 {{implicitly declared private here}}
+  // expected-note-re at +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 at +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 at +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


        


More information about the cfe-commits mailing list