Merge pull request #6051 from babel/5709-2

Rewrite parameter transform and drop _blockHoist reliance
This commit is contained in:
Henry Zhu 2017-08-04 18:55:23 -04:00 committed by GitHub
commit 47a9ba3440
14 changed files with 336 additions and 367 deletions

View File

@ -21,15 +21,15 @@ console.log(unpackObject({
}));
var unpackArray = function (_ref3, _ref4) {
var _ref6 = babelHelpers.slicedToArray(_ref3, 3),
a = _ref6[0],
b = _ref6[1],
c = _ref6[2];
var _ref5 = babelHelpers.slicedToArray(_ref3, 3),
a = _ref5[0],
b = _ref5[1],
c = _ref5[2];
var _ref5 = babelHelpers.slicedToArray(_ref4, 3),
x = _ref5[0],
y = _ref5[1],
z = _ref5[2];
var _ref6 = babelHelpers.slicedToArray(_ref4, 3),
x = _ref6[0],
y = _ref6[1],
z = _ref6[2];
return a + b + c;
};

View File

@ -1,185 +0,0 @@
import getFunctionArity from "babel-helper-get-function-arity";
import callDelegate from "babel-helper-call-delegate";
import template from "babel-template";
import * as t from "babel-types";
const buildDefaultParam = template(`
let VARIABLE_NAME =
ARGUMENTS.length > ARGUMENT_KEY && ARGUMENTS[ARGUMENT_KEY] !== undefined ?
ARGUMENTS[ARGUMENT_KEY]
:
DEFAULT_VALUE;
`);
const buildLooseDefaultParam = template(`
if (ASSIGMENT_IDENTIFIER === UNDEFINED) {
ASSIGMENT_IDENTIFIER = DEFAULT_VALUE;
}
`);
const buildLooseDestructuredDefaultParam = template(`
let ASSIGMENT_IDENTIFIER = PARAMETER_NAME === UNDEFINED ? DEFAULT_VALUE : PARAMETER_NAME ;
`);
const buildCutOff = template(`
let $0 = $1[$2];
`);
function hasDefaults(node) {
for (const param of (node.params: Array<Object>)) {
if (!t.isIdentifier(param)) return true;
}
return false;
}
function isSafeBinding(scope, node) {
if (!scope.hasOwnBinding(node.name)) return true;
const { kind } = scope.getOwnBinding(node.name);
return kind === "param" || kind === "local";
}
const iifeVisitor = {
ReferencedIdentifier(path, state) {
const { scope, node } = path;
if (node.name === "eval" || !isSafeBinding(scope, node)) {
state.iife = true;
path.stop();
}
},
Scope(path) {
// different bindings
path.skip();
},
};
export const visitor = {
Function(path) {
const { node, scope } = path;
if (!hasDefaults(node)) return;
// ensure it's a block, useful for arrow functions
path.ensureBlock();
const params = path.get("params");
if (this.opts.loose) {
const body = [];
for (let i = 0; i < params.length; ++i) {
const param = params[i];
if (param.isAssignmentPattern()) {
const left = param.get("left");
const right = param.get("right");
const undefinedNode = scope.buildUndefinedNode();
if (left.isIdentifier()) {
body.push(
buildLooseDefaultParam({
ASSIGMENT_IDENTIFIER: left.node,
DEFAULT_VALUE: right.node,
UNDEFINED: undefinedNode,
}),
);
param.replaceWith(left.node);
} else if (left.isObjectPattern() || left.isArrayPattern()) {
const paramName = scope.generateUidIdentifier();
body.push(
buildLooseDestructuredDefaultParam({
ASSIGMENT_IDENTIFIER: left.node,
DEFAULT_VALUE: right.node,
PARAMETER_NAME: paramName,
UNDEFINED: undefinedNode,
}),
);
param.replaceWith(paramName);
}
}
}
path.get("body").unshiftContainer("body", body);
return;
}
const state = {
iife: false,
scope: scope,
};
const body = [];
//
const argsIdentifier = t.identifier("arguments");
// push a default parameter definition
function pushDefNode(left, right, i) {
const defNode = buildDefaultParam({
VARIABLE_NAME: left,
DEFAULT_VALUE: right,
ARGUMENT_KEY: t.numericLiteral(i),
ARGUMENTS: argsIdentifier,
});
defNode._blockHoist = node.params.length - i;
body.push(defNode);
}
//
const lastNonDefaultParam = getFunctionArity(node);
//
for (let i = 0; i < params.length; i++) {
const param = params[i];
if (!param.isAssignmentPattern()) {
if (!state.iife && !param.isIdentifier()) {
param.traverse(iifeVisitor, state);
}
continue;
}
const left = param.get("left");
const right = param.get("right");
//
if (i >= lastNonDefaultParam || left.isPattern()) {
const placeholder = scope.generateUidIdentifier("x");
placeholder._isDefaultPlaceholder = true;
node.params[i] = placeholder;
} else {
node.params[i] = left.node;
}
//
if (!state.iife) {
if (right.isIdentifier() && !isSafeBinding(scope, right.node)) {
// the right hand side references a parameter
state.iife = true;
} else {
right.traverse(iifeVisitor, state);
}
}
pushDefNode(left.node, right.node, i);
}
// add declarations for trailing parameters
for (let i = lastNonDefaultParam + 1; i < node.params.length; i++) {
const param = node.params[i];
if (param._isDefaultPlaceholder) continue;
const declar = buildCutOff(param, argsIdentifier, t.numericLiteral(i));
declar._blockHoist = node.params.length - i;
body.push(declar);
}
// we need to cut off all trailing parameters
node.params = node.params.slice(0, lastNonDefaultParam);
if (state.iife) {
body.push(callDelegate(path, scope));
path.set("body", t.blockStatement(body));
} else {
path.get("body").unshiftContainer("body", body);
}
},
};

View File

@ -1,29 +0,0 @@
import * as t from "babel-types";
export const visitor = {
Function(path) {
const params: Array = path.get("params");
// If there's a rest param, no need to loop through it. Also, we need to
// hoist one more level to get `declar` at the right spot.
const hoistTweak = t.isRestElement(params[params.length - 1]) ? 1 : 0;
const outputParamsLength = params.length - hoistTweak;
for (let i = 0; i < outputParamsLength; i++) {
const param = params[i];
if (param.isArrayPattern() || param.isObjectPattern()) {
const uid = path.scope.generateUidIdentifier("ref");
const declar = t.variableDeclaration("let", [
t.variableDeclarator(param.node, uid),
]);
declar._blockHoist = outputParamsLength - i;
path.ensureBlock();
path.get("body").unshiftContainer("body", declar);
param.replaceWith(uid);
}
}
},
};

View File

@ -1,32 +1,28 @@
import type { NodePath } from "babel-traverse";
import { visitors } from "babel-traverse";
import * as destructuring from "./destructuring";
import * as def from "./default";
import * as rest from "./rest";
import convertFunctionParams from "./params";
import convertFunctionRest from "./rest";
export default function() {
return {
visitor: visitors.merge([
{
ArrowFunctionExpression(path) {
// In some conversion cases, it may have already been converted to a function while this callback
// was queued up.
if (!path.isArrowFunctionExpression()) return;
visitor: {
Function(path) {
if (
path.isArrowFunctionExpression() &&
path
.get("params")
.some(param => param.isRestElement() || param.isAssignmentPattern())
) {
// default/rest visitors require access to `arguments`, so it cannot be an arrow
path.arrowFunctionToExpression();
}
// default/rest visitors require access to `arguments`
const params: Array<NodePath> = path.get("params");
for (const param of params) {
if (param.isRestElement() || param.isAssignmentPattern()) {
path.arrowFunctionToExpression();
break;
}
}
},
const convertedRest = convertFunctionRest(path);
const convertedParams = convertFunctionParams(path, this.opts.loose);
if (convertedRest || convertedParams) {
// Manually reprocess this scope to ensure that the moved params are updated.
path.scope.crawl();
}
},
destructuring.visitor,
rest.visitor,
def.visitor,
]),
},
};
}

View File

@ -0,0 +1,162 @@
import callDelegate from "babel-helper-call-delegate";
import template from "babel-template";
import * as t from "babel-types";
const buildDefaultParam = template(`
let VARIABLE_NAME =
arguments.length > ARGUMENT_KEY && arguments[ARGUMENT_KEY] !== undefined ?
arguments[ARGUMENT_KEY]
:
DEFAULT_VALUE;
`);
const buildLooseDefaultParam = template(`
if (ASSIGNMENT_IDENTIFIER === UNDEFINED) {
ASSIGNMENT_IDENTIFIER = DEFAULT_VALUE;
}
`);
const buildLooseDestructuredDefaultParam = template(`
let ASSIGNMENT_IDENTIFIER = PARAMETER_NAME === UNDEFINED ? DEFAULT_VALUE : PARAMETER_NAME ;
`);
const buildArgumentsAccess = template(`
let $0 = arguments[$1];
`);
function isSafeBinding(scope, node) {
if (!scope.hasOwnBinding(node.name)) return true;
const { kind } = scope.getOwnBinding(node.name);
return kind === "param" || kind === "local";
}
const iifeVisitor = {
ReferencedIdentifier(path, state) {
const { scope, node } = path;
if (node.name === "eval" || !isSafeBinding(scope, node)) {
state.iife = true;
path.stop();
}
},
Scope(path) {
// different bindings
path.skip();
},
};
export default function convertFunctionParams(path, loose) {
const { node, scope } = path;
const state = {
iife: false,
scope: scope,
};
const body = [];
let firstOptionalIndex = null;
//
const params = path.get("params");
if (loose) {
const body = [];
for (let i = 0; i < params.length; ++i) {
const param = params[i];
if (param.isAssignmentPattern()) {
const left = param.get("left");
const right = param.get("right");
const undefinedNode = scope.buildUndefinedNode();
if (left.isIdentifier()) {
body.push(
buildLooseDefaultParam({
ASSIGNMENT_IDENTIFIER: left.node,
DEFAULT_VALUE: right.node,
UNDEFINED: undefinedNode,
}),
);
param.replaceWith(left.node);
} else if (left.isObjectPattern() || left.isArrayPattern()) {
const paramName = scope.generateUidIdentifier();
body.push(
buildLooseDestructuredDefaultParam({
ASSIGNMENT_IDENTIFIER: left.node,
DEFAULT_VALUE: right.node,
PARAMETER_NAME: paramName,
UNDEFINED: undefinedNode,
}),
);
param.replaceWith(paramName);
}
}
}
path.get("body").unshiftContainer("body", body);
return;
}
for (let i = 0; i < params.length; i++) {
const param = params[i];
if (param.isAssignmentPattern()) {
if (firstOptionalIndex === null) firstOptionalIndex = i;
const left = param.get("left");
const right = param.get("right");
//
if (!state.iife) {
if (right.isIdentifier() && !isSafeBinding(scope, right.node)) {
// the right hand side references a parameter
state.iife = true;
} else {
right.traverse(iifeVisitor, state);
}
}
const defNode = buildDefaultParam({
VARIABLE_NAME: left.node,
DEFAULT_VALUE: right.node,
ARGUMENT_KEY: t.numericLiteral(i),
});
body.push(defNode);
} else if (firstOptionalIndex !== null) {
const defNode = buildArgumentsAccess(param.node, t.numericLiteral(i));
body.push(defNode);
} else if (param.isObjectPattern() || param.isArrayPattern()) {
const uid = path.scope.generateUidIdentifier("ref");
const defNode = t.variableDeclaration("let", [
t.variableDeclarator(param.node, uid),
]);
body.push(defNode);
param.replaceWith(uid);
}
if (!state.iife && !param.isIdentifier()) {
param.traverse(iifeVisitor, state);
}
}
if (body.length === 0) return false;
// we need to cut off all trailing parameters
if (firstOptionalIndex !== null) {
node.params = node.params.slice(0, firstOptionalIndex);
}
// ensure it's a block, useful for arrow functions
path.ensureBlock();
if (state.iife) {
body.push(callDelegate(path, scope));
path.set("body", t.blockStatement(body));
} else {
path.get("body").unshiftContainer("body", body);
}
return true;
}

View File

@ -220,124 +220,120 @@ function optimiseLengthGetter(path, argsId, offset) {
}
}
export const visitor = {
Function(path) {
const { node, scope } = path;
if (!hasRest(node)) return;
export default function convertFunctionRest(path) {
const { node, scope } = path;
if (!hasRest(node)) return false;
const rest = node.params.pop().argument;
const rest = node.params.pop().argument;
const argsId = t.identifier("arguments");
const argsId = t.identifier("arguments");
// check and optimise for extremely common cases
const state = {
references: [],
offset: node.params.length,
// check and optimise for extremely common cases
const state = {
references: [],
offset: node.params.length,
argumentsNode: argsId,
outerBinding: scope.getBindingIdentifier(rest.name),
argumentsNode: argsId,
outerBinding: scope.getBindingIdentifier(rest.name),
// candidate member expressions we could optimise if there are no other references
candidates: [],
// candidate member expressions we could optimise if there are no other references
candidates: [],
// local rest binding name
name: rest.name,
// local rest binding name
name: rest.name,
/*
It may be possible to optimize the output code in certain ways, such as
not generating code to initialize an array (perhaps substituting direct
references to arguments[i] or arguments.length for reads of the
corresponding rest parameter property) or positioning the initialization
code so that it may not have to execute depending on runtime conditions.
/*
It may be possible to optimize the output code in certain ways, such as
not generating code to initialize an array (perhaps substituting direct
references to arguments[i] or arguments.length for reads of the
corresponding rest parameter property) or positioning the initialization
code so that it may not have to execute depending on runtime conditions.
This property tracks eligibility for optimization. "deopted" means give up
and don't perform optimization. For example, when any of rest's elements /
properties is assigned to at the top level, or referenced at all in a
nested function.
*/
deopted: false,
};
This property tracks eligibility for optimization. "deopted" means give up
and don't perform optimization. For example, when any of rest's elements /
properties is assigned to at the top level, or referenced at all in a
nested function.
*/
deopted: false,
};
path.traverse(memberExpressionOptimisationVisitor, state);
path.traverse(memberExpressionOptimisationVisitor, state);
// There are only "shorthand" references
if (!state.deopted && !state.references.length) {
for (const { path, cause } of (state.candidates: Array)) {
switch (cause) {
case "indexGetter":
optimiseIndexGetter(path, argsId, state.offset);
break;
case "lengthGetter":
optimiseLengthGetter(path, argsId, state.offset);
break;
default:
path.replaceWith(argsId);
}
// There are only "shorthand" references
if (!state.deopted && !state.references.length) {
for (const { path, cause } of (state.candidates: Array)) {
switch (cause) {
case "indexGetter":
optimiseIndexGetter(path, argsId, state.offset);
break;
case "lengthGetter":
optimiseLengthGetter(path, argsId, state.offset);
break;
default:
path.replaceWith(argsId);
}
return;
}
return true;
}
state.references = state.references.concat(
state.candidates.map(({ path }) => path),
state.references = state.references.concat(
state.candidates.map(({ path }) => path),
);
const start = t.numericLiteral(node.params.length);
const key = scope.generateUidIdentifier("key");
const len = scope.generateUidIdentifier("len");
let arrKey = key;
let arrLen = len;
if (node.params.length) {
// this method has additional params, so we need to subtract
// the index of the current argument position from the
// position in the array that we want to populate
arrKey = t.binaryExpression("-", key, start);
// we need to work out the size of the array that we're
// going to store all the rest parameters
//
// we need to add a check to avoid constructing the array
// with <0 if there are less arguments than params as it'll
// cause an error
arrLen = t.conditionalExpression(
t.binaryExpression(">", len, start),
t.binaryExpression("-", len, start),
t.numericLiteral(0),
);
}
const start = t.numericLiteral(node.params.length);
const key = scope.generateUidIdentifier("key");
const len = scope.generateUidIdentifier("len");
const loop = buildRest({
ARGUMENTS: argsId,
ARRAY_KEY: arrKey,
ARRAY_LEN: arrLen,
START: start,
ARRAY: rest,
KEY: key,
LEN: len,
});
let arrKey = key;
let arrLen = len;
if (node.params.length) {
// this method has additional params, so we need to subtract
// the index of the current argument position from the
// position in the array that we want to populate
arrKey = t.binaryExpression("-", key, start);
if (state.deopted) {
node.body.body.unshift(loop);
} else {
let target = path
.getEarliestCommonAncestorFrom(state.references)
.getStatementParent();
// we need to work out the size of the array that we're
// going to store all the rest parameters
//
// we need to add a check to avoid constructing the array
// with <0 if there are less arguments than params as it'll
// cause an error
arrLen = t.conditionalExpression(
t.binaryExpression(">", len, start),
t.binaryExpression("-", len, start),
t.numericLiteral(0),
);
}
const loop = buildRest({
ARGUMENTS: argsId,
ARRAY_KEY: arrKey,
ARRAY_LEN: arrLen,
START: start,
ARRAY: rest,
KEY: key,
LEN: len,
// don't perform the allocation inside a loop
target.findParent(path => {
if (path.isLoop()) {
target = path;
} else {
// Stop crawling up if this is a function.
return path.isFunction();
}
});
if (state.deopted) {
loop._blockHoist = node.params.length + 1;
node.body.body.unshift(loop);
} else {
// perform allocation at the lowest common ancestor of all references
loop._blockHoist = 1;
target.insertBefore(loop);
}
let target = path
.getEarliestCommonAncestorFrom(state.references)
.getStatementParent();
// don't perform the allocation inside a loop
target.findParent(path => {
if (path.isLoop()) {
target = path;
} else {
// Stop crawling up if this is a function.
return path.isFunction();
}
});
target.insertBefore(loop);
}
},
};
return true;
}

View File

@ -0,0 +1,8 @@
function fn(
a1,
a2 = 4,
{a3, a4},
a5,
{a6, a7} = {}) {
}

View File

@ -0,0 +1,11 @@
function fn(a1) {
var a2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 4;
var _arguments$ = arguments[2],
a3 = _arguments$.a3,
a4 = _arguments$.a4;
var a5 = arguments[3];
var _ref = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {},
a6 = _ref.a6,
a7 = _ref.a7;
}

View File

@ -1,9 +1,9 @@
// #3861
function t() {
var x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "default";
var _ref = arguments[1];
var a = _ref.a,
b = _ref.b;
var _arguments$ = arguments[1],
a = _arguments$.a,
b = _arguments$.b;
for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
args[_key - 2] = arguments[_key];

View File

@ -28,7 +28,7 @@ function d(thing) {
};
{
var _args = thing;
var args = thing;
foo(thing);
}
}

View File

@ -113,7 +113,6 @@ export default function({ types: t }) {
const declar = t.variableDeclaration("let", [
t.variableDeclarator(paramPath.node, uid),
]);
declar._blockHoist = i ? numParams - i : 1;
parentPath.ensureBlock();
parentPath.get("body").unshiftContainer("body", declar);
@ -129,7 +128,7 @@ export default function({ types: t }) {
// function a({ b, ...c }) {}
Function(path) {
const params = path.get("params");
for (let i = 0; i < params.length; i++) {
for (let i = params.length - 1; i >= 0; i--) {
replaceRestElement(params[i].parentPath, params[i], i, params.length);
}
},

View File

@ -17,15 +17,15 @@ function a3(_ref3) {
c2 = babelHelpers.objectWithoutProperties(_ref3, ["a2", "b2"]);
}
function a4(_ref4, _ref5) {
let {
a5
} = _ref5,
c5 = babelHelpers.objectWithoutProperties(_ref5, ["a5"]);
function a4(_ref5, _ref4) {
let {
a3
} = _ref5,
c3 = babelHelpers.objectWithoutProperties(_ref5, ["a3"]);
let {
a5
} = _ref4,
c3 = babelHelpers.objectWithoutProperties(_ref4, ["a3"]);
c5 = babelHelpers.objectWithoutProperties(_ref4, ["a5"]);
}
function a5(_ref6) {

View File

@ -1,7 +1,6 @@
function render(Component) {
var text = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
var _ref = <Component text={text} />;
var text = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '',
_ref = <Component text={text} />;
return function () {
return _ref;

View File

@ -5,11 +5,23 @@ export { default as Identifier } from "./inferer-reference";
export function VariableDeclarator() {
const id = this.get("id");
if (id.isIdentifier()) {
return this.get("init").getTypeAnnotation();
} else {
return;
if (!id.isIdentifier()) return;
const init = this.get("init");
let type = init.getTypeAnnotation();
if (type && type.type === "AnyTypeAnnotation") {
// Detect "var foo = Array()" calls so we can optimize for arrays vs iterables.
if (
init.isCallExpression() &&
init.get("callee").isIdentifier({ name: "Array" }) &&
!init.scope.hasBinding("Array", true /* noGlobals */)
) {
type = ArrayExpression();
}
}
return type;
}
export function TypeCastExpression(node) {