From 5d4cb1e065cc97d68a3950f22b4da7a4ffc3ea29 Mon Sep 17 00:00:00 2001 From: Erokhin Stanislav Date: Fri, 31 Jan 2014 14:01:35 +0400 Subject: [PATCH] JS backend: Fix safe call side effects --- .../k2js/test/semantics/SafeCallTest.java | 8 ++ .../k2js/translate/callTranslator/CallInfo.kt | 36 ++++++++- .../callTranslator/CallInfoExtensions.kt | 13 --- .../callTranslator/CallTranslator.kt | 16 +++- .../safeCall/cases/safeCallAndIntrinsic.kt | 28 +++++++ .../safeCall/cases/safeCallAndSideEffect.kt | 79 +++++++++++++++++++ 6 files changed, 161 insertions(+), 19 deletions(-) create mode 100644 js/js.translator/testFiles/safeCall/cases/safeCallAndIntrinsic.kt create mode 100644 js/js.translator/testFiles/safeCall/cases/safeCallAndSideEffect.kt diff --git a/js/js.tests/test/org/jetbrains/k2js/test/semantics/SafeCallTest.java b/js/js.tests/test/org/jetbrains/k2js/test/semantics/SafeCallTest.java index 0a7e36b38df..4d9490a0b4b 100644 --- a/js/js.tests/test/org/jetbrains/k2js/test/semantics/SafeCallTest.java +++ b/js/js.tests/test/org/jetbrains/k2js/test/semantics/SafeCallTest.java @@ -39,4 +39,12 @@ public final class SafeCallTest extends SingleFileTranslationTest { public void testSafeCallReturnsNullIfFails() throws Exception { fooBoxTest(); } + + public void testSafeCallAndSideEffect() throws Exception { + checkFooBoxIsOk(); + } + + public void testSafeCallAndIntrinsic() throws Exception { + checkFooBoxIsOk(); + } } diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfo.kt b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfo.kt index 49706d21df7..9ff8ff50510 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfo.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfo.kt @@ -35,6 +35,9 @@ 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.reference.CallArgumentTranslator +import org.jetbrains.k2js.translate.utils.TranslationUtils +import com.google.dart.compiler.backend.js.ast.JsLiteral +import com.google.dart.compiler.backend.js.ast.JsConditional trait CallInfo { @@ -44,6 +47,8 @@ trait CallInfo { val thisObject: JsExpression? val receiverObject: JsExpression? + fun constructSafeCallIsNeeded(result: JsExpression): JsExpression + fun toString(): String { val location = DiagnosticUtils.atLocation(context.bindingContext(), callableDescriptor) val name = callableDescriptor.getName().asString() @@ -110,10 +115,37 @@ private fun TranslationContext.createCallInfo(resolvedCall: ResolvedCall { + notNullConditional = TranslationUtils.notNullConditional(receiverObject!!, JsLiteral.NULL, this) + receiverObject = notNullConditional!!.getThenExpression() + } + else -> { + notNullConditional = TranslationUtils.notNullConditional(thisObject!!, JsLiteral.NULL, this) + thisObject = notNullConditional!!.getThenExpression() + } + } + } return object : CallInfo { override val context: TranslationContext = this@createCallInfo override val resolvedCall: ResolvedCall = resolvedCall - override val thisObject: JsExpression? = getThisObject() - override val receiverObject: JsExpression? = getReceiverObject() + override val thisObject: JsExpression? = thisObject + override val receiverObject: JsExpression? = receiverObject + + val notNullConditionalForSafeCall: JsConditional? = notNullConditional + + override fun constructSafeCallIsNeeded(result: JsExpression): JsExpression { + if (notNullConditionalForSafeCall == null) { + return result + } else { + notNullConditionalForSafeCall.setThenExpression(result) + return notNullConditionalForSafeCall + } + } }; } \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfoExtensions.kt b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfoExtensions.kt index 507afd16edb..3dd6437b381 100644 --- a/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfoExtensions.kt +++ b/js/js.translator/src/org/jetbrains/k2js/translate/callTranslator/CallInfoExtensions.kt @@ -44,19 +44,6 @@ fun CallInfo.isSuperInvocation(): Boolean { return thisObject is ExpressionReceiver && ((thisObject as ExpressionReceiver)).getExpression() is JetSuperExpression } -fun CallInfo.constructSafeCallIsNeeded(result: JsExpression): JsExpression { - if (!resolvedCall.isSafeCall()) - return result - - val nullableReceiverForSafeCall = when (resolvedCall.getExplicitReceiverKind()) { - BOTH_RECEIVERS, RECEIVER_ARGUMENT -> receiverObject - else -> thisObject - } - val expression = TranslationUtils.notNullConditional(nullableReceiverForSafeCall!!, JsLiteral.NULL, context) - expression.setThenExpression(result) - return expression -} - val VariableAccessInfo.variableDescriptor: VariableDescriptor get() = callableDescriptor as VariableDescriptor 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 fa3ba848878..4f72e979ad1 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 @@ -143,10 +143,18 @@ trait DelegateIntrinsic { fun I.getArgs(): List fun intrinsic(callInfo: I): JsExpression? { - return if (callInfo.canBeApply()) - callInfo.getIntrinsic() - else - null + val result = + if (callInfo.canBeApply()) { + callInfo.getIntrinsic() + } else { + null + } + + if (result != null) { + return callInfo.constructSafeCallIsNeeded(result) + } else { + return null + } } private fun I.getIntrinsic(): JsExpression? { diff --git a/js/js.translator/testFiles/safeCall/cases/safeCallAndIntrinsic.kt b/js/js.translator/testFiles/safeCall/cases/safeCallAndIntrinsic.kt new file mode 100644 index 00000000000..7fad71c0633 --- /dev/null +++ b/js/js.translator/testFiles/safeCall/cases/safeCallAndIntrinsic.kt @@ -0,0 +1,28 @@ +package foo + +var c1 = 0; + +fun getInt(): Int? { + c1++ + return c1 +} + +fun getNullInt(): Int? = null + +fun box(): String { + if (c1 != 0) { + return "Start value of counter not 0, it: $c1" + } + val nullRes = getNullInt()?.toString() + if (nullRes != null) { + return "Broken safeCall. nullRes: $nullRes" + } + val res = getInt()?.toString() + if (res != "1") { + return "res != 1, it: $res, and c1: $c1" + } + if (c1 != 1) { + return "Side effect. c1 != 1, it: $c1" + } + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testFiles/safeCall/cases/safeCallAndSideEffect.kt b/js/js.translator/testFiles/safeCall/cases/safeCallAndSideEffect.kt new file mode 100644 index 00000000000..d9f81fced00 --- /dev/null +++ b/js/js.translator/testFiles/safeCall/cases/safeCallAndSideEffect.kt @@ -0,0 +1,79 @@ +package foo + +var c1 = 0 +var c2 = 0 +var c3 = 0 +var c4 = 0 +var c5 = 0 + +fun toStr(): String { + return "$c1$c2$c3$c4$c5" +} + +fun getA(): A? { + c1++ + return A() +} + +fun getNullA(): A? { + c1++ + return null +} + +class A { + fun someFun(): Int { + c2++ + return 1 + } + val b: B + get() { + c3++ + return B() + } +} + +fun A.extFun(): Int { + c4++ + return 3 +} + +class B { + fun invoke(): Int { + c5++ + return 2 + } +} + +fun box(): String { + val n1 = getNullA()?.someFun() + if (n1 != null || toStr() != "10000") { + return "Bad call getNullA()?.someFun(). result: $n1, counters: ${toStr()}" + } + + val n2 = getNullA()?.b() + if (n2 != null || toStr() != "20000") { + return "Bad call getNullA()?.b(). result: $n2, counters: ${toStr()}" + } + + val n3 = getNullA()?.extFun() + if (n3 != null || toStr() != "30000") { + return "Bad call getNullA()?.extFun(). result: $n3, counters: ${toStr()}" + } + + val i1 = getA()?.someFun() + if (i1 != 1 || toStr() != "41000") { + return "Bad call getA()?.someFun(). result: $i1, counters: ${toStr()}" + } + + val i2 = getA()?.b() + if (i2 != 2 || toStr() != "51101") { + return "Bad call getA()?.b(). result: $i2, counters: ${toStr()}" + } + + val i3 = getA()?.extFun() + if (i3 != 3 || toStr() != "61111") { + return "Bad call getA()?.extFun(). result: $i3, counters: ${toStr()}" + } + + return "OK" +} \ No newline at end of file