JS backend: Fix safe call side effects
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
-13
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user