Skip to content

Commit

Permalink
[hack pipes] Inline topic token when possible (#14278)
Browse files Browse the repository at this point in the history
* Optimize away `|> #` pipes

* Inline topic token when it's used once and there are no side effects

* Fix deferred topic evaluation

* Update standalone tests
  • Loading branch information
nicolo-ribaudo committed Feb 19, 2022
1 parent 1937284 commit a35af3e
Show file tree
Hide file tree
Showing 158 changed files with 459 additions and 386 deletions.
66 changes: 52 additions & 14 deletions packages/babel-plugin-proposal-pipeline-operator/src/hackVisitor.ts
@@ -1,8 +1,27 @@
import { types as t } from "@babel/core";
import type { NodePath, Visitor } from "@babel/traverse";

const topicReferenceReplacementVisitor = {
TopicReference(path) {
path.replaceWith(t.cloneNode(this.topicVariable));
const topicReferenceVisitor: Visitor<{
topicReferences: NodePath<t.TopicReference>[];
sideEffectsBeforeFirstTopicReference: boolean;
}> = {
exit(path, state) {
if (path.isTopicReference()) {
state.topicReferences.push(path);
} else {
if (
state.topicReferences.length === 0 &&
!state.sideEffectsBeforeFirstTopicReference &&
!path.isPure()
) {
state.sideEffectsBeforeFirstTopicReference = true;
}
}
},
"ClassBody|Function"(_, state) {
if (state.topicReferences.length === 0) {
state.sideEffectsBeforeFirstTopicReference = true;
}
},
};

Expand All @@ -22,22 +41,41 @@ export default {
return;
}

const topicVariable = scope.generateUidIdentifierBasedOnNode(node);
const pipeBodyPath = path.get("right");

scope.push({ id: topicVariable });

if (pipeBodyPath.node.type === "TopicReference") {
// If the pipe body is itself a lone topic reference,
// then replace it with the topic variable.
pipeBodyPath.replaceWith(t.cloneNode(topicVariable));
} else {
// Replace topic references with the topic variable.
pipeBodyPath.traverse(topicReferenceReplacementVisitor, {
topicVariable,
});
// then replace the whole expression with its left operand.
path.replaceWith(node.left);
return;
}

const visitorState = {
topicReferences: [],
// pipeBodyPath might be a function, and it won't be visited by
// topicReferenceVisitor because traverse() skips the top-level
// node. We must handle that case here manually.
sideEffectsBeforeFirstTopicReference: pipeBodyPath.isFunction(),
};
pipeBodyPath.traverse(topicReferenceVisitor, visitorState);

if (
visitorState.topicReferences.length === 1 &&
(!visitorState.sideEffectsBeforeFirstTopicReference ||
path.scope.isPure(node.left, true))
) {
visitorState.topicReferences[0].replaceWith(node.left);
path.replaceWith(node.right);
return;
}

const topicVariable = scope.generateUidIdentifierBasedOnNode(node);
scope.push({ id: topicVariable });

// Replace topic references with the topic variable.
visitorState.topicReferences.forEach(path =>
path.replaceWith(t.cloneNode(topicVariable)),
);

// Replace the pipe expression itself with an assignment expression.
path.replaceWith(
t.sequenceExpression([
Expand Down
@@ -1,4 +1,2 @@
var _ref;

Tuple(0);
_ref = 1, Tuple(0, _ref);
Tuple(0, 1);
@@ -1,4 +1,2 @@
var _ref;

const result = (_ref = 5, _ref);
const result = 5;
expect(result).toBe(5);
@@ -1,3 +1,3 @@
const result = 5 |> ^ + 1 |> ^ + ^;
const result = 5 |> ^ + 1 |> 2 + ^ |> ^ + ^;

expect(result).toBe(12);
@@ -1,4 +1,4 @@
var _ref, _ref2;
var _ref;

const result = (_ref2 = 5, (_ref = _ref2 + 1, _ref + _ref));
const result = (_ref = 2 + (5 + 1), _ref + _ref);
expect(result).toBe(12);
@@ -1,8 +1,8 @@
var _ref4, _ref5, _ref6;
var _ref2;

const result = (_ref6 = 5, (_ref5 = Math.pow(_ref6, 2), (_ref4 = [1, 2, 3].map(n => {
var _ref, _ref2, _ref3;
const result = (_ref2 = Math.pow(5, 2), [1, 2, 3].map(n => {
var _ref;

return _ref3 = n + _ref5, (_ref2 = _ref3 * 2, (_ref = `${_ref2} apples`, _ref.toUpperCase()));
}), _ref4.join())));
return _ref = (n + _ref2) * 2, `${_ref} apples`.toUpperCase();
}).join());
expect(result).toEqual('52 APPLES,54 APPLES,56 APPLES');
@@ -1,8 +1,8 @@
var _ref, _ref2, _ref3;
var _ref;

const result = (_ref3 = -2.2 // -2.2
, (_ref2 = Math.floor(_ref3) // -3
, (_ref = () => Math.pow(_ref2, 5) // () => -243
, _ref()))); // -243
const result = (_ref = Math.floor(-2.2 // -2.2
) // -3
, (() => Math.pow(_ref, 5) // () => -243
)()); // -243

expect(result).toBe(-243);
Expand Up @@ -3,9 +3,9 @@ function triple(x) {
}

async function asyncFunction(n) {
var _ref, _ref2, _ref3;
var _ref;

return _ref3 = n, (_ref2 = Math.abs(_ref3), (_ref = await Promise.resolve(_ref2), triple(_ref)));
return _ref = Math.abs(n), triple(await Promise.resolve(_ref));
}

asyncFunction(-7).then(result => {
Expand Down
@@ -1,10 +1,8 @@
var _ref, _ref2, _ref3;

const result = (_ref3 = 1, (_ref2 = class {
const result = new class {
#baz;

constructor() {
this.#baz = _ref3;
this.#baz = 1;
}

#bar() {
Expand All @@ -15,5 +13,5 @@ const result = (_ref3 = 1, (_ref2 = class {
return this.#bar() + 3;
}

}, (_ref = new _ref2(), _ref.foo())));
}().foo();
expect(result).toBe(1 + 2 + 3);
@@ -1,5 +1,3 @@
var _ref;

const program = '(function() { return this; })()';
const result = (_ref = program, eval(_ref));
const result = eval(program);
expect(result).not.toBeUndefined();
@@ -1,4 +1,4 @@
var _ref, _ref2, _ref3, _ref4;
var _ref;

const result = (_ref4 = 5, (_ref3 = Math.pow(_ref4, 2), (_ref2 = _ref3 + 1, (_ref = `${_ref2} apples`, _ref.toUpperCase()))));
const result = (_ref = Math.pow(5, 2) + 1, `${_ref} apples`.toUpperCase());
expect(result).toEqual('26 APPLES');
@@ -1,11 +1,11 @@
var _ref, _ref2, _ref3;
var _ref;

function area(rect) {
return rect.width * rect.height;
}

const result = (_ref3 = -5, (_ref2 = Math.abs(_ref3), (_ref = {
width: _ref2,
height: _ref2 + 3
}, area(_ref))));
const result = (_ref = Math.abs(-5), area({
width: _ref,
height: _ref + 3
}));
expect(result).toBe(40);
@@ -1,4 +1,2 @@
var _ref;

const result = (_ref = 'Hello', _ref.toUpperCase());
const result = 'Hello'.toUpperCase();
expect(result).toBe('HELLO');
@@ -1,7 +1,7 @@
function* myGenerator(n) {
var _ref, _ref2;
var _ref;

return _ref2 = n, (_ref = yield _ref2, Math.abs(_ref));
return _ref = yield n, Math.abs(_ref);
}

const myIterator = myGenerator(15);
Expand Down
@@ -1,4 +1,2 @@
var _ref, _ref2;

const result = (_ref2 = (_ref = 5, Math.pow(_ref, 2)), _ref2 + 1);
const result = Math.pow(5, 2) + 1;
expect(result).toEqual(26);
@@ -1,10 +1,10 @@
const result = () => {
var _ref, _ref2, _ref3;
var _ref;

return _ref3 = -2.2 // -2.2
, (_ref2 = Math.floor(_ref3) // -3
, (_ref = () => Math.pow(_ref2, 5) // () => -243
, _ref()));
return _ref = Math.floor(-2.2 // -2.2
) // -3
, (() => Math.pow(_ref, 5) // () => -243
)();
}; // -243


Expand Down
@@ -0,0 +1,9 @@
let x = 0;

let fnA = x++ |> (() => ^);
let fnB = x++ |> (0, () => ^);

expect(x).toBe(2);
expect(fnA()).toBe(0);
expect(fnB()).toBe(1);
expect(x).toBe(2);
@@ -0,0 +1,9 @@
let x = 0;

let fnA = x++ |> (() => ^);
let fnB = x++ |> (0, () => ^);

expect(x).toBe(2);
expect(fnA()).toBe(0);
expect(fnB()).toBe(1);
expect(x).toBe(2);
@@ -0,0 +1,9 @@
var _ref, _ref2;

let x = 0;
let fnA = (_ref = x++, () => _ref);
let fnB = (_ref2 = x++, (0, () => _ref2));
expect(x).toBe(2);
expect(fnA()).toBe(0);
expect(fnB()).toBe(1);
expect(x).toBe(2);
@@ -1,8 +1,6 @@
var _ref, _ref2;

const triple = function (x) {
return x * 3;
};

const result = (_ref2 = -7, (_ref = Math.abs(_ref2), triple(_ref)));
const result = triple(Math.abs(-7));
return expect(result).toBe(21);
Expand Up @@ -3,9 +3,9 @@ const triple = function (x) {
};

async function myFunction(n) {
var _ref, _ref2, _ref3, _ref4;
var _ref;

return _ref4 = n, (_ref3 = Math.abs(_ref4), (_ref2 = Promise.resolve(_ref3), (_ref = await _ref2, triple(_ref))));
return _ref = Math.abs(n), triple(await Promise.resolve(_ref));
}

return myFunction(-7).then(function (result) {
Expand Down
@@ -1,7 +1,7 @@
let i = 0;
let sum = 0;

while (i |> (i = ^ + 1) |> ^ <= 10)
while (i |> (i = 2 * ^ - ^ + 1) |> ^ <= 10)
sum += i;

expect(sum).toBe(10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1);
@@ -1,7 +1,7 @@
let i = 0;
let sum = 0;

while (i |> (i = ^ + 1) |> ^ <= 10)
while (i |> (i = 2 * ^ - ^ + 1) |> ^ <= 10)
sum += i;

expect(sum).toBe(10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1);
@@ -1,8 +1,8 @@
let i = 0;
let sum = 0;

while (_ref2 = i, (_ref = i = _ref2 + 1, _ref <= 10)) {
var _ref, _ref2;
while (_ref = i, (i = 2 * _ref - _ref + 1) <= 10) {
var _ref;

sum += i;
}
Expand Down
@@ -1,4 +1,2 @@
var _ref;

const x = (_ref = 0, _ref + 1);
const x = 0 + 1;
expect(x).toBe(1);
@@ -1,5 +1,5 @@
let sum = 0;
for (var i = 0 |> ^; i <= 10; i++)
for (var i = 0 |> ^*^; i <= 10; i++)
sum += i;

expect(sum).toBe(10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1);
@@ -1,6 +1,6 @@
let sum = 0;

for (var i = (_ref = 0, _ref); i <= 10; i++) {
for (var i = (_ref = 0, _ref * _ref); i <= 10; i++) {
var _ref;

sum += i;
Expand Down
@@ -1,5 +1,5 @@
let sum = 0;
for (var i = 0; i |> ^ <= 10; i++)
for (var i = 0; i |> ^ + ^ <= 20; i++)
sum = sum + i;

expect(sum).toBe(10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1)
@@ -1,5 +1,5 @@
let sum = 0;
for (var i = 0; i |> ^ <= 10; i++)
for (var i = 0; i |> ^ + ^ <= 20; i++)
sum = sum + i;

expect(sum).toBe(10 + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1)
@@ -1,6 +1,6 @@
let sum = 0;

for (var i = 0; _ref = i, _ref <= 10; i++) {
for (var i = 0; _ref = i, _ref + _ref <= 20; i++) {
var _ref;

sum = sum + i;
Expand Down
@@ -1,8 +1,6 @@
let sum = 0;

for (var i = 0; i <= 10; i = (_ref = i, _ref + 1)) {
var _ref;

for (var i = 0; i <= 10; i = i + 1) {
sum = sum + i;
}

Expand Down
@@ -1,5 +1,5 @@
function* myGenerator(n) {
var _ref, _ref2;
var _ref;

return _ref2 = n, (_ref = yield _ref2, Math.abs(_ref));
return _ref = yield n, Math.abs(_ref);
}

0 comments on commit a35af3e

Please sign in to comment.