diff mbox series

c++: fix parsing with auto(x) [PR112410]

Message ID 20231109195835.429291-1-polacek@redhat.com
State New
Headers show
Series c++: fix parsing with auto(x) [PR112410] | expand

Commit Message

Marek Polacek Nov. 9, 2023, 7:58 p.m. UTC
Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
Here we are wrongly parsing

  int y(auto(42));

which uses the C++23 cast-to-prvalue feature, and initializes y to 42.
However, we were treating the auto as an implicit template parameter.

Fixing the auto{42} case is easy, but when auto is followed by a (,
I found the fix to be much more involved.  For instance, we cannot
use cp_parser_expression, because that can give hard errors.  It's
also necessary to disambiguate 'auto(i)' as 'auto i', not a cast.
auto(), auto(int), auto(f)(int), auto(*), auto(i[]), auto(...), etc.
are all function declarations.  We have to look at more than one
token to decide.

In this fix, I'm (ab)using cp_parser_declarator, with member_p=false
so that it doesn't commit.  But it handles even more complicated
cases as

  int fn (auto (*const **&f)(int) -> char);

	PR c++/112410

gcc/cp/ChangeLog:

	* parser.cc (cp_parser_simple_type_specifier): Disambiguate
	between a variable and function declaration with auto.
	(cp_parser_constructor_declarator_p): Use cp_parser_starts_param_decl_p.
	(cp_parser_starts_param_decl_p): New, factored out of
	cp_parser_constructor_declarator_p.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp23/auto-fncast13.C: New test.
---
 gcc/cp/parser.cc                           | 79 ++++++++++++++++++----
 gcc/testsuite/g++.dg/cpp23/auto-fncast13.C | 61 +++++++++++++++++
 2 files changed, 125 insertions(+), 15 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp23/auto-fncast13.C


base-commit: 016b3002e13acefb5773da78659c050187d3a96f

Comments

Jason Merrill Nov. 10, 2023, 12:07 a.m. UTC | #1
On 11/9/23 14:58, Marek Polacek wrote:
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
> 
> -- >8 --
> Here we are wrongly parsing
> 
>    int y(auto(42));
> 
> which uses the C++23 cast-to-prvalue feature, and initializes y to 42.
> However, we were treating the auto as an implicit template parameter.
> 
> Fixing the auto{42} case is easy, but when auto is followed by a (,
> I found the fix to be much more involved.  For instance, we cannot
> use cp_parser_expression, because that can give hard errors.  It's
> also necessary to disambiguate 'auto(i)' as 'auto i', not a cast.
> auto(), auto(int), auto(f)(int), auto(*), auto(i[]), auto(...), etc.
> are all function declarations.  We have to look at more than one
> token to decide.

Yeah, this is a most vexing parse problem.  The code is synthesizing 
template parameters before we've resolved whether the auto is a 
decl-specifier or not.

> In this fix, I'm (ab)using cp_parser_declarator, with member_p=false
> so that it doesn't commit.  But it handles even more complicated
> cases as
> 
>    int fn (auto (*const **&f)(int) -> char);

But it doesn't seem to handle the extremely vexing

struct A {
   A(int,int);
};

int main()
{
   int a;
   A b(auto(a), 42);
}

I think we need to stop synthesizing immediately when we see RID_AUTO, 
and instead go back after we successfully parse a declaration and 
synthesize for any autos we saw along the way.  :/

Jason
diff mbox series

Patch

diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 5116bcb78f6..3edee092e56 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -2887,6 +2887,8 @@  static bool cp_parser_next_token_ends_template_argument_p
   (cp_parser *);
 static bool cp_parser_nth_token_starts_template_argument_list_p
   (cp_parser *, size_t);
+static bool cp_parser_starts_param_decl_p
+  (cp_parser *);
 static enum tag_types cp_parser_token_is_class_key
   (cp_token *);
 static enum tag_types cp_parser_token_is_type_parameter_key
@@ -19991,6 +19993,8 @@  cp_parser_simple_type_specifier (cp_parser* parser,
 	  /* The 'auto' might be the placeholder return type for a function decl
 	     with trailing return type.  */
 	  bool have_trailing_return_fn_decl = false;
+	  /* Or it might be auto(x) or auto {x}.  */
+	  bool decay_copy = false;
 
 	  cp_parser_parse_tentatively (parser);
 	  cp_lexer_consume_token (parser->lexer);
@@ -20002,12 +20006,43 @@  cp_parser_simple_type_specifier (cp_parser* parser,
 	      if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_PAREN))
 		{
 		  cp_lexer_consume_token (parser->lexer);
+		  /* An auto specifier that appears in a parameter declaration
+		     might be the placeholder for a late return type, or it
+		     can be an implicit template parameter.  But it can also
+		     be a prvalue cast, rendering the current construct not
+		     a function declaration at all.  Check if it decidedly
+		     cannot be a valid function-style cast first...  */
+		  if (!cp_parser_starts_param_decl_p (parser))
+		    {
+		      /* Ug, we couldn't tell.  Try to parse whatever follows
+			 as a declarator; this should detect cases like
+			 auto(i), auto(*), auto(f[]), auto(f)(int).  */
+		      cp_parser_declarator (parser, CP_PARSER_DECLARATOR_EITHER,
+					    CP_PARSER_FLAGS_NONE,
+					    /*ctor_dtor_or_conv_p=*/nullptr,
+					    /*parenthesized_p=*/NULL,
+					    /*member_p=*/true,
+					    /*friend_p=*/false,
+					    /*static_p=*/true);
+		      /* OK, if we now see a ')', it looks like a valid
+			 function declaration.  Otherwise, let's go with
+			 auto(x).  */
+		      decay_copy
+			= cp_lexer_next_token_is_not (parser->lexer,
+						      CPP_CLOSE_PAREN);
+		    }
 		  cp_parser_skip_to_closing_parenthesis (parser,
 							 /*recovering*/false,
 							 /*or_comma*/false,
 							 /*consume_paren*/true);
 		  continue;
 		}
+	      /* The easy case: it has got to be C++23 auto(x).  */
+	      else if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE))
+		{
+		  decay_copy = true;
+		  break;
+		}
 
 	      if (cp_lexer_next_token_is (parser->lexer, CPP_DEREF))
 		{
@@ -20019,6 +20054,12 @@  cp_parser_simple_type_specifier (cp_parser* parser,
 	    }
 	  cp_parser_abort_tentative_parse (parser);
 
+	  if (decay_copy)
+	    {
+	      type = error_mark_node;
+	      break;
+	    }
+
 	  if (have_trailing_return_fn_decl)
 	    {
 	      type = make_auto ();
@@ -32066,21 +32107,7 @@  cp_parser_constructor_declarator_p (cp_parser *parser, cp_parser_flags flags,
 	  && !cp_parser_require (parser, CPP_OPEN_PAREN, RT_OPEN_PAREN))
 	constructor_p = false;
 
-      if (constructor_p
-	  && cp_lexer_next_token_is_not (parser->lexer, CPP_CLOSE_PAREN)
-	  && cp_lexer_next_token_is_not (parser->lexer, CPP_ELLIPSIS)
-	  /* A parameter declaration begins with a decl-specifier,
-	     which is either the "attribute" keyword, a storage class
-	     specifier, or (usually) a type-specifier.  */
-	  && !cp_lexer_next_token_is_decl_specifier_keyword (parser->lexer)
-	  /* GNU attributes can actually appear both at the start of
-	     a parameter and parenthesized declarator.
-	     S (__attribute__((unused)) int);
-	     is a constructor, but
-	     S (__attribute__((unused)) foo) (int);
-	     is a function declaration. [[attribute]] can appear in the
-	     first form too, but not in the second form.  */
-	  && !cp_next_tokens_can_be_std_attribute_p (parser))
+      if (constructor_p && !cp_parser_starts_param_decl_p (parser))
 	{
 	  tree type;
 	  tree pushed_scope = NULL_TREE;
@@ -34334,6 +34361,28 @@  cp_parser_nth_token_starts_template_argument_list_p (cp_parser * parser,
   return false;
 }
 
+/* We've consumed a '(' and now we're asking if what follows starts
+   a parameter declaration.  Return true if it does.  */
+
+static bool
+cp_parser_starts_param_decl_p (cp_parser *parser)
+{
+  return (cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_PAREN)
+	  || cp_lexer_next_token_is (parser->lexer, CPP_ELLIPSIS)
+	  /* A parameter declaration begins with a decl-specifier,
+	     which is either the "attribute" keyword, a storage class
+	     specifier, or (usually) a type-specifier.  */
+	  || cp_lexer_next_token_is_decl_specifier_keyword (parser->lexer)
+	  /* GNU attributes can actually appear both at the start of
+	     a parameter and parenthesized declarator.
+	     S (__attribute__((unused)) int);
+	     is a constructor, but
+	     S (__attribute__((unused)) foo) (int);
+	     is a function declaration.  [[attribute]] can appear in the
+	     first form too, but not in the second form.  */
+	  || cp_next_tokens_can_be_std_attribute_p (parser));
+}
+
 /* Returns the kind of tag indicated by TOKEN, if it is a class-key,
    or none_type otherwise.  */
 
diff --git a/gcc/testsuite/g++.dg/cpp23/auto-fncast13.C b/gcc/testsuite/g++.dg/cpp23/auto-fncast13.C
new file mode 100644
index 00000000000..1bceffb70cf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/auto-fncast13.C
@@ -0,0 +1,61 @@ 
+// PR c++/112410
+// { dg-do compile { target c++23 } }
+
+int f1 (auto(int) -> char);
+int f2 (auto x);
+int f3 (auto);
+int f4 (auto(i));
+
+int v1 (auto(42));
+int v2 (auto{42});
+int e1 (auto{i}); // { dg-error "not declared" }
+int i;
+int v3 (auto{i});
+int v4 (auto(i + 1));
+int v5 (auto(+i));
+int v6 (auto(i = 4));
+
+int f5 (auto(i));
+int f6 (auto());
+int f7 (auto(int));
+int f8 (auto(f)(int));
+int f9 (auto(...) -> char);
+// FIXME: ICEs (PR c++/89867)
+//int f10 (auto(__attribute__((unused)) i));
+int f11 (auto((i)));
+int f12 (auto(i[]));
+int f13 (auto(*i));
+int f14 (auto(*));
+
+int e2 (auto{}); // { dg-error "invalid use of .auto." }
+int e3 (auto(i, i)); // { dg-error "invalid use of .auto." }
+
+char bar (int);
+char baz ();
+char qux (...);
+
+void
+g (int i)
+{
+  f1 (bar);
+  f2 (42);
+  f3 (42);
+  f4 (42);
+  f5 (42);
+  f6 (baz);
+  f7 (bar);
+  f8 (bar);
+  f9 (qux);
+//  f10 (42);
+  f11 (42);
+  f12 (&i);
+  f13 (&i);
+  f14 (&i);
+
+  v1 = 1;
+  v2 = 2;
+  v3 = 3;
+  v4 = 4;
+  v5 = 5;
+  v6 = 6;
+}