Fix nullable number comparisons in JS

This commit is contained in:
Dmitry Petrov
2020-01-14 16:09:30 +03:00
parent f8341ad7eb
commit 933ac3a16b
15 changed files with 168 additions and 18 deletions
@@ -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*/
@@ -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
}
@@ -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"
}
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");