diff mbox

C/C++: add fix-it hints for various missing symbols

Message ID 1499107059-28855-1-git-send-email-dmalcolm@redhat.com
State New
Headers show

Commit Message

David Malcolm July 3, 2017, 6:37 p.m. UTC
This patch improves our C/C++ frontends' handling of missing
symbols, by making c_parser_require and cp_parser_require use
"better" locations for the diagnostic, and insert fix-it hints,
under certain circumstances (see the comments in the patch for
full details).

For example, for this code with a missing semicolon:

  $ cat test.c
  int missing_semicolon (void)
  {
    return 42
  }

trunk currently emits:

  test.c:4:1: error: expected ‘;’ before ‘}’ token
   }
   ^

This patch adds a fix-it hint for the missing semicolon, and puts
the error at the location of the missing semicolon, printing the
followup token as a secondary location:

  test.c:3:12: error: expected ‘;’ before ‘}’ token
     return 42
              ^
              ;
   }
   ~

More examples can be seen in the test cases.

For reference, clang prints the following:

  test.c:3:12: error: expected ';' after return statement
    return 42
             ^
             ;

i.e. describing what syntactic thing came before, which
I think is likely to be more meaningful to the user.

clang can also print notes about matching opening symbols
e.g. the note here:

  missing-symbol-2.c:25:22: error: expected ']'
    const char test [42;
                       ^
  missing-symbol-2.c:25:19: note: to match this '['
    const char test [42;
                    ^
which, although somewhat redundant for this example, seems much more
useful if there's non-trivial nesting of constructs, or more than a few
lines separating the open/close symbols (e.g. showing a stray "namespace {"
that the user forgot to close).

I'd like to implement both of these ideas as followups, but in
the meantime, is the fix-it hint patch OK for trunk?
(successfully bootstrapped & regrtested on x86_64-pc-linux-gnu)

gcc/c-family/ChangeLog:
	* c-common.c (c_parse_error): Add RICHLOC param, and use it rather
	than implicitly using input_location.
	(enum missing_token_insertion_kind): New enum.
	(get_missing_token_insertion_kind): New function.
	(maybe_suggest_missing_token_insertion): New function.
	* c-common.h (c_parse_error): Add RICHLOC param.
	(maybe_suggest_missing_token_insertion): New decl.

gcc/c/ChangeLog:
	* c-parser.c (struct c_parser): Add "previous_token_loc" field.
	(c_parser_consume_token): Set parser->previous_token_loc.
	(c_parser_error): Rename to...
	(c_parser_error_richloc): ...this, making static, and adding
	"richloc" parameter, passing it to the c_parse_error call,
	rather than calling c_parser_set_source_position_from_token.
	(c_parser_error): Reintroduce, reimplementing in terms of the
	above.
	(c_parser_require): Add "type_is_unique" param.  Use
	c_parser_error_richloc rather than c_parser_error, calling
	maybe_suggest_missing_token_insertion.
	(c_parser_parms_list_declarator): Override default value of new
	"type_is_unique" param to c_parser_require.
	(c_parser_asm_statement): Likewise.
	* c-parser.h (c_parser_require): Add "type_is_unique" param,
	defaulting to true.

gcc/cp/ChangeLog:
	* parser.c (cp_parser_error): Add rich_location to call to
	c_parse_error.
	(get_required_cpp_ttype): New function.
	(cp_parser_required_error): Remove calls to cp_parser_error,
	instead setting a non-NULL gmsgid, and handling it if set by
	calling c_parse_error, potentially with a fix-it hint.

gcc/testsuite/ChangeLog:
	* c-c++-common/cilk-plus/AN/parser_errors.c: Update expected
	output to reflect changes to reported locations of missing
	symbols.
	* c-c++-common/cilk-plus/AN/parser_errors2.c: Likewise.
	* c-c++-common/cilk-plus/AN/parser_errors3.c: Likewise.
	* c-c++-common/cilk-plus/AN/pr61191.c: Likewise.
	* c-c++-common/gomp/pr63326.c: Likewise.
	* c-c++-common/missing-symbol.c: New test case.
	* g++.dg/cpp1y/digit-sep-neg.C: Update expected output to reflect
	changes to reported locations of missing symbols.
	* g++.dg/cpp1y/pr65202.C: Likewise.
	* g++.dg/other/do1.C: Likewise.
	* g++.dg/missing-symbol-2.C: New test case.
	* g++.dg/parse/error11.C: Update expected output to reflect
	changes to reported locations of missing symbols.
	* g++.dg/parse/pragma2.C: Likewise.
	* g++.dg/template/error11.C: Likewise.
	* gcc.dg/missing-symbol-2.c: New test case.
	* gcc.dg/missing-symbol-3.c: New test case.
	* gcc.dg/noncompile/940112-1.c: Update expected output to reflect
	changes to reported locations of missing symbols.
	* gcc.dg/noncompile/971104-1.c: Likewise.
	* obj-c++.dg/exceptions-6.mm: Likewise.
	* obj-c++.dg/pr48187.mm: Likewise.
	* objc.dg/exceptions-6.m: Likewise.
---
 gcc/c-family/c-common.c                            | 176 +++++++++++++-
 gcc/c-family/c-common.h                            |   7 +-
 gcc/c/c-parser.c                                   |  55 ++++-
 gcc/c/c-parser.h                                   |   2 +-
 gcc/cp/parser.c                                    | 254 +++++++++++++--------
 .../c-c++-common/cilk-plus/AN/parser_errors.c      |   4 +-
 .../c-c++-common/cilk-plus/AN/parser_errors2.c     |   3 +-
 .../c-c++-common/cilk-plus/AN/parser_errors3.c     |   3 +-
 gcc/testsuite/c-c++-common/cilk-plus/AN/pr61191.c  |   3 +-
 gcc/testsuite/c-c++-common/gomp/pr63326.c          |  22 +-
 gcc/testsuite/c-c++-common/missing-symbol.c        |  34 +++
 gcc/testsuite/g++.dg/cpp1y/digit-sep-neg.C         |   4 +-
 gcc/testsuite/g++.dg/cpp1y/pr65202.C               |   4 +-
 gcc/testsuite/g++.dg/missing-symbol-2.C            |  58 +++++
 gcc/testsuite/g++.dg/other/do1.C                   |   4 +-
 gcc/testsuite/g++.dg/parse/error11.C               |   2 +-
 gcc/testsuite/g++.dg/parse/pragma2.C               |   4 +-
 gcc/testsuite/g++.dg/template/error11.C            |   2 +-
 gcc/testsuite/gcc.dg/missing-symbol-2.c            |  71 ++++++
 gcc/testsuite/gcc.dg/missing-symbol-3.c            |  50 ++++
 gcc/testsuite/gcc.dg/noncompile/940112-1.c         |   4 +-
 gcc/testsuite/gcc.dg/noncompile/971104-1.c         |   4 +-
 gcc/testsuite/obj-c++.dg/exceptions-6.mm           |   6 +-
 gcc/testsuite/obj-c++.dg/pr48187.mm                |   8 +-
 gcc/testsuite/objc.dg/exceptions-6.m               |   4 +-
 25 files changed, 629 insertions(+), 159 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/missing-symbol.c
 create mode 100644 gcc/testsuite/g++.dg/missing-symbol-2.C
 create mode 100644 gcc/testsuite/gcc.dg/missing-symbol-2.c
 create mode 100644 gcc/testsuite/gcc.dg/missing-symbol-3.c

Comments

Richard Sandiford July 3, 2017, 6:57 p.m. UTC | #1
[Thanks for all your diagnostic work btw.]

David Malcolm <dmalcolm@redhat.com> writes:
> clang can also print notes about matching opening symbols
> e.g. the note here:
>
>   missing-symbol-2.c:25:22: error: expected ']'
>     const char test [42;
>                        ^
>   missing-symbol-2.c:25:19: note: to match this '['
>     const char test [42;
>                     ^
> which, although somewhat redundant for this example, seems much more
> useful if there's non-trivial nesting of constructs, or more than a few
> lines separating the open/close symbols (e.g. showing a stray "namespace {"
> that the user forgot to close).
>
> I'd like to implement both of these ideas as followups, but in
> the meantime, is the fix-it hint patch OK for trunk?
> (successfully bootstrapped & regrtested on x86_64-pc-linux-gnu)

Just wondering: how easy would it be to restrict the note to the kinds
of cases you mention?  TBH I think clang goes in for extra notes too
much, and it's not always that case that an "expected 'foo'" message
really is caused by a missing 'foo'.  It'd be great if there was some
way of making the notes a bit more discerning. :-)

Or maybe do something like restrict the extra note to cases in which the
opening character is on a different line and use an underlined range
when the opening character is on the same line?

Thanks,
Richard
David Malcolm July 3, 2017, 7:33 p.m. UTC | #2
On Mon, 2017-07-03 at 19:57 +0100, Richard Sandiford wrote:
> [Thanks for all your diagnostic work btw.]
> 
> David Malcolm <dmalcolm@redhat.com> writes:
> > clang can also print notes about matching opening symbols
> > e.g. the note here:
> > 
> >   missing-symbol-2.c:25:22: error: expected ']'
> >     const char test [42;
> >                        ^
> >   missing-symbol-2.c:25:19: note: to match this '['
> >     const char test [42;
> >                     ^
> > which, although somewhat redundant for this example, seems much
> > more
> > useful if there's non-trivial nesting of constructs, or more than a
> > few
> > lines separating the open/close symbols (e.g. showing a stray
> > "namespace {"
> > that the user forgot to close).
> > 
> > I'd like to implement both of these ideas as followups, but in
> > the meantime, is the fix-it hint patch OK for trunk?
> > (successfully bootstrapped & regrtested on x86_64-pc-linux-gnu)
> 
> Just wondering: how easy would it be to restrict the note to the
> kinds
> of cases you mention?  TBH I think clang goes in for extra notes too
> much, and it's not always that case that an "expected 'foo'" message
> really is caused by a missing 'foo'.  It'd be great if there was some
> way of making the notes a bit more discerning. :-)

My plan was to only do it for open/close punctuation, i.e.:
  * '(' and ')'
  * '{' and '}'
  * '[' and ']'
  * maybe '<' and '>' in C++

> Or maybe do something like restrict the extra note to cases in which
> the
> opening character is on a different line and use an underlined range
> when the opening character is on the same line?

Good idea: if it's on the same line, use a secondary range; if it's on
a different line, use a note.

The above example would look something like this (with the '[' as a
secondary range):

  missing-symbol-2.c:25:22: error: expected ']'
  const char test [42;
                  ~  ^
                     ]

which is more compact than the "separate note" approach, whilst (IMHO)
being just as readable.

FWIW diagnostic-show-locus.c can handle widely-separated secondary
ranges within one rich_location, provided they're in the same source
file (see calculate_line_spans, and the start_span callback within
diagnostic_context).

Consider the unclosed namespace here:

$ cat -n test.cc
     1	namespace ns {
     2	
     3	void test ()
     4	{
     5	}

for which we currently emit the rather unhelpful:

$ gcc test.cc
test.cc:5:1: error: expected ‘}’ at end of input
 }
 ^

Printing it via a secondary range using a single rich_location with
just an "error_at_rich_loc" call would print something like:

test.cc:5:1: error: expected ‘}’ at end of input
test.cc:1:14:
 namespace ns {
              ^
test.cc:5:1:
 }
  ^
  }

which works, but I'm not a fan of.

In constrast, with the "if it's on a different line, use a note" approach, we would print:

test.cc:5:1: error: expected ‘}’ at end of input
 }
  ^
  }
test.cc:1:14: note: to match this '{'
 namespace ns {
              ^

which I think is better (and supports the cases where they're in different files (e.g. you have a stray unclosed namespace in a header file, somewhere...), or macros are involved, etc)

So I'll have a go at implementing the "is it on a different line" logic you suggest.

For reference, clang prints the following for the above case:

test.cc:5:2: error: expected '}'
}
 ^
test.cc:1:14: note: to match this '{'
namespace ns {
             ^

Thinking aloud, maybe it would be better for the fix-it hint to suggest putting the '}' on a whole new line.  Might even be good to suggest adding

} // namespace ns

or similar (for this specific case), giving this output:

test.cc:5:1: error: expected ‘}’ at end of input
 }
+} // namespace ns
test.cc:1:14: note: to match this '{'
 namespace ns {
              ^

(only works if the proposed insertion point is on the end of a line, given the current restrictions on what our fix-it machinery is capable of - we don't currently support splitting a pre-existing line via a fix-it hint)

Thanks.
Dave
Joseph Myers July 3, 2017, 11:01 p.m. UTC | #3
Does the changed location fix bug 7356?
David Malcolm July 5, 2017, 3:32 p.m. UTC | #4
On Mon, 2017-07-03 at 23:01 +0000, Joseph Myers wrote:
> Does the changed location fix bug 7356?

The patch as-written doesn't affect that bug, since the patch only
affects sites that use c_parser_require and cp_parser_require with
certain token types, and the diagnostic in PR 7356 is emitted by the C
FE here:

2174		  /* This can appear in many cases looking nothing like a
2175		     function definition, so we don't give a more specific
2176		     error suggesting there was one.  */
2177		  c_parser_error (parser, "expected %<=%>, %<,%>, %<;%>, %<asm%> "
2178				  "or %<__attribute__%>");

(the C++ FE handles it, emitting:

pr7356.c:1:1: error: ‘a’ does not name a type
 a//sample
 ^
)

c_parser_error currently uses the location of the next token, and
concats as description of the next token.

I tried hacking up c_parser_error to unconditionally attempt to use the
location immediately after the previous token.  This "fixes" PR 7356,
giving:

pr7356.c:1:2: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘typedef’
 a//sample
  ^

This error message might be better to be worded in terms of the
syntactic thing that came before, which would yield:

pr7356.c:1:2: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’
after declaration
 a//sample
  ^

or somesuch.  Doing so would presumably require adding an extra param to 
c_parser_error, e.g. an enum describing the syntactic elements that go before.

Does this sound worth pursuing as a followup?


Thanks
Dave
Joseph Myers July 5, 2017, 4:17 p.m. UTC | #5
On Wed, 5 Jul 2017, David Malcolm wrote:

> This error message might be better to be worded in terms of the
> syntactic thing that came before, which would yield:
> 
> pr7356.c:1:2: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’
> after declaration
>  a//sample
>   ^
> 
> or somesuch.  Doing so would presumably require adding an extra param to 
> c_parser_error, e.g. an enum describing the syntactic elements that go before.
> 
> Does this sound worth pursuing as a followup?

Yes.  When you're wording things in terms of what the syntax error comes 
after rather than saying it comes before some automatically-generated 
description of a token, it would be best if the caller passes the complete 
message in an i18n-friendly way, rather than using concat (bug 18248).
Jeff Law Aug. 28, 2017, 3:22 p.m. UTC | #6
On 07/03/2017 12:37 PM, David Malcolm wrote:
> This patch improves our C/C++ frontends' handling of missing
> symbols, by making c_parser_require and cp_parser_require use
> "better" locations for the diagnostic, and insert fix-it hints,
> under certain circumstances (see the comments in the patch for
> full details).
> 
> For example, for this code with a missing semicolon:
> 
>   $ cat test.c
>   int missing_semicolon (void)
>   {
>     return 42
>   }
> 
> trunk currently emits:
> 
>   test.c:4:1: error: expected ‘;’ before ‘}’ token
>    }
>    ^
> 
> This patch adds a fix-it hint for the missing semicolon, and puts
> the error at the location of the missing semicolon, printing the
> followup token as a secondary location:
> 
>   test.c:3:12: error: expected ‘;’ before ‘}’ token
>      return 42
>               ^
>               ;
>    }
>    ~
> 
> More examples can be seen in the test cases.
> 
> For reference, clang prints the following:
> 
>   test.c:3:12: error: expected ';' after return statement
>     return 42
>              ^
>              ;
> 
> i.e. describing what syntactic thing came before, which
> I think is likely to be more meaningful to the user.
> 
> clang can also print notes about matching opening symbols
> e.g. the note here:
> 
>   missing-symbol-2.c:25:22: error: expected ']'
>     const char test [42;
>                        ^
>   missing-symbol-2.c:25:19: note: to match this '['
>     const char test [42;
>                     ^
> which, although somewhat redundant for this example, seems much more
> useful if there's non-trivial nesting of constructs, or more than a few
> lines separating the open/close symbols (e.g. showing a stray "namespace {"
> that the user forgot to close).
> 
> I'd like to implement both of these ideas as followups, but in
> the meantime, is the fix-it hint patch OK for trunk?
> (successfully bootstrapped & regrtested on x86_64-pc-linux-gnu)
> 
> gcc/c-family/ChangeLog:
> 	* c-common.c (c_parse_error): Add RICHLOC param, and use it rather
> 	than implicitly using input_location.
> 	(enum missing_token_insertion_kind): New enum.
> 	(get_missing_token_insertion_kind): New function.
> 	(maybe_suggest_missing_token_insertion): New function.
> 	* c-common.h (c_parse_error): Add RICHLOC param.
> 	(maybe_suggest_missing_token_insertion): New decl.
> 
> gcc/c/ChangeLog:
> 	* c-parser.c (struct c_parser): Add "previous_token_loc" field.
> 	(c_parser_consume_token): Set parser->previous_token_loc.
> 	(c_parser_error): Rename to...
> 	(c_parser_error_richloc): ...this, making static, and adding
> 	"richloc" parameter, passing it to the c_parse_error call,
> 	rather than calling c_parser_set_source_position_from_token.
> 	(c_parser_error): Reintroduce, reimplementing in terms of the
> 	above.
> 	(c_parser_require): Add "type_is_unique" param.  Use
> 	c_parser_error_richloc rather than c_parser_error, calling
> 	maybe_suggest_missing_token_insertion.
> 	(c_parser_parms_list_declarator): Override default value of new
> 	"type_is_unique" param to c_parser_require.
> 	(c_parser_asm_statement): Likewise.
> 	* c-parser.h (c_parser_require): Add "type_is_unique" param,
> 	defaulting to true.
> 
> gcc/cp/ChangeLog:
> 	* parser.c (cp_parser_error): Add rich_location to call to
> 	c_parse_error.
> 	(get_required_cpp_ttype): New function.
> 	(cp_parser_required_error): Remove calls to cp_parser_error,
> 	instead setting a non-NULL gmsgid, and handling it if set by
> 	calling c_parse_error, potentially with a fix-it hint.
> 
> gcc/testsuite/ChangeLog:
> 	* c-c++-common/cilk-plus/AN/parser_errors.c: Update expected
> 	output to reflect changes to reported locations of missing
> 	symbols.
> 	* c-c++-common/cilk-plus/AN/parser_errors2.c: Likewise.
> 	* c-c++-common/cilk-plus/AN/parser_errors3.c: Likewise.
> 	* c-c++-common/cilk-plus/AN/pr61191.c: Likewise.
> 	* c-c++-common/gomp/pr63326.c: Likewise.
> 	* c-c++-common/missing-symbol.c: New test case.
> 	* g++.dg/cpp1y/digit-sep-neg.C: Update expected output to reflect
> 	changes to reported locations of missing symbols.
> 	* g++.dg/cpp1y/pr65202.C: Likewise.
> 	* g++.dg/other/do1.C: Likewise.
> 	* g++.dg/missing-symbol-2.C: New test case.
> 	* g++.dg/parse/error11.C: Update expected output to reflect
> 	changes to reported locations of missing symbols.
> 	* g++.dg/parse/pragma2.C: Likewise.
> 	* g++.dg/template/error11.C: Likewise.
> 	* gcc.dg/missing-symbol-2.c: New test case.
> 	* gcc.dg/missing-symbol-3.c: New test case.
> 	* gcc.dg/noncompile/940112-1.c: Update expected output to reflect
> 	changes to reported locations of missing symbols.
> 	* gcc.dg/noncompile/971104-1.c: Likewise.
> 	* obj-c++.dg/exceptions-6.mm: Likewise.
> 	* obj-c++.dg/pr48187.mm: Likewise.
> 	* objc.dg/exceptions-6.m: Likewise.
AFAICT, this never got moved forward after the comments from Richard and
Joseph.




> +}
> +
> +/* Given RICHLOC, a location for a diagnostic describing a missing token
> +   of kind TOKEN_TYPE, potentially add a fix-it hint suggesting the
> +   insertion of the token.
> +
> +   The location of the attemped fix-it hint depends on TOKEN_TYPE:
s/attemped/attempted/


> +
> +  if (gmsgid)
> +    {
> +      /* Emulate rest of cp_parser_error.  */
> +      cp_token *token = cp_lexer_peek_token (parser->lexer);
> +      cp_lexer_set_source_position_from_token (token);
> +
> +      rich_location richloc (line_table, input_location);
So is it worth trying to factor the bits you want to emulate from
cp_parser_error so that they're shared?  Or just a comment in
cp_parser_error in the hopes that if someone changes it in a meaningful
way they'll know to come back here and potentially update this routine?

In general though it looks really good.  I think we just want to make a
decision whether or not there's some way to avoid a long term
maintenance headache noted immediately above.

jeff
Jeff
diff mbox

Patch

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 4395e51..f75a869 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -5945,12 +5945,13 @@  catenate_strings (const char *lhs, const char *rhs_start, int rhs_size)
   return result;
 }
 
-/* Issue the error given by GMSGID, indicating that it occurred before
-   TOKEN, which had the associated VALUE.  */
+/* Issue the error given by GMSGID at RICHLOC, indicating that it occurred
+   before TOKEN, which had the associated VALUE.  */
 
 void
 c_parse_error (const char *gmsgid, enum cpp_ttype token_type,
-	       tree value, unsigned char token_flags)
+	       tree value, unsigned char token_flags,
+	       rich_location *richloc)
 {
 #define catenate_messages(M1, M2) catenate_strings ((M1), (M2), sizeof (M2))
 
@@ -5991,7 +5992,7 @@  c_parse_error (const char *gmsgid, enum cpp_ttype token_type,
       else
 	message = catenate_messages (gmsgid, " before %s'\\x%x'");
 
-      error (message, prefix, val);
+      error_at_rich_loc (richloc, message, prefix, val);
       free (message);
       message = NULL;
     }
@@ -6019,7 +6020,7 @@  c_parse_error (const char *gmsgid, enum cpp_ttype token_type,
   else if (token_type == CPP_NAME)
     {
       message = catenate_messages (gmsgid, " before %qE");
-      error (message, value);
+      error_at_rich_loc (richloc, message, value);
       free (message);
       message = NULL;
     }
@@ -6032,16 +6033,16 @@  c_parse_error (const char *gmsgid, enum cpp_ttype token_type,
   else if (token_type < N_TTYPES)
     {
       message = catenate_messages (gmsgid, " before %qs token");
-      error (message, cpp_type2name (token_type, token_flags));
+      error_at_rich_loc (richloc, message, cpp_type2name (token_type, token_flags));
       free (message);
       message = NULL;
     }
   else
-    error (gmsgid);
+    error_at_rich_loc (richloc, gmsgid);
 
   if (message)
     {
-      error (message);
+      error_at_rich_loc (richloc, message);
       free (message);
     }
 #undef catenate_messages
@@ -7996,4 +7997,163 @@  c_flt_eval_method (bool maybe_c11_only_p)
     return c_ts18661_flt_eval_method ();
 }
 
+/* An enum for get_missing_token_insertion_kind for describing the best
+   place to insert a missing token, if there is one.  */
+
+enum missing_token_insertion_kind
+{
+  MTIK_IMPOSSIBLE,
+  MTIK_INSERT_BEFORE_NEXT,
+  MTIK_INSERT_AFTER_PREV
+};
+
+/* Given a missing token of TYPE, determine if it is reasonable to
+   emit a fix-it hint suggesting the insertion of the token, and,
+   if so, where the token should be inserted relative to other tokens.
+
+   For example, it only makes sense to do this for values of TYPE that
+   are symbols.
+
+   Some symbols should go before the next token, e.g. in:
+     if flag)
+   we want to insert the missing '(' immediately before "flag",
+   giving:
+     if (flag)
+   rather than:
+     if( flag)
+   These use MTIK_INSERT_BEFORE_NEXT.
+
+   Other symbols should go after the previous token, e.g. in:
+     if (flag
+       do_something ();
+   we want to insert the missing ')' immediately after the "flag",
+   giving:
+     if (flag)
+       do_something ();
+   rather than:
+     if (flag
+       )do_something ();
+   These use MTIK_INSERT_AFTER_PREV.  */
+
+static enum missing_token_insertion_kind
+get_missing_token_insertion_kind (enum cpp_ttype type)
+{
+  switch (type)
+    {
+      /* Insert missing "opening" brackets immediately
+	 before the next token.  */
+    case CPP_OPEN_SQUARE:
+    case CPP_OPEN_PAREN:
+      return MTIK_INSERT_BEFORE_NEXT;
+
+      /* Insert other missing symbols immediately after
+	 the previous token.  */
+    case CPP_CLOSE_PAREN:
+    case CPP_CLOSE_SQUARE:
+    case CPP_SEMICOLON:
+    case CPP_COMMA:
+    case CPP_COLON:
+      return MTIK_INSERT_AFTER_PREV;
+
+      /* Other kinds of token don't get fix-it hints.  */
+    default:
+      return MTIK_IMPOSSIBLE;
+    }
+}
+
+/* Given RICHLOC, a location for a diagnostic describing a missing token
+   of kind TOKEN_TYPE, potentially add a fix-it hint suggesting the
+   insertion of the token.
+
+   The location of the attemped fix-it hint depends on TOKEN_TYPE:
+   it will either be:
+     (a) immediately after PREV_TOKEN_LOC, or
+
+     (b) immediately before the primary location within RICHLOC (taken to
+	 be that of the token following where the token was expected).
+
+   If we manage to add a fix-it hint, then the location of the
+   fix-it hint is likely to be more useful as the primary location
+   of the diagnostic than that of the following token, so we swap
+   these locations.
+
+   For example, given this bogus code:
+       123456789012345678901234567890
+   1 | int missing_semicolon (void)
+   2 | {
+   3 |   return 42
+   4 | }
+
+   we will emit:
+
+     "expected ';' before '}'"
+
+   RICHLOC's primary location is at the closing brace, so before "swapping"
+   we would emit the error at line 4 column 1:
+
+       123456789012345678901234567890
+   3 |   return 42  |< fix-it hint emitted for this line
+     |            ; |
+   4 | }            |< "expected ';' before '}'" emitted at this line
+     | ^            |
+
+   It's more useful for the location of the diagnostic to be at the
+   fix-it hint, so we swap the locations, so the primary location
+   is at the fix-it hint, with the old primary location inserted
+   as a secondary location, giving this, with the error at line 3
+   column 12:
+
+       123456789012345678901234567890
+   3 |   return 42   |< "expected ';' before '}'" emitted at this line,
+     |            ^  |   with fix-it hint
+   4 |            ;  |
+     | }             |< secondary range emitted here
+     | ~             |.  */
+
+void
+maybe_suggest_missing_token_insertion (rich_location *richloc,
+				       enum cpp_ttype token_type,
+				       location_t prev_token_loc)
+{
+  gcc_assert (richloc);
+
+  enum missing_token_insertion_kind mtik
+    = get_missing_token_insertion_kind (token_type);
+
+  switch (mtik)
+    {
+    default:
+      gcc_unreachable ();
+      break;
+
+    case MTIK_IMPOSSIBLE:
+      return;
+
+    case MTIK_INSERT_BEFORE_NEXT:
+      /* Attempt to add the fix-it hint before the primary location
+	 of RICHLOC.  */
+      richloc->add_fixit_insert_before (cpp_type2name (token_type, 0));
+      break;
+
+    case MTIK_INSERT_AFTER_PREV:
+      /* Attempt to add the fix-it hint after PREV_TOKEN_LOC.  */
+      richloc->add_fixit_insert_after (prev_token_loc,
+				       cpp_type2name (token_type, 0));
+      break;
+    }
+
+  /* If we were successful, use the fix-it hint's location as the
+     primary location within RICHLOC, adding the old primary location
+     back as a secondary location.  */
+  if (!richloc->seen_impossible_fixit_p ())
+    {
+      fixit_hint *hint = richloc->get_last_fixit_hint ();
+      location_t hint_loc = hint->get_start_loc ();
+      location_t old_loc = richloc->get_loc ();
+
+      richloc->set_range (line_table, 0, hint_loc, true);
+      richloc->add_range (old_loc, false);
+    }
+}
+
 #include "gt-c-family-c-common.h"
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 1748c19..f5fd6ae 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1124,7 +1124,8 @@  extern void builtin_define_with_int_value (const char *, HOST_WIDE_INT);
 extern void builtin_define_type_sizeof (const char *, tree);
 extern void c_stddef_cpp_builtins (void);
 extern void fe_file_change (const line_map_ordinary *);
-extern void c_parse_error (const char *, enum cpp_ttype, tree, unsigned char);
+extern void c_parse_error (const char *, enum cpp_ttype, tree, unsigned char,
+			   rich_location *richloc);
 
 /* In c-ppoutput.c  */
 extern void init_pp_output (FILE *);
@@ -1554,6 +1555,10 @@  excess_precision_mode_join (enum flt_eval_method, enum flt_eval_method);
 extern int c_flt_eval_method (bool ts18661_p);
 extern void add_no_sanitize_value (tree node, unsigned int flags);
 
+extern void maybe_suggest_missing_token_insertion (rich_location *richloc,
+						   enum cpp_ttype token_type,
+						   location_t prev_token_loc);
+
 #if CHECKING_P
 namespace selftest {
   extern void c_format_c_tests (void);
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 6f954f2..11f061f 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -206,6 +206,9 @@  struct GTY(()) c_parser {
   /* Buffer to hold all the tokens from parsing the vector attribute for the
      SIMD-enabled functions (formerly known as elemental functions).  */
   vec <c_token, va_gc> *cilk_simd_fn_tokens;
+
+  /* Location of the most-recently consumed token.  */
+  location_t previous_token_loc;
 };
 
 /* Return a pointer to the Nth token in PARSERs tokens_buf.  */
@@ -770,6 +773,7 @@  c_parser_consume_token (c_parser *parser)
   gcc_assert (parser->tokens[0].type != CPP_EOF);
   gcc_assert (!parser->in_pragma || parser->tokens[0].type != CPP_PRAGMA_EOL);
   gcc_assert (parser->error || parser->tokens[0].type != CPP_PRAGMA);
+  parser->previous_token_loc = parser->tokens[0].location;
   if (parser->tokens != &parser->tokens_buf[0])
     parser->tokens++;
   else if (parser->tokens_avail == 2)
@@ -850,14 +854,17 @@  c_parser_peek_conflict_marker (c_parser *parser, enum cpp_ttype tok1_kind,
    MESSAGE (specified by the caller) is usually of the form "expected
    OTHER-TOKEN".
 
+   Use RICHLOC as the location of the diagnostic.
+
    Do not issue a diagnostic if still recovering from an error.
 
    ??? This is taken from the C++ parser, but building up messages in
    this way is not i18n-friendly and some other approach should be
    used.  */
 
-void
-c_parser_error (c_parser *parser, const char *gmsgid)
+static void
+c_parser_error_richloc (c_parser *parser, const char *gmsgid,
+			rich_location *richloc)
 {
   c_token *token = c_parser_peek_token (parser);
   if (parser->error)
@@ -879,9 +886,6 @@  c_parser_error (c_parser *parser, const char *gmsgid)
 	}
     }
 
-  /* This diagnostic makes more sense if it is tagged to the line of
-     the token we just peeked at.  */
-  c_parser_set_source_position_from_token (token);
   c_parse_error (gmsgid,
 		 /* Because c_parse_error does not understand
 		    CPP_KEYWORD, keywords are treated like
@@ -891,18 +895,39 @@  c_parser_error (c_parser *parser, const char *gmsgid)
 		    token, we need to pass 0 here and we will not get
 		    the source spelling of some tokens but rather the
 		    canonical spelling.  */
-		 token->value, /*flags=*/0);
+		 token->value, /*flags=*/0, richloc);
+}
+
+/* As c_parser_error_richloc, but issue the message at the
+   location of PARSER's next token, or at input_location
+   if the next token is EOF.  */
+
+void
+c_parser_error (c_parser *parser, const char *gmsgid)
+{
+  c_token *token = c_parser_peek_token (parser);
+  c_parser_set_source_position_from_token (token);
+  rich_location richloc (line_table, input_location);
+  c_parser_error_richloc (parser, gmsgid, &richloc);
 }
 
 /* If the next token is of the indicated TYPE, consume it.  Otherwise,
    issue the error MSGID.  If MSGID is NULL then a message has already
    been produced and no message will be produced this time.  Returns
-   true if found, false otherwise.  */
+   true if found, false otherwise.
+
+   If TYPE_IS_UNIQUE is true (the default) then msgid describes exactly
+   one type (e.g. "expected %<)%>") and thus it may be reasonable to
+   attempt to generate a fix-it hint for the problem.
+   Otherwise msgid describes multiple token types (e.g.
+   "expected %<;%>, %<,%> or %<)%>"), and thus we shouldn't attempt to
+   generate a fix-it hint.  */
 
 bool
 c_parser_require (c_parser *parser,
 		  enum cpp_ttype type,
-		  const char *msgid)
+		  const char *msgid,
+		  bool type_is_unique)
 {
   if (c_parser_next_token_is (parser, type))
     {
@@ -911,7 +936,14 @@  c_parser_require (c_parser *parser,
     }
   else
     {
-      c_parser_error (parser, msgid);
+      rich_location richloc (line_table,
+			     c_parser_peek_token (parser)->location);
+      /* Potentially supply a fix-it hint, suggesting to add the
+	 missing token immediately after the *previous* token.  */
+      if (!parser->error && type_is_unique)
+	maybe_suggest_missing_token_insertion (&richloc, type,
+					       parser->previous_token_loc);
+      c_parser_error_richloc (parser, msgid, &richloc);
       return false;
     }
 }
@@ -3798,7 +3830,7 @@  c_parser_parms_list_declarator (c_parser *parser, tree attrs, tree expr)
 	    return get_parm_info (false, expr);
 	}
       if (!c_parser_require (parser, CPP_COMMA,
-			     "expected %<;%>, %<,%> or %<)%>"))
+			     "expected %<;%>, %<,%> or %<)%>", false))
 	{
 	  c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, NULL);
 	  return NULL;
@@ -6177,7 +6209,8 @@  c_parser_asm_statement (c_parser *parser)
       if (!c_parser_require (parser, CPP_COLON,
 			     is_goto
 			     ? G_("expected %<:%>")
-			     : G_("expected %<:%> or %<)%>")))
+			     : G_("expected %<:%> or %<)%>"),
+			     is_goto))
 	goto error_close_paren;
 
       /* Once past any colon, we're no longer a simple asm.  */
diff --git a/gcc/c/c-parser.h b/gcc/c/c-parser.h
index 1e344c4..70b318f 100644
--- a/gcc/c/c-parser.h
+++ b/gcc/c/c-parser.h
@@ -136,7 +136,7 @@  extern c_token * c_parser_peek_token (c_parser *parser);
 extern c_token * c_parser_peek_2nd_token (c_parser *parser);
 extern c_token * c_parser_peek_nth_token (c_parser *parser, unsigned int n);
 extern bool c_parser_require (c_parser *parser, enum cpp_ttype type,
-			      const char *msgid);
+			      const char *msgid, bool type_is_unique=true);
 extern void c_parser_error (c_parser *parser, const char *gmsgid);
 extern void c_parser_consume_token (c_parser *parser);
 extern void c_parser_skip_until_found (c_parser *parser, enum cpp_ttype type,
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 1ee3ffe..d605aef 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -2804,12 +2804,13 @@  cp_parser_error (cp_parser* parser, const char* gmsgid)
 	    }
 	}
 
+      rich_location richloc (line_table, input_location);
       c_parse_error (gmsgid,
 		     /* Because c_parser_error does not understand
 			CPP_KEYWORD, keywords are treated like
 			identifiers.  */
 		     (token->type == CPP_KEYWORD ? CPP_NAME : token->type),
-		     token->u.value, token->flags);
+		     token->u.value, token->flags, &richloc);
     }
 }
 
@@ -27758,6 +27759,39 @@  cp_parser_friend_p (const cp_decl_specifier_seq *decl_specifiers)
   return decl_spec_seq_has_spec_p (decl_specifiers, ds_friend);
 }
 
+/* Attempt to convert TOKEN_DESC from a required_token to an
+   enum cpp_ttype, returning CPP_EOF if there is no good conversion.  */
+
+static enum cpp_ttype
+get_required_cpp_ttype (required_token token_desc)
+{
+  switch (token_desc)
+    {
+    case RT_SEMICOLON:
+      return CPP_SEMICOLON;
+    case RT_OPEN_PAREN:
+      return CPP_OPEN_PAREN;
+    case RT_CLOSE_BRACE:
+      return CPP_CLOSE_BRACE;
+    case RT_OPEN_BRACE:
+      return CPP_OPEN_BRACE;
+    case RT_CLOSE_SQUARE:
+      return CPP_CLOSE_SQUARE;
+    case RT_OPEN_SQUARE:
+      return CPP_OPEN_SQUARE;
+    case RT_COMMA:
+      return CPP_COMMA;
+    case RT_COLON:
+      return CPP_COLON;
+    case RT_CLOSE_PAREN:
+      return CPP_CLOSE_PAREN;
+
+    default:
+      /* Use CPP_EOF as a "no completions possible" code.  */
+      return CPP_EOF;
+    }
+}
+
 /* Issue an error message indicating that TOKEN_DESC was expected.
    If KEYWORD is true, it indicated this function is called by
    cp_parser_require_keword and the required token can only be
@@ -27768,163 +27802,185 @@  cp_parser_required_error (cp_parser *parser,
 			  required_token token_desc,
 			  bool keyword)
 {
+  if (cp_parser_simulate_error (parser))
+    return;
+
+  const char *gmsgid = NULL;
   switch (token_desc)
     {
       case RT_NEW:
-	cp_parser_error (parser, "expected %<new%>");
-	return;
+	gmsgid = G_("expected %<new%>");
+	break;
       case RT_DELETE:
-	cp_parser_error (parser, "expected %<delete%>");
-	return;
+	gmsgid = G_("expected %<delete%>");
+	break;
       case RT_RETURN:
-	cp_parser_error (parser, "expected %<return%>");
-	return;
+	gmsgid = G_("expected %<return%>");
+	break;
       case RT_WHILE:
-	cp_parser_error (parser, "expected %<while%>");
-	return;
+	gmsgid = G_("expected %<while%>");
+	break;
       case RT_EXTERN:
-	cp_parser_error (parser, "expected %<extern%>");
-	return;
+	gmsgid = G_("expected %<extern%>");
+	break;
       case RT_STATIC_ASSERT:
-	cp_parser_error (parser, "expected %<static_assert%>");
-	return;
+	gmsgid = G_("expected %<static_assert%>");
+	break;
       case RT_DECLTYPE:
-	cp_parser_error (parser, "expected %<decltype%>");
-	return;
+	gmsgid = G_("expected %<decltype%>");
+	break;
       case RT_OPERATOR:
-	cp_parser_error (parser, "expected %<operator%>");
-	return;
+	gmsgid = G_("expected %<operator%>");
+	break;
       case RT_CLASS:
-	cp_parser_error (parser, "expected %<class%>");
-	return;
+	gmsgid = G_("expected %<class%>");
+	break;
       case RT_TEMPLATE:
-	cp_parser_error (parser, "expected %<template%>");
-	return;
+	gmsgid = G_("expected %<template%>");
+	break;
       case RT_NAMESPACE:
-	cp_parser_error (parser, "expected %<namespace%>");
-	return;
+	gmsgid = G_("expected %<namespace%>");
+	break;
       case RT_USING:
-	cp_parser_error (parser, "expected %<using%>");
-	return;
+	gmsgid = G_("expected %<using%>");
+	break;
       case RT_ASM:
-	cp_parser_error (parser, "expected %<asm%>");
-	return;
+	gmsgid = G_("expected %<asm%>");
+	break;
       case RT_TRY:
-	cp_parser_error (parser, "expected %<try%>");
-	return;
+	gmsgid = G_("expected %<try%>");
+	break;
       case RT_CATCH:
-	cp_parser_error (parser, "expected %<catch%>");
-	return;
+	gmsgid = G_("expected %<catch%>");
+	break;
       case RT_THROW:
-	cp_parser_error (parser, "expected %<throw%>");
-	return;
+	gmsgid = G_("expected %<throw%>");
+	break;
       case RT_LABEL:
-	cp_parser_error (parser, "expected %<__label__%>");
-	return;
+	gmsgid = G_("expected %<__label__%>");
+	break;
       case RT_AT_TRY:
-	cp_parser_error (parser, "expected %<@try%>");
-	return;
+	gmsgid = G_("expected %<@try%>");
+	break;
       case RT_AT_SYNCHRONIZED:
-	cp_parser_error (parser, "expected %<@synchronized%>");
-	return;
+	gmsgid = G_("expected %<@synchronized%>");
+	break;
       case RT_AT_THROW:
-	cp_parser_error (parser, "expected %<@throw%>");
-	return;
+	gmsgid = G_("expected %<@throw%>");
+	break;
       case RT_TRANSACTION_ATOMIC:
-	cp_parser_error (parser, "expected %<__transaction_atomic%>");
-	return;
+	gmsgid = G_("expected %<__transaction_atomic%>");
+	break;
       case RT_TRANSACTION_RELAXED:
-	cp_parser_error (parser, "expected %<__transaction_relaxed%>");
-	return;
+	gmsgid = G_("expected %<__transaction_relaxed%>");
+	break;
       default:
 	break;
     }
-  if (!keyword)
+
+  if (!gmsgid && !keyword)
     {
       switch (token_desc)
         {
 	  case RT_SEMICOLON:
-	    cp_parser_error (parser, "expected %<;%>");
-	    return;
+	    gmsgid = G_("expected %<;%>");
+	    break;
 	  case RT_OPEN_PAREN:
-	    cp_parser_error (parser, "expected %<(%>");
-	    return;
+	    gmsgid = G_("expected %<(%>");
+	    break;
 	  case RT_CLOSE_BRACE:
-	    cp_parser_error (parser, "expected %<}%>");
-	    return;
+	    gmsgid = G_("expected %<}%>");
+	    break;
 	  case RT_OPEN_BRACE:
-	    cp_parser_error (parser, "expected %<{%>");
-	    return;
+	    gmsgid = G_("expected %<{%>");
+	    break;
 	  case RT_CLOSE_SQUARE:
-	    cp_parser_error (parser, "expected %<]%>");
-	    return;
+	    gmsgid = G_("expected %<]%>");
+	    break;
 	  case RT_OPEN_SQUARE:
-	    cp_parser_error (parser, "expected %<[%>");
-	    return;
+	    gmsgid = G_("expected %<[%>");
+	    break;
 	  case RT_COMMA:
-	    cp_parser_error (parser, "expected %<,%>");
-	    return;
+	    gmsgid = G_("expected %<,%>");
+	    break;
 	  case RT_SCOPE:
-	    cp_parser_error (parser, "expected %<::%>");
-	    return;
+	    gmsgid = G_("expected %<::%>");
+	    break;
 	  case RT_LESS:
-	    cp_parser_error (parser, "expected %<<%>");
-	    return;
+	    gmsgid = G_("expected %<<%>");
+	    break;
 	  case RT_GREATER:
-	    cp_parser_error (parser, "expected %<>%>");
-	    return;
+	    gmsgid = G_("expected %<>%>");
+	    break;
 	  case RT_EQ:
-	    cp_parser_error (parser, "expected %<=%>");
-	    return;
+	    gmsgid = G_("expected %<=%>");
+	    break;
 	  case RT_ELLIPSIS:
-	    cp_parser_error (parser, "expected %<...%>");
-	    return;
+	    gmsgid = G_("expected %<...%>");
+	    break;
 	  case RT_MULT:
-	    cp_parser_error (parser, "expected %<*%>");
-	    return;
+	    gmsgid = G_("expected %<*%>");
+	    break;
 	  case RT_COMPL:
-	    cp_parser_error (parser, "expected %<~%>");
-	    return;
+	    gmsgid = G_("expected %<~%>");
+	    break;
 	  case RT_COLON:
-	    cp_parser_error (parser, "expected %<:%>");
-	    return;
+	    gmsgid = G_("expected %<:%>");
+	    break;
 	  case RT_COLON_SCOPE:
-	    cp_parser_error (parser, "expected %<:%> or %<::%>");
-	    return;
+	    gmsgid = G_("expected %<:%> or %<::%>");
+	    break;
 	  case RT_CLOSE_PAREN:
-	    cp_parser_error (parser, "expected %<)%>");
-	    return;
+	    gmsgid = G_("expected %<)%>");
+	    break;
 	  case RT_COMMA_CLOSE_PAREN:
-	    cp_parser_error (parser, "expected %<,%> or %<)%>");
-	    return;
+	    gmsgid = G_("expected %<,%> or %<)%>");
+	    break;
 	  case RT_PRAGMA_EOL:
-	    cp_parser_error (parser, "expected end of line");
-	    return;
+	    gmsgid = G_("expected end of line");
+	    break;
 	  case RT_NAME:
-	    cp_parser_error (parser, "expected identifier");
-	    return;
+	    gmsgid = G_("expected identifier");
+	    break;
 	  case RT_SELECT:
-	    cp_parser_error (parser, "expected selection-statement");
-	    return;
+	    gmsgid = G_("expected selection-statement");
+	    break;
 	  case RT_ITERATION:
-	    cp_parser_error (parser, "expected iteration-statement");
-	    return;
+	    gmsgid = G_("expected iteration-statement");
+	    break;
 	  case RT_JUMP:
-	    cp_parser_error (parser, "expected jump-statement");
-	    return;
+	    gmsgid = G_("expected jump-statement");
+	    break;
 	  case RT_CLASS_KEY:
-	    cp_parser_error (parser, "expected class-key");
-	    return;
+	    gmsgid = G_("expected class-key");
+	    break;
 	  case RT_CLASS_TYPENAME_TEMPLATE:
-	    cp_parser_error (parser,
-	  	 "expected %<class%>, %<typename%>, or %<template%>");
-	    return;
+	    gmsgid = G_("expected %<class%>, %<typename%>, or %<template%>");
+	    break;
 	  default:
 	    gcc_unreachable ();
 	}
     }
-  else
-    gcc_unreachable ();
+
+  if (gmsgid)
+    {
+      /* Emulate rest of cp_parser_error.  */
+      cp_token *token = cp_lexer_peek_token (parser->lexer);
+      cp_lexer_set_source_position_from_token (token);
+
+      rich_location richloc (line_table, input_location);
+
+      /* Potentially supply a fix-it hint, suggesting to add the
+	 missing token immediately after the *previous* token.  */
+      enum cpp_ttype ttype = get_required_cpp_ttype (token_desc);
+      location_t prev_token_loc
+	= cp_lexer_previous_token (parser->lexer)->location;
+      maybe_suggest_missing_token_insertion (&richloc, ttype, prev_token_loc);
+
+      c_parse_error (gmsgid,
+		     (token->type == CPP_KEYWORD ? CPP_NAME : token->type),
+		     token->u.value, token->flags, &richloc);
+    }
 }
 
 
diff --git a/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors.c b/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors.c
index 18816e0..fd4fe54 100644
--- a/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors.c
+++ b/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors.c
@@ -7,5 +7,5 @@  int main (void)
   
   array2[:] = array2[: ;  /* { dg-error "expected ']'" } */
 
-  return 0;
-} /* { dg-error "expected ';' before" "" { target c } } */
+  return 0; /* { dg-error "expected ';' before" "" { target c } } */
+}
diff --git a/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors2.c b/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors2.c
index 2bb9134..d003d7c 100644
--- a/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors2.c
+++ b/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors2.c
@@ -7,6 +7,7 @@  int main (void)
   
   array2[:] = array2[1:2:] ;  /* { dg-error "expected expression before" "" { target c } } */ 
   /* { dg-error  "expected primary-expression before" "" { target c++ } .-1 } */
+  /* { dg-error "expected ';' before" "" { target c } .-2 } */
 
-  return 0; /* { dg-error "expected ';' before" "" { target c }  } */
+  return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors3.c b/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors3.c
index 9270007..14256e9 100644
--- a/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors3.c
+++ b/gcc/testsuite/c-c++-common/cilk-plus/AN/parser_errors3.c
@@ -7,6 +7,7 @@  int main (void)
   
   array2[:] = array2[1: :] ;  /* { dg-error "expected expression before" "" { target c }  } */ 
   /* { dg-error "expected primary-expression before" "" { target c++ }  .-1 } */
+  /* { dg-error "expected ';' before" "" { target c } .-2 } */
 
-  return 0; /* { dg-error "expected ';' before" "" { target c } } */
+  return 0;
 }
diff --git a/gcc/testsuite/c-c++-common/cilk-plus/AN/pr61191.c b/gcc/testsuite/c-c++-common/cilk-plus/AN/pr61191.c
index a9a9d66..8c32ad9 100644
--- a/gcc/testsuite/c-c++-common/cilk-plus/AN/pr61191.c
+++ b/gcc/testsuite/c-c++-common/cilk-plus/AN/pr61191.c
@@ -7,4 +7,5 @@  double f(double * A, double * B)
   return __sec_reduce_add((B[0:500])(; /* { dg-error "called object" "" { target c } } */
 /* { dg-error "expected expression before ';' token" "" { target c } .-1 } */
 /* { dg-error "expected primary-expression before ';' token" "" { target c++ } .-2 } */
-} /* { dg-error "expected" "" { target c } } */
+/* { dg-error "expected" "" { target c } .-3 } */
+}
diff --git a/gcc/testsuite/c-c++-common/gomp/pr63326.c b/gcc/testsuite/c-c++-common/gomp/pr63326.c
index e319f49..3e62723 100644
--- a/gcc/testsuite/c-c++-common/gomp/pr63326.c
+++ b/gcc/testsuite/c-c++-common/gomp/pr63326.c
@@ -156,34 +156,34 @@  f4 (int x)
   {
     do
       #pragma omp barrier			/* { dg-error "may only be used in compound statements" } */
-    while (0);
+    while (0); /* { dg-error "before" "" { target c++ } } */
   } /* { dg-error "before" "" { target c++ } } */
   {
     do
       #pragma omp flush				/* { dg-error "may only be used in compound statements" } */
-    while (0);
+    while (0); /* { dg-error "before" "" { target c++ } } */
   } /* { dg-error "before" "" { target c++ } } */
   {
     do
       #pragma omp taskwait			/* { dg-error "may only be used in compound statements" } */
-    while (0);
+    while (0); /* { dg-error "before" "" { target c++ } } */
   } /* { dg-error "before" "" { target c++ } } */
   {
     do
       #pragma omp taskyield			/* { dg-error "may only be used in compound statements" } */
-    while (0);
+    while (0); /* { dg-error "before" "" { target c++ } } */
   } /* { dg-error "before" "" { target c++ } } */
   #pragma omp parallel
   {
     do
       #pragma omp cancel parallel		/* { dg-error "may only be used in compound statements" } */
-    while (0);
+    while (0); /* { dg-error "before" "" { target c++ } } */
   } /* { dg-error "before" "" { target c++ } } */
   #pragma omp parallel
   {
     do
       #pragma omp cancellation point parallel	/* { dg-error "may only be used in compound statements" } */
-    while (0);
+    while (0); /* { dg-error "before" "" { target c++ } } */
   } /* { dg-error "before" "" { target c++ } } */
   #pragma omp for ordered(1)
   for (i = 0; i < 16; i++)
@@ -191,28 +191,28 @@  f4 (int x)
       {
 	do
 	  #pragma omp ordered depend(source)	/* { dg-error "may only be used in compound statements" } */
-	while (0);
+	while (0); /* { dg-error "before" "" { target c++ } } */
       } /* { dg-error "before" "" { target c++ } } */
       {
 	do
 	  #pragma omp ordered depend(sink: i-1)	/* { dg-error "may only be used in compound statements" } */
-	while (0);
+	while (0); /* { dg-error "before" "" { target c++ } } */
       } /* { dg-error "before" "" { target c++ } } */
     }
   {
     do
       #pragma omp target enter data map(to:i)	/* { dg-error "may only be used in compound statements" } */
-    while (0);
+    while (0); /* { dg-error "before" "" { target c++ } } */
   } /* { dg-error "before" "" { target c++ } } */
   {
     do
       #pragma omp target update to(i)		/* { dg-error "may only be used in compound statements" } */
-    while (0);
+    while (0); /* { dg-error "before" "" { target c++ } } */
   } /* { dg-error "before" "" { target c++ } } */
   {
     do
       #pragma omp target exit data map(from:i)	/* { dg-error "may only be used in compound statements" } */
-    while (0);
+    while (0); /* { dg-error "before" "" { target c++ } } */
   } /* { dg-error "before" "" { target c++ } } */
 }
 
diff --git a/gcc/testsuite/c-c++-common/missing-symbol.c b/gcc/testsuite/c-c++-common/missing-symbol.c
new file mode 100644
index 0000000..d056516
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/missing-symbol.c
@@ -0,0 +1,34 @@ 
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+extern int foo (void);
+extern int bar (void);
+
+void missing_close_paren (void)
+{
+  if (foo ()
+      && bar () /* { dg-error "16: expected '\\)' before '.' token" } */
+    {
+      /* { dg-begin-multiline-output "" }
+       && bar ()
+                ^
+                )
+     {
+     ~           
+         { dg-end-multiline-output "" } */
+    }
+} /* { dg-error "1: expected" } */
+  /* { dg-begin-multiline-output "" }
+ }
+ ^
+     { dg-end-multiline-output "" } */
+
+
+int missing_colon_in_ternary (int flag)
+{
+  return flag ? 42 0; /* { dg-error "expected ':' before numeric constant" } */
+  /* { dg-begin-multiline-output "" }
+   return flag ? 42 0;
+                   ^~
+                   :
+     { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/g++.dg/cpp1y/digit-sep-neg.C b/gcc/testsuite/g++.dg/cpp1y/digit-sep-neg.C
index 833fab7..727e74e 100644
--- a/gcc/testsuite/g++.dg/cpp1y/digit-sep-neg.C
+++ b/gcc/testsuite/g++.dg/cpp1y/digit-sep-neg.C
@@ -26,5 +26,5 @@  main()
 }
 
 // { dg-error "exponent has no digits" "exponent has no digits" { target *-*-* } 21 }
-// { dg-error "expected ';' before" "expected ';' before" { target *-*-* } 14 }
-// { dg-error "expected ';' before" "expected ';' before" { target *-*-* } 25 }
+// { dg-error "expected ';' before" "expected ';' before" { target *-*-* } 13 }
+// { dg-error "expected ';' before" "expected ';' before" { target *-*-* } 24 }
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr65202.C b/gcc/testsuite/g++.dg/cpp1y/pr65202.C
index 602b264..7ce4895 100644
--- a/gcc/testsuite/g++.dg/cpp1y/pr65202.C
+++ b/gcc/testsuite/g++.dg/cpp1y/pr65202.C
@@ -22,5 +22,5 @@  struct bar;
 int main()
 {
     foo<ns::bar> f;
-    adl::swap(f, f)
-} // { dg-error "" }
+    adl::swap(f, f) // { dg-error "expected ';'" }
+} // { dg-error "expected '.'" "expected end of namespace" }
diff --git a/gcc/testsuite/g++.dg/missing-symbol-2.C b/gcc/testsuite/g++.dg/missing-symbol-2.C
new file mode 100644
index 0000000..4a119f8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/missing-symbol-2.C
@@ -0,0 +1,58 @@ 
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+extern int foo (void);
+
+void missing_open_paren (void)
+{
+  if foo ()) /* { dg-error "expected '\\(' before 'foo'" } */
+    {
+    }
+  /* { dg-begin-multiline-output "" }
+   if foo ())
+      ^~~
+      (
+     { dg-end-multiline-output "" } */
+}
+
+
+void missing_close_square (void)
+{
+  const char test [42;  /* { dg-error "22: expected ']' before ';' token" } */
+  /* { dg-begin-multiline-output "" }
+   const char test [42;
+                      ^
+                      ]
+     { dg-end-multiline-output "" } */
+}
+
+int missing_semicolon (void)
+{
+  return 42 /* { dg-error "expected ';'" } */
+}
+/* { dg-begin-multiline-output "" }
+   return 42
+            ^
+            ;
+ }
+ ~           
+   { dg-end-multiline-output "" } */
+
+
+int missing_colon_in_switch (int val)
+{
+  switch (val)
+    {
+    case 42 /* { dg-error "expected ':' before 'return'" } */
+      return 42;
+    /* { dg-begin-multiline-output "" }
+     case 42
+            ^
+            :
+       return 42;
+       ~~~~~~
+       { dg-end-multiline-output "" } */
+
+    default:
+      return val;
+    }
+}
diff --git a/gcc/testsuite/g++.dg/other/do1.C b/gcc/testsuite/g++.dg/other/do1.C
index b3a9daf..db65e7d 100644
--- a/gcc/testsuite/g++.dg/other/do1.C
+++ b/gcc/testsuite/g++.dg/other/do1.C
@@ -7,7 +7,7 @@ 
 
 void init ()
 {
-  do {  } while (0)
-	    obj = 0; // { dg-error "expected|not declared" }
+  do {  } while (0) // { dg-error "expected ';'" }
+	    obj = 0; // { dg-error "not declared" }
      
 }
diff --git a/gcc/testsuite/g++.dg/parse/error11.C b/gcc/testsuite/g++.dg/parse/error11.C
index d118c19..1a49d6e 100644
--- a/gcc/testsuite/g++.dg/parse/error11.C
+++ b/gcc/testsuite/g++.dg/parse/error11.C
@@ -52,7 +52,7 @@  void func(void)
   Foo[:B> k1;       // { dg-bogus "cannot begin|alternate spelling" "smart error should not be triggered here" } 
 // { dg-error "6:missing template arguments before" "template" { target *-*-* } 51 }
 // { dg-error "9:expected primary-expression before ':' token" "primary" { target *-*-* } 51 }
-// { dg-error "9:expected '\]' before ':' token" "backslash" { target *-*-* } 51 }
+// { dg-error "8:expected '\]' before ':' token" "backslash" { target *-*-* } 51 }
 // { dg-error "6:missing template arguments before" "template" { target *-*-* } 52 }
 // { dg-error "7:expected primary-expression before ':' token" "primary" { target *-*-* } 52 }
 // { dg-error "7:expected '\]' before ':' token" "backslash" { target *-*-* } 52 }
diff --git a/gcc/testsuite/g++.dg/parse/pragma2.C b/gcc/testsuite/g++.dg/parse/pragma2.C
index c5616ff..eb0cf95 100644
--- a/gcc/testsuite/g++.dg/parse/pragma2.C
+++ b/gcc/testsuite/g++.dg/parse/pragma2.C
@@ -4,5 +4,5 @@ 
 // does not.
 int f(int x,
 #pragma interface  // { dg-error "not allowed here" }
-      // The parser gets confused and issues an error on the next line.
-      int y); // { dg-bogus "" "" { xfail *-*-* } } 
+      // { dg-bogus "expected identifier" "" { xfail *-*-* } .-1 }
+      int y); 
diff --git a/gcc/testsuite/g++.dg/template/error11.C b/gcc/testsuite/g++.dg/template/error11.C
index 3a469fd..1640298 100644
--- a/gcc/testsuite/g++.dg/template/error11.C
+++ b/gcc/testsuite/g++.dg/template/error11.C
@@ -1,4 +1,4 @@ 
 // PR c++/12132
 
 inline template <int> void foo () {} // { dg-error "<" }
-void abort (); // { dg-error ";" }
+void abort (); // { dg-error ";" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/gcc.dg/missing-symbol-2.c b/gcc/testsuite/gcc.dg/missing-symbol-2.c
new file mode 100644
index 0000000..7ee795d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/missing-symbol-2.c
@@ -0,0 +1,71 @@ 
+/* { dg-options "-fdiagnostics-show-caret -Wno-switch-unreachable" } */
+
+extern int foo (void);
+
+void missing_open_paren (void)
+{
+  if foo ()) /* { dg-line missing_open_paren } */
+    {
+    }
+  /* { dg-error "expected '\\(' before 'foo'" "" { target c } missing_open_paren } */
+  /* { dg-begin-multiline-output "" }
+   if foo ())
+      ^~~
+      (
+     { dg-end-multiline-output "" } */
+  /* { dg-error "expected statement before '\\)' token"  "" { target c } missing_open_paren } */
+  /* { dg-begin-multiline-output "" }
+   if foo ())
+            ^
+     { dg-end-multiline-output "" } */
+}
+
+void missing_close_square (void)
+{
+  const char test [42;  /* { dg-error "22: expected ']' before ';' token" } */
+  /* { dg-begin-multiline-output "" }
+   const char test [42;
+                      ^
+                      ]
+     { dg-end-multiline-output "" } */
+}
+
+int missing_semicolon (void)
+{
+  return 42 /* { dg-error "expected ';'" } */
+}
+/* { dg-begin-multiline-output "" }
+   return 42
+            ^
+            ;
+ }
+ ~           
+   { dg-end-multiline-output "" } */
+
+
+/* We don't offer a fix-it hint for this case in C, as it could be
+   colon or ellipsis.
+   TODO: we could be smarter about error-recovery here; given the
+   return perhaps we could assume a missing colon.  */
+
+int missing_colon_in_switch (int val)
+{
+  switch (val)
+    {
+    case 42
+      return 42; /* { dg-error "expected ':' or '...' before 'return'" } */
+    /* { dg-begin-multiline-output "" }
+       return 42;
+       ^~~~~~
+       { dg-end-multiline-output "" } */
+
+    default:
+      return val;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+ int dummy;
+ ^~~
+   { dg-end-multiline-output "" } */
+int dummy;/* { dg-error "expected declaration or statement at end of input" "" { target c } } */
diff --git a/gcc/testsuite/gcc.dg/missing-symbol-3.c b/gcc/testsuite/gcc.dg/missing-symbol-3.c
new file mode 100644
index 0000000..5449665
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/missing-symbol-3.c
@@ -0,0 +1,50 @@ 
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+/* A sequence of bogus _Static_assert.
+   We can offer fix-it hints for some of these, but not all.  */
+
+void test_static_assert_1 (void)
+{
+  _Static_assert sizeof(int) >= sizeof(char); /* { dg-error "expected '\\(' before 'sizeof'" } */
+  /* { dg-begin-multiline-output "" }
+   _Static_assert sizeof(int) >= sizeof(char);
+                  ^~~~~~
+                  (
+     { dg-end-multiline-output "" } */
+}
+
+void test_static_assert_2 (void)
+{
+  _Static_assert(sizeof(int) >= sizeof(char); /* { dg-error "expected ',' before ';' token" } */
+  /* { dg-begin-multiline-output "" }
+   _Static_assert(sizeof(int) >= sizeof(char);
+                                             ^
+                                             ,
+     { dg-end-multiline-output "" } */
+}
+
+void test_static_assert_3 (void)
+{
+  _Static_assert(sizeof(int) >= sizeof(char),; /* { dg-error "expected string literal before ';' token" } */
+  /* { dg-begin-multiline-output "" }
+   _Static_assert(sizeof(int) >= sizeof(char),;
+                                              ^
+     { dg-end-multiline-output "" } */
+}
+
+void test_static_assert_4 (void)
+{
+  _Static_assert(sizeof(int) >= sizeof(char), "msg"; /* { dg-error "expected '\\)' before ';' token" } */
+  /* { dg-begin-multiline-output "" }
+   _Static_assert(sizeof(int) >= sizeof(char), "msg";
+                                                    ^
+                                                    )
+     { dg-end-multiline-output "" } */
+}
+
+/* The final one is correct.  */
+
+void test_static_assert_5 (void)
+{
+  _Static_assert(sizeof(int) >= sizeof(char), "msg");
+}
diff --git a/gcc/testsuite/gcc.dg/noncompile/940112-1.c b/gcc/testsuite/gcc.dg/noncompile/940112-1.c
index bb5e0f6..0a9e07d 100644
--- a/gcc/testsuite/gcc.dg/noncompile/940112-1.c
+++ b/gcc/testsuite/gcc.dg/noncompile/940112-1.c
@@ -3,5 +3,5 @@  f (int x)
 {
   double e = 1;
   e = 1;
-  return (e)
-}	/* { dg-error "parse error|syntax error|expected" } */
+  return (e) /* { dg-error "parse error|syntax error|expected" } */
+}	
diff --git a/gcc/testsuite/gcc.dg/noncompile/971104-1.c b/gcc/testsuite/gcc.dg/noncompile/971104-1.c
index 39e00c6..4a04dad 100644
--- a/gcc/testsuite/gcc.dg/noncompile/971104-1.c
+++ b/gcc/testsuite/gcc.dg/noncompile/971104-1.c
@@ -27,6 +27,6 @@  static void up(int sem){
     printf("%s had processes sleeping on it!\n",
     ({ "MUTEX     ", "BARB_SEM 1", "BARB_SEM 2", "CUST_SEM 1",
        "CUST_SEM 2", "WAIT_SEM 1", "WAIT_SEM 2", "WAIT_SEM 3",
-       "WAIT_SEM 4"}	 /* { dg-error "parse error|syntax error|expected" } */
-	[( sb.sem_num )]) ); /* { dg-error "expected" } */
+       "WAIT_SEM 4"}	 /* { dg-error "expected" } */
+	[( sb.sem_num )]) );
 }
diff --git a/gcc/testsuite/obj-c++.dg/exceptions-6.mm b/gcc/testsuite/obj-c++.dg/exceptions-6.mm
index 58882fe..6f6ba78 100644
--- a/gcc/testsuite/obj-c++.dg/exceptions-6.mm
+++ b/gcc/testsuite/obj-c++.dg/exceptions-6.mm
@@ -11,15 +11,15 @@  void test (id object)
   @throw object;   /* Ok */
   @throw;          /* { dg-error ".@throw. .rethrow. used outside of a @catch block" } */
   @throw (object); /* Ok.  */
-  @throw (id)0
-}                  /* { dg-error "expected" } */
+  @throw (id)0     /* { dg-error "expected" } */
+}
 
 void test2 (id object)
 {
   @throw object);  /* { dg-error "expected" } */
   @throw (...);    /* { dg-error "expected" } */
   @throw ();       /* { dg-error "expected" } */
-  @throw           
+  @throw           /* { dg-error "expected" } */
 }                  /* { dg-error "expected" } */
 
 void test3 (id object1, id object2)
diff --git a/gcc/testsuite/obj-c++.dg/pr48187.mm b/gcc/testsuite/obj-c++.dg/pr48187.mm
index 750710b..99677a5 100644
--- a/gcc/testsuite/obj-c++.dg/pr48187.mm
+++ b/gcc/testsuite/obj-c++.dg/pr48187.mm
@@ -1,19 +1,19 @@ 
 /* { dg-do compile } */
 
 @interface A
-{
+{    /* { dg-error "xpected" } */
   ]  /* { dg-error "xpected" } */
 }
 @end
 
 @interface B
-{
+{     /* { dg-error "xpected" } */
   ];  /* { dg-error "xpected" } */
 }
 @end
 
 @interface C
-{
+{     /* { dg-error "xpected" } */
   ];  /* { dg-error "xpected" } */
   int x;
 }
@@ -21,7 +21,7 @@ 
 
 @interface D
 {
-  (
+  (  /* { dg-error "xpected" } */
 }  /* { dg-error "xpected" } */
 @end
 
diff --git a/gcc/testsuite/objc.dg/exceptions-6.m b/gcc/testsuite/objc.dg/exceptions-6.m
index 58882fe..74be98d 100644
--- a/gcc/testsuite/objc.dg/exceptions-6.m
+++ b/gcc/testsuite/objc.dg/exceptions-6.m
@@ -11,8 +11,8 @@  void test (id object)
   @throw object;   /* Ok */
   @throw;          /* { dg-error ".@throw. .rethrow. used outside of a @catch block" } */
   @throw (object); /* Ok.  */
-  @throw (id)0
-}                  /* { dg-error "expected" } */
+  @throw (id)0     /* { dg-error "expected" } */
+}
 
 void test2 (id object)
 {