This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
[PATCH] C++ FE: offer suggestions for misspelled field names
- From: David Malcolm <dmalcolm at redhat dot com>
- To: gcc-patches at gcc dot gnu dot org
- Cc: David Malcolm <dmalcolm at redhat dot com>
- Date: Fri, 13 Nov 2015 15:35:09 -0500
- Subject: [PATCH] C++ FE: offer suggestions for misspelled field names
- Authentication-results: sourceware.org; auth=none
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®rtest?
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 | 138 +++++++++++++++++++++++++++++++
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, 288 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..fe794a7 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,143 @@ 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 (; vec_safe_iterate (method_vec, i, &fn); ++i)
+ 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