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

Transform for the smart pipeline operator proposal #9179

Merged
merged 6 commits into from Jan 21, 2019

Conversation

thiagoarrais
Copy link
Contributor

@thiagoarrais thiagoarrais commented Dec 13, 2018

Q                       A
Fixed Issues? Related to babel/proposals#29
Patch: Bug Fix? No
Major: Breaking Change? No
Minor: New Feature? Yes
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes? No
License MIT

This implements a first draft for the smart pipeline proposal. It is based on IIFE and does not support async/await inside the pipelines yet. That will need to be added by a subsequent PR.

Relates to https://github.com/babel/babel/projects/9#card-15461643

@nicolo-ribaudo nicolo-ribaudo added PR: New Feature 🚀 A type of pull request used for our changelog categories Spec: Pipeline Operator labels Dec 13, 2018
@nicolo-ribaudo nicolo-ribaudo added this to In progress in Pipeline Operator via automation Dec 13, 2018
@babel-bot
Copy link
Collaborator

babel-bot commented Dec 13, 2018

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/9842/

@mAAdhaTTah
Copy link
Contributor

Couple quick comments:

  1. I think we need to be passing the proposal option to the parser.
  2. It'll be easier to maintain the various proposal if there are clear "branches" for each proposal, and then extracting shared functionality to a module used by all of the branches.
  3. I could be wrong, but I'm not sure tests in a subfolder run. They didn't for the parser, as your test setup is pretty similar to what I initially did for the parser work. My first thought looking at the code is whether the minimal still works. Which it might! Just worth double checking.

@thiagoarrais
Copy link
Contributor Author

  1. I think we need to be passing the proposal option to the parser.

I think this is done by line 21 in babel-plugin-syntax-pipeline-operator/src/index.js but I'm not sure this is what you're talking about.

  1. It'll be easier to maintain the various proposal if there are clear "branches" for each proposal,

Good idea. I guess a first step on this direction would be having separate visitors and switching on the parseOpts proposal.

  1. I could be wrong, but I'm not sure tests in a subfolder run.

Yeah, they do without a problem. Maybe parser tests are different somehow?

@mAAdhaTTah
Copy link
Contributor

  1. Yes it is, my b.
  2. That sounds good.
  3. Could be.

Thanks!

@nicolo-ribaudo
Copy link
Member

It'll be easier to maintain the various proposal if there are clear "branches" for each proposal,

Yeah, for example I put the two different decorators transformers in two different files (https://github.com/nicolo-ribaudo/babel/blob/33ac9a246c4b4e45f8f1b63122f4ba502f3cf32e/packages/babel-plugin-proposal-decorators/src/index.js)

@thiagoarrais
Copy link
Contributor Author

Nice. I'll definitely steal that idea.

@js-choi
Copy link
Contributor

js-choi commented Dec 14, 2018

Just confirming: This version of the transformer uses IIFEs, so it doesn’t yet support await and yield, right? (See also a similar problem with the do-expressions transformer filed in #3780.)

@thiagoarrais
Copy link
Contributor Author

Yeah . You're right, @js-choi

@js-choi
Copy link
Contributor

js-choi commented Dec 14, 2018

Yes, great: thanks for your hard work, @thiagoarrais.

@thiagoarrais and I have already talked about this, but to explain for other people’s benefit: Because this current transform uses IIFEs, it won’t work with operations that are dependent on the current function: namely await and yield.

In other words, even though this:

async function () { return [g(), x |> f |> # + 1]; }

…is the same as this:

async function () { return [g(), ($ => $ + 1)(f(x))]; }

…adding an await or yield messes it up:

async function () { return [g(), x |> f |> await # |> # + 1]; }

…because that isn’t the same as this, which is an error:

async function () { return [g(), ($ => $ + 1)(($ => await $)(f(x)))]; }

So this IIFE-using transform is a preliminary approach. Eventually, we’ll have to write a function that finds expressions within statements and then recursively pulls them out into constant declarations. Such a function would convert code like this:

async function () { return [g(), x |> f |> await # |> # + 1]; }

…to code like this:

async function () {
  const $1 = g();
  const $2 = x;
  const $3 = f($2);
  const $4 = await $3;
  const $5 = $4 + 1;
  return [$1, $5];
}

I suspect that, in fact, the current F#-plus-await pipeline proposal will need something like this too for its own approach to await.

In fact, the do-expression plugin, which currently also uses IIFEs, could also use such functionality, as per #3780.

But even though this version of the transform might be preliminary, it’s still a great start! Thank you, @thiagoarrais.


const minimalVisitor = {
BinaryExpression: {
exit(path) {
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to run this on exit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some of the calls in this function need Expression nodes and the smart proposal child nodes aren't expressions, but can be converted by the smart proposal visitor. Running on exit guarantees that the child nodes are already converted.

Copy link
Contributor

Choose a reason for hiding this comment

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

This feels a bit like the logic is getting mixed between them. This might be a bit pendantic, but I'd extract the shared logic to a separate module and run them at different times.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could move this function to its own module and bind it to entry (or the default BinaryExpression) on minimal and to exit on smart. But I'm not really sure about that. It feels like splitting hairs.

Plus: running on exit works great with the flattening PR that I've got in the works.

@Wassna002

This comment has been minimized.

);
},
},
visitor: visitorsPerProposal[options.proposal],
Copy link
Member

Choose a reason for hiding this comment

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

We should add validation for the proposal optoin.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the syntax plugin already does this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we need to add it here too?

Copy link
Member

Choose a reason for hiding this comment

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

In the decorators plugin I put the check both in the syntax and in the transform plugin, so that the error message uses the correct plugin name. It is a nice-to-have but it isn't really needed.

Copy link
Contributor

@js-choi js-choi Dec 18, 2018

Choose a reason for hiding this comment

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

Is it possible for that extra check by the transform plugin to be observable by a test? All the fixtures in all the transform plugins that I’ve read seem to integration-test both parsing and transforming.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, you can test the error message by adding "error": "Expected error message" in the options.json file of the fixture.

Copy link
Contributor

@js-choi js-choi Dec 18, 2018

Choose a reason for hiding this comment

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

Well, even then, adding such a test fixture still wouldn’t distinguish between the parser throwing the error versus the transform plugin throwing the error, right?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah that's right. But usually parser error messages are different from plugin error messages. That is the important part; not where the error actually comes from.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I find the syntax plugin message (below) is clear enough in this case.

The pipeline operator plugin requires a 'proposal' option.
'proposal' must be one of: minimal, smart. More details:
https://babeljs.io/docs/en/next/babel-plugin-proposal-pipeline-operator

Maybe it should be moved to the transform plugin. What about holding it off for a later PR?

Copy link
Member

Choose a reason for hiding this comment

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

Oh I didn't noticed that the link alreay points to the transform plugin. It is ok as-is 👍

Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

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

Thank you! Don't know why I initially thought that this PR would have been much bigger; it looks good to me 🙂

@thiagoarrais
Copy link
Contributor Author

Nice! I'm done here for now. Please let me know if you need anything else.

@mAAdhaTTah
Copy link
Contributor

Damn, I need to hurry up!

@js-choi
Copy link
Contributor

js-choi commented Dec 19, 2018

@thiagoarrais: Great work, again. Let me know on whether you plan to tackle #9178 (yield parsing) or the IIFEs-to-flattened-expressions rewrite. For what it’s worth, I expect to submit a pull request for adding ?-placeholder support to babel-parser within two days, time permitting, and that will rename a lot of babel-parser’s pipeline test fixtures.

@mAAdhaTTah: No need to rush! We still don’t have await/yield support yet due to the use of IIFEs, after all. But feel free to keep working independently as you can; let me know if you need any help.

@thiagoarrais
Copy link
Contributor Author

Since, #9216 landed, I've rebased to the latest master and added a check for yield.

@thiagoarrais
Copy link
Contributor Author

Bumping this. Let me know if you need anything else, @nicolo-ribaudo.

@nicolo-ribaudo
Copy link
Member

It looks good to me. I didn't merge it because we have a policy of two ✔️ from core team members, but reviews by @mAAdhaTTah and @js-choi would probably be equally valuable on this topic.

Copy link
Contributor

@mAAdhaTTah mAAdhaTTah left a comment

Choose a reason for hiding this comment

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

Nice job! Happy to see this moving forward. Let some questions / suggestions.

const visitorsPerProposal = {
minimal: minimalVisitor,
smart: smartVisitor,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Hoist to module root?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. Wil do it.

@@ -0,0 +1,5 @@
const result = -2.2 // -2.2
|> Math.floor // -3
|> (() => Math.pow(#, 5)) // () => -243
Copy link
Contributor

Choose a reason for hiding this comment

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

@js-choi Question on the spec behavior: if we didn't use a topic token here, this arrow would get executed?

-2.2
    |> (x => Math.pow(x, 5))

This is also valid in Smart. Are we checking whether the token appears in the arrow body?


const minimalVisitor = {
BinaryExpression: {
exit(path) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels a bit like the logic is getting mixed between them. This might be a bit pendantic, but I'd extract the shared logic to a separate module and run them at different times.

@loganfsmyth loganfsmyth self-requested a review January 14, 2019 17:17
@nicolo-ribaudo nicolo-ribaudo mentioned this pull request Jan 14, 2019
3 tasks
Copy link
Member

@xtuc xtuc left a comment

Choose a reason for hiding this comment

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

LGTM (I don't know the spec well tho), thanks for the PR!

@nicolo-ribaudo nicolo-ribaudo merged commit e9331aa into babel:master Jan 21, 2019
Pipeline Operator automation moved this from In progress to Done Jan 21, 2019
@hzoo
Copy link
Member

hzoo commented Jan 21, 2019

Released in 7.3! Thanks everyone https://twitter.com/NicoloRibaudo/status/1087473662696017922?s=20

@thiagoarrais
Copy link
Contributor Author

Thank you everyone! I'll start work on flattening expressions (async/await) next.

@thiagoarrais thiagoarrais deleted the smart-pipeline-transform branch January 22, 2019 16:13
@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 4, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 4, 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: New Feature 🚀 A type of pull request used for our changelog categories Spec: Pipeline Operator
Projects
Development

Successfully merging this pull request may close these issues.

None yet

8 participants