Skip to content

Commit

Permalink
completions: speed up completions by filtering non-applicable traits
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbarsky committed Feb 13, 2024
1 parent 2c05da1 commit 26d794d
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 8 deletions.
1 change: 1 addition & 0 deletions crates/hir-ty/src/infer/unify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ impl<'a> InferenceTable<'a> {
}

/// Unify two relatable values (e.g. `Ty`) and register new trait goals that arise from that.
#[tracing::instrument(skip_all)]
pub(crate) fn unify<T: ?Sized + Zip<Interner>>(&mut self, ty1: &T, ty2: &T) -> bool {
let result = match self.try_unify(ty1, ty2) {
Ok(r) => r,
Expand Down
80 changes: 78 additions & 2 deletions crates/hir-ty/src/method_resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1167,10 +1167,13 @@ fn iterate_trait_method_candidates(
// trait, but if we find out it doesn't, we'll skip the rest of the
// iteration
let mut known_implemented = false;
for &(_, item) in data.items.iter() {
'items: for &(_, item) in data.items.iter() {
if let AssocItemId::TypeAliasId(_) = item {
continue 'items;
}
// Don't pass a `visible_from_module` down to `is_valid_candidate`,
// since only inherent methods should be included into visibility checking.
let visible = match is_valid_candidate(table, name, receiver_ty, item, self_ty, None) {
let visible = match is_valid_method_candidate(table, name, receiver_ty, item, self_ty) {
IsValidCandidate::Yes => true,
IsValidCandidate::NotVisible => false,
IsValidCandidate::No => continue,
Expand Down Expand Up @@ -1414,6 +1417,75 @@ fn is_valid_candidate(
}
}

/// Checks whether a given `AssocItemId` is applicable for `receiver_ty`.
///
/// This method should *only* be called by [`iterate_trait_method_candidates`],
/// as it is responsible for determining applicability in completions.
#[tracing::instrument(skip_all, fields(name))]
fn is_valid_method_candidate(
table: &mut InferenceTable<'_>,
name: Option<&Name>,
receiver_ty: Option<&Ty>,
item: AssocItemId,
self_ty: &Ty,
) -> IsValidCandidate {
let db = table.db;
match item {
AssocItemId::FunctionId(fn_id) => {
let db = table.db;
let data = db.function_data(fn_id);

check_that!(name.map_or(true, |n| n == &data.name));

table.run_in_snapshot(|table| {
let container = fn_id.lookup(db.upcast()).container;
let (impl_subst, expect_self_ty) = match container {
ItemContainerId::ImplId(it) => {
let subst = TyBuilder::subst_for_def(db, it, None)
.fill_with_inference_vars(table)
.build();
let self_ty = db.impl_self_ty(it).substitute(Interner, &subst);
(subst, self_ty)
}
ItemContainerId::TraitId(it) => {
let subst = TyBuilder::subst_for_def(db, it, None)
.fill_with_inference_vars(table)
.build();
let self_ty = subst.at(Interner, 0).assert_ty_ref(Interner).clone();
(subst, self_ty)
}
_ => unreachable!(),
};

check_that!(table.unify(&expect_self_ty, self_ty));

if let Some(receiver_ty) = receiver_ty {
check_that!(data.has_self_param());

let fn_subst = TyBuilder::subst_for_def(db, fn_id, Some(impl_subst.clone()))
.fill_with_inference_vars(table)
.build();

let sig = db.callable_item_signature(fn_id.into());
let expected_receiver =
sig.map(|s| s.params()[0].clone()).substitute(Interner, &fn_subst);

check_that!(table.unify(receiver_ty, &expected_receiver));
}

IsValidCandidate::Yes
})
}
AssocItemId::ConstId(c) => {
check_that!(receiver_ty.is_none());
check_that!(name.map_or(true, |n| db.const_data(c).name.as_ref() == Some(n)));

IsValidCandidate::Yes
}
_ => IsValidCandidate::No,
}
}

enum IsValidCandidate {
Yes,
No,
Expand Down Expand Up @@ -1441,6 +1513,8 @@ fn is_valid_fn_candidate(
}
table.run_in_snapshot(|table| {
let container = fn_id.lookup(db.upcast()).container;

let _p = tracing::span!(tracing::Level::INFO, "subst_for_def").entered();
let (impl_subst, expect_self_ty) = match container {
ItemContainerId::ImplId(it) => {
let subst =
Expand All @@ -1459,6 +1533,7 @@ fn is_valid_fn_candidate(

check_that!(table.unify(&expect_self_ty, self_ty));

let _p = tracing::span!(tracing::Level::INFO, "check_receiver_ty").entered();
if let Some(receiver_ty) = receiver_ty {
check_that!(data.has_self_param());

Expand All @@ -1473,6 +1548,7 @@ fn is_valid_fn_candidate(
check_that!(table.unify(receiver_ty, &expected_receiver));
}

let _p = tracing::span!(tracing::Level::INFO, "check_item_container").entered();
if let ItemContainerId::ImplId(impl_id) = container {
// We need to consider the bounds on the impl to distinguish functions of the same name
// for a type.
Expand Down
1 change: 1 addition & 0 deletions crates/hir-ty/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ fn solve(
block: Option<BlockId>,
goal: &chalk_ir::UCanonical<chalk_ir::InEnvironment<chalk_ir::Goal<Interner>>>,
) -> Option<chalk_solve::Solution<Interner>> {
let _p = tracing::span!(tracing::Level::INFO, "solve", ?krate, ?block).entered();
let context = ChalkContext { db, krate, block };
tracing::debug!("solve goal: {:?}", goal);
let mut solver = create_chalk_solver();
Expand Down
10 changes: 7 additions & 3 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use std::{iter, mem::discriminant, ops::ControlFlow};
use arrayvec::ArrayVec;
use base_db::{CrateDisplayName, CrateId, CrateOrigin, Edition, FileId};
use either::Either;
use hir_def::{
pub use hir_def::{
body::{BodyDiagnostic, SyntheticSyntax},
data::adt::VariantData,
generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance},
Expand All @@ -60,7 +60,7 @@ use hir_def::{
TypeOrConstParamId, TypeParamId, UnionId,
};
use hir_expand::{attrs::collect_attrs, name::name, proc_macro::ProcMacroKind, MacroCallKind};
use hir_ty::{
pub use hir_ty::{
all_super_traits, autoderef, check_orphan_rules,
consteval::{try_const_usize, unknown_const_as_generic, ConstExt},
db::InternedClosure,
Expand Down Expand Up @@ -266,6 +266,10 @@ impl Crate {
let data = &db.crate_graph()[self.id];
data.potential_cfg_options.clone().unwrap_or_else(|| data.cfg_options.clone())
}

pub fn id(&self) -> CrateId {
self.id
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -3691,7 +3695,7 @@ pub enum CaptureKind {
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct Type {
env: Arc<TraitEnvironment>,
ty: Ty,
pub ty: Ty,
}

impl Type {
Expand Down
31 changes: 28 additions & 3 deletions crates/ide-db/src/imports/import_assets.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Look up accessible paths for items.

use hir::{
AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, ModPath, Module, ModuleDef, Name,
PathResolution, PrefixKind, ScopeDef, Semantics, SemanticsScope, Type,
db::{DefDatabase, HirDatabase},
AsAssocItem, AssocItem, AssocItemContainer, Crate, HasCrate, ItemInNs, ModPath, Module,
ModuleDef, Name, PathResolution, PrefixKind, ScopeDef, Semantics, SemanticsScope, TraitId,
TyFingerprint, Type,
};
use itertools::{EitherOrBoth, Itertools};
use rustc_hash::{FxHashMap, FxHashSet};
Expand Down Expand Up @@ -517,7 +519,7 @@ fn trait_applicable_items(
let related_traits = inherent_traits.chain(env_traits).collect::<FxHashSet<_>>();

let mut required_assoc_items = FxHashSet::default();
let trait_candidates: FxHashSet<_> = items_locator::items_with_name(
let trait_candidates: FxHashSet<hir::TraitId> = items_locator::items_with_name(
sema,
current_crate,
trait_candidate.assoc_item_name.clone(),
Expand All @@ -536,6 +538,29 @@ fn trait_applicable_items(
required_assoc_items.insert(assoc);
Some(assoc_item_trait.into())
})
.filter(|candidate_trait_id| {
// blanket `Trait` implementations for type `A` can only exist in the crate defining
// the trait. However, an implementation for `A`` can only exist in `A`'s or `Trait`'s crate,
// which allows us to reduce the search space substantially.
let candidate_trait_id: TraitId = *candidate_trait_id;
let definining_crate_for_trait = db.trait_environment(candidate_trait_id.into()).krate;

let receiver_fingerprint =
TyFingerprint::for_trait_impl(&trait_candidate.receiver_ty.ty).unwrap();
let definitions_exist_in_trait_crate = db
.trait_impls_in_crate(definining_crate_for_trait)
.for_trait_and_self_ty(candidate_trait_id, receiver_fingerprint)
.next()
.is_some();

let definitions_exist_in_receiver_crate = db
.trait_impls_in_crate(trait_candidate.receiver_ty.krate(db).id())
.for_trait_and_self_ty(candidate_trait_id, receiver_fingerprint)
.next()
.is_some();

definitions_exist_in_trait_crate || definitions_exist_in_receiver_crate
})
.collect();

let mut located_imports = FxHashSet::default();
Expand Down

0 comments on commit 26d794d

Please sign in to comment.