Handle step progressions in ForLoopsLowering.
This commit is contained in:
committed by
max-kammerer
parent
38f0fd256e
commit
8a4185202f
@@ -6,10 +6,7 @@
|
||||
package org.jetbrains.kotlin.backend.common.ir
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.CommonBackendContext
|
||||
import org.jetbrains.kotlin.builtins.KOTLIN_REFLECT_FQ_NAME
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.PrimitiveType
|
||||
import org.jetbrains.kotlin.builtins.UnsignedType
|
||||
import org.jetbrains.kotlin.builtins.*
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
||||
@@ -109,6 +106,12 @@ abstract class Symbols<out T : CommonBackendContext>(val context: T, private val
|
||||
val progressionClasses = listOf(charProgression, intProgression, longProgression)
|
||||
val progressionClassesTypes = progressionClasses.map { it.descriptor.defaultType }.toSet()
|
||||
|
||||
val getProgressionLastElementByReturnType = builtInsPackage("kotlin", "internal").getContributedFunctions(
|
||||
Name.identifier("getProgressionLastElement"),
|
||||
NoLookupLocation.FROM_BACKEND
|
||||
).filter { it.containingDeclaration !is BuiltInsPackageFragment }
|
||||
.map { Pair(it.returnType!!, symbolTable.referenceSimpleFunction(it)) }.toMap()
|
||||
|
||||
val any = symbolTable.referenceClass(builtIns.any)
|
||||
val unit = symbolTable.referenceClass(builtIns.unit)
|
||||
|
||||
|
||||
+41
-17
@@ -31,22 +31,22 @@ internal enum class ProgressionType(val elementCastFunctionName: Name, val stepC
|
||||
|
||||
/** Returns the [IrType] of the `first`/`last` properties and elements in the progression. */
|
||||
fun elementType(builtIns: IrBuiltIns): IrType = when (this) {
|
||||
ProgressionType.INT_PROGRESSION -> builtIns.intType
|
||||
ProgressionType.LONG_PROGRESSION -> builtIns.longType
|
||||
ProgressionType.CHAR_PROGRESSION -> builtIns.charType
|
||||
INT_PROGRESSION -> builtIns.intType
|
||||
LONG_PROGRESSION -> builtIns.longType
|
||||
CHAR_PROGRESSION -> builtIns.charType
|
||||
}
|
||||
|
||||
/** Returns the [IrType] of the `step` property in the progression. */
|
||||
fun stepType(builtIns: IrBuiltIns): IrType = when (this) {
|
||||
ProgressionType.INT_PROGRESSION, ProgressionType.CHAR_PROGRESSION -> builtIns.intType
|
||||
ProgressionType.LONG_PROGRESSION -> builtIns.longType
|
||||
INT_PROGRESSION, CHAR_PROGRESSION -> builtIns.intType
|
||||
LONG_PROGRESSION -> builtIns.longType
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromIrType(irType: IrType, symbols: Symbols<CommonBackendContext>): ProgressionType? = when {
|
||||
irType.isSubtypeOfClass(symbols.charProgression) -> ProgressionType.CHAR_PROGRESSION
|
||||
irType.isSubtypeOfClass(symbols.intProgression) -> ProgressionType.INT_PROGRESSION
|
||||
irType.isSubtypeOfClass(symbols.longProgression) -> ProgressionType.LONG_PROGRESSION
|
||||
irType.isSubtypeOfClass(symbols.charProgression) -> CHAR_PROGRESSION
|
||||
irType.isSubtypeOfClass(symbols.intProgression) -> INT_PROGRESSION
|
||||
irType.isSubtypeOfClass(symbols.longProgression) -> LONG_PROGRESSION
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -107,26 +107,49 @@ internal class ProgressionHeaderInfo(
|
||||
val canOverflow: Boolean by lazy {
|
||||
if (canOverflow != null) return@lazy canOverflow
|
||||
|
||||
// Induction variable can overflow if it is not a const, or is MAX/MIN_VALUE (depending on direction).
|
||||
// We can't determine the safe limit at compile-time if "step" is not const.
|
||||
val stepValueAsLong = step.constLongValue ?: return@lazy true
|
||||
|
||||
// Induction variable can NOT overflow if "last" is const and is <= (MAX/MIN_VALUE - step) (depending on direction).
|
||||
//
|
||||
// Examples that can NOT overflow:
|
||||
// - `0..10` cannot overflow (10 <= MAX_VALUE - 1)
|
||||
// - `0..MAX_VALUE - 1` cannot overflow (MAX_VALUE - 1 <= MAX_VALUE - 1)
|
||||
// - `0..MAX_VALUE - 3 step 3` cannot overflow (MAX_VALUE - 3 <= MAX_VALUE - 3)
|
||||
// - `0 downTo -10` cannot overflow (-10 >= MIN_VALUE - (-1))
|
||||
// - `0 downTo MIN_VALUE + 1` (step is -1) cannot overflow (MIN_VALUE + 1 >= MIN_VALUE - (-1))
|
||||
// - `0 downTo MIN_VALUE + 3 step 3` (step is -3) cannot overflow (MIN_VALUE + 3 >= MIN_VALUE - (-3))
|
||||
//
|
||||
// Examples that CAN overflow:
|
||||
// - `0..MAX_VALUE` CAN overflow (MAX_VALUE > MAX_VALUE - 1)
|
||||
// - `0..MAX_VALUE - 2 step 3` cannot overflow (MAX_VALUE - 2 > MAX_VALUE - 3)
|
||||
// - `0 downTo MIN_VALUE` (step is -1) CAN overflow (MIN_VALUE < MIN_VALUE - (-1))
|
||||
// - `0 downTo MIN_VALUE + 2 step 3` (step is -3) cannot overflow (MIN_VALUE + 2 < MIN_VALUE - (-3))
|
||||
// - `0..10 step someStep()` CAN overflow (we don't know the step and hence can't determine the safe limit)
|
||||
// - `0..someLast()` CAN overflow (we don't know the direction)
|
||||
// - `someProgression()` CAN overflow (we don't know the direction)
|
||||
val lastValueAsLong = last.constLongValue ?: return@lazy true // If "last" is not a const Number or Char.
|
||||
val constLimitAsLong = when (direction) {
|
||||
when (direction) {
|
||||
ProgressionDirection.UNKNOWN ->
|
||||
// If we don't know the direction, we can't be sure which limit to use.
|
||||
return@lazy true
|
||||
ProgressionDirection.DECREASING ->
|
||||
when (progressionType) {
|
||||
true
|
||||
ProgressionDirection.DECREASING -> {
|
||||
val constLimitAsLong = when (progressionType) {
|
||||
ProgressionType.INT_PROGRESSION -> Int.MIN_VALUE.toLong()
|
||||
ProgressionType.CHAR_PROGRESSION -> Char.MIN_VALUE.toLong()
|
||||
ProgressionType.LONG_PROGRESSION -> Long.MIN_VALUE
|
||||
}
|
||||
ProgressionDirection.INCREASING ->
|
||||
when (progressionType) {
|
||||
lastValueAsLong < (constLimitAsLong - stepValueAsLong)
|
||||
}
|
||||
ProgressionDirection.INCREASING -> {
|
||||
val constLimitAsLong = when (progressionType) {
|
||||
ProgressionType.INT_PROGRESSION -> Int.MAX_VALUE.toLong()
|
||||
ProgressionType.CHAR_PROGRESSION -> Char.MAX_VALUE.toLong()
|
||||
ProgressionType.LONG_PROGRESSION -> Long.MAX_VALUE
|
||||
}
|
||||
lastValueAsLong > (constLimitAsLong - stepValueAsLong)
|
||||
}
|
||||
}
|
||||
constLimitAsLong == lastValueAsLong
|
||||
}
|
||||
|
||||
override fun asReversed() = ProgressionHeaderInfo(
|
||||
@@ -226,7 +249,8 @@ internal class HeaderInfoBuilder(context: CommonBackendContext, private val scop
|
||||
CharSequenceIndicesHandler(context),
|
||||
UntilHandler(context, progressionElementTypes),
|
||||
DownToHandler(context, progressionElementTypes),
|
||||
RangeToHandler(context, progressionElementTypes)
|
||||
RangeToHandler(context, progressionElementTypes),
|
||||
StepHandler(context, this)
|
||||
)
|
||||
|
||||
private val reversedHandler = ReversedHandler(context, this)
|
||||
|
||||
+303
-3
@@ -6,6 +6,7 @@
|
||||
package org.jetbrains.kotlin.backend.common.lower.loops
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.CommonBackendContext
|
||||
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.lower.matchers.Quantifier
|
||||
import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher
|
||||
@@ -22,6 +23,7 @@ import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
/** Builds a [HeaderInfo] for progressions built using the `rangeTo` function. */
|
||||
internal class RangeToHandler(private val context: CommonBackendContext, private val progressionElementTypes: Collection<IrType>) :
|
||||
@@ -93,7 +95,7 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
|
||||
// if (inductionVar <= last && B != MIN_VALUE) {
|
||||
// // Loop is not empty
|
||||
// do {
|
||||
// val loopVar = inductionVar
|
||||
// val i = inductionVar
|
||||
// inductionVar++
|
||||
// // Loop body
|
||||
// } while (inductionVar <= last)
|
||||
@@ -108,10 +110,10 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
|
||||
// // Standard form of loop over progression
|
||||
// var inductionVar = untilReceiverValue
|
||||
// val last = untilArg - 1
|
||||
// if (inductionVar <= last && untilFunArg != MIN_VALUE) {
|
||||
// if (inductionVar <= last && untilArg != MIN_VALUE) {
|
||||
// // Loop is not empty
|
||||
// do {
|
||||
// val loopVar = inductionVar
|
||||
// val i = inductionVar
|
||||
// inductionVar++
|
||||
// // Loop body
|
||||
// } while (inductionVar <= last)
|
||||
@@ -218,6 +220,303 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
|
||||
}
|
||||
}
|
||||
|
||||
/** Builds a [HeaderInfo] for progressions built using the `step` extension function. */
|
||||
internal class StepHandler(
|
||||
private val context: CommonBackendContext,
|
||||
private val visitor: IrElementVisitor<HeaderInfo?, Nothing?>
|
||||
) : ProgressionHandler {
|
||||
|
||||
private val symbols = context.ir.symbols
|
||||
|
||||
override val matcher = SimpleCalleeMatcher {
|
||||
singleArgumentExtension(FqName("kotlin.ranges.step"), symbols.progressionClasses.map { it.typeWith() })
|
||||
parameter(0) { it.type.isInt() || it.type.isLong() }
|
||||
}
|
||||
|
||||
override fun build(expression: IrCall, data: ProgressionType, scopeOwner: IrSymbol): HeaderInfo? =
|
||||
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
|
||||
// Retrieve the HeaderInfo from the underlying progression (if any).
|
||||
val nestedInfo = expression.extensionReceiver!!.accept(visitor, null) as? ProgressionHeaderInfo
|
||||
?: return null
|
||||
|
||||
val stepArg = expression.getValueArgument(0)!!
|
||||
// We can return the nested info if its step is constant and its absolute value is the same as the step argument. Examples:
|
||||
//
|
||||
// 1..10 step 1 // Nested step is 1, argument is 1. Equivalent to `1..10`.
|
||||
// 10 downTo 1 step 1 // Nested step is -1, argument is 1. Equivalent to `10 downTo 1`.
|
||||
// 10 downTo 1 step 2 step 2 // Nested step is -2, argument is 2. Equivalent to `10 downTo 1 step 2`.
|
||||
if (stepArg.constLongValue != null && nestedInfo.step.constLongValue?.absoluteValue == stepArg.constLongValue) {
|
||||
return nestedInfo
|
||||
}
|
||||
|
||||
// To reduce local variable usage, we create and use temporary variables only if necessary.
|
||||
var stepArgVar: IrVariable? = null
|
||||
val stepArgExpression = if (stepArg.canHaveSideEffects) {
|
||||
stepArgVar = scope.createTemporaryVariable(stepArg, nameHint = "stepArg")
|
||||
irGet(stepArgVar)
|
||||
} else {
|
||||
stepArg
|
||||
}
|
||||
|
||||
// The `step` standard library function only accepts positive values, and performs the following check:
|
||||
//
|
||||
// if (step > 0) step else throw IllegalArgumentException("Step must be positive, was: $step.")
|
||||
//
|
||||
// We insert this check in the lowered form only if necessary.
|
||||
val stepType = data.stepType(context.irBuiltIns)
|
||||
val stepGreaterFun = context.irBuiltIns.greaterFunByOperandType[stepType.toKotlinType()]!!
|
||||
val zeroStep = if (data == ProgressionType.LONG_PROGRESSION) irLong(0) else irInt(0)
|
||||
val throwIllegalStepExceptionCall = {
|
||||
irCall(context.irBuiltIns.illegalArgumentExceptionSymbol, stepType).apply {
|
||||
val exceptionMessage = irConcat()
|
||||
exceptionMessage.addArgument(irString("Step must be positive, was: "))
|
||||
exceptionMessage.addArgument(stepArgExpression.deepCopyWithSymbols())
|
||||
exceptionMessage.addArgument(irString("."))
|
||||
putValueArgument(0, exceptionMessage)
|
||||
}
|
||||
}
|
||||
val stepArgValueAsLong = stepArgExpression.constLongValue
|
||||
val checkedStepExpression = when {
|
||||
stepArgValueAsLong == null -> {
|
||||
// Step argument is not a constant.
|
||||
val stepPositiveCheck = irCall(stepGreaterFun).apply {
|
||||
putValueArgument(0, stepArgExpression.deepCopyWithSymbols())
|
||||
putValueArgument(1, zeroStep.deepCopyWithSymbols())
|
||||
}
|
||||
irIfThenElse(
|
||||
stepType,
|
||||
stepPositiveCheck,
|
||||
stepArgExpression.deepCopyWithSymbols(),
|
||||
throwIllegalStepExceptionCall()
|
||||
)
|
||||
}
|
||||
stepArgValueAsLong > 0L ->
|
||||
// Step argument is a positive constant and is valid.
|
||||
stepArgExpression.deepCopyWithSymbols()
|
||||
else ->
|
||||
// Step argument is a non-positive constant and is invalid, directly throw the exception.
|
||||
throwIllegalStepExceptionCall()
|
||||
}
|
||||
|
||||
// While the `step` function accepts positive values, the "step" value in the progression depends on the direction of the
|
||||
// nested progression. For example, in `10 downTo 1 step 2`, the nested progression is `10 downTo 1` which is decreasing,
|
||||
// therefore the step used should be negated (-2).
|
||||
//
|
||||
// If we don't know the direction of the nested progression (e.g., `someProgression() step 2`) then we have to check its value
|
||||
// first to determine whether to negate.
|
||||
var nestedStepVar: IrVariable? = null
|
||||
var checkedStepVar: IrVariable? = null
|
||||
val checkedAndMaybeNegatedStep = when (nestedInfo.direction) {
|
||||
ProgressionDirection.INCREASING -> checkedStepExpression
|
||||
ProgressionDirection.DECREASING -> checkedStepExpression.negate()
|
||||
ProgressionDirection.UNKNOWN -> {
|
||||
// Check value of nested step and negate step arg if needed: `if (nestedStep > 0) nestedStep else -nestedStep`
|
||||
// A temporary variable is created only if necessary, so we can preserve the evaluation order.
|
||||
val nestedStep = nestedInfo.step
|
||||
val nestedStepExpression = if (nestedStep.canHaveSideEffects) {
|
||||
nestedStepVar = scope.createTemporaryVariable(nestedStep, nameHint = "nestedStep")
|
||||
irGet(nestedStepVar)
|
||||
} else {
|
||||
nestedStep
|
||||
}
|
||||
val nestedStepPositiveCheck = irCall(stepGreaterFun).apply {
|
||||
putValueArgument(0, nestedStepExpression)
|
||||
putValueArgument(1, zeroStep.deepCopyWithSymbols())
|
||||
}
|
||||
|
||||
checkedStepVar = scope.createTemporaryVariable(checkedStepExpression, nameHint = "checkedStep")
|
||||
irIfThenElse(stepType, nestedStepPositiveCheck, irGet(checkedStepVar), irGet(checkedStepVar).negate())
|
||||
}
|
||||
}
|
||||
|
||||
// Store the final "step" a temporary variable only if necessary, so we can preserve the evaluation order.
|
||||
var newStepVar: IrVariable? = null
|
||||
val newStepExpression = if (checkedAndMaybeNegatedStep.canHaveSideEffects) {
|
||||
newStepVar = scope.createTemporaryVariable(checkedAndMaybeNegatedStep, nameHint = "newStep")
|
||||
irGet(newStepVar)
|
||||
} else {
|
||||
checkedAndMaybeNegatedStep
|
||||
}
|
||||
|
||||
// Store the nested "first" and "last" in temporary variables only if necessary, so we can preserve the evaluation order.
|
||||
var nestedFirstVar: IrVariable? = null
|
||||
val nestedFirst = nestedInfo.first
|
||||
val nestedFirstExpression = if (nestedFirst.canHaveSideEffects) {
|
||||
nestedFirstVar = scope.createTemporaryVariable(nestedFirst, nameHint = "nestedFirst")
|
||||
irGet(nestedFirstVar)
|
||||
} else {
|
||||
nestedFirst
|
||||
}
|
||||
|
||||
var nestedLastVar: IrVariable? = null
|
||||
val nestedLast = nestedInfo.last
|
||||
val nestedLastExpression = if (nestedLast.canHaveSideEffects) {
|
||||
nestedLastVar = scope.createTemporaryVariable(nestedLast, nameHint = "nestedLast")
|
||||
irGet(nestedLastVar)
|
||||
} else {
|
||||
nestedLast
|
||||
}
|
||||
|
||||
// Creating a progression with a step value != 1 may result in a "last" value that is smaller than the given "last". The new
|
||||
// "last" value is such that iterating over the progression (by incrementing by "step") does not go over the "last" value.
|
||||
//
|
||||
// For example, in `1..10 step 2`, the values in the progression are [1, 3, 5, 7, 9]. Therefore the "last" value used in the
|
||||
// stepped progression should be 9 even though the "last" in the nested progression is 10. Conversely, in `1..10 step 3`, the
|
||||
// values in the progression are [1, 4, 7, 10], therefore the "last" value in the stepped progression is still 10. On the other
|
||||
// hand, in `1..10 step 10`, the only value in the progression is 1, therefore the "last" value in the progression should be 1.
|
||||
// In all cases, the "first" value is unchanged and the nested "first" can be used.
|
||||
//
|
||||
// The standard library calculates the correct "last" value by calling the internal getProgressionLastElement() function and we
|
||||
// do the same when lowering to keep the behavior.
|
||||
//
|
||||
// In the case of multiple nested steps such as `1..10 step 2 step 3 step 2`, the recalculation happens 3 times:
|
||||
// - In the innermost stepped progression `1..10 step 2`, the values are [1, 3, 5, 7, 9], the new "last" value is 9. (The
|
||||
// return value of `getProgressionLastElement(1, 10, 2)` is 9.)
|
||||
// - For `...step 3`, the values are [1, 4, 7]. It is NOT [1, 4, 7, 10] because the innermost progression stopped at 9. (The
|
||||
// return value of `getProgressionLastElement(1, 9, 3)` is 7.)
|
||||
// - For `...step 2`, the original "last" value of 10 is NOT restored, because the previous step already reduced "last" to 7.
|
||||
// The values are [1, 3, 5, 7], the new "last" value is 7. (The return value of `getProgressionLastElement(1, 7, 2)` is 7.)
|
||||
// - Therefore the final values are: first = 1, last = 7, step = 2. The final "last" is calculated as:
|
||||
// getProgressionLastElement(1,
|
||||
// getProgressionLastElement(1,
|
||||
// getProgressionLastElement(1, 10, 2),
|
||||
// 3),
|
||||
// 2)
|
||||
val recalculatedLast =
|
||||
callGetProgressionLastElementIfNecessary(data, nestedFirstExpression, nestedLastExpression, newStepExpression)
|
||||
|
||||
// Consider the following for-loop:
|
||||
//
|
||||
// for (i in A..B step C step D) { // Loop body }
|
||||
//
|
||||
// ...where `A`, `B`, `C`, `D` may have side-effects. Variables will be created for those expressions where necessary, and we
|
||||
// must preserve the evaluation order when adding these variables. If all the above expressions can have side-effects (e.g.,
|
||||
// function calls), the final lowered form is something like:
|
||||
//
|
||||
// // Additional variables for inner step progression `A..B step C`:
|
||||
// val innerNestedFirst = A
|
||||
// val innerNestedLast = B
|
||||
// // No nested step var because step for `A..B` is a constant 1
|
||||
// val innerStepArg = C
|
||||
// val innerNewStep = if (innerStepArg > 0) innerStepArg
|
||||
// else throw IllegalArgumentException("Step must be positive, was: $innerStepArg.")
|
||||
//
|
||||
// // Additional variables for outer step progression `(A..B step C) step D`:
|
||||
// // No nested first var because `innerNestedFirst` is a local variable get (cannot have side-effects)
|
||||
// val outerNestedLast = // "last" for `A..B step C`
|
||||
// getProgressionLastElement(innerNestedFirst, innerNestedLast, innerNewStep)
|
||||
// // No nested step var because nested step `innerNewStep` is a local variable get (cannot have side-effects)
|
||||
// val outerStepArg = D
|
||||
// val outerNewStep = if (outerStepArg > 0) outerStepArg
|
||||
// else throw IllegalArgumentException("Step must be positive, was: outerStepArg.")
|
||||
//
|
||||
// // Standard form of loop over progression
|
||||
// var inductionVar = innerNestedFirst
|
||||
// val last = // "last" for `(A..B step C) step D`
|
||||
// getProgressionLastElement(innerNestedFirst, // "Passed through" from inner step progression
|
||||
// outerNestedLast, outerNewStep)
|
||||
// val step = outerNewStep
|
||||
// if (inductionVar <= last) {
|
||||
// // Loop is not empty
|
||||
// do {
|
||||
// val i = inductionVar
|
||||
// inductionVar += step
|
||||
// // Loop body
|
||||
// } while (i != last)
|
||||
// }
|
||||
//
|
||||
// Another example (`step` on non-literal progression expression):
|
||||
//
|
||||
// for (i in P step C) { // Loop body }
|
||||
//
|
||||
// ...where `P` and `C` have side-effects. The final lowered form is something like:
|
||||
//
|
||||
// // Additional variables:
|
||||
// val progression = P
|
||||
// val nestedFirst = progression.first
|
||||
// val nestedLast = progression.last
|
||||
// val nestedStep = progression.step
|
||||
// val stepArg = C
|
||||
// val checkedStep = if (stepArg > 0) stepArg
|
||||
// else throw IllegalArgumentException("Step must be positive, was: stepArg.")
|
||||
// val newStep = // Direction of P is unknown so we check its step to determine whether to negate
|
||||
// if (nestedStep > 0) checkedStep else -checkedStep
|
||||
//
|
||||
// // Standard form of loop over progression
|
||||
// var inductionVar = nestedFirst
|
||||
// val last = getProgressionLastElement(nestedFirst, nestedLast, newStep)
|
||||
// val step = newStep
|
||||
// if ((nestedStep > 0 && inductionVar <= last) || (nestedStep < 0 && last <= inductionVar)) {
|
||||
// // Loop is not empty
|
||||
// do {
|
||||
// val i = inductionVar
|
||||
// inductionVar += step
|
||||
// // Loop body
|
||||
// } while (i != last)
|
||||
// }
|
||||
//
|
||||
// If the nested progression is reversed, e.g.:
|
||||
//
|
||||
// for (i in (A..B).reversed() step C) { // Loop body }
|
||||
//
|
||||
// ...in the nested HeaderInfo, "first" is `B` and "last" is `A` (the progression goes from `B` to `A`). Therefore in this case,
|
||||
// the nested "last" variable must come before the nested "first" variable (if both variables are necessary).
|
||||
val additionalVariables = nestedInfo.additionalVariables + if (nestedInfo.isReversed) {
|
||||
listOfNotNull(nestedLastVar, nestedFirstVar, nestedStepVar, stepArgVar, checkedStepVar, newStepVar)
|
||||
} else {
|
||||
listOfNotNull(nestedFirstVar, nestedLastVar, nestedStepVar, stepArgVar, checkedStepVar, newStepVar)
|
||||
}
|
||||
|
||||
return ProgressionHeaderInfo(
|
||||
data,
|
||||
first = nestedFirstExpression,
|
||||
last = recalculatedLast,
|
||||
step = newStepExpression,
|
||||
isReversed = nestedInfo.isReversed,
|
||||
additionalVariables = additionalVariables,
|
||||
additionalNotEmptyCondition = nestedInfo.additionalNotEmptyCondition,
|
||||
direction = nestedInfo.direction
|
||||
)
|
||||
}
|
||||
|
||||
private fun DeclarationIrBuilder.callGetProgressionLastElementIfNecessary(
|
||||
progressionType: ProgressionType,
|
||||
first: IrExpression,
|
||||
last: IrExpression,
|
||||
step: IrExpression
|
||||
): IrExpression {
|
||||
// Calling getProgressionLastElement() is not needed if step == 1 or -1; the "last" value is unchanged in such cases.
|
||||
if (step.constLongValue?.absoluteValue == 1L) {
|
||||
return last
|
||||
}
|
||||
|
||||
// Call `getProgressionLastElement(first, last, step)`
|
||||
val stepType = progressionType.stepType(context.irBuiltIns).toKotlinType()
|
||||
val getProgressionLastElementFun = symbols.getProgressionLastElementByReturnType[stepType]
|
||||
?: throw IllegalArgumentException("No `getProgressionLastElement` for step type $stepType")
|
||||
return irCall(getProgressionLastElementFun).apply {
|
||||
putValueArgument(
|
||||
0, first.deepCopyWithSymbols().castIfNecessary(
|
||||
progressionType.stepType(context.irBuiltIns),
|
||||
progressionType.stepCastFunctionName
|
||||
)
|
||||
)
|
||||
putValueArgument(
|
||||
1, last.deepCopyWithSymbols().castIfNecessary(
|
||||
progressionType.stepType(context.irBuiltIns),
|
||||
progressionType.stepCastFunctionName
|
||||
)
|
||||
)
|
||||
putValueArgument(
|
||||
2, step.deepCopyWithSymbols().castIfNecessary(
|
||||
progressionType.stepType(context.irBuiltIns),
|
||||
progressionType.stepCastFunctionName
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Builds a [HeaderInfo] for progressions built using the `indices` extension property. */
|
||||
internal abstract class IndicesHandler(protected val context: CommonBackendContext) : ProgressionHandler {
|
||||
|
||||
@@ -243,6 +542,7 @@ internal abstract class IndicesHandler(protected val context: CommonBackendConte
|
||||
}
|
||||
|
||||
internal class CollectionIndicesHandler(context: CommonBackendContext) : IndicesHandler(context) {
|
||||
|
||||
override val matcher = SimpleCalleeMatcher {
|
||||
extensionReceiver { it != null && it.type.run { isArray() || isPrimitiveArray() || isCollection() } }
|
||||
fqName { it == FqName("kotlin.collections.<get-indices>") }
|
||||
|
||||
Reference in New Issue
Block a user