Can be replaced with binary operator: do not report if it leads to an error/warning after conversion

#KT-25050 Fixed
This commit is contained in:
Toshiaki Kameyama
2020-01-31 17:03:15 +09:00
committed by Yan Zhulanow
parent 81d01a8f8e
commit 43b106fc72
3 changed files with 37 additions and 12 deletions
@@ -23,6 +23,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.analysis.analyzeAsReplacement
import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
@@ -34,7 +35,6 @@ import org.jetbrains.kotlin.idea.intentions.callExpression
import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals
import org.jetbrains.kotlin.idea.intentions.isOperatorOrCompatible import org.jetbrains.kotlin.idea.intentions.isOperatorOrCompatible
import org.jetbrains.kotlin.idea.intentions.isReceiverExpressionWithValue 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.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.project.platform import org.jetbrains.kotlin.idea.project.platform
import org.jetbrains.kotlin.idea.util.calleeTextRangeInThis import org.jetbrains.kotlin.idea.util.calleeTextRangeInThis
@@ -45,6 +45,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getLastParentOfTypeInRow import org.jetbrains.kotlin.psi.psiUtil.getLastParentOfTypeInRow
import org.jetbrains.kotlin.resolve.calls.callUtil.getFirstArgumentExpression import org.jetbrains.kotlin.resolve.calls.callUtil.getFirstArgumentExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getReceiverExpression import org.jetbrains.kotlin.resolve.calls.callUtil.getReceiverExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getType import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
@@ -75,13 +76,18 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec
val calleeExpression = element.callExpression?.calleeExpression as? KtSimpleNameExpression ?: return false val calleeExpression = element.callExpression?.calleeExpression as? KtSimpleNameExpression ?: return false
if (operation(calleeExpression) == null) return false if (operation(calleeExpression) == null) return false
val resolvedCall = element.toResolvedCall(BodyResolveMode.PARTIAL) ?: return false val context = element.analyze(BodyResolveMode.PARTIAL)
val resolvedCall = element.callExpression?.getResolvedCall(context) ?: return false
if (!resolvedCall.isReallySuccess()) return false if (!resolvedCall.isReallySuccess()) return false
if (resolvedCall.call.typeArgumentList != null) return false if (resolvedCall.call.typeArgumentList != null) return false
val argument = resolvedCall.call.valueArguments.singleOrNull() ?: return false val argument = resolvedCall.call.valueArguments.singleOrNull() ?: return false
if ((resolvedCall.getArgumentMapping(argument) as ArgumentMatch).valueParameter.index != 0) return false if ((resolvedCall.getArgumentMapping(argument) as ArgumentMatch).valueParameter.index != 0) return false
return element.isReceiverExpressionWithValue() if (!element.isReceiverExpressionWithValue()) return false
val (expressionToBeReplaced, newExpression) = getReplacementExpression(element) ?: return false
val newContext = newExpression.analyzeAsReplacement(expressionToBeReplaced, context)
return newContext.diagnostics.noSuppression().forElement(newExpression).isEmpty()
} }
override fun inspectionHighlightRangeInElement(element: KtDotQualifiedExpression) = element.calleeTextRangeInThis() override fun inspectionHighlightRangeInElement(element: KtDotQualifiedExpression) = element.calleeTextRangeInThis()
@@ -120,26 +126,32 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec
} }
override fun applyTo(element: KtDotQualifiedExpression, project: Project, editor: Editor?) { override fun applyTo(element: KtDotQualifiedExpression, project: Project, editor: Editor?) {
val callExpression = element.callExpression ?: return val (expressionToBeReplaced, newExpression) = getReplacementExpression(element) ?: return
val operation = operation(callExpression.calleeExpression as? KtSimpleNameExpression ?: return) ?: return expressionToBeReplaced.replace(newExpression)
val argument = callExpression.valueArguments.single().getArgumentExpression() ?: return }
private fun getReplacementExpression(element: KtDotQualifiedExpression): Pair<KtExpression, KtExpression>? {
val callExpression = element.callExpression ?: return null
val calleeExpression = callExpression.calleeExpression as? KtSimpleNameExpression ?: return null
val operation = operation(calleeExpression) ?: return null
val argument = callExpression.valueArguments.single().getArgumentExpression() ?: return null
val receiver = element.receiverExpression val receiver = element.receiverExpression
val factory = KtPsiFactory(element) val factory = KtPsiFactory(element)
when (operation) { return when (operation) {
KtTokens.EXCLEQ -> { KtTokens.EXCLEQ -> {
val prefixExpression = element.getWrappingPrefixExpressionIfAny() ?: return val prefixExpression = element.getWrappingPrefixExpressionIfAny() ?: return null
val newExpression = factory.createExpressionByPattern("$0 != $1", receiver, argument) val newExpression = factory.createExpressionByPattern("$0 != $1", receiver, argument)
prefixExpression.replace(newExpression) prefixExpression to newExpression
} }
in OperatorConventions.COMPARISON_OPERATIONS -> { in OperatorConventions.COMPARISON_OPERATIONS -> {
val binaryParent = element.parent as? KtBinaryExpression ?: return val binaryParent = element.parent as? KtBinaryExpression ?: return null
val newExpression = factory.createExpressionByPattern("$0 ${operation.value} $1", receiver, argument) val newExpression = factory.createExpressionByPattern("$0 ${operation.value} $1", receiver, argument)
binaryParent.replace(newExpression) binaryParent to newExpression
} }
else -> { else -> {
val newExpression = factory.createExpressionByPattern("$0 ${operation.value} $1", receiver, argument) val newExpression = factory.createExpressionByPattern("$0 ${operation.value} $1", receiver, argument)
element.replace(newExpression) element to newExpression
} }
} }
} }
@@ -0,0 +1,8 @@
// PROBLEM: none
class Foo
class Foo2
fun main(f: Foo, f2: Foo2) {
if (f.<caret>equals(f2)) {
}
}
@@ -3074,6 +3074,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notOperatorPlus.kt"); runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/notOperatorPlus.kt");
} }
@TestMetadata("operatorCannotBeApplied.kt")
public void testOperatorCannotBeApplied() throws Exception {
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/operatorCannotBeApplied.kt");
}
@TestMetadata("plusFromJava.kt") @TestMetadata("plusFromJava.kt")
public void testPlusFromJava() throws Exception { public void testPlusFromJava() throws Exception {
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.kt"); runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/plusFromJava.kt");