blob: fb88c9021dfd09bed3a02f85f5462f541c629184 [file] [log] [blame]
// Copyright 2019 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.ImmutableMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* A {@link Module} represents a Starlark module, a container of global variables populated by
* executing a Starlark file. Each top-level assignment updates a global variable in the module.
*
* <p>Each module references its "predeclared" environment, which is often shared among many
* modules. These are the names that are defined even at the start of execution. For example, in
* Bazel, the predeclared environment of the module for a BUILD or .bzl file defines name values
* such as cc_binary and glob.
*
* <p>The predeclared environment implicitly includes the "universal" names present in every
* Starlark thread in every dialect, such as None, len, and str; see {@link Starlark#UNIVERSE}.
*
* <p>Global bindings in a Module may shadow bindings inherited from the predeclared block.
*
* <p>A module may carry an arbitrary piece of client data. In Bazel, for example, the client data
* records the module's build label (such as "//dir:file.bzl").
*
* <p>Use {@link #create} to create a {@link Module} with no predeclared bindings other than the
* universal ones. Use {@link #withPredeclared(StarlarkSemantics, Map)} to create a module with the
* predeclared environment specified by the map, using the semantics to determine whether any
* FlagGuardedValues in the map are enabled or disabled.
*/
public final class Module implements Resolver.Module {
// The module's predeclared environment. Excludes UNIVERSE bindings.
private ImmutableMap<String, Object> predeclared;
// The module's global bindings, in order of creation.
private final LinkedHashMap<String, Object> globals = new LinkedHashMap<>();
// Names of globals that are exported and can be loaded from other modules.
// TODO(adonovan): eliminate this field when the resolver does its job properly.
final HashSet<String> exportedGlobals = new HashSet<>();
// An optional piece of metadata associated with the module/file.
// May be set after construction (too obscure to burden the constructors).
// Its toString appears to Starlark in str(function): "<function f from ...>".
@Nullable private Object clientData;
private Module(ImmutableMap<String, Object> predeclared) {
this.predeclared = predeclared;
}
/**
* Constructs a Module with the specified predeclared bindings, filtered by the semantics, in
* addition to the standard environment, {@link Starlark#UNIVERSE}.
*/
public static Module withPredeclared(
StarlarkSemantics semantics, Map<String, Object> predeclared) {
return new Module(filter(predeclared, semantics));
}
/**
* Creates a module with no predeclared bindings other than the standard environment, {@link
* Starlark#UNIVERSE}.
*/
public static Module create() {
return new Module(/*predeclared=*/ ImmutableMap.of());
}
/**
* Returns the module (file) of the innermost enclosing Starlark function on the call stack, or
* null if none of the active calls are functions defined in Starlark.
*
* <p>The name of this function is intentionally horrible to make you feel bad for using it.
*/
@Nullable
public static Module ofInnermostEnclosingStarlarkFunction(StarlarkThread thread) {
for (Debug.Frame fr : thread.getDebugCallStack().reverse()) {
if (fr.getFunction() instanceof StarlarkFunction) {
return ((StarlarkFunction) fr.getFunction()).getModule();
}
}
return null;
}
/**
* Returns a map in which each semantics-enabled FlagGuardedValue has been replaced by the value
* it guards. Disabled FlagGuardedValues are left in place, and should be treated as unavailable.
* The iteration order is unchanged.
*/
private static ImmutableMap<String, Object> filter(
Map<String, Object> predeclared, StarlarkSemantics semantics) {
ImmutableMap.Builder<String, Object> filtered = ImmutableMap.builder();
for (Map.Entry<String, Object> bind : predeclared.entrySet()) {
Object v = bind.getValue();
if (v instanceof FlagGuardedValue) {
FlagGuardedValue fv = (FlagGuardedValue) bind.getValue();
if (fv.isObjectAccessibleUsingSemantics(semantics)) {
v = fv.getObject();
}
}
filtered.put(bind.getKey(), v);
}
return filtered.build();
}
/**
* Sets the client data (an arbitrary application-specific value) associated with the module. It
* may be retrieved using {@link #getClientData}. Its {@code toString} form appears in the result
* of {@code str(fn)} where {@code fn} is a StarlarkFunction: "<function f from ...>".
*/
public void setClientData(@Nullable Object clientData) {
this.clientData = clientData;
}
/**
* Returns the client data associated with this module by a prior call to {@link #setClientData}.
*/
@Nullable
public Object getClientData() {
return clientData;
}
/** Returns the value of a predeclared (or universal) binding in this module. */
Object getPredeclared(String name) {
Object v = predeclared.get(name);
if (v != null) {
return v;
}
return Starlark.UNIVERSE.get(name);
}
/**
* Returns this module's additional predeclared bindings. (Excludes {@link Starlark#UNIVERSE}.)
*
* <p>The map reflects any semantics-based filtering of FlagGuardedValues done by {@link
* #withPredeclared}: enabled FlagGuardedValues are replaced by their underlying value.
*/
public ImmutableMap<String, Object> getPredeclaredBindings() {
return predeclared;
}
/**
* Returns a read-only view of this module's global bindings.
*
* <p>The bindings are returned in a deterministic order (for a given sequence of initial values
* and updates).
*/
public Map<String, Object> getGlobals() {
return Collections.unmodifiableMap(globals);
}
/**
* Returns a map of bindings that are exported (i.e. symbols declared using `=` and `def`, but not
* `load`).
*/
// TODO(adonovan): whether bindings are exported should be decided by the resolver;
// non-exported bindings should never be added to the module. Delete this.
public ImmutableMap<String, Object> getExportedGlobals() {
ImmutableMap.Builder<String, Object> result = new ImmutableMap.Builder<>();
for (Map.Entry<String, Object> entry : globals.entrySet()) {
if (exportedGlobals.contains(entry.getKey())) {
result.put(entry);
}
}
return result.build();
}
/** Implements the resolver's module interface. */
@Override
public Set<String> getNames() {
// TODO(adonovan): for now, the resolver treats all predeclared/universe
// and global names as one bucket (Scope.PREDECLARED). Fix that.
// TODO(adonovan): opt: change the resolver to request names on
// demand to avoid all this set copying.
HashSet<String> names = new HashSet<>();
for (Map.Entry<String, Object> bind : getTransitiveBindings().entrySet()) {
if (bind.getValue() instanceof FlagGuardedValue) {
continue; // disabled
}
names.add(bind.getKey());
}
return names;
}
@Override
public String getUndeclaredNameError(String name) {
Object v = getPredeclared(name);
return v instanceof FlagGuardedValue
? ((FlagGuardedValue) v).getErrorFromAttemptingAccess(name)
: null;
}
/**
* Returns a new map containing the predeclared (including universal) and global bindings of this
* module.
*/
// TODO(adonovan): eliminate; clients should explicitly choose getPredeclared or getGlobals.
public Map<String, Object> getTransitiveBindings() {
// Can't use ImmutableMap.Builder because it doesn't allow duplicates.
LinkedHashMap<String, Object> env = new LinkedHashMap<>();
env.putAll(Starlark.UNIVERSE);
env.putAll(predeclared);
env.putAll(globals);
return env;
}
/**
* Returns the value of the specified global variable, or null if not bound. Does not look in the
* predeclared environment.
*/
public Object getGlobal(String name) {
return globals.get(name);
}
/**
* Returns the value of the named variable in the module global environment (as if by {@link
* #getGlobal}), or if not bound there, in the predeclared environment (as if by {@link
* #getPredeclared}, or if not bound there, null.
*/
public Object get(String name) {
// TODO(adonovan): delete this whole function, and getTransitiveBindings.
// With proper resolution, the interpreter will know whether
// to look in the module or the predeclared/universal environment.
Object v = getGlobal(name);
if (v != null) {
return v;
}
return getPredeclared(name);
}
/** Updates a global binding in the module environment. */
public void setGlobal(String name, Object value) {
Preconditions.checkNotNull(value, "Module.setGlobal(%s, null)", name);
globals.put(name, value);
}
@Override
public String toString() {
return String.format("<module %s>", clientData == null ? "?" : clientData);
}
}