https://github.com/ianayl created 
https://github.com/llvm/llvm-project/pull/224769

This PR implements [CWG3013](https://wg21.link/cwg3013) in clang, where error 
diagnostics are now issued if an `#embed` directive or a `__has_embed` 
expression contains a parameter (i.e. `limit`, `prefix`, `suffix`, `if_empty`) 
that has previously been defined as a macro, e.g.:

```cpp
#define limit something_else
const char foo[] = {
#embed <file.txt> limit(42)
};
```
Such code now generates the following diagnostic:
```
foo.cpp:3:19: error: cannot use 'limit' as an '#embed' parameter if also 
defined as a macro
    3 | #embed <file.txt> limit(42)
      |                   ^
foo.cpp:1:9: note: macro 'limit' defined here
    1 | #define limit something_else
      |         ^
```

This adheres to [CWG3013](https://wg21.link/cwg3013), which stipulates such 
code is now "ill-formed" in C++. However, (AFAIK) no such rules exist in C. 
Thus, the previous behavior is still in-place for C, e.g. macros named as e.g. 
`limit` or `prefix` are still expanded in-place, i.e. in our previous code:
```c
#embed <file.txt> limit(42)
```
In C, this directive becomes the following after macro expansion: 
```c
#embed <file.txt> something_else(42)
```

>From fd0ec1d6dd50bdd02a1e32ba49c7146dcf8bad73 Mon Sep 17 00:00:00 2001
From: "Li, Ian" <[email protected]>
Date: Fri, 18 Sep 2026 15:34:24 -0700
Subject: [PATCH] Implement CWG3013 in clang, but only for C++

---
 clang/docs/ReleaseNotes.md                    | 18 ++++++
 clang/include/clang/Basic/DiagnosticGroups.td |  4 +-
 .../include/clang/Basic/DiagnosticLexKinds.td |  8 +++
 clang/include/clang/Lex/Preprocessor.h        |  4 ++
 clang/lib/Lex/PPDirectives.cpp                |  4 ++
 clang/lib/Lex/Preprocessor.cpp                | 24 ++++++++
 clang/test/CXX/drs/cwg3013.cpp                | 58 +++++++++++++++++++
 clang/test/CXX/drs/inputs/media/art.txt       | 13 +++++
 clang/test/CXX/drs/inputs/media/empty         |  0
 9 files changed, 132 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/CXX/drs/cwg3013.cpp
 create mode 100644 clang/test/CXX/drs/inputs/media/art.txt
 create mode 100644 clang/test/CXX/drs/inputs/media/empty

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index f4a34a37aff52..da925b260ee1c 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -54,6 +54,13 @@ in a future version of Clang.
   mode, as it was removed from the standard by
   [P3475R2](https://wg21.link/P3475R2).
 
+- An error diagnostic is now issued if an `#embed` directive or a `__has_embed`
+  expression contains a parameter (i.e. `limit`, `prefix`, `suffix`, 
`if_empty`)
+  that has previously been defined as a macro, as per
+  [CWG3013](https://wg21.link/cwg3013). Previously macros that shared names 
with
+  `#embed` parameter names were expanded regardless, similar to its behavior in
+  C: Note that this expansion behavior is still present in C since there are no
+  rule analogue to CWG3013 in C.
 
 ### Objective-C Specific Potentially Breaking Changes
 
@@ -194,6 +201,11 @@ features cannot lower the translation-unit ABI level;
   them to an enumeration type with a fixed `bool` underlying type. This
   resolves [CWG1094](https://wg21.link/cwg1094).
 
+- Clang now diagnoses an error if an `#embed` directive or `__has_embed`
+  statement uses a parameter name (i.e. `limit`, `prefix`, `suffix`, 
`if_empty`)
+  that has previously been defined as a macro. This resolves
+  [CWG3013](https://wg21.link/cwg3013), which marks such code as ill-formed.
+
 ### C Language Changes
 
 #### C2y Feature Support
@@ -518,6 +530,12 @@ features cannot lower the translation-unit ABI level;
 
 - Improve Clang diagnoses when unary `__imag` operator with non-complex type 
operand is used as lvalue. (GH222383)
 
+- Added `-Wembed-parameter-is-macro`, which warns in C if an `#embed` directive
+  or a `__has_embed` expression uses a parameter (i.e. `limit`, `prefix`,
+  `suffix`, `if_empty`) that has also been defined as a macro. C expands the
+  macro, but the same code is ill-formed in C++, so 
`-Wembed-parameter-is-macro`
+  is also part of `-Wc++-compat`; this warning is disabled by default 
otherwise.
+
 ### Improvements to Clang's time-trace
 
 ### Improvements to Coverage Mapping
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 1da7698944b24..b3c36c76d490b 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -187,6 +187,7 @@ def C23Compat : DiagGroup<"c23-compat">;
 def : DiagGroup<"c2x-compat", [C23Compat]>;
 
 def CppKeywordInC : DiagGroup<"c++-keyword">;
+def EmbedParameterIsMacro : DiagGroup<"embed-parameter-is-macro">;
 def DuplicateDeclSpecifier : DiagGroup<"duplicate-decl-specifier">;
 def InitStringTooLongMissingNonString :
   DiagGroup<"unterminated-string-initialization">;
@@ -212,7 +213,8 @@ def CXXCompat: DiagGroup<"c++-compat", 
[ImplicitVoidPtrCast, DefaultConstInit,
                                         ImplicitIntToEnumCast, HiddenCppDecl,
                                         InitStringTooLongForCpp, CppKeywordInC,
                                         TentativeDefnCompat, JumpBypassesInit,
-                                        DuplicateDeclSpecifier]>;
+                                        DuplicateDeclSpecifier,
+                                        EmbedParameterIsMacro]>;
 
 def ExternCCompat : DiagGroup<"extern-c-compat">;
 def KeywordCompat : DiagGroup<"keyword-compat">;
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td 
b/clang/include/clang/Basic/DiagnosticLexKinds.td
index ff51485a1810b..006da9a53e96a 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -492,6 +492,14 @@ def warn_compat_pp_embed_directive : Warning<
   InGroup<CPre23Compat>, DefaultIgnore;
 def err_pp_embed_dup_params : Error<
   "cannot specify parameter '%0' twice in the same '#embed' directive">;
+def err_pp_embed_parameter_is_macro : Error<
+  "cannot use %0 as %select{an '#embed'|a '__has_embed'}1 parameter if also"
+  " defined as a macro">;
+def warn_c_pp_embed_parameter_is_macro : Warning<
+  "%0 is defined as a macro and gets expanded when used as a "
+  "%select{an '#embed'|a '__has_embed'}1 parameter in C; this is "
+  "ill-formed in C++">,
+  InGroup<EmbedParameterIsMacro>, DefaultIgnore;
 def err_pp_embed_device_file : Error<
   "device files are not yet supported by '#embed' directive">;
 
diff --git a/clang/include/clang/Lex/Preprocessor.h 
b/clang/include/clang/Lex/Preprocessor.h
index 31b68a0fd0670..4711728b1b764 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -302,6 +302,10 @@ class Preprocessor {
   /// True if we are currently preprocessing a #if or #elif directive
   bool ParsingIfOrElifDirective;
 
+  /// True if we are preprocessing the parameters of an #embed directive or a
+  /// __has_embed expression.
+  bool ParsingEmbedParameters;
+
   /// True if we are pre-expanding macro arguments.
   bool InMacroArgPreExpansion;
 
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index f1e9aaa72ff04..0b7c727d2ce89 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -3771,6 +3771,10 @@ void Preprocessor::HandleElifFamilyDirective(Token 
&ElifToken,
 std::optional<LexEmbedParametersResult>
 Preprocessor::LexEmbedParameters(Token &CurTok, bool ForHasEmbed) {
   LexEmbedParametersResult Result{};
+  if (ForHasEmbed)
+    assert(isParsingIfOrElifDirective() &&
+           "__has_embed outside of #if or #elif directive?");
+  llvm::SaveAndRestore InEmbedParams(ParsingEmbedParameters, true);
   tok::TokenKind EndTokenKind = ForHasEmbed ? tok::r_paren : tok::eod;
 
   auto DiagMismatchedBracesAndSkipToEOD =
diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp
index 84907adc5d744..9bab63818007c 100644
--- a/clang/lib/Lex/Preprocessor.cpp
+++ b/clang/lib/Lex/Preprocessor.cpp
@@ -119,6 +119,7 @@ Preprocessor::Preprocessor(const PreprocessorOptions 
&PPOpts,
   NumCachedTokenLexers = 0;
   PragmasEnabled = true;
   ParsingIfOrElifDirective = false;
+  ParsingEmbedParameters = false;
   PreprocessedOutput = false;
 
   // We haven't read anything from the external source.
@@ -879,6 +880,11 @@ void Preprocessor::updateOutOfDateIdentifier(const 
IdentifierInfo &II) const {
   getExternalSource()->updateOutOfDateIdentifier(II);
 }
 
+static bool isProtectedEmbedParameterName(const IdentifierInfo *II) {
+  return II->isStr("limit") || II->isStr("prefix") || II->isStr("suffix") ||
+         II->isStr("if_empty");
+}
+
 /// HandleIdentifier - This callback is invoked when the lexer reads an
 /// identifier.  This callback looks up the identifier in the map and/or
 /// potentially macro expands it or turns it into a named token (like 'for').
@@ -922,6 +928,24 @@ bool Preprocessor::HandleIdentifier(Token &Identifier) {
   if (const MacroDefinition MD = getMacroDefinition(&II)) {
     const auto *MI = MD.getMacroInfo();
     assert(MI && "macro definition with no macro info?");
+    // C++ [cpp.pre]/p4, [cpp.cond]/p9: if one of the pp-tokens of a #embed
+    // directive (or a has-embed-expression) is the identifier limit, prefix,
+    // suffix, or if_empty and that identifier is defined as a macro, the
+    // program is ill-formed.
+    //
+    // Thus, do not continue processing if compiling for C++. C doesn't have
+    // this restriction however, so only issue a warning for C if -Wc++-compat
+    // is enabled.
+    if (ParsingEmbedParameters && isProtectedEmbedParameterName(&II)) {
+      Diag(Identifier, getLangOpts().CPlusPlus
+                           ? diag::err_pp_embed_parameter_is_macro
+                           : diag::warn_c_pp_embed_parameter_is_macro)
+          << &II << isParsingIfOrElifDirective();
+      Diag(MI->getDefinitionLoc(), diag::note_macro_here) << &II;
+      if (getLangOpts().CPlusPlus)
+        return true;
+    }
+
     if (!DisableMacroExpansion) {
       if (!Identifier.isExpandDisabled() && MI->isEnabled()) {
         // C99 6.10.3p10: If the preprocessing token immediately after the
diff --git a/clang/test/CXX/drs/cwg3013.cpp b/clang/test/CXX/drs/cwg3013.cpp
new file mode 100644
index 0000000000000..95f4f6fc6f9e2
--- /dev/null
+++ b/clang/test/CXX/drs/cwg3013.cpp
@@ -0,0 +1,58 @@
+// RUN: %clang_cc1 --embed-dir=%S/inputs -Wno-c23-extensions -fsyntax-only 
-verify=cxx,common -x c++ %s
+// RUN: %clang_cc1 --embed-dir=%S/inputs -fsyntax-only -verify=c -std=c23 -x c 
%s
+// RUN: %clang_cc1 --embed-dir=%S/inputs -fsyntax-only -verify=c-compat,common 
-std=c23 -Wc++-compat -x c %s
+//
+// Test -Wembed-parameter-is-macro:
+// RUN: %clang_cc1 --embed-dir=%S/inputs -fsyntax-only -verify=c-compat,common 
-std=c23 -Wembed-parameter-is-macro -x c %s
+// RUN: %clang_cc1 --embed-dir=%S/inputs -fsyntax-only -verify=c -std=c23 
-Wc++-compat -Wno-embed-parameter-is-macro -x c %s
+
+// CWG3013: if one of the pp-tokens of a #embed directive (or a
+// has-embed-expression) is the identifier limit, prefix, suffix, or if_empty
+// and that identifier is defined as a macro, the program is ill-formed.
+// (C++ [cpp.pre]/p4, [cpp.cond]/p9)
+//
+// However, C doesn't have this restriction, so we should only issue a warning
+// for C if -Wc++-compat/-Wembed-parameter-is-macro is enabled.
+
+// c-no-diagnostics
+
+#define limit limit
+// common-note@-1 2 {{macro 'limit' defined here}}
+const int a[] = {
+#embed <media/art.txt> limit(2)
+// cxx-error@-1 {{cannot use 'limit' as an '#embed' parameter if also defined 
as a macro}}
+// c-compat-warning@-2 {{'limit' is defined as a macro and gets expanded when 
used as an '#embed' parameter in C; this is ill-formed in C++}}
+};
+
+#define prefix prefix
+// common-note@-1 {{macro 'prefix' defined here}}
+const int b[] = {
+#embed <media/art.txt> prefix(0,)
+// cxx-error@-1 {{cannot use 'prefix' as an '#embed' parameter if also defined 
as a macro}}
+// c-compat-warning@-2 {{'prefix' is defined as a macro and gets expanded when 
used as an '#embed' parameter in C; this is ill-formed in C++}}
+};
+
+#define suffix suffix
+// common-note@-1 2 {{macro 'suffix' defined here}}
+const int c[] = {
+#embed <media/art.txt> suffix(,0)
+// cxx-error@-1 {{cannot use 'suffix' as an '#embed' parameter if also defined 
as a macro}}
+// c-compat-warning@-2 {{'suffix' is defined as a macro and gets expanded when 
used as an '#embed' parameter in C; this is ill-formed in C++}}
+};
+
+#define if_empty if_empty
+// common-note@-1 {{macro 'if_empty' defined here}}
+const int d[] = {
+#embed <media/empty> if_empty(0)
+// cxx-error@-1 {{cannot use 'if_empty' as an '#embed' parameter if also 
defined as a macro}}
+// c-compat-warning@-2 {{'if_empty' is defined as a macro and gets expanded 
when used as an '#embed' parameter in C; this is ill-formed in C++}}
+};
+
+// The prohibition also covers the __has_embed argument.
+#if __has_embed(<media/art.txt> limit(1) suffix(0))
+// cxx-error@-1 {{cannot use 'limit' as a '__has_embed' parameter if also 
defined as a macro}}
+// cxx-error@-2 {{cannot use 'suffix' as a '__has_embed' parameter if also 
defined as a macro}}
+// c-compat-warning@-3 {{'limit' is defined as a macro and gets expanded when 
used as a '__has_embed' parameter in C; this is ill-formed in C++}}
+// c-compat-warning@-4 {{'suffix' is defined as a macro and gets expanded when 
used as a '__has_embed' parameter in C; this is ill-formed in C++}}
+int e;
+#endif
diff --git a/clang/test/CXX/drs/inputs/media/art.txt 
b/clang/test/CXX/drs/inputs/media/art.txt
new file mode 100644
index 0000000000000..f4536f39af349
--- /dev/null
+++ b/clang/test/CXX/drs/inputs/media/art.txt
@@ -0,0 +1,13 @@
+   
+   -------------------------------------------
+   .                                         .
+   .                       _                 .
+   .     _        _      >(. )      _        .
+   .   >(. )__  >(- )__    //___  >(. )__    .
+   .   ~(____/ -~(_(=-/-~~(_(__/-~~(____/~   .
+   .      ~.  -~~~ .  -~.             ~~-    .
+   .           ~-                            .
+   .                                         .
+   -------------------------------------------
+   
+                     O Pato
\ No newline at end of file
diff --git a/clang/test/CXX/drs/inputs/media/empty 
b/clang/test/CXX/drs/inputs/media/empty
new file mode 100644
index 0000000000000..e69de29bb2d1d

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to