tbaeder updated this revision to Diff 449942. CHANGES SINCE LAST ACTION https://reviews.llvm.org/D130894/new/
https://reviews.llvm.org/D130894 Files: 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: 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<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}} } 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<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: 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<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: 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<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,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 {{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 +} 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<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 @@ 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: clang/test/SemaCXX/recovery-expr-type.cpp =================================================================== --- clang/test/SemaCXX/recovery-expr-type.cpp +++ 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 { 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<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: 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/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 == 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 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<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: 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/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<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: 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 {{evaluates to '(2 == 4)'}} #endif static_assert(B<1>().w<1> == 1, ""); 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 == 13)'}} } void g() { f<int[3]>(); } // expected-note {{in instantiation of}} Index: clang/lib/Sema/SemaDeclCXX.cpp =================================================================== --- clang/lib/Sema/SemaDeclCXX.cpp +++ 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<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 @@ 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/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/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ 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">, Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -67,6 +67,9 @@ enum without a fixed underlying type is set to a value outside the range of the enumeration's values. Fixes `Issue 50055: <https://github.com/llvm/llvm-project/issues/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 -------------------------------------------------
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits