JS backend: fix equality check for nullable types

* Generate simple check when compare with `null` literal.
* Use Kotlin.equals if type of receiver or argument is nullable.
* Don't call equals method if any of receiver or argument is null (at runtime, not only literal). For more information see KT-4356.

 #KT-7530 fixed
 #KT-7916 fixed
This commit is contained in:
Zalim Bashorov
2015-06-02 20:07:48 +03:00
parent 6aaccae6a9
commit 92457543bc
6 changed files with 133 additions and 1 deletions
@@ -53,4 +53,16 @@ public final class EqualsTest extends AbstractExpressionTest {
public void testCompareNullableListWithNull() throws Exception {
checkFooBoxIsOk();
}
public void testCompareToNullWithCustomEquals() throws Exception {
checkFooBoxIsOk();
}
public void testCompareNullablesWithCustomEquals() throws Exception {
checkFooBoxIsOk();
}
public void testEqualsBehaviorOnNull() throws Exception {
checkFooBoxIsOk();
}
}
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.js.translate.context.TemporaryVariable;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.general.AbstractTranslator;
import org.jetbrains.kotlin.js.translate.general.Translation;
import org.jetbrains.kotlin.js.translate.intrinsic.functions.factories.TopLevelFIF;
import org.jetbrains.kotlin.js.translate.intrinsic.operation.BinaryOperationIntrinsic;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils;
@@ -35,8 +36,12 @@ import org.jetbrains.kotlin.psi.JetBinaryExpression;
import org.jetbrains.kotlin.psi.JetExpression;
import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilPackage;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.types.JetType;
import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.expressions.OperatorConventions;
import java.util.Collections;
import static org.jetbrains.kotlin.js.translate.operation.AssignmentTranslator.isAssignmentOperator;
import static org.jetbrains.kotlin.js.translate.operation.CompareToTranslator.isCompareToCall;
import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression;
@@ -108,6 +113,9 @@ public final class BinaryOperationTranslator extends AbstractTranslator {
if (isCompareToCall(operationToken, operationDescriptor)) {
return CompareToTranslator.translate(expression, context());
}
if (isEquals()) {
return translateEquals();
}
assert operationDescriptor != null :
"Overloadable operations must have not null descriptor";
return translateAsOverloadedBinaryOperation();
@@ -246,11 +254,33 @@ public final class BinaryOperationTranslator extends AbstractTranslator {
return result;
}
private boolean isEquals() {
return operationToken == JetTokens.EQEQ || operationToken == JetTokens.EXCLEQ;
}
private JsExpression translateEquals() {
JsExpression left = Translation.translateAsExpression(leftJetExpression, context());
JsExpression right = Translation.translateAsExpression(rightJetExpression, context());
if (left == JsLiteral.NULL || right == JsLiteral.NULL) {
JsBinaryOperator operator = operationToken == JetTokens.EXCLEQ ? JsBinaryOperator.NEQ : JsBinaryOperator.EQ;
return new JsBinaryOperation(operator, left, right);
}
JetType leftType = context().bindingContext().getType(leftJetExpression);
JetType rightType = context().bindingContext().getType(rightJetExpression);
if (leftType != null && TypeUtils.isNullableType(leftType) || rightType != null && TypeUtils.isNullableType(rightType)) {
return mayBeWrapWithNegation(TopLevelFIF.KOTLIN_EQUALS.apply(left, Collections.singletonList(right), context()));
}
return translateAsOverloadedBinaryOperation();
}
@NotNull
private JsExpression translateAsOverloadedBinaryOperation() {
ResolvedCall<? extends FunctionDescriptor> resolvedCall = getFunctionResolvedCallWithAssert(expression, bindingContext());
JsExpression result = CallTranslator.INSTANCE$.translate(context(), resolvedCall, getReceiver());
JsExpression result = CallTranslator.translate(context(), resolvedCall, getReceiver());
return mayBeWrapWithNegation(result);
}
@@ -0,0 +1,47 @@
package foo
class A {
override fun equals(other: Any?) = this === other
}
fun box(): String {
val a: A? = null
val b: A? = null
val c: A? = A()
val d: A? = A()
val e: A = A()
// compare nullable vals with null
testTrue { a == b }
testTrue { a == a }
testFalse { a != b }
testFalse { a != a }
// compare null and non-null inside nullable vals
testFalse { a == c }
testTrue { a != c }
testFalse { c == a }
testTrue { c != a }
// compare nullables vals with non-null
testFalse { c == d }
testTrue { c == c }
testTrue { c != d }
testFalse { d == c }
testTrue { d != c }
testFalse { d != d }
// compare nullable val with null with non-nullable
testFalse { a == e }
testTrue { a != e }
testFalse { e == a }
testTrue { e != a }
// compare nullable val with non-null with non-nullable
testFalse { c == e }
testTrue { c != e }
testFalse { e == c }
testTrue { e != c }
return "OK"
}
@@ -0,0 +1,16 @@
package foo
class A {
override fun equals(other: Any?) = super.equals(other)
}
fun box(): String {
val a: A? = null
testTrue { a == null }
testFalse { a != null }
testTrue { null == a }
testFalse { null != a }
return "OK"
}
@@ -0,0 +1,23 @@
package foo
var log = ""
class A {
override fun equals(o: Any?): Boolean {
log += "$o;"
return true
}
}
fun box(): String {
val a = A()
assertTrue(a.equals("aaa"))
assertEquals("aaa;", log)
assertFalse(a == null)
assertEquals("aaa;", log)
assertTrue(a.equals(null))
assertEquals("aaa;null;", log)
return "OK"
}
+4
View File
@@ -47,6 +47,10 @@
return obj2 == null;
}
if (obj2 == null) {
return false;
}
if (Array.isArray(obj1)) {
return Kotlin.arrayEquals(obj1, obj2);
}