diff --git a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/ModuleSupport.kt b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/ModuleSupport.kt index 44ae1fb5cc6..e94e3cb5754 100644 --- a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/ModuleSupport.kt +++ b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/ModuleSupport.kt @@ -1,7 +1,7 @@ package org.jetbrains.kotlin.native.interop.indexer import clang.* -import kotlinx.cinterop.* +import kotlinx.cinterop.readValue import java.nio.file.Files data class ModulesInfo(val topLevelHeaders: List, val ownHeaders: Set, val modules: List) @@ -9,23 +9,29 @@ data class ModulesInfo(val topLevelHeaders: List, val ownHeaders: S fun getModulesInfo(compilation: Compilation, modules: List): ModulesInfo { if (modules.isEmpty()) return ModulesInfo(emptyList(), emptySet(), emptyList()) + val areModulesEnabled = compilation.compilerArgs.contains("-fmodules") withIndex(excludeDeclarationsFromPCH = false) { index -> ModularCompilation(compilation).use { val modulesASTFiles = getModulesASTFiles(index, it, modules) - return buildModulesInfo(index, modules, modulesASTFiles) + return buildModulesInfo(index, modules, modulesASTFiles, areModulesEnabled) } } } data class IncludeInfo(val headerPath: String, val moduleName: String?) -private fun buildModulesInfo(index: CXIndex, modules: List, modulesASTFiles: List): ModulesInfo { +private fun buildModulesInfo( + index: CXIndex, + modules: List, + modulesASTFiles: List, + areModulesEnabled: Boolean +): ModulesInfo { val ownHeaders = mutableSetOf() val topLevelHeaders = linkedSetOf() modulesASTFiles.forEach { val moduleTranslationUnit = clang_createTranslationUnit(index, it)!! try { - val modulesHeaders = getModulesHeaders(index, moduleTranslationUnit, modules.toSet(), topLevelHeaders) + val modulesHeaders = getModulesHeaders(index, moduleTranslationUnit, modules.toSet(), topLevelHeaders, areModulesEnabled) modulesHeaders.mapTo(ownHeaders) { it.canonicalPath } } finally { clang_disposeTranslationUnit(moduleTranslationUnit) @@ -35,7 +41,7 @@ private fun buildModulesInfo(index: CXIndex, modules: List, modulesASTFi return ModulesInfo(topLevelHeaders.toList(), ownHeaders, modules) } -internal open class ModularCompilation(compilation: Compilation): Compilation by compilation, Disposable { +internal open class ModularCompilation(compilation: Compilation) : Compilation by compilation, Disposable { companion object { private const val moduleCacheFlag = "-fmodules-cache-path=" @@ -91,12 +97,28 @@ private fun getModulesHeaders( index: CXIndex, translationUnit: CXTranslationUnit, modules: Set, - topLevelHeaders: LinkedHashSet + topLevelHeaders: LinkedHashSet, + areModulesEnabled: Boolean ): Set { val nonModularIncludes = mutableMapOf>() val result = mutableSetOf() + val errors = mutableListOf() indexTranslationUnit(index, translationUnit, 0, object : Indexer { + override fun importedASTFile(info: CXIdxImportedASTFileInfo) { + val isModuleImport = info.isImplicit == 0 + if (isModuleImport && !areModulesEnabled) { + val name = clang_Module_getFullName(info.module).convertAndDispose() + val headerPath = clang_indexLoc_getCXSourceLocation(info.loc.readValue()).getContainingFile()?.canonicalPath + val message = buildString { + appendLine("use of '@import' when modules are disabled") + appendLine("header: '$headerPath'") + appendLine("module name: '$name'") + } + errors.add(Error(message)) + } + } + override fun ppIncludedFile(info: CXIdxIncludedFileInfo) { val file = info.file!! val includer = clang_indexLoc_getCXSourceLocation(info.hashLoc.readValue()).getContainingFile() @@ -122,6 +144,7 @@ private fun getModulesHeaders( } }) + if (errors.isNotEmpty()) throw errors.first() // There are cases when non-modular includes should also be considered as a part of module. For example: // 1. Some module maps are broken, diff --git a/kotlin-native/Interop/Indexer/src/test/kotlin/org/jetbrains/kotlin/native/interop/indexer/ModuleTests.kt b/kotlin-native/Interop/Indexer/src/test/kotlin/org/jetbrains/kotlin/native/interop/indexer/ModuleTests.kt index b0a85a7976d..08845d57f8d 100644 --- a/kotlin-native/Interop/Indexer/src/test/kotlin/org/jetbrains/kotlin/native/interop/indexer/ModuleTests.kt +++ b/kotlin-native/Interop/Indexer/src/test/kotlin/org/jetbrains/kotlin/native/interop/indexer/ModuleTests.kt @@ -98,6 +98,39 @@ class ModuleTests : IndexerTests() { assertEquals(listOf(fooH.absolutePath), modulesInfo.topLevelHeaders.canonicalize()) } + @Test + fun testModuleImport() { + val files = TempFiles("testModuleImport") + val fooH = files.file("Foo.h", "@import Bar;") + files.file("Bar.h", "") + files.file("module.modulemap", """ + module Foo { + header "Foo.h" + } + module Bar { + header "Bar.h" + } + """.trimIndent()) + + //without '-fmodules' + val error = assertFails { + getModulesInfo(compilation("-I${files.directory}"), listOf("Foo")) + } + assertContains( + error.message.orEmpty(), + """ + use of '@import' when modules are disabled + header: '${fooH.absolutePath}' + module name: 'Bar' + """.trimIndent() + ) + + //with '-fmodules' + val modulesInfo = getModulesInfo(compilation("-I${files.directory}", "-fmodules"), listOf("Foo")) + assertEquals(setOf(fooH.absolutePath), modulesInfo.ownHeaders) + assertEquals(listOf(fooH.absolutePath), modulesInfo.topLevelHeaders.canonicalize()) + } + @Test fun testMissingModule() { val files = TempFiles("testMissingModule")