diff options
author | Kirill Bulatov <[email protected]> | 2021-02-23 23:20:00 +0000 |
---|---|---|
committer | Kirill Bulatov <[email protected]> | 2021-03-08 21:58:48 +0000 |
commit | 309421c117fc20e58b9f30fb28a01a89f50b0086 (patch) | |
tree | 77ab1a1a1dd426a5ffa2f817e0df49c4b804782e | |
parent | c395c3311dc2ac59251e86eaa6b86b597358d31f (diff) |
Draft the qualifier import resolution
-rw-r--r-- | crates/ide_assists/src/handlers/qualify_path.rs | 9 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/flyimport.rs | 84 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/import_assets.rs | 173 | ||||
-rw-r--r-- | crates/ide_db/src/imports_locator.rs | 1 |
4 files changed, 224 insertions, 43 deletions
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs index 1fb4931cd..c0463311e 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, Qualifier}, | ||
6 | 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, |
@@ -45,7 +48,7 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
45 | 48 | ||
46 | let qualify_candidate = match candidate { | 49 | let qualify_candidate = match candidate { |
47 | ImportCandidate::Path(candidate) => { | 50 | ImportCandidate::Path(candidate) => { |
48 | if candidate.unresolved_qualifier.is_some() { | 51 | if !matches!(candidate.qualifier, Qualifier::Absent) { |
49 | cov_mark::hit!(qualify_path_qualifier_start); | 52 | cov_mark::hit!(qualify_path_qualifier_start); |
50 | let path = ast::Path::cast(syntax_under_caret)?; | 53 | let path = ast::Path::cast(syntax_under_caret)?; |
51 | let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); | 54 | let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); |
@@ -192,7 +195,7 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel { | |||
192 | fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { | 195 | fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { |
193 | match candidate { | 196 | match candidate { |
194 | ImportCandidate::Path(candidate) => { | 197 | ImportCandidate::Path(candidate) => { |
195 | if candidate.unresolved_qualifier.is_some() { | 198 | if !matches!(candidate.qualifier, Qualifier::Absent) { |
196 | format!("Qualify with `{}`", &import) | 199 | format!("Qualify with `{}`", &import) |
197 | } else { | 200 | } else { |
198 | format!("Qualify as `{}`", &import) | 201 | format!("Qualify as `{}`", &import) |
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs index 16384551c..64b60bbdd 100644 --- a/crates/ide_completion/src/completions/flyimport.rs +++ b/crates/ide_completion/src/completions/flyimport.rs | |||
@@ -775,18 +775,92 @@ fn main() { | |||
775 | } | 775 | } |
776 | 776 | ||
777 | #[test] | 777 | #[test] |
778 | fn unresolved_qualifiers() { | 778 | fn unresolved_qualifier() { |
779 | check_edit( | ||
780 | "Item", | ||
781 | r#" | ||
782 | mod foo { | ||
783 | pub mod bar { | ||
784 | pub mod baz { | ||
785 | pub struct Item; | ||
786 | } | ||
787 | } | ||
788 | } | ||
789 | |||
790 | fn main() { | ||
791 | bar::baz::Ite$0 | ||
792 | } | ||
793 | "#, | ||
794 | r#" | ||
795 | use foo::bar; | ||
796 | |||
797 | mod foo { | ||
798 | pub mod bar { | ||
799 | pub mod baz { | ||
800 | pub struct Item; | ||
801 | } | ||
802 | } | ||
803 | } | ||
804 | |||
805 | fn main() { | ||
806 | bar::baz::Item | ||
807 | } | ||
808 | "#, | ||
809 | ); | ||
810 | } | ||
811 | |||
812 | #[test] | ||
813 | fn unresolved_assoc_item_container() { | ||
814 | check_edit( | ||
815 | "Item", | ||
816 | r#" | ||
817 | mod foo { | ||
818 | pub struct Item; | ||
819 | |||
820 | impl Item { | ||
821 | pub const TEST_ASSOC: usize = 3; | ||
822 | } | ||
823 | } | ||
824 | |||
825 | fn main() { | ||
826 | Item::TEST_A$0; | ||
827 | } | ||
828 | "#, | ||
829 | r#" | ||
830 | use foo::Item; | ||
831 | |||
832 | mod foo { | ||
833 | pub struct Item; | ||
834 | |||
835 | impl Item { | ||
836 | pub const TEST_ASSOC: usize = 3; | ||
837 | } | ||
838 | } | ||
839 | |||
840 | fn main() { | ||
841 | Item::TEST_ASSOC | ||
842 | } | ||
843 | "#, | ||
844 | ); | ||
845 | } | ||
846 | |||
847 | #[test] | ||
848 | fn unresolved_assoc_item_container_with_path() { | ||
779 | check_edit( | 849 | check_edit( |
780 | "Item", | 850 | "Item", |
781 | r#" | 851 | r#" |
782 | mod foo { | 852 | mod foo { |
783 | pub mod bar { | 853 | pub mod bar { |
784 | pub struct Item; | 854 | pub struct Item; |
855 | |||
856 | impl Item { | ||
857 | pub const TEST_ASSOC: usize = 3; | ||
858 | } | ||
785 | } | 859 | } |
786 | } | 860 | } |
787 | 861 | ||
788 | fn main() { | 862 | fn main() { |
789 | bar::Ite$0 | 863 | bar::Item::TEST_A$0; |
790 | } | 864 | } |
791 | "#, | 865 | "#, |
792 | r#" | 866 | r#" |
@@ -795,11 +869,15 @@ use foo::bar; | |||
795 | mod foo { | 869 | mod foo { |
796 | pub mod bar { | 870 | pub mod bar { |
797 | pub struct Item; | 871 | pub struct Item; |
872 | |||
873 | impl Item { | ||
874 | pub const TEST_ASSOC: usize = 3; | ||
875 | } | ||
798 | } | 876 | } |
799 | } | 877 | } |
800 | 878 | ||
801 | fn main() { | 879 | fn main() { |
802 | bar::Item | 880 | bar::Item::TEST_ASSOC |
803 | } | 881 | } |
804 | "#, | 882 | "#, |
805 | ); | 883 | ); |
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, |