Transform for F# Pipeline (#9984)

* Transform for F#-style await

Inludes support for optimizing single-parameter arrow functions

* Wait until optimization before pushing placeholder into scope
This commit is contained in:
Thiago Arrais 2019-05-25 07:48:30 -03:00 committed by Nicolò Ribaudo
parent 6b8a37c413
commit a4170b5e32
30 changed files with 335 additions and 46 deletions

View File

@ -0,0 +1,54 @@
import { types as t } from "@babel/core";
// tries to optimize sequence expressions in the format
// (a = b, ((c) => d + e)(a))
// to
// (a = b, a + e)
const buildOptimizedSequenceExpression = ({ assign, call, path }) => {
const { left: placeholderNode, right: pipelineLeft } = assign;
const { callee: calledExpression } = call;
let optimizeArrow =
t.isArrowFunctionExpression(calledExpression) &&
t.isExpression(calledExpression.body) &&
!calledExpression.async &&
!calledExpression.generator;
let param;
if (optimizeArrow) {
const { params } = calledExpression;
if (params.length === 1 && t.isIdentifier(params[0])) {
param = params[0];
} else if (params.length > 0) {
optimizeArrow = false;
}
} else if (t.isIdentifier(calledExpression, { name: "eval" })) {
const evalSequence = t.sequenceExpression([
t.numericLiteral(0),
calledExpression,
]);
call.callee = evalSequence;
path.scope.push({ id: placeholderNode });
return t.sequenceExpression([assign, call]);
}
if (optimizeArrow && !param) {
// Arrow function with 0 arguments
return t.sequenceExpression([pipelineLeft, calledExpression.body]);
}
path.scope.push({ id: placeholderNode });
if (param) {
path.get("right").scope.rename(param.name, placeholderNode.name);
return t.sequenceExpression([assign, calledExpression.body]);
}
return t.sequenceExpression([assign, call]);
};
export default buildOptimizedSequenceExpression;

View File

@ -0,0 +1,25 @@
import { types as t } from "@babel/core";
import buildOptimizedSequenceExpression from "./buildOptimizedSequenceExpression";
const fsharpVisitor = {
BinaryExpression(path) {
const { scope, node } = path;
const { operator, left, right } = node;
if (operator !== "|>") return;
const placeholder = scope.generateUidIdentifierBasedOnNode(left);
const call =
right.type === "AwaitExpression"
? t.awaitExpression(t.cloneNode(placeholder))
: t.callExpression(right, [t.cloneNode(placeholder)]);
const sequence = buildOptimizedSequenceExpression({
assign: t.assignmentExpression("=", t.cloneNode(placeholder), left),
call,
path,
});
path.replaceWith(sequence);
},
};
export default fsharpVisitor;

View File

@ -2,10 +2,12 @@ import { declare } from "@babel/helper-plugin-utils";
import syntaxPipelineOperator from "@babel/plugin-syntax-pipeline-operator"; import syntaxPipelineOperator from "@babel/plugin-syntax-pipeline-operator";
import minimalVisitor from "./minimalVisitor"; import minimalVisitor from "./minimalVisitor";
import smartVisitor from "./smartVisitor"; import smartVisitor from "./smartVisitor";
import fsharpVisitor from "./fsharpVisitor";
const visitorsPerProposal = { const visitorsPerProposal = {
minimal: minimalVisitor, minimal: minimalVisitor,
smart: smartVisitor, smart: smartVisitor,
fsharp: fsharpVisitor,
}; };
export default declare((api, options) => { export default declare((api, options) => {

View File

@ -1,51 +1,21 @@
import { types as t } from "@babel/core"; import { types as t } from "@babel/core";
import buildOptimizedSequenceExpression from "./buildOptimizedSequenceExpression";
const minimalVisitor = { const minimalVisitor = {
BinaryExpression(path) { BinaryExpression(path) {
const { scope } = path; const { scope, node } = path;
const { node } = path; const { operator, left, right } = node;
const { operator, left } = node;
let { right } = node;
if (operator !== "|>") return; if (operator !== "|>") return;
let optimizeArrow = const placeholder = scope.generateUidIdentifierBasedOnNode(left);
t.isArrowFunctionExpression(right) &&
t.isExpression(right.body) &&
!right.async &&
!right.generator;
let param;
if (optimizeArrow) { const call = t.callExpression(right, [t.cloneNode(placeholder)]);
const { params } = right;
if (params.length === 1 && t.isIdentifier(params[0])) {
param = params[0];
} else if (params.length > 0) {
optimizeArrow = false;
}
} else if (t.isIdentifier(right, { name: "eval" })) {
right = t.sequenceExpression([t.numericLiteral(0), right]);
}
if (optimizeArrow && !param) {
// Arrow function with 0 arguments
path.replaceWith(t.sequenceExpression([left, right.body]));
return;
}
const placeholder = scope.generateUidIdentifierBasedOnNode(param || left);
scope.push({ id: placeholder });
if (param) {
path.get("right").scope.rename(param.name, placeholder.name);
}
const call = optimizeArrow
? right.body
: t.callExpression(right, [t.cloneNode(placeholder)]);
path.replaceWith( path.replaceWith(
t.sequenceExpression([ buildOptimizedSequenceExpression({
t.assignmentExpression("=", t.cloneNode(placeholder), left), assign: t.assignmentExpression("=", t.cloneNode(placeholder), left),
call, call,
]), path,
}),
); );
}, },
}; };

View File

@ -0,0 +1,27 @@
const y = 2;
const f = (x) => (x |> (y) => y + 1)
|> (z) => z * y
const _f = (x) => x
|> (y) => y + 1
|> (z) => z * y
const g = (x) => x
|> (y) => (y + 1 |> (z) => z * y)
const _g = (x) => x
|> (y => (y + 1 |> (z) => z * y))
const __g = (x) => x
|> (
y => {
return (y + 1 |> (z) => z * y);
}
)
expect( f(1)).toBe(4);
expect( _f(1)).toBe(4);
expect( g(1)).toBe(2);
expect( _g(1)).toBe(2);
expect(__g(1)).toBe(2);

View File

@ -0,0 +1,14 @@
var result = [5,10]
|> (_ => _.map(x => x * 2))
|> (_ => _.reduce( (a,b) => a + b ))
|> (sum => sum + 1)
expect(result).toBe(31);
var inc = (x) => x + 1;
var double = (x) => x * 2;
var result2 = [4, 9].map( x => x |> inc |> double )
expect(result2).toEqual([10, 20]);

View File

@ -0,0 +1,14 @@
var result = [5,10]
|> (_ => _.map(x => x * 2))
|> (_ => _.reduce( (a,b) => a + b ))
|> (sum => sum + 1)
expect(result).toBe(31);
var inc = (x) => x + 1;
var double = (x) => x * 2;
var result2 = [4, 9].map( x => x |> inc |> double )
expect(result2).toEqual([10, 20]);

View File

@ -0,0 +1,15 @@
var _ref, _ref2, _ref3;
var result = (_ref = (_ref2 = (_ref3 = [5, 10], _ref3.map(x => x * 2)), _ref2.reduce((a, b) => a + b)), _ref + 1);
expect(result).toBe(31);
var inc = x => x + 1;
var double = x => x * 2;
var result2 = [4, 9].map(x => {
var _ref4, _x;
return _ref4 = (_x = x, inc(_x)), double(_ref4);
});
expect(result2).toEqual([10, 20]);

View File

@ -0,0 +1,13 @@
const triple = (x) => x * 3;
async function myFunction(n) {
return n
|> Math.abs
|> Promise.resolve
|> await
|> triple;
}
return myFunction(-7).then(result => {
expect(result).toBe(21);
});

View File

@ -0,0 +1,6 @@
async function myFunction(n) {
return n
|> Math.abs
|> Promise.resolve
|> await;
}

View File

@ -0,0 +1,9 @@
{
"plugins": [
["proposal-pipeline-operator", { "proposal": "fsharp" }]
],
"parserOpts": {
"allowReturnOutsideFunction": true
},
"minNodeVersion": "8.0.0"
}

View File

@ -0,0 +1,5 @@
async function myFunction(n) {
var _ref, _ref2, _n;
return _ref = (_ref2 = (_n = n, Math.abs(_n)), Promise.resolve(_ref2)), await _ref;
}

View File

@ -0,0 +1,3 @@
var inc = (x) => x + 1
expect(10 |> inc).toBe(11);

View File

@ -0,0 +1,3 @@
var inc = (x) => x + 1
expect(10 |> inc).toBe(11);

View File

@ -0,0 +1,5 @@
var _;
var inc = x => x + 1;
expect((_ = 10, inc(_))).toBe(11);

View File

@ -0,0 +1,7 @@
(function() {
'use strict';
var result = '(function() { return this; })()'
|> eval;
expect(result).not.toBeUndefined();
})();

View File

@ -0,0 +1,7 @@
(function() {
'use strict';
var result = '(function() { return this; })()'
|> eval;
expect(result).not.toBeUndefined();
})();

View File

@ -0,0 +1,8 @@
(function () {
'use strict';
var _functionReturn;
var result = (_functionReturn = '(function() { return this; })()', (0, eval)(_functionReturn));
expect(result).not.toBeUndefined();
})();

View File

@ -0,0 +1,5 @@
var array = [10,20,30];
var last = array |> (a => a[a.length-1]);
expect(last).toBe(30);

View File

@ -0,0 +1,5 @@
var _array;
var array = [10, 20, 30];
var last = (_array = array, _array[_array.length - 1]);
expect(last).toBe(30);

View File

@ -0,0 +1,8 @@
var a = 1,
b = 2,
c = 3;
var result = a
|> (() => b)
|> (() => c);
expect(result).toBe(c);

View File

@ -0,0 +1,8 @@
var a = 1,
b = 2,
c = 3;
var result = a
|> (() => b)
|> (() => c);
expect(result).toBe(c);

View File

@ -0,0 +1,5 @@
var a = 1,
b = 2,
c = 3;
var result = ((a, b), c);
expect(result).toBe(c);

View File

@ -0,0 +1,8 @@
{
"plugins": [
["proposal-pipeline-operator", { "proposal": "fsharp" }]
],
"parserOpts": {
"allowReturnOutsideFunction": true
}
}

View File

@ -0,0 +1,20 @@
const y = 2;
const f = (x) => x
|> (y => y + 1)
|> (z => z * y)
const g = (x) => x
|> (y =>
y + 1
|> (z => z * y)
)
const h = (x) => x
|> (y => (
y + 1
|> (z => z * y)
))
expect(f(1)).toBe(4);
expect(g(1)).toBe(2);
expect(h(1)).toBe(2);

View File

@ -0,0 +1,20 @@
const y = 2;
const f = (x) => x
|> (y => y + 1)
|> (z => z * y)
const g = (x) => x
|> (y =>
y + 1
|> (z => z * y)
)
const h = (x) => x
|> (y => (
y + 1
|> (z => z * y)
))
expect(f(1)).toBe(4);
expect(g(1)).toBe(2);
expect(h(1)).toBe(2);

View File

@ -0,0 +1,23 @@
const y = 2;
const f = x => {
var _ref, _x;
return _ref = (_x = x, _x + 1), _ref * y;
};
const g = x => {
var _x2, _ref2;
return _x2 = x, (_ref2 = _x2 + 1, _ref2 * _x2);
};
const h = x => {
var _x3, _ref3;
return _x3 = x, (_ref3 = _x3 + 1, _ref3 * _x3);
};
expect(f(1)).toBe(4);
expect(g(1)).toBe(2);
expect(h(1)).toBe(2);

View File

@ -1,6 +1,6 @@
var _sum, _ref, _ref2; var _ref, _ref2, _ref3;
var result = (_sum = (_ref = (_ref2 = [5, 10], _ref2.map(x => x * 2)), _ref.reduce((a, b) => a + b)), _sum + 1); var result = (_ref = (_ref2 = (_ref3 = [5, 10], _ref3.map(x => x * 2)), _ref2.reduce((a, b) => a + b)), _ref + 1);
expect(result).toBe(31); expect(result).toBe(31);
var inc = x => x + 1; var inc = x => x + 1;
@ -8,8 +8,8 @@ var inc = x => x + 1;
var double = x => x * 2; var double = x => x * 2;
var result2 = [4, 9].map(x => { var result2 = [4, 9].map(x => {
var _ref3, _x; var _ref4, _x;
return _ref3 = (_x = x, inc(_x)), double(_ref3); return _ref4 = (_x = x, inc(_x)), double(_ref4);
}); });
expect(result2).toEqual([10, 20]); expect(result2).toEqual([10, 20]);

View File

@ -1,5 +1,5 @@
var _a; var _array;
var array = [10, 20, 30]; var array = [10, 20, 30];
var last = (_a = array, _a[_a.length - 1]); var last = (_array = array, _array[_array.length - 1]);
expect(last).toBe(30); expect(last).toBe(30);

View File

@ -1,6 +1,6 @@
import { declare } from "@babel/helper-plugin-utils"; import { declare } from "@babel/helper-plugin-utils";
export const proposals = ["minimal", "smart"]; export const proposals = ["minimal", "smart", "fsharp"];
export default declare((api, { proposal }) => { export default declare((api, { proposal }) => {
api.assertVersion(7); api.assertVersion(7);