blob: 5748f63bdba8fafa27cf46841a9b7c652b649f66 [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;
Jon Brandvein6696db22016-10-04 20:18:19 +000030import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
John Field585d1a02015-12-16 16:03:52 +000031import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010032import com.google.devtools.build.lib.syntax.EvalException.EvalExceptionWithJavaCause;
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +000033import com.google.devtools.build.lib.syntax.Runtime.NoneType;
Florian Weikert33f819b2015-11-09 18:15:55 +000034import com.google.devtools.build.lib.syntax.compiler.ByteCodeMethodCalls;
35import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils;
36import com.google.devtools.build.lib.syntax.compiler.DebugInfo;
37import com.google.devtools.build.lib.syntax.compiler.DebugInfo.AstAccessors;
38import com.google.devtools.build.lib.syntax.compiler.NewObject;
39import com.google.devtools.build.lib.syntax.compiler.Variable.InternalVariable;
40import com.google.devtools.build.lib.syntax.compiler.VariableScope;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000041import com.google.devtools.build.lib.util.Pair;
42import com.google.devtools.build.lib.util.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043import com.google.devtools.build.lib.util.StringUtilities;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044import java.lang.reflect.InvocationTargetException;
45import java.lang.reflect.Method;
46import java.lang.reflect.Modifier;
47import java.util.ArrayList;
48import java.util.Collections;
49import java.util.HashMap;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000050import java.util.HashSet;
Pedro Liberal Fernandez837dbc12016-08-18 14:13:01 +000051import java.util.LinkedHashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010052import java.util.List;
53import java.util.Map;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000054import java.util.Set;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010055import java.util.concurrent.ExecutionException;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000056import javax.annotation.Nullable;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000057import net.bytebuddy.description.type.TypeDescription;
58import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
59import net.bytebuddy.implementation.bytecode.Removal;
60import net.bytebuddy.implementation.bytecode.StackManipulation;
61import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
62import net.bytebuddy.implementation.bytecode.constant.TextConstant;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000063
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010064/**
65 * Syntax node for a function call expression.
66 */
67public final class FuncallExpression extends Expression {
68
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010069 /**
70 * A value class to store Methods with their corresponding SkylarkCallable annotations.
71 * This is needed because the annotation is sometimes in a superclass.
72 */
73 public static final class MethodDescriptor {
74 private final Method method;
75 private final SkylarkCallable annotation;
76
77 private MethodDescriptor(Method method, SkylarkCallable annotation) {
78 this.method = method;
79 this.annotation = annotation;
80 }
81
82 Method getMethod() {
83 return method;
84 }
85
86 /**
87 * Returns the SkylarkCallable annotation corresponding to this method.
88 */
89 public SkylarkCallable getAnnotation() {
90 return annotation;
91 }
92 }
93
94 private static final LoadingCache<Class<?>, Map<String, List<MethodDescriptor>>> methodCache =
95 CacheBuilder.newBuilder()
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000096 .initialCapacity(10)
97 .maximumSize(100)
98 .build(
99 new CacheLoader<Class<?>, Map<String, List<MethodDescriptor>>>() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100100
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000101 @Override
102 public Map<String, List<MethodDescriptor>> load(Class<?> key) throws Exception {
103 Map<String, List<MethodDescriptor>> methodMap = new HashMap<>();
104 for (Method method : key.getMethods()) {
105 // Synthetic methods lead to false multiple matches
106 if (method.isSynthetic()) {
107 continue;
108 }
Jon Brandvein6696db22016-10-04 20:18:19 +0000109 SkylarkCallable callable = SkylarkInterfaceUtils.getSkylarkCallable(method);
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000110 if (callable == null) {
111 continue;
112 }
113 Preconditions.checkArgument(
114 callable.parameters().length == 0 || !callable.structField(),
115 "Method "
116 + method
Jon Brandveinead58ae2016-09-29 18:41:10 +0000117 + " was annotated with both structField and parameters.");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000118 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()) {
Jon Brandvein6696db22016-10-04 20:18:19 +0000158 SkylarkCallable annotation = SkylarkInterfaceUtils.getSkylarkCallable(classObj, method);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100159 if (annotation != null) {
160 methodMap.put(method, annotation);
161 }
162 }
163 }
164 return methodMap.build();
165 }
166
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000167 private static class ArgumentListConversionResult {
168 private final ImmutableList<Object> arguments;
169 private final String error;
170
171 private ArgumentListConversionResult(ImmutableList<Object> arguments, String error) {
172 this.arguments = arguments;
173 this.error = error;
174 }
175
176 public static ArgumentListConversionResult fromArgumentList(ImmutableList<Object> arguments) {
177 return new ArgumentListConversionResult(arguments, null);
178 }
179
180 public static ArgumentListConversionResult fromError(String error) {
181 return new ArgumentListConversionResult(null, error);
182 }
183
184 public String getError() {
185 return error;
186 }
187
188 public ImmutableList<Object> getArguments() {
189 return arguments;
190 }
191 }
192
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100193 /**
194 * An exception class to handle exceptions in direct Java API calls.
195 */
196 public static final class FuncallException extends Exception {
197
198 public FuncallException(String msg) {
199 super(msg);
200 }
201 }
202
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000203 @Nullable private final Expression obj;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100204
Florian Weikert6f864c32015-07-23 11:26:39 +0000205 private final Identifier func;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100206
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000207 private final List<Argument.Passed> args;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100208
209 private final int numPositionalArgs;
210
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000211 public FuncallExpression(@Nullable Expression obj, Identifier func,
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000212 List<Argument.Passed> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100213 this.obj = obj;
214 this.func = func;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000215 this.args = args; // we assume the parser validated it with Argument#validateFuncallArguments()
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100216 this.numPositionalArgs = countPositionalArguments();
217 }
218
Florian Weikert6f864c32015-07-23 11:26:39 +0000219 public FuncallExpression(Identifier func, List<Argument.Passed> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100220 this(null, func, args);
221 }
222
223 /**
224 * Returns the number of positional arguments.
225 */
226 private int countPositionalArguments() {
227 int num = 0;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000228 for (Argument.Passed arg : args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100229 if (arg.isPositional()) {
230 num++;
231 }
232 }
233 return num;
234 }
235
236 /**
237 * Returns the function expression.
238 */
Florian Weikert6f864c32015-07-23 11:26:39 +0000239 public Identifier getFunction() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100240 return func;
241 }
242
243 /**
244 * Returns the object the function called on.
245 * It's null if the function is not called on an object.
246 */
247 public Expression getObject() {
248 return obj;
249 }
250
251 /**
252 * Returns an (immutable, ordered) list of function arguments. The first n are
253 * positional and the remaining ones are keyword args, where n =
254 * getNumPositionalArguments().
255 */
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000256 public List<Argument.Passed> getArguments() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100257 return Collections.unmodifiableList(args);
258 }
259
260 /**
261 * Returns the number of arguments which are positional; the remainder are
262 * keyword arguments.
263 */
264 public int getNumPositionalArguments() {
265 return numPositionalArgs;
266 }
267
Laurent Le Brun003bb882015-05-19 11:32:17 +0000268 private String functionName() {
Vladimir Moskva8d610c62016-09-15 14:36:41 +0000269 return "function " + func.getName();
Laurent Le Brun003bb882015-05-19 11:32:17 +0000270 }
271
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100272 @Override
273 public String toString() {
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000274 StringBuilder sb = new StringBuilder();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100275 if (obj != null) {
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000276 sb.append(obj).append(".");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100277 }
Florian Weikert90a15962015-09-11 13:43:10 +0000278 sb.append(func);
Florian Weikert2591e192015-10-05 14:24:51 +0000279 Printer.printList(sb, args, "(", ", ", ")", /* singletonTerminator */ null,
280 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT,
281 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH);
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000282 return sb.toString();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100283 }
284
285 /**
286 * Returns the list of Skylark callable Methods of objClass with the given name
287 * and argument number.
288 */
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000289 public static List<MethodDescriptor> getMethods(Class<?> objClass, String methodName,
Laurent Le Brun427bd972015-05-20 13:28:44 +0000290 Location loc) throws EvalException {
291 try {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000292 return methodCache.get(objClass).get(methodName);
Laurent Le Brun427bd972015-05-20 13:28:44 +0000293 } catch (ExecutionException e) {
294 throw new EvalException(loc, "Method invocation failed: " + e);
295 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100296 }
297
298 /**
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000299 * Returns a set of the Skylark name of all Skylark callable methods for object of type {@code
300 * objClass}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100301 */
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000302 public static Set<String> getMethodNames(Class<?> objClass) throws ExecutionException {
303 return methodCache.get(objClass).keySet();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100304 }
305
306 static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000307 Object[] args, Location loc, Environment env) throws EvalException {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000308 try {
309 Method method = methodDescriptor.getMethod();
310 if (obj == null && !Modifier.isStatic(method.getModifiers())) {
311 throw new EvalException(loc, "Method '" + methodName + "' is not static");
312 }
313 // This happens when the interface is public but the implementation classes
314 // have reduced visibility.
315 method.setAccessible(true);
316 Object result = method.invoke(obj, args);
317 if (method.getReturnType().equals(Void.TYPE)) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000318 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000319 }
320 if (result == null) {
321 if (methodDescriptor.getAnnotation().allowReturnNones()) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000322 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000323 } else {
324 throw new EvalException(loc,
325 "Method invocation returned None, please contact Skylark developers: " + methodName
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000326 + Printer.listString(ImmutableList.copyOf(args), "(", ", ", ")", null));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000327 }
328 }
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000329 // TODO(bazel-team): get rid of this, by having everyone use the Skylark data structures
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000330 result = SkylarkType.convertToSkylark(result, method, env);
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000331 if (result != null && !EvalUtils.isSkylarkAcceptable(result.getClass())) {
332 throw new EvalException(loc, Printer.format(
333 "Method '%s' returns an object of invalid type %r", methodName, result.getClass()));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000334 }
335 return result;
336 } catch (IllegalAccessException e) {
337 // TODO(bazel-team): Print a nice error message. Maybe the method exists
338 // and an argument is missing or has the wrong type.
339 throw new EvalException(loc, "Method invocation failed: " + e);
340 } catch (InvocationTargetException e) {
341 if (e.getCause() instanceof FuncallException) {
342 throw new EvalException(loc, e.getCause().getMessage());
343 } else if (e.getCause() != null) {
344 throw new EvalExceptionWithJavaCause(loc, e.getCause());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100345 } else {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000346 // This is unlikely to happen
347 throw new EvalException(loc, "Method invocation failed: " + e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100348 }
349 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100350 }
351
352 // TODO(bazel-team): If there's exactly one usable method, this works. If there are multiple
353 // matching methods, it still can be a problem. Figure out how the Java compiler does it
354 // exactly and copy that behaviour.
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000355 // Throws an EvalException when it cannot find a matching function.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000356 private Pair<MethodDescriptor, List<Object>> findJavaMethod(
357 Class<?> objClass, String methodName, List<Object> args, Map<String, Object> kwargs)
358 throws EvalException {
359 Pair<MethodDescriptor, List<Object>> matchingMethod = null;
360 List<MethodDescriptor> methods = getMethods(objClass, methodName, getLocation());
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000361 ArgumentListConversionResult argumentListConversionResult = null;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000362 if (methods != null) {
363 for (MethodDescriptor method : methods) {
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000364 if (method.getAnnotation().structField()) {
365 return new Pair<>(method, null);
366 } else {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000367 argumentListConversionResult = convertArgumentList(args, kwargs, method);
368 if (argumentListConversionResult.getArguments() != null) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000369 if (matchingMethod == null) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000370 matchingMethod =
371 new Pair<MethodDescriptor, List<Object>>(
372 method, argumentListConversionResult.getArguments());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000373 } else {
374 throw new EvalException(
375 getLocation(),
376 String.format(
377 "Type %s has multiple matches for %s",
378 EvalUtils.getDataTypeNameFromClass(objClass), formatMethod(args, kwargs)));
379 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100380 }
381 }
382 }
Laurent Le Brun427bd972015-05-20 13:28:44 +0000383 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000384 if (matchingMethod == null) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000385 String errorMessage;
386 if (argumentListConversionResult == null || argumentListConversionResult.getError() == null) {
387 errorMessage =
388 String.format(
389 "Type %s has no %s",
390 EvalUtils.getDataTypeNameFromClass(objClass), formatMethod(args, kwargs));
391
392 } else {
393 errorMessage =
394 String.format(
395 "%s (in %s of %s).",
396 argumentListConversionResult.getError(),
397 formatMethod(args, kwargs),
398 EvalUtils.getDataTypeNameFromClass(objClass));
399 }
400 throw new EvalException(getLocation(), errorMessage);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100401 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000402 return matchingMethod;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100403 }
404
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000405 private static SkylarkType getType(Param param) {
406 SkylarkType type =
407 param.generic1() != Object.class
408 ? SkylarkType.of(param.type(), param.generic1())
409 : SkylarkType.of(param.type());
410 return type;
411 }
412
413 /**
414 * Constructs the parameters list to actually pass to the method, filling with default values if
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000415 * any. If there is a type or argument mismatch, returns a result containing an error message.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000416 */
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000417 private ArgumentListConversionResult convertArgumentList(
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000418 List<Object> args, Map<String, Object> kwargs, MethodDescriptor method) {
419 ImmutableList.Builder<Object> builder = ImmutableList.builder();
420 Class<?>[] params = method.getMethod().getParameterTypes();
421 SkylarkCallable callable = method.getAnnotation();
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000422 int mandatoryPositionals = callable.mandatoryPositionals();
423 if (mandatoryPositionals < 0) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000424 if (callable.parameters().length > 0) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000425 mandatoryPositionals = 0;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000426 } else {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000427 mandatoryPositionals = params.length;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000428 }
429 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000430 if (mandatoryPositionals > args.size()
431 || args.size() > mandatoryPositionals + callable.parameters().length) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000432 return ArgumentListConversionResult.fromError("Too many arguments");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000433 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000434 // First process the legacy positional parameters.
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000435 int i = 0;
436 if (mandatoryPositionals > 0) {
437 for (Class<?> param : params) {
438 Object value = args.get(i);
439 if (!param.isAssignableFrom(value.getClass())) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000440 return ArgumentListConversionResult.fromError(
441 String.format(
Carmi Grushko12291602016-10-26 19:07:41 +0000442 "Cannot convert parameter at position %d from type %s to type %s",
443 i, EvalUtils.getDataTypeName(value), param.toString()));
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000444 }
445 builder.add(value);
446 i++;
447 if (mandatoryPositionals >= 0 && i >= mandatoryPositionals) {
448 // Stops for specified parameters instead.
449 break;
450 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000451 }
452 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000453
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000454 // Then the parameters specified in callable.parameters()
455 Set<String> keys = new HashSet<>(kwargs.keySet());
456 for (Param param : callable.parameters()) {
457 SkylarkType type = getType(param);
Googlere5da53c2016-09-21 12:34:44 +0000458 if (param.noneable()) {
459 type = SkylarkType.Union.of(type, SkylarkType.NONE);
460 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000461 Object value = null;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000462 if (i < args.size()) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000463 value = args.get(i);
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000464 if (!param.positional()) {
465 return ArgumentListConversionResult.fromError(
466 String.format("Parameter '%s' is not positional", param.name()));
467 } else if (!type.contains(value)) {
468 return ArgumentListConversionResult.fromError(
469 String.format(
470 "Cannot convert parameter '%s' to type %s", param.name(), type.toString()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000471 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000472 i++;
473 } else if (param.named() && keys.remove(param.name())) {
474 // Named parameters
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000475 value = kwargs.get(param.name());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000476 if (!type.contains(value)) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000477 return ArgumentListConversionResult.fromError(
478 String.format(
479 "Cannot convert parameter '%s' to type %s", param.name(), type.toString()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000480 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000481 } else {
482 // Use default value
483 if (param.defaultValue().isEmpty()) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000484 return ArgumentListConversionResult.fromError(
485 String.format("Parameter '%s' has no default value", param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000486 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000487 value = SkylarkSignatureProcessor.getDefaultValue(param, null);
488 }
489 builder.add(value);
490 if (!param.noneable() && value instanceof NoneType) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000491 return ArgumentListConversionResult.fromError(
492 String.format("Parameter '%s' cannot be None", param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000493 }
494 }
495 if (i < args.size() || !keys.isEmpty()) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000496 return ArgumentListConversionResult.fromError("Too many arguments");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000497 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000498 return ArgumentListConversionResult.fromArgumentList(builder.build());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000499 }
500
501 private String formatMethod(List<Object> args, Map<String, Object> kwargs) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100502 StringBuilder sb = new StringBuilder();
Laurent Le Brun648f8f32015-09-09 19:46:29 +0000503 sb.append(functionName()).append("(");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100504 boolean first = true;
505 for (Object obj : args) {
506 if (!first) {
507 sb.append(", ");
508 }
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000509 sb.append(EvalUtils.getDataTypeName(obj));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100510 first = false;
511 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000512 for (Map.Entry<String, Object> kwarg : kwargs.entrySet()) {
513 if (!first) {
514 sb.append(", ");
515 }
516 sb.append(EvalUtils.getDataTypeName(kwarg.getValue()));
517 sb.append(" ");
518 sb.append(kwarg.getKey());
Googler344449d2016-09-15 18:25:35 +0000519 first = false;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000520 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100521 return sb.append(")").toString();
522 }
523
524 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000525 * A {@link StackManipulation} invoking addKeywordArg.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000526 *
Florian Weikert33f819b2015-11-09 18:15:55 +0000527 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100528 */
Florian Weikert33f819b2015-11-09 18:15:55 +0000529 private static final StackManipulation addKeywordArg =
530 ByteCodeUtils.invoke(
531 FuncallExpression.class,
532 "addKeywordArg",
533 Map.class,
534 String.class,
535 Object.class,
536 ImmutableList.Builder.class);
537
538 /**
539 * Add one argument to the keyword map, registering a duplicate in case of conflict.
540 *
541 * <p>public for reflection by the compiler and calls from compiled functions
542 */
543 public static void addKeywordArg(
544 Map<String, Object> kwargs,
545 String name,
546 Object value,
547 ImmutableList.Builder<String> duplicates) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100548 if (kwargs.put(name, value) != null) {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000549 duplicates.add(name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100550 }
551 }
552
553 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000554 * A {@link StackManipulation} invoking addKeywordArgs.
555 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100556 */
Florian Weikert33f819b2015-11-09 18:15:55 +0000557 private static final StackManipulation addKeywordArgs =
558 ByteCodeUtils.invoke(
559 FuncallExpression.class,
560 "addKeywordArgs",
561 Map.class,
562 Object.class,
563 ImmutableList.Builder.class,
564 Location.class);
565
566 /**
567 * Add multiple arguments to the keyword map (**kwargs), registering duplicates
568 *
569 * <p>public for reflection by the compiler and calls from compiled functions
570 */
571 public static void addKeywordArgs(
572 Map<String, Object> kwargs,
573 Object items,
574 ImmutableList.Builder<String> duplicates,
575 Location location)
576 throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100577 if (!(items instanceof Map<?, ?>)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000578 throw new EvalException(
579 location,
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000580 "Argument after ** must be a dictionary, not " + EvalUtils.getDataTypeName(items));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100581 }
582 for (Map.Entry<?, ?> entry : ((Map<?, ?>) items).entrySet()) {
583 if (!(entry.getKey() instanceof String)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000584 throw new EvalException(
585 location, "Keywords must be strings, not " + EvalUtils.getDataTypeName(entry.getKey()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100586 }
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000587 addKeywordArg(kwargs, (String) entry.getKey(), entry.getValue(), duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100588 }
589 }
590
Florian Weikert33f819b2015-11-09 18:15:55 +0000591 /**
592 * A {@link StackManipulation} invoking checkCallable.
593 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
594 */
595 private static final StackManipulation checkCallable =
596 ByteCodeUtils.invoke(FuncallExpression.class, "checkCallable", Object.class, Location.class);
597
598 /**
599 * Checks whether the given object is a {@link BaseFunction}.
600 *
601 * <p>Public for reflection by the compiler and access from generated byte code.
602 *
603 * @throws EvalException If not a BaseFunction.
604 */
605 public static BaseFunction checkCallable(Object functionValue, Location location)
606 throws EvalException {
607 if (functionValue instanceof BaseFunction) {
608 return (BaseFunction) functionValue;
609 } else {
610 throw new EvalException(
611 location, "'" + EvalUtils.getDataTypeName(functionValue) + "' object is not callable");
612 }
613 }
614
615 /**
616 * A {@link StackManipulation} invoking checkDuplicates.
617 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
618 */
619 private static final StackManipulation checkDuplicates =
620 ByteCodeUtils.invoke(
621 FuncallExpression.class,
622 "checkDuplicates",
623 ImmutableList.Builder.class,
624 String.class,
625 Location.class);
626
627 /**
628 * Check the list from the builder and report an {@link EvalException} if not empty.
629 *
630 * <p>public for reflection by the compiler and calls from compiled functions
631 */
632 public static void checkDuplicates(
633 ImmutableList.Builder<String> duplicates, String function, Location location)
634 throws EvalException {
635 List<String> dups = duplicates.build();
636 if (!dups.isEmpty()) {
637 throw new EvalException(
638 location,
639 "duplicate keyword"
640 + (dups.size() > 1 ? "s" : "")
641 + " '"
642 + Joiner.on("', '").join(dups)
643 + "' in call to "
644 + function);
645 }
646 }
647
648 /**
649 * A {@link StackManipulation} invoking invokeObjectMethod.
650 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
651 */
652 private static final StackManipulation invokeObjectMethod =
653 ByteCodeUtils.invoke(
654 FuncallExpression.class,
655 "invokeObjectMethod",
656 String.class,
657 ImmutableList.class,
658 ImmutableMap.class,
659 FuncallExpression.class,
660 Environment.class);
661
662 /**
663 * Call a method depending on the type of an object it is called on.
664 *
665 * <p>Public for reflection by the compiler and access from generated byte code.
666 *
667 * @param positionals The first object is expected to be the object the method is called on.
668 * @param call the original expression that caused this call, needed for rules especially
669 */
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000670 public Object invokeObjectMethod(
Florian Weikert33f819b2015-11-09 18:15:55 +0000671 String method,
672 ImmutableList<Object> positionals,
673 ImmutableMap<String, Object> keyWordArgs,
674 FuncallExpression call,
675 Environment env)
676 throws EvalException, InterruptedException {
677 Location location = call.getLocation();
678 Object value = positionals.get(0);
679 ImmutableList<Object> positionalArgs = positionals.subList(1, positionals.size());
680 BaseFunction function = Runtime.getFunction(EvalUtils.getSkylarkType(value.getClass()), method);
681 if (function != null) {
682 if (!isNamespace(value.getClass())) {
683 // Use self as an implicit parameter in front.
684 positionalArgs = positionals;
685 }
686 return function.call(
687 positionalArgs, ImmutableMap.<String, Object>copyOf(keyWordArgs), call, env);
688 } else if (value instanceof ClassObject) {
689 Object fieldValue = ((ClassObject) value).getValue(method);
690 if (fieldValue == null) {
691 throw new EvalException(location, String.format("struct has no method '%s'", method));
692 }
693 if (!(fieldValue instanceof BaseFunction)) {
694 throw new EvalException(
695 location, String.format("struct field '%s' is not a function", method));
696 }
697 function = (BaseFunction) fieldValue;
698 return function.call(
699 positionalArgs, ImmutableMap.<String, Object>copyOf(keyWordArgs), call, env);
Laurent Le Brun88df1f52015-12-23 13:31:44 +0000700 } else {
Florian Weikert33f819b2015-11-09 18:15:55 +0000701 // When calling a Java method, the name is not in the Environment,
702 // so evaluating 'func' would fail.
703 Class<?> objClass;
704 Object obj;
705 if (value instanceof Class<?>) {
706 // Static call
707 obj = null;
708 objClass = (Class<?>) value;
709 } else {
710 obj = value;
711 objClass = value.getClass();
712 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000713 Pair<MethodDescriptor, List<Object>> javaMethod =
714 call.findJavaMethod(objClass, method, positionalArgs, keyWordArgs);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000715 if (javaMethod.first.getAnnotation().structField()) {
716 // Not a method but a callable attribute
717 try {
718 return callFunction(javaMethod.first.getMethod().invoke(obj), env);
719 } catch (IllegalAccessException e) {
720 throw new EvalException(getLocation(), "Method invocation failed: " + e);
721 } catch (InvocationTargetException e) {
722 if (e.getCause() instanceof FuncallException) {
723 throw new EvalException(getLocation(), e.getCause().getMessage());
724 } else if (e.getCause() != null) {
725 throw new EvalExceptionWithJavaCause(getLocation(), e.getCause());
726 } else {
727 // This is unlikely to happen
728 throw new EvalException(getLocation(), "Method invocation failed: " + e);
729 }
730 }
731 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000732 return callMethod(javaMethod.first, method, obj, javaMethod.second.toArray(), location, env);
Florian Weikert33f819b2015-11-09 18:15:55 +0000733 }
734 }
735
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000736 @SuppressWarnings("unchecked")
737 private void evalArguments(ImmutableList.Builder<Object> posargs, Map<String, Object> kwargs,
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000738 Environment env)
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000739 throws EvalException, InterruptedException {
Ulf Adams07dba942015-03-05 14:47:37 +0000740 ImmutableList.Builder<String> duplicates = new ImmutableList.Builder<>();
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000741 // Iterate over the arguments. We assume all positional arguments come before any keyword
742 // or star arguments, because the argument list was already validated by
743 // Argument#validateFuncallArguments, as called by the Parser,
744 // which should be the only place that build FuncallExpression-s.
745 for (Argument.Passed arg : args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100746 Object value = arg.getValue().eval(env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100747 if (arg.isPositional()) {
748 posargs.add(value);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000749 } else if (arg.isStar()) { // expand the starArg
750 if (value instanceof Iterable) {
751 posargs.addAll((Iterable<Object>) value);
752 }
753 } else if (arg.isStarStar()) { // expand the kwargs
Florian Weikert33f819b2015-11-09 18:15:55 +0000754 addKeywordArgs(kwargs, value, duplicates, getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100755 } else {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000756 addKeywordArg(kwargs, arg.getName(), value, duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100757 }
758 }
Florian Weikert33f819b2015-11-09 18:15:55 +0000759 checkDuplicates(duplicates, func.getName(), getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100760 }
761
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000762 @VisibleForTesting
763 public static boolean isNamespace(Class<?> classObject) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100764 return classObject.isAnnotationPresent(SkylarkModule.class)
765 && classObject.getAnnotation(SkylarkModule.class).namespace();
766 }
767
768 @Override
Florian Weikert90a15962015-09-11 13:43:10 +0000769 Object doEval(Environment env) throws EvalException, InterruptedException {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000770 return (obj != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000771 }
772
773 /**
774 * Invokes obj.func() and returns the result.
775 */
776 private Object invokeObjectMethod(Environment env) throws EvalException, InterruptedException {
777 Object objValue = obj.eval(env);
Ulf Adams07dba942015-03-05 14:47:37 +0000778 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000779 posargs.add(objValue);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000780 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
781 // 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 +0000782 Map<String, Object> kwargs = new LinkedHashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000783 evalArguments(posargs, kwargs, env);
784 return invokeObjectMethod(
785 func.getName(), posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000786 }
Googlera34d5072015-03-05 13:51:28 +0000787
Florian Weikert3f610e82015-08-18 14:37:46 +0000788 /**
789 * Invokes func() and returns the result.
790 */
791 private Object invokeGlobalFunction(Environment env) throws EvalException, InterruptedException {
792 Object funcValue = func.eval(env);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000793 return callFunction(funcValue, env);
794 }
795
796 /**
797 * Calls a function object
798 */
799 private Object callFunction(Object funcValue, Environment env)
800 throws EvalException, InterruptedException {
Florian Weikert3f610e82015-08-18 14:37:46 +0000801 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
802 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
803 // we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
Tobias Werthd41e08e2016-11-24 11:32:37 +0000804 Map<String, Object> kwargs = new HashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000805 BaseFunction function = checkCallable(funcValue, getLocation());
806 evalArguments(posargs, kwargs, env);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000807 return function.call(posargs.build(), ImmutableMap.copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000808 }
809
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000810 /**
811 * Returns the value of the argument 'name' (or null if there is none).
812 * This function is used to associate debugging information to rules created by skylark "macros".
813 */
814 @Nullable
815 public String getNameArg() {
816 for (Argument.Passed arg : args) {
817 if (arg != null) {
818 String name = arg.getName();
819 if (name != null && name.equals("name")) {
820 Expression expr = arg.getValue();
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000821 return (expr instanceof StringLiteral) ? ((StringLiteral) expr).getValue() : null;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000822 }
823 }
824 }
825 return null;
826 }
827
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100828 @Override
829 public void accept(SyntaxTreeVisitor visitor) {
830 visitor.visit(this);
831 }
832
833 @Override
Laurent Le Brun2e78d612015-04-15 09:06:46 +0000834 void validate(ValidationEnvironment env) throws EvalException {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000835 for (Argument.Passed arg : args) {
Laurent Le Brun386b90c2015-02-11 16:34:08 +0000836 arg.getValue().validate(env);
837 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100838
839 if (obj != null) {
Laurent Le Brun964d8d52015-04-13 12:15:04 +0000840 obj.validate(env);
841 } else if (!env.hasSymbolInEnvironment(func.getName())) {
842 throw new EvalException(getLocation(),
843 String.format("function '%s' does not exist", func.getName()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100844 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100845 }
Florian Weikert90a15962015-09-11 13:43:10 +0000846
847 @Override
848 protected boolean isNewScope() {
849 return true;
850 }
Florian Weikert33f819b2015-11-09 18:15:55 +0000851
852 @Override
853 ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo) throws EvalException {
854 AstAccessors debugAccessors = debugInfo.add(this);
855 List<ByteCodeAppender> code = new ArrayList<>();
856 if (obj != null) {
857 compileObjectMethodCall(scope, debugInfo, debugAccessors, code);
858 } else {
859 compileGlobalFunctionCall(scope, debugInfo, debugAccessors, code);
860 }
861 return ByteCodeUtils.compoundAppender(code);
862 }
863
864 /**
865 * Add code that compiles the argument expressions.
866 *
867 * <p>The byte code leaves the arguments on the stack in order of:
868 * positional arguments, key word arguments, this FuncallExpression, Environment
869 * This is the order required by {@link #invokeObjectMethod} and
870 * {@link BaseFunction#call(List, Map, FuncallExpression, Environment)}.
871 */
872 private void compileArguments(
873 VariableScope scope,
874 DebugInfo debugInfo,
875 AstAccessors debugAccessors,
876 List<ByteCodeAppender> code)
877 throws EvalException {
878 InternalVariable positionalsBuilder = scope.freshVariable(ImmutableList.Builder.class);
879 append(code, ByteCodeMethodCalls.BCImmutableList.builder);
880 code.add(positionalsBuilder.store());
881
882 InternalVariable keyWordArgs = scope.freshVariable(Map.class);
883 append(code, NewObject.fromConstructor(HashMap.class).arguments());
884 code.add(keyWordArgs.store());
885
886 InternalVariable duplicatesBuilder =
887 scope.freshVariable(new TypeDescription.ForLoadedType(ImmutableList.Builder.class));
888 append(code, ByteCodeMethodCalls.BCImmutableList.builder);
889 code.add(duplicatesBuilder.store());
890
891 StackManipulation builderAdd =
892 new StackManipulation.Compound(
893 ByteCodeMethodCalls.BCImmutableList.Builder.add, Removal.SINGLE);
894
895 // add an object the function is called on first
896 if (obj != null) {
897 append(code, positionalsBuilder.load());
898 code.add(obj.compile(scope, debugInfo));
899 append(code, builderAdd);
900 }
901 // add all arguments to their respective builder/map
902 for (Argument.Passed arg : args) {
903 ByteCodeAppender value = arg.getValue().compile(scope, debugInfo);
904 if (arg.isPositional()) {
905 append(code, positionalsBuilder.load());
906 code.add(value);
907 append(code, builderAdd);
908 } else if (arg.isStar()) {
909 // expand the starArg by adding all it's elements to the builder
910 append(code, positionalsBuilder.load());
911 code.add(value);
912 append(
913 code,
914 TypeCasting.to(new TypeDescription.ForLoadedType(Iterable.class)),
915 ByteCodeMethodCalls.BCImmutableList.Builder.addAll,
916 Removal.SINGLE);
917 } else if (arg.isStarStar()) {
918 append(code, keyWordArgs.load());
919 code.add(value);
920 append(code, duplicatesBuilder.load(), debugAccessors.loadLocation, addKeywordArgs);
921 } else {
922 append(code, keyWordArgs.load(), new TextConstant(arg.getName()));
923 code.add(value);
924 append(code, duplicatesBuilder.load(), addKeywordArg);
925 }
926 }
927 append(
928 code,
929 // check for duplicates in the key word arguments
930 duplicatesBuilder.load(),
931 new TextConstant(func.getName()),
932 debugAccessors.loadLocation,
933 checkDuplicates,
934 // load the arguments in the correct order for invokeObjectMethod and BaseFunction.call
935 positionalsBuilder.load(),
936 ByteCodeMethodCalls.BCImmutableList.Builder.build,
937 keyWordArgs.load(),
938 ByteCodeMethodCalls.BCImmutableMap.copyOf,
939 debugAccessors.loadAstNode,
940 TypeCasting.to(new TypeDescription.ForLoadedType(FuncallExpression.class)),
941 scope.loadEnvironment());
942 }
943
944 private void compileObjectMethodCall(
945 VariableScope scope,
946 DebugInfo debugInfo,
947 AstAccessors debugAccessors,
948 List<ByteCodeAppender> code)
949 throws EvalException {
950 append(code, new TextConstant(func.getName()));
951 compileArguments(scope, debugInfo, debugAccessors, code);
952 append(code, invokeObjectMethod);
953 }
954
955 private void compileGlobalFunctionCall(
956 VariableScope scope,
957 DebugInfo debugInfo,
958 AstAccessors debugAccessors,
959 List<ByteCodeAppender> code)
960 throws EvalException {
961 code.add(func.compile(scope, debugInfo));
962 append(code, debugAccessors.loadLocation, checkCallable);
963 compileArguments(scope, debugInfo, debugAccessors, code);
964 append(code, BaseFunction.call);
965 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100966}