blob: be41f9206183b13ef9029efa8b3d4cb965e52e4b [file] [edit]
// 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 static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import net.starlark.java.syntax.Location;
import net.starlark.java.syntax.StarlarkType;
import net.starlark.java.syntax.TypeContext;
import net.starlark.java.syntax.Types;
/**
* 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 {
@Override
default boolean isAcyclic() {
return true;
}
/**
* 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 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 an {@link ArgumentProcessor} which the callable can implement for faster calling.
*
* <p>By default this returns a {@link DefaultArgumentProcessor} which can be used to call the
* callable via {@link #call}.
*/
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;
}
/**
* Returns the first {@link Types.CallableType} in this callable's Starlark type hierarchy.
*
* @throws NullPointerException if no {@link Types.CallableType} was found.
*/
default Types.CallableType getCallableType(
StarlarkSemantics starlarkSemantics, TypeContext typeContext) {
StarlarkType type = Starlark.getStarlarkType(this, starlarkSemantics);
return checkNotNull(
Types.toCallableType(type, typeContext),
"Expected type '%s' of '%s' to be callable",
type,
this);
}
}