[FIR] Implement VAR_OVERRIDDEN_BY_VAL_BY_DELEGATION diagnostic

This commit is contained in:
Andrey Zinovyev
2021-06-11 13:53:46 +03:00
committed by teamcityserver
parent c98cd3b190
commit 78519f851e
11 changed files with 126 additions and 138 deletions
@@ -612,6 +612,11 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
parameter<FirCallableDeclaration<*>>("baseDeclaration")
}
val VAR_OVERRIDDEN_BY_VAL_BY_DELEGATION by error<KtClassOrObject>(PositioningStrategy.DECLARATION_NAME) {
parameter<FirCallableDeclaration<*>>("delegateDeclaration")
parameter<FirCallableDeclaration<*>>("baseDeclaration")
}
val ABSTRACT_MEMBER_NOT_IMPLEMENTED by error<KtClassOrObject>(PositioningStrategy.DECLARATION_NAME) {
parameter<FirClass>("classOrObject")
parameter<FirCallableDeclaration>("missingDeclaration")
@@ -352,6 +352,7 @@ object FirErrors {
val VAR_TYPE_MISMATCH_ON_INHERITANCE by error2<KtClassOrObject, FirCallableDeclaration<*>, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.DECLARATION_NAME)
val RETURN_TYPE_MISMATCH_BY_DELEGATION by error2<KtClassOrObject, FirCallableDeclaration<*>, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.DECLARATION_NAME)
val PROPERTY_TYPE_MISMATCH_BY_DELEGATION by error2<KtClassOrObject, FirCallableDeclaration<*>, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.DECLARATION_NAME)
val VAR_OVERRIDDEN_BY_VAL_BY_DELEGATION by error2<KtClassOrObject, 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)
@@ -15,11 +15,11 @@ 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.symbols.impl.*
import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.ConeKotlinErrorType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeCheckerContext
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.types.AbstractTypeChecker
@@ -40,6 +40,22 @@ object FirImplementationMismatchChecker : FirClassChecker() {
val classScope = declaration.unsubstitutedScope(context)
val dedupReporter = reporter.deduplicating()
for (name in classScope.getCallableNames()) {
classScope.processFunctionsByName(name) { checkInheritanceClash(declaration, context, dedupReporter, typeCheckerContext, it) }
classScope.processPropertiesByName(name) {
checkInheritanceClash(declaration, context, dedupReporter, typeCheckerContext, it)
checkValOverrideVar(declaration, context, dedupReporter, it)
}
}
}
private fun checkInheritanceClash(
containingClass: FirClass<*>,
context: CheckerContext,
reporter: DiagnosticReporter,
typeCheckerContext: ConeTypeCheckerContext,
symbol: FirCallableSymbol<*>
) {
fun reportTypeMismatch(member1: FirCallableDeclaration<*>, member2: FirCallableDeclaration<*>, isDelegation: Boolean) {
val error = if (member1 is FirProperty && member2 is FirProperty) {
if (member1.isVar || member2.isVar) {
@@ -52,7 +68,7 @@ object FirImplementationMismatchChecker : FirClassChecker() {
if (isDelegation) FirErrors.RETURN_TYPE_MISMATCH_BY_DELEGATION
else FirErrors.RETURN_TYPE_MISMATCH_ON_INHERITANCE
}
dedupReporter.reportOn(source, error, member1, member2, context)
reporter.reportOn(containingClass.source, error, member1, member2, context)
}
fun canOverride(
@@ -64,61 +80,78 @@ object FirImplementationMismatchChecker : FirClassChecker() {
else AbstractTypeChecker.isSubtypeOf(typeCheckerContext, inheritedType, baseType)
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 (symbol.callableId.classId != containingClass.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 == containingClass.classId) {
delegation = fir
break
}
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)
}
}
run {
var clash: Pair<FirCallableDeclaration<*>, FirCallableDeclaration<*>>? = null
val compatible = withTypes.any { (m1, type1) ->
withTypes.all { (m2, type2) ->
val result = canOverride(m2, type1, type2)
if (!result && clash == null && !canOverride(m1, type2, type1)) {
clash = m1 to m2
}
result
}
}
clash?.takeIf { !compatible }?.let { (m1, m2) ->
reportTypeMismatch(m1, m2, false)
return@checkSymbol
}
}
if (delegation != null || implementations.isNotEmpty()) {
//if there are more than one implementation we report nothing because it will be reported differently
val implementationMember = delegation ?: implementations.singleOrNull() ?: return
val methodType = context.returnTypeCalculator.tryCalculateReturnType(implementationMember).coneType
val (conflict, _) = withTypes.find { (baseMember, baseType) ->
!canOverride(baseMember, methodType, baseType)
} ?: return
reportTypeMismatch(implementationMember, conflict, delegation != null)
if (!(fir as FirCallableMemberDeclaration<*>).isAbstract) {
implementations.add(fir)
}
}
for (name in classScope.getCallableNames()) {
classScope.processFunctionsByName(name, ::checkSymbol)
classScope.processPropertiesByName(name, ::checkSymbol)
run {
var clash: Pair<FirCallableDeclaration<*>, FirCallableDeclaration<*>>? = null
val compatible = withTypes.any { (m1, type1) ->
withTypes.all { (m2, type2) ->
val result = canOverride(m2, type1, type2)
if (!result && clash == null && !canOverride(m1, type2, type1)) {
clash = m1 to m2
}
result
}
}
clash?.takeIf { !compatible }?.let { (m1, m2) ->
reportTypeMismatch(m1, m2, false)
return@checkInheritanceClash
}
}
if (delegation != null || implementations.isNotEmpty()) {
//if there are more than one implementation we report nothing because it will be reported differently
val implementationMember = delegation ?: implementations.singleOrNull() ?: return
val methodType = context.returnTypeCalculator.tryCalculateReturnType(implementationMember).coneType
val (conflict, _) = withTypes.find { (baseMember, baseType) ->
!canOverride(baseMember, methodType, baseType)
} ?: return
reportTypeMismatch(implementationMember, conflict, delegation != null)
}
}
private fun checkValOverrideVar(
containingClass: FirClass<*>,
context: CheckerContext,
reporter: DiagnosticReporter,
symbol: FirVariableSymbol<*>
) {
if (symbol.callableId.classId != containingClass.classId) return
if (symbol !is FirIntersectionOverridePropertySymbol) return
val (delegates, others) = symbol.intersections.partition {
val fir = it.fir as? FirProperty ?: return@partition false
fir.isVal && fir.delegatedWrapperData?.containingClass?.classId == containingClass.classId
}
val delegatedVal = delegates.singleOrNull() ?: return
val baseVar = others.find {
it is FirPropertySymbol && it.fir.isVar
}
if (baseVar != null) {
reporter.reportOn(containingClass.source, FirErrors.VAR_OVERRIDDEN_BY_VAL_BY_DELEGATION, delegatedVal.fir, baseVar.fir, context)
}
}
}
@@ -386,6 +386,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIABLE_NEVER_RE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIANCE_ON_TYPE_PARAMETER_NOT_ALLOWED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAR_ANNOTATION_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAR_OVERRIDDEN_BY_VAL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAR_OVERRIDDEN_BY_VAL_BY_DELEGATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAR_TYPE_MISMATCH_ON_INHERITANCE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAR_TYPE_MISMATCH_ON_OVERRIDE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VIRTUAL_MEMBER_HIDDEN
@@ -975,6 +976,13 @@ class FirDefaultErrorMessages {
NAME
)
map.put(
VAR_OVERRIDDEN_BY_VAL_BY_DELEGATION,
"Val-property ''{0}'' implicitly overrides a var-property ''{1}'' by delegation",
NAME,
NAME
)
// Redeclarations
map.put(MANY_COMPANION_OBJECTS, "Only one companion object is allowed per class")
map.put(CONFLICTING_OVERLOADS, "Conflicting overloads: {0}", SYMBOLS) // *