summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--clang/include/clang/AST/Decl.h10
-rw-r--r--clang/include/clang/AST/DeclCXX.h10
-rw-r--r--clang/include/clang/Basic/DiagnosticSemaKinds.td5
-rw-r--r--clang/include/clang/Sema/Sema.h9
-rw-r--r--clang/lib/AST/ExprConstant.cpp30
-rw-r--r--clang/lib/Sema/SemaDeclCXX.cpp341
-rw-r--r--clang/lib/Sema/SemaExpr.cpp9
-rw-r--r--clang/lib/Sema/SemaOverload.cpp126
-rw-r--r--clang/lib/Sema/SemaTemplateInstantiate.cpp20
-rw-r--r--clang/test/CXX/class/class.compare/class.compare.default/p2.cpp2
-rw-r--r--clang/test/CXX/class/class.compare/class.compare.default/p5.cpp45
-rw-r--r--clang/test/CXX/class/class.compare/class.eq/p2.cpp18
-rw-r--r--clang/test/CXX/class/class.compare/class.eq/p3.cpp11
-rw-r--r--clang/test/CXX/class/class.compare/class.rel/p2.cpp23
-rw-r--r--clang/test/CXX/class/class.compare/class.spaceship/p1.cpp108
-rw-r--r--clang/test/CXX/class/class.compare/class.spaceship/p3.cpp35
16 files changed, 743 insertions, 59 deletions
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index f4913540bab..2d1a30657cb 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -2115,6 +2115,16 @@ public:
FunctionDeclBits.IsExplicitlyDefaulted = ED;
}
+ /// True if this method is user-declared and was not
+ /// deleted or defaulted on its first declaration.
+ bool isUserProvided() const {
+ auto *DeclAsWritten = this;
+ if (FunctionDecl *Pattern = getTemplateInstantiationPattern())
+ DeclAsWritten = Pattern;
+ return !(DeclAsWritten->isDeleted() ||
+ DeclAsWritten->getCanonicalDecl()->isDefaulted());
+ }
+
/// Whether falling off this function implicitly returns null/zero.
/// If a more specific implicit return value is required, front-ends
/// should synthesize the appropriate return statements.
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 0f2018fb9e8..0043ce1a92d 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1995,16 +1995,6 @@ public:
return const_cast<CXXMethodDecl*>(this)->getMostRecentDecl();
}
- /// True if this method is user-declared and was not
- /// deleted or defaulted on its first declaration.
- bool isUserProvided() const {
- auto *DeclAsWritten = this;
- if (auto *Pattern = getTemplateInstantiationPattern())
- DeclAsWritten = cast<CXXMethodDecl>(Pattern);
- return !(DeclAsWritten->isDeleted() ||
- DeclAsWritten->getCanonicalDecl()->isDefaulted());
- }
-
void addOverriddenMethod(const CXXMethodDecl *MD);
using method_iterator = const CXXMethodDecl *const *;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index dcfc8fb5de9..939287014d9 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1732,7 +1732,10 @@ def note_ivar_decl : Note<"instance variable is declared here">;
def note_bitfield_decl : Note<"bit-field is declared here">;
def note_implicit_param_decl : Note<"%0 is an implicit parameter">;
def note_member_synthesized_at : Note<
- "in implicit %sub{select_special_member_kind}0 for %1 "
+ "in %select{implicit|defaulted}0 %sub{select_special_member_kind}1 for %2 "
+ "first required here">;
+def note_comparison_synthesized_at : Note<
+ "in defaulted %sub{select_defaulted_comparison_kind}0 for %1 "
"first required here">;
def err_missing_default_ctor : Error<
"%select{constructor for %1 must explicitly initialize the|"
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ed1f1370b33..2b0db07e6ba 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3319,7 +3319,12 @@ public:
const UnresolvedSetImpl &Fns,
Expr *LHS, Expr *RHS,
bool RequiresADL = true,
- bool AllowRewrittenCandidates = true);
+ bool AllowRewrittenCandidates = true,
+ FunctionDecl *DefaultedFn = nullptr);
+ ExprResult BuildSynthesizedThreeWayComparison(SourceLocation OpLoc,
+ const UnresolvedSetImpl &Fns,
+ Expr *LHS, Expr *RHS,
+ FunctionDecl *DefaultedFn);
ExprResult CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
SourceLocation RLoc,
@@ -6516,6 +6521,8 @@ public:
bool CheckExplicitlyDefaultedComparison(FunctionDecl *MD,
DefaultedComparisonKind DCK);
+ void DefineDefaultedComparison(SourceLocation Loc, FunctionDecl *FD,
+ DefaultedComparisonKind DCK);
//===--------------------------------------------------------------------===//
// C++ Derived Classes
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 3151ec044cb..5aa151984e5 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -6830,6 +6830,36 @@ public:
return StmtVisitorTy::Visit(Source);
}
+ bool VisitPseudoObjectExpr(const PseudoObjectExpr *E) {
+ for (const Expr *SemE : E->semantics()) {
+ if (auto *OVE = dyn_cast<OpaqueValueExpr>(SemE)) {
+ // FIXME: We can't handle the case where an OpaqueValueExpr is also the
+ // result expression: there could be two different LValues that would
+ // refer to the same object in that case, and we can't model that.
+ if (SemE == E->getResultExpr())
+ return Error(E);
+
+ // Unique OVEs get evaluated if and when we encounter them when
+ // emitting the rest of the semantic form, rather than eagerly.
+ if (OVE->isUnique())
+ continue;
+
+ LValue LV;
+ if (!Evaluate(Info.CurrentCall->createTemporary(
+ OVE, getStorageType(Info.Ctx, OVE), false, LV),
+ Info, OVE->getSourceExpr()))
+ return false;
+ } else if (SemE == E->getResultExpr()) {
+ if (!StmtVisitorTy::Visit(SemE))
+ return false;
+ } else {
+ if (!EvaluateIgnoredValue(Info, SemE))
+ return false;
+ }
+ }
+ return true;
+ }
+
bool VisitCallExpr(const CallExpr *E) {
APValue Result;
if (!handleCallExpr(E, Result, nullptr))
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index ba516b66608..c8b95983f03 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -7085,7 +7085,8 @@ namespace {
///
/// This is accomplished by performing two visitation steps over the eventual
/// body of the function.
-template<typename Derived, typename Result, typename Subobject>
+template<typename Derived, typename ResultList, typename Result,
+ typename Subobject>
class DefaultedComparisonVisitor {
public:
using DefaultedComparisonKind = Sema::DefaultedComparisonKind;
@@ -7094,45 +7095,54 @@ public:
DefaultedComparisonKind DCK)
: S(S), RD(RD), FD(FD), DCK(DCK) {}
- Result visit() {
+ ResultList visit() {
// The type of an lvalue naming a parameter of this function.
QualType ParamLvalType =
FD->getParamDecl(0)->getType().getNonReferenceType();
+ ResultList Results;
+
switch (DCK) {
case DefaultedComparisonKind::None:
llvm_unreachable("not a defaulted comparison");
case DefaultedComparisonKind::Equal:
case DefaultedComparisonKind::ThreeWay:
- return getDerived().visitSubobjects(RD, ParamLvalType.getQualifiers());
+ getDerived().visitSubobjects(Results, RD, ParamLvalType.getQualifiers());
+ return Results;
case DefaultedComparisonKind::NotEqual:
case DefaultedComparisonKind::Relational:
- return getDerived().visitExpandedSubobject(
- ParamLvalType, getDerived().getCompleteObject());
+ Results.add(getDerived().visitExpandedSubobject(
+ ParamLvalType, getDerived().getCompleteObject()));
+ return Results;
}
}
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 [...]
+ /// Visit the expanded list of subobjects of the given type, as specified in
+ /// C++2a [class.compare.default].
+ ///
+ /// \return \c true if the ResultList object said we're done, \c false if not.
+ bool visitSubobjects(ResultList &Results, CXXRecordDecl *Record,
+ Qualifiers Quals) {
+ // C++2a [class.compare.default]p4:
+ // The direct base class subobjects of C
for (CXXBaseSpecifier &Base : Record->bases())
- if (R.add(getDerived().visitSubobject(
+ if (Results.add(getDerived().visitSubobject(
S.Context.getQualifiedType(Base.getType(), Quals),
getDerived().getBase(&Base))))
- return R;
- // followed by the non-static data members of C [...]
+ return true;
+
+ // 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;
+ if (visitSubobjects(Results, Field->getType()->getAsCXXRecordDecl(),
+ Quals))
+ return true;
continue;
}
@@ -7143,12 +7153,13 @@ protected:
QualType FieldType =
S.Context.getQualifiedType(Field->getType(), FieldQuals);
- if (R.add(getDerived().visitSubobject(FieldType,
- getDerived().getField(Field))))
- return R;
+ if (Results.add(getDerived().visitSubobject(
+ FieldType, getDerived().getField(Field))))
+ return true;
}
+
// form a list of subobjects.
- return R;
+ return false;
}
Result visitSubobject(QualType Type, Subobject Subobj) {
@@ -7200,6 +7211,7 @@ struct DefaultedComparisonSubobject {
class DefaultedComparisonAnalyzer
: public DefaultedComparisonVisitor<DefaultedComparisonAnalyzer,
DefaultedComparisonInfo,
+ DefaultedComparisonInfo,
DefaultedComparisonSubobject> {
public:
enum DiagnosticKind { NoDiagnostics, ExplainDeleted, ExplainConstexpr };
@@ -7407,6 +7419,260 @@ private:
return R;
}
};
+
+/// A list of statements.
+struct StmtListResult {
+ bool IsInvalid = false;
+ llvm::SmallVector<Stmt*, 16> Stmts;
+
+ bool add(const StmtResult &S) {
+ IsInvalid |= S.isInvalid();
+ if (IsInvalid)
+ return true;
+ Stmts.push_back(S.get());
+ return false;
+ }
+};
+
+/// A visitor over the notional body of a defaulted comparison that synthesizes
+/// the actual body.
+class DefaultedComparisonSynthesizer
+ : public DefaultedComparisonVisitor<DefaultedComparisonSynthesizer,
+ StmtListResult, StmtResult,
+ std::pair<ExprResult, ExprResult>> {
+ SourceLocation Loc;
+
+public:
+ using Base = DefaultedComparisonVisitor;
+ using ExprPair = std::pair<ExprResult, ExprResult>;
+
+ friend Base;
+
+ DefaultedComparisonSynthesizer(Sema &S, CXXRecordDecl *RD, FunctionDecl *FD,
+ DefaultedComparisonKind DCK,
+ SourceLocation BodyLoc)
+ : Base(S, RD, FD, DCK), Loc(BodyLoc) {}
+
+ /// Build a suitable function body for this defaulted comparison operator.
+ StmtResult build() {
+ Sema::CompoundScopeRAII CompoundScope(S);
+
+ StmtListResult Stmts = visit();
+ if (Stmts.IsInvalid)
+ return StmtError();
+
+ ExprResult RetVal;
+ switch (DCK) {
+ case DefaultedComparisonKind::None:
+ llvm_unreachable("not a defaulted comparison");
+
+ case DefaultedComparisonKind::Equal:
+ // C++2a [class.eq]p3:
+ // [...] compar[e] the corresponding elements [...] until the first
+ // index i where xi == yi yields [...] false. If no such index exists,
+ // V is true. Otherwise, V is false.
+ //
+ // Join the comparisons with '&&'s and return the result. Use a right
+ // fold because that short-circuits more naturally.
+ for (Stmt *EAsStmt : llvm::reverse(Stmts.Stmts)) {
+ Expr *E = cast<Expr>(EAsStmt);
+ if (RetVal.isUnset()) {
+ RetVal = E;
+ continue;
+ }
+ RetVal = S.CreateBuiltinBinOp(Loc, BO_LAnd, E, RetVal.get());
+ if (RetVal.isInvalid())
+ return StmtError();
+ }
+ // If no such index exists, V is true.
+ if (RetVal.isUnset())
+ RetVal = S.ActOnCXXBoolLiteral(Loc, tok::kw_true);
+ Stmts.Stmts.clear();
+ break;
+
+ case DefaultedComparisonKind::ThreeWay: {
+ // Per C++2a [class.spaceship]p3, as a fallback add:
+ // return static_cast<R>(std::strong_ordering::equal);
+ QualType StrongOrdering = S.CheckComparisonCategoryType(
+ ComparisonCategoryType::StrongOrdering, Loc);
+ if (StrongOrdering.isNull())
+ return StmtError();
+ VarDecl *EqualVD = S.Context.CompCategories.getInfoForType(StrongOrdering)
+ .getValueInfo(ComparisonCategoryResult::Equal)
+ ->VD;
+ RetVal = S.BuildDeclarationNameExpr(
+ CXXScopeSpec(), DeclarationNameInfo(), EqualVD);
+ if (RetVal.isInvalid())
+ return StmtError();
+ RetVal = buildStaticCastToR(RetVal.get());
+ break;
+ }
+
+ case DefaultedComparisonKind::NotEqual:
+ case DefaultedComparisonKind::Relational:
+ RetVal = cast<Expr>(Stmts.Stmts.pop_back_val());
+ break;
+ }
+
+ // Build the final return statement.
+ if (RetVal.isInvalid())
+ return StmtError();
+ StmtResult ReturnStmt = S.BuildReturnStmt(Loc, RetVal.get());
+ if (ReturnStmt.isInvalid())
+ return StmtError();
+ Stmts.Stmts.push_back(ReturnStmt.get());
+
+ return S.ActOnCompoundStmt(Loc, Loc, Stmts.Stmts, /*IsStmtExpr=*/false);
+ }
+
+private:
+ ExprResult getParam(unsigned I) {
+ ParmVarDecl *PD = FD->getParamDecl(I);
+ return S.BuildDeclarationNameExpr(
+ CXXScopeSpec(), DeclarationNameInfo(PD->getDeclName(), Loc), PD);
+ }
+
+ ExprPair getCompleteObject() {
+ unsigned Param = 0;
+ ExprResult LHS;
+ if (isa<CXXMethodDecl>(FD)) {
+ // LHS is '*this'.
+ LHS = S.ActOnCXXThis(Loc);
+ if (!LHS.isInvalid())
+ LHS = S.CreateBuiltinUnaryOp(Loc, UO_Deref, LHS.get());
+ } else {
+ LHS = getParam(Param++);
+ }
+ ExprResult RHS = getParam(Param++);
+ assert(Param == FD->getNumParams());
+ return {LHS, RHS};
+ }
+
+ ExprPair getBase(CXXBaseSpecifier *Base) {
+ ExprPair Obj = getCompleteObject();
+ if (Obj.first.isInvalid() || Obj.second.isInvalid())
+ return {ExprError(), ExprError()};
+ CXXCastPath Path = {Base};
+ return {S.ImpCastExprToType(Obj.first.get(), Base->getType(),
+ CK_DerivedToBase, VK_LValue, &Path),
+ S.ImpCastExprToType(Obj.second.get(), Base->getType(),
+ CK_DerivedToBase, VK_LValue, &Path)};
+ }
+
+ ExprPair getField(FieldDecl *Field) {
+ ExprPair Obj = getCompleteObject();
+ if (Obj.first.isInvalid() || Obj.second.isInvalid())
+ return {ExprError(), ExprError()};
+
+ DeclAccessPair Found = DeclAccessPair::make(Field, Field->getAccess());
+ DeclarationNameInfo NameInfo(Field->getDeclName(), Loc);
+ return {S.BuildFieldReferenceExpr(Obj.first.get(), /*IsArrow=*/false, Loc,
+ CXXScopeSpec(), Field, Found, NameInfo),
+ S.BuildFieldReferenceExpr(Obj.second.get(), /*IsArrow=*/false, Loc,
+ CXXScopeSpec(), Field, Found, NameInfo)};
+ }
+
+ // FIXME: When expanding a subobject, register a note in the code synthesis
+ // stack to say which subobject we're comparing.
+
+ // FIXME: Build a loop for an array subobject.
+
+ StmtResult visitExpandedSubobject(QualType Type, ExprPair Obj) {
+ UnresolvedSet<4> Fns; // FIXME: Track this.
+
+ if (Obj.first.isInvalid() || Obj.second.isInvalid())
+ return StmtError();
+
+ OverloadedOperatorKind OO = FD->getOverloadedOperator();
+ ExprResult Op = S.CreateOverloadedBinOp(
+ Loc, BinaryOperator::getOverloadedOpcode(OO), Fns,
+ Obj.first.get(), Obj.second.get(), /*PerformADL=*/true,
+ /*AllowRewrittenCandidates=*/true, FD);
+ if (Op.isInvalid())
+ return StmtError();
+
+ switch (DCK) {
+ case DefaultedComparisonKind::None:
+ llvm_unreachable("not a defaulted comparison");
+
+ case DefaultedComparisonKind::Equal:
+ // Per C++2a [class.eq]p2, each comparison is individually contextually
+ // converted to bool.
+ Op = S.PerformContextuallyConvertToBool(Op.get());
+ if (Op.isInvalid())
+ return StmtError();
+ return Op.get();
+
+ case DefaultedComparisonKind::ThreeWay: {
+ // Per C++2a [class.spaceship]p3, form:
+ // if (R cmp = static_cast<R>(op); cmp != 0)
+ // return cmp;
+ QualType R = FD->getReturnType();
+ Op = buildStaticCastToR(Op.get());
+ if (Op.isInvalid())
+ return StmtError();
+
+ // R cmp = ...;
+ IdentifierInfo *Name = &S.Context.Idents.get("cmp");
+ VarDecl *VD =
+ VarDecl::Create(S.Context, S.CurContext, Loc, Loc, Name, R,
+ S.Context.getTrivialTypeSourceInfo(R, Loc), SC_None);
+ S.AddInitializerToDecl(VD, Op.get(), /*DirectInit=*/false);
+ Stmt *InitStmt = new (S.Context) DeclStmt(DeclGroupRef(VD), Loc, Loc);
+
+ // cmp != 0
+ ExprResult VDRef = S.BuildDeclarationNameExpr(
+ CXXScopeSpec(), DeclarationNameInfo(Name, Loc), VD);
+ if (VDRef.isInvalid())
+ return StmtError();
+ llvm::APInt ZeroVal(S.Context.getIntWidth(S.Context.IntTy), 0);
+ Expr *Zero =
+ IntegerLiteral::Create(S.Context, ZeroVal, S.Context.IntTy, Loc);
+ ExprResult Comp = S.CreateOverloadedBinOp(Loc, BO_NE, Fns, VDRef.get(),
+ Zero, true, true, FD);
+ if (Comp.isInvalid())
+ return StmtError();
+ Sema::ConditionResult Cond = S.ActOnCondition(
+ nullptr, Loc, Comp.get(), Sema::ConditionKind::Boolean);
+ if (Cond.isInvalid())
+ return StmtError();
+
+ // return cmp;
+ VDRef = S.BuildDeclarationNameExpr(
+ CXXScopeSpec(), DeclarationNameInfo(Name, Loc), VD);
+ if (VDRef.isInvalid())
+ return StmtError();
+ StmtResult ReturnStmt = S.BuildReturnStmt(Loc, VDRef.get());
+ if (ReturnStmt.isInvalid())
+ return StmtError();
+
+ // if (...)
+ return S.ActOnIfStmt(Loc, /*IsConstexpr=*/false, InitStmt, Cond,
+ ReturnStmt.get(), /*ElseLoc=*/SourceLocation(),
+ /*Else=*/nullptr);
+ }
+
+ case DefaultedComparisonKind::NotEqual:
+ case DefaultedComparisonKind::Relational:
+ // C++2a [class.compare.secondary]p2:
+ // Otherwise, the operator function yields x @ y.
+ return Op.get();
+ }
+ }
+
+ /// Build "static_cast<R>(E)".
+ ExprResult buildStaticCastToR(Expr *E) {
+ QualType R = FD->getReturnType();
+ assert(!R->isUndeducedType() && "type should have been deduced already");
+
+ // Don't bother forming a no-op cast in the common case.
+ if (E->isRValue() && S.Context.hasSameType(E->getType(), R))
+ return E;
+ return S.BuildCXXNamedCast(Loc, tok::kw_static_cast,
+ S.Context.getTrivialTypeSourceInfo(R, Loc), E,
+ SourceRange(Loc, Loc), SourceRange(Loc, Loc));
+ }
+};
}
bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
@@ -7540,6 +7806,43 @@ bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
return false;
}
+void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD,
+ DefaultedComparisonKind DCK) {
+ assert(FD->isDefaulted() && !FD->isDeleted() &&
+ !FD->doesThisDeclarationHaveABody());
+ if (FD->willHaveBody() || FD->isInvalidDecl())
+ return;
+
+ SynthesizedFunctionScope Scope(*this, FD);
+
+ // The exception specification is needed because we are defining the
+ // function.
+ // FIXME: Handle this better. Computing the exception specification will
+ // eventually need the function body.
+ ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
+
+ // Add a context note for diagnostics produced after this point.
+ Scope.addContextNote(UseLoc);
+
+ // Build and set up the function body.
+ {
+ CXXRecordDecl *RD = cast<CXXRecordDecl>(FD->getLexicalParent());
+ SourceLocation BodyLoc =
+ FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
+ StmtResult Body =
+ DefaultedComparisonSynthesizer(*this, RD, FD, DCK, BodyLoc).build();
+ if (Body.isInvalid()) {
+ FD->setInvalidDecl();
+ return;
+ }
+ FD->setBody(Body.get());
+ FD->markUsed(Context);
+ }
+
+ if (ASTMutationListener *L = getASTMutationListener())
+ L->CompletedImplicitDefinition(FD);
+}
+
void Sema::CheckDelayedMemberExceptionSpecs() {
decltype(DelayedOverridingExceptionSpecChecks) Overriding;
decltype(DelayedEquivalentExceptionSpecChecks) Equivalent;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 5eeeba3c2d1..b97352e27e1 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -15416,9 +15416,8 @@ static OdrUseContext isOdrUseContext(Sema &SemaRef) {
}
static bool isImplicitlyDefinableConstexprFunction(FunctionDecl *Func) {
- CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(Func);
return Func->isConstexpr() &&
- (Func->isImplicitlyInstantiable() || (MD && !MD->isUserProvided()));
+ (Func->isImplicitlyInstantiable() || !Func->isUserProvided());
}
/// Mark a function referenced, and check whether it is odr-used
@@ -15566,6 +15565,12 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
MarkVTableUsed(Loc, MethodDecl->getParent());
}
+ if (Func->isDefaulted() && !Func->isDeleted()) {
+ DefaultedComparisonKind DCK = getDefaultedComparisonKind(Func);
+ if (DCK != DefaultedComparisonKind::None)
+ DefineDefaultedComparison(Loc, Func, DCK);
+ }
+
// Implicit instantiation of function templates and member functions of
// class templates.
if (Func->isImplicitlyInstantiable()) {
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 27e1101b482..344e54b7f3f 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -12835,11 +12835,19 @@ void Sema::LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet,
///
/// \param LHS Left-hand argument.
/// \param RHS Right-hand argument.
+/// \param PerformADL Whether to consider operator candidates found by ADL.
+/// \param AllowRewrittenCandidates Whether to consider candidates found by
+/// C++20 operator rewrites.
+/// \param DefaultedFn If we are synthesizing a defaulted operator function,
+/// the function in question. Such a function is never a candidate in
+/// our overload resolution. This also enables synthesizing a three-way
+/// comparison from < and == as described in C++20 [class.spaceship]p1.
ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
BinaryOperatorKind Opc,
const UnresolvedSetImpl &Fns, Expr *LHS,
Expr *RHS, bool PerformADL,
- bool AllowRewrittenCandidates) {
+ bool AllowRewrittenCandidates,
+ FunctionDecl *DefaultedFn) {
Expr *Args[2] = { LHS, RHS };
LHS=RHS=nullptr; // Please use only Args instead of LHS/RHS couple
@@ -12906,6 +12914,8 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
OverloadCandidateSet CandidateSet(
OpLoc, OverloadCandidateSet::CSK_Operator,
OverloadCandidateSet::OperatorRewriteInfo(Op, AllowRewrittenCandidates));
+ if (DefaultedFn)
+ CandidateSet.exclude(DefaultedFn);
LookupOverloadedBinOp(CandidateSet, Op, Fns, Args, PerformADL);
bool HadMultipleCandidates = (CandidateSet.size() > 1);
@@ -13113,6 +13123,15 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
if (Opc == BO_Comma)
break;
+ // When defaulting an 'operator<=>', we can try to synthesize a three-way
+ // compare result using '==' and '<'.
+ if (DefaultedFn && Opc == BO_Cmp) {
+ ExprResult E = BuildSynthesizedThreeWayComparison(OpLoc, Fns, Args[0],
+ Args[1], DefaultedFn);
+ if (E.isInvalid() || E.isUsable())
+ return E;
+ }
+
// For class as left operand for assignment or compound assignment
// operator do not fall through to handling in built-in, but report that
// no overloaded assignment operator found
@@ -13194,6 +13213,111 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
}
+ExprResult Sema::BuildSynthesizedThreeWayComparison(
+ SourceLocation OpLoc, const UnresolvedSetImpl &Fns, Expr *LHS, Expr *RHS,
+ FunctionDecl *DefaultedFn) {
+ const ComparisonCategoryInfo *Info =
+ Context.CompCategories.lookupInfoForType(DefaultedFn->getReturnType());
+ // If we're not producing a known comparison category type, we can't
+ // synthesize a three-way comparison. Let the caller diagnose this.
+ if (!Info)
+ return ExprResult((Expr*)nullptr);
+
+ // If we ever want to perform this synthesis more generally, we will need to
+ // apply the temporary materialization conversion to the operands.
+ assert(LHS->isGLValue() && RHS->isGLValue() &&
+ "cannot use prvalue expressions more than once");
+ Expr *OrigLHS = LHS;
+ Expr *OrigRHS = RHS;
+
+ // Replace the LHS and RHS with OpaqueValueExprs; we're going to refer to
+ // each of them multiple times below.
+ LHS = new (Context)
+ OpaqueValueExpr(LHS->getExprLoc(), LHS->getType(), LHS->getValueKind(),
+ LHS->getObjectKind(), LHS);
+ RHS = new (Context)
+ OpaqueValueExpr(RHS->getExprLoc(), RHS->getType(), RHS->getValueKind(),
+ RHS->getObjectKind(), RHS);
+
+ ExprResult Eq = CreateOverloadedBinOp(OpLoc, BO_EQ, Fns, LHS, RHS, true, true,
+ DefaultedFn);
+ if (Eq.isInvalid())
+ return ExprError();
+
+ ExprResult Less;
+ if (Info->isOrdered()) {
+ Less = CreateOverloadedBinOp(OpLoc, BO_LT, Fns, LHS, RHS, true, true,
+ DefaultedFn);
+ if (Less.isInvalid())
+ return ExprError();
+ }
+
+ ExprResult Greater;
+ if (Info->isOrdered()) {
+ Greater = CreateOverloadedBinOp(OpLoc, BO_LT, Fns, RHS, LHS, true, true,
+ DefaultedFn);
+ if (Greater.isInvalid())
+ return ExprError();
+ }
+
+ // Form the list of comparisons we're going to perform.
+ struct Comparison {
+ ExprResult Cmp;
+ ComparisonCategoryResult Result;
+ } Comparisons[4] =
+ { {Eq, Info->isStrong() ? ComparisonCategoryResult::Equal
+ : ComparisonCategoryResult::Equivalent},
+ {Less, ComparisonCategoryResult::Less},
+ {Greater, ComparisonCategoryResult::Greater},
+ {ExprResult(), ComparisonCategoryResult::Unordered},
+ };
+
+ int I;
+ if (Info->isEquality()) {
+ Comparisons[1].Result = Info->isStrong()
+ ? ComparisonCategoryResult::Nonequal
+ : ComparisonCategoryResult::Nonequivalent;
+ I = 1;
+ } else if (!Info->isPartial()) {
+ I = 2;
+ } else {
+ I = 3;
+ }
+
+ // Combine the comparisons with suitable conditional expressions.
+ ExprResult Result;
+ for (; I >= 0; --I) {
+ // Build a reference to the comparison category constant.
+ auto *VI = Info->lookupValueInfo(Comparisons[I].Result);
+ // FIXME: Missing a constant for a comparison category. Diagnose this?
+ if (!VI)
+ return ExprResult((Expr*)nullptr);
+ ExprResult ThisResult =
+ BuildDeclarationNameExpr(CXXScopeSpec(), DeclarationNameInfo(), VI->VD);
+ if (ThisResult.isInvalid())
+ return ExprError();
+
+ // Build a conditional unless this is the final case.
+ if (Result.get()) {
+ Result = ActOnConditionalOp(OpLoc, OpLoc, Comparisons[I].Cmp.get(),
+ ThisResult.get(), Result.get());
+ if (Result.isInvalid())
+ return ExprError();
+ } else {
+ Result = ThisResult;
+ }
+ }
+
+ // Build a PseudoObjectExpr to model the rewriting of an <=> operator, and to
+ // bind the OpaqueValueExprs before they're (repeatedly) used.
+ Expr *SyntacticForm = new (Context)
+ BinaryOperator(OrigLHS, OrigRHS, BO_Cmp, Result.get()->getType(),
+ Result.get()->getValueKind(),
+ Result.get()->getObjectKind(), OpLoc, FPFeatures);
+ Expr *SemanticForm[] = {LHS, RHS, Result.get()};
+ return PseudoObjectExpr::Create(Context, SyntacticForm, SemanticForm, 2);
+}
+
ExprResult
Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
SourceLocation RLoc,
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 2496c919311..4d54ec17b99 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -672,13 +672,23 @@ void Sema::PrintInstantiationStack() {
break;
case CodeSynthesisContext::DefiningSynthesizedFunction: {
- // FIXME: For synthesized members other than special members, produce a note.
- auto *MD = dyn_cast<CXXMethodDecl>(Active->Entity);
- auto CSM = MD ? getSpecialMember(MD) : CXXInvalid;
- if (CSM != CXXInvalid) {
+ // FIXME: For synthesized functions that are not defaulted,
+ // produce a note.
+ auto *FD = dyn_cast<FunctionDecl>(Active->Entity);
+ DefaultedFunctionKind DFK =
+ FD ? getDefaultedFunctionKind(FD) : DefaultedFunctionKind();
+ if (DFK.isSpecialMember()) {
+ auto *MD = cast<CXXMethodDecl>(FD);
Diags.Report(Active->PointOfInstantiation,
diag::note_member_synthesized_at)
- << CSM << Context.getTagDeclType(MD->getParent());
+ << MD->isExplicitlyDefaulted() << DFK.asSpecialMember()
+ << Context.getTagDeclType(MD->getParent());
+ } else if (DFK.isComparison()) {
+ Diags.Report(Active->PointOfInstantiation,
+ diag::note_comparison_synthesized_at)
+ << (int)DFK.asComparison()
+ << Context.getTagDeclType(
+ cast<CXXRecordDecl>(FD->getLexicalDeclContext()));
}
break;
}
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 bbc9060df30..cdffd445f7c 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
@@ -14,7 +14,7 @@ struct A2 {
bool operator==(const A2&) const;
bool operator!=(const A2&) const = default;
- bool operator<=>(const A2&) const;
+ int operator<=>(const A2&) const;
bool operator<(const A2&) const = default;
bool operator<=(const A2&) const = default;
bool operator>(const A2&) const = default;
diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp
new file mode 100644
index 00000000000..f863ed09bc6
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+// expected-no-diagnostics
+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};
+}
+
+// Check that we compare subobjects in the right order.
+struct Log {
+ char buff[8] = {};
+ int n = 0;
+ constexpr void add(char c) { buff[n++] = c; }
+ constexpr bool operator==(const char *p) const { return __builtin_strcmp(p, buff) == 0; }
+};
+
+template<char C> struct B {
+ Log *log;
+ constexpr bool operator==(const B&) const { log->add(C); return true; }
+ constexpr std::strong_ordering operator<=>(const B&) const { log->add(C); return {0}; }
+};
+
+struct C : B<'a'>, B<'b'> {
+ B<'c'> c;
+ B<'d'> d;
+ // FIXME: Test arrays once we handle them properly.
+
+ constexpr C(Log *p) : B<'a'>{p}, B<'b'>{p}, c{p}, d{p} {}
+
+ bool operator==(const C&) const = default;
+ std::strong_ordering operator<=>(const C&) const = default;
+};
+
+constexpr bool check(bool which) {
+ Log log;
+ C c(&log);
+ (void)(which ? c == c : c <=> c);
+ return log == "abcd";
+}
+static_assert(check(false));
+static_assert(check(true));
diff --git a/clang/test/CXX/class/class.compare/class.eq/p2.cpp b/clang/test/CXX/class/class.compare/class.eq/p2.cpp
index 1a515dee490..d53d071f36f 100644
--- a/clang/test/CXX/class/class.compare/class.eq/p2.cpp
+++ b/clang/test/CXX/class/class.compare/class.eq/p2.cpp
@@ -8,13 +8,16 @@ struct D {
// expected-note@+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 E {
+ E(const E &) = delete; // expected-note {{deleted}}
+ int operator==(E) const; // expected-note {{passing}}
+};
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}}
+ bool operator==(const X&) const = default; // #x expected-note 3{{deleted here}}
T t; // expected-note 2{{because there is no viable comparison function for member 't'}}
// expected-note@-1 {{because it would invoke a deleted comparison function for member 't'}}
};
@@ -31,10 +34,13 @@ void test() {
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>());
+ // FIXME: We would benefit from a note identifying the member of 'X' we were comparing here and below.
+ // expected-error@#x {{call to deleted constructor of 'E'}}
+ void(X<E>() == X<E>()); // expected-note {{in defaulted equality comparison operator for 'X<E>' first required here}}
+
+ // FIXME: We would benefit from a note pointing at the selected 'operator==' here.
+ // expected-error@#x {{value of type 'void' is not contextually convertible to 'bool'}}
+ void(X<F>() == X<F>()); // expected-note {{in defaulted equality comparison operator for 'X<F>' first required here}}
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.eq/p3.cpp b/clang/test/CXX/class/class.compare/class.eq/p3.cpp
new file mode 100644
index 00000000000..f58b9daa523
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.eq/p3.cpp
@@ -0,0 +1,11 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+struct A {
+ int a, b, c;
+ bool operator==(const A&) const = default;
+};
+
+static_assert(A{1, 2, 3} == A{1, 2, 3});
+static_assert(A{1, 2, 3} == A{0, 2, 3}); // expected-error {{failed}}
+static_assert(A{1, 2, 3} == A{1, 0, 3}); // expected-error {{failed}}
+static_assert(A{1, 2, 3} == A{1, 2, 0}); // expected-error {{failed}}
diff --git a/clang/test/CXX/class/class.compare/class.rel/p2.cpp b/clang/test/CXX/class/class.compare/class.rel/p2.cpp
index d81c9634131..2abe7fb8d79 100644
--- a/clang/test/CXX/class/class.compare/class.rel/p2.cpp
+++ b/clang/test/CXX/class/class.compare/class.rel/p2.cpp
@@ -2,16 +2,23 @@
namespace Rel {
struct A {
- int operator<=>(A) const;
+ int n;
+ constexpr int operator<=>(A a) const { return n - a.n; }
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();
+ static_assert(A{0} < A{1});
+ static_assert(A{1} < A{1}); // expected-error {{failed}}
+ static_assert(A{0} <= A{1});
+ static_assert(A{1} <= A{1});
+ static_assert(A{2} <= A{1}); // expected-error {{failed}}
+ static_assert(A{1} > A{0});
+ static_assert(A{1} > A{1}); // expected-error {{failed}}
+ static_assert(A{1} >= A{0});
+ static_assert(A{1} >= A{1});
+ static_assert(A{1} >= A{2}); // expected-error {{failed}}
struct B {
bool operator<=>(B) const = delete; // expected-note 4{{deleted here}} expected-note-re 8{{candidate {{.*}} deleted}}
@@ -37,10 +44,12 @@ namespace Rel {
// Under P2002R0, operator!= follows these rules too.
namespace NotEqual {
struct A {
- bool operator==(A) const;
+ int n;
+ constexpr bool operator==(A a) const { return n == a.n; }
friend bool operator!=(const A&, const A&) = default;
};
- bool a = A() != A();
+ static_assert(A{1} != A{2});
+ static_assert(A{1} != A{1}); // expected-error {{failed}}
struct B {
bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}}
diff --git a/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
index 928fe203615..fafa99ff5cf 100644
--- a/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
+++ b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp
@@ -1,13 +1,39 @@
-// RUN: %clang_cc1 -std=c++2a -verify %s
+// RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions
namespace std {
- struct strong_ordering {
+ struct strong_ordering { // expected-note 3{{candidate}}
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};
+
+ struct weak_ordering {
+ int n;
+ constexpr weak_ordering(int n) : n(n) {}
+ constexpr weak_ordering(strong_ordering o) : n(o.n) {}
+ constexpr operator int() const { return n; }
+ static const weak_ordering less, equivalent, greater;
+ };
+ constexpr weak_ordering weak_ordering::less{-1},
+ weak_ordering::equivalent{0}, weak_ordering::greater{1};
+
+ struct partial_ordering {
+ double d;
+ constexpr partial_ordering(double d) : d(d) {}
+ constexpr partial_ordering(strong_ordering o) : d(o.n) {}
+ constexpr partial_ordering(weak_ordering o) : d(o.n) {}
+ constexpr operator double() const { return d; }
+ static const partial_ordering less, equivalent, greater, unordered;
+ };
+ constexpr partial_ordering partial_ordering::less{-1},
+ partial_ordering::equivalent{0}, partial_ordering::greater{1},
+ partial_ordering::unordered{__builtin_nan("")};
+
+ static_assert(!(partial_ordering::unordered < 0));
+ static_assert(!(partial_ordering::unordered == 0));
+ static_assert(!(partial_ordering::unordered > 0));
}
namespace Deletedness {
@@ -59,7 +85,7 @@ namespace Deletedness {
// 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}}
+ std::strong_ordering operator<=>(const Cmp&) const = default; // #cmp expected-note 5{{here}}
};
void use(...);
@@ -72,10 +98,80 @@ namespace Deletedness {
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
+ // FIXME: The following three errors are not very good.
+ // expected-error@#cmp {{value of type 'void' is not contextually convertible to 'bool'}}
+ Cmp<G1>() <=> Cmp<G1>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}Cmp<{{.*}}G1>' first required here}}j
+ // expected-error@#cmp {{value of type 'void' is not contextually convertible to 'bool'}}
+ Cmp<G2>() <=> Cmp<G2>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}Cmp<{{.*}}G2>' first required here}}j
+ // expected-error@#cmp {{no matching conversion for static_cast from 'void' to 'std::strong_ordering'}}
+ Cmp<H>() <=> Cmp<H>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}Cmp<{{.*}}H>' first required here}}j
0
);
}
}
+
+namespace Synthesis {
+ enum Result { False, True, Mu };
+
+ constexpr bool toBool(Result R) {
+ if (R == Mu) throw "should not ask this question";
+ return R == True;
+ }
+
+ struct Val {
+ Result equal, less;
+ constexpr bool operator==(const Val&) const { return toBool(equal); }
+ constexpr bool operator<(const Val&) const { return toBool(less); }
+ };
+
+ template<typename T> struct Cmp {
+ Val val;
+ friend T operator<=>(const Cmp&, const Cmp&) = default; // expected-note {{deleted}}
+ };
+
+ template<typename T> constexpr auto cmp(Result equal, Result less = Mu, Result reverse_less = Mu) {
+ return Cmp<T>{equal, less} <=> Cmp<T>{Mu, reverse_less};
+ }
+
+ static_assert(cmp<std::strong_ordering>(True) == 0);
+ static_assert(cmp<std::strong_ordering>(False, True) < 0);
+ static_assert(cmp<std::strong_ordering>(False, False) > 0);
+
+ static_assert(cmp<std::weak_ordering>(True) == 0);
+ static_assert(cmp<std::weak_ordering>(False, True) < 0);
+ static_assert(cmp<std::weak_ordering>(False, False) > 0);
+
+ static_assert(cmp<std::partial_ordering>(True) == 0);
+ static_assert(cmp<std::partial_ordering>(False, True) < 0);
+ static_assert(cmp<std::partial_ordering>(False, False, True) > 0);
+ static_assert(!(cmp<std::partial_ordering>(False, False, False) > 0));
+ static_assert(!(cmp<std::partial_ordering>(False, False, False) == 0));
+ static_assert(!(cmp<std::partial_ordering>(False, False, False) < 0));
+
+ // No synthesis is performed for a custom return type, even if it can be
+ // converted from a standard ordering.
+ struct custom_ordering {
+ custom_ordering(std::strong_ordering o);
+ };
+ void f(Cmp<custom_ordering> c) {
+ c <=> c; // expected-error {{deleted}}
+ }
+}
+
+namespace Preference {
+ struct A {
+ A(const A&) = delete; // expected-note {{deleted}}
+ // "usable" candidate that can't actually be called
+ friend void operator<=>(A, A); // expected-note {{passing}}
+ // Callable candidates for synthesis not considered.
+ friend bool operator==(A, A);
+ friend bool operator<(A, A);
+ };
+
+ struct B {
+ B();
+ A a;
+ std::strong_ordering operator<=>(const B&) const = default; // expected-error {{call to deleted constructor of 'Preference::A'}}
+ };
+ bool x = B() < B(); // expected-note {{in defaulted three-way comparison operator for 'Preference::B' first required here}}
+}
diff --git a/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp b/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp
new file mode 100644
index 00000000000..e4be892cd7c
--- /dev/null
+++ b/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp
@@ -0,0 +1,35 @@
+// 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};
+}
+
+struct A {
+ int a, b, c;
+ std::strong_ordering operator<=>(const A&) const = default;
+};
+
+static_assert(A{1, 2, 3} <= A{1, 2, 3});
+static_assert(A{1, 2, 3} <= A{0, 20, 3}); // expected-error {{failed}}
+static_assert(A{1, 2, 3} <= A{1, 0, 30}); // expected-error {{failed}}
+static_assert(A{1, 2, 3} <= A{1, 2, 0}); // expected-error {{failed}}
+
+struct reverse_compare {
+ int n;
+ constexpr explicit reverse_compare(std::strong_ordering o) : n(-o.n) {}
+ constexpr operator int() const { return n; }
+};
+
+struct B {
+ int a, b, c;
+ friend reverse_compare operator<=>(const B&, const B&) = default;
+};
+static_assert(B{1, 2, 3} >= B{1, 2, 3});
+static_assert(B{1, 2, 3} >= B{0, 20, 3}); // expected-error {{failed}}
+static_assert(B{1, 2, 3} >= B{1, 0, 30}); // expected-error {{failed}}
+static_assert(B{1, 2, 3} >= B{1, 2, 0}); // expected-error {{failed}}
OpenPOWER on IntegriCloud