Navigation Menu

Skip to content
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

Merged
merged 7 commits into from Jan 28, 2017
Merged

Conversation

kangax
Copy link
Member

@kangax kangax commented Jan 26, 2017

Fixes #192

`);

const expected = unpad(`
var x = 0;
Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member

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) {
Copy link
Member Author

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replacement = replacement.init;

forEachPrevSibling(replacementPath, (path) => {
if (t.isReturnStatement(path)) {
Copy link
Member

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;
}

Copy link
Member Author

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;
}

Copy link
Member

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;
}

Copy link
Member Author

@kangax kangax Jan 26, 2017

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;
}

Copy link
Member

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")
Copy link
Member

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

Copy link
Member Author

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;

Copy link
Member

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.

@kangax
Copy link
Member Author

kangax commented Jan 26, 2017

Ok, this was easier than I thought. And your case worked right away. What should we replace init path with?

@boopathi
Copy link
Member

boopathi commented Jan 27, 2017

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 1 but we will optimize it to undefined

@kangax
Copy link
Member Author

kangax commented Jan 27, 2017

Good point. We don't know if other scopes are executed (a)synchronously. Deopted.

@kangax kangax merged commit 9c016a3 into master Jan 28, 2017
@kangax kangax deleted the unreachable branch January 28, 2017 22:56
@boopathi boopathi added the Tag: Bug Fix Pull Request fixes a bug label Jan 29, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Tag: Bug Fix Pull Request fixes a bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants