[Kotlin] fix false positives in the "redundant override" inspection

Cover cases with interface implementation by delegation:
— Do not require two overridden symbols: both the delegate and the
explicit override can refer to the same interface method with a
default implementation.
— Fix subtyping check: delegates override members from the implemented
interface and its supertypes, not subtypes.

KTIJ-16228

GitOrigin-RevId: 93dde1f05141098c359e3e7d4f15a5a63083f44c
This commit is contained in:
Pavel Kirpichenkov
2025-04-15 07:41:05 +00:00
committed by intellij-monorepo-bot
parent 92f5232120
commit c94b90dfe8
9 changed files with 206 additions and 19 deletions
@@ -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<KaCallableSymbol>,
): 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<KaCallableSymbol>,
): Boolean {
val superTypeListEntries = function.containingClassOrObject?.superTypeListEntries
val delegatedSuperTypeEntries =
superTypeListEntries?.filterIsInstance<KtDelegatedSuperTypeEntry>()?.ifEmpty { return false } ?: return false
superTypeListEntries.orEmpty().filterIsInstance<KtDelegatedSuperTypeEntry>().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) }
}
}
}
@@ -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");
@@ -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");
@@ -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"
<caret>override fun test() {
super.test()
}
}
@@ -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 {
<caret>override fun bar() {
super.bar()
}
}
@@ -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 {
<caret>override fun bar() {
super.bar()
}
}
@@ -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 {
}
@@ -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"
<caret>override fun print() {
super.print()
}
}
class DerivedNoOverride(b: Base) : Base by b {
override val message = "DerivedNoOverride"
}
@@ -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 {
<caret>override fun foo() {
super.foo()
}
}
object FooImpl : Foo {
override fun foo() {
println("Impl")
}
}