blob: c301061dc3d35bc8c78073b7ddc5f6e3b8a159c6 [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;
24import com.google.common.collect.Lists;
25import com.google.devtools.build.lib.events.Location;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000026import com.google.devtools.build.lib.skylarkinterface.Param;
John Field585d1a02015-12-16 16:03:52 +000027import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
Jon Brandvein6696db22016-10-04 20:18:19 +000028import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
John Field585d1a02015-12-16 16:03:52 +000029import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.lib.syntax.EvalException.EvalExceptionWithJavaCause;
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +000031import com.google.devtools.build.lib.syntax.Runtime.NoneType;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000032import com.google.devtools.build.lib.util.Pair;
33import com.google.devtools.build.lib.util.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010034import com.google.devtools.build.lib.util.StringUtilities;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import java.lang.reflect.InvocationTargetException;
36import java.lang.reflect.Method;
37import java.lang.reflect.Modifier;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010038import java.util.Collections;
39import java.util.HashMap;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000040import java.util.HashSet;
Pedro Liberal Fernandez837dbc12016-08-18 14:13:01 +000041import java.util.LinkedHashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042import java.util.List;
43import java.util.Map;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000044import java.util.Set;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010045import java.util.concurrent.ExecutionException;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000046import javax.annotation.Nullable;
47
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048/**
49 * Syntax node for a function call expression.
50 */
51public final class FuncallExpression extends Expression {
52
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010053 /**
54 * A value class to store Methods with their corresponding SkylarkCallable annotations.
55 * This is needed because the annotation is sometimes in a superclass.
56 */
57 public static final class MethodDescriptor {
58 private final Method method;
59 private final SkylarkCallable annotation;
60
61 private MethodDescriptor(Method method, SkylarkCallable annotation) {
62 this.method = method;
63 this.annotation = annotation;
64 }
65
66 Method getMethod() {
67 return method;
68 }
69
70 /**
71 * Returns the SkylarkCallable annotation corresponding to this method.
72 */
73 public SkylarkCallable getAnnotation() {
74 return annotation;
75 }
76 }
77
78 private static final LoadingCache<Class<?>, Map<String, List<MethodDescriptor>>> methodCache =
79 CacheBuilder.newBuilder()
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000080 .initialCapacity(10)
81 .maximumSize(100)
82 .build(
83 new CacheLoader<Class<?>, Map<String, List<MethodDescriptor>>>() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010084
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000085 @Override
86 public Map<String, List<MethodDescriptor>> load(Class<?> key) throws Exception {
87 Map<String, List<MethodDescriptor>> methodMap = new HashMap<>();
88 for (Method method : key.getMethods()) {
89 // Synthetic methods lead to false multiple matches
90 if (method.isSynthetic()) {
91 continue;
92 }
Jon Brandvein6696db22016-10-04 20:18:19 +000093 SkylarkCallable callable = SkylarkInterfaceUtils.getSkylarkCallable(method);
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000094 if (callable == null) {
95 continue;
96 }
97 Preconditions.checkArgument(
98 callable.parameters().length == 0 || !callable.structField(),
99 "Method "
100 + method
Jon Brandveinead58ae2016-09-29 18:41:10 +0000101 + " was annotated with both structField and parameters.");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000102 if (callable.parameters().length > 0 || callable.mandatoryPositionals() >= 0) {
103 int nbArgs =
104 callable.parameters().length
105 + Math.max(0, callable.mandatoryPositionals());
106 Preconditions.checkArgument(
107 nbArgs == method.getParameterTypes().length,
108 "Method "
109 + method
110 + " was annotated for "
111 + nbArgs
112 + " arguments "
113 + "but accept only "
114 + method.getParameterTypes().length
115 + " arguments.");
116 }
117 String name = callable.name();
118 if (name.isEmpty()) {
119 name = StringUtilities.toPythonStyleFunctionName(method.getName());
120 }
121 if (methodMap.containsKey(name)) {
122 methodMap.get(name).add(new MethodDescriptor(method, callable));
123 } else {
124 methodMap.put(
125 name, Lists.newArrayList(new MethodDescriptor(method, callable)));
126 }
127 }
128 return ImmutableMap.copyOf(methodMap);
129 }
130 });
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100131
132 /**
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000133 * Returns a map of methods and corresponding SkylarkCallable annotations of the methods of the
134 * classObj class reachable from Skylark.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100135 */
136 public static ImmutableMap<Method, SkylarkCallable> collectSkylarkMethodsWithAnnotation(
137 Class<?> classObj) {
138 ImmutableMap.Builder<Method, SkylarkCallable> methodMap = ImmutableMap.builder();
139 for (Method method : classObj.getMethods()) {
140 // Synthetic methods lead to false multiple matches
141 if (!method.isSynthetic()) {
Jon Brandvein6696db22016-10-04 20:18:19 +0000142 SkylarkCallable annotation = SkylarkInterfaceUtils.getSkylarkCallable(classObj, method);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100143 if (annotation != null) {
144 methodMap.put(method, annotation);
145 }
146 }
147 }
148 return methodMap.build();
149 }
150
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000151 private static class ArgumentListConversionResult {
152 private final ImmutableList<Object> arguments;
153 private final String error;
154
155 private ArgumentListConversionResult(ImmutableList<Object> arguments, String error) {
156 this.arguments = arguments;
157 this.error = error;
158 }
159
160 public static ArgumentListConversionResult fromArgumentList(ImmutableList<Object> arguments) {
161 return new ArgumentListConversionResult(arguments, null);
162 }
163
164 public static ArgumentListConversionResult fromError(String error) {
165 return new ArgumentListConversionResult(null, error);
166 }
167
168 public String getError() {
169 return error;
170 }
171
172 public ImmutableList<Object> getArguments() {
173 return arguments;
174 }
175 }
176
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100177 /**
178 * An exception class to handle exceptions in direct Java API calls.
179 */
180 public static final class FuncallException extends Exception {
181
182 public FuncallException(String msg) {
183 super(msg);
184 }
185 }
186
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000187 @Nullable private final Expression obj;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100188
Florian Weikert6f864c32015-07-23 11:26:39 +0000189 private final Identifier func;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100190
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000191 private final List<Argument.Passed> args;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100192
193 private final int numPositionalArgs;
194
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000195 public FuncallExpression(@Nullable Expression obj, Identifier func,
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000196 List<Argument.Passed> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100197 this.obj = obj;
198 this.func = func;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000199 this.args = args; // we assume the parser validated it with Argument#validateFuncallArguments()
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100200 this.numPositionalArgs = countPositionalArguments();
201 }
202
Florian Weikert6f864c32015-07-23 11:26:39 +0000203 public FuncallExpression(Identifier func, List<Argument.Passed> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100204 this(null, func, args);
205 }
206
207 /**
208 * Returns the number of positional arguments.
209 */
210 private int countPositionalArguments() {
211 int num = 0;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000212 for (Argument.Passed arg : args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100213 if (arg.isPositional()) {
214 num++;
215 }
216 }
217 return num;
218 }
219
220 /**
221 * Returns the function expression.
222 */
Florian Weikert6f864c32015-07-23 11:26:39 +0000223 public Identifier getFunction() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100224 return func;
225 }
226
227 /**
228 * Returns the object the function called on.
229 * It's null if the function is not called on an object.
230 */
231 public Expression getObject() {
232 return obj;
233 }
234
235 /**
236 * Returns an (immutable, ordered) list of function arguments. The first n are
237 * positional and the remaining ones are keyword args, where n =
238 * getNumPositionalArguments().
239 */
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000240 public List<Argument.Passed> getArguments() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100241 return Collections.unmodifiableList(args);
242 }
243
244 /**
245 * Returns the number of arguments which are positional; the remainder are
246 * keyword arguments.
247 */
248 public int getNumPositionalArguments() {
249 return numPositionalArgs;
250 }
251
252 @Override
253 public String toString() {
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000254 StringBuilder sb = new StringBuilder();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100255 if (obj != null) {
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000256 sb.append(obj).append(".");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100257 }
Florian Weikert90a15962015-09-11 13:43:10 +0000258 sb.append(func);
Florian Weikert2591e192015-10-05 14:24:51 +0000259 Printer.printList(sb, args, "(", ", ", ")", /* singletonTerminator */ null,
260 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT,
261 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH);
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000262 return sb.toString();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100263 }
264
265 /**
Laurent Le Brun57badf42017-01-02 15:12:24 +0000266 * Returns the list of Skylark callable Methods of objClass with the given name and argument
267 * number.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100268 */
Laurent Le Brun57badf42017-01-02 15:12:24 +0000269 public static List<MethodDescriptor> getMethods(Class<?> objClass, String methodName) {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000270 try {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000271 return methodCache.get(objClass).get(methodName);
Laurent Le Brun427bd972015-05-20 13:28:44 +0000272 } catch (ExecutionException e) {
Laurent Le Brun57badf42017-01-02 15:12:24 +0000273 throw new IllegalStateException("method invocation failed: " + e);
Laurent Le Brun427bd972015-05-20 13:28:44 +0000274 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100275 }
276
277 /**
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000278 * Returns a set of the Skylark name of all Skylark callable methods for object of type {@code
279 * objClass}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100280 */
Laurent Le Brun57badf42017-01-02 15:12:24 +0000281 public static Set<String> getMethodNames(Class<?> objClass) {
282 try {
283 return methodCache.get(objClass).keySet();
284 } catch (ExecutionException e) {
285 throw new IllegalStateException("method invocation failed: " + e);
286 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100287 }
288
289 static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000290 Object[] args, Location loc, Environment env) throws EvalException {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000291 try {
292 Method method = methodDescriptor.getMethod();
293 if (obj == null && !Modifier.isStatic(method.getModifiers())) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000294 throw new EvalException(loc, "method '" + methodName + "' is not static");
Laurent Le Brun427bd972015-05-20 13:28:44 +0000295 }
296 // This happens when the interface is public but the implementation classes
297 // have reduced visibility.
298 method.setAccessible(true);
299 Object result = method.invoke(obj, args);
300 if (method.getReturnType().equals(Void.TYPE)) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000301 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000302 }
303 if (result == null) {
304 if (methodDescriptor.getAnnotation().allowReturnNones()) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000305 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000306 } else {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000307 throw new EvalException(
308 loc,
309 "method invocation returned None, please file a bug report: "
310 + methodName
311 + Printer.listString(ImmutableList.copyOf(args), "(", ", ", ")", null));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000312 }
313 }
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000314 // TODO(bazel-team): get rid of this, by having everyone use the Skylark data structures
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000315 result = SkylarkType.convertToSkylark(result, method, env);
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000316 if (result != null && !EvalUtils.isSkylarkAcceptable(result.getClass())) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000317 throw new EvalException(
318 loc,
319 Printer.format(
320 "method '%s' returns an object of invalid type %r", methodName, result.getClass()));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000321 }
322 return result;
323 } catch (IllegalAccessException e) {
324 // TODO(bazel-team): Print a nice error message. Maybe the method exists
325 // and an argument is missing or has the wrong type.
326 throw new EvalException(loc, "Method invocation failed: " + e);
327 } catch (InvocationTargetException e) {
328 if (e.getCause() instanceof FuncallException) {
329 throw new EvalException(loc, e.getCause().getMessage());
330 } else if (e.getCause() != null) {
331 throw new EvalExceptionWithJavaCause(loc, e.getCause());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100332 } else {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000333 // This is unlikely to happen
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000334 throw new EvalException(loc, "method invocation failed: " + e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100335 }
336 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100337 }
338
339 // TODO(bazel-team): If there's exactly one usable method, this works. If there are multiple
340 // matching methods, it still can be a problem. Figure out how the Java compiler does it
341 // exactly and copy that behaviour.
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000342 // Throws an EvalException when it cannot find a matching function.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000343 private Pair<MethodDescriptor, List<Object>> findJavaMethod(
344 Class<?> objClass, String methodName, List<Object> args, Map<String, Object> kwargs)
345 throws EvalException {
346 Pair<MethodDescriptor, List<Object>> matchingMethod = null;
Laurent Le Brun57badf42017-01-02 15:12:24 +0000347 List<MethodDescriptor> methods = getMethods(objClass, methodName);
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000348 ArgumentListConversionResult argumentListConversionResult = null;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000349 if (methods != null) {
350 for (MethodDescriptor method : methods) {
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000351 if (method.getAnnotation().structField()) {
352 return new Pair<>(method, null);
353 } else {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000354 argumentListConversionResult = convertArgumentList(args, kwargs, method);
355 if (argumentListConversionResult.getArguments() != null) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000356 if (matchingMethod == null) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000357 matchingMethod =
358 new Pair<MethodDescriptor, List<Object>>(
359 method, argumentListConversionResult.getArguments());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000360 } else {
361 throw new EvalException(
362 getLocation(),
363 String.format(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000364 "type '%s' has multiple matches for function %s",
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000365 EvalUtils.getDataTypeNameFromClass(objClass), formatMethod(args, kwargs)));
366 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100367 }
368 }
369 }
Laurent Le Brun427bd972015-05-20 13:28:44 +0000370 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000371 if (matchingMethod == null) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000372 String errorMessage;
373 if (argumentListConversionResult == null || argumentListConversionResult.getError() == null) {
374 errorMessage =
375 String.format(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000376 "type '%s' has no method %s",
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000377 EvalUtils.getDataTypeNameFromClass(objClass), formatMethod(args, kwargs));
378
379 } else {
380 errorMessage =
381 String.format(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000382 "%s, in method %s of '%s'",
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000383 argumentListConversionResult.getError(),
384 formatMethod(args, kwargs),
385 EvalUtils.getDataTypeNameFromClass(objClass));
386 }
387 throw new EvalException(getLocation(), errorMessage);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100388 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000389 return matchingMethod;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100390 }
391
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000392 private static SkylarkType getType(Param param) {
393 SkylarkType type =
394 param.generic1() != Object.class
395 ? SkylarkType.of(param.type(), param.generic1())
396 : SkylarkType.of(param.type());
397 return type;
398 }
399
400 /**
401 * Constructs the parameters list to actually pass to the method, filling with default values if
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000402 * any. If there is a type or argument mismatch, returns a result containing an error message.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000403 */
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000404 private ArgumentListConversionResult convertArgumentList(
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000405 List<Object> args, Map<String, Object> kwargs, MethodDescriptor method) {
406 ImmutableList.Builder<Object> builder = ImmutableList.builder();
407 Class<?>[] params = method.getMethod().getParameterTypes();
408 SkylarkCallable callable = method.getAnnotation();
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000409 int mandatoryPositionals = callable.mandatoryPositionals();
410 if (mandatoryPositionals < 0) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000411 if (callable.parameters().length > 0) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000412 mandatoryPositionals = 0;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000413 } else {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000414 mandatoryPositionals = params.length;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000415 }
416 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000417 if (mandatoryPositionals > args.size()
418 || args.size() > mandatoryPositionals + callable.parameters().length) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000419 return ArgumentListConversionResult.fromError("too many arguments");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000420 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000421 // First process the legacy positional parameters.
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000422 int i = 0;
423 if (mandatoryPositionals > 0) {
424 for (Class<?> param : params) {
425 Object value = args.get(i);
426 if (!param.isAssignableFrom(value.getClass())) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000427 return ArgumentListConversionResult.fromError(
428 String.format(
Carmi Grushko12291602016-10-26 19:07:41 +0000429 "Cannot convert parameter at position %d from type %s to type %s",
430 i, EvalUtils.getDataTypeName(value), param.toString()));
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000431 }
432 builder.add(value);
433 i++;
434 if (mandatoryPositionals >= 0 && i >= mandatoryPositionals) {
435 // Stops for specified parameters instead.
436 break;
437 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000438 }
439 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000440
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000441 // Then the parameters specified in callable.parameters()
442 Set<String> keys = new HashSet<>(kwargs.keySet());
443 for (Param param : callable.parameters()) {
444 SkylarkType type = getType(param);
Googlere5da53c2016-09-21 12:34:44 +0000445 if (param.noneable()) {
446 type = SkylarkType.Union.of(type, SkylarkType.NONE);
447 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000448 Object value = null;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000449 if (i < args.size()) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000450 value = args.get(i);
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000451 if (!param.positional()) {
452 return ArgumentListConversionResult.fromError(
453 String.format("Parameter '%s' is not positional", param.name()));
454 } else if (!type.contains(value)) {
455 return ArgumentListConversionResult.fromError(
456 String.format(
457 "Cannot convert parameter '%s' to type %s", param.name(), type.toString()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000458 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000459 i++;
460 } else if (param.named() && keys.remove(param.name())) {
461 // Named parameters
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000462 value = kwargs.get(param.name());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000463 if (!type.contains(value)) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000464 return ArgumentListConversionResult.fromError(
465 String.format(
466 "Cannot convert parameter '%s' to type %s", param.name(), type.toString()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000467 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000468 } else {
469 // Use default value
470 if (param.defaultValue().isEmpty()) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000471 return ArgumentListConversionResult.fromError(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000472 String.format("parameter '%s' has no default value", param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000473 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000474 value = SkylarkSignatureProcessor.getDefaultValue(param, null);
475 }
476 builder.add(value);
477 if (!param.noneable() && value instanceof NoneType) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000478 return ArgumentListConversionResult.fromError(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000479 String.format("parameter '%s' cannot be None", param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000480 }
481 }
482 if (i < args.size() || !keys.isEmpty()) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000483 return ArgumentListConversionResult.fromError("too many arguments");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000484 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000485 return ArgumentListConversionResult.fromArgumentList(builder.build());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000486 }
487
488 private String formatMethod(List<Object> args, Map<String, Object> kwargs) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100489 StringBuilder sb = new StringBuilder();
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000490 sb.append(func.getName()).append("(");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100491 boolean first = true;
492 for (Object obj : args) {
493 if (!first) {
494 sb.append(", ");
495 }
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000496 sb.append(EvalUtils.getDataTypeName(obj));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100497 first = false;
498 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000499 for (Map.Entry<String, Object> kwarg : kwargs.entrySet()) {
500 if (!first) {
501 sb.append(", ");
502 }
503 sb.append(EvalUtils.getDataTypeName(kwarg.getValue()));
504 sb.append(" ");
505 sb.append(kwarg.getKey());
Googler344449d2016-09-15 18:25:35 +0000506 first = false;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000507 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100508 return sb.append(")").toString();
509 }
510
511 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000512 * Add one argument to the keyword map, registering a duplicate in case of conflict.
513 *
514 * <p>public for reflection by the compiler and calls from compiled functions
515 */
516 public static void addKeywordArg(
517 Map<String, Object> kwargs,
518 String name,
519 Object value,
520 ImmutableList.Builder<String> duplicates) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100521 if (kwargs.put(name, value) != null) {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000522 duplicates.add(name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100523 }
524 }
525
526 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000527 * Add multiple arguments to the keyword map (**kwargs), registering duplicates
528 *
529 * <p>public for reflection by the compiler and calls from compiled functions
530 */
531 public static void addKeywordArgs(
532 Map<String, Object> kwargs,
533 Object items,
534 ImmutableList.Builder<String> duplicates,
535 Location location)
536 throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100537 if (!(items instanceof Map<?, ?>)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000538 throw new EvalException(
539 location,
Laurent Le Brun1159cc22017-01-04 12:27:39 +0000540 "argument after ** must be a dictionary, not '" + EvalUtils.getDataTypeName(items) + "'");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100541 }
542 for (Map.Entry<?, ?> entry : ((Map<?, ?>) items).entrySet()) {
543 if (!(entry.getKey() instanceof String)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000544 throw new EvalException(
Laurent Le Brun1159cc22017-01-04 12:27:39 +0000545 location,
546 "keywords must be strings, not '" + EvalUtils.getDataTypeName(entry.getKey()) + "'");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100547 }
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000548 addKeywordArg(kwargs, (String) entry.getKey(), entry.getValue(), duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100549 }
550 }
551
Florian Weikert33f819b2015-11-09 18:15:55 +0000552 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000553 * Checks whether the given object is a {@link BaseFunction}.
554 *
555 * <p>Public for reflection by the compiler and access from generated byte code.
556 *
557 * @throws EvalException If not a BaseFunction.
558 */
559 public static BaseFunction checkCallable(Object functionValue, Location location)
560 throws EvalException {
561 if (functionValue instanceof BaseFunction) {
562 return (BaseFunction) functionValue;
563 } else {
564 throw new EvalException(
565 location, "'" + EvalUtils.getDataTypeName(functionValue) + "' object is not callable");
566 }
567 }
568
569 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000570 * Check the list from the builder and report an {@link EvalException} if not empty.
571 *
572 * <p>public for reflection by the compiler and calls from compiled functions
573 */
574 public static void checkDuplicates(
575 ImmutableList.Builder<String> duplicates, String function, Location location)
576 throws EvalException {
577 List<String> dups = duplicates.build();
578 if (!dups.isEmpty()) {
579 throw new EvalException(
580 location,
581 "duplicate keyword"
582 + (dups.size() > 1 ? "s" : "")
583 + " '"
584 + Joiner.on("', '").join(dups)
585 + "' in call to "
586 + function);
587 }
588 }
589
590 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000591 * Call a method depending on the type of an object it is called on.
592 *
593 * <p>Public for reflection by the compiler and access from generated byte code.
594 *
595 * @param positionals The first object is expected to be the object the method is called on.
596 * @param call the original expression that caused this call, needed for rules especially
597 */
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000598 public Object invokeObjectMethod(
Florian Weikert33f819b2015-11-09 18:15:55 +0000599 String method,
600 ImmutableList<Object> positionals,
601 ImmutableMap<String, Object> keyWordArgs,
602 FuncallExpression call,
603 Environment env)
604 throws EvalException, InterruptedException {
605 Location location = call.getLocation();
606 Object value = positionals.get(0);
607 ImmutableList<Object> positionalArgs = positionals.subList(1, positionals.size());
608 BaseFunction function = Runtime.getFunction(EvalUtils.getSkylarkType(value.getClass()), method);
609 if (function != null) {
610 if (!isNamespace(value.getClass())) {
611 // Use self as an implicit parameter in front.
612 positionalArgs = positionals;
613 }
614 return function.call(
615 positionalArgs, ImmutableMap.<String, Object>copyOf(keyWordArgs), call, env);
616 } else if (value instanceof ClassObject) {
617 Object fieldValue = ((ClassObject) value).getValue(method);
618 if (fieldValue == null) {
619 throw new EvalException(location, String.format("struct has no method '%s'", method));
620 }
621 if (!(fieldValue instanceof BaseFunction)) {
622 throw new EvalException(
623 location, String.format("struct field '%s' is not a function", method));
624 }
625 function = (BaseFunction) fieldValue;
626 return function.call(
627 positionalArgs, ImmutableMap.<String, Object>copyOf(keyWordArgs), call, env);
Laurent Le Brun88df1f52015-12-23 13:31:44 +0000628 } else {
Florian Weikert33f819b2015-11-09 18:15:55 +0000629 // When calling a Java method, the name is not in the Environment,
630 // so evaluating 'func' would fail.
631 Class<?> objClass;
632 Object obj;
633 if (value instanceof Class<?>) {
634 // Static call
635 obj = null;
636 objClass = (Class<?>) value;
637 } else {
638 obj = value;
639 objClass = value.getClass();
640 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000641 Pair<MethodDescriptor, List<Object>> javaMethod =
642 call.findJavaMethod(objClass, method, positionalArgs, keyWordArgs);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000643 if (javaMethod.first.getAnnotation().structField()) {
644 // Not a method but a callable attribute
645 try {
646 return callFunction(javaMethod.first.getMethod().invoke(obj), env);
647 } catch (IllegalAccessException e) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000648 throw new EvalException(getLocation(), "method invocation failed: " + e);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000649 } catch (InvocationTargetException e) {
650 if (e.getCause() instanceof FuncallException) {
651 throw new EvalException(getLocation(), e.getCause().getMessage());
652 } else if (e.getCause() != null) {
653 throw new EvalExceptionWithJavaCause(getLocation(), e.getCause());
654 } else {
655 // This is unlikely to happen
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000656 throw new EvalException(getLocation(), "method invocation failed: " + e);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000657 }
658 }
659 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000660 return callMethod(javaMethod.first, method, obj, javaMethod.second.toArray(), location, env);
Florian Weikert33f819b2015-11-09 18:15:55 +0000661 }
662 }
663
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000664 @SuppressWarnings("unchecked")
665 private void evalArguments(ImmutableList.Builder<Object> posargs, Map<String, Object> kwargs,
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000666 Environment env)
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000667 throws EvalException, InterruptedException {
Ulf Adams07dba942015-03-05 14:47:37 +0000668 ImmutableList.Builder<String> duplicates = new ImmutableList.Builder<>();
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000669 // Iterate over the arguments. We assume all positional arguments come before any keyword
670 // or star arguments, because the argument list was already validated by
671 // Argument#validateFuncallArguments, as called by the Parser,
672 // which should be the only place that build FuncallExpression-s.
673 for (Argument.Passed arg : args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100674 Object value = arg.getValue().eval(env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100675 if (arg.isPositional()) {
676 posargs.add(value);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000677 } else if (arg.isStar()) { // expand the starArg
Laurent Le Brun54b9d2c2017-02-20 14:04:57 +0000678 if (!(value instanceof Iterable)) {
679 throw new EvalException(
680 getLocation(),
681 "argument after * must be an iterable, not " + EvalUtils.getDataTypeName(value));
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000682 }
Laurent Le Brun54b9d2c2017-02-20 14:04:57 +0000683 posargs.addAll((Iterable<Object>) value);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000684 } else if (arg.isStarStar()) { // expand the kwargs
Florian Weikert33f819b2015-11-09 18:15:55 +0000685 addKeywordArgs(kwargs, value, duplicates, getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100686 } else {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000687 addKeywordArg(kwargs, arg.getName(), value, duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100688 }
689 }
Florian Weikert33f819b2015-11-09 18:15:55 +0000690 checkDuplicates(duplicates, func.getName(), getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100691 }
692
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000693 @VisibleForTesting
694 public static boolean isNamespace(Class<?> classObject) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100695 return classObject.isAnnotationPresent(SkylarkModule.class)
696 && classObject.getAnnotation(SkylarkModule.class).namespace();
697 }
698
699 @Override
Florian Weikert90a15962015-09-11 13:43:10 +0000700 Object doEval(Environment env) throws EvalException, InterruptedException {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000701 return (obj != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000702 }
703
704 /**
705 * Invokes obj.func() and returns the result.
706 */
707 private Object invokeObjectMethod(Environment env) throws EvalException, InterruptedException {
708 Object objValue = obj.eval(env);
Ulf Adams07dba942015-03-05 14:47:37 +0000709 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000710 posargs.add(objValue);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000711 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
712 // 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 +0000713 Map<String, Object> kwargs = new LinkedHashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000714 evalArguments(posargs, kwargs, env);
715 return invokeObjectMethod(
716 func.getName(), posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000717 }
Googlera34d5072015-03-05 13:51:28 +0000718
Florian Weikert3f610e82015-08-18 14:37:46 +0000719 /**
720 * Invokes func() and returns the result.
721 */
722 private Object invokeGlobalFunction(Environment env) throws EvalException, InterruptedException {
723 Object funcValue = func.eval(env);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000724 return callFunction(funcValue, env);
725 }
726
727 /**
728 * Calls a function object
729 */
730 private Object callFunction(Object funcValue, Environment env)
731 throws EvalException, InterruptedException {
Florian Weikert3f610e82015-08-18 14:37:46 +0000732 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
733 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
734 // 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 +0000735 Map<String, Object> kwargs = new LinkedHashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000736 BaseFunction function = checkCallable(funcValue, getLocation());
737 evalArguments(posargs, kwargs, env);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000738 return function.call(posargs.build(), ImmutableMap.copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000739 }
740
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000741 /**
742 * Returns the value of the argument 'name' (or null if there is none).
743 * This function is used to associate debugging information to rules created by skylark "macros".
744 */
745 @Nullable
746 public String getNameArg() {
747 for (Argument.Passed arg : args) {
748 if (arg != null) {
749 String name = arg.getName();
750 if (name != null && name.equals("name")) {
751 Expression expr = arg.getValue();
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000752 return (expr instanceof StringLiteral) ? ((StringLiteral) expr).getValue() : null;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000753 }
754 }
755 }
756 return null;
757 }
758
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100759 @Override
760 public void accept(SyntaxTreeVisitor visitor) {
761 visitor.visit(this);
762 }
763
764 @Override
Laurent Le Brun2e78d612015-04-15 09:06:46 +0000765 void validate(ValidationEnvironment env) throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100766 if (obj != null) {
Laurent Le Brun964d8d52015-04-13 12:15:04 +0000767 obj.validate(env);
Laurent Le Brune102a2d2017-01-02 12:06:18 +0000768 } else {
769 func.validate(env);
770 }
771 for (Argument.Passed arg : args) {
772 arg.getValue().validate(env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100773 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100774 }
Florian Weikert90a15962015-09-11 13:43:10 +0000775
776 @Override
777 protected boolean isNewScope() {
778 return true;
779 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100780}