diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/qualifiedSupertypeExtendedByOtherSupertype.kt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/qualifiedSupertypeExtendedByOtherSupertype.kt new file mode 100644 index 00000000000..946667303a7 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/qualifiedSupertypeExtendedByOtherSupertype.kt @@ -0,0 +1,34 @@ +interface IBase { + fun foo() {} + fun bar() {} +} + +typealias AliasedIBase1 = IBase +typealias AliasedIBase = AliasedIBase1 + +interface IDerived : IBase { + override fun foo() {} + fun qux() {} +} + +class Test : IDerived, IBase { + fun test() { + super<IBase>.foo() + super<IBase>.bar() + super.foo() + super.bar() + super.qux() + } +} + +class Test2 : IDerived, AliasedIBase { + fun test() { + super<IBase>.foo() + super<IBase>.bar() + super<AliasedIBase>.foo() + super<AliasedIBase>.bar() + super.foo() + super.bar() + super.qux() + } +} \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/qualifiedSupertypeExtendedByOtherSupertype.txt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/qualifiedSupertypeExtendedByOtherSupertype.txt new file mode 100644 index 00000000000..782edfa9c7e --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/qualifiedSupertypeExtendedByOtherSupertype.txt @@ -0,0 +1,49 @@ +FILE: qualifiedSupertypeExtendedByOtherSupertype.kt + public abstract interface IBase : R|kotlin/Any| { + public open fun foo(): R|kotlin/Unit| { + } + + public open fun bar(): R|kotlin/Unit| { + } + + } + public final typealias AliasedIBase1 = R|IBase| + public final typealias AliasedIBase = R|AliasedIBase1| + public abstract interface IDerived : R|IBase| { + public open override fun foo(): R|kotlin/Unit| { + } + + public open fun qux(): R|kotlin/Unit| { + } + + } + public final class Test : R|IDerived|, R|IBase| { + public constructor(): R|Test| { + super() + } + + public final fun test(): R|kotlin/Unit| { + super.R|/IBase.foo|() + super.R|/IBase.bar|() + super.R|/IDerived.foo|() + super.R|/IBase.bar|() + super.R|/IDerived.qux|() + } + + } + public final class Test2 : R|IDerived|, R|AliasedIBase| { + public constructor(): R|Test2| { + super() + } + + public final fun test(): R|kotlin/Unit| { + super.R|/IBase.foo|() + super.R|/IBase.bar|() + super.R|/IBase.foo|() + super.R|/IBase.bar|() + super.R|/IDerived.foo|() + super.R|/IBase.bar|() + super.R|/IDerived.qux|() + } + + } diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java index 740632e5376..3e090f13683 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java @@ -826,6 +826,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest { runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/notASupertype.kt"); } + @TestMetadata("qualifiedSupertypeExtendedByOtherSupertype.kt") + public void testQualifiedSupertypeExtendedByOtherSupertype() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/qualifiedSupertypeExtendedByOtherSupertype.kt"); + } + @TestMetadata("redundantModifier.kt") public void testRedundantModifier() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/redundantModifier.kt"); diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java index 37149901e97..b50ded56eae 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java @@ -826,6 +826,11 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/notASupertype.kt"); } + @TestMetadata("qualifiedSupertypeExtendedByOtherSupertype.kt") + public void testQualifiedSupertypeExtendedByOtherSupertype() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/qualifiedSupertypeExtendedByOtherSupertype.kt"); + } + @TestMetadata("redundantModifier.kt") public void testRedundantModifier() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/redundantModifier.kt"); diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt index 7e37757f876..c8c1bebfa7d 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt @@ -9,9 +9,7 @@ import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSymbolOwner import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext -import org.jetbrains.kotlin.fir.declarations.FirClass -import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration -import org.jetbrains.kotlin.fir.declarations.FirRegularClass +import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.firSymbolProvider @@ -34,7 +32,9 @@ fun FirClass<*>.isSuperclassOf(other: FirClass<*>): Boolean { */ fun FirClass<*>.isSuperclassOf(other: FirClass<*>, exclude: MutableSet>): Boolean { for (it in other.superTypeRefs) { - val that = it.firClassLike(session) as? FirClass<*> + val that = it.firClassLike(session) + ?.followAllAlias(session) + ?.safeAs>() ?: continue if (that in exclude) { @@ -58,7 +58,7 @@ fun FirClass<*>.isSuperclassOf(other: FirClass<*>): Boolean { } /** - * Returns true if this is a superclass of other. + * Returns true if this is a supertype of other. */ fun FirClass<*>.isSupertypeOf(other: FirClass<*>): Boolean { /** @@ -66,20 +66,22 @@ fun FirClass<*>.isSupertypeOf(other: FirClass<*>): Boolean { */ fun FirClass<*>.isSupertypeOf(other: FirClass<*>, exclude: MutableSet>): Boolean { for (it in other.superTypeRefs) { - val that = it.firClassLike(session) as? FirClass<*> + val candidate = it.firClassLike(session) + ?.followAllAlias(session) + ?.safeAs>() ?: continue - if (that in exclude) { + if (candidate in exclude) { continue } - exclude.add(that) + exclude.add(candidate) - if (that == this) { + if (candidate == this) { return true } - if (this.isSupertypeOf(that, exclude)) { + if (this.isSupertypeOf(candidate, exclude)) { return true } } @@ -139,3 +141,47 @@ fun FirSymbolOwner<*>.getContainingClass(context: CheckerContext): FirClassLikeD return null } + +/** + * Returns the FirClassLikeDeclaration the type alias is pointing + * to provided `this` is a FirTypeAlias. Returns this otherwise. + */ +fun FirClassLikeDeclaration<*>.followAlias(session: FirSession): FirClassLikeDeclaration<*>? { + return this.safeAs() + ?.expandedTypeRef + ?.firClassLike(session) + ?: return this +} + +/** + * Returns the FirClassLikeDeclaration that the + * sequence of FirTypeAlias'es points to starting + * with `this`. Or null if something goes wrong. + */ +fun FirClassLikeDeclaration<*>.followAllAlias(session: FirSession): FirClassLikeDeclaration<*>? { + var it: FirClassLikeDeclaration<*>? = this + + while (it is FirTypeAlias) { + it = it.expandedTypeRef.firClassLike(session) + } + + return it +} + +/** + * Returns the closest to the end of context.containingDeclarations + * item like FirRegularClass or FirAnonymousObject + * or null if no such item could be found. + */ +fun CheckerContext.findClosestClassOrObject(): FirClass<*>? { + for (it in containingDeclarations.reversed()) { + if ( + it is FirRegularClass || + it is FirAnonymousObject + ) { + return it as FirClass<*> + } + } + + return null +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/call/CallCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/call/CallCheckers.kt index d57657f5180..798ec5c3b1e 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/call/CallCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/call/CallCheckers.kt @@ -14,7 +14,8 @@ object CallCheckers { FirSuperNotAvailableChecker, FirNotASupertypeChecker, FirSuperclassNotAccessibleFromInterfaceChecker, - FirAbstractSuperCallChecker + FirAbstractSuperCallChecker, + FirQualifiedSupertypeExtendedByOtherSupertypeChecker, ) + EXPRESSIONS val FUNCTION_CALLS: List = listOf( diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/call/FirQualifiedSupertypeExtendedByOtherSupertypeChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/call/FirQualifiedSupertypeExtendedByOtherSupertypeChecker.kt new file mode 100644 index 00000000000..864fed3c695 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/call/FirQualifiedSupertypeExtendedByOtherSupertypeChecker.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.analysis.checkers.call + +import org.jetbrains.kotlin.fir.FirSourceElement +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.findClosestClassOrObject +import org.jetbrains.kotlin.fir.analysis.checkers.followAllAlias +import org.jetbrains.kotlin.fir.analysis.checkers.isSupertypeOf +import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors +import org.jetbrains.kotlin.fir.declarations.FirClass +import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression +import org.jetbrains.kotlin.fir.references.FirSuperReference +import org.jetbrains.kotlin.fir.resolve.transformers.firClassLike +import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + +object FirQualifiedSupertypeExtendedByOtherSupertypeChecker : FirQualifiedAccessChecker() { + override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { + // require to be called over a super reference + val superReference = functionCall.calleeReference.safeAs() + ?: return + + val explicitType = superReference.superTypeRef.safeAs() + ?.firClassLike(context.session) + ?.followAllAlias(context.session).safeAs>() + ?: return + + val surroundingType = context.findClosestClassOrObject() + ?: return + + // how many supertypes of `surroundingType` + // have `explicitType` as their supertype or + // equal to it + var count = 0 + + for (it in surroundingType.superTypeRefs) { + val that = it.firClassLike(context.session) + ?.followAllAlias(context.session).safeAs>() + ?: continue + + if (explicitType == that || explicitType.isSupertypeOf(that)) { + count += 1 + + if (count >= 2) { + break + } + } + } + + if (count >= 2) { + reporter.report(superReference.superTypeRef.source) + } + } + + private fun DiagnosticReporter.report(source: FirSourceElement?) { + source?.let { + report(FirErrors.QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE.on(it)) + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 4583aa64f5a..366706ff463 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -44,6 +44,7 @@ object FirErrors { val NOT_A_SUPERTYPE by error0() val SUPERCLASS_NOT_ACCESSIBLE_FROM_INTERFACE by error0() val ABSTRACT_SUPER_CALL by error0() + val QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE by error0() val INAPPLICABLE_INFIX_MODIFIER by existing(Errors.INAPPLICABLE_INFIX_MODIFIER) val CONSTRUCTOR_IN_OBJECT by existing(Errors.CONSTRUCTOR_IN_OBJECT) diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/genericQualifiedSuperOverridden.fir.kt b/compiler/testData/diagnostics/tests/thisAndSuper/genericQualifiedSuperOverridden.fir.kt deleted file mode 100644 index a49b33534be..00000000000 --- a/compiler/testData/diagnostics/tests/thisAndSuper/genericQualifiedSuperOverridden.fir.kt +++ /dev/null @@ -1,19 +0,0 @@ -interface IBase { - fun foo() {} - fun bar() {} -} - -interface IDerived : IBase { - override fun foo() {} - fun qux() {} -} - -class Test : IDerived, IBase { - fun test() { - super.foo() - super.bar() - super.foo() - super.bar() - super.qux() - } -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/genericQualifiedSuperOverridden.kt b/compiler/testData/diagnostics/tests/thisAndSuper/genericQualifiedSuperOverridden.kt index 0086dfe65c5..a83da06a287 100644 --- a/compiler/testData/diagnostics/tests/thisAndSuper/genericQualifiedSuperOverridden.kt +++ b/compiler/testData/diagnostics/tests/thisAndSuper/genericQualifiedSuperOverridden.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL interface IBase { fun foo() {} fun bar() {} diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/qualifiedSuperOverridden.fir.kt b/compiler/testData/diagnostics/tests/thisAndSuper/qualifiedSuperOverridden.fir.kt deleted file mode 100644 index 7965bd13a7b..00000000000 --- a/compiler/testData/diagnostics/tests/thisAndSuper/qualifiedSuperOverridden.fir.kt +++ /dev/null @@ -1,29 +0,0 @@ -interface IBase { - fun foo() {} - fun bar() {} -} - -interface IDerived1 : IBase { - override fun foo() {} - fun qux() {} -} - -interface IDerived2 : IBase { - override fun foo() {} -} - -class Test : IDerived1, IBase, IDerived2 { - override fun foo() {} - - fun test() { - super.foo() - super.bar() - - super.foo() - super.bar() - super.qux() - - super.foo() - super.bar() - } -} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/thisAndSuper/qualifiedSuperOverridden.kt b/compiler/testData/diagnostics/tests/thisAndSuper/qualifiedSuperOverridden.kt index 23bacc0afcd..ec6a55eedbb 100644 --- a/compiler/testData/diagnostics/tests/thisAndSuper/qualifiedSuperOverridden.kt +++ b/compiler/testData/diagnostics/tests/thisAndSuper/qualifiedSuperOverridden.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL interface IBase { fun foo() {} fun bar() {} diff --git a/compiler/testData/diagnostics/tests/typealias/typeAliasAsSuperQualifier.fir.kt b/compiler/testData/diagnostics/tests/typealias/typeAliasAsSuperQualifier.fir.kt index 6bb4a6c32c7..39534bbba1b 100644 --- a/compiler/testData/diagnostics/tests/typealias/typeAliasAsSuperQualifier.fir.kt +++ b/compiler/testData/diagnostics/tests/typealias/typeAliasAsSuperQualifier.fir.kt @@ -20,7 +20,7 @@ class TestSuperForBase : B() { typealias MyBase = B override fun foo() { - super.foo() + super.foo() super.foo() super.foo() super.foo() @@ -32,7 +32,7 @@ class TestSuperForGenericBase : GB() { typealias MyBaseInt = GB override fun foo() { - super.foo() + super.foo() super.foo() super.foo() super.foo() // Type arguments don't matter here diff --git a/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/superCall.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/superCall.fir.kt index ad5f97039af..2100cb3dd5d 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/superCall.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmDefault/superCall.fir.kt @@ -40,7 +40,7 @@ open class Bar2 : Bar() { class ManySupers: Foo2(), B { fun foo() { super.test() - super.test() + super<B>.test() super.test() } }