Skip to content

Commit 1146255

Browse files
committed
fixes #4115, #4029, #3898
1 parent e4de4b3 commit 1146255

File tree

1 file changed

+92
-94
lines changed
  • rustfmt-core/rustfmt-lib/src

1 file changed

+92
-94
lines changed

Diff for: rustfmt-core/rustfmt-lib/src/attr.rs

+92-94
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use rustc_ast::ast;
44
use rustc_ast::attr::HasAttrs;
5-
use rustc_span::{symbol::sym, BytePos, Span, DUMMY_SP};
5+
use rustc_span::{symbol::sym, Span};
66

77
use self::doc_comment::DocCommentFormatter;
88
use crate::comment::{contains_comment, rewrite_doc_comment, CommentStyle};
@@ -52,15 +52,6 @@ fn is_derive(attr: &ast::Attribute) -> bool {
5252
attr.check_name(sym::derive)
5353
}
5454

55-
/// Returns the arguments of `#[derive(...)]`.
56-
fn get_derive_spans<'a>(attr: &'a ast::Attribute) -> Option<impl Iterator<Item = Span> + 'a> {
57-
attr.meta_item_list().map(|meta_item_list| {
58-
meta_item_list
59-
.into_iter()
60-
.map(|nested_meta_item| nested_meta_item.span())
61-
})
62-
}
63-
6455
// The shape of the arguments to a function-like attribute.
6556
fn argument_shape(
6657
left: usize,
@@ -89,36 +80,104 @@ fn argument_shape(
8980
}
9081

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

116-
result.push_str(&item_str);
117-
if item_str.starts_with('\n') {
118-
result.push(',');
136+
let tactic = definitive_tactic(
137+
&all_items,
138+
ListTactic::HorizontalVertical,
139+
Separator::Comma,
140+
argument_shape.width,
141+
);
142+
let trailing_separator = match context.config.indent_style() {
143+
// We always add the trailing comma and remove it if it is not needed.
144+
IndentStyle::Block => SeparatorTactic::Always,
145+
IndentStyle::Visual => SeparatorTactic::Never,
146+
};
147+
148+
// Format the collection of items.
149+
let fmt = ListFormatting::new(argument_shape, context.config)
150+
.tactic(tactic)
151+
.trailing_separator(trailing_separator)
152+
.ends_with_newline(false);
153+
let item_str = write_list(&all_items, &fmt)?;
154+
155+
debug!("item_str: '{}'", item_str);
156+
157+
// Determine if the result will be nested, i.e. if we're using the block
158+
// indent style and either the items are on multiple lines or we've exceeded
159+
// our budget to fit on a single line.
160+
let nested = context.config.indent_style() == IndentStyle::Block
161+
&& (item_str.contains('\n') || item_str.len() > one_line_budget);
162+
163+
// Format the final result.
164+
let mut result = String::with_capacity(128);
165+
result.push_str(prefix);
166+
result.push_str("[derive(");
167+
if nested {
168+
let nested_indent = argument_shape.indent.to_string_with_newline(context.config);
169+
result.push_str(&nested_indent);
170+
result.push_str(&item_str);
119171
result.push_str(&shape.indent.to_string_with_newline(context.config));
172+
} else if let SeparatorTactic::Always = context.config.trailing_comma() {
173+
// Retain the trailing comma.
174+
result.push_str(&item_str);
175+
} else {
176+
// Remove the trailing comma.
177+
result.push_str(&item_str[..item_str.len() - 1]);
120178
}
121179
result.push_str(")]");
180+
122181
Some(result)
123182
}
124183

@@ -244,7 +303,7 @@ impl Rewrite for ast::MetaItem {
244303
// width. Since a literal is basically unformattable unless it
245304
// is a string literal (and only if `format_strings` is set),
246305
// we might be better off ignoring the fact that the attribute
247-
// is longer than the max width and contiue on formatting.
306+
// is longer than the max width and continue on formatting.
248307
// See #2479 for example.
249308
let value = rewrite_literal(context, literal, lit_shape)
250309
.unwrap_or_else(|| context.snippet(literal.span).to_owned());
@@ -258,61 +317,6 @@ impl Rewrite for ast::MetaItem {
258317
}
259318
}
260319

261-
fn format_arg_list<I, T, F1, F2, F3>(
262-
list: I,
263-
get_lo: F1,
264-
get_hi: F2,
265-
get_item_string: F3,
266-
span: Span,
267-
context: &RewriteContext<'_>,
268-
shape: Shape,
269-
one_line_shape: Shape,
270-
one_line_limit: Option<usize>,
271-
combine: bool,
272-
) -> Option<String>
273-
where
274-
I: Iterator<Item = T>,
275-
F1: Fn(&T) -> BytePos,
276-
F2: Fn(&T) -> BytePos,
277-
F3: Fn(&T) -> Option<String>,
278-
{
279-
let items = itemize_list(
280-
context.snippet_provider,
281-
list,
282-
")",
283-
",",
284-
get_lo,
285-
get_hi,
286-
get_item_string,
287-
span.lo(),
288-
span.hi(),
289-
false,
290-
);
291-
let item_vec = items.collect::<Vec<_>>();
292-
let tactic = if let Some(limit) = one_line_limit {
293-
ListTactic::LimitedHorizontalVertical(limit)
294-
} else {
295-
ListTactic::HorizontalVertical
296-
};
297-
298-
let tactic = definitive_tactic(&item_vec, tactic, Separator::Comma, shape.width);
299-
let fmt = ListFormatting::new(shape, context.config)
300-
.tactic(tactic)
301-
.ends_with_newline(false);
302-
let item_str = write_list(&item_vec, &fmt)?;
303-
304-
let one_line_budget = one_line_shape.width;
305-
if context.config.indent_style() == IndentStyle::Visual
306-
|| combine
307-
|| (!item_str.contains('\n') && item_str.len() <= one_line_budget)
308-
{
309-
Some(item_str)
310-
} else {
311-
let nested_indent = shape.indent.to_string_with_newline(context.config);
312-
Some(format!("{}{}", nested_indent, item_str))
313-
}
314-
}
315-
316320
impl Rewrite for ast::Attribute {
317321
fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
318322
let snippet = context.snippet(self.span);
@@ -417,13 +421,7 @@ impl<'a> Rewrite for [ast::Attribute] {
417421
// Handle derives if we will merge them.
418422
if context.config.merge_derives() && is_derive(&attrs[0]) {
419423
let derives = take_while_with_pred(context, attrs, is_derive);
420-
let derive_spans: Vec<_> = derives
421-
.iter()
422-
.filter_map(get_derive_spans)
423-
.flatten()
424-
.collect();
425-
let derive_str =
426-
format_derive(&derive_spans, attr_prefix(&attrs[0]), shape, context)?;
424+
let derive_str = format_derive(derives, shape, context)?;
427425
result.push_str(&derive_str);
428426

429427
let missing_span = attrs

0 commit comments

Comments
 (0)