From cc0919d0f0b8a48a265d5f29ae44eb49479aaad2 Mon Sep 17 00:00:00 2001 From: Timofey Solonin Date: Sat, 21 Sep 2024 12:43:24 +0200 Subject: [PATCH] Allow using categories in the def file to reintroduce methods in the class ^KT-71624 --- .../kotlin/native/interop/indexer/Indexer.kt | 5 +- .../native/interop/indexer/NativeIndex.kt | 1 + .../kotlin/native/interop/gen/jvm/main.kt | 3 +- .../cases/smoke5/contents.gold.txt | 45 +++++++++++++++ .../cases/smoke5/dependency.def | 5 ++ .../cases/smoke6/contents.gold.txt | 55 +++++++++++++++++++ .../cases/smoke6/dependency.def | 9 +++ .../Headers/dependency4.h | 9 +++ .../Modules/module.modulemap | 6 ++ ...InteropIncludeCategoriesTestGenerated.java | 12 ++++ .../jetbrains/kotlin/konan/util/DefFile.kt | 4 ++ 11 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke5/contents.gold.txt create mode 100644 native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke5/dependency.def create mode 100644 native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke6/contents.gold.txt create mode 100644 native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke6/dependency.def create mode 100644 native/native.tests/testData/CInterop/frameworkIncludeCategories/dependency4.framework/Headers/dependency4.h create mode 100644 native/native.tests/testData/CInterop/frameworkIncludeCategories/dependency4.framework/Modules/module.modulemap 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 97d5b2fdf3791..7fac9c5328c2a 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 @@ -447,7 +447,10 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole val categoryClassName = clang_getCursorDisplayName(categoryClassCursor).convertAndDispose() if (className == categoryClassName) { val categoryFile = getContainingFile(childCursor) - if (clang_File_isEqual(categoryFile, classFile) != 0) { + val isCategoryInTheSameFileAsClass = clang_File_isEqual(categoryFile, classFile) != 0 + val isCategoryFromDefFile = library.allowIncludingObjCCategoriesFromDefFile + && clang_Location_isFromMainFile(clang_getCursorLocation(childCursor)) != 0 + if (isCategoryInTheSameFileAsClass || isCategoryFromDefFile) { result += childCursor } } 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 a2a852c0dc2bc..bdf3ae6a88df8 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 @@ -116,6 +116,7 @@ data class NativeLibrary( val headerExclusionPolicy: HeaderExclusionPolicy, val headerFilter: NativeLibraryHeaderFilter, val objCClassesIncludingCategories: Set, + val allowIncludingObjCCategoriesFromDefFile: Boolean, ) : Compilation data class IndexerResult(val index: NativeIndex, val compilation: CompilationWithPCH) diff --git a/kotlin-native/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/kotlin-native/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index db5133aef07ab..2db0de84116f9 100644 --- a/kotlin-native/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/kotlin-native/Interop/StubGenerator/src/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -595,7 +595,8 @@ internal fun buildNativeLibrary( excludeSystemLibs = excludeSystemLibs, headerExclusionPolicy = headerExclusionPolicy, headerFilter = headerFilter, - objCClassesIncludingCategories = objCClassesIncludingCategories + objCClassesIncludingCategories = objCClassesIncludingCategories, + allowIncludingObjCCategoriesFromDefFile = def.config.allowIncludingObjCCategoriesFromDefFile, ) } diff --git a/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke5/contents.gold.txt b/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke5/contents.gold.txt new file mode 100644 index 0000000000000..e48e22811d4be --- /dev/null +++ b/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke5/contents.gold.txt @@ -0,0 +1,45 @@ +library { + // module name: + + library fragment { + // package name: dependency + + // class name: dependency/MyClass + // class name: dependency/MyClass.Companion + // class name: dependency/MyClassMeta + // class name: dependency/MyProtocolProtocol + // class name: dependency/MyProtocolProtocolMeta + + @kotlinx/cinterop/ExternalObjCClass + public open class dependency/MyClass : kotlinx/cinterop/ObjCObjectBase { + + protected /* secondary */ constructor() + + // companion object: Companion + + // nested class: Companion + } + + public final companion object dependency/MyClass.Companion : dependency/MyClassMeta, kotlinx/cinterop/ObjCClassOf { + + private constructor() + } + + @kotlinx/cinterop/ExternalObjCClass + public open class dependency/MyClassMeta : kotlinx/cinterop/ObjCObjectBaseMeta { + + protected /* secondary */ constructor() + } + + @kotlinx/cinterop/ExternalObjCClass(protocolGetter = "kniprot_dependency0_MyProtocol") + public abstract interface dependency/MyProtocolProtocol : kotlinx/cinterop/ObjCObject { + + @kotlinx/cinterop/ObjCMethod(selector = "wasInMyClass", encoding = "v16@0:8", isStret = false) + public abstract fun wasInMyClass(): kotlin/Unit + } + + @kotlinx/cinterop/ExternalObjCClass(protocolGetter = "kniprot_dependency0_MyProtocol") + public abstract interface dependency/MyProtocolProtocolMeta : kotlinx/cinterop/ObjCClass /* = kotlinx/cinterop/ObjCObjectMeta^ */ { + } + } +} diff --git a/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke5/dependency.def b/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke5/dependency.def new file mode 100644 index 0000000000000..5d2fb5001e3f5 --- /dev/null +++ b/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke5/dependency.def @@ -0,0 +1,5 @@ +# In Apple frameworks methods are sometimes moved from class declaration into a protocol and the class conforms to the protocol in an extension +# This is a binary incompatible change in cinterop klibs +language = Objective-C +modules=dependency4 +objcClassesIncludingCategories = MyClass \ No newline at end of file diff --git a/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke6/contents.gold.txt b/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke6/contents.gold.txt new file mode 100644 index 0000000000000..ed882f75c31c6 --- /dev/null +++ b/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke6/contents.gold.txt @@ -0,0 +1,55 @@ +library { + // module name: + + library fragment { + // package name: dependency + + // class name: dependency/MyClass + // class name: dependency/MyClass.Companion + // class name: dependency/MyClassMeta + // class name: dependency/MyProtocolProtocol + // class name: dependency/MyProtocolProtocolMeta + + @kotlinx/cinterop/ExternalObjCClass + public open class dependency/MyClass : kotlinx/cinterop/ObjCObjectBase { + + protected /* secondary */ constructor() + + @kotlinx/cinterop/ObjCMethod(selector = "wasInMyClass", encoding = "v16@0:8", isStret = false) + public open external fun wasInMyClass(): kotlin/Unit + + // companion object: Companion + + // nested class: Companion + } + + public final companion object dependency/MyClass.Companion : dependency/MyClassMeta, kotlinx/cinterop/ObjCClassOf { + + private constructor() + } + + @kotlinx/cinterop/ExternalObjCClass + public open class dependency/MyClassMeta : kotlinx/cinterop/ObjCObjectBaseMeta { + + protected /* secondary */ constructor() + } + + @kotlinx/cinterop/ExternalObjCClass(protocolGetter = "kniprot_dependency0_MyProtocol") + public abstract interface dependency/MyProtocolProtocol : kotlinx/cinterop/ObjCObject { + + @kotlinx/cinterop/ObjCMethod(selector = "wasInMyClass", encoding = "v16@0:8", isStret = false) + public abstract fun wasInMyClass(): kotlin/Unit + } + + @kotlinx/cinterop/ExternalObjCClass(protocolGetter = "kniprot_dependency0_MyProtocol") + public abstract interface dependency/MyProtocolProtocolMeta : kotlinx/cinterop/ObjCClass /* = kotlinx/cinterop/ObjCObjectMeta^ */ { + } + + package { + + @kotlinx/cinterop/ObjCMethod(selector = "wasInMyClass", encoding = "v16@0:8", isStret = false) + @kotlin/Deprecated(message = "Use instance method instead", replaceWith = kotlin/ReplaceWith(imports = [], expression = ""), level = kotlin/DeprecationLevel.WARNING) + public final external fun dependency/MyClass.wasInMyClass(): kotlin/Unit + } + } +} diff --git a/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke6/dependency.def b/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke6/dependency.def new file mode 100644 index 0000000000000..0cfb3f5a264d8 --- /dev/null +++ b/native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke6/dependency.def @@ -0,0 +1,9 @@ +# We use the category in the def file to add back the method +language = Objective-C +modules=dependency4 +objcClassesIncludingCategories = MyClass +allowIncludingObjCCategoriesFromDefFile = true +--- +@interface MyClass (K) +-(void) wasInMyClass; +@end \ No newline at end of file diff --git a/native/native.tests/testData/CInterop/frameworkIncludeCategories/dependency4.framework/Headers/dependency4.h b/native/native.tests/testData/CInterop/frameworkIncludeCategories/dependency4.framework/Headers/dependency4.h new file mode 100644 index 0000000000000..3dc9430a2a692 --- /dev/null +++ b/native/native.tests/testData/CInterop/frameworkIncludeCategories/dependency4.framework/Headers/dependency4.h @@ -0,0 +1,9 @@ +@protocol MyProtocol +-(void) wasInMyClass; +@end + +@interface MyClass +@end + +@interface MyClass () +@end \ No newline at end of file diff --git a/native/native.tests/testData/CInterop/frameworkIncludeCategories/dependency4.framework/Modules/module.modulemap b/native/native.tests/testData/CInterop/frameworkIncludeCategories/dependency4.framework/Modules/module.modulemap new file mode 100644 index 0000000000000..9192b105a736a --- /dev/null +++ b/native/native.tests/testData/CInterop/frameworkIncludeCategories/dependency4.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module dependency4 { + umbrella header "dependency4.h" + + export * + module * { export * } +} \ No newline at end of file diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CInteropIncludeCategoriesTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CInteropIncludeCategoriesTestGenerated.java index f7fe5da6ad324..d7a8465aa4e47 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CInteropIncludeCategoriesTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/CInteropIncludeCategoriesTestGenerated.java @@ -52,4 +52,16 @@ public void testSmoke3() { public void testSmoke4() { runTest("native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke4/"); } + + @Test + @TestMetadata("smoke5") + public void testSmoke5() { + runTest("native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke5/"); + } + + @Test + @TestMetadata("smoke6") + public void testSmoke6() { + runTest("native/native.tests/testData/CInterop/frameworkIncludeCategories/cases/smoke6/"); + } } diff --git a/native/utils/src/org/jetbrains/kotlin/konan/util/DefFile.kt b/native/utils/src/org/jetbrains/kotlin/konan/util/DefFile.kt index b11e101b72269..33707167cf8a7 100644 --- a/native/utils/src/org/jetbrains/kotlin/konan/util/DefFile.kt +++ b/native/utils/src/org/jetbrains/kotlin/konan/util/DefFile.kt @@ -140,6 +140,10 @@ class DefFile(val file:File?, val config:DefFileConfig, val manifestAddendProper properties.getSpaceSeparated("objcClassesIncludingCategories") } + val allowIncludingObjCCategoriesFromDefFile by lazy { + properties.getProperty("allowIncludingObjCCategoriesFromDefFile")?.toBoolean() ?: false + } + val userSetupHint by lazy { properties.getProperty("userSetupHint") }