diff --git a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/KotlinRedundantOverrideInspection.kt b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/KotlinRedundantOverrideInspection.kt index c7f4b4f45a35..de5e2420f9ff 100644 --- a/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/KotlinRedundantOverrideInspection.kt +++ b/plugins/kotlin/code-insight/inspections-shared/src/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/KotlinRedundantOverrideInspection.kt @@ -45,8 +45,7 @@ internal class KotlinRedundantOverrideInspection : KotlinApplicableInspectionBas override fun isApplicableByPsi(element: KtNamedFunction): Boolean { val modifierList = element.modifierList ?: return false - val overrideKeyword = modifierList.getModifier(OVERRIDE_KEYWORD) - if (overrideKeyword == null) return false + if (modifierList.getModifier(OVERRIDE_KEYWORD) == null) return false if (MODIFIER_EXCLUDE_OVERRIDE.any { modifierList.hasModifier(it) }) return false if (KotlinPsiHeuristics.hasNonSuppressAnnotations(element)) return false @@ -111,7 +110,11 @@ internal class KotlinRedundantOverrideInspection : KotlinApplicableInspectionBas return null } - if (isAmbiguouslyDerived(element, allFunctionOverriddenSymbols)) { + if (isAmbiguouslyDerived(allFunctionOverriddenSymbols)) { + return null + } + + if (isOverridingDelegatedImplementation(element, allFunctionOverriddenSymbols)) { return null } @@ -177,37 +180,43 @@ internal class KotlinRedundantOverrideInspection : KotlinApplicableInspectionBas } private fun KaSession.isAmbiguouslyDerived( - function: KtNamedFunction, allFunctionOverriddenSymbols: Sequence, ): Boolean { // less than 2 functions if (allFunctionOverriddenSymbols.take(2).count() < 2) return false - // Two+ functions + // 2+ functions // At least one default in interface or abstract in class, or just something from Java - if (allFunctionOverriddenSymbols.any { overriddenSymbol -> - val javaSourceOrLibrary = overriddenSymbol.origin.isJavaSourceOrLibrary() - - val kind = (overriddenSymbol.containingDeclaration as? KaNamedClassSymbol)?.classKind - javaSourceOrLibrary || when (kind) { - KaClassKind.CLASS -> overriddenSymbol.modality == KaSymbolModality.ABSTRACT - KaClassKind.INTERFACE -> overriddenSymbol.modality != KaSymbolModality.ABSTRACT - else -> false - } + return allFunctionOverriddenSymbols.any { overriddenSymbol -> + val javaSourceOrLibrary = overriddenSymbol.origin.isJavaSourceOrLibrary() + val kind = (overriddenSymbol.containingDeclaration as? KaNamedClassSymbol)?.classKind + javaSourceOrLibrary || when (kind) { + KaClassKind.CLASS -> overriddenSymbol.modality == KaSymbolModality.ABSTRACT + KaClassKind.INTERFACE -> overriddenSymbol.modality != KaSymbolModality.ABSTRACT + else -> false } - ) { - return true } + } + /** + * Don't mark an override unused if it overrides an implementation by delegation. + * + * Members from interfaces implemented by delegation and their super interfaces are affected. + * Explicit overrides in this case replace the overrides from delegation and are not unused. + */ + private fun KaSession.isOverridingDelegatedImplementation( + function: KtNamedFunction, + allFunctionOverriddenSymbols: Sequence, + ): Boolean { val superTypeListEntries = function.containingClassOrObject?.superTypeListEntries val delegatedSuperTypeEntries = - superTypeListEntries?.filterIsInstance()?.ifEmpty { return false } ?: return false + superTypeListEntries.orEmpty().filterIsInstance().ifEmpty { return false } val delegatedSuperDeclarationTypes = delegatedSuperTypeEntries.mapNotNull { it.typeReference?.type } return allFunctionOverriddenSymbols.any { overriddenSymbol -> - val type = (overriddenSymbol.containingSymbol as? KaNamedClassSymbol)?.defaultType ?: return@any false - delegatedSuperDeclarationTypes.any { type.isSubtypeOf(it) } + val containingSymbolType = (overriddenSymbol.containingSymbol as? KaNamedClassSymbol)?.defaultType ?: return@any false + delegatedSuperDeclarationTypes.any { delegatedIFaceType -> delegatedIFaceType.isSubtypeOf(containingSymbolType) } } } } diff --git a/plugins/kotlin/code-insight/inspections-shared/tests/k1/test/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/SharedK1LocalInspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-shared/tests/k1/test/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/SharedK1LocalInspectionTestGenerated.java index 76346f89370a..eb25e923d832 100644 --- a/plugins/kotlin/code-insight/inspections-shared/tests/k1/test/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/SharedK1LocalInspectionTestGenerated.java +++ b/plugins/kotlin/code-insight/inspections-shared/tests/k1/test/org/jetbrains/kotlin/idea/codeInsight/inspections/shared/SharedK1LocalInspectionTestGenerated.java @@ -1879,6 +1879,21 @@ public abstract class SharedK1LocalInspectionTestGenerated extends AbstractShare runTest("../testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride4.kt"); } + @TestMetadata("delegatedMemberHidesSuperTypeOverride5.kt") + public void testDelegatedMemberHidesSuperTypeOverride5() throws Exception { + runTest("../testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride5.kt"); + } + + @TestMetadata("delegatedMemberHidesSuperTypeOverride6.kt") + public void testDelegatedMemberHidesSuperTypeOverride6() throws Exception { + runTest("../testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride6.kt"); + } + + @TestMetadata("delegatedMemberHidesSuperTypeOverride7.kt") + public void testDelegatedMemberHidesSuperTypeOverride7() throws Exception { + runTest("../testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride7.kt"); + } + @TestMetadata("equals.kt") public void testEquals() throws Exception { runTest("../testData/inspectionsLocal/redundantOverride/equals.kt"); @@ -1939,6 +1954,16 @@ public abstract class SharedK1LocalInspectionTestGenerated extends AbstractShare runTest("../testData/inspectionsLocal/redundantOverride/notCallSuper.kt"); } + @TestMetadata("overrideInterfaceDelegation.kt") + public void testOverrideInterfaceDelegation() throws Exception { + runTest("../testData/inspectionsLocal/redundantOverride/overrideInterfaceDelegation.kt"); + } + + @TestMetadata("overrideInterfaceDelegation2.kt") + public void testOverrideInterfaceDelegation2() throws Exception { + runTest("../testData/inspectionsLocal/redundantOverride/overrideInterfaceDelegation2.kt"); + } + @TestMetadata("overrideModifireFinal.kt") public void testOverrideModifireFinal() throws Exception { runTest("../testData/inspectionsLocal/redundantOverride/overrideModifireFinal.kt"); diff --git a/plugins/kotlin/code-insight/inspections-shared/tests/k2/test/org/jetbrains/kotlin/idea/k2/codeInsight/inspections/shared/SharedK2LocalInspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-shared/tests/k2/test/org/jetbrains/kotlin/idea/k2/codeInsight/inspections/shared/SharedK2LocalInspectionTestGenerated.java index a633dd31b90d..3d8b64c127f6 100644 --- a/plugins/kotlin/code-insight/inspections-shared/tests/k2/test/org/jetbrains/kotlin/idea/k2/codeInsight/inspections/shared/SharedK2LocalInspectionTestGenerated.java +++ b/plugins/kotlin/code-insight/inspections-shared/tests/k2/test/org/jetbrains/kotlin/idea/k2/codeInsight/inspections/shared/SharedK2LocalInspectionTestGenerated.java @@ -1879,6 +1879,21 @@ public abstract class SharedK2LocalInspectionTestGenerated extends AbstractShare runTest("../testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride4.kt"); } + @TestMetadata("delegatedMemberHidesSuperTypeOverride5.kt") + public void testDelegatedMemberHidesSuperTypeOverride5() throws Exception { + runTest("../testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride5.kt"); + } + + @TestMetadata("delegatedMemberHidesSuperTypeOverride6.kt") + public void testDelegatedMemberHidesSuperTypeOverride6() throws Exception { + runTest("../testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride6.kt"); + } + + @TestMetadata("delegatedMemberHidesSuperTypeOverride7.kt") + public void testDelegatedMemberHidesSuperTypeOverride7() throws Exception { + runTest("../testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride7.kt"); + } + @TestMetadata("equals.kt") public void testEquals() throws Exception { runTest("../testData/inspectionsLocal/redundantOverride/equals.kt"); @@ -1939,6 +1954,16 @@ public abstract class SharedK2LocalInspectionTestGenerated extends AbstractShare runTest("../testData/inspectionsLocal/redundantOverride/notCallSuper.kt"); } + @TestMetadata("overrideInterfaceDelegation.kt") + public void testOverrideInterfaceDelegation() throws Exception { + runTest("../testData/inspectionsLocal/redundantOverride/overrideInterfaceDelegation.kt"); + } + + @TestMetadata("overrideInterfaceDelegation2.kt") + public void testOverrideInterfaceDelegation2() throws Exception { + runTest("../testData/inspectionsLocal/redundantOverride/overrideInterfaceDelegation2.kt"); + } + @TestMetadata("overrideModifireFinal.kt") public void testOverrideModifireFinal() throws Exception { runTest("../testData/inspectionsLocal/redundantOverride/overrideModifireFinal.kt"); diff --git a/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride5.kt b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride5.kt new file mode 100644 index 000000000000..3daeef56e9fd --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride5.kt @@ -0,0 +1,18 @@ +// PROBLEM: none +// WITH_STDLIB + +interface Foo { + val message: String + fun test() { println(message) } +} + +class FooImpl : Foo { + override val message = "FooImpl" +} + +class Bar(foo: Foo) : Foo by foo { + override val message = "Bar" + override fun test() { + super.test() + } +} diff --git a/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride6.kt b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride6.kt new file mode 100644 index 000000000000..1b0aa09b5c8c --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride6.kt @@ -0,0 +1,22 @@ +// PROBLEM: none +// WITH_STDLIB + +interface Foo { + fun foo() +} + +interface Bar : Foo { + fun bar() +} + +abstract class Baz : Bar { + override fun bar() { + println("baz") + } +} + +class Boo(bar: Bar) : Baz(), Bar by bar { + override fun bar() { + super.bar() + } +} diff --git a/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride7.kt b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride7.kt new file mode 100644 index 000000000000..5362a52f7068 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride7.kt @@ -0,0 +1,21 @@ +// WITH_STDLIB + +interface Foo { + fun foo() +} + +interface Bar : Foo { + fun bar() +} + +abstract class Baz : Bar { + override fun bar() { + println("baz") + } +} + +class Boo(bar: Bar) : Baz(), Foo by bar { + override fun bar() { + super.bar() + } +} diff --git a/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride7.kt.after b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride7.kt.after new file mode 100644 index 000000000000..ff91932045a4 --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/delegatedMemberHidesSuperTypeOverride7.kt.after @@ -0,0 +1,18 @@ +// WITH_STDLIB + +interface Foo { + fun foo() +} + +interface Bar : Foo { + fun bar() +} + +abstract class Baz : Bar { + override fun bar() { + println("baz") + } +} + +class Boo(bar: Bar) : Baz(), Foo by bar { +} diff --git a/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/overrideInterfaceDelegation.kt b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/overrideInterfaceDelegation.kt new file mode 100644 index 000000000000..76058549af3a --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/overrideInterfaceDelegation.kt @@ -0,0 +1,26 @@ +// PROBLEM: none +// WITH_STDLIB + +interface Base { + val message: String + + fun print() { + println(message) + } +} + +class BaseImpl : Base { + override val message = "BaseImpl" +} + +class DerivedWithOverride(b: Base) : Base by b { + override val message = "DerivedWithOverride" + + override fun print() { + super.print() + } +} + +class DerivedNoOverride(b: Base) : Base by b { + override val message = "DerivedNoOverride" +} diff --git a/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/overrideInterfaceDelegation2.kt b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/overrideInterfaceDelegation2.kt new file mode 100644 index 000000000000..bd2f45c88d0c --- /dev/null +++ b/plugins/kotlin/code-insight/inspections-shared/tests/testData/inspectionsLocal/redundantOverride/overrideInterfaceDelegation2.kt @@ -0,0 +1,23 @@ +// PROBLEM: none +// WITH_STDLIB +interface Foo { + fun foo() +} + +open class C1 : Foo { + override fun foo() { + println("C1") + } +} + +class C2(delegate: Foo) : C1(), Foo by delegate { + override fun foo() { + super.foo() + } +} + +object FooImpl : Foo { + override fun foo() { + println("Impl") + } +}