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:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+31
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+47
@@ -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"
|
||||
}
|
||||
+16
@@ -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
@@ -47,6 +47,10 @@
|
||||
return obj2 == null;
|
||||
}
|
||||
|
||||
if (obj2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj1)) {
|
||||
return Kotlin.arrayEquals(obj1, obj2);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user