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 {
|
public void testSafeCallReturnsNullIfFails() throws Exception {
|
||||||
fooBoxTest();
|
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.utils.JsAstUtils
|
||||||
import org.jetbrains.k2js.translate.context.Namer
|
import org.jetbrains.k2js.translate.context.Namer
|
||||||
import org.jetbrains.k2js.translate.reference.CallArgumentTranslator
|
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 {
|
trait CallInfo {
|
||||||
@@ -44,6 +47,8 @@ trait CallInfo {
|
|||||||
val thisObject: JsExpression?
|
val thisObject: JsExpression?
|
||||||
val receiverObject: JsExpression?
|
val receiverObject: JsExpression?
|
||||||
|
|
||||||
|
fun constructSafeCallIsNeeded(result: JsExpression): JsExpression
|
||||||
|
|
||||||
fun toString(): String {
|
fun toString(): String {
|
||||||
val location = DiagnosticUtils.atLocation(context.bindingContext(), callableDescriptor)
|
val location = DiagnosticUtils.atLocation(context.bindingContext(), callableDescriptor)
|
||||||
val name = callableDescriptor.getName().asString()
|
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 {
|
return object : CallInfo {
|
||||||
override val context: TranslationContext = this@createCallInfo
|
override val context: TranslationContext = this@createCallInfo
|
||||||
override val resolvedCall: ResolvedCall<out CallableDescriptor> = resolvedCall
|
override val resolvedCall: ResolvedCall<out CallableDescriptor> = resolvedCall
|
||||||
override val thisObject: JsExpression? = getThisObject()
|
override val thisObject: JsExpression? = thisObject
|
||||||
override val receiverObject: JsExpression? = getReceiverObject()
|
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
|
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
|
val VariableAccessInfo.variableDescriptor: VariableDescriptor
|
||||||
get() = callableDescriptor as VariableDescriptor
|
get() = callableDescriptor as VariableDescriptor
|
||||||
|
|
||||||
|
|||||||
@@ -143,10 +143,18 @@ trait DelegateIntrinsic<I : CallInfo> {
|
|||||||
fun I.getArgs(): List<JsExpression>
|
fun I.getArgs(): List<JsExpression>
|
||||||
|
|
||||||
fun intrinsic(callInfo: I): JsExpression? {
|
fun intrinsic(callInfo: I): JsExpression? {
|
||||||
return if (callInfo.canBeApply())
|
val result =
|
||||||
callInfo.getIntrinsic()
|
if (callInfo.canBeApply()) {
|
||||||
else
|
callInfo.getIntrinsic()
|
||||||
null
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
return callInfo.constructSafeCallIsNeeded(result)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun I.getIntrinsic(): JsExpression? {
|
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