Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 1 | // 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 | |
| 15 | package com.google.devtools.build.lib.syntax; |
| 16 | |
| 17 | import com.google.common.base.Preconditions; |
| 18 | import com.google.common.collect.ImmutableMap; |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 19 | import java.util.Collections; |
janakr | 4bbda0b | 2020-05-12 09:17:24 -0700 | [diff] [blame] | 20 | import java.util.HashSet; |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 21 | import java.util.LinkedHashMap; |
| 22 | import java.util.Map; |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 23 | import java.util.Set; |
| 24 | import 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 | * |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 35 | * <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}. |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 37 | * |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 38 | * <p>Global bindings in a Module may shadow bindings inherited from the predeclared block. |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 39 | * |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 40 | * <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"). |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 42 | * |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 43 | * <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. |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 47 | */ |
adonovan | c743435 | 2020-04-24 09:05:50 -0700 | [diff] [blame] | 48 | public final class Module implements Resolver.Module { |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 49 | |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 50 | // The module's predeclared environment. Excludes UNIVERSE bindings. |
| 51 | private ImmutableMap<String, Object> predeclared; |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 52 | |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 53 | // The module's global bindings, in order of creation. |
| 54 | private final LinkedHashMap<String, Object> globals = new LinkedHashMap<>(); |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 55 | |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 56 | // 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 ...>". |
ajurkowski | 9f2cab5 | 2020-05-12 12:00:24 -0700 | [diff] [blame] | 63 | @Nullable private Object clientData; |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 64 | |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 65 | private Module(ImmutableMap<String, Object> predeclared) { |
| 66 | this.predeclared = predeclared; |
| 67 | } |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 68 | |
| 69 | /** |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 70 | * Constructs a Module with the specified predeclared bindings, filtered by the semantics, in |
| 71 | * addition to the standard environment, {@link Starlark#UNIVERSE}. |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 72 | */ |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 73 | public static Module withPredeclared( |
| 74 | StarlarkSemantics semantics, Map<String, Object> predeclared) { |
| 75 | return new Module(filter(predeclared, semantics)); |
| 76 | } |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 77 | |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 78 | /** |
| 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 | } |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 85 | |
adonovan | df05243 | 2020-01-28 13:22:46 -0800 | [diff] [blame] | 86 | /** |
| 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 | |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 102 | /** |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 103 | * 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. |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 106 | */ |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 107 | 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(); |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 116 | } |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 117 | } |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 118 | filtered.put(bind.getKey(), v); |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 119 | } |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 120 | return filtered.build(); |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 121 | } |
| 122 | |
| 123 | /** |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 124 | * 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 ...>". |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 127 | */ |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 128 | public void setClientData(@Nullable Object clientData) { |
| 129 | this.clientData = clientData; |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 130 | } |
| 131 | |
| 132 | /** |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 133 | * Returns the client data associated with this module by a prior call to {@link #setClientData}. |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 134 | */ |
| 135 | @Nullable |
ajurkowski | 9f2cab5 | 2020-05-12 12:00:24 -0700 | [diff] [blame] | 136 | public Object getClientData() { |
ajurkowski | 9f2cab5 | 2020-05-12 12:00:24 -0700 | [diff] [blame] | 137 | return clientData; |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 138 | } |
| 139 | |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 140 | /** 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 | |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 149 | /** |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 150 | * 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. |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 161 | * |
| 162 | * <p>The bindings are returned in a deterministic order (for a given sequence of initial values |
| 163 | * and updates). |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 164 | */ |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 165 | public Map<String, Object> getGlobals() { |
| 166 | return Collections.unmodifiableMap(globals); |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 167 | } |
| 168 | |
| 169 | /** |
| 170 | * Returns a map of bindings that are exported (i.e. symbols declared using `=` and `def`, but not |
| 171 | * `load`). |
| 172 | */ |
adonovan | c855244 | 2020-01-14 09:34:40 -0800 | [diff] [blame] | 173 | // TODO(adonovan): whether bindings are exported should be decided by the resolver; |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 174 | // non-exported bindings should never be added to the module. Delete this. |
| 175 | public ImmutableMap<String, Object> getExportedGlobals() { |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 176 | ImmutableMap.Builder<String, Object> result = new ImmutableMap.Builder<>(); |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 177 | for (Map.Entry<String, Object> entry : globals.entrySet()) { |
| 178 | if (exportedGlobals.contains(entry.getKey())) { |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 179 | result.put(entry); |
| 180 | } |
| 181 | } |
| 182 | return result.build(); |
| 183 | } |
| 184 | |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 185 | /** Implements the resolver's module interface. */ |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 186 | @Override |
| 187 | public Set<String> getNames() { |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 188 | // 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; |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 200 | } |
| 201 | |
| 202 | @Override |
adonovan | 034220a | 2020-03-24 10:11:26 -0700 | [diff] [blame] | 203 | public String getUndeclaredNameError(String name) { |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 204 | Object v = getPredeclared(name); |
| 205 | return v instanceof FlagGuardedValue |
| 206 | ? ((FlagGuardedValue) v).getErrorFromAttemptingAccess(name) |
| 207 | : null; |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 208 | } |
| 209 | |
adonovan | f886f65 | 2020-01-06 18:54:22 -0800 | [diff] [blame] | 210 | /** |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 211 | * 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 |
adonovan | c855244 | 2020-01-14 09:34:40 -0800 | [diff] [blame] | 226 | * predeclared environment. |
adonovan | f886f65 | 2020-01-06 18:54:22 -0800 | [diff] [blame] | 227 | */ |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 228 | public Object getGlobal(String name) { |
| 229 | return globals.get(name); |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 230 | } |
| 231 | |
adonovan | c855244 | 2020-01-14 09:34:40 -0800 | [diff] [blame] | 232 | /** |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 233 | * 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. |
adonovan | c855244 | 2020-01-14 09:34:40 -0800 | [diff] [blame] | 236 | */ |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 237 | public Object get(String name) { |
adonovan | f886f65 | 2020-01-06 18:54:22 -0800 | [diff] [blame] | 238 | // 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. |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 241 | Object v = getGlobal(name); |
| 242 | if (v != null) { |
| 243 | return v; |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 244 | } |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 245 | return getPredeclared(name); |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 246 | } |
| 247 | |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 248 | /** 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); |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 252 | } |
| 253 | |
| 254 | @Override |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 255 | public String toString() { |
adonovan | 09d1370 | 2020-05-19 08:26:55 -0700 | [diff] [blame] | 256 | return String.format("<module %s>", clientData == null ? "?" : clientData); |
| 257 | } |
Googler | 41c333f | 2019-10-15 12:12:24 -0700 | [diff] [blame] | 258 | } |