Merging subsequent filtering checks even when index variable is used
This commit is contained in:
@@ -80,10 +80,7 @@ fun match(loop: KtForExpression): MatchResult? {
|
||||
|
||||
MatchLoop@
|
||||
while (true) {
|
||||
val block = state.statements.singleOrNull() as? KtBlockExpression
|
||||
if (block != null) {
|
||||
state = state.copy(statements = block.statements)
|
||||
}
|
||||
state = state.unwrapBlock()
|
||||
|
||||
val inputVariableUsed = state.inputVariable.hasUsages(state.statements)
|
||||
|
||||
|
||||
+24
-49
@@ -22,7 +22,11 @@ import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.*
|
||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterNotNullTransformation
|
||||
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.MapTransformation
|
||||
import org.jetbrains.kotlin.idea.intentions.negate
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
@@ -37,11 +41,7 @@ class AddToCollectionTransformation(
|
||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
||||
return when (previousTransformation) {
|
||||
is FilterTransformation -> {
|
||||
FilterToTransformation.create(loop, previousTransformation.inputVariable, targetCollection, previousTransformation.condition, previousTransformation.isInverse)
|
||||
}
|
||||
|
||||
is FilterIndexedTransformation -> {
|
||||
FilterIndexedToTransformation.create(loop, previousTransformation.inputVariable, previousTransformation.indexVariable, targetCollection, previousTransformation.condition)
|
||||
FilterToTransformation.create(loop, previousTransformation.inputVariable, previousTransformation.indexVariable, targetCollection, previousTransformation.condition, previousTransformation.isInverse)
|
||||
}
|
||||
|
||||
is FilterNotNullTransformation -> {
|
||||
@@ -189,18 +189,28 @@ class AddToCollectionTransformation(
|
||||
class FilterToTransformation private constructor(
|
||||
loop: KtForExpression,
|
||||
private val inputVariable: KtCallableDeclaration,
|
||||
private val indexVariable: KtCallableDeclaration?,
|
||||
private val targetCollection: KtExpression,
|
||||
private val filter: KtExpression,
|
||||
isInverse: Boolean
|
||||
private val condition: KtExpression,
|
||||
private val isInverse: Boolean
|
||||
) : ReplaceLoopResultTransformation(loop) {
|
||||
|
||||
private val functionName = if (isInverse) "filterNotTo" else "filterTo"
|
||||
private fun effectiveCondition() = if (isInverse) condition.negate() else condition
|
||||
|
||||
private val functionName = when {
|
||||
indexVariable != null -> "filterIndexedTo"
|
||||
isInverse -> "filterNotTo"
|
||||
else -> "filterTo"
|
||||
}
|
||||
|
||||
override val presentation: String
|
||||
get() = "$functionName(){}"
|
||||
|
||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
val lambda = generateLambda(inputVariable, filter)
|
||||
val lambda = if (indexVariable != null)
|
||||
generateLambda(effectiveCondition(), indexVariable, inputVariable)
|
||||
else
|
||||
generateLambda(inputVariable, condition)
|
||||
return chainedCallGenerator.generate("$functionName($0) $1:'{}'", targetCollection, lambda)
|
||||
}
|
||||
|
||||
@@ -208,53 +218,18 @@ class FilterToTransformation private constructor(
|
||||
fun create(
|
||||
loop: KtForExpression,
|
||||
inputVariable: KtCallableDeclaration,
|
||||
indexVariable: KtCallableDeclaration?,
|
||||
targetCollection: KtExpression,
|
||||
filter: KtExpression,
|
||||
condition: KtExpression,
|
||||
isInverse: Boolean
|
||||
): ResultTransformation {
|
||||
val initialization = targetCollection.detectInitializationBeforeLoop(loop, checkNoOtherUsagesInLoop = true)
|
||||
if (initialization != null && initialization.initializer.hasNoSideEffect()) {
|
||||
val transformation = FilterToTransformation(loop, inputVariable, initialization.initializer, filter, isInverse)
|
||||
val transformation = FilterToTransformation(loop, inputVariable, indexVariable, initialization.initializer, condition, isInverse)
|
||||
return AssignToVariableResultTransformation.createDelegated(transformation, initialization)
|
||||
}
|
||||
else {
|
||||
return FilterToTransformation(loop, inputVariable, targetCollection, filter, isInverse)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FilterIndexedToTransformation private constructor(
|
||||
loop: KtForExpression,
|
||||
private val inputVariable: KtCallableDeclaration,
|
||||
private val indexVariable: KtCallableDeclaration,
|
||||
private val targetCollection: KtExpression,
|
||||
private val filter: KtExpression
|
||||
) : ReplaceLoopResultTransformation(loop) {
|
||||
|
||||
override val presentation: String
|
||||
get() = "filterIndexedTo(){}"
|
||||
|
||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
val lambda = generateLambda(filter, indexVariable, inputVariable)
|
||||
return chainedCallGenerator.generate("filterIndexedTo($0) $1:'{}'", targetCollection, lambda)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(
|
||||
loop: KtForExpression,
|
||||
inputVariable: KtCallableDeclaration,
|
||||
indexVariable: KtCallableDeclaration,
|
||||
targetCollection: KtExpression,
|
||||
filter: KtExpression
|
||||
): ResultTransformation {
|
||||
val initialization = targetCollection.detectInitializationBeforeLoop(loop, checkNoOtherUsagesInLoop = true)
|
||||
if (initialization != null && initialization.initializer.hasNoSideEffect()) {
|
||||
val transformation = FilterIndexedToTransformation(loop, inputVariable, indexVariable, initialization.initializer, filter)
|
||||
return AssignToVariableResultTransformation.createDelegated(transformation, initialization)
|
||||
}
|
||||
else {
|
||||
return FilterIndexedToTransformation(loop, inputVariable, indexVariable, targetCollection, filter)
|
||||
return FilterToTransformation(loop, inputVariable, indexVariable, targetCollection, condition, isInverse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ class CountTransformation(
|
||||
|
||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
||||
if (previousTransformation !is FilterTransformation) return null
|
||||
if (previousTransformation.indexVariable != null) return null
|
||||
assert(filter == null) { "Should not happen because no 2 consecutive FilterTransformation's possible"}
|
||||
return CountTransformation(loop, previousTransformation.inputVariable, initialization, previousTransformation.effectiveCondition())
|
||||
}
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ class FindAndAssignTransformation(
|
||||
|
||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
||||
if (previousTransformation !is FilterTransformation) return null
|
||||
if (previousTransformation.indexVariable != null) return null
|
||||
assert(filter == null) { "Should not happen because no 2 consecutive FilterTransformation's possible"}
|
||||
return FindAndAssignTransformation(loop, generator, initialization, previousTransformation.effectiveCondition())
|
||||
}
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ class FindAndReturnTransformation(
|
||||
|
||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
||||
if (previousTransformation !is FilterTransformation) return null
|
||||
if (previousTransformation.indexVariable != null) return null
|
||||
assert(filter == null) { "Should not happen because no 2 consecutive FilterTransformation's possible"}
|
||||
return FindAndReturnTransformation(loop, generator, endReturn, previousTransformation.effectiveCondition())
|
||||
}
|
||||
|
||||
+25
-42
@@ -26,22 +26,19 @@ import org.jetbrains.kotlin.psi.psiUtil.blockExpressionsOrSingle
|
||||
class FilterTransformation(
|
||||
override val loop: KtForExpression,
|
||||
val inputVariable: KtCallableDeclaration,
|
||||
val indexVariable: KtCallableDeclaration?,
|
||||
val condition: KtExpression,
|
||||
val isInverse: Boolean
|
||||
) : SequenceTransformation {
|
||||
|
||||
fun effectiveCondition() = if (isInverse) condition.negate() else condition
|
||||
|
||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): SequenceTransformation? {
|
||||
if (previousTransformation !is FilterTransformation) return null
|
||||
assert(previousTransformation.inputVariable == inputVariable)
|
||||
val mergedCondition = KtPsiFactory(condition).createExpressionByPattern(
|
||||
"$0 && $1", previousTransformation.effectiveCondition(), effectiveCondition())
|
||||
return FilterTransformation(loop, inputVariable, mergedCondition, isInverse = false) //TODO: build filterNot in some cases?
|
||||
private val functionName = when {
|
||||
indexVariable != null -> "filterIndexed"
|
||||
isInverse -> "filterNot"
|
||||
else -> "filter"
|
||||
}
|
||||
|
||||
private val functionName = if (isInverse) "filterNot" else "filter"
|
||||
|
||||
override val affectsIndex: Boolean
|
||||
get() = true
|
||||
|
||||
@@ -49,7 +46,10 @@ class FilterTransformation(
|
||||
get() = "$functionName{}"
|
||||
|
||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||
val lambda = generateLambda(inputVariable, condition)
|
||||
val lambda = if (indexVariable != null)
|
||||
generateLambda(effectiveCondition(), indexVariable, inputVariable)
|
||||
else
|
||||
generateLambda(inputVariable, condition)
|
||||
return chainedCallGenerator.generate("$0$1:'{}'", functionName, lambda)
|
||||
}
|
||||
|
||||
@@ -76,22 +76,29 @@ class FilterTransformation(
|
||||
*/
|
||||
object Matcher : SequenceTransformationMatcher {
|
||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
||||
var (transformation, currentState) = matchOneTransformation(state, takeWhileAllowed = true) ?: return null
|
||||
// we merge filter transformations here instead of FilterTransformation.mergeWithPrevious() because of filterIndexed that won't merge otherwise
|
||||
|
||||
/*
|
||||
if (transformation is FilterTransformation || transformation is FilterIndexedTransformation) {
|
||||
var (transformation, currentState) = matchOneTransformation(state) ?: return null
|
||||
|
||||
if (transformation is FilterTransformation) {
|
||||
while (true) {
|
||||
val (nextTransformation, nextState) = matchOneTransformation(currentState, takeWhileAllowed = false) ?: break
|
||||
if (nextTransformation !is FilterTransformation && nextTransformation !is FilterIndexedTransformation) break
|
||||
currentState = currentState.unwrapBlock()
|
||||
|
||||
val (nextTransformation, nextState) = matchOneTransformation(currentState) ?: break
|
||||
if (nextTransformation !is FilterTransformation) break
|
||||
|
||||
val indexVariable = transformation.indexVariable ?: nextTransformation.indexVariable
|
||||
val mergedCondition = KtPsiFactory(state.outerLoop).createExpressionByPattern(
|
||||
"$0 && $1", transformation.effectiveCondition(), nextTransformation.effectiveCondition())
|
||||
transformation = FilterTransformation(state.outerLoop, transformation.inputVariable, indexVariable, mergedCondition, isInverse = false) //TODO: build filterNot in some cases?
|
||||
currentState = nextState
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return SequenceTransformationMatch(transformation, currentState)
|
||||
}
|
||||
|
||||
private fun matchOneTransformation(state: MatchingState, takeWhileAllowed: Boolean): Pair<SequenceTransformation, MatchingState>? {
|
||||
private fun matchOneTransformation(state: MatchingState): Pair<SequenceTransformation, MatchingState>? {
|
||||
val ifStatement = state.statements.firstOrNull() as? KtIfExpression ?: return null
|
||||
if (ifStatement.`else` != null) return null
|
||||
val condition = ifStatement.condition ?: return null
|
||||
@@ -113,7 +120,6 @@ class FilterTransformation(
|
||||
}
|
||||
|
||||
is KtBreakExpression -> {
|
||||
if (!takeWhileAllowed) return null
|
||||
if (statement.targetLoop() != state.outerLoop) return null
|
||||
val transformation = TakeWhileTransformation(state.outerLoop, state.inputVariable, condition.negate())
|
||||
val newState = state.copy(statements = state.statements.drop(1))
|
||||
@@ -125,8 +131,6 @@ class FilterTransformation(
|
||||
}
|
||||
}
|
||||
|
||||
// private fun mergeFilterTransformations(transformation1: )
|
||||
|
||||
//TODO: choose filter or filterNot depending on condition
|
||||
private fun createFilterTransformation(
|
||||
loop: KtForExpression,
|
||||
@@ -139,7 +143,7 @@ class FilterTransformation(
|
||||
val effectiveCondition = if (isInverse) condition.negate() else condition
|
||||
|
||||
if (indexVariable != null && indexVariable.hasUsages(condition)) {
|
||||
return FilterIndexedTransformation(loop, inputVariable, indexVariable, effectiveCondition)
|
||||
return FilterTransformation(loop, inputVariable, indexVariable, effectiveCondition, isInverse = false)
|
||||
}
|
||||
|
||||
if (effectiveCondition is KtIsExpression
|
||||
@@ -160,7 +164,7 @@ class FilterTransformation(
|
||||
return FilterNotNullTransformation(loop)
|
||||
}
|
||||
|
||||
return FilterTransformation(loop, inputVariable, condition, isInverse)
|
||||
return FilterTransformation(loop, inputVariable, null, condition, isInverse)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,27 +204,6 @@ class FilterNotNullTransformation(override val loop: KtForExpression) : Sequence
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
class TakeWhileTransformation(
|
||||
override val loop: KtForExpression,
|
||||
val inputVariable: KtCallableDeclaration,
|
||||
|
||||
@@ -306,4 +306,9 @@ private fun isEmbeddedBreakOrContinue(expression: KtExpressionWithLabel): Boolea
|
||||
|
||||
else -> return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MatchingState.unwrapBlock(): MatchingState {
|
||||
val block = statements.singleOrNull() as? KtBlockExpression ?: return this
|
||||
return this.copy(statements = block.statements)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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 * 10) continue
|
||||
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 * 10 && s.length > index }
|
||||
.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
// INTENTION_TEXT: "Replace with 'filterIndexed{}.firstOrNull()'"
|
||||
fun foo(list: List<String>): String? {
|
||||
<caret>for ((index, s) in list.withIndex()) {
|
||||
if (s.isBlank()) continue
|
||||
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.isBlank() && s.length > index }
|
||||
.firstOrNull()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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) continue
|
||||
if (s.isNotBlank()) {
|
||||
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 && s.isNotBlank() }
|
||||
.firstOrNull()
|
||||
}
|
||||
Reference in New Issue
Block a user