[clang] 5253d91 - [c++20] Determine whether a defaulted comparison should be deleted or

Richard Smith via cfe-commits cfe-commits at lists.llvm.org
Fri Dec 6 16:33:04 PST 2019


Author: Richard Smith
Date: 2019-12-06T16:32:48-08:00
New Revision: 5253d9138eb31252594f5e14133df731551839c7

URL: https://github.com/llvm/llvm-project/commit/5253d9138eb31252594f5e14133df731551839c7
DIFF: https://github.com/llvm/llvm-project/commit/5253d9138eb31252594f5e14133df731551839c7.diff

LOG: [c++20] Determine whether a defaulted comparison should be deleted or
constexpr.

Added: 
    clang/test/CXX/class/class.compare/class.compare.default/p3.cpp
    clang/test/CXX/class/class.compare/class.eq/p2.cpp
    clang/test/CXX/class/class.compare/class.rel/p2.cpp
    clang/test/CXX/class/class.compare/class.spaceship/p1.cpp

Modified: 
    clang/include/clang/AST/ComparisonCategories.h
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Sema/Overload.h
    clang/include/clang/Sema/Sema.h
    clang/lib/Sema/SemaDeclCXX.cpp
    clang/lib/Sema/SemaExpr.cpp
    clang/lib/Sema/SemaOverload.cpp
    clang/test/CXX/class/class.compare/class.compare.default/p2.cpp
    clang/test/CXX/class/class.compare/class.eq/p1.cpp
    clang/test/CXX/class/class.compare/class.rel/p1.cpp
    clang/www/cxx_status.html

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/AST/ComparisonCategories.h b/clang/include/clang/AST/ComparisonCategories.h
index 9d591cc81495..3dd1db1f7e9b 100644
--- a/clang/include/clang/AST/ComparisonCategories.h
+++ b/clang/include/clang/AST/ComparisonCategories.h
@@ -221,7 +221,6 @@ class ComparisonCategories {
     return const_cast<ComparisonCategoryInfo *>(This.lookupInfo(Kind));
   }
 
-private:
   const ComparisonCategoryInfo *lookupInfoForType(QualType Ty) const;
 
 private:

diff  --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index e999ba10a003..6cb1a4e0700d 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -113,7 +113,7 @@ def UndefinedVarTemplate : DiagGroup<"undefined-var-template">;
 def UndefinedFuncTemplate : DiagGroup<"undefined-func-template">;
 def MissingNoEscape : DiagGroup<"missing-noescape">;
 
-def DefaultedComparison : DiagGroup<"defaulted-comparison">;
+def DefaultedFunctionDeleted : DiagGroup<"defaulted-function-deleted">;
 def DeleteIncomplete : DiagGroup<"delete-incomplete">;
 def DeleteNonAbstractNonVirtualDtor : DiagGroup<"delete-non-abstract-non-virtual-dtor">;
 def DeleteAbstractNonVirtualDtor : DiagGroup<"delete-abstract-non-virtual-dtor">;

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a2e4cf51232b..b6586bedd5c8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4067,7 +4067,10 @@ def err_ovl_deleted_oper : Error<
   "overload resolution selected deleted operator '%0'">;
 def err_ovl_deleted_special_oper : Error<
   "object of type %0 cannot be %select{constructed|copied|moved|assigned|"
-  "assigned|destroyed}1 because its %sub{select_special_member_kind}1 is implicitly deleted">;
+  "assigned|destroyed}1 because its %sub{select_special_member_kind}1 is "
+  "implicitly deleted">;
+def err_ovl_deleted_comparison : Error<
+  "object of type %0 cannot be compared because its %1 is implicitly deleted">;
 def err_ovl_rewrite_equalequal_not_bool : Error<
   "return type %0 of selected 'operator==' function for rewritten "
   "'%1' comparison is not 'bool'">;
@@ -8152,7 +8155,7 @@ def err_incorrect_defaulted_consteval : Error<
   "cannot be consteval because implicit definition is not constexpr">;
 def warn_defaulted_method_deleted : Warning<
   "explicitly defaulted %sub{select_special_member_kind}0 is implicitly "
-  "deleted">, InGroup<DiagGroup<"defaulted-function-deleted">>;
+  "deleted">, InGroup<DefaultedFunctionDeleted>;
 def err_out_of_line_default_deletes : Error<
   "defaulting this %sub{select_special_member_kind}0 "
   "would delete it after its first declaration">;
@@ -8194,21 +8197,42 @@ def err_defaulted_comparison_non_const : Error<
 def err_defaulted_comparison_return_type_not_bool : Error<
   "return type for defaulted %sub{select_defaulted_comparison_kind}0 "
   "must be 'bool', not %1">;
-def err_defaulted_comparison_reference_member : Error<
-  "cannot default %0 in class %1 with reference member">;
-def ext_defaulted_comparison_reference_member : ExtWarn<
-  "ISO C++2a does not allow defaulting %0 in class %1 with reference member">,
-  InGroup<DefaultedComparison>;
-def note_reference_member : Note<"reference member %0 declared here">;
-def err_defaulted_comparison_union : Error<
-  "cannot default %0 in %select{union-like class|union}1 %2">;
-def ext_defaulted_comparison_union : ExtWarn<
-  "ISO C++2a does not allow defaulting %0 in "
-  "%select{union-like class|union}1 %2">, InGroup<DefaultedComparison>;
-def ext_defaulted_comparison_empty_union : ExtWarn<
-  "ISO C++2a does not allow defaulting %0 in "
-  "%select{union-like class|union}1 %2 despite it having no variant members">,
-  InGroup<DefaultedComparison>;
+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 "
+  "would delete it after its first declaration">;
+def note_defaulted_comparison_union : Note<
+  "defaulted %0 is implicitly deleted because "
+  "%2 is a %select{union-like class|union}1 with variant members">;
+def note_defaulted_comparison_reference_member : Note<
+  "defaulted %0 is implicitly deleted because "
+  "class %1 has a reference member">;
+def note_defaulted_comparison_ambiguous : Note<
+  "defaulted %0 is implicitly deleted because implied %select{|'==' |'<' }1"
+  "comparison %select{|for member %3 |for base class %3 }2is ambiguous">;
+def note_defaulted_comparison_calls_deleted : Note<
+  "defaulted %0 is implicitly deleted because it would invoke a deleted "
+  "comparison function%select{| for member %2| for base class %2}1">;
+def note_defaulted_comparison_no_viable_function : Note<
+  "defaulted %0 is implicitly deleted because there is no viable comparison "
+  "function%select{| for member %2| for base class %2}1">;
+def note_defaulted_comparison_no_viable_function_synthesized : Note<
+  "three-way comparison cannot be synthesized because there is no viable "
+  "function for %select{'=='|'<'}0 comparison">;
+def note_defaulted_comparison_not_rewritten_callee : Note<
+  "defaulted %0 is implicitly deleted because this non-rewritten comparison "
+  "function would be the best match for the comparison">;
+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">;
+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 ext_implicit_exception_spec_mismatch : ExtWarn<
   "function previously declared with an %select{explicit|implicit}0 exception "

diff  --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h
index c6012d754db0..e0c3ba13ef54 100644
--- a/clang/include/clang/Sema/Overload.h
+++ b/clang/include/clang/Sema/Overload.h
@@ -935,7 +935,17 @@ class Sema;
       }
 
       bool isAcceptableCandidate(const FunctionDecl *FD) {
-        return AllowRewrittenCandidates || !isRewrittenOperator(FD);
+        if (!OriginalOperator)
+          return true;
+
+        // For an overloaded operator, we can have candidates with a 
diff erent
+        // name in our unqualified lookup set. Make sure we only consider the
+        // ones we're supposed to.
+        OverloadedOperatorKind OO =
+            FD->getDeclName().getCXXOverloadedOperator();
+        return OO && (OO == OriginalOperator ||
+                      (AllowRewrittenCandidates &&
+                       OO == getRewrittenOverloadedOperator(OriginalOperator)));
       }
 
       /// Determine the kind of rewrite that should be performed for this
@@ -1028,6 +1038,12 @@ class Sema;
       return Functions.insert(Key).second;
     }
 
+    /// Exclude a function from being considered by overload resolution.
+    void exclude(Decl *F) {
+      isNewCandidate(F, OverloadCandidateParamOrder::Normal);
+      isNewCandidate(F, OverloadCandidateParamOrder::Reversed);
+    }
+
     /// Clear out all of the candidates.
     void clear(CandidateSetKind CSK);
 

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f117cca0a56b..ed1f1370b332 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3310,6 +3310,10 @@ class Sema final {
                                      const UnresolvedSetImpl &Fns,
                                      Expr *input, bool RequiresADL = true);
 
+  void LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet,
+                             OverloadedOperatorKind Op,
+                             const UnresolvedSetImpl &Fns,
+                             ArrayRef<Expr *> Args, bool RequiresADL = true);
   ExprResult CreateOverloadedBinOp(SourceLocation OpLoc,
                                    BinaryOperatorKind Opc,
                                    const UnresolvedSetImpl &Fns,
@@ -5310,6 +5314,9 @@ class Sema final {
                                  InheritedConstructorInfo *ICI = nullptr,
                                  bool Diagnose = false);
 
+  /// Produce notes explaining why a defaulted function was defined as deleted.
+  void DiagnoseDeletedDefaultedFunction(FunctionDecl *FD);
+
   /// Declare the implicit default constructor for the given class.
   ///
   /// \param ClassDecl The class declaration into which the implicit

diff  --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 1561960c99d2..ba516b66608e 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -1627,9 +1627,8 @@ static bool CheckConstexprDestructorSubobjects(Sema &SemaRef,
   return true;
 }
 
-// CheckConstexprParameterTypes - Check whether a function's parameter types
-// are all literal types. If so, return true. If not, produce a suitable
-// diagnostic and return false.
+/// Check whether a function's parameter types are all literal types. If so,
+/// return true. If not, produce a suitable diagnostic and return false.
 static bool CheckConstexprParameterTypes(Sema &SemaRef,
                                          const FunctionDecl *FD,
                                          Sema::CheckConstexprKind Kind) {
@@ -1649,6 +1648,17 @@ static bool CheckConstexprParameterTypes(Sema &SemaRef,
   return true;
 }
 
+/// Check whether a function's return type is a literal type. If so, return
+/// true. If not, produce a suitable diagnostic and return false.
+static bool CheckConstexprReturnType(Sema &SemaRef, const FunctionDecl *FD,
+                                     Sema::CheckConstexprKind Kind) {
+  if (CheckLiteralType(SemaRef, Kind, FD->getLocation(), FD->getReturnType(),
+                       diag::err_constexpr_non_literal_return,
+                       FD->isConsteval()))
+    return false;
+  return true;
+}
+
 /// Get diagnostic %select index for tag kind for
 /// record diagnostic message.
 /// WARNING: Indexes apply to particular diagnostics only!
@@ -1729,10 +1739,7 @@ bool Sema::CheckConstexprFunctionDefinition(const FunctionDecl *NewFD,
     }
 
     // - its return type shall be a literal type;
-    QualType RT = NewFD->getReturnType();
-    if (CheckLiteralType(*this, Kind, NewFD->getLocation(), RT,
-                         diag::err_constexpr_non_literal_return,
-                         NewFD->isConsteval()))
+    if (!CheckConstexprReturnType(*this, NewFD, Kind))
       return false;
   }
 
@@ -6391,10 +6398,26 @@ void Sema::CheckCompletedCXXClass(CXXRecordDecl *Record) {
   if (HasTrivialABI)
     Record->setHasTrivialSpecialMemberForCall();
 
+  // Explicitly-defaulted secondary comparison functions (!=, <, <=, >, >=).
+  // We check these last because they can depend on the properties of the
+  // primary comparison functions (==, <=>).
+  llvm::SmallVector<FunctionDecl*, 5> DefaultedSecondaryComparisons;
+
+  auto CheckForDefaultedFunction = [&](FunctionDecl *FD) {
+    if (!FD || FD->isInvalidDecl() || !FD->isExplicitlyDefaulted())
+      return;
+
+    DefaultedFunctionKind DFK = getDefaultedFunctionKind(FD);
+    if (DFK.asComparison() == DefaultedComparisonKind::NotEqual ||
+        DFK.asComparison() == DefaultedComparisonKind::Relational)
+      DefaultedSecondaryComparisons.push_back(FD);
+    else
+      CheckExplicitlyDefaultedFunction(FD);
+  };
+
   auto CompleteMemberFunction = [&](CXXMethodDecl *M) {
     // Check whether the explicitly-defaulted members are valid.
-    if (!M->isInvalidDecl() && M->isExplicitlyDefaulted())
-      CheckExplicitlyDefaultedFunction(M);
+    CheckForDefaultedFunction(M);
 
     // For an explicitly defaulted or deleted special member, we defer
     // determining triviality until the class is complete. That time is now!
@@ -6477,12 +6500,15 @@ void Sema::CheckCompletedCXXClass(CXXRecordDecl *Record) {
   // Process any defaulted friends in the member-specification.
   if (!Record->isDependentType()) {
     for (FriendDecl *D : Record->friends()) {
-      auto *FD = dyn_cast_or_null<FunctionDecl>(D->getFriendDecl());
-      if (FD && !FD->isInvalidDecl() && FD->isExplicitlyDefaulted())
-        CheckExplicitlyDefaultedFunction(FD);
+      CheckForDefaultedFunction(
+          dyn_cast_or_null<FunctionDecl>(D->getFriendDecl()));
     }
   }
 
+  // Check the defaulted secondary comparisons after any other member functions.
+  for (FunctionDecl *FD : DefaultedSecondaryComparisons)
+    CheckExplicitlyDefaultedFunction(FD);
+
   // ms_struct is a request to use the same ABI rules as MSVC.  Check
   // whether this class uses any C++ features that are implemented
   // completely 
diff erently in MSVC, and if so, emit a diagnostic.
@@ -7046,6 +7072,343 @@ bool Sema::CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD,
   return HadError;
 }
 
+namespace {
+/// Helper class for building and checking a defaulted comparison.
+///
+/// Defaulted functions are built in two phases:
+///
+///  * First, the set of operations that the function will perform are
+///    identified, and some of them are checked. If any of the checked
+///    operations is invalid in certain ways, the comparison function is
+///    defined as deleted and no body is built.
+///  * Then, if the function is not defined as deleted, the body is built.
+///
+/// This is accomplished by performing two visitation steps over the eventual
+/// body of the function.
+template<typename Derived, typename Result, typename Subobject>
+class DefaultedComparisonVisitor {
+public:
+  using DefaultedComparisonKind = Sema::DefaultedComparisonKind;
+
+  DefaultedComparisonVisitor(Sema &S, CXXRecordDecl *RD, FunctionDecl *FD,
+                             DefaultedComparisonKind DCK)
+      : S(S), RD(RD), FD(FD), DCK(DCK) {}
+
+  Result visit() {
+    // The type of an lvalue naming a parameter of this function.
+    QualType ParamLvalType =
+        FD->getParamDecl(0)->getType().getNonReferenceType();
+
+    switch (DCK) {
+    case DefaultedComparisonKind::None:
+      llvm_unreachable("not a defaulted comparison");
+
+    case DefaultedComparisonKind::Equal:
+    case DefaultedComparisonKind::ThreeWay:
+      return getDerived().visitSubobjects(RD, ParamLvalType.getQualifiers());
+
+    case DefaultedComparisonKind::NotEqual:
+    case DefaultedComparisonKind::Relational:
+      return getDerived().visitExpandedSubobject(
+          ParamLvalType, getDerived().getCompleteObject());
+    }
+  }
+
+protected:
+  Derived &getDerived() { return static_cast<Derived&>(*this); }
+
+  Result visitSubobjects(CXXRecordDecl *Record, Qualifiers Quals) {
+    Result R;
+    // C++ [class.compare.default]p5:
+    //   The direct base class subobjects of C [...]
+    for (CXXBaseSpecifier &Base : Record->bases())
+      if (R.add(getDerived().visitSubobject(
+              S.Context.getQualifiedType(Base.getType(), Quals),
+              getDerived().getBase(&Base))))
+        return R;
+    //   followed by the non-static data members of C [...]
+    for (FieldDecl *Field : Record->fields()) {
+      // Recursively expand anonymous structs.
+      if (Field->isAnonymousStructOrUnion()) {
+        if (R.add(
+                visitSubobjects(Field->getType()->getAsCXXRecordDecl(), Quals)))
+          return R;
+        continue;
+      }
+
+      // Figure out the type of an lvalue denoting this field.
+      Qualifiers FieldQuals = Quals;
+      if (Field->isMutable())
+        FieldQuals.removeConst();
+      QualType FieldType =
+          S.Context.getQualifiedType(Field->getType(), FieldQuals);
+
+      if (R.add(getDerived().visitSubobject(FieldType,
+                                            getDerived().getField(Field))))
+        return R;
+    }
+    //   form a list of subobjects.
+    return R;
+  }
+
+  Result visitSubobject(QualType Type, Subobject Subobj) {
+    //   In that list, any subobject of array type is recursively expanded
+    const ArrayType *AT = S.Context.getAsArrayType(Type);
+    if (auto *CAT = dyn_cast_or_null<ConstantArrayType>(AT))
+      return getDerived().visitSubobjectArray(CAT->getElementType(),
+                                              CAT->getSize(), Subobj);
+    return getDerived().visitExpandedSubobject(Type, Subobj);
+  }
+
+  Result visitSubobjectArray(QualType Type, const llvm::APInt &Size,
+                             Subobject Subobj) {
+    return getDerived().visitSubobject(Type, Subobj);
+  }
+
+protected:
+  Sema &S;
+  CXXRecordDecl *RD;
+  FunctionDecl *FD;
+  DefaultedComparisonKind DCK;
+};
+
+/// Information about a defaulted comparison, as determined by
+/// DefaultedComparisonAnalyzer.
+struct DefaultedComparisonInfo {
+  bool Deleted = false;
+  bool Constexpr = true;
+
+  static DefaultedComparisonInfo deleted() { return {true, false}; }
+
+  bool add(const DefaultedComparisonInfo &R) {
+    Deleted |= R.Deleted;
+    Constexpr &= R.Constexpr;
+    return Deleted;
+  }
+};
+
+/// An element in the expanded list of subobjects of a defaulted comparison, as
+/// specified in C++2a [class.compare.default]p4.
+struct DefaultedComparisonSubobject {
+  enum { CompleteObject, Member, Base } Kind;
+  NamedDecl *Decl;
+  SourceLocation Loc;
+};
+
+/// A visitor over the notional body of a defaulted comparison that determines
+/// whether that body would be deleted or constexpr.
+class DefaultedComparisonAnalyzer
+    : public DefaultedComparisonVisitor<DefaultedComparisonAnalyzer,
+                                        DefaultedComparisonInfo,
+                                        DefaultedComparisonSubobject> {
+public:
+  enum DiagnosticKind { NoDiagnostics, ExplainDeleted, ExplainConstexpr };
+
+private:
+  DiagnosticKind Diagnose;
+
+public:
+  using Base = DefaultedComparisonVisitor;
+  using Result = DefaultedComparisonInfo;
+  using Subobject = DefaultedComparisonSubobject;
+
+  friend Base;
+
+  DefaultedComparisonAnalyzer(Sema &S, CXXRecordDecl *RD, FunctionDecl *FD,
+                              DefaultedComparisonKind DCK,
+                              DiagnosticKind Diagnose = NoDiagnostics)
+      : Base(S, RD, FD, DCK), Diagnose(Diagnose) {}
+
+  Result visit() {
+    if ((DCK == DefaultedComparisonKind::Equal ||
+         DCK == DefaultedComparisonKind::ThreeWay) &&
+        RD->hasVariantMembers()) {
+      // C++2a [class.compare.default]p2 [P2002R0]:
+      //   A defaulted comparison operator function for class C is defined as
+      //   deleted if [...] C has variant members.
+      if (Diagnose == ExplainDeleted) {
+        S.Diag(FD->getLocation(), diag::note_defaulted_comparison_union)
+          << FD << RD->isUnion() << RD;
+      }
+      return Result::deleted();
+    }
+
+    return Base::visit();
+  }
+
+private:
+  Subobject getCompleteObject() {
+    return Subobject{Subobject::CompleteObject, nullptr, FD->getLocation()};
+  }
+
+  Subobject getBase(CXXBaseSpecifier *Base) {
+    return Subobject{Subobject::Base, Base->getType()->getAsCXXRecordDecl(),
+                     Base->getBaseTypeLoc()};
+  }
+
+  Subobject getField(FieldDecl *Field) {
+    return Subobject{Subobject::Member, Field, Field->getLocation()};
+  }
+
+  Result visitExpandedSubobject(QualType Type, Subobject Subobj) {
+    // C++2a [class.compare.default]p2 [P2002R0]:
+    //   A defaulted <=> or == operator function for class C is defined as
+    //   deleted if any non-static data member of C is of reference type
+    if (Type->isReferenceType()) {
+      if (Diagnose == ExplainDeleted) {
+        S.Diag(Subobj.Loc, diag::note_defaulted_comparison_reference_member)
+            << FD << RD;
+      }
+      return Result::deleted();
+    }
+
+    // [...] Let xi be an lvalue denoting the ith element [...]
+    OpaqueValueExpr Xi(FD->getLocation(), Type, VK_LValue);
+    Expr *Args[] = {&Xi, &Xi};
+
+    // All operators start by trying to apply that same operator recursively.
+    OverloadedOperatorKind OO = FD->getOverloadedOperator();
+    assert(OO != OO_None && "not an overloaded operator!");
+    return visitBinaryOperator(OO, Args, Subobj);
+  }
+
+  Result
+  visitBinaryOperator(OverloadedOperatorKind OO, ArrayRef<Expr *> Args,
+                      Subobject Subobj,
+                      OverloadCandidateSet *SpaceshipCandidates = nullptr) {
+    UnresolvedSet<4> Fns; // FIXME: Track this.
+
+    // Note that there is no need to consider rewritten candidates here if
+    // we've already found there is no viable 'operator<=>' candidate (and are
+    // considering synthesizing a '<=>' from '==' and '<').
+    OverloadCandidateSet CandidateSet(
+        FD->getLocation(), OverloadCandidateSet::CSK_Operator,
+        OverloadCandidateSet::OperatorRewriteInfo(
+            OO, /*AllowRewrittenCandidates=*/!SpaceshipCandidates));
+
+    /// C++2a [class.compare.default]p1 [P2002R0]:
+    ///   [...] the defaulted function itself is never a candidate for overload
+    ///   resolution [...]
+    CandidateSet.exclude(FD);
+
+    S.LookupOverloadedBinOp(CandidateSet, OO, Fns, Args);
+
+    Result R;
+
+    OverloadCandidateSet::iterator Best;
+    switch (CandidateSet.BestViableFunction(S, FD->getLocation(), Best)) {
+    case OR_Success:
+      // C++2a [class.compare.secondary]p2 [P2002R0]:
+      //   The operator function [...] is defined as deleted if [...] the
+      //   candidate selected by overload resolution is not a rewritten
+      //   candidate.
+      if ((DCK == DefaultedComparisonKind::NotEqual ||
+           DCK == DefaultedComparisonKind::Relational) &&
+          !Best->RewriteKind) {
+        S.Diag(Best->Function->getLocation(),
+               diag::note_defaulted_comparison_not_rewritten_callee)
+            << FD;
+        return Result::deleted();
+      }
+
+      // C++2a [class.compare.default]p3 [P2002R0]:
+      //   A defaulted comparison function is constexpr-compatible if [...]
+      //   no overlod resolution performed [...] results in a non-constexpr
+      //   function.
+      if (FunctionDecl *FD = Best->Function) {
+        assert(!FD->isDeleted() && "wrong overload resolution result");
+        // If it's not constexpr, explain why not.
+        if (Diagnose == ExplainConstexpr && !FD->isConstexpr()) {
+          if (Subobj.Kind != Subobject::CompleteObject)
+            S.Diag(Subobj.Loc, diag::note_defaulted_comparison_not_constexpr)
+              << Subobj.Kind << Subobj.Decl;
+          S.Diag(FD->getLocation(),
+                 diag::note_defaulted_comparison_not_constexpr_here);
+          // Bail out after explaining; we don't want any more notes.
+          return Result::deleted();
+        }
+        R.Constexpr &= FD->isConstexpr();
+      }
+
+      // Note that we might be rewriting to a 
diff erent operator. That call is
+      // not considered until we come to actually build the comparison function.
+      break;
+
+    case OR_Ambiguous:
+      if (Diagnose == ExplainDeleted) {
+        unsigned Kind = 0;
+        if (FD->getOverloadedOperator() == OO_Spaceship && OO != OO_Spaceship)
+          Kind = OO == OO_EqualEqual ? 1 : 2;
+        CandidateSet.NoteCandidates(
+            PartialDiagnosticAt(
+                Subobj.Loc, S.PDiag(diag::note_defaulted_comparison_ambiguous)
+                                << FD << Kind << Subobj.Kind << Subobj.Decl),
+            S, OCD_AmbiguousCandidates, Args);
+      }
+      R = Result::deleted();
+      break;
+
+    case OR_Deleted:
+      if (Diagnose == ExplainDeleted) {
+        if ((DCK == DefaultedComparisonKind::NotEqual ||
+             DCK == DefaultedComparisonKind::Relational) &&
+            !Best->RewriteKind) {
+          S.Diag(Best->Function->getLocation(),
+                 diag::note_defaulted_comparison_not_rewritten_callee)
+              << FD;
+        } else {
+          S.Diag(Subobj.Loc,
+                 diag::note_defaulted_comparison_calls_deleted)
+              << FD << Subobj.Kind << Subobj.Decl;
+          S.NoteDeletedFunction(Best->Function);
+        }
+      }
+      R = Result::deleted();
+      break;
+
+    case OR_No_Viable_Function:
+      // If there's no usable candidate, we're done unless we can rewrite a
+      // '<=>' in terms of '==' and '<'.
+      if (OO == OO_Spaceship &&
+          S.Context.CompCategories.lookupInfoForType(FD->getReturnType())) {
+        // For any kind of comparison category return type, we need a usable
+        // '==' and a usable '<'.
+        if (!R.add(visitBinaryOperator(OO_EqualEqual, Args, Subobj,
+                                       &CandidateSet)))
+          R.add(visitBinaryOperator(OO_Less, Args, Subobj, &CandidateSet));
+        break;
+      }
+
+      if (Diagnose == ExplainDeleted) {
+        S.Diag(Subobj.Loc, diag::note_defaulted_comparison_no_viable_function)
+            << FD << Subobj.Kind << Subobj.Decl;
+
+        // For a three-way comparison, list both the candidates for the
+        // original operator and the candidates for the synthesized operator.
+        if (SpaceshipCandidates) {
+          SpaceshipCandidates->NoteCandidates(
+              S, Args,
+              SpaceshipCandidates->CompleteCandidates(S, OCD_AllCandidates,
+                                                      Args, FD->getLocation()));
+          S.Diag(Subobj.Loc,
+                 diag::note_defaulted_comparison_no_viable_function_synthesized)
+              << (OO == OO_EqualEqual ? 0 : 1);
+        }
+
+        CandidateSet.NoteCandidates(
+            S, Args,
+            CandidateSet.CompleteCandidates(S, OCD_AllCandidates, Args,
+                                            FD->getLocation()));
+      }
+      R = Result::deleted();
+      break;
+    }
+
+    return R;
+  }
+};
+}
+
 bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
                                               DefaultedComparisonKind DCK) {
   assert(DCK != DefaultedComparisonKind::None && "not a defaulted comparison");
@@ -7092,45 +7455,6 @@ bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
     assert(FD->getFriendObjectKind() && "expected a friend declaration");
   }
 
-  // C++2a [class.compare.default]p2:
-  //   A defaulted comparison operator function for class C is defined as
-  //   deleted if any non-static data member of C is of reference type or C is
-  //   a union-like class.
-  llvm::SmallVector<CXXRecordDecl*, 4> Classes(1, RD);
-  FieldDecl *ReferenceMember = nullptr;
-  bool UnionLike = RD->isUnion();
-  while (!Classes.empty()) {
-    if (Classes.back()->isUnion())
-      UnionLike = true;
-    for (FieldDecl *FD : Classes.pop_back_val()->fields()) {
-      if (FD->getType()->isReferenceType())
-        ReferenceMember = FD;
-      if (FD->isAnonymousStructOrUnion())
-        Classes.push_back(FD->getType()->getAsCXXRecordDecl());
-    }
-  }
-  // For non-memberwise comparisons, this rule is unjustified, so we permit
-  // those cases as an extension.
-  bool Memberwise = DCK == DefaultedComparisonKind::Equal ||
-                    DCK == DefaultedComparisonKind::ThreeWay;
-  if (ReferenceMember) {
-    Diag(FD->getLocation(),
-         Memberwise ? diag::err_defaulted_comparison_reference_member
-                    : diag::ext_defaulted_comparison_reference_member)
-        << FD << RD;
-    Diag(ReferenceMember->getLocation(), diag::note_reference_member)
-        << ReferenceMember;
-  } else if (UnionLike) {
-    // If the class actually has no variant members, this rule similarly
-    // is unjustified, so we permit those cases too.
-    Diag(FD->getLocation(),
-         !Memberwise ? diag::ext_defaulted_comparison_union
-                     : !RD->hasVariantMembers()
-                           ? diag::ext_defaulted_comparison_empty_union
-                           : diag::err_defaulted_comparison_union)
-        << FD << RD->isUnion() << RD;
-  }
-
   // C++2a [class.eq]p1, [class.rel]p1:
   //   A [defaulted comparison other than <=>] shall have a declared return
   //   type bool.
@@ -7142,20 +7466,77 @@ bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
     return true;
   }
 
-  // FIXME: Determine whether the function should be defined as deleted.
-
-  // C++2a [dcl.fct.def.default]p3:
-  //   An explicitly-defaulted function [..] may be declared constexpr or
-  //   consteval only if it would have been implicitly declared constexpr.
-  // FIXME: There are no rules governing when these should be constexpr,
-  // except for the special case of the injected operator==, for which
-  // C++2a [class.compare.default]p3 says:
-  //   The operator is a constexpr function if its definition would satisfy
-  //   the requirements for a constexpr function.
-  // FIXME: Apply this rule to all defaulted comparisons. The only way this
-  // can fail is if the return type of a defaulted operator<=> is not a literal
-  // type. We should additionally consider whether any of the operations
-  // performed by the comparison invokes a non-constexpr function.
+  // Determine whether the function should be defined as deleted.
+  DefaultedComparisonInfo Info =
+      DefaultedComparisonAnalyzer(*this, RD, FD, DCK).visit();
+
+  bool First = FD == FD->getCanonicalDecl();
+
+  // If we want to delete the function, then do so; there's nothing else to
+  // check in that case.
+  if (Info.Deleted) {
+    if (!First) {
+      // C++11 [dcl.fct.def.default]p4:
+      //   [For a] user-provided explicitly-defaulted function [...] if such a
+      //   function is implicitly defined as deleted, the program is ill-formed.
+      //
+      // 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;
+      DefaultedComparisonAnalyzer(*this, RD, FD, DCK,
+                                  DefaultedComparisonAnalyzer::ExplainDeleted)
+          .visit();
+      return true;
+    }
+
+    SetDeclDeleted(FD, FD->getLocation());
+    if (!inTemplateInstantiation()) {
+      Diag(FD->getLocation(), diag::warn_defaulted_comparison_deleted)
+          << (int)DCK;
+      DefaultedComparisonAnalyzer(*this, RD, FD, DCK,
+                                  DefaultedComparisonAnalyzer::ExplainDeleted)
+          .visit();
+    }
+    return false;
+  }
+
+  // FIXME: Deduce the return type now.
+
+  // C++2a [dcl.fct.def.default]p3 [P2002R0]:
+  //   An explicitly-defaulted function that is not defined as deleted may be
+  //   declared constexpr or consteval only if it is constexpr-compatible.
+  // C++2a [class.compare.default]p3 [P2002R0]:
+  //   A defaulted comparison function is constexpr-compatible if it satisfies
+  //   the requirements for a constexpr function [...]
+  // The only relevant requirements are that the parameter and return types are
+  // literal types. The remaining conditions are checked by the analyzer.
+  if (FD->isConstexpr()) {
+    if (CheckConstexprReturnType(*this, FD, CheckConstexprKind::Diagnose) &&
+        CheckConstexprParameterTypes(*this, FD, CheckConstexprKind::Diagnose) &&
+        !Info.Constexpr) {
+      Diag(FD->getBeginLoc(),
+           diag::err_incorrect_defaulted_comparison_constexpr)
+          << (int)DCK << FD->isConsteval();
+      DefaultedComparisonAnalyzer(*this, RD, FD, DCK,
+                                  DefaultedComparisonAnalyzer::ExplainConstexpr)
+          .visit();
+    }
+  }
+
+  // C++2a [dcl.fct.def.default]p3 [P2002R0]:
+  //   If a constexpr-compatible function is explicitly defaulted on its first
+  //   declaration, it is implicitly considered to be constexpr.
+  // FIXME: Only applying this to the first declaration seems problematic, as
+  // simple reorderings can affect the meaning of the program.
+  if (First) {
+    if (!FD->isConstexpr() && Info.Constexpr)
+      FD->setConstexprKind(CSK_constexpr);
+
+    // FIXME: Set up an implicit exception specification, or if given an
+    // explicit one, check that it matches.
+  }
+
   return false;
 }
 
@@ -7763,6 +8144,22 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, CXXSpecialMember CSM,
   return false;
 }
 
+void Sema::DiagnoseDeletedDefaultedFunction(FunctionDecl *FD) {
+  DefaultedFunctionKind DFK = getDefaultedFunctionKind(FD);
+  assert(DFK && "not a defaultable function");
+  assert(FD->isDefaulted() && FD->isDeleted() && "not defaulted and deleted");
+
+  if (DFK.isSpecialMember()) {
+    ShouldDeleteSpecialMember(cast<CXXMethodDecl>(FD), DFK.asSpecialMember(),
+                              nullptr, /*Diagnose=*/true);
+  } else {
+    DefaultedComparisonAnalyzer(
+        *this, cast<CXXRecordDecl>(FD->getLexicalDeclContext()), FD,
+        DFK.asComparison(), DefaultedComparisonAnalyzer::ExplainDeleted)
+        .visit();
+  }
+}
+
 /// Perform lookup for a special member of the specified kind, and determine
 /// whether it is trivial. If the triviality can be determined without the
 /// lookup, skip it. This is intended for use when determining whether a
@@ -15177,6 +15574,16 @@ void Sema::SetDeclDeleted(Decl *Dcl, SourceLocation DelLoc) {
   if (Fn->isDeleted())
     return;
 
+  // C++11 [basic.start.main]p3:
+  //   A program that defines main as deleted [...] is ill-formed.
+  if (Fn->isMain())
+    Diag(DelLoc, diag::err_deleted_main);
+
+  // C++11 [dcl.fct.def.delete]p4:
+  //  A deleted function is implicitly inline.
+  Fn->setImplicitlyInline();
+  Fn->setDeletedAsWritten();
+
   // See if we're deleting a function which is already known to override a
   // non-deleted virtual function.
   if (CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(Fn)) {
@@ -15193,19 +15600,8 @@ void Sema::SetDeclDeleted(Decl *Dcl, SourceLocation DelLoc) {
     // If this function was implicitly deleted because it was defaulted,
     // explain why it was deleted.
     if (IssuedDiagnostic && MD->isDefaulted())
-      ShouldDeleteSpecialMember(MD, getSpecialMember(MD), nullptr,
-                                /*Diagnose*/true);
+      DiagnoseDeletedDefaultedFunction(MD);
   }
-
-  // C++11 [basic.start.main]p3:
-  //   A program that defines main as deleted [...] is ill-formed.
-  if (Fn->isMain())
-    Diag(DelLoc, diag::err_deleted_main);
-
-  // C++11 [dcl.fct.def.delete]p4:
-  //  A deleted function is implicitly inline.
-  Fn->setImplicitlyInline();
-  Fn->setDeletedAsWritten();
 }
 
 void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) {

diff  --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index e2c37f8f5238..5eeeba3c2d12 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -98,21 +98,16 @@ static void DiagnoseUnusedOfDecl(Sema &S, NamedDecl *D, SourceLocation Loc) {
 
 /// Emit a note explaining that this function is deleted.
 void Sema::NoteDeletedFunction(FunctionDecl *Decl) {
-  assert(Decl->isDeleted());
+  assert(Decl && Decl->isDeleted());
 
-  CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(Decl);
-
-  if (Method && Method->isDeleted() && Method->isDefaulted()) {
+  if (Decl->isDefaulted()) {
     // If the method was explicitly defaulted, point at that declaration.
-    if (!Method->isImplicit())
+    if (!Decl->isImplicit())
       Diag(Decl->getLocation(), diag::note_implicitly_deleted);
 
     // Try to diagnose why this special member function was implicitly
     // deleted. This might fail, if that reason no longer applies.
-    CXXSpecialMember CSM = getSpecialMember(Method);
-    if (CSM != CXXInvalid)
-      ShouldDeleteSpecialMember(Method, CSM, nullptr, /*Diagnose=*/true);
-
+    DiagnoseDeletedDefaultedFunction(Decl);
     return;
   }
 

diff  --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 6f8ad637d9ba..27e1101b482d 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -11035,6 +11035,7 @@ CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand,
   unsigned ConvIdx = 0;
   unsigned ArgIdx = 0;
   ArrayRef<QualType> ParamTypes;
+  bool Reversed = Cand->RewriteKind & CRK_Reversed;
 
   if (Cand->IsSurrogate) {
     QualType ConvType
@@ -11048,7 +11049,7 @@ CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand,
     ParamTypes =
         Cand->Function->getType()->castAs<FunctionProtoType>()->getParamTypes();
     if (isa<CXXMethodDecl>(Cand->Function) &&
-        !isa<CXXConstructorDecl>(Cand->Function)) {
+        !isa<CXXConstructorDecl>(Cand->Function) && !Reversed) {
       // Conversion 0 is 'this', which doesn't have a corresponding parameter.
       ConvIdx = 1;
       if (CSK == OverloadCandidateSet::CSK_Operator &&
@@ -11063,7 +11064,6 @@ CompleteNonViableCandidate(Sema &S, OverloadCandidate *Cand,
   }
 
   // Fill in the rest of the conversions.
-  bool Reversed = Cand->RewriteKind & CRK_Reversed;
   for (unsigned ParamIdx = Reversed ? ParamTypes.size() - 1 : 0;
        ConvIdx != ConvCount;
        ++ConvIdx, ++ArgIdx, ParamIdx += (Reversed ? -1 : 1)) {
@@ -12755,6 +12755,70 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
   return CreateBuiltinUnaryOp(OpLoc, Opc, Input);
 }
 
+/// Perform lookup for an overloaded binary operator.
+void Sema::LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet,
+                                 OverloadedOperatorKind Op,
+                                 const UnresolvedSetImpl &Fns,
+                                 ArrayRef<Expr *> Args, bool PerformADL) {
+  SourceLocation OpLoc = CandidateSet.getLocation();
+
+  OverloadedOperatorKind ExtraOp =
+      CandidateSet.getRewriteInfo().AllowRewrittenCandidates
+          ? getRewrittenOverloadedOperator(Op)
+          : OO_None;
+
+  // Add the candidates from the given function set. This also adds the
+  // rewritten candidates using these functions if necessary.
+  AddNonMemberOperatorCandidates(Fns, Args, CandidateSet);
+
+  // Add operator candidates that are member functions.
+  AddMemberOperatorCandidates(Op, OpLoc, Args, CandidateSet);
+  if (CandidateSet.getRewriteInfo().shouldAddReversed(Op))
+    AddMemberOperatorCandidates(Op, OpLoc, {Args[1], Args[0]}, CandidateSet,
+                                OverloadCandidateParamOrder::Reversed);
+
+  // In C++20, also add any rewritten member candidates.
+  if (ExtraOp) {
+    AddMemberOperatorCandidates(ExtraOp, OpLoc, Args, CandidateSet);
+    if (CandidateSet.getRewriteInfo().shouldAddReversed(ExtraOp))
+      AddMemberOperatorCandidates(ExtraOp, OpLoc, {Args[1], Args[0]},
+                                  CandidateSet,
+                                  OverloadCandidateParamOrder::Reversed);
+  }
+
+  // Add candidates from ADL. Per [over.match.oper]p2, this lookup is not
+  // performed for an assignment operator (nor for operator[] nor operator->,
+  // which don't get here).
+  if (Op != OO_Equal && PerformADL) {
+    DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(Op);
+    AddArgumentDependentLookupCandidates(OpName, OpLoc, Args,
+                                         /*ExplicitTemplateArgs*/ nullptr,
+                                         CandidateSet);
+    if (ExtraOp) {
+      DeclarationName ExtraOpName =
+          Context.DeclarationNames.getCXXOperatorName(ExtraOp);
+      AddArgumentDependentLookupCandidates(ExtraOpName, OpLoc, Args,
+                                           /*ExplicitTemplateArgs*/ nullptr,
+                                           CandidateSet);
+    }
+  }
+
+  // Add builtin operator candidates.
+  //
+  // FIXME: We don't add any rewritten candidates here. This is strictly
+  // incorrect; a builtin candidate could be hidden by a non-viable candidate,
+  // resulting in our selecting a rewritten builtin candidate. For example:
+  //
+  //   enum class E { e };
+  //   bool operator!=(E, E) requires false;
+  //   bool k = E::e != E::e;
+  //
+  // ... should select the rewritten builtin candidate 'operator==(E, E)'. But
+  // it seems unreasonable to consider rewritten builtin candidates. A core
+  // issue has been filed proposing to removed this requirement.
+  AddBuiltinOperatorCandidates(Op, OpLoc, Args, CandidateSet);
+}
+
 /// Create a binary operation that may resolve to an overloaded
 /// operator.
 ///
@@ -12783,7 +12847,6 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
     AllowRewrittenCandidates = false;
 
   OverloadedOperatorKind Op = BinaryOperator::getOverloadedOperator(Opc);
-  DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(Op);
 
   // If either side is type-dependent, create an appropriate dependent
   // expression.
@@ -12805,6 +12868,7 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
     // FIXME: save results of ADL from here?
     CXXRecordDecl *NamingClass = nullptr; // lookup ignores member operators
     // TODO: provide better source location info in DNLoc component.
+    DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(Op);
     DeclarationNameInfo OpNameInfo(OpName, OpLoc);
     UnresolvedLookupExpr *Fn = UnresolvedLookupExpr::Create(
         Context, NamingClass, NestedNameSpecifierLoc(), OpNameInfo,
@@ -12838,63 +12902,11 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
   if (Opc == BO_PtrMemD)
     return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
 
-  // Build an empty overload set.
+  // Build the overload set.
   OverloadCandidateSet CandidateSet(
       OpLoc, OverloadCandidateSet::CSK_Operator,
       OverloadCandidateSet::OperatorRewriteInfo(Op, AllowRewrittenCandidates));
-
-  OverloadedOperatorKind ExtraOp =
-      AllowRewrittenCandidates ? getRewrittenOverloadedOperator(Op) : OO_None;
-
-  // Add the candidates from the given function set. This also adds the
-  // rewritten candidates using these functions if necessary.
-  AddNonMemberOperatorCandidates(Fns, Args, CandidateSet);
-
-  // Add operator candidates that are member functions.
-  AddMemberOperatorCandidates(Op, OpLoc, Args, CandidateSet);
-  if (CandidateSet.getRewriteInfo().shouldAddReversed(Op))
-    AddMemberOperatorCandidates(Op, OpLoc, {Args[1], Args[0]}, CandidateSet,
-                                OverloadCandidateParamOrder::Reversed);
-
-  // In C++20, also add any rewritten member candidates.
-  if (ExtraOp) {
-    AddMemberOperatorCandidates(ExtraOp, OpLoc, Args, CandidateSet);
-    if (CandidateSet.getRewriteInfo().shouldAddReversed(ExtraOp))
-      AddMemberOperatorCandidates(ExtraOp, OpLoc, {Args[1], Args[0]},
-                                  CandidateSet,
-                                  OverloadCandidateParamOrder::Reversed);
-  }
-
-  // Add candidates from ADL. Per [over.match.oper]p2, this lookup is not
-  // performed for an assignment operator (nor for operator[] nor operator->,
-  // which don't get here).
-  if (Opc != BO_Assign && PerformADL) {
-    AddArgumentDependentLookupCandidates(OpName, OpLoc, Args,
-                                         /*ExplicitTemplateArgs*/ nullptr,
-                                         CandidateSet);
-    if (ExtraOp) {
-      DeclarationName ExtraOpName =
-          Context.DeclarationNames.getCXXOperatorName(ExtraOp);
-      AddArgumentDependentLookupCandidates(ExtraOpName, OpLoc, Args,
-                                           /*ExplicitTemplateArgs*/ nullptr,
-                                           CandidateSet);
-    }
-  }
-
-  // Add builtin operator candidates.
-  //
-  // FIXME: We don't add any rewritten candidates here. This is strictly
-  // incorrect; a builtin candidate could be hidden by a non-viable candidate,
-  // resulting in our selecting a rewritten builtin candidate. For example:
-  //
-  //   enum class E { e };
-  //   bool operator!=(E, E) requires false;
-  //   bool k = E::e != E::e;
-  //
-  // ... should select the rewritten builtin candidate 'operator==(E, E)'. But
-  // it seems unreasonable to consider rewritten builtin candidates. A core
-  // issue has been filed proposing to removed this requirement.
-  AddBuiltinOperatorCandidates(Op, OpLoc, Args, CandidateSet);
+  LookupOverloadedBinOp(CandidateSet, Op, Fns, Args, PerformADL);
 
   bool HadMultipleCandidates = (CandidateSet.size() > 1);
 
@@ -13150,14 +13162,20 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
 
     case OR_Deleted:
       if (isImplicitlyDeleted(Best->Function)) {
-        CXXMethodDecl *Method = cast<CXXMethodDecl>(Best->Function);
-        Diag(OpLoc, diag::err_ovl_deleted_special_oper)
-          << Context.getRecordType(Method->getParent())
-          << getSpecialMember(Method);
+        FunctionDecl *DeletedFD = Best->Function;
+        DefaultedFunctionKind DFK = getDefaultedFunctionKind(DeletedFD);
+        if (DFK.isSpecialMember()) {
+          Diag(OpLoc, diag::err_ovl_deleted_special_oper)
+            << Args[0]->getType() << DFK.asSpecialMember();
+        } else {
+          assert(DFK.isComparison());
+          Diag(OpLoc, diag::err_ovl_deleted_comparison)
+            << Args[0]->getType() << DeletedFD;
+        }
 
         // The user probably meant to call this special member. Just
         // explain why it's deleted.
-        NoteDeletedFunction(Method);
+        NoteDeletedFunction(DeletedFD);
         return ExprError();
       }
       CandidateSet.NoteCandidates(

diff  --git a/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp
index eb4789e31376..bbc9060df305 100644
--- a/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp
+++ b/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp
@@ -1,70 +1,143 @@
 // RUN: %clang_cc1 -std=c++2a -verify %s
 
-struct A {
+struct A1 {
   int x;
-  int &y; // expected-note 7{{reference member 'y' declared here}}
+  int &y; // expected-note 9{{because class 'A1' has a reference member}}
 
-  bool operator==(const A&) const = default; // expected-error {{cannot default 'operator==' in class 'A' with reference member}}
-  bool operator!=(const A&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator!=' in class 'A' with reference member}}
+  bool operator==(const A1&) const = default; // expected-warning {{implicitly deleted}} expected-note 2{{deleted here}}
+  bool operator<=>(const A1&) const = default; // expected-warning {{implicitly deleted}} expected-note 5{{deleted here}}
+};
+struct A2 {
+  int x;
+  int &y;
+
+  bool operator==(const A2&) const;
+  bool operator!=(const A2&) const = default;
 
-  bool operator<=>(const A&) const = default; // expected-error {{cannot default 'operator<=>' in class 'A' with reference member}}
-  bool operator<(const A&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<' in class 'A' with reference member}}
-  bool operator<=(const A&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=' in class 'A' with reference member}}
-  bool operator>(const A&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>' in class 'A' with reference member}}
-  bool operator>=(const A&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>=' in class 'A' with reference member}}
+  bool operator<=>(const A2&) const;
+  bool operator<(const A2&) const = default;
+  bool operator<=(const A2&) const = default;
+  bool operator>(const A2&) const = default;
+  bool operator>=(const A2&) const = default;
 };
+void f(A1 a) {
+  void(a == a); // expected-error {{deleted}}
+  void(a != a); // expected-error {{deleted}}
+  void(a <=> a); // expected-error {{deleted}}
+  void(a < a); // expected-error {{deleted}}
+  void(a <= a); // expected-error {{deleted}}
+  void(a > a); // expected-error {{deleted}}
+  void(a >= a); // expected-error {{deleted}}
+}
+void f(A2 a) {
+  void(a == a);
+  void(a != a);
+  void(a <=> a);
+  void(a < a);
+  void(a <= a);
+  void(a > a);
+  void(a >= a);
+}
 
-struct B {
+struct B1 {
   struct {
     int x;
-    int &y; // expected-note 7{{reference member 'y' declared here}}
+    int &y; // expected-note 2{{because class 'B1' has a reference member}}
   };
 
-  bool operator==(const B&) const = default; // expected-error {{cannot default 'operator==' in class 'B' with reference member}}
-  bool operator!=(const B&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator!=' in class 'B' with reference member}}
+  bool operator==(const B1&) const = default; // expected-warning {{implicitly deleted}}
+  bool operator<=>(const B1&) const = default; // expected-warning {{implicitly deleted}}
+};
+
+struct B2 {
+  struct {
+    int x;
+    int &y;
+  };
+
+  bool operator==(const B2&) const;
+  bool operator!=(const B2&) const = default;
+
+  bool operator<=>(const B2&) const;
+  bool operator<(const B2&) const = default;
+  bool operator<=(const B2&) const = default;
+  bool operator>(const B2&) const = default;
+  bool operator>=(const B2&) const = default;
+};
+
+union C1 {
+  int a;
 
-  bool operator<=>(const B&) const = default; // expected-error {{cannot default 'operator<=>' in class 'B' with reference member}}
-  bool operator<(const B&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<' in class 'B' with reference member}}
-  bool operator<=(const B&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=' in class 'B' with reference member}}
-  bool operator>(const B&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>' in class 'B' with reference member}}
-  bool operator>=(const B&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>=' in class 'B' with reference member}}
+  bool operator==(const C1&) const = default; // expected-warning {{implicitly deleted}} expected-note {{because 'C1' is a union }}
+  bool operator<=>(const C1&) const = default; // expected-warning {{implicitly deleted}} expected-note {{because 'C1' is a union }}
 };
 
-union C {
+union C2 {
   int a;
 
-  bool operator==(const C&) const = default; // expected-error {{cannot default 'operator==' in union 'C'}}
-  bool operator!=(const C&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator!=' in union 'C'}}
+  bool operator==(const C2&) const;
+  bool operator!=(const C2&) const = default;
 
-  bool operator<=>(const C&) const = default; // expected-error {{cannot default 'operator<=>' in union 'C'}}
-  bool operator<(const C&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<' in union 'C'}}
-  bool operator<=(const C&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=' in union 'C'}}
-  bool operator>(const C&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>' in union 'C'}}
-  bool operator>=(const C&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>=' in union 'C'}}
+  bool operator<=>(const C2&) const;
+  bool operator<(const C2&) const = default;
+  bool operator<=(const C2&) const = default;
+  bool operator>(const C2&) const = default;
+  bool operator>=(const C2&) const = default;
 };
 
-struct D {
+struct D1 {
   union {
     int a;
   };
 
-  bool operator==(const D&) const = default; // expected-error {{cannot default 'operator==' in union-like class 'D'}}
-  bool operator!=(const D&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator!=' in union-like class 'D'}}
+  bool operator==(const D1&) const = default; // expected-warning {{implicitly deleted}} expected-note {{because 'D1' is a union-like class}}
+  bool operator<=>(const D1&) const = default; // expected-warning {{implicitly deleted}} expected-note {{because 'D1' is a union-like class}}
+};
+struct D2 {
+  union {
+    int a;
+  };
 
-  bool operator<=>(const D&) const = default; // expected-error {{cannot default 'operator<=>' in union-like class 'D'}}
-  bool operator<(const D&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<' in union-like class 'D'}}
-  bool operator<=(const D&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=' in union-like class 'D'}}
-  bool operator>(const D&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>' in union-like class 'D'}}
-  bool operator>=(const D&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>=' in union-like class 'D'}}
+  bool operator==(const D2&) const;
+  bool operator!=(const D2&) const = default;
+
+  bool operator<=>(const D2&) const;
+  bool operator<(const D2&) const = default;
+  bool operator<=(const D2&) const = default;
+  bool operator>(const D2&) const = default;
+  bool operator>=(const D2&) const = default;
 };
 
-union E {
-  bool operator==(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator==' in union 'E' despite it having no variant members}}
-  bool operator!=(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator!=' in union 'E'}}
+union E1 {
+  bool operator==(const E1&) const = default;
+  bool operator!=(const E1&) const = default;
+
+  bool operator<=>(const E1&) const = default;
+  bool operator<(const E1&) const = default;
+  bool operator<=(const E1&) const = default;
+  bool operator>(const E1&) const = default;
+  bool operator>=(const E1&) const = default;
+};
+union E2 {
+  bool operator==(const E2&) const = default;
+  bool operator!=(const E2&) const = default;
+
+  bool operator<=>(const E2&) const = default;
+  bool operator<(const E2&) const = default;
+  bool operator<=(const E2&) const = default;
+  bool operator>(const E2&) const = default;
+  bool operator>=(const E2&) const = default;
+};
 
-  bool operator<=>(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=>' in union 'E' despite it having no variant members}}
-  bool operator<(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<' in union 'E'}}
-  bool operator<=(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator<=' in union 'E'}}
-  bool operator>(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>' in union 'E'}}
-  bool operator>=(const E&) const = default; // expected-warning {{ISO C++2a does not allow defaulting 'operator>=' in union 'E'}}
+struct F;
+bool operator==(const F&, const F&);
+bool operator!=(const F&, const F&);
+bool operator<=>(const F&, const F&);
+bool operator<(const F&, const F&);
+struct F {
+  union { int a; };
+  friend bool operator==(const F&, const F&) = default; // expected-error {{defaulting this equality comparison operator would delete it after its first declaration}} expected-note {{implicitly deleted because 'F' is a union-like class}}
+  friend bool operator!=(const F&, const F&) = default;
+  friend bool operator<=>(const F&, const F&) = default; // expected-error {{defaulting this three-way comparison operator would delete it after its first declaration}} expected-note {{implicitly deleted because 'F' is a union-like class}}
+  friend bool operator<(const F&, const F&) = default;
 };

diff  --git a/clang/test/CXX/class/class.compare/class.compare.default/p3.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p3.cpp
new file mode 100644
index 000000000000..f6daaf020590
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.compare.default/p3.cpp
@@ -0,0 +1,192 @@
+// This test is for the [class.compare.default]p3 added by P2002R0
+
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+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 A {
+  friend bool operator==(const A&, const A&) = default;
+  friend bool operator!=(const A&, const A&) = default;
+
+  friend std::strong_ordering operator<=>(const A&, const A&) = default;
+  friend bool operator<(const A&, const A&) = default;
+  friend bool operator<=(const A&, const A&) = default;
+  friend bool operator>(const A&, const A&) = default;
+  friend bool operator>=(const A&, const A&) = default;
+};
+struct TestA {
+  friend constexpr bool operator==(const A&, const A&);
+  friend constexpr bool operator!=(const A&, const A&);
+
+  friend constexpr std::strong_ordering operator<=>(const A&, const A&);
+  friend constexpr bool operator<(const A&, const A&);
+  friend constexpr bool operator<=(const A&, const A&);
+  friend constexpr bool operator>(const A&, const A&);
+  friend constexpr bool operator>=(const A&, const A&);
+};
+
+// Declaration order doesn't matter, even though the secondary operators need
+// to know whether the primary ones are constexpr.
+struct ReversedA {
+  friend bool operator>=(const ReversedA&, const ReversedA&) = default;
+  friend bool operator>(const ReversedA&, const ReversedA&) = default;
+  friend bool operator<=(const ReversedA&, const ReversedA&) = default;
+  friend bool operator<(const ReversedA&, const ReversedA&) = default;
+  friend std::strong_ordering operator<=>(const ReversedA&, const ReversedA&) = default;
+
+  friend bool operator!=(const ReversedA&, const ReversedA&) = default;
+  friend bool operator==(const ReversedA&, const ReversedA&) = default;
+};
+struct TestReversedA {
+  friend constexpr bool operator>=(const ReversedA&, const ReversedA&);
+  friend constexpr bool operator>(const ReversedA&, const ReversedA&);
+  friend constexpr bool operator<=(const ReversedA&, const ReversedA&);
+  friend constexpr bool operator<(const ReversedA&, const ReversedA&);
+  friend constexpr std::strong_ordering operator<=>(const ReversedA&, const ReversedA&);
+
+  friend constexpr bool operator!=(const ReversedA&, const ReversedA&);
+  friend constexpr bool operator==(const ReversedA&, const ReversedA&);
+};
+
+struct B {
+  A a;
+  friend bool operator==(const B&, const B&) = default;
+  friend bool operator!=(const B&, const B&) = default;
+
+  friend std::strong_ordering operator<=>(const B&, const B&) = default;
+  friend bool operator<(const B&, const B&) = default;
+  friend bool operator<=(const B&, const B&) = default;
+  friend bool operator>(const B&, const B&) = default;
+  friend bool operator>=(const B&, const B&) = default;
+};
+struct TestB {
+  friend constexpr bool operator==(const B&, const B&);
+  friend constexpr bool operator!=(const B&, const B&);
+
+  friend constexpr std::strong_ordering operator<=>(const B&, const B&);
+  friend constexpr bool operator<(const B&, const B&);
+  friend constexpr bool operator<=(const B&, const B&);
+  friend constexpr bool operator>(const B&, const B&);
+  friend constexpr bool operator>=(const B&, const B&);
+};
+
+struct C {
+  friend bool operator==(const C&, const C&); // expected-note {{previous}} expected-note 2{{here}}
+  friend bool operator!=(const C&, const C&) = default; // expected-note {{previous}}
+
+  friend std::strong_ordering operator<=>(const C&, const C&); // expected-note {{previous}} expected-note 2{{here}}
+  friend bool operator<(const C&, const C&) = default; // expected-note {{previous}}
+  friend bool operator<=(const C&, const C&) = default; // expected-note {{previous}}
+  friend bool operator>(const C&, const C&) = default; // expected-note {{previous}}
+  friend bool operator>=(const C&, const C&) = default; // expected-note {{previous}}
+};
+struct TestC {
+  friend constexpr bool operator==(const C&, const C&); // expected-error {{non-constexpr}}
+  friend constexpr bool operator!=(const C&, const C&); // expected-error {{non-constexpr}}
+
+  friend constexpr std::strong_ordering operator<=>(const C&, const C&); // expected-error {{non-constexpr}}
+  friend constexpr bool operator<(const C&, const C&); // expected-error {{non-constexpr}}
+  friend constexpr bool operator<=(const C&, const C&); // expected-error {{non-constexpr}}
+  friend constexpr bool operator>(const C&, const C&); // expected-error {{non-constexpr}}
+  friend constexpr bool operator>=(const C&, const C&); // expected-error {{non-constexpr}}
+};
+
+struct D {
+  A a;
+  C c;
+  A b;
+  friend bool operator==(const D&, const D&) = default; // expected-note {{previous}}
+  friend bool operator!=(const D&, const D&) = default; // expected-note {{previous}}
+
+  friend std::strong_ordering operator<=>(const D&, const D&) = default; // expected-note {{previous}}
+  friend bool operator<(const D&, const D&) = default; // expected-note {{previous}}
+  friend bool operator<=(const D&, const D&) = default; // expected-note {{previous}}
+  friend bool operator>(const D&, const D&) = default; // expected-note {{previous}}
+  friend bool operator>=(const D&, const D&) = default; // expected-note {{previous}}
+};
+struct TestD {
+  friend constexpr bool operator==(const D&, const D&); // expected-error {{non-constexpr}}
+  friend constexpr bool operator!=(const D&, const D&); // expected-error {{non-constexpr}}
+
+  friend constexpr std::strong_ordering operator<=>(const D&, const D&); // expected-error {{non-constexpr}}
+  friend constexpr bool operator<(const D&, const D&); // expected-error {{non-constexpr}}
+  friend constexpr bool operator<=(const D&, const D&); // expected-error {{non-constexpr}}
+  friend constexpr bool operator>(const D&, const D&); // expected-error {{non-constexpr}}
+  friend constexpr bool operator>=(const D&, const D&); // expected-error {{non-constexpr}}
+};
+
+
+struct E {
+  A a;
+  C c; // expected-note 2{{non-constexpr comparison function would be used to compare member 'c'}}
+  A b;
+  friend constexpr bool operator==(const E&, const E&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+  friend constexpr bool operator!=(const E&, const E&) = default;
+
+  friend constexpr std::strong_ordering operator<=>(const E&, const E&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+  friend constexpr bool operator<(const E&, const E&) = default;
+  friend constexpr bool operator<=(const E&, const E&) = default;
+  friend constexpr bool operator>(const E&, const E&) = default;
+  friend constexpr bool operator>=(const E&, const E&) = default;
+};
+
+struct E2 : A, C { // expected-note 2{{non-constexpr comparison function would be used to compare base class 'C'}}
+  friend constexpr bool operator==(const E2&, const E2&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+  friend constexpr bool operator!=(const E2&, const E2&) = default;
+
+  friend constexpr std::strong_ordering operator<=>(const E2&, const E2&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+  friend constexpr bool operator<(const E2&, const E2&) = default;
+  friend constexpr bool operator<=(const E2&, const E2&) = default;
+  friend constexpr bool operator>(const E2&, const E2&) = default;
+  friend constexpr bool operator>=(const E2&, const E2&) = default;
+};
+
+struct F {
+  friend bool operator==(const F&, const F&); // expected-note {{here}}
+  friend constexpr bool operator!=(const F&, const F&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+
+  friend std::strong_ordering operator<=>(const F&, const F&); // expected-note 4{{here}}
+  friend constexpr bool operator<(const F&, const F&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+  friend constexpr bool operator<=(const F&, const F&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+  friend constexpr bool operator>(const F&, const F&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+  friend constexpr bool operator>=(const F&, const F&) = default; // expected-error {{cannot be declared constexpr because it invokes a non-constexpr comparison function}}
+};
+
+// No implicit 'constexpr' if it's not the first declaration.
+// FIXME: This rule creates problems for reordering of declarations; is this
+// really the right model?
+struct G;
+bool operator==(const G&, const G&);
+bool operator!=(const G&, const G&);
+std::strong_ordering operator<=>(const G&, const G&);
+bool operator<(const G&, const G&);
+bool operator<=(const G&, const G&);
+bool operator>(const G&, const G&);
+bool operator>=(const G&, const G&);
+struct G {
+  friend bool operator==(const G&, const G&) = default;
+  friend bool operator!=(const G&, const G&) = default;
+
+  friend std::strong_ordering operator<=>(const G&, const G&) = default;
+  friend bool operator<(const G&, const G&) = default;
+  friend bool operator<=(const G&, const G&) = default;
+  friend bool operator>(const G&, const G&) = default;
+  friend bool operator>=(const G&, const G&) = default;
+};
+bool operator==(const G&, const G&);
+bool operator!=(const G&, const G&);
+
+std::strong_ordering operator<=>(const G&, const G&);
+bool operator<(const G&, const G&);
+bool operator<=(const G&, const G&);
+bool operator>(const G&, const G&);
+bool operator>=(const G&, const G&);

diff  --git a/clang/test/CXX/class/class.compare/class.eq/p1.cpp b/clang/test/CXX/class/class.compare/class.eq/p1.cpp
index 622f66cf9281..72f6ad50c368 100644
--- a/clang/test/CXX/class/class.compare/class.eq/p1.cpp
+++ b/clang/test/CXX/class/class.compare/class.eq/p1.cpp
@@ -1,10 +1,12 @@
 // RUN: %clang_cc1 -std=c++2a -verify %s
 
-struct Good {
-  bool operator==(const Good&) const = default;
-  bool operator!=(const Good&) const = default;
-  friend bool operator==(const Good&, const Good&) = default;
-  friend bool operator!=(const Good&, const Good&) = default;
+struct Good1 {
+  bool operator==(const Good1&) const = default;
+  bool operator!=(const Good1&) const = default;
+};
+struct Good2 {
+  friend bool operator==(const Good2&, const Good2&) = default;
+  friend bool operator!=(const Good2&, const Good2&) = default;
 };
 
 enum Bool : bool {};

diff  --git a/clang/test/CXX/class/class.compare/class.eq/p2.cpp b/clang/test/CXX/class/class.compare/class.eq/p2.cpp
new file mode 100644
index 000000000000..1a515dee4900
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.eq/p2.cpp
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+struct A {};
+struct B { bool operator==(B) const; };
+struct C { int operator==(C) const; };
+struct D {
+  // expected-note at +2 {{candidate function not viable: 'this' argument has type 'const}}
+  // expected-note at +1 {{candidate function (with reversed parameter order) not viable: 1st argument ('const}}
+  bool operator==(D);
+};
+struct E { E(const E&) = delete; int operator==(E) const; };
+struct F { void operator==(F) const; };
+struct G { bool operator==(G) const = delete; }; // expected-note {{deleted here}}
+
+template<typename T> struct X {
+  X();
+  bool operator==(const X&) const = default; // expected-note 3{{deleted here}}
+  T t; // expected-note 2{{because there is no viable comparison function for member 't'}}
+       // expected-note at -1 {{because it would invoke a deleted comparison function for member 't'}}
+};
+
+struct Mutable {
+  bool operator==(const Mutable&) const = default;
+  mutable D d;
+};
+
+void test() {
+  void(X<A>() == X<A>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}}
+  void(X<B>() == X<B>());
+  void(X<C>() == X<C>());
+  void(X<D>() == X<D>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}}
+  void(Mutable() == Mutable());
+
+  // FIXME: Not deleted, but once we start synthesizing comparison function definitions, we should reject this.
+  void(X<E>() == X<E>());
+  // FIXME: Similarly, not deleted under P2002R0, but synthesized body is ill-formed.
+  void(X<F>() == X<F>());
+
+  void(X<G>() == X<G>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}}
+}

diff  --git a/clang/test/CXX/class/class.compare/class.rel/p1.cpp b/clang/test/CXX/class/class.compare/class.rel/p1.cpp
index 3797d5f81f56..f61dd4a927d5 100644
--- a/clang/test/CXX/class/class.compare/class.rel/p1.cpp
+++ b/clang/test/CXX/class/class.compare/class.rel/p1.cpp
@@ -1,6 +1,8 @@
 // RUN: %clang_cc1 -std=c++2a -verify %s
 
 struct Good {
+  int operator<=>(const Good&) const;
+
   bool operator<(const Good&) const = default;
   bool operator>(const Good&) const = default;
   friend bool operator<=(const Good&, const Good&) = default;

diff  --git a/clang/test/CXX/class/class.compare/class.rel/p2.cpp b/clang/test/CXX/class/class.compare/class.rel/p2.cpp
new file mode 100644
index 000000000000..d81c9634131f
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.rel/p2.cpp
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+namespace Rel {
+  struct A {
+    int operator<=>(A) const;
+    friend bool operator<(const A&, const A&) = default;
+    friend bool operator<=(const A&, const A&) = default;
+    friend bool operator>(const A&, const A&) = default;
+    friend bool operator>=(const A&, const A&) = default;
+  };
+  bool a1 = A() < A();
+  bool a2 = A() <= A();
+  bool a3 = A() > A();
+  bool a4 = A() >= A();
+
+  struct B {
+    bool operator<=>(B) const = delete; // expected-note 4{{deleted here}} expected-note-re 8{{candidate {{.*}} deleted}}
+    friend bool operator<(const B&, const B&) = default; // expected-warning {{implicitly deleted}} expected-note {{because it would invoke a deleted comparison}} expected-note-re {{candidate {{.*}} deleted}}
+    friend bool operator<=(const B&, const B&) = default; // expected-warning {{implicitly deleted}} expected-note {{because it would invoke a deleted comparison}} expected-note-re {{candidate {{.*}} deleted}}
+    friend bool operator>(const B&, const B&) = default; // expected-warning {{implicitly deleted}} expected-note {{because it would invoke a deleted comparison}} expected-note-re {{candidate {{.*}} deleted}}
+    friend bool operator>=(const B&, const B&) = default; // expected-warning {{implicitly deleted}} expected-note {{because it would invoke a deleted comparison}} expected-note-re {{candidate {{.*}} deleted}}
+  };
+  bool b1 = B() < B(); // expected-error {{deleted}}
+  bool b2 = B() <= B(); // expected-error {{deleted}}
+  bool b3 = B() > B(); // expected-error {{deleted}}
+  bool b4 = B() >= B(); // expected-error {{deleted}}
+
+  struct C {
+    friend bool operator<=>(const C&, const C&);
+    friend bool operator<(const C&, const C&); // expected-note {{because this non-rewritten comparison function would be the best match}}
+
+    bool operator<(const C&) const = default; // expected-warning {{implicitly deleted}}
+    bool operator>(const C&) const = default; // OK
+  };
+}
+
+// Under P2002R0, operator!= follows these rules too.
+namespace NotEqual {
+  struct A {
+    bool operator==(A) const;
+    friend bool operator!=(const A&, const A&) = default;
+  };
+  bool a = A() != A();
+
+  struct B {
+    bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}}
+    friend bool operator!=(const B&, const B&) = default; // expected-warning {{implicitly deleted}} expected-note {{because it would invoke a deleted comparison}} expected-note-re {{candidate {{.*}} deleted}}
+  };
+  bool b = B() != B(); // expected-error {{deleted}}
+
+  struct C {
+    friend bool operator==(const C&, const C&);
+    friend bool operator!=(const C&, const C&); // expected-note {{because this non-rewritten comparison function would be the best match}}
+
+    bool operator!=(const C&) const = default; // expected-warning {{implicitly deleted}}
+  };
+
+  // Ensure we don't go into an infinite loop diagnosing this: the first function
+  // is deleted because it calls the second function, which is deleted because it
+  // calls the first.
+  struct Evil {
+    friend bool operator!=(const Evil&, const Evil&) = default; // expected-warning {{implicitly deleted}} expected-note {{would be the best match}}
+    bool operator!=(const Evil&) const = default; // expected-warning {{implicitly deleted}} expected-note {{would be the best match}}
+  };
+}

diff  --git a/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
new file mode 100644
index 000000000000..928fe2036156
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
@@ -0,0 +1,81 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+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},
+      strong_ordering::equal{0}, strong_ordering::greater{1};
+}
+
+namespace Deletedness {
+  struct A {
+    std::strong_ordering operator<=>(const A&) const;
+  };
+  struct B {
+    bool operator==(const B&) const;
+    bool operator<(const B&) const;
+  };
+  struct C {
+    std::strong_ordering operator<=>(const C&) const = delete; // expected-note {{deleted}}
+  };
+  struct D1 {
+    bool operator==(const D1&) const;
+    std::strong_ordering operator<=>(int) const; // expected-note {{function not viable}} expected-note {{function (with reversed parameter order) not viable}}
+    bool operator<(int) const; // expected-note {{function not viable}}
+  };
+  struct D2 {
+    bool operator<(const D2&) const;
+    std::strong_ordering operator<=>(int) const; // expected-note {{function not viable}} expected-note {{function (with reversed parameter order) not viable}}
+    bool operator==(int) const; // expected-note {{function not viable}}
+  };
+  struct E {
+    bool operator==(const E&) const;
+    bool operator<(const E&) const = delete; // expected-note {{deleted}}
+  };
+  struct F {
+    std::strong_ordering operator<=>(const F&) const; // expected-note {{candidate}}
+    std::strong_ordering operator<=>(F) const; // expected-note {{candidate}}
+  };
+  struct G1 {
+    bool operator==(const G1&) const;
+    void operator<(const G1&) const;
+  };
+  struct G2 {
+    void operator==(const G2&) const;
+    bool operator<(const G2&) const;
+  };
+  struct H {
+    void operator<=>(const H&) const;
+  };
+
+  // expected-note@#base {{deleted comparison function for base class 'C'}}
+  // expected-note@#base {{no viable comparison function for base class 'D1'}}
+  // expected-note@#base {{three-way comparison cannot be synthesized because there is no viable function for '<' comparison}}
+  // expected-note@#base {{no viable comparison function for base class 'D2'}}
+  // expected-note@#base {{three-way comparison cannot be synthesized because there is no viable function for '==' comparison}}
+  // expected-note@#base {{deleted comparison function for base class 'E'}}
+  // expected-note@#base {{implied comparison for base class 'F' is ambiguous}}
+  template<typename T> struct Cmp : T { // #base
+    std::strong_ordering operator<=>(const Cmp&) const = default; // expected-note 5{{here}}
+  };
+
+  void use(...);
+  void f() {
+    use(
+      Cmp<A>() <=> Cmp<A>(),
+      Cmp<B>() <=> Cmp<B>(),
+      Cmp<C>() <=> Cmp<C>(), // expected-error {{deleted}}
+      Cmp<D1>() <=> Cmp<D1>(), // expected-error {{deleted}}
+      Cmp<D2>() <=> Cmp<D2>(), // expected-error {{deleted}}
+      Cmp<E>() <=> Cmp<E>(), // expected-error {{deleted}}
+      Cmp<F>() <=> Cmp<F>(), // expected-error {{deleted}}
+      Cmp<G1>() <=> Cmp<G1>(), // FIXME: ok but synthesized body is ill-formed
+      Cmp<G2>() <=> Cmp<G2>(), // FIXME: ok but synthesized body is ill-formed
+      Cmp<H>() <=> Cmp<H>(), // FIXME: ok but synthesized body is ill-formed
+      0
+    );
+  }
+}

diff  --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 2022402b6950..77089288cc0f 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -934,18 +934,16 @@ <h2 id="cxx20">C++2a implementation status</h2>
       </tr>
       <tr> <!-- from Rapperswil -->
         <td><a href="https://wg21.link/p1120r0">P1120R0</a></td>
-        <td rowspan="2" class="partial" align="center">Partial</td>
+        <td rowspan="4" class="partial" align="center">Partial</td>
       </tr>
       <tr> <!-- from Kona 2019 -->
         <td><a href="https://wg21.link/p1185r2">P1185R2</a></td>
       </tr>
       <tr> <!-- from Cologne -->
         <td><a href="https://wg21.link/p1186r3">P1186R3</a></td>
-        <td class="none" align="center">No</td>
       </tr>
       <tr>
         <td><a href="https://wg21.link/p1630r1">P1630R1</a></td>
-        <td class="partial" align="center">Partial</td>
       </tr>
       <tr> <!-- from Belfast -->
         <td><a href="https://wg21.link/p1946r0">P1946R0</a></td>


        


More information about the cfe-commits mailing list