Supported indexOfFirst/indexOfLast
This commit is contained in:
+1
-1
@@ -39,7 +39,7 @@ class LoopToCallChainIntention : SelfTargetingRangeIntention<KtForExpression>(
|
|||||||
return if (match != null) element.forKeyword.textRange else null
|
return if (match != null) element.forKeyword.textRange else null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ResultTransformationMatch.buildPresentation(): String {
|
private fun TransformationMatch.Result.buildPresentation(): String {
|
||||||
var transformations = sequenceTransformations + resultTransformation
|
var transformations = sequenceTransformations + resultTransformation
|
||||||
val MAX = 3
|
val MAX = 3
|
||||||
var result: String? = null
|
var result: String? = null
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ fun KtExpression?.isSimpleName(name: Name): Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun KtCallableDeclaration.hasUsages(inElement: KtElement): Boolean {
|
fun KtCallableDeclaration.hasUsages(inElement: KtElement): Boolean {
|
||||||
|
assert(inElement.isPhysical)
|
||||||
return hasUsages(listOf(inElement))
|
return hasUsages(listOf(inElement))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+116
-77
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.intentions.loopToCallChain
|
|||||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
|
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
|
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
|
||||||
import org.jetbrains.kotlin.types.typeUtil.nullability
|
import org.jetbrains.kotlin.types.typeUtil.nullability
|
||||||
@@ -26,9 +27,14 @@ import org.jetbrains.kotlin.types.typeUtil.nullability
|
|||||||
interface FindOperatorGenerator {
|
interface FindOperatorGenerator {
|
||||||
val functionName: String
|
val functionName: String
|
||||||
|
|
||||||
|
val hasFilter: Boolean
|
||||||
|
|
||||||
|
val presentation: String
|
||||||
|
get() = functionName + (if (hasFilter) "{}" else "()")
|
||||||
|
|
||||||
val shouldUseInputVariable: Boolean
|
val shouldUseInputVariable: Boolean
|
||||||
|
|
||||||
fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression
|
fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression
|
||||||
|
|
||||||
val chainCallCount: Int
|
val chainCallCount: Int
|
||||||
get() = 1
|
get() = 1
|
||||||
@@ -38,11 +44,15 @@ fun buildFindOperationGenerator(
|
|||||||
valueIfFound: KtExpression,
|
valueIfFound: KtExpression,
|
||||||
valueIfNotFound: KtExpression,
|
valueIfNotFound: KtExpression,
|
||||||
inputVariable: KtCallableDeclaration,
|
inputVariable: KtCallableDeclaration,
|
||||||
|
indexVariable: KtCallableDeclaration?,
|
||||||
|
filterTransformation: FilterTransformation?,
|
||||||
findFirst: Boolean
|
findFirst: Boolean
|
||||||
): FindOperatorGenerator? {
|
): FindOperatorGenerator? {
|
||||||
assert(valueIfFound.isPhysical)
|
assert(valueIfFound.isPhysical)
|
||||||
assert(valueIfNotFound.isPhysical)
|
assert(valueIfNotFound.isPhysical)
|
||||||
|
|
||||||
|
val filterCondition = filterTransformation?.effectiveCondition()
|
||||||
|
|
||||||
fun generateChainedCall(stdlibFunName: String, chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
fun generateChainedCall(stdlibFunName: String, chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
||||||
return if (filter == null) {
|
return if (filter == null) {
|
||||||
chainedCallGenerator.generate("$stdlibFunName()")
|
chainedCallGenerator.generate("$stdlibFunName()")
|
||||||
@@ -53,97 +63,126 @@ fun buildFindOperationGenerator(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SimpleGenerator(override val functionName: String, override val shouldUseInputVariable: Boolean) : FindOperatorGenerator {
|
class SimpleGenerator(
|
||||||
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
override val functionName: String,
|
||||||
return generateChainedCall(functionName, chainedCallGenerator, filter)
|
override val shouldUseInputVariable: Boolean
|
||||||
|
) : FindOperatorGenerator {
|
||||||
|
override val hasFilter: Boolean
|
||||||
|
get() = filterCondition != null
|
||||||
|
|
||||||
|
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
|
return generateChainedCall(functionName, chainedCallGenerator, filterCondition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val inputVariableCanHoldNull = (inputVariable.resolveToDescriptor() as VariableDescriptor).type.nullability() != TypeNullability.NOT_NULL
|
if (indexVariable != null) {
|
||||||
|
if (filterTransformation == null) return null // makes no sense, indexVariable must be always 0
|
||||||
|
if (filterTransformation.indexVariable != null) return null // cannot use index in condition for indexOfFirst/indexOfLast
|
||||||
|
|
||||||
fun FindOperatorGenerator.useElvisOperatorIfNeeded(): FindOperatorGenerator? {
|
//TODO: what if value when not found is not "-1"?
|
||||||
if (valueIfNotFound.isNullExpression()) return this
|
if (valueIfFound.isVariableReference(indexVariable) && valueIfNotFound.text == "-1") {
|
||||||
|
return SimpleGenerator(if (findFirst) "indexOfFirst" else "indexOfLast", shouldUseInputVariable = false)
|
||||||
// we cannot use ?: if found value can be null
|
|
||||||
if (inputVariableCanHoldNull) return null
|
|
||||||
|
|
||||||
return object: FindOperatorGenerator by this {
|
|
||||||
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
|
||||||
val generated = this@useElvisOperatorIfNeeded.generate(chainedCallGenerator, filter)
|
|
||||||
return KtPsiFactory(generated).createExpressionByPattern("$0 ?: $1", generated, valueIfNotFound)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
val inputVariableCanHoldNull = (inputVariable.resolveToDescriptor() as VariableDescriptor).type.nullability() != TypeNullability.NOT_NULL
|
||||||
|
|
||||||
when {
|
fun FindOperatorGenerator.useElvisOperatorIfNeeded(): FindOperatorGenerator? {
|
||||||
valueIfFound.isVariableReference(inputVariable) -> {
|
if (valueIfNotFound.isNullExpression()) return this
|
||||||
val generator = SimpleGenerator(if (findFirst) "firstOrNull" else "lastOrNull", shouldUseInputVariable = true)
|
|
||||||
return generator.useElvisOperatorIfNeeded()
|
|
||||||
}
|
|
||||||
|
|
||||||
valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> return SimpleGenerator("any", shouldUseInputVariable = false)
|
// we cannot use ?: if found value can be null
|
||||||
|
|
||||||
valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> return SimpleGenerator("none", shouldUseInputVariable = false)
|
|
||||||
|
|
||||||
inputVariable.hasUsages(valueIfFound) -> {
|
|
||||||
if (!findFirst) return null // too dangerous because of side effects
|
|
||||||
|
|
||||||
// specially handle the case when the result expression is "<input variable>.<some call>" or "<input variable>?.<some call>"
|
|
||||||
val qualifiedExpression = valueIfFound as? KtQualifiedExpression
|
|
||||||
if (qualifiedExpression != null) {
|
|
||||||
val receiver = qualifiedExpression.receiverExpression
|
|
||||||
val selector = qualifiedExpression.selectorExpression
|
|
||||||
if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) {
|
|
||||||
return object: FindOperatorGenerator {
|
|
||||||
override val functionName: String
|
|
||||||
get() = "firstOrNull"
|
|
||||||
|
|
||||||
override val shouldUseInputVariable: Boolean
|
|
||||||
get() = true
|
|
||||||
|
|
||||||
override val chainCallCount: Int
|
|
||||||
get() = 2
|
|
||||||
|
|
||||||
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
|
||||||
val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filter)
|
|
||||||
return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true)
|
|
||||||
}
|
|
||||||
}.useElvisOperatorIfNeeded()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// in case of nullable input variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found
|
|
||||||
if (inputVariableCanHoldNull) return null
|
if (inputVariableCanHoldNull) return null
|
||||||
|
|
||||||
return object: FindOperatorGenerator {
|
return object: FindOperatorGenerator by this {
|
||||||
override val functionName: String
|
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
get() = "firstOrNull"
|
val generated = this@useElvisOperatorIfNeeded.generate(chainedCallGenerator)
|
||||||
|
return KtPsiFactory(generated).createExpressionByPattern("$0 ?: $1", generated, valueIfNotFound)
|
||||||
override val shouldUseInputVariable: Boolean
|
|
||||||
get() = true
|
|
||||||
|
|
||||||
override val chainCallCount: Int
|
|
||||||
get() = 2 // also includes "let"
|
|
||||||
|
|
||||||
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
|
||||||
val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filter)
|
|
||||||
val letBody = generateLambda(inputVariable, valueIfFound)
|
|
||||||
return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true)
|
|
||||||
}
|
}
|
||||||
}.useElvisOperatorIfNeeded()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
when {
|
||||||
return object: FindOperatorGenerator {
|
valueIfFound.isVariableReference(inputVariable) -> {
|
||||||
override val functionName: String
|
val generator = SimpleGenerator(if (findFirst) "firstOrNull" else "lastOrNull", shouldUseInputVariable = true)
|
||||||
get() = "any"
|
return generator.useElvisOperatorIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
override val shouldUseInputVariable: Boolean
|
valueIfFound.isTrueConstant() && valueIfNotFound.isFalseConstant() -> return SimpleGenerator("any", shouldUseInputVariable = false)
|
||||||
get() = false
|
|
||||||
|
|
||||||
override fun generate(chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?): KtExpression {
|
valueIfFound.isFalseConstant() && valueIfNotFound.isTrueConstant() -> return SimpleGenerator("none", shouldUseInputVariable = false)
|
||||||
val chainedCall = generateChainedCall(functionName, chainedCallGenerator, filter)
|
|
||||||
return KtPsiFactory(chainedCall).createExpressionByPattern("if ($0) $1 else $2", chainedCall, valueIfFound, valueIfNotFound)
|
inputVariable.hasUsages(valueIfFound) -> {
|
||||||
|
if (!findFirst) return null // too dangerous because of side effects
|
||||||
|
|
||||||
|
// specially handle the case when the result expression is "<input variable>.<some call>" or "<input variable>?.<some call>"
|
||||||
|
val qualifiedExpression = valueIfFound as? KtQualifiedExpression
|
||||||
|
if (qualifiedExpression != null) {
|
||||||
|
val receiver = qualifiedExpression.receiverExpression
|
||||||
|
val selector = qualifiedExpression.selectorExpression
|
||||||
|
if (receiver.isVariableReference(inputVariable) && selector != null && !inputVariable.hasUsages(selector)) {
|
||||||
|
return object: FindOperatorGenerator {
|
||||||
|
override val functionName: String
|
||||||
|
get() = "firstOrNull"
|
||||||
|
|
||||||
|
override val hasFilter: Boolean
|
||||||
|
get() = filterCondition != null
|
||||||
|
|
||||||
|
override val shouldUseInputVariable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val chainCallCount: Int
|
||||||
|
get() = 2
|
||||||
|
|
||||||
|
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
|
val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition)
|
||||||
|
return chainedCallGenerator.generate("$0", selector, receiver = findFirstCall, safeCall = true)
|
||||||
|
}
|
||||||
|
}.useElvisOperatorIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in case of nullable input variable we cannot distinguish by the result of "firstOrNull" whether nothing was found or 'null' was found
|
||||||
|
if (inputVariableCanHoldNull) return null
|
||||||
|
|
||||||
|
return object: FindOperatorGenerator {
|
||||||
|
override val functionName: String
|
||||||
|
get() = "firstOrNull"
|
||||||
|
|
||||||
|
override val hasFilter: Boolean
|
||||||
|
get() = filterCondition != null
|
||||||
|
|
||||||
|
override val shouldUseInputVariable: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override val chainCallCount: Int
|
||||||
|
get() = 2 // also includes "let"
|
||||||
|
|
||||||
|
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
|
val findFirstCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition)
|
||||||
|
val letBody = generateLambda(inputVariable, valueIfFound)
|
||||||
|
return chainedCallGenerator.generate("let $0:'{}'", letBody, receiver = findFirstCall, safeCall = true)
|
||||||
|
}
|
||||||
|
}.useElvisOperatorIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
return object: FindOperatorGenerator {
|
||||||
|
override val functionName: String
|
||||||
|
get() = "any"
|
||||||
|
|
||||||
|
override val hasFilter: Boolean
|
||||||
|
get() = filterCondition != null
|
||||||
|
|
||||||
|
override val shouldUseInputVariable: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
override fun generate(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
|
val chainedCall = generateChainedCall(functionName, chainedCallGenerator, filterCondition)
|
||||||
|
return KtPsiFactory(chainedCall).createExpressionByPattern("if ($0) $1 else $2", chainedCall, valueIfFound, valueIfNotFound)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,11 @@ data class MatchingState(
|
|||||||
val initializationStatementsToDelete: Collection<KtExpression> = emptyList()
|
val initializationStatementsToDelete: Collection<KtExpression> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
interface BaseMatcher {
|
interface TransformationMatcher {
|
||||||
|
fun match(state: MatchingState): TransformationMatch?
|
||||||
|
|
||||||
|
val indexVariableAllowed: Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementors should return true if they match some constructs with expression-embedded break or continue.
|
* Implementors should return true if they match some constructs with expression-embedded break or continue.
|
||||||
* In this case they are obliged to deal with them and filter out invalid cases.
|
* In this case they are obliged to deal with them and filter out invalid cases.
|
||||||
@@ -109,36 +113,28 @@ interface BaseMatcher {
|
|||||||
get() = false
|
get() = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
sealed class TransformationMatch(val sequenceTransformations: List<SequenceTransformation>) {
|
||||||
* A matcher that can recognize one or more [SequenceTransformation]'s
|
abstract val allTransformations: List<Transformation>
|
||||||
*/
|
|
||||||
interface SequenceTransformationMatcher : BaseMatcher {
|
|
||||||
fun match(state: MatchingState): SequenceTransformationMatch?
|
|
||||||
}
|
|
||||||
|
|
||||||
class SequenceTransformationMatch(
|
/**
|
||||||
val transformations: List<SequenceTransformation>,
|
* A partial match, includes [newState] for further matching
|
||||||
val newState: MatchingState
|
*/
|
||||||
) {
|
class Sequence(transformations: List<SequenceTransformation>, val newState: MatchingState) : TransformationMatch(transformations) {
|
||||||
constructor(transformation: SequenceTransformation, newState: MatchingState) : this(listOf(transformation), newState)
|
constructor(transformation: SequenceTransformation, newState: MatchingState) : this(listOf(transformation), newState)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
override val allTransformations: List<Transformation>
|
||||||
* A matcher that can recognize a [ResultTransformation] (optionally prepended by some [SequenceTransformation]'s).
|
get() = sequenceTransformations
|
||||||
* Should match the whole rest part of the loop.
|
}
|
||||||
*/
|
|
||||||
interface ResultTransformationMatcher : BaseMatcher {
|
|
||||||
fun match(state: MatchingState): ResultTransformationMatch?
|
|
||||||
|
|
||||||
val indexVariableUsePossible: Boolean
|
/**
|
||||||
}
|
* A match of the whole rest part of the loop
|
||||||
|
*/
|
||||||
|
class Result(val resultTransformation: ResultTransformation, sequenceTransformations: List<SequenceTransformation>) : TransformationMatch(sequenceTransformations) {
|
||||||
|
constructor(resultTransformation: ResultTransformation, vararg sequenceTransformations: SequenceTransformation)
|
||||||
|
: this(resultTransformation, sequenceTransformations.asList())
|
||||||
|
|
||||||
class ResultTransformationMatch(
|
override val allTransformations = sequenceTransformations + resultTransformation
|
||||||
val resultTransformation: ResultTransformation,
|
}
|
||||||
val sequenceTransformations: List<SequenceTransformation>
|
|
||||||
) {
|
|
||||||
constructor(resultTransformation: ResultTransformation, vararg sequenceTransformations: SequenceTransformation)
|
|
||||||
: this(resultTransformation, sequenceTransformations.asList())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
|||||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.AddToCollectionTransformation
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.AddToCollectionTransformation
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.CountTransformation
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.CountTransformation
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindAndAssignTransformation
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindTransformationMatcher
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindAndReturnTransformation
|
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation
|
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.FlatMapTransformation
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.IntroduceIndexMatcher
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.IntroduceIndexMatcher
|
||||||
@@ -42,24 +41,20 @@ import org.jetbrains.kotlin.utils.addToStdlib.check
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object MatcherRegistrar {
|
object MatcherRegistrar {
|
||||||
val sequenceMatchers: Collection<SequenceTransformationMatcher> = listOf(
|
val matchers: Collection<TransformationMatcher> = listOf(
|
||||||
|
FindTransformationMatcher,
|
||||||
|
AddToCollectionTransformation.Matcher,
|
||||||
|
CountTransformation.Matcher,
|
||||||
IntroduceIndexMatcher,
|
IntroduceIndexMatcher,
|
||||||
FilterTransformation.Matcher,
|
FilterTransformation.Matcher,
|
||||||
MapTransformation.Matcher,
|
MapTransformation.Matcher,
|
||||||
FlatMapTransformation.Matcher
|
FlatMapTransformation.Matcher
|
||||||
)
|
)
|
||||||
|
|
||||||
val resultMatchers: Collection<ResultTransformationMatcher> = listOf(
|
|
||||||
FindAndReturnTransformation.Matcher,
|
|
||||||
FindAndAssignTransformation.Matcher,
|
|
||||||
AddToCollectionTransformation.Matcher,
|
|
||||||
CountTransformation.Matcher
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MatchResult(
|
data class MatchResult(
|
||||||
val sequenceExpression: KtExpression,
|
val sequenceExpression: KtExpression,
|
||||||
val transformationMatch: ResultTransformationMatch,
|
val transformationMatch: TransformationMatch.Result,
|
||||||
val initializationStatementsToDelete: Collection<KtExpression>
|
val initializationStatementsToDelete: Collection<KtExpression>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -91,52 +86,47 @@ fun match(loop: KtForExpression): MatchResult? {
|
|||||||
|
|
||||||
val restContainsEmbeddedBreakOrContinue = loopContainsEmbeddedBreakOrContinue && state.statements.any { it.containsEmbeddedBreakOrContinue() }
|
val restContainsEmbeddedBreakOrContinue = loopContainsEmbeddedBreakOrContinue && state.statements.any { it.containsEmbeddedBreakOrContinue() }
|
||||||
|
|
||||||
for (matcher in MatcherRegistrar.resultMatchers) {
|
MatchersLoop@
|
||||||
if (state.indexVariable != null && !matcher.indexVariableUsePossible) continue
|
for (matcher in MatcherRegistrar.matchers) {
|
||||||
if (restContainsEmbeddedBreakOrContinue && !matcher.embeddedBreakOrContinuePossible) continue
|
if (state.indexVariable != null && !matcher.indexVariableAllowed) continue
|
||||||
|
|
||||||
val match = matcher.match(state)
|
val match = matcher.match(state)
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
if (!inputVariableUsed
|
if (!inputVariableUsed && match.allTransformations.any { it.shouldUseInputVariable }) return null
|
||||||
&& (match.sequenceTransformations.any { it.shouldUseInputVariable }
|
|
||||||
|| match.resultTransformation.shouldUseInputVariable)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
sequenceTransformations.addAll(match.sequenceTransformations)
|
sequenceTransformations.addAll(match.sequenceTransformations)
|
||||||
return ResultTransformationMatch(match.resultTransformation, sequenceTransformations)
|
|
||||||
.let { mergeTransformations(it) }
|
|
||||||
.let { MatchResult(sequenceExpression, it, state.initializationStatementsToDelete) }
|
|
||||||
.check { checkSmartCastsPreserved(loop, it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (matcher in MatcherRegistrar.sequenceMatchers) {
|
when (match) {
|
||||||
val match = matcher.match(state)
|
is TransformationMatch.Sequence -> {
|
||||||
if (match != null) {
|
// check that old input variable is not needed anymore
|
||||||
if (!inputVariableUsed && match.transformations.any { it.shouldUseInputVariable }) {
|
var newState = match.newState
|
||||||
return null
|
if (state.inputVariable != newState.inputVariable && state.inputVariable.hasUsages(newState.statements)) return null
|
||||||
|
|
||||||
|
if (state.indexVariable != null && match.sequenceTransformations.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restContainsEmbeddedBreakOrContinue && !matcher.embeddedBreakOrContinuePossible) {
|
||||||
|
val countBefore = state.statements.sumBy { it.countEmbeddedBreaksAndContinues() }
|
||||||
|
val countAfter = newState.statements.sumBy { it.countEmbeddedBreaksAndContinues() }
|
||||||
|
if (countAfter != countBefore) continue@MatchersLoop // some embedded break or continue in the matched part
|
||||||
|
}
|
||||||
|
|
||||||
|
state = newState
|
||||||
|
continue@MatchLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
is TransformationMatch.Result -> {
|
||||||
|
if (restContainsEmbeddedBreakOrContinue && !matcher.embeddedBreakOrContinuePossible) continue@MatchersLoop
|
||||||
|
|
||||||
|
return TransformationMatch.Result(match.resultTransformation, sequenceTransformations)
|
||||||
|
.let { mergeTransformations(it) }
|
||||||
|
.let { MatchResult(sequenceExpression, it, state.initializationStatementsToDelete) }
|
||||||
|
.check { checkSmartCastsPreserved(loop, it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that old input variable is not needed anymore
|
|
||||||
var newState = match.newState
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (restContainsEmbeddedBreakOrContinue && !matcher.embeddedBreakOrContinuePossible) {
|
|
||||||
val countBefore = state.statements.sumBy { it.countEmbeddedBreaksAndContinues() }
|
|
||||||
val countAfter = newState.statements.sumBy { it.countEmbeddedBreaksAndContinues() }
|
|
||||||
if (countAfter != countBefore) continue // some embedded break or continue in the matched part
|
|
||||||
}
|
|
||||||
|
|
||||||
sequenceTransformations.addAll(match.transformations)
|
|
||||||
state = newState
|
|
||||||
continue@MatchLoop
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,7 +269,7 @@ private fun MatchResult.generateCallChain(loop: KtForExpression): KtExpression {
|
|||||||
return callChain
|
return callChain
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mergeTransformations(match: ResultTransformationMatch): ResultTransformationMatch {
|
private fun mergeTransformations(match: TransformationMatch.Result): TransformationMatch.Result {
|
||||||
val transformations = ArrayList<Transformation>().apply { addAll(match.sequenceTransformations); add(match.resultTransformation) }
|
val transformations = ArrayList<Transformation>().apply { addAll(match.sequenceTransformations); add(match.resultTransformation) }
|
||||||
|
|
||||||
var anyChange: Boolean
|
var anyChange: Boolean
|
||||||
@@ -302,6 +292,6 @@ private fun mergeTransformations(match: ResultTransformationMatch): ResultTransf
|
|||||||
} while(anyChange)
|
} while(anyChange)
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return ResultTransformationMatch(transformations.last() as ResultTransformation, transformations.dropLast(1) as List<SequenceTransformation>)
|
return TransformationMatch.Result(transformations.last() as ResultTransformation, transformations.dropLast(1) as List<SequenceTransformation>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -84,11 +84,11 @@ class AddToCollectionTransformation(
|
|||||||
* collection.add(...)
|
* collection.add(...)
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
object Matcher : ResultTransformationMatcher {
|
object Matcher : TransformationMatcher {
|
||||||
override val indexVariableUsePossible: Boolean
|
override val indexVariableAllowed: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
override fun match(state: MatchingState): TransformationMatch.Result? {
|
||||||
val statement = state.statements.singleOrNull() ?: return null
|
val statement = state.statements.singleOrNull() ?: return null
|
||||||
//TODO: it can be implicit 'this' too
|
//TODO: it can be implicit 'this' too
|
||||||
val qualifiedExpression = statement as? KtDotQualifiedExpression ?: return null
|
val qualifiedExpression = statement as? KtDotQualifiedExpression ?: return null
|
||||||
@@ -108,11 +108,11 @@ class AddToCollectionTransformation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.indexVariable == null && argumentValue.isVariableReference(state.inputVariable)) {
|
if (state.indexVariable == null && argumentValue.isVariableReference(state.inputVariable)) {
|
||||||
return ResultTransformationMatch(AddToCollectionTransformation(state.outerLoop, targetCollection))
|
return TransformationMatch.Result(AddToCollectionTransformation(state.outerLoop, targetCollection))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//TODO: recognize "?: continue" in the argument
|
//TODO: recognize "?: continue" in the argument
|
||||||
return ResultTransformationMatch(MapToTransformation.create(
|
return TransformationMatch.Result(MapToTransformation.create(
|
||||||
state.outerLoop, state.inputVariable, state.indexVariable, targetCollection, argumentValue, mapNotNull = false))
|
state.outerLoop, state.inputVariable, state.indexVariable, targetCollection, argumentValue, mapNotNull = false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ class AddToCollectionTransformation(
|
|||||||
state: MatchingState,
|
state: MatchingState,
|
||||||
targetCollection: KtExpression,
|
targetCollection: KtExpression,
|
||||||
addOperationArgument: KtExpression
|
addOperationArgument: KtExpression
|
||||||
): ResultTransformationMatch? {
|
): TransformationMatch.Result? {
|
||||||
val collectionInitialization = targetCollection.detectInitializationBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) ?: return null
|
val collectionInitialization = targetCollection.detectInitializationBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) ?: return null
|
||||||
val collectionKind = collectionInitialization.initializer.isSimpleCollectionInstantiation() ?: return null
|
val collectionKind = collectionInitialization.initializer.isSimpleCollectionInstantiation() ?: return null
|
||||||
val argumentIsInputVariable = addOperationArgument.isVariableReference(state.inputVariable)
|
val argumentIsInputVariable = addOperationArgument.isVariableReference(state.inputVariable)
|
||||||
@@ -136,13 +136,13 @@ class AddToCollectionTransformation(
|
|||||||
val mapTransformation = MapTransformation(state.outerLoop, state.inputVariable, null, addOperationArgument, mapNotNull = false)
|
val mapTransformation = MapTransformation(state.outerLoop, state.inputVariable, null, addOperationArgument, mapNotNull = false)
|
||||||
AssignSequenceTransformationResultTransformation(mapTransformation, collectionInitialization)
|
AssignSequenceTransformationResultTransformation(mapTransformation, collectionInitialization)
|
||||||
}
|
}
|
||||||
return ResultTransformationMatch(transformation)
|
return TransformationMatch.Result(transformation)
|
||||||
}
|
}
|
||||||
|
|
||||||
canChangeInitializerType(collectionInitialization, KotlinBuiltIns.FQ_NAMES.mutableList, state.outerLoop) -> {
|
canChangeInitializerType(collectionInitialization, KotlinBuiltIns.FQ_NAMES.mutableList, state.outerLoop) -> {
|
||||||
if (argumentIsInputVariable) {
|
if (argumentIsInputVariable) {
|
||||||
val transformation = AssignToMutableListTransformation(state.outerLoop, collectionInitialization)
|
val transformation = AssignToMutableListTransformation(state.outerLoop, collectionInitialization)
|
||||||
return ResultTransformationMatch(transformation)
|
return TransformationMatch.Result(transformation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,11 +162,11 @@ class AddToCollectionTransformation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (argumentIsInputVariable) {
|
if (argumentIsInputVariable) {
|
||||||
return ResultTransformationMatch(assignToSetTransformation)
|
return TransformationMatch.Result(assignToSetTransformation)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val mapTransformation = MapTransformation(state.outerLoop, state.inputVariable, null, addOperationArgument, mapNotNull = false)
|
val mapTransformation = MapTransformation(state.outerLoop, state.inputVariable, null, addOperationArgument, mapNotNull = false)
|
||||||
return ResultTransformationMatch(assignToSetTransformation, mapTransformation)
|
return TransformationMatch.Result(assignToSetTransformation, mapTransformation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -68,11 +68,11 @@ class CountTransformation(
|
|||||||
* variable++ (or ++variable)
|
* variable++ (or ++variable)
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
object Matcher : ResultTransformationMatcher {
|
object Matcher : TransformationMatcher {
|
||||||
override val indexVariableUsePossible: Boolean
|
override val indexVariableAllowed: Boolean
|
||||||
get() = false
|
get() = false
|
||||||
|
|
||||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
override fun match(state: MatchingState): TransformationMatch.Result? {
|
||||||
val operand = state.statements.singleOrNull()?.isPlusPlusOf() ?: return null
|
val operand = state.statements.singleOrNull()?.isPlusPlusOf() ?: return null
|
||||||
val initialization = operand.detectInitializationBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) ?: return null
|
val initialization = operand.detectInitializationBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) ?: return null
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ class CountTransformation(
|
|||||||
if (!KotlinBuiltIns.isInt(variableType)) return null
|
if (!KotlinBuiltIns.isInt(variableType)) return null
|
||||||
|
|
||||||
val transformation = CountTransformation(state.outerLoop, state.inputVariable, initialization, null)
|
val transformation = CountTransformation(state.outerLoop, state.inputVariable, initialization, null)
|
||||||
return ResultTransformationMatch(transformation)
|
return TransformationMatch.Result(transformation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
-67
@@ -16,30 +16,21 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.intentions.loopToCallChain.result
|
package org.jetbrains.kotlin.idea.intentions.loopToCallChain.result
|
||||||
|
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.AssignToVariableResultTransformation
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.ChainedCallGenerator
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.FindOperatorGenerator
|
||||||
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.VariableInitialization
|
||||||
import org.jetbrains.kotlin.psi.KtBreakExpression
|
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.psi.KtForExpression
|
import org.jetbrains.kotlin.psi.KtForExpression
|
||||||
|
|
||||||
class FindAndAssignTransformation(
|
class FindAndAssignTransformation(
|
||||||
loop: KtForExpression,
|
loop: KtForExpression,
|
||||||
private val generator: FindOperatorGenerator,
|
private val generator: FindOperatorGenerator,
|
||||||
initialization: VariableInitialization,
|
initialization: VariableInitialization
|
||||||
private val filter: KtExpression? = null
|
|
||||||
) : AssignToVariableResultTransformation(loop, initialization) {
|
) : AssignToVariableResultTransformation(loop, initialization) {
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
override val presentation: String
|
override val presentation: String
|
||||||
get() = generator.functionName + (if (filter != null) "{}" else "()")
|
get() = generator.presentation
|
||||||
|
|
||||||
override val chainCallCount: Int
|
override val chainCallCount: Int
|
||||||
get() = generator.chainCallCount
|
get() = generator.chainCallCount
|
||||||
@@ -48,57 +39,6 @@ class FindAndAssignTransformation(
|
|||||||
get() = generator.shouldUseInputVariable
|
get() = generator.shouldUseInputVariable
|
||||||
|
|
||||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
return generator.generate(chainedCallGenerator, filter)
|
return generator.generate(chainedCallGenerator)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Matches:
|
|
||||||
* val variable = ...
|
|
||||||
* for (...) {
|
|
||||||
* ...
|
|
||||||
* variable = ...
|
|
||||||
* break
|
|
||||||
* }
|
|
||||||
* or
|
|
||||||
* val variable = ...
|
|
||||||
* for (...) {
|
|
||||||
* ...
|
|
||||||
* variable = ...
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
object Matcher : ResultTransformationMatcher {
|
|
||||||
override val indexVariableUsePossible: Boolean
|
|
||||||
get() = false
|
|
||||||
|
|
||||||
override fun match(state: MatchingState): ResultTransformationMatch? {
|
|
||||||
when (state.statements.size) {
|
|
||||||
1 -> {}
|
|
||||||
|
|
||||||
2 -> {
|
|
||||||
val breakExpression = state.statements.last() as? KtBreakExpression ?: return null
|
|
||||||
if (breakExpression.targetLoop() != state.outerLoop) return null
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> return null
|
|
||||||
}
|
|
||||||
val findFirst = state.statements.size == 2
|
|
||||||
|
|
||||||
val binaryExpression = state.statements.first() as? KtBinaryExpression ?: return null
|
|
||||||
if (binaryExpression.operationToken != KtTokens.EQ) return null
|
|
||||||
val left = binaryExpression.left ?: return null
|
|
||||||
val right = binaryExpression.right ?: return null
|
|
||||||
|
|
||||||
val initialization = left.detectInitializationBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) ?: return null
|
|
||||||
|
|
||||||
if (initialization.variable.countUsages(state.outerLoop) != 1) return null // this should be the only usage of this variable inside the loop
|
|
||||||
|
|
||||||
// we do not try to convert anything if the initializer is not compile-time constant because of possible side-effects
|
|
||||||
if (!initialization.initializer.isConstant()) return null
|
|
||||||
|
|
||||||
val generator = buildFindOperationGenerator(right, initialization.initializer, state.inputVariable, findFirst) ?: return null
|
|
||||||
|
|
||||||
val transformation = FindAndAssignTransformation(state.outerLoop, generator, initialization)
|
|
||||||
return ResultTransformationMatch(transformation)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-39
@@ -17,7 +17,6 @@
|
|||||||
package org.jetbrains.kotlin.idea.intentions.loopToCallChain.result
|
package org.jetbrains.kotlin.idea.intentions.loopToCallChain.result
|
||||||
|
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation
|
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.psi.KtForExpression
|
import org.jetbrains.kotlin.psi.KtForExpression
|
||||||
import org.jetbrains.kotlin.psi.KtReturnExpression
|
import org.jetbrains.kotlin.psi.KtReturnExpression
|
||||||
@@ -26,21 +25,13 @@ import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange
|
|||||||
class FindAndReturnTransformation(
|
class FindAndReturnTransformation(
|
||||||
override val loop: KtForExpression,
|
override val loop: KtForExpression,
|
||||||
private val generator: FindOperatorGenerator,
|
private val generator: FindOperatorGenerator,
|
||||||
private val endReturn: KtReturnExpression,
|
private val endReturn: KtReturnExpression
|
||||||
private val filter: KtExpression? = null
|
|
||||||
) : ResultTransformation {
|
) : ResultTransformation {
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
override val commentSavingRange = PsiChildRange(loop.unwrapIfLabeled(), endReturn)
|
override val commentSavingRange = PsiChildRange(loop.unwrapIfLabeled(), endReturn)
|
||||||
|
|
||||||
override val presentation: String
|
override val presentation: String
|
||||||
get() = generator.functionName + (if (filter != null) "{}" else "()")
|
get() = generator.presentation
|
||||||
|
|
||||||
override val chainCallCount: Int
|
override val chainCallCount: Int
|
||||||
get() = generator.chainCallCount
|
get() = generator.chainCallCount
|
||||||
@@ -49,7 +40,7 @@ class FindAndReturnTransformation(
|
|||||||
get() = generator.shouldUseInputVariable
|
get() = generator.shouldUseInputVariable
|
||||||
|
|
||||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
return generator.generate(chainedCallGenerator, filter)
|
return generator.generate(chainedCallGenerator)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
|
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
|
||||||
@@ -57,31 +48,4 @@ class FindAndReturnTransformation(
|
|||||||
loop.deleteWithLabels()
|
loop.deleteWithLabels()
|
||||||
return endReturn
|
return endReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Matches:
|
|
||||||
* for (...) {
|
|
||||||
* ...
|
|
||||||
* return ...
|
|
||||||
* }
|
|
||||||
* return ...
|
|
||||||
*/
|
|
||||||
object Matcher : ResultTransformationMatcher {
|
|
||||||
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
|
|
||||||
|
|
||||||
val returnValueInLoop = returnInLoop.returnedExpression ?: return null
|
|
||||||
val returnValueAfterLoop = returnAfterLoop.returnedExpression ?: return null
|
|
||||||
|
|
||||||
val generator = buildFindOperationGenerator(returnValueInLoop, returnValueAfterLoop, state.inputVariable, findFirst = true) ?: return null
|
|
||||||
|
|
||||||
val transformation = FindAndReturnTransformation(state.outerLoop, generator, returnAfterLoop)
|
|
||||||
return ResultTransformationMatch(transformation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* 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.result
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.FilterTransformation
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtBreakExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtReturnExpression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches:
|
||||||
|
* val variable = ...
|
||||||
|
* for (...) {
|
||||||
|
* ...
|
||||||
|
* variable = ...
|
||||||
|
* break
|
||||||
|
* }
|
||||||
|
* or
|
||||||
|
* val variable = ...
|
||||||
|
* for (...) {
|
||||||
|
* ...
|
||||||
|
* variable = ...
|
||||||
|
* }
|
||||||
|
* or
|
||||||
|
* for (...) {
|
||||||
|
* ...
|
||||||
|
* return ...
|
||||||
|
* }
|
||||||
|
* return ...
|
||||||
|
*/
|
||||||
|
object FindTransformationMatcher : TransformationMatcher {
|
||||||
|
override val indexVariableAllowed: Boolean
|
||||||
|
get() = false
|
||||||
|
|
||||||
|
override fun match(state: MatchingState): TransformationMatch.Result? {
|
||||||
|
return matchWithFilterBefore(state, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun matchWithFilterBefore(state: MatchingState, filterTransformation: FilterTransformation?): TransformationMatch.Result? {
|
||||||
|
matchReturn(state, filterTransformation)?.let { return it }
|
||||||
|
|
||||||
|
when (state.statements.size) {
|
||||||
|
1 -> { }
|
||||||
|
|
||||||
|
2 -> {
|
||||||
|
val breakExpression = state.statements.last() as? KtBreakExpression ?: return null
|
||||||
|
if (breakExpression.targetLoop() != state.outerLoop) return null
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
val findFirst = state.statements.size == 2
|
||||||
|
|
||||||
|
val binaryExpression = state.statements.first() as? KtBinaryExpression ?: return null
|
||||||
|
if (binaryExpression.operationToken != KtTokens.EQ) return null
|
||||||
|
val left = binaryExpression.left ?: return null
|
||||||
|
val right = binaryExpression.right ?: return null
|
||||||
|
|
||||||
|
val initialization = left.detectInitializationBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) ?: return null
|
||||||
|
|
||||||
|
if (initialization.variable.countUsages(state.outerLoop) != 1) return null // this should be the only usage of this variable inside the loop
|
||||||
|
|
||||||
|
// we do not try to convert anything if the initializer is not compile-time constant because of possible side-effects
|
||||||
|
if (!initialization.initializer.isConstant()) return null
|
||||||
|
|
||||||
|
val generator = buildFindOperationGenerator(right, initialization.initializer, state.inputVariable, state.indexVariable, filterTransformation, findFirst)
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
val transformation = FindAndAssignTransformation(state.outerLoop, generator, initialization)
|
||||||
|
return TransformationMatch.Result(transformation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun matchReturn(state: MatchingState, filterTransformation: FilterTransformation?): TransformationMatch.Result? {
|
||||||
|
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
|
||||||
|
|
||||||
|
val returnValueInLoop = returnInLoop.returnedExpression ?: return null
|
||||||
|
val returnValueAfterLoop = returnAfterLoop.returnedExpression ?: return null
|
||||||
|
|
||||||
|
val generator = buildFindOperationGenerator(returnValueInLoop, returnValueAfterLoop,
|
||||||
|
state.inputVariable, state.indexVariable, filterTransformation, findFirst = true)
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
val transformation = FindAndReturnTransformation(state.outerLoop, generator, returnAfterLoop)
|
||||||
|
return TransformationMatch.Result(transformation)
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-3
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
|
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.*
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindTransformationMatcher
|
||||||
import org.jetbrains.kotlin.idea.intentions.negate
|
import org.jetbrains.kotlin.idea.intentions.negate
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
@@ -73,9 +74,14 @@ class FilterTransformation(
|
|||||||
* if (<condition>) break
|
* if (<condition>) break
|
||||||
* ...
|
* ...
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
|
* plus optionally consequent find operation (assignment or return)
|
||||||
*/
|
*/
|
||||||
object Matcher : SequenceTransformationMatcher {
|
object Matcher : TransformationMatcher {
|
||||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
override val indexVariableAllowed: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override fun match(state: MatchingState): TransformationMatch? {
|
||||||
// we merge filter transformations here instead of FilterTransformation.mergeWithPrevious() because of filterIndexed that won't merge otherwise
|
// we merge filter transformations here instead of FilterTransformation.mergeWithPrevious() because of filterIndexed that won't merge otherwise
|
||||||
|
|
||||||
var (transformation, currentState) = matchOneTransformation(state) ?: return null
|
var (transformation, currentState) = matchOneTransformation(state) ?: return null
|
||||||
@@ -93,9 +99,12 @@ class FilterTransformation(
|
|||||||
transformation = FilterTransformation(state.outerLoop, transformation.inputVariable, indexVariable, mergedCondition, isInverse = false) //TODO: build filterNot in some cases?
|
transformation = FilterTransformation(state.outerLoop, transformation.inputVariable, indexVariable, mergedCondition, isInverse = false) //TODO: build filterNot in some cases?
|
||||||
currentState = nextState
|
currentState = nextState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FindTransformationMatcher.matchWithFilterBefore(currentState, transformation)
|
||||||
|
?.let { return it }
|
||||||
}
|
}
|
||||||
|
|
||||||
return SequenceTransformationMatch(transformation, currentState)
|
return TransformationMatch.Sequence(transformation, currentState)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun matchOneTransformation(state: MatchingState): Pair<SequenceTransformation, MatchingState>? {
|
private fun matchOneTransformation(state: MatchingState): Pair<SequenceTransformation, MatchingState>? {
|
||||||
|
|||||||
+7
-4
@@ -49,8 +49,11 @@ class FlatMapTransformation(
|
|||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
object Matcher : SequenceTransformationMatcher {
|
object Matcher : TransformationMatcher {
|
||||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
override val indexVariableAllowed: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
override fun match(state: MatchingState): TransformationMatch.Sequence? {
|
||||||
val nestedLoop = state.statements.singleOrNull() as? KtForExpression ?: return null
|
val nestedLoop = state.statements.singleOrNull() as? KtForExpression ?: return null
|
||||||
|
|
||||||
val transform = nestedLoop.loopRange ?: return null
|
val transform = nestedLoop.loopRange ?: return null
|
||||||
@@ -73,7 +76,7 @@ class FlatMapTransformation(
|
|||||||
statements = listOf(nestedLoopBody),
|
statements = listOf(nestedLoopBody),
|
||||||
inputVariable = newInputVariable
|
inputVariable = newInputVariable
|
||||||
)
|
)
|
||||||
return SequenceTransformationMatch(listOf(mapIndexedTransformation, flatMapTransformation), newState)
|
return TransformationMatch.Sequence(listOf(mapIndexedTransformation, flatMapTransformation), newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
val transformation = FlatMapTransformation(state.outerLoop, state.inputVariable, transform)
|
val transformation = FlatMapTransformation(state.outerLoop, state.inputVariable, transform)
|
||||||
@@ -82,7 +85,7 @@ class FlatMapTransformation(
|
|||||||
statements = listOf(nestedLoopBody),
|
statements = listOf(nestedLoopBody),
|
||||||
inputVariable = newInputVariable
|
inputVariable = newInputVariable
|
||||||
)
|
)
|
||||||
return SequenceTransformationMatch(transformation, newState)
|
return TransformationMatch.Sequence(transformation, newState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+6
-4
@@ -22,9 +22,11 @@ import org.jetbrains.kotlin.psi.KtContinueExpression
|
|||||||
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||||
|
|
||||||
object IntroduceIndexMatcher : SequenceTransformationMatcher {
|
object IntroduceIndexMatcher : TransformationMatcher {
|
||||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
override val indexVariableAllowed: Boolean
|
||||||
if (state.indexVariable != null) return null // old index variable is still needed - cannot introduce another one
|
get() = false // old index variable is still needed - cannot introduce another one
|
||||||
|
|
||||||
|
override fun match(state: MatchingState): TransformationMatch.Sequence? {
|
||||||
if (state.statements.size < 2) return null
|
if (state.statements.size < 2) return null
|
||||||
val operand = state.statements.last().isPlusPlusOf() ?: return null
|
val operand = state.statements.last().isPlusPlusOf() ?: return null
|
||||||
val restStatements = state.statements.dropLast(1)
|
val restStatements = state.statements.dropLast(1)
|
||||||
@@ -52,6 +54,6 @@ object IntroduceIndexMatcher : SequenceTransformationMatcher {
|
|||||||
val newState = state.copy(statements = restStatements,
|
val newState = state.copy(statements = restStatements,
|
||||||
indexVariable = variable,
|
indexVariable = variable,
|
||||||
initializationStatementsToDelete = state.initializationStatementsToDelete + variableInitialization.initializationStatement)
|
initializationStatementsToDelete = state.initializationStatementsToDelete + variableInitialization.initializationStatement)
|
||||||
return SequenceTransformationMatch(emptyList(), newState)
|
return TransformationMatch.Sequence(emptyList(), newState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
-4
@@ -54,11 +54,14 @@ class MapTransformation(
|
|||||||
* ...
|
* ...
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
object Matcher : SequenceTransformationMatcher {
|
object Matcher : TransformationMatcher {
|
||||||
|
override val indexVariableAllowed: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
override val embeddedBreakOrContinuePossible: Boolean
|
override val embeddedBreakOrContinuePossible: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
override fun match(state: MatchingState): SequenceTransformationMatch? {
|
override fun match(state: MatchingState): TransformationMatch.Sequence? {
|
||||||
val declaration = state.statements.firstOrNull() as? KtProperty ?: return null //TODO: support multi-variables
|
val declaration = state.statements.firstOrNull() as? KtProperty ?: return null //TODO: support multi-variables
|
||||||
val initializer = declaration.initializer ?: return null
|
val initializer = declaration.initializer ?: return null
|
||||||
if (declaration.hasWriteUsages()) return null
|
if (declaration.hasWriteUsages()) return null
|
||||||
@@ -76,7 +79,7 @@ class MapTransformation(
|
|||||||
else
|
else
|
||||||
MapTransformation(state.outerLoop, state.inputVariable, null, mapping, mapNotNull = true)
|
MapTransformation(state.outerLoop, state.inputVariable, null, mapping, mapNotNull = true)
|
||||||
val newState = state.copy(statements = restStatements, inputVariable = declaration)
|
val newState = state.copy(statements = restStatements, inputVariable = declaration)
|
||||||
return SequenceTransformationMatch(transformation, newState)
|
return TransformationMatch.Sequence(transformation, newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initializer.containsEmbeddedBreakOrContinue()) return null
|
if (initializer.containsEmbeddedBreakOrContinue()) return null
|
||||||
@@ -86,7 +89,7 @@ class MapTransformation(
|
|||||||
else
|
else
|
||||||
MapTransformation(state.outerLoop, state.inputVariable, null, initializer, mapNotNull = false)
|
MapTransformation(state.outerLoop, state.inputVariable, null, initializer, mapNotNull = false)
|
||||||
val newState = state.copy(statements = restStatements, inputVariable = declaration)
|
val newState = state.copy(statements = restStatements, inputVariable = declaration)
|
||||||
return SequenceTransformationMatch(transformation, newState)
|
return TransformationMatch.Sequence(transformation, newState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// INTENTION_TEXT: "Replace with 'indexOfFirst{}'"
|
||||||
|
fun foo(list: List<String>) {
|
||||||
|
var result = -1
|
||||||
|
<caret>for ((index, s) in list.withIndex()) {
|
||||||
|
if (s.length > 0) {
|
||||||
|
result = index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// INTENTION_TEXT: "Replace with 'indexOfFirst{}'"
|
||||||
|
fun foo(list: List<String>) {
|
||||||
|
val <caret>result = list.indexOfFirst { it.length > 0 }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// INTENTION_TEXT: "Replace with 'indexOfFirst{}'"
|
||||||
|
fun foo(list: List<String>): Int {
|
||||||
|
<caret>for ((index, s) in list.withIndex()) {
|
||||||
|
if (s.length > 0) {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// INTENTION_TEXT: "Replace with 'indexOfFirst{}'"
|
||||||
|
fun foo(list: List<String>): Int {
|
||||||
|
<caret>return list.indexOfFirst { it.length > 0 }
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// IS_APPLICABLE: false
|
||||||
|
fun foo(list: List<String>) {
|
||||||
|
var result = -1
|
||||||
|
<caret>for ((index, s) in list.withIndex()) {
|
||||||
|
if (s.length > index) {
|
||||||
|
result = index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// INTENTION_TEXT: "Replace with 'indexOfLast{}'"
|
||||||
|
fun foo(list: List<String>) {
|
||||||
|
var result = -1
|
||||||
|
<caret>for ((index, s) in list.withIndex()) {
|
||||||
|
if (s.length > 0) {
|
||||||
|
result = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// INTENTION_TEXT: "Replace with 'indexOfLast{}'"
|
||||||
|
fun foo(list: List<String>) {
|
||||||
|
val <caret>result = list.indexOfLast { it.length > 0 }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user