diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaSymbolProvider.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaSymbolProvider.kt index c0452969577..7bc7e490dfe 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaSymbolProvider.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaSymbolProvider.kt @@ -404,7 +404,7 @@ class JavaSymbolProvider( private fun hasTopLevelClassOf(classId: ClassId): Boolean { val knownNames = knownClassNamesInPackage.getOrPut(classId.packageFqName) { - facade.knownClassNamesInPackage(classId.packageFqName) + facade.knownClassNamesInPackage(classId.packageFqName, searchScope) } ?: return true return classId.relativeClassName.topLevelName() in knownNames } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/AbstractJavaClassFinder.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/AbstractJavaClassFinder.kt index 9a2fbf5d3af..d93cd236d7d 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/AbstractJavaClassFinder.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/AbstractJavaClassFinder.kt @@ -23,6 +23,7 @@ import com.intellij.psi.search.GlobalSearchScope import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer +import org.jetbrains.kotlin.resolve.jvm.TopPackageNamesProvider import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer import javax.annotation.PostConstruct import javax.inject.Inject @@ -32,10 +33,14 @@ abstract class AbstractJavaClassFinder : JavaClassFinder { protected lateinit var project: Project protected lateinit var javaSearchScope: GlobalSearchScope - @Inject fun setScope(scope: GlobalSearchScope) { - javaSearchScope = FilterOutKotlinSourceFilesScope(scope) + javaSearchScope = + if (scope == GlobalSearchScope.EMPTY_SCOPE) { + GlobalSearchScope.EMPTY_SCOPE + } else { + FilterOutKotlinSourceFilesScope(scope) + } } @Inject @@ -48,9 +53,15 @@ abstract class AbstractJavaClassFinder : JavaClassFinder { CodeAnalyzerInitializer.getInstance(project).initialize(trace, codeAnalyzer.moduleDescriptor, codeAnalyzer) } - inner class FilterOutKotlinSourceFilesScope(baseScope: GlobalSearchScope) : DelegatingGlobalSearchScope(baseScope) { + inner class FilterOutKotlinSourceFilesScope(baseScope: GlobalSearchScope) : DelegatingGlobalSearchScope(baseScope), + TopPackageNamesProvider { - override fun contains(file: VirtualFile) = myBaseScope.contains(file) && (file.isDirectory || file.fileType !== KotlinFileType.INSTANCE) + override val topPackageNames: Set? + get() = (myBaseScope as? TopPackageNamesProvider)?.topPackageNames + + override fun contains(file: VirtualFile) = + (file.isDirectory || file.fileType !== KotlinFileType.INSTANCE) && + myBaseScope.contains(file) val base: GlobalSearchScope = myBaseScope diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/JavaClassFinderImpl.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/JavaClassFinderImpl.kt index 10d80766fc2..7659ab96bd1 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/JavaClassFinderImpl.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/JavaClassFinderImpl.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.load.java import com.intellij.openapi.project.Project import org.jetbrains.kotlin.load.java.structure.JavaClass +import org.jetbrains.kotlin.load.java.structure.JavaPackage import org.jetbrains.kotlin.load.java.structure.impl.JavaPackageImpl import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade @@ -33,11 +34,16 @@ class JavaClassFinderImpl : AbstractJavaClassFinder() { javaFacade = KotlinJavaPsiFacade.getInstance(project) } - override fun findClass(request: JavaClassFinder.Request): JavaClass? = javaFacade.findClass(request, javaSearchScope) + override fun findClass(request: JavaClassFinder.Request): JavaClass? { + return javaFacade.findClass(request, javaSearchScope) + } - override fun findPackage(fqName: FqName) = - javaFacade.findPackage(fqName.asString(), javaSearchScope)?.let { JavaPackageImpl(it, javaSearchScope) } + override fun findPackage(fqName: FqName): JavaPackage? { + return javaFacade.findPackage(fqName.asString(), javaSearchScope)?.let { JavaPackageImpl(it, javaSearchScope) } + } - override fun knownClassNamesInPackage(packageFqName: FqName): Set? = javaFacade.knownClassNamesInPackage(packageFqName) + override fun knownClassNamesInPackage(packageFqName: FqName): Set? { + return javaFacade.knownClassNamesInPackage(packageFqName, javaSearchScope) + } } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/incremental/IncrementalPackageFragmentProvider.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/incremental/IncrementalPackageFragmentProvider.kt index 8991b7b4fdc..e9d94e9352f 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/incremental/IncrementalPackageFragmentProvider.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/incremental/IncrementalPackageFragmentProvider.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.serialization.deserialization.DeserializationComponents import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPackageMemberScope import org.jetbrains.kotlin.storage.StorageManager +import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.keysToMap class IncrementalPackageFragmentProvider( @@ -56,10 +57,8 @@ class IncrementalPackageFragmentProvider( override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection = emptySet() - override fun getPackageFragments(fqName: FqName): List { - return listOfNotNull(fqNameToPackageFragment[fqName]) - } - + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) = + packageFragments.addIfNotNull(fqNameToPackageFragment[fqName]) inner class IncrementalPackageFragment(fqName: FqName) : PackageFragmentDescriptorImpl(moduleDescriptor, fqName) { val target: TargetId diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/multiplatform/OptionalAnnotationPackageFragmentProvider.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/multiplatform/OptionalAnnotationPackageFragmentProvider.kt index 788bfbdca58..31762cd20a1 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/multiplatform/OptionalAnnotationPackageFragmentProvider.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/multiplatform/OptionalAnnotationPackageFragmentProvider.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.storage.NotNullLazyValue import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.storage.getValue import org.jetbrains.kotlin.utils.Printer +import org.jetbrains.kotlin.utils.addIfNotNull class OptionalAnnotationPackageFragmentProvider( module: ModuleDescriptor, @@ -79,8 +80,8 @@ class OptionalAnnotationPackageFragmentProvider( } } - override fun getPackageFragments(fqName: FqName): List = - packages[fqName]?.let(::listOf).orEmpty() + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) = + packageFragments.addIfNotNull(packages[fqName]) override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection = emptyList() diff --git a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AbstractResolverForProject.kt b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AbstractResolverForProject.kt index 667de3bdb0a..285aaef451d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AbstractResolverForProject.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AbstractResolverForProject.kt @@ -241,10 +241,10 @@ private class DelegatingPackageFragmentProvider( ) : PackageFragmentProvider { private val syntheticFilePackages = moduleContent.syntheticFiles.map { it.packageFqName }.toSet() - override fun getPackageFragments(fqName: FqName): List { - if (certainlyDoesNotExist(fqName)) return emptyList() + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) { + if (certainlyDoesNotExist(fqName)) return - return resolverForProject.resolverForModuleDescriptor(module).packageFragmentProvider.getPackageFragments(fqName) + resolverForProject.resolverForModuleDescriptor(module).packageFragmentProvider.collectPackageFragments(fqName, packageFragments) } override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt index 54efdaf9a3e..1a078064c08 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt @@ -129,23 +129,28 @@ class LazyModuleDependencies( firstDependency: M? = null, private val resolverForProject: AbstractResolverForProject ) : ModuleDependencies { + private val dependencies = storageManager.createLazyValue { + + val moduleDescriptors = mutableSetOf() + firstDependency?.let { + moduleDescriptors.add(resolverForProject.descriptorForModule(it)) + } val moduleDescriptor = resolverForProject.descriptorForModule(module) - sequence { - if (firstDependency != null) { - yield(resolverForProject.descriptorForModule(firstDependency)) - } - if (module.dependencyOnBuiltIns() == ModuleInfo.DependencyOnBuiltIns.AFTER_SDK) { - yield(moduleDescriptor.builtIns.builtInsModule) - } - for (dependency in module.dependencies()) { - @Suppress("UNCHECKED_CAST") - yield(resolverForProject.descriptorForModule(dependency as M)) - } - if (module.dependencyOnBuiltIns() == ModuleInfo.DependencyOnBuiltIns.LAST) { - yield(moduleDescriptor.builtIns.builtInsModule) - } - }.toList() + val dependencyOnBuiltIns = module.dependencyOnBuiltIns() + if (dependencyOnBuiltIns == ModuleInfo.DependencyOnBuiltIns.AFTER_SDK) { + moduleDescriptors.add(moduleDescriptor.builtIns.builtInsModule) + } + for (dependency in module.dependencies()) { + if (dependency == firstDependency) continue + + @Suppress("UNCHECKED_CAST") + moduleDescriptors.add(resolverForProject.descriptorForModule(dependency as M)) + } + if (dependencyOnBuiltIns == ModuleInfo.DependencyOnBuiltIns.LAST) { + moduleDescriptors.add(moduleDescriptor.builtIns.builtInsModule) + } + moduleDescriptors.toList() } override val allDependencies: List get() = dependencies() diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt index aca160c93c7..806d0619127 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.resolve +import com.intellij.codeInsight.completion.CompletionUtilCore import com.intellij.psi.impl.source.DummyHolder import com.intellij.util.SmartList import org.jetbrains.kotlin.config.AnalysisFlags @@ -573,6 +574,9 @@ class QualifiedExpressionResolver(val languageVersionSettings: LanguageVersionSe context: ExpressionTypingContext ): Qualifier? { val name = expression.getReferencedNameAsName() + if (!expression.isPhysical && !name.isSpecial && name.asString().endsWith(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED)) { + return null + } val location = KotlinLookupLocation(expression) val qualifierDescriptor = when (receiver) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/ResolveSession.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/ResolveSession.java index 0437f7c2987..7f38d196fa5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/ResolveSession.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/lazy/ResolveSession.java @@ -180,6 +180,16 @@ public class ResolveSession implements KotlinCodeAnalyzer, LazyClassContext { this.declarationProviderFactory = declarationProviderFactory; this.packageFragmentProvider = new PackageFragmentProvider() { + @Override + public void collectPackageFragments( + @NotNull FqName fqName, @NotNull Collection packageFragments + ) { + LazyPackageDescriptor fragment = getPackageFragment(fqName); + if (fragment != null) { + packageFragments.add(fragment); + } + } + @NotNull @Override public List getPackageFragments(@NotNull FqName fqName) { diff --git a/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/resolve/jvm/KotlinJavaPsiFacade.java b/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/resolve/jvm/KotlinJavaPsiFacade.java index ff0c064455c..1d79feed0b3 100644 --- a/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/resolve/jvm/KotlinJavaPsiFacade.java +++ b/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/resolve/jvm/KotlinJavaPsiFacade.java @@ -21,9 +21,13 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.PackageIndex; +import com.intellij.openapi.util.LowMemoryWatcher; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; +import com.intellij.openapi.vfs.newvfs.events.*; import com.intellij.psi.*; import com.intellij.psi.impl.PsiElementFinderImpl; import com.intellij.psi.impl.file.PsiPackageImpl; @@ -31,13 +35,11 @@ import com.intellij.psi.impl.file.impl.JavaFileManager; import com.intellij.psi.impl.light.LightModifierList; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiModificationTracker; -import com.intellij.reference.SoftReference; import com.intellij.util.CommonProcessors; import com.intellij.util.ConcurrencyUtil; import com.intellij.util.Query; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.messages.MessageBus; -import kotlin.collections.ArraysKt; +import com.intellij.util.messages.MessageBusConnection; import kotlin.collections.CollectionsKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -60,11 +62,26 @@ public class KotlinJavaPsiFacade { private volatile KotlinPsiElementFinderWrapper[] elementFinders; private static class PackageCache { + // long term cache + final ConcurrentMap, PsiPackage> packageInLibScopeCache = ContainerUtil.newConcurrentMap(); + + // short term caches final ConcurrentMap, PsiPackage> packageInScopeCache = ContainerUtil.newConcurrentMap(); final ConcurrentMap hasPackageInAllScopeCache = ContainerUtil.newConcurrentMap(); + + void clear() { + packageInScopeCache.clear(); + hasPackageInAllScopeCache.clear(); + } } - private volatile SoftReference packageCache; + private static final PsiPackage NULL_PACKAGE = new PsiPackageImpl(null, "NULL_PACKAGE"); + + private static @Nullable PsiPackage unwrap(@NotNull PsiPackage psiPackage) { + return psiPackage == NULL_PACKAGE ? null : psiPackage; + } + + private volatile PackageCache packageCache; private final Project project; private final LightModifierList emptyModifierList; @@ -78,27 +95,60 @@ public class KotlinJavaPsiFacade { emptyModifierList = new LightModifierList(PsiManager.getInstance(project), KotlinLanguage.INSTANCE); - PsiModificationTracker modificationTracker = PsiManager.getInstance(project).getModificationTracker(); - MessageBus bus = project.getMessageBus(); + // drop entire cache when it is low free memory + LowMemoryWatcher.register(this::clearPackageCaches, project); - bus.connect().subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() { + MessageBusConnection connection = project.getMessageBus().connect(); + + // VFS changes like create/delete/copy/move directory are subject to clean up short term caches + connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { + @Override + public void after(@NotNull List events) { + boolean relevant = false; + for (VFileEvent event : events) { + VirtualFile file = event.getFile(); + relevant = ((event instanceof VFileCreateEvent && ((VFileCreateEvent) event).isDirectory()) || + (file != null && file.isDirectory() && + (event instanceof VFileDeleteEvent || + event instanceof VFileMoveEvent || + event instanceof VFileCopyEvent))); + + if (relevant) break; + } + if (relevant) { + clearPackageCaches(false); + } + } + }); + + // PSI changes (like in R files) could lead to creating virtual packages + // therefore it has to clean up short term caches + PsiModificationTracker modificationTracker = PsiManager.getInstance(project).getModificationTracker(); + connection.subscribe(PsiModificationTracker.TOPIC, new PsiModificationTracker.Listener() { private long lastTimeSeen = -1L; @Override - @SuppressWarnings("deprecation") public void modificationCountChanged() { - long now = modificationTracker.getJavaStructureModificationCount(); + long now = modificationTracker.getModificationCount(); if (lastTimeSeen != now) { lastTimeSeen = now; - packageCache = null; + clearPackageCaches(false); } } }); } public void clearPackageCaches() { - packageCache = null; + clearPackageCaches(true); + } + + private void clearPackageCaches(boolean force) { + if (force) { + packageCache = null; + } else { + obtainPackageCache().clear(); + } } public LightModifierList getEmptyModifierList() { @@ -106,7 +156,10 @@ public class KotlinJavaPsiFacade { } public JavaClass findClass(@NotNull JavaClassFinder.Request request, @NotNull GlobalSearchScope scope) { - ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly + if (scope == GlobalSearchScope.EMPTY_SCOPE) return null; + + // We hope this method is being called often enough to cancel daemon processes smoothly + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); ClassId classId = request.getClassId(); String qualifiedName = classId.asSingleFqName().asString(); @@ -150,7 +203,9 @@ public class KotlinJavaPsiFacade { } @Nullable - public Set knownClassNamesInPackage(@NotNull FqName packageFqName) { + public Set knownClassNamesInPackage(@NotNull FqName packageFqName, @NotNull GlobalSearchScope scope) { + if (scope == GlobalSearchScope.EMPTY_SCOPE) return null; + KotlinPsiElementFinderWrapper[] finders = finders(); if (finders.length == 1 && finders[0] instanceof CliFinder) { @@ -207,17 +262,19 @@ public class KotlinJavaPsiFacade { : new NonCliFinder(project, javaFileManager) ); - List nonKotlinFinders = ArraysKt.filter( - PsiElementFinder.EP.getPoint(getProject()).getExtensions(), - finder -> (finder instanceof KotlinSafeClassFinder) || - !(finder instanceof NonClasspathClassFinder || - finder instanceof KotlinFinderMarker || - finder instanceof PsiElementFinderImpl) - ); + List nonKotlinFinders = new ArrayList<>(); + for (PsiElementFinder finder : PsiElementFinder.EP.getExtensions(getProject())) { + if ((finder instanceof KotlinSafeClassFinder) || + !(finder instanceof NonClasspathClassFinder || + finder instanceof KotlinFinderMarker || + finder instanceof PsiElementFinderImpl)) { + nonKotlinFinders.add(finder); + } + } elementFinders.addAll(CollectionsKt.map(nonKotlinFinders, KotlinJavaPsiFacade::wrap)); - return elementFinders.toArray(new KotlinPsiElementFinderWrapper[elementFinders.size()]); + return elementFinders.toArray(new KotlinPsiElementFinderWrapper[0]); } @NotNull @@ -230,60 +287,116 @@ public class KotlinJavaPsiFacade { } public PsiPackage findPackage(@NotNull String qualifiedName, GlobalSearchScope searchScope) { + if (searchScope == GlobalSearchScope.EMPTY_SCOPE) return null; + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); + if (certainlyDoesNotExist(qualifiedName, searchScope)) return null; - PackageCache cache = SoftReference.dereference(packageCache); - if (cache == null) { - packageCache = new SoftReference<>(cache = new PackageCache()); - } - - Pair key = new Pair<>(qualifiedName, searchScope); - PsiPackage aPackage = cache.packageInScopeCache.get(key); - if (aPackage != null) { - return aPackage; - } - - KotlinPsiElementFinderWrapper[] finders = filteredFinders(); + PackageCache cache = obtainPackageCache(); Boolean packageFoundInAllScope = cache.hasPackageInAllScopeCache.get(qualifiedName); - if (packageFoundInAllScope != null) { - if (!packageFoundInAllScope.booleanValue()) return null; + if (packageFoundInAllScope != null && !packageFoundInAllScope.booleanValue()) return null; - // Package was found in AllScope with some of finders but is absent in packageCache for current scope. - // We check only finders that depend on scope. - for (KotlinPsiElementFinderWrapper finder : finders) { - if (!finder.isSameResultForAnyScope()) { - aPackage = finder.findPackage(qualifiedName, searchScope); - if (aPackage != null) { - return ConcurrencyUtil.cacheOrGet(cache.packageInScopeCache, key, aPackage); + Pair key = new Pair<>(qualifiedName, searchScope); + PsiPackage pkg = cache.packageInLibScopeCache.get(key); + if (pkg != null) { + return unwrap(pkg); + } + pkg = cache.packageInScopeCache.get(key); + if (pkg != null) { + return unwrap(pkg); + } + + boolean isALibrarySearchScope = isALibrarySearchScope(searchScope); + + { + // store found package in a long term cache if package is found in library search scope + ConcurrentMap, PsiPackage> existedPackageInScopeCache = + isALibrarySearchScope ? cache.packageInLibScopeCache : cache.packageInScopeCache; + + KotlinPsiElementFinderWrapper[] finders = filteredFinders(); + if (packageFoundInAllScope != null) { + // Package was found in AllScope with some of finders but is absent in packageCache for current scope. + // We check only finders that depend on scope. + for (KotlinPsiElementFinderWrapper finder : finders) { + if (!finder.isSameResultForAnyScope()) { + PsiPackage aPackage = finder.findPackage(qualifiedName, searchScope); + if (aPackage != null) { + return unwrap(ConcurrencyUtil.cacheOrGet(existedPackageInScopeCache, key, aPackage)); + } } } } - } - else { - for (KotlinPsiElementFinderWrapper finder : finders) { - aPackage = finder.findPackage(qualifiedName, searchScope); + else { + for (KotlinPsiElementFinderWrapper finder : finders) { + PsiPackage aPackage = finder.findPackage(qualifiedName, searchScope); - if (aPackage != null) { - return ConcurrencyUtil.cacheOrGet(cache.packageInScopeCache, key, aPackage); - } - } - - boolean found = false; - for (KotlinPsiElementFinderWrapper finder : finders) { - if (!finder.isSameResultForAnyScope()) { - aPackage = finder.findPackage(qualifiedName, GlobalSearchScope.allScope(project)); if (aPackage != null) { - found = true; - break; + return unwrap(ConcurrencyUtil.cacheOrGet(existedPackageInScopeCache, key, aPackage)); } } - } - cache.hasPackageInAllScopeCache.put(qualifiedName, found); + boolean found = false; + for (KotlinPsiElementFinderWrapper finder : finders) { + if (!finder.isSameResultForAnyScope()) { + PsiPackage aPackage = finder.findPackage(qualifiedName, GlobalSearchScope.allScope(project)); + if (aPackage != null) { + found = true; + break; + } + } + } + + cache.hasPackageInAllScopeCache.put(qualifiedName, found); + } } - return null; + // qualifiedName could be like a proper package name, e.g `org.jetbrains.kotlin` + // but it could be as well part of typed text like `fooba` + // + // all those temporary names and those don't even look like a package name should be stored in a short term cache + // while names those are potentially proper package name could be stored for a long time + // (till PROJECT_ROOTS or specific VFS changes) + boolean packageLikeQName = qualifiedName.indexOf('.') > 0; + + ConcurrentMap, PsiPackage> notFoundPackageInScopeCache = + // store NULL_PACKAGE (attribute that package not found) in a long term cache if: + // - library search scope + // - qualifiedName looks like package (has `.` in its name) + isALibrarySearchScope && packageLikeQName ? + cache.packageInLibScopeCache : cache.packageInScopeCache; + + return unwrap(ConcurrencyUtil.cacheOrGet(notFoundPackageInScopeCache, key, NULL_PACKAGE)); + } + + private PackageCache obtainPackageCache() { + PackageCache cache = packageCache; + if (cache == null) { + packageCache = cache = new PackageCache(); + } + return cache; + } + + private static boolean isALibrarySearchScope(GlobalSearchScope searchScope) { + return searchScope.isSearchInLibraries(); + } + + private static boolean certainlyDoesNotExist(@NotNull String qualifiedName, GlobalSearchScope searchScope) { + if (searchScope instanceof TopPackageNamesProvider) { + TopPackageNamesProvider topPackageAwareSearchScope = (TopPackageNamesProvider) searchScope; + Set topPackageNames = topPackageAwareSearchScope.getTopPackageNames(); + if (topPackageNames != null) { + String topPackageName = qualifiedName; + int index = topPackageName.indexOf('.'); + if (index > 0) { + topPackageName = topPackageName.substring(0, index); + } + if (!topPackageNames.contains(topPackageName)) { + return true; + } + } + } + return false; } @NotNull @@ -292,7 +405,7 @@ public class KotlinJavaPsiFacade { KotlinPsiElementFinderWrapper[] finders = finders(); if (dumbService.isDumb()) { List list = dumbService.filterByDumbAwareness(Arrays.asList(finders)); - finders = list.toArray(new KotlinPsiElementFinderWrapper[list.size()]); + finders = list.toArray(new KotlinPsiElementFinderWrapper[0]); } return finders; } diff --git a/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/resolve/jvm/TopPackageNamesProvider.kt b/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/resolve/jvm/TopPackageNamesProvider.kt new file mode 100644 index 00000000000..247b2b0817f --- /dev/null +++ b/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/resolve/jvm/TopPackageNamesProvider.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.jvm + +interface TopPackageNamesProvider { + val topPackageNames: Set? +} \ No newline at end of file diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/LazyJavaPackageFragmentProvider.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/LazyJavaPackageFragmentProvider.kt index f8196df06cb..d33fa57941f 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/LazyJavaPackageFragmentProvider.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/lazy/LazyJavaPackageFragmentProvider.kt @@ -16,11 +16,13 @@ package org.jetbrains.kotlin.load.java.lazy +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.PackageFragmentProvider import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageFragment import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.storage.CacheWithNotNullValues +import org.jetbrains.kotlin.utils.addIfNotNull class LazyJavaPackageFragmentProvider( components: JavaResolverComponents @@ -41,6 +43,9 @@ class LazyJavaPackageFragmentProvider( override fun getPackageFragments(fqName: FqName) = listOfNotNull(getPackageFragment(fqName)) + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) = + packageFragments.addIfNotNull(getPackageFragment(fqName)) + override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean) = getPackageFragment(fqName)?.getSubPackageFqNames().orEmpty() } diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackageFragmentProvider.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackageFragmentProvider.kt index 216c723fbfe..b2740cbd0ba 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackageFragmentProvider.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackageFragmentProvider.kt @@ -20,13 +20,19 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name interface PackageFragmentProvider { - fun getPackageFragments(fqName: FqName): List + fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) + + fun getPackageFragments(fqName: FqName): List { + val packageFragments = mutableListOf() + collectPackageFragments(fqName, packageFragments) + return packageFragments + } fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection object Empty : PackageFragmentProvider { - override fun getPackageFragments(fqName: FqName) = emptyList() + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) {} override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean) = emptySet() } diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackageFragmentProviderImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackageFragmentProviderImpl.kt index 6e2960d86bb..ebb4e1e10de 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackageFragmentProviderImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/PackageFragmentProviderImpl.kt @@ -20,14 +20,15 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name class PackageFragmentProviderImpl( - private val packageFragments: Collection + private val packageFragments: Collection ) : PackageFragmentProvider { - override fun getPackageFragments(fqName: FqName): List = - packageFragments.filter { it.fqName == fqName } + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) { + this.packageFragments.filterTo(packageFragments) { it.fqName == fqName } + } override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection = - packageFragments.asSequence() - .map { it.fqName } - .filter { !it.isRoot && it.parent() == fqName } - .toList() + packageFragments.asSequence() + .map { it.fqName } + .filter { !it.isRoot && it.parent() == fqName } + .toList() } diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/CompositePackageFragmentProvider.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/CompositePackageFragmentProvider.kt index 50298ddd906..b91e827f113 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/CompositePackageFragmentProvider.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/CompositePackageFragmentProvider.kt @@ -23,14 +23,19 @@ import java.util.* import org.jetbrains.kotlin.name.Name class CompositePackageFragmentProvider(// can be modified from outside - private val providers: List) : PackageFragmentProvider { + private val providers: List +) : PackageFragmentProvider { - override fun getPackageFragments(fqName: FqName): List { - val result = ArrayList() - for (provider in providers) { - result.addAll(provider.getPackageFragments(fqName)) + init { + assert(providers.size == providers.toSet().size) { + "providers.size is ${providers.size} while only ${providers.toSet().size} unique providers" + } + } + + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) { + for (provider in providers) { + provider.collectPackageFragments(fqName, packageFragments) } - return result.toList() } override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection { diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/LazyPackageViewDescriptorImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/LazyPackageViewDescriptorImpl.kt index ad642ef7540..bbd2d20a7e2 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/LazyPackageViewDescriptorImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/LazyPackageViewDescriptorImpl.kt @@ -28,9 +28,9 @@ import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.storage.getValue class LazyPackageViewDescriptorImpl( - override val module: ModuleDescriptorImpl, - override val fqName: FqName, - storageManager: StorageManager + override val module: ModuleDescriptorImpl, + override val fqName: FqName, + storageManager: StorageManager ) : DeclarationDescriptorImpl(Annotations.EMPTY, fqName.shortNameOrSpecial()), PackageViewDescriptor { override val fragments: List by storageManager.createLazyValue { diff --git a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/AbstractDeserializedPackageFragmentProvider.kt b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/AbstractDeserializedPackageFragmentProvider.kt index 275674056fd..218387e21b8 100644 --- a/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/AbstractDeserializedPackageFragmentProvider.kt +++ b/core/deserialization/src/org/jetbrains/kotlin/serialization/deserialization/AbstractDeserializedPackageFragmentProvider.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.PackageFragmentProvider import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.storage.StorageManager +import org.jetbrains.kotlin.utils.addIfNotNull abstract class AbstractDeserializedPackageFragmentProvider( protected val storageManager: StorageManager, @@ -38,7 +39,9 @@ abstract class AbstractDeserializedPackageFragmentProvider( protected abstract fun findPackage(fqName: FqName): DeserializedPackageFragment? - override fun getPackageFragments(fqName: FqName): List = listOfNotNull(fragments(fqName)) + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) { + packageFragments.addIfNotNull(fragments(fqName)) + } override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection = emptySet() } 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 17f245ed3bd..cb5bb24ccf3 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 @@ -15,6 +15,8 @@ import com.intellij.openapi.roots.impl.libraries.LibraryEx import com.intellij.openapi.roots.libraries.Library import com.intellij.openapi.util.ModificationTracker import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.newvfs.NewVirtualFile +import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.PathUtil import com.intellij.util.SmartList @@ -58,6 +60,7 @@ import org.jetbrains.kotlin.platform.konan.NativePlatform import org.jetbrains.kotlin.platform.konan.NativePlatforms import org.jetbrains.kotlin.platform.konan.isNative import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices +import org.jetbrains.kotlin.resolve.jvm.TopPackageNamesProvider import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices import org.jetbrains.kotlin.types.typeUtil.closure import org.jetbrains.kotlin.utils.addIfNotNull @@ -459,9 +462,40 @@ object NotUnderContentRootModuleInfo : IdeaModuleInfo { get() = platform.single().findAnalyzerServices() } +internal open class PoweredLibraryScopeBase(project: Project, classes: Array, sources: Array) : + LibraryScopeBase(project, classes, sources), TopPackageNamesProvider { + + private val entriesVirtualFileSystems: Set? = run { + val fileSystems = mutableSetOf() + for (file in classes + sources) { + val newVirtualFile = file as? NewVirtualFile ?: return@run null + fileSystems.add(newVirtualFile.fileSystem) + } + fileSystems + } + + override val topPackageNames: Set by lazy { + (classes + sources) + .flatMap { it.children.toList() } + .filter(VirtualFile::isDirectory) + .map(VirtualFile::getName) + .toSet() + "" // empty package is always present + } + + override fun contains(file: VirtualFile): Boolean { + ((file as? NewVirtualFile)?.fileSystem)?.let { + if (entriesVirtualFileSystems != null && !entriesVirtualFileSystems.contains(it)) { + return false + } + } + return super.contains(file) + } + +} + @Suppress("EqualsOrHashCode") // DelegatingGlobalSearchScope requires to provide calcHashCode() private class LibraryWithoutSourceScope(project: Project, private val library: Library) : - LibraryScopeBase(project, library.getFiles(OrderRootType.CLASSES), arrayOf()) { + PoweredLibraryScopeBase(project, library.getFiles(OrderRootType.CLASSES), arrayOf()) { override fun getFileRoot(file: VirtualFile): VirtualFile? = myIndex.getClassRootForFile(file) @@ -474,7 +508,7 @@ private class LibraryWithoutSourceScope(project: Project, private val library: L @Suppress("EqualsOrHashCode") // DelegatingGlobalSearchScope requires to provide calcHashCode() private class LibrarySourceScope(project: Project, private val library: Library) : - LibraryScopeBase(project, arrayOf(), library.getFiles(OrderRootType.SOURCES)) { + PoweredLibraryScopeBase(project, arrayOf(), library.getFiles(OrderRootType.SOURCES)) { override fun getFileRoot(file: VirtualFile): VirtualFile? = myIndex.getSourceRootForFile(file) @@ -488,7 +522,7 @@ private class LibrarySourceScope(project: Project, private val library: Library) //TODO: (module refactoring) android sdk has modified scope @Suppress("EqualsOrHashCode") // DelegatingGlobalSearchScope requires to provide calcHashCode() private class SdkScope(project: Project, val sdk: Sdk) : - LibraryScopeBase(project, sdk.rootProvider.getFiles(OrderRootType.CLASSES), arrayOf()) { + PoweredLibraryScopeBase(project, sdk.rootProvider.getFiles(OrderRootType.CLASSES), arrayOf()) { override fun equals(other: Any?) = other is SdkScope && sdk == other.sdk diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/textBuilder/DeserializerForDecompilerBase.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/textBuilder/DeserializerForDecompilerBase.kt index 579d0d43652..4b5371dc693 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/textBuilder/DeserializerForDecompilerBase.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/textBuilder/DeserializerForDecompilerBase.kt @@ -29,8 +29,8 @@ abstract class DeserializerForDecompilerBase(val directoryPackageFqName: FqName) protected val moduleDescriptor: ModuleDescriptorImpl = createDummyModule("module for building decompiled sources") protected val packageFragmentProvider: PackageFragmentProvider = object : PackageFragmentProvider { - override fun getPackageFragments(fqName: FqName): List { - return listOf(createDummyPackageFragment(fqName)) + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) { + packageFragments.add(createDummyPackageFragment(fqName)) } override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection { diff --git a/idea/src/org/jetbrains/kotlin/idea/PluginStartupActivity.java b/idea/src/org/jetbrains/kotlin/idea/PluginStartupActivity.java index dd8cd5ef13c..db2dcdb44d9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/PluginStartupActivity.java +++ b/idea/src/org/jetbrains/kotlin/idea/PluginStartupActivity.java @@ -16,10 +16,13 @@ package org.jetbrains.kotlin.idea; +import com.intellij.ProjectTopics; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootEvent; +import com.intellij.openapi.roots.ModuleRootListener; import com.intellij.openapi.startup.StartupActivity; import com.intellij.openapi.updateSettings.impl.UpdateChecker; import org.jetbrains.annotations.NotNull; @@ -27,6 +30,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory; import org.jetbrains.kotlin.diagnostics.Errors; import org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter; import org.jetbrains.kotlin.js.resolve.diagnostics.ErrorsJs; +import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade; import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm; import org.jetbrains.kotlin.resolve.konan.diagnostics.ErrorsNative; @@ -44,6 +48,13 @@ public class PluginStartupActivity implements StartupActivity { StartupCompatKt.runActivity(project); PluginStartupService.Companion.getInstance(project).register(project); + project.getMessageBus().connect().subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { + @Override + public void rootsChanged(@NotNull ModuleRootEvent event) { + KotlinJavaPsiFacade.getInstance(project).clearPackageCaches(); + } + }); + initializeDiagnostics(); try { diff --git a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/builder/CommonizedPackageFragmentProvider.kt b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/builder/CommonizedPackageFragmentProvider.kt index fb5227e153f..ad0f39bda36 100644 --- a/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/builder/CommonizedPackageFragmentProvider.kt +++ b/native/commonizer/src/org/jetbrains/kotlin/descriptors/commonizer/builder/CommonizedPackageFragmentProvider.kt @@ -17,8 +17,9 @@ internal class CommonizedPackageFragmentProvider : PackageFragmentProvider { this.packageFragments += packageFragment } - override fun getPackageFragments(fqName: FqName): List = - packageFragments.filter { it.fqName == fqName } + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) { + this.packageFragments.filterTo(packageFragments) { it.fqName == fqName } + } override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection = packageFragments.asSequence() diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/res/AndroidPackageFragmentProviderExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/res/AndroidPackageFragmentProviderExtension.kt index 098684956bf..c54a2d5734b 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/res/AndroidPackageFragmentProviderExtension.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/res/AndroidPackageFragmentProviderExtension.kt @@ -34,6 +34,7 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension import org.jetbrains.kotlin.storage.StorageManager +import org.jetbrains.kotlin.utils.addIfNotNull abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProviderExtension { protected abstract fun getLayoutXmlFileManager(project: Project, moduleInfo: ModuleInfo?): AndroidLayoutXmlFileManager? @@ -132,7 +133,8 @@ abstract class AndroidPackageFragmentProviderExtension : PackageFragmentProvider class AndroidSyntheticPackageFragmentProvider( val packages: Map PackageFragmentDescriptor> ) : PackageFragmentProvider { - override fun getPackageFragments(fqName: FqName) = listOfNotNull(packages[fqName]?.invoke()) + override fun collectPackageFragments(fqName: FqName, packageFragments: MutableCollection) = + packageFragments.addIfNotNull(packages[fqName]?.invoke()) override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): List { return packages.asSequence()