blob: 6bfa55ad1d9bc9608dce424dd1a174558dfcf378 [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
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000017import com.google.common.annotations.VisibleForTesting;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +000018import com.google.common.base.Joiner;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010019import com.google.common.cache.CacheBuilder;
20import com.google.common.cache.CacheLoader;
21import com.google.common.cache.LoadingCache;
22import com.google.common.collect.ImmutableList;
23import com.google.common.collect.ImmutableMap;
dslomov40ddec32017-07-03 07:15:31 -040024import com.google.common.collect.Iterables;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import com.google.common.collect.Lists;
26import com.google.devtools.build.lib.events.Location;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000027import com.google.devtools.build.lib.skylarkinterface.Param;
John Field585d1a02015-12-16 16:03:52 +000028import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
Jon Brandvein6696db22016-10-04 20:18:19 +000029import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
John Field585d1a02015-12-16 16:03:52 +000030import 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;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000033import com.google.devtools.build.lib.util.Pair;
34import com.google.devtools.build.lib.util.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import com.google.devtools.build.lib.util.StringUtilities;
brandjone2ffd5d2017-06-27 18:14:54 +020036import java.io.IOException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037import java.lang.reflect.InvocationTargetException;
38import java.lang.reflect.Method;
39import java.lang.reflect.Modifier;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010040import java.util.Collections;
41import java.util.HashMap;
Pedro Liberal Fernandez837dbc12016-08-18 14:13:01 +000042import java.util.LinkedHashMap;
dslomov40ddec32017-07-03 07:15:31 -040043import java.util.LinkedHashSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044import java.util.List;
45import java.util.Map;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000046import java.util.Set;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047import java.util.concurrent.ExecutionException;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000048import javax.annotation.Nullable;
49
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010050/**
51 * Syntax node for a function call expression.
52 */
53public final class FuncallExpression extends Expression {
54
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010055 /**
56 * A value class to store Methods with their corresponding SkylarkCallable annotations.
57 * This is needed because the annotation is sometimes in a superclass.
58 */
59 public static final class MethodDescriptor {
60 private final Method method;
61 private final SkylarkCallable annotation;
62
63 private MethodDescriptor(Method method, SkylarkCallable annotation) {
64 this.method = method;
65 this.annotation = annotation;
66 }
67
68 Method getMethod() {
69 return method;
70 }
71
72 /**
73 * Returns the SkylarkCallable annotation corresponding to this method.
74 */
75 public SkylarkCallable getAnnotation() {
76 return annotation;
77 }
78 }
79
80 private static final LoadingCache<Class<?>, Map<String, List<MethodDescriptor>>> methodCache =
81 CacheBuilder.newBuilder()
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000082 .initialCapacity(10)
83 .maximumSize(100)
84 .build(
85 new CacheLoader<Class<?>, Map<String, List<MethodDescriptor>>>() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010086
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000087 @Override
88 public Map<String, List<MethodDescriptor>> load(Class<?> key) throws Exception {
89 Map<String, List<MethodDescriptor>> methodMap = new HashMap<>();
90 for (Method method : key.getMethods()) {
91 // Synthetic methods lead to false multiple matches
92 if (method.isSynthetic()) {
93 continue;
94 }
Jon Brandvein6696db22016-10-04 20:18:19 +000095 SkylarkCallable callable = SkylarkInterfaceUtils.getSkylarkCallable(method);
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000096 if (callable == null) {
97 continue;
98 }
99 Preconditions.checkArgument(
100 callable.parameters().length == 0 || !callable.structField(),
101 "Method "
102 + method
Jon Brandveinead58ae2016-09-29 18:41:10 +0000103 + " was annotated with both structField and parameters.");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000104 if (callable.parameters().length > 0 || callable.mandatoryPositionals() >= 0) {
105 int nbArgs =
106 callable.parameters().length
107 + Math.max(0, callable.mandatoryPositionals());
108 Preconditions.checkArgument(
109 nbArgs == method.getParameterTypes().length,
110 "Method "
111 + method
112 + " was annotated for "
113 + nbArgs
114 + " arguments "
115 + "but accept only "
116 + method.getParameterTypes().length
117 + " arguments.");
118 }
119 String name = callable.name();
120 if (name.isEmpty()) {
121 name = StringUtilities.toPythonStyleFunctionName(method.getName());
122 }
123 if (methodMap.containsKey(name)) {
124 methodMap.get(name).add(new MethodDescriptor(method, callable));
125 } else {
126 methodMap.put(
127 name, Lists.newArrayList(new MethodDescriptor(method, callable)));
128 }
129 }
130 return ImmutableMap.copyOf(methodMap);
131 }
132 });
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100133
134 /**
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000135 * Returns a map of methods and corresponding SkylarkCallable annotations of the methods of the
136 * classObj class reachable from Skylark.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100137 */
138 public static ImmutableMap<Method, SkylarkCallable> collectSkylarkMethodsWithAnnotation(
139 Class<?> classObj) {
140 ImmutableMap.Builder<Method, SkylarkCallable> methodMap = ImmutableMap.builder();
141 for (Method method : classObj.getMethods()) {
142 // Synthetic methods lead to false multiple matches
143 if (!method.isSynthetic()) {
Jon Brandvein6696db22016-10-04 20:18:19 +0000144 SkylarkCallable annotation = SkylarkInterfaceUtils.getSkylarkCallable(classObj, method);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100145 if (annotation != null) {
146 methodMap.put(method, annotation);
147 }
148 }
149 }
150 return methodMap.build();
151 }
152
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000153 private static class ArgumentListConversionResult {
154 private final ImmutableList<Object> arguments;
155 private final String error;
156
157 private ArgumentListConversionResult(ImmutableList<Object> arguments, String error) {
158 this.arguments = arguments;
159 this.error = error;
160 }
161
162 public static ArgumentListConversionResult fromArgumentList(ImmutableList<Object> arguments) {
163 return new ArgumentListConversionResult(arguments, null);
164 }
165
166 public static ArgumentListConversionResult fromError(String error) {
167 return new ArgumentListConversionResult(null, error);
168 }
169
170 public String getError() {
171 return error;
172 }
173
174 public ImmutableList<Object> getArguments() {
175 return arguments;
176 }
177 }
178
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100179 /**
180 * An exception class to handle exceptions in direct Java API calls.
181 */
182 public static final class FuncallException extends Exception {
183
184 public FuncallException(String msg) {
185 super(msg);
186 }
187 }
188
fzaiser8c27a892017-08-11 17:26:44 +0200189 private final Expression function;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100190
brandjon990622b2017-07-11 19:56:45 +0200191 private final List<Argument.Passed> arguments;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100192
193 private final int numPositionalArgs;
194
fzaiser8c27a892017-08-11 17:26:44 +0200195 public FuncallExpression(Expression function, List<Argument.Passed> arguments) {
196 this.function = Preconditions.checkNotNull(function);
197 this.arguments = Preconditions.checkNotNull(arguments);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100198 this.numPositionalArgs = countPositionalArguments();
199 }
200
fzaiser8c27a892017-08-11 17:26:44 +0200201 /** Returns the function that is called. */
202 public Expression getFunction() {
203 return this.function;
204 }
205
206 /**
207 * Returns the name of the called function if it's available in the AST.
208 *
209 * <p>It may not be available in cases like `list[0](arg1, arg2)`.
210 */
211 @Nullable
212 public String getFunctionNameIfPossible() {
213 Identifier ident = getFunctionIdentifierIfPossible();
214 return ident == null ? null : ident.getName();
215 }
216
217 /**
218 * Returns the identifier of the called function if it's available in the AST.
219 *
220 * <p>It may not be available in cases like `list[0](arg1, arg2)`.
221 */
222 @Nullable
223 public Identifier getFunctionIdentifierIfPossible() {
224 if (function instanceof Identifier) {
225 return (Identifier) function;
226 }
227 if (function instanceof DotExpression) {
228 return ((DotExpression) function).getField();
229 }
230 return null;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100231 }
232
233 /**
234 * Returns the number of positional arguments.
235 */
236 private int countPositionalArguments() {
237 int num = 0;
brandjon990622b2017-07-11 19:56:45 +0200238 for (Argument.Passed arg : arguments) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100239 if (arg.isPositional()) {
240 num++;
241 }
242 }
243 return num;
244 }
245
246 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100247 * Returns an (immutable, ordered) list of function arguments. The first n are
248 * positional and the remaining ones are keyword args, where n =
249 * getNumPositionalArguments().
250 */
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000251 public List<Argument.Passed> getArguments() {
brandjon990622b2017-07-11 19:56:45 +0200252 return Collections.unmodifiableList(arguments);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100253 }
254
255 /**
256 * Returns the number of arguments which are positional; the remainder are
257 * keyword arguments.
258 */
259 public int getNumPositionalArguments() {
260 return numPositionalArgs;
261 }
262
brandjone2ffd5d2017-06-27 18:14:54 +0200263 @Override
264 public void prettyPrint(Appendable buffer) throws IOException {
brandjon990622b2017-07-11 19:56:45 +0200265 function.prettyPrint(buffer);
brandjone2ffd5d2017-06-27 18:14:54 +0200266 buffer.append('(');
267 String sep = "";
brandjon990622b2017-07-11 19:56:45 +0200268 for (Argument.Passed arg : arguments) {
brandjone2ffd5d2017-06-27 18:14:54 +0200269 buffer.append(sep);
270 arg.prettyPrint(buffer);
271 sep = ", ";
272 }
273 buffer.append(')');
274 }
275
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100276 @Override
277 public String toString() {
vladmos6ff634d2017-07-05 10:25:01 -0400278 Printer.LengthLimitedPrinter printer = new Printer.LengthLimitedPrinter();
brandjon990622b2017-07-11 19:56:45 +0200279 printer.append(function.toString());
280 printer.printAbbreviatedList(arguments, "(", ", ", ")", null,
Florian Weikert2591e192015-10-05 14:24:51 +0000281 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT,
282 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH);
vladmos46907932017-06-30 14:01:45 +0200283 return printer.toString();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100284 }
285
286 /**
Laurent Le Brun57badf42017-01-02 15:12:24 +0000287 * Returns the list of Skylark callable Methods of objClass with the given name and argument
288 * number.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100289 */
Laurent Le Brun57badf42017-01-02 15:12:24 +0000290 public static List<MethodDescriptor> getMethods(Class<?> objClass, String methodName) {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000291 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) {
Laurent Le Brun57badf42017-01-02 15:12:24 +0000294 throw new IllegalStateException("method invocation failed: " + e);
Laurent Le Brun427bd972015-05-20 13:28:44 +0000295 }
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 */
Laurent Le Brun57badf42017-01-02 15:12:24 +0000302 public static Set<String> getMethodNames(Class<?> objClass) {
303 try {
304 return methodCache.get(objClass).keySet();
305 } catch (ExecutionException e) {
306 throw new IllegalStateException("method invocation failed: " + e);
307 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100308 }
309
310 static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000311 Object[] args, Location loc, Environment env) throws EvalException {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000312 try {
313 Method method = methodDescriptor.getMethod();
314 if (obj == null && !Modifier.isStatic(method.getModifiers())) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000315 throw new EvalException(loc, "method '" + methodName + "' is not static");
Laurent Le Brun427bd972015-05-20 13:28:44 +0000316 }
317 // This happens when the interface is public but the implementation classes
318 // have reduced visibility.
319 method.setAccessible(true);
320 Object result = method.invoke(obj, args);
321 if (method.getReturnType().equals(Void.TYPE)) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000322 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000323 }
324 if (result == null) {
325 if (methodDescriptor.getAnnotation().allowReturnNones()) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000326 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000327 } else {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000328 throw new EvalException(
329 loc,
330 "method invocation returned None, please file a bug report: "
331 + methodName
vladmos46907932017-06-30 14:01:45 +0200332 + Printer.printAbbreviatedList(
333 ImmutableList.copyOf(args), "(", ", ", ")", null));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000334 }
335 }
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000336 // TODO(bazel-team): get rid of this, by having everyone use the Skylark data structures
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000337 result = SkylarkType.convertToSkylark(result, method, env);
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000338 if (result != null && !EvalUtils.isSkylarkAcceptable(result.getClass())) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000339 throw new EvalException(
340 loc,
341 Printer.format(
342 "method '%s' returns an object of invalid type %r", methodName, result.getClass()));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000343 }
344 return result;
345 } catch (IllegalAccessException e) {
346 // TODO(bazel-team): Print a nice error message. Maybe the method exists
347 // and an argument is missing or has the wrong type.
348 throw new EvalException(loc, "Method invocation failed: " + e);
349 } catch (InvocationTargetException e) {
350 if (e.getCause() instanceof FuncallException) {
351 throw new EvalException(loc, e.getCause().getMessage());
352 } else if (e.getCause() != null) {
353 throw new EvalExceptionWithJavaCause(loc, e.getCause());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100354 } else {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000355 // This is unlikely to happen
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000356 throw new EvalException(loc, "method invocation failed: " + e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100357 }
358 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100359 }
360
361 // TODO(bazel-team): If there's exactly one usable method, this works. If there are multiple
362 // matching methods, it still can be a problem. Figure out how the Java compiler does it
363 // exactly and copy that behaviour.
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000364 // Throws an EvalException when it cannot find a matching function.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000365 private Pair<MethodDescriptor, List<Object>> findJavaMethod(
366 Class<?> objClass, String methodName, List<Object> args, Map<String, Object> kwargs)
367 throws EvalException {
368 Pair<MethodDescriptor, List<Object>> matchingMethod = null;
Laurent Le Brun57badf42017-01-02 15:12:24 +0000369 List<MethodDescriptor> methods = getMethods(objClass, methodName);
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000370 ArgumentListConversionResult argumentListConversionResult = null;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000371 if (methods != null) {
372 for (MethodDescriptor method : methods) {
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000373 if (method.getAnnotation().structField()) {
374 return new Pair<>(method, null);
375 } else {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000376 argumentListConversionResult = convertArgumentList(args, kwargs, method);
377 if (argumentListConversionResult.getArguments() != null) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000378 if (matchingMethod == null) {
brandjon990622b2017-07-11 19:56:45 +0200379 matchingMethod = new Pair<>(method, argumentListConversionResult.getArguments());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000380 } else {
381 throw new EvalException(
382 getLocation(),
383 String.format(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000384 "type '%s' has multiple matches for function %s",
fzaiser8c27a892017-08-11 17:26:44 +0200385 EvalUtils.getDataTypeNameFromClass(objClass),
386 formatMethod(methodName, args, kwargs)));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000387 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100388 }
389 }
390 }
Laurent Le Brun427bd972015-05-20 13:28:44 +0000391 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000392 if (matchingMethod == null) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000393 String errorMessage;
Michael Staib79bd2c22017-03-22 14:37:03 +0000394 if (ClassObject.class.isAssignableFrom(objClass)) {
395 errorMessage = String.format("struct has no method '%s'", methodName);
396 } else if (argumentListConversionResult == null
397 || argumentListConversionResult.getError() == null) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000398 errorMessage =
399 String.format(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000400 "type '%s' has no method %s",
fzaiser8c27a892017-08-11 17:26:44 +0200401 EvalUtils.getDataTypeNameFromClass(objClass),
402 formatMethod(methodName, args, kwargs));
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000403
404 } else {
405 errorMessage =
406 String.format(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000407 "%s, in method %s of '%s'",
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000408 argumentListConversionResult.getError(),
fzaiser8c27a892017-08-11 17:26:44 +0200409 formatMethod(methodName, args, kwargs),
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000410 EvalUtils.getDataTypeNameFromClass(objClass));
411 }
412 throw new EvalException(getLocation(), errorMessage);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100413 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000414 return matchingMethod;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100415 }
416
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000417 private static SkylarkType getType(Param param) {
418 SkylarkType type =
419 param.generic1() != Object.class
420 ? SkylarkType.of(param.type(), param.generic1())
421 : SkylarkType.of(param.type());
422 return type;
423 }
424
425 /**
426 * Constructs the parameters list to actually pass to the method, filling with default values if
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000427 * any. If there is a type or argument mismatch, returns a result containing an error message.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000428 */
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000429 private ArgumentListConversionResult convertArgumentList(
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000430 List<Object> args, Map<String, Object> kwargs, MethodDescriptor method) {
431 ImmutableList.Builder<Object> builder = ImmutableList.builder();
432 Class<?>[] params = method.getMethod().getParameterTypes();
433 SkylarkCallable callable = method.getAnnotation();
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000434 int mandatoryPositionals = callable.mandatoryPositionals();
435 if (mandatoryPositionals < 0) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000436 if (callable.parameters().length > 0) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000437 mandatoryPositionals = 0;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000438 } else {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000439 mandatoryPositionals = params.length;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000440 }
441 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000442 if (mandatoryPositionals > args.size()
443 || args.size() > mandatoryPositionals + callable.parameters().length) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000444 return ArgumentListConversionResult.fromError("too many arguments");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000445 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000446 // First process the legacy positional parameters.
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000447 int i = 0;
448 if (mandatoryPositionals > 0) {
449 for (Class<?> param : params) {
450 Object value = args.get(i);
451 if (!param.isAssignableFrom(value.getClass())) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000452 return ArgumentListConversionResult.fromError(
453 String.format(
Carmi Grushko12291602016-10-26 19:07:41 +0000454 "Cannot convert parameter at position %d from type %s to type %s",
455 i, EvalUtils.getDataTypeName(value), param.toString()));
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000456 }
457 builder.add(value);
458 i++;
459 if (mandatoryPositionals >= 0 && i >= mandatoryPositionals) {
460 // Stops for specified parameters instead.
461 break;
462 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000463 }
464 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000465
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000466 // Then the parameters specified in callable.parameters()
dslomov40ddec32017-07-03 07:15:31 -0400467 Set<String> keys = new LinkedHashSet<>(kwargs.keySet());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000468 for (Param param : callable.parameters()) {
469 SkylarkType type = getType(param);
Googlere5da53c2016-09-21 12:34:44 +0000470 if (param.noneable()) {
471 type = SkylarkType.Union.of(type, SkylarkType.NONE);
472 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000473 Object value = null;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000474 if (i < args.size()) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000475 value = args.get(i);
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000476 if (!param.positional()) {
477 return ArgumentListConversionResult.fromError(
478 String.format("Parameter '%s' is not positional", param.name()));
479 } else if (!type.contains(value)) {
480 return ArgumentListConversionResult.fromError(
481 String.format(
482 "Cannot convert parameter '%s' to type %s", param.name(), type.toString()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000483 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000484 i++;
485 } else if (param.named() && keys.remove(param.name())) {
486 // Named parameters
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000487 value = kwargs.get(param.name());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000488 if (!type.contains(value)) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000489 return ArgumentListConversionResult.fromError(
490 String.format(
491 "Cannot convert parameter '%s' to type %s", param.name(), type.toString()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000492 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000493 } else {
494 // Use default value
495 if (param.defaultValue().isEmpty()) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000496 return ArgumentListConversionResult.fromError(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000497 String.format("parameter '%s' has no default value", param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000498 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000499 value = SkylarkSignatureProcessor.getDefaultValue(param, null);
500 }
501 builder.add(value);
502 if (!param.noneable() && value instanceof NoneType) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000503 return ArgumentListConversionResult.fromError(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000504 String.format("parameter '%s' cannot be None", param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000505 }
506 }
dslomov40ddec32017-07-03 07:15:31 -0400507 if (i < args.size()) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000508 return ArgumentListConversionResult.fromError("too many arguments");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000509 }
dslomov40ddec32017-07-03 07:15:31 -0400510 if (!keys.isEmpty()) {
511 return ArgumentListConversionResult.fromError(
512 String.format("unexpected keyword%s %s",
513 keys.size() > 1 ? "s" : "",
514 Joiner.on(",").join(Iterables.transform(keys, s -> "'" + s + "'"))));
515 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000516 return ArgumentListConversionResult.fromArgumentList(builder.build());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000517 }
518
fzaiser8c27a892017-08-11 17:26:44 +0200519 private static String formatMethod(String name, List<Object> args, Map<String, Object> kwargs) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100520 StringBuilder sb = new StringBuilder();
fzaiser8c27a892017-08-11 17:26:44 +0200521 sb.append(name).append("(");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100522 boolean first = true;
523 for (Object obj : args) {
524 if (!first) {
525 sb.append(", ");
526 }
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000527 sb.append(EvalUtils.getDataTypeName(obj));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100528 first = false;
529 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000530 for (Map.Entry<String, Object> kwarg : kwargs.entrySet()) {
531 if (!first) {
532 sb.append(", ");
533 }
534 sb.append(EvalUtils.getDataTypeName(kwarg.getValue()));
535 sb.append(" ");
536 sb.append(kwarg.getKey());
Googler344449d2016-09-15 18:25:35 +0000537 first = false;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000538 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100539 return sb.append(")").toString();
540 }
541
542 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000543 * Add one argument to the keyword map, registering a duplicate in case of conflict.
544 *
545 * <p>public for reflection by the compiler and calls from compiled functions
546 */
547 public static void addKeywordArg(
548 Map<String, Object> kwargs,
549 String name,
550 Object value,
551 ImmutableList.Builder<String> duplicates) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100552 if (kwargs.put(name, value) != null) {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000553 duplicates.add(name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100554 }
555 }
556
557 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000558 * Add multiple arguments to the keyword map (**kwargs), registering duplicates
559 *
560 * <p>public for reflection by the compiler and calls from compiled functions
561 */
562 public static void addKeywordArgs(
563 Map<String, Object> kwargs,
564 Object items,
565 ImmutableList.Builder<String> duplicates,
566 Location location)
567 throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100568 if (!(items instanceof Map<?, ?>)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000569 throw new EvalException(
570 location,
Laurent Le Brun1159cc22017-01-04 12:27:39 +0000571 "argument after ** must be a dictionary, not '" + EvalUtils.getDataTypeName(items) + "'");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100572 }
573 for (Map.Entry<?, ?> entry : ((Map<?, ?>) items).entrySet()) {
574 if (!(entry.getKey() instanceof String)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000575 throw new EvalException(
Laurent Le Brun1159cc22017-01-04 12:27:39 +0000576 location,
577 "keywords must be strings, not '" + EvalUtils.getDataTypeName(entry.getKey()) + "'");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100578 }
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000579 addKeywordArg(kwargs, (String) entry.getKey(), entry.getValue(), duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100580 }
581 }
582
Florian Weikert33f819b2015-11-09 18:15:55 +0000583 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000584 * Checks whether the given object is a {@link BaseFunction}.
585 *
586 * <p>Public for reflection by the compiler and access from generated byte code.
587 *
588 * @throws EvalException If not a BaseFunction.
589 */
590 public static BaseFunction checkCallable(Object functionValue, Location location)
591 throws EvalException {
592 if (functionValue instanceof BaseFunction) {
593 return (BaseFunction) functionValue;
594 } else {
595 throw new EvalException(
596 location, "'" + EvalUtils.getDataTypeName(functionValue) + "' object is not callable");
597 }
598 }
599
600 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000601 * Check the list from the builder and report an {@link EvalException} if not empty.
602 *
603 * <p>public for reflection by the compiler and calls from compiled functions
604 */
605 public static void checkDuplicates(
606 ImmutableList.Builder<String> duplicates, String function, Location location)
607 throws EvalException {
608 List<String> dups = duplicates.build();
609 if (!dups.isEmpty()) {
610 throw new EvalException(
611 location,
612 "duplicate keyword"
613 + (dups.size() > 1 ? "s" : "")
614 + " '"
615 + Joiner.on("', '").join(dups)
616 + "' in call to "
617 + function);
618 }
619 }
620
621 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000622 * Call a method depending on the type of an object it is called on.
623 *
624 * <p>Public for reflection by the compiler and access from generated byte code.
625 *
626 * @param positionals The first object is expected to be the object the method is called on.
627 * @param call the original expression that caused this call, needed for rules especially
628 */
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000629 public Object invokeObjectMethod(
Florian Weikert33f819b2015-11-09 18:15:55 +0000630 String method,
631 ImmutableList<Object> positionals,
632 ImmutableMap<String, Object> keyWordArgs,
633 FuncallExpression call,
634 Environment env)
635 throws EvalException, InterruptedException {
636 Location location = call.getLocation();
637 Object value = positionals.get(0);
638 ImmutableList<Object> positionalArgs = positionals.subList(1, positionals.size());
639 BaseFunction function = Runtime.getFunction(EvalUtils.getSkylarkType(value.getClass()), method);
Michael Staib79bd2c22017-03-22 14:37:03 +0000640 Object fieldValue =
641 (value instanceof ClassObject) ? ((ClassObject) value).getValue(method) : null;
Florian Weikert33f819b2015-11-09 18:15:55 +0000642 if (function != null) {
643 if (!isNamespace(value.getClass())) {
644 // Use self as an implicit parameter in front.
645 positionalArgs = positionals;
646 }
647 return function.call(
brandjon990622b2017-07-11 19:56:45 +0200648 positionalArgs, ImmutableMap.copyOf(keyWordArgs), call, env);
Michael Staib79bd2c22017-03-22 14:37:03 +0000649 } else if (fieldValue != null) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000650 if (!(fieldValue instanceof BaseFunction)) {
651 throw new EvalException(
652 location, String.format("struct field '%s' is not a function", method));
653 }
654 function = (BaseFunction) fieldValue;
655 return function.call(
brandjon990622b2017-07-11 19:56:45 +0200656 positionalArgs, ImmutableMap.copyOf(keyWordArgs), call, env);
Laurent Le Brun88df1f52015-12-23 13:31:44 +0000657 } else {
Florian Weikert33f819b2015-11-09 18:15:55 +0000658 // When calling a Java method, the name is not in the Environment,
brandjon990622b2017-07-11 19:56:45 +0200659 // so evaluating 'function' would fail.
Florian Weikert33f819b2015-11-09 18:15:55 +0000660 Class<?> objClass;
661 Object obj;
662 if (value instanceof Class<?>) {
663 // Static call
664 obj = null;
665 objClass = (Class<?>) value;
666 } else {
667 obj = value;
668 objClass = value.getClass();
669 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000670 Pair<MethodDescriptor, List<Object>> javaMethod =
671 call.findJavaMethod(objClass, method, positionalArgs, keyWordArgs);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000672 if (javaMethod.first.getAnnotation().structField()) {
673 // Not a method but a callable attribute
674 try {
675 return callFunction(javaMethod.first.getMethod().invoke(obj), env);
676 } catch (IllegalAccessException e) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000677 throw new EvalException(getLocation(), "method invocation failed: " + e);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000678 } catch (InvocationTargetException e) {
679 if (e.getCause() instanceof FuncallException) {
680 throw new EvalException(getLocation(), e.getCause().getMessage());
681 } else if (e.getCause() != null) {
682 throw new EvalExceptionWithJavaCause(getLocation(), e.getCause());
683 } else {
684 // This is unlikely to happen
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000685 throw new EvalException(getLocation(), "method invocation failed: " + e);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000686 }
687 }
688 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000689 return callMethod(javaMethod.first, method, obj, javaMethod.second.toArray(), location, env);
Florian Weikert33f819b2015-11-09 18:15:55 +0000690 }
691 }
692
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000693 @SuppressWarnings("unchecked")
694 private void evalArguments(ImmutableList.Builder<Object> posargs, Map<String, Object> kwargs,
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000695 Environment env)
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000696 throws EvalException, InterruptedException {
Ulf Adams07dba942015-03-05 14:47:37 +0000697 ImmutableList.Builder<String> duplicates = new ImmutableList.Builder<>();
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000698 // Iterate over the arguments. We assume all positional arguments come before any keyword
699 // or star arguments, because the argument list was already validated by
700 // Argument#validateFuncallArguments, as called by the Parser,
701 // which should be the only place that build FuncallExpression-s.
brandjon990622b2017-07-11 19:56:45 +0200702 for (Argument.Passed arg : arguments) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100703 Object value = arg.getValue().eval(env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100704 if (arg.isPositional()) {
705 posargs.add(value);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000706 } else if (arg.isStar()) { // expand the starArg
Laurent Le Brun54b9d2c2017-02-20 14:04:57 +0000707 if (!(value instanceof Iterable)) {
708 throw new EvalException(
709 getLocation(),
710 "argument after * must be an iterable, not " + EvalUtils.getDataTypeName(value));
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000711 }
Laurent Le Brun54b9d2c2017-02-20 14:04:57 +0000712 posargs.addAll((Iterable<Object>) value);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000713 } else if (arg.isStarStar()) { // expand the kwargs
Florian Weikert33f819b2015-11-09 18:15:55 +0000714 addKeywordArgs(kwargs, value, duplicates, getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100715 } else {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000716 addKeywordArg(kwargs, arg.getName(), value, duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100717 }
718 }
fzaiser8c27a892017-08-11 17:26:44 +0200719 checkDuplicates(duplicates, function.toString(), getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100720 }
721
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000722 @VisibleForTesting
723 public static boolean isNamespace(Class<?> classObject) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100724 return classObject.isAnnotationPresent(SkylarkModule.class)
725 && classObject.getAnnotation(SkylarkModule.class).namespace();
726 }
727
728 @Override
Florian Weikert90a15962015-09-11 13:43:10 +0000729 Object doEval(Environment env) throws EvalException, InterruptedException {
fzaiser8c27a892017-08-11 17:26:44 +0200730 if (function instanceof DotExpression) {
731 return invokeObjectMethod(env, (DotExpression) function);
732 }
733 if (function instanceof Identifier) {
734 return invokeGlobalFunction(env);
735 }
736 throw new EvalException(
737 getLocation(), Printer.format("cannot evaluate function '%s'", function));
Florian Weikert3f610e82015-08-18 14:37:46 +0000738 }
739
fzaiser8c27a892017-08-11 17:26:44 +0200740 /** Invokes object.function() and returns the result. */
741 private Object invokeObjectMethod(Environment env, DotExpression dot)
742 throws EvalException, InterruptedException {
743 Object objValue = dot.getObject().eval(env);
Ulf Adams07dba942015-03-05 14:47:37 +0000744 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000745 posargs.add(objValue);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000746 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
747 // 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 +0000748 Map<String, Object> kwargs = new LinkedHashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000749 evalArguments(posargs, kwargs, env);
750 return invokeObjectMethod(
fzaiser8c27a892017-08-11 17:26:44 +0200751 dot.getField().getName(), posargs.build(), ImmutableMap.copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000752 }
Googlera34d5072015-03-05 13:51:28 +0000753
Florian Weikert3f610e82015-08-18 14:37:46 +0000754 /**
brandjon990622b2017-07-11 19:56:45 +0200755 * Invokes function() and returns the result.
Florian Weikert3f610e82015-08-18 14:37:46 +0000756 */
757 private Object invokeGlobalFunction(Environment env) throws EvalException, InterruptedException {
brandjon990622b2017-07-11 19:56:45 +0200758 Object funcValue = function.eval(env);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000759 return callFunction(funcValue, env);
760 }
761
762 /**
763 * Calls a function object
764 */
765 private Object callFunction(Object funcValue, Environment env)
766 throws EvalException, InterruptedException {
Florian Weikert3f610e82015-08-18 14:37:46 +0000767 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
768 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
769 // we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
Vladimir Moskva76e31d12016-12-05 16:28:37 +0000770 Map<String, Object> kwargs = new LinkedHashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000771 BaseFunction function = checkCallable(funcValue, getLocation());
772 evalArguments(posargs, kwargs, env);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000773 return function.call(posargs.build(), ImmutableMap.copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000774 }
775
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000776 /**
777 * Returns the value of the argument 'name' (or null if there is none).
778 * This function is used to associate debugging information to rules created by skylark "macros".
779 */
780 @Nullable
781 public String getNameArg() {
brandjon990622b2017-07-11 19:56:45 +0200782 for (Argument.Passed arg : arguments) {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000783 if (arg != null) {
784 String name = arg.getName();
785 if (name != null && name.equals("name")) {
786 Expression expr = arg.getValue();
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000787 return (expr instanceof StringLiteral) ? ((StringLiteral) expr).getValue() : null;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000788 }
789 }
790 }
791 return null;
792 }
793
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100794 @Override
795 public void accept(SyntaxTreeVisitor visitor) {
796 visitor.visit(this);
797 }
798
799 @Override
Laurent Le Brun2e78d612015-04-15 09:06:46 +0000800 void validate(ValidationEnvironment env) throws EvalException {
fzaiser8c27a892017-08-11 17:26:44 +0200801 function.validate(env);
brandjon990622b2017-07-11 19:56:45 +0200802 for (Argument.Passed arg : arguments) {
Laurent Le Brune102a2d2017-01-02 12:06:18 +0000803 arg.getValue().validate(env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100804 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100805 }
Florian Weikert90a15962015-09-11 13:43:10 +0000806
807 @Override
808 protected boolean isNewScope() {
809 return true;
810 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100811}