From patchwork Fri Nov 13 20:35:09 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 544441 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id D8BB7141434 for ; Sat, 14 Nov 2015 07:16:54 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=gcc.gnu.org header.i=@gcc.gnu.org header.b=iLTTLNk+; dkim-atps=neutral DomainKey-Signature: a=rsa-sha1; c=nofws; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:from :to:cc:subject:date:message-id; q=dns; s=default; b=vbVgvHn+Rzm9 8hgM0PxVzcGhiw18+SW94PTIQnBdBFPuyf+vsybmAs5DpQp7QCC2wxUme9WGWWmq NSBCo7VPo6j6mlcFmwVH4EtO8QemRwQX12esEQ/wVz7Em0n3pgUitdqpF11D5TE7 JuCMn+pbmHrPZW49JoFR2IS1+NzNYLk= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:from :to:cc:subject:date:message-id; s=default; bh=Ys/0bnDnZeU2lh1tQH uTec0cDio=; b=iLTTLNk+TbgXMI0iy0eiryknuWUswCa5vm6ji2dOdQ9EJ769HS VdqKisB6oLnSuwuQABcctUafQv3ATFKpsWBv45c7//fy2ep5hDFaTzSApGf4QcHn 90NnBqMz72CyKbqNG1kANNoRQqgTVS3KqHqXA9TNvs85LQ83oIY2J/Vo8= Received: (qmail 122477 invoked by alias); 13 Nov 2015 20:16:48 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Received: (qmail 122467 invoked by uid 89); 13 Nov 2015 20:16:47 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.0 required=5.0 tests=AWL, BAYES_00, RP_MATCHES_RCVD, SPF_HELO_PASS autolearn=ham version=3.3.2 X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES256-GCM-SHA384 encrypted) ESMTPS; Fri, 13 Nov 2015 20:16:45 +0000 Received: from int-mx09.intmail.prod.int.phx2.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.22]) by mx1.redhat.com (Postfix) with ESMTPS id 06F971309 for ; Fri, 13 Nov 2015 20:16:44 +0000 (UTC) Received: from c64.redhat.com (vpn-231-129.phx2.redhat.com [10.3.231.129]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id tADKGhcx022478; Fri, 13 Nov 2015 15:16:43 -0500 From: David Malcolm To: gcc-patches@gcc.gnu.org Cc: David Malcolm Subject: [PATCH] C++ FE: offer suggestions for misspelled field names Date: Fri, 13 Nov 2015 15:35:09 -0500 Message-Id: <1447446909-44545-1-git-send-email-dmalcolm@redhat.com> X-IsSubscribed: yes 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 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 (; 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 *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 *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::' 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'?" } */ +}