Index: clang/include/clang/Basic/DiagnosticGroups.td =================================================================== --- clang/include/clang/Basic/DiagnosticGroups.td +++ clang/include/clang/Basic/DiagnosticGroups.td @@ -253,6 +253,11 @@ // Name of this warning in GCC. def NoexceptType : DiagGroup<"noexcept-type", [CXX17CompatMangling]>; +// Warnings for C2x code which is not compatible with prior C standards. +def CPre2xCompat : DiagGroup<"pre-c2x-compat">; +def CPre2xCompatPedantic : DiagGroup<"pre-c2x-compat-pedantic", + [CPre2xCompat]>; + // Warnings for C++1y code which is not compatible with prior C++ standards. def CXXPre14Compat : DiagGroup<"c++98-c++11-compat">; def CXXPre14CompatPedantic : DiagGroup<"c++98-c++11-compat-pedantic", Index: clang/include/clang/Basic/DiagnosticParseKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticParseKinds.td +++ clang/include/clang/Basic/DiagnosticParseKinds.td @@ -424,12 +424,21 @@ def warn_cxx98_compat_static_assert : Warning< "static_assert declarations are incompatible with C++98">, InGroup, DefaultIgnore; -def ext_static_assert_no_message : ExtWarn< - "static_assert with no message is a C++17 extension">, InGroup; +def warn_cxx_static_assert_in_c : Warning< + "'static_assert' is spelled '_Static_assert' in C; consider including " + "">, InGroup>; +def ext_cxx_static_assert_no_message : Extension< + "'static_assert' with no message is a C++17 extension">, InGroup; +def ext_c_static_assert_no_message : Extension< + "'_Static_assert' with no message is a C2x extension">, InGroup; def warn_cxx14_compat_static_assert_no_message : Warning< - "static_assert with no message is incompatible with C++ standards before " + "'static_assert' with no message is incompatible with C++ standards before " "C++17">, DefaultIgnore, InGroup; +def warn_c17_compat_static_assert_no_message : Warning< + "'_Static_assert' with no message is incompatible with C standards before " + "C2x">, + DefaultIgnore, InGroup; def err_function_definition_not_allowed : Error< "function definition is not allowed here">; def err_expected_end_of_enumerator : Error< Index: clang/lib/Lex/PPDirectives.cpp =================================================================== --- clang/lib/Lex/PPDirectives.cpp +++ clang/lib/Lex/PPDirectives.cpp @@ -2870,6 +2870,21 @@ // If the callbacks want to know, tell them about the macro definition. if (Callbacks) Callbacks->MacroDefined(MacroNameTok, MD); + + // If we're in MS compatibility mode and the macro being defined is the + // assert macro, implicitly add a macro definition for static_assert to work + // around their broken assert.h header file in C. + if (!getLangOpts().CPlusPlus && getLangOpts().MSVCCompat && + MacroNameTok.getIdentifierInfo()->isStr("assert")) { + MacroInfo *MI = AllocateMacroInfo(SourceLocation()); + + Token Tok; + Tok.startToken(); + Tok.setKind(tok::kw__Static_assert); + Tok.setIdentifierInfo(getIdentifierInfo("_Static_assert")); + MI->AddTokenToBody(Tok); + (void)appendDefMacroDirective(getIdentifierInfo("static_assert"), MI); + } } /// HandleUndefDirective - Implements \#undef. Index: clang/lib/Parse/ParseDeclCXX.cpp =================================================================== --- clang/lib/Parse/ParseDeclCXX.cpp +++ clang/lib/Parse/ParseDeclCXX.cpp @@ -870,8 +870,20 @@ if (Tok.is(tok::kw__Static_assert) && !getLangOpts().C11) Diag(Tok, diag::ext_c11_feature) << Tok.getName(); - if (Tok.is(tok::kw_static_assert)) - Diag(Tok, diag::warn_cxx98_compat_static_assert); + if (Tok.is(tok::kw_static_assert)) { + // MSVC accepts static_assert in C mode even when is not + // included. We want to warn about use of the keyword, so test to see + // whether the assert macro has been defined or not. If it's been defined, + // then it's safe to not diagnose because the user's code is portable. If + // it's not been defined, then we should recommend the user include + // to get the macro definition for static_assert even though + // MSVC's assert.h does not provide such a macro definition. + if (!getLangOpts().CPlusPlus && !PP.isMacroDefined("assert")) + Diag(Tok, diag::warn_cxx_static_assert_in_c) + << FixItHint::CreateReplacement(Tok.getLocation(), "_Static_assert"); + else + Diag(Tok, diag::warn_cxx98_compat_static_assert); + } SourceLocation StaticAssertLoc = ConsumeToken(); @@ -892,12 +904,19 @@ ExprResult AssertMessage; if (Tok.is(tok::r_paren)) { - Diag(Tok, getLangOpts().CPlusPlus17 - ? diag::warn_cxx14_compat_static_assert_no_message - : diag::ext_static_assert_no_message) - << (getLangOpts().CPlusPlus17 - ? FixItHint() - : FixItHint::CreateInsertion(Tok.getLocation(), ", \"\"")); + unsigned DiagVal; + if (getLangOpts().CPlusPlus17) + DiagVal = diag::warn_cxx14_compat_static_assert_no_message; + else if (getLangOpts().CPlusPlus) + DiagVal = diag::ext_cxx_static_assert_no_message; + else if (getLangOpts().C2x) + DiagVal = diag::warn_c17_compat_static_assert_no_message; + else + DiagVal = diag::ext_c_static_assert_no_message; + Diag(Tok, DiagVal) << ((getLangOpts().CPlusPlus17 || getLangOpts().C2x) + ? FixItHint() + : FixItHint::CreateInsertion(Tok.getLocation(), + ", \"\"")); } else { if (ExpectAndConsume(tok::comma)) { SkipUntil(tok::semi); Index: clang/test/Parser/static_assert.c =================================================================== --- /dev/null +++ clang/test/Parser/static_assert.c @@ -0,0 +1,45 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c2x -DTEST_SPELLING -verify=c2x %s +// RUN: %clang_cc1 -fsyntax-only -std=c2x -DTEST_SPELLING -fms-compatibility -verify=c2x-ms %s +// RUN: %clang_cc1 -fsyntax-only -std=c2x -Wpre-c2x-compat -verify=c2x-compat %s +// RUN: %clang_cc1 -fsyntax-only -std=c99 -verify=c99 %s +// RUN: %clang_cc1 -fsyntax-only -std=c99 -pedantic -verify=c99-pedantic %s +// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify=cxx17 -x c++ %s +// RUN: %clang_cc1 -fsyntax-only -std=c++17 -pedantic -verify=cxx17-pedantic -x c++ %s +// RUN: %clang_cc1 -fsyntax-only -std=c++98 -verify=cxx98 -x c++ %s +// RUN: %clang_cc1 -fsyntax-only -std=c++98 -pedantic -verify=cxx98-pedantic -x c++ %s +// RUN: %clang_cc1 -fsyntax-only -std=c++17 -Wc++98-c++11-c++14-compat -verify=cxx17-compat -x c++ %s + +// c99-no-diagnostics +// cxx17-no-diagnostics +// cxx98-no-diagnostics + +#ifdef TEST_SPELLING +// Only test the C++ spelling in C mode in some of the tests, to reduce the +// amount of diagnostics to have to check. This spelling is allowed in MS- +// compatibility mode in C, but otherwise produces errors. +static_assert(1, ""); // c2x-error {{expected parameter declarator}} \ + // c2x-error {{expected ')'}} \ + // c2x-note {{to match this '('}} \ + // c2x-warning {{type specifier missing, defaults to 'int'}} \ + // c2x-ms-warning {{'static_assert' is spelled '_Static_assert' in C; consider including }} +#endif + +// We support _Static_assert as an extension in older C modes and in all C++ +// modes, but only as a pedantic warning. +_Static_assert(1, ""); // c99-pedantic-warning {{'_Static_assert' is a C11 extension}} \ + // cxx17-pedantic-warning {{'_Static_assert' is a C11 extension}} \ + // cxx98-pedantic-warning {{'_Static_assert' is a C11 extension}} + +// _Static_assert without a message has more complex diagnostic logic: +// * In C++17 or C2x mode, it's supported by default. +// * But there is a special compat warning flag to warn about portability to +// older standards. +// * In older standard pedantic modes, warn about supporting without a +// message as an extension. +_Static_assert(1); // c99-pedantic-warning {{'_Static_assert' with no message is a C2x extension}} \ + // cxx98-pedantic-warning {{'static_assert' with no message is a C++17 extension}} \ + // c2x-compat-warning {{'_Static_assert' with no message is incompatible with C standards before C2x}} \ + // cxx17-compat-warning {{'static_assert' with no message is incompatible with C++ standards before C++17}} \ + // c99-pedantic-warning {{'_Static_assert' is a C11 extension}} \ + // cxx17-pedantic-warning {{'_Static_assert' is a C11 extension}} \ + // cxx98-pedantic-warning {{'_Static_assert' is a C11 extension}} Index: clang/test/Preprocessor/static_assert.c =================================================================== --- /dev/null +++ clang/test/Preprocessor/static_assert.c @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -E -dM %s | FileCheck --strict-whitespace --check-prefix=NOMS %s +// RUN: %clang_cc1 -fms-compatibility -E -dM %s | FileCheck --strict-whitespace --check-prefix=MS %s + +// If the assert macro is defined in MS compatibility mode in C, we +// automatically inject a macro definition for static_assert. Test that the +// macro is properly added to the preprocessed output. This allows us to +// diagonse use of the static_assert keyword when has not been +// included while still being able to compile preprocessed code. +#define assert + +MS: #define static_assert _Static_assert +NOMS-NOT: #define static_assert _Static_assert Index: clang/test/Sema/static-assert.c =================================================================== --- clang/test/Sema/static-assert.c +++ clang/test/Sema/static-assert.c @@ -1,5 +1,5 @@ // RUN: %clang_cc1 -std=c11 -fsyntax-only -verify %s -// RUN: %clang_cc1 -fms-compatibility -DMS -fsyntax-only -verify %s +// RUN: %clang_cc1 -fms-compatibility -DMS -fsyntax-only -verify=expected,ms %s // RUN: %clang_cc1 -std=c99 -pedantic -fsyntax-only -verify=expected,ext %s // RUN: %clang_cc1 -xc++ -std=c++11 -pedantic -fsyntax-only -verify=expected,ext,cxx %s @@ -13,7 +13,7 @@ // ext-warning {{'_Static_assert' is a C11 extension}} #ifdef MS -static_assert(1, "1 is nonzero"); +static_assert(1, "1 is nonzero"); // ms-warning {{'static_assert' is spelled '_Static_assert' in C; consider including }} #endif void foo(void) { @@ -21,7 +21,7 @@ _Static_assert(0, "0 is nonzero"); // expected-error {{static_assert failed "0 is nonzero"}} \ // ext-warning {{'_Static_assert' is a C11 extension}} #ifdef MS - static_assert(1, "1 is nonzero"); + static_assert(1, "1 is nonzero"); // ms-warning {{'static_assert' is spelled '_Static_assert'}} #endif } @@ -34,7 +34,7 @@ _Static_assert(0, "0 is nonzero"); // expected-error {{static_assert failed "0 is nonzero"}} \ // ext-warning {{'_Static_assert' is a C11 extension}} #ifdef MS - static_assert(1, "1 is nonzero"); + static_assert(1, "1 is nonzero"); // ms-warning {{'static_assert' is spelled '_Static_assert'}} #endif }; @@ -58,3 +58,11 @@ // 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}} + +// After defining the assert macro in MS-compatibility mode, we should +// no longer warn about including under the assumption the +// user already did that. +#ifdef MS +#define assert(expr) +static_assert(1, "1 is nonzero"); // ok +#endif Index: clang/test/SemaCXX/static-assert.cpp =================================================================== --- clang/test/SemaCXX/static-assert.cpp +++ clang/test/SemaCXX/static-assert.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11 -triple=x86_64-linux-gnu +// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11 -pedantic -triple=x86_64-linux-gnu int f(); // expected-note {{declared here}}