From ced3cf090464c83c2c1579aa2c40c653da8c5356 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Wed, 30 May 2018 16:30:36 +0200 Subject: [PATCH] Light classes: fix multifile facades for mpp modules with jvm part When creating multifile facades respect that facade files can be both in common and jvm parts #KT-23942 Fixed --- .../idea/caches/project/IdeaModuleInfos.kt | 4 +- .../idea/caches/project/multiplatformUtil.kt | 19 +++++++ .../caches/resolve/IDEKotlinAsJavaSupport.kt | 56 +++++++++++++------ .../caches/resolve/KtFileClassProviderImpl.kt | 2 +- 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt index 8e375d9f4ae..c572f65b051 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt @@ -400,9 +400,9 @@ interface SourceForBinaryModuleInfo : IdeaModuleInfo { get() = ModuleOrigin.OTHER } -class PlatformModuleInfo( +data class PlatformModuleInfo( internal val platformModule: ModuleSourceInfo, - private val commonModules: List + private val commonModules: List // NOTE: usually contains a single element for current implementation ) : IdeaModuleInfo, CombinedModuleInfo, TrackableModuleInfo { override val capabilities: Map, Any?> get() = platformModule.capabilities diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/multiplatformUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/multiplatformUtil.kt index 3a17da9eb4b..3ef759ac3a8 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/multiplatformUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/multiplatformUtil.kt @@ -11,12 +11,14 @@ import com.intellij.openapi.externalSystem.service.project.IdeModelsProviderImpl import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.roots.ProjectRootModificationTracker +import com.intellij.psi.PsiElement import com.intellij.psi.util.CachedValueProvider import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.idea.facet.KotlinFacetType import org.jetbrains.kotlin.idea.facet.KotlinFacetType.Companion.ID +import org.jetbrains.kotlin.resolve.TargetPlatform val Module.implementingModules: List get() = cached(CachedValueProvider { @@ -72,3 +74,20 @@ val ModuleDescriptor.implementedDescriptors: List private fun ModuleSourceInfo.toDescriptor() = KotlinCacheService.getInstance(module.project) .getResolutionFacadeByModuleInfo(this, platform)?.moduleDescriptor + +fun PsiElement.getPlatformModuleInfo(desiredPlatform: TargetPlatform): PlatformModuleInfo? { + assert(desiredPlatform != TargetPlatform.Common) { "Platform module cannot have Common platform" } + val moduleInfo = getModuleInfo() as? ModuleSourceInfo ?: return null + return when (moduleInfo.platform) { + TargetPlatform.Common -> { + val correspondingImplementingModule = moduleInfo.module.implementingModules.map { it.toInfo(moduleInfo.isTests()) } + .firstOrNull { it?.platform == desiredPlatform } ?: return null + PlatformModuleInfo(correspondingImplementingModule, correspondingImplementingModule.expectedBy) + } + desiredPlatform -> { + val expectedBy = moduleInfo.expectedBy.takeIf { it.isNotEmpty() } ?: return null + PlatformModuleInfo(moduleInfo, expectedBy) + } + else -> null + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEKotlinAsJavaSupport.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEKotlinAsJavaSupport.kt index 1d2086b6eba..3d5b957fda3 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEKotlinAsJavaSupport.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDEKotlinAsJavaSupport.kt @@ -19,16 +19,17 @@ import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName import org.jetbrains.kotlin.idea.caches.lightClasses.ClsJavaStubByVirtualFileCache import org.jetbrains.kotlin.idea.caches.lightClasses.KtLightClassForDecompiledDeclaration import org.jetbrains.kotlin.idea.caches.lightClasses.platformMutabilityWrapper +import org.jetbrains.kotlin.idea.caches.project.* import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo -import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo -import org.jetbrains.kotlin.idea.caches.project.getModuleInfo import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile import org.jetbrains.kotlin.idea.decompiler.navigation.SourceNavigationHelper +import org.jetbrains.kotlin.idea.project.platform import org.jetbrains.kotlin.idea.stubindex.* import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.utils.sure import java.util.* @@ -47,10 +48,10 @@ class IDEKotlinAsJavaSupport(private val project: Project): KotlinAsJavaSupport( override fun getFacadeClassesInPackage(packageFqName: FqName, scope: GlobalSearchScope): Collection { val facadeFilesInPackage = runReadAction { KotlinFileFacadeClassByPackageIndex.getInstance() - .get(packageFqName.asString(), project, scope) + .get(packageFqName.asString(), project, scope).platformSourcesFirst() } val groupedByFqNameAndModuleInfo = facadeFilesInPackage.groupBy { - Pair(it.javaFileFacadeFqName, it.getModuleInfo()) + Pair(it.javaFileFacadeFqName, it.getModuleInfoPreferringJvmPlatform()) } return groupedByFqNameAndModuleInfo.flatMap { @@ -141,11 +142,9 @@ class IDEKotlinAsJavaSupport(private val project: Project): KotlinAsJavaSupport( KtLightClassForScript.create(script) private fun withFakeLightClasses( - lightClassForFacade: KtLightClassForFacade?, + lightClassForFacade: KtLightClassForFacade, facadeFiles: List ): List { - if (lightClassForFacade == null) return emptyList() - val lightClasses = ArrayList() lightClasses.add(lightClassForFacade) if (facadeFiles.size > 1) { @@ -157,7 +156,7 @@ class IDEKotlinAsJavaSupport(private val project: Project): KotlinAsJavaSupport( } override fun getFacadeClasses(facadeFqName: FqName, scope: GlobalSearchScope): Collection { - val filesByModule = findFilesForFacade(facadeFqName, scope).groupBy(KtFile::getModuleInfo) + val filesByModule = findFilesForFacade(facadeFqName, scope).groupBy(PsiElement::getModuleInfoPreferringJvmPlatform) return filesByModule.flatMap { createLightClassForFileFacade(facadeFqName, it.value, it.key) @@ -209,23 +208,40 @@ class IDEKotlinAsJavaSupport(private val project: Project): KotlinAsJavaSupport( moduleInfo: IdeaModuleInfo ): List { val (clsFiles, sourceFiles) = facadeFiles.partition { it is KtClsFile } - val lightClassesForClsFacades = clsFiles.mapNotNull { createLightClassForDecompiledKotlinFile(it as KtClsFile) } - if (moduleInfo is ModuleSourceInfo && sourceFiles.isNotEmpty()) { - val lightClassForFacade = KtLightClassForFacade.createForFacade( - psiManager, facadeFqName, moduleInfo.contentScope(), sourceFiles - ) - return withFakeLightClasses(lightClassForFacade, sourceFiles) + lightClassesForClsFacades - } else { - return lightClassesForClsFacades - } + val facadesFromCls = clsFiles.mapNotNull { createLightClassForDecompiledKotlinFile(it as KtClsFile) } + val facadesFromSources = createFacadesForSourceFiles(moduleInfo, sourceFiles, facadeFqName) + return facadesFromSources + facadesFromCls + } + + private fun createFacadesForSourceFiles( + moduleInfo: IdeaModuleInfo, + sourceFiles: List, + facadeFqName: FqName + ): List { + if (sourceFiles.isEmpty()) return listOf() + if (moduleInfo !is ModuleSourceInfo && moduleInfo !is PlatformModuleInfo) return listOf() + + val lightClassForFacade = KtLightClassForFacade.createForFacade( + psiManager, facadeFqName, moduleInfo.contentScope(), sourceFiles + ) + return withFakeLightClasses(lightClassForFacade, sourceFiles) } override fun findFilesForFacade(facadeFqName: FqName, scope: GlobalSearchScope): Collection { return runReadAction { - KotlinFileFacadeFqNameIndex.INSTANCE.get(facadeFqName.asString(), project, scope) + KotlinFileFacadeFqNameIndex.INSTANCE.get(facadeFqName.asString(), project, scope).platformSourcesFirst() } } + + // NOTE: this is a hacky solution to the following problem: + // when building this light class resolver will be built by the first file in the list + // (we could assume that files are in the same module before) + // thus we need to ensure that resolver will be built by the file from platform part of the module + // (resolver built by a file from the common part will have no knowledge of the platform part) + // the actual of order of files that resolver receives is controlled by *findFilesForFacade* method + private fun Collection.platformSourcesFirst() = sortedByDescending { it.platform == JvmPlatform } + private fun getLightClassForDecompiledClassOrObject(decompiledClassOrObject: KtClassOrObject): KtLightClassForDecompiledDeclaration? { if (decompiledClassOrObject is KtEnumEntry) { return null @@ -307,4 +323,8 @@ class IDEKotlinAsJavaSupport(private val project: Project): KotlinAsJavaSupport( javaFileStub.psi = fakeFile return fakeFile.classes.single() as ClsClassImpl } +} + +internal fun PsiElement.getModuleInfoPreferringJvmPlatform(): IdeaModuleInfo { + return getPlatformModuleInfo(JvmPlatform) ?: getModuleInfo() } \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KtFileClassProviderImpl.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KtFileClassProviderImpl.kt index 4921eab7d9f..bc76e3a0926 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KtFileClassProviderImpl.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/KtFileClassProviderImpl.kt @@ -29,7 +29,7 @@ class KtFileClassProviderImpl(val kotlinAsJavaSupport: KotlinAsJavaSupport) : val result = arrayListOf() file.declarations.filterIsInstance().map { it.toLightClass() }.filterNotNullTo(result) - val moduleInfo = file.getModuleInfo() + val moduleInfo = file.getModuleInfoPreferringJvmPlatform() val jvmClassInfo = JvmFileClassUtil.getFileClassInfoNoResolve(file) val fileClassFqName = file.javaFileFacadeFqName