diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt index 9f5beaf0b16..2ad7454f251 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt @@ -33,6 +33,8 @@ object BinaryOptions : BinaryOptionRegistry() { val objcExportIgnoreInterfaceMethodCollisions by booleanOption() + val objcExportReportNameCollisions by booleanOption() + val gc by option(shortcut = { it.shortcut }) val gcSchedulerType by option(hideValue = { it.deprecatedWithReplacement != null }) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanCompilerFrontendServices.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanCompilerFrontendServices.kt index 7c98a06a223..08d0eec6c9c 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanCompilerFrontendServices.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanCompilerFrontendServices.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportLazyImpl import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportProblemCollector import org.jetbrains.kotlin.backend.konan.objcexport.dumpObjCHeader import org.jetbrains.kotlin.container.* +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver @@ -23,7 +24,7 @@ internal fun StorageComponentContainer.initContainer(config: KonanConfig) { useImpl() useInstance(object : ObjCExportProblemCollector { override fun reportWarning(text: String) {} - override fun reportWarning(method: FunctionDescriptor, text: String) {} + override fun reportWarning(declaration: DeclarationDescriptor, text: String) {} override fun reportException(throwable: Throwable) = throw throwable }) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt index 00730c18a9b..4356374ef31 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt @@ -50,17 +50,23 @@ internal fun produceObjCExportInterface( val objcGenerics = config.configuration.getBoolean(KonanConfigKeys.OBJC_GENERICS) val disableSwiftMemberNameMangling = config.configuration.getBoolean(BinaryOptions.objcExportDisableSwiftMemberNameMangling) val ignoreInterfaceMethodCollisions = config.configuration.getBoolean(BinaryOptions.objcExportIgnoreInterfaceMethodCollisions) + val reportNameCollisions = config.configuration.getBoolean(BinaryOptions.objcExportReportNameCollisions) + + val problemCollector = ObjCExportHeaderGeneratorImpl.ProblemCollector(context) + val namer = ObjCExportNamerImpl( moduleDescriptors.toSet(), moduleDescriptor.builtIns, mapper, + problemCollector, topLevelNamePrefix, local = false, objcGenerics = objcGenerics, disableSwiftMemberNameMangling = disableSwiftMemberNameMangling, ignoreInterfaceMethodCollisions = ignoreInterfaceMethodCollisions, + reportNameCollisions = reportNameCollisions, ) - val headerGenerator = ObjCExportHeaderGeneratorImpl(context, moduleDescriptors, mapper, namer, objcGenerics) + val headerGenerator = ObjCExportHeaderGeneratorImpl(context, moduleDescriptors, mapper, namer, problemCollector, objcGenerics) headerGenerator.translateModule() return headerGenerator.buildInterface() } @@ -128,6 +134,7 @@ internal class ObjCExport( setOf(moduleDescriptor), moduleDescriptor.builtIns, mapper, + ObjCExportProblemCollector.SILENT, topLevelNamePrefix, local = false ) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 4ecd9e43da4..5a279de145a 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -42,12 +42,12 @@ interface ObjCExportTranslator { interface ObjCExportProblemCollector { fun reportWarning(text: String) - fun reportWarning(method: FunctionDescriptor, text: String) + fun reportWarning(declaration: DeclarationDescriptor, text: String) fun reportException(throwable: Throwable) object SILENT : ObjCExportProblemCollector { override fun reportWarning(text: String) {} - override fun reportWarning(method: FunctionDescriptor, text: String) {} + override fun reportWarning(declaration: DeclarationDescriptor, text: String) {} override fun reportException(throwable: Throwable) {} } } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt index 0528ceb10c7..60a0cefcf44 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.konan.driver.PhaseContext import org.jetbrains.kotlin.backend.konan.reportCompilationWarning import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageUtil +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -21,20 +22,21 @@ internal class ObjCExportHeaderGeneratorImpl( moduleDescriptors: List, mapper: ObjCExportMapper, namer: ObjCExportNamer, + problemCollector: ObjCExportProblemCollector, objcGenerics: Boolean -) : ObjCExportHeaderGenerator(moduleDescriptors, mapper, namer, objcGenerics, ProblemCollector(context)) { +) : ObjCExportHeaderGenerator(moduleDescriptors, mapper, namer, objcGenerics, problemCollector) { override val shouldExportKDoc = context.shouldExportKDoc() - private class ProblemCollector(val context: PhaseContext) : ObjCExportProblemCollector { + internal class ProblemCollector(val context: PhaseContext) : ObjCExportProblemCollector { override fun reportWarning(text: String) { context.reportCompilationWarning(text) } - override fun reportWarning(method: FunctionDescriptor, text: String) { - val psi = (method as? DeclarationDescriptorWithSource)?.source?.getPsi() + override fun reportWarning(declaration: DeclarationDescriptor, text: String) { + val psi = (declaration as? DeclarationDescriptorWithSource)?.source?.getPsi() ?: return reportWarning( - "$text\n (at ${DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(method)})" + "$text\n (at ${DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(declaration)})" ) val location = MessageUtil.psiElementToMessageLocation(psi) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt index 219ede5788e..bc1ec025117 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportLazy.kt @@ -96,7 +96,7 @@ internal class ObjCExportLazyImpl( private val mapper = ObjCExportMapper(deprecationResolver, local = true, configuration.unitSuspendFunctionExport) - private val namer = ObjCExportNamerImpl(namerConfiguration, builtIns, mapper, local = true) + private val namer = ObjCExportNamerImpl(namerConfiguration, builtIns, mapper, problemCollector, local = true) private val translator: ObjCExportTranslator = ObjCExportTranslatorImpl( null, diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index 7f964495aed..2d5ab74e8e7 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -58,6 +58,9 @@ interface ObjCExportNamer { get() = false val ignoreInterfaceMethodCollisions: Boolean get() = false + + val reportNameCollisions: Boolean + get() = false } val topLevelNamePrefix: String @@ -103,6 +106,7 @@ fun createNamer( (exportedDependencies + moduleDescriptor).toSet(), moduleDescriptor.builtIns, ObjCExportMapper(local = true, unitSuspendFunctionExport = UnitSuspendFunctionObjCExport.DEFAULT), + ObjCExportProblemCollector.SILENT, topLevelNamePrefix, local = true ) @@ -273,6 +277,7 @@ internal class ObjCExportNamerImpl( private val configuration: ObjCExportNamer.Configuration, builtIns: KotlinBuiltIns, private val mapper: ObjCExportMapper, + private val problemCollector: ObjCExportProblemCollector, private val local: Boolean ) : ObjCExportNamer { @@ -280,11 +285,13 @@ internal class ObjCExportNamerImpl( moduleDescriptors: Set, builtIns: KotlinBuiltIns, mapper: ObjCExportMapper, + problemCollector: ObjCExportProblemCollector, topLevelNamePrefix: String, local: Boolean, objcGenerics: Boolean = false, disableSwiftMemberNameMangling: Boolean = false, ignoreInterfaceMethodCollisions: Boolean = false, + reportNameCollisions: Boolean = false, ) : this( object : ObjCExportNamer.Configuration { override val topLevelNamePrefix: String @@ -301,9 +308,13 @@ internal class ObjCExportNamerImpl( override val ignoreInterfaceMethodCollisions: Boolean get() = ignoreInterfaceMethodCollisions + + override val reportNameCollisions: Boolean + get() = reportNameCollisions }, builtIns, mapper, + problemCollector, local ) @@ -826,10 +837,21 @@ internal class ObjCExportNamerImpl( inline fun getOrPut(element: T, nameCandidates: () -> Sequence): N { getIfAssigned(element)?.let { return it } + var reportedCollision = false + nameCandidates().forEach { if (tryAssign(element, it)) { return it } + + if (configuration.reportNameCollisions && !reportedCollision) { + if (element is DeclarationDescriptor) { + problemCollector.reportWarning(element, "name is mangled when generating Objective-C header") + } else { + problemCollector.reportWarning("name \"$it\" is mangled when generating Objective-C header") + } + reportedCollision = true + } } error("name candidates run out") diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjcExportHeaderGeneratorMobile.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjcExportHeaderGeneratorMobile.kt index af0f9cfee8d..2034274d65b 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjcExportHeaderGeneratorMobile.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjcExportHeaderGeneratorMobile.kt @@ -26,7 +26,7 @@ class ObjcExportHeaderGeneratorMobile internal constructor( restrictToLocalModules: Boolean = false): ObjCExportHeaderGenerator { val mapper = ObjCExportMapper(deprecationResolver, local, configuration.unitSuspendFunctionExport) val namerConfiguration = createNamerConfiguration(configuration) - val namer = ObjCExportNamerImpl(namerConfiguration, builtIns, mapper, local) + val namer = ObjCExportNamerImpl(namerConfiguration, builtIns, mapper, problemCollector, local) return ObjcExportHeaderGeneratorMobile( moduleDescriptors, diff --git a/native/native.tests/testData/compilerOutput/ObjCExportDiagnostics/lib1.kt b/native/native.tests/testData/compilerOutput/ObjCExportDiagnostics/lib1.kt new file mode 100644 index 00000000000..1b90342cb9f --- /dev/null +++ b/native/native.tests/testData/compilerOutput/ObjCExportDiagnostics/lib1.kt @@ -0,0 +1,38 @@ +@file:OptIn(kotlin.experimental.ExperimentalObjCName::class) + +package pkg.lib1 + +interface InterfaceClash +class ClassClash + +interface InterfaceClash2 +class ClassClash2 + +@ObjCName("InterfaceClash2") +interface InterfaceClashWithObjCName +@ObjCName("ClassClash2") +class ClassClashWithObjCName + +class Lib1Kt + +enum class E { + ONE, + one, + @ObjCName("one") + TWO, + ENTRIES, + @ObjCName("values") VALUES1; +} + +interface I1 { + val prop: Int + fun method() +} + +interface I2 { + val prop: String + fun method() +} + +fun topLevel(arg: Int) {} +fun topLevel(arg: Long) {} \ No newline at end of file diff --git a/native/native.tests/testData/compilerOutput/ObjCExportDiagnostics/lib2.kt b/native/native.tests/testData/compilerOutput/ObjCExportDiagnostics/lib2.kt new file mode 100644 index 00000000000..1c0845e5beb --- /dev/null +++ b/native/native.tests/testData/compilerOutput/ObjCExportDiagnostics/lib2.kt @@ -0,0 +1,4 @@ +package pkg.lib2 + +interface InterfaceClash +class ClassClash diff --git a/native/native.tests/testData/compilerOutput/ObjCExportDiagnostics/output.txt b/native/native.tests/testData/compilerOutput/ObjCExportDiagnostics/output.txt new file mode 100644 index 00000000000..84ecffa4cd8 --- /dev/null +++ b/native/native.tests/testData/compilerOutput/ObjCExportDiagnostics/output.txt @@ -0,0 +1,25 @@ +warning: name is mangled when generating Objective-C header + (at class ClassClashWithObjCName defined in pkg.lib1) +warning: name is mangled when generating Objective-C header + (at enum entry one defined in pkg.lib1.E) +warning: name is mangled when generating Objective-C header + (at enum entry TWO defined in pkg.lib1.E) +warning: name is mangled when generating Objective-C header + (at fun values(): Array defined in pkg.lib1.E) +warning: name is mangled when generating Objective-C header + (at val entries: EnumEntries defined in pkg.lib1.E) +warning: name is mangled when generating Objective-C header + (at val prop: String defined in pkg.lib1.I2) +warning: name is mangled when generating Objective-C header + (at fun ``(): String defined in pkg.lib1.I2) +warning: name is mangled when generating Objective-C header + (at interface InterfaceClashWithObjCName defined in pkg.lib1) +warning: name is mangled when generating Objective-C header + (at class ClassClash defined in pkg.lib2) +warning: name is mangled when generating Objective-C header + (at interface InterfaceClash defined in pkg.lib2) +warning: name "TOCEDLib1Kt" is mangled when generating Objective-C header +warning: name "Lib1Kt" is mangled when generating Objective-C header +warning: name is mangled when generating Objective-C header + (at fun topLevel(arg: Long): Unit defined in pkg.lib1) +OK diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/CompilerOutputTest.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/CompilerOutputTest.kt index f87d4725e22..2cbdf4810b2 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/CompilerOutputTest.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/CompilerOutputTest.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.konan.test.blackbox.support.EnforcedProperty import org.jetbrains.kotlin.konan.test.blackbox.support.LoggedData import org.jetbrains.kotlin.konan.test.blackbox.support.TestCompilerArgs import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.LibraryCompilation +import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.ObjCFrameworkCompilation import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.TestCompilationArtifact import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.TestCompilationResult import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.TestCompilationResult.Companion.assertSuccess @@ -21,6 +22,7 @@ import org.jetbrains.kotlin.konan.test.blackbox.support.group.FirPipeline import org.jetbrains.kotlin.konan.test.blackbox.support.settings.PipelineType import org.jetbrains.kotlin.konan.test.blackbox.support.settings.Settings import org.jetbrains.kotlin.test.KotlinTestUtils +import org.junit.jupiter.api.Assumptions import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import java.io.File @@ -79,11 +81,39 @@ abstract class CompilerOutputTestBase : AbstractNativeSimpleTest() { KotlinTestUtils.assertEqualsToFile(goldenData, compilationResult.toOutput()) } + @Test + fun testObjCExportDiagnostics() { + Assumptions.assumeTrue(targets.hostTarget.family.isAppleFamily) + + val rootDir = File("native/native.tests/testData/compilerOutput/ObjCExportDiagnostics") + val settings = testRunSettings + val lib1 = compileLibrary(settings, rootDir.resolve("lib1.kt")).assertSuccess().resultingArtifact + val lib2 = compileLibrary(settings, rootDir.resolve("lib2.kt")).assertSuccess().resultingArtifact + + val freeCompilerArgs = TestCompilerArgs( + "-Xinclude=${lib1.path}", + "-Xinclude=${lib2.path}", + "-Xbinary=objcExportReportNameCollisions=true" + ) + val expectedArtifact = TestCompilationArtifact.ObjCFramework(buildDir, "testObjCExportDiagnostics") + + val compilationResult = ObjCFrameworkCompilation( + settings, + freeCompilerArgs, + sourceModules = emptyList(), + dependencies = emptyList(), + expectedArtifact + ).result + val goldenData = rootDir.resolve("output.txt") + + KotlinTestUtils.assertEqualsToFile(goldenData, compilationResult.toOutput()) + } + private fun compileLibrary( settings: Settings, source: File, - freeCompilerArgs: List, - dependencies: List + freeCompilerArgs: List = emptyList(), + dependencies: List = emptyList(), ): TestCompilationResult { val testCase = generateTestCaseWithSingleModule(source, TestCompilerArgs(freeCompilerArgs)) val compilation = LibraryCompilation(