diff mbox

[2/2] C++ FE: handle misspelled identifiers and typenames

Message ID 1467312830-36082-2-git-send-email-dmalcolm@redhat.com
State New
Headers show

Commit Message

David Malcolm June 30, 2016, 6:53 p.m. UTC
This is a port of the C frontend's r237714 [1] to the C++ frontend:
  https://gcc.gnu.org/ml/gcc-patches/2016-06/msg01052.html
offering spelling suggestions for misspelled identifiers, macro names,
and some keywords (e.g. "singed" vs "signed" aka PR c/70339).

Unlike the C frontend, there doesn't seem to be an easy way to
distinguish between cases where we're expecting a typename vs
a variable name, so some of the logic is a little different.

Examples of suggestions can be seen in the test case.

Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu
(in combination with the prior patch); adds 240 PASS results to
g++.sum.

OK for trunk?

Dave

[1] aka 8469aece13814deddf2cd80538d33c2d0a8d60d9 in the git mirror

gcc/c/ChangeLog:
	PR c/70339
	* c-decl.c (struct edit_distance_traits<cpp_hashnode *>): Move to
	spellcheck-tree.h
	(best_macro_match): Likewise, converting from a typedef to a
	subclass.
	(find_closest_macro_cpp_cb): Move to spellcheck-tree.c.
	(lookup_name_fuzzy): Update for change of best_macro_match to a
	subclass with a ctor that calls cpp_forall_identifiers.

gcc/cp/ChangeLog:
	PR c/70339
	* name-lookup.c: Include gcc-rich-location.h, spellcheck-tree.h,
	and parser.h.
	(suggest_alternatives_for): If no candidates are found, try
	lookup_name_fuzzy and report if if finds a suggestion.
	(consider_binding_level): New function.
	(consider_binding_levels): New function.
	(lookup_name_fuzzy) New function.
	* parser.c: Include gcc-rich-location.h.
	(cp_lexer_next_token_is_decl_specifier_keyword): Move most of
	logic into...
	(cp_keyword_starts_decl_specifier_p): ...this new function.
	(cp_parser_diagnose_invalid_type_name): When issuing
	"does not name a type" errors, attempt to make a suggestion using
	lookup_name_fuzzy.
	* parser.h (cp_keyword_starts_decl_specifier_p): New prototype.
	* search.c (lookup_field_fuzzy_info::fuzzy_lookup_field): Don't
	attempt to access TYPE_FIELDS within a TYPE_PACK_EXPANSION.

gcc/ChangeLog:
	PR c/70339
	* diagnostic-show-locus.c (diagnostic_show_locus): If this is the
	same location as last time, don't skip if we have fix-it hints.
	Clarify the skipping logic by converting it from one "if" clause
	to repeated "if" clauses.
	* spellcheck-tree.c: Include "cpplib.h".
	(find_closest_macro_cpp_cb): Move here from c/c-decl.c.
	(best_macro_match::best_macro_match): New constructor.
	* spellcheck-tree.h (struct edit_distance_traits<cpp_hashnode *>):
	Move here from c/c-decl.c.
	(class best_macro_match): Move here from c/c-decl.c, converting
	from a typedef to a subclass, gaining a ctor.

gcc/testsuite/ChangeLog:
	PR c/70339
	* g++.dg/spellcheck-identifiers.C: New test case, based on
	gcc.dg/spellcheck-identifiers.c.
	* g++.dg/spellcheck-typenames.C: New test case, based on
	gcc.dg/spellcheck-typenames.c
---
 gcc/c/c-decl.c                                |  42 +----
 gcc/cp/name-lookup.c                          | 140 +++++++++++++-
 gcc/cp/parser.c                               |  43 +++--
 gcc/cp/parser.h                               |   1 +
 gcc/cp/search.c                               |   4 +
 gcc/diagnostic-show-locus.c                   |  15 +-
 gcc/spellcheck-tree.c                         |  31 ++++
 gcc/spellcheck-tree.h                         |  26 +++
 gcc/testsuite/g++.dg/spellcheck-identifiers.C | 255 ++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/spellcheck-typenames.C   |  84 +++++++++
 10 files changed, 584 insertions(+), 57 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/spellcheck-identifiers.C
 create mode 100644 gcc/testsuite/g++.dg/spellcheck-typenames.C

Comments

Manuel López-Ibáñez July 2, 2016, 12:01 p.m. UTC | #1
On 30/06/16 19:53, David Malcolm wrote:
> This is a port of the C frontend's r237714 [1] to the C++ frontend:
>    https://gcc.gnu.org/ml/gcc-patches/2016-06/msg01052.html
> offering spelling suggestions for misspelled identifiers, macro names,
> and some keywords (e.g. "singed" vs "signed" aka PR c/70339).

Cool!

> -      error_at (location, "%qE does not name a type", id);
> +      const char *suggestion = NULL;
> +      if (TREE_CODE (id) == IDENTIFIER_NODE)
> +        suggestion = lookup_name_fuzzy (id, FUZZY_LOOKUP_TYPENAME);
> +      if (suggestion)
> +	{
> +	  gcc_rich_location richloc (location);
> +	  richloc.add_fixit_misspelled_id (location, suggestion);
> +	  error_at_rich_loc (&richloc,
> +			     "%qE does not name a type; did you mean %qs?",
> +			     id, suggestion);
> +	}
> +      else
> +	error_at (location, "%qE does not name a type", id);

It should be possible to encapsulate all this suggestion logic very deep in 
diagnostics.c and replace all the above with something similar to:

const char *suggestion = NULL;
if (TREE_CODE (id) == IDENTIFIER_NODE)
     suggestion = lookup_name_fuzzy (id, FUZZY_LOOKUP_TYPENAME);

error_with_suggestion(location, suggestion, "%qE does not name a type",
                       "%qE does not name a type; did you mean %qs?", id);

Perhaps with a bit more effort, even make the language-specific printers handle 
the task of looking for the suggestion, replacing the above with something like:

error_with_suggestion(location, FUZZY_LOOKUP_TYPENAME,
                       "%qE does not name a type",
                       "%qE does not name a type; did you mean %qs?", id);


> diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
> index 7aab658..49f7f11 100644
> --- a/gcc/diagnostic-show-locus.c
> +++ b/gcc/diagnostic-show-locus.c
> @@ -1280,9 +1280,18 @@ diagnostic_show_locus (diagnostic_context * context,
>   {
>     pp_newline (context->printer);
>
> -  if (!context->show_caret
> -      || diagnostic_location (diagnostic, 0) <= BUILTINS_LOCATION
> -      || diagnostic_location (diagnostic, 0) == context->last_location)
> +  /* Do nothing if source-printing has been disabled.  */
> +  if (!context->show_caret)
> +    return;
> +
> +  /* Don't attempt to print source for UNKNOWN_LOCATION and for builtins.  */
> +  if (diagnostic_location (diagnostic, 0) <= BUILTINS_LOCATION)
> +    return;
> +
> +  /* Don't print the same source location twice in a row, unless we have
> +     fix-it hints.  */
> +  if (diagnostic_location (diagnostic, 0) == context->last_location
> +      && diagnostic->richloc->get_num_fixit_hints () == 0)
>       return;
>
>     context->last_location = diagnostic_location (diagnostic, 0);

This seems independent of the suggestion stuff and could be committed separately.

> --- a/gcc/spellcheck-tree.c
> +++ b/gcc/spellcheck-tree.c
> @@ -22,6 +22,7 @@ along with GCC; see the file COPYING3.  If not see
>   #include "coretypes.h"
>   #include "tm.h"
>   #include "tree.h"
> +#include "cpplib.h"

You make a language-independent file depend on cpplib.h. Wouldn't it be better 
to move c-specific stuff to a new c-family/c-spellcheck.c ?

Cheers,

Manuel.
Jeff Law July 13, 2016, 10:12 p.m. UTC | #2
On 06/30/2016 12:53 PM, David Malcolm wrote:
> This is a port of the C frontend's r237714 [1] to the C++ frontend:
>   https://gcc.gnu.org/ml/gcc-patches/2016-06/msg01052.html
> offering spelling suggestions for misspelled identifiers, macro names,
> and some keywords (e.g. "singed" vs "signed" aka PR c/70339).
>
> Unlike the C frontend, there doesn't seem to be an easy way to
> distinguish between cases where we're expecting a typename vs
> a variable name, so some of the logic is a little different.
>
> Examples of suggestions can be seen in the test case.
>
> Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu
> (in combination with the prior patch); adds 240 PASS results to
> g++.sum.
>
> OK for trunk?
>
> Dave
>
> [1] aka 8469aece13814deddf2cd80538d33c2d0a8d60d9 in the git mirror
>
> gcc/c/ChangeLog:
> 	PR c/70339
> 	* c-decl.c (struct edit_distance_traits<cpp_hashnode *>): Move to
> 	spellcheck-tree.h
> 	(best_macro_match): Likewise, converting from a typedef to a
> 	subclass.
> 	(find_closest_macro_cpp_cb): Move to spellcheck-tree.c.
> 	(lookup_name_fuzzy): Update for change of best_macro_match to a
> 	subclass with a ctor that calls cpp_forall_identifiers.
These are fine.

>
> gcc/cp/ChangeLog:
> 	PR c/70339
> 	* name-lookup.c: Include gcc-rich-location.h, spellcheck-tree.h,
> 	and parser.h.
> 	(suggest_alternatives_for): If no candidates are found, try
> 	lookup_name_fuzzy and report if if finds a suggestion.
> 	(consider_binding_level): New function.
> 	(consider_binding_levels): New function.
> 	(lookup_name_fuzzy) New function.
> 	* parser.c: Include gcc-rich-location.h.
> 	(cp_lexer_next_token_is_decl_specifier_keyword): Move most of
> 	logic into...
> 	(cp_keyword_starts_decl_specifier_p): ...this new function.
> 	(cp_parser_diagnose_invalid_type_name): When issuing
> 	"does not name a type" errors, attempt to make a suggestion using
> 	lookup_name_fuzzy.
> 	* parser.h (cp_keyword_starts_decl_specifier_p): New prototype.
> 	* search.c (lookup_field_fuzzy_info::fuzzy_lookup_field): Don't
> 	attempt to access TYPE_FIELDS within a TYPE_PACK_EXPANSION.
Going to let Jason on this part.

>
> gcc/ChangeLog:
> 	PR c/70339
> 	* diagnostic-show-locus.c (diagnostic_show_locus): If this is the
> 	same location as last time, don't skip if we have fix-it hints.
> 	Clarify the skipping logic by converting it from one "if" clause
> 	to repeated "if" clauses.
> 	* spellcheck-tree.c: Include "cpplib.h".
> 	(find_closest_macro_cpp_cb): Move here from c/c-decl.c.
> 	(best_macro_match::best_macro_match): New constructor.
> 	* spellcheck-tree.h (struct edit_distance_traits<cpp_hashnode *>):
> 	Move here from c/c-decl.c.
> 	(class best_macro_match): Move here from c/c-decl.c, converting
> 	from a typedef to a subclass, gaining a ctor.
OK.

>
> gcc/testsuite/ChangeLog:
> 	PR c/70339
> 	* g++.dg/spellcheck-identifiers.C: New test case, based on
> 	gcc.dg/spellcheck-identifiers.c.
> 	* g++.dg/spellcheck-typenames.C: New test case, based on
> 	gcc.dg/spellcheck-typenames.c
OK


jeff
David Malcolm July 14, 2016, 6:31 p.m. UTC | #3
On Wed, 2016-07-13 at 16:12 -0600, Jeff Law wrote:
> On 06/30/2016 12:53 PM, David Malcolm wrote:
> > This is a port of the C frontend's r237714 [1] to the C++ frontend:
> >   https://gcc.gnu.org/ml/gcc-patches/2016-06/msg01052.html
> > offering spelling suggestions for misspelled identifiers, macro
> > names,
> > and some keywords (e.g. "singed" vs "signed" aka PR c/70339).
> > 
> > Unlike the C frontend, there doesn't seem to be an easy way to
> > distinguish between cases where we're expecting a typename vs
> > a variable name, so some of the logic is a little different.
> > 
> > Examples of suggestions can be seen in the test case.
> > 
> > Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu
> > (in combination with the prior patch); adds 240 PASS results to
> > g++.sum.
> > 
> > OK for trunk?
> > 
> > Dave
> > 
> > [1] aka 8469aece13814deddf2cd80538d33c2d0a8d60d9 in the git mirror
> > 
> > gcc/c/ChangeLog:
> > 	PR c/70339
> > 	* c-decl.c (struct edit_distance_traits<cpp_hashnode *>): Move
> > to
> > 	spellcheck-tree.h
> > 	(best_macro_match): Likewise, converting from a typedef to a
> > 	subclass.
> > 	(find_closest_macro_cpp_cb): Move to spellcheck-tree.c.
> > 	(lookup_name_fuzzy): Update for change of best_macro_match to a
> > 	subclass with a ctor that calls cpp_forall_identifiers.
> These are fine.
> 
> > 
> > gcc/cp/ChangeLog:
> > 	PR c/70339
> > 	* name-lookup.c: Include gcc-rich-location.h, spellcheck
> > -tree.h,
> > 	and parser.h.
> > 	(suggest_alternatives_for): If no candidates are found, try
> > 	lookup_name_fuzzy and report if if finds a suggestion.
> > 	(consider_binding_level): New function.
> > 	(consider_binding_levels): New function.
> > 	(lookup_name_fuzzy) New function.
> > 	* parser.c: Include gcc-rich-location.h.
> > 	(cp_lexer_next_token_is_decl_specifier_keyword): Move most of
> > 	logic into...
> > 	(cp_keyword_starts_decl_specifier_p): ...this new function.
> > 	(cp_parser_diagnose_invalid_type_name): When issuing
> > 	"does not name a type" errors, attempt to make a suggestion
> > using
> > 	lookup_name_fuzzy.
> > 	* parser.h (cp_keyword_starts_decl_specifier_p): New prototype.
> > 	* search.c (lookup_field_fuzzy_info::fuzzy_lookup_field): Don't
> > 	attempt to access TYPE_FIELDS within a TYPE_PACK_EXPANSION.
> Going to let Jason on this part.

Thanks.  The corresponding code in the C frontend is likely to change
due to the fix being discussed for PR c/71858, so I think I'll wait
until that other PR is fixed, and then resubmit an updated version of
this for review, incorporating equivalent fixes for C++.

> > 
> > gcc/ChangeLog:
> > 	PR c/70339
> > 	* diagnostic-show-locus.c (diagnostic_show_locus): If this is
> > the
> > 	same location as last time, don't skip if we have fix-it hints.
> > 	Clarify the skipping logic by converting it from one "if"
> > clause
> > 	to repeated "if" clauses.
> > 	* spellcheck-tree.c: Include "cpplib.h".
> > 	(find_closest_macro_cpp_cb): Move here from c/c-decl.c.
> > 	(best_macro_match::best_macro_match): New constructor.
> > 	* spellcheck-tree.h (struct edit_distance_traits<cpp_hashnode
> > *>):
> > 	Move here from c/c-decl.c.
> > 	(class best_macro_match): Move here from c/c-decl.c, converting
> > 	from a typedef to a subclass, gaining a ctor.
> OK.
> 
> > 
> > gcc/testsuite/ChangeLog:
> > 	PR c/70339
> > 	* g++.dg/spellcheck-identifiers.C: New test case, based on
> > 	gcc.dg/spellcheck-identifiers.c.
> > 	* g++.dg/spellcheck-typenames.C: New test case, based on
> > 	gcc.dg/spellcheck-typenames.c
> OK
> 
> 
> jeff
>
diff mbox

Patch

diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index 09f7f79..dc14485 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -3955,45 +3955,6 @@  lookup_name_in_scope (tree name, struct c_scope *scope)
   return NULL_TREE;
 }
 
-/* Specialization of edit_distance_traits for preprocessor macros.  */
-
-template <>
-struct edit_distance_traits<cpp_hashnode *>
-{
-  static size_t get_length (cpp_hashnode *hashnode)
-  {
-    return hashnode->ident.len;
-  }
-
-  static const char *get_string (cpp_hashnode *hashnode)
-  {
-    return (const char *)hashnode->ident.str;
-  }
-};
-
-/* Specialization of best_match<> for finding the closest preprocessor
-   macro to a given identifier.  */
-
-typedef best_match<tree, cpp_hashnode *> best_macro_match;
-
-/* A callback for cpp_forall_identifiers, for use by lookup_name_fuzzy.
-   Process HASHNODE and update the best_macro_match instance pointed to be
-   USER_DATA.  */
-
-static int
-find_closest_macro_cpp_cb (cpp_reader *, cpp_hashnode *hashnode,
-			   void *user_data)
-{
-  if (hashnode->type != NT_MACRO)
-    return 1;
-
-  best_macro_match *bmm = (best_macro_match *)user_data;
-  bmm->consider (hashnode);
-
-  /* Keep iterating.  */
-  return 1;
-}
-
 /* Look for the closest match for NAME within the currently valid
    scopes.
 
@@ -4047,8 +4008,7 @@  lookup_name_fuzzy (tree name, enum lookup_name_fuzzy_kind kind)
      non-NULL result for best_macro_match if it's better than any of
      the identifiers already checked, which avoids needless creation
      of identifiers for macro hashnodes.  */
-  best_macro_match bmm (name, bm.get_best_distance ());
-  cpp_forall_identifiers (parse_in, find_closest_macro_cpp_cb, &bmm);
+  best_macro_match bmm (name, bm.get_best_distance (), parse_in);
   cpp_hashnode *best_macro = bmm.get_best_meaningful_candidate ();
   /* If a macro is the closest so far to NAME, use it, creating an
      identifier tree node for it.  */
diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c
index cbd5209..88a5339 100644
--- a/gcc/cp/name-lookup.c
+++ b/gcc/cp/name-lookup.c
@@ -29,6 +29,9 @@  along with GCC; see the file COPYING3.  If not see
 #include "debug.h"
 #include "c-family/c-pragma.h"
 #include "params.h"
+#include "gcc-rich-location.h"
+#include "spellcheck-tree.h"
+#include "parser.h"
 
 /* The bindings for a particular name in a particular scope.  */
 
@@ -4435,9 +4438,20 @@  suggest_alternatives_for (location_t location, tree name)
 
   namespaces_to_search.release ();
 
-  /* Nothing useful to report.  */
+  /* Nothing useful to report for NAME.  Report on likely misspellings,
+     or do nothing.  */
   if (candidates.is_empty ())
-    return;
+    {
+      const char *fuzzy_name = lookup_name_fuzzy (name, FUZZY_LOOKUP_NAME);
+      if (fuzzy_name)
+	{
+	  gcc_rich_location richloc (location);
+	  richloc.add_fixit_misspelled_id (location, fuzzy_name);
+	  inform_at_rich_loc (&richloc, "suggested alternative: %qs",
+			      fuzzy_name);
+	}
+      return;
+    }
 
   inform_n (location, candidates.length (),
 	    "suggested alternative:",
@@ -4672,6 +4686,128 @@  qualified_lookup_using_namespace (tree name, tree scope,
   return result->value != error_mark_node;
 }
 
+/* Helper function for consider_binding_levels (and, in turn,
+   lookup_name_fuzzy).
+   Traverse binding level LVL, looking for good name matches for NAME
+   (and BM).
+   Skip builtin functions unless CONSIDER_BUILTIN_FUNCTIONS is set.  */
+static void
+consider_binding_level (tree name, best_match <tree, tree> &bm,
+			cp_binding_level *lvl, bool look_within_fields,
+			bool consider_builtin_functions,
+			enum lookup_name_fuzzy_kind kind)
+{
+  if (look_within_fields)
+    if (lvl->this_entity && TREE_CODE (lvl->this_entity) == RECORD_TYPE)
+      {
+	tree type = lvl->this_entity;
+	bool want_type_p = (kind == FUZZY_LOOKUP_TYPENAME);
+	tree best_matching_field
+	  = lookup_member_fuzzy (type, name, want_type_p);
+	if (best_matching_field)
+	  bm.consider (best_matching_field);
+      }
+
+  for (tree t = lvl->names; t; t = TREE_CHAIN (t))
+    {
+      /* Don't use bindings from implicitly declared functions,
+	 as they were likely misspellings themselves.  */
+      if (TREE_TYPE (t) == error_mark_node)
+	continue;
+
+      /* Skip builtin functions if we're not considering them this time.  */
+      if (!consider_builtin_functions)
+	if (TREE_CODE (t) == FUNCTION_DECL)
+	  if (DECL_BUILT_IN (t))
+	    continue;
+
+      if (DECL_NAME (t))
+	bm.consider (DECL_NAME (t));
+    }
+}
+
+/* Helper function for lookup_name_fuzzy.
+   Traverse the binding levels, looking for good name matches for BM.
+   Skip builtin functions unless CONSIDER_BUILTIN_FUNCTIONS is set.  */
+static void
+consider_binding_levels (tree name, best_match <tree, tree> &bm,
+			 bool consider_builtin_functions,
+			 enum lookup_name_fuzzy_kind kind)
+{
+  cp_binding_level *lvl;
+  for (lvl = scope_chain->class_bindings; lvl; lvl = lvl->level_chain)
+    consider_binding_level (name, bm, lvl, true, consider_builtin_functions,
+			    kind);
+
+  for (lvl = current_binding_level; lvl; lvl = lvl->level_chain)
+    consider_binding_level (name, bm, lvl, false, consider_builtin_functions,
+			    kind);
+}
+
+/* Search for near-matches for NAME within the current bindings, and within
+   macro names, returning the best match as a const char *, or NULL if
+   no reasonable match is found.  */
+
+const char *
+lookup_name_fuzzy (tree name, enum lookup_name_fuzzy_kind kind)
+{
+  gcc_assert (TREE_CODE (name) == IDENTIFIER_NODE);
+
+  best_match <tree, tree> bm (name);
+
+  /* First pass: traverse the binding levels, ignoring builtin functions
+     for now.  We do this to give everything else a higher priority:
+     there are many builtin functions with short names that otherwise
+     tend to show up as unhelpful suggestions (e.g. "carg" is a poor
+     suggestion for "arg" and "char").  */
+  consider_binding_levels (name, bm, false, kind);
+
+  /* Consider macros: if the user misspelled a macro name e.g. "SOME_MACRO"
+     as:
+       x = SOME_OTHER_MACRO (y);
+     then "SOME_OTHER_MACRO" will survive to the frontend and show up
+     as a misspelled identifier.
+
+     Use the best distance so far so that a candidate is only set if
+     a macro is better than anything so far.  This allows early rejection
+     (without calculating the edit distance) of macro names that must have
+     distance >= bm.get_best_distance (), and means that we only get a
+     non-NULL result for best_macro_match if it's better than any of
+     the identifiers already checked.  */
+  best_macro_match bmm (name, bm.get_best_distance (), parse_in);
+  cpp_hashnode *best_macro = bmm.get_best_meaningful_candidate ();
+  /* If a macro is the closest so far to NAME, suggest it.  */
+  if (best_macro)
+    return (const char *)best_macro->ident.str;
+
+  /* Try the "starts_decl_specifier_p" keywords to detect
+     "singed" vs "signed" typos.  */
+  for (unsigned i = 0; i < num_c_common_reswords; i++)
+    {
+      const c_common_resword *resword = &c_common_reswords[i];
+
+      if (!cp_keyword_starts_decl_specifier_p (resword->rid))
+	continue;
+
+      tree resword_identifier = ridpointers [resword->rid];
+      if (!resword_identifier)
+	continue;
+      gcc_assert (TREE_CODE (resword_identifier) == IDENTIFIER_NODE);
+      bm.consider (resword_identifier);
+    }
+
+  /* Try the levels again, this time considering builtin functions.  */
+  consider_binding_levels (name, bm, true, kind);
+
+  /* See if we have a good suggesion for the user.  */
+  tree best_id = bm.get_best_meaningful_candidate ();
+  if (best_id)
+    return IDENTIFIER_POINTER (best_id);
+
+  /* No meaningful suggestion available.  */
+  return NULL;
+}
+
 /* Subroutine of outer_binding.
 
    Returns TRUE if BINDING is a binding to a template parameter of
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index f27c479..18dc9d4 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -40,6 +40,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "c-family/c-indentation.h"
 #include "context.h"
 #include "cp-cilkplus.h"
+#include "gcc-rich-location.h"
 
 
 /* The lexer.  */
@@ -937,15 +938,12 @@  cp_lexer_next_token_is_not_keyword (cp_lexer* lexer, enum rid keyword)
   return cp_lexer_peek_token (lexer)->keyword != keyword;
 }
 
-/* Return true if the next token is a keyword for a decl-specifier.  */
+/* Return true if KEYWORD can start a decl-specifier.  */
 
-static bool
-cp_lexer_next_token_is_decl_specifier_keyword (cp_lexer *lexer)
+bool
+cp_keyword_starts_decl_specifier_p (enum rid keyword)
 {
-  cp_token *token;
-
-  token = cp_lexer_peek_token (lexer);
-  switch (token->keyword) 
+  switch (keyword)
     {
       /* auto specifier: storage-class-specifier in C++,
          simple-type-specifier in C++0x.  */
@@ -985,14 +983,25 @@  cp_lexer_next_token_is_decl_specifier_keyword (cp_lexer *lexer)
       return true;
 
     default:
-      if (token->keyword >= RID_FIRST_INT_N
-	  && token->keyword < RID_FIRST_INT_N + NUM_INT_N_ENTS
-	  && int_n_enabled_p[token->keyword - RID_FIRST_INT_N])
+      if (keyword >= RID_FIRST_INT_N
+	  && keyword < RID_FIRST_INT_N + NUM_INT_N_ENTS
+	  && int_n_enabled_p[keyword - RID_FIRST_INT_N])
 	return true;
       return false;
     }
 }
 
+/* Return true if the next token is a keyword for a decl-specifier.  */
+
+static bool
+cp_lexer_next_token_is_decl_specifier_keyword (cp_lexer *lexer)
+{
+  cp_token *token;
+
+  token = cp_lexer_peek_token (lexer);
+  return cp_keyword_starts_decl_specifier_p (token->keyword);
+}
+
 /* Returns TRUE iff the token T begins a decltype type.  */
 
 static bool
@@ -3154,7 +3163,19 @@  cp_parser_diagnose_invalid_type_name (cp_parser *parser, tree id,
   else if (!parser->scope)
     {
       /* Issue an error message.  */
-      error_at (location, "%qE does not name a type", id);
+      const char *suggestion = NULL;
+      if (TREE_CODE (id) == IDENTIFIER_NODE)
+        suggestion = lookup_name_fuzzy (id, FUZZY_LOOKUP_TYPENAME);
+      if (suggestion)
+	{
+	  gcc_rich_location richloc (location);
+	  richloc.add_fixit_misspelled_id (location, suggestion);
+	  error_at_rich_loc (&richloc,
+			     "%qE does not name a type; did you mean %qs?",
+			     id, suggestion);
+	}
+      else
+	error_at (location, "%qE does not name a type", id);
       /* If we're in a template class, it's possible that the user was
 	 referring to a type from a base class.  For example:
 
diff --git a/gcc/cp/parser.h b/gcc/cp/parser.h
index ccbace9..2923378 100644
--- a/gcc/cp/parser.h
+++ b/gcc/cp/parser.h
@@ -420,5 +420,6 @@  extern void debug (vec<cp_token, va_gc> *ptr);
 extern void cp_debug_parser (FILE *, cp_parser *);
 extern void debug (cp_parser &ref);
 extern void debug (cp_parser *ptr);
+extern bool cp_keyword_starts_decl_specifier_p (enum rid keyword);
 
 #endif  /* GCC_CP_PARSER_H  */
diff --git a/gcc/cp/search.c b/gcc/cp/search.c
index 990c3fe..4a862b7 100644
--- a/gcc/cp/search.c
+++ b/gcc/cp/search.c
@@ -1407,6 +1407,10 @@  lookup_field_fuzzy_info::fuzzy_lookup_field (tree type)
        The TYPE_FIELDS of TYPENAME_TYPE is its TYPENAME_TYPE_FULLNAME.  */
     return;
 
+  /* TYPE_FIELDS is not valid for a TYPE_PACK_EXPANSION.  */
+  if (TREE_CODE (type) == TYPE_PACK_EXPANSION)
+    return;
+
   for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
     {
       if (!m_want_type_p || DECL_DECLARES_TYPE_P (field))
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index 7aab658..49f7f11 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -1280,9 +1280,18 @@  diagnostic_show_locus (diagnostic_context * context,
 {
   pp_newline (context->printer);
 
-  if (!context->show_caret
-      || diagnostic_location (diagnostic, 0) <= BUILTINS_LOCATION
-      || diagnostic_location (diagnostic, 0) == context->last_location)
+  /* Do nothing if source-printing has been disabled.  */
+  if (!context->show_caret)
+    return;
+
+  /* Don't attempt to print source for UNKNOWN_LOCATION and for builtins.  */
+  if (diagnostic_location (diagnostic, 0) <= BUILTINS_LOCATION)
+    return;
+
+  /* Don't print the same source location twice in a row, unless we have
+     fix-it hints.  */
+  if (diagnostic_location (diagnostic, 0) == context->last_location
+      && diagnostic->richloc->get_num_fixit_hints () == 0)
     return;
 
   context->last_location = diagnostic_location (diagnostic, 0);
diff --git a/gcc/spellcheck-tree.c b/gcc/spellcheck-tree.c
index 63fb1a8..ef1e689 100644
--- a/gcc/spellcheck-tree.c
+++ b/gcc/spellcheck-tree.c
@@ -22,6 +22,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "coretypes.h"
 #include "tm.h"
 #include "tree.h"
+#include "cpplib.h"
 #include "spellcheck-tree.h"
 #include "selftest.h"
 #include "stringpool.h"
@@ -65,6 +66,36 @@  find_closest_identifier (tree target, const auto_vec<tree> *candidates)
   return bm.get_best_meaningful_candidate ();
 }
 
+/* A callback for cpp_forall_identifiers, for use by best_macro_match's ctor.
+   Process HASHNODE and update the best_macro_match instance pointed to be
+   USER_DATA.  */
+
+static int
+find_closest_macro_cpp_cb (cpp_reader *, cpp_hashnode *hashnode,
+			   void *user_data)
+{
+  if (hashnode->type != NT_MACRO)
+    return 1;
+
+  best_macro_match *bmm = (best_macro_match *)user_data;
+  bmm->consider (hashnode);
+
+  /* Keep iterating.  */
+  return 1;
+}
+
+/* Constructor for best_macro_match.
+   Use find_closest_macro_cpp_cb to find the closest matching macro to
+   NAME within distance < best_distance_so_far. */
+
+best_macro_match::best_macro_match (tree goal,
+				    edit_distance_t best_distance_so_far,
+				    cpp_reader *reader)
+  : best_match (goal, best_distance_so_far)
+{
+  cpp_forall_identifiers (reader, find_closest_macro_cpp_cb, this);
+}
+
 #if CHECKING_P
 
 namespace selftest {
diff --git a/gcc/spellcheck-tree.h b/gcc/spellcheck-tree.h
index 0d5e253..0e43d14 100644
--- a/gcc/spellcheck-tree.h
+++ b/gcc/spellcheck-tree.h
@@ -48,4 +48,30 @@  struct edit_distance_traits<tree>
   }
 };
 
+/* Specialization of edit_distance_traits for preprocessor macros.  */
+
+template <>
+struct edit_distance_traits<cpp_hashnode *>
+{
+  static size_t get_length (cpp_hashnode *hashnode)
+  {
+    return hashnode->ident.len;
+  }
+
+  static const char *get_string (cpp_hashnode *hashnode)
+  {
+    return (const char *)hashnode->ident.str;
+  }
+};
+
+/* Specialization of best_match<> for finding the closest preprocessor
+   macro to a given identifier.  */
+
+class best_macro_match : public best_match<tree, cpp_hashnode *>
+{
+ public:
+  best_macro_match (tree goal, edit_distance_t best_distance_so_far,
+		    cpp_reader *reader);
+};
+
 #endif  /* GCC_SPELLCHECK_TREE_H  */
diff --git a/gcc/testsuite/g++.dg/spellcheck-identifiers.C b/gcc/testsuite/g++.dg/spellcheck-identifiers.C
new file mode 100644
index 0000000..e8168be
--- /dev/null
+++ b/gcc/testsuite/g++.dg/spellcheck-identifiers.C
@@ -0,0 +1,255 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+typedef struct GtkWidget { int dummy; } GtkWidget;
+
+extern void gtk_widget_show_all (GtkWidget *w);
+
+
+void
+test_1 (GtkWidget *w)
+{
+  gtk_widget_showall (w); // { dg-error "3: 'gtk_widget_showall' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   gtk_widget_showall (w);
+   ^~~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "3: suggested alternative: 'gtk_widget_show_all'" "" { target *-*-* } 12 }
+  /* { dg-begin-multiline-output "" }
+   gtk_widget_showall (w);
+   ^~~~~~~~~~~~~~~~~~
+   gtk_widget_show_all
+   { dg-end-multiline-output "" } */
+
+  /* Ensure we don't try to suggest "gtk_widget_showall" for subsequent
+     corrections.  */
+  gtk_widget_showall_ (w); // { dg-error "3: 'gtk_widget_showall_' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   gtk_widget_showall_ (w);
+   ^~~~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "3: suggested alternative: 'gtk_widget_show_all'" "" { target *-*-* } 26 }
+  /* { dg-begin-multiline-output "" }
+   gtk_widget_showall_ (w);
+   ^~~~~~~~~~~~~~~~~~~
+   gtk_widget_show_all
+   { dg-end-multiline-output "" } */
+
+  GtkWidgetShowAll (w); // { dg-error "3: 'GtkWidgetShowAll' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   GtkWidgetShowAll (w);
+   ^~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "3: suggested alternative: 'gtk_widget_show_all'" "" { target *-*-* } 38 }
+  /* { dg-begin-multiline-output "" }
+   GtkWidgetShowAll (w);
+   ^~~~~~~~~~~~~~~~
+   gtk_widget_show_all
+   { dg-end-multiline-output "" } */
+}
+
+int
+test_2 (int param)
+{
+  return parma * parma; // { dg-error "10: 'parma' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   return parma * parma;
+          ^~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "10: suggested alternative: 'param'" "" { target *-*-* } 54 }
+  /* { dg-begin-multiline-output "" }
+   return parma * parma;
+          ^~~~~
+          param
+   { dg-end-multiline-output "" } */
+}
+
+#define MACRO(X) ((X))
+
+int
+test_3 (int i)
+{
+  return MACRAME (i); // { dg-error "10: 'MACRAME' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   return MACRAME (i);
+          ^~~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "10: suggested alternative: 'MACRO'" "" { target *-*-* } 72 }
+  /* { dg-begin-multiline-output "" }
+   return MACRAME (i);
+          ^~~~~~~
+          MACRO
+   { dg-end-multiline-output "" } */
+}
+
+#define IDENTIFIER_POINTER(X) ((X))
+
+int
+test_4 (int node)
+{
+  return IDENTIFIER_PTR (node); // { dg-error "10: 'IDENTIFIER_PTR' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   return IDENTIFIER_PTR (node);
+          ^~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "10: suggested alternative: 'IDENTIFIER_POINTER'" "" { target *-*-* } 90 }
+  /* { dg-begin-multiline-output "" }
+   return IDENTIFIER_PTR (node);
+          ^~~~~~~~~~~~~~
+          IDENTIFIER_POINTER
+   { dg-end-multiline-output "" } */
+}
+
+
+int
+test_5 (void)
+{
+  return __LINE_; /* { dg-error "10: '__LINE_' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   return __LINE_;
+          ^~~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "10: suggested alternative: '__LINE__'" "" { target *-*-* } 107 }
+  /* { dg-begin-multiline-output "" }
+   return __LINE_;
+          ^~~~~~~
+          __LINE__
+   { dg-end-multiline-output "" } */
+}
+
+#define MAX_ITEMS 100
+int array[MAX_ITEM]; // { dg-error "11: 'MAX_ITEM' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+ int array[MAX_ITEM];
+           ^~~~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "11: suggested alternative: 'MAX_ITEMS'" "" { target *-*-* } 121 }
+  /* { dg-begin-multiline-output "" }
+ int array[MAX_ITEM];
+           ^~~~~~~~
+           MAX_ITEMS
+   { dg-end-multiline-output "" } */
+
+
+enum foo {
+  FOO_FIRST,
+  FOO_SECOND
+};
+
+int
+test_6 (enum foo f)
+{
+  switch (f)
+    {
+    case FOO_FURST: // { dg-error "10: 'FOO_FURST' was not declared in this scope" }
+      break;
+  /* { dg-begin-multiline-output "" }
+     case FOO_FURST:
+          ^~~~~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "10: suggested alternative: 'FOO_FIRST'" "" { target *-*-* } 144 }
+  /* { dg-begin-multiline-output "" }
+     case FOO_FURST:
+          ^~~~~~~~~
+          FOO_FIRST
+   { dg-end-multiline-output "" } */
+
+    case FOO_SECCOND: // { dg-error "10: 'FOO_SECCOND' was not declared in this scope" }
+      break;
+  /* { dg-begin-multiline-output "" }
+     case FOO_SECCOND:
+          ^~~~~~~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "10: suggested alternative: 'FOO_SECOND'" "" { target *-*-* } 157 }
+  /* { dg-begin-multiline-output "" }
+     case FOO_SECCOND:
+          ^~~~~~~~~~~
+          FOO_SECOND
+   { dg-end-multiline-output "" } */
+
+    default:
+      break;
+    }
+}
+
+/* Verify that we offer names of builtins as suggestions'  */
+
+void
+test_7 (int i, int j)
+{
+  int buffer[100];
+  snprint (buffer, 100, "%i of %i", i, j); // { dg-error "3: 'snprint' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   snprint (buffer, 100, "%i of %i", i, j);
+   ^~~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "3: suggested alternative: 'snprintf'" "" { target *-*-* } 181 }
+  /* { dg-begin-multiline-output "" }
+   snprint (buffer, 100, "%i of %i", i, j);
+   ^~~~~~~
+   snprintf
+   { dg-end-multiline-output "" } */
+}
+
+int
+test_8 ()
+{
+  int local = 42;
+  
+  return locale; // { dg-error "10: 'locale' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   return locale;
+          ^~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "10: suggested alternative: 'local'" "" { target *-*-* } 199 }
+  /* { dg-begin-multiline-output "" }
+   return locale;
+          ^~~~~~
+          local
+   { dg-end-multiline-output "" } */
+}
+
+class base
+{
+public:
+  int test_method_1 ();
+
+protected:
+  int m_foo;
+};
+
+class sub : public base
+{
+public:
+  int test_method_2 ();
+};
+
+int base::test_method_1 ()
+{
+  return m_food; // { dg-error "10: 'm_food' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   return m_food;
+          ^~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "10: suggested alternative: 'm_foo'" "" { target *-*-* } 229 }
+  /* { dg-begin-multiline-output "" }
+   return m_food;
+          ^~~~~~
+          m_foo
+   { dg-end-multiline-output "" } */
+}
+
+int sub::test_method_2 ()
+{
+  return m_food; // { dg-error "10: 'm_food' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   return m_food;
+          ^~~~~~
+   { dg-end-multiline-output "" } */
+  // { dg-message "10: suggested alternative: 'm_foo'" "" { target *-*-* } 244 }
+  /* { dg-begin-multiline-output "" }
+   return m_food;
+          ^~~~~~
+          m_foo
+   { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/g++.dg/spellcheck-typenames.C b/gcc/testsuite/g++.dg/spellcheck-typenames.C
new file mode 100644
index 0000000..9aa5b72
--- /dev/null
+++ b/gcc/testsuite/g++.dg/spellcheck-typenames.C
@@ -0,0 +1,84 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+void test_1 (signed char e);
+
+/* PR c/70339.  */
+void test_2 (singed char e); // { dg-error "21: variable or field 'test_2' declared void" }
+/* { dg-begin-multiline-output "" }
+ void test_2 (singed char e);
+                     ^~~~
+   { dg-end-multiline-output "" } */
+// { dg-message "14: 'singed' was not declared in this scope" "" { target *-*-* } 7 }
+/* { dg-begin-multiline-output "" }
+ void test_2 (singed char e);
+              ^~~~~~
+   { dg-end-multiline-output "" } */
+// { dg-message "14: suggested alternative: 'signed'" "" { target *-*-* } 7 }
+/* { dg-begin-multiline-output "" }
+ void test_2 (singed char e);
+              ^~~~~~
+              signed
+   { dg-end-multiline-output "" } */
+
+void test_3 (car e); // { dg-error "14: variable or field 'test_3' declared void" }
+/* { dg-begin-multiline-output "" }
+ void test_3 (car e);
+              ^~~
+   { dg-end-multiline-output "" } */
+// { dg-message "14: 'car' was not declared in this scope" "" { target *-*-* } 24 }
+// { dg-message "14: suggested alternative: 'char'" "" { target *-*-* } 24 }
+/* { dg-begin-multiline-output "" }
+ void test_3 (car e);
+              ^~~
+              char
+   { dg-end-multiline-output "" } */
+
+/* TODO: this one could be handled better.  */
+void test_4 (signed car e); // { dg-error "25: expected ',' or '...' before 'e'" }
+/* { dg-begin-multiline-output "" }
+ void test_4 (signed car e);
+                         ^
+   { dg-end-multiline-output "" } */
+
+/* Verify that we handle misspelled typedef names.  */
+
+typedef struct something {} something_t;
+
+some_thing_t test_5; // { dg-error "1: 'some_thing_t' does not name a type; did you mean 'something_t'?" }
+  /* { dg-begin-multiline-output "" }
+ some_thing_t test_5;
+ ^~~~~~~~~~~~
+ something_t
+   { dg-end-multiline-output "" } */
+
+/* TODO: we don't yet handle misspelled struct names.  */
+struct some_thing test_6; // { dg-error "aggregate 'some_thing test_6' has incomplete type and cannot be defined" }
+  /* { dg-begin-multiline-output "" }
+ struct some_thing test_6;
+                   ^~~~~~
+   { dg-end-multiline-output "" } */
+
+typedef long int64_t;
+int64 i; // { dg-error "1: 'int64' does not name a type; did you mean 'int64_t'?" }
+/* { dg-begin-multiline-output "" }
+ int64 i;
+ ^~~~~
+ int64_t
+   { dg-end-multiline-output "" } */
+
+/* Verify that gcc doesn't offer nonsensical suggestions.  */
+
+nonsensical_suggestion_t var; /* { dg-bogus "did you mean" } */
+/* { dg-error "'nonsensical_suggestion_t' does not name a type" "" { target { *-*-* } } 72 } */
+/* { dg-begin-multiline-output "" }
+ nonsensical_suggestion_t var;
+ ^~~~~~~~~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
+
+singed char ch; // { dg-error "1: 'singed' does not name a type; did you mean 'signed'?" }
+/* { dg-begin-multiline-output "" }
+ singed char ch;
+ ^~~~~~
+ signed
+   { dg-end-multiline-output "" } */