[clang][sema] Print more information about failed static assertions
authorTimm Bäder <tbaeder@redhat.com>
Mon, 1 Aug 2022 12:12:32 +0000 (14:12 +0200)
committerTimm Bäder <tbaeder@redhat.com>
Thu, 11 Aug 2022 06:44:38 +0000 (08:44 +0200)
For failed static assertions, try to take the expression apart and print
useful information about why it failed. In particular, look at binary
operators and print the compile-time evaluated value of the LHS/RHS.

Differential Revision: https://reviews.llvm.org/D130894

18 files changed:
clang/docs/ReleaseNotes.rst
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Sema/Sema.h
clang/lib/Sema/SemaDeclCXX.cpp
clang/test/CXX/dcl.decl/dcl.meaning/dcl.array/p3.cpp
clang/test/CXX/drs/dr7xx.cpp
clang/test/CXX/temp/temp.decls/temp.variadic/init-capture.cpp
clang/test/Lexer/cxx1z-trigraphs.cpp
clang/test/PCH/cxx-templates.cpp
clang/test/Parser/objc-static-assert.mm
clang/test/Sema/static-assert.c
clang/test/SemaCXX/constant-expression-cxx11.cpp
clang/test/SemaCXX/recovery-expr-type.cpp
clang/test/SemaCXX/static-assert-cxx17.cpp
clang/test/SemaCXX/static-assert.cpp
clang/test/SemaTemplate/instantiate-var-template.cpp
clang/test/SemaTemplate/instantiation-dependence.cpp
clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp

index 3b1d2a4..5da9539 100644 (file)
@@ -96,6 +96,9 @@ Improvements to Clang's diagnostics
   missing when used, or vice versa. This makes sure that Clang picks the
   correct one, where it previously would consider multiple ones as potentially
   acceptable (and erroneously use whichever one is tried first).
+- Clang will now print more information about failed static assertions. In
+  particular, simple static assertion expressions are evaluated to their
+  compile-time value and printed out if the assertion fails.
 
 Non-comprehensive list of changes in this release
 -------------------------------------------------
index d794c68..92217ae 100644 (file)
@@ -1532,6 +1532,8 @@ def err_constexpr_if_condition_expression_is_not_constant : Error<
 def err_static_assert_failed : Error<"static assertion failed%select{: %1|}0">;
 def err_static_assert_requirement_failed : Error<
   "static assertion failed due to requirement '%0'%select{: %2|}1">;
+def note_expr_evaluates_to : Note<
+  "expression evaluates to '%0 %1 %2'">;
 
 def warn_consteval_if_always_true : Warning<
   "consteval if is always true in an %select{unevaluated|immediate}0 context">,
index 138b993..bb2fa13 100644 (file)
@@ -7480,6 +7480,7 @@ public:
                                      StringLiteral *AssertMessageExpr,
                                      SourceLocation RParenLoc,
                                      bool Failed);
+  void DiagnoseStaticAssertDetails(const Expr *E);
 
   FriendDecl *CheckFriendTypeDecl(SourceLocation LocStart,
                                   SourceLocation FriendLoc,
index ec2437c..41b7ff5 100644 (file)
@@ -16554,6 +16554,137 @@ Decl *Sema::ActOnStaticAssertDeclaration(SourceLocation StaticAssertLoc,
                                       AssertMessage, RParenLoc, false);
 }
 
+/// 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,
+                                   SmallVectorImpl<char> &Str) {
+  if (!V.hasValue())
+    return false;
+
+  switch (V.getKind()) {
+  case APValue::ValueKind::Int:
+    if (T->isBooleanType()) {
+      // Bools are reduced to ints during evaluation, but for
+      // diagnostic purposes we want to print them as
+      // true or false.
+      int64_t BoolValue = V.getInt().getExtValue();
+      assert((BoolValue == 0 || BoolValue == 1) &&
+             "Bool type, but value is not 0 or 1");
+      llvm::raw_svector_ostream OS(Str);
+      OS << (BoolValue ? "true" : "false");
+    } else if (T->isCharType()) {
+      // Same is true for chars.
+      Str.push_back('\'');
+      Str.push_back(V.getInt().getExtValue());
+      Str.push_back('\'');
+    } else
+      V.getInt().toString(Str);
+
+    break;
+
+  case APValue::ValueKind::Float:
+    V.getFloat().toString(Str);
+    break;
+
+  case APValue::ValueKind::LValue:
+    if (V.isNullPointer()) {
+      llvm::raw_svector_ostream OS(Str);
+      OS << "nullptr";
+    } else
+      return false;
+    break;
+
+  case APValue::ValueKind::ComplexFloat: {
+    llvm::raw_svector_ostream OS(Str);
+    OS << '(';
+    V.getComplexFloatReal().toString(Str);
+    OS << " + ";
+    V.getComplexFloatImag().toString(Str);
+    OS << "i)";
+  } break;
+
+  case APValue::ValueKind::ComplexInt: {
+    llvm::raw_svector_ostream OS(Str);
+    OS << '(';
+    V.getComplexIntReal().toString(Str);
+    OS << " + ";
+    V.getComplexIntImag().toString(Str);
+    OS << "i)";
+  } break;
+
+  default:
+    return false;
+  }
+
+  return true;
+}
+
+/// 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) {
+  E = E->IgnoreParenImpCasts();
+  // Literals are pretty easy for humans to understand.
+  if (isa<IntegerLiteral, FloatingLiteral, CharacterLiteral, CXXBoolLiteralExpr,
+          CXXNullPtrLiteralExpr, FixedPointLiteral, ImaginaryLiteral>(E))
+    return false;
+
+  // These have been substituted from template parameters
+  // and appear as literals in the static assert error.
+  if (isa<SubstNonTypeTemplateParmExpr>(E))
+    return false;
+
+  // -5 is also simple to understand.
+  if (const auto *UnaryOp = dyn_cast_or_null<UnaryOperator>(E))
+    return UsefulToPrintExpr(UnaryOp->getSubExpr());
+
+  // Ignore nested binary operators. This could be a FIXME for improvements
+  // to the diagnostics in the future.
+  if (isa<BinaryOperator>(E))
+    return false;
+
+  return true;
+}
+
+/// 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_or_null<BinaryOperator>(E)) {
+    const Expr *LHS = Op->getLHS()->IgnoreParenImpCasts();
+    const Expr *RHS = Op->getRHS()->IgnoreParenImpCasts();
+
+    // Ignore comparisons of boolean expressions with a boolean literal.
+    if ((isa<CXXBoolLiteralExpr>(LHS) && RHS->getType()->isBooleanType()) ||
+        (isa<CXXBoolLiteralExpr>(RHS) && LHS->getType()->isBooleanType()))
+      return;
+
+    // Don't print obvious expressions.
+    if (!UsefulToPrintExpr(LHS) && !UsefulToPrintExpr(RHS))
+      return;
+
+    struct {
+      const Expr *Expr;
+      Expr::EvalResult Result;
+      SmallString<12> ValueString;
+      bool Print;
+    } DiagSide[2] = {{LHS, Expr::EvalResult(), {}, false},
+                     {RHS, Expr::EvalResult(), {}, false}};
+    for (unsigned I = 0; I < 2; I++) {
+      const Expr *Side = DiagSide[I].Expr;
+
+      Side->EvaluateAsRValue(DiagSide[I].Result, Context, true);
+
+      DiagSide[I].Print = ConvertAPValueToString(
+          DiagSide[I].Result.Val, Side->getType(), DiagSide[I].ValueString);
+    }
+    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();
+    }
+  }
+}
+
 Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
                                          Expr *AssertExpr,
                                          StringLiteral *AssertMessage,
@@ -16612,6 +16743,7 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
         Diag(StaticAssertLoc, diag::err_static_assert_requirement_failed)
           << InnerCondDescription << !AssertMessage
           << Msg.str() << InnerCond->getSourceRange();
+        DiagnoseStaticAssertDetails(InnerCond);
       } else {
         Diag(StaticAssertLoc, diag::err_static_assert_failed)
           << !AssertMessage << Msg.str() << AssertExpr->getSourceRange();
index bf16d12..1993972 100644 (file)
@@ -98,7 +98,8 @@ namespace dependent {
     static_assert(sizeof(arr2) == 12, "");
 
     // Use a failing test to ensure the type isn't considered dependent.
-    static_assert(sizeof(arr2) == 13, ""); // expected-error {{failed}}
+    static_assert(sizeof(arr2) == 13, ""); // expected-error {{failed}} \
+                                           // expected-note {{evaluates to '12 == 13'}}
   }
 
   void g() { f<int[3]>(); } // expected-note {{in instantiation of}}
index 2b72425..f6a2e59 100644 (file)
@@ -178,7 +178,8 @@ namespace dr727 { // dr727: partial
     static_assert(B<0>().v<1> == 3, "");
     static_assert(B<0>().v<0> == 4, "");
 #if __cplusplus < 201702L
-    // expected-error@-2 {{failed}}
+    // expected-error@-2 {{failed}} \
+    // expected-note@-2 {{evaluates to '2 == 4'}}
 #endif
 
     static_assert(B<1>().w<1> == 1, "");
index ce2fccc..b4e100a 100644 (file)
@@ -40,8 +40,10 @@ template<typename ...T> void f(T ...t) {
 
 template<int ...a> constexpr auto x = [...z = a] (auto F) { return F(z...); };
 static_assert(x<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 123);
-static_assert(x<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 124); // expected-error {{failed}}
+static_assert(x<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 124); // expected-error {{failed}} \
+                                                                                          // expected-note {{evaluates to '123 == 124'}}
 
 template<int ...a> constexpr auto y = [z = a...] (auto F) { return F(z...); }; // expected-error {{must appear before the name of the capture}}
 static_assert(y<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 123);
-static_assert(y<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 124); // expected-error {{failed}}
+static_assert(y<1,2,3>([](int a, int b, int c) { return 100 * a + 10 * b + c; }) == 124); // expected-error {{failed}} \
+                                                                                          // expected-note {{evaluates to '123 == 124'}}
index d6069e7..2e47c05 100644 (file)
@@ -21,7 +21,7 @@ error here;
 
 #if !ENABLED_TRIGRAPHS
 // expected-error@11 {{}} expected-warning@11 {{trigraph ignored}}
-// expected-error@13 {{failed}} expected-warning@13 {{trigraph ignored}}
+// expected-error@13 {{failed}} expected-warning@13 {{trigraph ignored}} expected-note@13 {{evaluates to ''?' == '#''}}
 // expected-error@16 {{}}
 // expected-error@20 {{}}
 #else
index 804c42e..eaedb9b 100644 (file)
@@ -167,7 +167,8 @@ namespace DependentMemberExpr {
   // This used to mark 'f' invalid without producing any diagnostic. That's a
   // little hard to detect, but we can make sure that constexpr evaluation
   // fails when it should.
-  static_assert(A<int>().f() == 1); // expected-error {{static assertion failed}}
+  static_assert(A<int>().f() == 1); // expected-error {{static assertion failed}} \
+                                    // expected-note {{evaluates to '0 == 1'}}
 #endif
 }
 
index 936e484..12ee2f9 100644 (file)
@@ -26,7 +26,8 @@
 
   static_assert(a, ""); // expected-error {{static assertion expression is not an integral constant expression}}
   static_assert(sizeof(a) == 4, "");
-  static_assert(sizeof(a) == 3, ""); // expected-error {{static assertion failed}}
+  static_assert(sizeof(a) == 3, ""); // expected-error {{static assertion failed}} \
+                                     // expected-note {{evaluates to '4 == 3'}}
 }
 
 static_assert(1, "");
@@ -40,7 +41,8 @@ _Static_assert(1, "");
   static_assert(1, "");
   _Static_assert(1, "");
   static_assert(sizeof(b) == 4, "");
-  static_assert(sizeof(b) == 3, ""); // expected-error {{static assertion failed}}
+  static_assert(sizeof(b) == 3, ""); // expected-error {{static assertion failed}} \
+                                     // expected-note {{evaluates to '4 == 3'}}
 }
 
 static_assert(1, "");
@@ -56,7 +58,8 @@ static_assert(1, "");
 @interface B () {
   int b;
   static_assert(sizeof(b) == 4, "");
-  static_assert(sizeof(b) == 3, ""); // expected-error {{static assertion failed}}
+  static_assert(sizeof(b) == 3, ""); // expected-error {{static assertion failed}} \
+                                     // expected-note {{evaluates to '4 == 3'}}
 }
 @end
 
index 60a45af..622b3f8 100644 (file)
@@ -55,6 +55,7 @@ struct A {
 typedef UNION(unsigned, struct A) U1; // ext-warning 3 {{'_Static_assert' is a C11 extension}}
 UNION(char[2], short) u2 = { .one = { 'a', 'b' } }; // ext-warning 3 {{'_Static_assert' is a C11 extension}} cxx-warning {{designated initializers are a C++20 extension}}
 typedef UNION(char, short) U3; // expected-error {{static assertion failed due to requirement 'sizeof(char) == sizeof(short)': type size mismatch}} \
+                               // expected-note{{evaluates to '1 == 2'}} \
                                // ext-warning 3 {{'_Static_assert' is a C11 extension}}
 typedef UNION(float, 0.5f) U4; // expected-error {{expected a type}} \
                                // ext-warning 3 {{'_Static_assert' is a C11 extension}}
index 3760fa4..23dfde8 100644 (file)
@@ -1913,11 +1913,13 @@ namespace VirtualFromBase {
   // cxx11-error@-1    {{not an integral constant expression}}
   // cxx11-note@-2     {{call to virtual function}}
   // cxx20_2b-error@-3 {{static assertion failed}}
+  // cxx20_2b-note@-4 {{8 == 16}}
 
   // Non-virtual f(), OK.
   constexpr X<X<S2>> xxs2;
   constexpr X<S2> *q = const_cast<X<X<S2>>*>(&xxs2);
-  static_assert(q->f() == sizeof(S2), ""); // cxx20_2b-error {{static assertion failed}}
+  static_assert(q->f() == sizeof(S2), ""); // cxx20_2b-error {{static assertion failed}} \
+                                           // cxx20_2b-note {{16 == 8}}
 }
 
 namespace ConstexprConstructorRecovery {
index 5f04d44..df6f4b6 100644 (file)
@@ -149,7 +149,8 @@ enum Circular {             // expected-note {{not complete until the closing '}
   Circular_A = Circular(1), // expected-error {{'Circular' is an incomplete type}}
 };
 // 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}}
+static_assert(Circular_A == 0 && Circular_A != 0, ""); // expected-error {{static assertion failed}} \
+                                                       // expected-note {{evaluates to '0 != 0'}}
 }
 
 namespace test14 {
index 888117d..41a7b02 100644 (file)
@@ -88,7 +88,8 @@ void foo6() {
   static_assert(typename T::T(0));
   // expected-error@-1{{static assertion failed due to requirement 'int(0)'}}
   static_assert(sizeof(X<typename T::T>) == 0);
-  // expected-error@-1{{static assertion failed due to requirement 'sizeof(X<int>) == 0'}}
+  // expected-error@-1{{static assertion failed due to requirement 'sizeof(X<int>) == 0'}} \
+  // expected-note@-1 {{evaluates to '8 == 0'}}
   static_assert((const X<typename T::T> *)nullptr);
   // expected-error@-1{{static assertion failed due to requirement '(const X<int> *)nullptr'}}
   static_assert(static_cast<const X<typename T::T> *>(nullptr));
@@ -96,7 +97,8 @@ void foo6() {
   static_assert((const X<typename T::T>[]){} == nullptr);
   // expected-error@-1{{static assertion failed due to requirement '(const X<int>[0]){} == nullptr'}}
   static_assert(sizeof(X<decltype(X<typename T::T>().X<typename T::T>::~X())>) == 0);
-  // expected-error@-1{{static assertion failed due to requirement 'sizeof(X<void>) == 0'}}
+  // expected-error@-1{{static assertion failed due to requirement 'sizeof(X<void>) == 0'}} \
+  // expected-note@-1 {{evaluates to '8 == 0'}}
   static_assert(constexpr_return_false<typename T::T, typename T::U>());
   // expected-error@-1{{static assertion failed due to requirement 'constexpr_return_false<int, float>()'}}
 }
index 7e1ffb3..ed2389f 100644 (file)
@@ -22,7 +22,8 @@ T<1> t1; // expected-note {{in instantiation of template class 'T<1>' requested
 T<2> t2;
 
 template<typename T> struct S {
-    static_assert(sizeof(T) > sizeof(char), "Type not big enough!"); // expected-error {{static assertion failed due to requirement 'sizeof(char) > sizeof(char)': Type not big enough!}}
+    static_assert(sizeof(T) > sizeof(char), "Type not big enough!"); // expected-error {{static assertion failed due to requirement 'sizeof(char) > sizeof(char)': Type not big enough!}} \
+                                                                     // expected-note {{1 > 1}}
 };
 
 S<char> s1; // expected-note {{in instantiation of template class 'S<char>' requested here}}
@@ -215,3 +216,51 @@ static_assert(notBool, "message");          // expected-error {{value of type 's
 static_assert(constexprNotBool, "message"); // expected-error {{value of type 'const NotBool' is not contextually convertible to 'bool'}}
 
 static_assert(1 , "") // expected-error {{expected ';' after 'static_assert'}}
+
+
+namespace Diagnostics {
+  /// No notes for literals.
+  static_assert(false, ""); // expected-error {{failed}}
+  static_assert(1.0 > 2.0, ""); // expected-error {{failed}}
+  static_assert('c' == 'd', ""); // expected-error {{failed}}
+  static_assert(1 == 2, ""); // expected-error {{failed}}
+
+  /// Simple things are ignored.
+  static_assert(1 == (-(1)), ""); //expected-error {{failed}}
+
+  /// Chars are printed as chars.
+  constexpr char getChar() {
+    return 'c';
+  }
+  static_assert(getChar() == 'a', ""); // expected-error {{failed}} \
+                                       // expected-note {{evaluates to ''c' == 'a''}}
+
+  /// Bools are printed as bools.
+  constexpr bool invert(bool b) {
+    return !b;
+  }
+  static_assert(invert(true) == invert(false), ""); // expected-error {{failed}} \
+                                                    // expected-note {{evaluates to 'false == true'}}
+
+  /// No notes here since we compare a bool expression with a bool literal.
+  static_assert(invert(true) == true, ""); // expected-error {{failed}}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc99-extensions"
+  constexpr _Complex float com = {5,6};
+  constexpr _Complex float com2 = {1, 9};
+  static_assert(com == com2, ""); // expected-error {{failed}} \
+                                  // expected-note {{evaluates to '(5 + 6i) == (1 + 9i)'}}
+#pragma clang diagnostic pop
+
+#define CHECK_4(x) ((x) == 4)
+#define A_IS_B (a == b)
+  static_assert(CHECK_4(5), ""); // expected-error {{failed}}
+
+  constexpr int a = 4;
+  constexpr int b = 5;
+  static_assert(CHECK_4(a) && A_IS_B, ""); // expected-error {{failed}} \
+                                           // expected-note {{evaluates to '4 == 5'}}
+
+
+}
index 89c4a65..1096795 100644 (file)
@@ -31,7 +31,8 @@ namespace InstantiationDependent {
   static_assert(b<char> == 1, ""); // expected-note {{in instantiation of}} expected-error {{not an integral constant}}
 
   template<typename T> void f() {
-    static_assert(a<sizeof(sizeof(f(T())))> == 0, ""); // expected-error {{static assertion failed due to requirement 'a<sizeof (sizeof (f(type-parameter-0-0())))> == 0'}}
+    static_assert(a<sizeof(sizeof(f(T())))> == 0, ""); // expected-error {{static assertion failed due to requirement 'a<sizeof (sizeof (f(type-parameter-0-0())))> == 0'}} \
+                                                       // expected-note {{evaluates to '1 == 0'}}
   }
 }
 
index ae64108..8ef18e8 100644 (file)
@@ -68,8 +68,10 @@ namespace PR46791 { // also PR45782
   struct D : B, C {};
 
   static_assert(trait<A>::specialization == 0);
-  static_assert(trait<B>::specialization == 1); // FIXME expected-error {{failed}}
-  static_assert(trait<C>::specialization == 2); // FIXME expected-error {{failed}}
+  static_assert(trait<B>::specialization == 1); // FIXME expected-error {{failed}} \
+                                                // expected-note {{evaluates to '0 == 1'}}
+  static_assert(trait<C>::specialization == 2); // FIXME expected-error {{failed}} \
+                                                // expected-note {{evaluates to '0 == 2'}}
   static_assert(trait<D>::specialization == 0); // FIXME-error {{ambiguous partial specialization}}
 }
 
index 584c366..76a8746 100644 (file)
@@ -184,7 +184,8 @@ namespace TemplateSpecializations {
 
 namespace Diags {
   struct A { int n, m; };
-  template<A a> struct X { static_assert(a.n == a.m); }; // expected-error {{static assertion failed due to requirement 'Diags::A{1, 2}.n == Diags::A{1, 2}.m'}}
+  template<A a> struct X { static_assert(a.n == a.m); }; // expected-error {{static assertion failed due to requirement 'Diags::A{1, 2}.n == Diags::A{1, 2}.m'}} \
+                                                         // expected-note {{evaluates to '1 == 2'}}
   template struct X<A{1, 2}>; // expected-note {{in instantiation of template class 'Diags::X<{1, 2}>' requested here}}
 }