[fortran,v4] Use Levenshtein spelling suggestions in Fortran FE
diff mbox

Message ID 1466279927-9661-1-git-send-email-rep.dot.nop@gmail.com
State New
Headers show

Commit Message

Bernhard Reutner-Fischer June 18, 2016, 7:58 p.m. UTC
Hi,

Ok for trunk?

Changes for v4 -> v3:

- rebased
- Use 4 argument levenshtein_distance() to save multiple strlen(typo)
  calls as suggested by dmalcolm

Changes for v2 -> v3:

- rebased

Changes for v1 -> v2:

- subroutines using interfaces
- keyword arguments (named parameters)

Rewrite C++ autovec in plain C.
Factor out levenshtein distance handling into a commonly used
gfc_closest_fuzzy_match().

gcc/fortran/ChangeLog

2015-12-27  Bernhard Reutner-Fischer  <aldot@gcc.gnu.org>

	* gfortran.h (gfc_lookup_function_fuzzy): New declaration.
	(gfc_closest_fuzzy_match): New declaration.
	(vec_push): New definition.
	* misc.c (gfc_closest_fuzzy_match): New definition.
	* resolve.c: Include spellcheck.h.
	(lookup_function_fuzzy_find_candidates): New static function.
	(lookup_uop_fuzzy_find_candidates): Likewise.
	(lookup_uop_fuzzy): Likewise.
	(resolve_operator) <INTRINSIC_USER>: Call lookup_uop_fuzzy.
	(gfc_lookup_function_fuzzy): New definition.
	(resolve_unknown_f): Call gfc_lookup_function_fuzzy.
	* interface.c (check_interface0): Likewise.
	(lookup_arg_fuzzy_find_candidates): New static function.
	(lookup_arg_fuzzy ): Likewise.
	(compare_actual_formal): Call lookup_arg_fuzzy.
	* symbol.c: Include spellcheck.h.
	(lookup_symbol_fuzzy_find_candidates): New static function.
	(lookup_symbol_fuzzy): Likewise.
	(gfc_set_default_type): Call lookup_symbol_fuzzy.
	(lookup_component_fuzzy_find_candidates): New static function.
	(lookup_component_fuzzy): Likewise.
	(gfc_find_component): Call lookup_component_fuzzy.

gcc/testsuite/ChangeLog

2015-12-27  Bernhard Reutner-Fischer  <aldot@gcc.gnu.org>

	* gfortran.dg/spellcheck-operator.f90: New testcase.
	* gfortran.dg/spellcheck-procedure_1.f90: New testcase.
	* gfortran.dg/spellcheck-procedure_2.f90: New testcase.
	* gfortran.dg/spellcheck-structure.f90: New testcase.
	* gfortran.dg/spellcheck-parameter.f90: New testcase.

---

David Malcolm's nice Levenshtein distance spelling check helpers
were used in some parts of other frontends. This proposed patch adds
some spelling corrections to the fortran frontend.

Suggestions are printed if we can find a suitable name, currently
perusing a very simple cutoff factor:
/* If more than half of the letters were misspelled, the suggestion is
   likely to be meaningless.  */
cutoff = MAX (strlen (typo), strlen (best_guess)) / 2;
which effectively skips names with less than 4 characters.
For e.g. structures, one could try to be much smarter in an attempt to
also provide suggestions for single-letter members/components.

This patch covers (at least partly):
- user-defined operators
- structures (types and their components)
- functions
- symbols (variables)

If anybody has a testcase where a spelling-suggestion would make sense
then please pass it along so we maybe can add support for GCC-7.

Signed-off-by: Bernhard Reutner-Fischer <rep.dot.nop@gmail.com>
---
 gcc/fortran/gfortran.h                             |  12 +++
 gcc/fortran/interface.c                            |  72 +++++++++++++--
 gcc/fortran/misc.c                                 |  41 +++++++++
 gcc/fortran/resolve.c                              | 100 ++++++++++++++++++++-
 gcc/fortran/symbol.c                               |  86 +++++++++++++++++-
 gcc/testsuite/gfortran.dg/spellcheck-operator.f90  |  30 +++++++
 gcc/testsuite/gfortran.dg/spellcheck-parameter.f90 |  15 ++++
 .../gfortran.dg/spellcheck-procedure_1.f90         |  41 +++++++++
 .../gfortran.dg/spellcheck-procedure_2.f90         |  35 ++++++++
 gcc/testsuite/gfortran.dg/spellcheck-structure.f90 |  35 ++++++++
 10 files changed, 450 insertions(+), 17 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/spellcheck-operator.f90
 create mode 100644 gcc/testsuite/gfortran.dg/spellcheck-parameter.f90
 create mode 100644 gcc/testsuite/gfortran.dg/spellcheck-procedure_1.f90
 create mode 100644 gcc/testsuite/gfortran.dg/spellcheck-procedure_2.f90
 create mode 100644 gcc/testsuite/gfortran.dg/spellcheck-structure.f90

Comments

VandeVondele Joost June 20, 2016, 10:25 a.m. UTC | #1
From my point of view, would be really nice to have.

Joost
Bernhard Reutner-Fischer July 3, 2016, 10:42 p.m. UTC | #2
Ping

On June 18, 2016 9:58:47 PM GMT+02:00, Bernhard Reutner-Fischer <rep.dot.nop@gmail.com> wrote:
>Hi,
>
>Ok for trunk?
>
>Changes for v4 -> v3:
>
>- rebased
>- Use 4 argument levenshtein_distance() to save multiple strlen(typo)
>  calls as suggested by dmalcolm
>
>Changes for v2 -> v3:
>
>- rebased
>
>Changes for v1 -> v2:
>
>- subroutines using interfaces
>- keyword arguments (named parameters)
>
>Rewrite C++ autovec in plain C.
>Factor out levenshtein distance handling into a commonly used
>gfc_closest_fuzzy_match().
>
>gcc/fortran/ChangeLog
>
>2015-12-27  Bernhard Reutner-Fischer  <aldot@gcc.gnu.org>
>
>	* gfortran.h (gfc_lookup_function_fuzzy): New declaration.
>	(gfc_closest_fuzzy_match): New declaration.
>	(vec_push): New definition.
>	* misc.c (gfc_closest_fuzzy_match): New definition.
>	* resolve.c: Include spellcheck.h.
>	(lookup_function_fuzzy_find_candidates): New static function.
>	(lookup_uop_fuzzy_find_candidates): Likewise.
>	(lookup_uop_fuzzy): Likewise.
>	(resolve_operator) <INTRINSIC_USER>: Call lookup_uop_fuzzy.
>	(gfc_lookup_function_fuzzy): New definition.
>	(resolve_unknown_f): Call gfc_lookup_function_fuzzy.
>	* interface.c (check_interface0): Likewise.
>	(lookup_arg_fuzzy_find_candidates): New static function.
>	(lookup_arg_fuzzy ): Likewise.
>	(compare_actual_formal): Call lookup_arg_fuzzy.
>	* symbol.c: Include spellcheck.h.
>	(lookup_symbol_fuzzy_find_candidates): New static function.
>	(lookup_symbol_fuzzy): Likewise.
>	(gfc_set_default_type): Call lookup_symbol_fuzzy.
>	(lookup_component_fuzzy_find_candidates): New static function.
>	(lookup_component_fuzzy): Likewise.
>	(gfc_find_component): Call lookup_component_fuzzy.
>
>gcc/testsuite/ChangeLog
>
>2015-12-27  Bernhard Reutner-Fischer  <aldot@gcc.gnu.org>
>
>	* gfortran.dg/spellcheck-operator.f90: New testcase.
>	* gfortran.dg/spellcheck-procedure_1.f90: New testcase.
>	* gfortran.dg/spellcheck-procedure_2.f90: New testcase.
>	* gfortran.dg/spellcheck-structure.f90: New testcase.
>	* gfortran.dg/spellcheck-parameter.f90: New testcase.
>
>---
>
>David Malcolm's nice Levenshtein distance spelling check helpers
>were used in some parts of other frontends. This proposed patch adds
>some spelling corrections to the fortran frontend.
>
>Suggestions are printed if we can find a suitable name, currently
>perusing a very simple cutoff factor:
>/* If more than half of the letters were misspelled, the suggestion is
>   likely to be meaningless.  */
>cutoff = MAX (strlen (typo), strlen (best_guess)) / 2;
>which effectively skips names with less than 4 characters.
>For e.g. structures, one could try to be much smarter in an attempt to
>also provide suggestions for single-letter members/components.
>
>This patch covers (at least partly):
>- user-defined operators
>- structures (types and their components)
>- functions
>- symbols (variables)
>
>If anybody has a testcase where a spelling-suggestion would make sense
>then please pass it along so we maybe can add support for GCC-7.
>
>Signed-off-by: Bernhard Reutner-Fischer <rep.dot.nop@gmail.com>
>---
> gcc/fortran/gfortran.h                             |  12 +++
>gcc/fortran/interface.c                            |  72
>+++++++++++++--
> gcc/fortran/misc.c                                 |  41 +++++++++
>gcc/fortran/resolve.c                              | 100
>++++++++++++++++++++-
>gcc/fortran/symbol.c                               |  86
>+++++++++++++++++-
> gcc/testsuite/gfortran.dg/spellcheck-operator.f90  |  30 +++++++
> gcc/testsuite/gfortran.dg/spellcheck-parameter.f90 |  15 ++++
> .../gfortran.dg/spellcheck-procedure_1.f90         |  41 +++++++++
> .../gfortran.dg/spellcheck-procedure_2.f90         |  35 ++++++++
> gcc/testsuite/gfortran.dg/spellcheck-structure.f90 |  35 ++++++++
> 10 files changed, 450 insertions(+), 17 deletions(-)
> create mode 100644 gcc/testsuite/gfortran.dg/spellcheck-operator.f90
> create mode 100644 gcc/testsuite/gfortran.dg/spellcheck-parameter.f90
>create mode 100644 gcc/testsuite/gfortran.dg/spellcheck-procedure_1.f90
>create mode 100644 gcc/testsuite/gfortran.dg/spellcheck-procedure_2.f90
> create mode 100644 gcc/testsuite/gfortran.dg/spellcheck-structure.f90
>
>diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h
>index 0bb71cb..5d43c2d 100644
>--- a/gcc/fortran/gfortran.h
>+++ b/gcc/fortran/gfortran.h
>@@ -2682,6 +2682,17 @@ void gfc_done_2 (void);
> 
> int get_c_kind (const char *, CInteropKind_t *);
> 
>+const char *gfc_closest_fuzzy_match (const char *, char **);
>+static inline void
>+vec_push (char **&optr, size_t &osz, const char *elt)
>+{
>+  /* {auto,}vec.safe_push () replacement.  Don't ask..  */
>+  // if (strlen (elt) < 4) return; premature optimization: eliminated
>by cutoff
>+  optr = XRESIZEVEC (char *, optr, osz + 2);
>+  optr[osz] = CONST_CAST (char *, elt);
>+  optr[++osz] = NULL;
>+}
>+
> /* options.c */
> unsigned int gfc_option_lang_mask (void);
> void gfc_init_options_struct (struct gcc_options *);
>@@ -3103,6 +3114,7 @@ bool gfc_type_is_extensible (gfc_symbol *);
> bool gfc_resolve_intrinsic (gfc_symbol *, locus *);
> bool gfc_explicit_interface_required (gfc_symbol *, char *, int);
> extern int gfc_do_concurrent_flag;
>+const char* gfc_lookup_function_fuzzy (const char *, gfc_symtree *);
> 
> 
> /* array.c */
>diff --git a/gcc/fortran/interface.c b/gcc/fortran/interface.c
>index b012de5..bef514c 100644
>--- a/gcc/fortran/interface.c
>+++ b/gcc/fortran/interface.c
>@@ -1694,13 +1694,27 @@ check_interface0 (gfc_interface *p, const char
>*interface_name)
> 	   || !p->sym->attr.if_source)
> 	  && !gfc_fl_struct (p->sym->attr.flavor))
> 	{
>+	  const char *guessed
>+	    = gfc_lookup_function_fuzzy (p->sym->name, p->sym->ns->sym_root);
>+
> 	  if (p->sym->attr.external)
>-	    gfc_error ("Procedure %qs in %s at %L has no explicit interface",
>-		       p->sym->name, interface_name, &p->sym->declared_at);
>+	    if (guessed)
>+	      gfc_error ("Procedure %qs in %s at %L has no explicit
>interface"
>+			 "; did you mean %qs?",
>+			 p->sym->name, interface_name, &p->sym->declared_at,
>+			 guessed);
>+	    else
>+	      gfc_error ("Procedure %qs in %s at %L has no explicit
>interface",
>+			 p->sym->name, interface_name, &p->sym->declared_at);
> 	  else
>-	    gfc_error ("Procedure %qs in %s at %L is neither function nor "
>-		       "subroutine", p->sym->name, interface_name,
>-		      &p->sym->declared_at);
>+	    if (guessed)
>+	      gfc_error ("Procedure %qs in %s at %L is neither function nor "
>+			 "subroutine; did you mean %qs?", p->sym->name,
>+			interface_name, &p->sym->declared_at, guessed);
>+	    else
>+	      gfc_error ("Procedure %qs in %s at %L is neither function nor "
>+			 "subroutine", p->sym->name, interface_name,
>+			&p->sym->declared_at);
> 	  return 1;
> 	}
> 
>@@ -2684,6 +2698,31 @@ is_procptr_result (gfc_expr *expr)
> }
> 
> 
>+/* Recursively append candidate argument ARG to CANDIDATES.  Store the
>+   number of total candidates in CANDIDATES_LEN.  */
>+
>+static void
>+lookup_arg_fuzzy_find_candidates (gfc_formal_arglist *arg,
>+				  char **&candidates,
>+				  size_t &candidates_len)
>+{
>+  for (gfc_formal_arglist *p = arg; p && p->sym; p = p->next)
>+    vec_push (candidates, candidates_len, p->sym->name);
>+}
>+
>+
>+/* Lookup argument ARG fuzzily, taking names in ARGUMENTS into
>account.  */
>+
>+static const char*
>+lookup_arg_fuzzy (const char *arg, gfc_formal_arglist *arguments)
>+{
>+  char **candidates = NULL;
>+  size_t candidates_len = 0;
>+  lookup_arg_fuzzy_find_candidates (arguments, candidates,
>candidates_len);
>+  return gfc_closest_fuzzy_match (arg, candidates);
>+}
>+
>+
> /* Given formal and actual argument lists, see if they are compatible.
>    If they are compatible, the actual argument list is sorted to
>    correspond with the formal list, and elements for missing optional
>@@ -2736,8 +2775,16 @@ compare_actual_formal (gfc_actual_arglist **ap,
>gfc_formal_arglist *formal,
> 	  if (f == NULL)
> 	    {
> 	      if (where)
>-		gfc_error ("Keyword argument %qs at %L is not in "
>-			   "the procedure", a->name, &a->expr->where);
>+		{
>+		  const char *guessed = lookup_arg_fuzzy (a->name, formal);
>+		  if (guessed)
>+		    gfc_error ("Keyword argument %qs at %L is not in "
>+			       "the procedure; did you mean %qs?",
>+			       a->name, &a->expr->where, guessed);
>+		  else
>+		    gfc_error ("Keyword argument %qs at %L is not in "
>+			       "the procedure", a->name, &a->expr->where);
>+		}
> 	      return 0;
> 	    }
> 
>@@ -3436,8 +3483,15 @@ gfc_procedure_use (gfc_symbol *sym,
>gfc_actual_arglist **ap, locus *where)
>     {
>if (sym->ns->has_implicit_none_export && sym->attr.proc ==
>PROC_UNKNOWN)
> 	{
>-	  gfc_error ("Procedure %qs called at %L is not explicitly declared",
>-		     sym->name, where);
>+	  const char *guessed
>+	    = gfc_lookup_function_fuzzy (sym->name, sym->ns->sym_root);
>+	  if (guessed)
>+	    gfc_error ("Procedure %qs called at %L is not explicitly
>declared"
>+		       "; did you mean %qs?",
>+		       sym->name, where, guessed);
>+	  else
>+	    gfc_error ("Procedure %qs called at %L is not explicitly
>declared",
>+		       sym->name, where);
> 	  return false;
> 	}
>       if (warn_implicit_interface)
>diff --git a/gcc/fortran/misc.c b/gcc/fortran/misc.c
>index 1747ff2..dd17f46 100644
>--- a/gcc/fortran/misc.c
>+++ b/gcc/fortran/misc.c
>@@ -22,6 +22,7 @@ along with GCC; see the file COPYING3.  If not see
> #include "system.h"
> #include "coretypes.h"
> #include "gfortran.h"
>+#include "spellcheck.h"
> 
> 
> /* Initialize a typespec to unknown.  */
>@@ -280,3 +281,43 @@ get_c_kind(const char *c_kind_name, CInteropKind_t
>kinds_table[])
> 
>   return ISOCBINDING_INVALID;
> }
>+
>+
>+/* For a given name TYPO, determine the best candidate from CANDIDATES
>+   perusing Levenshtein distance.  Frees CANDIDATES before returning. 
>*/
>+
>+const char *
>+gfc_closest_fuzzy_match (const char *typo, char **candidates)
>+{
>+  /* Determine closest match.  */
>+  const char *best = NULL;
>+  char **cand = candidates;
>+  edit_distance_t best_distance = MAX_EDIT_DISTANCE;
>+  const size_t tl = strlen (typo);
>+
>+  while (cand && *cand)
>+    {
>+      edit_distance_t dist = levenshtein_distance (typo, tl, *cand,
>+	  strlen (*cand));
>+      if (dist < best_distance)
>+	{
>+	   best_distance = dist;
>+	   best = *cand;
>+	}
>+      cand++;
>+    }
>+  /* If more than half of the letters were misspelled, the suggestion
>is
>+     likely to be meaningless.  */
>+  if (best)
>+    {
>+      unsigned int cutoff = MAX (tl, strlen (best)) / 2;
>+
>+      if (best_distance > cutoff)
>+	{
>+	  XDELETEVEC (candidates);
>+	  return NULL;
>+	}
>+      XDELETEVEC (candidates);
>+    }
>+  return best;
>+}
>diff --git a/gcc/fortran/resolve.c b/gcc/fortran/resolve.c
>index 77f8c10..089afa3 100644
>--- a/gcc/fortran/resolve.c
>+++ b/gcc/fortran/resolve.c
>@@ -2693,6 +2693,43 @@ resolve_specific_f (gfc_expr *expr)
>   return true;
> }
> 
>+/* Recursively append candidate SYM to CANDIDATES.  Store the number
>of
>+   candidates in CANDIDATES_LEN.  */
>+
>+static void
>+lookup_function_fuzzy_find_candidates (gfc_symtree *sym,
>+				       char **&candidates,
>+				       size_t &candidates_len)
>+{
>+  gfc_symtree *p;
>+
>+  if (sym == NULL)
>+    return;
>+  if ((sym->n.sym->ts.type != BT_UNKNOWN || sym->n.sym->attr.external)
>+      && sym->n.sym->attr.flavor == FL_PROCEDURE)
>+    vec_push (candidates, candidates_len, sym->name);
>+
>+  p = sym->left;
>+  if (p)
>+    lookup_function_fuzzy_find_candidates (p, candidates,
>candidates_len);
>+
>+  p = sym->right;
>+  if (p)
>+    lookup_function_fuzzy_find_candidates (p, candidates,
>candidates_len);
>+}
>+
>+
>+/* Lookup function FN fuzzily, taking names in SYMROOT into account. 
>*/
>+
>+const char*
>+gfc_lookup_function_fuzzy (const char *fn, gfc_symtree *symroot)
>+{
>+  char **candidates = NULL;
>+  size_t candidates_len = 0;
>+  lookup_function_fuzzy_find_candidates (symroot, candidates,
>candidates_len);
>+  return gfc_closest_fuzzy_match (fn, candidates);
>+}
>+
> 
> /* Resolve a procedure call not known to be generic nor specific.  */
> 
>@@ -2743,8 +2780,15 @@ set_type:
> 
>       if (ts->type == BT_UNKNOWN)
> 	{
>-	  gfc_error ("Function %qs at %L has no IMPLICIT type",
>-		     sym->name, &expr->where);
>+	  const char *guessed
>+	    = gfc_lookup_function_fuzzy (sym->name, sym->ns->sym_root);
>+	  if (guessed)
>+	    gfc_error ("Function %qs at %L has no IMPLICIT type"
>+		       "; did you mean %qs?",
>+		       sym->name, &expr->where, guessed);
>+	  else
>+	    gfc_error ("Function %qs at %L has no IMPLICIT type",
>+		       sym->name, &expr->where);
> 	  return false;
> 	}
>       else
>@@ -3516,6 +3560,46 @@ compare_shapes (gfc_expr *op1, gfc_expr *op2)
> }
> 
> 
>+/* Recursively append candidate UOP to CANDIDATES.  Store the number
>of
>+   candidates in CANDIDATES_LEN.  */
>+static void
>+lookup_uop_fuzzy_find_candidates (gfc_symtree *uop,
>+				  char **&candidates,
>+				  size_t &candidates_len)
>+{
>+  gfc_symtree *p;
>+
>+  if (uop == NULL)
>+    return;
>+
>+  /* Not sure how to properly filter here.  Use all for a start.
>+     n.uop.op is NULL for empty interface operators (is that legal?)
>disregard
>+     these as i suppose they don't make terribly sense.  */
>+
>+  if (uop->n.uop->op != NULL)
>+    vec_push (candidates, candidates_len, uop->name);
>+
>+  p = uop->left;
>+  if (p)
>+    lookup_uop_fuzzy_find_candidates (p, candidates, candidates_len);
>+
>+  p = uop->right;
>+  if (p)
>+    lookup_uop_fuzzy_find_candidates (p, candidates, candidates_len);
>+}
>+
>+/* Lookup user-operator OP fuzzily, taking names in UOP into account. 
>*/
>+
>+static const char*
>+lookup_uop_fuzzy (const char *op, gfc_symtree *uop)
>+{
>+  char **candidates = NULL;
>+  size_t candidates_len = 0;
>+  lookup_uop_fuzzy_find_candidates (uop, candidates, candidates_len);
>+  return gfc_closest_fuzzy_match (op, candidates);
>+}
>+
>+
>/* Resolve an operator expression node.  This can involve replacing the
>    operation with a user defined function call.  */
> 
>@@ -3714,8 +3798,16 @@ resolve_operator (gfc_expr *e)
> 
>     case INTRINSIC_USER:
>       if (e->value.op.uop->op == NULL)
>-	sprintf (msg, _("Unknown operator %%<%s%%> at %%L"),
>-		 e->value.op.uop->name);
>+	{
>+	  const char *name = e->value.op.uop->name;
>+	  const char *guessed;
>+	  guessed = lookup_uop_fuzzy (name, e->value.op.uop->ns->uop_root);
>+	  if (guessed)
>+	    sprintf (msg, _("Unknown operator %%<%s%%> at %%L; did you mean
>'%s'?"),
>+		name, guessed);
>+	  else
>+	    sprintf (msg, _("Unknown operator %%<%s%%> at %%L"), name);
>+	}
>       else if (op2 == NULL)
> 	sprintf (msg, _("Operand of user operator %%<%s%%> at %%L is %s"),
> 		 e->value.op.uop->name, gfc_typename (&op1->ts));
>diff --git a/gcc/fortran/symbol.c b/gcc/fortran/symbol.c
>index 0ee7dec..776610c 100644
>--- a/gcc/fortran/symbol.c
>+++ b/gcc/fortran/symbol.c
>@@ -236,6 +236,44 @@ gfc_get_default_type (const char *name,
>gfc_namespace *ns)
> }
> 
> 
>+/* Recursively append candidate SYM to CANDIDATES.  Store the number
>of
>+   candidates in CANDIDATES_LEN.  */
>+
>+static void
>+lookup_symbol_fuzzy_find_candidates (gfc_symtree *sym,
>+				     char **&candidates,
>+				     size_t &candidates_len)
>+{
>+  gfc_symtree *p;
>+
>+  if (sym == NULL)
>+    return;
>+
>+  if (sym->n.sym->ts.type != BT_UNKNOWN && sym->n.sym->ts.type !=
>BT_PROCEDURE)
>+    vec_push (candidates, candidates_len, sym->name);
>+  p = sym->left;
>+  if (p)
>+    lookup_symbol_fuzzy_find_candidates (p, candidates,
>candidates_len);
>+
>+  p = sym->right;
>+  if (p)
>+    lookup_symbol_fuzzy_find_candidates (p, candidates,
>candidates_len);
>+}
>+
>+
>+/* Lookup symbol SYM_NAME fuzzily, taking names in SYMBOL into
>account.  */
>+
>+static const char*
>+lookup_symbol_fuzzy (const char *sym_name, gfc_symbol *symbol)
>+{
>+  char **candidates = NULL;
>+  size_t candidates_len = 0;
>+  lookup_symbol_fuzzy_find_candidates (symbol->ns->sym_root,
>candidates,
>+				       candidates_len);
>+  return gfc_closest_fuzzy_match (sym_name, candidates);
>+}
>+
>+
> /* Given a pointer to a symbol, set its type according to the first
>    letter of its name.  Fails if the letter in question has no default
>    type.  */
>@@ -254,8 +292,14 @@ gfc_set_default_type (gfc_symbol *sym, int
>error_flag, gfc_namespace *ns)
>     {
>       if (error_flag && !sym->attr.untyped)
> 	{
>-	  gfc_error ("Symbol %qs at %L has no IMPLICIT type",
>-		     sym->name, &sym->declared_at);
>+	  const char *guessed = lookup_symbol_fuzzy (sym->name, sym);
>+	  if (guessed)
>+	    gfc_error ("Symbol %qs at %L has no IMPLICIT type"
>+		       "; did you mean %qs?",
>+		       sym->name, &sym->declared_at, guessed);
>+	  else
>+	    gfc_error ("Symbol %qs at %L has no IMPLICIT type",
>+		       sym->name, &sym->declared_at);
> 	  sym->attr.untyped = 1; /* Ensure we only give an error once.  */
> 	}
> 
>@@ -2233,6 +2277,32 @@ find_union_component (gfc_symbol *un, const char
>*name,
> }
> 
> 
>+/* Recursively append candidate COMPONENT structures to CANDIDATES. 
>Store
>+   the number of total candidates in CANDIDATES_LEN.  */
>+
>+static void
>+lookup_component_fuzzy_find_candidates (gfc_component *component,
>+					char **&candidates,
>+					size_t &candidates_len)
>+{
>+  for (gfc_component *p = component; p; p = p->next)
>+    vec_push (candidates, candidates_len, p->name);
>+}
>+
>+
>+/* Lookup component MEMBER fuzzily, taking names in COMPONENT into
>account.  */
>+
>+static const char*
>+lookup_component_fuzzy (const char *member, gfc_component *component)
>+{
>+  char **candidates = NULL;
>+  size_t candidates_len = 0;
>+  lookup_component_fuzzy_find_candidates (component, candidates,
>+					  candidates_len);
>+  return gfc_closest_fuzzy_match (member, candidates);
>+}
>+
>+
> /* Given a derived type node and a component name, try to locate the
>    component structure.  Returns the NULL pointer if the component is
>not found or the components are private.  If noaccess is set, no access
>@@ -2330,8 +2400,16 @@ gfc_find_component (gfc_symbol *sym, const char
>*name,
>     }
> 
>   if (p == NULL && !silent)
>-    gfc_error ("%qs at %C is not a member of the %qs structure",
>-	       name, sym->name);
>+    {
>+      const char *guessed = lookup_component_fuzzy (name,
>sym->components);
>+      if (guessed)
>+	gfc_error ("%qs at %C is not a member of the %qs structure"
>+		   "; did you mean %qs?",
>+		   name, sym->name, guessed);
>+      else
>+	gfc_error ("%qs at %C is not a member of the %qs structure",
>+		   name, sym->name);
>+    }
> 
>   /* Component was found; build the ultimate component reference. */
>   if (p != NULL && ref)
>diff --git a/gcc/testsuite/gfortran.dg/spellcheck-operator.f90
>b/gcc/testsuite/gfortran.dg/spellcheck-operator.f90
>new file mode 100644
>index 0000000..810a770
>--- /dev/null
>+++ b/gcc/testsuite/gfortran.dg/spellcheck-operator.f90
>@@ -0,0 +1,30 @@
>+! { dg-do compile }
>+! test levenshtein based spelling suggestions
>+
>+module mymod1
>+  implicit none
>+  contains
>+    function something_good (iarg1)
>+      integer :: something_good
>+      integer, intent(in) :: iarg1
>+      something_good = iarg1 + 42
>+    end function something_good
>+end module mymod1
>+
>+program spellchekc
>+  use mymod1
>+  implicit none
>+
>+  interface operator (.mywrong.)
>+    module procedure something_wring ! { dg-error "Procedure
>.something_wring. in operator interface .mywrong. at .1. is neither
>function nor subroutine; did you mean .something_good.\\?|User operator
>procedure .something_wring. at .1. must be a FUNCTION" }
>+  end interface
>+
>+  interface operator (.mygood.)
>+    module procedure something_good
>+  end interface
>+
>+  integer :: i, j, added
>+  i = 0
>+  j = 0
>+  added = .mygoof. j ! { dg-error "Unknown operator .mygoof. at .1.;
>did you mean .mygood.\\?" }
>+end program spellchekc
>diff --git a/gcc/testsuite/gfortran.dg/spellcheck-parameter.f90
>b/gcc/testsuite/gfortran.dg/spellcheck-parameter.f90
>new file mode 100644
>index 0000000..715c5ab
>--- /dev/null
>+++ b/gcc/testsuite/gfortran.dg/spellcheck-parameter.f90
>@@ -0,0 +1,15 @@
>+! { dg-do compile }
>+! Contributed by Joost VandeVondele
>+! test levenshtein based spelling suggestions for keyword arguments
>+
>+module test
>+contains
>+  subroutine mysub(iarg1)
>+    integer :: iarg1
>+  end subroutine
>+end module
>+
>+use test
>+call mysub(iarg=1) ! { dg-error "Keyword argument .iarg. at .1. is not
>in the procedure; did you mean .iarg1.\\?" }
>+
>+end
>diff --git a/gcc/testsuite/gfortran.dg/spellcheck-procedure_1.f90
>b/gcc/testsuite/gfortran.dg/spellcheck-procedure_1.f90
>new file mode 100644
>index 0000000..3b7f716
>--- /dev/null
>+++ b/gcc/testsuite/gfortran.dg/spellcheck-procedure_1.f90
>@@ -0,0 +1,41 @@
>+! { dg-do compile }
>+! test levenshtein based spelling suggestions
>+
>+module mymod1
>+  implicit none
>+  contains
>+    function something_else (iarg1)
>+      integer :: something_else
>+      integer, intent(in) :: iarg1
>+      something_else = iarg1 + 42
>+    end function something_else
>+    function add_fourtytwo (iarg1)
>+      integer :: add_fourtytwo
>+      integer, intent(in) :: iarg1
>+      add_fourtytwo = iarg1 + 42
>+    end function add_fourtytwo
>+end module mymod1
>+
>+function myadd(iarg1, iarg2)
>+  implicit none
>+  integer :: myadd
>+  integer, intent(in) :: iarg1, iarg2
>+  myadd = iarg1 + iarg2
>+end function myadd
>+
>+program spellchekc
>+  use mymod1, something_good => something_else
>+  implicit none
>+
>+  integer :: myadd, i, j, myvar
>+  i = 0
>+  j = 0
>+
>+  j = something_goof(j) ! { dg-error "no IMPLICIT type; did you mean
>.something_good.\\?" }
>+  j = myaddd(i, j) ! { dg-error "no IMPLICIT type; did you mean
>.myadd.\\?" }
>+  if (j /= 42) call abort
>+  j = add_fourtytow(i, j) ! { dg-error "no IMPLICIT type; did you mean
>.add_fourtytwo.\\?" }
>+  myval = myadd(i, j) ! { dg-error "no IMPLICIT type; did you mean
>.myvar.\\?" }
>+  if (j /= 42 * 2) call abort
>+
>+end program spellchekc
>diff --git a/gcc/testsuite/gfortran.dg/spellcheck-procedure_2.f90
>b/gcc/testsuite/gfortran.dg/spellcheck-procedure_2.f90
>new file mode 100644
>index 0000000..a6ea5f9
>--- /dev/null
>+++ b/gcc/testsuite/gfortran.dg/spellcheck-procedure_2.f90
>@@ -0,0 +1,35 @@
>+! { dg-do compile }
>+! test levenshtein based spelling suggestions
>+
>+
>+program spellchekc
>+  implicit none (external) ! { dg-warning "GNU Extension: IMPORT NONE
>with spec list" }
>+
>+  interface
>+    subroutine bark_unless_zero(iarg)
>+      implicit none
>+      integer, intent(in) :: iarg
>+    end subroutine bark_unless_zero
>+  end interface
>+
>+  integer :: i
>+  i = 0
>+
>+  if (i /= 1) call abort
>+  call bark_unless_0(i) ! { dg-error "not explicitly declared; did you
>mean .bark_unless_zero.\\?" }
>+!  call complain_about_0(i) ! { -dg-error "not explicitly declared;
>did you mean .complain_about_zero.\\?" }
>+
>+contains
>+! We cannot reliably see this ATM, would need an unambiguous bit
>somewhere
>+  subroutine complain_about_zero(iarg)
>+    integer, intent(in) :: iarg
>+    if (iarg /= 0) call abort
>+  end subroutine complain_about_zero
>+
>+end program spellchekc
>+
>+subroutine bark_unless_zero(iarg)
>+  implicit none
>+  integer, intent(in) :: iarg
>+  if (iarg /= 0) call abort
>+end subroutine bark_unless_zero
>diff --git a/gcc/testsuite/gfortran.dg/spellcheck-structure.f90
>b/gcc/testsuite/gfortran.dg/spellcheck-structure.f90
>new file mode 100644
>index 0000000..929e05f
>--- /dev/null
>+++ b/gcc/testsuite/gfortran.dg/spellcheck-structure.f90
>@@ -0,0 +1,35 @@
>+! { dg-do compile }
>+! test levenshtein based spelling suggestions
>+implicit none
>+
>+!!!!!!!!!!!!!! structure tests !!!!!!!!!!!!!!
>+type type1
>+   real :: radius
>+   integer :: i
>+end type type1
>+
>+type type2
>+  integer :: myint
>+  type(type1) :: mytype
>+end type type2
>+
>+type type3
>+  type(type2) :: type_2
>+end type type3
>+type type4
>+  type(type3) :: type_3
>+end type type4
>+
>+type(type1) :: t1
>+t1%radiuz = .0 ! { dg-error ".radiuz. at .1. is not a member of the
>.type1. structure; did you mean .radius.\\?" }
>+t1%x = .0 ! { dg-error ".x. at .1. is not a member of the .type1.
>structure" }
>+type(type2) :: t2
>+t2%mytape%radius = .0 ! { dg-error ".mytape. at .1. is not a member of
>the .type2. structure; did you mean .mytype.\\?" }
>+t2%mytype%radious = .0 ! { dg-error ".radious. at .1. is not a member
>of the .type1. structure; did you mean .radius.\\?" }
>+type(type4) :: t4
>+t4%type_3%type_2%mytype%radium = 88.0 ! { dg-error ".radium. at .1. is
>not a member of the .type1. structure; did you mean .radius.\\?" }
>+
>+!!!!!!!!!!!!!! symbol tests !!!!!!!!!!!!!!
>+integer :: iarg1
>+iarg2 = 1 ! { dg-error "Symbol .iarg2. at .1. has no IMPLICIT type;
>did you mean .iarg1.\\?" }
>+end
Bernhard Reutner-Fischer Oct. 19, 2017, 7:51 a.m. UTC | #3
[forgot to CC gcc-patches]

On Sat, Jun 18, 2016 at 09:58:47PM +0200, Bernhard Reutner-Fischer wrote:
> Hi,
> 
> Ok for trunk?

This was ACKed about a year ago by Janne and Jerry and since there were
no objections in the meantime i've installed this first step towards
providing spelling suggestions in the fortran FE as r253877.

cheers,
> 
> Changes for v4 -> v3:
> 
> - rebased
> - Use 4 argument levenshtein_distance() to save multiple strlen(typo)
>   calls as suggested by dmalcolm
> 
> Changes for v2 -> v3:
> 
> - rebased
> 
> Changes for v1 -> v2:
> 
> - subroutines using interfaces
> - keyword arguments (named parameters)
> 
> Rewrite C++ autovec in plain C.
> Factor out levenshtein distance handling into a commonly used
> gfc_closest_fuzzy_match().
> 
> gcc/fortran/ChangeLog
> 
> 2015-12-27  Bernhard Reutner-Fischer  <aldot@gcc.gnu.org>
> 
> 	* gfortran.h (gfc_lookup_function_fuzzy): New declaration.
> 	(gfc_closest_fuzzy_match): New declaration.
> 	(vec_push): New definition.
> 	* misc.c (gfc_closest_fuzzy_match): New definition.
> 	* resolve.c: Include spellcheck.h.
> 	(lookup_function_fuzzy_find_candidates): New static function.
> 	(lookup_uop_fuzzy_find_candidates): Likewise.
> 	(lookup_uop_fuzzy): Likewise.
> 	(resolve_operator) <INTRINSIC_USER>: Call lookup_uop_fuzzy.
> 	(gfc_lookup_function_fuzzy): New definition.
> 	(resolve_unknown_f): Call gfc_lookup_function_fuzzy.
> 	* interface.c (check_interface0): Likewise.
> 	(lookup_arg_fuzzy_find_candidates): New static function.
> 	(lookup_arg_fuzzy ): Likewise.
> 	(compare_actual_formal): Call lookup_arg_fuzzy.
> 	* symbol.c: Include spellcheck.h.
> 	(lookup_symbol_fuzzy_find_candidates): New static function.
> 	(lookup_symbol_fuzzy): Likewise.
> 	(gfc_set_default_type): Call lookup_symbol_fuzzy.
> 	(lookup_component_fuzzy_find_candidates): New static function.
> 	(lookup_component_fuzzy): Likewise.
> 	(gfc_find_component): Call lookup_component_fuzzy.
> 
> gcc/testsuite/ChangeLog
> 
> 2015-12-27  Bernhard Reutner-Fischer  <aldot@gcc.gnu.org>
> 
> 	* gfortran.dg/spellcheck-operator.f90: New testcase.
> 	* gfortran.dg/spellcheck-procedure_1.f90: New testcase.
> 	* gfortran.dg/spellcheck-procedure_2.f90: New testcase.
> 	* gfortran.dg/spellcheck-structure.f90: New testcase.
> 	* gfortran.dg/spellcheck-parameter.f90: New testcase.
> 
> ---
> 
> David Malcolm's nice Levenshtein distance spelling check helpers
> were used in some parts of other frontends. This proposed patch adds
> some spelling corrections to the fortran frontend.
> 
> Suggestions are printed if we can find a suitable name, currently
> perusing a very simple cutoff factor:
> /* If more than half of the letters were misspelled, the suggestion is
>    likely to be meaningless.  */
> cutoff = MAX (strlen (typo), strlen (best_guess)) / 2;
> which effectively skips names with less than 4 characters.
> For e.g. structures, one could try to be much smarter in an attempt to
> also provide suggestions for single-letter members/components.
> 
> This patch covers (at least partly):
> - user-defined operators
> - structures (types and their components)
> - functions
> - symbols (variables)
> 
> If anybody has a testcase where a spelling-suggestion would make sense
> then please pass it along so we maybe can add support for GCC-7.

Patch
diff mbox

diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h
index 0bb71cb..5d43c2d 100644
--- a/gcc/fortran/gfortran.h
+++ b/gcc/fortran/gfortran.h
@@ -2682,6 +2682,17 @@  void gfc_done_2 (void);
 
 int get_c_kind (const char *, CInteropKind_t *);
 
+const char *gfc_closest_fuzzy_match (const char *, char **);
+static inline void
+vec_push (char **&optr, size_t &osz, const char *elt)
+{
+  /* {auto,}vec.safe_push () replacement.  Don't ask..  */
+  // if (strlen (elt) < 4) return; premature optimization: eliminated by cutoff
+  optr = XRESIZEVEC (char *, optr, osz + 2);
+  optr[osz] = CONST_CAST (char *, elt);
+  optr[++osz] = NULL;
+}
+
 /* options.c */
 unsigned int gfc_option_lang_mask (void);
 void gfc_init_options_struct (struct gcc_options *);
@@ -3103,6 +3114,7 @@  bool gfc_type_is_extensible (gfc_symbol *);
 bool gfc_resolve_intrinsic (gfc_symbol *, locus *);
 bool gfc_explicit_interface_required (gfc_symbol *, char *, int);
 extern int gfc_do_concurrent_flag;
+const char* gfc_lookup_function_fuzzy (const char *, gfc_symtree *);
 
 
 /* array.c */
diff --git a/gcc/fortran/interface.c b/gcc/fortran/interface.c
index b012de5..bef514c 100644
--- a/gcc/fortran/interface.c
+++ b/gcc/fortran/interface.c
@@ -1694,13 +1694,27 @@  check_interface0 (gfc_interface *p, const char *interface_name)
 	   || !p->sym->attr.if_source)
 	  && !gfc_fl_struct (p->sym->attr.flavor))
 	{
+	  const char *guessed
+	    = gfc_lookup_function_fuzzy (p->sym->name, p->sym->ns->sym_root);
+
 	  if (p->sym->attr.external)
-	    gfc_error ("Procedure %qs in %s at %L has no explicit interface",
-		       p->sym->name, interface_name, &p->sym->declared_at);
+	    if (guessed)
+	      gfc_error ("Procedure %qs in %s at %L has no explicit interface"
+			 "; did you mean %qs?",
+			 p->sym->name, interface_name, &p->sym->declared_at,
+			 guessed);
+	    else
+	      gfc_error ("Procedure %qs in %s at %L has no explicit interface",
+			 p->sym->name, interface_name, &p->sym->declared_at);
 	  else
-	    gfc_error ("Procedure %qs in %s at %L is neither function nor "
-		       "subroutine", p->sym->name, interface_name,
-		      &p->sym->declared_at);
+	    if (guessed)
+	      gfc_error ("Procedure %qs in %s at %L is neither function nor "
+			 "subroutine; did you mean %qs?", p->sym->name,
+			interface_name, &p->sym->declared_at, guessed);
+	    else
+	      gfc_error ("Procedure %qs in %s at %L is neither function nor "
+			 "subroutine", p->sym->name, interface_name,
+			&p->sym->declared_at);
 	  return 1;
 	}
 
@@ -2684,6 +2698,31 @@  is_procptr_result (gfc_expr *expr)
 }
 
 
+/* Recursively append candidate argument ARG to CANDIDATES.  Store the
+   number of total candidates in CANDIDATES_LEN.  */
+
+static void
+lookup_arg_fuzzy_find_candidates (gfc_formal_arglist *arg,
+				  char **&candidates,
+				  size_t &candidates_len)
+{
+  for (gfc_formal_arglist *p = arg; p && p->sym; p = p->next)
+    vec_push (candidates, candidates_len, p->sym->name);
+}
+
+
+/* Lookup argument ARG fuzzily, taking names in ARGUMENTS into account.  */
+
+static const char*
+lookup_arg_fuzzy (const char *arg, gfc_formal_arglist *arguments)
+{
+  char **candidates = NULL;
+  size_t candidates_len = 0;
+  lookup_arg_fuzzy_find_candidates (arguments, candidates, candidates_len);
+  return gfc_closest_fuzzy_match (arg, candidates);
+}
+
+
 /* Given formal and actual argument lists, see if they are compatible.
    If they are compatible, the actual argument list is sorted to
    correspond with the formal list, and elements for missing optional
@@ -2736,8 +2775,16 @@  compare_actual_formal (gfc_actual_arglist **ap, gfc_formal_arglist *formal,
 	  if (f == NULL)
 	    {
 	      if (where)
-		gfc_error ("Keyword argument %qs at %L is not in "
-			   "the procedure", a->name, &a->expr->where);
+		{
+		  const char *guessed = lookup_arg_fuzzy (a->name, formal);
+		  if (guessed)
+		    gfc_error ("Keyword argument %qs at %L is not in "
+			       "the procedure; did you mean %qs?",
+			       a->name, &a->expr->where, guessed);
+		  else
+		    gfc_error ("Keyword argument %qs at %L is not in "
+			       "the procedure", a->name, &a->expr->where);
+		}
 	      return 0;
 	    }
 
@@ -3436,8 +3483,15 @@  gfc_procedure_use (gfc_symbol *sym, gfc_actual_arglist **ap, locus *where)
     {
       if (sym->ns->has_implicit_none_export && sym->attr.proc == PROC_UNKNOWN)
 	{
-	  gfc_error ("Procedure %qs called at %L is not explicitly declared",
-		     sym->name, where);
+	  const char *guessed
+	    = gfc_lookup_function_fuzzy (sym->name, sym->ns->sym_root);
+	  if (guessed)
+	    gfc_error ("Procedure %qs called at %L is not explicitly declared"
+		       "; did you mean %qs?",
+		       sym->name, where, guessed);
+	  else
+	    gfc_error ("Procedure %qs called at %L is not explicitly declared",
+		       sym->name, where);
 	  return false;
 	}
       if (warn_implicit_interface)
diff --git a/gcc/fortran/misc.c b/gcc/fortran/misc.c
index 1747ff2..dd17f46 100644
--- a/gcc/fortran/misc.c
+++ b/gcc/fortran/misc.c
@@ -22,6 +22,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "gfortran.h"
+#include "spellcheck.h"
 
 
 /* Initialize a typespec to unknown.  */
@@ -280,3 +281,43 @@  get_c_kind(const char *c_kind_name, CInteropKind_t kinds_table[])
 
   return ISOCBINDING_INVALID;
 }
+
+
+/* For a given name TYPO, determine the best candidate from CANDIDATES
+   perusing Levenshtein distance.  Frees CANDIDATES before returning.  */
+
+const char *
+gfc_closest_fuzzy_match (const char *typo, char **candidates)
+{
+  /* Determine closest match.  */
+  const char *best = NULL;
+  char **cand = candidates;
+  edit_distance_t best_distance = MAX_EDIT_DISTANCE;
+  const size_t tl = strlen (typo);
+
+  while (cand && *cand)
+    {
+      edit_distance_t dist = levenshtein_distance (typo, tl, *cand,
+	  strlen (*cand));
+      if (dist < best_distance)
+	{
+	   best_distance = dist;
+	   best = *cand;
+	}
+      cand++;
+    }
+  /* If more than half of the letters were misspelled, the suggestion is
+     likely to be meaningless.  */
+  if (best)
+    {
+      unsigned int cutoff = MAX (tl, strlen (best)) / 2;
+
+      if (best_distance > cutoff)
+	{
+	  XDELETEVEC (candidates);
+	  return NULL;
+	}
+      XDELETEVEC (candidates);
+    }
+  return best;
+}
diff --git a/gcc/fortran/resolve.c b/gcc/fortran/resolve.c
index 77f8c10..089afa3 100644
--- a/gcc/fortran/resolve.c
+++ b/gcc/fortran/resolve.c
@@ -2693,6 +2693,43 @@  resolve_specific_f (gfc_expr *expr)
   return true;
 }
 
+/* Recursively append candidate SYM to CANDIDATES.  Store the number of
+   candidates in CANDIDATES_LEN.  */
+
+static void
+lookup_function_fuzzy_find_candidates (gfc_symtree *sym,
+				       char **&candidates,
+				       size_t &candidates_len)
+{
+  gfc_symtree *p;
+
+  if (sym == NULL)
+    return;
+  if ((sym->n.sym->ts.type != BT_UNKNOWN || sym->n.sym->attr.external)
+      && sym->n.sym->attr.flavor == FL_PROCEDURE)
+    vec_push (candidates, candidates_len, sym->name);
+
+  p = sym->left;
+  if (p)
+    lookup_function_fuzzy_find_candidates (p, candidates, candidates_len);
+
+  p = sym->right;
+  if (p)
+    lookup_function_fuzzy_find_candidates (p, candidates, candidates_len);
+}
+
+
+/* Lookup function FN fuzzily, taking names in SYMROOT into account.  */
+
+const char*
+gfc_lookup_function_fuzzy (const char *fn, gfc_symtree *symroot)
+{
+  char **candidates = NULL;
+  size_t candidates_len = 0;
+  lookup_function_fuzzy_find_candidates (symroot, candidates, candidates_len);
+  return gfc_closest_fuzzy_match (fn, candidates);
+}
+
 
 /* Resolve a procedure call not known to be generic nor specific.  */
 
@@ -2743,8 +2780,15 @@  set_type:
 
       if (ts->type == BT_UNKNOWN)
 	{
-	  gfc_error ("Function %qs at %L has no IMPLICIT type",
-		     sym->name, &expr->where);
+	  const char *guessed
+	    = gfc_lookup_function_fuzzy (sym->name, sym->ns->sym_root);
+	  if (guessed)
+	    gfc_error ("Function %qs at %L has no IMPLICIT type"
+		       "; did you mean %qs?",
+		       sym->name, &expr->where, guessed);
+	  else
+	    gfc_error ("Function %qs at %L has no IMPLICIT type",
+		       sym->name, &expr->where);
 	  return false;
 	}
       else
@@ -3516,6 +3560,46 @@  compare_shapes (gfc_expr *op1, gfc_expr *op2)
 }
 
 
+/* Recursively append candidate UOP to CANDIDATES.  Store the number of
+   candidates in CANDIDATES_LEN.  */
+static void
+lookup_uop_fuzzy_find_candidates (gfc_symtree *uop,
+				  char **&candidates,
+				  size_t &candidates_len)
+{
+  gfc_symtree *p;
+
+  if (uop == NULL)
+    return;
+
+  /* Not sure how to properly filter here.  Use all for a start.
+     n.uop.op is NULL for empty interface operators (is that legal?) disregard
+     these as i suppose they don't make terribly sense.  */
+
+  if (uop->n.uop->op != NULL)
+    vec_push (candidates, candidates_len, uop->name);
+
+  p = uop->left;
+  if (p)
+    lookup_uop_fuzzy_find_candidates (p, candidates, candidates_len);
+
+  p = uop->right;
+  if (p)
+    lookup_uop_fuzzy_find_candidates (p, candidates, candidates_len);
+}
+
+/* Lookup user-operator OP fuzzily, taking names in UOP into account.  */
+
+static const char*
+lookup_uop_fuzzy (const char *op, gfc_symtree *uop)
+{
+  char **candidates = NULL;
+  size_t candidates_len = 0;
+  lookup_uop_fuzzy_find_candidates (uop, candidates, candidates_len);
+  return gfc_closest_fuzzy_match (op, candidates);
+}
+
+
 /* Resolve an operator expression node.  This can involve replacing the
    operation with a user defined function call.  */
 
@@ -3714,8 +3798,16 @@  resolve_operator (gfc_expr *e)
 
     case INTRINSIC_USER:
       if (e->value.op.uop->op == NULL)
-	sprintf (msg, _("Unknown operator %%<%s%%> at %%L"),
-		 e->value.op.uop->name);
+	{
+	  const char *name = e->value.op.uop->name;
+	  const char *guessed;
+	  guessed = lookup_uop_fuzzy (name, e->value.op.uop->ns->uop_root);
+	  if (guessed)
+	    sprintf (msg, _("Unknown operator %%<%s%%> at %%L; did you mean '%s'?"),
+		name, guessed);
+	  else
+	    sprintf (msg, _("Unknown operator %%<%s%%> at %%L"), name);
+	}
       else if (op2 == NULL)
 	sprintf (msg, _("Operand of user operator %%<%s%%> at %%L is %s"),
 		 e->value.op.uop->name, gfc_typename (&op1->ts));
diff --git a/gcc/fortran/symbol.c b/gcc/fortran/symbol.c
index 0ee7dec..776610c 100644
--- a/gcc/fortran/symbol.c
+++ b/gcc/fortran/symbol.c
@@ -236,6 +236,44 @@  gfc_get_default_type (const char *name, gfc_namespace *ns)
 }
 
 
+/* Recursively append candidate SYM to CANDIDATES.  Store the number of
+   candidates in CANDIDATES_LEN.  */
+
+static void
+lookup_symbol_fuzzy_find_candidates (gfc_symtree *sym,
+				     char **&candidates,
+				     size_t &candidates_len)
+{
+  gfc_symtree *p;
+
+  if (sym == NULL)
+    return;
+
+  if (sym->n.sym->ts.type != BT_UNKNOWN && sym->n.sym->ts.type != BT_PROCEDURE)
+    vec_push (candidates, candidates_len, sym->name);
+  p = sym->left;
+  if (p)
+    lookup_symbol_fuzzy_find_candidates (p, candidates, candidates_len);
+
+  p = sym->right;
+  if (p)
+    lookup_symbol_fuzzy_find_candidates (p, candidates, candidates_len);
+}
+
+
+/* Lookup symbol SYM_NAME fuzzily, taking names in SYMBOL into account.  */
+
+static const char*
+lookup_symbol_fuzzy (const char *sym_name, gfc_symbol *symbol)
+{
+  char **candidates = NULL;
+  size_t candidates_len = 0;
+  lookup_symbol_fuzzy_find_candidates (symbol->ns->sym_root, candidates,
+				       candidates_len);
+  return gfc_closest_fuzzy_match (sym_name, candidates);
+}
+
+
 /* Given a pointer to a symbol, set its type according to the first
    letter of its name.  Fails if the letter in question has no default
    type.  */
@@ -254,8 +292,14 @@  gfc_set_default_type (gfc_symbol *sym, int error_flag, gfc_namespace *ns)
     {
       if (error_flag && !sym->attr.untyped)
 	{
-	  gfc_error ("Symbol %qs at %L has no IMPLICIT type",
-		     sym->name, &sym->declared_at);
+	  const char *guessed = lookup_symbol_fuzzy (sym->name, sym);
+	  if (guessed)
+	    gfc_error ("Symbol %qs at %L has no IMPLICIT type"
+		       "; did you mean %qs?",
+		       sym->name, &sym->declared_at, guessed);
+	  else
+	    gfc_error ("Symbol %qs at %L has no IMPLICIT type",
+		       sym->name, &sym->declared_at);
 	  sym->attr.untyped = 1; /* Ensure we only give an error once.  */
 	}
 
@@ -2233,6 +2277,32 @@  find_union_component (gfc_symbol *un, const char *name,
 }
 
 
+/* Recursively append candidate COMPONENT structures to CANDIDATES.  Store
+   the number of total candidates in CANDIDATES_LEN.  */
+
+static void
+lookup_component_fuzzy_find_candidates (gfc_component *component,
+					char **&candidates,
+					size_t &candidates_len)
+{
+  for (gfc_component *p = component; p; p = p->next)
+    vec_push (candidates, candidates_len, p->name);
+}
+
+
+/* Lookup component MEMBER fuzzily, taking names in COMPONENT into account.  */
+
+static const char*
+lookup_component_fuzzy (const char *member, gfc_component *component)
+{
+  char **candidates = NULL;
+  size_t candidates_len = 0;
+  lookup_component_fuzzy_find_candidates (component, candidates,
+					  candidates_len);
+  return gfc_closest_fuzzy_match (member, candidates);
+}
+
+
 /* Given a derived type node and a component name, try to locate the
    component structure.  Returns the NULL pointer if the component is
    not found or the components are private.  If noaccess is set, no access
@@ -2330,8 +2400,16 @@  gfc_find_component (gfc_symbol *sym, const char *name,
     }
 
   if (p == NULL && !silent)
-    gfc_error ("%qs at %C is not a member of the %qs structure",
-	       name, sym->name);
+    {
+      const char *guessed = lookup_component_fuzzy (name, sym->components);
+      if (guessed)
+	gfc_error ("%qs at %C is not a member of the %qs structure"
+		   "; did you mean %qs?",
+		   name, sym->name, guessed);
+      else
+	gfc_error ("%qs at %C is not a member of the %qs structure",
+		   name, sym->name);
+    }
 
   /* Component was found; build the ultimate component reference. */
   if (p != NULL && ref)
diff --git a/gcc/testsuite/gfortran.dg/spellcheck-operator.f90 b/gcc/testsuite/gfortran.dg/spellcheck-operator.f90
new file mode 100644
index 0000000..810a770
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/spellcheck-operator.f90
@@ -0,0 +1,30 @@ 
+! { dg-do compile }
+! test levenshtein based spelling suggestions
+
+module mymod1
+  implicit none
+  contains
+    function something_good (iarg1)
+      integer :: something_good
+      integer, intent(in) :: iarg1
+      something_good = iarg1 + 42
+    end function something_good
+end module mymod1
+
+program spellchekc
+  use mymod1
+  implicit none
+
+  interface operator (.mywrong.)
+    module procedure something_wring ! { dg-error "Procedure .something_wring. in operator interface .mywrong. at .1. is neither function nor subroutine; did you mean .something_good.\\?|User operator procedure .something_wring. at .1. must be a FUNCTION" }
+  end interface
+
+  interface operator (.mygood.)
+    module procedure something_good
+  end interface
+
+  integer :: i, j, added
+  i = 0
+  j = 0
+  added = .mygoof. j ! { dg-error "Unknown operator .mygoof. at .1.; did you mean .mygood.\\?" }
+end program spellchekc
diff --git a/gcc/testsuite/gfortran.dg/spellcheck-parameter.f90 b/gcc/testsuite/gfortran.dg/spellcheck-parameter.f90
new file mode 100644
index 0000000..715c5ab
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/spellcheck-parameter.f90
@@ -0,0 +1,15 @@ 
+! { dg-do compile }
+! Contributed by Joost VandeVondele
+! test levenshtein based spelling suggestions for keyword arguments
+
+module test
+contains
+  subroutine mysub(iarg1)
+    integer :: iarg1
+  end subroutine
+end module
+
+use test
+call mysub(iarg=1) ! { dg-error "Keyword argument .iarg. at .1. is not in the procedure; did you mean .iarg1.\\?" }
+
+end
diff --git a/gcc/testsuite/gfortran.dg/spellcheck-procedure_1.f90 b/gcc/testsuite/gfortran.dg/spellcheck-procedure_1.f90
new file mode 100644
index 0000000..3b7f716
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/spellcheck-procedure_1.f90
@@ -0,0 +1,41 @@ 
+! { dg-do compile }
+! test levenshtein based spelling suggestions
+
+module mymod1
+  implicit none
+  contains
+    function something_else (iarg1)
+      integer :: something_else
+      integer, intent(in) :: iarg1
+      something_else = iarg1 + 42
+    end function something_else
+    function add_fourtytwo (iarg1)
+      integer :: add_fourtytwo
+      integer, intent(in) :: iarg1
+      add_fourtytwo = iarg1 + 42
+    end function add_fourtytwo
+end module mymod1
+
+function myadd(iarg1, iarg2)
+  implicit none
+  integer :: myadd
+  integer, intent(in) :: iarg1, iarg2
+  myadd = iarg1 + iarg2
+end function myadd
+
+program spellchekc
+  use mymod1, something_good => something_else
+  implicit none
+
+  integer :: myadd, i, j, myvar
+  i = 0
+  j = 0
+
+  j = something_goof(j) ! { dg-error "no IMPLICIT type; did you mean .something_good.\\?" }
+  j = myaddd(i, j) ! { dg-error "no IMPLICIT type; did you mean .myadd.\\?" }
+  if (j /= 42) call abort
+  j = add_fourtytow(i, j) ! { dg-error "no IMPLICIT type; did you mean .add_fourtytwo.\\?" }
+  myval = myadd(i, j) ! { dg-error "no IMPLICIT type; did you mean .myvar.\\?" }
+  if (j /= 42 * 2) call abort
+
+end program spellchekc
diff --git a/gcc/testsuite/gfortran.dg/spellcheck-procedure_2.f90 b/gcc/testsuite/gfortran.dg/spellcheck-procedure_2.f90
new file mode 100644
index 0000000..a6ea5f9
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/spellcheck-procedure_2.f90
@@ -0,0 +1,35 @@ 
+! { dg-do compile }
+! test levenshtein based spelling suggestions
+
+
+program spellchekc
+  implicit none (external) ! { dg-warning "GNU Extension: IMPORT NONE with spec list" }
+
+  interface
+    subroutine bark_unless_zero(iarg)
+      implicit none
+      integer, intent(in) :: iarg
+    end subroutine bark_unless_zero
+  end interface
+
+  integer :: i
+  i = 0
+
+  if (i /= 1) call abort
+  call bark_unless_0(i) ! { dg-error "not explicitly declared; did you mean .bark_unless_zero.\\?" }
+!  call complain_about_0(i) ! { -dg-error "not explicitly declared; did you mean .complain_about_zero.\\?" }
+
+contains
+! We cannot reliably see this ATM, would need an unambiguous bit somewhere
+  subroutine complain_about_zero(iarg)
+    integer, intent(in) :: iarg
+    if (iarg /= 0) call abort
+  end subroutine complain_about_zero
+
+end program spellchekc
+
+subroutine bark_unless_zero(iarg)
+  implicit none
+  integer, intent(in) :: iarg
+  if (iarg /= 0) call abort
+end subroutine bark_unless_zero
diff --git a/gcc/testsuite/gfortran.dg/spellcheck-structure.f90 b/gcc/testsuite/gfortran.dg/spellcheck-structure.f90
new file mode 100644
index 0000000..929e05f
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/spellcheck-structure.f90
@@ -0,0 +1,35 @@ 
+! { dg-do compile }
+! test levenshtein based spelling suggestions
+implicit none
+
+!!!!!!!!!!!!!! structure tests !!!!!!!!!!!!!!
+type type1
+   real :: radius
+   integer :: i
+end type type1
+
+type type2
+  integer :: myint
+  type(type1) :: mytype
+end type type2
+
+type type3
+  type(type2) :: type_2
+end type type3
+type type4
+  type(type3) :: type_3
+end type type4
+
+type(type1) :: t1
+t1%radiuz = .0 ! { dg-error ".radiuz. at .1. is not a member of the .type1. structure; did you mean .radius.\\?" }
+t1%x = .0 ! { dg-error ".x. at .1. is not a member of the .type1. structure" }
+type(type2) :: t2
+t2%mytape%radius = .0 ! { dg-error ".mytape. at .1. is not a member of the .type2. structure; did you mean .mytype.\\?" }
+t2%mytype%radious = .0 ! { dg-error ".radious. at .1. is not a member of the .type1. structure; did you mean .radius.\\?" }
+type(type4) :: t4
+t4%type_3%type_2%mytype%radium = 88.0 ! { dg-error ".radium. at .1. is not a member of the .type1. structure; did you mean .radius.\\?" }
+
+!!!!!!!!!!!!!! symbol tests !!!!!!!!!!!!!!
+integer :: iarg1
+iarg2 = 1 ! { dg-error "Symbol .iarg2. at .1. has no IMPLICIT type; did you mean .iarg1.\\?" }
+end