about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_builtin_macros/src/source_util.rs11
-rw-r--r--compiler/rustc_expand/src/base.rs37
-rw-r--r--compiler/rustc_expand/src/expand.rs114
-rw-r--r--compiler/rustc_expand/src/lib.rs2
-rw-r--r--compiler/rustc_expand/src/module.rs366
-rw-r--r--compiler/rustc_interface/src/passes.rs6
-rw-r--r--compiler/rustc_session/src/parse.rs4
7 files changed, 263 insertions, 277 deletions
diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs
index 28efd483c86..4aafcb2fb6d 100644
--- a/compiler/rustc_builtin_macros/src/source_util.rs
+++ b/compiler/rustc_builtin_macros/src/source_util.rs
@@ -4,7 +4,7 @@ use rustc_ast::token;
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast_pretty::pprust;
 use rustc_expand::base::{self, *};
-use rustc_expand::module::DirectoryOwnership;
+use rustc_expand::module::DirOwnership;
 use rustc_parse::parser::{ForceCollect, Parser};
 use rustc_parse::{self, new_parser_from_file};
 use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
@@ -101,7 +101,7 @@ pub fn expand_include<'cx>(
         None => return DummyResult::any(sp),
     };
     // The file will be added to the code map by the parser
-    let mut file = match cx.resolve_path(file, sp) {
+    let file = match cx.resolve_path(file, sp) {
         Ok(f) => f,
         Err(mut err) => {
             err.emit();
@@ -114,10 +114,9 @@ pub fn expand_include<'cx>(
     // then the path of `bar.rs` should be relative to the directory of `file`.
     // See https://github.com/rust-lang/rust/pull/69838/files#r395217057 for a discussion.
     // `MacroExpander::fully_expand_fragment` later restores, so "stack discipline" is maintained.
-    file.pop();
-    cx.current_expansion.directory_ownership = DirectoryOwnership::Owned { relative: None };
-    let mod_path = cx.current_expansion.module.mod_path.clone();
-    cx.current_expansion.module = Rc::new(ModuleData { mod_path, directory: file });
+    let dir_path = file.parent().unwrap_or(&file).to_owned();
+    cx.current_expansion.module = Rc::new(cx.current_expansion.module.with_dir_path(dir_path));
+    cx.current_expansion.dir_ownership = DirOwnership::Owned { relative: None };
 
     struct ExpandResult<'a> {
         p: Parser<'a>,
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 929cc56294d..5bd06e8e7d1 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -1,11 +1,11 @@
 use crate::expand::{self, AstFragment, Invocation};
-use crate::module::DirectoryOwnership;
+use crate::module::DirOwnership;
 
 use rustc_ast::ptr::P;
 use rustc_ast::token::{self, Nonterminal};
 use rustc_ast::tokenstream::{CanSynthesizeMissingTokens, LazyTokenStream, TokenStream};
 use rustc_ast::visit::{AssocCtxt, Visitor};
-use rustc_ast::{self as ast, AstLike, Attribute, NodeId, PatKind};
+use rustc_ast::{self as ast, AstLike, Attribute, Item, NodeId, PatKind};
 use rustc_attr::{self as attr, Deprecation, Stability};
 use rustc_data_structures::fx::FxHashMap;
 use rustc_data_structures::sync::{self, Lrc};
@@ -900,10 +900,26 @@ pub trait ResolverExpand {
     fn cfg_accessible(&mut self, expn_id: ExpnId, path: &ast::Path) -> Result<bool, Indeterminate>;
 }
 
-#[derive(Clone)]
+#[derive(Clone, Default)]
 pub struct ModuleData {
+    /// Path to the module starting from the crate name, like `my_crate::foo::bar`.
     pub mod_path: Vec<Ident>,
-    pub directory: PathBuf,
+    /// Stack of paths to files loaded by out-of-line module items,
+    /// used to detect and report recursive module inclusions.
+    pub file_path_stack: Vec<PathBuf>,
+    /// Directory to search child module files in,
+    /// often (but not necessarily) the parent of the top file path on the `file_path_stack`.
+    pub dir_path: PathBuf,
+}
+
+impl ModuleData {
+    pub fn with_dir_path(&self, dir_path: PathBuf) -> ModuleData {
+        ModuleData {
+            mod_path: self.mod_path.clone(),
+            file_path_stack: self.file_path_stack.clone(),
+            dir_path,
+        }
+    }
 }
 
 #[derive(Clone)]
@@ -911,10 +927,13 @@ pub struct ExpansionData {
     pub id: ExpnId,
     pub depth: usize,
     pub module: Rc<ModuleData>,
-    pub directory_ownership: DirectoryOwnership,
+    pub dir_ownership: DirOwnership,
     pub prior_type_ascription: Option<(Span, bool)>,
 }
 
+type OnExternModLoaded<'a> =
+    Option<&'a dyn Fn(Ident, Vec<Attribute>, Vec<P<Item>>, Span) -> (Vec<Attribute>, Vec<P<Item>>)>;
+
 /// One of these is made during expansion and incrementally updated as we go;
 /// when a macro expansion occurs, the resulting nodes have the `backtrace()
 /// -> expn_data` of their expansion context stored into their span.
@@ -932,7 +951,7 @@ pub struct ExtCtxt<'a> {
     /// Called directly after having parsed an external `mod foo;` in expansion.
     ///
     /// `Ident` is the module name.
-    pub(super) extern_mod_loaded: Option<&'a dyn Fn(&ast::Crate, Ident)>,
+    pub(super) extern_mod_loaded: OnExternModLoaded<'a>,
 }
 
 impl<'a> ExtCtxt<'a> {
@@ -940,7 +959,7 @@ impl<'a> ExtCtxt<'a> {
         sess: &'a Session,
         ecfg: expand::ExpansionConfig<'a>,
         resolver: &'a mut dyn ResolverExpand,
-        extern_mod_loaded: Option<&'a dyn Fn(&ast::Crate, Ident)>,
+        extern_mod_loaded: OnExternModLoaded<'a>,
     ) -> ExtCtxt<'a> {
         ExtCtxt {
             sess,
@@ -952,8 +971,8 @@ impl<'a> ExtCtxt<'a> {
             current_expansion: ExpansionData {
                 id: ExpnId::root(),
                 depth: 0,
-                module: Rc::new(ModuleData { mod_path: Vec::new(), directory: PathBuf::new() }),
-                directory_ownership: DirectoryOwnership::Owned { relative: None },
+                module: Default::default(),
+                dir_ownership: DirOwnership::Owned { relative: None },
                 prior_type_ascription: None,
             },
             force_mode: false,
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index b474cad1242..dd2eaa0f3d5 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -3,7 +3,7 @@ use crate::config::StripUnconfigured;
 use crate::configure;
 use crate::hygiene::SyntaxContext;
 use crate::mbe::macro_rules::annotate_err_with_kind;
-use crate::module::{parse_external_mod, push_directory, Directory, DirectoryOwnership};
+use crate::module::{mod_dir_path, parse_external_mod, DirOwnership, ParsedExternalMod};
 use crate::placeholders::{placeholder, PlaceholderExpander};
 
 use rustc_ast as ast;
@@ -355,16 +355,17 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
     // FIXME: Avoid visiting the crate as a `Mod` item,
     // make crate a first class expansion target instead.
     pub fn expand_crate(&mut self, mut krate: ast::Crate) -> ast::Crate {
-        let mut module = ModuleData {
-            mod_path: vec![Ident::from_str(&self.cx.ecfg.crate_name)],
-            directory: match self.cx.source_map().span_to_unmapped_path(krate.span) {
-                FileName::Real(name) => name.into_local_path(),
-                other => PathBuf::from(other.to_string()),
-            },
+        let file_path = match self.cx.source_map().span_to_unmapped_path(krate.span) {
+            FileName::Real(name) => name.into_local_path(),
+            other => PathBuf::from(other.to_string()),
         };
-        module.directory.pop();
-        self.cx.root_path = module.directory.clone();
-        self.cx.current_expansion.module = Rc::new(module);
+        let dir_path = file_path.parent().unwrap_or(&file_path).to_owned();
+        self.cx.root_path = dir_path.clone();
+        self.cx.current_expansion.module = Rc::new(ModuleData {
+            mod_path: vec![Ident::from_str(&self.cx.ecfg.crate_name)],
+            file_path_stack: vec![file_path],
+            dir_path,
+        });
 
         let krate_item = AstFragment::Items(smallvec![P(ast::Item {
             attrs: krate.attrs,
@@ -1245,10 +1246,12 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
     }
 
     fn visit_block(&mut self, block: &mut P<Block>) {
-        let old_directory_ownership = self.cx.current_expansion.directory_ownership;
-        self.cx.current_expansion.directory_ownership = DirectoryOwnership::UnownedViaBlock;
+        let orig_dir_ownership = mem::replace(
+            &mut self.cx.current_expansion.dir_ownership,
+            DirOwnership::UnownedViaBlock,
+        );
         noop_visit_block(block, self);
-        self.cx.current_expansion.directory_ownership = old_directory_ownership;
+        self.cx.current_expansion.dir_ownership = orig_dir_ownership;
     }
 
     fn flat_map_item(&mut self, item: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
@@ -1276,63 +1279,76 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
                 })
             }
             ast::ItemKind::Mod(_, ref mut mod_kind) if ident != Ident::invalid() => {
-                let sess = &self.cx.sess.parse_sess;
-                let orig_ownership = self.cx.current_expansion.directory_ownership;
-                let mut module = (*self.cx.current_expansion.module).clone();
-
-                let pushed = &mut false; // Record `parse_external_mod` pushing so we can pop.
-                let dir = Directory { ownership: orig_ownership, path: module.directory };
-                let Directory { ownership, path } = match mod_kind {
-                    ModKind::Loaded(_, Inline::Yes, _) => {
+                let (file_path, dir_path, dir_ownership) = match mod_kind {
+                    ModKind::Loaded(_, inline, _) => {
                         // Inline `mod foo { ... }`, but we still need to push directories.
+                        assert!(
+                            *inline == Inline::Yes,
+                            "`mod` item is loaded from a file for the second time"
+                        );
+                        let (dir_path, dir_ownership) = mod_dir_path(
+                            &self.cx.sess,
+                            ident,
+                            &attrs,
+                            &self.cx.current_expansion.module,
+                            self.cx.current_expansion.dir_ownership,
+                        );
                         item.attrs = attrs;
-                        push_directory(&self.cx.sess, ident, &item.attrs, dir)
-                    }
-                    ModKind::Loaded(_, Inline::No, _) => {
-                        panic!("`mod` item is loaded from a file for the second time")
+                        (None, dir_path, dir_ownership)
                     }
                     ModKind::Unloaded => {
                         // We have an outline `mod foo;` so we need to parse the file.
-                        let (items, inner_span, dir) =
-                            parse_external_mod(&self.cx.sess, ident, span, dir, &mut attrs, pushed);
+                        let old_attrs_len = attrs.len();
+                        let ParsedExternalMod {
+                            mut items,
+                            inner_span,
+                            file_path,
+                            dir_path,
+                            dir_ownership,
+                        } = parse_external_mod(
+                            &self.cx.sess,
+                            ident,
+                            span,
+                            &self.cx.current_expansion.module,
+                            self.cx.current_expansion.dir_ownership,
+                            &mut attrs,
+                        );
 
-                        let krate =
-                            ast::Crate { attrs, items, span: inner_span, proc_macros: vec![] };
                         if let Some(extern_mod_loaded) = self.cx.extern_mod_loaded {
-                            extern_mod_loaded(&krate, ident);
+                            (attrs, items) = extern_mod_loaded(ident, attrs, items, inner_span);
                         }
 
-                        *mod_kind = ModKind::Loaded(krate.items, Inline::No, inner_span);
-                        item.attrs = krate.attrs;
-                        // File can have inline attributes, e.g., `#![cfg(...)]` & co. => Reconfigure.
-                        item = match self.configure(item) {
-                            Some(node) => node,
-                            None => {
-                                if *pushed {
-                                    sess.included_mod_stack.borrow_mut().pop();
-                                }
-                                return Default::default();
-                            }
-                        };
-                        dir
+                        *mod_kind = ModKind::Loaded(items, Inline::No, inner_span);
+                        item.attrs = attrs;
+                        if item.attrs.len() > old_attrs_len {
+                            // If we loaded an out-of-line module and added some inner attributes,
+                            // then we need to re-configure it.
+                            // FIXME: Attributes also need to be recollected
+                            // for resolution and expansion.
+                            item = configure!(self, item);
+                        }
+                        (Some(file_path), dir_path, dir_ownership)
                     }
                 };
 
                 // Set the module info before we flat map.
-                self.cx.current_expansion.directory_ownership = ownership;
-                module.directory = path;
+                let mut module = self.cx.current_expansion.module.with_dir_path(dir_path);
                 module.mod_path.push(ident);
+                if let Some(file_path) = file_path {
+                    module.file_path_stack.push(file_path);
+                }
+
                 let orig_module =
                     mem::replace(&mut self.cx.current_expansion.module, Rc::new(module));
+                let orig_dir_ownership =
+                    mem::replace(&mut self.cx.current_expansion.dir_ownership, dir_ownership);
 
                 let result = noop_flat_map_item(item, self);
 
                 // Restore the module info.
+                self.cx.current_expansion.dir_ownership = orig_dir_ownership;
                 self.cx.current_expansion.module = orig_module;
-                self.cx.current_expansion.directory_ownership = orig_ownership;
-                if *pushed {
-                    sess.included_mod_stack.borrow_mut().pop();
-                }
+
                 result
             }
             _ => {
diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs
index c5d8ff25ea9..1a93975533d 100644
--- a/compiler/rustc_expand/src/lib.rs
+++ b/compiler/rustc_expand/src/lib.rs
@@ -1,5 +1,7 @@
+#![feature(bool_to_option)]
 #![feature(crate_visibility_modifier)]
 #![feature(decl_macro)]
+#![feature(destructuring_assignment)]
 #![feature(or_patterns)]
 #![feature(proc_macro_diagnostic)]
 #![feature(proc_macro_internals)]
diff --git a/compiler/rustc_expand/src/module.rs b/compiler/rustc_expand/src/module.rs
index 076d3b02be9..2ec656d4895 100644
--- a/compiler/rustc_expand/src/module.rs
+++ b/compiler/rustc_expand/src/module.rs
@@ -1,232 +1,155 @@
+use crate::base::ModuleData;
 use rustc_ast::ptr::P;
 use rustc_ast::{token, Attribute, Item};
-use rustc_errors::{struct_span_err, PResult};
+use rustc_errors::{struct_span_err, DiagnosticBuilder};
 use rustc_parse::new_parser_from_file;
 use rustc_session::parse::ParseSess;
 use rustc_session::Session;
-use rustc_span::source_map::{FileName, Span};
 use rustc_span::symbol::{sym, Ident};
+use rustc_span::Span;
 
 use std::path::{self, Path, PathBuf};
 
-#[derive(Clone)]
-pub struct Directory {
-    pub path: PathBuf,
-    pub ownership: DirectoryOwnership,
-}
-
 #[derive(Copy, Clone)]
-pub enum DirectoryOwnership {
+pub enum DirOwnership {
     Owned {
         // None if `mod.rs`, `Some("foo")` if we're in `foo.rs`.
         relative: Option<Ident>,
     },
     UnownedViaBlock,
-    UnownedViaMod,
 }
 
-/// Information about the path to a module.
 // Public for rustfmt usage.
-pub struct ModulePath<'a> {
-    name: String,
-    path_exists: bool,
-    pub result: PResult<'a, ModulePathSuccess>,
+pub struct ModulePathSuccess {
+    pub file_path: PathBuf,
+    pub dir_ownership: DirOwnership,
 }
 
-// Public for rustfmt usage.
-pub struct ModulePathSuccess {
-    pub path: PathBuf,
-    pub ownership: DirectoryOwnership,
+crate struct ParsedExternalMod {
+    pub items: Vec<P<Item>>,
+    pub inner_span: Span,
+    pub file_path: PathBuf,
+    pub dir_path: PathBuf,
+    pub dir_ownership: DirOwnership,
+}
+
+pub enum ModError<'a> {
+    CircularInclusion(Vec<PathBuf>),
+    ModInBlock(Option<Ident>),
+    FileNotFound(Ident, PathBuf),
+    MultipleCandidates(Ident, String, String),
+    ParserError(DiagnosticBuilder<'a>),
 }
 
 crate fn parse_external_mod(
     sess: &Session,
-    id: Ident,
+    ident: Ident,
     span: Span, // The span to blame on errors.
-    Directory { mut ownership, path }: Directory,
+    module: &ModuleData,
+    mut dir_ownership: DirOwnership,
     attrs: &mut Vec<Attribute>,
-    pop_mod_stack: &mut bool,
-) -> (Vec<P<Item>>, Span, Directory) {
+) -> ParsedExternalMod {
     // We bail on the first error, but that error does not cause a fatal error... (1)
-    let result: PResult<'_, _> = try {
+    let result: Result<_, ModError<'_>> = try {
         // Extract the file path and the new ownership.
-        let mp = submod_path(sess, id, span, &attrs, ownership, &path)?;
-        ownership = mp.ownership;
+        let mp = mod_file_path(sess, ident, &attrs, &module.dir_path, dir_ownership)?;
+        dir_ownership = mp.dir_ownership;
 
         // Ensure file paths are acyclic.
-        let mut included_mod_stack = sess.parse_sess.included_mod_stack.borrow_mut();
-        error_on_circular_module(&sess.parse_sess, span, &mp.path, &included_mod_stack)?;
-        included_mod_stack.push(mp.path.clone());
-        *pop_mod_stack = true; // We have pushed, so notify caller.
-        drop(included_mod_stack);
+        if let Some(pos) = module.file_path_stack.iter().position(|p| p == &mp.file_path) {
+            Err(ModError::CircularInclusion(module.file_path_stack[pos..].to_vec()))?;
+        }
 
         // Actually parse the external file as a module.
-        let mut parser = new_parser_from_file(&sess.parse_sess, &mp.path, Some(span));
-        let (mut inner_attrs, items, inner_span) = parser.parse_mod(&token::Eof)?;
+        let mut parser = new_parser_from_file(&sess.parse_sess, &mp.file_path, Some(span));
+        let (mut inner_attrs, items, inner_span) =
+            parser.parse_mod(&token::Eof).map_err(|err| ModError::ParserError(err))?;
         attrs.append(&mut inner_attrs);
-        (items, inner_span)
+        (items, inner_span, mp.file_path)
     };
     // (1) ...instead, we return a dummy module.
-    let (items, inner_span) = result.map_err(|mut err| err.emit()).unwrap_or_default();
+    let (items, inner_span, file_path) =
+        result.map_err(|err| err.report(sess, span)).unwrap_or_default();
 
-    // Extract the directory path for submodules of  the module.
-    let path = sess.source_map().span_to_unmapped_path(inner_span);
-    let mut path = match path {
-        FileName::Real(name) => name.into_local_path(),
-        other => PathBuf::from(other.to_string()),
-    };
-    path.pop();
+    // Extract the directory path for submodules of the module.
+    let dir_path = file_path.parent().unwrap_or(&file_path).to_owned();
 
-    (items, inner_span, Directory { ownership, path })
+    ParsedExternalMod { items, inner_span, file_path, dir_path, dir_ownership }
 }
 
-fn error_on_circular_module<'a>(
-    sess: &'a ParseSess,
-    span: Span,
-    path: &Path,
-    included_mod_stack: &[PathBuf],
-) -> PResult<'a, ()> {
-    if let Some(i) = included_mod_stack.iter().position(|p| *p == path) {
-        let mut err = String::from("circular modules: ");
-        for p in &included_mod_stack[i..] {
-            err.push_str(&p.to_string_lossy());
-            err.push_str(" -> ");
-        }
-        err.push_str(&path.to_string_lossy());
-        return Err(sess.span_diagnostic.struct_span_err(span, &err[..]));
-    }
-    Ok(())
-}
-
-crate fn push_directory(
+crate fn mod_dir_path(
     sess: &Session,
-    id: Ident,
+    ident: Ident,
     attrs: &[Attribute],
-    Directory { mut ownership, mut path }: Directory,
-) -> Directory {
-    if let Some(filename) = sess.first_attr_value_str_by_name(attrs, sym::path) {
-        path.push(&*filename.as_str());
-        ownership = DirectoryOwnership::Owned { relative: None };
-    } else {
-        // We have to push on the current module name in the case of relative
-        // paths in order to ensure that any additional module paths from inline
-        // `mod x { ... }` come after the relative extension.
-        //
-        // For example, a `mod z { ... }` inside `x/y.rs` should set the current
-        // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`.
-        if let DirectoryOwnership::Owned { relative } = &mut ownership {
-            if let Some(ident) = relative.take() {
-                // Remove the relative offset.
-                path.push(&*ident.as_str());
-            }
+    module: &ModuleData,
+    mut dir_ownership: DirOwnership,
+) -> (PathBuf, DirOwnership) {
+    if let Some(file_path) = mod_file_path_from_attr(sess, attrs, &module.dir_path) {
+        // For inline modules file path from `#[path]` is actually the directory path
+        // for historical reasons, so we don't pop the last segment here.
+        return (file_path, DirOwnership::Owned { relative: None });
+    }
+
+    // We have to push on the current module name in the case of relative
+    // paths in order to ensure that any additional module paths from inline
+    // `mod x { ... }` come after the relative extension.
+    //
+    // For example, a `mod z { ... }` inside `x/y.rs` should set the current
+    // directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`.
+    let mut dir_path = module.dir_path.clone();
+    if let DirOwnership::Owned { relative } = &mut dir_ownership {
+        if let Some(ident) = relative.take() {
+            // Remove the relative offset.
+            dir_path.push(&*ident.as_str());
         }
-        path.push(&*id.as_str());
     }
-    Directory { ownership, path }
+    dir_path.push(&*ident.as_str());
+
+    (dir_path, dir_ownership)
 }
 
-fn submod_path<'a>(
+fn mod_file_path<'a>(
     sess: &'a Session,
-    id: Ident,
-    span: Span,
+    ident: Ident,
     attrs: &[Attribute],
-    ownership: DirectoryOwnership,
     dir_path: &Path,
-) -> PResult<'a, ModulePathSuccess> {
-    if let Some(path) = submod_path_from_attr(sess, attrs, dir_path) {
-        let ownership = match path.file_name().and_then(|s| s.to_str()) {
-            // All `#[path]` files are treated as though they are a `mod.rs` file.
-            // This means that `mod foo;` declarations inside `#[path]`-included
-            // files are siblings,
-            //
-            // Note that this will produce weirdness when a file named `foo.rs` is
-            // `#[path]` included and contains a `mod foo;` declaration.
-            // If you encounter this, it's your own darn fault :P
-            Some(_) => DirectoryOwnership::Owned { relative: None },
-            _ => DirectoryOwnership::UnownedViaMod,
-        };
-        return Ok(ModulePathSuccess { ownership, path });
+    dir_ownership: DirOwnership,
+) -> Result<ModulePathSuccess, ModError<'a>> {
+    if let Some(file_path) = mod_file_path_from_attr(sess, attrs, dir_path) {
+        // All `#[path]` files are treated as though they are a `mod.rs` file.
+        // This means that `mod foo;` declarations inside `#[path]`-included
+        // files are siblings,
+        //
+        // Note that this will produce weirdness when a file named `foo.rs` is
+        // `#[path]` included and contains a `mod foo;` declaration.
+        // If you encounter this, it's your own darn fault :P
+        let dir_ownership = DirOwnership::Owned { relative: None };
+        return Ok(ModulePathSuccess { file_path, dir_ownership });
     }
 
-    let relative = match ownership {
-        DirectoryOwnership::Owned { relative } => relative,
-        DirectoryOwnership::UnownedViaBlock | DirectoryOwnership::UnownedViaMod => None,
+    let relative = match dir_ownership {
+        DirOwnership::Owned { relative } => relative,
+        DirOwnership::UnownedViaBlock => None,
     };
-    let ModulePath { path_exists, name, result } =
-        default_submod_path(&sess.parse_sess, id, span, relative, dir_path);
-    match ownership {
-        DirectoryOwnership::Owned { .. } => Ok(result?),
-        DirectoryOwnership::UnownedViaBlock => {
-            let _ = result.map_err(|mut err| err.cancel());
-            error_decl_mod_in_block(&sess.parse_sess, span, path_exists, &name)
-        }
-        DirectoryOwnership::UnownedViaMod => {
-            let _ = result.map_err(|mut err| err.cancel());
-            error_cannot_declare_mod_here(&sess.parse_sess, span, path_exists, &name)
-        }
+    let result = default_submod_path(&sess.parse_sess, ident, relative, dir_path);
+    match dir_ownership {
+        DirOwnership::Owned { .. } => result,
+        DirOwnership::UnownedViaBlock => Err(ModError::ModInBlock(match result {
+            Ok(_) | Err(ModError::MultipleCandidates(..)) => Some(ident),
+            _ => None,
+        })),
     }
 }
 
-fn error_decl_mod_in_block<'a, T>(
-    sess: &'a ParseSess,
-    span: Span,
-    path_exists: bool,
-    name: &str,
-) -> PResult<'a, T> {
-    let msg = "Cannot declare a non-inline module inside a block unless it has a path attribute";
-    let mut err = sess.span_diagnostic.struct_span_err(span, msg);
-    if path_exists {
-        let msg = format!("Maybe `use` the module `{}` instead of redeclaring it", name);
-        err.span_note(span, &msg);
-    }
-    Err(err)
-}
-
-fn error_cannot_declare_mod_here<'a, T>(
-    sess: &'a ParseSess,
-    span: Span,
-    path_exists: bool,
-    name: &str,
-) -> PResult<'a, T> {
-    let mut err =
-        sess.span_diagnostic.struct_span_err(span, "cannot declare a new module at this location");
-    if !span.is_dummy() {
-        if let FileName::Real(src_name) = sess.source_map().span_to_filename(span) {
-            let src_path = src_name.into_local_path();
-            if let Some(stem) = src_path.file_stem() {
-                let mut dest_path = src_path.clone();
-                dest_path.set_file_name(stem);
-                dest_path.push("mod.rs");
-                err.span_note(
-                    span,
-                    &format!(
-                        "maybe move this module `{}` to its own directory via `{}`",
-                        src_path.display(),
-                        dest_path.display()
-                    ),
-                );
-            }
-        }
-    }
-    if path_exists {
-        err.span_note(
-            span,
-            &format!("... or maybe `use` the module `{}` instead of possibly redeclaring it", name),
-        );
-    }
-    Err(err)
-}
-
 /// Derive a submodule path from the first found `#[path = "path_string"]`.
 /// The provided `dir_path` is joined with the `path_string`.
-pub(super) fn submod_path_from_attr(
+fn mod_file_path_from_attr(
     sess: &Session,
     attrs: &[Attribute],
     dir_path: &Path,
 ) -> Option<PathBuf> {
     // Extract path string from first `#[path = "path_string"]` attribute.
-    let path_string = sess.first_attr_value_str_by_name(attrs, sym::path)?;
-    let path_string = path_string.as_str();
+    let path_string = sess.first_attr_value_str_by_name(attrs, sym::path)?.as_str();
 
     // On windows, the base path might have the form
     // `\\?\foo\bar` in which case it does not tolerate
@@ -242,15 +165,14 @@ pub(super) fn submod_path_from_attr(
 // Public for rustfmt usage.
 pub fn default_submod_path<'a>(
     sess: &'a ParseSess,
-    id: Ident,
-    span: Span,
+    ident: Ident,
     relative: Option<Ident>,
     dir_path: &Path,
-) -> ModulePath<'a> {
+) -> Result<ModulePathSuccess, ModError<'a>> {
     // If we're in a foo.rs file instead of a mod.rs file,
     // we need to look for submodules in
-    // `./foo/<id>.rs` and `./foo/<id>/mod.rs` rather than
-    // `./<id>.rs` and `./<id>/mod.rs`.
+    // `./foo/<ident>.rs` and `./foo/<ident>/mod.rs` rather than
+    // `./<ident>.rs` and `./<ident>/mod.rs`.
     let relative_prefix_string;
     let relative_prefix = if let Some(ident) = relative {
         relative_prefix_string = format!("{}{}", ident.name, path::MAIN_SEPARATOR);
@@ -259,7 +181,7 @@ pub fn default_submod_path<'a>(
         ""
     };
 
-    let mod_name = id.name.to_string();
+    let mod_name = ident.name.to_string();
     let default_path_str = format!("{}{}.rs", relative_prefix, mod_name);
     let secondary_path_str =
         format!("{}{}{}mod.rs", relative_prefix, mod_name, path::MAIN_SEPARATOR);
@@ -268,44 +190,74 @@ pub fn default_submod_path<'a>(
     let default_exists = sess.source_map().file_exists(&default_path);
     let secondary_exists = sess.source_map().file_exists(&secondary_path);
 
-    let result = match (default_exists, secondary_exists) {
+    match (default_exists, secondary_exists) {
         (true, false) => Ok(ModulePathSuccess {
-            path: default_path,
-            ownership: DirectoryOwnership::Owned { relative: Some(id) },
+            file_path: default_path,
+            dir_ownership: DirOwnership::Owned { relative: Some(ident) },
         }),
         (false, true) => Ok(ModulePathSuccess {
-            path: secondary_path,
-            ownership: DirectoryOwnership::Owned { relative: None },
+            file_path: secondary_path,
+            dir_ownership: DirOwnership::Owned { relative: None },
         }),
-        (false, false) => {
-            let mut err = struct_span_err!(
-                sess.span_diagnostic,
-                span,
-                E0583,
-                "file not found for module `{}`",
-                mod_name,
-            );
-            err.help(&format!(
-                "to create the module `{}`, create file \"{}\"",
-                mod_name,
-                default_path.display(),
-            ));
-            Err(err)
-        }
+        (false, false) => Err(ModError::FileNotFound(ident, default_path)),
         (true, true) => {
-            let mut err = struct_span_err!(
-                sess.span_diagnostic,
-                span,
-                E0761,
-                "file for module `{}` found at both {} and {}",
-                mod_name,
-                default_path_str,
-                secondary_path_str,
-            );
-            err.help("delete or rename one of them to remove the ambiguity");
-            Err(err)
+            Err(ModError::MultipleCandidates(ident, default_path_str, secondary_path_str))
         }
-    };
+    }
+}
 
-    ModulePath { name: mod_name, path_exists: default_exists || secondary_exists, result }
+impl ModError<'_> {
+    fn report(self, sess: &Session, span: Span) {
+        let diag = &sess.parse_sess.span_diagnostic;
+        match self {
+            ModError::CircularInclusion(file_paths) => {
+                let mut msg = String::from("circular modules: ");
+                for file_path in &file_paths {
+                    msg.push_str(&file_path.display().to_string());
+                    msg.push_str(" -> ");
+                }
+                msg.push_str(&file_paths[0].display().to_string());
+                diag.struct_span_err(span, &msg)
+            }
+            ModError::ModInBlock(ident) => {
+                let msg = "cannot declare a non-inline module inside a block unless it has a path attribute";
+                let mut err = diag.struct_span_err(span, msg);
+                if let Some(ident) = ident {
+                    let note =
+                        format!("maybe `use` the module `{}` instead of redeclaring it", ident);
+                    err.span_note(span, &note);
+                }
+                err
+            }
+            ModError::FileNotFound(ident, default_path) => {
+                let mut err = struct_span_err!(
+                    diag,
+                    span,
+                    E0583,
+                    "file not found for module `{}`",
+                    ident,
+                );
+                err.help(&format!(
+                    "to create the module `{}`, create file \"{}\"",
+                    ident,
+                    default_path.display(),
+                ));
+                err
+            }
+            ModError::MultipleCandidates(ident, default_path_short, secondary_path_short) => {
+                let mut err = struct_span_err!(
+                    diag,
+                    span,
+                    E0761,
+                    "file for module `{}` found at both {} and {}",
+                    ident,
+                    default_path_short,
+                    secondary_path_short,
+                );
+                err.help("delete or rename one of them to remove the ambiguity");
+                err
+            }
+            ModError::ParserError(err) => err,
+        }.emit()
+    }
 }
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index fb6ef7d6b25..94be7a03a93 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -302,8 +302,10 @@ fn configure_and_expand_inner<'a>(
             ..rustc_expand::expand::ExpansionConfig::default(crate_name.to_string())
         };
 
-        let extern_mod_loaded = |k: &ast::Crate, ident: Ident| {
-            pre_expansion_lint(sess, lint_store, k, &*ident.name.as_str())
+        let extern_mod_loaded = |ident: Ident, attrs, items, span| {
+            let krate = ast::Crate { attrs, items, span, proc_macros: vec![] };
+            pre_expansion_lint(sess, lint_store, &krate, &ident.name.as_str());
+            (krate.attrs, krate.items)
         };
         let mut ecx = ExtCtxt::new(&sess, cfg, &mut resolver, Some(&extern_mod_loaded));
 
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
index 81b38347414..592773bfe1b 100644
--- a/compiler/rustc_session/src/parse.rs
+++ b/compiler/rustc_session/src/parse.rs
@@ -13,7 +13,6 @@ use rustc_span::hygiene::ExpnId;
 use rustc_span::source_map::{FilePathMapping, SourceMap};
 use rustc_span::{MultiSpan, Span, Symbol};
 
-use std::path::PathBuf;
 use std::str;
 
 /// The set of keys (and, optionally, values) that define the compilation
@@ -122,8 +121,6 @@ pub struct ParseSess {
     pub missing_fragment_specifiers: Lock<FxHashMap<Span, NodeId>>,
     /// Places where raw identifiers were used. This is used for feature-gating raw identifiers.
     pub raw_identifier_spans: Lock<Vec<Span>>,
-    /// Used to determine and report recursive module inclusions.
-    pub included_mod_stack: Lock<Vec<PathBuf>>,
     source_map: Lrc<SourceMap>,
     pub buffered_lints: Lock<Vec<BufferedEarlyLint>>,
     /// Contains the spans of block expressions that could have been incomplete based on the
@@ -157,7 +154,6 @@ impl ParseSess {
             edition: ExpnId::root().expn_data().edition,
             missing_fragment_specifiers: Default::default(),
             raw_identifier_spans: Lock::new(Vec::new()),
-            included_mod_stack: Lock::new(vec![]),
             source_map,
             buffered_lints: Lock::new(vec![]),
             ambiguous_block_expr_parse: Lock::new(FxHashMap::default()),