[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 jsAnyToString = getInternalFunction("anyToString")
|
||||
val jsCompareTo = getInternalFunction("compareTo")
|
||||
// TODO: Use annotations
|
||||
val compareToDoNotIntrinsicify = getInternalFunction("compareToDoNotIntrinsicify")
|
||||
val jsEquals = getInternalFunction("equals")
|
||||
|
||||
// Coroutines
|
||||
|
||||
+151
-221
@@ -6,9 +6,9 @@
|
||||
package org.jetbrains.kotlin.ir.backend.js.lower
|
||||
|
||||
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.descriptors.PropertyAccessorDescriptor
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
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.Namer
|
||||
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.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||
@@ -131,9 +127,8 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
||||
symbolToTransformer.run {
|
||||
add(irBuiltIns.eqeqeqSymbol, intrinsics.jsEqeqeq)
|
||||
add(irBuiltIns.eqeqSymbol, ::transformEqeqOperator)
|
||||
|
||||
// ieee754equals can only be applied in between statically known Floats or Doubles
|
||||
add(irBuiltIns.ieee754equalsFunByOperandType, intrinsics.jsEqeqeq)
|
||||
// ieee754equals can only be applied in between statically known Floats, Doubles, null or undefined
|
||||
add(irBuiltIns.ieee754equalsFunByOperandType, ::chooseEqualityOperatorForPrimitiveTypes)
|
||||
|
||||
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 ->
|
||||
if (shouldReplaceToStringWithRuntimeCall(call)) {
|
||||
if (call.isSuperToAny()) {
|
||||
@@ -266,7 +256,7 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
put(Name.identifier("compareTo"), ::transformCompareToMethodCall)
|
||||
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 {
|
||||
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 ->
|
||||
assert(call.valueArgumentsCount == 1)
|
||||
@@ -528,177 +480,149 @@ class IntrinsicifyCallsLowering(private val context: JsIrBackendContext) : FileL
|
||||
}
|
||||
}
|
||||
|
||||
private fun transformEqualsMethodCall(call: IrCall): IrExpression {
|
||||
val symbol = call.symbol
|
||||
val function = (symbol.owner as? IrFunction) ?: return call
|
||||
val lhs = function.dispatchReceiverParameter ?: function.extensionReceiverParameter ?: return call
|
||||
val rhs = call.getValueArgument(0) ?: return call
|
||||
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, true)
|
||||
is RuntimeOrMethodCall -> if (symbol.owner.isFakeOverriddenFromAny()) {
|
||||
if (call.isSuperToAny()) {
|
||||
irCall(call, intrinsics.jsEqeqeq.symbol, dispatchReceiverAsFirstArgument = true)
|
||||
} else {
|
||||
irCall(call, intrinsics.jsEquals, dispatchReceiverAsFirstArgument = true)
|
||||
}
|
||||
} else {
|
||||
private fun transformEqeqOperator(call: IrCall): IrExpression {
|
||||
val lhs = call.getValueArgument(0)!!
|
||||
val rhs = call.getValueArgument(1)!!
|
||||
|
||||
|
||||
val lhsJsType = lhs.type.getPrimitiveType()
|
||||
val rhsJsType = rhs.type.getPrimitiveType()
|
||||
|
||||
val equalsMethod = lhs.type.findEqualsMethod()
|
||||
val isLhsPrimitive = lhsJsType != PrimitiveType.OTHER
|
||||
|
||||
return when {
|
||||
lhs.type is IrDynamicType ->
|
||||
irCall(call, intrinsics.jsEqeq.symbol)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
// 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
|
||||
enum class PrimitiveType {
|
||||
FLOATING_POINT_NUMBER,
|
||||
INTEGER_NUMBER,
|
||||
STRING,
|
||||
BOOLEAN,
|
||||
OTHER
|
||||
}
|
||||
|
||||
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 IrType.getPrimitiveType() = makeNotNull().run {
|
||||
when {
|
||||
isBoolean() -> PrimitiveType.BOOLEAN
|
||||
isByte() || isShort() || isInt() -> PrimitiveType.INTEGER_NUMBER
|
||||
isFloat() || isDouble() -> PrimitiveType.FLOATING_POINT_NUMBER
|
||||
isString() -> PrimitiveType.STRING
|
||||
else -> PrimitiveType.OTHER
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
put(from, to)
|
||||
}
|
||||
@@ -780,4 +710,4 @@ private val IrClassSymbol.sizeConstructor
|
||||
get() = owner.declarations.filterIsInstance<IrConstructor>().first { it.valueParameters.size == 1 }.symbol
|
||||
|
||||
private val IrClassSymbol.lengthProperty
|
||||
get() = owner.declarations.filterIsInstance<IrProperty>().first { it.name.asString() == "length" }.getter!!.symbol
|
||||
get() = owner.declarations.filterIsInstance<IrProperty>().first { it.name.asString() == "length" }.getter!!.symbol
|
||||
|
||||
@@ -60,6 +60,8 @@ fun IrType.isFloat(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES._floa
|
||||
fun IrType.isDouble(): Boolean = isNotNullClassType(KotlinBuiltIns.FQ_NAMES._double)
|
||||
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.isNullableLong(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._long)
|
||||
fun IrType.isNullableChar(): Boolean = isNullableClassType(KotlinBuiltIns.FQ_NAMES._char)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun box(): String {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
data class Test(val z1: Double, val z2: Double?)
|
||||
|
||||
fun box(): String {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// !LANGUAGE: +ProperIeee754Comparisons
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// WITH_RUNTIME
|
||||
|
||||
import kotlin.test.*
|
||||
@@ -94,6 +95,7 @@ fun box(): String {
|
||||
assertTrue(adnq == dn, "Double: (Any?)NaN != 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?")
|
||||
assertTrue(dnq == adnq, "Double: NaN? != (Any?)NaN")
|
||||
assertTrue(adnq == dnq, "Double: (Any?)NaN != NaN?")
|
||||
@@ -114,6 +116,7 @@ fun box(): String {
|
||||
assertFalse(adnq != dn, "Double: (Any?)NaN != 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?")
|
||||
assertFalse(dnq != adnq, "Double: NaN? != (Any?)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 {
|
||||
return a < b
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// !LANGUAGE: +InlineClasses
|
||||
// IGNORE_BACKEND: JVM_IR, JS_IR
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
inline class InlineFloat(val data: Float)
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// !LANGUAGE: +InlineClasses
|
||||
// WITH_RUNTIME
|
||||
// IGNORE_BACKEND: JVM_IR, JS_IR
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
|
||||
inline class GCmp<T>(val xc: Comparable<T>)
|
||||
inline class GSCmp<T>(val sc: Comparable<String>)
|
||||
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
// !LANGUAGE: +ProperIeee754Comparisons
|
||||
//IGNORE_BACKEND: JS_IR
|
||||
|
||||
fun ltDD(x: Comparable<Double>, y: Double) =
|
||||
x is Double && x < y
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// TARGET_BACKEND: JS
|
||||
// Contains JS-specific checks, e.g. null == undefined
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// WITH_RUNTIME
|
||||
|
||||
val i1 = 1
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// WITH_RUNTIME
|
||||
//KT-1800 error/NonExistentClass generated on runtime
|
||||
package i
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun box(): String {
|
||||
|
||||
+5
@@ -6404,6 +6404,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
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")
|
||||
public void testNumberIncDec() throws Exception {
|
||||
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");
|
||||
}
|
||||
|
||||
@TestMetadata("numberEquals.kt")
|
||||
public void testNumberEquals() throws Exception {
|
||||
runTest("js/js.translator/testData/box/number/numberEquals.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("numberIncDec.kt")
|
||||
public void testNumberIncDec() throws Exception {
|
||||
runTest("js/js.translator/testData/box/number/numberIncDec.kt");
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// EXPECTED_REACHABLE_NODES: 1282
|
||||
fun box(): String {
|
||||
val r1 = trueFun() > falseFun()
|
||||
@@ -24,4 +23,4 @@ fun box(): String {
|
||||
|
||||
fun trueFun() = true
|
||||
|
||||
fun falseFun() = false
|
||||
fun falseFun() = false
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
// TODO: Replace to a.unsafeCast<Comparable<*>>().compareTo(b) when bridge is implemented
|
||||
return js("a.compareTo(b)").unsafeCast<Int>()
|
||||
return compareToDoNotIntrinsicify(a, b)
|
||||
}
|
||||
|
||||
// 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 =
|
||||
js("a < b ? -1 : a > b ? 1 : 0").unsafeCast<Int>()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user