]> git.lizzy.rs Git - rust.git/blob - src/tools/rustfmt/src/modules.rs
Auto merge of #100210 - mystor:proc_macro_diag_struct, r=eddyb
[rust.git] / src / tools / rustfmt / src / modules.rs
1 use std::borrow::Cow;
2 use std::collections::BTreeMap;
3 use std::path::{Path, PathBuf};
4
5 use rustc_ast::ast;
6 use rustc_ast::visit::Visitor;
7 use rustc_span::symbol::{self, sym, Symbol};
8 use rustc_span::Span;
9 use thiserror::Error;
10
11 use crate::attr::MetaVisitor;
12 use crate::config::FileName;
13 use crate::items::is_mod_decl;
14 use crate::parse::parser::{
15     Directory, DirectoryOwnership, ModError, ModulePathSuccess, Parser, ParserError,
16 };
17 use crate::parse::session::ParseSess;
18 use crate::utils::{contains_skip, mk_sp};
19
20 mod visitor;
21
22 type FileModMap<'ast> = BTreeMap<FileName, Module<'ast>>;
23
24 /// Represents module with its inner attributes.
25 #[derive(Debug, Clone)]
26 pub(crate) struct Module<'a> {
27     ast_mod_kind: Option<Cow<'a, ast::ModKind>>,
28     pub(crate) items: Cow<'a, Vec<rustc_ast::ptr::P<ast::Item>>>,
29     inner_attr: ast::AttrVec,
30     pub(crate) span: Span,
31 }
32
33 impl<'a> Module<'a> {
34     pub(crate) fn new(
35         mod_span: Span,
36         ast_mod_kind: Option<Cow<'a, ast::ModKind>>,
37         mod_items: Cow<'a, Vec<rustc_ast::ptr::P<ast::Item>>>,
38         mod_attrs: Cow<'a, ast::AttrVec>,
39     ) -> Self {
40         let inner_attr = mod_attrs
41             .iter()
42             .filter(|attr| attr.style == ast::AttrStyle::Inner)
43             .cloned()
44             .collect();
45         Module {
46             items: mod_items,
47             inner_attr,
48             span: mod_span,
49             ast_mod_kind,
50         }
51     }
52
53     pub(crate) fn attrs(&self) -> &[ast::Attribute] {
54         &self.inner_attr
55     }
56 }
57
58 /// Maps each module to the corresponding file.
59 pub(crate) struct ModResolver<'ast, 'sess> {
60     parse_sess: &'sess ParseSess,
61     directory: Directory,
62     file_map: FileModMap<'ast>,
63     recursive: bool,
64 }
65
66 /// Represents errors while trying to resolve modules.
67 #[derive(Debug, Error)]
68 #[error("failed to resolve mod `{module}`: {kind}")]
69 pub struct ModuleResolutionError {
70     pub(crate) module: String,
71     pub(crate) kind: ModuleResolutionErrorKind,
72 }
73
74 /// Defines variants similar to those of [rustc_expand::module::ModError]
75 #[derive(Debug, Error)]
76 pub(crate) enum ModuleResolutionErrorKind {
77     /// Find a file that cannot be parsed.
78     #[error("cannot parse {file}")]
79     ParseError { file: PathBuf },
80     /// File cannot be found.
81     #[error("{file} does not exist")]
82     NotFound { file: PathBuf },
83     /// File a.rs and a/mod.rs both exist
84     #[error("file for module found at both {default_path:?} and {secondary_path:?}")]
85     MultipleCandidates {
86         default_path: PathBuf,
87         secondary_path: PathBuf,
88     },
89 }
90
91 #[derive(Clone)]
92 enum SubModKind<'a, 'ast> {
93     /// `mod foo;`
94     External(PathBuf, DirectoryOwnership, Module<'ast>),
95     /// `mod foo;` with multiple sources.
96     MultiExternal(Vec<(PathBuf, DirectoryOwnership, Module<'ast>)>),
97     /// `mod foo {}`
98     Internal(&'a ast::Item),
99 }
100
101 impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
102     /// Creates a new `ModResolver`.
103     pub(crate) fn new(
104         parse_sess: &'sess ParseSess,
105         directory_ownership: DirectoryOwnership,
106         recursive: bool,
107     ) -> Self {
108         ModResolver {
109             directory: Directory {
110                 path: PathBuf::new(),
111                 ownership: directory_ownership,
112             },
113             file_map: BTreeMap::new(),
114             parse_sess,
115             recursive,
116         }
117     }
118
119     /// Creates a map that maps a file name to the module in AST.
120     pub(crate) fn visit_crate(
121         mut self,
122         krate: &'ast ast::Crate,
123     ) -> Result<FileModMap<'ast>, ModuleResolutionError> {
124         let root_filename = self.parse_sess.span_to_filename(krate.spans.inner_span);
125         self.directory.path = match root_filename {
126             FileName::Real(ref p) => p.parent().unwrap_or(Path::new("")).to_path_buf(),
127             _ => PathBuf::new(),
128         };
129
130         // Skip visiting sub modules when the input is from stdin.
131         if self.recursive {
132             self.visit_mod_from_ast(&krate.items)?;
133         }
134
135         let snippet_provider = self.parse_sess.snippet_provider(krate.spans.inner_span);
136
137         self.file_map.insert(
138             root_filename,
139             Module::new(
140                 mk_sp(snippet_provider.start_pos(), snippet_provider.end_pos()),
141                 None,
142                 Cow::Borrowed(&krate.items),
143                 Cow::Borrowed(&krate.attrs),
144             ),
145         );
146         Ok(self.file_map)
147     }
148
149     /// Visit `cfg_if` macro and look for module declarations.
150     fn visit_cfg_if(&mut self, item: Cow<'ast, ast::Item>) -> Result<(), ModuleResolutionError> {
151         let mut visitor = visitor::CfgIfVisitor::new(self.parse_sess);
152         visitor.visit_item(&item);
153         for module_item in visitor.mods() {
154             if let ast::ItemKind::Mod(_, ref sub_mod_kind) = module_item.item.kind {
155                 self.visit_sub_mod(
156                     &module_item.item,
157                     Module::new(
158                         module_item.item.span,
159                         Some(Cow::Owned(sub_mod_kind.clone())),
160                         Cow::Owned(vec![]),
161                         Cow::Owned(ast::AttrVec::new()),
162                     ),
163                 )?;
164             }
165         }
166         Ok(())
167     }
168
169     /// Visit modules defined inside macro calls.
170     fn visit_mod_outside_ast(
171         &mut self,
172         items: Vec<rustc_ast::ptr::P<ast::Item>>,
173     ) -> Result<(), ModuleResolutionError> {
174         for item in items {
175             if is_cfg_if(&item) {
176                 self.visit_cfg_if(Cow::Owned(item.into_inner()))?;
177                 continue;
178             }
179
180             if let ast::ItemKind::Mod(_, ref sub_mod_kind) = item.kind {
181                 let span = item.span;
182                 self.visit_sub_mod(
183                     &item,
184                     Module::new(
185                         span,
186                         Some(Cow::Owned(sub_mod_kind.clone())),
187                         Cow::Owned(vec![]),
188                         Cow::Owned(ast::AttrVec::new()),
189                     ),
190                 )?;
191             }
192         }
193         Ok(())
194     }
195
196     /// Visit modules from AST.
197     fn visit_mod_from_ast(
198         &mut self,
199         items: &'ast [rustc_ast::ptr::P<ast::Item>],
200     ) -> Result<(), ModuleResolutionError> {
201         for item in items {
202             if is_cfg_if(item) {
203                 self.visit_cfg_if(Cow::Borrowed(item))?;
204             }
205
206             if let ast::ItemKind::Mod(_, ref sub_mod_kind) = item.kind {
207                 let span = item.span;
208                 self.visit_sub_mod(
209                     item,
210                     Module::new(
211                         span,
212                         Some(Cow::Borrowed(sub_mod_kind)),
213                         Cow::Owned(vec![]),
214                         Cow::Borrowed(&item.attrs),
215                     ),
216                 )?;
217             }
218         }
219         Ok(())
220     }
221
222     fn visit_sub_mod(
223         &mut self,
224         item: &'c ast::Item,
225         sub_mod: Module<'ast>,
226     ) -> Result<(), ModuleResolutionError> {
227         let old_directory = self.directory.clone();
228         let sub_mod_kind = self.peek_sub_mod(item, &sub_mod)?;
229         if let Some(sub_mod_kind) = sub_mod_kind {
230             self.insert_sub_mod(sub_mod_kind.clone())?;
231             self.visit_sub_mod_inner(sub_mod, sub_mod_kind)?;
232         }
233         self.directory = old_directory;
234         Ok(())
235     }
236
237     /// Inspect the given sub-module which we are about to visit and returns its kind.
238     fn peek_sub_mod(
239         &self,
240         item: &'c ast::Item,
241         sub_mod: &Module<'ast>,
242     ) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
243         if contains_skip(&item.attrs) {
244             return Ok(None);
245         }
246
247         if is_mod_decl(item) {
248             // mod foo;
249             // Look for an extern file.
250             self.find_external_module(item.ident, &item.attrs, sub_mod)
251         } else {
252             // An internal module (`mod foo { /* ... */ }`);
253             Ok(Some(SubModKind::Internal(item)))
254         }
255     }
256
257     fn insert_sub_mod(
258         &mut self,
259         sub_mod_kind: SubModKind<'c, 'ast>,
260     ) -> Result<(), ModuleResolutionError> {
261         match sub_mod_kind {
262             SubModKind::External(mod_path, _, sub_mod) => {
263                 self.file_map
264                     .entry(FileName::Real(mod_path))
265                     .or_insert(sub_mod);
266             }
267             SubModKind::MultiExternal(mods) => {
268                 for (mod_path, _, sub_mod) in mods {
269                     self.file_map
270                         .entry(FileName::Real(mod_path))
271                         .or_insert(sub_mod);
272                 }
273             }
274             _ => (),
275         }
276         Ok(())
277     }
278
279     fn visit_sub_mod_inner(
280         &mut self,
281         sub_mod: Module<'ast>,
282         sub_mod_kind: SubModKind<'c, 'ast>,
283     ) -> Result<(), ModuleResolutionError> {
284         match sub_mod_kind {
285             SubModKind::External(mod_path, directory_ownership, sub_mod) => {
286                 let directory = Directory {
287                     path: mod_path.parent().unwrap().to_path_buf(),
288                     ownership: directory_ownership,
289                 };
290                 self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))
291             }
292             SubModKind::Internal(item) => {
293                 self.push_inline_mod_directory(item.ident, &item.attrs);
294                 self.visit_sub_mod_after_directory_update(sub_mod, None)
295             }
296             SubModKind::MultiExternal(mods) => {
297                 for (mod_path, directory_ownership, sub_mod) in mods {
298                     let directory = Directory {
299                         path: mod_path.parent().unwrap().to_path_buf(),
300                         ownership: directory_ownership,
301                     };
302                     self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))?;
303                 }
304                 Ok(())
305             }
306         }
307     }
308
309     fn visit_sub_mod_after_directory_update(
310         &mut self,
311         sub_mod: Module<'ast>,
312         directory: Option<Directory>,
313     ) -> Result<(), ModuleResolutionError> {
314         if let Some(directory) = directory {
315             self.directory = directory;
316         }
317         match (sub_mod.ast_mod_kind, sub_mod.items) {
318             (Some(Cow::Borrowed(ast::ModKind::Loaded(items, _, _))), _) => {
319                 self.visit_mod_from_ast(items)
320             }
321             (Some(Cow::Owned(ast::ModKind::Loaded(items, _, _))), _) | (_, Cow::Owned(items)) => {
322                 self.visit_mod_outside_ast(items)
323             }
324             (_, _) => Ok(()),
325         }
326     }
327
328     /// Find a file path in the filesystem which corresponds to the given module.
329     fn find_external_module(
330         &self,
331         mod_name: symbol::Ident,
332         attrs: &[ast::Attribute],
333         sub_mod: &Module<'ast>,
334     ) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
335         let relative = match self.directory.ownership {
336             DirectoryOwnership::Owned { relative } => relative,
337             DirectoryOwnership::UnownedViaBlock => None,
338         };
339         if let Some(path) = Parser::submod_path_from_attr(attrs, &self.directory.path) {
340             if self.parse_sess.is_file_parsed(&path) {
341                 return Ok(None);
342             }
343             return match Parser::parse_file_as_module(self.parse_sess, &path, sub_mod.span) {
344                 Ok((ref attrs, _, _)) if contains_skip(attrs) => Ok(None),
345                 Ok((attrs, items, span)) => Ok(Some(SubModKind::External(
346                     path,
347                     DirectoryOwnership::Owned { relative: None },
348                     Module::new(
349                         span,
350                         Some(Cow::Owned(ast::ModKind::Unloaded)),
351                         Cow::Owned(items),
352                         Cow::Owned(attrs),
353                     ),
354                 ))),
355                 Err(ParserError::ParseError) => Err(ModuleResolutionError {
356                     module: mod_name.to_string(),
357                     kind: ModuleResolutionErrorKind::ParseError { file: path },
358                 }),
359                 Err(..) => Err(ModuleResolutionError {
360                     module: mod_name.to_string(),
361                     kind: ModuleResolutionErrorKind::NotFound { file: path },
362                 }),
363             };
364         }
365
366         // Look for nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`.
367         let mut mods_outside_ast = self.find_mods_outside_of_ast(attrs, sub_mod);
368
369         match self
370             .parse_sess
371             .default_submod_path(mod_name, relative, &self.directory.path)
372         {
373             Ok(ModulePathSuccess {
374                 file_path,
375                 dir_ownership,
376                 ..
377             }) => {
378                 let outside_mods_empty = mods_outside_ast.is_empty();
379                 let should_insert = !mods_outside_ast
380                     .iter()
381                     .any(|(outside_path, _, _)| outside_path == &file_path);
382                 if self.parse_sess.is_file_parsed(&file_path) {
383                     if outside_mods_empty {
384                         return Ok(None);
385                     } else {
386                         if should_insert {
387                             mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
388                         }
389                         return Ok(Some(SubModKind::MultiExternal(mods_outside_ast)));
390                     }
391                 }
392                 match Parser::parse_file_as_module(self.parse_sess, &file_path, sub_mod.span) {
393                     Ok((ref attrs, _, _)) if contains_skip(attrs) => Ok(None),
394                     Ok((attrs, items, span)) if outside_mods_empty => {
395                         Ok(Some(SubModKind::External(
396                             file_path,
397                             dir_ownership,
398                             Module::new(
399                                 span,
400                                 Some(Cow::Owned(ast::ModKind::Unloaded)),
401                                 Cow::Owned(items),
402                                 Cow::Owned(attrs),
403                             ),
404                         )))
405                     }
406                     Ok((attrs, items, span)) => {
407                         mods_outside_ast.push((
408                             file_path.clone(),
409                             dir_ownership,
410                             Module::new(
411                                 span,
412                                 Some(Cow::Owned(ast::ModKind::Unloaded)),
413                                 Cow::Owned(items),
414                                 Cow::Owned(attrs),
415                             ),
416                         ));
417                         if should_insert {
418                             mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
419                         }
420                         Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
421                     }
422                     Err(ParserError::ParseError) => Err(ModuleResolutionError {
423                         module: mod_name.to_string(),
424                         kind: ModuleResolutionErrorKind::ParseError { file: file_path },
425                     }),
426                     Err(..) if outside_mods_empty => Err(ModuleResolutionError {
427                         module: mod_name.to_string(),
428                         kind: ModuleResolutionErrorKind::NotFound { file: file_path },
429                     }),
430                     Err(..) => {
431                         if should_insert {
432                             mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
433                         }
434                         Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
435                     }
436                 }
437             }
438             Err(mod_err) if !mods_outside_ast.is_empty() => {
439                 if let ModError::ParserError(e) = mod_err {
440                     e.cancel();
441                 }
442                 Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
443             }
444             Err(e) => match e {
445                 ModError::FileNotFound(_, default_path, _secondary_path) => {
446                     Err(ModuleResolutionError {
447                         module: mod_name.to_string(),
448                         kind: ModuleResolutionErrorKind::NotFound { file: default_path },
449                     })
450                 }
451                 ModError::MultipleCandidates(_, default_path, secondary_path) => {
452                     Err(ModuleResolutionError {
453                         module: mod_name.to_string(),
454                         kind: ModuleResolutionErrorKind::MultipleCandidates {
455                             default_path,
456                             secondary_path,
457                         },
458                     })
459                 }
460                 ModError::ParserError(_)
461                 | ModError::CircularInclusion(_)
462                 | ModError::ModInBlock(_) => Err(ModuleResolutionError {
463                     module: mod_name.to_string(),
464                     kind: ModuleResolutionErrorKind::ParseError {
465                         file: self.directory.path.clone(),
466                     },
467                 }),
468             },
469         }
470     }
471
472     fn push_inline_mod_directory(&mut self, id: symbol::Ident, attrs: &[ast::Attribute]) {
473         if let Some(path) = find_path_value(attrs) {
474             self.directory.path.push(path.as_str());
475             self.directory.ownership = DirectoryOwnership::Owned { relative: None };
476         } else {
477             let id = id.as_str();
478             // We have to push on the current module name in the case of relative
479             // paths in order to ensure that any additional module paths from inline
480             // `mod x { ... }` come after the relative extension.
481             //
482             // For example, a `mod z { ... }` inside `x/y.rs` should set the current
483             // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`.
484             if let DirectoryOwnership::Owned { relative } = &mut self.directory.ownership {
485                 if let Some(ident) = relative.take() {
486                     // remove the relative offset
487                     self.directory.path.push(ident.as_str());
488
489                     // In the case where there is an x.rs and an ./x directory we want
490                     // to prevent adding x twice. For example, ./x/x
491                     if self.directory.path.exists() && !self.directory.path.join(id).exists() {
492                         return;
493                     }
494                 }
495             }
496             self.directory.path.push(id);
497         }
498     }
499
500     fn find_mods_outside_of_ast(
501         &self,
502         attrs: &[ast::Attribute],
503         sub_mod: &Module<'ast>,
504     ) -> Vec<(PathBuf, DirectoryOwnership, Module<'ast>)> {
505         // Filter nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`.
506         let mut path_visitor = visitor::PathVisitor::default();
507         for attr in attrs.iter() {
508             if let Some(meta) = attr.meta() {
509                 path_visitor.visit_meta_item(&meta)
510             }
511         }
512         let mut result = vec![];
513         for path in path_visitor.paths() {
514             let mut actual_path = self.directory.path.clone();
515             actual_path.push(&path);
516             if !actual_path.exists() {
517                 continue;
518             }
519             if self.parse_sess.is_file_parsed(&actual_path) {
520                 // If the specified file is already parsed, then we just use that.
521                 result.push((
522                     actual_path,
523                     DirectoryOwnership::Owned { relative: None },
524                     sub_mod.clone(),
525                 ));
526                 continue;
527             }
528             let (attrs, items, span) =
529                 match Parser::parse_file_as_module(self.parse_sess, &actual_path, sub_mod.span) {
530                     Ok((ref attrs, _, _)) if contains_skip(attrs) => continue,
531                     Ok(m) => m,
532                     Err(..) => continue,
533                 };
534
535             result.push((
536                 actual_path,
537                 DirectoryOwnership::Owned { relative: None },
538                 Module::new(
539                     span,
540                     Some(Cow::Owned(ast::ModKind::Unloaded)),
541                     Cow::Owned(items),
542                     Cow::Owned(attrs),
543                 ),
544             ))
545         }
546         result
547     }
548 }
549
550 fn path_value(attr: &ast::Attribute) -> Option<Symbol> {
551     if attr.has_name(sym::path) {
552         attr.value_str()
553     } else {
554         None
555     }
556 }
557
558 // N.B., even when there are multiple `#[path = ...]` attributes, we just need to
559 // examine the first one, since rustc ignores the second and the subsequent ones
560 // as unused attributes.
561 fn find_path_value(attrs: &[ast::Attribute]) -> Option<Symbol> {
562     attrs.iter().flat_map(path_value).next()
563 }
564
565 fn is_cfg_if(item: &ast::Item) -> bool {
566     match item.kind {
567         ast::ItemKind::MacCall(ref mac) => {
568             if let Some(first_segment) = mac.path.segments.first() {
569                 if first_segment.ident.name == Symbol::intern("cfg_if") {
570                     return true;
571                 }
572             }
573             false
574         }
575         _ => false,
576     }
577 }