blob: 64ce78a7beab8156e2eb6776a355192e80cac09f [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.syntax;
16
Florian Weikert33f819b2015-11-09 18:15:55 +000017import static com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils.append;
18
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000019import com.google.common.annotations.VisibleForTesting;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +000020import com.google.common.base.Joiner;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.common.cache.CacheBuilder;
22import com.google.common.cache.CacheLoader;
23import com.google.common.cache.LoadingCache;
24import com.google.common.collect.ImmutableList;
25import com.google.common.collect.ImmutableMap;
26import com.google.common.collect.Lists;
27import com.google.devtools.build.lib.events.Location;
John Field585d1a02015-12-16 16:03:52 +000028import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
29import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.lib.syntax.EvalException.EvalExceptionWithJavaCause;
Florian Weikert33f819b2015-11-09 18:15:55 +000031import com.google.devtools.build.lib.syntax.compiler.ByteCodeMethodCalls;
32import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils;
33import com.google.devtools.build.lib.syntax.compiler.DebugInfo;
34import com.google.devtools.build.lib.syntax.compiler.DebugInfo.AstAccessors;
35import com.google.devtools.build.lib.syntax.compiler.NewObject;
36import com.google.devtools.build.lib.syntax.compiler.Variable.InternalVariable;
37import com.google.devtools.build.lib.syntax.compiler.VariableScope;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010038import com.google.devtools.build.lib.util.StringUtilities;
39
Florian Weikert33f819b2015-11-09 18:15:55 +000040import net.bytebuddy.description.type.TypeDescription;
41import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
42import net.bytebuddy.implementation.bytecode.Removal;
43import net.bytebuddy.implementation.bytecode.StackManipulation;
44import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
45import net.bytebuddy.implementation.bytecode.constant.TextConstant;
46
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047import java.lang.reflect.InvocationTargetException;
48import java.lang.reflect.Method;
49import java.lang.reflect.Modifier;
50import java.util.ArrayList;
51import java.util.Collections;
52import java.util.HashMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010053import java.util.List;
54import java.util.Map;
55import java.util.concurrent.ExecutionException;
56
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000057import javax.annotation.Nullable;
58
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010059/**
60 * Syntax node for a function call expression.
61 */
62public final class FuncallExpression extends Expression {
63
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010064 /**
65 * A value class to store Methods with their corresponding SkylarkCallable annotations.
66 * This is needed because the annotation is sometimes in a superclass.
67 */
68 public static final class MethodDescriptor {
69 private final Method method;
70 private final SkylarkCallable annotation;
71
72 private MethodDescriptor(Method method, SkylarkCallable annotation) {
73 this.method = method;
74 this.annotation = annotation;
75 }
76
77 Method getMethod() {
78 return method;
79 }
80
81 /**
82 * Returns the SkylarkCallable annotation corresponding to this method.
83 */
84 public SkylarkCallable getAnnotation() {
85 return annotation;
86 }
87 }
88
89 private static final LoadingCache<Class<?>, Map<String, List<MethodDescriptor>>> methodCache =
90 CacheBuilder.newBuilder()
91 .initialCapacity(10)
92 .maximumSize(100)
93 .build(new CacheLoader<Class<?>, Map<String, List<MethodDescriptor>>>() {
94
95 @Override
96 public Map<String, List<MethodDescriptor>> load(Class<?> key) throws Exception {
97 Map<String, List<MethodDescriptor>> methodMap = new HashMap<>();
98 for (Method method : key.getMethods()) {
99 // Synthetic methods lead to false multiple matches
100 if (method.isSynthetic()) {
101 continue;
102 }
103 SkylarkCallable callable = getAnnotationFromParentClass(
104 method.getDeclaringClass(), method);
105 if (callable == null) {
106 continue;
107 }
108 String name = callable.name();
109 if (name.isEmpty()) {
110 name = StringUtilities.toPythonStyleFunctionName(method.getName());
111 }
112 String signature = name + "#" + method.getParameterTypes().length;
113 if (methodMap.containsKey(signature)) {
114 methodMap.get(signature).add(new MethodDescriptor(method, callable));
115 } else {
116 methodMap.put(signature, Lists.newArrayList(new MethodDescriptor(method, callable)));
117 }
118 }
119 return ImmutableMap.copyOf(methodMap);
120 }
121 });
122
123 /**
124 * Returns a map of methods and corresponding SkylarkCallable annotations
125 * of the methods of the classObj class reachable from Skylark.
126 */
127 public static ImmutableMap<Method, SkylarkCallable> collectSkylarkMethodsWithAnnotation(
128 Class<?> classObj) {
129 ImmutableMap.Builder<Method, SkylarkCallable> methodMap = ImmutableMap.builder();
130 for (Method method : classObj.getMethods()) {
131 // Synthetic methods lead to false multiple matches
132 if (!method.isSynthetic()) {
133 SkylarkCallable annotation = getAnnotationFromParentClass(classObj, method);
134 if (annotation != null) {
135 methodMap.put(method, annotation);
136 }
137 }
138 }
139 return methodMap.build();
140 }
141
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000142 @Nullable
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100143 private static SkylarkCallable getAnnotationFromParentClass(Class<?> classObj, Method method) {
144 boolean keepLooking = false;
145 try {
146 Method superMethod = classObj.getMethod(method.getName(), method.getParameterTypes());
147 if (classObj.isAnnotationPresent(SkylarkModule.class)
148 && superMethod.isAnnotationPresent(SkylarkCallable.class)) {
149 return superMethod.getAnnotation(SkylarkCallable.class);
150 } else {
151 keepLooking = true;
152 }
153 } catch (NoSuchMethodException e) {
154 // The class might not have the specified method, so an exceptions is OK.
155 keepLooking = true;
156 }
157 if (keepLooking) {
158 if (classObj.getSuperclass() != null) {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000159 SkylarkCallable annotation =
160 getAnnotationFromParentClass(classObj.getSuperclass(), method);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100161 if (annotation != null) {
162 return annotation;
163 }
164 }
165 for (Class<?> interfaceObj : classObj.getInterfaces()) {
166 SkylarkCallable annotation = getAnnotationFromParentClass(interfaceObj, method);
167 if (annotation != null) {
168 return annotation;
169 }
170 }
171 }
172 return null;
173 }
174
175 /**
176 * An exception class to handle exceptions in direct Java API calls.
177 */
178 public static final class FuncallException extends Exception {
179
180 public FuncallException(String msg) {
181 super(msg);
182 }
183 }
184
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000185 @Nullable private final Expression obj;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100186
Florian Weikert6f864c32015-07-23 11:26:39 +0000187 private final Identifier func;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100188
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000189 private final List<Argument.Passed> args;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100190
191 private final int numPositionalArgs;
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 */
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000200 public FuncallExpression(@Nullable Expression obj, Identifier func,
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000201 List<Argument.Passed> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100202 this.obj = obj;
203 this.func = func;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000204 this.args = args; // we assume the parser validated it with Argument#validateFuncallArguments()
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100205 this.numPositionalArgs = countPositionalArguments();
206 }
207
208 /**
209 * Note: the grammar definition restricts the function value in a function
210 * call expression to be a global identifier; however, the representation of
211 * values in the interpreter is flexible enough to allow functions to be
212 * arbitrary expressions. In any case, the "func" expression is always
213 * evaluated, so functions and variables share a common namespace.
214 */
Florian Weikert6f864c32015-07-23 11:26:39 +0000215 public FuncallExpression(Identifier func, List<Argument.Passed> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100216 this(null, func, args);
217 }
218
219 /**
220 * Returns the number of positional arguments.
221 */
222 private int countPositionalArguments() {
223 int num = 0;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000224 for (Argument.Passed arg : args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100225 if (arg.isPositional()) {
226 num++;
227 }
228 }
229 return num;
230 }
231
232 /**
233 * Returns the function expression.
234 */
Florian Weikert6f864c32015-07-23 11:26:39 +0000235 public Identifier getFunction() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100236 return func;
237 }
238
239 /**
240 * Returns the object the function called on.
241 * It's null if the function is not called on an object.
242 */
243 public Expression getObject() {
244 return obj;
245 }
246
247 /**
248 * Returns an (immutable, ordered) list of function arguments. The first n are
249 * positional and the remaining ones are keyword args, where n =
250 * getNumPositionalArguments().
251 */
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000252 public List<Argument.Passed> getArguments() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100253 return Collections.unmodifiableList(args);
254 }
255
256 /**
257 * Returns the number of arguments which are positional; the remainder are
258 * keyword arguments.
259 */
260 public int getNumPositionalArguments() {
261 return numPositionalArgs;
262 }
263
Laurent Le Brun003bb882015-05-19 11:32:17 +0000264 private String functionName() {
265 String name = func.getName();
266 if (name.equals("$slice")) {
267 return "operator [:]";
268 } else if (name.equals("$index")) {
269 return "operator []";
270 } else {
Laurent Le Brun648f8f32015-09-09 19:46:29 +0000271 return "function " + name;
Laurent Le Brun003bb882015-05-19 11:32:17 +0000272 }
273 }
274
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100275 @Override
276 public String toString() {
Laurent Le Bruneeef30f2015-03-16 15:12:35 +0000277 if (func.getName().equals("$slice")) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100278 return obj + "[" + args.get(0) + ":" + args.get(1) + "]";
279 }
280 if (func.getName().equals("$index")) {
281 return obj + "[" + args.get(0) + "]";
282 }
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000283 StringBuilder sb = new StringBuilder();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100284 if (obj != null) {
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000285 sb.append(obj).append(".");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100286 }
Florian Weikert90a15962015-09-11 13:43:10 +0000287 sb.append(func);
Florian Weikert2591e192015-10-05 14:24:51 +0000288 Printer.printList(sb, args, "(", ", ", ")", /* singletonTerminator */ null,
289 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT,
290 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH);
Francois-Rene Rideau9e3cc2e2015-05-18 18:35:36 +0000291 return sb.toString();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100292 }
293
294 /**
295 * Returns the list of Skylark callable Methods of objClass with the given name
296 * and argument number.
297 */
Laurent Le Brun427bd972015-05-20 13:28:44 +0000298 public static List<MethodDescriptor> getMethods(Class<?> objClass, String methodName, int argNum,
299 Location loc) throws EvalException {
300 try {
301 return methodCache.get(objClass).get(methodName + "#" + argNum);
302 } catch (ExecutionException e) {
303 throw new EvalException(loc, "Method invocation failed: " + e);
304 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100305 }
306
307 /**
308 * Returns the list of the Skylark name of all Skylark callable methods.
309 */
310 public static List<String> getMethodNames(Class<?> objClass)
311 throws ExecutionException {
312 List<String> names = new ArrayList<>();
313 for (List<MethodDescriptor> methods : methodCache.get(objClass).values()) {
314 for (MethodDescriptor method : methods) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000315 // TODO(bazel-team): store the Skylark name in the MethodDescriptor.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100316 String name = method.annotation.name();
317 if (name.isEmpty()) {
318 name = StringUtilities.toPythonStyleFunctionName(method.method.getName());
319 }
320 names.add(name);
321 }
322 }
323 return names;
324 }
325
326 static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000327 Object[] args, Location loc, Environment env) throws EvalException {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000328 try {
329 Method method = methodDescriptor.getMethod();
330 if (obj == null && !Modifier.isStatic(method.getModifiers())) {
331 throw new EvalException(loc, "Method '" + methodName + "' is not static");
332 }
333 // This happens when the interface is public but the implementation classes
334 // have reduced visibility.
335 method.setAccessible(true);
336 Object result = method.invoke(obj, args);
337 if (method.getReturnType().equals(Void.TYPE)) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000338 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000339 }
340 if (result == null) {
341 if (methodDescriptor.getAnnotation().allowReturnNones()) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000342 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000343 } else {
344 throw new EvalException(loc,
345 "Method invocation returned None, please contact Skylark developers: " + methodName
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000346 + Printer.listString(ImmutableList.copyOf(args), "(", ", ", ")", null));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000347 }
348 }
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000349 // TODO(bazel-team): get rid of this, by having everyone use the Skylark data structures
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000350 result = SkylarkType.convertToSkylark(result, method, env);
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000351 if (result != null && !EvalUtils.isSkylarkAcceptable(result.getClass())) {
352 throw new EvalException(loc, Printer.format(
353 "Method '%s' returns an object of invalid type %r", methodName, result.getClass()));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000354 }
355 return result;
356 } catch (IllegalAccessException e) {
357 // TODO(bazel-team): Print a nice error message. Maybe the method exists
358 // and an argument is missing or has the wrong type.
359 throw new EvalException(loc, "Method invocation failed: " + e);
360 } catch (InvocationTargetException e) {
361 if (e.getCause() instanceof FuncallException) {
362 throw new EvalException(loc, e.getCause().getMessage());
363 } else if (e.getCause() != null) {
364 throw new EvalExceptionWithJavaCause(loc, e.getCause());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100365 } else {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000366 // This is unlikely to happen
367 throw new EvalException(loc, "Method invocation failed: " + e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100368 }
369 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100370 }
371
372 // TODO(bazel-team): If there's exactly one usable method, this works. If there are multiple
373 // matching methods, it still can be a problem. Figure out how the Java compiler does it
374 // exactly and copy that behaviour.
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000375 private MethodDescriptor findJavaMethod(
376 Class<?> objClass, String methodName, List<Object> args) throws EvalException {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000377 MethodDescriptor matchingMethod = null;
378 List<MethodDescriptor> methods = getMethods(objClass, methodName, args.size(), getLocation());
379 if (methods != null) {
380 for (MethodDescriptor method : methods) {
381 Class<?>[] params = method.getMethod().getParameterTypes();
382 int i = 0;
383 boolean matching = true;
384 for (Class<?> param : params) {
385 if (!param.isAssignableFrom(args.get(i).getClass())) {
386 matching = false;
387 break;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100388 }
Laurent Le Brun427bd972015-05-20 13:28:44 +0000389 i++;
390 }
391 if (matching) {
392 if (matchingMethod == null) {
393 matchingMethod = method;
394 } else {
Laurent Le Brun648f8f32015-09-09 19:46:29 +0000395 throw new EvalException(
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000396 getLocation(),
Laurent Le Brun648f8f32015-09-09 19:46:29 +0000397 String.format(
398 "Type %s has multiple matches for %s",
399 EvalUtils.getDataTypeNameFromClass(objClass),
400 formatMethod(args)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100401 }
402 }
403 }
Laurent Le Brun427bd972015-05-20 13:28:44 +0000404 }
405 if (matchingMethod != null && !matchingMethod.getAnnotation().structField()) {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000406 return matchingMethod;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100407 }
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000408 throw new EvalException(
409 getLocation(),
410 String.format(
411 "Type %s has no %s",
412 EvalUtils.getDataTypeNameFromClass(objClass),
413 formatMethod(args)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100414 }
415
Laurent Le Brun648f8f32015-09-09 19:46:29 +0000416 private String formatMethod(List<Object> args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100417 StringBuilder sb = new StringBuilder();
Laurent Le Brun648f8f32015-09-09 19:46:29 +0000418 sb.append(functionName()).append("(");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100419 boolean first = true;
420 for (Object obj : args) {
421 if (!first) {
422 sb.append(", ");
423 }
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000424 sb.append(EvalUtils.getDataTypeName(obj));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100425 first = false;
426 }
427 return sb.append(")").toString();
428 }
429
430 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000431 * A {@link StackManipulation} invoking addKeywordArg.
432 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100433 */
Florian Weikert33f819b2015-11-09 18:15:55 +0000434 private static final StackManipulation addKeywordArg =
435 ByteCodeUtils.invoke(
436 FuncallExpression.class,
437 "addKeywordArg",
438 Map.class,
439 String.class,
440 Object.class,
441 ImmutableList.Builder.class);
442
443 /**
444 * Add one argument to the keyword map, registering a duplicate in case of conflict.
445 *
446 * <p>public for reflection by the compiler and calls from compiled functions
447 */
448 public static void addKeywordArg(
449 Map<String, Object> kwargs,
450 String name,
451 Object value,
452 ImmutableList.Builder<String> duplicates) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100453 if (kwargs.put(name, value) != null) {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000454 duplicates.add(name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100455 }
456 }
457
458 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000459 * A {@link StackManipulation} invoking addKeywordArgs.
460 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100461 */
Florian Weikert33f819b2015-11-09 18:15:55 +0000462 private static final StackManipulation addKeywordArgs =
463 ByteCodeUtils.invoke(
464 FuncallExpression.class,
465 "addKeywordArgs",
466 Map.class,
467 Object.class,
468 ImmutableList.Builder.class,
469 Location.class);
470
471 /**
472 * Add multiple arguments to the keyword map (**kwargs), registering duplicates
473 *
474 * <p>public for reflection by the compiler and calls from compiled functions
475 */
476 public static void addKeywordArgs(
477 Map<String, Object> kwargs,
478 Object items,
479 ImmutableList.Builder<String> duplicates,
480 Location location)
481 throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100482 if (!(items instanceof Map<?, ?>)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000483 throw new EvalException(
484 location,
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000485 "Argument after ** must be a dictionary, not " + EvalUtils.getDataTypeName(items));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100486 }
487 for (Map.Entry<?, ?> entry : ((Map<?, ?>) items).entrySet()) {
488 if (!(entry.getKey() instanceof String)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000489 throw new EvalException(
490 location, "Keywords must be strings, not " + EvalUtils.getDataTypeName(entry.getKey()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100491 }
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000492 addKeywordArg(kwargs, (String) entry.getKey(), entry.getValue(), duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100493 }
494 }
495
Florian Weikert33f819b2015-11-09 18:15:55 +0000496 /**
497 * A {@link StackManipulation} invoking checkCallable.
498 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
499 */
500 private static final StackManipulation checkCallable =
501 ByteCodeUtils.invoke(FuncallExpression.class, "checkCallable", Object.class, Location.class);
502
503 /**
504 * Checks whether the given object is a {@link BaseFunction}.
505 *
506 * <p>Public for reflection by the compiler and access from generated byte code.
507 *
508 * @throws EvalException If not a BaseFunction.
509 */
510 public static BaseFunction checkCallable(Object functionValue, Location location)
511 throws EvalException {
512 if (functionValue instanceof BaseFunction) {
513 return (BaseFunction) functionValue;
514 } else {
515 throw new EvalException(
516 location, "'" + EvalUtils.getDataTypeName(functionValue) + "' object is not callable");
517 }
518 }
519
520 /**
521 * A {@link StackManipulation} invoking checkDuplicates.
522 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
523 */
524 private static final StackManipulation checkDuplicates =
525 ByteCodeUtils.invoke(
526 FuncallExpression.class,
527 "checkDuplicates",
528 ImmutableList.Builder.class,
529 String.class,
530 Location.class);
531
532 /**
533 * Check the list from the builder and report an {@link EvalException} if not empty.
534 *
535 * <p>public for reflection by the compiler and calls from compiled functions
536 */
537 public static void checkDuplicates(
538 ImmutableList.Builder<String> duplicates, String function, Location location)
539 throws EvalException {
540 List<String> dups = duplicates.build();
541 if (!dups.isEmpty()) {
542 throw new EvalException(
543 location,
544 "duplicate keyword"
545 + (dups.size() > 1 ? "s" : "")
546 + " '"
547 + Joiner.on("', '").join(dups)
548 + "' in call to "
549 + function);
550 }
551 }
552
553 /**
554 * A {@link StackManipulation} invoking invokeObjectMethod.
555 * <p>Kept close to the definition of the method to avoid reflection errors when changing it.
556 */
557 private static final StackManipulation invokeObjectMethod =
558 ByteCodeUtils.invoke(
559 FuncallExpression.class,
560 "invokeObjectMethod",
561 String.class,
562 ImmutableList.class,
563 ImmutableMap.class,
564 FuncallExpression.class,
565 Environment.class);
566
567 /**
568 * Call a method depending on the type of an object it is called on.
569 *
570 * <p>Public for reflection by the compiler and access from generated byte code.
571 *
572 * @param positionals The first object is expected to be the object the method is called on.
573 * @param call the original expression that caused this call, needed for rules especially
574 */
575 public static Object invokeObjectMethod(
576 String method,
577 ImmutableList<Object> positionals,
578 ImmutableMap<String, Object> keyWordArgs,
579 FuncallExpression call,
580 Environment env)
581 throws EvalException, InterruptedException {
582 Location location = call.getLocation();
583 Object value = positionals.get(0);
584 ImmutableList<Object> positionalArgs = positionals.subList(1, positionals.size());
585 BaseFunction function = Runtime.getFunction(EvalUtils.getSkylarkType(value.getClass()), method);
586 if (function != null) {
587 if (!isNamespace(value.getClass())) {
588 // Use self as an implicit parameter in front.
589 positionalArgs = positionals;
590 }
591 return function.call(
592 positionalArgs, ImmutableMap.<String, Object>copyOf(keyWordArgs), call, env);
593 } else if (value instanceof ClassObject) {
594 Object fieldValue = ((ClassObject) value).getValue(method);
595 if (fieldValue == null) {
596 throw new EvalException(location, String.format("struct has no method '%s'", method));
597 }
598 if (!(fieldValue instanceof BaseFunction)) {
599 throw new EvalException(
600 location, String.format("struct field '%s' is not a function", method));
601 }
602 function = (BaseFunction) fieldValue;
603 return function.call(
604 positionalArgs, ImmutableMap.<String, Object>copyOf(keyWordArgs), call, env);
605 } else if (env.isSkylark()) {
606 // Only allow native Java calls when using Skylark
607 // When calling a Java method, the name is not in the Environment,
608 // so evaluating 'func' would fail.
609 Class<?> objClass;
610 Object obj;
611 if (value instanceof Class<?>) {
612 // Static call
613 obj = null;
614 objClass = (Class<?>) value;
615 } else {
616 obj = value;
617 objClass = value.getClass();
618 }
619 MethodDescriptor methodDescriptor = call.findJavaMethod(objClass, method, positionalArgs);
620 if (!keyWordArgs.isEmpty()) {
621 throw new EvalException(
622 call.func.getLocation(),
623 String.format(
624 "Keyword arguments are not allowed when calling a java method"
625 + "\nwhile calling method '%s' for type %s",
626 method,
627 EvalUtils.getDataTypeNameFromClass(objClass)));
628 }
629 return callMethod(methodDescriptor, method, obj, positionalArgs.toArray(), location, env);
630 } else {
631 throw new EvalException(
632 location,
633 String.format(
634 "%s is not defined on object of type '%s'",
635 call.functionName(),
636 EvalUtils.getDataTypeName(value)));
637 }
638 }
639
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000640 @SuppressWarnings("unchecked")
641 private void evalArguments(ImmutableList.Builder<Object> posargs, Map<String, Object> kwargs,
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000642 Environment env)
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000643 throws EvalException, InterruptedException {
Ulf Adams07dba942015-03-05 14:47:37 +0000644 ImmutableList.Builder<String> duplicates = new ImmutableList.Builder<>();
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000645 // Iterate over the arguments. We assume all positional arguments come before any keyword
646 // or star arguments, because the argument list was already validated by
647 // Argument#validateFuncallArguments, as called by the Parser,
648 // which should be the only place that build FuncallExpression-s.
649 for (Argument.Passed arg : args) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100650 Object value = arg.getValue().eval(env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100651 if (arg.isPositional()) {
652 posargs.add(value);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000653 } else if (arg.isStar()) { // expand the starArg
654 if (value instanceof Iterable) {
655 posargs.addAll((Iterable<Object>) value);
656 }
657 } else if (arg.isStarStar()) { // expand the kwargs
Florian Weikert33f819b2015-11-09 18:15:55 +0000658 addKeywordArgs(kwargs, value, duplicates, getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100659 } else {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000660 addKeywordArg(kwargs, arg.getName(), value, duplicates);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100661 }
662 }
Florian Weikert33f819b2015-11-09 18:15:55 +0000663 checkDuplicates(duplicates, func.getName(), getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100664 }
665
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000666 @VisibleForTesting
667 public static boolean isNamespace(Class<?> classObject) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100668 return classObject.isAnnotationPresent(SkylarkModule.class)
669 && classObject.getAnnotation(SkylarkModule.class).namespace();
670 }
671
672 @Override
Florian Weikert90a15962015-09-11 13:43:10 +0000673 Object doEval(Environment env) throws EvalException, InterruptedException {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000674 return (obj != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000675 }
676
677 /**
678 * Invokes obj.func() and returns the result.
679 */
680 private Object invokeObjectMethod(Environment env) throws EvalException, InterruptedException {
681 Object objValue = obj.eval(env);
Ulf Adams07dba942015-03-05 14:47:37 +0000682 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000683 posargs.add(objValue);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000684 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
685 // we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
686 Map<String, Object> kwargs = new HashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000687 evalArguments(posargs, kwargs, env);
688 return invokeObjectMethod(
689 func.getName(), posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000690 }
Googlera34d5072015-03-05 13:51:28 +0000691
Florian Weikert3f610e82015-08-18 14:37:46 +0000692 /**
693 * Invokes func() and returns the result.
694 */
695 private Object invokeGlobalFunction(Environment env) throws EvalException, InterruptedException {
696 Object funcValue = func.eval(env);
697 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
698 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
699 // we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
700 Map<String, Object> kwargs = new HashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000701 BaseFunction function = checkCallable(funcValue, getLocation());
702 evalArguments(posargs, kwargs, env);
703 return function.call(posargs.build(), ImmutableMap.<String, Object>copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000704 }
705
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000706 /**
707 * Returns the value of the argument 'name' (or null if there is none).
708 * This function is used to associate debugging information to rules created by skylark "macros".
709 */
710 @Nullable
711 public String getNameArg() {
712 for (Argument.Passed arg : args) {
713 if (arg != null) {
714 String name = arg.getName();
715 if (name != null && name.equals("name")) {
716 Expression expr = arg.getValue();
717 return (expr != null && expr instanceof StringLiteral)
718 ? ((StringLiteral) expr).getValue() : null;
719 }
720 }
721 }
722 return null;
723 }
724
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100725 @Override
726 public void accept(SyntaxTreeVisitor visitor) {
727 visitor.visit(this);
728 }
729
730 @Override
Laurent Le Brun2e78d612015-04-15 09:06:46 +0000731 void validate(ValidationEnvironment env) throws EvalException {
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000732 for (Argument.Passed arg : args) {
Laurent Le Brun386b90c2015-02-11 16:34:08 +0000733 arg.getValue().validate(env);
734 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100735
736 if (obj != null) {
Laurent Le Brun964d8d52015-04-13 12:15:04 +0000737 obj.validate(env);
738 } else if (!env.hasSymbolInEnvironment(func.getName())) {
739 throw new EvalException(getLocation(),
740 String.format("function '%s' does not exist", func.getName()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100741 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100742 }
Florian Weikert90a15962015-09-11 13:43:10 +0000743
744 @Override
745 protected boolean isNewScope() {
746 return true;
747 }
Florian Weikert33f819b2015-11-09 18:15:55 +0000748
749 @Override
750 ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo) throws EvalException {
751 AstAccessors debugAccessors = debugInfo.add(this);
752 List<ByteCodeAppender> code = new ArrayList<>();
753 if (obj != null) {
754 compileObjectMethodCall(scope, debugInfo, debugAccessors, code);
755 } else {
756 compileGlobalFunctionCall(scope, debugInfo, debugAccessors, code);
757 }
758 return ByteCodeUtils.compoundAppender(code);
759 }
760
761 /**
762 * Add code that compiles the argument expressions.
763 *
764 * <p>The byte code leaves the arguments on the stack in order of:
765 * positional arguments, key word arguments, this FuncallExpression, Environment
766 * This is the order required by {@link #invokeObjectMethod} and
767 * {@link BaseFunction#call(List, Map, FuncallExpression, Environment)}.
768 */
769 private void compileArguments(
770 VariableScope scope,
771 DebugInfo debugInfo,
772 AstAccessors debugAccessors,
773 List<ByteCodeAppender> code)
774 throws EvalException {
775 InternalVariable positionalsBuilder = scope.freshVariable(ImmutableList.Builder.class);
776 append(code, ByteCodeMethodCalls.BCImmutableList.builder);
777 code.add(positionalsBuilder.store());
778
779 InternalVariable keyWordArgs = scope.freshVariable(Map.class);
780 append(code, NewObject.fromConstructor(HashMap.class).arguments());
781 code.add(keyWordArgs.store());
782
783 InternalVariable duplicatesBuilder =
784 scope.freshVariable(new TypeDescription.ForLoadedType(ImmutableList.Builder.class));
785 append(code, ByteCodeMethodCalls.BCImmutableList.builder);
786 code.add(duplicatesBuilder.store());
787
788 StackManipulation builderAdd =
789 new StackManipulation.Compound(
790 ByteCodeMethodCalls.BCImmutableList.Builder.add, Removal.SINGLE);
791
792 // add an object the function is called on first
793 if (obj != null) {
794 append(code, positionalsBuilder.load());
795 code.add(obj.compile(scope, debugInfo));
796 append(code, builderAdd);
797 }
798 // add all arguments to their respective builder/map
799 for (Argument.Passed arg : args) {
800 ByteCodeAppender value = arg.getValue().compile(scope, debugInfo);
801 if (arg.isPositional()) {
802 append(code, positionalsBuilder.load());
803 code.add(value);
804 append(code, builderAdd);
805 } else if (arg.isStar()) {
806 // expand the starArg by adding all it's elements to the builder
807 append(code, positionalsBuilder.load());
808 code.add(value);
809 append(
810 code,
811 TypeCasting.to(new TypeDescription.ForLoadedType(Iterable.class)),
812 ByteCodeMethodCalls.BCImmutableList.Builder.addAll,
813 Removal.SINGLE);
814 } else if (arg.isStarStar()) {
815 append(code, keyWordArgs.load());
816 code.add(value);
817 append(code, duplicatesBuilder.load(), debugAccessors.loadLocation, addKeywordArgs);
818 } else {
819 append(code, keyWordArgs.load(), new TextConstant(arg.getName()));
820 code.add(value);
821 append(code, duplicatesBuilder.load(), addKeywordArg);
822 }
823 }
824 append(
825 code,
826 // check for duplicates in the key word arguments
827 duplicatesBuilder.load(),
828 new TextConstant(func.getName()),
829 debugAccessors.loadLocation,
830 checkDuplicates,
831 // load the arguments in the correct order for invokeObjectMethod and BaseFunction.call
832 positionalsBuilder.load(),
833 ByteCodeMethodCalls.BCImmutableList.Builder.build,
834 keyWordArgs.load(),
835 ByteCodeMethodCalls.BCImmutableMap.copyOf,
836 debugAccessors.loadAstNode,
837 TypeCasting.to(new TypeDescription.ForLoadedType(FuncallExpression.class)),
838 scope.loadEnvironment());
839 }
840
841 private void compileObjectMethodCall(
842 VariableScope scope,
843 DebugInfo debugInfo,
844 AstAccessors debugAccessors,
845 List<ByteCodeAppender> code)
846 throws EvalException {
847 append(code, new TextConstant(func.getName()));
848 compileArguments(scope, debugInfo, debugAccessors, code);
849 append(code, invokeObjectMethod);
850 }
851
852 private void compileGlobalFunctionCall(
853 VariableScope scope,
854 DebugInfo debugInfo,
855 AstAccessors debugAccessors,
856 List<ByteCodeAppender> code)
857 throws EvalException {
858 code.add(func.compile(scope, debugInfo));
859 append(code, debugAccessors.loadLocation, checkCallable);
860 compileArguments(scope, debugInfo, debugAccessors, code);
861 append(code, BaseFunction.call);
862 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100863}