Skip to content

Commit 3df383a

Browse files
ahlcalebcartwright
authored andcommitted
fixes #4115, #4029, #3898
1 parent c77c6a4 commit 3df383a

File tree

1 file changed

+91
-93
lines changed

1 file changed

+91
-93
lines changed

src/attr.rs

+91-93
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,6 @@ fn is_derive(attr: &ast::Attribute) -> bool {
6363
attr.has_name(sym::derive)
6464
}
6565

66-
/// Returns the arguments of `#[derive(...)]`.
67-
fn get_derive_spans<'a>(attr: &'a ast::Attribute) -> Option<impl Iterator<Item = Span> + 'a> {
68-
attr.meta_item_list().map(|meta_item_list| {
69-
meta_item_list
70-
.into_iter()
71-
.map(|nested_meta_item| nested_meta_item.span())
72-
})
73-
}
74-
7566
// The shape of the arguments to a function-like attribute.
7667
fn argument_shape(
7768
left: usize,
@@ -100,36 +91,104 @@ fn argument_shape(
10091
}
10192

10293
fn format_derive(
103-
derive_args: &[Span],
104-
prefix: &str,
94+
derives: &[ast::Attribute],
10595
shape: Shape,
10696
context: &RewriteContext<'_>,
10797
) -> Option<String> {
108-
let mut result = String::with_capacity(128);
109-
result.push_str(prefix);
110-
result.push_str("[derive(");
111-
112-
let argument_shape = argument_shape(10 + prefix.len(), 2, false, shape, context)?;
113-
let item_str = format_arg_list(
114-
derive_args.iter(),
115-
|_| DUMMY_SP.lo(),
116-
|_| DUMMY_SP.hi(),
117-
|sp| Some(context.snippet(**sp).to_owned()),
118-
DUMMY_SP,
119-
context,
120-
argument_shape,
121-
// 10 = "[derive()]", 3 = "()" and "]"
122-
shape.offset_left(10 + prefix.len())?.sub_width(3)?,
123-
None,
98+
// Collect all items from all attributes
99+
let all_items = derives
100+
.iter()
101+
.map(|attr| {
102+
// Parse the derive items and extract the span for each item; if any
103+
// attribute is not parseable, none of the attributes will be
104+
// reformatted.
105+
let item_spans = attr.meta_item_list().map(|meta_item_list| {
106+
meta_item_list
107+
.into_iter()
108+
.map(|nested_meta_item| nested_meta_item.span())
109+
})?;
110+
111+
let items = itemize_list(
112+
context.snippet_provider,
113+
item_spans,
114+
")",
115+
",",
116+
|span| span.lo(),
117+
|span| span.hi(),
118+
|span| Some(context.snippet(*span).to_owned()),
119+
attr.span.lo(),
120+
attr.span.hi(),
121+
false,
122+
);
123+
124+
Some(items)
125+
})
126+
// Fail if any attribute failed.
127+
.collect::<Option<Vec<_>>>()?
128+
// Collect the results into a single, flat, Vec.
129+
.into_iter()
130+
.flatten()
131+
.collect::<Vec<_>>();
132+
133+
// Collect formatting parameters.
134+
let prefix = attr_prefix(&derives[0]);
135+
let argument_shape = argument_shape(
136+
"[derive()]".len() + prefix.len(),
137+
")]".len(),
124138
false,
139+
shape,
140+
context,
125141
)?;
142+
let one_line_shape = shape
143+
.offset_left("[derive()]".len() + prefix.len())?
144+
.sub_width("()]".len())?;
145+
let one_line_budget = one_line_shape.width;
126146

127-
result.push_str(&item_str);
128-
if item_str.starts_with('\n') {
129-
result.push(',');
147+
let tactic = definitive_tactic(
148+
&all_items,
149+
ListTactic::HorizontalVertical,
150+
Separator::Comma,
151+
argument_shape.width,
152+
);
153+
let trailing_separator = match context.config.indent_style() {
154+
// We always add the trailing comma and remove it if it is not needed.
155+
IndentStyle::Block => SeparatorTactic::Always,
156+
IndentStyle::Visual => SeparatorTactic::Never,
157+
};
158+
159+
// Format the collection of items.
160+
let fmt = ListFormatting::new(argument_shape, context.config)
161+
.tactic(tactic)
162+
.trailing_separator(trailing_separator)
163+
.ends_with_newline(false);
164+
let item_str = write_list(&all_items, &fmt)?;
165+
166+
debug!("item_str: '{}'", item_str);
167+
168+
// Determine if the result will be nested, i.e. if we're using the block
169+
// indent style and either the items are on multiple lines or we've exceeded
170+
// our budget to fit on a single line.
171+
let nested = context.config.indent_style() == IndentStyle::Block
172+
&& (item_str.contains('\n') || item_str.len() > one_line_budget);
173+
174+
// Format the final result.
175+
let mut result = String::with_capacity(128);
176+
result.push_str(prefix);
177+
result.push_str("[derive(");
178+
if nested {
179+
let nested_indent = argument_shape.indent.to_string_with_newline(context.config);
180+
result.push_str(&nested_indent);
181+
result.push_str(&item_str);
130182
result.push_str(&shape.indent.to_string_with_newline(context.config));
183+
} else if let SeparatorTactic::Always = context.config.trailing_comma() {
184+
// Retain the trailing comma.
185+
result.push_str(&item_str);
186+
} else {
187+
// Remove the trailing comma.
188+
result.push_str(&item_str[..item_str.len() - 1]);
131189
}
132190
result.push_str(")]");
191+
133192
Some(result)
134193
}
135194

@@ -255,7 +314,7 @@ impl Rewrite for ast::MetaItem {
255314
// width. Since a literal is basically unformattable unless it
256315
// is a string literal (and only if `format_strings` is set),
257316
// we might be better off ignoring the fact that the attribute
258-
// is longer than the max width and contiue on formatting.
317+
// is longer than the max width and continue on formatting.
259318
// See #2479 for example.
260319
let value = rewrite_literal(context, literal, lit_shape)
261320
.unwrap_or_else(|| context.snippet(literal.span).to_owned());
@@ -265,61 +324,6 @@ impl Rewrite for ast::MetaItem {
265324
}
266325
}
267326

268-
fn format_arg_list<I, T, F1, F2, F3>(
269-
list: I,
270-
get_lo: F1,
271-
get_hi: F2,
272-
get_item_string: F3,
273-
span: Span,
274-
context: &RewriteContext<'_>,
275-
shape: Shape,
276-
one_line_shape: Shape,
277-
one_line_limit: Option<usize>,
278-
combine: bool,
279-
) -> Option<String>
280-
where
281-
I: Iterator<Item = T>,
282-
F1: Fn(&T) -> BytePos,
283-
F2: Fn(&T) -> BytePos,
284-
F3: Fn(&T) -> Option<String>,
285-
{
286-
let items = itemize_list(
287-
context.snippet_provider,
288-
list,
289-
")",
290-
",",
291-
get_lo,
292-
get_hi,
293-
get_item_string,
294-
span.lo(),
295-
span.hi(),
296-
false,
297-
);
298-
let item_vec = items.collect::<Vec<_>>();
299-
let tactic = if let Some(limit) = one_line_limit {
300-
ListTactic::LimitedHorizontalVertical(limit)
301-
} else {
302-
ListTactic::HorizontalVertical
303-
};
304-
305-
let tactic = definitive_tactic(&item_vec, tactic, Separator::Comma, shape.width);
306-
let fmt = ListFormatting::new(shape, context.config)
307-
.tactic(tactic)
308-
.ends_with_newline(false);
309-
let item_str = write_list(&item_vec, &fmt)?;
310-
311-
let one_line_budget = one_line_shape.width;
312-
if context.config.indent_style() == IndentStyle::Visual
313-
|| combine
314-
|| (!item_str.contains('\n') && item_str.len() <= one_line_budget)
315-
{
316-
Some(item_str)
317-
} else {
318-
let nested_indent = shape.indent.to_string_with_newline(context.config);
319-
Some(format!("{}{}", nested_indent, item_str))
320-
}
321-
}
322-
323327
impl Rewrite for ast::Attribute {
324328
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
325329
let snippet = context.snippet(self.span);
@@ -424,13 +428,7 @@ impl<'a> Rewrite for [ast::Attribute] {
424428
// Handle derives if we will merge them.
425429
if context.config.merge_derives() && is_derive(&attrs[0]) {
426430
let derives = take_while_with_pred(context, attrs, is_derive);
427-
let derive_spans: Vec<_> = derives
428-
.iter()
429-
.filter_map(get_derive_spans)
430-
.flatten()
431-
.collect();
432-
let derive_str =
433-
format_derive(&derive_spans, attr_prefix(&attrs[0]), shape, context)?;
431+
let derive_str = format_derive(derives, shape, context)?;
434432
result.push_str(&derive_str);
435433

436434
let missing_span = attrs

0 commit comments

Comments
 (0)