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

Fix tdz checks in transform-block-scoping plugin #9498

Merged
merged 10 commits into from Jul 21, 2019
31 changes: 18 additions & 13 deletions packages/babel-helpers/src/helpers.js
Expand Up @@ -817,18 +817,6 @@ helpers.taggedTemplateLiteralLoose = helper("7.0.0-beta.0")`
}
`;

helpers.temporalRef = helper("7.0.0-beta.0")`
import undef from "temporalUndefined";

export default function _temporalRef(val, name) {
if (val === undef) {
throw new ReferenceError(name + " is not defined - temporal dead zone");
} else {
return val;
}
}
`;

helpers.readOnlyError = helper("7.0.0-beta.0")`
export default function _readOnlyError(name) {
throw new Error("\\"" + name + "\\" is read-only");
Expand All @@ -842,7 +830,24 @@ helpers.classNameTDZError = helper("7.0.0-beta.0")`
`;

helpers.temporalUndefined = helper("7.0.0-beta.0")`
export default {};
// This function isn't mean to be called, but to be used as a reference.
// We can't use a normal object because it isn't hoisted.
export default function _temporalUndefined() {}
`;

helpers.tdz = helper("7.5.5")`
export default function _tdzError(name) {
throw new ReferenceError(name + " is not defined - temporal dead zone");
}
`;

helpers.temporalRef = helper("7.0.0-beta.0")`
import undef from "temporalUndefined";
import err from "tdz";

export default function _temporalRef(val, name) {
return val === undef ? err(name) : val;
}
`;

helpers.slicedToArray = helper("7.0.0-beta.0")`
Expand Down
18 changes: 11 additions & 7 deletions packages/babel-plugin-transform-block-scoping/src/index.js
Expand Up @@ -16,7 +16,7 @@ export default declare((api, opts) => {
throw new Error(`.throwIfClosureRequired must be a boolean, or undefined`);
}
if (typeof tdzEnabled !== "boolean") {
throw new Error(`.throwIfClosureRequired must be a boolean, or undefined`);
throw new Error(`.tdz must be a boolean, or undefined`);
}

return {
Expand All @@ -33,11 +33,13 @@ export default declare((api, opts) => {

for (let i = 0; i < node.declarations.length; i++) {
const decl = node.declarations[i];
if (decl.init) {
const assign = t.assignmentExpression("=", decl.id, decl.init);
assign._ignoreBlockScopingTDZ = true;
nodes.push(t.expressionStatement(assign));
}
const assign = t.assignmentExpression(
"=",
decl.id,
decl.init || scope.buildUndefinedNode(),
);
assign._ignoreBlockScopingTDZ = true;
nodes.push(t.expressionStatement(assign));
decl.init = this.addHelper("temporalUndefined");
}

Expand Down Expand Up @@ -181,6 +183,8 @@ const letReferenceBlockVisitor = traverse.visitors.merge([
// simply rename the variables.
if (state.loopDepth > 0) {
path.traverse(letReferenceFunctionVisitor, state);
} else {
path.traverse(tdzVisitor, state);
}
return path.skip();
},
Expand Down Expand Up @@ -756,7 +760,7 @@ class BlockScoping {
closurify: false,
loopDepth: 0,
tdzEnabled: this.tdzEnabled,
addHelper: name => this.addHelper(name),
addHelper: name => this.state.addHelper(name),
};

if (isInLoop(this.blockPath)) {
Expand Down
27 changes: 8 additions & 19 deletions packages/babel-plugin-transform-block-scoping/src/tdz.js
@@ -1,12 +1,12 @@
import { types as t } from "@babel/core";
import { types as t, template } from "@babel/core";

function getTDZStatus(refPath, bindingPath) {
const executionStatus = bindingPath._guessExecutionStatusRelativeTo(refPath);

if (executionStatus === "before") {
return "inside";
} else if (executionStatus === "after") {
return "outside";
} else if (executionStatus === "after") {
return "inside";
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 that "inside" and "outside" were swapped? To me "inside" means "inside the temporal dead zone", so "the lexical declaration is after the reference".

Copy link
Member

Choose a reason for hiding this comment

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

Yeah looking at the code it seems that "inside" was meant as "inside valid Zone". It is easier to understand for me now after swapping.

} else {
return "maybe";
}
Expand Down Expand Up @@ -41,7 +41,7 @@ export const visitor = {
if (bindingPath.isFunctionDeclaration()) return;

const status = getTDZStatus(path, bindingPath);
if (status === "inside") return;
if (status === "outside") return;

if (status === "maybe") {
const assert = buildTDZAssert(node, state);
Expand All @@ -57,19 +57,8 @@ export const visitor = {
} else {
path.replaceWith(assert);
}
} else if (status === "outside") {
path.replaceWith(
t.throwStatement(
t.inherits(
t.newExpression(t.identifier("ReferenceError"), [
t.stringLiteral(
`${node.name} is not defined - temporal dead zone`,
),
]),
node,
),
),
);
} else if (status === "inside") {
path.replaceWith(template.ast`${state.addHelper("tdz")}("${node.name}")`);
}
},

Expand All @@ -87,14 +76,14 @@ export const visitor = {
const id = ids[name];

if (isReference(id, path.scope, state)) {
nodes.push(buildTDZAssert(id, state));
nodes.push(id);
}
}

if (nodes.length) {
node._ignoreBlockScopingTDZ = true;
nodes.push(node);
path.replaceWithMultiple(nodes.map(t.expressionStatement));
path.replaceWithMultiple(nodes.map(n => t.expressionStatement(n)));
}
},
},
Expand Down
Expand Up @@ -5,6 +5,7 @@ if (x) {

var innerScope = true;
var res = transform(code, {
configFile: false,
plugins: opts.plugins.concat([
function (b) {
var t = b.types;
Expand Down Expand Up @@ -34,3 +35,4 @@ if (x) {
}`;

expect(res.code).toBe(expected);
expect(innerScope).toBe(false);
@@ -1,3 +1,5 @@
f();
expect(() => {
f();

const f = function f() {}
const f = function f() {}
}).toThrow(ReferenceError);
@@ -0,0 +1,3 @@
f();

const f = function f() {}

This file was deleted.

@@ -0,0 +1,3 @@
babelHelpers.tdz("f")();

var f = function f() {};
@@ -1 +1,3 @@
let { b: d } = { d }
expect(() => {
let { b: d } = { d }
}).toThrow(ReferenceError);
@@ -0,0 +1 @@
let { b: d } = { d }

This file was deleted.

@@ -0,0 +1,5 @@
var {
b: d
} = {
d: babelHelpers.tdz("d")
};
@@ -0,0 +1,7 @@
expect(() => {
function f() {
x;
}
let x;
f();
}).not.toThrow();
@@ -0,0 +1,6 @@
function f() {
x;
}

var x;
f();
@@ -0,0 +1,7 @@
expect(() => {
function f() {
x;
}
f();
let x;
}).toThrow(ReferenceError);
@@ -0,0 +1,5 @@
function f() {
x;
}
f();
let x;
@@ -0,0 +1,6 @@
function f() {
babelHelpers.tdz("x");
}

f();
var x;
@@ -0,0 +1,6 @@
expect(() => {
function f() { x }
Math.random() === 2 && f();
let x;
f();
}).not.toThrow();
@@ -0,0 +1,4 @@
function f() { x }
Math.random() === 2 && f();
let x;
f();
@@ -0,0 +1,9 @@
var x = babelHelpers.temporalUndefined;

function f() {
babelHelpers.temporalRef(x, "x");
}

Math.random() === 2 && f();
x = void 0;
f();
@@ -0,0 +1,5 @@
function f() { x }
Math.random() === 2 && f();
let x = 3;

expect(x).toBe(3);
@@ -0,0 +1,5 @@
function f() { x }
Math.random() === 2 && f();
let x = 3;

expect(x).toBe(3);
@@ -0,0 +1,9 @@
var x = babelHelpers.temporalUndefined;

function f() {
babelHelpers.temporalRef(x, "x");
}

Math.random() === 2 && f();
x = 3;
expect(x).toBe(3);
@@ -0,0 +1,29 @@
// "random" :)
let random = (i => {
const vals = [0, 0, 1, 1];
return () => vals[i++];
})(0);

expect(() => {
function f() { x }
random() && f();
let x;
}).not.toThrow();

expect(() => {
function f() { x }
random() || f();
let x;
}).toThrow(ReferenceError);

expect(() => {
function f() { x }
random() && f();
let x;
}).toThrow(ReferenceError);

expect(() => {
function f() { x }
random() || f();
let x;
}).not.toThrow();
@@ -0,0 +1,3 @@
function f() { x }
Math.random() && f();
let x;
@@ -0,0 +1,9 @@
var x = babelHelpers.temporalUndefined;

function f() {
babelHelpers.temporalRef(x, "x");
}

Math.random() && f();
x = void 0;
void 0;
@@ -0,0 +1,17 @@
expect(() => {
function f() {
return function() { x };
}
let g = f();
let x;
g();
}).not.toThrow();

expect(() => {
function f() {
return function() { x };
}
let g = f();
g();
let x;
}).toThrow(ReferenceError);
@@ -0,0 +1,5 @@
function f() {
return function() { x };
}
f();
let x;
@@ -0,0 +1,11 @@
var x = babelHelpers.temporalUndefined;

function f() {
return function () {
babelHelpers.temporalRef(x, "x");
};
}

f();
x = void 0;
void 0;
@@ -0,0 +1,9 @@
expect(() => {
function f(i) {
if (i) f(i - 1);
x;
}

let x;
f(3);
}).not.toThrow();
@@ -0,0 +1,7 @@
function f(i) {
if (i) f(i - 1);
x;
}

let x;
f(3);
@@ -0,0 +1,7 @@
function f(i) {
if (i) f(i - 1);
x;
}

var x;
f(3);