diff options
author | Florian Diebold <[email protected]> | 2019-12-30 22:07:47 +0000 |
---|---|---|
committer | Florian Diebold <[email protected]> | 2020-01-11 22:33:04 +0000 |
commit | 38cd9f0c947981388af652c8691574675673c768 (patch) | |
tree | a02df4b0f68ee2a6f5ce58c620683e5987fb40a9 | |
parent | b1325488ec4c1b965e2e9a0b8b6dec1c8342498b (diff) |
Handle cycles
-rw-r--r-- | crates/ra_hir_def/src/find_path.rs | 59 |
1 files changed, 54 insertions, 5 deletions
diff --git a/crates/ra_hir_def/src/find_path.rs b/crates/ra_hir_def/src/find_path.rs index 09f3bf87d..3df2f2c09 100644 --- a/crates/ra_hir_def/src/find_path.rs +++ b/crates/ra_hir_def/src/find_path.rs | |||
@@ -10,8 +10,16 @@ use crate::{ | |||
10 | use hir_expand::name::Name; | 10 | use hir_expand::name::Name; |
11 | 11 | ||
12 | pub fn find_path(db: &impl DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> { | 12 | pub fn find_path(db: &impl DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> { |
13 | find_path_inner(db, item, from, 15) | ||
14 | } | ||
15 | |||
16 | fn find_path_inner(db: &impl DefDatabase, item: ItemInNs, from: ModuleId, max_len: usize) -> Option<ModPath> { | ||
13 | // Base cases: | 17 | // Base cases: |
14 | 18 | ||
19 | if max_len == 0 { | ||
20 | return None; | ||
21 | } | ||
22 | |||
15 | // - if the item is already in scope, return the name under which it is | 23 | // - if the item is already in scope, return the name under which it is |
16 | let def_map = db.crate_def_map(from.krate); | 24 | let def_map = db.crate_def_map(from.krate); |
17 | let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope; | 25 | let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope; |
@@ -75,18 +83,31 @@ pub fn find_path(db: &impl DefDatabase, item: ItemInNs, from: ModuleId) -> Optio | |||
75 | 83 | ||
76 | // - otherwise, look for modules containing (reexporting) it and import it from one of those | 84 | // - otherwise, look for modules containing (reexporting) it and import it from one of those |
77 | let importable_locations = find_importable_locations(db, item, from); | 85 | let importable_locations = find_importable_locations(db, item, from); |
78 | let mut candidate_paths = Vec::new(); | 86 | let mut best_path = None; |
87 | let mut best_path_len = max_len; | ||
79 | for (module_id, name) in importable_locations { | 88 | for (module_id, name) in importable_locations { |
80 | // TODO prevent infinite loops | 89 | let mut path = match find_path_inner(db, ItemInNs::Types(ModuleDefId::ModuleId(module_id)), from, best_path_len - 1) |
81 | let mut path = match find_path(db, ItemInNs::Types(ModuleDefId::ModuleId(module_id)), from) | ||
82 | { | 90 | { |
83 | None => continue, | 91 | None => continue, |
84 | Some(path) => path, | 92 | Some(path) => path, |
85 | }; | 93 | }; |
86 | path.segments.push(name); | 94 | path.segments.push(name); |
87 | candidate_paths.push(path); | 95 | if path_len(&path) < best_path_len { |
96 | best_path_len = path_len(&path); | ||
97 | best_path = Some(path); | ||
98 | } | ||
99 | } | ||
100 | best_path | ||
101 | } | ||
102 | |||
103 | fn path_len(path: &ModPath) -> usize { | ||
104 | path.segments.len() + match path.kind { | ||
105 | PathKind::Plain => 0, | ||
106 | PathKind::Super(i) => i as usize, | ||
107 | PathKind::Crate => 1, | ||
108 | PathKind::Abs => 0, | ||
109 | PathKind::DollarCrate(_) => 1, | ||
88 | } | 110 | } |
89 | candidate_paths.into_iter().min_by_key(|path| path.segments.len()) | ||
90 | } | 111 | } |
91 | 112 | ||
92 | fn find_importable_locations( | 113 | fn find_importable_locations( |
@@ -96,6 +117,9 @@ fn find_importable_locations( | |||
96 | ) -> Vec<(ModuleId, Name)> { | 117 | ) -> Vec<(ModuleId, Name)> { |
97 | let crate_graph = db.crate_graph(); | 118 | let crate_graph = db.crate_graph(); |
98 | let mut result = Vec::new(); | 119 | let mut result = Vec::new(); |
120 | // We only look in the crate from which we are importing, and the direct | ||
121 | // dependencies. We cannot refer to names from transitive dependencies | ||
122 | // directly (only through reexports in direct dependencies). | ||
99 | for krate in Some(from.krate) | 123 | for krate in Some(from.krate) |
100 | .into_iter() | 124 | .into_iter() |
101 | .chain(crate_graph.dependencies(from.krate).map(|dep| dep.crate_id)) | 125 | .chain(crate_graph.dependencies(from.krate).map(|dep| dep.crate_id)) |
@@ -110,6 +134,13 @@ fn find_importable_locations( | |||
110 | result | 134 | result |
111 | } | 135 | } |
112 | 136 | ||
137 | /// Collects all locations from which we might import the item in a particular | ||
138 | /// crate. These include the original definition of the item, and any | ||
139 | /// non-private `use`s. | ||
140 | /// | ||
141 | /// Note that the crate doesn't need to be the one in which the item is defined; | ||
142 | /// it might be re-exported in other crates. We cache this as a query since we | ||
143 | /// need to walk the whole def map for it. | ||
113 | pub(crate) fn importable_locations_in_crate_query( | 144 | pub(crate) fn importable_locations_in_crate_query( |
114 | db: &impl DefDatabase, | 145 | db: &impl DefDatabase, |
115 | item: ItemInNs, | 146 | item: ItemInNs, |
@@ -372,4 +403,22 @@ mod tests { | |||
372 | // crate::S would be shorter, but using private imports seems wrong | 403 | // crate::S would be shorter, but using private imports seems wrong |
373 | check_found_path(code, "crate::bar::S"); | 404 | check_found_path(code, "crate::bar::S"); |
374 | } | 405 | } |
406 | |||
407 | #[test] | ||
408 | fn import_cycle() { | ||
409 | let code = r#" | ||
410 | //- /main.rs | ||
411 | pub mod foo; | ||
412 | pub mod bar; | ||
413 | pub mod baz; | ||
414 | //- /bar.rs | ||
415 | <|> | ||
416 | //- /foo.rs | ||
417 | pub use super::baz; | ||
418 | pub struct S; | ||
419 | //- /baz.rs | ||
420 | pub use super::foo; | ||
421 | "#; | ||
422 | check_found_path(code, "crate::foo::S"); | ||
423 | } | ||
375 | } | 424 | } |