Refactor FuncallExpression to allow for complex function terms later.

RELNOTES: None.
PiperOrigin-RevId: 164981071
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 ab68d65..6bfa55a 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
@@ -186,24 +186,48 @@
     }
   }
 
-  @Nullable private final Expression object;
-
-  private final Identifier function;
+  private final Expression function;
 
   private final List<Argument.Passed> arguments;
 
   private final int numPositionalArgs;
 
-  public FuncallExpression(@Nullable Expression object, Identifier function,
-                           List<Argument.Passed> arguments) {
-    this.object = object;
-    this.function = function;
-    this.arguments = arguments;
+  public FuncallExpression(Expression function, List<Argument.Passed> arguments) {
+    this.function = Preconditions.checkNotNull(function);
+    this.arguments = Preconditions.checkNotNull(arguments);
     this.numPositionalArgs = countPositionalArguments();
   }
 
-  public FuncallExpression(Identifier function, List<Argument.Passed> arguments) {
-    this(null, function, arguments);
+  /** Returns the function that is called. */
+  public Expression getFunction() {
+    return this.function;
+  }
+
+  /**
+   * Returns the name of the called function if it's available in the AST.
+   *
+   * <p>It may not be available in cases like `list[0](arg1, arg2)`.
+   */
+  @Nullable
+  public String getFunctionNameIfPossible() {
+    Identifier ident = getFunctionIdentifierIfPossible();
+    return ident == null ? null : ident.getName();
+  }
+
+  /**
+   * Returns the identifier of the called function if it's available in the AST.
+   *
+   * <p>It may not be available in cases like `list[0](arg1, arg2)`.
+   */
+  @Nullable
+  public Identifier getFunctionIdentifierIfPossible() {
+    if (function instanceof Identifier) {
+      return (Identifier) function;
+    }
+    if (function instanceof DotExpression) {
+      return ((DotExpression) function).getField();
+    }
+    return null;
   }
 
   /**
@@ -220,22 +244,6 @@
   }
 
   /**
-   * Returns the function expression.
-   */
-  public Identifier getFunction() {
-    return function;
-  }
-
-  /**
-   * Returns the object the function called on.
-   * It's null if the function is not called on an object.
-   */
-  @Nullable
-  public Expression getObject() {
-    return object;
-  }
-
-  /**
    * Returns an (immutable, ordered) list of function arguments. The first n are
    * positional and the remaining ones are keyword args, where n =
    * getNumPositionalArguments().
@@ -254,10 +262,6 @@
 
    @Override
    public void prettyPrint(Appendable buffer) throws IOException {
-     if (object != null) {
-       object.prettyPrint(buffer);
-       buffer.append('.');
-     }
      function.prettyPrint(buffer);
      buffer.append('(');
      String sep = "";
@@ -272,9 +276,6 @@
   @Override
   public String toString() {
     Printer.LengthLimitedPrinter printer = new Printer.LengthLimitedPrinter();
-    if (object != null) {
-      printer.append(object.toString()).append(".");
-    }
     printer.append(function.toString());
     printer.printAbbreviatedList(arguments, "(", ", ", ")", null,
         Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT,
@@ -381,7 +382,8 @@
                   getLocation(),
                   String.format(
                       "type '%s' has multiple matches for function %s",
-                      EvalUtils.getDataTypeNameFromClass(objClass), formatMethod(args, kwargs)));
+                      EvalUtils.getDataTypeNameFromClass(objClass),
+                      formatMethod(methodName, args, kwargs)));
             }
           }
         }
@@ -396,14 +398,15 @@
         errorMessage =
             String.format(
                 "type '%s' has no method %s",
-                EvalUtils.getDataTypeNameFromClass(objClass), formatMethod(args, kwargs));
+                EvalUtils.getDataTypeNameFromClass(objClass),
+                formatMethod(methodName, args, kwargs));
 
       } else {
         errorMessage =
             String.format(
                 "%s, in method %s of '%s'",
                 argumentListConversionResult.getError(),
-                formatMethod(args, kwargs),
+                formatMethod(methodName, args, kwargs),
                 EvalUtils.getDataTypeNameFromClass(objClass));
       }
       throw new EvalException(getLocation(), errorMessage);
@@ -513,9 +516,9 @@
     return ArgumentListConversionResult.fromArgumentList(builder.build());
   }
 
-  private String formatMethod(List<Object> args, Map<String, Object> kwargs) {
+  private static String formatMethod(String name, List<Object> args, Map<String, Object> kwargs) {
     StringBuilder sb = new StringBuilder();
-    sb.append(function.getName()).append("(");
+    sb.append(name).append("(");
     boolean first = true;
     for (Object obj : args) {
       if (!first) {
@@ -713,7 +716,7 @@
         addKeywordArg(kwargs, arg.getName(), value, duplicates);
       }
     }
-    checkDuplicates(duplicates, function.getName(), getLocation());
+    checkDuplicates(duplicates, function.toString(), getLocation());
   }
 
   @VisibleForTesting
@@ -724,14 +727,20 @@
 
   @Override
   Object doEval(Environment env) throws EvalException, InterruptedException {
-    return (object != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env);
+    if (function instanceof DotExpression) {
+      return invokeObjectMethod(env, (DotExpression) function);
+    }
+    if (function instanceof Identifier) {
+      return invokeGlobalFunction(env);
+    }
+    throw new EvalException(
+        getLocation(), Printer.format("cannot evaluate function '%s'", function));
   }
 
-  /**
-   * Invokes object.function() and returns the result.
-   */
-  private Object invokeObjectMethod(Environment env) throws EvalException, InterruptedException {
-    Object objValue = object.eval(env);
+  /** Invokes object.function() and returns the result. */
+  private Object invokeObjectMethod(Environment env, DotExpression dot)
+      throws EvalException, InterruptedException {
+    Object objValue = dot.getObject().eval(env);
     ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
     posargs.add(objValue);
     // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
@@ -739,7 +748,7 @@
     Map<String, Object> kwargs = new LinkedHashMap<>();
     evalArguments(posargs, kwargs, env);
     return invokeObjectMethod(
-        function.getName(), posargs.build(), ImmutableMap.copyOf(kwargs), this, env);
+        dot.getField().getName(), posargs.build(), ImmutableMap.copyOf(kwargs), this, env);
   }
 
   /**
@@ -789,11 +798,7 @@
 
   @Override
   void validate(ValidationEnvironment env) throws EvalException {
-    if (object != null) {
-      object.validate(env);
-    } else {
-      function.validate(env);
-    }
+    function.validate(env);
     for (Argument.Passed arg : arguments) {
       arg.getValue().validate(env);
     }