diff options
Diffstat (limited to 'crates/ide_db/src')
-rw-r--r-- | crates/ide_db/src/helpers/import_assets.rs | 173 | ||||
-rw-r--r-- | crates/ide_db/src/imports_locator.rs | 1 |
2 files changed, 137 insertions, 37 deletions
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs index 976dc92fb..dc3b92a64 100644 --- a/crates/ide_db/src/helpers/import_assets.rs +++ b/crates/ide_db/src/helpers/import_assets.rs | |||
@@ -1,8 +1,8 @@ | |||
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::{ | 3 | use hir::{ |
4 | AsAssocItem, AssocItem, Crate, ItemInNs, MacroDef, ModPath, Module, ModuleDef, PrefixKind, | 4 | AsAssocItem, AssocItem, Crate, ItemInNs, MacroDef, ModPath, Module, ModuleDef, Name, |
5 | Semantics, | 5 | PrefixKind, Semantics, |
6 | }; | 6 | }; |
7 | use rustc_hash::FxHashSet; | 7 | use rustc_hash::FxHashSet; |
8 | use syntax::{ast, AstNode}; | 8 | use syntax::{ast, AstNode}; |
@@ -34,11 +34,17 @@ pub struct TraitImportCandidate { | |||
34 | 34 | ||
35 | #[derive(Debug)] | 35 | #[derive(Debug)] |
36 | pub struct PathImportCandidate { | 36 | pub struct PathImportCandidate { |
37 | pub unresolved_qualifier: Option<ast::Path>, | 37 | pub qualifier: Qualifier, |
38 | pub name: NameToImport, | 38 | pub name: NameToImport, |
39 | } | 39 | } |
40 | 40 | ||
41 | #[derive(Debug)] | 41 | #[derive(Debug)] |
42 | pub enum Qualifier { | ||
43 | Absent, | ||
44 | FirstSegmentUnresolved(ast::PathSegment, ast::Path), | ||
45 | } | ||
46 | |||
47 | #[derive(Debug)] | ||
42 | pub enum NameToImport { | 48 | pub enum NameToImport { |
43 | Exact(String), | 49 | Exact(String), |
44 | Fuzzy(String), | 50 | Fuzzy(String), |
@@ -162,8 +168,9 @@ impl ImportAssets { | |||
162 | let (assoc_item_search, limit) = if self.import_candidate.is_trait_candidate() { | 168 | let (assoc_item_search, limit) = if self.import_candidate.is_trait_candidate() { |
163 | (AssocItemSearch::AssocItemsOnly, None) | 169 | (AssocItemSearch::AssocItemsOnly, None) |
164 | } else { | 170 | } else { |
165 | (AssocItemSearch::Exclude, Some(DEFAULT_QUERY_SEARCH_LIMIT)) | 171 | (AssocItemSearch::Include, Some(DEFAULT_QUERY_SEARCH_LIMIT)) |
166 | }; | 172 | }; |
173 | |||
167 | imports_locator::find_similar_imports( | 174 | imports_locator::find_similar_imports( |
168 | sema, | 175 | sema, |
169 | current_crate, | 176 | current_crate, |
@@ -192,17 +199,16 @@ impl ImportAssets { | |||
192 | let db = sema.db; | 199 | let db = sema.db; |
193 | 200 | ||
194 | match &self.import_candidate { | 201 | match &self.import_candidate { |
195 | ImportCandidate::Path(path_candidate) => Box::new(path_applicable_items( | 202 | ImportCandidate::Path(path_candidate) => Box::new( |
196 | sema, | 203 | path_applicable_items( |
197 | path_candidate, | 204 | db, |
198 | unfiltered_defs | 205 | path_candidate, |
199 | .into_iter() | 206 | &self.module_with_candidate, |
200 | .map(|def| def.either(ItemInNs::from, ItemInNs::from)) | 207 | prefixed, |
201 | .filter_map(move |item_to_search| { | 208 | unfiltered_defs, |
202 | get_mod_path(db, item_to_search, &self.module_with_candidate, prefixed) | 209 | ) |
203 | .zip(Some(item_to_search)) | 210 | .into_iter(), |
204 | }), | 211 | ), |
205 | )), | ||
206 | ImportCandidate::TraitAssocItem(trait_candidate) => Box::new( | 212 | ImportCandidate::TraitAssocItem(trait_candidate) => Box::new( |
207 | trait_applicable_defs(db, current_crate, trait_candidate, true, unfiltered_defs) | 213 | trait_applicable_defs(db, current_crate, trait_candidate, true, unfiltered_defs) |
208 | .into_iter() | 214 | .into_iter() |
@@ -224,27 +230,110 @@ impl ImportAssets { | |||
224 | } | 230 | } |
225 | 231 | ||
226 | fn path_applicable_items<'a>( | 232 | fn path_applicable_items<'a>( |
227 | sema: &'a Semantics<RootDatabase>, | 233 | db: &'a RootDatabase, |
228 | path_candidate: &PathImportCandidate, | 234 | path_candidate: &'a PathImportCandidate, |
229 | unfiltered_defs: impl Iterator<Item = (ModPath, ItemInNs)> + 'a, | 235 | module_with_candidate: &hir::Module, |
230 | ) -> Box<dyn Iterator<Item = (ModPath, ItemInNs)> + 'a> { | 236 | prefixed: Option<hir::PrefixKind>, |
231 | let unresolved_qualifier = match &path_candidate.unresolved_qualifier { | 237 | unfiltered_defs: impl Iterator<Item = Either<ModuleDef, MacroDef>> + 'a, |
232 | Some(qualifier) => qualifier, | 238 | ) -> FxHashSet<(ModPath, ItemInNs)> { |
233 | None => { | 239 | let applicable_items = unfiltered_defs |
234 | return Box::new(unfiltered_defs); | 240 | .filter_map(|def| { |
241 | let (assoc_original, candidate) = match def { | ||
242 | Either::Left(module_def) => match module_def.as_assoc_item(db) { | ||
243 | Some(assoc_item) => match assoc_item.container(db) { | ||
244 | hir::AssocItemContainer::Trait(trait_) => { | ||
245 | (Some(module_def), ItemInNs::from(ModuleDef::from(trait_))) | ||
246 | } | ||
247 | hir::AssocItemContainer::Impl(impl_) => ( | ||
248 | Some(module_def), | ||
249 | ItemInNs::from(ModuleDef::from(impl_.target_ty(db).as_adt()?)), | ||
250 | ), | ||
251 | }, | ||
252 | None => (None, ItemInNs::from(module_def)), | ||
253 | }, | ||
254 | Either::Right(macro_def) => (None, ItemInNs::from(macro_def)), | ||
255 | }; | ||
256 | Some((assoc_original, candidate)) | ||
257 | }) | ||
258 | .filter_map(|(assoc_original, candidate)| { | ||
259 | get_mod_path(db, candidate, module_with_candidate, prefixed) | ||
260 | .zip(Some((assoc_original, candidate))) | ||
261 | }); | ||
262 | |||
263 | let (unresolved_first_segment, unresolved_qualifier) = match &path_candidate.qualifier { | ||
264 | Qualifier::Absent => { | ||
265 | return applicable_items | ||
266 | .map(|(candidate_path, (_, candidate))| (candidate_path, candidate)) | ||
267 | .collect(); | ||
235 | } | 268 | } |
269 | Qualifier::FirstSegmentUnresolved(first_segment, qualifier) => (first_segment, qualifier), | ||
236 | }; | 270 | }; |
237 | 271 | ||
238 | let qualifier_string = unresolved_qualifier.to_string(); | 272 | // TODO kb need to remove turbofish from the qualifier, maybe use the segments instead? |
239 | Box::new(unfiltered_defs.filter(move |(candidate_path, _)| { | 273 | let unresolved_qualifier_string = unresolved_qualifier.to_string(); |
240 | let mut candidate_qualifier = candidate_path.clone(); | 274 | let unresolved_first_segment_string = unresolved_first_segment.to_string(); |
241 | candidate_qualifier.pop_segment(); | 275 | |
276 | applicable_items | ||
277 | .filter(|(candidate_path, _)| { | ||
278 | let candidate_path_string = candidate_path.to_string(); | ||
279 | candidate_path_string.contains(&unresolved_qualifier_string) | ||
280 | && candidate_path_string.contains(&unresolved_first_segment_string) | ||
281 | }) | ||
282 | // TODO kb need to adjust the return type: I get the results rendered rather badly | ||
283 | .filter_map(|(candidate_path, (assoc_original, candidate))| { | ||
284 | if let Some(assoc_original) = assoc_original { | ||
285 | if item_name(db, candidate)?.to_string() == unresolved_first_segment_string { | ||
286 | return Some((candidate_path, ItemInNs::from(assoc_original))); | ||
287 | } | ||
288 | } | ||
289 | |||
290 | let matching_module = | ||
291 | module_with_matching_name(db, &unresolved_first_segment_string, candidate)?; | ||
292 | let path = get_mod_path( | ||
293 | db, | ||
294 | ItemInNs::from(ModuleDef::from(matching_module)), | ||
295 | module_with_candidate, | ||
296 | prefixed, | ||
297 | )?; | ||
298 | Some((path, candidate)) | ||
299 | }) | ||
300 | .collect() | ||
301 | } | ||
302 | |||
303 | fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> { | ||
304 | match item { | ||
305 | ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).name(db), | ||
306 | ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).name(db), | ||
307 | ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).name(db), | ||
308 | } | ||
309 | } | ||
242 | 310 | ||
243 | // TODO kb | 311 | fn item_module(db: &RootDatabase, item: ItemInNs) -> Option<Module> { |
244 | // * take 1st segment of `unresolved_qualifier` and return it instead of the original `ItemInNs` | 312 | match item { |
245 | // * Update `ModPath`: pop until 1st segment of `unresolved_qualifier` reached (do not rely on name comparison, nested mod names can repeat) | 313 | ItemInNs::Types(module_def_id) => ModuleDef::from(module_def_id).module(db), |
246 | candidate_qualifier.to_string().ends_with(&qualifier_string) | 314 | ItemInNs::Values(module_def_id) => ModuleDef::from(module_def_id).module(db), |
247 | })) | 315 | ItemInNs::Macros(macro_def_id) => MacroDef::from(macro_def_id).module(db), |
316 | } | ||
317 | } | ||
318 | |||
319 | fn module_with_matching_name( | ||
320 | db: &RootDatabase, | ||
321 | unresolved_first_segment_string: &str, | ||
322 | candidate: ItemInNs, | ||
323 | ) -> Option<Module> { | ||
324 | let mut current_module = item_module(db, candidate); | ||
325 | while let Some(module) = current_module { | ||
326 | match module.name(db) { | ||
327 | Some(module_name) => { | ||
328 | if module_name.to_string().as_str() == unresolved_first_segment_string { | ||
329 | return Some(module); | ||
330 | } | ||
331 | } | ||
332 | None => {} | ||
333 | } | ||
334 | current_module = module.parent(db); | ||
335 | } | ||
336 | None | ||
248 | } | 337 | } |
249 | 338 | ||
250 | fn trait_applicable_defs<'a>( | 339 | fn trait_applicable_defs<'a>( |
@@ -367,10 +456,20 @@ fn path_import_candidate( | |||
367 | ) -> Option<ImportCandidate> { | 456 | ) -> Option<ImportCandidate> { |
368 | Some(match qualifier { | 457 | Some(match qualifier { |
369 | Some(qualifier) => match sema.resolve_path(&qualifier) { | 458 | Some(qualifier) => match sema.resolve_path(&qualifier) { |
370 | None => ImportCandidate::Path(PathImportCandidate { | 459 | None => { |
371 | unresolved_qualifier: Some(qualifier), | 460 | let qualifier_start = |
372 | name, | 461 | qualifier.syntax().descendants().find_map(ast::PathSegment::cast)?; |
373 | }), | 462 | let qualifier_start_path = |
463 | qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; | ||
464 | if sema.resolve_path(&qualifier_start_path).is_none() { | ||
465 | ImportCandidate::Path(PathImportCandidate { | ||
466 | qualifier: Qualifier::FirstSegmentUnresolved(qualifier_start, qualifier), | ||
467 | name, | ||
468 | }) | ||
469 | } else { | ||
470 | return None; | ||
471 | } | ||
472 | } | ||
374 | Some(hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path))) => { | 473 | Some(hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path))) => { |
375 | ImportCandidate::TraitAssocItem(TraitImportCandidate { | 474 | ImportCandidate::TraitAssocItem(TraitImportCandidate { |
376 | receiver_ty: assoc_item_path.ty(sema.db), | 475 | receiver_ty: assoc_item_path.ty(sema.db), |
@@ -379,6 +478,6 @@ fn path_import_candidate( | |||
379 | } | 478 | } |
380 | Some(_) => return None, | 479 | Some(_) => return None, |
381 | }, | 480 | }, |
382 | None => ImportCandidate::Path(PathImportCandidate { unresolved_qualifier: None, name }), | 481 | None => ImportCandidate::Path(PathImportCandidate { qualifier: Qualifier::Absent, name }), |
383 | }) | 482 | }) |
384 | } | 483 | } |
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs index 502e8281a..480cbf1ea 100644 --- a/crates/ide_db/src/imports_locator.rs +++ b/crates/ide_db/src/imports_locator.rs | |||
@@ -40,6 +40,7 @@ pub fn find_exact_imports<'a>( | |||
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, |