Rewrite param processing to be more clearly defined.

This commit is contained in:
Logan Smyth
2017-05-01 16:17:55 -07:00
committed by Brian Ng
parent 99ab2b206c
commit 95882d4e5a
9 changed files with 260 additions and 351 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,23 @@
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;
}
}
},
convertFunctionRest(path);
convertFunctionParams(path);
},
destructuring.visitor,
rest.visitor,
def.visitor,
]),
},
};
}

View File

@@ -0,0 +1,115 @@
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 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) {
const { node, scope } = path;
const state = {
iife: false,
scope: scope,
};
const body = [];
let firstOptionalIndex = null;
//
const params = path.get("params");
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),
});
defNode._blockHoist = node.params.length - i;
body.push(defNode);
} else if (firstOptionalIndex !== null) {
const defNode = buildArgumentsAccess(param.node, t.numericLiteral(i));
defNode._blockHoist = node.params.length - 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),
]);
defNode._blockHoist = node.params.length - i;
body.push(defNode);
param.replaceWith(uid);
}
if (!state.iife && !param.isIdentifier()) {
param.traverse(iifeVisitor, state);
}
}
if (body.length === 0) return;
// 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);
}
}

View File

@@ -220,124 +220,122 @@ 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;
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;
}
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) {
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;
// 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),
);
}
let target = path
.getEarliestCommonAncestorFrom(state.references)
.getStatementParent();
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;
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);
}
},
};
target.insertBefore(loop);
}
}

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];