Initial support for "+=", filterTo and mapTo for collections
This commit is contained in:
@@ -25,6 +25,8 @@ import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange
|
|||||||
* An abstraction for generating a chained call that knows about receiver expression and handles proper formatting
|
* An abstraction for generating a chained call that knows about receiver expression and handles proper formatting
|
||||||
*/
|
*/
|
||||||
interface ChainedCallGenerator {
|
interface ChainedCallGenerator {
|
||||||
|
val receiver: KtExpression
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pattern pattern string for generating the part of the call to the right from the dot
|
* @param pattern pattern string for generating the part of the call to the right from the dot
|
||||||
*/
|
*/
|
||||||
@@ -56,7 +58,7 @@ interface ResultTransformation : Transformation {
|
|||||||
fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? = null
|
fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? = null
|
||||||
|
|
||||||
val commentSavingRange: PsiChildRange
|
val commentSavingRange: PsiChildRange
|
||||||
val commentRestoringRange: PsiChildRange
|
fun commentRestoringRange(convertLoopResult: KtExpression): PsiChildRange
|
||||||
|
|
||||||
fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression
|
fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.intellij.openapi.util.Key
|
|||||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
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.FindAndAssignTransformation
|
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindAndAssignTransformation
|
||||||
import org.jetbrains.kotlin.idea.intentions.loopToCallChain.result.FindAndReturnTransformation
|
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
|
||||||
@@ -46,7 +47,8 @@ object MatcherRegistrar {
|
|||||||
|
|
||||||
val resultMatchers: Collection<ResultTransformationMatcher> = listOf(
|
val resultMatchers: Collection<ResultTransformationMatcher> = listOf(
|
||||||
FindAndReturnTransformation.Matcher,
|
FindAndReturnTransformation.Matcher,
|
||||||
FindAndAssignTransformation.Matcher
|
FindAndAssignTransformation.Matcher,
|
||||||
|
AddToCollectionTransformation.Matcher
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +100,7 @@ fun convertLoop(loop: KtForExpression, matchResult: ResultTransformationMatch):
|
|||||||
|
|
||||||
val result = matchResult.resultTransformation.convertLoop(callChain)
|
val result = matchResult.resultTransformation.convertLoop(callChain)
|
||||||
|
|
||||||
commentSaver.restore(matchResult.resultTransformation.commentRestoringRange)
|
commentSaver.restore(matchResult.resultTransformation.commentRestoringRange(result))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -169,6 +171,9 @@ private fun ResultTransformationMatch.generateCallChain(loop: KtForExpression):
|
|||||||
|
|
||||||
val psiFactory = KtPsiFactory(loop)
|
val psiFactory = KtPsiFactory(loop)
|
||||||
val chainedCallGenerator = object : ChainedCallGenerator {
|
val chainedCallGenerator = object : ChainedCallGenerator {
|
||||||
|
override val receiver: KtExpression
|
||||||
|
get() = callChain
|
||||||
|
|
||||||
override fun generate(pattern: String, vararg args: Any): KtExpression {
|
override fun generate(pattern: String, vararg args: Any): KtExpression {
|
||||||
val newPattern = "\$${args.size}$lineBreak.$pattern"
|
val newPattern = "\$${args.size}$lineBreak.$pattern"
|
||||||
return psiFactory.createExpressionByPattern(newPattern, *args, callChain)
|
return psiFactory.createExpressionByPattern(newPattern, *args, callChain)
|
||||||
|
|||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression
|
||||||
|
|
||||||
|
class AddToCollectionTransformation(
|
||||||
|
loop: KtForExpression,
|
||||||
|
inputVariable: KtCallableDeclaration,
|
||||||
|
private val targetCollection: KtExpression
|
||||||
|
) : ReplaceLoopTransformation(loop, inputVariable) {
|
||||||
|
|
||||||
|
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
||||||
|
if (previousTransformation !is FilterTransformation) return null
|
||||||
|
return FilterToTransformation(loop, inputVariable, targetCollection, previousTransformation.effectiveCondition()) //TODO: use filterNotTo?
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
|
return KtPsiFactory(loop).createExpressionByPattern("$0 += $1", targetCollection, chainedCallGenerator.receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches:
|
||||||
|
* for (...) {
|
||||||
|
* ...
|
||||||
|
* collection.add(...)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
object Matcher : ResultTransformationMatcher {
|
||||||
|
override fun match(state: MatchingState): ResultTransformationMatch? {
|
||||||
|
//TODO: pass indexVariable as null if not used
|
||||||
|
if (state.indexVariable != null) return null
|
||||||
|
|
||||||
|
val statement = state.statements.singleOrNull() ?: return null
|
||||||
|
//TODO: it can be implicit 'this' too
|
||||||
|
val qualifiedExpression = statement as? KtDotQualifiedExpression ?: return null
|
||||||
|
val targetCollection = qualifiedExpression.receiverExpression
|
||||||
|
//TODO: check that receiver is stable!
|
||||||
|
val callExpression = qualifiedExpression.selectorExpression as? KtCallExpression ?: return null
|
||||||
|
if (callExpression.getCallNameExpression()?.getReferencedName() != "add") return null
|
||||||
|
//TODO: check that it's MutableCollection's add
|
||||||
|
val argument = callExpression.valueArguments.singleOrNull() ?: return null
|
||||||
|
val argumentValue = argument.getArgumentExpression() ?: return null
|
||||||
|
|
||||||
|
//TODO: if variable is initialized with new collection than generate toList(), toSet()
|
||||||
|
|
||||||
|
val transformation = if (argumentValue.isVariableReference(state.workingVariable)) {
|
||||||
|
AddToCollectionTransformation(state.outerLoop, state.workingVariable, targetCollection)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
MapToTransformation(state.outerLoop, state.workingVariable, targetCollection, argumentValue)
|
||||||
|
}
|
||||||
|
return ResultTransformationMatch(transformation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilterToTransformation(
|
||||||
|
loop: KtForExpression,
|
||||||
|
inputVariable: KtCallableDeclaration,
|
||||||
|
private val targetCollection: KtExpression,
|
||||||
|
private val filter: KtExpression
|
||||||
|
) : ReplaceLoopTransformation(loop, inputVariable) {
|
||||||
|
|
||||||
|
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
|
val lambda = generateLambda(inputVariable, filter)
|
||||||
|
return chainedCallGenerator.generate("filterTo($0) $1:'{}'", targetCollection, lambda)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MapToTransformation(
|
||||||
|
loop: KtForExpression,
|
||||||
|
inputVariable: KtCallableDeclaration,
|
||||||
|
private val targetCollection: KtExpression,
|
||||||
|
private val mapping: KtExpression
|
||||||
|
) : ReplaceLoopTransformation(loop, inputVariable) {
|
||||||
|
|
||||||
|
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
|
val lambda = generateLambda(inputVariable, mapping)
|
||||||
|
return chainedCallGenerator.generate("mapTo($0) $1:'{}'", targetCollection, lambda)
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-2
@@ -38,11 +38,14 @@ class FindAndAssignTransformation(
|
|||||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
||||||
if (previousTransformation !is FilterTransformation) return null
|
if (previousTransformation !is FilterTransformation) return null
|
||||||
assert(filter == null) { "Should not happen because no 2 consecutive FilterTransformation's possible"}
|
assert(filter == null) { "Should not happen because no 2 consecutive FilterTransformation's possible"}
|
||||||
return FindAndAssignTransformation(loop, previousTransformation.inputVariable, generator, initialization, previousTransformation.buildRealCondition())
|
return FindAndAssignTransformation(loop, previousTransformation.inputVariable, generator, initialization, previousTransformation.effectiveCondition())
|
||||||
}
|
}
|
||||||
|
|
||||||
override val commentSavingRange = PsiChildRange(initialization.initializationStatement, loop.unwrapIfLabeled())
|
override val commentSavingRange = PsiChildRange(initialization.initializationStatement, loop.unwrapIfLabeled())
|
||||||
override val commentRestoringRange = commentSavingRange.withoutLastStatement()
|
|
||||||
|
private val commentRestoringRange = commentSavingRange.withoutLastStatement()
|
||||||
|
|
||||||
|
override fun commentRestoringRange(convertLoopResult: KtExpression) = commentRestoringRange
|
||||||
|
|
||||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
return generator(chainedCallGenerator, filter)
|
return generator(chainedCallGenerator, filter)
|
||||||
|
|||||||
+4
-2
@@ -35,12 +35,14 @@ class FindAndReturnTransformation(
|
|||||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? {
|
||||||
if (previousTransformation !is FilterTransformation) return null
|
if (previousTransformation !is FilterTransformation) return null
|
||||||
assert(filter == null) { "Should not happen because no 2 consecutive FilterTransformation's possible"}
|
assert(filter == null) { "Should not happen because no 2 consecutive FilterTransformation's possible"}
|
||||||
return FindAndReturnTransformation(loop, previousTransformation.inputVariable, generator, endReturn, previousTransformation.buildRealCondition())
|
return FindAndReturnTransformation(loop, previousTransformation.inputVariable, generator, endReturn, previousTransformation.effectiveCondition())
|
||||||
}
|
}
|
||||||
|
|
||||||
override val commentSavingRange = PsiChildRange(loop.unwrapIfLabeled(), endReturn)
|
override val commentSavingRange = PsiChildRange(loop.unwrapIfLabeled(), endReturn)
|
||||||
|
|
||||||
override val commentRestoringRange = commentSavingRange.withoutFirstStatement()
|
private val commentRestoringRange = commentSavingRange.withoutFirstStatement()
|
||||||
|
|
||||||
|
override fun commentRestoringRange(convertLoopResult: KtExpression) = commentRestoringRange
|
||||||
|
|
||||||
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression {
|
||||||
return generator(chainedCallGenerator, filter)
|
return generator(chainedCallGenerator, filter)
|
||||||
|
|||||||
+11
-11
@@ -29,13 +29,13 @@ class FilterTransformation(
|
|||||||
val isInverse: Boolean
|
val isInverse: Boolean
|
||||||
) : SequenceTransformation {
|
) : SequenceTransformation {
|
||||||
|
|
||||||
fun buildRealCondition() = if (isInverse) condition.negate() else condition
|
fun effectiveCondition() = if (isInverse) condition.negate() else condition
|
||||||
|
|
||||||
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): SequenceTransformation? {
|
override fun mergeWithPrevious(previousTransformation: SequenceTransformation): SequenceTransformation? {
|
||||||
if (previousTransformation !is FilterTransformation) return null
|
if (previousTransformation !is FilterTransformation) return null
|
||||||
assert(previousTransformation.inputVariable == inputVariable)
|
assert(previousTransformation.inputVariable == inputVariable)
|
||||||
val mergedCondition = KtPsiFactory(condition).createExpressionByPattern(
|
val mergedCondition = KtPsiFactory(condition).createExpressionByPattern(
|
||||||
"$0 && $1", previousTransformation.buildRealCondition(), buildRealCondition())
|
"$0 && $1", previousTransformation.effectiveCondition(), effectiveCondition())
|
||||||
return FilterTransformation(inputVariable, mergedCondition, isInverse = false) //TODO: build filterNot in some cases?
|
return FilterTransformation(inputVariable, mergedCondition, isInverse = false) //TODO: build filterNot in some cases?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,22 +92,22 @@ class FilterTransformation(
|
|||||||
condition: KtExpression,
|
condition: KtExpression,
|
||||||
isInverse: Boolean): SequenceTransformation {
|
isInverse: Boolean): SequenceTransformation {
|
||||||
|
|
||||||
val realCondition = if (isInverse) condition.negate() else condition
|
val effectiveCondition = if (isInverse) condition.negate() else condition
|
||||||
|
|
||||||
if (realCondition is KtIsExpression
|
if (effectiveCondition is KtIsExpression
|
||||||
&& !realCondition.isNegated
|
&& !effectiveCondition.isNegated
|
||||||
&& realCondition.leftHandSide.isSimpleName(inputVariable.nameAsSafeName) // we cannot use isVariableReference here because expression can be non-physical
|
&& effectiveCondition.leftHandSide.isSimpleName(inputVariable.nameAsSafeName) // we cannot use isVariableReference here because expression can be non-physical
|
||||||
) {
|
) {
|
||||||
val typeRef = realCondition.typeReference
|
val typeRef = effectiveCondition.typeReference
|
||||||
if (typeRef != null) {
|
if (typeRef != null) {
|
||||||
return FilterIsInstanceTransformation(inputVariable, typeRef)
|
return FilterIsInstanceTransformation(inputVariable, typeRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (realCondition is KtBinaryExpression
|
if (effectiveCondition is KtBinaryExpression
|
||||||
&& realCondition.operationToken == KtTokens.EXCLEQ
|
&& effectiveCondition.operationToken == KtTokens.EXCLEQ
|
||||||
&& realCondition.right.isNullExpression()
|
&& effectiveCondition.right.isNullExpression()
|
||||||
&& realCondition.left.isSimpleName(inputVariable.nameAsSafeName)
|
&& effectiveCondition.left.isSimpleName(inputVariable.nameAsSafeName)
|
||||||
) {
|
) {
|
||||||
return FilterNotNullTransformation(inputVariable)
|
return FilterNotNullTransformation(inputVariable)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import org.jetbrains.kotlin.name.Name
|
|||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
|
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
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
|
||||||
@@ -257,3 +256,17 @@ fun KtExpression.detectInitializationBeforeLoop(loop: KtForExpression): Variable
|
|||||||
val initializer = assignment.right ?: return null
|
val initializer = assignment.right ?: return null
|
||||||
return VariableInitialization(variable, assignment, initializer)
|
return VariableInitialization(variable, assignment, initializer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class ReplaceLoopTransformation(
|
||||||
|
protected val loop: KtForExpression,
|
||||||
|
override val inputVariable: KtCallableDeclaration
|
||||||
|
): ResultTransformation {
|
||||||
|
|
||||||
|
override val commentSavingRange = PsiChildRange.singleElement(loop.unwrapIfLabeled())
|
||||||
|
|
||||||
|
override fun commentRestoringRange(convertLoopResult: KtExpression) = PsiChildRange.singleElement(convertLoopResult)
|
||||||
|
|
||||||
|
override fun convertLoop(resultCallChain: KtExpression): KtExpression {
|
||||||
|
return loop.unwrapIfLabeled().replaced(resultCallChain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo(list: List<String>, target: MutableList<String>) {
|
||||||
|
<caret>for (s in list) {
|
||||||
|
target.add(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo(list: List<String>, target: MutableList<String>) {
|
||||||
|
<caret>target += list
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo(list: List<String>, target: MutableList<String>) {
|
||||||
|
<caret>for (s in list) {
|
||||||
|
if (s.length > 0)
|
||||||
|
target.add(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo(list: List<String>, target: MutableList<String>) {
|
||||||
|
list.filterTo(target) { it.length > 0 }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo(list: List<String>, target: MutableList<Int>) {
|
||||||
|
<caret>for (s in list) {
|
||||||
|
if (s.length > 0)
|
||||||
|
target.add(s.hashCode())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo(list: List<String>, target: MutableList<Int>) {
|
||||||
|
<caret>list
|
||||||
|
.filter { it.length > 0 }
|
||||||
|
.mapTo(target) { it.hashCode() }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user