diff --git a/src/formatting.rs b/src/formatting.rs index 92d7c5ec111..69ada297f4e 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -10,7 +10,7 @@ pub(crate) use syntux::session::ParseSess; use crate::config::{Config, FileName}; use crate::formatting::{ comment::{CharClasses, FullCodeCharKind}, - modules::Module, + modules::{FileModMap, Module}, newline_style::apply_newline_style, report::NonFormattedRange, syntux::parser::{DirectoryOwnership, Parser, ParserError}, @@ -107,6 +107,11 @@ fn format_project( } }; + if !operation_setting.recursive { + // Suppress error output for sub-modules if we are not in recurisve mode. + parse_session.set_silent_emitter(); + } + let files = modules::ModResolver::new( &parse_session, directory_ownership.unwrap_or(DirectoryOwnership::UnownedViaMod), @@ -119,9 +124,9 @@ fn format_project( // Suppress error output if we have to do any further parsing. parse_session.set_silent_emitter(); - for (path, module) in files { + for (path, module) in &files { let should_ignore = !input_is_stdin && parse_session.ignore_file(&path); - if (!operation_setting.recursive && path != main_file) || should_ignore { + if (!operation_setting.recursive && path != &main_file) || should_ignore { continue; } should_emit_verbose(input_is_stdin, operation_setting.verbosity, || { @@ -134,6 +139,7 @@ fn format_project( path, &module, &format_report, + &files, original_snippet.clone(), )?; } @@ -154,14 +160,20 @@ fn format_file( parse_session: &ParseSess, config: &Config, krate: &ast::Crate, - path: FileName, + path: &FileName, module: &Module<'_>, report: &FormatReport, + file_mod_map: &FileModMap<'_>, original_snippet: Option, ) -> Result<(), OperationError> { let snippet_provider = parse_session.snippet_provider(module.as_ref().inner); - let mut visitor = - FmtVisitor::from_parse_sess(&parse_session, config, &snippet_provider, report.clone()); + let mut visitor = FmtVisitor::from_parse_sess( + &parse_session, + config, + &snippet_provider, + file_mod_map, + report.clone(), + ); visitor.skip_context.update_with_attrs(&krate.attrs); visitor.last_pos = snippet_provider.start_pos(); visitor.skip_empty_lines(snippet_provider.end_pos()); @@ -210,7 +222,7 @@ fn format_file( original_text, config.newline_style(), ); - report.add_format_result(path, format_result); + report.add_format_result(path.clone(), format_result); Ok(()) } diff --git a/src/formatting/modules.rs b/src/formatting/modules.rs index 2570d9b1a65..cf9039aaddf 100644 --- a/src/formatting/modules.rs +++ b/src/formatting/modules.rs @@ -19,23 +19,65 @@ use crate::formatting::{ mod visitor; -type FileModMap<'ast> = BTreeMap>; +pub(crate) type FileModMap<'ast> = BTreeMap>; + +pub(crate) fn get_mod_inner_attrs<'a>( + item: &'a ast::Item, + file_mod_map: &'a FileModMap<'_>, +) -> Option<&'a [ast::Attribute]> { + file_mod_map.values().find_map(|m| { + m.ast_item.as_ref().and_then(|ast_item| { + if ast_item.span == item.span { + Some(m.inner_attr.as_slice()) + } else { + None + } + }) + }) +} /// Represents module with its inner attributes. #[derive(Debug, Clone)] pub(crate) struct Module<'a> { ast_mod: Cow<'a, ast::Mod>, + ast_item: Option>, inner_attr: Vec, } impl<'a> Module<'a> { - pub(crate) fn new(ast_mod: Cow<'a, ast::Mod>, attrs: &[ast::Attribute]) -> Self { + fn ident(&self) -> symbol::Ident { + match self.ast_item { + None => symbol::Ident::invalid(), + Some(ref item) => item.ident, + } + } + + fn name(&self) -> String { + match self.ast_item { + None => String::new(), + Some(ref item) => item.ident.to_string(), + } + } + + fn outer_attrs(&self) -> &[ast::Attribute] { + match self.ast_item { + None => &[], + Some(ref item) => &item.attrs, + } + } + + pub(crate) fn new( + ast_mod: Cow<'a, ast::Mod>, + ast_item: Option>, + attrs: &[ast::Attribute], + ) -> Self { let inner_attr = attrs .iter() .filter(|attr| attr.style == ast::AttrStyle::Inner) .cloned() .collect(); Module { + ast_item, ast_mod, inner_attr, } @@ -84,16 +126,16 @@ pub(crate) enum ModuleResolutionErrorKind { } #[derive(Clone)] -enum SubModKind<'a, 'ast> { +enum SubModKind<'ast> { /// `mod foo;` External(PathBuf, DirectoryOwnership, Module<'ast>), /// `mod foo;` with multiple sources. MultiExternal(Vec<(PathBuf, DirectoryOwnership, Module<'ast>)>), /// `mod foo {}` - Internal(&'a ast::Item), + Internal(Cow<'ast, ast::Item>), } -impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { +impl<'ast, 'sess> ModResolver<'ast, 'sess> { /// Creates a new `ModResolver`. pub(crate) fn new( parse_sess: &'sess ParseSess, @@ -122,14 +164,11 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { _ => PathBuf::new(), }; - // Skip visiting sub modules when the input is from stdin. - if self.recursive { - self.visit_mod_from_ast(&krate.module)?; - } + self.visit_mod_from_ast(&krate.module)?; self.file_map.insert( root_filename, - Module::new(Cow::Borrowed(&krate.module), &krate.attrs), + Module::new(Cow::Borrowed(&krate.module), None, &krate.attrs), ); Ok(self.file_map) } @@ -140,10 +179,11 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { visitor.visit_item(&item); for module_item in visitor.mods() { if let ast::ItemKind::Mod(ref sub_mod) = module_item.item.kind { - self.visit_sub_mod( - &module_item.item, - Module::new(Cow::Owned(sub_mod.clone()), &module_item.item.attrs), - )?; + self.visit_sub_mod(Module::new( + Cow::Owned(sub_mod.clone()), + Some(Cow::Owned(module_item.item)), + &[], + ))?; } } Ok(()) @@ -158,7 +198,11 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { } if let ast::ItemKind::Mod(ref sub_mod) = item.kind { - self.visit_sub_mod(&item, Module::new(Cow::Owned(sub_mod.clone()), &item.attrs))?; + self.visit_sub_mod(Module::new( + Cow::Owned(sub_mod.clone()), + Some(Cow::Owned(item.into_inner())), + &[], + ))?; } } Ok(()) @@ -168,26 +212,34 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { fn visit_mod_from_ast(&mut self, module: &'ast ast::Mod) -> Result<(), ModuleResolutionError> { for item in &module.items { if is_cfg_if(item) { - self.visit_cfg_if(Cow::Borrowed(item))?; + let result = self.visit_cfg_if(Cow::Borrowed(item)); + if result.is_err() && self.recursive { + return result; + } } if let ast::ItemKind::Mod(ref sub_mod) = item.kind { - self.visit_sub_mod(item, Module::new(Cow::Borrowed(sub_mod), &item.attrs))?; + let result = self.visit_sub_mod(Module::new( + Cow::Borrowed(sub_mod), + Some(Cow::Borrowed(item)), + &item.attrs, + )); + if result.is_err() && self.recursive { + return result; + } } } Ok(()) } - fn visit_sub_mod( - &mut self, - item: &'c ast::Item, - sub_mod: Module<'ast>, - ) -> Result<(), ModuleResolutionError> { + fn visit_sub_mod(&mut self, sub_mod: Module<'ast>) -> Result<(), ModuleResolutionError> { let old_directory = self.directory.clone(); - let sub_mod_kind = self.peek_sub_mod(item, &sub_mod)?; + let sub_mod_kind = self.peek_sub_mod(&sub_mod)?; if let Some(sub_mod_kind) = sub_mod_kind { self.insert_sub_mod(sub_mod_kind.clone())?; - self.visit_sub_mod_inner(sub_mod, sub_mod_kind)?; + if self.recursive { + self.visit_sub_mod_inner(sub_mod, sub_mod_kind)?; + } } self.directory = old_directory; Ok(()) @@ -196,26 +248,31 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { /// Inspect the given sub-module which we are about to visit and returns its kind. fn peek_sub_mod( &self, - item: &'c ast::Item, sub_mod: &Module<'ast>, - ) -> Result>, ModuleResolutionError> { - if contains_skip(&item.attrs) { + ) -> Result>, ModuleResolutionError> { + if contains_skip(&sub_mod.outer_attrs()) { return Ok(None); } - if is_mod_decl(item) { + if sub_mod + .ast_item + .as_ref() + .map_or(false, |item| is_mod_decl(&item)) + { // mod foo; // Look for an extern file. - self.find_external_module(item.ident, &item.attrs, sub_mod) + self.find_external_module(sub_mod) } else { // An internal module (`mod foo { /* ... */ }`); - Ok(Some(SubModKind::Internal(item))) + Ok(Some(SubModKind::Internal( + sub_mod.ast_item.clone().unwrap(), + ))) } } fn insert_sub_mod( &mut self, - sub_mod_kind: SubModKind<'c, 'ast>, + sub_mod_kind: SubModKind<'ast>, ) -> Result<(), ModuleResolutionError> { match sub_mod_kind { SubModKind::External(mod_path, _, sub_mod) => { @@ -238,7 +295,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { fn visit_sub_mod_inner( &mut self, sub_mod: Module<'ast>, - sub_mod_kind: SubModKind<'c, 'ast>, + sub_mod_kind: SubModKind<'ast>, ) -> Result<(), ModuleResolutionError> { match sub_mod_kind { SubModKind::External(mod_path, directory_ownership, sub_mod) => { @@ -282,15 +339,15 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { /// Find a file path in the filesystem which corresponds to the given module. fn find_external_module( &self, - mod_name: symbol::Ident, - attrs: &[ast::Attribute], sub_mod: &Module<'ast>, - ) -> Result>, ModuleResolutionError> { + ) -> Result>, ModuleResolutionError> { let relative = match self.directory.ownership { DirectoryOwnership::Owned { relative } => relative, DirectoryOwnership::UnownedViaBlock | DirectoryOwnership::UnownedViaMod => None, }; - if let Some(path) = Parser::submod_path_from_attr(attrs, &self.directory.path) { + if let Some(path) = + Parser::submod_path_from_attr(sub_mod.outer_attrs(), &self.directory.path) + { if self.parse_sess.is_file_parsed(&path) { return Ok(None); } @@ -299,25 +356,25 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { Ok(m) => Ok(Some(SubModKind::External( path, DirectoryOwnership::Owned { relative: None }, - Module::new(Cow::Owned(m.0), &m.1), + Module::new(Cow::Owned(m.0), sub_mod.ast_item.clone(), &m.1), ))), Err(ParserError::ParseError) => Err(ModuleResolutionError { - module: mod_name.to_string(), + module: sub_mod.name(), kind: ModuleResolutionErrorKind::ParseError { file: path }, }), Err(..) => Err(ModuleResolutionError { - module: mod_name.to_string(), + module: sub_mod.name(), kind: ModuleResolutionErrorKind::NotFound { file: path }, }), }; } // Look for nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`. - let mut mods_outside_ast = self.find_mods_outside_of_ast(attrs, sub_mod); + let mut mods_outside_ast = self.find_mods_outside_of_ast(sub_mod); match self .parse_sess - .default_submod_path(mod_name, relative, &self.directory.path) + .default_submod_path(sub_mod.ident(), relative, &self.directory.path) .result { Ok(ModulePathSuccess { @@ -341,13 +398,13 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { Ok(m) if outside_mods_empty => Ok(Some(SubModKind::External( path, ownership, - Module::new(Cow::Owned(m.0), &m.1), + Module::new(Cow::Owned(m.0), sub_mod.ast_item.clone(), &m.1), ))), Ok(m) => { mods_outside_ast.push(( path.clone(), ownership, - Module::new(Cow::Owned(m.0), &m.1), + Module::new(Cow::Owned(m.0), sub_mod.ast_item.clone(), &m.1), )); if should_insert { mods_outside_ast.push((path, ownership, sub_mod.clone())); @@ -355,11 +412,11 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { Ok(Some(SubModKind::MultiExternal(mods_outside_ast))) } Err(ParserError::ParseError) => Err(ModuleResolutionError { - module: mod_name.to_string(), + module: sub_mod.name(), kind: ModuleResolutionErrorKind::ParseError { file: path }, }), Err(..) if outside_mods_empty => Err(ModuleResolutionError { - module: mod_name.to_string(), + module: sub_mod.name(), kind: ModuleResolutionErrorKind::NotFound { file: path }, }), Err(..) => { @@ -377,7 +434,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { Err(mut e) => { e.cancel(); Err(ModuleResolutionError { - module: mod_name.to_string(), + module: sub_mod.name(), kind: ModuleResolutionErrorKind::NotFound { file: self.directory.path.clone(), }, @@ -409,12 +466,11 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { fn find_mods_outside_of_ast( &self, - attrs: &[ast::Attribute], sub_mod: &Module<'ast>, ) -> Vec<(PathBuf, DirectoryOwnership, Module<'ast>)> { // Filter nested path, like `#[cfg_attr(feature = "foo", path = "bar.rs")]`. let mut path_visitor = visitor::PathVisitor::default(); - for attr in attrs.iter() { + for attr in sub_mod.outer_attrs() { if let Some(meta) = attr.meta() { path_visitor.visit_meta_item(&meta) } @@ -450,7 +506,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { result.push(( actual_path, DirectoryOwnership::Owned { relative: None }, - Module::new(Cow::Owned(m.0), &m.1), + Module::new(Cow::Owned(m.0), sub_mod.ast_item.clone(), &m.1), )) } result diff --git a/src/formatting/reorder.rs b/src/formatting/reorder.rs index 1f5312b2f73..d0afc50032a 100644 --- a/src/formatting/reorder.rs +++ b/src/formatting/reorder.rs @@ -12,6 +12,7 @@ use rustc_ast::{ast, attr}; use rustc_span::{symbol::sym, Span}; use crate::config::Config; +use crate::formatting::modules::{get_mod_inner_attrs, FileModMap}; use crate::formatting::{ imports::{merge_use_trees, UseTree}, items::{is_mod_decl, rewrite_extern_crate, rewrite_mod}, @@ -263,8 +264,8 @@ fn rewrite_reorderable_items( } } -fn contains_macro_use_attr(item: &ast::Item) -> bool { - attr::contains_name(&item.attrs, sym::macro_use) +fn contains_macro_use_attr(attrs: &[ast::Attribute]) -> bool { + attr::contains_name(attrs, sym::macro_use) } /// A simplified version of `ast::ItemKind`. @@ -279,20 +280,26 @@ enum ReorderableItemKind { } impl ReorderableItemKind { - fn from(item: &ast::Item) -> Self { + fn from(item: &ast::Item, file_mod_map: &FileModMap<'_>) -> Self { match item.kind { - _ if contains_macro_use_attr(item) | contains_skip(&item.attrs) => { + _ if contains_macro_use_attr(&item.attrs) | contains_skip(&item.attrs) => { ReorderableItemKind::Other } ast::ItemKind::ExternCrate(..) => ReorderableItemKind::ExternCrate, - ast::ItemKind::Mod(..) if is_mod_decl(item) => ReorderableItemKind::Mod, + ast::ItemKind::Mod(..) + if is_mod_decl(item) + && !get_mod_inner_attrs(item, file_mod_map) + .map_or(false, contains_macro_use_attr) => + { + ReorderableItemKind::Mod + } ast::ItemKind::Use(..) => ReorderableItemKind::Use, _ => ReorderableItemKind::Other, } } - fn is_same_item_kind(self, item: &ast::Item) -> bool { - ReorderableItemKind::from(item) == self + fn is_same_item_kind(self, item: &ast::Item, file_mod_map: &FileModMap<'_>) -> bool { + ReorderableItemKind::from(item, file_mod_map) == self } fn is_reorderable(self, config: &Config) -> bool { @@ -328,7 +335,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { let item_length = items .iter() .take_while(|ppi| { - item_kind.is_same_item_kind(&***ppi) + item_kind.is_same_item_kind(&***ppi, self.file_mod_map) && (!in_group || { let current = self.parse_sess.lookup_line_range(ppi.span()); let in_same_group = current.lo < last.hi + 2; @@ -365,7 +372,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { // If the next item is a `use`, `extern crate` or `mod`, then extract it and any // subsequent items that have the same item kind to be reordered within // `walk_reorderable_items`. Otherwise, just format the next item for output. - let item_kind = ReorderableItemKind::from(items[0]); + let item_kind = ReorderableItemKind::from(items[0], self.file_mod_map); if item_kind.is_reorderable(self.config) { let visited_items_num = self.walk_reorderable_items(items, item_kind, item_kind.in_group()); diff --git a/src/formatting/rewrite.rs b/src/formatting/rewrite.rs index 306e4658b41..fc11b065a44 100644 --- a/src/formatting/rewrite.rs +++ b/src/formatting/rewrite.rs @@ -8,6 +8,7 @@ use rustc_span::Span; use crate::config::{Config, IndentStyle}; use crate::formatting::{ + modules::FileModMap, report::{FormatReport, NonFormattedRange}, shape::Shape, skip::SkipContext, @@ -29,6 +30,7 @@ impl Rewrite for ptr::P { #[derive(Clone)] pub(crate) struct RewriteContext<'a> { pub(crate) parse_sess: &'a ParseSess, + pub(crate) file_mod_map: &'a FileModMap<'a>, pub(crate) config: &'a Config, pub(crate) inside_macro: Rc>, // Force block indent style even if we are using visual indent style. diff --git a/src/formatting/visitor.rs b/src/formatting/visitor.rs index 19fa49ae9f1..262ec5cf64e 100644 --- a/src/formatting/visitor.rs +++ b/src/formatting/visitor.rs @@ -5,7 +5,6 @@ use rustc_ast::{ast, attr::HasAttrs, token::DelimToken, visit}; use rustc_span::{symbol, BytePos, Pos, Span}; use crate::config::{BraceStyle, Config}; -use crate::formatting::modules::Module; use crate::formatting::{ attr::*, comment::{rewrite_comment, CodeCharKind, CommentCodeSlices}, @@ -15,6 +14,7 @@ use crate::formatting::{ rewrite_opaque_type, rewrite_type_alias, FnBraceStyle, FnSig, StaticParts, StructParts, }, macros::{macro_style, rewrite_macro, rewrite_macro_def, MacroPosition}, + modules::{FileModMap, Module}, report::{FormatReport, NonFormattedRange}, rewrite::{Rewrite, RewriteContext}, shape::{Indent, Shape}, @@ -73,6 +73,7 @@ impl SnippetProvider { pub(crate) struct FmtVisitor<'a> { parent_context: Option<&'a RewriteContext<'a>>, pub(crate) parse_sess: &'a ParseSess, + pub(crate) file_mod_map: &'a FileModMap<'a>, pub(crate) buffer: String, pub(crate) last_pos: BytePos, // FIXME: use an RAII util or closure for indenting @@ -788,6 +789,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { ctx.parse_sess, ctx.config, ctx.snippet_provider, + ctx.file_mod_map, ctx.report.clone(), ); visitor.skip_context.update(ctx.skip_context.clone()); @@ -799,10 +801,12 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { parse_session: &'a ParseSess, config: &'a Config, snippet_provider: &'a SnippetProvider, + file_mod_map: &'a FileModMap<'_>, report: FormatReport, ) -> FmtVisitor<'a> { FmtVisitor { parent_context: None, + file_mod_map, parse_sess: parse_session, buffer: String::with_capacity(snippet_provider.big_snippet.len() * 2), last_pos: BytePos(0), @@ -992,6 +996,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { pub(crate) fn get_context(&self) -> RewriteContext<'_> { RewriteContext { parse_sess: self.parse_sess, + file_mod_map: self.file_mod_map, config: self.config, inside_macro: Rc::new(Cell::new(false)), use_block: Cell::new(false), diff --git a/tests/source/macro_use/a.rs b/tests/source/macro_use/a.rs new file mode 100644 index 00000000000..0f000af91eb --- /dev/null +++ b/tests/source/macro_use/a.rs @@ -0,0 +1 @@ +my_macro!( ) ; \ No newline at end of file diff --git a/tests/source/macro_use/b.rs b/tests/source/macro_use/b.rs new file mode 100644 index 00000000000..422c963df37 --- /dev/null +++ b/tests/source/macro_use/b.rs @@ -0,0 +1,6 @@ +#![macro_use] + +#[macro_export] +macro_rules! my_macro { + () => {}; +} diff --git a/tests/source/macro_use/c.rs b/tests/source/macro_use/c.rs new file mode 100644 index 00000000000..4c1676e2a45 --- /dev/null +++ b/tests/source/macro_use/c.rs @@ -0,0 +1 @@ +another_macro!(); diff --git a/tests/source/macro_use/foo/bar/d.rs b/tests/source/macro_use/foo/bar/d.rs new file mode 100644 index 00000000000..74741c61a42 --- /dev/null +++ b/tests/source/macro_use/foo/bar/d.rs @@ -0,0 +1,6 @@ +#![macro_use] + +#[macro_export] +macro_rules! another_macro { + () => {}; +} diff --git a/tests/source/macro_use/mod.rs b/tests/source/macro_use/mod.rs new file mode 100644 index 00000000000..4982ca020b2 --- /dev/null +++ b/tests/source/macro_use/mod.rs @@ -0,0 +1,5 @@ +mod b; +mod a; +#[path = "foo/bar/d.rs"] +mod d; +mod c; diff --git a/tests/target/macro_use/a.rs b/tests/target/macro_use/a.rs new file mode 100644 index 00000000000..6c9408f6b0d --- /dev/null +++ b/tests/target/macro_use/a.rs @@ -0,0 +1 @@ +my_macro!(); diff --git a/tests/target/macro_use/b.rs b/tests/target/macro_use/b.rs new file mode 100644 index 00000000000..422c963df37 --- /dev/null +++ b/tests/target/macro_use/b.rs @@ -0,0 +1,6 @@ +#![macro_use] + +#[macro_export] +macro_rules! my_macro { + () => {}; +} diff --git a/tests/target/macro_use/c.rs b/tests/target/macro_use/c.rs new file mode 100644 index 00000000000..4c1676e2a45 --- /dev/null +++ b/tests/target/macro_use/c.rs @@ -0,0 +1 @@ +another_macro!(); diff --git a/tests/target/macro_use/foo/bar/d.rs b/tests/target/macro_use/foo/bar/d.rs new file mode 100644 index 00000000000..74741c61a42 --- /dev/null +++ b/tests/target/macro_use/foo/bar/d.rs @@ -0,0 +1,6 @@ +#![macro_use] + +#[macro_export] +macro_rules! another_macro { + () => {}; +} diff --git a/tests/target/macro_use/mod.rs b/tests/target/macro_use/mod.rs new file mode 100644 index 00000000000..4982ca020b2 --- /dev/null +++ b/tests/target/macro_use/mod.rs @@ -0,0 +1,5 @@ +mod b; +mod a; +#[path = "foo/bar/d.rs"] +mod d; +mod c;