KT-11724 False suggestion to replace with compound assignment
#KT-11724 Fixed
This commit is contained in:
+26
-6
@@ -20,14 +20,20 @@ import com.intellij.openapi.editor.Editor
|
|||||||
import com.intellij.psi.tree.IElementType
|
import com.intellij.psi.tree.IElementType
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||||
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.inspections.IntentionBasedInspection
|
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
||||||
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.matches
|
import org.jetbrains.kotlin.idea.util.psi.patternMatching.matches
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||||
|
import org.jetbrains.kotlin.psi.doNotAnalyze
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
|
||||||
class ReplaceWithOperatorAssignmentInspection : IntentionBasedInspection<KtBinaryExpression>(ReplaceWithOperatorAssignmentIntention())
|
class ReplaceWithOperatorAssignmentInspection : IntentionBasedInspection<KtBinaryExpression>(ReplaceWithOperatorAssignmentIntention())
|
||||||
|
|
||||||
@@ -38,12 +44,22 @@ class ReplaceWithOperatorAssignmentIntention : SelfTargetingOffsetIndependentInt
|
|||||||
val right = element.right as? KtBinaryExpression ?: return false
|
val right = element.right as? KtBinaryExpression ?: return false
|
||||||
if (right.left == null || right.right == null) return false
|
if (right.left == null || right.right == null) return false
|
||||||
|
|
||||||
return checkExpressionRepeat(left, right)
|
val bindingContext = right.analyze(BodyResolveMode.PARTIAL)
|
||||||
|
if (!checkExpressionRepeat(left, right, bindingContext)) return false
|
||||||
|
|
||||||
|
// now check that the resulting operator assignment will be resolved
|
||||||
|
val opAssign = buildOperatorAssignment(element)
|
||||||
|
opAssign.getContainingKtFile().doNotAnalyze = null //TODO: strange hack
|
||||||
|
val resolutionScope = element.getResolutionScope(bindingContext, element.getResolutionFacade())
|
||||||
|
val newBindingContext = opAssign.analyzeInContext(resolutionScope,
|
||||||
|
contextExpression = element,
|
||||||
|
dataFlowInfo = bindingContext.getDataFlowInfo(element),
|
||||||
|
isStatement = true)
|
||||||
|
return newBindingContext.diagnostics.forElement(opAssign.operationReference).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkExpressionRepeat(variableExpression: KtNameReferenceExpression, expression: KtBinaryExpression): Boolean {
|
private fun checkExpressionRepeat(variableExpression: KtNameReferenceExpression, expression: KtBinaryExpression, bindingContext: BindingContext): Boolean {
|
||||||
val context = expression.analyze()
|
val descriptor = bindingContext[BindingContext.REFERENCE_TARGET, expression.operationReference]?.containingDeclaration
|
||||||
val descriptor = context[BindingContext.REFERENCE_TARGET, expression.operationReference]?.containingDeclaration
|
|
||||||
val isPrimitiveOperation = descriptor is ClassDescriptor && KotlinBuiltIns.isPrimitiveType(descriptor.defaultType)
|
val isPrimitiveOperation = descriptor is ClassDescriptor && KotlinBuiltIns.isPrimitiveType(descriptor.defaultType)
|
||||||
|
|
||||||
val operationToken = expression.operationToken
|
val operationToken = expression.operationToken
|
||||||
@@ -62,7 +78,7 @@ class ReplaceWithOperatorAssignmentIntention : SelfTargetingOffsetIndependentInt
|
|||||||
|
|
||||||
expressionLeft is KtBinaryExpression -> {
|
expressionLeft is KtBinaryExpression -> {
|
||||||
val sameCommutativeOperation = expressionLeft.operationToken == operationToken && isCommutative(operationToken)
|
val sameCommutativeOperation = expressionLeft.operationToken == operationToken && isCommutative(operationToken)
|
||||||
isPrimitiveOperation && sameCommutativeOperation && checkExpressionRepeat(variableExpression, expressionLeft)
|
isPrimitiveOperation && sameCommutativeOperation && checkExpressionRepeat(variableExpression, expressionLeft, bindingContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@@ -79,12 +95,16 @@ class ReplaceWithOperatorAssignmentIntention : SelfTargetingOffsetIndependentInt
|
|||||||
operationToken == KtTokens.PERC
|
operationToken == KtTokens.PERC
|
||||||
|
|
||||||
override fun applyTo(element: KtBinaryExpression, editor: Editor?) {
|
override fun applyTo(element: KtBinaryExpression, editor: Editor?) {
|
||||||
|
element.replace(buildOperatorAssignment(element))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildOperatorAssignment(element: KtBinaryExpression): KtBinaryExpression {
|
||||||
val replacement = buildOperatorAssignmentText(
|
val replacement = buildOperatorAssignmentText(
|
||||||
element.left as KtNameReferenceExpression,
|
element.left as KtNameReferenceExpression,
|
||||||
element.right as KtBinaryExpression,
|
element.right as KtBinaryExpression,
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
element.replace(KtPsiFactory(element).createExpression(replacement))
|
return KtPsiFactory(element).createExpression(replacement) as KtBinaryExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
tailrec private fun buildOperatorAssignmentText(variableExpression: KtNameReferenceExpression, expression: KtBinaryExpression, tail: String): String {
|
tailrec private fun buildOperatorAssignmentText(variableExpression: KtNameReferenceExpression, expression: KtBinaryExpression, tail: String): String {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// IS_APPLICABLE: false
|
||||||
|
// WITH_RUNTIME
|
||||||
|
fun foo() {
|
||||||
|
var list1 = java.util.Collections.emptyList<String>()
|
||||||
|
val list2 = listOf("b")
|
||||||
|
list1 <caret>= list1 + list2
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// IS_APPLICABLE: false
|
||||||
|
class A
|
||||||
|
|
||||||
|
operator fun A.plus(a: A): A = A()
|
||||||
|
operator fun A.plusAssign(a: A){}
|
||||||
|
|
||||||
|
fun foo() {
|
||||||
|
var a1 = A()
|
||||||
|
val a2 = A()
|
||||||
|
a1 <caret>= a1 + a2
|
||||||
|
}
|
||||||
@@ -8137,6 +8137,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceWithOperatorAssignment"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/replaceWithOperatorAssignment"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("flexibleTypeBug.kt")
|
||||||
|
public void testFlexibleTypeBug() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/flexibleTypeBug.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("illegalMultipleOperators.kt")
|
@TestMetadata("illegalMultipleOperators.kt")
|
||||||
public void testIllegalMultipleOperators() throws Exception {
|
public void testIllegalMultipleOperators() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/illegalMultipleOperators.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/illegalMultipleOperators.kt");
|
||||||
@@ -8179,6 +8185,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("plusAssignConflict.kt")
|
||||||
|
public void testPlusAssignConflict() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/plusAssignConflict.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("rightSideRepeat.kt")
|
@TestMetadata("rightSideRepeat.kt")
|
||||||
public void testRightSideRepeat() throws Exception {
|
public void testRightSideRepeat() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/rightSideRepeat.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/rightSideRepeat.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user