diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/conventionNameCalls/ReplaceCallWithBinaryOperatorInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/conventionNameCalls/ReplaceCallWithBinaryOperatorInspection.kt index a1ab0573ed8..f1e51ca554b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/conventionNameCalls/ReplaceCallWithBinaryOperatorInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/conventionNameCalls/ReplaceCallWithBinaryOperatorInspection.kt @@ -22,13 +22,17 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.tree.IElementType import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.inspections.AbstractApplicabilityBasedInspection -import org.jetbrains.kotlin.idea.intentions.* +import org.jetbrains.kotlin.idea.intentions.callExpression import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals +import org.jetbrains.kotlin.idea.intentions.isOperatorOrCompatible +import org.jetbrains.kotlin.idea.intentions.isReceiverExpressionWithValue +import org.jetbrains.kotlin.idea.intentions.toResolvedCall import org.jetbrains.kotlin.idea.project.languageVersionSettings import org.jetbrains.kotlin.lexer.KtSingleValueToken import org.jetbrains.kotlin.lexer.KtTokens @@ -63,7 +67,7 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec override fun isApplicable(element: KtDotQualifiedExpression): Boolean { val calleeExpression = element.callExpression?.calleeExpression as? KtSimpleNameExpression ?: return false - val operation = operation(calleeExpression) ?: return false + if (operation(calleeExpression) == null) return false val resolvedCall = element.toResolvedCall(BodyResolveMode.PARTIAL) ?: return false if (!resolvedCall.isReallySuccess()) return false @@ -137,6 +141,9 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec private fun operation(calleeExpression: KtSimpleNameExpression): KtSingleValueToken? { val identifier = calleeExpression.getReferencedNameAsName() val dotQualified = calleeExpression.parent.parent as? KtDotQualifiedExpression ?: return null + val isOperatorOrCompatible by lazy { + (calleeExpression.resolveToCall()?.resultingDescriptor as? FunctionDescriptor)?.isOperatorOrCompatible == true + } return when (identifier) { OperatorNameConventions.EQUALS -> { if (!dotQualified.isAnyEquals()) return null @@ -145,6 +152,7 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec else KtTokens.EQEQ } OperatorNameConventions.COMPARE_TO -> { + if (!isOperatorOrCompatible) return null // callee -> call -> DotQualified -> Binary val binaryParent = dotQualified.parent as? KtBinaryExpression ?: return null val notZero = when { @@ -160,7 +168,10 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec null } } - else -> OperatorConventions.BINARY_OPERATION_NAMES.inverse()[identifier] + else -> { + if (!isOperatorOrCompatible) return null + OperatorConventions.BINARY_OPERATION_NAMES.inverse()[identifier] + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt index 8a02ca94751..c54a54389bb 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/Utils.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.idea.core.setType import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClass @@ -42,6 +43,7 @@ import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.isFlexible import org.jetbrains.kotlin.types.typeUtil.isUnit +import org.jetbrains.kotlin.util.OperatorChecks fun KtContainerNode.description(): String? { when (node.elementType) { @@ -330,3 +332,11 @@ fun KtDotQualifiedExpression.isToString(): Boolean { val callableDescriptor = resolvedCall.resultingDescriptor as? CallableMemberDescriptor ?: return false return callableDescriptor.getDeepestSuperDeclarations().any { it.fqNameUnsafe.asString() == "kotlin.Any.toString" } } + +val FunctionDescriptor.isOperatorOrCompatible: Boolean + get() { + if (this is JavaMethodDescriptor) { + return OperatorChecks.check(this).isSuccess + } + return isOperator + } diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ReplaceContainsIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ReplaceContainsIntention.kt index e851e281e45..7d53d38dfab 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ReplaceContainsIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/conventionNameCalls/ReplaceContainsIntention.kt @@ -23,13 +23,11 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.intentions.* import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode -import org.jetbrains.kotlin.util.OperatorChecks import org.jetbrains.kotlin.util.OperatorNameConventions class ReplaceContainsIntention : SelfTargetingRangeIntention(KtDotQualifiedExpression::class.java, "Replace 'contains' call with 'in' operator"), HighPriorityAction { @@ -54,14 +52,6 @@ class ReplaceContainsIntention : SelfTargetingRangeIntentioncompareTo(p2) < 0 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/compareToFromJava.kt.after b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/compareToFromJava.kt.after new file mode 100644 index 00000000000..ce20d7290a7 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/compareToFromJava.kt.after @@ -0,0 +1,5 @@ +// FIX: Replace with '<' + +fun test(p1: Operation, p2: Operation) { + p1 < p2 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notOperatorCompareTo.kt b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notOperatorCompareTo.kt new file mode 100644 index 00000000000..e603d6715e4 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notOperatorCompareTo.kt @@ -0,0 +1,9 @@ +// PROBLEM: none + +fun test(p1: Operation, p2: Operation) { + p1.compareTo(p2) < 0 +} + +class Operation { + fun compareTo(other: Operation) = 0 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notOperatorPlus.kt b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notOperatorPlus.kt new file mode 100644 index 00000000000..6d3fe1ef230 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notOperatorPlus.kt @@ -0,0 +1,9 @@ +// PROBLEM: none + +fun test(p1: Operation, p2: Operation) { + p1.plus(p2) +} + +class Operation { + fun plus(other: Operation) = 0 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.1.java b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.1.java new file mode 100644 index 00000000000..8c796252dd9 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.1.java @@ -0,0 +1,5 @@ +public class Operation { + public int plus(Operation other) { + return 0; + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.1.java.after b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.1.java.after new file mode 100644 index 00000000000..8c796252dd9 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.1.java.after @@ -0,0 +1,5 @@ +public class Operation { + public int plus(Operation other) { + return 0; + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.kt b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.kt new file mode 100644 index 00000000000..e113923f978 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.kt @@ -0,0 +1,5 @@ +// FIX: Replace with '+' + +fun test(p1: Operation, p2: Operation) { + p1.plus(p2) +} diff --git a/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.kt.after b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.kt.after new file mode 100644 index 00000000000..df03d6e8f98 --- /dev/null +++ b/idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.kt.after @@ -0,0 +1,5 @@ +// FIX: Replace with '+' + +fun test(p1: Operation, p2: Operation) { + p1 + p2 +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index c6987ee8e43..44198be5dba 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -1594,6 +1594,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); } + @TestMetadata("compareToFromJava.kt") + public void testCompareToFromJava() throws Exception { + runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/compareToFromJava.kt"); + } + @TestMetadata("divSanityTest.kt") public void testDivSanityTest() throws Exception { runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/divSanityTest.kt"); @@ -1699,6 +1704,21 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notEqualsBracketsComplex.kt"); } + @TestMetadata("notOperatorCompareTo.kt") + public void testNotOperatorCompareTo() throws Exception { + runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notOperatorCompareTo.kt"); + } + + @TestMetadata("notOperatorPlus.kt") + public void testNotOperatorPlus() throws Exception { + runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notOperatorPlus.kt"); + } + + @TestMetadata("plusFromJava.kt") + public void testPlusFromJava() throws Exception { + runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.kt"); + } + @TestMetadata("plusSanityTest.kt") public void testPlusSanityTest() throws Exception { runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusSanityTest.kt");