diff options
-rw-r--r-- | crates/hir/src/lib.rs | 11 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/auto_import.rs | 52 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/qualify_path.rs | 40 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | 33 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/flyimport.rs | 270 | ||||
-rw-r--r-- | crates/ide_completion/src/item.rs | 43 | ||||
-rw-r--r-- | crates/ide_completion/src/lib.rs | 28 | ||||
-rw-r--r-- | crates/ide_completion/src/render.rs | 19 | ||||
-rw-r--r-- | crates/ide_db/src/helpers.rs | 10 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/import_assets.rs | 578 | ||||
-rw-r--r-- | crates/ide_db/src/items_locator.rs (renamed from crates/ide_db/src/imports_locator.rs) | 79 | ||||
-rw-r--r-- | crates/ide_db/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 10 | ||||
-rw-r--r-- | crates/syntax/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/syntax/src/ast/make.rs | 4 | ||||
-rw-r--r-- | crates/syntax/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/syntax/src/utils.rs | 43 |
18 files changed, 847 insertions, 380 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index e3a332d30..d5a3d9034 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs | |||
@@ -1114,6 +1114,7 @@ pub enum AssocItem { | |||
1114 | Const(Const), | 1114 | Const(Const), |
1115 | TypeAlias(TypeAlias), | 1115 | TypeAlias(TypeAlias), |
1116 | } | 1116 | } |
1117 | #[derive(Debug)] | ||
1117 | pub enum AssocItemContainer { | 1118 | pub enum AssocItemContainer { |
1118 | Trait(Trait), | 1119 | Trait(Trait), |
1119 | Impl(Impl), | 1120 | Impl(Impl), |
@@ -2136,6 +2137,16 @@ impl ScopeDef { | |||
2136 | } | 2137 | } |
2137 | } | 2138 | } |
2138 | 2139 | ||
2140 | impl From<ItemInNs> for ScopeDef { | ||
2141 | fn from(item: ItemInNs) -> Self { | ||
2142 | match item { | ||
2143 | ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()), | ||
2144 | ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()), | ||
2145 | ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()), | ||
2146 | } | ||
2147 | } | ||
2148 | } | ||
2149 | |||
2139 | pub trait HasVisibility { | 2150 | pub trait HasVisibility { |
2140 | fn visibility(&self, db: &dyn HirDatabase) -> Visibility; | 2151 | fn visibility(&self, db: &dyn HirDatabase) -> Visibility; |
2141 | fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool { | 2152 | fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool { |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index b600178ee..f83ed65d5 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -478,7 +478,6 @@ impl Analysis { | |||
478 | position: FilePosition, | 478 | position: FilePosition, |
479 | full_import_path: &str, | 479 | full_import_path: &str, |
480 | imported_name: String, | 480 | imported_name: String, |
481 | import_for_trait_assoc_item: bool, | ||
482 | ) -> Cancelable<Vec<TextEdit>> { | 481 | ) -> Cancelable<Vec<TextEdit>> { |
483 | Ok(self | 482 | Ok(self |
484 | .with_db(|db| { | 483 | .with_db(|db| { |
@@ -488,7 +487,6 @@ impl Analysis { | |||
488 | position, | 487 | position, |
489 | full_import_path, | 488 | full_import_path, |
490 | imported_name, | 489 | imported_name, |
491 | import_for_trait_assoc_item, | ||
492 | ) | 490 | ) |
493 | })? | 491 | })? |
494 | .unwrap_or_default()) | 492 | .unwrap_or_default()) |
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs index 1422224ac..7caee8df0 100644 --- a/crates/ide_assists/src/handlers/auto_import.rs +++ b/crates/ide_assists/src/handlers/auto_import.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | use ide_db::helpers::{ | 1 | use ide_db::helpers::{ |
2 | import_assets::{ImportAssets, ImportCandidate}, | 2 | import_assets::{ImportAssets, ImportCandidate}, |
3 | insert_use::{insert_use, ImportScope}, | 3 | insert_use::{insert_use, ImportScope}, |
4 | mod_path_to_ast, | 4 | item_name, mod_path_to_ast, |
5 | }; | 5 | }; |
6 | use syntax::{ast, AstNode, SyntaxNode}; | 6 | use syntax::{ast, AstNode, SyntaxNode}; |
7 | 7 | ||
@@ -92,14 +92,19 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
92 | let range = ctx.sema.original_range(&syntax_under_caret).range; | 92 | let range = ctx.sema.original_range(&syntax_under_caret).range; |
93 | let group = import_group_message(import_assets.import_candidate()); | 93 | let group = import_group_message(import_assets.import_candidate()); |
94 | let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?; | 94 | let scope = ImportScope::find_insert_use_container(&syntax_under_caret, &ctx.sema)?; |
95 | for (import, _) in proposed_imports { | 95 | for import in proposed_imports { |
96 | let name = match item_name(ctx.db(), import.original_item) { | ||
97 | Some(name) => name, | ||
98 | None => continue, | ||
99 | }; | ||
96 | acc.add_group( | 100 | acc.add_group( |
97 | &group, | 101 | &group, |
98 | AssistId("auto_import", AssistKind::QuickFix), | 102 | AssistId("auto_import", AssistKind::QuickFix), |
99 | format!("Import `{}`", &import), | 103 | format!("Import `{}`", name), |
100 | range, | 104 | range, |
101 | |builder| { | 105 | |builder| { |
102 | let rewriter = insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use); | 106 | let rewriter = |
107 | insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use); | ||
103 | builder.rewrite(rewriter); | 108 | builder.rewrite(rewriter); |
104 | }, | 109 | }, |
105 | ); | 110 | ); |
@@ -125,10 +130,10 @@ fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel { | |||
125 | let name = match import_candidate { | 130 | let name = match import_candidate { |
126 | ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()), | 131 | ImportCandidate::Path(candidate) => format!("Import {}", candidate.name.text()), |
127 | ImportCandidate::TraitAssocItem(candidate) => { | 132 | ImportCandidate::TraitAssocItem(candidate) => { |
128 | format!("Import a trait for item {}", candidate.name.text()) | 133 | format!("Import a trait for item {}", candidate.assoc_item_name.text()) |
129 | } | 134 | } |
130 | ImportCandidate::TraitMethod(candidate) => { | 135 | ImportCandidate::TraitMethod(candidate) => { |
131 | format!("Import a trait for method {}", candidate.name.text()) | 136 | format!("Import a trait for method {}", candidate.assoc_item_name.text()) |
132 | } | 137 | } |
133 | }; | 138 | }; |
134 | GroupLabel(name) | 139 | GroupLabel(name) |
@@ -221,41 +226,6 @@ mod tests { | |||
221 | } | 226 | } |
222 | 227 | ||
223 | #[test] | 228 | #[test] |
224 | fn auto_imports_are_merged() { | ||
225 | check_assist( | ||
226 | auto_import, | ||
227 | r" | ||
228 | use PubMod::PubStruct1; | ||
229 | |||
230 | struct Test { | ||
231 | test: Pub$0Struct2<u8>, | ||
232 | } | ||
233 | |||
234 | pub mod PubMod { | ||
235 | pub struct PubStruct1; | ||
236 | pub struct PubStruct2<T> { | ||
237 | _t: T, | ||
238 | } | ||
239 | } | ||
240 | ", | ||
241 | r" | ||
242 | use PubMod::{PubStruct1, PubStruct2}; | ||
243 | |||
244 | struct Test { | ||
245 | test: PubStruct2<u8>, | ||
246 | } | ||
247 | |||
248 | pub mod PubMod { | ||
249 | pub struct PubStruct1; | ||
250 | pub struct PubStruct2<T> { | ||
251 | _t: T, | ||
252 | } | ||
253 | } | ||
254 | ", | ||
255 | ); | ||
256 | } | ||
257 | |||
258 | #[test] | ||
259 | fn applicable_when_found_multiple_imports() { | 229 | fn applicable_when_found_multiple_imports() { |
260 | check_assist( | 230 | check_assist( |
261 | auto_import, | 231 | auto_import, |
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs index d3e34e540..272874ae3 100644 --- a/crates/ide_assists/src/handlers/qualify_path.rs +++ b/crates/ide_assists/src/handlers/qualify_path.rs | |||
@@ -1,7 +1,10 @@ | |||
1 | use std::iter; | 1 | use std::iter; |
2 | 2 | ||
3 | use hir::AsAssocItem; | 3 | use hir::AsAssocItem; |
4 | use ide_db::helpers::{import_assets::ImportCandidate, mod_path_to_ast}; | 4 | use ide_db::helpers::{ |
5 | import_assets::{ImportCandidate, LocatedImport}, | ||
6 | item_name, mod_path_to_ast, | ||
7 | }; | ||
5 | use ide_db::RootDatabase; | 8 | use ide_db::RootDatabase; |
6 | use syntax::{ | 9 | use syntax::{ |
7 | ast, | 10 | ast, |
@@ -71,17 +74,17 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
71 | }; | 74 | }; |
72 | 75 | ||
73 | let group_label = group_label(candidate); | 76 | let group_label = group_label(candidate); |
74 | for (import, item) in proposed_imports { | 77 | for import in proposed_imports { |
75 | acc.add_group( | 78 | acc.add_group( |
76 | &group_label, | 79 | &group_label, |
77 | AssistId("qualify_path", AssistKind::QuickFix), | 80 | AssistId("qualify_path", AssistKind::QuickFix), |
78 | label(candidate, &import), | 81 | label(ctx.db(), candidate, &import), |
79 | range, | 82 | range, |
80 | |builder| { | 83 | |builder| { |
81 | qualify_candidate.qualify( | 84 | qualify_candidate.qualify( |
82 | |replace_with: String| builder.replace(range, replace_with), | 85 | |replace_with: String| builder.replace(range, replace_with), |
83 | import, | 86 | &import.import_path, |
84 | item, | 87 | import.item_to_import, |
85 | ) | 88 | ) |
86 | }, | 89 | }, |
87 | ); | 90 | ); |
@@ -97,8 +100,13 @@ enum QualifyCandidate<'db> { | |||
97 | } | 100 | } |
98 | 101 | ||
99 | impl QualifyCandidate<'_> { | 102 | impl QualifyCandidate<'_> { |
100 | fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) { | 103 | fn qualify( |
101 | let import = mod_path_to_ast(&import); | 104 | &self, |
105 | mut replacer: impl FnMut(String), | ||
106 | import: &hir::ModPath, | ||
107 | item: hir::ItemInNs, | ||
108 | ) { | ||
109 | let import = mod_path_to_ast(import); | ||
102 | match self { | 110 | match self { |
103 | QualifyCandidate::QualifierStart(segment, generics) => { | 111 | QualifyCandidate::QualifierStart(segment, generics) => { |
104 | let generics = generics.as_ref().map_or_else(String::new, ToString::to_string); | 112 | let generics = generics.as_ref().map_or_else(String::new, ToString::to_string); |
@@ -183,23 +191,29 @@ fn item_as_trait(db: &RootDatabase, item: hir::ItemInNs) -> Option<hir::Trait> { | |||
183 | fn group_label(candidate: &ImportCandidate) -> GroupLabel { | 191 | fn group_label(candidate: &ImportCandidate) -> GroupLabel { |
184 | let name = match candidate { | 192 | let name = match candidate { |
185 | ImportCandidate::Path(it) => &it.name, | 193 | ImportCandidate::Path(it) => &it.name, |
186 | ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, | 194 | ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => { |
195 | &it.assoc_item_name | ||
196 | } | ||
187 | } | 197 | } |
188 | .text(); | 198 | .text(); |
189 | GroupLabel(format!("Qualify {}", name)) | 199 | GroupLabel(format!("Qualify {}", name)) |
190 | } | 200 | } |
191 | 201 | ||
192 | fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { | 202 | fn label(db: &RootDatabase, candidate: &ImportCandidate, import: &LocatedImport) -> String { |
203 | let display_path = match item_name(db, import.original_item) { | ||
204 | Some(display_path) => display_path.to_string(), | ||
205 | None => "{unknown}".to_string(), | ||
206 | }; | ||
193 | match candidate { | 207 | match candidate { |
194 | ImportCandidate::Path(candidate) => { | 208 | ImportCandidate::Path(candidate) => { |
195 | if candidate.qualifier.is_some() { | 209 | if candidate.qualifier.is_some() { |
196 | format!("Qualify with `{}`", &import) | 210 | format!("Qualify with `{}`", display_path) |
197 | } else { | 211 | } else { |
198 | format!("Qualify as `{}`", &import) | 212 | format!("Qualify as `{}`", display_path) |
199 | } | 213 | } |
200 | } | 214 | } |
201 | ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import), | 215 | ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", display_path), |
202 | ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import), | 216 | ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", display_path), |
203 | } | 217 | } |
204 | } | 218 | } |
205 | 219 | ||
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs index c69bc5cac..88fe2fe90 100644 --- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | use hir::ModuleDef; | ||
1 | use ide_db::helpers::mod_path_to_ast; | 2 | use ide_db::helpers::mod_path_to_ast; |
2 | use ide_db::imports_locator; | 3 | use ide_db::items_locator; |
3 | use itertools::Itertools; | 4 | use itertools::Itertools; |
4 | use syntax::{ | 5 | use syntax::{ |
5 | ast::{self, make, AstNode, NameOwner}, | 6 | ast::{self, make, AstNode, NameOwner}, |
@@ -64,22 +65,20 @@ pub(crate) fn replace_derive_with_manual_impl( | |||
64 | let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; | 65 | let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; |
65 | let current_crate = current_module.krate(); | 66 | let current_crate = current_module.krate(); |
66 | 67 | ||
67 | let found_traits = imports_locator::find_exact_imports( | 68 | let found_traits = |
68 | &ctx.sema, | 69 | items_locator::with_exact_name(&ctx.sema, current_crate, trait_token.text().to_string()) |
69 | current_crate, | 70 | .into_iter() |
70 | trait_token.text().to_string(), | 71 | .filter_map(|item| match ModuleDef::from(item.as_module_def_id()?) { |
71 | ) | 72 | ModuleDef::Trait(trait_) => Some(trait_), |
72 | .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate { | 73 | _ => None, |
73 | either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_), | 74 | }) |
74 | _ => None, | 75 | .flat_map(|trait_| { |
75 | }) | 76 | current_module |
76 | .flat_map(|trait_| { | 77 | .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) |
77 | current_module | 78 | .as_ref() |
78 | .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) | 79 | .map(mod_path_to_ast) |
79 | .as_ref() | 80 | .zip(Some(trait_)) |
80 | .map(mod_path_to_ast) | 81 | }); |
81 | .zip(Some(trait_)) | ||
82 | }); | ||
83 | 82 | ||
84 | let mut no_traits_found = true; | 83 | let mut no_traits_found = true; |
85 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { | 84 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { |
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs index f34764b61..391a11c91 100644 --- a/crates/ide_completion/src/completions/flyimport.rs +++ b/crates/ide_completion/src/completions/flyimport.rs | |||
@@ -21,6 +21,46 @@ | |||
21 | //! ``` | 21 | //! ``` |
22 | //! | 22 | //! |
23 | //! Also completes associated items, that require trait imports. | 23 | //! Also completes associated items, that require trait imports. |
24 | //! If any unresolved and/or partially-qualified path predeces the input, it will be taken into account. | ||
25 | //! Currently, only the imports with their import path ending with the whole qialifier will be proposed | ||
26 | //! (no fuzzy matching for qualifier). | ||
27 | //! | ||
28 | //! ``` | ||
29 | //! mod foo { | ||
30 | //! pub mod bar { | ||
31 | //! pub struct Item; | ||
32 | //! | ||
33 | //! impl Item { | ||
34 | //! pub const TEST_ASSOC: usize = 3; | ||
35 | //! } | ||
36 | //! } | ||
37 | //! } | ||
38 | //! | ||
39 | //! fn main() { | ||
40 | //! bar::Item::TEST_A$0 | ||
41 | //! } | ||
42 | //! ``` | ||
43 | //! -> | ||
44 | //! ``` | ||
45 | //! use foo::bar; | ||
46 | //! | ||
47 | //! mod foo { | ||
48 | //! pub mod bar { | ||
49 | //! pub struct Item; | ||
50 | //! | ||
51 | //! impl Item { | ||
52 | //! pub const TEST_ASSOC: usize = 3; | ||
53 | //! } | ||
54 | //! } | ||
55 | //! } | ||
56 | //! | ||
57 | //! fn main() { | ||
58 | //! bar::Item::TEST_ASSOC | ||
59 | //! } | ||
60 | //! ``` | ||
61 | //! | ||
62 | //! NOTE: currently, if an assoc item comes from a trait that's not currently imported and it also has an unresolved and/or partially-qualified path, | ||
63 | //! no imports will be proposed. | ||
24 | //! | 64 | //! |
25 | //! .Fuzzy search details | 65 | //! .Fuzzy search details |
26 | //! | 66 | //! |
@@ -48,12 +88,12 @@ | |||
48 | //! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding | 88 | //! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding |
49 | //! capability enabled. | 89 | //! capability enabled. |
50 | 90 | ||
51 | use hir::{AsAssocItem, ModPath, ScopeDef}; | 91 | use hir::ModPath; |
52 | use ide_db::helpers::{ | 92 | use ide_db::helpers::{ |
53 | import_assets::{ImportAssets, ImportCandidate}, | 93 | import_assets::{ImportAssets, ImportCandidate}, |
54 | insert_use::ImportScope, | 94 | insert_use::ImportScope, |
55 | }; | 95 | }; |
56 | use rustc_hash::FxHashSet; | 96 | use itertools::Itertools; |
57 | use syntax::{AstNode, SyntaxNode, T}; | 97 | use syntax::{AstNode, SyntaxNode, T}; |
58 | 98 | ||
59 | use crate::{ | 99 | use crate::{ |
@@ -92,50 +132,26 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) | |||
92 | &ctx.sema, | 132 | &ctx.sema, |
93 | )?; | 133 | )?; |
94 | 134 | ||
95 | let scope_definitions = scope_definitions(ctx); | 135 | acc.add_all( |
96 | let mut all_mod_paths = import_assets | 136 | import_assets |
97 | .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind) | 137 | .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind) |
98 | .into_iter() | 138 | .into_iter() |
99 | .map(|(mod_path, item_in_ns)| { | 139 | .sorted_by_key(|located_import| { |
100 | let scope_item = match item_in_ns { | 140 | compute_fuzzy_completion_order_key( |
101 | hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()), | 141 | &located_import.import_path, |
102 | hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()), | 142 | &user_input_lowercased, |
103 | hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()), | 143 | ) |
104 | }; | 144 | }) |
105 | (mod_path, scope_item) | 145 | .filter_map(|import| { |
106 | }) | 146 | render_resolution_with_import( |
107 | .filter(|(_, proposed_def)| !scope_definitions.contains(proposed_def)) | 147 | RenderContext::new(ctx), |
108 | .collect::<Vec<_>>(); | 148 | ImportEdit { import, scope: import_scope.clone() }, |
109 | all_mod_paths.sort_by_cached_key(|(mod_path, _)| { | 149 | ) |
110 | compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased) | 150 | }), |
111 | }); | 151 | ); |
112 | |||
113 | acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| { | ||
114 | let import_for_trait_assoc_item = match definition { | ||
115 | ScopeDef::ModuleDef(module_def) => module_def | ||
116 | .as_assoc_item(ctx.db) | ||
117 | .and_then(|assoc| assoc.containing_trait(ctx.db)) | ||
118 | .is_some(), | ||
119 | _ => false, | ||
120 | }; | ||
121 | let import_edit = ImportEdit { | ||
122 | import_path, | ||
123 | import_scope: import_scope.clone(), | ||
124 | import_for_trait_assoc_item, | ||
125 | }; | ||
126 | render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition) | ||
127 | })); | ||
128 | Some(()) | 152 | Some(()) |
129 | } | 153 | } |
130 | 154 | ||
131 | fn scope_definitions(ctx: &CompletionContext) -> FxHashSet<ScopeDef> { | ||
132 | let mut scope_definitions = FxHashSet::default(); | ||
133 | ctx.scope.process_all_names(&mut |_, scope_def| { | ||
134 | scope_definitions.insert(scope_def); | ||
135 | }); | ||
136 | scope_definitions | ||
137 | } | ||
138 | |||
139 | pub(crate) fn position_for_import<'a>( | 155 | pub(crate) fn position_for_import<'a>( |
140 | ctx: &'a CompletionContext, | 156 | ctx: &'a CompletionContext, |
141 | import_candidate: Option<&ImportCandidate>, | 157 | import_candidate: Option<&ImportCandidate>, |
@@ -160,23 +176,30 @@ fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAs | |||
160 | current_module, | 176 | current_module, |
161 | ctx.sema.type_of_expr(dot_receiver)?, | 177 | ctx.sema.type_of_expr(dot_receiver)?, |
162 | fuzzy_name, | 178 | fuzzy_name, |
179 | dot_receiver.syntax().clone(), | ||
163 | ) | 180 | ) |
164 | } else { | 181 | } else { |
165 | let fuzzy_name_length = fuzzy_name.len(); | 182 | let fuzzy_name_length = fuzzy_name.len(); |
183 | let approximate_node = match current_module.definition_source(ctx.db).value { | ||
184 | hir::ModuleSource::SourceFile(s) => s.syntax().clone(), | ||
185 | hir::ModuleSource::Module(m) => m.syntax().clone(), | ||
186 | hir::ModuleSource::BlockExpr(b) => b.syntax().clone(), | ||
187 | }; | ||
166 | let assets_for_path = ImportAssets::for_fuzzy_path( | 188 | let assets_for_path = ImportAssets::for_fuzzy_path( |
167 | current_module, | 189 | current_module, |
168 | ctx.path_qual.clone(), | 190 | ctx.path_qual.clone(), |
169 | fuzzy_name, | 191 | fuzzy_name, |
170 | &ctx.sema, | 192 | &ctx.sema, |
171 | ); | 193 | approximate_node, |
194 | )?; | ||
172 | 195 | ||
173 | if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_)) | 196 | if matches!(assets_for_path.import_candidate(), ImportCandidate::Path(_)) |
174 | && fuzzy_name_length < 2 | 197 | && fuzzy_name_length < 2 |
175 | { | 198 | { |
176 | cov_mark::hit!(ignore_short_input_for_path); | 199 | cov_mark::hit!(ignore_short_input_for_path); |
177 | None | 200 | None |
178 | } else { | 201 | } else { |
179 | assets_for_path | 202 | Some(assets_for_path) |
180 | } | 203 | } |
181 | } | 204 | } |
182 | } | 205 | } |
@@ -186,11 +209,11 @@ fn compute_fuzzy_completion_order_key( | |||
186 | user_input_lowercased: &str, | 209 | user_input_lowercased: &str, |
187 | ) -> usize { | 210 | ) -> usize { |
188 | cov_mark::hit!(certain_fuzzy_order_test); | 211 | cov_mark::hit!(certain_fuzzy_order_test); |
189 | let proposed_import_name = match proposed_mod_path.segments().last() { | 212 | let import_name = match proposed_mod_path.segments().last() { |
190 | Some(name) => name.to_string().to_lowercase(), | 213 | Some(name) => name.to_string().to_lowercase(), |
191 | None => return usize::MAX, | 214 | None => return usize::MAX, |
192 | }; | 215 | }; |
193 | match proposed_import_name.match_indices(user_input_lowercased).next() { | 216 | match import_name.match_indices(user_input_lowercased).next() { |
194 | Some((first_matching_index, _)) => first_matching_index, | 217 | Some((first_matching_index, _)) => first_matching_index, |
195 | None => usize::MAX, | 218 | None => usize::MAX, |
196 | } | 219 | } |
@@ -773,4 +796,155 @@ fn main() { | |||
773 | }"#, | 796 | }"#, |
774 | ); | 797 | ); |
775 | } | 798 | } |
799 | |||
800 | #[test] | ||
801 | fn unresolved_qualifier() { | ||
802 | let fixture = r#" | ||
803 | mod foo { | ||
804 | pub mod bar { | ||
805 | pub mod baz { | ||
806 | pub struct Item; | ||
807 | } | ||
808 | } | ||
809 | } | ||
810 | |||
811 | fn main() { | ||
812 | bar::baz::Ite$0 | ||
813 | }"#; | ||
814 | |||
815 | check( | ||
816 | fixture, | ||
817 | expect![[r#" | ||
818 | st foo::bar::baz::Item | ||
819 | "#]], | ||
820 | ); | ||
821 | |||
822 | check_edit( | ||
823 | "Item", | ||
824 | fixture, | ||
825 | r#" | ||
826 | use foo::bar; | ||
827 | |||
828 | mod foo { | ||
829 | pub mod bar { | ||
830 | pub mod baz { | ||
831 | pub struct Item; | ||
832 | } | ||
833 | } | ||
834 | } | ||
835 | |||
836 | fn main() { | ||
837 | bar::baz::Item | ||
838 | }"#, | ||
839 | ); | ||
840 | } | ||
841 | |||
842 | #[test] | ||
843 | fn unresolved_assoc_item_container() { | ||
844 | let fixture = r#" | ||
845 | mod foo { | ||
846 | pub struct Item; | ||
847 | |||
848 | impl Item { | ||
849 | pub const TEST_ASSOC: usize = 3; | ||
850 | } | ||
851 | } | ||
852 | |||
853 | fn main() { | ||
854 | Item::TEST_A$0 | ||
855 | }"#; | ||
856 | |||
857 | check( | ||
858 | fixture, | ||
859 | expect![[r#" | ||
860 | ct TEST_ASSOC (foo::Item) | ||
861 | "#]], | ||
862 | ); | ||
863 | |||
864 | check_edit( | ||
865 | "TEST_ASSOC", | ||
866 | fixture, | ||
867 | r#" | ||
868 | use foo::Item; | ||
869 | |||
870 | mod foo { | ||
871 | pub struct Item; | ||
872 | |||
873 | impl Item { | ||
874 | pub const TEST_ASSOC: usize = 3; | ||
875 | } | ||
876 | } | ||
877 | |||
878 | fn main() { | ||
879 | Item::TEST_ASSOC | ||
880 | }"#, | ||
881 | ); | ||
882 | } | ||
883 | |||
884 | #[test] | ||
885 | fn unresolved_assoc_item_container_with_path() { | ||
886 | let fixture = r#" | ||
887 | mod foo { | ||
888 | pub mod bar { | ||
889 | pub struct Item; | ||
890 | |||
891 | impl Item { | ||
892 | pub const TEST_ASSOC: usize = 3; | ||
893 | } | ||
894 | } | ||
895 | } | ||
896 | |||
897 | fn main() { | ||
898 | bar::Item::TEST_A$0 | ||
899 | }"#; | ||
900 | |||
901 | check( | ||
902 | fixture, | ||
903 | expect![[r#" | ||
904 | ct TEST_ASSOC (foo::bar::Item) | ||
905 | "#]], | ||
906 | ); | ||
907 | |||
908 | check_edit( | ||
909 | "TEST_ASSOC", | ||
910 | fixture, | ||
911 | r#" | ||
912 | use foo::bar; | ||
913 | |||
914 | mod foo { | ||
915 | pub mod bar { | ||
916 | pub struct Item; | ||
917 | |||
918 | impl Item { | ||
919 | pub const TEST_ASSOC: usize = 3; | ||
920 | } | ||
921 | } | ||
922 | } | ||
923 | |||
924 | fn main() { | ||
925 | bar::Item::TEST_ASSOC | ||
926 | }"#, | ||
927 | ); | ||
928 | } | ||
929 | |||
930 | #[test] | ||
931 | fn fuzzy_unresolved_path() { | ||
932 | check( | ||
933 | r#" | ||
934 | mod foo { | ||
935 | pub mod bar { | ||
936 | pub struct Item; | ||
937 | |||
938 | impl Item { | ||
939 | pub const TEST_ASSOC: usize = 3; | ||
940 | } | ||
941 | } | ||
942 | } | ||
943 | |||
944 | fn main() { | ||
945 | bar::Ass$0 | ||
946 | }"#, | ||
947 | expect![[]], | ||
948 | ) | ||
949 | } | ||
776 | } | 950 | } |
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs index 9b2435c4b..9b039e3e5 100644 --- a/crates/ide_completion/src/item.rs +++ b/crates/ide_completion/src/item.rs | |||
@@ -2,15 +2,16 @@ | |||
2 | 2 | ||
3 | use std::fmt; | 3 | use std::fmt; |
4 | 4 | ||
5 | use hir::{Documentation, ModPath, Mutability}; | 5 | use hir::{Documentation, Mutability}; |
6 | use ide_db::{ | 6 | use ide_db::{ |
7 | helpers::{ | 7 | helpers::{ |
8 | import_assets::LocatedImport, | ||
8 | insert_use::{self, ImportScope, InsertUseConfig}, | 9 | insert_use::{self, ImportScope, InsertUseConfig}, |
9 | mod_path_to_ast, SnippetCap, | 10 | mod_path_to_ast, SnippetCap, |
10 | }, | 11 | }, |
11 | SymbolKind, | 12 | SymbolKind, |
12 | }; | 13 | }; |
13 | use stdx::{impl_from, never}; | 14 | use stdx::{format_to, impl_from, never}; |
14 | use syntax::{algo, TextRange}; | 15 | use syntax::{algo, TextRange}; |
15 | use text_edit::TextEdit; | 16 | use text_edit::TextEdit; |
16 | 17 | ||
@@ -272,9 +273,8 @@ impl CompletionItem { | |||
272 | /// An extra import to add after the completion is applied. | 273 | /// An extra import to add after the completion is applied. |
273 | #[derive(Debug, Clone)] | 274 | #[derive(Debug, Clone)] |
274 | pub struct ImportEdit { | 275 | pub struct ImportEdit { |
275 | pub import_path: ModPath, | 276 | pub import: LocatedImport, |
276 | pub import_scope: ImportScope, | 277 | pub scope: ImportScope, |
277 | pub import_for_trait_assoc_item: bool, | ||
278 | } | 278 | } |
279 | 279 | ||
280 | impl ImportEdit { | 280 | impl ImportEdit { |
@@ -284,7 +284,7 @@ impl ImportEdit { | |||
284 | let _p = profile::span("ImportEdit::to_text_edit"); | 284 | let _p = profile::span("ImportEdit::to_text_edit"); |
285 | 285 | ||
286 | let rewriter = | 286 | let rewriter = |
287 | insert_use::insert_use(&self.import_scope, mod_path_to_ast(&self.import_path), cfg); | 287 | insert_use::insert_use(&self.scope, mod_path_to_ast(&self.import.import_path), cfg); |
288 | let old_ast = rewriter.rewrite_root()?; | 288 | let old_ast = rewriter.rewrite_root()?; |
289 | let mut import_insert = TextEdit::builder(); | 289 | let mut import_insert = TextEdit::builder(); |
290 | algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); | 290 | algo::diff(&old_ast, &rewriter.rewrite(&old_ast)).into_text_edit(&mut import_insert); |
@@ -322,20 +322,19 @@ impl Builder { | |||
322 | let mut lookup = self.lookup; | 322 | let mut lookup = self.lookup; |
323 | let mut insert_text = self.insert_text; | 323 | let mut insert_text = self.insert_text; |
324 | 324 | ||
325 | if let Some(import_to_add) = self.import_to_add.as_ref() { | 325 | if let Some(original_path) = self |
326 | if import_to_add.import_for_trait_assoc_item { | 326 | .import_to_add |
327 | lookup = lookup.or_else(|| Some(label.clone())); | 327 | .as_ref() |
328 | insert_text = insert_text.or_else(|| Some(label.clone())); | 328 | .and_then(|import_edit| import_edit.import.original_path.as_ref()) |
329 | label = format!("{} ({})", label, import_to_add.import_path); | 329 | { |
330 | lookup = lookup.or_else(|| Some(label.clone())); | ||
331 | insert_text = insert_text.or_else(|| Some(label.clone())); | ||
332 | |||
333 | let original_path_label = original_path.to_string(); | ||
334 | if original_path_label.ends_with(&label) { | ||
335 | label = original_path_label; | ||
330 | } else { | 336 | } else { |
331 | let mut import_path_without_last_segment = import_to_add.import_path.to_owned(); | 337 | format_to!(label, " ({})", original_path) |
332 | let _ = import_path_without_last_segment.pop_segment(); | ||
333 | |||
334 | if !import_path_without_last_segment.segments().is_empty() { | ||
335 | lookup = lookup.or_else(|| Some(label.clone())); | ||
336 | insert_text = insert_text.or_else(|| Some(label.clone())); | ||
337 | label = format!("{}::{}", import_path_without_last_segment, label); | ||
338 | } | ||
339 | } | 338 | } |
340 | } | 339 | } |
341 | 340 | ||
@@ -439,9 +438,3 @@ impl Builder { | |||
439 | self | 438 | self |
440 | } | 439 | } |
441 | } | 440 | } |
442 | |||
443 | impl<'a> Into<CompletionItem> for Builder { | ||
444 | fn into(self) -> CompletionItem { | ||
445 | self.build() | ||
446 | } | ||
447 | } | ||
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index b0b809791..a0c8c374d 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs | |||
@@ -13,7 +13,9 @@ mod completions; | |||
13 | 13 | ||
14 | use completions::flyimport::position_for_import; | 14 | use completions::flyimport::position_for_import; |
15 | use ide_db::{ | 15 | use ide_db::{ |
16 | base_db::FilePosition, helpers::insert_use::ImportScope, imports_locator, RootDatabase, | 16 | base_db::FilePosition, |
17 | helpers::{import_assets::LocatedImport, insert_use::ImportScope}, | ||
18 | items_locator, RootDatabase, | ||
17 | }; | 19 | }; |
18 | use text_edit::TextEdit; | 20 | use text_edit::TextEdit; |
19 | 21 | ||
@@ -139,25 +141,27 @@ pub fn resolve_completion_edits( | |||
139 | position: FilePosition, | 141 | position: FilePosition, |
140 | full_import_path: &str, | 142 | full_import_path: &str, |
141 | imported_name: String, | 143 | imported_name: String, |
142 | import_for_trait_assoc_item: bool, | ||
143 | ) -> Option<Vec<TextEdit>> { | 144 | ) -> Option<Vec<TextEdit>> { |
144 | let ctx = CompletionContext::new(db, position, config)?; | 145 | let ctx = CompletionContext::new(db, position, config)?; |
145 | let position_for_import = position_for_import(&ctx, None)?; | 146 | let position_for_import = position_for_import(&ctx, None)?; |
146 | let import_scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?; | 147 | let scope = ImportScope::find_insert_use_container(position_for_import, &ctx.sema)?; |
147 | 148 | ||
148 | let current_module = ctx.sema.scope(position_for_import).module()?; | 149 | let current_module = ctx.sema.scope(position_for_import).module()?; |
149 | let current_crate = current_module.krate(); | 150 | let current_crate = current_module.krate(); |
150 | 151 | ||
151 | let import_path = imports_locator::find_exact_imports(&ctx.sema, current_crate, imported_name) | 152 | let (import_path, item_to_import) = |
152 | .filter_map(|candidate| { | 153 | items_locator::with_exact_name(&ctx.sema, current_crate, imported_name) |
153 | let item: hir::ItemInNs = candidate.either(Into::into, Into::into); | 154 | .into_iter() |
154 | current_module.find_use_path_prefixed(db, item, config.insert_use.prefix_kind) | 155 | .filter_map(|candidate| { |
155 | }) | 156 | current_module |
156 | .find(|mod_path| mod_path.to_string() == full_import_path)?; | 157 | .find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind) |
158 | .zip(Some(candidate)) | ||
159 | }) | ||
160 | .find(|(mod_path, _)| mod_path.to_string() == full_import_path)?; | ||
161 | let import = | ||
162 | LocatedImport::new(import_path.clone(), item_to_import, item_to_import, Some(import_path)); | ||
157 | 163 | ||
158 | ImportEdit { import_path, import_scope, import_for_trait_assoc_item } | 164 | ImportEdit { import, scope }.to_text_edit(config.insert_use).map(|edit| vec![edit]) |
159 | .to_text_edit(config.insert_use) | ||
160 | .map(|edit| vec![edit]) | ||
161 | } | 165 | } |
162 | 166 | ||
163 | #[cfg(test)] | 167 | #[cfg(test)] |
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index dcfac23c5..fae5685e2 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs | |||
@@ -13,7 +13,10 @@ mod builder_ext; | |||
13 | use hir::{ | 13 | use hir::{ |
14 | AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type, | 14 | AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type, |
15 | }; | 15 | }; |
16 | use ide_db::{helpers::SnippetCap, RootDatabase, SymbolKind}; | 16 | use ide_db::{ |
17 | helpers::{item_name, SnippetCap}, | ||
18 | RootDatabase, SymbolKind, | ||
19 | }; | ||
17 | use syntax::TextRange; | 20 | use syntax::TextRange; |
18 | 21 | ||
19 | use crate::{ | 22 | use crate::{ |
@@ -50,18 +53,20 @@ pub(crate) fn render_resolution<'a>( | |||
50 | pub(crate) fn render_resolution_with_import<'a>( | 53 | pub(crate) fn render_resolution_with_import<'a>( |
51 | ctx: RenderContext<'a>, | 54 | ctx: RenderContext<'a>, |
52 | import_edit: ImportEdit, | 55 | import_edit: ImportEdit, |
53 | resolution: &ScopeDef, | ||
54 | ) -> Option<CompletionItem> { | 56 | ) -> Option<CompletionItem> { |
57 | let resolution = ScopeDef::from(import_edit.import.original_item); | ||
55 | let local_name = match resolution { | 58 | let local_name = match resolution { |
56 | ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(), | 59 | ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(), |
57 | ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(), | 60 | ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(), |
58 | ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(), | 61 | ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(), |
59 | _ => import_edit.import_path.segments().last()?.to_string(), | 62 | _ => item_name(ctx.db(), import_edit.import.original_item)?.to_string(), |
60 | }; | 63 | }; |
61 | Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| { | 64 | Render::new(ctx).render_resolution(local_name, Some(import_edit), &resolution).map( |
62 | item.completion_kind = CompletionKind::Magic; | 65 | |mut item| { |
63 | item | 66 | item.completion_kind = CompletionKind::Magic; |
64 | }) | 67 | item |
68 | }, | ||
69 | ) | ||
65 | } | 70 | } |
66 | 71 | ||
67 | /// Interface for data and methods required for items rendering. | 72 | /// Interface for data and methods required for items rendering. |
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index 3ff77400b..3c95d3cff 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs | |||
@@ -2,11 +2,19 @@ | |||
2 | pub mod insert_use; | 2 | pub mod insert_use; |
3 | pub mod import_assets; | 3 | pub mod import_assets; |
4 | 4 | ||
5 | use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait}; | 5 | use hir::{Crate, Enum, ItemInNs, MacroDef, Module, ModuleDef, Name, ScopeDef, Semantics, Trait}; |
6 | use syntax::ast::{self, make}; | 6 | use syntax::ast::{self, make}; |
7 | 7 | ||
8 | use crate::RootDatabase; | 8 | use crate::RootDatabase; |
9 | 9 | ||
10 | pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> { | ||
11 | match item { | ||
12 | ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).name(db), | ||
13 | ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).name(db), | ||
14 | ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).name(db), | ||
15 | } | ||
16 | } | ||
17 | |||
10 | /// Converts the mod path struct into its ast representation. | 18 | /// Converts the mod path struct into its ast representation. |
11 | pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { | 19 | pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path { |
12 | let _p = profile::span("mod_path_to_ast"); | 20 | let _p = profile::span("mod_path_to_ast"); |
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs index 517abbb4b..e03ccd351 100644 --- a/crates/ide_db/src/helpers/import_assets.rs +++ b/crates/ide_db/src/helpers/import_assets.rs | |||
@@ -1,19 +1,29 @@ | |||
1 | //! Look up accessible paths for items. | 1 | //! Look up accessible paths for items. |
2 | use either::Either; | 2 | use hir::{ |
3 | use hir::{AsAssocItem, AssocItem, Crate, MacroDef, Module, ModuleDef, PrefixKind, Semantics}; | 3 | AsAssocItem, AssocItem, AssocItemContainer, Crate, ItemInNs, MacroDef, ModPath, Module, |
4 | ModuleDef, PathResolution, PrefixKind, ScopeDef, Semantics, Type, | ||
5 | }; | ||
6 | use itertools::Itertools; | ||
4 | use rustc_hash::FxHashSet; | 7 | use rustc_hash::FxHashSet; |
5 | use syntax::{ast, AstNode}; | 8 | use syntax::{ast, utils::path_to_string_stripping_turbo_fish, AstNode, SyntaxNode}; |
6 | 9 | ||
7 | use crate::{ | 10 | use crate::{ |
8 | imports_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT}, | 11 | items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT}, |
9 | RootDatabase, | 12 | RootDatabase, |
10 | }; | 13 | }; |
11 | 14 | ||
15 | use super::item_name; | ||
16 | |||
17 | /// A candidate for import, derived during various IDE activities: | ||
18 | /// * completion with imports on the fly proposals | ||
19 | /// * completion edit resolve requests | ||
20 | /// * assists | ||
21 | /// * etc. | ||
12 | #[derive(Debug)] | 22 | #[derive(Debug)] |
13 | pub enum ImportCandidate { | 23 | pub enum ImportCandidate { |
14 | // A path, qualified (`std::collections::HashMap`) or not (`HashMap`). | 24 | /// A path, qualified (`std::collections::HashMap`) or not (`HashMap`). |
15 | Path(PathImportCandidate), | 25 | Path(PathImportCandidate), |
16 | /// A trait associated function (with no self parameter) or associated constant. | 26 | /// A trait associated function (with no self parameter) or an associated constant. |
17 | /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type | 27 | /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type |
18 | /// and `name` is the `test_function` | 28 | /// and `name` is the `test_function` |
19 | TraitAssocItem(TraitImportCandidate), | 29 | TraitAssocItem(TraitImportCandidate), |
@@ -23,21 +33,40 @@ pub enum ImportCandidate { | |||
23 | TraitMethod(TraitImportCandidate), | 33 | TraitMethod(TraitImportCandidate), |
24 | } | 34 | } |
25 | 35 | ||
36 | /// A trait import needed for a given associated item access. | ||
37 | /// For `some::path::SomeStruct::ASSOC_`, contains the | ||
38 | /// type of `some::path::SomeStruct` and `ASSOC_` as the item name. | ||
26 | #[derive(Debug)] | 39 | #[derive(Debug)] |
27 | pub struct TraitImportCandidate { | 40 | pub struct TraitImportCandidate { |
28 | pub receiver_ty: hir::Type, | 41 | /// A type of the item that has the associated item accessed at. |
29 | pub name: NameToImport, | 42 | pub receiver_ty: Type, |
43 | /// The associated item name that the trait to import should contain. | ||
44 | pub assoc_item_name: NameToImport, | ||
30 | } | 45 | } |
31 | 46 | ||
47 | /// Path import for a given name, qualified or not. | ||
32 | #[derive(Debug)] | 48 | #[derive(Debug)] |
33 | pub struct PathImportCandidate { | 49 | pub struct PathImportCandidate { |
34 | pub qualifier: Option<ast::Path>, | 50 | /// Optional qualifier before name. |
51 | pub qualifier: Option<FirstSegmentUnresolved>, | ||
52 | /// The name the item (struct, trait, enum, etc.) should have. | ||
35 | pub name: NameToImport, | 53 | pub name: NameToImport, |
36 | } | 54 | } |
37 | 55 | ||
56 | /// A qualifier that has a first segment and it's unresolved. | ||
57 | #[derive(Debug)] | ||
58 | pub struct FirstSegmentUnresolved { | ||
59 | fist_segment: ast::NameRef, | ||
60 | full_qualifier: ast::Path, | ||
61 | } | ||
62 | |||
63 | /// A name that will be used during item lookups. | ||
38 | #[derive(Debug)] | 64 | #[derive(Debug)] |
39 | pub enum NameToImport { | 65 | pub enum NameToImport { |
66 | /// Requires items with names that exactly match the given string, case-sensitive. | ||
40 | Exact(String), | 67 | Exact(String), |
68 | /// Requires items with names that case-insensitively contain all letters from the string, | ||
69 | /// in the same order, but not necessary adjacent. | ||
41 | Fuzzy(String), | 70 | Fuzzy(String), |
42 | } | 71 | } |
43 | 72 | ||
@@ -50,10 +79,12 @@ impl NameToImport { | |||
50 | } | 79 | } |
51 | } | 80 | } |
52 | 81 | ||
82 | /// A struct to find imports in the project, given a certain name (or its part) and the context. | ||
53 | #[derive(Debug)] | 83 | #[derive(Debug)] |
54 | pub struct ImportAssets { | 84 | pub struct ImportAssets { |
55 | import_candidate: ImportCandidate, | 85 | import_candidate: ImportCandidate, |
56 | module_with_candidate: hir::Module, | 86 | candidate_node: SyntaxNode, |
87 | module_with_candidate: Module, | ||
57 | } | 88 | } |
58 | 89 | ||
59 | impl ImportAssets { | 90 | impl ImportAssets { |
@@ -61,9 +92,11 @@ impl ImportAssets { | |||
61 | method_call: &ast::MethodCallExpr, | 92 | method_call: &ast::MethodCallExpr, |
62 | sema: &Semantics<RootDatabase>, | 93 | sema: &Semantics<RootDatabase>, |
63 | ) -> Option<Self> { | 94 | ) -> Option<Self> { |
95 | let candidate_node = method_call.syntax().clone(); | ||
64 | Some(Self { | 96 | Some(Self { |
65 | import_candidate: ImportCandidate::for_method_call(sema, method_call)?, | 97 | import_candidate: ImportCandidate::for_method_call(sema, method_call)?, |
66 | module_with_candidate: sema.scope(method_call.syntax()).module()?, | 98 | module_with_candidate: sema.scope(&candidate_node).module()?, |
99 | candidate_node, | ||
67 | }) | 100 | }) |
68 | } | 101 | } |
69 | 102 | ||
@@ -71,94 +104,94 @@ impl ImportAssets { | |||
71 | fully_qualified_path: &ast::Path, | 104 | fully_qualified_path: &ast::Path, |
72 | sema: &Semantics<RootDatabase>, | 105 | sema: &Semantics<RootDatabase>, |
73 | ) -> Option<Self> { | 106 | ) -> Option<Self> { |
74 | let syntax_under_caret = fully_qualified_path.syntax(); | 107 | let candidate_node = fully_qualified_path.syntax().clone(); |
75 | if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() { | 108 | if candidate_node.ancestors().find_map(ast::Use::cast).is_some() { |
76 | return None; | 109 | return None; |
77 | } | 110 | } |
78 | Some(Self { | 111 | Some(Self { |
79 | import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?, | 112 | import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?, |
80 | module_with_candidate: sema.scope(syntax_under_caret).module()?, | 113 | module_with_candidate: sema.scope(&candidate_node).module()?, |
114 | candidate_node, | ||
81 | }) | 115 | }) |
82 | } | 116 | } |
83 | 117 | ||
84 | pub fn for_fuzzy_path( | 118 | pub fn for_fuzzy_path( |
85 | module_with_path: Module, | 119 | module_with_candidate: Module, |
86 | qualifier: Option<ast::Path>, | 120 | qualifier: Option<ast::Path>, |
87 | fuzzy_name: String, | 121 | fuzzy_name: String, |
88 | sema: &Semantics<RootDatabase>, | 122 | sema: &Semantics<RootDatabase>, |
123 | candidate_node: SyntaxNode, | ||
89 | ) -> Option<Self> { | 124 | ) -> Option<Self> { |
90 | Some(match qualifier { | 125 | Some(Self { |
91 | Some(qualifier) => { | 126 | import_candidate: ImportCandidate::for_fuzzy_path(qualifier, fuzzy_name, sema)?, |
92 | let qualifier_resolution = sema.resolve_path(&qualifier)?; | 127 | module_with_candidate, |
93 | match qualifier_resolution { | 128 | candidate_node, |
94 | hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => Self { | ||
95 | import_candidate: ImportCandidate::TraitAssocItem(TraitImportCandidate { | ||
96 | receiver_ty: assoc_item_path.ty(sema.db), | ||
97 | name: NameToImport::Fuzzy(fuzzy_name), | ||
98 | }), | ||
99 | module_with_candidate: module_with_path, | ||
100 | }, | ||
101 | _ => Self { | ||
102 | import_candidate: ImportCandidate::Path(PathImportCandidate { | ||
103 | qualifier: Some(qualifier), | ||
104 | name: NameToImport::Fuzzy(fuzzy_name), | ||
105 | }), | ||
106 | module_with_candidate: module_with_path, | ||
107 | }, | ||
108 | } | ||
109 | } | ||
110 | None => Self { | ||
111 | import_candidate: ImportCandidate::Path(PathImportCandidate { | ||
112 | qualifier: None, | ||
113 | name: NameToImport::Fuzzy(fuzzy_name), | ||
114 | }), | ||
115 | module_with_candidate: module_with_path, | ||
116 | }, | ||
117 | }) | 129 | }) |
118 | } | 130 | } |
119 | 131 | ||
120 | pub fn for_fuzzy_method_call( | 132 | pub fn for_fuzzy_method_call( |
121 | module_with_method_call: Module, | 133 | module_with_method_call: Module, |
122 | receiver_ty: hir::Type, | 134 | receiver_ty: Type, |
123 | fuzzy_method_name: String, | 135 | fuzzy_method_name: String, |
136 | candidate_node: SyntaxNode, | ||
124 | ) -> Option<Self> { | 137 | ) -> Option<Self> { |
125 | Some(Self { | 138 | Some(Self { |
126 | import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate { | 139 | import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate { |
127 | receiver_ty, | 140 | receiver_ty, |
128 | name: NameToImport::Fuzzy(fuzzy_method_name), | 141 | assoc_item_name: NameToImport::Fuzzy(fuzzy_method_name), |
129 | }), | 142 | }), |
130 | module_with_candidate: module_with_method_call, | 143 | module_with_candidate: module_with_method_call, |
144 | candidate_node, | ||
131 | }) | 145 | }) |
132 | } | 146 | } |
133 | } | 147 | } |
134 | 148 | ||
149 | /// An import (not necessary the only one) that corresponds a certain given [`PathImportCandidate`]. | ||
150 | /// (the structure is not entirely correct, since there can be situations requiring two imports, see FIXME below for the details) | ||
151 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
152 | pub struct LocatedImport { | ||
153 | /// The path to use in the `use` statement for a given candidate to be imported. | ||
154 | pub import_path: ModPath, | ||
155 | /// An item that will be imported with the import path given. | ||
156 | pub item_to_import: ItemInNs, | ||
157 | /// The path import candidate, resolved. | ||
158 | /// | ||
159 | /// Not necessary matches the import: | ||
160 | /// For any associated constant from the trait, we try to access as `some::path::SomeStruct::ASSOC_` | ||
161 | /// the original item is the associated constant, but the import has to be a trait that | ||
162 | /// defines this constant. | ||
163 | pub original_item: ItemInNs, | ||
164 | /// A path of the original item. | ||
165 | pub original_path: Option<ModPath>, | ||
166 | } | ||
167 | |||
168 | impl LocatedImport { | ||
169 | pub fn new( | ||
170 | import_path: ModPath, | ||
171 | item_to_import: ItemInNs, | ||
172 | original_item: ItemInNs, | ||
173 | original_path: Option<ModPath>, | ||
174 | ) -> Self { | ||
175 | Self { import_path, item_to_import, original_item, original_path } | ||
176 | } | ||
177 | } | ||
178 | |||
135 | impl ImportAssets { | 179 | impl ImportAssets { |
136 | pub fn import_candidate(&self) -> &ImportCandidate { | 180 | pub fn import_candidate(&self) -> &ImportCandidate { |
137 | &self.import_candidate | 181 | &self.import_candidate |
138 | } | 182 | } |
139 | 183 | ||
140 | fn name_to_import(&self) -> &NameToImport { | ||
141 | match &self.import_candidate { | ||
142 | ImportCandidate::Path(candidate) => &candidate.name, | ||
143 | ImportCandidate::TraitAssocItem(candidate) | ||
144 | | ImportCandidate::TraitMethod(candidate) => &candidate.name, | ||
145 | } | ||
146 | } | ||
147 | |||
148 | pub fn search_for_imports( | 184 | pub fn search_for_imports( |
149 | &self, | 185 | &self, |
150 | sema: &Semantics<RootDatabase>, | 186 | sema: &Semantics<RootDatabase>, |
151 | prefix_kind: PrefixKind, | 187 | prefix_kind: PrefixKind, |
152 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { | 188 | ) -> Vec<LocatedImport> { |
153 | let _p = profile::span("import_assets::search_for_imports"); | 189 | let _p = profile::span("import_assets::search_for_imports"); |
154 | self.search_for(sema, Some(prefix_kind)) | 190 | self.search_for(sema, Some(prefix_kind)) |
155 | } | 191 | } |
156 | 192 | ||
157 | /// This may return non-absolute paths if a part of the returned path is already imported into scope. | 193 | /// This may return non-absolute paths if a part of the returned path is already imported into scope. |
158 | pub fn search_for_relative_paths( | 194 | pub fn search_for_relative_paths(&self, sema: &Semantics<RootDatabase>) -> Vec<LocatedImport> { |
159 | &self, | ||
160 | sema: &Semantics<RootDatabase>, | ||
161 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { | ||
162 | let _p = profile::span("import_assets::search_for_relative_paths"); | 195 | let _p = profile::span("import_assets::search_for_relative_paths"); |
163 | self.search_for(sema, None) | 196 | self.search_for(sema, None) |
164 | } | 197 | } |
@@ -166,29 +199,29 @@ impl ImportAssets { | |||
166 | fn search_for( | 199 | fn search_for( |
167 | &self, | 200 | &self, |
168 | sema: &Semantics<RootDatabase>, | 201 | sema: &Semantics<RootDatabase>, |
169 | prefixed: Option<hir::PrefixKind>, | 202 | prefixed: Option<PrefixKind>, |
170 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { | 203 | ) -> Vec<LocatedImport> { |
171 | let current_crate = self.module_with_candidate.krate(); | 204 | let items_with_candidate_name = match self.name_to_import() { |
172 | 205 | NameToImport::Exact(exact_name) => items_locator::with_exact_name( | |
173 | let unfiltered_imports = match self.name_to_import() { | 206 | sema, |
174 | NameToImport::Exact(exact_name) => { | 207 | self.module_with_candidate.krate(), |
175 | imports_locator::find_exact_imports(sema, current_crate, exact_name.clone()) | 208 | exact_name.clone(), |
176 | } | 209 | ), |
177 | // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items: | 210 | // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items: |
178 | // instead, we need to look up all trait impls for a certain struct and search through them only | 211 | // instead, we need to look up all trait impls for a certain struct and search through them only |
179 | // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032 | 212 | // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032 |
180 | // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup | 213 | // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup |
181 | // for the details | 214 | // for the details |
182 | NameToImport::Fuzzy(fuzzy_name) => { | 215 | NameToImport::Fuzzy(fuzzy_name) => { |
183 | let (assoc_item_search, limit) = match self.import_candidate { | 216 | let (assoc_item_search, limit) = if self.import_candidate.is_trait_candidate() { |
184 | ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => { | 217 | (AssocItemSearch::AssocItemsOnly, None) |
185 | (AssocItemSearch::AssocItemsOnly, None) | 218 | } else { |
186 | } | 219 | (AssocItemSearch::Include, Some(DEFAULT_QUERY_SEARCH_LIMIT)) |
187 | _ => (AssocItemSearch::Exclude, Some(DEFAULT_QUERY_SEARCH_LIMIT)), | ||
188 | }; | 220 | }; |
189 | imports_locator::find_similar_imports( | 221 | |
222 | items_locator::with_similar_name( | ||
190 | sema, | 223 | sema, |
191 | current_crate, | 224 | self.module_with_candidate.krate(), |
192 | fuzzy_name.clone(), | 225 | fuzzy_name.clone(), |
193 | assoc_item_search, | 226 | assoc_item_search, |
194 | limit, | 227 | limit, |
@@ -196,63 +229,224 @@ impl ImportAssets { | |||
196 | } | 229 | } |
197 | }; | 230 | }; |
198 | 231 | ||
199 | let db = sema.db; | 232 | let scope_definitions = self.scope_definitions(sema); |
200 | let mut res = | 233 | self.applicable_defs(sema.db, prefixed, items_with_candidate_name) |
201 | applicable_defs(self.import_candidate(), current_crate, db, unfiltered_imports) | 234 | .into_iter() |
202 | .filter_map(|candidate| { | 235 | .filter(|import| import.import_path.len() > 1) |
203 | let item: hir::ItemInNs = candidate.clone().either(Into::into, Into::into); | 236 | .filter(|import| !scope_definitions.contains(&ScopeDef::from(import.item_to_import))) |
204 | 237 | .sorted_by_key(|import| import.import_path.clone()) | |
205 | let item_to_search = match self.import_candidate { | 238 | .collect() |
206 | ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => { | 239 | } |
207 | let canidate_trait = match candidate { | 240 | |
208 | Either::Left(module_def) => { | 241 | fn scope_definitions(&self, sema: &Semantics<RootDatabase>) -> FxHashSet<ScopeDef> { |
209 | module_def.as_assoc_item(db)?.containing_trait(db) | 242 | let mut scope_definitions = FxHashSet::default(); |
210 | } | 243 | sema.scope(&self.candidate_node).process_all_names(&mut |_, scope_def| { |
211 | _ => None, | 244 | scope_definitions.insert(scope_def); |
212 | }?; | 245 | }); |
213 | ModuleDef::from(canidate_trait).into() | 246 | scope_definitions |
214 | } | 247 | } |
215 | _ => item, | 248 | |
216 | }; | 249 | fn name_to_import(&self) -> &NameToImport { |
217 | 250 | match &self.import_candidate { | |
218 | if let Some(prefix_kind) = prefixed { | 251 | ImportCandidate::Path(candidate) => &candidate.name, |
219 | self.module_with_candidate.find_use_path_prefixed( | 252 | ImportCandidate::TraitAssocItem(candidate) |
220 | db, | 253 | | ImportCandidate::TraitMethod(candidate) => &candidate.assoc_item_name, |
221 | item_to_search, | 254 | } |
222 | prefix_kind, | 255 | } |
223 | ) | 256 | |
224 | } else { | 257 | fn applicable_defs( |
225 | self.module_with_candidate.find_use_path(db, item_to_search) | 258 | &self, |
226 | } | 259 | db: &RootDatabase, |
227 | .map(|path| (path, item)) | 260 | prefixed: Option<PrefixKind>, |
228 | }) | 261 | items_with_candidate_name: FxHashSet<ItemInNs>, |
229 | .filter(|(use_path, _)| use_path.len() > 1) | 262 | ) -> FxHashSet<LocatedImport> { |
230 | .collect::<Vec<_>>(); | 263 | let _p = profile::span("import_assets::applicable_defs"); |
231 | res.sort_by_cached_key(|(path, _)| path.clone()); | 264 | let current_crate = self.module_with_candidate.krate(); |
232 | res | 265 | |
266 | let mod_path = |item| { | ||
267 | get_mod_path(db, item_for_path_search(db, item)?, &self.module_with_candidate, prefixed) | ||
268 | }; | ||
269 | |||
270 | match &self.import_candidate { | ||
271 | ImportCandidate::Path(path_candidate) => { | ||
272 | path_applicable_imports(db, path_candidate, mod_path, items_with_candidate_name) | ||
273 | } | ||
274 | ImportCandidate::TraitAssocItem(trait_candidate) => trait_applicable_items( | ||
275 | db, | ||
276 | current_crate, | ||
277 | trait_candidate, | ||
278 | true, | ||
279 | mod_path, | ||
280 | items_with_candidate_name, | ||
281 | ), | ||
282 | ImportCandidate::TraitMethod(trait_candidate) => trait_applicable_items( | ||
283 | db, | ||
284 | current_crate, | ||
285 | trait_candidate, | ||
286 | false, | ||
287 | mod_path, | ||
288 | items_with_candidate_name, | ||
289 | ), | ||
290 | } | ||
233 | } | 291 | } |
234 | } | 292 | } |
235 | 293 | ||
236 | fn applicable_defs<'a>( | 294 | fn path_applicable_imports( |
237 | import_candidate: &ImportCandidate, | ||
238 | current_crate: Crate, | ||
239 | db: &RootDatabase, | 295 | db: &RootDatabase, |
240 | unfiltered_imports: Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a>, | 296 | path_candidate: &PathImportCandidate, |
241 | ) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> { | 297 | mod_path: impl Fn(ItemInNs) -> Option<ModPath> + Copy, |
242 | let receiver_ty = match import_candidate { | 298 | items_with_candidate_name: FxHashSet<ItemInNs>, |
243 | ImportCandidate::Path(_) => return unfiltered_imports, | 299 | ) -> FxHashSet<LocatedImport> { |
244 | ImportCandidate::TraitAssocItem(candidate) | ImportCandidate::TraitMethod(candidate) => { | 300 | let _p = profile::span("import_assets::path_applicable_imports"); |
245 | &candidate.receiver_ty | 301 | |
302 | let (unresolved_first_segment, unresolved_qualifier) = match &path_candidate.qualifier { | ||
303 | None => { | ||
304 | return items_with_candidate_name | ||
305 | .into_iter() | ||
306 | .filter_map(|item| { | ||
307 | Some(LocatedImport::new(mod_path(item)?, item, item, mod_path(item))) | ||
308 | }) | ||
309 | .collect(); | ||
246 | } | 310 | } |
311 | Some(first_segment_unresolved) => ( | ||
312 | first_segment_unresolved.fist_segment.to_string(), | ||
313 | path_to_string_stripping_turbo_fish(&first_segment_unresolved.full_qualifier), | ||
314 | ), | ||
247 | }; | 315 | }; |
248 | 316 | ||
317 | items_with_candidate_name | ||
318 | .into_iter() | ||
319 | .filter_map(|item| { | ||
320 | import_for_item(db, mod_path, &unresolved_first_segment, &unresolved_qualifier, item) | ||
321 | }) | ||
322 | .collect() | ||
323 | } | ||
324 | |||
325 | fn import_for_item( | ||
326 | db: &RootDatabase, | ||
327 | mod_path: impl Fn(ItemInNs) -> Option<ModPath>, | ||
328 | unresolved_first_segment: &str, | ||
329 | unresolved_qualifier: &str, | ||
330 | original_item: ItemInNs, | ||
331 | ) -> Option<LocatedImport> { | ||
332 | let _p = profile::span("import_assets::import_for_item"); | ||
333 | |||
334 | let original_item_candidate = item_for_path_search(db, original_item)?; | ||
335 | let import_path_candidate = mod_path(original_item_candidate)?; | ||
336 | let import_path_string = import_path_candidate.to_string(); | ||
337 | |||
338 | let expected_import_end = if item_as_assoc(db, original_item).is_some() { | ||
339 | unresolved_qualifier.to_string() | ||
340 | } else { | ||
341 | format!("{}::{}", unresolved_qualifier, item_name(db, original_item)?) | ||
342 | }; | ||
343 | if !import_path_string.contains(unresolved_first_segment) | ||
344 | || !import_path_string.ends_with(&expected_import_end) | ||
345 | { | ||
346 | return None; | ||
347 | } | ||
348 | |||
349 | let segment_import = | ||
350 | find_import_for_segment(db, original_item_candidate, &unresolved_first_segment)?; | ||
351 | let trait_item_to_import = item_as_assoc(db, original_item) | ||
352 | .and_then(|assoc| assoc.containing_trait(db)) | ||
353 | .map(|trait_| ItemInNs::from(ModuleDef::from(trait_))); | ||
354 | Some(match (segment_import == original_item_candidate, trait_item_to_import) { | ||
355 | (true, Some(_)) => { | ||
356 | // FIXME we should be able to import both the trait and the segment, | ||
357 | // but it's unclear what to do with overlapping edits (merge imports?) | ||
358 | // especially in case of lazy completion edit resolutions. | ||
359 | return None; | ||
360 | } | ||
361 | (false, Some(trait_to_import)) => LocatedImport::new( | ||
362 | mod_path(trait_to_import)?, | ||
363 | trait_to_import, | ||
364 | original_item, | ||
365 | mod_path(original_item), | ||
366 | ), | ||
367 | (true, None) => LocatedImport::new( | ||
368 | import_path_candidate, | ||
369 | original_item_candidate, | ||
370 | original_item, | ||
371 | mod_path(original_item), | ||
372 | ), | ||
373 | (false, None) => LocatedImport::new( | ||
374 | mod_path(segment_import)?, | ||
375 | segment_import, | ||
376 | original_item, | ||
377 | mod_path(original_item), | ||
378 | ), | ||
379 | }) | ||
380 | } | ||
381 | |||
382 | fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> { | ||
383 | Some(match item { | ||
384 | ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) { | ||
385 | Some(assoc_item) => match assoc_item.container(db) { | ||
386 | AssocItemContainer::Trait(trait_) => ItemInNs::from(ModuleDef::from(trait_)), | ||
387 | AssocItemContainer::Impl(impl_) => { | ||
388 | ItemInNs::from(ModuleDef::from(impl_.target_ty(db).as_adt()?)) | ||
389 | } | ||
390 | }, | ||
391 | None => item, | ||
392 | }, | ||
393 | ItemInNs::Macros(_) => item, | ||
394 | }) | ||
395 | } | ||
396 | |||
397 | fn find_import_for_segment( | ||
398 | db: &RootDatabase, | ||
399 | original_item: ItemInNs, | ||
400 | unresolved_first_segment: &str, | ||
401 | ) -> Option<ItemInNs> { | ||
402 | let segment_is_name = item_name(db, original_item) | ||
403 | .map(|name| name.to_string() == unresolved_first_segment) | ||
404 | .unwrap_or(false); | ||
405 | |||
406 | Some(if segment_is_name { | ||
407 | original_item | ||
408 | } else { | ||
409 | let matching_module = | ||
410 | module_with_segment_name(db, &unresolved_first_segment, original_item)?; | ||
411 | ItemInNs::from(ModuleDef::from(matching_module)) | ||
412 | }) | ||
413 | } | ||
414 | |||
415 | fn module_with_segment_name( | ||
416 | db: &RootDatabase, | ||
417 | segment_name: &str, | ||
418 | candidate: ItemInNs, | ||
419 | ) -> Option<Module> { | ||
420 | let mut current_module = match candidate { | ||
421 | ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).module(db), | ||
422 | ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).module(db), | ||
423 | ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).module(db), | ||
424 | }; | ||
425 | while let Some(module) = current_module { | ||
426 | if let Some(module_name) = module.name(db) { | ||
427 | if module_name.to_string() == segment_name { | ||
428 | return Some(module); | ||
429 | } | ||
430 | } | ||
431 | current_module = module.parent(db); | ||
432 | } | ||
433 | None | ||
434 | } | ||
435 | |||
436 | fn trait_applicable_items( | ||
437 | db: &RootDatabase, | ||
438 | current_crate: Crate, | ||
439 | trait_candidate: &TraitImportCandidate, | ||
440 | trait_assoc_item: bool, | ||
441 | mod_path: impl Fn(ItemInNs) -> Option<ModPath>, | ||
442 | items_with_candidate_name: FxHashSet<ItemInNs>, | ||
443 | ) -> FxHashSet<LocatedImport> { | ||
444 | let _p = profile::span("import_assets::trait_applicable_items"); | ||
249 | let mut required_assoc_items = FxHashSet::default(); | 445 | let mut required_assoc_items = FxHashSet::default(); |
250 | 446 | ||
251 | let trait_candidates = unfiltered_imports | 447 | let trait_candidates = items_with_candidate_name |
252 | .filter_map(|input| match input { | 448 | .into_iter() |
253 | Either::Left(module_def) => module_def.as_assoc_item(db), | 449 | .filter_map(|input| item_as_assoc(db, input)) |
254 | _ => None, | ||
255 | }) | ||
256 | .filter_map(|assoc| { | 450 | .filter_map(|assoc| { |
257 | let assoc_item_trait = assoc.containing_trait(db)?; | 451 | let assoc_item_trait = assoc.containing_trait(db)?; |
258 | required_assoc_items.insert(assoc); | 452 | required_assoc_items.insert(assoc); |
@@ -260,11 +454,10 @@ fn applicable_defs<'a>( | |||
260 | }) | 454 | }) |
261 | .collect(); | 455 | .collect(); |
262 | 456 | ||
263 | let mut applicable_defs = FxHashSet::default(); | 457 | let mut located_imports = FxHashSet::default(); |
264 | 458 | ||
265 | match import_candidate { | 459 | if trait_assoc_item { |
266 | ImportCandidate::Path(_) => unreachable!(), | 460 | trait_candidate.receiver_ty.iterate_path_candidates( |
267 | ImportCandidate::TraitAssocItem(_) => receiver_ty.iterate_path_candidates( | ||
268 | db, | 461 | db, |
269 | current_crate, | 462 | current_crate, |
270 | &trait_candidates, | 463 | &trait_candidates, |
@@ -276,12 +469,21 @@ fn applicable_defs<'a>( | |||
276 | return None; | 469 | return None; |
277 | } | 470 | } |
278 | } | 471 | } |
279 | applicable_defs.insert(Either::Left(assoc_to_module_def(assoc))); | 472 | |
473 | let item = ItemInNs::from(ModuleDef::from(assoc.containing_trait(db)?)); | ||
474 | let original_item = assoc_to_item(assoc); | ||
475 | located_imports.insert(LocatedImport::new( | ||
476 | mod_path(item)?, | ||
477 | item, | ||
478 | original_item, | ||
479 | mod_path(original_item), | ||
480 | )); | ||
280 | } | 481 | } |
281 | None::<()> | 482 | None::<()> |
282 | }, | 483 | }, |
283 | ), | 484 | ) |
284 | ImportCandidate::TraitMethod(_) => receiver_ty.iterate_method_candidates( | 485 | } else { |
486 | trait_candidate.receiver_ty.iterate_method_candidates( | ||
285 | db, | 487 | db, |
286 | current_crate, | 488 | current_crate, |
287 | &trait_candidates, | 489 | &trait_candidates, |
@@ -289,21 +491,41 @@ fn applicable_defs<'a>( | |||
289 | |_, function| { | 491 | |_, function| { |
290 | let assoc = function.as_assoc_item(db)?; | 492 | let assoc = function.as_assoc_item(db)?; |
291 | if required_assoc_items.contains(&assoc) { | 493 | if required_assoc_items.contains(&assoc) { |
292 | applicable_defs.insert(Either::Left(assoc_to_module_def(assoc))); | 494 | let item = ItemInNs::from(ModuleDef::from(assoc.containing_trait(db)?)); |
495 | let original_item = assoc_to_item(assoc); | ||
496 | located_imports.insert(LocatedImport::new( | ||
497 | mod_path(item)?, | ||
498 | item, | ||
499 | original_item, | ||
500 | mod_path(original_item), | ||
501 | )); | ||
293 | } | 502 | } |
294 | None::<()> | 503 | None::<()> |
295 | }, | 504 | }, |
296 | ), | 505 | ) |
297 | }; | 506 | }; |
298 | 507 | ||
299 | Box::new(applicable_defs.into_iter()) | 508 | located_imports |
300 | } | 509 | } |
301 | 510 | ||
302 | fn assoc_to_module_def(assoc: AssocItem) -> ModuleDef { | 511 | fn assoc_to_item(assoc: AssocItem) -> ItemInNs { |
303 | match assoc { | 512 | match assoc { |
304 | AssocItem::Function(f) => f.into(), | 513 | AssocItem::Function(f) => ItemInNs::from(ModuleDef::from(f)), |
305 | AssocItem::Const(c) => c.into(), | 514 | AssocItem::Const(c) => ItemInNs::from(ModuleDef::from(c)), |
306 | AssocItem::TypeAlias(t) => t.into(), | 515 | AssocItem::TypeAlias(t) => ItemInNs::from(ModuleDef::from(t)), |
516 | } | ||
517 | } | ||
518 | |||
519 | fn get_mod_path( | ||
520 | db: &RootDatabase, | ||
521 | item_to_search: ItemInNs, | ||
522 | module_with_candidate: &Module, | ||
523 | prefixed: Option<PrefixKind>, | ||
524 | ) -> Option<ModPath> { | ||
525 | if let Some(prefix_kind) = prefixed { | ||
526 | module_with_candidate.find_use_path_prefixed(db, item_to_search, prefix_kind) | ||
527 | } else { | ||
528 | module_with_candidate.find_use_path(db, item_to_search) | ||
307 | } | 529 | } |
308 | } | 530 | } |
309 | 531 | ||
@@ -316,7 +538,7 @@ impl ImportCandidate { | |||
316 | Some(_) => None, | 538 | Some(_) => None, |
317 | None => Some(Self::TraitMethod(TraitImportCandidate { | 539 | None => Some(Self::TraitMethod(TraitImportCandidate { |
318 | receiver_ty: sema.type_of_expr(&method_call.receiver()?)?, | 540 | receiver_ty: sema.type_of_expr(&method_call.receiver()?)?, |
319 | name: NameToImport::Exact(method_call.name_ref()?.to_string()), | 541 | assoc_item_name: NameToImport::Exact(method_call.name_ref()?.to_string()), |
320 | })), | 542 | })), |
321 | } | 543 | } |
322 | } | 544 | } |
@@ -325,41 +547,63 @@ impl ImportCandidate { | |||
325 | if sema.resolve_path(path).is_some() { | 547 | if sema.resolve_path(path).is_some() { |
326 | return None; | 548 | return None; |
327 | } | 549 | } |
550 | path_import_candidate( | ||
551 | sema, | ||
552 | path.qualifier(), | ||
553 | NameToImport::Exact(path.segment()?.name_ref()?.to_string()), | ||
554 | ) | ||
555 | } | ||
328 | 556 | ||
329 | let segment = path.segment()?; | 557 | fn for_fuzzy_path( |
330 | let candidate = if let Some(qualifier) = path.qualifier() { | 558 | qualifier: Option<ast::Path>, |
331 | let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; | 559 | fuzzy_name: String, |
332 | let qualifier_start_path = | 560 | sema: &Semantics<RootDatabase>, |
333 | qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; | 561 | ) -> Option<Self> { |
334 | if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) { | 562 | path_import_candidate(sema, qualifier, NameToImport::Fuzzy(fuzzy_name)) |
335 | let qualifier_resolution = if qualifier_start_path == qualifier { | 563 | } |
336 | qualifier_start_resolution | 564 | |
565 | fn is_trait_candidate(&self) -> bool { | ||
566 | matches!(self, ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_)) | ||
567 | } | ||
568 | } | ||
569 | |||
570 | fn path_import_candidate( | ||
571 | sema: &Semantics<RootDatabase>, | ||
572 | qualifier: Option<ast::Path>, | ||
573 | name: NameToImport, | ||
574 | ) -> Option<ImportCandidate> { | ||
575 | Some(match qualifier { | ||
576 | Some(qualifier) => match sema.resolve_path(&qualifier) { | ||
577 | None => { | ||
578 | let qualifier_start = | ||
579 | qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; | ||
580 | let qualifier_start_path = | ||
581 | qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; | ||
582 | if sema.resolve_path(&qualifier_start_path).is_none() { | ||
583 | ImportCandidate::Path(PathImportCandidate { | ||
584 | qualifier: Some(FirstSegmentUnresolved { | ||
585 | fist_segment: qualifier_start, | ||
586 | full_qualifier: qualifier, | ||
587 | }), | ||
588 | name, | ||
589 | }) | ||
337 | } else { | 590 | } else { |
338 | sema.resolve_path(&qualifier)? | 591 | return None; |
339 | }; | ||
340 | match qualifier_resolution { | ||
341 | hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { | ||
342 | ImportCandidate::TraitAssocItem(TraitImportCandidate { | ||
343 | receiver_ty: assoc_item_path.ty(sema.db), | ||
344 | name: NameToImport::Exact(segment.name_ref()?.to_string()), | ||
345 | }) | ||
346 | } | ||
347 | _ => return None, | ||
348 | } | 592 | } |
349 | } else { | 593 | } |
350 | ImportCandidate::Path(PathImportCandidate { | 594 | Some(PathResolution::Def(ModuleDef::Adt(assoc_item_path))) => { |
351 | qualifier: Some(qualifier), | 595 | ImportCandidate::TraitAssocItem(TraitImportCandidate { |
352 | name: NameToImport::Exact(qualifier_start.to_string()), | 596 | receiver_ty: assoc_item_path.ty(sema.db), |
597 | assoc_item_name: name, | ||
353 | }) | 598 | }) |
354 | } | 599 | } |
355 | } else { | 600 | Some(_) => return None, |
356 | ImportCandidate::Path(PathImportCandidate { | 601 | }, |
357 | qualifier: None, | 602 | None => ImportCandidate::Path(PathImportCandidate { qualifier: None, name }), |
358 | name: NameToImport::Exact( | 603 | }) |
359 | segment.syntax().descendants().find_map(ast::NameRef::cast)?.to_string(), | 604 | } |
360 | ), | 605 | |
361 | }) | 606 | fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> { |
362 | }; | 607 | item.as_module_def_id() |
363 | Some(candidate) | 608 | .and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db)) |
364 | } | ||
365 | } | 609 | } |
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/items_locator.rs index 502e8281a..8a7f02935 100644 --- a/crates/ide_db/src/imports_locator.rs +++ b/crates/ide_db/src/items_locator.rs | |||
@@ -1,9 +1,10 @@ | |||
1 | //! This module contains an import search functionality that is provided to the assists module. | 1 | //! This module contains an import search functionality that is provided to the assists module. |
2 | //! Later, this should be moved away to a separate crate that is accessible from the assists module. | 2 | //! Later, this should be moved away to a separate crate that is accessible from the assists module. |
3 | 3 | ||
4 | use either::Either; | ||
4 | use hir::{ | 5 | use hir::{ |
5 | import_map::{self, ImportKind}, | 6 | import_map::{self, ImportKind}, |
6 | AsAssocItem, Crate, MacroDef, ModuleDef, Semantics, | 7 | AsAssocItem, Crate, ItemInNs, ModuleDef, Semantics, |
7 | }; | 8 | }; |
8 | use syntax::{ast, AstNode, SyntaxKind::NAME}; | 9 | use syntax::{ast, AstNode, SyntaxKind::NAME}; |
9 | 10 | ||
@@ -12,47 +13,47 @@ use crate::{ | |||
12 | symbol_index::{self, FileSymbol}, | 13 | symbol_index::{self, FileSymbol}, |
13 | RootDatabase, | 14 | RootDatabase, |
14 | }; | 15 | }; |
15 | use either::Either; | ||
16 | use rustc_hash::FxHashSet; | 16 | use rustc_hash::FxHashSet; |
17 | 17 | ||
18 | pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40; | 18 | pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40; |
19 | 19 | ||
20 | pub fn find_exact_imports<'a>( | 20 | pub fn with_exact_name( |
21 | sema: &Semantics<'a, RootDatabase>, | 21 | sema: &Semantics<'_, RootDatabase>, |
22 | krate: Crate, | 22 | krate: Crate, |
23 | name_to_import: String, | 23 | exact_name: String, |
24 | ) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>>> { | 24 | ) -> FxHashSet<ItemInNs> { |
25 | let _p = profile::span("find_exact_imports"); | 25 | let _p = profile::span("find_exact_imports"); |
26 | Box::new(find_imports( | 26 | find_items( |
27 | sema, | 27 | sema, |
28 | krate, | 28 | krate, |
29 | { | 29 | { |
30 | let mut local_query = symbol_index::Query::new(name_to_import.clone()); | 30 | let mut local_query = symbol_index::Query::new(exact_name.clone()); |
31 | local_query.exact(); | 31 | local_query.exact(); |
32 | local_query.limit(DEFAULT_QUERY_SEARCH_LIMIT); | 32 | local_query.limit(DEFAULT_QUERY_SEARCH_LIMIT); |
33 | local_query | 33 | local_query |
34 | }, | 34 | }, |
35 | import_map::Query::new(name_to_import) | 35 | import_map::Query::new(exact_name) |
36 | .limit(DEFAULT_QUERY_SEARCH_LIMIT) | 36 | .limit(DEFAULT_QUERY_SEARCH_LIMIT) |
37 | .name_only() | 37 | .name_only() |
38 | .search_mode(import_map::SearchMode::Equals) | 38 | .search_mode(import_map::SearchMode::Equals) |
39 | .case_sensitive(), | 39 | .case_sensitive(), |
40 | )) | 40 | ) |
41 | } | 41 | } |
42 | 42 | ||
43 | #[derive(Debug)] | ||
43 | pub enum AssocItemSearch { | 44 | pub enum AssocItemSearch { |
44 | Include, | 45 | Include, |
45 | Exclude, | 46 | Exclude, |
46 | AssocItemsOnly, | 47 | AssocItemsOnly, |
47 | } | 48 | } |
48 | 49 | ||
49 | pub fn find_similar_imports<'a>( | 50 | pub fn with_similar_name( |
50 | sema: &Semantics<'a, RootDatabase>, | 51 | sema: &Semantics<'_, RootDatabase>, |
51 | krate: Crate, | 52 | krate: Crate, |
52 | fuzzy_search_string: String, | 53 | fuzzy_search_string: String, |
53 | assoc_item_search: AssocItemSearch, | 54 | assoc_item_search: AssocItemSearch, |
54 | limit: Option<usize>, | 55 | limit: Option<usize>, |
55 | ) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> { | 56 | ) -> FxHashSet<ItemInNs> { |
56 | let _p = profile::span("find_similar_imports"); | 57 | let _p = profile::span("find_similar_imports"); |
57 | 58 | ||
58 | let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) | 59 | let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) |
@@ -76,37 +77,39 @@ pub fn find_similar_imports<'a>( | |||
76 | local_query.limit(limit); | 77 | local_query.limit(limit); |
77 | } | 78 | } |
78 | 79 | ||
79 | let db = sema.db; | 80 | find_items(sema, krate, local_query, external_query) |
80 | Box::new(find_imports(sema, krate, local_query, external_query).filter( | 81 | .into_iter() |
81 | move |import_candidate| match assoc_item_search { | 82 | .filter(move |&item| match assoc_item_search { |
82 | AssocItemSearch::Include => true, | 83 | AssocItemSearch::Include => true, |
83 | AssocItemSearch::Exclude => !is_assoc_item(import_candidate, db), | 84 | AssocItemSearch::Exclude => !is_assoc_item(item, sema.db), |
84 | AssocItemSearch::AssocItemsOnly => is_assoc_item(import_candidate, db), | 85 | AssocItemSearch::AssocItemsOnly => is_assoc_item(item, sema.db), |
85 | }, | 86 | }) |
86 | )) | 87 | .collect() |
87 | } | 88 | } |
88 | 89 | ||
89 | fn is_assoc_item(import_candidate: &Either<ModuleDef, MacroDef>, db: &RootDatabase) -> bool { | 90 | fn is_assoc_item(item: ItemInNs, db: &RootDatabase) -> bool { |
90 | match import_candidate { | 91 | item.as_module_def_id() |
91 | Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_some(), | 92 | .and_then(|module_def_id| ModuleDef::from(module_def_id).as_assoc_item(db)) |
92 | Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_some(), | 93 | .is_some() |
93 | Either::Left(ModuleDef::TypeAlias(type_alias)) => type_alias.as_assoc_item(db).is_some(), | ||
94 | _ => false, | ||
95 | } | ||
96 | } | 94 | } |
97 | 95 | ||
98 | fn find_imports<'a>( | 96 | fn find_items( |
99 | sema: &Semantics<'a, RootDatabase>, | 97 | sema: &Semantics<'_, RootDatabase>, |
100 | krate: Crate, | 98 | krate: Crate, |
101 | local_query: symbol_index::Query, | 99 | local_query: symbol_index::Query, |
102 | external_query: import_map::Query, | 100 | external_query: import_map::Query, |
103 | ) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { | 101 | ) -> FxHashSet<ItemInNs> { |
104 | let _p = profile::span("find_similar_imports"); | 102 | let _p = profile::span("find_similar_imports"); |
105 | let db = sema.db; | 103 | let db = sema.db; |
106 | 104 | ||
107 | // Query dependencies first. | 105 | // Query dependencies first. |
108 | let mut candidates: FxHashSet<_> = | 106 | let mut candidates = krate |
109 | krate.query_external_importables(db, external_query).collect(); | 107 | .query_external_importables(db, external_query) |
108 | .map(|external_importable| match external_importable { | ||
109 | Either::Left(module_def) => ItemInNs::from(module_def), | ||
110 | Either::Right(macro_def) => ItemInNs::from(macro_def), | ||
111 | }) | ||
112 | .collect::<FxHashSet<_>>(); | ||
110 | 113 | ||
111 | // Query the local crate using the symbol index. | 114 | // Query the local crate using the symbol index. |
112 | let local_results = symbol_index::crate_symbols(db, krate.into(), local_query); | 115 | let local_results = symbol_index::crate_symbols(db, krate.into(), local_query); |
@@ -114,19 +117,19 @@ fn find_imports<'a>( | |||
114 | candidates.extend( | 117 | candidates.extend( |
115 | local_results | 118 | local_results |
116 | .into_iter() | 119 | .into_iter() |
117 | .filter_map(|import_candidate| get_name_definition(sema, &import_candidate)) | 120 | .filter_map(|local_candidate| get_name_definition(sema, &local_candidate)) |
118 | .filter_map(|name_definition_to_import| match name_definition_to_import { | 121 | .filter_map(|name_definition_to_import| match name_definition_to_import { |
119 | Definition::ModuleDef(module_def) => Some(Either::Left(module_def)), | 122 | Definition::ModuleDef(module_def) => Some(ItemInNs::from(module_def)), |
120 | Definition::Macro(macro_def) => Some(Either::Right(macro_def)), | 123 | Definition::Macro(macro_def) => Some(ItemInNs::from(macro_def)), |
121 | _ => None, | 124 | _ => None, |
122 | }), | 125 | }), |
123 | ); | 126 | ); |
124 | 127 | ||
125 | candidates.into_iter() | 128 | candidates |
126 | } | 129 | } |
127 | 130 | ||
128 | fn get_name_definition<'a>( | 131 | fn get_name_definition( |
129 | sema: &Semantics<'a, RootDatabase>, | 132 | sema: &Semantics<'_, RootDatabase>, |
130 | import_candidate: &FileSymbol, | 133 | import_candidate: &FileSymbol, |
131 | ) -> Option<Definition> { | 134 | ) -> Option<Definition> { |
132 | let _p = profile::span("get_name_definition"); | 135 | let _p = profile::span("get_name_definition"); |
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index 6eb34b06b..88ee4a87d 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs | |||
@@ -8,7 +8,7 @@ pub mod line_index; | |||
8 | pub mod symbol_index; | 8 | pub mod symbol_index; |
9 | pub mod defs; | 9 | pub mod defs; |
10 | pub mod search; | 10 | pub mod search; |
11 | pub mod imports_locator; | 11 | pub mod items_locator; |
12 | pub mod source_change; | 12 | pub mod source_change; |
13 | pub mod ty_filter; | 13 | pub mod ty_filter; |
14 | pub mod traits; | 14 | pub mod traits; |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 4f6f250d6..2c4c339cb 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -697,7 +697,6 @@ pub(crate) fn handle_completion_resolve( | |||
697 | FilePosition { file_id, offset }, | 697 | FilePosition { file_id, offset }, |
698 | &resolve_data.full_import_path, | 698 | &resolve_data.full_import_path, |
699 | resolve_data.imported_name, | 699 | resolve_data.imported_name, |
700 | resolve_data.import_for_trait_assoc_item, | ||
701 | )? | 700 | )? |
702 | .into_iter() | 701 | .into_iter() |
703 | .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel))) | 702 | .flat_map(|edit| edit.into_iter().map(|indel| to_proto::text_edit(&line_index, indel))) |
@@ -1525,7 +1524,6 @@ struct CompletionResolveData { | |||
1525 | position: lsp_types::TextDocumentPositionParams, | 1524 | position: lsp_types::TextDocumentPositionParams, |
1526 | full_import_path: String, | 1525 | full_import_path: String, |
1527 | imported_name: String, | 1526 | imported_name: String, |
1528 | import_for_trait_assoc_item: bool, | ||
1529 | } | 1527 | } |
1530 | 1528 | ||
1531 | fn fill_resolve_data( | 1529 | fn fill_resolve_data( |
@@ -1534,15 +1532,13 @@ fn fill_resolve_data( | |||
1534 | position: &TextDocumentPositionParams, | 1532 | position: &TextDocumentPositionParams, |
1535 | ) -> Option<()> { | 1533 | ) -> Option<()> { |
1536 | let import_edit = item.import_to_add()?; | 1534 | let import_edit = item.import_to_add()?; |
1537 | let full_import_path = import_edit.import_path.to_string(); | 1535 | let import_path = &import_edit.import.import_path; |
1538 | let imported_name = import_edit.import_path.segments().last()?.to_string(); | ||
1539 | 1536 | ||
1540 | *resolve_data = Some( | 1537 | *resolve_data = Some( |
1541 | to_value(CompletionResolveData { | 1538 | to_value(CompletionResolveData { |
1542 | position: position.to_owned(), | 1539 | position: position.to_owned(), |
1543 | full_import_path, | 1540 | full_import_path: import_path.to_string(), |
1544 | imported_name, | 1541 | imported_name: import_path.segments().last()?.to_string(), |
1545 | import_for_trait_assoc_item: import_edit.import_for_trait_assoc_item, | ||
1546 | }) | 1542 | }) |
1547 | .unwrap(), | 1543 | .unwrap(), |
1548 | ); | 1544 | ); |
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index c39095def..33bde099b 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml | |||
@@ -25,10 +25,10 @@ serde = { version = "1.0.106", features = ["derive"] } | |||
25 | stdx = { path = "../stdx", version = "0.0.0" } | 25 | stdx = { path = "../stdx", version = "0.0.0" } |
26 | text_edit = { path = "../text_edit", version = "0.0.0" } | 26 | text_edit = { path = "../text_edit", version = "0.0.0" } |
27 | parser = { path = "../parser", version = "0.0.0" } | 27 | parser = { path = "../parser", version = "0.0.0" } |
28 | test_utils = { path = "../test_utils", version = "0.0.0" } | ||
29 | profile = { path = "../profile", version = "0.0.0" } | 28 | profile = { path = "../profile", version = "0.0.0" } |
30 | 29 | ||
31 | [dev-dependencies] | 30 | [dev-dependencies] |
31 | test_utils = { path = "../test_utils" } | ||
32 | walkdir = "2.3.1" | 32 | walkdir = "2.3.1" |
33 | rayon = "1" | 33 | rayon = "1" |
34 | expect-test = "1.1" | 34 | expect-test = "1.1" |
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index b6c5de658..70ba8adb4 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs | |||
@@ -91,6 +91,10 @@ pub fn path_from_segments( | |||
91 | }) | 91 | }) |
92 | } | 92 | } |
93 | 93 | ||
94 | pub fn path_from_text(text: &str) -> ast::Path { | ||
95 | ast_from_text(&format!("fn main() {{ let test = {}; }}", text)) | ||
96 | } | ||
97 | |||
94 | pub fn glob_use_tree() -> ast::UseTree { | 98 | pub fn glob_use_tree() -> ast::UseTree { |
95 | ast_from_text("use *;") | 99 | ast_from_text("use *;") |
96 | } | 100 | } |
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs index 11294c5b2..09e212e8c 100644 --- a/crates/syntax/src/lib.rs +++ b/crates/syntax/src/lib.rs | |||
@@ -37,6 +37,7 @@ pub mod algo; | |||
37 | pub mod ast; | 37 | pub mod ast; |
38 | #[doc(hidden)] | 38 | #[doc(hidden)] |
39 | pub mod fuzz; | 39 | pub mod fuzz; |
40 | pub mod utils; | ||
40 | 41 | ||
41 | use std::{marker::PhantomData, sync::Arc}; | 42 | use std::{marker::PhantomData, sync::Arc}; |
42 | 43 | ||
diff --git a/crates/syntax/src/utils.rs b/crates/syntax/src/utils.rs new file mode 100644 index 000000000..f4c02518b --- /dev/null +++ b/crates/syntax/src/utils.rs | |||
@@ -0,0 +1,43 @@ | |||
1 | //! A set of utils methods to reuse on other abstraction levels | ||
2 | |||
3 | use itertools::Itertools; | ||
4 | |||
5 | use crate::{ast, match_ast, AstNode}; | ||
6 | |||
7 | pub fn path_to_string_stripping_turbo_fish(path: &ast::Path) -> String { | ||
8 | path.syntax() | ||
9 | .children() | ||
10 | .filter_map(|node| { | ||
11 | match_ast! { | ||
12 | match node { | ||
13 | ast::PathSegment(it) => { | ||
14 | Some(it.name_ref()?.to_string()) | ||
15 | }, | ||
16 | ast::Path(it) => { | ||
17 | Some(path_to_string_stripping_turbo_fish(&it)) | ||
18 | }, | ||
19 | _ => None, | ||
20 | } | ||
21 | } | ||
22 | }) | ||
23 | .join("::") | ||
24 | } | ||
25 | |||
26 | #[cfg(test)] | ||
27 | mod tests { | ||
28 | use super::path_to_string_stripping_turbo_fish; | ||
29 | use crate::ast::make; | ||
30 | |||
31 | #[test] | ||
32 | fn turbofishes_are_stripped() { | ||
33 | assert_eq!("Vec", path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::<i32>")),); | ||
34 | assert_eq!( | ||
35 | "Vec::new", | ||
36 | path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::<i32>::new")), | ||
37 | ); | ||
38 | assert_eq!( | ||
39 | "Vec::new", | ||
40 | path_to_string_stripping_turbo_fish(&make::path_from_text("Vec::new()")), | ||
41 | ); | ||
42 | } | ||
43 | } | ||