From 930f7960ead3d1842ca46cd3cc7248322682613a Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 17 Aug 2016 18:49:13 +0300 Subject: [PATCH] max()/minI() supported --- .../loopToCallChain/matchAndConvert.kt | 1 + .../result/MaxOrMinTransformation.kt | 86 +++++++++++++++++++ .../intentions/loopToCallChain/max1.kt | 15 ++++ .../intentions/loopToCallChain/max1.kt.after | 12 +++ .../intentions/loopToCallChain/max1.kt.after2 | 13 +++ .../intentions/loopToCallChain/max2.kt | 15 ++++ .../intentions/loopToCallChain/max2.kt.after | 12 +++ .../intentions/loopToCallChain/max2.kt.after2 | 13 +++ .../intentions/loopToCallChain/min1.kt | 15 ++++ .../intentions/loopToCallChain/min1.kt.after | 12 +++ .../intentions/loopToCallChain/min1.kt.after2 | 13 +++ .../intentions/loopToCallChain/min2.kt | 15 ++++ .../intentions/loopToCallChain/min2.kt.after | 12 +++ .../intentions/loopToCallChain/min2.kt.after2 | 13 +++ .../intentions/IntentionTest2Generated.java | 24 ++++++ .../intentions/IntentionTestGenerated.java | 24 ++++++ 16 files changed, 295 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/MaxOrMinTransformation.kt create mode 100644 idea/testData/intentions/loopToCallChain/max1.kt create mode 100644 idea/testData/intentions/loopToCallChain/max1.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/max1.kt.after2 create mode 100644 idea/testData/intentions/loopToCallChain/max2.kt create mode 100644 idea/testData/intentions/loopToCallChain/max2.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/max2.kt.after2 create mode 100644 idea/testData/intentions/loopToCallChain/min1.kt create mode 100644 idea/testData/intentions/loopToCallChain/min1.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/min1.kt.after2 create mode 100644 idea/testData/intentions/loopToCallChain/min2.kt create mode 100644 idea/testData/intentions/loopToCallChain/min2.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/min2.kt.after2 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 db5efec3930..71748f38c9b 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, + MaxOrMinTransformation.Matcher, IntroduceIndexMatcher, FilterTransformation.Matcher, MapTransformation.Matcher, 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 new file mode 100644 index 00000000000..e95d882975e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/MaxOrMinTransformation.kt @@ -0,0 +1,86 @@ +/* + * 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.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.blockExpressionsOrSingle + +class MaxOrMinTransformation( + loop: KtForExpression, + private val inputVariable: KtCallableDeclaration, + initialization: VariableInitialization, + private val isMax: Boolean +) : AssignToVariableResultTransformation(loop, initialization) { + + override val presentation: String + get() = if (isMax) "max()" else "min()" + + override fun generateCode(chainedCallGenerator: ChainedCallGenerator): KtExpression { + val call = chainedCallGenerator.generate(presentation) + return KtPsiFactory(call).createExpressionByPattern("$0\n ?: $1", call, initialization.initializer) + } + + /** + * Matches: + * val variable = + * for (...) { + * ... + * if (variable > ) { // or '<' or operands swapped + * variable = + * } + * } + */ + object Matcher : TransformationMatcher { + override val indexVariableAllowed: Boolean + get() = false + + override fun match(state: MatchingState): TransformationMatch.Result? { + val ifExpression = state.statements.singleOrNull() as? KtIfExpression ?: return null + if (ifExpression.`else` != null) return null + + val condition = ifExpression.condition as? KtBinaryExpression ?: return null + val comparison = condition.operationToken + if (comparison !in setOf(KtTokens.GT, KtTokens.LT)) return null + val left = condition.left as? KtNameReferenceExpression ?: return null + val right = condition.right as? KtNameReferenceExpression ?: return null + val otherHand = if (left.isVariableReference(state.inputVariable)) { + right + } + else if (right.isVariableReference(state.inputVariable)) { + left + } + else { + return null + } + + val variableInitialization = (otherHand.isVariableInitializedBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = false) + ?: return null) + + val then = ifExpression.then ?: return null + val statement = then.blockExpressionsOrSingle().singleOrNull() as? KtBinaryExpression ?: return null + if (statement.operationToken != KtTokens.EQ) return null + if (!statement.left.isVariableReference(variableInitialization.variable)) return null + if (!statement.right.isVariableReference(state.inputVariable)) return null + + val isMax = (comparison == KtTokens.GT) xor (otherHand == left) + val transformation = MaxOrMinTransformation(state.outerLoop, state.inputVariable, variableInitialization, isMax) + return TransformationMatch.Result(transformation) + } + } +} \ No newline at end of file diff --git a/idea/testData/intentions/loopToCallChain/max1.kt b/idea/testData/intentions/loopToCallChain/max1.kt new file mode 100644 index 00000000000..53870f160cf --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/max1.kt @@ -0,0 +1,15 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.max()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'" +fun getMaxLineWidth(lineCount: Int): Float { + var max_width = 0.0f + for (i in 0..lineCount - 1) { + val width = getLineWidth(i) + if (width > max_width) { + max_width = width + } + } + return max_width +} + +fun getLineWidth(i: Int): Float = TODO() diff --git a/idea/testData/intentions/loopToCallChain/max1.kt.after b/idea/testData/intentions/loopToCallChain/max1.kt.after new file mode 100644 index 00000000000..47fb2b03f6d --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/max1.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.max()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'" +fun getMaxLineWidth(lineCount: Int): Float { + val max_width = (0..lineCount - 1) + .map { getLineWidth(it) } + .max() + ?: 0.0f + return max_width +} + +fun getLineWidth(i: Int): Float = TODO() diff --git a/idea/testData/intentions/loopToCallChain/max1.kt.after2 b/idea/testData/intentions/loopToCallChain/max1.kt.after2 new file mode 100644 index 00000000000..8411a195010 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/max1.kt.after2 @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.max()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'" +fun getMaxLineWidth(lineCount: Int): Float { + val max_width = (0..lineCount - 1) + .asSequence() + .map { getLineWidth(it) } + .max() + ?: 0.0f + return max_width +} + +fun getLineWidth(i: Int): Float = TODO() diff --git a/idea/testData/intentions/loopToCallChain/max2.kt b/idea/testData/intentions/loopToCallChain/max2.kt new file mode 100644 index 00000000000..19039e82991 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/max2.kt @@ -0,0 +1,15 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.max()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'" +fun getMaxLineWidth(lineCount: Int): Float { + var max_width = 0.0f + for (i in 0..lineCount - 1) { + val width = getLineWidth(i) + if (max_width < width) { + max_width = width + } + } + return max_width +} + +fun getLineWidth(i: Int): Float = TODO() diff --git a/idea/testData/intentions/loopToCallChain/max2.kt.after b/idea/testData/intentions/loopToCallChain/max2.kt.after new file mode 100644 index 00000000000..47fb2b03f6d --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/max2.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.max()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'" +fun getMaxLineWidth(lineCount: Int): Float { + val max_width = (0..lineCount - 1) + .map { getLineWidth(it) } + .max() + ?: 0.0f + return max_width +} + +fun getLineWidth(i: Int): Float = TODO() diff --git a/idea/testData/intentions/loopToCallChain/max2.kt.after2 b/idea/testData/intentions/loopToCallChain/max2.kt.after2 new file mode 100644 index 00000000000..8411a195010 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/max2.kt.after2 @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.max()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'" +fun getMaxLineWidth(lineCount: Int): Float { + val max_width = (0..lineCount - 1) + .asSequence() + .map { getLineWidth(it) } + .max() + ?: 0.0f + return max_width +} + +fun getLineWidth(i: Int): Float = TODO() diff --git a/idea/testData/intentions/loopToCallChain/min1.kt b/idea/testData/intentions/loopToCallChain/min1.kt new file mode 100644 index 00000000000..528a8f0fff2 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min1.kt @@ -0,0 +1,15 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.min()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.min()'" +fun getMinLineWidth(lineCount: Int): Double { + var min_width = Double.MAX_VALUE + for (i in 0..lineCount - 1) { + val width = getLineWidth(i) + if (width < min_width) { + min_width = width + } + } + return min_width +} + +fun getLineWidth(i: Int): Double = TODO() diff --git a/idea/testData/intentions/loopToCallChain/min1.kt.after b/idea/testData/intentions/loopToCallChain/min1.kt.after new file mode 100644 index 00000000000..de805321864 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min1.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.min()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.min()'" +fun getMinLineWidth(lineCount: Int): Double { + val min_width = (0..lineCount - 1) + .map { getLineWidth(it) } + .min() + ?: Double.MAX_VALUE + return min_width +} + +fun getLineWidth(i: Int): Double = TODO() diff --git a/idea/testData/intentions/loopToCallChain/min1.kt.after2 b/idea/testData/intentions/loopToCallChain/min1.kt.after2 new file mode 100644 index 00000000000..2d1336352cb --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min1.kt.after2 @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.min()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.min()'" +fun getMinLineWidth(lineCount: Int): Double { + val min_width = (0..lineCount - 1) + .asSequence() + .map { getLineWidth(it) } + .min() + ?: Double.MAX_VALUE + return min_width +} + +fun getLineWidth(i: Int): Double = TODO() diff --git a/idea/testData/intentions/loopToCallChain/min2.kt b/idea/testData/intentions/loopToCallChain/min2.kt new file mode 100644 index 00000000000..de250a9f7fd --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min2.kt @@ -0,0 +1,15 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.min()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.min()'" +fun getMinLineWidth(lineCount: Int): Double { + var min_width = Double.MAX_VALUE + for (i in 0..lineCount - 1) { + val width = getLineWidth(i) + if (min_width > width) { + min_width = width + } + } + return min_width +} + +fun getLineWidth(i: Int): Double = TODO() diff --git a/idea/testData/intentions/loopToCallChain/min2.kt.after b/idea/testData/intentions/loopToCallChain/min2.kt.after new file mode 100644 index 00000000000..de805321864 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min2.kt.after @@ -0,0 +1,12 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.min()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.min()'" +fun getMinLineWidth(lineCount: Int): Double { + val min_width = (0..lineCount - 1) + .map { getLineWidth(it) } + .min() + ?: Double.MAX_VALUE + return min_width +} + +fun getLineWidth(i: Int): Double = TODO() diff --git a/idea/testData/intentions/loopToCallChain/min2.kt.after2 b/idea/testData/intentions/loopToCallChain/min2.kt.after2 new file mode 100644 index 00000000000..2d1336352cb --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min2.kt.after2 @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// INTENTION_TEXT: "Replace with 'map{}.min()'" +// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.min()'" +fun getMinLineWidth(lineCount: Int): Double { + val min_width = (0..lineCount - 1) + .asSequence() + .map { getLineWidth(it) } + .min() + ?: Double.MAX_VALUE + return min_width +} + +fun getLineWidth(i: Int): Double = TODO() diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java index ecb0d61cf9a..612a889a94e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java @@ -947,6 +947,30 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 { doTest(fileName); } + @TestMetadata("max1.kt") + public void testMax1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max1.kt"); + doTest(fileName); + } + + @TestMetadata("max2.kt") + public void testMax2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max2.kt"); + doTest(fileName); + } + + @TestMetadata("min1.kt") + public void testMin1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min1.kt"); + doTest(fileName); + } + + @TestMetadata("min2.kt") + public void testMin2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min2.kt"); + doTest(fileName); + } + @TestMetadata("moveInitializationsCloserToLoop.kt") public void testMoveInitializationsCloserToLoop() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/moveInitializationsCloserToLoop.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 96bb2b54fa7..93243498355 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -8107,6 +8107,30 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("max1.kt") + public void testMax1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max1.kt"); + doTest(fileName); + } + + @TestMetadata("max2.kt") + public void testMax2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max2.kt"); + doTest(fileName); + } + + @TestMetadata("min1.kt") + public void testMin1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min1.kt"); + doTest(fileName); + } + + @TestMetadata("min2.kt") + public void testMin2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min2.kt"); + doTest(fileName); + } + @TestMetadata("moveInitializationsCloserToLoop.kt") public void testMoveInitializationsCloserToLoop() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/moveInitializationsCloserToLoop.kt");