Skip to content

Commit 8da0903

Browse files
committed
Improve static_assert/_Static_assert diagnostics
Our diagnostics relating to static assertions were a bit confused. For instance, when in MS compatibility mode in C (where we accept static_assert even without including <assert.h>), we would fail to warn the user that they were using the wrong spelling (even in pedantic mode), we were missing a compatibility warning about using _Static_assert in earlier standards modes, diagnostics for the optional message were not reflected in C as they were in C++, etc.
1 parent 4672bac commit 8da0903

File tree

9 files changed

+150
-14
lines changed

9 files changed

+150
-14
lines changed

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,11 @@ def : DiagGroup<"c++1z-compat-mangling", [CXX17CompatMangling]>;
253253
// Name of this warning in GCC.
254254
def NoexceptType : DiagGroup<"noexcept-type", [CXX17CompatMangling]>;
255255

256+
// Warnings for C2x code which is not compatible with prior C standards.
257+
def CPre2xCompat : DiagGroup<"pre-c2x-compat">;
258+
def CPre2xCompatPedantic : DiagGroup<"pre-c2x-compat-pedantic",
259+
[CPre2xCompat]>;
260+
256261
// Warnings for C++1y code which is not compatible with prior C++ standards.
257262
def CXXPre14Compat : DiagGroup<"c++98-c++11-compat">;
258263
def CXXPre14CompatPedantic : DiagGroup<"c++98-c++11-compat-pedantic",
@@ -1074,6 +1079,8 @@ def MicrosoftAnonTag : DiagGroup<"microsoft-anon-tag">;
10741079
def MicrosoftCommentPaste : DiagGroup<"microsoft-comment-paste">;
10751080
def MicrosoftEndOfFile : DiagGroup<"microsoft-end-of-file">;
10761081
def MicrosoftInaccessibleBase : DiagGroup<"microsoft-inaccessible-base">;
1082+
def MicrosoftStaticAssert : DiagGroup<"microsoft-static-assert">;
1083+
10771084
// Aliases.
10781085
def : DiagGroup<"msvc-include", [MicrosoftInclude]>;
10791086
// -Wmsvc-include = -Wmicrosoft-include
@@ -1089,7 +1096,7 @@ def Microsoft : DiagGroup<"microsoft",
10891096
MicrosoftRedeclareStatic, MicrosoftEnumForwardReference, MicrosoftGoto,
10901097
MicrosoftFlexibleArray, MicrosoftExtraQualification, MicrosoftCast,
10911098
MicrosoftConstInit, MicrosoftVoidPseudoDtor, MicrosoftAnonTag,
1092-
MicrosoftCommentPaste, MicrosoftEndOfFile,
1099+
MicrosoftCommentPaste, MicrosoftEndOfFile, MicrosoftStaticAssert,
10931100
MicrosoftInconsistentDllImport]>;
10941101

10951102
def ClangClPch : DiagGroup<"clang-cl-pch">;

clang/include/clang/Basic/DiagnosticParseKinds.td

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -424,12 +424,21 @@ def err_bool_redeclaration : Error<
424424
def warn_cxx98_compat_static_assert : Warning<
425425
"static_assert declarations are incompatible with C++98">,
426426
InGroup<CXX98Compat>, DefaultIgnore;
427-
def ext_static_assert_no_message : ExtWarn<
428-
"static_assert with no message is a C++17 extension">, InGroup<CXX17>;
427+
def ext_ms_static_assert : ExtWarn<
428+
"use of 'static_assert' without inclusion of <assert.h> is a Microsoft "
429+
"extension">, InGroup<MicrosoftStaticAssert>;
430+
def ext_cxx_static_assert_no_message : ExtWarn<
431+
"'static_assert' with no message is a C++17 extension">, InGroup<CXX17>;
432+
def ext_c_static_assert_no_message : ExtWarn<
433+
"'_Static_assert' with no message is a C2x extension">, InGroup<C2x>;
429434
def warn_cxx14_compat_static_assert_no_message : Warning<
430-
"static_assert with no message is incompatible with C++ standards before "
435+
"'static_assert' with no message is incompatible with C++ standards before "
431436
"C++17">,
432437
DefaultIgnore, InGroup<CXXPre17Compat>;
438+
def warn_c17_compat_static_assert_no_message : Warning<
439+
"'_Static_assert' with no message is incompatible with C standards before "
440+
"C2x">,
441+
DefaultIgnore, InGroup<CPre2xCompat>;
433442
def err_function_definition_not_allowed : Error<
434443
"function definition is not allowed here">;
435444
def err_expected_end_of_enumerator : Error<

clang/lib/Lex/PPDirectives.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2870,6 +2870,23 @@ void Preprocessor::HandleDefineDirective(
28702870
// If the callbacks want to know, tell them about the macro definition.
28712871
if (Callbacks)
28722872
Callbacks->MacroDefined(MacroNameTok, MD);
2873+
2874+
// If we're in MS compatibility mode and the macro being defined is the
2875+
// assert macro, implicitly add a macro definition for static_assert to work
2876+
// around their broken assert.h header file in C. Only do so if there isn't
2877+
// already a static_assert macro defined.
2878+
if (!getLangOpts().CPlusPlus && getLangOpts().MSVCCompat &&
2879+
MacroNameTok.getIdentifierInfo()->isStr("assert") &&
2880+
!isMacroDefined("static_assert")) {
2881+
MacroInfo *MI = AllocateMacroInfo(SourceLocation());
2882+
2883+
Token Tok;
2884+
Tok.startToken();
2885+
Tok.setKind(tok::kw__Static_assert);
2886+
Tok.setIdentifierInfo(getIdentifierInfo("_Static_assert"));
2887+
MI->AddTokenToBody(Tok);
2888+
(void)appendDefMacroDirective(getIdentifierInfo("static_assert"), MI);
2889+
}
28732890
}
28742891

28752892
/// HandleUndefDirective - Implements \#undef.

clang/lib/Parse/ParseDeclCXX.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -880,8 +880,13 @@ Decl *Parser::ParseStaticAssertDeclaration(SourceLocation &DeclEnd){
880880

881881
if (Tok.is(tok::kw__Static_assert) && !getLangOpts().C11)
882882
Diag(Tok, diag::ext_c11_feature) << Tok.getName();
883-
if (Tok.is(tok::kw_static_assert))
884-
Diag(Tok, diag::warn_cxx98_compat_static_assert);
883+
if (Tok.is(tok::kw_static_assert)) {
884+
if (!getLangOpts().CPlusPlus)
885+
Diag(Tok, diag::ext_ms_static_assert)
886+
<< FixItHint::CreateReplacement(Tok.getLocation(), "_Static_assert");
887+
else
888+
Diag(Tok, diag::warn_cxx98_compat_static_assert);
889+
}
885890

886891
SourceLocation StaticAssertLoc = ConsumeToken();
887892

@@ -902,11 +907,17 @@ Decl *Parser::ParseStaticAssertDeclaration(SourceLocation &DeclEnd){
902907

903908
ExprResult AssertMessage;
904909
if (Tok.is(tok::r_paren)) {
910+
unsigned DiagVal;
905911
if (getLangOpts().CPlusPlus17)
906-
Diag(Tok, diag::warn_cxx14_compat_static_assert_no_message);
912+
DiagVal = diag::warn_cxx14_compat_static_assert_no_message;
913+
else if (getLangOpts().CPlusPlus)
914+
DiagVal = diag::ext_cxx_static_assert_no_message;
915+
else if (getLangOpts().C2x)
916+
DiagVal = diag::warn_c17_compat_static_assert_no_message;
907917
else
908-
Diag(Tok, diag::ext_static_assert_no_message)
909-
<< getStaticAssertNoMessageFixIt(AssertExpr.get(), Tok.getLocation());
918+
DiagVal = diag::ext_c_static_assert_no_message;
919+
Diag(Tok, DiagVal) << getStaticAssertNoMessageFixIt(AssertExpr.get(),
920+
Tok.getLocation());
910921
} else {
911922
if (ExpectAndConsume(tok::comma)) {
912923
SkipUntil(tok::semi);

clang/test/Parser/static_assert.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// RUN: %clang_cc1 -fsyntax-only -std=c2x -DTEST_SPELLING -verify=c2x %s
2+
// RUN: %clang_cc1 -fsyntax-only -std=c2x -DTEST_SPELLING -fms-compatibility -verify=c2x-ms %s
3+
// RUN: %clang_cc1 -fsyntax-only -std=c2x -Wpre-c2x-compat -verify=c2x-compat %s
4+
// RUN: %clang_cc1 -fsyntax-only -std=c99 -verify=c99 %s
5+
// RUN: %clang_cc1 -fsyntax-only -std=c99 -pedantic -verify=c99-pedantic %s
6+
// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify=cxx17 -x c++ %s
7+
// RUN: %clang_cc1 -fsyntax-only -std=c++17 -pedantic -verify=cxx17-pedantic -x c++ %s
8+
// RUN: %clang_cc1 -fsyntax-only -std=c++98 -verify=cxx98 -x c++ %s
9+
// RUN: %clang_cc1 -fsyntax-only -std=c++98 -pedantic -verify=cxx98-pedantic -x c++ %s
10+
// RUN: %clang_cc1 -fsyntax-only -std=c++17 -Wc++98-c++11-c++14-compat -verify=cxx17-compat -x c++ %s
11+
12+
// cxx17-no-diagnostics
13+
14+
#ifdef TEST_SPELLING
15+
// Only test the C++ spelling in C mode in some of the tests, to reduce the
16+
// amount of diagnostics to have to check. This spelling is allowed in MS-
17+
// compatibility mode in C, but otherwise produces errors.
18+
static_assert(1, ""); // c2x-error {{expected parameter declarator}} \
19+
// c2x-error {{expected ')'}} \
20+
// c2x-note {{to match this '('}} \
21+
// c2x-warning {{type specifier missing, defaults to 'int'}} \
22+
// c2x-ms-warning {{use of 'static_assert' without inclusion of <assert.h> is a Microsoft extension}}
23+
#endif
24+
25+
// We support _Static_assert as an extension in older C modes and in all C++
26+
// modes, but only as a pedantic warning.
27+
_Static_assert(1, ""); // c99-pedantic-warning {{'_Static_assert' is a C11 extension}} \
28+
// cxx17-pedantic-warning {{'_Static_assert' is a C11 extension}} \
29+
// cxx98-pedantic-warning {{'_Static_assert' is a C11 extension}}
30+
31+
// _Static_assert without a message has more complex diagnostic logic:
32+
// * In C++17 or C2x mode, it's supported by default.
33+
// * But there is a special compat warning flag to warn about portability to
34+
// older standards.
35+
// * In older standard pedantic modes, warn about supporting without a
36+
// message as an extension.
37+
_Static_assert(1); // c99-pedantic-warning {{'_Static_assert' with no message is a C2x extension}} \
38+
// c99-warning {{'_Static_assert' with no message is a C2x extension}} \
39+
// cxx98-pedantic-warning {{'static_assert' with no message is a C++17 extension}} \
40+
// cxx98-warning {{'static_assert' with no message is a C++17 extension}} \
41+
// c2x-compat-warning {{'_Static_assert' with no message is incompatible with C standards before C2x}} \
42+
// cxx17-compat-warning {{'static_assert' with no message is incompatible with C++ standards before C++17}} \
43+
// c99-pedantic-warning {{'_Static_assert' is a C11 extension}} \
44+
// cxx17-pedantic-warning {{'_Static_assert' is a C11 extension}} \
45+
// cxx98-pedantic-warning {{'_Static_assert' is a C11 extension}}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %clang_cc1 -DFIRST_WAY -E -dM %s | FileCheck --strict-whitespace %s
2+
// RUN: %clang_cc1 -DFIRST_WAY -fms-compatibility -E -dM %s | FileCheck --strict-whitespace %s
3+
// RUN: %clang_cc1 -E -dM %s | FileCheck --strict-whitespace %s
4+
// RUN: %clang_cc1 -fms-compatibility -E -dM %s | FileCheck --strict-whitespace %s
5+
6+
// If the assert macro is defined in MS compatibility mode in C, we
7+
// automatically inject a macro definition for static_assert. Test that the
8+
// macro is not added if there is already a definition of static_assert to
9+
// ensure that we don't re-define the macro in the event the Microsoft assert.h
10+
// header starts to define the macro some day (or the user defined their own
11+
// macro with the same name). Test that the order of the macro definitions does
12+
// not matter to the behavior.
13+
14+
#ifdef FIRST_WAY
15+
#define static_assert 12
16+
#define assert
17+
#else
18+
#define assert
19+
#define static_assert 12
20+
#endif
21+
22+
CHECK: #define static_assert 12
23+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %clang_cc1 -E -dM %s | FileCheck --strict-whitespace --check-prefix=NOMS %s
2+
// RUN: %clang_cc1 -fms-compatibility -E -dM %s | FileCheck --strict-whitespace --check-prefix=MS %s
3+
4+
// If the assert macro is defined in MS compatibility mode in C, we
5+
// automatically inject a macro definition for static_assert. Test that the
6+
// macro is properly added to the preprocessed output. This allows us to
7+
// diagonse use of the static_assert keyword when <assert.h> has not been
8+
// included while still being able to compile preprocessed code.
9+
#define assert
10+
11+
MS: #define static_assert _Static_assert
12+
NOMS-NOT: #define static_assert _Static_assert

clang/test/Sema/static-assert.c

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// RUN: %clang_cc1 -std=c11 -fsyntax-only -verify %s
2-
// RUN: %clang_cc1 -fms-compatibility -DMS -fsyntax-only -verify %s
2+
// RUN: %clang_cc1 -fms-compatibility -DMS -fsyntax-only -verify=expected,ms %s
33
// RUN: %clang_cc1 -std=c99 -pedantic -fsyntax-only -verify=expected,ext %s
44
// RUN: %clang_cc1 -xc++ -std=c++11 -pedantic -fsyntax-only -verify=expected,ext,cxx %s
55

@@ -13,15 +13,15 @@ _Static_assert(0, "0 is nonzero"); // expected-error {{static_assert failed "0 i
1313
// ext-warning {{'_Static_assert' is a C11 extension}}
1414

1515
#ifdef MS
16-
static_assert(1, "1 is nonzero");
16+
static_assert(1, "1 is nonzero"); // ms-warning {{use of 'static_assert' without inclusion of <assert.h> is a Microsoft extension}}
1717
#endif
1818

1919
void foo(void) {
2020
_Static_assert(1, "1 is nonzero"); // ext-warning {{'_Static_assert' is a C11 extension}}
2121
_Static_assert(0, "0 is nonzero"); // expected-error {{static_assert failed "0 is nonzero"}} \
2222
// ext-warning {{'_Static_assert' is a C11 extension}}
2323
#ifdef MS
24-
static_assert(1, "1 is nonzero");
24+
static_assert(1, "1 is nonzero"); // ms-warning {{use of 'static_assert' without}}
2525
#endif
2626
}
2727

@@ -34,7 +34,7 @@ struct A {
3434
_Static_assert(0, "0 is nonzero"); // expected-error {{static_assert failed "0 is nonzero"}} \
3535
// ext-warning {{'_Static_assert' is a C11 extension}}
3636
#ifdef MS
37-
static_assert(1, "1 is nonzero");
37+
static_assert(1, "1 is nonzero"); // ms-warning {{use of 'static_assert' without}}
3838
#endif
3939
};
4040

@@ -58,3 +58,15 @@ typedef UNION(char, short) U3; // expected-error {{static_assert failed due to r
5858
// ext-warning 3 {{'_Static_assert' is a C11 extension}}
5959
typedef UNION(float, 0.5f) U4; // expected-error {{expected a type}} \
6060
// ext-warning 3 {{'_Static_assert' is a C11 extension}}
61+
62+
// After defining the assert macro in MS-compatibility mode, we should
63+
// no longer warn about including <assert.h> under the assumption the
64+
// user already did that.
65+
#ifdef MS
66+
#define assert(expr)
67+
static_assert(1, "1 is nonzero"); // ok
68+
69+
// But we should still warn if the user did something bonkers.
70+
#undef static_assert
71+
static_assert(1, "1 is nonzero"); // ms-warning {{use of 'static_assert' without}}
72+
#endif

clang/test/SemaCXX/static-assert.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11 -triple=x86_64-linux-gnu
1+
// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11 -pedantic -triple=x86_64-linux-gnu
22

33
int f(); // expected-note {{declared here}}
44

0 commit comments

Comments
 (0)