| /* |
| * Copyright 2010-2016 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.jetbrains.kotlin.idea.caches.resolve |
| |
| import com.intellij.openapi.diagnostic.Logger |
| import com.intellij.openapi.project.Project |
| import com.intellij.openapi.projectRoots.Sdk |
| import com.intellij.openapi.roots.ProjectRootModificationTracker |
| import com.intellij.openapi.util.ModificationTracker |
| import com.intellij.psi.PsiCodeFragment |
| import com.intellij.psi.PsiFile |
| import com.intellij.psi.util.CachedValue |
| import com.intellij.psi.util.CachedValueProvider |
| import com.intellij.psi.util.CachedValuesManager |
| import com.intellij.psi.util.PsiModificationTracker |
| import com.intellij.util.containers.SLRUCache |
| import org.jetbrains.kotlin.analyzer.ModuleInfo |
| import org.jetbrains.kotlin.builtins.KotlinBuiltIns |
| import org.jetbrains.kotlin.caches.resolve.KotlinCacheService |
| import org.jetbrains.kotlin.config.LanguageFeature |
| import org.jetbrains.kotlin.context.GlobalContext |
| import org.jetbrains.kotlin.context.GlobalContextImpl |
| import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor |
| import org.jetbrains.kotlin.idea.caches.project.* |
| import org.jetbrains.kotlin.idea.caches.resolve.util.contextWithNewLockAndCompositeExceptionTracker |
| import org.jetbrains.kotlin.idea.compiler.IDELanguageSettingsProvider |
| import org.jetbrains.kotlin.idea.core.script.ScriptDependenciesModificationTracker |
| import org.jetbrains.kotlin.idea.project.TargetPlatformDetector |
| import org.jetbrains.kotlin.idea.project.outOfBlockModificationCount |
| import org.jetbrains.kotlin.idea.resolve.ResolutionFacade |
| import org.jetbrains.kotlin.idea.util.ProjectRootsUtil |
| import org.jetbrains.kotlin.psi.* |
| import org.jetbrains.kotlin.psi.psiUtil.contains |
| import org.jetbrains.kotlin.resolve.BindingContext |
| import org.jetbrains.kotlin.resolve.TargetPlatform |
| import org.jetbrains.kotlin.resolve.diagnostics.KotlinSuppressCache |
| import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform |
| import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode |
| import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull |
| import org.jetbrains.kotlin.utils.addToStdlib.sumByLong |
| import java.lang.AssertionError |
| import java.lang.IllegalStateException |
| |
| internal val LOG = Logger.getInstance(KotlinCacheService::class.java) |
| |
| // For every different instance of these settings we must create a different builtIns instance and thus a different moduleDescriptor graph |
| // since in the current implementation types from one module are leaking into other modules' resolution |
| // meaning that we can't just change those setting on a per module basis |
| data class PlatformAnalysisSettings(val platform: TargetPlatform, val sdk: Sdk?, val isAdditionalBuiltInFeaturesSupported: Boolean) |
| |
| class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService { |
| override fun getResolutionFacade(elements: List<KtElement>): ResolutionFacade { |
| return getFacadeToAnalyzeFiles(elements.map { |
| // in theory `containingKtFile` is `@NotNull` but in practice EA-114080 |
| it.containingKtFile ?: throw IllegalStateException("containingKtFile was null for $it of ${it.javaClass}") |
| }) |
| } |
| |
| override fun getSuppressionCache(): KotlinSuppressCache = kotlinSuppressCache.value |
| |
| private val globalFacadesPerPlatformAndSdk: SLRUCache<PlatformAnalysisSettings, GlobalFacade> = |
| object : SLRUCache<PlatformAnalysisSettings, GlobalFacade>(2 * 3 * 2, 2 * 3 * 2) { |
| override fun createValue(settings: PlatformAnalysisSettings): GlobalFacade { |
| return GlobalFacade(settings) |
| } |
| } |
| |
| |
| private val facadesForScriptDependencies: SLRUCache<ScriptModuleInfo, ProjectResolutionFacade> = |
| object : SLRUCache<ScriptModuleInfo, ProjectResolutionFacade>(2, 3) { |
| override fun createValue(scriptModuleInfo: ScriptModuleInfo?): ProjectResolutionFacade { |
| return createFacadeForScriptDependencies( |
| ScriptDependenciesModuleInfo( |
| project, |
| scriptModuleInfo |
| ) |
| ) |
| } |
| } |
| |
| private fun getFacadeForScriptDependencies(scriptModuleInfo: ScriptModuleInfo): ProjectResolutionFacade { |
| return synchronized(facadesForScriptDependencies) { |
| facadesForScriptDependencies.get(scriptModuleInfo) |
| } |
| } |
| |
| private fun createFacadeForScriptDependencies( |
| dependenciesModuleInfo: ScriptDependenciesModuleInfo, |
| syntheticFiles: Collection<KtFile> = listOf() |
| ): ProjectResolutionFacade { |
| val sdk = findJdk(dependenciesModuleInfo.scriptModuleInfo?.externalDependencies, project) |
| val platform = JvmPlatform // TODO: Js scripts? |
| val settings = PlatformAnalysisSettings(platform, sdk, true) |
| val sdkFacade = GlobalFacade(settings).facadeForSdk |
| val globalContext = sdkFacade.globalContext.contextWithNewLockAndCompositeExceptionTracker() |
| return ProjectResolutionFacade( |
| "facadeForScriptDependencies", "dependencies of scripts", |
| project, globalContext, settings, |
| reuseDataFrom = sdkFacade, |
| allModules = dependenciesModuleInfo.dependencies(), |
| //TODO: provide correct trackers |
| dependencies = listOf( |
| LibraryModificationTracker.getInstance(project), |
| ProjectRootModificationTracker.getInstance(project), |
| ScriptDependenciesModificationTracker.getInstance(project) |
| ), |
| moduleFilter = { it == dependenciesModuleInfo }, |
| invalidateOnOOCB = true, |
| syntheticFiles = syntheticFiles |
| ) |
| } |
| |
| |
| private inner class GlobalFacade(settings: PlatformAnalysisSettings) { |
| private val sdkContext = GlobalContext() |
| val facadeForSdk = ProjectResolutionFacade( |
| "facadeForSdk", "sdk ${settings.sdk}", |
| project, sdkContext, settings, |
| moduleFilter = { it is SdkInfo }, |
| dependencies = listOf( |
| LibraryModificationTracker.getInstance(project), |
| ProjectRootModificationTracker.getInstance(project) |
| ), |
| invalidateOnOOCB = false, |
| reuseDataFrom = null |
| ) |
| |
| private val librariesContext = sdkContext.contextWithNewLockAndCompositeExceptionTracker() |
| val facadeForLibraries = ProjectResolutionFacade( |
| "facadeForLibraries", "project libraries for platform ${settings.sdk}", |
| project, librariesContext, settings, |
| reuseDataFrom = facadeForSdk, |
| moduleFilter = { it is LibraryInfo }, |
| invalidateOnOOCB = false, |
| dependencies = listOf( |
| LibraryModificationTracker.getInstance(project), |
| ProjectRootModificationTracker.getInstance(project) |
| ) |
| ) |
| |
| private val modulesContext = librariesContext.contextWithNewLockAndCompositeExceptionTracker() |
| val facadeForModules = ProjectResolutionFacade( |
| "facadeForModules", "project source roots and libraries for platform ${settings.platform}", |
| project, modulesContext, settings, |
| reuseDataFrom = facadeForLibraries, |
| moduleFilter = { !it.isLibraryClasses() }, |
| dependencies = listOf( |
| LibraryModificationTracker.getInstance(project), |
| ProjectRootModificationTracker.getInstance(project) |
| ), |
| invalidateOnOOCB = true |
| ) |
| } |
| |
| private fun IdeaModuleInfo.supportsAdditionalBuiltInsMembers(): Boolean { |
| return IDELanguageSettingsProvider |
| .getLanguageVersionSettings(this, project) |
| .supportsFeature(LanguageFeature.AdditionalBuiltInsMembers) |
| } |
| |
| private fun globalFacade(settings: PlatformAnalysisSettings) = |
| getOrBuildGlobalFacade(settings).facadeForModules |
| |
| private fun librariesFacade(settings: PlatformAnalysisSettings) = getOrBuildGlobalFacade(settings).facadeForLibraries |
| |
| @Synchronized |
| private fun getOrBuildGlobalFacade(settings: PlatformAnalysisSettings) = globalFacadesPerPlatformAndSdk[settings] |
| |
| private val IdeaModuleInfo.sdk: Sdk? get() = dependencies().firstIsInstanceOrNull<SdkInfo>()?.sdk |
| |
| private fun createFacadeForSyntheticFiles(files: Set<KtFile>): ProjectResolutionFacade { |
| // we assume that all files come from the same module |
| val targetPlatform = files.map { TargetPlatformDetector.getPlatform(it) }.toSet().single() |
| val syntheticFileModule = files.map(KtFile::getModuleInfo).toSet().single() |
| val sdk = syntheticFileModule.sdk |
| val settings = PlatformAnalysisSettings(targetPlatform, sdk, syntheticFileModule.supportsAdditionalBuiltInsMembers()) |
| // File copies are created during completion and receive correct modification events through POM. |
| // Dummy files created e.g. by J2K do not receive events. |
| val filesModificationTracker = if (files.all { it.originalFile != it }) { |
| ModificationTracker { |
| files.sumByLong { it.outOfBlockModificationCount } |
| } |
| } else { |
| ModificationTracker { |
| files.sumByLong { it.outOfBlockModificationCount + it.modificationStamp } |
| } |
| } |
| |
| val dependenciesForSyntheticFileCache = |
| listOf( |
| PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT, |
| filesModificationTracker, |
| ScriptDependenciesModificationTracker.getInstance(project) |
| ) |
| |
| val resolverDebugName = |
| "completion/highlighting in $syntheticFileModule for files ${files.joinToString { it.name }} for platform $targetPlatform" |
| |
| fun makeProjectResolutionFacade( |
| debugName: String, |
| globalContext: GlobalContextImpl, |
| reuseDataFrom: ProjectResolutionFacade? = null, |
| moduleFilter: (IdeaModuleInfo) -> Boolean = { true }, |
| allModules: Collection<IdeaModuleInfo>? = null |
| ): ProjectResolutionFacade { |
| return ProjectResolutionFacade( |
| debugName, |
| resolverDebugName, |
| project, |
| globalContext, |
| settings, |
| syntheticFiles = files, |
| reuseDataFrom = reuseDataFrom, |
| moduleFilter = moduleFilter, |
| dependencies = dependenciesForSyntheticFileCache, |
| invalidateOnOOCB = true, |
| allModules = allModules |
| ) |
| } |
| |
| return when { |
| syntheticFileModule is ModuleSourceInfo -> { |
| val dependentModules = syntheticFileModule.getDependentModules() |
| val modulesFacade = globalFacade(settings) |
| val globalContext = modulesFacade.globalContext.contextWithNewLockAndCompositeExceptionTracker() |
| makeProjectResolutionFacade( |
| "facadeForSynthetic in ModuleSourceInfo", |
| globalContext, |
| reuseDataFrom = modulesFacade, |
| moduleFilter = { it in dependentModules } |
| ) |
| } |
| |
| syntheticFileModule is ScriptModuleInfo -> { |
| val facadeForScriptDependencies = getFacadeForScriptDependencies(syntheticFileModule) |
| val globalContext = facadeForScriptDependencies.globalContext.contextWithNewLockAndCompositeExceptionTracker() |
| makeProjectResolutionFacade( |
| "facadeForSynthetic in ScriptModuleInfo", |
| globalContext, |
| reuseDataFrom = facadeForScriptDependencies, |
| allModules = syntheticFileModule.dependencies(), |
| moduleFilter = { it == syntheticFileModule } |
| ) |
| } |
| syntheticFileModule is ScriptDependenciesModuleInfo -> { |
| createFacadeForScriptDependencies(syntheticFileModule, files) |
| } |
| syntheticFileModule is ScriptDependenciesSourceModuleInfo -> { |
| // TODO: can be optimized by caching facadeForScriptDependencies |
| val facadeForScriptDependencies = createFacadeForScriptDependencies(syntheticFileModule.binariesModuleInfo, files) |
| val globalContext = facadeForScriptDependencies.globalContext.contextWithNewLockAndCompositeExceptionTracker() |
| makeProjectResolutionFacade( |
| "facadeForSynthetic in ScriptDependenciesSourceModuleInfo", |
| globalContext, |
| reuseDataFrom = facadeForScriptDependencies, |
| allModules = syntheticFileModule.dependencies(), |
| moduleFilter = { it == syntheticFileModule } |
| ) |
| } |
| |
| syntheticFileModule is LibrarySourceInfo || syntheticFileModule is NotUnderContentRootModuleInfo -> { |
| val librariesFacade = librariesFacade(settings) |
| val globalContext = librariesFacade.globalContext.contextWithNewLockAndCompositeExceptionTracker() |
| makeProjectResolutionFacade( |
| "facadeForSynthetic in LibrarySourceInfo or NotUnderContentRootModuleInfo", |
| globalContext, |
| reuseDataFrom = librariesFacade, |
| moduleFilter = { it == syntheticFileModule } |
| ) |
| } |
| |
| syntheticFileModule.isLibraryClasses() -> { |
| //NOTE: this code should not be called for sdk or library classes |
| // currently the only known scenario is when we cannot determine that file is a library source |
| // (file under both classes and sources root) |
| LOG.warn("Creating cache with synthetic files ($files) in classes of library $syntheticFileModule") |
| val globalContext = GlobalContext() |
| makeProjectResolutionFacade( |
| "facadeForSynthetic for file under both classes and root", |
| globalContext |
| ) |
| } |
| |
| else -> throw IllegalStateException("Unknown IdeaModuleInfo ${syntheticFileModule::class.java}") |
| } |
| } |
| |
| private val suppressAnnotationShortName = KotlinBuiltIns.FQ_NAMES.suppress.shortName().identifier |
| private val kotlinSuppressCache: CachedValue<KotlinSuppressCache> = CachedValuesManager.getManager(project).createCachedValue( |
| { |
| CachedValueProvider.Result<KotlinSuppressCache>( |
| object : KotlinSuppressCache() { |
| override fun getSuppressionAnnotations(annotated: KtAnnotated): List<AnnotationDescriptor> { |
| if (annotated.annotationEntries.none { |
| it.calleeExpression?.text?.endsWith(suppressAnnotationShortName) == true |
| } |
| ) { |
| // Avoid running resolve heuristics |
| // TODO: Check aliases in imports |
| return emptyList() |
| } |
| |
| val context = |
| when (annotated) { |
| is KtFile -> { |
| annotated.fileAnnotationList?.analyze(BodyResolveMode.PARTIAL) |
| ?: return emptyList() |
| } |
| is KtModifierListOwner -> { |
| annotated.modifierList?.analyze(BodyResolveMode.PARTIAL) |
| ?: return emptyList() |
| } |
| else -> |
| annotated.analyze(BodyResolveMode.PARTIAL) |
| } |
| |
| val annotatedDescriptor = context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, annotated) |
| |
| if (annotatedDescriptor != null) { |
| return annotatedDescriptor.annotations.toList() |
| } |
| |
| return annotated.annotationEntries.mapNotNull { |
| context.get( |
| BindingContext.ANNOTATION, |
| it |
| ) |
| } |
| } |
| }, |
| LibraryModificationTracker.getInstance(project), |
| PsiModificationTracker.MODIFICATION_COUNT |
| ) |
| }, |
| false |
| ) |
| |
| private val syntheticFileCachesLock = Any() |
| |
| private val syntheticFilesCacheProvider = CachedValueProvider { |
| CachedValueProvider.Result(object : SLRUCache<Set<KtFile>, ProjectResolutionFacade>(2, 3) { |
| override fun createValue(files: Set<KtFile>) = createFacadeForSyntheticFiles(files) |
| }, LibraryModificationTracker.getInstance(project), ProjectRootModificationTracker.getInstance(project)) |
| } |
| |
| private fun getFacadeForSyntheticFiles(files: Set<KtFile>): ProjectResolutionFacade { |
| val cachedValue = synchronized(syntheticFileCachesLock) { |
| //NOTE: computations inside createCacheForSyntheticFiles depend on project root structure |
| // so we additionally drop the whole slru cache on change |
| CachedValuesManager.getManager(project).getCachedValue(project, syntheticFilesCacheProvider) |
| } |
| // In Upsource, we create multiple instances of KotlinCacheService, which all access the same CachedValue instance (UP-8046) |
| // To avoid race conditions, we can't use the local lock to access the cached value contents. |
| synchronized(cachedValue) { |
| return cachedValue.get(files) |
| } |
| } |
| |
| private fun getFacadeToAnalyzeFiles(files: Collection<KtFile>): ResolutionFacade { |
| val file = files.first() |
| val moduleInfo = file.getModuleInfo() |
| val notInSourceFiles = files.filterNotInProjectSource(moduleInfo) |
| return if (notInSourceFiles.isNotEmpty()) { |
| val projectFacade = getFacadeForSyntheticFiles(notInSourceFiles) |
| ResolutionFacadeImpl(projectFacade, moduleInfo) |
| } else { |
| val platform = TargetPlatformDetector.getPlatform(file) |
| getResolutionFacadeByModuleInfo(moduleInfo, platform) |
| } |
| } |
| |
| override fun getResolutionFacadeByFile(file: PsiFile, platform: TargetPlatform): ResolutionFacade? { |
| if (!ProjectRootsUtil.isInProjectOrLibraryContent(file)) { |
| return null |
| } |
| |
| assert(file !is PsiCodeFragment) |
| |
| val moduleInfo = file.getModuleInfo() |
| return getResolutionFacadeByModuleInfo(moduleInfo, platform) |
| } |
| |
| private fun getResolutionFacadeByModuleInfo(moduleInfo: IdeaModuleInfo, platform: TargetPlatform): ResolutionFacade { |
| val settings = PlatformAnalysisSettings(platform, moduleInfo.sdk, moduleInfo.supportsAdditionalBuiltInsMembers()) |
| val projectFacade = globalFacade(settings) |
| return ResolutionFacadeImpl(projectFacade, moduleInfo) |
| } |
| |
| override fun getResolutionFacadeByModuleInfo(moduleInfo: ModuleInfo, platform: TargetPlatform): ResolutionFacade? = |
| (moduleInfo as? IdeaModuleInfo)?.let { getResolutionFacadeByModuleInfo(it, platform) } |
| |
| private fun Collection<KtFile>.filterNotInProjectSource(moduleInfo: IdeaModuleInfo) = mapNotNull { |
| if (it is KtCodeFragment) it.getContextFile() else it |
| }.filter { |
| !ProjectRootsUtil.isInProjectSource(it) || !moduleInfo.contentScope().contains(it) |
| }.toSet() |
| |
| private fun KtCodeFragment.getContextFile(): KtFile? { |
| val contextElement = context ?: return null |
| val contextFile = (contextElement as? KtElement)?.containingKtFile |
| ?: throw AssertionError("Analyzing kotlin code fragment of type ${this::class.java} with java context of type ${contextElement::class.java}") |
| return if (contextFile is KtCodeFragment) contextFile.getContextFile() else contextFile |
| } |
| } |
| |