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