1use std::cmp;
4
5use itertools::Itertools;
6use rustc_ast::ast;
7use rustc_span::{BytePos, Span};
8
9use crate::comment::combine_strs_with_missing_comments;
10use crate::config::lists::*;
11use crate::expr::rewrite_field;
12use crate::items::{rewrite_struct_field, rewrite_struct_field_prefix};
13use crate::lists::{
14 ListFormatting, ListItem, Separator, definitive_tactic, itemize_list, write_list,
15};
16use crate::rewrite::{Rewrite, RewriteContext, RewriteResult};
17use crate::shape::{Indent, Shape};
18use crate::source_map::SpanUtils;
19use crate::spanned::Spanned;
20use crate::utils::{
21 contains_skip, is_attributes_extendable, mk_sp, rewrite_ident, trimmed_last_line_width,
22};
23
24pub(crate) trait AlignedItem {
25 fn skip(&self) -> bool;
26 fn get_span(&self) -> Span;
27 fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult;
28 fn rewrite_aligned_item(
29 &self,
30 context: &RewriteContext<'_>,
31 shape: Shape,
32 prefix_max_width: usize,
33 ) -> RewriteResult;
34}
35
36impl AlignedItem for ast::FieldDef {
37 fn skip(&self) -> bool {
38 contains_skip(&self.attrs)
39 }
40
41 fn get_span(&self) -> Span {
42 self.span()
43 }
44
45 fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
46 let attrs_str = self.attrs.rewrite_result(context, shape)?;
47 let missing_span = if self.attrs.is_empty() {
48 mk_sp(self.span.lo(), self.span.lo())
49 } else {
50 mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
51 };
52 let attrs_extendable = self.ident.is_none() && is_attributes_extendable(&attrs_str);
53 let field_str = rewrite_struct_field_prefix(context, self)?;
54 combine_strs_with_missing_comments(
55 context,
56 &attrs_str,
57 &field_str,
58 missing_span,
59 shape,
60 attrs_extendable,
61 )
62 }
63
64 fn rewrite_aligned_item(
65 &self,
66 context: &RewriteContext<'_>,
67 shape: Shape,
68 prefix_max_width: usize,
69 ) -> RewriteResult {
70 rewrite_struct_field(context, self, shape, prefix_max_width)
71 }
72}
73
74impl AlignedItem for ast::ExprField {
75 fn skip(&self) -> bool {
76 contains_skip(&self.attrs)
77 }
78
79 fn get_span(&self) -> Span {
80 self.span()
81 }
82
83 fn rewrite_prefix(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
84 let attrs_str = self.attrs.rewrite_result(context, shape)?;
85 let name = rewrite_ident(context, self.ident);
86 let missing_span = if self.attrs.is_empty() {
87 mk_sp(self.span.lo(), self.span.lo())
88 } else {
89 mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo())
90 };
91 combine_strs_with_missing_comments(
92 context,
93 &attrs_str,
94 name,
95 missing_span,
96 shape,
97 is_attributes_extendable(&attrs_str),
98 )
99 }
100
101 fn rewrite_aligned_item(
102 &self,
103 context: &RewriteContext<'_>,
104 shape: Shape,
105 prefix_max_width: usize,
106 ) -> RewriteResult {
107 rewrite_field(context, self, shape, prefix_max_width)
108 }
109}
110
111pub(crate) fn rewrite_with_alignment<T: AlignedItem>(
112 fields: &[T],
113 context: &RewriteContext<'_>,
114 shape: Shape,
115 span: Span,
116 one_line_width: usize,
117) -> Option<String> {
118 let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 {
119 group_aligned_items(context, fields)
120 } else {
121 ("", fields.len() - 1)
122 };
123 let init = &fields[0..=group_index];
124 let rest = &fields[group_index + 1..];
125 let init_last_pos = if rest.is_empty() {
126 span.hi()
127 } else {
128 let init_hi = init[init.len() - 1].get_span().hi();
130 let rest_lo = rest[0].get_span().lo();
131 let missing_span = mk_sp(init_hi, rest_lo);
132 let missing_span = mk_sp(
133 context.snippet_provider.span_after(missing_span, ","),
134 missing_span.hi(),
135 );
136
137 let snippet = context.snippet(missing_span);
138 if snippet.trim_start().starts_with("//") {
139 let offset = snippet.lines().next().map_or(0, str::len);
140 init_hi + BytePos(offset as u32 + 2)
142 } else if snippet.trim_start().starts_with("/*") {
143 let comment_lines = snippet
144 .lines()
145 .position(|line| line.trim_end().ends_with("*/"))
146 .unwrap_or(0);
147
148 let offset = snippet
149 .lines()
150 .take(comment_lines + 1)
151 .collect::<Vec<_>>()
152 .join("\n")
153 .len();
154
155 init_hi + BytePos(offset as u32 + 2)
156 } else {
157 missing_span.lo()
158 }
159 };
160 let init_span = mk_sp(span.lo(), init_last_pos);
161 let one_line_width = if rest.is_empty() { one_line_width } else { 0 };
162
163 let force_separator = !rest.is_empty();
165
166 let result = rewrite_aligned_items_inner(
167 context,
168 init,
169 init_span,
170 shape.indent,
171 one_line_width,
172 force_separator,
173 )?;
174 if rest.is_empty() {
175 Some(result + spaces)
176 } else {
177 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)?;
179 Some(format!(
180 "{}{}\n{}{}",
181 result,
182 spaces,
183 &shape.indent.to_string(context.config),
184 &rest_str
185 ))
186 }
187}
188
189fn struct_field_prefix_max_min_width<T: AlignedItem>(
190 context: &RewriteContext<'_>,
191 fields: &[T],
192 shape: Shape,
193) -> (usize, usize) {
194 fields
195 .iter()
196 .map(|field| {
197 field
198 .rewrite_prefix(context, shape)
199 .map(|field_str| trimmed_last_line_width(&field_str))
200 })
201 .fold_ok((0, ::std::usize::MAX), |(max_len, min_len), len| {
202 (cmp::max(max_len, len), cmp::min(min_len, len))
203 })
204 .unwrap_or((0, 0))
205}
206
207fn rewrite_aligned_items_inner<T: AlignedItem>(
208 context: &RewriteContext<'_>,
209 fields: &[T],
210 span: Span,
211 offset: Indent,
212 one_line_width: usize,
213 force_trailing_separator: bool,
214) -> Option<String> {
215 let item_shape = Shape::indented(offset, context.config).sub_width(1)?;
217 let (mut field_prefix_max_width, field_prefix_min_width) =
218 struct_field_prefix_max_min_width(context, fields, item_shape);
219 let max_diff = field_prefix_max_width.saturating_sub(field_prefix_min_width);
220 if max_diff > context.config.struct_field_align_threshold() {
221 field_prefix_max_width = 0;
222 }
223
224 let mut items = itemize_list(
225 context.snippet_provider,
226 fields.iter(),
227 "}",
228 ",",
229 |field| field.get_span().lo(),
230 |field| field.get_span().hi(),
231 |field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width),
232 span.lo(),
233 span.hi(),
234 false,
235 )
236 .collect::<Vec<_>>();
237
238 let tactic = definitive_tactic(
239 &items,
240 ListTactic::HorizontalVertical,
241 Separator::Comma,
242 one_line_width,
243 );
244
245 if tactic == DefinitiveListTactic::Horizontal {
246 let do_rewrite =
248 |field: &T| -> RewriteResult { field.rewrite_aligned_item(context, item_shape, 0) };
249 fields
250 .iter()
251 .zip(items.iter_mut())
252 .for_each(|(field, list_item): (&T, &mut ListItem)| {
253 if list_item.item.is_ok() {
254 list_item.item = do_rewrite(field);
255 }
256 });
257 }
258
259 let separator_tactic = if force_trailing_separator {
260 SeparatorTactic::Always
261 } else {
262 context.config.trailing_comma()
263 };
264
265 let fmt = ListFormatting::new(item_shape, context.config)
266 .tactic(tactic)
267 .trailing_separator(separator_tactic)
268 .preserve_newline(true);
269 write_list(&items, &fmt).ok()
270}
271
272fn group_aligned_items<T: AlignedItem>(
276 context: &RewriteContext<'_>,
277 fields: &[T],
278) -> (&'static str, usize) {
279 let mut index = 0;
280 for i in 0..fields.len() - 1 {
281 if fields[i].skip() {
282 return ("", index);
283 }
284 let span = mk_sp(fields[i].get_span().hi(), fields[i + 1].get_span().lo());
285 let snippet = context
286 .snippet(span)
287 .lines()
288 .skip(1)
289 .collect::<Vec<_>>()
290 .join("\n");
291 let has_blank_line = snippet
292 .lines()
293 .dropping_back(1)
294 .any(|l| l.trim().is_empty());
295 if has_blank_line {
296 return ("\n", index);
297 }
298 index += 1;
299 }
300 ("", index)
301}