[clang] [Clang][Sema] Print more static_assert exprs (PR #74852)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Jan 14 09:46:53 PST 2024
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 <seth at 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 at -1{{static assertion failed due to requirement '(const X<int> *)nullptr'}}
static_assert(static_cast<const X<typename T::T> *>(nullptr));
// expected-error at -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 at -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 at -1{{static assertion failed due to requirement 'sizeof(X<void>) == 0'}} \
>From 2fd5fc464868a682cb4b66ea4e97a6a7100ab120 Mon Sep 17 00:00:00 2001
From: Seth Pellegrino <seth at 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 at +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 at +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 at +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 at +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 at +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 at +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 <seth at 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 at +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 <seth at 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 at +1 {{evaluates to}}
+// expected-note at +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 at +1 {{evaluates to}}
+// expected-note at +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 at +1 {{evaluates to}}
+// expected-note at +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 at +1 {{evaluates to}}
+// expected-note at +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 <seth at 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 at +1 {{{evaluates to '{{2, 3, 4}} == {0, 3, 4}'}}}
+// output: '{{2, 3, 4}} == {0, 3, 4}' (the `{{` breaks VerifyDiagnosticConsumer::ParseDirective)
+// expected-note at +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 at +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 at +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 at +1 {{{evaluates to '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}'}}}
+// '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}'
+// expected-note at +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 at +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 at +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 <seth at 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 at +1 {{evaluates to}}
+// expected-note at +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 at +1 {{evaluates to}}
+// expected-note at +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 at +1 {{evaluates to}}
+// expected-note at +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 at +1 {{evaluates to}}
+// expected-note at +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 <seth at 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 at +1 {{evaluates to '{0, {0, 3, 4}, 5} == {1, {2, 3, 4}, 5}'}}
+// expected-note at +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 at +1 {{{evaluates to '{{2, 3, 4}} == {0, 3, 4}'}}}
+// expected-note at +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 at +1 {{evaluates to '{7, 6} == {8, 6}'}}
+// expected-note at +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 at +1 {{{evaluates to '{{1, {2, 3, 4}, 5}, {7, 6}, 0} == {{0, {0, 3, 4}, 5}, {5, 0}, 1}'}}}
+// expected-note at +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 at +1 {{{evaluates to '{{1, 2, 3, 4}} == {{1, 2, 0, 4}}'}}}
+// expected-note at +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 at +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 at +1 {{{evaluates to '{{false, true, false, false, false, false, false, false}} == {{true, false, false, false, false, false, false, false}}'}}}
+
+// expected-note at +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 at +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 <seth at 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 at -1{{static assertion failed due to requirement '(const X<int> *)nullptr'}}
static_assert(static_cast<const X<typename T::T> *>(nullptr));
// expected-error at -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 at -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 at -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 at +1 {{evaluates to 'A{0, {0, 3, 4}, 5} == A{1, {2, 3, 4}, 5}'}}
+// expected-note at +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 at +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 at +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 at +1 {{{evaluates to 'C{{1, {2, 3, 4}, 5}, {7, 6}, 0} == C{{0, {0, 3, 4}, 5}, {5, 0}, 1}'}}}
+// expected-note at +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 at -1 {{{evaluates to 'numerator == (E)7'}}}
+
+typedef enum { something } MyEnum;
+static_assert(MyEnum::something == ((MyEnum)7)); // expected-error {{failed}}
+// expected-note at -1 {{{evaluates to 'something == (MyEnum)7'}}}
+
+// unnamed enums
+static_assert(C::E1 == (decltype(C::e))0);
+// expected-note at +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 at -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 at -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 at +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 at -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 at +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 <seth at 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 at +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 at -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 at +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 at +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 at +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
More information about the cfe-commits
mailing list