[clang] 68009c2 - [c++20] Return type deduction for defaulted three-way comparisons.

Richard Smith via cfe-commits cfe-commits at lists.llvm.org
Tue Dec 10 13:03:52 PST 2019


Author: Richard Smith
Date: 2019-12-10T13:03:12-08:00
New Revision: 68009c245dbe4c420ca06c0fea2a908f918137bb

URL: https://github.com/llvm/llvm-project/commit/68009c245dbe4c420ca06c0fea2a908f918137bb
DIFF: https://github.com/llvm/llvm-project/commit/68009c245dbe4c420ca06c0fea2a908f918137bb.diff

LOG: [c++20] Return type deduction for defaulted three-way comparisons.

Added: 
    clang/test/CXX/class/class.compare/class.spaceship/p2.cpp

Modified: 
    clang/include/clang/AST/ComparisonCategories.h
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/AST/ComparisonCategories.cpp
    clang/lib/Sema/SemaDeclCXX.cpp
    clang/lib/Sema/SemaExpr.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/AST/ComparisonCategories.h b/clang/include/clang/AST/ComparisonCategories.h
index 3dd1db1f7e9b..e28d49963384 100644
--- a/clang/include/clang/AST/ComparisonCategories.h
+++ b/clang/include/clang/AST/ComparisonCategories.h
@@ -50,6 +50,20 @@ enum class ComparisonCategoryType : unsigned char {
   Last = StrongOrdering
 };
 
+/// Determine the common comparison type, as defined in C++2a
+/// [class.spaceship]p4.
+inline ComparisonCategoryType commonComparisonType(ComparisonCategoryType A,
+                                                   ComparisonCategoryType B) {
+  if ((A == ComparisonCategoryType::StrongEquality) ^
+      (B == ComparisonCategoryType::StrongEquality))
+    return ComparisonCategoryType::WeakEquality;
+  return A < B ? A : B;
+}
+
+/// Get the comparison category that should be used when comparing values of
+/// type \c T.
+Optional<ComparisonCategoryType> getComparisonCategoryForBuiltinCmp(QualType T);
+
 /// An enumeration representing the possible results of a three-way
 /// comparison. These values map onto instances of comparison category types
 /// defined in the standard library. e.g. 'std::strong_ordering::less'.

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8a0ff1e8a697..aeeff2b9a76e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8200,6 +8200,9 @@ 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_deduced_return_type_not_auto : Error<
+  "deduced return type for defaulted %sub{select_defaulted_comparison_kind}0 "
+  "must be 'auto', not %1">;
 def warn_defaulted_comparison_deleted : Warning<
   "explicitly defaulted %sub{select_defaulted_comparison_kind}0 is implicitly "
   "deleted">, InGroup<DefaultedFunctionDeleted>;
@@ -8227,6 +8230,12 @@ def note_defaulted_comparison_no_viable_function_synthesized : Note<
 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 note_defaulted_comparison_cannot_deduce : Note<
+  "return type of defaulted 'operator<=>' cannot be deduced because "
+  "return type %2 of three-way comparison for %select{|member|base class}0 %1 "
+  "is not a standard comparison category type">;
+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 "

diff  --git a/clang/lib/AST/ComparisonCategories.cpp b/clang/lib/AST/ComparisonCategories.cpp
index 3fb500c580e8..9a07c2494ac2 100644
--- a/clang/lib/AST/ComparisonCategories.cpp
+++ b/clang/lib/AST/ComparisonCategories.cpp
@@ -19,6 +19,41 @@
 
 using namespace clang;
 
+Optional<ComparisonCategoryType>
+clang::getComparisonCategoryForBuiltinCmp(QualType T) {
+  using CCT = ComparisonCategoryType;
+
+  if (const ComplexType *CT = T->getAs<ComplexType>()) {
+    if (CT->getElementType()->hasFloatingRepresentation())
+      return CCT::WeakEquality;
+    // FIXME: Remove this, consistent with P1959R0.
+    return CCT::StrongEquality;
+  }
+
+  if (T->isIntegralOrEnumerationType())
+    return CCT::StrongOrdering;
+
+  if (T->hasFloatingRepresentation())
+    return CCT::PartialOrdering;
+
+  // C++2a [expr.spaceship]p7: If the composite pointer type is a function
+  // pointer type, a pointer-to-member type, or std::nullptr_t, the
+  // result is of type std::strong_equality
+  if (T->isFunctionPointerType() || T->isMemberPointerType() ||
+      T->isNullPtrType())
+    // FIXME: This case was removed by P1959R0.
+    return CCT::StrongEquality;
+
+  // C++2a [expr.spaceship]p8: If the composite pointer type is an object
+  // pointer type, p <=> q is of type std::strong_ordering.
+  // Note: this assumes neither operand is a null pointer constant.
+  if (T->isPointerType())
+    return CCT::StrongOrdering;
+
+  // TODO: Extend support for operator<=> to ObjC types.
+  return llvm::None;
+}
+
 bool ComparisonCategoryInfo::ValueInfo::hasValidIntValue() const {
   assert(VD && "must have var decl");
   if (!VD->checkInitIsICE())

diff  --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 6d15e1e2025f..7bbd0114b1e2 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7214,12 +7214,18 @@ class DefaultedComparisonVisitor {
 struct DefaultedComparisonInfo {
   bool Deleted = false;
   bool Constexpr = true;
+  ComparisonCategoryType Category = ComparisonCategoryType::StrongOrdering;
 
-  static DefaultedComparisonInfo deleted() { return {true, false}; }
+  static DefaultedComparisonInfo deleted() {
+    DefaultedComparisonInfo Deleted;
+    Deleted.Deleted = true;
+    return Deleted;
+  }
 
   bool add(const DefaultedComparisonInfo &R) {
     Deleted |= R.Deleted;
     Constexpr &= R.Constexpr;
+    Category = commonComparisonType(Category, R.Category);
     return Deleted;
   }
 };
@@ -7353,19 +7359,43 @@ class DefaultedComparisonAnalyzer
       //   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 (FunctionDecl *BestFD = Best->Function) {
+        assert(!BestFD->isDeleted() && "wrong overload resolution result");
         // If it's not constexpr, explain why not.
-        if (Diagnose == ExplainConstexpr && !FD->isConstexpr()) {
+        if (Diagnose == ExplainConstexpr && !BestFD->isConstexpr()) {
           if (Subobj.Kind != Subobject::CompleteObject)
             S.Diag(Subobj.Loc, diag::note_defaulted_comparison_not_constexpr)
               << Subobj.Kind << Subobj.Decl;
-          S.Diag(FD->getLocation(),
+          S.Diag(BestFD->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();
+        R.Constexpr &= BestFD->isConstexpr();
+      }
+
+      if (OO == OO_Spaceship && FD->getReturnType()->isUndeducedAutoType()) {
+        if (auto *BestFD = Best->Function) {
+          if (auto *Info = S.Context.CompCategories.lookupInfoForType(
+              BestFD->getCallResultType())) {
+            R.Category = Info->Kind;
+          } else {
+            if (Diagnose == ExplainDeleted) {
+              S.Diag(Subobj.Loc, diag::note_defaulted_comparison_cannot_deduce)
+                  << Subobj.Kind << Subobj.Decl
+                  << BestFD->getCallResultType().withoutLocalFastQualifiers();
+              S.Diag(BestFD->getLocation(),
+                     diag::note_defaulted_comparison_cannot_deduce_callee)
+                  << Subobj.Kind << Subobj.Decl;
+            }
+            return Result::deleted();
+          }
+        } else {
+          Optional<ComparisonCategoryType> Cat =
+              getComparisonCategoryForBuiltinCmp(Args[0]->getType());
+          assert(Cat && "no category for builtin comparison?");
+          R.Category = *Cat;
+        }
       }
 
       // Note that we might be rewriting to a 
diff erent operator. That call is
@@ -7914,6 +7944,19 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
         << FD->getReturnTypeSourceRange();
     return true;
   }
+  // C++2a [class.spaceship]p2 [P2002R0]:
+  //   Let R be the declared return type [...]. If R is auto, [...]. Otherwise,
+  //   R shall not contain a placeholder type.
+  if (DCK == DefaultedComparisonKind::ThreeWay &&
+      FD->getDeclaredReturnType()->getContainedDeducedType() &&
+      !Context.hasSameType(FD->getDeclaredReturnType(),
+                           Context.getAutoDeductType())) {
+    Diag(FD->getLocation(),
+         diag::err_defaulted_comparison_deduced_return_type_not_auto)
+        << (int)DCK << FD->getDeclaredReturnType() << Context.AutoDeductTy
+        << FD->getReturnTypeSourceRange();
+    return true;
+  }
 
   // For a defaulted function in a dependent class, defer all remaining checks
   // until instantiation.
@@ -7955,7 +7998,21 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
     return false;
   }
 
-  // FIXME: Deduce the return type now.
+  // C++2a [class.spaceship]p2:
+  //   The return type is deduced as the common comparison type of R0, R1, ...
+  if (DCK == DefaultedComparisonKind::ThreeWay &&
+      FD->getDeclaredReturnType()->isUndeducedAutoType()) {
+    SourceLocation RetLoc = FD->getReturnTypeSourceRange().getBegin();
+    if (RetLoc.isInvalid())
+      RetLoc = FD->getBeginLoc();
+    // FIXME: Should we really care whether we have the complete type and the
+    // 'enumerator' constants here? A forward declaration seems sufficient.
+    QualType Cat = CheckComparisonCategoryType(Info.Category, RetLoc);
+    if (Cat.isNull())
+      return true;
+    Context.adjustDeducedFunctionResultType(
+        FD, SubstAutoType(FD->getDeclaredReturnType(), Cat));
+  }
 
   // C++2a [dcl.fct.def.default]p3 [P2002R0]:
   //   An explicitly-defaulted function that is not defined as deleted may be

diff  --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index b97352e27e15..f6bcf899a7fb 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -10521,8 +10521,6 @@ static QualType checkArithmeticOrEnumeralThreeWayCompare(Sema &S,
                                                          ExprResult &LHS,
                                                          ExprResult &RHS,
                                                          SourceLocation Loc) {
-  using CCT = ComparisonCategoryType;
-
   QualType LHSType = LHS.get()->getType();
   QualType RHSType = RHS.get()->getType();
   // Dig out the original argument type and expression before implicit casts
@@ -10582,6 +10580,8 @@ static QualType checkArithmeticOrEnumeralThreeWayCompare(Sema &S,
     return S.InvalidOperands(Loc, LHS, RHS);
   assert(Type->isArithmeticType() || Type->isEnumeralType());
 
+  // FIXME: Reject complex types, consistent with P1959R0.
+
   bool HasNarrowing = checkThreeWayNarrowingConversion(
       S, Type, LHS.get(), LHSType, LHS.get()->getBeginLoc());
   HasNarrowing |= checkThreeWayNarrowingConversion(S, Type, RHS.get(), RHSType,
@@ -10591,20 +10591,8 @@ static QualType checkArithmeticOrEnumeralThreeWayCompare(Sema &S,
 
   assert(!Type.isNull() && "composite type for <=> has not been set");
 
-  auto TypeKind = [&]() {
-    if (const ComplexType *CT = Type->getAs<ComplexType>()) {
-      if (CT->getElementType()->hasFloatingRepresentation())
-        return CCT::WeakEquality;
-      return CCT::StrongEquality;
-    }
-    if (Type->isIntegralOrEnumerationType())
-      return CCT::StrongOrdering;
-    if (Type->hasFloatingRepresentation())
-      return CCT::PartialOrdering;
-    llvm_unreachable("other types are unimplemented");
-  }();
-
-  return S.CheckComparisonCategoryType(TypeKind, Loc);
+  return S.CheckComparisonCategoryType(
+      *getComparisonCategoryForBuiltinCmp(Type), Loc);
 }
 
 static QualType checkArithmeticOrEnumeralCompare(Sema &S, ExprResult &LHS,
@@ -10729,34 +10717,20 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS,
     QualType CompositeTy = LHS.get()->getType();
     assert(!CompositeTy->isReferenceType());
 
-    auto buildResultTy = [&](ComparisonCategoryType Kind) {
-      return CheckComparisonCategoryType(Kind, Loc);
-    };
-
-    // C++2a [expr.spaceship]p7: If the composite pointer type is a function
-    // pointer type, a pointer-to-member type, or std::nullptr_t, the
-    // result is of type std::strong_equality
-    if (CompositeTy->isFunctionPointerType() ||
-        CompositeTy->isMemberPointerType() || CompositeTy->isNullPtrType())
-      // FIXME: consider making the function pointer case produce
-      // strong_ordering not strong_equality, per P0946R0-Jax18 discussion
-      // and direction polls
-      return buildResultTy(ComparisonCategoryType::StrongEquality);
-
-    // C++2a [expr.spaceship]p8: If the composite pointer type is an object
-    // pointer type, p <=> q is of type std::strong_ordering.
-    if (CompositeTy->isPointerType()) {
+    Optional<ComparisonCategoryType> CCT =
+        getComparisonCategoryForBuiltinCmp(CompositeTy);
+    if (!CCT)
+      return InvalidOperands(Loc, LHS, RHS);
+
+    if (CompositeTy->isPointerType() && LHSIsNull != RHSIsNull) {
       // P0946R0: Comparisons between a null pointer constant and an object
       // pointer result in std::strong_equality
-      if (LHSIsNull != RHSIsNull)
-        return buildResultTy(ComparisonCategoryType::StrongEquality);
-      return buildResultTy(ComparisonCategoryType::StrongOrdering);
+      // FIXME: Reject this, consistent with P1959R0 + P0946R0.
+      CCT = ComparisonCategoryType::StrongEquality;
     }
-    // C++2a [expr.spaceship]p9: Otherwise, the program is ill-formed.
-    // TODO: Extend support for operator<=> to ObjC types.
-    return InvalidOperands(Loc, LHS, RHS);
-  };
 
+    return CheckComparisonCategoryType(*CCT, Loc);
+  };
 
   if (!IsRelational && LHSIsNull != RHSIsNull) {
     bool IsEquality = Opc == BO_EQ;

diff  --git a/clang/test/CXX/class/class.compare/class.spaceship/p2.cpp b/clang/test/CXX/class/class.compare/class.spaceship/p2.cpp
new file mode 100644
index 000000000000..1290a063e794
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.spaceship/p2.cpp
@@ -0,0 +1,125 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+namespace std {
+  class strong_ordering {
+    int n;
+    constexpr strong_ordering(int n) : n(n) {}
+  public:
+    static const strong_ordering less, equal, greater;
+    bool operator!=(int) { return n != 0; }
+  };
+  constexpr strong_ordering strong_ordering::less{-1},
+      strong_ordering::equal{0}, strong_ordering::greater{1};
+
+  class weak_ordering {
+    int n;
+    constexpr weak_ordering(int n) : n(n) {}
+  public:
+    constexpr weak_ordering(strong_ordering o);
+    static const weak_ordering less, equivalent, greater;
+    bool operator!=(int) { return n != 0; }
+  };
+  constexpr weak_ordering weak_ordering::less{-1},
+      weak_ordering::equivalent{0}, weak_ordering::greater{1};
+
+  class partial_ordering {
+    int n;
+    constexpr partial_ordering(int n) : n(n) {}
+  public:
+    constexpr partial_ordering(strong_ordering o);
+    constexpr partial_ordering(weak_ordering o);
+    static const partial_ordering less, equivalent, greater, unordered;
+    bool operator!=(int) { return n != 0; }
+  };
+  constexpr partial_ordering partial_ordering::less{-1},
+      partial_ordering::equivalent{0}, partial_ordering::greater{1},
+      partial_ordering::unordered{2};
+}
+
+namespace DeducedNotCat {
+  struct A {
+    A operator<=>(const A&) const; // expected-note {{selected 'operator<=>' for member 'a' declared here}}
+  };
+  struct B {
+    A a; // expected-note {{return type 'DeducedNotCat::A' of three-way comparison for member 'a' is not a standard comparison category type}}
+    auto operator<=>(const B&) const = default; // expected-warning {{implicitly deleted}}
+  };
+}
+
+namespace DeducedVsSynthesized {
+  struct A {
+    bool operator==(const A&) const;
+    bool operator<(const A&) const;
+  };
+  struct B {
+    A a; // expected-note {{no viable comparison function for member 'a'}}
+    auto operator<=>(const B&) const = default; // expected-warning {{implicitly deleted}}
+  };
+}
+
+namespace Deduction {
+  template<typename T> struct wrap {
+    T t;
+    friend auto operator<=>(const wrap&, const wrap&) = default;
+  };
+
+  using strong = wrap<int>;
+  using strong2 = wrap<int*>;
+  struct weak {
+    friend std::weak_ordering operator<=>(weak, weak);
+  };
+  using partial = wrap<float>;
+
+  template<typename ...T> struct A : T... {
+    friend auto operator<=>(const A&, const A&) = default;
+  };
+
+  template<typename Expected, typename ...Ts> void f() {
+    using T = Expected; // expected-note {{previous}}
+    using T = decltype(A<Ts...>() <=> A<Ts...>()); // expected-error {{
diff erent type}}
+    void(A<Ts...>() <=> A<Ts...>()); // trigger synthesis of body
+  }
+
+  template void f<std::strong_ordering>();
+  template void f<std::strong_ordering, strong>();
+  template void f<std::strong_ordering, strong, strong2>();
+
+  template void f<std::weak_ordering, weak>();
+  template void f<std::weak_ordering, weak, strong>();
+  template void f<std::weak_ordering, strong, weak>();
+
+  template void f<std::partial_ordering, partial>();
+  template void f<std::partial_ordering, weak, partial>();
+  template void f<std::partial_ordering, strong, partial>();
+  template void f<std::partial_ordering, partial, weak>();
+  template void f<std::partial_ordering, partial, strong>();
+  template void f<std::partial_ordering, weak, partial, strong>();
+
+  // Check that the above mechanism works.
+  template void f<std::strong_ordering, weak>(); // expected-note {{instantiation of}}
+}
+
+namespace BadDeducedType {
+  struct A {
+    // expected-error at +1 {{deduced return type for defaulted three-way comparison operator must be 'auto', not 'auto &'}}
+    friend auto &operator<=>(const A&, const A&) = default;
+  };
+
+  struct B {
+    // expected-error at +1 {{deduced return type for defaulted three-way comparison operator must be 'auto', not 'const auto'}}
+    friend const auto operator<=>(const B&, const B&) = default;
+  };
+
+  template<typename T> struct X {}; // expected-note {{here}}
+  struct C {
+    // expected-error at +1 {{deduction not allowed in function return type}}
+    friend X operator<=>(const C&, const C&) = default;
+  };
+
+  template<typename T> concept CmpCat = true;
+  struct D {
+    // FIXME: Once we support P1141R2, we should give a better diagnostic here:
+    // {{deduced return type for defaulted three-way comparison operator must be 'auto', not 'CmpCat auto'}}
+    friend CmpCat auto operator<=>(const D&, const D&) = default; // expected-error {{unknown type name 'CmpCat'}}
+  };
+}


        


More information about the cfe-commits mailing list