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");