|  | // 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); | 
|  | } | 
|  | } |