blob: 0245ea6a209fa90bd73511fb24c422af47883a53 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.syntax;
16
Florian Weikert33f819b2015-11-09 18:15:55 +000017import static com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils.append;
18
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000019import com.google.common.annotations.VisibleForTesting;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +000020import com.google.common.base.Joiner;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.common.cache.CacheBuilder;
22import com.google.common.cache.CacheLoader;
23import com.google.common.cache.LoadingCache;
24import com.google.common.collect.ImmutableList;
25import com.google.common.collect.ImmutableMap;
26import com.google.common.collect.Lists;
27import com.google.devtools.build.lib.events.Location;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000028import com.google.devtools.build.lib.skylarkinterface.Param;
John Field585d1a02015-12-16 16:03:52 +000029import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
30import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031import com.google.devtools.build.lib.syntax.EvalException.EvalExceptionWithJavaCause;
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +000032import com.google.devtools.build.lib.syntax.Runtime.NoneType;
Florian Weikert33f819b2015-11-09 18:15:55 +000033import com.google.devtools.build.lib.syntax.compiler.ByteCodeMethodCalls;
34import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils;
35import com.google.devtools.build.lib.syntax.compiler.DebugInfo;
36import com.google.devtools.build.lib.syntax.compiler.DebugInfo.AstAccessors;
37import com.google.devtools.build.lib.syntax.compiler.NewObject;
38import com.google.devtools.build.lib.syntax.compiler.Variable.InternalVariable;
39import com.google.devtools.build.lib.syntax.compiler.VariableScope;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000040import com.google.devtools.build.lib.util.Pair;
41import com.google.devtools.build.lib.util.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042import com.google.devtools.build.lib.util.StringUtilities;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043import java.lang.reflect.InvocationTargetException;
44import java.lang.reflect.Method;
45import java.lang.reflect.Modifier;
46import java.util.ArrayList;
47import java.util.Collections;
48import java.util.HashMap;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000049import java.util.HashSet;
Pedro Liberal Fernandez837dbc12016-08-18 14:13:01 +000050import java.util.LinkedHashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010051import java.util.List;
52import java.util.Map;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000053import java.util.Set;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054import java.util.concurrent.ExecutionException;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000055import javax.annotation.Nullable;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000056import net.bytebuddy.description.type.TypeDescription;
57import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
58import net.bytebuddy.implementation.bytecode.Removal;
59import net.bytebuddy.implementation.bytecode.StackManipulation;
60import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
61import net.bytebuddy.implementation.bytecode.constant.TextConstant;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000062
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010063/**
64 * Syntax node for a function call expression.
65 */
66public final class FuncallExpression extends Expression {
67
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010068 /**
69 * A value class to store Methods with their corresponding SkylarkCallable annotations.
70 * This is needed because the annotation is sometimes in a superclass.
71 */
72 public static final class MethodDescriptor {
73 private final Method method;
74 private final SkylarkCallable annotation;
75
76 private MethodDescriptor(Method method, SkylarkCallable annotation) {
77 this.method = method;
78 this.annotation = annotation;
79 }
80
81 Method getMethod() {
82 return method;
83 }
84
85 /**
86 * Returns the SkylarkCallable annotation corresponding to this method.
87 */
88 public SkylarkCallable getAnnotation() {
89 return annotation;
90 }
91 }
92
93 private static final LoadingCache<Class<?>, Map<String, List<MethodDescriptor>>> methodCache =
94 CacheBuilder.newBuilder()
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000095 .initialCapacity(10)
96 .maximumSize(100)
97 .build(
98 new CacheLoader<Class<?>, Map<String, List<MethodDescriptor>>>() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010099
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000100 @Override
101 public Map<String, List<MethodDescriptor>> load(Class<?> key) throws Exception {
102 Map<String, List<MethodDescriptor>> methodMap = new HashMap<>();
103 for (Method method : key.getMethods()) {
104 // Synthetic methods lead to false multiple matches
105 if (method.isSynthetic()) {
106 continue;
107 }
108 SkylarkCallable callable =
109 getAnnotationFromParentClass(method.getDeclaringClass(), method);
110 if (callable == null) {
111 continue;
112 }
113 Preconditions.checkArgument(
114 callable.parameters().length == 0 || !callable.structField(),
115 "Method "
116 + method
117 + " was annotated with both structField amd parameters.");
118 if (callable.parameters().length > 0 || callable.mandatoryPositionals() >= 0) {
119 int nbArgs =
120 callable.parameters().length
121 + Math.max(0, callable.mandatoryPositionals());
122 Preconditions.checkArgument(
123 nbArgs == method.getParameterTypes().length,
124 "Method "
125 + method
126 + " was annotated for "
127 + nbArgs
128 + " arguments "
129 + "but accept only "
130 + method.getParameterTypes().length
131 + " arguments.");
132 }
133 String name = callable.name();
134 if (name.isEmpty()) {
135 name = StringUtilities.toPythonStyleFunctionName(method.getName());
136 }
137 if (methodMap.containsKey(name)) {
138 methodMap.get(name).add(new MethodDescriptor(method, callable));
139 } else {
140 methodMap.put(
141 name, Lists.newArrayList(new MethodDescriptor(method, callable)));
142 }
143 }
144 return ImmutableMap.copyOf(methodMap);
145 }
146 });
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100147
148 /**
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000149 * Returns a map of methods and corresponding SkylarkCallable annotations of the methods of the
150 * classObj class reachable from Skylark.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100151 */
152 public static ImmutableMap<Method, SkylarkCallable> collectSkylarkMethodsWithAnnotation(
153 Class<?> classObj) {
154 ImmutableMap.Builder<Method, SkylarkCallable> methodMap = ImmutableMap.builder();
155 for (Method method : classObj.getMethods()) {
156 // Synthetic methods lead to false multiple matches
157 if (!method.isSynthetic()) {
158 SkylarkCallable annotation = getAnnotationFromParentClass(classObj, method);
159 if (annotation != null) {
160 methodMap.put(method, annotation);
161 }
162 }
163 }
164 return methodMap.build();
165 }
166
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000167 @Nullable
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100168 private static SkylarkCallable getAnnotationFromParentClass(Class<?> classObj, Method method) {
169 boolean keepLooking = false;
170 try {
171 Method superMethod = classObj.getMethod(method.getName(), method.getParameterTypes());
172 if (classObj.isAnnotationPresent(SkylarkModule.class)
173 && superMethod.isAnnotationPresent(SkylarkCallable.class)) {
174 return superMethod.getAnnotation(SkylarkCallable.class);
175 } else {
176 keepLooking = true;
177 }
178 } catch (NoSuchMethodException e) {
179 // The class might not have the specified method, so an exceptions is OK.
180 keepLooking = true;
181 }
182 if (keepLooking) {
183 if (classObj.getSuperclass() != null) {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000184 SkylarkCallable annotation =
185 getAnnotationFromParentClass(classObj.getSuperclass(), method);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100186 if (annotation != null) {
187 return annotation;
188 }
189 }
190 for (Class<?> interfaceObj : classObj.getInterfaces()) {
191 SkylarkCallable annotation = getAnnotationFromParentClass(interfaceObj, method);
192 if (annotation != null) {
193 return annotation;
194 }
195 }
196 }
197 return null;
198 }
199
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000200 private static class ArgumentListConversionResult {
201 private final ImmutableList<Object> arguments;
202 private final String error;
203
204 private ArgumentListConversionResult(ImmutableList<Object> arguments, String error) {
205 this.arguments = arguments;
206 this.error = error;
207 }
208
209 public static ArgumentListConversionResult fromArgumentList(ImmutableList<Object> arguments) {
210 return new ArgumentListConversionResult(arguments, null);
211 }
212
213 public static ArgumentListConversionResult fromError(String error) {
214 return new ArgumentListConversionResult(null, error);
215 }
216
217 public String getError() {
218 return error;
219 }
220
221 public ImmutableList<Object> getArguments() {
222 return arguments;
223 }
224 }
225
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100226 /**
227 * An exception class to handle exceptions in direct Java API calls.
228 */
229 public static final class FuncallException extends Exception {
230
231 public FuncallException(String msg) {
232 super(msg);
233 }
234 }
235
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000236 @Nullable private final Expression obj;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100237
Florian Weikert6f864c32015-07-23 11:26:39 +0000238 private final Identifier func;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100239
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000240 private final List<Argument.Passed> args;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100241
242 private final int numPositionalArgs;
243
244 /**
245 * Note: the grammar definition restricts the function value in a function
246 * call expression to be a global identifier; however, the representation of
247 * values in the interpreter is flexible enough to allow functions to be
248 * arbitrary expressions. In any case, the "func" expression is always
249 * evaluated, so functions and variables share a common namespace.
250 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000251 public FuncallExpression(@Nullable Expression obj, Identifier func,
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000252 List<Argument.Passed> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100253 this.obj = obj;
254 this.func = func;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000255 this.args = args; // we assume the parser validated it with Argument#validateFuncallArguments()
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100256 this.numPositionalArgs = countPositionalArguments();
257 }
258
259 /**
260 * Note: the grammar definition restricts the function value in a function
261 * call expression to be a global identifier; however, the representation of
262 * values in the interpreter is flexible enough to allow functions to be
263 * arbitrary expressions. In any case, the "func" expression is always
264 * evaluated, so functions and variables share a common namespace.
265 */
Florian Weikert6f864c32015-07-23 11:26:39 +0000266 public FuncallExpression(Identifier func, List<Argument.Passed> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100267 this(null, func, args);
268 }
269
270 /**
271 * Returns the number of positional arguments.
272 */
273 private int countPositionalArguments() {
274 int num = 0;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000275 for (Argument.Passed arg : args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100276 if (arg.isPositional()) {
277 num++;
278 }
279 }
280 return num;
281 }
282
283 /**
284 * Returns the function expression.
285 */
Florian Weikert6f864c32015-07-23 11:26:39 +0000286 public Identifier getFunction() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100287 return func;
288 }
289
290 /**
291 * Returns the object the function called on.
292 * It's null if the function is not called on an object.
293 */
294 public Expression getObject() {
295 return obj;
296 }
297
298 /**
299 * Returns an (immutable, ordered) list of function arguments. The first n are
300 * positional and the remaining ones are keyword args, where n =
301 * getNumPositionalArguments().
302 */
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000303 public List<Argument.Passed> getArguments() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100304 return Collections.unmodifiableList(args);
305 }
306
307 /**
308 * Returns the number of arguments which are positional; the remainder are
309 * keyword arguments.
310 */
311 public int getNumPositionalArguments() {
312 return numPositionalArgs;
313 }
314
Laurent Le Brun003bb882015-05-19 11:32:17 +0000315 private String functionName() {
Vladimir Moskva8d610c62016-09-15 14:36:41 +0000316 return "function " + func.getName();
Laurent Le Brun003bb882015-05-19 11:32:17 +0000317 }
318
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100319 @Override
320 public String toString() {
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000321 StringBuilder sb = new StringBuilder();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100322 if (obj != null) {
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000323 sb.append(obj).append(".");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100324 }
Florian Weikert90a15962015-09-11 13:43:10 +0000325 sb.append(func);
Florian Weikert2591e192015-10-05 14:24:51 +0000326 Printer.printList(sb, args, "(", ", ", ")", /* singletonTerminator */ null,
327 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT,
328 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH);
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000329 return sb.toString();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100330 }
331
332 /**
333 * Returns the list of Skylark callable Methods of objClass with the given name
334 * and argument number.
335 */
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000336 public static List<MethodDescriptor> getMethods(Class<?> objClass, String methodName,
Laurent Le Brun427bd972015-05-20 13:28:44 +0000337 Location loc) throws EvalException {
338 try {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000339 return methodCache.get(objClass).get(methodName);
Laurent Le Brun427bd972015-05-20 13:28:44 +0000340 } catch (ExecutionException e) {
341 throw new EvalException(loc, "Method invocation failed: " + e);
342 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100343 }
344
345 /**
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000346 * Returns a set of the Skylark name of all Skylark callable methods for object of type {@code
347 * objClass}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100348 */
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000349 public static Set<String> getMethodNames(Class<?> objClass) throws ExecutionException {
350 return methodCache.get(objClass).keySet();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100351 }
352
353 static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000354 Object[] args, Location loc, Environment env) throws EvalException {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000355 try {
356 Method method = methodDescriptor.getMethod();
357 if (obj == null && !Modifier.isStatic(method.getModifiers())) {
358 throw new EvalException(loc, "Method '" + methodName + "' is not static");
359 }
360 // This happens when the interface is public but the implementation classes
361 // have reduced visibility.
362 method.setAccessible(true);
363 Object result = method.invoke(obj, args);
364 if (method.getReturnType().equals(Void.TYPE)) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000365 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000366 }
367 if (result == null) {
368 if (methodDescriptor.getAnnotation().allowReturnNones()) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000369 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000370 } else {
371 throw new EvalException(loc,
372 "Method invocation returned None, please contact Skylark developers: " + methodName
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000373 + Printer.listString(ImmutableList.copyOf(args), "(", ", ", ")", null));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000374 }
375 }
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000376 // TODO(bazel-team): get rid of this, by having everyone use the Skylark data structures
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000377 result = SkylarkType.convertToSkylark(result, method, env);
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000378 if (result != null && !EvalUtils.isSkylarkAcceptable(result.getClass())) {
379 throw new EvalException(loc, Printer.format(
380 "Method '%s' returns an object of invalid type %r", methodName, result.getClass()));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000381 }
382 return result;
383 } catch (IllegalAccessException e) {
384 // TODO(bazel-team): Print a nice error message. Maybe the method exists
385 // and an argument is missing or has the wrong type.
386 throw new EvalException(loc, "Method invocation failed: " + e);
387 } catch (InvocationTargetException e) {
388 if (e.getCause() instanceof FuncallException) {
389 throw new EvalException(loc, e.getCause().getMessage());
390 } else if (e.getCause() != null) {
391 throw new EvalExceptionWithJavaCause(loc, e.getCause());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100392 } else {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000393 // This is unlikely to happen
394 throw new EvalException(loc, "Method invocation failed: " + e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100395 }
396 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100397 }
398
399 // TODO(bazel-team): If there's exactly one usable method, this works. If there are multiple
400 // matching methods, it still can be a problem. Figure out how the Java compiler does it
401 // exactly and copy that behaviour.
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000402 // Throws an EvalException when it cannot find a matching function.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000403 private Pair<MethodDescriptor, List<Object>> findJavaMethod(
404 Class<?> objClass, String methodName, List<Object> args, Map<String, Object> kwargs)
405 throws EvalException {
406 Pair<MethodDescriptor, List<Object>> matchingMethod = null;
407 List<MethodDescriptor> methods = getMethods(objClass, methodName, getLocation());
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000408 ArgumentListConversionResult argumentListConversionResult = null;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000409 if (methods != null) {
410 for (MethodDescriptor method : methods) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000411 if (!method.getAnnotation().structField()) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000412 argumentListConversionResult = convertArgumentList(args, kwargs, method);
413 if (argumentListConversionResult.getArguments() != null) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000414 if (matchingMethod == null) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000415 matchingMethod =
416 new Pair<MethodDescriptor, List<Object>>(
417 method, argumentListConversionResult.getArguments());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000418 } else {
419 throw new EvalException(
420 getLocation(),
421 String.format(
422 "Type %s has multiple matches for %s",
423 EvalUtils.getDataTypeNameFromClass(objClass), formatMethod(args, kwargs)));
424 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100425 }
426 }
427 }
Laurent Le Brun427bd972015-05-20 13:28:44 +0000428 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000429 if (matchingMethod == null) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000430 String errorMessage;
431 if (argumentListConversionResult == null || argumentListConversionResult.getError() == null) {
432 errorMessage =
433 String.format(
434 "Type %s has no %s",
435 EvalUtils.getDataTypeNameFromClass(objClass), formatMethod(args, kwargs));
436
437 } else {
438 errorMessage =
439 String.format(
440 "%s (in %s of %s).",
441 argumentListConversionResult.getError(),
442 formatMethod(args, kwargs),
443 EvalUtils.getDataTypeNameFromClass(objClass));
444 }
445 throw new EvalException(getLocation(), errorMessage);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100446 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000447 return matchingMethod;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100448 }
449
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000450 private static SkylarkType getType(Param param) {
451 SkylarkType type =
452 param.generic1() != Object.class
453 ? SkylarkType.of(param.type(), param.generic1())
454 : SkylarkType.of(param.type());
455 return type;
456 }
457
458 /**
459 * Constructs the parameters list to actually pass to the method, filling with default values if
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000460 * any. If the type does not match, returns null and fills in methodTypeMatchError with the
461 * appropriate message.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000462 */
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000463 private ArgumentListConversionResult convertArgumentList(
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000464 List<Object> args, Map<String, Object> kwargs, MethodDescriptor method) {
465 ImmutableList.Builder<Object> builder = ImmutableList.builder();
466 Class<?>[] params = method.getMethod().getParameterTypes();
467 SkylarkCallable callable = method.getAnnotation();
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000468 int mandatoryPositionals = callable.mandatoryPositionals();
469 if (mandatoryPositionals < 0) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000470 if (callable.parameters().length > 0) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000471 mandatoryPositionals = 0;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000472 } else {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000473 mandatoryPositionals = params.length;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000474 }
475 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000476 if (mandatoryPositionals > args.size()
477 || args.size() > mandatoryPositionals + callable.parameters().length) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000478 return ArgumentListConversionResult.fromError("Too many arguments");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000479 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000480 // First process the legacy positional parameters.
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000481 int i = 0;
482 if (mandatoryPositionals > 0) {
483 for (Class<?> param : params) {
484 Object value = args.get(i);
485 if (!param.isAssignableFrom(value.getClass())) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000486 return ArgumentListConversionResult.fromError(
487 String.format(
488 "Cannot convert parameter at position %d to type %s", i, param.toString()));
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000489 }
490 builder.add(value);
491 i++;
492 if (mandatoryPositionals >= 0 && i >= mandatoryPositionals) {
493 // Stops for specified parameters instead.
494 break;
495 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000496 }
497 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000498
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000499 // Then the parameters specified in callable.parameters()
500 Set<String> keys = new HashSet<>(kwargs.keySet());
501 for (Param param : callable.parameters()) {
502 SkylarkType type = getType(param);
Googlere5da53c2016-09-21 12:34:44 +0000503 if (param.noneable()) {
504 type = SkylarkType.Union.of(type, SkylarkType.NONE);
505 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000506 Object value = null;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000507 if (i < args.size()) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000508 value = args.get(i);
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000509 if (!param.positional()) {
510 return ArgumentListConversionResult.fromError(
511 String.format("Parameter '%s' is not positional", param.name()));
512 } else if (!type.contains(value)) {
513 return ArgumentListConversionResult.fromError(
514 String.format(
515 "Cannot convert parameter '%s' to type %s", param.name(), type.toString()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000516 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000517 i++;
518 } else if (param.named() && keys.remove(param.name())) {
519 // Named parameters
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000520 value = kwargs.get(param.name());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000521 if (!type.contains(value)) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000522 return ArgumentListConversionResult.fromError(
523 String.format(
524 "Cannot convert parameter '%s' to type %s", param.name(), type.toString()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000525 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000526 } else {
527 // Use default value
528 if (param.defaultValue().isEmpty()) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000529 return ArgumentListConversionResult.fromError(
530 String.format("Parameter '%s' has no default value", param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000531 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000532 value = SkylarkSignatureProcessor.getDefaultValue(param, null);
533 }
534 builder.add(value);
535 if (!param.noneable() && value instanceof NoneType) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000536 return ArgumentListConversionResult.fromError(
537 String.format("Parameter '%s' cannot be None", param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000538 }
539 }
540 if (i < args.size() || !keys.isEmpty()) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000541 return ArgumentListConversionResult.fromError("Too many arguments");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000542 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000543 return ArgumentListConversionResult.fromArgumentList(builder.build());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000544 }
545
546 private String formatMethod(List<Object> args, Map<String, Object> kwargs) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100547 StringBuilder sb = new StringBuilder();
Laurent Le Brun648f8f32015-09-09 19:46:29 +0000548 sb.append(functionName()).append("(");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100549 boolean first = true;
550 for (Object obj : args) {
551 if (!first) {
552 sb.append(", ");
553 }
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000554 sb.append(EvalUtils.getDataTypeName(obj));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100555 first = false;
556 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000557 for (Map.Entry<String, Object> kwarg : kwargs.entrySet()) {
558 if (!first) {
559 sb.append(", ");
560 }
561 sb.append(EvalUtils.getDataTypeName(kwarg.getValue()));
562 sb.append(" ");
563 sb.append(kwarg.getKey());
Googler344449d2016-09-15 18:25:35 +0000564 first = false;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000565 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100566 return sb.append(")").toString();
567 }
568
569 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000570 * A {@link StackManipulation} invoking addKeywordArg.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000571 *
Florian Weikert33f819b2015-11-09 18:15:55 +0000572 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100573 */
Florian Weikert33f819b2015-11-09 18:15:55 +0000574 private static final StackManipulation addKeywordArg =
575 ByteCodeUtils.invoke(
576 FuncallExpression.class,
577 "addKeywordArg",
578 Map.class,
579 String.class,
580 Object.class,
581 ImmutableList.Builder.class);
582
583 /**
584 * Add one argument to the keyword map, registering a duplicate in case of conflict.
585 *
586 * <p>public for reflection by the compiler and calls from compiled functions
587 */
588 public static void addKeywordArg(
589 Map<String, Object> kwargs,
590 String name,
591 Object value,
592 ImmutableList.Builder<String> duplicates) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100593 if (kwargs.put(name, value) != null) {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000594 duplicates.add(name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100595 }
596 }
597
598 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000599 * A {@link StackManipulation} invoking addKeywordArgs.
600 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100601 */
Florian Weikert33f819b2015-11-09 18:15:55 +0000602 private static final StackManipulation addKeywordArgs =
603 ByteCodeUtils.invoke(
604 FuncallExpression.class,
605 "addKeywordArgs",
606 Map.class,
607 Object.class,
608 ImmutableList.Builder.class,
609 Location.class);
610
611 /**
612 * Add multiple arguments to the keyword map (**kwargs), registering duplicates
613 *
614 * <p>public for reflection by the compiler and calls from compiled functions
615 */
616 public static void addKeywordArgs(
617 Map<String, Object> kwargs,
618 Object items,
619 ImmutableList.Builder<String> duplicates,
620 Location location)
621 throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100622 if (!(items instanceof Map<?, ?>)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000623 throw new EvalException(
624 location,
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000625 "Argument after ** must be a dictionary, not " + EvalUtils.getDataTypeName(items));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100626 }
627 for (Map.Entry<?, ?> entry : ((Map<?, ?>) items).entrySet()) {
628 if (!(entry.getKey() instanceof String)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000629 throw new EvalException(
630 location, "Keywords must be strings, not " + EvalUtils.getDataTypeName(entry.getKey()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100631 }
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000632 addKeywordArg(kwargs, (String) entry.getKey(), entry.getValue(), duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100633 }
634 }
635
Florian Weikert33f819b2015-11-09 18:15:55 +0000636 /**
637 * A {@link StackManipulation} invoking checkCallable.
638 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
639 */
640 private static final StackManipulation checkCallable =
641 ByteCodeUtils.invoke(FuncallExpression.class, "checkCallable", Object.class, Location.class);
642
643 /**
644 * Checks whether the given object is a {@link BaseFunction}.
645 *
646 * <p>Public for reflection by the compiler and access from generated byte code.
647 *
648 * @throws EvalException If not a BaseFunction.
649 */
650 public static BaseFunction checkCallable(Object functionValue, Location location)
651 throws EvalException {
652 if (functionValue instanceof BaseFunction) {
653 return (BaseFunction) functionValue;
654 } else {
655 throw new EvalException(
656 location, "'" + EvalUtils.getDataTypeName(functionValue) + "' object is not callable");
657 }
658 }
659
660 /**
661 * A {@link StackManipulation} invoking checkDuplicates.
662 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
663 */
664 private static final StackManipulation checkDuplicates =
665 ByteCodeUtils.invoke(
666 FuncallExpression.class,
667 "checkDuplicates",
668 ImmutableList.Builder.class,
669 String.class,
670 Location.class);
671
672 /**
673 * Check the list from the builder and report an {@link EvalException} if not empty.
674 *
675 * <p>public for reflection by the compiler and calls from compiled functions
676 */
677 public static void checkDuplicates(
678 ImmutableList.Builder<String> duplicates, String function, Location location)
679 throws EvalException {
680 List<String> dups = duplicates.build();
681 if (!dups.isEmpty()) {
682 throw new EvalException(
683 location,
684 "duplicate keyword"
685 + (dups.size() > 1 ? "s" : "")
686 + " '"
687 + Joiner.on("', '").join(dups)
688 + "' in call to "
689 + function);
690 }
691 }
692
693 /**
694 * A {@link StackManipulation} invoking invokeObjectMethod.
695 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
696 */
697 private static final StackManipulation invokeObjectMethod =
698 ByteCodeUtils.invoke(
699 FuncallExpression.class,
700 "invokeObjectMethod",
701 String.class,
702 ImmutableList.class,
703 ImmutableMap.class,
704 FuncallExpression.class,
705 Environment.class);
706
707 /**
708 * Call a method depending on the type of an object it is called on.
709 *
710 * <p>Public for reflection by the compiler and access from generated byte code.
711 *
712 * @param positionals The first object is expected to be the object the method is called on.
713 * @param call the original expression that caused this call, needed for rules especially
714 */
715 public static Object invokeObjectMethod(
716 String method,
717 ImmutableList<Object> positionals,
718 ImmutableMap<String, Object> keyWordArgs,
719 FuncallExpression call,
720 Environment env)
721 throws EvalException, InterruptedException {
722 Location location = call.getLocation();
723 Object value = positionals.get(0);
724 ImmutableList<Object> positionalArgs = positionals.subList(1, positionals.size());
725 BaseFunction function = Runtime.getFunction(EvalUtils.getSkylarkType(value.getClass()), method);
726 if (function != null) {
727 if (!isNamespace(value.getClass())) {
728 // Use self as an implicit parameter in front.
729 positionalArgs = positionals;
730 }
731 return function.call(
732 positionalArgs, ImmutableMap.<String, Object>copyOf(keyWordArgs), call, env);
733 } else if (value instanceof ClassObject) {
734 Object fieldValue = ((ClassObject) value).getValue(method);
735 if (fieldValue == null) {
736 throw new EvalException(location, String.format("struct has no method '%s'", method));
737 }
738 if (!(fieldValue instanceof BaseFunction)) {
739 throw new EvalException(
740 location, String.format("struct field '%s' is not a function", method));
741 }
742 function = (BaseFunction) fieldValue;
743 return function.call(
744 positionalArgs, ImmutableMap.<String, Object>copyOf(keyWordArgs), call, env);
Laurent Le Brun88df1f52015-12-23 13:31:44 +0000745 } else {
Florian Weikert33f819b2015-11-09 18:15:55 +0000746 // When calling a Java method, the name is not in the Environment,
747 // so evaluating 'func' would fail.
748 Class<?> objClass;
749 Object obj;
750 if (value instanceof Class<?>) {
751 // Static call
752 obj = null;
753 objClass = (Class<?>) value;
754 } else {
755 obj = value;
756 objClass = value.getClass();
757 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000758 Pair<MethodDescriptor, List<Object>> javaMethod =
759 call.findJavaMethod(objClass, method, positionalArgs, keyWordArgs);
760 return callMethod(javaMethod.first, method, obj, javaMethod.second.toArray(), location, env);
Florian Weikert33f819b2015-11-09 18:15:55 +0000761 }
762 }
763
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000764 @SuppressWarnings("unchecked")
765 private void evalArguments(ImmutableList.Builder<Object> posargs, Map<String, Object> kwargs,
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000766 Environment env)
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000767 throws EvalException, InterruptedException {
Ulf Adams07dba942015-03-05 14:47:37 +0000768 ImmutableList.Builder<String> duplicates = new ImmutableList.Builder<>();
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000769 // Iterate over the arguments. We assume all positional arguments come before any keyword
770 // or star arguments, because the argument list was already validated by
771 // Argument#validateFuncallArguments, as called by the Parser,
772 // which should be the only place that build FuncallExpression-s.
773 for (Argument.Passed arg : args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100774 Object value = arg.getValue().eval(env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100775 if (arg.isPositional()) {
776 posargs.add(value);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000777 } else if (arg.isStar()) { // expand the starArg
778 if (value instanceof Iterable) {
779 posargs.addAll((Iterable<Object>) value);
780 }
781 } else if (arg.isStarStar()) { // expand the kwargs
Florian Weikert33f819b2015-11-09 18:15:55 +0000782 addKeywordArgs(kwargs, value, duplicates, getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100783 } else {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000784 addKeywordArg(kwargs, arg.getName(), value, duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100785 }
786 }
Florian Weikert33f819b2015-11-09 18:15:55 +0000787 checkDuplicates(duplicates, func.getName(), getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100788 }
789
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000790 @VisibleForTesting
791 public static boolean isNamespace(Class<?> classObject) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100792 return classObject.isAnnotationPresent(SkylarkModule.class)
793 && classObject.getAnnotation(SkylarkModule.class).namespace();
794 }
795
796 @Override
Florian Weikert90a15962015-09-11 13:43:10 +0000797 Object doEval(Environment env) throws EvalException, InterruptedException {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000798 return (obj != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000799 }
800
801 /**
802 * Invokes obj.func() and returns the result.
803 */
804 private Object invokeObjectMethod(Environment env) throws EvalException, InterruptedException {
805 Object objValue = obj.eval(env);
Ulf Adams07dba942015-03-05 14:47:37 +0000806 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000807 posargs.add(objValue);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000808 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
809 // we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
Pedro Liberal Fernandez837dbc12016-08-18 14:13:01 +0000810 Map<String, Object> kwargs = new LinkedHashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000811 evalArguments(posargs, kwargs, env);
812 return invokeObjectMethod(
813 func.getName(), posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000814 }
Googlera34d5072015-03-05 13:51:28 +0000815
Florian Weikert3f610e82015-08-18 14:37:46 +0000816 /**
817 * Invokes func() and returns the result.
818 */
819 private Object invokeGlobalFunction(Environment env) throws EvalException, InterruptedException {
820 Object funcValue = func.eval(env);
821 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
822 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
823 // we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
824 Map<String, Object> kwargs = new HashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000825 BaseFunction function = checkCallable(funcValue, getLocation());
826 evalArguments(posargs, kwargs, env);
827 return function.call(posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000828 }
829
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000830 /**
831 * Returns the value of the argument 'name' (or null if there is none).
832 * This function is used to associate debugging information to rules created by skylark "macros".
833 */
834 @Nullable
835 public String getNameArg() {
836 for (Argument.Passed arg : args) {
837 if (arg != null) {
838 String name = arg.getName();
839 if (name != null && name.equals("name")) {
840 Expression expr = arg.getValue();
841 return (expr != null && expr instanceof StringLiteral)
842 ? ((StringLiteral) expr).getValue() : null;
843 }
844 }
845 }
846 return null;
847 }
848
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100849 @Override
850 public void accept(SyntaxTreeVisitor visitor) {
851 visitor.visit(this);
852 }
853
854 @Override
Laurent Le Brun2e78d612015-04-15 09:06:46 +0000855 void validate(ValidationEnvironment env) throws EvalException {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000856 for (Argument.Passed arg : args) {
Laurent Le Brun386b90c2015-02-11 16:34:08 +0000857 arg.getValue().validate(env);
858 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100859
860 if (obj != null) {
Laurent Le Brun964d8d52015-04-13 12:15:04 +0000861 obj.validate(env);
862 } else if (!env.hasSymbolInEnvironment(func.getName())) {
863 throw new EvalException(getLocation(),
864 String.format("function '%s' does not exist", func.getName()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100865 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100866 }
Florian Weikert90a15962015-09-11 13:43:10 +0000867
868 @Override
869 protected boolean isNewScope() {
870 return true;
871 }
Florian Weikert33f819b2015-11-09 18:15:55 +0000872
873 @Override
874 ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo) throws EvalException {
875 AstAccessors debugAccessors = debugInfo.add(this);
876 List<ByteCodeAppender> code = new ArrayList<>();
877 if (obj != null) {
878 compileObjectMethodCall(scope, debugInfo, debugAccessors, code);
879 } else {
880 compileGlobalFunctionCall(scope, debugInfo, debugAccessors, code);
881 }
882 return ByteCodeUtils.compoundAppender(code);
883 }
884
885 /**
886 * Add code that compiles the argument expressions.
887 *
888 * <p>The byte code leaves the arguments on the stack in order of:
889 * positional arguments, key word arguments, this FuncallExpression, Environment
890 * This is the order required by {@link #invokeObjectMethod} and
891 * {@link BaseFunction#call(List, Map, FuncallExpression, Environment)}.
892 */
893 private void compileArguments(
894 VariableScope scope,
895 DebugInfo debugInfo,
896 AstAccessors debugAccessors,
897 List<ByteCodeAppender> code)
898 throws EvalException {
899 InternalVariable positionalsBuilder = scope.freshVariable(ImmutableList.Builder.class);
900 append(code, ByteCodeMethodCalls.BCImmutableList.builder);
901 code.add(positionalsBuilder.store());
902
903 InternalVariable keyWordArgs = scope.freshVariable(Map.class);
904 append(code, NewObject.fromConstructor(HashMap.class).arguments());
905 code.add(keyWordArgs.store());
906
907 InternalVariable duplicatesBuilder =
908 scope.freshVariable(new TypeDescription.ForLoadedType(ImmutableList.Builder.class));
909 append(code, ByteCodeMethodCalls.BCImmutableList.builder);
910 code.add(duplicatesBuilder.store());
911
912 StackManipulation builderAdd =
913 new StackManipulation.Compound(
914 ByteCodeMethodCalls.BCImmutableList.Builder.add, Removal.SINGLE);
915
916 // add an object the function is called on first
917 if (obj != null) {
918 append(code, positionalsBuilder.load());
919 code.add(obj.compile(scope, debugInfo));
920 append(code, builderAdd);
921 }
922 // add all arguments to their respective builder/map
923 for (Argument.Passed arg : args) {
924 ByteCodeAppender value = arg.getValue().compile(scope, debugInfo);
925 if (arg.isPositional()) {
926 append(code, positionalsBuilder.load());
927 code.add(value);
928 append(code, builderAdd);
929 } else if (arg.isStar()) {
930 // expand the starArg by adding all it's elements to the builder
931 append(code, positionalsBuilder.load());
932 code.add(value);
933 append(
934 code,
935 TypeCasting.to(new TypeDescription.ForLoadedType(Iterable.class)),
936 ByteCodeMethodCalls.BCImmutableList.Builder.addAll,
937 Removal.SINGLE);
938 } else if (arg.isStarStar()) {
939 append(code, keyWordArgs.load());
940 code.add(value);
941 append(code, duplicatesBuilder.load(), debugAccessors.loadLocation, addKeywordArgs);
942 } else {
943 append(code, keyWordArgs.load(), new TextConstant(arg.getName()));
944 code.add(value);
945 append(code, duplicatesBuilder.load(), addKeywordArg);
946 }
947 }
948 append(
949 code,
950 // check for duplicates in the key word arguments
951 duplicatesBuilder.load(),
952 new TextConstant(func.getName()),
953 debugAccessors.loadLocation,
954 checkDuplicates,
955 // load the arguments in the correct order for invokeObjectMethod and BaseFunction.call
956 positionalsBuilder.load(),
957 ByteCodeMethodCalls.BCImmutableList.Builder.build,
958 keyWordArgs.load(),
959 ByteCodeMethodCalls.BCImmutableMap.copyOf,
960 debugAccessors.loadAstNode,
961 TypeCasting.to(new TypeDescription.ForLoadedType(FuncallExpression.class)),
962 scope.loadEnvironment());
963 }
964
965 private void compileObjectMethodCall(
966 VariableScope scope,
967 DebugInfo debugInfo,
968 AstAccessors debugAccessors,
969 List<ByteCodeAppender> code)
970 throws EvalException {
971 append(code, new TextConstant(func.getName()));
972 compileArguments(scope, debugInfo, debugAccessors, code);
973 append(code, invokeObjectMethod);
974 }
975
976 private void compileGlobalFunctionCall(
977 VariableScope scope,
978 DebugInfo debugInfo,
979 AstAccessors debugAccessors,
980 List<ByteCodeAppender> code)
981 throws EvalException {
982 code.add(func.compile(scope, debugInfo));
983 append(code, debugAccessors.loadLocation, checkCallable);
984 compileArguments(scope, debugInfo, debugAccessors, code);
985 append(code, BaseFunction.call);
986 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100987}