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