RangeContainsLowering: Handle Comparable ranges.
This commit is contained in:
committed by
Alexander Udalov
parent
1c1b1b4b0f
commit
60a2f7d03f
@@ -223,6 +223,7 @@ open class BuiltinSymbolsBase(protected val irBuiltIns: IrBuiltIns, protected va
|
|||||||
val mutableIterable = symbolTable.referenceClass(builtIns.mutableIterable)
|
val mutableIterable = symbolTable.referenceClass(builtIns.mutableIterable)
|
||||||
val mutableIterator = symbolTable.referenceClass(builtIns.mutableIterator)
|
val mutableIterator = symbolTable.referenceClass(builtIns.mutableIterator)
|
||||||
val mutableListIterator = symbolTable.referenceClass(builtIns.mutableListIterator)
|
val mutableListIterator = symbolTable.referenceClass(builtIns.mutableListIterator)
|
||||||
|
val comparable = symbolTable.referenceClass(builtIns.comparable)
|
||||||
|
|
||||||
private val binaryOperatorCache = mutableMapOf<Triple<Name, KotlinType, KotlinType>, IrSimpleFunctionSymbol>()
|
private val binaryOperatorCache = mutableMapOf<Triple<Name, KotlinType, KotlinType>, IrSimpleFunctionSymbol>()
|
||||||
|
|
||||||
|
|||||||
+73
-31
@@ -35,12 +35,10 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
|
|||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// 1. Handle (kotlin.ranges.)ClosedRange.contains on (kotlin.ranges.)Comparable.rangeTo(Comparable).
|
// - Handle contains on ClosedFloatingPointRange non-literal expression (similar to DefaultProgressionHandler)
|
||||||
// Make sure right extension function was called!
|
// - Note for unsigned until, the decremented last is signed (see UntilHandler.kt:81). Rather than converting back to unsigned,
|
||||||
// 2. Handle contains on ClosedFloatingPointRange non-literal expression (similar to DefaultProgressionHandler)
|
|
||||||
// 3. Note for unsigned until, the decremented last is signed (see UntilHandler.kt:81). Rather than converting back to unsigned,
|
|
||||||
// should we remove the conversion call instead?
|
// should we remove the conversion call instead?
|
||||||
// 4. Unsigned step has similar concerns as well. getProgressionLastElement return value is signed.
|
// - Unsigned step has similar concerns as well. getProgressionLastElement return value is signed.
|
||||||
|
|
||||||
val rangeContainsLoweringPhase = makeIrFilePhase(
|
val rangeContainsLoweringPhase = makeIrFilePhase(
|
||||||
::RangeContainsLowering,
|
::RangeContainsLowering,
|
||||||
@@ -133,6 +131,7 @@ private class Transformer(
|
|||||||
val upper: IrExpression
|
val upper: IrExpression
|
||||||
val shouldUpperComeFirst: Boolean
|
val shouldUpperComeFirst: Boolean
|
||||||
val useCompareTo: Boolean
|
val useCompareTo: Boolean
|
||||||
|
val isNumericRange: Boolean
|
||||||
val additionalNotEmptyCondition: IrExpression?
|
val additionalNotEmptyCondition: IrExpression?
|
||||||
val additionalStatements = mutableListOf<IrStatement>()
|
val additionalStatements = mutableListOf<IrStatement>()
|
||||||
|
|
||||||
@@ -184,6 +183,7 @@ private class Transformer(
|
|||||||
|
|
||||||
// `compareTo` must be used for UInt/ULong; they don't have intrinsic comparison operators.
|
// `compareTo` must be used for UInt/ULong; they don't have intrinsic comparison operators.
|
||||||
useCompareTo = headerInfo.progressionType is UnsignedProgressionType
|
useCompareTo = headerInfo.progressionType is UnsignedProgressionType
|
||||||
|
isNumericRange = true
|
||||||
additionalNotEmptyCondition = headerInfo.additionalNotEmptyCondition
|
additionalNotEmptyCondition = headerInfo.additionalNotEmptyCondition
|
||||||
}
|
}
|
||||||
is FloatingPointRangeHeaderInfo -> {
|
is FloatingPointRangeHeaderInfo -> {
|
||||||
@@ -191,6 +191,15 @@ private class Transformer(
|
|||||||
upper = headerInfo.endInclusive
|
upper = headerInfo.endInclusive
|
||||||
shouldUpperComeFirst = false
|
shouldUpperComeFirst = false
|
||||||
useCompareTo = false
|
useCompareTo = false
|
||||||
|
isNumericRange = true
|
||||||
|
additionalNotEmptyCondition = null
|
||||||
|
}
|
||||||
|
is ComparableRangeInfo -> {
|
||||||
|
lower = headerInfo.start
|
||||||
|
upper = headerInfo.endInclusive
|
||||||
|
shouldUpperComeFirst = false
|
||||||
|
useCompareTo = true
|
||||||
|
isNumericRange = false
|
||||||
additionalNotEmptyCondition = null
|
additionalNotEmptyCondition = null
|
||||||
}
|
}
|
||||||
else -> return null
|
else -> return null
|
||||||
@@ -226,9 +235,9 @@ private class Transformer(
|
|||||||
// ** - Bound with side effect is stored in a temp variable to ensure evaluation even if right side is short-circuited.
|
// ** - Bound with side effect is stored in a temp variable to ensure evaluation even if right side is short-circuited.
|
||||||
// *** - Bound with side effect is on left side of && to make sure it always gets evaluated.
|
// *** - Bound with side effect is on left side of && to make sure it always gets evaluated.
|
||||||
|
|
||||||
val (argVar, argExpression) = createTemporaryVariableIfNecessary(argument, "containsArg")
|
var (argVar, argExpression) = createTemporaryVariableIfNecessary(argument, "containsArg")
|
||||||
val lowerExpression: IrExpression
|
var lowerExpression: IrExpression
|
||||||
val upperExpression: IrExpression
|
var upperExpression: IrExpression
|
||||||
val useLowerClauseOnLeftSide: Boolean
|
val useLowerClauseOnLeftSide: Boolean
|
||||||
if (argVar != null) {
|
if (argVar != null) {
|
||||||
val (lowerVar, tmpLowerExpression) = createTemporaryVariableIfNecessary(lower, "containsLower")
|
val (lowerVar, tmpLowerExpression) = createTemporaryVariableIfNecessary(lower, "containsLower")
|
||||||
@@ -266,44 +275,57 @@ private class Transformer(
|
|||||||
|
|
||||||
// TODO: Handle unsigned (comparisonClass is currently null).
|
// TODO: Handle unsigned (comparisonClass is currently null).
|
||||||
val builtIns = context.irBuiltIns
|
val builtIns = context.irBuiltIns
|
||||||
val comparisonClass =
|
val comparisonClass = if (isNumericRange) {
|
||||||
computeComparisonClass(this@Transformer.context.ir.symbols, lowerExpression.type, upperExpression.type, argExpression.type)
|
computeComparisonClass(this@Transformer.context.ir.symbols, lowerExpression.type, upperExpression.type, argExpression.type)
|
||||||
?: return null
|
?: return null
|
||||||
|
} else {
|
||||||
|
assert(headerInfo is ComparableRangeInfo)
|
||||||
|
this@Transformer.context.ir.symbols.comparable.owner
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNumericRange) {
|
||||||
|
lowerExpression = lowerExpression.castIfNecessary(comparisonClass)
|
||||||
|
upperExpression = upperExpression.castIfNecessary(comparisonClass)
|
||||||
|
argExpression = argExpression.castIfNecessary(comparisonClass)
|
||||||
|
}
|
||||||
|
|
||||||
val lessOrEqualFun = builtIns.lessOrEqualFunByOperandType.getValue(if (useCompareTo) builtIns.intClass else comparisonClass.symbol)
|
val lessOrEqualFun = builtIns.lessOrEqualFunByOperandType.getValue(if (useCompareTo) builtIns.intClass else comparisonClass.symbol)
|
||||||
val compareToFun = comparisonClass.functions.singleOrNull {
|
val compareToFun = comparisonClass.functions.singleOrNull {
|
||||||
it.name == OperatorNameConventions.COMPARE_TO &&
|
it.name == OperatorNameConventions.COMPARE_TO &&
|
||||||
it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null &&
|
it.dispatchReceiverParameter != null && it.extensionReceiverParameter == null &&
|
||||||
it.valueParameters.size == 1 && it.valueParameters[0].type == argument.type.makeNotNull()
|
it.valueParameters.size == 1 && (!isNumericRange || it.valueParameters[0].type == argument.type.makeNotNull())
|
||||||
} ?: return null
|
} ?: return null
|
||||||
// TODO: Handle null. This can happen with an extension fun, e.g., IntRange.compareTo(String) (See rangeContainsString.kt).
|
|
||||||
|
|
||||||
|
// contains() function for ComparableRange is implemented as `value >= start && value <= endInclusive` (`value` is the argument).
|
||||||
|
// Therefore the dispatch receiver for the compareTo() calls should be the argument. This is important since the implementation
|
||||||
|
// for compareTo() may have side effects dependent on which expressions are the receiver and argument
|
||||||
|
// (see evaluationOrderForComparableRange.kt test).
|
||||||
val lowerClause = if (useCompareTo) {
|
val lowerClause = if (useCompareTo) {
|
||||||
|
irCall(lessOrEqualFun).apply {
|
||||||
|
putValueArgument(0, irInt(0))
|
||||||
|
putValueArgument(1, irCall(compareToFun).apply {
|
||||||
|
dispatchReceiver = argExpression
|
||||||
|
putValueArgument(0, lowerExpression)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
irCall(lessOrEqualFun).apply {
|
||||||
|
putValueArgument(0, lowerExpression)
|
||||||
|
putValueArgument(1, argExpression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val upperClause = if (useCompareTo) {
|
||||||
irCall(lessOrEqualFun).apply {
|
irCall(lessOrEqualFun).apply {
|
||||||
putValueArgument(0, irCall(compareToFun).apply {
|
putValueArgument(0, irCall(compareToFun).apply {
|
||||||
dispatchReceiver = lowerExpression.castIfNecessary(comparisonClass)
|
dispatchReceiver = argExpression.deepCopyWithSymbols()
|
||||||
putValueArgument(0, argExpression.castIfNecessary(comparisonClass))
|
putValueArgument(0, upperExpression)
|
||||||
})
|
})
|
||||||
putValueArgument(1, irInt(0))
|
putValueArgument(1, irInt(0))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
irCall(lessOrEqualFun).apply {
|
irCall(lessOrEqualFun).apply {
|
||||||
putValueArgument(0, lowerExpression.castIfNecessary(comparisonClass))
|
putValueArgument(0, argExpression.deepCopyWithSymbols())
|
||||||
putValueArgument(1, argExpression.castIfNecessary(comparisonClass))
|
putValueArgument(1, upperExpression)
|
||||||
}
|
|
||||||
}
|
|
||||||
val upperClause = if (useCompareTo) {
|
|
||||||
irCall(lessOrEqualFun).apply {
|
|
||||||
putValueArgument(0, irInt(0))
|
|
||||||
putValueArgument(1, irCall(compareToFun).apply {
|
|
||||||
dispatchReceiver = upperExpression.castIfNecessary(comparisonClass)
|
|
||||||
putValueArgument(0, argExpression.deepCopyWithSymbols().castIfNecessary(comparisonClass))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
irCall(lessOrEqualFun).apply {
|
|
||||||
putValueArgument(0, argExpression.deepCopyWithSymbols().castIfNecessary(comparisonClass))
|
|
||||||
putValueArgument(1, upperExpression.castIfNecessary(comparisonClass))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +400,11 @@ internal open class RangeHeaderInfoBuilder(context: CommonBackendContext, scopeO
|
|||||||
RangeToHandler(context)
|
RangeToHandler(context)
|
||||||
)
|
)
|
||||||
|
|
||||||
override val callHandlers = listOf(ReversedHandler(context, this), FloatingPointRangeToHandler)
|
override val callHandlers = listOf(
|
||||||
|
FloatingPointRangeToHandler,
|
||||||
|
ComparableRangeToHandler(context),
|
||||||
|
ReversedHandler(context, this)
|
||||||
|
)
|
||||||
|
|
||||||
override val expressionHandlers = listOf(DefaultProgressionHandler(context))
|
override val expressionHandlers = listOf(DefaultProgressionHandler(context))
|
||||||
}
|
}
|
||||||
@@ -387,7 +413,7 @@ internal open class RangeHeaderInfoBuilder(context: CommonBackendContext, scopeO
|
|||||||
internal object FloatingPointRangeToHandler : HeaderInfoFromCallHandler<Nothing?> {
|
internal object FloatingPointRangeToHandler : HeaderInfoFromCallHandler<Nothing?> {
|
||||||
|
|
||||||
override val matcher = SimpleCalleeMatcher {
|
override val matcher = SimpleCalleeMatcher {
|
||||||
fqName { it == FqName("kotlin.ranges.rangeTo") }
|
fqName { it == FqName("kotlin.ranges.${OperatorNameConventions.RANGE_TO}") }
|
||||||
extensionReceiver { it != null && it.type.run { isFloat() || isDouble() } }
|
extensionReceiver { it != null && it.type.run { isFloat() || isDouble() } }
|
||||||
parameterCount { it == 1 }
|
parameterCount { it == 1 }
|
||||||
parameter(0) { it.type.run { isFloat() || isDouble() } }
|
parameter(0) { it.type.run { isFloat() || isDouble() } }
|
||||||
@@ -399,3 +425,19 @@ internal object FloatingPointRangeToHandler : HeaderInfoFromCallHandler<Nothing?
|
|||||||
endInclusive = expression.getValueArgument(0)!!
|
endInclusive = expression.getValueArgument(0)!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Builds a [HeaderInfo] for ranges of Comparables built using the `rangeTo` extension function. */
|
||||||
|
internal class ComparableRangeToHandler(context: CommonBackendContext) : HeaderInfoFromCallHandler<Nothing?> {
|
||||||
|
|
||||||
|
override val matcher = SimpleCalleeMatcher {
|
||||||
|
fqName { it == FqName("kotlin.ranges.${OperatorNameConventions.RANGE_TO}") }
|
||||||
|
extensionReceiver { it != null && it.type.isSubtypeOfClass(context.ir.symbols.comparable) }
|
||||||
|
parameterCount { it == 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun build(expression: IrCall, data: Nothing?, scopeOwner: IrSymbol) =
|
||||||
|
ComparableRangeInfo(
|
||||||
|
start = expression.extensionReceiver!!,
|
||||||
|
endInclusive = expression.getValueArgument(0)!!
|
||||||
|
)
|
||||||
|
}
|
||||||
+7
@@ -55,6 +55,13 @@ internal class FloatingPointRangeHeaderInfo(
|
|||||||
override fun asReversed(): HeaderInfo? = null
|
override fun asReversed(): HeaderInfo? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class ComparableRangeInfo(
|
||||||
|
val start: IrExpression,
|
||||||
|
val endInclusive: IrExpression
|
||||||
|
) : HeaderInfo() {
|
||||||
|
override fun asReversed(): HeaderInfo? = null
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class NumericHeaderInfo(
|
internal sealed class NumericHeaderInfo(
|
||||||
val progressionType: ProgressionType,
|
val progressionType: ProgressionType,
|
||||||
val first: IrExpression,
|
val first: IrExpression,
|
||||||
|
|||||||
+2
-2
@@ -440,7 +440,7 @@ internal class WithIndexLoopHeader(
|
|||||||
is ProgressionHeaderInfo -> ProgressionLoopHeader(nestedInfo, this@with, context)
|
is ProgressionHeaderInfo -> ProgressionLoopHeader(nestedInfo, this@with, context)
|
||||||
is IterableHeaderInfo -> IterableLoopHeader(nestedInfo)
|
is IterableHeaderInfo -> IterableLoopHeader(nestedInfo)
|
||||||
is WithIndexHeaderInfo -> throw IllegalStateException("Nested WithIndexHeaderInfo not allowed for WithIndexLoopHeader")
|
is WithIndexHeaderInfo -> throw IllegalStateException("Nested WithIndexHeaderInfo not allowed for WithIndexLoopHeader")
|
||||||
is FloatingPointRangeHeaderInfo -> error("Unexpected FloatingPointRangeHeaderInfo for loops")
|
is FloatingPointRangeHeaderInfo, is ComparableRangeInfo -> error("Unexpected ${nestedInfo::class.simpleName} for loops")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not build own indexVariable if the nested loop header has an inductionVariable == 0 and step == 1.
|
// Do not build own indexVariable if the nested loop header has an inductionVariable == 0 and step == 1.
|
||||||
@@ -669,7 +669,7 @@ internal class HeaderProcessor(
|
|||||||
is ProgressionHeaderInfo -> ProgressionLoopHeader(headerInfo, builder, context)
|
is ProgressionHeaderInfo -> ProgressionLoopHeader(headerInfo, builder, context)
|
||||||
is WithIndexHeaderInfo -> WithIndexLoopHeader(headerInfo, builder, context)
|
is WithIndexHeaderInfo -> WithIndexLoopHeader(headerInfo, builder, context)
|
||||||
is IterableHeaderInfo -> IterableLoopHeader(headerInfo)
|
is IterableHeaderInfo -> IterableLoopHeader(headerInfo)
|
||||||
is FloatingPointRangeHeaderInfo -> error("Unexpected FloatingPointRangeHeaderInfo for loops")
|
is FloatingPointRangeHeaderInfo, is ComparableRangeInfo -> error("Unexpected ${headerInfo::class.simpleName} for loops")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TODO KT-36829 Optimize 'in' expressions in JVM_IR
|
|
||||||
fun test1(a: String) = a in "alpha" .. "omega"
|
fun test1(a: String) = a in "alpha" .. "omega"
|
||||||
fun test2(a: String) = a !in "alpha" .. "omega"
|
fun test2(a: String) = a !in "alpha" .. "omega"
|
||||||
|
fun <T : Comparable<T>> test3(x: T, left: T, right: T) = x in left .. right
|
||||||
|
fun <T : Enum<T>> test4(x: T, left: T, right: T) = x in left .. right
|
||||||
|
|
||||||
// 0 rangeTo
|
// 0 rangeTo
|
||||||
// 0 contains
|
// 0 contains
|
||||||
// 4 compareTo
|
// 8 compareTo
|
||||||
Reference in New Issue
Block a user