KT-11724 False suggestion to replace with compound assignment

#KT-11724 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-04-07 10:43:36 +03:00
parent 7c90fbe4de
commit b258af5288
4 changed files with 56 additions and 6 deletions
@@ -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");