Blame SOURCES/gcc11-stringify-__VA_OPT__.patch

e60d6e
c++: Add C++20 #__VA_OPT__ support
e60d6e
e60d6e
The following patch implements C++20 # __VA_OPT__ (...) support.
e60d6e
Testcases cover what I came up with myself and what LLVM has for #__VA_OPT__
e60d6e
in its testsuite and the string literals are identical between the two
e60d6e
compilers on the va-opt-5.c testcase.
e60d6e
e60d6e
2021-08-17  Jakub Jelinek  <jakub@redhat.com>
e60d6e
e60d6e
libcpp/
e60d6e
	* macro.c (vaopt_state): Add m_stringify member.
e60d6e
	(vaopt_state::vaopt_state): Initialize it.
e60d6e
	(vaopt_state::update): Overwrite it.
e60d6e
	(vaopt_state::stringify): New method.
e60d6e
	(stringify_arg): Replace arg argument with first, count arguments
e60d6e
	and add va_opt argument.  Use first instead of arg->first and
e60d6e
	count instead of arg->count, for va_opt add paste_tokens handling.
e60d6e
	(paste_tokens): Fix up len calculation.  Don't spell rhs twice,
e60d6e
	instead use %.*s to supply lhs and rhs spelling lengths.  Don't call
e60d6e
	_cpp_backup_tokens here.
e60d6e
	(paste_all_tokens): Call it here instead.
e60d6e
	(replace_args): Adjust stringify_arg caller.  For vaopt_state::END
e60d6e
	if stringify is true handle __VA_OPT__ stringification.
e60d6e
	(create_iso_definition): Handle # __VA_OPT__ similarly to # macro_arg.
e60d6e
gcc/testsuite/
e60d6e
	* c-c++-common/cpp/va-opt-5.c: New test.
e60d6e
	* c-c++-common/cpp/va-opt-6.c: New test.
e60d6e
e60d6e
--- libcpp/macro.c
e60d6e
+++ libcpp/macro.c
e60d6e
@@ -118,6 +118,7 @@ class vaopt_state {
e60d6e
     m_arg (arg),
e60d6e
     m_variadic (is_variadic),
e60d6e
     m_last_was_paste (false),
e60d6e
+    m_stringify (false),
e60d6e
     m_state (0),
e60d6e
     m_paste_location (0),
e60d6e
     m_location (0),
e60d6e
@@ -145,6 +146,7 @@ class vaopt_state {
e60d6e
 	  }
e60d6e
 	++m_state;
e60d6e
 	m_location = token->src_loc;
e60d6e
+	m_stringify = (token->flags & STRINGIFY_ARG) != 0;
e60d6e
 	return BEGIN;
e60d6e
       }
e60d6e
     else if (m_state == 1)
e60d6e
@@ -234,6 +236,12 @@ class vaopt_state {
e60d6e
     return m_state == 0;
e60d6e
   }
e60d6e
 
e60d6e
+  /* Return true for # __VA_OPT__.  */
e60d6e
+  bool stringify () const
e60d6e
+  {
e60d6e
+    return m_stringify;
e60d6e
+  }
e60d6e
+
e60d6e
  private:
e60d6e
 
e60d6e
   /* The cpp_reader.  */
e60d6e
@@ -247,6 +255,8 @@ class vaopt_state {
e60d6e
   /* If true, the previous token was ##.  This is used to detect when
e60d6e
      a paste occurs at the end of the sequence.  */
e60d6e
   bool m_last_was_paste;
e60d6e
+  /* True for #__VA_OPT__.  */
e60d6e
+  bool m_stringify;
e60d6e
 
e60d6e
   /* The state variable:
e60d6e
      0 means not parsing
e60d6e
@@ -284,7 +294,8 @@ static _cpp_buff *collect_args (cpp_read
e60d6e
 static cpp_context *next_context (cpp_reader *);
e60d6e
 static const cpp_token *padding_token (cpp_reader *, const cpp_token *);
e60d6e
 static const cpp_token *new_string_token (cpp_reader *, uchar *, unsigned int);
e60d6e
-static const cpp_token *stringify_arg (cpp_reader *, macro_arg *);
e60d6e
+static const cpp_token *stringify_arg (cpp_reader *, const cpp_token **,
e60d6e
+				       unsigned int, bool);
e60d6e
 static void paste_all_tokens (cpp_reader *, const cpp_token *);
e60d6e
 static bool paste_tokens (cpp_reader *, location_t,
e60d6e
 			  const cpp_token **, const cpp_token *);
e60d6e
@@ -812,10 +823,11 @@ cpp_quote_string (uchar *dest, const uch
e60d6e
   return dest;
e60d6e
 }
e60d6e
 
e60d6e
-/* Convert a token sequence ARG to a single string token according to
e60d6e
-   the rules of the ISO C #-operator.  */
e60d6e
+/* Convert a token sequence FIRST to FIRST+COUNT-1 to a single string token
e60d6e
+   according to the rules of the ISO C #-operator.  */
e60d6e
 static const cpp_token *
e60d6e
-stringify_arg (cpp_reader *pfile, macro_arg *arg)
e60d6e
+stringify_arg (cpp_reader *pfile, const cpp_token **first, unsigned int count,
e60d6e
+	       bool va_opt)
e60d6e
 {
e60d6e
   unsigned char *dest;
e60d6e
   unsigned int i, escape_it, backslash_count = 0;
e60d6e
@@ -828,9 +840,27 @@ stringify_arg (cpp_reader *pfile, macro_
e60d6e
   *dest++ = '"';
e60d6e
 
e60d6e
   /* Loop, reading in the argument's tokens.  */
e60d6e
-  for (i = 0; i < arg->count; i++)
e60d6e
+  for (i = 0; i < count; i++)
e60d6e
     {
e60d6e
-      const cpp_token *token = arg->first[i];
e60d6e
+      const cpp_token *token = first[i];
e60d6e
+
e60d6e
+      if (va_opt && (token->flags & PASTE_LEFT))
e60d6e
+	{
e60d6e
+	  location_t virt_loc = pfile->invocation_location;
e60d6e
+	  const cpp_token *rhs;
e60d6e
+	  do
e60d6e
+	    {
e60d6e
+	      if (i == count)
e60d6e
+		abort ();
e60d6e
+	      rhs = first[++i];
e60d6e
+	      if (!paste_tokens (pfile, virt_loc, &token, rhs))
e60d6e
+		{
e60d6e
+		  --i;
e60d6e
+		  break;
e60d6e
+		}
e60d6e
+	    }
e60d6e
+	  while (rhs->flags & PASTE_LEFT);
e60d6e
+	}
e60d6e
 
e60d6e
       if (token->type == CPP_PADDING)
e60d6e
 	{
e60d6e
@@ -917,7 +947,7 @@ paste_tokens (cpp_reader *pfile, locatio
e60d6e
   cpp_token *lhs;
e60d6e
   unsigned int len;
e60d6e
 
e60d6e
-  len = cpp_token_len (*plhs) + cpp_token_len (rhs) + 1;
e60d6e
+  len = cpp_token_len (*plhs) + cpp_token_len (rhs) + 2;
e60d6e
   buf = (unsigned char *) alloca (len);
e60d6e
   end = lhsend = cpp_spell_token (pfile, *plhs, buf, true);
e60d6e
 
e60d6e
@@ -943,8 +973,10 @@ paste_tokens (cpp_reader *pfile, locatio
e60d6e
       location_t saved_loc = lhs->src_loc;
e60d6e
 
e60d6e
       _cpp_pop_buffer (pfile);
e60d6e
-      _cpp_backup_tokens (pfile, 1);
e60d6e
-      *lhsend = '\0';
e60d6e
+
e60d6e
+      unsigned char *rhsstart = lhsend;
e60d6e
+      if ((*plhs)->type == CPP_DIV && rhs->type != CPP_EQ)
e60d6e
+	rhsstart++;
e60d6e
 
e60d6e
       /* We have to remove the PASTE_LEFT flag from the old lhs, but
e60d6e
 	 we want to keep the new location.  */
e60d6e
@@ -956,8 +988,10 @@ paste_tokens (cpp_reader *pfile, locatio
e60d6e
       /* Mandatory error for all apart from assembler.  */
e60d6e
       if (CPP_OPTION (pfile, lang) != CLK_ASM)
e60d6e
 	cpp_error_with_line (pfile, CPP_DL_ERROR, location, 0,
e60d6e
-	 "pasting \"%s\" and \"%s\" does not give a valid preprocessing token",
e60d6e
-		   buf, cpp_token_as_text (pfile, rhs));
e60d6e
+			     "pasting \"%.*s\" and \"%.*s\" does not give "
e60d6e
+			     "a valid preprocessing token",
e60d6e
+			     (int) (lhsend - buf), buf,
e60d6e
+			     (int) (end - rhsstart), rhsstart);
e60d6e
       return false;
e60d6e
     }
e60d6e
 
e60d6e
@@ -1033,7 +1067,10 @@ paste_all_tokens (cpp_reader *pfile, con
e60d6e
 	    abort ();
e60d6e
 	}
e60d6e
       if (!paste_tokens (pfile, virt_loc, &lhs, rhs))
e60d6e
-	break;
e60d6e
+	{
e60d6e
+	  _cpp_backup_tokens (pfile, 1);
e60d6e
+	  break;
e60d6e
+	}
e60d6e
     }
e60d6e
   while (rhs->flags & PASTE_LEFT);
e60d6e
 
e60d6e
@@ -1900,7 +1937,8 @@ replace_args (cpp_reader *pfile, cpp_has
e60d6e
 	if (src->flags & STRINGIFY_ARG)
e60d6e
 	  {
e60d6e
 	    if (!arg->stringified)
e60d6e
-	      arg->stringified = stringify_arg (pfile, arg);
e60d6e
+	      arg->stringified = stringify_arg (pfile, arg->first, arg->count,
e60d6e
+						false);
e60d6e
 	  }
e60d6e
 	else if ((src->flags & PASTE_LEFT)
e60d6e
 		 || (src != macro->exp.tokens && (src[-1].flags & PASTE_LEFT)))
e60d6e
@@ -2023,6 +2061,24 @@ replace_args (cpp_reader *pfile, cpp_has
e60d6e
 		  paste_flag = tokens_buff_last_token_ptr (buff);
e60d6e
 		}
e60d6e
 
e60d6e
+	      if (vaopt_tracker.stringify ())
e60d6e
+		{
e60d6e
+		  unsigned int count
e60d6e
+		    = start ? paste_flag - start : tokens_buff_count (buff);
e60d6e
+		  const cpp_token *t
e60d6e
+		    = stringify_arg (pfile,
e60d6e
+				     start ? start + 1
e60d6e
+				     : (const cpp_token **) (buff->base),
e60d6e
+				     count, true);
e60d6e
+		  while (count--)
e60d6e
+		    tokens_buff_remove_last_token (buff);
e60d6e
+		  if (src->flags & PASTE_LEFT)
e60d6e
+		    copy_paste_flag (pfile, &t, src);
e60d6e
+		  tokens_buff_add_token (buff, virt_locs,
e60d6e
+					 t, t->src_loc, t->src_loc,
e60d6e
+					 NULL, 0);
e60d6e
+		  continue;
e60d6e
+		}
e60d6e
 	      if (start && paste_flag == start && (*start)->flags & PASTE_LEFT)
e60d6e
 		/* If __VA_OPT__ expands to nothing (either because __VA_ARGS__
e60d6e
 		   is empty or because it is __VA_OPT__() ), drop PASTE_LEFT
e60d6e
@@ -3584,7 +3640,10 @@ create_iso_definition (cpp_reader *pfile
e60d6e
 	 function-like macros when lexing the subsequent token.  */
e60d6e
       if (macro->count > 1 && token[-1].type == CPP_HASH && macro->fun_like)
e60d6e
 	{
e60d6e
-	  if (token->type == CPP_MACRO_ARG)
e60d6e
+	  if (token->type == CPP_MACRO_ARG
e60d6e
+	      || (macro->variadic
e60d6e
+		  && token->type == CPP_NAME
e60d6e
+		  && token->val.node.node == pfile->spec_nodes.n__VA_OPT__))
e60d6e
 	    {
e60d6e
 	      if (token->flags & PREV_WHITE)
e60d6e
 		token->flags |= SP_PREV_WHITE;
e60d6e
--- gcc/testsuite/c-c++-common/cpp/va-opt-5.c
e60d6e
+++ gcc/testsuite/c-c++-common/cpp/va-opt-5.c
e60d6e
@@ -0,0 +1,67 @@
e60d6e
+/* { dg-do run } */
e60d6e
+/* { dg-options "-std=gnu99" { target c } } */
e60d6e
+/* { dg-options "-std=c++20" { target c++ } } */
e60d6e
+
e60d6e
+#define lparen (
e60d6e
+#define a0 fooa0
e60d6e
+#define a1  fooa1 a0
e60d6e
+#define a2  fooa2 a1
e60d6e
+#define a3  fooa3 a2
e60d6e
+#define a() b lparen )
e60d6e
+#define b() c lparen )
e60d6e
+#define c() d lparen )
e60d6e
+#define g h
e60d6e
+#define i(j) j
e60d6e
+#define f(...) #__VA_OPT__(g i(0))
e60d6e
+#define k(x,...) # __VA_OPT__(x) #x #__VA_OPT__(__VA_ARGS__)
e60d6e
+#define l(x,...) #__VA_OPT__(a1 x)
e60d6e
+#define m(x,...) "a()" #__VA_OPT__(a3 __VA_ARGS__ x ## __VA_ARGS__ ## x ## c a3) "a()"
e60d6e
+#define n(x,...) = #__VA_OPT__(a3 __VA_ARGS__ x ## __VA_ARGS__ ## x ## c a3) #x #__VA_OPT__(a0 __VA_ARGS__ x ## __VA_ARGS__ ## x ## c a0) ;
e60d6e
+#define o(x, ...) #__VA_OPT__(x##x x##x)
e60d6e
+#define p(x, ...) #__VA_OPT__(_Pragma ("foobar"))
e60d6e
+#define q(...) #__VA_OPT__(/* foo */x/* bar */)
e60d6e
+const char *v1 = f();
e60d6e
+const char *v2 = f(123);
e60d6e
+const char *v3 = k(1);
e60d6e
+const char *v4 = k(1, 2, 3 );
e60d6e
+const char *v5 = l(a());
e60d6e
+const char *v6 = l(a1 a(), 1);
e60d6e
+const char *v7 = m();
e60d6e
+const char *v8 = m(,);
e60d6e
+const char *v9 = m(,a3);
e60d6e
+const char *v10 = m(a3,a(),a0);
e60d6e
+const char *v11 n()
e60d6e
+const char *v12 n(,)
e60d6e
+const char *v13 n(,a0)
e60d6e
+const char *v14 n(a0, a(),a0)
e60d6e
+const char *v15 = o(, 0);
e60d6e
+const char *v16 = p(0);
e60d6e
+const char *v17 = p(0, 1);
e60d6e
+const char *v18 = q();
e60d6e
+const char *v19 = q(1);
e60d6e
+
e60d6e
+int
e60d6e
+main ()
e60d6e
+{
e60d6e
+  if (__builtin_strcmp (v1, "")
e60d6e
+      || __builtin_strcmp (v2, "g i(0)")
e60d6e
+      || __builtin_strcmp (v3, "1")
e60d6e
+      || __builtin_strcmp (v4, "112, 3")
e60d6e
+      || __builtin_strcmp (v5, "")
e60d6e
+      || __builtin_strcmp (v6, "a1 fooa1 fooa0 b ( )")
e60d6e
+      || __builtin_strcmp (v7, "a()a()")
e60d6e
+      || __builtin_strcmp (v8, "a()a()")
e60d6e
+      || __builtin_strcmp (v9, "a()a3 fooa3 fooa2 fooa1 fooa0 a3c a3a()")
e60d6e
+      || __builtin_strcmp (v10, "a()a3 b ( ),fooa0 a3a(),a0a3c a3a()")
e60d6e
+      || __builtin_strcmp (v11, "")
e60d6e
+      || __builtin_strcmp (v12, "")
e60d6e
+      || __builtin_strcmp (v13, "a3 fooa0 a0c a3a0 fooa0 a0c a0")
e60d6e
+      || __builtin_strcmp (v14, "a3 b ( ),fooa0 a0a(),a0a0c a3a0a0 b ( ),fooa0 a0a(),a0a0c a0")
e60d6e
+      || __builtin_strcmp (v15, "")
e60d6e
+      || __builtin_strcmp (v16, "")
e60d6e
+      || __builtin_strcmp (v17, "_Pragma (\"foobar\")")
e60d6e
+      || __builtin_strcmp (v18, "")
e60d6e
+      || __builtin_strcmp (v19, "x"))
e60d6e
+    __builtin_abort ();
e60d6e
+  return 0;
e60d6e
+}
e60d6e
--- gcc/testsuite/c-c++-common/cpp/va-opt-6.c
e60d6e
+++ gcc/testsuite/c-c++-common/cpp/va-opt-6.c
e60d6e
@@ -0,0 +1,17 @@
e60d6e
+/* { dg-do preprocess } */
e60d6e
+/* { dg-options "-std=gnu99" { target c } } */
e60d6e
+/* { dg-options "-std=c++20" { target c++ } } */
e60d6e
+
e60d6e
+#define a ""
e60d6e
+#define b(...) a ## #__VA_OPT__(1)	/* { dg-error "pasting \"a\" and \"\"\"\" does not give a valid preprocessing token" } */
e60d6e
+#define c(...) a ## #__VA_OPT__(1)	/* { dg-error "pasting \"a\" and \"\"1\"\" does not give a valid preprocessing token" } */
e60d6e
+#define d(...) #__VA_OPT__(1) ## !
e60d6e
+#define e(...) #__VA_OPT__(1) ## !
e60d6e
+#define f(...) #__VA_OPT__(. ## !)
e60d6e
+#define g(...) #__VA_OPT__(. ## !)
e60d6e
+b()
e60d6e
+c(1)
e60d6e
+d(   )		/* { dg-error "pasting \"\"\"\" and \"!\" does not give a valid preprocessing token" } */
e60d6e
+e(  1 )		/* { dg-error "pasting \"\"1\"\" and \"!\" does not give a valid preprocessing token" } */
e60d6e
+f()
e60d6e
+g(0)		/* { dg-error "pasting \".\" and \"!\" does not give a valid preprocessing token" } */