]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_expand/src/module.rs
New upstream version 1.56.0~beta.4+dfsg1
[rustc.git] / compiler / rustc_expand / src / module.rs
CommitLineData
6a06907d
XL
1use crate::base::ModuleData;
2use rustc_ast::ptr::P;
3use rustc_ast::{token, Attribute, Inline, Item};
4use rustc_errors::{struct_span_err, DiagnosticBuilder};
ba9703b0
XL
5use rustc_parse::new_parser_from_file;
6use rustc_session::parse::ParseSess;
3dfed10e 7use rustc_session::Session;
f9f354fc 8use rustc_span::symbol::{sym, Ident};
6a06907d 9use rustc_span::Span;
ba9703b0
XL
10
11use std::path::{self, Path, PathBuf};
12
ba9703b0 13#[derive(Copy, Clone)]
6a06907d 14pub 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
23pub struct ModulePathSuccess {
24 pub file_path: PathBuf,
25 pub dir_ownership: DirOwnership,
ba9703b0
XL
26}
27
6a06907d
XL
28crate 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
36pub 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
44crate 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 80crate 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
130fn 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 165fn 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.
185pub 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
226impl 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, &note);
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}