Add 'did you mean' suggestion when accessing a struct field

--
PiperOrigin-RevId: 143380643
MOS_MIGRATED_REVID=143380643
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Package.java b/src/main/java/com/google/devtools/build/lib/packages/Package.java
index dc84c5d..8296c36 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Package.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Package.java
@@ -540,12 +540,7 @@
       suffix = "; however, a source file of this name exists.  (Perhaps add "
           + "'exports_files([\"" + targetName + "\"])' to " + name + "/BUILD?)";
     } else {
-      String suggestion = SpellChecker.suggest(targetName, targets.keySet());
-      if (suggestion != null) {
-        suffix = " (did you mean '" + suggestion + "'?)";
-      } else {
-        suffix = "";
-      }
+      suffix = SpellChecker.didYouMean(targetName, targets.keySet());
     }
 
     throw makeNoSuchTargetException(targetName, suffix);
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 5d94498..ec5a8a8 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
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils;
 import com.google.devtools.build.lib.syntax.compiler.DebugInfo;
 import com.google.devtools.build.lib.syntax.compiler.VariableScope;
+import com.google.devtools.build.lib.util.SpellChecker;
 import java.util.ArrayList;
 import java.util.List;
 import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
@@ -64,19 +65,22 @@
    */
   public static Object checkResult(Object objValue, Object result, String name, Location loc)
       throws EvalException {
-    if (result == null) {
-      if (objValue instanceof ClassObject) {
-        String customErrorMessage = ((ClassObject) objValue).errorMessage(name);
-        if (customErrorMessage != null) {
-          throw new EvalException(loc, customErrorMessage);
-        }
-      }
-      throw new EvalException(
-          loc,
-          Printer.format(
-              "object of type '%s' has no field %r", EvalUtils.getDataTypeName(objValue), name));
+    if (result != null) {
+      return result;
     }
-    return result;
+    String suffix = "";
+    if (objValue instanceof ClassObject) {
+      String customErrorMessage = ((ClassObject) objValue).errorMessage(name);
+      if (customErrorMessage != null) {
+        throw new EvalException(loc, customErrorMessage);
+      }
+      suffix = SpellChecker.didYouMean(name, ((ClassObject) objValue).getKeys());
+    }
+    throw new EvalException(
+        loc,
+        String.format(
+            "object of type '%s' has no field '%s'%s",
+            EvalUtils.getDataTypeName(objValue), name, suffix));
   }
 
   /**
@@ -102,9 +106,10 @@
       }
     }
 
-    Iterable<MethodDescriptor> methods = objValue instanceof Class<?>
-        ? FuncallExpression.getMethods((Class<?>) objValue, name, loc)
-        : FuncallExpression.getMethods(objValue.getClass(), name, loc);
+    Iterable<MethodDescriptor> methods =
+        objValue instanceof Class<?>
+            ? FuncallExpression.getMethods((Class<?>) objValue, name)
+            : FuncallExpression.getMethods(objValue.getClass(), name);
 
     if (methods != null) {
       methods =
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
index 30b9417..e3468da 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
@@ -279,15 +279,14 @@
   }
 
   /**
-   * Returns the list of Skylark callable Methods of objClass with the given name
-   * and argument number.
+   * Returns the list of Skylark callable Methods of objClass with the given name and argument
+   * number.
    */
-  public static List<MethodDescriptor> getMethods(Class<?> objClass, String methodName,
-      Location loc) throws EvalException {
+  public static List<MethodDescriptor> getMethods(Class<?> objClass, String methodName) {
     try {
       return methodCache.get(objClass).get(methodName);
     } catch (ExecutionException e) {
-      throw new EvalException(loc, "method invocation failed: " + e);
+      throw new IllegalStateException("method invocation failed: " + e);
     }
   }
 
@@ -295,8 +294,12 @@
    * Returns a set of the Skylark name of all Skylark callable methods for object of type {@code
    * objClass}.
    */
-  public static Set<String> getMethodNames(Class<?> objClass) throws ExecutionException {
-    return methodCache.get(objClass).keySet();
+  public static Set<String> getMethodNames(Class<?> objClass) {
+    try {
+      return methodCache.get(objClass).keySet();
+    } catch (ExecutionException e) {
+      throw new IllegalStateException("method invocation failed: " + e);
+    }
   }
 
   static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
@@ -357,7 +360,7 @@
       Class<?> objClass, String methodName, List<Object> args, Map<String, Object> kwargs)
       throws EvalException {
     Pair<MethodDescriptor, List<Object>> matchingMethod = null;
-    List<MethodDescriptor> methods = getMethods(objClass, methodName, getLocation());
+    List<MethodDescriptor> methods = getMethods(objClass, methodName);
     ArgumentListConversionResult argumentListConversionResult = null;
     if (methods != null) {
       for (MethodDescriptor method : methods) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java b/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
index 83335e2..a6c1708 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
@@ -95,14 +95,7 @@
     if (name.equals("$error$")) {
       return new EvalException(getLocation(), "contains syntax error(s)", true);
     }
-
-    String suggestion = SpellChecker.suggest(name, symbols);
-    if (suggestion == null) {
-      suggestion = "";
-    } else {
-      suggestion = " (did you mean '" + suggestion + "'?)";
-    }
-
+    String suggestion = SpellChecker.didYouMean(name, symbols);
     return new EvalException(getLocation(), "name '" + name + "' is not defined" + suggestion);
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
index 415d427..572b281 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
@@ -39,7 +39,6 @@
 import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.concurrent.ExecutionException;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -1960,12 +1959,7 @@
       return true;
     }
 
-    try {
-      return FuncallExpression.getMethodNames(obj.getClass()).contains(name);
-    } catch (ExecutionException e) {
-      // This shouldn't happen
-      throw new EvalException(loc, e.getMessage());
-    }
+    return FuncallExpression.getMethodNames(obj.getClass()).contains(name);
   }
 
   @SkylarkSignature(
@@ -1988,12 +1982,7 @@
             fields.addAll(((ClassObject) object).getKeys());
           }
           fields.addAll(Runtime.getFunctionNames(object.getClass()));
-          try {
-            fields.addAll(FuncallExpression.getMethodNames(object.getClass()));
-          } catch (ExecutionException e) {
-            // This shouldn't happen
-            throw new EvalException(loc, e.getMessage());
-          }
+          fields.addAll(FuncallExpression.getMethodNames(object.getClass()));
           return new MutableList(fields, env);
         }
       };
diff --git a/src/main/java/com/google/devtools/build/lib/util/SpellChecker.java b/src/main/java/com/google/devtools/build/lib/util/SpellChecker.java
index b8fda8f..2671a90 100644
--- a/src/main/java/com/google/devtools/build/lib/util/SpellChecker.java
+++ b/src/main/java/com/google/devtools/build/lib/util/SpellChecker.java
@@ -94,4 +94,17 @@
     }
     return best;
   }
+
+  /**
+   * Return a string to be used at the end of an error message. It is either an empty string, or a
+   * spelling suggestion, e.g. " (did you mean 'x'?)".
+   */
+  public static String didYouMean(String input, Iterable<String> words) {
+    String suggestion = suggest(input, words);
+    if (suggestion == null) {
+      return "";
+    } else {
+      return " (did you mean '" + suggestion + "'?)";
+    }
+  }
 }