diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/InvokeConventionTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/InvokeConventionTest.java index a15222971b0..91e71db73cd 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/InvokeConventionTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/InvokeConventionTest.java @@ -23,11 +23,34 @@ public final class InvokeConventionTest extends AbstractExpressionTest { } public void testInvokeMethod() throws Exception { - // TODO uncomment this method to make the test case fail - // fooBoxIsValue("hello world!"); + fooBoxIsValue("hello world!"); } public void testExplicitInvokeLambda() throws Exception { checkFooBoxIsOk(); } + + public void testInvokeOnExprByConvention() throws Exception { + checkFooBoxIsOk(); + } + + public void testInvokeInExtensionFunctionLiteral() throws Exception { + fooBoxTest(); + } + + public void testInvokeInFunctionLiteral() throws Exception { + fooBoxTest(); + } + + public void testInvokeWithReceiverArgument() throws Exception { + fooBoxTest(); + } + + public void testInvokeWithThisObject() throws Exception { + fooBoxTest(); + } + + public void testInvokeWithThisObjectAndReceiver() throws Exception { + fooBoxTest(); + } } \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallTranslator.kt b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallTranslator.kt index d9e06099de4..140eab1370f 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallTranslator.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallTranslator.kt @@ -23,16 +23,15 @@ import com.google.dart.compiler.backend.js.ast.JsExpression import org.jetbrains.jet.lang.descriptors.VariableDescriptor import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall import org.jetbrains.k2js.translate.context.TranslationContext -import java.util.ArrayList -import org.jetbrains.k2js.facade.exceptions.UnsupportedFeatureException import org.jetbrains.jet.lang.resolve.calls.tasks.ExplicitReceiverKind.* -import com.google.dart.compiler.backend.js.ast.JsNameRef -import org.jetbrains.k2js.translate.utils.JsAstUtils -import org.jetbrains.k2js.translate.context.Namer -import org.jetbrains.k2js.translate.utils.ErrorReportingUtils import org.jetbrains.k2js.translate.utils.AnnotationsUtils -import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils import org.jetbrains.k2js.translate.reference.CallArgumentTranslator +import org.jetbrains.k2js.translate.general.Translation +import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver +import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue +import org.jetbrains.jet.lang.resolve.calls.CallResolverUtil +import org.jetbrains.jet.lang.psi.Call.CallType +import kotlin.test.assertNotNull object CallTranslator { fun translate(context: TranslationContext, @@ -89,24 +88,67 @@ private fun translateCall(context: TranslationContext, val variableCall = resolvedCall.getVariableCall() if (variableCall.expectedReceivers()) { val newReceiver = CallTranslator.translateGet(context, variableCall, explicitReceivers.receiverOrThisObject) - return translateCall(context, resolvedCall.getFunctionCall(), ExplicitReceivers(newReceiver)) + return translateFunctionCall(context, resolvedCall.getFunctionCall(), ExplicitReceivers(newReceiver)) } else { val thisObject = CallTranslator.translateGet(context, variableCall, null) if (explicitReceivers.receiverOrThisObject == null) - return translateCall(context, resolvedCall.getFunctionCall(), ExplicitReceivers(thisObject)) + return translateFunctionCall(context, resolvedCall.getFunctionCall(), ExplicitReceivers(thisObject)) else - return translateCall(context, resolvedCall.getFunctionCall(), ExplicitReceivers(thisObject, explicitReceivers.receiverOrThisObject)) + return translateFunctionCall(context, resolvedCall.getFunctionCall(), ExplicitReceivers(thisObject, explicitReceivers.receiverOrThisObject)) } } - val functionCallInfo = context.getCallInfo(resolvedCall, explicitReceivers) - return functionCallInfo.translateFunctionCall() + val call = resolvedCall.getCall() + if (call.getCallType() == CallType.INVOKE && !CallResolverUtil.isInvokeCallOnVariable(call)) { + val explicitReceiversForInvoke = computeExplicitReceiversForInvoke(context, resolvedCall, explicitReceivers) + return translateFunctionCall(context, resolvedCall, explicitReceiversForInvoke) + } + + return translateFunctionCall(context, resolvedCall, explicitReceivers) } +private fun translateFunctionCall(context: TranslationContext, + resolvedCall: ResolvedCall, + explicitReceivers: ExplicitReceivers +): JsExpression { + return context.getCallInfo(resolvedCall, explicitReceivers).translateFunctionCall() +} + +fun computeExplicitReceiversForInvoke( + context: TranslationContext, + resolvedCall: ResolvedCall, + explicitReceivers: ExplicitReceivers +): ExplicitReceivers { + val callElement = resolvedCall.getCall().getCallElement() + assert(explicitReceivers.receiverObject == null, "'Invoke' call must have one receiver: $callElement") + + fun translateReceiverAsExpression(receiver: ReceiverValue): JsExpression? = + (receiver as? ExpressionReceiver)?.let { Translation.translateAsExpression(it.getExpression(), context) } + + val thisObject = resolvedCall.getThisObject() + val receiverArgument = resolvedCall.getReceiverArgument() + + if (thisObject.exists() && receiverArgument.exists()) { + assertNotNull(explicitReceivers.receiverOrThisObject, "No explicit receiver for 'invoke' resolved call with both receivers: $callElement") + } + else { + assert(explicitReceivers.receiverOrThisObject == null, + "Non trivial explicit receiver ${explicitReceivers.receiverOrThisObject}\n for 'invoke' resolved call: $callElement\n" + + "This object: $thisObject Receiver argument: $receiverArgument") + } + + val thisObjectExpression = translateReceiverAsExpression(thisObject) + return when (Pair(thisObject.exists(), receiverArgument.exists())) { + Pair(true, true) -> ExplicitReceivers(thisObjectExpression, explicitReceivers.receiverOrThisObject) + Pair(true, false) -> ExplicitReceivers(thisObjectExpression) + Pair(false, true) -> ExplicitReceivers(translateReceiverAsExpression(receiverArgument)) + else -> throw AssertionError("'Invoke' resolved call without receivers: $callElement") + } +} trait CallCase { - protected fun I.unsupported(message: String = "") : Nothing = throw UnsupportedOperationException("this case unsopported. $this") + protected fun I.unsupported(message: String = "") : Nothing = throw UnsupportedOperationException("this case unsupported. $this") protected fun I.noReceivers(): JsExpression = unsupported() diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt index 08fa2a41546..fc24891ee17 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/FunctionCallCases.kt @@ -34,6 +34,7 @@ import org.jetbrains.k2js.translate.context.TranslationContext import org.jetbrains.k2js.translate.reference.CallArgumentTranslator import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor import org.jetbrains.jet.lang.descriptors.Visibilities +import org.jetbrains.jet.lang.psi.Call.CallType public fun addReceiverToArgs(receiver: JsExpression, arguments: List): List { if (arguments.isEmpty()) @@ -174,29 +175,6 @@ object ConstructorCallCase : FunctionCallCase { } } -object ExpressionAsFunctionDescriptorIntrinsic : FunctionCallCase { - fun canApply(callInfo: FunctionCallInfo): Boolean { - return callInfo.callableDescriptor is ExpressionAsFunctionDescriptor - } - - override fun FunctionCallInfo.noReceivers(): JsExpression { - if (callableDescriptor !is ExpressionAsFunctionDescriptor) { - throw IllegalStateException("callableDescriptor must be ExpressionAsFunctionDescriptor $this") - } - val funRef = Translation.translateAsExpression((callableDescriptor as ExpressionAsFunctionDescriptor).getExpression()!!, context) - return JsInvocation(funRef, argumentsInfo.getTranslateArguments()) - } - - override fun FunctionCallInfo.receiverArgument(): JsExpression { - if (callableDescriptor !is ExpressionAsFunctionDescriptor) { - throw IllegalStateException("callableDescriptor must be ExpressionAsFunctionDescriptor $this") - } - - val funRef = Translation.translateAsExpression((callableDescriptor as ExpressionAsFunctionDescriptor).getExpression()!!, context) - return JsInvocation(Namer.getFunctionCallRef(funRef), addReceiverToArgs(receiverObject!!, argumentsInfo.getTranslateArguments())) - } -} - object SuperCallCase : FunctionCallCase { fun canApply(callInfo: FunctionCallInfo): Boolean { return callInfo.isSuperInvocation() @@ -216,8 +194,6 @@ fun FunctionCallInfo.translateFunctionCall(): JsExpression { return when { intrinsic != null -> intrinsic - ExpressionAsFunctionDescriptorIntrinsic.canApply(this) -> - ExpressionAsFunctionDescriptorIntrinsic.translate(this) InvokeIntrinsic.canApply(this) -> InvokeIntrinsic.translate(this) ConstructorCallCase.canApply(this) -> diff --git a/js/js.translator/testData/expression/invoke/cases/InvokeWithThisObjectAndReceiver.kt b/js/js.translator/testData/expression/invoke/cases/InvokeWithThisObjectAndReceiver.kt new file mode 100644 index 00000000000..03be01a7f3a --- /dev/null +++ b/js/js.translator/testData/expression/invoke/cases/InvokeWithThisObjectAndReceiver.kt @@ -0,0 +1,12 @@ +package foo + +class A +class B { + fun A.invoke(i: Int) = i +} + +fun box(): Boolean { + val a = A() + val b = B() + return a.(b)(1) == 1 +} diff --git a/js/js.translator/testData/expression/invoke/cases/invokeInExtensionFunctionLiteral.kt b/js/js.translator/testData/expression/invoke/cases/invokeInExtensionFunctionLiteral.kt new file mode 100644 index 00000000000..bb42317963c --- /dev/null +++ b/js/js.translator/testData/expression/invoke/cases/invokeInExtensionFunctionLiteral.kt @@ -0,0 +1,10 @@ +package foo + +fun box(): Boolean { + val v1 = 1.{ Int.(x: Int) -> this + x }(2) + + val f = { Int.(x: Int) -> this + x } + val v2 = 1.(f)(2) + + return v1 == 3 && v2 == 3 +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/invoke/cases/invokeInFunctionLiteral.kt b/js/js.translator/testData/expression/invoke/cases/invokeInFunctionLiteral.kt new file mode 100644 index 00000000000..a719663d7b3 --- /dev/null +++ b/js/js.translator/testData/expression/invoke/cases/invokeInFunctionLiteral.kt @@ -0,0 +1,10 @@ +package foo + +fun box(): Boolean { + val v1 = {(x: Int) -> x}(2) + + val f = {(x: Int) -> x} + val v2 = (f)(2) + + return v1 == 2 && v2 == 2 +} diff --git a/js/js.translator/testData/expression/invoke/cases/invokeOnExprByConvention.kt b/js/js.translator/testData/expression/invoke/cases/invokeOnExprByConvention.kt new file mode 100644 index 00000000000..54c5a729f79 --- /dev/null +++ b/js/js.translator/testData/expression/invoke/cases/invokeOnExprByConvention.kt @@ -0,0 +1,19 @@ +package foo + +class A { + fun invoke() = "##" + fun invoke(i: Int) = "#${i}" +} + +fun foo() = A() + +fun box(): String { + if (A()() != "##") return "fail1" + if (A()(1) != "#1") return "fail2" + if (foo()() != "##") return "fail3" + if (foo()(42) != "#42") return "fail4" + if ((foo())(42) != "#42") return "fail5" + if ({() -> A()}()() != "##") return "fail6" + if ({() -> A()}()(37) != "#37") return "fail7" + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/expression/invoke/cases/invokeWithReceiverArgument.kt b/js/js.translator/testData/expression/invoke/cases/invokeWithReceiverArgument.kt new file mode 100644 index 00000000000..98ad6219c11 --- /dev/null +++ b/js/js.translator/testData/expression/invoke/cases/invokeWithReceiverArgument.kt @@ -0,0 +1,6 @@ +package foo + +fun Int.invoke(x: Int) = this + x +fun box(): Boolean { + return 1(2) == 3 +} diff --git a/js/js.translator/testData/expression/invoke/cases/invokeWithThisObject.kt b/js/js.translator/testData/expression/invoke/cases/invokeWithThisObject.kt new file mode 100644 index 00000000000..921719f95f3 --- /dev/null +++ b/js/js.translator/testData/expression/invoke/cases/invokeWithThisObject.kt @@ -0,0 +1,9 @@ +package foo + +class A { + fun invoke(i: Int) = i +} + +fun box(): Boolean { + return A()(1) == 1 +}