Math.max/min pattern for min/max supported

This commit is contained in:
Valentin Kipyatkov
2016-08-17 20:22:20 +03:00
parent 84fd24a1a0
commit 91954e131a
18 changed files with 292 additions and 4 deletions
@@ -20,7 +20,9 @@ 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.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.readWriteAccess
@@ -32,6 +34,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
@@ -169,3 +172,19 @@ fun PsiChildRange.withoutLastStatement(): PsiChildRange {
val newLast = last!!.siblings(forward = false, withItself = false).first { it !is PsiWhiteSpace }
return PsiChildRange(first, newLast)
}
fun KtExpression?.extractStaticFunctionCallArguments(functionFqName: String): List<KtExpression?>? {
val callExpression = when (this) {
is KtDotQualifiedExpression -> selectorExpression as? KtCallExpression
is KtCallExpression -> this
else -> null
} ?: return null
val bindingContext = callExpression.analyze(BodyResolveMode.PARTIAL)
val resolvedCall = callExpression.getResolvedCall(bindingContext) ?: return null
val functionDescriptor = resolvedCall.resultingDescriptor as? FunctionDescriptor ?: return null
if (functionDescriptor.dispatchReceiverParameter != null || functionDescriptor.extensionReceiverParameter != null) return null
if (functionDescriptor.importableFqName?.asString() != functionFqName) return null
return resolvedCall.valueArgumentsByIndex?.map { it?.arguments?.singleOrNull()?.getArgumentExpression() }
}
@@ -17,13 +17,14 @@
package org.jetbrains.kotlin.idea.intentions.loopToCallChain.result
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.psi.psiUtil.blockExpressionsOrSingle
import org.jetbrains.kotlin.utils.singletonOrEmptyList
class MaxOrMinTransformation(
loop: KtForExpression,
private val inputVariable: KtCallableDeclaration,
initialization: VariableInitialization,
private val isMax: Boolean
) : AssignToVariableResultTransformation(loop, initialization) {
@@ -58,10 +59,12 @@ class MaxOrMinTransformation(
*/
object Matcher : TransformationMatcher {
override val indexVariableAllowed: Boolean
get() = false
get() = true
override fun match(state: MatchingState): TransformationMatch.Result? {
return matchIfAssign(state) ?: matchAssignIf(state)
return matchIfAssign(state)
?: matchAssignIf(state)
?: matchMathMaxOrMin(state)
}
private fun matchIfAssign(state: MatchingState): TransformationMatch.Result? {
@@ -84,6 +87,46 @@ class MaxOrMinTransformation(
return match(ifExpression.condition, assignment.left, ifExpression.then, ifExpression.`else`, state.inputVariable, state.outerLoop)
}
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)
?: return null
return matchMathMaxOrMin(variableInitialization, assignment, state, isMax = true)
?: matchMathMaxOrMin(variableInitialization, assignment, state, isMax = false)
}
private fun matchMathMaxOrMin(
variableInitialization: VariableInitialization,
assignment: KtBinaryExpression,
state: MatchingState,
isMax: Boolean
): TransformationMatch.Result? {
val functionName = if (isMax) "max" else "min"
val arguments = assignment.right.extractStaticFunctionCallArguments("java.lang.Math." + functionName) ?: return null
if (arguments.size != 2) return null
val value = if (arguments[0].isVariableReference(variableInitialization.variable)) {
arguments[1] ?: return null
}
else if (arguments[1].isVariableReference(variableInitialization.variable)) {
arguments[0] ?: return null
}
else {
return null
}
val mapTransformation = if (value.isVariableReference(state.inputVariable))
null
else
MapTransformation(state.outerLoop, state.inputVariable, state.indexVariable, value, mapNotNull = false)
val transformation = MaxOrMinTransformation(state.outerLoop, variableInitialization, isMax)
return TransformationMatch.Result(transformation, mapTransformation.singletonOrEmptyList())
}
private fun match(
condition: KtExpression?,
assignmentTarget: KtExpression?,
@@ -124,7 +167,7 @@ class MaxOrMinTransformation(
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)
val transformation = MaxOrMinTransformation(loop, variableInitialization, isMax)
return TransformationMatch.Result(transformation)
}
}
+10
View File
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'max()'"
// IS_APPLICABLE_2: false
fun getMaxLineWidth(list: List<Float>): Float {
var max = 0.0f
<caret>for (item in list) {
max = Math.max(item, max)
}
return max
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'max()'"
// IS_APPLICABLE_2: false
fun getMaxLineWidth(list: List<Float>): Float {
val <caret>max = list.max()
?: 0.0f
return max
}
+12
View File
@@ -0,0 +1,12 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'map{}.max()'"
// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'"
fun getMaxLineWidth(count: Int): Double {
var max = 0.0
<caret>for (i in 0..count-1) {
max = Math.max(max, getLineWidth(i))
}
return max
}
fun getLineWidth(i: Int): Double = TODO()
+12
View File
@@ -0,0 +1,12 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'map{}.max()'"
// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'"
fun getMaxLineWidth(count: Int): Double {
val max = (0..count-1)
.map { getLineWidth(it) }
.max()
?: 0.0
return max
}
fun getLineWidth(i: Int): Double = TODO()
+13
View File
@@ -0,0 +1,13 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'map{}.max()'"
// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'"
fun getMaxLineWidth(count: Int): Double {
val max = (0..count-1)
.asSequence()
.map { getLineWidth(it) }
.max()
?: 0.0
return max
}
fun getLineWidth(i: Int): Double = TODO()
+10
View File
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'mapIndexed{}.max()'"
// INTENTION_TEXT_2: "Replace with 'asSequence().mapIndexed{}.max()'"
fun getMaxLineWidth(list: List<Double>): Double {
var max = 0.0
<caret>for ((i, item) in list.withIndex()) {
max = Math.max(max, item * i)
}
return max
}
+10
View File
@@ -0,0 +1,10 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'mapIndexed{}.max()'"
// INTENTION_TEXT_2: "Replace with 'asSequence().mapIndexed{}.max()'"
fun getMaxLineWidth(list: List<Double>): Double {
val <caret>max = list
.mapIndexed { i, item -> item * i }
.max()
?: 0.0
return max
}
+11
View File
@@ -0,0 +1,11 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'mapIndexed{}.max()'"
// INTENTION_TEXT_2: "Replace with 'asSequence().mapIndexed{}.max()'"
fun getMaxLineWidth(list: List<Double>): Double {
val <caret>max = list
.asSequence()
.mapIndexed { i, item -> item * i }
.max()
?: 0.0
return max
}
+14
View File
@@ -0,0 +1,14 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'map{}.max()'"
// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'"
import java.lang.Math.max
fun getMaxLineWidth(count: Int): Double {
var m = 0.0
<caret>for (i in 0..count-1) {
m = max(m, getLineWidth(i))
}
return m
}
fun getLineWidth(i: Int): Double = TODO()
+14
View File
@@ -0,0 +1,14 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'map{}.max()'"
// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'"
import java.lang.Math.max
fun getMaxLineWidth(count: Int): Double {
val <caret>m = (0..count-1)
.map { getLineWidth(it) }
.max()
?: 0.0
return m
}
fun getLineWidth(i: Int): Double = TODO()
+15
View File
@@ -0,0 +1,15 @@
// WITH_RUNTIME
// INTENTION_TEXT: "Replace with 'map{}.max()'"
// INTENTION_TEXT_2: "Replace with 'asSequence().map{}.max()'"
import java.lang.Math.max
fun getMaxLineWidth(count: Int): Double {
val <caret>m = (0..count-1)
.asSequence()
.map { getLineWidth(it) }
.max()
?: 0.0
return m
}
fun getLineWidth(i: Int): Double = TODO()
+12
View File
@@ -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 {
var min_width = Double.MAX_VALUE
<caret>for (i in 0..lineCount - 1) {
min_width = Math.min(getLineWidth(i), min_width)
}
return min_width
}
fun getLineWidth(i: Int): Double = TODO()
+12
View File
@@ -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 <caret>min_width = (0..lineCount - 1)
.map { getLineWidth(it) }
.min()
?: Double.MAX_VALUE
return min_width
}
fun getLineWidth(i: Int): Double = TODO()
+13
View File
@@ -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 <caret>min_width = (0..lineCount - 1)
.asSequence()
.map { getLineWidth(it) }
.min()
?: Double.MAX_VALUE
return min_width
}
fun getLineWidth(i: Int): Double = TODO()
@@ -965,6 +965,30 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 {
doTest(fileName);
}
@TestMetadata("max4.kt")
public void testMax4() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max4.kt");
doTest(fileName);
}
@TestMetadata("max5.kt")
public void testMax5() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max5.kt");
doTest(fileName);
}
@TestMetadata("max6.kt")
public void testMax6() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max6.kt");
doTest(fileName);
}
@TestMetadata("max7.kt")
public void testMax7() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max7.kt");
doTest(fileName);
}
@TestMetadata("min1.kt")
public void testMin1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min1.kt");
@@ -989,6 +1013,12 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 {
doTest(fileName);
}
@TestMetadata("min5.kt")
public void testMin5() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min5.kt");
doTest(fileName);
}
@TestMetadata("moveInitializationsCloserToLoop.kt")
public void testMoveInitializationsCloserToLoop() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/moveInitializationsCloserToLoop.kt");
@@ -8125,6 +8125,30 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("max4.kt")
public void testMax4() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max4.kt");
doTest(fileName);
}
@TestMetadata("max5.kt")
public void testMax5() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max5.kt");
doTest(fileName);
}
@TestMetadata("max6.kt")
public void testMax6() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max6.kt");
doTest(fileName);
}
@TestMetadata("max7.kt")
public void testMax7() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/max7.kt");
doTest(fileName);
}
@TestMetadata("min1.kt")
public void testMin1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min1.kt");
@@ -8149,6 +8173,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("min5.kt")
public void testMin5() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/min5.kt");
doTest(fileName);
}
@TestMetadata("moveInitializationsCloserToLoop.kt")
public void testMoveInitializationsCloserToLoop() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/moveInitializationsCloserToLoop.kt");