[FIR] Implement RETURN_TYPE_MISMATCH_ON_INHERITANCE diagnostic

This commit is contained in:
Andrey Zinovyev
2021-06-10 18:45:30 +03:00
committed by teamcityserver
parent 8012eb3214
commit 94da1e37aa
21 changed files with 173 additions and 180 deletions
@@ -587,6 +587,12 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
parameter<Name>("containingClassName")
}
val RETURN_TYPE_MISMATCH_ON_INHERITANCE by error<KtClassOrObject>(PositioningStrategy.DECLARATION_NAME) {
parameter<FirClass<*>>("classOrObject")
parameter<FirCallableDeclaration<*>>("conflictingDeclaration1")
parameter<FirCallableDeclaration<*>>("conflictingDeclaration2")
}
val ABSTRACT_MEMBER_NOT_IMPLEMENTED by error<KtClassOrObject>(PositioningStrategy.DECLARATION_NAME) {
parameter<FirClass>("classOrObject")
parameter<FirCallableDeclaration>("missingDeclaration")
@@ -347,6 +347,7 @@ object FirErrors {
val CANNOT_WEAKEN_ACCESS_PRIVILEGE by error3<KtModifierListOwner, Visibility, FirCallableDeclaration, Name>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
val CANNOT_CHANGE_ACCESS_PRIVILEGE by error3<KtModifierListOwner, Visibility, FirCallableDeclaration, Name>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
val OVERRIDING_FINAL_MEMBER by error2<KtNamedDeclaration, FirCallableDeclaration, Name>(SourceElementPositioningStrategies.OVERRIDE_MODIFIER)
val RETURN_TYPE_MISMATCH_ON_INHERITANCE by error3<KtClassOrObject, FirClass<*>, FirCallableDeclaration<*>, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.DECLARATION_NAME)
val ABSTRACT_MEMBER_NOT_IMPLEMENTED by error2<KtClassOrObject, FirClass, FirCallableDeclaration>(SourceElementPositioningStrategies.DECLARATION_NAME)
val ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED by error2<KtClassOrObject, FirClass, FirCallableDeclaration>(SourceElementPositioningStrategies.DECLARATION_NAME)
val INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER by error2<KtClassOrObject, FirClass, FirCallableDeclaration>(SourceElementPositioningStrategies.DECLARATION_NAME)
@@ -66,6 +66,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
FirSealedSupertypeChecker,
FirMemberFunctionsChecker,
FirMemberPropertiesChecker,
FirImplementationMismatchChecker,
)
override val regularClassCheckers: Set<FirRegularClassChecker>
@@ -0,0 +1,96 @@
/*
* Copyright 2010-2021 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.declaration
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.impl.deduplicating
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.scopes.impl.delegatedWrapperData
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionCallableSymbol
import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.ConeKotlinErrorType
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.types.AbstractTypeChecker
object FirImplementationMismatchChecker : FirClassChecker() {
override fun check(declaration: FirClass<*>, context: CheckerContext, reporter: DiagnosticReporter) {
val source = declaration.source ?: return
val sourceKind = source.kind
if (sourceKind is FirFakeSourceElementKind && sourceKind != FirFakeSourceElementKind.EnumInitializer) return
if (declaration is FirRegularClass && declaration.isExpect) return
val classKind = declaration.classKind
if (classKind == ClassKind.ANNOTATION_CLASS || classKind == ClassKind.ENUM_CLASS) return
val typeCheckerContext = context.session.typeContext.newBaseTypeCheckerContext(
errorTypesEqualToAnything = false,
stubTypesEqualToAnything = false
)
val classScope = declaration.unsubstitutedScope(context)
val dedupReporter = reporter.deduplicating()
fun checkSymbol(symbol: FirCallableSymbol<*>) {
if (symbol.callableId.classId != declaration.classId) return
if (symbol !is FirIntersectionCallableSymbol) return
val withTypes = symbol.intersections.map {
it.fir to context.returnTypeCalculator.tryCalculateReturnType(it.fir).coneType
}
if (withTypes.any { it.second is ConeKotlinErrorType }) return
var delegation: FirCallableDeclaration<*>? = null
val implementations = mutableListOf<FirCallableDeclaration<*>>()
for (intSymbol in symbol.intersections) {
val fir = intSymbol.fir
if (fir.delegatedWrapperData?.containingClass?.classId == declaration.classId) {
delegation = fir
break
}
if (!(fir as FirCallableMemberDeclaration<*>).isAbstract) {
implementations.add(fir)
}
// val type = context.returnTypeCalculator.tryCalculateReturnType(intSymbol.fir).coneType
}
if (delegation != null || implementations.isNotEmpty()) {
//if there are more than one implementation we report nothing because it will be reported differently
val method = delegation ?: implementations.singleOrNull() ?: return
val methodType = context.returnTypeCalculator.tryCalculateReturnType(method).coneType
val (conflict, _) = withTypes.find { (_, type) ->
!AbstractTypeChecker.isSubtypeOf(typeCheckerContext, methodType, type)
} ?: return
dedupReporter.reportOn(source, FirErrors.RETURN_TYPE_MISMATCH_ON_INHERITANCE, declaration, method, conflict, context)
} else {
//if there is no implementation, check that there can be any type compatible (subtype of) with all
var clash: Pair<FirCallableDeclaration<*>, FirCallableDeclaration<*>>? = null
val compatible = withTypes.any { (m1, type1) ->
withTypes.all { (m2, type2) ->
val result = AbstractTypeChecker.isSubtypeOf(typeCheckerContext, type1, type2)
if (!result && clash == null && !AbstractTypeChecker.isSubtypeOf(typeCheckerContext, type2, type1)) {
clash = m1 to m2
}
result
}
}
clash?.takeIf { !compatible }?.let { (m1, m2) ->
dedupReporter.reportOn(source, FirErrors.RETURN_TYPE_MISMATCH_ON_INHERITANCE, declaration, m1, m2, context)
}
}
}
for (name in classScope.getCallableNames()) {
classScope.processFunctionsByName(name, ::checkSymbol)
}
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2021 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.diagnostics.impl
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.AbstractFirDiagnosticFactory
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic
class DeduplicatingDiagnosticReporter(private val inner: DiagnosticReporter) : DiagnosticReporter() {
private val reported = mutableSetOf<Pair<FirSourceElement, AbstractFirDiagnosticFactory>>()
override fun report(diagnostic: FirDiagnostic?, context: CheckerContext) {
if (diagnostic != null && reported.add(Pair(diagnostic.element, diagnostic.factory))) {
inner.report(diagnostic, context)
}
}
}
fun DiagnosticReporter.deduplicating(): DeduplicatingDiagnosticReporter = DeduplicatingDiagnosticReporter(this)
@@ -10,9 +10,9 @@ interface Three {
public fun foo(): String
}
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED!>class Test123<!>(val v1: One, val v2: Two, val v3: Three) : One by v1, Two by v2, Three by v3 { }
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED!>class Test132<!>(val v1: One, val v2: Two, val v3: Three) : One by v1, Three by v3, Two by v2 { }
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED, RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test123<!>(val v1: One, val v2: Two, val v3: Three) : One by v1, Two by v2, Three by v3 { }
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED, RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test132<!>(val v1: One, val v2: Two, val v3: Three) : One by v1, Three by v3, Two by v2 { }
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED!>class Test312<!>(val v1: One, val v2: Two, val v3: Three) : Three by v3, One by v1, Two by v2 { }
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED!>class Test321<!>(val v1: One, val v2: Two, val v3: Three) : Three by v3, Two by v2, One by v1 { }
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED!>class Test231<!>(val v1: One, val v2: Two, val v3: Three) : Two by v2, Three by v3, One by v1 { }
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED!>class Test213<!>(val v1: One, val v2: Two, val v3: Three) : Two by v2, One by v1, Three by v3 { }
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED, RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test231<!>(val v1: One, val v2: Two, val v3: Three) : Two by v2, Three by v3, One by v1 { }
<!MANY_IMPL_MEMBER_NOT_IMPLEMENTED, RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test213<!>(val v1: One, val v2: Two, val v3: Three) : Two by v2, One by v1, Three by v3 { }
@@ -32,28 +32,28 @@ class CGeneric<T> : IGeneric<T> {
}
}
abstract class Test1 : IStr by CStr(), IInt
abstract <!RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test1<!> : IStr by CStr(), IInt
abstract class Test2 : IStr, IInt by CInt()
abstract <!RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test2<!> : IStr, IInt by CInt()
abstract <!MANY_IMPL_MEMBER_NOT_IMPLEMENTED!>class Test3<!> : IStr by CStr(), IInt by CInt()
abstract <!MANY_IMPL_MEMBER_NOT_IMPLEMENTED, RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test3<!> : IStr by CStr(), IInt by CInt()
abstract class Test4 : IStr by CStr(), IGeneric<String>
abstract class Test5 : IStr by CStr(), IGeneric<Any>
abstract class Test6 : IStr by CStr(), IGeneric<Int>
abstract <!RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test6<!> : IStr by CStr(), IGeneric<Int>
abstract class Test7 : IGeneric<String> by CGeneric<String>(), IStr
abstract class Test8 : IGeneric<String> by CGeneric<String>(), IInt
abstract <!RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test8<!> : IGeneric<String> by CGeneric<String>(), IInt
// Can't test due to https://youtrack.jetbrains.com/issue/KT-10258
// abstract class Test9 : IGeneric<String> by CGeneric<String>(), IGeneric<Int>
abstract <!MANY_IMPL_MEMBER_NOT_IMPLEMENTED!>class Test10<!> : IInt by CInt(), IStr by CStr(), IAny by CAny()
abstract <!MANY_IMPL_MEMBER_NOT_IMPLEMENTED, RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test10<!> : IInt by CInt(), IStr by CStr(), IAny by CAny()
abstract <!MANY_IMPL_MEMBER_NOT_IMPLEMENTED!>class Test11<!> : IInt, IStr by CStr(), IAny by CAny()
abstract <!MANY_IMPL_MEMBER_NOT_IMPLEMENTED, RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test11<!> : IInt, IStr by CStr(), IAny by CAny()
abstract class Test12 : IInt, IStr, IAny by CAny()
abstract <!RETURN_TYPE_MISMATCH_ON_INHERITANCE!>class Test12<!> : IInt, IStr, IAny by CAny()
@@ -1,37 +0,0 @@
interface IBase {
fun copy(): IBase
}
interface ILeft : IBase {
override fun copy(): ILeft
}
open class CLeft : ILeft {
override fun copy(): ILeft = CLeft()
}
interface IRight : IBase {
override fun copy(): IRight
}
interface IDerived : ILeft, IRight {
override fun copy(): IDerived
}
// Error: ILeft::copy and IRight::copy have unrelated return types
<!ABSTRACT_MEMBER_NOT_IMPLEMENTED!>class CDerivedInvalid1<!> : ILeft, IRight
// Error: CLeft::copy and IRight::copy have unrelated return types
class CDerivedInvalid2 : CLeft(), IRight
// OK: CDerived1::copy overrides both ILeft::copy and IRight::copy
class CDerived1 : ILeft, IRight {
override fun copy(): CDerived1 = CDerived1()
}
// Although ILeft::copy and IRight::copy return types are unrelated, IDerived::copy return type is the most specific of three.
abstract class CDerived2 : ILeft, IRight, IDerived
class CDerived2a : ILeft, IRight, IDerived {
override fun copy(): IDerived = CDerived2a()
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
interface IBase {
fun copy(): IBase
}
@@ -1,43 +0,0 @@
// FILE: J.java
public interface J {
java.util.List<String> foo();
}
// FILE: K.kt
interface ILNS {
fun foo(): List<String?>
}
interface IMLS {
fun foo(): MutableList<String>
}
interface IMLNS {
fun foo(): MutableList<String?>
}
interface ILS {
fun foo(): List<String>
}
interface Test1 : ILNS, J
interface Test2 : J, ILNS
interface Test3 : IMLS, J
interface Test4 : J, IMLS
interface Test5 : ILNS, IMLS, J
interface Test6 : ILNS, J, IMLS
interface Test7 : J, ILNS, IMLS
// Return types of ILS::foo and IMLNS::foo are incompatible themselves.
// However, return type of J::foo is (Mutable)List<String!>!,
// which is subtype of both List<String> and MutalbeList<String?>.
// Thus, inheriting from J, IMLNS, and ILS is Ok,
// but inheriting from IMLNS and ILS is not.
interface Test8 : J, IMLNS, ILS
interface Test9 : IMLNS, J, ILS
interface Test10 : IMLNS, ILS, J
interface Test11 : IMLNS, ILS
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: J.java
public interface J {
java.util.List<String> foo();
@@ -1,47 +0,0 @@
// FILE: K.kt
abstract class ATest1 : TestNN.JNullVsNotNull()
abstract class ATest2 : TestNN.JUnknownImpl(), TestNN.JNotNull
abstract class ATest3 : TestNN.JUnknownVsNotNull()
class CTest1 : TestNN.JNullVsNotNull()
class CTest2 : TestNN.JUnknownImpl(), TestNN.JNotNull
class CTest3 : TestNN.JUnknownVsNotNull()
// FILE: TestNN.java
import org.jetbrains.annotations.*;
public class TestNN {
public interface JNull {
@Nullable Object foo();
}
public interface JNotNull {
@NotNull Object foo();
}
public static class JNullVsNotNull implements JNull, JNotNull {
public Object foo() {
return this;
}
}
public static class JNullBase {
@Nullable public Object foo() {
return null;
}
}
public static class JUnknownImpl extends JNullBase {
public Object foo() {
return this;
}
}
public static class JUnknownVsNotNull extends JUnknownImpl implements JNotNull {
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// FILE: K.kt
abstract class ATest1 : TestNN.JNullVsNotNull()
@@ -1,29 +0,0 @@
open class A {
open fun foo(): Boolean = true
}
interface IA {
fun foo(): String
}
interface IAA {
fun foo(): Int
}
interface IGA<T> {
fun foo(): T
}
class B1: A(), IA
class B2: A(), IA, IAA
abstract class B3: IA, IAA
class BS1: A(), IGA<Boolean>
class BS2: A(), IGA<Any>
class BS3: A(), IGA<String>
class BG1<T>: A(), IGA<T>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
open class A {
open fun foo(): Boolean = true
}
@@ -1,11 +0,0 @@
interface A {
fun f(): String
}
open class B {
open fun f(): CharSequence = "charSequence"
}
class C : B(), A
val d: A = object : B(), A {}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
interface A {
fun f(): String
}
@@ -20624,7 +20624,7 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
@Test
@TestMetadata("kt13355viaJava.kt")
public void testKt13355viaJava() throws Exception {
public void testKt13355viaJava1111() throws Exception {
runTest("compiler/testData/diagnostics/tests/override/clashesOnInheritance/kt13355viaJava.kt");
}
@@ -1652,6 +1652,15 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.RETURN_TYPE_MISMATCH_ON_INHERITANCE) { firDiagnostic ->
ReturnTypeMismatchOnInheritanceImpl(
firSymbolBuilder.classifierBuilder.buildClassLikeSymbol(firDiagnostic.a),
firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.b as FirCallableDeclaration),
firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.c as FirCallableDeclaration),
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.ABSTRACT_MEMBER_NOT_IMPLEMENTED) { firDiagnostic ->
AbstractMemberNotImplementedImpl(
firSymbolBuilder.classifierBuilder.buildClassLikeSymbol(firDiagnostic.a),
@@ -1171,6 +1171,13 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val containingClassName: Name
}
abstract class ReturnTypeMismatchOnInheritance : KtFirDiagnostic<KtClassOrObject>() {
override val diagnosticClass get() = ReturnTypeMismatchOnInheritance::class
abstract val classOrObject: KtClassLikeSymbol
abstract val conflictingDeclaration1: KtCallableSymbol
abstract val conflictingDeclaration2: KtCallableSymbol
}
abstract class AbstractMemberNotImplemented : KtFirDiagnostic<KtClassOrObject>() {
override val diagnosticClass get() = AbstractMemberNotImplemented::class
abstract val classOrObject: KtClassLikeSymbol
@@ -1883,6 +1883,16 @@ internal class OverridingFinalMemberImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class ReturnTypeMismatchOnInheritanceImpl(
override val classOrObject: KtClassLikeSymbol,
override val conflictingDeclaration1: KtCallableSymbol,
override val conflictingDeclaration2: KtCallableSymbol,
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.ReturnTypeMismatchOnInheritance(), KtAbstractFirDiagnostic<KtClassOrObject> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class AbstractMemberNotImplementedImpl(
override val classOrObject: KtClassLikeSymbol,
override val missingDeclaration: KtCallableSymbol,