Skip to content

Commit

Permalink
PERF: introduce RsCachedImplItem
Browse files Browse the repository at this point in the history
This optimizes trait selection and method resolve by reducing
cache and PSI tree access. The idea is to combine all stuff
related to impl item (implemented trait, type, crate root, etc)
into one data class and place it to the cache (with impl item
as a cache key)
  • Loading branch information
vlad20012 committed Jul 28, 2019
1 parent 0a3349f commit fcc0724
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 32 deletions.
Expand Up @@ -238,9 +238,8 @@ class AutoImportFix(element: RsElement) : LocalQuickFixOnPsiElement(element), Hi
val traits = sources.mapNotNull { source ->
val trait = when (source) {
is TraitImplSource.ExplicitImpl -> {
val impl = source.value
val traitRef = impl.traitRef ?: return null
traitRef.resolveToTrait() ?: return@mapNotNull null
if (source.isInherent) return null
source.implementedTrait?.element ?: return@mapNotNull null
}
is TraitImplSource.Derived -> source.value
is TraitImplSource.Collapsed -> source.value
Expand Down
52 changes: 33 additions & 19 deletions src/main/kotlin/org/rust/lang/core/resolve/ImplLookup.kt
Expand Up @@ -105,25 +105,38 @@ val HARDCODED_FROM_IMPLS_MAP: Map<TyPrimitive, List<TyPrimitive>> = run {
sealed class TraitImplSource {
abstract val value: RsTraitOrImpl

open val implementedTrait: BoundElement<RsTraitItem>? get() = value.implementedTrait
open val members: RsMembers? get() = value.members

val impl: RsImplItem?
get() = (this as? ExplicitImpl)?.value

/** An impl block, directly defined in the code */
data class ExplicitImpl(override val value: RsImplItem): TraitImplSource()
data class ExplicitImpl(private val cachedImpl: RsCachedImplItem) : TraitImplSource() {
override val value: RsImplItem get() = cachedImpl.impl
val isInherent: Boolean get() = cachedImpl.traitRef == null
override val implementedTrait: BoundElement<RsTraitItem>? get() = cachedImpl.implementedTrait
override val members: RsMembers? get() = cachedImpl.membres
}

/** T: Trait */
data class TraitBound(override val value: RsTraitItem): TraitImplSource()
data class TraitBound(override val value: RsTraitItem) : TraitImplSource()

/** Trait is implemented for item via ```#[derive]``` attribute. */
data class Derived(override val value: RsTraitItem): TraitImplSource()
data class Derived(override val value: RsTraitItem) : TraitImplSource()

/** dyn/impl Trait or a closure */
data class Object(override val value: RsTraitItem): TraitImplSource()
data class Object(override val value: RsTraitItem) : TraitImplSource()

/**
* Used only as a result of method pick. It means that method is resolved to multiple impls of the same trait
* (with different type parameter values), so we collapsed all impls to that trait. Specific impl
* will be selected during type inference.
*/
data class Collapsed(override val value: RsTraitItem): TraitImplSource()
data class Collapsed(override val value: RsTraitItem) : TraitImplSource()

/** A trait impl hardcoded in Intellij-Rust. Mostly it's something defined with a macro in stdlib */
data class Hardcoded(override val value: RsTraitItem): TraitImplSource()
data class Hardcoded(override val value: RsTraitItem) : TraitImplSource()
}

/**
Expand Down Expand Up @@ -207,7 +220,7 @@ class ImplLookup(
}

val ctx: RsInferenceContext by lazy(NONE) {
RsInferenceContext(this, items)
RsInferenceContext(project, this, items)
}

fun getEnvBoundTransitivelyFor(ty: Ty): Sequence<BoundElement<RsTraitItem>> {
Expand Down Expand Up @@ -348,12 +361,13 @@ class ImplLookup(
return impls
}

private fun findSimpleImpls(selfTy: Ty): Sequence<RsImplItem> {
return RsImplIndex.findPotentialImpls(project, selfTy).mapNotNull { impl ->
val subst = impl.generics.associate { it to ctx.typeVarForParam(it) }.toTypeSubst()
private fun findSimpleImpls(selfTy: Ty): Sequence<RsCachedImplItem> {
return RsImplIndex.findPotentialImpls(project, selfTy).mapNotNull { cachedImpl ->
val (type, generics) = cachedImpl.typeAndGenerics ?: return@mapNotNull null
val subst = generics.associateWith { ctx.typeVarForParam(it) }.toTypeSubst()
// TODO: take into account the lifetimes (?)
val formalSelfTy = impl.typeReference?.type?.substitute(subst) ?: return@mapNotNull null
impl.takeIf { ctx.canCombineTypes(formalSelfTy, selfTy) }
val formalSelfTy = type.substitute(subst)
cachedImpl.takeIf { ctx.canCombineTypes(formalSelfTy, selfTy) }
}
}

Expand Down Expand Up @@ -500,15 +514,15 @@ class ImplLookup(
.toList()
}

private fun Sequence<RsImplItem>.assembleImplCandidates(ref: TraitRef): Sequence<SelectionCandidate> =
mapNotNull { impl ->
val formalTraitRef = impl.implementedTrait ?: return@mapNotNull null
private fun Sequence<RsCachedImplItem>.assembleImplCandidates(ref: TraitRef): Sequence<SelectionCandidate> =
mapNotNull { cachedImpl ->
val formalTraitRef = cachedImpl.implementedTrait ?: return@mapNotNull null
if (formalTraitRef.element != ref.trait.element) return@mapNotNull null
val formalSelfTy = impl.typeReference?.type ?: return@mapNotNull null
val (formalSelfTy, generics) = cachedImpl.typeAndGenerics ?: return@mapNotNull null
val (_, implTraitRef) =
prepareSubstAndTraitRefRaw(ctx, impl.generics, formalSelfTy, formalTraitRef, ref.selfTy)
prepareSubstAndTraitRefRaw(ctx, generics, formalSelfTy, formalTraitRef, ref.selfTy)
if (!ctx.probe { ctx.combineTraitRefs(implTraitRef, ref) }) return@mapNotNull null
SelectionCandidate.Impl(impl, formalSelfTy, formalTraitRef)
SelectionCandidate.Impl(cachedImpl.impl, formalSelfTy, formalTraitRef)
}

private fun assembleDerivedCandidates(ref: TraitRef): List<SelectionCandidate> {
Expand Down Expand Up @@ -847,7 +861,7 @@ private fun prepareSubstAndTraitRefRaw(
formalTrait: BoundElement<RsTraitItem>,
selfTy: Ty
): Pair<Substitution, TraitRef> {
val subst = generics.associate { it to ctx.typeVarForParam(it) }.toTypeSubst()
val subst = generics.associateWith { ctx.typeVarForParam(it) }.toTypeSubst()
val boundSubst = formalTrait.substitute(subst).subst.mapTypeValues { (k, v) ->
if (k == v && k.parameter is TyTypeParameter.Named) {
// Default type parameter values `trait Tr<T=Foo> {}`
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/org/rust/lang/core/resolve/NameResolution.kt
Expand Up @@ -528,7 +528,7 @@ private fun processTypeQualifiedPathResolveVariants(
fun(e: AssocItemScopeEntry): Boolean {
if (e.element !is RsTypeAlias) return processor(e)

val implementedTrait = e.source.value.implementedTrait
val implementedTrait = e.source.implementedTrait
?.foldTyTypeParameterWith { TyInfer.TyVar(it) }
?: return processor(e)

Expand Down Expand Up @@ -1129,12 +1129,12 @@ private fun processAssociatedItems(
* which are not implemented.
*/
fun processMembersWithDefaults(accessor: (RsMembers) -> List<RsAbstractable>): Boolean {
val directlyImplemented = traitOrImpl.value.members?.let { accessor(it) }.orEmpty()
val directlyImplemented = traitOrImpl.members?.let { accessor(it) }.orEmpty()
if (directlyImplemented.any { inherentProcessor(it) }) return true

if (traitOrImpl is TraitImplSource.ExplicitImpl) {
val direct = directlyImplemented.map { it.name }.toSet()
val membersFromTrait = traitOrImpl.value.implementedTrait?.element?.members ?: return false
val membersFromTrait = traitOrImpl.implementedTrait?.element?.members ?: return false
for (member in accessor(membersFromTrait)) {
if (member.name !in direct && inherentProcessor(member)) return true
}
Expand All @@ -1152,7 +1152,7 @@ private fun processAssociatedItems(
return false
}

val (inherent, traits) = lookup.findImplsAndTraits(type).partition { it is TraitImplSource.ExplicitImpl && it.value.traitRef == null }
val (inherent, traits) = lookup.findImplsAndTraits(type).partition { it is TraitImplSource.ExplicitImpl && it.isInherent }
if (inherent.any { processTraitOrImpl(it, true) }) return true
if (traits.any { processTraitOrImpl(it, false) }) return true
return false
Expand Down
51 changes: 51 additions & 0 deletions src/main/kotlin/org/rust/lang/core/resolve/RsCachedImplItem.kt
@@ -0,0 +1,51 @@
/*
* 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.project.Project
import org.rust.lang.core.psi.RsImplItem
import org.rust.lang.core.psi.RsMembers
import org.rust.lang.core.psi.RsTraitItem
import org.rust.lang.core.psi.RsTraitRef
import org.rust.lang.core.psi.ext.RsMod
import org.rust.lang.core.psi.ext.resolveToBoundTrait
import org.rust.lang.core.resolve.ref.ResolveCacheDependency
import org.rust.lang.core.resolve.ref.RsResolveCache
import org.rust.lang.core.types.BoundElement
import org.rust.lang.core.types.infer.generics
import org.rust.lang.core.types.ty.Ty
import org.rust.lang.core.types.ty.TyTypeParameter
import org.rust.lang.core.types.type
import kotlin.LazyThreadSafetyMode.PUBLICATION

/**
* Used for optimization purposes, to reduce access to cache and PSI tree in some very hot places,
* [ImplLookup.assembleCandidates] and [processAssociatedItems] in particular
*/
class RsCachedImplItem(
val impl: RsImplItem,
val crateRoot: RsMod? = impl.crateRoot,
val traitRef: RsTraitRef? = impl.traitRef,
val membres: RsMembers? = impl.members
) {
val implementedTrait: BoundElement<RsTraitItem>? by lazy(PUBLICATION) { traitRef?.resolveToBoundTrait() }
val typeAndGenerics: Pair<Ty, List<TyTypeParameter>>? by lazy(PUBLICATION) {
impl.typeReference?.type?.let { it to impl.generics }
}

companion object {
fun forImpl(project: Project, impl: RsImplItem): RsCachedImplItem {
return RsResolveCache.getInstance(project)
.resolveWithCaching(impl, ResolveCacheDependency.RUST_STRUCTURE, Resolver)!!
}

private object Resolver : (RsImplItem) -> RsCachedImplItem {
override fun invoke(impl: RsImplItem): RsCachedImplItem {
return RsCachedImplItem(impl)
}
}
}
}
Expand Up @@ -15,6 +15,7 @@ import org.rust.ide.search.RsWithMacrosProjectScope
import org.rust.lang.core.macros.macroExpansionManager
import org.rust.lang.core.psi.RsImplItem
import org.rust.lang.core.psi.ext.typeParameters
import org.rust.lang.core.resolve.RsCachedImplItem
import org.rust.lang.core.stubs.RsFileStub
import org.rust.lang.core.stubs.RsImplItemStub
import org.rust.lang.core.types.TyFingerprint
Expand All @@ -31,7 +32,7 @@ class RsImplIndex : AbstractStubIndex<TyFingerprint, RsImplItem>() {
* Note this method may return false positives
* @see TyFingerprint
*/
fun findPotentialImpls(project: Project, target: Ty): Sequence<RsImplItem> {
fun findPotentialImpls(project: Project, target: Ty): Sequence<RsCachedImplItem> {
project.macroExpansionManager.ensureUpToDate()
val impls = run {
val fingerprint = TyFingerprint.create(target)
Expand All @@ -40,14 +41,18 @@ class RsImplIndex : AbstractStubIndex<TyFingerprint, RsImplItem>() {
}
val freeImpls = getElements(KEY, TyFingerprint.TYPE_PARAMETER_FINGERPRINT, project, RsWithMacrosProjectScope(project))
// filter dangling (not attached to some crate) rust files, e.g. tests, generated source
return (impls.asSequence() + freeImpls.asSequence()).filter { it.crateRoot != null }
return (impls.asSequence() + freeImpls.asSequence())
.map { RsCachedImplItem.forImpl(project, it) }
.filter { it.crateRoot != null }
}

/** return impls for generic type `impl<T> Trait for T {}` */
fun findFreeImpls(project: Project): Sequence<RsImplItem> {
fun findFreeImpls(project: Project): Sequence<RsCachedImplItem> {
val freeImpls = getElements(KEY, TyFingerprint.TYPE_PARAMETER_FINGERPRINT, project, GlobalSearchScope.allScope(project))
// filter dangling (not attached to some crate) rust files, e.g. tests, generated source
return freeImpls.asSequence().filter { it.crateRoot != null }
return freeImpls.asSequence()
.map { RsCachedImplItem.forImpl(project, it) }
.filter { it.crateRoot != null }
}

fun index(stub: RsImplItemStub, sink: IndexSink) {
Expand Down
Expand Up @@ -5,6 +5,7 @@

package org.rust.lang.core.types.infer

import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Computable
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
Expand Down Expand Up @@ -115,6 +116,7 @@ class RsInferenceResult(
* A mutable object, which is filled while we walk function body top down.
*/
class RsInferenceContext(
val project: Project,
val lookup: ImplLookup,
val items: KnownItems
) : RsInferenceData {
Expand Down Expand Up @@ -245,13 +247,15 @@ class RsInferenceContext(
val fnName = (variant.element as? RsFunction)?.name
val impl = lookup.select(resolveTypeVarsIfPossible(traitRef)).ok()?.impl as? RsImplItem ?: continue
val fn = impl.members?.functionList?.find { it.name == fnName } ?: continue
resolvedPaths[path] = listOf(ResolvedPath.AssocItem(fn, TraitImplSource.ExplicitImpl(impl)))
val source = TraitImplSource.ExplicitImpl(RsCachedImplItem.forImpl(project, impl))
resolvedPaths[path] = listOf(ResolvedPath.AssocItem(fn, source))
}
for ((call, traitRef) in methodRefinements) {
val variant = resolvedMethods[call]?.firstOrNull() ?: continue
val impl = lookup.select(resolveTypeVarsIfPossible(traitRef)).ok()?.impl as? RsImplItem ?: continue
val fn = impl.members?.functionList?.find { it.name == variant.name } ?: continue
resolvedMethods[call] = listOf(variant.copy(element = fn, source = TraitImplSource.ExplicitImpl(impl)))
val source = TraitImplSource.ExplicitImpl(RsCachedImplItem.forImpl(project, impl))
resolvedMethods[call] = listOf(variant.copy(element = fn, source = source))
}
}

Expand Down

0 comments on commit fcc0724

Please sign in to comment.