Author: Richard Smith Date: 2019-12-09T14:54:06-08:00 New Revision: e6e6e34b95cfe03275943fde0db259cc7d57f4ad
URL: https://github.com/llvm/llvm-project/commit/e6e6e34b95cfe03275943fde0db259cc7d57f4ad DIFF: https://github.com/llvm/llvm-project/commit/e6e6e34b95cfe03275943fde0db259cc7d57f4ad.diff LOG: [c++20] Defaulted comparison support for array members. Added: Modified: clang/lib/Sema/SemaDeclCXX.cpp clang/test/CXX/class/class.compare/class.compare.default/p5.cpp clang/test/CXX/class/class.compare/class.eq/p2.cpp clang/test/CXX/class/class.compare/class.eq/p3.cpp clang/test/CXX/class/class.compare/class.spaceship/p1.cpp clang/test/CXX/class/class.compare/class.spaceship/p3.cpp Removed: ################################################################################ diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 28159b9c0812..1ee6ee5dcf12 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7442,6 +7442,7 @@ class DefaultedComparisonSynthesizer StmtListResult, StmtResult, std::pair<ExprResult, ExprResult>> { SourceLocation Loc; + unsigned ArrayDepth = 0; public: using Base = DefaultedComparisonVisitor; @@ -7467,29 +7468,56 @@ class DefaultedComparisonSynthesizer case DefaultedComparisonKind::None: llvm_unreachable("not a defaulted comparison"); - case DefaultedComparisonKind::Equal: + 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; + // fold (traversing the conditions right-to-left), because that + // short-circuits more naturally. + auto OldStmts = std::move(Stmts.Stmts); + Stmts.Stmts.clear(); + ExprResult CmpSoFar; + // Finish a particular comparison chain. + auto FinishCmp = [&] { + if (Expr *Prior = CmpSoFar.get()) { + // Convert the last expression to 'return ...;' + if (RetVal.isUnset() && Stmts.Stmts.empty()) + RetVal = CmpSoFar; + // Convert any prior comparison to 'if (!(...)) return false;' + else if (Stmts.add(buildIfNotCondReturnFalse(Prior))) + return true; + CmpSoFar = ExprResult(); + } + return false; + }; + for (Stmt *EAsStmt : llvm::reverse(OldStmts)) { + Expr *E = dyn_cast<Expr>(EAsStmt); + if (!E) { + // Found an array comparison. + if (FinishCmp() || Stmts.add(EAsStmt)) + return StmtError(); + continue; + } + + if (CmpSoFar.isUnset()) { + CmpSoFar = E; continue; } - RetVal = S.CreateBuiltinBinOp(Loc, BO_LAnd, E, RetVal.get()); - if (RetVal.isInvalid()) + CmpSoFar = S.CreateBuiltinBinOp(Loc, BO_LAnd, E, CmpSoFar.get()); + if (CmpSoFar.isInvalid()) return StmtError(); } + if (FinishCmp()) + return StmtError(); + std::reverse(Stmts.Stmts.begin(), Stmts.Stmts.end()); // 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: @@ -7579,7 +7607,100 @@ class DefaultedComparisonSynthesizer // 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 buildIfNotCondReturnFalse(ExprResult Cond) { + if (Cond.isInvalid()) + return StmtError(); + + ExprResult NotCond = S.CreateBuiltinUnaryOp(Loc, UO_LNot, Cond.get()); + if (NotCond.isInvalid()) + return StmtError(); + + ExprResult False = S.ActOnCXXBoolLiteral(Loc, tok::kw_false); + assert(!False.isInvalid() && "should never fail"); + StmtResult ReturnFalse = S.BuildReturnStmt(Loc, False.get()); + if (ReturnFalse.isInvalid()) + return StmtError(); + + return S.ActOnIfStmt(Loc, false, nullptr, + S.ActOnCondition(nullptr, Loc, NotCond.get(), + Sema::ConditionKind::Boolean), + ReturnFalse.get(), SourceLocation(), nullptr); + } + + StmtResult visitSubobjectArray(QualType Type, llvm::APInt Size, + ExprPair Subobj) { + QualType SizeType = S.Context.getSizeType(); + Size = Size.zextOrTrunc(S.Context.getTypeSize(SizeType)); + + // Build 'size_t i$n = 0'. + IdentifierInfo *IterationVarName = nullptr; + { + SmallString<8> Str; + llvm::raw_svector_ostream OS(Str); + OS << "i" << ArrayDepth; + IterationVarName = &S.Context.Idents.get(OS.str()); + } + VarDecl *IterationVar = VarDecl::Create( + S.Context, S.CurContext, Loc, Loc, IterationVarName, SizeType, + S.Context.getTrivialTypeSourceInfo(SizeType, Loc), SC_None); + llvm::APInt Zero(S.Context.getTypeSize(SizeType), 0); + IterationVar->setInit( + IntegerLiteral::Create(S.Context, Zero, SizeType, Loc)); + Stmt *Init = new (S.Context) DeclStmt(DeclGroupRef(IterationVar), Loc, Loc); + + auto IterRef = [&] { + ExprResult Ref = S.BuildDeclarationNameExpr( + CXXScopeSpec(), DeclarationNameInfo(IterationVarName, Loc), + IterationVar); + assert(!Ref.isInvalid() && "can't reference our own variable?"); + return Ref.get(); + }; + + // Build 'i$n != Size'. + ExprResult Cond = S.CreateBuiltinBinOp( + Loc, BO_NE, IterRef(), + IntegerLiteral::Create(S.Context, Size, SizeType, Loc)); + assert(!Cond.isInvalid() && "should never fail"); + + // Build '++i$n'. + ExprResult Inc = S.CreateBuiltinUnaryOp(Loc, UO_PreInc, IterRef()); + assert(!Inc.isInvalid() && "should never fail"); + + // Build 'a[i$n]' and 'b[i$n]'. + auto Index = [&](ExprResult E) { + if (E.isInvalid()) + return ExprError(); + return S.CreateBuiltinArraySubscriptExpr(E.get(), Loc, IterRef(), Loc); + }; + Subobj.first = Index(Subobj.first); + Subobj.second = Index(Subobj.second); + + // Compare the array elements. + ++ArrayDepth; + StmtResult Substmt = visitSubobject(Type, Subobj); + --ArrayDepth; + + if (Substmt.isInvalid()) + return StmtError(); + + // For the inner level of an 'operator==', build 'if (!cmp) return false;'. + // For outer levels or for an 'operator<=>' we already have a suitable + // statement that returns as necessary. + if (Expr *ElemCmp = dyn_cast<Expr>(Substmt.get())) { + assert(DCK == DefaultedComparisonKind::Equal && + "should have non-expression statement"); + Substmt = buildIfNotCondReturnFalse(ElemCmp); + if (Substmt.isInvalid()) + return StmtError(); + } + + // Build 'for (...) ...' + return S.ActOnForStmt(Loc, Loc, Init, + S.ActOnCondition(nullptr, Loc, Cond.get(), + Sema::ConditionKind::Boolean), + S.MakeFullDiscardedValueExpr(Inc.get()), Loc, + Substmt.get()); + } StmtResult visitExpandedSubobject(QualType Type, ExprPair Obj) { UnresolvedSet<4> Fns; // FIXME: Track this. 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 index f863ed09bc63..5489051a29c9 100644 --- a/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp +++ b/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp @@ -12,7 +12,7 @@ namespace std { // Check that we compare subobjects in the right order. struct Log { - char buff[8] = {}; + char buff[10] = {}; int n = 0; constexpr void add(char c) { buff[n++] = c; } constexpr bool operator==(const char *p) const { return __builtin_strcmp(p, buff) == 0; } @@ -24,22 +24,37 @@ template<char C> struct B { constexpr std::strong_ordering operator<=>(const B&) const { log->add(C); return {0}; } }; +template<typename T> constexpr bool check(bool which, const char *str) { + Log log; + T c(&log); + (void)(which ? c == c : c <=> c); + return log == str; +} + struct C : B<'a'>, B<'b'> { + B<'r'> r[3]; B<'c'> c; + B<'s'> s[2]; 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} {} + constexpr C(Log *p) : B<'a'>{p}, B<'b'>{p}, r{p, p, p}, c{p}, s{p, 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)); +static_assert(check<C>(false, "abrrrcssd")); +static_assert(check<C>(true, "abrrrcssd")); + +struct D { + B<'x'> x; + B<'y'> y[2]; + + constexpr D(Log *p) : x{p}, y{p, p} {} + + bool operator==(const D&) const = default; + std::strong_ordering operator<=>(const D&) const = default; +}; + +static_assert(check<D>(false, "xyy")); +static_assert(check<D>(true, "xyy")); 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 d53d071f36fe..55b2cc3c08f6 100644 --- a/clang/test/CXX/class/class.compare/class.eq/p2.cpp +++ b/clang/test/CXX/class/class.compare/class.eq/p2.cpp @@ -17,8 +17,8 @@ struct G { bool operator==(G) const = delete; }; // expected-note {{deleted here template<typename T> struct X { X(); - 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'}} + bool operator==(const X&) const = default; // #x expected-note 4{{deleted here}} + T t; // expected-note 3{{because there is no viable comparison function for member 't'}} // expected-note@-1 {{because it would invoke a deleted comparison function for member 't'}} }; @@ -43,4 +43,7 @@ void test() { 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}} + + void(X<A[3]>() == X<A[3]>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}} + void(X<B[3]>() == X<B[3]>()); } diff --git a/clang/test/CXX/class/class.compare/class.eq/p3.cpp b/clang/test/CXX/class/class.compare/class.eq/p3.cpp index f58b9daa5235..a7356b5381cd 100644 --- a/clang/test/CXX/class/class.compare/class.eq/p3.cpp +++ b/clang/test/CXX/class/class.compare/class.eq/p3.cpp @@ -1,11 +1,13 @@ // RUN: %clang_cc1 -std=c++2a -verify %s struct A { - int a, b, c; + int a, b[3], 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}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 4, 5}); +static_assert(A{1, 2, 3, 4, 5} == A{0, 2, 3, 4, 5}); // expected-error {{failed}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 0, 3, 4, 5}); // expected-error {{failed}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 0, 4, 5}); // expected-error {{failed}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 0, 5}); // expected-error {{failed}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 4, 0}); // expected-error {{failed}} 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 fafa99ff5cfe..a131842d3944 100644 --- a/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp +++ b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp @@ -1,7 +1,7 @@ // RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions namespace std { - struct strong_ordering { // expected-note 3{{candidate}} + struct strong_ordering { // expected-note 6{{candidate}} int n; constexpr operator int() const { return n; } static const strong_ordering less, equal, greater; @@ -45,25 +45,25 @@ namespace Deletedness { bool operator<(const B&) const; }; struct C { - std::strong_ordering operator<=>(const C&) const = delete; // expected-note {{deleted}} + std::strong_ordering operator<=>(const C&) const = delete; // expected-note 2{{deleted}} }; struct D1 { bool operator==(const D1&) const; - std::strong_ordering operator<=>(int) const; // expected-note {{function not viable}} expected-note {{function (with reversed parameter order) not viable}} - bool operator<(int) const; // expected-note {{function not viable}} + std::strong_ordering operator<=>(int) const; // expected-note 2{{function not viable}} expected-note 2{{function (with reversed parameter order) not viable}} + bool operator<(int) const; // expected-note 2{{function not viable}} }; struct D2 { bool operator<(const D2&) const; - std::strong_ordering operator<=>(int) const; // expected-note {{function not viable}} expected-note {{function (with reversed parameter order) not viable}} - bool operator==(int) const; // expected-note {{function not viable}} + std::strong_ordering operator<=>(int) const; // expected-note 2{{function not viable}} expected-note 2{{function (with reversed parameter order) not viable}} + bool operator==(int) const; // expected-note 2{{function not viable}} }; struct E { bool operator==(const E&) const; - bool operator<(const E&) const = delete; // expected-note {{deleted}} + bool operator<(const E&) const = delete; // expected-note 2{{deleted}} }; struct F { - std::strong_ordering operator<=>(const F&) const; // expected-note {{candidate}} - std::strong_ordering operator<=>(F) const; // expected-note {{candidate}} + std::strong_ordering operator<=>(const F&) const; // expected-note 2{{candidate}} + std::strong_ordering operator<=>(F) const; // expected-note 2{{candidate}} }; struct G1 { bool operator==(const G1&) const; @@ -108,6 +108,37 @@ namespace Deletedness { 0 ); } + + // expected-note@#arr {{deleted comparison function for member 'arr'}} + // expected-note@#arr {{no viable comparison function for member 'arr'}} + // expected-note@#arr {{three-way comparison cannot be synthesized because there is no viable function for '<' comparison}} + // expected-note@#arr {{no viable comparison function for member 'arr'}} + // expected-note@#arr {{three-way comparison cannot be synthesized because there is no viable function for '==' comparison}} + // expected-note@#arr {{deleted comparison function for member 'arr'}} + // expected-note@#arr {{implied comparison for member 'arr' is ambiguous}} + template<typename T> struct CmpArray { + T arr[3]; // #arr + std::strong_ordering operator<=>(const CmpArray&) const = default; // #cmparray expected-note 5{{here}} + }; + void g() { + use( + CmpArray<A>() <=> CmpArray<A>(), + CmpArray<B>() <=> CmpArray<B>(), + CmpArray<C>() <=> CmpArray<C>(), // expected-error {{deleted}} + CmpArray<D1>() <=> CmpArray<D1>(), // expected-error {{deleted}} + CmpArray<D2>() <=> CmpArray<D2>(), // expected-error {{deleted}} + CmpArray<E>() <=> CmpArray<E>(), // expected-error {{deleted}} + CmpArray<F>() <=> CmpArray<F>(), // expected-error {{deleted}} + // FIXME: The following three errors are not very good. + // expected-error@#cmparray {{value of type 'void' is not contextually convertible to 'bool'}} + CmpArray<G1>() <=> CmpArray<G1>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}CmpArray<{{.*}}G1>' first required here}}j + // expected-error@#cmparray {{value of type 'void' is not contextually convertible to 'bool'}} + CmpArray<G2>() <=> CmpArray<G2>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}CmpArray<{{.*}}G2>' first required here}}j + // expected-error@#cmparray {{no matching conversion for static_cast from 'void' to 'std::strong_ordering'}} + CmpArray<H>() <=> CmpArray<H>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}CmpArray<{{.*}}H>' first required here}}j + 0 + ); + } } namespace Synthesis { diff --git a/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp b/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp index e4be892cd7c5..79fce259e4f9 100644 --- a/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp +++ b/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp @@ -10,14 +10,16 @@ namespace std { } struct A { - int a, b, c; + int a, b[3], 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}} +static_assert(A{1, 2, 3, 4, 5} <= A{1, 2, 3, 4, 5}); +static_assert(A{1, 2, 3, 4, 5} <= A{0, 20, 3, 4, 5}); // expected-error {{failed}} +static_assert(A{1, 2, 3, 4, 5} <= A{1, 0, 30, 4, 5}); // expected-error {{failed}} +static_assert(A{1, 2, 3, 4, 5} <= A{1, 2, 0, 40, 5}); // expected-error {{failed}} +static_assert(A{1, 2, 3, 4, 5} <= A{1, 2, 3, 0, 50}); // expected-error {{failed}} +static_assert(A{1, 2, 3, 4, 5} <= A{1, 2, 3, 4, 0}); // expected-error {{failed}} struct reverse_compare { int n; @@ -26,10 +28,12 @@ struct reverse_compare { }; struct B { - int a, b, c; + int a, b[3], 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}} +static_assert(B{1, 2, 3, 4, 5} >= B{1, 2, 3, 4, 5}); +static_assert(B{1, 2, 3, 4, 5} >= B{0, 20, 3, 4, 5}); // expected-error {{failed}} +static_assert(B{1, 2, 3, 4, 5} >= B{1, 0, 30, 4, 5}); // expected-error {{failed}} +static_assert(B{1, 2, 3, 4, 5} >= B{1, 2, 0, 40, 5}); // expected-error {{failed}} +static_assert(B{1, 2, 3, 4, 5} >= B{1, 2, 3, 0, 50}); // expected-error {{failed}} +static_assert(B{1, 2, 3, 4, 5} >= B{1, 2, 3, 4, 0}); // expected-error {{failed}} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits