Patchwork [RFC/PATCH] Fix-it hints

login
register
mail settings
Submitter Manuel López-Ibáñez
Date Aug. 31, 2014, 9:57 p.m.
Message ID <CAESRpQCLTvV5K=D7fBpmvZJGE+iavpRBGpcsnVxHh=EhLFjsPQ@mail.gmail.com>
Download mbox | patch
Permalink /patch/384590/
State New
Headers show

Comments

Manuel López-Ibáñez - Aug. 31, 2014, 9:57 p.m.
This patch implements fix-it hints. See https://gcc.gnu.org/PR62314

When the caret line is active (which is the default), this adds an
additional source-line indicating how to fix the code:

gcc/testsuite/g++.dg/template/crash83.C:5:21: error: an explicit
specialization must be preceded by 'template <>'
 template<typename = class A<0>: > struct B {}; // { dg-error
"explicit specialization|expected" }
                     ^
                     template<>

When the caret line is disabled with -fno-diagnostics-show-caret, the
fix-it hint is printed as:

gcc/testsuite/g++.dg/template/crash83.C:5:21: error: an explicit
specialization must be preceded by 'template <>'
gcc/testsuite/g++.dg/template/crash83.C:5:21: fixit: template<>

The latter form may allow an IDE (such as emacs) to automatically apply the fix.

Currently, fix-it hints are limited to insertions at one single
location, whereas Clang allows insertions, deletions, and replacements
at arbitrary location ranges.

Opinions? Is the proposed interface/implementation acceptable?

Any other diagnostics that could use a fix-it hint? In principle, we
should only give them when we are sure that the proposed fix will fix
the error or silence a warning.  For example, the C++ parser often
says 'x' expected before 'y' but adding x before y rarely fixes
anything.

gcc/ChangeLog:

2014-08-31  Manuel López-Ibáñez  <manu@gcc.gnu.org>

    PR c++/62314
    * diagnostic.def (DK_FIXIT): New.
    * diagnostic.c (adjust_column): New.
    (adjust_line): Factor out adjust_column.
    (get_source_line_and_column): New.
    (diagnostic_show_locus): Call get_source_line_and_column.
    (diagnostic_action_after_output): Ignore DK_FIXIT.
    (fixit_hint): New.
    * diagnostic-core.h (fixit_hint): Declare.

gcc/cp/ChangeLog:

2014-08-31  Manuel López-Ibáñez  <manu@gcc.gnu.org>

    PR c++/62314
    * parser.c (cp_parser_diagnose_invalid_type_name): Call fixit_hint.
    (cp_parser_expression_statement): Likewise.
    (cp_parser_class_head): Likewise. Update location.


gcc/testsuite/ChangeLog:

2014-08-31  Manuel López-Ibáñez  <manu@gcc.gnu.org>

    PR c++/62314
    * g++.old-deja/g++.oliva/typename1.C: Handle fixit hint.
    * g++.old-deja/g++.oliva/typename2.C: Likewise.
    * g++.old-deja/g++.other/typename1.C: Likewise.
    * g++.old-deja/g++.pt/typename6.C: Likewise.
    * g++.old-deja/g++.pt/typename3.C: Likewise.
    * g++.old-deja/g++.pt/typename4.C: Likewise.
    * g++.dg/parse/error36.C: Likewise.
    * g++.dg/parse/typedef2.C: Likewise.
    * g++.dg/template/error6.C: Likewise.
    * g++.dg/template/dependent-name5.C: Likewise.
    * g++.dg/template/crash83.C: Likewise.
    * g++.dg/template/typename3.C: Likewise.

Patch

Index: gcc/diagnostic.def
===================================================================
--- gcc/diagnostic.def	(revision 214756)
+++ gcc/diagnostic.def	(working copy)
@@ -35,10 +35,11 @@  DEFINE_DIAGNOSTIC_KIND (DK_ICE, "interna
 DEFINE_DIAGNOSTIC_KIND (DK_ERROR, "error: ", "error")
 DEFINE_DIAGNOSTIC_KIND (DK_SORRY, "sorry, unimplemented: ", "error")
 DEFINE_DIAGNOSTIC_KIND (DK_WARNING, "warning: ", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_ANACHRONISM, "anachronism: ", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_NOTE, "note: ", "note")
+DEFINE_DIAGNOSTIC_KIND (DK_FIXIT, "fixit: ", "note")
 DEFINE_DIAGNOSTIC_KIND (DK_DEBUG, "debug: ", "note")
 /* These two would be re-classified as DK_WARNING or DK_ERROR, so the
 prefix does not matter.  */
 DEFINE_DIAGNOSTIC_KIND (DK_PEDWARN, "pedwarn: ", NULL)
 DEFINE_DIAGNOSTIC_KIND (DK_PERMERROR, "permerror: ", NULL)
Index: gcc/diagnostic.c
===================================================================
--- gcc/diagnostic.c	(revision 214756)
+++ gcc/diagnostic.c	(working copy)
@@ -254,61 +254,81 @@  diagnostic_build_prefix (diagnostic_cont
 			     s.column, locus_ce, text_cs, text, text_ce)
      : build_message_string ("%s%s:%d:%s %s%s%s", locus_cs, s.file, s.line, locus_ce,
 			     text_cs, text, text_ce));
 }
 
+
+static int
+adjust_column (int line_width, int max_width, int column)
+
+{
+  int right_margin = 10;
+  gcc_checking_assert (line_width >= column);
+  if (line_width >= max_width)
+    {
+      right_margin = MIN (line_width - column, right_margin);
+      right_margin = max_width - right_margin;
+      if (column > right_margin)
+	return right_margin;
+    }
+  return column;
+}
+
 /* If LINE is longer than MAX_WIDTH, and COLUMN is not smaller than
    MAX_WIDTH by some margin, then adjust the start of the line such
    that the COLUMN is smaller than MAX_WIDTH minus the margin.  The
    margin is either 10 characters or the difference between the column
    and the length of the line, whatever is smaller.  The length of
    LINE is given by LINE_WIDTH.  */
 static const char *
 adjust_line (const char *line, int line_width,
 	     int max_width, int *column_p)
 {
-  int right_margin = 10;
-  int column = *column_p;
+  int old_column = *column_p;
 
-  gcc_checking_assert (line_width >= column);
-  right_margin = MIN (line_width - column, right_margin);
-  right_margin = max_width - right_margin;
-  if (line_width >= max_width && column > right_margin)
-    {
-      line += column - right_margin;
-      *column_p = right_margin;
-    }
+  *column_p = adjust_column (line_width, max_width, old_column);
+  line += old_column - *column_p;
   return line;
 }
 
+static const char *
+get_source_line_and_column (location_t loc, int *line_width, int *column)
+{
+  expanded_location s = expand_location_to_spelling_point (loc);
+  const char * line = location_get_source_line (s, line_width);
+  *column = s.column;
+  if (s.column > *line_width)
+    return NULL;
+
+  return line;
+}
 /* Print the physical source line corresponding to the location of
    this diagnostic, and a caret indicating the precise column.  */
 void
 diagnostic_show_locus (diagnostic_context * context,
 		       const diagnostic_info *diagnostic)
 {
   const char *line;
-  int line_width;
+  int line_width, column;
   char *buffer;
-  expanded_location s;
   int max_width;
   const char *saved_prefix;
   const char *caret_cs, *caret_ce;
 
   if (!context->show_caret
       || diagnostic->location <= BUILTINS_LOCATION
       || diagnostic->location == context->last_location)
     return;
 
   context->last_location = diagnostic->location;
-  s = expand_location_to_spelling_point (diagnostic->location);
-  line = location_get_source_line (s, &line_width);
-  if (line == NULL || s.column > line_width)
+  line = get_source_line_and_column (diagnostic->location,
+				     &line_width, &column);
+  if (line == NULL)
     return;
 
   max_width = context->caret_max_width;
-  line = adjust_line (line, line_width, max_width, &(s.column));
+  line = adjust_line (line, line_width, max_width, &column);
 
   pp_newline (context->printer);
   saved_prefix = pp_get_prefix (context->printer);
   pp_set_prefix (context->printer, NULL);
   pp_space (context->printer);
@@ -325,13 +345,13 @@  diagnostic_show_locus (diagnostic_contex
   pp_newline (context->printer);
   caret_cs = colorize_start (pp_show_color (context->printer), "caret");
   caret_ce = colorize_stop (pp_show_color (context->printer));
 
   /* pp_printf does not implement %*c.  */
-  size_t len = s.column + 3 + strlen (caret_cs) + strlen (caret_ce);
+  size_t len = column + 3 + strlen (caret_cs) + strlen (caret_ce);
   buffer = XALLOCAVEC (char, len);
-  snprintf (buffer, len, "%s %*c%s", caret_cs, s.column, context->caret_char,
+  snprintf (buffer, len, "%s %*c%s", caret_cs, column, context->caret_char,
 	    caret_ce);
   pp_string (context->printer, buffer);
   pp_set_prefix (context->printer, saved_prefix);
   pp_needs_newline (context->printer) = true;
 }
@@ -439,10 +459,11 @@  diagnostic_action_after_output (diagnost
     {
     case DK_DEBUG:
     case DK_NOTE:
     case DK_ANACHRONISM:
     case DK_WARNING:
+    case DK_FIXIT:
       break;
 
     case DK_ERROR:
     case DK_SORRY:
       if (context->abort_on_error)
@@ -940,10 +961,55 @@  inform (location_t location, const char
   diagnostic_set_info (&diagnostic, gmsgid, &ap, location, DK_NOTE);
   report_diagnostic (&diagnostic);
   va_end (ap);
 }
 
+/* A fix-it hint at LOCATION.  Use this recommended textual
+   replacements after another diagnostic message.  */
+void
+fixit_hint (location_t location, const char *msg, ...)
+{
+  diagnostic_info diagnostic;
+  va_list ap;
+
+  va_start (ap, msg);
+  diagnostic_set_info (&diagnostic, msg, &ap, location, DK_FIXIT);
+  if (!global_dc->show_caret)
+    {
+      report_diagnostic (&diagnostic);
+    }
+  else 
+    {
+      diagnostic_context * context = global_dc;
+      const char *line;
+      int line_width, column;
+      line = get_source_line_and_column (location, &line_width, &column);
+      if (line == NULL)
+	{
+	  va_end (ap);
+	  return;
+	}
+      column = adjust_column (line_width, context->caret_max_width, column);
+
+      const char *saved_prefix = pp_get_prefix (context->printer);
+      pp_set_prefix (context->printer, NULL);
+
+      const char *caret_cs, *caret_ce;
+      caret_cs = colorize_start (pp_show_color (context->printer), "caret");
+      caret_ce = colorize_stop (pp_show_color (context->printer));
+
+      /* pp_printf does not implement %*c.  */
+      size_t len = strlen (caret_cs) + column + 3 + strlen(msg) + strlen (caret_ce);
+      char * buffer = XALLOCAVEC (char, len);
+      snprintf (buffer, len, "%s%*c%s%s", caret_cs, column, ' ', msg, caret_ce);
+      pp_string (context->printer, buffer);
+      pp_newline_and_flush (context->printer);
+      pp_set_prefix (context->printer, saved_prefix);
+    }
+  va_end (ap);
+}
+
 /* An informative note at LOCATION.  Use this for additional details on an
    error message.  */
 void
 inform_n (location_t location, int n, const char *singular_gmsgid,
           const char *plural_gmsgid, ...)
Index: gcc/diagnostic-core.h
===================================================================
--- gcc/diagnostic-core.h	(revision 214756)
+++ gcc/diagnostic-core.h	(working copy)
@@ -70,10 +70,11 @@  extern void fatal_error (const char *, .
 extern bool pedwarn (location_t, int, const char *, ...)
      ATTRIBUTE_GCC_DIAG(3,4);
 extern bool permerror (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3);
 extern void sorry (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
 extern void inform (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3);
+extern void fixit_hint (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3);
 extern void inform_n (location_t, int, const char *, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,5) ATTRIBUTE_GCC_DIAG(4,5);
 extern void verbatim (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
 extern bool emit_diagnostic (diagnostic_t, location_t, int,
 			     const char *, ...) ATTRIBUTE_GCC_DIAG(4,5);
Index: gcc/testsuite/g++.old-deja/g++.oliva/typename1.C
===================================================================
--- gcc/testsuite/g++.old-deja/g++.oliva/typename1.C	(revision 214756)
+++ gcc/testsuite/g++.old-deja/g++.oliva/typename1.C	(working copy)
@@ -10,7 +10,8 @@  template <class T> struct foo;
 template <class T> struct bar {
   typedef int foo;
 };
 
 template <class T> struct baz {
-  typedef bar<T>::foo foo; // { dg-error "" } missing typename
+  typedef bar<T>::foo foo; // { dg-error "typename" "missing typename" }
+  // { dg-message "11:fixit: typename" "fixit" { target *-*-*} 15 }
 };
Index: gcc/testsuite/g++.old-deja/g++.oliva/typename2.C
===================================================================
--- gcc/testsuite/g++.old-deja/g++.oliva/typename2.C	(revision 214756)
+++ gcc/testsuite/g++.old-deja/g++.oliva/typename2.C	(working copy)
@@ -21,8 +21,9 @@  struct foo;
 template <class T> struct bar {
   typedef int foo;
 };
 
 template <class T> struct baz {
-  typedef bar<T>::foo foo; // { dg-error "" } implicit typename
+  typedef bar<T>::foo foo; // { dg-error "typename" "implicit typename" }
+  // { dg-message "11:fixit: typename" "fixit" { target *-*-*} 26 }
   void m(foo); 
 };
Index: gcc/testsuite/g++.old-deja/g++.other/typename1.C
===================================================================
--- gcc/testsuite/g++.old-deja/g++.other/typename1.C	(revision 214756)
+++ gcc/testsuite/g++.old-deja/g++.other/typename1.C	(working copy)
@@ -12,6 +12,7 @@  public:
 
 template<class T>
 void f()
 {
   Vector<T>::iterator i = 0; // { dg-error "typename" "typename" } missing typename
-} // { dg-error "expected" "expected" { target *-*-* } 16 }
+  // { dg-message "3:fixit: typename" "fixit" { target *-*-*} 16 }
+} // { dg-bogus "expected" "expected" { xfail *-*-* } 16 }
Index: gcc/testsuite/g++.old-deja/g++.pt/typename6.C
===================================================================
--- gcc/testsuite/g++.old-deja/g++.pt/typename6.C	(revision 214756)
+++ gcc/testsuite/g++.old-deja/g++.pt/typename6.C	(working copy)
@@ -14,7 +14,8 @@  struct B : public A<U>
   // { dg-message "note" "note" { target *-*-* } 13 }
 };
 
 template <class U>
 A<U>::A_Type B<U>::Func()       // { dg-error "typename" } function
+// { dg-message "1:fixit: typename" "fixit" { target *-*-*} 18 }
 {				
 }
Index: gcc/testsuite/g++.old-deja/g++.pt/typename3.C
===================================================================
--- gcc/testsuite/g++.old-deja/g++.pt/typename3.C	(revision 214756)
+++ gcc/testsuite/g++.old-deja/g++.pt/typename3.C	(working copy)
@@ -16,6 +16,7 @@  struct B : public A<U>
 };
 
 
 template <class U>
 B<U>::A_Type B<U>::Func() { // { dg-error "typename" } implicit typename
+ // { dg-message "1:fixit: typename" "fixit" { target *-*-*} 20 }
 }
Index: gcc/testsuite/g++.old-deja/g++.pt/typename4.C
===================================================================
--- gcc/testsuite/g++.old-deja/g++.pt/typename4.C	(revision 214756)
+++ gcc/testsuite/g++.old-deja/g++.pt/typename4.C	(working copy)
@@ -21,6 +21,7 @@  struct C : public B<U>
 };
 
 
 template <class U>
 C<U>::A_Type C<U>::Func() { // { dg-error "typename" } implicit typename
+// { dg-message "1:fixit: typename" "fixit" { target *-*-*} 25 }
 }
Index: gcc/testsuite/g++.dg/parse/error36.C
===================================================================
--- gcc/testsuite/g++.dg/parse/error36.C	(revision 214756)
+++ gcc/testsuite/g++.dg/parse/error36.C	(working copy)
@@ -9,23 +9,28 @@  template <class T> struct A
 
 template <class T>
 void f(T t)
 {
   typedef A<T>::foo type;	// { dg-error "typename" }
+  // { dg-message "11:fixit: typename" "fixit" { target *-*-* } 13 }
   A<T>::bar b;			// { dg-error "typename" "typename" }
-} // { dg-error "expected ';'" "expected" { target *-*-* } 14 }
+  // { dg-message "3:fixit: typename" "fixit" { target *-*-* } 15 }
+} // { dg-bogus "expected ';'" "expected" { xfail *-*-* } 15 }
 
 // PR c++/36353
 template <class T> struct B
 {
   void f()
   {
     A<T>::baz z;		// { dg-error "typename" "typename" }
-  } // { dg-error "expected ';'" "expected" { target *-*-* } 22 }
+  // { dg-message "5:fixit: typename" "fixit" { target *-*-* } 24 }
+  } // { dg-bogus "expected ';'" "expected" { xfail *-*-* } 24 }
 };
 
 // PR c++/40738
 template <class T>
 void g(const A<T>::type &t);	// { dg-error "typename" "typename" }
+// { dg-message "14:fixit: typename" "fixit" { target *-*-* } 31 }
 
 // PR c++/18451
 template <class T> A<T>::B A<T>::b; // { dg-error "typename" }
+// { dg-message "20:fixit: typename" "fixit" { target *-*-* } 35 }
Index: gcc/testsuite/g++.dg/parse/typedef2.C
===================================================================
--- gcc/testsuite/g++.dg/parse/typedef2.C	(revision 214756)
+++ gcc/testsuite/g++.dg/parse/typedef2.C	(working copy)
@@ -1,3 +1,4 @@ 
 template <typename T> struct B { typedef typename T::X X; };
-template <typename T> struct A { typedef B<T>::X::Y Z; }; // { dg-error "" }
- 
+template <typename T> struct A { typedef B<T>::X::Y Z; }; // { dg-error "typename" }
+// { dg-message "42:fixit: typename" "fixit" { target *-*-* } 2 }
+
Index: gcc/testsuite/g++.dg/template/error6.C
===================================================================
--- gcc/testsuite/g++.dg/template/error6.C	(revision 214756)
+++ gcc/testsuite/g++.dg/template/error6.C	(working copy)
@@ -2,10 +2,11 @@  template<int n>
 struct tento {
   enum {value = 10*tento<n-1>::value};
 };
 
 struct tento<0> { // { dg-error "" }
+  // { dg-message "1:fixit: template<>" "fixit" { target *-*-*} 6 }
    enum {value=1};
 };
 
 int main() {
   if (tento<4>::value != 10000) return -1;
Index: gcc/testsuite/g++.dg/template/dependent-name5.C
===================================================================
--- gcc/testsuite/g++.dg/template/dependent-name5.C	(revision 214756)
+++ gcc/testsuite/g++.dg/template/dependent-name5.C	(working copy)
@@ -15,17 +15,19 @@  struct A
   struct N {};
 
   typedef Bar          type1;
   typedef A::Bar       type2;
   typedef A<T>::Bar    type3;
-  typedef A<T*>::Bar    type4;  // { dg-error "" }
+  typedef A<T*>::Bar    type4;  // { dg-error "typename" }
+  // { dg-message "11:fixit: typename" "fixit" { target *-*-*} 20 }
   typedef typename A<T*>::Bar type5;
 
   typedef N<int>       type6;
   typedef A::N<int>    type7;
   typedef A<T>::N<int> type8;
-  typedef A<T*>::template N<int> type9;  // { dg-error "" }
+  typedef A<T*>::template N<int> type9;  // { dg-error "typename" }
+  // { dg-message "11:fixit: typename" "fixit" { target *-*-*} 27 }
   typedef typename A<T*>::template N<int> type10;
 
   typedef D Bar2;
   struct N2 { typedef int K; };
 
@@ -34,11 +36,12 @@  struct A
   typedef A::Bar2 type11;
   typedef type11::K k3;
 
   typedef A::N2 type12;
   typedef typename type12::K k2;
-  typedef type12::K k1;  // { dg-error "" }
+  typedef type12::K k1;  // { dg-error "typename" }
+  // { dg-message "11:fixit: typename" "fixit" { target *-*-*} 41 }
 
   // Check that A::Bar2 is not considered dependent even if we use
   // the typename keyword.
   typedef typename A::Bar2 type13;
   typedef type13::K k4;
Index: gcc/testsuite/g++.dg/template/crash83.C
===================================================================
--- gcc/testsuite/g++.dg/template/crash83.C	(revision 214756)
+++ gcc/testsuite/g++.dg/template/crash83.C	(working copy)
@@ -1,5 +1,6 @@ 
 // PR c++/37650
 
 template<int> struct A {};
 
 template<typename = class A<0>: > struct B {}; // { dg-error "explicit specialization|expected" }
+// { dg-message "21:fixit: template<>" "fixit" { target *-*-* } 5 }
Index: gcc/testsuite/g++.dg/template/typename3.C
===================================================================
--- gcc/testsuite/g++.dg/template/typename3.C	(revision 214756)
+++ gcc/testsuite/g++.dg/template/typename3.C	(working copy)
@@ -2,6 +2,7 @@ 
 // crash test - PR 7266
 
 template <class A>
 struct B {
  typedef A::C::D E;  // { dg-error "" }
+  // { dg-message "10:fixit: typename" "fixit" { target *-*-*} 6 }
 };
Index: gcc/cp/parser.c
===================================================================
--- gcc/cp/parser.c	(revision 214756)
+++ gcc/cp/parser.c	(working copy)
@@ -2992,13 +2992,16 @@  cp_parser_diagnose_invalid_type_name (cp
 	    error_at (location, "and %qT has no template constructors",
 		      parser->scope);
 	}
       else if (TYPE_P (parser->scope)
 	       && dependent_scope_p (parser->scope))
-	error_at (location, "need %<typename%> before %<%T::%E%> because "
-		  "%qT is a dependent scope",
-		  parser->scope, id, parser->scope);
+	{
+	  error_at (location, "%<typename%> is needed before %<%T::%E%> because "
+		    "%qT is a dependent scope",
+		    parser->scope, id, parser->scope);
+	  fixit_hint (location, "typename ");
+	}
       else if (TYPE_P (parser->scope))
 	{
 	  if (cp_lexer_next_token_is (parser->lexer, CPP_LESS))
 	    error_at (location_of (id),
 		      "%qE in %q#T does not name a template type",
@@ -9813,13 +9816,16 @@  cp_parser_expression_statement (cp_parse
   /* Give a helpful message for "A<T>::type t;" and the like.  */
   if (cp_lexer_next_token_is_not (parser->lexer, CPP_SEMICOLON)
       && !cp_parser_uncommitted_to_tentative_parse_p (parser))
     {
       if (TREE_CODE (statement) == SCOPE_REF)
-	error_at (token->location, "need %<typename%> before %qE because "
-		  "%qT is a dependent scope",
-		  statement, TREE_OPERAND (statement, 0));
+	{
+	  error_at (token->location, "%<typename%> is needed before %qE because "
+		    "%qT is a dependent scope",
+		    statement, TREE_OPERAND (statement, 0));
+	  fixit_hint (token->location, "typename ");
+	}
       else if (is_overloaded_fn (statement)
 	       && DECL_CONSTRUCTOR_P (get_first_fn (statement)))
 	{
 	  /* A::A a; */
 	  tree fn = get_first_fn (statement);
@@ -19820,10 +19826,12 @@  cp_parser_class_head (cp_parser* parser,
   /* Look for the class-key.  */
   class_key = cp_parser_class_key (parser);
   if (class_key == none_type)
     return error_mark_node;
 
+  location_t class_head_start_location = input_location;
+
   /* Parse the attributes.  */
   attributes = cp_parser_attributes_opt (parser);
 
   /* If the next token is `::', that is invalid -- but sometimes
      people do try to write:
@@ -20036,12 +20044,14 @@  cp_parser_class_head (cp_parser* parser,
      it is not, try to recover gracefully.  */
   if (at_namespace_scope_p ()
       && parser->num_template_parameter_lists == 0
       && template_id_p)
     {
-      error_at (type_start_token->location,
+      error_at (class_head_start_location,
 		"an explicit specialization must be preceded by %<template <>%>");
+      fixit_hint (class_head_start_location, "template<> ");
+
       invalid_explicit_specialization_p = true;
       /* Take the same action that would have been taken by
 	 cp_parser_explicit_specialization.  */
       ++parser->num_template_parameter_lists;
       begin_specialization ();