diff options
author | Aleksey Kladov <[email protected]> | 2018-10-23 17:15:31 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-10-23 18:44:23 +0100 |
commit | dc477db757247d5184250bffe9dd0c38dd867778 (patch) | |
tree | 1eebf0ff17462a44668f95b3e042de09d09985d0 /crates/ra_analysis/src/descriptors/module/imp.rs | |
parent | 1d574ed6543936af7d1d16c4b4ea9b4bd858aa41 (diff) |
Introduce ModuleId
Previously, module was synonym with a file, and so a module could have
had several parents. This commit introduces a separate module concept,
such that each module has only one parent, but a single file can
correspond to different modules.
Diffstat (limited to 'crates/ra_analysis/src/descriptors/module/imp.rs')
-rw-r--r-- | crates/ra_analysis/src/descriptors/module/imp.rs | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/descriptors/module/imp.rs b/crates/ra_analysis/src/descriptors/module/imp.rs new file mode 100644 index 000000000..22e4bd785 --- /dev/null +++ b/crates/ra_analysis/src/descriptors/module/imp.rs | |||
@@ -0,0 +1,146 @@ | |||
1 | use std::sync::Arc; | ||
2 | |||
3 | use relative_path::RelativePathBuf; | ||
4 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
5 | use ra_syntax::{ | ||
6 | SmolStr, | ||
7 | ast::{self, NameOwner}, | ||
8 | }; | ||
9 | |||
10 | use crate::{ | ||
11 | FileId, Cancelable, FileResolverImp, | ||
12 | db, | ||
13 | }; | ||
14 | |||
15 | use super::{ | ||
16 | ModuleData, ModuleTree, ModuleId, LinkId, LinkData, Problem, ModulesDatabase | ||
17 | }; | ||
18 | |||
19 | |||
20 | pub(super) fn submodules(db: &impl ModulesDatabase, file_id: FileId) -> Cancelable<Arc<Vec<SmolStr>>> { | ||
21 | db::check_canceled(db)?; | ||
22 | let file = db.file_syntax(file_id); | ||
23 | let root = file.ast(); | ||
24 | let submodules = modules(root).map(|(name, _)| name).collect(); | ||
25 | Ok(Arc::new(submodules)) | ||
26 | } | ||
27 | |||
28 | pub(super) fn modules(root: ast::Root<'_>) -> impl Iterator<Item = (SmolStr, ast::Module<'_>)> { | ||
29 | root.modules().filter_map(|module| { | ||
30 | let name = module.name()?.text(); | ||
31 | if !module.has_semi() { | ||
32 | return None; | ||
33 | } | ||
34 | Some((name, module)) | ||
35 | }) | ||
36 | } | ||
37 | |||
38 | pub(super) fn module_tree(db: &impl ModulesDatabase) -> Cancelable<Arc<ModuleTree>> { | ||
39 | db::check_canceled(db)?; | ||
40 | let res = create_module_tree(db)?; | ||
41 | Ok(Arc::new(res)) | ||
42 | } | ||
43 | |||
44 | |||
45 | #[derive(Clone, Hash, PartialEq, Eq, Debug)] | ||
46 | pub struct Submodule { | ||
47 | pub name: SmolStr, | ||
48 | } | ||
49 | |||
50 | |||
51 | fn create_module_tree<'a>( | ||
52 | db: &impl ModulesDatabase, | ||
53 | ) -> Cancelable<ModuleTree> { | ||
54 | let mut tree = ModuleTree { | ||
55 | mods: Vec::new(), | ||
56 | links: Vec::new(), | ||
57 | }; | ||
58 | |||
59 | let mut roots = FxHashMap::default(); | ||
60 | let mut visited = FxHashSet::default(); | ||
61 | |||
62 | for &file_id in db.file_set().files.iter() { | ||
63 | if visited.contains(&file_id) { | ||
64 | continue; // TODO: use explicit crate_roots here | ||
65 | } | ||
66 | assert!(!roots.contains_key(&file_id)); | ||
67 | let module_id = build_subtree(db, &mut tree, &mut visited, &mut roots, None, file_id)?; | ||
68 | roots.insert(file_id, module_id); | ||
69 | } | ||
70 | Ok(tree) | ||
71 | } | ||
72 | |||
73 | fn build_subtree( | ||
74 | db: &impl ModulesDatabase, | ||
75 | tree: &mut ModuleTree, | ||
76 | visited: &mut FxHashSet<FileId>, | ||
77 | roots: &mut FxHashMap<FileId, ModuleId>, | ||
78 | parent: Option<LinkId>, | ||
79 | file_id: FileId, | ||
80 | ) -> Cancelable<ModuleId> { | ||
81 | visited.insert(file_id); | ||
82 | let id = tree.push_mod(ModuleData { | ||
83 | file_id, | ||
84 | parent, | ||
85 | children: Vec::new(), | ||
86 | }); | ||
87 | let file_set = db.file_set(); | ||
88 | let file_resolver = &file_set.resolver; | ||
89 | for name in db.submodules(file_id)?.iter() { | ||
90 | let (points_to, problem) = resolve_submodule(file_id, name, file_resolver); | ||
91 | let link = tree.push_link(LinkData { | ||
92 | name: name.clone(), | ||
93 | owner: id, | ||
94 | points_to: Vec::new(), | ||
95 | problem: None, | ||
96 | }); | ||
97 | |||
98 | let points_to = points_to | ||
99 | .into_iter() | ||
100 | .map(|file_id| match roots.remove(&file_id) { | ||
101 | Some(module_id) => { | ||
102 | tree.module_mut(module_id).parent = Some(link); | ||
103 | Ok(module_id) | ||
104 | } | ||
105 | None => build_subtree(db, tree, visited, roots, Some(link), file_id), | ||
106 | }) | ||
107 | .collect::<Cancelable<Vec<_>>>()?; | ||
108 | tree.link_mut(link).points_to = points_to; | ||
109 | tree.link_mut(link).problem = problem; | ||
110 | } | ||
111 | Ok(id) | ||
112 | } | ||
113 | |||
114 | fn resolve_submodule( | ||
115 | file_id: FileId, | ||
116 | name: &SmolStr, | ||
117 | file_resolver: &FileResolverImp, | ||
118 | ) -> (Vec<FileId>, Option<Problem>) { | ||
119 | let mod_name = file_resolver.file_stem(file_id); | ||
120 | let is_dir_owner = mod_name == "mod" || mod_name == "lib" || mod_name == "main"; | ||
121 | |||
122 | let file_mod = RelativePathBuf::from(format!("../{}.rs", name)); | ||
123 | let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", name)); | ||
124 | let points_to: Vec<FileId>; | ||
125 | let problem: Option<Problem>; | ||
126 | if is_dir_owner { | ||
127 | points_to = [&file_mod, &dir_mod] | ||
128 | .iter() | ||
129 | .filter_map(|path| file_resolver.resolve(file_id, path)) | ||
130 | .collect(); | ||
131 | problem = if points_to.is_empty() { | ||
132 | Some(Problem::UnresolvedModule { | ||
133 | candidate: file_mod, | ||
134 | }) | ||
135 | } else { | ||
136 | None | ||
137 | } | ||
138 | } else { | ||
139 | points_to = Vec::new(); | ||
140 | problem = Some(Problem::NotDirOwner { | ||
141 | move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)), | ||
142 | candidate: file_mod, | ||
143 | }); | ||
144 | } | ||
145 | (points_to, problem) | ||
146 | } | ||