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 01/12] [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 c6218a491aece..e3d46c3140741 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 04db022fe7302..53c4dda133301 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 90115284d2bd0..07501c6a08184 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 95d6a55aee66a..8f31e8947a768 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 41a7b025d0eb7..1d78915aa13e1 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 02/12] 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 0000000000000..432a3f4bd5465 --- /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 03/12] 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 432a3f4bd5465..a92a6d5fb08c3 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 04/12] [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 715e0c0dc8fa8..4bf0ab54a046c 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 ab8174f4f4db9..5eab7bd3619f1 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 a92a6d5fb08c3..f6c38c0c7313b 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 05/12] 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 4bf0ab54a046c..715e0c0dc8fa8 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 5eab7bd3619f1..ab8174f4f4db9 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 f6c38c0c7313b..a92a6d5fb08c3 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 06/12] 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 715e0c0dc8fa8..4bf0ab54a046c 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 ab8174f4f4db9..5eab7bd3619f1 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 a92a6d5fb08c3..f6c38c0c7313b 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 07/12] 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 e3d46c3140741..9cad7dffd1a79 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 f6c38c0c7313b..352f6622676ce 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 08/12] [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 4eae308ef5b34..d9689db6f4e5f 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 9cad7dffd1a79..20a2495e00146 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 d629be083d8c3..b6df7ff39031e 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 15a0baccfca17..e055075be5e6f 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 479039f284799..490b5dd908c18 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 1d78915aa13e1..b6cc4a71c850b 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 352f6622676ce..bc05992a5b237 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 09/12] 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 20a2495e00146..c05dc5f8e9ecc 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 07501c6a08184..19f951df61c76 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 8f31e8947a768..80980f83ef1b4 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 bc05992a5b237..ecc04e8c4c8a2 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 >From 8db11a3b420afcae3a31f4f976e65031fb83d2f1 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Wed, 17 Jan 2024 10:49:28 -0800 Subject: [PATCH 10/12] [Clang][Sema] Print limited, non-redundant vals Introduces size_limited_ostream which clips output sent to it to a maximum number of leading/trailing bytes. Makes use of that new facility to avoid emitting overly large struct or vector/array values (unless explicitly requested with the new `-fconstexpr-print-value-size-limit`. Additionally, look a little deeper in the expr tree to try and avoid printing redundant notes when the expressions evaluate exactly as-written. --- clang/include/clang/Basic/Diagnostic.h | 14 ++ .../include/clang/Basic/DiagnosticOptions.def | 2 + clang/include/clang/Basic/DiagnosticOptions.h | 1 + clang/include/clang/Driver/Options.td | 4 + clang/lib/Basic/Warnings.cpp | 2 + clang/lib/Driver/ToolChains/Clang.cpp | 1 + clang/lib/Sema/SemaDeclCXX.cpp | 187 +++++++++++++++++- .../CXX/class/class.compare/class.rel/p2.cpp | 10 +- .../over.match.oper/p9-2a.cpp | 2 +- .../SemaCXX/static-assert-diagnostics.cpp | 70 ++++++- 10 files changed, 272 insertions(+), 21 deletions(-) diff --git a/clang/include/clang/Basic/Diagnostic.h b/clang/include/clang/Basic/Diagnostic.h index 0c7836c2ea569..342b3f4572873 100644 --- a/clang/include/clang/Basic/Diagnostic.h +++ b/clang/include/clang/Basic/Diagnostic.h @@ -290,6 +290,10 @@ class DiagnosticsEngine : public RefCountedBase<DiagnosticsEngine> { // Cap on depth of constexpr evaluation backtrace stack, 0 -> no limit. unsigned ConstexprBacktraceLimit = 0; + // Cap on size of value in a constexpr diagnostic without ellipsis, 0 -> no + // limit. + unsigned ConstexprValueSizeLimit = 0; + IntrusiveRefCntPtr<DiagnosticIDs> Diags; IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts; DiagnosticConsumer *Client = nullptr; @@ -644,6 +648,16 @@ class DiagnosticsEngine : public RefCountedBase<DiagnosticsEngine> { return ConstexprBacktraceLimit; } + /// Specify the maximum size of a value to print without ellipsis. + void setConstexprValueSizeLimit(unsigned Limit) { + ConstexprValueSizeLimit = Limit; + } + + /// Retrieve the maximum size of a value to print without ellipsis. + unsigned getConstexprValueSizeLimit() const { + return ConstexprValueSizeLimit; + } + /// When set to true, any unmapped warnings are ignored. /// /// If this and WarningsAsErrors are both set, then this one wins. diff --git a/clang/include/clang/Basic/DiagnosticOptions.def b/clang/include/clang/Basic/DiagnosticOptions.def index 6d0c1b14acc12..4e2dcc2b720f1 100644 --- a/clang/include/clang/Basic/DiagnosticOptions.def +++ b/clang/include/clang/Basic/DiagnosticOptions.def @@ -86,6 +86,8 @@ VALUE_DIAGOPT(MacroBacktraceLimit, 32, DefaultMacroBacktraceLimit) VALUE_DIAGOPT(TemplateBacktraceLimit, 32, DefaultTemplateBacktraceLimit) /// Limit depth of constexpr backtrace. VALUE_DIAGOPT(ConstexprBacktraceLimit, 32, DefaultConstexprBacktraceLimit) +/// Limit size of constexpr diagnostic value. +VALUE_DIAGOPT(ConstexprValueSizeLimit, 32, DefaultConstexprValueSizeLimit) /// Limit number of times to perform spell checking. VALUE_DIAGOPT(SpellCheckingLimit, 32, DefaultSpellCheckingLimit) /// Limit number of lines shown in a snippet. diff --git a/clang/include/clang/Basic/DiagnosticOptions.h b/clang/include/clang/Basic/DiagnosticOptions.h index 099982c3bdd5a..95ca6ce0d46a6 100644 --- a/clang/include/clang/Basic/DiagnosticOptions.h +++ b/clang/include/clang/Basic/DiagnosticOptions.h @@ -84,6 +84,7 @@ class DiagnosticOptions : public RefCountedBase<DiagnosticOptions>{ DefaultMacroBacktraceLimit = 6, DefaultTemplateBacktraceLimit = 10, DefaultConstexprBacktraceLimit = 10, + DefaultConstexprValueSizeLimit = 320, DefaultSpellCheckingLimit = 50, DefaultSnippetLineLimit = 16, DefaultShowLineNumbers = 1, diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 0eec2b3526376..b6ec6af61b9dc 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1878,6 +1878,10 @@ def fconstexpr_backtrace_limit_EQ : Joined<["-"], "fconstexpr-backtrace-limit="> Visibility<[ClangOption, CC1Option]>, HelpText<"Set the maximum number of entries to print in a constexpr evaluation backtrace (0 = no limit)">, MarshallingInfoInt<DiagnosticOpts<"ConstexprBacktraceLimit">, "DiagnosticOptions::DefaultConstexprBacktraceLimit">; +def fconstexpr_print_value_size_limit_EQ : Joined<["-"], "fconstexpr-print-value-size-limit=">, Group<f_Group>, + Visibility<[ClangOption, CC1Option]>, + HelpText<"Set the maximum size of a value in a constexpr diagnostic to be printed without ellipsis (0 = no limit)">, + MarshallingInfoInt<DiagnosticOpts<"ConstexprValueSizeLimit">, "DiagnosticOptions::DefaultConstexprValueSizeLimit">; def fcrash_diagnostics_EQ : Joined<["-"], "fcrash-diagnostics=">, Group<f_clang_Group>, Flags<[NoArgumentUnused]>, Visibility<[ClangOption, CLOption, DXCOption]>, HelpText<"Set level of crash diagnostic reporting, (option: off, compiler, all)">; diff --git a/clang/lib/Basic/Warnings.cpp b/clang/lib/Basic/Warnings.cpp index cc8c138233ca1..0cfcb45dd0e8f 100644 --- a/clang/lib/Basic/Warnings.cpp +++ b/clang/lib/Basic/Warnings.cpp @@ -59,6 +59,8 @@ void clang::ProcessWarningOptions(DiagnosticsEngine &Diags, Diags.setTemplateBacktraceLimit(Opts.TemplateBacktraceLimit); if (Opts.ConstexprBacktraceLimit) Diags.setConstexprBacktraceLimit(Opts.ConstexprBacktraceLimit); + if (Opts.ConstexprValueSizeLimit) + Diags.setConstexprValueSizeLimit(Opts.ConstexprValueSizeLimit); // If -pedantic or -pedantic-errors was specified, then we want to map all // extension diagnostics onto WARNING or ERROR unless the user has futz'd diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index f02f7c841b91f..c96bd93f32d52 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -6135,6 +6135,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back("19"); Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_backtrace_limit_EQ); + Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_print_value_size_limit_EQ); Args.AddLastArg(CmdArgs, options::OPT_fmacro_backtrace_limit_EQ); Args.AddLastArg(CmdArgs, options::OPT_ftemplate_backtrace_limit_EQ); Args.AddLastArg(CmdArgs, options::OPT_fspell_checking_limit_EQ); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index c05dc5f8e9ecc..216ab610c87ab 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -22,10 +22,12 @@ #include "clang/AST/EvaluatedExprVisitor.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/OperationKinds.h" #include "clang/AST/PrettyPrinter.h" #include "clang/AST/RecordLayout.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/StmtVisitor.h" +#include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" #include "clang/AST/TypeOrdering.h" #include "clang/Basic/AttributeCommonInfo.h" @@ -52,7 +54,11 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/SaveAndRestore.h" +#include "llvm/Support/raw_ostream.h" +#include <algorithm> +#include <cstring> #include <map> +#include <memory> #include <optional> #include <set> @@ -17136,6 +17142,113 @@ static void WriteCharValueForDiagnostic(uint32_t Value, const BuiltinType *BTy, } } +/// A raw_ostream that clips its output beyond a certain size, inserting a +/// human-readable message about how much output was skipped. +/// +/// size_limited_ostream acts as a passthrough to the underlying raw_ostream +/// until too many bytes have been written, at which point it buffers up to +/// \p MaxTrailing bytes to emit an output like: +/// +/// `leading ...(+ NN bytes)... trailing` +/// +/// NB: since size_limited_ostream prints a diagnostic, when the output +/// overflows it will write more bytes to the underlying \p OS than strictly +/// requested. +/// +/// fixme: size_limited_ostream will happily e.g. "snap off" a utf-8 sequence or +/// utf-16 surrogate pair, or break a combining sequence in the middle. Since +/// this is intended for human eyes, probably it should be counting in terms of +/// graphemes instead of raw bytes, but then it would have to know the +/// underlying encoding of the stream. +class size_limited_ostream : public llvm::raw_ostream { + size_t Seen; + raw_ostream &OS; + SmallVector<char, 0> Buffer; + +public: + const size_t MaxLeading, MaxTrailing; + + /// Construct a size_limited_ostream. + /// + /// \param OS the wrapped output stream + /// \param Limit the maximum number of bytes to print, split evenly + /// before and after the ellipsis. May be zero. + explicit size_limited_ostream(llvm::raw_ostream &OS, size_t Limit) + : size_limited_ostream(OS, Limit >> 1, Limit >> 1) {} + + /// Construct a size_limited_ostream. + /// + /// \param OS the wrapped output stream + /// \param MaxLeading the maximum number of "leading" bytes to print + /// (i.e. before the ellipsis). May be zero. + /// \param MaxTrailing the maximum numberof "trailing" bytes to print + /// (i.e. after the ellipsis. May be zero.) + explicit size_limited_ostream(llvm::raw_ostream &OS, size_t MaxLeading, + size_t MaxTrailing) + : Seen(0), OS(OS), MaxLeading(MaxLeading), MaxTrailing(MaxTrailing) { + SetUnbuffered(); // avoid using the buffering facilities in raw_ostream, + // which don't quite work how we'd like + Buffer.reserve(MaxTrailing); + }; + ~size_limited_ostream() override { + if (Seen > limit()) { + if (MaxLeading) + OS << " ..."; + OS << "(+" << omitted() << " bytes)"; + if (MaxTrailing) + OS << "... "; + } + OS << StringRef(Buffer.data(), Buffer.size()); + }; + void flush() = delete; + + inline size_t limit() { return MaxLeading + MaxTrailing; } + inline size_t omitted() { + if (Seen < limit()) + return 0; + return Seen - limit(); + } + uint64_t current_pos() const override { return Seen; } + + void write_impl(const char *Ptr, size_t Size) override { + size_t N = 0; + // write directly to the wrapped OS until we satisfy MaxLeading + if (Seen < MaxLeading) { + N = std::min(Size, MaxLeading - Seen); + OS.write(Ptr, N); + } + // write up to MaxTrailing bytes into Buffer + size_t Rem = Size - N; + const char *End = Ptr + Size; + if (Buffer.size() + Rem <= MaxTrailing) { + // write Rem bytes + Buffer.append(Ptr + N, End); + } else if (Rem >= MaxTrailing) { + // overwrite the whole buffer + assert(Size >= + MaxTrailing); // else (End - MaxTrailing) would underflow, but + // it must be, because MaxTrailing < Rem <= Size + Buffer.clear(); + Buffer.append(End - MaxTrailing, End); + } else { + assert(Rem > 0); + // shift "left" just enough bytes to keep the buffer full + size_t Keep = MaxTrailing - Rem; + assert(Keep < Buffer.size()); // b/c (Buffer.size() + Rem) > MaxTrailing + // => Buffer.size() > (MaxTrailing - Rem) + std::move(Buffer.end() - Keep, Buffer.end(), Buffer.begin()); + Buffer.truncate(Keep); + // write in the new bits + Buffer.append(Ptr + N, End); + } + + Seen += Size; + assert(Buffer.capacity() == MaxTrailing); + assert((Seen < MaxLeading && Buffer.size() == 0) || + Buffer.size() == std::min(Seen - MaxLeading, MaxTrailing)); + } +}; + /// Convert \V to a string we can present to the user in a diagnostic /// \T is the type of the expression that has been evaluated into \V static bool ConvertAPValueToString(const APValue &V, QualType T, @@ -17236,21 +17349,46 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, OS << '(' << T << ')'; PrintingPolicy Policy(Context.getPrintingPolicy()); Policy.UseEnumerators = true; - V.printPretty(OS, Policy, T, &Context); + unsigned Limit = Context.getDiagnostics().getConstexprValueSizeLimit(); + if (Limit) { + size_limited_ostream OSS(OS, Limit); + V.printPretty(OSS, Policy, T, &Context); + } else + V.printPretty(OS, Policy, T, &Context); } break; case APValue::ValueKind::Struct: { llvm::raw_svector_ostream OS(Str); - if (T.hasQualifiers()) + const auto *RT = T->getAsStructureType(); + if (RT->hasUnnamedOrLocalType()) { + OS << '('; + // e.g. `(unnamed struct at ...)`, unless... + if (const auto *Defn = RT->getDecl()->getDefinition()) { + if (const NamedDecl *ND = + dyn_cast_if_present<NamedDecl>(Defn->getNextDeclInContext())) { + // ... it's part of a declaration, then use `(decltype(...))` + OS << "decltype("; + ND->printQualifiedName(OS); + OS << ")"; + } else + Defn->printQualifiedName(OS); + } + OS << ')'; + } else if (T.hasQualifiers()) { OS << '(' << T << ')'; - else { + } 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); + unsigned Limit = Context.getDiagnostics().getConstexprValueSizeLimit(); + if (Limit) { + size_limited_ostream OSS(OS, Limit); + V.printPretty(OSS, Policy, T, &Context); + } else + V.printPretty(OS, Policy, T, &Context); } break; default: @@ -17263,7 +17401,14 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, /// Some Expression types are not useful to print notes about, /// e.g. literals and values that have already been expanded /// before such as int-valued template parameters. -static bool UsefulToPrintExpr(const Expr *E) { +static bool UsefulToPrintExpr(const Expr *E, unsigned MaxDepth = ~0) { + if (MaxDepth == 0) + return true; + + // Always expand partial initializer lists + if (isa<ImplicitValueInitExpr>(E)) + return true; + E = E->IgnoreParenImpCasts(); // Literals are pretty easy for humans to understand. if (isa<IntegerLiteral, FloatingLiteral, CharacterLiteral, CXXBoolLiteralExpr, @@ -17275,15 +17420,41 @@ static bool UsefulToPrintExpr(const Expr *E) { if (isa<SubstNonTypeTemplateParmExpr>(E)) return false; - // -5 is also simple to understand. - if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E)) - return UsefulToPrintExpr(UnaryOp->getSubExpr()); + // -5 is also simple to understand + if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E)) { + // but print the result of e.g. `-(-5)` + return UsefulToPrintExpr(UnaryOp->getSubExpr(), std::min(1U, MaxDepth - 1)); + } // Only print nested arithmetic operators. if (const auto *BO = dyn_cast<BinaryOperator>(E)) return (BO->isShiftOp() || BO->isAdditiveOp() || BO->isMultiplicativeOp() || BO->isBitwiseOp()); + if (const auto *CE = dyn_cast<CastExpr>(E)) { + // It's usually useful to print an enum (to see whether it matches a + // constant) + if (CE->getType()->isEnumeralType()) + return true; + // There are useful diagnostics about the many ways to specify a character + if (CE->getType()->isAnyCharacterType()) + return true; + return UsefulToPrintExpr(CE->getSubExprAsWritten(), MaxDepth - 1); + } + + if (const auto *ILE = dyn_cast<InitListExpr>(E)) { + // for whatever reason, ExtVectors don't get `ImplicitValueInitExpr`s in + // unwritten slots + if (const auto *EVT = ILE->getType()->getAs<ExtVectorType>()) + if (ILE->getNumInits() < EVT->getNumElements()) + return true; + + for (const auto *Init : ILE->inits()) + if (UsefulToPrintExpr(Init, MaxDepth - 1)) + return true; + return false; + } + return true; } 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 19f951df61c76..90115284d2bd0 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 {{'A{1} < 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}} expected-note {{'A{2} <= 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}} expected-note {{'A{1} > A{1}'}} + 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}} expected-note {{'A{1} >= A{2}'}} + 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}} @@ -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 {{'A{1} != A{1}'}} + 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/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 80980f83ef1b4..95d6a55aee66a 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{{'X{false} == Y{}'}} +static_assert(X{false} == Y{}); // expected-error {{failed}} // 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 ecc04e8c4c8a2..5648c69328ddc 100644 --- a/clang/test/SemaCXX/static-assert-diagnostics.cpp +++ b/clang/test/SemaCXX/static-assert-diagnostics.cpp @@ -1,5 +1,8 @@ // 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 +// RUN: %clang_cc1 -std=c++2a -verify -fsyntax-only -triple aarch64_be %s +// RUN: %clang_cc1 -std=c++2a -verify -fsyntax-only -triple x86_64 -DTEST_CLIP %s +// RUN: %clang_cc1 -std=c++2a -verify -fsyntax-only -triple x86_64 -DTEST_CLIP=SMALL -fconstexpr-print-value-size-limit=60 %s +// RUN: %clang_cc1 -std=c++2a -verify -fsyntax-only -triple x86_64 -DTEST_CLIP=NO_LIMIT -fconstexpr-print-value-size-limit=0 %s struct A { int a, b[3], c; @@ -30,14 +33,13 @@ static_assert(_arr{{2, 3, 4}} == (const int[3]){2, 3, 4}); // 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; }; // expected-note@+1 {{evaluates to 'B{7, 6} == B{8, 6}'}} -static_assert(B{7, 6} == B{8, 6}); // expected-error {{failed}} +static_assert(B{7, 6} == [] { return B{8, 6}; }()); // expected-error {{failed}} typedef int v4si __attribute__((__vector_size__(16))); @@ -153,12 +155,14 @@ constexpr bool operator==(const V& lhs, const v4si& rhs) { return lhs == V{rhs}; } +constexpr auto vv = V{1, 2, 3, 4}; + static_assert(V{1, 2, 3, 4} == V{1, 2, 3, 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}} +static_assert(V{1, 2, 3, 4} == [] { return V{1, 2, 0, 4}; }()); // expected-error {{failed}} // 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}} +static_assert(V{1, 2, 3, 4} == [] { return (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 @@ -200,6 +204,58 @@ unsigned char a, b, c; 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}} +// expected-error@+2 {{static assertion failed due to requirement '(anonymous namespace)::S{1, 2, 3} == std::bit_cast(bits)'}} +// expected-note@+1 {{evaluates to 'S{1, 2, 3} == S{1, 2, 3}'}} +static_assert(S{1, 2, 3} == std::bit_cast<S>(bits)); + +// but there should be no redundant notes +// expected-error@+1 {{static assertion failed due to requirement '(anonymous namespace)::S{1, 2, 3} == (anonymous namespace)::S{1, 2, 3}'}} +static_assert(S{1, 2, 3} == S{1, 2, 3}); + +// more examples of notes considered "non-redundant" +// expected-note@+1 {{evaluates to 'S{1, 2, 0} == S{1, 2, 0}'}} +static_assert(S{1, 2} == S{1, 2}); // expected-error {{failed}} +// expected-note@+1 {{evaluates to 'S{1, 2, 3} == S{1, 2, 3}'}} +static_assert(S{1, 2, 3} == S{1 + 0, 2, 3}); // expected-error {{failed}} +// expected-note@+1 {{evaluates to 'S{1, 2, 3} == S{1, 2, 3}'}} +static_assert(S{1, 2, 3} == S{1 << 0, 2, 3}); // expected-error {{failed}} +// expected-note@+1 {{evaluates to 'S{1, 2, 3} == S{1, 2, 3}'}} +static_assert(S{1, 2, 3} == S{~~1, 2, 3}); // expected-error {{failed}} + } // namespace + +#ifdef TEST_CLIP +#define NO_LIMIT 'n' +#define SMALL 's' + +namespace clipping_large_values { + constexpr unsigned _BitInt(__BITINT_MAXWIDTH__ >> 12) Z = ~0; + +#if TEST_CLIP == NO_LIMIT + // expected-note@+6 {{'32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230655 == 1'}} +#elif TEST_CLIP == SMALL // fixme: see https://github.com/llvm/llvm-project/issues/71675 + // expected-note@+4 {{'32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230655 == 1'}} +#else // fixme: as above + // expected-note@+2 {{'32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230655 == 1'}} +#endif + static_assert(Z == 1); // expected-error {{failed}} + + constexpr struct { + unsigned _BitInt(__BITINT_MAXWIDTH__ >> 12) F; + + constexpr bool operator==(const unsigned int& v) const { + return F == v; + } + } my_god_its_full_of_bits = {(decltype(my_god_its_full_of_bits.F))~0}; + static_assert((decltype(my_god_its_full_of_bits)){} == 0); + +#if TEST_CLIP == NO_LIMIT + // expected-note@+6 {{'(decltype(clipping_large_values::my_god_its_full_of_bits)){32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230655} == 1'}} +#elif TEST_CLIP == SMALL + // expected-note@+4 {{'(decltype(clipping_large_values::my_god_its_full_of_bits)){32317006071311007300714876688 ...(+559 bytes)... 52148193555853611059596230655} == 1'}} +#else + // expected-note@+2 {{'(decltype(clipping_large_values::my_god_its_full_of_bits)){323170060713110073007148766886699519604441026697154840321303454275246551388678908931972014115229134636887179609218980194941195591504909210950881523864482831206 ...(+299 bytes)... 287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230655} == 1'}} +#endif + static_assert(my_god_its_full_of_bits == 1); // expected-error {{failed}} +} +#endif >From 2d3af43c0dd64e91f6afa8f4357cfc89a0665ce5 Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Thu, 18 Jan 2024 08:08:18 -0800 Subject: [PATCH 11/12] fixup! [Clang][Sema] Print limited, non-redundant vals --- .../CXX/class/class.compare/class.eq/p3.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 53c4dda133301..04db022fe7302 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}} 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}} +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}} 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}} 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}} +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}} >From 4953e13f432e46c360e9a983aceafe1b85f72b3a Mon Sep 17 00:00:00 2001 From: Seth Pellegrino <s...@codecopse.net> Date: Fri, 19 Jan 2024 09:01:19 -0800 Subject: [PATCH 12/12] fixup!: [doc] add release note --- clang/docs/ReleaseNotes.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 392f694065a24..6254d1ff0d12e 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -564,6 +564,37 @@ Improvements to Clang's diagnostics 48 | static_assert(1 << 4 == 15); | ~~~~~~~^~~~~ +- Clang will now print ``static_assert`` failure details for binary operators on + structs, vectors, or arrays. The diagnostic is limited in size (the limit may + be adjusted with `-fconstexpr-print-value-size-limit=N`), and is not emitted + when it would be redundant with the existing "requirement" diagnostic. + Example: + + .. code-block:: cpp + + struct S { + int a, b; + bool operator==(const S &) const = default; + }; + + static_assert(S{1, 2} == S{3, 4}); + constexpr auto f = [] { return S{3, 4}; }; + static_assert(S{1, 2} == f()); + + will now print: + + .. code-block:: text + + error: static assertion failed due to requirement 'S{1, 2} == S{3, 4}' + 85 | static_assert(S{1, 2} == S{3, 4}); + | ^~~~~~~~~~~~~~~~~~ + error: static assertion failed due to requirement 'S{1, 2} == f()' + 87 | static_assert(S{1, 2} == f()); + | ^~~~~~~~~~~~~~ + note: expression evaluates to 'S{1, 2} == S{3, 4}' + 87 | static_assert(S{1, 2} == f()); + | ~~~~~~~~^~~~~~ + - Clang now diagnoses definitions of friend function specializations, e.g. ``friend void f<>(int) {}``. - Clang now diagnoses narrowing conversions involving const references. (`#63151: <https://github.com/llvm/llvm-project/issues/63151>`_). @@ -611,6 +642,7 @@ Improvements to Clang's diagnostics inside namespace. The original diagnostic message is confusing. (`#73893: <https://github.com/llvm/llvm-project/issues/73893>`_) + Improvements to Clang's time-trace ---------------------------------- - Two time-trace scope variables are added. A time trace scope variable of _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits