Add typo detection when lookups on SkylarkModules fail.

Also consolidate code with getattr so getattr now also gets typo detection.

PiperOrigin-RevId: 197612666
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
index 4a1507b..ed45d60 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
@@ -63,19 +63,54 @@
     if (result != null) {
       return result;
     }
+    throw getMissingFieldException(objValue, name, loc, "field");
+  }
+
+  static EvalException getMissingFieldException(
+      Object objValue, String name, Location loc, String accessName) {
     String suffix = "";
+    EvalException toSuppress = null;
     if (objValue instanceof ClassObject) {
       String customErrorMessage = ((ClassObject) objValue).getErrorMessageForUnknownField(name);
       if (customErrorMessage != null) {
-        throw new EvalException(loc, customErrorMessage);
+        return new EvalException(loc, customErrorMessage);
       }
-      suffix = SpellChecker.didYouMean(name, ((ClassObject) objValue).getFieldNames());
+      try {
+        suffix = SpellChecker.didYouMean(name, ((ClassObject) objValue).getFieldNames());
+      } catch (EvalException ee) {
+        toSuppress = ee;
+      }
+    } else {
+      suffix =
+          SpellChecker.didYouMean(
+              name,
+              FuncallExpression.getStructFieldNames(
+                  objValue instanceof Class ? (Class<?>) objValue : objValue.getClass()));
     }
-    throw new EvalException(
-        loc,
-        String.format(
-            "object of type '%s' has no field '%s'%s",
-            EvalUtils.getDataTypeName(objValue), name, suffix));
+    if (suffix.isEmpty() && hasMethod(objValue, name)) {
+      // If looking up the field failed, then we know that this method must have struct_field=false
+      suffix = ", however, a method of that name exists";
+    }
+    EvalException ee =
+        new EvalException(
+            loc,
+            String.format(
+                "object of type '%s' has no %s '%s'%s",
+                EvalUtils.getDataTypeName(objValue), accessName, name, suffix));
+    if (toSuppress != null) {
+      ee.addSuppressed(toSuppress);
+    }
+    return ee;
+  }
+
+  /** Returns whether the given object has a method with the given name. */
+  static boolean hasMethod(Object obj, String name) {
+    Class<?> cls = obj instanceof Class ? (Class<?>) obj : obj.getClass();
+    if (Runtime.getBuiltinRegistry().getFunctionNames(cls).contains(name)) {
+      return true;
+    }
+
+    return FuncallExpression.getMethodNames(cls).contains(name);
   }
 
   /**