libcpp: Implement C++23 P2334R1 - #elifdef/#elifndef

This patch implements C++23 P2334R1, which is easy because Joseph has done
all the hard work for C2X already.
Unlike the C N2645 paper, the C++ P2334R1 contains one important addition
(but not in the normative text):
"While this is a new preprocessor feature and cannot be treated as a defect
report, implementations that support older versions of the standard are
encouraged to implement this feature in the older language modes as well
as C++23."
so there are different variants how to implement it.
One is ignoring that sentence and only implementing it
for -std=c++23/-std=gnu++23 like it is only implemented for -std=c2x.
Another option would be to implement it also in the older GNU modes but
not in the C/CXX modes (but it would be strange if we did that just for
C++ and not for C).
Yet another option is to enable it unconditionally.
And yet another option would be to enable it unconditionally but emit
a warning (or pedwarn) when it is seen.
Note, when it is enabled for the older language modes, as Joseph wrote
in the c11-elifdef-1.c testcase, it can result e.g. in rejecting previously
valid code:
 #define A
 #undef B
 #if 0
 #elifdef A
 #error "#elifdef A applied"
 #endif
 #if 0
 #elifndef B
 #error "#elifndef B applied"
 #endif
Note, seems clang went the enable it unconditionally in all standard
versions of both C and C++, no warnings or anything whatsoever, so
essentially treated it as a DR that changed behavior of e.g. the above code.
After feedback, this option enables #elifdef/#elifndef for -std=c2x
and -std=c++2{b,3} and enables it also for -std=gnu*, but for GNU modes
older than C2X or C++23 if -pedantic it emits a pedwarn on the directives
that either would be rejected in the corresponding -std=c* modes, e.g.
  #if 1
  #elifdef A // pedwarn if -pedantic
  #endif
or when the directives would be silently accepted, but when they are
recognized it changes behavior, so e.g.
  #define A
  #if 0
  #elifdef A // pedwarn if -pedantic
  #define M 1
  #endif
It won't pedwarn if the directives would be silently ignored and wouldn't
change anything, like:
  #define A
  #if 0
  #elifndef A
  #define M 1
  #endif
or
  #undef B
  #if 0
  #elifdef B
  #define M 1
  #endif

2021-10-06  Jakub Jelinek  <jakub@redhat.com>

libcpp/
	* init.c (lang_defaults): Implement P2334R1, enable elifdef for
	-std=c++23 and -std=gnu++23.
	* directives.c (_cpp_handle_directive): Support elifdef/elifndef if
	either CPP_OPTION (pfile, elifdef) or !CPP_OPTION (pfile, std).
	(do_elif): For older non-std modes if pedantic pedwarn about
	#elifdef/#elifndef directives that change behavior.
gcc/testsuite/
	* gcc.dg/cpp/gnu11-elifdef-1.c: New test.
	* gcc.dg/cpp/gnu11-elifdef-2.c: New test.
	* gcc.dg/cpp/gnu11-elifdef-3.c: New test.
	* gcc.dg/cpp/gnu11-elifdef-4.c: New test.
	* g++.dg/cpp/elifdef-1.C: New test.
	* g++.dg/cpp/elifdef-2.C: New test.
	* g++.dg/cpp/elifdef-3.C: New test.
	* g++.dg/cpp/elifdef-4.C: New test.
	* g++.dg/cpp/elifdef-5.C: New test.
	* g++.dg/cpp/elifdef-6.C: New test.
	* g++.dg/cpp/elifdef-7.C: New test.
This commit is contained in:
Jakub Jelinek 2021-10-06 10:13:51 +02:00
parent ece8b0fce6
commit f43eb7707c
13 changed files with 508 additions and 4 deletions

View File

@ -0,0 +1,3 @@
// { dg-do preprocess { target { ! c++23 } } }
#include "../../gcc.dg/cpp/c11-elifdef-1.c"

View File

@ -0,0 +1,4 @@
// P2334R1
// { dg-do preprocess { target c++23 } }
#include "../../gcc.dg/cpp/c2x-elifdef-1.c"

View File

@ -0,0 +1,62 @@
// P2334R1
// { dg-do preprocess { target c++23 } }
#define A
#undef B
#elifdef A // { dg-error "#elifdef without #if" }
#elifdef B // { dg-error "#elifdef without #if" }
#elifndef A // { dg-error "#elifndef without #if" }
#elifndef B // { dg-error "#elifndef without #if" }
#if 1 // { dg-error "-:began here" }
#else
#elifdef A // { dg-error "#elifdef after #else" }
#endif
#if 1 // { dg-error "-:began here" }
#else
#elifdef B // { dg-error "#elifdef after #else" }
#endif
#if 1 // { dg-error "-:began here" }
#else
#elifndef A // { dg-error "#elifndef after #else" }
#endif
#if 1 // { dg-error "-:began here" }
#else
#elifndef B // { dg-error "#elifndef after #else" }
#endif
#if 0
#elifdef A = // { dg-error "extra tokens at end of #elifdef directive" }
#endif
#if 0
#elifdef B = // { dg-error "extra tokens at end of #elifdef directive" }
#endif
#if 0
#elifndef A = // { dg-error "extra tokens at end of #elifndef directive" }
#endif
#if 0
#elifndef B = // { dg-error "extra tokens at end of #elifndef directive" }
#endif
#if 0
#elifdef // { dg-error "no macro name given in #elifdef directive" }
#endif
#if 0
#elifndef // { dg-error "no macro name given in #elifndef directive" }
#endif
#if 0
#elifdef , // { dg-error "macro names must be identifiers" }
#endif
#if 0
#elifndef , // { dg-error "macro names must be identifiers" }
#endif

View File

@ -0,0 +1,5 @@
// P2334R1
// { dg-do preprocess }
// { dg-options "" }
#include "../../gcc.dg/cpp/c2x-elifdef-1.c"

View File

@ -0,0 +1,63 @@
// P2334R1
// { dg-do preprocess }
// { dg-options "" }
#define A
#undef B
#elifdef A // { dg-error "#elifdef without #if" }
#elifdef B // { dg-error "#elifdef without #if" }
#elifndef A // { dg-error "#elifndef without #if" }
#elifndef B // { dg-error "#elifndef without #if" }
#if 1 // { dg-error "-:began here" }
#else
#elifdef A // { dg-error "#elifdef after #else" }
#endif
#if 1 // { dg-error "-:began here" }
#else
#elifdef B // { dg-error "#elifdef after #else" }
#endif
#if 1 // { dg-error "-:began here" }
#else
#elifndef A // { dg-error "#elifndef after #else" }
#endif
#if 1 // { dg-error "-:began here" }
#else
#elifndef B // { dg-error "#elifndef after #else" }
#endif
#if 0
#elifdef A = // { dg-warning "extra tokens at end of #elifdef directive" }
#endif
#if 0
#elifdef B = // { dg-warning "extra tokens at end of #elifdef directive" }
#endif
#if 0
#elifndef A = // { dg-warning "extra tokens at end of #elifndef directive" }
#endif
#if 0
#elifndef B = // { dg-warning "extra tokens at end of #elifndef directive" }
#endif
#if 0
#elifdef // { dg-error "no macro name given in #elifdef directive" }
#endif
#if 0
#elifndef // { dg-error "no macro name given in #elifndef directive" }
#endif
#if 0
#elifdef , // { dg-error "macro names must be identifiers" }
#endif
#if 0
#elifndef , // { dg-error "macro names must be identifiers" }
#endif

View File

@ -0,0 +1,65 @@
// P2334R1
// { dg-do preprocess }
// { dg-options "-pedantic" }
#define A
#undef B
#if 0
#elifdef A // { dg-warning "#elifdef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#define M1 1
#endif
#if M1 != 1
#error "#elifdef A did not apply"
#endif
#if 0
#elifdef B
#error "#elifdef B applied"
#endif
#if 0
#elifndef A
#error "#elifndef A applied"
#endif
#if 0
#elifndef B // { dg-warning "#elifndef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#define M2 2
#endif
#if M2 != 2
#error "#elifndef B did not apply"
#endif
#if 0
#elifdef A // { dg-warning "#elifdef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#else
#error "#elifdef A did not apply"
#endif
#if 0
#elifndef B // { dg-warning "#elifndef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#else
#error "#elifndef B did not apply"
#endif
#if 1
#elifdef A // { dg-warning "#elifdef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#endif
#if 1
#elifndef B // { dg-warning "#elifndef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#endif
// As with #elif, the syntax of the new directives is relaxed after a
non-skipped group.
#if 1
#elifdef x * y // { dg-warning "#elifdef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#endif
#if 1
#elifndef ! // { dg-warning "#elifndef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#endif

View File

@ -0,0 +1,65 @@
// P2334R1
// { dg-do preprocess }
// { dg-options "-pedantic-errors" }
#define A
#undef B
#if 0
#elifdef A // { dg-error "#elifdef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#define M1 1
#endif
#if M1 != 1
#error "#elifdef A did not apply"
#endif
#if 0
#elifdef B
#error "#elifdef B applied"
#endif
#if 0
#elifndef A
#error "#elifndef A applied"
#endif
#if 0
#elifndef B // { dg-error "#elifndef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#define M2 2
#endif
#if M2 != 2
#error "#elifndef B did not apply"
#endif
#if 0
#elifdef A // { dg-error "#elifdef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#else
#error "#elifdef A did not apply"
#endif
#if 0
#elifndef B // { dg-error "#elifndef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#else
#error "#elifndef B did not apply"
#endif
#if 1
#elifdef A // { dg-error "#elifdef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#endif
#if 1
#elifndef B // { dg-error "#elifndef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#endif
// As with #elif, the syntax of the new directives is relaxed after a
non-skipped group.
#if 1
#elifdef x * y // { dg-error "#elifdef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#endif
#if 1
#elifndef ! // { dg-error "#elifndef before C\\\+\\\+23 is a GCC extension" "" { target c++20_down } }
#endif

View File

@ -0,0 +1,5 @@
/* Test #elifdef and #elifndef in GNU11. */
/* { dg-do preprocess } */
/* { dg-options "-std=gnu11" } */
#include "c2x-elifdef-1.c"

View File

@ -0,0 +1,63 @@
/* Test #elifdef and #elifndef in GNU11: erroneous usages. */
/* { dg-do preprocess } */
/* { dg-options "-std=gnu11" } */
#define A
#undef B
#elifdef A /* { dg-error "#elifdef without #if" } */
#elifdef B /* { dg-error "#elifdef without #if" } */
#elifndef A /* { dg-error "#elifndef without #if" } */
#elifndef B /* { dg-error "#elifndef without #if" } */
#if 1 /* { dg-error "-:began here" } */
#else
#elifdef A /* { dg-error "#elifdef after #else" } */
#endif
#if 1 /* { dg-error "-:began here" } */
#else
#elifdef B /* { dg-error "#elifdef after #else" } */
#endif
#if 1 /* { dg-error "-:began here" } */
#else
#elifndef A /* { dg-error "#elifndef after #else" } */
#endif
#if 1 /* { dg-error "-:began here" } */
#else
#elifndef B /* { dg-error "#elifndef after #else" } */
#endif
#if 0
#elifdef A = /* { dg-warning "extra tokens at end of #elifdef directive" } */
#endif
#if 0
#elifdef B = /* { dg-warning "extra tokens at end of #elifdef directive" } */
#endif
#if 0
#elifndef A = /* { dg-warning "extra tokens at end of #elifndef directive" } */
#endif
#if 0
#elifndef B = /* { dg-warning "extra tokens at end of #elifndef directive" } */
#endif
#if 0
#elifdef /* { dg-error "no macro name given in #elifdef directive" } */
#endif
#if 0
#elifndef /* { dg-error "no macro name given in #elifndef directive" } */
#endif
#if 0
#elifdef , /* { dg-error "macro names must be identifiers" } */
#endif
#if 0
#elifndef , /* { dg-error "macro names must be identifiers" } */
#endif

View File

@ -0,0 +1,65 @@
/* Test #elifdef and #elifndef in GNU11. */
/* { dg-do preprocess } */
/* { dg-options "-std=gnu11 -pedantic" } */
#define A
#undef B
#if 0
#elifdef A /* { dg-warning "#elifdef before C2X is a GCC extension" } */
#define M1 1
#endif
#if M1 != 1
#error "#elifdef A did not apply"
#endif
#if 0
#elifdef B
#error "#elifdef B applied"
#endif
#if 0
#elifndef A
#error "#elifndef A applied"
#endif
#if 0
#elifndef B /* { dg-warning "#elifndef before C2X is a GCC extension" } */
#define M2 2
#endif
#if M2 != 2
#error "#elifndef B did not apply"
#endif
#if 0
#elifdef A /* { dg-warning "#elifdef before C2X is a GCC extension" } */
#else
#error "#elifdef A did not apply"
#endif
#if 0
#elifndef B /* { dg-warning "#elifndef before C2X is a GCC extension" } */
#else
#error "#elifndef B did not apply"
#endif
#if 1
#elifdef A /* { dg-warning "#elifdef before C2X is a GCC extension" } */
#endif
#if 1
#elifndef B /* { dg-warning "#elifndef before C2X is a GCC extension" } */
#endif
/* As with #elif, the syntax of the new directives is relaxed after a
non-skipped group. */
#if 1
#elifdef x * y /* { dg-warning "#elifdef before C2X is a GCC extension" } */
#endif
#if 1
#elifndef ! /* { dg-warning "#elifndef before C2X is a GCC extension" } */
#endif

View File

@ -0,0 +1,65 @@
/* Test #elifdef and #elifndef in GNU11. */
/* { dg-do preprocess } */
/* { dg-options "-std=gnu11 -pedantic-errors" } */
#define A
#undef B
#if 0
#elifdef A /* { dg-error "#elifdef before C2X is a GCC extension" } */
#define M1 1
#endif
#if M1 != 1
#error "#elifdef A did not apply"
#endif
#if 0
#elifdef B
#error "#elifdef B applied"
#endif
#if 0
#elifndef A
#error "#elifndef A applied"
#endif
#if 0
#elifndef B /* { dg-error "#elifndef before C2X is a GCC extension" } */
#define M2 2
#endif
#if M2 != 2
#error "#elifndef B did not apply"
#endif
#if 0
#elifdef A /* { dg-error "#elifdef before C2X is a GCC extension" } */
#else
#error "#elifdef A did not apply"
#endif
#if 0
#elifndef B /* { dg-error "#elifndef before C2X is a GCC extension" } */
#else
#error "#elifndef B did not apply"
#endif
#if 1
#elifdef A /* { dg-error "#elifdef before C2X is a GCC extension" } */
#endif
#if 1
#elifndef B /* { dg-error "#elifndef before C2X is a GCC extension" } */
#endif
/* As with #elif, the syntax of the new directives is relaxed after a
non-skipped group. */
#if 1
#elifdef x * y /* { dg-error "#elifdef before C2X is a GCC extension" } */
#endif
#if 1
#elifndef ! /* { dg-error "#elifndef before C2X is a GCC extension" } */
#endif

View File

@ -447,7 +447,11 @@ _cpp_handle_directive (cpp_reader *pfile, bool indented)
if (dname->val.node.node->is_directive)
{
dir = &dtable[dname->val.node.node->directive_index];
if ((dir->flags & ELIFDEF) && !CPP_OPTION (pfile, elifdef))
if ((dir->flags & ELIFDEF)
&& !CPP_OPTION (pfile, elifdef)
/* For -std=gnu* modes elifdef is supported with
a pedwarn if pedantic. */
&& CPP_OPTION (pfile, std))
dir = 0;
}
}
@ -2117,7 +2121,26 @@ do_elif (cpp_reader *pfile)
are skipped and their controlling directives are processed as
if they were in a group that is skipped." */
if (ifs->skip_elses)
pfile->state.skipping = 1;
{
/* In older GNU standards, #elifdef/#elifndef is supported
as an extension, but pedwarn if -pedantic if the presence
of the directive would be rejected. */
if (pfile->directive != &dtable[T_ELIF]
&& ! CPP_OPTION (pfile, elifdef)
&& CPP_PEDANTIC (pfile)
&& !pfile->state.skipping)
{
if (CPP_OPTION (pfile, cplusplus))
cpp_error (pfile, CPP_DL_PEDWARN,
"#%s before C++23 is a GCC extension",
pfile->directive->name);
else
cpp_error (pfile, CPP_DL_PEDWARN,
"#%s before C2X is a GCC extension",
pfile->directive->name);
}
pfile->state.skipping = 1;
}
else
{
if (pfile->directive == &dtable[T_ELIF])
@ -2139,6 +2162,22 @@ do_elif (cpp_reader *pfile)
if (pfile->cb.used)
pfile->cb.used (pfile, pfile->directive_line, node);
check_eol (pfile, false);
/* In older GNU standards, #elifdef/#elifndef is supported
as an extension, but pedwarn if -pedantic if the presence
of the directive would change behavior. */
if (! CPP_OPTION (pfile, elifdef)
&& CPP_PEDANTIC (pfile)
&& pfile->state.skipping != skip)
{
if (CPP_OPTION (pfile, cplusplus))
cpp_error (pfile, CPP_DL_PEDWARN,
"#%s before C++23 is a GCC extension",
pfile->directive->name);
else
cpp_error (pfile, CPP_DL_PEDWARN,
"#%s before C2X is a GCC extension",
pfile->directive->name);
}
pfile->state.skipping = skip;
}
}

View File

@ -122,8 +122,8 @@ static const struct lang_flags lang_defaults[] =
/* CXX17 */ { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0 },
/* GNUCXX20 */ { 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0 },
/* CXX20 */ { 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0 },
/* GNUCXX23 */ { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0 },
/* CXX23 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0 },
/* GNUCXX23 */ { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1 },
/* CXX23 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1 },
/* ASM */ { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};