From 64f01e53af8eddd70701a7c0379a810cd9b4533d Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 14 Dec 2017 18:25:25 +0100 Subject: [PATCH] Check that `add()` method is in fact `MutableCollection.add()` #KT-18881 Fixed --- .../result/AddToCollectionTransformation.kt | 23 +++++++++++++++++-- .../loopToCallChain/toCollection/KT18881.kt | 14 +++++++++++ .../intentions/IntentionTest2Generated.java | 6 +++++ .../intentions/IntentionTestGenerated.java | 6 +++++ 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 idea/testData/intentions/loopToCallChain/toCollection/KT18881.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/AddToCollectionTransformation.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/AddToCollectionTransformation.kt index fdb48eacc44..c47015f59ed 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/AddToCollectionTransformation.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/loopToCallChain/result/AddToCollectionTransformation.kt @@ -17,18 +17,23 @@ package org.jetbrains.kotlin.idea.intentions.loopToCallChain.result import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.intentions.loopToCallChain.* import org.jetbrains.kotlin.idea.intentions.loopToCallChain.sequence.* +import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression import org.jetbrains.kotlin.renderer.render +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.overriddenTreeUniqueAsSequence class AddToCollectionTransformation( loop: KtForExpression, @@ -96,8 +101,11 @@ class AddToCollectionTransformation( val qualifiedExpression = statement as? KtDotQualifiedExpression ?: return null val targetCollection = qualifiedExpression.receiverExpression val callExpression = qualifiedExpression.selectorExpression as? KtCallExpression ?: return null - if (callExpression.getCallNameExpression()?.getReferencedName() != "add") return null - //TODO: check that it's MutableCollection's add + val callNameExpression = callExpression.getCallNameExpression() ?: return null + if (callNameExpression.getReferencedName() != "add") return null + val calleeMethodDescriptors = callNameExpression.resolveMainReferenceToDescriptors() + if (calleeMethodDescriptors.none { it.isCollectionAdd() }) return null + val argument = callExpression.valueArguments.singleOrNull() ?: return null val argumentValue = argument.getArgumentExpression() ?: return null @@ -204,6 +212,17 @@ class AddToCollectionTransformation( private fun KtExpression?.isRangeLiteral() = this is KtBinaryExpression && operationToken == KtTokens.RANGE +private fun DeclarationDescriptor.isCollectionAdd(): Boolean { + if ((containingDeclaration as? ClassDescriptor)?.fqNameSafe == KotlinBuiltIns.FQ_NAMES.mutableCollection) { + return true + } + + val overrides = (this as? CallableDescriptor)?.overriddenTreeUniqueAsSequence(true) ?: return false + return overrides.any { + (it.containingDeclaration as? ClassDescriptor)?.fqNameSafe == KotlinBuiltIns.FQ_NAMES.mutableCollection + } +} + class FilterToTransformation private constructor( loop: KtForExpression, private val inputVariable: KtCallableDeclaration, diff --git a/idea/testData/intentions/loopToCallChain/toCollection/KT18881.kt b/idea/testData/intentions/loopToCallChain/toCollection/KT18881.kt new file mode 100644 index 00000000000..28814d4aa6a --- /dev/null +++ b/idea/testData/intentions/loopToCallChain/toCollection/KT18881.kt @@ -0,0 +1,14 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +// IS_APPLICABLE_2: false +data class Node(private val data: MutableList) { + fun add(word: String) { + data.add(word) + } +} + +fun foo(node: Node, words: List) { + for (word in words) { + node.add(word) + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java index 81de09adea3..b8f59d9c717 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTest2Generated.java @@ -1550,6 +1550,12 @@ public class IntentionTest2Generated extends AbstractIntentionTest2 { doTest(fileName); } + @TestMetadata("KT18881.kt") + public void testKT18881() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/toCollection/KT18881.kt"); + doTest(fileName); + } + @TestMetadata("resultCollectionUsedInsideLoop.kt") public void testResultCollectionUsedInsideLoop() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/toCollection/resultCollectionUsedInsideLoop.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 38f91c8891b..6be7e9b11f5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -11461,6 +11461,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("KT18881.kt") + public void testKT18881() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/toCollection/KT18881.kt"); + doTest(fileName); + } + @TestMetadata("resultCollectionUsedInsideLoop.kt") public void testResultCollectionUsedInsideLoop() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/loopToCallChain/toCollection/resultCollectionUsedInsideLoop.kt");