From 11d8dd6fe925464af9dc8283360e196c55c48e17 Mon Sep 17 00:00:00 2001 From: dmalcolm Date: Fri, 20 Nov 2015 01:26:00 +0000 Subject: [PATCH] C++ FE: offer suggestions for misspelled field names 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. git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@230638 138bc75d-0d04-0410-961f-82ee72b054a4 --- gcc/c/ChangeLog | 5 ++ gcc/c/c-typeck.c | 28 +------- gcc/cp/ChangeLog | 13 ++++ 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/ChangeLog | 4 ++ 9 files changed, 222 insertions(+), 30 deletions(-) diff --git a/gcc/c/ChangeLog b/gcc/c/ChangeLog index a0cab878d6f4..9399053a7f9f 100644 --- a/gcc/c/ChangeLog +++ b/gcc/c/ChangeLog @@ -1,3 +1,8 @@ +2015-11-19 David Malcolm + + * c-typeck.c (lookup_field_fuzzy): Move determination of closest + candidate into a new function, find_closest_identifier. + 2015-11-19 Marek Polacek PR c/68412 diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c index 5cb0f7e622ed..9284bfcea66e 100644 --- a/gcc/c/c-typeck.c +++ b/gcc/c/c-typeck.c @@ -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 diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index 92b1d280c3d8..cf436d7d106b 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,16 @@ +2015-11-19 David Malcolm + + * 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 * except.c (do_free_exception): Use transactional wrapper. diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 14ea1194113b..38bd7dd614ff 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -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); diff --git a/gcc/cp/search.c b/gcc/cp/search.c index 94502f6322fb..0c11a8318cb2 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 m_candidates; +}; + +/* Locate all methods within TYPE, append them to m_candidates. */ + +void +lookup_field_fuzzy_info::fuzzy_lookup_fnfields (tree type) +{ + vec *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 5f7d4bb994ad..95178905f9e5 100644 --- a/gcc/cp/typeck.c +++ b/gcc/cp/typeck.c @@ -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) diff --git a/gcc/spellcheck-tree.c b/gcc/spellcheck-tree.c index d2037766ecad..f7fbcc0be32d 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 *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 673a75625109..ad029985de2c 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 *candidates); + #endif /* GCC_SPELLCHECK_H */ diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index f107483de21f..face304aaaee 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,7 @@ +2015-11-19 David Malcolm + + * g++.dg/spellcheck-fields.C: New file. + 2015-11-19 Aditya Kumar Sebastian Pop -- 2.47.2