[FIR] Support diagnostic QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE

This commit is contained in:
Nick
2020-04-20 01:46:29 +03:00
committed by Dmitriy Novozhilov
parent 7a928dfed2
commit 96238cbe1f
14 changed files with 222 additions and 62 deletions
@@ -0,0 +1,34 @@
interface IBase<T> {
fun foo() {}
fun bar() {}
}
typealias AliasedIBase1 = IBase<String>
typealias AliasedIBase = AliasedIBase1
interface IDerived<T> : IBase<T> {
override fun foo() {}
fun qux() {}
}
class Test : IDerived<String>, IBase<String> {
fun test() {
super<<!QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE!>IBase<!>>.foo()
super<<!QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE!>IBase<!>>.bar()
super<IDerived>.foo()
super<IDerived>.bar()
super<IDerived>.qux()
}
}
class Test2 : IDerived<String>, AliasedIBase {
fun test() {
super<<!QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE!>IBase<!>>.foo()
super<<!QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE!>IBase<!>>.bar()
super<<!QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE!>AliasedIBase<!>>.foo()
super<<!QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE!>AliasedIBase<!>>.bar()
super<IDerived>.foo()
super<IDerived>.bar()
super<IDerived>.qux()
}
}
@@ -0,0 +1,49 @@
FILE: qualifiedSupertypeExtendedByOtherSupertype.kt
public abstract interface IBase<T> : R|kotlin/Any| {
public open fun foo(): R|kotlin/Unit| {
}
public open fun bar(): R|kotlin/Unit| {
}
}
public final typealias AliasedIBase1 = R|IBase<kotlin/String>|
public final typealias AliasedIBase = R|AliasedIBase1|
public abstract interface IDerived<T> : R|IBase<T>| {
public open override fun foo(): R|kotlin/Unit| {
}
public open fun qux(): R|kotlin/Unit| {
}
}
public final class Test : R|IDerived<kotlin/String>|, R|IBase<kotlin/String>| {
public constructor(): R|Test| {
super<R|kotlin/Any|>()
}
public final fun test(): R|kotlin/Unit| {
super<R|IBase|>.R|/IBase.foo|()
super<R|IBase|>.R|/IBase.bar|()
super<R|IDerived|>.R|/IDerived.foo|()
super<R|IDerived|>.R|/IBase.bar|()
super<R|IDerived|>.R|/IDerived.qux|()
}
}
public final class Test2 : R|IDerived<kotlin/String>|, R|AliasedIBase| {
public constructor(): R|Test2| {
super<R|kotlin/Any|>()
}
public final fun test(): R|kotlin/Unit| {
super<R|IBase|>.R|/IBase.foo|()
super<R|IBase|>.R|/IBase.bar|()
super<R|AliasedIBase|>.R|/IBase.foo|()
super<R|AliasedIBase|>.R|/IBase.bar|()
super<R|IDerived|>.R|/IDerived.foo|()
super<R|IDerived|>.R|/IBase.bar|()
super<R|IDerived|>.R|/IDerived.qux|()
}
}
@@ -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");
@@ -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");
@@ -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<FirClass<*>>): Boolean {
for (it in other.superTypeRefs) {
val that = it.firClassLike(session) as? FirClass<*>
val that = it.firClassLike(session)
?.followAllAlias(session)
?.safeAs<FirClass<*>>()
?: 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<FirClass<*>>): Boolean {
for (it in other.superTypeRefs) {
val that = it.firClassLike(session) as? FirClass<*>
val candidate = it.firClassLike(session)
?.followAllAlias(session)
?.safeAs<FirClass<*>>()
?: 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<FirTypeAlias>()
?.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
}
@@ -14,7 +14,8 @@ object CallCheckers {
FirSuperNotAvailableChecker,
FirNotASupertypeChecker,
FirSuperclassNotAccessibleFromInterfaceChecker,
FirAbstractSuperCallChecker
FirAbstractSuperCallChecker,
FirQualifiedSupertypeExtendedByOtherSupertypeChecker,
) + EXPRESSIONS
val FUNCTION_CALLS: List<FirFunctionCallChecker> = listOf<FirFunctionCallChecker>(
@@ -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<FirSuperReference>()
?: return
val explicitType = superReference.superTypeRef.safeAs<FirResolvedTypeRef>()
?.firClassLike(context.session)
?.followAllAlias(context.session).safeAs<FirClass<*>>()
?: 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<FirClass<*>>()
?: 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))
}
}
}
@@ -44,6 +44,7 @@ object FirErrors {
val NOT_A_SUPERTYPE by error0<FirSourceElement, PsiElement>()
val SUPERCLASS_NOT_ACCESSIBLE_FROM_INTERFACE by error0<FirSourceElement, PsiElement>()
val ABSTRACT_SUPER_CALL by error0<FirSourceElement, PsiElement>()
val QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE by error0<FirSourceElement, PsiElement>()
val INAPPLICABLE_INFIX_MODIFIER by existing<FirSourceElement, PsiElement, String>(Errors.INAPPLICABLE_INFIX_MODIFIER)
val CONSTRUCTOR_IN_OBJECT by existing<FirSourceElement, KtDeclaration>(Errors.CONSTRUCTOR_IN_OBJECT)
@@ -1,19 +0,0 @@
interface IBase<T> {
fun foo() {}
fun bar() {}
}
interface IDerived<T> : IBase<T> {
override fun foo() {}
fun qux() {}
}
class Test : IDerived<String>, IBase<String> {
fun test() {
super<IBase>.foo()
super<IBase>.bar()
super<IDerived>.foo()
super<IDerived>.bar()
super<IDerived>.qux()
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
interface IBase<T> {
fun foo() {}
fun bar() {}
@@ -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<IBase>.foo()
super<IBase>.bar()
super<IDerived1>.foo()
super<IDerived1>.bar()
super<IDerived1>.qux()
super<IDerived2>.foo()
super<IDerived2>.bar()
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
interface IBase {
fun foo() {}
fun bar() {}
@@ -20,7 +20,7 @@ class TestSuperForBase : B() {
typealias MyBase = B
override fun foo() {
<!NOT_A_SUPERTYPE!>super<Base><!>.foo()
super<Base>.foo()
super<B>.foo()
super<MyBase>.<!UNRESOLVED_REFERENCE!>foo<!>()
super<U>.foo()
@@ -32,7 +32,7 @@ class TestSuperForGenericBase<T> : GB<T>() {
typealias MyBaseInt = GB<Int>
override fun foo() {
<!NOT_A_SUPERTYPE!>super<GenericBase><!>.foo()
super<GenericBase>.foo()
super<GB>.foo()
super<MyBase>.<!UNRESOLVED_REFERENCE!>foo<!>()
super<MyBaseInt>.<!UNRESOLVED_REFERENCE!>foo<!>() // Type arguments don't matter here
@@ -40,7 +40,7 @@ open class Bar2 : Bar() {
class ManySupers: Foo2(), B {
fun foo() {
super<Foo2>.test()
super<B>.test()
super<<!QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE!>B<!>>.test()
super.test()
}
}