Skip to content

Commit

Permalink
Merge pull request #2603 from topecongiro/merge-nested-imports
Browse files Browse the repository at this point in the history
Merge imports
  • Loading branch information
nrc authored Apr 12, 2018
2 parents ba5b4fa + 8820a59 commit 55dd8f1
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 17 deletions.
22 changes: 22 additions & 0 deletions Configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,28 @@ use foo::{aaa,
fff};
```

## `merge_imports`

Merge multiple imports into a single nested import.

- **Default value**: `false`
- **Possible values**: `true`, `false`
- **Stable**: No

#### `false` (default):

```rust
use foo::{a, c, d};
use foo::{b, g};
use foo::{e, f};
```

#### `true`:

```rust
use foo::{a, b, c, d, e, f, g};
```


## `match_block_trailing_comma`

Expand Down
1 change: 1 addition & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ create_config! {
// Imports
imports_indent: IndentStyle, IndentStyle::Visual, false, "Indent of imports";
imports_layout: ListTactic, ListTactic::Mixed, false, "Item layout inside a import block";
merge_imports: bool, false, false, "Merge imports";

// Ordering
reorder_imports: bool, true, false, "Reorder import and extern crate statements alphabetically";
Expand Down
277 changes: 270 additions & 7 deletions src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::cmp::Ordering;

use config::lists::*;
use syntax::ast::{self, UseTreeKind};
use syntax::codemap::{BytePos, Span};
use syntax::codemap::{self, BytePos, Span, DUMMY_SP};

use codemap::SpanUtils;
use config::IndentStyle;
Expand All @@ -24,6 +24,7 @@ use utils::mk_sp;
use visitor::FmtVisitor;

use std::borrow::Cow;
use std::fmt;

/// Returns a name imported by a `use` declaration. e.g. returns `Ordering`
/// for `std::cmp::Ordering` and `self` for `std::cmp::self`.
Expand Down Expand Up @@ -89,7 +90,7 @@ impl<'a> FmtVisitor<'a> {
// sorting.

// FIXME we do a lot of allocation to make our own representation.
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Clone, Eq, PartialEq)]
pub enum UseSegment {
Ident(String, Option<String>),
Slf(Option<String>),
Expand All @@ -98,12 +99,12 @@ pub enum UseSegment {
List(Vec<UseTree>),
}

#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct UseTree {
pub path: Vec<UseSegment>,
pub span: Span,
// Comment information within nested use tree.
list_item: Option<ListItem>,
pub list_item: Option<ListItem>,
// Additional fields for top level use items.
// Should we have another struct for top-level use items rather than reusing this?
visibility: Option<ast::Visibility>,
Expand Down Expand Up @@ -143,6 +144,78 @@ impl UseSegment {
}
}

pub fn merge_use_trees(use_trees: Vec<UseTree>) -> Vec<UseTree> {
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() {
result.push(use_tree);
continue;
}

for flattened in use_tree.flatten() {
merge_use_trees_inner(&mut result, flattened);
}
}
result
}

fn merge_use_trees_inner(trees: &mut Vec<UseTree>, use_tree: UseTree) {
for tree in trees.iter_mut() {
if tree.share_prefix(&use_tree) {
tree.merge(use_tree);
return;
}
}

trees.push(use_tree);
}

impl fmt::Debug for UseTree {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

impl fmt::Debug for UseSegment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

impl fmt::Display for UseSegment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
UseSegment::Glob => write!(f, "*"),
UseSegment::Ident(ref s, _) => write!(f, "{}", s),
UseSegment::Slf(..) => write!(f, "self"),
UseSegment::Super(..) => write!(f, "super"),
UseSegment::List(ref list) => {
write!(f, "{{")?;
for (i, item) in list.iter().enumerate() {
let is_last = i == list.len() - 1;
write!(f, "{}", item)?;
if !is_last {
write!(f, ", ")?;
}
}
write!(f, "}}")
}
}
}
}
impl fmt::Display for UseTree {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, segment) in self.path.iter().enumerate() {
let is_last = i == self.path.len() - 1;
write!(f, "{}", segment)?;
if !is_last {
write!(f, "::")?;
}
}
write!(f, "")
}
}

impl UseTree {
// Rewrite use tree with `use ` and a trailing `;`.
pub fn rewrite_top_level(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
Expand All @@ -168,6 +241,21 @@ impl UseTree {
Some(result)
}

// FIXME: Use correct span?
// The given span is essentially incorrect, since we are reconstructing
// use statements. This should not be a problem, though, since we have
// already tried to extract comment and observed that there are no comment
// around the given use item, and the span will not be used afterward.
fn from_path(path: Vec<UseSegment>, span: Span) -> UseTree {
UseTree {
path,
span,
list_item: None,
visibility: None,
attrs: None,
}
}

pub fn from_ast_with_normalization(
context: &RewriteContext,
item: &ast::Item,
Expand Down Expand Up @@ -358,6 +446,124 @@ impl UseTree {
self.path.push(last);
self
}

fn has_comment(&self) -> bool {
self.list_item.as_ref().map_or(false, ListItem::has_comment)
}

fn same_visibility(&self, other: &UseTree) -> bool {
match (&self.visibility, &other.visibility) {
(
Some(codemap::Spanned {
node: ast::VisibilityKind::Inherited,
..
}),
None,
)
| (
None,
Some(codemap::Spanned {
node: ast::VisibilityKind::Inherited,
..
}),
)
| (None, None) => true,
(
Some(codemap::Spanned { node: lnode, .. }),
Some(codemap::Spanned { node: rnode, .. }),
) => lnode == rnode,
_ => false,
}
}

fn share_prefix(&self, other: &UseTree) -> bool {
if self.path.is_empty() || other.path.is_empty() || self.attrs.is_some()
|| !self.same_visibility(other)
{
false
} else {
self.path[0] == other.path[0]
}
}

fn flatten(self) -> Vec<UseTree> {
if self.path.is_empty() {
return vec![self];
}
match self.path.clone().last().unwrap() {
UseSegment::List(list) => {
let prefix = &self.path[..self.path.len() - 1];
let mut result = vec![];
for nested_use_tree in list.into_iter() {
for mut flattend in nested_use_tree.clone().flatten().iter_mut() {
let mut new_path = prefix.to_vec();
new_path.append(&mut flattend.path);
result.push(UseTree {
path: new_path,
span: self.span,
list_item: None,
visibility: self.visibility.clone(),
attrs: None,
});
}
}

result
}
_ => vec![self],
}
}

fn merge(&mut self, other: UseTree) {
let mut new_path = vec![];
for (mut a, b) in self.path
.clone()
.iter_mut()
.zip(other.path.clone().into_iter())
{
if *a == b {
new_path.push(b);
} else {
break;
}
}
if let Some(merged) = merge_rest(&self.path, &other.path, new_path.len()) {
new_path.push(merged);
self.span = self.span.to(other.span);
}
self.path = new_path;
}
}

fn merge_rest(a: &[UseSegment], b: &[UseSegment], len: usize) -> Option<UseSegment> {
let a_rest = &a[len..];
let b_rest = &b[len..];
if a_rest.is_empty() && b_rest.is_empty() {
return None;
}
if a_rest.is_empty() {
return Some(UseSegment::List(vec![
UseTree::from_path(vec![UseSegment::Slf(None)], DUMMY_SP),
UseTree::from_path(b_rest.to_vec(), DUMMY_SP),
]));
}
if b_rest.is_empty() {
return Some(UseSegment::List(vec![
UseTree::from_path(vec![UseSegment::Slf(None)], DUMMY_SP),
UseTree::from_path(a_rest.to_vec(), DUMMY_SP),
]));
}
if let UseSegment::List(mut list) = a_rest[0].clone() {
merge_use_trees_inner(&mut list, UseTree::from_path(b_rest.to_vec(), DUMMY_SP));
list.sort();
return Some(UseSegment::List(list.clone()));
}
let mut list = vec![
UseTree::from_path(a_rest.to_vec(), DUMMY_SP),
UseTree::from_path(b_rest.to_vec(), DUMMY_SP),
];
list.sort();
Some(UseSegment::List(list))
}

impl PartialOrd for UseSegment {
Expand Down Expand Up @@ -459,9 +665,12 @@ fn rewrite_nested_use_tree(
IndentStyle::Visual => shape.visual_indent(0),
};
for use_tree in use_tree_list {
let mut list_item = use_tree.list_item.clone()?;
list_item.item = use_tree.rewrite(context, nested_shape);
list_items.push(list_item);
if let Some(mut list_item) = use_tree.list_item.clone() {
list_item.item = use_tree.rewrite(context, nested_shape);
list_items.push(list_item);
} else {
list_items.push(ListItem::from_str(use_tree.rewrite(context, nested_shape)?));
}
}
let (tactic, remaining_width) = if use_tree_list.iter().any(|use_segment| {
use_segment
Expand Down Expand Up @@ -682,6 +891,60 @@ mod test {
parser.parse_in_list()
}

macro parse_use_trees($($s:expr),* $(,)*) {
vec![
$(parse_use_tree($s),)*
]
}

#[test]
fn test_use_tree_merge() {
macro test_merge([$($input:expr),* $(,)*], [$($output:expr),* $(,)*]) {
assert_eq!(
merge_use_trees(parse_use_trees!($($input,)*)),
parse_use_trees!($($output,)*),
);
}

test_merge!(["a::b::{c, d}", "a::b::{e, f}"], ["a::b::{c, d, e, f}"]);
test_merge!(["a::b::c", "a::b"], ["a::b::{self, c}"]);
test_merge!(["a::b", "a::b"], ["a::b"]);
test_merge!(["a", "a::b", "a::b::c"], ["a::{self, b::{self, c}}"]);
test_merge!(
["a::{b::{self, c}, d::e}", "a::d::f"],
["a::{b::{self, c}, d::{e, f}}"]
);
test_merge!(
["a::d::f", "a::{b::{self, c}, d::e}"],
["a::{b::{self, c}, d::{e, f}}"]
);
test_merge!(
["a::{c, d, b}", "a::{d, e, b, a, f}", "a::{f, g, c}"],
["a::{a, b, c, d, e, f, g}"]
);
}

#[test]
fn test_use_tree_flatten() {
assert_eq!(
parse_use_tree("a::b::{c, d, e, f}").flatten(),
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_trees![
"a::b::c::d",
"a::b::c::e",
"a::b::c::f",
"a::b::g",
"a::b::h::i",
"a::b::h::j",
"a::b::h::k",
]
);
}

#[test]
fn test_use_tree_normalize() {
assert_eq!(parse_use_tree("a::self").normalize(), parse_use_tree("a"));
Expand Down
10 changes: 10 additions & 0 deletions src/lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ pub struct ListItem {
}

impl ListItem {
pub fn empty() -> ListItem {
ListItem {
pre_comment: None,
pre_comment_style: ListItemCommentStyle::None,
item: None,
post_comment: None,
new_lines: false,
}
}

pub fn inner_as_ref(&self) -> &str {
self.item.as_ref().map_or("", |s| s)
}
Expand Down
Loading

0 comments on commit 55dd8f1

Please sign in to comment.