blob: fb88c9021dfd09bed3a02f85f5462f541c629184 [file] [log] [blame]
Googler41c333f2019-10-15 12:12:24 -07001// Copyright 2019 The Bazel Authors. All rights reserved.
2//
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.
14
15package com.google.devtools.build.lib.syntax;
16
17import com.google.common.base.Preconditions;
18import com.google.common.collect.ImmutableMap;
Googler41c333f2019-10-15 12:12:24 -070019import java.util.Collections;
janakr4bbda0b2020-05-12 09:17:24 -070020import java.util.HashSet;
Googler41c333f2019-10-15 12:12:24 -070021import java.util.LinkedHashMap;
22import java.util.Map;
Googler41c333f2019-10-15 12:12:24 -070023import java.util.Set;
24import javax.annotation.Nullable;
25
26/**
27 * A {@link Module} represents a Starlark module, a container of global variables populated by
28 * executing a Starlark file. Each top-level assignment updates a global variable in the module.
29 *
30 * <p>Each module references its "predeclared" environment, which is often shared among many
31 * modules. These are the names that are defined even at the start of execution. For example, in
32 * Bazel, the predeclared environment of the module for a BUILD or .bzl file defines name values
33 * such as cc_binary and glob.
34 *
adonovan09d13702020-05-19 08:26:55 -070035 * <p>The predeclared environment implicitly includes the "universal" names present in every
36 * Starlark thread in every dialect, such as None, len, and str; see {@link Starlark#UNIVERSE}.
Googler41c333f2019-10-15 12:12:24 -070037 *
adonovan09d13702020-05-19 08:26:55 -070038 * <p>Global bindings in a Module may shadow bindings inherited from the predeclared block.
Googler41c333f2019-10-15 12:12:24 -070039 *
adonovan09d13702020-05-19 08:26:55 -070040 * <p>A module may carry an arbitrary piece of client data. In Bazel, for example, the client data
41 * records the module's build label (such as "//dir:file.bzl").
Googler41c333f2019-10-15 12:12:24 -070042 *
adonovan09d13702020-05-19 08:26:55 -070043 * <p>Use {@link #create} to create a {@link Module} with no predeclared bindings other than the
44 * universal ones. Use {@link #withPredeclared(StarlarkSemantics, Map)} to create a module with the
45 * predeclared environment specified by the map, using the semantics to determine whether any
46 * FlagGuardedValues in the map are enabled or disabled.
Googler41c333f2019-10-15 12:12:24 -070047 */
adonovanc7434352020-04-24 09:05:50 -070048public final class Module implements Resolver.Module {
Googler41c333f2019-10-15 12:12:24 -070049
adonovan09d13702020-05-19 08:26:55 -070050 // The module's predeclared environment. Excludes UNIVERSE bindings.
51 private ImmutableMap<String, Object> predeclared;
Googler41c333f2019-10-15 12:12:24 -070052
adonovan09d13702020-05-19 08:26:55 -070053 // The module's global bindings, in order of creation.
54 private final LinkedHashMap<String, Object> globals = new LinkedHashMap<>();
Googler41c333f2019-10-15 12:12:24 -070055
adonovan09d13702020-05-19 08:26:55 -070056 // Names of globals that are exported and can be loaded from other modules.
57 // TODO(adonovan): eliminate this field when the resolver does its job properly.
58 final HashSet<String> exportedGlobals = new HashSet<>();
59
60 // An optional piece of metadata associated with the module/file.
61 // May be set after construction (too obscure to burden the constructors).
62 // Its toString appears to Starlark in str(function): "<function f from ...>".
ajurkowski9f2cab52020-05-12 12:00:24 -070063 @Nullable private Object clientData;
Googler41c333f2019-10-15 12:12:24 -070064
adonovan09d13702020-05-19 08:26:55 -070065 private Module(ImmutableMap<String, Object> predeclared) {
66 this.predeclared = predeclared;
67 }
Googler41c333f2019-10-15 12:12:24 -070068
69 /**
adonovan09d13702020-05-19 08:26:55 -070070 * Constructs a Module with the specified predeclared bindings, filtered by the semantics, in
71 * addition to the standard environment, {@link Starlark#UNIVERSE}.
Googler41c333f2019-10-15 12:12:24 -070072 */
adonovan09d13702020-05-19 08:26:55 -070073 public static Module withPredeclared(
74 StarlarkSemantics semantics, Map<String, Object> predeclared) {
75 return new Module(filter(predeclared, semantics));
76 }
Googler41c333f2019-10-15 12:12:24 -070077
adonovan09d13702020-05-19 08:26:55 -070078 /**
79 * Creates a module with no predeclared bindings other than the standard environment, {@link
80 * Starlark#UNIVERSE}.
81 */
82 public static Module create() {
83 return new Module(/*predeclared=*/ ImmutableMap.of());
84 }
Googler41c333f2019-10-15 12:12:24 -070085
adonovandf052432020-01-28 13:22:46 -080086 /**
87 * Returns the module (file) of the innermost enclosing Starlark function on the call stack, or
88 * null if none of the active calls are functions defined in Starlark.
89 *
90 * <p>The name of this function is intentionally horrible to make you feel bad for using it.
91 */
92 @Nullable
93 public static Module ofInnermostEnclosingStarlarkFunction(StarlarkThread thread) {
94 for (Debug.Frame fr : thread.getDebugCallStack().reverse()) {
95 if (fr.getFunction() instanceof StarlarkFunction) {
96 return ((StarlarkFunction) fr.getFunction()).getModule();
97 }
98 }
99 return null;
100 }
101
Googler41c333f2019-10-15 12:12:24 -0700102 /**
adonovan09d13702020-05-19 08:26:55 -0700103 * Returns a map in which each semantics-enabled FlagGuardedValue has been replaced by the value
104 * it guards. Disabled FlagGuardedValues are left in place, and should be treated as unavailable.
105 * The iteration order is unchanged.
Googler41c333f2019-10-15 12:12:24 -0700106 */
adonovan09d13702020-05-19 08:26:55 -0700107 private static ImmutableMap<String, Object> filter(
108 Map<String, Object> predeclared, StarlarkSemantics semantics) {
109 ImmutableMap.Builder<String, Object> filtered = ImmutableMap.builder();
110 for (Map.Entry<String, Object> bind : predeclared.entrySet()) {
111 Object v = bind.getValue();
112 if (v instanceof FlagGuardedValue) {
113 FlagGuardedValue fv = (FlagGuardedValue) bind.getValue();
114 if (fv.isObjectAccessibleUsingSemantics(semantics)) {
115 v = fv.getObject();
Googler41c333f2019-10-15 12:12:24 -0700116 }
Googler41c333f2019-10-15 12:12:24 -0700117 }
adonovan09d13702020-05-19 08:26:55 -0700118 filtered.put(bind.getKey(), v);
Googler41c333f2019-10-15 12:12:24 -0700119 }
adonovan09d13702020-05-19 08:26:55 -0700120 return filtered.build();
Googler41c333f2019-10-15 12:12:24 -0700121 }
122
123 /**
adonovan09d13702020-05-19 08:26:55 -0700124 * Sets the client data (an arbitrary application-specific value) associated with the module. It
125 * may be retrieved using {@link #getClientData}. Its {@code toString} form appears in the result
126 * of {@code str(fn)} where {@code fn} is a StarlarkFunction: "<function f from ...>".
Googler41c333f2019-10-15 12:12:24 -0700127 */
adonovan09d13702020-05-19 08:26:55 -0700128 public void setClientData(@Nullable Object clientData) {
129 this.clientData = clientData;
Googler41c333f2019-10-15 12:12:24 -0700130 }
131
132 /**
adonovan09d13702020-05-19 08:26:55 -0700133 * Returns the client data associated with this module by a prior call to {@link #setClientData}.
Googler41c333f2019-10-15 12:12:24 -0700134 */
135 @Nullable
ajurkowski9f2cab52020-05-12 12:00:24 -0700136 public Object getClientData() {
ajurkowski9f2cab52020-05-12 12:00:24 -0700137 return clientData;
Googler41c333f2019-10-15 12:12:24 -0700138 }
139
adonovan09d13702020-05-19 08:26:55 -0700140 /** Returns the value of a predeclared (or universal) binding in this module. */
141 Object getPredeclared(String name) {
142 Object v = predeclared.get(name);
143 if (v != null) {
144 return v;
145 }
146 return Starlark.UNIVERSE.get(name);
147 }
148
Googler41c333f2019-10-15 12:12:24 -0700149 /**
adonovan09d13702020-05-19 08:26:55 -0700150 * Returns this module's additional predeclared bindings. (Excludes {@link Starlark#UNIVERSE}.)
151 *
152 * <p>The map reflects any semantics-based filtering of FlagGuardedValues done by {@link
153 * #withPredeclared}: enabled FlagGuardedValues are replaced by their underlying value.
154 */
155 public ImmutableMap<String, Object> getPredeclaredBindings() {
156 return predeclared;
157 }
158
159 /**
160 * Returns a read-only view of this module's global bindings.
Googler41c333f2019-10-15 12:12:24 -0700161 *
162 * <p>The bindings are returned in a deterministic order (for a given sequence of initial values
163 * and updates).
Googler41c333f2019-10-15 12:12:24 -0700164 */
adonovan09d13702020-05-19 08:26:55 -0700165 public Map<String, Object> getGlobals() {
166 return Collections.unmodifiableMap(globals);
Googler41c333f2019-10-15 12:12:24 -0700167 }
168
169 /**
170 * Returns a map of bindings that are exported (i.e. symbols declared using `=` and `def`, but not
171 * `load`).
172 */
adonovanc8552442020-01-14 09:34:40 -0800173 // TODO(adonovan): whether bindings are exported should be decided by the resolver;
adonovan09d13702020-05-19 08:26:55 -0700174 // non-exported bindings should never be added to the module. Delete this.
175 public ImmutableMap<String, Object> getExportedGlobals() {
Googler41c333f2019-10-15 12:12:24 -0700176 ImmutableMap.Builder<String, Object> result = new ImmutableMap.Builder<>();
adonovan09d13702020-05-19 08:26:55 -0700177 for (Map.Entry<String, Object> entry : globals.entrySet()) {
178 if (exportedGlobals.contains(entry.getKey())) {
Googler41c333f2019-10-15 12:12:24 -0700179 result.put(entry);
180 }
181 }
182 return result.build();
183 }
184
adonovan09d13702020-05-19 08:26:55 -0700185 /** Implements the resolver's module interface. */
Googler41c333f2019-10-15 12:12:24 -0700186 @Override
187 public Set<String> getNames() {
adonovan09d13702020-05-19 08:26:55 -0700188 // TODO(adonovan): for now, the resolver treats all predeclared/universe
189 // and global names as one bucket (Scope.PREDECLARED). Fix that.
190 // TODO(adonovan): opt: change the resolver to request names on
191 // demand to avoid all this set copying.
192 HashSet<String> names = new HashSet<>();
193 for (Map.Entry<String, Object> bind : getTransitiveBindings().entrySet()) {
194 if (bind.getValue() instanceof FlagGuardedValue) {
195 continue; // disabled
196 }
197 names.add(bind.getKey());
198 }
199 return names;
Googler41c333f2019-10-15 12:12:24 -0700200 }
201
202 @Override
adonovan034220a2020-03-24 10:11:26 -0700203 public String getUndeclaredNameError(String name) {
adonovan09d13702020-05-19 08:26:55 -0700204 Object v = getPredeclared(name);
205 return v instanceof FlagGuardedValue
206 ? ((FlagGuardedValue) v).getErrorFromAttemptingAccess(name)
207 : null;
Googler41c333f2019-10-15 12:12:24 -0700208 }
209
adonovanf886f652020-01-06 18:54:22 -0800210 /**
adonovan09d13702020-05-19 08:26:55 -0700211 * Returns a new map containing the predeclared (including universal) and global bindings of this
212 * module.
213 */
214 // TODO(adonovan): eliminate; clients should explicitly choose getPredeclared or getGlobals.
215 public Map<String, Object> getTransitiveBindings() {
216 // Can't use ImmutableMap.Builder because it doesn't allow duplicates.
217 LinkedHashMap<String, Object> env = new LinkedHashMap<>();
218 env.putAll(Starlark.UNIVERSE);
219 env.putAll(predeclared);
220 env.putAll(globals);
221 return env;
222 }
223
224 /**
225 * Returns the value of the specified global variable, or null if not bound. Does not look in the
adonovanc8552442020-01-14 09:34:40 -0800226 * predeclared environment.
adonovanf886f652020-01-06 18:54:22 -0800227 */
adonovan09d13702020-05-19 08:26:55 -0700228 public Object getGlobal(String name) {
229 return globals.get(name);
Googler41c333f2019-10-15 12:12:24 -0700230 }
231
adonovanc8552442020-01-14 09:34:40 -0800232 /**
adonovan09d13702020-05-19 08:26:55 -0700233 * Returns the value of the named variable in the module global environment (as if by {@link
234 * #getGlobal}), or if not bound there, in the predeclared environment (as if by {@link
235 * #getPredeclared}, or if not bound there, null.
adonovanc8552442020-01-14 09:34:40 -0800236 */
adonovan09d13702020-05-19 08:26:55 -0700237 public Object get(String name) {
adonovanf886f652020-01-06 18:54:22 -0800238 // TODO(adonovan): delete this whole function, and getTransitiveBindings.
239 // With proper resolution, the interpreter will know whether
240 // to look in the module or the predeclared/universal environment.
adonovan09d13702020-05-19 08:26:55 -0700241 Object v = getGlobal(name);
242 if (v != null) {
243 return v;
Googler41c333f2019-10-15 12:12:24 -0700244 }
adonovan09d13702020-05-19 08:26:55 -0700245 return getPredeclared(name);
Googler41c333f2019-10-15 12:12:24 -0700246 }
247
adonovan09d13702020-05-19 08:26:55 -0700248 /** Updates a global binding in the module environment. */
249 public void setGlobal(String name, Object value) {
250 Preconditions.checkNotNull(value, "Module.setGlobal(%s, null)", name);
251 globals.put(name, value);
Googler41c333f2019-10-15 12:12:24 -0700252 }
253
254 @Override
Googler41c333f2019-10-15 12:12:24 -0700255 public String toString() {
adonovan09d13702020-05-19 08:26:55 -0700256 return String.format("<module %s>", clientData == null ? "?" : clientData);
257 }
Googler41c333f2019-10-15 12:12:24 -0700258}