Skip to content

Commit 38cd9f0

Browse files
committed
Handle cycles
1 parent b132548 commit 38cd9f0

File tree

1 file changed

+54
-5
lines changed

1 file changed

+54
-5
lines changed

crates/ra_hir_def/src/find_path.rs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,16 @@ use crate::{
1010
use hir_expand::name::Name;
1111

1212
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> {
1317
// Base cases:
1418

19+
if max_len == 0 {
20+
return None;
21+
}
22+
1523
// - if the item is already in scope, return the name under which it is
1624
let def_map = db.crate_def_map(from.krate);
1725
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
7583

7684
// - otherwise, look for modules containing (reexporting) it and import it from one of those
7785
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;
7988
for (module_id, name) in importable_locations {
80-
// TODO prevent infinite loops
81-
let mut path = match find_path(db, ItemInNs::Types(ModuleDefId::ModuleId(module_id)), from)
89+
let mut path = match find_path_inner(db, ItemInNs::Types(ModuleDefId::ModuleId(module_id)), from, best_path_len - 1)
8290
{
8391
None => continue,
8492
Some(path) => path,
8593
};
8694
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,
88110
}
89-
candidate_paths.into_iter().min_by_key(|path| path.segments.len())
90111
}
91112

92113
fn find_importable_locations(
@@ -96,6 +117,9 @@ fn find_importable_locations(
96117
) -> Vec<(ModuleId, Name)> {
97118
let crate_graph = db.crate_graph();
98119
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).
99123
for krate in Some(from.krate)
100124
.into_iter()
101125
.chain(crate_graph.dependencies(from.krate).map(|dep| dep.crate_id))
@@ -110,6 +134,13 @@ fn find_importable_locations(
110134
result
111135
}
112136

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.
113144
pub(crate) fn importable_locations_in_crate_query(
114145
db: &impl DefDatabase,
115146
item: ItemInNs,
@@ -372,4 +403,22 @@ mod tests {
372403
// crate::S would be shorter, but using private imports seems wrong
373404
check_found_path(code, "crate::bar::S");
374405
}
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+
}
375424
}

0 commit comments

Comments
 (0)