]>
Commit | Line | Data |
---|---|---|
6a06907d XL |
1 | use crate::base::ModuleData; |
2 | use rustc_ast::ptr::P; | |
3 | use rustc_ast::{token, Attribute, Inline, Item}; | |
4 | use rustc_errors::{struct_span_err, DiagnosticBuilder}; | |
ba9703b0 XL |
5 | use rustc_parse::new_parser_from_file; |
6 | use rustc_session::parse::ParseSess; | |
3dfed10e | 7 | use rustc_session::Session; |
f9f354fc | 8 | use rustc_span::symbol::{sym, Ident}; |
6a06907d | 9 | use rustc_span::Span; |
ba9703b0 XL |
10 | |
11 | use std::path::{self, Path, PathBuf}; | |
12 | ||
ba9703b0 | 13 | #[derive(Copy, Clone)] |
6a06907d | 14 | pub enum DirOwnership { |
ba9703b0 XL |
15 | Owned { |
16 | // None if `mod.rs`, `Some("foo")` if we're in `foo.rs`. | |
f9f354fc | 17 | relative: Option<Ident>, |
ba9703b0 XL |
18 | }, |
19 | UnownedViaBlock, | |
ba9703b0 XL |
20 | } |
21 | ||
ba9703b0 | 22 | // Public for rustfmt usage. |
6a06907d XL |
23 | pub struct ModulePathSuccess { |
24 | pub file_path: PathBuf, | |
25 | pub dir_ownership: DirOwnership, | |
ba9703b0 XL |
26 | } |
27 | ||
6a06907d XL |
28 | crate struct ParsedExternalMod { |
29 | pub items: Vec<P<Item>>, | |
30 | pub inner_span: Span, | |
31 | pub file_path: PathBuf, | |
32 | pub dir_path: PathBuf, | |
33 | pub dir_ownership: DirOwnership, | |
34 | } | |
35 | ||
36 | pub enum ModError<'a> { | |
37 | CircularInclusion(Vec<PathBuf>), | |
38 | ModInBlock(Option<Ident>), | |
17df50a5 XL |
39 | FileNotFound(Ident, PathBuf, PathBuf), |
40 | MultipleCandidates(Ident, PathBuf, PathBuf), | |
6a06907d | 41 | ParserError(DiagnosticBuilder<'a>), |
ba9703b0 XL |
42 | } |
43 | ||
44 | crate fn parse_external_mod( | |
3dfed10e | 45 | sess: &Session, |
6a06907d | 46 | ident: Ident, |
ba9703b0 | 47 | span: Span, // The span to blame on errors. |
6a06907d XL |
48 | module: &ModuleData, |
49 | mut dir_ownership: DirOwnership, | |
ba9703b0 | 50 | attrs: &mut Vec<Attribute>, |
6a06907d | 51 | ) -> ParsedExternalMod { |
ba9703b0 | 52 | // We bail on the first error, but that error does not cause a fatal error... (1) |
6a06907d | 53 | let result: Result<_, ModError<'_>> = try { |
ba9703b0 | 54 | // Extract the file path and the new ownership. |
6a06907d XL |
55 | let mp = mod_file_path(sess, ident, &attrs, &module.dir_path, dir_ownership)?; |
56 | dir_ownership = mp.dir_ownership; | |
ba9703b0 XL |
57 | |
58 | // Ensure file paths are acyclic. | |
6a06907d XL |
59 | if let Some(pos) = module.file_path_stack.iter().position(|p| p == &mp.file_path) { |
60 | Err(ModError::CircularInclusion(module.file_path_stack[pos..].to_vec()))?; | |
61 | } | |
ba9703b0 XL |
62 | |
63 | // Actually parse the external file as a module. | |
6a06907d XL |
64 | let mut parser = new_parser_from_file(&sess.parse_sess, &mp.file_path, Some(span)); |
65 | let (mut inner_attrs, items, inner_span) = | |
66 | parser.parse_mod(&token::Eof).map_err(|err| ModError::ParserError(err))?; | |
67 | attrs.append(&mut inner_attrs); | |
68 | (items, inner_span, mp.file_path) | |
ba9703b0 XL |
69 | }; |
70 | // (1) ...instead, we return a dummy module. | |
6a06907d XL |
71 | let (items, inner_span, file_path) = |
72 | result.map_err(|err| err.report(sess, span)).unwrap_or_default(); | |
ba9703b0 | 73 | |
6a06907d XL |
74 | // Extract the directory path for submodules of the module. |
75 | let dir_path = file_path.parent().unwrap_or(&file_path).to_owned(); | |
ba9703b0 | 76 | |
6a06907d | 77 | ParsedExternalMod { items, inner_span, file_path, dir_path, dir_ownership } |
ba9703b0 XL |
78 | } |
79 | ||
6a06907d | 80 | crate fn mod_dir_path( |
3dfed10e | 81 | sess: &Session, |
6a06907d | 82 | ident: Ident, |
ba9703b0 | 83 | attrs: &[Attribute], |
6a06907d XL |
84 | module: &ModuleData, |
85 | mut dir_ownership: DirOwnership, | |
86 | inline: Inline, | |
87 | ) -> (PathBuf, DirOwnership) { | |
88 | match inline { | |
94222f64 XL |
89 | Inline::Yes if let Some(file_path) = mod_file_path_from_attr(sess, attrs, &module.dir_path) => { |
90 | // For inline modules file path from `#[path]` is actually the directory path | |
91 | // for historical reasons, so we don't pop the last segment here. | |
92 | (file_path, DirOwnership::Owned { relative: None }) | |
93 | } | |
6a06907d | 94 | Inline::Yes => { |
6a06907d XL |
95 | // We have to push on the current module name in the case of relative |
96 | // paths in order to ensure that any additional module paths from inline | |
97 | // `mod x { ... }` come after the relative extension. | |
ba9703b0 | 98 | // |
6a06907d XL |
99 | // For example, a `mod z { ... }` inside `x/y.rs` should set the current |
100 | // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`. | |
101 | let mut dir_path = module.dir_path.clone(); | |
102 | if let DirOwnership::Owned { relative } = &mut dir_ownership { | |
103 | if let Some(ident) = relative.take() { | |
104 | // Remove the relative offset. | |
105 | dir_path.push(&*ident.as_str()); | |
106 | } | |
107 | } | |
108 | dir_path.push(&*ident.as_str()); | |
ba9703b0 | 109 | |
6a06907d | 110 | (dir_path, dir_ownership) |
ba9703b0 | 111 | } |
6a06907d XL |
112 | Inline::No => { |
113 | // FIXME: This is a subset of `parse_external_mod` without actual parsing, | |
114 | // check whether the logic for unloaded, loaded and inline modules can be unified. | |
115 | let file_path = mod_file_path(sess, ident, &attrs, &module.dir_path, dir_ownership) | |
116 | .map(|mp| { | |
117 | dir_ownership = mp.dir_ownership; | |
118 | mp.file_path | |
119 | }) | |
120 | .unwrap_or_default(); | |
121 | ||
122 | // Extract the directory path for submodules of the module. | |
123 | let dir_path = file_path.parent().unwrap_or(&file_path).to_owned(); | |
124 | ||
125 | (dir_path, dir_ownership) | |
ba9703b0 XL |
126 | } |
127 | } | |
128 | } | |
129 | ||
6a06907d XL |
130 | fn mod_file_path<'a>( |
131 | sess: &'a Session, | |
132 | ident: Ident, | |
133 | attrs: &[Attribute], | |
134 | dir_path: &Path, | |
135 | dir_ownership: DirOwnership, | |
136 | ) -> Result<ModulePathSuccess, ModError<'a>> { | |
137 | if let Some(file_path) = mod_file_path_from_attr(sess, attrs, dir_path) { | |
138 | // All `#[path]` files are treated as though they are a `mod.rs` file. | |
139 | // This means that `mod foo;` declarations inside `#[path]`-included | |
140 | // files are siblings, | |
141 | // | |
142 | // Note that this will produce weirdness when a file named `foo.rs` is | |
143 | // `#[path]` included and contains a `mod foo;` declaration. | |
144 | // If you encounter this, it's your own darn fault :P | |
145 | let dir_ownership = DirOwnership::Owned { relative: None }; | |
146 | return Ok(ModulePathSuccess { file_path, dir_ownership }); | |
ba9703b0 | 147 | } |
ba9703b0 | 148 | |
6a06907d XL |
149 | let relative = match dir_ownership { |
150 | DirOwnership::Owned { relative } => relative, | |
151 | DirOwnership::UnownedViaBlock => None, | |
152 | }; | |
153 | let result = default_submod_path(&sess.parse_sess, ident, relative, dir_path); | |
154 | match dir_ownership { | |
155 | DirOwnership::Owned { .. } => result, | |
156 | DirOwnership::UnownedViaBlock => Err(ModError::ModInBlock(match result { | |
157 | Ok(_) | Err(ModError::MultipleCandidates(..)) => Some(ident), | |
158 | _ => None, | |
159 | })), | |
ba9703b0 | 160 | } |
ba9703b0 XL |
161 | } |
162 | ||
163 | /// Derive a submodule path from the first found `#[path = "path_string"]`. | |
164 | /// The provided `dir_path` is joined with the `path_string`. | |
6a06907d | 165 | fn mod_file_path_from_attr( |
3dfed10e XL |
166 | sess: &Session, |
167 | attrs: &[Attribute], | |
168 | dir_path: &Path, | |
169 | ) -> Option<PathBuf> { | |
ba9703b0 | 170 | // Extract path string from first `#[path = "path_string"]` attribute. |
6a06907d | 171 | let path_string = sess.first_attr_value_str_by_name(attrs, sym::path)?.as_str(); |
ba9703b0 XL |
172 | |
173 | // On windows, the base path might have the form | |
174 | // `\\?\foo\bar` in which case it does not tolerate | |
175 | // mixed `/` and `\` separators, so canonicalize | |
176 | // `/` to `\`. | |
177 | #[cfg(windows)] | |
178 | let path_string = path_string.replace("/", "\\"); | |
179 | ||
180 | Some(dir_path.join(&*path_string)) | |
181 | } | |
182 | ||
183 | /// Returns a path to a module. | |
184 | // Public for rustfmt usage. | |
185 | pub fn default_submod_path<'a>( | |
186 | sess: &'a ParseSess, | |
6a06907d | 187 | ident: Ident, |
f9f354fc | 188 | relative: Option<Ident>, |
ba9703b0 | 189 | dir_path: &Path, |
6a06907d | 190 | ) -> Result<ModulePathSuccess, ModError<'a>> { |
ba9703b0 XL |
191 | // If we're in a foo.rs file instead of a mod.rs file, |
192 | // we need to look for submodules in | |
6a06907d XL |
193 | // `./foo/<ident>.rs` and `./foo/<ident>/mod.rs` rather than |
194 | // `./<ident>.rs` and `./<ident>/mod.rs`. | |
ba9703b0 XL |
195 | let relative_prefix_string; |
196 | let relative_prefix = if let Some(ident) = relative { | |
197 | relative_prefix_string = format!("{}{}", ident.name, path::MAIN_SEPARATOR); | |
198 | &relative_prefix_string | |
199 | } else { | |
200 | "" | |
201 | }; | |
202 | ||
6a06907d | 203 | let mod_name = ident.name.to_string(); |
ba9703b0 XL |
204 | let default_path_str = format!("{}{}.rs", relative_prefix, mod_name); |
205 | let secondary_path_str = | |
206 | format!("{}{}{}mod.rs", relative_prefix, mod_name, path::MAIN_SEPARATOR); | |
207 | let default_path = dir_path.join(&default_path_str); | |
208 | let secondary_path = dir_path.join(&secondary_path_str); | |
209 | let default_exists = sess.source_map().file_exists(&default_path); | |
210 | let secondary_exists = sess.source_map().file_exists(&secondary_path); | |
211 | ||
6a06907d | 212 | match (default_exists, secondary_exists) { |
ba9703b0 | 213 | (true, false) => Ok(ModulePathSuccess { |
6a06907d XL |
214 | file_path: default_path, |
215 | dir_ownership: DirOwnership::Owned { relative: Some(ident) }, | |
ba9703b0 XL |
216 | }), |
217 | (false, true) => Ok(ModulePathSuccess { | |
6a06907d XL |
218 | file_path: secondary_path, |
219 | dir_ownership: DirOwnership::Owned { relative: None }, | |
ba9703b0 | 220 | }), |
17df50a5 XL |
221 | (false, false) => Err(ModError::FileNotFound(ident, default_path, secondary_path)), |
222 | (true, true) => Err(ModError::MultipleCandidates(ident, default_path, secondary_path)), | |
6a06907d XL |
223 | } |
224 | } | |
ba9703b0 | 225 | |
6a06907d XL |
226 | impl ModError<'_> { |
227 | fn report(self, sess: &Session, span: Span) { | |
228 | let diag = &sess.parse_sess.span_diagnostic; | |
229 | match self { | |
230 | ModError::CircularInclusion(file_paths) => { | |
231 | let mut msg = String::from("circular modules: "); | |
232 | for file_path in &file_paths { | |
233 | msg.push_str(&file_path.display().to_string()); | |
234 | msg.push_str(" -> "); | |
235 | } | |
236 | msg.push_str(&file_paths[0].display().to_string()); | |
237 | diag.struct_span_err(span, &msg) | |
238 | } | |
239 | ModError::ModInBlock(ident) => { | |
240 | let msg = "cannot declare a non-inline module inside a block unless it has a path attribute"; | |
241 | let mut err = diag.struct_span_err(span, msg); | |
242 | if let Some(ident) = ident { | |
243 | let note = | |
244 | format!("maybe `use` the module `{}` instead of redeclaring it", ident); | |
245 | err.span_note(span, ¬e); | |
246 | } | |
247 | err | |
248 | } | |
17df50a5 | 249 | ModError::FileNotFound(ident, default_path, secondary_path) => { |
6a06907d XL |
250 | let mut err = struct_span_err!( |
251 | diag, | |
252 | span, | |
253 | E0583, | |
254 | "file not found for module `{}`", | |
255 | ident, | |
256 | ); | |
257 | err.help(&format!( | |
17df50a5 | 258 | "to create the module `{}`, create file \"{}\" or \"{}\"", |
6a06907d XL |
259 | ident, |
260 | default_path.display(), | |
17df50a5 | 261 | secondary_path.display(), |
6a06907d XL |
262 | )); |
263 | err | |
264 | } | |
17df50a5 | 265 | ModError::MultipleCandidates(ident, default_path, secondary_path) => { |
6a06907d XL |
266 | let mut err = struct_span_err!( |
267 | diag, | |
268 | span, | |
269 | E0761, | |
17df50a5 | 270 | "file for module `{}` found at both \"{}\" and \"{}\"", |
6a06907d | 271 | ident, |
17df50a5 XL |
272 | default_path.display(), |
273 | secondary_path.display(), | |
6a06907d XL |
274 | ); |
275 | err.help("delete or rename one of them to remove the ambiguity"); | |
276 | err | |
277 | } | |
278 | ModError::ParserError(err) => err, | |
279 | }.emit() | |
280 | } | |
ba9703b0 | 281 | } |