Skip to content

Commit

Permalink
Avoid adding unnecessary closure for block scoping
Browse files Browse the repository at this point in the history
When you write

```
for (const x of l) {
  setTimeout(() => x);
}
```

we need to add a closure because the variable is meant to be block-scoped and recreated each time the block runs. We do this.

However, we also add the closure when no loop is present. This isn't necessary, because if no loop is present then each piece of code runs at most once. I changed the transform to only add a closure if a variable is referenced from within a loop.
  • Loading branch information
sophiebits committed Feb 11, 2017
1 parent 75ac320 commit 7f9af76
Show file tree
Hide file tree
Showing 16 changed files with 215 additions and 101 deletions.
@@ -1,6 +1,6 @@
function wrapper(fn) {
return (...args) => {
if (someCondition) {
while (someCondition) {
const val = fn(...args);
return val.test(() => {
console.log(val);
Expand Down
Expand Up @@ -2,17 +2,19 @@ function wrapper(fn) {
return function () {
var _arguments = arguments;

if (someCondition) {
var _ret = function () {
var val = fn(..._arguments);
return {
v: val.test(function () {
console.log(val);
})
};
}();
var _loop = function () {
var val = fn(..._arguments);
return {
v: val.test(function () {
console.log(val);
})
};
};

while (someCondition) {
var _ret = _loop();

if (typeof _ret === "object") return _ret.v;
}
};
}
}
27 changes: 25 additions & 2 deletions packages/babel-plugin-transform-es2015-block-scoping/src/index.js
Expand Up @@ -112,8 +112,21 @@ function isVar(node) {
}

const letReferenceBlockVisitor = traverse.visitors.merge([{
Loop: {
enter(path, state) {
state.loopDepth++;
},
exit(path, state) {
state.loopDepth--;
},
},
Function(path, state) {
path.traverse(letReferenceFunctionVisitor, state);
// References to block-scoped variables only require added closures if it's
// possible for the code to run more than once -- otherwise it is safe to
// simply rename the variables.
if (state.loopDepth > 0) {
path.traverse(letReferenceFunctionVisitor, state);
}
return path.skip();
}
}, tdzVisitor]);
Expand Down Expand Up @@ -549,9 +562,19 @@ class BlockScoping {
const state = {
letReferences: this.letReferences,
closurify: false,
file: this.file
file: this.file,
loopDepth: 0,
};

const loopOrFunctionParent = this.blockPath.find(
(path) => path.isLoop() || path.isFunction()
);
if (loopOrFunctionParent && loopOrFunctionParent.isLoop()) {
// There is a loop ancestor closer than the closest function, so we
// consider ourselves to be in a loop.
state.loopDepth++;
}

// traverse through this block, stopping on functions and checking if they
// contain any local let references
this.blockPath.traverse(letReferenceBlockVisitor, state);
Expand Down
@@ -1,11 +1,9 @@
if (true) {
var x;
var foo = function () {};

(function () {
function foo() {}
function bar() {
return foo;
}
for (x in {}) {}
})();
var bar = function () {
return foo;
};

for (var x in {}) {}
}
@@ -0,0 +1,34 @@
function foo() {
const x = 5;
console.log(x);

{
const x = 7;
setTimeout(() => x, 0);
}
}

function bar() {
const x = 5;
console.log(x);

for (let i = 0; i < 7; i++) {
{
const x = i;
setTimeout(() => x, 0);
}
}
}

function baz() {
const x = 5;
console.log(x);

for (let i = 0; i < 7; i++) {
var qux = function qux(y) {
const x = y;
setTimeout(() => x, 0);
};
qux(i);
}
}
@@ -0,0 +1,42 @@
function foo() {
var x = 5;
console.log(x);

{
var _x = 7;
setTimeout(function () {
return _x;
}, 0);
}
}

function bar() {
var x = 5;
console.log(x);

for (var i = 0; i < 7; i++) {
{
(function () {
var x = i;
setTimeout(function () {
return x;
}, 0);
})();
}
}
}

function baz() {
var x = 5;
console.log(x);

for (var i = 0; i < 7; i++) {
var qux = function qux(y) {
var x = y;
setTimeout(function () {
return x;
}, 0);
};
qux(i);
}
}
@@ -0,0 +1,11 @@
var f1, f2;
{
let z = 'z1 value';
f1 = function() { return z; };
}
{
let z = 'z2 value';
f2 = function() { return z; };
}
f1();
f2();
@@ -0,0 +1,15 @@
var f1, f2;
{
var z = 'z1 value';
f1 = function () {
return z;
};
}
{
var _z = 'z2 value';
f2 = function () {
return _z;
};
}
f1();
f2();
@@ -1,16 +1,18 @@
function foo() {
switch (2) {
case 0: {
if (true) {
return;
}
while (true) {
switch (2) {
case 0: {
if (true) {
return;
}

const stuff = new Map();
const data = 0;
stuff.forEach(() => {
const d = data;
});
break;
const stuff = new Map();
const data = 0;
stuff.forEach(() => {
const d = data;
});
break;
}
}
}
}
@@ -1,29 +1,31 @@
function foo() {
switch (2) {
case 0:
{
var _ret = function () {
if (true) {
return {
v: void 0
};
}
while (true) {
switch (2) {
case 0:
{
var _ret = function () {
if (true) {
return {
v: void 0
};
}

var stuff = new Map();
var data = 0;
stuff.forEach(function () {
var d = data;
});
return "break";
}();
var stuff = new Map();
var data = 0;
stuff.forEach(function () {
var d = data;
});
return "break";
}();

switch (_ret) {
case "break":
break;
switch (_ret) {
case "break":
break;

default:
if (typeof _ret === "object") return _ret.v;
default:
if (typeof _ret === "object") return _ret.v;
}
}
}
}
}
}
@@ -1,10 +1,12 @@
function fn() {
switch (true) {
default:
let foo = 4;
if (true) {
let bar = () => foo;
console.log(bar());
while (true) {
switch (true) {
default:
let foo = 4;
if (true) {
let bar = () => foo;
console.log(bar());
}
}
}
}
@@ -1,14 +1,16 @@
function fn() {
(function () {
switch (true) {
default:
var foo = 4;
if (true) {
var bar = function () {
return foo;
};
console.log(bar());
}
}
})();
while (true) {
(function () {
switch (true) {
default:
var foo = 4;
if (true) {
var bar = function () {
return foo;
};
console.log(bar());
}
}
})();
}
}
@@ -0,0 +1,6 @@
for (var i = 0; i < 5; i++) {
var l = i;
setTimeout(function () {
console.log(l);
}, 1);
}

This file was deleted.

This file was deleted.

@@ -1,17 +1,11 @@
function render(flag) {
if (flag) {
var _ret = function () {
var bar = "bar";
var bar = "bar";

[].map(() => bar);
[].map(() => bar);

return {
v: <foo bar={bar} />
};
}();

if (typeof _ret === "object") return _ret.v;
return <foo bar={bar} />;
}

return null;
}
}

0 comments on commit 7f9af76

Please sign in to comment.