From f51c5a19dd3bb83b51ddb21db38ad70561b3cf80 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 6 Apr 2016 19:20:08 +0300 Subject: [PATCH] Basic support for "add" to collection used inside the loop --- .../intentions/loopToCallChain/interfaces.kt | 13 ++-- .../result/AddToCollectionTransformation.kt | 74 +++++++++++++++++-- .../result/FindAndAssignTransformation.kt | 26 +------ .../result/FindAndReturnTransformation.kt | 2 +- .../sequence/FilterTransformation.kt | 16 ++-- .../sequence/FlatMapTransformation.kt | 3 +- .../sequence/MapTransformation.kt | 4 +- .../idea/intentions/loopToCallChain/utils.kt | 66 ++++++++++++++++- .../loopToCallChain/assignFilter.kt | 12 +++ .../loopToCallChain/assignFilter.kt.after | 7 ++ .../intentions/loopToCallChain/assignMap.kt | 11 +++ .../loopToCallChain/assignMap.kt.after | 9 +++ .../resultCollectionUsedInsideLoop.kt | 12 +++ .../resultCollectionUsedInsideLoop.kt.after | 10 +++ .../intentions/loopToCallChain/toList.kt | 10 +++ .../loopToCallChain/toList.kt.after | 7 ++ .../intentions/loopToCallChain/toSet.kt | 10 +++ .../intentions/loopToCallChain/toSet.kt.after | 7 ++ .../loopToCallChain/toSetWithMap.kt | 10 +++ .../loopToCallChain/toSetWithMap.kt.after | 9 +++ 20 files changed, 272 insertions(+), 46 deletions(-) create mode 100644 idea/testData/intentions/loopToCallChain/assignFilter.kt create mode 100644 idea/testData/intentions/loopToCallChain/assignFilter.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/assignMap.kt create mode 100644 idea/testData/intentions/loopToCallChain/assignMap.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt create mode 100644 idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/toList.kt create mode 100644 idea/testData/intentions/loopToCallChain/toList.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/toSet.kt create mode 100644 idea/testData/intentions/loopToCallChain/toSet.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/toSetWithMap.kt create mode 100644 idea/testData/intentions/loopToCallChain/toSetWithMap.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt index f88c17c93a2..ca042dd3464 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/interfaces.kt @@ -38,6 +38,9 @@ interface ChainedCallGenerator { */ interface Transformation { val inputVariable: KtCallableDeclaration + val loop: KtForExpression + + fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression } /** @@ -48,7 +51,6 @@ interface SequenceTransformation : Transformation { val affectsIndex: Boolean - fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression } /** @@ -60,8 +62,6 @@ interface ResultTransformation : Transformation { val commentSavingRange: PsiChildRange fun commentRestoringRange(convertLoopResult: KtExpression): PsiChildRange - fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression - fun convertLoop(resultCallChain: KtExpression): KtExpression } @@ -104,5 +104,8 @@ interface ResultTransformationMatcher { class ResultTransformationMatch( val resultTransformation: ResultTransformation, - val sequenceTransformations: List = listOf() -) + val sequenceTransformations: List +) { + constructor(resultTransformation: ResultTransformation, vararg sequenceTransformations: SequenceTransformation) + : this(resultTransformation, sequenceTransformations.asList()) +} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/AddToCollectionTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/AddToCollectionTransformation.kt index b224cc01df0..b825ba8543a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/AddToCollectionTransformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/AddToCollectionTransformation.kt @@ -19,6 +19,7 @@ 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.idea.intentions.loopToCallChain.sequence.FlatMapTransformation +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.MapTransformation import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression @@ -26,7 +27,7 @@ class AddToCollectionTransformation( loop: KtForExpression, inputVariable: KtCallableDeclaration, private val targetCollection: KtExpression -) : ReplaceLoopTransformation(loop, inputVariable) { +) : ReplaceLoopResultTransformation(loop, inputVariable) { override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? { return when (previousTransformation) { @@ -68,10 +69,42 @@ class AddToCollectionTransformation( //TODO: check that it's MutableCollection's add val argument = callExpression.valueArguments.singleOrNull() ?: return null val argumentValue = argument.getArgumentExpression() ?: return null + val argumentIsInputVariable = argumentValue.isVariableReference(state.inputVariable) - //TODO: if variable is initialized with new collection than generate toList(), toSet() + //TODO: collection can be used as mutable collection or even ArrayList! + val collectionInitialization = targetCollection.detectInitializationBeforeLoop(state.outerLoop) + if (collectionInitialization != null) { + val collectionKind = collectionInitialization.initializer.isSimpleCollectionInstantiation() + when (collectionKind) { + CollectionKind.LIST -> { + val transformation = if (argumentIsInputVariable) { + AssignToListTransformation(state.outerLoop, state.inputVariable, collectionInitialization) + } + else { + val mapTransformation = MapTransformation(state.outerLoop, state.inputVariable, argumentValue) + AssignSequenceTransformationResultTransformation(mapTransformation, collectionInitialization) + } + return ResultTransformationMatch(transformation) + } - val transformation = if (argumentValue.isVariableReference(state.inputVariable)) { + CollectionKind.SET -> { + if (argumentIsInputVariable) { + val transformation = AssignToSetTransformation(state.outerLoop, state.inputVariable, collectionInitialization) + return ResultTransformationMatch(transformation) + } + else { + val mapTransformation = MapTransformation(state.outerLoop, state.inputVariable, argumentValue) + val transformation = AssignToSetTransformation( + state.outerLoop, + state.inputVariable/*TODO: it's not correct and it looks like not all transformations should have inputVariable*/, + collectionInitialization) + return ResultTransformationMatch(transformation, mapTransformation) + } + } + } + } + + val transformation = if (argumentIsInputVariable) { AddToCollectionTransformation(state.outerLoop, state.inputVariable, targetCollection) } else { @@ -87,7 +120,7 @@ class FilterToTransformation( inputVariable: KtCallableDeclaration, private val targetCollection: KtExpression, private val filter: KtExpression -) : ReplaceLoopTransformation(loop, inputVariable) { +) : ReplaceLoopResultTransformation(loop, inputVariable) { override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { val lambda = generateLambda(inputVariable, filter) @@ -100,7 +133,7 @@ class MapToTransformation( inputVariable: KtCallableDeclaration, private val targetCollection: KtExpression, private val mapping: KtExpression -) : ReplaceLoopTransformation(loop, inputVariable) { +) : ReplaceLoopResultTransformation(loop, inputVariable) { override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { val lambda = generateLambda(inputVariable, mapping) @@ -113,10 +146,37 @@ class FlatMapToTransformation( inputVariable: KtCallableDeclaration, private val targetCollection: KtExpression, private val transform: KtExpression -) : ReplaceLoopTransformation(loop, inputVariable) { +) : ReplaceLoopResultTransformation(loop, inputVariable) { override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { val lambda = generateLambda(inputVariable, transform) return chainedCallGenerator.generate("flatMapTo($0) $1:'{}'", targetCollection, lambda) } -} \ No newline at end of file +} + +class AssignToListTransformation( + loop: KtForExpression, + inputVariable: KtCallableDeclaration, + initialization: VariableInitialization +) : AssignToVariableResultTransformation(loop, inputVariable, initialization) { + + override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? { + if (previousTransformation !is FilterTransformation) return null + return AssignSequenceTransformationResultTransformation(previousTransformation, initialization) + } + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + return chainedCallGenerator.generate("toList()") + } +} + +class AssignToSetTransformation( + loop: KtForExpression, + inputVariable: KtCallableDeclaration, + initialization: VariableInitialization +) : AssignToVariableResultTransformation(loop, inputVariable, initialization) { + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + return chainedCallGenerator.generate("toSet()") + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndAssignTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndAssignTransformation.kt index 486f6eafa5c..386cd18a312 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndAssignTransformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndAssignTransformation.kt @@ -23,17 +23,16 @@ 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.* -import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode class FindAndAssignTransformation( - private val loop: KtForExpression, - override val inputVariable: KtCallableDeclaration, + loop: KtForExpression, + inputVariable: KtCallableDeclaration, private val generator: (chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?) -> KtExpression, - private val initialization: VariableInitialization, + initialization: VariableInitialization, private val filter: KtExpression? = null -) : ResultTransformation { +) : AssignToVariableResultTransformation(loop, inputVariable, initialization) { override fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? { if (previousTransformation !is FilterTransformation) return null @@ -41,27 +40,10 @@ class FindAndAssignTransformation( return FindAndAssignTransformation(loop, previousTransformation.inputVariable, generator, initialization, previousTransformation.effectiveCondition()) } - override val commentSavingRange = PsiChildRange(initialization.initializationStatement, loop.unwrapIfLabeled()) - - private val commentRestoringRange = commentSavingRange.withoutLastStatement() - - override fun commentRestoringRange(convertLoopResult: KtExpression) = commentRestoringRange - override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { return generator(chainedCallGenerator, filter) } - override fun convertLoop(resultCallChain: KtExpression): KtExpression { - initialization.initializer.replace(resultCallChain) - loop.deleteWithLabels() - - if (!initialization.variable.hasWriteUsages()) { // change variable to 'val' if possible - initialization.variable.valOrVarKeyword.replace(KtPsiFactory(initialization.variable).createValKeyword()) - } - - return initialization.initializationStatement - } - /** * Matches: * val variable = ... diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndReturnTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndReturnTransformation.kt index 9c176237f35..e8b6b810a2e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndReturnTransformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/FindAndReturnTransformation.kt @@ -25,7 +25,7 @@ import org.jetbrains.kotlin.psi.KtReturnExpression import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange class FindAndReturnTransformation( - private val loop: KtForExpression, + override val loop: KtForExpression, override val inputVariable: KtCallableDeclaration, private val generator: (chainedCallGenerator: ChainedCallGenerator, filter: KtExpression?) -> KtExpression, private val endReturn: KtReturnExpression, diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt index 4e499884a25..f0b6e61a7dc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FilterTransformation.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.blockExpressionsOrSingle class FilterTransformation( + override val loop: KtForExpression, override val inputVariable: KtCallableDeclaration, val condition: KtExpression, val isInverse: Boolean @@ -36,7 +37,7 @@ class FilterTransformation( assert(previousTransformation.inputVariable == inputVariable) val mergedCondition = KtPsiFactory(condition).createExpressionByPattern( "$0 && $1", previousTransformation.effectiveCondition(), effectiveCondition()) - return FilterTransformation(inputVariable, mergedCondition, isInverse = false) //TODO: build filterNot in some cases? + return FilterTransformation(loop, inputVariable, mergedCondition, isInverse = false) //TODO: build filterNot in some cases? } override val affectsIndex: Boolean @@ -73,14 +74,14 @@ class FilterTransformation( val then = ifStatement.then ?: return null if (state.statements.size == 1) { - val transformation = createFilterTransformation(state.inputVariable, condition, isInverse = false) + val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, condition, isInverse = false) val newState = state.copy(statements = listOf(then)) return SequenceTransformationMatch(transformation, newState) } else { val continueExpression = then.blockExpressionsOrSingle().singleOrNull() as? KtContinueExpression ?: return null if (!continueExpression.isBreakOrContinueOfLoop(state.innerLoop)) return null - val transformation = createFilterTransformation(state.inputVariable, condition, isInverse = true) + val transformation = createFilterTransformation(state.outerLoop, state.inputVariable, condition, isInverse = true) val newState = state.copy(statements = state.statements.drop(1)) return SequenceTransformationMatch(transformation, newState) } @@ -88,6 +89,7 @@ class FilterTransformation( //TODO: choose filter or filterNot depending on condition private fun createFilterTransformation( + loop: KtForExpression, inputVariable: KtCallableDeclaration, condition: KtExpression, isInverse: Boolean): SequenceTransformation { @@ -100,7 +102,7 @@ class FilterTransformation( ) { val typeRef = effectiveCondition.typeReference if (typeRef != null) { - return FilterIsInstanceTransformation(inputVariable, typeRef) + return FilterIsInstanceTransformation(loop, inputVariable, typeRef) } } @@ -109,15 +111,16 @@ class FilterTransformation( && effectiveCondition.right.isNullExpression() && effectiveCondition.left.isSimpleName(inputVariable.nameAsSafeName) ) { - return FilterNotNullTransformation(inputVariable) + return FilterNotNullTransformation(loop, inputVariable) } - return FilterTransformation(inputVariable, condition, isInverse) + return FilterTransformation(loop, inputVariable, condition, isInverse) } } } class FilterIsInstanceTransformation( + override val loop: KtForExpression, override val inputVariable: KtCallableDeclaration, private val type: KtTypeReference ) : SequenceTransformation { @@ -131,6 +134,7 @@ class FilterIsInstanceTransformation( } class FilterNotNullTransformation( + override val loop: KtForExpression, override val inputVariable: KtCallableDeclaration ) : SequenceTransformation { diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt index 3a93c33d734..06a2fbd5a8e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/FlatMapTransformation.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.psi.KtForExpression import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode class FlatMapTransformation( + override val loop: KtForExpression, override val inputVariable: KtCallableDeclaration, val transform: KtExpression ) : SequenceTransformation { @@ -63,7 +64,7 @@ class FlatMapTransformation( val nestedLoopBody = nestedLoop.body ?: return null val newWorkingVariable = nestedLoop.loopParameter ?: return null - val transformation = FlatMapTransformation(state.inputVariable, transform) + val transformation = FlatMapTransformation(state.outerLoop, state.inputVariable, transform) val newState = state.copy( innerLoop = nestedLoop, statements = listOf(nestedLoopBody), diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/MapTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/MapTransformation.kt index 3a984e6407f..f3aed211e53 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/MapTransformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/sequence/MapTransformation.kt @@ -19,9 +19,11 @@ package org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* import org.jetbrains.kotlin.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtForExpression import org.jetbrains.kotlin.psi.KtProperty class MapTransformation( + override val loop: KtForExpression, override val inputVariable: KtCallableDeclaration, val mapping: KtExpression ) : SequenceTransformation { @@ -50,7 +52,7 @@ class MapTransformation( if (declaration.hasWriteUsages()) return null val restStatements = state.statements.drop(1) - val transformation = MapTransformation(state.inputVariable, initializer) + val transformation = MapTransformation(state.outerLoop, state.inputVariable, initializer) val newState = state.copy(statements = restStatements, inputVariable = declaration) return SequenceTransformationMatch(transformation, newState) } diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt index 1a6197f4bf5..5ca1561ac90 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt @@ -21,12 +21,14 @@ import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.idea.analysis.analyzeInContext import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isNullExpression import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.mainReference @@ -36,6 +38,7 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.types.typeUtil.TypeNullability import org.jetbrains.kotlin.types.typeUtil.nullability @@ -246,6 +249,9 @@ fun KtExpression.detectInitializationBeforeLoop(loop: KtForExpression): Variable val variable = this.mainReference.resolve() as? KtProperty ?: return null val statementBeforeLoop = loop.previousStatement() //TODO: support initialization not right before the loop + // do not allow any other usages of this variable inside the loop + if (ReferencesSearch.search(variable, LocalSearchScope(loop)).count() > 1) return null + if (statementBeforeLoop == variable) { val initializer = variable.initializer ?: return null return VariableInitialization(variable, variable, initializer) @@ -253,13 +259,14 @@ fun KtExpression.detectInitializationBeforeLoop(loop: KtForExpression): Variable val assignment = statementBeforeLoop?.asAssignment() ?: return null if (!assignment.left.isVariableReference(variable)) return null + val initializer = assignment.right ?: return null return VariableInitialization(variable, assignment, initializer) } -abstract class ReplaceLoopTransformation( - protected val loop: KtForExpression, - override val inputVariable: KtCallableDeclaration +abstract class ReplaceLoopResultTransformation( + override val loop: KtForExpression, + final override val inputVariable: KtCallableDeclaration ): ResultTransformation { override val commentSavingRange = PsiChildRange.singleElement(loop.unwrapIfLabeled()) @@ -270,3 +277,56 @@ abstract class ReplaceLoopTransformation( return loop.unwrapIfLabeled().replaced(resultCallChain) } } + +abstract class AssignToVariableResultTransformation( + override val loop: KtForExpression, + final override val inputVariable: KtCallableDeclaration, + protected val initialization: VariableInitialization +) : ResultTransformation { + + override val commentSavingRange = PsiChildRange(initialization.initializationStatement, loop.unwrapIfLabeled()) + + private val commentRestoringRange = commentSavingRange.withoutLastStatement() + + override fun commentRestoringRange(convertLoopResult: KtExpression) = commentRestoringRange + + override fun convertLoop(resultCallChain: KtExpression): KtExpression { + initialization.initializer.replace(resultCallChain) + loop.deleteWithLabels() + + if (initialization.variable.isVar && !initialization.variable.hasWriteUsages()) { // change variable to 'val' if possible + initialization.variable.valOrVarKeyword.replace(KtPsiFactory(initialization.variable).createValKeyword()) + } + + return initialization.initializationStatement + } +} + +class AssignSequenceTransformationResultTransformation( + private val sequenceTransformation: SequenceTransformation, + initialization: VariableInitialization +) : AssignToVariableResultTransformation(sequenceTransformation.loop, sequenceTransformation.inputVariable, initialization) { + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + return sequenceTransformation.generateCode(chainedCallGenerator) + } +} + +enum class CollectionKind { + LIST, SET/*, MAP*/ +} + +fun KtExpression.isSimpleCollectionInstantiation(): CollectionKind? { + val callExpression = this as? KtCallExpression ?: return null //TODO: it can be qualified too + if (callExpression.valueArguments.isNotEmpty()) return null + val bindingContext = callExpression.analyze(BodyResolveMode.PARTIAL) + val resolvedCall = callExpression.getResolvedCall(bindingContext) ?: return null + val constructorDescriptor = resolvedCall.resultingDescriptor as? ConstructorDescriptor ?: return null + val classDescriptor = constructorDescriptor.containingDeclaration + val classFqName = classDescriptor.importableFqName?.asString() + return when (classFqName) { + "java.util.ArrayList" -> CollectionKind.LIST + "java.util.HashSet", "java.util.LinkedHashSet" -> CollectionKind.SET + else -> null + } +} diff --git a/idea/testData/intentions/loopToCallChain/assignFilter.kt b/idea/testData/intentions/loopToCallChain/assignFilter.kt new file mode 100644 index 00000000000..4176eb06310 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/assignFilter.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +import java.util.ArrayList + +fun foo(list: List): List { + val result = ArrayList() + for (s in list) { + if (s.length > 0) { + result.add(s) + } + } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/assignFilter.kt.after b/idea/testData/intentions/loopToCallChain/assignFilter.kt.after new file mode 100644 index 00000000000..d9e1916c29d --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/assignFilter.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +import java.util.ArrayList + +fun foo(list: List): List { + val result = list.filter { it.length > 0 } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/assignMap.kt b/idea/testData/intentions/loopToCallChain/assignMap.kt new file mode 100644 index 00000000000..beb502fa41b --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/assignMap.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME +import java.util.ArrayList + +fun foo(list: List): List { + val result = ArrayList() + for (s in list) { + if (s.length > 0) + result.add(s.hashCode()) + } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/assignMap.kt.after b/idea/testData/intentions/loopToCallChain/assignMap.kt.after new file mode 100644 index 00000000000..0918a8afa72 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/assignMap.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +import java.util.ArrayList + +fun foo(list: List): List { + val result = list + .filter { it.length > 0 } + .map { it.hashCode() } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt b/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt new file mode 100644 index 00000000000..2545bbb4723 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +import java.util.ArrayList + +fun foo(list: List): List { + val result = ArrayList() + for (s in list) { + if (s.length > result.size) { + result.add(s.hashCode()) + } + } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt.after b/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt.after new file mode 100644 index 00000000000..94d73dd0600 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/resultCollectionUsedInsideLoop.kt.after @@ -0,0 +1,10 @@ +// WITH_RUNTIME +import java.util.ArrayList + +fun foo(list: List): List { + val result = ArrayList() + list + .filter { it.length > result.size } + .mapTo(result) { it.hashCode() } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/toList.kt b/idea/testData/intentions/loopToCallChain/toList.kt new file mode 100644 index 00000000000..919a46b47bc --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/toList.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +import java.util.ArrayList + +fun foo(map: Map): List { + val result = ArrayList() + for (s in map.values) { + result.add(s) + } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/toList.kt.after b/idea/testData/intentions/loopToCallChain/toList.kt.after new file mode 100644 index 00000000000..985d603ff6a --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/toList.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +import java.util.ArrayList + +fun foo(map: Map): List { + val result = map.values.toList() + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/toSet.kt b/idea/testData/intentions/loopToCallChain/toSet.kt new file mode 100644 index 00000000000..e7b09bb9dd9 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/toSet.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +import java.util.HashSet + +fun foo(map: Map): Collection { + val result = HashSet() + for (s in map.values) { + result.add(s) + } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/toSet.kt.after b/idea/testData/intentions/loopToCallChain/toSet.kt.after new file mode 100644 index 00000000000..990e3818f8c --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/toSet.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +import java.util.HashSet + +fun foo(map: Map): Collection { + val result = map.values.toSet() + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/toSetWithMap.kt b/idea/testData/intentions/loopToCallChain/toSetWithMap.kt new file mode 100644 index 00000000000..13da6a5e383 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/toSetWithMap.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +import java.util.HashSet + +fun foo(map: Map): Collection { + val result = HashSet() + for (s in map.values) { + result.add(s.length) + } + return result +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/toSetWithMap.kt.after b/idea/testData/intentions/loopToCallChain/toSetWithMap.kt.after new file mode 100644 index 00000000000..dd5e26c235b --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/toSetWithMap.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +import java.util.HashSet + +fun foo(map: Map): Collection { + val result = map.values + .map { it.length } + .toSet() + return result +} \ No newline at end of file