]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! An algorithm to find a path to refer to a certain item. |
2 | ||
2b03887a | 3 | use std::{cmp::Ordering, iter}; |
064997fb FG |
4 | |
5 | use hir_expand::name::{known, AsName, Name}; | |
6 | use rustc_hash::FxHashSet; | |
7 | ||
8 | use crate::{ | |
9 | db::DefDatabase, | |
10 | item_scope::ItemInNs, | |
11 | nameres::DefMap, | |
12 | path::{ModPath, PathKind}, | |
13 | visibility::Visibility, | |
14 | ModuleDefId, ModuleId, | |
15 | }; | |
16 | ||
17 | /// Find a path that can be used to refer to a certain item. This can depend on | |
18 | /// *from where* you're referring to the item, hence the `from` parameter. | |
2b03887a FG |
19 | pub fn find_path( |
20 | db: &dyn DefDatabase, | |
21 | item: ItemInNs, | |
22 | from: ModuleId, | |
23 | prefer_no_std: bool, | |
24 | ) -> Option<ModPath> { | |
064997fb | 25 | let _p = profile::span("find_path"); |
2b03887a | 26 | find_path_inner(db, item, from, None, prefer_no_std) |
064997fb FG |
27 | } |
28 | ||
29 | pub fn find_path_prefixed( | |
30 | db: &dyn DefDatabase, | |
31 | item: ItemInNs, | |
32 | from: ModuleId, | |
33 | prefix_kind: PrefixKind, | |
2b03887a | 34 | prefer_no_std: bool, |
064997fb FG |
35 | ) -> Option<ModPath> { |
36 | let _p = profile::span("find_path_prefixed"); | |
2b03887a | 37 | find_path_inner(db, item, from, Some(prefix_kind), prefer_no_std) |
064997fb FG |
38 | } |
39 | ||
40 | const MAX_PATH_LEN: usize = 15; | |
41 | ||
064997fb FG |
42 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
43 | pub enum PrefixKind { | |
44 | /// Causes paths to always start with either `self`, `super`, `crate` or a crate-name. | |
45 | /// This is the same as plain, just that paths will start with `self` iprepended f the path | |
46 | /// starts with an identifier that is not a crate. | |
47 | BySelf, | |
48 | /// Causes paths to ignore imports in the local module. | |
49 | Plain, | |
50 | /// Causes paths to start with `crate` where applicable, effectively forcing paths to be absolute. | |
51 | ByCrate, | |
52 | } | |
53 | ||
54 | impl PrefixKind { | |
55 | #[inline] | |
56 | fn prefix(self) -> PathKind { | |
57 | match self { | |
58 | PrefixKind::BySelf => PathKind::Super(0), | |
59 | PrefixKind::Plain => PathKind::Plain, | |
60 | PrefixKind::ByCrate => PathKind::Crate, | |
61 | } | |
62 | } | |
63 | ||
64 | #[inline] | |
65 | fn is_absolute(&self) -> bool { | |
66 | self == &PrefixKind::ByCrate | |
67 | } | |
68 | } | |
2b03887a | 69 | |
064997fb FG |
70 | /// Attempts to find a path to refer to the given `item` visible from the `from` ModuleId |
71 | fn find_path_inner( | |
72 | db: &dyn DefDatabase, | |
73 | item: ItemInNs, | |
74 | from: ModuleId, | |
75 | prefixed: Option<PrefixKind>, | |
2b03887a | 76 | prefer_no_std: bool, |
064997fb | 77 | ) -> Option<ModPath> { |
2b03887a FG |
78 | // - if the item is a builtin, it's in scope |
79 | if let ItemInNs::Types(ModuleDefId::BuiltinType(builtin)) = item { | |
80 | return Some(ModPath::from_segments(PathKind::Plain, Some(builtin.as_name()))); | |
81 | } | |
064997fb | 82 | |
064997fb | 83 | let def_map = from.def_map(db); |
2b03887a FG |
84 | let crate_root = def_map.crate_root(db); |
85 | // - if the item is a module, jump straight to module search | |
86 | if let ItemInNs::Types(ModuleDefId::ModuleId(module_id)) = item { | |
87 | let mut visited_modules = FxHashSet::default(); | |
88 | return find_path_for_module( | |
89 | db, | |
90 | &def_map, | |
91 | &mut visited_modules, | |
92 | crate_root, | |
93 | from, | |
94 | module_id, | |
95 | MAX_PATH_LEN, | |
96 | prefixed, | |
97 | prefer_no_std || db.crate_supports_no_std(crate_root.krate), | |
98 | ); | |
99 | } | |
100 | ||
101 | // - if the item is already in scope, return the name under which it is | |
102 | let scope_name = find_in_scope(db, &def_map, from, item); | |
103 | if prefixed.is_none() { | |
104 | if let Some(scope_name) = scope_name { | |
105 | return Some(ModPath::from_segments(PathKind::Plain, Some(scope_name))); | |
106 | } | |
107 | } | |
108 | ||
109 | // - if the item is in the prelude, return the name from there | |
f25598a0 | 110 | if let value @ Some(_) = find_in_prelude(db, &crate_root.def_map(db), &def_map, item, from) { |
2b03887a FG |
111 | return value; |
112 | } | |
113 | ||
114 | if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() { | |
115 | // - if the item is an enum variant, refer to it via the enum | |
116 | if let Some(mut path) = find_path_inner( | |
117 | db, | |
118 | ItemInNs::Types(variant.parent.into()), | |
119 | from, | |
120 | prefixed, | |
121 | prefer_no_std, | |
122 | ) { | |
123 | let data = db.enum_data(variant.parent); | |
124 | path.push_segment(data.variants[variant.local_id].name.clone()); | |
125 | return Some(path); | |
126 | } | |
127 | // If this doesn't work, it seems we have no way of referring to the | |
128 | // enum; that's very weird, but there might still be a reexport of the | |
129 | // variant somewhere | |
130 | } | |
131 | ||
132 | let mut visited_modules = FxHashSet::default(); | |
133 | ||
134 | calculate_best_path( | |
135 | db, | |
136 | &def_map, | |
137 | &mut visited_modules, | |
138 | crate_root, | |
139 | MAX_PATH_LEN, | |
140 | item, | |
141 | from, | |
142 | prefixed, | |
143 | prefer_no_std || db.crate_supports_no_std(crate_root.krate), | |
144 | scope_name, | |
145 | ) | |
064997fb FG |
146 | } |
147 | ||
2b03887a | 148 | fn find_path_for_module( |
064997fb FG |
149 | db: &dyn DefDatabase, |
150 | def_map: &DefMap, | |
2b03887a FG |
151 | visited_modules: &mut FxHashSet<ModuleId>, |
152 | crate_root: ModuleId, | |
064997fb | 153 | from: ModuleId, |
2b03887a | 154 | module_id: ModuleId, |
064997fb | 155 | max_len: usize, |
2b03887a FG |
156 | prefixed: Option<PrefixKind>, |
157 | prefer_no_std: bool, | |
064997fb FG |
158 | ) -> Option<ModPath> { |
159 | if max_len == 0 { | |
160 | return None; | |
161 | } | |
162 | ||
163 | // Base cases: | |
064997fb | 164 | // - if the item is already in scope, return the name under which it is |
2b03887a | 165 | let scope_name = find_in_scope(db, def_map, from, ItemInNs::Types(module_id.into())); |
064997fb FG |
166 | if prefixed.is_none() { |
167 | if let Some(scope_name) = scope_name { | |
168 | return Some(ModPath::from_segments(PathKind::Plain, Some(scope_name))); | |
169 | } | |
170 | } | |
171 | ||
064997fb | 172 | // - if the item is the crate root, return `crate` |
2b03887a | 173 | if module_id == crate_root { |
064997fb FG |
174 | return Some(ModPath::from_segments(PathKind::Crate, None)); |
175 | } | |
176 | ||
2b03887a | 177 | // - if relative paths are fine, check if we are searching for a parent |
064997fb | 178 | if prefixed.filter(PrefixKind::is_absolute).is_none() { |
f25598a0 | 179 | if let modpath @ Some(_) = find_self_super(def_map, module_id, from) { |
064997fb FG |
180 | return modpath; |
181 | } | |
182 | } | |
183 | ||
184 | // - if the item is the crate root of a dependency crate, return the name from the extern prelude | |
185 | let root_def_map = crate_root.def_map(db); | |
2b03887a FG |
186 | for (name, &def_id) in root_def_map.extern_prelude() { |
187 | if module_id == def_id { | |
188 | let name = scope_name.unwrap_or_else(|| name.clone()); | |
189 | ||
190 | let name_already_occupied_in_type_ns = def_map | |
191 | .with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| { | |
192 | def_map[local_id] | |
193 | .scope | |
194 | .type_(&name) | |
195 | .filter(|&(id, _)| id != ModuleDefId::ModuleId(def_id)) | |
196 | }) | |
197 | .is_some(); | |
198 | let kind = if name_already_occupied_in_type_ns { | |
199 | cov_mark::hit!(ambiguous_crate_start); | |
200 | PathKind::Abs | |
201 | } else { | |
202 | PathKind::Plain | |
203 | }; | |
204 | return Some(ModPath::from_segments(kind, Some(name))); | |
064997fb FG |
205 | } |
206 | } | |
207 | ||
f25598a0 FG |
208 | if let value @ Some(_) = |
209 | find_in_prelude(db, &root_def_map, &def_map, ItemInNs::Types(module_id.into()), from) | |
2b03887a FG |
210 | { |
211 | return value; | |
212 | } | |
213 | calculate_best_path( | |
214 | db, | |
215 | def_map, | |
216 | visited_modules, | |
217 | crate_root, | |
218 | max_len, | |
219 | ItemInNs::Types(module_id.into()), | |
220 | from, | |
221 | prefixed, | |
222 | prefer_no_std, | |
223 | scope_name, | |
224 | ) | |
225 | } | |
226 | ||
227 | fn find_in_scope( | |
228 | db: &dyn DefDatabase, | |
229 | def_map: &DefMap, | |
230 | from: ModuleId, | |
231 | item: ItemInNs, | |
232 | ) -> Option<Name> { | |
233 | def_map.with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| { | |
234 | def_map[local_id].scope.name_of(item).map(|(name, _)| name.clone()) | |
235 | }) | |
236 | } | |
237 | ||
f25598a0 FG |
238 | /// Returns single-segment path (i.e. without any prefix) if `item` is found in prelude and its |
239 | /// name doesn't clash in current scope. | |
2b03887a FG |
240 | fn find_in_prelude( |
241 | db: &dyn DefDatabase, | |
242 | root_def_map: &DefMap, | |
f25598a0 | 243 | local_def_map: &DefMap, |
2b03887a FG |
244 | item: ItemInNs, |
245 | from: ModuleId, | |
f25598a0 FG |
246 | ) -> Option<ModPath> { |
247 | let prelude_module = root_def_map.prelude()?; | |
248 | // Preludes in block DefMaps are ignored, only the crate DefMap is searched | |
249 | let prelude_def_map = prelude_module.def_map(db); | |
250 | let prelude_scope = &prelude_def_map[prelude_module.local_id].scope; | |
251 | let (name, vis) = prelude_scope.name_of(item)?; | |
252 | if !vis.is_visible_from(db, from) { | |
253 | return None; | |
254 | } | |
255 | ||
256 | // Check if the name is in current scope and it points to the same def. | |
257 | let found_and_same_def = | |
258 | local_def_map.with_ancestor_maps(db, from.local_id, &mut |def_map, local_id| { | |
259 | let per_ns = def_map[local_id].scope.get(name); | |
260 | let same_def = match item { | |
261 | ItemInNs::Types(it) => per_ns.take_types()? == it, | |
262 | ItemInNs::Values(it) => per_ns.take_values()? == it, | |
263 | ItemInNs::Macros(it) => per_ns.take_macros()? == it, | |
264 | }; | |
265 | Some(same_def) | |
266 | }); | |
267 | ||
268 | if found_and_same_def.unwrap_or(true) { | |
269 | Some(ModPath::from_segments(PathKind::Plain, Some(name.clone()))) | |
270 | } else { | |
271 | None | |
064997fb | 272 | } |
2b03887a | 273 | } |
064997fb | 274 | |
2b03887a FG |
275 | fn find_self_super(def_map: &DefMap, item: ModuleId, from: ModuleId) -> Option<ModPath> { |
276 | if item == from { | |
277 | // - if the item is the module we're in, use `self` | |
278 | Some(ModPath::from_segments(PathKind::Super(0), None)) | |
279 | } else if let Some(parent_id) = def_map[from.local_id].parent { | |
280 | // - if the item is the parent module, use `super` (this is not used recursively, since `super::super` is ugly) | |
281 | let parent_id = def_map.module_id(parent_id); | |
282 | if item == parent_id { | |
283 | Some(ModPath::from_segments(PathKind::Super(1), None)) | |
284 | } else { | |
285 | None | |
064997fb | 286 | } |
2b03887a FG |
287 | } else { |
288 | None | |
064997fb | 289 | } |
2b03887a | 290 | } |
064997fb | 291 | |
2b03887a FG |
292 | fn calculate_best_path( |
293 | db: &dyn DefDatabase, | |
294 | def_map: &DefMap, | |
295 | visited_modules: &mut FxHashSet<ModuleId>, | |
296 | crate_root: ModuleId, | |
297 | max_len: usize, | |
298 | item: ItemInNs, | |
299 | from: ModuleId, | |
300 | mut prefixed: Option<PrefixKind>, | |
301 | prefer_no_std: bool, | |
302 | scope_name: Option<Name>, | |
303 | ) -> Option<ModPath> { | |
304 | if max_len <= 1 { | |
305 | return None; | |
306 | } | |
064997fb | 307 | let mut best_path = None; |
2b03887a FG |
308 | // Recursive case: |
309 | // - otherwise, look for modules containing (reexporting) it and import it from one of those | |
064997fb | 310 | if item.krate(db) == Some(from.krate) { |
2b03887a | 311 | let mut best_path_len = max_len; |
064997fb FG |
312 | // Item was defined in the same crate that wants to import it. It cannot be found in any |
313 | // dependency in this case. | |
064997fb FG |
314 | for (module_id, name) in find_local_import_locations(db, item, from) { |
315 | if !visited_modules.insert(module_id) { | |
316 | cov_mark::hit!(recursive_imports); | |
317 | continue; | |
318 | } | |
2b03887a | 319 | if let Some(mut path) = find_path_for_module( |
064997fb FG |
320 | db, |
321 | def_map, | |
2b03887a FG |
322 | visited_modules, |
323 | crate_root, | |
064997fb | 324 | from, |
2b03887a | 325 | module_id, |
064997fb FG |
326 | best_path_len - 1, |
327 | prefixed, | |
2b03887a | 328 | prefer_no_std, |
064997fb FG |
329 | ) { |
330 | path.push_segment(name); | |
331 | ||
332 | let new_path = match best_path { | |
333 | Some(best_path) => select_best_path(best_path, path, prefer_no_std), | |
334 | None => path, | |
335 | }; | |
336 | best_path_len = new_path.len(); | |
337 | best_path = Some(new_path); | |
338 | } | |
339 | } | |
340 | } else { | |
341 | // Item was defined in some upstream crate. This means that it must be exported from one, | |
342 | // too (unless we can't name it at all). It could *also* be (re)exported by the same crate | |
343 | // that wants to import it here, but we always prefer to use the external path here. | |
344 | ||
345 | let crate_graph = db.crate_graph(); | |
346 | let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| { | |
347 | let import_map = db.import_map(dep.crate_id); | |
348 | import_map.import_info_for(item).and_then(|info| { | |
349 | // Determine best path for containing module and append last segment from `info`. | |
350 | // FIXME: we should guide this to look up the path locally, or from the same crate again? | |
2b03887a | 351 | let mut path = find_path_for_module( |
064997fb FG |
352 | db, |
353 | def_map, | |
2b03887a FG |
354 | visited_modules, |
355 | crate_root, | |
064997fb | 356 | from, |
2b03887a FG |
357 | info.container, |
358 | max_len - 1, | |
064997fb | 359 | prefixed, |
2b03887a | 360 | prefer_no_std, |
064997fb FG |
361 | )?; |
362 | cov_mark::hit!(partially_imported); | |
363 | path.push_segment(info.path.segments.last()?.clone()); | |
364 | Some(path) | |
365 | }) | |
366 | }); | |
367 | ||
368 | for path in extern_paths { | |
369 | let new_path = match best_path { | |
370 | Some(best_path) => select_best_path(best_path, path, prefer_no_std), | |
371 | None => path, | |
372 | }; | |
373 | best_path = Some(new_path); | |
374 | } | |
375 | } | |
2b03887a FG |
376 | if let Some(module) = item.module(db) { |
377 | if module.def_map(db).block_id().is_some() && prefixed.is_some() { | |
064997fb FG |
378 | cov_mark::hit!(prefixed_in_block_expression); |
379 | prefixed = Some(PrefixKind::Plain); | |
380 | } | |
381 | } | |
064997fb FG |
382 | match prefixed.map(PrefixKind::prefix) { |
383 | Some(prefix) => best_path.or_else(|| { | |
384 | scope_name.map(|scope_name| ModPath::from_segments(prefix, Some(scope_name))) | |
385 | }), | |
386 | None => best_path, | |
387 | } | |
388 | } | |
389 | ||
390 | fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) -> ModPath { | |
2b03887a FG |
391 | const STD_CRATES: [Name; 3] = [known::std, known::core, known::alloc]; |
392 | match (old_path.segments().first(), new_path.segments().first()) { | |
393 | (Some(old), Some(new)) if STD_CRATES.contains(old) && STD_CRATES.contains(new) => { | |
394 | let rank = match prefer_no_std { | |
395 | false => |name: &Name| match name { | |
396 | name if name == &known::core => 0, | |
397 | name if name == &known::alloc => 0, | |
398 | name if name == &known::std => 1, | |
399 | _ => unreachable!(), | |
400 | }, | |
401 | true => |name: &Name| match name { | |
402 | name if name == &known::core => 2, | |
403 | name if name == &known::alloc => 1, | |
404 | name if name == &known::std => 0, | |
405 | _ => unreachable!(), | |
406 | }, | |
407 | }; | |
408 | let nrank = rank(new); | |
409 | let orank = rank(old); | |
410 | match nrank.cmp(&orank) { | |
411 | Ordering::Less => old_path, | |
412 | Ordering::Equal => { | |
413 | if new_path.len() < old_path.len() { | |
414 | new_path | |
415 | } else { | |
416 | old_path | |
417 | } | |
418 | } | |
419 | Ordering::Greater => new_path, | |
420 | } | |
064997fb | 421 | } |
2b03887a FG |
422 | _ => { |
423 | if new_path.len() < old_path.len() { | |
424 | new_path | |
425 | } else { | |
426 | old_path | |
427 | } | |
064997fb | 428 | } |
064997fb FG |
429 | } |
430 | } | |
431 | ||
2b03887a | 432 | // FIXME: Remove allocations |
064997fb FG |
433 | /// Finds locations in `from.krate` from which `item` can be imported by `from`. |
434 | fn find_local_import_locations( | |
435 | db: &dyn DefDatabase, | |
436 | item: ItemInNs, | |
437 | from: ModuleId, | |
438 | ) -> Vec<(ModuleId, Name)> { | |
439 | let _p = profile::span("find_local_import_locations"); | |
440 | ||
441 | // `from` can import anything below `from` with visibility of at least `from`, and anything | |
442 | // above `from` with any visibility. That means we do not need to descend into private siblings | |
443 | // of `from` (and similar). | |
444 | ||
445 | let def_map = from.def_map(db); | |
446 | ||
447 | // Compute the initial worklist. We start with all direct child modules of `from` as well as all | |
448 | // of its (recursive) parent modules. | |
449 | let data = &def_map[from.local_id]; | |
450 | let mut worklist = | |
451 | data.children.values().map(|child| def_map.module_id(*child)).collect::<Vec<_>>(); | |
452 | // FIXME: do we need to traverse out of block expressions here? | |
453 | for ancestor in iter::successors(from.containing_module(db), |m| m.containing_module(db)) { | |
454 | worklist.push(ancestor); | |
455 | } | |
456 | ||
457 | let def_map = def_map.crate_root(db).def_map(db); | |
458 | ||
459 | let mut seen: FxHashSet<_> = FxHashSet::default(); | |
460 | ||
461 | let mut locations = Vec::new(); | |
462 | while let Some(module) = worklist.pop() { | |
463 | if !seen.insert(module) { | |
464 | continue; // already processed this module | |
465 | } | |
466 | ||
467 | let ext_def_map; | |
468 | let data = if module.krate == from.krate { | |
469 | if module.block.is_some() { | |
470 | // Re-query the block's DefMap | |
471 | ext_def_map = module.def_map(db); | |
472 | &ext_def_map[module.local_id] | |
473 | } else { | |
474 | // Reuse the root DefMap | |
475 | &def_map[module.local_id] | |
476 | } | |
477 | } else { | |
478 | // The crate might reexport a module defined in another crate. | |
479 | ext_def_map = module.def_map(db); | |
480 | &ext_def_map[module.local_id] | |
481 | }; | |
482 | ||
483 | if let Some((name, vis)) = data.scope.name_of(item) { | |
484 | if vis.is_visible_from(db, from) { | |
485 | let is_private = match vis { | |
486 | Visibility::Module(private_to) => private_to.local_id == module.local_id, | |
487 | Visibility::Public => false, | |
488 | }; | |
489 | let is_original_def = match item.as_module_def_id() { | |
490 | Some(module_def_id) => data.scope.declarations().any(|it| it == module_def_id), | |
491 | None => false, | |
492 | }; | |
493 | ||
494 | // Ignore private imports. these could be used if we are | |
495 | // in a submodule of this module, but that's usually not | |
496 | // what the user wants; and if this module can import | |
497 | // the item and we're a submodule of it, so can we. | |
498 | // Also this keeps the cached data smaller. | |
499 | if !is_private || is_original_def { | |
500 | locations.push((module, name.clone())); | |
501 | } | |
502 | } | |
503 | } | |
504 | ||
505 | // Descend into all modules visible from `from`. | |
506 | for (ty, vis) in data.scope.types() { | |
507 | if let ModuleDefId::ModuleId(module) = ty { | |
508 | if vis.is_visible_from(db, from) { | |
509 | worklist.push(module); | |
510 | } | |
511 | } | |
512 | } | |
513 | } | |
514 | ||
515 | locations | |
516 | } | |
517 | ||
518 | #[cfg(test)] | |
519 | mod tests { | |
520 | use base_db::fixture::WithFixture; | |
521 | use hir_expand::hygiene::Hygiene; | |
522 | use syntax::ast::AstNode; | |
523 | ||
524 | use crate::test_db::TestDB; | |
525 | ||
526 | use super::*; | |
527 | ||
528 | /// `code` needs to contain a cursor marker; checks that `find_path` for the | |
529 | /// item the `path` refers to returns that same path when called from the | |
530 | /// module the cursor is in. | |
531 | fn check_found_path_(ra_fixture: &str, path: &str, prefix_kind: Option<PrefixKind>) { | |
532 | let (db, pos) = TestDB::with_position(ra_fixture); | |
533 | let module = db.module_at_position(pos); | |
f25598a0 | 534 | let parsed_path_file = syntax::SourceFile::parse(&format!("use {path};")); |
064997fb FG |
535 | let ast_path = |
536 | parsed_path_file.syntax_node().descendants().find_map(syntax::ast::Path::cast).unwrap(); | |
537 | let mod_path = ModPath::from_src(&db, ast_path, &Hygiene::new_unhygienic()).unwrap(); | |
538 | ||
539 | let def_map = module.def_map(&db); | |
540 | let resolved = def_map | |
541 | .resolve_path( | |
542 | &db, | |
543 | module.local_id, | |
544 | &mod_path, | |
545 | crate::item_scope::BuiltinShadowMode::Module, | |
546 | ) | |
547 | .0 | |
548 | .take_types() | |
549 | .unwrap(); | |
550 | ||
2b03887a FG |
551 | let found_path = |
552 | find_path_inner(&db, ItemInNs::Types(resolved), module, prefix_kind, false); | |
f25598a0 | 553 | assert_eq!(found_path, Some(mod_path), "{prefix_kind:?}"); |
064997fb FG |
554 | } |
555 | ||
556 | fn check_found_path( | |
557 | ra_fixture: &str, | |
558 | unprefixed: &str, | |
559 | prefixed: &str, | |
560 | absolute: &str, | |
561 | self_prefixed: &str, | |
562 | ) { | |
563 | check_found_path_(ra_fixture, unprefixed, None); | |
564 | check_found_path_(ra_fixture, prefixed, Some(PrefixKind::Plain)); | |
565 | check_found_path_(ra_fixture, absolute, Some(PrefixKind::ByCrate)); | |
566 | check_found_path_(ra_fixture, self_prefixed, Some(PrefixKind::BySelf)); | |
567 | } | |
568 | ||
569 | #[test] | |
570 | fn same_module() { | |
571 | check_found_path( | |
572 | r#" | |
573 | struct S; | |
574 | $0 | |
575 | "#, | |
576 | "S", | |
577 | "S", | |
578 | "crate::S", | |
579 | "self::S", | |
580 | ); | |
581 | } | |
582 | ||
583 | #[test] | |
584 | fn enum_variant() { | |
585 | check_found_path( | |
586 | r#" | |
587 | enum E { A } | |
588 | $0 | |
589 | "#, | |
590 | "E::A", | |
591 | "E::A", | |
2b03887a FG |
592 | "crate::E::A", |
593 | "self::E::A", | |
064997fb FG |
594 | ); |
595 | } | |
596 | ||
597 | #[test] | |
598 | fn sub_module() { | |
599 | check_found_path( | |
600 | r#" | |
601 | mod foo { | |
602 | pub struct S; | |
603 | } | |
604 | $0 | |
605 | "#, | |
606 | "foo::S", | |
607 | "foo::S", | |
608 | "crate::foo::S", | |
609 | "self::foo::S", | |
610 | ); | |
611 | } | |
612 | ||
613 | #[test] | |
614 | fn super_module() { | |
615 | check_found_path( | |
616 | r#" | |
617 | //- /main.rs | |
618 | mod foo; | |
619 | //- /foo.rs | |
620 | mod bar; | |
621 | struct S; | |
622 | //- /foo/bar.rs | |
623 | $0 | |
624 | "#, | |
625 | "super::S", | |
626 | "super::S", | |
627 | "crate::foo::S", | |
628 | "super::S", | |
629 | ); | |
630 | } | |
631 | ||
632 | #[test] | |
633 | fn self_module() { | |
634 | check_found_path( | |
635 | r#" | |
636 | //- /main.rs | |
637 | mod foo; | |
638 | //- /foo.rs | |
639 | $0 | |
640 | "#, | |
641 | "self", | |
642 | "self", | |
643 | "crate::foo", | |
644 | "self", | |
645 | ); | |
646 | } | |
647 | ||
648 | #[test] | |
649 | fn crate_root() { | |
650 | check_found_path( | |
651 | r#" | |
652 | //- /main.rs | |
653 | mod foo; | |
654 | //- /foo.rs | |
655 | $0 | |
656 | "#, | |
657 | "crate", | |
658 | "crate", | |
659 | "crate", | |
660 | "crate", | |
661 | ); | |
662 | } | |
663 | ||
664 | #[test] | |
665 | fn same_crate() { | |
666 | check_found_path( | |
667 | r#" | |
668 | //- /main.rs | |
669 | mod foo; | |
670 | struct S; | |
671 | //- /foo.rs | |
672 | $0 | |
673 | "#, | |
674 | "crate::S", | |
675 | "crate::S", | |
676 | "crate::S", | |
677 | "crate::S", | |
678 | ); | |
679 | } | |
680 | ||
681 | #[test] | |
682 | fn different_crate() { | |
683 | check_found_path( | |
684 | r#" | |
685 | //- /main.rs crate:main deps:std | |
686 | $0 | |
687 | //- /std.rs crate:std | |
688 | pub struct S; | |
689 | "#, | |
690 | "std::S", | |
691 | "std::S", | |
692 | "std::S", | |
693 | "std::S", | |
694 | ); | |
695 | } | |
696 | ||
697 | #[test] | |
698 | fn different_crate_renamed() { | |
699 | check_found_path( | |
700 | r#" | |
701 | //- /main.rs crate:main deps:std | |
702 | extern crate std as std_renamed; | |
703 | $0 | |
704 | //- /std.rs crate:std | |
705 | pub struct S; | |
706 | "#, | |
707 | "std_renamed::S", | |
708 | "std_renamed::S", | |
709 | "std_renamed::S", | |
710 | "std_renamed::S", | |
711 | ); | |
712 | } | |
713 | ||
714 | #[test] | |
715 | fn partially_imported() { | |
716 | cov_mark::check!(partially_imported); | |
717 | // Tests that short paths are used even for external items, when parts of the path are | |
718 | // already in scope. | |
719 | check_found_path( | |
720 | r#" | |
721 | //- /main.rs crate:main deps:syntax | |
722 | ||
723 | use syntax::ast; | |
724 | $0 | |
725 | ||
726 | //- /lib.rs crate:syntax | |
727 | pub mod ast { | |
728 | pub enum ModuleItem { | |
729 | A, B, C, | |
730 | } | |
731 | } | |
732 | "#, | |
733 | "ast::ModuleItem", | |
734 | "syntax::ast::ModuleItem", | |
735 | "syntax::ast::ModuleItem", | |
736 | "syntax::ast::ModuleItem", | |
737 | ); | |
738 | ||
739 | check_found_path( | |
740 | r#" | |
741 | //- /main.rs crate:main deps:syntax | |
742 | $0 | |
743 | ||
744 | //- /lib.rs crate:syntax | |
745 | pub mod ast { | |
746 | pub enum ModuleItem { | |
747 | A, B, C, | |
748 | } | |
749 | } | |
750 | "#, | |
751 | "syntax::ast::ModuleItem", | |
752 | "syntax::ast::ModuleItem", | |
753 | "syntax::ast::ModuleItem", | |
754 | "syntax::ast::ModuleItem", | |
755 | ); | |
756 | } | |
757 | ||
758 | #[test] | |
759 | fn same_crate_reexport() { | |
760 | check_found_path( | |
761 | r#" | |
762 | mod bar { | |
763 | mod foo { pub(super) struct S; } | |
764 | pub(crate) use foo::*; | |
765 | } | |
766 | $0 | |
767 | "#, | |
768 | "bar::S", | |
769 | "bar::S", | |
770 | "crate::bar::S", | |
771 | "self::bar::S", | |
772 | ); | |
773 | } | |
774 | ||
775 | #[test] | |
776 | fn same_crate_reexport_rename() { | |
777 | check_found_path( | |
778 | r#" | |
779 | mod bar { | |
780 | mod foo { pub(super) struct S; } | |
781 | pub(crate) use foo::S as U; | |
782 | } | |
783 | $0 | |
784 | "#, | |
785 | "bar::U", | |
786 | "bar::U", | |
787 | "crate::bar::U", | |
788 | "self::bar::U", | |
789 | ); | |
790 | } | |
791 | ||
792 | #[test] | |
793 | fn different_crate_reexport() { | |
794 | check_found_path( | |
795 | r#" | |
796 | //- /main.rs crate:main deps:std | |
797 | $0 | |
798 | //- /std.rs crate:std deps:core | |
799 | pub use core::S; | |
800 | //- /core.rs crate:core | |
801 | pub struct S; | |
802 | "#, | |
803 | "std::S", | |
804 | "std::S", | |
805 | "std::S", | |
806 | "std::S", | |
807 | ); | |
808 | } | |
809 | ||
810 | #[test] | |
811 | fn prelude() { | |
812 | check_found_path( | |
813 | r#" | |
814 | //- /main.rs crate:main deps:std | |
815 | $0 | |
816 | //- /std.rs crate:std | |
817 | pub mod prelude { | |
818 | pub mod rust_2018 { | |
819 | pub struct S; | |
820 | } | |
821 | } | |
822 | "#, | |
823 | "S", | |
824 | "S", | |
825 | "S", | |
826 | "S", | |
827 | ); | |
828 | } | |
829 | ||
f25598a0 FG |
830 | #[test] |
831 | fn shadowed_prelude() { | |
832 | check_found_path( | |
833 | r#" | |
834 | //- /main.rs crate:main deps:std | |
835 | struct S; | |
836 | $0 | |
837 | //- /std.rs crate:std | |
838 | pub mod prelude { | |
839 | pub mod rust_2018 { | |
840 | pub struct S; | |
841 | } | |
842 | } | |
843 | "#, | |
844 | "std::prelude::rust_2018::S", | |
845 | "std::prelude::rust_2018::S", | |
846 | "std::prelude::rust_2018::S", | |
847 | "std::prelude::rust_2018::S", | |
848 | ); | |
849 | } | |
850 | ||
851 | #[test] | |
852 | fn imported_prelude() { | |
853 | check_found_path( | |
854 | r#" | |
855 | //- /main.rs crate:main deps:std | |
856 | use S; | |
857 | $0 | |
858 | //- /std.rs crate:std | |
859 | pub mod prelude { | |
860 | pub mod rust_2018 { | |
861 | pub struct S; | |
862 | } | |
863 | } | |
864 | "#, | |
865 | "S", | |
866 | "S", | |
867 | "S", | |
868 | "S", | |
869 | ); | |
870 | } | |
871 | ||
064997fb FG |
872 | #[test] |
873 | fn enum_variant_from_prelude() { | |
874 | let code = r#" | |
875 | //- /main.rs crate:main deps:std | |
876 | $0 | |
877 | //- /std.rs crate:std | |
878 | pub mod prelude { | |
879 | pub mod rust_2018 { | |
880 | pub enum Option<T> { Some(T), None } | |
881 | pub use Option::*; | |
882 | } | |
883 | } | |
884 | "#; | |
885 | check_found_path(code, "None", "None", "None", "None"); | |
886 | check_found_path(code, "Some", "Some", "Some", "Some"); | |
887 | } | |
888 | ||
889 | #[test] | |
890 | fn shortest_path() { | |
891 | check_found_path( | |
892 | r#" | |
893 | //- /main.rs | |
894 | pub mod foo; | |
895 | pub mod baz; | |
896 | struct S; | |
897 | $0 | |
898 | //- /foo.rs | |
899 | pub mod bar { pub struct S; } | |
900 | //- /baz.rs | |
901 | pub use crate::foo::bar::S; | |
902 | "#, | |
903 | "baz::S", | |
904 | "baz::S", | |
905 | "crate::baz::S", | |
906 | "self::baz::S", | |
907 | ); | |
908 | } | |
909 | ||
910 | #[test] | |
911 | fn discount_private_imports() { | |
912 | check_found_path( | |
913 | r#" | |
914 | //- /main.rs | |
915 | mod foo; | |
916 | pub mod bar { pub struct S; } | |
917 | use bar::S; | |
918 | //- /foo.rs | |
919 | $0 | |
920 | "#, | |
921 | // crate::S would be shorter, but using private imports seems wrong | |
922 | "crate::bar::S", | |
923 | "crate::bar::S", | |
924 | "crate::bar::S", | |
925 | "crate::bar::S", | |
926 | ); | |
927 | } | |
928 | ||
929 | #[test] | |
930 | fn import_cycle() { | |
931 | check_found_path( | |
932 | r#" | |
933 | //- /main.rs | |
934 | pub mod foo; | |
935 | pub mod bar; | |
936 | pub mod baz; | |
937 | //- /bar.rs | |
938 | $0 | |
939 | //- /foo.rs | |
940 | pub use super::baz; | |
941 | pub struct S; | |
942 | //- /baz.rs | |
943 | pub use super::foo; | |
944 | "#, | |
945 | "crate::foo::S", | |
946 | "crate::foo::S", | |
947 | "crate::foo::S", | |
948 | "crate::foo::S", | |
949 | ); | |
950 | } | |
951 | ||
952 | #[test] | |
953 | fn prefer_std_paths_over_alloc() { | |
064997fb FG |
954 | check_found_path( |
955 | r#" | |
956 | //- /main.rs crate:main deps:alloc,std | |
957 | $0 | |
958 | ||
959 | //- /std.rs crate:std deps:alloc | |
960 | pub mod sync { | |
961 | pub use alloc::sync::Arc; | |
962 | } | |
963 | ||
964 | //- /zzz.rs crate:alloc | |
965 | pub mod sync { | |
966 | pub struct Arc; | |
967 | } | |
968 | "#, | |
969 | "std::sync::Arc", | |
970 | "std::sync::Arc", | |
971 | "std::sync::Arc", | |
972 | "std::sync::Arc", | |
973 | ); | |
974 | } | |
975 | ||
976 | #[test] | |
977 | fn prefer_core_paths_over_std() { | |
064997fb FG |
978 | check_found_path( |
979 | r#" | |
980 | //- /main.rs crate:main deps:core,std | |
981 | #![no_std] | |
982 | ||
983 | $0 | |
984 | ||
985 | //- /std.rs crate:std deps:core | |
986 | ||
987 | pub mod fmt { | |
988 | pub use core::fmt::Error; | |
989 | } | |
990 | ||
991 | //- /zzz.rs crate:core | |
992 | ||
993 | pub mod fmt { | |
994 | pub struct Error; | |
995 | } | |
996 | "#, | |
997 | "core::fmt::Error", | |
998 | "core::fmt::Error", | |
999 | "core::fmt::Error", | |
1000 | "core::fmt::Error", | |
1001 | ); | |
1002 | ||
1003 | // Should also work (on a best-effort basis) if `no_std` is conditional. | |
1004 | check_found_path( | |
1005 | r#" | |
1006 | //- /main.rs crate:main deps:core,std | |
1007 | #![cfg_attr(not(test), no_std)] | |
1008 | ||
1009 | $0 | |
1010 | ||
1011 | //- /std.rs crate:std deps:core | |
1012 | ||
1013 | pub mod fmt { | |
1014 | pub use core::fmt::Error; | |
1015 | } | |
1016 | ||
1017 | //- /zzz.rs crate:core | |
1018 | ||
1019 | pub mod fmt { | |
1020 | pub struct Error; | |
1021 | } | |
1022 | "#, | |
1023 | "core::fmt::Error", | |
1024 | "core::fmt::Error", | |
1025 | "core::fmt::Error", | |
1026 | "core::fmt::Error", | |
1027 | ); | |
1028 | } | |
1029 | ||
1030 | #[test] | |
1031 | fn prefer_alloc_paths_over_std() { | |
1032 | check_found_path( | |
1033 | r#" | |
1034 | //- /main.rs crate:main deps:alloc,std | |
1035 | #![no_std] | |
1036 | ||
1037 | $0 | |
1038 | ||
1039 | //- /std.rs crate:std deps:alloc | |
1040 | ||
1041 | pub mod sync { | |
1042 | pub use alloc::sync::Arc; | |
1043 | } | |
1044 | ||
1045 | //- /zzz.rs crate:alloc | |
1046 | ||
1047 | pub mod sync { | |
1048 | pub struct Arc; | |
1049 | } | |
1050 | "#, | |
1051 | "alloc::sync::Arc", | |
1052 | "alloc::sync::Arc", | |
1053 | "alloc::sync::Arc", | |
1054 | "alloc::sync::Arc", | |
1055 | ); | |
1056 | } | |
1057 | ||
1058 | #[test] | |
1059 | fn prefer_shorter_paths_if_not_alloc() { | |
1060 | check_found_path( | |
1061 | r#" | |
1062 | //- /main.rs crate:main deps:megaalloc,std | |
1063 | $0 | |
1064 | ||
1065 | //- /std.rs crate:std deps:megaalloc | |
1066 | pub mod sync { | |
1067 | pub use megaalloc::sync::Arc; | |
1068 | } | |
1069 | ||
1070 | //- /zzz.rs crate:megaalloc | |
1071 | pub struct Arc; | |
1072 | "#, | |
1073 | "megaalloc::Arc", | |
1074 | "megaalloc::Arc", | |
1075 | "megaalloc::Arc", | |
1076 | "megaalloc::Arc", | |
1077 | ); | |
1078 | } | |
1079 | ||
1080 | #[test] | |
1081 | fn builtins_are_in_scope() { | |
1082 | let code = r#" | |
1083 | $0 | |
1084 | ||
1085 | pub mod primitive { | |
1086 | pub use u8; | |
1087 | } | |
1088 | "#; | |
1089 | check_found_path(code, "u8", "u8", "u8", "u8"); | |
1090 | check_found_path(code, "u16", "u16", "u16", "u16"); | |
1091 | } | |
1092 | ||
1093 | #[test] | |
1094 | fn inner_items() { | |
1095 | check_found_path( | |
1096 | r#" | |
1097 | fn main() { | |
1098 | struct Inner {} | |
1099 | $0 | |
1100 | } | |
1101 | "#, | |
1102 | "Inner", | |
1103 | "Inner", | |
1104 | "Inner", | |
1105 | "Inner", | |
1106 | ); | |
1107 | } | |
1108 | ||
1109 | #[test] | |
1110 | fn inner_items_from_outer_scope() { | |
1111 | check_found_path( | |
1112 | r#" | |
1113 | fn main() { | |
1114 | struct Struct {} | |
1115 | { | |
1116 | $0 | |
1117 | } | |
1118 | } | |
1119 | "#, | |
1120 | "Struct", | |
1121 | "Struct", | |
1122 | "Struct", | |
1123 | "Struct", | |
1124 | ); | |
1125 | } | |
1126 | ||
1127 | #[test] | |
1128 | fn inner_items_from_inner_module() { | |
1129 | cov_mark::check!(prefixed_in_block_expression); | |
1130 | check_found_path( | |
1131 | r#" | |
1132 | fn main() { | |
1133 | mod module { | |
1134 | struct Struct {} | |
1135 | } | |
1136 | { | |
1137 | $0 | |
1138 | } | |
1139 | } | |
1140 | "#, | |
1141 | "module::Struct", | |
1142 | "module::Struct", | |
1143 | "module::Struct", | |
1144 | "module::Struct", | |
1145 | ); | |
1146 | } | |
1147 | ||
1148 | #[test] | |
1149 | fn outer_items_with_inner_items_present() { | |
1150 | check_found_path( | |
1151 | r#" | |
1152 | mod module { | |
1153 | pub struct CompleteMe; | |
1154 | } | |
1155 | ||
1156 | fn main() { | |
1157 | fn inner() {} | |
1158 | $0 | |
1159 | } | |
1160 | "#, | |
1161 | // FIXME: these could use fewer/better prefixes | |
1162 | "module::CompleteMe", | |
1163 | "crate::module::CompleteMe", | |
1164 | "crate::module::CompleteMe", | |
1165 | "crate::module::CompleteMe", | |
1166 | ) | |
1167 | } | |
1168 | ||
1169 | #[test] | |
1170 | fn from_inside_module() { | |
1171 | // This worked correctly, but the test suite logic was broken. | |
1172 | cov_mark::check!(submodule_in_testdb); | |
1173 | check_found_path( | |
1174 | r#" | |
1175 | mod baz { | |
1176 | pub struct Foo {} | |
1177 | } | |
1178 | ||
1179 | mod bar { | |
1180 | fn bar() { | |
1181 | $0 | |
1182 | } | |
1183 | } | |
1184 | "#, | |
1185 | "crate::baz::Foo", | |
1186 | "crate::baz::Foo", | |
1187 | "crate::baz::Foo", | |
1188 | "crate::baz::Foo", | |
1189 | ) | |
1190 | } | |
1191 | ||
1192 | #[test] | |
1193 | fn from_inside_module_with_inner_items() { | |
1194 | check_found_path( | |
1195 | r#" | |
1196 | mod baz { | |
1197 | pub struct Foo {} | |
1198 | } | |
1199 | ||
1200 | mod bar { | |
1201 | fn bar() { | |
1202 | fn inner() {} | |
1203 | $0 | |
1204 | } | |
1205 | } | |
1206 | "#, | |
1207 | "crate::baz::Foo", | |
1208 | "crate::baz::Foo", | |
1209 | "crate::baz::Foo", | |
1210 | "crate::baz::Foo", | |
1211 | ) | |
1212 | } | |
1213 | ||
1214 | #[test] | |
1215 | fn recursive_pub_mod_reexport() { | |
1216 | cov_mark::check!(recursive_imports); | |
1217 | check_found_path( | |
1218 | r#" | |
1219 | fn main() { | |
1220 | let _ = 22_i32.as_name$0(); | |
1221 | } | |
1222 | ||
1223 | pub mod name { | |
1224 | pub trait AsName { | |
1225 | fn as_name(&self) -> String; | |
1226 | } | |
1227 | impl AsName for i32 { | |
1228 | fn as_name(&self) -> String { | |
1229 | format!("Name: {}", self) | |
1230 | } | |
1231 | } | |
1232 | pub use crate::name; | |
1233 | } | |
1234 | "#, | |
1235 | "name::AsName", | |
1236 | "name::AsName", | |
1237 | "crate::name::AsName", | |
1238 | "self::name::AsName", | |
1239 | ); | |
1240 | } | |
1241 | ||
1242 | #[test] | |
1243 | fn extern_crate() { | |
1244 | check_found_path( | |
1245 | r#" | |
1246 | //- /main.rs crate:main deps:dep | |
1247 | $0 | |
1248 | //- /dep.rs crate:dep | |
1249 | "#, | |
1250 | "dep", | |
1251 | "dep", | |
1252 | "dep", | |
1253 | "dep", | |
1254 | ); | |
1255 | ||
1256 | check_found_path( | |
1257 | r#" | |
1258 | //- /main.rs crate:main deps:dep | |
1259 | fn f() { | |
1260 | fn inner() {} | |
1261 | $0 | |
1262 | } | |
1263 | //- /dep.rs crate:dep | |
1264 | "#, | |
1265 | "dep", | |
1266 | "dep", | |
1267 | "dep", | |
1268 | "dep", | |
1269 | ); | |
1270 | } | |
1271 | ||
1272 | #[test] | |
1273 | fn prelude_with_inner_items() { | |
1274 | check_found_path( | |
1275 | r#" | |
1276 | //- /main.rs crate:main deps:std | |
1277 | fn f() { | |
1278 | fn inner() {} | |
1279 | $0 | |
1280 | } | |
1281 | //- /std.rs crate:std | |
1282 | pub mod prelude { | |
1283 | pub mod rust_2018 { | |
1284 | pub enum Option { None } | |
1285 | pub use Option::*; | |
1286 | } | |
1287 | } | |
1288 | "#, | |
1289 | "None", | |
1290 | "None", | |
1291 | "None", | |
1292 | "None", | |
1293 | ); | |
1294 | } | |
1295 | } |