resolve: allow if/for/while statements at toplevel
These constructs are still disallowed in the Bazel build language,
but it is easy to check for them as a separate pass over the syntax tree,
so there is no need to complicate the spec with this restriction,
which is a nuisance in other dialects.
Clients that set the -globalreassign flag to suppress the check
need do so no longer.
Change-Id: I39ddb2128e6151a878a884fdb84d7017df2d5d51
diff --git a/doc/spec.md b/doc/spec.md
index dfa67ce..c48234d 100644
--- a/doc/spec.md
+++ b/doc/spec.md
@@ -97,6 +97,7 @@
* [Expression statements](#expression-statements)
* [If statements](#if-statements)
* [For loops](#for-loops)
+ * [While loops](#while-loops)
* [Break and Continue](#break-and-continue)
* [Load statements](#load-statements)
* [Module execution](#module-execution)
@@ -2629,14 +2630,6 @@
result = 0
```
-An `if` statement is permitted only within a function definition.
-An `if` statement at top level results in a static error.
-
-<b>Implementation note:</b>
-The Go implementation of Starlark permits `if`-statements to appear at top-level
-if the `-globalreassign` flag is enabled.
-
-
### While loops
A `while` loop evaluates an expression (the _condition_) and if the truth
@@ -2698,13 +2691,6 @@
be used to stop the execution of the loop or advance to the next
iteration.
-In Starlark, a `for` loop is permitted only within a function definition.
-A `for` loop at top level results in a static error.
-
-<b>Implementation note:</b>
-The Go implementation of Starlark permits loops to appear at top-level
-if the `-globalreassign` flag is enabled.
-
### Break and Continue
@@ -2776,7 +2762,8 @@
load("module.star", "x", y2="y", "z") # assigns x, y2, and z
```
-A load statement within a function is a static error.
+Load statements must be at top level.
+It is a static error if a load statement appears within a function, if-statement, or a loop.
## Module execution
@@ -4034,5 +4021,4 @@
* `hash` accepts operands besides strings.
* `sorted` accepts the additional parameters `key` and `reverse`.
* `type(x)` returns `"builtin_function_or_method"` for built-in functions.
-* `if`, `for`, and `while` are permitted at toplevel (option: `-globalreassign`).
* top-level rebindings are permitted (option: `-globalreassign`).
diff --git a/resolve/resolve.go b/resolve/resolve.go
index 9c5a0b8..34b4214 100644
--- a/resolve/resolve.go
+++ b/resolve/resolve.go
@@ -93,7 +93,7 @@
AllowLambda = false // allow lambda expressions
AllowFloat = false // allow floating point literals, the 'float' built-in, and x / y
AllowSet = false // allow the 'set' built-in
- AllowGlobalReassign = false // allow reassignment to globals declared in same file (deprecated)
+ AllowGlobalReassign = false // allow reassignment to globals declared in same file
AllowBitwise = false // allow bitwise operations (&, |, ^, ~, <<, and >>)
AllowRecursion = false // allow while statements and recursive functions
)
@@ -211,6 +211,7 @@
isPredeclared, isUniversal func(name string) bool
loops int // number of enclosing for loops
+ ifs int // number of enclosing if-statements
errors ErrorList
}
@@ -431,12 +432,11 @@
}
case *syntax.IfStmt:
- if !AllowGlobalReassign && r.container().function == nil {
- r.errorf(stmt.If, "if statement not within a function")
- }
r.expr(stmt.Cond)
+ r.ifs++
r.stmts(stmt.True)
r.stmts(stmt.False)
+ r.ifs--
case *syntax.AssignStmt:
if !AllowBitwise {
@@ -463,9 +463,6 @@
r.function(stmt.Def, stmt.Name.Name, &stmt.Function)
case *syntax.ForStmt:
- if !AllowGlobalReassign && r.container().function == nil {
- r.errorf(stmt.For, "for loop not within a function")
- }
r.expr(stmt.X)
const allowRebind = false
r.assign(stmt.Vars, allowRebind)
@@ -477,9 +474,6 @@
if !AllowRecursion {
r.errorf(stmt.While, doesnt+"support while loops")
}
- if !AllowGlobalReassign && r.container().function == nil {
- r.errorf(stmt.While, "while loop not within a function")
- }
r.expr(stmt.Cond)
r.loops++
r.stmts(stmt.Body)
@@ -494,8 +488,8 @@
}
case *syntax.LoadStmt:
- if r.container().function != nil {
- r.errorf(stmt.Load, "load statement within a function")
+ if r.ifs > 0 || r.loops > 0 || r.container().function != nil {
+ r.errorf(stmt.Load, "load statement not at top level")
}
const allowRebind = false
diff --git a/resolve/testdata/resolve.star b/resolve/testdata/resolve.star
index bd038ce..ae8e73f 100644
--- a/resolve/testdata/resolve.star
+++ b/resolve/testdata/resolve.star
@@ -134,7 +134,7 @@
load("module", "name") # ok
def f():
- load("foo", "bar") ### "load statement within a function"
+ load("foo", "bar") ### "load statement not at top level"
load("foo",
"", ### "load: empty identifier"
@@ -149,17 +149,8 @@
return ### "return statement not within a function"
---
-# if-statements and for-loops at top-level are forbidden
-# (without globalreassign option)
-
-for x in "abc": ### "for loop not within a function"
- pass
-
-if x: ### "if statement not within a function"
- pass
-
----
-# option:globalreassign
+# if-statements and for-loops at top-level are OK in core Starlark,
+# but they are rejected by the Bazel BUILD/.bzl dialect.
for x in "abc": # ok
pass
@@ -170,22 +161,11 @@
---
# while loops are forbidden (without -recursion option)
-def f():
- while U: ### "dialect does not support while loops"
- pass
-
----
-# option:recursion
-
-def f():
- while U: # ok
- pass
-
-while U: ### "while loop not within a function"
+while U: ### "dialect does not support while loops"
pass
---
-# option:globalreassign option:recursion
+# option:recursion
while U: # ok
pass