diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -139,6 +139,7 @@ def BuiltinMacroRedefined : DiagGroup<"builtin-macro-redefined">; def BuiltinRequiresHeader : DiagGroup<"builtin-requires-header">; def C99Compat : DiagGroup<"c99-compat">; +def C2xCompat : DiagGroup<"c2x-compat">; def CXXCompat: DiagGroup<"c++-compat">; def ExternCCompat : DiagGroup<"extern-c-compat">; def KeywordCompat : DiagGroup<"keyword-compat">; diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -80,6 +80,10 @@ InGroup, DefaultIgnore; def warn_cxx20_keyword : Warning<"'%0' is a keyword in C++20">, InGroup, DefaultIgnore; +def warn_c99_keyword : Warning<"'%0' is a keyword in C99">, + InGroup, DefaultIgnore; +def warn_c2x_keyword : Warning<"'%0' is a keyword in C2x">, + InGroup, DefaultIgnore; def ext_unterminated_char_or_string : ExtWarn< "missing terminating %select{'|'\"'}0 character">, InGroup; diff --git a/clang/include/clang/Basic/IdentifierTable.h b/clang/include/clang/Basic/IdentifierTable.h --- a/clang/include/clang/Basic/IdentifierTable.h +++ b/clang/include/clang/Basic/IdentifierTable.h @@ -17,6 +17,7 @@ #include "clang/Basic/LLVM.h" #include "clang/Basic/TokenKinds.h" +#include "clang/Lex/LexDiagnostic.h" #include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringMap.h" @@ -668,6 +669,12 @@ /// Populate the identifier table with info about the language keywords /// for the language specified by \p LangOpts. void AddKeywords(const LangOptions &LangOpts); + + /// Returns the correct diagnostic to issue for a future-compat diagnostic + /// warning. Note, this function assumes the identifier passed has already + /// been determined to be a future compatible keyword. + diag::kind getFutureCompatDiagKind(const IdentifierInfo &II, + const LangOptions &LangOpts); }; /// A family of Objective-C methods. diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -28,6 +28,12 @@ #ifndef CXX20_KEYWORD #define CXX20_KEYWORD(X,Y) KEYWORD(X,KEYCXX20|(Y)) #endif +#ifndef C99_KEYWORD +#define C99_KEYWORD(X,Y) KEYWORD(X,KEYC99|(Y)) +#endif +#ifndef C2X_KEYWORD +#define C2X_KEYWORD(X,Y) KEYWORD(X,KEYC2X|(Y)) +#endif #ifndef COROUTINES_KEYWORD #define COROUTINES_KEYWORD(X) CXX20_KEYWORD(X,KEYCOROUTINES) #endif @@ -250,6 +256,7 @@ // always be treated as a keyword // KEYC99 - This is a keyword introduced to C in C99 // KEYC11 - This is a keyword introduced to C in C11 +// KEYC2X - This is a keyword introduced to C in C2x // KEYCXX - This is a C++ keyword, or a C++-specific keyword in the // implementation namespace // KEYNOCXX - This is a keyword in every non-C++ dialect. @@ -291,13 +298,11 @@ KEYWORD(for , KEYALL) KEYWORD(goto , KEYALL) KEYWORD(if , KEYALL) -KEYWORD(inline , KEYC99|KEYCXX|KEYGNU) KEYWORD(int , KEYALL) KEYWORD(_ExtInt , KEYALL) KEYWORD(_BitInt , KEYALL) KEYWORD(long , KEYALL) KEYWORD(register , KEYALL) -KEYWORD(restrict , KEYC99) KEYWORD(return , KEYALL) KEYWORD(short , KEYALL) KEYWORD(signed , KEYALL) @@ -328,7 +333,6 @@ // C++ 2.11p1: Keywords. KEYWORD(asm , KEYCXX|KEYGNU) -KEYWORD(bool , BOOLSUPPORT) KEYWORD(catch , KEYCXX) KEYWORD(class , KEYCXX) KEYWORD(const_cast , KEYCXX) @@ -336,7 +340,6 @@ KEYWORD(dynamic_cast , KEYCXX) KEYWORD(explicit , KEYCXX) KEYWORD(export , KEYCXX) -KEYWORD(false , BOOLSUPPORT) KEYWORD(friend , KEYCXX) KEYWORD(mutable , KEYCXX) KEYWORD(namespace , KEYCXX) @@ -350,7 +353,6 @@ KEYWORD(template , KEYCXX) KEYWORD(this , KEYCXX) KEYWORD(throw , KEYCXX) -KEYWORD(true , BOOLSUPPORT) KEYWORD(try , KEYCXX) KEYWORD(typename , KEYCXX) KEYWORD(typeid , KEYCXX) @@ -371,18 +373,28 @@ CXX_KEYWORD_OPERATOR(xor , caret) CXX_KEYWORD_OPERATOR(xor_eq , caretequal) +// C99 Keywords. +C99_KEYWORD(restrict , 0) +C99_KEYWORD(inline , KEYCXX|KEYGNU) + +// C23 Keywords. +C2X_KEYWORD(bool , BOOLSUPPORT) +C2X_KEYWORD(false , BOOLSUPPORT) +C2X_KEYWORD(true , BOOLSUPPORT) +C2X_KEYWORD(remove_quals , 0) + // C++11 keywords -CXX11_KEYWORD(alignas , 0) +CXX11_KEYWORD(alignas , KEYC2X) // alignof and _Alignof return the required ABI alignment -CXX11_UNARY_EXPR_OR_TYPE_TRAIT(alignof, AlignOf, 0) +CXX11_UNARY_EXPR_OR_TYPE_TRAIT(alignof, AlignOf, KEYC2X) CXX11_KEYWORD(char16_t , KEYNOMS18) CXX11_KEYWORD(char32_t , KEYNOMS18) CXX11_KEYWORD(constexpr , 0) CXX11_KEYWORD(decltype , 0) CXX11_KEYWORD(noexcept , 0) CXX11_KEYWORD(nullptr , 0) -CXX11_KEYWORD(static_assert , KEYMSCOMPAT) -CXX11_KEYWORD(thread_local , 0) +CXX11_KEYWORD(static_assert , KEYMSCOMPAT|KEYC2X) +CXX11_KEYWORD(thread_local , KEYC2X) // C++20 / coroutines TS keywords COROUTINES_KEYWORD(co_await) @@ -444,7 +456,7 @@ KEYWORD(__auto_type , KEYALL) // GNU Extensions (outside impl-reserved namespace) -KEYWORD(typeof , KEYGNU) +KEYWORD(typeof , KEYGNU|KEYC2X) // MS Extensions KEYWORD(__FUNCDNAME__ , KEYMS) @@ -933,3 +945,5 @@ #undef KEYWORD #undef PUNCTUATOR #undef TOK +#undef C99_KEYWORD +#undef C2X_KEYWORD diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -93,7 +93,7 @@ KEYNOCXX = 0x80, KEYBORLAND = 0x100, KEYOPENCLC = 0x200, - KEYC11 = 0x400, + KEYC2X = 0x400, KEYNOMS18 = 0x800, KEYNOOPENCL = 0x1000, WCHARSUPPORT = 0x2000, @@ -140,15 +140,13 @@ switch (Flag) { case KEYC99: - // FIXME: This should have KS_Future logic here, but that can only happen if - // getFutureCompatDiagKind ALSO gets updated. This is safe, since C mode is - // ALWAYS implied. - return LangOpts.C99 ? KS_Enabled : KS_Unknown; - case KEYC11: - // FIXME: This should have KS_Future logic here, but that can only happen if - // getFutureCompatDiagKind ALSO gets updated. This is safe, since C mode is - // ALWAYS implied. - return LangOpts.C11 ? KS_Enabled : KS_Unknown; + if (LangOpts.C99) + return KS_Enabled; + return !LangOpts.CPlusPlus ? KS_Future : KS_Unknown; + case KEYC2X: + if (LangOpts.C2x) + return KS_Enabled; + return !LangOpts.CPlusPlus ? KS_Future : KS_Unknown; case KEYCXX: return LangOpts.CPlusPlus ? KS_Enabled : KS_Unknown; case KEYCXX11: @@ -845,3 +843,39 @@ } llvm_unreachable("Unknown nullability kind."); } + +/// Returns a diagnostic message kind for reporting a future keyword as +/// appropriate for the identifier and specified language. +diag::kind +IdentifierTable::getFutureCompatDiagKind(const IdentifierInfo &II, + const LangOptions &LangOpts) { + assert(II.isFutureCompatKeyword() && "diagnostic should not be needed"); + + unsigned Flags = llvm::StringSwitch(II.getName()) +#define KEYWORD(NAME, FLAGS) .Case(#NAME, FLAGS) +#include "clang/Basic/TokenKinds.def" +#undef KEYWORD + ; + + // Checking the Language mode and then for the diagnostics. + + if (LangOpts.CPlusPlus) { + if ((Flags & KEYCXX11) == KEYCXX11) + return diag::warn_cxx11_keyword; + + // char8_t is not modeled as a CXX20_KEYWORD because it's not + // unconditionally enabled in C++20 mode. (It can be disabled + // by -fno-char8_t.) + if (((Flags & KEYCXX20) == KEYCXX20) || + ((Flags & CHAR8SUPPORT) == CHAR8SUPPORT)) + return diag::warn_cxx20_keyword; + } else { + if ((Flags & KEYC99) == KEYC99) + return diag::warn_c99_keyword; + if ((Flags & KEYC2X) == KEYC2X) + return diag::warn_c2x_keyword; + } + + llvm_unreachable( + "Keyword not known to come from a newer Standard or proposed Standard"); +} diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -773,29 +773,6 @@ Diag(Identifier,it->second) << Identifier.getIdentifierInfo(); } -/// Returns a diagnostic message kind for reporting a future keyword as -/// appropriate for the identifier and specified language. -static diag::kind getFutureCompatDiagKind(const IdentifierInfo &II, - const LangOptions &LangOpts) { - assert(II.isFutureCompatKeyword() && "diagnostic should not be needed"); - - if (LangOpts.CPlusPlus) - return llvm::StringSwitch(II.getName()) -#define CXX11_KEYWORD(NAME, FLAGS) \ - .Case(#NAME, diag::warn_cxx11_keyword) -#define CXX20_KEYWORD(NAME, FLAGS) \ - .Case(#NAME, diag::warn_cxx20_keyword) -#include "clang/Basic/TokenKinds.def" - // char8_t is not modeled as a CXX20_KEYWORD because it's not - // unconditionally enabled in C++20 mode. (It can be disabled - // by -fno-char8_t.) - .Case("char8_t", diag::warn_cxx20_keyword) - ; - - llvm_unreachable( - "Keyword not known to come from a newer Standard or proposed Standard"); -} - void Preprocessor::updateOutOfDateIdentifier(IdentifierInfo &II) const { assert(II.isOutOfDate() && "not out of date"); getExternalSource()->updateOutOfDateIdentifier(II); @@ -867,7 +844,7 @@ // FIXME: This warning is disabled in cases where it shouldn't be, like // "#define constexpr constexpr", "int constexpr;" if (II.isFutureCompatKeyword() && !DisableMacroExpansion) { - Diag(Identifier, getFutureCompatDiagKind(II, getLangOpts())) + Diag(Identifier, getIdentifierTable().getFutureCompatDiagKind(II, getLangOpts())) << II.getName(); // Don't diagnose this keyword again in this translation unit. II.setIsFutureCompatKeyword(false); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -934,10 +934,11 @@ if (Tok.is(tok::kw__Static_assert) && !getLangOpts().C11) Diag(Tok, diag::ext_c11_feature) << Tok.getName(); if (Tok.is(tok::kw_static_assert)) { - if (!getLangOpts().CPlusPlus) - Diag(Tok, diag::ext_ms_static_assert) - << FixItHint::CreateReplacement(Tok.getLocation(), "_Static_assert"); - else + if (!getLangOpts().CPlusPlus) { + if (!getLangOpts().C2x) + Diag(Tok, diag::ext_ms_static_assert) << FixItHint::CreateReplacement( + Tok.getLocation(), "_Static_assert"); + } else Diag(Tok, diag::warn_cxx98_compat_static_assert); } diff --git a/clang/test/Lexer/keywords_test.c b/clang/test/Lexer/keywords_test.c --- a/clang/test/Lexer/keywords_test.c +++ b/clang/test/Lexer/keywords_test.c @@ -14,6 +14,41 @@ // RUN: %clang_cc1 -std=c99 -fms-extensions -fno-declspec -E %s -o - \ // RUN: | FileCheck --check-prefix=CHECK-MS-KEYWORDS-WITHOUT-DECLSPEC %s +// RUN: %clang_cc1 -std=c99 -DC99 -fsyntax-only %s +// RUN: %clang_cc1 -std=c2x -DC99 -DC2x -fsyntax-only %s + +// RUN: %clang_cc1 -fsyntax-only -std=c89 -DFutureKeyword -Wc2x-compat -Wc99-compat -verify=c89 %s + +#define IS_KEYWORD(NAME) _Static_assert(!__is_identifier(NAME), #NAME) +#define NOT_KEYWORD(NAME) _Static_assert(__is_identifier(NAME), #NAME) + +#if defined(C99) +#define C99_KEYWORD(NAME) IS_KEYWORD(NAME) +#else +#define C99_KEYWORD(NAME) NOT_KEYWORD(NAME) +#endif + +#if defined(C2x) +#define C2x_KEYWORD(NAME) IS_KEYWORD(NAME) +#else +#define C2x_KEYWORD(NAME) NOT_KEYWORD(NAME) +#endif + +// C99 Keywords. +C99_KEYWORD(restrict); +C99_KEYWORD(inline); + +// C2x Keywords. +C2x_KEYWORD(bool); +C2x_KEYWORD(true); +C2x_KEYWORD(false); +C2x_KEYWORD(remove_quals); +C2x_KEYWORD(static_assert); +C2x_KEYWORD(typeof); +C2x_KEYWORD(thread_local); +C2x_KEYWORD(alignas); +C2x_KEYWORD(alignof); + void f() { // CHECK-NONE: int asm // CHECK-GNU-KEYWORDS: asm ("ret" : :) @@ -52,3 +87,19 @@ #else void has_static_assert(); #endif + +#ifdef FutureKeyword + + int restrict; // c89-warning {{'restrict' is a keyword in C99}} + int inline; // c89-warning {{'inline' is a keyword in C99}} + + int bool; // c89-warning {{'bool' is a keyword in C2x}} + char true; // c89-warning {{'true' is a keyword in C2x}} + char false; // c89-warning {{'false' is a keyword in C2x}} + float alignof; // c89-warning {{'alignof' is a keyword in C2x}} + int typeof; // c89-warning {{'typeof' is a keyword in C2x}} + int alignas; // c89-warning {{'alignas' is a keyword in C2x}} + int remove_quals; // c89-warning {{'remove_quals' is a keyword in C2x}} + int static_assert; // c89-warning {{'static_assert' is a keyword in C2x}} + +#endif diff --git a/clang/test/Lexer/keywords_test.cpp b/clang/test/Lexer/keywords_test.cpp --- a/clang/test/Lexer/keywords_test.cpp +++ b/clang/test/Lexer/keywords_test.cpp @@ -15,6 +15,8 @@ // RUN: %clang -std=c++03 -target i686-windows-msvc -DMS -fno-declspec -fsyntax-only %s // RUN: %clang -std=c++03 -target x86_64-scei-ps4 -fno-declspec -fsyntax-only %s +// RUN: %clang_cc1 -std=c++98 -DFutureKeyword -fsyntax-only -Wc++11-compat -Wc++20-compat -verify=cxx98 %s + #define IS_KEYWORD(NAME) _Static_assert(!__is_identifier(NAME), #NAME) #define NOT_KEYWORD(NAME) _Static_assert(__is_identifier(NAME), #NAME) #define IS_TYPE(NAME) void is_##NAME##_type() { int f(NAME); } @@ -50,17 +52,24 @@ CXX11_TYPE(char32_t); CXX11_KEYWORD(constexpr); CXX11_KEYWORD(noexcept); + #ifndef MS CXX11_KEYWORD(static_assert); #else // MS compiler recognizes static_assert in all modes. So should we. IS_KEYWORD(static_assert); #endif + CXX11_KEYWORD(thread_local); // Concepts keywords CXX20_KEYWORD(concept); CXX20_KEYWORD(requires); +CXX20_KEYWORD(consteval); +CXX20_KEYWORD(constinit); +CXX20_KEYWORD(co_await); +CXX20_KEYWORD(co_return); +CXX20_KEYWORD(co_yield); // __declspec extension DECLSPEC_KEYWORD(__declspec); @@ -70,3 +79,26 @@ IS_TYPE(__char16_t); IS_KEYWORD(__char32_t); IS_TYPE(__char32_t); + +#ifdef FutureKeyword + +int nullptr; // cxx98-warning {{'nullptr' is a keyword in C++11}} +int decltype; // cxx98-warning {{'decltype' is a keyword in C++11}} +int alignof; // cxx98-warning {{'alignof' is a keyword in C++11}} +int alignas; // cxx98-warning {{'alignas' is a keyword in C++11}} +int char16_t; // cxx98-warning {{'char16_t' is a keyword in C++11}} +int char32_t; // cxx98-warning {{'char32_t' is a keyword in C++11}} +int constexpr; // cxx98-warning {{'constexpr' is a keyword in C++11}} +int noexcept; // cxx98-warning {{'noexcept' is a keyword in C++11}} +int static_assert; // cxx98-warning {{'static_assert' is a keyword in C++11}} +char thread_local; // cxx98-warning {{'thread_local' is a keyword in C++11}} + +int co_await; // cxx98-warning {{'co_await' is a keyword in C++20}} +char co_return; // cxx98-warning {{'co_return' is a keyword in C++20}} +char co_yield; // cxx98-warning {{'co_yield' is a keyword in C++20}} +int constinit; // cxx98-warning {{'constinit' is a keyword in C++20}} +int consteval; // cxx98-warning {{'consteval' is a keyword in C++20}} +int requires; // cxx98-warning {{'requires' is a keyword in C++20}} +int concept; // cxx98-warning {{'concept' is a keyword in C++20}} + +#endif diff --git a/clang/test/Parser/static_assert.c b/clang/test/Parser/static_assert.c --- a/clang/test/Parser/static_assert.c +++ b/clang/test/Parser/static_assert.c @@ -1,5 +1,5 @@ -// 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=c17 -DTEST_SPELLING -Weverything -verify=c17 %s +// RUN: %clang_cc1 -fsyntax-only -std=c17 -DTEST_SPELLING -fms-compatibility -verify=c17-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 @@ -15,11 +15,15 @@ // 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-error {{a type specifier is required for all declarations}} \ - // c2x-ms-warning {{use of 'static_assert' without inclusion of is a Microsoft extension}} +static_assert(1, ""); // c17-warning {{'static_assert' is a keyword in C2x}} \ + // c17-error {{expected parameter declarator}} \ + // c17-error {{expected ')'}} \ + // c17-note {{to match this '('}} \ + // c17-error {{type specifier missing, defaults to 'int'; ISO C99 and later do not support implicit int}} \ + // c17-warning {{ function declaration without a prototype is deprecated in all versions of C}} \ + // c17-ms-warning {{use of 'static_assert' without inclusion of is a Microsoft extension}} + + #endif // We support _Static_assert as an extension in older C modes and in all C++ @@ -42,4 +46,7 @@ // 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}} + // cxx98-pedantic-warning {{'_Static_assert' is a C11 extension}} \ + // c17-warning {{'_Static_assert' with no message is a C2x extension}} \ + // c17-ms-warning {{'_Static_assert' with no message is a C2x extension}} +