blob: 8419e7fc64f14ad2bdedb0a1e757579a34161bd0 [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 com.google.devtools.build.lib.syntax;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.profiler.ProfilerTask;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.syntax.Environment.LexicalFrame;
import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
/**
* A function-object abstraction on object methods exposed to skylark using {@link SkylarkCallable}.
*/
public class BuiltinCallable extends BaseFunction {
/** Represents a required interpreter parameter as dictated by {@link SkylarkCallable} */
public enum ExtraArgKind {
LOCATION, // SkylarkCallable.useLocation
SYNTAX_TREE, // SkylarkCallable.useAst
ENVIRONMENT, // SkylarkCallable.useEnvironment
SEMANTICS; // SkylarkCallable.useSemantics
}
// Builtins cannot create or modify variable bindings. So it's sufficient to use a shared
// instance.
private static final LexicalFrame SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS =
LexicalFrame.create(Mutability.IMMUTABLE);
private final Object obj;
private final MethodDescriptor descriptor;
private final List<ExtraArgKind> extraArgs;
private int innerArgumentCount;
public BuiltinCallable(String name, Object obj, MethodDescriptor descriptor) {
super(name);
this.obj = obj;
this.descriptor = descriptor;
this.extraArgs = getExtraArgs(descriptor.getAnnotation());
configure(obj, descriptor);
}
@Override
protected int getArgArraySize () {
return innerArgumentCount;
}
private static List<ExtraArgKind> getExtraArgs(SkylarkCallable annotation) {
ImmutableList.Builder<ExtraArgKind> extraArgs = ImmutableList.builder();
if (annotation.useLocation()) {
extraArgs.add(ExtraArgKind.LOCATION);
}
if (annotation.useAst()) {
extraArgs.add(ExtraArgKind.SYNTAX_TREE);
}
if (annotation.useEnvironment()) {
extraArgs.add(ExtraArgKind.ENVIRONMENT);
}
if (annotation.useSkylarkSemantics()) {
extraArgs.add(ExtraArgKind.SEMANTICS);
}
return extraArgs.build();
}
/** Configure a BaseFunction from a @SkylarkCallable-annotated method */
private void configure(Object obj, MethodDescriptor descriptor) {
Preconditions.checkState(!isConfigured()); // must not be configured yet
this.paramDoc = new ArrayList<>();
this.signature = SkylarkSignatureProcessor.getSignatureForCallable(
getName(), descriptor, paramDoc, getEnforcedArgumentTypes());
this.objectType = obj.getClass();
this.innerArgumentCount = signature.getSignature().getShape().getArguments() + extraArgs.size();
configure();
}
@Override
@Nullable
public Object call(Object[] args,
FuncallExpression ast, Environment env)
throws EvalException, InterruptedException {
Preconditions.checkNotNull(env);
// ast is null when called from Java (as there's no Skylark call site).
Location loc = ast == null ? Location.BUILTIN : ast.getLocation();
// Add extra arguments, if needed
int index = args.length - extraArgs.size();
for (ExtraArgKind extraArg : extraArgs) {
switch(extraArg) {
case LOCATION:
args[index] = loc;
break;
case SYNTAX_TREE:
args[index] = ast;
break;
case ENVIRONMENT:
args[index] = env;
break;
case SEMANTICS:
args[index] = env.getSemantics();
break;
}
index++;
}
Profiler.instance().startTask(ProfilerTask.SKYLARK_BUILTIN_FN, getName());
try {
env.enterScope(this, SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS, ast, env.getGlobals());
return FuncallExpression.callMethod(
descriptor, getName(), obj, args, ast.getLocation(), env);
} finally {
Profiler.instance().completeTask(ProfilerTask.SKYLARK_BUILTIN_FN);
env.exitScope();
}
}
}