Added support spread operator in JS-Backend.

This commit is contained in:
Zalim Bashorov
2013-06-05 20:52:59 +04:00
parent 2bae647412
commit ee5c7c8f5c
6 changed files with 239 additions and 28 deletions
@@ -85,7 +85,7 @@ public class FunctionTest extends AbstractExpressionTest {
public void testVararg() throws Exception { public void testVararg() throws Exception {
fooBoxTest(); checkFooBoxIsOk();
} }
public void testKT921() throws Exception { public void testKT921() throws Exception {
@@ -50,7 +50,7 @@ public final class NativeInteropTest extends SingleFileTranslationTest {
} }
public void testVararg() throws Exception { public void testVararg() throws Exception {
fooBoxTest(); checkFooBoxIsOk();
} }
public void testUndefined() throws Exception { public void testUndefined() throws Exception {
@@ -16,7 +16,8 @@
package org.jetbrains.k2js.translate.reference; package org.jetbrains.k2js.translate.reference;
import com.google.dart.compiler.backend.js.ast.JsExpression; import com.google.dart.compiler.backend.js.ast.*;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.CallableDescriptor; import org.jetbrains.jet.lang.descriptors.CallableDescriptor;
@@ -24,10 +25,13 @@ import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.psi.JetCallExpression; import org.jetbrains.jet.lang.psi.JetCallExpression;
import org.jetbrains.jet.lang.psi.JetExpression; import org.jetbrains.jet.lang.psi.JetExpression;
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; import org.jetbrains.jet.lang.psi.JetSimpleNameExpression;
import org.jetbrains.jet.lang.resolve.calls.util.ExpressionAsFunctionDescriptor; import org.jetbrains.jet.lang.psi.ValueArgument;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument; import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument;
import org.jetbrains.jet.lang.resolve.calls.model.VarargValueArgument;
import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall; import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall;
import org.jetbrains.jet.lang.resolve.calls.util.ExpressionAsFunctionDescriptor;
import org.jetbrains.k2js.translate.context.TemporaryVariable;
import org.jetbrains.k2js.translate.context.TranslationContext; import org.jetbrains.k2js.translate.context.TranslationContext;
import org.jetbrains.k2js.translate.general.Translation; import org.jetbrains.k2js.translate.general.Translation;
import org.jetbrains.k2js.translate.utils.AnnotationsUtils; import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
@@ -52,6 +56,11 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
} }
private final boolean isNativeFunctionCall; private final boolean isNativeFunctionCall;
private boolean hasSpreadOperator = false;
private TemporaryVariable cachedReceiver = null;
private List<JsExpression> translatedArguments = null;
private JsExpression translatedReceiver = null;
private JsExpression translatedCallee = null;
private CallExpressionTranslator(@NotNull JetCallExpression expression, private CallExpressionTranslator(@NotNull JetCallExpression expression,
@Nullable JsExpression receiver, @Nullable JsExpression receiver,
@@ -62,15 +71,23 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
@NotNull @NotNull
private JsExpression translate() { private JsExpression translate() {
prepareToBuildCall();
return CallBuilder.build(context()) return CallBuilder.build(context())
.receiver(getReceiver()) .receiver(translatedReceiver)
.callee(getCalleeExpression()) .callee(translatedCallee)
.args(translateArguments()) .args(translatedArguments)
.resolvedCall(getResolvedCall()) .resolvedCall(getResolvedCall())
.type(callType) .type(callType)
.translate(); .translate();
} }
private void prepareToBuildCall() {
translatedArguments = translateArguments();
translatedReceiver = getReceiver();
translatedCallee = getCalleeExpression();
}
@NotNull @NotNull
private ResolvedCall<?> getResolvedCall() { private ResolvedCall<?> getResolvedCall() {
if (resolvedCall instanceof VariableAsFunctionResolvedCall) { if (resolvedCall instanceof VariableAsFunctionResolvedCall) {
@@ -81,14 +98,23 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
@Nullable @Nullable
private JsExpression getReceiver() { private JsExpression getReceiver() {
if (receiver != null) { assert translatedArguments != null : "the results of this function depends on the results of translateArguments()";
return receiver; if (receiver == null) {
return null;
} }
return null; if (cachedReceiver != null) {
return cachedReceiver.assignmentExpression();
}
return receiver;
} }
@Nullable @Nullable
private JsExpression getCalleeExpression() { private JsExpression getCalleeExpression() {
assert translatedArguments != null : "the results of this function depends on the results of translateArguments()";
if (isNativeFunctionCall && hasSpreadOperator) {
String functionName = resolvedCall.getCandidateDescriptor().getOriginal().getName().getIdentifier();
return new JsNameRef("apply", functionName);
}
CallableDescriptor candidateDescriptor = resolvedCall.getCandidateDescriptor(); CallableDescriptor candidateDescriptor = resolvedCall.getCandidateDescriptor();
if (candidateDescriptor instanceof ExpressionAsFunctionDescriptor) { if (candidateDescriptor instanceof ExpressionAsFunctionDescriptor) {
return translateExpressionAsFunction(); return translateExpressionAsFunction();
@@ -118,15 +144,47 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
@NotNull @NotNull
private List<JsExpression> translateArguments() { private List<JsExpression> translateArguments() {
List<JsExpression> result = new ArrayList<JsExpression>(); List<JsExpression> result = new ArrayList<JsExpression>();
List<JsExpression> argsBeforeVararg = null;
for (ValueParameterDescriptor parameterDescriptor : resolvedCall.getResultingDescriptor().getValueParameters()) { for (ValueParameterDescriptor parameterDescriptor : resolvedCall.getResultingDescriptor().getValueParameters()) {
ResolvedValueArgument actualArgument = resolvedCall.getValueArgumentsByIndex().get(parameterDescriptor.getIndex()); ResolvedValueArgument actualArgument = resolvedCall.getValueArgumentsByIndex().get(parameterDescriptor.getIndex());
if (actualArgument instanceof VarargValueArgument) {
assert !hasSpreadOperator;
List<ValueArgument> arguments = actualArgument.getArguments();
hasSpreadOperator = arguments.size() == 1 && arguments.get(0).getSpreadElement() != null;
if (isNativeFunctionCall && hasSpreadOperator) {
assert argsBeforeVararg == null;
argsBeforeVararg = result;
result = new SmartList<JsExpression>();
}
}
result.addAll(translateSingleArgument(actualArgument, parameterDescriptor)); result.addAll(translateSingleArgument(actualArgument, parameterDescriptor));
} }
if (isNativeFunctionCall && hasSpreadOperator) {
assert argsBeforeVararg != null;
if (!argsBeforeVararg.isEmpty()) {
JsInvocation concatArguments = new JsInvocation(new JsNameRef("concat", new JsArrayLiteral(argsBeforeVararg)), result);
result = new SmartList<JsExpression>(concatArguments);
}
if (receiver != null) {
cachedReceiver = context().declareTemporary(receiver);
result.add(0, cachedReceiver.reference());
}
else {
result.add(0, JsLiteral.NULL);
}
}
return result; return result;
} }
@Override @Override
public boolean shouldWrapVarargInArray() { public boolean shouldWrapVarargInArray() {
return !isNativeFunctionCall; return !isNativeFunctionCall && !hasSpreadOperator;
} }
} }
@@ -13,5 +13,57 @@ fun testSum(expectedSum : Int, vararg i : Int) : Boolean {
return (expectedSum == sum) return (expectedSum == sum)
} }
fun box() = testSize(0) && testSum(0) && testSize(3, 1, 1, 1) && testSum(3, 1, 1, 1) && testSize(6, 1, 1, 1, 2, 3, 4) && fun testSpreadOperator(vararg args : Int): Boolean {
testSum(30, 10, 20, 0) var sum = 0;
for (a in args) sum += a
return testSize(args.size, *args) && testSum(sum, *args)
}
class Bar(val size: Int, val sum: Int) {
fun test(vararg args : Int) = testSize(size, *args) && testSum(sum, *args)
}
object obj {
fun test(size: Int, sum: Int, vararg args: Int) = testSize(size, *args) && testSum(sum, *args)
}
fun spreadInMethodCall(size: Int, sum: Int, vararg args: Int) = Bar(size, sum).test(*args)
fun spreadInObjectMethodCall(size: Int, sum: Int, vararg args: Int) = obj.test(size, sum, *args)
fun testVarargWithFunLit(vararg args: Int, f: (a: IntArray) -> Boolean): Boolean = f(args)
fun box(): String {
if (!testSize(0))
return "wrong vararg size when call function without args"
if (!testSum(0))
return "wrong vararg sum (arguments) when call function without args"
if (!testSize(6, 1, 1, 1, 2, 3, 4))
return "wrong vararg size when call function with some args (1)"
if (!testSum(30, 10, 20, 0))
return "wrong vararg sum (arguments) when call function with some args (1)"
if (!testSpreadOperator(30, 10, 20, 0))
return "failed when call function using spread operator"
if (!Bar(3, 30).test(10, 20, 0))
return "failed when call method"
if (!spreadInMethodCall(2, 3, 1, 2))
return "failed when call method using spread operator"
if (!obj.test(5, 15, 1, 2, 3, 4, 5))
return "failed when call method of object"
if (!spreadInObjectMethodCall(2, 3, 1, 2))
return "failed when call method of object using spread operator"
if (!testVarargWithFunLit(1, 2, 3) { args -> args.size == 3 })
return "failed when call function with vararg and fun literal"
return "OK"
}
@@ -5,20 +5,85 @@ import js.*
native native
fun paramCount(vararg a : Int) : Int = js.noImpl fun paramCount(vararg a : Int) : Int = js.noImpl
fun count(vararg a : Int) = a.size // test spread operator
fun count(vararg a : Int) = paramCount(*a)
fun box() : Boolean { native
if (paramCount(1, 2 ,3) != 3) { class Bar(val size: Int, order: Int = 0) {
return false; fun test(order: Int, dummy: Int, vararg args: Int): Boolean = js.noImpl
} class object {
if (paramCount() != 0) { fun startNewTest(): Boolean = js.noImpl
return false; var hasOrderProblem: Boolean = false
} }
if (count() != 0) { }
return false;
} native
if (count(1, 1, 1, 1) != 4) { object obj {
return false; fun test(size: Int, vararg args: Int): Boolean = js.noImpl
} }
return true;
fun spreadInMethodCall(size: Int, vararg args: Int) = Bar(size).test(0, 1, *args)
fun spreadInObjectMethodCall(size: Int, vararg args: Int) = obj.test(size, *args)
native
fun testNativeVarargWithFunLit(vararg args: Int, f: (a: IntArray) -> Boolean): Boolean = js.noImpl
fun testSpreadOperatorWithSafeCall(a: Bar?, expected: Boolean?, vararg args: Int): Boolean {
return a?.test(0, 1, *args) == expected
}
fun testSpreadOperatorWithSureCall(a: Bar?, vararg args: Int): Boolean {
return a!!.test(0, 1, *args)
}
fun testCallOrder(vararg args: Int) =
Bar.startNewTest() &&
Bar(args.size, 0).test(1, 1, *args) && Bar(args.size, 2).test(3, 1, *args) &&
!Bar.hasOrderProblem
fun box(): String {
if (paramCount() != 0)
return "failed when call native function without args"
if (paramCount(1, 2 ,3) != 3)
return "failed when call native function with some args"
if (count() != 0)
return "failed when call native function without args using spread operator"
if (count(1, 1, 1, 1) != 4)
return "failed when call native function with some args using spread operator"
if (!Bar(5).test(0, 1, 1, 2, 3, 4, 5))
return "failed when call method with some args"
if (!spreadInMethodCall(2, 1, 2))
return "failed when call method using spread operator"
if (!(obj.test(5, 1, 2, 3, 4, 5)))
return "failed when call method of object"
if (!(spreadInObjectMethodCall(2, 1, 2)))
return "failed when call method of object using spread operator"
if (!(testNativeVarargWithFunLit(1, 2, 3) { args -> args.size == 3 }))
return "failed when call native function with vararg and fun literal"
if (!(testSpreadOperatorWithSafeCall(null, null)))
return "failed when test spread operator with SafeCall (?.) using null receiver"
if (!(testSpreadOperatorWithSafeCall(Bar(3), true, 1, 2, 3)))
return "failed when test spread operator with SafeCall (?.)"
if (!(testSpreadOperatorWithSureCall(Bar(3), 1, 2, 3)))
return "failed when test spread operator with SureCall (!!)"
if (!(testCallOrder()))
return "failed when test calling order when using spread operator without args"
if (!(testCallOrder(1, 2, 3, 4)))
return "failed when test calling order when using spread operator without args"
return "OK"
} }
@@ -16,4 +16,40 @@
function paramCount() { function paramCount() {
return arguments.length return arguments.length
}
function Bar(size, order) {
this.size = size;
Bar.checkOrder(order);
}
Bar.order = 0;
Bar.hasOrderProblem = false;
Bar.checkOrder = function (expectedOrder) {
var curOrder = Bar.order++;
Bar.hasOrderProblem = Bar.hasOrderProblem || curOrder !== expectedOrder;
};
Bar.startNewTest = function () {
Bar.hasOrderProblem = false;
Bar.order = 0;
return true;
};
Bar.prototype.test = function (order, dummy /*, args */) {
Bar.checkOrder(order);
return dummy === 1 && (arguments.length - 2) === this.size;
};
var obj = {
test : function (size /*, args */) {
return (arguments.length - 1) === size;
}
};
function testNativeVarargWithFunLit(/* args, f */) {
var args = Array.prototype.slice.call(arguments, 0, arguments.length - 1);
var f = arguments[arguments.length - 1];
return typeof f === "function" && f(args);
} }