C++ FE: offer suggestions for misspelled field names
authorDavid Malcolm <dmalcolm@redhat.com>
Fri, 20 Nov 2015 01:26:00 +0000 (01:26 +0000)
committerDavid Malcolm <dmalcolm@gcc.gnu.org>
Fri, 20 Nov 2015 01:26:00 +0000 (01:26 +0000)
gcc/c/ChangeLog:
* c-typeck.c (lookup_field_fuzzy): Move determination of closest
candidate into a new function, find_closest_identifier.

gcc/cp/ChangeLog:
* cp-tree.h (lookup_member_fuzzy): New decl.
* search.c: Include spellcheck.h.
(class lookup_field_fuzzy_info): New class.
(lookup_field_fuzzy_info::fuzzy_lookup_fnfields): New.
(lookup_field_fuzzy_info::fuzzy_lookup_field): New.
(lookup_field_fuzzy_r): New.
(lookup_member_fuzzy): New.
* typeck.c (finish_class_member_access_expr): When issuing
a "has no member named" error, call lookup_member_fuzzy, and
offer any result as a suggestion.

gcc/ChangeLog:
* spellcheck-tree.c (find_closest_identifier): New function, taken
from c/c-typeck.c:lookup_field_fuzzy, with NULL corrected to
NULL_TREE in two places.
* spellcheck.h (find_closest_identifier): New decl.

gcc/testsuite/ChangeLog:
* g++.dg/spellcheck-fields.C: New file.

From-SVN: r230638

gcc/c/ChangeLog
gcc/c/c-typeck.c
gcc/cp/ChangeLog
gcc/cp/cp-tree.h
gcc/cp/search.c
gcc/cp/typeck.c
gcc/spellcheck-tree.c
gcc/spellcheck.h
gcc/testsuite/ChangeLog

index a0cab878d6f4e7611f0ce1c82ccb5edeed17a4c8..9399053a7f9f5375c4aa6ff7bd4ad42e1e09858b 100644 (file)
@@ -1,3 +1,8 @@
+2015-11-19  David Malcolm  <dmalcolm@redhat.com>
+
+       * c-typeck.c (lookup_field_fuzzy): Move determination of closest
+       candidate into a new function, find_closest_identifier.
+
 2015-11-19  Marek Polacek  <polacek@redhat.com>
 
        PR c/68412
index 5cb0f7e622edeea020f91eac7415b68925dc472c..9284bfcea66e464a679609792ffb59f348b76439 100644 (file)
@@ -2274,33 +2274,7 @@ lookup_field_fuzzy (tree type, tree component)
   lookup_field_fuzzy_find_candidates (type, component,
                                      &candidates);
 
-  /* Now determine which is closest.  */
-  int i;
-  tree identifier;
-  tree best_identifier = NULL;
-  edit_distance_t best_distance = MAX_EDIT_DISTANCE;
-  FOR_EACH_VEC_ELT (candidates, i, identifier)
-    {
-      gcc_assert (TREE_CODE (identifier) == IDENTIFIER_NODE);
-      edit_distance_t dist = levenshtein_distance (component, identifier);
-      if (dist < best_distance)
-       {
-         best_distance = dist;
-         best_identifier = identifier;
-       }
-    }
-
-  /* If more than half of the letters were misspelled, the suggestion is
-     likely to be meaningless.  */
-  if (best_identifier)
-    {
-      unsigned int cutoff = MAX (IDENTIFIER_LENGTH (component),
-                                IDENTIFIER_LENGTH (best_identifier)) / 2;
-      if (best_distance > cutoff)
-       return NULL;
-    }
-
-  return best_identifier;
+  return find_closest_identifier (component, &candidates);
 }
 
 /* Make an expression to refer to the COMPONENT field of structure or
index 92b1d280c3d85ad2c6a49fa5152aa5270c8552f4..cf436d7d106bfe51c3e73cf66387c6638470e4ae 100644 (file)
@@ -1,3 +1,16 @@
+2015-11-19  David Malcolm  <dmalcolm@redhat.com>
+
+       * cp-tree.h (lookup_member_fuzzy): New decl.
+       * search.c: Include spellcheck.h.
+       (class lookup_field_fuzzy_info): New class.
+       (lookup_field_fuzzy_info::fuzzy_lookup_fnfields): New.
+       (lookup_field_fuzzy_info::fuzzy_lookup_field): New.
+       (lookup_field_fuzzy_r): New.
+       (lookup_member_fuzzy): New.
+       * typeck.c (finish_class_member_access_expr): When issuing
+       a "has no member named" error, call lookup_member_fuzzy, and
+       offer any result as a suggestion.
+
 2015-11-19  Torvald Riegel  <triegel@redhat.com>
 
        * except.c (do_free_exception): Use transactional wrapper.
index 14ea1194113b8f42d23b708fd0a1930933bbf161..38bd7dd614ffb5f7735e20eb10c2d89fe35307c4 100644 (file)
@@ -6152,6 +6152,7 @@ extern int class_method_index_for_fn              (tree, tree);
 extern tree lookup_fnfields                    (tree, tree, int);
 extern tree lookup_member                      (tree, tree, int, bool,
                                                 tsubst_flags_t);
+extern tree lookup_member_fuzzy                (tree, tree, bool);
 extern int look_for_overrides                  (tree, tree);
 extern void get_pure_virtuals                  (tree);
 extern void maybe_suppress_debug_info          (tree);
index 94502f6322fb43a8bcd1337dbf0bb956b85005ac..0c11a8318cb2388287aee78e8306deaff446b9ef 100644 (file)
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "cp-tree.h"
 #include "intl.h"
 #include "toplev.h"
+#include "spellcheck.h"
 
 static int is_subobject_of_p (tree, tree);
 static tree dfs_lookup_base (tree, void *);
@@ -1352,6 +1353,144 @@ lookup_member (tree xbasetype, tree name, int protect, bool want_type,
   return rval;
 }
 
+/* Helper class for lookup_member_fuzzy.  */
+
+class lookup_field_fuzzy_info
+{
+ public:
+  lookup_field_fuzzy_info (bool want_type_p) :
+    m_want_type_p (want_type_p), m_candidates () {}
+
+  void fuzzy_lookup_fnfields (tree type);
+  void fuzzy_lookup_field (tree type);
+
+  /* If true, we are looking for types, not data members.  */
+  bool m_want_type_p;
+  /* The result: a vec of identifiers.  */
+  auto_vec<tree> m_candidates;
+};
+
+/* Locate all methods within TYPE, append them to m_candidates.  */
+
+void
+lookup_field_fuzzy_info::fuzzy_lookup_fnfields (tree type)
+{
+  vec<tree, va_gc> *method_vec;
+  tree fn;
+  size_t i;
+
+  if (!CLASS_TYPE_P (type))
+    return;
+
+  method_vec = CLASSTYPE_METHOD_VEC (type);
+  if (!method_vec)
+    return;
+
+  for (i = 0; vec_safe_iterate (method_vec, i, &fn); ++i)
+    if (fn)
+      m_candidates.safe_push (DECL_NAME (OVL_CURRENT (fn)));
+}
+
+/* Locate all fields within TYPE, append them to m_candidates.  */
+
+void
+lookup_field_fuzzy_info::fuzzy_lookup_field (tree type)
+{
+  if (TREE_CODE (type) == TEMPLATE_TYPE_PARM
+      || TREE_CODE (type) == BOUND_TEMPLATE_TEMPLATE_PARM
+      || TREE_CODE (type) == TYPENAME_TYPE)
+    /* The TYPE_FIELDS of a TEMPLATE_TYPE_PARM and
+       BOUND_TEMPLATE_TEMPLATE_PARM are not fields at all;
+       instead TYPE_FIELDS is the TEMPLATE_PARM_INDEX.
+       The TYPE_FIELDS of TYPENAME_TYPE is its TYPENAME_TYPE_FULLNAME.  */
+    return;
+
+  for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
+    {
+      if (!m_want_type_p || DECL_DECLARES_TYPE_P (field))
+       if (DECL_NAME (field))
+         m_candidates.safe_push (DECL_NAME (field));
+    }
+}
+
+
+/* Helper function for lookup_member_fuzzy, called via dfs_walk_all
+   DATA is really a lookup_field_fuzzy_info.  Look for a field with
+   the name indicated there in BINFO.  Gathers pertinent identifiers into
+   m_candidates.  */
+
+static tree
+lookup_field_fuzzy_r (tree binfo, void *data)
+{
+  lookup_field_fuzzy_info *lffi = (lookup_field_fuzzy_info *) data;
+  tree type = BINFO_TYPE (binfo);
+
+  /* First, look for functions.  */
+  if (!lffi->m_want_type_p)
+    lffi->fuzzy_lookup_fnfields (type);
+
+  /* Look for data member and types.  */
+  lffi->fuzzy_lookup_field (type);
+
+  return NULL_TREE;
+}
+
+/* Like lookup_member, but try to find the closest match for NAME,
+   rather than an exact match, and return an identifier (or NULL_TREE).
+   Do not complain.  */
+
+tree
+lookup_member_fuzzy (tree xbasetype, tree name, bool want_type_p)
+{
+  tree type = NULL_TREE, basetype_path = NULL_TREE;
+  struct lookup_field_fuzzy_info lffi (want_type_p);
+
+  /* rval_binfo is the binfo associated with the found member, note,
+     this can be set with useful information, even when rval is not
+     set, because it must deal with ALL members, not just non-function
+     members.  It is used for ambiguity checking and the hidden
+     checks.  Whereas rval is only set if a proper (not hidden)
+     non-function member is found.  */
+
+  if (name == error_mark_node
+      || xbasetype == NULL_TREE
+      || xbasetype == error_mark_node)
+    return NULL_TREE;
+
+  gcc_assert (identifier_p (name));
+
+  if (TREE_CODE (xbasetype) == TREE_BINFO)
+    {
+      type = BINFO_TYPE (xbasetype);
+      basetype_path = xbasetype;
+    }
+  else
+    {
+      if (!RECORD_OR_UNION_CODE_P (TREE_CODE (xbasetype)))
+       return NULL_TREE;
+      type = xbasetype;
+      xbasetype = NULL_TREE;
+    }
+
+  type = complete_type (type);
+
+  /* Make sure we're looking for a member of the current instantiation in the
+     right partial specialization.  */
+  if (flag_concepts && dependent_type_p (type))
+    type = currently_open_class (type);
+
+  if (!basetype_path)
+    basetype_path = TYPE_BINFO (type);
+
+  if (!basetype_path)
+    return NULL_TREE;
+
+  /* Populate lffi.m_candidates.  */
+  dfs_walk_all (basetype_path, &lookup_field_fuzzy_r, NULL, &lffi);
+
+  return find_closest_identifier (name, &lffi.m_candidates);
+}
+
 /* Like lookup_member, except that if we find a function member we
    return NULL_TREE.  */
 
index 5f7d4bb994adfec3ee42215a2495b35bf7bc531a..95178905f9e5884fc4c32a13bad36afac0317d0a 100644 (file)
@@ -2792,9 +2792,18 @@ finish_class_member_access_expr (tree object, tree name, bool template_p,
          if (member == NULL_TREE)
            {
              if (complain & tf_error)
-               error ("%q#T has no member named %qE",
-                      TREE_CODE (access_path) == TREE_BINFO
-                      ? TREE_TYPE (access_path) : object_type, name);
+               {
+                 tree guessed_id = lookup_member_fuzzy (access_path, name,
+                                                        /*want_type=*/false);
+                 if (guessed_id)
+                   error ("%q#T has no member named %qE; did you mean %qE?",
+                          TREE_CODE (access_path) == TREE_BINFO
+                          ? TREE_TYPE (access_path) : object_type, name, guessed_id);
+                 else
+                   error ("%q#T has no member named %qE",
+                          TREE_CODE (access_path) == TREE_BINFO
+                          ? TREE_TYPE (access_path) : object_type, name);
+               }
              return error_mark_node;
            }
          if (member == error_mark_node)
index d2037766ecad46a6a0cf1d8515e78cfaff4f447b..f7fbcc0be32d7c767a1220de9a54460f71ba412f 100644 (file)
@@ -37,3 +37,44 @@ levenshtein_distance (tree ident_s, tree ident_t)
                               IDENTIFIER_POINTER (ident_t),
                               IDENTIFIER_LENGTH (ident_t));
 }
+
+/* Given TARGET, an identifier, and CANDIDATES, a vec of identifiers,
+   determine which element within CANDIDATES has the lowest edit
+   distance to TARGET.  If there are multiple elements with the
+   same minimal distance, the first in the vector wins.
+
+   If more than half of the letters were misspelled, the suggestion is
+   likely to be meaningless, so return NULL_TREE for this case.  */
+
+tree
+find_closest_identifier (tree target, const auto_vec<tree> *candidates)
+{
+  gcc_assert (TREE_CODE (target) == IDENTIFIER_NODE);
+
+  int i;
+  tree identifier;
+  tree best_identifier = NULL_TREE;
+  edit_distance_t best_distance = MAX_EDIT_DISTANCE;
+  FOR_EACH_VEC_ELT (*candidates, i, identifier)
+    {
+      gcc_assert (TREE_CODE (identifier) == IDENTIFIER_NODE);
+      edit_distance_t dist = levenshtein_distance (target, identifier);
+      if (dist < best_distance)
+       {
+         best_distance = dist;
+         best_identifier = identifier;
+       }
+    }
+
+  /* If more than half of the letters were misspelled, the suggestion is
+     likely to be meaningless.  */
+  if (best_identifier)
+    {
+      unsigned int cutoff = MAX (IDENTIFIER_LENGTH (target),
+                                IDENTIFIER_LENGTH (best_identifier)) / 2;
+      if (best_distance > cutoff)
+       return NULL_TREE;
+    }
+
+  return best_identifier;
+}
index 673a75625109d7c282cec41a71aee714e7cbcfef..ad029985de2c780f810959d9fb36d43b175782da 100644 (file)
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 typedef unsigned int edit_distance_t;
 const edit_distance_t MAX_EDIT_DISTANCE = UINT_MAX;
 
+/* spellcheck.c  */
 extern edit_distance_t
 levenshtein_distance (const char *s, int len_s,
                      const char *t, int len_t);
@@ -30,7 +31,12 @@ levenshtein_distance (const char *s, int len_s,
 extern edit_distance_t
 levenshtein_distance (const char *s, const char *t);
 
+/* spellcheck-tree.c  */
+
 extern edit_distance_t
 levenshtein_distance (tree ident_s, tree ident_t);
 
+extern tree
+find_closest_identifier (tree target, const auto_vec<tree> *candidates);
+
 #endif  /* GCC_SPELLCHECK_H  */
index f107483de21f298ba6778d7a2ee42a8ef6e167d6..face304aaaeecd73bfd18fbb2923a0932708a940 100644 (file)
@@ -1,3 +1,7 @@
+2015-11-19  David Malcolm  <dmalcolm@redhat.com>
+
+       * g++.dg/spellcheck-fields.C: New file.
+
 2015-11-19  Aditya Kumar  <aditya.k7@samsung.com>
            Sebastian Pop  <s.pop@samsung.com>