Fix false positive "Collection count can be converted to size" with Iterable

#KT-34677 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-11-10 16:00:25 +09:00
committed by Vladimir Dolzhenko
parent d906c814cf
commit e2a7170b2f
7 changed files with 64 additions and 18 deletions
@@ -11,23 +11,30 @@ import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.inspections.collections.isCalling import org.jetbrains.kotlin.idea.inspections.collections.isCalling
import org.jetbrains.kotlin.idea.inspections.collections.receiverType
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.callExpressionVisitor import org.jetbrains.kotlin.psi.callExpressionVisitor
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class ReplaceCollectionCountWithSizeInspection : AbstractKotlinInspection() { class ReplaceCollectionCountWithSizeInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return callExpressionVisitor { callExpression -> return callExpressionVisitor(fun(callExpression: KtCallExpression) {
if (callExpression.isCount()) { if (callExpression.calleeExpression?.text != "count" || callExpression.valueArguments.isNotEmpty()) return
holder.registerProblem( val context = callExpression.analyze(BodyResolveMode.PARTIAL)
callExpression, if (!callExpression.isCalling(FqName("kotlin.collections.count"))) return
KotlinBundle.message("could.be.replaced.with.size"), val receiverType = callExpression.receiverType(context) ?: return
ReplaceCollectionCountWithSizeQuickFix() if (KotlinBuiltIns.isIterableOrNullableIterable(receiverType)) return
) holder.registerProblem(
} callExpression,
} KotlinBundle.message("could.be.replaced.with.size"),
ReplaceCollectionCountWithSizeQuickFix()
)
})
} }
} }
@@ -41,5 +48,3 @@ class ReplaceCollectionCountWithSizeQuickFix : LocalQuickFix {
element.replace(KtPsiFactory(element).createExpression("size")) element.replace(KtPsiFactory(element).createExpression("size"))
} }
} }
private fun KtCallExpression.isCount(): Boolean = valueArguments.isEmpty() && isCalling(FqName("kotlin.collections.count"))
@@ -148,9 +148,7 @@ private fun KtQualifiedExpression.findCallChain(): CallChain? {
if (calls.isEmpty()) return null if (calls.isEmpty()) return null
val lastCall = calls.last() val lastCall = calls.last()
val receiverType = val receiverType = lastCall.receiverType(context)
(lastCall.getQualifiedExpressionForSelector())?.receiverExpression?.getResolvedCall(context)?.resultingDescriptor?.returnType
?: lastCall.implicitReceiver(context)?.type
if (receiverType?.isIterable(DefaultBuiltIns.Instance) != true) return null if (receiverType?.isIterable(DefaultBuiltIns.Instance) != true) return null
val firstCall = calls.first() val firstCall = calls.first()
@@ -186,10 +184,6 @@ private fun KtQualifiedExpression.collectCallExpression(context: BindingContext)
return transformationCalls return transformationCalls
} }
private fun KtExpression.implicitReceiver(context: BindingContext): ImplicitReceiver? {
return getResolvedCall(context)?.getImplicitReceiverValue()
}
private fun KtCallExpression.hasReturn(): Boolean = valueArguments.any { arg -> private fun KtCallExpression.hasReturn(): Boolean = valueArguments.any { arg ->
arg.anyDescendantOfType<KtReturnExpression> { it.labelQualifier == null } arg.anyDescendantOfType<KtReturnExpression> { it.labelQualifier == null }
} }
@@ -23,17 +23,20 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getType import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.resolve.calls.model.ReceiverKotlinCallArgument import org.jetbrains.kotlin.resolve.calls.model.ReceiverKotlinCallArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedLambdaAtom import org.jetbrains.kotlin.resolve.calls.model.ResolvedLambdaAtom
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue
import org.jetbrains.kotlin.resolve.calls.tower.NewResolvedCallImpl import org.jetbrains.kotlin.resolve.calls.tower.NewResolvedCallImpl
import org.jetbrains.kotlin.resolve.calls.tower.receiverValue import org.jetbrains.kotlin.resolve.calls.tower.receiverValue
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -85,4 +88,13 @@ fun ResolvedCall<*>.hasLastFunctionalParameterWithResult(context: BindingContext
if (!functionalType.isFunctionOfAnyKind()) return false if (!functionalType.isFunctionOfAnyKind()) return false
val resultType = functionalType.arguments.lastOrNull()?.type ?: return false val resultType = functionalType.arguments.lastOrNull()?.type ?: return false
return predicate(resultType) return predicate(resultType)
}
fun KtCallExpression.implicitReceiver(context: BindingContext): ImplicitReceiver? {
return getResolvedCall(context)?.getImplicitReceiverValue()
}
fun KtCallExpression.receiverType(context: BindingContext): KotlinType? {
return (getQualifiedExpressionForSelector())?.receiverExpression?.getResolvedCall(context)?.resultingDescriptor?.returnType
?: implicitReceiver(context)?.type
} }
@@ -0,0 +1,6 @@
// PROBLEM: none
// WITH_RUNTIME
fun foo(iterable: Iterable<String>) {
iterable.<caret>count()
}
@@ -0,0 +1,6 @@
// PROBLEM: none
// WITH_RUNTIME
fun foo(iterable: Iterable<String>?) {
iterable?.<caret>count()
}
@@ -0,0 +1,8 @@
// PROBLEM: none
// WITH_RUNTIME
fun foo(iterable: Iterable<String>) {
iterable.run {
<caret>count()
}
}
@@ -9849,6 +9849,21 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/replaceCollectionCountWithSize/countOfCollection.kt"); runTest("idea/testData/inspectionsLocal/replaceCollectionCountWithSize/countOfCollection.kt");
} }
@TestMetadata("countOfIterable.kt")
public void testCountOfIterable() throws Exception {
runTest("idea/testData/inspectionsLocal/replaceCollectionCountWithSize/countOfIterable.kt");
}
@TestMetadata("countOfIterable2.kt")
public void testCountOfIterable2() throws Exception {
runTest("idea/testData/inspectionsLocal/replaceCollectionCountWithSize/countOfIterable2.kt");
}
@TestMetadata("countOfIterable3.kt")
public void testCountOfIterable3() throws Exception {
runTest("idea/testData/inspectionsLocal/replaceCollectionCountWithSize/countOfIterable3.kt");
}
@TestMetadata("countOfMap.kt") @TestMetadata("countOfMap.kt")
public void testCountOfMap() throws Exception { public void testCountOfMap() throws Exception {
runTest("idea/testData/inspectionsLocal/replaceCollectionCountWithSize/countOfMap.kt"); runTest("idea/testData/inspectionsLocal/replaceCollectionCountWithSize/countOfMap.kt");