Skip to content

Commit

Permalink
Merge #9229
Browse files Browse the repository at this point in the history
9229: TY&RES: ignore `impl`s from non-dependency crates r=dima74 a=vlad20012



Co-authored-by: vlad20012 <beskvlad@gmail.com>
  • Loading branch information
bors[bot] and vlad20012 committed Sep 7, 2022
2 parents dc688e2 + f83d6b3 commit e03e2e1
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 128 deletions.
3 changes: 3 additions & 0 deletions src/main/kotlin/org/rust/lang/core/crate/Crate.kt
Expand Up @@ -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
36 changes: 27 additions & 9 deletions src/main/kotlin/org/rust/lang/core/psi/RsFile.kt
Expand Up @@ -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
Expand Down Expand Up @@ -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<Crate> get() = cachedData.crates
override val crateRoot: RsMod? get() = cachedData.crateRoot
val isDeeplyEnabledByCfg: Boolean get() = cachedData.isDeeplyEnabledByCfg
val isIncludedByIncludeMacro: Boolean get() = cachedData.isIncludedByIncludeMacro
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -296,12 +303,23 @@ private data class CachedData(
val cargoWorkspace: CargoWorkspace? = null,
val crateRoot: RsFile? = null,
val crate: Crate? = null,
val crates: List<Crate> = 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<ModData>.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)
Expand All @@ -324,14 +342,14 @@ private val CACHED_DATA_KEY: Key<CachedValue<CachedData>> = Key.create("CACHED_D
val RsElement.isValidProjectMember: Boolean
get() = isValidProjectMemberAndContainingCrate.first

val RsElement.isValidProjectMemberAndContainingCrate: Pair<Boolean, Crate?>
val RsElement.isValidProjectMemberAndContainingCrate: Triple<Boolean, Crate?, List<Crate>>
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 */
Expand Down
65 changes: 26 additions & 39 deletions src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
Expand Up @@ -8,12 +8,11 @@ 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.*
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
Expand All @@ -22,10 +21,8 @@ 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.Cache
import org.rust.stdext.buildList
import org.rust.stdext.exhaustive
import org.rust.stdext.swapRemoveAt
Expand Down Expand Up @@ -183,7 +180,7 @@ data class ParamEnv(val callerBounds: List<TraitRef>) {
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)
Expand All @@ -199,27 +196,14 @@ data class ParamEnv(val callerBounds: List<TraitRef>) {

class ImplLookup(
private val project: Project,
cargoProject: CargoProject?,
private val containingCrate: Crate?,
val items: KnownItems,
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<TraitRef, SelectionResult<SelectionCandidate>> =
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<Ty, List<TraitImplSource>> =
if (cargoProject != null) {
cargoProjectGlobalFindImplsAndTraitsCache.getCache(cargoProject)
} else {
Cache.new()
}
private val implIndexCache: Cache<TyFingerprint, List<RsCachedImplItem>> = Cache.new()
private val typeAliasIndexCache: Cache<TyFingerprint, List<RsCachedTypeAlias>> = Cache.new()
private val traitSelectionCache: MutableMap<TraitRef, SelectionResult<SelectionCandidate>> = hashMapOf()
private val findImplsAndTraitsCache: MutableMap<Ty, List<TraitImplSource>> = 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
Expand Down Expand Up @@ -321,7 +305,7 @@ class ImplLookup(
}

private fun findExplicitImplsWithoutAliases(selfTy: Ty, tyf: TyFingerprint, processor: RsProcessor<RsCachedImplItem>): Boolean {
val impls = implIndexCache.getOrPut(tyf) { RsImplIndex.findPotentialImpls(project, tyf) }
val impls = findPotentialImpls(tyf)
return impls.any { cachedImpl ->
if (cachedImpl.isNegativeImpl) return@any false
val (type, generics, constGenerics) = cachedImpl.typeAndGenerics ?: return@any false
Expand All @@ -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 = findPotentialAliases(fingerprint)
val result = aliases.any {
val name = it.name ?: return@any false
val aliasFingerprint = TyFingerprint(name)
Expand All @@ -355,6 +337,19 @@ class ImplLookup(
return processor(TyFingerprint.TYPE_PARAMETER_OR_MACRO_FINGERPRINT)
}

private fun findPotentialImpls(tyf: TyFingerprint): Sequence<RsCachedImplItem> =
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<Crate>): Boolean =
containingCrate == null || crates.any { containingCrate.hasTransitiveDependencyOrSelf(it) }

private fun canCombineTypes(
ty1: Ty,
ty2: Ty,
Expand All @@ -381,10 +376,8 @@ class ImplLookup(
}

/** return impls for a generic type `impl<T> Trait for T {}` */
private fun findBlanketImpls(): List<RsCachedImplItem> {
return implIndexCache.getOrPut(TyFingerprint.TYPE_PARAMETER_OR_MACRO_FINGERPRINT) {
RsImplIndex.findPotentialImpls(project, TyFingerprint.TYPE_PARAMETER_OR_MACRO_FINGERPRINT)
}
private fun findBlanketImpls(): Sequence<RsCachedImplItem> {
return findPotentialImpls(TyFingerprint.TYPE_PARAMETER_OR_MACRO_FINGERPRINT)
}

/**
Expand Down Expand Up @@ -767,7 +760,7 @@ class ImplLookup(
}

private fun assembleImplCandidatesWithoutAliases(ref: TraitRef, tyf: TyFingerprint, processor: RsProcessor<SelectionCandidate>): Boolean {
val impls = implIndexCache.getOrPut(tyf) { RsImplIndex.findPotentialImpls(project, tyf) }
val impls = findPotentialImpls(tyf)
return impls.any {
val candidate = it.trySelectCandidate(ref)
candidate != null && processor(candidate)
Expand Down Expand Up @@ -1288,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<Ty, List<TraitImplSource>>("cargoProjectGlobalFindImplsAndTraitsCache")

private val cargoProjectGlobalTraitSelectionCache =
CargoProjectCache<TraitRef, SelectionResult<SelectionCandidate>>("cargoProjectGlobalTraitSelectionCache")
}
}

Expand Down
Expand Up @@ -32,12 +32,14 @@ class RsCachedImplItem(
) {
private val traitRef: RsTraitRef? = impl.traitRef
val containingCrate: Crate?
val containingCrates: List<Crate>
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
}

Expand Down
Expand Up @@ -30,10 +30,12 @@ class RsCachedTypeAlias(
val isFreeAndValid: Boolean

val containingCrate: Crate?
val containingCrates: List<Crate>

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
Expand Down
@@ -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<ConcurrentMap<TyFingerprint, List<RsCachedImplItem>>?> = AtomicReference(null)
private val _typeAliasIndexCache: AtomicReference<ConcurrentMap<TyFingerprint, List<RsCachedTypeAlias>>?> = AtomicReference(null)

private val implIndexCache: ConcurrentMap<TyFingerprint, List<RsCachedImplItem>>
get() = _implIndexCache.getOrCreateMap()

private val typeAliasIndexCache: ConcurrentMap<TyFingerprint, List<RsCachedTypeAlias>>
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<PsiFile, Any> = 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<RsCachedImplItem> {
return implIndexCache.getOrPut(tyf) {
RsImplIndex.findPotentialImpls(project, tyf).filter {
retainPsi(it.impl.containingFile)
it.isValid
}
}
}

fun findPotentialAliases(tyf: TyFingerprint): List<RsCachedTypeAlias> {
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 <T : Any> AtomicReference<ConcurrentMap<TyFingerprint, T>?>.getOrCreateMap(): ConcurrentMap<TyFingerprint, T> {
while (true) {
get()?.let { return it }
val map = ContainerUtil.createConcurrentSoftValueMap<TyFingerprint, T>()
if (compareAndSet(null, map)) return map
}
}
}
}
Expand Up @@ -39,10 +39,7 @@ class RsImplIndex : AbstractStubIndex<TyFingerprint, RsImplItem>() {

// 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) {
Expand Down
Expand Up @@ -35,7 +35,6 @@ class RsTypeAliasIndex : AbstractStubIndex<TyFingerprint, RsTypeAlias>() {
// `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
Expand Down

0 comments on commit e03e2e1

Please sign in to comment.