JS backend: Fix safe call side effects

This commit is contained in:
Erokhin Stanislav
2014-01-31 14:01:35 +04:00
parent 7f8c17d6af
commit 5d4cb1e065
6 changed files with 161 additions and 19 deletions
@@ -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();
}
}
@@ -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<out Cal
}
}
var thisObject = getThisObject()
var receiverObject = getReceiverObject()
var notNullConditional: JsConditional? = null
if (resolvedCall.isSafeCall()) {
when (resolvedCall.getExplicitReceiverKind()) {
BOTH_RECEIVERS, RECEIVER_ARGUMENT -> {
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<out CallableDescriptor> = 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
}
}
};
}
@@ -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
@@ -143,10 +143,18 @@ trait DelegateIntrinsic<I : CallInfo> {
fun I.getArgs(): List<JsExpression>
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? {
@@ -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"
}
@@ -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"
}