Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -64,6 +64,9 @@ enum without a fixed underlying type is set to a value outside the range of the enumeration's values. Fixes `Issue 50055: `_. +- 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: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1532,6 +1532,10 @@ 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_bin_op_evaluates_to : Note< + "%select{left-hand|right-hand}0 side of operator '%1' evaluates to '%2'">; +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: clang/include/clang/Sema/Sema.h =================================================================== --- clang/include/clang/Sema/Sema.h +++ clang/include/clang/Sema/Sema.h @@ -7479,6 +7479,7 @@ StringLiteral *AssertMessageExpr, SourceLocation RParenLoc, bool Failed); + void DiagnoseStaticAssertDetails(const Expr *E); FriendDecl *CheckFriendTypeDecl(SourceLocation LocStart, SourceLocation FriendLoc, Index: clang/lib/Sema/SemaDeclCXX.cpp =================================================================== --- clang/lib/Sema/SemaDeclCXX.cpp +++ clang/lib/Sema/SemaDeclCXX.cpp @@ -16554,6 +16554,144 @@ 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(V.getInt().getExtValue()); + } 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; + + 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; + + if (!UsefulToPrintExpr(Side)) + continue; + + 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(); + } else if (DiagSide[0].Print) { + // Only print LHS. + Diag(DiagSide[0].Expr->getExprLoc(), diag::note_bin_op_evaluates_to) + << 0 << Op->getOpcodeStr() << DiagSide[0].ValueString + << DiagSide[0].Expr->getSourceRange(); + } else if (DiagSide[1].Print) { + // Only print RHS. + Diag(DiagSide[1].Expr->getExprLoc(), diag::note_bin_op_evaluates_to) + << 1 << Op->getOpcodeStr() << DiagSide[1].ValueString + << DiagSide[1].Expr->getSourceRange(); + } + } +} + Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc, Expr *AssertExpr, StringLiteral *AssertMessage, @@ -16612,6 +16750,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(); Index: clang/test/CXX/dcl.decl/dcl.meaning/dcl.array/p3.cpp =================================================================== --- clang/test/CXX/dcl.decl/dcl.meaning/dcl.array/p3.cpp +++ 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'}} } void g() { f(); } // expected-note {{in instantiation of}} Index: clang/test/CXX/drs/dr7xx.cpp =================================================================== --- clang/test/CXX/drs/dr7xx.cpp +++ 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 {{left-hand side of operator '==' evaluates to '2'}} #endif static_assert(B<1>().w<1> == 1, ""); Index: clang/test/CXX/temp/temp.decls/temp.variadic/init-capture.cpp =================================================================== --- clang/test/CXX/temp/temp.decls/temp.variadic/init-capture.cpp +++ 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'}} 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'}} Index: clang/test/Lexer/cxx1z-trigraphs.cpp =================================================================== --- clang/test/Lexer/cxx1z-trigraphs.cpp +++ 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 Index: clang/test/PCH/cxx-templates.cpp =================================================================== --- clang/test/PCH/cxx-templates.cpp +++ 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 {{left-hand side of operator '==' evaluates to '0'}} #endif } Index: clang/test/Parser/objc-static-assert.mm =================================================================== --- clang/test/Parser/objc-static-assert.mm +++ 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'}} } 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'}} } 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'}} } @end Index: clang/test/Sema/static-assert.c =================================================================== --- clang/test/Sema/static-assert.c +++ 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}} Index: clang/test/SemaCXX/constant-expression-cxx11.cpp =================================================================== --- clang/test/SemaCXX/constant-expression-cxx11.cpp +++ 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 { Index: clang/test/SemaCXX/recovery-expr-type.cpp =================================================================== --- clang/test/SemaCXX/recovery-expr-type.cpp +++ clang/test/SemaCXX/recovery-expr-type.cpp @@ -150,7 +150,7 @@ }; // 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}} + // expected-note {{evaluates to '0'}} } namespace test14 { Index: clang/test/SemaCXX/static-assert-cxx17.cpp =================================================================== --- clang/test/SemaCXX/static-assert-cxx17.cpp +++ 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'}} 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'}} static_assert(constexpr_return_false()); // expected-error@-1{{static assertion failed due to requirement 'constexpr_return_false()'}} } Index: clang/test/SemaCXX/static-assert.cpp =================================================================== --- clang/test/SemaCXX/static-assert.cpp +++ 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,40 @@ 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 {{left-hand side of operator '==' evaluates to 'c'}} + + /// 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 +} Index: clang/test/SemaTemplate/instantiate-var-template.cpp =================================================================== --- clang/test/SemaTemplate/instantiate-var-template.cpp +++ 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'}} } } Index: clang/test/SemaTemplate/instantiation-dependence.cpp =================================================================== --- clang/test/SemaTemplate/instantiation-dependence.cpp +++ 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'}} + static_assert(trait::specialization == 2); // FIXME expected-error {{failed}} \ + // expected-note {{evaluates to '0'}} static_assert(trait::specialization == 0); // FIXME-error {{ambiguous partial specialization}} } Index: clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp =================================================================== --- clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp +++ 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}} }