Distinguish between Module scope and Universe scope in ValidationEnvironment
This is just a refactoring, no behavior change intended. This change makes
obvious a previous bug we want to fix. It is also a prerequisite for other
changes related to name resolution.
https://github.com/bazelbuild/bazel/issues/5637
RELNOTES: None.
PiperOrigin-RevId: 209761775
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 7021b6a..56eede9 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,12 +26,23 @@
/** A class for doing static checks on files, before evaluating them. */
public final class ValidationEnvironment extends SyntaxTreeVisitor {
+ private enum Scope {
+ /** Symbols defined inside a function or a comprehension. */
+ Local,
+ /** Symbols defined at a module top-level, e.g. functions, loaded symbols. */
+ Module,
+ /** Predefined symbols (builtins) */
+ Universe,
+ }
+
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;
- Block(@Nullable Block parent) {
+ Block(Scope scope, @Nullable Block parent) {
+ this.scope = scope;
this.parent = parent;
}
}
@@ -60,10 +71,10 @@
private Block block;
private int loopCount;
- /** Create a ValidationEnvironment for a given global Environment. */
+ /** Create a ValidationEnvironment for a given global Environment (containing builtins). */
ValidationEnvironment(Environment env) {
Preconditions.checkArgument(env.isGlobal());
- block = new Block(null);
+ block = new Block(Scope.Universe, null);
Set<String> builtinVariables = env.getVariableNames();
block.variables.addAll(builtinVariables);
block.readOnlyVariables.addAll(builtinVariables);
@@ -105,7 +116,7 @@
@Override
public void visit(ReturnStatement node) {
- if (isTopLevel()) {
+ if (block.scope != Scope.Local) {
throw new ValidationException(
node.getLocation(), "return statements must be inside a function");
}
@@ -137,7 +148,7 @@
@Override
public void visit(AbstractComprehension node) {
- openBlock();
+ openBlock(Scope.Local);
super.visit(node);
closeBlock();
}
@@ -149,7 +160,7 @@
visit(param.getDefaultValue());
}
}
- openBlock();
+ openBlock(Scope.Local);
for (Parameter<Expression, Expression> param : node.getParameters()) {
if (param.hasName()) {
declare(param.getName(), param.getLocation());
@@ -161,7 +172,7 @@
@Override
public void visit(IfStatement node) {
- if (isTopLevel()) {
+ if (block.scope != Scope.Local) {
throw new ValidationException(
node.getLocation(),
"if statements are not allowed at the top level. You may move it inside a function "
@@ -180,20 +191,25 @@
super.visit(node);
}
- /** Returns true if the current block is the top level i.e. has no parent. */
- private boolean isTopLevel() {
- return block.parent == null;
- }
-
/** 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) {
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 (isTopLevel()) { // top-level values are immutable
+ if (block.scope == Scope.Module) {
+ // Symbols defined in the module scope cannot be reassigned.
block.readOnlyVariables.add(varname);
}
block.variables.add(varname);
@@ -255,6 +271,8 @@
checkLoadAfterStatement(statements);
}
+ openBlock(Scope.Module);
+
// Add every function in the environment before validating. This is
// necessary because functions may call other functions defined
// later in the file.
@@ -266,6 +284,7 @@
}
this.visitAll(statements);
+ closeBlock();
}
public static void validateAst(Environment env, List<Statement> statements) throws EvalException {
@@ -293,8 +312,8 @@
}
/** Open a new lexical block that will contain the future declarations. */
- private void openBlock() {
- block = new Block(block);
+ private void openBlock(Scope scope) {
+ block = new Block(scope, block);
}
/** Close a lexical block (and lose all declarations it contained). */