[FIR] Implement VAR_OVERRIDDEN_BY_VAL_BY_DELEGATION diagnostic
This commit is contained in:
committed by
teamcityserver
parent
c98cd3b190
commit
78519f851e
+5
@@ -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)
|
||||
|
||||
+87
-54
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -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) // *
|
||||
|
||||
-55
@@ -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
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
open class Final {
|
||||
fun foo() {}
|
||||
val bar: Int = 0
|
||||
|
||||
-29
@@ -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
|
||||
}
|
||||
|
||||
+8
@@ -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),
|
||||
|
||||
+6
@@ -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
|
||||
|
||||
+9
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user