diff options
Diffstat (limited to 'crates/hir_def/src/nameres/mod_resolution.rs')
-rw-r--r-- | crates/hir_def/src/nameres/mod_resolution.rs | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/crates/hir_def/src/nameres/mod_resolution.rs b/crates/hir_def/src/nameres/mod_resolution.rs new file mode 100644 index 000000000..e8389b484 --- /dev/null +++ b/crates/hir_def/src/nameres/mod_resolution.rs | |||
@@ -0,0 +1,139 @@ | |||
1 | //! This module resolves `mod foo;` declaration to file. | ||
2 | use base_db::FileId; | ||
3 | use hir_expand::name::Name; | ||
4 | use syntax::SmolStr; | ||
5 | |||
6 | use crate::{db::DefDatabase, HirFileId}; | ||
7 | |||
8 | #[derive(Clone, Debug)] | ||
9 | pub(super) struct ModDir { | ||
10 | /// `` for `mod.rs`, `lib.rs` | ||
11 | /// `foo/` for `foo.rs` | ||
12 | /// `foo/bar/` for `mod bar { mod x; }` nested in `foo.rs` | ||
13 | /// Invariant: path.is_empty() || path.ends_with('/') | ||
14 | dir_path: DirPath, | ||
15 | /// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/` | ||
16 | root_non_dir_owner: bool, | ||
17 | } | ||
18 | |||
19 | impl ModDir { | ||
20 | pub(super) fn root() -> ModDir { | ||
21 | ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false } | ||
22 | } | ||
23 | |||
24 | pub(super) fn descend_into_definition( | ||
25 | &self, | ||
26 | name: &Name, | ||
27 | attr_path: Option<&SmolStr>, | ||
28 | ) -> ModDir { | ||
29 | let path = match attr_path.map(|it| it.as_str()) { | ||
30 | None => { | ||
31 | let mut path = self.dir_path.clone(); | ||
32 | path.push(&name.to_string()); | ||
33 | path | ||
34 | } | ||
35 | Some(attr_path) => { | ||
36 | let mut path = self.dir_path.join_attr(attr_path, self.root_non_dir_owner); | ||
37 | if !(path.is_empty() || path.ends_with('/')) { | ||
38 | path.push('/') | ||
39 | } | ||
40 | DirPath::new(path) | ||
41 | } | ||
42 | }; | ||
43 | ModDir { dir_path: path, root_non_dir_owner: false } | ||
44 | } | ||
45 | |||
46 | pub(super) fn resolve_declaration( | ||
47 | &self, | ||
48 | db: &dyn DefDatabase, | ||
49 | file_id: HirFileId, | ||
50 | name: &Name, | ||
51 | attr_path: Option<&SmolStr>, | ||
52 | ) -> Result<(FileId, bool, ModDir), String> { | ||
53 | let file_id = file_id.original_file(db.upcast()); | ||
54 | |||
55 | let mut candidate_files = Vec::new(); | ||
56 | match attr_path { | ||
57 | Some(attr_path) => { | ||
58 | candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner)) | ||
59 | } | ||
60 | None => { | ||
61 | candidate_files.push(format!("{}{}.rs", self.dir_path.0, name)); | ||
62 | candidate_files.push(format!("{}{}/mod.rs", self.dir_path.0, name)); | ||
63 | } | ||
64 | }; | ||
65 | |||
66 | for candidate in candidate_files.iter() { | ||
67 | if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) { | ||
68 | let is_mod_rs = candidate.ends_with("mod.rs"); | ||
69 | |||
70 | let (dir_path, root_non_dir_owner) = if is_mod_rs || attr_path.is_some() { | ||
71 | (DirPath::empty(), false) | ||
72 | } else { | ||
73 | (DirPath::new(format!("{}/", name)), true) | ||
74 | }; | ||
75 | return Ok((file_id, is_mod_rs, ModDir { dir_path, root_non_dir_owner })); | ||
76 | } | ||
77 | } | ||
78 | Err(candidate_files.remove(0)) | ||
79 | } | ||
80 | } | ||
81 | |||
82 | #[derive(Clone, Debug)] | ||
83 | struct DirPath(String); | ||
84 | |||
85 | impl DirPath { | ||
86 | fn assert_invariant(&self) { | ||
87 | assert!(self.0.is_empty() || self.0.ends_with('/')); | ||
88 | } | ||
89 | fn new(repr: String) -> DirPath { | ||
90 | let res = DirPath(repr); | ||
91 | res.assert_invariant(); | ||
92 | res | ||
93 | } | ||
94 | fn empty() -> DirPath { | ||
95 | DirPath::new(String::new()) | ||
96 | } | ||
97 | fn push(&mut self, name: &str) { | ||
98 | self.0.push_str(name); | ||
99 | self.0.push('/'); | ||
100 | self.assert_invariant(); | ||
101 | } | ||
102 | fn parent(&self) -> Option<&str> { | ||
103 | if self.0.is_empty() { | ||
104 | return None; | ||
105 | }; | ||
106 | let idx = | ||
107 | self.0[..self.0.len() - '/'.len_utf8()].rfind('/').map_or(0, |it| it + '/'.len_utf8()); | ||
108 | Some(&self.0[..idx]) | ||
109 | } | ||
110 | /// So this is the case which doesn't really work I think if we try to be | ||
111 | /// 100% platform agnostic: | ||
112 | /// | ||
113 | /// ``` | ||
114 | /// mod a { | ||
115 | /// #[path="C://sad/face"] | ||
116 | /// mod b { mod c; } | ||
117 | /// } | ||
118 | /// ``` | ||
119 | /// | ||
120 | /// Here, we need to join logical dir path to a string path from an | ||
121 | /// attribute. Ideally, we should somehow losslessly communicate the whole | ||
122 | /// construction to `FileLoader`. | ||
123 | fn join_attr(&self, mut attr: &str, relative_to_parent: bool) -> String { | ||
124 | let base = if relative_to_parent { self.parent().unwrap() } else { &self.0 }; | ||
125 | |||
126 | if attr.starts_with("./") { | ||
127 | attr = &attr["./".len()..]; | ||
128 | } | ||
129 | let tmp; | ||
130 | let attr = if attr.contains('\\') { | ||
131 | tmp = attr.replace('\\', "/"); | ||
132 | &tmp | ||
133 | } else { | ||
134 | attr | ||
135 | }; | ||
136 | let res = format!("{}{}", base, attr); | ||
137 | res | ||
138 | } | ||
139 | } | ||