[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
@@ -12,7 +12,8 @@ import org.jetbrains.kotlin.fir.analysis.web.common.checkers.declaration.FirJsEx
object JsDeclarationCheckers : DeclarationCheckers() {
override val functionCheckers: Set<FirFunctionChecker>
get() = setOf(
FirJsInheritanceFunctionChecker,
FirJsInheritanceFunctionChecker.Regular,
FirJsInheritanceFunctionChecker.ForExpectClass,
)
override val basicDeclarationCheckers: Set<FirBasicDeclarationChecker>
@@ -30,11 +31,15 @@ object JsDeclarationCheckers : DeclarationCheckers() {
override val classCheckers: Set<FirClassChecker>
get() = setOf(
FirJsMultipleInheritanceChecker,
FirJsMultipleInheritanceChecker.Regular,
FirJsMultipleInheritanceChecker.ForExpectClass,
FirJsDynamicDeclarationChecker,
FirJsInheritanceClassChecker,
FirJsExternalInheritorOnlyChecker,
FirJsNameClashClassMembersChecker
FirJsInheritanceClassChecker.Regular,
FirJsInheritanceClassChecker.ForExpectClass,
FirJsExternalInheritorOnlyChecker.Regular,
FirJsExternalInheritorOnlyChecker.ForExpectClass,
FirJsNameClashClassMembersChecker.Regular,
FirJsNameClashClassMembersChecker.ForExpectClass,
)
override val simpleFunctionCheckers: Set<FirSimpleFunctionChecker>
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.analysis.js.checkers.isEffectivelyExternal
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.superConeTypes
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
@@ -21,8 +22,21 @@ import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
import org.jetbrains.kotlin.name.JsStandardClassIds.Annotations.JsExternalInheritorsOnly
import org.jetbrains.kotlin.utils.addToStdlib.popLast
// TODO: extract common checker for expect interfaces
object FirJsExternalInheritorOnlyChecker : FirClassChecker(MppCheckerKind.Platform) {
sealed class FirJsExternalInheritorOnlyChecker(mppKind: MppCheckerKind) : FirClassChecker(mppKind) {
object Regular : FirJsExternalInheritorOnlyChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.isExpect) return
super.check(declaration, context, reporter)
}
}
object ForExpectClass : FirJsExternalInheritorOnlyChecker(MppCheckerKind.Common) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.isExpect) return
super.check(declaration, context, reporter)
}
}
private fun FirClass.forEachParents(context: CheckerContext, f: (FirRegularClassSymbol) -> Unit) {
val todo = superConeTypes.toMutableList()
val done = hashSetOf<FirRegularClassSymbol>()
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.analysis.js.checkers.isEffectivelyExternal
import org.jetbrains.kotlin.fir.analysis.js.checkers.isOverridingExternalWithOptionalParams
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.superConeTypes
import org.jetbrains.kotlin.fir.scopes.collectAllFunctions
import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionOverrideFunctionSymbol
@@ -26,8 +27,21 @@ import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.isSuspendOrKSuspendFunctionType
import org.jetbrains.kotlin.fir.types.typeContext
// TODO: extract common checker for expect interfaces
object FirJsInheritanceClassChecker : FirClassChecker(MppCheckerKind.Platform) {
sealed class FirJsInheritanceClassChecker(mppKind: MppCheckerKind) : FirClassChecker(mppKind) {
object Regular : FirJsInheritanceClassChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.isExpect) return
super.check(declaration, context, reporter)
}
}
object ForExpectClass : FirJsInheritanceClassChecker(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) {
if (!declaration.symbol.isEffectivelyExternal(context)) {
val fakeOverriddenMethod = declaration.findFakeMethodOverridingExternalWithOptionalParams(context)
@@ -13,11 +13,26 @@ import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFunctionChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.analysis.js.checkers.isEffectivelyExternal
import org.jetbrains.kotlin.fir.analysis.js.checkers.isOverridingExternalWithOptionalParams
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
sealed class FirJsInheritanceFunctionChecker(mppKind: MppCheckerKind) : FirFunctionChecker(mppKind) {
object Regular : FirJsInheritanceFunctionChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirFunction, context: CheckerContext, reporter: DiagnosticReporter) {
if ((context.containingDeclarations.last() as? FirClass)?.isExpect == true) return
super.check(declaration, context, reporter)
}
}
object ForExpectClass : FirJsInheritanceFunctionChecker(MppCheckerKind.Common) {
override fun check(declaration: FirFunction, context: CheckerContext, reporter: DiagnosticReporter) {
if ((context.containingDeclarations.last() as? FirClass)?.isExpect != true) return
super.check(declaration, context, reporter)
}
}
// TODO: extract common checker for expect interfaces
object FirJsInheritanceFunctionChecker : FirFunctionChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirFunction, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.isNotEffectivelyExternalFunctionButOverridesExternal(context)) {
reporter.reportOn(declaration.source, FirJsErrors.OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS, context)
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.overriddenFunctions
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.scopes.getFunctions
import org.jetbrains.kotlin.fir.types.ConeKotlinType
@@ -23,8 +24,21 @@ import org.jetbrains.kotlin.fir.types.isSubtypeOf
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.OperatorNameConventions
// TODO: extract common checker for expect interfaces
object FirJsMultipleInheritanceChecker : FirClassChecker(MppCheckerKind.Platform) {
sealed class FirJsMultipleInheritanceChecker(mppKind: MppCheckerKind) : FirClassChecker(mppKind) {
object Regular : FirJsMultipleInheritanceChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.isExpect) return
super.check(declaration, context, reporter)
}
}
object ForExpectClass : FirJsMultipleInheritanceChecker(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) {
declaration.checkFunctionIfSubtypeOf(
functionToCheck = OperatorNameConventions.GET,
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin
import org.jetbrains.kotlin.fir.declarations.constructors
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.isFinal
import org.jetbrains.kotlin.fir.scopes.*
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
@@ -31,8 +32,21 @@ import org.jetbrains.kotlin.fir.unwrapFakeOverridesOrDelegated
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.popLast
// TODO: extract common checker for expect interfaces
object FirJsNameClashClassMembersChecker : FirClassChecker(MppCheckerKind.Platform) {
sealed class FirJsNameClashClassMembersChecker(mppKind: MppCheckerKind) : FirClassChecker(mppKind) {
object Regular : FirJsNameClashClassMembersChecker(MppCheckerKind.Platform) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (declaration.isExpect) return
super.check(declaration, context, reporter)
}
}
object ForExpectClass : FirJsNameClashClassMembersChecker(MppCheckerKind.Common) {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.isExpect) return
super.check(declaration, context, reporter)
}
}
private class StableNamesCollector {
val jsStableNames = mutableSetOf<FirJsStableName>()
val overrideIntersections = hashMapOf<FirCallableSymbol<*>, HashSet<FirCallableSymbol<*>>>()