[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)