diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index ccf575e84f5..e49e641b3d7 100644 --- a/crates/assists/src/handlers/auto_import.rs +++ b/crates/assists/src/handlers/auto_import.rs @@ -100,7 +100,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> let group = import_group_message(import_assets.import_candidate()); let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; let syntax = scope.as_syntax_node(); - for import in proposed_imports { + for (import, _) in proposed_imports { acc.add_group( &group, AssistId("auto_import", AssistKind::QuickFix), diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs new file mode 100644 index 00000000000..f436bdbbfa6 --- /dev/null +++ b/crates/assists/src/handlers/qualify_path.rs @@ -0,0 +1,1048 @@ +use std::iter; + +use hir::AsName; +use ide_db::RootDatabase; +use syntax::{ + ast, + ast::{make, ArgListOwner}, + AstNode, +}; +use test_utils::mark; + +use crate::{ + assist_context::{AssistContext, Assists}, + utils::import_assets::{ImportAssets, ImportCandidate}, + utils::mod_path_to_ast, + AssistId, AssistKind, GroupLabel, +}; + +// Assist: qualify_path +// +// If the name is unresolved, provides all possible qualified paths for it. +// +// ``` +// fn main() { +// let map = HashMap<|>::new(); +// } +// # pub mod std { pub mod collections { pub struct HashMap { } } } +// ``` +// -> +// ``` +// fn main() { +// let map = std::collections::HashMap::new(); +// } +// # pub mod std { pub mod collections { pub struct HashMap { } } } +// ``` +pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let import_assets = + if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::() { + ImportAssets::for_regular_path(path_under_caret, &ctx.sema) + } else if let Some(method_under_caret) = + ctx.find_node_at_offset_with_descend::() + { + ImportAssets::for_method_call(method_under_caret, &ctx.sema) + } else { + None + }?; + let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema); + if proposed_imports.is_empty() { + return None; + } + + let candidate = import_assets.import_candidate(); + let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; + + let qualify_candidate = match candidate { + ImportCandidate::QualifierStart(_) => { + mark::hit!(qualify_path_qualifier_start); + let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; + let segment = path.segment()?; + QualifyCandidate::QualifierStart(segment) + } + ImportCandidate::UnqualifiedName(_) => { + mark::hit!(qualify_path_unqualified_name); + QualifyCandidate::UnqualifiedName + } + ImportCandidate::TraitAssocItem(_) => { + mark::hit!(qualify_path_trait_assoc_item); + let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; + let (qualifier, segment) = (path.qualifier()?, path.segment()?); + QualifyCandidate::TraitAssocItem(qualifier, segment) + } + ImportCandidate::TraitMethod(_) => { + mark::hit!(qualify_path_trait_method); + let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?; + QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr) + } + }; + + let group_label = group_label(candidate); + for (import, item) in proposed_imports { + acc.add_group( + &group_label, + AssistId("qualify_path", AssistKind::QuickFix), + label(candidate, &import), + range, + |builder| { + qualify_candidate.qualify( + |replace_with: String| builder.replace(range, replace_with), + import, + item, + ) + }, + ); + } + Some(()) +} + +enum QualifyCandidate<'db> { + QualifierStart(ast::PathSegment), + UnqualifiedName, + TraitAssocItem(ast::Path, ast::PathSegment), + TraitMethod(&'db RootDatabase, ast::MethodCallExpr), +} + +impl QualifyCandidate<'_> { + fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) { + match self { + QualifyCandidate::QualifierStart(segment) => { + let import = mod_path_to_ast(&import); + replacer(format!("{}::{}", import, segment)); + } + QualifyCandidate::UnqualifiedName => replacer(mod_path_to_ast(&import).to_string()), + QualifyCandidate::TraitAssocItem(qualifier, segment) => { + let import = mod_path_to_ast(&import); + replacer(format!("<{} as {}>::{}", qualifier, import, segment)); + } + &QualifyCandidate::TraitMethod(db, ref mcall_expr) => { + Self::qualify_trait_method(db, mcall_expr, replacer, import, item); + } + } + } + + fn qualify_trait_method( + db: &RootDatabase, + mcall_expr: &ast::MethodCallExpr, + mut replacer: impl FnMut(String), + import: hir::ModPath, + item: hir::ItemInNs, + ) -> Option<()> { + let receiver = mcall_expr.receiver()?; + let trait_method_name = mcall_expr.name_ref()?; + let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args()); + let trait_ = item_as_trait(item)?; + let method = find_trait_method(db, trait_, &trait_method_name)?; + if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) { + let import = mod_path_to_ast(&import); + let receiver = match self_access { + hir::Access::Shared => make::expr_ref(receiver, false), + hir::Access::Exclusive => make::expr_ref(receiver, true), + hir::Access::Owned => receiver, + }; + replacer(format!( + "{}::{}{}", + import, + trait_method_name, + match arg_list.clone() { + Some(args) => make::arg_list(iter::once(receiver).chain(args)), + None => make::arg_list(iter::once(receiver)), + } + )); + } + Some(()) + } +} + +fn find_trait_method( + db: &RootDatabase, + trait_: hir::Trait, + trait_method_name: &ast::NameRef, +) -> Option { + if let Some(hir::AssocItem::Function(method)) = + trait_.items(db).into_iter().find(|item: &hir::AssocItem| { + item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false) + }) + { + Some(method) + } else { + None + } +} + +fn item_as_trait(item: hir::ItemInNs) -> Option { + if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(item.as_module_def_id()?) { + Some(trait_) + } else { + None + } +} + +fn group_label(candidate: &ImportCandidate) -> GroupLabel { + let name = match candidate { + ImportCandidate::UnqualifiedName(it) | ImportCandidate::QualifierStart(it) => &it.name, + ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, + }; + GroupLabel(format!("Qualify {}", name)) +} + +fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { + match candidate { + ImportCandidate::UnqualifiedName(_) => format!("Qualify as `{}`", &import), + ImportCandidate::QualifierStart(_) => format!("Qualify with `{}`", &import), + ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import), + ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import), + } +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; + + use super::*; + + #[test] + fn applicable_when_found_an_import_partial() { + mark::check!(qualify_path_unqualified_name); + check_assist( + qualify_path, + r" + mod std { + pub mod fmt { + pub struct Formatter; + } + } + + use std::fmt; + + <|>Formatter + ", + r" + mod std { + pub mod fmt { + pub struct Formatter; + } + } + + use std::fmt; + + fmt::Formatter + ", + ); + } + + #[test] + fn applicable_when_found_an_import() { + check_assist( + qualify_path, + r" + <|>PubStruct + + pub mod PubMod { + pub struct PubStruct; + } + ", + r" + PubMod::PubStruct + + pub mod PubMod { + pub struct PubStruct; + } + ", + ); + } + + #[test] + fn applicable_in_macros() { + check_assist( + qualify_path, + r" + macro_rules! foo { + ($i:ident) => { fn foo(a: $i) {} } + } + foo!(Pub<|>Struct); + + pub mod PubMod { + pub struct PubStruct; + } + ", + r" + macro_rules! foo { + ($i:ident) => { fn foo(a: $i) {} } + } + foo!(PubMod::PubStruct); + + pub mod PubMod { + pub struct PubStruct; + } + ", + ); + } + + #[test] + fn applicable_when_found_multiple_imports() { + check_assist( + qualify_path, + r" + PubSt<|>ruct + + pub mod PubMod1 { + pub struct PubStruct; + } + pub mod PubMod2 { + pub struct PubStruct; + } + pub mod PubMod3 { + pub struct PubStruct; + } + ", + r" + PubMod3::PubStruct + + pub mod PubMod1 { + pub struct PubStruct; + } + pub mod PubMod2 { + pub struct PubStruct; + } + pub mod PubMod3 { + pub struct PubStruct; + } + ", + ); + } + + #[test] + fn not_applicable_for_already_imported_types() { + check_assist_not_applicable( + qualify_path, + r" + use PubMod::PubStruct; + + PubStruct<|> + + pub mod PubMod { + pub struct PubStruct; + } + ", + ); + } + + #[test] + fn not_applicable_for_types_with_private_paths() { + check_assist_not_applicable( + qualify_path, + r" + PrivateStruct<|> + + pub mod PubMod { + struct PrivateStruct; + } + ", + ); + } + + #[test] + fn not_applicable_when_no_imports_found() { + check_assist_not_applicable( + qualify_path, + " + PubStruct<|>", + ); + } + + #[test] + fn not_applicable_in_import_statements() { + check_assist_not_applicable( + qualify_path, + r" + use PubStruct<|>; + + pub mod PubMod { + pub struct PubStruct; + }", + ); + } + + #[test] + fn qualify_function() { + check_assist( + qualify_path, + r" + test_function<|> + + pub mod PubMod { + pub fn test_function() {}; + } + ", + r" + PubMod::test_function + + pub mod PubMod { + pub fn test_function() {}; + } + ", + ); + } + + #[test] + fn qualify_macro() { + check_assist( + qualify_path, + r" +//- /lib.rs crate:crate_with_macro +#[macro_export] +macro_rules! foo { + () => () +} + +//- /main.rs crate:main deps:crate_with_macro +fn main() { + foo<|> +} +", + r" +fn main() { + crate_with_macro::foo +} +", + ); + } + + #[test] + fn qualify_path_target() { + check_assist_target( + qualify_path, + r" + struct AssistInfo { + group_label: Option<<|>GroupLabel>, + } + + mod m { pub struct GroupLabel; } + ", + "GroupLabel", + ) + } + + #[test] + fn not_applicable_when_path_start_is_imported() { + check_assist_not_applicable( + qualify_path, + r" + pub mod mod1 { + pub mod mod2 { + pub mod mod3 { + pub struct TestStruct; + } + } + } + + use mod1::mod2; + fn main() { + mod2::mod3::TestStruct<|> + } + ", + ); + } + + #[test] + fn not_applicable_for_imported_function() { + check_assist_not_applicable( + qualify_path, + r" + pub mod test_mod { + pub fn test_function() {} + } + + use test_mod::test_function; + fn main() { + test_function<|> + } + ", + ); + } + + #[test] + fn associated_struct_function() { + check_assist( + qualify_path, + r" + mod test_mod { + pub struct TestStruct {} + impl TestStruct { + pub fn test_function() {} + } + } + + fn main() { + TestStruct::test_function<|> + } + ", + r" + mod test_mod { + pub struct TestStruct {} + impl TestStruct { + pub fn test_function() {} + } + } + + fn main() { + test_mod::TestStruct::test_function + } + ", + ); + } + + #[test] + fn associated_struct_const() { + mark::check!(qualify_path_qualifier_start); + check_assist( + qualify_path, + r" + mod test_mod { + pub struct TestStruct {} + impl TestStruct { + const TEST_CONST: u8 = 42; + } + } + + fn main() { + TestStruct::TEST_CONST<|> + } + ", + r" + mod test_mod { + pub struct TestStruct {} + impl TestStruct { + const TEST_CONST: u8 = 42; + } + } + + fn main() { + test_mod::TestStruct::TEST_CONST + } + ", + ); + } + + #[test] + fn associated_trait_function() { + check_assist( + qualify_path, + r" + mod test_mod { + pub trait TestTrait { + fn test_function(); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_function() {} + } + } + + fn main() { + test_mod::TestStruct::test_function<|> + } + ", + r" + mod test_mod { + pub trait TestTrait { + fn test_function(); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_function() {} + } + } + + fn main() { + ::test_function + } + ", + ); + } + + #[test] + fn not_applicable_for_imported_trait_for_function() { + check_assist_not_applicable( + qualify_path, + r" + mod test_mod { + pub trait TestTrait { + fn test_function(); + } + pub trait TestTrait2 { + fn test_function(); + } + pub enum TestEnum { + One, + Two, + } + impl TestTrait2 for TestEnum { + fn test_function() {} + } + impl TestTrait for TestEnum { + fn test_function() {} + } + } + + use test_mod::TestTrait2; + fn main() { + test_mod::TestEnum::test_function<|>; + } + ", + ) + } + + #[test] + fn associated_trait_const() { + mark::check!(qualify_path_trait_assoc_item); + check_assist( + qualify_path, + r" + mod test_mod { + pub trait TestTrait { + const TEST_CONST: u8; + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + const TEST_CONST: u8 = 42; + } + } + + fn main() { + test_mod::TestStruct::TEST_CONST<|> + } + ", + r" + mod test_mod { + pub trait TestTrait { + const TEST_CONST: u8; + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + const TEST_CONST: u8 = 42; + } + } + + fn main() { + ::TEST_CONST + } + ", + ); + } + + #[test] + fn not_applicable_for_imported_trait_for_const() { + check_assist_not_applicable( + qualify_path, + r" + mod test_mod { + pub trait TestTrait { + const TEST_CONST: u8; + } + pub trait TestTrait2 { + const TEST_CONST: f64; + } + pub enum TestEnum { + One, + Two, + } + impl TestTrait2 for TestEnum { + const TEST_CONST: f64 = 42.0; + } + impl TestTrait for TestEnum { + const TEST_CONST: u8 = 42; + } + } + + use test_mod::TestTrait2; + fn main() { + test_mod::TestEnum::TEST_CONST<|>; + } + ", + ) + } + + #[test] + fn trait_method() { + mark::check!(qualify_path_trait_method); + check_assist( + qualify_path, + r" + mod test_mod { + pub trait TestTrait { + fn test_method(&self); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_method(&self) {} + } + } + + fn main() { + let test_struct = test_mod::TestStruct {}; + test_struct.test_meth<|>od() + } + ", + r" + mod test_mod { + pub trait TestTrait { + fn test_method(&self); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_method(&self) {} + } + } + + fn main() { + let test_struct = test_mod::TestStruct {}; + test_mod::TestTrait::test_method(&test_struct) + } + ", + ); + } + + #[test] + fn trait_method_multi_params() { + check_assist( + qualify_path, + r" + mod test_mod { + pub trait TestTrait { + fn test_method(&self, test: i32); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_method(&self, test: i32) {} + } + } + + fn main() { + let test_struct = test_mod::TestStruct {}; + test_struct.test_meth<|>od(42) + } + ", + r" + mod test_mod { + pub trait TestTrait { + fn test_method(&self, test: i32); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_method(&self, test: i32) {} + } + } + + fn main() { + let test_struct = test_mod::TestStruct {}; + test_mod::TestTrait::test_method(&test_struct, 42) + } + ", + ); + } + + #[test] + fn trait_method_consume() { + check_assist( + qualify_path, + r" + mod test_mod { + pub trait TestTrait { + fn test_method(self); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_method(self) {} + } + } + + fn main() { + let test_struct = test_mod::TestStruct {}; + test_struct.test_meth<|>od() + } + ", + r" + mod test_mod { + pub trait TestTrait { + fn test_method(self); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_method(self) {} + } + } + + fn main() { + let test_struct = test_mod::TestStruct {}; + test_mod::TestTrait::test_method(test_struct) + } + ", + ); + } + + #[test] + fn trait_method_cross_crate() { + check_assist( + qualify_path, + r" + //- /main.rs crate:main deps:dep + fn main() { + let test_struct = dep::test_mod::TestStruct {}; + test_struct.test_meth<|>od() + } + //- /dep.rs crate:dep + pub mod test_mod { + pub trait TestTrait { + fn test_method(&self); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_method(&self) {} + } + } + ", + r" + fn main() { + let test_struct = dep::test_mod::TestStruct {}; + dep::test_mod::TestTrait::test_method(&test_struct) + } + ", + ); + } + + #[test] + fn assoc_fn_cross_crate() { + check_assist( + qualify_path, + r" + //- /main.rs crate:main deps:dep + fn main() { + dep::test_mod::TestStruct::test_func<|>tion + } + //- /dep.rs crate:dep + pub mod test_mod { + pub trait TestTrait { + fn test_function(); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_function() {} + } + } + ", + r" + fn main() { + ::test_function + } + ", + ); + } + + #[test] + fn assoc_const_cross_crate() { + check_assist( + qualify_path, + r" + //- /main.rs crate:main deps:dep + fn main() { + dep::test_mod::TestStruct::CONST<|> + } + //- /dep.rs crate:dep + pub mod test_mod { + pub trait TestTrait { + const CONST: bool; + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + const CONST: bool = true; + } + } + ", + r" + fn main() { + ::CONST + } + ", + ); + } + + #[test] + fn assoc_fn_as_method_cross_crate() { + check_assist_not_applicable( + qualify_path, + r" + //- /main.rs crate:main deps:dep + fn main() { + let test_struct = dep::test_mod::TestStruct {}; + test_struct.test_func<|>tion() + } + //- /dep.rs crate:dep + pub mod test_mod { + pub trait TestTrait { + fn test_function(); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_function() {} + } + } + ", + ); + } + + #[test] + fn private_trait_cross_crate() { + check_assist_not_applicable( + qualify_path, + r" + //- /main.rs crate:main deps:dep + fn main() { + let test_struct = dep::test_mod::TestStruct {}; + test_struct.test_meth<|>od() + } + //- /dep.rs crate:dep + pub mod test_mod { + trait TestTrait { + fn test_method(&self); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_method(&self) {} + } + } + ", + ); + } + + #[test] + fn not_applicable_for_imported_trait_for_method() { + check_assist_not_applicable( + qualify_path, + r" + mod test_mod { + pub trait TestTrait { + fn test_method(&self); + } + pub trait TestTrait2 { + fn test_method(&self); + } + pub enum TestEnum { + One, + Two, + } + impl TestTrait2 for TestEnum { + fn test_method(&self) {} + } + impl TestTrait for TestEnum { + fn test_method(&self) {} + } + } + + use test_mod::TestTrait2; + fn main() { + let one = test_mod::TestEnum::One; + one.test<|>_method(); + } + ", + ) + } + + #[test] + fn dep_import() { + check_assist( + qualify_path, + r" +//- /lib.rs crate:dep +pub struct Struct; + +//- /main.rs crate:main deps:dep +fn main() { + Struct<|> +} +", + r" +fn main() { + dep::Struct +} +", + ); + } + + #[test] + fn whole_segment() { + // Tests that only imports whose last segment matches the identifier get suggested. + check_assist( + qualify_path, + r" +//- /lib.rs crate:dep +pub mod fmt { + pub trait Display {} +} + +pub fn panic_fmt() {} + +//- /main.rs crate:main deps:dep +struct S; + +impl f<|>mt::Display for S {} +", + r" +struct S; + +impl dep::fmt::Display for S {} +", + ); + } + + #[test] + fn macro_generated() { + // Tests that macro-generated items are suggested from external crates. + check_assist( + qualify_path, + r" +//- /lib.rs crate:dep +macro_rules! mac { + () => { + pub struct Cheese; + }; +} + +mac!(); + +//- /main.rs crate:main deps:dep +fn main() { + Cheese<|>; +} +", + r" +fn main() { + dep::Cheese; +} +", + ); + } + + #[test] + fn casing() { + // Tests that differently cased names don't interfere and we only suggest the matching one. + check_assist( + qualify_path, + r" +//- /lib.rs crate:dep +pub struct FMT; +pub struct fmt; + +//- /main.rs crate:main deps:dep +fn main() { + FMT<|>; +} +", + r" +fn main() { + dep::FMT; +} +", + ); + } +} diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index a2bec818c1a..f29b8212f9c 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs @@ -150,6 +150,7 @@ mod handlers { mod merge_match_arms; mod move_bounds; mod move_guard; + mod qualify_path; mod raw_string; mod remove_dbg; mod remove_mut; @@ -196,6 +197,7 @@ mod handlers { move_bounds::move_bounds_to_where_clause, move_guard::move_arm_cond_to_match_guard, move_guard::move_guard_to_arm_body, + qualify_path::qualify_path, raw_string::add_hash, raw_string::make_raw_string, raw_string::make_usual_string, diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index 41f536574ea..7d561826318 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs @@ -712,6 +712,25 @@ fn handle(action: Action) { ) } +#[test] +fn doctest_qualify_path() { + check_doc_test( + "qualify_path", + r#####" +fn main() { + let map = HashMap<|>::new(); +} +pub mod std { pub mod collections { pub struct HashMap { } } } +"#####, + r#####" +fn main() { + let map = std::collections::HashMap::new(); +} +pub mod std { pub mod collections { pub struct HashMap { } } } +"#####, + ) +} + #[test] fn doctest_remove_dbg() { check_doc_test( diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs index 601f51098d0..23db3a74bdb 100644 --- a/crates/assists/src/utils/import_assets.rs +++ b/crates/assists/src/utils/import_assets.rs @@ -1,6 +1,4 @@ //! Look up accessible paths for items. -use std::collections::BTreeSet; - use either::Either; use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; use ide_db::{imports_locator, RootDatabase}; @@ -29,12 +27,12 @@ pub(crate) enum ImportCandidate { #[derive(Debug)] pub(crate) struct TraitImportCandidate { pub ty: hir::Type, - pub name: String, + pub name: ast::NameRef, } #[derive(Debug)] pub(crate) struct PathImportCandidate { - pub name: String, + pub name: ast::NameRef, } #[derive(Debug)] @@ -86,9 +84,9 @@ impl ImportAssets { fn get_search_query(&self) -> &str { match &self.import_candidate { ImportCandidate::UnqualifiedName(candidate) - | ImportCandidate::QualifierStart(candidate) => &candidate.name, + | ImportCandidate::QualifierStart(candidate) => candidate.name.text(), ImportCandidate::TraitAssocItem(candidate) - | ImportCandidate::TraitMethod(candidate) => &candidate.name, + | ImportCandidate::TraitMethod(candidate) => candidate.name.text(), } } @@ -96,7 +94,7 @@ impl ImportAssets { &self, sema: &Semantics, config: &InsertUseConfig, - ) -> BTreeSet { + ) -> Vec<(hir::ModPath, hir::ItemInNs)> { let _p = profile::span("import_assists::search_for_imports"); self.search_for(sema, Some(config.prefix_kind)) } @@ -106,7 +104,7 @@ impl ImportAssets { pub(crate) fn search_for_relative_paths( &self, sema: &Semantics, - ) -> BTreeSet { + ) -> Vec<(hir::ModPath, hir::ItemInNs)> { let _p = profile::span("import_assists::search_for_relative_paths"); self.search_for(sema, None) } @@ -115,7 +113,7 @@ impl ImportAssets { &self, sema: &Semantics, prefixed: Option, - ) -> BTreeSet { + ) -> Vec<(hir::ModPath, hir::ItemInNs)> { let db = sema.db; let mut trait_candidates = FxHashSet::default(); let current_crate = self.module_with_name_to_import.krate(); @@ -181,7 +179,7 @@ impl ImportAssets { } }; - imports_locator::find_imports(sema, current_crate, &self.get_search_query()) + let mut res = imports_locator::find_imports(sema, current_crate, &self.get_search_query()) .into_iter() .filter_map(filter) .filter_map(|candidate| { @@ -191,10 +189,13 @@ impl ImportAssets { } else { self.module_with_name_to_import.find_use_path(db, item) } + .map(|path| (path, item)) }) - .filter(|use_path| !use_path.segments.is_empty()) + .filter(|(use_path, _)| !use_path.segments.is_empty()) .take(20) - .collect::>() + .collect::>(); + res.sort_by_key(|(path, _)| path.clone()); + res } fn assoc_to_trait(assoc: AssocItemContainer) -> Option { @@ -215,7 +216,7 @@ impl ImportCandidate { Some(_) => None, None => Some(Self::TraitMethod(TraitImportCandidate { ty: sema.type_of_expr(&method_call.receiver()?)?, - name: method_call.name_ref()?.syntax().to_string(), + name: method_call.name_ref()?, })), } } @@ -243,24 +244,17 @@ impl ImportCandidate { hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { ImportCandidate::TraitAssocItem(TraitImportCandidate { ty: assoc_item_path.ty(sema.db), - name: segment.syntax().to_string(), + name: segment.name_ref()?, }) } _ => return None, } } else { - ImportCandidate::QualifierStart(PathImportCandidate { - name: qualifier_start.syntax().to_string(), - }) + ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start }) } } else { ImportCandidate::UnqualifiedName(PathImportCandidate { - name: segment - .syntax() - .descendants() - .find_map(ast::NameRef::cast)? - .syntax() - .to_string(), + name: segment.syntax().descendants().find_map(ast::NameRef::cast)?, }) }; Some(candidate) diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 74dbdfaf7b7..5b06cb76718 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -172,6 +172,9 @@ pub fn expr_call(f: ast::Expr, arg_list: ast::ArgList) -> ast::Expr { pub fn expr_method_call(receiver: ast::Expr, method: &str, arg_list: ast::ArgList) -> ast::Expr { expr_from_text(&format!("{}.{}{}", receiver, method, arg_list)) } +pub fn expr_ref(expr: ast::Expr, exclusive: bool) -> ast::Expr { + expr_from_text(&if exclusive { format!("&mut {}", expr) } else { format!("&{}", expr) }) +} fn expr_from_text(text: &str) -> ast::Expr { ast_from_text(&format!("const C: () = {};", text)) }