diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -96,6 +96,9 @@ 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 ------------------------------------------------- diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1532,6 +1532,8 @@ 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">, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -7480,6 +7480,7 @@ StringLiteral *AssertMessageExpr, SourceLocation RParenLoc, bool Failed); + void DiagnoseStaticAssertDetails(const Expr *E); FriendDecl *CheckFriendTypeDecl(SourceLocation LocStart, SourceLocation FriendLoc, diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -16554,6 +16554,137 @@ 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 &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(E)) + return false; + + // These have been substituted from template parameters + // and appear as literals in the static assert error. + if (isa(E)) + return false; + + // -5 is also simple to understand. + if (const auto *UnaryOp = dyn_cast_or_null(E)) + return UsefulToPrintExpr(UnaryOp->getSubExpr()); + + // Ignore nested binary operators. This could be a FIXME for improvements + // to the diagnostics in the future. + if (isa(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(E)) { + const Expr *LHS = Op->getLHS()->IgnoreParenImpCasts(); + const Expr *RHS = Op->getRHS()->IgnoreParenImpCasts(); + + // Ignore comparisons of boolean expressions with a boolean literal. + if ((isa(LHS) && RHS->getType()->isBooleanType()) || + (isa(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 @@ 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(); diff --git a/clang/test/CXX/dcl.decl/dcl.meaning/dcl.array/p3.cpp b/clang/test/CXX/dcl.decl/dcl.meaning/dcl.array/p3.cpp --- a/clang/test/CXX/dcl.decl/dcl.meaning/dcl.array/p3.cpp +++ b/clang/test/CXX/dcl.decl/dcl.meaning/dcl.array/p3.cpp @@ -98,7 +98,8 @@ 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(); } // expected-note {{in instantiation of}} diff --git a/clang/test/CXX/drs/dr7xx.cpp b/clang/test/CXX/drs/dr7xx.cpp --- a/clang/test/CXX/drs/dr7xx.cpp +++ b/clang/test/CXX/drs/dr7xx.cpp @@ -178,7 +178,8 @@ 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, ""); diff --git a/clang/test/CXX/temp/temp.decls/temp.variadic/init-capture.cpp b/clang/test/CXX/temp/temp.decls/temp.variadic/init-capture.cpp --- a/clang/test/CXX/temp/temp.decls/temp.variadic/init-capture.cpp +++ b/clang/test/CXX/temp/temp.decls/temp.variadic/init-capture.cpp @@ -40,8 +40,10 @@ template 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 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'}} diff --git a/clang/test/Lexer/cxx1z-trigraphs.cpp b/clang/test/Lexer/cxx1z-trigraphs.cpp --- a/clang/test/Lexer/cxx1z-trigraphs.cpp +++ b/clang/test/Lexer/cxx1z-trigraphs.cpp @@ -21,7 +21,7 @@ #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 diff --git a/clang/test/PCH/cxx-templates.cpp b/clang/test/PCH/cxx-templates.cpp --- a/clang/test/PCH/cxx-templates.cpp +++ b/clang/test/PCH/cxx-templates.cpp @@ -167,7 +167,8 @@ // 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().f() == 1); // expected-error {{static assertion failed}} + static_assert(A().f() == 1); // expected-error {{static assertion failed}} \ + // expected-note {{evaluates to '0 == 1'}} #endif } diff --git a/clang/test/Parser/objc-static-assert.mm b/clang/test/Parser/objc-static-assert.mm --- a/clang/test/Parser/objc-static-assert.mm +++ b/clang/test/Parser/objc-static-assert.mm @@ -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(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 @@ @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 diff --git a/clang/test/Sema/static-assert.c b/clang/test/Sema/static-assert.c --- a/clang/test/Sema/static-assert.c +++ b/clang/test/Sema/static-assert.c @@ -55,6 +55,7 @@ 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}} diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -1913,11 +1913,13 @@ // 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> xxs2; constexpr X *q = const_cast>*>(&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 { diff --git a/clang/test/SemaCXX/recovery-expr-type.cpp b/clang/test/SemaCXX/recovery-expr-type.cpp --- a/clang/test/SemaCXX/recovery-expr-type.cpp +++ b/clang/test/SemaCXX/recovery-expr-type.cpp @@ -149,7 +149,8 @@ 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 { diff --git a/clang/test/SemaCXX/static-assert-cxx17.cpp b/clang/test/SemaCXX/static-assert-cxx17.cpp --- a/clang/test/SemaCXX/static-assert-cxx17.cpp +++ b/clang/test/SemaCXX/static-assert-cxx17.cpp @@ -88,7 +88,8 @@ static_assert(typename T::T(0)); // expected-error@-1{{static assertion failed due to requirement 'int(0)'}} static_assert(sizeof(X) == 0); - // expected-error@-1{{static assertion failed due to requirement 'sizeof(X) == 0'}} + // expected-error@-1{{static assertion failed due to requirement 'sizeof(X) == 0'}} \ + // expected-note@-1 {{evaluates to '8 == 0'}} static_assert((const X *)nullptr); // expected-error@-1{{static assertion failed due to requirement '(const X *)nullptr'}} static_assert(static_cast *>(nullptr)); @@ -96,7 +97,8 @@ static_assert((const X[]){} == nullptr); // expected-error@-1{{static assertion failed due to requirement '(const X[0]){} == nullptr'}} static_assert(sizeof(X().X::~X())>) == 0); - // expected-error@-1{{static assertion failed due to requirement 'sizeof(X) == 0'}} + // expected-error@-1{{static assertion failed due to requirement 'sizeof(X) == 0'}} \ + // expected-note@-1 {{evaluates to '8 == 0'}} static_assert(constexpr_return_false()); // expected-error@-1{{static assertion failed due to requirement 'constexpr_return_false()'}} } diff --git a/clang/test/SemaCXX/static-assert.cpp b/clang/test/SemaCXX/static-assert.cpp --- a/clang/test/SemaCXX/static-assert.cpp +++ b/clang/test/SemaCXX/static-assert.cpp @@ -22,7 +22,8 @@ T<2> t2; template 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 s1; // expected-note {{in instantiation of template class 'S' requested here}} @@ -215,3 +216,51 @@ 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'}} + + +} diff --git a/clang/test/SemaTemplate/instantiate-var-template.cpp b/clang/test/SemaTemplate/instantiate-var-template.cpp --- a/clang/test/SemaTemplate/instantiate-var-template.cpp +++ b/clang/test/SemaTemplate/instantiate-var-template.cpp @@ -31,7 +31,8 @@ static_assert(b == 1, ""); // expected-note {{in instantiation of}} expected-error {{not an integral constant}} template void f() { - static_assert(a == 0, ""); // expected-error {{static assertion failed due to requirement 'a == 0'}} + static_assert(a == 0, ""); // expected-error {{static assertion failed due to requirement 'a == 0'}} \ + // expected-note {{evaluates to '1 == 0'}} } } diff --git a/clang/test/SemaTemplate/instantiation-dependence.cpp b/clang/test/SemaTemplate/instantiation-dependence.cpp --- a/clang/test/SemaTemplate/instantiation-dependence.cpp +++ b/clang/test/SemaTemplate/instantiation-dependence.cpp @@ -68,8 +68,10 @@ struct D : B, C {}; static_assert(trait::specialization == 0); - static_assert(trait::specialization == 1); // FIXME expected-error {{failed}} - static_assert(trait::specialization == 2); // FIXME expected-error {{failed}} + static_assert(trait::specialization == 1); // FIXME expected-error {{failed}} \ + // expected-note {{evaluates to '0 == 1'}} + static_assert(trait::specialization == 2); // FIXME expected-error {{failed}} \ + // expected-note {{evaluates to '0 == 2'}} static_assert(trait::specialization == 0); // FIXME-error {{ambiguous partial specialization}} } diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp @@ -184,7 +184,8 @@ namespace Diags { struct A { int n, m; }; - template 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 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; // expected-note {{in instantiation of template class 'Diags::X<{1, 2}>' requested here}} }