Redundant override inspection: don't report for ambiguous derivation

#KT-24405 Fixed
#KT-25883 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-09-04 15:45:26 +03:00
parent 607392a2ab
commit dead2516c2
7 changed files with 105 additions and 17 deletions
@@ -8,13 +8,16 @@ package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.* import com.intellij.codeInspection.*
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
class KotlinRedundantOverrideInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool { class KotlinRedundantOverrideInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) = override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) =
@@ -49,7 +52,7 @@ class KotlinRedundantOverrideInspection : AbstractKotlinInspection(), CleanupLoc
val superCallElement = qualifiedExpression.selectorExpression as? KtCallElement ?: return val superCallElement = qualifiedExpression.selectorExpression as? KtCallElement ?: return
if (!isSameFunctionName(superCallElement, function)) return if (!isSameFunctionName(superCallElement, function)) return
if (!isSameArguments(superCallElement, function)) return if (!isSameArguments(superCallElement, function)) return
if (function.isDefinedInDelegatedSuperType(qualifiedExpression)) return if (function.isAmbiguouslyDerived()) return
val descriptor = holder.manager.createProblemDescriptor( val descriptor = holder.manager.createProblemDescriptor(
function, function,
@@ -90,23 +93,31 @@ class KotlinRedundantOverrideInspection : AbstractKotlinInspection(), CleanupLoc
} }
} }
private fun KtNamedFunction.isDefinedInDelegatedSuperType(superQualifiedExpression: KtDotQualifiedExpression): Boolean { private fun KtNamedFunction.isAmbiguouslyDerived(): Boolean {
val context = analyze()
val original = context[BindingContext.FUNCTION, this]?.original
val overriddenDescriptors = original?.overriddenDescriptors ?: return false
if (overriddenDescriptors.size < 2) return false
// Two+ functions
// At least one default in interface or abstract in class, or just something from Java
if (overriddenDescriptors.any { overriddenFunction ->
overriddenFunction is JavaMethodDescriptor || when ((overriddenFunction.containingDeclaration as? ClassDescriptor)?.kind) {
ClassKind.CLASS -> overriddenFunction.modality == Modality.ABSTRACT
ClassKind.INTERFACE -> overriddenFunction.modality != Modality.ABSTRACT
else -> false
}
}
) return true
val delegatedSuperTypeEntries = val delegatedSuperTypeEntries =
containingClassOrObject?.superTypeListEntries?.filterIsInstance<KtDelegatedSuperTypeEntry>() ?: return false containingClassOrObject?.superTypeListEntries?.filterIsInstance<KtDelegatedSuperTypeEntry>() ?: return false
if (delegatedSuperTypeEntries.isEmpty()) return false if (delegatedSuperTypeEntries.isEmpty()) return false
val delegatedSuperDeclarations = delegatedSuperTypeEntries.mapNotNull { entry ->
val context = superQualifiedExpression.analyze() context[BindingContext.TYPE, entry.typeReference]?.constructor?.declarationDescriptor
val delegatedSuperTypes = delegatedSuperTypeEntries.mapNotNull { entry ->
context[BindingContext.TYPE, entry.typeReference]
} }
return overriddenDescriptors.any {
val superResolvedCall = superQualifiedExpression.getResolvedCall(context) ?: return false it.containingDeclaration in delegatedSuperDeclarations
val superCallResolvedDescriptor = superResolvedCall.resultingDescriptor
val superCallResolvedReceiverTypes = superCallResolvedDescriptor.overriddenDescriptors.mapNotNull { it.dispatchReceiverParameter?.type }
return delegatedSuperTypes.any { delegatedSuperType ->
superCallResolvedReceiverTypes.any { superCallReceiverType ->
delegatedSuperType.isSubtypeOf(superCallReceiverType)
}
} }
} }
@@ -0,0 +1,13 @@
// PROBLEM: none
abstract class AbstractClass {
abstract fun foo(): Int
}
interface Interface {
fun foo(): Int = 3
}
class ChildClass : AbstractClass(), Interface {
override <caret>fun foo() = super.foo()
}
@@ -0,0 +1,10 @@
// PROBLEM: none
// WITH_RUNTIME
interface Foo {
fun add(i: Int): Boolean
}
class Bar: ArrayList<Int>(), Foo {
override <caret>fun add(i: Int) = super.add(i)
}
@@ -0,0 +1,11 @@
open class Class {
open fun foo(): Int = 4
}
interface Interface {
fun foo(): Int
}
class ChildClass : Class(), Interface {
override <caret>fun foo() = super.foo()
}
@@ -0,0 +1,10 @@
open class Class {
open fun foo(): Int = 4
}
interface Interface {
fun foo(): Int
}
class ChildClass : Class(), Interface {
}
@@ -0,0 +1,13 @@
// PROBLEM: none
interface Foo {
fun test()
}
interface Gav {
fun test() {}
}
class TwoInterfaces : Foo, Gav {
override <caret>fun test() = super.test()
}
@@ -4027,6 +4027,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
} }
@TestMetadata("abstractClassAndInterface.kt")
public void testAbstractClassAndInterface() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantOverride/abstractClassAndInterface.kt");
}
public void testAllFilesPresentInRedundantOverride() throws Exception { public void testAllFilesPresentInRedundantOverride() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/redundantOverride"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/redundantOverride"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
} }
@@ -4051,11 +4056,21 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/redundantOverride/basic.kt"); runTest("idea/testData/inspectionsLocal/redundantOverride/basic.kt");
} }
@TestMetadata("boxedParameters.kt")
public void testBoxedParameters() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantOverride/boxedParameters.kt");
}
@TestMetadata("callDifferentSuperMethod.kt") @TestMetadata("callDifferentSuperMethod.kt")
public void testCallDifferentSuperMethod() throws Exception { public void testCallDifferentSuperMethod() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantOverride/callDifferentSuperMethod.kt"); runTest("idea/testData/inspectionsLocal/redundantOverride/callDifferentSuperMethod.kt");
} }
@TestMetadata("classAndInterface.kt")
public void testClassAndInterface() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantOverride/classAndInterface.kt");
}
@TestMetadata("dataClass.kt") @TestMetadata("dataClass.kt")
public void testDataClass() throws Exception { public void testDataClass() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantOverride/dataClass.kt"); runTest("idea/testData/inspectionsLocal/redundantOverride/dataClass.kt");
@@ -4101,6 +4116,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/redundantOverride/singleExpressionFunction.kt"); runTest("idea/testData/inspectionsLocal/redundantOverride/singleExpressionFunction.kt");
} }
@TestMetadata("twoInterfaces.kt")
public void testTwoInterfaces() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantOverride/twoInterfaces.kt");
}
@TestMetadata("useGenericsSuper.kt") @TestMetadata("useGenericsSuper.kt")
public void testUseGenericsSuper() throws Exception { public void testUseGenericsSuper() throws Exception {
runTest("idea/testData/inspectionsLocal/redundantOverride/useGenericsSuper.kt"); runTest("idea/testData/inspectionsLocal/redundantOverride/useGenericsSuper.kt");