blob: ec05688f8645fa3c441d6e5dbf3c69c0bc96332c [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.syntax;
import static com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils.append;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.syntax.EvalException.EvalExceptionWithJavaCause;
import com.google.devtools.build.lib.syntax.compiler.ByteCodeMethodCalls;
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.DebugInfo.AstAccessors;
import com.google.devtools.build.lib.syntax.compiler.NewObject;
import com.google.devtools.build.lib.syntax.compiler.Variable.InternalVariable;
import com.google.devtools.build.lib.syntax.compiler.VariableScope;
import com.google.devtools.build.lib.util.StringUtilities;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.Removal;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
import net.bytebuddy.implementation.bytecode.constant.TextConstant;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
/**
* Syntax node for a function call expression.
*/
public final class FuncallExpression extends Expression {
/**
* A value class to store Methods with their corresponding SkylarkCallable annotations.
* This is needed because the annotation is sometimes in a superclass.
*/
public static final class MethodDescriptor {
private final Method method;
private final SkylarkCallable annotation;
private MethodDescriptor(Method method, SkylarkCallable annotation) {
this.method = method;
this.annotation = annotation;
}
Method getMethod() {
return method;
}
/**
* Returns the SkylarkCallable annotation corresponding to this method.
*/
public SkylarkCallable getAnnotation() {
return annotation;
}
}
private static final LoadingCache<Class<?>, Map<String, List<MethodDescriptor>>> methodCache =
CacheBuilder.newBuilder()
.initialCapacity(10)
.maximumSize(100)
.build(new CacheLoader<Class<?>, Map<String, List<MethodDescriptor>>>() {
@Override
public Map<String, List<MethodDescriptor>> load(Class<?> key) throws Exception {
Map<String, List<MethodDescriptor>> methodMap = new HashMap<>();
for (Method method : key.getMethods()) {
// Synthetic methods lead to false multiple matches
if (method.isSynthetic()) {
continue;
}
SkylarkCallable callable = getAnnotationFromParentClass(
method.getDeclaringClass(), method);
if (callable == null) {
continue;
}
String name = callable.name();
if (name.isEmpty()) {
name = StringUtilities.toPythonStyleFunctionName(method.getName());
}
String signature = name + "#" + method.getParameterTypes().length;
if (methodMap.containsKey(signature)) {
methodMap.get(signature).add(new MethodDescriptor(method, callable));
} else {
methodMap.put(signature, Lists.newArrayList(new MethodDescriptor(method, callable)));
}
}
return ImmutableMap.copyOf(methodMap);
}
});
/**
* Returns a map of methods and corresponding SkylarkCallable annotations
* of the methods of the classObj class reachable from Skylark.
*/
public static ImmutableMap<Method, SkylarkCallable> collectSkylarkMethodsWithAnnotation(
Class<?> classObj) {
ImmutableMap.Builder<Method, SkylarkCallable> methodMap = ImmutableMap.builder();
for (Method method : classObj.getMethods()) {
// Synthetic methods lead to false multiple matches
if (!method.isSynthetic()) {
SkylarkCallable annotation = getAnnotationFromParentClass(classObj, method);
if (annotation != null) {
methodMap.put(method, annotation);
}
}
}
return methodMap.build();
}
@Nullable
private static SkylarkCallable getAnnotationFromParentClass(Class<?> classObj, Method method) {
boolean keepLooking = false;
try {
Method superMethod = classObj.getMethod(method.getName(), method.getParameterTypes());
if (classObj.isAnnotationPresent(SkylarkModule.class)
&& superMethod.isAnnotationPresent(SkylarkCallable.class)) {
return superMethod.getAnnotation(SkylarkCallable.class);
} else {
keepLooking = true;
}
} catch (NoSuchMethodException e) {
// The class might not have the specified method, so an exceptions is OK.
keepLooking = true;
}
if (keepLooking) {
if (classObj.getSuperclass() != null) {
SkylarkCallable annotation =
getAnnotationFromParentClass(classObj.getSuperclass(), method);
if (annotation != null) {
return annotation;
}
}
for (Class<?> interfaceObj : classObj.getInterfaces()) {
SkylarkCallable annotation = getAnnotationFromParentClass(interfaceObj, method);
if (annotation != null) {
return annotation;
}
}
}
return null;
}
/**
* An exception class to handle exceptions in direct Java API calls.
*/
public static final class FuncallException extends Exception {
public FuncallException(String msg) {
super(msg);
}
}
@Nullable private final Expression obj;
private final Identifier func;
private final List<Argument.Passed> args;
private final int numPositionalArgs;
/**
* Note: the grammar definition restricts the function value in a function
* call expression to be a global identifier; however, the representation of
* values in the interpreter is flexible enough to allow functions to be
* arbitrary expressions. In any case, the "func" expression is always
* evaluated, so functions and variables share a common namespace.
*/
public FuncallExpression(@Nullable Expression obj, Identifier func,
List<Argument.Passed> args) {
this.obj = obj;
this.func = func;
this.args = args; // we assume the parser validated it with Argument#validateFuncallArguments()
this.numPositionalArgs = countPositionalArguments();
}
/**
* Note: the grammar definition restricts the function value in a function
* call expression to be a global identifier; however, the representation of
* values in the interpreter is flexible enough to allow functions to be
* arbitrary expressions. In any case, the "func" expression is always
* evaluated, so functions and variables share a common namespace.
*/
public FuncallExpression(Identifier func, List<Argument.Passed> args) {
this(null, func, args);
}
/**
* Returns the number of positional arguments.
*/
private int countPositionalArguments() {
int num = 0;
for (Argument.Passed arg : args) {
if (arg.isPositional()) {
num++;
}
}
return num;
}
/**
* Returns the function expression.
*/
public Identifier getFunction() {
return func;
}
/**
* Returns the object the function called on.
* It's null if the function is not called on an object.
*/
public Expression getObject() {
return obj;
}
/**
* Returns an (immutable, ordered) list of function arguments. The first n are
* positional and the remaining ones are keyword args, where n =
* getNumPositionalArguments().
*/
public List<Argument.Passed> getArguments() {
return Collections.unmodifiableList(args);
}
/**
* Returns the number of arguments which are positional; the remainder are
* keyword arguments.
*/
public int getNumPositionalArguments() {
return numPositionalArgs;
}
private String functionName() {
String name = func.getName();
if (name.equals("$slice")) {
return "operator [:]";
} else if (name.equals("$index")) {
return "operator []";
} else {
return "function " + name;
}
}
@Override
public String toString() {
if (func.getName().equals("$slice")) {
return obj + "[" + args.get(0) + ":" + args.get(1) + "]";
}
if (func.getName().equals("$index")) {
return obj + "[" + args.get(0) + "]";
}
StringBuilder sb = new StringBuilder();
if (obj != null) {
sb.append(obj).append(".");
}
sb.append(func);
Printer.printList(sb, args, "(", ", ", ")", /* singletonTerminator */ null,
Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT,
Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH);
return sb.toString();
}
/**
* 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, int argNum,
Location loc) throws EvalException {
try {
return methodCache.get(objClass).get(methodName + "#" + argNum);
} catch (ExecutionException e) {
throw new EvalException(loc, "Method invocation failed: " + e);
}
}
/**
* Returns the list of the Skylark name of all Skylark callable methods.
*/
public static List<String> getMethodNames(Class<?> objClass)
throws ExecutionException {
List<String> names = new ArrayList<>();
for (List<MethodDescriptor> methods : methodCache.get(objClass).values()) {
for (MethodDescriptor method : methods) {
// TODO(bazel-team): store the Skylark name in the MethodDescriptor.
String name = method.annotation.name();
if (name.isEmpty()) {
name = StringUtilities.toPythonStyleFunctionName(method.method.getName());
}
names.add(name);
}
}
return names;
}
static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
Object[] args, Location loc, Environment env) throws EvalException {
try {
Method method = methodDescriptor.getMethod();
if (obj == null && !Modifier.isStatic(method.getModifiers())) {
throw new EvalException(loc, "Method '" + methodName + "' is not static");
}
// This happens when the interface is public but the implementation classes
// have reduced visibility.
method.setAccessible(true);
Object result = method.invoke(obj, args);
if (method.getReturnType().equals(Void.TYPE)) {
return Runtime.NONE;
}
if (result == null) {
if (methodDescriptor.getAnnotation().allowReturnNones()) {
return Runtime.NONE;
} else {
throw new EvalException(loc,
"Method invocation returned None, please contact Skylark developers: " + methodName
+ Printer.listString(ImmutableList.copyOf(args), "(", ", ", ")", null));
}
}
// TODO(bazel-team): get rid of this, by having everyone use the Skylark data structures
result = SkylarkType.convertToSkylark(result, method, env);
if (result != null && !EvalUtils.isSkylarkAcceptable(result.getClass())) {
throw new EvalException(loc, Printer.format(
"Method '%s' returns an object of invalid type %r", methodName, result.getClass()));
}
return result;
} catch (IllegalAccessException e) {
// TODO(bazel-team): Print a nice error message. Maybe the method exists
// and an argument is missing or has the wrong type.
throw new EvalException(loc, "Method invocation failed: " + e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof FuncallException) {
throw new EvalException(loc, e.getCause().getMessage());
} else if (e.getCause() != null) {
throw new EvalExceptionWithJavaCause(loc, e.getCause());
} else {
// This is unlikely to happen
throw new EvalException(loc, "Method invocation failed: " + e);
}
}
}
// TODO(bazel-team): If there's exactly one usable method, this works. If there are multiple
// matching methods, it still can be a problem. Figure out how the Java compiler does it
// exactly and copy that behaviour.
private MethodDescriptor findJavaMethod(
Class<?> objClass, String methodName, List<Object> args) throws EvalException {
MethodDescriptor matchingMethod = null;
List<MethodDescriptor> methods = getMethods(objClass, methodName, args.size(), getLocation());
if (methods != null) {
for (MethodDescriptor method : methods) {
Class<?>[] params = method.getMethod().getParameterTypes();
int i = 0;
boolean matching = true;
for (Class<?> param : params) {
if (!param.isAssignableFrom(args.get(i).getClass())) {
matching = false;
break;
}
i++;
}
if (matching) {
if (matchingMethod == null) {
matchingMethod = method;
} else {
throw new EvalException(
getLocation(),
String.format(
"Type %s has multiple matches for %s",
EvalUtils.getDataTypeNameFromClass(objClass),
formatMethod(args)));
}
}
}
}
if (matchingMethod != null && !matchingMethod.getAnnotation().structField()) {
return matchingMethod;
}
throw new EvalException(
getLocation(),
String.format(
"Type %s has no %s",
EvalUtils.getDataTypeNameFromClass(objClass),
formatMethod(args)));
}
private String formatMethod(List<Object> args) {
StringBuilder sb = new StringBuilder();
sb.append(functionName()).append("(");
boolean first = true;
for (Object obj : args) {
if (!first) {
sb.append(", ");
}
sb.append(EvalUtils.getDataTypeName(obj));
first = false;
}
return sb.append(")").toString();
}
/**
* A {@link StackManipulation} invoking addKeywordArg.
* <p>Kept close to the definition of the method to avoid reflection errors when changing it.
*/
private static final StackManipulation addKeywordArg =
ByteCodeUtils.invoke(
FuncallExpression.class,
"addKeywordArg",
Map.class,
String.class,
Object.class,
ImmutableList.Builder.class);
/**
* Add one argument to the keyword map, registering a duplicate in case of conflict.
*
* <p>public for reflection by the compiler and calls from compiled functions
*/
public static void addKeywordArg(
Map<String, Object> kwargs,
String name,
Object value,
ImmutableList.Builder<String> duplicates) {
if (kwargs.put(name, value) != null) {
duplicates.add(name);
}
}
/**
* A {@link StackManipulation} invoking addKeywordArgs.
* <p>Kept close to the definition of the method to avoid reflection errors when changing it.
*/
private static final StackManipulation addKeywordArgs =
ByteCodeUtils.invoke(
FuncallExpression.class,
"addKeywordArgs",
Map.class,
Object.class,
ImmutableList.Builder.class,
Location.class);
/**
* Add multiple arguments to the keyword map (**kwargs), registering duplicates
*
* <p>public for reflection by the compiler and calls from compiled functions
*/
public static void addKeywordArgs(
Map<String, Object> kwargs,
Object items,
ImmutableList.Builder<String> duplicates,
Location location)
throws EvalException {
if (!(items instanceof Map<?, ?>)) {
throw new EvalException(
location,
"Argument after ** must be a dictionary, not " + EvalUtils.getDataTypeName(items));
}
for (Map.Entry<?, ?> entry : ((Map<?, ?>) items).entrySet()) {
if (!(entry.getKey() instanceof String)) {
throw new EvalException(
location, "Keywords must be strings, not " + EvalUtils.getDataTypeName(entry.getKey()));
}
addKeywordArg(kwargs, (String) entry.getKey(), entry.getValue(), duplicates);
}
}
/**
* A {@link StackManipulation} invoking checkCallable.
* <p>Kept close to the definition of the method to avoid reflection errors when changing it.
*/
private static final StackManipulation checkCallable =
ByteCodeUtils.invoke(FuncallExpression.class, "checkCallable", Object.class, Location.class);
/**
* Checks whether the given object is a {@link BaseFunction}.
*
* <p>Public for reflection by the compiler and access from generated byte code.
*
* @throws EvalException If not a BaseFunction.
*/
public static BaseFunction checkCallable(Object functionValue, Location location)
throws EvalException {
if (functionValue instanceof BaseFunction) {
return (BaseFunction) functionValue;
} else {
throw new EvalException(
location, "'" + EvalUtils.getDataTypeName(functionValue) + "' object is not callable");
}
}
/**
* A {@link StackManipulation} invoking checkDuplicates.
* <p>Kept close to the definition of the method to avoid reflection errors when changing it.
*/
private static final StackManipulation checkDuplicates =
ByteCodeUtils.invoke(
FuncallExpression.class,
"checkDuplicates",
ImmutableList.Builder.class,
String.class,
Location.class);
/**
* Check the list from the builder and report an {@link EvalException} if not empty.
*
* <p>public for reflection by the compiler and calls from compiled functions
*/
public static void checkDuplicates(
ImmutableList.Builder<String> duplicates, String function, Location location)
throws EvalException {
List<String> dups = duplicates.build();
if (!dups.isEmpty()) {
throw new EvalException(
location,
"duplicate keyword"
+ (dups.size() > 1 ? "s" : "")
+ " '"
+ Joiner.on("', '").join(dups)
+ "' in call to "
+ function);
}
}
/**
* A {@link StackManipulation} invoking invokeObjectMethod.
* <p>Kept close to the definition of the method to avoid reflection errors when changing it.
*/
private static final StackManipulation invokeObjectMethod =
ByteCodeUtils.invoke(
FuncallExpression.class,
"invokeObjectMethod",
String.class,
ImmutableList.class,
ImmutableMap.class,
FuncallExpression.class,
Environment.class);
/**
* Call a method depending on the type of an object it is called on.
*
* <p>Public for reflection by the compiler and access from generated byte code.
*
* @param positionals The first object is expected to be the object the method is called on.
* @param call the original expression that caused this call, needed for rules especially
*/
public static Object invokeObjectMethod(
String method,
ImmutableList<Object> positionals,
ImmutableMap<String, Object> keyWordArgs,
FuncallExpression call,
Environment env)
throws EvalException, InterruptedException {
Location location = call.getLocation();
Object value = positionals.get(0);
ImmutableList<Object> positionalArgs = positionals.subList(1, positionals.size());
BaseFunction function = Runtime.getFunction(EvalUtils.getSkylarkType(value.getClass()), method);
if (function != null) {
if (!isNamespace(value.getClass())) {
// Use self as an implicit parameter in front.
positionalArgs = positionals;
}
return function.call(
positionalArgs, ImmutableMap.<String, Object>copyOf(keyWordArgs), call, env);
} else if (value instanceof ClassObject) {
Object fieldValue = ((ClassObject) value).getValue(method);
if (fieldValue == null) {
throw new EvalException(location, String.format("struct has no method '%s'", method));
}
if (!(fieldValue instanceof BaseFunction)) {
throw new EvalException(
location, String.format("struct field '%s' is not a function", method));
}
function = (BaseFunction) fieldValue;
return function.call(
positionalArgs, ImmutableMap.<String, Object>copyOf(keyWordArgs), call, env);
} else {
// When calling a Java method, the name is not in the Environment,
// so evaluating 'func' would fail.
Class<?> objClass;
Object obj;
if (value instanceof Class<?>) {
// Static call
obj = null;
objClass = (Class<?>) value;
} else {
obj = value;
objClass = value.getClass();
}
MethodDescriptor methodDescriptor = call.findJavaMethod(objClass, method, positionalArgs);
if (!keyWordArgs.isEmpty()) {
throw new EvalException(
call.func.getLocation(),
String.format(
"Keyword arguments are not allowed when calling a java method"
+ "\nwhile calling method '%s' for type %s",
method,
EvalUtils.getDataTypeNameFromClass(objClass)));
}
return callMethod(methodDescriptor, method, obj, positionalArgs.toArray(), location, env);
}
}
@SuppressWarnings("unchecked")
private void evalArguments(ImmutableList.Builder<Object> posargs, Map<String, Object> kwargs,
Environment env)
throws EvalException, InterruptedException {
ImmutableList.Builder<String> duplicates = new ImmutableList.Builder<>();
// Iterate over the arguments. We assume all positional arguments come before any keyword
// or star arguments, because the argument list was already validated by
// Argument#validateFuncallArguments, as called by the Parser,
// which should be the only place that build FuncallExpression-s.
for (Argument.Passed arg : args) {
Object value = arg.getValue().eval(env);
if (arg.isPositional()) {
posargs.add(value);
} else if (arg.isStar()) { // expand the starArg
if (value instanceof Iterable) {
posargs.addAll((Iterable<Object>) value);
}
} else if (arg.isStarStar()) { // expand the kwargs
addKeywordArgs(kwargs, value, duplicates, getLocation());
} else {
addKeywordArg(kwargs, arg.getName(), value, duplicates);
}
}
checkDuplicates(duplicates, func.getName(), getLocation());
}
@VisibleForTesting
public static boolean isNamespace(Class<?> classObject) {
return classObject.isAnnotationPresent(SkylarkModule.class)
&& classObject.getAnnotation(SkylarkModule.class).namespace();
}
@Override
Object doEval(Environment env) throws EvalException, InterruptedException {
return (obj != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env);
}
/**
* Invokes obj.func() and returns the result.
*/
private Object invokeObjectMethod(Environment env) throws EvalException, InterruptedException {
Object objValue = obj.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
// we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
Map<String, Object> kwargs = new HashMap<>();
evalArguments(posargs, kwargs, env);
return invokeObjectMethod(
func.getName(), posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env);
}
/**
* Invokes func() and returns the result.
*/
private Object invokeGlobalFunction(Environment env) throws EvalException, InterruptedException {
Object funcValue = func.eval(env);
ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
// We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
// we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
Map<String, Object> kwargs = new HashMap<>();
BaseFunction function = checkCallable(funcValue, getLocation());
evalArguments(posargs, kwargs, env);
return function.call(posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env);
}
/**
* Returns the value of the argument 'name' (or null if there is none).
* This function is used to associate debugging information to rules created by skylark "macros".
*/
@Nullable
public String getNameArg() {
for (Argument.Passed arg : args) {
if (arg != null) {
String name = arg.getName();
if (name != null && name.equals("name")) {
Expression expr = arg.getValue();
return (expr != null && expr instanceof StringLiteral)
? ((StringLiteral) expr).getValue() : null;
}
}
}
return null;
}
@Override
public void accept(SyntaxTreeVisitor visitor) {
visitor.visit(this);
}
@Override
void validate(ValidationEnvironment env) throws EvalException {
for (Argument.Passed arg : args) {
arg.getValue().validate(env);
}
if (obj != null) {
obj.validate(env);
} else if (!env.hasSymbolInEnvironment(func.getName())) {
throw new EvalException(getLocation(),
String.format("function '%s' does not exist", func.getName()));
}
}
@Override
protected boolean isNewScope() {
return true;
}
@Override
ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo) throws EvalException {
AstAccessors debugAccessors = debugInfo.add(this);
List<ByteCodeAppender> code = new ArrayList<>();
if (obj != null) {
compileObjectMethodCall(scope, debugInfo, debugAccessors, code);
} else {
compileGlobalFunctionCall(scope, debugInfo, debugAccessors, code);
}
return ByteCodeUtils.compoundAppender(code);
}
/**
* Add code that compiles the argument expressions.
*
* <p>The byte code leaves the arguments on the stack in order of:
* positional arguments, key word arguments, this FuncallExpression, Environment
* This is the order required by {@link #invokeObjectMethod} and
* {@link BaseFunction#call(List, Map, FuncallExpression, Environment)}.
*/
private void compileArguments(
VariableScope scope,
DebugInfo debugInfo,
AstAccessors debugAccessors,
List<ByteCodeAppender> code)
throws EvalException {
InternalVariable positionalsBuilder = scope.freshVariable(ImmutableList.Builder.class);
append(code, ByteCodeMethodCalls.BCImmutableList.builder);
code.add(positionalsBuilder.store());
InternalVariable keyWordArgs = scope.freshVariable(Map.class);
append(code, NewObject.fromConstructor(HashMap.class).arguments());
code.add(keyWordArgs.store());
InternalVariable duplicatesBuilder =
scope.freshVariable(new TypeDescription.ForLoadedType(ImmutableList.Builder.class));
append(code, ByteCodeMethodCalls.BCImmutableList.builder);
code.add(duplicatesBuilder.store());
StackManipulation builderAdd =
new StackManipulation.Compound(
ByteCodeMethodCalls.BCImmutableList.Builder.add, Removal.SINGLE);
// add an object the function is called on first
if (obj != null) {
append(code, positionalsBuilder.load());
code.add(obj.compile(scope, debugInfo));
append(code, builderAdd);
}
// add all arguments to their respective builder/map
for (Argument.Passed arg : args) {
ByteCodeAppender value = arg.getValue().compile(scope, debugInfo);
if (arg.isPositional()) {
append(code, positionalsBuilder.load());
code.add(value);
append(code, builderAdd);
} else if (arg.isStar()) {
// expand the starArg by adding all it's elements to the builder
append(code, positionalsBuilder.load());
code.add(value);
append(
code,
TypeCasting.to(new TypeDescription.ForLoadedType(Iterable.class)),
ByteCodeMethodCalls.BCImmutableList.Builder.addAll,
Removal.SINGLE);
} else if (arg.isStarStar()) {
append(code, keyWordArgs.load());
code.add(value);
append(code, duplicatesBuilder.load(), debugAccessors.loadLocation, addKeywordArgs);
} else {
append(code, keyWordArgs.load(), new TextConstant(arg.getName()));
code.add(value);
append(code, duplicatesBuilder.load(), addKeywordArg);
}
}
append(
code,
// check for duplicates in the key word arguments
duplicatesBuilder.load(),
new TextConstant(func.getName()),
debugAccessors.loadLocation,
checkDuplicates,
// load the arguments in the correct order for invokeObjectMethod and BaseFunction.call
positionalsBuilder.load(),
ByteCodeMethodCalls.BCImmutableList.Builder.build,
keyWordArgs.load(),
ByteCodeMethodCalls.BCImmutableMap.copyOf,
debugAccessors.loadAstNode,
TypeCasting.to(new TypeDescription.ForLoadedType(FuncallExpression.class)),
scope.loadEnvironment());
}
private void compileObjectMethodCall(
VariableScope scope,
DebugInfo debugInfo,
AstAccessors debugAccessors,
List<ByteCodeAppender> code)
throws EvalException {
append(code, new TextConstant(func.getName()));
compileArguments(scope, debugInfo, debugAccessors, code);
append(code, invokeObjectMethod);
}
private void compileGlobalFunctionCall(
VariableScope scope,
DebugInfo debugInfo,
AstAccessors debugAccessors,
List<ByteCodeAppender> code)
throws EvalException {
code.add(func.compile(scope, debugInfo));
append(code, debugAccessors.loadLocation, checkCallable);
compileArguments(scope, debugInfo, debugAccessors, code);
append(code, BaseFunction.call);
}
}