1use rustc_ast::HasAttrs;
4use rustc_ast::ast;
5use rustc_span::{Span, symbol::sym};
6use tracing::debug;
7
8use self::doc_comment::DocCommentFormatter;
9use crate::comment::{CommentStyle, contains_comment, rewrite_doc_comment};
10use crate::config::IndentStyle;
11use crate::config::lists::*;
12use crate::expr::rewrite_literal;
13use crate::lists::{ListFormatting, Separator, definitive_tactic, itemize_list, write_list};
14use crate::overflow;
15use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
16use crate::shape::Shape;
17use crate::source_map::SpanUtils;
18use crate::types::{PathContext, rewrite_path};
19use crate::utils::{count_newlines, mk_sp};
20
21mod doc_comment;
22
23pub(crate) fn get_attrs_from_stmt(stmt: &ast::Stmt) -> &[ast::Attribute] {
25 stmt.attrs()
26}
27
28pub(crate) fn get_span_without_attrs(stmt: &ast::Stmt) -> Span {
29 match stmt.kind {
30 ast::StmtKind::Let(ref local) => local.span,
31 ast::StmtKind::Item(ref item) => item.span,
32 ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => expr.span,
33 ast::StmtKind::MacCall(ref mac_stmt) => mac_stmt.mac.span(),
34 ast::StmtKind::Empty => stmt.span,
35 }
36}
37
38pub(crate) fn filter_inline_attrs(attrs: &[ast::Attribute], outer_span: Span) -> ast::AttrVec {
40 attrs
41 .iter()
42 .filter(|a| outer_span.lo() <= a.span.lo() && a.span.hi() <= outer_span.hi())
43 .cloned()
44 .collect()
45}
46
47fn is_derive(attr: &ast::Attribute) -> bool {
48 attr.has_name(sym::derive)
49}
50
51fn argument_shape(
53 left: usize,
54 right: usize,
55 combine: bool,
56 shape: Shape,
57 context: &RewriteContext<'_>,
58) -> Option<Shape> {
59 let shape = match context.config.indent_style() {
60 IndentStyle::Block => {
61 if combine {
62 shape.offset_left_opt(left)?
63 } else {
64 shape
65 .block_indent(context.config.tab_spaces())
66 .with_max_width(context.config)
67 }
68 }
69 IndentStyle::Visual => shape
70 .visual_indent(0)
71 .shrink_left_opt(left)?
72 .sub_width_opt(right)?,
73 };
74 Some(shape)
75}
76
77fn format_derive(
78 derives: &[ast::Attribute],
79 shape: Shape,
80 context: &RewriteContext<'_>,
81) -> Option<String> {
82 let all_items = derives
84 .iter()
85 .map(|attr| {
86 let item_spans = attr.meta_item_list().map(|meta_item_list| {
90 meta_item_list
91 .into_iter()
92 .map(|meta_item_inner| meta_item_inner.span())
93 })?;
94
95 let items = itemize_list(
96 context.snippet_provider,
97 item_spans,
98 ")",
99 ",",
100 |span| span.lo(),
101 |span| span.hi(),
102 |span| Ok(context.snippet(*span).to_owned()),
103 context.snippet_provider.span_after(attr.span, "("),
106 attr.span.hi(),
107 false,
108 );
109
110 Some(items)
111 })
112 .collect::<Option<Vec<_>>>()?
114 .into_iter()
116 .flatten()
117 .collect::<Vec<_>>();
118
119 let prefix = attr_prefix(&derives[0]);
121 let argument_shape = argument_shape(
122 "[derive()]".len() + prefix.len(),
123 ")]".len(),
124 false,
125 shape,
126 context,
127 )?;
128 let one_line_shape = shape
129 .offset_left_opt("[derive()]".len() + prefix.len())?
130 .sub_width_opt("()]".len())?;
131 let one_line_budget = one_line_shape.width;
132
133 let tactic = definitive_tactic(
134 &all_items,
135 ListTactic::HorizontalVertical,
136 Separator::Comma,
137 argument_shape.width,
138 );
139 let trailing_separator = match context.config.indent_style() {
140 IndentStyle::Block => SeparatorTactic::Always,
142 IndentStyle::Visual => SeparatorTactic::Never,
143 };
144
145 let fmt = ListFormatting::new(argument_shape, context.config)
147 .tactic(tactic)
148 .trailing_separator(trailing_separator)
149 .ends_with_newline(false);
150 let item_str = write_list(&all_items, &fmt).ok()?;
151
152 debug!("item_str: '{}'", item_str);
153
154 let nested = context.config.indent_style() == IndentStyle::Block
158 && (item_str.contains('\n') || item_str.len() > one_line_budget);
159
160 let mut result = String::with_capacity(128);
162 result.push_str(prefix);
163 result.push_str("[derive(");
164 if nested {
165 let nested_indent = argument_shape.indent.to_string_with_newline(context.config);
166 result.push_str(&nested_indent);
167 result.push_str(&item_str);
168 result.push_str(&shape.indent.to_string_with_newline(context.config));
169 } else if let SeparatorTactic::Always = context.config.trailing_comma() {
170 result.push_str(&item_str);
172 } else if item_str.ends_with(',') {
173 result.push_str(&item_str[..item_str.len() - 1]);
175 } else {
176 result.push_str(&item_str);
177 }
178 result.push_str(")]");
179
180 Some(result)
181}
182
183fn take_while_with_pred<'a, P>(
186 context: &RewriteContext<'_>,
187 attrs: &'a [ast::Attribute],
188 pred: P,
189) -> &'a [ast::Attribute]
190where
191 P: Fn(&ast::Attribute) -> bool,
192{
193 let mut len = 0;
194 let mut iter = attrs.iter().peekable();
195
196 while let Some(attr) = iter.next() {
197 if pred(attr) {
198 len += 1;
199 } else {
200 break;
201 }
202 if let Some(next_attr) = iter.peek() {
203 let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo());
205 let snippet = context.snippet(span_between_attr);
206 if count_newlines(snippet) >= 2 || snippet.contains('/') {
207 break;
208 }
209 }
210 }
211
212 &attrs[..len]
213}
214
215fn rewrite_initial_doc_comments(
217 context: &RewriteContext<'_>,
218 attrs: &[ast::Attribute],
219 shape: Shape,
220) -> Result<(usize, Option<String>), RewriteError> {
221 if attrs.is_empty() {
222 return Ok((0, None));
223 }
224 let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_doc_comment());
226 if !sugared_docs.is_empty() {
227 let snippet = sugared_docs
228 .iter()
229 .map(|a| context.snippet(a.span))
230 .collect::<Vec<_>>()
231 .join("\n");
232 return Ok((
233 sugared_docs.len(),
234 Some(rewrite_doc_comment(
235 &snippet,
236 shape.comment(context.config),
237 context.config,
238 )?),
239 ));
240 }
241
242 Ok((0, None))
243}
244
245impl Rewrite for ast::MetaItemInner {
246 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
247 self.rewrite_result(context, shape).ok()
248 }
249
250 fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
251 match self {
252 ast::MetaItemInner::MetaItem(ref meta_item) => meta_item.rewrite_result(context, shape),
253 ast::MetaItemInner::Lit(ref l) => {
254 rewrite_literal(context, l.as_token_lit(), l.span, shape)
255 }
256 }
257 }
258}
259
260fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
261 let comment_begin = comment.find('/');
263 let len = comment_begin.unwrap_or_else(|| comment.len());
264 let mlb = count_newlines(&comment[..len]) > 1;
265 let mla = if comment_begin.is_none() {
266 mlb
267 } else {
268 comment
269 .chars()
270 .rev()
271 .take_while(|c| c.is_whitespace())
272 .filter(|&c| c == '\n')
273 .count()
274 > 1
275 };
276 (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" })
277}
278
279impl Rewrite for ast::MetaItem {
280 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
281 self.rewrite_result(context, shape).ok()
282 }
283
284 fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
285 Ok(match self.kind {
286 ast::MetaItemKind::Word => {
287 rewrite_path(context, PathContext::Type, &None, &self.path, shape)?
288 }
289 ast::MetaItemKind::List(ref list) => {
290 let path = rewrite_path(context, PathContext::Type, &None, &self.path, shape)?;
291 let has_trailing_comma = crate::expr::span_ends_with_comma(context, self.span);
292 overflow::rewrite_with_parens(
293 context,
294 &path,
295 list.iter(),
296 shape.sub_width(1, self.span)?,
298 self.span,
299 context.config.attr_fn_like_width(),
300 Some(if has_trailing_comma {
301 SeparatorTactic::Always
302 } else {
303 SeparatorTactic::Never
304 }),
305 )?
306 }
307 ast::MetaItemKind::NameValue(ref lit) => {
308 let path = rewrite_path(context, PathContext::Type, &None, &self.path, shape)?;
309 let lit_shape = shape.shrink_left(path.len() + 3, self.span)?;
311 let value = rewrite_literal(context, lit.as_token_lit(), lit.span, lit_shape)
318 .unwrap_or_else(|_| context.snippet(lit.span).to_owned());
319 format!("{path} = {value}")
320 }
321 })
322 }
323}
324
325impl Rewrite for ast::Attribute {
326 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
327 self.rewrite_result(context, shape).ok()
328 }
329
330 fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
331 let snippet = context.snippet(self.span);
332 if self.is_doc_comment() {
333 rewrite_doc_comment(snippet, shape.comment(context.config), context.config)
334 } else {
335 let should_skip = self
336 .name()
337 .map(|s| context.skip_context.attributes.skip(s.as_str()))
338 .unwrap_or(false);
339 let prefix = attr_prefix(self);
340
341 if should_skip || contains_comment(snippet) {
342 return Ok(snippet.to_owned());
343 }
344
345 if let Some(ref meta) = self.meta() {
346 if context.config.normalize_doc_attributes() && meta.has_name(sym::doc) {
348 if let Some(ref literal) = meta.value_str() {
349 let comment_style = match self.style {
350 ast::AttrStyle::Inner => CommentStyle::Doc,
351 ast::AttrStyle::Outer => CommentStyle::TripleSlash,
352 };
353
354 let literal_str = literal.as_str();
355 let doc_comment_formatter =
356 DocCommentFormatter::new(literal_str, comment_style);
357 let doc_comment = format!("{doc_comment_formatter}");
358 return rewrite_doc_comment(
359 &doc_comment,
360 shape.comment(context.config),
361 context.config,
362 );
363 }
364 }
365
366 let shape = shape.offset_left(prefix.len() + 1, self.span)?;
368 Ok(meta.rewrite_result(context, shape).map_or_else(
369 |_| snippet.to_owned(),
370 |rw| match &self.kind {
371 ast::AttrKind::Normal(normal_attr) => match normal_attr.item.unsafety {
372 ast::Safety::Unsafe(_) => format!("{}[unsafe({})]", prefix, rw),
375 _ => format!("{}[{}]", prefix, rw),
376 },
377 _ => format!("{}[{}]", prefix, rw),
378 },
379 ))
380 } else {
381 Ok(snippet.to_owned())
382 }
383 }
384 }
385}
386
387impl Rewrite for [ast::Attribute] {
388 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
389 self.rewrite_result(context, shape).ok()
390 }
391
392 fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
393 if self.is_empty() {
394 return Ok(String::new());
395 }
396
397 let mut attrs = self;
399 let mut result = String::new();
400
401 let skip_derives = context.skip_context.attributes.skip("derive");
404
405 loop {
409 if attrs.is_empty() {
410 return Ok(result);
411 }
412
413 let (doc_comment_len, doc_comment_str) =
415 rewrite_initial_doc_comments(context, attrs, shape)?;
416 if doc_comment_len > 0 {
417 let doc_comment_str = doc_comment_str.expect("doc comments, but no result");
418 result.push_str(&doc_comment_str);
419
420 let missing_span = attrs
421 .get(doc_comment_len)
422 .map(|next| mk_sp(attrs[doc_comment_len - 1].span.hi(), next.span.lo()));
423 if let Some(missing_span) = missing_span {
424 let snippet = context.snippet(missing_span);
425 let (mla, mlb) = has_newlines_before_after_comment(snippet);
426 let comment = crate::comment::recover_missing_comment_in_span(
427 missing_span,
428 shape.with_max_width(context.config),
429 context,
430 0,
431 )?;
432 let comment = if comment.is_empty() {
433 format!("\n{mlb}")
434 } else {
435 format!("{mla}{comment}\n{mlb}")
436 };
437 result.push_str(&comment);
438 result.push_str(&shape.indent.to_string(context.config));
439 }
440
441 attrs = &attrs[doc_comment_len..];
442
443 continue;
444 }
445
446 if !skip_derives && context.config.merge_derives() && is_derive(&attrs[0]) {
448 let derives = take_while_with_pred(context, attrs, is_derive);
449 let derive_str = format_derive(derives, shape, context).unknown_error()?;
450 result.push_str(&derive_str);
451
452 let missing_span = attrs
453 .get(derives.len())
454 .map(|next| mk_sp(attrs[derives.len() - 1].span.hi(), next.span.lo()));
455 if let Some(missing_span) = missing_span {
456 let comment = crate::comment::recover_missing_comment_in_span(
457 missing_span,
458 shape.with_max_width(context.config),
459 context,
460 0,
461 )?;
462 result.push_str(&comment);
463 if let Some(next) = attrs.get(derives.len()) {
464 if next.is_doc_comment() {
465 let snippet = context.snippet(missing_span);
466 let (_, mlb) = has_newlines_before_after_comment(snippet);
467 result.push_str(mlb);
468 }
469 }
470 result.push('\n');
471 result.push_str(&shape.indent.to_string(context.config));
472 }
473
474 attrs = &attrs[derives.len()..];
475
476 continue;
477 }
478
479 let formatted_attr = attrs[0].rewrite_result(context, shape)?;
483 result.push_str(&formatted_attr);
484
485 let missing_span = attrs
486 .get(1)
487 .map(|next| mk_sp(attrs[0].span.hi(), next.span.lo()));
488 if let Some(missing_span) = missing_span {
489 let comment = crate::comment::recover_missing_comment_in_span(
490 missing_span,
491 shape.with_max_width(context.config),
492 context,
493 0,
494 )?;
495 result.push_str(&comment);
496 if let Some(next) = attrs.get(1) {
497 if next.is_doc_comment() {
498 let snippet = context.snippet(missing_span);
499 let (_, mlb) = has_newlines_before_after_comment(snippet);
500 result.push_str(mlb);
501 }
502 }
503 result.push('\n');
504 result.push_str(&shape.indent.to_string(context.config));
505 }
506
507 attrs = &attrs[1..];
508 }
509 }
510}
511
512fn attr_prefix(attr: &ast::Attribute) -> &'static str {
513 match attr.style {
514 ast::AttrStyle::Inner => "#!",
515 ast::AttrStyle::Outer => "#",
516 }
517}
518
519pub(crate) trait MetaVisitor<'ast> {
520 fn visit_meta_item(&mut self, meta_item: &'ast ast::MetaItem) {
521 match meta_item.kind {
522 ast::MetaItemKind::Word => self.visit_meta_word(meta_item),
523 ast::MetaItemKind::List(ref list) => self.visit_meta_list(meta_item, list),
524 ast::MetaItemKind::NameValue(ref lit) => self.visit_meta_name_value(meta_item, lit),
525 }
526 }
527
528 fn visit_meta_list(
529 &mut self,
530 _meta_item: &'ast ast::MetaItem,
531 list: &'ast [ast::MetaItemInner],
532 ) {
533 for nm in list {
534 self.visit_meta_item_inner(nm);
535 }
536 }
537
538 fn visit_meta_word(&mut self, _meta_item: &'ast ast::MetaItem) {}
539
540 fn visit_meta_name_value(
541 &mut self,
542 _meta_item: &'ast ast::MetaItem,
543 _lit: &'ast ast::MetaItemLit,
544 ) {
545 }
546
547 fn visit_meta_item_inner(&mut self, nm: &'ast ast::MetaItemInner) {
548 match nm {
549 ast::MetaItemInner::MetaItem(ref meta_item) => self.visit_meta_item(meta_item),
550 ast::MetaItemInner::Lit(ref lit) => self.visit_meta_item_lit(lit),
551 }
552 }
553
554 fn visit_meta_item_lit(&mut self, _lit: &'ast ast::MetaItemLit) {}
555}