Do not cache "last" value (i.e., size/length) in lowered for-loop
iteration over CharSequences. CharSequences may be mutable (e.g., StringBuilder) and therefore its contents and length can change within the loop.
This commit is contained in:
committed by
max-kammerer
parent
3da3e1cae9
commit
c16b59191b
+13
-1
@@ -72,6 +72,7 @@ internal sealed class HeaderInfo(
|
|||||||
val first: IrExpression,
|
val first: IrExpression,
|
||||||
val last: IrExpression,
|
val last: IrExpression,
|
||||||
val step: IrExpression,
|
val step: IrExpression,
|
||||||
|
val canCacheLast: Boolean,
|
||||||
val isReversed: Boolean,
|
val isReversed: Boolean,
|
||||||
val direction: ProgressionDirection,
|
val direction: ProgressionDirection,
|
||||||
val additionalNotEmptyCondition: IrExpression?
|
val additionalNotEmptyCondition: IrExpression?
|
||||||
@@ -95,7 +96,13 @@ internal class ProgressionHeaderInfo(
|
|||||||
direction: ProgressionDirection,
|
direction: ProgressionDirection,
|
||||||
additionalNotEmptyCondition: IrExpression? = null,
|
additionalNotEmptyCondition: IrExpression? = null,
|
||||||
val additionalVariables: List<IrVariable> = listOf()
|
val additionalVariables: List<IrVariable> = listOf()
|
||||||
) : HeaderInfo(progressionType, first, last, step, isReversed, direction, additionalNotEmptyCondition) {
|
) : HeaderInfo(
|
||||||
|
progressionType, first, last, step,
|
||||||
|
canCacheLast = true,
|
||||||
|
isReversed = isReversed,
|
||||||
|
direction = direction,
|
||||||
|
additionalNotEmptyCondition = additionalNotEmptyCondition
|
||||||
|
) {
|
||||||
|
|
||||||
val canOverflow: Boolean by lazy {
|
val canOverflow: Boolean by lazy {
|
||||||
if (canOverflow != null) return@lazy canOverflow
|
if (canOverflow != null) return@lazy canOverflow
|
||||||
@@ -142,6 +149,7 @@ internal class IndexedGetHeaderInfo(
|
|||||||
first: IrExpression,
|
first: IrExpression,
|
||||||
last: IrExpression,
|
last: IrExpression,
|
||||||
step: IrExpression,
|
step: IrExpression,
|
||||||
|
canCacheLast: Boolean = true,
|
||||||
val objectVariable: IrVariable,
|
val objectVariable: IrVariable,
|
||||||
val expressionHandler: IndexedGetIterationHandler
|
val expressionHandler: IndexedGetIterationHandler
|
||||||
) : HeaderInfo(
|
) : HeaderInfo(
|
||||||
@@ -149,6 +157,7 @@ internal class IndexedGetHeaderInfo(
|
|||||||
first,
|
first,
|
||||||
last,
|
last,
|
||||||
step,
|
step,
|
||||||
|
canCacheLast = canCacheLast,
|
||||||
isReversed = false,
|
isReversed = false,
|
||||||
direction = ProgressionDirection.INCREASING,
|
direction = ProgressionDirection.INCREASING,
|
||||||
additionalNotEmptyCondition = null
|
additionalNotEmptyCondition = null
|
||||||
@@ -222,9 +231,12 @@ internal class HeaderInfoBuilder(context: CommonBackendContext, private val scop
|
|||||||
|
|
||||||
private val reversedHandler = ReversedHandler(context, this)
|
private val reversedHandler = ReversedHandler(context, this)
|
||||||
|
|
||||||
|
// NOTE: StringIterationHandler MUST come before CharSequenceIterationHandler.
|
||||||
|
// String is subtype of CharSequence and therefore its handler is more specialized.
|
||||||
private val expressionHandlers = listOf(
|
private val expressionHandlers = listOf(
|
||||||
ArrayIterationHandler(context),
|
ArrayIterationHandler(context),
|
||||||
DefaultProgressionHandler(context),
|
DefaultProgressionHandler(context),
|
||||||
|
StringIterationHandler(context),
|
||||||
CharSequenceIterationHandler(context)
|
CharSequenceIterationHandler(context)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+101
-85
@@ -43,17 +43,15 @@ internal data class LoopReplacement(
|
|||||||
internal sealed class ForLoopHeader(
|
internal sealed class ForLoopHeader(
|
||||||
protected open val headerInfo: HeaderInfo,
|
protected open val headerInfo: HeaderInfo,
|
||||||
val inductionVariable: IrVariable,
|
val inductionVariable: IrVariable,
|
||||||
val last: IrVariable,
|
val lastExpression: IrExpression,
|
||||||
val step: IrVariable,
|
val step: IrVariable,
|
||||||
var loopVariable: IrVariable? = null,
|
var loopVariable: IrVariable? = null,
|
||||||
val isLastInclusive: Boolean
|
val isLastInclusive: Boolean,
|
||||||
|
val declarations: List<IrVariable>
|
||||||
) {
|
) {
|
||||||
/** Expression used to initialize the loop variable at the beginning of the loop. */
|
/** Expression used to initialize the loop variable at the beginning of the loop. */
|
||||||
abstract fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder): IrExpression
|
abstract fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder): IrExpression
|
||||||
|
|
||||||
/** Declarations used in the loop condition and body (e.g., induction variable). */
|
|
||||||
abstract val declarations: List<IrStatement>
|
|
||||||
|
|
||||||
/** Builds a new loop from the old loop. */
|
/** Builds a new loop from the old loop. */
|
||||||
abstract fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?): LoopReplacement
|
abstract fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?): LoopReplacement
|
||||||
|
|
||||||
@@ -88,14 +86,14 @@ internal sealed class ForLoopHeader(
|
|||||||
ProgressionDirection.DECREASING ->
|
ProgressionDirection.DECREASING ->
|
||||||
// last <= inductionVar (use `<` if last is exclusive)
|
// last <= inductionVar (use `<` if last is exclusive)
|
||||||
irCall(compFun).apply {
|
irCall(compFun).apply {
|
||||||
putValueArgument(0, irGet(last))
|
putValueArgument(0, lastExpression)
|
||||||
putValueArgument(1, irGet(inductionVariable))
|
putValueArgument(1, irGet(inductionVariable))
|
||||||
}
|
}
|
||||||
ProgressionDirection.INCREASING ->
|
ProgressionDirection.INCREASING ->
|
||||||
// inductionVar <= last (use `<` if last is exclusive)
|
// inductionVar <= last (use `<` if last is exclusive)
|
||||||
irCall(compFun).apply {
|
irCall(compFun).apply {
|
||||||
putValueArgument(0, irGet(inductionVariable))
|
putValueArgument(0, irGet(inductionVariable))
|
||||||
putValueArgument(1, irGet(last))
|
putValueArgument(1, lastExpression)
|
||||||
}
|
}
|
||||||
ProgressionDirection.UNKNOWN -> {
|
ProgressionDirection.UNKNOWN -> {
|
||||||
// If the direction is unknown, we check depending on the "step" value:
|
// If the direction is unknown, we check depending on the "step" value:
|
||||||
@@ -111,7 +109,7 @@ internal sealed class ForLoopHeader(
|
|||||||
},
|
},
|
||||||
irCall(compFun).apply {
|
irCall(compFun).apply {
|
||||||
putValueArgument(0, irGet(inductionVariable))
|
putValueArgument(0, irGet(inductionVariable))
|
||||||
putValueArgument(1, irGet(last))
|
putValueArgument(1, lastExpression)
|
||||||
}),
|
}),
|
||||||
context.andand(
|
context.andand(
|
||||||
irCall(builtIns.lessFunByOperandType[stepKotlinType]!!).apply {
|
irCall(builtIns.lessFunByOperandType[stepKotlinType]!!).apply {
|
||||||
@@ -119,7 +117,7 @@ internal sealed class ForLoopHeader(
|
|||||||
putValueArgument(1, if (isLong) irLong(0) else irInt(0))
|
putValueArgument(1, if (isLong) irLong(0) else irInt(0))
|
||||||
},
|
},
|
||||||
irCall(compFun).apply {
|
irCall(compFun).apply {
|
||||||
putValueArgument(0, irGet(last))
|
putValueArgument(0, lastExpression)
|
||||||
putValueArgument(1, irGet(inductionVariable))
|
putValueArgument(1, irGet(inductionVariable))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -131,29 +129,22 @@ internal sealed class ForLoopHeader(
|
|||||||
internal class ProgressionLoopHeader(
|
internal class ProgressionLoopHeader(
|
||||||
override val headerInfo: ProgressionHeaderInfo,
|
override val headerInfo: ProgressionHeaderInfo,
|
||||||
inductionVariable: IrVariable,
|
inductionVariable: IrVariable,
|
||||||
last: IrVariable,
|
lastExpression: IrExpression,
|
||||||
step: IrVariable
|
step: IrVariable,
|
||||||
) : ForLoopHeader(headerInfo, inductionVariable, last, step, isLastInclusive = true) {
|
declarations: List<IrVariable>
|
||||||
|
) : ForLoopHeader(
|
||||||
|
headerInfo, inductionVariable,
|
||||||
|
lastExpression = lastExpression,
|
||||||
|
step = step,
|
||||||
|
isLastInclusive = true,
|
||||||
|
declarations = declarations
|
||||||
|
) {
|
||||||
|
|
||||||
override fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder) = with(builder) {
|
override fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder) = with(builder) {
|
||||||
// loopVariable = inductionVariable
|
// loopVariable = inductionVariable
|
||||||
irGet(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.
|
|
||||||
//
|
|
||||||
// In the case of a reversed range, the `inductionVariable` and `last` variables are swapped, therefore the declaration order must be
|
|
||||||
// swapped to preserve the correct evaluation order.
|
|
||||||
override val declarations: List<IrStatement>
|
|
||||||
get() = headerInfo.additionalVariables +
|
|
||||||
(if (headerInfo.isReversed) listOf(last, inductionVariable) else listOf(inductionVariable, last)) +
|
|
||||||
step
|
|
||||||
|
|
||||||
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?) =
|
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?) =
|
||||||
with(builder) {
|
with(builder) {
|
||||||
val newLoop = if (headerInfo.canOverflow) {
|
val newLoop = if (headerInfo.canOverflow) {
|
||||||
@@ -169,7 +160,7 @@ internal class ProgressionLoopHeader(
|
|||||||
// }
|
// }
|
||||||
IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
|
IrDoWhileLoopImpl(oldLoop.startOffset, oldLoop.endOffset, oldLoop.type, oldLoop.origin).apply {
|
||||||
label = oldLoop.label
|
label = oldLoop.label
|
||||||
condition = irNotEquals(irGet(loopVariable!!), irGet(last))
|
condition = irNotEquals(irGet(loopVariable!!), lastExpression)
|
||||||
body = newBody
|
body = newBody
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -203,9 +194,14 @@ internal class ProgressionLoopHeader(
|
|||||||
internal class IndexedGetLoopHeader(
|
internal class IndexedGetLoopHeader(
|
||||||
override val headerInfo: IndexedGetHeaderInfo,
|
override val headerInfo: IndexedGetHeaderInfo,
|
||||||
inductionVariable: IrVariable,
|
inductionVariable: IrVariable,
|
||||||
last: IrVariable,
|
lastExpression: IrExpression,
|
||||||
step: IrVariable
|
step: IrVariable,
|
||||||
) : ForLoopHeader(headerInfo, inductionVariable, last, step, isLastInclusive = false) {
|
declarations: List<IrVariable>
|
||||||
|
) : ForLoopHeader(
|
||||||
|
headerInfo, inductionVariable, lastExpression, step,
|
||||||
|
isLastInclusive = false,
|
||||||
|
declarations = declarations
|
||||||
|
) {
|
||||||
|
|
||||||
override fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder) = with(builder) {
|
override fun initializeLoopVariable(symbols: Symbols<CommonBackendContext>, builder: DeclarationIrBuilder) = with(builder) {
|
||||||
// inductionVar = loopVar[inductionVariable]
|
// inductionVar = loopVar[inductionVariable]
|
||||||
@@ -216,16 +212,13 @@ internal class IndexedGetLoopHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val declarations: List<IrStatement>
|
|
||||||
get() = listOf(headerInfo.objectVariable, inductionVariable, last, step)
|
|
||||||
|
|
||||||
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?): LoopReplacement = with(builder) {
|
override fun buildLoop(builder: DeclarationIrBuilder, oldLoop: IrLoop, newBody: IrExpression?): LoopReplacement = with(builder) {
|
||||||
// Loop is lowered into something like:
|
// Loop is lowered into something like:
|
||||||
//
|
//
|
||||||
// var inductionVar = 0
|
// var inductionVar = 0
|
||||||
// var last = array.size
|
// var last = objectVariable.size
|
||||||
// while (inductionVar < last) {
|
// while (inductionVar < last) {
|
||||||
// val loopVar = array[inductionVar]
|
// val loopVar = objectVariable.get(inductionVar)
|
||||||
// inductionVar++
|
// inductionVar++
|
||||||
// // Loop body
|
// // Loop body
|
||||||
// }
|
// }
|
||||||
@@ -278,7 +271,8 @@ internal class HeaderProcessor(
|
|||||||
if (it.valueArgumentsCount == 0
|
if (it.valueArgumentsCount == 0
|
||||||
&& function.isTopLevel
|
&& function.isTopLevel
|
||||||
&& function.getPackageFragment()?.fqName == FqName("kotlin.text")
|
&& function.getPackageFragment()?.fqName == FqName("kotlin.text")
|
||||||
&& function.name == OperatorNameConventions.ITERATOR) {
|
&& function.name == OperatorNameConventions.ITERATOR
|
||||||
|
) {
|
||||||
extensionReceiver
|
extensionReceiver
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@@ -293,62 +287,84 @@ internal class HeaderProcessor(
|
|||||||
?: return null // If the iterable is not supported.
|
?: return null // If the iterable is not supported.
|
||||||
|
|
||||||
val builder = context.createIrBuilder(scopeOwnerSymbol(), variable.startOffset, variable.endOffset)
|
val builder = context.createIrBuilder(scopeOwnerSymbol(), variable.startOffset, variable.endOffset)
|
||||||
with(builder) builder@{
|
with(builder) {
|
||||||
with(headerInfo) {
|
// For this loop:
|
||||||
// For this loop:
|
//
|
||||||
//
|
// for (i in first()..last() step step())
|
||||||
// for (i in first()..last() step step())
|
//
|
||||||
//
|
// We need to cast first(), last(). and step() to conform to the progression type so
|
||||||
// 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.
|
||||||
// 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
|
||||||
// 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(
|
||||||
val inductionVariable = scope.createTemporaryVariable(
|
headerInfo.first.castIfNecessary(
|
||||||
first.castIfNecessary(
|
headerInfo.progressionType.elementType(context.irBuiltIns),
|
||||||
progressionType.elementType(context.irBuiltIns),
|
headerInfo.progressionType.elementCastFunctionName
|
||||||
progressionType.elementCastFunctionName
|
),
|
||||||
),
|
nameHint = "inductionVariable",
|
||||||
nameHint = "inductionVariable",
|
isMutable = true
|
||||||
isMutable = true
|
)
|
||||||
)
|
|
||||||
|
|
||||||
// Due to features of PSI2IR we can obtain nullable arguments here while actually
|
// Due to features of PSI2IR we can obtain nullable arguments here while actually
|
||||||
// they are non-nullable (the frontend takes care about this). So we need to cast
|
// they are non-nullable (the frontend takes care about this). So we need to cast
|
||||||
// them to non-nullable.
|
// them to non-nullable.
|
||||||
// TODO: Confirm if casting to non-nullable is still necessary
|
// TODO: Confirm if casting to non-nullable is still necessary
|
||||||
val lastValue = scope.createTemporaryVariable(
|
val lastExpression = ensureNotNullable(
|
||||||
ensureNotNullable(
|
headerInfo.last.castIfNecessary(
|
||||||
last.castIfNecessary(
|
headerInfo.progressionType.elementType(context.irBuiltIns),
|
||||||
progressionType.elementType(context.irBuiltIns),
|
headerInfo.progressionType.elementCastFunctionName
|
||||||
progressionType.elementCastFunctionName
|
)
|
||||||
)
|
)
|
||||||
),
|
|
||||||
|
val lastVariableIfCanCacheLast = if (headerInfo.canCacheLast) {
|
||||||
|
scope.createTemporaryVariable(
|
||||||
|
lastExpression,
|
||||||
nameHint = "last"
|
nameHint = "last"
|
||||||
)
|
)
|
||||||
|
} else null
|
||||||
|
|
||||||
val stepValue = scope.createTemporaryVariable(
|
val stepVariable = scope.createTemporaryVariable(
|
||||||
ensureNotNullable(
|
ensureNotNullable(
|
||||||
step.castIfNecessary(
|
headerInfo.step.castIfNecessary(
|
||||||
progressionType.stepType(context.irBuiltIns),
|
headerInfo.progressionType.stepType(context.irBuiltIns),
|
||||||
progressionType.stepCastFunctionName
|
headerInfo.progressionType.stepCastFunctionName
|
||||||
)
|
|
||||||
),
|
|
||||||
nameHint = "step"
|
|
||||||
)
|
|
||||||
|
|
||||||
return when (headerInfo) {
|
|
||||||
is IndexedGetHeaderInfo -> IndexedGetLoopHeader(
|
|
||||||
headerInfo,
|
|
||||||
inductionVariable,
|
|
||||||
lastValue,
|
|
||||||
stepValue
|
|
||||||
)
|
)
|
||||||
is ProgressionHeaderInfo -> ProgressionLoopHeader(
|
),
|
||||||
|
nameHint = "step"
|
||||||
|
)
|
||||||
|
|
||||||
|
return when (headerInfo) {
|
||||||
|
is IndexedGetHeaderInfo -> IndexedGetLoopHeader(
|
||||||
|
headerInfo,
|
||||||
|
inductionVariable,
|
||||||
|
if (headerInfo.canCacheLast) irGet(lastVariableIfCanCacheLast!!) else lastExpression,
|
||||||
|
stepVariable,
|
||||||
|
listOfNotNull(headerInfo.objectVariable, inductionVariable, lastVariableIfCanCacheLast, stepVariable)
|
||||||
|
)
|
||||||
|
is ProgressionHeaderInfo -> {
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// In the case of a reversed range, the `inductionVariable` and `last` variables are swapped, therefore the declaration order must be
|
||||||
|
// swapped to preserve the correct evaluation order.
|
||||||
|
val declarations = headerInfo.additionalVariables + (
|
||||||
|
if (headerInfo.isReversed)
|
||||||
|
listOfNotNull(lastVariableIfCanCacheLast, inductionVariable)
|
||||||
|
else
|
||||||
|
listOfNotNull(inductionVariable, lastVariableIfCanCacheLast)
|
||||||
|
) +
|
||||||
|
stepVariable
|
||||||
|
ProgressionLoopHeader(
|
||||||
headerInfo,
|
headerInfo,
|
||||||
inductionVariable,
|
inductionVariable,
|
||||||
lastValue,
|
if (headerInfo.canCacheLast) irGet(lastVariableIfCanCacheLast!!) else lastExpression,
|
||||||
stepValue
|
stepVariable,
|
||||||
|
declarations
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-6
@@ -330,7 +330,11 @@ internal class DefaultProgressionHandler(private val context: CommonBackendConte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class IndexedGetIterationHandler(protected val context: CommonBackendContext) : ExpressionHandler {
|
internal abstract class IndexedGetIterationHandler(
|
||||||
|
protected val context: CommonBackendContext,
|
||||||
|
val canCacheLast: Boolean = true
|
||||||
|
) :
|
||||||
|
ExpressionHandler {
|
||||||
override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? =
|
override fun build(expression: IrExpression, scopeOwner: IrSymbol): HeaderInfo? =
|
||||||
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
|
with(context.createIrBuilder(scopeOwner, expression.startOffset, expression.endOffset)) {
|
||||||
// Consider the case like:
|
// Consider the case like:
|
||||||
@@ -348,19 +352,20 @@ internal abstract class IndexedGetIterationHandler(protected val context: Common
|
|||||||
//
|
//
|
||||||
// This also ensures that the semantics of re-assignment of array variables used in the loop is consistent with the semantics
|
// This also ensures that the semantics of re-assignment of array variables used in the loop is consistent with the semantics
|
||||||
// proposed in https://youtrack.jetbrains.com/issue/KT-21354.
|
// proposed in https://youtrack.jetbrains.com/issue/KT-21354.
|
||||||
val arrayReference = scope.createTemporaryVariable(
|
val objectVariable = scope.createTemporaryVariable(
|
||||||
expression, nameHint = "indexedObject"
|
expression, nameHint = "indexedObject"
|
||||||
)
|
)
|
||||||
|
|
||||||
val last = irCall(expression.type.sizePropertyGetter).apply {
|
val last = irCall(expression.type.sizePropertyGetter).apply {
|
||||||
dispatchReceiver = irGet(arrayReference)
|
dispatchReceiver = irGet(objectVariable)
|
||||||
}
|
}
|
||||||
|
|
||||||
IndexedGetHeaderInfo(
|
IndexedGetHeaderInfo(
|
||||||
first = irInt(0),
|
first = irInt(0),
|
||||||
last = last,
|
last = last,
|
||||||
step = irInt(1),
|
step = irInt(1),
|
||||||
objectVariable = arrayReference,
|
canCacheLast = canCacheLast,
|
||||||
|
objectVariable = objectVariable,
|
||||||
expressionHandler = this@IndexedGetIterationHandler
|
expressionHandler = this@IndexedGetIterationHandler
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -381,8 +386,24 @@ internal class ArrayIterationHandler(context: CommonBackendContext) : IndexedGet
|
|||||||
get() = getClass()!!.functions.first { it.name.asString() == "get" }
|
get() = getClass()!!.functions.first { it.name.asString() == "get" }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Builds a [HeaderInfo] for iteration over characters in a `CharacterSequence`. */
|
/** Builds a [HeaderInfo] for iteration over characters in a [String]. */
|
||||||
internal class CharSequenceIterationHandler(context: CommonBackendContext) : IndexedGetIterationHandler(context) {
|
internal class StringIterationHandler(context: CommonBackendContext) : IndexedGetIterationHandler(context) {
|
||||||
|
override fun match(expression: IrExpression) = expression.type.isString()
|
||||||
|
|
||||||
|
override val IrType.sizePropertyGetter
|
||||||
|
get() = getClass()!!.properties.first { it.name.asString() == "length" }.getter!!
|
||||||
|
|
||||||
|
override val IrType.getFunction
|
||||||
|
get() = getClass()!!.functions.first { it.name.asString() == "get" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a [HeaderInfo] for iteration over characters in a [CharSequence].
|
||||||
|
*
|
||||||
|
* Note: The value for "last" can NOT be cached (i.e., stored in a variable) because the size/length can change within the loop. This means
|
||||||
|
* that "last" is re-evaluated with each iteration of the loop.
|
||||||
|
*/
|
||||||
|
internal class CharSequenceIterationHandler(context: CommonBackendContext) : IndexedGetIterationHandler(context, canCacheLast = false) {
|
||||||
override fun match(expression: IrExpression) = expression.type.isSubtypeOfClass(context.ir.symbols.charSequence)
|
override fun match(expression: IrExpression) = expression.type.isSubtypeOfClass(context.ir.symbols.charSequence)
|
||||||
|
|
||||||
// The lowering operates on subtypes of CharSequence. Therefore, the IrType could be
|
// The lowering operates on subtypes of CharSequence. Therefore, the IrType could be
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// KJS_WITH_FULL_RUNTIME
|
||||||
|
// WITH_RUNTIME
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val sb = StringBuilder("1234")
|
||||||
|
val result = StringBuilder()
|
||||||
|
for (c in sb) {
|
||||||
|
sb.clear()
|
||||||
|
result.append(c)
|
||||||
|
}
|
||||||
|
assertEquals("", sb.toString())
|
||||||
|
assertEquals("1", result.toString())
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
// KJS_WITH_FULL_RUNTIME
|
||||||
|
// WITH_RUNTIME
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val sb = StringBuilder("1234")
|
||||||
|
val result = StringBuilder()
|
||||||
|
var ctr = 0
|
||||||
|
for (c in sb) {
|
||||||
|
if (ctr % 2 == 0)
|
||||||
|
sb.append('x')
|
||||||
|
ctr++
|
||||||
|
result.append(c)
|
||||||
|
}
|
||||||
|
assertEquals("1234xxxx", sb.toString())
|
||||||
|
assertEquals("1234xxxx", result.toString())
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+10
@@ -18819,6 +18819,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
runTest("compiler/testData/codegen/box/ranges/forByteProgressionWithIntIncrement.kt");
|
runTest("compiler/testData/codegen/box/ranges/forByteProgressionWithIntIncrement.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInCharSequenceLengthDecreasedInLoopBody.kt")
|
||||||
|
public void testForInCharSequenceLengthDecreasedInLoopBody() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/ranges/forInCharSequenceLengthDecreasedInLoopBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInCharSequenceLengthIncreasedInLoopBody.kt")
|
||||||
|
public void testForInCharSequenceLengthIncreasedInLoopBody() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/ranges/forInCharSequenceLengthIncreasedInLoopBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||||
|
|||||||
+10
@@ -18819,6 +18819,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
runTest("compiler/testData/codegen/box/ranges/forByteProgressionWithIntIncrement.kt");
|
runTest("compiler/testData/codegen/box/ranges/forByteProgressionWithIntIncrement.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInCharSequenceLengthDecreasedInLoopBody.kt")
|
||||||
|
public void testForInCharSequenceLengthDecreasedInLoopBody() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/ranges/forInCharSequenceLengthDecreasedInLoopBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInCharSequenceLengthIncreasedInLoopBody.kt")
|
||||||
|
public void testForInCharSequenceLengthIncreasedInLoopBody() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/ranges/forInCharSequenceLengthIncreasedInLoopBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||||
|
|||||||
+10
@@ -17704,6 +17704,16 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
runTest("compiler/testData/codegen/box/ranges/forByteProgressionWithIntIncrement.kt");
|
runTest("compiler/testData/codegen/box/ranges/forByteProgressionWithIntIncrement.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInCharSequenceLengthDecreasedInLoopBody.kt")
|
||||||
|
public void testForInCharSequenceLengthDecreasedInLoopBody() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/ranges/forInCharSequenceLengthDecreasedInLoopBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInCharSequenceLengthIncreasedInLoopBody.kt")
|
||||||
|
public void testForInCharSequenceLengthIncreasedInLoopBody() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/ranges/forInCharSequenceLengthIncreasedInLoopBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||||
|
|||||||
Generated
+10
@@ -14639,6 +14639,16 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
|
|||||||
runTest("compiler/testData/codegen/box/ranges/forByteProgressionWithIntIncrement.kt");
|
runTest("compiler/testData/codegen/box/ranges/forByteProgressionWithIntIncrement.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInCharSequenceLengthDecreasedInLoopBody.kt")
|
||||||
|
public void testForInCharSequenceLengthDecreasedInLoopBody() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/ranges/forInCharSequenceLengthDecreasedInLoopBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInCharSequenceLengthIncreasedInLoopBody.kt")
|
||||||
|
public void testForInCharSequenceLengthIncreasedInLoopBody() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/ranges/forInCharSequenceLengthIncreasedInLoopBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||||
|
|||||||
+10
@@ -15794,6 +15794,16 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
|||||||
runTest("compiler/testData/codegen/box/ranges/forByteProgressionWithIntIncrement.kt");
|
runTest("compiler/testData/codegen/box/ranges/forByteProgressionWithIntIncrement.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInCharSequenceLengthDecreasedInLoopBody.kt")
|
||||||
|
public void testForInCharSequenceLengthDecreasedInLoopBody() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/ranges/forInCharSequenceLengthDecreasedInLoopBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("forInCharSequenceLengthIncreasedInLoopBody.kt")
|
||||||
|
public void testForInCharSequenceLengthIncreasedInLoopBody() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/box/ranges/forInCharSequenceLengthIncreasedInLoopBody.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
@TestMetadata("forInRangeLiteralWithMixedTypeBounds.kt")
|
||||||
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
public void testForInRangeLiteralWithMixedTypeBounds() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
runTest("compiler/testData/codegen/box/ranges/forInRangeLiteralWithMixedTypeBounds.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user