-
-
Notifications
You must be signed in to change notification settings - Fork 225
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
Inline hoisted, post-return declarations properly #391
Conversation
`); | ||
|
||
const expected = unpad(` | ||
var x = 0; |
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 not ideal, of course. But we could revisit later.
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.
It's actually correct. This is not removed because it's a global.
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.
Created #392
@@ -3,6 +3,15 @@ | |||
const some = require("lodash.some"); | |||
const { markEvalScopes, isMarked: isEvalScopesMarked, hasEval } = require("babel-helper-mark-eval-scopes"); | |||
|
|||
function forEachPrevSibling(path, callback) { |
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.
Considering that DCE has a lot of cases of path.parentPath.getSibling(path.parentPath.key - 1)
and path.parentPath.getSibling(path.parentPath.key + 1)
, I think it makes sense to add helper traversal methods to Babel.
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.
Filed babel/babel#5223
replacement = replacement.init; | ||
|
||
forEachPrevSibling(replacementPath, (path) => { | ||
if (t.isReturnStatement(path)) { |
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 will fail for cases like -
function foo() {
bar = x;
var x = 1;
}
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.
You're right. But then we have a much "deeper" problem:
function foo() {
(function() {
alert(function(){ return x }());
})();
var x = 1;
}
This brings us again to CFG and knowing when an identifier is referenced before declaration (having to traverse entire tree before declaration).
I think in this case it would be simpler to just hoist vars before doing DCE. So this:
function foo() {
bar = x;
var x = 1;
}
would first become:
function foo() {
var x;
bar = x;
x = 1;
}
which could then be transformed to:
function foo() {
bar = undefined;
}
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.
Case 1 can be solved without a CFG ... siblingPath.isAncestor(referencedPath)
will tell you this.
function foo() {
(() => {
return x // x - ReferencedPath
})() // CallExpression SiblingPath
var x = 1;
}
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.
Right, for each binding reference we can traverse a subtree upwards and check if parent is a previous sibling. We also need to check that identifier is not already defined:
function foo() {
((x) => {
return x // x - ReferencedPath but is a local var
})(2) // CallExpression SiblingPath
var x = 1;
}
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.
Yes! But, this case we are dealing with is "one-time-used-constant" - binding.references === 1 and binding.constant, and we already have the path to the reference (binding.referencePaths[0]
). So the local variable case wouldn't be this referencePath.
// simulate hoisting by replacing value | ||
// with undefined if declaration is after return | ||
replacement = isAfterReturn | ||
? t.identifier("undefined") |
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.
void 0
is safe. We can't assume that there is no local variable called undefined
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.
Good point, thanks
replacement = isAfterReturn | ||
? t.identifier("undefined") | ||
: replacement.init; | ||
|
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.
a few lines below this, replacementPath = replacementPath.get("init")
- we might have to fix this here too. Else replacement and replacementPath might point to different things.
Ok, this was easier than I thought. And your case worked right away. What should we replace init path with? |
Just found one more case - I think we should deopt if the reference is in a different scope - instead of replacing it with undefined. function foo() {
Promise.resolve().then(() => console.log(x));
var x = 1;
} will log |
Good point. We don't know if other scopes are executed (a)synchronously. Deopted. |
Fixes #192