Simplify call: Fix false positive in "filter {}" detection (KT-32468)

"Simplify filter {}" conversion changes semantics when the casted type is not a subtype if an initial collection element type.
This commit limits a replacement suggestion to subtype cases.
This commit is contained in:
Yan Zhulanow
2019-07-16 16:05:29 +09:00
parent b1f132d750
commit a8d08815a6
4 changed files with 42 additions and 0 deletions
@@ -11,13 +11,18 @@ import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
class SimplifiableCallInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) =
@@ -103,6 +108,19 @@ class SimplifiableCallInspection : AbstractKotlinInspection() {
if (statement.isNegated) return null
if (!statement.leftHandSide.isNameReferenceTo(lambdaParameterName)) return null
val rightTypeReference = statement.typeReference ?: return null
val bindingContext = callExpression.analyze(BodyResolveMode.PARTIAL)
val rightType = bindingContext[BindingContext.TYPE, rightTypeReference]
val resolvedCall = callExpression.getResolvedCall(bindingContext)
if (resolvedCall != null && rightType != null) {
val resultingElementType = resolvedCall.resultingDescriptor.returnType
?.arguments?.singleOrNull()?.takeIf { !it.isStarProjection }?.type
if (resultingElementType != null && !rightType.isSubtypeOf(resultingElementType)) {
return null
}
}
return "filterIsInstance<${rightTypeReference.text}>()"
}
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
// PROBLEM: none
interface Foo
interface Bar
fun instanceOfMarkerInterface(x: List<Foo>): List<Foo> = x.<caret>filter { it is Bar }
@@ -0,0 +1,7 @@
// WITH_RUNTIME
// PROBLEM: none
interface Foo
interface Bar : Foo
fun instanceOfMarkerInterface(x: List<Bar>): List<Bar> = x.<caret>filter { it is Foo }
@@ -1477,6 +1477,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/explicitLambdaParameter.kt");
}
@TestMetadata("filterIsInstanceDisjointHierarchy.kt")
public void testFilterIsInstanceDisjointHierarchy() throws Exception {
runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/filterIsInstanceDisjointHierarchy.kt");
}
@TestMetadata("filterIsInstanceReceiver.kt")
public void testFilterIsInstanceReceiver() throws Exception {
runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/filterIsInstanceReceiver.kt");
@@ -1487,6 +1492,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/filterIsInstanceSimple.kt");
}
@TestMetadata("filterIsInstanceSupertype.kt")
public void testFilterIsInstanceSupertype() throws Exception {
runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/filterIsInstanceSupertype.kt");
}
@TestMetadata("filterIsNotInstance.kt")
public void testFilterIsNotInstance() throws Exception {
runTest("idea/testData/inspectionsLocal/collections/simplifiableCall/filterIsNotInstance.kt");