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
This commit is contained in:
Pavel V. Talanov
2018-05-30 16:30:36 +02:00
committed by Pavel V. Talanov
parent a8a490ea5a
commit ced3cf0904
4 changed files with 60 additions and 21 deletions
@@ -400,9 +400,9 @@ interface SourceForBinaryModuleInfo : IdeaModuleInfo {
get() = ModuleOrigin.OTHER
}
class PlatformModuleInfo(
data class PlatformModuleInfo(
internal val platformModule: ModuleSourceInfo,
private val commonModules: List<ModuleSourceInfo>
private val commonModules: List<ModuleSourceInfo> // NOTE: usually contains a single element for current implementation
) : IdeaModuleInfo, CombinedModuleInfo, TrackableModuleInfo {
override val capabilities: Map<ModuleDescriptor.Capability<*>, Any?>
get() = platformModule.capabilities
@@ -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<Module>
get() = cached(CachedValueProvider {
@@ -72,3 +74,20 @@ val ModuleDescriptor.implementedDescriptors: List<ModuleDescriptor>
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
}
}
@@ -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<PsiClass> {
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<KtFile>
): List<PsiClass> {
if (lightClassForFacade == null) return emptyList()
val lightClasses = ArrayList<PsiClass>()
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<PsiClass> {
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<PsiClass> {
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<KtFile>,
facadeFqName: FqName
): List<PsiClass> {
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<KtFile> {
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<KtFile>.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()
}
@@ -29,7 +29,7 @@ class KtFileClassProviderImpl(val kotlinAsJavaSupport: KotlinAsJavaSupport) :
val result = arrayListOf<PsiClass>()
file.declarations.filterIsInstance<KtClassOrObject>().map { it.toLightClass() }.filterNotNullTo(result)
val moduleInfo = file.getModuleInfo()
val moduleInfo = file.getModuleInfoPreferringJvmPlatform()
val jvmClassInfo = JvmFileClassUtil.getFileClassInfoNoResolve(file)
val fileClassFqName = file.javaFileFacadeFqName