[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) // *
@@ -1,55 +0,0 @@
open class Final {
fun foo() {}
val bar: Int = 0
var qux: Int = 0
}
open class Derived : Final()
interface IFoo {
fun foo()
}
class CFoo : IFoo {
override fun foo() {}
}
interface IBar {
val bar: Int
}
class CBar : IBar {
override val bar: Int get() = 0
}
interface IQux {
val qux: Int
}
class CQux : IQux {
override val qux: Int get() = 0
}
interface IBarT<T> {
val bar: T
}
class CBarT<T> : IBarT<T> {
override val bar: T get() = null!!
}
<!OVERRIDING_FINAL_MEMBER_BY_DELEGATION!>class Test1<!> : Final(), IFoo by CFoo()
<!OVERRIDING_FINAL_MEMBER_BY_DELEGATION!>class Test2<!> : Final(), IBar by CBar()
<!OVERRIDING_FINAL_MEMBER_BY_DELEGATION!>class Test3<!> : Final(), IQux by CQux()
<!OVERRIDING_FINAL_MEMBER_BY_DELEGATION!>class Test4<!> : Derived(), IFoo by CFoo()
<!OVERRIDING_FINAL_MEMBER_BY_DELEGATION!>class Test5<!> : Derived(), IBar by CBar()
<!OVERRIDING_FINAL_MEMBER_BY_DELEGATION!>class Test6<!> : Derived(), IQux by CQux()
<!OVERRIDING_FINAL_MEMBER_BY_DELEGATION!>class Test7<!> : Final(), IBarT<Int> by CBarT<Int>()
<!OVERRIDING_FINAL_MEMBER_BY_DELEGATION!>class Test8<!> : Final(), IBarT<Int> by <!TYPE_MISMATCH!>CBar()<!>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
open class Final {
fun foo() {}
val bar: Int = 0
@@ -1,29 +0,0 @@
interface IVar {
var foo: Int
}
interface IDerived : IVar
interface IVal {
val foo: Int
}
class CVal : IVal {
override val foo: Int get() = 42
}
interface IValT<T> {
val foo: T
}
class CValT<T> : IValT<T> {
override val foo: T get() = null!!
}
abstract class Test1 : IVar, IVal by CVal()
abstract class Test2 : IVar, IValT<Int> by CValT<Int>()
abstract class Test3 : IDerived, IVal by CVal()
abstract class Test4 : IDerived, IValT<Int> by CValT<Int>()
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
interface IVar {
var foo: Int
}
@@ -1692,6 +1692,14 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.VAR_OVERRIDDEN_BY_VAL_BY_DELEGATION) { firDiagnostic ->
VarOverriddenByValByDelegationImpl(
firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.a as FirCallableDeclaration),
firSymbolBuilder.callableBuilder.buildCallableSymbol(firDiagnostic.b as FirCallableDeclaration),
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.ABSTRACT_MEMBER_NOT_IMPLEMENTED) { firDiagnostic ->
AbstractMemberNotImplementedImpl(
firSymbolBuilder.classifierBuilder.buildClassLikeSymbol(firDiagnostic.a),
@@ -1201,6 +1201,12 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val baseDeclaration: KtCallableSymbol
}
abstract class VarOverriddenByValByDelegation : KtFirDiagnostic<KtClassOrObject>() {
override val diagnosticClass get() = VarOverriddenByValByDelegation::class
abstract val delegateDeclaration: KtCallableSymbol
abstract val baseDeclaration: KtCallableSymbol
}
abstract class AbstractMemberNotImplemented : KtFirDiagnostic<KtClassOrObject>() {
override val diagnosticClass get() = AbstractMemberNotImplemented::class
abstract val classOrObject: KtClassLikeSymbol
@@ -1928,6 +1928,15 @@ internal class PropertyTypeMismatchByDelegationImpl(
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class VarOverriddenByValByDelegationImpl(
override val delegateDeclaration: KtCallableSymbol,
override val baseDeclaration: KtCallableSymbol,
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.VarOverriddenByValByDelegation(), KtAbstractFirDiagnostic<KtClassOrObject> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class AbstractMemberNotImplementedImpl(
override val classOrObject: KtClassLikeSymbol,
override val missingDeclaration: KtCallableSymbol,