Implemented equality comparison for primitive types

This commit is contained in:
Anton Bannykh
2018-03-01 18:02:26 +03:00
committed by Anton Bannykh
parent 7cde03938a
commit 3a7518cad2
6 changed files with 2747 additions and 113 deletions
+7 -4
View File
@@ -78,6 +78,9 @@ fun box(): String {
val dnq: Double? = dn
val adnq: Any? = dn
// see https://bugs.openjdk.java.net/browse/JDK-8141407
val nanBug = js("dn == dn")
assertFalse(dn == dn, "Double: NaN == NaN")
assertTrue(dn == adn, "Double: NaN != (Any)NaN")
assertTrue(adn == dn, "Double: (Any)NaN != NaN")
@@ -93,7 +96,7 @@ fun box(): String {
assertTrue(adnq == dn, "Double: (Any?)NaN != NaN")
assertTrue(adnq == adn, "Double: (Any?)NaN != (Any)NaN")
assertFalse(dnq == dnq, "Double: NaN? == NaN?")
if (!nanBug) assertFalse(dnq == dnq, "Double: NaN? == NaN?")
assertTrue(dnq == adnq, "Double: NaN? != (Any?)NaN")
assertTrue(adnq == dnq, "Double: (Any?)NaN != NaN?")
assertTrue(adnq == adnq, "Double: (Any?)NaN != (Any?)NaN")
@@ -113,7 +116,7 @@ fun box(): String {
assertFalse(adnq != dn, "Double: (Any?)NaN != NaN")
assertFalse(adnq != adn, "Double: (Any?)NaN != (Any)NaN")
assertTrue(dnq != dnq, "Double: NaN? == NaN?")
if (!nanBug) assertTrue(dnq != dnq, "Double: NaN? == NaN?")
assertFalse(dnq != adnq, "Double: NaN? != (Any?)NaN")
assertFalse(adnq != dnq, "Double: (Any?)NaN != NaN?")
assertFalse(adnq != adnq, "Double: (Any?)NaN != (Any?)NaN")
@@ -168,7 +171,7 @@ fun box(): String {
assertTrue(afnq == fn, "Float: (Any?)NaN != NaN")
assertTrue(afnq == afn, "Float: (Any?)NaN != (Any)NaN")
assertFalse(fnq == fnq, "Float: NaN? == NaN?")
if (!nanBug) assertFalse(fnq == fnq, "Float: NaN? == NaN?")
assertTrue(fnq == afnq, "Float: NaN? != (Any?)NaN")
assertTrue(afnq == fnq, "Float: (Any?)NaN != NaN?")
assertTrue(afnq == afnq, "Float: (Any?)NaN != (Any?)NaN")
@@ -188,7 +191,7 @@ fun box(): String {
assertFalse(afnq != fn, "Float: (Any?)NaN != NaN")
assertFalse(afnq != afn, "Float: (Any?)NaN != (Any)NaN")
assertTrue(fnq != fnq, "Float: NaN? == NaN?")
if (!nanBug) assertTrue(fnq != fnq, "Float: NaN? == NaN?")
assertFalse(fnq != afnq, "Float: NaN? != (Any?)NaN")
assertFalse(afnq != fnq, "Float: (Any?)NaN != NaN?")
assertFalse(afnq != afnq, "Float: (Any?)NaN != (Any?)NaN")
File diff suppressed because it is too large Load Diff
@@ -16780,6 +16780,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
doTest(fileName);
}
@TestMetadata("crossTypeEquals.kt")
public void testCrossTypeEquals() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/primitiveTypes/crossTypeEquals.kt");
doTest(fileName);
}
@TestMetadata("ea35963.kt")
public void testEa35963() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/primitiveTypes/ea35963.kt");
@@ -19,14 +19,9 @@ package org.jetbrains.kotlin.js.translate.intrinsic.operation
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.js.backend.ast.JsBinaryOperation
import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.js.backend.ast.JsIntLiteral
import org.jetbrains.kotlin.js.patterns.PatternBuilder.pattern
import org.jetbrains.kotlin.js.translate.operation.OperatorTable
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.*
import org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken
import org.jetbrains.kotlin.lexer.KtSingleValueToken
import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic
import org.jetbrains.kotlin.types.KotlinType
@@ -36,36 +31,15 @@ import org.jetbrains.kotlin.utils.identity
object CompareToBOIF : BinaryOperationIntrinsicFactory {
override fun getSupportTokens(): Set<KtSingleValueToken> = OperatorConventions.COMPARISON_OPERATIONS
// toLeft(L, R) OP toRight(L, R)
private fun intrinsic(
toLeft: (JsExpression, JsExpression) -> JsExpression,
toRight: (JsExpression, JsExpression) -> JsExpression
): BinaryOperationIntrinsic = { expression, left, right, _ ->
val operator = OperatorTable.getBinaryOperator(getOperationToken(expression))
JsBinaryOperation(operator, toLeft(left, right), toRight(left, right))
}
// toLeft(L) OP toRight(R)
private fun primitiveIntrinsic(
toLeft: (JsExpression) -> JsExpression,
toRight: (JsExpression) -> JsExpression
): BinaryOperationIntrinsic = intrinsic({ l, _ -> toLeft(l) }, { _, r -> toRight(r) })
private fun unboxCharIfNeeded(type: KotlinType): (JsExpression) -> JsExpression = { e ->
if (KotlinBuiltIns.isCharOrNullableChar(type)) {
charToInt(e)
} else e
}
private val patterns = listOf(
pattern("Int|Short|Byte|Float|Double.compareTo(Long)") to primitiveIntrinsic(identity(), ::longToNumber),
pattern("Long.compareTo(Int|Short|Byte|Float|Double)") to primitiveIntrinsic(::longToNumber, identity()),
pattern("Int|Short|Byte|Float|Double.compareTo(Long)") to binaryIntrinsic(toRight = { r, _ -> longToNumber(r) }),
pattern("Long.compareTo(Int|Short|Byte|Float|Double)") to binaryIntrinsic(toLeft = { l, _ -> longToNumber(l) }),
// L.compareTo(R) OP 0
pattern("Long.compareTo(Long)") to intrinsic({ l, r -> compareForObject(l, r) }, { _, _ -> JsIntLiteral(0) })
pattern("Long.compareTo(Long)") to intrinsic({ l, r, _ -> compareForObject(l, r) }, { _, _, _-> JsIntLiteral(0) })
)
override fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic? {
if (descriptor.isDynamic()) return primitiveIntrinsic(identity(), identity())
if (descriptor.isDynamic()) return binaryIntrinsic()
if (leftType == null || rightType == null || !KotlinBuiltIns.isBuiltIn(descriptor)) return null
@@ -73,9 +47,10 @@ object CompareToBOIF : BinaryOperationIntrinsicFactory {
// Types may be nullable if properIeeeComparisons are switched off, e.g. fun foo(a: Double?) = a != null && a < 0.0
return if (isPrimitiveTypeOrNullablePrimitiveType(leftType) && isPrimitiveTypeOrNullablePrimitiveType(rightType)) {
primitiveIntrinsic(unboxCharIfNeeded(leftType), unboxCharIfNeeded(rightType))
binaryIntrinsic(coerceTo(leftType), coerceTo(rightType))
} else {
intrinsic({ l, r -> compareTo(l, r) }, { _, _ -> JsIntLiteral(0) })
// Kotlin.compareTo(L, R) OP 0
intrinsic({ l, r, _ -> compareTo(l, r) }, { _, _, _ -> JsIntLiteral(0) })
}
}
}
@@ -17,19 +17,14 @@
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.JsNullLiteral
import org.jetbrains.kotlin.js.translate.context.TranslationContext
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.PsiUtils.getOperationToken
import org.jetbrains.kotlin.js.translate.utils.PsiUtils.isNegatedOperation
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
@@ -37,89 +32,97 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.expressions.OperatorConventions
import org.jetbrains.kotlin.types.isDynamic
import java.util.*
object EqualsBOIF : BinaryOperationIntrinsicFactory {
override fun getSupportTokens() = OperatorConventions.EQUALS_OPERATIONS!!
private val JS_NUMBER_PRIMITIVES =
EnumSet.of(PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.INT, PrimitiveType.DOUBLE, PrimitiveType.FLOAT)
private fun equalsIntrinsic(expression: KtBinaryExpression, left: JsExpression, right: JsExpression, context: TranslationContext): JsExpression {
val isNegated = expression.isNegated()
val anyType = context.currentModule.builtIns.anyType
if (right is JsNullLiteral || left is JsNullLiteral) {
val (subject, ktSubject) = if (right is JsNullLiteral) Pair(left, expression.left!!) else Pair(right, expression.right!!)
return TranslationUtils.nullCheck(ktSubject, subject, context, isNegatedOperation(expression))
}
val (leftKotlinType, rightKotlinType) = binaryOperationTypes(expression, context)
val leftType = leftKotlinType?.let { KotlinBuiltIns.getPrimitiveType(it) }
val rightType = rightKotlinType?.let { KotlinBuiltIns.getPrimitiveType(it) }
if (leftType != null && rightType != null) {
}
if (leftType != null && rightType != null && (
leftType in JS_NUMBER_PRIMITIVES && rightType in JS_NUMBER_PRIMITIVES ||
leftType in JS_NUMBER_PRIMITIVES && rightType == PrimitiveType.LONG ||
leftType == PrimitiveType.LONG && rightType in JS_NUMBER_PRIMITIVES ||
leftType == PrimitiveType.BOOLEAN && rightType == PrimitiveType.BOOLEAN ||
leftType == PrimitiveType.CHAR && rightType == PrimitiveType.CHAR
)) {
val useEq = leftType == PrimitiveType.LONG || rightType == PrimitiveType.LONG
val operator = when {
useEq && isNegated -> JsBinaryOperator.NEQ
useEq && !isNegated -> JsBinaryOperator.EQ
!useEq && isNegated -> JsBinaryOperator.REF_NEQ
else /* !useEq && !isNegated */ -> JsBinaryOperator.REF_EQ
}
val coercedLeft = TranslationUtils.coerce(context, left, leftKotlinType)
val coercedRight = TranslationUtils.coerce(context, right, rightKotlinType)
return JsBinaryOperation(operator, coercedLeft, coercedRight)
}
val resolvedCall = expression.getResolvedCall(context.bindingContext())
val appliedToDynamic =
resolvedCall != null &&
with(resolvedCall.dispatchReceiver) {
if (this != null) type.isDynamic() else false
}
if (appliedToDynamic) {
return JsBinaryOperation(if (isNegated) JsBinaryOperator.NEQ else JsBinaryOperator.EQ, left, right)
}
val coercedLeft = TranslationUtils.coerce(context, left, anyType)
val coercedRight = TranslationUtils.coerce(context, right, anyType)
val result = TopLevelFIF.KOTLIN_EQUALS.apply(coercedLeft, listOf(coercedRight), context)
return if (isNegated) JsAstUtils.not(result) else result
private val equalsNullIntrinsic: BinaryOperationIntrinsic = { expression, left, right, context ->
val (subject, ktSubject) = if (right is JsNullLiteral) Pair(left, expression.left!!) else Pair(right, expression.right!!)
TranslationUtils.nullCheck(ktSubject, subject, context, isNegatedOperation(expression))
}
override fun getSupportTokens() = OperatorConventions.EQUALS_OPERATIONS!!
private val kotlinEqualsIntrinsic: BinaryOperationIntrinsic = { expression, left, right, context ->
val coercedLeft = TranslationUtils.coerce(context, left, context.currentModule.builtIns.anyType)
val coercedRight = TranslationUtils.coerce(context, right, context.currentModule.builtIns.anyType)
val result = TopLevelFIF.KOTLIN_EQUALS.apply(coercedLeft, listOf(coercedRight), context)
if (isNegatedOperation(expression)) JsAstUtils.not(result) else result
}
private val refEqSelector: OperatorSelector = { if (isNegatedOperation(it)) JsBinaryOperator.REF_NEQ else JsBinaryOperator.REF_EQ }
private val eqSelector: OperatorSelector = { if (isNegatedOperation(it)) JsBinaryOperator.NEQ else JsBinaryOperator.EQ }
private val refEqIntrinsic = binaryIntrinsic(operator = refEqSelector)
private val eqIntrinsic = binaryIntrinsic(operator = eqSelector)
private fun primitiveTypes(
leftKotlinType: KotlinType, rightKotlinType: KotlinType
): BinaryOperationIntrinsic {
fun <T> select(number: T, long: T, bool: T, char: T): (KotlinType) -> T = {
when {
KotlinBuiltIns.isLongOrNullableLong(it) -> long
KotlinBuiltIns.isBooleanOrNullableBoolean(it) -> bool
KotlinBuiltIns.isCharOrNullableChar(it) -> char
else -> number
}
}
val eq = binaryIntrinsic(coerceTo(leftKotlinType), coerceTo(rightKotlinType), eqSelector)
val refEq = binaryIntrinsic(coerceTo(leftKotlinType), coerceTo(rightKotlinType), refEqSelector)
// Used for number to number comparison
val default = { leftNullable: Boolean, rightNullable: Boolean ->
if (leftNullable && rightNullable) eq else refEq
}
// Used to compare Boolean with number types and Long. Kotlin.equals handles cases like 0: Int? == false: Boolean?
val bool = { leftNullable: Boolean, rightNullable: Boolean ->
if (leftNullable && rightNullable) kotlinEqualsIntrinsic else refEq
}
// Used to compare Long with number types.
val allEq = { _: Boolean, _: Boolean -> eq }
// Used to compare Char with other primitive types and Long with Long
val allKEq = { _: Boolean, _: Boolean -> kotlinEqualsIntrinsic }
return select(
select(default, allEq, bool, allKEq),
select(allEq, allKEq, bool, allKEq),
select(bool, bool, default, allKEq),
select(allKEq, allKEq, allKEq, default)
)(leftKotlinType)(rightKotlinType)(TypeUtils.isNullableType(leftKotlinType), TypeUtils.isNullableType(rightKotlinType))
}
override fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic? =
when {
isEnumEqualsIntrinsicApplicable(descriptor, leftType, rightType) -> { expression, left, right, _ ->
val operator = if (expression.isNegated()) JsBinaryOperator.REF_NEQ else JsBinaryOperator.REF_EQ
JsBinaryOperation(operator, left, right)
}
leftType == null || rightType == null -> null
KotlinBuiltIns.isBuiltIn(descriptor) ||
TopLevelFIF.EQUALS_IN_ANY.test(descriptor) -> ::equalsIntrinsic
isEnumEqualsIntrinsicApplicable(descriptor, leftType, rightType) -> refEqIntrinsic
KotlinBuiltIns.isBuiltIn(descriptor) || TopLevelFIF.EQUALS_IN_ANY.test(descriptor) -> { expression, left, right, context ->
when {
left is JsNullLiteral || right is JsNullLiteral -> equalsNullIntrinsic
leftType.primitive && rightType.primitive -> primitiveTypes(leftType, rightType)
expression.appliedToDynamic(context) -> eqIntrinsic
else -> kotlinEqualsIntrinsic
}(expression, left, right, context)
}
else -> null
}
private fun isEnumEqualsIntrinsicApplicable(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): Boolean {
return DescriptorUtils.isEnumClass(descriptor.containingDeclaration) && leftType != null && rightType != null &&
private fun KtBinaryExpression.appliedToDynamic(context: TranslationContext) =
getResolvedCall(context.bindingContext())?.dispatchReceiver?.type?.isDynamic() ?: false
private val KotlinType.primitive
get() = KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(this)
private fun isEnumEqualsIntrinsicApplicable(descriptor: FunctionDescriptor, leftType: KotlinType, rightType: KotlinType): Boolean {
return DescriptorUtils.isEnumClass(descriptor.containingDeclaration) &&
!TypeUtils.isNullableType(leftType) && !TypeUtils.isNullableType(rightType)
}
private fun KtBinaryExpression.isNegated() = getOperationToken(this) == KtTokens.EXCLEQ
}
@@ -18,9 +18,13 @@ package org.jetbrains.kotlin.js.translate.intrinsic.operation
import org.jetbrains.kotlin.js.backend.ast.JsExpression
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.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.operation.OperatorTable
import org.jetbrains.kotlin.js.translate.utils.BindingUtils.getCallableDescriptorForOperationExpression
import org.jetbrains.kotlin.js.translate.utils.PsiUtils.getOperationToken
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.js.translate.utils.getPrecisePrimitiveType
import org.jetbrains.kotlin.js.translate.utils.getPrimitiveNumericComparisonInfo
import org.jetbrains.kotlin.lexer.KtToken
@@ -43,7 +47,8 @@ class BinaryOperationIntrinsics {
private val intrinsicCache = mutableMapOf<IntrinsicKey, BinaryOperationIntrinsic?>()
fun getIntrinsic(expression: KtBinaryExpression, context: TranslationContext): BinaryOperationIntrinsic? {
val descriptor = getCallableDescriptorForOperationExpression(context.bindingContext(), expression) as? FunctionDescriptor ?: return null
val descriptor =
getCallableDescriptorForOperationExpression(context.bindingContext(), expression) as? FunctionDescriptor ?: return null
val (leftType, rightType) = binaryOperationTypes(expression, context)
@@ -83,4 +88,30 @@ interface BinaryOperationIntrinsicFactory {
fun getSupportTokens(): Set<KtToken>
fun getIntrinsic(descriptor: FunctionDescriptor, leftType: KotlinType?, rightType: KotlinType?): BinaryOperationIntrinsic?
}
}
typealias OperatorSelector = (KtBinaryExpression) -> JsBinaryOperator
val defaultOperatorSelector: OperatorSelector = { OperatorTable.getBinaryOperator(getOperationToken(it)) }
// toLeft(L, R) OP toRight(L, R)
fun intrinsic(
toLeft: (JsExpression, JsExpression, TranslationContext) -> JsExpression,
toRight: (JsExpression, JsExpression, TranslationContext) -> JsExpression,
operator: (KtBinaryExpression) -> JsBinaryOperator = defaultOperatorSelector
): BinaryOperationIntrinsic = { expression, left, right, context ->
JsBinaryOperation(operator(expression), toLeft(left, right, context), toRight(left, right, context))
}
// toLeft(L, C) OP toRight(R, C)
fun binaryIntrinsic(
toLeft: (JsExpression, TranslationContext) -> JsExpression = { l, _ -> l },
toRight: (JsExpression, TranslationContext) -> JsExpression = { r, _ -> r },
operator: OperatorSelector = defaultOperatorSelector
): BinaryOperationIntrinsic = intrinsic({ l, _, c -> toLeft(l, c) }, { _, r, c -> toRight(r, c) }, operator)
fun coerceTo(type: KotlinType): (JsExpression, TranslationContext) -> JsExpression =
{ e, c ->
TranslationUtils.coerce(c, e, type)
}