diff options
Diffstat (limited to 'crates/ra_hir_def/src/nameres')
-rw-r--r-- | crates/ra_hir_def/src/nameres/mod_resolution.rs | 116 |
1 files changed, 87 insertions, 29 deletions
diff --git a/crates/ra_hir_def/src/nameres/mod_resolution.rs b/crates/ra_hir_def/src/nameres/mod_resolution.rs index 39e9a6d97..953961632 100644 --- a/crates/ra_hir_def/src/nameres/mod_resolution.rs +++ b/crates/ra_hir_def/src/nameres/mod_resolution.rs | |||
@@ -1,23 +1,24 @@ | |||
1 | //! This module resolves `mod foo;` declaration to file. | 1 | //! This module resolves `mod foo;` declaration to file. |
2 | use hir_expand::name::Name; | 2 | use hir_expand::name::Name; |
3 | use ra_db::{FileId, RelativePathBuf}; | 3 | use ra_db::FileId; |
4 | use ra_syntax::SmolStr; | 4 | use ra_syntax::SmolStr; |
5 | 5 | ||
6 | use crate::{db::DefDatabase, HirFileId}; | 6 | use crate::{db::DefDatabase, HirFileId}; |
7 | 7 | ||
8 | #[derive(Clone, Debug)] | 8 | #[derive(Clone, Debug)] |
9 | pub(super) struct ModDir { | 9 | pub(super) struct ModDir { |
10 | /// `.` for `mod.rs`, `lib.rs` | 10 | /// `` for `mod.rs`, `lib.rs` |
11 | /// `./foo` for `foo.rs` | 11 | /// `foo/` for `foo.rs` |
12 | /// `./foo/bar` for `mod bar { mod x; }` nested in `foo.rs` | 12 | /// `foo/bar/` for `mod bar { mod x; }` nested in `foo.rs` |
13 | path: RelativePathBuf, | 13 | /// Invariant: path.is_empty() || path.ends_with('/') |
14 | dir_path: DirPath, | ||
14 | /// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/` | 15 | /// inside `./foo.rs`, mods with `#[path]` should *not* be relative to `./foo/` |
15 | root_non_dir_owner: bool, | 16 | root_non_dir_owner: bool, |
16 | } | 17 | } |
17 | 18 | ||
18 | impl ModDir { | 19 | impl ModDir { |
19 | pub(super) fn root() -> ModDir { | 20 | pub(super) fn root() -> ModDir { |
20 | ModDir { path: RelativePathBuf::default(), root_non_dir_owner: false } | 21 | ModDir { dir_path: DirPath::empty(), root_non_dir_owner: false } |
21 | } | 22 | } |
22 | 23 | ||
23 | pub(super) fn descend_into_definition( | 24 | pub(super) fn descend_into_definition( |
@@ -25,17 +26,21 @@ impl ModDir { | |||
25 | name: &Name, | 26 | name: &Name, |
26 | attr_path: Option<&SmolStr>, | 27 | attr_path: Option<&SmolStr>, |
27 | ) -> ModDir { | 28 | ) -> ModDir { |
28 | let mut path = self.path.clone(); | 29 | let path = match attr_path.map(|it| it.as_str()) { |
29 | match attr_to_path(attr_path) { | 30 | None => { |
30 | None => path.push(&name.to_string()), | 31 | let mut path = self.dir_path.clone(); |
32 | path.push(&name.to_string()); | ||
33 | path | ||
34 | } | ||
31 | Some(attr_path) => { | 35 | Some(attr_path) => { |
32 | if self.root_non_dir_owner { | 36 | let mut path = self.dir_path.join_attr(attr_path, self.root_non_dir_owner); |
33 | assert!(path.pop()); | 37 | if !(path.is_empty() || path.ends_with('/')) { |
38 | path.push('/') | ||
34 | } | 39 | } |
35 | path.push(attr_path); | 40 | DirPath::new(path) |
36 | } | 41 | } |
37 | } | 42 | }; |
38 | ModDir { path, root_non_dir_owner: false } | 43 | ModDir { dir_path: path, root_non_dir_owner: false } |
39 | } | 44 | } |
40 | 45 | ||
41 | pub(super) fn resolve_declaration( | 46 | pub(super) fn resolve_declaration( |
@@ -48,34 +53,87 @@ impl ModDir { | |||
48 | let file_id = file_id.original_file(db.upcast()); | 53 | let file_id = file_id.original_file(db.upcast()); |
49 | 54 | ||
50 | let mut candidate_files = Vec::new(); | 55 | let mut candidate_files = Vec::new(); |
51 | match attr_to_path(attr_path) { | 56 | match attr_path { |
52 | Some(attr_path) => { | 57 | Some(attr_path) => { |
53 | let base = | 58 | candidate_files.push(self.dir_path.join_attr(attr_path, self.root_non_dir_owner)) |
54 | if self.root_non_dir_owner { self.path.parent().unwrap() } else { &self.path }; | ||
55 | candidate_files.push(base.join(attr_path).to_string()) | ||
56 | } | 59 | } |
57 | None => { | 60 | None => { |
58 | candidate_files.push(self.path.join(&format!("{}.rs", name)).to_string()); | 61 | candidate_files.push(format!("{}{}.rs", self.dir_path.0, name)); |
59 | candidate_files.push(self.path.join(&format!("{}/mod.rs", name)).to_string()); | 62 | candidate_files.push(format!("{}{}/mod.rs", self.dir_path.0, name)); |
60 | } | 63 | } |
61 | }; | 64 | }; |
62 | 65 | ||
63 | for candidate in candidate_files.iter() { | 66 | for candidate in candidate_files.iter() { |
64 | if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) { | 67 | if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) { |
65 | let mut root_non_dir_owner = false; | ||
66 | let mut mod_path = RelativePathBuf::new(); | ||
67 | let is_mod_rs = candidate.ends_with("mod.rs"); | 68 | let is_mod_rs = candidate.ends_with("mod.rs"); |
68 | if !(is_mod_rs || attr_path.is_some()) { | 69 | |
69 | root_non_dir_owner = true; | 70 | let (dir_path, root_non_dir_owner) = if is_mod_rs || attr_path.is_some() { |
70 | mod_path.push(&name.to_string()); | 71 | (DirPath::empty(), false) |
71 | } | 72 | } else { |
72 | return Ok((file_id, is_mod_rs, ModDir { path: mod_path, root_non_dir_owner })); | 73 | (DirPath::new(format!("{}/", name)), true) |
74 | }; | ||
75 | return Ok((file_id, is_mod_rs, ModDir { dir_path, root_non_dir_owner })); | ||
73 | } | 76 | } |
74 | } | 77 | } |
75 | Err(candidate_files.remove(0)) | 78 | Err(candidate_files.remove(0)) |
76 | } | 79 | } |
77 | } | 80 | } |
78 | 81 | ||
79 | fn attr_to_path(attr: Option<&SmolStr>) -> Option<RelativePathBuf> { | 82 | #[derive(Clone, Debug)] |
80 | attr.and_then(|it| RelativePathBuf::from_path(&it.replace("\\", "/")).ok()) | 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 | } | ||
81 | } | 139 | } |