[clang] [NFC][Clang] Add test for P2843R3 - Preprocessing is never undefined (PR #192073)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 15 09:41:17 PDT 2026
https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/192073
>From d5c3cd1f77a34e6b0b281d2a40f23a16dc760457 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 14 Apr 2026 22:44:09 +0800
Subject: [PATCH 1/4] [NFC][Clang] Add test for P2843R3 - Preprocessing is
never undefined
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/docs/ReleaseNotes.rst | 4 ++++
clang/test/Preprocessor/p2843r3.cpp | 35 +++++++++++++++++++++++++++++
clang/www/cxx_status.html | 2 +-
3 files changed, 40 insertions(+), 1 deletion(-)
create mode 100644 clang/test/Preprocessor/p2843r3.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3e2d287d1eb1f..859b7e999848a 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -133,6 +133,10 @@ C++ Language Changes
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
+- Implemented `P2843R3 <https://wg21.link/P2843R3>`_ Preprocessing is never undefined.
+ The constructs it makes ill-formed were already diagnosed by Clang under
+ ``-pedantic``; no behavior change, just conformance.
+
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/test/Preprocessor/p2843r3.cpp b/clang/test/Preprocessor/p2843r3.cpp
new file mode 100644
index 0000000000000..53c272390527c
--- /dev/null
+++ b/clang/test/Preprocessor/p2843r3.cpp
@@ -0,0 +1,35 @@
+// RUN: %clang_cc1 -std=c++26 -pedantic -verify -Wno-invalid-pp-token %s
+// RUN: %clang_cc1 -std=c++23 -pedantic -verify -Wno-invalid-pp-token %s
+
+// P2843R3: Preprocessing is never undefined.
+// These constructs were previously "undefined behavior" in the preprocessor;
+// as of C++26 they are ill-formed (diagnostic required). Clang already
+// diagnoses them under -pedantic, so this test just pins that behavior down.
+
+// [cpp.cond] A macro that expands to 'defined' in a conditional expression.
+#define DEFINED defined
+#if DEFINED(bar) // expected-warning {{macro expansion producing 'defined' has undefined behavior}}
+#endif
+
+// [cpp.replace.general] A preprocessing directive inside the arguments of a
+// function-like macro invocation.
+#define FUNCTION_MACRO(...)
+FUNCTION_MACRO(
+ #if 0 // expected-warning {{embedding a directive within macro arguments has undefined behavior}}
+ #endif
+)
+
+// [cpp.concat] Concatenation that does not form a valid preprocessing token.
+#define CONCAT(A, B) A ## B
+CONCAT(=, >) // expected-error {{pasting formed '=>', an invalid preprocessing token}}
+// expected-error at -1 {{expected unqualified-id}}
+
+// [cpp.predefined] #undef of a reserved identifier / builtin macro.
+#undef defined // expected-error {{'defined' cannot be used as a macro name}}
+#undef __DATE__ // expected-warning {{undefining builtin macro}}
+
+// [cpp.line] #line with a non-positive or out-of-range argument.
+#line 0 // expected-warning {{#line directive with zero argument is a GNU extension}}
+#line -1 // expected-error {{#line directive requires a positive integer argument}}
+#line 2147483647 // ok, largest value required to be accepted
+#line 2147483648 // expected-warning {{C requires #line number to be less than 2147483648, allowed as extension}}
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 2c834b07f9a8f..4a669ff4da8a1 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -330,7 +330,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>Preprocessing is never undefined</td>
<td><a href="https://wg21.link/P2843">P2843R3</a></td>
- <td class="none" align="center">No</td>
+ <td class="full" align="center">Clang 23</td>
</tr>
<!-- Kona, Fall 2025-->
<tr>
>From 3cf297853d16cb4215fb1b98468b4537c0c98406 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Tue, 14 Apr 2026 23:00:19 +0800
Subject: [PATCH 2/4] Update ReleaseNotes
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/docs/ReleaseNotes.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 859b7e999848a..8d69c4716620b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -133,7 +133,7 @@ C++ Language Changes
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
-- Implemented `P2843R3 <https://wg21.link/P2843R3>`_ Preprocessing is never undefined.
+- Mark `P2843R3 <https://wg21.link/P2843R3>`_ Preprocessing is never undefined as implemented.
The constructs it makes ill-formed were already diagnosed by Clang under
``-pedantic``; no behavior change, just conformance.
>From f374e17bf507f7786d3268c5fcdbb46a94d47894 Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Wed, 15 Apr 2026 00:32:18 +0800
Subject: [PATCH 3/4] Convert ext_embedded_directive to hard error and add more
test
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
.../include/clang/Basic/DiagnosticLexKinds.td | 2 +
clang/lib/Lex/PPDirectives.cpp | 42 +++++++++++++------
clang/lib/Lex/PPExpressions.cpp | 4 +-
clang/test/Lexer/cxx-features.cpp | 20 +++++----
clang/test/Preprocessor/p2843r3.cpp | 36 +++++++++++-----
5 files changed, 72 insertions(+), 32 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 30efb0d90c124..2d80538efb932 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -1053,6 +1053,8 @@ def warn_defined_in_object_type_macro : Warning<
def warn_defined_in_function_type_macro : Extension<
"macro expansion producing 'defined' has undefined behavior">,
InGroup<ExpansionToDefined>;
+def err_defined_in_macro : Error<
+ "macro expansion producing 'defined' is not allowed">;
let CategoryName = "Nullability Issue" in {
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index be2878076510d..be68a61db4051 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -1351,8 +1351,14 @@ void Preprocessor::HandleDirective(Token &Result) {
// not support this for #include-like directives, since that can result in
// terrible diagnostics, and does not work in GCC.
if (InMacroArgs) {
- if (IdentifierInfo *II = Result.getIdentifierInfo()) {
- switch (II->getPPKeywordID()) {
+ IdentifierInfo *II = Result.getIdentifierInfo();
+
+ // Certain #include-like and module-related directives never work inside
+ // a macro argument list: supporting them would produce terrible
+ // diagnostics and is already incompatible with GCC. They are always an
+ // error regardless of language mode.
+ auto IsAlwaysUnsupported = [](tok::PPKeywordKind K) {
+ switch (K) {
case tok::pp_include:
case tok::pp_import:
case tok::pp_include_next:
@@ -1362,20 +1368,30 @@ void Preprocessor::HandleDirective(Token &Result) {
case tok::pp_module:
case tok::pp___preprocessed_module:
case tok::pp___preprocessed_import:
- Diag(Result, diag::err_embedded_directive)
- << (getLangOpts().CPlusPlusModules &&
- Introducer.isModuleContextualKeyword(
- /*AllowExport=*/false))
- << II->getName();
- Diag(*ArgMacro, diag::note_macro_expansion_here)
- << ArgMacro->getIdentifierInfo();
- DiscardUntilEndOfDirective();
- return;
+ return true;
default:
- break;
+ return false;
}
+ };
+
+ // [cpp.replace.general] makes any embedded directive ill-formed in
+ // C++26; in earlier modes the construct is undefined behavior and only
+ // pedantically warned about.
+ const bool IsError = LangOpts.CPlusPlus26 ||
+ (II && IsAlwaysUnsupported(II->getPPKeywordID()));
+
+ if (!IsError) {
+ Diag(Result, diag::ext_embedded_directive);
+ } else {
+ Diag(Result, diag::err_embedded_directive)
+ << (LangOpts.CPlusPlusModules &&
+ Introducer.isModuleContextualKeyword(/*AllowExport=*/false))
+ << (II ? II->getName() : StringRef());
+ Diag(*ArgMacro, diag::note_macro_expansion_here)
+ << ArgMacro->getIdentifierInfo();
+ DiscardUntilEndOfDirective();
+ return;
}
- Diag(Result, diag::ext_embedded_directive);
}
// Temporarily enable macro expansion if set so
diff --git a/clang/lib/Lex/PPExpressions.cpp b/clang/lib/Lex/PPExpressions.cpp
index 887fd25ac318d..46eb66a2eac7e 100644
--- a/clang/lib/Lex/PPExpressions.cpp
+++ b/clang/lib/Lex/PPExpressions.cpp
@@ -204,7 +204,9 @@ static bool EvaluateDefined(PPValue &Result, Token &PeekTok, DefinedTracker &DT,
// in a different way, and compilers seem to agree on how to behave here.
// So warn by default on object-type macros, but only warn in -pedantic
// mode on function-type macros.
- if (IsFunctionTypeMacro)
+ if (PP.getLangOpts().CPlusPlus26)
+ PP.Diag(beginLoc, diag::err_defined_in_macro);
+ else if (IsFunctionTypeMacro)
PP.Diag(beginLoc, diag::warn_defined_in_function_type_macro);
else
PP.Diag(beginLoc, diag::warn_defined_in_object_type_macro);
diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp
index 8eb9ea032879c..446bb0ac76056 100644
--- a/clang/test/Lexer/cxx-features.cpp
+++ b/clang/test/Lexer/cxx-features.cpp
@@ -14,21 +14,25 @@
// expected-no-diagnostics
-// FIXME using `defined` in a macro has undefined behavior.
+// An undefined feature-test macro evaluates to 0 in an #if expression, so
+// `__cpp_##macro != N` tests the feature is defined with the exact value N
+// when N is nonzero, and tests the feature is not defined (or is defined to 0,
+// which feature-test macros never are) when N is 0. Avoiding `defined` inside
+// a macro expansion is required for conformance with [cpp.cond].
#if __cplusplus < 201103L
-#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx98 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx98)
+#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx98)
#elif __cplusplus < 201402L
-#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx11 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx11)
+#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx11)
#elif __cplusplus < 201703L
-#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx14 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx14)
+#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx14)
#elif __cplusplus < 202002L
-#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx17 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx17)
+#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx17)
#elif __cplusplus < 202302L
-#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx20 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx20)
+#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx20)
#elif __cplusplus == 202302L
-#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx23 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx23)
+#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx23)
#else
-#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (cxx26 == 0 ? defined(__cpp_##macro) : __cpp_##macro != cxx26)
+#define check(macro, cxx98, cxx11, cxx14, cxx17, cxx20, cxx23, cxx26) (__cpp_##macro != cxx26)
#endif
// --- C++26 features ---
diff --git a/clang/test/Preprocessor/p2843r3.cpp b/clang/test/Preprocessor/p2843r3.cpp
index 53c272390527c..747fe59e25e45 100644
--- a/clang/test/Preprocessor/p2843r3.cpp
+++ b/clang/test/Preprocessor/p2843r3.cpp
@@ -1,21 +1,37 @@
-// RUN: %clang_cc1 -std=c++26 -pedantic -verify -Wno-invalid-pp-token %s
-// RUN: %clang_cc1 -std=c++23 -pedantic -verify -Wno-invalid-pp-token %s
+// RUN: %clang_cc1 -std=c++26 -pedantic -verify=cxx26,expected -Wno-invalid-pp-token %s
+// RUN: %clang_cc1 -std=c++23 -pedantic -verify=cxx23,expected -Wno-invalid-pp-token %s
-// P2843R3: Preprocessing is never undefined.
-// These constructs were previously "undefined behavior" in the preprocessor;
-// as of C++26 they are ill-formed (diagnostic required). Clang already
-// diagnoses them under -pedantic, so this test just pins that behavior down.
+// P2843R3: Preprocessing is never undefined. The constructs this paper makes
+// ill-formed were previously undefined behavior; under C++26 Clang now
+// diagnoses them as errors, while retaining the pre-existing pedantic warning
+// in earlier language modes for compatibility.
-// [cpp.cond] A macro that expands to 'defined' in a conditional expression.
+// [cpp.cond] A macro expansion that produces 'defined' in a conditional
+// expression. P2843R3 makes this ill-formed; promoted to a hard error in
+// C++26.
#define DEFINED defined
-#if DEFINED(bar) // expected-warning {{macro expansion producing 'defined' has undefined behavior}}
+// cxx26-error at +2 {{macro expansion producing 'defined' is not allowed}}
+// cxx23-warning at +1 {{macro expansion producing 'defined' has undefined behavior}}
+#if DEFINED(bar)
+#endif
+
+// Malformed 'defined' operands are ill-formed in all modes.
+#if defined() // expected-error {{macro name must be an identifier}}
+#endif
+#if defined(a b) // expected-error {{missing ')' after 'defined'}} expected-note {{to match this '('}}
+#endif
+#if defined(a, b) // expected-error {{missing ')' after 'defined'}} expected-note {{to match this '('}}
#endif
// [cpp.replace.general] A preprocessing directive inside the arguments of a
-// function-like macro invocation.
+// function-like macro invocation. Promoted to a hard error in C++26.
#define FUNCTION_MACRO(...)
+// cxx26-note at +1 2 {{expansion of macro 'FUNCTION_MACRO' requested here}}
FUNCTION_MACRO(
- #if 0 // expected-warning {{embedding a directive within macro arguments has undefined behavior}}
+ // cxx26-error at +2 {{embedding a #if directive within macro arguments is not supported}}
+ // cxx23-warning at +1 {{embedding a directive within macro arguments has undefined behavior}}
+ #if 0
+ // cxx26-error at +1 {{embedding a #endif directive within macro arguments is not supported}}
#endif
)
>From 30c37db3b5e5baa8a2117f7058a689ae0c06874c Mon Sep 17 00:00:00 2001
From: yronglin <yronglin777 at gmail.com>
Date: Thu, 16 Apr 2026 00:40:48 +0800
Subject: [PATCH 4/4] Revert embeding directive change, let's finish it in a
separate PR
Signed-off-by: yronglin <yronglin777 at gmail.com>
---
clang/docs/ReleaseNotes.rst | 8 +++---
clang/lib/Lex/PPDirectives.cpp | 42 +++++++++--------------------
clang/test/Preprocessor/p2843r3.cpp | 9 +++----
clang/www/cxx_status.html | 2 +-
4 files changed, 22 insertions(+), 39 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 8d69c4716620b..6d06771b9b8e6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -133,9 +133,11 @@ C++ Language Changes
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
-- Mark `P2843R3 <https://wg21.link/P2843R3>`_ Preprocessing is never undefined as implemented.
- The constructs it makes ill-formed were already diagnosed by Clang under
- ``-pedantic``; no behavior change, just conformance.
+- Partially implemented `P2843R3 <https://wg21.link/P2843R3>`_ Preprocessing is
+ never undefined. A macro expansion producing ``defined`` in a conditional
+ expression is now a hard error in C++26; other constructs the paper makes
+ ill-formed (notably embedding a directive within macro arguments) remain a
+ pedantic warning for now and will be promoted separately.
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index be68a61db4051..be2878076510d 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -1351,14 +1351,8 @@ void Preprocessor::HandleDirective(Token &Result) {
// not support this for #include-like directives, since that can result in
// terrible diagnostics, and does not work in GCC.
if (InMacroArgs) {
- IdentifierInfo *II = Result.getIdentifierInfo();
-
- // Certain #include-like and module-related directives never work inside
- // a macro argument list: supporting them would produce terrible
- // diagnostics and is already incompatible with GCC. They are always an
- // error regardless of language mode.
- auto IsAlwaysUnsupported = [](tok::PPKeywordKind K) {
- switch (K) {
+ if (IdentifierInfo *II = Result.getIdentifierInfo()) {
+ switch (II->getPPKeywordID()) {
case tok::pp_include:
case tok::pp_import:
case tok::pp_include_next:
@@ -1368,30 +1362,20 @@ void Preprocessor::HandleDirective(Token &Result) {
case tok::pp_module:
case tok::pp___preprocessed_module:
case tok::pp___preprocessed_import:
- return true;
+ Diag(Result, diag::err_embedded_directive)
+ << (getLangOpts().CPlusPlusModules &&
+ Introducer.isModuleContextualKeyword(
+ /*AllowExport=*/false))
+ << II->getName();
+ Diag(*ArgMacro, diag::note_macro_expansion_here)
+ << ArgMacro->getIdentifierInfo();
+ DiscardUntilEndOfDirective();
+ return;
default:
- return false;
+ break;
}
- };
-
- // [cpp.replace.general] makes any embedded directive ill-formed in
- // C++26; in earlier modes the construct is undefined behavior and only
- // pedantically warned about.
- const bool IsError = LangOpts.CPlusPlus26 ||
- (II && IsAlwaysUnsupported(II->getPPKeywordID()));
-
- if (!IsError) {
- Diag(Result, diag::ext_embedded_directive);
- } else {
- Diag(Result, diag::err_embedded_directive)
- << (LangOpts.CPlusPlusModules &&
- Introducer.isModuleContextualKeyword(/*AllowExport=*/false))
- << (II ? II->getName() : StringRef());
- Diag(*ArgMacro, diag::note_macro_expansion_here)
- << ArgMacro->getIdentifierInfo();
- DiscardUntilEndOfDirective();
- return;
}
+ Diag(Result, diag::ext_embedded_directive);
}
// Temporarily enable macro expansion if set so
diff --git a/clang/test/Preprocessor/p2843r3.cpp b/clang/test/Preprocessor/p2843r3.cpp
index 747fe59e25e45..58d06cb27a22f 100644
--- a/clang/test/Preprocessor/p2843r3.cpp
+++ b/clang/test/Preprocessor/p2843r3.cpp
@@ -24,14 +24,11 @@
#endif
// [cpp.replace.general] A preprocessing directive inside the arguments of a
-// function-like macro invocation. Promoted to a hard error in C++26.
+// function-like macro invocation. Still only diagnosed as a pedantic warning;
+// promoting this to a hard error is tracked separately.
#define FUNCTION_MACRO(...)
-// cxx26-note at +1 2 {{expansion of macro 'FUNCTION_MACRO' requested here}}
FUNCTION_MACRO(
- // cxx26-error at +2 {{embedding a #if directive within macro arguments is not supported}}
- // cxx23-warning at +1 {{embedding a directive within macro arguments has undefined behavior}}
- #if 0
- // cxx26-error at +1 {{embedding a #endif directive within macro arguments is not supported}}
+ #if 0 // expected-warning {{embedding a directive within macro arguments has undefined behavior}}
#endif
)
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 4a669ff4da8a1..0c20a51b7ce53 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -330,7 +330,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
<tr>
<td>Preprocessing is never undefined</td>
<td><a href="https://wg21.link/P2843">P2843R3</a></td>
- <td class="full" align="center">Clang 23</td>
+ <td class="partial" align="center">Partial</td>
</tr>
<!-- Kona, Fall 2025-->
<tr>
More information about the cfe-commits
mailing list