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 org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
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.getResolutionFacade
|
||||
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.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.doNotAnalyze
|
||||
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())
|
||||
|
||||
@@ -38,12 +44,22 @@ class ReplaceWithOperatorAssignmentIntention : SelfTargetingOffsetIndependentInt
|
||||
val right = element.right as? KtBinaryExpression ?: 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 {
|
||||
val context = expression.analyze()
|
||||
val descriptor = context[BindingContext.REFERENCE_TARGET, expression.operationReference]?.containingDeclaration
|
||||
private fun checkExpressionRepeat(variableExpression: KtNameReferenceExpression, expression: KtBinaryExpression, bindingContext: BindingContext): Boolean {
|
||||
val descriptor = bindingContext[BindingContext.REFERENCE_TARGET, expression.operationReference]?.containingDeclaration
|
||||
val isPrimitiveOperation = descriptor is ClassDescriptor && KotlinBuiltIns.isPrimitiveType(descriptor.defaultType)
|
||||
|
||||
val operationToken = expression.operationToken
|
||||
@@ -62,7 +78,7 @@ class ReplaceWithOperatorAssignmentIntention : SelfTargetingOffsetIndependentInt
|
||||
|
||||
expressionLeft is KtBinaryExpression -> {
|
||||
val sameCommutativeOperation = expressionLeft.operationToken == operationToken && isCommutative(operationToken)
|
||||
isPrimitiveOperation && sameCommutativeOperation && checkExpressionRepeat(variableExpression, expressionLeft)
|
||||
isPrimitiveOperation && sameCommutativeOperation && checkExpressionRepeat(variableExpression, expressionLeft, bindingContext)
|
||||
}
|
||||
|
||||
else -> {
|
||||
@@ -79,12 +95,16 @@ class ReplaceWithOperatorAssignmentIntention : SelfTargetingOffsetIndependentInt
|
||||
operationToken == KtTokens.PERC
|
||||
|
||||
override fun applyTo(element: KtBinaryExpression, editor: Editor?) {
|
||||
element.replace(buildOperatorAssignment(element))
|
||||
}
|
||||
|
||||
private fun buildOperatorAssignment(element: KtBinaryExpression): KtBinaryExpression {
|
||||
val replacement = buildOperatorAssignmentText(
|
||||
element.left as KtNameReferenceExpression,
|
||||
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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@TestMetadata("flexibleTypeBug.kt")
|
||||
public void testFlexibleTypeBug() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/flexibleTypeBug.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("illegalMultipleOperators.kt")
|
||||
public void testIllegalMultipleOperators() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/illegalMultipleOperators.kt");
|
||||
@@ -8179,6 +8185,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
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")
|
||||
public void testRightSideRepeat() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/replaceWithOperatorAssignment/rightSideRepeat.kt");
|
||||
|
||||
Reference in New Issue
Block a user