[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