Allow shadowing of builtins in bzl files
This was previously allowed only with --incompatible_static_name_resolution (although it was a backward-compatible change). Removing the code path simplifies the code and makes easier to implement the static name resolution proposal.
The downside is that it will temporarily allow this corner-case:
```
x = len(...) # reference to the builtin
len = ... # shadow the builtin with a new global
```
RELNOTES: None.
PiperOrigin-RevId: 211987952
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
index a8158e3..c32ab06 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java
@@ -1112,7 +1112,14 @@
/** Returns the value of a variable defined in the Universe scope (builtins). */
public Object universeLookup(String varname) {
// TODO(laurentlb): look only at globalFrame.parent.
- return globalFrame.get(varname);
+ Object result = globalFrame.get(varname);
+
+ if (result == null) {
+ // TODO(laurentlb): Remove once PACKAGE_NAME and REPOSITOYRY_NAME are removed (they are the
+ // only two user-visible values that use the dynamicFrame).
+ return dynamicLookup(varname);
+ }
+ return result;
}
/** Returns the value of a variable defined with setupDynamic. */
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java b/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
index d212c67..84de492 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java
@@ -107,9 +107,12 @@
if (result == null) {
// Since Scope was set, we know that the variable is defined in the scope.
// However, the assignment was not yet executed.
- throw new EvalException(
- getLocation(),
- scope.getQualifier() + " variable '" + name + "' is referenced before assignment.");
+ EvalException e = getSpecialException();
+ throw e != null
+ ? e
+ : new EvalException(
+ getLocation(),
+ scope.getQualifier() + " variable '" + name + "' is referenced before assignment.");
}
return result;
@@ -125,11 +128,8 @@
return Kind.IDENTIFIER;
}
- EvalException createInvalidIdentifierException(Set<String> symbols) {
- if (name.equals("$error$")) {
- return new EvalException(getLocation(), "contains syntax error(s)", true);
- }
-
+ /** Exception to provide a better error message for using PACKAGE_NAME or REPOSITORY_NAME. */
+ private EvalException getSpecialException() {
if (name.equals("PACKAGE_NAME")) {
return new EvalException(
getLocation(),
@@ -148,6 +148,18 @@
+ " You can temporarily allow the old name "
+ "by using --incompatible_package_name_is_a_function=false");
}
+ return null;
+ }
+
+ EvalException createInvalidIdentifierException(Set<String> symbols) {
+ if (name.equals("$error$")) {
+ return new EvalException(getLocation(), "contains syntax error(s)", true);
+ }
+
+ EvalException e = getSpecialException();
+ if (e != null) {
+ return e;
+ }
String suggestion = SpellChecker.didYouMean(name, symbols);
return new EvalException(getLocation(), "name '" + name + "' is not defined" + suggestion);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java b/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
index ec7a900..8900e5f 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
@@ -26,17 +26,12 @@
/**
* A class for doing static checks on files, before evaluating them.
*
- * <p>The behavior is affected by semantics.incompatibleStaticNameResolution(). When it is set to
- * true, we implement the semantics discussed in
+ * <p>We implement the semantics discussed in
* https://github.com/bazelbuild/proposals/blob/master/docs/2018-06-18-name-resolution.md
*
* <p>When a variable is defined, it is visible in the entire block. For example, a global variable
* is visible in the entire file; a variable in a function is visible in the entire function block
* (even on the lines before its first assignment).
- *
- * <p>The legacy behavior is kept during the transition and will be removed in the future. In the
- * legacy behavior, there is no clear separation between the first pass (collect all definitions)
- * and the second pass (ensure the symbols can be resolved).
*/
public final class ValidationEnvironment extends SyntaxTreeVisitor {
@@ -61,7 +56,6 @@
private static class Block {
private final Set<String> variables = new HashSet<>();
- private final Set<String> readOnlyVariables = new HashSet<>();
private final Scope scope;
@Nullable private final Block parent;
@@ -102,18 +96,12 @@
block = new Block(Scope.Universe, null);
Set<String> builtinVariables = env.getVariableNames();
block.variables.addAll(builtinVariables);
- if (!semantics.incompatibleStaticNameResolution()) {
- block.readOnlyVariables.addAll(builtinVariables);
- }
}
/**
* First pass: add all definitions to the current block. This is done because symbols are
* sometimes used before their definition point (e.g. a functions are not necessarily declared in
* order).
- *
- * <p>The old behavior (when incompatibleStaticNameResolution is false) doesn't have this first
- * pass.
*/
private void collectDefinitions(Iterable<Statement> stmts) {
for (Statement stmt : stmts) {
@@ -166,40 +154,22 @@
}
@Override
- public void visit(LoadStatement node) {
- if (semantics.incompatibleStaticNameResolution()) {
- return;
- }
-
- for (Identifier symbol : node.getSymbols()) {
- declare(symbol.getName(), node.getLocation());
- }
- }
-
- @Override
public void visit(Identifier node) {
@Nullable Block b = blockThatDefines(node.getName());
if (b == null) {
throw new ValidationException(node.createInvalidIdentifierException(getAllSymbols()));
}
- if (semantics.incompatibleStaticNameResolution()) {
- // The scoping information is reliable only with the new behavior.
- node.setScope(b.scope);
- }
+ node.setScope(b.scope);
}
private void validateLValue(Location loc, Expression expr) {
- if (expr instanceof Identifier) {
- if (!semantics.incompatibleStaticNameResolution()) {
- declare(((Identifier) expr).getName(), loc);
- }
- } else if (expr instanceof IndexExpression) {
+ if (expr instanceof IndexExpression) {
visit(expr);
} else if (expr instanceof ListLiteral) {
for (Expression e : ((ListLiteral) expr).getElements()) {
validateLValue(loc, e);
}
- } else {
+ } else if (!(expr instanceof Identifier)) {
throw new ValidationException(loc, "cannot assign to '" + expr + "'");
}
}
@@ -244,11 +214,9 @@
@Override
public void visit(AbstractComprehension node) {
openBlock(Scope.Local);
- if (semantics.incompatibleStaticNameResolution()) {
- for (AbstractComprehension.Clause clause : node.getClauses()) {
- if (clause.getLValue() != null) {
- collectDefinitions(clause.getLValue());
- }
+ for (AbstractComprehension.Clause clause : node.getClauses()) {
+ if (clause.getLValue() != null) {
+ collectDefinitions(clause.getLValue());
}
}
super.visit(node);
@@ -268,9 +236,7 @@
declare(param.getName(), param.getLocation());
}
}
- if (semantics.incompatibleStaticNameResolution()) {
- collectDefinitions(node.getStatements());
- }
+ collectDefinitions(node.getStatements());
visitAll(node.getStatements());
closeBlock();
}
@@ -298,25 +264,13 @@
/** Declare a variable and add it to the environment. */
private void declare(String varname, Location location) {
- boolean readOnlyViolation = false;
- if (block.readOnlyVariables.contains(varname)) {
- readOnlyViolation = true;
- }
- if (block.scope == Scope.Module && block.parent.readOnlyVariables.contains(varname)) {
- // TODO(laurentlb): This behavior is buggy. Symbols in the module scope should shadow symbols
- // from the universe. https://github.com/bazelbuild/bazel/issues/5637
- readOnlyViolation = true;
- }
- if (readOnlyViolation) {
+ if (block.scope == Scope.Module && block.variables.contains(varname)) {
+ // Symbols defined in the module scope cannot be reassigned.
throw new ValidationException(
location,
String.format("Variable %s is read only", varname),
"https://bazel.build/versions/master/docs/skylark/errors/read-only-variable.html");
}
- if (block.scope == Scope.Module) {
- // Symbols defined in the module scope cannot be reassigned.
- block.readOnlyVariables.add(varname);
- }
block.variables.add(varname);
}
@@ -378,20 +332,9 @@
openBlock(Scope.Module);
- if (semantics.incompatibleStaticNameResolution()) {
- // Add each variable defined by statements, not including definitions that appear in
- // sub-scopes of the given statements (function bodies and comprehensions).
- collectDefinitions(statements);
- } else {
- // Legacy behavior, to be removed. Add only the functions in the environment before
- // validating.
- for (Statement statement : statements) {
- if (statement instanceof FunctionDefStatement) {
- FunctionDefStatement fct = (FunctionDefStatement) statement;
- declare(fct.getIdentifier().getName(), fct.getLocation());
- }
- }
- }
+ // Add each variable defined by statements, not including definitions that appear in
+ // sub-scopes of the given statements (function bodies and comprehensions).
+ collectDefinitions(statements);
// Second pass: ensure that all symbols have been defined.
visitAll(statements);
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java
index 1b53776..bbad014 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java
@@ -217,7 +217,7 @@
} catch (EvalExceptionWithStackTrace e) {
assertThat(e)
.hasMessageThat()
- .contains("Variable 'global_var' is referenced before assignment.");
+ .contains("local variable 'global_var' is referenced before assignment.");
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
index 1ee0dff..c2aa865 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
@@ -357,8 +357,15 @@
@Test
public void testListComprehensionDefinitionOrder() throws Exception {
- newTest().testIfErrorContains("name 'y' is not defined",
- "[x for x in (1, 2) if y for y in (3, 4)]");
+ new BuildTest()
+ .testIfErrorContains("name 'y' is not defined", "[x for x in (1, 2) if y for y in (3, 4)]");
+
+ // We provide a better error message when we use the ValidationEnvironment. It should be used
+ // for BUILD files too in the future.
+ new SkylarkTest()
+ .testIfErrorContains(
+ "local variable 'y' is referenced before assignment",
+ "[x for x in (1, 2) if y for y in (3, 4)]");
}
@Test
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java b/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java
index f91e2a8..95fd030 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java
@@ -105,7 +105,8 @@
@Test
public void testFunctionDefLocalVariableReferencedBeforeAssignment() throws Exception {
- checkEvalErrorContains("Variable 'a' is referenced before assignment.",
+ checkEvalErrorContains(
+ "local variable 'a' is referenced before assignment.",
"a = 1",
"def func():",
" b = a",
@@ -116,7 +117,8 @@
@Test
public void testFunctionDefLocalVariableReferencedInCallBeforeAssignment() throws Exception {
- checkEvalErrorContains("Variable 'a' is referenced before assignment.",
+ checkEvalErrorContains(
+ "local variable 'a' is referenced before assignment.",
"def dummy(x):",
" pass",
"a = 1",
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index 8333245..2f5a19a 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -1789,15 +1789,17 @@
@Test
public void testFunctionCallBadOrdering() throws Exception {
- new SkylarkTest().testIfErrorContains("name 'foo' is not defined",
- "def func(): return foo() * 2",
- "x = func()",
- "def foo(): return 2");
+ new SkylarkTest()
+ .testIfErrorContains(
+ "global variable 'foo' is referenced before assignment",
+ "def func(): return foo() * 2",
+ "x = func()",
+ "def foo(): return 2");
}
@Test
public void testLocalVariableDefinedBelow() throws Exception {
- new SkylarkTest("--incompatible_static_name_resolution=true")
+ new SkylarkTest()
.setUp(
"def beforeEven(li):", // returns the value before the first even number
" for i in li:",
@@ -1822,10 +1824,11 @@
}
@Test
- public void testLegacyGlobalIsNotInitialized() throws Exception {
+ public void testShadowBuiltin() throws Exception {
+ // TODO(laurentlb): Forbid this.
new SkylarkTest("--incompatible_static_name_resolution=false")
- .setUp("a = len")
- .testIfErrorContains("Variable len is read only", "len = 2");
+ .setUp("x = len('abc')", "len = 2", "y = x + len")
+ .testLookup("y", 5);
}
@Test
@@ -2194,7 +2197,7 @@
@Test
public void testListComprehensionsDoNotLeakVariables() throws Exception {
checkEvalErrorContains(
- "name 'a' is not defined",
+ "local variable 'a' is referenced before assignment",
"def foo():",
" a = 10",
" b = [a for a in range(3)]",
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
index 403bfa3..24179208 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java
@@ -93,7 +93,6 @@
@Test
public void testDefinitionByItself() throws Exception {
- env = newEnvironmentWithSkylarkOptions("--incompatible_static_name_resolution=true");
// Variables are assumed to be statically visible in the block (even if they might not be
// initialized).
parse("a = a");
@@ -103,28 +102,12 @@
}
@Test
- public void testDefinitionByItselfLegacy() throws Exception {
- env = newEnvironmentWithSkylarkOptions("--incompatible_static_name_resolution=false");
- checkError("name 'a' is not defined", "a = a");
- checkError("name 'a' is not defined", "a += a");
- checkError("name 'a' is not defined", "[[] for a in a]");
- checkError("name 'a' is not defined", "def f():", " for a in a: pass");
- }
-
- @Test
public void testLocalValidationEnvironmentsAreSeparated() throws Exception {
parse("def func1():", " a = 1", "def func2():", " a = 'abc'\n");
}
@Test
- public void testBuiltinSymbolsAreReadOnlyLegacy() throws Exception {
- env = newEnvironmentWithSkylarkOptions("--incompatible_static_name_resolution=false");
- checkError("Variable repr is read only", "repr = 1");
- }
-
- @Test
public void testBuiltinsCanBeShadowed() throws Exception {
- env = newEnvironmentWithSkylarkOptions("--incompatible_static_name_resolution=true");
parse("repr = 1");
}