https://github.com/sethp updated https://github.com/llvm/llvm-project/pull/74852
>From f281d34a51f662c934f158e4770774b0dc3588a2 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Thu, 7 Dec 2023 08:45:51 -0800 Subject: [PATCH 1/9] [Clang][Sema] Print more static_assert exprs This change introspects more values involved in a static_assert, and extends the supported set of operators for introspection to include binary operator method calls. It's intended to address the use-case where a small static_assert helper looks something like this (via `constexpr-builtin-bit-cast.cpp`): ```c++ struct int_splicer { unsigned x; unsigned y; constexpr bool operator==(const int_splicer &other) const { return other.x == x && other.y == y; } }; ``` When used like so: ```c++ constexpr int_splicer got{1, 2}; constexpr int_splicer want{3, 4}; static_assert(got == want); ``` Then we'd expect to get the error: ``` Static assertion failed due to requirement 'got == want' ``` And this change adds the helpful note: ``` Expression evaluates to '{1, 2} == {3, 4}' ``` --- clang/lib/Sema/SemaDeclCXX.cpp | 31 ++++++++++++++----- .../CXX/class/class.compare/class.eq/p3.cpp | 20 ++++++------ .../CXX/class/class.compare/class.rel/p2.cpp | 10 +++--- .../over.match.oper/p9-2a.cpp | 2 +- clang/test/SemaCXX/static-assert-cxx17.cpp | 2 +- 5 files changed, 40 insertions(+), 25 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index c6218a491aecec..e3d46c3140741b 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -17219,6 +17219,13 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, OS << "i)"; } break; + case APValue::ValueKind::Array: + case APValue::ValueKind::Vector: + case APValue::ValueKind::Struct: { + llvm::raw_svector_ostream OS(Str); + V.printPretty(OS, Context, T); + } break; + default: return false; } @@ -17256,11 +17263,10 @@ static bool UsefulToPrintExpr(const Expr *E) { /// Try to print more useful information about a failed static_assert /// with expression \E void Sema::DiagnoseStaticAssertDetails(const Expr *E) { - if (const auto *Op = dyn_cast<BinaryOperator>(E); - Op && Op->getOpcode() != BO_LOr) { - const Expr *LHS = Op->getLHS()->IgnoreParenImpCasts(); - const Expr *RHS = Op->getRHS()->IgnoreParenImpCasts(); - + const auto Diagnose = [&](const Expr *LHS, const Expr *RHS, + const llvm::StringRef &OpStr) { + LHS = LHS->IgnoreParenImpCasts(); + RHS = RHS->IgnoreParenImpCasts(); // Ignore comparisons of boolean expressions with a boolean literal. if ((isa<CXXBoolLiteralExpr>(LHS) && RHS->getType()->isBooleanType()) || (isa<CXXBoolLiteralExpr>(RHS) && LHS->getType()->isBooleanType())) @@ -17287,10 +17293,19 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) { DiagSide[I].ValueString, Context); } if (DiagSide[0].Print && DiagSide[1].Print) { - Diag(Op->getExprLoc(), diag::note_expr_evaluates_to) - << DiagSide[0].ValueString << Op->getOpcodeStr() - << DiagSide[1].ValueString << Op->getSourceRange(); + Diag(E->getExprLoc(), diag::note_expr_evaluates_to) + << DiagSide[0].ValueString << OpStr << DiagSide[1].ValueString + << E->getSourceRange(); } + }; + + if (const auto *Op = dyn_cast<BinaryOperator>(E); + Op && Op->getOpcode() != BO_LOr) { + Diagnose(Op->getLHS(), Op->getRHS(), Op->getOpcodeStr()); + } else if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E); + Op && Op->isInfixBinaryOp()) { + Diagnose(Op->getArg(0), Op->getArg(1), + getOperatorSpelling(Op->getOperator())); } } 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 04db022fe73021..53c4dda133301b 100644 --- a/clang/test/CXX/class/class.compare/class.eq/p3.cpp +++ b/clang/test/CXX/class/class.compare/class.eq/p3.cpp @@ -6,11 +6,11 @@ struct A { }; 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}} +static_assert(A{1, 2, 3, 4, 5} == A{0, 2, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 0, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 0, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 0, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 4, 0}); // expected-error {{failed}} expected-note {{evaluates to}} struct B { int a, b[3], c; @@ -18,8 +18,8 @@ struct B { }; static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 4, 5}); -static_assert(B{1, 2, 3, 4, 5} == B{0, 2, 3, 4, 5}); // expected-error {{failed}} -static_assert(B{1, 2, 3, 4, 5} == B{1, 0, 3, 4, 5}); // expected-error {{failed}} -static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 0, 4, 5}); // expected-error {{failed}} -static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 0, 5}); // expected-error {{failed}} -static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 4, 0}); // expected-error {{failed}} +static_assert(B{1, 2, 3, 4, 5} == B{0, 2, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(B{1, 2, 3, 4, 5} == B{1, 0, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 0, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 0, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 4, 0}); // expected-error {{failed}} expected-note {{evaluates to}} 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 90115284d2bd02..07501c6a081841 100644 --- a/clang/test/CXX/class/class.compare/class.rel/p2.cpp +++ b/clang/test/CXX/class/class.compare/class.rel/p2.cpp @@ -10,15 +10,15 @@ namespace Rel { friend bool operator>=(const A&, const A&) = default; }; static_assert(A{0} < A{1}); - static_assert(A{1} < A{1}); // expected-error {{failed}} + static_assert(A{1} < A{1}); // expected-error {{failed}} expected-note {{'{1} < {1}'}} static_assert(A{0} <= A{1}); static_assert(A{1} <= A{1}); - static_assert(A{2} <= A{1}); // expected-error {{failed}} + static_assert(A{2} <= A{1}); // expected-error {{failed}} expected-note {{'{2} <= {1}'}} static_assert(A{1} > A{0}); - static_assert(A{1} > A{1}); // expected-error {{failed}} + static_assert(A{1} > A{1}); // expected-error {{failed}} expected-note {{'{1} > {1}'}} static_assert(A{1} >= A{0}); static_assert(A{1} >= A{1}); - static_assert(A{1} >= A{2}); // expected-error {{failed}} + static_assert(A{1} >= A{2}); // expected-error {{failed}} expected-note {{'{1} >= {2}'}} struct B { bool operator<=>(B) const = delete; // expected-note 4{{deleted here}} expected-note-re 8{{candidate {{.*}} deleted}} @@ -49,7 +49,7 @@ namespace NotEqual { friend bool operator!=(const A&, const A&) = default; }; static_assert(A{1} != A{2}); - static_assert(A{1} != A{1}); // expected-error {{failed}} + static_assert(A{1} != A{1}); // expected-error {{failed}} expected-note {{'{1} != {1}'}} struct B { bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}} diff --git a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp index 95d6a55aee66a1..8f31e8947a768c 100644 --- a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp +++ b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp @@ -33,7 +33,7 @@ struct Y {}; constexpr bool operator==(X x, Y) { return x.equal; } static_assert(X{true} == Y{}); -static_assert(X{false} == Y{}); // expected-error {{failed}} +static_assert(X{false} == Y{}); // expected-error {{failed}} expected-note{{'{false} == {}'}} // x == y -> y == x static_assert(Y{} == X{true}); diff --git a/clang/test/SemaCXX/static-assert-cxx17.cpp b/clang/test/SemaCXX/static-assert-cxx17.cpp index 41a7b025d0eb75..1d78915aa13e18 100644 --- a/clang/test/SemaCXX/static-assert-cxx17.cpp +++ b/clang/test/SemaCXX/static-assert-cxx17.cpp @@ -94,7 +94,7 @@ void foo6() { // expected-error@-1{{static assertion failed due to requirement '(const X<int> *)nullptr'}} static_assert(static_cast<const X<typename T::T> *>(nullptr)); // expected-error@-1{{static assertion failed due to requirement 'static_cast<const X<int> *>(nullptr)'}} - static_assert((const X<typename T::T>[]){} == nullptr); + static_assert((const X<typename T::T>[]){} == nullptr); // expected-note{{expression evaluates to '{} == nullptr'}} // expected-error@-1{{static assertion failed due to requirement '(const X<int>[0]){} == nullptr'}} static_assert(sizeof(X<decltype(X<typename T::T>().X<typename T::T>::~X())>) == 0); // expected-error@-1{{static assertion failed due to requirement 'sizeof(X<void>) == 0'}} \ >From 2fd5fc464868a682cb4b66ea4e97a6a7100ab120 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Fri, 5 Jan 2024 08:49:02 -0800 Subject: [PATCH 2/9] add test for review discussion --- .../SemaCXX/static-assert-diagnostics.cpp | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 clang/test/SemaCXX/static-assert-diagnostics.cpp diff --git a/clang/test/SemaCXX/static-assert-diagnostics.cpp b/clang/test/SemaCXX/static-assert-diagnostics.cpp new file mode 100644 index 00000000000000..432a3f4bd54656 --- /dev/null +++ b/clang/test/SemaCXX/static-assert-diagnostics.cpp @@ -0,0 +1,117 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +struct A { + int a, b[3], c; + bool operator==(const A&) const = default; +}; + +constexpr auto a0 = A{0, 0, 3, 4, 5}; + +// expected-note@+1 {{evaluates to '{0, {0, 3, 4}, 5} == {1, {2, 3, 4}, 5}'}} +static_assert(a0 == A{1, {2, 3, 4}, 5}); // expected-error {{failed}} + +struct _arr { + const int b[3]; + constexpr bool operator==(const int rhs[3]) const { + for (unsigned i = 0; i < sizeof(b) / sizeof(int); i++) + if (b[i] != rhs[i]) + return false; + return true; + } +}; + +// TODO[seth] syntactically sort of valid but almost entirely unusuable +// (it's an int *, not an int [3] ) +// constexpr int _[3] = {...}; would work, but that's not piecewise substitutable +// maybe it's ok? I mean, not like we can do better really... +constexpr auto _ = (int[3]){2, 3, 4}; + +// output: '{{2, 3, 4}} == {0, 3, 4}' (the `{{` breaks VerifyDiagnosticConsumer::ParseDirective) +// expected-note@+1 {{evaluates to}} +static_assert(_arr{2, 3, 4} == a0.b); // expected-error {{failed}} + +struct B { + int a, c; // named the same just to keep things fresh + bool operator==(const B&) const = default; +}; + +// expected-note@+1 {{evaluates to '{7, 6} == {8, 6}'}} +static_assert(B{7, 6} == B{8, 6}); // expected-error {{failed}} + +typedef int v4si __attribute__((__vector_size__(16))); + +struct C: A, B { + enum { E1, E2 } e; + bool operator==(const C&) const = default; +}; + +constexpr auto cc = C{A{1, {2, 3, 4}, 5}, B{7, 6}, C::E1}; + +// actually '{{1, {2, 3, 4}, 5}, {7, 6}, 0} == {{0, {0, 3, 4}, 5}, {5, 0}, 1}' (the `{{` breaks VerifyDiagnosticConsumer::ParseDirective) +// expected-note@+1 {{evaluates to}} +static_assert(cc == C{a0, {5}, C::E2}); // expected-error {{failed}} + +// this little guy? oh, I wouldn't worry about this little guy +namespace std { +template <class To, class From> +constexpr To bit_cast(const From &from) { + static_assert(sizeof(To) == sizeof(From)); + return __builtin_bit_cast(To, from); +} +} // namespace std + +typedef int v4si __attribute__((__vector_size__(16))); + +struct V { + v4si v; + + // doesn't work + // vectors are not contextually convertable to `bool`, and + // `==` on vectors produces a vector of element-wise results + // bool operator==(const V&) const = default; + + constexpr bool operator==(const V& rhs) const { + // doesn't work + // __builtin_reduce_and is not valid in a constant expression + // return __builtin_reduce_and(b == rhs.b) && __builtin_reduce_and(v == rhs.v); + + // also doesn't work + // surprisingly, b[0] is also not valid in a constant expression (nor v[0]) + // return b[0] == rhs.b[0] && ... + + struct cmp { + unsigned char v [sizeof(v4si)]; + bool operator==(const cmp&) const = default; + }; + return std::bit_cast<cmp>(v) == std::bit_cast<cmp>(rhs.v); + }; + +}; +// todo[seth] do I cause infinite evaluator recursion if I move this up into the function above? +static_assert(V{1, 2, 3, 4} == V{1, 2, 3, 4}); + +// '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}' +// expected-note@+1 {{evaluates to}} +static_assert(V{1, 2, 3, 4} == V{1, 2, 0, 4}); // expected-error {{failed}} + +constexpr auto v = (v4si){1, 2, 3, 4}; +constexpr auto vv = V{{1, 2, 3, 4}}; + + +// there appears to be no constexpr-compatible way to write an == for +// two `bool4`s at this time, since std::bit_cast doesn't support it +// typedef bool bool4 __attribute__((ext_vector_type(4))); + +// so we use a bool8 +typedef bool bool8 __attribute__((ext_vector_type(8))); + +struct BV { + bool8 b; + constexpr bool operator==(const BV& rhs) const { + return std::bit_cast<unsigned char>(b) == std::bit_cast<unsigned char>(rhs.b); + } +}; + +// '{{false, true, false, false, false, false, false, false}} == {{true, false, false, false, false, false, false, false}}' +// expected-note@+1 {{evaluates to}} +static_assert(BV{{0, 1}} == BV{{1, 0}}); // expected-error {{failed}} >From db9118fc6f14e84a518e3ee0990041bac8979522 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Fri, 5 Jan 2024 08:53:23 -0800 Subject: [PATCH 3/9] fixup!: remove notes to self unrelated to PR Oops, those were just things I was curious about, not meant to be left in. --- clang/test/SemaCXX/static-assert-diagnostics.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/clang/test/SemaCXX/static-assert-diagnostics.cpp b/clang/test/SemaCXX/static-assert-diagnostics.cpp index 432a3f4bd54656..a92a6d5fb08c3c 100644 --- a/clang/test/SemaCXX/static-assert-diagnostics.cpp +++ b/clang/test/SemaCXX/static-assert-diagnostics.cpp @@ -20,12 +20,6 @@ struct _arr { } }; -// TODO[seth] syntactically sort of valid but almost entirely unusuable -// (it's an int *, not an int [3] ) -// constexpr int _[3] = {...}; would work, but that's not piecewise substitutable -// maybe it's ok? I mean, not like we can do better really... -constexpr auto _ = (int[3]){2, 3, 4}; - // output: '{{2, 3, 4}} == {0, 3, 4}' (the `{{` breaks VerifyDiagnosticConsumer::ParseDirective) // expected-note@+1 {{evaluates to}} static_assert(_arr{2, 3, 4} == a0.b); // expected-error {{failed}} @@ -87,7 +81,6 @@ struct V { }; }; -// todo[seth] do I cause infinite evaluator recursion if I move this up into the function above? static_assert(V{1, 2, 3, 4} == V{1, 2, 3, 4}); // '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}' >From 153d36338a73d8fbbb6087b2cf07e671b7aa660a Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Sat, 6 Jan 2024 07:54:31 -0800 Subject: [PATCH 4/9] [Clang] Wide delimiters ('{{{') for expect strings Prior to this commit, it was impossible to use the simple string matching directives to look for most content that contains `{{`, such as: ``` // expected-note {{my_struct{{1}, 2}}} ``` Which would parse like so: ``` "nested" brace v // expected-note {{my_struct{{1}, 2}}} closes the nested brace ^ | trailing } ``` And the frontend would complain 'cannot find end ('}}') of expected'. At this snapshot, VerifyDiagnosticConsumer's parser now counts the opening braces and looks for a matching length of closing sigils, allowing the above to be written as: ``` // expected-note {{{my_struct{{1}, 2}}}} opening brace |-| |-| closing brace is '}}}', found here ^ ``` --- .../clang/Basic/DiagnosticFrontendKinds.td | 2 +- clang/lib/Frontend/VerifyDiagnosticConsumer.cpp | 15 +++++++++++---- clang/test/SemaCXX/static-assert-diagnostics.cpp | 12 ++++-------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index 715e0c0dc8fa84..4bf0ab54a046c1 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -166,7 +166,7 @@ def err_verify_no_such_marker : Error< def err_verify_missing_start : Error< "cannot find start ('{{') of expected %0">; def err_verify_missing_end : Error< - "cannot find end ('}}') of expected %0">; + "cannot find end ('%1') of expected %0">; def err_verify_invalid_content : Error< "invalid expected %0: %1">; def err_verify_missing_regex : Error< diff --git a/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp b/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp index ab8174f4f4db92..5eab7bd3619f19 100644 --- a/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp +++ b/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp @@ -612,12 +612,19 @@ static bool ParseDirective(StringRef S, ExpectedData *ED, SourceManager &SM, diag::err_verify_missing_start) << KindStr; continue; } + llvm::SmallString<8> CloseBrace("}}"); + const char *const DelimBegin = PH.C; PH.Advance(); + // Count the number of opening braces for `string` kinds + for (; !D.RegexKind && PH.Next("{"); PH.Advance()) + CloseBrace += '}'; const char* const ContentBegin = PH.C; // mark content begin - // Search for token: }} - if (!PH.SearchClosingBrace("{{", "}}")) { - Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin), - diag::err_verify_missing_end) << KindStr; + // Search for closing brace + StringRef OpenBrace(DelimBegin, ContentBegin - DelimBegin); + if (!PH.SearchClosingBrace(OpenBrace, CloseBrace)) { + Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin), + diag::err_verify_missing_end) + << KindStr << CloseBrace; continue; } const char* const ContentEnd = PH.P; // mark content end diff --git a/clang/test/SemaCXX/static-assert-diagnostics.cpp b/clang/test/SemaCXX/static-assert-diagnostics.cpp index a92a6d5fb08c3c..f6c38c0c7313b2 100644 --- a/clang/test/SemaCXX/static-assert-diagnostics.cpp +++ b/clang/test/SemaCXX/static-assert-diagnostics.cpp @@ -20,8 +20,7 @@ struct _arr { } }; -// output: '{{2, 3, 4}} == {0, 3, 4}' (the `{{` breaks VerifyDiagnosticConsumer::ParseDirective) -// expected-note@+1 {{evaluates to}} +// expected-note@+1 {{{evaluates to '{{2, 3, 4}} == {0, 3, 4}'}}} static_assert(_arr{2, 3, 4} == a0.b); // expected-error {{failed}} struct B { @@ -41,8 +40,7 @@ struct C: A, B { constexpr auto cc = C{A{1, {2, 3, 4}, 5}, B{7, 6}, C::E1}; -// actually '{{1, {2, 3, 4}, 5}, {7, 6}, 0} == {{0, {0, 3, 4}, 5}, {5, 0}, 1}' (the `{{` breaks VerifyDiagnosticConsumer::ParseDirective) -// expected-note@+1 {{evaluates to}} +// expected-note@+1 {{{evaluates to '{{1, {2, 3, 4}, 5}, {7, 6}, 0} == {{0, {0, 3, 4}, 5}, {5, 0}, 1}'}}} static_assert(cc == C{a0, {5}, C::E2}); // expected-error {{failed}} // this little guy? oh, I wouldn't worry about this little guy @@ -83,8 +81,7 @@ struct V { }; static_assert(V{1, 2, 3, 4} == V{1, 2, 3, 4}); -// '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}' -// expected-note@+1 {{evaluates to}} +// expected-note@+1 {{{evaluates to '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}'}}} static_assert(V{1, 2, 3, 4} == V{1, 2, 0, 4}); // expected-error {{failed}} constexpr auto v = (v4si){1, 2, 3, 4}; @@ -105,6 +102,5 @@ struct BV { } }; -// '{{false, true, false, false, false, false, false, false}} == {{true, false, false, false, false, false, false, false}}' -// expected-note@+1 {{evaluates to}} +// expected-note@+1 {{{evaluates to '{{false, true, false, false, false, false, false, false}} == {{true, false, false, false, false, false, false, false}}'}}} static_assert(BV{{0, 1}} == BV{{1, 0}}); // expected-error {{failed}} >From 6597d6c3dda1890c0c999ef4fbc24e492a89ae8b Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Mon, 8 Jan 2024 07:34:39 -0800 Subject: [PATCH 5/9] Revert "[Clang] Wide delimiters ('{{{') for expect strings" This reverts commit 153d36338a73d8fbbb6087b2cf07e671b7aa660a. --- .../clang/Basic/DiagnosticFrontendKinds.td | 2 +- clang/lib/Frontend/VerifyDiagnosticConsumer.cpp | 15 ++++----------- clang/test/SemaCXX/static-assert-diagnostics.cpp | 12 ++++++++---- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index 4bf0ab54a046c1..715e0c0dc8fa84 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -166,7 +166,7 @@ def err_verify_no_such_marker : Error< def err_verify_missing_start : Error< "cannot find start ('{{') of expected %0">; def err_verify_missing_end : Error< - "cannot find end ('%1') of expected %0">; + "cannot find end ('}}') of expected %0">; def err_verify_invalid_content : Error< "invalid expected %0: %1">; def err_verify_missing_regex : Error< diff --git a/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp b/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp index 5eab7bd3619f19..ab8174f4f4db92 100644 --- a/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp +++ b/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp @@ -612,19 +612,12 @@ static bool ParseDirective(StringRef S, ExpectedData *ED, SourceManager &SM, diag::err_verify_missing_start) << KindStr; continue; } - llvm::SmallString<8> CloseBrace("}}"); - const char *const DelimBegin = PH.C; PH.Advance(); - // Count the number of opening braces for `string` kinds - for (; !D.RegexKind && PH.Next("{"); PH.Advance()) - CloseBrace += '}'; const char* const ContentBegin = PH.C; // mark content begin - // Search for closing brace - StringRef OpenBrace(DelimBegin, ContentBegin - DelimBegin); - if (!PH.SearchClosingBrace(OpenBrace, CloseBrace)) { - Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin), - diag::err_verify_missing_end) - << KindStr << CloseBrace; + // Search for token: }} + if (!PH.SearchClosingBrace("{{", "}}")) { + Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin), + diag::err_verify_missing_end) << KindStr; continue; } const char* const ContentEnd = PH.P; // mark content end diff --git a/clang/test/SemaCXX/static-assert-diagnostics.cpp b/clang/test/SemaCXX/static-assert-diagnostics.cpp index f6c38c0c7313b2..a92a6d5fb08c3c 100644 --- a/clang/test/SemaCXX/static-assert-diagnostics.cpp +++ b/clang/test/SemaCXX/static-assert-diagnostics.cpp @@ -20,7 +20,8 @@ struct _arr { } }; -// expected-note@+1 {{{evaluates to '{{2, 3, 4}} == {0, 3, 4}'}}} +// output: '{{2, 3, 4}} == {0, 3, 4}' (the `{{` breaks VerifyDiagnosticConsumer::ParseDirective) +// expected-note@+1 {{evaluates to}} static_assert(_arr{2, 3, 4} == a0.b); // expected-error {{failed}} struct B { @@ -40,7 +41,8 @@ struct C: A, B { constexpr auto cc = C{A{1, {2, 3, 4}, 5}, B{7, 6}, C::E1}; -// expected-note@+1 {{{evaluates to '{{1, {2, 3, 4}, 5}, {7, 6}, 0} == {{0, {0, 3, 4}, 5}, {5, 0}, 1}'}}} +// actually '{{1, {2, 3, 4}, 5}, {7, 6}, 0} == {{0, {0, 3, 4}, 5}, {5, 0}, 1}' (the `{{` breaks VerifyDiagnosticConsumer::ParseDirective) +// expected-note@+1 {{evaluates to}} static_assert(cc == C{a0, {5}, C::E2}); // expected-error {{failed}} // this little guy? oh, I wouldn't worry about this little guy @@ -81,7 +83,8 @@ struct V { }; static_assert(V{1, 2, 3, 4} == V{1, 2, 3, 4}); -// expected-note@+1 {{{evaluates to '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}'}}} +// '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}' +// expected-note@+1 {{evaluates to}} static_assert(V{1, 2, 3, 4} == V{1, 2, 0, 4}); // expected-error {{failed}} constexpr auto v = (v4si){1, 2, 3, 4}; @@ -102,5 +105,6 @@ struct BV { } }; -// expected-note@+1 {{{evaluates to '{{false, true, false, false, false, false, false, false}} == {{true, false, false, false, false, false, false, false}}'}}} +// '{{false, true, false, false, false, false, false, false}} == {{true, false, false, false, false, false, false, false}}' +// expected-note@+1 {{evaluates to}} static_assert(BV{{0, 1}} == BV{{1, 0}}); // expected-error {{failed}} >From c27070185d4ee833b877fef500bfe17177d4a8d9 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Mon, 8 Jan 2024 07:35:15 -0800 Subject: [PATCH 6/9] fixup!: Reapply "[Clang] Wide delimiters ('{{{') for expect strings" This reverts commit 6597d6c3dda1890c0c999ef4fbc24e492a89ae8b. --- .../clang/Basic/DiagnosticFrontendKinds.td | 2 +- clang/lib/Frontend/VerifyDiagnosticConsumer.cpp | 15 +++++++++++---- clang/test/SemaCXX/static-assert-diagnostics.cpp | 12 ++++-------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index 715e0c0dc8fa84..4bf0ab54a046c1 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -166,7 +166,7 @@ def err_verify_no_such_marker : Error< def err_verify_missing_start : Error< "cannot find start ('{{') of expected %0">; def err_verify_missing_end : Error< - "cannot find end ('}}') of expected %0">; + "cannot find end ('%1') of expected %0">; def err_verify_invalid_content : Error< "invalid expected %0: %1">; def err_verify_missing_regex : Error< diff --git a/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp b/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp index ab8174f4f4db92..5eab7bd3619f19 100644 --- a/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp +++ b/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp @@ -612,12 +612,19 @@ static bool ParseDirective(StringRef S, ExpectedData *ED, SourceManager &SM, diag::err_verify_missing_start) << KindStr; continue; } + llvm::SmallString<8> CloseBrace("}}"); + const char *const DelimBegin = PH.C; PH.Advance(); + // Count the number of opening braces for `string` kinds + for (; !D.RegexKind && PH.Next("{"); PH.Advance()) + CloseBrace += '}'; const char* const ContentBegin = PH.C; // mark content begin - // Search for token: }} - if (!PH.SearchClosingBrace("{{", "}}")) { - Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin), - diag::err_verify_missing_end) << KindStr; + // Search for closing brace + StringRef OpenBrace(DelimBegin, ContentBegin - DelimBegin); + if (!PH.SearchClosingBrace(OpenBrace, CloseBrace)) { + Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin), + diag::err_verify_missing_end) + << KindStr << CloseBrace; continue; } const char* const ContentEnd = PH.P; // mark content end diff --git a/clang/test/SemaCXX/static-assert-diagnostics.cpp b/clang/test/SemaCXX/static-assert-diagnostics.cpp index a92a6d5fb08c3c..f6c38c0c7313b2 100644 --- a/clang/test/SemaCXX/static-assert-diagnostics.cpp +++ b/clang/test/SemaCXX/static-assert-diagnostics.cpp @@ -20,8 +20,7 @@ struct _arr { } }; -// output: '{{2, 3, 4}} == {0, 3, 4}' (the `{{` breaks VerifyDiagnosticConsumer::ParseDirective) -// expected-note@+1 {{evaluates to}} +// expected-note@+1 {{{evaluates to '{{2, 3, 4}} == {0, 3, 4}'}}} static_assert(_arr{2, 3, 4} == a0.b); // expected-error {{failed}} struct B { @@ -41,8 +40,7 @@ struct C: A, B { constexpr auto cc = C{A{1, {2, 3, 4}, 5}, B{7, 6}, C::E1}; -// actually '{{1, {2, 3, 4}, 5}, {7, 6}, 0} == {{0, {0, 3, 4}, 5}, {5, 0}, 1}' (the `{{` breaks VerifyDiagnosticConsumer::ParseDirective) -// expected-note@+1 {{evaluates to}} +// expected-note@+1 {{{evaluates to '{{1, {2, 3, 4}, 5}, {7, 6}, 0} == {{0, {0, 3, 4}, 5}, {5, 0}, 1}'}}} static_assert(cc == C{a0, {5}, C::E2}); // expected-error {{failed}} // this little guy? oh, I wouldn't worry about this little guy @@ -83,8 +81,7 @@ struct V { }; static_assert(V{1, 2, 3, 4} == V{1, 2, 3, 4}); -// '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}' -// expected-note@+1 {{evaluates to}} +// expected-note@+1 {{{evaluates to '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}'}}} static_assert(V{1, 2, 3, 4} == V{1, 2, 0, 4}); // expected-error {{failed}} constexpr auto v = (v4si){1, 2, 3, 4}; @@ -105,6 +102,5 @@ struct BV { } }; -// '{{false, true, false, false, false, false, false, false}} == {{true, false, false, false, false, false, false, false}}' -// expected-note@+1 {{evaluates to}} +// expected-note@+1 {{{evaluates to '{{false, true, false, false, false, false, false, false}} == {{true, false, false, false, false, false, false, false}}'}}} static_assert(BV{{0, 1}} == BV{{1, 0}}); // expected-error {{failed}} >From e769f158c8ee001e95299ce4c83e5250e2a8f963 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Tue, 9 Jan 2024 09:03:45 -0800 Subject: [PATCH 7/9] print a valid initializer expression for value --- clang/lib/Sema/SemaDeclCXX.cpp | 10 +++++- .../SemaCXX/static-assert-diagnostics.cpp | 33 ++++++++++++------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index e3d46c3140741b..9cad7dffd1a790 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -17220,9 +17220,17 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, } break; case APValue::ValueKind::Array: - case APValue::ValueKind::Vector: + case APValue::ValueKind::Vector: { + llvm::raw_svector_ostream OS(Str); + OS << '('; + OS << T.getUnqualifiedType(); + OS << ')'; + V.printPretty(OS, Context, T); + } break; + case APValue::ValueKind::Struct: { llvm::raw_svector_ostream OS(Str); + OS << T.getUnqualifiedType(); V.printPretty(OS, Context, T); } break; diff --git a/clang/test/SemaCXX/static-assert-diagnostics.cpp b/clang/test/SemaCXX/static-assert-diagnostics.cpp index f6c38c0c7313b2..352f6622676ce5 100644 --- a/clang/test/SemaCXX/static-assert-diagnostics.cpp +++ b/clang/test/SemaCXX/static-assert-diagnostics.cpp @@ -7,7 +7,7 @@ struct A { constexpr auto a0 = A{0, 0, 3, 4, 5}; -// expected-note@+1 {{evaluates to '{0, {0, 3, 4}, 5} == {1, {2, 3, 4}, 5}'}} +// expected-note@+1 {{evaluates to 'A{0, {0, 3, 4}, 5} == A{1, {2, 3, 4}, 5}'}} static_assert(a0 == A{1, {2, 3, 4}, 5}); // expected-error {{failed}} struct _arr { @@ -20,7 +20,7 @@ struct _arr { } }; -// expected-note@+1 {{{evaluates to '{{2, 3, 4}} == {0, 3, 4}'}}} +// expected-note@+1 {{{evaluates to '_arr{{2, 3, 4}} == (int[3]){0, 3, 4}'}}} static_assert(_arr{2, 3, 4} == a0.b); // expected-error {{failed}} struct B { @@ -28,7 +28,7 @@ struct B { bool operator==(const B&) const = default; }; -// expected-note@+1 {{evaluates to '{7, 6} == {8, 6}'}} +// expected-note@+1 {{evaluates to 'B{7, 6} == B{8, 6}'}} static_assert(B{7, 6} == B{8, 6}); // expected-error {{failed}} typedef int v4si __attribute__((__vector_size__(16))); @@ -40,10 +40,10 @@ struct C: A, B { constexpr auto cc = C{A{1, {2, 3, 4}, 5}, B{7, 6}, C::E1}; -// expected-note@+1 {{{evaluates to '{{1, {2, 3, 4}, 5}, {7, 6}, 0} == {{0, {0, 3, 4}, 5}, {5, 0}, 1}'}}} +// expected-note@+1 {{{evaluates to 'C{{1, {2, 3, 4}, 5}, {7, 6}, 0} == C{{0, {0, 3, 4}, 5}, {5, 0}, 1}'}}} static_assert(cc == C{a0, {5}, C::E2}); // expected-error {{failed}} -// this little guy? oh, I wouldn't worry about this little guy +// define `std::bit_cast` as a helper for doing constexpr vector comparisons namespace std { template <class To, class From> constexpr To bit_cast(const From &from) { @@ -71,22 +71,24 @@ struct V { // surprisingly, b[0] is also not valid in a constant expression (nor v[0]) // return b[0] == rhs.b[0] && ... + // cmp an array of bytes that does element-wise comparisons that's the same size as v struct cmp { unsigned char v [sizeof(v4si)]; bool operator==(const cmp&) const = default; }; return std::bit_cast<cmp>(v) == std::bit_cast<cmp>(rhs.v); }; - }; +constexpr bool operator==(const V& lhs, const v4si& rhs) { + return lhs == V{rhs}; +} + static_assert(V{1, 2, 3, 4} == V{1, 2, 3, 4}); -// expected-note@+1 {{{evaluates to '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}'}}} +// expected-note@+1 {{{evaluates to 'V{{1, 2, 3, 4}} == V{{1, 2, 0, 4}}'}}} static_assert(V{1, 2, 3, 4} == V{1, 2, 0, 4}); // expected-error {{failed}} - -constexpr auto v = (v4si){1, 2, 3, 4}; -constexpr auto vv = V{{1, 2, 3, 4}}; - +// expected-note@+1 {{{evaluates to 'V{{1, 2, 3, 4}} == (v4si){1, 2, 0, 4}'}}} +static_assert(V{1, 2, 3, 4} == (v4si){1, 2, 0, 4}); // expected-error {{failed}} // there appears to be no constexpr-compatible way to write an == for // two `bool4`s at this time, since std::bit_cast doesn't support it @@ -101,6 +103,13 @@ struct BV { return std::bit_cast<unsigned char>(b) == std::bit_cast<unsigned char>(rhs.b); } }; +constexpr bool operator==(const BV& lhs, const bool8& rhs) { + return lhs == BV{rhs}; +} -// expected-note@+1 {{{evaluates to '{{false, true, false, false, false, false, false, false}} == {{true, false, false, false, false, false, false, false}}'}}} + +// expected-note@+1 {{{evaluates to 'BV{{false, true, false, false, false, false, false, false}} == BV{{true, false, false, false, false, false, false, false}}'}}} static_assert(BV{{0, 1}} == BV{{1, 0}}); // expected-error {{failed}} + +// expected-note@+1 {{{evaluates to 'BV{{false, true, false, false, false, false, false, false}} == (bool8){true, false, false, false, false, false, false, false}'}}} +static_assert(BV{{0, 1}} == (bool8){true, false}); // expected-error {{failed}} >From 345b7872198bf1035fc6185a01d6474e2fe990f1 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Wed, 10 Jan 2024 11:46:40 -0800 Subject: [PATCH 8/9] [Clang][Sema] Print enumerators by name Teaches APValue::printPretty to print appropriate cast expressions for enumeral types rather than just bare ints, and to respect the pre-existing PrintingPolicy::UseEnumerators field (falling back to printing a cast when the value doesn't match any named constant). --- clang/lib/AST/APValue.cpp | 50 +++++++++++++++- clang/lib/Sema/SemaDeclCXX.cpp | 22 +++++-- clang/lib/Sema/SemaExpr.cpp | 21 +++++-- clang/test/SemaCXX/compare-cxx2a.cpp | 4 +- clang/test/SemaCXX/recovery-expr-type.cpp | 2 +- clang/test/SemaCXX/static-assert-cxx17.cpp | 2 +- .../SemaCXX/static-assert-diagnostics.cpp | 60 +++++++++++++++++-- 7 files changed, 139 insertions(+), 22 deletions(-) diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp index 4eae308ef5b34c..d9689db6f4e5f2 100644 --- a/clang/lib/AST/APValue.cpp +++ b/clang/lib/AST/APValue.cpp @@ -18,6 +18,7 @@ #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/Type.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" using namespace clang; @@ -711,11 +712,54 @@ void APValue::printPretty(raw_ostream &Out, const PrintingPolicy &Policy, case APValue::Indeterminate: Out << "<uninitialized>"; return; - case APValue::Int: + case APValue::Int: { + const APSInt &Val = getInt(); + if (const EnumType *ET = Ty->getAs<EnumType>()) { + // print the enumerator name if requested (and one exists) + if (Policy.UseEnumerators) { + for (const EnumConstantDecl *ECD : ET->getDecl()->enumerators()) { + if (APSInt::isSameValue(ECD->getInitVal(), Val)) { + if (ECD->isCXXClassMember()) + ECD->printQualifiedName(Out, Policy); + else + ECD->printName(Out, Policy); + return; + } + } + } + + // otherwise, we print it as a cast from `Val` + if (ET->hasUnnamedOrLocalType()) { + // e.g. `(unnamed enum at ...)7`, unless... + if (const EnumDecl *Defn = ET->getDecl()->getDefinition()) { + // ... can identify the defn somehow + if (const IdentifierInfo *II = Defn->getIdentifier()) + Out << '(' << II->getName() << ')'; + else if (const TypedefNameDecl *Typedef = + Defn->getTypedefNameForAnonDecl()) { + assert(Typedef->getIdentifier() && "Typedef without identifier?"); + Out << '(' << Typedef->getIdentifier()->getName() << ')'; + } else if (const NamedDecl *ND = dyn_cast_if_present<NamedDecl>( + Defn->getNextDeclInContext())) { + // if it's part of a declaration, then use `(decltype(...))7` + Out << "(decltype("; + ND->printQualifiedName(Out); + Out << "))"; + } else + Defn->printQualifiedName(Out); + } else + ET->getDecl()->printQualifiedName(Out); + } else { + // e.g. `(E)7` for some `enum E {};` + Out << '(' << Ty << ')'; + } + } + if (Ty->isBooleanType()) - Out << (getInt().getBoolValue() ? "true" : "false"); + Out << (Val.getBoolValue() ? "true" : "false"); else - Out << getInt(); + Out << Val; + } return; case APValue::Float: Out << GetApproxValue(getFloat()); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 9cad7dffd1a790..20a2495e001462 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -22,6 +22,7 @@ #include "clang/AST/EvaluatedExprVisitor.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/PrettyPrinter.h" #include "clang/AST/RecordLayout.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/StmtVisitor.h" @@ -17143,6 +17144,8 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, if (!V.hasValue()) return false; + PrintingPolicy Policy(Context.getPrintingPolicy()); + Policy.UseEnumerators = true; switch (V.getKind()) { case APValue::ValueKind::Int: if (T->isBooleanType()) { @@ -17184,6 +17187,12 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, break; } } + // print enums as either their named value or a cast + if (T->isEnumeralType()) { + llvm::raw_svector_ostream OS(Str); + V.printPretty(OS, Policy, T, &Context); + break; + } V.getInt().toString(Str); } @@ -17222,16 +17231,17 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, case APValue::ValueKind::Array: case APValue::ValueKind::Vector: { llvm::raw_svector_ostream OS(Str); - OS << '('; - OS << T.getUnqualifiedType(); - OS << ')'; - V.printPretty(OS, Context, T); + OS << '(' << T << ')'; + V.printPretty(OS, Policy, T, &Context); } break; case APValue::ValueKind::Struct: { llvm::raw_svector_ostream OS(Str); - OS << T.getUnqualifiedType(); - V.printPretty(OS, Context, T); + if (T.hasQualifiers()) + OS << '(' << T << ")"; + else + OS << T; + V.printPretty(OS, Policy, T, &Context); } break; default: diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index d629be083d8c38..b6df7ff39031e6 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -26,6 +26,7 @@ #include "clang/AST/ExprOpenMP.h" #include "clang/AST/OperationKinds.h" #include "clang/AST/ParentMapContext.h" +#include "clang/AST/PrettyPrinter.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" @@ -12924,12 +12925,22 @@ static bool checkThreeWayNarrowingConversion(Sema &S, QualType ToType, Expr *E, case NK_Not_Narrowing: return false; - case NK_Constant_Narrowing: - // Implicit conversion to a narrower type, and the value is not a constant - // expression. + case NK_Constant_Narrowing: { + // Implicit conversion to a narrower type, and the value is a constant + // expression + std::string ValueAsString; + { + llvm::raw_string_ostream Out(ValueAsString); + PrintingPolicy Policy(S.getPrintingPolicy()); + // If PreNarrowingType is an enumeral type, prefer to print + // the value as `(EnumT)<underlying value>` to make it clear what's being + // narrowed here + Policy.UseEnumerators = false; + PreNarrowingValue.printPretty(Out, Policy, PreNarrowingType, &S.Context); + } S.Diag(E->getBeginLoc(), diag::err_spaceship_argument_narrowing) - << /*Constant*/ 1 - << PreNarrowingValue.getAsString(S.Context, PreNarrowingType) << ToType; + << /*Constant*/ 1 << ValueAsString << ToType; + } return true; case NK_Variable_Narrowing: diff --git a/clang/test/SemaCXX/compare-cxx2a.cpp b/clang/test/SemaCXX/compare-cxx2a.cpp index 15a0baccfca17a..e055075be5e6fa 100644 --- a/clang/test/SemaCXX/compare-cxx2a.cpp +++ b/clang/test/SemaCXX/compare-cxx2a.cpp @@ -248,7 +248,7 @@ void test_enum_integral_compare() { (void)(A <=> (unsigned)0); (void)((unsigned)0 <=> A); - (void)(ANeg <=> (unsigned)0); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned int'}} + (void)(ANeg <=> (unsigned)0); // expected-error {{argument to 'operator<=>' evaluates to (EnumA)-1, which cannot be narrowed to type 'unsigned int'}} (void)((unsigned)0 <=> ANeg); // expected-error {{cannot be narrowed}} (void)(B <=> 42); @@ -258,7 +258,7 @@ void test_enum_integral_compare() { (void)(BMax <=> (unsigned long)-1); (void)(C0 <=> (unsigned)42); - (void)(C <=> (unsigned)42); // expected-error {{argument to 'operator<=>' evaluates to -1, which cannot be narrowed to type 'unsigned int'}} + (void)(C <=> (unsigned)42); // expected-error {{argument to 'operator<=>' evaluates to (EnumC)-1, which cannot be narrowed to type 'unsigned int'}} } namespace EnumCompareTests { diff --git a/clang/test/SemaCXX/recovery-expr-type.cpp b/clang/test/SemaCXX/recovery-expr-type.cpp index 479039f2847998..490b5dd908c18e 100644 --- a/clang/test/SemaCXX/recovery-expr-type.cpp +++ b/clang/test/SemaCXX/recovery-expr-type.cpp @@ -152,7 +152,7 @@ enum Circular { // expected-note {{not complete until the closing '} }; // Enumerators can be evaluated (they evaluate as zero, but we don't care). static_assert(Circular_A == 0 && Circular_A != 0, ""); // expected-error {{static assertion failed}} \ - // expected-note {{evaluates to '0 != 0'}} + // expected-note {{evaluates to 'Circular_A != 0'}} } namespace test14 { diff --git a/clang/test/SemaCXX/static-assert-cxx17.cpp b/clang/test/SemaCXX/static-assert-cxx17.cpp index 1d78915aa13e18..b6cc4a71c850b7 100644 --- a/clang/test/SemaCXX/static-assert-cxx17.cpp +++ b/clang/test/SemaCXX/static-assert-cxx17.cpp @@ -94,7 +94,7 @@ void foo6() { // expected-error@-1{{static assertion failed due to requirement '(const X<int> *)nullptr'}} static_assert(static_cast<const X<typename T::T> *>(nullptr)); // expected-error@-1{{static assertion failed due to requirement 'static_cast<const X<int> *>(nullptr)'}} - static_assert((const X<typename T::T>[]){} == nullptr); // expected-note{{expression evaluates to '{} == nullptr'}} + static_assert((const X<typename T::T>[]){} == nullptr); // expected-note{{expression evaluates to '(const X<typename ExampleTypes::T>[0]){} == nullptr'}} // expected-error@-1{{static assertion failed due to requirement '(const X<int>[0]){} == nullptr'}} static_assert(sizeof(X<decltype(X<typename T::T>().X<typename T::T>::~X())>) == 0); // expected-error@-1{{static assertion failed due to requirement 'sizeof(X<void>) == 0'}} \ diff --git a/clang/test/SemaCXX/static-assert-diagnostics.cpp b/clang/test/SemaCXX/static-assert-diagnostics.cpp index 352f6622676ce5..bc05992a5b237b 100644 --- a/clang/test/SemaCXX/static-assert-diagnostics.cpp +++ b/clang/test/SemaCXX/static-assert-diagnostics.cpp @@ -7,7 +7,7 @@ struct A { constexpr auto a0 = A{0, 0, 3, 4, 5}; -// expected-note@+1 {{evaluates to 'A{0, {0, 3, 4}, 5} == A{1, {2, 3, 4}, 5}'}} +// expected-note@+1 {{evaluates to '(const A){0, {0, 3, 4}, 5} == A{1, {2, 3, 4}, 5}'}} static_assert(a0 == A{1, {2, 3, 4}, 5}); // expected-error {{failed}} struct _arr { @@ -20,9 +20,15 @@ struct _arr { } }; -// expected-note@+1 {{{evaluates to '_arr{{2, 3, 4}} == (int[3]){0, 3, 4}'}}} +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc99-extensions" +static_assert(_arr{{2, 3, 4}} == (const int[3]){2, 3, 4}); +#pragma clang diagnostic pop + +// expected-note@+1 {{{evaluates to '_arr{{2, 3, 4}} == (const int[3]){0, 3, 4}'}}} static_assert(_arr{2, 3, 4} == a0.b); // expected-error {{failed}} + struct B { int a, c; // named the same just to keep things fresh bool operator==(const B&) const = default; @@ -40,9 +46,56 @@ struct C: A, B { constexpr auto cc = C{A{1, {2, 3, 4}, 5}, B{7, 6}, C::E1}; -// expected-note@+1 {{{evaluates to 'C{{1, {2, 3, 4}, 5}, {7, 6}, 0} == C{{0, {0, 3, 4}, 5}, {5, 0}, 1}'}}} +// expected-note@+1 {{{evaluates to '(const C){{1, {2, 3, 4}, 5}, {7, 6}, C::E1} == C{{0, {0, 3, 4}, 5}, {5, 0}, C::E2}'}}} static_assert(cc == C{a0, {5}, C::E2}); // expected-error {{failed}} +enum E { numerator }; +constexpr E e = E::numerator; +static_assert(numerator == ((E)0)); +static_assert(((E)0) == ((E)7)); // expected-error {{failed}} +// expected-note@-1 {{{evaluates to 'numerator == (E)7'}}} + +typedef enum { something } MyEnum; +static_assert(MyEnum::something == ((MyEnum)7)); // expected-error {{failed}} +// expected-note@-1 {{{evaluates to 'something == (MyEnum)7'}}} + +// unnamed enums +static_assert(C::E1 == (decltype(C::e))0); +// expected-note@+1 {{{evaluates to 'C::E1 == C::E2'}}} +static_assert(C::E1 == (decltype(C::e))1); // expected-error {{failed}} +static_assert(C::E1 == (decltype(C::e))7); // expected-error {{failed}} +// expected-note@-1 {{{evaluates to 'C::E1 == (decltype(C::e))7'}}} + +constexpr enum { declLocal } ee = declLocal; +static_assert(((decltype(ee))0) == ee); +static_assert(((decltype(ee))0) == ((decltype(ee))7)); // expected-error {{failed}} +// expected-note@-1 {{{evaluates to 'declLocal == (decltype(ee))7'}}} + +struct TU { + enum { S, U } Tag; + union { + signed int s; + unsigned int u; + }; + constexpr bool operator==(const TU& rhs) const { + if (Tag != rhs.Tag) return false; + switch (Tag) { + case S: + return s == rhs.s; + case U: + return u == rhs.u; + } + }; +}; +static_assert(TU{TU::S, {7}} == TU{TU::S, {.s=7}}); +static_assert(TU{TU::U, {.u=9}} == TU{TU::U, {.u=9}}); + +// expected-note@+1 {{{evaluates to 'TU{TU::S, {.s = 7}} == TU{TU::S, {.s = 6}}'}}} +static_assert(TU{TU::S, {.s=7}} == TU{TU::S, {.s=6}}); // expected-error {{failed}} +static_assert(TU{TU::U, {.u=7}} == TU{TU::U, {.u=9}}); // expected-error {{failed}} +// expected-note@-1 {{{evaluates to 'TU{TU::U, {.u = 7}} == TU{TU::U, {.u = 9}}'}}} + + // define `std::bit_cast` as a helper for doing constexpr vector comparisons namespace std { template <class To, class From> @@ -107,7 +160,6 @@ constexpr bool operator==(const BV& lhs, const bool8& rhs) { return lhs == BV{rhs}; } - // expected-note@+1 {{{evaluates to 'BV{{false, true, false, false, false, false, false, false}} == BV{{true, false, false, false, false, false, false, false}}'}}} static_assert(BV{{0, 1}} == BV{{1, 0}}); // expected-error {{failed}} >From 86b0623407315a908d8489a6e3162bcfcc275e02 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Sun, 14 Jan 2024 09:43:03 -0800 Subject: [PATCH 9/9] fixup! [Clang][Sema] Print enumerators by name --- clang/lib/Sema/SemaDeclCXX.cpp | 19 ++++++--- .../CXX/class/class.compare/class.rel/p2.cpp | 10 ++--- .../over.match.oper/p9-2a.cpp | 2 +- .../SemaCXX/static-assert-diagnostics.cpp | 42 ++++++++++++++++++- 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 20a2495e001462..c05dc5f8e9ecc5 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -17144,8 +17144,6 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, if (!V.hasValue()) return false; - PrintingPolicy Policy(Context.getPrintingPolicy()); - Policy.UseEnumerators = true; switch (V.getKind()) { case APValue::ValueKind::Int: if (T->isBooleanType()) { @@ -17189,6 +17187,8 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, } // print enums as either their named value or a cast if (T->isEnumeralType()) { + PrintingPolicy Policy(Context.getPrintingPolicy()); + Policy.UseEnumerators = true; llvm::raw_svector_ostream OS(Str); V.printPretty(OS, Policy, T, &Context); break; @@ -17231,16 +17231,25 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, case APValue::ValueKind::Array: case APValue::ValueKind::Vector: { llvm::raw_svector_ostream OS(Str); + // we hope to emit a valid initalizer expression + // like `(const int[3]){1, 2, 3}` OS << '(' << T << ')'; + PrintingPolicy Policy(Context.getPrintingPolicy()); + Policy.UseEnumerators = true; V.printPretty(OS, Policy, T, &Context); } break; case APValue::ValueKind::Struct: { llvm::raw_svector_ostream OS(Str); if (T.hasQualifiers()) - OS << '(' << T << ")"; - else - OS << T; + OS << '(' << T << ')'; + else { + PrintingPolicy TyPolicy(Context.getPrintingPolicy()); + TyPolicy.SuppressUnwrittenScope = true; + T.print(OS, TyPolicy); + } + PrintingPolicy Policy(Context.getPrintingPolicy()); + Policy.UseEnumerators = true; V.printPretty(OS, Policy, T, &Context); } break; 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 07501c6a081841..19f951df61c769 100644 --- a/clang/test/CXX/class/class.compare/class.rel/p2.cpp +++ b/clang/test/CXX/class/class.compare/class.rel/p2.cpp @@ -10,15 +10,15 @@ namespace Rel { friend bool operator>=(const A&, const A&) = default; }; static_assert(A{0} < A{1}); - static_assert(A{1} < A{1}); // expected-error {{failed}} expected-note {{'{1} < {1}'}} + static_assert(A{1} < A{1}); // expected-error {{failed}} expected-note {{'A{1} < A{1}'}} static_assert(A{0} <= A{1}); static_assert(A{1} <= A{1}); - static_assert(A{2} <= A{1}); // expected-error {{failed}} expected-note {{'{2} <= {1}'}} + static_assert(A{2} <= A{1}); // expected-error {{failed}} expected-note {{'A{2} <= A{1}'}} static_assert(A{1} > A{0}); - static_assert(A{1} > A{1}); // expected-error {{failed}} expected-note {{'{1} > {1}'}} + static_assert(A{1} > A{1}); // expected-error {{failed}} expected-note {{'A{1} > A{1}'}} static_assert(A{1} >= A{0}); static_assert(A{1} >= A{1}); - static_assert(A{1} >= A{2}); // expected-error {{failed}} expected-note {{'{1} >= {2}'}} + static_assert(A{1} >= A{2}); // expected-error {{failed}} expected-note {{'A{1} >= A{2}'}} struct B { bool operator<=>(B) const = delete; // expected-note 4{{deleted here}} expected-note-re 8{{candidate {{.*}} deleted}} @@ -49,7 +49,7 @@ namespace NotEqual { friend bool operator!=(const A&, const A&) = default; }; static_assert(A{1} != A{2}); - static_assert(A{1} != A{1}); // expected-error {{failed}} expected-note {{'{1} != {1}'}} + static_assert(A{1} != A{1}); // expected-error {{failed}} expected-note {{'A{1} != A{1}'}} struct B { bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}} diff --git a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp index 8f31e8947a768c..80980f83ef1b4b 100644 --- a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp +++ b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp @@ -33,7 +33,7 @@ struct Y {}; constexpr bool operator==(X x, Y) { return x.equal; } static_assert(X{true} == Y{}); -static_assert(X{false} == Y{}); // expected-error {{failed}} expected-note{{'{false} == {}'}} +static_assert(X{false} == Y{}); // expected-error {{failed}} expected-note{{'X{false} == Y{}'}} // x == y -> y == x static_assert(Y{} == X{true}); diff --git a/clang/test/SemaCXX/static-assert-diagnostics.cpp b/clang/test/SemaCXX/static-assert-diagnostics.cpp index bc05992a5b237b..ecc04e8c4c8a27 100644 --- a/clang/test/SemaCXX/static-assert-diagnostics.cpp +++ b/clang/test/SemaCXX/static-assert-diagnostics.cpp @@ -1,4 +1,5 @@ -// RUN: %clang_cc1 -std=c++2a -verify %s +// RUN: %clang_cc1 -std=c++2a -verify -fsyntax-only -triple wasm32 %s +// RUN: %clang_cc1 -std=c++2a -verify -fsyntax-only -triple aarch64_be-linux-gnu %s struct A { int a, b[3], c; @@ -10,6 +11,7 @@ constexpr auto a0 = A{0, 0, 3, 4, 5}; // expected-note@+1 {{evaluates to '(const A){0, {0, 3, 4}, 5} == A{1, {2, 3, 4}, 5}'}} static_assert(a0 == A{1, {2, 3, 4}, 5}); // expected-error {{failed}} +// `operator==` wrapper type struct _arr { const int b[3]; constexpr bool operator==(const int rhs[3]) const { @@ -95,8 +97,22 @@ static_assert(TU{TU::S, {.s=7}} == TU{TU::S, {.s=6}}); // expected-error {{faile static_assert(TU{TU::U, {.u=7}} == TU{TU::U, {.u=9}}); // expected-error {{failed}} // expected-note@-1 {{{evaluates to 'TU{TU::U, {.u = 7}} == TU{TU::U, {.u = 9}}'}}} +struct EnumArray { + const E nums[3]; + constexpr bool operator==(const E rhs[3]) const { + for (unsigned i = 0; i < sizeof(nums) / sizeof(E); i++) + if (nums[i] != rhs[i]) + return false; + return true; + + }; +}; +static_assert(EnumArray{} == (const E[3]){numerator}); + +// expected-note@+1 {{{evaluates to 'EnumArray{{}} == (const E[3]){numerator, (const E)1, (const E)2}'}}} +static_assert(EnumArray{} == (const E[3]){(E)0, (E)1, (E)2}); // expected-error {{failed}} -// define `std::bit_cast` as a helper for doing constexpr vector comparisons +// define `std::bit_cast` namespace std { template <class To, class From> constexpr To bit_cast(const From &from) { @@ -105,6 +121,7 @@ constexpr To bit_cast(const From &from) { } } // namespace std +namespace vector { typedef int v4si __attribute__((__vector_size__(16))); struct V { @@ -165,3 +182,24 @@ static_assert(BV{{0, 1}} == BV{{1, 0}}); // expected-error {{failed}} // expected-note@+1 {{{evaluates to 'BV{{false, true, false, false, false, false, false, false}} == (bool8){true, false, false, false, false, false, false, false}'}}} static_assert(BV{{0, 1}} == (bool8){true, false}); // expected-error {{failed}} +} // namespace vector + +namespace { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +constexpr auto bits = 0x030201; +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +constexpr auto bits = 0x01020300; +#else +#error "don't know what to do with mixed endianness" +#endif + +struct alignas(decltype(bits)) S { +unsigned char a, b, c; +}; +// confusing `==` on purpose +constexpr bool operator==(const S&, const S&) { return false; } + +// the note should clearly implicate the `==` implementation +// expected-note@+1 {{{evaluates to 'S{1, 2, 3} == S{1, 2, 3}'}}} +static_assert(S{1, 2, 3} == std::bit_cast<S>(bits)); // expected-error {{failed}} +} // namespace _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits