diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index eb4e1fc..9a23ba2 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -2280,33 +2280,7 @@ lookup_field_fuzzy (tree type, tree component)
   lookup_field_fuzzy_find_candidates (type, component,
 				      &candidates);
 
-  /* Now determine which is closest.  */
-  int i;
-  tree identifier;
-  tree best_identifier = NULL;
-  edit_distance_t best_distance = MAX_EDIT_DISTANCE;
-  FOR_EACH_VEC_ELT (candidates, i, identifier)
-    {
-      gcc_assert (TREE_CODE (identifier) == IDENTIFIER_NODE);
-      edit_distance_t dist = levenshtein_distance (component, identifier);
-      if (dist < best_distance)
-	{
-	  best_distance = dist;
-	  best_identifier = identifier;
-	}
-    }
-
-  /* If more than half of the letters were misspelled, the suggestion is
-     likely to be meaningless.  */
-  if (best_identifier)
-    {
-      unsigned int cutoff = MAX (IDENTIFIER_LENGTH (component),
-				 IDENTIFIER_LENGTH (best_identifier)) / 2;
-      if (best_distance > cutoff)
-	return NULL;
-    }
-
-  return best_identifier;
+  return find_closest_identifier (component, &candidates);
 }
 
 /* Make an expression to refer to the COMPONENT field of structure or
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 828f268..9dc0e44 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6116,6 +6116,7 @@ extern int class_method_index_for_fn		(tree, tree);
 extern tree lookup_fnfields			(tree, tree, int);
 extern tree lookup_member			(tree, tree, int, bool,
 						 tsubst_flags_t);
+extern tree lookup_member_fuzzy		(tree, tree, bool);
 extern int look_for_overrides			(tree, tree);
 extern void get_pure_virtuals			(tree);
 extern void maybe_suppress_debug_info		(tree);
diff --git a/gcc/cp/search.c b/gcc/cp/search.c
index 94502f6..fe794a7 100644
--- a/gcc/cp/search.c
+++ b/gcc/cp/search.c
@@ -27,6 +27,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "cp-tree.h"
 #include "intl.h"
 #include "toplev.h"
+#include "spellcheck.h"
 
 static int is_subobject_of_p (tree, tree);
 static tree dfs_lookup_base (tree, void *);
@@ -1352,6 +1353,143 @@ lookup_member (tree xbasetype, tree name, int protect, bool want_type,
   return rval;
 }
 
+/* Helper class for lookup_member_fuzzy.  */
+
+class lookup_field_fuzzy_info
+{
+ public:
+  lookup_field_fuzzy_info (bool want_type_p) :
+    m_want_type_p (want_type_p), m_candidates () {}
+
+  void fuzzy_lookup_fnfields (tree type);
+  void fuzzy_lookup_field (tree type);
+
+  /* If true, we are looking for types, not data members.  */
+  bool m_want_type_p;
+  /* The result: a vec of identifiers.  */
+  auto_vec<tree> m_candidates;
+};
+
+/* Locate all methods within TYPE, append them to m_candidates.  */
+
+void
+lookup_field_fuzzy_info::fuzzy_lookup_fnfields (tree type)
+{
+  vec<tree, va_gc> *method_vec;
+  tree fn;
+  size_t i;
+
+  if (!CLASS_TYPE_P (type))
+    return;
+
+  method_vec = CLASSTYPE_METHOD_VEC (type);
+  if (!method_vec)
+    return;
+
+  for (; vec_safe_iterate (method_vec, i, &fn); ++i)
+    m_candidates.safe_push (DECL_NAME (OVL_CURRENT (fn)));
+}
+
+/* Locate all fields within TYPE, append them to m_candidates.  */
+
+void
+lookup_field_fuzzy_info::fuzzy_lookup_field (tree type)
+{
+  if (TREE_CODE (type) == TEMPLATE_TYPE_PARM
+      || TREE_CODE (type) == BOUND_TEMPLATE_TEMPLATE_PARM
+      || TREE_CODE (type) == TYPENAME_TYPE)
+    /* The TYPE_FIELDS of a TEMPLATE_TYPE_PARM and
+       BOUND_TEMPLATE_TEMPLATE_PARM are not fields at all;
+       instead TYPE_FIELDS is the TEMPLATE_PARM_INDEX.
+       The TYPE_FIELDS of TYPENAME_TYPE is its TYPENAME_TYPE_FULLNAME.  */
+    return;
+
+  for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
+    {
+      if (!m_want_type_p || DECL_DECLARES_TYPE_P (field))
+	if (DECL_NAME (field))
+	  m_candidates.safe_push (DECL_NAME (field));
+    }
+}
+
+
+/* Helper function for lookup_member_fuzzy, called via dfs_walk_all
+   DATA is really a lookup_field_fuzzy_info.  Look for a field with
+   the name indicated there in BINFO.  Gathers pertinent identifiers into
+   m_candidates.  */
+
+static tree
+lookup_field_fuzzy_r (tree binfo, void *data)
+{
+  lookup_field_fuzzy_info *lffi = (lookup_field_fuzzy_info *) data;
+  tree type = BINFO_TYPE (binfo);
+
+  /* First, look for functions.  */
+  if (!lffi->m_want_type_p)
+    lffi->fuzzy_lookup_fnfields (type);
+
+  /* Look for data member and types.  */
+  lffi->fuzzy_lookup_field (type);
+
+  return NULL_TREE;
+}
+
+/* Like lookup_member, but try to find the closest match for NAME,
+   rather than an exact match, and return an identifier (or NULL_TREE).
+   Do not complain.  */
+
+tree
+lookup_member_fuzzy (tree xbasetype, tree name, bool want_type_p)
+{
+  tree type = NULL_TREE, basetype_path = NULL_TREE;
+  struct lookup_field_fuzzy_info lffi (want_type_p);
+
+  /* rval_binfo is the binfo associated with the found member, note,
+     this can be set with useful information, even when rval is not
+     set, because it must deal with ALL members, not just non-function
+     members.  It is used for ambiguity checking and the hidden
+     checks.  Whereas rval is only set if a proper (not hidden)
+     non-function member is found.  */
+
+  if (name == error_mark_node
+      || xbasetype == NULL_TREE
+      || xbasetype == error_mark_node)
+    return NULL_TREE;
+
+  gcc_assert (identifier_p (name));
+
+  if (TREE_CODE (xbasetype) == TREE_BINFO)
+    {
+      type = BINFO_TYPE (xbasetype);
+      basetype_path = xbasetype;
+    }
+  else
+    {
+      if (!RECORD_OR_UNION_CODE_P (TREE_CODE (xbasetype)))
+	return NULL_TREE;
+      type = xbasetype;
+      xbasetype = NULL_TREE;
+    }
+
+  type = complete_type (type);
+
+  /* Make sure we're looking for a member of the current instantiation in the
+     right partial specialization.  */
+  if (flag_concepts && dependent_type_p (type))
+    type = currently_open_class (type);
+
+  if (!basetype_path)
+    basetype_path = TYPE_BINFO (type);
+
+  if (!basetype_path)
+    return NULL_TREE;
+
+  /* Populate lffi.m_candidates.  */
+  dfs_walk_all (basetype_path, &lookup_field_fuzzy_r, NULL, &lffi);
+
+  return find_closest_identifier (name, &lffi.m_candidates);
+}
+
 /* Like lookup_member, except that if we find a function member we
    return NULL_TREE.  */
 
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 90c95fb..c1e8546 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -2795,9 +2795,18 @@ finish_class_member_access_expr (tree object, tree name, bool template_p,
 	  if (member == NULL_TREE)
 	    {
 	      if (complain & tf_error)
-		error ("%q#T has no member named %qE",
-		       TREE_CODE (access_path) == TREE_BINFO
-		       ? TREE_TYPE (access_path) : object_type, name);
+		{
+		  tree guessed_id = lookup_member_fuzzy (access_path, name,
+							 /*want_type=*/false);
+		  if (guessed_id)
+		    error ("%q#T has no member named %qE; did you mean %qE?",
+			   TREE_CODE (access_path) == TREE_BINFO
+			   ? TREE_TYPE (access_path) : object_type, name, guessed_id);
+		  else
+		    error ("%q#T has no member named %qE",
+			   TREE_CODE (access_path) == TREE_BINFO
+			   ? TREE_TYPE (access_path) : object_type, name);
+		}
 	      return error_mark_node;
 	    }
 	  if (member == error_mark_node)
diff --git a/gcc/spellcheck-tree.c b/gcc/spellcheck-tree.c
index d203776..f7fbcc0 100644
--- a/gcc/spellcheck-tree.c
+++ b/gcc/spellcheck-tree.c
@@ -37,3 +37,44 @@ levenshtein_distance (tree ident_s, tree ident_t)
 			       IDENTIFIER_POINTER (ident_t),
 			       IDENTIFIER_LENGTH (ident_t));
 }
+
+/* Given TARGET, an identifier, and CANDIDATES, a vec of identifiers,
+   determine which element within CANDIDATES has the lowest edit
+   distance to TARGET.  If there are multiple elements with the
+   same minimal distance, the first in the vector wins.
+
+   If more than half of the letters were misspelled, the suggestion is
+   likely to be meaningless, so return NULL_TREE for this case.  */
+
+tree
+find_closest_identifier (tree target, const auto_vec<tree> *candidates)
+{
+  gcc_assert (TREE_CODE (target) == IDENTIFIER_NODE);
+
+  int i;
+  tree identifier;
+  tree best_identifier = NULL_TREE;
+  edit_distance_t best_distance = MAX_EDIT_DISTANCE;
+  FOR_EACH_VEC_ELT (*candidates, i, identifier)
+    {
+      gcc_assert (TREE_CODE (identifier) == IDENTIFIER_NODE);
+      edit_distance_t dist = levenshtein_distance (target, identifier);
+      if (dist < best_distance)
+	{
+	  best_distance = dist;
+	  best_identifier = identifier;
+	}
+    }
+
+  /* If more than half of the letters were misspelled, the suggestion is
+     likely to be meaningless.  */
+  if (best_identifier)
+    {
+      unsigned int cutoff = MAX (IDENTIFIER_LENGTH (target),
+				 IDENTIFIER_LENGTH (best_identifier)) / 2;
+      if (best_distance > cutoff)
+	return NULL_TREE;
+    }
+
+  return best_identifier;
+}
diff --git a/gcc/spellcheck.h b/gcc/spellcheck.h
index 673a756..ad02998 100644
--- a/gcc/spellcheck.h
+++ b/gcc/spellcheck.h
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 typedef unsigned int edit_distance_t;
 const edit_distance_t MAX_EDIT_DISTANCE = UINT_MAX;
 
+/* spellcheck.c  */
 extern edit_distance_t
 levenshtein_distance (const char *s, int len_s,
 		      const char *t, int len_t);
@@ -30,7 +31,12 @@ levenshtein_distance (const char *s, int len_s,
 extern edit_distance_t
 levenshtein_distance (const char *s, const char *t);
 
+/* spellcheck-tree.c  */
+
 extern edit_distance_t
 levenshtein_distance (tree ident_s, tree ident_t);
 
+extern tree
+find_closest_identifier (tree target, const auto_vec<tree> *candidates);
+
 #endif  /* GCC_SPELLCHECK_H  */
diff --git a/gcc/testsuite/g++.dg/spellcheck-fields.C b/gcc/testsuite/g++.dg/spellcheck-fields.C
new file mode 100644
index 0000000..df49c92
--- /dev/null
+++ b/gcc/testsuite/g++.dg/spellcheck-fields.C
@@ -0,0 +1,89 @@
+/* { dg-do compile } */
+
+struct foo
+{
+  int foo;
+  int bar;
+  int baz;
+};
+
+int test (struct foo *ptr)
+{
+  return ptr->m_bar; /* { dg-error "'struct foo' has no member named 'm_bar'; did you mean 'bar'?" } */
+}
+
+int test2 (void)
+{
+  struct foo instance = {0, 0, 0};
+  return instance.m_bar; /* { dg-error "'struct foo' has no member named 'm_bar'; did you mean 'bar'?" } */
+}
+
+struct s {
+    struct j { int aa; } kk;
+    int ab;
+};
+
+void test3 (struct s x)
+{
+  x.ac;  /* { dg-error "'struct s' has no member named 'ac'; did you mean 'ab'?" } */
+}
+
+int test4 (struct foo *ptr)
+{
+  return sizeof (ptr->foa); /* { dg-error "'struct foo' has no member named 'foa'; did you mean 'foo'?" } */
+}
+
+/* Verify that we don't offer nonsensical suggestions.  */
+
+int test5 (struct foo *ptr)
+{
+  return ptr->this_is_unlike_any_of_the_fields;   /* { dg-bogus "did you mean" } */
+  /* { dg-error "has no member named" "" { target *-*-* } 40 } */
+}
+
+union u
+{
+  int color;
+  int shape;
+};
+
+int test6 (union u *ptr)
+{
+  return ptr->colour; /* { dg-error "'union u' has no member named 'colour'; did you mean 'color'?" } */
+}
+
+struct has_anon
+{
+  struct { int color; } s;
+};
+
+int test7 (struct has_anon *ptr)
+{
+  return ptr->s.colour; /* { dg-error "'struct has_anon::<anonymous>' has no member named 'colour'; did you mean 'color'?" } */
+}
+
+int test8 (foo &ref)
+{
+  return ref.m_bar; /* { dg-error "'struct foo' has no member named 'm_bar'; did you mean 'bar'?" } */
+}
+
+struct bar : public foo
+{
+  int fizz;
+  typedef int my_type;
+};
+
+int test9 (bar *ptr)
+{
+  return ptr->fuzz; /* { dg-error "'struct bar' has no member named 'fuzz'; did you mean 'fizz'?" } */
+}
+
+int test10 (bar *ptr)
+{
+  return ptr->m_foo; /* { dg-error "'struct bar' has no member named 'm_foo'; did you mean 'foo'?" } */
+}
+
+int test11 (bar *ptr)
+{
+  return ptr->mytype; /* { dg-error "'struct bar' has no member named 'mytype'; did you mean 'my_type'?" } */
+}
