Allow excluding specific traits from completion

To be accurate, only their methods are excluded, the trait themselves are still available.

I also excluded a bunch of std traits by default. Some less opinionated, like `AsRef`, which should never be used directly except in generic scenarios (and won't be excluded there), some more opinionated, like the ops traits, which I know some users sometimes want to use directly. Either way it's configurable.

It should be pretty easy to extend support to excluding only specific methods, but I didn't do that currently.

Traits configured to be excluded are resolved in each completion request from scratch. If this proves too expensive, it is easy enough to cache them in the DB.
This commit is contained in:
Chayim Refael Friedman 2024-09-24 19:24:15 +03:00 committed by Lukas Wirth
parent c86dd17cb3
commit 7e6ade117c
18 changed files with 1012 additions and 83 deletions

View file

@ -1,6 +1,9 @@
//! Completion of names from the current scope in expression position.
use hir::{sym, Name, ScopeDef};
use std::ops::ControlFlow;
use hir::{sym, Name, PathCandidateCallback, ScopeDef};
use ide_db::FxHashSet;
use syntax::ast;
use crate::{
@ -9,6 +12,39 @@ use crate::{
CompletionContext, Completions,
};
struct PathCallback<'a, F> {
ctx: &'a CompletionContext<'a>,
acc: &'a mut Completions,
add_assoc_item: F,
seen: FxHashSet<hir::AssocItem>,
}
impl<F> PathCandidateCallback for PathCallback<'_, F>
where
F: FnMut(&mut Completions, hir::AssocItem),
{
fn on_inherent_item(&mut self, item: hir::AssocItem) -> ControlFlow<()> {
if self.seen.insert(item) {
(self.add_assoc_item)(self.acc, item);
}
ControlFlow::Continue(())
}
#[allow(unstable_name_collisions)] // FIXME: Remove this when `is_none_or()` reaches stable.
fn on_trait_item(&mut self, item: hir::AssocItem) -> ControlFlow<()> {
// The excluded check needs to come before the `seen` test, so that if we see the same method twice,
// once as inherent and once not, we will include it.
if item
.container_trait(self.ctx.db)
.is_none_or(|trait_| !self.ctx.exclude_traits.contains(&trait_))
&& self.seen.insert(item)
{
(self.add_assoc_item)(self.acc, item);
}
ControlFlow::Continue(())
}
}
pub(crate) fn complete_expr_path(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
@ -50,12 +86,18 @@ pub(crate) fn complete_expr_path(
};
match qualified {
// We exclude associated types/consts of excluded traits here together with methods,
// even though we don't exclude them when completing in type position, because it's easier.
Qualified::TypeAnchor { ty: None, trait_: None } => ctx
.traits_in_scope()
.iter()
.flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
.copied()
.map(hir::Trait::from)
.filter(|it| !ctx.exclude_traits.contains(it))
.flat_map(|it| it.items(ctx.sema.db))
.for_each(|item| add_assoc_item(acc, item)),
Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
// Don't filter excluded traits here, user requested this specific trait.
trait_.items(ctx.sema.db).into_iter().for_each(|item| add_assoc_item(acc, item))
}
Qualified::TypeAnchor { ty: Some(ty), trait_: None } => {
@ -64,9 +106,14 @@ pub(crate) fn complete_expr_path(
acc.add_enum_variants(ctx, path_ctx, e);
}
ctx.iterate_path_candidates(ty, |item| {
add_assoc_item(acc, item);
});
ty.iterate_path_candidates_split_inherent(
ctx.db,
&ctx.scope,
&ctx.traits_in_scope(),
Some(ctx.module),
None,
PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
);
// Iterate assoc types separately
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
@ -121,9 +168,14 @@ pub(crate) fn complete_expr_path(
// XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
// (where AssocType is defined on a trait, not an inherent impl)
ctx.iterate_path_candidates(&ty, |item| {
add_assoc_item(acc, item);
});
ty.iterate_path_candidates_split_inherent(
ctx.db,
&ctx.scope,
&ctx.traits_in_scope(),
Some(ctx.module),
None,
PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
);
// Iterate assoc types separately
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
@ -134,6 +186,7 @@ pub(crate) fn complete_expr_path(
});
}
hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
// Don't filter excluded traits here, user requested this specific trait.
// Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
for item in t.items(ctx.db) {
add_assoc_item(acc, item);
@ -151,9 +204,14 @@ pub(crate) fn complete_expr_path(
acc.add_enum_variants(ctx, path_ctx, e);
}
ctx.iterate_path_candidates(&ty, |item| {
add_assoc_item(acc, item);
});
ty.iterate_path_candidates_split_inherent(
ctx.db,
&ctx.scope,
&ctx.traits_in_scope(),
Some(ctx.module),
None,
PathCallback { ctx, acc, add_assoc_item, seen: FxHashSet::default() },
);
}
_ => (),
}