From 32da1b70f51be83d4e6821b1eb56fe9ce5ce6664 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Thu, 1 Feb 2024 14:29:58 +0100 Subject: [PATCH] [ObjCExport] Implement initial sorting and ordering for ObjC translation ^KT-65237 --- .../kotlin/objcexport/translateToObjCClass.kt | 6 +- .../objcexport/translateToObjCHeader.kt | 11 +-- .../objcexport/translateToObjCProtocol.kt | 4 +- ...nslateToObjCTopLevelInterfaceFileFacade.kt | 1 + .../kotlin/objcexport/translationOrder.kt | 60 +++++++++++++++++ .../InlineSourceCodeAnalysisExtension.kt | 28 +++++++- .../testUtils/createAnalysisSession.kt | 8 +-- .../objcexport/tests/TranslationOrderTest.kt | 47 +++++++++++++ .../tests/ObjCExportHeaderGeneratorTest.kt | 14 +++- .../!classWithManyMembers.h | 40 +++++++++++ .../headers/classWithManyMembers/Foo.kt | 22 ++++++ .../!manyClassesAndInterfaces.h | 67 +++++++++++++++++++ .../headers/manyClassesAndInterfaces/A.kt | 6 ++ .../headers/manyClassesAndInterfaces/B.kt | 6 ++ 14 files changed, 307 insertions(+), 13 deletions(-) create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt create mode 100644 native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/TranslationOrderTest.kt create mode 100644 native/objcexport-header-generator/testData/headers/classWithManyMembers/!classWithManyMembers.h create mode 100644 native/objcexport-header-generator/testData/headers/classWithManyMembers/Foo.kt create mode 100644 native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/!manyClassesAndInterfaces.h create mode 100644 native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/A.kt create mode 100644 native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/B.kt diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt index 1e5d8e4899e..84b6c94239f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt @@ -27,7 +27,11 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? { val comment: ObjCComment? = annotationsList.translateToObjCComment() val origin: ObjCExportStubOrigin = getObjCExportStubOrigin() val superProtocols: List = superProtocols() - val members: List = getAllMembers().flatMap { it.translateToObjCExportStubs() } + + val members: List = getAllMembers() + .sortedWith(StableSymbolOrder) + .flatMap { it.translateToObjCExportStubs() } + val categoryName: String? = null val generics: List = emptyList() val superClassGenerics: List = emptyList() diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt index 3e25a366063..d3fb320e616 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt @@ -17,7 +17,11 @@ import org.jetbrains.kotlin.psi.KtFile context(KtAnalysisSession, KtObjCExportSession) fun translateToObjCHeader(files: List): ObjCHeader { - val declarations = files.flatMap { ktFile -> ktFile.translateToObjCExportStubs() }.toMutableList() + val declarations = files + .sortedWith(StableFileOrder) + .flatMap { file -> file.getFileSymbol().translateToObjCExportStubs() } + .toMutableList() + val classForwardDeclarations = getClassForwardDeclarations(declarations).toMutableSet() val protocolForwardDeclarations = getProtocolForwardDeclarations(declarations) @@ -66,14 +70,11 @@ private fun getProtocolForwardDeclarations(declarations: List) = .flatMap { it.superProtocols } .toSet() -context(KtAnalysisSession, KtObjCExportSession) -fun KtFile.translateToObjCExportStubs(): List { - return this.getFileSymbol().translateToObjCExportStubs() -} context(KtAnalysisSession, KtObjCExportSession) fun KtFileSymbol.translateToObjCExportStubs(): List { return listOfNotNull(translateToObjCTopLevelInterfaceFileFacade()) + getFileScope().getClassifierSymbols() + .sortedWith(StableClassifierOrder) .flatMap { classifierSymbol -> classifierSymbol.translateToObjCExportStubs() } } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt index 1b01430f4ea..94dc1a0c736 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt @@ -26,7 +26,9 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? { // TODO: Check error type! val name = getObjCClassOrProtocolName() - val members = getDeclaredMembers().flatMap { it.translateToObjCExportStubs() } + val members = getDeclaredMembers() + .sortedWith(StableSymbolOrder) + .flatMap { it.translateToObjCExportStubs() } val comment: ObjCComment? = annotationsList.translateToObjCComment() diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt index dab932617c6..043b8a71454 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt @@ -44,6 +44,7 @@ import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrPr context(KtAnalysisSession, KtObjCExportSession) fun KtFileSymbol.translateToObjCTopLevelInterfaceFileFacade(): ObjCInterface? { val topLevelCallableStubs = getFileScope().getCallableSymbols() + .sortedWith(StableCallableOrder) .flatMap { callableSymbol -> callableSymbol.translateToObjCExportStubs() } .toList() /* If there are no top level functions or properties, we do not need to export a file facade */ diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt new file mode 100644 index 00000000000..f023dd87684 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt @@ -0,0 +1,60 @@ +/* + * 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.objcexport + +import org.jetbrains.kotlin.analysis.api.symbols.* +import org.jetbrains.kotlin.psi.KtFile + +internal val StableFileOrder: Comparator + get() = compareBy { file -> file.packageFqName.asString() } + .thenComparing { file -> file.name } + +internal val StablePropertyOrder: Comparator = compareBy { it.name } + +internal val StableFunctionOrder: Comparator + get() = compareBy( + { it.isConstructor }, + { it.name }, + { it.valueParameters.size }, + // TODO NOW! { KonanManglerDesc.run { it.signatureString(false) } } + ) + +internal val StableClassifierOrder: Comparator = + compareBy { classifier -> + if (classifier !is KtClassOrObjectSymbol) return@compareBy 0 + else 2 + }.thenComparing { classifier -> + if (classifier is KtClassLikeSymbol) classifier.classIdIfNonLocal?.toString().orEmpty() + else "" + } + +internal val StableCallableOrder: Comparator = compareBy { + when (it) { + is KtConstructorSymbol -> 0 + is KtFunctionSymbol -> 1 + is KtPropertySymbol -> 2 + else -> 3 + } +} + .thenComparing(StablePropertyOrder) + .thenComparing(StableFunctionOrder) + +internal val StableSymbolOrder: Comparator = compareBy { symbol -> + when (symbol) { + is KtFileSymbol -> 0 + is KtClassifierSymbol -> 1 + is KtCallableSymbol -> 2 + else -> Int.MAX_VALUE + } +} + .thenComparing(StableClassifierOrder) + .thenComparing(StableCallableOrder) + +private inline fun Comparator.thenComparing(comparator: Comparator): Comparator where R : T { + return thenComparing { a, b -> + if (a is R && b is R) comparator.compare(a, b) else 0 + } +} diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/InlineSourceCodeAnalysisExtension.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/InlineSourceCodeAnalysisExtension.kt index d8e2cc92d8b..f479e4204f2 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/InlineSourceCodeAnalysisExtension.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/InlineSourceCodeAnalysisExtension.kt @@ -36,7 +36,16 @@ import java.nio.file.Files * ``` */ interface InlineSourceCodeAnalysis { + fun createKtFile(@Language("kotlin") sourceCode: String): KtFile + + fun createKtFiles( + builder: KtModuleBuilder.() -> Unit, + ): Map + + interface KtModuleBuilder { + fun sourceFile(fileName: String, @Language("kotlin") sourceCode: String) + } } /** @@ -68,8 +77,25 @@ class InlineSourceCodeAnalysisExtension : ParameterResolver, AfterEachCallback { */ private class InlineSourceCodeAnalysisImpl(private val tempDir: File) : InlineSourceCodeAnalysis { override fun createKtFile(@Language("kotlin") sourceCode: String): KtFile { - return createStandaloneAnalysisApiSession(tempDir, listOf(sourceCode)) + return createStandaloneAnalysisApiSession(tempDir, mapOf("TestSources.kt" to sourceCode)) .modulesWithFiles.entries.single() .value.single() as KtFile } + + override fun createKtFiles(builder: InlineSourceCodeAnalysis.KtModuleBuilder.() -> Unit): Map { + val sources = KtModuleBuilderImpl().also(builder).sources.toMap() + return createStandaloneAnalysisApiSession(tempDir, sources) + .modulesWithFiles.entries.single() + .value.map { it as KtFile } + .associateBy { it.name } + } + + private class KtModuleBuilderImpl : InlineSourceCodeAnalysis.KtModuleBuilder { + val sources = mutableMapOf() + + override fun sourceFile(fileName: String, sourceCode: String) { + check(fileName !in sourceCode) + sources[fileName] = sourceCode + } + } } diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/createAnalysisSession.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/createAnalysisSession.kt index 05ebb2fc6e9..8ec77aa46ae 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/createAnalysisSession.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/createAnalysisSession.kt @@ -23,14 +23,14 @@ import kotlin.io.path.Path */ fun createStandaloneAnalysisApiSession( tempDir: File, - kotlinSources: List, + kotlinSources: Map, ): StandaloneAnalysisAPISession { val testModuleRoot = tempDir.resolve("testModule") testModuleRoot.mkdirs() - kotlinSources.forEachIndexed { index, kotlinSource -> - testModuleRoot.resolve("TestSources$index.kt").apply { - writeText(kotlinSource) + kotlinSources.forEach { (fileName, sourceCode) -> + testModuleRoot.resolve(fileName).apply { + writeText(sourceCode) } } return createStandaloneAnalysisApiSession(listOf(testModuleRoot)) diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/TranslationOrderTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/TranslationOrderTest.kt new file mode 100644 index 00000000000..a1c6ed337a2 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/TranslationOrderTest.kt @@ -0,0 +1,47 @@ +/* + * 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.objcexport.tests + +import org.jetbrains.kotlin.objcexport.StableFileOrder +import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class TranslationOrderTest( + private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis, +) { + + @Test + fun `test - StableFileOrder - sort by packageName`() { + val files = inlineSourceCodeAnalysis.createKtFiles { + sourceFile("A.kt", "package com.a") + sourceFile("B.kt", "package com.b") + sourceFile("C.kt", "package com.c") + } + + val unsorted = listOf(files.getValue("C.kt"), files.getValue("B.kt"), files.getValue("A.kt")) + val sorted = listOf(files.getValue("A.kt"), files.getValue("B.kt"), files.getValue("C.kt")) + + assertNotEquals(sorted, unsorted) + assertEquals(sorted, unsorted.sortedWith(StableFileOrder)) + } + + @Test + fun `test - StableFileOrder - sort by packageName then FileName`() { + val files = inlineSourceCodeAnalysis.createKtFiles { + sourceFile("A.kt", "package com.a") + sourceFile("B1.kt", "package com.b") + sourceFile("B2.kt", "package com.b") + } + + val unsorted = listOf(files.getValue("B2.kt"), files.getValue("A.kt"), files.getValue("B1.kt")) + val sorted = listOf(files.getValue("A.kt"), files.getValue("B1.kt"), files.getValue("B2.kt")) + + assertNotEquals(sorted, unsorted) + assertEquals(sorted, unsorted.sortedWith(StableFileOrder)) + } +} \ No newline at end of file diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt index b7d9bc52f87..403722dfc8d 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt @@ -83,11 +83,13 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) { } @Test + @TodoAnalysisApi // Early translate of forward declarations fun `test - classImplementingInterface`() { doTest(headersTestDataDir.resolve("classImplementingInterface")) } @Test + @TodoAnalysisApi fun `test - interfaceImplementingInterface`() { doTest(headersTestDataDir.resolve("interfaceImplementingInterface")) } @@ -176,16 +178,26 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) { } @Test + @TodoAnalysisApi // Early translate of forward declarations fun `test - classWithUnresolvedSuperTypeGenerics`() { doTest(headersTestDataDir.resolve("classWithUnresolvedSuperTypeGenerics")) } @Test - @TodoAnalysisApi fun `test - topLevelFunctionWithNumberReturn`() { doTest(headersTestDataDir.resolve("topLevelFunctionWithNumberReturn")) } + @Test + fun `test - classWithManyMembers`() { + doTest(headersTestDataDir.resolve("classWithManyMembers")) + } + + @Test + fun `test - manyClassesAndInterfaces`() { + doTest(headersTestDataDir.resolve("manyClassesAndInterfaces")) + } + private fun doTest(root: File, configuration: Configuration = Configuration()) { if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory") val generatedHeaders = generator.generateHeaders(root, configuration).toString() diff --git a/native/objcexport-header-generator/testData/headers/classWithManyMembers/!classWithManyMembers.h b/native/objcexport-header-generator/testData/headers/classWithManyMembers/!classWithManyMembers.h new file mode 100644 index 00000000000..b462f16bf0d --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/classWithManyMembers/!classWithManyMembers.h @@ -0,0 +1,40 @@ +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wincompatible-property-type" +#pragma clang diagnostic ignored "-Wnullability" + +#pragma push_macro("_Nullable_result") +#if !__has_feature(nullability_nullable_result) +#undef _Nullable_result +#define _Nullable_result _Nullable +#endif + +__attribute__((objc_subclassing_restricted)) +@interface Foo : Base +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +- (void)a __attribute__((swift_name("a()"))); +- (void)aP0:(int32_t)p0 __attribute__((swift_name("a(p0:)"))); +- (void)aP0:(int32_t)p0 p1:(int32_t)p1 __attribute__((swift_name("a(p0:p1:)"))); +- (void)b __attribute__((swift_name("b()"))); +- (void)bP0:(int32_t)p0 __attribute__((swift_name("b(p0:)"))); +- (void)bP0:(int32_t)p0 p1:(int32_t)p1 __attribute__((swift_name("b(p0:p1:)"))); +- (void)c __attribute__((swift_name("c()"))); +- (int32_t)d __attribute__((swift_name("d()"))); +@property (readonly) int32_t pA __attribute__((swift_name("pA"))); +@property (readonly) int32_t pB __attribute__((swift_name("pB"))); +@property (readonly) int32_t pC __attribute__((swift_name("pC"))); +@end + +#pragma pop_macro("_Nullable_result") +#pragma clang diagnostic pop +NS_ASSUME_NONNULL_END diff --git a/native/objcexport-header-generator/testData/headers/classWithManyMembers/Foo.kt b/native/objcexport-header-generator/testData/headers/classWithManyMembers/Foo.kt new file mode 100644 index 00000000000..a6858f8dd81 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/classWithManyMembers/Foo.kt @@ -0,0 +1,22 @@ +class Foo { + /* Number of parameters increases, relevant for ordering */ + fun a() = Unit + fun a(p0: Int) = Unit + fun a(p0: Int, p1: Int) = Unit + + /* Should be ordered a, b, c, but is explicitly placed as a, c, b in source code */ + fun c() = Unit + + /* Number of parameters decreases, should be orderd in increasing number */ + fun b(p0: Int, p1: Int) = Unit + fun b(p0: Int) = Unit + fun b() = Unit + + /* Short get sorted in alphabetical order */ + val pA = 42 + val pC = 42 + val pB = 42 + + /* Functions should be prioritised over properties */ + fun d() = 42 +} diff --git a/native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/!manyClassesAndInterfaces.h b/native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/!manyClassesAndInterfaces.h new file mode 100644 index 00000000000..3060fe83b74 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/!manyClassesAndInterfaces.h @@ -0,0 +1,67 @@ +#import +#import +#import +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#pragma clang diagnostic ignored "-Wincompatible-property-type" +#pragma clang diagnostic ignored "-Wnullability" + +#pragma push_macro("_Nullable_result") +#if !__has_feature(nullability_nullable_result) +#undef _Nullable_result +#define _Nullable_result _Nullable +#endif + +@protocol A0 +@required +@end + +__attribute__((objc_subclassing_restricted)) +@interface A1 : Base +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +@end + +__attribute__((objc_subclassing_restricted)) +@interface A2 : Base +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +@end + +__attribute__((objc_subclassing_restricted)) +@interface A3 : Base +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +@end + +__attribute__((objc_subclassing_restricted)) +@interface B1 : Base +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +@end + +__attribute__((objc_subclassing_restricted)) +@interface B2 : Base +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +@end + +__attribute__((objc_subclassing_restricted)) +@interface B3 : Base +- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer)); ++ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead"))); +@end + +@protocol B4 +@required +@end + +#pragma pop_macro("_Nullable_result") +#pragma clang diagnostic pop +NS_ASSUME_NONNULL_END diff --git a/native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/A.kt b/native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/A.kt new file mode 100644 index 00000000000..a76e86b8bb7 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/A.kt @@ -0,0 +1,6 @@ +package com.a + +class A2 +class A1 +interface A0 +class A3 diff --git a/native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/B.kt b/native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/B.kt new file mode 100644 index 00000000000..47da51f8618 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/manyClassesAndInterfaces/B.kt @@ -0,0 +1,6 @@ +package com.b + +class B2 +class B1 +class B3 +interface B4 \ No newline at end of file