2 use std::collections::BTreeMap;
3 use std::path::{Path, PathBuf};
6 use rustc_ast::visit::Visitor;
7 use rustc_span::symbol::{self, sym, Symbol};
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,
17 use crate::parse::session::ParseSess;
18 use crate::utils::{contains_skip, mk_sp};
22 type FileModMap<'ast> = BTreeMap<FileName, Module<'ast>>;
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: Vec<ast::Attribute>,
30 pub(crate) 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, Vec<ast::Attribute>>,
40 let inner_attr = mod_attrs
42 .filter(|attr| attr.style == ast::AttrStyle::Inner)
53 pub(crate) fn attrs(&self) -> &[ast::Attribute] {
58 /// Maps each module to the corresponding file.
59 pub(crate) struct ModResolver<'ast, 'sess> {
60 parse_sess: &'sess ParseSess,
62 file_map: FileModMap<'ast>,
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,
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:?}")]
86 default_path: PathBuf,
87 secondary_path: PathBuf,
92 enum SubModKind<'a, 'ast> {
94 External(PathBuf, DirectoryOwnership, Module<'ast>),
95 /// `mod foo;` with multiple sources.
96 MultiExternal(Vec<(PathBuf, DirectoryOwnership, Module<'ast>)>),
98 Internal(&'a ast::Item),
101 impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
102 /// Creates a new `ModResolver`.
104 parse_sess: &'sess ParseSess,
105 directory_ownership: DirectoryOwnership,
109 directory: Directory {
110 path: PathBuf::new(),
111 ownership: directory_ownership,
113 file_map: BTreeMap::new(),
119 /// Creates a map that maps a file name to the module in AST.
120 pub(crate) fn visit_crate(
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(),
130 // Skip visiting sub modules when the input is from stdin.
132 self.visit_mod_from_ast(&krate.items)?;
135 let snippet_provider = self.parse_sess.snippet_provider(krate.spans.inner_span);
137 self.file_map.insert(
140 mk_sp(snippet_provider.start_pos(), snippet_provider.end_pos()),
142 Cow::Borrowed(&krate.items),
143 Cow::Borrowed(&krate.attrs),
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 {
158 module_item.item.span,
159 Some(Cow::Owned(sub_mod_kind.clone())),
169 /// Visit modules defined inside macro calls.
170 fn visit_mod_outside_ast(
172 items: Vec<rustc_ast::ptr::P<ast::Item>>,
173 ) -> Result<(), ModuleResolutionError> {
175 if is_cfg_if(&item) {
176 self.visit_cfg_if(Cow::Owned(item.into_inner()))?;
180 if let ast::ItemKind::Mod(_, ref sub_mod_kind) = item.kind {
181 let span = item.span;
186 Some(Cow::Owned(sub_mod_kind.clone())),
196 /// Visit modules from AST.
197 fn visit_mod_from_ast(
199 items: &'ast [rustc_ast::ptr::P<ast::Item>],
200 ) -> Result<(), ModuleResolutionError> {
203 self.visit_cfg_if(Cow::Borrowed(item))?;
206 if let ast::ItemKind::Mod(_, ref sub_mod_kind) = item.kind {
207 let span = item.span;
212 Some(Cow::Borrowed(sub_mod_kind)),
214 Cow::Borrowed(&item.attrs),
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)?;
233 self.directory = old_directory;
237 /// Inspect the given sub-module which we are about to visit and returns its kind.
241 sub_mod: &Module<'ast>,
242 ) -> Result<Option<SubModKind<'c, 'ast>>, ModuleResolutionError> {
243 if contains_skip(&item.attrs) {
247 if is_mod_decl(item) {
249 // Look for an extern file.
250 self.find_external_module(item.ident, &item.attrs, sub_mod)
252 // An internal module (`mod foo { /* ... */ }`);
253 Ok(Some(SubModKind::Internal(item)))
259 sub_mod_kind: SubModKind<'c, 'ast>,
260 ) -> Result<(), ModuleResolutionError> {
262 SubModKind::External(mod_path, _, sub_mod) => {
264 .entry(FileName::Real(mod_path))
267 SubModKind::MultiExternal(mods) => {
268 for (mod_path, _, sub_mod) in mods {
270 .entry(FileName::Real(mod_path))
279 fn visit_sub_mod_inner(
281 sub_mod: Module<'ast>,
282 sub_mod_kind: SubModKind<'c, 'ast>,
283 ) -> Result<(), ModuleResolutionError> {
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,
290 self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))
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)
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,
302 self.visit_sub_mod_after_directory_update(sub_mod, Some(directory))?;
309 fn visit_sub_mod_after_directory_update(
311 sub_mod: Module<'ast>,
312 directory: Option<Directory>,
313 ) -> Result<(), ModuleResolutionError> {
314 if let Some(directory) = directory {
315 self.directory = directory;
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)
321 (Some(Cow::Owned(ast::ModKind::Loaded(items, _, _))), _) | (_, Cow::Owned(items)) => {
322 self.visit_mod_outside_ast(items)
328 /// Find a file path in the filesystem which corresponds to the given module.
329 fn find_external_module(
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,
339 if let Some(path) = Parser::submod_path_from_attr(attrs, &self.directory.path) {
340 if self.parse_sess.is_file_parsed(&path) {
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(
347 DirectoryOwnership::Owned { relative: None },
350 Some(Cow::Owned(ast::ModKind::Unloaded)),
355 Err(ParserError::ParseError) => Err(ModuleResolutionError {
356 module: mod_name.to_string(),
357 kind: ModuleResolutionErrorKind::ParseError { file: path },
359 Err(..) => Err(ModuleResolutionError {
360 module: mod_name.to_string(),
361 kind: ModuleResolutionErrorKind::NotFound { file: path },
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);
371 .default_submod_path(mod_name, relative, &self.directory.path)
373 Ok(ModulePathSuccess {
378 let outside_mods_empty = mods_outside_ast.is_empty();
379 let should_insert = !mods_outside_ast
381 .any(|(outside_path, _, _)| outside_path == &file_path);
382 if self.parse_sess.is_file_parsed(&file_path) {
383 if outside_mods_empty {
387 mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
389 return Ok(Some(SubModKind::MultiExternal(mods_outside_ast)));
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(
400 Some(Cow::Owned(ast::ModKind::Unloaded)),
406 Ok((attrs, items, span)) => {
407 mods_outside_ast.push((
412 Some(Cow::Owned(ast::ModKind::Unloaded)),
418 mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
420 Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
422 Err(ParserError::ParseError) => Err(ModuleResolutionError {
423 module: mod_name.to_string(),
424 kind: ModuleResolutionErrorKind::ParseError { file: file_path },
426 Err(..) if outside_mods_empty => Err(ModuleResolutionError {
427 module: mod_name.to_string(),
428 kind: ModuleResolutionErrorKind::NotFound { file: file_path },
432 mods_outside_ast.push((file_path, dir_ownership, sub_mod.clone()));
434 Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
438 Err(mod_err) if !mods_outside_ast.is_empty() => {
439 if let ModError::ParserError(e) = mod_err {
442 Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
445 ModError::FileNotFound(_, default_path, _secondary_path) => {
446 Err(ModuleResolutionError {
447 module: mod_name.to_string(),
448 kind: ModuleResolutionErrorKind::NotFound { file: default_path },
451 ModError::MultipleCandidates(_, default_path, secondary_path) => {
452 Err(ModuleResolutionError {
453 module: mod_name.to_string(),
454 kind: ModuleResolutionErrorKind::MultipleCandidates {
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(),
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 };
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.
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());
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() {
496 self.directory.path.push(id);
500 fn find_mods_outside_of_ast(
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)
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() {
519 if self.parse_sess.is_file_parsed(&actual_path) {
520 // If the specified file is already parsed, then we just use that.
523 DirectoryOwnership::Owned { relative: None },
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,
537 DirectoryOwnership::Owned { relative: None },
540 Some(Cow::Owned(ast::ModKind::Unloaded)),
550 fn path_value(attr: &ast::Attribute) -> Option<Symbol> {
551 if attr.has_name(sym::path) {
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()
565 fn is_cfg_if(item: &ast::Item) -> bool {
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") {