JS: fixed Double.NaN behaviour (KT-13610).

This commit is contained in:
Anton Bannykh
2017-02-27 18:44:50 +03:00
parent f636ab21f8
commit 9e5ecc11b7
12 changed files with 357 additions and 63 deletions
+4
View File
@@ -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);
}
@@ -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());
@@ -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