From d0f6e25783c3fb9ee381b3431dd84e56051e0882 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Thu, 18 Aug 2016 20:19:35 +0300 Subject: [PATCH] sum and sumBy supported --- .../intentions/loopToCallChain/commonUtils.kt | 10 ++ .../loopToCallChain/matchAndConvert.kt | 1 + .../result/CountTransformation.kt | 2 +- .../result/MaxOrMinTransformation.kt | 7 +- .../result/SumTransformation.kt | 170 ++++++++++++++++++ .../idea/intentions/loopToCallChain/utils.kt | 2 +- .../intentions/loopToCallChain/sum/bytes.kt | 10 ++ .../loopToCallChain/sum/bytes.kt.after | 7 + .../intentions/loopToCallChain/sum/doubles.kt | 10 ++ .../loopToCallChain/sum/doubles.kt.after | 7 + .../intentions/loopToCallChain/sum/floats.kt | 10 ++ .../loopToCallChain/sum/floats.kt.after | 7 + .../loopToCallChain/sum/floatsIntoDouble.kt | 10 ++ .../sum/floatsIntoDouble.kt.after | 7 + .../loopToCallChain/sum/indexUsed.kt | 10 ++ .../loopToCallChain/sum/indexUsed.kt.after | 9 + .../loopToCallChain/sum/indexUsed.kt.after2 | 10 ++ .../loopToCallChain/sum/indexUsed2.kt | 12 ++ .../loopToCallChain/sum/indexUsed2.kt.after | 11 ++ .../loopToCallChain/sum/indexUsed2.kt.after2 | 12 ++ .../intentions/loopToCallChain/sum/ints.kt | 10 ++ .../loopToCallChain/sum/ints.kt.after | 7 + .../intentions/loopToCallChain/sum/longs.kt | 10 ++ .../loopToCallChain/sum/longs.kt.after | 7 + .../loopToCallChain/sum/nonNumbers.kt | 14 ++ .../loopToCallChain/sum/nonZeroInitial.kt | 10 ++ .../sum/nonZeroInitial.kt.after | 7 + .../intentions/loopToCallChain/sum/short.kt | 10 ++ .../loopToCallChain/sum/short.kt.after | 7 + .../loopToCallChain/sum/sumByDouble.kt | 10 ++ .../loopToCallChain/sum/sumByDouble.kt.after | 7 + .../loopToCallChain/sum/sumByInts.kt | 10 ++ .../loopToCallChain/sum/sumByInts.kt.after | 7 + .../loopToCallChain/sum/sumByIntsIntoLong.kt | 10 ++ .../sum/sumByIntsIntoLong.kt.after | 9 + .../sum/sumByIntsIntoLong.kt.after2 | 10 ++ .../loopToCallChain/sum/sumByShorts.kt | 12 ++ .../loopToCallChain/sum/sumByShorts.kt.after | 9 + .../intentions/IntentionTest2Generated.java | 99 ++++++++++ .../intentions/IntentionTestGenerated.java | 99 ++++++++++ 40 files changed, 682 insertions(+), 6 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/SumTransformation.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/bytes.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/bytes.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/doubles.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/doubles.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/floats.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/floats.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/floatsIntoDouble.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/floatsIntoDouble.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/indexUsed.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/indexUsed.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/indexUsed.kt.after2 create mode 100644 idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt.after2 create mode 100644 idea/testData/intentions/loopToCallChain/sum/ints.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/ints.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/longs.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/longs.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/nonNumbers.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/nonZeroInitial.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/nonZeroInitial.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/short.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/short.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/sumByDouble.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/sumByDouble.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/sumByInts.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/sumByInts.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt.after2 create mode 100644 idea/testData/intentions/loopToCallChain/sum/sumByShorts.kt create mode 100644 idea/testData/intentions/loopToCallChain/sum/sumByShorts.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/commonUtils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/commonUtils.kt index 4be8a3e0f41..93235ecd331 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/commonUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/commonUtils.kt @@ -51,6 +51,16 @@ fun KtExpression?.isTrueConstant() fun KtExpression?.isFalseConstant() = this != null && node?.elementType == KtNodeTypes.BOOLEAN_CONSTANT && text == "false" +private val ZERO_VALUES = setOf(0, 0L, 0f, 0.0) + +fun KtExpression.isZeroConstant(): Boolean { + if (this !is KtConstantExpression) return false + val bindingContext = analyze(BodyResolveMode.PARTIAL) + val type = bindingContext.getType(this) ?: return false + val constant = ConstantExpressionEvaluator.getConstant(this, bindingContext) ?: return false + return constant.getValue(type) in ZERO_VALUES +} + fun KtExpression?.isVariableReference(variable: KtCallableDeclaration): Boolean { return this is KtNameReferenceExpression && this.mainReference.isReferenceTo(variable) } diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt index 71748f38c9b..14919692200 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/matchAndConvert.kt @@ -45,6 +45,7 @@ object MatcherRegistrar { FindTransformationMatcher, AddToCollectionTransformation.Matcher, CountTransformation.Matcher, + SumTransformationBase.Matcher, MaxOrMinTransformation.Matcher, IntroduceIndexMatcher, FilterTransformation.Matcher, diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/CountTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/CountTransformation.kt index 8f00c6e0649..31621214ab5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/CountTransformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/CountTransformation.kt @@ -49,7 +49,7 @@ class CountTransformation( chainedCallGenerator.generate("count()") } - if ((initialization.initializer as? KtConstantExpression)?.text == "0") { + if (initialization.initializer.isZeroConstant()) { return call } else { diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/MaxOrMinTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/MaxOrMinTransformation.kt index bdd27aeac6e..1130134ddf2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/MaxOrMinTransformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/MaxOrMinTransformation.kt @@ -100,9 +100,8 @@ class MaxOrMinTransformation( private fun matchMathMaxOrMin(state: MatchingState): TransformationMatch.Result? { val assignment = state.statements.singleOrNull() as? KtBinaryExpression ?: return null if (assignment.operationToken != KtTokens.EQ) return null - val assignmentTarget = assignment.left ?: return null - val variableInitialization = assignmentTarget.isVariableInitializedBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = false) + val variableInitialization = assignment.left.isVariableInitializedBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = false) ?: return null return matchMathMaxOrMin(variableInitialization, assignment, state, isMax = true) @@ -160,8 +159,8 @@ class MaxOrMinTransformation( return null } - val variableInitialization = (otherHand.isVariableInitializedBeforeLoop(loop, checkNoOtherUsagesInLoop = false) - ?: return null) + val variableInitialization = otherHand.isVariableInitializedBeforeLoop(loop, checkNoOtherUsagesInLoop = false) + ?: return null if (!assignmentTarget.isVariableReference(variableInitialization.variable)) return null diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/SumTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/SumTransformation.kt new file mode 100644 index 00000000000..35030555e16 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/SumTransformation.kt @@ -0,0 +1,170 @@ +/* + * 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.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.VariableDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.MapTransformation +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.KotlinType + +abstract class SumTransformationBase( + loop: KtForExpression, + initialization: VariableInitialization +) : AssignToVariableResultTransformation(loop, initialization) { + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val call = generateCall(chainedCallGenerator) + + if (initialization.initializer.isZeroConstant()) { + return call + } + else { + return KtPsiFactory(call).createExpressionByPattern("$0 + $1", initialization.initializer, call) + } + } + + protected abstract fun generateCall(chainedCallGenerator: ChainedCallGenerator): KtExpression + + /** + * Matches: + * val variable = + * for (...) { + * ... + * variable += + * } + */ + object Matcher : TransformationMatcher { + override val indexVariableAllowed: Boolean + get() = true + + override fun match(state: MatchingState): TransformationMatch.Result? { + val statement = state.statements.singleOrNull() as? KtBinaryExpression ?: return null + if (statement.operationToken != KtTokens.PLUSEQ) return null + + val variableInitialization = statement.left.isVariableInitializedBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = true) + ?: return null + + val value = statement.right ?: return null + + val valueType = value.analyze(BodyResolveMode.PARTIAL).getType(value)?.toSupportedType() ?: return null + val sumType = (variableInitialization.variable.resolveToDescriptorIfAny() as? VariableDescriptor)?.type?.toSupportedType() ?: return null + + val conversionFunctionName = when (sumType) { + SupportedType.INT -> { + val needConversion = when (valueType) { + SupportedType.INT -> false + + SupportedType.BYTE, SupportedType.SHORT -> { + // we don't need conversion to Int to use "sum" function but need it for "sumBy" + !value.isVariableReference(state.inputVariable) && state.indexVariable == null + } + + else -> true + } + if (needConversion) "toInt" else null + } + + SupportedType.LONG -> if (valueType != SupportedType.LONG) "toLong" else null + + SupportedType.FLOAT -> if (valueType != SupportedType.FLOAT) "toFloat" else null + + SupportedType.DOUBLE -> if (valueType != SupportedType.DOUBLE) "toDouble" else null + + SupportedType.BYTE, SupportedType.SHORT -> return null // cannot use sum or sumBy to get Byte or Short result + } + + val byExpression = if (conversionFunctionName != null) + KtPsiFactory(value).createExpressionByPattern("$0.$conversionFunctionName()", value) + else + value + + if (byExpression.isVariableReference(state.inputVariable)) { + val transformation = SumTransformation(state.outerLoop, variableInitialization) + return TransformationMatch.Result(transformation) + } + + if (state.indexVariable != null) { + val mapTransformation = MapTransformation(state.outerLoop, state.inputVariable, state.indexVariable, byExpression, mapNotNull = false) + val sumTransformation = SumTransformation(state.outerLoop, variableInitialization) + return TransformationMatch.Result(sumTransformation, mapTransformation) + } + + val sumByFunctionName = when (sumType) { + SupportedType.INT -> "sumBy" + + SupportedType.DOUBLE -> "sumByDouble" + + else -> { + val mapTransformation = MapTransformation(state.outerLoop, state.inputVariable, null, byExpression, mapNotNull = false) + val sumTransformation = SumTransformation(state.outerLoop, variableInitialization) + return TransformationMatch.Result(sumTransformation, mapTransformation) + } + } + + val transformation = SumByTransformation(state.outerLoop, variableInitialization, state.inputVariable, byExpression, sumByFunctionName) + return TransformationMatch.Result(transformation) + } + + private enum class SupportedType { + INT, LONG, SHORT, BYTE, DOUBLE, FLOAT + } + + private fun KotlinType.toSupportedType(): SupportedType? { + return when { + KotlinBuiltIns.isInt(this) -> SupportedType.INT + KotlinBuiltIns.isLong(this) -> SupportedType.LONG + KotlinBuiltIns.isShort(this) -> SupportedType.SHORT + KotlinBuiltIns.isByte(this) -> SupportedType.BYTE + KotlinBuiltIns.isDouble(this) -> SupportedType.DOUBLE + KotlinBuiltIns.isFloat(this) -> SupportedType.FLOAT + else -> null + } + } + } +} + +class SumTransformation(loop: KtForExpression, initialization: VariableInitialization) : SumTransformationBase(loop, initialization) { + override val presentation: String + get() = "sum()" + + override fun generateCall(chainedCallGenerator: ChainedCallGenerator): KtExpression { + return chainedCallGenerator.generate("sum()") + } +} + +class SumByTransformation( + loop: KtForExpression, + initialization: VariableInitialization, + private val inputVariable: KtCallableDeclaration, + private val byExpression: KtExpression, + private val functionName: String +) : SumTransformationBase(loop, initialization) { + + override val presentation: String + get() = "$functionName{}" + + override fun generateCall(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val lambda = generateLambda(inputVariable, byExpression) + return chainedCallGenerator.generate("$functionName $0:'{}'", lambda) + } +} \ No newline at end of file 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 df0b1174a11..d23e6d0fa87 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/utils.kt @@ -141,7 +141,7 @@ data class VariableInitialization( val initializer: KtExpression) //TODO: we need more correctness checks (if variable is non-local or is local but can be changed by some local functions) -fun KtExpression.isVariableInitializedBeforeLoop( +fun KtExpression?.isVariableInitializedBeforeLoop( loop: KtForExpression, checkNoOtherUsagesInLoop: Boolean ): VariableInitialization? { diff --git a/idea/testData/intentions/loopToCallChain/sum/bytes.kt b/idea/testData/intentions/loopToCallChain/sum/bytes.kt new file mode 100644 index 00000000000..ffe3319a03f --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/bytes.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + var s = 0 + for (item in list) { + s += item + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/bytes.kt.after b/idea/testData/intentions/loopToCallChain/sum/bytes.kt.after new file mode 100644 index 00000000000..dd2549c3e29 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/bytes.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + val s = list.sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/doubles.kt b/idea/testData/intentions/loopToCallChain/sum/doubles.kt new file mode 100644 index 00000000000..2f22b905298 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/doubles.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Double { + var s = 0.0 + for (item in list) { + s += item + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/doubles.kt.after b/idea/testData/intentions/loopToCallChain/sum/doubles.kt.after new file mode 100644 index 00000000000..be8035d4b95 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/doubles.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Double { + val s = list.sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/floats.kt b/idea/testData/intentions/loopToCallChain/sum/floats.kt new file mode 100644 index 00000000000..0df1f5984ac --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/floats.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Float { + var s = 0.0f + for (item in list) { + s += item + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/floats.kt.after b/idea/testData/intentions/loopToCallChain/sum/floats.kt.after new file mode 100644 index 00000000000..2a9b392c301 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/floats.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Float { + val s = list.sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/floatsIntoDouble.kt b/idea/testData/intentions/loopToCallChain/sum/floatsIntoDouble.kt new file mode 100644 index 00000000000..05b99c811b1 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/floatsIntoDouble.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sumByDouble{}'" +// IS_APPLICABLE_2: false +fun foo(list: List): Double { + var s = 0.0 + for (item in list) { + s += item + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/floatsIntoDouble.kt.after b/idea/testData/intentions/loopToCallChain/sum/floatsIntoDouble.kt.after new file mode 100644 index 00000000000..ae545304576 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/floatsIntoDouble.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sumByDouble{}'" +// IS_APPLICABLE_2: false +fun foo(list: List): Double { + val s = list.sumByDouble { it.toDouble() } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/indexUsed.kt b/idea/testData/intentions/loopToCallChain/sum/indexUsed.kt new file mode 100644 index 00000000000..ebdb56c0706 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/indexUsed.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'mapIndexed{}.sum()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().mapIndexed{}.sum()'" +fun foo(list: List): Int { + var s = 0 + for ((index, item) in list.withIndex()) { + s += item * index + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/indexUsed.kt.after b/idea/testData/intentions/loopToCallChain/sum/indexUsed.kt.after new file mode 100644 index 00000000000..117c1f04d9b --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/indexUsed.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'mapIndexed{}.sum()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().mapIndexed{}.sum()'" +fun foo(list: List): Int { + val s = list + .mapIndexed { index, item -> item * index } + .sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/indexUsed.kt.after2 b/idea/testData/intentions/loopToCallChain/sum/indexUsed.kt.after2 new file mode 100644 index 00000000000..06c80e7e589 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/indexUsed.kt.after2 @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'mapIndexed{}.sum()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().mapIndexed{}.sum()'" +fun foo(list: List): Int { + val s = list + .asSequence() + .mapIndexed { index, item -> item * index } + .sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt b/idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt new file mode 100644 index 00000000000..0e3ad5bdae0 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'mapIndexed{}.sum()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().mapIndexed{}.sum()'" +fun foo(list: List): Int { + var s = 0 + for ((index, item) in list.withIndex()) { + s += getShort(index, item) + } + return s +} + +fun getShort(index: Int, item: Any): Short = TODO() diff --git a/idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt.after b/idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt.after new file mode 100644 index 00000000000..7027cf9680c --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt.after @@ -0,0 +1,11 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'mapIndexed{}.sum()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().mapIndexed{}.sum()'" +fun foo(list: List): Int { + val s = list + .mapIndexed { index, item -> getShort(index, item) } + .sum() + return s +} + +fun getShort(index: Int, item: Any): Short = TODO() diff --git a/idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt.after2 b/idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt.after2 new file mode 100644 index 00000000000..8882a6d1899 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt.after2 @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'mapIndexed{}.sum()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().mapIndexed{}.sum()'" +fun foo(list: List): Int { + val s = list + .asSequence() + .mapIndexed { index, item -> getShort(index, item) } + .sum() + return s +} + +fun getShort(index: Int, item: Any): Short = TODO() diff --git a/idea/testData/intentions/loopToCallChain/sum/ints.kt b/idea/testData/intentions/loopToCallChain/sum/ints.kt new file mode 100644 index 00000000000..6a22a5fc9e6 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/ints.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + var s = 0 + for (item in list) { + s += item + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/ints.kt.after b/idea/testData/intentions/loopToCallChain/sum/ints.kt.after new file mode 100644 index 00000000000..87a22301848 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/ints.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + val s = list.sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/longs.kt b/idea/testData/intentions/loopToCallChain/sum/longs.kt new file mode 100644 index 00000000000..b760b170d45 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/longs.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Long { + var s = 0L + for (item in list) { + s += item + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/longs.kt.after b/idea/testData/intentions/loopToCallChain/sum/longs.kt.after new file mode 100644 index 00000000000..d2edbe1f1ab --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/longs.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Long { + val s = list.sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/nonNumbers.kt b/idea/testData/intentions/loopToCallChain/sum/nonNumbers.kt new file mode 100644 index 00000000000..c9b04019aee --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/nonNumbers.kt @@ -0,0 +1,14 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +// IS_APPLICABLE_2: false +class C + +operator fun C.plus(other: C): C = TODO() + +fun foo(list: List): C { + var s = C() + for (item in list) { + s += item + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/nonZeroInitial.kt b/idea/testData/intentions/loopToCallChain/sum/nonZeroInitial.kt new file mode 100644 index 00000000000..b69b11e7e02 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/nonZeroInitial.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + var s = 1 + for (item in list) { + s += item + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/nonZeroInitial.kt.after b/idea/testData/intentions/loopToCallChain/sum/nonZeroInitial.kt.after new file mode 100644 index 00000000000..8c9894a0044 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/nonZeroInitial.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + val s = 1 + list.sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/short.kt b/idea/testData/intentions/loopToCallChain/sum/short.kt new file mode 100644 index 00000000000..6de16a9daf7 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/short.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + var s = 0 + for (item in list) { + s += item + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/short.kt.after b/idea/testData/intentions/loopToCallChain/sum/short.kt.after new file mode 100644 index 00000000000..a57a1f9adc0 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/short.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sum()'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + val s = list.sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/sumByDouble.kt b/idea/testData/intentions/loopToCallChain/sum/sumByDouble.kt new file mode 100644 index 00000000000..14372fed43e --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/sumByDouble.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sumByDouble{}'" +// IS_APPLICABLE_2: false +fun foo(list: List): Double { + var s = 0.0 + for (item in list) { + s += item.length + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/sumByDouble.kt.after b/idea/testData/intentions/loopToCallChain/sum/sumByDouble.kt.after new file mode 100644 index 00000000000..9ad73c35146 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/sumByDouble.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sumByDouble{}'" +// IS_APPLICABLE_2: false +fun foo(list: List): Double { + val s = list.sumByDouble { it.length.toDouble() } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/sumByInts.kt b/idea/testData/intentions/loopToCallChain/sum/sumByInts.kt new file mode 100644 index 00000000000..e401e32ae94 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/sumByInts.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sumBy{}'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + var l = 0 + for (item in list) { + l += item.length + } + return l +} diff --git a/idea/testData/intentions/loopToCallChain/sum/sumByInts.kt.after b/idea/testData/intentions/loopToCallChain/sum/sumByInts.kt.after new file mode 100644 index 00000000000..48093c3afbd --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/sumByInts.kt.after @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sumBy{}'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + val l = list.sumBy { it.length } + return l +} diff --git a/idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt b/idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt new file mode 100644 index 00000000000..206f114ccc6 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.sum()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.sum()'" +fun foo(list: List): Long { + var s = 0L + for (item in list) { + s += item + } + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt.after b/idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt.after new file mode 100644 index 00000000000..71c9a6dd767 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.sum()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.sum()'" +fun foo(list: List): Long { + val s = list + .map { it.toLong() } + .sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt.after2 b/idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt.after2 new file mode 100644 index 00000000000..ddf3858af3a --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt.after2 @@ -0,0 +1,10 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.sum()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.sum()'" +fun foo(list: List): Long { + val s = list + .asSequence() + .map { it.toLong() } + .sum() + return s +} diff --git a/idea/testData/intentions/loopToCallChain/sum/sumByShorts.kt b/idea/testData/intentions/loopToCallChain/sum/sumByShorts.kt new file mode 100644 index 00000000000..5020637c4ca --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/sumByShorts.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sumBy{}'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + var l = 0 + for (item in list) { + l += item.getShort() + } + return l +} + +fun String.getShort(): Short = TODO() diff --git a/idea/testData/intentions/loopToCallChain/sum/sumByShorts.kt.after b/idea/testData/intentions/loopToCallChain/sum/sumByShorts.kt.after new file mode 100644 index 00000000000..89b36965fdb --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/sum/sumByShorts.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'sumBy{}'" +// IS_APPLICABLE_2: false +fun foo(list: List): Int { + val l = list.sumBy { it.getShort().toInt() } + return l +} + +fun String.getShort(): Short = TODO() diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java index d7e315c4828..e42107f245e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java @@ -1180,4 +1180,103 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/twoInitializationsBeforeLoop.kt"); doTest(fileName); } + + @TestMetadata("idea/testData/intentions/loopToCallChain/sum") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Sum extends AbstractIntentionTest2 { + public void testAllFilesPresentInSum() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/loopToCallChain/sum"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("bytes.kt") + public void testBytes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/bytes.kt"); + doTest(fileName); + } + + @TestMetadata("doubles.kt") + public void testDoubles() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/doubles.kt"); + doTest(fileName); + } + + @TestMetadata("floats.kt") + public void testFloats() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/floats.kt"); + doTest(fileName); + } + + @TestMetadata("floatsIntoDouble.kt") + public void testFloatsIntoDouble() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/floatsIntoDouble.kt"); + doTest(fileName); + } + + @TestMetadata("indexUsed.kt") + public void testIndexUsed() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/indexUsed.kt"); + doTest(fileName); + } + + @TestMetadata("indexUsed2.kt") + public void testIndexUsed2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt"); + doTest(fileName); + } + + @TestMetadata("ints.kt") + public void testInts() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/ints.kt"); + doTest(fileName); + } + + @TestMetadata("longs.kt") + public void testLongs() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/longs.kt"); + doTest(fileName); + } + + @TestMetadata("nonNumbers.kt") + public void testNonNumbers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/nonNumbers.kt"); + doTest(fileName); + } + + @TestMetadata("nonZeroInitial.kt") + public void testNonZeroInitial() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/nonZeroInitial.kt"); + doTest(fileName); + } + + @TestMetadata("short.kt") + public void testShort() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/short.kt"); + doTest(fileName); + } + + @TestMetadata("sumByDouble.kt") + public void testSumByDouble() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/sumByDouble.kt"); + doTest(fileName); + } + + @TestMetadata("sumByInts.kt") + public void testSumByInts() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/sumByInts.kt"); + doTest(fileName); + } + + @TestMetadata("sumByIntsIntoLong.kt") + public void testSumByIntsIntoLong() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt"); + doTest(fileName); + } + + @TestMetadata("sumByShorts.kt") + public void testSumByShorts() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/sumByShorts.kt"); + doTest(fileName); + } + } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index f903a2161b5..b3dfdda42e7 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -8340,6 +8340,105 @@ public class IntentionTestGenerated extends AbstractIntentionTest { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/twoInitializationsBeforeLoop.kt"); doTest(fileName); } + + @TestMetadata("idea/testData/intentions/loopToCallChain/sum") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Sum extends AbstractIntentionTest { + public void testAllFilesPresentInSum() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/loopToCallChain/sum"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("bytes.kt") + public void testBytes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/bytes.kt"); + doTest(fileName); + } + + @TestMetadata("doubles.kt") + public void testDoubles() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/doubles.kt"); + doTest(fileName); + } + + @TestMetadata("floats.kt") + public void testFloats() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/floats.kt"); + doTest(fileName); + } + + @TestMetadata("floatsIntoDouble.kt") + public void testFloatsIntoDouble() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/floatsIntoDouble.kt"); + doTest(fileName); + } + + @TestMetadata("indexUsed.kt") + public void testIndexUsed() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/indexUsed.kt"); + doTest(fileName); + } + + @TestMetadata("indexUsed2.kt") + public void testIndexUsed2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/indexUsed2.kt"); + doTest(fileName); + } + + @TestMetadata("ints.kt") + public void testInts() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/ints.kt"); + doTest(fileName); + } + + @TestMetadata("longs.kt") + public void testLongs() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/longs.kt"); + doTest(fileName); + } + + @TestMetadata("nonNumbers.kt") + public void testNonNumbers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/nonNumbers.kt"); + doTest(fileName); + } + + @TestMetadata("nonZeroInitial.kt") + public void testNonZeroInitial() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/nonZeroInitial.kt"); + doTest(fileName); + } + + @TestMetadata("short.kt") + public void testShort() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/short.kt"); + doTest(fileName); + } + + @TestMetadata("sumByDouble.kt") + public void testSumByDouble() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/sumByDouble.kt"); + doTest(fileName); + } + + @TestMetadata("sumByInts.kt") + public void testSumByInts() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/sumByInts.kt"); + doTest(fileName); + } + + @TestMetadata("sumByIntsIntoLong.kt") + public void testSumByIntsIntoLong() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/sumByIntsIntoLong.kt"); + doTest(fileName); + } + + @TestMetadata("sumByShorts.kt") + public void testSumByShorts() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/sum/sumByShorts.kt"); + doTest(fileName); + } + } } @TestMetadata("idea/testData/intentions/moveAssignmentToInitializer")