diff --git a/compiler/testData/codegen/box/cinterop/kt64105.kt b/compiler/testData/codegen/box/cinterop/kt64105.kt new file mode 100644 index 00000000000..98b1c42246f --- /dev/null +++ b/compiler/testData/codegen/box/cinterop/kt64105.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2023 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. + */ +// TARGET_BACKEND: NATIVE +// MODULE: lib1 +// FILE: lib1.def +language=Objective-C +headers=lib1.h + +// FILE: lib1.h +@class Foo; +@protocol Bar; +struct Baz; + +// MODULE: lib2(lib1) +// FILE: lib2.def +language=Objective-C +headers=lib2.h + +// FILE: lib2.h +#import "../lib1/lib1.h" + +Foo* createFoo() { + return 0; +} + +id createBar() { + return 0; +} + +struct Baz* createBaz() { + return 0; +} + +// MODULE: main(lib1,lib2) +// FILE: main.kt +import kotlinx.cinterop.CPointer +import lib1.* +import lib2.* +import cnames.structs.Baz +import objcnames.classes.Foo +import objcnames.protocols.BarProtocol + +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) +fun box(): String { + val foo: Foo? = createFoo() + if (foo !== null) return "FAIL 1" + + val bar: BarProtocol? = createBar() + if (bar !== null) return "FAIL 2" + + val baz: CPointer? = createBaz() + if (baz !== null) return "FAIL 3" + + return "OK" +} 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 6b88aad3096..ee3fcf3e932 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 @@ -936,6 +936,11 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole CXIdxEntity_ObjCClass -> if (cursor.kind != CXCursorKind.CXCursor_ObjCClassRef /* not a forward declaration */) { indexObjCClass(cursor) + } else { + // It is a class reference. To get the declaration cursor, we can use clang_getCursorReferenced. + // If there is a real declaration besides this forward declaration, the function will automatically + // resolve it. + indexObjCClass(clang_getCursorReferenced(cursor)) } CXIdxEntity_ObjCCategory -> { @@ -946,6 +951,11 @@ public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boole CXIdxEntity_ObjCProtocol -> if (cursor.kind != CXCursorKind.CXCursor_ObjCProtocolRef /* not a forward declaration */) { indexObjCProtocol(cursor) + } else { + // It is a protocol reference. To get the declaration cursor, we can use clang_getCursorReferenced. + // If there is a real declaration besides this forward declaration, the function will automatically + // resolve it. + indexObjCProtocol(clang_getCursorReferenced(cursor)) } CXIdxEntity_ObjCProperty -> { diff --git a/kotlin-native/Interop/StubGenerator/src/test/kotlin/org/jetbrains/kotlin/native/interop/gen/ForwardDeclarationsTests.kt b/kotlin-native/Interop/StubGenerator/src/test/kotlin/org/jetbrains/kotlin/native/interop/gen/ForwardDeclarationsTests.kt new file mode 100644 index 00000000000..8693535e076 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/kotlin/org/jetbrains/kotlin/native/interop/gen/ForwardDeclarationsTests.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2024 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.native.interop.gen + +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.native.interop.indexer.IndexerResult +import org.jetbrains.kotlin.native.interop.indexer.ObjCClassOrProtocol +import org.jetbrains.kotlin.native.interop.indexer.StructDecl +import org.junit.Assume +import kotlin.test.* + +class ForwardDeclarationsTests : InteropTestsBase() { + + private fun StructDecl.getName() = spelling.removePrefix("struct ") + + @Test + fun `struct forward declarations`() { + fun IndexerResult.assertHasOnlyForwardStructs(vararg names: String) { + this.index.structs.forEach { + assertNull(it.def, "${it.spelling} is not a forward declaration") + } + assertEquals(names.toSet(), this.index.structs.map { it.getName() }.toSet()) + } + + val dir = "ForwardDeclarations/struct" + val dependency = buildNativeIndex(dir, "dependency.def") + val main = buildNativeIndex(dir, "main.def", mockImports(dependency)) + + dependency.assertHasOnlyForwardStructs("DependencyUsed", "DependencyUnused", "DependencyAndMain") + + main.assertHasOnlyForwardStructs( + "ImportedUsed", "IncludedUnused", "IncludedUsed", + "MainUnused", "MainUsed", "DependencyAndMain" + ) + } + + @Test + fun `objc forward declarations`() { + Assume.assumeTrue(HostManager.hostIsMac) + + fun Collection.assertHasOnlyForward(vararg names: String) { + this.forEach { + assertTrue(it.isForwardDeclaration, "${it.name} is not a forward declaration") + } + assertEquals(names.toSet(), this.map { it.name }.toSet()) + } + + val dir = "ForwardDeclarations/objc" + val dependency = buildNativeIndex(dir, "dependency.def") + val main = buildNativeIndex(dir, "main.def", mockImports(dependency)) + + dependency.index.objCClasses.assertHasOnlyForward( + "DependencyClassUsed", "DependencyClassUnused", "DependencyAndMainClass" + ) + dependency.index.objCProtocols.assertHasOnlyForward( + "DependencyProtocolUsed", "DependencyProtocolUnused", "DependencyAndMainProtocol" + ) + + main.index.objCClasses.assertHasOnlyForward( + "ImportedClassUsed", "IncludedClassUsed", "IncludedClassUnused", + "MainClassUsed", "MainClassUnused", "DependencyAndMainClass" + ) + + main.index.objCProtocols.assertHasOnlyForward( + "ImportedProtocolUsed", "IncludedProtocolUsed", "IncludedProtocolUnused", + "MainProtocolUsed", "MainProtocolUnused", "DependencyAndMainProtocol" + ) + } + + @Test + fun `struct forward declarations with definitions`() { + val dir = "ForwardDeclarations/structWithDefinition" + val main = buildNativeIndex(dir, "main.def") + val structs = main.index.structs + + structs.forEach { + assertNotNull(it.def, "${it.spelling} is forward declaration") + } + + assertEquals(setOf("Struct1", "Struct2", "Struct3", "Struct4"), structs.map { it.getName() }.toSet()) + } + + @Test + fun `objc forward declarations with definitions`() { + Assume.assumeTrue(HostManager.hostIsMac) + + fun Collection.assertHasOnlyNonForward(vararg names: String) { + this.forEach { + assertFalse(it.isForwardDeclaration, "${it.name} is a forward declaration") + } + assertEquals(names.toSet(), this.map { it.name }.toSet()) + } + + val dir = "ForwardDeclarations/objcWithDefinition" + val main = buildNativeIndex(dir, "main.def") + + main.index.objCClasses.assertHasOnlyNonForward("Class1", "Class2", "Class3", "Class4") + main.index.objCProtocols.assertHasOnlyNonForward("Protocol1", "Protocol2", "Protocol3", "Protocol4") + } +} diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/dependency.def b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/dependency.def new file mode 100644 index 00000000000..74f40e7e17c --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/dependency.def @@ -0,0 +1,3 @@ +headers = dependency.h +headerFilter = dependency.h +language = Objective-C diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/dependency.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/dependency.h new file mode 100644 index 00000000000..ddf24f7186b --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/dependency.h @@ -0,0 +1,7 @@ +@class DependencyClassUsed; +@class DependencyClassUnused; +@class DependencyAndMainClass; + +@protocol DependencyProtocolUsed; +@protocol DependencyProtocolUnused; +@protocol DependencyAndMainProtocol; diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/imported.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/imported.h new file mode 100644 index 00000000000..e3591ec61fa --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/imported.h @@ -0,0 +1,5 @@ +@class ImportedClassUsed; +@class ImportedClassUnused; + +@protocol ImportedProtocolUsed; +@protocol ImportedProtocolUnused; diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/included.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/included.h new file mode 100644 index 00000000000..2aeae2d5371 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/included.h @@ -0,0 +1,2 @@ +@class IncludedClassUsed, IncludedClassUnused; +@protocol IncludedProtocolUsed, IncludedProtocolUnused; diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/main.def b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/main.def new file mode 100644 index 00000000000..9d9fa1c69a9 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/main.def @@ -0,0 +1,3 @@ +headers = main.h +headerFilter = main.h included.h dependency.h +language = Objective-C diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/main.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/main.h new file mode 100644 index 00000000000..37e065d775f --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objc/main.h @@ -0,0 +1,17 @@ +#import "dependency.h" +#import "imported.h" +#import "included.h" + + +@class MainClassUsed; +@class MainClassUnused; +@class DependencyAndMainClass; + +@protocol MainProtocolUsed; +@protocol MainProtocolUnused; +@protocol DependencyAndMainProtocol; + +void useDependency(DependencyClassUsed*, id); +void useImported(ImportedClassUsed*, id); +void useIncluded(IncludedClassUsed*, id); +void useMain(MainClassUsed*, id); diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objcWithDefinition/included.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objcWithDefinition/included.h new file mode 100644 index 00000000000..534fdd22c4a --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objcWithDefinition/included.h @@ -0,0 +1,8 @@ +@class Class3; +@protocol Protocol3; + +@interface Class4 +@end + +@protocol Protocol4 +@end \ No newline at end of file diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objcWithDefinition/main.def b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objcWithDefinition/main.def new file mode 100644 index 00000000000..ce68e01b69a --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objcWithDefinition/main.def @@ -0,0 +1,2 @@ +headers = main.h +language = Objective-C \ No newline at end of file diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objcWithDefinition/main.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objcWithDefinition/main.h new file mode 100644 index 00000000000..f681e1416f1 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/objcWithDefinition/main.h @@ -0,0 +1,31 @@ +#import "included.h" + +@class Class1, Class2; +@class Class2; + +@interface Class1 +@end + +@interface Class2 +@end + +@class Class2; + +@protocol Protocol1; + +@protocol Protocol1 +@end + +@protocol Protocol2; + +@protocol Protocol2 +@end + +@interface Class3 +@end + +@protocol Protocol3 +@end + +@class Class4; +@protocol Protocol4; \ No newline at end of file diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/dependency.def b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/dependency.def new file mode 100644 index 00000000000..03a4bd64c67 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/dependency.def @@ -0,0 +1,2 @@ +headers = dependency.h +headerFilter = dependency.h diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/dependency.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/dependency.h new file mode 100644 index 00000000000..f825119c2b7 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/dependency.h @@ -0,0 +1,4 @@ +struct DependencyUsed; +struct DependencyUnused; + +struct DependencyAndMain; \ No newline at end of file diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/imported.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/imported.h new file mode 100644 index 00000000000..2aece87d383 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/imported.h @@ -0,0 +1,2 @@ +struct ImportedUsed; +struct ImportedUnused; diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/included.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/included.h new file mode 100644 index 00000000000..be1b2ffec2b --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/included.h @@ -0,0 +1,2 @@ +struct IncludedUsed; +struct IncludedUnused; diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/main.def b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/main.def new file mode 100644 index 00000000000..7365c799c49 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/main.def @@ -0,0 +1,2 @@ +headers = main.h +headerFilter = main.h included.h dependency.h diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/main.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/main.h new file mode 100644 index 00000000000..a96890c2bec --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/struct/main.h @@ -0,0 +1,12 @@ +#include "dependency.h" +#include "imported.h" +#include "included.h" + +struct MainUsed; +struct MainUnused; +struct DependencyAndMain; + +void useDependency(struct DependencyUsed*); +void useImported(struct ImportedUsed*); +void useIncluded(struct IncludedUsed*); +void useMain(struct MainUsed*); diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/structWithDefinition/included.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/structWithDefinition/included.h new file mode 100644 index 00000000000..20b5b617901 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/structWithDefinition/included.h @@ -0,0 +1,3 @@ +struct Struct3; + +struct Struct4 {}; \ No newline at end of file diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/structWithDefinition/main.def b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/structWithDefinition/main.def new file mode 100644 index 00000000000..e61a028fab0 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/structWithDefinition/main.def @@ -0,0 +1 @@ +headers = main.h \ No newline at end of file diff --git a/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/structWithDefinition/main.h b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/structWithDefinition/main.h new file mode 100644 index 00000000000..d1e979c06b8 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/test/resources/ForwardDeclarations/structWithDefinition/main.h @@ -0,0 +1,12 @@ +#include "included.h" + +struct Struct1; +struct Struct1 {}; + +struct Struct2; +struct Struct2 {}; +struct Struct2; + +struct Struct3 {}; + +struct Struct4; \ No newline at end of file diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirNativeCodegenBoxTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirNativeCodegenBoxTestGenerated.java index f82689f96ce..8f70edd3c45 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirNativeCodegenBoxTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirNativeCodegenBoxTestGenerated.java @@ -4874,6 +4874,12 @@ public class FirNativeCodegenBoxTestGenerated extends AbstractNativeCodegenBoxTe runTest("compiler/testData/codegen/box/cinterop/kt63049.kt"); } + @Test + @TestMetadata("kt64105.kt") + public void testKt64105() throws Exception { + runTest("compiler/testData/codegen/box/cinterop/kt64105.kt"); + } + @Test @TestMetadata("leakMemoryWithRunningThreadUnchecked.kt") public void testLeakMemoryWithRunningThreadUnchecked() throws Exception { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirNativeCodegenBoxTestNoPLGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirNativeCodegenBoxTestNoPLGenerated.java index bf9f6cfc1d6..2bb44c4f644 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirNativeCodegenBoxTestNoPLGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/FirNativeCodegenBoxTestNoPLGenerated.java @@ -4984,6 +4984,12 @@ public class FirNativeCodegenBoxTestNoPLGenerated extends AbstractNativeCodegenB runTest("compiler/testData/codegen/box/cinterop/kt63049.kt"); } + @Test + @TestMetadata("kt64105.kt") + public void testKt64105() throws Exception { + runTest("compiler/testData/codegen/box/cinterop/kt64105.kt"); + } + @Test @TestMetadata("leakMemoryWithRunningThreadUnchecked.kt") public void testLeakMemoryWithRunningThreadUnchecked() throws Exception { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/NativeCodegenBoxTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/NativeCodegenBoxTestGenerated.java index 45b2f73e8ee..4ecab30fa27 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/NativeCodegenBoxTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/NativeCodegenBoxTestGenerated.java @@ -4764,6 +4764,12 @@ public class NativeCodegenBoxTestGenerated extends AbstractNativeCodegenBoxTest runTest("compiler/testData/codegen/box/cinterop/kt63049.kt"); } + @Test + @TestMetadata("kt64105.kt") + public void testKt64105() throws Exception { + runTest("compiler/testData/codegen/box/cinterop/kt64105.kt"); + } + @Test @TestMetadata("leakMemoryWithRunningThreadUnchecked.kt") public void testLeakMemoryWithRunningThreadUnchecked() throws Exception { diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/NativeCodegenBoxTestNoPLGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/NativeCodegenBoxTestNoPLGenerated.java index 79c83cf3921..6f1f36c7d48 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/NativeCodegenBoxTestNoPLGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/NativeCodegenBoxTestNoPLGenerated.java @@ -4875,6 +4875,12 @@ public class NativeCodegenBoxTestNoPLGenerated extends AbstractNativeCodegenBoxT runTest("compiler/testData/codegen/box/cinterop/kt63049.kt"); } + @Test + @TestMetadata("kt64105.kt") + public void testKt64105() throws Exception { + runTest("compiler/testData/codegen/box/cinterop/kt64105.kt"); + } + @Test @TestMetadata("leakMemoryWithRunningThreadUnchecked.kt") public void testLeakMemoryWithRunningThreadUnchecked() throws Exception {