Skip to main content

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_custom_attributes, 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<Box<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<Box<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    fn visit_cfg_select(
171        &mut self,
172        item: Cow<'ast, ast::Item>,
173    ) -> Result<(), ModuleResolutionError> {
174        let mut visitor = visitor::CfgSelectVisitor::new(self.psess);
175        visitor.visit_item(&item);
176        for module_item in visitor.mods() {
177            if let ast::ItemKind::Mod(_, _, ref sub_mod_kind) = module_item.item.kind {
178                self.visit_sub_mod(
179                    &module_item.item,
180                    Module::new(
181                        module_item.item.span,
182                        Some(Cow::Owned(sub_mod_kind.clone())),
183                        Cow::Owned(ThinVec::new()),
184                        Cow::Owned(ast::AttrVec::new()),
185                    ),
186                )?;
187            }
188        }
189        Ok(())
190    }
191
192    /// Visit modules defined inside macro calls.
193    fn visit_mod_outside_ast(
194        &mut self,
195        items: ThinVec<Box<ast::Item>>,
196    ) -> Result<(), ModuleResolutionError> {
197        for item in items {
198            if is_cfg_if(&item) {
199                self.visit_cfg_if(Cow::Owned(*item))?;
200                continue;
201            }
202
203            if is_cfg_select(&item) {
204                self.visit_cfg_select(Cow::Owned(*item))?;
205                continue;
206            }
207
208            if let ast::ItemKind::Mod(_, _, ref sub_mod_kind) = item.kind {
209                let span = item.span;
210                self.visit_sub_mod(
211                    &item,
212                    Module::new(
213                        span,
214                        Some(Cow::Owned(sub_mod_kind.clone())),
215                        Cow::Owned(ThinVec::new()),
216                        Cow::Owned(ast::AttrVec::new()),
217                    ),
218                )?;
219            }
220        }
221        Ok(())
222    }
223
224    /// Visit modules from AST.
225    fn visit_mod_from_ast(
226        &mut self,
227        items: &'ast [Box<ast::Item>],
228    ) -> Result<(), ModuleResolutionError> {
229        for item in items {
230            if is_cfg_if(item) {
231                self.visit_cfg_if(Cow::Borrowed(item))?;
232            }
233
234            if is_cfg_select(item) {
235                self.visit_cfg_select(Cow::Borrowed(item))?;
236            }
237
238            if let ast::ItemKind::Mod(_, _, ref sub_mod_kind) = item.kind {
239                let span = item.span;
240                self.visit_sub_mod(
241                    item,
242                    Module::new(
243                        span,
244                        Some(Cow::Borrowed(sub_mod_kind)),
245                        Cow::Owned(ThinVec::new()),
246                        Cow::Borrowed(&item.attrs),
247                    ),
248                )?;
249            }
250        }
251        Ok(())
252    }
253
254    fn visit_sub_mod(
255        &mut self,
256        item: &'c ast::Item,
257        sub_mod: Module<'ast>,
258    ) -> Result<(), ModuleResolutionError> {
259        let old_directory = self.directory.clone();
260        let sub_mod_kind = self.peek_sub_mod(item, &sub_mod)?;
261        if let Some(sub_mod_kind) = sub_mod_kind {
262            self.insert_sub_mod(sub_mod_kind.clone())?;
263            self.visit_sub_mod_inner(sub_mod, sub_mod_kind)?;
264        }
265        self.directory = old_directory;
266        Ok(())
267    }
268
269    /// Inspect the given sub-module which we are about to visit and returns its kind.
270    fn peek_sub_mod(
271        &self,
272        item: &'c ast::Item,
273        sub_mod: &Module<'ast>,
274    ) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
275        if contains_skip(&item.attrs) {
276            return Ok(None);
277        }
278
279        if is_mod_decl(item) {
280            // mod foo;
281            // Look for an extern file.
282            self.find_external_module(item.kind.ident().unwrap(), &item.attrs, sub_mod)
283        } else {
284            // An internal module (`mod foo { /* ... */ }`);
285            Ok(Some(SubModKind::Internal(item)))
286        }
287    }
288
289    fn insert_sub_mod(
290        &mut self,
291        sub_mod_kind: SubModKind<'c, 'ast>,
292    ) -> Result<(), ModuleResolutionError> {
293        match sub_mod_kind {
294            SubModKind::External(mod_path, _, sub_mod) => {
295                self.file_map
296                    .entry(FileName::Real(mod_path))
297                    .or_insert(sub_mod);
298            }
299            SubModKind::MultiExternal(mods) => {
300                for (mod_path, _, sub_mod) in mods {
301                    self.file_map
302                        .entry(FileName::Real(mod_path))
303                        .or_insert(sub_mod);
304                }
305            }
306            _ => (),
307        }
308        Ok(())
309    }
310
311    fn visit_sub_mod_inner(
312        &mut self,
313        sub_mod: Module<'ast>,
314        sub_mod_kind: SubModKind<'c, 'ast>,
315    ) -> Result<(), ModuleResolutionError> {
316        match sub_mod_kind {
317            SubModKind::External(mod_path, directory_ownership, sub_mod) => {
318                let directory = Directory {
319                    path: mod_path.parent().unwrap().to_path_buf(),
320                    ownership: directory_ownership,
321                };
322                self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))
323            }
324            SubModKind::Internal(item) => {
325                self.push_inline_mod_directory(item.kind.ident().unwrap(), &item.attrs);
326                self.visit_sub_mod_after_directory_update(sub_mod, None)
327            }
328            SubModKind::MultiExternal(mods) => {
329                for (mod_path, directory_ownership, sub_mod) in mods {
330                    let directory = Directory {
331                        path: mod_path.parent().unwrap().to_path_buf(),
332                        ownership: directory_ownership,
333                    };
334                    self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))?;
335                }
336                Ok(())
337            }
338        }
339    }
340
341    fn visit_sub_mod_after_directory_update(
342        &mut self,
343        sub_mod: Module<'ast>,
344        directory: Option<Directory>,
345    ) -> Result<(), ModuleResolutionError> {
346        if let Some(directory) = directory {
347            self.directory = directory;
348        }
349        match (sub_mod.ast_mod_kind, sub_mod.items) {
350            (Some(Cow::Borrowed(ast::ModKind::Loaded(items, _, _))), _) => {
351                self.visit_mod_from_ast(items)
352            }
353            (Some(Cow::Owned(ast::ModKind::Loaded(items, _, _))), _) | (_, Cow::Owned(items)) => {
354                self.visit_mod_outside_ast(items)
355            }
356            (_, _) => Ok(()),
357        }
358    }
359
360    /// Find a file path in the filesystem which corresponds to the given module.
361    fn find_external_module(
362        &self,
363        mod_name: symbol::Ident,
364        attrs: &[ast::Attribute],
365        sub_mod: &Module<'ast>,
366    ) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
367        let relative = match self.directory.ownership {
368            DirectoryOwnership::Owned { relative } => relative,
369            DirectoryOwnership::UnownedViaBlock => None,
370        };
371        if let Some(path) = Parser::submod_path_from_attr(attrs, &self.directory.path) {
372            if self.psess.is_file_parsed(&path) {
373                return Ok(None);
374            }
375            return match Parser::parse_file_as_module(self.psess, &path, sub_mod.span) {
376                Ok((ref attrs, _, _)) if contains_skip(attrs) => Ok(None),
377                Ok((attrs, items, span)) => Ok(Some(SubModKind::External(
378                    path,
379                    DirectoryOwnership::Owned { relative: None },
380                    Module::new(
381                        span,
382                        Some(Cow::Owned(ast::ModKind::Unloaded)),
383                        Cow::Owned(items),
384                        Cow::Owned(attrs),
385                    ),
386                ))),
387                Err(ParserError::ParseError) => Err(ModuleResolutionError {
388                    module: mod_name.to_string(),
389                    kind: ModuleResolutionErrorKind::ParseError { file: path },
390                }),
391                Err(..) => Err(ModuleResolutionError {
392                    module: mod_name.to_string(),
393                    kind: ModuleResolutionErrorKind::NotFound { file: path },
394                }),
395            };
396        }
397
398        // Look for nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`.
399        let mut mods_outside_ast = self.find_mods_outside_of_ast(attrs, sub_mod);
400
401        match self
402            .psess
403            .default_submod_path(mod_name, relative, &self.directory.path)
404        {
405            Ok(ModulePathSuccess {
406                file_path,
407                dir_ownership,
408                ..
409            }) => {
410                let outside_mods_empty = mods_outside_ast.is_empty();
411                let should_insert = !mods_outside_ast
412                    .iter()
413                    .any(|(outside_path, _, _)| outside_path == &file_path);
414                if self.psess.is_file_parsed(&file_path) {
415                    if outside_mods_empty {
416                        return Ok(None);
417                    } else {
418                        if should_insert {
419                            mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
420                        }
421                        return Ok(Some(SubModKind::MultiExternal(mods_outside_ast)));
422                    }
423                }
424                match Parser::parse_file_as_module(self.psess, &file_path, sub_mod.span) {
425                    Ok((ref attrs, _, _)) if contains_skip(attrs) => Ok(None),
426                    Ok((attrs, items, span)) if outside_mods_empty => {
427                        Ok(Some(SubModKind::External(
428                            file_path,
429                            dir_ownership,
430                            Module::new(
431                                span,
432                                Some(Cow::Owned(ast::ModKind::Unloaded)),
433                                Cow::Owned(items),
434                                Cow::Owned(attrs),
435                            ),
436                        )))
437                    }
438                    Ok((attrs, items, span)) => {
439                        mods_outside_ast.push((
440                            file_path.clone(),
441                            dir_ownership,
442                            Module::new(
443                                span,
444                                Some(Cow::Owned(ast::ModKind::Unloaded)),
445                                Cow::Owned(items),
446                                Cow::Owned(attrs),
447                            ),
448                        ));
449                        if should_insert {
450                            mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
451                        }
452                        Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
453                    }
454                    Err(ParserError::ParseError) => Err(ModuleResolutionError {
455                        module: mod_name.to_string(),
456                        kind: ModuleResolutionErrorKind::ParseError { file: file_path },
457                    }),
458                    Err(..) if outside_mods_empty => Err(ModuleResolutionError {
459                        module: mod_name.to_string(),
460                        kind: ModuleResolutionErrorKind::NotFound { file: file_path },
461                    }),
462                    Err(..) => {
463                        if should_insert {
464                            mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
465                        }
466                        Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
467                    }
468                }
469            }
470            Err(mod_err) if !mods_outside_ast.is_empty() => {
471                if let ModError::ParserError(e) = mod_err {
472                    e.cancel();
473                }
474                Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
475            }
476            Err(e) => match e {
477                ModError::FileNotFound(_, default_path, _secondary_path) => {
478                    if contains_custom_attributes(attrs) {
479                        // It's possible that at least one of the attributes is a custom proc macro
480                        // that takes the module tokens as an input. It's hard to know for sure
481                        // since rustfmt only operates on the AST pre-expansion. In this case we'll
482                        // be overly permissive and just ignore the file not found error so rustfmt
483                        // can still try formatting the input.
484                        tracing::warn!("Couldn't find file for mod {};`", mod_name.to_string());
485                        return Ok(None);
486                    }
487
488                    Err(ModuleResolutionError {
489                        module: mod_name.to_string(),
490                        kind: ModuleResolutionErrorKind::NotFound { file: default_path },
491                    })
492                }
493                ModError::MultipleCandidates(_, default_path, secondary_path) => {
494                    Err(ModuleResolutionError {
495                        module: mod_name.to_string(),
496                        kind: ModuleResolutionErrorKind::MultipleCandidates {
497                            default_path,
498                            secondary_path,
499                        },
500                    })
501                }
502                ModError::ParserError(_)
503                | ModError::CircularInclusion(_)
504                | ModError::ModInBlock(_) => Err(ModuleResolutionError {
505                    module: mod_name.to_string(),
506                    kind: ModuleResolutionErrorKind::ParseError {
507                        file: self.directory.path.clone(),
508                    },
509                }),
510            },
511        }
512    }
513
514    fn push_inline_mod_directory(&mut self, id: symbol::Ident, attrs: &[ast::Attribute]) {
515        if let Some(path) = find_path_value(attrs) {
516            self.directory.path.push(path.as_str());
517            self.directory.ownership = DirectoryOwnership::Owned { relative: None };
518        } else {
519            let id = id.as_str();
520            // We have to push on the current module name in the case of relative
521            // paths in order to ensure that any additional module paths from inline
522            // `mod x { ... }` come after the relative extension.
523            //
524            // For example, a `mod z { ... }` inside `x/y.rs` should set the current
525            // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`.
526            if let DirectoryOwnership::Owned { relative } = &mut self.directory.ownership {
527                if let Some(ident) = relative.take() {
528                    // remove the relative offset
529                    self.directory.path.push(ident.as_str());
530
531                    // In the case where there is an x.rs and an ./x directory we want
532                    // to prevent adding x twice. For example, ./x/x
533                    if self.directory.path.exists() && !self.directory.path.join(id).exists() {
534                        return;
535                    }
536                }
537            }
538            self.directory.path.push(id);
539        }
540    }
541
542    fn find_mods_outside_of_ast(
543        &self,
544        attrs: &[ast::Attribute],
545        sub_mod: &Module<'ast>,
546    ) -> Vec<(PathBuf, DirectoryOwnership, Module<'ast>)> {
547        // Filter nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`.
548        let mut path_visitor = visitor::PathVisitor::default();
549        for attr in attrs.iter() {
550            if let Some(meta) = attr.meta() {
551                path_visitor.visit_meta_item(&meta)
552            }
553        }
554        let mut result = vec![];
555        for path in path_visitor.paths() {
556            let mut actual_path = self.directory.path.clone();
557            actual_path.push(&path);
558            if !actual_path.exists() {
559                continue;
560            }
561            if self.psess.is_file_parsed(&actual_path) {
562                // If the specified file is already parsed, then we just use that.
563                result.push((
564                    actual_path,
565                    DirectoryOwnership::Owned { relative: None },
566                    sub_mod.clone(),
567                ));
568                continue;
569            }
570            let (attrs, items, span) =
571                match Parser::parse_file_as_module(self.psess, &actual_path, sub_mod.span) {
572                    Ok((ref attrs, _, _)) if contains_skip(attrs) => continue,
573                    Ok(m) => m,
574                    Err(..) => continue,
575                };
576
577            result.push((
578                actual_path,
579                DirectoryOwnership::Owned { relative: None },
580                Module::new(
581                    span,
582                    Some(Cow::Owned(ast::ModKind::Unloaded)),
583                    Cow::Owned(items),
584                    Cow::Owned(attrs),
585                ),
586            ));
587        }
588        result
589    }
590}
591
592fn path_value(attr: &ast::Attribute) -> Option<Symbol> {
593    if attr.has_name(sym::path) {
594        attr.value_str()
595    } else {
596        None
597    }
598}
599
600// N.B., even when there are multiple `#[path = ...]` attributes, we just need to
601// examine the first one, since rustc ignores the second and the subsequent ones
602// as unused attributes.
603fn find_path_value(attrs: &[ast::Attribute]) -> Option<Symbol> {
604    attrs.iter().flat_map(path_value).next()
605}
606
607fn is_cfg_if(item: &ast::Item) -> bool {
608    match item.kind {
609        ast::ItemKind::MacCall(ref mac) => {
610            if let Some(first_segment) = mac.path.segments.first() {
611                if first_segment.ident.name == Symbol::intern("cfg_if") {
612                    return true;
613                }
614            }
615            false
616        }
617        _ => false,
618    }
619}
620
621fn is_cfg_select(item: &ast::Item) -> bool {
622    match item.kind {
623        ast::ItemKind::MacCall(ref mac) => {
624            if let Some(last_segment) = mac.path.segments.last() {
625                if last_segment.ident.name == Symbol::intern("cfg_select") {
626                    return true;
627                }
628            }
629            false
630        }
631        _ => false,
632    }
633}