Math.max/min pattern for min/max supported
This commit is contained in:
@@ -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() }
|
||||
}
|
||||
|
||||
+47
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user