blob: a5724ee3bcde6785be19092f89e91a2b64512e2f [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net.starlark.java.eval;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import net.starlark.java.syntax.Location;
/**
* The StarlarkCallable interface is implemented by all Starlark values that may be called from
* Starlark like a function, including built-in functions and methods, Starlark functions, and
* application-defined objects (such as rules, aspects, and providers in Bazel).
*
* <p>It defines two methods: {@code fastcall}, for performance, or {@code call} for convenience. By
* default, {@code fastcall} delegates to {@code call}, and call throws an exception, so an
* implementer may override either one.
*/
public interface StarlarkCallable extends StarlarkValue {
/**
* Defines the "convenient" implementation of function calling for a callable value.
*
* <p>Do not call this function directly. Use the {@link Starlark#call} function to make a call,
* as it handles necessary book-keeping such as maintenance of the call stack, exception handling,
* and so on.
*
* <p>The default implementation throws an EvalException.
*
* <p>See {@link Starlark#fastcall} for basic information about function calls.
*
* @param thread the StarlarkThread in which the function is called
* @param args a tuple of the arguments passed by position
* @param kwargs a new, mutable dict of the arguments passed by keyword. Iteration order is
* determined by keyword order in the call expression.
*/
default Object call(StarlarkThread thread, Tuple args, Dict<String, Object> kwargs)
throws EvalException, InterruptedException {
throw Starlark.errorf("function %s not implemented", getName());
}
/**
* Defines the "fast" implementation of function calling for a callable value.
*
* <p>Do not call this function directly. Use the {@link Starlark#call} or {@link
* Starlark#fastcall} function to make a call, as it handles necessary book-keeping such as
* maintenance of the call stack, exception handling, and so on.
*
* <p>The fastcall implementation takes ownership of the two arrays, and may retain them
* indefinitely or modify them. The caller must not modify or even access the two arrays after
* making the call.
*
* <p>This method defines the low-level or "fast" calling convention. A more convenient interface
* is provided by the {@link #call} method, which provides a signature analogous to {@code def
* f(*args, **kwargs)}, or possibly the "self-call" feature of the {@link StarlarkMethod#selfCall}
* annotation mechanism.
*
* <p>The default implementation forwards the call to {@code call}, after rejecting any duplicate
* named arguments. Other implementations of this method should similarly reject duplicates.
*
* <p>See {@link Starlark#fastcall} for basic information about function calls.
*
* @param thread the StarlarkThread in which the function is called
* @param positional a list of positional arguments
* @param named a list of named arguments, as alternating Strings/Objects. May contain dups.
*/
default Object fastcall(StarlarkThread thread, Object[] positional, Object[] named)
throws EvalException, InterruptedException {
LinkedHashMap<String, Object> kwargs = Maps.newLinkedHashMapWithExpectedSize(named.length >> 1);
for (int i = 0; i < named.length; i += 2) {
if (kwargs.put((String) named[i], named[i + 1]) != null) {
throw Starlark.errorf("%s got multiple values for parameter '%s'", this, named[i]);
}
}
return call(thread, Tuple.of(positional), Dict.wrap(thread.mutability(), kwargs));
}
/**
* Defines the "fast" implementation variant of function calling with only positional arguments.
*
* <p>Do not call this function directly. Use the {@link Starlark#easycall} function to make a
* call, as it handles necessary book-keeping such as maintenance of the call stack, exception
* handling, and so on.
*
* <p>The fastcall implementation takes ownership of the {@code positional} array, and may retain
* it indefinitely or modify it. The caller must not modify or even access the array after making
* the call.
*
* <p>The default implementation forwards the call to {@code call}.
*
* @param thread the StarlarkThread in which the function is called
* @param positional a list of positional arguments
*/
default Object positionalOnlyCall(StarlarkThread thread, Object... positional)
throws EvalException, InterruptedException {
ArgumentProcessor argumentProcessor = requestArgumentProcessor(thread);
for (Object value : positional) {
argumentProcessor.addPositionalArg(value);
}
return argumentProcessor.call(thread);
}
/**
* Defines a helper object for invoking a StarlarkCallable.
*
* <p>An ArgumentProcessor implementation is returned by {@link #requestArgumentProcessor}. The
* ArgumentProcessor implementation must then be used to first place the arguments, and then its
* method {@link #call} is used to make the invocation.
*/
abstract static class ArgumentProcessor {
protected final StarlarkThread thread;
public ArgumentProcessor(StarlarkThread thread) {
this.thread = thread;
}
public abstract void addPositionalArg(Object value) throws EvalException;
public abstract void addNamedArg(String name, Object value) throws EvalException;
public abstract StarlarkCallable getCallable();
public abstract Object call(StarlarkThread thread) throws EvalException, InterruptedException;
/**
* Throws a given {@code EvalException} from inside {@link #addPositionalArg} or {@link
* #addNamedArg}.
*
* <p>In the Starlark evaluation model, the work of ArgumentProcessor is logically part of the
* callable's evaluation, so the stack trace for any exceptions thrown during argument
* processing needs to contain the name and location of the callable. This method pushes the
* stack before throwing the exception, ensuring that the stack trace is as expected.
*/
protected void pushCallableAndThrow(EvalException e) throws EvalException {
thread.push(getCallable());
throw e;
}
}
/**
* A default implementation of ArgumentProcessor that simply stores the arguments in a list and a
* LinkedHashMap and then passes them to the StarlarkCallable.call() method.
*/
static final class DefaultArgumentProcessor extends ArgumentProcessor {
private final StarlarkCallable owner;
private final ArrayList<Object> positional;
private final LinkedHashMap<String, Object> named;
DefaultArgumentProcessor(StarlarkCallable owner, StarlarkThread thread) {
super(thread);
this.owner = owner;
this.positional = new ArrayList<>();
this.named = Maps.newLinkedHashMapWithExpectedSize(0);
}
@Override
public void addPositionalArg(Object value) throws EvalException {
positional.add(value);
}
@Override
public void addNamedArg(String name, Object value) throws EvalException {
if (named.put(name, value) != null) {
pushCallableAndThrow(
Starlark.errorf("%s got multiple values for parameter '%s'", owner, name));
}
}
@Override
public StarlarkCallable getCallable() {
return owner;
}
@Override
public Object call(StarlarkThread thread) throws EvalException, InterruptedException {
return owner.call(
thread, Tuple.wrap(positional.toArray()), Dict.wrap(thread.mutability(), named));
}
}
/**
* Returns a FasterCall implementation if the callable supports fasterCall invocations, else null.
*/
default ArgumentProcessor requestArgumentProcessor(StarlarkThread thread) throws EvalException {
return new DefaultArgumentProcessor(this, thread);
}
/** Returns the form this callable value should take in a stack trace. */
String getName();
/**
* Returns the location of the definition of this callable value, or BUILTIN if it was not defined
* in Starlark code.
*/
default Location getLocation() {
return Location.BUILTIN;
}
}