| // 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 net.starlark.java.eval; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | |
| import java.util.Map; | |
| import java.util.TreeMap; | |
| import net.starlark.java.syntax.FileOptions; | |
| /** | |
| * A StarlarkSemantics is an immutable set of optional name/value pairs that affect the dynamic | |
| * behavior of Starlark operators and built-in functions, both core and application-defined. | |
| * | |
| * <p>For extensibility, a StarlarkSemantics only records a name/value pair when the value differs | |
| * from the default value appropriate to that name. Values of most types are accessed using a {@link | |
| * Key}, which defines the name, type, and default value for an entry. Boolean values are accessed | |
| * using a string key; the string must have the prefix "+" or "-", indicating the default value: + | |
| * for true, - for false. The reason for the special treatment of boolean entries is that they may | |
| * enable or disable methods and parameters in the StarlarkMethod annotation system, and it is not | |
| * possible to refer to a Key from a Java annotation, only a string. | |
| * | |
| * <p>It is the client's responsibility to ensure that a StarlarkSemantics does not encounter | |
| * multiple Keys of the same name but different value types. | |
| * | |
| * <p>For Bazel's semantics options, see {@link packages.semantics.BuildLanguageOptions}. | |
| * | |
| * <p>For options that affect the static behavior of the Starlark frontend (lexer, parser, | |
| * validator, compiler), see {@link FileOptions}. | |
| */ | |
| public class StarlarkSemantics { | |
| /** | |
| * Returns the empty semantics, in which every option has its default value. | |
| * | |
| * <p><i>Usage note:</i> Usually all Starlark evaluation contexts (i.e., {@link StarlarkThread}s | |
| * or other interpreter APIs that accept a {@code StarlarkSemantics}) within the same application | |
| * should use the same semantics. Otherwise, different pieces of code -- or even the same code | |
| * when executed in different capacities -- could produce diverging results. It is therefore | |
| * generally a code smell to use the {@code DEFAULT} semantics rather than propagating a {@code | |
| * StarlarkSemantics} from another context. | |
| */ | |
| public static final StarlarkSemantics DEFAULT = new StarlarkSemantics(ImmutableMap.of()); | |
| // A map entry must be accessed by Key iff its name has no [+-] prefix. | |
| // Key<Boolean> is permitted too. | |
| // The map keys are sorted but we avoid ImmutableSortedMap due to observed inefficiency. | |
| private final ImmutableMap<String, Object> map; | |
| private final int hashCode; | |
| private StarlarkSemantics(ImmutableMap<String, Object> map) { | |
| this.map = map; | |
| this.hashCode = map.hashCode(); | |
| } | |
| protected StarlarkSemantics(StarlarkSemantics otherSemantics) { | |
| this(otherSemantics.map); | |
| } | |
| /** Returns the value of a boolean option, which must have a [+-] prefix. */ | |
| public final boolean getBool(String name) { | |
| char prefix = name.charAt(0); | |
| Preconditions.checkArgument(prefix == '+' || prefix == '-'); | |
| boolean defaultValue = prefix == '+'; | |
| Boolean v = (Boolean) map.get(name); // prefix => cast cannot fail | |
| return v != null ? v : defaultValue; | |
| } | |
| /** Returns the value of the option denoted by {@code key}. */ | |
| public final <T> T get(Key<T> key) { | |
| @SuppressWarnings("unchecked") // safe, if Key.names are unique | |
| T v = (T) map.get(key.name); | |
| return v != null ? v : key.defaultValue; | |
| } | |
| // TODO(bazel-team): This exists solely for BuiltinsInternalModule#getFlag, which allows a | |
| // (privileged) Starlark caller to programmatically retrieve a flag's value without knowing its | |
| // schema and default value. Reconsider whether we should support that use case from this class. | |
| /** | |
| * Returns the value of the option with the given name, or the default value if it is not set or | |
| * does not exist. | |
| */ | |
| public final Object getGeneric(String name, Object defaultValue) { | |
| Object v = map.get(name); | |
| // Try boolean prefixes if that didn't work. | |
| if (v == null) { | |
| v = map.get("+" + name); | |
| } | |
| if (v == null) { | |
| v = map.get("-" + name); | |
| } | |
| return v != null ? v : defaultValue; | |
| } | |
| /** A Key identifies an option, providing its name, type, and default value. */ | |
| public static final class Key<T> { | |
| public final String name; | |
| public final T defaultValue; | |
| /** | |
| * Constructs a key. The name must not start with [+-]. The value must not be subsequently | |
| * modified. | |
| */ | |
| public Key(String name, T defaultValue) { | |
| char prefix = name.charAt(0); | |
| Preconditions.checkArgument(prefix != '-' && prefix != '+'); | |
| this.name = name; | |
| this.defaultValue = Preconditions.checkNotNull(defaultValue); | |
| } | |
| @Override | |
| public String toString() { | |
| return this.name; | |
| } | |
| } | |
| /** | |
| * Returns a new builder that initially holds the same key/value pairs as this StarlarkSemantics. | |
| */ | |
| public final Builder toBuilder() { | |
| return new Builder(new TreeMap<>(map)); | |
| } | |
| /** Returns a new empty builder. */ | |
| public static Builder builder() { | |
| return new Builder(new TreeMap<>()); | |
| } | |
| /** A Builder is a mutable container used to construct an immutable StarlarkSemantics. */ | |
| public static final class Builder { | |
| private final TreeMap<String, Object> map; | |
| private Builder(TreeMap<String, Object> map) { | |
| this.map = map; | |
| } | |
| /** Sets the value for the specified key. */ | |
| @CanIgnoreReturnValue | |
| public <T> Builder set(Key<T> key, T value) { | |
| if (!value.equals(key.defaultValue)) { | |
| map.put(key.name, value); | |
| } else { | |
| map.remove(key.name); | |
| } | |
| return this; | |
| } | |
| /** Sets the value for the boolean key, which must have a [+-] prefix. */ | |
| @CanIgnoreReturnValue | |
| public Builder setBool(String name, boolean value) { | |
| char prefix = name.charAt(0); | |
| Preconditions.checkArgument(prefix == '+' || prefix == '-'); | |
| boolean defaultValue = prefix == '+'; | |
| if (value != defaultValue) { | |
| map.put(name, value); | |
| } else { | |
| map.remove(name); | |
| } | |
| return this; | |
| } | |
| /** Returns an immutable StarlarkSemantics. */ | |
| public StarlarkSemantics build() { | |
| if (map.isEmpty()) { | |
| return DEFAULT; | |
| } | |
| return new StarlarkSemantics(ImmutableMap.copyOf(map)); | |
| } | |
| } | |
| /** | |
| * Returns true if a feature attached to the given toggling flags should be enabled. | |
| * | |
| * <ul> | |
| * <li>If both parameters are empty, this indicates the feature is not controlled by flags, and | |
| * should thus be enabled. | |
| * <li>If the {@code enablingFlag} parameter is non-empty, this returns true if and only if that | |
| * flag is true. (This represents a feature that is only on if a given flag is *on*). | |
| * <li>If the {@code disablingFlag} parameter is non-empty, this returns true if and only if | |
| * that flag is false. (This represents a feature that is only on if a given flag is *off*). | |
| * <li>It is illegal to pass both parameters as non-empty. | |
| * </ul> | |
| */ | |
| final boolean isFeatureEnabledBasedOnTogglingFlags(String enablingFlag, String disablingFlag) { | |
| Preconditions.checkArgument( | |
| enablingFlag.isEmpty() || disablingFlag.isEmpty(), | |
| "at least one of 'enablingFlag' or 'disablingFlag' must be empty"); | |
| if (!enablingFlag.isEmpty()) { | |
| return this.getBool(enablingFlag); | |
| } else if (!disablingFlag.isEmpty()) { | |
| return !this.getBool(disablingFlag); | |
| } else { | |
| return true; | |
| } | |
| } | |
| /** | |
| * Returns a possibly different {@link StarlarkSemantics} instance that is equivalent to this one | |
| * for the purpose of caching the methods available on any given Starlark class. | |
| */ | |
| public StarlarkSemantics getBuiltinManagerCacheKey() { | |
| return this; | |
| } | |
| @Override | |
| public final int hashCode() { | |
| return hashCode; | |
| } | |
| @Override | |
| public final boolean equals(Object that) { | |
| return this == that | |
| || (that instanceof StarlarkSemantics && this.map.equals(((StarlarkSemantics) that).map)); | |
| } | |
| /** | |
| * Returns a representation of this StarlarkSemantics' non-default key/value pairs in key order. | |
| */ | |
| @Override | |
| public final String toString() { | |
| // Print "StarlarkSemantics{k=v, ...}", without +/- prefixes. | |
| StringBuilder buf = new StringBuilder(); | |
| buf.append("StarlarkSemantics{"); | |
| String sep = ""; | |
| for (Map.Entry<String, Object> e : map.entrySet()) { | |
| String key = e.getKey(); | |
| buf.append(sep); | |
| sep = ", "; | |
| if (key.charAt(0) == '+' || key.charAt(0) == '-') { | |
| buf.append(key, 1, key.length()); | |
| } else { | |
| buf.append(key); | |
| } | |
| buf.append('=').append(e.getValue()); | |
| } | |
| return buf.append('}').toString(); | |
| } | |
| /** Makes a {@link FileOptions.Builder} consistent with this {@link StarlarkSemantics}. */ | |
| @CanIgnoreReturnValue | |
| public FileOptions.Builder updateFileOptions(FileOptions.Builder builder) { | |
| boolean needsTypeInfo = | |
| getBool(EXPERIMENTAL_STARLARK_STATIC_TYPE_CHECKING) | |
| || getBool(EXPERIMENTAL_STARLARK_DYNAMIC_TYPE_CHECKING); | |
| builder | |
| .allowTypeSyntax(getBool(EXPERIMENTAL_STARLARK_TYPE_SYNTAX)) | |
| .resolveTypeSyntax(needsTypeInfo); | |
| if (needsTypeInfo) { | |
| builder.tolerateInvalidTypeExpressions(false); | |
| } | |
| return builder; | |
| } | |
| /** Returns the default {@link FileOptions} consistent with this {@link StarlarkSemantics}. */ | |
| public FileOptions getDefaultFileOptions() { | |
| return updateFileOptions(FileOptions.builder()).build(); | |
| } | |
| // -- semantics options affecting the Starlark interpreter itself -- | |
| /** Change the behavior of 'print' statements. Used in tests to verify flag propagation. */ | |
| public static final String PRINT_TEST_MARKER = "-print_test_marker"; | |
| /** | |
| * Whether recursive function calls are allowed. This option is not exposed to Bazel, which | |
| * unconditionally prohibits recursion. | |
| */ | |
| public static final String ALLOW_RECURSION = "-allow_recursion"; | |
| /** Whether StarlarkSet objects may be constructed by the interpreter. */ | |
| public static final String EXPERIMENTAL_ENABLE_STARLARK_SET = "+experimental_enable_starlark_set"; | |
| /** Whether the Starlark interpreter uses UTF-8 byte strings instead of UTF-16 strings. */ | |
| public static final String INTERNAL_BAZEL_ONLY_UTF_8_BYTE_STRINGS = | |
| "-internal_bazel_only_utf_8_byte_strings"; | |
| /** | |
| * Whether to allow type annotation syntax in Starlark. | |
| * | |
| * <p>Type annotation syntax is not really relevant to evaluation per se, but we store it here in | |
| * order to thread it through to {@link FileOptions}. | |
| */ | |
| public static final String EXPERIMENTAL_STARLARK_TYPE_SYNTAX = | |
| // LINT.IfChange | |
| "+experimental_starlark_type_syntax"; | |
| // LINT.ThenChange(//src/main/java/net/starlark/java/syntax/FileOptions.java) | |
| /** | |
| * Whether static type checking should be performed. | |
| * | |
| * <p>Static type checking is not really relevant to evaluation per se, but we store it here in | |
| * order to thread it through to {@link FileOptions}. | |
| */ | |
| public static final String EXPERIMENTAL_STARLARK_STATIC_TYPE_CHECKING = | |
| // LINT.IfChange | |
| "+experimental_starlark_static_type_checking"; | |
| // LINT.ThenChange(//src/main/java/net/starlark/java/syntax/FileOptions.java) | |
| /** Whether dynamic type checking should be performed. */ | |
| public static final String EXPERIMENTAL_STARLARK_DYNAMIC_TYPE_CHECKING = | |
| // LINT.IfChange | |
| "-experimental_starlark_dynamic_type_checking"; | |
| // LINT.ThenChange(//src/main/java/net/starlark/java/syntax/FileOptions.java) | |
| /** Globally Override fail(stack_trace=) to true. Flag default is false. */ | |
| public static final String FORCE_STARLARK_STACK_TRACE = "-force_starlark_stack_trace"; | |
| } |