aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_def
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_hir_def')
-rw-r--r--crates/ra_hir_def/src/find_path.rs455
-rw-r--r--crates/ra_hir_def/src/item_scope.rs39
-rw-r--r--crates/ra_hir_def/src/lib.rs1
-rw-r--r--crates/ra_hir_def/src/path.rs42
-rw-r--r--crates/ra_hir_def/src/resolver.rs15
-rw-r--r--crates/ra_hir_def/src/test_db.rs13
6 files changed, 559 insertions, 6 deletions
diff --git a/crates/ra_hir_def/src/find_path.rs b/crates/ra_hir_def/src/find_path.rs
new file mode 100644
index 000000000..f7dc8acb7
--- /dev/null
+++ b/crates/ra_hir_def/src/find_path.rs
@@ -0,0 +1,455 @@
1//! An algorithm to find a path to refer to a certain item.
2
3use crate::{
4 db::DefDatabase,
5 item_scope::ItemInNs,
6 path::{ModPath, PathKind},
7 visibility::Visibility,
8 CrateId, ModuleDefId, ModuleId,
9};
10use hir_expand::name::Name;
11
12const MAX_PATH_LEN: usize = 15;
13
14// FIXME: handle local items
15
16/// Find a path that can be used to refer to a certain item. This can depend on
17/// *from where* you're referring to the item, hence the `from` parameter.
18pub fn find_path(db: &impl DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> {
19 find_path_inner(db, item, from, MAX_PATH_LEN)
20}
21
22fn find_path_inner(
23 db: &impl DefDatabase,
24 item: ItemInNs,
25 from: ModuleId,
26 max_len: usize,
27) -> Option<ModPath> {
28 if max_len == 0 {
29 return None;
30 }
31
32 // Base cases:
33
34 // - if the item is already in scope, return the name under which it is
35 let def_map = db.crate_def_map(from.krate);
36 let from_scope: &crate::item_scope::ItemScope = &def_map.modules[from.local_id].scope;
37 if let Some((name, _)) = from_scope.name_of(item) {
38 return Some(ModPath::from_simple_segments(PathKind::Plain, vec![name.clone()]));
39 }
40
41 // - if the item is the crate root, return `crate`
42 if item
43 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
44 krate: from.krate,
45 local_id: def_map.root,
46 }))
47 {
48 return Some(ModPath::from_simple_segments(PathKind::Crate, Vec::new()));
49 }
50
51 // - if the item is the module we're in, use `self`
52 if item == ItemInNs::Types(from.into()) {
53 return Some(ModPath::from_simple_segments(PathKind::Super(0), Vec::new()));
54 }
55
56 // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly)
57 if let Some(parent_id) = def_map.modules[from.local_id].parent {
58 if item
59 == ItemInNs::Types(ModuleDefId::ModuleId(ModuleId {
60 krate: from.krate,
61 local_id: parent_id,
62 }))
63 {
64 return Some(ModPath::from_simple_segments(PathKind::Super(1), Vec::new()));
65 }
66 }
67
68 // - if the item is the crate root of a dependency crate, return the name from the extern prelude
69 for (name, def_id) in &def_map.extern_prelude {
70 if item == ItemInNs::Types(*def_id) {
71 return Some(ModPath::from_simple_segments(PathKind::Plain, vec![name.clone()]));
72 }
73 }
74
75 // - if the item is in the prelude, return the name from there
76 if let Some(prelude_module) = def_map.prelude {
77 let prelude_def_map = db.crate_def_map(prelude_module.krate);
78 let prelude_scope: &crate::item_scope::ItemScope =
79 &prelude_def_map.modules[prelude_module.local_id].scope;
80 if let Some((name, vis)) = prelude_scope.name_of(item) {
81 if vis.is_visible_from(db, from) {
82 return Some(ModPath::from_simple_segments(PathKind::Plain, vec![name.clone()]));
83 }
84 }
85 }
86
87 // Recursive case:
88 // - if the item is an enum variant, refer to it via the enum
89 if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() {
90 if let Some(mut path) = find_path(db, ItemInNs::Types(variant.parent.into()), from) {
91 let data = db.enum_data(variant.parent);
92 path.segments.push(data.variants[variant.local_id].name.clone());
93 return Some(path);
94 }
95 // If this doesn't work, it seems we have no way of referring to the
96 // enum; that's very weird, but there might still be a reexport of the
97 // variant somewhere
98 }
99
100 // - otherwise, look for modules containing (reexporting) it and import it from one of those
101 let importable_locations = find_importable_locations(db, item, from);
102 let mut best_path = None;
103 let mut best_path_len = max_len;
104 for (module_id, name) in importable_locations {
105 let mut path = match find_path_inner(
106 db,
107 ItemInNs::Types(ModuleDefId::ModuleId(module_id)),
108 from,
109 best_path_len - 1,
110 ) {
111 None => continue,
112 Some(path) => path,
113 };
114 path.segments.push(name);
115 if path_len(&path) < best_path_len {
116 best_path_len = path_len(&path);
117 best_path = Some(path);
118 }
119 }
120 best_path
121}
122
123fn path_len(path: &ModPath) -> usize {
124 path.segments.len()
125 + match path.kind {
126 PathKind::Plain => 0,
127 PathKind::Super(i) => i as usize,
128 PathKind::Crate => 1,
129 PathKind::Abs => 0,
130 PathKind::DollarCrate(_) => 1,
131 }
132}
133
134fn find_importable_locations(
135 db: &impl DefDatabase,
136 item: ItemInNs,
137 from: ModuleId,
138) -> Vec<(ModuleId, Name)> {
139 let crate_graph = db.crate_graph();
140 let mut result = Vec::new();
141 // We only look in the crate from which we are importing, and the direct
142 // dependencies. We cannot refer to names from transitive dependencies
143 // directly (only through reexports in direct dependencies).
144 for krate in Some(from.krate)
145 .into_iter()
146 .chain(crate_graph.dependencies(from.krate).map(|dep| dep.crate_id))
147 {
148 result.extend(
149 importable_locations_in_crate(db, item, krate)
150 .iter()
151 .filter(|(_, _, vis)| vis.is_visible_from(db, from))
152 .map(|(m, n, _)| (*m, n.clone())),
153 );
154 }
155 result
156}
157
158/// Collects all locations from which we might import the item in a particular
159/// crate. These include the original definition of the item, and any
160/// non-private `use`s.
161///
162/// Note that the crate doesn't need to be the one in which the item is defined;
163/// it might be re-exported in other crates.
164fn importable_locations_in_crate(
165 db: &impl DefDatabase,
166 item: ItemInNs,
167 krate: CrateId,
168) -> Vec<(ModuleId, Name, Visibility)> {
169 let def_map = db.crate_def_map(krate);
170 let mut result = Vec::new();
171 for (local_id, data) in def_map.modules.iter() {
172 if let Some((name, vis)) = data.scope.name_of(item) {
173 let is_private = if let Visibility::Module(private_to) = vis {
174 private_to.local_id == local_id
175 } else {
176 false
177 };
178 let is_original_def = if let Some(module_def_id) = item.as_module_def_id() {
179 data.scope.declarations().any(|it| it == module_def_id)
180 } else {
181 false
182 };
183 if is_private && !is_original_def {
184 // Ignore private imports. these could be used if we are
185 // in a submodule of this module, but that's usually not
186 // what the user wants; and if this module can import
187 // the item and we're a submodule of it, so can we.
188 // Also this keeps the cached data smaller.
189 continue;
190 }
191 result.push((ModuleId { krate, local_id }, name.clone(), vis));
192 }
193 }
194 result
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::test_db::TestDB;
201 use hir_expand::hygiene::Hygiene;
202 use ra_db::fixture::WithFixture;
203 use ra_syntax::ast::AstNode;
204
205 /// `code` needs to contain a cursor marker; checks that `find_path` for the
206 /// item the `path` refers to returns that same path when called from the
207 /// module the cursor is in.
208 fn check_found_path(code: &str, path: &str) {
209 let (db, pos) = TestDB::with_position(code);
210 let module = db.module_for_file(pos.file_id);
211 let parsed_path_file = ra_syntax::SourceFile::parse(&format!("use {};", path));
212 let ast_path = parsed_path_file
213 .syntax_node()
214 .descendants()
215 .find_map(ra_syntax::ast::Path::cast)
216 .unwrap();
217 let mod_path = ModPath::from_src(ast_path, &Hygiene::new_unhygienic()).unwrap();
218
219 let crate_def_map = db.crate_def_map(module.krate);
220 let resolved = crate_def_map
221 .resolve_path(
222 &db,
223 module.local_id,
224 &mod_path,
225 crate::item_scope::BuiltinShadowMode::Module,
226 )
227 .0
228 .take_types()
229 .unwrap();
230
231 let found_path = find_path(&db, ItemInNs::Types(resolved), module);
232
233 assert_eq!(found_path, Some(mod_path));
234 }
235
236 #[test]
237 fn same_module() {
238 let code = r#"
239 //- /main.rs
240 struct S;
241 <|>
242 "#;
243 check_found_path(code, "S");
244 }
245
246 #[test]
247 fn enum_variant() {
248 let code = r#"
249 //- /main.rs
250 enum E { A }
251 <|>
252 "#;
253 check_found_path(code, "E::A");
254 }
255
256 #[test]
257 fn sub_module() {
258 let code = r#"
259 //- /main.rs
260 mod foo {
261 pub struct S;
262 }
263 <|>
264 "#;
265 check_found_path(code, "foo::S");
266 }
267
268 #[test]
269 fn super_module() {
270 let code = r#"
271 //- /main.rs
272 mod foo;
273 //- /foo.rs
274 mod bar;
275 struct S;
276 //- /foo/bar.rs
277 <|>
278 "#;
279 check_found_path(code, "super::S");
280 }
281
282 #[test]
283 fn self_module() {
284 let code = r#"
285 //- /main.rs
286 mod foo;
287 //- /foo.rs
288 <|>
289 "#;
290 check_found_path(code, "self");
291 }
292
293 #[test]
294 fn crate_root() {
295 let code = r#"
296 //- /main.rs
297 mod foo;
298 //- /foo.rs
299 <|>
300 "#;
301 check_found_path(code, "crate");
302 }
303
304 #[test]
305 fn same_crate() {
306 let code = r#"
307 //- /main.rs
308 mod foo;
309 struct S;
310 //- /foo.rs
311 <|>
312 "#;
313 check_found_path(code, "crate::S");
314 }
315
316 #[test]
317 fn different_crate() {
318 let code = r#"
319 //- /main.rs crate:main deps:std
320 <|>
321 //- /std.rs crate:std
322 pub struct S;
323 "#;
324 check_found_path(code, "std::S");
325 }
326
327 #[test]
328 fn different_crate_renamed() {
329 let code = r#"
330 //- /main.rs crate:main deps:std
331 extern crate std as std_renamed;
332 <|>
333 //- /std.rs crate:std
334 pub struct S;
335 "#;
336 check_found_path(code, "std_renamed::S");
337 }
338
339 #[test]
340 fn same_crate_reexport() {
341 let code = r#"
342 //- /main.rs
343 mod bar {
344 mod foo { pub(super) struct S; }
345 pub(crate) use foo::*;
346 }
347 <|>
348 "#;
349 check_found_path(code, "bar::S");
350 }
351
352 #[test]
353 fn same_crate_reexport_rename() {
354 let code = r#"
355 //- /main.rs
356 mod bar {
357 mod foo { pub(super) struct S; }
358 pub(crate) use foo::S as U;
359 }
360 <|>
361 "#;
362 check_found_path(code, "bar::U");
363 }
364
365 #[test]
366 fn different_crate_reexport() {
367 let code = r#"
368 //- /main.rs crate:main deps:std
369 <|>
370 //- /std.rs crate:std deps:core
371 pub use core::S;
372 //- /core.rs crate:core
373 pub struct S;
374 "#;
375 check_found_path(code, "std::S");
376 }
377
378 #[test]
379 fn prelude() {
380 let code = r#"
381 //- /main.rs crate:main deps:std
382 <|>
383 //- /std.rs crate:std
384 pub mod prelude { pub struct S; }
385 #[prelude_import]
386 pub use prelude::*;
387 "#;
388 check_found_path(code, "S");
389 }
390
391 #[test]
392 fn enum_variant_from_prelude() {
393 let code = r#"
394 //- /main.rs crate:main deps:std
395 <|>
396 //- /std.rs crate:std
397 pub mod prelude {
398 pub enum Option<T> { Some(T), None }
399 pub use Option::*;
400 }
401 #[prelude_import]
402 pub use prelude::*;
403 "#;
404 check_found_path(code, "None");
405 check_found_path(code, "Some");
406 }
407
408 #[test]
409 fn shortest_path() {
410 let code = r#"
411 //- /main.rs
412 pub mod foo;
413 pub mod baz;
414 struct S;
415 <|>
416 //- /foo.rs
417 pub mod bar { pub struct S; }
418 //- /baz.rs
419 pub use crate::foo::bar::S;
420 "#;
421 check_found_path(code, "baz::S");
422 }
423
424 #[test]
425 fn discount_private_imports() {
426 let code = r#"
427 //- /main.rs
428 mod foo;
429 pub mod bar { pub struct S; }
430 use bar::S;
431 //- /foo.rs
432 <|>
433 "#;
434 // crate::S would be shorter, but using private imports seems wrong
435 check_found_path(code, "crate::bar::S");
436 }
437
438 #[test]
439 fn import_cycle() {
440 let code = r#"
441 //- /main.rs
442 pub mod foo;
443 pub mod bar;
444 pub mod baz;
445 //- /bar.rs
446 <|>
447 //- /foo.rs
448 pub use super::baz;
449 pub struct S;
450 //- /baz.rs
451 pub use super::foo;
452 "#;
453 check_found_path(code, "crate::foo::S");
454 }
455}
diff --git a/crates/ra_hir_def/src/item_scope.rs b/crates/ra_hir_def/src/item_scope.rs
index fe7bb9779..d74a1cef2 100644
--- a/crates/ra_hir_def/src/item_scope.rs
+++ b/crates/ra_hir_def/src/item_scope.rs
@@ -104,6 +104,15 @@ impl ItemScope {
104 } 104 }
105 } 105 }
106 106
107 pub(crate) fn name_of(&self, item: ItemInNs) -> Option<(&Name, Visibility)> {
108 for (name, per_ns) in &self.visible {
109 if let Some(vis) = item.match_with(*per_ns) {
110 return Some((name, vis));
111 }
112 }
113 None
114 }
115
107 pub(crate) fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a { 116 pub(crate) fn traits<'a>(&'a self) -> impl Iterator<Item = TraitId> + 'a {
108 self.visible.values().filter_map(|def| match def.take_types() { 117 self.visible.values().filter_map(|def| match def.take_types() {
109 Some(ModuleDefId::TraitId(t)) => Some(t), 118 Some(ModuleDefId::TraitId(t)) => Some(t),
@@ -173,3 +182,33 @@ impl PerNs {
173 } 182 }
174 } 183 }
175} 184}
185
186#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
187pub enum ItemInNs {
188 Types(ModuleDefId),
189 Values(ModuleDefId),
190 Macros(MacroDefId),
191}
192
193impl ItemInNs {
194 fn match_with(self, per_ns: PerNs) -> Option<Visibility> {
195 match self {
196 ItemInNs::Types(def) => {
197 per_ns.types.filter(|(other_def, _)| *other_def == def).map(|(_, vis)| vis)
198 }
199 ItemInNs::Values(def) => {
200 per_ns.values.filter(|(other_def, _)| *other_def == def).map(|(_, vis)| vis)
201 }
202 ItemInNs::Macros(def) => {
203 per_ns.macros.filter(|(other_def, _)| *other_def == def).map(|(_, vis)| vis)
204 }
205 }
206 }
207
208 pub fn as_module_def_id(self) -> Option<ModuleDefId> {
209 match self {
210 ItemInNs::Types(id) | ItemInNs::Values(id) => Some(id),
211 ItemInNs::Macros(_) => None,
212 }
213 }
214}
diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs
index 61f044ecf..ebc12e891 100644
--- a/crates/ra_hir_def/src/lib.rs
+++ b/crates/ra_hir_def/src/lib.rs
@@ -37,6 +37,7 @@ pub mod src;
37pub mod child_by_source; 37pub mod child_by_source;
38 38
39pub mod visibility; 39pub mod visibility;
40pub mod find_path;
40 41
41#[cfg(test)] 42#[cfg(test)]
42mod test_db; 43mod test_db;
diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs
index 82cfa67a9..9f93a5424 100644
--- a/crates/ra_hir_def/src/path.rs
+++ b/crates/ra_hir_def/src/path.rs
@@ -1,7 +1,11 @@
1//! A desugared representation of paths like `crate::foo` or `<Type as Trait>::bar`. 1//! A desugared representation of paths like `crate::foo` or `<Type as Trait>::bar`.
2mod lower; 2mod lower;
3 3
4use std::{iter, sync::Arc}; 4use std::{
5 fmt::{self, Display},
6 iter,
7 sync::Arc,
8};
5 9
6use hir_expand::{ 10use hir_expand::{
7 hygiene::Hygiene, 11 hygiene::Hygiene,
@@ -248,6 +252,42 @@ impl From<Name> for ModPath {
248 } 252 }
249} 253}
250 254
255impl Display for ModPath {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 let mut first_segment = true;
258 let mut add_segment = |s| {
259 if !first_segment {
260 f.write_str("::")?;
261 }
262 first_segment = false;
263 f.write_str(s)?;
264 Ok(())
265 };
266 match self.kind {
267 PathKind::Plain => {}
268 PathKind::Super(n) => {
269 if n == 0 {
270 add_segment("self")?;
271 }
272 for _ in 0..n {
273 add_segment("super")?;
274 }
275 }
276 PathKind::Crate => add_segment("crate")?,
277 PathKind::Abs => add_segment("")?,
278 PathKind::DollarCrate(_) => add_segment("$crate")?,
279 }
280 for segment in &self.segments {
281 if !first_segment {
282 f.write_str("::")?;
283 }
284 first_segment = false;
285 write!(f, "{}", segment)?;
286 }
287 Ok(())
288 }
289}
290
251pub use hir_expand::name as __name; 291pub use hir_expand::name as __name;
252 292
253#[macro_export] 293#[macro_export]
diff --git a/crates/ra_hir_def/src/resolver.rs b/crates/ra_hir_def/src/resolver.rs
index 5d16dd087..f7bac5801 100644
--- a/crates/ra_hir_def/src/resolver.rs
+++ b/crates/ra_hir_def/src/resolver.rs
@@ -128,7 +128,7 @@ impl Resolver {
128 path: &ModPath, 128 path: &ModPath,
129 shadow: BuiltinShadowMode, 129 shadow: BuiltinShadowMode,
130 ) -> PerNs { 130 ) -> PerNs {
131 let (item_map, module) = match self.module() { 131 let (item_map, module) = match self.module_scope() {
132 Some(it) => it, 132 Some(it) => it,
133 None => return PerNs::none(), 133 None => return PerNs::none(),
134 }; 134 };
@@ -239,7 +239,7 @@ impl Resolver {
239 ) -> Option<Visibility> { 239 ) -> Option<Visibility> {
240 match visibility { 240 match visibility {
241 RawVisibility::Module(_) => { 241 RawVisibility::Module(_) => {
242 let (item_map, module) = match self.module() { 242 let (item_map, module) = match self.module_scope() {
243 Some(it) => it, 243 Some(it) => it,
244 None => return None, 244 None => return None,
245 }; 245 };
@@ -379,7 +379,7 @@ impl Resolver {
379 db: &impl DefDatabase, 379 db: &impl DefDatabase,
380 path: &ModPath, 380 path: &ModPath,
381 ) -> Option<MacroDefId> { 381 ) -> Option<MacroDefId> {
382 let (item_map, module) = self.module()?; 382 let (item_map, module) = self.module_scope()?;
383 item_map.resolve_path(db, module, &path, BuiltinShadowMode::Other).0.take_macros() 383 item_map.resolve_path(db, module, &path, BuiltinShadowMode::Other).0.take_macros()
384 } 384 }
385 385
@@ -403,7 +403,7 @@ impl Resolver {
403 traits 403 traits
404 } 404 }
405 405
406 fn module(&self) -> Option<(&CrateDefMap, LocalModuleId)> { 406 fn module_scope(&self) -> Option<(&CrateDefMap, LocalModuleId)> {
407 self.scopes.iter().rev().find_map(|scope| match scope { 407 self.scopes.iter().rev().find_map(|scope| match scope {
408 Scope::ModuleScope(m) => Some((&*m.crate_def_map, m.module_id)), 408 Scope::ModuleScope(m) => Some((&*m.crate_def_map, m.module_id)),
409 409
@@ -411,8 +411,13 @@ impl Resolver {
411 }) 411 })
412 } 412 }
413 413
414 pub fn module(&self) -> Option<ModuleId> {
415 let (def_map, local_id) = self.module_scope()?;
416 Some(ModuleId { krate: def_map.krate, local_id })
417 }
418
414 pub fn krate(&self) -> Option<CrateId> { 419 pub fn krate(&self) -> Option<CrateId> {
415 self.module().map(|t| t.0.krate) 420 self.module_scope().map(|t| t.0.krate)
416 } 421 }
417 422
418 pub fn where_predicates_in_scope<'a>( 423 pub fn where_predicates_in_scope<'a>(
diff --git a/crates/ra_hir_def/src/test_db.rs b/crates/ra_hir_def/src/test_db.rs
index 54e3a84bd..1568820e9 100644
--- a/crates/ra_hir_def/src/test_db.rs
+++ b/crates/ra_hir_def/src/test_db.rs
@@ -5,6 +5,7 @@ use std::{
5 sync::{Arc, Mutex}, 5 sync::{Arc, Mutex},
6}; 6};
7 7
8use crate::db::DefDatabase;
8use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; 9use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath};
9 10
10#[salsa::database( 11#[salsa::database(
@@ -54,6 +55,18 @@ impl FileLoader for TestDB {
54} 55}
55 56
56impl TestDB { 57impl TestDB {
58 pub fn module_for_file(&self, file_id: FileId) -> crate::ModuleId {
59 for &krate in self.relevant_crates(file_id).iter() {
60 let crate_def_map = self.crate_def_map(krate);
61 for (local_id, data) in crate_def_map.modules.iter() {
62 if data.origin.file_id() == Some(file_id) {
63 return crate::ModuleId { krate, local_id };
64 }
65 }
66 }
67 panic!("Can't find module for file")
68 }
69
57 pub fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event<TestDB>> { 70 pub fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event<TestDB>> {
58 *self.events.lock().unwrap() = Some(Vec::new()); 71 *self.events.lock().unwrap() = Some(Vec::new());
59 f(); 72 f();