blob: 5887f005690c038d05d2494948d2e887ea046252 [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;
Francois-Rene Rideau4a8da552015-04-10 18:56:21 +000024import com.google.devtools.build.lib.syntax.SkylarkType.SkylarkFunctionType;
Googlera3421e22019-09-26 06:48:32 -070025import com.google.devtools.build.lib.syntax.StarlarkThread.LexicalFrame;
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
Googler298fcb82019-09-24 12:56:21 -070077 /** Create unconfigured (signature-less) function from its name */
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +000078 public BuiltinFunction(String name) {
79 super(name);
80 }
81
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +000082 /** Creates a BuiltinFunction with the given name and signature */
83 public BuiltinFunction(String name, FunctionSignature signature) {
84 super(name, signature);
85 configure();
86 }
87
88 /** Creates a BuiltinFunction with the given name and signature with values */
89 public BuiltinFunction(String name,
90 FunctionSignature.WithValues<Object, SkylarkType> signature) {
91 super(name, signature);
92 configure();
93 }
94
95 /** Creates a BuiltinFunction with the given name and signature and extra arguments */
96 public BuiltinFunction(String name, FunctionSignature signature, ExtraArgKind[] extraArgs) {
97 super(name, signature);
98 this.extraArgs = extraArgs;
99 configure();
100 }
101
vladmos3feea742017-07-06 12:16:18 -0400102 /** Creates a BuiltinFunction with the given name, signature, extra arguments, and a rule flag */
103 public BuiltinFunction(
104 String name, FunctionSignature signature, ExtraArgKind[] extraArgs, boolean isRule) {
105 super(name, signature);
106 this.extraArgs = extraArgs;
107 this.isRule = isRule;
108 configure();
109 }
110
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000111 /** Creates a BuiltinFunction with the given name, signature with values, and extra arguments */
112 public BuiltinFunction(String name,
113 FunctionSignature.WithValues<Object, SkylarkType> signature, ExtraArgKind[] extraArgs) {
114 super(name, signature);
115 this.extraArgs = extraArgs;
116 configure();
117 }
118
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000119 /** Creates a BuiltinFunction from the given name and a Factory */
120 public BuiltinFunction(String name, Factory factory) {
121 super(name);
122 configure(factory);
123 }
124
125 @Override
126 protected int getArgArraySize () {
127 return innerArgumentCount;
128 }
129
130 protected ExtraArgKind[] getExtraArgs () {
131 return extraArgs;
132 }
133
134 @Override
135 @Nullable
Googlera3421e22019-09-26 06:48:32 -0700136 public Object call(Object[] args, @Nullable FuncallExpression ast, StarlarkThread thread)
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000137 throws EvalException, InterruptedException {
Googlera3421e22019-09-26 06:48:32 -0700138 Preconditions.checkNotNull(thread);
Vladimir Moskva658a8ea2016-09-02 15:39:17 +0000139
140 // ast is null when called from Java (as there's no Skylark call site).
141 Location loc = ast == null ? Location.BUILTIN : ast.getLocation();
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000142
143 // Add extra arguments, if needed
144 if (extraArgs != null) {
145 int i = args.length - extraArgs.length;
146 for (BuiltinFunction.ExtraArgKind extraArg : extraArgs) {
147 switch(extraArg) {
148 case LOCATION:
149 args[i] = loc;
150 break;
151
152 case SYNTAX_TREE:
153 args[i] = ast;
154 break;
155
156 case ENVIRONMENT:
Googlera3421e22019-09-26 06:48:32 -0700157 args[i] = thread;
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000158 break;
159 }
160 i++;
161 }
162 }
163
164 // Last but not least, actually make an inner call to the function with the resolved arguments.
ulfjack4cf2ebd2018-06-11 06:00:36 -0700165 try (SilentCloseable c =
laurentlb3cdfd1a2018-11-09 04:55:08 -0800166 Profiler.instance().profile(ProfilerTask.STARLARK_BUILTIN_FN, getName())) {
Googlera3421e22019-09-26 06:48:32 -0700167 thread.enterScope(
168 this, SHARED_LEXICAL_FRAME_FOR_BUILTIN_FUNCTION_CALLS, ast, thread.getGlobals());
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000169 return invokeMethod.invoke(this, args);
170 } catch (InvocationTargetException x) {
171 Throwable e = x.getCause();
Florian Weikertbf267622016-06-08 11:02:28 +0000172
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000173 if (e instanceof EvalException) {
Francois-Rene Rideaub6038e02015-06-11 19:51:16 +0000174 throw ((EvalException) e).ensureLocation(loc);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000175 } else if (e instanceof IllegalArgumentException) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000176 throw new EvalException(loc, "illegal argument in call to " + getName(), e);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000177 }
Benjamin Petersonbf1db782018-08-08 10:03:22 -0700178 Throwables.throwIfInstanceOf(e, InterruptedException.class);
179 Throwables.throwIfUnchecked(e);
Florian Weikertbf267622016-06-08 11:02:28 +0000180 throw badCallException(loc, e, args);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000181 } catch (IllegalArgumentException e) {
Florian Weikertee5e5e12015-08-11 16:47:31 +0000182 // Either this was thrown by Java itself, or it's a bug
183 // To cover the first case, let's manually check the arguments.
184 final int len = args.length - ((extraArgs == null) ? 0 : extraArgs.length);
185 final Class<?>[] types = invokeMethod.getParameterTypes();
186 for (int i = 0; i < args.length; i++) {
187 if (args[i] != null && !types[i].isAssignableFrom(args[i].getClass())) {
188 String paramName =
189 i < len ? signature.getSignature().getNames().get(i) : extraArgs[i - len].name();
Florian Weikertee5e5e12015-08-11 16:47:31 +0000190 throw new EvalException(
191 loc,
192 String.format(
laurentlb9e540882017-07-07 06:58:45 -0400193 "argument '%s' has type '%s', but should be '%s'\nin call to builtin %s %s",
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000194 paramName,
195 EvalUtils.getDataTypeName(args[i]),
laurentlb9e540882017-07-07 06:58:45 -0400196 EvalUtils.getDataTypeNameFromClass(types[i]),
197 hasSelfArgument() ? "method" : "function",
198 getShortSignature()));
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000199 }
Florian Weikertee5e5e12015-08-11 16:47:31 +0000200 }
201 throw badCallException(loc, e, args);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000202 } catch (IllegalAccessException e) {
203 throw badCallException(loc, e, args);
Googler768cbc42015-08-28 12:52:14 +0000204 } finally {
Googlera3421e22019-09-26 06:48:32 -0700205 thread.exitScope();
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000206 }
207 }
208
Han-Wen Nienhuys612d2212016-01-21 17:00:47 +0000209 private static String stacktraceToString(StackTraceElement[] elts) {
210 StringBuilder b = new StringBuilder();
211 for (StackTraceElement e : elts) {
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000212 b.append(e);
Han-Wen Nienhuys612d2212016-01-21 17:00:47 +0000213 b.append("\n");
214 }
215 return b.toString();
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000216 }
217
Han-Wen Nienhuys612d2212016-01-21 17:00:47 +0000218 private IllegalStateException badCallException(Location loc, Throwable e, Object... args) {
219 // If this happens, it's a bug in our code.
220 return new IllegalStateException(
221 String.format(
222 "%s%s (%s)\n"
223 + "while calling %s with args %s\n"
laurentlb7dcad732018-10-25 05:17:20 -0700224 + "Java parameter types: %s\nStarlark type checks: %s",
Han-Wen Nienhuys612d2212016-01-21 17:00:47 +0000225 (loc == null) ? "" : loc + ": ",
226 Arrays.asList(args),
227 e.getClass().getName(),
228 stacktraceToString(e.getStackTrace()),
229 this,
230 Arrays.asList(invokeMethod.getParameterTypes()),
231 signature.getTypes()),
232 e);
233 }
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000234
235 /** Configure the reflection mechanism */
236 @Override
237 public void configure(SkylarkSignature annotation) {
238 Preconditions.checkState(!isConfigured()); // must not be configured yet
239 enforcedArgumentTypes = new ArrayList<>();
240 this.extraArgs = SkylarkSignatureProcessor.getExtraArgs(annotation);
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000241 this.returnType = annotation.returnType();
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000242 super.configure(annotation);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000243 }
244
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000245 /** Configure the reflection mechanism */
246 @Override
247 protected void configure() {
248 invokeMethod = findMethod("invoke");
249
250 int arguments = signature.getSignature().getShape().getArguments();
251 innerArgumentCount = arguments + (extraArgs == null ? 0 : extraArgs.length);
252 Class<?>[] parameterTypes = invokeMethod.getParameterTypes();
Janak Ramakrishnanba395072016-01-19 15:46:42 +0000253 if (innerArgumentCount != parameterTypes.length) {
254 // Guard message construction by check to avoid autoboxing two integers.
255 throw new IllegalStateException(
256 String.format(
257 "bad argument count for %s: method has %s arguments, type list has %s",
Googler99cbc172018-11-08 07:43:00 -0800258 getName(), innerArgumentCount, parameterTypes.length));
Janak Ramakrishnanba395072016-01-19 15:46:42 +0000259 }
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000260
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000261 if (enforcedArgumentTypes != null) {
262 for (int i = 0; i < arguments; i++) {
263 SkylarkType enforcedType = enforcedArgumentTypes.get(i);
264 if (enforcedType != null) {
265 Class<?> parameterType = parameterTypes[i];
Googler99cbc172018-11-08 07:43:00 -0800266 String msg =
267 String.format(
268 "fun %s(%s), param %s, enforcedType: %s (%s); parameterType: %s",
269 getName(),
270 signature,
271 signature.getSignature().getNames().get(i),
272 enforcedType,
273 enforcedType.getType(),
274 parameterType);
Francois-Rene Rideau4a8da552015-04-10 18:56:21 +0000275 if (enforcedType instanceof SkylarkType.Simple
276 || enforcedType instanceof SkylarkFunctionType) {
Googler99cbc172018-11-08 07:43:00 -0800277 Preconditions.checkArgument(enforcedType.getType() == parameterType, msg);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000278 // No need to enforce Simple types on the Skylark side, the JVM will do it for us.
279 enforcedArgumentTypes.set(i, null);
280 } else if (enforcedType instanceof SkylarkType.Combination) {
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000281 Preconditions.checkArgument(enforcedType.getType() == parameterType, msg);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000282 } else {
283 Preconditions.checkArgument(
284 parameterType == Object.class || parameterType == null, msg);
285 }
286 }
287 }
288 }
289 // No need for the enforcedArgumentTypes List if all the types were Simple
brandjonc06e7462017-07-11 20:54:58 +0200290 enforcedArgumentTypes = FunctionSignature.valueListOrNull(enforcedArgumentTypes);
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000291
292 if (returnType != null) {
293 Class<?> type = returnType;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000294 Class<?> methodReturnType = invokeMethod.getReturnType();
Googler99cbc172018-11-08 07:43:00 -0800295 Preconditions.checkArgument(
296 type == methodReturnType,
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000297 "signature for function %s says it returns %s but its invoke method returns %s",
Googler99cbc172018-11-08 07:43:00 -0800298 getName(),
299 returnType,
300 methodReturnType);
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000301 }
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000302 }
303
304 /** Configure by copying another function's configuration */
305 // Alternatively, we could have an extension BuiltinFunctionSignature of FunctionSignature,
306 // and use *that* instead of a Factory.
307 public void configure(BuiltinFunction.Factory factory) {
308 // this function must not be configured yet, but the factory must be
309 Preconditions.checkState(!isConfigured());
Googler99cbc172018-11-08 07:43:00 -0800310 Preconditions.checkState(
311 factory.isConfigured(), "function factory is not configured for %s", getName());
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000312
313 this.paramDoc = factory.getParamDoc();
314 this.signature = factory.getSignature();
315 this.extraArgs = factory.getExtraArgs();
316 this.objectType = factory.getObjectType();
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000317 configure();
318 }
319
Googler99cbc172018-11-08 07:43:00 -0800320 // finds the method and makes it accessible (which is needed to find it, and later to use it)
321 protected Method findMethod(final String name) {
322 Method found = null;
323 for (Method method : this.getClass().getDeclaredMethods()) {
324 method.setAccessible(true);
325 if (name.equals(method.getName())) {
326 if (found != null) {
327 throw new IllegalArgumentException(
328 String.format("function %s has more than one method named %s", getName(), name));
329 }
330 found = method;
331 }
332 }
333 if (found == null) {
334 throw new NoSuchElementException(
335 String.format("function %s doesn't have a method named %s", getName(), name));
336 }
337 return found;
338 }
339
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000340 /**
341 * A Factory allows for a @SkylarkSignature annotation to be provided and processed in advance
342 * for a function that will be defined later as a closure (see e.g. in PackageFactory).
343 *
344 * <p>Each instance of this class must define a method create that closes over some (final)
345 * variables and returns a BuiltinFunction.
346 */
347 public abstract static class Factory extends BuiltinFunction {
348 @Nullable private Method createMethod;
349
350 /** Create unconfigured function Factory from its name */
351 public Factory(String name) {
352 super(name);
353 }
354
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000355 @Override
356 public void configure() {
357 if (createMethod != null) {
358 return;
359 }
360 createMethod = findMethod("create");
361 }
362
363 @Override
Googlera3421e22019-09-26 06:48:32 -0700364 public Object call(Object[] args, @Nullable FuncallExpression ast, StarlarkThread thread)
tomlud8a21e32018-04-24 18:49:52 -0700365 throws EvalException {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000366 throw new EvalException(null, "tried to invoke a Factory for function " + this);
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000367 }
368
369 /** Instantiate the Factory
370 * @param args arguments to pass to the create method
371 * @return a new BuiltinFunction that closes over the arguments
372 */
373 public BuiltinFunction apply(Object... args) {
374 try {
375 return (BuiltinFunction) createMethod.invoke(this, args);
376 } catch (InvocationTargetException | IllegalArgumentException | IllegalAccessException e) {
377 throw new RuntimeException(String.format(
378 "Exception while applying BuiltinFunction.Factory %s: %s",
379 this, e.getMessage()), e);
380 }
381 }
382 }
vladmos3feea742017-07-06 12:16:18 -0400383
384 @Override
385 public void repr(SkylarkPrinter printer) {
386 if (isRule) {
387 printer.append("<built-in rule " + getName() + ">");
388 } else {
389 printer.append("<built-in function " + getName() + ">");
390 }
391 }
Francois-Rene Rideau4feb1602015-03-18 19:49:13 +0000392}