dart-lang / dart-lang/language
Clarify specification of for statement
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
@nshahan was looking into a DDC failure of an ancient and fairly obscure test of for statement behavior. The test passes on the VM and dart2js but fails in DDC [here](https://github.com/dart-lang/sdk/blob/main/tests/language/loop/for_variable_capture_test.dart#L67-L84):
```dart
initializer_update() {
var update_closures = [];
update(callback) {
update_closures.add(callback);
return callback();
}
var init_closure;
for (var i = 0, fn = () => i; i < 4; update(() => ++i)) {
init_closure = fn;
if (i == 0) {
++i; // Mutate copy of 'i' from first iteration.
}
}
Expect.equals(1, init_closure());
Expect.listEquals([3, 4, 5], update_closures.map(run).toList());
Expect.equals(1, init_closure());
}
```
A simplified form that also fails in the same way is:
```dart
initializer_update() {
var init_closure;
for (var i = 0, fn = () => i; i < 4; ++i) {
init_closure = fn;
if (i == 0) {
++i; // Mutate copy of 'i' from first iteration.
}
}
Expect.equals(1, init_closure());
}
```
On DDC, `init_closure()` returns `0`, not `1`. DDC compiles this code to essentially the same code in JS (using `let` instead of `var`). So it seems this is a corner where the Dart semantics and JS semantics don't quite align and DDC currently doesn't handle that. I believe the VM and dart2js do what we want and follow the spec, so the test failure is just a DDC implementation bug.
But in trying to determine whether or not that was the case, I spent time looking at the specification of for loops and came away wanting. It took a lot of time for us to figure out what the spec is trying to say and how/if it applies to this case. I think it could be clarified. The current specification of for loops is:
---
Execution of a for statement of the form `for (var v = e0; c; e)` *s* proceeds
as follows:
If *c* is empty then let *c'* be `true` otherwise let *c'* be *c*.
First the variable declaration statement `var v = e0` is executed. Then:
1. If this is the first iteration of the for loop, let *v′* be *v*. Otherwise, let *v′* be the variable *v′′* created in the previous execution of step 3.
2. The expression *[v′/v]c* is evaluated to an object *o*. It is a dynamic error if the run-time type of *o* is not `bool`. If *o* is `false`, the for loop completes normally. Otherwise, execution continues at step 3.
3. The statement *[v′/v]{s}* is executed.
If this execution completes normally, continues without a label, or continues to a label (18.13) that prefixes this for statement (18.0.1), then
execution of the statement is treated as if it had completed normally.
Let *v′′* be a fresh variable. *v′′** is bound to the value of *v′*.
4. The expression *[v′′/v]e* is evaluated, and the process recurses at step 1.
---
Three issues:
1. It took a *very* close reading before @nshahan and I realized that "let *v′* be *v*" means "*v'* is literally the same *variable* as *v*". On a superficial reading, it reads like you're initializing a new variable *v'* with the value of *v*. It might be worth emphasizing that it's binding two metasyntactic variables to the same underlying storage location.
2. I think the intent is that `var v = e0` is metasyntax for *any* variable declaration statement, but it reads an awful lot like it applies only to variable declaration statements that declare a single variable. The specification says nothing about how multiple variables are handled and requires the reader to infer that `v = e0` may refer to arbitrarily many variables and initializers. It is not clear about the scoping of initializer expressions of subsequent variables and what happens if they close over earlier ones.
3. As far as I can tell, the spec doesn't specify the behavior of for loops whose first clause is an expression and not a variable declaration statement *at all*. I don't think there's anything particularly surprising to specify, but it does seem like it should be specified.
Contributor guide
Research direction
Start with tests/language/loop/for_variable_capture_test.dart and the current for-statement specification quoted in the issue. Trace the three concerns: same-variable binding, multiple declarations and initializers, and expression first clauses. Done means the specification explicitly covers these cases and the linked test behavior is addressed or documented.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100