tbaeder updated this revision to Diff 449589.
tbaeder added a comment.
I just took the plunge and made `DiagnoseStaticAssertDetails()` take into
account all static assertions, regardless of integer literals. It now also
prints integers, booleans, chars and floats correctly.
For example:
test.cpp:30:1: error: static assertion failed due to requirement 'inv(true)
== inv(false)'
static_assert(inv(true) == inv(false));
^ ~~~~~~~~~~~~~~~~~~~~~~~
test.cpp:30:15: note: left-hand side of operator '==' evaluates to 'false'
static_assert(inv(true) == inv(false));
^~~~~~~~~
test.cpp:30:28: note: right-hand side of operator '==' evaluates to 'true'
static_assert(inv(true) == inv(false));
^~~~~~~~~~
the output when both sides are printed is rather clumsy to me, and some things
are not handled at all (e.g. failed static assertions when the toplevel
expression is not a `BinaryOpertator`, e.g. `static_assert(!k)` - the user
doesn't know what value `k` is, which is important in case `k` is of an integer
type).
But again, this kind of begs people to suggest more and more improvements, but
I think this is a good enough start :)
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/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,9 @@
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'}} \
+ // expected-note {{evaluates to '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'}}
+ static_assert(trait<C>::specialization == 2); // FIXME expected-error {{failed}} \
+ // expected-note {{evaluates to '0'}}
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'}}
}
}
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 2{{evaluates to '1'}}
};
S<char> s1; // expected-note {{in instantiation of template class 'S<char>' requested here}}
@@ -215,3 +216,33 @@
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 {{left-hand side of operator '==' evaluates to 'false'}} \
+ // expected-note {{right-hand side of operator '==' evaluates to 'true'}} \
+
+ /// No notes here since we compare a bool expression with a bool literal.
+ static_assert(invert(true) == true, ""); // expected-error {{failed}}
+}
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'}}
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'}}
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/constant-expression-cxx11.cpp
===================================================================
--- clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1913,11 +1913,15 @@
// 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 {{evaluates to '8'}}
+ // cxx20_2b-note@-5 {{evaluates to '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 {{evaluates to '16'}} \
+ // cxx20_2b-note {{evaluates to '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,8 @@
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 {{left-hand side of operator '==' evaluates to '1'}} \
+ // expected-note {{right-hand side of operator '==' evaluates to '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'}}
}
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/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 {{left-hand side of operator '==' evaluates to '0'}}
#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'}}
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'}}
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/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<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,108 @@
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");
+ if (BoolValue) {
+ Str.push_back('t');
+ Str.push_back('r');
+ Str.push_back('u');
+ Str.push_back('e');
+ } else {
+ Str.push_back('f');
+ Str.push_back('a');
+ Str.push_back('l');
+ Str.push_back('s');
+ Str.push_back('e');
+ }
+ } 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;
+
+ 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>(E) || isa<FloatingLiteral>(E) ||
+ isa<CharacterLiteral>(E) || isa<CXXBoolLiteralExpr>(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 UnaryOperator *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 BinaryOperator *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;
+
+ for (const Expr *Side : {LHS, RHS}) {
+ if (!UsefulToPrintExpr(Side))
+ continue;
+
+ Expr::EvalResult Result;
+ Side->EvaluateAsRValue(Result, Context, true);
+
+ SmallString<12> ValueString;
+ if (ConvertAPValueToString(Result.Val, Side->getType(), ValueString))
+ Diag(Side->getExprLoc(), diag::note_bin_op_evaluates)
+ << (Side != LHS) << Op->getOpcodeStr() << ValueString
+ << Side->getSourceRange();
+ }
+ }
+}
+
Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
Expr *AssertExpr,
StringLiteral *AssertMessage,
@@ -16612,6 +16714,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_bin_op_evaluates : Note<
+ "%select{left-hand|right-hand}0 side of operator '%1' evaluates to '%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
@@ -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: <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
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits