* to be functions. Otherwise, we can't know what statements the
* invocation would actually invoke. The static reference would be null
* when trying operate on references to external functions, or functions
* as arguments to another function.
*/
JsFunction f = isFunction(x.getQualifier());
if (f == null) {
return;
}
// Don't inline blacklisted functions
if (blacklist.contains(f)) {
return;
}
List<JsName> localVariableNames = new ArrayList<JsName>();
List<JsStatement> statements = new ArrayList<JsStatement>(
f.getBody().getStatements());
List<JsExpression> hoisted = new ArrayList<JsExpression>(
statements.size());
boolean sawReturnStatement = false;
for (JsStatement statement : statements) {
if (sawReturnStatement) {
/*
* We've already seen a return statement, but there are still more
* statements. The target is unsafe to inline, so bail. Note: in most
* cases JsStaticEval will have removed any statements following a
* return statement.
*
* The reason we have to bail is that the return statement's
* expression MUST be the last thing evaluated.
*
* TODO(bobv): maybe it could still be inlined with smart
* transformation?
*/
return;
}
/*
* Create replacement expressions to use in place of the original
* statements. It is important that the replacement is newly-minted and
* therefore not referenced by any other AST nodes. Consider the case of
* a common, delegating function. If the hoisted expressions were not
* distinct objects, it would not be possible to substitute different
* JsNameRefs at different call sites.
*/
JsExpression h = hoistedExpression(program, statement,
localVariableNames);
if (h == null) {
return;
}
hoisted.add(h);
if (isReturnStatement(statement)) {
sawReturnStatement = true;
}
}
/*
* If the inlined method has no return statement, synthesize an undefined
* reference. It will be reclaimed if the method call is from a
* JsExprStmt.
*/
if (!sawReturnStatement) {
hoisted.add(program.getUndefinedLiteral());
}
assert (hoisted.size() > 0);
/*
* Build up the new comma expression from right-to-left; building the
* rightmost comma expressions first. Bootstrapping with i.previous()
* ensures that this logic will function correctly in the case of a single
* expression.
*/
ListIterator<JsExpression> i = hoisted.listIterator(hoisted.size());
JsExpression op = i.previous();
while (i.hasPrevious()) {
JsBinaryOperation outerOp = new JsBinaryOperation(
JsBinaryOperator.COMMA);
outerOp.setArg1(i.previous());
outerOp.setArg2(op);
op = outerOp;
}
// Confirm that the expression conforms to the desired heuristics
if (!isInlinable(program, functionStack.peek(), x, f, op)) {
return;
}
// Perform the name replacement
NameRefReplacerVisitor v = new NameRefReplacerVisitor(x, f);
for (ListIterator<JsName> nameIterator = localVariableNames.listIterator(); nameIterator.hasNext();) {
JsName name = nameIterator.next();
/*
* Find an unused identifier in the caller's scope. It's possible that
* the same function has been inlined in multiple places within the
* function so we'll use a counter for disambiguation.
*/
String ident;
int count = 0;
JsScope scope = functionStack.peek().getScope();
do {
ident = f.getName() + "_" + name.getIdent() + "_" + count++;
} while (scope.findExistingName(ident) != null);
JsName newName = scope.declareName(ident, name.getShortIdent());
v.setReplacementName(name, newName);
nameIterator.set(newName);