rustfmt_nightly/
modules.rs

1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::path::{Path, PathBuf};
4
5use rustc_ast::ast;
6use rustc_ast::visit::Visitor;
7use rustc_span::Span;
8use rustc_span::symbol::{self, Symbol, sym};
9use thin_vec::ThinVec;
10use thiserror::Error;
11
12use crate::attr::MetaVisitor;
13use crate::config::FileName;
14use crate::items::is_mod_decl;
15use crate::parse::parser::{
16    Directory, DirectoryOwnership, ModError, ModulePathSuccess, Parser, ParserError,
17};
18use crate::parse::session::ParseSess;
19use crate::utils::{contains_skip, mk_sp};
20
21mod visitor;
22
23type FileModMap<'ast> = BTreeMap<FileName, Module<'ast>>;
24
25/// Represents module with its inner attributes.
26#[derive(Debug, Clone)]
27pub(crate) struct Module<'a> {
28    ast_mod_kind: Option<Cow<'a, ast::ModKind>>,
29    pub(crate) items: Cow<'a, ThinVec<rustc_ast::ptr::P<ast::Item>>>,
30    inner_attr: ast::AttrVec,
31    pub(crate) span: Span,
32}
33
34impl<'a> Module<'a> {
35    pub(crate) fn new(
36        mod_span: Span,
37        ast_mod_kind: Option<Cow<'a, ast::ModKind>>,
38        mod_items: Cow<'a, ThinVec<rustc_ast::ptr::P<ast::Item>>>,
39        mod_attrs: Cow<'a, ast::AttrVec>,
40    ) -> Self {
41        let inner_attr = mod_attrs
42            .iter()
43            .filter(|attr| attr.style == ast::AttrStyle::Inner)
44            .cloned()
45            .collect();
46        Module {
47            items: mod_items,
48            inner_attr,
49            span: mod_span,
50            ast_mod_kind,
51        }
52    }
53
54    pub(crate) fn attrs(&self) -> &[ast::Attribute] {
55        &self.inner_attr
56    }
57}
58
59/// Maps each module to the corresponding file.
60pub(crate) struct ModResolver<'ast, 'psess> {
61    psess: &'psess ParseSess,
62    directory: Directory,
63    file_map: FileModMap<'ast>,
64    recursive: bool,
65}
66
67/// Represents errors while trying to resolve modules.
68#[derive(Debug, Error)]
69#[error("failed to resolve mod `{module}`: {kind}")]
70pub struct ModuleResolutionError {
71    pub(crate) module: String,
72    pub(crate) kind: ModuleResolutionErrorKind,
73}
74
75/// Defines variants similar to those of [rustc_expand::module::ModError]
76#[derive(Debug, Error)]
77pub(crate) enum ModuleResolutionErrorKind {
78    /// Find a file that cannot be parsed.
79    #[error("cannot parse {file}")]
80    ParseError { file: PathBuf },
81    /// File cannot be found.
82    #[error("{file} does not exist")]
83    NotFound { file: PathBuf },
84    /// File a.rs and a/mod.rs both exist
85    #[error("file for module found at both {default_path:?} and {secondary_path:?}")]
86    MultipleCandidates {
87        default_path: PathBuf,
88        secondary_path: PathBuf,
89    },
90}
91
92#[derive(Clone)]
93enum SubModKind<'a, 'ast> {
94    /// `mod foo;`
95    External(PathBuf, DirectoryOwnership, Module<'ast>),
96    /// `mod foo;` with multiple sources.
97    MultiExternal(Vec<(PathBuf, DirectoryOwnership, Module<'ast>)>),
98    /// `mod foo {}`
99    Internal(&'a ast::Item),
100}
101
102impl<'ast, 'psess, 'c> ModResolver<'ast, 'psess> {
103    /// Creates a new `ModResolver`.
104    pub(crate) fn new(
105        psess: &'psess ParseSess,
106        directory_ownership: DirectoryOwnership,
107        recursive: bool,
108    ) -> Self {
109        ModResolver {
110            directory: Directory {
111                path: PathBuf::new(),
112                ownership: directory_ownership,
113            },
114            file_map: BTreeMap::new(),
115            psess,
116            recursive,
117        }
118    }
119
120    /// Creates a map that maps a file name to the module in AST.
121    pub(crate) fn visit_crate(
122        mut self,
123        krate: &'ast ast::Crate,
124    ) -> Result<FileModMap<'ast>, ModuleResolutionError> {
125        let root_filename = self.psess.span_to_filename(krate.spans.inner_span);
126        self.directory.path = match root_filename {
127            FileName::Real(ref p) => p.parent().unwrap_or(Path::new("")).to_path_buf(),
128            _ => PathBuf::new(),
129        };
130
131        // Skip visiting sub modules when the input is from stdin.
132        if self.recursive {
133            self.visit_mod_from_ast(&krate.items)?;
134        }
135
136        let snippet_provider = self.psess.snippet_provider(krate.spans.inner_span);
137
138        self.file_map.insert(
139            root_filename,
140            Module::new(
141                mk_sp(snippet_provider.start_pos(), snippet_provider.end_pos()),
142                None,
143                Cow::Borrowed(&krate.items),
144                Cow::Borrowed(&krate.attrs),
145            ),
146        );
147        Ok(self.file_map)
148    }
149
150    /// Visit `cfg_if` macro and look for module declarations.
151    fn visit_cfg_if(&mut self, item: Cow<'ast, ast::Item>) -> Result<(), ModuleResolutionError> {
152        let mut visitor = visitor::CfgIfVisitor::new(self.psess);
153        visitor.visit_item(&item);
154        for module_item in visitor.mods() {
155            if let ast::ItemKind::Mod(_, ref sub_mod_kind) = module_item.item.kind {
156                self.visit_sub_mod(
157                    &module_item.item,
158                    Module::new(
159                        module_item.item.span,
160                        Some(Cow::Owned(sub_mod_kind.clone())),
161                        Cow::Owned(ThinVec::new()),
162                        Cow::Owned(ast::AttrVec::new()),
163                    ),
164                )?;
165            }
166        }
167        Ok(())
168    }
169
170    /// Visit modules defined inside macro calls.
171    fn visit_mod_outside_ast(
172        &mut self,
173        items: ThinVec<rustc_ast::ptr::P<ast::Item>>,
174    ) -> Result<(), ModuleResolutionError> {
175        for item in items {
176            if is_cfg_if(&item) {
177                self.visit_cfg_if(Cow::Owned(item.into_inner()))?;
178                continue;
179            }
180
181            if let ast::ItemKind::Mod(_, ref sub_mod_kind) = item.kind {
182                let span = item.span;
183                self.visit_sub_mod(
184                    &item,
185                    Module::new(
186                        span,
187                        Some(Cow::Owned(sub_mod_kind.clone())),
188                        Cow::Owned(ThinVec::new()),
189                        Cow::Owned(ast::AttrVec::new()),
190                    ),
191                )?;
192            }
193        }
194        Ok(())
195    }
196
197    /// Visit modules from AST.
198    fn visit_mod_from_ast(
199        &mut self,
200        items: &'ast [rustc_ast::ptr::P<ast::Item>],
201    ) -> Result<(), ModuleResolutionError> {
202        for item in items {
203            if is_cfg_if(item) {
204                self.visit_cfg_if(Cow::Borrowed(item))?;
205            }
206
207            if let ast::ItemKind::Mod(_, ref sub_mod_kind) = item.kind {
208                let span = item.span;
209                self.visit_sub_mod(
210                    item,
211                    Module::new(
212                        span,
213                        Some(Cow::Borrowed(sub_mod_kind)),
214                        Cow::Owned(ThinVec::new()),
215                        Cow::Borrowed(&item.attrs),
216                    ),
217                )?;
218            }
219        }
220        Ok(())
221    }
222
223    fn visit_sub_mod(
224        &mut self,
225        item: &'c ast::Item,
226        sub_mod: Module<'ast>,
227    ) -> Result<(), ModuleResolutionError> {
228        let old_directory = self.directory.clone();
229        let sub_mod_kind = self.peek_sub_mod(item, &sub_mod)?;
230        if let Some(sub_mod_kind) = sub_mod_kind {
231            self.insert_sub_mod(sub_mod_kind.clone())?;
232            self.visit_sub_mod_inner(sub_mod, sub_mod_kind)?;
233        }
234        self.directory = old_directory;
235        Ok(())
236    }
237
238    /// Inspect the given sub-module which we are about to visit and returns its kind.
239    fn peek_sub_mod(
240        &self,
241        item: &'c ast::Item,
242        sub_mod: &Module<'ast>,
243    ) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
244        if contains_skip(&item.attrs) {
245            return Ok(None);
246        }
247
248        if is_mod_decl(item) {
249            // mod foo;
250            // Look for an extern file.
251            self.find_external_module(item.ident, &item.attrs, sub_mod)
252        } else {
253            // An internal module (`mod foo { /* ... */ }`);
254            Ok(Some(SubModKind::Internal(item)))
255        }
256    }
257
258    fn insert_sub_mod(
259        &mut self,
260        sub_mod_kind: SubModKind<'c, 'ast>,
261    ) -> Result<(), ModuleResolutionError> {
262        match sub_mod_kind {
263            SubModKind::External(mod_path, _, sub_mod) => {
264                self.file_map
265                    .entry(FileName::Real(mod_path))
266                    .or_insert(sub_mod);
267            }
268            SubModKind::MultiExternal(mods) => {
269                for (mod_path, _, sub_mod) in mods {
270                    self.file_map
271                        .entry(FileName::Real(mod_path))
272                        .or_insert(sub_mod);
273                }
274            }
275            _ => (),
276        }
277        Ok(())
278    }
279
280    fn visit_sub_mod_inner(
281        &mut self,
282        sub_mod: Module<'ast>,
283        sub_mod_kind: SubModKind<'c, 'ast>,
284    ) -> Result<(), ModuleResolutionError> {
285        match sub_mod_kind {
286            SubModKind::External(mod_path, directory_ownership, sub_mod) => {
287                let directory = Directory {
288                    path: mod_path.parent().unwrap().to_path_buf(),
289                    ownership: directory_ownership,
290                };
291                self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))
292            }
293            SubModKind::Internal(item) => {
294                self.push_inline_mod_directory(item.ident, &item.attrs);
295                self.visit_sub_mod_after_directory_update(sub_mod, None)
296            }
297            SubModKind::MultiExternal(mods) => {
298                for (mod_path, directory_ownership, sub_mod) in mods {
299                    let directory = Directory {
300                        path: mod_path.parent().unwrap().to_path_buf(),
301                        ownership: directory_ownership,
302                    };
303                    self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))?;
304                }
305                Ok(())
306            }
307        }
308    }
309
310    fn visit_sub_mod_after_directory_update(
311        &mut self,
312        sub_mod: Module<'ast>,
313        directory: Option<Directory>,
314    ) -> Result<(), ModuleResolutionError> {
315        if let Some(directory) = directory {
316            self.directory = directory;
317        }
318        match (sub_mod.ast_mod_kind, sub_mod.items) {
319            (Some(Cow::Borrowed(ast::ModKind::Loaded(items, _, _, _))), _) => {
320                self.visit_mod_from_ast(items)
321            }
322            (Some(Cow::Owned(ast::ModKind::Loaded(items, _, _, _))), _)
323            | (_, Cow::Owned(items)) => self.visit_mod_outside_ast(items),
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.psess.is_file_parsed(&path) {
341                return Ok(None);
342            }
343            return match Parser::parse_file_as_module(self.psess, &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            .psess
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.psess.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.psess, &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.psess.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.psess, &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
550fn 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.
561fn find_path_value(attrs: &[ast::Attribute]) -> Option<Symbol> {
562    attrs.iter().flat_map(path_value).next()
563}
564
565fn 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}