blob: 1569fa51c208ef67611464b958a8d3da5abd948e [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;
tomlua155b532017-11-08 20:12:47 +010019import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020import com.google.common.cache.CacheBuilder;
21import com.google.common.cache.CacheLoader;
22import com.google.common.cache.LoadingCache;
23import com.google.common.collect.ImmutableList;
24import com.google.common.collect.ImmutableMap;
dmarting9ae2adc2017-10-05 03:07:12 +020025import com.google.common.collect.ImmutableSortedMap;
dslomov40ddec32017-07-03 07:15:31 -040026import com.google.common.collect.Iterables;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.common.collect.Lists;
28import com.google.devtools.build.lib.events.Location;
janakr76de1ad2018-03-03 11:12:36 -080029import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000030import com.google.devtools.build.lib.skylarkinterface.Param;
Dmitry Lomovd22e1de2017-09-25 08:53:50 -040031import com.google.devtools.build.lib.skylarkinterface.ParamType;
John Field585d1a02015-12-16 16:03:52 +000032import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
Jon Brandvein6696db22016-10-04 20:18:19 +000033import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
John Field585d1a02015-12-16 16:03:52 +000034import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import com.google.devtools.build.lib.syntax.EvalException.EvalExceptionWithJavaCause;
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +000036import com.google.devtools.build.lib.syntax.Runtime.NoneType;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000037import com.google.devtools.build.lib.util.Pair;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010038import com.google.devtools.build.lib.util.StringUtilities;
brandjone2ffd5d2017-06-27 18:14:54 +020039import java.io.IOException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010040import java.lang.reflect.InvocationTargetException;
41import java.lang.reflect.Method;
42import java.lang.reflect.Modifier;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043import java.util.Collections;
dmarting9ae2adc2017-10-05 03:07:12 +020044import java.util.Comparator;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010045import java.util.HashMap;
cparsons6c1c0662018-02-05 02:01:28 -080046import java.util.HashSet;
Pedro Liberal Fernandez837dbc12016-08-18 14:13:01 +000047import java.util.LinkedHashMap;
dslomov40ddec32017-07-03 07:15:31 -040048import java.util.LinkedHashSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010049import java.util.List;
50import java.util.Map;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000051import java.util.Set;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010052import java.util.concurrent.ExecutionException;
cparsons6c1c0662018-02-05 02:01:28 -080053import java.util.stream.Collectors;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +000054import javax.annotation.Nullable;
55
janakr76de1ad2018-03-03 11:12:36 -080056/** Syntax node for a function call expression. */
57@AutoCodec
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010058public final class FuncallExpression extends Expression {
59
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010060 /**
61 * A value class to store Methods with their corresponding SkylarkCallable annotations.
62 * This is needed because the annotation is sometimes in a superclass.
63 */
64 public static final class MethodDescriptor {
65 private final Method method;
66 private final SkylarkCallable annotation;
67
68 private MethodDescriptor(Method method, SkylarkCallable annotation) {
69 this.method = method;
70 this.annotation = annotation;
71 }
72
73 Method getMethod() {
74 return method;
75 }
76
77 /**
78 * Returns the SkylarkCallable annotation corresponding to this method.
79 */
80 public SkylarkCallable getAnnotation() {
81 return annotation;
82 }
83 }
84
85 private static final LoadingCache<Class<?>, Map<String, List<MethodDescriptor>>> methodCache =
86 CacheBuilder.newBuilder()
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000087 .initialCapacity(10)
88 .maximumSize(100)
89 .build(
90 new CacheLoader<Class<?>, Map<String, List<MethodDescriptor>>>() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010091
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +000092 @Override
93 public Map<String, List<MethodDescriptor>> load(Class<?> key) throws Exception {
94 Map<String, List<MethodDescriptor>> methodMap = new HashMap<>();
95 for (Method method : key.getMethods()) {
96 // Synthetic methods lead to false multiple matches
97 if (method.isSynthetic()) {
98 continue;
99 }
Jon Brandvein6696db22016-10-04 20:18:19 +0000100 SkylarkCallable callable = SkylarkInterfaceUtils.getSkylarkCallable(method);
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000101 if (callable == null) {
102 continue;
103 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000104 String name = callable.name();
105 if (name.isEmpty()) {
106 name = StringUtilities.toPythonStyleFunctionName(method.getName());
107 }
108 if (methodMap.containsKey(name)) {
109 methodMap.get(name).add(new MethodDescriptor(method, callable));
110 } else {
111 methodMap.put(
112 name, Lists.newArrayList(new MethodDescriptor(method, callable)));
113 }
114 }
115 return ImmutableMap.copyOf(methodMap);
116 }
117 });
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100118
cparsons6c1c0662018-02-05 02:01:28 -0800119 private static final LoadingCache<Class<?>, Map<String, MethodDescriptor>> fieldCache =
120 CacheBuilder.newBuilder()
121 .initialCapacity(10)
122 .maximumSize(100)
123 .build(
124 new CacheLoader<Class<?>, Map<String, MethodDescriptor>>() {
125
126 @Override
127 public Map<String, MethodDescriptor> load(Class<?> key) throws Exception {
128 ImmutableMap.Builder<String, MethodDescriptor> fieldMap = ImmutableMap.builder();
129 HashSet<String> fieldNamesForCollisions = new HashSet<>();
130 List<MethodDescriptor> fieldMethods =
131 methodCache
132 .get(key)
133 .values()
134 .stream()
135 .flatMap(List::stream)
136 .filter(
137 methodDescriptor -> methodDescriptor.getAnnotation().structField())
138 .collect(Collectors.toList());
139
140 for (MethodDescriptor fieldMethod : fieldMethods) {
141 SkylarkCallable callable = fieldMethod.getAnnotation();
142 String name = callable.name();
143 if (name.isEmpty()) {
144 name =
145 StringUtilities.toPythonStyleFunctionName(
146 fieldMethod.getMethod().getName());
147 }
148 // TODO(b/72113542): Validate with annotation processor instead of at runtime.
149 if (!fieldNamesForCollisions.add(name)) {
150 throw new IllegalArgumentException(
151 String.format(
152 "Class %s has two structField methods named %s defined",
153 key.getName(), name));
154 }
155 fieldMap.put(name, fieldMethod);
156 }
157 return fieldMap.build();
158 }
159 });
160
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100161 /**
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000162 * Returns a map of methods and corresponding SkylarkCallable annotations of the methods of the
163 * classObj class reachable from Skylark.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100164 */
165 public static ImmutableMap<Method, SkylarkCallable> collectSkylarkMethodsWithAnnotation(
166 Class<?> classObj) {
dmarting9ae2adc2017-10-05 03:07:12 +0200167 ImmutableSortedMap.Builder<Method, SkylarkCallable> methodMap
168 = ImmutableSortedMap.orderedBy(Comparator.comparing(Object::toString));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100169 for (Method method : classObj.getMethods()) {
170 // Synthetic methods lead to false multiple matches
171 if (!method.isSynthetic()) {
Jon Brandvein6696db22016-10-04 20:18:19 +0000172 SkylarkCallable annotation = SkylarkInterfaceUtils.getSkylarkCallable(classObj, method);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100173 if (annotation != null) {
174 methodMap.put(method, annotation);
175 }
176 }
177 }
178 return methodMap.build();
179 }
180
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000181 private static class ArgumentListConversionResult {
182 private final ImmutableList<Object> arguments;
183 private final String error;
184
185 private ArgumentListConversionResult(ImmutableList<Object> arguments, String error) {
186 this.arguments = arguments;
187 this.error = error;
188 }
189
190 public static ArgumentListConversionResult fromArgumentList(ImmutableList<Object> arguments) {
191 return new ArgumentListConversionResult(arguments, null);
192 }
193
194 public static ArgumentListConversionResult fromError(String error) {
195 return new ArgumentListConversionResult(null, error);
196 }
197
198 public String getError() {
199 return error;
200 }
201
202 public ImmutableList<Object> getArguments() {
203 return arguments;
204 }
205 }
206
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100207 /**
208 * An exception class to handle exceptions in direct Java API calls.
209 */
210 public static final class FuncallException extends Exception {
211
212 public FuncallException(String msg) {
213 super(msg);
214 }
215 }
216
fzaiser8c27a892017-08-11 17:26:44 +0200217 private final Expression function;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100218
michajlo5f394752017-10-06 23:51:10 +0200219 private final ImmutableList<Argument.Passed> arguments;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100220
221 private final int numPositionalArgs;
222
michajlo5f394752017-10-06 23:51:10 +0200223 public FuncallExpression(Expression function, ImmutableList<Argument.Passed> arguments) {
fzaiser8c27a892017-08-11 17:26:44 +0200224 this.function = Preconditions.checkNotNull(function);
225 this.arguments = Preconditions.checkNotNull(arguments);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100226 this.numPositionalArgs = countPositionalArguments();
227 }
228
fzaiser8c27a892017-08-11 17:26:44 +0200229 /** Returns the function that is called. */
230 public Expression getFunction() {
231 return this.function;
232 }
233
234 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100235 * Returns the number of positional arguments.
236 */
237 private int countPositionalArguments() {
238 int num = 0;
brandjon990622b2017-07-11 19:56:45 +0200239 for (Argument.Passed arg : arguments) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100240 if (arg.isPositional()) {
241 num++;
242 }
243 }
244 return num;
245 }
246
247 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100248 * 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() {
brandjon990622b2017-07-11 19:56:45 +0200253 return Collections.unmodifiableList(arguments);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100254 }
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
cparsons5d446a72018-03-07 11:01:17 -0800264 @Override
265 public void prettyPrint(Appendable buffer) throws IOException {
266 function.prettyPrint(buffer);
267 buffer.append('(');
268 String sep = "";
269 for (Argument.Passed arg : arguments) {
270 buffer.append(sep);
271 arg.prettyPrint(buffer);
272 sep = ", ";
273 }
274 buffer.append(')');
275 }
brandjone2ffd5d2017-06-27 18:14:54 +0200276
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100277 @Override
278 public String toString() {
vladmos6ff634d2017-07-05 10:25:01 -0400279 Printer.LengthLimitedPrinter printer = new Printer.LengthLimitedPrinter();
brandjon990622b2017-07-11 19:56:45 +0200280 printer.append(function.toString());
281 printer.printAbbreviatedList(arguments, "(", ", ", ")", null,
Florian Weikert2591e192015-10-05 14:24:51 +0000282 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT,
283 Printer.SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH);
vladmos46907932017-06-30 14:01:45 +0200284 return printer.toString();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100285 }
286
cparsons6c1c0662018-02-05 02:01:28 -0800287 /** Returns the Skylark callable Method of objClass with structField=true and the given name. */
288 public static MethodDescriptor getStructField(Class<?> objClass, String methodName) {
289 try {
290 return fieldCache.get(objClass).get(methodName);
291 } catch (ExecutionException e) {
292 throw new IllegalStateException("Method loading failed: " + e);
293 }
294 }
295
296 /** Returns the list of names of Skylark callable Methods of objClass with structField=true. */
297 public static Set<String> getStructFieldNames(Class<?> objClass) {
298 try {
299 return fieldCache.get(objClass).keySet();
300 } catch (ExecutionException e) {
301 throw new IllegalStateException("Method loading failed: " + e);
302 }
303 }
304
305 /** Returns the list of Skylark callable Methods of objClass with the given name. */
Laurent Le Brun57badf42017-01-02 15:12:24 +0000306 public static List<MethodDescriptor> getMethods(Class<?> objClass, String methodName) {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000307 try {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000308 return methodCache.get(objClass).get(methodName);
Laurent Le Brun427bd972015-05-20 13:28:44 +0000309 } catch (ExecutionException e) {
cparsons6c1c0662018-02-05 02:01:28 -0800310 throw new IllegalStateException("Method loading failed: " + e);
Laurent Le Brun427bd972015-05-20 13:28:44 +0000311 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100312 }
313
314 /**
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000315 * Returns a set of the Skylark name of all Skylark callable methods for object of type {@code
316 * objClass}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100317 */
Laurent Le Brun57badf42017-01-02 15:12:24 +0000318 public static Set<String> getMethodNames(Class<?> objClass) {
319 try {
320 return methodCache.get(objClass).keySet();
321 } catch (ExecutionException e) {
cparsons6c1c0662018-02-05 02:01:28 -0800322 throw new IllegalStateException("Method loading failed: " + e);
Laurent Le Brun57badf42017-01-02 15:12:24 +0000323 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100324 }
325
cparsons6c1c0662018-02-05 02:01:28 -0800326 /**
327 * Invokes the given structField=true method and returns the result.
328 *
329 * @param methodDescriptor the descriptor of the method to invoke
330 * @param fieldName the name of the struct field
331 * @param obj the object on which to invoke the method
332 * @return the method return value
333 * @throws EvalException if there was an issue evaluating the method
334 */
335 public static Object invokeStructField(
336 MethodDescriptor methodDescriptor, String fieldName, Object obj) throws EvalException {
337 Preconditions.checkArgument(methodDescriptor.getAnnotation().structField());
338 return callMethod(methodDescriptor, fieldName, obj, new Object[0], Location.BUILTIN, null);
339 }
340
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100341 static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000342 Object[] args, Location loc, Environment env) throws EvalException {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000343 try {
344 Method method = methodDescriptor.getMethod();
345 if (obj == null && !Modifier.isStatic(method.getModifiers())) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000346 throw new EvalException(loc, "method '" + methodName + "' is not static");
Laurent Le Brun427bd972015-05-20 13:28:44 +0000347 }
348 // This happens when the interface is public but the implementation classes
349 // have reduced visibility.
350 method.setAccessible(true);
351 Object result = method.invoke(obj, args);
352 if (method.getReturnType().equals(Void.TYPE)) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000353 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000354 }
355 if (result == null) {
356 if (methodDescriptor.getAnnotation().allowReturnNones()) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000357 return Runtime.NONE;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000358 } else {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000359 throw new EvalException(
360 loc,
361 "method invocation returned None, please file a bug report: "
362 + methodName
vladmos46907932017-06-30 14:01:45 +0200363 + Printer.printAbbreviatedList(
cparsons5d446a72018-03-07 11:01:17 -0800364 ImmutableList.copyOf(args), "(", ", ", ")", null));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000365 }
366 }
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000367 // TODO(bazel-team): get rid of this, by having everyone use the Skylark data structures
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000368 result = SkylarkType.convertToSkylark(result, method, env);
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000369 if (result != null && !EvalUtils.isSkylarkAcceptable(result.getClass())) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000370 throw new EvalException(
371 loc,
372 Printer.format(
373 "method '%s' returns an object of invalid type %r", methodName, result.getClass()));
Laurent Le Brun427bd972015-05-20 13:28:44 +0000374 }
375 return result;
376 } catch (IllegalAccessException e) {
377 // TODO(bazel-team): Print a nice error message. Maybe the method exists
378 // and an argument is missing or has the wrong type.
379 throw new EvalException(loc, "Method invocation failed: " + e);
380 } catch (InvocationTargetException e) {
381 if (e.getCause() instanceof FuncallException) {
382 throw new EvalException(loc, e.getCause().getMessage());
383 } else if (e.getCause() != null) {
384 throw new EvalExceptionWithJavaCause(loc, e.getCause());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100385 } else {
Laurent Le Brun427bd972015-05-20 13:28:44 +0000386 // This is unlikely to happen
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000387 throw new EvalException(loc, "method invocation failed: " + e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100388 }
389 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100390 }
391
392 // TODO(bazel-team): If there's exactly one usable method, this works. If there are multiple
393 // matching methods, it still can be a problem. Figure out how the Java compiler does it
394 // exactly and copy that behaviour.
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000395 // Throws an EvalException when it cannot find a matching function.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000396 private Pair<MethodDescriptor, List<Object>> findJavaMethod(
cparsons5d446a72018-03-07 11:01:17 -0800397 Class<?> objClass,
398 String methodName,
399 List<Object> args,
400 Map<String, Object> kwargs,
401 Environment environment)
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000402 throws EvalException {
403 Pair<MethodDescriptor, List<Object>> matchingMethod = null;
Laurent Le Brun57badf42017-01-02 15:12:24 +0000404 List<MethodDescriptor> methods = getMethods(objClass, methodName);
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000405 ArgumentListConversionResult argumentListConversionResult = null;
Laurent Le Brun427bd972015-05-20 13:28:44 +0000406 if (methods != null) {
407 for (MethodDescriptor method : methods) {
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000408 if (method.getAnnotation().structField()) {
cparsons5d446a72018-03-07 11:01:17 -0800409 // TODO(cparsons): Allow structField methods to accept interpreter-supplied arguments.
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000410 return new Pair<>(method, null);
411 } else {
cparsons5d446a72018-03-07 11:01:17 -0800412 argumentListConversionResult = convertArgumentList(args, kwargs, method, environment);
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000413 if (argumentListConversionResult.getArguments() != null) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000414 if (matchingMethod == null) {
brandjon990622b2017-07-11 19:56:45 +0200415 matchingMethod = new Pair<>(method, argumentListConversionResult.getArguments());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000416 } else {
417 throw new EvalException(
418 getLocation(),
419 String.format(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000420 "type '%s' has multiple matches for function %s",
fzaiser8c27a892017-08-11 17:26:44 +0200421 EvalUtils.getDataTypeNameFromClass(objClass),
422 formatMethod(methodName, args, kwargs)));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000423 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100424 }
425 }
426 }
Laurent Le Brun427bd972015-05-20 13:28:44 +0000427 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000428 if (matchingMethod == null) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000429 String errorMessage;
Michael Staib79bd2c22017-03-22 14:37:03 +0000430 if (ClassObject.class.isAssignableFrom(objClass)) {
431 errorMessage = String.format("struct has no method '%s'", methodName);
432 } else if (argumentListConversionResult == null
433 || argumentListConversionResult.getError() == null) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000434 errorMessage =
435 String.format(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000436 "type '%s' has no method %s",
fzaiser8c27a892017-08-11 17:26:44 +0200437 EvalUtils.getDataTypeNameFromClass(objClass),
438 formatMethod(methodName, args, kwargs));
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000439
440 } else {
441 errorMessage =
442 String.format(
cparsonsa95884e2018-03-21 11:06:13 -0700443 "%s, in method call %s of '%s'",
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000444 argumentListConversionResult.getError(),
fzaiser8c27a892017-08-11 17:26:44 +0200445 formatMethod(methodName, args, kwargs),
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000446 EvalUtils.getDataTypeNameFromClass(objClass));
447 }
448 throw new EvalException(getLocation(), errorMessage);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100449 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000450 return matchingMethod;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100451 }
452
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000453 private static SkylarkType getType(Param param) {
Dmitry Lomovd22e1de2017-09-25 08:53:50 -0400454 if (param.allowedTypes().length > 0) {
455 Preconditions.checkState(Object.class.equals(param.type()));
456 SkylarkType result = SkylarkType.BOTTOM;
457 for (ParamType paramType : param.allowedTypes()) {
458 SkylarkType t =
459 paramType.generic1() != Object.class
460 ? SkylarkType.of(paramType.type(), paramType.generic1())
461 : SkylarkType.of(paramType.type());
462 result = SkylarkType.Union.of(result, t);
463 }
464 return result;
465 } else {
466 SkylarkType type =
467 param.generic1() != Object.class
468 ? SkylarkType.of(param.type(), param.generic1())
469 : SkylarkType.of(param.type());
470 return type;
471 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000472 }
473
474 /**
475 * Constructs the parameters list to actually pass to the method, filling with default values if
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000476 * any. If there is a type or argument mismatch, returns a result containing an error message.
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000477 */
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000478 private ArgumentListConversionResult convertArgumentList(
cparsons5d446a72018-03-07 11:01:17 -0800479 List<Object> args,
480 Map<String, Object> kwargs,
481 MethodDescriptor method,
482 Environment environment) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000483 ImmutableList.Builder<Object> builder = ImmutableList.builder();
cparsons5d446a72018-03-07 11:01:17 -0800484 Class<?>[] javaMethodSignatureParams = method.getMethod().getParameterTypes();
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000485 SkylarkCallable callable = method.getAnnotation();
cparsons5d446a72018-03-07 11:01:17 -0800486 int numExtraInterpreterParams = 0;
487 numExtraInterpreterParams += callable.useLocation() ? 1 : 0;
488 numExtraInterpreterParams += callable.useAst() ? 1 : 0;
489 numExtraInterpreterParams += callable.useEnvironment() ? 1 : 0;
cparsons73e10162018-03-22 10:02:02 -0700490 numExtraInterpreterParams += callable.useSkylarkSemantics() ? 1 : 0;
cparsons5d446a72018-03-07 11:01:17 -0800491
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000492 int mandatoryPositionals = callable.mandatoryPositionals();
493 if (mandatoryPositionals < 0) {
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000494 if (callable.parameters().length > 0) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000495 mandatoryPositionals = 0;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000496 } else {
cparsons5d446a72018-03-07 11:01:17 -0800497 mandatoryPositionals = javaMethodSignatureParams.length - numExtraInterpreterParams;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000498 }
499 }
cparsons5d446a72018-03-07 11:01:17 -0800500 if (mandatoryPositionals > args.size()) {
501 return ArgumentListConversionResult.fromError("too few arguments");
502 }
503 if (args.size() > mandatoryPositionals + callable.parameters().length) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000504 return ArgumentListConversionResult.fromError("too many arguments");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000505 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000506 // First process the legacy positional parameters.
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000507 int i = 0;
508 if (mandatoryPositionals > 0) {
cparsons5d446a72018-03-07 11:01:17 -0800509 for (Class<?> param : javaMethodSignatureParams) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000510 Object value = args.get(i);
511 if (!param.isAssignableFrom(value.getClass())) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000512 return ArgumentListConversionResult.fromError(
513 String.format(
Carmi Grushko12291602016-10-26 19:07:41 +0000514 "Cannot convert parameter at position %d from type %s to type %s",
515 i, EvalUtils.getDataTypeName(value), param.toString()));
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000516 }
517 builder.add(value);
518 i++;
cparsons5d446a72018-03-07 11:01:17 -0800519 if (i >= mandatoryPositionals) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000520 // Stops for specified parameters instead.
521 break;
522 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000523 }
524 }
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000525
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000526 // Then the parameters specified in callable.parameters()
dslomov40ddec32017-07-03 07:15:31 -0400527 Set<String> keys = new LinkedHashSet<>(kwargs.keySet());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000528 for (Param param : callable.parameters()) {
529 SkylarkType type = getType(param);
Googlere5da53c2016-09-21 12:34:44 +0000530 if (param.noneable()) {
531 type = SkylarkType.Union.of(type, SkylarkType.NONE);
532 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000533 Object value = null;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000534 if (i < args.size()) {
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000535 value = args.get(i);
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000536 if (!param.positional()) {
537 return ArgumentListConversionResult.fromError(
538 String.format("Parameter '%s' is not positional", param.name()));
539 } else if (!type.contains(value)) {
540 return ArgumentListConversionResult.fromError(
541 String.format(
cparsonsa95884e2018-03-21 11:06:13 -0700542 "expected value of type '%s' for parameter '%s'",
543 type.toString(), param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000544 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000545 i++;
546 } else if (param.named() && keys.remove(param.name())) {
547 // Named parameters
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000548 value = kwargs.get(param.name());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000549 if (!type.contains(value)) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000550 return ArgumentListConversionResult.fromError(
551 String.format(
cparsonsa95884e2018-03-21 11:06:13 -0700552 "expected value of type '%s' for parameter '%s'",
553 type.toString(), param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000554 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000555 } else {
556 // Use default value
557 if (param.defaultValue().isEmpty()) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000558 return ArgumentListConversionResult.fromError(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000559 String.format("parameter '%s' has no default value", param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000560 }
Pedro Liberal Fernandezd0c5ff22016-08-18 18:53:46 +0000561 value = SkylarkSignatureProcessor.getDefaultValue(param, null);
562 }
563 builder.add(value);
564 if (!param.noneable() && value instanceof NoneType) {
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000565 return ArgumentListConversionResult.fromError(
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000566 String.format("parameter '%s' cannot be None", param.name()));
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000567 }
568 }
dslomov40ddec32017-07-03 07:15:31 -0400569 if (i < args.size()) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000570 return ArgumentListConversionResult.fromError("too many arguments");
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000571 }
dslomov40ddec32017-07-03 07:15:31 -0400572 if (!keys.isEmpty()) {
573 return ArgumentListConversionResult.fromError(
574 String.format("unexpected keyword%s %s",
cparsons5d446a72018-03-07 11:01:17 -0800575 keys.size() > 1 ? "s" : "",
576 Joiner.on(",").join(Iterables.transform(keys, s -> "'" + s + "'"))));
dslomov40ddec32017-07-03 07:15:31 -0400577 }
cparsons5d446a72018-03-07 11:01:17 -0800578
579 // Then add any skylark-info arguments (for example the Environment).
580 if (callable.useLocation()) {
581 builder.add(getLocation());
582 }
583 if (callable.useAst()) {
584 builder.add(this);
585 }
586 if (callable.useEnvironment()) {
587 builder.add(environment);
588 }
cparsons73e10162018-03-22 10:02:02 -0700589 if (callable.useSkylarkSemantics()) {
590 builder.add(environment.getSemantics());
591 }
cparsons5d446a72018-03-07 11:01:17 -0800592
Pedro Liberal Fernandez92f06a52016-09-28 07:33:42 +0000593 return ArgumentListConversionResult.fromArgumentList(builder.build());
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000594 }
595
fzaiser8c27a892017-08-11 17:26:44 +0200596 private static String formatMethod(String name, List<Object> args, Map<String, Object> kwargs) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100597 StringBuilder sb = new StringBuilder();
fzaiser8c27a892017-08-11 17:26:44 +0200598 sb.append(name).append("(");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100599 boolean first = true;
600 for (Object obj : args) {
601 if (!first) {
602 sb.append(", ");
603 }
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000604 sb.append(EvalUtils.getDataTypeName(obj));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100605 first = false;
606 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000607 for (Map.Entry<String, Object> kwarg : kwargs.entrySet()) {
608 if (!first) {
609 sb.append(", ");
610 }
611 sb.append(EvalUtils.getDataTypeName(kwarg.getValue()));
612 sb.append(" ");
613 sb.append(kwarg.getKey());
Googler344449d2016-09-15 18:25:35 +0000614 first = false;
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000615 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100616 return sb.append(")").toString();
617 }
618
619 /**
nharmata701da6e2018-02-27 14:49:17 -0800620 * Add one named argument to the keyword map, and returns whether that name has been encountered
621 * before.
Florian Weikert33f819b2015-11-09 18:15:55 +0000622 */
nharmata701da6e2018-02-27 14:49:17 -0800623 private static boolean addKeywordArgAndCheckIfDuplicate(
Florian Weikert33f819b2015-11-09 18:15:55 +0000624 Map<String, Object> kwargs,
625 String name,
nharmata701da6e2018-02-27 14:49:17 -0800626 Object value) {
627 return kwargs.put(name, value) != null;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100628 }
629
630 /**
nharmata701da6e2018-02-27 14:49:17 -0800631 * Add multiple arguments to the keyword map (**kwargs), and returns all the names of those
632 * arguments that have been encountered before or {@code null} if there are no such names.
Florian Weikert33f819b2015-11-09 18:15:55 +0000633 */
nharmata701da6e2018-02-27 14:49:17 -0800634 @Nullable
635 private static ImmutableList<String> addKeywordArgsAndReturnDuplicates(
Florian Weikert33f819b2015-11-09 18:15:55 +0000636 Map<String, Object> kwargs,
637 Object items,
Florian Weikert33f819b2015-11-09 18:15:55 +0000638 Location location)
639 throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100640 if (!(items instanceof Map<?, ?>)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000641 throw new EvalException(
642 location,
Laurent Le Brun1159cc22017-01-04 12:27:39 +0000643 "argument after ** must be a dictionary, not '" + EvalUtils.getDataTypeName(items) + "'");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100644 }
nharmata701da6e2018-02-27 14:49:17 -0800645 ImmutableList.Builder<String> duplicatesBuilder = null;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100646 for (Map.Entry<?, ?> entry : ((Map<?, ?>) items).entrySet()) {
647 if (!(entry.getKey() instanceof String)) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000648 throw new EvalException(
Laurent Le Brun1159cc22017-01-04 12:27:39 +0000649 location,
650 "keywords must be strings, not '" + EvalUtils.getDataTypeName(entry.getKey()) + "'");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100651 }
nharmata701da6e2018-02-27 14:49:17 -0800652 String argName = (String) entry.getKey();
653 if (addKeywordArgAndCheckIfDuplicate(kwargs, argName, entry.getValue())) {
654 if (duplicatesBuilder == null) {
655 duplicatesBuilder = ImmutableList.builder();
656 }
657 duplicatesBuilder.add(argName);
658 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100659 }
nharmata701da6e2018-02-27 14:49:17 -0800660 return duplicatesBuilder == null ? null : duplicatesBuilder.build();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100661 }
662
Florian Weikert33f819b2015-11-09 18:15:55 +0000663 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000664 * Checks whether the given object is a {@link BaseFunction}.
665 *
Florian Weikert33f819b2015-11-09 18:15:55 +0000666 * @throws EvalException If not a BaseFunction.
667 */
shreyax529d7ea2017-09-22 17:39:06 -0400668 private static BaseFunction checkCallable(Object functionValue, Location location)
Florian Weikert33f819b2015-11-09 18:15:55 +0000669 throws EvalException {
670 if (functionValue instanceof BaseFunction) {
671 return (BaseFunction) functionValue;
672 } else {
673 throw new EvalException(
674 location, "'" + EvalUtils.getDataTypeName(functionValue) + "' object is not callable");
675 }
676 }
677
678 /**
Florian Weikert33f819b2015-11-09 18:15:55 +0000679 * Call a method depending on the type of an object it is called on.
680 *
Florian Weikert33f819b2015-11-09 18:15:55 +0000681 * @param positionals The first object is expected to be the object the method is called on.
682 * @param call the original expression that caused this call, needed for rules especially
683 */
shreyax529d7ea2017-09-22 17:39:06 -0400684 private Object invokeObjectMethod(
Florian Weikert33f819b2015-11-09 18:15:55 +0000685 String method,
686 ImmutableList<Object> positionals,
687 ImmutableMap<String, Object> keyWordArgs,
688 FuncallExpression call,
689 Environment env)
690 throws EvalException, InterruptedException {
691 Location location = call.getLocation();
692 Object value = positionals.get(0);
693 ImmutableList<Object> positionalArgs = positionals.subList(1, positionals.size());
brandjondc2c5502017-12-07 14:30:04 -0800694 BaseFunction function = Runtime.getBuiltinRegistry().getFunction(value.getClass(), method);
Michael Staib79bd2c22017-03-22 14:37:03 +0000695 Object fieldValue =
696 (value instanceof ClassObject) ? ((ClassObject) value).getValue(method) : null;
Florian Weikert33f819b2015-11-09 18:15:55 +0000697 if (function != null) {
698 if (!isNamespace(value.getClass())) {
699 // Use self as an implicit parameter in front.
700 positionalArgs = positionals;
701 }
702 return function.call(
brandjon990622b2017-07-11 19:56:45 +0200703 positionalArgs, ImmutableMap.copyOf(keyWordArgs), call, env);
Michael Staib79bd2c22017-03-22 14:37:03 +0000704 } else if (fieldValue != null) {
Florian Weikert33f819b2015-11-09 18:15:55 +0000705 if (!(fieldValue instanceof BaseFunction)) {
706 throw new EvalException(
707 location, String.format("struct field '%s' is not a function", method));
708 }
709 function = (BaseFunction) fieldValue;
710 return function.call(
brandjon990622b2017-07-11 19:56:45 +0200711 positionalArgs, ImmutableMap.copyOf(keyWordArgs), call, env);
Laurent Le Brun88df1f52015-12-23 13:31:44 +0000712 } else {
Florian Weikert33f819b2015-11-09 18:15:55 +0000713 // When calling a Java method, the name is not in the Environment,
brandjon990622b2017-07-11 19:56:45 +0200714 // so evaluating 'function' would fail.
Florian Weikert33f819b2015-11-09 18:15:55 +0000715 Class<?> objClass;
716 Object obj;
717 if (value instanceof Class<?>) {
718 // Static call
719 obj = null;
720 objClass = (Class<?>) value;
721 } else {
722 obj = value;
723 objClass = value.getClass();
724 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000725 Pair<MethodDescriptor, List<Object>> javaMethod =
cparsons5d446a72018-03-07 11:01:17 -0800726 call.findJavaMethod(objClass, method, positionalArgs, keyWordArgs, env);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000727 if (javaMethod.first.getAnnotation().structField()) {
728 // Not a method but a callable attribute
729 try {
730 return callFunction(javaMethod.first.getMethod().invoke(obj), env);
731 } catch (IllegalAccessException e) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000732 throw new EvalException(getLocation(), "method invocation failed: " + e);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000733 } catch (InvocationTargetException e) {
734 if (e.getCause() instanceof FuncallException) {
735 throw new EvalException(getLocation(), e.getCause().getMessage());
736 } else if (e.getCause() != null) {
737 throw new EvalExceptionWithJavaCause(getLocation(), e.getCause());
738 } else {
739 // This is unlikely to happen
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000740 throw new EvalException(getLocation(), "method invocation failed: " + e);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000741 }
742 }
743 }
Damien Martin-Guillerez2d32c582016-08-04 14:29:18 +0000744 return callMethod(javaMethod.first, method, obj, javaMethod.second.toArray(), location, env);
Florian Weikert33f819b2015-11-09 18:15:55 +0000745 }
746 }
747
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000748 @SuppressWarnings("unchecked")
749 private void evalArguments(ImmutableList.Builder<Object> posargs, Map<String, Object> kwargs,
Francois-Rene Rideau93ed7f12015-10-20 15:39:33 +0000750 Environment env)
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000751 throws EvalException, InterruptedException {
nharmata701da6e2018-02-27 14:49:17 -0800752 // Optimize allocations for the common case where they are no duplicates.
753 ImmutableList.Builder<String> duplicatesBuilder = null;
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000754 // Iterate over the arguments. We assume all positional arguments come before any keyword
755 // or star arguments, because the argument list was already validated by
756 // Argument#validateFuncallArguments, as called by the Parser,
757 // which should be the only place that build FuncallExpression-s.
michajlo5f394752017-10-06 23:51:10 +0200758 // Argument lists are typically short and functions are frequently called, so go by index
759 // (O(1) for ImmutableList) to avoid the iterator overhead.
760 for (int i = 0; i < arguments.size(); i++) {
761 Argument.Passed arg = arguments.get(i);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100762 Object value = arg.getValue().eval(env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100763 if (arg.isPositional()) {
764 posargs.add(value);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000765 } else if (arg.isStar()) { // expand the starArg
Laurent Le Brun54b9d2c2017-02-20 14:04:57 +0000766 if (!(value instanceof Iterable)) {
767 throw new EvalException(
768 getLocation(),
769 "argument after * must be an iterable, not " + EvalUtils.getDataTypeName(value));
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000770 }
Laurent Le Brun54b9d2c2017-02-20 14:04:57 +0000771 posargs.addAll((Iterable<Object>) value);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000772 } else if (arg.isStarStar()) { // expand the kwargs
nharmata701da6e2018-02-27 14:49:17 -0800773 ImmutableList<String> duplicates =
774 addKeywordArgsAndReturnDuplicates(kwargs, value, getLocation());
775 if (duplicates != null) {
776 if (duplicatesBuilder == null) {
777 duplicatesBuilder = ImmutableList.builder();
778 }
779 duplicatesBuilder.addAll(duplicates);
780 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100781 } else {
nharmata701da6e2018-02-27 14:49:17 -0800782 if (addKeywordArgAndCheckIfDuplicate(kwargs, arg.getName(), value)) {
783 if (duplicatesBuilder == null) {
784 duplicatesBuilder = ImmutableList.builder();
785 }
786 duplicatesBuilder.add(arg.getName());
787 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100788 }
789 }
nharmata701da6e2018-02-27 14:49:17 -0800790 if (duplicatesBuilder != null) {
791 ImmutableList<String> dups = duplicatesBuilder.build();
792 throw new EvalException(
793 getLocation(),
794 "duplicate keyword"
795 + (dups.size() > 1 ? "s" : "")
796 + " '"
797 + Joiner.on("', '").join(dups)
798 + "' in call to "
799 + function);
800 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100801 }
802
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000803 @VisibleForTesting
804 public static boolean isNamespace(Class<?> classObject) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100805 return classObject.isAnnotationPresent(SkylarkModule.class)
806 && classObject.getAnnotation(SkylarkModule.class).namespace();
807 }
808
809 @Override
Florian Weikert90a15962015-09-11 13:43:10 +0000810 Object doEval(Environment env) throws EvalException, InterruptedException {
fzaisere0f13332017-08-14 12:00:51 +0200811 // TODO: Remove this special case once method resolution and invocation are supported as
812 // separate steps.
fzaiser8c27a892017-08-11 17:26:44 +0200813 if (function instanceof DotExpression) {
814 return invokeObjectMethod(env, (DotExpression) function);
815 }
fzaisere0f13332017-08-14 12:00:51 +0200816 Object funcValue = function.eval(env);
817 return callFunction(funcValue, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000818 }
819
fzaiser8c27a892017-08-11 17:26:44 +0200820 /** Invokes object.function() and returns the result. */
821 private Object invokeObjectMethod(Environment env, DotExpression dot)
822 throws EvalException, InterruptedException {
823 Object objValue = dot.getObject().eval(env);
Ulf Adams07dba942015-03-05 14:47:37 +0000824 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000825 posargs.add(objValue);
Francois-Rene Rideau5dcdbf92015-02-19 18:36:17 +0000826 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
827 // 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 +0000828 Map<String, Object> kwargs = new LinkedHashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000829 evalArguments(posargs, kwargs, env);
830 return invokeObjectMethod(
fzaiser8c27a892017-08-11 17:26:44 +0200831 dot.getField().getName(), posargs.build(), ImmutableMap.copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000832 }
Googlera34d5072015-03-05 13:51:28 +0000833
Florian Weikert3f610e82015-08-18 14:37:46 +0000834 /**
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000835 * Calls a function object
836 */
837 private Object callFunction(Object funcValue, Environment env)
838 throws EvalException, InterruptedException {
Florian Weikert3f610e82015-08-18 14:37:46 +0000839 ImmutableList.Builder<Object> posargs = new ImmutableList.Builder<>();
840 // We copy this into an ImmutableMap in the end, but we can't use an ImmutableMap.Builder, or
841 // 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 +0000842 Map<String, Object> kwargs = new LinkedHashMap<>();
Florian Weikert33f819b2015-11-09 18:15:55 +0000843 BaseFunction function = checkCallable(funcValue, getLocation());
844 evalArguments(posargs, kwargs, env);
Vladimir Moskvaa5b16742016-10-31 14:09:41 +0000845 return function.call(posargs.build(), ImmutableMap.copyOf(kwargs), this, env);
Florian Weikert3f610e82015-08-18 14:37:46 +0000846 }
847
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000848 /**
849 * Returns the value of the argument 'name' (or null if there is none).
850 * This function is used to associate debugging information to rules created by skylark "macros".
851 */
852 @Nullable
853 public String getNameArg() {
brandjon990622b2017-07-11 19:56:45 +0200854 for (Argument.Passed arg : arguments) {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000855 if (arg != null) {
856 String name = arg.getName();
857 if (name != null && name.equals("name")) {
858 Expression expr = arg.getValue();
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000859 return (expr instanceof StringLiteral) ? ((StringLiteral) expr).getValue() : null;
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000860 }
861 }
862 }
863 return null;
864 }
865
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100866 @Override
867 public void accept(SyntaxTreeVisitor visitor) {
868 visitor.visit(this);
869 }
870
871 @Override
laurentlbaf682d12017-08-24 20:32:02 +0200872 public Kind kind() {
873 return Kind.FUNCALL;
874 }
875
876 @Override
Florian Weikert90a15962015-09-11 13:43:10 +0000877 protected boolean isNewScope() {
878 return true;
879 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100880}