Convert put to assignment: don't report when receiver object has custom 'set' method

#KT-33212 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-12-09 12:26:02 +09:00
committed by Yan Zhulanow
parent 5abb6bc7a5
commit 653e20dcba
5 changed files with 54 additions and 12 deletions
@@ -21,16 +21,19 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.analysis.analyzeAsReplacement
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.intentions.callExpression
import org.jetbrains.kotlin.idea.util.calleeTextRangeInThis
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
class ReplacePutWithAssignmentInspection : AbstractApplicabilityBasedInspection<KtDotQualifiedExpression>(
@@ -60,25 +63,32 @@ class ReplacePutWithAssignmentInspection : AbstractApplicabilityBasedInspection<
val resolvedCall = element.getResolvedCall(context)
val receiverType = resolvedCall?.getExplicitReceiverValue()?.type ?: return false
val receiverClass = receiverType.constructor.declarationDescriptor as? ClassDescriptor ?: return false
return receiverClass.isSubclassOf(DefaultBuiltIns.Instance.mutableMap)
if (!receiverClass.isSubclassOf(DefaultBuiltIns.Instance.mutableMap)) return false
val assignment = createAssignmentExpression(element) ?: return false
val newContext = assignment.analyzeAsReplacement(element, context)
return assignment.left.getResolvedCall(newContext)?.resultingDescriptor?.fqNameOrNull() == FqName("kotlin.collections.set")
}
override fun applyTo(element: KtDotQualifiedExpression, project: Project, editor: Editor?) {
val valueArguments = element.callExpression?.valueArguments ?: return
val firstArg = valueArguments[0]?.getArgumentExpression() ?: return
val secondArg = valueArguments[1]?.getArgumentExpression() ?: return
val assignment = createAssignmentExpression(element) ?: return
element.replace(assignment)
}
private fun createAssignmentExpression(element: KtDotQualifiedExpression): KtBinaryExpression? {
val valueArguments = element.callExpression?.valueArguments ?: return null
val firstArg = valueArguments[0]?.getArgumentExpression() ?: return null
val secondArg = valueArguments[1]?.getArgumentExpression() ?: return null
val label = if (secondArg is KtLambdaExpression) {
val returnLabel = secondArg.findDescendantOfType<KtReturnExpression>()?.getLabelName()
compatibleNames.firstOrNull { it == returnLabel }?.plus("@") ?: ""
} else ""
element.replace(
KtPsiFactory(element).createExpressionByPattern(
"$0[$1] = $label$2",
element.receiverExpression,
firstArg,
secondArg
)
)
return KtPsiFactory(element).createExpressionByPattern(
"$0[$1] = $label$2",
element.receiverExpression,
firstArg,
secondArg
) as? KtBinaryExpression
}
override fun inspectionHighlightRangeInElement(element: KtDotQualifiedExpression) = element.calleeTextRangeInThis()
@@ -0,0 +1,6 @@
import java.util.HashMap;
public class Foo extends HashMap<String, String> {
public void set(String x, String y) {
}
}
@@ -0,0 +1,5 @@
// PROBLEM: none
fun test() {
val foo = Foo()
foo.<caret>put("a", "b")
}
@@ -0,0 +1,11 @@
// PROBLEM: none
// WITH_RUNTIME
class Foo : HashMap<String, String>() {
operator fun set(x: String, y: String) {
println("wrong method")
}
}
fun main() {
Foo().<caret>put("x", "y")
}
@@ -11593,6 +11593,16 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/replacePutWithAssignment/beforeElvis.kt");
}
@TestMetadata("hasSet.kt")
public void testHasSet() throws Exception {
runTest("idea/testData/inspectionsLocal/replacePutWithAssignment/hasSet.kt");
}
@TestMetadata("hasSet2.kt")
public void testHasSet2() throws Exception {
runTest("idea/testData/inspectionsLocal/replacePutWithAssignment/hasSet2.kt");
}
@TestMetadata("nonMap.kt")
public void testNonMap() throws Exception {
runTest("idea/testData/inspectionsLocal/replacePutWithAssignment/nonMap.kt");