blob: 70d2bbff30a3093d328ee66467f06cd8aba852d9 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +00002//
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.
14package com.google.devtools.build.lib.syntax;
15
tomlua155b532017-11-08 20:12:47 +010016import com.google.common.base.Preconditions;
Florian Weikertbf267622016-06-08 11:02:28 +000017import com.google.common.base.Throwables;
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +000018import com.google.devtools.build.lib.events.Location;
Googler768cbc42015-08-28 12:52:14 +000019import com.google.devtools.build.lib.profiler.Profiler;
20import com.google.devtools.build.lib.profiler.ProfilerTask;
ulfjack4cf2ebd2018-06-11 06:00:36 -070021import com.google.devtools.build.lib.profiler.SilentCloseable;
vladmos3feea742017-07-06 12:16:18 -040022import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
John Field585d1a02015-12-16 16:03:52 +000023import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
nharmata68cc06b2018-03-01 10:21:57 -080024import com.google.devtools.build.lib.syntax.Environment.LexicalFrame;
Francois-Rene Rideau4a8da552015-04-10 18:56:21 +000025import com.google.devtools.build.lib.syntax.SkylarkType.SkylarkFunctionType;
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +000026import java.lang.reflect.InvocationTargetException;
27import java.lang.reflect.Method;
28import java.util.ArrayList;
29import java.util.Arrays;
30import java.util.NoSuchElementException;
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +000031import javax.annotation.Nullable;
32
33/**
mjhalupkacfa0bb72018-03-12 12:43:15 -070034 * A class for Skylark functions provided as builtins by the Skylark implementation. Instances of
35 * this class do not need to be serializable because they should effectively be treated as
36 * constants.
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +000037 */
38public class BuiltinFunction extends BaseFunction {
39
40 /** ExtraArgKind so you can tweek your function's own calling convention */
brandjonc06e7462017-07-11 20:54:58 +020041 public enum ExtraArgKind {
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +000042 LOCATION,
43 SYNTAX_TREE,
44 ENVIRONMENT;
45 }
46 // Predefined system add-ons to function signatures
47 public static final ExtraArgKind[] USE_LOC =
48 new ExtraArgKind[] {ExtraArgKind.LOCATION};
49 public static final ExtraArgKind[] USE_LOC_ENV =
50 new ExtraArgKind[] {ExtraArgKind.LOCATION, ExtraArgKind.ENVIRONMENT};
51 public static final ExtraArgKind[] USE_AST =
52 new ExtraArgKind[] {ExtraArgKind.SYNTAX_TREE};
53 public static final ExtraArgKind[] USE_AST_ENV =
54 new ExtraArgKind[] {ExtraArgKind.SYNTAX_TREE, ExtraArgKind.ENVIRONMENT};
55
nharmata68cc06b2018-03-01 10:21:57 -080056 // Builtins cannot create or modify variable bindings. So it's sufficient to use a shared
57 // instance.
58 private static final LexicalFrame SHARED_LEXICAL_FRAME_FOR_BUILTIN_FUNCTION_CALLS =
59 LexicalFrame.create(Mutability.IMMUTABLE);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +000060
61 // The underlying invoke() method.
62 @Nullable private Method invokeMethod;
63
64 // extra arguments required beside signature.
65 @Nullable private ExtraArgKind[] extraArgs;
66
67 // The count of arguments in the inner invoke method,
68 // to be used as size of argument array by the outer call method.
69 private int innerArgumentCount;
70
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000071 // The returnType of the method.
72 private Class<?> returnType;
73
vladmos3feea742017-07-06 12:16:18 -040074 // True if the function is a rule class
75 private boolean isRule;
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +000076
77 /** Create unconfigured function from its name */
78 public BuiltinFunction(String name) {
79 super(name);
80 }
81
82 /** Creates an unconfigured BuiltinFunction with the given name and defaultValues */
83 public BuiltinFunction(String name, Iterable<Object> defaultValues) {
84 super(name, defaultValues);
85 }
86
87 /** Creates a BuiltinFunction with the given name and signature */
88 public BuiltinFunction(String name, FunctionSignature signature) {
89 super(name, signature);
90 configure();
91 }
92
93 /** Creates a BuiltinFunction with the given name and signature with values */
94 public BuiltinFunction(String name,
95 FunctionSignature.WithValues<Object, SkylarkType> signature) {
96 super(name, signature);
97 configure();
98 }
99
100 /** Creates a BuiltinFunction with the given name and signature and extra arguments */
101 public BuiltinFunction(String name, FunctionSignature signature, ExtraArgKind[] extraArgs) {
102 super(name, signature);
103 this.extraArgs = extraArgs;
104 configure();
105 }
106
vladmos3feea742017-07-06 12:16:18 -0400107 /** Creates a BuiltinFunction with the given name, signature, extra arguments, and a rule flag */
108 public BuiltinFunction(
109 String name, FunctionSignature signature, ExtraArgKind[] extraArgs, boolean isRule) {
110 super(name, signature);
111 this.extraArgs = extraArgs;
112 this.isRule = isRule;
113 configure();
114 }
115
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000116 /** Creates a BuiltinFunction with the given name, signature with values, and extra arguments */
117 public BuiltinFunction(String name,
118 FunctionSignature.WithValues<Object, SkylarkType> signature, ExtraArgKind[] extraArgs) {
119 super(name, signature);
120 this.extraArgs = extraArgs;
121 configure();
122 }
123
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000124 /** Creates a BuiltinFunction from the given name and a Factory */
125 public BuiltinFunction(String name, Factory factory) {
126 super(name);
127 configure(factory);
128 }
129
130 @Override
131 protected int getArgArraySize () {
132 return innerArgumentCount;
133 }
134
135 protected ExtraArgKind[] getExtraArgs () {
136 return extraArgs;
137 }
138
139 @Override
140 @Nullable
brendandouglasfe785e12018-06-12 09:39:38 -0700141 public Object call(Object[] args, @Nullable FuncallExpression ast, Environment env)
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000142 throws EvalException, InterruptedException {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000143 Preconditions.checkNotNull(env);
Vladimir Moskva658a8ea2016-09-02 15:39:17 +0000144
145 // ast is null when called from Java (as there's no Skylark call site).
146 Location loc = ast == null ? Location.BUILTIN : ast.getLocation();
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000147
148 // Add extra arguments, if needed
149 if (extraArgs != null) {
150 int i = args.length - extraArgs.length;
151 for (BuiltinFunction.ExtraArgKind extraArg : extraArgs) {
152 switch(extraArg) {
153 case LOCATION:
154 args[i] = loc;
155 break;
156
157 case SYNTAX_TREE:
158 args[i] = ast;
159 break;
160
161 case ENVIRONMENT:
162 args[i] = env;
163 break;
164 }
165 i++;
166 }
167 }
168
169 // Last but not least, actually make an inner call to the function with the resolved arguments.
ulfjack4cf2ebd2018-06-11 06:00:36 -0700170 try (SilentCloseable c =
171 Profiler.instance().profile(ProfilerTask.SKYLARK_BUILTIN_FN, getName())) {
nharmata68cc06b2018-03-01 10:21:57 -0800172 env.enterScope(this, SHARED_LEXICAL_FRAME_FOR_BUILTIN_FUNCTION_CALLS, ast, env.getGlobals());
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000173 return invokeMethod.invoke(this, args);
174 } catch (InvocationTargetException x) {
175 Throwable e = x.getCause();
Florian Weikertbf267622016-06-08 11:02:28 +0000176
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000177 if (e instanceof EvalException) {
Francois-Rene Rideaub6038e02015-06-11 19:51:16 +0000178 throw ((EvalException) e).ensureLocation(loc);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000179 } else if (e instanceof IllegalArgumentException) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000180 throw new EvalException(loc, "illegal argument in call to " + getName(), e);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000181 }
Benjamin Petersonbf1db782018-08-08 10:03:22 -0700182 Throwables.throwIfInstanceOf(e, InterruptedException.class);
183 Throwables.throwIfUnchecked(e);
Florian Weikertbf267622016-06-08 11:02:28 +0000184 throw badCallException(loc, e, args);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000185 } catch (IllegalArgumentException e) {
Florian Weikertee5e5e12015-08-11 16:47:31 +0000186 // Either this was thrown by Java itself, or it's a bug
187 // To cover the first case, let's manually check the arguments.
188 final int len = args.length - ((extraArgs == null) ? 0 : extraArgs.length);
189 final Class<?>[] types = invokeMethod.getParameterTypes();
190 for (int i = 0; i < args.length; i++) {
191 if (args[i] != null && !types[i].isAssignableFrom(args[i].getClass())) {
192 String paramName =
193 i < len ? signature.getSignature().getNames().get(i) : extraArgs[i - len].name();
Florian Weikertee5e5e12015-08-11 16:47:31 +0000194 throw new EvalException(
195 loc,
196 String.format(
laurentlb9e540882017-07-07 06:58:45 -0400197 "argument '%s' has type '%s', but should be '%s'\nin call to builtin %s %s",
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000198 paramName,
199 EvalUtils.getDataTypeName(args[i]),
laurentlb9e540882017-07-07 06:58:45 -0400200 EvalUtils.getDataTypeNameFromClass(types[i]),
201 hasSelfArgument() ? "method" : "function",
202 getShortSignature()));
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000203 }
Florian Weikertee5e5e12015-08-11 16:47:31 +0000204 }
205 throw badCallException(loc, e, args);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000206 } catch (IllegalAccessException e) {
207 throw badCallException(loc, e, args);
Googler768cbc42015-08-28 12:52:14 +0000208 } finally {
Francois-Rene Rideau89312fb2015-09-10 18:53:03 +0000209 env.exitScope();
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000210 }
211 }
212
Han-Wen Nienhuys612d2212016-01-21 17:00:47 +0000213 private static String stacktraceToString(StackTraceElement[] elts) {
214 StringBuilder b = new StringBuilder();
215 for (StackTraceElement e : elts) {
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000216 b.append(e);
Han-Wen Nienhuys612d2212016-01-21 17:00:47 +0000217 b.append("\n");
218 }
219 return b.toString();
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000220 }
221
Han-Wen Nienhuys612d2212016-01-21 17:00:47 +0000222 private IllegalStateException badCallException(Location loc, Throwable e, Object... args) {
223 // If this happens, it's a bug in our code.
224 return new IllegalStateException(
225 String.format(
226 "%s%s (%s)\n"
227 + "while calling %s with args %s\n"
228 + "Java parameter types: %s\nSkylark type checks: %s",
229 (loc == null) ? "" : loc + ": ",
230 Arrays.asList(args),
231 e.getClass().getName(),
232 stacktraceToString(e.getStackTrace()),
233 this,
234 Arrays.asList(invokeMethod.getParameterTypes()),
235 signature.getTypes()),
236 e);
237 }
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000238
239 /** Configure the reflection mechanism */
240 @Override
241 public void configure(SkylarkSignature annotation) {
242 Preconditions.checkState(!isConfigured()); // must not be configured yet
243 enforcedArgumentTypes = new ArrayList<>();
244 this.extraArgs = SkylarkSignatureProcessor.getExtraArgs(annotation);
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000245 this.returnType = annotation.returnType();
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000246 super.configure(annotation);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000247 }
248
249 // finds the method and makes it accessible (which is needed to find it, and later to use it)
250 protected Method findMethod(final String name) {
251 Method found = null;
252 for (Method method : this.getClass().getDeclaredMethods()) {
253 method.setAccessible(true);
254 if (name.equals(method.getName())) {
Francois-Rene Rideau0b6fdcd2015-03-31 21:47:03 +0000255 if (found != null) {
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000256 throw new IllegalArgumentException(String.format(
257 "function %s has more than one method named %s", getName(), name));
258 }
259 found = method;
260 }
261 }
262 if (found == null) {
263 throw new NoSuchElementException(String.format(
264 "function %s doesn't have a method named %s", getName(), name));
265 }
266 return found;
267 }
268
269 /** Configure the reflection mechanism */
270 @Override
271 protected void configure() {
272 invokeMethod = findMethod("invoke");
273
274 int arguments = signature.getSignature().getShape().getArguments();
275 innerArgumentCount = arguments + (extraArgs == null ? 0 : extraArgs.length);
276 Class<?>[] parameterTypes = invokeMethod.getParameterTypes();
Janak Ramakrishnanba395072016-01-19 15:46:42 +0000277 if (innerArgumentCount != parameterTypes.length) {
278 // Guard message construction by check to avoid autoboxing two integers.
279 throw new IllegalStateException(
280 String.format(
281 "bad argument count for %s: method has %s arguments, type list has %s",
282 getName(),
283 innerArgumentCount,
284 parameterTypes.length));
285 }
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000286
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000287 if (enforcedArgumentTypes != null) {
288 for (int i = 0; i < arguments; i++) {
289 SkylarkType enforcedType = enforcedArgumentTypes.get(i);
290 if (enforcedType != null) {
291 Class<?> parameterType = parameterTypes[i];
Francois-Rene Rideau4a8da552015-04-10 18:56:21 +0000292 String msg = String.format(
293 "fun %s(%s), param %s, enforcedType: %s (%s); parameterType: %s",
294 getName(), signature, signature.getSignature().getNames().get(i),
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000295 enforcedType, enforcedType.getType(), parameterType);
Francois-Rene Rideau4a8da552015-04-10 18:56:21 +0000296 if (enforcedType instanceof SkylarkType.Simple
297 || enforcedType instanceof SkylarkFunctionType) {
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000298 Preconditions.checkArgument(
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000299 enforcedType.getType() == parameterType, msg);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000300 // No need to enforce Simple types on the Skylark side, the JVM will do it for us.
301 enforcedArgumentTypes.set(i, null);
302 } else if (enforcedType instanceof SkylarkType.Combination) {
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000303 Preconditions.checkArgument(enforcedType.getType() == parameterType, msg);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000304 } else {
305 Preconditions.checkArgument(
306 parameterType == Object.class || parameterType == null, msg);
307 }
308 }
309 }
310 }
311 // No need for the enforcedArgumentTypes List if all the types were Simple
brandjonc06e7462017-07-11 20:54:58 +0200312 enforcedArgumentTypes = FunctionSignature.valueListOrNull(enforcedArgumentTypes);
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000313
314 if (returnType != null) {
315 Class<?> type = returnType;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000316 Class<?> methodReturnType = invokeMethod.getReturnType();
317 Preconditions.checkArgument(type == methodReturnType,
318 "signature for function %s says it returns %s but its invoke method returns %s",
319 getName(), returnType, methodReturnType);
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000320 }
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000321 }
322
323 /** Configure by copying another function's configuration */
324 // Alternatively, we could have an extension BuiltinFunctionSignature of FunctionSignature,
325 // and use *that* instead of a Factory.
326 public void configure(BuiltinFunction.Factory factory) {
327 // this function must not be configured yet, but the factory must be
328 Preconditions.checkState(!isConfigured());
329 Preconditions.checkState(factory.isConfigured(),
330 "function factory is not configured for %s", getName());
331
332 this.paramDoc = factory.getParamDoc();
333 this.signature = factory.getSignature();
334 this.extraArgs = factory.getExtraArgs();
335 this.objectType = factory.getObjectType();
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000336 configure();
337 }
338
339 /**
340 * A Factory allows for a @SkylarkSignature annotation to be provided and processed in advance
341 * for a function that will be defined later as a closure (see e.g. in PackageFactory).
342 *
343 * <p>Each instance of this class must define a method create that closes over some (final)
344 * variables and returns a BuiltinFunction.
345 */
346 public abstract static class Factory extends BuiltinFunction {
347 @Nullable private Method createMethod;
348
349 /** Create unconfigured function Factory from its name */
350 public Factory(String name) {
351 super(name);
352 }
353
354 /** Creates an unconfigured function Factory with the given name and defaultValues */
355 public Factory(String name, Iterable<Object> defaultValues) {
356 super(name, defaultValues);
357 }
358
359 @Override
360 public void configure() {
361 if (createMethod != null) {
362 return;
363 }
364 createMethod = findMethod("create");
365 }
366
367 @Override
tomlud8a21e32018-04-24 18:49:52 -0700368 public Object call(Object[] args, @Nullable FuncallExpression ast, Environment env)
369 throws EvalException {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000370 throw new EvalException(null, "tried to invoke a Factory for function " + this);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000371 }
372
373 /** Instantiate the Factory
374 * @param args arguments to pass to the create method
375 * @return a new BuiltinFunction that closes over the arguments
376 */
377 public BuiltinFunction apply(Object... args) {
378 try {
379 return (BuiltinFunction) createMethod.invoke(this, args);
380 } catch (InvocationTargetException | IllegalArgumentException | IllegalAccessException e) {
381 throw new RuntimeException(String.format(
382 "Exception while applying BuiltinFunction.Factory %s: %s",
383 this, e.getMessage()), e);
384 }
385 }
386 }
vladmos3feea742017-07-06 12:16:18 -0400387
388 @Override
389 public void repr(SkylarkPrinter printer) {
390 if (isRule) {
391 printer.append("<built-in rule " + getName() + ">");
392 } else {
393 printer.append("<built-in function " + getName() + ">");
394 }
395 }
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000396}