Simplify loop building by encoding open/closed bound information in the

HeaderInfo object, and modifying the operator in the loop condition.

The "additional emptiness condition" is no longer necessary with this.
The open/closed property was removed from HeaderInfo in an earlier
commit, but bringing it back in to simplify the loop building makes
more sense.

Also expanded tests for evaluation order of range bounds.
This commit is contained in:
Mark Punzalan
2019-04-02 10:12:55 -07:00
committed by max-kammerer
parent 7680e7fd56
commit dd65e0876f
16 changed files with 454 additions and 361 deletions
@@ -41,48 +41,59 @@ val forLoopsPhase = makeIrFilePhase(
*
* For example, this loop:
* ```
* for (i in first..last) { // Do something with i }
* for (loopVar in A..B) { // Loop body }
* ```
* is represented in IR in such a manner:
* ```
* val it = (first..last).iterator()
* val it = (A..B).iterator()
* while (it.hasNext()) {
* val i = it.next()
* // Do something with i
* val loopVar = it.next()
* // Loop body
* }
* ```
* We transform it into one of the following loops:
*
* ```
* // 1. If the induction variable cannot overflow, i.e., `last` is const and != MAX_VALUE (if increasing, or MIN_VALUE if decreasing),
* // OR if loop is an until loop (e.g., `for (i in first until last)`):
* // 1. If the induction variable cannot overflow, i.e., `B` is const and != MAX_VALUE (if increasing, or MIN_VALUE if decreasing).
*
* var inductionVar = first
* while (inductionVar <= last) { // (inductionVar >= last if the progression is decreasing)
* val i = inductionVar
* inductionVar++
* // Do something with i
* var inductionVar = A
* var last = B
* while (inductionVar <= last) { // (`inductionVar >= last` if the progression is decreasing)
* val loopVar = inductionVar
* inductionVar++ // (`inductionVar--` if the progression is decreasing)
* // Loop body
* }
*
* // 2. If the induction variable CAN overflow, i.e., `last` is not const or is MAX/MIN_VALUE:
*
* var inductionVar = first
* if (inductionVar <= last) { // (inductionVar >= last if the progression is decreasing)
* var inductionVar = A
* var last = B
* if (inductionVar <= last) { // (`inductionVar >= last` if the progression is decreasing)
* // Loop is not empty
* do {
* val i = inductionVar
* inductionVar++
* // Do something with i
* } while (i != last)
* val loopVar = inductionVar
* inductionVar++ // (`inductionVar--` if the progression is decreasing)
* // Loop body
* } while (loopVar != last)
* }
*
* // 3. If loop is an until loop (e.g., `for (i in A until B)`), it cannot overflow and we use `<` for comparisons:
*
* var inductionVar = A
* var last = B
* while (inductionVar < last) {
* val loopVar = inductionVar
* inductionVar++
* // Loop body
* }
* ```
* In case of iteration over an array, we transform it into the following:
* In case of iteration over an array (e.g., `for (i in array)`), we transform it into the following:
* ```
* var inductionVar = 0
* val last = array.size - 1
* while (inductionVar <= last) {
* val i = array[inductionVar++]
* // Do something with i
* val last = array.size
* while (inductionVar < last) {
* val loopVar = array[inductionVar++]
* // Loop body
* }
* ```
*/
@@ -61,7 +61,8 @@ internal sealed class HeaderInfo(
val progressionType: ProgressionType,
val first: IrExpression,
val last: IrExpression,
val step: IrExpression
val step: IrExpression,
val isLastInclusive: Boolean
) {
val direction: ProgressionDirection by lazy {
// If step is a constant (either Int or Long), then we can determine the direction.
@@ -82,10 +83,10 @@ internal class ProgressionHeaderInfo(
first: IrExpression,
last: IrExpression,
step: IrExpression,
isLastInclusive: Boolean = true,
canOverflow: Boolean? = null,
val additionalVariables: List<IrVariable> = listOf(),
val additionalNotEmptyCondition: IrExpression? = null
) : HeaderInfo(progressionType, first, last, step) {
val additionalVariables: List<IrVariable> = listOf()
) : HeaderInfo(progressionType, first, last, step, isLastInclusive) {
private val _canOverflow: Boolean? = canOverflow
val canOverflow: Boolean by lazy {
@@ -129,7 +130,8 @@ internal class ArrayHeaderInfo(
ProgressionType.INT_PROGRESSION,
first,
last,
step
step,
isLastInclusive = false
)
/** Matches a call to `iterator()` and builds a [HeaderInfo] out of the call's context. */
@@ -26,10 +26,10 @@ import org.jetbrains.kotlin.name.Name
/** Contains information about variables used in the loop. */
internal sealed class ForLoopHeader(
protected open val headerInfo: HeaderInfo,
val inductionVariable: IrVariable,
val last: IrVariable,
val step: IrVariable,
val progressionType: ProgressionType,
var loopVariable: IrVariable? = null
) {
/** Expression used to initialize the loop variable at the beginning of the loop. */
@@ -48,26 +48,98 @@ internal sealed class ForLoopHeader(
* Returns null if no check is needed for the for-loop.
*/
abstract fun buildNotEmptyConditionIfNecessary(builder: DeclarationIrBuilder): IrExpression?
protected fun buildLoopCondition(builder: DeclarationIrBuilder): IrExpression =
with(builder) {
val builtIns = context.irBuiltIns
val progressionType = headerInfo.progressionType
val progressionKotlinType = progressionType.elementType(builtIns).toKotlinType()
val compFun =
if (headerInfo.isLastInclusive) builtIns.lessOrEqualFunByOperandType[progressionKotlinType]!!
else builtIns.lessFunByOperandType[progressionKotlinType]!!
// The default condition depends on the direction.
return when (headerInfo.direction) {
ProgressionDirection.DECREASING ->
// last <= inductionVar (use `<` if last is exclusive)
irCall(compFun).apply {
putValueArgument(0, irGet(last))
putValueArgument(1, irGet(inductionVariable))
}
ProgressionDirection.INCREASING ->
// inductionVar <= last (use `<` if last is exclusive)
irCall(compFun).apply {
putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, irGet(last))
}
ProgressionDirection.UNKNOWN -> {
// If the direction is unknown, we check depending on the "step" value:
// // (use `<` if last is exclusive)
// (step > 0 && inductionVar <= last) || (step < 0 || last <= inductionVar)
val stepKotlinType = progressionType.stepType(builtIns).toKotlinType()
val zero = if (progressionType == ProgressionType.LONG_PROGRESSION) irLong(0) else irInt(0)
context.oror(
context.andand(
irCall(builtIns.greaterFunByOperandType[stepKotlinType]!!).apply {
putValueArgument(0, irGet(step))
putValueArgument(1, zero)
},
irCall(compFun).apply {
putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, irGet(last))
}),
context.andand(
irCall(builtIns.lessFunByOperandType[stepKotlinType]!!).apply {
putValueArgument(0, irGet(step))
putValueArgument(1, zero)
},
irCall(compFun).apply {
putValueArgument(0, irGet(last))
putValueArgument(1, irGet(inductionVariable))
})
)
}
}
}
}
internal class ProgressionLoopHeader(
private val headerInfo: ProgressionHeaderInfo,
override val headerInfo: ProgressionHeaderInfo,
inductionVariable: IrVariable,
last: IrVariable,
step: IrVariable
) : ForLoopHeader(inductionVariable, last, step, headerInfo.progressionType) {
) : ForLoopHeader(headerInfo, inductionVariable, last, step) {
override fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder) = with(builder) {
// loopVariable = inductionVariable
irGet(inductionVariable)
}
// For this loop:
//
// for (i in first()..last() step step())
//
// ...the functions may have side-effects so we need to call them in the following order: first() (inductionVariable), last(), step().
// Additional variables come first as they may be needed to the subsequent variables.
override val declarations: List<IrStatement>
get() = headerInfo.additionalVariables + listOf(inductionVariable, last, step)
override fun buildInnerLoop(builder: DeclarationIrBuilder, loop: IrLoop, newBody: IrExpression?): IrLoop = with(builder) {
if (headerInfo.canOverflow) {
// Condition: loopVariable != last. We cannot use the induction variable because it can overflow.
// If the induction variable CAN overflow, we cannot use it in the loop condition. Loop is lowered into something like:
//
// var inductionVar = A
// var last = B
// if (inductionVar <= last) {
// // Loop is not empty
// do {
// val loopVar = inductionVar
// inductionVar++
// // Loop body
// } while (loopVar != last)
// }
//
// The `if (inductionVar <= last)` "not empty" check is added in buildNotEmptyConditionIfNecessary().
assert(loopVariable != null)
val booleanNotFun = context.irBuiltIns.booleanClass.functions.first { it.owner.name.asString() == "not" }
val newCondition = irCallOp(booleanNotFun, booleanNotFun.owner.returnType, irCall(context.irBuiltIns.eqeqSymbol).apply {
@@ -80,85 +152,38 @@ internal class ProgressionLoopHeader(
body = newBody
}
} else {
// If the induction variable cannot overflow, use a while loop using the "not empty" condition.
val newCondition = buildNotEmptyCondition(this@with)
// If the induction variable can NOT overflow, use a simple while loop. Loop is lowered into something like:
//
// var inductionVar = A
// var last = B
// while (inductionVar <= last) {
// val loopVar = inductionVar
// inductionVar++
// // Loop body
// }
IrWhileLoopImpl(loop.startOffset, loop.endOffset, loop.type, loop.origin).apply {
label = loop.label
condition = newCondition
condition = buildLoopCondition(this@with)
body = newBody
}
}
}
// If the induction variable cannot overflow, we do NOT need the enclosing "not empty" check because the for-loop is lowered into a
// while loop that uses the "not empty" condition (see buildInnerLoop()); the enclosing check would be redundant.
// If the induction variable can NOT overflow, we do NOT need the enclosing "not empty" check because the for-loop is lowered into a
// simple while loop (see buildInnerLoop()); the enclosing check would be redundant.
override fun buildNotEmptyConditionIfNecessary(builder: DeclarationIrBuilder): IrExpression? =
if (headerInfo.canOverflow) buildNotEmptyCondition(builder) else null
private fun buildNotEmptyCondition(builder: DeclarationIrBuilder): IrExpression =
with(builder) {
val builtIns = context.irBuiltIns
val progressionKotlinType = progressionType.elementType(builtIns).toKotlinType()
val lessOrEqualFun = builtIns.lessOrEqualFunByOperandType[progressionKotlinType]!!
// The default "not empty" condition depends on the direction.
val notEmptyCondition = when (headerInfo.direction) {
ProgressionDirection.DECREASING ->
// last <= inductionVariable
irCall(lessOrEqualFun).apply {
putValueArgument(0, irGet(last))
putValueArgument(1, irGet(inductionVariable))
}
ProgressionDirection.INCREASING ->
// inductionVariable <= last
irCall(lessOrEqualFun).apply {
putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, irGet(last))
}
ProgressionDirection.UNKNOWN -> {
// If the direction is unknown, we check depending on the "step" value:
// (step > 0 && inductionVariable <= last) || (step < 0 || last <= inductionVariable)
val stepKotlinType = progressionType.stepType(builtIns).toKotlinType()
val zero = if (progressionType == ProgressionType.LONG_PROGRESSION) irLong(0) else irInt(0)
context.oror(
context.andand(
irCall(builtIns.greaterFunByOperandType[stepKotlinType]!!).apply {
putValueArgument(0, irGet(step))
putValueArgument(1, zero)
},
irCall(lessOrEqualFun).apply {
putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, irGet(last))
}),
context.andand(
irCall(builtIns.lessFunByOperandType[stepKotlinType]!!).apply {
putValueArgument(0, irGet(step))
putValueArgument(1, zero)
},
irCall(lessOrEqualFun).apply {
putValueArgument(0, irGet(last))
putValueArgument(1, irGet(inductionVariable))
})
)
}
}
if (headerInfo.additionalNotEmptyCondition != null) context.andand(
headerInfo.additionalNotEmptyCondition,
notEmptyCondition
) else notEmptyCondition
}
if (headerInfo.canOverflow) buildLoopCondition(builder) else null
}
internal class ArrayLoopHeader(
private val headerInfo: ArrayHeaderInfo,
override val headerInfo: ArrayHeaderInfo,
inductionVariable: IrVariable,
last: IrVariable,
step: IrVariable
) : ForLoopHeader(inductionVariable, last, step, ProgressionType.INT_PROGRESSION) {
) : ForLoopHeader(headerInfo, inductionVariable, last, step) {
override fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder) = with(builder) {
// loopVariable = array[inductionVariable]
// inductionVar = loopVar[inductionVariable]
val arrayGetFun = headerInfo.arrayVariable.type.getClass()!!.functions.first { it.name.asString() == "get" }
irCall(arrayGetFun).apply {
dispatchReceiver = irGet(headerInfo.arrayVariable)
@@ -170,17 +195,18 @@ internal class ArrayLoopHeader(
get() = listOf(headerInfo.arrayVariable, inductionVariable, last, step)
override fun buildInnerLoop(builder: DeclarationIrBuilder, loop: IrLoop, newBody: IrExpression?): IrLoop = with(builder) {
// Condition: inductionVariable != last
val builtIns = context.irBuiltIns
val callee = builtIns.lessOrEqualFunByOperandType[builtIns.int]!!
val newCondition = irCall(callee).apply {
putValueArgument(0, irGet(inductionVariable))
putValueArgument(1, irGet(last))
}
// Loop is lowered into something like:
//
// var inductionVar = 0
// var last = array.size
// while (inductionVar < last) {
// val loopVar = array[inductionVar++]
// inductionVar++
// // Loop body
// }
IrWhileLoopImpl(loop.startOffset, loop.endOffset, loop.type, loop.origin).apply {
label = loop.label
condition = newCondition
condition = buildLoopCondition(this@with)
body = newBody
}
}
@@ -224,18 +250,13 @@ internal class HeaderProcessor(
with(headerInfo) {
// For this loop:
//
// ```
// for (i in first()..last() step step())
// ```
// for (i in first()..last() step step())
//
// ...the functions may have side-effects so we need to call them in the following
// order: first(), last(), step().
//
// We also need to cast them to conform to the progression type so that operations
// in the induction variable within the loop are more efficient.
// We need to cast first(), last(). and step() to conform to the progression type so
// that operations on the induction variable within the loop are more efficient.
//
// In the above example, if first() is a Long and last() is an Int, this creates a
// LongProgression so last(), should be cast to a Long.
// LongProgression so last() should be cast to a Long.
val inductionVariable = scope.createTemporaryVariable(
first.castIfNecessary(
progressionType.elementType(context.irBuiltIns),
@@ -6,23 +6,19 @@
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.SimpleCalleeMatcher
import org.jetbrains.kotlin.backend.common.lower.matchers.createIrCallMatcher
import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.properties
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -80,78 +76,15 @@ internal class UntilHandler(private val context: CommonBackendContext, private v
override fun build(call: IrCall, data: ProgressionType): HeaderInfo? =
with(context.createIrBuilder(call.symbol, call.startOffset, call.endOffset)) {
val boundArg = call.getValueArgument(0)!!
val bound = ensureNotNullable(
boundArg.castIfNecessary(
data.elementType(context.irBuiltIns),
data.elementCastFunctionName
)
)
// `bound` may be needed for an additional condition to the emptiness check (see comments below). If so, store `bound` in a
// temporary variable as it may be an expression with side-effects and we should only evaluate it once.
val boundVar = if (needsMinValueCondition(data, boundArg)) scope.createTemporaryVariable(
bound,
nameHint = "bound",
origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE
) else null
// `last = bound - 1` for the loop `for (i in first until bound)`.
val decFun = data.decFun(context.irBuiltIns)
val last = irCallOp(decFun.symbol, bound.type, if (boundVar != null) irGet(boundVar) else bound)
// The default "not empty" check cannot be used for the corner case:
//
// for (i in a until MIN_VALUE) {}
//
// ...which should always be considered an empty range. When the given bound is MIN_VALUE, and because `last = bound - 1`,
// "last" will underflow to MAX_VALUE, therefore the default "not empty" check:
//
// if (inductionVar <= last) { /* loop */ }
//
// ...will always be true and won't consider the range as empty. Therefore, we need to add an additional condition to the
// "not empty" check so that it becomes:
//
// if (inductionVar <= last && bound > MIN_VALUE) { /* loop */ }
ProgressionHeaderInfo(
data,
first = call.extensionReceiver!!,
last = last,
last = call.getValueArgument(0)!!,
step = irInt(1),
canOverflow = false,
additionalVariables = listOfNotNull(boundVar),
additionalNotEmptyCondition = if (boundVar != null) buildMinValueCondition(data, irGet(boundVar)) else null
isLastInclusive = false,
canOverflow = false
)
}
/** Returns true (i.e., min value condition is needed) if `bound` is non-const OR is MIN_VALUE. */
private fun needsMinValueCondition(progressionType: ProgressionType, bound: IrExpression): Boolean {
val boundValue = (bound as? IrConst<*>)?.value
val boundValueAsLong = when (boundValue) {
is Number -> boundValue.toLong()
is Char -> boundValue.toLong()
else -> return true // If "bound" is not a const Number or Char.
}
val minValueAsLong = when (progressionType) {
ProgressionType.INT_PROGRESSION -> Int.MIN_VALUE.toLong()
ProgressionType.CHAR_PROGRESSION -> Char.MIN_VALUE.toLong()
ProgressionType.LONG_PROGRESSION -> Long.MIN_VALUE
}
return minValueAsLong == boundValueAsLong
}
private fun DeclarationIrBuilder.buildMinValueCondition(progressionType: ProgressionType, bound: IrExpression): IrExpression {
val irBuiltIns = context.irBuiltIns
val minConst = when (progressionType) {
ProgressionType.INT_PROGRESSION -> irInt(Int.MIN_VALUE)
ProgressionType.CHAR_PROGRESSION -> irChar(Char.MIN_VALUE)
ProgressionType.LONG_PROGRESSION -> irLong(Long.MIN_VALUE)
}
val progressionKotlinType = progressionType.elementType(irBuiltIns).toKotlinType()
return irCall(irBuiltIns.greaterFunByOperandType[progressionKotlinType]!!).apply {
putValueArgument(0, bound)
putValueArgument(1, minConst)
}
}
}
/** Builds a [HeaderInfo] for progressions built using the `indices` extension property. */
@@ -167,18 +100,18 @@ internal class IndicesHandler(val context: CommonBackendContext) : ProgressionHa
override fun build(call: IrCall, data: ProgressionType): HeaderInfo? =
with(context.createIrBuilder(call.symbol, call.startOffset, call.endOffset)) {
// `last = array.size - 1` for the loop `for (i in array.indices)`.
// `last = array.size` for the loop `for (i in array.indices)`.
val arraySizeProperty = call.extensionReceiver!!.type.getClass()!!.properties.first { it.name.asString() == "size" }
val decFun = data.decFun(context.irBuiltIns)
val last = irCallOp(decFun.symbol, data.elementType(context.irBuiltIns), irCall(arraySizeProperty.getter!!).apply {
val last = irCall(arraySizeProperty.getter!!).apply {
dispatchReceiver = call.extensionReceiver
})
}
ProgressionHeaderInfo(
data,
first = irInt(0),
last = last,
step = irInt(1),
isLastInclusive = false,
canOverflow = false // Cannot overflow because `last` is at most MAX_VALUE - 1
)
}
@@ -225,8 +158,6 @@ internal class DefaultProgressionHandler(private val context: CommonBackendConte
/** Builds a [HeaderInfo] for arrays. */
internal class ArrayIterationHandler(private val context: CommonBackendContext) : HeaderInfoHandler<Nothing?> {
private val intDecFun = ProgressionType.INT_PROGRESSION.decFun(context.irBuiltIns)
override val matcher = createIrCallMatcher {
origin { it == IrStatementOrigin.FOR_LOOP_ITERATOR }
// TODO: Support rare cases like `T : IntArray`
@@ -255,11 +186,11 @@ internal class ArrayIterationHandler(private val context: CommonBackendContext)
origin = IrDeclarationOrigin.FOR_LOOP_IMPLICIT_VARIABLE
)
// `last = array.size - 1` for the loop `for (i in array.indices)`.
// `last = array.size` for the loop `for (i in array.indices)`.
val arraySizeProperty = arrayReference.type.getClass()!!.properties.first { it.name.asString() == "size" }
val last = irCallOp(intDecFun.symbol, context.irBuiltIns.intType, irCall(arraySizeProperty.getter!!).apply {
val last = irCall(arraySizeProperty.getter!!).apply {
dispatchReceiver = irGet(arrayReference)
})
}
ArrayHeaderInfo(
first = irInt(0),
@@ -268,14 +199,4 @@ internal class ArrayIterationHandler(private val context: CommonBackendContext)
arrayVariable = arrayReference
)
}
}
private fun ProgressionType.decFun(builtIns: IrBuiltIns): IrFunction {
val symbol =
when (this) {
ProgressionType.INT_PROGRESSION -> builtIns.intClass
ProgressionType.LONG_PROGRESSION -> builtIns.longClass
ProgressionType.CHAR_PROGRESSION -> builtIns.charClass
}
return symbol.owner.functions.first { it.name.asString() == "dec" }
}
@@ -0,0 +1,21 @@
// KJS_WITH_FULL_RUNTIME
// WITH_RUNTIME
import kotlin.test.*
val log = StringBuilder()
fun logged(message: String, value: Int) =
value.also { log.append(message) }
fun box(): String {
var sum = 0
for (i in logged("start;", 4) downTo logged("end;", 1)) {
sum = sum * 10 + i
}
assertEquals(4321, sum)
assertEquals("start;end;", log.toString())
return "OK"
}
@@ -5,15 +5,15 @@ import kotlin.test.*
val log = StringBuilder()
fun logged(message: String, value: Int) =
value.also { log.append(message) }
value.also { log.append(message) }
fun box(): String {
var s = 0
for (i in (logged("start;", 1) .. logged("end;", 2)).reversed()) {
s += i
var sum = 0
for (i in logged("start;", 1)..logged("end;", 4)) {
sum = sum * 10 + i
}
assertEquals(3, s)
assertEquals(1234, sum)
assertEquals("start;end;", log.toString())
@@ -5,15 +5,15 @@ import kotlin.test.*
val log = StringBuilder()
fun logged(message: String, value: Int) =
value.also { log.append(message) }
value.also { log.append(message) }
fun box(): String {
var s = 0
for (i in (logged("start;", 2) downTo logged("end;", 1)).reversed()) {
s += i
var sum = 0
for (i in (logged("start;", 4) downTo logged("end;", 1)).reversed()) {
sum = sum * 10 + i
}
assertEquals(3, s)
assertEquals(1234, sum)
assertEquals("start;end;", log.toString())
@@ -0,0 +1,21 @@
// KJS_WITH_FULL_RUNTIME
// WITH_RUNTIME
import kotlin.test.*
val log = StringBuilder()
fun logged(message: String, value: Int) =
value.also { log.append(message) }
fun box(): String {
var sum = 0
for (i in (logged("start;", 1)..logged("end;", 4)).reversed()) {
sum = sum * 10 + i
}
assertEquals(4321, sum)
assertEquals("start;end;", log.toString())
return "OK"
}
@@ -5,15 +5,15 @@ import kotlin.test.*
val log = StringBuilder()
fun logged(message: String, value: Int) =
value.also { log.append(message) }
value.also { log.append(message) }
fun box(): String {
var s = 0
for (i in (logged("start;", 1) until logged("end;", 3)).reversed()) {
s += i
var sum = 0
for (i in (logged("start;", 1) until logged("end;", 5)).reversed()) {
sum = sum * 10 + i
}
assertEquals(3, s)
assertEquals(4321, sum)
assertEquals("start;end;", log.toString())
@@ -0,0 +1,21 @@
// KJS_WITH_FULL_RUNTIME
// WITH_RUNTIME
import kotlin.test.*
val log = StringBuilder()
fun logged(message: String, value: Int) =
value.also { log.append(message) }
fun box(): String {
var sum = 0
for (i in logged("start;", 1) until logged("end;", 5)) {
sum = sum * 10 + i
}
assertEquals(1234, sum)
assertEquals("start;end;", log.toString())
return "OK"
}
@@ -1,6 +1,6 @@
fun test(): Int {
fun test(a: Int, b: Int): Int {
var sum = 0
for (i in 1 until 6) {
for (i in a until b) {
sum = sum * 10 + i
}
return sum
@@ -18583,6 +18583,49 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/evaluationOrder")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluationOrder extends AbstractBlackBoxCodegenTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInEvaluationOrder() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/evaluationOrder"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("forInDownToEvaluationOrder.kt")
public void testForInDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInDownToEvaluationOrder.kt");
}
@TestMetadata("forInRangeLiteralEvaluationOrder.kt")
public void testForInRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedDownToEvaluationOrder.kt")
public void testForInReversedDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedDownToEvaluationOrder.kt");
}
@TestMetadata("forInReversedRangeLiteralEvaluationOrder.kt")
public void testForInReversedRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedUntilEvaluationOrder.kt")
public void testForInReversedUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedUntilEvaluationOrder.kt");
}
@TestMetadata("forInUntilEvaluationOrder.kt")
public void testForInUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInUntilEvaluationOrder.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/expression")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -18958,34 +19001,6 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
public void testForInReversedUntilWithNonConstBounds() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/forInReversedUntilWithNonConstBounds.kt");
}
@TestMetadata("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluationOrder extends AbstractBlackBoxCodegenTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInEvaluationOrder() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("forInReversedDownToEvaluationOrder.kt")
public void testForInReversedDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedDownToEvaluationOrder.kt");
}
@TestMetadata("forInReversedRangeLiteralEvaluationOrder.kt")
public void testForInReversedRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedUntilEvaluationOrder.kt")
public void testForInReversedUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedUntilEvaluationOrder.kt");
}
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/forInUntil")
@@ -18583,6 +18583,49 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/evaluationOrder")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluationOrder extends AbstractLightAnalysisModeTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInEvaluationOrder() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/evaluationOrder"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("forInDownToEvaluationOrder.kt")
public void testForInDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInDownToEvaluationOrder.kt");
}
@TestMetadata("forInRangeLiteralEvaluationOrder.kt")
public void testForInRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedDownToEvaluationOrder.kt")
public void testForInReversedDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedDownToEvaluationOrder.kt");
}
@TestMetadata("forInReversedRangeLiteralEvaluationOrder.kt")
public void testForInReversedRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedUntilEvaluationOrder.kt")
public void testForInReversedUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedUntilEvaluationOrder.kt");
}
@TestMetadata("forInUntilEvaluationOrder.kt")
public void testForInUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInUntilEvaluationOrder.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/expression")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -18958,34 +19001,6 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
public void testForInReversedUntilWithNonConstBounds() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/forInReversedUntilWithNonConstBounds.kt");
}
@TestMetadata("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluationOrder extends AbstractLightAnalysisModeTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInEvaluationOrder() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("forInReversedDownToEvaluationOrder.kt")
public void testForInReversedDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedDownToEvaluationOrder.kt");
}
@TestMetadata("forInReversedRangeLiteralEvaluationOrder.kt")
public void testForInReversedRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedUntilEvaluationOrder.kt")
public void testForInReversedUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedUntilEvaluationOrder.kt");
}
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/forInUntil")
@@ -18588,6 +18588,49 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/evaluationOrder")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluationOrder extends AbstractIrBlackBoxCodegenTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
public void testAllFilesPresentInEvaluationOrder() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/evaluationOrder"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
}
@TestMetadata("forInDownToEvaluationOrder.kt")
public void testForInDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInDownToEvaluationOrder.kt");
}
@TestMetadata("forInRangeLiteralEvaluationOrder.kt")
public void testForInRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedDownToEvaluationOrder.kt")
public void testForInReversedDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedDownToEvaluationOrder.kt");
}
@TestMetadata("forInReversedRangeLiteralEvaluationOrder.kt")
public void testForInReversedRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedUntilEvaluationOrder.kt")
public void testForInReversedUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedUntilEvaluationOrder.kt");
}
@TestMetadata("forInUntilEvaluationOrder.kt")
public void testForInUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInUntilEvaluationOrder.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/expression")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -18963,34 +19006,6 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
public void testForInReversedUntilWithNonConstBounds() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/forInReversedUntilWithNonConstBounds.kt");
}
@TestMetadata("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluationOrder extends AbstractIrBlackBoxCodegenTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
public void testAllFilesPresentInEvaluationOrder() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
}
@TestMetadata("forInReversedDownToEvaluationOrder.kt")
public void testForInReversedDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedDownToEvaluationOrder.kt");
}
@TestMetadata("forInReversedRangeLiteralEvaluationOrder.kt")
public void testForInReversedRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedUntilEvaluationOrder.kt")
public void testForInReversedUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedUntilEvaluationOrder.kt");
}
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/forInUntil")
@@ -14643,6 +14643,49 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/evaluationOrder")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluationOrder extends AbstractIrJsCodegenBoxTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
}
public void testAllFilesPresentInEvaluationOrder() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/evaluationOrder"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS_IR, true);
}
@TestMetadata("forInDownToEvaluationOrder.kt")
public void testForInDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInDownToEvaluationOrder.kt");
}
@TestMetadata("forInRangeLiteralEvaluationOrder.kt")
public void testForInRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedDownToEvaluationOrder.kt")
public void testForInReversedDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedDownToEvaluationOrder.kt");
}
@TestMetadata("forInReversedRangeLiteralEvaluationOrder.kt")
public void testForInReversedRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedUntilEvaluationOrder.kt")
public void testForInReversedUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedUntilEvaluationOrder.kt");
}
@TestMetadata("forInUntilEvaluationOrder.kt")
public void testForInUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInUntilEvaluationOrder.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/expression")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -15018,34 +15061,6 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
public void testForInReversedUntilWithNonConstBounds() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/forInReversedUntilWithNonConstBounds.kt");
}
@TestMetadata("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluationOrder extends AbstractIrJsCodegenBoxTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
}
public void testAllFilesPresentInEvaluationOrder() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS_IR, true);
}
@TestMetadata("forInReversedDownToEvaluationOrder.kt")
public void testForInReversedDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedDownToEvaluationOrder.kt");
}
@TestMetadata("forInReversedRangeLiteralEvaluationOrder.kt")
public void testForInReversedRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedUntilEvaluationOrder.kt")
public void testForInReversedUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedUntilEvaluationOrder.kt");
}
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/forInUntil")
@@ -15813,6 +15813,49 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/evaluationOrder")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluationOrder extends AbstractJsCodegenBoxTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath);
}
public void testAllFilesPresentInEvaluationOrder() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/evaluationOrder"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
}
@TestMetadata("forInDownToEvaluationOrder.kt")
public void testForInDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInDownToEvaluationOrder.kt");
}
@TestMetadata("forInRangeLiteralEvaluationOrder.kt")
public void testForInRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedDownToEvaluationOrder.kt")
public void testForInReversedDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedDownToEvaluationOrder.kt");
}
@TestMetadata("forInReversedRangeLiteralEvaluationOrder.kt")
public void testForInReversedRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedUntilEvaluationOrder.kt")
public void testForInReversedUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInReversedUntilEvaluationOrder.kt");
}
@TestMetadata("forInUntilEvaluationOrder.kt")
public void testForInUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/evaluationOrder/forInUntilEvaluationOrder.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/expression")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -16188,34 +16231,6 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
public void testForInReversedUntilWithNonConstBounds() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/forInReversedUntilWithNonConstBounds.kt");
}
@TestMetadata("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EvaluationOrder extends AbstractJsCodegenBoxTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath);
}
public void testAllFilesPresentInEvaluationOrder() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
}
@TestMetadata("forInReversedDownToEvaluationOrder.kt")
public void testForInReversedDownToEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedDownToEvaluationOrder.kt");
}
@TestMetadata("forInReversedRangeLiteralEvaluationOrder.kt")
public void testForInReversedRangeLiteralEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedRangeLiteralEvaluationOrder.kt");
}
@TestMetadata("forInReversedUntilEvaluationOrder.kt")
public void testForInReversedUntilEvaluationOrder() throws Exception {
runTest("compiler/testData/codegen/box/ranges/forInReversed/evaluationOrder/forInReversedUntilEvaluationOrder.kt");
}
}
}
@TestMetadata("compiler/testData/codegen/box/ranges/forInUntil")