From 2f0bdfc5e286ccac2ab7fe11d36e750baa7ff3ee Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Thu, 12 Jan 2023 12:40:39 +0200 Subject: [PATCH] [K/N] Add objcClassesIncludingCategories cinterop property It allows to list Objective-C classes that should include corresponding categories from the same file. The current implementation is super-simple and slow, but it is OK since it is not intended to be a general-purpose solution for now. --- .../kotlin/native/interop/indexer/Indexer.kt | 43 ++++++++++++++++++- .../native/interop/indexer/NativeIndex.kt | 31 ++++++++----- .../kotlin/native/interop/gen/jvm/main.kt | 4 +- .../jetbrains/kotlin/konan/util/DefFile.kt | 3 ++ 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index ead9eb78a04..372b0fe1936 100644 --- a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -64,6 +64,7 @@ private class ObjCClassImpl( override val methods = mutableListOf() override val properties = mutableListOf() override var baseClass: ObjCClass? = null + override val includedCategories = mutableListOf() } private class ObjCCategoryImpl( @@ -392,10 +393,48 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole return objCClassRegistry.getOrPut(cursor, { ObjCClassImpl(name, getLocation(cursor), isForwardDeclaration = false, binaryName = getObjCBinaryName(cursor).takeIf { it != name }) - }) { - addChildrenToObjCContainer(cursor, it) + }) { objcClass -> + addChildrenToObjCContainer(cursor, objcClass) + if (name in this.library.objCClassesIncludingCategories) { + // We don't include methods from categories to class during indexing + // because indexing does not care about how class is represented in Kotlin. + // Instead, it should be done during StubIR construction. + objcClass.includedCategories += collectClassCategories(cursor, name).mapNotNull { getObjCCategoryAt(it) } + } } + } + /** + * Find all categories for a class that is pointed by [classCursor] in the same file. + * NB: Current implementation is rather slow as it walks the whole translation unit. + */ + private fun collectClassCategories(classCursor: CValue, className: String): List> { + assert(classCursor.kind == CXCursorKind.CXCursor_ObjCInterfaceDecl) { classCursor.kind } + val classFile = getContainingFile(classCursor) + val result = mutableListOf>() + // Accessing the whole translation unit (TU) is overkill, but it is the simplest solution which is doable + // since we use this function for a narrow set of cases. + // Possible improvements: + // 1. Find/create a function that returns a file scope. `clang_findReferencesInFile` does not seem to work because for categories + // it returns `CXCursor_ObjCClassRef` (@interface >CLASS_REFERENCE<(CategoryName)) and there is no easy way to access category from + // there. + // 2. Extract categories collection into a separate TU pass and create Class -> [Category] mapping. This way we can avoid visiting + // TU for every class. + val translationUnit = clang_getCursorLexicalParent(classCursor) + visitChildren(translationUnit) { childCursor, _ -> + if (childCursor.kind == CXCursorKind.CXCursor_ObjCCategoryDecl) { + val categoryClassCursor = getObjCCategoryClassCursor(childCursor) + val categoryClassName = clang_getCursorDisplayName(categoryClassCursor).convertAndDispose() + if (className == categoryClassName) { + val categoryFile = getContainingFile(childCursor) + if (clang_File_isEqual(categoryFile, classFile) != 0) { + result += childCursor + } + } + } + CXChildVisitResult.CXChildVisit_Continue + } + return result } private fun getObjCProtocolAt(cursor: CValue): ObjCProtocolImpl { diff --git a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index cff67b954ed..3ebaaa58d82 100644 --- a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -100,16 +100,23 @@ data class CompilationWithPCH( get() = emptyList() } -// TODO: Compilation hierarchy seems to require some refactoring. - -data class NativeLibrary(override val includes: List, - override val additionalPreambleLines: List, - override val compilerArgs: List, - val headerToIdMapper: HeaderToIdMapper, - override val language: Language, - val excludeSystemLibs: Boolean, // TODO: drop? - val headerExclusionPolicy: HeaderExclusionPolicy, - val headerFilter: NativeLibraryHeaderFilter) : Compilation +/** + * + * @param objCClassesIncludingCategories Objective-C classes that should be merged with categories from the same file. + * + * TODO: Compilation hierarchy seems to require some refactoring. + */ +data class NativeLibrary( + override val includes: List, + override val additionalPreambleLines: List, + override val compilerArgs: List, + val headerToIdMapper: HeaderToIdMapper, + override val language: Language, + val excludeSystemLibs: Boolean, // TODO: drop? + val headerExclusionPolicy: HeaderExclusionPolicy, + val headerFilter: NativeLibraryHeaderFilter, + val objCClassesIncludingCategories: Set, +) : Compilation data class IndexerResult(val index: NativeIndex, val compilation: CompilationWithPCH) @@ -270,6 +277,10 @@ data class ObjCProperty(val name: String, val getter: ObjCMethod, val setter: Ob abstract class ObjCClass(name: String) : ObjCClassOrProtocol(name) { abstract val binaryName: String? abstract val baseClass: ObjCClass? + /** + * Categories whose methods and properties should be generated as members of Kotlin class. + */ + abstract val includedCategories: List } abstract class ObjCProtocol(name: String) : ObjCClassOrProtocol(name) diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index ba15f4e9532..f3b02630b46 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -568,6 +568,7 @@ internal fun buildNativeLibrary( val headerExclusionPolicy = HeaderExclusionPolicyImpl(imports) + val objCClassesIncludingCategories = def.config.objcClassesIncludingCategories.toSet() return NativeLibrary( includes = includes, additionalPreambleLines = compilation.additionalPreambleLines, @@ -576,7 +577,8 @@ internal fun buildNativeLibrary( language = compilation.language, excludeSystemLibs = excludeSystemLibs, headerExclusionPolicy = headerExclusionPolicy, - headerFilter = headerFilter + headerFilter = headerFilter, + objCClassesIncludingCategories = objCClassesIncludingCategories ) } diff --git a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt index 00a5004745d..a9da3309f79 100644 --- a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt +++ b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt @@ -132,6 +132,9 @@ class DefFile(val file:File?, val config:DefFileConfig, val manifestAddendProper properties.getProperty("plugin") } + val objcClassesIncludingCategories by lazy { + properties.getSpaceSeparated("objcClassesIncludingCategories") + } } }