Patchwork [c++] provide intelligent error messages for missing semicolon after class definition

login
register
mail settings
Submitter Nathan Froyd
Date Nov. 19, 2010, 2:31 p.m.
Message ID <20101119143135.GN24469@nightcrawler>
Download mbox | patch
Permalink /patch/72251/
State New
Headers show

Comments

Nathan Froyd - Nov. 19, 2010, 2:31 p.m.
On Tue, Nov 16, 2010 at 06:39:20PM -0800, Mark Mitchell wrote:
> On 11/9/2010 5:30 AM, Nathan Froyd wrote:
> > The patch below addresses PR c++/45331 (and related PRs) by providing a
> > more helpful error message (in some cases) when the user forgets a
> > semicolon at the end of a class definition.
> 
> Nice.
> 
> > +        want_semicolon = false;
> > +        break;
> > +        /* While it's legal for type qualifiers and storage class
> 
> Usual GNU style is a blank line after the "break", especially before a
> big comment.

Done.

> Also, I'm a little worried that we'll add something somewhere and
> forget to update this list, or the list of
> 
> > +	  case RID_CONST:
> > +	  case RID_VOLATILE:
> 
> specifiers and types (RID_INT, etc).  Would you please factor into a
> predicate function?

Added keyword_begins_type_specifier and
keyword_is_storage_class_specifier to c-common.[ch].

I added new tests to make sure that we catch:

class x { ... }
extern struct s *func (...);

and the like, which we weren't doing before (an unexpected benefit of
factoring things out!).

The error message has also been slightly modified: only compiler writers
would say "after class specifier", even when the user has defined a
struct or union.  The new message matches the thing being defined and
labels it as a definition.  Old tests have been tweaked further to
accommodate the new message.

Tested the same way as before.  OK to commit?

-Nathan

gcc/c-family/
	PR c++/16189
	PR c++/36888
	PR c++/45331
	* c-common.h (keyword_begins_type_specifier): Declare.
	(keyword_is_storage_class_specifier): Declare.
	* c-common.c (keyword_begins_type_specifier): New function.
	(keyword_is_storage_class_specifier): New function.

gcc/cp/
	PR c++/16189
	PR c++/36888
	PR c++/45331
	* parser.c (cp_lexer_set_token_position): New function.
	(cp_lexer_previous_token_position): New function.
	(cp_lexer_previous_token): Call it.
	(cp_parser_class_specifier): Try to gracefully handle a missing
	semicolon.

gcc/testsuite/
	PR c++/16189
	PR c++/36888
	PR c++/45331
	* g++.dg/parse/semicolon3.C: New test.
	* g++.dg/debug/pr22514.C: Adjust.
	* g++.dg/init/error1.C: Adjust.
	* g++.dg/other/bitfield3.C: Adjust.
	* g++.dg/other/semicolon.C: Adjust.
	* g++.dg/parse/error14.C: Adjust.
	* g++.dg/parse/error5.C: Adjust.
	* g++.dg/parse/parameter-declaration-1.C: Adjust.
	* g++.dg/template/pr23510.C: Adjust.
	* g++.dg/template/pr39425.C: Adjust.
	* g++.old-deja/g++.robertl/eb125.C: Adjust.
Mark Mitchell - Nov. 19, 2010, 3:05 p.m.
On 11/19/2010 6:31 AM, Nathan Froyd wrote:

> 	* c-common.h (keyword_begins_type_specifier): Declare.
> 	(keyword_is_storage_class_specifier): Declare.
> 	* c-common.c (keyword_begins_type_specifier): New function.
> 	(keyword_is_storage_class_specifier): New function.

There may be places we could/should use these new functions in the C/C++
parsers.  But, that can be a cleanup for another day.

Patch OK.

Thank you,
Joseph S. Myers - Nov. 19, 2010, 3:06 p.m.
On Fri, 19 Nov 2010, Nathan Froyd wrote:

> +/* Return true if KEYWORD names a storage class specifier.  */
> +
> +bool
> +keyword_is_storage_class_specifier (enum rid keyword)
> +{
> +  switch (keyword)
> +    {
> +    case RID_CONST:
> +    case RID_VOLATILE:
> +    case RID_INLINE:
> +    case RID_RESTRICT:

Not storage class specifiers in C (const, volatile and restrict are type 
qualifiers, inline is a function specifier).  However, typedef is a 
storage class specifier in C.  And so in C1X is _Thread_local - I imagine 
we'll end up making both __thread and _Thread_local use RID_THREAD and 
having GCC accept all the cases that are valid according to C1X (plus any 
other existing GNU cases unless -pedantic) rather than having distinct 
rules for the two spellings.

Thus, if the definitions of storage class specifiers for C and C++ are 
incompatible I don't think this belongs in c-common.c - or maybe the 
function should be renamed and the comment adjusted to reflect the actual 
rules for the set of keywords included.
Joseph S. Myers - Nov. 19, 2010, 3:15 p.m.
On Fri, 19 Nov 2010, Nathan Froyd wrote:

> +/* Return true if KEYWORD starts a type specifier.  */
> +
> +bool
> +keyword_begins_type_specifier (enum rid keyword)
> +{
> +  switch (keyword)
> +    {
> +    case RID_INT:
> +    case RID_CHAR:
> +    case RID_FLOAT:
> +    case RID_DOUBLE:
> +    case RID_VOID:
> +    case RID_INT128:
> +    case RID_UNSIGNED:
> +    case RID_LONG:
> +    case RID_SHORT:
> +    case RID_SIGNED:
> +    case RID_DFLOAT32:
> +    case RID_DFLOAT64:
> +    case RID_DFLOAT128:
> +    case RID_FRACT:
> +    case RID_ACCUM:
> +    case RID_BOOL:
> +    case RID_WCHAR:
> +    case RID_CHAR16:
> +    case RID_CHAR32:
> +    case RID_SAT:
> +    case RID_STRUCT:
> +    case RID_CLASS:
> +    case RID_UNION:
> +    case RID_ENUM:
> +      return true;

What about RID_COMPLEX?
Nathan Froyd - Nov. 19, 2010, 4:25 p.m.
On Fri, Nov 19, 2010 at 03:06:52PM +0000, Joseph S. Myers wrote:
> On Fri, 19 Nov 2010, Nathan Froyd wrote:
> > +/* Return true if KEYWORD names a storage class specifier.  */
> > +
> > +bool
> > +keyword_is_storage_class_specifier (enum rid keyword)
> > +{
> > +  switch (keyword)
> > +    {
> > +    case RID_CONST:
> > +    case RID_VOLATILE:
> > +    case RID_INLINE:
> > +    case RID_RESTRICT:
> 
> Not storage class specifiers in C (const, volatile and restrict are type 
> qualifiers, inline is a function specifier).  However, typedef is a 
> storage class specifier in C.  And so in C1X is _Thread_local - I imagine 
> we'll end up making both __thread and _Thread_local use RID_THREAD and 
> having GCC accept all the cases that are valid according to C1X (plus any 
> other existing GNU cases unless -pedantic) rather than having distinct 
> rules for the two spellings.

I admit to playing a little loose with the name here.  C++ N1905, from
2005, says that:

- inline, virtual, and explicit are function specifiers [dcl.fct.spec];

- auto, register, static, extern, and mutable are storage class
  specifiers [dcl.stc];

- typedef and friend are decl specifiers [dcl.spec].

- I think [basic.type.qualifier] qualifies as making const and volatile
  type qualifiers; I assume we can shoehorn restrict in there as well.

(I do not have access to a more recent copy of the spec.)

Proposal:

- keyword_is_function_specifier, recognizing inline, virtual, and
  explicit above;

- keyword_is_type_specifier, recognizing const, volatile, and restrict;

These would go in c-common.  Is that non-controverisal?

Whatever recognizes `friend' should probably just go in the C++ front
end.

I don't know what to do about a common storage_class recognizer.  We
could either have the c-common function recognize the common subset (the
[dct.stc] identifiers above, plus RID_THREAD).  The C front-end would
then have to remember RID_TYPEDEF in appropriate places.  Or we could
silently stuff typedef in there and assume it will DTRT in C++.  Or we
can just give up on having that code shared between the C and C++ front
ends, and I'll put keyword_is_storage_class_specifier somewhere in the
C++ front end.  WDYT?

-Nathan
Joseph S. Myers - Nov. 19, 2010, 4:56 p.m.
On Fri, 19 Nov 2010, Nathan Froyd wrote:

> I admit to playing a little loose with the name here.  C++ N1905, from
> 2005, says that:
> 
> - inline, virtual, and explicit are function specifiers [dcl.fct.spec];
> 
> - auto, register, static, extern, and mutable are storage class
>   specifiers [dcl.stc];
> 
> - typedef and friend are decl specifiers [dcl.spec].
> 
> - I think [basic.type.qualifier] qualifies as making const and volatile
>   type qualifiers; I assume we can shoehorn restrict in there as well.
> 
> (I do not have access to a more recent copy of the spec.)

http://www.open-std.org/jtc1/sc22/wg21/ lists N3126 as the most recent 
C++0x draft.

> Proposal:
> 
> - keyword_is_function_specifier, recognizing inline, virtual, and
>   explicit above;

Yes.

> - keyword_is_type_specifier, recognizing const, volatile, and restrict;

You mean _is_type_qualifier?

> These would go in c-common.  Is that non-controverisal?

Yes, so far.

> I don't know what to do about a common storage_class recognizer.  We
> could either have the c-common function recognize the common subset (the
> [dct.stc] identifiers above, plus RID_THREAD).  The C front-end would
> then have to remember RID_TYPEDEF in appropriate places.  Or we could
> silently stuff typedef in there and assume it will DTRT in C++.  Or we
> can just give up on having that code shared between the C and C++ front
> ends, and I'll put keyword_is_storage_class_specifier somewhere in the
> C++ front end.  WDYT?

Whatever you do, if it goes in common code I think you need a comment 
carefully explaining the semantics.

Patch

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index afe9b9d..c7335fd 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -9453,4 +9453,63 @@  make_tree_vector_copy (const VEC(tree,gc) *orig)
   return ret;
 }
 
+/* Return true if KEYWORD starts a type specifier.  */
+
+bool
+keyword_begins_type_specifier (enum rid keyword)
+{
+  switch (keyword)
+    {
+    case RID_INT:
+    case RID_CHAR:
+    case RID_FLOAT:
+    case RID_DOUBLE:
+    case RID_VOID:
+    case RID_INT128:
+    case RID_UNSIGNED:
+    case RID_LONG:
+    case RID_SHORT:
+    case RID_SIGNED:
+    case RID_DFLOAT32:
+    case RID_DFLOAT64:
+    case RID_DFLOAT128:
+    case RID_FRACT:
+    case RID_ACCUM:
+    case RID_BOOL:
+    case RID_WCHAR:
+    case RID_CHAR16:
+    case RID_CHAR32:
+    case RID_SAT:
+    case RID_STRUCT:
+    case RID_CLASS:
+    case RID_UNION:
+    case RID_ENUM:
+      return true;
+    default:
+      return false;
+    }
+}
+
+/* Return true if KEYWORD names a storage class specifier.  */
+
+bool
+keyword_is_storage_class_specifier (enum rid keyword)
+{
+  switch (keyword)
+    {
+    case RID_CONST:
+    case RID_VOLATILE:
+    case RID_INLINE:
+    case RID_RESTRICT:
+    case RID_STATIC:
+    case RID_EXTERN:
+    case RID_REGISTER:
+    case RID_AUTO:
+    case RID_MUTABLE:
+      return true;
+    default:
+      return 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 3de32cf..1023c76 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -736,6 +736,9 @@  extern void set_float_const_decimal64 (void);
 extern void clear_float_const_decimal64 (void);
 extern bool float_const_decimal64_p (void);
 
+extern bool keyword_begins_type_specifier (enum rid);
+extern bool keyword_is_storage_class_specifier (enum rid);
+
 #define c_sizeof(LOC, T)  c_sizeof_or_alignof_type (LOC, T, true, 1)
 #define c_alignof(LOC, T) c_sizeof_or_alignof_type (LOC, T, false, 1)
 
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 906b0c3..a99aaa8 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -502,15 +502,25 @@  cp_lexer_token_at (cp_lexer *lexer ATTRIBUTE_UNUSED, cp_token_position pos)
   return pos;
 }
 
-static inline cp_token *
-cp_lexer_previous_token (cp_lexer *lexer)
+static inline void
+cp_lexer_set_token_position (cp_lexer *lexer, cp_token_position pos)
 {
-  cp_token_position tp;
+  lexer->next_token = cp_lexer_token_at (lexer, pos);
+}
 
+static inline cp_token_position
+cp_lexer_previous_token_position (cp_lexer *lexer)
+{
   if (lexer->next_token == &eof_token)
-    tp = lexer->last_token - 1;
+    return lexer->last_token - 1;
   else
-    tp = cp_lexer_token_position (lexer, true);
+    return cp_lexer_token_position (lexer, true);
+}
+
+static inline cp_token *
+cp_lexer_previous_token (cp_lexer *lexer)
+{
+  cp_token_position tp = cp_lexer_previous_token_position (lexer);
 
   return cp_lexer_token_at (lexer, tp);
 }
@@ -16860,6 +16870,99 @@  cp_parser_class_specifier (cp_parser* parser)
     type = finish_struct (type, attributes);
   if (nested_name_specifier_p)
     pop_inner_scope (old_scope, scope);
+
+  /* We've finished a type definition.  Check for the common syntax
+     error of forgetting a semicolon after the definition.  We need to
+     be careful, as we can't just check for not-a-semicolon and be done
+     with it; the user might have typed:
+
+     class X { } c = ...;
+     class X { } *p = ...;
+
+     and so forth.  Instead, enumerate all the possible tokens that
+     might follow this production; if we don't see one of them, then
+     complain and silently insert the semicolon.  */
+  {
+    cp_token *token = cp_lexer_peek_token (parser->lexer);
+    bool want_semicolon = true;
+
+    switch (token->type)
+      {
+      case CPP_NAME:
+      case CPP_SEMICOLON:
+      case CPP_MULT:
+      case CPP_AND:
+      case CPP_OPEN_PAREN:
+      case CPP_CLOSE_PAREN:
+      case CPP_COMMA:
+        want_semicolon = false;
+        break;
+
+        /* While it's legal for type qualifiers and storage class
+           specifiers to follow type definitions in the grammar, only
+           compiler testsuites contain code like that.  Assume that if
+           we see such code, then what we're really seeing is a case
+           like:
+
+           class X { }
+           const <type> var = ...;
+
+           or
+
+           class Y { }
+           static <type> var = ...;
+
+           i.e. the qualifier or specifier applies to the next
+           declaration.  To do so, however, we need to look ahead one
+           more token to see if *that* token is a type specifier.
+
+	   This code could be improved to handle:
+
+	   class Z { }
+	   static const <type> var = ...;  */
+      case CPP_KEYWORD:
+	if (keyword_is_storage_class_specifier (token->keyword))
+	  {
+	    cp_token *lookahead = cp_lexer_peek_nth_token (parser->lexer, 2);
+
+	    if (lookahead->type == CPP_KEYWORD
+		&& !keyword_begins_type_specifier (lookahead->keyword))
+	      want_semicolon = false;
+	    else if (lookahead->type == CPP_NAME)
+	      /* Handling user-defined types here would be nice, but
+		 very tricky.  */
+	      want_semicolon = false;
+	  }
+	break;
+      default:
+	break;
+      }
+
+    if (want_semicolon)
+      {
+	cp_token_position prev
+	  = cp_lexer_previous_token_position (parser->lexer);
+	cp_token *prev_token = cp_lexer_token_at (parser->lexer, prev);
+	location_t loc = prev_token->location;
+
+	if (CLASSTYPE_DECLARED_CLASS (type))
+	  error_at (loc, "expected %<;%> after class definition");
+	else if (TREE_CODE (type) == RECORD_TYPE)
+	  error_at (loc, "expected %<;%> after struct definition");
+	else if (TREE_CODE (type) == UNION_TYPE)
+	  error_at (loc, "expected %<;%> after union definition");
+	else
+	  gcc_unreachable ();
+
+	/* Unget one token and smash it to look as though we encountered
+	   a semicolon in the input stream.  */
+	cp_lexer_set_token_position (parser->lexer, prev);
+	token = cp_lexer_peek_token (parser->lexer);
+	token->type = CPP_SEMICOLON;
+	token->keyword = RID_MAX;
+      }
+  }
+
   /* If this class is not itself within the scope of another class,
      then we need to parse the bodies of all of the queued function
      definitions.  Note that the queued functions defined in a class
diff --git a/gcc/testsuite/g++.dg/debug/pr22514.C b/gcc/testsuite/g++.dg/debug/pr22514.C
index 3df9e23..ed31cc7 100644
--- a/gcc/testsuite/g++.dg/debug/pr22514.C
+++ b/gcc/testsuite/g++.dg/debug/pr22514.C
@@ -8,6 +8,6 @@  namespace s
   template<int i> struct list : _List_base<i>
   {
     using _List_base<i>::_M_impl;
-  }
-}  /* { dg-error "expected unqualified-id before '\}'" } */
+  } // { dg-error "after struct definition" }
+}
 s::list<1> OutputModuleListType;
diff --git a/gcc/testsuite/g++.dg/init/error1.C b/gcc/testsuite/g++.dg/init/error1.C
index dd12e4c..bdd983f 100644
--- a/gcc/testsuite/g++.dg/init/error1.C
+++ b/gcc/testsuite/g++.dg/init/error1.C
@@ -2,6 +2,6 @@ 
 
 struct A {
   static float b[10];
-}
+} // { dg-error "after struct definition" }
 
-float A::b[] = {1,2,3}; // { dg-error "" }
+float A::b[] = {1,2,3};
diff --git a/gcc/testsuite/g++.dg/other/bitfield3.C b/gcc/testsuite/g++.dg/other/bitfield3.C
index b9726c2..befd7f8 100644
--- a/gcc/testsuite/g++.dg/other/bitfield3.C
+++ b/gcc/testsuite/g++.dg/other/bitfield3.C
@@ -3,13 +3,15 @@ 
 
 template<int> struct A
 {
-  struct {} : 2;	// { dg-error "with non-integral type" }
+  // multiple errors below: missing semicolon, no anonymous structs, etc.
+  struct {} : 2;	// { dg-error "" }
 };
 
 template<int> struct B
 {
   int a;
-  struct {} : 2;	// { dg-error "with non-integral type" }
+  // multiple errors below: missing semicolon, no anonymous structs, etc.
+  struct {} : 2;	// { dg-error "" }
   int b;
 };
 
diff --git a/gcc/testsuite/g++.dg/other/semicolon.C b/gcc/testsuite/g++.dg/other/semicolon.C
index efbae8b..8797bd3 100644
--- a/gcc/testsuite/g++.dg/other/semicolon.C
+++ b/gcc/testsuite/g++.dg/other/semicolon.C
@@ -5,7 +5,6 @@ 
 
 struct A
 {
-  struct B { int i; } // { dg-error "3:new types may not be defined in a return type" }
-                      // { dg-message "perhaps a semicolon is missing" "note" { target *-*-* } 8 }
-  void foo();   // { dg-error "12:two or more" }
+  struct B { int i; } // { dg-error "after struct definition" }
+  void foo();
 };
diff --git a/gcc/testsuite/g++.dg/parse/error14.C b/gcc/testsuite/g++.dg/parse/error14.C
index 9e672c2..37abe37 100644
--- a/gcc/testsuite/g++.dg/parse/error14.C
+++ b/gcc/testsuite/g++.dg/parse/error14.C
@@ -21,6 +21,6 @@  struct X
 
 }; // { dg-error "2:expected '.' at end of input" "at end of input" }
    // { dg-error "1:expected primary-expression before '.' token" "primary" { target *-*-* } 22 }
-   // { dg-error "1:expected ';' before '.' token" "semicolon" { target *-*-* } 22 }
-   // { dg-error "1:expected unqualified-id at end of input" "unqual" { target *-*-* } 22 }
+   // { dg-error "2:expected ';' after struct definition" "semicolon" { target *-*-* } 22 }
+   // { dg-error "1:expected ';' before '.' token" "function" { target *-*-* } 22 }
 
diff --git a/gcc/testsuite/g++.dg/parse/error5.C b/gcc/testsuite/g++.dg/parse/error5.C
index 6ebb087..eb1f9c7 100644
--- a/gcc/testsuite/g++.dg/parse/error5.C
+++ b/gcc/testsuite/g++.dg/parse/error5.C
@@ -3,17 +3,17 @@ 
 
 class Foo { int foo() return 0; } };
 
-// { dg-error "30:expected identifier before numeric constant" "" { target *-*-* } 4 }
+// { dg-error "30:expected identifier before numeric constant" "identifier" { target *-*-* } 4 }
 
-// { dg-error "23:named return values are no longer supported" "" { target *-*-* } 4 }
+// { dg-error "23:named return values are no longer supported" "named return" { target *-*-* } 4 }
 
 // the column number info of this error output is still wrong because the error
 // message has been generated by cp_parser_error() which does not
 // necessarily allow accurate column number display. At some point, we will
 // need make cp_parser_error() report more accurate column numbers.
-// { dg-error "30:expected '\{' at end of input" "" { target *-*-* } 4 }
+// { dg-error "30:expected '\{' at end of input" "brace" { target *-*-* } 4 }
 
-// { dg-error "35:expected unqualified-id before '\}' token" "" {target *-*-* } 4 }
+// { dg-error "33:expected ';' after class definition" "semicolon" {target *-*-* } 4 }
 
-// { dg-error "35:expected declaration before '\}' token" "" {target *-*-* } 4 }
+// { dg-error "35:expected declaration before '\}' token" "declaration" {target *-*-* } 4 }
 
diff --git a/gcc/testsuite/g++.dg/parse/parameter-declaration-1.C b/gcc/testsuite/g++.dg/parse/parameter-declaration-1.C
index 22d6f21..58f6799 100644
--- a/gcc/testsuite/g++.dg/parse/parameter-declaration-1.C
+++ b/gcc/testsuite/g++.dg/parse/parameter-declaration-1.C
@@ -2,5 +2,5 @@ 
 // Origin: Robert Schiele; PR C++/8799
 // { dg-do compile }
 
-struct {
+struct {			// { dg-error "" }
    a(void = 0; a(0), a(0)	// { dg-error "" "" { target *-*-* } }
diff --git a/gcc/testsuite/g++.dg/parse/semicolon3.C b/gcc/testsuite/g++.dg/parse/semicolon3.C
new file mode 100644
index 0000000..6e3ef52
--- /dev/null
+++ b/gcc/testsuite/g++.dg/parse/semicolon3.C
@@ -0,0 +1,198 @@ 
+// PR c++/45331
+// { dg-do compile }
+
+struct OK1
+{
+  int a;
+} // no complaints
+  *s5;
+
+struct OK2
+{
+  int a;
+} // no complaints
+  &s6 = *(new OK2());
+
+struct OK3
+{
+  int a;
+} // no complaints
+  (s7);
+
+__SIZE_TYPE__
+test_offsetof (void)
+{
+  // no complaints about a missing semicolon
+  return __builtin_offsetof (struct OK4 { int a; int b; }, b);
+}
+
+struct OK5
+{
+  int a;
+} ok5_var;			// no complaints
+
+struct OK6
+{
+  int a;
+} static ok6_var;		// no complaints
+
+class OK7
+{
+public:
+  OK7() { };
+  int a;
+} const ok7_var;		// no complaints
+
+class OK8
+{
+  int a;
+} extern ok8_var;		// no complaints
+
+class OK9
+{
+  class OK9sub { int a; } mutable ok9sub; // no complaints
+  int a;
+};
+
+int
+autotest (void)
+{
+  struct OK10 { int a; } auto ok10 = { 0 }; // no complaints
+
+  return ok10.a;
+}
+
+struct E1
+{
+  int a;
+} // { dg-error "after struct definition" }
+
+typedef float BAR;
+
+struct E2
+{
+  int a;
+} // { dg-error "after struct definition" }
+
+const int i0 = 1;
+
+struct E3
+{
+  int a;
+} // { dg-error "after struct definition" }
+
+volatile long l0 = 1;
+
+struct E4
+{
+  int a;
+} // { dg-error "after struct definition" }
+
+extern char c0;
+
+struct E5
+{
+  int a;
+} // { dg-error "after struct definition" }
+
+static wchar_t wc0;
+
+struct E6
+{
+  int a;
+} // { dg-error "after struct definition" }
+
+bool b0;
+
+class E7
+{
+  int a;
+} // { dg-error "after class definition" }
+
+extern double d0;
+
+class E8
+{
+  int a;
+} // { dg-error "after class definition" }
+
+inline short f(void)
+{
+  return 2;
+}
+
+class E9
+{
+  int a;
+} // { dg-error "after class definition" }
+
+class D0
+{
+  int a;
+};
+
+class E10
+{
+  int a;
+} // { dg-error "after class definition" }
+
+extern class D0 &f0 (void);
+
+class E11
+{
+  int a;
+} // { dg-error "after class definition" }
+
+const struct E6 *f1 (void) { return 0; }
+
+union U0 {
+  int i;
+  double d;
+};
+
+class E12
+{
+  int a;
+} // { dg-error "after class definition" }
+
+const union U0 *f2 (void) { return 0; }
+
+enum e {
+  U, V
+};
+
+class E13
+{
+  int a;
+} // { dg-error "after class definition" }
+
+static enum e f3 (void) { return U; }
+
+union E14
+{
+  int i;
+  double d;
+} // { dg-error "after union definition" }
+
+unsigned int i1 = 2;
+
+union E15
+{
+  int i;
+  double d;
+} // { dg-error "after union definition" }
+
+signed long l1 = 3;
+
+/* This was the original test from the PR.  */
+
+class C0
+{
+public:
+ int a;
+} // { dg-error "after class definition" }
+
+const int foo(const C0 &x)
+{
+ return x.a;
+}
diff --git a/gcc/testsuite/g++.dg/template/pr23510.C b/gcc/testsuite/g++.dg/template/pr23510.C
index b9e9889..1c3180f 100644
--- a/gcc/testsuite/g++.dg/template/pr23510.C
+++ b/gcc/testsuite/g++.dg/template/pr23510.C
@@ -6,13 +6,13 @@  struct Factorial
   enum { nValue = nFactor * Factorial<nFactor - 1>::nValue }; // { dg-error "depth exceeds maximum" } 
   // { dg-message "recursively instantiated" "" { target *-*-* } 6 } 
   // { dg-error "incomplete type" "" { target *-*-* } 6 } 
-} 
+} // { dg-error "expected ';' after" }
 
-  template<> // { dg-error "expected" } 
+  template<>
   struct Factorial<0>
   {
     enum { nValue = 1 };
-  }
+  };
 
     static const unsigned int FACTOR = 20;
 
diff --git a/gcc/testsuite/g++.dg/template/pr39425.C b/gcc/testsuite/g++.dg/template/pr39425.C
index a063e05..d55f547 100644
--- a/gcc/testsuite/g++.dg/template/pr39425.C
+++ b/gcc/testsuite/g++.dg/template/pr39425.C
@@ -15,4 +15,4 @@  class a {
 
   static const unsigned int value = _rec < 1 >::size;
 
-}		// { dg-error "unqualified-id" }
+} // { dg-error "after class definition" }
diff --git a/gcc/testsuite/g++.old-deja/g++.robertl/eb125.C b/gcc/testsuite/g++.old-deja/g++.robertl/eb125.C
index b068236..f2352c2 100644
--- a/gcc/testsuite/g++.old-deja/g++.robertl/eb125.C
+++ b/gcc/testsuite/g++.old-deja/g++.robertl/eb125.C
@@ -10,13 +10,13 @@  void test<class BOX> (test_box *);   // { dg-error "" } illegal code
 class test_square
     {
       friend void test<class BOX> (test_box *); // { dg-error "" } does not match
-    }
+    }						// { dg-error "after class definition" }
 
 
 
-template <class BOX> void test(BOX *the_box)  // { dg-error "" } semicolon missing
-    {x
-    the_box->print();
-    };
+template <class BOX> void test(BOX *the_box)
+    {x				// { dg-error "not declared in this scope" }
+    the_box->print();		// { dg-error "before" }
+    }
 
-template void test<> (test_box *); // { dg-error "" }
+template void test<> (test_box *);