diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt index 25d4303081e..42d44095eda 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/ConvertTwoComparisonsToRangeCheckInspection.kt @@ -10,17 +10,22 @@ import com.intellij.openapi.project.Project import com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.analysis.analyzeAsReplacement import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.intentions.branchedTransformations.evaluatesTo import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getType import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.builtIns import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType +import org.jetbrains.kotlin.util.OperatorNameConventions class ConvertTwoComparisonsToRangeCheckInspection : AbstractApplicabilityBasedInspection(KtBinaryExpression::class.java) { @@ -28,24 +33,35 @@ class ConvertTwoComparisonsToRangeCheckInspection : override val defaultFixText get() = KotlinBundle.message("convert.to.a.range.check") - override fun isApplicable(element: KtBinaryExpression) = generateRangeExpressionData(element) != null + override fun isApplicable(element: KtBinaryExpression): Boolean { + val rangeData = generateRangeExpressionData(element) ?: return false + val function = element.getStrictParentOfType() + if (function != null && function.hasModifier(KtTokens.OPERATOR_KEYWORD) && function.nameAsName == OperatorNameConventions.CONTAINS) { + val context = element.analyze(BodyResolveMode.PARTIAL) + val functionDescriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, function] + val newExpression = rangeData.createExpression() + val newContext = newExpression.analyzeAsReplacement(element, context) + if (newExpression.operationReference.getResolvedCall(newContext)?.resultingDescriptor == functionDescriptor) return false + } + return true + } override fun applyTo(element: KtBinaryExpression, project: Project, editor: Editor?) { val rangeData = generateRangeExpressionData(element) ?: return - val factory = KtPsiFactory(element) - val replaced = element.replace( - factory.createExpressionByPattern( - "$0 in $1..$2", rangeData.value, - factory.createExpression(rangeData.min), - factory.createExpression(rangeData.max) - ) - ) + val replaced = element.replace(rangeData.createExpression()) (replaced as? KtBinaryExpression)?.right?.let { ReplaceRangeToWithUntilInspection.applyFixIfApplicable(it) } } - private data class RangeExpressionData(val value: KtExpression, val min: String, val max: String) + private data class RangeExpressionData(val value: KtExpression, val min: String, val max: String) { + fun createExpression(): KtBinaryExpression { + val factory = KtPsiFactory(value) + return factory.createExpressionByPattern( + "$0 in $1..$2", value, factory.createExpression(min), factory.createExpression(max) + ) as KtBinaryExpression + } + } private fun generateRangeExpressionData(condition: KtBinaryExpression): RangeExpressionData? { if (condition.operationToken != KtTokens.ANDAND) return null diff --git a/idea/testData/inspectionsLocal/convertTwoComparisonsToRangeCheck/recursiveCall.kt b/idea/testData/inspectionsLocal/convertTwoComparisonsToRangeCheck/recursiveCall.kt new file mode 100644 index 00000000000..10dd9ab8874 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertTwoComparisonsToRangeCheck/recursiveCall.kt @@ -0,0 +1,21 @@ +// PROBLEM: none +// WITH_RUNTIME +class TimeIndex(val intValue: Int) : Comparable { + override fun compareTo(other: TimeIndex): Int { + TODO() + } + + operator fun rangeTo(other: TimeIndex): TimeIndexRange { + return TimeIndexRange(this, other) + } +} + +data class TimeIndexRange(val start: TimeIndex, val end: TimeIndex) : Iterable { + override fun iterator(): Iterator { + TODO() + } + + operator fun contains(index: TimeIndex): Boolean { + return start <= index && index <= end + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 94f10ad324e..948d1fb1c55 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -3871,6 +3871,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/convertTwoComparisonsToRangeCheck/nonConstants.kt"); } + @TestMetadata("recursiveCall.kt") + public void testRecursiveCall() throws Exception { + runTest("idea/testData/inspectionsLocal/convertTwoComparisonsToRangeCheck/recursiveCall.kt"); + } + @TestMetadata("until.kt") public void testUntil() throws Exception { runTest("idea/testData/inspectionsLocal/convertTwoComparisonsToRangeCheck/until.kt");