JS: fixed Double.NaN behaviour (KT-13610).
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS
|
||||
data class Test(val z1: Double, val z2: Double?)
|
||||
|
||||
fun box(): String {
|
||||
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
import kotlin.test.*
|
||||
|
||||
object O {
|
||||
var equalsCalled: Boolean = false
|
||||
get(): Boolean {
|
||||
val result = field
|
||||
field = false
|
||||
return result
|
||||
}
|
||||
set(v: Boolean) {
|
||||
field = v
|
||||
}
|
||||
|
||||
override fun equals(a: Any?): Boolean {
|
||||
equalsCalled = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val A: Any = O
|
||||
|
||||
fun <T: Double> testDouble(d: Double, v: T, vararg va: T) {
|
||||
assertFalse(d == d, "Double: d != d")
|
||||
assertFalse(d == v, "Double: d != v")
|
||||
assertFalse(d == va[0], "Double: d != va[0]")
|
||||
assertFalse(v == d, "Double: v != d")
|
||||
assertFalse(v == v, "Double: v != v")
|
||||
assertFalse(v == va[0], "Double: v != va[0]")
|
||||
assertFalse(va[0] == d, "Double: va[0] != d")
|
||||
assertFalse(va[0] == v, "Double: va[0] != v")
|
||||
assertFalse(va[0] == va[0], "Double: va[0] != va[0]")
|
||||
|
||||
assertTrue(d != d, "Double: d == d")
|
||||
assertTrue(d != v, "Double: d == v")
|
||||
assertTrue(d != va[0], "Double: d == va[0]")
|
||||
assertTrue(v != d, "Double: v == d")
|
||||
assertTrue(v != v, "Double: v == v")
|
||||
assertTrue(v != va[0], "Double: v == va[0]")
|
||||
assertTrue(va[0] != d, "Double: va[0] == d")
|
||||
assertTrue(va[0] != v, "Double: va[0] == v")
|
||||
assertTrue(va[0] != va[0], "Double: va[0] == va[0]")
|
||||
}
|
||||
|
||||
fun <T: Float> testFloat(d: Float, v: T, vararg va: T) {
|
||||
assertFalse(d == d, "Float: d != d")
|
||||
assertFalse(d == v, "Float: d != v")
|
||||
assertFalse(d == va[0], "Float: d != va[0]")
|
||||
assertFalse(v == d, "Float: v != d")
|
||||
assertFalse(v == v, "Float: v != v")
|
||||
assertFalse(v == va[0], "Float: v != va[0]")
|
||||
assertFalse(va[0] == d, "Float: va[0] != d")
|
||||
assertFalse(va[0] == v, "Float: va[0] != v")
|
||||
assertFalse(va[0] == va[0], "Float: va[0] != va[0]")
|
||||
|
||||
assertTrue(d != d, "Float: d == d")
|
||||
assertTrue(d != v, "Float: d == v")
|
||||
assertTrue(d != va[0], "Float: d == va[0]")
|
||||
assertTrue(v != d, "Float: v == d")
|
||||
assertTrue(v != v, "Float: v == v")
|
||||
assertTrue(v != va[0], "Float: v == va[0]")
|
||||
assertTrue(va[0] != d, "Float: va[0] == d")
|
||||
assertTrue(va[0] != v, "Float: va[0] == v")
|
||||
assertTrue(va[0] != va[0], "Float: va[0] == va[0]")
|
||||
}
|
||||
|
||||
var gdn: Any = Double.NaN
|
||||
var gfn: Any = Float.NaN
|
||||
|
||||
fun box(): String {
|
||||
|
||||
// Double
|
||||
|
||||
val dn = Double.NaN
|
||||
val adn: Any = dn
|
||||
val dnq: Double? = dn
|
||||
val adnq: Any? = dn
|
||||
|
||||
assertFalse(dn == dn, "Double: NaN == NaN")
|
||||
assertTrue(dn == adn, "Double: NaN != (Any)NaN")
|
||||
assertTrue(adn == dn, "Double: (Any)NaN != NaN")
|
||||
assertTrue(adn == adn, "Double: (Any)NaN != (Any)NaN")
|
||||
|
||||
assertFalse(dn == dnq, "Double: NaN == NaN?")
|
||||
assertTrue(dn == adnq, "Double: NaN != (Any?)NaN")
|
||||
assertTrue(adn == dnq, "Double: (Any)NaN != NaN?")
|
||||
assertTrue(adn == adnq, "Double: (Any)NaN != (Any?)NaN")
|
||||
|
||||
assertFalse(dnq == dn, "Double: NaN? == NaN")
|
||||
assertTrue(dnq == adn, "Double: NaN? != (Any)NaN")
|
||||
assertTrue(adnq == dn, "Double: (Any?)NaN != NaN")
|
||||
assertTrue(adnq == adn, "Double: (Any?)NaN != (Any)NaN")
|
||||
|
||||
assertFalse(dnq == dnq, "Double: NaN? == NaN?")
|
||||
assertTrue(dnq == adnq, "Double: NaN? != (Any?)NaN")
|
||||
assertTrue(adnq == dnq, "Double: (Any?)NaN != NaN?")
|
||||
assertTrue(adnq == adnq, "Double: (Any?)NaN != (Any?)NaN")
|
||||
|
||||
assertTrue(dn != dn, "Double: NaN == NaN")
|
||||
assertFalse(dn != adn, "Double: NaN != (Any)NaN")
|
||||
assertFalse(adn != dn, "Double: (Any)NaN != NaN")
|
||||
assertFalse(adn != adn, "Double: (Any)NaN != (Any)NaN")
|
||||
|
||||
assertTrue(dn != dnq, "Double: NaN == NaN?")
|
||||
assertFalse(dn != adnq, "Double: NaN != (Any?)NaN")
|
||||
assertFalse(adn != dnq, "Double: (Any)NaN != NaN?")
|
||||
assertFalse(adn != adnq, "Double: (Any)NaN != (Any?)NaN")
|
||||
|
||||
assertTrue(dnq != dn, "Double: NaN? == NaN")
|
||||
assertFalse(dnq != adn, "Double: NaN? != (Any)NaN")
|
||||
assertFalse(adnq != dn, "Double: (Any?)NaN != NaN")
|
||||
assertFalse(adnq != adn, "Double: (Any?)NaN != (Any)NaN")
|
||||
|
||||
assertTrue(dnq != dnq, "Double: NaN? == NaN?")
|
||||
assertFalse(dnq != adnq, "Double: NaN? != (Any?)NaN")
|
||||
assertFalse(adnq != dnq, "Double: (Any?)NaN != NaN?")
|
||||
assertFalse(adnq != adnq, "Double: (Any?)NaN != (Any?)NaN")
|
||||
|
||||
// Stable smart-casts
|
||||
if (adn is Double) {
|
||||
assertFalse(adn == adn, "Double smart-cast: NaN == NaN")
|
||||
assertTrue(adn != adn, "Double smart-cast: NaN == NaN")
|
||||
}
|
||||
if (adnq is Double?) {
|
||||
assertFalse(adnq == adnq, "Double? smart-cast: NaN? == NaN?")
|
||||
assertTrue(adnq != adnq, "Double? smart-cast: NaN? == NaN?")
|
||||
}
|
||||
// Unstable smart-casts
|
||||
if (gdn is Double) {
|
||||
assertTrue(gdn == gdn, "Unstable Double smart-cast: NaN != NaN")
|
||||
assertFalse(gdn != gdn, "Unstable Double smart-cast: NaN != NaN")
|
||||
}
|
||||
if (gdn is Double?) {
|
||||
assertTrue(gdn == gdn, "Unstable Double smart-cast: NaN != NaN")
|
||||
assertFalse(gdn != gdn, "Unstable Double smart-cast: NaN != NaN")
|
||||
}
|
||||
|
||||
// Explicit .equals
|
||||
assertTrue(A == dn && O.equalsCalled, "A.equals not called for A == dn")
|
||||
assertTrue(dn != A && !O.equalsCalled, "A.equals called for dn == A")
|
||||
assertFalse(A != dn || !O.equalsCalled, "A.equals not called for A != dn")
|
||||
assertFalse(dn == A || O.equalsCalled, "A.equals called for dn != A")
|
||||
|
||||
// Generics and varags
|
||||
testDouble(Double.NaN, Double.NaN, Double.NaN)
|
||||
|
||||
// Float
|
||||
|
||||
val fn = Float.NaN
|
||||
val afn: Any = fn
|
||||
val fnq: Float? = fn
|
||||
val afnq: Any? = fn
|
||||
|
||||
assertFalse(fn == fn, "Float: NaN == NaN")
|
||||
assertTrue(fn == afn, "Float: NaN != (Any)NaN")
|
||||
assertTrue(afn == fn, "Float: (Any)NaN != NaN")
|
||||
assertTrue(afn == afn, "Float: (Any)NaN != (Any)NaN")
|
||||
|
||||
assertFalse(fn == fnq, "Float: NaN == NaN?")
|
||||
assertTrue(fn == afnq, "Float: NaN != (Any?)NaN")
|
||||
assertTrue(afn == fnq, "Float: (Any)NaN != NaN?")
|
||||
assertTrue(afn == afnq, "Float: (Any)NaN != (Any?)NaN")
|
||||
|
||||
assertFalse(fnq == fn, "Float: NaN? == NaN")
|
||||
assertTrue(fnq == afn, "Float: NaN? != (Any)NaN")
|
||||
assertTrue(afnq == fn, "Float: (Any?)NaN != NaN")
|
||||
assertTrue(afnq == afn, "Float: (Any?)NaN != (Any)NaN")
|
||||
|
||||
assertFalse(fnq == fnq, "Float: NaN? == NaN?")
|
||||
assertTrue(fnq == afnq, "Float: NaN? != (Any?)NaN")
|
||||
assertTrue(afnq == fnq, "Float: (Any?)NaN != NaN?")
|
||||
assertTrue(afnq == afnq, "Float: (Any?)NaN != (Any?)NaN")
|
||||
|
||||
assertTrue(fn != fn, "Float: NaN == NaN")
|
||||
assertFalse(fn != afn, "Float: NaN != (Any)NaN")
|
||||
assertFalse(afn != fn, "Float: (Any)NaN != NaN")
|
||||
assertFalse(afn != afn, "Float: (Any)NaN != (Any)NaN")
|
||||
|
||||
assertTrue(fn != fnq, "Float: NaN == NaN?")
|
||||
assertFalse(fn != afnq, "Float: NaN != (Any?)NaN")
|
||||
assertFalse(afn != fnq, "Float: (Any)NaN != NaN?")
|
||||
assertFalse(afn != afnq, "Float: (Any)NaN != (Any?)NaN")
|
||||
|
||||
assertTrue(fnq != fn, "Float: NaN? == NaN")
|
||||
assertFalse(fnq != afn, "Float: NaN? != (Any)NaN")
|
||||
assertFalse(afnq != fn, "Float: (Any?)NaN != NaN")
|
||||
assertFalse(afnq != afn, "Float: (Any?)NaN != (Any)NaN")
|
||||
|
||||
assertTrue(fnq != fnq, "Float: NaN? == NaN?")
|
||||
assertFalse(fnq != afnq, "Float: NaN? != (Any?)NaN")
|
||||
assertFalse(afnq != fnq, "Float: (Any?)NaN != NaN?")
|
||||
assertFalse(afnq != afnq, "Float: (Any?)NaN != (Any?)NaN")
|
||||
|
||||
// Stable smart-casts
|
||||
if (afn is Float) {
|
||||
assertFalse(afn == afn, "Float smart-cast: NaN == NaN")
|
||||
assertTrue(afn != afn, "Float smart-cast: NaN == NaN")
|
||||
}
|
||||
if (afnq is Float?) {
|
||||
assertFalse(afnq == afnq, "Float? smart-cast: NaN? == NaN?")
|
||||
assertTrue(afnq != afnq, "Float? smart-cast: NaN? == NaN?")
|
||||
}
|
||||
// Unstable smart-casts
|
||||
if (gfn is Float) {
|
||||
assertTrue(gfn == gfn, "Unstable Float smart-cast: NaN != NaN")
|
||||
assertFalse(gfn != gfn, "Unstable Float smart-cast: NaN != NaN")
|
||||
}
|
||||
if (gfn is Float?) {
|
||||
assertTrue(gfn == gfn, "Unstable Float smart-cast: NaN != NaN")
|
||||
assertFalse(gfn != gfn, "Unstable Float smart-cast: NaN != NaN")
|
||||
}
|
||||
|
||||
assertTrue(A == fn && O.equalsCalled, "A.equals not called for A == fn")
|
||||
assertTrue(fn != A && !O.equalsCalled, "A.equals called for fn == A")
|
||||
assertFalse(A != fn || !O.equalsCalled, "A.equals not called for A != fn")
|
||||
assertFalse(fn == A || O.equalsCalled, "A.equals called for fn != A")
|
||||
|
||||
// Generics and varags
|
||||
testFloat(Float.NaN, Float.NaN, Float.NaN)
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
@kotlin.Metadata
|
||||
public final class EqualsNaNKt {
|
||||
private final static @org.jetbrains.annotations.NotNull field A: java.lang.Object
|
||||
private static @org.jetbrains.annotations.NotNull field gdn: java.lang.Object
|
||||
private static @org.jetbrains.annotations.NotNull field gfn: java.lang.Object
|
||||
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
|
||||
public final static @org.jetbrains.annotations.NotNull method getA(): java.lang.Object
|
||||
public final static @org.jetbrains.annotations.NotNull method getGdn(): java.lang.Object
|
||||
public final static @org.jetbrains.annotations.NotNull method getGfn(): java.lang.Object
|
||||
public final static method setGdn(@org.jetbrains.annotations.NotNull p0: java.lang.Object): void
|
||||
public final static method setGfn(@org.jetbrains.annotations.NotNull p0: java.lang.Object): void
|
||||
public final static method testDouble(p0: double, p1: double, @org.jetbrains.annotations.NotNull p2: java.lang.Double[]): void
|
||||
public final static method testFloat(p0: float, p1: float, @org.jetbrains.annotations.NotNull p2: java.lang.Float[]): void
|
||||
}
|
||||
|
||||
@kotlin.Metadata
|
||||
public final class O {
|
||||
public final static field INSTANCE: O
|
||||
private static field equalsCalled: boolean
|
||||
private method <init>(): void
|
||||
public method equals(@org.jetbrains.annotations.Nullable p0: java.lang.Object): boolean
|
||||
public final method getEqualsCalled(): boolean
|
||||
public final method setEqualsCalled(p0: boolean): void
|
||||
}
|
||||
+6
@@ -8525,6 +8525,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("equalsNaN.kt")
|
||||
public void testEqualsNaN() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNaN.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("equalsNullableDouble.kt")
|
||||
public void testEqualsNullableDouble() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNullableDouble.kt");
|
||||
|
||||
@@ -8525,6 +8525,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("equalsNaN.kt")
|
||||
public void testEqualsNaN() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNaN.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("equalsNullableDouble.kt")
|
||||
public void testEqualsNullableDouble() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNullableDouble.kt");
|
||||
|
||||
@@ -878,8 +878,19 @@ public abstract class KotlinBuiltIns {
|
||||
}
|
||||
|
||||
public static boolean isPrimitiveType(@NotNull KotlinType type) {
|
||||
return !type.isMarkedNullable() && isPrimitiveTypeOrNullablePrimitiveType(type);
|
||||
}
|
||||
|
||||
public static boolean isPrimitiveTypeOrNullablePrimitiveType(@NotNull KotlinType type) {
|
||||
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
|
||||
return !type.isMarkedNullable() && descriptor instanceof ClassDescriptor && isPrimitiveClass((ClassDescriptor) descriptor);
|
||||
return descriptor instanceof ClassDescriptor && isPrimitiveClass((ClassDescriptor) descriptor);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PrimitiveType getPrimitiveTypeByKotlinType(@NotNull KotlinType type) {
|
||||
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
|
||||
if (descriptor == null) return null;
|
||||
return getPrimitiveTypeByFqName(getFqName(descriptor));
|
||||
}
|
||||
|
||||
public static boolean isPrimitiveClass(@NotNull ClassDescriptor descriptor) {
|
||||
|
||||
@@ -23,6 +23,10 @@ Kotlin.equals = function (obj1, obj2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj1 !== obj1) {
|
||||
return obj2 !== obj2;
|
||||
}
|
||||
|
||||
if (typeof obj1 == "object" && typeof obj1.equals === "function") {
|
||||
return obj1.equals(obj2);
|
||||
}
|
||||
|
||||
+7
-7
@@ -9603,13 +9603,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
@TestMetadata("dataClass.kt")
|
||||
public void testDataClass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/dataClass.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("equalsDouble.kt")
|
||||
@@ -9624,6 +9618,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("equalsNaN.kt")
|
||||
public void testEqualsNaN() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNaN.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("equalsNullableDouble.kt")
|
||||
public void testEqualsNullableDouble() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNullableDouble.kt");
|
||||
|
||||
@@ -190,11 +190,7 @@ public final class Translation {
|
||||
JsNode jsNode = translateExpression(expression, context, block);
|
||||
if (jsNode instanceof JsExpression) {
|
||||
KotlinType expressionType = context.bindingContext().getType(expression);
|
||||
if (expressionType != null && KotlinBuiltIns.isCharOrNullableChar(expressionType) &&
|
||||
(jsNode instanceof JsInvocation || jsNode instanceof JsNameRef || jsNode instanceof JsArrayAccess)) {
|
||||
jsNode = JsAstUtils.boxedCharToChar((JsExpression) jsNode);
|
||||
}
|
||||
return (JsExpression) jsNode;
|
||||
return unboxIfNeeded((JsExpression) jsNode, expressionType != null && KotlinBuiltIns.isCharOrNullableChar(expressionType));
|
||||
}
|
||||
|
||||
assert jsNode instanceof JsStatement : "Unexpected node of type: " + jsNode.getClass().toString();
|
||||
@@ -209,6 +205,16 @@ public final class Translation {
|
||||
return JsLiteral.NULL;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsExpression unboxIfNeeded(@NotNull JsExpression expression, boolean charOrNullableChar) {
|
||||
if (charOrNullableChar &&
|
||||
(expression instanceof JsInvocation || expression instanceof JsNameRef || expression instanceof JsArrayAccess)
|
||||
) {
|
||||
expression = JsAstUtils.boxedCharToChar(expression);
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsStatement translateAsStatement(@NotNull KtExpression expression, @NotNull TranslationContext context) {
|
||||
return translateAsStatement(expression, context, context.dynamicContext().jsBlock());
|
||||
|
||||
+54
-27
@@ -16,23 +16,26 @@
|
||||
|
||||
package org.jetbrains.kotlin.js.translate.intrinsic.operation
|
||||
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.PrimitiveType
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperation
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperator
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsExpression
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsLiteral
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.js.patterns.NamePredicate
|
||||
import org.jetbrains.kotlin.js.translate.context.TranslationContext
|
||||
import org.jetbrains.kotlin.js.translate.general.Translation
|
||||
import org.jetbrains.kotlin.js.translate.intrinsic.functions.factories.TopLevelFIF
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils
|
||||
import org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken
|
||||
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||
@@ -41,13 +44,26 @@ import java.util.*
|
||||
|
||||
object EqualsBOIF : BinaryOperationIntrinsicFactory {
|
||||
private object EqualsIntrinsic : AbstractBinaryOperationIntrinsic() {
|
||||
|
||||
// Primitives with no boxing or tricky NaN behavior, represented by a Number at runtime.
|
||||
// Whenever the left side of equality is of this type, equality could be tested via '==='.
|
||||
private val SIMPLE_PRIMITIVES = EnumSet.of(PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.INT)
|
||||
|
||||
override fun apply(expression: KtBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression {
|
||||
val isNegated = expression.isNegated()
|
||||
if (right == JsLiteral.NULL || left == JsLiteral.NULL) {
|
||||
return TranslationUtils.nullCheck(if (right == JsLiteral.NULL) left else right, isNegated)
|
||||
}
|
||||
else if (canUseSimpleEquals(expression, context)) {
|
||||
return JsBinaryOperation(if (isNegated) JsBinaryOperator.REF_NEQ else JsBinaryOperator.REF_EQ, left, right)
|
||||
|
||||
val ktLeft = checkNotNull(expression.left) { "No left-hand side: " + expression.text }
|
||||
val ktRight = checkNotNull(expression.right) { "No right-hand side: " + expression.text }
|
||||
val leftType = getRefinedType(ktLeft, context)?.let { KotlinBuiltIns.getPrimitiveTypeByKotlinType(it) }
|
||||
val rightType = getRefinedType(ktRight, context)?.let { KotlinBuiltIns.getPrimitiveTypeByKotlinType(it) }
|
||||
|
||||
if (leftType != null && (leftType in SIMPLE_PRIMITIVES || leftType == rightType && leftType != PrimitiveType.LONG)) {
|
||||
return JsBinaryOperation(if (isNegated) JsBinaryOperator.REF_NEQ else JsBinaryOperator.REF_EQ,
|
||||
Translation.unboxIfNeeded(left, leftType == PrimitiveType.CHAR),
|
||||
Translation.unboxIfNeeded(right, rightType == PrimitiveType.CHAR))
|
||||
}
|
||||
|
||||
val resolvedCall = expression.getResolvedCall(context.bindingContext())
|
||||
@@ -61,15 +77,41 @@ object EqualsBOIF : BinaryOperationIntrinsicFactory {
|
||||
return JsBinaryOperation(if (isNegated) JsBinaryOperator.NEQ else JsBinaryOperator.EQ, left, right)
|
||||
}
|
||||
|
||||
val result = TopLevelFIF.KOTLIN_EQUALS.apply(left, Arrays.asList<JsExpression>(right), context)
|
||||
val maybeBoxedLeft = if (leftType == PrimitiveType.CHAR) JsAstUtils.charToBoxedChar(left) else left
|
||||
val maybeBoxedRight = if (rightType == PrimitiveType.CHAR) JsAstUtils.charToBoxedChar(right) else right
|
||||
|
||||
val result = TopLevelFIF.KOTLIN_EQUALS.apply(maybeBoxedLeft, Arrays.asList<JsExpression>(maybeBoxedRight), context)
|
||||
return if (isNegated) JsAstUtils.not(result) else result
|
||||
}
|
||||
|
||||
private fun canUseSimpleEquals(expression: KtBinaryExpression, context: TranslationContext): Boolean {
|
||||
val left = expression.left
|
||||
assert(left != null) { "No left-hand side: " + expression.text }
|
||||
val typeName = JsDescriptorUtils.getNameIfStandardType(left!!, context)
|
||||
return typeName != null && NamePredicate.PRIMITIVE_NUMBERS_MAPPED_TO_PRIMITIVE_JS.apply(typeName)
|
||||
/**
|
||||
* Tries to get as precise statically known primitive type as possible - taking smart-casts and generic supertypes into account.
|
||||
* This is needed to be compatible with JVM NaN behaviour, in particular the following two cases.
|
||||
*
|
||||
* // Smart-casts
|
||||
* val a: Any = Double.NaN
|
||||
* println(a == a) // true
|
||||
* if (a is Double) {
|
||||
* println(a == a) // false
|
||||
* }
|
||||
*
|
||||
* // Generics with Double super-type
|
||||
* fun <T: Double> foo(v: T) = println(v == v)
|
||||
* foo(Double.NaN) // false
|
||||
*
|
||||
* Also see org/jetbrains/kotlin/codegen/codegenUtil.kt#calcTypeForIEEE754ArithmeticIfNeeded
|
||||
*/
|
||||
private fun getRefinedType(expression: KtExpression, context: TranslationContext): KotlinType? {
|
||||
val bindingContext = context.bindingContext()
|
||||
val descriptor = context.declarationDescriptor ?: context.currentModule
|
||||
val ktType = bindingContext.getType(expression) ?: return null
|
||||
|
||||
val dataFlow = DataFlowValueFactory.createDataFlowValue(expression, ktType, bindingContext, descriptor)
|
||||
val isPrimitiveFn = KotlinBuiltIns::isPrimitiveTypeOrNullablePrimitiveType
|
||||
|
||||
return bindingContext.getDataFlowInfoBefore(expression).getStableTypes(dataFlow).find(isPrimitiveFn) ?: // Smart-casts
|
||||
TypeUtils.getAllSupertypes(ktType).find(isPrimitiveFn) ?: // Generic super-types
|
||||
ktType // Default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,21 +134,6 @@ object EqualsBOIF : BinaryOperationIntrinsicFactory {
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (result != null && leftType != rightType) {
|
||||
val leftChar = (leftType != null && KotlinBuiltIns.isCharOrNullableChar(leftType))
|
||||
val rightChar = (rightType != null && KotlinBuiltIns.isCharOrNullableChar(rightType))
|
||||
|
||||
if (leftChar xor rightChar) {
|
||||
return object : AbstractBinaryOperationIntrinsic() {
|
||||
override fun apply(expression: KtBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression {
|
||||
val maybeBoxedLeft = if (leftChar) JsAstUtils.charToBoxedChar(left) else left
|
||||
val maybeBoxedRight = if (rightChar) JsAstUtils.charToBoxedChar(right) else right
|
||||
return result.apply(expression, maybeBoxedLeft, maybeBoxedRight, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt;
|
||||
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
|
||||
import org.jetbrains.kotlin.name.Name;
|
||||
import org.jetbrains.kotlin.psi.KtExpression;
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
||||
import org.jetbrains.kotlin.resolve.calls.tasks.DynamicCallsKt;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver;
|
||||
@@ -152,12 +148,6 @@ public final class JsDescriptorUtils {
|
||||
!ModalityKt.isOverridableOrOverrides(propertyDescriptor);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Name getNameIfStandardType(@NotNull KtExpression expression, @NotNull TranslationContext context) {
|
||||
KotlinType type = context.bindingContext().getType(expression);
|
||||
return type != null ? DescriptorUtilsKt.getNameIfStandardType(type) : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getModuleName(@NotNull DeclarationDescriptor descriptor) {
|
||||
ModuleDescriptor moduleDescriptor = DescriptorUtils.getContainingModule(findRealInlineDeclaration(descriptor));
|
||||
|
||||
@@ -22,12 +22,11 @@ fun box(): String {
|
||||
mapWithDoubleKeys[1.0] = 1
|
||||
assertEquals("number", jsTypeOf (mapWithDoubleKeys.keys.iterator().next()), "mapWithDoubleKeys")
|
||||
|
||||
// boxed NaNs are not equal, KT-13610
|
||||
// mapWithDoubleKeys.clear()
|
||||
// var dNaN = 0.0 / 0.0
|
||||
// mapWithDoubleKeys[dNaN] = 100
|
||||
// assertEquals(100, mapWithDoubleKeys[dNaN])
|
||||
// assertEquals("number", jsTypeOf (mapWithDoubleKeys.keys.iterator().next()), "dNaN")
|
||||
mapWithDoubleKeys.clear()
|
||||
var dNaN = 0.0 / 0.0
|
||||
mapWithDoubleKeys[dNaN] = 100
|
||||
assertEquals(100, mapWithDoubleKeys[dNaN])
|
||||
assertEquals("number", jsTypeOf (mapWithDoubleKeys.keys.iterator().next()), "dNaN")
|
||||
|
||||
mapWithDoubleKeys.clear()
|
||||
var dPositiveInfinity = +1.0 / 0.0
|
||||
@@ -45,12 +44,11 @@ fun box(): String {
|
||||
mapWithFloatKeys[1.0f] = 1
|
||||
assertEquals("number", jsTypeOf (mapWithFloatKeys.keys.iterator().next()), "mapWithFloatKeys")
|
||||
|
||||
// boxed NaNs are not equal, KT-13610
|
||||
// mapWithFloatKeys.clear()
|
||||
// var fNaN: Float = 0.0f / 0.0f
|
||||
// mapWithFloatKeys[fNaN] = 100
|
||||
// assertEquals(100, mapWithFloatKeys[fNaN])
|
||||
// assertEquals("number", jsTypeOf (mapWithFloatKeys.keys.iterator().next()), "fNaN")
|
||||
mapWithFloatKeys.clear()
|
||||
var fNaN: Float = 0.0f / 0.0f
|
||||
mapWithFloatKeys[fNaN] = 100
|
||||
assertEquals(100, mapWithFloatKeys[fNaN])
|
||||
assertEquals("number", jsTypeOf (mapWithFloatKeys.keys.iterator().next()), "fNaN")
|
||||
|
||||
mapWithFloatKeys.clear()
|
||||
var fPositiveInfinity = +1.0f / 0.0f
|
||||
|
||||
Reference in New Issue
Block a user