JS: do not extract expressions, when inline call can be expression

This commit is contained in:
Alexey Tsvetkov
2015-04-14 18:12:25 +03:00
parent bf5300a849
commit ff5090136c
4 changed files with 74 additions and 16 deletions
@@ -224,6 +224,10 @@ class FunctionInlineMutator {
return !thisRefs.isEmpty(); return !thisRefs.isEmpty();
} }
public static boolean canBeExpression(JsFunction function) {
return canBeExpression(function.getBody());
}
private static boolean canBeExpression(JsBlock body) { private static boolean canBeExpression(JsBlock body) {
List<JsStatement> statements = body.getStatements(); List<JsStatement> statements = body.getStatements();
return statements.size() == 1 && statements.get(0) instanceof JsReturn; return statements.size() == 1 && statements.get(0) instanceof JsReturn;
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.resolve.inline.InlineStrategy;
import java.util.*; import java.util.*;
import kotlin.Function1; import kotlin.Function1;
import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.canBeExpression;
import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.getInlineableCallReplacement; import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.getInlineableCallReplacement;
import static org.jetbrains.kotlin.js.inline.clean.CleanPackage.removeUnusedFunctionDefinitions; import static org.jetbrains.kotlin.js.inline.clean.CleanPackage.removeUnusedFunctionDefinitions;
import static org.jetbrains.kotlin.js.inline.clean.CleanPackage.removeUnusedLocalFunctionDeclarations; import static org.jetbrains.kotlin.js.inline.clean.CleanPackage.removeUnusedLocalFunctionDeclarations;
@@ -55,7 +56,13 @@ public class JsInliner extends JsVisitorWithContextImpl {
if (!(node instanceof JsInvocation)) return false; if (!(node instanceof JsInvocation)) return false;
JsInvocation call = (JsInvocation) node; JsInvocation call = (JsInvocation) node;
return hasToBeInlined(call);
if (hasToBeInlined(call)) {
JsFunction function = getFunctionContext().getFunctionDefinition(call);
return !canBeExpression(function);
}
return false;
} }
}; };
@@ -16,10 +16,7 @@
package org.jetbrains.kotlin.js.test.utils; package org.jetbrains.kotlin.js.test.utils;
import com.google.dart.compiler.backend.js.ast.JsExpression; import com.google.dart.compiler.backend.js.ast.*;
import com.google.dart.compiler.backend.js.ast.JsFunction;
import com.google.dart.compiler.backend.js.ast.JsLabel;
import com.google.dart.compiler.backend.js.ast.JsNode;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata; import org.jetbrains.kotlin.js.translate.expression.InlineMetadata;
@@ -94,29 +91,57 @@ public class DirectiveTestUtils {
} }
}; };
private static final DirectiveHandler COUNT_LABELS = new DirectiveHandler("CHECK_LABELS_COUNT") { private abstract static class CountNodesDirective<T extends JsNode> extends DirectiveHandler {
@NotNull
private final Class<T> klass;
CountNodesDirective(@NotNull String directive, @NotNull Class<T> klass) {
super(directive);
this.klass = klass;
}
@Override @Override
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception { void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
String functionName = arguments.getNamedArgument("function"); String functionName = arguments.getNamedArgument("function");
String labelName = arguments.getNamedArgument("name");
String countStr = arguments.getNamedArgument("count"); String countStr = arguments.getNamedArgument("count");
int expectedCount = Integer.valueOf(countStr); int expectedCount = Integer.valueOf(countStr);
JsNode scope = AstSearchUtil.getFunction(ast, functionName); JsNode scope = AstSearchUtil.getFunction(ast, functionName);
List<JsLabel> labels = collectInstances(JsLabel.class, scope); List<T> nodes = collectInstances(klass, scope);
int actualCount = 0; int actualCount = 0;
for (JsLabel label : labels) {
if (label.getName().getIdent().equals(labelName)) { for (T node : nodes) {
actualCount++; actualCount += getActualCountFor(node, arguments);
}
} }
String message = "Label " + labelName + String message = "Function " + functionName + " contains " + actualCount +
" is expected to be counted " + expectedCount + " nodes of type " + klass.getName() +
" times at function " + functionName + ", but expected count is " + expectedCount;
" but was encountered " + actualCount + " times";
assertEquals(message, expectedCount, actualCount); assertEquals(message, expectedCount, actualCount);
} }
protected abstract int getActualCountFor(@NotNull T node, @NotNull ArgumentsHelper arguments);
}
private static final DirectiveHandler COUNT_LABELS = new CountNodesDirective<JsLabel>("CHECK_LABELS_COUNT", JsLabel.class) {
@Override
protected int getActualCountFor(@NotNull JsLabel node, @NotNull ArgumentsHelper arguments) {
String labelName = arguments.getNamedArgument("name");
if (node.getName().getIdent().equals(labelName)) {
return 1;
}
return 0;
}
};
private static final DirectiveHandler COUNT_VARS = new CountNodesDirective<JsVars>("CHECK_VARS_COUNT", JsVars.class) {
@Override
protected int getActualCountFor(@NotNull JsVars node, @NotNull ArgumentsHelper arguments) {
return node.getVars().size();
}
}; };
private static final DirectiveHandler HAS_INLINE_METADATA = new DirectiveHandler("CHECK_HAS_INLINE_METADATA") { private static final DirectiveHandler HAS_INLINE_METADATA = new DirectiveHandler("CHECK_HAS_INLINE_METADATA") {
@@ -146,6 +171,7 @@ public class DirectiveTestUtils {
FUNCTION_NOT_CALLED_IN_SCOPE.process(ast, sourceCode); FUNCTION_NOT_CALLED_IN_SCOPE.process(ast, sourceCode);
FUNCTIONS_HAVE_SAME_LINES.process(ast, sourceCode); FUNCTIONS_HAVE_SAME_LINES.process(ast, sourceCode);
COUNT_LABELS.process(ast, sourceCode); COUNT_LABELS.process(ast, sourceCode);
COUNT_VARS.process(ast, sourceCode);
HAS_INLINE_METADATA.process(ast, sourceCode); HAS_INLINE_METADATA.process(ast, sourceCode);
HAS_NO_INLINE_METADATA.process(ast, sourceCode); HAS_NO_INLINE_METADATA.process(ast, sourceCode);
} }
@@ -0,0 +1,21 @@
package foo
// CHECK_CONTAINS_NO_CALLS: test
// COUNT_VARS: function=test count=0
// A copy of stdlib run function.
// Copied to not to depend on run implementation.
// It's important, that the body is just `return fn()`.
inline fun evaluate<T>(fn: ()->T): T = fn()
fun test(x: Int): Int =
evaluate {
evaluate { 2 } * evaluate { x }
}
fun box(): String {
assertEquals(6, test(3))
assertEquals(8, test(4))
return "OK"
}