diff mbox

C++: fix fix-it hints for misspellings within explicit namespaces (v2)

Message ID 1484778575-17209-1-git-send-email-dmalcolm@redhat.com
State New
Headers show

Commit Message

David Malcolm Jan. 18, 2017, 10:29 p.m. UTC
On Sat, 2017-01-14 at 09:50 -0500, Jason Merrill wrote:
> On Fri, Jan 13, 2017 at 5:05 PM, David Malcolm <dmalcolm@redhat.com>
> wrote:
> > On Wed, 2017-01-04 at 14:58 -0500, Jason Merrill wrote:
> > > On Tue, Jan 3, 2017 at 8:28 PM, David Malcolm <
> > > dmalcolm@redhat.com>
> > > wrote:
> > > > PR c++/77829 and PR c++/78656 identify an issue within the C++
> > > > frontend
> > > > where it issues nonsensical fix-it hints for misspelled name
> > > > lookups
> > > > within an explicitly given namespace: it finds the closest name
> > > > within
> > > > all namespaces, and uses the location of the namespace for the
> > > > replacement,
> > > > rather than the name.
> > > >
> > > > For example, for this error:
> > > >
> > > >   #include <memory>
> > > >   void* allocate(std::size_t n)
> > > >   {
> > > >     return std::alocator<char>().allocate(n);
> > > >   }
> > > >
> > > > we currently emit an erroneous fix-it hint that would generate
> > > > this
> > > > nonsensical patch:
> > > >
> > > >    {
> > > >   -  return std::alocator<char>().allocate(n);
> > > >   +  return allocate::alocator<char>().allocate(n);
> > > >    }
> > > >
> > > > whereas we ought to emit a fix-it hint that would generate this
> > > > patch:
> > > >
> > > >    {
> > > >   -  return std::alocator<char>().allocate(n);
> > > >   +  return std::allocator<char>().allocate(n);
> > > >    }
> > > >
> > > > This patch fixes the suggestions, in two parts:
> > > >
> > > > The incorrect name in the suggestion is fixed by introducing a
> > > > new function "suggest_alternative_in_explicit_scope"
> > > > for use by qualified_name_lookup_error when handling failures
> > > > in explicitly-given namespaces, looking for hint candidates
> > > > within
> > > > just that namespace.  The function suggest_alternatives_for
> > > > gains a
> > > > "suggest_misspellings" bool param, so that we can disable fuzzy
> > > > name
> > > > lookup for the case where we've ruled out hint candidates in
> > > > the
> > > > explicitly-given namespace.
> > > >
> > > > This lets us suggest "allocator" (found in "std") rather
> > > > "allocate"
> > > > (found in the global ns).
> > >
> > > This looks good.
> > >
> > > > The patch fixes the location for the replacement by updating
> > > > local "unqualified_id" in cp_parser_id_expression from tree to
> > > > cp_expr to avoid implicitly dropping location information, and
> > > > passing this location to a new param of finish_id_expression.
> > > > There are multiple users of finish_id_expression, and it wasn't
> > > > possible to provide location information for the id for all of
> > > > them
> > > > so the new location information is assumed to be optional
> > > > there.
> > >
> > > > This fixes the underlined location, and ensures that the fix-it
> > > > hint
> > > > replaces "alocator", rather than "std".
> > >
> > > I'm dubious about this approach, as I think this will fix some
> > > cases
> > > and not others.  The general problem is that we aren't sure what
> > > to
> > > do
> > > with the location of a qualified-id: sometimes we use the
> > > location of
> > > the unqualified-id, sometimes we use the beginning of the first
> > > nested-name-specifier.  I think the right answer is probably to
> > > use a
> > > rich location with the caret on the unqualified-id.  Then the fix
> > > -it
> > > hint can use the caret location for the fix-it.  Does that make
> > > sense?
> >
> > Sorry, I'm not sure I follow you.
> >
> > By "rich location" do you mean providing multiple ranges (e.g. one
> > for
> > the nested-name-specifier, another for the unqualified-id)?
> >
> > e.g.
> >
> >   ::foo::bar
> >     ~~~  ^~~
> >      |    |
> >      |    `-(primary location and range)
> >      `-(secondary range)
> >
> > or:
> >
> >   ::foo::bar
> >   ~~~~~  ^~~
> >      |    |
> >      |    `-(primary location and
> > range)
> >      `-(secondary range)
> >
> > (if that ASCII art makes sense)
> >
> > Note that such a rich location (with more than one range) can only
> > exist during the emission of a diagnostic; it's not something we
> > can
> > use as the location of a tree node.
> >
> > Or do you mean that we should supply a range for the unqualified
> > -id,
> > with the caret at the start of the unqualified-id, e.g.:
> >
> >    ::foo::bar
> >           ^~~
> >
> > (a tree node code can have such a location).
> >
> > It seems to me that we ought to have
> >
> >    ::foo::bar
> >    ^~~~~~~~~~
> >
> > as the location of such a tree node, and only use the range of just
> > "bar" as the location when handling name lookup errors (such as
> > when
> > emitting fix-it hints).
>
> Yes, sorry, I meant range.  I was thinking
>
> ::foo::bar
> ~~~~~~~^~~
>
> And then the fix-it would know to replace from the caret to the end
> of
> the range?
>

Thanks.

Here's a version of the patch which simply tweaks
cp_parser_primary_expression's call to finish_id_expression so that
it passes the location of the id_expression, rather than that of
id_expr_token.

The id_expression in question came from cp_parser_id_expression,
whereas the id_expr_token is the first token within the id-expression.

The location passed here to finish_id_expression only affects:
the location used for name-lookup errors, and for the resulting
decl cp_expr.  Given that the following code immediately does this:
	decl.set_location (id_expr_token->location);
to override the latter, nothing is changing apart from the reported
location (and it uses it for the replacement range for the fix-it
hint).

Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu.

Is this more minimal version of the patch OK for trunk as-is, or do you
want me to do the extended ranges idea discussed above?

gcc/cp/ChangeLog:
	PR c++/77829
	PR c++/78656
	* cp-tree.h (suggest_alternatives_for): Add bool param.
	(suggest_alternative_in_explicit_scope): New decl.
	* error.c (qualified_name_lookup_error): When SCOPE is a namespace
	that isn't the global one, call new function
	suggest_alternative_in_explicit_scope, only calling
	suggest_alternatives_for if it fails, and disabling near match
	searches fort that case.  When SCOPE is the global namespace,
	pass true for new param to suggest_alternatives_for to allow for
	fuzzy name lookups.
	* lex.c (unqualified_name_lookup_error): Pass true for new param
	to suggest_alternatives_for.
	* name-lookup.c (consider_binding_level): Add forward decl.
	(suggest_alternatives_for): Add "suggest_misspellings" param,
	using it to conditionalize the fuzzy name-lookup code.
	(suggest_alternative_in_explicit_scope): New function.
	* parser.c (cp_parser_primary_expression): When calling
	finish_id_expression, pass location of id_expression rather
	than that of id_expr_token.
	(cp_parser_id_expression): Convert local "unqualified_id" from
	tree to cp_expr to avoid implicitly dropping location information.

gcc/testsuite/ChangeLog:
	PR c++/77829
	PR c++/78656
	* g++.dg/spellcheck-pr77829.C: New test case.
	* g++.dg/spellcheck-pr78656.C: New test case.
---
 gcc/cp/cp-tree.h                          |   3 +-
 gcc/cp/error.c                            |   5 +-
 gcc/cp/lex.c                              |   2 +-
 gcc/cp/name-lookup.c                      |  55 ++++++++--
 gcc/cp/parser.c                           |   4 +-
 gcc/testsuite/g++.dg/spellcheck-pr77829.C | 167 ++++++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/spellcheck-pr78656.C |  39 +++++++
 7 files changed, 261 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/spellcheck-pr77829.C
 create mode 100644 gcc/testsuite/g++.dg/spellcheck-pr78656.C

Comments

Jason Merrill Jan. 19, 2017, 6:15 p.m. UTC | #1
On Wed, Jan 18, 2017 at 5:29 PM, David Malcolm <dmalcolm@redhat.com> wrote:
> Here's a version of the patch which simply tweaks
> cp_parser_primary_expression's call to finish_id_expression so that
> it passes the location of the id_expression, rather than that of
> id_expr_token.
>
> The id_expression in question came from cp_parser_id_expression,
> whereas the id_expr_token is the first token within the id-expression.
>
> The location passed here to finish_id_expression only affects:
> the location used for name-lookup errors, and for the resulting
> decl cp_expr.  Given that the following code immediately does this:
>         decl.set_location (id_expr_token->location);

What happens if we use id_expression.get_location() here, too?

OK.

Jason
David Malcolm Jan. 19, 2017, 8:56 p.m. UTC | #2
On Thu, 2017-01-19 at 13:15 -0500, Jason Merrill wrote:
> On Wed, Jan 18, 2017 at 5:29 PM, David Malcolm <dmalcolm@redhat.com>
> wrote:
> > Here's a version of the patch which simply tweaks
> > cp_parser_primary_expression's call to finish_id_expression so that
> > it passes the location of the id_expression, rather than that of
> > id_expr_token.
> > 
> > The id_expression in question came from cp_parser_id_expression,
> > whereas the id_expr_token is the first token within the id
> > -expression.
> > 
> > The location passed here to finish_id_expression only affects:
> > the location used for name-lookup errors, and for the resulting
> > decl cp_expr.  Given that the following code immediately does this:
> >         decl.set_location (id_expr_token->location);
> 
> What happens if we use id_expression.get_location() here, too?
> 
> OK.

With that other change it bootstraps but introduces some regressions:

 PASS -> FAIL : g++.dg/cpp0x/pr51420.C  -std=c++11  (test for errors, line 6)
 PASS -> FAIL : g++.dg/cpp0x/pr51420.C  -std=c++11 (test for excess errors)
 PASS -> FAIL : g++.dg/cpp0x/pr51420.C  -std=c++14  (test for errors, line 6)
 PASS -> FAIL : g++.dg/cpp0x/pr51420.C  -std=c++14 (test for excess errors)
 PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++11  (test for errors, line 11)
 PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++11  (test for errors, line 14)
 PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++11  (test for errors, line 5)
 PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++11  (test for errors, line 8)
 PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++11 (test for excess errors)
 PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++14  (test for errors, line 11)
 PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++14  (test for errors, line 14)
 PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++14  (test for errors, line 5)
 PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++14  (test for errors, line 8)
 PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++14 (test for excess errors)

It would change:

g++.dg/cpp0x/pr51420.C: In function ‘void foo()’:
g++.dg/cpp0x/pr51420.C:6:13: error: ‘operator""_F’ was not declared in this scope
   float x = operator"" _F();  //  { dg-error  "13:'operator\"\"_F' was not declared in this scope" }
             ^~~~~~~~
g++.dg/cpp0x/pr51420.C:6:13: note: suggested alternative: ‘operator new’
   float x = operator"" _F();  //  { dg-error  "13:'operator\"\"_F' was not declared in this scope" }
             ^~~~~~~~
             operator new

to:

g++.dg/cpp0x/pr51420.C: In function ‘void foo()’:
g++.dg/cpp0x/pr51420.C:6:27: error: ‘operator""_F’ was not declared in this scope
   float x = operator"" _F();  //  { dg-error  "13:'operator\"\"_F' was not declared in this scope" }
                           ^

and would change:

g++.dg/cpp0x/udlit-declare-neg.C:5:9: error: ‘operator""_Bar’ was not declared in this scope
 int i = operator"" _Bar('x');  // { dg-error "9:'operator\"\"_Bar' was not declared in this scope" }
         ^~~~~~~~
g++.dg/cpp0x/udlit-declare-neg.C:5:9: note: suggested alternative: ‘operator new’
 int i = operator"" _Bar('x');  // { dg-error "9:'operator\"\"_Bar' was not declared in this scope" }
         ^~~~~~~~
         operator new

to:

g++.dg/cpp0x/udlit-declare-neg.C:5:28: error: ‘operator""_Bar’ was not declared in this scope
 int i = operator"" _Bar('x');  // { dg-error "9:'operator\"\"_Bar' was not declared in this scope" }
                            ^

(DejaGnu picked up on this via the changing column numbers, but it
didn't detect the missing "suggested alternative").


With the patch I posted as-is, we get:

g++.dg/cpp0x/pr51420.C:6:13: error: ‘operator""_F’ was not declared in this scope
   float x = operator"" _F();  //  { dg-error  "13:'operator\"\"_F' was not declared in this scope" }
             ^~~~~~~~

and:

g++.dg/cpp0x/udlit-declare-neg.C:5:9: error: ‘operator""_Bar’ was not declared in this scope
 int i = operator"" _Bar('x');  // { dg-error "9:'operator\"\"_Bar' was not declared in this scope" }
         ^~~~~~~~

i.e. the same locations as the status quo, but dropping the suggested
"operator new" hint.


Is the patch still OK as-is?
Dave
Jason Merrill Jan. 20, 2017, 1:47 p.m. UTC | #3
On Thu, Jan 19, 2017 at 3:56 PM, David Malcolm <dmalcolm@redhat.com> wrote:
> On Thu, 2017-01-19 at 13:15 -0500, Jason Merrill wrote:
>> On Wed, Jan 18, 2017 at 5:29 PM, David Malcolm <dmalcolm@redhat.com>
>> wrote:
>> > Here's a version of the patch which simply tweaks
>> > cp_parser_primary_expression's call to finish_id_expression so that
>> > it passes the location of the id_expression, rather than that of
>> > id_expr_token.
>> >
>> > The id_expression in question came from cp_parser_id_expression,
>> > whereas the id_expr_token is the first token within the id
>> > -expression.
>> >
>> > The location passed here to finish_id_expression only affects:
>> > the location used for name-lookup errors, and for the resulting
>> > decl cp_expr.  Given that the following code immediately does this:
>> >         decl.set_location (id_expr_token->location);
>>
>> What happens if we use id_expression.get_location() here, too?
>>
>> OK.
>
> With that other change it bootstraps but introduces some regressions:
>
>  PASS -> FAIL : g++.dg/cpp0x/pr51420.C  -std=c++11  (test for errors, line 6)
>  PASS -> FAIL : g++.dg/cpp0x/pr51420.C  -std=c++11 (test for excess errors)
>  PASS -> FAIL : g++.dg/cpp0x/pr51420.C  -std=c++14  (test for errors, line 6)
>  PASS -> FAIL : g++.dg/cpp0x/pr51420.C  -std=c++14 (test for excess errors)
>  PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++11  (test for errors, line 11)
>  PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++11  (test for errors, line 14)
>  PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++11  (test for errors, line 5)
>  PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++11  (test for errors, line 8)
>  PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++11 (test for excess errors)
>  PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++14  (test for errors, line 11)
>  PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++14  (test for errors, line 14)
>  PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++14  (test for errors, line 5)
>  PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++14  (test for errors, line 8)
>  PASS -> FAIL : g++.dg/cpp0x/udlit-declare-neg.C  -std=c++14 (test for excess errors)
>
> It would change:
>
> g++.dg/cpp0x/pr51420.C: In function ‘void foo()’:
> g++.dg/cpp0x/pr51420.C:6:13: error: ‘operator""_F’ was not declared in this scope
>    float x = operator"" _F();  //  { dg-error  "13:'operator\"\"_F' was not declared in this scope" }
>              ^~~~~~~~
> g++.dg/cpp0x/pr51420.C:6:13: note: suggested alternative: ‘operator new’
>    float x = operator"" _F();  //  { dg-error  "13:'operator\"\"_F' was not declared in this scope" }
>              ^~~~~~~~
>              operator new
>
> to:
>
> g++.dg/cpp0x/pr51420.C: In function ‘void foo()’:
> g++.dg/cpp0x/pr51420.C:6:27: error: ‘operator""_F’ was not declared in this scope
>    float x = operator"" _F();  //  { dg-error  "13:'operator\"\"_F' was not declared in this scope" }
>                            ^
>
> and would change:
>
> g++.dg/cpp0x/udlit-declare-neg.C:5:9: error: ‘operator""_Bar’ was not declared in this scope
>  int i = operator"" _Bar('x');  // { dg-error "9:'operator\"\"_Bar' was not declared in this scope" }
>          ^~~~~~~~
> g++.dg/cpp0x/udlit-declare-neg.C:5:9: note: suggested alternative: ‘operator new’
>  int i = operator"" _Bar('x');  // { dg-error "9:'operator\"\"_Bar' was not declared in this scope" }
>          ^~~~~~~~
>          operator new
>
> to:
>
> g++.dg/cpp0x/udlit-declare-neg.C:5:28: error: ‘operator""_Bar’ was not declared in this scope
>  int i = operator"" _Bar('x');  // { dg-error "9:'operator\"\"_Bar' was not declared in this scope" }
>                             ^
>
> (DejaGnu picked up on this via the changing column numbers, but it
> didn't detect the missing "suggested alternative").
>
>
> With the patch I posted as-is, we get:
>
> g++.dg/cpp0x/pr51420.C:6:13: error: ‘operator""_F’ was not declared in this scope
>    float x = operator"" _F();  //  { dg-error  "13:'operator\"\"_F' was not declared in this scope" }
>              ^~~~~~~~
>
> and:
>
> g++.dg/cpp0x/udlit-declare-neg.C:5:9: error: ‘operator""_Bar’ was not declared in this scope
>  int i = operator"" _Bar('x');  // { dg-error "9:'operator\"\"_Bar' was not declared in this scope" }
>          ^~~~~~~~
>
> i.e. the same locations as the status quo, but dropping the suggested
> "operator new" hint.
>
>
> Is the patch still OK as-is?

Yes.

Jason
diff mbox

Patch

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 24de346..edbe1ca 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6928,7 +6928,8 @@  extern tree cp_fully_fold			(tree);
 extern void clear_fold_cache			(void);
 
 /* in name-lookup.c */
-extern void suggest_alternatives_for            (location_t, tree);
+extern void suggest_alternatives_for            (location_t, tree, bool);
+extern bool suggest_alternative_in_explicit_scope (location_t, tree, tree);
 extern tree strip_using_decl                    (tree);
 
 /* in constraint.cc */
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index 72044a9..4f4c11d 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -3777,11 +3777,12 @@  qualified_name_lookup_error (tree scope, tree name,
   else if (scope != global_namespace)
     {
       error_at (location, "%qD is not a member of %qD", name, scope);
-      suggest_alternatives_for (location, name);
+      if (!suggest_alternative_in_explicit_scope (location, name, scope))
+	suggest_alternatives_for (location, name, false);
     }
   else
     {
       error_at (location, "%<::%D%> has not been declared", name);
-      suggest_alternatives_for (location, name);
+      suggest_alternatives_for (location, name, true);
     }
 }
diff --git a/gcc/cp/lex.c b/gcc/cp/lex.c
index 797dd96..60a70e9 100644
--- a/gcc/cp/lex.c
+++ b/gcc/cp/lex.c
@@ -441,7 +441,7 @@  unqualified_name_lookup_error (tree name, location_t loc)
       if (!objc_diagnose_private_ivar (name))
 	{
 	  error_at (loc, "%qD was not declared in this scope", name);
-	  suggest_alternatives_for (loc, name);
+	  suggest_alternatives_for (loc, name, true);
 	}
       /* Prevent repeated error messages by creating a VAR_DECL with
 	 this NAME in the innermost block scope.  */
diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c
index c422d2f..af00005 100644
--- a/gcc/cp/name-lookup.c
+++ b/gcc/cp/name-lookup.c
@@ -48,6 +48,10 @@  static bool lookup_using_namespace (tree, struct scope_binding *, tree,
 				    tree, int);
 static bool qualified_lookup_using_namespace (tree, tree,
 					      struct scope_binding *, int);
+static void consider_binding_level (tree name, best_match <tree, tree> &bm,
+				    cp_binding_level *lvl,
+				    bool look_within_fields,
+				    enum lookup_name_fuzzy_kind kind);
 static tree lookup_type_current_level (tree);
 static tree push_using_directive (tree);
 static tree lookup_extern_c_fun_in_all_ns (tree);
@@ -4424,10 +4428,13 @@  remove_hidden_names (tree fns)
 
 /* Suggest alternatives for NAME, an IDENTIFIER_NODE for which name
    lookup failed.  Search through all available namespaces and print out
-   possible candidates.  */
+   possible candidates.  If no exact matches are found, and
+   SUGGEST_MISSPELLINGS is true, then also look for near-matches and
+   suggest the best near-match, if there is one.  */
 
 void
-suggest_alternatives_for (location_t location, tree name)
+suggest_alternatives_for (location_t location, tree name,
+			  bool suggest_misspellings)
 {
   vec<tree> candidates = vNULL;
   vec<tree> namespaces_to_search = vNULL;
@@ -4474,13 +4481,16 @@  suggest_alternatives_for (location_t location, tree name)
      or do nothing.  */
   if (candidates.is_empty ())
     {
-      const char *fuzzy_name = lookup_name_fuzzy (name, FUZZY_LOOKUP_NAME);
-      if (fuzzy_name)
+      if (suggest_misspellings)
 	{
-	  gcc_rich_location richloc (location);
-	  richloc.add_fixit_replace (fuzzy_name);
-	  inform_at_rich_loc (&richloc, "suggested alternative: %qs",
-			      fuzzy_name);
+	  const char *fuzzy_name = lookup_name_fuzzy (name, FUZZY_LOOKUP_NAME);
+	  if (fuzzy_name)
+	    {
+	      gcc_rich_location richloc (location);
+	      richloc.add_fixit_replace (fuzzy_name);
+	      inform_at_rich_loc (&richloc, "suggested alternative: %qs",
+				  fuzzy_name);
+	    }
 	}
       return;
     }
@@ -4495,6 +4505,35 @@  suggest_alternatives_for (location_t location, tree name)
   candidates.release ();
 }
 
+/* Look for alternatives for NAME, an IDENTIFIER_NODE for which name
+   lookup failed within the explicitly provided SCOPE.  Suggest the
+   the best meaningful candidates (if any) as a fix-it hint.
+   Return true iff a suggestion was provided.  */
+
+bool
+suggest_alternative_in_explicit_scope (location_t location, tree name,
+				       tree scope)
+{
+  cp_binding_level *level = NAMESPACE_LEVEL (scope);
+
+  best_match <tree, tree> bm (name);
+  consider_binding_level (name, bm, level, false, FUZZY_LOOKUP_NAME);
+
+  /* See if we have a good suggesion for the user.  */
+  tree best_id = bm.get_best_meaningful_candidate ();
+  if (best_id)
+    {
+      const char *fuzzy_name = IDENTIFIER_POINTER (best_id);
+      gcc_rich_location richloc (location);
+      richloc.add_fixit_replace (fuzzy_name);
+      inform_at_rich_loc (&richloc, "suggested alternative: %qs",
+			  fuzzy_name);
+      return true;
+    }
+
+  return false;
+}
+
 /* Unscoped lookup of a global: iterate over current namespaces,
    considering using-directives.  */
 
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 7b3ee30..8a7ae32 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -5332,7 +5332,7 @@  cp_parser_primary_expression (cp_parser *parser,
 		 template_p, done, address_p,
 		 template_arg_p,
 		 &error_msg,
-                 id_expr_token->location));
+		 id_expression.get_location ()));
 	if (error_msg)
 	  cp_parser_error (parser, error_msg);
 	decl.set_location (id_expr_token->location);
@@ -5425,7 +5425,7 @@  cp_parser_id_expression (cp_parser *parser,
       tree saved_scope;
       tree saved_object_scope;
       tree saved_qualifying_scope;
-      tree unqualified_id;
+      cp_expr unqualified_id;
       bool is_template;
 
       /* See if the next token is the `template' keyword.  */
diff --git a/gcc/testsuite/g++.dg/spellcheck-pr77829.C b/gcc/testsuite/g++.dg/spellcheck-pr77829.C
new file mode 100644
index 0000000..2f75779
--- /dev/null
+++ b/gcc/testsuite/g++.dg/spellcheck-pr77829.C
@@ -0,0 +1,167 @@ 
+// { dg-options "-fdiagnostics-show-caret" }
+
+/* Various tests of name lookup within a namespace, both within an explicitly
+   given namespace, or implicitly.  */
+
+namespace detail {
+  /* Various things to look for.  */
+
+  typedef int some_typedef;
+
+  int _foo(int i) { return i; }
+
+  template <typename T>
+  T something_else (T i) { return i; }
+}
+
+/* Tests of lookup of a typedef.  */
+
+void fn_1_explicit ()
+{
+  detail::some_type i; // { dg-error ".some_type. is not a member of .detail." }
+  // { dg-message "suggested alternative: .some_typedef." "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+   detail::some_type i;
+           ^~~~~~~~~
+     { dg-end-multiline-output "" } */
+  /* { dg-begin-multiline-output "" }
+   detail::some_type i;
+           ^~~~~~~~~
+           some_typedef
+     { dg-end-multiline-output "" } */
+}
+
+namespace detail {
+
+void fn_1_implicit ()
+{
+  some_type i; // { dg-error ".some_type. was not declared in this scope" }
+  // { dg-message "suggested alternative: .some_typedef." "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+   some_type i;
+   ^~~~~~~~~
+     { dg-end-multiline-output "" } */
+  /* { dg-begin-multiline-output "" }
+   some_type i;
+   ^~~~~~~~~
+   some_typedef
+     { dg-end-multiline-output "" } */
+}
+
+} // namespace detail
+
+
+/* Tests of lookup of a function.  */
+
+void fn_2_explicit (int i) {
+  detail::foo(i); // { dg-error ".foo. is not a member of .detail." }
+  // { dg-message "suggested alternative: ._foo." "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+   detail::foo(i);
+           ^~~
+     { dg-end-multiline-output "" } */
+  /* { dg-begin-multiline-output "" }
+   detail::foo(i);
+           ^~~
+           _foo
+     { dg-end-multiline-output "" } */
+}
+
+namespace detail {
+
+void fn_2_implicit (int i) {
+  foo(i); // { dg-error ".foo. was not declared in this scope" }
+  // { dg-message "suggested alternative: ._foo." "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+   foo(i);
+   ^~~
+     { dg-end-multiline-output "" } */
+  /* { dg-begin-multiline-output "" }
+   foo(i);
+   ^~~
+   _foo
+     { dg-end-multiline-output "" } */
+}
+
+} // namespace detail
+
+
+/* Examples using a template.  */
+
+void fn_3_explicit (int i) {
+  detail::something_els(i); // { dg-error ".something_els. is not a member of .detail." }
+  // { dg-message "suggested alternative: .something_else." "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+   detail::something_els(i);
+           ^~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+
+  /* { dg-begin-multiline-output "" }
+   detail::something_els(i);
+           ^~~~~~~~~~~~~
+           something_else
+     { dg-end-multiline-output "" } */
+}
+
+namespace detail {
+
+void fn_3_implicit (int i) {
+  something_els(i); // { dg-error ".something_els. was not declared in this scope" }
+  // { dg-message "suggested alternative: .something_else." "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+   something_els(i);
+   ^~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+
+  /* { dg-begin-multiline-output "" }
+   something_els(i);
+   ^~~~~~~~~~~~~
+   something_else
+     { dg-end-multiline-output "" } */
+}
+
+} // namespace detail
+
+
+/* Tests of lookup for which no hint is available.  */
+
+void fn_4_explicit (int i) {
+  detail::not_recognized(i); // { dg-error ".not_recognized. is not a member of .detail." }
+  /* { dg-begin-multiline-output "" }
+   detail::not_recognized(i);
+           ^~~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+}
+
+namespace detail {
+
+void fn_4_implicit (int i)
+{
+  not_recognized(i); // { dg-error ".not_recognized. was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   not_recognized(i);
+   ^~~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+}
+
+} // namespace detail
+
+
+/* Test for failed lookup explicitly within global namespace.  */
+
+typedef int another_typedef;
+
+void fn_5 ()
+{
+  ::another_type i; // { dg-error ".::another_type. has not been declared" }
+  // { dg-message "suggested alternative: .another_typedef." "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+   ::another_type i;
+     ^~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+  /* { dg-begin-multiline-output "" }
+   ::another_type i;
+     ^~~~~~~~~~~~
+     another_typedef
+     { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/g++.dg/spellcheck-pr78656.C b/gcc/testsuite/g++.dg/spellcheck-pr78656.C
new file mode 100644
index 0000000..ded4bb6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/spellcheck-pr78656.C
@@ -0,0 +1,39 @@ 
+// { dg-options "-fdiagnostics-show-caret" }
+
+#include <memory>
+
+void* allocate(std::size_t n)
+{
+  return std::allocate<char>().allocate(n); // { dg-error ".allocate. is not a member of .std." }
+  // { dg-message "suggested alternative: .allocator." "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+   return std::allocate<char>().allocate(n);
+               ^~~~~~~~
+     { dg-end-multiline-output "" } */ 
+  /* { dg-begin-multiline-output "" }
+   return std::allocate<char>().allocate(n);
+               ^~~~~~~~
+               allocator
+     { dg-end-multiline-output "" } */
+
+  // Various errors follow that we don't care about; suppress them:
+  // { dg-excess-errors "7: " }
+}
+
+void* test_2(std::size_t n)
+{
+  return std::alocator<char>().allocate(n); // { dg-error ".alocator. is not a member of .std." }
+  // { dg-message "suggested alternative: .allocator." "" { target *-*-* } .-1 }
+  /* { dg-begin-multiline-output "" }
+   return std::alocator<char>().allocate(n);
+               ^~~~~~~~
+     { dg-end-multiline-output "" } */ 
+  /* { dg-begin-multiline-output "" }
+   return std::alocator<char>().allocate(n);
+               ^~~~~~~~
+               allocator
+     { dg-end-multiline-output "" } */
+
+  // Various errors follow that we don't care about; suppress them:
+  // { dg-excess-errors "25: " }
+}