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