Fix nullable number comparisons in JS
This commit is contained in:
@@ -3756,13 +3756,13 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
@Nullable
|
||||
private static KotlinType getLeftOperandType(@Nullable PrimitiveNumericComparisonInfo numericComparisonInfo) {
|
||||
if (numericComparisonInfo == null) return null;
|
||||
return numericComparisonInfo.getLeftType();
|
||||
return numericComparisonInfo.getLeftPrimitiveType();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static KotlinType getRightOperandType(@Nullable PrimitiveNumericComparisonInfo numericComparisonInfo) {
|
||||
if (numericComparisonInfo == null) return null;
|
||||
return numericComparisonInfo.getRightType();
|
||||
return numericComparisonInfo.getRightPrimitiveType();
|
||||
}
|
||||
|
||||
/*tries to use IEEE 754 arithmetic*/
|
||||
|
||||
+16
-6
@@ -21,6 +21,8 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
|
||||
|
||||
class PrimitiveNumericComparisonInfo(
|
||||
val comparisonType: KotlinType,
|
||||
val leftPrimitiveType: KotlinType,
|
||||
val rightPrimitiveType: KotlinType,
|
||||
val leftType: KotlinType,
|
||||
val rightType: KotlinType
|
||||
)
|
||||
@@ -49,14 +51,20 @@ object PrimitiveNumericComparisonCallChecker : CallChecker {
|
||||
rightTypes: List<KotlinType>,
|
||||
comparison: KtExpression
|
||||
) {
|
||||
val leftPrimitiveType = leftTypes.findPrimitiveType() ?: return
|
||||
val rightPrimitiveType = rightTypes.findPrimitiveType() ?: return
|
||||
val leftPrimitiveOrNullableType = leftTypes.findPrimitiveOrNullablePrimitiveType() ?: return
|
||||
val rightPrimitiveOrNullableType = rightTypes.findPrimitiveOrNullablePrimitiveType() ?: return
|
||||
val leftPrimitiveType = leftPrimitiveOrNullableType.makeNotNullable()
|
||||
val rightPrimitiveType = rightPrimitiveOrNullableType.makeNotNullable()
|
||||
val leastCommonType = leastCommonPrimitiveNumericType(leftPrimitiveType, rightPrimitiveType)
|
||||
|
||||
trace.record(
|
||||
BindingContext.PRIMITIVE_NUMERIC_COMPARISON_INFO,
|
||||
comparison,
|
||||
PrimitiveNumericComparisonInfo(leastCommonType, leftPrimitiveType, rightPrimitiveType)
|
||||
PrimitiveNumericComparisonInfo(
|
||||
leastCommonType,
|
||||
leftPrimitiveType, rightPrimitiveType,
|
||||
leftPrimitiveOrNullableType, rightPrimitiveOrNullableType
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,15 +98,17 @@ object PrimitiveNumericComparisonCallChecker : CallChecker {
|
||||
return listOf(type) + stableTypes
|
||||
}
|
||||
|
||||
private fun List<KotlinType>.findPrimitiveType() =
|
||||
private fun List<KotlinType>.findPrimitiveOrNullablePrimitiveType() =
|
||||
firstNotNullResult { it.getPrimitiveTypeOrSupertype() }
|
||||
|
||||
private fun KotlinType.getPrimitiveTypeOrSupertype(): KotlinType? =
|
||||
when {
|
||||
constructor.declarationDescriptor is TypeParameterDescriptor ->
|
||||
immediateSupertypes().firstNotNullResult { it.getPrimitiveTypeOrSupertype() }
|
||||
immediateSupertypes().firstNotNullResult {
|
||||
it.getPrimitiveTypeOrSupertype()
|
||||
}
|
||||
isPrimitiveNumberOrNullableType() ->
|
||||
makeNotNullable()
|
||||
this
|
||||
else ->
|
||||
null
|
||||
}
|
||||
|
||||
+6
-6
@@ -326,8 +326,8 @@ class OperatorExpressionGenerator(statementGenerator: StatementGenerator) : Stat
|
||||
eqeqSymbol,
|
||||
context.irBuiltIns.booleanType,
|
||||
irOperator,
|
||||
expression.left!!.generateAsPrimitiveNumericComparisonOperand(comparisonInfo?.leftType, comparisonType),
|
||||
expression.right!!.generateAsPrimitiveNumericComparisonOperand(comparisonInfo?.rightType, comparisonType)
|
||||
expression.left!!.generateAsPrimitiveNumericComparisonOperand(comparisonInfo?.leftPrimitiveType, comparisonType),
|
||||
expression.right!!.generateAsPrimitiveNumericComparisonOperand(comparisonInfo?.rightPrimitiveType, comparisonType)
|
||||
)
|
||||
|
||||
return when (irOperator) {
|
||||
@@ -363,8 +363,8 @@ class OperatorExpressionGenerator(statementGenerator: StatementGenerator) : Stat
|
||||
eqeqSymbol,
|
||||
context.irBuiltIns.booleanType,
|
||||
irOperator,
|
||||
arg1.promoteToPrimitiveNumericType(comparisonInfo.leftType, comparisonType),
|
||||
arg2.promoteToPrimitiveNumericType(comparisonInfo.rightType, comparisonType)
|
||||
arg1.promoteToPrimitiveNumericType(comparisonInfo.leftPrimitiveType, comparisonType),
|
||||
arg2.promoteToPrimitiveNumericType(comparisonInfo.rightPrimitiveType, comparisonType)
|
||||
)
|
||||
} else {
|
||||
primitiveOp2(
|
||||
@@ -456,8 +456,8 @@ class OperatorExpressionGenerator(statementGenerator: StatementGenerator) : Stat
|
||||
getComparisonOperatorSymbol(origin, kotlinTypeToIrType(comparisonType) ?: error("$comparisonType expected to be primitive")),
|
||||
context.irBuiltIns.booleanType,
|
||||
origin,
|
||||
ktLeft.generateAsPrimitiveNumericComparisonOperand(comparisonInfo.leftType, comparisonInfo.comparisonType),
|
||||
ktRight.generateAsPrimitiveNumericComparisonOperand(comparisonInfo.rightType, comparisonInfo.comparisonType)
|
||||
ktLeft.generateAsPrimitiveNumericComparisonOperand(comparisonInfo.leftPrimitiveType, comparisonInfo.comparisonType),
|
||||
ktRight.generateAsPrimitiveNumericComparisonOperand(comparisonInfo.rightPrimitiveType, comparisonInfo.comparisonType)
|
||||
)
|
||||
} else {
|
||||
val resolvedCall = getResolvedCall(ktExpression)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
// reason - multifile tests are not supported in JS tests
|
||||
//FILE: Holder.java
|
||||
|
||||
class Holder {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
// reason - multifile tests are not supported in JS tests
|
||||
//FILE: JavaClass.java
|
||||
|
||||
class JavaClass {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
// reason - multifile tests are not supported in JS tests
|
||||
//FILE: Holder.java
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
// reason - multifile tests are not supported in JS tests
|
||||
//FILE: JavaClass.java
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// !LANGUAGE: +ProperIeee754Comparisons
|
||||
// IGNORE_BACKEND: JS
|
||||
// ^ if (eq_double_any(0.0, 0)) throw AssertionError("eq_double_any(0.0, 0)")
|
||||
|
||||
fun eq_double_doubleN(a: Double, b: Double?) = a == b
|
||||
|
||||
fun eq_double_any(a: Double, b: Any) = a == b
|
||||
|
||||
fun eq_double_anyN(a: Double, b: Any?) = a == b
|
||||
|
||||
fun eq_doubleN_double(a: Double?, b: Double) = a == b
|
||||
|
||||
fun eq_doubleN_doubleN(a: Double?, b: Double?) = a == b
|
||||
|
||||
fun eq_doubleN_any(a: Double?, b: Any) = a == b
|
||||
|
||||
fun eq_doubleN_anyN(a: Double?, b: Any?) = a == b
|
||||
|
||||
fun box(): String {
|
||||
if (!eq_double_doubleN(0.0, -0.0)) throw AssertionError("!eq_double_doubleN(0.0, -0.0)")
|
||||
if (eq_double_doubleN(0.0, null)) throw AssertionError("eq_double_doubleN(0.0, null)")
|
||||
if (!eq_double_any(0.0, 0.0)) throw AssertionError("!eq_double_any(0.0, 0.0)")
|
||||
if (eq_double_any(0.0, -0.0)) throw AssertionError("eq_double_any(0.0, -0.0)")
|
||||
if (eq_double_any(0.0, 0)) throw AssertionError("eq_double_any(0.0, 0)")
|
||||
if (!eq_double_anyN(0.0, 0.0)) throw AssertionError("!eq_double_anyN(0.0, 0.0)")
|
||||
if (eq_double_anyN(0.0, -0.0)) throw AssertionError("eq_double_anyN(0.0, -0.0)")
|
||||
if (eq_double_anyN(0.0, 0)) throw AssertionError("eq_double_anyN(0.0, 0)")
|
||||
if (eq_double_anyN(0.0, null)) throw AssertionError("eq_double_anyN(0.0, null)")
|
||||
|
||||
if (eq_doubleN_double(null, 0.0)) throw AssertionError("eq_doubleN_double(null, 0.0)")
|
||||
if (!eq_doubleN_doubleN(0.0, -0.0)) throw AssertionError("!eq_doubleN_doubleN(0.0, -0.0)")
|
||||
if (eq_doubleN_doubleN(0.0, null)) throw AssertionError("eq_doubleN_doubleN(0.0, null)")
|
||||
if (!eq_doubleN_any(0.0, 0.0)) throw AssertionError("!eq_doubleN_any(0.0, 0.0)")
|
||||
if (eq_doubleN_any(0.0, -0.0)) throw AssertionError("eq_doubleN_any(0.0, -0.0)")
|
||||
if (eq_doubleN_any(0.0, 0)) throw AssertionError("eq_doubleN_any(0.0, 0)")
|
||||
if (!eq_doubleN_anyN(0.0, 0.0)) throw AssertionError("!eq_doubleN_anyN(0.0, 0.0)")
|
||||
if (eq_doubleN_anyN(0.0, -0.0)) throw AssertionError("eq_doubleN_anyN(0.0, -0.0)")
|
||||
if (eq_doubleN_anyN(0.0, 0)) throw AssertionError("eq_doubleN_anyN(0.0, 0)")
|
||||
if (eq_doubleN_anyN(0.0, null)) throw AssertionError("eq_doubleN_anyN(0.0, null)")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// !LANGUAGE: +ProperIeee754Comparisons
|
||||
// IGNORE_BACKEND: JS
|
||||
// ^ if (eq_double_any(0.0, 0)) throw AssertionError("eq_double_any(0.0, 0)")
|
||||
|
||||
fun <A: Double, B: Double?> eq_double_doubleN(a: A, b: B) = a == b
|
||||
|
||||
fun <A: Double, B: Any> eq_double_any(a: A, b: B) = a == b
|
||||
|
||||
fun <A: Double, B: Any?> eq_double_anyN(a: A, b: B) = a == b
|
||||
|
||||
fun <A: Double?, B: Double> eq_doubleN_double(a: A, b: B) = a == b
|
||||
|
||||
fun <A: Double?, B: Double?> eq_doubleN_doubleN(a: A, b: B) = a == b
|
||||
|
||||
fun <A: Double?, B: Any> eq_doubleN_any(a: A, b: B) = a == b
|
||||
|
||||
fun <A: Double?, B: Any?> eq_doubleN_anyN(a: A, b: B) = a == b
|
||||
|
||||
fun box(): String {
|
||||
if (!eq_double_doubleN(0.0, -0.0)) throw AssertionError("!eq_double_doubleN(0.0, -0.0)")
|
||||
if (eq_double_doubleN(0.0, null)) throw AssertionError("eq_double_doubleN(0.0, null)")
|
||||
if (!eq_double_any(0.0, 0.0)) throw AssertionError("!eq_double_any(0.0, 0.0)")
|
||||
if (eq_double_any(0.0, -0.0)) throw AssertionError("eq_double_any(0.0, -0.0)")
|
||||
if (eq_double_any(0.0, 0)) throw AssertionError("eq_double_any(0.0, 0)")
|
||||
if (!eq_double_anyN(0.0, 0.0)) throw AssertionError("!eq_double_anyN(0.0, 0.0)")
|
||||
if (eq_double_anyN(0.0, -0.0)) throw AssertionError("eq_double_anyN(0.0, -0.0)")
|
||||
if (eq_double_anyN(0.0, 0)) throw AssertionError("eq_double_anyN(0.0, 0)")
|
||||
if (eq_double_anyN(0.0, null)) throw AssertionError("eq_double_anyN(0.0, null)")
|
||||
|
||||
if (eq_doubleN_double(null, 0.0)) throw AssertionError("eq_doubleN_double(null, 0.0)")
|
||||
if (!eq_doubleN_doubleN(0.0, -0.0)) throw AssertionError("!eq_doubleN_doubleN(0.0, -0.0)")
|
||||
if (eq_doubleN_doubleN(0.0, null)) throw AssertionError("eq_doubleN_doubleN(0.0, null)")
|
||||
if (!eq_doubleN_any(0.0, 0.0)) throw AssertionError("!eq_doubleN_any(0.0, 0.0)")
|
||||
if (eq_doubleN_any(0.0, -0.0)) throw AssertionError("eq_doubleN_any(0.0, -0.0)")
|
||||
if (eq_doubleN_any(0.0, 0)) throw AssertionError("eq_doubleN_any(0.0, 0)")
|
||||
if (!eq_doubleN_anyN(0.0, 0.0)) throw AssertionError("!eq_doubleN_anyN(0.0, 0.0)")
|
||||
if (eq_doubleN_anyN(0.0, -0.0)) throw AssertionError("eq_doubleN_anyN(0.0, -0.0)")
|
||||
if (eq_doubleN_anyN(0.0, 0)) throw AssertionError("eq_doubleN_anyN(0.0, 0)")
|
||||
if (eq_doubleN_anyN(0.0, null)) throw AssertionError("eq_doubleN_anyN(0.0, null)")
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+10
@@ -1116,6 +1116,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/divisionByZero.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoubles.kt")
|
||||
public void testEqNullableDoubles() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoubles.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoublesWithTP.kt")
|
||||
public void testEqNullableDoublesWithTP() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoublesWithTP.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intrinsic.kt")
|
||||
public void testIntrinsic() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt");
|
||||
|
||||
+10
@@ -1116,6 +1116,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTest("compiler/testData/codegen/box/binaryOp/divisionByZero.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoubles.kt")
|
||||
public void testEqNullableDoubles() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoubles.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoublesWithTP.kt")
|
||||
public void testEqNullableDoublesWithTP() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoublesWithTP.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intrinsic.kt")
|
||||
public void testIntrinsic() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt");
|
||||
|
||||
+10
@@ -1096,6 +1096,16 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
runTest("compiler/testData/codegen/box/binaryOp/divisionByZero.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoubles.kt")
|
||||
public void testEqNullableDoubles() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoubles.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoublesWithTP.kt")
|
||||
public void testEqNullableDoublesWithTP() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoublesWithTP.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intrinsic.kt")
|
||||
public void testIntrinsic() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt");
|
||||
|
||||
+10
@@ -1096,6 +1096,16 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/binaryOp/divisionByZero.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoubles.kt")
|
||||
public void testEqNullableDoubles() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoubles.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoublesWithTP.kt")
|
||||
public void testEqNullableDoublesWithTP() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoublesWithTP.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intrinsic.kt")
|
||||
public void testIntrinsic() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt");
|
||||
|
||||
Generated
+10
@@ -756,6 +756,16 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/divisionByZero.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoubles.kt")
|
||||
public void testEqNullableDoubles() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoubles.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoublesWithTP.kt")
|
||||
public void testEqNullableDoublesWithTP() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoublesWithTP.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intrinsic.kt")
|
||||
public void testIntrinsic() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt");
|
||||
|
||||
+10
@@ -756,6 +756,16 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/divisionByZero.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoubles.kt")
|
||||
public void testEqNullableDoubles() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoubles.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("eqNullableDoublesWithTP.kt")
|
||||
public void testEqNullableDoublesWithTP() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/eqNullableDoublesWithTP.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intrinsic.kt")
|
||||
public void testIntrinsic() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt");
|
||||
|
||||
Reference in New Issue
Block a user