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 {
fooBoxTest();
checkFooBoxIsOk();
}
public void testKT921() throws Exception {
@@ -50,7 +50,7 @@ public final class NativeInteropTest extends SingleFileTranslationTest {
}
public void testVararg() throws Exception {
fooBoxTest();
checkFooBoxIsOk();
}
public void testUndefined() throws Exception {
@@ -16,7 +16,8 @@
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.Nullable;
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.JetExpression;
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.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.util.ExpressionAsFunctionDescriptor;
import org.jetbrains.k2js.translate.context.TemporaryVariable;
import org.jetbrains.k2js.translate.context.TranslationContext;
import org.jetbrains.k2js.translate.general.Translation;
import org.jetbrains.k2js.translate.utils.AnnotationsUtils;
@@ -52,6 +56,11 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
}
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,
@Nullable JsExpression receiver,
@@ -62,15 +71,23 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
@NotNull
private JsExpression translate() {
prepareToBuildCall();
return CallBuilder.build(context())
.receiver(getReceiver())
.callee(getCalleeExpression())
.args(translateArguments())
.receiver(translatedReceiver)
.callee(translatedCallee)
.args(translatedArguments)
.resolvedCall(getResolvedCall())
.type(callType)
.translate();
}
private void prepareToBuildCall() {
translatedArguments = translateArguments();
translatedReceiver = getReceiver();
translatedCallee = getCalleeExpression();
}
@NotNull
private ResolvedCall<?> getResolvedCall() {
if (resolvedCall instanceof VariableAsFunctionResolvedCall) {
@@ -81,14 +98,23 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
@Nullable
private JsExpression getReceiver() {
if (receiver != null) {
return receiver;
assert translatedArguments != null : "the results of this function depends on the results of translateArguments()";
if (receiver == null) {
return null;
}
return null;
if (cachedReceiver != null) {
return cachedReceiver.assignmentExpression();
}
return receiver;
}
@Nullable
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();
if (candidateDescriptor instanceof ExpressionAsFunctionDescriptor) {
return translateExpressionAsFunction();
@@ -118,15 +144,47 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
@NotNull
private List<JsExpression> translateArguments() {
List<JsExpression> result = new ArrayList<JsExpression>();
List<JsExpression> argsBeforeVararg = null;
for (ValueParameterDescriptor parameterDescriptor : resolvedCall.getResultingDescriptor().getValueParameters()) {
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));
}
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;
}
@Override
public boolean shouldWrapVarargInArray() {
return !isNativeFunctionCall;
return !isNativeFunctionCall && !hasSpreadOperator;
}
}
@@ -13,5 +13,57 @@ fun testSum(expectedSum : Int, vararg i : Int) : Boolean {
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) &&
testSum(30, 10, 20, 0)
fun testSpreadOperator(vararg args : Int): Boolean {
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
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 {
if (paramCount(1, 2 ,3) != 3) {
return false;
}
if (paramCount() != 0) {
return false;
}
if (count() != 0) {
return false;
}
if (count(1, 1, 1, 1) != 4) {
return false;
}
return true;
native
class Bar(val size: Int, order: Int = 0) {
fun test(order: Int, dummy: Int, vararg args: Int): Boolean = js.noImpl
class object {
fun startNewTest(): Boolean = js.noImpl
var hasOrderProblem: Boolean = false
}
}
native
object obj {
fun test(size: Int, vararg args: Int): Boolean = js.noImpl
}
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() {
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);
}