[clang-tools-extra] [llvm] [clang] [Clang][Sema] Print more static_assert exprs (PR #74852)

via llvm-commits llvm-commits at lists.llvm.org
Fri Jan 19 09:04:09 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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

>From 8db11a3b420afcae3a31f4f976e65031fb83d2f1 Mon Sep 17 00:00:00 2001
From: Seth Pellegrino <seth at 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 0c7836c2ea569c..342b3f45728730 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 6d0c1b14acc120..4e2dcc2b720f19 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 099982c3bdd5a0..95ca6ce0d46a63 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 0eec2b35263762..b6ec6af61b9dce 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 cc8c138233ca1d..0cfcb45dd0e8fd 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 f02f7c841b91f0..c96bd93f32d524 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 c05dc5f8e9ecc5..216ab610c87abf 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 19f951df61c769..90115284d2bd02 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 80980f83ef1b4b..95d6a55aee66a1 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 ecc04e8c4c8a27..5648c69328ddc2 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 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;
 };
 
 // expected-note at +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 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}}
+static_assert(V{1, 2, 3, 4} == [] { return V{1, 2, 0, 4}; }()); // expected-error {{failed}}
 // 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}}
+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 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}}
+// expected-error at +2 {{static assertion failed due to requirement '(anonymous namespace)::S{1, 2, 3} == std::bit_cast(bits)'}}
+// 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));
+
+// but there should be no redundant notes
+// expected-error at +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 at +1 {{evaluates to 'S{1, 2, 0} == S{1, 2, 0}'}}
+static_assert(S{1, 2} == S{1, 2}); // expected-error {{failed}}
+// expected-note at +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 at +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 at +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 at +6 {{'32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230655 == 1'}}
+#elif TEST_CLIP == SMALL // fixme: see https://github.com/llvm/llvm-project/issues/71675
+  // expected-note at +4 {{'32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230655 == 1'}}
+#else // fixme: as above
+  // expected-note at +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 at +6 {{'(decltype(clipping_large_values::my_god_its_full_of_bits)){32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230655} == 1'}}
+#elif TEST_CLIP == SMALL
+  // expected-note at +4 {{'(decltype(clipping_large_values::my_god_its_full_of_bits)){32317006071311007300714876688 ...(+559 bytes)... 52148193555853611059596230655} == 1'}}
+#else
+  // expected-note at +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 <seth at 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 53c4dda133301b..04db022fe73021 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 <seth at 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 392f694065a242..6254d1ff0d12ef 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



More information about the llvm-commits mailing list