blob: 8d1eb56f9c88950ab6224f586f4feb92da574bc8 [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. All rights reserved.
2//
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;
26import com.google.devtools.build.lib.syntax.EvalException.EvalExceptionWithJavaCause;
27import com.google.devtools.build.lib.util.StringUtilities;
28
29import java.lang.reflect.InvocationTargetException;
30import java.lang.reflect.Method;
31import java.lang.reflect.Modifier;
32import java.util.ArrayList;
33import java.util.Collections;
34import java.util.HashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import java.util.List;
36import java.util.Map;
37import java.util.concurrent.ExecutionException;
38
39/**
40 * Syntax node for a function call expression.
41 */
42public final class FuncallExpression extends Expression {
43
44 private static enum ArgConversion {
45 FROM_SKYLARK,
46 TO_SKYLARK,
47 NO_CONVERSION
48 }
49
50 /**
51 * A value class to store Methods with their corresponding SkylarkCallable annotations.
52 * This is needed because the annotation is sometimes in a superclass.
53 */
54 public static final class MethodDescriptor {
55 private final Method method;
56 private final SkylarkCallable annotation;
57
58 private MethodDescriptor(Method method, SkylarkCallable annotation) {
59 this.method = method;
60 this.annotation = annotation;
61 }
62
63 Method getMethod() {
64 return method;
65 }
66
67 /**
68 * Returns the SkylarkCallable annotation corresponding to this method.
69 */
70 public SkylarkCallable getAnnotation() {
71 return annotation;
72 }
73 }
74
75 private static final LoadingCache<Class<?>, Map<String, List<MethodDescriptor>>> methodCache =
76 CacheBuilder.newBuilder()
77 .initialCapacity(10)
78 .maximumSize(100)
79 .build(new CacheLoader<Class<?>, Map<String, List<MethodDescriptor>>>() {
80
81 @Override
82 public Map<String, List<MethodDescriptor>> load(Class<?> key) throws Exception {
83 Map<String, List<MethodDescriptor>> methodMap = new HashMap<>();
84 for (Method method : key.getMethods()) {
85 // Synthetic methods lead to false multiple matches
86 if (method.isSynthetic()) {
87 continue;
88 }
89 SkylarkCallable callable = getAnnotationFromParentClass(
90 method.getDeclaringClass(), method);
91 if (callable == null) {
92 continue;
93 }
94 String name = callable.name();
95 if (name.isEmpty()) {
96 name = StringUtilities.toPythonStyleFunctionName(method.getName());
97 }
98 String signature = name + "#" + method.getParameterTypes().length;
99 if (methodMap.containsKey(signature)) {
100 methodMap.get(signature).add(new MethodDescriptor(method, callable));
101 } else {
102 methodMap.put(signature, Lists.newArrayList(new MethodDescriptor(method, callable)));
103 }
104 }
105 return ImmutableMap.copyOf(methodMap);
106 }
107 });
108
109 /**
110 * Returns a map of methods and corresponding SkylarkCallable annotations
111 * of the methods of the classObj class reachable from Skylark.
112 */
113 public static ImmutableMap<Method, SkylarkCallable> collectSkylarkMethodsWithAnnotation(
114 Class<?> classObj) {
115 ImmutableMap.Builder<Method, SkylarkCallable> methodMap = ImmutableMap.builder();
116 for (Method method : classObj.getMethods()) {
117 // Synthetic methods lead to false multiple matches
118 if (!method.isSynthetic()) {
119 SkylarkCallable annotation = getAnnotationFromParentClass(classObj, method);
120 if (annotation != null) {
121 methodMap.put(method, annotation);
122 }
123 }
124 }
125 return methodMap.build();
126 }
127
128 private static SkylarkCallable getAnnotationFromParentClass(Class<?> classObj, Method method) {
129 boolean keepLooking = false;
130 try {
131 Method superMethod = classObj.getMethod(method.getName(), method.getParameterTypes());
132 if (classObj.isAnnotationPresent(SkylarkModule.class)
133 && superMethod.isAnnotationPresent(SkylarkCallable.class)) {
134 return superMethod.getAnnotation(SkylarkCallable.class);
135 } else {
136 keepLooking = true;
137 }
138 } catch (NoSuchMethodException e) {
139 // The class might not have the specified method, so an exceptions is OK.
140 keepLooking = true;
141 }
142 if (keepLooking) {
143 if (classObj.getSuperclass() != null) {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000144 SkylarkCallable annotation =
145 getAnnotationFromParentClass(classObj.getSuperclass(), method);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100146 if (annotation != null) {
147 return annotation;
148 }
149 }
150 for (Class<?> interfaceObj : classObj.getInterfaces()) {
151 SkylarkCallable annotation = getAnnotationFromParentClass(interfaceObj, method);
152 if (annotation != null) {
153 return annotation;
154 }
155 }
156 }
157 return null;
158 }
159
160 /**
161 * An exception class to handle exceptions in direct Java API calls.
162 */
163 public static final class FuncallException extends Exception {
164
165 public FuncallException(String msg) {
166 super(msg);
167 }
168 }
169
170 private final Expression obj;
171
Florian Weikert6f864c32015-07-23 11:26:39 +0000172 private final Identifier func;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100173
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000174 private final List<Argument.Passed> args;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100175
176 private final int numPositionalArgs;
177
178 /**
179 * Note: the grammar definition restricts the function value in a function
180 * call expression to be a global identifier; however, the representation of
181 * values in the interpreter is flexible enough to allow functions to be
182 * arbitrary expressions. In any case, the "func" expression is always
183 * evaluated, so functions and variables share a common namespace.
184 */
Florian Weikert6f864c32015-07-23 11:26:39 +0000185 public FuncallExpression(Expression obj, Identifier func,
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000186 List<Argument.Passed> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100187 this.obj = obj;
188 this.func = func;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000189 this.args = args; // we assume the parser validated it with Argument#validateFuncallArguments()
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100190 this.numPositionalArgs = countPositionalArguments();
191 }
192
193 /**
194 * Note: the grammar definition restricts the function value in a function
195 * call expression to be a global identifier; however, the representation of
196 * values in the interpreter is flexible enough to allow functions to be
197 * arbitrary expressions. In any case, the "func" expression is always
198 * evaluated, so functions and variables share a common namespace.
199 */
Florian Weikert6f864c32015-07-23 11:26:39 +0000200 public FuncallExpression(Identifier func, List<Argument.Passed> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100201 this(null, func, args);
202 }
203
204 /**
205 * Returns the number of positional arguments.
206 */
207 private int countPositionalArguments() {
208 int num = 0;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000209 for (Argument.Passed arg : args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100210 if (arg.isPositional()) {
211 num++;
212 }
213 }
214 return num;
215 }
216
217 /**
218 * Returns the function expression.
219 */
Florian Weikert6f864c32015-07-23 11:26:39 +0000220 public Identifier getFunction() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100221 return func;
222 }
223
224 /**
225 * Returns the object the function called on.
226 * It's null if the function is not called on an object.
227 */
228 public Expression getObject() {
229 return obj;
230 }
231
232 /**
233 * Returns an (immutable, ordered) list of function arguments. The first n are
234 * positional and the remaining ones are keyword args, where n =
235 * getNumPositionalArguments().
236 */
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000237 public List<Argument.Passed> getArguments() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100238 return Collections.unmodifiableList(args);
239 }
240
241 /**
242 * Returns the number of arguments which are positional; the remainder are
243 * keyword arguments.
244 */
245 public int getNumPositionalArguments() {
246 return numPositionalArgs;
247 }
248
Laurent Le Brun003bb882015-05-19 11:32:17 +0000249 private String functionName() {
250 String name = func.getName();
251 if (name.equals("$slice")) {
252 return "operator [:]";
253 } else if (name.equals("$index")) {
254 return "operator []";
255 } else {
256 return "function '" + name + "'";
257 }
258 }
259
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100260 @Override
261 public String toString() {
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000262 if (func.getName().equals("$slice")) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100263 return obj + "[" + args.get(0) + ":" + args.get(1) + "]";
264 }
265 if (func.getName().equals("$index")) {
266 return obj + "[" + args.get(0) + "]";
267 }
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000268 StringBuilder sb = new StringBuilder();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100269 if (obj != null) {
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000270 sb.append(obj).append(".");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100271 }
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000272 Printer.printList(sb.append(func), args, "(", ", ", ")", null);
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000273 return sb.toString();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100274 }
275
276 /**
277 * Returns the list of Skylark callable Methods of objClass with the given name
278 * and argument number.
279 */
Laurent Le Brun427bd972015-05-20 13:28:44 +0000280 public static List<MethodDescriptor> getMethods(Class<?> objClass, String methodName, int argNum,
281 Location loc) throws EvalException {
282 try {
283 return methodCache.get(objClass).get(methodName + "#" + argNum);
284 } catch (ExecutionException e) {
285 throw new EvalException(loc, "Method invocation failed: " + e);
286 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100287 }
288
289 /**
290 * Returns the list of the Skylark name of all Skylark callable methods.
291 */
292 public static List<String> getMethodNames(Class<?> objClass)
293 throws ExecutionException {
294 List<String> names = new ArrayList<>();
295 for (List<MethodDescriptor> methods : methodCache.get(objClass).values()) {
296 for (MethodDescriptor method : methods) {
297 // TODO(bazel-team): store the Skylark name in the MethodDescriptor.
298 String name = method.annotation.name();
299 if (name.isEmpty()) {
300 name = StringUtilities.toPythonStyleFunctionName(method.method.getName());
301 }
302 names.add(name);
303 }
304 }
305 return names;
306 }
307
308 static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
Laurent Le Brun427bd972015-05-20 13:28:44 +0000309 Object[] args, Location loc) throws EvalException {
310 try {
311 Method method = methodDescriptor.getMethod();
312 if (obj == null && !Modifier.isStatic(method.getModifiers())) {
313 throw new EvalException(loc, "Method '" + methodName + "' is not static");
314 }
315 // This happens when the interface is public but the implementation classes
316 // have reduced visibility.
317 method.setAccessible(true);
318 Object result = method.invoke(obj, args);
319 if (method.getReturnType().equals(Void.TYPE)) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000320 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000321 }
322 if (result == null) {
323 if (methodDescriptor.getAnnotation().allowReturnNones()) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000324 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000325 } else {
326 throw new EvalException(loc,
327 "Method invocation returned None, please contact Skylark developers: " + methodName
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000328 + Printer.listString(ImmutableList.copyOf(args), "(", ", ", ")", null));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000329 }
330 }
331 result = SkylarkType.convertToSkylark(result, method);
332 if (result != null && !EvalUtils.isSkylarkImmutable(result.getClass())) {
333 throw new EvalException(loc, "Method '" + methodName
334 + "' returns a mutable object (type of " + EvalUtils.getDataTypeName(result) + ")");
335 }
336 return result;
337 } catch (IllegalAccessException e) {
338 // TODO(bazel-team): Print a nice error message. Maybe the method exists
339 // and an argument is missing or has the wrong type.
340 throw new EvalException(loc, "Method invocation failed: " + e);
341 } catch (InvocationTargetException e) {
342 if (e.getCause() instanceof FuncallException) {
343 throw new EvalException(loc, e.getCause().getMessage());
344 } else if (e.getCause() != null) {
345 throw new EvalExceptionWithJavaCause(loc, e.getCause());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100346 } else {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000347 // This is unlikely to happen
348 throw new EvalException(loc, "Method invocation failed: " + e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100349 }
350 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100351 }
352
353 // TODO(bazel-team): If there's exactly one usable method, this works. If there are multiple
354 // matching methods, it still can be a problem. Figure out how the Java compiler does it
355 // exactly and copy that behaviour.
356 // TODO(bazel-team): check if this and SkylarkBuiltInFunctions.createObject can be merged.
357 private Object invokeJavaMethod(
Francois-Rene Rideau676905a2015-08-31 15:39:09 +0000358 Object obj, Class<?> objClass, String methodName, List<Object> args, boolean hasKwArgs)
359 throws EvalException {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000360 MethodDescriptor matchingMethod = null;
361 List<MethodDescriptor> methods = getMethods(objClass, methodName, args.size(), getLocation());
362 if (methods != null) {
Francois-Rene Rideau676905a2015-08-31 15:39:09 +0000363 if (hasKwArgs) {
364 throw new EvalException(
365 func.getLocation(),
366 String.format(
367 "Keyword arguments are not allowed when calling a java method"
368 + "\nwhile calling method '%s' on object of type %s",
369 func.getName(), EvalUtils.getDataTypeNameFromClass(objClass)));
370 }
Laurent Le Brun427bd972015-05-20 13:28:44 +0000371 for (MethodDescriptor method : methods) {
372 Class<?>[] params = method.getMethod().getParameterTypes();
373 int i = 0;
374 boolean matching = true;
375 for (Class<?> param : params) {
376 if (!param.isAssignableFrom(args.get(i).getClass())) {
377 matching = false;
378 break;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100379 }
Laurent Le Brun427bd972015-05-20 13:28:44 +0000380 i++;
381 }
382 if (matching) {
383 if (matchingMethod == null) {
384 matchingMethod = method;
385 } else {
386 throw new EvalException(func.getLocation(),
387 "Multiple matching methods for " + formatMethod(methodName, args)
388 + " in " + EvalUtils.getDataTypeNameFromClass(objClass));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100389 }
390 }
391 }
Laurent Le Brun427bd972015-05-20 13:28:44 +0000392 }
393 if (matchingMethod != null && !matchingMethod.getAnnotation().structField()) {
394 return callMethod(matchingMethod, methodName, obj, args.toArray(), getLocation());
395 } else {
396 throw new EvalException(getLocation(), "No matching method found for "
397 + formatMethod(methodName, args) + " in "
398 + EvalUtils.getDataTypeNameFromClass(objClass));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100399 }
400 }
401
402 private String formatMethod(String methodName, List<Object> args) {
403 StringBuilder sb = new StringBuilder();
404 sb.append(methodName).append("(");
405 boolean first = true;
406 for (Object obj : args) {
407 if (!first) {
408 sb.append(", ");
409 }
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000410 sb.append(EvalUtils.getDataTypeName(obj));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100411 first = false;
412 }
413 return sb.append(")").toString();
414 }
415
416 /**
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000417 * Add one argument to the keyword map, registering a duplicate in case of conflict.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100418 */
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000419 private void addKeywordArg(Map<String, Object> kwargs, String name,
420 Object value, ImmutableList.Builder<String> duplicates) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100421 if (kwargs.put(name, value) != null) {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000422 duplicates.add(name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100423 }
424 }
425
426 /**
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000427 * Add multiple arguments to the keyword map (**kwargs), registering duplicates
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100428 */
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000429 private void addKeywordArgs(Map<String, Object> kwargs,
430 Object items, ImmutableList.Builder<String> duplicates) throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100431 if (!(items instanceof Map<?, ?>)) {
432 throw new EvalException(getLocation(),
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000433 "Argument after ** must be a dictionary, not " + EvalUtils.getDataTypeName(items));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100434 }
435 for (Map.Entry<?, ?> entry : ((Map<?, ?>) items).entrySet()) {
436 if (!(entry.getKey() instanceof String)) {
437 throw new EvalException(getLocation(),
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000438 "Keywords must be strings, not " + EvalUtils.getDataTypeName(entry.getKey()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100439 }
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000440 addKeywordArg(kwargs, (String) entry.getKey(), entry.getValue(), duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100441 }
442 }
443
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000444 @SuppressWarnings("unchecked")
445 private void evalArguments(ImmutableList.Builder<Object> posargs, Map<String, Object> kwargs,
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +0000446 Environment env, BaseFunction function)
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000447 throws EvalException, InterruptedException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100448 ArgConversion conversion = getArgConversion(function);
Ulf Adams07dba942015-03-05 14:47:37 +0000449 ImmutableList.Builder<String> duplicates = new ImmutableList.Builder<>();
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000450 // Iterate over the arguments. We assume all positional arguments come before any keyword
451 // or star arguments, because the argument list was already validated by
452 // Argument#validateFuncallArguments, as called by the Parser,
453 // which should be the only place that build FuncallExpression-s.
454 for (Argument.Passed arg : args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100455 Object value = arg.getValue().eval(env);
456 if (conversion == ArgConversion.FROM_SKYLARK) {
457 value = SkylarkType.convertFromSkylark(value);
458 } else if (conversion == ArgConversion.TO_SKYLARK) {
459 // We try to auto convert the type if we can.
460 value = SkylarkType.convertToSkylark(value, getLocation());
461 // We call into Skylark so we need to be sure that the caller uses the appropriate types.
462 SkylarkType.checkTypeAllowedInSkylark(value, getLocation());
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000463 } // else NO_CONVERSION
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100464 if (arg.isPositional()) {
465 posargs.add(value);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000466 } else if (arg.isStar()) { // expand the starArg
467 if (value instanceof Iterable) {
468 posargs.addAll((Iterable<Object>) value);
469 }
470 } else if (arg.isStarStar()) { // expand the kwargs
471 addKeywordArgs(kwargs, value, duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100472 } else {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000473 addKeywordArg(kwargs, arg.getName(), value, duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100474 }
475 }
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000476 List<String> dups = duplicates.build();
477 if (!dups.isEmpty()) {
478 throw new EvalException(getLocation(),
479 "duplicate keyword" + (dups.size() > 1 ? "s" : "") + " '"
480 + Joiner.on("', '").join(dups)
481 + "' in call to " + func);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100482 }
483 }
484
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000485 @VisibleForTesting
486 public static boolean isNamespace(Class<?> classObject) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100487 return classObject.isAnnotationPresent(SkylarkModule.class)
488 && classObject.getAnnotation(SkylarkModule.class).namespace();
489 }
490
491 @Override
492 Object eval(Environment env) throws EvalException, InterruptedException {
Florian Weikert6a663392015-09-02 14:04:33 +0000493 // Adds the calling rule to the stack trace of the Environment if it is a BUILD environment.
494 // There are two reasons for this:
495 // a) When using aliases in load(), the rule class name in the BUILD file will differ from
496 // the implementation name in the bzl file. Consequently, we need to store the calling name.
497 // b) We need the location of the calling rule inside the BUILD file.
498 boolean hasAddedElement =
499 env.isSkylark() ? false : env.tryAddingStackTraceRoot(new StackTraceElement(func, args));
500 try {
501 return (obj != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env);
502 } finally {
503 if (hasAddedElement) {
504 env.removeStackTraceRoot();
505 }
506 }
Florian Weikert3f610e82015-08-18 14:37:46 +0000507 }
508
509 /**
510 * Invokes obj.func() and returns the result.
511 */
512 private Object invokeObjectMethod(Environment env) throws EvalException, InterruptedException {
513 Object objValue = obj.eval(env);
Ulf Adams07dba942015-03-05 14:47:37 +0000514 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000515 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
516 // we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
517 Map<String, Object> kwargs = new HashMap<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100518
Florian Weikert3f610e82015-08-18 14:37:46 +0000519 // Strings, lists and dictionaries (maps) have functions that we want to use in
520 // MethodLibrary.
521 // For other classes, we can call the Java methods.
522 BaseFunction function =
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000523 Runtime.getFunction(EvalUtils.getSkylarkType(objValue.getClass()), func.getName());
Florian Weikert3f610e82015-08-18 14:37:46 +0000524 if (function != null) {
525 if (!isNamespace(objValue.getClass())) {
526 // Add self as an implicit parameter in front.
527 posargs.add(objValue);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100528 }
Florian Weikert3f610e82015-08-18 14:37:46 +0000529 evalArguments(posargs, kwargs, env, function);
530 return convertFromSkylark(
531 function.call(posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env),
532 env);
Francois-Rene Rideau676905a2015-08-31 15:39:09 +0000533 } else if (objValue instanceof ClassObject) {
534 Object fieldValue = ((ClassObject) objValue).getValue(func.getName());
535 if (fieldValue == null) {
536 throw new EvalException(
537 getLocation(), String.format("struct has no method '%s'", func.getName()));
538 }
539 if (!(fieldValue instanceof BaseFunction)) {
540 throw new EvalException(
541 getLocation(), String.format("struct field '%s' is not a function", func.getName()));
542 }
543 function = (BaseFunction) fieldValue;
544 evalArguments(posargs, kwargs, env, function);
545 return convertFromSkylark(
546 function.call(posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env),
547 env);
Francois-Rene Rideau22aab6d2015-08-25 22:55:31 +0000548 } else if (env.isSkylark()) {
Florian Weikert3f610e82015-08-18 14:37:46 +0000549 // Only allow native Java calls when using Skylark
550 // When calling a Java method, the name is not in the Environment,
551 // so evaluating 'func' would fail.
552 evalArguments(posargs, kwargs, env, null);
Florian Weikert3f610e82015-08-18 14:37:46 +0000553 if (objValue instanceof Class<?>) {
554 // Static Java method call. We can return the value from here directly because
555 // invokeJavaMethod() has special checks.
Francois-Rene Rideau676905a2015-08-31 15:39:09 +0000556 return invokeJavaMethod(
557 null, (Class<?>) objValue, func.getName(), posargs.build(), !kwargs.isEmpty());
Florian Weikert3f610e82015-08-18 14:37:46 +0000558 } else {
Francois-Rene Rideau676905a2015-08-31 15:39:09 +0000559 return invokeJavaMethod(
560 objValue, objValue.getClass(), func.getName(), posargs.build(), !kwargs.isEmpty());
Florian Weikert3f610e82015-08-18 14:37:46 +0000561 }
562 } else {
563 throw new EvalException(
564 getLocation(),
565 String.format("%s is not defined on object of type '%s'", functionName(),
566 EvalUtils.getDataTypeName(objValue)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100567 }
Florian Weikert3f610e82015-08-18 14:37:46 +0000568 }
Googlera34d5072015-03-05 13:51:28 +0000569
Florian Weikert3f610e82015-08-18 14:37:46 +0000570 /**
571 * Invokes func() and returns the result.
572 */
573 private Object invokeGlobalFunction(Environment env) throws EvalException, InterruptedException {
574 Object funcValue = func.eval(env);
575 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
576 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
577 // we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
578 Map<String, Object> kwargs = new HashMap<>();
579 if ((funcValue instanceof BaseFunction)) {
580 BaseFunction function = (BaseFunction) funcValue;
581 evalArguments(posargs, kwargs, env, function);
582 return convertFromSkylark(
583 function.call(posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env),
584 env);
585 } else {
586 throw new EvalException(
587 getLocation(), "'" + EvalUtils.getDataTypeName(funcValue) + "' object is not callable");
588 }
589 }
590
591 protected Object convertFromSkylark(Object returnValue, Environment env) throws EvalException {
Googlera34d5072015-03-05 13:51:28 +0000592 EvalUtils.checkNotNull(this, returnValue);
Francois-Rene Rideau22aab6d2015-08-25 22:55:31 +0000593 if (!env.isSkylark()) {
Googlera34d5072015-03-05 13:51:28 +0000594 // The call happens in the BUILD language. Note that accessing "BUILD language" functions in
595 // Skylark should never happen.
596 return SkylarkType.convertFromSkylark(returnValue);
597 }
598 return returnValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100599 }
600
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +0000601 private ArgConversion getArgConversion(BaseFunction function) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100602 if (function == null) {
603 // It means we try to call a Java function.
604 return ArgConversion.FROM_SKYLARK;
605 }
606 // If we call a UserDefinedFunction we call into Skylark. If we call from Skylark
607 // the argument conversion is invariant, but if we call from the BUILD language
608 // we might need an auto conversion.
609 return function instanceof UserDefinedFunction
610 ? ArgConversion.TO_SKYLARK : ArgConversion.NO_CONVERSION;
611 }
612
613 @Override
614 public void accept(SyntaxTreeVisitor visitor) {
615 visitor.visit(this);
616 }
617
618 @Override
Laurent Le Brun2e78d612015-04-15 09:06:46 +0000619 void validate(ValidationEnvironment env) throws EvalException {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000620 for (Argument.Passed arg : args) {
Laurent Le Brun386b90c2015-02-11 16:34:08 +0000621 arg.getValue().validate(env);
622 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100623
624 if (obj != null) {
Laurent Le Brun964d8d52015-04-13 12:15:04 +0000625 obj.validate(env);
626 } else if (!env.hasSymbolInEnvironment(func.getName())) {
627 throw new EvalException(getLocation(),
628 String.format("function '%s' does not exist", func.getName()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100629 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100630 }
631}