From ee5c7c8f5cedc9f0365657b2f641a5007d97533c Mon Sep 17 00:00:00 2001 From: Zalim Bashorov Date: Wed, 5 Jun 2013 20:52:59 +0400 Subject: [PATCH] Added support spread operator in JS-Backend. --- .../k2js/test/semantics/FunctionTest.java | 2 +- .../test/semantics/NativeInteropTest.java | 2 +- .../reference/CallExpressionTranslator.java | 76 +++++++++++++-- .../expression/function/cases/vararg.kt | 56 ++++++++++- .../testFiles/native/cases/vararg.kt | 95 ++++++++++++++++--- .../testFiles/native/native/vararg.js | 36 +++++++ 6 files changed, 239 insertions(+), 28 deletions(-) diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/FunctionTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/FunctionTest.java index 9a621c43758..0ee20a057e5 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/FunctionTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/FunctionTest.java @@ -85,7 +85,7 @@ public class FunctionTest extends AbstractExpressionTest { public void testVararg() throws Exception { - fooBoxTest(); + checkFooBoxIsOk(); } public void testKT921() throws Exception { diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/NativeInteropTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/NativeInteropTest.java index c736b354396..6c67e4f7f88 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/NativeInteropTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/NativeInteropTest.java @@ -50,7 +50,7 @@ public final class NativeInteropTest extends SingleFileTranslationTest { } public void testVararg() throws Exception { - fooBoxTest(); + checkFooBoxIsOk(); } public void testUndefined() throws Exception { diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallExpressionTranslator.java b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallExpressionTranslator.java index 4c2c1b249a9..d1c9cb177f6 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallExpressionTranslator.java +++ b/js/js.translator/src/org/jetbrains/k2js/translate/reference/CallExpressionTranslator.java @@ -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 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 translateArguments() { List result = new ArrayList(); + List argsBeforeVararg = null; for (ValueParameterDescriptor parameterDescriptor : resolvedCall.getResultingDescriptor().getValueParameters()) { ResolvedValueArgument actualArgument = resolvedCall.getValueArgumentsByIndex().get(parameterDescriptor.getIndex()); + + if (actualArgument instanceof VarargValueArgument) { + assert !hasSpreadOperator; + + List arguments = actualArgument.getArguments(); + hasSpreadOperator = arguments.size() == 1 && arguments.get(0).getSpreadElement() != null; + + if (isNativeFunctionCall && hasSpreadOperator) { + assert argsBeforeVararg == null; + argsBeforeVararg = result; + result = new SmartList(); + } + } + 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(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; } } diff --git a/js/js.translator/testFiles/expression/function/cases/vararg.kt b/js/js.translator/testFiles/expression/function/cases/vararg.kt index d3ab09ec173..58796e61419 100644 --- a/js/js.translator/testFiles/expression/function/cases/vararg.kt +++ b/js/js.translator/testFiles/expression/function/cases/vararg.kt @@ -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) \ No newline at end of file +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" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/native/cases/vararg.kt b/js/js.translator/testFiles/native/cases/vararg.kt index b031e3de2b3..f81532714e9 100644 --- a/js/js.translator/testFiles/native/cases/vararg.kt +++ b/js/js.translator/testFiles/native/cases/vararg.kt @@ -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" } \ No newline at end of file diff --git a/js/js.translator/testFiles/native/native/vararg.js b/js/js.translator/testFiles/native/native/vararg.js index 39626c647c8..1222cb5d625 100644 --- a/js/js.translator/testFiles/native/native/vararg.js +++ b/js/js.translator/testFiles/native/native/vararg.js @@ -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); } \ No newline at end of file