Repository: llvm/llvm-project
Pull request #137234: [C] Diagnose use of C++ keywords in C
Author: login: AaronBallman
Labels: name: clang
description: Clang issues not falling into any other category
name: c
name: clang:frontend
description: Language frontend issues, e.g. anything involving "Sema"
Created: 2025-04-24T18:56:27Z | Merged: 2025-05-02T11:20:02Z

This adds a new diagnostic group, -Wc++-keyword, which is off by default and grouped under -Wc++-compat. The diagnostic catches use of C++ keywords in C code.

This change additionally fixes an issue with -Wreserved-identifier not diagnosing use of reserved identifiers in function parameter lists in a function declaration which is not a definition.

Fixes https://github.com/llvm/llvm-project/issues/21898


Diff:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 1dd97bcc67364..ee19309e4a9ce 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -154,6 +154,8 @@ C Language Changes
 - Added ``-Wimplicit-void-ptr-cast``, grouped under ``-Wc++-compat``, which
   diagnoses implicit conversion from ``void *`` to another pointer type as
   being incompatible with C++. (#GH17792)
+- Added ``-Wc++-keyword``, grouped under ``-Wc++-compat``, which diagnoses when
+  a C++ keyword is used as an identifier in C. (#GH21898)
 - Added ``-Wc++-hidden-decl``, grouped under ``-Wc++-compat``, which diagnoses
   use of tag types which are visible in C but not visible in C++ due to scoping
   rules. e.g.,
@@ -481,6 +483,8 @@ Improvements to Clang's diagnostics
 - ``-Winitializer-overrides`` and ``-Wreorder-init-list`` are now grouped under
   the ``-Wc99-designator`` diagnostic group, as they also are about the
   behavior of the C99 feature as it was introduced into C++20. Fixes #GH47037
+- ``-Wreserved-identifier`` now fires on reserved parameter names in a function
+  declaration which is not a definition.
 
 Improvements to Clang's time-trace
 ----------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 4f235fecf4cf9..58439553d41c9 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -157,6 +157,7 @@ def C99Compat : DiagGroup<"c99-compat">;
 def C23Compat : DiagGroup<"c23-compat">;
 def : DiagGroup<"c2x-compat", [C23Compat]>;
 
+def CppKeywordInC : DiagGroup<"c++-keyword">;
 def DuplicateDeclSpecifier : DiagGroup<"duplicate-decl-specifier">;
 def InitStringTooLongMissingNonString :
   DiagGroup<"unterminated-string-initialization">;
@@ -178,9 +179,9 @@ def ImplicitIntToEnumCast : DiagGroup<"implicit-int-enum-cast",
                                       [ImplicitEnumEnumCast]>;
 def TentativeDefnCompat : DiagGroup<"tentative-definition-compat">;
 def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast, DefaultConstInit,
-                                        ImplicitIntToEnumCast, HiddenCppDecl,
-                                        InitStringTooLongForCpp,
-                                        TentativeDefnCompat,
+                                        ImplicitIntToEnumCast, CppKeywordInC,
+                                        HiddenCppDecl, TentativeDefnCompat,
+                                        InitStringTooLongForCpp,                                        
                                         DuplicateDeclSpecifier]>;
 
 def ExternCCompat : DiagGroup<"extern-c-compat">;
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index f29edfa835d42..a3c32107596f0 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -421,6 +421,9 @@ def warn_pp_macro_is_reserved_attribute_id : Warning<
 def warn_pp_objc_macro_redef_ignored : Warning<
   "ignoring redefinition of Objective-C qualifier macro">,
   InGroup<DiagGroup<"objc-macro-redefinition">>;
+def warn_pp_identifier_is_cpp_keyword : Warning<
+  "identifier %0 conflicts with a C++ keyword">,
+  InGroup<CppKeywordInC>, DefaultIgnore;
 
 def pp_invalid_string_literal : Warning<
   "invalid string literal, ignoring final '\\'">;
diff --git a/clang/include/clang/Basic/IdentifierTable.h b/clang/include/clang/Basic/IdentifierTable.h
index 1275b056227b5..54540193cfcc0 100644
--- a/clang/include/clang/Basic/IdentifierTable.h
+++ b/clang/include/clang/Basic/IdentifierTable.h
@@ -195,7 +195,11 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
   LLVM_PREFERRED_TYPE(bool)
   unsigned IsFinal : 1;
 
-  // 22 bits left in a 64-bit word.
+  // True if this identifier would be a keyword in C++ mode.
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned IsKeywordInCpp : 1;
+
+  // 21 bits left in a 64-bit word.
 
   // Managed by the language front-end.
   void *FETokenInfo = nullptr;
@@ -212,7 +216,7 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
         IsFromAST(false), ChangedAfterLoad(false), FEChangedAfterLoad(false),
         RevertedTokenID(false), OutOfDate(false), IsModulesImport(false),
         IsMangledOpenMPVariantName(false), IsDeprecatedMacro(false),
-        IsRestrictExpansion(false), IsFinal(false) {}
+        IsRestrictExpansion(false), IsFinal(false), IsKeywordInCpp(false) {}
 
 public:
   IdentifierInfo(const IdentifierInfo &) = delete;
@@ -444,6 +448,10 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
   }
   bool isCPlusPlusOperatorKeyword() const { return IsCPPOperatorKeyword; }
 
+  /// Return true if this identifier would be a keyword in C++ mode.
+  bool IsKeywordInCPlusPlus() const { return IsKeywordInCpp; }
+  void setIsKeywordInCPlusPlus(bool Val = true) { IsKeywordInCpp = Val; }
+
   /// Return true if this token is a keyword in the specified language.
   bool isKeyword(const LangOptions &LangOpts) const;
 
@@ -462,6 +470,7 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
   /// If this returns false, we know that HandleIdentifier will not affect
   /// the token.
   bool isHandleIdentifierCase() const { return NeedsHandleIdentifier; }
+  void setHandleIdentifierCase(bool Val = true) { NeedsHandleIdentifier = Val; }
 
   /// Return true if the identifier in its current state was loaded
   /// from an AST file.
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 868e851342eb8..fb53d88deea4a 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -1042,6 +1042,7 @@ ANNOTATION(embed)
 #undef TYPE_TRAIT_2
 #undef TYPE_TRAIT_1
 #undef TYPE_TRAIT
+#undef MODULES_KEYWORD
 #undef CXX20_KEYWORD
 #undef CXX11_KEYWORD
 #undef KEYWORD
diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp
index 16151c94464f9..cc472c9044c82 100644
--- a/clang/lib/Basic/IdentifierTable.cpp
+++ b/clang/lib/Basic/IdentifierTable.cpp
@@ -250,6 +250,32 @@ static KeywordStatus getKeywordStatus(const LangOptions &LangOpts,
   return CurStatus;
 }
 
+static bool IsKeywordInCpp(unsigned Flags) {
+  while (Flags != 0) {
+    unsigned CurFlag = Flags & ~(Flags - 1);
+    Flags = Flags & ~CurFlag;
+    switch (static_cast<TokenKey>(CurFlag)) {
+    case KEYCXX:
+    case KEYCXX11:
+    case KEYCXX20:
+    case BOOLSUPPORT:
+    case WCHARSUPPORT:
+    case CHAR8SUPPORT:
+      return true;
+    default:
+      break; // Go to the next flag, try again.
+    }
+  }
+  return false;
+}
+
+static void MarkIdentifierAsKeywordInCpp(IdentifierTable &Table,
+                                         StringRef Name) {
+  IdentifierInfo &II = Table.get(Name, tok::identifier);
+  II.setIsKeywordInCPlusPlus();
+  II.setHandleIdentifierCase();
+}
+
 /// AddKeyword - This method is used to associate a token ID with specific
 /// identifiers because they are language keywords.  This causes the lexer to
 /// automatically map matching identifiers to specialized token codes.
@@ -258,8 +284,18 @@ static void AddKeyword(StringRef Keyword,
                        const LangOptions &LangOpts, IdentifierTable &Table) {
   KeywordStatus AddResult = getKeywordStatus(LangOpts, Flags);
 
-  // Don't add this keyword if disabled in this language.
-  if (AddResult == KS_Disabled) return;
+  // Don't add this keyword if disabled in this language and isn't otherwise
+  // special.
+  if (AddResult == KS_Disabled) {
+    // We do not consider any identifiers to be C++ keywords when in
+    // Objective-C because @ effectively introduces a custom grammar where C++
+    // keywords can be used (and similar for selectors). We could enable this
+    // for Objective-C, but it would require more logic to ensure we do not
+    // issue compatibility diagnostics in these cases.
+    if (!LangOpts.ObjC && IsKeywordInCpp(Flags))
+      MarkIdentifierAsKeywordInCpp(Table, Keyword);
+    return;
+  }
 
   IdentifierInfo &Info =
       Table.get(Keyword, AddResult == KS_Future ? tok::identifier : TokenCode);
@@ -304,9 +340,11 @@ void IdentifierTable::AddKeywords(const LangOptions &LangOpts) {
 #define ALIAS(NAME, TOK, FLAGS) \
   AddKeyword(StringRef(NAME), tok::kw_ ## TOK,  \
              FLAGS, LangOpts, *this);
-#define CXX_KEYWORD_OPERATOR(NAME, ALIAS) \
-  if (LangOpts.CXXOperatorNames)          \
-    AddCXXOperatorKeyword(StringRef(#NAME), tok::ALIAS, *this);
+#define CXX_KEYWORD_OPERATOR(NAME, ALIAS)                                      \
+  if (LangOpts.CXXOperatorNames)                                               \
+    AddCXXOperatorKeyword(StringRef(#NAME), tok::ALIAS, *this);                \
+  else                                                                         \
+    MarkIdentifierAsKeywordInCpp(*this, StringRef(#NAME));
 #define OBJC_AT_KEYWORD(NAME)  \
   if (LangOpts.ObjC)           \
     AddObjCKeyword(StringRef(#NAME), tok::objc_##NAME, *this);
diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp
index 4c050bf1f5bb2..9ea7b95622c76 100644
--- a/clang/lib/Lex/Preprocessor.cpp
+++ b/clang/lib/Lex/Preprocessor.cpp
@@ -834,6 +834,11 @@ bool Preprocessor::HandleIdentifier(Token &Identifier) {
     II.setIsFutureCompatKeyword(false);
   }
 
+  // If this identifier would be a keyword in C++, diagnose as a compatibility
+  // issue.
+  if (II.IsKeywordInCPlusPlus() && !DisableMacroExpansion)
+    Diag(Identifier, diag::warn_pp_identifier_is_cpp_keyword) << &II;
+
   // If this is an extension token, diagnose its use.
   // We avoid diagnosing tokens that originate from macro definitions.
   // FIXME: This warning is disabled in cases where it shouldn't be,
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 517b3067696a0..5d425778c850a 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -59,8 +59,10 @@
 #include "clang/Sema/SemaWasm.h"
 #include "clang/Sema/Template.h"
 #include "llvm/ADT/STLForwardCompat.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringSwitch.h"
 #include "llvm/Support/SaveAndRestore.h"
 #include "llvm/TargetParser/Triple.h"
 #include <algorithm>
@@ -10421,6 +10423,15 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
   // Finally, we know we have the right number of parameters, install them.
   NewFD->setParams(Params);
 
+  // If this declarator is a declaration and not a definition, its parameters
+  // will not be pushed onto a scope chain. That means we will not issue any
+  // reserved identifier warnings for the declaration, but we will for the
+  // definition. Handle those here.
+  if (!D.isFunctionDefinition()) {
+    for (const ParmVarDecl *PVD : Params)
+      warnOnReservedIdentifier(PVD);
+  }
+
   if (D.getDeclSpec().isNoreturnSpecified())
     NewFD->addAttr(
         C11NoReturnAttr::Create(Context, D.getDeclSpec().getNoreturnSpecLoc()));
diff --git a/clang/test/OpenMP/assumes_messages.c b/clang/test/OpenMP/assumes_messages.c
index 9bbedf59b77fc..4d63cc2e8aca5 100644
--- a/clang/test/OpenMP/assumes_messages.c
+++ b/clang/test/OpenMP/assumes_messages.c
@@ -59,8 +59,14 @@
 #pragma omp begin assumes ext // expected-warning {{valid begin assumes clauses start with 'ext_', 'absent', 'contains', 'holds', 'no_openmp', 'no_openmp_routines', 'no_openmp_constructs', 'no_parallelism'; token will be ignored}}
 #pragma omp end assumes
 
-#pragma omp assumes ext_123(not allowed) // expected-warning {{'ext_123' clause should not be followed by arguments; tokens will be ignored}} expected-note {{the ignored tokens spans until here}}
-#pragma omp begin assumes ext_123(not allowed) // expected-warning {{'ext_123' clause should not be followed by arguments; tokens will be ignored}} expected-note {{the ignored tokens spans until here}}
+// FIXME: We should be getting an expected note about where the span of ignored
+// tokens ends. However, error recovery ends up lexing the 'not' token,
+// emitting a (silenced) diagnostic about use of a C++ keyword in C, and the
+// note gets associated with *that* (silenced) diagnostic. This is an existing
+// issue that also happens with error recovery of reserved identifiers or
+// extension tokens, but is unfortunate nonetheless.
+#pragma omp assumes ext_123(not allowed) // expected-warning {{'ext_123' clause should not be followed by arguments; tokens will be ignored}}
+#pragma omp begin assumes ext_123(not allowed) // expected-warning {{'ext_123' clause should not be followed by arguments; tokens will be ignored}}
 #pragma omp end assumes
 
 #pragma omp end assumes // expected-error {{'#pragma omp end assumes' with no matching '#pragma omp begin assumes'}}
diff --git a/clang/test/Sema/c++-keyword-in-c.c b/clang/test/Sema/c++-keyword-in-c.c
new file mode 100644
index 0000000000000..f417469441114
--- /dev/null
+++ b/clang/test/Sema/c++-keyword-in-c.c
@@ -0,0 +1,239 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-keyword %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-compat %s
+// RUN: %clang_cc1 -fsyntax-only -verify=good %s
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx -x c++ -std=c++2c %s
+// good-no-diagnostics
+
+// 'try', 'this' and 'throw' are not tested as identifiers, but are instead
+// tested as other constructs (otherwise there would be redefinition errors in
+// C).
+int catch;              // expected-warning {{identifier 'catch' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int class;              // expected-warning {{identifier 'class' conflicts with a C++ keyword}} \
+                           cxx-error {{declaration of anonymous class must be a definition}} \
+                           cxx-warning {{declaration does not declare anything}}
+int const_cast;         // expected-warning {{identifier 'const_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int delete;             // expected-warning {{identifier 'delete' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int dynamic_cast;       // expected-warning {{identifier 'dynamic_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int explicit;           // expected-warning {{identifier 'explicit' conflicts with a C++ keyword}} \
+                           cxx-error {{'explicit' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+int export;             // expected-warning {{identifier 'export' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int friend;             // expected-warning {{identifier 'friend' conflicts with a C++ keyword}} \
+                           cxx-error {{'friend' used outside of class}} \
+                           cxx-warning {{declaration does not declare anything}}
+int mutable;            // expected-warning {{identifier 'mutable' conflicts with a C++ keyword}} \
+                           cxx-warning {{declaration does not declare anything}}
+int namespace;          // expected-warning {{identifier 'namespace' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int new;                // expected-warning {{identifier 'new' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int operator;           // expected-warning {{identifier 'operator' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a type}}
+int private;            // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int protected;          // expected-warning {{identifier 'protected' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int public;             // expected-warning {{identifier 'public' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int reinterpret_cast;   // expected-warning {{identifier 'reinterpret_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int static_cast;        // expected-warning {{identifier 'static_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int template;           // expected-warning {{identifier 'template' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int typename;           // expected-warning {{identifier 'typename' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a qualified name after 'typename'}}
+int typeid;             // expected-warning {{identifier 'typeid' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int using;              // expected-warning {{identifier 'using' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int virtual;            // expected-warning {{identifier 'virtual' conflicts with a C++ keyword}} \
+                           cxx-error {{'virtual' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+int wchar_t;            // expected-warning {{identifier 'wchar_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char8_t;            // expected-warning {{identifier 'char8_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char16_t;           // expected-warning {{identifier 'char16_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char32_t;           // expected-warning {{identifier 'char32_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int noexcept;           // expected-warning {{identifier 'noexcept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_await;           // expected-warning {{identifier 'co_await' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_return;          // expected-warning {{identifier 'co_return' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_yield;           // expected-warning {{identifier 'co_yield' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int consteval;          // expected-warning {{identifier 'consteval' conflicts with a C++ keyword}} \
+                           cxx-error {{consteval can only be used in function declarations}}
+int constinit;          // expected-warning {{identifier 'constinit' conflicts with a C++ keyword}} \
+                           cxx-error {{constinit can only be used in variable declarations}}
+int concept;            // expected-warning {{identifier 'concept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int requires;           // expected-warning {{identifier 'requires' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+
+// Now try the same thing, but as struct members.
+struct S {
+  int catch;            // expected-warning {{identifier 'catch' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int class;            // expected-warning {{identifier 'class' conflicts with a C++ keyword}} \
+                           cxx-error {{declaration of anonymous class must be a definition}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int const_cast;       // expected-warning {{identifier 'const_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int delete;           // expected-warning {{identifier 'delete' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int dynamic_cast;     // expected-warning {{identifier 'dynamic_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int explicit;         // expected-warning {{identifier 'explicit' conflicts with a C++ keyword}} \
+                           cxx-error {{'explicit' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int export;           // expected-warning {{identifier 'export' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int friend;           // expected-warning {{identifier 'friend' conflicts with a C++ keyword}} \
+                           cxx-error {{'friend' must appear first in a non-function declaration}}
+  int mutable;          // expected-warning {{identifier 'mutable' conflicts with a C++ keyword}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int namespace;        // expected-warning {{identifier 'namespace' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int new;              // expected-warning {{identifier 'new' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int operator;         // expected-warning {{identifier 'operator' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a type}}
+  int private;          // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int protected;        // expected-warning {{identifier 'protected' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int public;           // expected-warning {{identifier 'public' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int reinterpret_cast; // expected-warning {{identifier 'reinterpret_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int static_cast;      // expected-warning {{identifier 'static_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int template;         // expected-warning {{identifier 'template' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int this;             // expected-warning {{identifier 'this' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int throw;            // expected-warning {{identifier 'throw' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int try;              // expected-warning {{identifier 'try' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int typename;         // expected-warning {{identifier 'typename' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a qualified name after 'typename'}}
+  int typeid;           // expected-warning {{identifier 'typeid' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int using;            // expected-warning {{identifier 'using' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int virtual;          // expected-warning {{identifier 'virtual' conflicts with a C++ keyword}} \
+                           cxx-error {{'virtual' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int wchar_t;          // expected-warning {{identifier 'wchar_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int char8_t;          // expected-warning {{identifier 'char8_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int char16_t;         // expected-warning {{identifier 'char16_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int char32_t;         // expected-warning {{identifier 'char32_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int noexcept;         // expected-warning {{identifier 'noexcept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int co_await;         // expected-warning {{identifier 'co_await' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int co_return;        // expected-warning {{identifier 'co_return' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int co_yield;         // expected-warning {{identifier 'co_yield' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int consteval;        // expected-warning {{identifier 'consteval' conflicts with a C++ keyword}} \
+                           cxx-error {{consteval can only be used in function declarations}}
+  int constinit;        // expected-warning {{identifier 'constinit' conflicts with a C++ keyword}} \
+                           cxx-error {{constinit can only be used in variable declarations}}
+  int concept;          // expected-warning {{identifier 'concept' conflicts with a C++ keyword}} \
+                           cxx-error {{concept declarations may only appear in global or namespace scope}}
+  int requires;         // expected-warning {{identifier 'requires' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}} \
+                           cxx-error {{trailing requires clause can only be used when declaring a function}} \
+                           cxx-error {{expected expression}}
+};
+
+// Smoke test that we catch a keyword used as an enumerator. If we diagnose
+// one, we'll diagnose them all.
+enum E {
+  throw, // expected-warning {{identifier 'throw' conflicts with a C++ keyword}} \
+            cxx-error {{expected identifier}}
+};
+
+// Smoke test that we catch a keyword used as a tag name.
+struct try { // expected-warning {{identifier 'try' conflicts with a C++ keyword}} \
+                cxx-error {{declaration of anonymous struct must be a definition}} \
+                cxx-warning {{declaration does not declare anything}}
+  int x;
+};
+
+// Smoke test that we catch keyword use in a function name.
+void this(void);        // expected-warning {{identifier 'this' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+
+// Smoke test that we catch keyword use in function parameters too.
+void func(int private); // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{invalid parameter name: 'private' is a keyword}}
+
+// These are conditionally a keyword in C++, so they're intentionally not being
+// diagnosed as a keyword.
+int module;
+int import;
+int override;
+int final;
+
+// We do not diagnose use of C++ keywords when used as a macro name because
+// that does not conflict with C++ (the macros will be replaced before the
+// keyword is seen by the parser).
+#define this 12
+
+// FIXME: These tests are disabled for C++ because it causes a crash.
+// See GH114815.
+#ifndef __cplusplus
+int decltype;           // expected-warning {{identifier 'decltype' conflicts with a C++ keyword}}
+struct T {
+  int decltype;         // expected-warning {{identifier 'decltype' conflicts with a C++ keyword}}
+};
+#endif // __cplusplus
+
+// Check alternative operator names.
+int and;      // expected-warning {{identifier 'and' conflicts with a C++ keyword}} \
+                 cxx-error {{expected unqualified-id}}
+int and_eq;   // expected-warning {{identifier 'and_eq' conflicts with a C++ keyword}} \
+                 cxx-error {{expected unqualified-id}}
+int bitand;   // expected-warning {{identifier 'bitand' conflicts with a C++ keyword}} \
+                 cxx-error {{expected unqualified-id}}
+int bitor;    // expected-warning {{identifier 'bitor' conflicts with a C++ keyword}} \
+                 cxx-error {{expected unqualified-id}}
+int compl;    // expected-warning {{identifier 'compl' conflicts with a C++ keyword}} \
+                 cxx-error {{expected a class name after '~' to name a destructor}}
+int not;      // expected-warning {{identifier 'not' conflicts with a C++ keyword}} \
+                 cxx-error {{expected unqualified-id}}
+int not_eq;   // expected-warning {{identifier 'not_eq' conflicts with a C++ keyword}} \
+                 cxx-error {{expected unqualified-id}}
+int or;       // expected-warning {{identifier 'or' conflicts with a C++ keyword}} \
+                 cxx-error {{expected unqualified-id}}
+int or_eq;    // expected-warning {{identifier 'or_eq' conflicts with a C++ keyword}} \
+                 cxx-error {{expected unqualified-id}}
+int xor;      // expected-warning {{identifier 'xor' conflicts with a C++ keyword}} \
+                 cxx-error {{expected unqualified-id}}
+int xor_eq;   // expected-warning {{identifier 'xor_eq' conflicts with a C++ keyword}} \
+                 cxx-error {{expected unqualified-id}}
diff --git a/clang/test/Sema/c++-keyword-in-objc.m b/clang/test/Sema/c++-keyword-in-objc.m
new file mode 100644
index 0000000000000..2a6c366991ba5
--- /dev/null
+++ b/clang/test/Sema/c++-keyword-in-objc.m
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-keyword %s
+// expected-no-diagnostics
+
+// Show that we do not diagnose any C++ keywords in Objective-C.
+
+@class Foo; // Okay, Objective-C @ keyword, not a regular identifier
+
+// FIXME: it would be nice to diagnose this, but it is intentionally allowed
+// due to @ and selectors allowing C++ keywords in ways that are supposed to be
+// contextually compatible with C++.
+int class = 12;
+


Reviews:
user: login: shafik
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T19:39:23Z
user: login: Sirraide
body: What about `and`, `or`, and friends (which I think are only keywords in C if you include a certain header irrc).
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T23:00:37Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-25T11:38:07Z
user: login: erichkeane
body: sorry!  I made these comments a while ago, but apparently never submitted them.  
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T15:35:10Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T15:44:32Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T15:45:51Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T15:47:24Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T15:47:45Z
user: login: erichkeane
body: You can remove the `Params.empty` if you'd like, but this LGTM anyway.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-28T15:48:12Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T15:51:40Z
user: login: efriedma-quic
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T17:58:20Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T18:12:54Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T18:17:45Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T18:22:45Z
user: login: efriedma-quic
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T19:19:45Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T21:03:51Z
user: login: efriedma-quic
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T21:31:02Z
user: login: zygoloid
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T22:06:10Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-28T22:28:44Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-29T11:54:19Z
user: login: erichkeane
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-29T13:07:00Z
user: login: zygoloid
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-29T16:55:10Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-29T17:09:55Z
user: login: nikic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T13:26:46Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T13:31:29Z
user: login: AaronBallman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T13:40:11Z

Review comments:
diff_hunk: @@ -0,0 +1,213 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Widentifier-is-c++-keyword %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-compat %s
+// RUN: %clang_cc1 -fsyntax-only -verify=good %s
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx -x c++ -std=c++2c %s
+// good-no-diagnostics
+
+// 'try', 'this' and 'throw' are not tested as identifiers, but are instead
+// tested as other constructs (otherwise there would be redefinition errors in
+// C).
+int catch;              // expected-warning {{identifier 'catch' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int class;              // expected-warning {{identifier 'class' conflicts with a C++ keyword}} \
+                           cxx-error {{declaration of anonymous class must be a definition}} \
+                           cxx-warning {{declaration does not declare anything}}
+int const_cast;         // expected-warning {{identifier 'const_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int delete;             // expected-warning {{identifier 'delete' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int dynamic_cast;       // expected-warning {{identifier 'dynamic_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int explicit;           // expected-warning {{identifier 'explicit' conflicts with a C++ keyword}} \
+                           cxx-error {{'explicit' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+int export;             // expected-warning {{identifier 'export' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int friend;             // expected-warning {{identifier 'friend' conflicts with a C++ keyword}} \
+                           cxx-error {{'friend' used outside of class}} \
+                           cxx-warning {{declaration does not declare anything}}
+int mutable;            // expected-warning {{identifier 'mutable' conflicts with a C++ keyword}} \
+                           cxx-warning {{declaration does not declare anything}}
+int namespace;          // expected-warning {{identifier 'namespace' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int new;                // expected-warning {{identifier 'new' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int operator;           // expected-warning {{identifier 'operator' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a type}}
+int private;            // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int protected;          // expected-warning {{identifier 'protected' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int public;             // expected-warning {{identifier 'public' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int reinterpret_cast;   // expected-warning {{identifier 'reinterpret_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int static_cast;        // expected-warning {{identifier 'static_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int template;           // expected-warning {{identifier 'template' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int typename;           // expected-warning {{identifier 'typename' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a qualified name after 'typename'}}
+int typeid;             // expected-warning {{identifier 'typeid' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int using;              // expected-warning {{identifier 'using' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int virtual;            // expected-warning {{identifier 'virtual' conflicts with a C++ keyword}} \
+                           cxx-error {{'virtual' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+int wchar_t;            // expected-warning {{identifier 'wchar_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char8_t;            // expected-warning {{identifier 'char8_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char16_t;           // expected-warning {{identifier 'char16_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char32_t;           // expected-warning {{identifier 'char32_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int noexcept;           // expected-warning {{identifier 'noexcept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_await;           // expected-warning {{identifier 'co_await' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_return;          // expected-warning {{identifier 'co_return' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_yield;           // expected-warning {{identifier 'co_yield' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int consteval;          // expected-warning {{identifier 'consteval' conflicts with a C++ keyword}} \
+                           cxx-error {{consteval can only be used in function declarations}}
+int constinit;          // expected-warning {{identifier 'constinit' conflicts with a C++ keyword}} \
+                           cxx-error {{constinit can only be used in variable declarations}}
+int concept;            // expected-warning {{identifier 'concept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int requires;           // expected-warning {{identifier 'requires' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+
+// Now try the same thing, but as struct members.
+struct S {
+  int catch;            // expected-warning {{identifier 'catch' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int class;            // expected-warning {{identifier 'class' conflicts with a C++ keyword}} \
+                           cxx-error {{declaration of anonymous class must be a definition}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int const_cast;       // expected-warning {{identifier 'const_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int delete;           // expected-warning {{identifier 'delete' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int dynamic_cast;     // expected-warning {{identifier 'dynamic_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int explicit;         // expected-warning {{identifier 'explicit' conflicts with a C++ keyword}} \
+                           cxx-error {{'explicit' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int export;           // expected-warning {{identifier 'export' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int friend;           // expected-warning {{identifier 'friend' conflicts with a C++ keyword}} \
+                           cxx-error {{'friend' must appear first in a non-function declaration}}
+  int mutable;          // expected-warning {{identifier 'mutable' conflicts with a C++ keyword}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int namespace;        // expected-warning {{identifier 'namespace' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int new;              // expected-warning {{identifier 'new' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int operator;         // expected-warning {{identifier 'operator' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a type}}
+  int private;          // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int protected;        // expected-warning {{identifier 'protected' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int public;           // expected-warning {{identifier 'public' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int reinterpret_cast; // expected-warning {{identifier 'reinterpret_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int static_cast;      // expected-warning {{identifier 'static_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int template;         // expected-warning {{identifier 'template' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int this;             // expected-warning {{identifier 'this' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int throw;            // expected-warning {{identifier 'throw' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int try;              // expected-warning {{identifier 'try' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int typename;         // expected-warning {{identifier 'typename' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a qualified name after 'typename'}}
+  int typeid;           // expected-warning {{identifier 'typeid' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int using;            // expected-warning {{identifier 'using' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int virtual;          // expected-warning {{identifier 'virtual' conflicts with a C++ keyword}} \
+                           cxx-error {{'virtual' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int wchar_t;          // expected-warning {{identifier 'wchar_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int char8_t;          // expected-warning {{identifier 'char8_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int char16_t;         // expected-warning {{identifier 'char16_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int char32_t;         // expected-warning {{identifier 'char32_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int noexcept;         // expected-warning {{identifier 'noexcept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int co_await;         // expected-warning {{identifier 'co_await' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int co_return;        // expected-warning {{identifier 'co_return' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int co_yield;         // expected-warning {{identifier 'co_yield' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int consteval;        // expected-warning {{identifier 'consteval' conflicts with a C++ keyword}} \
+                           cxx-error {{consteval can only be used in function declarations}}
+  int constinit;        // expected-warning {{identifier 'constinit' conflicts with a C++ keyword}} \
+                           cxx-error {{constinit can only be used in variable declarations}}
+  int concept;          // expected-warning {{identifier 'concept' conflicts with a C++ keyword}} \
+                           cxx-error {{concept declarations may only appear in global or namespace scope}}
+  int requires;         // expected-warning {{identifier 'requires' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}} \
+                           cxx-error {{trailing requires clause can only be used when declaring a function}} \
+                           cxx-error {{expected expression}}
+};
+
+// Smoke test that we catch a keyword used as an enumerator. If we diagnose
+// one, we'll diagnose them all.
+enum E {
+  throw, // expected-warning {{identifier 'throw' conflicts with a C++ keyword}} \
+            cxx-error {{expected identifier}}
+};
+
+// Smoke test that we catch a keyword used as a tag name.
+struct try { // expected-warning {{identifier 'try' conflicts with a C++ keyword}} \
+                cxx-error {{declaration of anonymous struct must be a definition}} \
+                cxx-warning {{declaration does not declare anything}}
+  int x;
+};
+
+// Smoke test that we catch keyword use in a function name.
+void this(void);        // expected-warning {{identifier 'this' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+
+// Smoke test that we catch keyword use in function parameters too.
+void func(int private); // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{invalid parameter name: 'private' is a keyword}}
+
+// These are conditionally a keyword in C++, so they're intentionally not being
+// diagnosed as a keyword.
+int module;
path: clang/test/Sema/c++-keyword-in-c.c
user: login: shafik
body: what about `override` and `final`?
created_at: 2025-04-24T19:39:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,213 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Widentifier-is-c++-keyword %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-compat %s
+// RUN: %clang_cc1 -fsyntax-only -verify=good %s
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx -x c++ -std=c++2c %s
+// good-no-diagnostics
+
+// 'try', 'this' and 'throw' are not tested as identifiers, but are instead
+// tested as other constructs (otherwise there would be redefinition errors in
+// C).
+int catch;              // expected-warning {{identifier 'catch' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int class;              // expected-warning {{identifier 'class' conflicts with a C++ keyword}} \
+                           cxx-error {{declaration of anonymous class must be a definition}} \
+                           cxx-warning {{declaration does not declare anything}}
+int const_cast;         // expected-warning {{identifier 'const_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int delete;             // expected-warning {{identifier 'delete' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int dynamic_cast;       // expected-warning {{identifier 'dynamic_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int explicit;           // expected-warning {{identifier 'explicit' conflicts with a C++ keyword}} \
+                           cxx-error {{'explicit' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+int export;             // expected-warning {{identifier 'export' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int friend;             // expected-warning {{identifier 'friend' conflicts with a C++ keyword}} \
+                           cxx-error {{'friend' used outside of class}} \
+                           cxx-warning {{declaration does not declare anything}}
+int mutable;            // expected-warning {{identifier 'mutable' conflicts with a C++ keyword}} \
+                           cxx-warning {{declaration does not declare anything}}
+int namespace;          // expected-warning {{identifier 'namespace' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int new;                // expected-warning {{identifier 'new' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int operator;           // expected-warning {{identifier 'operator' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a type}}
+int private;            // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int protected;          // expected-warning {{identifier 'protected' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int public;             // expected-warning {{identifier 'public' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int reinterpret_cast;   // expected-warning {{identifier 'reinterpret_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int static_cast;        // expected-warning {{identifier 'static_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int template;           // expected-warning {{identifier 'template' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int typename;           // expected-warning {{identifier 'typename' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a qualified name after 'typename'}}
+int typeid;             // expected-warning {{identifier 'typeid' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int using;              // expected-warning {{identifier 'using' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int virtual;            // expected-warning {{identifier 'virtual' conflicts with a C++ keyword}} \
+                           cxx-error {{'virtual' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+int wchar_t;            // expected-warning {{identifier 'wchar_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char8_t;            // expected-warning {{identifier 'char8_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char16_t;           // expected-warning {{identifier 'char16_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char32_t;           // expected-warning {{identifier 'char32_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int noexcept;           // expected-warning {{identifier 'noexcept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_await;           // expected-warning {{identifier 'co_await' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_return;          // expected-warning {{identifier 'co_return' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_yield;           // expected-warning {{identifier 'co_yield' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int consteval;          // expected-warning {{identifier 'consteval' conflicts with a C++ keyword}} \
+                           cxx-error {{consteval can only be used in function declarations}}
+int constinit;          // expected-warning {{identifier 'constinit' conflicts with a C++ keyword}} \
+                           cxx-error {{constinit can only be used in variable declarations}}
+int concept;            // expected-warning {{identifier 'concept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int requires;           // expected-warning {{identifier 'requires' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+
+// Now try the same thing, but as struct members.
+struct S {
+  int catch;            // expected-warning {{identifier 'catch' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int class;            // expected-warning {{identifier 'class' conflicts with a C++ keyword}} \
+                           cxx-error {{declaration of anonymous class must be a definition}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int const_cast;       // expected-warning {{identifier 'const_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int delete;           // expected-warning {{identifier 'delete' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int dynamic_cast;     // expected-warning {{identifier 'dynamic_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int explicit;         // expected-warning {{identifier 'explicit' conflicts with a C++ keyword}} \
+                           cxx-error {{'explicit' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int export;           // expected-warning {{identifier 'export' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int friend;           // expected-warning {{identifier 'friend' conflicts with a C++ keyword}} \
+                           cxx-error {{'friend' must appear first in a non-function declaration}}
+  int mutable;          // expected-warning {{identifier 'mutable' conflicts with a C++ keyword}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int namespace;        // expected-warning {{identifier 'namespace' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int new;              // expected-warning {{identifier 'new' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int operator;         // expected-warning {{identifier 'operator' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a type}}
+  int private;          // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int protected;        // expected-warning {{identifier 'protected' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int public;           // expected-warning {{identifier 'public' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int reinterpret_cast; // expected-warning {{identifier 'reinterpret_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int static_cast;      // expected-warning {{identifier 'static_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int template;         // expected-warning {{identifier 'template' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int this;             // expected-warning {{identifier 'this' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int throw;            // expected-warning {{identifier 'throw' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int try;              // expected-warning {{identifier 'try' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int typename;         // expected-warning {{identifier 'typename' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a qualified name after 'typename'}}
+  int typeid;           // expected-warning {{identifier 'typeid' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int using;            // expected-warning {{identifier 'using' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int virtual;          // expected-warning {{identifier 'virtual' conflicts with a C++ keyword}} \
+                           cxx-error {{'virtual' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int wchar_t;          // expected-warning {{identifier 'wchar_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int char8_t;          // expected-warning {{identifier 'char8_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int char16_t;         // expected-warning {{identifier 'char16_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int char32_t;         // expected-warning {{identifier 'char32_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int noexcept;         // expected-warning {{identifier 'noexcept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int co_await;         // expected-warning {{identifier 'co_await' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int co_return;        // expected-warning {{identifier 'co_return' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int co_yield;         // expected-warning {{identifier 'co_yield' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int consteval;        // expected-warning {{identifier 'consteval' conflicts with a C++ keyword}} \
+                           cxx-error {{consteval can only be used in function declarations}}
+  int constinit;        // expected-warning {{identifier 'constinit' conflicts with a C++ keyword}} \
+                           cxx-error {{constinit can only be used in variable declarations}}
+  int concept;          // expected-warning {{identifier 'concept' conflicts with a C++ keyword}} \
+                           cxx-error {{concept declarations may only appear in global or namespace scope}}
+  int requires;         // expected-warning {{identifier 'requires' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}} \
+                           cxx-error {{trailing requires clause can only be used when declaring a function}} \
+                           cxx-error {{expected expression}}
+};
+
+// Smoke test that we catch a keyword used as an enumerator. If we diagnose
+// one, we'll diagnose them all.
+enum E {
+  throw, // expected-warning {{identifier 'throw' conflicts with a C++ keyword}} \
+            cxx-error {{expected identifier}}
+};
+
+// Smoke test that we catch a keyword used as a tag name.
+struct try { // expected-warning {{identifier 'try' conflicts with a C++ keyword}} \
+                cxx-error {{declaration of anonymous struct must be a definition}} \
+                cxx-warning {{declaration does not declare anything}}
+  int x;
+};
+
+// Smoke test that we catch keyword use in a function name.
+void this(void);        // expected-warning {{identifier 'this' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+
+// Smoke test that we catch keyword use in function parameters too.
+void func(int private); // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{invalid parameter name: 'private' is a keyword}}
+
+// These are conditionally a keyword in C++, so they're intentionally not being
+// diagnosed as a keyword.
+int module;
path: clang/test/Sema/c++-keyword-in-c.c
user: login: AaronBallman
body: I'll add them as test cases; they're not intended to be diagnosed either.
created_at: 2025-04-25T11:38:07Z
author_association: COLLABORATOR
diff_hunk: @@ -10416,6 +10447,15 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
   // Finally, we know we have the right number of parameters, install them.
   NewFD->setParams(Params);
 
+  // If this declarator is a declaration and not a definition, its parameters
+  // will not be pushed onto a scope chain. That means we will not issue any
+  // reserved identifier warnings for the declaration, but we will for the
+  // definition. Handle those here.
+  if (!Params.empty() && !D.isFunctionDefinition()) {
path: clang/lib/Sema/SemaDecl.cpp
user: login: erichkeane
body: I suspect THIS is where your slowdown is going to come from, and I see no tests for this (that is, function parameters).

ALSO, `!Params.empty()` isnt' really necessary, the `for` loop will do that for you.
created_at: 2025-04-25T13:58:39Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,29 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static llvm::SmallPtrSet<IdentifierInfo *, 32> Keywords;
path: clang/lib/Sema/SemaDecl.cpp
user: login: erichkeane
body: note llvm::SmallPtrSet vs SmallSet is irrelevant here, the specialization for SmallSet on pointers is SmallPtrSet, but don't mind this being here, just interesting note :) 
created_at: 2025-04-25T14:08:09Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,29 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static llvm::SmallPtrSet<IdentifierInfo *, 32> Keywords;
+  if (Keywords.empty()) {
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords.insert(&S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords.insert(&S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+  }
+
+  return Keywords.contains(II);
path: clang/lib/Sema/SemaDecl.cpp
user: login: erichkeane
body: This function is interesting.  I like the approach.

The `insert` here is obviously pretty slow, but all of those are only happening 1x per TU and a 'small' number (which, btw, how accurate is the 32 here? we NEED to make sure, perhaps via assert, that we always fall in the 'small' here).

An alternative implementation idea here...

In IdentifierTable, 'start' the TU by creating all the CXXKeywords in 1 place, via 1 allocation.  Then add them to the IdentifierTable.  That then makes this lookup:

`return II >= IdentTable.FirstCXXKeyword && II <= IdentTable.LastCXXKeyword`

The result is this is effectively 'free' to check, at a slight startup time/memory cost(basically, time to do the ABOVE getIdentifierTable.get calls).

That said, the 'cost' of this warning, now that you have `isIgnored` checked, is pretty minor I presume? So perhaps not worth the effort.

WDYT?
created_at: 2025-04-25T14:16:40Z
author_association: COLLABORATOR
diff_hunk: @@ -10416,6 +10447,15 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
   // Finally, we know we have the right number of parameters, install them.
   NewFD->setParams(Params);
 
+  // If this declarator is a declaration and not a definition, its parameters
+  // will not be pushed onto a scope chain. That means we will not issue any
+  // reserved identifier warnings for the declaration, but we will for the
+  // definition. Handle those here.
+  if (!Params.empty() && !D.isFunctionDefinition()) {
path: clang/lib/Sema/SemaDecl.cpp
user: login: AaronBallman
body: They're tested:
```
// Smoke test that we catch keyword use in function parameters too.
void func(int private); // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
                           cxx-error {{invalid parameter name: 'private' is a keyword}}
```
around line 192
created_at: 2025-04-28T15:44:32Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,29 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static llvm::SmallPtrSet<IdentifierInfo *, 32> Keywords;
+  if (Keywords.empty()) {
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords.insert(&S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords.insert(&S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+  }
+
+  return Keywords.contains(II);
path: clang/lib/Sema/SemaDecl.cpp
user: login: AaronBallman
body: I'd really rather not try to muck about with the layout of things in the identifier table, that's actually some pretty complicated logic because of "interesting identifiers" and Objective-C. I think the current perf is reasonable given that the checking only happens if the (off by default) diagnostic is enabled. WDYT?
created_at: 2025-04-28T15:45:51Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,29 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static llvm::SmallPtrSet<IdentifierInfo *, 32> Keywords;
+  if (Keywords.empty()) {
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords.insert(&S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords.insert(&S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+  }
+
+  return Keywords.contains(II);
path: clang/lib/Sema/SemaDecl.cpp
user: login: erichkeane
body: Thats sensible.  Perhaps an idea to hold onto in the future, but since this is only 'when diag enabled', its something we can think further about when/if this ever becomes a concern
created_at: 2025-04-28T15:47:24Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,29 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static llvm::SmallPtrSet<IdentifierInfo *, 32> Keywords;
+  if (Keywords.empty()) {
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords.insert(&S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords.insert(&S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+  }
+
+  return Keywords.contains(II);
path: clang/lib/Sema/SemaDecl.cpp
user: login: AaronBallman
body: 32 is wrong though, we have 34 keywords. Good catch on that. I'll bump to 48 to give wiggle room and pick something power-of-two-ish. WDYT?
created_at: 2025-04-28T15:47:45Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,29 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static llvm::SmallPtrSet<IdentifierInfo *, 32> Keywords;
+  if (Keywords.empty()) {
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords.insert(&S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords.insert(&S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+  }
+
+  return Keywords.contains(II);
path: clang/lib/Sema/SemaDecl.cpp
user: login: erichkeane
body: Oof!  48 would be fine.

Else, I'd love a static-assert (or, if you're doing the work for that, just making this the 'right' number so it never actually allocates and we don't have 'extra'), but can live with '48 should be big enough for anybody!'
created_at: 2025-04-28T15:51:40Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,43 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static std::array<uintptr_t, countCPlusPlusKeywords()> Keywords = {};
+  if (!Keywords[0]) {
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+    assert(Idx == Keywords.size() && "expected to fill every member!");
+    llvm::sort(Keywords);
path: clang/lib/Sema/SemaDecl.cpp
user: login: efriedma-quic
body: This isn't thread-safe.

Maybe make the "Keywords" array a member of Sema?
created_at: 2025-04-28T17:58:20Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,43 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static std::array<uintptr_t, countCPlusPlusKeywords()> Keywords = {};
+  if (!Keywords[0]) {
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+    assert(Idx == Keywords.size() && "expected to fill every member!");
+    llvm::sort(Keywords);
path: clang/lib/Sema/SemaDecl.cpp
user: login: AaronBallman
body: Wait... Clang is generally supposed to be thread safe? I thought only very specific interfaces in Clang were thread-safe as a purely best-effort mechanism because we don't test anything in a multi-threaded way? (Otherwise, what guards things like adding new nodes to the AST?)
created_at: 2025-04-28T18:12:54Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,43 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static std::array<uintptr_t, countCPlusPlusKeywords()> Keywords = {};
+  if (!Keywords[0]) {
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+    assert(Idx == Keywords.size() && "expected to fill every member!");
+    llvm::sort(Keywords);
path: clang/lib/Sema/SemaDecl.cpp
user: login: AaronBallman
body: Oh wait, this isn't really about thread safety in terms of race conditions. It's about being able to initialize clang as a library from multiple threads?
created_at: 2025-04-28T18:17:45Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,43 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static std::array<uintptr_t, countCPlusPlusKeywords()> Keywords = {};
+  if (!Keywords[0]) {
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+    assert(Idx == Keywords.size() && "expected to fill every member!");
+    llvm::sort(Keywords);
path: clang/lib/Sema/SemaDecl.cpp
user: login: AaronBallman
body: Rather than hoist this all the way up into `Sema` (I'd like to avoid making that class larger if I can help it), would it make sense to use a `ManagedStatic` instead?
created_at: 2025-04-28T18:22:45Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,43 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static std::array<uintptr_t, countCPlusPlusKeywords()> Keywords = {};
+  if (!Keywords[0]) {
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+    assert(Idx == Keywords.size() && "expected to fill every member!");
+    llvm::sort(Keywords);
path: clang/lib/Sema/SemaDecl.cpp
user: login: efriedma-quic
body: Thread safety in the sense that we want to allow running multiple instances of the parser in the same process, yes.

Are IdentifierInfo pointers equal across different instances of the parser?  If they are... you can probably do something with a static constructor.  ManagedStatic is probably overkill here; just use a lambda in the initializer.
created_at: 2025-04-28T19:19:45Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,43 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static std::array<uintptr_t, countCPlusPlusKeywords()> Keywords = {};
+  if (!Keywords[0]) {
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+    assert(Idx == Keywords.size() && "expected to fill every member!");
+    llvm::sort(Keywords);
path: clang/lib/Sema/SemaDecl.cpp
user: login: AaronBallman
body: Okay, easy enough, thanks!
created_at: 2025-04-28T21:03:51Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,43 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static std::array<uintptr_t, countCPlusPlusKeywords()> Keywords = {};
+  if (!Keywords[0]) {
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Keywords[Idx++] = reinterpret_cast<uint64_t>(                                \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+    assert(Idx == Keywords.size() && "expected to fill every member!");
+    llvm::sort(Keywords);
path: clang/lib/Sema/SemaDecl.cpp
user: login: efriedma-quic
body: Are you sure IdentifierInfo pointers are actually unique across the entire process?  I've tried to find the code, and as far as I can tell IndentifierInfos are dynamically allocated by each preprocessor instance.
created_at: 2025-04-28T21:31:02Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,44 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static auto Keywords = [&S] {
+    std::array<uintptr_t, countCPlusPlusKeywords()> Ret;
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Ret[Idx++] = reinterpret_cast<uint64_t>(                                     \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
path: clang/lib/Sema/SemaDecl.cpp
user: login: zygoloid
body: This will break for at least all users of Clang as a library -- we can't store pointers to `IdentifierInfo`s in a static local table, because we can have multiple preprocessors (and hence identifier tables and `IdentifierInfo`s for a single identifier) in a process.
created_at: 2025-04-28T22:02:40Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,44 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static auto Keywords = [&S] {
+    std::array<uintptr_t, countCPlusPlusKeywords()> Ret;
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Ret[Idx++] = reinterpret_cast<uint64_t>(                                     \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Ret[Idx++] = reinterpret_cast<uint64_t>(                                     \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+    assert(Idx == Ret.size() && "expected to fill every member!");
+    llvm::sort(Ret);
+    return Ret;
+  }();
+
+  return llvm::binary_search(Keywords, reinterpret_cast<uintptr_t>(II));
path: clang/lib/Sema/SemaDecl.cpp
user: login: zygoloid
body: A binary search for each identifier still seems excessively expensive. Can we instead just store a flag on the `IdentifierInfo` that we set up when building the initial table? That's what we do in other similar cases (eg, `IsExtension` / `IsFutureCompatKeyword`); it doesn't seem like we need to invent something different here.
created_at: 2025-04-28T22:04:02Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,44 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static auto Keywords = [&S] {
+    std::array<uintptr_t, countCPlusPlusKeywords()> Ret;
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Ret[Idx++] = reinterpret_cast<uint64_t>(                                     \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Ret[Idx++] = reinterpret_cast<uint64_t>(                                     \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+    assert(Idx == Ret.size() && "expected to fill every member!");
+    llvm::sort(Ret);
+    return Ret;
+  }();
+
+  return llvm::binary_search(Keywords, reinterpret_cast<uintptr_t>(II));
path: clang/lib/Sema/SemaDecl.cpp
user: login: erichkeane
body: Based on our count, this leaves us with ~6 compares at worst, and only when the flag was enabled.  According to benchmarking, it is sub-0.5% perf-hit when the warning is enabled, so expense didn't really show up as overly measurable I think?

As far as the flag on IdentifierInfo... I'm amazed to find out we have bits left to do so!  Apparently we have 22 bits left according to the comment/my count confirming it.

I'd considered suggesting it at one point but apparently miscounted us having bits left over (an off-by-10 count is.... something), and didn't want every IdentifierInfo to pay the cost.  

I think this is going to be the best way forward here.  A slight startup cost, to calculate these, but makes this a way easier patch to swallow.
created_at: 2025-04-28T22:28:43Z
author_association: COLLABORATOR
diff_hunk: @@ -6107,6 +6109,44 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+constexpr unsigned countCPlusPlusKeywords() {
+  unsigned Ret = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS) ++Ret;
+#define CXX_KEYWORD_OPERATOR(NAME, TOK) ++Ret;
+#include "clang/Basic/TokenKinds.def"
+  return Ret;
+}
+
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo *II) {
+  if (!II)
+    return false;
+
+  // Build a static map of identifiers for all of the keywords in C++ that are
+  // not keywords in C. This allows us to do pointer comparisons instead of
+  // string comparisons when deciding whether the given identifier is a keyword
+  // or not. Note, this treats all keywords as being enabled, regardless of the
+  // setting of other language options. It intentionally disables the modules
+  // keywords because those are conditional keywords, so may be safe to use.
+  static auto Keywords = [&S] {
+    std::array<uintptr_t, countCPlusPlusKeywords()> Ret;
+    unsigned Idx = 0;
+#define MODULES_KEYWORD(NAME)
+#define KEYWORD(NAME, FLAGS)                                                   \
+  Ret[Idx++] = reinterpret_cast<uint64_t>(                                     \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#define CXX_KEYWORD_OPERATOR(NAME, TOK)                                        \
+  Ret[Idx++] = reinterpret_cast<uint64_t>(                                     \
+      &S.getPreprocessor().getIdentifierTable().get(#NAME));
+#include "clang/Basic/TokenKinds.def"
+    assert(Idx == Ret.size() && "expected to fill every member!");
+    llvm::sort(Ret);
+    return Ret;
+  }();
+
+  return llvm::binary_search(Keywords, reinterpret_cast<uintptr_t>(II));
path: clang/lib/Sema/SemaDecl.cpp
user: login: AaronBallman
body: Good suggestion, I've gone ahead with those changes.
created_at: 2025-04-29T11:54:18Z
author_association: COLLABORATOR
diff_hunk: @@ -258,8 +283,13 @@ static void AddKeyword(StringRef Keyword,
                        const LangOptions &LangOpts, IdentifierTable &Table) {
   KeywordStatus AddResult = getKeywordStatus(LangOpts, Flags);
 
-  // Don't add this keyword if disabled in this language.
-  if (AddResult == KS_Disabled) return;
+  // Don't add this keyword if disabled in this language and isn't otherwise
+  // special.
+  if (AddResult == KS_Disabled) {
path: clang/lib/Basic/IdentifierTable.cpp
user: login: zygoloid
body: Optional:

It'd be more consistent with the existing code to add an extra KeywordStatus value for this case, analogous to KS_Future. To do that, maybe we'd need to also replace the special macro for C++ operator keywords with a normal flag so that it can be taken into account when computing the KeywordStatus too.

I don't think it's necessary to be consistent here, though; we can always refactor later.
created_at: 2025-04-29T16:47:11Z
author_association: COLLABORATOR
diff_hunk: @@ -6118,6 +6120,10 @@ void Sema::warnOnReservedIdentifier(const NamedDecl *D) {
     Diag(D->getLocation(), diag::warn_reserved_extern_symbol)
         << D << static_cast<int>(Status);
   }
+  // Diagnose use of C++ keywords in C as being incompatible with C++.
+  if (const IdentifierInfo *II = D->getIdentifier();
path: clang/lib/Sema/SemaDecl.cpp
user: login: zygoloid
body: Is there a reason to warn on this here rather than in the same place we warn on "future keywords"? (When the preprocessor produces the final identifier token.) I would worry that this check would miss some cases. (Parameter names appear to need special handling, and they're probably not going to remain the only case even if they currently are.)
created_at: 2025-04-29T16:50:24Z
author_association: COLLABORATOR
diff_hunk: @@ -6118,6 +6120,10 @@ void Sema::warnOnReservedIdentifier(const NamedDecl *D) {
     Diag(D->getLocation(), diag::warn_reserved_extern_symbol)
         << D << static_cast<int>(Status);
   }
+  // Diagnose use of C++ keywords in C as being incompatible with C++.
+  if (const IdentifierInfo *II = D->getIdentifier();
path: clang/lib/Sema/SemaDecl.cpp
user: login: AaronBallman
body: I was thinking this was similar to reserved identifiers, but I think your suggestion is cleaner. I'll work on an update for that
created_at: 2025-04-29T17:09:55Z
author_association: COLLABORATOR
diff_hunk: @@ -250,6 +250,32 @@ static KeywordStatus getKeywordStatus(const LangOptions &LangOpts,
   return CurStatus;
 }
 
+static bool IsKeywordInCpp(unsigned Flags) {
+  while (Flags != 0) {
+    unsigned CurFlag = Flags & ~(Flags - 1);
+    Flags = Flags & ~CurFlag;
+    switch (static_cast<TokenKey>(CurFlag)) {
+    case KEYCXX:
+    case KEYCXX11:
+    case KEYCXX20:
+    case BOOLSUPPORT:
+    case WCHARSUPPORT:
+    case CHAR8SUPPORT:
+      return true;
+    default:
+      break; // Go to the next flag, try again.
+    }
+  }
+  return false;
path: clang/lib/Basic/IdentifierTable.cpp
user: login: nikic
body: I'm a bit confused by this function. Why does this go through all bits in a loop instead of directly checking them? I'd have expected this whole function to be something like `return Flags & (KEYALLCXX|BOOLSUPPORT|WCHARSUPPORT|CHAR8SUPPORT);`
created_at: 2025-05-02T13:26:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -250,6 +250,32 @@ static KeywordStatus getKeywordStatus(const LangOptions &LangOpts,
   return CurStatus;
 }
 
+static bool IsKeywordInCpp(unsigned Flags) {
+  while (Flags != 0) {
+    unsigned CurFlag = Flags & ~(Flags - 1);
+    Flags = Flags & ~CurFlag;
+    switch (static_cast<TokenKey>(CurFlag)) {
+    case KEYCXX:
+    case KEYCXX11:
+    case KEYCXX20:
+    case BOOLSUPPORT:
+    case WCHARSUPPORT:
+    case CHAR8SUPPORT:
+      return true;
+    default:
+      break; // Go to the next flag, try again.
+    }
+  }
+  return false;
path: clang/lib/Basic/IdentifierTable.cpp
user: login: AaronBallman
body: Code was lifted from https://github.com/llvm/llvm-project/blob/a4ceac7e3e04c32cb3e334eb89b54d8e99a298f8/clang/lib/Basic/IdentifierTable.cpp#L240 but yeah, this seems to be something I can simplify
created_at: 2025-05-02T13:31:29Z
author_association: COLLABORATOR
diff_hunk: @@ -250,6 +250,32 @@ static KeywordStatus getKeywordStatus(const LangOptions &LangOpts,
   return CurStatus;
 }
 
+static bool IsKeywordInCpp(unsigned Flags) {
+  while (Flags != 0) {
+    unsigned CurFlag = Flags & ~(Flags - 1);
+    Flags = Flags & ~CurFlag;
+    switch (static_cast<TokenKey>(CurFlag)) {
+    case KEYCXX:
+    case KEYCXX11:
+    case KEYCXX20:
+    case BOOLSUPPORT:
+    case WCHARSUPPORT:
+    case CHAR8SUPPORT:
+      return true;
+    default:
+      break; // Go to the next flag, try again.
+    }
+  }
+  return false;
path: clang/lib/Basic/IdentifierTable.cpp
user: login: AaronBallman
body: I updated in 0b2ab11321d2b400987e9ade28a221dff67aea7d thank you for the catch!
created_at: 2025-05-02T13:40:10Z
author_association: COLLABORATOR

PR comments:
user: login: llvmbot
created_at: 2025-04-24T18:56:47Z
body: 
<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Aaron Ballman (AaronBallman)

<details>
<summary>Changes</summary>

This adds a new diagnostic group, -Widentifier-is-c++-keyword, which is off by default and grouped under -Wc++-compat. The diagnostic catches use of C++ keywords in C code.

This change additionally fixes an issue with -Wreserved-identifier not diagnosing use of reserved identifiers in function parameter lists in a function declaration which is not a definition.

Partially fixes https://github.com/llvm/llvm-project/issues/21898

---

Patch is 25.28 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/137234.diff


7 Files Affected:

- (modified) clang/docs/ReleaseNotes.rst (+5) 
- (modified) clang/include/clang/Basic/DiagnosticGroups.td (+2-1) 
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+3) 
- (modified) clang/include/clang/Basic/IdentifierTable.h (+8-3) 
- (modified) clang/lib/Basic/IdentifierTable.cpp (+27) 
- (modified) clang/lib/Sema/SemaDecl.cpp (+31) 
- (added) clang/test/Sema/c++-keyword-in-c.c (+213) 


``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d1f24fb23d44d..453bdc9870bdb 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -143,6 +143,9 @@ C Language Changes
 - Added ``-Wimplicit-void-ptr-cast``, grouped under ``-Wc++-compat``, which
   diagnoses implicit conversion from ``void *`` to another pointer type as
   being incompatible with C++. (#GH17792)
+- Added ``-Widentifier-is-c++-keyword``, grouped under ``-Wc++-compat``, which
+  diagnoses when a C++ keyword is used as an identifier in C. Partially
+  addresses #GH21898.
 
 C2y Feature Support
 ^^^^^^^^^^^^^^^^^^^
@@ -416,6 +419,8 @@ Improvements to Clang's diagnostics
 - ``-Winitializer-overrides`` and ``-Wreorder-init-list`` are now grouped under
   the ``-Wc99-designator`` diagnostic group, as they also are about the
   behavior of the C99 feature as it was introduced into C++20. Fixes #GH47037
+- ``-Wreserved-identifier`` now fires on reserved parameter names in a function
+  declaration which is not a definition.
 
 Improvements to Clang's time-trace
 ----------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 6441b8049ed8d..84e590a857398 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -155,8 +155,9 @@ def C99Compat : DiagGroup<"c99-compat">;
 def C23Compat : DiagGroup<"c23-compat">;
 def : DiagGroup<"c2x-compat", [C23Compat]>;
 
+def CppKeywordInC : DiagGroup<"identifier-is-c++-keyword">;
 def ImplicitVoidPtrCast : DiagGroup<"implicit-void-ptr-cast">;
-def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast]>;
+def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast, CppKeywordInC]>;
 def ExternCCompat : DiagGroup<"extern-c-compat">;
 def KeywordCompat : DiagGroup<"keyword-compat">;
 def GNUCaseRange : DiagGroup<"gnu-case-range">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8ff170520aafe..50a960313349a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -496,6 +496,9 @@ def warn_unused_lambda_capture: Warning<"lambda capture %0 is not "
   "%select{used|required to be captured for this use}1">,
   InGroup<UnusedLambdaCapture>, DefaultIgnore;
 
+def warn_identifier_is_cpp_keyword : Warning<
+  "identifier %0 conflicts with a C++ keyword">,
+  InGroup<CppKeywordInC>, DefaultIgnore;
 def warn_reserved_extern_symbol: Warning<
   "identifier %0 is reserved because %select{"
   "<ERROR>|" // ReservedIdentifierStatus::NotReserved
diff --git a/clang/include/clang/Basic/IdentifierTable.h b/clang/include/clang/Basic/IdentifierTable.h
index 1275b056227b5..05c989c1b07d9 100644
--- a/clang/include/clang/Basic/IdentifierTable.h
+++ b/clang/include/clang/Basic/IdentifierTable.h
@@ -444,13 +444,18 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
   }
   bool isCPlusPlusOperatorKeyword() const { return IsCPPOperatorKeyword; }
 
-  /// Return true if this token is a keyword in the specified language.
+  /// Return true if this identifier uses a keyword token and is a keyword in
+  /// the specified language.
   bool isKeyword(const LangOptions &LangOpts) const;
 
-  /// Return true if this token is a C++ keyword in the specified
-  /// language.
+  /// Return true if this identifier uses a keyword token and is a C++ keyword
+  /// in the specified language.
   bool isCPlusPlusKeyword(const LangOptions &LangOpts) const;
 
+  /// Returns true if the name of this identifier matches a keyword given the
+  /// specified language options.
+  bool isNameKeyword(const LangOptions &LangOpts) const;
+
   /// Get and set FETokenInfo. The language front-end is allowed to associate
   /// arbitrary metadata with this token.
   void *getFETokenInfo() const { return FETokenInfo; }
diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp
index 16151c94464f9..412f0af40861a 100644
--- a/clang/lib/Basic/IdentifierTable.cpp
+++ b/clang/lib/Basic/IdentifierTable.cpp
@@ -343,6 +343,14 @@ static KeywordStatus getTokenKwStatus(const LangOptions &LangOpts,
   }
 }
 
+static KeywordStatus getNameKwStatus(const LangOptions &LangOpts,
+                                     StringRef Name) {
+  return llvm::StringSwitch<KeywordStatus>(Name)
+#define KEYWORD(NAME, FLAGS) .Case(#NAME, getKeywordStatus(LangOpts, FLAGS))
+#include "clang/Basic/TokenKinds.def"
+      .Default(KS_Disabled);
+}
+
 /// Returns true if the identifier represents a keyword in the
 /// specified language.
 bool IdentifierInfo::isKeyword(const LangOptions &LangOpts) const {
@@ -355,6 +363,25 @@ bool IdentifierInfo::isKeyword(const LangOptions &LangOpts) const {
   }
 }
 
+bool IdentifierInfo::isNameKeyword(const LangOptions &LangOpts) const {
+  // This differs from IdentifierInfo::isCPlusPlusKeyword(). That function
+  // tests if the identifier is a keyword token in C++ mode and then isn't a
+  // keyword token in C modes. In our case, if it was a keyword, we wouldn't
+  // have gotten the identifier for it anyway, so that function will always
+  // return false for us. Instead, check against the token definitions directly.
+  //
+  // FIXME: It would be nice to handle things like char8_t and wchar_t here as
+  // well, but those require looking at the currently selected language options
+  // which would make this interface confusing with isCPlusPlusKeyword.
+  switch (getNameKwStatus(LangOpts, getName())) {
+  case KS_Enabled:
+  case KS_Extension:
+    return true;
+  default:
+    return false;
+  }
+}
+
 /// Returns true if the identifier represents a C++ keyword in the
 /// specified language.
 bool IdentifierInfo::isCPlusPlusKeyword(const LangOptions &LangOpts) const {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d28a2107d58a9..74432ea34bd57 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -61,6 +61,7 @@
 #include "llvm/ADT/STLForwardCompat.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringSwitch.h"
 #include "llvm/Support/SaveAndRestore.h"
 #include "llvm/TargetParser/Triple.h"
 #include <algorithm>
@@ -6107,6 +6108,23 @@ static bool isFromSystemHeader(SourceManager &SM, const Decl *D) {
          SM.isInSystemMacro(D->getLocation());
 }
 
+static bool isKeywordInCPlusPlus(const Sema &S, const IdentifierInfo* II) {
+  if (!II)
+    return false;
+
+  // Clear all the C language options, set all the C++ language options, but
+  // otherwise leave all the defaults alone. This isn't ideal because some
+  // feature flags are language-specific, but this is a reasonable
+  // approximation.
+  LangOptions LO = S.getLangOpts();
+  LO.CPlusPlus = LO.CPlusPlus11 = LO.CPlusPlus14 = LO.CPlusPlus17 =
+      LO.CPlusPlus20 = LO.CPlusPlus23 = LO.CPlusPlus26 = 1;
+  LO.C99 = LO.C11 = LO.C17 = LO.C23 = LO.C2y = 0;
+  LO.Char8 = 1; // Presume this is always a keyword in C++.
+  LO.WChar = 1; // Presume this is always a keyword in C++.
+  return II->isNameKeyword(LO);
+}
+
 void Sema::warnOnReservedIdentifier(const NamedDecl *D) {
   // Avoid warning twice on the same identifier, and don't warn on redeclaration
   // of system decl.
@@ -6118,6 +6136,10 @@ void Sema::warnOnReservedIdentifier(const NamedDecl *D) {
     Diag(D->getLocation(), diag::warn_reserved_extern_symbol)
         << D << static_cast<int>(Status);
   }
+  // Diagnose use of C++ keywords in C as being incompatible with C++.
+  if (!getLangOpts().CPlusPlus &&
+      isKeywordInCPlusPlus(*this, D->getIdentifier()))
+    Diag(D->getLocation(), diag::warn_identifier_is_cpp_keyword) << D;
 }
 
 Decl *Sema::ActOnDeclarator(Scope *S, Declarator &D) {
@@ -10416,6 +10438,15 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
   // Finally, we know we have the right number of parameters, install them.
   NewFD->setParams(Params);
 
+  // If this declarator is a declaration and not a definition, its parameters
+  // will not be pushed onto a scope chain. That means we will not issue any
+  // reserved identifier warnings for the declaration, but we will for the
+  // definition. Handle those here.
+  if (!Params.empty() && !D.isFunctionDefinition()) {
+    for (const ParmVarDecl *PVD : Params)
+      warnOnReservedIdentifier(PVD);
+  }
+
   if (D.getDeclSpec().isNoreturnSpecified())
     NewFD->addAttr(
         C11NoReturnAttr::Create(Context, D.getDeclSpec().getNoreturnSpecLoc()));
diff --git a/clang/test/Sema/c++-keyword-in-c.c b/clang/test/Sema/c++-keyword-in-c.c
new file mode 100644
index 0000000000000..364f115ddcb44
--- /dev/null
+++ b/clang/test/Sema/c++-keyword-in-c.c
@@ -0,0 +1,213 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -Widentifier-is-c++-keyword %s
+// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-compat %s
+// RUN: %clang_cc1 -fsyntax-only -verify=good %s
+// RUN: %clang_cc1 -fsyntax-only -verify=cxx -x c++ -std=c++2c %s
+// good-no-diagnostics
+
+// 'try', 'this' and 'throw' are not tested as identifiers, but are instead
+// tested as other constructs (otherwise there would be redefinition errors in
+// C).
+int catch;              // expected-warning {{identifier 'catch' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int class;              // expected-warning {{identifier 'class' conflicts with a C++ keyword}} \
+                           cxx-error {{declaration of anonymous class must be a definition}} \
+                           cxx-warning {{declaration does not declare anything}}
+int const_cast;         // expected-warning {{identifier 'const_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int delete;             // expected-warning {{identifier 'delete' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int dynamic_cast;       // expected-warning {{identifier 'dynamic_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int explicit;           // expected-warning {{identifier 'explicit' conflicts with a C++ keyword}} \
+                           cxx-error {{'explicit' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+int export;             // expected-warning {{identifier 'export' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int friend;             // expected-warning {{identifier 'friend' conflicts with a C++ keyword}} \
+                           cxx-error {{'friend' used outside of class}} \
+                           cxx-warning {{declaration does not declare anything}}
+int mutable;            // expected-warning {{identifier 'mutable' conflicts with a C++ keyword}} \
+                           cxx-warning {{declaration does not declare anything}}
+int namespace;          // expected-warning {{identifier 'namespace' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int new;                // expected-warning {{identifier 'new' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int operator;           // expected-warning {{identifier 'operator' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a type}}
+int private;            // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int protected;          // expected-warning {{identifier 'protected' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int public;             // expected-warning {{identifier 'public' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int reinterpret_cast;   // expected-warning {{identifier 'reinterpret_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int static_cast;        // expected-warning {{identifier 'static_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int template;           // expected-warning {{identifier 'template' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int typename;           // expected-warning {{identifier 'typename' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a qualified name after 'typename'}}
+int typeid;             // expected-warning {{identifier 'typeid' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int using;              // expected-warning {{identifier 'using' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int virtual;            // expected-warning {{identifier 'virtual' conflicts with a C++ keyword}} \
+                           cxx-error {{'virtual' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+int wchar_t;            // expected-warning {{identifier 'wchar_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char8_t;            // expected-warning {{identifier 'char8_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char16_t;           // expected-warning {{identifier 'char16_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int char32_t;           // expected-warning {{identifier 'char32_t' conflicts with a C++ keyword}} \
+                           cxx-error {{cannot combine with previous 'int' declaration specifier}} \
+                           cxx-warning {{declaration does not declare anything}}
+int noexcept;           // expected-warning {{identifier 'noexcept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_await;           // expected-warning {{identifier 'co_await' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_return;          // expected-warning {{identifier 'co_return' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int co_yield;           // expected-warning {{identifier 'co_yield' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int consteval;          // expected-warning {{identifier 'consteval' conflicts with a C++ keyword}} \
+                           cxx-error {{consteval can only be used in function declarations}}
+int constinit;          // expected-warning {{identifier 'constinit' conflicts with a C++ keyword}} \
+                           cxx-error {{constinit can only be used in variable declarations}}
+int concept;            // expected-warning {{identifier 'concept' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+int requires;           // expected-warning {{identifier 'requires' conflicts with a C++ keyword}} \
+                           cxx-error {{expected unqualified-id}}
+
+// Now try the same thing, but as struct members.
+struct S {
+  int catch;            // expected-warning {{identifier 'catch' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int class;            // expected-warning {{identifier 'class' conflicts with a C++ keyword}} \
+                           cxx-error {{declaration of anonymous class must be a definition}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int const_cast;       // expected-warning {{identifier 'const_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int delete;           // expected-warning {{identifier 'delete' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int dynamic_cast;     // expected-warning {{identifier 'dynamic_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int explicit;         // expected-warning {{identifier 'explicit' conflicts with a C++ keyword}} \
+                           cxx-error {{'explicit' can only appear on non-static member functions}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int export;           // expected-warning {{identifier 'export' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int friend;           // expected-warning {{identifier 'friend' conflicts with a C++ keyword}} \
+                           cxx-error {{'friend' must appear first in a non-function declaration}}
+  int mutable;          // expected-warning {{identifier 'mutable' conflicts with a C++ keyword}} \
+                           cxx-warning {{declaration does not declare anything}}
+  int namespace;        // expected-warning {{identifier 'namespace' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int new;              // expected-warning {{identifier 'new' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int operator;         // expected-warning {{identifier 'operator' conflicts with a C++ keyword}} \
+                           cxx-error {{expected a type}}
+  int private;          // expected-warning {{identifier 'private' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int protected;        // expected-warning {{identifier 'protected' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int public;           // expected-warning {{identifier 'public' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int reinterpret_cast; // expected-warning {{identifier 'reinterpret_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int static_cast;      // expected-warning {{identifier 'static_cast' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int template;         // expected-warning {{identifier 'template' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int this;             // expected-warning {{identifier 'this' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int throw;            // expected-warning {{identifier 'throw' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int try;              // expected-warning {{identifier 'try' conflicts with a C++ keyword}} \
+                           cxx-error {{expected member name or ';' after declaration specifiers}}
+  int typename;         // expected-warning {{identifier 'typename' conflicts with a C++ keyword}} \
+                           cxx-error {{exp...
[truncated]

``````````

</details>

author_association: MEMBER
user: login: AaronBallman
created_at: 2025-04-24T18:57:43Z
body: Note, I'm running this one through llvm-compile-time-tracker because of the lookup happening on every identifier. I'm not convinced that the "is this identifier a keyword in other language modes" code has good performance and wanted to double-check.
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-04-24T18:59:18Z
body: <!--LLVM CODE FORMAT COMMENT: {clang-format}-->

:white_check_mark: With the latest revision this PR passed the C/C++ code formatter.
author_association: NONE
user: login: philnik777
created_at: 2025-04-24T19:00:16Z
body: Maybe `-Wkeyword-in-c++` or `-Wc++-keyword` would be a more concise name for the group?
author_association: CONTRIBUTOR
user: login: AaronBallman
created_at: 2025-04-24T19:01:46Z
body: > Maybe `-Wkeyword-in-c++` or `-Wc++-keyword` would be a more concise name for the group?

I'm not tied to the name I picked, so either of these is fine by me. GCC doesn't split this into its own warning group, so we've got some latitude.

Any strong preferences?
author_association: COLLABORATOR
user: login: philnik777
created_at: 2025-04-24T19:05:06Z
body: > > Maybe `-Wkeyword-in-c++` or `-Wc++-keyword` would be a more concise name for the group?
> 
> I'm not tied to the name I picked, so either of these is fine by me. GCC doesn't split this into its own warning group, so we've got some latitude.
> 
> Any strong preferences?

FWIW I feel like the current one feels quite artificial. I think `-Wc++-keyword` is kinda nice, since it feels very similar to `-Wc++-compat`, but I don't have strong preferences.

author_association: CONTRIBUTOR
user: login: Sirraide
created_at: 2025-04-24T22:56:52Z
body: > I think `-Wc++-keyword` is kinda nice

+1
author_association: MEMBER
user: login: AaronBallman
created_at: 2025-04-25T11:28:46Z
body: Alas, I think this PR may be a non-starter due to performance: https://llvm-compile-time-tracker.com/?config=Overview&stat=wall-time&remote=AaronBallman:
```
C 		56a3f3cd28 	8.04s (+2.96%) 	8.23s (+3.32%) 	11.87s (+1.92%) 	2.83s (+10.21%) 	8.50s (+2.99%) 	3.22s (+7.82%) 	7.12s (+1.63%) 	2.43s (+5.91%) 	580.64s (+0.44%)
```
+10% compile time overhead is painful. I believe the issue is with needing to look at token *names* (doing a string compare in a loop across a pile of tokens on every identifier declares). But the token kind we have is `identifier` and not one of the keywords, so we can't do the more trivial integer comparisons. Even if we could, I suspect this would have a performance hit just due to the number of identifiers.

I'm going to try checking whether the diagnostic is enabled before doing the comparisons to see if that improves things. The diagnostic is off by default and only enabled under `-Wc++-compat`, so some amount of performance hit is reasonable given the need to avoid C++ keywords in C code.
author_association: COLLABORATOR
user: login: AaronBallman
created_at: 2025-04-25T11:38:21Z
body: > What about `and`, `or`, and friends (which I think are only keywords in C if you include a certain header irrc).

Good call, I'll add those.
author_association: COLLABORATOR
user: login: Sirraide
created_at: 2025-04-25T11:42:53Z
body: > But the token kind we have is `identifier` and not one of the keywords, so we can't do the more trivial integer comparisons.

Can we precompute a list/map/whatever of all identifiers that are keywords in C++ but not in C and then do pointer comparisons on the `IdentifierInfo*` maybe? At least then we wouldn’t have to do actual string comparisons; that said, I’m not sure that’s going to be that much better...
author_association: MEMBER
user: login: AaronBallman
created_at: 2025-04-25T11:54:14Z
body: > > But the token kind we have is `identifier` and not one of the keywords, so we can't do the more trivial integer comparisons.
> 
> Can we precompute a list/map/whatever of all identifiers that are keywords in C++ but not in C and then do pointer comparisons on the `IdentifierInfo*` maybe? At least then we wouldn’t have to do actual string comparisons; that said, I’m not sure that’s going to be that much better...

That's a pretty good idea, let me try that. I'll back out the changes that disable checking when the diagnostic isn't enabled so we get a better comparison.
author_association: COLLABORATOR
user: login: AaronBallman
created_at: 2025-04-25T13:53:00Z
body: > > > But the token kind we have is `identifier` and not one of the keywords, so we can't do the more trivial integer comparisons.
> > 
> > 
> > Can we precompute a list/map/whatever of all identifiers that are keywords in C++ but not in C and then do pointer comparisons on the `IdentifierInfo*` maybe? At least then we wouldn’t have to do actual string comparisons; that said, I’m not sure that’s going to be that much better...
> 
> That's a pretty good idea, let me try that. I'll back out the changes that disable checking when the diagnostic isn't enabled so we get a better comparison.

That idea clawed back most of the performance:
```
Initial slow-down: C 		56a3f3cd28 	8.04s (+2.96%) 	8.23s (+3.32%) 	11.87s (+1.92%) 	2.83s (+10.21%) 	8.50s (+2.99%) 	3.22s (+7.82%) 	7.12s (+1.63%) 	2.43s (+5.91%) 	580.64s (+0.44%)
New approach: C 		07e44e05f8 	7.80s (-2.85%) 	8.03s (-2.86%) 	11.63s (-1.57%) 	2.57s (-8.93%) 	8.25s (-2.60%) 	2.97s (-7.09%) 	7.06s (-1.02%) 	2.33s (-3.10%) 	580.01s (+0.01%)
```
author_association: COLLABORATOR
user: login: AaronBallman
created_at: 2025-04-28T16:28:47Z
body: After talking with Erich offline, I've updated the implementation to use a binary_search. The pointers are being cast to `uintptr_t` so that the `llvm::sort()` isn't operating on unrelated pointers.
author_association: COLLABORATOR
user: login: AaronBallman
created_at: 2025-04-29T18:49:19Z
body: Adding @rjmccall because this touches on Objective-C `@` keyword handling. (Note, maybe we want `-Wc++-compat` to be disabled in Objective-C entirely and we should have `-Wobjc++-compat` instead? `[foo bar];` is not valid C++, but I doubt anyone wants that diagnosed in Objective-C code, so it's a bit unclear what the diagnostic group means for ObjC.)
author_association: COLLABORATOR
user: login: rjmccall
created_at: 2025-04-29T19:11:20Z
body: The ObjC `@` is essentially an escape into a completely different grammar, and it doesn't matter whether the following identifier is a keyword or not in the base language. This warning should never kick in on that identifier.

Similarly, ObjC selector components exist outside of the normal keyword rules, and the warning should never kick in on them.

Otherwise, this warning would be very useful in baseline Objective-C as a way of discovering names that would be keywords in Objective-C++, mostly in the incorporated C subset but also for ObjC features that ultimately end up in the C namespace and therefore are subject to the normal keyword rules, like method parameters and class names.
author_association: CONTRIBUTOR
user: login: AaronBallman
created_at: 2025-04-30T12:38:57Z
body: > The ObjC `@` is essentially an escape into a completely different grammar, and it doesn't matter whether the following identifier is a keyword or not in the base language. This warning should never kick in on that identifier.

Okay, I've done that.

> Similarly, ObjC selector components exist outside of the normal keyword rules, and the warning should never kick in on them.

I don't know about selectors all that much; can you give me a test case that you think I should handle?

> Otherwise, this warning would be very useful in baseline Objective-C as a way of discovering names that would be keywords in Objective-C++, mostly in the incorporated C subset but also for ObjC features that ultimately end up in the C namespace and therefore are subject to the normal keyword rules, like method parameters and class names.

Okay, it remains enabled in Objective-C, just silenced if the preceding token is `@`.
author_association: COLLABORATOR
user: login: rjmccall
created_at: 2025-04-30T18:30:11Z
body: > > Similarly, ObjC selector components exist outside of the normal keyword rules, and the warning should never kick in on them.
> 
> I don't know about selectors all that much; can you give me a test case that you think I should handle?

In general: look at `ParseObjCSelectorPiece` and how it generally accepts keywords in the selector. Actually, the list of keywords there seems out of date and notably doesn't include post-C++98 keywords like `decltype`. The logic is absolutely meant to encompass every keyword; note that it even includes basic C keywords like `if` and `struct`. You probably don't want to fix that, but I can ping someone at Apple to do it.

Anyway, selectors show up in three places in the ObjC grammar:
- `@selector` expressions, e.g. `@selector(virtual:typeid:bool:)`
- message send expressions, e.g. `[obj virtual: 0 typeid: 0 bool: 0]`
- method declarations, e.g. `
-  declarations, e.g.

```
@interface Foo
- (void) virtual: (int) x typeid: (int) y bool: (int) z;
@end
```

But there are also some special-case parsing rules for `class` because it is an important property name for `NSObject`. For example, in Objective-C++, the member name following `-> ` or `.` is allowed to be `class` despite it being a keyword.
author_association: CONTRIBUTOR
user: login: AaronBallman
created_at: 2025-04-30T18:45:13Z
body: > > > Similarly, ObjC selector components exist outside of the normal keyword rules, and the warning should never kick in on them.
> > 
> > 
> > I don't know about selectors all that much; can you give me a test case that you think I should handle?
> 
> In general: look at `ParseObjCSelectorPiece` and how it generally accepts keywords in the selector. Actually, the list of keywords there seems out of date and notably doesn't include post-C++98 keywords like `decltype`. The logic is absolutely meant to encompass every keyword; note that it even includes basic C keywords like `if` and `struct`. You probably don't want to fix that, but I can ping someone at Apple to do it.
> 
> Anyway, selectors show up in three places in the ObjC grammar:
> 
>     * `@selector` expressions, e.g. `@selector(virtual:typeid:bool:)`
> 
>     * message send expressions, e.g. `[obj virtual: 0 typeid: 0 bool: 0]`
> 
>     * method declarations, e.g. `
> 
>     * declarations, e.g.
> 
> 
> ```
> @interface Foo
> - (void) virtual: (int) x typeid: (int) y bool: (int) z;
> @end
> ```
> 
> But there are also some special-case parsing rules for `class` because it is an important property name for `NSObject`. For example, in Objective-C++, the member name following `-> ` or `.` is allowed to be `class` despite it being a keyword.

Ooof, it seems like there may not be a good way of handling this with the approach recommended by @zygoloid where we diagnose from `HandleIdentifier()` in the preprocessor. That requires far too much looking around to try to identify whether we're in the middle of a selector or not. The best I could think of would be to use some sort of "don't complain about keyword compatibility, we're doing <things>" RAII object to set a flag on the preprocessor so we can read it in `HandleIdentifier`. If we go that route, I could also stop looking to see if the preceding token is `@` from `HandleIdentifier()` and just use that mode. But is this too much of a hack?
author_association: COLLABORATOR
user: login: rjmccall
created_at: 2025-04-30T18:50:29Z
body: Yeah, triggering this at the preprocessor level is not going to let you do it properly in Objective-C, where keywords are effectively context-sensitive. If that's the only reasonable way to implement it, you should just turn it off in Objective-C completely.
author_association: CONTRIBUTOR
user: login: zygoloid
created_at: 2025-04-30T23:17:42Z
body: For what it's worth, the existing "keyword from language you're not using" warnings [already fire for analogous Objective-C++ cases](https://godbolt.org/z/3K6835PPK)... which in those cases is correct, since Clang does *not* accept C++11-onwards keywords as selector names in the corresponding versions of Objective-C++ (but presumably should -- I wonder if we should just check whether the token has an identifier spelling and not care what kind it is, [like we do for attribute names](https://github.com/llvm/llvm-project/blob/c3715ecb1e1e63856a029be3c95b7e9595a56018/clang/lib/Parse/ParseDeclCXX.cpp#L4575)?).

I wonder if it'd suffice to do a single-token lookahead / lookbehind in Objective-C / Objective-C++ and disable all of these warnings if the previous token is `@` or the next token is `:`. That would have false negatives for some weird cases like `class co_await : base {};` under `-Wc++20-compat` in Objective-C++17 or earlier, but that might be better than losing the warning entirely.
author_association: COLLABORATOR
user: login: AaronBallman
created_at: 2025-05-01T12:01:08Z
body: I've removed support for Objective-C for now; we can come back to it again in the future.
author_association: COLLABORATOR
user: login: rjmccall
created_at: 2025-05-01T22:20:32Z
body: > For what it's worth, the existing "keyword from language you're not using" warnings [already fire for analogous Objective-C++ cases](https://godbolt.org/z/3K6835PPK)... which in those cases is correct, since Clang does _not_ accept C++11-onwards keywords as selector names in the corresponding versions of Objective-C++ (but presumably should -- I wonder if we should just check whether the token has an identifier spelling and not care what kind it is, [like we do for attribute names](https://github.com/llvm/llvm-project/blob/c3715ecb1e1e63856a029be3c95b7e9595a56018/clang/lib/Parse/ParseDeclCXX.cpp#L4575)?).

Yeah, I can't think of any good reason why this has a hardcoded list of keywords. Even if it's desirable for switching purposes, seems like it should be autogenerated from `TokenKinds.def`.

> I wonder if it'd suffice to do a single-token lookahead / lookbehind in Objective-C / Objective-C++ and disable all of these warnings if the previous token is `@` or the next token is `:`

Sadly, nullary selector pieces aren't followed by colons: `[foo decltype]`, etc.

> I've removed support for Objective-C for now; we can come back to it again in the future.

I think that's fine.
author_association: CONTRIBUTOR
user: login: seanm
created_at: 2025-08-28T15:06:18Z
body: Should this warn about `wchar_t` in C?

```c
#include <wchar.h>

int main (void)
{
  wchar_t foo;
  (void)foo;
  return 0;
}
```

```
% xcrun /Users/builder/llvm/llvm-install/bin/clang -fsyntax-only -Weverything ~/Desktop/test.c

/Users/builder/Desktop/test.c:5:3: warning: identifier 'wchar_t' conflicts with a C++ keyword [-Wc++-keyword]
    5 |   wchar_t foo;
      |   ^
```

author_association: NONE
user: login: AaronBallman
created_at: 2025-08-29T08:37:31Z
body: > Should this warn about `wchar_t` in C?
> 
> ```c
> #include <wchar.h>
> 
> int main (void)
> {
>   wchar_t foo;
>   (void)foo;
>   return 0;
> }
> ```
> 
> ```
> % xcrun /Users/builder/llvm/llvm-install/bin/clang -fsyntax-only -Weverything ~/Desktop/test.c
> 
> /Users/builder/Desktop/test.c:5:3: warning: identifier 'wchar_t' conflicts with a C++ keyword [-Wc++-keyword]
>     5 |   wchar_t foo;
>       |   ^
> ```

That looks like a bug to me, good catch! I've filed https://github.com/llvm/llvm-project/issues/155988 to track it, thank you!
author_association: COLLABORATOR