google / google/closure-compiler
Call return() on the iterator when a for-of loop exits abruptly
- Dominant language
- JavaScript
- Stars
- 7.7k
- Forks
- 1.2k
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 6
Description
This breaks out one part of #2899.
I looked into this briefly today. In order to do it right, we end up bloating our for-of transpilation quite a bit. Currently we transpile
```js
for (var x of y) {
...
}
```
to
```js
for (var iter=$jscomp.makeIterator(y), key=iter.next(); !key.done; key=iter.next()) {
let x = key.value;
...
}
```
which isn't so bad. We can handle `break`, `continue`, and `return` pretty easily by inserting the `return` call immediately before each, and it doesn't cost anything if you don't use them. But `throw` can be invisible (i.e. it's likely not coming from a `THROW` node in the block's AST), so the only way to handle it correctly is to wrap the whole thing in a catch:
```js
for (var iter=$jscomp.makeIterator(y), key=iter.next(); !key.done; key=iter.next()) {
let x = key.value;
try {
...
} catch (e) {
if (iter.return) iter.return();
throw e;
}
}
```
Even this only gets most of the way there - if `return` throws then we end up with the wrong error thrown. To get that correct, we need an additional `try-finally` around the `return`. If we're dealing with `break`s as well, at this point, it probably makes sense to handle those at the same time, rather than mutating the AST inside the body:
```js
try {
for (var iter=$jscomp.makeIterator(y), key=iter.next(); !key.done; key=iter.next()) {
let x = key.value;
...
}
} catch (e) {
key.thrown = {thrown: e};
} finally {
try {
if (!key.done && iter.return) iter.return();
} finally {
if (key.thrown) throw key.thrown.thrown;
}
}
```
I estimate that this will add somewhere between 50 and 80 extra gzipped bytes to every for-of loop.
Contributor guide
Assessment
This issue has not been assessed yet.