Skip to content

Commit 6af9fb6

Browse files
committed
Add #[rustfmt::sort] for struct structs
1 parent 1695605 commit 6af9fb6

File tree

6 files changed

+103
-9
lines changed

6 files changed

+103
-9
lines changed

src/expr.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,7 @@ fn rewrite_struct_lit<'a>(
16841684
v_shape,
16851685
mk_sp(body_lo, span.hi()),
16861686
one_line_width,
1687+
None,
16871688
)
16881689
.unknown_error()?
16891690
} else {

src/items.rs

+38-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use itertools::Itertools;
44
use regex::Regex;
5-
use rustc_ast::{AttrVec, visit};
5+
use rustc_ast::visit;
66
use rustc_ast::{ast, ptr};
77
use rustc_span::{BytePos, DUMMY_SP, Span, symbol};
88
use std::borrow::Cow;
@@ -519,13 +519,19 @@ impl<'a> FmtVisitor<'a> {
519519
self.push_rewrite(static_parts.span, rewrite);
520520
}
521521

522-
pub(crate) fn visit_struct(&mut self, struct_parts: &StructParts<'_>) {
522+
pub(crate) fn visit_struct(&mut self, struct_parts: &StructParts<'_>, sort: bool) {
523523
let is_tuple = match struct_parts.def {
524524
ast::VariantData::Tuple(..) => true,
525525
_ => false,
526526
};
527-
let rewrite = format_struct(&self.get_context(), struct_parts, self.block_indent, None)
528-
.map(|s| if is_tuple { s + ";" } else { s });
527+
let rewrite = format_struct(
528+
&self.get_context(),
529+
struct_parts,
530+
self.block_indent,
531+
None,
532+
sort,
533+
)
534+
.map(|s| if is_tuple { s + ";" } else { s });
529535
self.push_rewrite(struct_parts.span, rewrite);
530536
}
531537

@@ -705,6 +711,7 @@ impl<'a> FmtVisitor<'a> {
705711
&StructParts::from_variant(field, &context),
706712
self.block_indent,
707713
Some(one_line_width),
714+
false,
708715
)?,
709716
ast::VariantData::Unit(..) => rewrite_ident(&context, field.ident).to_owned(),
710717
};
@@ -1153,14 +1160,15 @@ fn format_struct(
11531160
struct_parts: &StructParts<'_>,
11541161
offset: Indent,
11551162
one_line_width: Option<usize>,
1163+
sort: bool,
11561164
) -> Option<String> {
11571165
match struct_parts.def {
11581166
ast::VariantData::Unit(..) => format_unit_struct(context, struct_parts, offset),
11591167
ast::VariantData::Tuple(fields, _) => {
11601168
format_tuple_struct(context, struct_parts, fields, offset)
11611169
}
11621170
ast::VariantData::Struct { fields, .. } => {
1163-
format_struct_struct(context, struct_parts, fields, offset, one_line_width)
1171+
format_struct_struct(context, struct_parts, fields, offset, one_line_width, sort)
11641172
}
11651173
}
11661174
}
@@ -1439,6 +1447,7 @@ pub(crate) fn format_struct_struct(
14391447
fields: &[ast::FieldDef],
14401448
offset: Indent,
14411449
one_line_width: Option<usize>,
1450+
sort: bool,
14421451
) -> Option<String> {
14431452
let mut result = String::with_capacity(1024);
14441453
let span = struct_parts.span;
@@ -1507,12 +1516,36 @@ pub(crate) fn format_struct_struct(
15071516
let one_line_budget =
15081517
one_line_width.map_or(0, |one_line_width| min(one_line_width, one_line_budget));
15091518

1519+
let ranks: Option<Vec<_>> = if sort {
1520+
// get the sequence of indices that would sort the vec
1521+
let indices: Vec<usize> = fields
1522+
.iter()
1523+
.enumerate()
1524+
.sorted_by(|(_, field_a), (_, field_b)| {
1525+
field_a
1526+
.ident
1527+
.zip(field_b.ident)
1528+
.map(|(a, b)| a.name.as_str().cmp(b.name.as_str()))
1529+
.unwrap_or(Ordering::Equal)
1530+
})
1531+
.map(|(i, _)| i)
1532+
.collect();
1533+
// create a vec with ranks for the fields, allowing for use in Itertools.sorted_by_key
1534+
let mut ranks = vec![0; indices.len()];
1535+
for (rank, original_index) in indices.into_iter().enumerate() {
1536+
ranks[original_index] = rank;
1537+
}
1538+
Some(ranks)
1539+
} else {
1540+
None
1541+
};
15101542
let items_str = rewrite_with_alignment(
15111543
fields,
15121544
context,
15131545
Shape::indented(offset.block_indent(context.config), context.config).sub_width(1)?,
15141546
mk_sp(body_lo, span.hi()),
15151547
one_line_budget,
1548+
ranks.as_ref().map(|v| v.as_slice()),
15161549
)?;
15171550

15181551
if !items_str.contains('\n')

src/vertical.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub(crate) fn rewrite_with_alignment<T: AlignedItem>(
114114
shape: Shape,
115115
span: Span,
116116
one_line_width: usize,
117+
ranks: Option<&[usize]>,
117118
) -> Option<String> {
118119
let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
119120
group_aligned_items(context, fields)
@@ -170,12 +171,20 @@ pub(crate) fn rewrite_with_alignment<T: AlignedItem>(
170171
shape.indent,
171172
one_line_width,
172173
force_separator,
174+
ranks.map(|v| &v[0..=group_index]),
173175
)?;
174176
if rest.is_empty() {
175177
Some(result + spaces)
176178
} else {
177179
let rest_span = mk_sp(init_last_pos, span.hi());
178-
let rest_str = rewrite_with_alignment(rest, context, shape, rest_span, one_line_width)?;
180+
let rest_str = rewrite_with_alignment(
181+
rest,
182+
context,
183+
shape,
184+
rest_span,
185+
one_line_width,
186+
ranks.map(|v| &v[group_index + 1..]),
187+
)?;
179188
Some(format!(
180189
"{}{}\n{}{}",
181190
result,
@@ -211,6 +220,7 @@ fn rewrite_aligned_items_inner<T: AlignedItem>(
211220
offset: Indent,
212221
one_line_width: usize,
213222
force_trailing_separator: bool,
223+
ranks: Option<&[usize]>,
214224
) -> Option<String> {
215225
// 1 = ","
216226
let item_shape = Shape::indented(offset, context.config).sub_width(1)?;
@@ -266,6 +276,14 @@ fn rewrite_aligned_items_inner<T: AlignedItem>(
266276
.tactic(tactic)
267277
.trailing_separator(separator_tactic)
268278
.preserve_newline(true);
279+
if let Some(ranks) = ranks {
280+
items = ranks
281+
.iter()
282+
.zip(items.into_iter())
283+
.sorted_by_key(|&(index, _)| *index)
284+
.map(|(_, item)| item)
285+
.collect();
286+
}
269287
write_list(&items, &fmt).ok()
270288
}
271289

src/visitor.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ use crate::source_map::{LineRangeUtils, SpanUtils};
2323
use crate::spanned::Spanned;
2424
use crate::stmt::Stmt;
2525
use crate::utils::{
26-
self, contains_skip, count_newlines, depr_skip_annotation, format_safety, inner_attributes,
27-
last_line_width, mk_sp, ptr_vec_to_ref_vec, rewrite_ident, starts_with_newline, stmt_expr,
26+
self, contains_skip, contains_sort, count_newlines, depr_skip_annotation, format_safety,
27+
inner_attributes, last_line_width, mk_sp, ptr_vec_to_ref_vec, rewrite_ident,
28+
starts_with_newline, stmt_expr,
2829
};
2930
use crate::{ErrorKind, FormatReport, FormattingError};
3031

@@ -511,7 +512,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
511512
self.push_rewrite(span, rw);
512513
}
513514
ast::ItemKind::Struct(..) | ast::ItemKind::Union(..) => {
514-
self.visit_struct(&StructParts::from_item(item));
515+
self.visit_struct(&StructParts::from_item(item), contains_sort(&item.attrs));
515516
}
516517
ast::ItemKind::Enum(ref def, ref generics) => {
517518
self.format_missing_with_indent(source!(self, item.span).lo());

tests/source/structs.rs

+21
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,24 @@ struct Test {
296296
// #2818
297297
struct Paren((i32)) where i32: Trait;
298298
struct Parens((i32, i32)) where i32: Trait;
299+
300+
// #3422
301+
#[rustfmt::sort]
302+
struct Foo {
303+
304+
#[skip]
305+
b: u32,
306+
a: u32, // a
307+
308+
bb: u32,
309+
/// A
310+
aa: u32,
311+
}
312+
313+
#[rustfmt::sort]
314+
struct Fooy {
315+
a: u32, // a
316+
b: u32,
317+
/// C
318+
c: u32,
319+
}

tests/target/structs.rs

+20
Original file line numberDiff line numberDiff line change
@@ -356,3 +356,23 @@ where
356356
struct Parens((i32, i32))
357357
where
358358
i32: Trait;
359+
360+
// #3422
361+
#[rustfmt::sort]
362+
struct Foo {
363+
a: u32, // a
364+
365+
/// A
366+
aa: u32,
367+
#[skip]
368+
b: u32,
369+
bb: u32,
370+
}
371+
372+
#[rustfmt::sort]
373+
struct Fooy {
374+
a: u32, // a
375+
b: u32,
376+
/// C
377+
c: u32,
378+
}

0 commit comments

Comments
 (0)