[FIR] Part 5. Introduce paired common checkers for expect classes

There are some cases when we want to run some platform checker not from
  platform session but from common session. All such cases appear when
  we check some `expect` class

```kotlin
// MODULE: common
expect interface A
expect class B : A

class C : A

// MODULE: platform()()(common)
actual interface A {
    fun foo()
}

actual class B : A {
    override fun foo() {}
}
```

In this example we want to report "abstract foo not implemented" on
  class `C`, but we don't want to report it on `expect class B` (as
  its supertype is always `expect A`, never `actual A`)

So to cover such cases some platform checkers were split into two parts:
- `Regular`, which is platform checkers and runs for everything except
  expect declaration
- `ForExpectClass`, which is common checkers and runs only for expect
  declarations

^KT-58881 Fixed
^KT-58881 Fixed
^KT-64187 Fixed
This commit is contained in:
Dmitriy Novozhilov
2024-01-09 15:12:05 +02:00
committed by Nikolay Lunyak
parent f5d8113de3
commit 990da9fa1a
27 changed files with 495 additions and 156 deletions
@@ -14,14 +14,33 @@ import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
object FirNativeObjCNameCallableChecker : FirCallableDeclarationChecker(MppCheckerKind.Common) {
override fun check(declaration: FirCallableDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration !is FirSimpleFunction && declaration !is FirProperty) return
val containingClass = context.containingDeclarations.lastOrNull() as? FirClass
if (containingClass != null) {
val firTypeScope = containingClass.unsubstitutedScope(context)
FirNativeObjCNameUtilities.checkCallableMember(firTypeScope, declaration.symbol, declaration, context, reporter)
sealed class FirNativeObjCNameCallableChecker(mppKind: MppCheckerKind) : FirCallableDeclarationChecker(mppKind) {
object Regular : FirNativeObjCNameCallableChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirCallableDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
val containingClass = context.containingDeclarations.lastOrNull() as? FirClass ?: return
if (containingClass.isExpect) return
check(declaration, containingClass, context, reporter)
}
}
object ForExpectClass : FirNativeObjCNameCallableChecker(MppCheckerKind.Common) {
override fun check(declaration: FirCallableDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
val containingClass = context.containingDeclarations.lastOrNull() as? FirClass ?: return
if (!containingClass.isExpect) return
check(declaration, containingClass, context, reporter)
}
}
protected fun check(
declaration: FirCallableDeclaration,
containingClass: FirClass,
context: CheckerContext,
reporter: DiagnosticReporter,
) {
if (declaration !is FirSimpleFunction && declaration !is FirProperty) return
val firTypeScope = containingClass.unsubstitutedScope(context)
FirNativeObjCNameUtilities.checkCallableMember(firTypeScope, declaration.symbol, declaration, context, reporter)
}
}
@@ -12,11 +12,25 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCNameUtilities.checkCallableMember
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.isIntersectionOverride
import org.jetbrains.kotlin.fir.scopes.processAllFunctions
import org.jetbrains.kotlin.fir.scopes.processAllProperties
object FirNativeObjCNameOverridesChecker : FirClassChecker(MppCheckerKind.Platform) {
sealed class FirNativeObjCNameOverridesChecker(mppKind: MppCheckerKind) : FirClassChecker(mppKind) {
object Regular : FirNativeObjCNameOverridesChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.isExpect) return
super.check(declaration, context, reporter)
}
}
object ForExpectClass : FirNativeObjCNameOverridesChecker(MppCheckerKind.Common) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.isExpect) return
super.check(declaration, context, reporter)
}
}
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
// We just need to check intersection overrides, all other declarations are checked by FirNativeObjCNameChecker
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirCallableDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.REDUNDANT_SWIFT_REFINEMENT
import org.jetbrains.kotlin.fir.analysis.native.checkers.FirNativeObjCRefinementOverridesChecker.check
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.name.ClassId
@@ -35,7 +34,15 @@ object FirNativeObjCRefinementChecker : FirCallableDeclarationChecker(MppChecker
val containingClass = context.containingDeclarations.lastOrNull() as? FirClass
if (containingClass != null) {
val firTypeScope = containingClass.unsubstitutedScope(context)
check(firTypeScope, declaration.symbol, declaration, context, reporter, objCAnnotations, swiftAnnotations)
FirNativeObjCRefinementOverridesChecker.check(
firTypeScope,
declaration.symbol,
declaration,
context,
reporter,
objCAnnotations,
swiftAnnotations
)
}
}
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.toAnnotationClassId
import org.jetbrains.kotlin.fir.declarations.toAnnotationClassLikeSymbol
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.isIntersectionOverride
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
@@ -29,8 +30,20 @@ import org.jetbrains.kotlin.fir.scopes.processAllFunctions
import org.jetbrains.kotlin.fir.scopes.processAllProperties
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
// TODO: extract common checker for expect interfaces
object FirNativeObjCRefinementOverridesChecker : FirClassChecker(MppCheckerKind.Platform) {
sealed class FirNativeObjCRefinementOverridesChecker(mppKind: MppCheckerKind) : FirClassChecker(mppKind) {
object Regular : FirNativeObjCRefinementOverridesChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.isExpect) return
super.check(declaration, context, reporter)
}
}
object ForExpectClass : FirNativeObjCRefinementOverridesChecker(MppCheckerKind.Common) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.isExpect) return
super.check(declaration, context, reporter)
}
}
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
// We just need to check intersection overrides, all other declarations are checked by FirNativeObjCRefinementChecker
@@ -45,80 +58,83 @@ object FirNativeObjCRefinementOverridesChecker : FirClassChecker(MppCheckerKind.
}
}
fun check(
baseScope: FirTypeScope,
memberSymbol: FirCallableSymbol<*>,
declarationToReport: FirDeclaration,
context: CheckerContext,
reporter: DiagnosticReporter,
objCAnnotations: List<FirAnnotation>,
swiftAnnotations: List<FirAnnotation>
) {
val overriddenMemberSymbols = baseScope.getDirectOverriddenMembersWithBaseScope(memberSymbol)
if (overriddenMemberSymbols.isEmpty()) return
var isHiddenFromObjC = objCAnnotations.isNotEmpty()
var isRefinedInSwift = swiftAnnotations.isNotEmpty()
val supersNotHiddenFromObjC = mutableListOf<FirCallableSymbol<*>>()
val supersNotRefinedInSwift = mutableListOf<FirCallableSymbol<*>>()
for ((symbol, scope) in overriddenMemberSymbols) {
val (superIsHiddenFromObjC, superIsRefinedInSwift) = symbol.inheritsRefinedAnnotations(context.session, scope)
if (superIsHiddenFromObjC) isHiddenFromObjC = true else supersNotHiddenFromObjC.add(symbol)
if (superIsRefinedInSwift) isRefinedInSwift = true else supersNotRefinedInSwift.add(symbol)
companion object {
fun check(
baseScope: FirTypeScope,
memberSymbol: FirCallableSymbol<*>,
declarationToReport: FirDeclaration,
context: CheckerContext,
reporter: DiagnosticReporter,
objCAnnotations: List<FirAnnotation>,
swiftAnnotations: List<FirAnnotation>
) {
val overriddenMemberSymbols = baseScope.getDirectOverriddenMembersWithBaseScope(memberSymbol)
if (overriddenMemberSymbols.isEmpty()) return
var isHiddenFromObjC = objCAnnotations.isNotEmpty()
var isRefinedInSwift = swiftAnnotations.isNotEmpty()
val supersNotHiddenFromObjC = mutableListOf<FirCallableSymbol<*>>()
val supersNotRefinedInSwift = mutableListOf<FirCallableSymbol<*>>()
for ((symbol, scope) in overriddenMemberSymbols) {
val (superIsHiddenFromObjC, superIsRefinedInSwift) = symbol.inheritsRefinedAnnotations(context.session, scope)
if (superIsHiddenFromObjC) isHiddenFromObjC = true else supersNotHiddenFromObjC.add(symbol)
if (superIsRefinedInSwift) isRefinedInSwift = true else supersNotRefinedInSwift.add(symbol)
}
if (isHiddenFromObjC && supersNotHiddenFromObjC.isNotEmpty()) {
reporter.reportIncompatibleOverride(declarationToReport, objCAnnotations, supersNotHiddenFromObjC, context)
}
if (isRefinedInSwift && supersNotRefinedInSwift.isNotEmpty()) {
reporter.reportIncompatibleOverride(declarationToReport, swiftAnnotations, supersNotRefinedInSwift, context)
}
}
if (isHiddenFromObjC && supersNotHiddenFromObjC.isNotEmpty()) {
reporter.reportIncompatibleOverride(declarationToReport, objCAnnotations, supersNotHiddenFromObjC, context)
private fun FirCallableSymbol<*>.inheritsRefinedAnnotations(session: FirSession, baseScope: FirTypeScope): Pair<Boolean, Boolean> {
val (hasObjC, hasSwift) = hasRefinedAnnotations(session)
if (hasObjC && hasSwift) return true to true
// Note: `checkMember` requires all overridden symbols to be either refined or not refined.
val (overriddenMemberSymbol, scope) = baseScope.getDirectOverriddenMembersWithBaseScope(this).firstOrNull()
?: return hasObjC to hasSwift
val (inheritsObjC, inheritsSwift) = overriddenMemberSymbol.inheritsRefinedAnnotations(session, scope)
return (hasObjC || inheritsObjC) to (hasSwift || inheritsSwift)
}
if (isRefinedInSwift && supersNotRefinedInSwift.isNotEmpty()) {
reporter.reportIncompatibleOverride(declarationToReport, swiftAnnotations, supersNotRefinedInSwift, context)
}
}
private fun FirCallableSymbol<*>.inheritsRefinedAnnotations(session: FirSession, baseScope: FirTypeScope): Pair<Boolean, Boolean> {
val (hasObjC, hasSwift) = hasRefinedAnnotations(session)
if (hasObjC && hasSwift) return true to true
// Note: `checkMember` requires all overridden symbols to be either refined or not refined.
val (overriddenMemberSymbol, scope) = baseScope.getDirectOverriddenMembersWithBaseScope(this).firstOrNull()
?: return hasObjC to hasSwift
val (inheritsObjC, inheritsSwift) = overriddenMemberSymbol.inheritsRefinedAnnotations(session, scope)
return (hasObjC || inheritsObjC) to (hasSwift || inheritsSwift)
}
private fun FirCallableSymbol<*>.hasRefinedAnnotations(session: FirSession): Pair<Boolean, Boolean> {
var hasObjC = false
var hasSwift = false
for (annotation in resolvedAnnotationsWithClassIds) {
val metaAnnotations = annotation.toAnnotationClassLikeSymbol(session)?.resolvedAnnotationsWithClassIds.orEmpty()
for (metaAnnotation in metaAnnotations) {
when (metaAnnotation.toAnnotationClassId(session)) {
hidesFromObjCClassId -> {
hasObjC = true
break
}
private fun FirCallableSymbol<*>.hasRefinedAnnotations(session: FirSession): Pair<Boolean, Boolean> {
var hasObjC = false
var hasSwift = false
for (annotation in resolvedAnnotationsWithClassIds) {
val metaAnnotations = annotation.toAnnotationClassLikeSymbol(session)?.resolvedAnnotationsWithClassIds.orEmpty()
for (metaAnnotation in metaAnnotations) {
when (metaAnnotation.toAnnotationClassId(session)) {
hidesFromObjCClassId -> {
hasObjC = true
break
}
refinesInSwiftClassId -> {
hasSwift = true
break
refinesInSwiftClassId -> {
hasSwift = true
break
}
}
}
if (hasObjC && hasSwift) return true to true
}
if (hasObjC && hasSwift) return true to true
return hasObjC to hasSwift
}
return hasObjC to hasSwift
}
private fun DiagnosticReporter.reportIncompatibleOverride(
declaration: FirDeclaration,
annotations: List<FirAnnotation>,
notRefinedSupers: List<FirCallableSymbol<*>>,
context: CheckerContext
) {
val containingDeclarations = notRefinedSupers.mapNotNull { it.containingClassLookupTag()?.toFirRegularClassSymbol(context.session) }
if (annotations.isEmpty()) {
reportOn(declaration.source, INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE, declaration.symbol, containingDeclarations, context)
} else {
for (annotation in annotations) {
reportOn(annotation.source, INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE, declaration.symbol, containingDeclarations, context)
private fun DiagnosticReporter.reportIncompatibleOverride(
declaration: FirDeclaration,
annotations: List<FirAnnotation>,
notRefinedSupers: List<FirCallableSymbol<*>>,
context: CheckerContext
) {
val containingDeclarations = notRefinedSupers.mapNotNull { it.containingClassLookupTag()?.toFirRegularClassSymbol(context.session) }
if (annotations.isEmpty()) {
reportOn(declaration.source, INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE, declaration.symbol, containingDeclarations, context)
} else {
for (annotation in annotations) {
reportOn(annotation.source, INCOMPATIBLE_OBJC_REFINEMENT_OVERRIDE, declaration.symbol, containingDeclarations, context)
}
}
}
}
}
@@ -18,8 +18,10 @@ import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
import org.jetbrains.kotlin.fir.references.isError
@@ -34,19 +36,34 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.annotations.KOTLIN_THROWS_ANNOTATION_FQ_NAME
// TODO: extract common checker for expect interfaces
object FirNativeThrowsChecker : FirBasicDeclarationChecker(MppCheckerKind.Platform) {
private val throwsClassId = ClassId.topLevel(KOTLIN_THROWS_ANNOTATION_FQ_NAME)
sealed class FirNativeThrowsChecker(mppKind: MppCheckerKind) : FirBasicDeclarationChecker(mppKind) {
object Regular : FirNativeThrowsChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if ((declaration as? FirMemberDeclaration)?.isExpect == true) return
super.check(declaration, context, reporter)
}
}
private val cancellationExceptionFqName = FqName("kotlin.coroutines.cancellation.CancellationException")
object ForExpectClass : FirNativeThrowsChecker(MppCheckerKind.Common) {
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
if ((declaration as? FirMemberDeclaration)?.isExpect != true) return
super.check(declaration, context, reporter)
}
}
private val cancellationExceptionAndSupersClassIds = setOf(
ClassId.topLevel(StandardNames.FqNames.throwable),
ClassId.topLevel(FqName("kotlin.Exception")),
ClassId.topLevel(FqName("kotlin.RuntimeException")),
ClassId.topLevel(FqName("kotlin.IllegalStateException")),
ClassId.topLevel(cancellationExceptionFqName)
)
companion object {
private val throwsClassId = ClassId.topLevel(KOTLIN_THROWS_ANNOTATION_FQ_NAME)
private val cancellationExceptionFqName = FqName("kotlin.coroutines.cancellation.CancellationException")
private val cancellationExceptionAndSupersClassIds = setOf(
ClassId.topLevel(StandardNames.FqNames.throwable),
ClassId.topLevel(FqName("kotlin.Exception")),
ClassId.topLevel(FqName("kotlin.RuntimeException")),
ClassId.topLevel(FqName("kotlin.IllegalStateException")),
ClassId.topLevel(cancellationExceptionFqName)
)
}
override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) {
val throwsAnnotation = declaration.getAnnotationByClassId(throwsClassId, context.session) as? FirAnnotationCall
@@ -10,7 +10,8 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.*
object NativeDeclarationCheckers : DeclarationCheckers() {
override val basicDeclarationCheckers: Set<FirBasicDeclarationChecker>
get() = setOf(
FirNativeThrowsChecker,
FirNativeThrowsChecker.Regular,
FirNativeThrowsChecker.ForExpectClass,
FirNativeSharedImmutableChecker,
FirNativeThreadLocalChecker,
FirNativeIdentifierChecker,
@@ -20,13 +21,16 @@ object NativeDeclarationCheckers : DeclarationCheckers() {
override val callableDeclarationCheckers: Set<FirCallableDeclarationChecker>
get() = setOf(
FirNativeObjCRefinementChecker,
FirNativeObjCNameCallableChecker,
FirNativeObjCNameCallableChecker.Regular,
FirNativeObjCNameCallableChecker.ForExpectClass,
)
override val classCheckers: Set<FirClassChecker>
get() = setOf(
FirNativeObjCRefinementOverridesChecker,
FirNativeObjCNameOverridesChecker,
FirNativeObjCRefinementOverridesChecker.Regular,
FirNativeObjCRefinementOverridesChecker.ForExpectClass,
FirNativeObjCNameOverridesChecker.Regular,
FirNativeObjCNameOverridesChecker.ForExpectClass,
FirNativeObjCOutletChecker,
FirNativeObjCActionChecker,
FirNativeObjCOverrideInitChecker,