[FIR] Promote numeric types during comparisons

Added support for comparing platform types. Extracted logic to convert
operator expressions to OperatorExpressionGenerator.
This commit is contained in:
Mark Punzalan
2020-04-14 00:51:19 -07:00
committed by Mikhail Glukhikh
parent 1d9967013b
commit d4cbfcb79e
18 changed files with 310 additions and 547 deletions
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.backend.generators.CallAndReferenceGenerator
import org.jetbrains.kotlin.fir.backend.generators.ClassMemberGenerator
import org.jetbrains.kotlin.fir.backend.generators.OperatorExpressionGenerator
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.impl.FirElseIfTrueCondition
@@ -21,42 +22,36 @@ import org.jetbrains.kotlin.fir.resolve.firSymbolProvider
import org.jetbrains.kotlin.fir.resolve.isIteratorNext
import org.jetbrains.kotlin.fir.resolve.transformers.IntegerLiteralTypeApproximationTransformer
import org.jetbrains.kotlin.fir.scopes.impl.FirIntegerOperator
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor
import org.jetbrains.kotlin.fir.visitors.transformSingle
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.IrGeneratorContextInterface
import org.jetbrains.kotlin.ir.builders.constFalse
import org.jetbrains.kotlin.ir.builders.constTrue
import org.jetbrains.kotlin.ir.builders.elseBranch
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.makeNotNull
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import java.util.*
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi.KtForExpression
class Fir2IrVisitor(
private val converter: Fir2IrConverter,
private val components: Fir2IrComponents,
fakeOverrideMode: FakeOverrideMode
) : Fir2IrComponents by components, FirDefaultVisitor<IrElement, Any?>(), IrGeneratorContextInterface {
companion object {
private val NEGATED_OPERATIONS: Set<FirOperation> = EnumSet.of(FirOperation.NOT_EQ, FirOperation.NOT_IDENTITY)
private val UNARY_OPERATIONS: Set<FirOperation> = EnumSet.of(FirOperation.EXCL)
}
private val integerApproximator = IntegerLiteralTypeApproximationTransformer(session.firSymbolProvider, session.inferenceContext)
@@ -66,6 +61,8 @@ class Fir2IrVisitor(
private val memberGenerator = ClassMemberGenerator(components, this, conversionScope, callGenerator, fakeOverrideMode)
private val operatorGenerator = OperatorExpressionGenerator(components, this, callGenerator)
private fun FirTypeRef.toIrType(): IrType = with(typeConverter) { toIrType() }
private fun <T : IrDeclaration> applyParentFromStackTo(declaration: T): T = conversionScope.applyParentFromStackTo(declaration)
@@ -611,106 +608,11 @@ class Fir2IrVisitor(
}
}
override fun visitComparisonExpression(comparisonExpression: FirComparisonExpression, data: Any?): IrElement {
return comparisonExpression.convertWithOffsets { startOffset, endOffset ->
generateComparisonCall(startOffset, endOffset, comparisonExpression)
}
}
override fun visitComparisonExpression(comparisonExpression: FirComparisonExpression, data: Any?): IrElement =
operatorGenerator.convertComparisonExpression(comparisonExpression)
private fun generateComparisonCall(
startOffset: Int, endOffset: Int,
comparisonExpression: FirComparisonExpression
): IrExpression {
val operation = comparisonExpression.operation
fun fallbackToRealCall(): IrExpression {
val (symbol, origin) = getSymbolAndOriginForComparison(operation, irBuiltIns.intType.classifierOrFail)
return primitiveOp2(
startOffset, endOffset,
symbol!!,
irBuiltIns.booleanType,
origin,
visitFunctionCall(comparisonExpression.compareToCall, null),
IrConstImpl.int(startOffset, endOffset, irBuiltIns.intType, 0)
)
}
val comparisonInfo = comparisonExpression.inferPrimitiveNumericComparisonInfo() ?: return fallbackToRealCall()
val comparisonType = comparisonInfo.comparisonType
// Currently inferPrimitiveNumericComparisonInfo returns null for different primitive values
// TODO: Support different primitive types as well and fix inferPrimitiveNumericComparisonInfo
require(comparisonInfo.leftPrimitiveType == comparisonInfo.rightPrimitiveType && comparisonInfo.leftType == comparisonInfo.rightType) {
"Contract for inferPrimitiveNumericComparisonInfo is violated"
}
val simpleType = when ((comparisonType.type as? ConeClassLikeType)?.lookupTag?.classId) {
StandardClassIds.Long -> irBuiltIns.longType
StandardClassIds.Int -> irBuiltIns.intType
StandardClassIds.Float -> irBuiltIns.floatType
StandardClassIds.Double -> irBuiltIns.doubleType
StandardClassIds.Char -> irBuiltIns.charType
else -> return fallbackToRealCall()
}
val (symbol, origin) = getSymbolAndOriginForComparison(operation, simpleType.classifierOrFail)
return primitiveOp2(
startOffset, endOffset, symbol!!, irBuiltIns.booleanType, origin,
convertToIrExpression(comparisonExpression.left), convertToIrExpression(comparisonExpression.right)
)
}
private fun getSymbolAndOriginForComparison(
operation: FirOperation,
classifier: IrClassifierSymbol
): Pair<IrSimpleFunctionSymbol?, IrStatementOriginImpl> {
return when (operation) {
FirOperation.LT -> irBuiltIns.lessFunByOperandType[classifier] to IrStatementOrigin.LT
FirOperation.GT -> irBuiltIns.greaterFunByOperandType[classifier] to IrStatementOrigin.GT
FirOperation.LT_EQ -> irBuiltIns.lessOrEqualFunByOperandType[classifier] to IrStatementOrigin.LTEQ
FirOperation.GT_EQ -> irBuiltIns.greaterOrEqualFunByOperandType[classifier] to IrStatementOrigin.GTEQ
else -> error("Unexpected comparison operation: $operation")
}
}
private fun generateOperatorCall(
startOffset: Int, endOffset: Int, operation: FirOperation, arguments: List<FirExpression>
): IrExpression {
val (type, symbol, origin) = when (operation) {
FirOperation.EQ -> Triple(irBuiltIns.booleanType, irBuiltIns.eqeqSymbol, IrStatementOrigin.EQEQ)
FirOperation.NOT_EQ -> Triple(irBuiltIns.booleanType, irBuiltIns.eqeqSymbol, IrStatementOrigin.EXCLEQ)
FirOperation.IDENTITY -> Triple(irBuiltIns.booleanType, irBuiltIns.eqeqeqSymbol, IrStatementOrigin.EQEQEQ)
FirOperation.NOT_IDENTITY -> Triple(irBuiltIns.booleanType, irBuiltIns.eqeqeqSymbol, IrStatementOrigin.EXCLEQEQ)
FirOperation.EXCL -> Triple(irBuiltIns.booleanType, irBuiltIns.booleanNotSymbol, IrStatementOrigin.EXCL)
FirOperation.LT, FirOperation.GT,
FirOperation.LT_EQ, FirOperation.GT_EQ,
FirOperation.OTHER, FirOperation.ASSIGN, FirOperation.PLUS_ASSIGN,
FirOperation.MINUS_ASSIGN, FirOperation.TIMES_ASSIGN,
FirOperation.DIV_ASSIGN, FirOperation.REM_ASSIGN,
FirOperation.IS, FirOperation.NOT_IS,
FirOperation.AS, FirOperation.SAFE_AS
-> {
TODO("Should not be here: incompatible operation in FirOperatorCall: $operation")
}
}
val result = if (operation in UNARY_OPERATIONS) {
primitiveOp1(startOffset, endOffset, symbol, type, origin, convertToIrExpression(arguments[0]))
} else {
primitiveOp2(
startOffset, endOffset, symbol, type, origin,
convertToIrExpression(arguments[0]), convertToIrExpression(arguments[1])
)
}
if (operation !in NEGATED_OPERATIONS) return result
return primitiveOp1(startOffset, endOffset, irBuiltIns.booleanNotSymbol, irBuiltIns.booleanType, origin, result)
}
override fun visitOperatorCall(operatorCall: FirOperatorCall, data: Any?): IrElement {
return operatorCall.convertWithOffsets { startOffset, endOffset ->
generateOperatorCall(startOffset, endOffset, operatorCall.operation, operatorCall.arguments)
}
}
override fun visitOperatorCall(operatorCall: FirOperatorCall, data: Any?): IrElement =
operatorGenerator.convertOperatorCall(operatorCall)
override fun visitStringConcatenationCall(stringConcatenationCall: FirStringConcatenationCall, data: Any?): IrElement {
return stringConcatenationCall.convertWithOffsets { startOffset, endOffset ->
@@ -9,15 +9,14 @@ import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.expressions.FirComparisonExpression
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.arguments
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
class PrimitiveConeNumericComparisonInfo(
val comparisonType: ConeKotlinType,
val leftPrimitiveType: ConeClassLikeType,
val rightPrimitiveType: ConeClassLikeType,
val leftType: ConeKotlinType,
val rightType: ConeKotlinType
val comparisonType: ConeClassLikeType,
val leftType: ConeClassLikeType,
val rightType: ConeClassLikeType
)
val FirComparisonExpression.left: FirExpression
@@ -26,25 +25,39 @@ val FirComparisonExpression.left: FirExpression
val FirComparisonExpression.right: FirExpression
get() = compareToCall.arguments.getOrNull(0) ?: error("There should be a first arg for ${compareToCall.render()}")
fun FirComparisonExpression.inferPrimitiveNumericComparisonInfo(): PrimitiveConeNumericComparisonInfo? {
fun FirComparisonExpression.inferPrimitiveNumericComparisonInfo(): PrimitiveConeNumericComparisonInfo? =
inferPrimitiveNumericComparisonInfo(left, right)
fun inferPrimitiveNumericComparisonInfo(left: FirExpression, right: FirExpression): PrimitiveConeNumericComparisonInfo? {
val leftType = left.typeRef.coneTypeSafe<ConeKotlinType>() ?: return null
val rightType = right.typeRef.coneTypeSafe<ConeKotlinType>() ?: return null
val leftPrimitiveOrNullableType = leftType.getPrimitiveTypeOrSupertype() ?: return null
val rightPrimitiveOrNullableType = rightType.getPrimitiveTypeOrSupertype() ?: return null
val leftPrimitiveType = leftPrimitiveOrNullableType.withNullability(ConeNullability.NOT_NULL)
val rightPrimitiveType = rightPrimitiveOrNullableType.withNullability(ConeNullability.NOT_NULL)
val leastCommonType = leastCommonPrimitiveNumericType(leftPrimitiveOrNullableType, rightPrimitiveOrNullableType)
// TODO: Support different types with coercion
if (leftPrimitiveType != rightPrimitiveType) return null
val leastCommonType = rightPrimitiveType
return PrimitiveConeNumericComparisonInfo(
leastCommonType,
leftPrimitiveType, rightPrimitiveType,
leftPrimitiveOrNullableType, rightPrimitiveOrNullableType
)
return PrimitiveConeNumericComparisonInfo(leastCommonType, leftPrimitiveOrNullableType, rightPrimitiveOrNullableType)
}
private fun leastCommonPrimitiveNumericType(t1: ConeClassLikeType, t2: ConeClassLikeType): ConeClassLikeType {
val pt1 = t1.promoteIntegerTypeToIntIfRequired()
val pt2 = t2.promoteIntegerTypeToIntIfRequired()
return when {
pt1.isDouble() || pt2.isDouble() -> PrimitiveTypes.Double
pt1.isFloat() || pt2.isFloat() -> PrimitiveTypes.Float
pt1.isLong() || pt2.isLong() -> PrimitiveTypes.Long
pt1.isInt() || pt2.isInt() -> PrimitiveTypes.Int
else -> throw AssertionError("Unexpected types: t1=$t1, t2=$t2")
}
}
private fun ConeClassLikeType.promoteIntegerTypeToIntIfRequired(): ConeClassLikeType =
when (lookupTag.classId) {
StandardClassIds.Byte, StandardClassIds.Short -> PrimitiveTypes.Int
StandardClassIds.Long, StandardClassIds.Int, StandardClassIds.Float, StandardClassIds.Double, StandardClassIds.Char -> this
else -> throw AssertionError("Primitive number type expected: $this")
}
private fun ConeKotlinType.getPrimitiveTypeOrSupertype(): ConeClassLikeType? =
when {
this is ConeTypeParameterType ->
@@ -53,6 +66,8 @@ private fun ConeKotlinType.getPrimitiveTypeOrSupertype(): ConeClassLikeType? =
}
this is ConeClassLikeType && isPrimitiveNumberType() ->
this
this is ConeFlexibleType ->
this.lowerBound.getPrimitiveTypeOrSupertype()
else ->
null
}
@@ -33,9 +33,9 @@ import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.makeNotNull
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.psi.KtPropertyDelegate
import org.jetbrains.kotlin.psi2ir.generators.hasNoSideEffects
import java.lang.IllegalArgumentException
internal class CallAndReferenceGenerator(
private val components: Fir2IrComponents,
@@ -110,37 +110,46 @@ internal class CallAndReferenceGenerator(
return convertToUnsafeIrCall(qualifiedAccess, typeRef, explicitReceiverExpression)
}
return qualifiedAccess.convertWithOffsets { startOffset, endOffset ->
val type = typeRef.toIrType()
val callableSymbol = (qualifiedAccess.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirCallableSymbol<*>
val typeShouldBeNotNull = callableSymbol?.fir?.returnTypeRef?.coneTypeSafe<ConeKotlinType>()?.isNullable == false
val unsafeIrCall = convertToUnsafeIrCall(
qualifiedAccess, typeRef, explicitReceiverExpression, makeNotNull = typeShouldBeNotNull
val unsafeIrCall =
convertToUnsafeIrCall(qualifiedAccess, typeRef, explicitReceiverExpression, makeNotNull = typeShouldBeNotNull)
convertToSafeIrCall(
unsafeIrCall,
explicitReceiverExpression!!,
isDispatch = explicitReceiver == qualifiedAccess.dispatchReceiver
)
IrBlockImpl(startOffset, endOffset, type, IrStatementOrigin.SAFE_CALL).apply {
val receiverVariable = declarationStorage.declareTemporaryVariable(explicitReceiverExpression!!).apply {
parent = conversionScope.parentFromStack()
}
statements += receiverVariable
statements += IrWhenImpl(startOffset, endOffset, type).apply {
val variableSymbol = symbolTable.referenceValue(receiverVariable.descriptor)
val condition = IrCallImpl(
startOffset, endOffset, irBuiltIns.booleanType, irBuiltIns.eqeqSymbol, origin = IrStatementOrigin.EQEQ
).apply {
putValueArgument(0, IrGetValueImpl(startOffset, endOffset, variableSymbol))
putValueArgument(1, IrConstImpl.constNull(startOffset, endOffset, irBuiltIns.nothingNType))
}
branches += IrBranchImpl(
condition, IrConstImpl.constNull(startOffset, endOffset, irBuiltIns.nothingNType)
)
val newReceiver = IrGetValueImpl(startOffset, endOffset, variableSymbol)
val replacedCall = unsafeIrCall.replaceReceiver(
newReceiver, isDispatch = explicitReceiver == qualifiedAccess.dispatchReceiver
)
branches += IrElseBranchImpl(
IrConstImpl.boolean(startOffset, endOffset, irBuiltIns.booleanType, true),
replacedCall
)
}
}
internal fun convertToSafeIrCall(call: IrExpression, explicitReceiverExpression: IrExpression, isDispatch: Boolean): IrExpression {
val startOffset = call.startOffset
val endOffset = call.endOffset
val receiverVariable = declarationStorage.declareTemporaryVariable(explicitReceiverExpression, "safe_receiver").apply {
parent = conversionScope.parentFromStack()
}
val variableSymbol = symbolTable.referenceValue(receiverVariable.descriptor)
val resultType = call.type.makeNullable()
return IrBlockImpl(startOffset, endOffset, resultType, IrStatementOrigin.SAFE_CALL).apply {
statements += receiverVariable
statements += IrWhenImpl(startOffset, endOffset, resultType).apply {
val condition = IrCallImpl(
startOffset, endOffset, irBuiltIns.booleanType, irBuiltIns.eqeqSymbol, origin = IrStatementOrigin.EQEQ
).apply {
putValueArgument(0, IrGetValueImpl(startOffset, endOffset, variableSymbol))
putValueArgument(1, IrConstImpl.constNull(startOffset, endOffset, irBuiltIns.nothingNType))
}
branches += IrBranchImpl(
condition, IrConstImpl.constNull(startOffset, endOffset, irBuiltIns.nothingNType)
)
val newReceiver = IrGetValueImpl(startOffset, endOffset, variableSymbol)
val replacedCall = call.replaceReceiver(newReceiver, isDispatch)
branches += IrElseBranchImpl(
IrConstImpl.boolean(startOffset, endOffset, irBuiltIns.booleanType, true),
replacedCall
)
}
}
}
@@ -0,0 +1,152 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.backend.generators
import org.jetbrains.kotlin.fir.backend.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.isNullable
import org.jetbrains.kotlin.ir.builders.primitiveOp1
import org.jetbrains.kotlin.ir.builders.primitiveOp2
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.IrStatementOriginImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.util.getSimpleFunction
import java.util.*
internal class OperatorExpressionGenerator(
private val components: Fir2IrComponents,
private val visitor: Fir2IrVisitor,
private val callGenerator: CallAndReferenceGenerator
) : Fir2IrComponents by components {
companion object {
private val NEGATED_OPERATIONS: Set<FirOperation> = EnumSet.of(FirOperation.NOT_EQ, FirOperation.NOT_IDENTITY)
private val UNARY_OPERATIONS: Set<FirOperation> = EnumSet.of(FirOperation.EXCL)
}
fun convertComparisonExpression(comparisonExpression: FirComparisonExpression): IrExpression {
return comparisonExpression.convertWithOffsets { startOffset, endOffset ->
generateComparisonCall(startOffset, endOffset, comparisonExpression)
}
}
fun convertOperatorCall(operatorCall: FirOperatorCall): IrExpression {
return operatorCall.convertWithOffsets { startOffset, endOffset ->
generateOperatorCall(startOffset, endOffset, operatorCall.operation, operatorCall.arguments)
}
}
private fun generateComparisonCall(
startOffset: Int, endOffset: Int,
comparisonExpression: FirComparisonExpression
): IrExpression {
val operation = comparisonExpression.operation
fun fallbackToRealCall(): IrExpression {
val (symbol, origin) = getSymbolAndOriginForComparison(operation, irBuiltIns.intType.classifierOrFail)
return primitiveOp2(
startOffset, endOffset,
symbol!!,
irBuiltIns.booleanType,
origin,
comparisonExpression.compareToCall.accept(visitor, null) as IrExpression,
IrConstImpl.int(startOffset, endOffset, irBuiltIns.intType, 0)
)
}
val comparisonInfo = comparisonExpression.inferPrimitiveNumericComparisonInfo() ?: return fallbackToRealCall()
val comparisonType = comparisonInfo.comparisonType
val comparisonIrType = typeConverter.classIdToTypeMap[comparisonType.lookupTag.classId] ?: return fallbackToRealCall()
val (symbol, origin) = getSymbolAndOriginForComparison(operation, comparisonIrType.classifierOrFail)
return primitiveOp2(
startOffset, endOffset, symbol!!, irBuiltIns.booleanType, origin,
visitor.convertToIrExpression(comparisonExpression.left).asComparisonOperand(comparisonInfo.leftType, comparisonType),
visitor.convertToIrExpression(comparisonExpression.right).asComparisonOperand(comparisonInfo.rightType, comparisonType),
)
}
private fun getSymbolAndOriginForComparison(
operation: FirOperation,
classifier: IrClassifierSymbol
): Pair<IrSimpleFunctionSymbol?, IrStatementOriginImpl> {
return when (operation) {
FirOperation.LT -> irBuiltIns.lessFunByOperandType[classifier] to IrStatementOrigin.LT
FirOperation.GT -> irBuiltIns.greaterFunByOperandType[classifier] to IrStatementOrigin.GT
FirOperation.LT_EQ -> irBuiltIns.lessOrEqualFunByOperandType[classifier] to IrStatementOrigin.LTEQ
FirOperation.GT_EQ -> irBuiltIns.greaterOrEqualFunByOperandType[classifier] to IrStatementOrigin.GTEQ
else -> error("Unexpected comparison operation: $operation")
}
}
private fun generateOperatorCall(
startOffset: Int, endOffset: Int, operation: FirOperation, arguments: List<FirExpression>
): IrExpression {
val (type, symbol, origin) = when (operation) {
FirOperation.EQ -> Triple(irBuiltIns.booleanType, irBuiltIns.eqeqSymbol, IrStatementOrigin.EQEQ)
FirOperation.NOT_EQ -> Triple(irBuiltIns.booleanType, irBuiltIns.eqeqSymbol, IrStatementOrigin.EXCLEQ)
FirOperation.IDENTITY -> Triple(irBuiltIns.booleanType, irBuiltIns.eqeqeqSymbol, IrStatementOrigin.EQEQEQ)
FirOperation.NOT_IDENTITY -> Triple(irBuiltIns.booleanType, irBuiltIns.eqeqeqSymbol, IrStatementOrigin.EXCLEQEQ)
FirOperation.EXCL -> Triple(irBuiltIns.booleanType, irBuiltIns.booleanNotSymbol, IrStatementOrigin.EXCL)
FirOperation.LT, FirOperation.GT,
FirOperation.LT_EQ, FirOperation.GT_EQ,
FirOperation.OTHER, FirOperation.ASSIGN, FirOperation.PLUS_ASSIGN,
FirOperation.MINUS_ASSIGN, FirOperation.TIMES_ASSIGN,
FirOperation.DIV_ASSIGN, FirOperation.REM_ASSIGN,
FirOperation.IS, FirOperation.NOT_IS,
FirOperation.AS, FirOperation.SAFE_AS
-> {
TODO("Should not be here: incompatible operation in FirOperatorCall: $operation")
}
}
val result = if (operation in UNARY_OPERATIONS) {
primitiveOp1(startOffset, endOffset, symbol, type, origin, visitor.convertToIrExpression(arguments[0]))
} else {
val comparisonInfo = inferPrimitiveNumericComparisonInfo(arguments[0], arguments[1])
val comparisonType = comparisonInfo?.comparisonType
primitiveOp2(
startOffset, endOffset, symbol, type, origin,
visitor.convertToIrExpression(arguments[0]).asComparisonOperand(comparisonInfo?.leftType, comparisonType),
visitor.convertToIrExpression(arguments[1]).asComparisonOperand(comparisonInfo?.rightType, comparisonType)
)
}
if (operation !in NEGATED_OPERATIONS) return result
return primitiveOp1(startOffset, endOffset, irBuiltIns.booleanNotSymbol, irBuiltIns.booleanType, origin, result)
}
private fun IrExpression.asComparisonOperand(
operandType: ConeClassLikeType?,
targetType: ConeClassLikeType?
): IrExpression {
if (targetType == null) return this
if (operandType == null) throw AssertionError("operandType should be non-null")
val operandClassId = operandType.lookupTag.classId
val targetClassId = targetType.lookupTag.classId
if (operandClassId == targetClassId) return this
val conversionFunction =
typeConverter.classIdToSymbolMap[operandClassId]?.getSimpleFunction("to${targetType.lookupTag.classId.shortClassName.asString()}")
?: throw AssertionError("No conversion function for $operandType ~> $targetType")
val dispatchReceiver = this@asComparisonOperand
val unsafeIrCall = IrCallImpl(startOffset, endOffset, conversionFunction.owner.returnType, conversionFunction).also {
it.dispatchReceiver = dispatchReceiver
}
return if (operandType.isNullable) {
callGenerator.convertToSafeIrCall(unsafeIrCall, dispatchReceiver, isDispatch = true)
} else {
unsafeIrCall
}
}
}