Redundant override inspection: don't report for ambiguous derivation
#KT-24405 Fixed #KT-25883 Fixed
This commit is contained in:
+28
-17
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
@@ -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()
|
||||||
|
}
|
||||||
+20
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user