Skip to content

Commit 9e5ecc1

Browse files
committed
JS: fixed Double.NaN behaviour (KT-13610).
1 parent f636ab2 commit 9e5ecc1

File tree

12 files changed

+357
-63
lines changed

12 files changed

+357
-63
lines changed

compiler/testData/codegen/box/ieee754/dataClass.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// IGNORE_BACKEND: JS
21
data class Test(val z1: Double, val z2: Double?)
32

43
fun box(): String {
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
// WITH_RUNTIME
2+
3+
import kotlin.test.*
4+
5+
object O {
6+
var equalsCalled: Boolean = false
7+
get(): Boolean {
8+
val result = field
9+
field = false
10+
return result
11+
}
12+
set(v: Boolean) {
13+
field = v
14+
}
15+
16+
override fun equals(a: Any?): Boolean {
17+
equalsCalled = true
18+
return true
19+
}
20+
}
21+
22+
val A: Any = O
23+
24+
fun <T: Double> testDouble(d: Double, v: T, vararg va: T) {
25+
assertFalse(d == d, "Double: d != d")
26+
assertFalse(d == v, "Double: d != v")
27+
assertFalse(d == va[0], "Double: d != va[0]")
28+
assertFalse(v == d, "Double: v != d")
29+
assertFalse(v == v, "Double: v != v")
30+
assertFalse(v == va[0], "Double: v != va[0]")
31+
assertFalse(va[0] == d, "Double: va[0] != d")
32+
assertFalse(va[0] == v, "Double: va[0] != v")
33+
assertFalse(va[0] == va[0], "Double: va[0] != va[0]")
34+
35+
assertTrue(d != d, "Double: d == d")
36+
assertTrue(d != v, "Double: d == v")
37+
assertTrue(d != va[0], "Double: d == va[0]")
38+
assertTrue(v != d, "Double: v == d")
39+
assertTrue(v != v, "Double: v == v")
40+
assertTrue(v != va[0], "Double: v == va[0]")
41+
assertTrue(va[0] != d, "Double: va[0] == d")
42+
assertTrue(va[0] != v, "Double: va[0] == v")
43+
assertTrue(va[0] != va[0], "Double: va[0] == va[0]")
44+
}
45+
46+
fun <T: Float> testFloat(d: Float, v: T, vararg va: T) {
47+
assertFalse(d == d, "Float: d != d")
48+
assertFalse(d == v, "Float: d != v")
49+
assertFalse(d == va[0], "Float: d != va[0]")
50+
assertFalse(v == d, "Float: v != d")
51+
assertFalse(v == v, "Float: v != v")
52+
assertFalse(v == va[0], "Float: v != va[0]")
53+
assertFalse(va[0] == d, "Float: va[0] != d")
54+
assertFalse(va[0] == v, "Float: va[0] != v")
55+
assertFalse(va[0] == va[0], "Float: va[0] != va[0]")
56+
57+
assertTrue(d != d, "Float: d == d")
58+
assertTrue(d != v, "Float: d == v")
59+
assertTrue(d != va[0], "Float: d == va[0]")
60+
assertTrue(v != d, "Float: v == d")
61+
assertTrue(v != v, "Float: v == v")
62+
assertTrue(v != va[0], "Float: v == va[0]")
63+
assertTrue(va[0] != d, "Float: va[0] == d")
64+
assertTrue(va[0] != v, "Float: va[0] == v")
65+
assertTrue(va[0] != va[0], "Float: va[0] == va[0]")
66+
}
67+
68+
var gdn: Any = Double.NaN
69+
var gfn: Any = Float.NaN
70+
71+
fun box(): String {
72+
73+
// Double
74+
75+
val dn = Double.NaN
76+
val adn: Any = dn
77+
val dnq: Double? = dn
78+
val adnq: Any? = dn
79+
80+
assertFalse(dn == dn, "Double: NaN == NaN")
81+
assertTrue(dn == adn, "Double: NaN != (Any)NaN")
82+
assertTrue(adn == dn, "Double: (Any)NaN != NaN")
83+
assertTrue(adn == adn, "Double: (Any)NaN != (Any)NaN")
84+
85+
assertFalse(dn == dnq, "Double: NaN == NaN?")
86+
assertTrue(dn == adnq, "Double: NaN != (Any?)NaN")
87+
assertTrue(adn == dnq, "Double: (Any)NaN != NaN?")
88+
assertTrue(adn == adnq, "Double: (Any)NaN != (Any?)NaN")
89+
90+
assertFalse(dnq == dn, "Double: NaN? == NaN")
91+
assertTrue(dnq == adn, "Double: NaN? != (Any)NaN")
92+
assertTrue(adnq == dn, "Double: (Any?)NaN != NaN")
93+
assertTrue(adnq == adn, "Double: (Any?)NaN != (Any)NaN")
94+
95+
assertFalse(dnq == dnq, "Double: NaN? == NaN?")
96+
assertTrue(dnq == adnq, "Double: NaN? != (Any?)NaN")
97+
assertTrue(adnq == dnq, "Double: (Any?)NaN != NaN?")
98+
assertTrue(adnq == adnq, "Double: (Any?)NaN != (Any?)NaN")
99+
100+
assertTrue(dn != dn, "Double: NaN == NaN")
101+
assertFalse(dn != adn, "Double: NaN != (Any)NaN")
102+
assertFalse(adn != dn, "Double: (Any)NaN != NaN")
103+
assertFalse(adn != adn, "Double: (Any)NaN != (Any)NaN")
104+
105+
assertTrue(dn != dnq, "Double: NaN == NaN?")
106+
assertFalse(dn != adnq, "Double: NaN != (Any?)NaN")
107+
assertFalse(adn != dnq, "Double: (Any)NaN != NaN?")
108+
assertFalse(adn != adnq, "Double: (Any)NaN != (Any?)NaN")
109+
110+
assertTrue(dnq != dn, "Double: NaN? == NaN")
111+
assertFalse(dnq != adn, "Double: NaN? != (Any)NaN")
112+
assertFalse(adnq != dn, "Double: (Any?)NaN != NaN")
113+
assertFalse(adnq != adn, "Double: (Any?)NaN != (Any)NaN")
114+
115+
assertTrue(dnq != dnq, "Double: NaN? == NaN?")
116+
assertFalse(dnq != adnq, "Double: NaN? != (Any?)NaN")
117+
assertFalse(adnq != dnq, "Double: (Any?)NaN != NaN?")
118+
assertFalse(adnq != adnq, "Double: (Any?)NaN != (Any?)NaN")
119+
120+
// Stable smart-casts
121+
if (adn is Double) {
122+
assertFalse(adn == adn, "Double smart-cast: NaN == NaN")
123+
assertTrue(adn != adn, "Double smart-cast: NaN == NaN")
124+
}
125+
if (adnq is Double?) {
126+
assertFalse(adnq == adnq, "Double? smart-cast: NaN? == NaN?")
127+
assertTrue(adnq != adnq, "Double? smart-cast: NaN? == NaN?")
128+
}
129+
// Unstable smart-casts
130+
if (gdn is Double) {
131+
assertTrue(gdn == gdn, "Unstable Double smart-cast: NaN != NaN")
132+
assertFalse(gdn != gdn, "Unstable Double smart-cast: NaN != NaN")
133+
}
134+
if (gdn is Double?) {
135+
assertTrue(gdn == gdn, "Unstable Double smart-cast: NaN != NaN")
136+
assertFalse(gdn != gdn, "Unstable Double smart-cast: NaN != NaN")
137+
}
138+
139+
// Explicit .equals
140+
assertTrue(A == dn && O.equalsCalled, "A.equals not called for A == dn")
141+
assertTrue(dn != A && !O.equalsCalled, "A.equals called for dn == A")
142+
assertFalse(A != dn || !O.equalsCalled, "A.equals not called for A != dn")
143+
assertFalse(dn == A || O.equalsCalled, "A.equals called for dn != A")
144+
145+
// Generics and varags
146+
testDouble(Double.NaN, Double.NaN, Double.NaN)
147+
148+
// Float
149+
150+
val fn = Float.NaN
151+
val afn: Any = fn
152+
val fnq: Float? = fn
153+
val afnq: Any? = fn
154+
155+
assertFalse(fn == fn, "Float: NaN == NaN")
156+
assertTrue(fn == afn, "Float: NaN != (Any)NaN")
157+
assertTrue(afn == fn, "Float: (Any)NaN != NaN")
158+
assertTrue(afn == afn, "Float: (Any)NaN != (Any)NaN")
159+
160+
assertFalse(fn == fnq, "Float: NaN == NaN?")
161+
assertTrue(fn == afnq, "Float: NaN != (Any?)NaN")
162+
assertTrue(afn == fnq, "Float: (Any)NaN != NaN?")
163+
assertTrue(afn == afnq, "Float: (Any)NaN != (Any?)NaN")
164+
165+
assertFalse(fnq == fn, "Float: NaN? == NaN")
166+
assertTrue(fnq == afn, "Float: NaN? != (Any)NaN")
167+
assertTrue(afnq == fn, "Float: (Any?)NaN != NaN")
168+
assertTrue(afnq == afn, "Float: (Any?)NaN != (Any)NaN")
169+
170+
assertFalse(fnq == fnq, "Float: NaN? == NaN?")
171+
assertTrue(fnq == afnq, "Float: NaN? != (Any?)NaN")
172+
assertTrue(afnq == fnq, "Float: (Any?)NaN != NaN?")
173+
assertTrue(afnq == afnq, "Float: (Any?)NaN != (Any?)NaN")
174+
175+
assertTrue(fn != fn, "Float: NaN == NaN")
176+
assertFalse(fn != afn, "Float: NaN != (Any)NaN")
177+
assertFalse(afn != fn, "Float: (Any)NaN != NaN")
178+
assertFalse(afn != afn, "Float: (Any)NaN != (Any)NaN")
179+
180+
assertTrue(fn != fnq, "Float: NaN == NaN?")
181+
assertFalse(fn != afnq, "Float: NaN != (Any?)NaN")
182+
assertFalse(afn != fnq, "Float: (Any)NaN != NaN?")
183+
assertFalse(afn != afnq, "Float: (Any)NaN != (Any?)NaN")
184+
185+
assertTrue(fnq != fn, "Float: NaN? == NaN")
186+
assertFalse(fnq != afn, "Float: NaN? != (Any)NaN")
187+
assertFalse(afnq != fn, "Float: (Any?)NaN != NaN")
188+
assertFalse(afnq != afn, "Float: (Any?)NaN != (Any)NaN")
189+
190+
assertTrue(fnq != fnq, "Float: NaN? == NaN?")
191+
assertFalse(fnq != afnq, "Float: NaN? != (Any?)NaN")
192+
assertFalse(afnq != fnq, "Float: (Any?)NaN != NaN?")
193+
assertFalse(afnq != afnq, "Float: (Any?)NaN != (Any?)NaN")
194+
195+
// Stable smart-casts
196+
if (afn is Float) {
197+
assertFalse(afn == afn, "Float smart-cast: NaN == NaN")
198+
assertTrue(afn != afn, "Float smart-cast: NaN == NaN")
199+
}
200+
if (afnq is Float?) {
201+
assertFalse(afnq == afnq, "Float? smart-cast: NaN? == NaN?")
202+
assertTrue(afnq != afnq, "Float? smart-cast: NaN? == NaN?")
203+
}
204+
// Unstable smart-casts
205+
if (gfn is Float) {
206+
assertTrue(gfn == gfn, "Unstable Float smart-cast: NaN != NaN")
207+
assertFalse(gfn != gfn, "Unstable Float smart-cast: NaN != NaN")
208+
}
209+
if (gfn is Float?) {
210+
assertTrue(gfn == gfn, "Unstable Float smart-cast: NaN != NaN")
211+
assertFalse(gfn != gfn, "Unstable Float smart-cast: NaN != NaN")
212+
}
213+
214+
assertTrue(A == fn && O.equalsCalled, "A.equals not called for A == fn")
215+
assertTrue(fn != A && !O.equalsCalled, "A.equals called for fn == A")
216+
assertFalse(A != fn || !O.equalsCalled, "A.equals not called for A != fn")
217+
assertFalse(fn == A || O.equalsCalled, "A.equals called for fn != A")
218+
219+
// Generics and varags
220+
testFloat(Float.NaN, Float.NaN, Float.NaN)
221+
222+
return "OK"
223+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@kotlin.Metadata
2+
public final class EqualsNaNKt {
3+
private final static @org.jetbrains.annotations.NotNull field A: java.lang.Object
4+
private static @org.jetbrains.annotations.NotNull field gdn: java.lang.Object
5+
private static @org.jetbrains.annotations.NotNull field gfn: java.lang.Object
6+
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
7+
public final static @org.jetbrains.annotations.NotNull method getA(): java.lang.Object
8+
public final static @org.jetbrains.annotations.NotNull method getGdn(): java.lang.Object
9+
public final static @org.jetbrains.annotations.NotNull method getGfn(): java.lang.Object
10+
public final static method setGdn(@org.jetbrains.annotations.NotNull p0: java.lang.Object): void
11+
public final static method setGfn(@org.jetbrains.annotations.NotNull p0: java.lang.Object): void
12+
public final static method testDouble(p0: double, p1: double, @org.jetbrains.annotations.NotNull p2: java.lang.Double[]): void
13+
public final static method testFloat(p0: float, p1: float, @org.jetbrains.annotations.NotNull p2: java.lang.Float[]): void
14+
}
15+
16+
@kotlin.Metadata
17+
public final class O {
18+
public final static field INSTANCE: O
19+
private static field equalsCalled: boolean
20+
private method <init>(): void
21+
public method equals(@org.jetbrains.annotations.Nullable p0: java.lang.Object): boolean
22+
public final method getEqualsCalled(): boolean
23+
public final method setEqualsCalled(p0: boolean): void
24+
}

compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8525,6 +8525,12 @@ public void testEqualsFloat() throws Exception {
85258525
doTest(fileName);
85268526
}
85278527

8528+
@TestMetadata("equalsNaN.kt")
8529+
public void testEqualsNaN() throws Exception {
8530+
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNaN.kt");
8531+
doTest(fileName);
8532+
}
8533+
85288534
@TestMetadata("equalsNullableDouble.kt")
85298535
public void testEqualsNullableDouble() throws Exception {
85308536
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNullableDouble.kt");

compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8525,6 +8525,12 @@ public void testEqualsFloat() throws Exception {
85258525
doTest(fileName);
85268526
}
85278527

8528+
@TestMetadata("equalsNaN.kt")
8529+
public void testEqualsNaN() throws Exception {
8530+
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNaN.kt");
8531+
doTest(fileName);
8532+
}
8533+
85288534
@TestMetadata("equalsNullableDouble.kt")
85298535
public void testEqualsNullableDouble() throws Exception {
85308536
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNullableDouble.kt");

core/descriptors/src/org/jetbrains/kotlin/builtins/KotlinBuiltIns.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,8 +878,19 @@ public static boolean isPrimitiveArray(@NotNull KotlinType type) {
878878
}
879879

880880
public static boolean isPrimitiveType(@NotNull KotlinType type) {
881+
return !type.isMarkedNullable() && isPrimitiveTypeOrNullablePrimitiveType(type);
882+
}
883+
884+
public static boolean isPrimitiveTypeOrNullablePrimitiveType(@NotNull KotlinType type) {
885+
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
886+
return descriptor instanceof ClassDescriptor && isPrimitiveClass((ClassDescriptor) descriptor);
887+
}
888+
889+
@Nullable
890+
public static PrimitiveType getPrimitiveTypeByKotlinType(@NotNull KotlinType type) {
881891
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
882-
return !type.isMarkedNullable() && descriptor instanceof ClassDescriptor && isPrimitiveClass((ClassDescriptor) descriptor);
892+
if (descriptor == null) return null;
893+
return getPrimitiveTypeByFqName(getFqName(descriptor));
883894
}
884895

885896
public static boolean isPrimitiveClass(@NotNull ClassDescriptor descriptor) {

js/js.libraries/src/js/core.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Kotlin.equals = function (obj1, obj2) {
2323
return false;
2424
}
2525

26+
if (obj1 !== obj1) {
27+
return obj2 !== obj2;
28+
}
29+
2630
if (typeof obj1 == "object" && typeof obj1.equals === "function") {
2731
return obj1.equals(obj2);
2832
}

js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9603,13 +9603,7 @@ public void testComparableTypeCast() throws Exception {
96039603
@TestMetadata("dataClass.kt")
96049604
public void testDataClass() throws Exception {
96059605
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/dataClass.kt");
9606-
try {
9607-
doTest(fileName);
9608-
}
9609-
catch (Throwable ignore) {
9610-
return;
9611-
}
9612-
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
9606+
doTest(fileName);
96139607
}
96149608

96159609
@TestMetadata("equalsDouble.kt")
@@ -9624,6 +9618,12 @@ public void testEqualsFloat() throws Exception {
96249618
doTest(fileName);
96259619
}
96269620

9621+
@TestMetadata("equalsNaN.kt")
9622+
public void testEqualsNaN() throws Exception {
9623+
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNaN.kt");
9624+
doTest(fileName);
9625+
}
9626+
96279627
@TestMetadata("equalsNullableDouble.kt")
96289628
public void testEqualsNullableDouble() throws Exception {
96299629
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/ieee754/equalsNullableDouble.kt");

js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,7 @@ public static JsExpression translateAsExpression(
190190
JsNode jsNode = translateExpression(expression, context, block);
191191
if (jsNode instanceof JsExpression) {
192192
KotlinType expressionType = context.bindingContext().getType(expression);
193-
if (expressionType != null && KotlinBuiltIns.isCharOrNullableChar(expressionType) &&
194-
(jsNode instanceof JsInvocation || jsNode instanceof JsNameRef || jsNode instanceof JsArrayAccess)) {
195-
jsNode = JsAstUtils.boxedCharToChar((JsExpression) jsNode);
196-
}
197-
return (JsExpression) jsNode;
193+
return unboxIfNeeded((JsExpression) jsNode, expressionType != null && KotlinBuiltIns.isCharOrNullableChar(expressionType));
198194
}
199195

200196
assert jsNode instanceof JsStatement : "Unexpected node of type: " + jsNode.getClass().toString();
@@ -209,6 +205,16 @@ public static JsExpression translateAsExpression(
209205
return JsLiteral.NULL;
210206
}
211207

208+
@NotNull
209+
public static JsExpression unboxIfNeeded(@NotNull JsExpression expression, boolean charOrNullableChar) {
210+
if (charOrNullableChar &&
211+
(expression instanceof JsInvocation || expression instanceof JsNameRef || expression instanceof JsArrayAccess)
212+
) {
213+
expression = JsAstUtils.boxedCharToChar(expression);
214+
}
215+
return expression;
216+
}
217+
212218
@NotNull
213219
public static JsStatement translateAsStatement(@NotNull KtExpression expression, @NotNull TranslationContext context) {
214220
return translateAsStatement(expression, context, context.dynamicContext().jsBlock());

0 commit comments

Comments
 (0)