[K/N] Implement FirNativeObjCOverrideInitChecker

^KT-61564
This commit is contained in:
Vladimir Sukharev
2023-11-14 14:28:44 +01:00
committed by Space Team
parent 4a97db82b3
commit dbba6d09ff
9 changed files with 159 additions and 5 deletions
@@ -94,5 +94,14 @@ object NATIVE_DIAGNOSTICS_LIST : DiagnosticList("FirNativeErrors") {
parameter<String>("annotationKind")
parameter<ConeKotlinType>("unexpectedType")
}
val CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER by error<KtElement>() {
parameter<FqName>("annotation")
}
val CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR by error<KtElement>() {
parameter<FqName>("annotation")
}
val CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS by error<KtElement>() {
parameter<FqName>("annotation")
}
}
}
@@ -59,6 +59,9 @@ object FirNativeErrors {
val MUST_NOT_HAVE_EXTENSION_RECEIVER: KtDiagnosticFactory1<String> by error1<KtElement, String>()
val MUST_BE_OBJC_OBJECT_TYPE: KtDiagnosticFactory2<String, ConeKotlinType> by error2<KtElement, String, ConeKotlinType>()
val MUST_BE_UNIT_TYPE: KtDiagnosticFactory2<String, ConeKotlinType> by error2<KtElement, String, ConeKotlinType>()
val CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER: KtDiagnosticFactory1<FqName> by error1<KtElement, FqName>()
val CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR: KtDiagnosticFactory1<FqName> by error1<KtElement, FqName>()
val CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS: KtDiagnosticFactory1<FqName> by error1<KtElement, FqName>()
init {
RootDiagnosticRendererFactory.registerFactory(FirNativeErrorsDefaultMessages)
@@ -12,6 +12,9 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOLS
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.CANNOT_CHECK_FOR_FORWARD_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.EMPTY_OBJC_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.FORWARD_DECLARATION_AS_CLASS_LITERAL
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT
@@ -127,5 +130,20 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
TO_STRING,
FirDiagnosticRenderers.RENDER_TYPE
)
map.put(
CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER,
"Constructor with @{0} overrides initializer that is already overridden explicitly",
TO_STRING
)
map.put(
CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR,
"Constructor with @{0} doesn''t override any super class constructor.\nIt must completely match by parameter names and types.",
TO_STRING
)
map.put(
CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS,
"Constructor with @{0} matches more than one of super constructors",
TO_STRING
)
}
}
@@ -0,0 +1,120 @@
/*
* Copyright 2010-2022 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.native.checkers
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.*
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.unsubstitutedScope
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.backend.native.interop.getObjCInitMethod
import org.jetbrains.kotlin.fir.backend.native.interop.isKotlinObjCClass
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.isOverride
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.scopes.getDirectOverriddenMembers
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
object FirNativeObjCOverrideInitChecker : FirClassChecker() {
val objCOverrideInitClassId = ClassId(FqName("kotlinx.cinterop"), Name.identifier("ObjCObjectBase.OverrideInit"))
@OptIn(SymbolInternals::class)
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
val session = context.session
fun FirConstructor.overridesConstructor(other: FirConstructor): Boolean {
if (valueParameters.size != other.valueParameters.size)
return false
return valueParameters.zip(other.valueParameters).all { (thisParameter, otherParameter) ->
thisParameter.name == otherParameter.name && thisParameter.returnTypeRef.coneType == otherParameter.returnTypeRef.coneType
}
}
fun checkCanGenerateOverrideInit(firClass: FirClass, constructor: FirConstructor) {
val superClass = (firClass as FirRegularClass).symbol.getSuperClassSymbolOrAny(session)
val superConstructors = superClass.fir.constructors(session).filter {
constructor.overridesConstructor(it.fir)
}.toList()
val superConstructor: FirConstructorSymbol = superConstructors.singleOrNull() ?: run {
if (superConstructors.isEmpty())
reporter.reportOn(
constructor.source,
FirNativeErrors.CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR,
objCOverrideInitClassId.asSingleFqName(),
context
)
else
reporter.reportOn(
constructor.source,
FirNativeErrors.CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS,
objCOverrideInitClassId.asSingleFqName(),
context
)
return
}
val initMethod = superConstructor.fir.getObjCInitMethod(session, ScopeSession())!!
// Remove fake overrides of this init method, also check for explicit overriding:
firClass.declarations.forEach {
if (it is FirSimpleFunction && initMethod.symbol in it.getOverriddenSymbols(context) && !it.isSubstitutionOrIntersectionOverride) {
reporter.reportOn(
constructor.source,
FirNativeErrors.CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER,
objCOverrideInitClassId.asSingleFqName(),
context
)
}
}
}
fun checkKotlinObjCClass(firClass: FirClass) {
for (decl in firClass.declarations) {
if (decl is FirConstructor && decl.annotations.hasAnnotation(objCOverrideInitClassId, session))
checkCanGenerateOverrideInit(firClass, decl)
}
}
if (!declaration.isExpect && declaration.symbol.isKotlinObjCClass(context.session)) {
checkKotlinObjCClass(declaration)
}
}
}
// Copy-pasted from plugins/noarg/noarg.k2/src/org/jetbrains/kotlin/noarg/fir/FirNoArgDeclarationChecker.kt
private fun FirRegularClassSymbol.getSuperClassSymbolOrAny(session: FirSession): FirRegularClassSymbol {
for (superType in resolvedSuperTypes) {
val symbol = superType.fullyExpandedType(session).toRegularClassSymbol(session) ?: continue
if (symbol.classKind == ClassKind.CLASS) return symbol
}
return session.builtinTypes.anyType.type.toRegularClassSymbol(session) ?: error("Symbol for Any not found")
}
// copy-pasted from org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirInlineDeclarationChecker
private fun FirCallableDeclaration.getOverriddenSymbols(context: CheckerContext): List<FirCallableSymbol<out FirCallableDeclaration>> {
if (!this.isOverride) return emptyList()
val classSymbol = this.containingClassLookupTag()?.toSymbol(context.session) as? FirClassSymbol<*> ?: return emptyList()
val scope = classSymbol.unsubstitutedScope(context)
//this call is needed because AbstractFirUseSiteMemberScope collect overrides in it only,
//and not in processDirectOverriddenFunctionsWithBaseScope
scope.processFunctionsByName(this.symbol.name) { }
return scope.getDirectOverriddenMembers(this.symbol, true)
}
@@ -28,6 +28,7 @@ object NativeDeclarationCheckers : DeclarationCheckers() {
FirNativeObjCNameOverridesChecker,
FirNativeObjCOutletChecker,
FirNativeObjCActionChecker,
FirNativeObjCOverrideInitChecker,
)
override val regularClassCheckers: Set<FirRegularClassChecker>
@@ -672,6 +672,9 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set<String> = setOf(
"MUST_NOT_HAVE_EXTENSION_RECEIVER",
"MUST_BE_OBJC_OBJECT_TYPE",
"MUST_BE_UNIT_TYPE",
"CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER",
"CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR",
"CONSTRUCTOR_MATCHES_SEVERAL_SUPER_CONSTRUCTORS",
"NESTED_EXTERNAL_DECLARATION",
"WRONG_EXTERNAL_DECLARATION",
"NESTED_CLASS_IN_EXTERNAL_INTERFACE",
@@ -53,7 +53,7 @@ internal fun FirFunction.getObjCMethodInfoFromOverriddenFunctions(session: FirSe
* mimics ConstructorDescriptor.getObjCInitMethod()
*/
@OptIn(SymbolInternals::class)
private fun FirConstructor.getObjCInitMethod(session: FirSession, scopeSession: ScopeSession): FirFunction? {
fun FirConstructor.getObjCInitMethod(session: FirSession, scopeSession: ScopeSession): FirFunction? {
this.annotations.getAnnotationByClassId(NativeStandardInteropNames.objCConstructorClassId, session)?.let { annotation ->
val initSelector: String = annotation.constStringArgument("initSelector")
val classSymbol = containingClassLookupTag()?.toSymbol(session) as FirClassSymbol<*>
@@ -5,7 +5,7 @@ import platform.darwin.*
import platform.Foundation.*
class Zzz : NSAssertionHandler {
@OptIn(kotlinx.cinterop.BetaInteropApi::class)
<!CONSTRUCTOR_DOES_NOT_OVERRIDE_ANY_SUPER_CONSTRUCTOR!>@OptIn(kotlinx.cinterop.BetaInteropApi::class)
@OverrideInit
constructor(x: Int) { }
constructor(x: Int) { }<!>
}
@@ -5,9 +5,9 @@ import platform.darwin.*
import platform.Foundation.*
class Zzz : NSString {
@OptIn(kotlinx.cinterop.BetaInteropApi::class)
<!CONSTRUCTOR_OVERRIDES_ALREADY_OVERRIDDEN_OBJC_INITIALIZER!>@OptIn(kotlinx.cinterop.BetaInteropApi::class)
@OverrideInit
constructor(coder: NSCoder) { }
constructor(coder: NSCoder) { }<!>
@Suppress("OVERRIDE_DEPRECATION")
override fun initWithCoder(coder: NSCoder): String? = "zzz"