[Clang] Implement P2843R3 - Preprocessing is never undefined (#192073)

This PR marks [P2843R3 - Preprocessing is never
undefined](https://wg21.link/P2843) as implemented and add tests.

Fixes https://github.com/llvm/llvm-project/issues/145658

---------

Signed-off-by: yronglin <yronglin777@gmail.com>
This commit is contained in:
yronglin 2026-04-16 19:40:35 +08:00 committed by GitHub
parent 2b3965adc7
commit 42e0cdf2fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 80 additions and 10 deletions

View file

@ -133,6 +133,12 @@ C++ Language Changes
C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^
- 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
^^^^^^^^^^^^^^^^^^^^^

View file

@ -1060,6 +1060,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 {

View file

@ -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);

View file

@ -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 ---

View file

@ -0,0 +1,48 @@
// 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. 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 expansion that produces 'defined' in a conditional
// expression. P2843R3 makes this ill-formed; promoted to a hard error in
// C++26.
#define DEFINED defined
// cxx26-error@+2 {{macro expansion producing 'defined' is not allowed}}
// cxx23-warning@+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. Still only diagnosed as a pedantic warning;
// promoting this to a hard error is tracked separately.
#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@-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}}

View file

@ -330,7 +330,15 @@ C++23, informally referred to as C++26.</p>
<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="partial" align="center">
<details>
<summary>Partial</summary>
Clang diagnoses macro expansions producing <tt>defined</tt> in
conditional expressions in C++26, but embedded directives in macro
arguments and other constructs made ill-formed by this paper are not
yet fully implemented.
</details>
</td>
</tr>
<!-- Kona, Fall 2025-->
<tr>