From 1f0775d4103d383ac40f9e544762610b5e4aa983 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 29 Oct 2015 15:31:19 -0400
Subject: [PATCH 10/17] FIXME: C FE: suggest corrections for misspelled
 identifiers and type names

---
 gcc/c-family/c-common.h                       |  1 +
 gcc/c/c-decl.c                                | 79 +++++++++++++++++++++++--
 gcc/c/c-parser.c                              | 55 +++++++++++++++---
 gcc/diagnostic-core.h                         |  2 +
 gcc/diagnostic-show-locus.c                   |  3 +-
 gcc/diagnostic.c                              | 17 ++++++
 gcc/testsuite/gcc.dg/spellcheck-identifiers.c | 16 ++++++
 gcc/testsuite/gcc.dg/spellcheck-typenames.c   | 83 +++++++++++++++++++++++++++
 8 files changed, 242 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/spellcheck-identifiers.c
 create mode 100644 gcc/testsuite/gcc.dg/spellcheck-typenames.c

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index fa3746c..9616169 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -984,6 +984,7 @@ extern tree finish_label_address_expr (tree, location_t);
    different implementations.  Used in c-common.c.  */
 extern tree lookup_label (tree);
 extern tree lookup_name (tree);
+extern tree lookup_name_fuzzy (tree, bool);
 extern bool lvalue_p (const_tree);
 
 extern bool vector_targets_convertible_p (const_tree t1, const_tree t2);
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index bab4715..8df3a4e 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -51,6 +51,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "c-family/c-ada-spec.h"
 #include "cilk.h"
 #include "builtins.h"
+#include "spellcheck.h"
 
 /* In grokdeclarator, distinguish syntactic contexts of declarators.  */
 enum decl_context
@@ -3092,13 +3093,38 @@ implicit_decl_warning (location_t loc, tree id, tree olddecl)
   if (warn_implicit_function_declaration)
     {
       bool warned;
+      tree hint = NULL_TREE;
+      if (!olddecl)
+	hint = lookup_name_fuzzy (id, false);
 
       if (flag_isoc99)
-	warned = pedwarn (loc, OPT_Wimplicit_function_declaration,
-			  "implicit declaration of function %qE", id);
+	if (hint)
+	  {
+	    rich_location richloc (line_table, loc);
+	    richloc.add_fixit_replace (get_range_from_loc (line_table, loc),
+				       IDENTIFIER_POINTER (hint));
+	    warned = pedwarn_at_rich_loc
+	      (&richloc, OPT_Wimplicit_function_declaration,
+	       "implicit declaration of function %qE; did you mean %qE?",
+	       id, hint);
+	  }
+	else
+	  warned = pedwarn (loc, OPT_Wimplicit_function_declaration,
+			    "implicit declaration of function %qE", id);
       else
-	warned = warning_at (loc, OPT_Wimplicit_function_declaration,
-			     G_("implicit declaration of function %qE"), id);
+	if (hint)
+	  {
+	    rich_location richloc (line_table, loc);
+	    richloc.add_fixit_replace (get_range_from_loc (line_table, loc),
+				       IDENTIFIER_POINTER (hint));
+	    warned = warning_at_rich_loc
+	      (&richloc, OPT_Wimplicit_function_declaration,
+	       G_("implicit declaration of function %qE;did you mean %qE?"),
+	       id, hint);
+	  }
+	else
+	  warned = warning_at (loc, OPT_Wimplicit_function_declaration,
+			       G_("implicit declaration of function %qE"), id);
       if (olddecl && warned)
 	locate_old_decl (olddecl);
     }
@@ -3910,6 +3936,51 @@ lookup_name_in_scope (tree name, struct c_scope *scope)
       return b->decl;
   return NULL_TREE;
 }
+
+/* Look for the closest match for NAME within the currently valid
+   scopes.
+
+   This finds the identifier with the lowest Levenshtein distance to
+   NAME.  If there are multiple candidates with equal minimal distance,
+   the first one found is returned.  Scopes are searched from innermost
+   outwards, and within a scope in reverse order of declaration, thus
+   benefiting candidates "near" to the current scope.  */
+
+tree
+lookup_name_fuzzy (tree name, bool want_type_p)
+{
+  gcc_assert (TREE_CODE (name) == IDENTIFIER_NODE);
+
+  c_binding *best_binding = NULL;
+  edit_distance_t best_distance = MAX_EDIT_DISTANCE;
+
+  for (c_scope *scope = current_scope; scope; scope = scope->outer)
+    for (c_binding *binding = scope->bindings; binding; binding = binding->prev)
+      {
+	if (!binding->id)
+	  continue;
+	if (want_type_p)
+	  if (TREE_CODE (binding->decl) != TYPE_DECL)
+	    continue;
+	edit_distance_t dist = levenshtein_distance (name, binding->id);
+	if (dist < best_distance)
+	  {
+	    best_distance = dist;
+	    best_binding = binding;
+	  }
+      }
+
+  if (!best_binding)
+    return NULL;
+
+  /* If more than half of the letters were misspelled, the suggestion is
+     likely to be meaningless.  */
+  if (best_distance > IDENTIFIER_LENGTH (name) / 2 )
+    return NULL;
+
+  return best_binding->id;
+}
+
 
 /* Create the predefined scalar types of C,
    and some nodes representing standard constants (0, 1, (void *) 0).
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 7bb4493..8ada238 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -1654,15 +1654,52 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
       && (!nested || !lookup_name (c_parser_peek_token (parser)->value)))
     {
       tree name = c_parser_peek_token (parser)->value;
-      error_at (here, "unknown type name %qE", name);
-      /* Give a hint to the user.  This is not C++ with its implicit
-	 typedef.  */
-      if (tag_exists_p (RECORD_TYPE, name))
-	inform (here, "use %<struct%> keyword to refer to the type");
-      else if (tag_exists_p (UNION_TYPE, name))
-	inform (here, "use %<union%> keyword to refer to the type");
-      else if (tag_exists_p (ENUMERAL_TYPE, name))
-	inform (here, "use %<enum%> keyword to refer to the type");
+
+      /* Issue a warning about NAME being an unknown type name, perhaps
+	 with some kind of hint.
+	 If the user forgot a "struct" etc, suggest inserting
+	 it.  Otherwise, attempt to look for misspellings.  */
+      tree hint = NULL_TREE;
+      if (!(tag_exists_p (RECORD_TYPE, name)
+	    || tag_exists_p (UNION_TYPE, name)
+	    || tag_exists_p (ENUMERAL_TYPE, name)))
+	{
+	  hint = lookup_name_fuzzy (name, true);
+	}
+      if (hint)
+	{
+	  rich_location richloc (line_table, here);
+	  richloc.add_fixit_replace (get_range_from_loc (line_table, here),
+				     IDENTIFIER_POINTER (hint));
+	  error_at_rich_loc (&richloc,
+			     "unknown type name %qE; did you mean %qE?",
+			     name, hint);
+	}
+      else
+	{
+	  error_at (here, "unknown type name %qE", name);
+	  /* Give a hint to the user.  This is not C++ with its implicit
+	     typedef.  */
+	  rich_location richloc (line_table, here);
+	  if (tag_exists_p (RECORD_TYPE, name))
+	    {
+	      richloc.add_fixit_insert (here, "struct");
+	      inform_at_rich_loc
+		(&richloc, "use %<struct%> keyword to refer to the type");
+	    }
+	  else if (tag_exists_p (UNION_TYPE, name))
+	    {
+	      richloc.add_fixit_insert (here, "union");
+	      inform_at_rich_loc
+		(&richloc, "use %<union%> keyword to refer to the type");
+	    }
+	  else if (tag_exists_p (ENUMERAL_TYPE, name))
+	    {
+	      richloc.add_fixit_insert (here, "enum");
+	      inform_at_rich_loc
+		(&richloc, "use %<enum%> keyword to refer to the type");
+	    }
+	}
 
       /* Parse declspecs normally to get a correct pointer type, but avoid
          a further "fails to be a type name" error.  Refuse nested functions
diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
index f783761..51df150 100644
--- a/gcc/diagnostic-core.h
+++ b/gcc/diagnostic-core.h
@@ -76,6 +76,8 @@ extern void fatal_error (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3)
 /* Pass one of the OPT_W* from options.h as the second parameter.  */
 extern bool pedwarn (location_t, int, const char *, ...)
      ATTRIBUTE_GCC_DIAG(3,4);
+extern bool pedwarn_at_rich_loc (rich_location *, int, const char *, ...)
+     ATTRIBUTE_GCC_DIAG(3,4);
 extern bool permerror (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3);
 extern bool permerror_at_rich_loc (rich_location *, const char *,
 				   ...) ATTRIBUTE_GCC_DIAG(2,3);
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index 8618fa6..edad0aa 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -1105,7 +1105,8 @@ diagnostic_show_locus (diagnostic_context * context,
 
   if (!context->show_caret
       || diagnostic_location (diagnostic, 0) <= BUILTINS_LOCATION
-      || diagnostic_location (diagnostic, 0) == context->last_location)
+      || (diagnostic_location (diagnostic, 0) == context->last_location
+	  && diagnostic->richloc->get_num_fixit_hints () == 0))
     return;
 
   context->last_location = diagnostic_location (diagnostic, 0);
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index 6a679cb..8aa7a04 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -1098,6 +1098,23 @@ pedwarn (location_t location, int opt, const char *gmsgid, ...)
   return ret;
 }
 
+/* Same as pedwarn, but using RICHLOC.  */
+
+bool
+pedwarn_at_rich_loc (rich_location *richloc, int opt, const char *gmsgid, ...)
+{
+  diagnostic_info diagnostic;
+  va_list ap;
+  bool ret;
+
+  va_start (ap, gmsgid);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, richloc,  DK_PEDWARN);
+  diagnostic.option_index = opt;
+  ret = report_diagnostic (&diagnostic);
+  va_end (ap);
+  return ret;
+}
+
 /* A "permissive" error at LOCATION: issues an error unless
    -fpermissive was given on the command line, in which case it issues
    a warning.  Use this for things that really should be errors but we
diff --git a/gcc/testsuite/gcc.dg/spellcheck-identifiers.c b/gcc/testsuite/gcc.dg/spellcheck-identifiers.c
new file mode 100644
index 0000000..f0faa27
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/spellcheck-identifiers.c
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-Wimplicit-function-declaration -fdiagnostics-show-caret" } */
+
+typedef struct GtkWidget { int dummy; } GtkWidget;
+
+extern void gtk_widget_show_all (GtkWidget *w);
+
+void test (GtkWidget *w)
+{
+  gtk_widget_showall (w); /* { dg-warning "3: implicit declaration of function .gtk_widget_showall.; did you mean .gtk_widget_show_all.?" } */
+  /* { dg-begin-multiline-output "" }
+   gtk_widget_showall (w);
+   ^~~~~~~~~~~~~~~~~~
+   gtk_widget_show_all
+   { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/spellcheck-typenames.c b/gcc/testsuite/gcc.dg/spellcheck-typenames.c
new file mode 100644
index 0000000..30d86fa
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/spellcheck-typenames.c
@@ -0,0 +1,83 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+#include <inttypes.h>
+int64 i; /* { dg-error "unknown type name 'int64'; did you mean 'int64_t'?" } */
+/* { dg-begin-multiline-output "" }
+ int64 i;
+ ^~~~~
+ int64_t
+   { dg-end-multiline-output "" } */
+
+/* Verify that gcc doesn't offer nonsensical suggestions.  */
+
+nonsensical_suggestion_t var; /* { dg-bogus "did you mean" } */
+/* { dg-error "unknown type name" "" { target { *-*-* } } 14 } */
+/* { dg-begin-multiline-output "" }
+ nonsensical_suggestion_t var;
+ ^~~~~~~~~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
+
+
+/* In the following, we should suggest inserting "struct" (rather
+   than "did you mean 'float'").  */
+
+struct foo_t {
+  int i;
+};
+foo_t *foo_ptr; /* { dg-error "1: unknown type name .foo_t." } */
+/* { dg-begin-multiline-output "" }
+ foo_t *foo_ptr;
+ ^~~~~
+   { dg-end-multiline-output "" } */
+
+/* Verify that we get a fixit hint:  */
+/* { dg-message "1: use .struct. keyword to refer to the type" "" { target *-*-* } 28 } */
+/* { dg-begin-multiline-output "" }
+ foo_t *foo_ptr;
+ ^~~~~
+ struct
+   { dg-end-multiline-output "" } */
+
+
+/* Similarly for unions.  */
+
+union bar_t {
+  int i;
+  char j;
+};
+bar_t *bar_ptr; /* { dg-error "1: unknown type name .bar_t." } */
+/* { dg-begin-multiline-output "" }
+ bar_t *bar_ptr;
+ ^~~~~
+   { dg-end-multiline-output "" } */
+
+/* Verify that we get a fixit hint:  */
+/* { dg-message "1: use .union. keyword to refer to the type" "" { target *-*-* } 49 } */
+/* { dg-begin-multiline-output "" }
+ bar_t *bar_ptr;
+ ^~~~~
+ union
+   { dg-end-multiline-output "" } */
+
+
+/* Similarly for enums.  */
+
+enum baz {
+  BAZ_FIRST,
+  BAZ_SECOND
+};
+
+baz value; /* { dg-error "1: unknown type name .baz." } */
+/* { dg-begin-multiline-output "" }
+ baz value;
+ ^~~
+   { dg-end-multiline-output "" } */
+
+/* Verify that we get a fixit hint:  */
+/* { dg-message "1: use .enum. keyword to refer to the type" "" { target *-*-* } 71 } */
+/* { dg-begin-multiline-output "" }
+ baz value;
+ ^~~
+ enum
+   { dg-end-multiline-output "" } */
-- 
1.8.5.3

