From 68009c245dbe4c420ca06c0fea2a908f918137bb Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 10 Dec 2019 11:33:27 -0800 Subject: [PATCH] [c++20] Return type deduction for defaulted three-way comparisons. --- clang/include/clang/AST/ComparisonCategories.h | 14 +++ clang/include/clang/Basic/DiagnosticSemaKinds.td | 9 ++ clang/lib/AST/ComparisonCategories.cpp | 35 ++++++ clang/lib/Sema/SemaDeclCXX.cpp | 71 ++++++++++-- clang/lib/Sema/SemaExpr.cpp | 54 +++------ .../CXX/class/class.compare/class.spaceship/p2.cpp | 125 +++++++++++++++++++++ 6 files changed, 261 insertions(+), 47 deletions(-) create mode 100644 clang/test/CXX/class/class.compare/class.spaceship/p2.cpp diff --git a/clang/include/clang/AST/ComparisonCategories.h b/clang/include/clang/AST/ComparisonCategories.h index 3dd1db1..e28d499 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 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 8a0ff1e..aeeff2b 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; @@ -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 3fb500c5..9a07c24 100644 --- a/clang/lib/AST/ComparisonCategories.cpp +++ b/clang/lib/AST/ComparisonCategories.cpp @@ -19,6 +19,41 @@ using namespace clang; +Optional +clang::getComparisonCategoryForBuiltinCmp(QualType T) { + using CCT = ComparisonCategoryType; + + if (const ComplexType *CT = T->getAs()) { + 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 6d15e1e..7bbd011 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7214,12 +7214,18 @@ protected: 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 @@ private: // 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 Cat = + getComparisonCategoryForBuiltinCmp(Args[0]->getType()); + assert(Cat && "no category for builtin comparison?"); + R.Category = *Cat; + } } // Note that we might be rewriting to a different 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 b97352e..f6bcf89 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()) { - 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 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 0000000..1290a06 --- /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 struct wrap { + T t; + friend auto operator<=>(const wrap&, const wrap&) = default; + }; + + using strong = wrap; + using strong2 = wrap; + struct weak { + friend std::weak_ordering operator<=>(weak, weak); + }; + using partial = wrap; + + template struct A : T... { + friend auto operator<=>(const A&, const A&) = default; + }; + + template void f() { + using T = Expected; // expected-note {{previous}} + using T = decltype(A() <=> A()); // expected-error {{different type}} + void(A() <=> A()); // trigger synthesis of body + } + + template void f(); + template void f(); + template void f(); + + template void f(); + template void f(); + template void f(); + + template void f(); + template void f(); + template void f(); + template void f(); + template void f(); + template void f(); + + // Check that the above mechanism works. + template void f(); // expected-note {{instantiation of}} +} + +namespace BadDeducedType { + struct A { + // expected-error@+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@+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 struct X {}; // expected-note {{here}} + struct C { + // expected-error@+1 {{deduction not allowed in function return type}} + friend X operator<=>(const C&, const C&) = default; + }; + + template 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'}} + }; +} -- 2.7.4