From 84fd24a1a01a4c1c8366a45fb724ed7e5c7d554b Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 17 Aug 2016 19:30:03 +0300 Subject: [PATCH] Another pattern for min/max supported --- .../result/MaxOrMinTransformation.kt | 69 +++++++++++++++---- .../intentions/loopToCallChain/max3.kt | 13 ++++ .../intentions/loopToCallChain/max3.kt.after | 12 ++++ .../intentions/loopToCallChain/max3.kt.after2 | 13 ++++ .../intentions/loopToCallChain/min3.kt | 13 ++++ .../intentions/loopToCallChain/min3.kt.after | 12 ++++ .../intentions/loopToCallChain/min3.kt.after2 | 13 ++++ .../intentions/loopToCallChain/min4.kt | 13 ++++ .../intentions/loopToCallChain/min4.kt.after | 12 ++++ .../intentions/loopToCallChain/min4.kt.after2 | 13 ++++ .../intentions/IntentionTest2Generated.java | 18 +++++ .../intentions/IntentionTestGenerated.java | 18 +++++ 12 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 idea/testData/intentions/loopToCallChain/max3.kt create mode 100644 idea/testData/intentions/loopToCallChain/max3.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/max3.kt.after2 create mode 100644 idea/testData/intentions/loopToCallChain/min3.kt create mode 100644 idea/testData/intentions/loopToCallChain/min3.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/min3.kt.after2 create mode 100644 idea/testData/intentions/loopToCallChain/min4.kt create mode 100644 idea/testData/intentions/loopToCallChain/min4.kt.after create mode 100644 idea/testData/intentions/loopToCallChain/min4.kt.after2 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 e95d882975e..50a9e4ef942 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 @@ -45,41 +45,86 @@ class MaxOrMinTransformation( * variable = * } * } + * + * or + * + * val variable = + * for (...) { + * ... + * // or '<', '<=', '>=' or operands swapped + * variable = if (variable > ) else variable + * } + * } */ object Matcher : TransformationMatcher { override val indexVariableAllowed: Boolean get() = false override fun match(state: MatchingState): TransformationMatch.Result? { + return matchIfAssign(state) ?: matchAssignIf(state) + } + + private fun matchIfAssign(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 then = ifExpression.then ?: return null + val statement = then.blockExpressionsOrSingle().singleOrNull() as? KtBinaryExpression ?: return null + if (statement.operationToken != KtTokens.EQ) return null + + return match(ifExpression.condition, statement.left, statement.right, null, state.inputVariable, state.outerLoop) + } + + private fun matchAssignIf(state: MatchingState): TransformationMatch.Result? { + val assignment = state.statements.singleOrNull() as? KtBinaryExpression ?: return null + if (assignment.operationToken != KtTokens.EQ) return null + + val ifExpression = assignment.right as? KtIfExpression ?: return null + + return match(ifExpression.condition, assignment.left, ifExpression.then, ifExpression.`else`, state.inputVariable, state.outerLoop) + } + + private fun match( + condition: KtExpression?, + assignmentTarget: KtExpression?, + valueAssignedIfTrue: KtExpression?, + valueAssignedIfFalse: KtExpression?, + inputVariable: KtCallableDeclaration, + loop: KtForExpression + ): TransformationMatch.Result? { + if (condition !is KtBinaryExpression) return null val comparison = condition.operationToken - if (comparison !in setOf(KtTokens.GT, KtTokens.LT)) return null + if (comparison !in setOf(KtTokens.GT, KtTokens.LT, KtTokens.GTEQ, KtTokens.LTEQ)) 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)) { + val otherHand = if (left.isVariableReference(inputVariable)) { right } - else if (right.isVariableReference(state.inputVariable)) { + else if (right.isVariableReference(inputVariable)) { left } else { return null } - val variableInitialization = (otherHand.isVariableInitializedBeforeLoop(state.outerLoop, checkNoOtherUsagesInLoop = false) + val variableInitialization = (otherHand.isVariableInitializedBeforeLoop(loop, 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 + if (!assignmentTarget.isVariableReference(variableInitialization.variable)) return null - val isMax = (comparison == KtTokens.GT) xor (otherHand == left) - val transformation = MaxOrMinTransformation(state.outerLoop, state.inputVariable, variableInitialization, isMax) + val valueToBeVariable = if (valueAssignedIfTrue.isVariableReference(inputVariable)) { + valueAssignedIfFalse + } + else if (valueAssignedIfFalse.isVariableReference(inputVariable)) { + valueAssignedIfTrue + } + else { + return null + } + if (valueToBeVariable != null && !valueToBeVariable.isVariableReference(variableInitialization.variable)) return null + + val isMax = (comparison == KtTokens.GT || comparison == KtTokens.GTEQ) xor (otherHand == left) + val transformation = MaxOrMinTransformation(loop, inputVariable, variableInitialization, isMax) return TransformationMatch.Result(transformation) } } diff --git a/idea/testData/intentions/loopToCallChain/max3.kt b/idea/testData/intentions/loopToCallChain/max3.kt new file mode 100644 index 00000000000..778bc78f294 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/max3.kt @@ -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 { + var max_width = 0.0f + for (i in 0..lineCount - 1) { + val width = getLineWidth(i) + max_width = if (width > max_width) width else max_width + } + return max_width +} + +fun getLineWidth(i: Int): Float = TODO() diff --git a/idea/testData/intentions/loopToCallChain/max3.kt.after b/idea/testData/intentions/loopToCallChain/max3.kt.after new file mode 100644 index 00000000000..47fb2b03f6d --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/max3.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/max3.kt.after2 b/idea/testData/intentions/loopToCallChain/max3.kt.after2 new file mode 100644 index 00000000000..8411a195010 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/max3.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/min3.kt b/idea/testData/intentions/loopToCallChain/min3.kt new file mode 100644 index 00000000000..f5817d44e03 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min3.kt @@ -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 { + var min_width = Double.MAX_VALUE + for (i in 0..lineCount - 1) { + val width = getLineWidth(i) + min_width = if (min_width > width) min_width else width + } + return min_width +} + +fun getLineWidth(i: Int): Double = TODO() diff --git a/idea/testData/intentions/loopToCallChain/min3.kt.after b/idea/testData/intentions/loopToCallChain/min3.kt.after new file mode 100644 index 00000000000..de805321864 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min3.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/min3.kt.after2 b/idea/testData/intentions/loopToCallChain/min3.kt.after2 new file mode 100644 index 00000000000..2d1336352cb --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min3.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/min4.kt b/idea/testData/intentions/loopToCallChain/min4.kt new file mode 100644 index 00000000000..8dd25efb4b5 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min4.kt @@ -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 { + var min_width = Double.MAX_VALUE + for (i in 0..lineCount - 1) { + val width = getLineWidth(i) + min_width = if (min_width >= width) width else min_width + } + return min_width +} + +fun getLineWidth(i: Int): Double = TODO() diff --git a/idea/testData/intentions/loopToCallChain/min4.kt.after b/idea/testData/intentions/loopToCallChain/min4.kt.after new file mode 100644 index 00000000000..de805321864 --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min4.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/min4.kt.after2 b/idea/testData/intentions/loopToCallChain/min4.kt.after2 new file mode 100644 index 00000000000..2d1336352cb --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/min4.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 612a889a94e..38a97468b92 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java @@ -959,6 +959,12 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 { doTest(fileName); } + @TestMetadata("max3.kt") + public void testMax3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max3.kt"); + doTest(fileName); + } + @TestMetadata("min1.kt") public void testMin1() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min1.kt"); @@ -971,6 +977,18 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 { doTest(fileName); } + @TestMetadata("min3.kt") + public void testMin3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min3.kt"); + doTest(fileName); + } + + @TestMetadata("min4.kt") + public void testMin4() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min4.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 93243498355..7527474e458 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -8119,6 +8119,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("max3.kt") + public void testMax3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max3.kt"); + doTest(fileName); + } + @TestMetadata("min1.kt") public void testMin1() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min1.kt"); @@ -8131,6 +8137,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("min3.kt") + public void testMin3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min3.kt"); + doTest(fileName); + } + + @TestMetadata("min4.kt") + public void testMin4() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min4.kt"); + doTest(fileName); + } + @TestMetadata("moveInitializationsCloserToLoop.kt") public void testMoveInitializationsCloserToLoop() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/moveInitializationsCloserToLoop.kt");