Skip to content

Commit 68009c2

Browse files
committed
[c++20] Return type deduction for defaulted three-way comparisons.
1 parent 336ac71 commit 68009c2

File tree

6 files changed

+261
-47
lines changed

6 files changed

+261
-47
lines changed

clang/include/clang/AST/ComparisonCategories.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,20 @@ enum class ComparisonCategoryType : unsigned char {
5050
Last = StrongOrdering
5151
};
5252

53+
/// Determine the common comparison type, as defined in C++2a
54+
/// [class.spaceship]p4.
55+
inline ComparisonCategoryType commonComparisonType(ComparisonCategoryType A,
56+
ComparisonCategoryType B) {
57+
if ((A == ComparisonCategoryType::StrongEquality) ^
58+
(B == ComparisonCategoryType::StrongEquality))
59+
return ComparisonCategoryType::WeakEquality;
60+
return A < B ? A : B;
61+
}
62+
63+
/// Get the comparison category that should be used when comparing values of
64+
/// type \c T.
65+
Optional<ComparisonCategoryType> getComparisonCategoryForBuiltinCmp(QualType T);
66+
5367
/// An enumeration representing the possible results of a three-way
5468
/// comparison. These values map onto instances of comparison category types
5569
/// defined in the standard library. e.g. 'std::strong_ordering::less'.

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8200,6 +8200,9 @@ def err_defaulted_comparison_non_const : Error<
82008200
def err_defaulted_comparison_return_type_not_bool : Error<
82018201
"return type for defaulted %sub{select_defaulted_comparison_kind}0 "
82028202
"must be 'bool', not %1">;
8203+
def err_defaulted_comparison_deduced_return_type_not_auto : Error<
8204+
"deduced return type for defaulted %sub{select_defaulted_comparison_kind}0 "
8205+
"must be 'auto', not %1">;
82038206
def warn_defaulted_comparison_deleted : Warning<
82048207
"explicitly defaulted %sub{select_defaulted_comparison_kind}0 is implicitly "
82058208
"deleted">, InGroup<DefaultedFunctionDeleted>;
@@ -8227,6 +8230,12 @@ def note_defaulted_comparison_no_viable_function_synthesized : Note<
82278230
def note_defaulted_comparison_not_rewritten_callee : Note<
82288231
"defaulted %0 is implicitly deleted because this non-rewritten comparison "
82298232
"function would be the best match for the comparison">;
8233+
def note_defaulted_comparison_cannot_deduce : Note<
8234+
"return type of defaulted 'operator<=>' cannot be deduced because "
8235+
"return type %2 of three-way comparison for %select{|member|base class}0 %1 "
8236+
"is not a standard comparison category type">;
8237+
def note_defaulted_comparison_cannot_deduce_callee : Note<
8238+
"selected 'operator<=>' for %select{|member|base class}0 %1 declared here">;
82308239
def err_incorrect_defaulted_comparison_constexpr : Error<
82318240
"defaulted definition of %sub{select_defaulted_comparison_kind}0 "
82328241
"cannot be declared %select{constexpr|consteval}1 because it invokes "

clang/lib/AST/ComparisonCategories.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,41 @@
1919

2020
using namespace clang;
2121

22+
Optional<ComparisonCategoryType>
23+
clang::getComparisonCategoryForBuiltinCmp(QualType T) {
24+
using CCT = ComparisonCategoryType;
25+
26+
if (const ComplexType *CT = T->getAs<ComplexType>()) {
27+
if (CT->getElementType()->hasFloatingRepresentation())
28+
return CCT::WeakEquality;
29+
// FIXME: Remove this, consistent with P1959R0.
30+
return CCT::StrongEquality;
31+
}
32+
33+
if (T->isIntegralOrEnumerationType())
34+
return CCT::StrongOrdering;
35+
36+
if (T->hasFloatingRepresentation())
37+
return CCT::PartialOrdering;
38+
39+
// C++2a [expr.spaceship]p7: If the composite pointer type is a function
40+
// pointer type, a pointer-to-member type, or std::nullptr_t, the
41+
// result is of type std::strong_equality
42+
if (T->isFunctionPointerType() || T->isMemberPointerType() ||
43+
T->isNullPtrType())
44+
// FIXME: This case was removed by P1959R0.
45+
return CCT::StrongEquality;
46+
47+
// C++2a [expr.spaceship]p8: If the composite pointer type is an object
48+
// pointer type, p <=> q is of type std::strong_ordering.
49+
// Note: this assumes neither operand is a null pointer constant.
50+
if (T->isPointerType())
51+
return CCT::StrongOrdering;
52+
53+
// TODO: Extend support for operator<=> to ObjC types.
54+
return llvm::None;
55+
}
56+
2257
bool ComparisonCategoryInfo::ValueInfo::hasValidIntValue() const {
2358
assert(VD && "must have var decl");
2459
if (!VD->checkInitIsICE())

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7214,12 +7214,18 @@ class DefaultedComparisonVisitor {
72147214
struct DefaultedComparisonInfo {
72157215
bool Deleted = false;
72167216
bool Constexpr = true;
7217+
ComparisonCategoryType Category = ComparisonCategoryType::StrongOrdering;
72177218

7218-
static DefaultedComparisonInfo deleted() { return {true, false}; }
7219+
static DefaultedComparisonInfo deleted() {
7220+
DefaultedComparisonInfo Deleted;
7221+
Deleted.Deleted = true;
7222+
return Deleted;
7223+
}
72197224

72207225
bool add(const DefaultedComparisonInfo &R) {
72217226
Deleted |= R.Deleted;
72227227
Constexpr &= R.Constexpr;
7228+
Category = commonComparisonType(Category, R.Category);
72237229
return Deleted;
72247230
}
72257231
};
@@ -7353,19 +7359,43 @@ class DefaultedComparisonAnalyzer
73537359
// A defaulted comparison function is constexpr-compatible if [...]
73547360
// no overlod resolution performed [...] results in a non-constexpr
73557361
// function.
7356-
if (FunctionDecl *FD = Best->Function) {
7357-
assert(!FD->isDeleted() && "wrong overload resolution result");
7362+
if (FunctionDecl *BestFD = Best->Function) {
7363+
assert(!BestFD->isDeleted() && "wrong overload resolution result");
73587364
// If it's not constexpr, explain why not.
7359-
if (Diagnose == ExplainConstexpr && !FD->isConstexpr()) {
7365+
if (Diagnose == ExplainConstexpr && !BestFD->isConstexpr()) {
73607366
if (Subobj.Kind != Subobject::CompleteObject)
73617367
S.Diag(Subobj.Loc, diag::note_defaulted_comparison_not_constexpr)
73627368
<< Subobj.Kind << Subobj.Decl;
7363-
S.Diag(FD->getLocation(),
7369+
S.Diag(BestFD->getLocation(),
73647370
diag::note_defaulted_comparison_not_constexpr_here);
73657371
// Bail out after explaining; we don't want any more notes.
73667372
return Result::deleted();
73677373
}
7368-
R.Constexpr &= FD->isConstexpr();
7374+
R.Constexpr &= BestFD->isConstexpr();
7375+
}
7376+
7377+
if (OO == OO_Spaceship && FD->getReturnType()->isUndeducedAutoType()) {
7378+
if (auto *BestFD = Best->Function) {
7379+
if (auto *Info = S.Context.CompCategories.lookupInfoForType(
7380+
BestFD->getCallResultType())) {
7381+
R.Category = Info->Kind;
7382+
} else {
7383+
if (Diagnose == ExplainDeleted) {
7384+
S.Diag(Subobj.Loc, diag::note_defaulted_comparison_cannot_deduce)
7385+
<< Subobj.Kind << Subobj.Decl
7386+
<< BestFD->getCallResultType().withoutLocalFastQualifiers();
7387+
S.Diag(BestFD->getLocation(),
7388+
diag::note_defaulted_comparison_cannot_deduce_callee)
7389+
<< Subobj.Kind << Subobj.Decl;
7390+
}
7391+
return Result::deleted();
7392+
}
7393+
} else {
7394+
Optional<ComparisonCategoryType> Cat =
7395+
getComparisonCategoryForBuiltinCmp(Args[0]->getType());
7396+
assert(Cat && "no category for builtin comparison?");
7397+
R.Category = *Cat;
7398+
}
73697399
}
73707400

73717401
// Note that we might be rewriting to a different operator. That call is
@@ -7914,6 +7944,19 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
79147944
<< FD->getReturnTypeSourceRange();
79157945
return true;
79167946
}
7947+
// C++2a [class.spaceship]p2 [P2002R0]:
7948+
// Let R be the declared return type [...]. If R is auto, [...]. Otherwise,
7949+
// R shall not contain a placeholder type.
7950+
if (DCK == DefaultedComparisonKind::ThreeWay &&
7951+
FD->getDeclaredReturnType()->getContainedDeducedType() &&
7952+
!Context.hasSameType(FD->getDeclaredReturnType(),
7953+
Context.getAutoDeductType())) {
7954+
Diag(FD->getLocation(),
7955+
diag::err_defaulted_comparison_deduced_return_type_not_auto)
7956+
<< (int)DCK << FD->getDeclaredReturnType() << Context.AutoDeductTy
7957+
<< FD->getReturnTypeSourceRange();
7958+
return true;
7959+
}
79177960

79187961
// For a defaulted function in a dependent class, defer all remaining checks
79197962
// until instantiation.
@@ -7955,7 +7998,21 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
79557998
return false;
79567999
}
79578000

7958-
// FIXME: Deduce the return type now.
8001+
// C++2a [class.spaceship]p2:
8002+
// The return type is deduced as the common comparison type of R0, R1, ...
8003+
if (DCK == DefaultedComparisonKind::ThreeWay &&
8004+
FD->getDeclaredReturnType()->isUndeducedAutoType()) {
8005+
SourceLocation RetLoc = FD->getReturnTypeSourceRange().getBegin();
8006+
if (RetLoc.isInvalid())
8007+
RetLoc = FD->getBeginLoc();
8008+
// FIXME: Should we really care whether we have the complete type and the
8009+
// 'enumerator' constants here? A forward declaration seems sufficient.
8010+
QualType Cat = CheckComparisonCategoryType(Info.Category, RetLoc);
8011+
if (Cat.isNull())
8012+
return true;
8013+
Context.adjustDeducedFunctionResultType(
8014+
FD, SubstAutoType(FD->getDeclaredReturnType(), Cat));
8015+
}
79598016

79608017
// C++2a [dcl.fct.def.default]p3 [P2002R0]:
79618018
// An explicitly-defaulted function that is not defined as deleted may be

clang/lib/Sema/SemaExpr.cpp

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10521,8 +10521,6 @@ static QualType checkArithmeticOrEnumeralThreeWayCompare(Sema &S,
1052110521
ExprResult &LHS,
1052210522
ExprResult &RHS,
1052310523
SourceLocation Loc) {
10524-
using CCT = ComparisonCategoryType;
10525-
1052610524
QualType LHSType = LHS.get()->getType();
1052710525
QualType RHSType = RHS.get()->getType();
1052810526
// Dig out the original argument type and expression before implicit casts
@@ -10582,6 +10580,8 @@ static QualType checkArithmeticOrEnumeralThreeWayCompare(Sema &S,
1058210580
return S.InvalidOperands(Loc, LHS, RHS);
1058310581
assert(Type->isArithmeticType() || Type->isEnumeralType());
1058410582

10583+
// FIXME: Reject complex types, consistent with P1959R0.
10584+
1058510585
bool HasNarrowing = checkThreeWayNarrowingConversion(
1058610586
S, Type, LHS.get(), LHSType, LHS.get()->getBeginLoc());
1058710587
HasNarrowing |= checkThreeWayNarrowingConversion(S, Type, RHS.get(), RHSType,
@@ -10591,20 +10591,8 @@ static QualType checkArithmeticOrEnumeralThreeWayCompare(Sema &S,
1059110591

1059210592
assert(!Type.isNull() && "composite type for <=> has not been set");
1059310593

10594-
auto TypeKind = [&]() {
10595-
if (const ComplexType *CT = Type->getAs<ComplexType>()) {
10596-
if (CT->getElementType()->hasFloatingRepresentation())
10597-
return CCT::WeakEquality;
10598-
return CCT::StrongEquality;
10599-
}
10600-
if (Type->isIntegralOrEnumerationType())
10601-
return CCT::StrongOrdering;
10602-
if (Type->hasFloatingRepresentation())
10603-
return CCT::PartialOrdering;
10604-
llvm_unreachable("other types are unimplemented");
10605-
}();
10606-
10607-
return S.CheckComparisonCategoryType(TypeKind, Loc);
10594+
return S.CheckComparisonCategoryType(
10595+
*getComparisonCategoryForBuiltinCmp(Type), Loc);
1060810596
}
1060910597

1061010598
static QualType checkArithmeticOrEnumeralCompare(Sema &S, ExprResult &LHS,
@@ -10729,34 +10717,20 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS,
1072910717
QualType CompositeTy = LHS.get()->getType();
1073010718
assert(!CompositeTy->isReferenceType());
1073110719

10732-
auto buildResultTy = [&](ComparisonCategoryType Kind) {
10733-
return CheckComparisonCategoryType(Kind, Loc);
10734-
};
10735-
10736-
// C++2a [expr.spaceship]p7: If the composite pointer type is a function
10737-
// pointer type, a pointer-to-member type, or std::nullptr_t, the
10738-
// result is of type std::strong_equality
10739-
if (CompositeTy->isFunctionPointerType() ||
10740-
CompositeTy->isMemberPointerType() || CompositeTy->isNullPtrType())
10741-
// FIXME: consider making the function pointer case produce
10742-
// strong_ordering not strong_equality, per P0946R0-Jax18 discussion
10743-
// and direction polls
10744-
return buildResultTy(ComparisonCategoryType::StrongEquality);
10745-
10746-
// C++2a [expr.spaceship]p8: If the composite pointer type is an object
10747-
// pointer type, p <=> q is of type std::strong_ordering.
10748-
if (CompositeTy->isPointerType()) {
10720+
Optional<ComparisonCategoryType> CCT =
10721+
getComparisonCategoryForBuiltinCmp(CompositeTy);
10722+
if (!CCT)
10723+
return InvalidOperands(Loc, LHS, RHS);
10724+
10725+
if (CompositeTy->isPointerType() && LHSIsNull != RHSIsNull) {
1074910726
// P0946R0: Comparisons between a null pointer constant and an object
1075010727
// pointer result in std::strong_equality
10751-
if (LHSIsNull != RHSIsNull)
10752-
return buildResultTy(ComparisonCategoryType::StrongEquality);
10753-
return buildResultTy(ComparisonCategoryType::StrongOrdering);
10728+
// FIXME: Reject this, consistent with P1959R0 + P0946R0.
10729+
CCT = ComparisonCategoryType::StrongEquality;
1075410730
}
10755-
// C++2a [expr.spaceship]p9: Otherwise, the program is ill-formed.
10756-
// TODO: Extend support for operator<=> to ObjC types.
10757-
return InvalidOperands(Loc, LHS, RHS);
10758-
};
1075910731

10732+
return CheckComparisonCategoryType(*CCT, Loc);
10733+
};
1076010734

1076110735
if (!IsRelational && LHSIsNull != RHSIsNull) {
1076210736
bool IsEquality = Opc == BO_EQ;

0 commit comments

Comments
 (0)