New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create additional scope when redeclaring array variable in for..of loop #8920
Conversation
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/9281/ |
1 similar comment
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/9281/ |
Seems that this PR fixes the issue. But I used a primitive method to check this out:
I am going to apply changes from this PR to my package inside the |
I applied the patch to package installed locally and it works! ❤️ |
@@ -140,6 +145,10 @@ export default declare((api, options) => { | |||
t.inherits(loop, node); | |||
t.ensureBlock(loop); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great effort, thanks so much, the main issues are
- Technically there can be multiple names, and we need to check if any of them collide
for (const {a, b} of arr) { const b = 4; }
- Rather than assigning to
block.body
it would be better to replace thepath.node.body
node, I think
Something along the lines of
const iterationKey = scope.generateUidIdentifier("i");
const body = path.get("body");
const hasKeyConflict =
body.isBlockStatement() &&
path.getBindingIdentifiers().some(id => body.scope.hasBinding(id));
let body = hasKeyConflict
? t.blockStatement([node.body])
: node.body;
let loop = buildForOfArray({
BODY: body,
I forget the exact return value of path.getBindingIdentifiers()
so you might need to tweak that.
Also, we should make sure this also adds the block wrapper for code like
let a, b;
for ({a, b} of arr) {
const b = 4;
}
since that would also collide. I think this might just work automatically with getBindingIdentifiers
but please check if you can.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback!
As for your first suggestion, I suppose that comes down the scope of the PR. The original issue was caused by a reuse of the array identifier, not the iteration variable. This was causing the array lookup to use the wrong identifier when the inner variable was renamed.
There are a couple of other issues open which specifically deal with the iteration variables being redeclared, namely #8498 and #8839.
I'm rather new to this whole game, so I'm not sure whether it would be preferred to fix those issues in this PR as well, since it is similar, or just leave them to someone else. This PR has already been referenced in #8498 as a related solution. But I'll defer back to you on that.
As for your second point, the reason I used block.body
was because block
is guaranteed to be a block statement, whereas path.node.body
is not. If there is some other reason this is preferred, then I suppose the code could be changed to the following:
if (path.get("body").scope.hasOwnBinding(right.name)) {
path.node.body = t.blockStatement([t.toBlock(body)]);
}
const block = t.toBlock(path.node.body);
But this seems a bit underhanded to me.
Any update on this? We've had one or two Create React App users hit this. 😄 |
This seems it would be fixed by #9697 |
When transforming a
for..of
loop that iterates through an array, redeclaring the array identifier within the loop would result in an ambiguity when assigning the iteration variable at the top of the transformed loop body, which could result in incorrect behavior.This fix detects whether the array identifier is redeclared within the loop, and if so, wraps the original loop body in an additional block statement to prevent ambiguities when assigning the iteration variable.