diff --git a/src/imports.rs b/src/imports.rs index 02319809486..efe4e9498c9 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -10,6 +10,7 @@ use rustc_span::{ use crate::comment::combine_strs_with_missing_comments; use crate::config::lists::*; +use crate::config::ImportGranularity; use crate::config::{Edition, IndentStyle}; use crate::lists::{ definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator, @@ -182,7 +183,18 @@ impl UseSegment { } } -pub(crate) fn merge_use_trees(use_trees: Vec, merge_by: SharedPrefix) -> Vec { +pub(crate) fn normalize_use_trees_with_granularity( + use_trees: Vec, + import_granularity: ImportGranularity, +) -> Vec { + let merge_by = match import_granularity { + ImportGranularity::Item => return flatten_use_trees(use_trees, ImportGranularity::Item), + ImportGranularity::Preserve => return use_trees, + ImportGranularity::Crate => SharedPrefix::Crate, + ImportGranularity::Module => SharedPrefix::Module, + ImportGranularity::One => SharedPrefix::One, + }; + let mut result = Vec::with_capacity(use_trees.len()); for use_tree in use_trees { if use_tree.has_comment() || use_tree.attrs.is_some() { @@ -190,7 +202,7 @@ pub(crate) fn merge_use_trees(use_trees: Vec, merge_by: SharedPrefix) - continue; } - for mut flattened in use_tree.flatten() { + for mut flattened in use_tree.flatten(import_granularity) { if let Some(tree) = result .iter_mut() .find(|tree| tree.share_prefix(&flattened, merge_by)) @@ -208,10 +220,13 @@ pub(crate) fn merge_use_trees(use_trees: Vec, merge_by: SharedPrefix) - result } -pub(crate) fn flatten_use_trees(use_trees: Vec) -> Vec { +fn flatten_use_trees( + use_trees: Vec, + import_granularity: ImportGranularity, +) -> Vec { use_trees .into_iter() - .flat_map(UseTree::flatten) + .flat_map(|tree| tree.flatten(import_granularity)) .map(UseTree::nest_trailing_self) .collect() } @@ -581,7 +596,7 @@ impl UseTree { } } - fn flatten(self) -> Vec { + fn flatten(self, import_granularity: ImportGranularity) -> Vec { if self.path.is_empty() { return vec![self]; } @@ -595,7 +610,7 @@ impl UseTree { let prefix = &self.path[..self.path.len() - 1]; let mut result = vec![]; for nested_use_tree in list { - for flattend in &mut nested_use_tree.clone().flatten() { + for flattend in &mut nested_use_tree.clone().flatten(import_granularity) { let mut new_path = prefix.to_vec(); new_path.append(&mut flattend.path); result.push(UseTree { @@ -603,7 +618,11 @@ impl UseTree { span: self.span, list_item: None, visibility: self.visibility.clone(), - attrs: None, + // only retain attributes for `ImportGranularity::Item` + attrs: match import_granularity { + ImportGranularity::Item => self.attrs.clone(), + _ => None, + }, }); } } @@ -951,7 +970,7 @@ impl Rewrite for UseTree { } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub(crate) enum SharedPrefix { +enum SharedPrefix { Crate, Module, One, @@ -1106,7 +1125,10 @@ mod test { macro_rules! test_merge { ($by:ident, [$($input:expr),* $(,)*], [$($output:expr),* $(,)*]) => { assert_eq!( - merge_use_trees(parse_use_trees!($($input,)*), SharedPrefix::$by), + normalize_use_trees_with_granularity( + parse_use_trees!($($input,)*), + ImportGranularity::$by, + ), parse_use_trees!($($output,)*), ); } @@ -1215,12 +1237,18 @@ mod test { #[test] fn test_flatten_use_trees() { assert_eq!( - flatten_use_trees(parse_use_trees!["foo::{a::{b, c}, d::e}"]), + flatten_use_trees( + parse_use_trees!["foo::{a::{b, c}, d::e}"], + ImportGranularity::Item + ), parse_use_trees!["foo::a::b", "foo::a::c", "foo::d::e"] ); assert_eq!( - flatten_use_trees(parse_use_trees!["foo::{self, a, b::{c, d}, e::*}"]), + flatten_use_trees( + parse_use_trees!["foo::{self, a, b::{c, d}, e::*}"], + ImportGranularity::Item + ), parse_use_trees![ "foo::{self}", "foo::a", @@ -1234,12 +1262,13 @@ mod test { #[test] fn test_use_tree_flatten() { assert_eq!( - parse_use_tree("a::b::{c, d, e, f}").flatten(), + parse_use_tree("a::b::{c, d, e, f}").flatten(ImportGranularity::Item), parse_use_trees!("a::b::c", "a::b::d", "a::b::e", "a::b::f",) ); assert_eq!( - parse_use_tree("a::b::{c::{d, e, f}, g, h::{i, j, k}}").flatten(), + parse_use_tree("a::b::{c::{d, e, f}, g, h::{i, j, k}}") + .flatten(ImportGranularity::Item), parse_use_trees![ "a::b::c::d", "a::b::c::e", diff --git a/src/reorder.rs b/src/reorder.rs index 13bfc92507d..8ae297de25b 100644 --- a/src/reorder.rs +++ b/src/reorder.rs @@ -11,8 +11,8 @@ use std::cmp::{Ord, Ordering}; use rustc_ast::ast; use rustc_span::{symbol::sym, Span}; -use crate::config::{Config, GroupImportsTactic, ImportGranularity}; -use crate::imports::{flatten_use_trees, merge_use_trees, SharedPrefix, UseSegment, UseTree}; +use crate::config::{Config, GroupImportsTactic}; +use crate::imports::{normalize_use_trees_with_granularity, UseSegment, UseTree}; use crate::items::{is_mod_decl, rewrite_extern_crate, rewrite_mod}; use crate::lists::{itemize_list, write_list, ListFormatting, ListItem}; use crate::rewrite::RewriteContext; @@ -107,15 +107,10 @@ fn rewrite_reorderable_or_regroupable_items( for (item, list_item) in normalized_items.iter_mut().zip(list_items) { item.list_item = Some(list_item.clone()); } - normalized_items = match context.config.imports_granularity() { - ImportGranularity::Crate => merge_use_trees(normalized_items, SharedPrefix::Crate), - ImportGranularity::Module => { - merge_use_trees(normalized_items, SharedPrefix::Module) - } - ImportGranularity::Item => flatten_use_trees(normalized_items), - ImportGranularity::One => merge_use_trees(normalized_items, SharedPrefix::One), - ImportGranularity::Preserve => normalized_items, - }; + normalized_items = normalize_use_trees_with_granularity( + normalized_items, + context.config.imports_granularity(), + ); let mut regrouped_items = match context.config.group_imports() { GroupImportsTactic::Preserve | GroupImportsTactic::One => { diff --git a/tests/source/issue-5030.rs b/tests/source/issue-5030.rs new file mode 100644 index 00000000000..08ffaac7d1d --- /dev/null +++ b/tests/source/issue-5030.rs @@ -0,0 +1,22 @@ +// rustfmt-imports_granularity: Item +// rustfmt-group_imports: One + +// Confirm that attributes are duplicated to all items in the use statement +#[cfg(feature = "foo")] +use std::collections::{ + HashMap, + HashSet, +}; + +// Separate the imports below from the ones above +const A: usize = 0; + +// Copying attrs works with import grouping as well +#[cfg(feature = "foo")] +use std::collections::{ + HashMap, + HashSet, +}; + +#[cfg(feature = "spam")] +use qux::{bar, baz}; diff --git a/tests/target/issue-5030.rs b/tests/target/issue-5030.rs new file mode 100644 index 00000000000..8ac3888bdbe --- /dev/null +++ b/tests/target/issue-5030.rs @@ -0,0 +1,21 @@ +// rustfmt-imports_granularity: Item +// rustfmt-group_imports: One + +// Confirm that attributes are duplicated to all items in the use statement +#[cfg(feature = "foo")] +use std::collections::HashMap; +#[cfg(feature = "foo")] +use std::collections::HashSet; + +// Separate the imports below from the ones above +const A: usize = 0; + +// Copying attrs works with import grouping as well +#[cfg(feature = "spam")] +use qux::bar; +#[cfg(feature = "spam")] +use qux::baz; +#[cfg(feature = "foo")] +use std::collections::HashMap; +#[cfg(feature = "foo")] +use std::collections::HashSet;