From 72637f19dc5ca7b91c08bddee49dfcc40a835571 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Thu, 12 Jan 2023 13:14:29 +0200 Subject: [PATCH] [K/N][cinterop] Generate members of included categories as class members This way inheritors of such class would be able to override category members the same way they can in Objective-C. Also, API surface of interop libraries should become a little more stable. --- .../kotlin/native/interop/gen/ObjCStubs.kt | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt index 82f741af6d0..019371eb74b 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt @@ -98,7 +98,7 @@ private class ObjCMethodStubBuilder( private val method: ObjCMethod, private val container: ObjCContainer, private val isDesignatedInitializer: Boolean, - override val context: StubsBuildingContext + override val context: StubsBuildingContext, ) : StubElementBuilder { private val isStret: Boolean private val stubReturnType: StubType @@ -112,6 +112,9 @@ private class ObjCMethodStubBuilder( private val isOverride: Boolean = container is ObjCClassOrProtocol && method.isOverride(container) + private val isDeprecatedCategoryMethod: Boolean = + container is ObjCCategory && container in container.clazz.includedCategories + init { val returnType = method.getReturnType(container.classOrProtocol) isStret = returnType.isStret(context.configuration.target) @@ -221,6 +224,7 @@ private class ObjCMethodStubBuilder( annotations = annotations, modality = MemberStubModality.FINAL ) + // TODO: Should we deprecate it as well? createMethod } is ObjCProtocol -> null @@ -228,6 +232,10 @@ private class ObjCMethodStubBuilder( } else { null } + if (isDeprecatedCategoryMethod && annotations.filterIsInstance().isEmpty()) { + val target = if (method.isClass) "class" else "instance" + annotations += AnnotationStub.Deprecated(message = "Use $target method instead", replaceWith = "", level = DeprecationLevel.WARNING) + } return listOfNotNull( FunctionStub( name, @@ -292,8 +300,9 @@ internal val ObjCClassOrProtocol.selfAndSuperTypes: Sequence get() = this.immediateSuperTypes.flatMap { it.selfAndSuperTypes }.distinct() -internal fun ObjCClassOrProtocol.declaredMethods(isClass: Boolean): Sequence = - this.methods.asSequence().filter { it.isClass == isClass } +private fun ObjCContainer.declaredMethods(isClass: Boolean): Sequence = + this.methods.asSequence().filter { it.isClass == isClass } + + if (this is ObjCClass) { includedCategoriesMethods(isClass) } else emptyList() @Suppress("UNUSED_PARAMETER") internal fun Sequence.inheritedTo(container: ObjCClassOrProtocol, isMeta: Boolean): Sequence = @@ -331,6 +340,16 @@ internal fun ObjCClass.getDesignatedInitializerSelectors(result: MutableSet superType.methods.any(this::replaces) } +private fun ObjCClass.includedCategoriesMethods(isMeta: Boolean): List = + includedCategories.flatMap { category -> + category.declaredMethods(isMeta) + } + +private fun ObjCClass.includedCategoriesProperties(isMeta: Boolean): List = + includedCategories.flatMap { category -> + category.properties.filter { it.getter.isClass == isMeta } + } + internal abstract class ObjCContainerStubBuilder( final override val context: StubsBuildingContext, private val container: ObjCClassOrProtocol, @@ -381,7 +400,13 @@ internal abstract class ObjCContainerStubBuilder( this.methods = methods.distinctBy { it.selector }.toList() - this.properties = container.properties.filter { property -> + val properties = container.properties + if (container is ObjCClass) { + container.includedCategoriesProperties(isMeta) + } else { + emptyList() + } + + this.properties = properties.filter { property -> property.getter.isClass == isMeta && // Select only properties that don't override anything: superMethods.none { property.getter.replaces(it) || property.setter?.replaces(it) ?: false } @@ -589,6 +614,10 @@ private class ObjCPropertyStubBuilder( private val getterBuilder: ObjCMethodStubBuilder, private val setterMethod: ObjCMethodStubBuilder? ) : StubElementBuilder { + + private val isDeprecatedCategoryProperty = + container is ObjCCategory && container in container.clazz.includedCategories + override fun build(): List { val type = property.getType(container.classOrProtocol) val kotlinType = context.mirror(type).argType @@ -601,7 +630,12 @@ private class ObjCPropertyStubBuilder( is ObjCCategory -> ClassifierStubType(context.getKotlinClassFor(container.clazz, isMeta = property.getter.isClass)) } val origin = StubOrigin.ObjCProperty(property, container) - return listOf(PropertyStub(mangleSimple(property.name), kotlinType.toStubIrType(), kind, modality, receiver, origin = origin)) + val annotations = if (isDeprecatedCategoryProperty) { + listOf(AnnotationStub.Deprecated(message = "Use instance property instead", replaceWith = "", level = DeprecationLevel.WARNING)) + } else { + emptyList() + } + return listOf(PropertyStub(mangleSimple(property.name), kotlinType.toStubIrType(), kind, modality, receiver, annotations, origin = origin)) } }