Split or glue together checks for not null and is instance + more intelligent use of filter vs filterTo

#KT-14284 Fixed
 #KT-14286 Fixed
 #KT-14287 Fixed
 #KT-14303 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-10-17 16:27:01 +03:00
parent 53220b980e
commit 89099b93b1
41 changed files with 548 additions and 126 deletions
@@ -22,7 +22,6 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
@@ -164,8 +163,8 @@ private fun KtExpression.specialNegation(): KtExpression? {
if (operationReference.getReferencedName() == "!") {
val baseExpression = baseExpression
if (baseExpression != null) {
val context = baseExpression.analyzeAndGetResult().bindingContext
val type = context.getType(baseExpression)
val bindingContext = baseExpression.analyze(BodyResolveMode.PARTIAL)
val type = bindingContext.getType(baseExpression)
if (type != null && KotlinBuiltIns.isBoolean(type)) {
return KtPsiUtil.safeDeparenthesize(baseExpression)
}
@@ -22,11 +22,7 @@ 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.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.intentions.loopToCallChain.sequence.*
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
@@ -41,7 +37,9 @@ class AddToCollectionTransformation(
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
return when (previousTransformation) {
is FilterTransformation -> {
FilterToTransformation.create(loop, previousTransformation.inputVariable, previousTransformation.indexVariable, targetCollection, previousTransformation.condition, previousTransformation.isInverse)
FilterToTransformation.create(
loop, previousTransformation.inputVariable, previousTransformation.indexVariable,
targetCollection, previousTransformation.effectiveCondition, previousTransformation.isFilterNot)
}
is FilterNotNullTransformation -> {
@@ -201,15 +199,19 @@ class FilterToTransformation private constructor(
private val inputVariable: KtCallableDeclaration,
private val indexVariable: KtCallableDeclaration?,
private val targetCollection: KtExpression,
private val condition: KtExpression,
private val isInverse: Boolean
private val effectiveCondition: Condition,
private val isFilterNot: Boolean
) : ReplaceLoopResultTransformation(loop) {
private fun effectiveCondition() = if (isInverse) condition.negate() else condition
init {
if (isFilterNot) {
assert(indexVariable == null)
}
}
private val functionName = when {
indexVariable != null -> "filterIndexedTo"
isInverse -> "filterNotTo"
isFilterNot -> "filterNotTo"
else -> "filterTo"
}
@@ -218,9 +220,9 @@ class FilterToTransformation private constructor(
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
val lambda = if (indexVariable != null)
generateLambda(inputVariable, indexVariable, effectiveCondition())
generateLambda(inputVariable, indexVariable, effectiveCondition.asExpression())
else
generateLambda(inputVariable, condition)
generateLambda(inputVariable, if (isFilterNot) effectiveCondition.asNegatedExpression() else effectiveCondition.asExpression())
return chainedCallGenerator.generate("$functionName($0) $1:'{}'", targetCollection, lambda)
}
@@ -230,16 +232,16 @@ class FilterToTransformation private constructor(
inputVariable: KtCallableDeclaration,
indexVariable: KtCallableDeclaration?,
targetCollection: KtExpression,
condition: KtExpression,
isInverse: Boolean
condition: Condition,
isFilterNot: Boolean
): ResultTransformation {
val initialization = targetCollection.findVariableInitializationBeforeLoop(loop, checkNoOtherUsagesInLoop = true)
if (initialization != null && initialization.initializer.hasNoSideEffect()) {
val transformation = FilterToTransformation(loop, inputVariable, indexVariable, initialization.initializer, condition, isInverse)
val transformation = FilterToTransformation(loop, inputVariable, indexVariable, initialization.initializer, condition, isFilterNot)
return AssignToVariableResultTransformation.createDelegated(transformation, initialization)
}
else {
return FilterToTransformation(loop, inputVariable, indexVariable, targetCollection, condition, isInverse)
return FilterToTransformation(loop, inputVariable, indexVariable, targetCollection, condition, isFilterNot)
}
}
}
@@ -34,9 +34,9 @@ class CountTransformation(
if (previousTransformation !is FilterTransformationBase) return null
if (previousTransformation.indexVariable != null) return null
val newFilter = if (filter == null)
previousTransformation.effectiveCondition
previousTransformation.effectiveCondition.asExpression()
else
KtPsiFactory(filter).createExpressionByPattern("$0 && $1", previousTransformation.effectiveCondition, filter)
KtPsiFactory(filter).createExpressionByPattern("$0 && $1", previousTransformation.effectiveCondition.asExpression(), filter)
return CountTransformation(loop, previousTransformation.inputVariable, initialization, newFilter)
}
@@ -225,7 +225,7 @@ object FindTransformationMatcher : TransformationMatcher {
assert(valueIfFound.isPhysical)
assert(valueIfNotFound.isPhysical)
val filter = filterTransformation?.effectiveCondition
val filter = filterTransformation?.effectiveCondition?.asExpression()
if (indexVariable != null) {
if (filterTransformation == null) return null // makes no sense, indexVariable must be always null
@@ -0,0 +1,101 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.hasUsages
import org.jetbrains.kotlin.idea.intentions.negate
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
interface Condition {
fun asExpression(): KtExpression
fun asNegatedExpression(): KtExpression
fun toAtomicConditions(): List<AtomicCondition>
companion object {
fun create(expression: KtExpression, negated: Boolean = false): Condition {
if (negated) {
if (expression is KtBinaryExpression && expression.operationToken == KtTokens.OROR) {
//TODO: check Boolean type for operands
val left = expression.left
val right = expression.right
if (left != null && right != null) {
val leftCondition = create(left, negated = true)
val rightCondition = create(right, negated = true)
return CompositeCondition.create(leftCondition.toAtomicConditions() + rightCondition.toAtomicConditions())
}
}
}
else {
if (expression is KtBinaryExpression && expression.operationToken == KtTokens.ANDAND) {
//TODO: check Boolean type for operands
val left = expression.left
val right = expression.right
if (left != null && right != null) {
val leftCondition = create(left)
val rightCondition = create(right)
return CompositeCondition.create(leftCondition.toAtomicConditions() + rightCondition.toAtomicConditions())
}
}
}
return AtomicCondition(expression, negated)
}
}
}
class AtomicCondition(val expression: KtExpression, val isNegated: Boolean = false) : Condition {
init {
assert(expression.isPhysical)
}
override fun asExpression() = if (isNegated) expression.negate() else expression
override fun asNegatedExpression() = if (isNegated) expression else expression.negate()
override fun toAtomicConditions() = listOf(this)
fun negate() = AtomicCondition(expression, !isNegated)
}
class CompositeCondition private constructor(val conditions: List<AtomicCondition>) : Condition {
override fun asExpression(): KtExpression {
val factory = KtPsiFactory(conditions.first().expression)
return factory.buildExpression {
for ((index, condition) in conditions.withIndex()) {
if (index > 0) {
appendFixedText("&&")
}
appendExpression(condition.asExpression())
}
}
}
override fun asNegatedExpression(): KtExpression {
return asExpression().negate() //TODO?
}
override fun toAtomicConditions() = conditions
companion object {
fun create(conditions: List<AtomicCondition>): Condition {
return conditions.singleOrNull() ?: CompositeCondition(conditions)
}
}
}
fun Condition.hasUsagesOf(variable: KtCallableDeclaration): Boolean {
return toAtomicConditions().any { variable.hasUsages(it.expression) }
}
@@ -16,17 +16,25 @@
package org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindTransformationMatcher
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.MaxOrMinTransformation
import org.jetbrains.kotlin.idea.intentions.negate
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.blockExpressionsOrSingle
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.utils.addToStdlib.singletonList
import java.util.*
abstract class FilterTransformationBase : SequenceTransformation {
abstract val effectiveCondition: KtExpression
abstract val effectiveCondition: Condition
abstract val inputVariable: KtCallableDeclaration
abstract val indexVariable: KtCallableDeclaration?
@@ -66,31 +74,75 @@ abstract class FilterTransformationBase : SequenceTransformation {
var (transformation, currentState) = matchOneTransformation(state) ?: return null
assert(currentState.indexVariable == state.indexVariable) // indexVariable should not change
if (transformation is FilterTransformation) {
while (true) {
currentState = currentState.unwrapBlock()
if (transformation !is FilterTransformationBase) {
return TransformationMatch.Sequence(transformation, currentState)
}
if (MaxOrMinTransformation.Matcher.match(currentState) != null) break // do not take 'if' which is required for min/max matcher
val atomicConditions = transformation.effectiveCondition.toAtomicConditions().toMutableList()
while (true) {
currentState = currentState.unwrapBlock()
val (nextTransformation, nextState) = matchOneTransformation(currentState) ?: break
if (nextTransformation !is FilterTransformation) break
assert(nextState.indexVariable == currentState.indexVariable) // indexVariable should not change
if (MaxOrMinTransformation.Matcher.match(currentState) != null) break // do not take 'if' which is required for min/max matcher
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
val (nextTransformation, nextState) = matchOneTransformation(currentState) ?: break
if (nextTransformation !is FilterTransformationBase) break
assert(nextState.indexVariable == currentState.indexVariable) // indexVariable should not change
atomicConditions.addAll(nextTransformation.effectiveCondition.toAtomicConditions())
currentState = nextState
}
val transformations = createTransformationsByAtomicConditions(
currentState.outerLoop,
currentState.inputVariable,
currentState.indexVariable,
atomicConditions,
currentState.statements)
assert(transformations.isNotEmpty())
val findTransformationMatch = FindTransformationMatcher.matchWithFilterBefore(currentState, transformations.last())
if (findTransformationMatch != null) {
return TransformationMatch.Result(findTransformationMatch.resultTransformation,
transformations.dropLast(1) + findTransformationMatch.sequenceTransformations)
}
else {
return TransformationMatch.Sequence(transformations, currentState)
}
}
private fun createTransformationsByAtomicConditions(
loop: KtForExpression,
inputVariable: KtCallableDeclaration,
indexVariable: KtCallableDeclaration?,
conditions: List<AtomicCondition>,
restStatements: List<KtExpression>
): List<FilterTransformationBase> {
//TODO: indexVariable case
if (conditions.size == 1) {
return createFilterTransformation(loop, inputVariable, indexVariable, conditions.single()).singletonList()
}
val transformations = ArrayList<FilterTransformationBase>()
for (condition in conditions) {
val transformation = createFilterTransformation(loop, inputVariable, indexVariable, condition)
if (transformation !is FilterTransformation && isSmartCastUsed(inputVariable, restStatements)) { // filterIsInstance of filterNotNull
transformations.add(transformation)
}
else {
val prevFilter = transformations.lastOrNull() as? FilterTransformation
if (prevFilter != null) {
val mergedCondition = CompositeCondition.create(prevFilter.effectiveCondition.toAtomicConditions() + transformation.effectiveCondition.toAtomicConditions())
val mergedTransformation = createFilterTransformation(loop, inputVariable, indexVariable, mergedCondition, onlyFilterOrFilterNot = true)
transformations[transformations.lastIndex] = mergedTransformation
}
else {
transformations.add(createFilterTransformation(loop, inputVariable, indexVariable, condition, onlyFilterOrFilterNot = true))
}
}
}
if (transformation is FilterTransformationBase) {
FindTransformationMatcher.matchWithFilterBefore(currentState, transformation)
?.let { return it }
}
return TransformationMatch.Sequence(transformation, currentState)
return transformations
}
private fun matchOneTransformation(state: MatchingState): Pair<SequenceTransformation, MatchingState>? {
@@ -104,7 +156,7 @@ abstract class FilterTransformationBase : SequenceTransformation {
if (!state.inputVariable.hasUsages(condition) && (state.indexVariable == null || !state.indexVariable.hasUsages(condition))) return null
if (state.statements.size == 1) {
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, state.indexVariable, condition, isInverse = false)
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, state.indexVariable, Condition.create(condition))
val newState = state.copy(statements = listOf(then))
return transformation to newState
}
@@ -113,7 +165,7 @@ abstract class FilterTransformationBase : SequenceTransformation {
when (statement) {
is KtContinueExpression -> {
if (statement.targetLoop() != state.innerLoop) return null
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, state.indexVariable, condition, isInverse = true)
val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, state.indexVariable, Condition.create(condition, negated = true))
val newState = state.copy(statements = state.statements.drop(1))
return transformation to newState
}
@@ -130,40 +182,52 @@ abstract class FilterTransformationBase : SequenceTransformation {
}
}
//TODO: choose filter or filterNot depending on condition
private fun createFilterTransformation(
loop: KtForExpression,
inputVariable: KtCallableDeclaration,
indexVariable: KtCallableDeclaration?,
condition: KtExpression,
isInverse: Boolean
condition: Condition,
onlyFilterOrFilterNot: Boolean = false
): FilterTransformationBase {
val effectiveCondition = if (isInverse) condition.negate() else condition
if (indexVariable != null && indexVariable.hasUsages(condition)) {
return FilterTransformation(loop, inputVariable, indexVariable, effectiveCondition, isInverse = false)
if (indexVariable != null && condition.hasUsagesOf(indexVariable)) {
return FilterTransformation(loop, inputVariable, indexVariable, condition, isFilterNot = false)
}
if (effectiveCondition is KtIsExpression
&& !effectiveCondition.isNegated
&& effectiveCondition.leftHandSide.isSimpleName(inputVariable.nameAsSafeName) // we cannot use isVariableReference here because expression can be non-physical
) {
val typeRef = effectiveCondition.typeReference
if (typeRef != null) {
return FilterIsInstanceTransformation(loop, inputVariable, typeRef)
val conditionAsExpression = condition.asExpression()
if (!onlyFilterOrFilterNot) {
if (conditionAsExpression is KtIsExpression
&& !conditionAsExpression.isNegated
&& conditionAsExpression.leftHandSide.isSimpleName(inputVariable.nameAsSafeName) // we cannot use isVariableReference here because expression can be non-physical
) {
val typeRef = conditionAsExpression.typeReference
if (typeRef != null) {
return FilterIsInstanceTransformation(loop, inputVariable, typeRef, condition)
}
}
if (conditionAsExpression is KtBinaryExpression
&& conditionAsExpression.operationToken == KtTokens.EXCLEQ
&& conditionAsExpression.right.isNullExpression()
&& conditionAsExpression.left.isSimpleName(inputVariable.nameAsSafeName)
) {
return FilterNotNullTransformation(loop, inputVariable, condition)
}
}
if (effectiveCondition is KtBinaryExpression
&& effectiveCondition.operationToken == KtTokens.EXCLEQ
&& effectiveCondition.right.isNullExpression()
&& effectiveCondition.left.isSimpleName(inputVariable.nameAsSafeName)
) {
return FilterNotNullTransformation(loop, inputVariable)
if (conditionAsExpression is KtPrefixExpression && conditionAsExpression.operationToken == KtTokens.EXCL) {
return FilterTransformation(loop, inputVariable, null, condition, isFilterNot = true)
}
return FilterTransformation(loop, inputVariable, null, condition, isInverse)
return FilterTransformation(loop, inputVariable, null, condition, isFilterNot = false)
}
private fun isSmartCastUsed(inputVariable: KtCallableDeclaration, statements: List<KtExpression>): Boolean {
return statements.any { statement ->
statement.anyDescendantOfType<KtNameReferenceExpression> {
it.mainReference.resolve() == inputVariable && it.analyze(BodyResolveMode.PARTIAL)[BindingContext.SMARTCAST, it] != null
}
}
}
}
}
@@ -172,17 +236,19 @@ class FilterTransformation(
override val loop: KtForExpression,
override val inputVariable: KtCallableDeclaration,
override val indexVariable: KtCallableDeclaration?,
val condition: KtExpression,
val isInverse: Boolean
override val effectiveCondition: Condition,
val isFilterNot: Boolean
) : FilterTransformationBase() {
override val effectiveCondition: KtExpression by lazy {
if (isInverse) condition.negate() else condition
init {
if (isFilterNot) {
assert(indexVariable == null)
}
}
private val functionName = when {
indexVariable != null -> "filterIndexed"
isInverse -> "filterNot"
isFilterNot -> "filterNot"
else -> "filter"
}
@@ -191,9 +257,9 @@ class FilterTransformation(
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
val lambda = if (indexVariable != null)
generateLambda(inputVariable, indexVariable, effectiveCondition)
generateLambda(inputVariable, indexVariable, effectiveCondition.asExpression())
else
generateLambda(inputVariable, condition)
generateLambda(inputVariable, if (isFilterNot) effectiveCondition.asNegatedExpression() else effectiveCondition.asExpression())
return chainedCallGenerator.generate("$0$1:'{}'", functionName, lambda)
}
}
@@ -201,13 +267,10 @@ class FilterTransformation(
class FilterIsInstanceTransformation(
override val loop: KtForExpression,
override val inputVariable: KtCallableDeclaration,
private val type: KtTypeReference
private val type: KtTypeReference,
override val effectiveCondition: Condition
) : FilterTransformationBase() {
override val effectiveCondition by lazy {
KtPsiFactory(loop).createExpressionByPattern("$0 is $1", inputVariable.nameAsSafeName, type)
}
override val indexVariable: KtCallableDeclaration? get() = null
override val presentation: String
@@ -220,13 +283,10 @@ class FilterIsInstanceTransformation(
class FilterNotNullTransformation(
override val loop: KtForExpression,
override val inputVariable: KtCallableDeclaration
override val inputVariable: KtCallableDeclaration,
override val effectiveCondition: Condition
) : FilterTransformationBase() {
override val effectiveCondition: KtExpression by lazy {
KtPsiFactory(loop).createExpressionByPattern("$0 != null", inputVariable.nameAsSafeName)
}
override val indexVariable: KtCallableDeclaration? get() = null
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): SequenceTransformation? {
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterIndexedTo(){}'"
// IS_APPLICABLE_2: false
fun foo(list: List<Any>, out: MutableList<Any>){
<caret>for ((i, any) in list.withIndex()) {
if (any is String && i % 2 == 0)
out.add(any)
}
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterIndexedTo(){}'"
// IS_APPLICABLE_2: false
fun foo(list: List<Any>, out: MutableList<Any>){
list.filterIndexedTo(out) { i, any -> any is String && i % 2 == 0 }
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filter{}.forEach{}'"
fun foo(list: List<String?>){
<caret>for (l in list) {
if (l != null && l.startsWith("IMG:"))
println(l)
}
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filter{}.forEach{}'"
fun foo(list: List<String?>){
list
.filter { it != null && it.startsWith("IMG:") }
.forEach { println(it) }
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filter{}.forEach{}'"
fun foo(list: List<String?>){
list
.asSequence()
.filter { it != null && it.startsWith("IMG:") }
.forEach { println(it) }
}
@@ -1,6 +1,6 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with '...flatMap{}.filterNot{}.mapTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence()...flatMap{}.filterNot{}.mapTo(){}'"
// INTENTION_TEXT: "Replace with '...flatMap{}.filter{}.mapTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence()...flatMap{}.filter{}.mapTo(){}'"
fun foo(list: List<String>, target: MutableCollection<String>) {
var i = 0
<caret>for (s in list) {
@@ -1,10 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with '...flatMap{}.filterNot{}.mapTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence()...flatMap{}.filterNot{}.mapTo(){}'"
// INTENTION_TEXT: "Replace with '...flatMap{}.filter{}.mapTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence()...flatMap{}.filter{}.mapTo(){}'"
fun foo(list: List<String>, target: MutableCollection<String>) {
<caret>list
.filterIndexed { i, s -> i % 10 != 0 }
.flatMap { it.indices }
.filterNot { it == 10 }
.filter { it != 10 }
.mapTo(target) { it.toString() }
}
@@ -1,11 +1,11 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with '...flatMap{}.filterNot{}.mapTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence()...flatMap{}.filterNot{}.mapTo(){}'"
// INTENTION_TEXT: "Replace with '...flatMap{}.filter{}.mapTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence()...flatMap{}.filter{}.mapTo(){}'"
fun foo(list: List<String>, target: MutableCollection<String>) {
<caret>list
.asSequence()
.filterIndexed { i, s -> i % 10 != 0 }
.flatMap { it.indices.asSequence() }
.filterNot { it == 10 }
.filter { it != 10 }
.mapTo(target) { it.toString() }
}
@@ -3,7 +3,9 @@
// IS_APPLICABLE_2: false
fun foo(list: List<String>, target: MutableList<String>) {
<caret>for (s in list) {
if (s.length == 0) continue
if (bar(s)) continue
target.add(s)
}
}
}
fun bar(string: String): Boolean = TODO()
@@ -2,5 +2,7 @@
// INTENTION_TEXT: "Replace with 'filterNotTo(){}'"
// IS_APPLICABLE_2: false
fun foo(list: List<String>, target: MutableList<String>) {
<caret>list.filterNotTo(target) { it.length == 0 }
}
list.filterNotTo(target) { bar(it) }
}
fun bar(string: String): Boolean = TODO()
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filter{}.forEach{}'"
fun foo(list: List<String?>){
<caret>for (l in list) {
if (l == null) continue
if (l.startsWith("IMG:"))
println(l)
}
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filter{}.forEach{}'"
fun foo(list: List<String?>){
list
.filter { it != null && it.startsWith("IMG:") }
.forEach { println(it) }
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filter{}.forEach{}'"
fun foo(list: List<String?>){
list
.asSequence()
.filter { it != null && it.startsWith("IMG:") }
.forEach { println(it) }
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterIsInstance<>().filterTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterIsInstance<>().filterTo(){}'"
fun foo(list: List<Any>, out: MutableList<String>){
<caret>for (any in list) {
if (any is String && any.length > 0)
out.add(any)
}
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterIsInstance<>().filterTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterIsInstance<>().filterTo(){}'"
fun foo(list: List<Any>, out: MutableList<String>){
list
.filterIsInstance<String>()
.filterTo(out) { it.length > 0 }
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterIsInstance<>().filterTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterIsInstance<>().filterTo(){}'"
fun foo(list: List<Any>, out: MutableList<String>){
list
.asSequence()
.filterIsInstance<String>()
.filterTo(out) { it.length > 0 }
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterNotNull().filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterNotNull().filter{}.forEach{}'"
fun foo(list: List<String?>){
<caret>for (l in list) {
if (l != null && l.startsWith("IMG:"))
println(l.hashCode())
}
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterNotNull().filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterNotNull().filter{}.forEach{}'"
fun foo(list: List<String?>){
list
.filterNotNull()
.filter { it.startsWith("IMG:") }
.forEach { println(it.hashCode()) }
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterNotNull().filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterNotNull().filter{}.forEach{}'"
fun foo(list: List<String?>){
list
.asSequence()
.filterNotNull()
.filter { it.startsWith("IMG:") }
.forEach { println(it.hashCode()) }
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterNotNull().filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterNotNull().filter{}.forEach{}'"
fun foo(list: List<String?>){
<caret>for (l in list) {
if (l == null || !l.startsWith("IMG:")) continue
println(l.hashCode())
}
}
@@ -0,0 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterNotNull().filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterNotNull().filter{}.forEach{}'"
fun foo(list: List<String?>){
list
.filterNotNull()
.filter { it.startsWith("IMG:") }
.forEach { println(it.hashCode()) }
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterNotNull().filter{}.forEach{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterNotNull().filter{}.forEach{}'"
fun foo(list: List<String?>){
list
.asSequence()
.filterNotNull()
.filter { it.startsWith("IMG:") }
.forEach { println(it.hashCode()) }
}
@@ -1,6 +1,6 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterNotNull().firstOrNull{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterNotNull().firstOrNull{}'"
// INTENTION_TEXT: "Replace with 'firstOrNull{}'"
// IS_APPLICABLE_2: false
fun getFirstValue() = "value"
@@ -1,13 +1,11 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterNotNull().firstOrNull{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterNotNull().firstOrNull{}'"
// INTENTION_TEXT: "Replace with 'firstOrNull{}'"
// IS_APPLICABLE_2: false
fun getFirstValue() = "value"
fun foo(list: List<String?>): String? {
val value = getFirstValue()
val <caret>found: String? = list
.filterNotNull()
.firstOrNull { !it.startsWith("IMG:") && it.contains(value) }
val found: String? = list.firstOrNull { it != null && !it.startsWith("IMG:") && it.contains(value) }
return found
}
@@ -1,14 +0,0 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'filterNotNull().firstOrNull{}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().filterNotNull().firstOrNull{}'"
fun getFirstValue() = "value"
fun foo(list: List<String?>): String? {
val value = getFirstValue()
val <caret>found: String? = list
.asSequence()
.filterNotNull()
.firstOrNull { !it.startsWith("IMG:") && it.contains(value) }
return found
}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'indexOfFirst{}'"
// IS_APPLICABLE_2: false
fun f8_indexOfFirst_complex(value: String, list: List<Any?>):Int{
<caret>for ((i, any) in list.withIndex())
if (any != null)
if (any is String)
if (any == value)
return i
return -1
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'indexOfFirst{}'"
// IS_APPLICABLE_2: false
fun f8_indexOfFirst_complex(value: String, list: List<Any?>):Int{
return list.indexOfFirst { it != null && it is String && it == value }
}
@@ -1,6 +1,6 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'flatMap{}.filterNot{}.mapIndexedTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().flatMap{}.filterNot{}.mapIndexedTo(){}'"
// INTENTION_TEXT: "Replace with 'flatMap{}.filter{}.mapIndexedTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().flatMap{}.filter{}.mapIndexedTo(){}'"
fun foo(list: List<String>, target: MutableCollection<Int>) {
var i = 0
<caret>for (s in list) {
@@ -1,9 +1,9 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'flatMap{}.filterNot{}.mapIndexedTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().flatMap{}.filterNot{}.mapIndexedTo(){}'"
// INTENTION_TEXT: "Replace with 'flatMap{}.filter{}.mapIndexedTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().flatMap{}.filter{}.mapIndexedTo(){}'"
fun foo(list: List<String>, target: MutableCollection<Int>) {
<caret>list
.flatMap { it.indices }
.filterNot { it == 10 }
.filter { it != 10 }
.mapIndexedTo(target) { i, j -> i + j }
}
@@ -1,10 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'flatMap{}.filterNot{}.mapIndexedTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().flatMap{}.filterNot{}.mapIndexedTo(){}'"
// INTENTION_TEXT: "Replace with 'flatMap{}.filter{}.mapIndexedTo(){}'"
// INTENTION_TEXT_2: "Replace with 'asSequence().flatMap{}.filter{}.mapIndexedTo(){}'"
fun foo(list: List<String>, target: MutableCollection<Int>) {
<caret>list
.asSequence()
.flatMap { it.indices.asSequence() }
.filterNot { it == 10 }
.filter { it != 10 }
.mapIndexedTo(target) { i, j -> i + j }
}
@@ -1,6 +1,6 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
// IS_APPLICABLE_2: false
// INTENTION_TEXT: "Replace with '...filter{}.map{}.firstOrNull()'"
// INTENTION_TEXT_2: "Replace with 'asSequence()...filter{}.map{}.firstOrNull()'"
fun foo(list: List<Any>, o: Any): Int? {
<caret>for (s in list) {
if (s is String && s.length > 0) {
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with '...filter{}.map{}.firstOrNull()'"
// INTENTION_TEXT_2: "Replace with 'asSequence()...filter{}.map{}.firstOrNull()'"
fun foo(list: List<Any>, o: Any): Int? {
return list
.filterIsInstance<String>()
.filter { it.length > 0 }
.map { it.length * 2 }
.firstOrNull()
}
@@ -0,0 +1,11 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with '...filter{}.map{}.firstOrNull()'"
// INTENTION_TEXT_2: "Replace with 'asSequence()...filter{}.map{}.firstOrNull()'"
fun foo(list: List<Any>, o: Any): Int? {
return list
.asSequence()
.filterIsInstance<String>()
.filter { it.length > 0 }
.map { it.length * 2 }
.firstOrNull()
}
@@ -358,6 +358,18 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 {
doTest(fileName);
}
@TestMetadata("doNotSplitOutFilterIsInstance.kt")
public void testDoNotSplitOutFilterIsInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/doNotSplitOutFilterIsInstance.kt");
doTest(fileName);
}
@TestMetadata("doNotSplitOutFilterNotNull.kt")
public void testDoNotSplitOutFilterNotNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/doNotSplitOutFilterNotNull.kt");
doTest(fileName);
}
@TestMetadata("filterIndexed.kt")
public void testFilterIndexed() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/filterIndexed.kt");
@@ -514,6 +526,12 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 {
doTest(fileName);
}
@TestMetadata("glueTogetherFilterNotNull.kt")
public void testGlueTogetherFilterNotNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/glueTogetherFilterNotNull.kt");
doTest(fileName);
}
@TestMetadata("ifContinue.kt")
public void testIfContinue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/ifContinue.kt");
@@ -555,6 +573,24 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/mergeMultiple.kt");
doTest(fileName);
}
@TestMetadata("splitOutFilterIsInstance.kt")
public void testSplitOutFilterIsInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/splitOutFilterIsInstance.kt");
doTest(fileName);
}
@TestMetadata("splitOutFilterNotNull.kt")
public void testSplitOutFilterNotNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/splitOutFilterNotNull.kt");
doTest(fileName);
}
@TestMetadata("splitOutFilterNotNull2.kt")
public void testSplitOutFilterNotNull2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/splitOutFilterNotNull2.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/loopToCallChain/firstOrNull")
@@ -844,6 +880,12 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 {
doTest(fileName);
}
@TestMetadata("KT14303.kt")
public void testKT14303() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/indexOf/KT14303.kt");
doTest(fileName);
}
@TestMetadata("lastIndexOf.kt")
public void testLastIndexOf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/indexOf/lastIndexOf.kt");
@@ -8266,6 +8266,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("doNotSplitOutFilterIsInstance.kt")
public void testDoNotSplitOutFilterIsInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/doNotSplitOutFilterIsInstance.kt");
doTest(fileName);
}
@TestMetadata("doNotSplitOutFilterNotNull.kt")
public void testDoNotSplitOutFilterNotNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/doNotSplitOutFilterNotNull.kt");
doTest(fileName);
}
@TestMetadata("filterIndexed.kt")
public void testFilterIndexed() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/filterIndexed.kt");
@@ -8422,6 +8434,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("glueTogetherFilterNotNull.kt")
public void testGlueTogetherFilterNotNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/glueTogetherFilterNotNull.kt");
doTest(fileName);
}
@TestMetadata("ifContinue.kt")
public void testIfContinue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/ifContinue.kt");
@@ -8463,6 +8481,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/mergeMultiple.kt");
doTest(fileName);
}
@TestMetadata("splitOutFilterIsInstance.kt")
public void testSplitOutFilterIsInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/splitOutFilterIsInstance.kt");
doTest(fileName);
}
@TestMetadata("splitOutFilterNotNull.kt")
public void testSplitOutFilterNotNull() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/splitOutFilterNotNull.kt");
doTest(fileName);
}
@TestMetadata("splitOutFilterNotNull2.kt")
public void testSplitOutFilterNotNull2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/filter/splitOutFilterNotNull2.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/loopToCallChain/firstOrNull")
@@ -8752,6 +8788,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("KT14303.kt")
public void testKT14303() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/indexOf/KT14303.kt");
doTest(fileName);
}
@TestMetadata("lastIndexOf.kt")
public void testLastIndexOf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/indexOf/lastIndexOf.kt");