From 7e6ade117c1ae9fca2d42bd29f5c9e7879e889b7 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 24 Sep 2024 19:24:15 +0300 Subject: [PATCH 1/6] 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. --- crates/hir-def/src/item_scope.rs | 7 + crates/hir-ty/src/method_resolution.rs | 69 +++- crates/hir/src/attrs.rs | 2 +- crates/hir/src/lib.rs | 178 ++++++-- crates/ide-completion/src/completions/dot.rs | 56 ++- crates/ide-completion/src/completions/expr.rs | 80 +++- .../src/completions/flyimport.rs | 16 + crates/ide-completion/src/config.rs | 6 +- crates/ide-completion/src/context.rs | 91 ++++- crates/ide-completion/src/lib.rs | 4 +- crates/ide-completion/src/tests.rs | 14 +- crates/ide-completion/src/tests/expression.rs | 381 +++++++++++++++++- crates/ide-completion/src/tests/special.rs | 2 +- crates/ide/src/lib.rs | 4 +- crates/rust-analyzer/src/config.rs | 57 ++- .../src/integrated_benchmarks.rs | 6 + docs/user/generated_config.adoc | 64 +++ editors/code/package.json | 56 +++ 18 files changed, 1011 insertions(+), 82 deletions(-) diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs index 2c3eb5c8e5e4..f600efea9f24 100644 --- a/crates/hir-def/src/item_scope.rs +++ b/crates/hir-def/src/item_scope.rs @@ -354,6 +354,13 @@ impl ItemScope { .chain(self.unnamed_trait_imports.keys().copied()) } + pub fn trait_by_name(&self, name: &Name) -> Option { + self.types.get(name).and_then(|def| match def.def { + ModuleDefId::TraitId(it) => Some(it), + _ => None, + }) + } + pub(crate) fn resolutions(&self) -> impl Iterator, PerNs)> + '_ { self.entries().map(|(name, res)| (Some(name.clone()), res)).chain( self.unnamed_trait_imports.iter().map(|(tr, trait_)| { diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index 952580c3b70d..dd6104aec895 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -913,7 +913,7 @@ pub fn iterate_path_candidates( traits_in_scope: &FxHashSet, visible_from_module: VisibleFromModule, name: Option<&Name>, - callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>, + callback: &mut dyn MethodCandidateCallback, ) -> ControlFlow<()> { iterate_method_candidates_dyn( ty, @@ -924,7 +924,7 @@ pub fn iterate_path_candidates( name, LookupMode::Path, // the adjustments are not relevant for path lookup - &mut |_, id, _| callback(id), + callback, ) } @@ -936,7 +936,7 @@ pub fn iterate_method_candidates_dyn( visible_from_module: VisibleFromModule, name: Option<&Name>, mode: LookupMode, - callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>, + callback: &mut dyn MethodCandidateCallback, ) -> ControlFlow<()> { let _p = tracing::info_span!( "iterate_method_candidates_dyn", @@ -1006,7 +1006,7 @@ fn iterate_method_candidates_with_autoref( traits_in_scope: &FxHashSet, visible_from_module: VisibleFromModule, name: Option<&Name>, - mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>, + callback: &mut dyn MethodCandidateCallback, ) -> ControlFlow<()> { if receiver_ty.value.is_general_var(Interner, &receiver_ty.binders) { // don't try to resolve methods on unknown types @@ -1021,7 +1021,7 @@ fn iterate_method_candidates_with_autoref( traits_in_scope, visible_from_module, name, - &mut callback, + callback, ) }; @@ -1051,6 +1051,45 @@ fn iterate_method_candidates_with_autoref( iterate_method_candidates_by_receiver(ref_muted, first_adjustment.with_autoref(Mutability::Mut)) } +pub trait MethodCandidateCallback { + fn on_inherent_method( + &mut self, + adjustments: ReceiverAdjustments, + item: AssocItemId, + is_visible: bool, + ) -> ControlFlow<()>; + + fn on_trait_method( + &mut self, + adjustments: ReceiverAdjustments, + item: AssocItemId, + is_visible: bool, + ) -> ControlFlow<()>; +} + +impl MethodCandidateCallback for F +where + F: FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>, +{ + fn on_inherent_method( + &mut self, + adjustments: ReceiverAdjustments, + item: AssocItemId, + is_visible: bool, + ) -> ControlFlow<()> { + self(adjustments, item, is_visible) + } + + fn on_trait_method( + &mut self, + adjustments: ReceiverAdjustments, + item: AssocItemId, + is_visible: bool, + ) -> ControlFlow<()> { + self(adjustments, item, is_visible) + } +} + #[tracing::instrument(skip_all, fields(name = ?name))] fn iterate_method_candidates_by_receiver( table: &mut InferenceTable<'_>, @@ -1059,7 +1098,7 @@ fn iterate_method_candidates_by_receiver( traits_in_scope: &FxHashSet, visible_from_module: VisibleFromModule, name: Option<&Name>, - mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>, + callback: &mut dyn MethodCandidateCallback, ) -> ControlFlow<()> { let receiver_ty = table.instantiate_canonical(receiver_ty); // We're looking for methods with *receiver* type receiver_ty. These could @@ -1075,7 +1114,9 @@ fn iterate_method_candidates_by_receiver( Some(&receiver_ty), Some(receiver_adjustments.clone()), visible_from_module, - &mut callback, + &mut |adjustments, item, is_visible| { + callback.on_inherent_method(adjustments, item, is_visible) + }, )? } ControlFlow::Continue(()) @@ -1095,7 +1136,9 @@ fn iterate_method_candidates_by_receiver( name, Some(&receiver_ty), Some(receiver_adjustments.clone()), - &mut callback, + &mut |adjustments, item, is_visible| { + callback.on_trait_method(adjustments, item, is_visible) + }, )? } ControlFlow::Continue(()) @@ -1110,7 +1153,7 @@ fn iterate_method_candidates_for_self_ty( traits_in_scope: &FxHashSet, visible_from_module: VisibleFromModule, name: Option<&Name>, - mut callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>, + callback: &mut dyn MethodCandidateCallback, ) -> ControlFlow<()> { let mut table = InferenceTable::new(db, env); let self_ty = table.instantiate_canonical(self_ty.clone()); @@ -1121,7 +1164,9 @@ fn iterate_method_candidates_for_self_ty( None, None, visible_from_module, - &mut callback, + &mut |adjustments, item, is_visible| { + callback.on_inherent_method(adjustments, item, is_visible) + }, )?; iterate_trait_method_candidates( &self_ty, @@ -1130,7 +1175,9 @@ fn iterate_method_candidates_for_self_ty( name, None, None, - callback, + &mut |adjustments, item, is_visible| { + callback.on_trait_method(adjustments, item, is_visible) + }, ) } diff --git a/crates/hir/src/attrs.rs b/crates/hir/src/attrs.rs index af60c233e551..a23fdf1b3934 100644 --- a/crates/hir/src/attrs.rs +++ b/crates/hir/src/attrs.rs @@ -258,7 +258,7 @@ fn resolve_impl_trait_item( &traits_in_scope, method_resolution::VisibleFromModule::None, Some(name), - &mut |assoc_item_id| { + &mut |_, assoc_item_id: AssocItemId, _| { // If two traits in scope define the same item, Rustdoc links to no specific trait (for // instance, given two methods `a`, Rustdoc simply links to `method.a` with no // disambiguation) so we just pick the first one we find as well. diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index f8af04302f0c..4c5f9d2a5a17 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -5219,28 +5219,49 @@ impl Type { traits_in_scope: &FxHashSet, with_local_impls: Option, name: Option<&Name>, - mut callback: impl FnMut(Function) -> Option, + callback: impl FnMut(Function) -> Option, ) -> Option { + struct Callback { + f: F, + slot: Option, + } + impl MethodCandidateCallback for &'_ mut Callback + where + F: FnMut(Function) -> Option, + { + fn on_inherent_method(&mut self, f: Function) -> ControlFlow<()> { + match (self.f)(f) { + it @ Some(_) => { + self.slot = it; + ControlFlow::Break(()) + } + None => ControlFlow::Continue(()), + } + } + + fn on_trait_method(&mut self, f: Function) -> ControlFlow<()> { + match (self.f)(f) { + it @ Some(_) => { + self.slot = it; + ControlFlow::Break(()) + } + None => ControlFlow::Continue(()), + } + } + } + let _p = tracing::info_span!("iterate_method_candidates_with_traits").entered(); - let mut slot = None; + let mut callback = Callback { slot: None, f: callback }; - self.iterate_method_candidates_dyn( + self.iterate_method_candidates_split_inherent( db, scope, traits_in_scope, with_local_impls, name, - &mut |assoc_item_id| { - if let AssocItemId::FunctionId(func) = assoc_item_id { - if let Some(res) = callback(func.into()) { - slot = Some(res); - return ControlFlow::Break(()); - } - } - ControlFlow::Continue(()) - }, + &mut callback, ); - slot + callback.slot } pub fn iterate_method_candidates( @@ -5261,15 +5282,49 @@ impl Type { ) } - fn iterate_method_candidates_dyn( + /// Allows you to treat inherent and non-inherent methods differently. + /// + /// Note that inherent methods may actually be trait methods! For example, in `dyn Trait`, the trait's methods + /// are considered inherent methods. + pub fn iterate_method_candidates_split_inherent( &self, db: &dyn HirDatabase, scope: &SemanticsScope<'_>, traits_in_scope: &FxHashSet, with_local_impls: Option, name: Option<&Name>, - callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>, + callback: impl MethodCandidateCallback, ) { + struct Callback(T); + + impl method_resolution::MethodCandidateCallback for Callback { + fn on_inherent_method( + &mut self, + _adjustments: method_resolution::ReceiverAdjustments, + item: AssocItemId, + _is_visible: bool, + ) -> ControlFlow<()> { + if let AssocItemId::FunctionId(func) = item { + self.0.on_inherent_method(func.into()) + } else { + ControlFlow::Continue(()) + } + } + + fn on_trait_method( + &mut self, + _adjustments: method_resolution::ReceiverAdjustments, + item: AssocItemId, + _is_visible: bool, + ) -> ControlFlow<()> { + if let AssocItemId::FunctionId(func) = item { + self.0.on_trait_method(func.into()) + } else { + ControlFlow::Continue(()) + } + } + } + let _p = tracing::info_span!( "iterate_method_candidates_dyn", with_local_impls = traits_in_scope.len(), @@ -5294,7 +5349,7 @@ impl Type { with_local_impls.and_then(|b| b.id.containing_block()).into(), name, method_resolution::LookupMode::MethodCall, - &mut |_adj, id, _| callback(id), + &mut Callback(callback), ); } @@ -5306,37 +5361,88 @@ impl Type { traits_in_scope: &FxHashSet, with_local_impls: Option, name: Option<&Name>, - mut callback: impl FnMut(AssocItem) -> Option, + callback: impl FnMut(AssocItem) -> Option, ) -> Option { + struct Callback { + f: F, + slot: Option, + } + impl PathCandidateCallback for &'_ mut Callback + where + F: FnMut(AssocItem) -> Option, + { + fn on_inherent_item(&mut self, item: AssocItem) -> ControlFlow<()> { + match (self.f)(item) { + it @ Some(_) => { + self.slot = it; + ControlFlow::Break(()) + } + None => ControlFlow::Continue(()), + } + } + + fn on_trait_item(&mut self, item: AssocItem) -> ControlFlow<()> { + match (self.f)(item) { + it @ Some(_) => { + self.slot = it; + ControlFlow::Break(()) + } + None => ControlFlow::Continue(()), + } + } + } + let _p = tracing::info_span!("iterate_path_candidates").entered(); - let mut slot = None; - self.iterate_path_candidates_dyn( + let mut callback = Callback { slot: None, f: callback }; + + self.iterate_path_candidates_split_inherent( db, scope, traits_in_scope, with_local_impls, name, - &mut |assoc_item_id| { - if let Some(res) = callback(assoc_item_id.into()) { - slot = Some(res); - return ControlFlow::Break(()); - } - ControlFlow::Continue(()) - }, + &mut callback, ); - slot + callback.slot } + /// Iterates over inherent methods. + /// + /// In some circumstances, inherent methods methods may actually be trait methods! + /// For example, when `dyn Trait` is a receiver, _trait_'s methods would be considered + /// to be inherent methods. #[tracing::instrument(skip_all, fields(name = ?name))] - fn iterate_path_candidates_dyn( + pub fn iterate_path_candidates_split_inherent( &self, db: &dyn HirDatabase, scope: &SemanticsScope<'_>, traits_in_scope: &FxHashSet, with_local_impls: Option, name: Option<&Name>, - callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>, + callback: impl PathCandidateCallback, ) { + struct Callback(T); + + impl method_resolution::MethodCandidateCallback for Callback { + fn on_inherent_method( + &mut self, + _adjustments: method_resolution::ReceiverAdjustments, + item: AssocItemId, + _is_visible: bool, + ) -> ControlFlow<()> { + self.0.on_inherent_item(item.into()) + } + + fn on_trait_method( + &mut self, + _adjustments: method_resolution::ReceiverAdjustments, + item: AssocItemId, + _is_visible: bool, + ) -> ControlFlow<()> { + self.0.on_trait_item(item.into()) + } + } + let canonical = hir_ty::replace_errors_with_variables(&self.ty); let krate = scope.krate(); @@ -5352,7 +5458,7 @@ impl Type { traits_in_scope, with_local_impls.and_then(|b| b.id.containing_block()).into(), name, - callback, + &mut Callback(callback), ); } @@ -6054,3 +6160,15 @@ fn push_ty_diagnostics( ); } } + +pub trait MethodCandidateCallback { + fn on_inherent_method(&mut self, f: Function) -> ControlFlow<()>; + + fn on_trait_method(&mut self, f: Function) -> ControlFlow<()>; +} + +pub trait PathCandidateCallback { + fn on_inherent_item(&mut self, item: AssocItem) -> ControlFlow<()>; + + fn on_trait_item(&mut self, item: AssocItem) -> ControlFlow<()>; +} diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index 229ce7723b57..26074672ba9c 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -1,6 +1,8 @@ //! Completes references after dot (fields and method calls). -use hir::{sym, Name}; +use std::ops::ControlFlow; + +use hir::{sym, HasContainer, ItemContainer, MethodCandidateCallback, Name}; use ide_db::FxHashSet; use syntax::SmolStr; @@ -158,21 +160,55 @@ fn complete_fields( fn complete_methods( ctx: &CompletionContext<'_>, receiver: &hir::Type, - mut f: impl FnMut(hir::Function), + f: impl FnMut(hir::Function), ) { - let mut seen_methods = FxHashSet::default(); - receiver.iterate_method_candidates_with_traits( + struct Callback<'a, F> { + ctx: &'a CompletionContext<'a>, + f: F, + seen_methods: FxHashSet, + } + + impl MethodCandidateCallback for Callback<'_, F> + where + F: FnMut(hir::Function), + { + // We don't want to exclude inherent trait methods - that is, methods of traits available from + // `where` clauses or `dyn Trait`. + fn on_inherent_method(&mut self, func: hir::Function) -> ControlFlow<()> { + if func.self_param(self.ctx.db).is_some() + && self.seen_methods.insert(func.name(self.ctx.db)) + { + (self.f)(func); + } + ControlFlow::Continue(()) + } + + fn on_trait_method(&mut self, func: hir::Function) -> ControlFlow<()> { + // This needs to come before the `seen_methods` test, so that if we see the same method twice, + // once as inherent and once not, we will include it. + if let ItemContainer::Trait(trait_) = func.container(self.ctx.db) { + if self.ctx.exclude_traits.contains(&trait_) { + return ControlFlow::Continue(()); + } + } + + if func.self_param(self.ctx.db).is_some() + && self.seen_methods.insert(func.name(self.ctx.db)) + { + (self.f)(func); + } + + ControlFlow::Continue(()) + } + } + + receiver.iterate_method_candidates_split_inherent( ctx.db, &ctx.scope, &ctx.traits_in_scope(), Some(ctx.module), None, - |func| { - if func.self_param(ctx.db).is_some() && seen_methods.insert(func.name(ctx.db)) { - f(func); - } - None::<()> - }, + Callback { ctx, f, seen_methods: FxHashSet::default() }, ); } diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index ff2c8da42130..e9eb3fc8428f 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -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, +} + +impl 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 `::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() }, + ); } _ => (), } diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 2a6b310d3a21..3f74e3610183 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -267,6 +267,15 @@ fn import_on_the_fly( && !ctx.is_item_hidden(original_item) && ctx.check_stability(original_item.attrs(ctx.db).as_deref()) }) + .filter(|import| { + if let ModuleDef::Trait(trait_) = import.item_to_import.into_module_def() { + let excluded = ctx.exclude_flyimport_traits.contains(&trait_); + let trait_itself_imported = import.item_to_import == import.original_item; + !excluded || trait_itself_imported + } else { + true + } + }) .sorted_by(|a, b| { let key = |import_path| { ( @@ -352,6 +361,13 @@ fn import_on_the_fly_method( !ctx.is_item_hidden(&import.item_to_import) && !ctx.is_item_hidden(&import.original_item) }) + .filter(|import| { + if let ModuleDef::Trait(trait_) = import.item_to_import.into_module_def() { + !ctx.exclude_flyimport_traits.contains(&trait_) + } else { + true + } + }) .sorted_by(|a, b| { let key = |import_path| { ( diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs index 1d05419c96de..ed36fe8d0283 100644 --- a/crates/ide-completion/src/config.rs +++ b/crates/ide-completion/src/config.rs @@ -10,7 +10,7 @@ use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap}; use crate::{snippet::Snippet, CompletionFieldsToResolve}; #[derive(Clone, Debug, PartialEq, Eq)] -pub struct CompletionConfig { +pub struct CompletionConfig<'a> { pub enable_postfix_completions: bool, pub enable_imports_on_the_fly: bool, pub enable_self_on_the_fly: bool, @@ -28,6 +28,8 @@ pub struct CompletionConfig { pub snippets: Vec, pub limit: Option, pub fields_to_resolve: CompletionFieldsToResolve, + pub exclude_flyimport_traits: &'a [String], + pub exclude_traits: &'a [String], } #[derive(Clone, Debug, PartialEq, Eq)] @@ -36,7 +38,7 @@ pub enum CallableSnippets { AddParentheses, } -impl CompletionConfig { +impl CompletionConfig<'_> { pub fn postfix_snippets(&self) -> impl Iterator { self.snippets .iter() diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index f8d403122d13..db909874dfe6 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -7,8 +7,8 @@ mod tests; use std::{iter, ops::ControlFlow}; use hir::{ - HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, - Symbol, Type, TypeInfo, + db::DefDatabase, HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, + SemanticsScope, Symbol, Type, TypeInfo, }; use ide_db::{ base_db::SourceDatabase, famous_defs::FamousDefs, helpers::is_editable_crate, FilePosition, @@ -429,7 +429,7 @@ pub(crate) struct CompletionContext<'a> { pub(crate) sema: Semantics<'a, RootDatabase>, pub(crate) scope: SemanticsScope<'a>, pub(crate) db: &'a RootDatabase, - pub(crate) config: &'a CompletionConfig, + pub(crate) config: &'a CompletionConfig<'a>, pub(crate) position: FilePosition, /// The token before the cursor, in the original file. @@ -462,6 +462,17 @@ pub(crate) struct CompletionContext<'a> { /// Here depth will be 2 pub(crate) depth_from_crate_root: usize, + /// Traits whose methods will be excluded from flyimport. Flyimport should not suggest + /// importing those traits. + /// + /// Note the trait *themselves* are not excluded, only their methods are. + pub(crate) exclude_flyimport_traits: FxHashSet, + /// Traits whose methods should always be excluded, even when in scope (compare `exclude_flyimport_traits`). + /// They will *not* be excluded, however, if they are available as a generic bound. + /// + /// Note the trait *themselves* are not excluded, only their methods are. + pub(crate) exclude_traits: FxHashSet, + /// Whether and how to complete semicolon for unit-returning functions. pub(crate) complete_semicolon: CompleteSemicolon, } @@ -670,7 +681,7 @@ impl<'a> CompletionContext<'a> { pub(crate) fn new( db: &'a RootDatabase, position @ FilePosition { file_id, offset }: FilePosition, - config: &'a CompletionConfig, + config: &'a CompletionConfig<'a>, ) -> Option<(CompletionContext<'a>, CompletionAnalysis)> { let _p = tracing::info_span!("CompletionContext::new").entered(); let sema = Semantics::new(db); @@ -753,6 +764,11 @@ impl<'a> CompletionContext<'a> { // exclude `m` itself .saturating_sub(1); + let exclude_traits = resolve_exclude_traits_list(db, config.exclude_traits); + let mut exclude_flyimport_traits = + resolve_exclude_traits_list(db, config.exclude_flyimport_traits); + exclude_flyimport_traits.extend(exclude_traits.iter().copied()); + let complete_semicolon = if config.add_semicolon_to_unit { let inside_closure_ret = token.parent_ancestors().try_for_each(|ancestor| { match_ast! { @@ -817,12 +833,79 @@ impl<'a> CompletionContext<'a> { qualifier_ctx, locals, depth_from_crate_root, + exclude_flyimport_traits, + exclude_traits, complete_semicolon, }; Some((ctx, analysis)) } } +fn resolve_exclude_traits_list(db: &RootDatabase, traits: &[String]) -> FxHashSet { + let _g = tracing::debug_span!("resolve_exclude_trait_list", ?traits).entered(); + let crate_graph = db.crate_graph(); + let mut crate_name_to_def_map = FxHashMap::default(); + let mut result = FxHashSet::default(); + 'process_traits: for trait_ in traits { + let mut segments = trait_.split("::").peekable(); + let Some(crate_name) = segments.next() else { + tracing::error!( + ?trait_, + "error resolving trait from traits exclude list: invalid path" + ); + continue; + }; + let Some(def_map) = crate_name_to_def_map.entry(crate_name).or_insert_with(|| { + let krate = crate_graph + .iter() + .find(|&krate| crate_graph[krate].display_name.as_deref() == Some(crate_name)); + let def_map = krate.map(|krate| db.crate_def_map(krate)); + if def_map.is_none() { + tracing::error!( + "error resolving `{trait_}` from trait exclude lists: crate could not be found" + ); + } + def_map + }) else { + // Do not report more than one error for the same crate. + continue; + }; + let mut module = &def_map[hir::DefMap::ROOT]; + let trait_name = 'lookup_mods: { + while let Some(segment) = segments.next() { + if segments.peek().is_none() { + break 'lookup_mods segment; + } + + let Some(&inner) = + module.children.get(&Name::new_symbol_root(hir::Symbol::intern(segment))) + else { + tracing::error!( + "error resolving `{trait_}` from trait exclude lists: could not find module `{segment}`" + ); + continue 'process_traits; + }; + module = &def_map[inner]; + } + + tracing::error!("error resolving `{trait_}` from trait exclude lists: invalid path"); + continue 'process_traits; + }; + let resolved_trait = module + .scope + .trait_by_name(&Name::new_symbol_root(hir::Symbol::intern(trait_name))) + .map(hir::Trait::from); + let Some(resolved_trait) = resolved_trait else { + tracing::error!( + "error resolving `{trait_}` from trait exclude lists: trait could not be found" + ); + continue; + }; + result.insert(resolved_trait); + } + result +} + const OP_TRAIT_LANG_NAMES: &[&str] = &[ "add_assign", "add", diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 14f42b40055e..05563bc912d9 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -184,7 +184,7 @@ impl CompletionFieldsToResolve { /// analysis. pub fn completions( db: &RootDatabase, - config: &CompletionConfig, + config: &CompletionConfig<'_>, position: FilePosition, trigger_character: Option, ) -> Option> { @@ -269,7 +269,7 @@ pub fn completions( /// This is used for import insertion done via completions like flyimport and custom user snippets. pub fn resolve_completion_edits( db: &RootDatabase, - config: &CompletionConfig, + config: &CompletionConfig<'_>, FilePosition { file_id, offset }: FilePosition, imports: impl IntoIterator, ) -> Option> { diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index e01097a9105b..3b2d3fbbfc11 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -61,7 +61,7 @@ fn function() {} union Union { field: i32 } "#; -pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { +pub(crate) const TEST_CONFIG: CompletionConfig<'_> = CompletionConfig { enable_postfix_completions: true, enable_imports_on_the_fly: true, enable_self_on_the_fly: true, @@ -85,6 +85,8 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { snippets: Vec::new(), limit: None, fields_to_resolve: CompletionFieldsToResolve::empty(), + exclude_flyimport_traits: &[], + exclude_traits: &[], }; pub(crate) fn completion_list(ra_fixture: &str) -> String { @@ -109,7 +111,7 @@ pub(crate) fn completion_list_with_trigger_character( } fn completion_list_with_config_raw( - config: CompletionConfig, + config: CompletionConfig<'_>, ra_fixture: &str, include_keywords: bool, trigger_character: Option, @@ -132,7 +134,7 @@ fn completion_list_with_config_raw( } fn completion_list_with_config( - config: CompletionConfig, + config: CompletionConfig<'_>, ra_fixture: &str, include_keywords: bool, trigger_character: Option, @@ -161,7 +163,7 @@ pub(crate) fn do_completion(code: &str, kind: CompletionItemKind) -> Vec, code: &str, kind: CompletionItemKind, ) -> Vec { @@ -220,7 +222,7 @@ pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: #[track_caller] pub(crate) fn check_edit_with_config( - config: CompletionConfig, + config: CompletionConfig<'_>, what: &str, ra_fixture_before: &str, ra_fixture_after: &str, @@ -257,7 +259,7 @@ fn check_empty(ra_fixture: &str, expect: Expect) { } pub(crate) fn get_all_items( - config: CompletionConfig, + config: CompletionConfig<'_>, code: &str, trigger_character: Option, ) -> Vec { diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index ea1b7ad78719..6c95980507c7 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -1,13 +1,29 @@ //! Completion tests for expressions. use expect_test::{expect, Expect}; -use crate::tests::{check_edit, check_empty, completion_list, BASE_ITEMS_FIXTURE}; +use crate::{ + tests::{ + check_edit, check_empty, completion_list, completion_list_with_config, BASE_ITEMS_FIXTURE, + TEST_CONFIG, + }, + CompletionConfig, +}; fn check(ra_fixture: &str, expect: Expect) { let actual = completion_list(&format!("{BASE_ITEMS_FIXTURE}{ra_fixture}")); expect.assert_eq(&actual) } +fn check_with_config(config: CompletionConfig<'_>, ra_fixture: &str, expect: Expect) { + let actual = completion_list_with_config( + config, + &format!("{BASE_ITEMS_FIXTURE}{ra_fixture}"), + true, + None, + ); + expect.assert_eq(&actual) +} + #[test] fn complete_literal_struct_with_a_private_field() { // `FooDesc.bar` is private, the completion should not be triggered. @@ -1390,3 +1406,366 @@ fn main() { "#]], ); } + +#[test] +fn excluded_trait_method_is_excluded() { + check_with_config( + CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG }, + r#" +trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} +} + +impl ExcludedTrait for T {} + +struct Foo; +impl Foo { + fn inherent(&self) {} +} + +fn foo() { + Foo.$0 +} + "#, + expect![[r#" + me inherent() fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + "#]], + ); +} + +#[test] +fn excluded_trait_not_excluded_when_inherent() { + check_with_config( + CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG }, + r#" +trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} +} + +impl ExcludedTrait for T {} + +fn foo(v: &dyn ExcludedTrait) { + v.$0 +} + "#, + expect![[r#" + me bar() (as ExcludedTrait) fn(&self) + me baz() (as ExcludedTrait) fn(&self) + me foo() (as ExcludedTrait) fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + "#]], + ); + check_with_config( + CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG }, + r#" +trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} +} + +impl ExcludedTrait for T {} + +fn foo(v: impl ExcludedTrait) { + v.$0 +} + "#, + expect![[r#" + me bar() (as ExcludedTrait) fn(&self) + me baz() (as ExcludedTrait) fn(&self) + me foo() (as ExcludedTrait) fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + "#]], + ); + check_with_config( + CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG }, + r#" +trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} +} + +impl ExcludedTrait for T {} + +fn foo(v: T) { + v.$0 +} + "#, + expect![[r#" + me bar() (as ExcludedTrait) fn(&self) + me baz() (as ExcludedTrait) fn(&self) + me foo() (as ExcludedTrait) fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + "#]], + ); +} + +#[test] +fn excluded_trait_method_is_excluded_from_flyimport() { + check_with_config( + CompletionConfig { + exclude_traits: &["test::module2::ExcludedTrait".to_owned()], + ..TEST_CONFIG + }, + r#" +mod module2 { + pub trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} + } + + impl ExcludedTrait for T {} +} + +struct Foo; +impl Foo { + fn inherent(&self) {} +} + +fn foo() { + Foo.$0 +} + "#, + expect![[r#" + me inherent() fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + "#]], + ); +} + +#[test] +fn flyimport_excluded_trait_method_is_excluded_from_flyimport() { + check_with_config( + CompletionConfig { + exclude_flyimport_traits: &["test::module2::ExcludedTrait".to_owned()], + ..TEST_CONFIG + }, + r#" +mod module2 { + pub trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} + } + + impl ExcludedTrait for T {} +} + +struct Foo; +impl Foo { + fn inherent(&self) {} +} + +fn foo() { + Foo.$0 +} + "#, + expect![[r#" + me inherent() fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + "#]], + ); +} + +#[test] +fn excluded_trait_method_is_excluded_from_path_completion() { + check_with_config( + CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG }, + r#" +pub trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} +} + +impl ExcludedTrait for T {} + +struct Foo; +impl Foo { + fn inherent(&self) {} +} + +fn foo() { + Foo::$0 +} + "#, + expect![[r#" + me inherent(…) fn(&self) + "#]], + ); +} + +#[test] +fn excluded_trait_method_is_not_excluded_when_trait_is_specified() { + check_with_config( + CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG }, + r#" +pub trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} +} + +impl ExcludedTrait for T {} + +struct Foo; +impl Foo { + fn inherent(&self) {} +} + +fn foo() { + ExcludedTrait::$0 +} + "#, + expect![[r#" + me bar(…) (as ExcludedTrait) fn(&self) + me baz(…) (as ExcludedTrait) fn(&self) + me foo(…) (as ExcludedTrait) fn(&self) + "#]], + ); + check_with_config( + CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG }, + r#" +pub trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} +} + +impl ExcludedTrait for T {} + +struct Foo; +impl Foo { + fn inherent(&self) {} +} + +fn foo() { + ::$0 +} + "#, + expect![[r#" + me bar(…) (as ExcludedTrait) fn(&self) + me baz(…) (as ExcludedTrait) fn(&self) + me foo(…) (as ExcludedTrait) fn(&self) + "#]], + ); +} + +#[test] +fn excluded_trait_not_excluded_when_inherent_path() { + check_with_config( + CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG }, + r#" +trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} +} + +impl ExcludedTrait for T {} + +fn foo() { + ::$0 +} + "#, + expect![[r#" + me bar(…) (as ExcludedTrait) fn(&self) + me baz(…) (as ExcludedTrait) fn(&self) + me foo(…) (as ExcludedTrait) fn(&self) + "#]], + ); + check_with_config( + CompletionConfig { exclude_traits: &["test::ExcludedTrait".to_owned()], ..TEST_CONFIG }, + r#" +trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} +} + +impl ExcludedTrait for T {} + +fn foo() { + T::$0 +} + "#, + expect![[r#" + me bar(…) (as ExcludedTrait) fn(&self) + me baz(…) (as ExcludedTrait) fn(&self) + me foo(…) (as ExcludedTrait) fn(&self) + "#]], + ); +} diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs index 388af48c68b4..6cfb2231a990 100644 --- a/crates/ide-completion/src/tests/special.rs +++ b/crates/ide-completion/src/tests/special.rs @@ -1345,7 +1345,7 @@ struct Foo = { let mut x = TEST_CONFIG; x.full_function_signatures = true; x diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 1637d578fdf1..22a9ee52079c 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -671,7 +671,7 @@ impl Analysis { /// Computes completions at the given position. pub fn completions( &self, - config: &CompletionConfig, + config: &CompletionConfig<'_>, position: FilePosition, trigger_character: Option, ) -> Cancellable>> { @@ -683,7 +683,7 @@ impl Analysis { /// Resolves additional completion data at the position given. pub fn resolve_completion_edits( &self, - config: &CompletionConfig, + config: &CompletionConfig<'_>, position: FilePosition, imports: impl IntoIterator + std::panic::UnwindSafe, ) -> Cancellable> { diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index c182952c731d..c53ca230409d 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -440,11 +440,64 @@ config_data! { /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. completion_autoimport_enable: bool = true, + /// A list of full paths to traits to exclude from flyimport. + /// + /// Traits in this list won't be suggested to be imported by flyimport for their methods. Methods from them won't be available in flyimport completion. They will still be available if in scope. + /// + /// Note that the trait themselves can still be suggested by flyimport. + /// + /// This setting also inherits `#rust-analyzer.completion.excludeTraits#`. + /// + /// This setting defaults to: + /// + /// - [`core::borrow::Borrow`](https://doc.rust-lang.org/nightly/core/borrow/trait.Borrow.html) + /// - [`core::borrow::BorrowMut`](https://doc.rust-lang.org/nightly/core/borrow/trait.BorrowMut.html) + /// - [`core::cmp::PartialEq`](https://doc.rust-lang.org/nightly/core/cmp/trait.PartialEq.html) + /// - All operator traits (in [`core::ops`](https://doc.rust-lang.org/nightly/core/ops)) + /// + /// Note that if you override this setting, those traits won't be automatically inserted, so you may want to insert them manually. + completion_autoimport_excludeTraits: Vec = vec![ + "core::borrow::Borrow".to_owned(), + "core::borrow::BorrowMut".to_owned(), + "core::cmp::PartialEq".to_owned(), + "core::ops::Add".to_owned(), + "core::ops::AddAssign".to_owned(), + "core::ops::BitAnd".to_owned(), + "core::ops::BitAndAssign".to_owned(), + "core::ops::BitOr".to_owned(), + "core::ops::BitOrAssign".to_owned(), + "core::ops::BitXor".to_owned(), + "core::ops::BitXorAssign".to_owned(), + "core::ops::Div".to_owned(), + "core::ops::DivAssign".to_owned(), + "core::ops::Mul".to_owned(), + "core::ops::MulAssign".to_owned(), + "core::ops::Rem".to_owned(), + "core::ops::RemAssign".to_owned(), + "core::ops::Shl".to_owned(), + "core::ops::ShlAssign".to_owned(), + "core::ops::Shr".to_owned(), + "core::ops::ShrAssign".to_owned(), + "core::ops::Sub".to_owned(), + "core::ops::SubAssign".to_owned(), + "core::ops::Neg".to_owned(), + "core::ops::Not".to_owned(), + "core::ops::Index".to_owned(), + "core::ops::IndexMut".to_owned(), + "core::ops::Deref".to_owned(), + "core::ops::DerefMut".to_owned(), + ], /// Toggles the additional completions that automatically show method calls and field accesses /// with `self` prefixed to them when inside a method. completion_autoself_enable: bool = true, /// Whether to add parenthesis and argument snippets when completing function. completion_callable_snippets: CallableCompletionDef = CallableCompletionDef::FillArguments, + /// A list of full paths to traits to exclude from completion. + /// + /// Methods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`. + /// + /// Note that the trait themselves can still be completed. + completion_excludeTraits: Vec = Vec::new(), /// Whether to show full function/method signatures in completion docs. completion_fullFunctionSignatures_enable: bool = false, /// Whether to omit deprecated items from autocompletion. By default they are marked as deprecated but not hidden. @@ -1431,7 +1484,7 @@ impl Config { CallHierarchyConfig { exclude_tests: self.references_excludeTests().to_owned() } } - pub fn completion(&self, source_root: Option) -> CompletionConfig { + pub fn completion(&self, source_root: Option) -> CompletionConfig<'_> { let client_capability_fields = self.completion_resolve_support_properties(); CompletionConfig { enable_postfix_completions: self.completion_postfix_enable(source_root).to_owned(), @@ -1462,6 +1515,8 @@ impl Config { } else { CompletionFieldsToResolve::from_client_capabilities(&client_capability_fields) }, + exclude_flyimport_traits: self.completion_autoimport_excludeTraits(source_root), + exclude_traits: self.completion_excludeTraits(source_root), } } diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index 8946c7acb938..bd164fe44081 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -174,6 +174,8 @@ fn integrated_completion_benchmark() { limit: None, add_semicolon_to_unit: true, fields_to_resolve: CompletionFieldsToResolve::empty(), + exclude_flyimport_traits: &[], + exclude_traits: &[], }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -222,6 +224,8 @@ fn integrated_completion_benchmark() { limit: None, add_semicolon_to_unit: true, fields_to_resolve: CompletionFieldsToResolve::empty(), + exclude_flyimport_traits: &[], + exclude_traits: &[], }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -268,6 +272,8 @@ fn integrated_completion_benchmark() { limit: None, add_semicolon_to_unit: true, fields_to_resolve: CompletionFieldsToResolve::empty(), + exclude_flyimport_traits: &[], + exclude_traits: &[], }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 5056c7d977ce..27ade71b331a 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -285,6 +285,61 @@ In `match` arms it completes a comma instead. -- Toggles the additional completions that automatically add imports when completed. Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. +-- +[[rust-analyzer.completion.autoimport.excludeTraits]]rust-analyzer.completion.autoimport.excludeTraits:: ++ +-- +Default: +---- +[ + "core::borrow::Borrow", + "core::borrow::BorrowMut", + "core::cmp::PartialEq", + "core::ops::Add", + "core::ops::AddAssign", + "core::ops::BitAnd", + "core::ops::BitAndAssign", + "core::ops::BitOr", + "core::ops::BitOrAssign", + "core::ops::BitXor", + "core::ops::BitXorAssign", + "core::ops::Div", + "core::ops::DivAssign", + "core::ops::Mul", + "core::ops::MulAssign", + "core::ops::Rem", + "core::ops::RemAssign", + "core::ops::Shl", + "core::ops::ShlAssign", + "core::ops::Shr", + "core::ops::ShrAssign", + "core::ops::Sub", + "core::ops::SubAssign", + "core::ops::Neg", + "core::ops::Not", + "core::ops::Index", + "core::ops::IndexMut", + "core::ops::Deref", + "core::ops::DerefMut" +] +---- +A list of full paths to traits to exclude from flyimport. + +Traits in this list won't be suggested to be imported by flyimport for their methods. Methods from them won't be available in flyimport completion. They will still be available if in scope. + +Note that the trait themselves can still be suggested by flyimport. + +This setting also inherits `#rust-analyzer.completion.excludeTraits#`. + +This setting defaults to: + + - [`core::borrow::Borrow`](https://doc.rust-lang.org/nightly/core/borrow/trait.Borrow.html) + - [`core::borrow::BorrowMut`](https://doc.rust-lang.org/nightly/core/borrow/trait.BorrowMut.html) + - [`core::cmp::PartialEq`](https://doc.rust-lang.org/nightly/core/cmp/trait.PartialEq.html) + - All operator traits (in [`core::ops`](https://doc.rust-lang.org/nightly/core/ops)) + +Note that if you override this setting, those traits won't be automatically inserted, so you may want to insert them manually. + -- [[rust-analyzer.completion.autoself.enable]]rust-analyzer.completion.autoself.enable (default: `true`):: + @@ -297,6 +352,15 @@ with `self` prefixed to them when inside a method. -- Whether to add parenthesis and argument snippets when completing function. -- +[[rust-analyzer.completion.excludeTraits]]rust-analyzer.completion.excludeTraits (default: `[]`):: ++ +-- +A list of full paths to traits to exclude from completion. + +Methods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`. + +Note that the trait themselves can still be completed. +-- [[rust-analyzer.completion.fullFunctionSignatures.enable]]rust-analyzer.completion.fullFunctionSignatures.enable (default: `false`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index b9249e9ac8ba..a6e92838a068 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1139,6 +1139,49 @@ } } }, + { + "title": "completion", + "properties": { + "rust-analyzer.completion.autoimport.excludeTraits": { + "markdownDescription": "A list of full paths to traits to exclude from flyimport.\n\nTraits in this list won't be suggested to be imported by flyimport for their methods. Methods from them won't be available in flyimport completion. They will still be available if in scope.\n\nNote that the trait themselves can still be suggested by flyimport.\n\nThis setting also inherits `#rust-analyzer.completion.excludeTraits#`.\n\nThis setting defaults to:\n\n - [`core::borrow::Borrow`](https://doc.rust-lang.org/nightly/core/borrow/trait.Borrow.html)\n - [`core::borrow::BorrowMut`](https://doc.rust-lang.org/nightly/core/borrow/trait.BorrowMut.html)\n - [`core::cmp::PartialEq`](https://doc.rust-lang.org/nightly/core/cmp/trait.PartialEq.html)\n - All operator traits (in [`core::ops`](https://doc.rust-lang.org/nightly/core/ops))\n\nNote that if you override this setting, those traits won't be automatically inserted, so you may want to insert them manually.", + "default": [ + "core::borrow::Borrow", + "core::borrow::BorrowMut", + "core::cmp::PartialEq", + "core::ops::Add", + "core::ops::AddAssign", + "core::ops::BitAnd", + "core::ops::BitAndAssign", + "core::ops::BitOr", + "core::ops::BitOrAssign", + "core::ops::BitXor", + "core::ops::BitXorAssign", + "core::ops::Div", + "core::ops::DivAssign", + "core::ops::Mul", + "core::ops::MulAssign", + "core::ops::Rem", + "core::ops::RemAssign", + "core::ops::Shl", + "core::ops::ShlAssign", + "core::ops::Shr", + "core::ops::ShrAssign", + "core::ops::Sub", + "core::ops::SubAssign", + "core::ops::Neg", + "core::ops::Not", + "core::ops::Index", + "core::ops::IndexMut", + "core::ops::Deref", + "core::ops::DerefMut" + ], + "type": "array", + "items": { + "type": "string" + } + } + } + }, { "title": "completion", "properties": { @@ -1169,6 +1212,19 @@ } } }, + { + "title": "completion", + "properties": { + "rust-analyzer.completion.excludeTraits": { + "markdownDescription": "A list of full paths to traits to exclude from completion.\n\nMethods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`.\n\nNote that the trait themselves can still be completed.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, { "title": "completion", "properties": { From a02a1afc922c7435baa909f81c5ca66e1ae56296 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 29 Sep 2024 09:15:58 +0300 Subject: [PATCH 2/6] Allow flyimporting excluded trait items if there is an exact match in the name I.e. with `fn foo()`, don't complete at `x.fo|`, but complete (with imports) for `x.foo|`, since this is less likely to have false positives. I opted to only do that for flyimport, even though for basic imports there can also be snippet completion (completing the params list for a method), since this is less universally applicable and seems not so useful. --- .../src/completions/flyimport.rs | 20 +++++++++-- crates/ide-completion/src/tests/flyimport.rs | 34 ++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 3f74e3610183..afa94affb3b9 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -258,6 +258,8 @@ fn import_on_the_fly( let import_cfg = ctx.config.import_path_config(); + let completed_name = ctx.token.to_string(); + import_assets .search_for_imports(&ctx.sema, import_cfg, ctx.config.insert_use.prefix_kind) .filter(ns_filter) @@ -271,7 +273,13 @@ fn import_on_the_fly( if let ModuleDef::Trait(trait_) = import.item_to_import.into_module_def() { let excluded = ctx.exclude_flyimport_traits.contains(&trait_); let trait_itself_imported = import.item_to_import == import.original_item; - !excluded || trait_itself_imported + if !excluded || trait_itself_imported { + return true; + } + + let item = import.original_item.into_module_def(); + // Filter that item out, unless its name matches the name the user wrote exactly - in which case preserve it. + item.name(ctx.db).is_some_and(|name| name.eq_ident(&completed_name)) } else { true } @@ -355,6 +363,8 @@ fn import_on_the_fly_method( let cfg = ctx.config.import_path_config(); + let completed_name = ctx.token.to_string(); + import_assets .search_for_imports(&ctx.sema, cfg, ctx.config.insert_use.prefix_kind) .filter(|import| { @@ -363,7 +373,13 @@ fn import_on_the_fly_method( }) .filter(|import| { if let ModuleDef::Trait(trait_) = import.item_to_import.into_module_def() { - !ctx.exclude_flyimport_traits.contains(&trait_) + if !ctx.exclude_flyimport_traits.contains(&trait_) { + return true; + } + + let item = import.original_item.into_module_def(); + // Filter that method out, unless its name matches the name the user wrote exactly - in which case preserve it. + item.name(ctx.db).is_some_and(|name| name.eq_ident(&completed_name)) } else { true } diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs index 447dbc998b56..d413977f7c8f 100644 --- a/crates/ide-completion/src/tests/flyimport.rs +++ b/crates/ide-completion/src/tests/flyimport.rs @@ -3,10 +3,14 @@ use expect_test::{expect, Expect}; use crate::{ context::{CompletionAnalysis, NameContext, NameKind, NameRefKind}, tests::{check_edit, check_edit_with_config, TEST_CONFIG}, + CompletionConfig, }; fn check(ra_fixture: &str, expect: Expect) { - let config = TEST_CONFIG; + check_with_config(TEST_CONFIG, ra_fixture, expect); +} + +fn check_with_config(config: CompletionConfig<'_>, ra_fixture: &str, expect: Expect) { let (db, position) = crate::tests::position(ra_fixture); let (ctx, analysis) = crate::context::CompletionContext::new(&db, position, &config).unwrap(); @@ -1762,3 +1766,31 @@ fn function() { expect![""], ); } + +#[test] +fn excluded_trait_item_included_when_exact_match() { + check_with_config( + CompletionConfig { + exclude_traits: &["test::module2::ExcludedTrait".to_owned()], + ..TEST_CONFIG + }, + r#" +mod module2 { + pub trait ExcludedTrait { + fn foo(&self) {} + fn bar(&self) {} + fn baz(&self) {} + } + + impl ExcludedTrait for T {} +} + +fn foo() { + true.foo$0 +} + "#, + expect![[r#" + me foo() (use module2::ExcludedTrait) fn(&self) + "#]], + ); +} From 45954ebaa49a2011b06333f1e91549ee055578bd Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 1 Jan 2025 14:09:05 +0100 Subject: [PATCH 3/6] Reduce the default autoimport exclusion list --- crates/hir-def/src/item_scope.rs | 7 - crates/ide-completion/src/tests/expression.rs | 154 ++++++++++-------- crates/rust-analyzer/src/config.rs | 41 +---- docs/user/generated_config.adoc | 43 +---- editors/code/package.json | 31 +--- 5 files changed, 90 insertions(+), 186 deletions(-) diff --git a/crates/hir-def/src/item_scope.rs b/crates/hir-def/src/item_scope.rs index f600efea9f24..2c3eb5c8e5e4 100644 --- a/crates/hir-def/src/item_scope.rs +++ b/crates/hir-def/src/item_scope.rs @@ -354,13 +354,6 @@ impl ItemScope { .chain(self.unnamed_trait_imports.keys().copied()) } - pub fn trait_by_name(&self, name: &Name) -> Option { - self.types.get(name).and_then(|def| match def.def { - ModuleDefId::TraitId(it) => Some(it), - _ => None, - }) - } - pub(crate) fn resolutions(&self) -> impl Iterator, PerNs)> + '_ { self.entries().map(|(name, res)| (Some(name.clone()), res)).chain( self.unnamed_trait_imports.iter().map(|(tr, trait_)| { diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index 6c95980507c7..d1bbf1e8d0bc 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -1430,19 +1430,22 @@ fn foo() { } "#, expect![[r#" - me inherent() fn(&self) - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn deref *expr - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr - sn return return expr - sn unsafe unsafe {} + me bar() (as ExcludedTrait) fn(&self) + me baz() (as ExcludedTrait) fn(&self) + me foo() (as ExcludedTrait) fn(&self) + me inherent() fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} "#]], ); } @@ -1468,18 +1471,18 @@ fn foo(v: &dyn ExcludedTrait) { me bar() (as ExcludedTrait) fn(&self) me baz() (as ExcludedTrait) fn(&self) me foo() (as ExcludedTrait) fn(&self) - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) sn dbgr dbg!(&expr) - sn deref *expr - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr sn return return expr - sn unsafe unsafe {} + sn unsafe unsafe {} "#]], ); check_with_config( @@ -1501,18 +1504,18 @@ fn foo(v: impl ExcludedTrait) { me bar() (as ExcludedTrait) fn(&self) me baz() (as ExcludedTrait) fn(&self) me foo() (as ExcludedTrait) fn(&self) - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) sn dbgr dbg!(&expr) - sn deref *expr - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr sn return return expr - sn unsafe unsafe {} + sn unsafe unsafe {} "#]], ); check_with_config( @@ -1534,18 +1537,18 @@ fn foo(v: T) { me bar() (as ExcludedTrait) fn(&self) me baz() (as ExcludedTrait) fn(&self) me foo() (as ExcludedTrait) fn(&self) - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) sn dbgr dbg!(&expr) - sn deref *expr - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr sn return return expr - sn unsafe unsafe {} + sn unsafe unsafe {} "#]], ); } @@ -1578,19 +1581,22 @@ fn foo() { } "#, expect![[r#" - me inherent() fn(&self) - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn deref *expr - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr - sn return return expr - sn unsafe unsafe {} + me bar() (use module2::ExcludedTrait) fn(&self) + me baz() (use module2::ExcludedTrait) fn(&self) + me foo() (use module2::ExcludedTrait) fn(&self) + me inherent() fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} "#]], ); } @@ -1623,19 +1629,22 @@ fn foo() { } "#, expect![[r#" - me inherent() fn(&self) - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn deref *expr - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr - sn return return expr - sn unsafe unsafe {} + me bar() (use module2::ExcludedTrait) fn(&self) + me baz() (use module2::ExcludedTrait) fn(&self) + me foo() (use module2::ExcludedTrait) fn(&self) + me inherent() fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} "#]], ); } @@ -1663,8 +1672,11 @@ fn foo() { } "#, expect![[r#" - me inherent(…) fn(&self) - "#]], + me bar(…) (as ExcludedTrait) fn(&self) + me baz(…) (as ExcludedTrait) fn(&self) + me foo(…) (as ExcludedTrait) fn(&self) + me inherent(…) fn(&self) + "#]], ); } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index c53ca230409d..b6678c12c703 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -442,50 +442,13 @@ config_data! { completion_autoimport_enable: bool = true, /// A list of full paths to traits to exclude from flyimport. /// - /// Traits in this list won't be suggested to be imported by flyimport for their methods. Methods from them won't be available in flyimport completion. They will still be available if in scope. - /// - /// Note that the trait themselves can still be suggested by flyimport. + /// Traits in this list won't have their methods suggested in completions unless the trait + /// is in scope. /// /// This setting also inherits `#rust-analyzer.completion.excludeTraits#`. - /// - /// This setting defaults to: - /// - /// - [`core::borrow::Borrow`](https://doc.rust-lang.org/nightly/core/borrow/trait.Borrow.html) - /// - [`core::borrow::BorrowMut`](https://doc.rust-lang.org/nightly/core/borrow/trait.BorrowMut.html) - /// - [`core::cmp::PartialEq`](https://doc.rust-lang.org/nightly/core/cmp/trait.PartialEq.html) - /// - All operator traits (in [`core::ops`](https://doc.rust-lang.org/nightly/core/ops)) - /// - /// Note that if you override this setting, those traits won't be automatically inserted, so you may want to insert them manually. completion_autoimport_excludeTraits: Vec = vec![ "core::borrow::Borrow".to_owned(), "core::borrow::BorrowMut".to_owned(), - "core::cmp::PartialEq".to_owned(), - "core::ops::Add".to_owned(), - "core::ops::AddAssign".to_owned(), - "core::ops::BitAnd".to_owned(), - "core::ops::BitAndAssign".to_owned(), - "core::ops::BitOr".to_owned(), - "core::ops::BitOrAssign".to_owned(), - "core::ops::BitXor".to_owned(), - "core::ops::BitXorAssign".to_owned(), - "core::ops::Div".to_owned(), - "core::ops::DivAssign".to_owned(), - "core::ops::Mul".to_owned(), - "core::ops::MulAssign".to_owned(), - "core::ops::Rem".to_owned(), - "core::ops::RemAssign".to_owned(), - "core::ops::Shl".to_owned(), - "core::ops::ShlAssign".to_owned(), - "core::ops::Shr".to_owned(), - "core::ops::ShrAssign".to_owned(), - "core::ops::Sub".to_owned(), - "core::ops::SubAssign".to_owned(), - "core::ops::Neg".to_owned(), - "core::ops::Not".to_owned(), - "core::ops::Index".to_owned(), - "core::ops::IndexMut".to_owned(), - "core::ops::Deref".to_owned(), - "core::ops::DerefMut".to_owned(), ], /// Toggles the additional completions that automatically show method calls and field accesses /// with `self` prefixed to them when inside a method. diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 27ade71b331a..bce26f7dd74f 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -293,53 +293,16 @@ Default: ---- [ "core::borrow::Borrow", - "core::borrow::BorrowMut", - "core::cmp::PartialEq", - "core::ops::Add", - "core::ops::AddAssign", - "core::ops::BitAnd", - "core::ops::BitAndAssign", - "core::ops::BitOr", - "core::ops::BitOrAssign", - "core::ops::BitXor", - "core::ops::BitXorAssign", - "core::ops::Div", - "core::ops::DivAssign", - "core::ops::Mul", - "core::ops::MulAssign", - "core::ops::Rem", - "core::ops::RemAssign", - "core::ops::Shl", - "core::ops::ShlAssign", - "core::ops::Shr", - "core::ops::ShrAssign", - "core::ops::Sub", - "core::ops::SubAssign", - "core::ops::Neg", - "core::ops::Not", - "core::ops::Index", - "core::ops::IndexMut", - "core::ops::Deref", - "core::ops::DerefMut" + "core::borrow::BorrowMut" ] ---- A list of full paths to traits to exclude from flyimport. -Traits in this list won't be suggested to be imported by flyimport for their methods. Methods from them won't be available in flyimport completion. They will still be available if in scope. - -Note that the trait themselves can still be suggested by flyimport. +Traits in this list won't have their methods suggested in completions unless the trait +is in scope. This setting also inherits `#rust-analyzer.completion.excludeTraits#`. -This setting defaults to: - - - [`core::borrow::Borrow`](https://doc.rust-lang.org/nightly/core/borrow/trait.Borrow.html) - - [`core::borrow::BorrowMut`](https://doc.rust-lang.org/nightly/core/borrow/trait.BorrowMut.html) - - [`core::cmp::PartialEq`](https://doc.rust-lang.org/nightly/core/cmp/trait.PartialEq.html) - - All operator traits (in [`core::ops`](https://doc.rust-lang.org/nightly/core/ops)) - -Note that if you override this setting, those traits won't be automatically inserted, so you may want to insert them manually. - -- [[rust-analyzer.completion.autoself.enable]]rust-analyzer.completion.autoself.enable (default: `true`):: + diff --git a/editors/code/package.json b/editors/code/package.json index a6e92838a068..31419a1942d4 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1143,37 +1143,10 @@ "title": "completion", "properties": { "rust-analyzer.completion.autoimport.excludeTraits": { - "markdownDescription": "A list of full paths to traits to exclude from flyimport.\n\nTraits in this list won't be suggested to be imported by flyimport for their methods. Methods from them won't be available in flyimport completion. They will still be available if in scope.\n\nNote that the trait themselves can still be suggested by flyimport.\n\nThis setting also inherits `#rust-analyzer.completion.excludeTraits#`.\n\nThis setting defaults to:\n\n - [`core::borrow::Borrow`](https://doc.rust-lang.org/nightly/core/borrow/trait.Borrow.html)\n - [`core::borrow::BorrowMut`](https://doc.rust-lang.org/nightly/core/borrow/trait.BorrowMut.html)\n - [`core::cmp::PartialEq`](https://doc.rust-lang.org/nightly/core/cmp/trait.PartialEq.html)\n - All operator traits (in [`core::ops`](https://doc.rust-lang.org/nightly/core/ops))\n\nNote that if you override this setting, those traits won't be automatically inserted, so you may want to insert them manually.", + "markdownDescription": "A list of full paths to traits to exclude from flyimport.\n\nTraits in this list won't have their methods suggested in completions unless the trait\nis in scope.\n\nThis setting also inherits `#rust-analyzer.completion.excludeTraits#`.", "default": [ "core::borrow::Borrow", - "core::borrow::BorrowMut", - "core::cmp::PartialEq", - "core::ops::Add", - "core::ops::AddAssign", - "core::ops::BitAnd", - "core::ops::BitAndAssign", - "core::ops::BitOr", - "core::ops::BitOrAssign", - "core::ops::BitXor", - "core::ops::BitXorAssign", - "core::ops::Div", - "core::ops::DivAssign", - "core::ops::Mul", - "core::ops::MulAssign", - "core::ops::Rem", - "core::ops::RemAssign", - "core::ops::Shl", - "core::ops::ShlAssign", - "core::ops::Shr", - "core::ops::ShrAssign", - "core::ops::Sub", - "core::ops::SubAssign", - "core::ops::Neg", - "core::ops::Not", - "core::ops::Index", - "core::ops::IndexMut", - "core::ops::Deref", - "core::ops::DerefMut" + "core::borrow::BorrowMut" ], "type": "array", "items": { From c5bda0d3f7a115f6bd9adbea72cfb888aaf174c2 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 1 Jan 2025 14:26:10 +0100 Subject: [PATCH 4/6] Simplify completion config path resolutions --- crates/hir/src/semantics.rs | 5 ++ crates/ide-completion/src/context.rs | 103 +++++++++------------------ crates/ide-completion/src/snippet.rs | 35 +++------ 3 files changed, 49 insertions(+), 94 deletions(-) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index ae5251db98b0..7f44f396bf36 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -2060,6 +2060,11 @@ impl SemanticsScope<'_> { ) } + pub fn resolve_mod_path(&self, path: &ModPath) -> impl Iterator { + let items = self.resolver.resolve_module_path_in_items(self.db.upcast(), path); + items.iter_items().map(|(item, _)| item.into()) + } + /// Iterates over associated types that may be specified after the given path (using /// `Ty::Assoc` syntax). pub fn assoc_type_shorthand_candidates( diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index db909874dfe6..c43652842519 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -7,7 +7,7 @@ mod tests; use std::{iter, ops::ControlFlow}; use hir::{ - db::DefDatabase, HasAttrs, Local, ModuleSource, Name, PathResolution, ScopeDef, Semantics, + HasAttrs, Local, ModPath, ModuleDef, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Symbol, Type, TypeInfo, }; use ide_db::{ @@ -758,15 +758,43 @@ impl<'a> CompletionContext<'a> { }); let depth_from_crate_root = iter::successors(Some(module), |m| m.parent(db)) - // `BlockExpr` modules are not count as module depth + // `BlockExpr` modules do not count towards module depth .filter(|m| !matches!(m.definition_source(db).value, ModuleSource::BlockExpr(_))) .count() // exclude `m` itself .saturating_sub(1); - let exclude_traits = resolve_exclude_traits_list(db, config.exclude_traits); - let mut exclude_flyimport_traits = - resolve_exclude_traits_list(db, config.exclude_flyimport_traits); + let exclude_traits: FxHashSet<_> = config + .exclude_traits + .iter() + .filter_map(|path| { + scope + .resolve_mod_path(&ModPath::from_segments( + hir::PathKind::Plain, + path.split("::").map(Symbol::intern).map(Name::new_symbol_root), + )) + .find_map(|it| match it { + hir::ItemInNs::Types(ModuleDef::Trait(t)) => Some(t), + _ => None, + }) + }) + .collect(); + + let mut exclude_flyimport_traits: FxHashSet<_> = config + .exclude_flyimport_traits + .iter() + .filter_map(|path| { + scope + .resolve_mod_path(&ModPath::from_segments( + hir::PathKind::Plain, + path.split("::").map(Symbol::intern).map(Name::new_symbol_root), + )) + .find_map(|it| match it { + hir::ItemInNs::Types(ModuleDef::Trait(t)) => Some(t), + _ => None, + }) + }) + .collect(); exclude_flyimport_traits.extend(exclude_traits.iter().copied()); let complete_semicolon = if config.add_semicolon_to_unit { @@ -841,71 +869,6 @@ impl<'a> CompletionContext<'a> { } } -fn resolve_exclude_traits_list(db: &RootDatabase, traits: &[String]) -> FxHashSet { - let _g = tracing::debug_span!("resolve_exclude_trait_list", ?traits).entered(); - let crate_graph = db.crate_graph(); - let mut crate_name_to_def_map = FxHashMap::default(); - let mut result = FxHashSet::default(); - 'process_traits: for trait_ in traits { - let mut segments = trait_.split("::").peekable(); - let Some(crate_name) = segments.next() else { - tracing::error!( - ?trait_, - "error resolving trait from traits exclude list: invalid path" - ); - continue; - }; - let Some(def_map) = crate_name_to_def_map.entry(crate_name).or_insert_with(|| { - let krate = crate_graph - .iter() - .find(|&krate| crate_graph[krate].display_name.as_deref() == Some(crate_name)); - let def_map = krate.map(|krate| db.crate_def_map(krate)); - if def_map.is_none() { - tracing::error!( - "error resolving `{trait_}` from trait exclude lists: crate could not be found" - ); - } - def_map - }) else { - // Do not report more than one error for the same crate. - continue; - }; - let mut module = &def_map[hir::DefMap::ROOT]; - let trait_name = 'lookup_mods: { - while let Some(segment) = segments.next() { - if segments.peek().is_none() { - break 'lookup_mods segment; - } - - let Some(&inner) = - module.children.get(&Name::new_symbol_root(hir::Symbol::intern(segment))) - else { - tracing::error!( - "error resolving `{trait_}` from trait exclude lists: could not find module `{segment}`" - ); - continue 'process_traits; - }; - module = &def_map[inner]; - } - - tracing::error!("error resolving `{trait_}` from trait exclude lists: invalid path"); - continue 'process_traits; - }; - let resolved_trait = module - .scope - .trait_by_name(&Name::new_symbol_root(hir::Symbol::intern(trait_name))) - .map(hir::Trait::from); - let Some(resolved_trait) = resolved_trait else { - tracing::error!( - "error resolving `{trait_}` from trait exclude lists: trait could not be found" - ); - continue; - }; - result.insert(resolved_trait); - } - result -} - const OP_TRAIT_LANG_NAMES: &[&str] = &[ "add_assign", "add", diff --git a/crates/ide-completion/src/snippet.rs b/crates/ide-completion/src/snippet.rs index 5265aa8515b6..04bb178c658f 100644 --- a/crates/ide-completion/src/snippet.rs +++ b/crates/ide-completion/src/snippet.rs @@ -100,9 +100,9 @@ // } // ---- +use hir::{ModPath, Name, Symbol}; use ide_db::imports::import_assets::LocatedImport; use itertools::Itertools; -use syntax::{ast, AstNode, GreenNode, SyntaxNode}; use crate::context::CompletionContext; @@ -123,10 +123,7 @@ pub struct Snippet { pub scope: SnippetScope, pub description: Option>, snippet: String, - // These are `ast::Path`'s but due to SyntaxNodes not being Send we store these - // and reconstruct them on demand instead. This is cheaper than reparsing them - // from strings - requires: Box<[GreenNode]>, + requires: Box<[ModPath]>, } impl Snippet { @@ -143,7 +140,6 @@ impl Snippet { } let (requires, snippet, description) = validate_snippet(snippet, description, requires)?; Some(Snippet { - // Box::into doesn't work as that has a Copy bound 😒 postfix_triggers: postfix_triggers.iter().map(String::as_str).map(Into::into).collect(), prefix_triggers: prefix_triggers.iter().map(String::as_str).map(Into::into).collect(), scope, @@ -167,15 +163,11 @@ impl Snippet { } } -fn import_edits(ctx: &CompletionContext<'_>, requires: &[GreenNode]) -> Option> { +fn import_edits(ctx: &CompletionContext<'_>, requires: &[ModPath]) -> Option> { let import_cfg = ctx.config.import_path_config(); - let resolve = |import: &GreenNode| { - let path = ast::Path::cast(SyntaxNode::new_root(import.clone()))?; - let item = match ctx.scope.speculative_resolve(&path)? { - hir::PathResolution::Def(def) => def.into(), - _ => return None, - }; + let resolve = |import| { + let item = ctx.scope.resolve_mod_path(import).next()?; let path = ctx.module.find_use_path( ctx.db, item, @@ -198,19 +190,14 @@ fn validate_snippet( snippet: &[String], description: &str, requires: &[String], -) -> Option<(Box<[GreenNode]>, String, Option>)> { +) -> Option<(Box<[ModPath]>, String, Option>)> { let mut imports = Vec::with_capacity(requires.len()); for path in requires.iter() { - let use_path = - ast::SourceFile::parse(&format!("use {path};"), syntax::Edition::CURRENT_FIXME) - .syntax_node() - .descendants() - .find_map(ast::Path::cast)?; - if use_path.syntax().text() != path.as_str() { - return None; - } - let green = use_path.syntax().green().into_owned(); - imports.push(green); + let use_path = ModPath::from_segments( + hir::PathKind::Plain, + path.split("::").map(Symbol::intern).map(Name::new_symbol_root), + ); + imports.push(use_path); } let snippet = snippet.iter().join("\n"); let description = (!description.is_empty()) From 5303dc5d9976de583c351b3e575c491d1f08c3de Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 1 Jan 2025 15:05:24 +0100 Subject: [PATCH 5/6] Revamp auto-import exclude config --- .../src/completions/flyimport.rs | 42 +++++----- crates/ide-completion/src/config.rs | 8 +- crates/ide-completion/src/context.rs | 19 +++-- crates/ide-completion/src/lib.rs | 2 +- crates/ide-completion/src/tests.rs | 2 +- crates/ide-completion/src/tests/expression.rs | 6 +- crates/rust-analyzer/src/config.rs | 77 +++++++++++++++++-- .../src/integrated_benchmarks.rs | 6 +- docs/user/generated_config.adoc | 21 +++-- editors/code/package.json | 41 ++++++++-- 10 files changed, 169 insertions(+), 55 deletions(-) diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index afa94affb3b9..3b2b2fd706e1 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -8,6 +8,7 @@ use itertools::Itertools; use syntax::{ast, AstNode, SyntaxNode, ToSmolStr, T}; use crate::{ + config::AutoImportExclusionType, context::{ CompletionContext, DotAccess, PathCompletionCtx, PathKind, PatternContext, Qualified, TypeLocation, @@ -258,8 +259,6 @@ fn import_on_the_fly( let import_cfg = ctx.config.import_path_config(); - let completed_name = ctx.token.to_string(); - import_assets .search_for_imports(&ctx.sema, import_cfg, ctx.config.insert_use.prefix_kind) .filter(ns_filter) @@ -270,19 +269,17 @@ fn import_on_the_fly( && ctx.check_stability(original_item.attrs(ctx.db).as_deref()) }) .filter(|import| { - if let ModuleDef::Trait(trait_) = import.item_to_import.into_module_def() { - let excluded = ctx.exclude_flyimport_traits.contains(&trait_); - let trait_itself_imported = import.item_to_import == import.original_item; - if !excluded || trait_itself_imported { - return true; + let def = import.item_to_import.into_module_def(); + if let Some(&kind) = ctx.exclude_flyimport.get(&def) { + if kind == AutoImportExclusionType::Always { + return false; + } + let method_imported = import.item_to_import != import.original_item; + if method_imported { + return false; } - - let item = import.original_item.into_module_def(); - // Filter that item out, unless its name matches the name the user wrote exactly - in which case preserve it. - item.name(ctx.db).is_some_and(|name| name.eq_ident(&completed_name)) - } else { - true } + true }) .sorted_by(|a, b| { let key = |import_path| { @@ -363,8 +360,6 @@ fn import_on_the_fly_method( let cfg = ctx.config.import_path_config(); - let completed_name = ctx.token.to_string(); - import_assets .search_for_imports(&ctx.sema, cfg, ctx.config.insert_use.prefix_kind) .filter(|import| { @@ -372,14 +367,19 @@ fn import_on_the_fly_method( && !ctx.is_item_hidden(&import.original_item) }) .filter(|import| { - if let ModuleDef::Trait(trait_) = import.item_to_import.into_module_def() { - if !ctx.exclude_flyimport_traits.contains(&trait_) { - return true; + let def = import.item_to_import.into_module_def(); + if let Some(&kind) = ctx.exclude_flyimport.get(&def) { + if kind == AutoImportExclusionType::Always { + return false; } + let method_imported = import.item_to_import != import.original_item; + if method_imported { + return false; + } + } - let item = import.original_item.into_module_def(); - // Filter that method out, unless its name matches the name the user wrote exactly - in which case preserve it. - item.name(ctx.db).is_some_and(|name| name.eq_ident(&completed_name)) + if let ModuleDef::Trait(_) = import.item_to_import.into_module_def() { + !ctx.exclude_flyimport.contains_key(&def) } else { true } diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs index ed36fe8d0283..8b1ce11e8a45 100644 --- a/crates/ide-completion/src/config.rs +++ b/crates/ide-completion/src/config.rs @@ -28,10 +28,16 @@ pub struct CompletionConfig<'a> { pub snippets: Vec, pub limit: Option, pub fields_to_resolve: CompletionFieldsToResolve, - pub exclude_flyimport_traits: &'a [String], + pub exclude_flyimport: Vec<(String, AutoImportExclusionType)>, pub exclude_traits: &'a [String], } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum AutoImportExclusionType { + Always, + Methods, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum CallableSnippets { FillArguments, diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index c43652842519..183490c2ed84 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -22,6 +22,7 @@ use syntax::{ }; use crate::{ + config::AutoImportExclusionType, context::analysis::{expand_and_analyze, AnalysisResult}, CompletionConfig, }; @@ -466,7 +467,7 @@ pub(crate) struct CompletionContext<'a> { /// importing those traits. /// /// Note the trait *themselves* are not excluded, only their methods are. - pub(crate) exclude_flyimport_traits: FxHashSet, + pub(crate) exclude_flyimport: FxHashMap, /// Traits whose methods should always be excluded, even when in scope (compare `exclude_flyimport_traits`). /// They will *not* be excluded, however, if they are available as a generic bound. /// @@ -780,22 +781,20 @@ impl<'a> CompletionContext<'a> { }) .collect(); - let mut exclude_flyimport_traits: FxHashSet<_> = config - .exclude_flyimport_traits + let mut exclude_flyimport: FxHashMap<_, _> = config + .exclude_flyimport .iter() - .filter_map(|path| { + .flat_map(|(path, kind)| { scope .resolve_mod_path(&ModPath::from_segments( hir::PathKind::Plain, path.split("::").map(Symbol::intern).map(Name::new_symbol_root), )) - .find_map(|it| match it { - hir::ItemInNs::Types(ModuleDef::Trait(t)) => Some(t), - _ => None, - }) + .map(|it| (it.into_module_def(), *kind)) }) .collect(); - exclude_flyimport_traits.extend(exclude_traits.iter().copied()); + exclude_flyimport + .extend(exclude_traits.iter().map(|&t| (t.into(), AutoImportExclusionType::Always))); let complete_semicolon = if config.add_semicolon_to_unit { let inside_closure_ret = token.parent_ancestors().try_for_each(|ancestor| { @@ -861,7 +860,7 @@ impl<'a> CompletionContext<'a> { qualifier_ctx, locals, depth_from_crate_root, - exclude_flyimport_traits, + exclude_flyimport, exclude_traits, complete_semicolon, }; diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index 05563bc912d9..ca6c9ad9f083 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -31,7 +31,7 @@ use crate::{ }; pub use crate::{ - config::{CallableSnippets, CompletionConfig}, + config::{AutoImportExclusionType, CallableSnippets, CompletionConfig}, item::{ CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceReturnType, CompletionRelevanceTypeMatch, diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs index 3b2d3fbbfc11..1815f3405321 100644 --- a/crates/ide-completion/src/tests.rs +++ b/crates/ide-completion/src/tests.rs @@ -85,7 +85,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig<'_> = CompletionConfig { snippets: Vec::new(), limit: None, fields_to_resolve: CompletionFieldsToResolve::empty(), - exclude_flyimport_traits: &[], + exclude_flyimport: vec![], exclude_traits: &[], }; diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs index d1bbf1e8d0bc..a9db1d953189 100644 --- a/crates/ide-completion/src/tests/expression.rs +++ b/crates/ide-completion/src/tests/expression.rs @@ -2,6 +2,7 @@ use expect_test::{expect, Expect}; use crate::{ + config::AutoImportExclusionType, tests::{ check_edit, check_empty, completion_list, completion_list_with_config, BASE_ITEMS_FIXTURE, TEST_CONFIG, @@ -1605,7 +1606,10 @@ fn foo() { fn flyimport_excluded_trait_method_is_excluded_from_flyimport() { check_with_config( CompletionConfig { - exclude_flyimport_traits: &["test::module2::ExcludedTrait".to_owned()], + exclude_flyimport: vec![( + "test::module2::ExcludedTrait".to_owned(), + AutoImportExclusionType::Methods, + )], ..TEST_CONFIG }, r#" diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index b6678c12c703..b7297d0c02a9 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -440,22 +440,27 @@ config_data! { /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. completion_autoimport_enable: bool = true, - /// A list of full paths to traits to exclude from flyimport. + /// A list of full paths to items to exclude from auto-importing completions. /// /// Traits in this list won't have their methods suggested in completions unless the trait /// is in scope. /// + /// You can either specify a string path which defaults to type "always" or use the more verbose + /// form `{ "path": "path::to::item", type: "always" }`. + /// + /// For traits the type "methods" can be used to only exclude the methods but not the trait itself. + /// /// This setting also inherits `#rust-analyzer.completion.excludeTraits#`. - completion_autoimport_excludeTraits: Vec = vec![ - "core::borrow::Borrow".to_owned(), - "core::borrow::BorrowMut".to_owned(), + completion_autoimport_exclude: Vec = vec![ + AutoImportExclusion::Verbose { path: "core::borrow::Borrow".to_owned(), r#type: AutoImportExclusionType::Methods }, + AutoImportExclusion::Verbose { path: "core::borrow::BorrowMut".to_owned(), r#type: AutoImportExclusionType::Methods }, ], /// Toggles the additional completions that automatically show method calls and field accesses /// with `self` prefixed to them when inside a method. completion_autoself_enable: bool = true, /// Whether to add parenthesis and argument snippets when completing function. completion_callable_snippets: CallableCompletionDef = CallableCompletionDef::FillArguments, - /// A list of full paths to traits to exclude from completion. + /// A list of full paths to traits whose methods to exclude from completion. /// /// Methods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`. /// @@ -1478,7 +1483,26 @@ impl Config { } else { CompletionFieldsToResolve::from_client_capabilities(&client_capability_fields) }, - exclude_flyimport_traits: self.completion_autoimport_excludeTraits(source_root), + exclude_flyimport: self + .completion_autoimport_exclude(source_root) + .iter() + .map(|it| match it { + AutoImportExclusion::Path(path) => { + (path.clone(), ide_completion::AutoImportExclusionType::Always) + } + AutoImportExclusion::Verbose { path, r#type } => ( + path.clone(), + match r#type { + AutoImportExclusionType::Always => { + ide_completion::AutoImportExclusionType::Always + } + AutoImportExclusionType::Methods => { + ide_completion::AutoImportExclusionType::Methods + } + }, + ), + }) + .collect(), exclude_traits: self.completion_excludeTraits(source_root), } } @@ -2419,6 +2443,21 @@ enum ExprFillDefaultDef { Default, } +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +#[serde(rename_all = "snake_case")] +pub enum AutoImportExclusion { + Path(String), + Verbose { path: String, r#type: AutoImportExclusionType }, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum AutoImportExclusionType { + Always, + Methods, +} + #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ImportGranularityDef { @@ -3490,6 +3529,32 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json } ] }, + "Vec" => set! { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "object", + "properties": { + "path": { + "type": "string", + }, + "type": { + "type": "string", + "enum": ["always", "methods"], + "enumDescriptions": [ + "Do not show this item or its methods (if it is a trait) in auto-import completions.", + "Do not show this traits methods in auto-import completions." + ], + }, + } + } + ] + } + }, _ => panic!("missing entry for {ty}: {default} (field {field})"), } diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index bd164fe44081..fcfd06679bf2 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -174,7 +174,7 @@ fn integrated_completion_benchmark() { limit: None, add_semicolon_to_unit: true, fields_to_resolve: CompletionFieldsToResolve::empty(), - exclude_flyimport_traits: &[], + exclude_flyimport: vec![], exclude_traits: &[], }; let position = @@ -224,7 +224,7 @@ fn integrated_completion_benchmark() { limit: None, add_semicolon_to_unit: true, fields_to_resolve: CompletionFieldsToResolve::empty(), - exclude_flyimport_traits: &[], + exclude_flyimport: vec![], exclude_traits: &[], }; let position = @@ -272,7 +272,7 @@ fn integrated_completion_benchmark() { limit: None, add_semicolon_to_unit: true, fields_to_resolve: CompletionFieldsToResolve::empty(), - exclude_flyimport_traits: &[], + exclude_flyimport: vec![], exclude_traits: &[], }; let position = diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index bce26f7dd74f..a44f5a12b284 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -286,21 +286,32 @@ In `match` arms it completes a comma instead. Toggles the additional completions that automatically add imports when completed. Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. -- -[[rust-analyzer.completion.autoimport.excludeTraits]]rust-analyzer.completion.autoimport.excludeTraits:: +[[rust-analyzer.completion.autoimport.exclude]]rust-analyzer.completion.autoimport.exclude:: + -- Default: ---- [ - "core::borrow::Borrow", - "core::borrow::BorrowMut" + { + "path": "core::borrow::Borrow", + "type": "methods" + }, + { + "path": "core::borrow::BorrowMut", + "type": "methods" + } ] ---- -A list of full paths to traits to exclude from flyimport. +A list of full paths to items to exclude from auto-importing completions. Traits in this list won't have their methods suggested in completions unless the trait is in scope. +You can either specify a string path which defaults to type "always" or use the more verbose +form `{ "path": "path::to::item", type: "always" }`. + +For traits the type "methods" can be used to only exclude the methods but not the trait itself. + This setting also inherits `#rust-analyzer.completion.excludeTraits#`. -- @@ -318,7 +329,7 @@ Whether to add parenthesis and argument snippets when completing function. [[rust-analyzer.completion.excludeTraits]]rust-analyzer.completion.excludeTraits (default: `[]`):: + -- -A list of full paths to traits to exclude from completion. +A list of full paths to traits whose methods to exclude from completion. Methods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`. diff --git a/editors/code/package.json b/editors/code/package.json index 31419a1942d4..03c00a37fb16 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1142,15 +1142,44 @@ { "title": "completion", "properties": { - "rust-analyzer.completion.autoimport.excludeTraits": { - "markdownDescription": "A list of full paths to traits to exclude from flyimport.\n\nTraits in this list won't have their methods suggested in completions unless the trait\nis in scope.\n\nThis setting also inherits `#rust-analyzer.completion.excludeTraits#`.", + "rust-analyzer.completion.autoimport.exclude": { + "markdownDescription": "A list of full paths to items to exclude from auto-importing completions.\n\nTraits in this list won't have their methods suggested in completions unless the trait\nis in scope.\n\nYou can either specify a string path which defaults to type \"always\" or use the more verbose\nform `{ \"path\": \"path::to::item\", type: \"always\" }`.\n\nFor traits the type \"methods\" can be used to only exclude the methods but not the trait itself.\n\nThis setting also inherits `#rust-analyzer.completion.excludeTraits#`.", "default": [ - "core::borrow::Borrow", - "core::borrow::BorrowMut" + { + "path": "core::borrow::Borrow", + "type": "methods" + }, + { + "path": "core::borrow::BorrowMut", + "type": "methods" + } ], "type": "array", "items": { - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "always", + "methods" + ], + "enumDescriptions": [ + "Do not show this item or its methods (if it is a trait) in auto-import completions.", + "Do not show this traits methods in auto-import completions." + ] + } + } + } + ] } } } @@ -1189,7 +1218,7 @@ "title": "completion", "properties": { "rust-analyzer.completion.excludeTraits": { - "markdownDescription": "A list of full paths to traits to exclude from completion.\n\nMethods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`.\n\nNote that the trait themselves can still be completed.", + "markdownDescription": "A list of full paths to traits whose methods to exclude from completion.\n\nMethods from these traits won't be completed, even if the trait is in scope. However, they will still be suggested on expressions whose type is `dyn Trait`, `impl Trait` or `T where T: Trait`.\n\nNote that the trait themselves can still be completed.", "default": [], "type": "array", "items": { From 1adc805dfa7b78912059edfbd9bfe8d6b9f5b40d Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 1 Jan 2025 15:21:49 +0100 Subject: [PATCH 6/6] Cleanup --- crates/hir/src/lib.rs | 113 +++++++----------- crates/ide-completion/src/completions/expr.rs | 1 - 2 files changed, 46 insertions(+), 68 deletions(-) diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 4c5f9d2a5a17..55cf3b7fecc8 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -5219,49 +5219,25 @@ impl Type { traits_in_scope: &FxHashSet, with_local_impls: Option, name: Option<&Name>, - callback: impl FnMut(Function) -> Option, + mut callback: impl FnMut(Function) -> Option, ) -> Option { - struct Callback { - f: F, - slot: Option, - } - impl MethodCandidateCallback for &'_ mut Callback - where - F: FnMut(Function) -> Option, - { - fn on_inherent_method(&mut self, f: Function) -> ControlFlow<()> { - match (self.f)(f) { - it @ Some(_) => { - self.slot = it; - ControlFlow::Break(()) - } - None => ControlFlow::Continue(()), - } - } - - fn on_trait_method(&mut self, f: Function) -> ControlFlow<()> { - match (self.f)(f) { - it @ Some(_) => { - self.slot = it; - ControlFlow::Break(()) - } - None => ControlFlow::Continue(()), - } - } - } - let _p = tracing::info_span!("iterate_method_candidates_with_traits").entered(); - let mut callback = Callback { slot: None, f: callback }; - + let mut slot = None; self.iterate_method_candidates_split_inherent( db, scope, traits_in_scope, with_local_impls, name, - &mut callback, + |f| match callback(f) { + it @ Some(_) => { + slot = it; + ControlFlow::Break(()) + } + None => ControlFlow::Continue(()), + }, ); - callback.slot + slot } pub fn iterate_method_candidates( @@ -5361,39 +5337,10 @@ impl Type { traits_in_scope: &FxHashSet, with_local_impls: Option, name: Option<&Name>, - callback: impl FnMut(AssocItem) -> Option, + mut callback: impl FnMut(AssocItem) -> Option, ) -> Option { - struct Callback { - f: F, - slot: Option, - } - impl PathCandidateCallback for &'_ mut Callback - where - F: FnMut(AssocItem) -> Option, - { - fn on_inherent_item(&mut self, item: AssocItem) -> ControlFlow<()> { - match (self.f)(item) { - it @ Some(_) => { - self.slot = it; - ControlFlow::Break(()) - } - None => ControlFlow::Continue(()), - } - } - - fn on_trait_item(&mut self, item: AssocItem) -> ControlFlow<()> { - match (self.f)(item) { - it @ Some(_) => { - self.slot = it; - ControlFlow::Break(()) - } - None => ControlFlow::Continue(()), - } - } - } - let _p = tracing::info_span!("iterate_path_candidates").entered(); - let mut callback = Callback { slot: None, f: callback }; + let mut slot = None; self.iterate_path_candidates_split_inherent( db, @@ -5401,9 +5348,15 @@ impl Type { traits_in_scope, with_local_impls, name, - &mut callback, + |item| match callback(item) { + it @ Some(_) => { + slot = it; + ControlFlow::Break(()) + } + None => ControlFlow::Continue(()), + }, ); - callback.slot + slot } /// Iterates over inherent methods. @@ -6167,8 +6120,34 @@ pub trait MethodCandidateCallback { fn on_trait_method(&mut self, f: Function) -> ControlFlow<()>; } +impl MethodCandidateCallback for F +where + F: FnMut(Function) -> ControlFlow<()>, +{ + fn on_inherent_method(&mut self, f: Function) -> ControlFlow<()> { + self(f) + } + + fn on_trait_method(&mut self, f: Function) -> ControlFlow<()> { + self(f) + } +} + pub trait PathCandidateCallback { fn on_inherent_item(&mut self, item: AssocItem) -> ControlFlow<()>; fn on_trait_item(&mut self, item: AssocItem) -> ControlFlow<()>; } + +impl PathCandidateCallback for F +where + F: FnMut(AssocItem) -> ControlFlow<()>, +{ + fn on_inherent_item(&mut self, item: AssocItem) -> ControlFlow<()> { + self(item) + } + + fn on_trait_item(&mut self, item: AssocItem) -> ControlFlow<()> { + self(item) + } +} diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index e9eb3fc8428f..f748ce9ad63f 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -30,7 +30,6 @@ where 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.