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

Kill the "shadow-functions.js" internal plugin in favor of an explicit helper #5677

Merged
merged 3 commits into from
May 5, 2017

Conversation

loganfsmyth
Copy link
Member

Q A
Patch: Bug Fix? Y
Major: Breaking Change? Y, if something was relying on this stuff not being unwrapped immediately, or was using the _forceShadow, .shadow or ._shadowedFunctionLiteral properties. Hopefully minimal though.
Minor: New Feature? Maybe?
Deprecations?
Spec Compliancy?
Tests Added/Pass?
Fixed Tickets Fixes #5597, Fixes #3930
License MIT
Doc PR
Dependency Changes

This removes the private "shadow"-related properties from the AST and relies on explicit traversal to handle the conversion of arrow functions into non-arrow functions.

I've also added support for handling super and new.target into the generic utility to handle cases that we could not handle properly with the old implementation.

This also has the added benefit of not performing unexpected transformations on the AST when no plugins were enabled by the user, since we've had users reporting this as an annoyance on several occasions. This is also a bit of a blocker for any work trying to make Babel a more general codemod-like tool.

@loganfsmyth loganfsmyth added the PR: Breaking Change 💥 A type of pull request used for our changelog categories for next major release label Apr 28, 2017
@loganfsmyth loganfsmyth added this to the Babel 7 milestone Apr 28, 2017
@mention-bot
Copy link

@loganfsmyth, thanks for your PR! By analyzing the history of the files in this pull request, we identified @hzoo, @existentialism and @motiz88 to be potential reviewers.

(function () {
return _this;
});
() => this;
Copy link
Member Author

Choose a reason for hiding this comment

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

These changes are from the removed call on https://github.com/babel/babel/pull/5677/files#diff-3953cdd7a44c58e0431a2e202efb64d4L31 that wasn't needed.

@@ -0,0 +1,11 @@
class Foo extends class {} {
method() {
var _superprop_callMethod = (..._args) => super.method(..._args);
Copy link
Member Author

Choose a reason for hiding this comment

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

It's not pretty, but it's the only way we can deal with this.

Copy link
Member

Choose a reason for hiding this comment

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

This looks okay to me. Nice job, you use the same reference to the super.method call (instead of two separate) 👍

@@ -416,56 +416,79 @@ class BlockScoping {
// build the closure that we're going to wrap the block with, possible wrapping switch(){}
const fn = t.functionExpression(null, params,
t.blockStatement(isSwitch ? [block] : block.body));
fn.shadow = true;
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 whole set of changes is gross and probably the most likely to be missing edge cases, but it works as far as I can tell.

These changes were all needed so that we can get access to the final NodePath for the function, since it wasn't available anywhere before.

@@ -1,5 +1,5 @@
(function () {
var _loop = function (i) {
var _loop2 = function (i) {
Copy link
Member Author

Choose a reason for hiding this comment

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

A change in ordering in block scoping means these variable names are generated in opposite order now, thus breaking the test.

@@ -46,6 +46,10 @@ export default function ({ types: t }) {
if (state.opts.loose) Constructor = LooseTransformer;

path.replaceWith(new Constructor(path, state.file).run());

if (path.isCallExpression() && path.get("callee").isArrowFunctionExpression()) {
path.get("callee").arrowFunctionToExpression();
Copy link
Member Author

Choose a reason for hiding this comment

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

We don't really have a firm policy for when it's cool to insert actual arrows, so I added a few new arrows in place of .shadow but explicitly converted them to keep the tests passing.

Copy link
Member

Choose a reason for hiding this comment

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

Would hope that the dependency system would allow us to add/remove, preferring to instead arrows/newer syntax

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, we can always drop this now if we want, since the plugins are already in a fine order for it AFAIK, I just figured it would be better to leave that for a separate PR.


ObjectExpression: {
exit(path, file) {
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 implementation had to change because the usage of the exit handler meant that the arrow transform would fail for

var obj = {
  method(){
    var fn = () => super.method();
  }
};

since it can't handle super, meaning that for object-super to work, it needs to run before the arrow transform.

Copy link
Member

Choose a reason for hiding this comment

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

So there's no need for the CONTAINS_SUPER check anymore?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think there's a good reason to have it if we've not using the exit handler, since the main benefit of it was avoiding traversing the methods more than once. and there's no way to avoid that without the exit, but the exit would run it all in the wrong order.

@@ -239,6 +238,8 @@ export function replaceExpressionWithStatements(nodes: Array<Object>) {
}
}

this.get("callee").arrowFunctionToExpression();
Copy link
Member Author

Choose a reason for hiding this comment

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

Again just to make the tests pass, we could also leave this out and insert real arrows if we want to.

@@ -245,10 +245,10 @@ defineType("MetaProperty", {
fields: {
// todo: limit to new.target
meta: {
validate: assertValueType("string"),
validate: assertNodeType("Identifier"),
Copy link
Member Author

Choose a reason for hiding this comment

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

These were just wrong :P

@codecov
Copy link

codecov bot commented Apr 28, 2017

Codecov Report

Merging #5677 into 7.0 will decrease coverage by 0.03%.
The diff coverage is 92.24%.

Impacted file tree graph

@@            Coverage Diff             @@
##              7.0    #5677      +/-   ##
==========================================
- Coverage   84.61%   84.57%   -0.04%     
==========================================
  Files         283      282       -1     
  Lines        9744     9857     +113     
  Branches     2736     2766      +30     
==========================================
+ Hits         8245     8337      +92     
- Misses        986      999      +13     
- Partials      513      521       +8
Impacted Files Coverage Δ
...bel-plugin-transform-es2015-parameters/src/rest.js 98.14% <ø> (-0.04%) ⬇️
packages/babel-types/src/definitions/es2015.js 96.15% <ø> (ø) ⬆️
...-plugin-transform-es2015-parameters/src/default.js 89.09% <ø> (-0.2%) ⬇️
...ckages/babel-core/src/transformation/file/index.js 87.14% <ø> (ø) ⬆️
packages/babel-messages/src/index.js 61.53% <ø> (ø) ⬆️
packages/babel-traverse/src/path/ancestry.js 78.08% <ø> (-2.65%) ⬇️
...babel-plugin-transform-es2015-classes/src/index.js 86.36% <100%> (+1.36%) ⬆️
...bel-plugin-transform-es2015-classes/src/vanilla.js 90.12% <100%> (-0.47%) ⬇️
...gin-transform-es2015-modules-commonjs/src/index.js 94.47% <100%> (-0.03%) ⬇️
packages/babel-traverse/src/path/replacement.js 74.33% <100%> (ø) ⬆️
... and 16 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 86a5377...7a9d330. Read the comment docs.

@@ -14,7 +14,7 @@ function foo() {

return function () {
babelHelpers.newArrowCheck(this, _this2);
return this;
Copy link
Member

@hzoo hzoo Apr 28, 2017

Choose a reason for hiding this comment

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

👍 looks like this used to be a bug too?

Copy link
Member Author

Choose a reason for hiding this comment

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

Since it is .bind both work, this way is just a little more consistent with the non-spec arrow transform, since they share more code now.

Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer if we could keep this as this, but it's not like it's incorrect code here. Minifies better too!

Copy link
Member Author

Choose a reason for hiding this comment

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

Up for expanding on your reasoning? My motivation was mostly that it's less separation between spec and non-spec mode, but it could be made to work either way I guess.

Copy link
Member Author

Choose a reason for hiding this comment

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

Alright, I added support for spec arrow in subclass constructors, and made this stay this in spec arrows except for the subclass constructor case.

@@ -0,0 +1,11 @@
class Foo extends class {} {
method() {
var _superprop_callMethod = (..._args) => super.method(..._args);
Copy link
Member

Choose a reason for hiding this comment

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

This looks okay to me. Nice job, you use the same reference to the super.method call (instead of two separate) 👍

* Please Note, these flags are for private internal use only and should be avoided.
* Only "shadow" is a public property that other transforms may manipulate.
*/

Copy link
Member

Choose a reason for hiding this comment

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

Could we keep this comment. It helped me understand what shadow'ing meant.

Copy link
Member Author

Choose a reason for hiding this comment

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

Happy to chat if you have suggestions. This PR mostly removes the whole concept of "shadowing" that we had before, in favor of fixing everything up right away, so it's not obvious where we'd put this, or if it would be useful to people at this point.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I you did great by removing unnecessary complexity. The term variable shadowing as explained in the wiki there is confusing to me. It seems that shadowing wasn't the correct concept for passing this since it's just a transpilation technique. We could maybe chat about that on Slack if you have time?

Copy link
Member

Choose a reason for hiding this comment

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

It looks like the shadow transform was actually used for _un_shadowing 😆

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeaaah talk about confusing naming.

Copy link
Member

Choose a reason for hiding this comment

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

I talked with @loganfsmyth and now I'm fine with that.

@@ -95,6 +90,9 @@ function classOrObjectMethod(path: NodePath, callId: Object) {
// Regardless of whether or not the wrapped function is a an async method
// or generator the outer function should not be
node.generator = false;

// Unwrap the wrapper IIFE's environment so super and this and such still work.
path.get("body.body.0.argument.callee.arguments.0").unwrapFunctionEnvironment();
Copy link
Member

Choose a reason for hiding this comment

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

wth is body.body.0.argument.callee.arguments.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.

It's gross garbage :P It's reaching in to the path where we put the IIFE, since we only get access to the path after it is inserted, so we have to insert it then do this gross stuff to get the reference.

for (var _len2 = arguments.length, innerArgs = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
innerArgs[_key2 - 2] = arguments[_key2];
}

yield z;
Copy link
Member

Choose a reason for hiding this comment

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

Why did this yield move up?

Copy link
Member Author

Choose a reason for hiding this comment

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

I forget, but this change seems more like what it's supposed to do, so I didn't worry about it. It's possible I looked into the reason, but I don't remember anymore.

}
path.arrowFunctionToExpression({
// While other utils may be fine inserting other arrows to make more transforms possible,
// the arrow transform itself absolutely cannot insert new arrow functions.
Copy link
Member

Choose a reason for hiding this comment

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

Because it would leave arrows in the output defeating the purpose, or because it could lead to infinite recursion? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Why not both? :P

t.variableDeclarator(ref, fn),
]));
}
const has = this.has;
Copy link
Member

Choose a reason for hiding this comment

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

this.has was just written to a few lines ago in the same function; can it just write directly to const has, or are there things that look into this.has?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this was leftover from copy-pasting from https://github.com/babel/babel/pull/5677/files/c8ff06f90661f5e67a93b7e2f2d387a029edd98a#diff-775ae6452de339480d54604c7d217b5dL463

Looks like this.has is used one other place, but otherwise isn't updated, so you're right I could move this up farther.

}

// handlers async functions
const hasAsync = traverse.hasType(fn.body, this.scope, "AwaitExpression", t.FUNCTION_TYPES);
if (hasAsync) {
fn.async = true;
call = t.awaitExpression(call);
basePath = ".argument" + basePath;
}
Copy link
Member

Choose a reason for hiding this comment

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

How does this break with async iterators?

Though anyone not transforming async iterators are certainly running on an engine where they can skip transforming block scoping... right Safari? <_<

Copy link
Member Author

Choose a reason for hiding this comment

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

No idea, I didn't realize this logic even did this until inspecting this code.

* Please Note, these flags are for private internal use only and should be avoided.
* Only "shadow" is a public property that other transforms may manipulate.
*/

Copy link
Member

Choose a reason for hiding this comment

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

It looks like the shadow transform was actually used for _un_shadowing 😆

t.thisExpression(),
t.identifier(thisBinding),
]))
);
Copy link
Member

Choose a reason for hiding this comment

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

Looks like I got you a merge conflict by merging #5620

Copy link
Member Author

Choose a reason for hiding this comment

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

No worries.

@@ -14,7 +14,7 @@ function foo() {

return function () {
babelHelpers.newArrowCheck(this, _this2);
return this;
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer if we could keep this as this, but it's not like it's incorrect code here. Minifies better too!

ThisExpression(child) {
thisPaths.push(child);
},
JSXIdentifier(child) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this for <this.Foo/>?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is, I guess I should make this JSXMemberExpression now that you mention it though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added some checks to explicitly verify the location of the identifier.

() => super();
() => this;
`, `
var _supercall = (..._args) => _this = super(..._args),
Copy link
Member

Choose a reason for hiding this comment

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

Was wondering if something would catch multiple super() invocations, but I guess that's the job of the JS engine / class transform.

Copy link
Member Author

Choose a reason for hiding this comment

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

That I think we should leave up to the class transform, it could certainly add logic for that.

@loganfsmyth
Copy link
Member Author

Okay, I rebased to fix the merge conflict with spec function naming, and addressed the few issues people have raised except the one question of this vs _this in arrow spec mode. Happy to chat on that on Slack tomorrow.

@loganfsmyth loganfsmyth merged commit 14584c2 into babel:7.0 May 5, 2017
@loganfsmyth loganfsmyth deleted the kill-shadow-functions branch May 5, 2017 20:27
@mikemorancodes
Copy link

What if I just need to fix the #3930 issue?

@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 6, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 6, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: Breaking Change 💥 A type of pull request used for our changelog categories for next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants