Initial support for indexed transformations

This commit is contained in:
Valentin Kipyatkov
2016-04-20 20:00:41 +03:00
parent 955e1166fd
commit 14e87b1f2c
26 changed files with 311 additions and 46 deletions
@@ -35,7 +35,7 @@ class LoopToCallChainIntention : SelfTargetingRangeIntention<KtForExpression>(
) {
override fun applicabilityRange(element: KtForExpression): TextRange? {
val match = match(element)
text = if (match != null) "Replace with '${match.buildPresentation()}'" else defaultText
text = if (match != null) "Replace with '${match.transformationMatch.buildPresentation()}'" else defaultText
return if (match != null) element.forKeyword.textRange else null
}
@@ -86,6 +86,9 @@ data class MatchingState(
val innerLoop: KtForExpression,
val statements: Collection<KtExpression>,
val inputVariable: KtCallableDeclaration,
/**
* Matchers can assume that indexVariable is null if it's not used in the rest of the loop
*/
val indexVariable: KtCallableDeclaration?
)
@@ -113,6 +116,8 @@ class SequenceTransformationMatch(
*/
interface ResultTransformationMatcher {
fun match(state: MatchingState): ResultTransformationMatch?
val indexVariableUsePossible: Boolean
}
class ResultTransformationMatch(
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FlatMapTran
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.MapTransformation
import org.jetbrains.kotlin.idea.util.CommentSaver
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
@@ -54,14 +55,21 @@ object MatcherRegistrar {
)
}
fun match(loop: KtForExpression): ResultTransformationMatch? {
data class MatchResult(
val sequenceExpression: KtExpression,
val transformationMatch: ResultTransformationMatch
)
fun match(loop: KtForExpression): MatchResult? {
val (inputVariable, indexVariable, sequenceExpression) = extractLoopData(loop) ?: return null
val sequenceTransformations = ArrayList<SequenceTransformation>()
var state = MatchingState(
outerLoop = loop,
innerLoop = loop,
statements = listOf(loop.body ?: return null),
inputVariable = loop.loopParameter ?: return null,
indexVariable = null
inputVariable = inputVariable,
indexVariable = indexVariable
)
MatchLoop@
@@ -73,7 +81,14 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
val inputVariableUsed = state.inputVariable.hasUsages(state.statements)
// drop index variable if it's not used anymore
if (state.indexVariable != null && !state.indexVariable!!.hasUsages(state.statements)) {
state = state.copy(indexVariable = null)
}
for (matcher in MatcherRegistrar.resultMatchers) {
if (state.indexVariable != null && !matcher.indexVariableUsePossible) continue
val match = matcher.match(state)
if (match != null) {
if (!inputVariableUsed
@@ -85,6 +100,7 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
sequenceTransformations.addAll(match.sequenceTransformations)
return ResultTransformationMatch(match.resultTransformation, sequenceTransformations)
.let { mergeTransformations(it) }
.let { MatchResult(sequenceExpression, it) }
.check { checkSmartCastsPreserved(loop, it) }
}
}
@@ -96,10 +112,16 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
return null
}
val newState = match.newState
var newState = match.newState
// check that old input variable is not needed anymore
if (state.inputVariable != newState.inputVariable && state.inputVariable.hasUsages(newState.statements)) return null
if (state.indexVariable != null && match.transformations.any { it.affectsIndex }) {
// index variable is still needed but index in the new sequence is different
if (state.indexVariable!!.hasUsages(newState.statements)) return null
newState = newState.copy(indexVariable = null)
}
sequenceTransformations.addAll(match.transformations)
state = newState
continue@MatchLoop
@@ -111,19 +133,44 @@ fun match(loop: KtForExpression): ResultTransformationMatch? {
}
//TODO: offer to use of .asSequence() as an option
fun convertLoop(loop: KtForExpression, matchResult: ResultTransformationMatch): KtExpression {
val commentSaver = CommentSaver(matchResult.resultTransformation.commentSavingRange)
fun convertLoop(loop: KtForExpression, matchResult: MatchResult): KtExpression {
val resultTransformation = matchResult.transformationMatch.resultTransformation
val commentSaver = CommentSaver(resultTransformation.commentSavingRange)
val callChain = matchResult.generateCallChain(loop)
val result = matchResult.resultTransformation.convertLoop(callChain)
val result = resultTransformation.convertLoop(callChain)
commentSaver.restore(matchResult.resultTransformation.commentRestoringRange(result))
commentSaver.restore(resultTransformation.commentRestoringRange(result))
return result
}
private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: ResultTransformationMatch): Boolean {
data class LoopData(
val inputVariable: KtCallableDeclaration,
val indexVariable: KtCallableDeclaration?,
val sequenceExpression: KtExpression)
private fun extractLoopData(loop: KtForExpression): LoopData? {
val loopRange = loop.loopRange ?: return null
//TODO: recognize other patterns for loop with index
val destructuringParameter = loop.destructuringParameter
if (destructuringParameter != null && destructuringParameter.entries.size == 2) {
val qualifiedExpression = loopRange as? KtDotQualifiedExpression
if (qualifiedExpression != null) {
val call = qualifiedExpression.selectorExpression as? KtCallExpression
//TODO: check that it's the correct "withIndex"
if (call != null && call.calleeExpression.isSimpleName(Name.identifier("withIndex")) && call.valueArguments.isEmpty()) {
return LoopData(destructuringParameter.entries[1], destructuringParameter.entries[0], qualifiedExpression.receiverExpression)
}
}
}
return LoopData(loop.loopParameter ?: return null, null, loopRange)
}
private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: MatchResult): Boolean {
val bindingContext = loop.analyze(BodyResolveMode.FULL)
val SMARTCAST_KEY = Key<KotlinType>("SMARTCAST_KEY")
@@ -174,9 +221,9 @@ private fun checkSmartCastsPreserved(loop: KtForExpression, matchResult: ResultT
}
}
private fun ResultTransformationMatch.generateCallChain(loop: KtForExpression): KtExpression {
var sequenceTransformations = sequenceTransformations
var resultTransformation = resultTransformation
private fun MatchResult.generateCallChain(loop: KtForExpression): KtExpression {
var sequenceTransformations = transformationMatch.sequenceTransformations
var resultTransformation = transformationMatch.resultTransformation
while(true) {
val last = sequenceTransformations.lastOrNull() ?: break
resultTransformation = resultTransformation.mergeWithPrevious(last) ?: break
@@ -186,7 +233,7 @@ private fun ResultTransformationMatch.generateCallChain(loop: KtForExpression):
val chainCallCount = sequenceTransformations.sumBy { it.chainCallCount } + resultTransformation.chainCallCount
val lineBreak = if (chainCallCount > 1) "\n" else ""
var callChain = loop.loopRange!!
var callChain = sequenceExpression
val psiFactory = KtPsiFactory(loop)
val chainedCallGenerator = object : ChainedCallGenerator {
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FlatMapTransformation
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.MapIndexedTransformation
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.MapTransformation
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.name.FqName
@@ -33,14 +34,13 @@ import org.jetbrains.kotlin.renderer.render
class AddToCollectionTransformation(
loop: KtForExpression,
private val inputVariable: KtCallableDeclaration,
private val targetCollection: KtExpression
) : ReplaceLoopResultTransformation(loop) {
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
return when (previousTransformation) {
is FilterTransformation -> {
FilterToTransformation.create(loop, inputVariable, targetCollection, previousTransformation.effectiveCondition()) //TODO: use filterNotTo?
FilterToTransformation.create(loop, previousTransformation.inputVariable, targetCollection, previousTransformation.effectiveCondition()) //TODO: use filterNotTo?
}
is MapTransformation -> {
@@ -80,10 +80,10 @@ class AddToCollectionTransformation(
* }
*/
object Matcher : ResultTransformationMatcher {
override fun match(state: MatchingState): ResultTransformationMatch? {
//TODO: pass indexVariable as null if not used
if (state.indexVariable != null) return null
override val indexVariableUsePossible: Boolean
get() = true
override fun match(state: MatchingState): ResultTransformationMatch? {
val statement = state.statements.singleOrNull() ?: return null
//TODO: it can be implicit 'this' too
val qualifiedExpression = statement as? KtDotQualifiedExpression ?: return null
@@ -95,16 +95,22 @@ class AddToCollectionTransformation(
val argument = callExpression.valueArguments.singleOrNull() ?: return null
val argumentValue = argument.getArgumentExpression() ?: return null
matchWithCollectionInitializationReplacement(state, targetCollection, argumentValue)
?.let { return it }
if (state.indexVariable == null) {
matchWithCollectionInitializationReplacement(state, targetCollection, argumentValue)
?.let { return it }
}
val transformation = if (argumentValue.isVariableReference(state.inputVariable)) {
AddToCollectionTransformation(state.outerLoop, state.inputVariable, targetCollection)
if (state.indexVariable == null && argumentValue.isVariableReference(state.inputVariable)) {
return ResultTransformationMatch(AddToCollectionTransformation(state.outerLoop, targetCollection))
}
else if (state.indexVariable != null) {
val mapIndexedTransformation = MapIndexedTransformation(state.outerLoop, state.inputVariable, state.indexVariable, argumentValue)
val addToCollectionTransformation = AddToCollectionTransformation(state.outerLoop, targetCollection)
return ResultTransformationMatch(addToCollectionTransformation, mapIndexedTransformation)
}
else {
MapToTransformation.create(state.outerLoop, state.inputVariable, targetCollection, argumentValue)
return ResultTransformationMatch(MapToTransformation.create(state.outerLoop, state.inputVariable, targetCollection, argumentValue))
}
return ResultTransformationMatch(transformation)
}
private fun matchWithCollectionInitializationReplacement(
@@ -71,6 +71,9 @@ class CountTransformation(
* }
*/
object Matcher : ResultTransformationMatcher {
override val indexVariableUsePossible: Boolean
get() = false
override fun match(state: MatchingState): ResultTransformationMatch? {
val statement = state.statements.singleOrNull() as? KtUnaryExpression ?: return null
if (statement.operationToken != KtTokens.PLUSPLUS) return null
@@ -71,10 +71,10 @@ class FindAndAssignTransformation(
* }
*/
object Matcher : ResultTransformationMatcher {
override fun match(state: MatchingState): ResultTransformationMatch? {
//TODO: pass indexVariable as null if not used
if (state.indexVariable != null) return null
override val indexVariableUsePossible: Boolean
get() = false
override fun match(state: MatchingState): ResultTransformationMatch? {
when (state.statements.size) {
1 -> {}
@@ -70,10 +70,10 @@ class FindAndReturnTransformation(
* return ...
*/
object Matcher : ResultTransformationMatcher {
override fun match(state: MatchingState): ResultTransformationMatch? {
//TODO: pass indexVariable as null if not used
if (state.indexVariable != null) return null
override val indexVariableUsePossible: Boolean
get() = false
override fun match(state: MatchingState): ResultTransformationMatch? {
val returnInLoop = state.statements.singleOrNull() as? KtReturnExpression ?: return null
val returnAfterLoop = state.outerLoop.nextStatement() as? KtReturnExpression ?: return null
if (returnInLoop.getLabelName() != returnAfterLoop.getLabelName()) return null
@@ -69,22 +69,20 @@ class FilterTransformation(
*/
object Matcher : SequenceTransformationMatcher {
override fun match(state: MatchingState): SequenceTransformationMatch? {
if (state.indexVariable != null) return null //TODO?
val ifStatement = state.statements.firstOrNull() as? KtIfExpression ?: return null
if (ifStatement.`else` != null) return null
val condition = ifStatement.condition ?: return null
val then = ifStatement.then ?: return null
if (state.statements.size == 1) {
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, condition, isInverse = false)
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, state.indexVariable, condition, isInverse = false)
val newState = state.copy(statements = listOf(then))
return SequenceTransformationMatch(transformation, newState)
}
else {
val continueExpression = then.blockExpressionsOrSingle().singleOrNull() as? KtContinueExpression ?: return null
if (!continueExpression.isBreakOrContinueOfLoop(state.innerLoop)) return null
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, condition, isInverse = true)
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, state.indexVariable, condition, isInverse = true)
val newState = state.copy(statements = state.statements.drop(1))
return SequenceTransformationMatch(transformation, newState)
}
@@ -94,11 +92,17 @@ class FilterTransformation(
private fun createFilterTransformation(
loop: KtForExpression,
inputVariable: KtCallableDeclaration,
indexVariable: KtCallableDeclaration?,
condition: KtExpression,
isInverse: Boolean): SequenceTransformation {
isInverse: Boolean
): SequenceTransformation {
val effectiveCondition = if (isInverse) condition.negate() else condition
if (indexVariable != null && indexVariable.hasUsages(condition)) {
return FilterIndexedTransformation(loop, inputVariable, indexVariable, effectiveCondition)
}
if (effectiveCondition is KtIsExpression
&& !effectiveCondition.isNegated
&& effectiveCondition.leftHandSide.isSimpleName(inputVariable.nameAsSafeName) // we cannot use isVariableReference here because expression can be non-physical
@@ -150,3 +154,25 @@ class FilterNotNullTransformation(override val loop: KtForExpression) : Sequence
return chainedCallGenerator.generate("filterNotNull()")
}
}
class FilterIndexedTransformation(
override val loop: KtForExpression,
val inputVariable: KtCallableDeclaration,
val indexVariable: KtCallableDeclaration,
val condition: KtExpression
) : SequenceTransformation {
//TODO: how to handle multiple if's using index?
override val affectsIndex: Boolean
get() = true
override val presentation: String
get() = "filterIndexed{}"
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
val lambda = generateLambda(condition, indexVariable, inputVariable)
return chainedCallGenerator.generate("filterIndexed $0:'{}'", lambda)
}
}
@@ -20,9 +20,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class FlatMapTransformation(
@@ -53,8 +51,6 @@ class FlatMapTransformation(
*/
object Matcher : SequenceTransformationMatcher {
override fun match(state: MatchingState): SequenceTransformationMatch? {
if (state.indexVariable != null) return null
val nestedLoop = state.statements.singleOrNull() as? KtForExpression ?: return null
val transform = nestedLoop.loopRange ?: return null
@@ -65,8 +61,21 @@ class FlatMapTransformation(
if (iterableType.checkIsSuperTypeOf(nestedSequenceType) == null) return null
val nestedLoopBody = nestedLoop.body ?: return null
val newWorkingVariable = nestedLoop.loopParameter ?: return null
if (state.indexVariable != null && state.indexVariable.hasUsages(transform)) {
// if nested loop range uses index, convert to "mapIndexed {...}.flatMap { it }"
val mapIndexedTransformation = MapIndexedTransformation(state.outerLoop, state.inputVariable, state.indexVariable, transform)
val inputVarExpression = KtPsiFactory(nestedLoop).createExpressionByPattern("$0", state.inputVariable.nameAsSafeName)
val flatMapTransformation = FlatMapTransformation(state.outerLoop, state.inputVariable, inputVarExpression)
val newState = state.copy(
innerLoop = nestedLoop,
statements = listOf(nestedLoopBody),
inputVariable = newWorkingVariable
)
return SequenceTransformationMatch(listOf(mapIndexedTransformation, flatMapTransformation), newState)
}
val transformation = FlatMapTransformation(state.outerLoop, state.inputVariable, transform)
val newState = state.copy(
innerLoop = nestedLoop,
@@ -48,16 +48,36 @@ class MapTransformation(
*/
object Matcher : SequenceTransformationMatcher {
override fun match(state: MatchingState): SequenceTransformationMatch? {
if (state.indexVariable != null) return null //TODO?
val declaration = state.statements.firstOrNull() as? KtProperty ?: return null //TODO: support multi-variables
val initializer = declaration.initializer ?: return null
if (declaration.hasWriteUsages()) return null
val restStatements = state.statements.drop(1)
val transformation = MapTransformation(state.outerLoop, state.inputVariable, initializer)
val transformation = if (state.indexVariable != null && state.indexVariable.hasUsages(initializer))
MapIndexedTransformation(state.outerLoop, state.inputVariable, state.indexVariable, initializer)
else
MapTransformation(state.outerLoop, state.inputVariable, initializer)
val newState = state.copy(statements = restStatements, inputVariable = declaration)
return SequenceTransformationMatch(transformation, newState)
}
}
}
}
class MapIndexedTransformation(
override val loop: KtForExpression,
val inputVariable: KtCallableDeclaration,
val indexVariable: KtCallableDeclaration,
val mapping: KtExpression
) : SequenceTransformation {
override val affectsIndex: Boolean
get() = false
override val presentation: String
get() = "mapIndexed{}"
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
val lambda = generateLambda(mapping, indexVariable, inputVariable)
return chainedCallGenerator.generate("mapIndexed $0:'{}'", lambda)
}
}
@@ -81,6 +81,25 @@ fun generateLambda(inputVariable: KtCallableDeclaration, expression: KtExpressio
return psiFactory.createExpressionByPattern("{ $0 }", lambdaExpression.bodyExpression!!) as KtLambdaExpression
}
fun generateLambda(expression: KtExpression, vararg inputVariables: KtCallableDeclaration): KtLambdaExpression {
return KtPsiFactory(expression).buildExpression {
appendFixedText("{")
for ((index, variable) in inputVariables.withIndex()) {
if (index > 0) {
appendFixedText(",")
}
appendName(variable.nameAsSafeName)
}
appendFixedText("->")
appendExpression(expression)
appendFixedText("}")
} as KtLambdaExpression
}
fun KtExpression?.isTrueConstant()
= this != null && node?.elementType == KtNodeTypes.BOOLEAN_CONSTANT && text == "true"
@@ -100,12 +119,14 @@ fun KtCallableDeclaration.hasUsages(inElement: KtElement): Boolean {
}
fun KtCallableDeclaration.hasUsages(inElements: Collection<KtElement>): Boolean {
assert(this.isPhysical)
// TODO: it's a temporary workaround about strange dead-lock when running inspections
return inElements.any { ReferencesSearch.search(this, LocalSearchScope(it)).any() }
// return ReferencesSearch.search(this, LocalSearchScope(inElements.toTypedArray())).any()
}
fun KtProperty.hasWriteUsages(): Boolean {
assert(this.isPhysical)
if (!isVar) return false
return ReferencesSearch.search(this, useScope).any {
(it as? KtSimpleNameReference)?.element?.readWriteAccess(useResolveForReadWrite = true)?.isWrite == true
@@ -0,0 +1,11 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterIndexed{}.any()'"
fun foo(list: List<String>) {
var found = false
<caret>for ((index, s) in list.withIndex()) {
if (s.length > index) {
found = true
break
}
}
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterIndexed{}.any()'"
fun foo(list: List<String>) {
val <caret>found = list
.filterIndexed { index, s -> s.length > index }
.any()
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterIndexed{}.firstOrNull()'"
fun foo(list: List<String>): String? {
<caret>for ((index, s) in list.withIndex()) {
if (s.length > index) {
return s
}
}
return null
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterIndexed{}.firstOrNull()'"
fun foo(list: List<String>): String? {
<caret>return list
.filterIndexed { index, s -> s.length > index }
.firstOrNull()
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'mapIndexed{}.flatMap{}.firstOrNull{}'"
fun foo(list: List<String>): String? {
<caret>for ((index, s) in list.withIndex()) {
for (line in s.lines().take(index)) {
if (line.isNotBlank()) return line
}
}
return null
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'mapIndexed{}.flatMap{}.firstOrNull{}'"
fun foo(list: List<String>): String? {
<caret>return list
.mapIndexed { index, s -> s.lines().take(index) }
.flatMap { it }
.firstOrNull { it.isNotBlank() }
}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'map{}.filterIndexed{}.firstOrNull()'"
fun foo(list: List<String>): Int? {
<caret>for ((index, s) in list.withIndex()) {
val l = s.length
if (l > index) {
return l
}
}
return null
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'map{}.filterIndexed{}.firstOrNull()'"
fun foo(list: List<String>): Int? {
<caret>return list
.map { it.length }
.filterIndexed { index, l -> l > index }
.firstOrNull()
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'mapIndexed{}.firstOrNull{}'"
fun foo(list: List<String>): Int? {
<caret>for ((index, s) in list.withIndex()) {
val x = s.length * index
if (x > 0) return x
}
return null
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'mapIndexed{}.firstOrNull{}'"
fun foo(list: List<String>): Int? {
<caret>return list
.mapIndexed { index, s -> s.length * index }
.firstOrNull { it > 0 }
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'mapIndexed{}.mapIndexed{}.firstOrNull{}'"
fun foo(list: List<String>): Int? {
<caret>for ((index, s) in list.withIndex()) {
val x = s.length * index
val y = x + index
if (y > 0) return y
}
return null
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'mapIndexed{}.mapIndexed{}.firstOrNull{}'"
fun foo(list: List<String>): Int? {
<caret>return list
.mapIndexed { index, s -> s.length * index }
.mapIndexed { index, x -> x + index }
.firstOrNull { it > 0 }
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with '+= mapIndexed{}'"
fun foo(list: List<String>, target: MutableList<Int>) {
<caret>for ((index, s) in list.withIndex()) {
target.add(s.hashCode() * index)
}
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with '+= mapIndexed{}'"
fun foo(list: List<String>, target: MutableList<Int>) {
<caret>target += list.mapIndexed { index, s -> s.hashCode() * index }
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
fun foo(list: List<String>): Int? {
<caret>for ((index, s) in list.withIndex()) {
if (s.isBlank()) continue
val x = s.length * index
if (x > 1000) return x
}
return null
}