forked from OSchip/llvm-project
[clang][sema] Print more information about failed static assertions
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
This commit is contained in:
parent
2813151730
commit
09117b2189
|
@ -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
|
||||
-------------------------------------------------
|
||||
|
|
|
@ -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">,
|
||||
|
|
|
@ -7480,6 +7480,7 @@ public:
|
|||
StringLiteral *AssertMessageExpr,
|
||||
SourceLocation RParenLoc,
|
||||
bool Failed);
|
||||
void DiagnoseStaticAssertDetails(const Expr *E);
|
||||
|
||||
FriendDecl *CheckFriendTypeDecl(SourceLocation LocStart,
|
||||
SourceLocation FriendLoc,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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, "");
|
||||
|
|
|
@ -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'}}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>()'}}
|
||||
}
|
||||
|
|
|
@ -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'}}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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'}}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}}
|
||||
}
|
||||
|
||||
|
|
|
@ -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}}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue