This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [PATCH] C++ FE: offer suggestions for misspelled field names


On Fri, 2015-11-13 at 15:35 -0500, David Malcolm wrote:
> This is analogous to:
>   "[PATCH 2/2] C FE: suggest corrections for misspelled field names"
>      https://gcc.gnu.org/ml/gcc-patches/2015-10/msg03380.html
> but for the C++ frontend.
> 
> OK for trunk if it passes bootstrap&regrtest?

Oops; it didn't.  Sorry.


Updated patch attached; this one does pass bootstrap&regrtest.

OK for trunk?

>From fdf5026e324e00d66774c9f581840120061d4e39 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 13 Nov 2015 15:30:18 -0500
Subject: [PATCH] C++ FE: offer suggestions for misspelled field names (v2)

This is analogous to:
  "[PATCH 2/2] C FE: suggest corrections for misspelled field names"
     https://gcc.gnu.org/ml/gcc-patches/2015-10/msg03380.html
but for the C++ frontend.

Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu;
adds 39 PASS results to g++.sum.

OK for trunk?

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.
---
 gcc/c/c-typeck.c                         |  28 +------
 gcc/cp/cp-tree.h                         |   1 +
 gcc/cp/search.c                          | 139 +++++++++++++++++++++++++++++++
 gcc/cp/typeck.c                          |  15 +++-
 gcc/spellcheck-tree.c                    |  41 +++++++++
 gcc/spellcheck.h                         |   6 ++
 gcc/testsuite/g++.dg/spellcheck-fields.C |  89 ++++++++++++++++++++
 7 files changed, 289 insertions(+), 30 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/spellcheck-fields.C

diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index eb4e1fc..9a23ba2 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -2280,33 +2280,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
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 828f268..9dc0e44 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6116,6 +6116,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);
diff --git a/gcc/cp/search.c b/gcc/cp/search.c
index 94502f6..0c11a83 100644
--- a/gcc/cp/search.c
+++ b/gcc/cp/search.c
@@ -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.  */
 
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 90c95fb..c1e8546 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -2795,9 +2795,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)
diff --git a/gcc/spellcheck-tree.c b/gcc/spellcheck-tree.c
index d203776..f7fbcc0 100644
--- a/gcc/spellcheck-tree.c
+++ b/gcc/spellcheck-tree.c
@@ -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;
+}
diff --git a/gcc/spellcheck.h b/gcc/spellcheck.h
index 673a756..ad02998 100644
--- a/gcc/spellcheck.h
+++ b/gcc/spellcheck.h
@@ -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  */
diff --git a/gcc/testsuite/g++.dg/spellcheck-fields.C b/gcc/testsuite/g++.dg/spellcheck-fields.C
new file mode 100644
index 0000000..df49c92
--- /dev/null
+++ b/gcc/testsuite/g++.dg/spellcheck-fields.C
@@ -0,0 +1,89 @@
+/* { dg-do compile } */
+
+struct foo
+{
+  int foo;
+  int bar;
+  int baz;
+};
+
+int test (struct foo *ptr)
+{
+  return ptr->m_bar; /* { dg-error "'struct foo' has no member named 'm_bar'; did you mean 'bar'?" } */
+}
+
+int test2 (void)
+{
+  struct foo instance = {0, 0, 0};
+  return instance.m_bar; /* { dg-error "'struct foo' has no member named 'm_bar'; did you mean 'bar'?" } */
+}
+
+struct s {
+    struct j { int aa; } kk;
+    int ab;
+};
+
+void test3 (struct s x)
+{
+  x.ac;  /* { dg-error "'struct s' has no member named 'ac'; did you mean 'ab'?" } */
+}
+
+int test4 (struct foo *ptr)
+{
+  return sizeof (ptr->foa); /* { dg-error "'struct foo' has no member named 'foa'; did you mean 'foo'?" } */
+}
+
+/* Verify that we don't offer nonsensical suggestions.  */
+
+int test5 (struct foo *ptr)
+{
+  return ptr->this_is_unlike_any_of_the_fields;   /* { dg-bogus "did you mean" } */
+  /* { dg-error "has no member named" "" { target *-*-* } 40 } */
+}
+
+union u
+{
+  int color;
+  int shape;
+};
+
+int test6 (union u *ptr)
+{
+  return ptr->colour; /* { dg-error "'union u' has no member named 'colour'; did you mean 'color'?" } */
+}
+
+struct has_anon
+{
+  struct { int color; } s;
+};
+
+int test7 (struct has_anon *ptr)
+{
+  return ptr->s.colour; /* { dg-error "'struct has_anon::<anonymous>' has no member named 'colour'; did you mean 'color'?" } */
+}
+
+int test8 (foo &ref)
+{
+  return ref.m_bar; /* { dg-error "'struct foo' has no member named 'm_bar'; did you mean 'bar'?" } */
+}
+
+struct bar : public foo
+{
+  int fizz;
+  typedef int my_type;
+};
+
+int test9 (bar *ptr)
+{
+  return ptr->fuzz; /* { dg-error "'struct bar' has no member named 'fuzz'; did you mean 'fizz'?" } */
+}
+
+int test10 (bar *ptr)
+{
+  return ptr->m_foo; /* { dg-error "'struct bar' has no member named 'm_foo'; did you mean 'foo'?" } */
+}
+
+int test11 (bar *ptr)
+{
+  return ptr->mytype; /* { dg-error "'struct bar' has no member named 'mytype'; did you mean 'my_type'?" } */
+}
-- 
1.8.5.3


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]