From cad2ecbf8f705b1bbdf72aefd5f064e33c6e4507 Mon Sep 17 00:00:00 2001 From: vlad20012 Date: Mon, 22 Aug 2022 13:05:10 +0200 Subject: [PATCH 1/3] Refactor ImplLookup caches Now `TyFingerprint`-based cache is global, but `Ty`-based caches are local. The new design helps with `impl`s filtering based on a crate where located a function in which we infer types --- .../org/rust/lang/core/resolve/ImplLookup.kt | 34 ++----- .../resolve/RsImplIndexAndTypeAliasCache.kt | 95 +++++++++++++++++++ .../lang/core/resolve/indexes/RsImplIndex.kt | 5 +- .../core/resolve/indexes/RsTypeAliasIndex.kt | 1 - 4 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 src/main/kotlin/org/rust/lang/core/resolve/RsImplIndexAndTypeAliasCache.kt diff --git a/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt b/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt index e807368ad5d..f2ecea41b53 100644 --- a/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt +++ b/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt @@ -12,8 +12,6 @@ import org.rust.cargo.project.model.CargoProject import org.rust.lang.core.psi.* import org.rust.lang.core.psi.ext.* import org.rust.lang.core.resolve.SelectionCandidate.* -import org.rust.lang.core.resolve.indexes.RsImplIndex -import org.rust.lang.core.resolve.indexes.RsTypeAliasIndex import org.rust.lang.core.types.* import org.rust.lang.core.types.consts.CtConstParameter import org.rust.lang.core.types.consts.CtInferVar @@ -25,7 +23,6 @@ import org.rust.lang.core.types.ty.* import org.rust.lang.utils.CargoProjectCache import org.rust.openapiext.hitOnTrue import org.rust.openapiext.testAssert -import org.rust.stdext.Cache import org.rust.stdext.buildList import org.rust.stdext.exhaustive import org.rust.stdext.swapRemoveAt @@ -204,22 +201,9 @@ class ImplLookup( private val paramEnv: ParamEnv = ParamEnv.EMPTY ) { // Non-concurrent HashMap and lazy(NONE) are safe here because this class isn't shared between threads - private val traitSelectionCache: Cache> = - if (paramEnv.isEmpty() && cargoProject != null) { - cargoProjectGlobalTraitSelectionCache.getCache(cargoProject) - } else { - // function-local cache is used when [paramEnv] is not empty, i.e. if there are trait bounds - // that affect trait selection - Cache.new() - } - private val findImplsAndTraitsCache: Cache> = - if (cargoProject != null) { - cargoProjectGlobalFindImplsAndTraitsCache.getCache(cargoProject) - } else { - Cache.new() - } - private val implIndexCache: Cache> = Cache.new() - private val typeAliasIndexCache: Cache> = Cache.new() + private val traitSelectionCache: MutableMap> = hashMapOf() + private val findImplsAndTraitsCache: MutableMap> = hashMapOf() + private val indexCache = RsImplIndexAndTypeAliasCache.getInstance(project) private val fnTraits = listOfNotNull(items.Fn, items.FnMut, items.FnOnce) private val fnOnceOutput: RsTypeAlias? by lazy(NONE) { val trait = items.FnOnce ?: return@lazy null @@ -321,7 +305,7 @@ class ImplLookup( } private fun findExplicitImplsWithoutAliases(selfTy: Ty, tyf: TyFingerprint, processor: RsProcessor): Boolean { - val impls = implIndexCache.getOrPut(tyf) { RsImplIndex.findPotentialImpls(project, tyf) } + val impls = indexCache.findPotentialImpls(tyf) return impls.any { cachedImpl -> if (cachedImpl.isNegativeImpl) return@any false val (type, generics, constGenerics) = cachedImpl.typeAndGenerics ?: return@any false @@ -338,9 +322,7 @@ class ImplLookup( if (fingerprint != null) { val set = mutableSetOf(fingerprint) if (processor(fingerprint)) return true - val aliases = typeAliasIndexCache.getOrPut(fingerprint) { - RsTypeAliasIndex.findPotentialAliases(project, fingerprint) - } + val aliases = indexCache.findPotentialAliases(fingerprint) val result = aliases.any { val name = it.name ?: return@any false val aliasFingerprint = TyFingerprint(name) @@ -382,9 +364,7 @@ class ImplLookup( /** return impls for a generic type `impl Trait for T {}` */ private fun findBlanketImpls(): List { - return implIndexCache.getOrPut(TyFingerprint.TYPE_PARAMETER_OR_MACRO_FINGERPRINT) { - RsImplIndex.findPotentialImpls(project, TyFingerprint.TYPE_PARAMETER_OR_MACRO_FINGERPRINT) - } + return indexCache.findPotentialImpls(TyFingerprint.TYPE_PARAMETER_OR_MACRO_FINGERPRINT) } /** @@ -767,7 +747,7 @@ class ImplLookup( } private fun assembleImplCandidatesWithoutAliases(ref: TraitRef, tyf: TyFingerprint, processor: RsProcessor): Boolean { - val impls = implIndexCache.getOrPut(tyf) { RsImplIndex.findPotentialImpls(project, tyf) } + val impls = indexCache.findPotentialImpls(tyf) return impls.any { val candidate = it.trySelectCandidate(ref) candidate != null && processor(candidate) diff --git a/src/main/kotlin/org/rust/lang/core/resolve/RsImplIndexAndTypeAliasCache.kt b/src/main/kotlin/org/rust/lang/core/resolve/RsImplIndexAndTypeAliasCache.kt new file mode 100644 index 00000000000..1c5ddeb38fe --- /dev/null +++ b/src/main/kotlin/org/rust/lang/core/resolve/RsImplIndexAndTypeAliasCache.kt @@ -0,0 +1,95 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.rust.lang.core.resolve + +import com.intellij.openapi.Disposable +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.util.containers.ContainerUtil +import org.rust.lang.core.psi.RustStructureChangeListener +import org.rust.lang.core.psi.rustPsiManager +import org.rust.lang.core.resolve.indexes.RsImplIndex +import org.rust.lang.core.resolve.indexes.RsTypeAliasIndex +import org.rust.lang.core.types.TyFingerprint +import java.util.concurrent.ConcurrentMap +import java.util.concurrent.atomic.AtomicReference + +@Service +class RsImplIndexAndTypeAliasCache(private val project: Project) : Disposable { + // strong key -> soft value maps + private val _implIndexCache: AtomicReference>?> = AtomicReference(null) + private val _typeAliasIndexCache: AtomicReference>?> = AtomicReference(null) + + private val implIndexCache: ConcurrentMap> + get() = _implIndexCache.getOrCreateMap() + + private val typeAliasIndexCache: ConcurrentMap> + get() = _typeAliasIndexCache.getOrCreateMap() + + /** + * This map is actually used is a [Set] (the value is always [placeholder]). + * The only purpose of this set is holding links to [PsiFile]s, so retain them in memory. + * Without this set [PsiFile]s are retained only by [java.lang.ref.WeakReference], hence they are + * quickly collected by GC and then further index lookups work slower. + * + * Note: keys in this map are referenced via [java.lang.ref.SoftReference], so they're also + * can be collected by GC, hence there isn't a memory leak + */ + private val usedPsiFiles: ConcurrentMap = ContainerUtil.createConcurrentSoftMap() + private val placeholder = Any() + + init { + val rustPsiManager = project.rustPsiManager + val connection = project.messageBus.connect(this) + rustPsiManager.subscribeRustStructureChange(connection, object : RustStructureChangeListener { + override fun rustStructureChanged(file: PsiFile?, changedElement: PsiElement?) { + _implIndexCache.getAndSet(null) + _typeAliasIndexCache.getAndSet(null) + } + }) + } + + fun findPotentialImpls(tyf: TyFingerprint): List { + return implIndexCache.getOrPut(tyf) { + RsImplIndex.findPotentialImpls(project, tyf).filter { + retainPsi(it.impl.containingFile) + it.isValid + } + } + } + + fun findPotentialAliases(tyf: TyFingerprint): List { + return typeAliasIndexCache.getOrPut(tyf) { + RsTypeAliasIndex.findPotentialAliases(project, tyf).filter { + retainPsi(it.alias.containingFile) + it.isFreeAndValid + } + } + } + + private fun retainPsi(containingFile: PsiFile) { + usedPsiFiles[containingFile] = placeholder + } + + override fun dispose() {} + + companion object { + fun getInstance(project: Project): RsImplIndexAndTypeAliasCache = + project.service() + + @JvmStatic + private fun AtomicReference?>.getOrCreateMap(): ConcurrentMap { + while (true) { + get()?.let { return it } + val map = ContainerUtil.createConcurrentSoftValueMap() + if (compareAndSet(null, map)) return map + } + } + } +} diff --git a/src/main/kotlin/org/rust/lang/core/resolve/indexes/RsImplIndex.kt b/src/main/kotlin/org/rust/lang/core/resolve/indexes/RsImplIndex.kt index c2a0ae01c18..5f487de3fe2 100644 --- a/src/main/kotlin/org/rust/lang/core/resolve/indexes/RsImplIndex.kt +++ b/src/main/kotlin/org/rust/lang/core/resolve/indexes/RsImplIndex.kt @@ -39,10 +39,7 @@ class RsImplIndex : AbstractStubIndex() { // Note that `getElements` is intentionally used with intermediate collection instead of // `StubIndex.processElements` in order to simplify profiling - return impls.mapNotNull { impl -> - val cachedImpl = RsCachedImplItem.forImpl(impl) - cachedImpl.takeIf { it.isValid } - } + return impls.map { RsCachedImplItem.forImpl(it) } } fun index(stub: RsImplItemStub, sink: IndexSink) { diff --git a/src/main/kotlin/org/rust/lang/core/resolve/indexes/RsTypeAliasIndex.kt b/src/main/kotlin/org/rust/lang/core/resolve/indexes/RsTypeAliasIndex.kt index 84cbe6b59ec..a246a8612ca 100644 --- a/src/main/kotlin/org/rust/lang/core/resolve/indexes/RsTypeAliasIndex.kt +++ b/src/main/kotlin/org/rust/lang/core/resolve/indexes/RsTypeAliasIndex.kt @@ -35,7 +35,6 @@ class RsTypeAliasIndex : AbstractStubIndex() { // `StubIndex.processElements` in order to simplify profiling val aliases = getElements(KEY, tyf, project, RsWithMacrosProjectScope(project)) .map { RsCachedTypeAlias.forAlias(it) } - .filter { it.isFreeAndValid } // This is basically a hack to make some crates (winapi 0.2) work in a reasonable amount of time. // If the number of aliases exceeds the threshold, we prefer ones from stdlib and a workspace over From 87fbf673cf1a165ee826718f7935e3310e906b40 Mon Sep 17 00:00:00 2001 From: vlad20012 Date: Tue, 6 Sep 2022 16:17:21 +0200 Subject: [PATCH 2/3] Allow to retrieve all crates where `RsFile` is included to --- .../kotlin/org/rust/lang/core/psi/RsFile.kt | 36 ++++++++++++++----- .../lang/core/resolve/RsCachedImplItem.kt | 4 ++- .../lang/core/resolve/RsCachedTypeAlias.kt | 4 ++- .../rust/lang/core/resolve2/FacadeResolve.kt | 23 +++++------- 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/org/rust/lang/core/psi/RsFile.kt b/src/main/kotlin/org/rust/lang/core/psi/RsFile.kt index 1f9e54853bd..96cb19a19fd 100644 --- a/src/main/kotlin/org/rust/lang/core/psi/RsFile.kt +++ b/src/main/kotlin/org/rust/lang/core/psi/RsFile.kt @@ -42,6 +42,7 @@ import org.rust.lang.core.macros.macroExpansionManager import org.rust.lang.core.psi.ext.* import org.rust.lang.core.resolve.DEFAULT_RECURSION_LIMIT import org.rust.lang.core.resolve.ref.RsReference +import org.rust.lang.core.resolve2.ModData import org.rust.lang.core.resolve2.findModDataFor import org.rust.lang.core.resolve2.toRsMod import org.rust.lang.core.stubs.RsFileStub @@ -75,6 +76,7 @@ class RsFile( val cargoProject: CargoProject? get() = cachedData.cargoProject val cargoWorkspace: CargoWorkspace? get() = cachedData.cargoWorkspace val crate: Crate? get() = cachedData.crate + val crates: List get() = cachedData.crates override val crateRoot: RsMod? get() = cachedData.crateRoot val isDeeplyEnabledByCfg: Boolean get() = cachedData.isDeeplyEnabledByCfg val isIncludedByIncludeMacro: Boolean get() = cachedData.isIncludedByIncludeMacro @@ -114,20 +116,25 @@ class RsFile( crate.cargoWorkspace, crate.rootMod, crate, + crates = listOf(crate), isDeeplyEnabledByCfg = true, // Macros are ony expanded in cfg-enabled mods isIncludedByIncludeMacro = false, // An expansion file obviously can't be included ) } // Note: `this` file can be not a module (can be included with `include!()` macro) - val modData = findModDataFor(this) + val allModData = findModDataFor(this) + val modData = allModData.pickSingleModData() if (modData != null) { - val crate = project.crateGraph.findCrateById(modData.crate) ?: return EMPTY_CACHED_DATA + val crateGraph = project.crateGraph + val crates = allModData.mapNotNull { crateGraph.findCrateById(it.crate) } + val crate = crates.find { it.id == modData.crate } ?: return EMPTY_CACHED_DATA return CachedData( crate.cargoProject, crate.cargoWorkspace, crate.rootMod, crate, + crates, modData.isDeeplyEnabledByCfg, isIncludedByIncludeMacro = virtualFile is VirtualFileWithId && virtualFile.id != modData.fileId, ) @@ -173,7 +180,7 @@ class RsFile( override val `super`: RsMod? get() { - val modData = findModDataFor(this) ?: return null + val modData = findModDataFor(this).pickSingleModData() ?: return null val parenModData = modData.parent ?: return null return parenModData.toRsMod(project).firstOrNull() } @@ -296,12 +303,23 @@ private data class CachedData( val cargoWorkspace: CargoWorkspace? = null, val crateRoot: RsFile? = null, val crate: Crate? = null, + val crates: List = emptyList(), val isDeeplyEnabledByCfg: Boolean = true, val isIncludedByIncludeMacro: Boolean = false, ) private val EMPTY_CACHED_DATA: CachedData = CachedData() +// A rust file can be included in multiple places, but currently IntelliJ Rust works properly only with one +// inclusion point, so we have to choose one +private fun List.pickSingleModData(): ModData? { + if (isEmpty()) return null + singleOrNull()?.let { return it } + + // If there are still multiple options, choose one deterministically + return minByOrNull { it.crate } +} + private fun VirtualFile.getInjectedFromIfDoctestInjection(project: Project): RsFile? { if (!isDoctestInjection(project)) return null return ((this as? VirtualFileWindow)?.delegate?.toPsiFile(project) as? RsFile) @@ -324,14 +342,14 @@ private val CACHED_DATA_KEY: Key> = Key.create("CACHED_D val RsElement.isValidProjectMember: Boolean get() = isValidProjectMemberAndContainingCrate.first -val RsElement.isValidProjectMemberAndContainingCrate: Pair +val RsElement.isValidProjectMemberAndContainingCrate: Triple> get() { - val file = containingRsFileSkippingCodeFragments ?: return true to null - if (!file.isDeeplyEnabledByCfg) return false to null - val crate = file.crate ?: return false to null - if (!existsAfterExpansion(crate)) return false to null + val file = containingRsFileSkippingCodeFragments ?: return Triple(true, null, emptyList()) + if (!file.isDeeplyEnabledByCfg) return Triple(false, null, emptyList()) + val crate = file.crate ?: return Triple(false, null, emptyList()) + if (!existsAfterExpansion(crate)) return Triple(false, null, emptyList()) - return true to crate + return Triple(true, crate, file.crates) } /** Usually used to filter out test/bench non-workspace crates */ diff --git a/src/main/kotlin/org/rust/lang/core/resolve/RsCachedImplItem.kt b/src/main/kotlin/org/rust/lang/core/resolve/RsCachedImplItem.kt index 24a782a8e46..3b32d3e2926 100644 --- a/src/main/kotlin/org/rust/lang/core/resolve/RsCachedImplItem.kt +++ b/src/main/kotlin/org/rust/lang/core/resolve/RsCachedImplItem.kt @@ -32,12 +32,14 @@ class RsCachedImplItem( ) { private val traitRef: RsTraitRef? = impl.traitRef val containingCrate: Crate? + val containingCrates: List val isValid: Boolean val isNegativeImpl: Boolean = impl.isNegativeImpl init { - val (isValid, crate) = impl.isValidProjectMemberAndContainingCrate + val (isValid, crate, crates) = impl.isValidProjectMemberAndContainingCrate this.containingCrate = crate + this.containingCrates = crates this.isValid = isValid && !impl.isReservationImpl } diff --git a/src/main/kotlin/org/rust/lang/core/resolve/RsCachedTypeAlias.kt b/src/main/kotlin/org/rust/lang/core/resolve/RsCachedTypeAlias.kt index 0abf22b07bf..f836c2ecaf6 100644 --- a/src/main/kotlin/org/rust/lang/core/resolve/RsCachedTypeAlias.kt +++ b/src/main/kotlin/org/rust/lang/core/resolve/RsCachedTypeAlias.kt @@ -30,10 +30,12 @@ class RsCachedTypeAlias( val isFreeAndValid: Boolean val containingCrate: Crate? + val containingCrates: List init { - val (isValid, crate) = alias.isValidProjectMemberAndContainingCrate + val (isValid, crate, crates) = alias.isValidProjectMemberAndContainingCrate this.containingCrate = crate + this.containingCrates = crates this.isFreeAndValid = isValid && name != null && alias.owner is RsAbstractableOwner.Free diff --git a/src/main/kotlin/org/rust/lang/core/resolve2/FacadeResolve.kt b/src/main/kotlin/org/rust/lang/core/resolve2/FacadeResolve.kt index 647bacc36bb..e793f20b4a8 100644 --- a/src/main/kotlin/org/rust/lang/core/resolve2/FacadeResolve.kt +++ b/src/main/kotlin/org/rust/lang/core/resolve2/FacadeResolve.kt @@ -652,11 +652,11 @@ private fun ModData.toRsModNullable(project: Project): List { private inline fun RsItemsOwner.getExpandedItemsWithName(name: String): List = expandedItemsCached.named[name]?.filterIsInstance() ?: emptyList() -fun findModDataFor(file: RsFile): ModData? { +fun findModDataFor(file: RsFile): List { val project = file.project val defMapService = project.defMapService - val virtualFile = file.virtualFile ?: return null - if (virtualFile !is VirtualFileWithId) return null + val virtualFile = file.virtualFile ?: return emptyList() + if (virtualFile !is VirtualFileWithId) return emptyList() if (!defMapService.areAllDefMapsUpToDate()) { // Ensure def maps are up-to-date (`findCrates` may return an old crate if def maps haven't updated) @@ -675,22 +675,17 @@ fun findModDataFor(file: RsFile): ModData? { } } - return defMapService + val rawList = defMapService .findCrates(file) .mapNotNull { crateId -> val defMap = defMapService.getOrUpdateIfNeeded(crateId) ?: return@mapNotNull null val fileInfo = defMap.fileInfos[virtualFile.id] ?: return@mapNotNull null fileInfo.modData } - .pickSingleModData() -} - -private fun List.pickSingleModData(): ModData? { - singleOrNull()?.let { return it } - - val cfgEnabled = filter { it.isDeeplyEnabledByCfg }.ifEmpty { this } - cfgEnabled.singleOrNull()?.let { return it } - // If after filtering cfg-enabled modules there are still multiple options, choose one deterministically - return cfgEnabled.minByOrNull { it.crate } + return if (rawList.size == 1) { + rawList + } else { + rawList.filter { it.isDeeplyEnabledByCfg }.ifEmpty { rawList } + } } From f83d6b30238fb1731b02e2c850338a1f8e0152b1 Mon Sep 17 00:00:00 2001 From: vlad20012 Date: Mon, 22 Aug 2022 18:12:59 +0200 Subject: [PATCH 3/3] TY&RES: ignore `impl`s from non-dependency crates --- .../kotlin/org/rust/lang/core/crate/Crate.kt | 3 + .../org/rust/lang/core/resolve/ImplLookup.kt | 39 ++++++++----- .../resolve/RsImplIndexAndTypeAliasCache.kt | 4 +- .../lang/core/types/infer/TypeInference.kt | 2 +- .../org/rust/lang/utils/CargoProjectCache.kt | 58 ------------------- .../core/resolve/RsStubOnlyResolveTest.kt | 40 +++++++++++++ .../RsGenericExpressionTypeInferenceTest.kt | 25 ++++++++ 7 files changed, 94 insertions(+), 77 deletions(-) delete mode 100644 src/main/kotlin/org/rust/lang/utils/CargoProjectCache.kt diff --git a/src/main/kotlin/org/rust/lang/core/crate/Crate.kt b/src/main/kotlin/org/rust/lang/core/crate/Crate.kt index a53a728d23e..10d7efb9063 100644 --- a/src/main/kotlin/org/rust/lang/core/crate/Crate.kt +++ b/src/main/kotlin/org/rust/lang/core/crate/Crate.kt @@ -112,3 +112,6 @@ interface Crate : UserDataHolderEx { fun Crate.findDependency(normName: String): Crate? = dependencies.find { it.normName == normName }?.crate + +fun Crate.hasTransitiveDependencyOrSelf(other: Crate): Boolean = + other == this || other in flatDependencies diff --git a/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt b/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt index f2ecea41b53..566f580ac14 100644 --- a/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt +++ b/src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt @@ -8,7 +8,8 @@ package org.rust.lang.core.resolve import com.intellij.openapi.project.Project import com.intellij.util.SmartList import gnu.trove.THashMap -import org.rust.cargo.project.model.CargoProject +import org.rust.lang.core.crate.Crate +import org.rust.lang.core.crate.hasTransitiveDependencyOrSelf import org.rust.lang.core.psi.* import org.rust.lang.core.psi.ext.* import org.rust.lang.core.resolve.SelectionCandidate.* @@ -20,7 +21,6 @@ import org.rust.lang.core.types.infer.* import org.rust.lang.core.types.infer.TypeInferenceMarks.WinnowParamCandidateLoses import org.rust.lang.core.types.infer.TypeInferenceMarks.WinnowParamCandidateWins import org.rust.lang.core.types.ty.* -import org.rust.lang.utils.CargoProjectCache import org.rust.openapiext.hitOnTrue import org.rust.openapiext.testAssert import org.rust.stdext.buildList @@ -180,7 +180,7 @@ data class ParamEnv(val callerBounds: List) { 1 -> return ParamEnv(rawBounds) } - val lookup = ImplLookup(decl.project, decl.cargoProject, decl.knownItems, ParamEnv(rawBounds)) + val lookup = ImplLookup(decl.project, decl.containingCrate, decl.knownItems, ParamEnv(rawBounds)) val ctx = lookup.ctx val bounds2 = rawBounds.map { val (bound, obligations) = ctx.normalizeAssociatedTypesIn(it) @@ -196,7 +196,7 @@ data class ParamEnv(val callerBounds: List) { class ImplLookup( private val project: Project, - cargoProject: CargoProject?, + private val containingCrate: Crate?, val items: KnownItems, private val paramEnv: ParamEnv = ParamEnv.EMPTY ) { @@ -305,7 +305,7 @@ class ImplLookup( } private fun findExplicitImplsWithoutAliases(selfTy: Ty, tyf: TyFingerprint, processor: RsProcessor): Boolean { - val impls = indexCache.findPotentialImpls(tyf) + val impls = findPotentialImpls(tyf) return impls.any { cachedImpl -> if (cachedImpl.isNegativeImpl) return@any false val (type, generics, constGenerics) = cachedImpl.typeAndGenerics ?: return@any false @@ -322,7 +322,7 @@ class ImplLookup( if (fingerprint != null) { val set = mutableSetOf(fingerprint) if (processor(fingerprint)) return true - val aliases = indexCache.findPotentialAliases(fingerprint) + val aliases = findPotentialAliases(fingerprint) val result = aliases.any { val name = it.name ?: return@any false val aliasFingerprint = TyFingerprint(name) @@ -337,6 +337,19 @@ class ImplLookup( return processor(TyFingerprint.TYPE_PARAMETER_OR_MACRO_FINGERPRINT) } + private fun findPotentialImpls(tyf: TyFingerprint): Sequence = + indexCache.findPotentialImpls(tyf) + .asSequence() + .filter { useImplsFromCrate(it.containingCrates) } + + private fun findPotentialAliases(tyf: TyFingerprint) = + indexCache.findPotentialAliases(tyf) + .asSequence() + .filter { useImplsFromCrate(it.containingCrates) } + + private fun useImplsFromCrate(crates: List): Boolean = + containingCrate == null || crates.any { containingCrate.hasTransitiveDependencyOrSelf(it) } + private fun canCombineTypes( ty1: Ty, ty2: Ty, @@ -363,8 +376,8 @@ class ImplLookup( } /** return impls for a generic type `impl Trait for T {}` */ - private fun findBlanketImpls(): List { - return indexCache.findPotentialImpls(TyFingerprint.TYPE_PARAMETER_OR_MACRO_FINGERPRINT) + private fun findBlanketImpls(): Sequence { + return findPotentialImpls(TyFingerprint.TYPE_PARAMETER_OR_MACRO_FINGERPRINT) } /** @@ -747,7 +760,7 @@ class ImplLookup( } private fun assembleImplCandidatesWithoutAliases(ref: TraitRef, tyf: TyFingerprint, processor: RsProcessor): Boolean { - val impls = indexCache.findPotentialImpls(tyf) + val impls = findPotentialImpls(tyf) return impls.any { val candidate = it.trySelectCandidate(ref) candidate != null && processor(candidate) @@ -1268,14 +1281,8 @@ class ImplLookup( } else { ParamEnv.EMPTY } - return ImplLookup(psi.project, psi.cargoProject, psi.knownItems, paramEnv) + return ImplLookup(psi.project, psi.containingCrate, psi.knownItems, paramEnv) } - - private val cargoProjectGlobalFindImplsAndTraitsCache = - CargoProjectCache>("cargoProjectGlobalFindImplsAndTraitsCache") - - private val cargoProjectGlobalTraitSelectionCache = - CargoProjectCache>("cargoProjectGlobalTraitSelectionCache") } } diff --git a/src/main/kotlin/org/rust/lang/core/resolve/RsImplIndexAndTypeAliasCache.kt b/src/main/kotlin/org/rust/lang/core/resolve/RsImplIndexAndTypeAliasCache.kt index 1c5ddeb38fe..db1a23eb732 100644 --- a/src/main/kotlin/org/rust/lang/core/resolve/RsImplIndexAndTypeAliasCache.kt +++ b/src/main/kotlin/org/rust/lang/core/resolve/RsImplIndexAndTypeAliasCache.kt @@ -80,11 +80,11 @@ class RsImplIndexAndTypeAliasCache(private val project: Project) : Disposable { override fun dispose() {} companion object { - fun getInstance(project: Project): RsImplIndexAndTypeAliasCache = + fun getInstance(project: Project): RsImplIndexAndTypeAliasCache = project.service() @JvmStatic - private fun AtomicReference?>.getOrCreateMap(): ConcurrentMap { + private fun AtomicReference?>.getOrCreateMap(): ConcurrentMap { while (true) { get()?.let { return it } val map = ContainerUtil.createConcurrentSoftValueMap() diff --git a/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt b/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt index a89d01d6824..3c6afb415fa 100644 --- a/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt +++ b/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt @@ -35,7 +35,7 @@ import org.rust.stdext.RsResult.Ok fun inferTypesIn(element: RsInferenceContextOwner): RsInferenceResult { val items = element.knownItems val paramEnv = if (element is RsGenericDeclaration) ParamEnv.buildFor(element) else ParamEnv.EMPTY - val lookup = ImplLookup(element.project, element.cargoProject, items, paramEnv) + val lookup = ImplLookup(element.project, element.containingCrate, items, paramEnv) return recursionGuard(element, { lookup.ctx.infer(element) }, memoize = false) ?: error("Can not run nested type inference") } diff --git a/src/main/kotlin/org/rust/lang/utils/CargoProjectCache.kt b/src/main/kotlin/org/rust/lang/utils/CargoProjectCache.kt deleted file mode 100644 index 4654198b2d1..00000000000 --- a/src/main/kotlin/org/rust/lang/utils/CargoProjectCache.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Use of this source code is governed by the MIT license that can be - * found in the LICENSE file. - */ - -package org.rust.lang.utils - -import com.intellij.openapi.util.Key -import com.intellij.psi.util.CachedValue -import com.intellij.psi.util.CachedValueProvider -import com.intellij.psi.util.CachedValuesManager -import com.intellij.util.containers.ContainerUtil -import org.rust.cargo.project.model.CargoProject -import org.rust.lang.core.psi.rustStructureModificationTracker -import org.rust.stdext.Cache -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap - -/** - * Similar to [org.rust.openapiext.ProjectCache], but based on [CargoProject] - */ -class CargoProjectCache( - cacheName: String, - private val dependencyGetter: (CargoProject) -> Any = { it.project.rustStructureModificationTracker } -) { - init { - if (!registered.add(cacheName)) { - error(""" - CargoProjectCache `$cacheName` is already registered. - Make sure ProjectCache is static, that is, put it inside companion object. - """.trimIndent()) - } - } - - private val cacheKey: Key>> = Key.create(cacheName) - - fun getOrPut(project: CargoProject, key: T, defaultValue: () -> R): R { - val cache = getCacheInternal(project) - return cache.getOrPut(key) { defaultValue() } - } - - fun getCache(project: CargoProject): Cache = - Cache.fromConcurrentMap(getCacheInternal(project)) - - private fun getCacheInternal(project: CargoProject): ConcurrentMap { - return CachedValuesManager.getManager(project.project) - .getCachedValue(project, cacheKey, { - CachedValueProvider.Result.create( - ConcurrentHashMap(), - dependencyGetter(project) - ) - }, false) - } - - companion object { - private val registered = ContainerUtil.newConcurrentSet() - } -} diff --git a/src/test/kotlin/org/rust/lang/core/resolve/RsStubOnlyResolveTest.kt b/src/test/kotlin/org/rust/lang/core/resolve/RsStubOnlyResolveTest.kt index b7eeac2cac1..c30bc0f197c 100644 --- a/src/test/kotlin/org/rust/lang/core/resolve/RsStubOnlyResolveTest.kt +++ b/src/test/kotlin/org/rust/lang/core/resolve/RsStubOnlyResolveTest.kt @@ -892,4 +892,44 @@ class RsStubOnlyResolveTest : RsResolveTestBase() { pub fn func() {} } """) + + fun `test method is not resolved to independent crate 1`() = stubOnlyResolve(""" + //- lib.rs + pub struct Foo; + + fn foo() { + Foo.bar(); + } //^ unresolved + //- main.rs + use test_package::*; + + impl Foo { // Incoherent impl + fn bar(&self) {} + } + """) + + fun `test method is not resolved to independent crate 2`() = stubOnlyResolve(""" + //- bar.rs + pub struct Foo; + pub trait Bar { + fn bar(&self); + } + //- lib.rs + mod bar; + pub use bar::*; + + impl Bar for Foo { + fn bar(&self) {} + } + + fn foo() { + Foo.bar(); + } //^ lib.rs + //- main.rs + use test_package::*; + + impl Bar for Foo { // Incoherent impl + fn bar(&self) {} + } + """) } diff --git a/src/test/kotlin/org/rust/lang/core/type/RsGenericExpressionTypeInferenceTest.kt b/src/test/kotlin/org/rust/lang/core/type/RsGenericExpressionTypeInferenceTest.kt index b66713fdb07..b993552f099 100644 --- a/src/test/kotlin/org/rust/lang/core/type/RsGenericExpressionTypeInferenceTest.kt +++ b/src/test/kotlin/org/rust/lang/core/type/RsGenericExpressionTypeInferenceTest.kt @@ -2240,4 +2240,29 @@ class RsGenericExpressionTypeInferenceTest : RsTypificationTestBase() { a; } //^ X """) + + fun `test ignore impl in independent crate`() = stubOnlyTypeInfer(""" + //- lib.rs + pub struct Foo; + pub trait Bar { + type Item; + } + + impl Bar for Foo { + type Item = i32; + } + + fn infer(_: T) -> T::Item { todo!() } + + fn foo() { + let a = infer(Foo); + a; + } //^ i32 + //- main.rs + use test_package::*; + + impl Bar for Foo { // Incoherent impl + type Item = u8; + } + """) }