aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKirill Bulatov <[email protected]>2021-02-23 23:20:00 +0000
committerKirill Bulatov <[email protected]>2021-03-08 21:58:48 +0000
commit309421c117fc20e58b9f30fb28a01a89f50b0086 (patch)
tree77ab1a1a1dd426a5ffa2f817e0df49c4b804782e
parentc395c3311dc2ac59251e86eaa6b86b597358d31f (diff)
Draft the qualifier import resolution
-rw-r--r--crates/ide_assists/src/handlers/qualify_path.rs9
-rw-r--r--crates/ide_completion/src/completions/flyimport.rs84
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs173
-rw-r--r--crates/ide_db/src/imports_locator.rs1
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 @@
1use std::iter; 1use std::iter;
2 2
3use hir::AsAssocItem; 3use hir::AsAssocItem;
4use ide_db::helpers::{import_assets::ImportCandidate, mod_path_to_ast}; 4use ide_db::helpers::{
5 import_assets::{ImportCandidate, Qualifier},
6 mod_path_to_ast,
7};
5use ide_db::RootDatabase; 8use ide_db::RootDatabase;
6use syntax::{ 9use 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 {
192fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { 195fn 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#"
782mod foo {
783 pub mod bar {
784 pub mod baz {
785 pub struct Item;
786 }
787 }
788}
789
790fn main() {
791 bar::baz::Ite$0
792}
793"#,
794 r#"
795use foo::bar;
796
797mod foo {
798 pub mod bar {
799 pub mod baz {
800 pub struct Item;
801 }
802 }
803}
804
805fn 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#"
817mod foo {
818 pub struct Item;
819
820 impl Item {
821 pub const TEST_ASSOC: usize = 3;
822 }
823}
824
825fn main() {
826 Item::TEST_A$0;
827}
828"#,
829 r#"
830use foo::Item;
831
832mod foo {
833 pub struct Item;
834
835 impl Item {
836 pub const TEST_ASSOC: usize = 3;
837 }
838}
839
840fn 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#"
782mod foo { 852mod 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
788fn main() { 862fn 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;
795mod foo { 869mod 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
801fn main() { 879fn 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.
2use either::Either; 2use either::Either;
3use hir::{ 3use 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};
7use rustc_hash::FxHashSet; 7use rustc_hash::FxHashSet;
8use syntax::{ast, AstNode}; 8use syntax::{ast, AstNode};
@@ -34,11 +34,17 @@ pub struct TraitImportCandidate {
34 34
35#[derive(Debug)] 35#[derive(Debug)]
36pub struct PathImportCandidate { 36pub 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)]
42pub enum Qualifier {
43 Absent,
44 FirstSegmentUnresolved(ast::PathSegment, ast::Path),
45}
46
47#[derive(Debug)]
42pub enum NameToImport { 48pub 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
226fn path_applicable_items<'a>( 232fn 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
303fn 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 311fn 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
319fn 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
250fn trait_applicable_defs<'a>( 339fn 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)]
43pub enum AssocItemSearch { 44pub enum AssocItemSearch {
44 Include, 45 Include,
45 Exclude, 46 Exclude,