[K/N] Implement FirNativeObjCOutletChecker and FirNativeObjCActionChecker

^KT-61564
This commit is contained in:
Vladimir Sukharev
2023-11-12 19:45:04 +01:00
committed by Space Team
parent 08bc0a6bd0
commit c5aed2b094
17 changed files with 213 additions and 19 deletions
@@ -79,5 +79,20 @@ object NATIVE_DIAGNOSTICS_LIST : DiagnosticList("FirNativeErrors") {
val FORWARD_DECLARATION_AS_CLASS_LITERAL by error<KtElement> {
parameter<ConeKotlinType>("type")
}
val TWO_OR_LESS_PARAMETERS_ARE_SUPPORTED_HERE by error<KtElement>()
val PROPERTY_MUST_BE_VAR by error<KtElement>() {
parameter<FqName>("annotationName")
}
val MUST_NOT_HAVE_EXTENSION_RECEIVER by error<KtElement> {
parameter<String>("annotationKind")
}
val MUST_BE_OBJC_OBJECT_TYPE by error<KtElement>() {
parameter<String>("annotationName")
parameter<ConeKotlinType>("unexpectedType")
}
val MUST_BE_UNIT_TYPE by error<KtElement>() {
parameter<String>("annotationKind")
parameter<ConeKotlinType>("unexpectedType")
}
}
}
@@ -14,6 +14,7 @@ dependencies {
*/
implementation(project(":compiler:frontend"))
implementation(project(":compiler:psi"))
implementation(project(":compiler:fir:native"))
compileOnly(project(":core:compiler.common.native"))
compileOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
@@ -54,6 +54,11 @@ object FirNativeErrors {
val UNCHECKED_CAST_TO_FORWARD_DECLARATION: KtDiagnosticFactory2<ConeKotlinType, ConeKotlinType> by warning2<KtElement, ConeKotlinType, ConeKotlinType>()
val FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT: KtDiagnosticFactory1<ConeKotlinType> by error1<KtElement, ConeKotlinType>()
val FORWARD_DECLARATION_AS_CLASS_LITERAL: KtDiagnosticFactory1<ConeKotlinType> by error1<KtElement, ConeKotlinType>()
val TWO_OR_LESS_PARAMETERS_ARE_SUPPORTED_HERE: KtDiagnosticFactory0 by error0<KtElement>()
val PROPERTY_MUST_BE_VAR: KtDiagnosticFactory1<FqName> by error1<KtElement, FqName>()
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>()
init {
RootDiagnosticRendererFactory.registerFactory(FirNativeErrorsDefaultMessages)
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics.native
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.KtDiagnosticRenderers.TO_STRING
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
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
@@ -34,10 +33,15 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVA
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.INVALID_REFINES_IN_SWIFT_TARGETS
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MISSING_EXACT_OBJC_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MISSING_EXCEPTION_IN_THROWS_ON_SUSPEND
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.REDUNDANT_SWIFT_REFINEMENT
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MUST_BE_OBJC_OBJECT_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MUST_BE_UNIT_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MUST_NOT_HAVE_EXTENSION_RECEIVER
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.NON_LITERAL_OBJC_NAME_ARG
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.PROPERTY_MUST_BE_VAR
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.REDUNDANT_SWIFT_REFINEMENT
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.SUBTYPE_OF_HIDDEN_FROM_OBJC
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.THROWS_LIST_EMPTY
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.TWO_OR_LESS_PARAMETERS_ARE_SUPPORTED_HERE
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.UNCHECKED_CAST_TO_FORWARD_DECLARATION
object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
@@ -108,5 +112,20 @@ object FirNativeErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
"Cannot refer to forward declaration ''{0}'' from class literal.",
FirDiagnosticRenderers.RENDER_TYPE
)
map.put(TWO_OR_LESS_PARAMETERS_ARE_SUPPORTED_HERE, "Only 0, 1 or 2 parameters are supported here")
map.put(PROPERTY_MUST_BE_VAR, "@{0} property must be var", TO_STRING)
map.put(MUST_NOT_HAVE_EXTENSION_RECEIVER, "{0} must not have extension receiver", TO_STRING)
map.put(
MUST_BE_OBJC_OBJECT_TYPE,
"Unexpected {0}: {1}\nOnly Objective-C object types are supported here",
TO_STRING,
FirDiagnosticRenderers.RENDER_TYPE
)
map.put(
MUST_BE_UNIT_TYPE,
"Unexpected {0}: {1}\nOnly \"Unit\" is supported here",
TO_STRING,
FirDiagnosticRenderers.RENDER_TYPE
)
}
}
@@ -0,0 +1,72 @@
/*
* 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.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.FirClassChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MUST_BE_OBJC_OBJECT_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MUST_BE_UNIT_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors.MUST_NOT_HAVE_EXTENSION_RECEIVER
import org.jetbrains.kotlin.fir.backend.native.interop.isKotlinObjCClass
import org.jetbrains.kotlin.fir.backend.native.interop.isObjCObjectType
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.types.UnexpandedTypeCheck
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isUnit
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
object FirNativeObjCActionChecker : FirClassChecker() {
// Copy/pasted from InteropFqNames.objCAction, since module `kotlin-native:backend.native` cannot be imported here for now.
val objCActionFqName = FqName("kotlinx.cinterop.ObjCAction")
@OptIn(UnexpandedTypeCheck::class)
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
val session = context.session
fun checkCanGenerateFunctionImp(function: FirSimpleFunction) {
if (function.valueParameters.size > 2)
reporter.reportOn(function.source, FirNativeErrors.TWO_OR_LESS_PARAMETERS_ARE_SUPPORTED_HERE, context)
}
fun checkCanGenerateActionImp(function: FirSimpleFunction) {
val action = "@$objCActionFqName"
function.receiverParameter?.let {
reporter.reportOn(it.source, MUST_NOT_HAVE_EXTENSION_RECEIVER, "$action method", context)
}
function.valueParameters.forEach {
val kotlinType = it.returnTypeRef
if (!kotlinType.isObjCObjectType(session))
reporter.reportOn(it.source, MUST_BE_OBJC_OBJECT_TYPE, "$action method parameter type", kotlinType.coneType, context)
}
val returnType = function.returnTypeRef
if (!returnType.isUnit)
reporter.reportOn(function.source, MUST_BE_UNIT_TYPE, "$action method return type", returnType.coneType, context)
checkCanGenerateFunctionImp(function)
}
fun checkKotlinObjCClass(firClass: FirClass) {
for (decl in firClass.declarations) {
if (decl is FirSimpleFunction && decl.annotations.hasAnnotation(ClassId.topLevel(objCActionFqName), session))
checkCanGenerateActionImp(decl)
}
}
if (!declaration.isExpect && declaration.symbol.isKotlinObjCClass(context.session)) {
checkKotlinObjCClass(declaration)
}
}
}
@@ -0,0 +1,70 @@
/*
* 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.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.FirClassChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.native.FirNativeErrors
import org.jetbrains.kotlin.fir.backend.native.interop.isKotlinObjCClass
import org.jetbrains.kotlin.fir.backend.native.interop.isObjCObjectType
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirPropertyAccessor
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
object FirNativeObjCOutletChecker : FirClassChecker() {
// Copy/pasted from InteropFqNames.objCOutlet, since module `kotlin-native:backend.native` cannot be imported here for now.
val objCOutletFqName = FqName("kotlinx.cinterop.ObjCOutlet")
override fun check(declaration: FirClass, context: CheckerContext, reporter: DiagnosticReporter) {
val session = context.session
fun checkCanGenerateFunctionImp(setter: FirPropertyAccessor) {
if (setter.valueParameters.size > 2)
reporter.reportOn(setter.source, FirNativeErrors.TWO_OR_LESS_PARAMETERS_ARE_SUPPORTED_HERE, context)
}
fun checkCanGenerateOutletSetterImp(property: FirProperty) {
if (!property.isVar) {
reporter.reportOn(property.source, FirNativeErrors.PROPERTY_MUST_BE_VAR, objCOutletFqName, context)
return
}
property.receiverParameter?.let {
reporter.reportOn(it.source, FirNativeErrors.MUST_NOT_HAVE_EXTENSION_RECEIVER, "@$objCOutletFqName", context)
}
val type = property.returnTypeRef
if (!type.isObjCObjectType(session))
reporter.reportOn(
property.returnTypeRef.source,
FirNativeErrors.MUST_BE_OBJC_OBJECT_TYPE,
"@$objCOutletFqName type",
type.coneType,
context
)
checkCanGenerateFunctionImp(property.setter!!)
}
fun checkKotlinObjCClass(firClass: FirClass) {
for (decl in firClass.declarations) {
if (decl is FirProperty && decl.annotations.hasAnnotation(ClassId.topLevel(objCOutletFqName), session))
checkCanGenerateOutletSetterImp(decl)
}
}
if (!declaration.isExpect && declaration.symbol.isKotlinObjCClass(context.session)) {
checkKotlinObjCClass(declaration)
}
}
}
@@ -25,7 +25,9 @@ object NativeDeclarationCheckers : DeclarationCheckers() {
override val classCheckers: Set<FirClassChecker>
get() = setOf(
FirNativeObjCRefinementOverridesChecker,
FirNativeObjCNameOverridesChecker
FirNativeObjCNameOverridesChecker,
FirNativeObjCOutletChecker,
FirNativeObjCActionChecker,
)
override val regularClassCheckers: Set<FirRegularClassChecker>
@@ -667,6 +667,11 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set<String> = setOf(
"CANNOT_CHECK_FOR_FORWARD_DECLARATION",
"FORWARD_DECLARATION_AS_REIFIED_TYPE_ARGUMENT",
"FORWARD_DECLARATION_AS_CLASS_LITERAL",
"TWO_OR_LESS_PARAMETERS_ARE_SUPPORTED_HERE",
"PROPERTY_MUST_BE_VAR",
"MUST_NOT_HAVE_EXTENSION_RECEIVER",
"MUST_BE_OBJC_OBJECT_TYPE",
"MUST_BE_UNIT_TYPE",
"NESTED_EXTERNAL_DECLARATION",
"WRONG_EXTERNAL_DECLARATION",
"NESTED_CLASS_IN_EXTERNAL_INTERFACE",
@@ -10,12 +10,9 @@ import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.getContainingClass
import org.jetbrains.kotlin.fir.resolve.getContainingDeclaration
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.providers.toSymbol
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.scopes.getDirectOverriddenFunctions
import org.jetbrains.kotlin.fir.scopes.processAllFunctions
import org.jetbrains.kotlin.fir.scopes.scopeForClass
@@ -24,6 +21,7 @@ import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.NativeStandardInteropNames
@@ -154,3 +152,10 @@ internal fun FirClassSymbol<*>.isExternalObjCClass(session: FirSession): Boolean
fun FirClassSymbol<*>.parentsWithSelf(session: FirSession): Sequence<FirClassLikeDeclaration> {
return generateSequence<FirClassLikeDeclaration>(fir) { it.getContainingDeclaration(session) }
}
fun FirClassSymbol<*>.isKotlinObjCClass(session: FirSession): Boolean = isObjCClass(session) && !isExternalObjCClass(session)
fun FirTypeRef.isObjCObjectType(session: FirSession): Boolean {
val symbol = firClassLike(session)?.symbol
return symbol is FirClassSymbol && symbol.isObjCClass(session)
}
@@ -8,5 +8,5 @@ import platform.Foundation.*
class Zzz : NSAssertionHandler() {
@OptIn(kotlinx.cinterop.BetaInteropApi::class)
@ObjCAction
fun String.foo() = println(this)
fun <!MUST_NOT_HAVE_EXTENSION_RECEIVER!>String<!>.foo() = println(this)
}
@@ -8,5 +8,5 @@ import platform.Foundation.*
class Zzz : NSAssertionHandler() {
@OptIn(kotlinx.cinterop.BetaInteropApi::class)
@ObjCAction
fun foo(x: String) = println(x)
fun foo(<!MUST_BE_OBJC_OBJECT_TYPE!>x: String<!>) = println(x)
}
@@ -5,7 +5,7 @@ import platform.darwin.*
import platform.Foundation.*
class Zzz : NSAssertionHandler() {
@OptIn(kotlinx.cinterop.BetaInteropApi::class)
<!MUST_BE_UNIT_TYPE!>@OptIn(kotlinx.cinterop.BetaInteropApi::class)
@ObjCAction
fun foo() = 42
fun foo() = 42<!>
}
@@ -6,7 +6,7 @@ import platform.Foundation.*
@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
class Zzz : NSAssertionHandler() {
@OptIn(kotlinx.cinterop.BetaInteropApi::class)
<!PROPERTY_MUST_BE_VAR!>@OptIn(kotlinx.cinterop.BetaInteropApi::class)
@ObjCOutlet
val x: NSObject get() = this
val x: NSObject get() = this<!>
}
@@ -7,7 +7,7 @@ import platform.Foundation.*
class Zzz : NSAssertionHandler() {
@OptIn(kotlinx.cinterop.BetaInteropApi::class)
@ObjCOutlet
var NSObject.x: NSObject
var <!MUST_NOT_HAVE_EXTENSION_RECEIVER!>NSObject<!>.x: NSObject
get() = this
set(value: NSObject) { }
}
@@ -7,7 +7,7 @@ import platform.Foundation.*
class Zzz : NSAssertionHandler() {
@OptIn(kotlinx.cinterop.BetaInteropApi::class)
@ObjCOutlet
var x: String
var x: <!MUST_BE_OBJC_OBJECT_TYPE!>String<!>
get() = "zzz"
set(value: String) { }
}
@@ -5,7 +5,7 @@ import platform.darwin.*
import platform.Foundation.*
class Zzz : NSAssertionHandler() {
@OptIn(kotlinx.cinterop.BetaInteropApi::class)
<!TWO_OR_LESS_PARAMETERS_ARE_SUPPORTED_HERE!>@OptIn(kotlinx.cinterop.BetaInteropApi::class)
@ObjCAction
fun foo(x: NSObject, y: NSObject, z: NSObject) { }
fun foo(x: NSObject, y: NSObject, z: NSObject) { }<!>
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.test.frontend.fir
import org.jetbrains.kotlin.backend.common.CommonKLibResolver
import org.jetbrains.kotlin.cli.common.messages.getLogger
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.fir.FirSession
@@ -13,7 +14,6 @@ import org.jetbrains.kotlin.fir.deserialization.ModuleDataProvider
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
import org.jetbrains.kotlin.fir.session.FirNativeSessionFactory
import org.jetbrains.kotlin.ir.backend.js.resolverLogger
import org.jetbrains.kotlin.test.services.configuration.NativeEnvironmentConfigurator
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.test.model.TestModule
@@ -33,7 +33,7 @@ object TestFirNativeSessionFactory {
): FirSession {
val resolvedLibraries = CommonKLibResolver.resolve(
getAllNativeDependenciesPaths(module, testServices),
configuration.resolverLogger,
configuration.getLogger(treatWarningsAsErrors = true),
knownIrProviders = listOf("kotlin.native.cinterop"), // FIXME use KonanLibraryProperResolver instead, as in production.
).getFullResolvedList()