Skip to content

Commit

Permalink
Add #[rustfmt::sort] for struct structs
Browse files Browse the repository at this point in the history
  • Loading branch information
Code0x58 committed Sep 4, 2024
1 parent 1695605 commit 6af9fb6
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1684,6 +1684,7 @@ fn rewrite_struct_lit<'a>(
v_shape,
mk_sp(body_lo, span.hi()),
one_line_width,
None,
)
.unknown_error()?
} else {
Expand Down
43 changes: 38 additions & 5 deletions src/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use itertools::Itertools;
use regex::Regex;
use rustc_ast::{AttrVec, visit};
use rustc_ast::visit;
use rustc_ast::{ast, ptr};
use rustc_span::{BytePos, DUMMY_SP, Span, symbol};
use std::borrow::Cow;
Expand Down Expand Up @@ -519,13 +519,19 @@ impl<'a> FmtVisitor<'a> {
self.push_rewrite(static_parts.span, rewrite);
}

pub(crate) fn visit_struct(&mut self, struct_parts: &StructParts<'_>) {
pub(crate) fn visit_struct(&mut self, struct_parts: &StructParts<'_>, sort: bool) {
let is_tuple = match struct_parts.def {
ast::VariantData::Tuple(..) => true,
_ => false,
};
let rewrite = format_struct(&self.get_context(), struct_parts, self.block_indent, None)
.map(|s| if is_tuple { s + ";" } else { s });
let rewrite = format_struct(
&self.get_context(),
struct_parts,
self.block_indent,
None,
sort,
)
.map(|s| if is_tuple { s + ";" } else { s });
self.push_rewrite(struct_parts.span, rewrite);
}

Expand Down Expand Up @@ -705,6 +711,7 @@ impl<'a> FmtVisitor<'a> {
&StructParts::from_variant(field, &context),
self.block_indent,
Some(one_line_width),
false,
)?,
ast::VariantData::Unit(..) => rewrite_ident(&context, field.ident).to_owned(),
};
Expand Down Expand Up @@ -1153,14 +1160,15 @@ fn format_struct(
struct_parts: &StructParts<'_>,
offset: Indent,
one_line_width: Option<usize>,
sort: bool,
) -> Option<String> {
match struct_parts.def {
ast::VariantData::Unit(..) => format_unit_struct(context, struct_parts, offset),
ast::VariantData::Tuple(fields, _) => {
format_tuple_struct(context, struct_parts, fields, offset)
}
ast::VariantData::Struct { fields, .. } => {
format_struct_struct(context, struct_parts, fields, offset, one_line_width)
format_struct_struct(context, struct_parts, fields, offset, one_line_width, sort)
}
}
}
Expand Down Expand Up @@ -1439,6 +1447,7 @@ pub(crate) fn format_struct_struct(
fields: &[ast::FieldDef],
offset: Indent,
one_line_width: Option<usize>,
sort: bool,
) -> Option<String> {
let mut result = String::with_capacity(1024);
let span = struct_parts.span;
Expand Down Expand Up @@ -1507,12 +1516,36 @@ pub(crate) fn format_struct_struct(
let one_line_budget =
one_line_width.map_or(0, |one_line_width| min(one_line_width, one_line_budget));

let ranks: Option<Vec<_>> = if sort {
// get the sequence of indices that would sort the vec
let indices: Vec<usize> = fields
.iter()
.enumerate()
.sorted_by(|(_, field_a), (_, field_b)| {
field_a
.ident
.zip(field_b.ident)
.map(|(a, b)| a.name.as_str().cmp(b.name.as_str()))
.unwrap_or(Ordering::Equal)
})
.map(|(i, _)| i)
.collect();
// create a vec with ranks for the fields, allowing for use in Itertools.sorted_by_key
let mut ranks = vec![0; indices.len()];
for (rank, original_index) in indices.into_iter().enumerate() {
ranks[original_index] = rank;
}
Some(ranks)
} else {
None
};
let items_str = rewrite_with_alignment(
fields,
context,
Shape::indented(offset.block_indent(context.config), context.config).sub_width(1)?,
mk_sp(body_lo, span.hi()),
one_line_budget,
ranks.as_ref().map(|v| v.as_slice()),
)?;

if !items_str.contains('\n')
Expand Down
20 changes: 19 additions & 1 deletion src/vertical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub(crate) fn rewrite_with_alignment<T: AlignedItem>(
shape: Shape,
span: Span,
one_line_width: usize,
ranks: Option<&[usize]>,
) -> Option<String> {
let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
group_aligned_items(context, fields)
Expand Down Expand Up @@ -170,12 +171,20 @@ pub(crate) fn rewrite_with_alignment<T: AlignedItem>(
shape.indent,
one_line_width,
force_separator,
ranks.map(|v| &v[0..=group_index]),
)?;
if rest.is_empty() {
Some(result + spaces)
} else {
let rest_span = mk_sp(init_last_pos, span.hi());
let rest_str = rewrite_with_alignment(rest, context, shape, rest_span, one_line_width)?;
let rest_str = rewrite_with_alignment(
rest,
context,
shape,
rest_span,
one_line_width,
ranks.map(|v| &v[group_index + 1..]),
)?;
Some(format!(
"{}{}\n{}{}",
result,
Expand Down Expand Up @@ -211,6 +220,7 @@ fn rewrite_aligned_items_inner<T: AlignedItem>(
offset: Indent,
one_line_width: usize,
force_trailing_separator: bool,
ranks: Option<&[usize]>,
) -> Option<String> {
// 1 = ","
let item_shape = Shape::indented(offset, context.config).sub_width(1)?;
Expand Down Expand Up @@ -266,6 +276,14 @@ fn rewrite_aligned_items_inner<T: AlignedItem>(
.tactic(tactic)
.trailing_separator(separator_tactic)
.preserve_newline(true);
if let Some(ranks) = ranks {
items = ranks
.iter()
.zip(items.into_iter())
.sorted_by_key(|&(index, _)| *index)
.map(|(_, item)| item)
.collect();
}
write_list(&items, &fmt).ok()
}

Expand Down
7 changes: 4 additions & 3 deletions src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ use crate::source_map::{LineRangeUtils, SpanUtils};
use crate::spanned::Spanned;
use crate::stmt::Stmt;
use crate::utils::{
self, contains_skip, count_newlines, depr_skip_annotation, format_safety, inner_attributes,
last_line_width, mk_sp, ptr_vec_to_ref_vec, rewrite_ident, starts_with_newline, stmt_expr,
self, contains_skip, contains_sort, count_newlines, depr_skip_annotation, format_safety,
inner_attributes, last_line_width, mk_sp, ptr_vec_to_ref_vec, rewrite_ident,
starts_with_newline, stmt_expr,
};
use crate::{ErrorKind, FormatReport, FormattingError};

Expand Down Expand Up @@ -511,7 +512,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
self.push_rewrite(span, rw);
}
ast::ItemKind::Struct(..) | ast::ItemKind::Union(..) => {
self.visit_struct(&StructParts::from_item(item));
self.visit_struct(&StructParts::from_item(item), contains_sort(&item.attrs));
}
ast::ItemKind::Enum(ref def, ref generics) => {
self.format_missing_with_indent(source!(self, item.span).lo());
Expand Down
21 changes: 21 additions & 0 deletions tests/source/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,24 @@ struct Test {
// #2818
struct Paren((i32)) where i32: Trait;
struct Parens((i32, i32)) where i32: Trait;

// #3422
#[rustfmt::sort]
struct Foo {

#[skip]
b: u32,
a: u32, // a

bb: u32,
/// A
aa: u32,
}

#[rustfmt::sort]
struct Fooy {
a: u32, // a
b: u32,
/// C
c: u32,
}
20 changes: 20 additions & 0 deletions tests/target/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,23 @@ where
struct Parens((i32, i32))
where
i32: Trait;

// #3422
#[rustfmt::sort]
struct Foo {
a: u32, // a

/// A
aa: u32,
#[skip]
b: u32,
bb: u32,
}

#[rustfmt::sort]
struct Fooy {
a: u32, // a
b: u32,
/// C
c: u32,
}

0 comments on commit 6af9fb6

Please sign in to comment.