[FIR JS] Implement FirJsInheritanceChecker

This commit is contained in:
Nikolay Lunyak
2023-01-04 15:37:15 +02:00
committed by Space Team
parent 9e1c6f2f61
commit 4cf8d9ffb9
17 changed files with 235 additions and 29 deletions
@@ -10,6 +10,8 @@ import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtElement
/*
@@ -27,6 +29,13 @@ object FirJsErrors {
// Supertypes
val WRONG_MULTIPLE_INHERITANCE by error1<KtElement, FirCallableSymbol<*>>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
// Fun Interfaces
val IMPLEMENTING_FUNCTION_INTERFACE by error0<KtClassOrObject>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
// External
val OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS by error0<KtElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS_WITH_FAKE by error1<KtElement, FirNamedFunctionSymbol>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
// Dynamics
val DELEGATION_BY_DYNAMIC by error0<KtElement>()
@@ -10,9 +10,12 @@ import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers
import org.jetbrains.kotlin.fir.analysis.diagnostics.checkMissingMessages
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.DELEGATION_BY_DYNAMIC
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.IMPLEMENTING_FUNCTION_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_MODULE_PROHIBITED_ON_NON_NATIVE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_MODULE_PROHIBITED_ON_VAR
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.NESTED_JS_MODULE_PROHIBITED
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS_WITH_FAKE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_JS_QUALIFIER
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.WRONG_MULTIPLE_INHERITANCE
@@ -32,6 +35,13 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
FirDiagnosticRenderers.SYMBOL
)
map.put(DELEGATION_BY_DYNAMIC, "Can't delegate to dynamic value")
map.put(IMPLEMENTING_FUNCTION_INTERFACE, "Implementing function interface is prohibited in JavaScript")
map.put(OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS, "Overriding `external` function with optional parameters")
map.put(
OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS_WITH_FAKE,
"Overriding `external` function with optional parameters by declaration from superclass: {0}",
FirDiagnosticRenderers.SYMBOL
)
map.checkMissingMessages(FirJsErrors)
}
@@ -7,12 +7,16 @@
package org.jetbrains.kotlin.fir.analysis.js.checkers
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.directOverriddenFunctions
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.hasAnnotationOrInsideAnnotatedClass
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isExternal
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
@@ -42,6 +46,20 @@ fun FirBasedSymbol<*>.isEffectivelyExternal(session: FirSession): Boolean {
return getContainingClassSymbol(session)?.isEffectivelyExternal(session) == true
}
fun FirBasedSymbol<*>.isEffectivelyExternal(context: CheckerContext) = isEffectivelyExternal(context.session)
fun FirFunctionSymbol<*>.isOverridingExternalWithOptionalParams(context: CheckerContext): Boolean {
if (!isSubstitutionOrIntersectionOverride && modality == Modality.ABSTRACT) return false
val overridden = (this as? FirNamedFunctionSymbol)?.directOverriddenFunctions(context) ?: return false
for (overriddenFunction in overridden.filter { it.isEffectivelyExternal(context) }) {
if (overriddenFunction.valueParameterSymbols.any { it.hasDefaultValue }) return true
}
return false
}
fun FirBasedSymbol<*>.isNativeObject(session: FirSession): Boolean {
if (hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsNative, session) || isEffectivelyExternal(session)) {
return true
@@ -12,6 +12,7 @@ object JsDeclarationCheckers : DeclarationCheckers() {
override val functionCheckers: Set<FirFunctionChecker>
get() = setOf(
FirJsInlineDeclarationChecker,
FirJsInheritanceFunctionChecker,
)
override val propertyCheckers: Set<FirPropertyChecker>
@@ -28,5 +29,6 @@ object JsDeclarationCheckers : DeclarationCheckers() {
get() = setOf(
FirJsMultipleInheritanceChecker,
FirJsDynamicDeclarationChecker,
FirJsInheritanceClassChecker,
)
}
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.js.checkers.declaration
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
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.superConeTypes
import org.jetbrains.kotlin.fir.scopes.collectAllFunctions
import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionOverrideFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.typeContext
object FirJsInheritanceClassChecker : FirClassChecker() {
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.symbol.isEffectivelyExternal(context)) {
val fakeOverriddenMethod = declaration.findFakeMethodOverridingExternalWithOptionalParams(context)
if (fakeOverriddenMethod != null) {
reporter.reportOn(
declaration.source, FirJsErrors.OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS_WITH_FAKE,
fakeOverriddenMethod, context
)
}
}
if (
!context.languageVersionSettings.supportsFeature(LanguageFeature.JsAllowImplementingFunctionInterface) &&
declaration.superConeTypes.any {
it.isBuiltinFunctionalTypeOrSubtype(context.session) && !it.isSuspendFunctionTypeOrSubtype(context.session)
}
) {
reporter.reportOn(declaration.source, FirJsErrors.IMPLEMENTING_FUNCTION_INTERFACE, context)
}
}
private fun ConeClassLikeType.isBuiltinFunctionalTypeOrSubtype(session: FirSession): Boolean {
return with(session.typeContext) { isBuiltinFunctionalTypeOrSubtype() }
}
private fun ConeClassLikeType.isSuspendFunctionTypeOrSubtype(session: FirSession): Boolean {
return with(session.typeContext) { isSuspendFunctionTypeOrSubtype() }
}
private fun FirClass.findFakeMethodOverridingExternalWithOptionalParams(context: CheckerContext): FirNamedFunctionSymbol? {
val scope = symbol.unsubstitutedScope(context)
val members = scope.collectAllFunctions()
.filterIsInstance<FirIntersectionOverrideFunctionSymbol>()
.filter {
val container = it.getContainingClassSymbol(context.session)
container == symbol && it.intersections.isNotEmpty()
}
return members.firstOrNull {
it.isOverridingExternalWithOptionalParams(context)
}
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.js.checkers.declaration
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirFunctionChecker
import org.jetbrains.kotlin.fir.analysis.js.checkers.isEffectivelyExternal
import org.jetbrains.kotlin.fir.analysis.js.checkers.isOverridingExternalWithOptionalParams
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFunction
object FirJsInheritanceFunctionChecker : FirFunctionChecker() {
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)
}
}
private fun FirDeclaration.isNotEffectivelyExternalFunctionButOverridesExternal(context: CheckerContext): Boolean {
if (this !is FirFunction || symbol.isEffectivelyExternal(context)) return false
return symbol.isOverridingExternalWithOptionalParams(context)
}
}