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 @Nullable
private static KotlinType getLeftOperandType(@Nullable PrimitiveNumericComparisonInfo numericComparisonInfo) { private static KotlinType getLeftOperandType(@Nullable PrimitiveNumericComparisonInfo numericComparisonInfo) {
if (numericComparisonInfo == null) return null; if (numericComparisonInfo == null) return null;
return numericComparisonInfo.getLeftType(); return numericComparisonInfo.getLeftPrimitiveType();
} }
@Nullable @Nullable
private static KotlinType getRightOperandType(@Nullable PrimitiveNumericComparisonInfo numericComparisonInfo) { private static KotlinType getRightOperandType(@Nullable PrimitiveNumericComparisonInfo numericComparisonInfo) {
if (numericComparisonInfo == null) return null; if (numericComparisonInfo == null) return null;
return numericComparisonInfo.getRightType(); return numericComparisonInfo.getRightPrimitiveType();
} }
/*tries to use IEEE 754 arithmetic*/ /*tries to use IEEE 754 arithmetic*/
@@ -21,6 +21,8 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
class PrimitiveNumericComparisonInfo( class PrimitiveNumericComparisonInfo(
val comparisonType: KotlinType, val comparisonType: KotlinType,
val leftPrimitiveType: KotlinType,
val rightPrimitiveType: KotlinType,
val leftType: KotlinType, val leftType: KotlinType,
val rightType: KotlinType val rightType: KotlinType
) )
@@ -49,14 +51,20 @@ object PrimitiveNumericComparisonCallChecker : CallChecker {
rightTypes: List<KotlinType>, rightTypes: List<KotlinType>,
comparison: KtExpression comparison: KtExpression
) { ) {
val leftPrimitiveType = leftTypes.findPrimitiveType() ?: return val leftPrimitiveOrNullableType = leftTypes.findPrimitiveOrNullablePrimitiveType() ?: return
val rightPrimitiveType = rightTypes.findPrimitiveType() ?: return val rightPrimitiveOrNullableType = rightTypes.findPrimitiveOrNullablePrimitiveType() ?: return
val leftPrimitiveType = leftPrimitiveOrNullableType.makeNotNullable()
val rightPrimitiveType = rightPrimitiveOrNullableType.makeNotNullable()
val leastCommonType = leastCommonPrimitiveNumericType(leftPrimitiveType, rightPrimitiveType) val leastCommonType = leastCommonPrimitiveNumericType(leftPrimitiveType, rightPrimitiveType)
trace.record( trace.record(
BindingContext.PRIMITIVE_NUMERIC_COMPARISON_INFO, BindingContext.PRIMITIVE_NUMERIC_COMPARISON_INFO,
comparison, comparison,
PrimitiveNumericComparisonInfo(leastCommonType, leftPrimitiveType, rightPrimitiveType) PrimitiveNumericComparisonInfo(
leastCommonType,
leftPrimitiveType, rightPrimitiveType,
leftPrimitiveOrNullableType, rightPrimitiveOrNullableType
)
) )
} }
@@ -90,15 +98,17 @@ object PrimitiveNumericComparisonCallChecker : CallChecker {
return listOf(type) + stableTypes return listOf(type) + stableTypes
} }
private fun List<KotlinType>.findPrimitiveType() = private fun List<KotlinType>.findPrimitiveOrNullablePrimitiveType() =
firstNotNullResult { it.getPrimitiveTypeOrSupertype() } firstNotNullResult { it.getPrimitiveTypeOrSupertype() }
private fun KotlinType.getPrimitiveTypeOrSupertype(): KotlinType? = private fun KotlinType.getPrimitiveTypeOrSupertype(): KotlinType? =
when { when {
constructor.declarationDescriptor is TypeParameterDescriptor -> constructor.declarationDescriptor is TypeParameterDescriptor ->
immediateSupertypes().firstNotNullResult { it.getPrimitiveTypeOrSupertype() } immediateSupertypes().firstNotNullResult {
it.getPrimitiveTypeOrSupertype()
}
isPrimitiveNumberOrNullableType() -> isPrimitiveNumberOrNullableType() ->
makeNotNullable() this
else -> else ->
null null
} }
@@ -326,8 +326,8 @@ class OperatorExpressionGenerator(statementGenerator: StatementGenerator) : Stat
eqeqSymbol, eqeqSymbol,
context.irBuiltIns.booleanType, context.irBuiltIns.booleanType,
irOperator, irOperator,
expression.left!!.generateAsPrimitiveNumericComparisonOperand(comparisonInfo?.leftType, comparisonType), expression.left!!.generateAsPrimitiveNumericComparisonOperand(comparisonInfo?.leftPrimitiveType, comparisonType),
expression.right!!.generateAsPrimitiveNumericComparisonOperand(comparisonInfo?.rightType, comparisonType) expression.right!!.generateAsPrimitiveNumericComparisonOperand(comparisonInfo?.rightPrimitiveType, comparisonType)
) )
return when (irOperator) { return when (irOperator) {
@@ -363,8 +363,8 @@ class OperatorExpressionGenerator(statementGenerator: StatementGenerator) : Stat
eqeqSymbol, eqeqSymbol,
context.irBuiltIns.booleanType, context.irBuiltIns.booleanType,
irOperator, irOperator,
arg1.promoteToPrimitiveNumericType(comparisonInfo.leftType, comparisonType), arg1.promoteToPrimitiveNumericType(comparisonInfo.leftPrimitiveType, comparisonType),
arg2.promoteToPrimitiveNumericType(comparisonInfo.rightType, comparisonType) arg2.promoteToPrimitiveNumericType(comparisonInfo.rightPrimitiveType, comparisonType)
) )
} else { } else {
primitiveOp2( primitiveOp2(
@@ -456,8 +456,8 @@ class OperatorExpressionGenerator(statementGenerator: StatementGenerator) : Stat
getComparisonOperatorSymbol(origin, kotlinTypeToIrType(comparisonType) ?: error("$comparisonType expected to be primitive")), getComparisonOperatorSymbol(origin, kotlinTypeToIrType(comparisonType) ?: error("$comparisonType expected to be primitive")),
context.irBuiltIns.booleanType, context.irBuiltIns.booleanType,
origin, origin,
ktLeft.generateAsPrimitiveNumericComparisonOperand(comparisonInfo.leftType, comparisonInfo.comparisonType), ktLeft.generateAsPrimitiveNumericComparisonOperand(comparisonInfo.leftPrimitiveType, comparisonInfo.comparisonType),
ktRight.generateAsPrimitiveNumericComparisonOperand(comparisonInfo.rightType, comparisonInfo.comparisonType) ktRight.generateAsPrimitiveNumericComparisonOperand(comparisonInfo.rightPrimitiveType, comparisonInfo.comparisonType)
) )
} else { } else {
val resolvedCall = getResolvedCall(ktExpression) val resolvedCall = getResolvedCall(ktExpression)
@@ -1,6 +1,5 @@
// IGNORE_BACKEND_FIR: JVM_IR // IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// reason - multifile tests are not supported in JS tests
//FILE: Holder.java //FILE: Holder.java
class Holder { class Holder {
@@ -1,6 +1,5 @@
// IGNORE_BACKEND_FIR: JVM_IR // IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// reason - multifile tests are not supported in JS tests
//FILE: JavaClass.java //FILE: JavaClass.java
class JavaClass { class JavaClass {
@@ -1,6 +1,5 @@
// IGNORE_BACKEND_FIR: JVM_IR // IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// reason - multifile tests are not supported in JS tests
//FILE: Holder.java //FILE: Holder.java
import org.jetbrains.annotations.*; import org.jetbrains.annotations.*;
@@ -1,6 +1,5 @@
// IGNORE_BACKEND_FIR: JVM_IR // IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// reason - multifile tests are not supported in JS tests
//FILE: JavaClass.java //FILE: JavaClass.java
import org.jetbrains.annotations.*; 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"); 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") @TestMetadata("intrinsic.kt")
public void testIntrinsic() throws Exception { public void testIntrinsic() throws Exception {
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt"); 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"); 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") @TestMetadata("intrinsic.kt")
public void testIntrinsic() throws Exception { public void testIntrinsic() throws Exception {
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt"); 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"); 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") @TestMetadata("intrinsic.kt")
public void testIntrinsic() throws Exception { public void testIntrinsic() throws Exception {
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt"); 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"); 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") @TestMetadata("intrinsic.kt")
public void testIntrinsic() throws Exception { public void testIntrinsic() throws Exception {
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt"); 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"); 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") @TestMetadata("intrinsic.kt")
public void testIntrinsic() throws Exception { public void testIntrinsic() throws Exception {
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt"); 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"); 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") @TestMetadata("intrinsic.kt")
public void testIntrinsic() throws Exception { public void testIntrinsic() throws Exception {
runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt"); runTest("compiler/testData/codegen/box/binaryOp/intrinsic.kt");