Convert put to assignment: don't report when receiver object has custom 'set' method
#KT-33212 Fixed
This commit is contained in:
committed by
Yan Zhulanow
parent
5abb6bc7a5
commit
653e20dcba
+22
-12
@@ -21,16 +21,19 @@ import com.intellij.openapi.project.Project
|
|||||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
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.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.intentions.callExpression
|
import org.jetbrains.kotlin.idea.intentions.callExpression
|
||||||
import org.jetbrains.kotlin.idea.util.calleeTextRangeInThis
|
import org.jetbrains.kotlin.idea.util.calleeTextRangeInThis
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
|
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue
|
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
|
||||||
|
|
||||||
class ReplacePutWithAssignmentInspection : AbstractApplicabilityBasedInspection<KtDotQualifiedExpression>(
|
class ReplacePutWithAssignmentInspection : AbstractApplicabilityBasedInspection<KtDotQualifiedExpression>(
|
||||||
@@ -60,25 +63,32 @@ class ReplacePutWithAssignmentInspection : AbstractApplicabilityBasedInspection<
|
|||||||
val resolvedCall = element.getResolvedCall(context)
|
val resolvedCall = element.getResolvedCall(context)
|
||||||
val receiverType = resolvedCall?.getExplicitReceiverValue()?.type ?: return false
|
val receiverType = resolvedCall?.getExplicitReceiverValue()?.type ?: return false
|
||||||
val receiverClass = receiverType.constructor.declarationDescriptor as? ClassDescriptor ?: 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?) {
|
override fun applyTo(element: KtDotQualifiedExpression, project: Project, editor: Editor?) {
|
||||||
val valueArguments = element.callExpression?.valueArguments ?: return
|
val assignment = createAssignmentExpression(element) ?: return
|
||||||
val firstArg = valueArguments[0]?.getArgumentExpression() ?: return
|
element.replace(assignment)
|
||||||
val secondArg = valueArguments[1]?.getArgumentExpression() ?: return
|
}
|
||||||
|
|
||||||
|
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 label = if (secondArg is KtLambdaExpression) {
|
||||||
val returnLabel = secondArg.findDescendantOfType<KtReturnExpression>()?.getLabelName()
|
val returnLabel = secondArg.findDescendantOfType<KtReturnExpression>()?.getLabelName()
|
||||||
compatibleNames.firstOrNull { it == returnLabel }?.plus("@") ?: ""
|
compatibleNames.firstOrNull { it == returnLabel }?.plus("@") ?: ""
|
||||||
} else ""
|
} else ""
|
||||||
element.replace(
|
return KtPsiFactory(element).createExpressionByPattern(
|
||||||
KtPsiFactory(element).createExpressionByPattern(
|
"$0[$1] = $label$2",
|
||||||
"$0[$1] = $label$2",
|
element.receiverExpression,
|
||||||
element.receiverExpression,
|
firstArg,
|
||||||
firstArg,
|
secondArg
|
||||||
secondArg
|
) as? KtBinaryExpression
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inspectionHighlightRangeInElement(element: KtDotQualifiedExpression) = element.calleeTextRangeInThis()
|
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")
|
||||||
|
}
|
||||||
+10
@@ -11593,6 +11593,16 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
runTest("idea/testData/inspectionsLocal/replacePutWithAssignment/beforeElvis.kt");
|
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")
|
@TestMetadata("nonMap.kt")
|
||||||
public void testNonMap() throws Exception {
|
public void testNonMap() throws Exception {
|
||||||
runTest("idea/testData/inspectionsLocal/replacePutWithAssignment/nonMap.kt");
|
runTest("idea/testData/inspectionsLocal/replacePutWithAssignment/nonMap.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user