[JS IR BE] Refactor equals and compareTo lowering
This commit is contained in:
@@ -134,6 +134,8 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
|
|||||||
val jsToString = getInternalFunction("toString")
|
val jsToString = getInternalFunction("toString")
|
||||||
val jsAnyToString = getInternalFunction("anyToString")
|
val jsAnyToString = getInternalFunction("anyToString")
|
||||||
val jsCompareTo = getInternalFunction("compareTo")
|
val jsCompareTo = getInternalFunction("compareTo")
|
||||||
|
// TODO: Use annotations
|
||||||
|
val compareToDoNotIntrinsicify = getInternalFunction("compareToDoNotIntrinsicify")
|
||||||
val jsEquals = getInternalFunction("equals")
|
val jsEquals = getInternalFunction("equals")
|
||||||
|
|
||||||
// Coroutines
|
// Coroutines
|
||||||
|
|||||||
+150
-220
@@ -6,9 +6,9 @@
|
|||||||
package org.jetbrains.kotlin.ir.backend.js.lower
|
package org.jetbrains.kotlin.ir.backend.js.lower
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
import org.jetbrains.kotlin.backend.common.utils.isSubtypeOf
|
|
||||||
import org.jetbrains.kotlin.backend.common.utils.isSubtypeOfClass
|
import org.jetbrains.kotlin.backend.common.utils.isSubtypeOfClass
|
||||||
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
|
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
|
||||||
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||||
@@ -16,10 +16,6 @@ import org.jetbrains.kotlin.ir.backend.js.ir.irCall
|
|||||||
import org.jetbrains.kotlin.ir.backend.js.utils.ConversionNames
|
import org.jetbrains.kotlin.ir.backend.js.utils.ConversionNames
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
|
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
|
import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||||
@@ -131,9 +127,8 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
|||||||
symbolToTransformer.run {
|
symbolToTransformer.run {
|
||||||
add(irBuiltIns.eqeqeqSymbol, intrinsics.jsEqeqeq)
|
add(irBuiltIns.eqeqeqSymbol, intrinsics.jsEqeqeq)
|
||||||
add(irBuiltIns.eqeqSymbol, ::transformEqeqOperator)
|
add(irBuiltIns.eqeqSymbol, ::transformEqeqOperator)
|
||||||
|
// ieee754equals can only be applied in between statically known Floats, Doubles, null or undefined
|
||||||
// ieee754equals can only be applied in between statically known Floats or Doubles
|
add(irBuiltIns.ieee754equalsFunByOperandType, ::chooseEqualityOperatorForPrimitiveTypes)
|
||||||
add(irBuiltIns.ieee754equalsFunByOperandType, intrinsics.jsEqeqeq)
|
|
||||||
|
|
||||||
add(irBuiltIns.booleanNotSymbol, intrinsics.jsNot)
|
add(irBuiltIns.booleanNotSymbol, intrinsics.jsNot)
|
||||||
|
|
||||||
@@ -237,11 +232,6 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
addWithPredicate(
|
|
||||||
Name.identifier("compareTo"), ::shouldReplaceCompareToWithRuntimeCall,
|
|
||||||
{ call -> irCall(call, intrinsics.jsCompareTo, dispatchReceiverAsFirstArgument = true) }
|
|
||||||
)
|
|
||||||
|
|
||||||
put(Name.identifier("toString")) { call ->
|
put(Name.identifier("toString")) { call ->
|
||||||
if (shouldReplaceToStringWithRuntimeCall(call)) {
|
if (shouldReplaceToStringWithRuntimeCall(call)) {
|
||||||
if (call.isSuperToAny()) {
|
if (call.isSuperToAny()) {
|
||||||
@@ -266,7 +256,7 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
put(Name.identifier("compareTo"), ::transformCompareToMethodCall)
|
||||||
put(Name.identifier("equals"), ::transformEqualsMethodCall)
|
put(Name.identifier("equals"), ::transformEqualsMethodCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,6 +342,12 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visitFunction(declaration: IrFunction): IrStatement {
|
||||||
|
if (declaration.symbol == intrinsics.compareToDoNotIntrinsicify)
|
||||||
|
return declaration
|
||||||
|
return super.visitFunction(declaration)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitCall(expression: IrCall): IrExpression {
|
override fun visitCall(expression: IrCall): IrExpression {
|
||||||
val call = super.visitCall(expression)
|
val call = super.visitCall(expression)
|
||||||
|
|
||||||
@@ -430,50 +426,6 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transformEqeqOperator(call: IrCall): IrExpression {
|
|
||||||
val lhs = call.getValueArgument(0)!!
|
|
||||||
val rhs = call.getValueArgument(1)!!
|
|
||||||
|
|
||||||
// Special optimization for "<expression> == null"
|
|
||||||
if (lhs.isNullConst() || rhs.isNullConst())
|
|
||||||
return irCall(call, intrinsics.jsEqeq.symbol)
|
|
||||||
|
|
||||||
return when (translateEquals(lhs.type, rhs.type)) {
|
|
||||||
is IdentityOperator -> irCall(call, intrinsics.jsEqeqeq.symbol)
|
|
||||||
is EqualityOperator -> irCall(call, intrinsics.jsEqeq.symbol)
|
|
||||||
is RuntimeFunctionCall -> irCall(call, intrinsics.jsEquals)
|
|
||||||
is RuntimeOrMethodCall -> {
|
|
||||||
assert(!lhs.type.isNullable())
|
|
||||||
val equalsMethod = lhs.type.findEqualsMethod(rhs.type)
|
|
||||||
if (equalsMethod != null) {
|
|
||||||
irCall(call, equalsMethod.symbol, firstArgumentAsDispatchReceiver = true)
|
|
||||||
} else {
|
|
||||||
irCall(call, intrinsics.jsEquals)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun IrType.findEqualsMethod(rhs: IrType): IrSimpleFunction? {
|
|
||||||
val classifier = classifierOrNull ?: return null
|
|
||||||
return ((classifier.owner as? IrClass) ?: return null).declarations
|
|
||||||
.filterIsInstance<IrSimpleFunction>()
|
|
||||||
.filter {
|
|
||||||
it.name == Name.identifier("equals")
|
|
||||||
&& it.valueParameters.size == 1
|
|
||||||
&& rhs.isSubtypeOf(it.valueParameters[0].type)
|
|
||||||
&& !it.isFakeOverriddenFromAny()
|
|
||||||
}
|
|
||||||
.maxWith( // Find the most specific function
|
|
||||||
Comparator { f1, f2 ->
|
|
||||||
val t1 = f1.valueParameters[0].type
|
|
||||||
val t2 = f2.valueParameters[0].type
|
|
||||||
if (t1.isSubtypeOf(t2)) {
|
|
||||||
if (t2.isSubtypeOf(t1)) 0 else 1
|
|
||||||
} else -1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun withLongCoercion(intrinsic: IrSimpleFunction): (IrCall) -> IrExpression = { call ->
|
private fun withLongCoercion(intrinsic: IrSimpleFunction): (IrCall) -> IrExpression = { call ->
|
||||||
assert(call.valueArgumentsCount == 1)
|
assert(call.valueArgumentsCount == 1)
|
||||||
@@ -528,177 +480,149 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transformEqualsMethodCall(call: IrCall): IrExpression {
|
private fun transformEqeqOperator(call: IrCall): IrExpression {
|
||||||
val symbol = call.symbol
|
val lhs = call.getValueArgument(0)!!
|
||||||
val function = (symbol.owner as? IrFunction) ?: return call
|
val rhs = call.getValueArgument(1)!!
|
||||||
val lhs = function.dispatchReceiverParameter ?: function.extensionReceiverParameter ?: return call
|
|
||||||
val rhs = call.getValueArgument(0) ?: return call
|
|
||||||
return when (translateEquals(lhs.type, rhs.type)) {
|
val lhsJsType = lhs.type.getPrimitiveType()
|
||||||
is IdentityOperator -> irCall(call, intrinsics.jsEqeqeq.symbol)
|
val rhsJsType = rhs.type.getPrimitiveType()
|
||||||
is EqualityOperator -> irCall(call, intrinsics.jsEqeq.symbol)
|
|
||||||
is RuntimeFunctionCall -> irCall(call, intrinsics.jsEquals, true)
|
val equalsMethod = lhs.type.findEqualsMethod()
|
||||||
is RuntimeOrMethodCall -> if (symbol.owner.isFakeOverriddenFromAny()) {
|
val isLhsPrimitive = lhsJsType != PrimitiveType.OTHER
|
||||||
if (call.isSuperToAny()) {
|
|
||||||
irCall(call, intrinsics.jsEqeqeq.symbol, dispatchReceiverAsFirstArgument = true)
|
return when {
|
||||||
} else {
|
lhs.type is IrDynamicType ->
|
||||||
irCall(call, intrinsics.jsEquals, dispatchReceiverAsFirstArgument = true)
|
irCall(call, intrinsics.jsEqeq.symbol)
|
||||||
}
|
|
||||||
} else {
|
// Special optimization for "<expression> == null"
|
||||||
|
lhs.isNullConst() || rhs.isNullConst() ->
|
||||||
|
irCall(call, intrinsics.jsEqeq.symbol)
|
||||||
|
|
||||||
|
// For non-float primitives of the same type use JS `==`
|
||||||
|
isLhsPrimitive && lhsJsType == rhsJsType && lhsJsType != PrimitiveType.FLOATING_POINT_NUMBER ->
|
||||||
|
chooseEqualityOperatorForPrimitiveTypes(call)
|
||||||
|
|
||||||
|
!isLhsPrimitive && !lhs.type.toKotlinType().isNullable() && equalsMethod != null ->
|
||||||
|
irCall(call, equalsMethod.symbol, firstArgumentAsDispatchReceiver = true)
|
||||||
|
|
||||||
|
else ->
|
||||||
|
irCall(call, intrinsics.jsEquals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chooseEqualityOperatorForPrimitiveTypes(call: IrCall): IrExpression = when {
|
||||||
|
call.allValueArgumentsAreNullable() ->
|
||||||
|
irCall(call, intrinsics.jsEqeq.symbol)
|
||||||
|
else ->
|
||||||
|
irCall(call, intrinsics.jsEqeqeq.symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrCall.allValueArgumentsAreNullable() =
|
||||||
|
(0 until valueArgumentsCount).all { getValueArgument(it)!!.type.isNullable() }
|
||||||
|
|
||||||
|
private fun transformCompareToMethodCall(call: IrCall): IrExpression {
|
||||||
|
val function = call.symbol.owner as IrSimpleFunction
|
||||||
|
if (function.parent !is IrClass) return call
|
||||||
|
|
||||||
|
fun IrSimpleFunction.isFakeOverriddenFromComparable(): Boolean = when {
|
||||||
|
origin != IrDeclarationOrigin.FAKE_OVERRIDE ->
|
||||||
|
parentAsClass.thisReceiver!!.type.isComparable()
|
||||||
|
|
||||||
|
else -> overriddenSymbols.all { it.owner.isFakeOverriddenFromComparable() }
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
// Use runtime function call in case when receiverType is a primitive JS type that doesn't have `compareTo` method,
|
||||||
|
// or has a potential to be primitive type (being fake overridden from `Comparable`)
|
||||||
|
function.isMethodOfPrimitiveJSType() || function.isFakeOverriddenFromComparable() ->
|
||||||
|
irCall(call, intrinsics.jsCompareTo, dispatchReceiverAsFirstArgument = true)
|
||||||
|
|
||||||
|
// Valid `compareTo` method must be present at this point
|
||||||
|
else ->
|
||||||
call
|
call
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun transformEqualsMethodCall(call: IrCall): IrExpression {
|
||||||
|
val function = call.symbol.owner
|
||||||
|
return when {
|
||||||
|
// Nothing special
|
||||||
|
!function.isEqualsInheritedFromAny() -> call
|
||||||
|
|
||||||
|
// `Any.equals` works as identity operator
|
||||||
|
call.isSuperToAny() ->
|
||||||
|
irCall(call, intrinsics.jsEqeqeq.symbol, dispatchReceiverAsFirstArgument = true)
|
||||||
|
|
||||||
|
// Use runtime function call in case when receiverType is a primitive JS type that doesn't have `equals` method,
|
||||||
|
// or has a potential to be primitive type (being fake overridden from `Any`)
|
||||||
|
function.isMethodOfPotentiallyPrimitiveJSType() ->
|
||||||
|
irCall(call, intrinsics.jsEquals, dispatchReceiverAsFirstArgument = true)
|
||||||
|
|
||||||
|
// Valid `equals` method must be present at this point
|
||||||
|
else -> call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrType.findEqualsMethod(): IrSimpleFunction? {
|
||||||
|
val klass = getClass() ?: return null
|
||||||
|
return klass.declarations
|
||||||
|
.filterIsInstance<IrSimpleFunction>()
|
||||||
|
.filter { it.isEqualsInheritedFromAny() && !it.isFakeOverriddenFromAny() }
|
||||||
|
.also { assert(it.size <= 1) }
|
||||||
|
.singleOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrFunction.isMethodOfPrimitiveJSType() =
|
||||||
|
dispatchReceiverParameter?.type?.getPrimitiveType() != PrimitiveType.OTHER
|
||||||
|
|
||||||
|
private fun IrFunction.isMethodOfPotentiallyPrimitiveJSType() =
|
||||||
|
isMethodOfPrimitiveJSType() || isFakeOverriddenFromAny()
|
||||||
|
|
||||||
|
private fun IrFunction.isEqualsInheritedFromAny() =
|
||||||
|
name == Name.identifier("equals") &&
|
||||||
|
dispatchReceiverParameter != null &&
|
||||||
|
valueParameters.size == 1 &&
|
||||||
|
valueParameters[0].type.isNullableAny()
|
||||||
|
|
||||||
|
private fun shouldReplaceToStringWithRuntimeCall(call: IrCall): Boolean {
|
||||||
|
// TODO: (KOTLIN-CR-2079)
|
||||||
|
// - User defined extension functions Any?.toString() call can be lost during lowering.
|
||||||
|
// - Use direct method call for dynamic types???
|
||||||
|
// - Define Any?.toString() in runtime library and stop intrincifying extensions
|
||||||
|
|
||||||
|
if (call.valueArgumentsCount > 0)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val receiverParameterType = with(call.symbol.owner) {
|
||||||
|
dispatchReceiverParameter ?: extensionReceiverParameter
|
||||||
|
}?.type ?: return false
|
||||||
|
|
||||||
|
return receiverParameterType.run {
|
||||||
|
isArray() || isAny() || isNullable() || this is IrDynamicType || isString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shouldReplaceToStringWithRuntimeCall(call: IrCall): Boolean {
|
enum class PrimitiveType {
|
||||||
// TODO: (KOTLIN-CR-2079)
|
FLOATING_POINT_NUMBER,
|
||||||
// - User defined extension functions Any?.toString() call can be lost during lowering.
|
INTEGER_NUMBER,
|
||||||
// - Use direct method call for dynamic types???
|
STRING,
|
||||||
// - Define Any?.toString() in runtime library and stop intrincifying extensions
|
BOOLEAN,
|
||||||
|
OTHER
|
||||||
|
}
|
||||||
|
|
||||||
if (call.valueArgumentsCount > 0)
|
fun IrType.getPrimitiveType() = makeNotNull().run {
|
||||||
return false
|
when {
|
||||||
|
isBoolean() -> PrimitiveType.BOOLEAN
|
||||||
val receiverParameterType = with(call.symbol.owner) {
|
isByte() || isShort() || isInt() -> PrimitiveType.INTEGER_NUMBER
|
||||||
dispatchReceiverParameter ?: extensionReceiverParameter
|
isFloat() || isDouble() -> PrimitiveType.FLOATING_POINT_NUMBER
|
||||||
}?.type ?: return false
|
isString() -> PrimitiveType.STRING
|
||||||
|
else -> PrimitiveType.OTHER
|
||||||
return receiverParameterType.run {
|
|
||||||
isArray() || isAny() || isNullable() || this is IrDynamicType || isString()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shouldReplaceCompareToWithRuntimeCall(call: IrCall): Boolean {
|
|
||||||
// TODO: Replace all compareTo to with runtime call when Comparable<*>.compareTo() bridge is implemented
|
|
||||||
return call.symbol.owner.dispatchReceiverParameter?.run {
|
|
||||||
type is IrDynamicType
|
|
||||||
|| type.isJsNumber()
|
|
||||||
|| type.isNullableJsNumber()
|
|
||||||
|| type.isBoolean() || type.isNullableBoolean()
|
|
||||||
|| type.isString() || type.isNullableString()
|
|
||||||
} ?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Equality translation table:
|
|
||||||
|
|
||||||
| | JsN | JsN? | Long | Long? | Bool | Bool? | String | String? | Other | Other? |
|
|
||||||
|----------------|------|------|------|-------|------|-------|--------|---------|-------|--------|
|
|
||||||
| JsN | === | === | == | == | === | === | === | === | K.eq | K.eq |
|
|
||||||
| JsN? | === | == | == | == | === | K.eq | === | K.eq | K.eq | K.eq |
|
|
||||||
| Long | == | == | K.eq | K.eq | === | === | === | === | K.eq | K.eq |
|
|
||||||
| Long? | == | == | K.eq | K.eq | === | K.eq | === | K.eq | K.eq | K.eq |
|
|
||||||
| Bool | === | === | === | === | === | === | === | === | K.eq | K.eq |
|
|
||||||
| Bool? | === | K.eq | === | K.eq | === | == | === | K.eq | K.eq | K.eq |
|
|
||||||
| String | === | === | === | === | === | === | === | === | K.eq | K.eq |
|
|
||||||
| String? | === | K.eq | === | K.eq | === | K.eq | === | == | K.eq | K.eq |
|
|
||||||
| Other with .eq | .eq | .eq | .eq | .eq | .eq | .eq | .eq | .eq | .eq | .eq |
|
|
||||||
| Other w/o .eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq |
|
|
||||||
| Other? | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq | K.eq |
|
|
||||||
|
|
||||||
|
|
||||||
JsNumber -- type lowered to JS Number
|
|
||||||
K.eq -- runtime library call
|
|
||||||
.eq -- .equals(x) method call
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
sealed class EqualityLoweringType
|
|
||||||
object IdentityOperator : EqualityLoweringType()
|
|
||||||
object EqualityOperator : EqualityLoweringType()
|
|
||||||
object RuntimeFunctionCall : EqualityLoweringType()
|
|
||||||
object RuntimeOrMethodCall : EqualityLoweringType()
|
|
||||||
|
|
||||||
fun translateEquals(lhs: IrType, rhs: IrType): EqualityLoweringType = when {
|
|
||||||
lhs is IrDynamicType -> EqualityOperator
|
|
||||||
lhs.isJsNumber() -> translateEqualsForJsNumber(rhs)
|
|
||||||
lhs.isNullableJsNumber() -> translateEqualsForNullableJsNumber(rhs)
|
|
||||||
lhs.isLong() -> translateEqualsForLong(rhs)
|
|
||||||
lhs.isNullableLong() -> translateEqualsForNullableLong(rhs)
|
|
||||||
lhs.isBoolean() -> translateEqualsForBoolean(rhs)
|
|
||||||
lhs.isNullableBoolean() -> translateEqualsForNullableBoolean(rhs)
|
|
||||||
lhs.isString() -> translateEqualsForString(rhs)
|
|
||||||
lhs.isNullableString() -> translateEqualsForNullableString(rhs)
|
|
||||||
// TODO: Fix unbound symbols (in inline)
|
|
||||||
lhs.toKotlinType().isNullable() -> RuntimeFunctionCall
|
|
||||||
else -> RuntimeOrMethodCall
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translateEqualsForJsNumber(rhs: IrType): EqualityLoweringType = when {
|
|
||||||
rhs.isJsNumber() || rhs.isNullableJsNumber() -> IdentityOperator
|
|
||||||
rhs.isLong() || rhs.isNullableLong() -> EqualityOperator
|
|
||||||
rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator
|
|
||||||
rhs.isString() || rhs.isNullableString() -> IdentityOperator
|
|
||||||
else -> RuntimeFunctionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translateEqualsForNullableJsNumber(rhs: IrType): EqualityLoweringType = when {
|
|
||||||
rhs.isJsNumber() -> IdentityOperator
|
|
||||||
rhs.isNullableJsNumber() -> EqualityOperator
|
|
||||||
rhs.isLong() || rhs.isNullableLong() -> EqualityOperator
|
|
||||||
rhs.isBoolean() || rhs.isString() -> IdentityOperator
|
|
||||||
else -> RuntimeFunctionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translateEqualsForLong(rhs: IrType): EqualityLoweringType = when {
|
|
||||||
rhs.isJsNumber() || rhs.isNullableJsNumber() -> EqualityOperator
|
|
||||||
rhs.isLong() || rhs.isNullableLong() -> RuntimeFunctionCall
|
|
||||||
rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator
|
|
||||||
rhs.isString() || rhs.isNullableString() -> IdentityOperator
|
|
||||||
else -> RuntimeFunctionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translateEqualsForNullableLong(rhs: IrType): EqualityLoweringType = when {
|
|
||||||
rhs.isJsNumber() || rhs.isNullableJsNumber() -> EqualityOperator
|
|
||||||
rhs.isLong() || rhs.isNullableLong() -> RuntimeFunctionCall
|
|
||||||
rhs.isBoolean() -> IdentityOperator
|
|
||||||
rhs.isString() -> IdentityOperator
|
|
||||||
else -> RuntimeFunctionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translateEqualsForBoolean(rhs: IrType): EqualityLoweringType = when {
|
|
||||||
rhs.isJsNumber() || rhs.isNullableJsNumber() -> IdentityOperator
|
|
||||||
rhs.isLong() || rhs.isNullableLong() -> IdentityOperator
|
|
||||||
rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator
|
|
||||||
rhs.isString() || rhs.isNullableString() -> IdentityOperator
|
|
||||||
else -> RuntimeFunctionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translateEqualsForNullableBoolean(rhs: IrType): EqualityLoweringType = when {
|
|
||||||
rhs.isJsNumber() -> IdentityOperator
|
|
||||||
rhs.isNullableJsNumber() -> RuntimeFunctionCall
|
|
||||||
rhs.isLong() -> IdentityOperator
|
|
||||||
rhs.isNullableLong() -> RuntimeFunctionCall
|
|
||||||
rhs.isBoolean() -> IdentityOperator
|
|
||||||
rhs.isNullableBoolean() -> EqualityOperator
|
|
||||||
rhs.isString() -> IdentityOperator
|
|
||||||
else -> RuntimeFunctionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translateEqualsForString(rhs: IrType): EqualityLoweringType = when {
|
|
||||||
rhs.isJsNumber() || rhs.isNullableJsNumber() -> IdentityOperator
|
|
||||||
rhs.isLong() || rhs.isNullableLong() -> IdentityOperator
|
|
||||||
rhs.isBoolean() || rhs.isNullableBoolean() -> IdentityOperator
|
|
||||||
rhs.isString() || rhs.isNullableString() -> IdentityOperator
|
|
||||||
else -> RuntimeFunctionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translateEqualsForNullableString(rhs: IrType): EqualityLoweringType = when {
|
|
||||||
rhs.isJsNumber() -> IdentityOperator
|
|
||||||
rhs.isNullableJsNumber() -> RuntimeFunctionCall
|
|
||||||
rhs.isLong() -> IdentityOperator
|
|
||||||
rhs.isNullableLong() -> RuntimeFunctionCall
|
|
||||||
rhs.isBoolean() -> IdentityOperator
|
|
||||||
rhs.isNullableBoolean() -> RuntimeFunctionCall
|
|
||||||
rhs.isString() -> IdentityOperator
|
|
||||||
rhs.isNullableString() -> EqualityOperator
|
|
||||||
else -> RuntimeFunctionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun IrType.isNullableJsNumber(): Boolean = isNullablePrimitiveType() && !isNullableLong() && !isNullableChar()
|
|
||||||
|
|
||||||
private fun IrType.isJsNumber(): Boolean = isPrimitiveType() && !isLong() && !isChar()
|
|
||||||
|
|
||||||
private fun MemberToTransformer.op(type: IrType, name: Name, v: IrSimpleFunctionSymbol) {
|
private fun MemberToTransformer.op(type: IrType, name: Name, v: IrSimpleFunctionSymbol) {
|
||||||
op(type, name, v = { irCall(it, v, dispatchReceiverAsFirstArgument = true) })
|
op(type, name, v = { irCall(it, v, dispatchReceiverAsFirstArgument = true) })
|
||||||
}
|
}
|
||||||
@@ -723,6 +647,12 @@ private fun SymbolToTransformer.add(from: Map<SimpleType, IrSimpleFunction>, to:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun SymbolToTransformer.add(from: Map<SimpleType, IrSimpleFunction>, to: (IrCall) -> IrExpression) {
|
||||||
|
from.forEach { _, func ->
|
||||||
|
add(func.symbol, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun SymbolToTransformer.add(from: IrFunctionSymbol, to: (IrCall) -> IrExpression) {
|
private fun SymbolToTransformer.add(from: IrFunctionSymbol, to: (IrCall) -> IrExpression) {
|
||||||
put(from, to)
|
put(from, to)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ fun IrType.isFloat(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES._floa
|
|||||||
fun IrType.isDouble(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES._double)
|
fun IrType.isDouble(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES._double)
|
||||||
fun IrType.isNumber(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.number)
|
fun IrType.isNumber(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.number)
|
||||||
|
|
||||||
|
fun IrType.isComparable(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES.comparable.toUnsafe())
|
||||||
|
|
||||||
fun IrType.isNullableBoolean(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._boolean)
|
fun IrType.isNullableBoolean(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._boolean)
|
||||||
fun IrType.isNullableLong(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._long)
|
fun IrType.isNullableLong(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._long)
|
||||||
fun IrType.isNullableChar(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._char)
|
fun IrType.isNullableChar(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._char)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|
||||||
fun box(): String {
|
fun box(): String {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
// IGNORE_BACKEND: JVM_IR
|
||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
data class Test(val z1: Double, val z2: Double?)
|
data class Test(val z1: Double, val z2: Double?)
|
||||||
|
|
||||||
fun box(): String {
|
fun box(): String {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// !LANGUAGE: +ProperIeee754Comparisons
|
// !LANGUAGE: +ProperIeee754Comparisons
|
||||||
// IGNORE_BACKEND: JVM_IR
|
// IGNORE_BACKEND: JVM_IR
|
||||||
|
// IGNORE_BACKEND: JS_IR
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
@@ -94,6 +95,7 @@ fun box(): String {
|
|||||||
assertTrue(adnq == dn, "Double: (Any?)NaN != NaN")
|
assertTrue(adnq == dn, "Double: (Any?)NaN != NaN")
|
||||||
assertTrue(adnq == adn, "Double: (Any?)NaN != (Any)NaN")
|
assertTrue(adnq == adn, "Double: (Any?)NaN != (Any)NaN")
|
||||||
|
|
||||||
|
// JS IR fails due to: https://bugs.openjdk.java.net/browse/JDK-8141407
|
||||||
assertFalse(dnq == dnq, "Double: NaN? == NaN?")
|
assertFalse(dnq == dnq, "Double: NaN? == NaN?")
|
||||||
assertTrue(dnq == adnq, "Double: NaN? != (Any?)NaN")
|
assertTrue(dnq == adnq, "Double: NaN? != (Any?)NaN")
|
||||||
assertTrue(adnq == dnq, "Double: (Any?)NaN != NaN?")
|
assertTrue(adnq == dnq, "Double: (Any?)NaN != NaN?")
|
||||||
@@ -114,6 +116,7 @@ fun box(): String {
|
|||||||
assertFalse(adnq != dn, "Double: (Any?)NaN != NaN")
|
assertFalse(adnq != dn, "Double: (Any?)NaN != NaN")
|
||||||
assertFalse(adnq != adn, "Double: (Any?)NaN != (Any)NaN")
|
assertFalse(adnq != adn, "Double: (Any?)NaN != (Any)NaN")
|
||||||
|
|
||||||
|
// JS IR fails due to: https://bugs.openjdk.java.net/browse/JDK-8141407
|
||||||
assertTrue(dnq != dnq, "Double: NaN? == NaN?")
|
assertTrue(dnq != dnq, "Double: NaN? == NaN?")
|
||||||
assertFalse(dnq != adnq, "Double: NaN? != (Any?)NaN")
|
assertFalse(dnq != adnq, "Double: NaN? != (Any?)NaN")
|
||||||
assertFalse(adnq != dnq, "Double: (Any?)NaN != NaN?")
|
assertFalse(adnq != dnq, "Double: (Any?)NaN != NaN?")
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
inline fun less(a: Comparable<Double>, b: Double): Boolean {
|
inline fun less(a: Comparable<Double>, b: Double): Boolean {
|
||||||
return a < b
|
return a < b
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
// !LANGUAGE: +InlineClasses
|
// !LANGUAGE: +InlineClasses
|
||||||
// IGNORE_BACKEND: JVM_IR, JS_IR
|
// IGNORE_BACKEND: JVM_IR
|
||||||
|
|
||||||
inline class InlineFloat(val data: Float)
|
inline class InlineFloat(val data: Float)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
// !LANGUAGE: +InlineClasses
|
// !LANGUAGE: +InlineClasses
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// IGNORE_BACKEND: JVM_IR, JS_IR
|
// IGNORE_BACKEND: JVM_IR
|
||||||
|
|
||||||
inline class GCmp<T>(val xc: Comparable<T>)
|
inline class GCmp<T>(val xc: Comparable<T>)
|
||||||
inline class GSCmp<T>(val sc: Comparable<String>)
|
inline class GSCmp<T>(val sc: Comparable<String>)
|
||||||
|
|||||||
-1
@@ -1,5 +1,4 @@
|
|||||||
// !LANGUAGE: +ProperIeee754Comparisons
|
// !LANGUAGE: +ProperIeee754Comparisons
|
||||||
//IGNORE_BACKEND: JS_IR
|
|
||||||
|
|
||||||
fun ltDD(x: Comparable<Double>, y: Double) =
|
fun ltDD(x: Comparable<Double>, y: Double) =
|
||||||
x is Double && x < y
|
x is Double && x < y
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// TARGET_BACKEND: JS
|
// TARGET_BACKEND: JS
|
||||||
// Contains JS-specific checks, e.g. null == undefined
|
// Contains JS-specific checks, e.g. null == undefined
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|
||||||
val i1 = 1
|
val i1 = 1
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
//KT-1800 error/NonExistentClass generated on runtime
|
//KT-1800 error/NonExistentClass generated on runtime
|
||||||
package i
|
package i
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|
||||||
fun box(): String {
|
fun box(): String {
|
||||||
|
|||||||
+5
@@ -6404,6 +6404,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
|||||||
runTest("js/js.translator/testData/box/number/numberConversions.kt");
|
runTest("js/js.translator/testData/box/number/numberConversions.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("numberEquals.kt")
|
||||||
|
public void testNumberEquals() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/number/numberEquals.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("numberIncDec.kt")
|
@TestMetadata("numberIncDec.kt")
|
||||||
public void testNumberIncDec() throws Exception {
|
public void testNumberIncDec() throws Exception {
|
||||||
runTest("js/js.translator/testData/box/number/numberIncDec.kt");
|
runTest("js/js.translator/testData/box/number/numberIncDec.kt");
|
||||||
|
|||||||
+5
@@ -6404,6 +6404,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
|||||||
runTest("js/js.translator/testData/box/number/numberConversions.kt");
|
runTest("js/js.translator/testData/box/number/numberConversions.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("numberEquals.kt")
|
||||||
|
public void testNumberEquals() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/number/numberEquals.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("numberIncDec.kt")
|
@TestMetadata("numberIncDec.kt")
|
||||||
public void testNumberIncDec() throws Exception {
|
public void testNumberIncDec() throws Exception {
|
||||||
runTest("js/js.translator/testData/box/number/numberIncDec.kt");
|
runTest("js/js.translator/testData/box/number/numberIncDec.kt");
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// EXPECTED_REACHABLE_NODES: 1282
|
// EXPECTED_REACHABLE_NODES: 1282
|
||||||
fun box(): String {
|
fun box(): String {
|
||||||
val r1 = trueFun() > falseFun()
|
val r1 = trueFun() > falseFun()
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// !LANGUAGE: +ProperIeee754Comparisons
|
||||||
|
// IGNORE_BACKEND: JS
|
||||||
|
// EXPECTED_REACHABLE_NODES: 1112
|
||||||
|
package foo
|
||||||
|
|
||||||
|
|
||||||
|
fun testNullable(): String {
|
||||||
|
val undefined: Double? = js("undefined")
|
||||||
|
val doubleNull: Double? = null
|
||||||
|
|
||||||
|
val plusZero: Double? = +0.0
|
||||||
|
val minusZero: Double? = -0.0
|
||||||
|
|
||||||
|
if ((+0.0).equals(minusZero)) return "Total order fail"
|
||||||
|
if (plusZero != minusZero) return "IEEE 754 equals fail"
|
||||||
|
|
||||||
|
if (plusZero == doubleNull) return "+0.0 != null fail"
|
||||||
|
if (plusZero == undefined) return "+0.0 != undefined fail"
|
||||||
|
|
||||||
|
if (undefined != doubleNull) return "undefined == null fail"
|
||||||
|
if (undefined != undefined) return "undefined = undefined fail"
|
||||||
|
if (doubleNull != doubleNull) return "doubleNull = doubleNull fail"
|
||||||
|
|
||||||
|
// Double == Float
|
||||||
|
val plusZeroAny: Any? = +0.0
|
||||||
|
val minusZeroAny: Any? = -0.0f
|
||||||
|
|
||||||
|
if (plusZeroAny is Double && minusZeroAny is Float) {
|
||||||
|
if (plusZeroAny != minusZeroAny) return "IEEE 754 quals fail 2"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val plusZero: Double = +0.0
|
||||||
|
val minusZero: Double = -0.0
|
||||||
|
|
||||||
|
if (plusZero.equals(minusZero)) return "Total order fail"
|
||||||
|
if (plusZero != minusZero) return "IEEE 754 equals fail"
|
||||||
|
|
||||||
|
val plusZeroFloat: Float = +0.0f
|
||||||
|
val minusZeroFloat: Float = -0.0f
|
||||||
|
|
||||||
|
if (plusZeroFloat.equals(minusZeroFloat)) return "Total order fail 2"
|
||||||
|
if (plusZeroFloat != minusZeroFloat) return "IEEE 754 equals fail 2"
|
||||||
|
|
||||||
|
if ((plusZero as Any) == (minusZero as Any)) return "Total order fail 4"
|
||||||
|
if ((plusZeroFloat as Any) == (minusZeroFloat as Any)) return "Total order fail 5"
|
||||||
|
if (plusZero == (minusZero as Any)) return "Total order fail 6"
|
||||||
|
|
||||||
|
val nullableRes = testNullable()
|
||||||
|
if (nullableRes != "OK")
|
||||||
|
return "Nullable" + nullableRes
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
@@ -20,10 +20,13 @@ fun compareTo(a: dynamic, b: dynamic): Int {
|
|||||||
return primitiveCompareTo(a, b)
|
return primitiveCompareTo(a, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replace to a.unsafeCast<Comparable<*>>().compareTo(b) when bridge is implemented
|
return compareToDoNotIntrinsicify(a, b)
|
||||||
return js("a.compareTo(b)").unsafeCast<Int>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Use @DoNotIntrincify annotation
|
||||||
|
private fun <T : Comparable<T>> compareToDoNotIntrinsicify(a: Comparable<T>, b: T) =
|
||||||
|
a.compareTo(b)
|
||||||
|
|
||||||
fun primitiveCompareTo(a: dynamic, b: dynamic): Int =
|
fun primitiveCompareTo(a: dynamic, b: dynamic): Int =
|
||||||
js("a < b ? -1 : a > b ? 1 : 0").unsafeCast<Int>()
|
js("a < b ? -1 : a > b ? 1 : 0").unsafeCast<Int>()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user