diff options
Diffstat (limited to 'crates/ide_db/src')
-rw-r--r-- | crates/ide_db/src/helpers/import_assets.rs | 356 | ||||
-rw-r--r-- | crates/ide_db/src/imports_locator.rs | 76 |
2 files changed, 274 insertions, 158 deletions
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs index edc3da318..517abbb4b 100644 --- a/crates/ide_db/src/helpers/import_assets.rs +++ b/crates/ide_db/src/helpers/import_assets.rs | |||
@@ -1,12 +1,13 @@ | |||
1 | //! Look up accessible paths for items. | 1 | //! Look up accessible paths for items. |
2 | use either::Either; | 2 | use either::Either; |
3 | use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; | 3 | use hir::{AsAssocItem, AssocItem, Crate, MacroDef, Module, ModuleDef, PrefixKind, Semantics}; |
4 | use rustc_hash::FxHashSet; | 4 | use rustc_hash::FxHashSet; |
5 | use syntax::{ast, AstNode, SyntaxNode}; | 5 | use syntax::{ast, AstNode}; |
6 | 6 | ||
7 | use crate::{imports_locator, RootDatabase}; | 7 | use crate::{ |
8 | 8 | imports_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT}, | |
9 | use super::insert_use::InsertUseConfig; | 9 | RootDatabase, |
10 | }; | ||
10 | 11 | ||
11 | #[derive(Debug)] | 12 | #[derive(Debug)] |
12 | pub enum ImportCandidate { | 13 | pub enum ImportCandidate { |
@@ -24,86 +25,141 @@ pub enum ImportCandidate { | |||
24 | 25 | ||
25 | #[derive(Debug)] | 26 | #[derive(Debug)] |
26 | pub struct TraitImportCandidate { | 27 | pub struct TraitImportCandidate { |
27 | pub ty: hir::Type, | 28 | pub receiver_ty: hir::Type, |
28 | pub name: ast::NameRef, | 29 | pub name: NameToImport, |
29 | } | 30 | } |
30 | 31 | ||
31 | #[derive(Debug)] | 32 | #[derive(Debug)] |
32 | pub struct PathImportCandidate { | 33 | pub struct PathImportCandidate { |
33 | pub qualifier: Option<ast::Path>, | 34 | pub qualifier: Option<ast::Path>, |
34 | pub name: ast::NameRef, | 35 | pub name: NameToImport, |
36 | } | ||
37 | |||
38 | #[derive(Debug)] | ||
39 | pub enum NameToImport { | ||
40 | Exact(String), | ||
41 | Fuzzy(String), | ||
42 | } | ||
43 | |||
44 | impl NameToImport { | ||
45 | pub fn text(&self) -> &str { | ||
46 | match self { | ||
47 | NameToImport::Exact(text) => text.as_str(), | ||
48 | NameToImport::Fuzzy(text) => text.as_str(), | ||
49 | } | ||
50 | } | ||
35 | } | 51 | } |
36 | 52 | ||
37 | #[derive(Debug)] | 53 | #[derive(Debug)] |
38 | pub struct ImportAssets { | 54 | pub struct ImportAssets { |
39 | import_candidate: ImportCandidate, | 55 | import_candidate: ImportCandidate, |
40 | module_with_name_to_import: hir::Module, | 56 | module_with_candidate: hir::Module, |
41 | syntax_under_caret: SyntaxNode, | ||
42 | } | 57 | } |
43 | 58 | ||
44 | impl ImportAssets { | 59 | impl ImportAssets { |
45 | pub fn for_method_call( | 60 | pub fn for_method_call( |
46 | method_call: ast::MethodCallExpr, | 61 | method_call: &ast::MethodCallExpr, |
47 | sema: &Semantics<RootDatabase>, | 62 | sema: &Semantics<RootDatabase>, |
48 | ) -> Option<Self> { | 63 | ) -> Option<Self> { |
49 | let syntax_under_caret = method_call.syntax().to_owned(); | ||
50 | let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?; | ||
51 | Some(Self { | 64 | Some(Self { |
52 | import_candidate: ImportCandidate::for_method_call(sema, &method_call)?, | 65 | import_candidate: ImportCandidate::for_method_call(sema, method_call)?, |
53 | module_with_name_to_import, | 66 | module_with_candidate: sema.scope(method_call.syntax()).module()?, |
54 | syntax_under_caret, | ||
55 | }) | 67 | }) |
56 | } | 68 | } |
57 | 69 | ||
58 | pub fn for_regular_path( | 70 | pub fn for_exact_path( |
59 | path_under_caret: ast::Path, | 71 | fully_qualified_path: &ast::Path, |
60 | sema: &Semantics<RootDatabase>, | 72 | sema: &Semantics<RootDatabase>, |
61 | ) -> Option<Self> { | 73 | ) -> Option<Self> { |
62 | let syntax_under_caret = path_under_caret.syntax().to_owned(); | 74 | let syntax_under_caret = fully_qualified_path.syntax(); |
63 | if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() { | 75 | if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() { |
64 | return None; | 76 | return None; |
65 | } | 77 | } |
66 | |||
67 | let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?; | ||
68 | Some(Self { | 78 | Some(Self { |
69 | import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?, | 79 | import_candidate: ImportCandidate::for_regular_path(sema, fully_qualified_path)?, |
70 | module_with_name_to_import, | 80 | module_with_candidate: sema.scope(syntax_under_caret).module()?, |
71 | syntax_under_caret, | ||
72 | }) | 81 | }) |
73 | } | 82 | } |
74 | 83 | ||
75 | pub fn syntax_under_caret(&self) -> &SyntaxNode { | 84 | pub fn for_fuzzy_path( |
76 | &self.syntax_under_caret | 85 | module_with_path: Module, |
86 | qualifier: Option<ast::Path>, | ||
87 | fuzzy_name: String, | ||
88 | sema: &Semantics<RootDatabase>, | ||
89 | ) -> Option<Self> { | ||
90 | Some(match qualifier { | ||
91 | Some(qualifier) => { | ||
92 | let qualifier_resolution = sema.resolve_path(&qualifier)?; | ||
93 | match qualifier_resolution { | ||
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 | }) | ||
77 | } | 118 | } |
78 | 119 | ||
120 | pub fn for_fuzzy_method_call( | ||
121 | module_with_method_call: Module, | ||
122 | receiver_ty: hir::Type, | ||
123 | fuzzy_method_name: String, | ||
124 | ) -> Option<Self> { | ||
125 | Some(Self { | ||
126 | import_candidate: ImportCandidate::TraitMethod(TraitImportCandidate { | ||
127 | receiver_ty, | ||
128 | name: NameToImport::Fuzzy(fuzzy_method_name), | ||
129 | }), | ||
130 | module_with_candidate: module_with_method_call, | ||
131 | }) | ||
132 | } | ||
133 | } | ||
134 | |||
135 | impl ImportAssets { | ||
79 | pub fn import_candidate(&self) -> &ImportCandidate { | 136 | pub fn import_candidate(&self) -> &ImportCandidate { |
80 | &self.import_candidate | 137 | &self.import_candidate |
81 | } | 138 | } |
82 | 139 | ||
83 | fn get_search_query(&self) -> &str { | 140 | fn name_to_import(&self) -> &NameToImport { |
84 | match &self.import_candidate { | 141 | match &self.import_candidate { |
85 | ImportCandidate::Path(candidate) => candidate.name.text(), | 142 | ImportCandidate::Path(candidate) => &candidate.name, |
86 | ImportCandidate::TraitAssocItem(candidate) | 143 | ImportCandidate::TraitAssocItem(candidate) |
87 | | ImportCandidate::TraitMethod(candidate) => candidate.name.text(), | 144 | | ImportCandidate::TraitMethod(candidate) => &candidate.name, |
88 | } | 145 | } |
89 | } | 146 | } |
90 | 147 | ||
91 | pub fn search_for_imports( | 148 | pub fn search_for_imports( |
92 | &self, | 149 | &self, |
93 | sema: &Semantics<RootDatabase>, | 150 | sema: &Semantics<RootDatabase>, |
94 | config: &InsertUseConfig, | 151 | prefix_kind: PrefixKind, |
95 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { | 152 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { |
96 | let _p = profile::span("import_assists::search_for_imports"); | 153 | let _p = profile::span("import_assets::search_for_imports"); |
97 | self.search_for(sema, Some(config.prefix_kind)) | 154 | self.search_for(sema, Some(prefix_kind)) |
98 | } | 155 | } |
99 | 156 | ||
100 | /// This may return non-absolute paths if a part of the returned path is already imported into scope. | 157 | /// This may return non-absolute paths if a part of the returned path is already imported into scope. |
101 | #[allow(dead_code)] | ||
102 | pub fn search_for_relative_paths( | 158 | pub fn search_for_relative_paths( |
103 | &self, | 159 | &self, |
104 | sema: &Semantics<RootDatabase>, | 160 | sema: &Semantics<RootDatabase>, |
105 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { | 161 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { |
106 | let _p = profile::span("import_assists::search_for_relative_paths"); | 162 | let _p = profile::span("import_assets::search_for_relative_paths"); |
107 | self.search_for(sema, None) | 163 | self.search_for(sema, None) |
108 | } | 164 | } |
109 | 165 | ||
@@ -112,99 +168,142 @@ impl ImportAssets { | |||
112 | sema: &Semantics<RootDatabase>, | 168 | sema: &Semantics<RootDatabase>, |
113 | prefixed: Option<hir::PrefixKind>, | 169 | prefixed: Option<hir::PrefixKind>, |
114 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { | 170 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { |
115 | let db = sema.db; | 171 | let current_crate = self.module_with_candidate.krate(); |
116 | let mut trait_candidates = FxHashSet::default(); | ||
117 | let current_crate = self.module_with_name_to_import.krate(); | ||
118 | 172 | ||
119 | let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| { | 173 | let unfiltered_imports = match self.name_to_import() { |
120 | trait_candidates.clear(); | 174 | NameToImport::Exact(exact_name) => { |
121 | match &self.import_candidate { | 175 | imports_locator::find_exact_imports(sema, current_crate, exact_name.clone()) |
122 | ImportCandidate::TraitAssocItem(trait_candidate) => { | 176 | } |
123 | let located_assoc_item = match candidate { | 177 | // FIXME: ideally, we should avoid using `fst` for seacrhing trait imports for assoc items: |
124 | Either::Left(ModuleDef::Function(located_function)) => { | 178 | // instead, we need to look up all trait impls for a certain struct and search through them only |
125 | located_function.as_assoc_item(db) | 179 | // see https://github.com/rust-analyzer/rust-analyzer/pull/7293#issuecomment-761585032 |
126 | } | 180 | // and https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Blanket.20trait.20impls.20lookup |
127 | Either::Left(ModuleDef::Const(located_const)) => { | 181 | // for the details |
128 | located_const.as_assoc_item(db) | 182 | NameToImport::Fuzzy(fuzzy_name) => { |
129 | } | 183 | let (assoc_item_search, limit) = match self.import_candidate { |
130 | _ => None, | 184 | ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => { |
185 | (AssocItemSearch::AssocItemsOnly, None) | ||
131 | } | 186 | } |
132 | .map(|assoc| assoc.container(db)) | 187 | _ => (AssocItemSearch::Exclude, Some(DEFAULT_QUERY_SEARCH_LIMIT)), |
133 | .and_then(Self::assoc_to_trait)?; | 188 | }; |
134 | 189 | imports_locator::find_similar_imports( | |
135 | trait_candidates.insert(located_assoc_item.into()); | 190 | sema, |
191 | current_crate, | ||
192 | fuzzy_name.clone(), | ||
193 | assoc_item_search, | ||
194 | limit, | ||
195 | ) | ||
196 | } | ||
197 | }; | ||
136 | 198 | ||
137 | trait_candidate | 199 | let db = sema.db; |
138 | .ty | 200 | let mut res = |
139 | .iterate_path_candidates( | 201 | applicable_defs(self.import_candidate(), current_crate, db, unfiltered_imports) |
140 | db, | 202 | .filter_map(|candidate| { |
141 | current_crate, | 203 | let item: hir::ItemInNs = candidate.clone().either(Into::into, Into::into); |
142 | &trait_candidates, | ||
143 | None, | ||
144 | |_, assoc| Self::assoc_to_trait(assoc.container(db)), | ||
145 | ) | ||
146 | .map(ModuleDef::from) | ||
147 | .map(Either::Left) | ||
148 | } | ||
149 | ImportCandidate::TraitMethod(trait_candidate) => { | ||
150 | let located_assoc_item = | ||
151 | if let Either::Left(ModuleDef::Function(located_function)) = candidate { | ||
152 | located_function | ||
153 | .as_assoc_item(db) | ||
154 | .map(|assoc| assoc.container(db)) | ||
155 | .and_then(Self::assoc_to_trait) | ||
156 | } else { | ||
157 | None | ||
158 | }?; | ||
159 | 204 | ||
160 | trait_candidates.insert(located_assoc_item.into()); | 205 | let item_to_search = match self.import_candidate { |
206 | ImportCandidate::TraitAssocItem(_) | ImportCandidate::TraitMethod(_) => { | ||
207 | let canidate_trait = match candidate { | ||
208 | Either::Left(module_def) => { | ||
209 | module_def.as_assoc_item(db)?.containing_trait(db) | ||
210 | } | ||
211 | _ => None, | ||
212 | }?; | ||
213 | ModuleDef::from(canidate_trait).into() | ||
214 | } | ||
215 | _ => item, | ||
216 | }; | ||
161 | 217 | ||
162 | trait_candidate | 218 | if let Some(prefix_kind) = prefixed { |
163 | .ty | 219 | self.module_with_candidate.find_use_path_prefixed( |
164 | .iterate_method_candidates( | ||
165 | db, | 220 | db, |
166 | current_crate, | 221 | item_to_search, |
167 | &trait_candidates, | 222 | prefix_kind, |
168 | None, | ||
169 | |_, function| { | ||
170 | Self::assoc_to_trait(function.as_assoc_item(db)?.container(db)) | ||
171 | }, | ||
172 | ) | 223 | ) |
173 | .map(ModuleDef::from) | 224 | } else { |
174 | .map(Either::Left) | 225 | self.module_with_candidate.find_use_path(db, item_to_search) |
175 | } | 226 | } |
176 | _ => Some(candidate), | 227 | .map(|path| (path, item)) |
177 | } | 228 | }) |
178 | }; | 229 | .filter(|(use_path, _)| use_path.len() > 1) |
179 | 230 | .collect::<Vec<_>>(); | |
180 | let mut res = imports_locator::find_exact_imports( | 231 | res.sort_by_cached_key(|(path, _)| path.clone()); |
181 | sema, | ||
182 | current_crate, | ||
183 | self.get_search_query().to_string(), | ||
184 | ) | ||
185 | .filter_map(filter) | ||
186 | .filter_map(|candidate| { | ||
187 | let item: hir::ItemInNs = candidate.either(Into::into, Into::into); | ||
188 | if let Some(prefix_kind) = prefixed { | ||
189 | self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind) | ||
190 | } else { | ||
191 | self.module_with_name_to_import.find_use_path(db, item) | ||
192 | } | ||
193 | .map(|path| (path, item)) | ||
194 | }) | ||
195 | .filter(|(use_path, _)| use_path.len() > 1) | ||
196 | .take(20) | ||
197 | .collect::<Vec<_>>(); | ||
198 | res.sort_by_key(|(path, _)| path.clone()); | ||
199 | res | 232 | res |
200 | } | 233 | } |
234 | } | ||
201 | 235 | ||
202 | fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> { | 236 | fn applicable_defs<'a>( |
203 | if let AssocItemContainer::Trait(extracted_trait) = assoc { | 237 | import_candidate: &ImportCandidate, |
204 | Some(extracted_trait) | 238 | current_crate: Crate, |
205 | } else { | 239 | db: &RootDatabase, |
206 | None | 240 | unfiltered_imports: Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a>, |
241 | ) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> { | ||
242 | let receiver_ty = match import_candidate { | ||
243 | ImportCandidate::Path(_) => return unfiltered_imports, | ||
244 | ImportCandidate::TraitAssocItem(candidate) | ImportCandidate::TraitMethod(candidate) => { | ||
245 | &candidate.receiver_ty | ||
207 | } | 246 | } |
247 | }; | ||
248 | |||
249 | let mut required_assoc_items = FxHashSet::default(); | ||
250 | |||
251 | let trait_candidates = unfiltered_imports | ||
252 | .filter_map(|input| match input { | ||
253 | Either::Left(module_def) => module_def.as_assoc_item(db), | ||
254 | _ => None, | ||
255 | }) | ||
256 | .filter_map(|assoc| { | ||
257 | let assoc_item_trait = assoc.containing_trait(db)?; | ||
258 | required_assoc_items.insert(assoc); | ||
259 | Some(assoc_item_trait.into()) | ||
260 | }) | ||
261 | .collect(); | ||
262 | |||
263 | let mut applicable_defs = FxHashSet::default(); | ||
264 | |||
265 | match import_candidate { | ||
266 | ImportCandidate::Path(_) => unreachable!(), | ||
267 | ImportCandidate::TraitAssocItem(_) => receiver_ty.iterate_path_candidates( | ||
268 | db, | ||
269 | current_crate, | ||
270 | &trait_candidates, | ||
271 | None, | ||
272 | |_, assoc| { | ||
273 | if required_assoc_items.contains(&assoc) { | ||
274 | if let AssocItem::Function(f) = assoc { | ||
275 | if f.self_param(db).is_some() { | ||
276 | return None; | ||
277 | } | ||
278 | } | ||
279 | applicable_defs.insert(Either::Left(assoc_to_module_def(assoc))); | ||
280 | } | ||
281 | None::<()> | ||
282 | }, | ||
283 | ), | ||
284 | ImportCandidate::TraitMethod(_) => receiver_ty.iterate_method_candidates( | ||
285 | db, | ||
286 | current_crate, | ||
287 | &trait_candidates, | ||
288 | None, | ||
289 | |_, function| { | ||
290 | let assoc = function.as_assoc_item(db)?; | ||
291 | if required_assoc_items.contains(&assoc) { | ||
292 | applicable_defs.insert(Either::Left(assoc_to_module_def(assoc))); | ||
293 | } | ||
294 | None::<()> | ||
295 | }, | ||
296 | ), | ||
297 | }; | ||
298 | |||
299 | Box::new(applicable_defs.into_iter()) | ||
300 | } | ||
301 | |||
302 | fn assoc_to_module_def(assoc: AssocItem) -> ModuleDef { | ||
303 | match assoc { | ||
304 | AssocItem::Function(f) => f.into(), | ||
305 | AssocItem::Const(c) => c.into(), | ||
306 | AssocItem::TypeAlias(t) => t.into(), | ||
208 | } | 307 | } |
209 | } | 308 | } |
210 | 309 | ||
@@ -216,22 +315,19 @@ impl ImportCandidate { | |||
216 | match sema.resolve_method_call(method_call) { | 315 | match sema.resolve_method_call(method_call) { |
217 | Some(_) => None, | 316 | Some(_) => None, |
218 | None => Some(Self::TraitMethod(TraitImportCandidate { | 317 | None => Some(Self::TraitMethod(TraitImportCandidate { |
219 | ty: sema.type_of_expr(&method_call.receiver()?)?, | 318 | receiver_ty: sema.type_of_expr(&method_call.receiver()?)?, |
220 | name: method_call.name_ref()?, | 319 | name: NameToImport::Exact(method_call.name_ref()?.to_string()), |
221 | })), | 320 | })), |
222 | } | 321 | } |
223 | } | 322 | } |
224 | 323 | ||
225 | fn for_regular_path( | 324 | fn for_regular_path(sema: &Semantics<RootDatabase>, path: &ast::Path) -> Option<Self> { |
226 | sema: &Semantics<RootDatabase>, | 325 | if sema.resolve_path(path).is_some() { |
227 | path_under_caret: &ast::Path, | ||
228 | ) -> Option<Self> { | ||
229 | if sema.resolve_path(path_under_caret).is_some() { | ||
230 | return None; | 326 | return None; |
231 | } | 327 | } |
232 | 328 | ||
233 | let segment = path_under_caret.segment()?; | 329 | let segment = path.segment()?; |
234 | let candidate = if let Some(qualifier) = path_under_caret.qualifier() { | 330 | let candidate = if let Some(qualifier) = path.qualifier() { |
235 | let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; | 331 | let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; |
236 | let qualifier_start_path = | 332 | let qualifier_start_path = |
237 | qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; | 333 | qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; |
@@ -244,8 +340,8 @@ impl ImportCandidate { | |||
244 | match qualifier_resolution { | 340 | match qualifier_resolution { |
245 | hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { | 341 | hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { |
246 | ImportCandidate::TraitAssocItem(TraitImportCandidate { | 342 | ImportCandidate::TraitAssocItem(TraitImportCandidate { |
247 | ty: assoc_item_path.ty(sema.db), | 343 | receiver_ty: assoc_item_path.ty(sema.db), |
248 | name: segment.name_ref()?, | 344 | name: NameToImport::Exact(segment.name_ref()?.to_string()), |
249 | }) | 345 | }) |
250 | } | 346 | } |
251 | _ => return None, | 347 | _ => return None, |
@@ -253,13 +349,15 @@ impl ImportCandidate { | |||
253 | } else { | 349 | } else { |
254 | ImportCandidate::Path(PathImportCandidate { | 350 | ImportCandidate::Path(PathImportCandidate { |
255 | qualifier: Some(qualifier), | 351 | qualifier: Some(qualifier), |
256 | name: qualifier_start, | 352 | name: NameToImport::Exact(qualifier_start.to_string()), |
257 | }) | 353 | }) |
258 | } | 354 | } |
259 | } else { | 355 | } else { |
260 | ImportCandidate::Path(PathImportCandidate { | 356 | ImportCandidate::Path(PathImportCandidate { |
261 | qualifier: None, | 357 | qualifier: None, |
262 | name: segment.syntax().descendants().find_map(ast::NameRef::cast)?, | 358 | name: NameToImport::Exact( |
359 | segment.syntax().descendants().find_map(ast::NameRef::cast)?.to_string(), | ||
360 | ), | ||
263 | }) | 361 | }) |
264 | }; | 362 | }; |
265 | Some(candidate) | 363 | Some(candidate) |
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs index d111fba92..502e8281a 100644 --- a/crates/ide_db/src/imports_locator.rs +++ b/crates/ide_db/src/imports_locator.rs | |||
@@ -1,7 +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 hir::{import_map, AsAssocItem, Crate, MacroDef, ModuleDef, Semantics}; | 4 | use hir::{ |
5 | import_map::{self, ImportKind}, | ||
6 | AsAssocItem, Crate, MacroDef, ModuleDef, Semantics, | ||
7 | }; | ||
5 | use syntax::{ast, AstNode, SyntaxKind::NAME}; | 8 | use syntax::{ast, AstNode, SyntaxKind::NAME}; |
6 | 9 | ||
7 | use crate::{ | 10 | use crate::{ |
@@ -12,69 +15,84 @@ use crate::{ | |||
12 | use either::Either; | 15 | use either::Either; |
13 | use rustc_hash::FxHashSet; | 16 | use rustc_hash::FxHashSet; |
14 | 17 | ||
15 | const QUERY_SEARCH_LIMIT: usize = 40; | 18 | pub(crate) const DEFAULT_QUERY_SEARCH_LIMIT: usize = 40; |
16 | 19 | ||
17 | pub fn find_exact_imports<'a>( | 20 | pub fn find_exact_imports<'a>( |
18 | sema: &Semantics<'a, RootDatabase>, | 21 | sema: &Semantics<'a, RootDatabase>, |
19 | krate: Crate, | 22 | krate: Crate, |
20 | name_to_import: String, | 23 | name_to_import: String, |
21 | ) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> { | 24 | ) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>>> { |
22 | let _p = profile::span("find_exact_imports"); | 25 | let _p = profile::span("find_exact_imports"); |
23 | find_imports( | 26 | Box::new(find_imports( |
24 | sema, | 27 | sema, |
25 | krate, | 28 | krate, |
26 | { | 29 | { |
27 | let mut local_query = symbol_index::Query::new(name_to_import.clone()); | 30 | let mut local_query = symbol_index::Query::new(name_to_import.clone()); |
28 | local_query.exact(); | 31 | local_query.exact(); |
29 | local_query.limit(QUERY_SEARCH_LIMIT); | 32 | local_query.limit(DEFAULT_QUERY_SEARCH_LIMIT); |
30 | local_query | 33 | local_query |
31 | }, | 34 | }, |
32 | import_map::Query::new(name_to_import) | 35 | import_map::Query::new(name_to_import) |
33 | .limit(QUERY_SEARCH_LIMIT) | 36 | .limit(DEFAULT_QUERY_SEARCH_LIMIT) |
34 | .name_only() | 37 | .name_only() |
35 | .search_mode(import_map::SearchMode::Equals) | 38 | .search_mode(import_map::SearchMode::Equals) |
36 | .case_sensitive(), | 39 | .case_sensitive(), |
37 | ) | 40 | )) |
41 | } | ||
42 | |||
43 | pub enum AssocItemSearch { | ||
44 | Include, | ||
45 | Exclude, | ||
46 | AssocItemsOnly, | ||
38 | } | 47 | } |
39 | 48 | ||
40 | pub fn find_similar_imports<'a>( | 49 | pub fn find_similar_imports<'a>( |
41 | sema: &Semantics<'a, RootDatabase>, | 50 | sema: &Semantics<'a, RootDatabase>, |
42 | krate: Crate, | 51 | krate: Crate, |
43 | limit: Option<usize>, | ||
44 | fuzzy_search_string: String, | 52 | fuzzy_search_string: String, |
45 | ignore_assoc_items: bool, | 53 | assoc_item_search: AssocItemSearch, |
46 | name_only: bool, | 54 | limit: Option<usize>, |
47 | ) -> impl Iterator<Item = Either<ModuleDef, MacroDef>> + 'a { | 55 | ) -> Box<dyn Iterator<Item = Either<ModuleDef, MacroDef>> + 'a> { |
48 | let _p = profile::span("find_similar_imports"); | 56 | let _p = profile::span("find_similar_imports"); |
49 | 57 | ||
50 | let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) | 58 | let mut external_query = import_map::Query::new(fuzzy_search_string.clone()) |
51 | .search_mode(import_map::SearchMode::Fuzzy); | 59 | .search_mode(import_map::SearchMode::Fuzzy) |
52 | if name_only { | 60 | .name_only(); |
53 | external_query = external_query.name_only(); | 61 | |
62 | match assoc_item_search { | ||
63 | AssocItemSearch::Include => {} | ||
64 | AssocItemSearch::Exclude => { | ||
65 | external_query = external_query.exclude_import_kind(ImportKind::AssociatedItem); | ||
66 | } | ||
67 | AssocItemSearch::AssocItemsOnly => { | ||
68 | external_query = external_query.assoc_items_only(); | ||
69 | } | ||
54 | } | 70 | } |
55 | 71 | ||
56 | let mut local_query = symbol_index::Query::new(fuzzy_search_string); | 72 | let mut local_query = symbol_index::Query::new(fuzzy_search_string); |
57 | 73 | ||
58 | if let Some(limit) = limit { | 74 | if let Some(limit) = limit { |
59 | local_query.limit(limit); | ||
60 | external_query = external_query.limit(limit); | 75 | external_query = external_query.limit(limit); |
76 | local_query.limit(limit); | ||
61 | } | 77 | } |
62 | 78 | ||
63 | let db = sema.db; | 79 | let db = sema.db; |
64 | find_imports(sema, krate, local_query, external_query).filter(move |import_candidate| { | 80 | Box::new(find_imports(sema, krate, local_query, external_query).filter( |
65 | if ignore_assoc_items { | 81 | move |import_candidate| match assoc_item_search { |
66 | match import_candidate { | 82 | AssocItemSearch::Include => true, |
67 | Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_none(), | 83 | AssocItemSearch::Exclude => !is_assoc_item(import_candidate, db), |
68 | Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_none(), | 84 | AssocItemSearch::AssocItemsOnly => is_assoc_item(import_candidate, db), |
69 | Either::Left(ModuleDef::TypeAlias(type_alias)) => { | 85 | }, |
70 | type_alias.as_assoc_item(db).is_none() | 86 | )) |
71 | } | 87 | } |
72 | _ => true, | 88 | |
73 | } | 89 | fn is_assoc_item(import_candidate: &Either<ModuleDef, MacroDef>, db: &RootDatabase) -> bool { |
74 | } else { | 90 | match import_candidate { |
75 | true | 91 | Either::Left(ModuleDef::Function(function)) => function.as_assoc_item(db).is_some(), |
76 | } | 92 | Either::Left(ModuleDef::Const(const_)) => const_.as_assoc_item(db).is_some(), |
77 | }) | 93 | Either::Left(ModuleDef::TypeAlias(type_alias)) => type_alias.as_assoc_item(db).is_some(), |
94 | _ => false, | ||
95 | } | ||
78 | } | 96 | } |
79 | 97 | ||
80 | fn find_imports<'a>( | 98 | fn find_imports<'a>( |