]> git.lizzy.rs Git - rust.git/blob - src/attr.rs
Update rustc-ap-* crates to 581.0.0 (#3783)
[rust.git] / src / attr.rs
1 //! Format attributes and meta items.
2
3 use syntax::ast;
4 use syntax::source_map::{BytePos, Span, DUMMY_SP};
5 use syntax::symbol::sym;
6
7 use self::doc_comment::DocCommentFormatter;
8 use crate::comment::{contains_comment, rewrite_doc_comment, CommentStyle};
9 use crate::config::lists::*;
10 use crate::config::IndentStyle;
11 use crate::expr::rewrite_literal;
12 use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator};
13 use crate::overflow;
14 use crate::rewrite::{Rewrite, RewriteContext};
15 use crate::shape::Shape;
16 use crate::types::{rewrite_path, PathContext};
17 use crate::utils::{count_newlines, mk_sp};
18
19 mod doc_comment;
20
21 /// Returns attributes on the given statement.
22 pub(crate) fn get_attrs_from_stmt(stmt: &ast::Stmt) -> &[ast::Attribute] {
23     match stmt.node {
24         ast::StmtKind::Local(ref local) => &local.attrs,
25         ast::StmtKind::Item(ref item) => &item.attrs,
26         ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => &expr.attrs,
27         ast::StmtKind::Mac(ref mac) => &mac.2,
28     }
29 }
30
31 pub(crate) fn get_span_without_attrs(stmt: &ast::Stmt) -> Span {
32     match stmt.node {
33         ast::StmtKind::Local(ref local) => local.span,
34         ast::StmtKind::Item(ref item) => item.span,
35         ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => expr.span,
36         ast::StmtKind::Mac(ref mac) => {
37             let (ref mac, _, _) = **mac;
38             mac.span
39         }
40     }
41 }
42
43 /// Returns attributes that are within `outer_span`.
44 pub(crate) fn filter_inline_attrs(
45     attrs: &[ast::Attribute],
46     outer_span: Span,
47 ) -> Vec<ast::Attribute> {
48     attrs
49         .iter()
50         .filter(|a| outer_span.lo() <= a.span.lo() && a.span.hi() <= outer_span.hi())
51         .cloned()
52         .collect()
53 }
54
55 fn is_derive(attr: &ast::Attribute) -> bool {
56     attr.check_name(sym::derive)
57 }
58
59 /// Returns the arguments of `#[derive(...)]`.
60 fn get_derive_spans<'a>(attr: &'a ast::Attribute) -> Option<impl Iterator<Item = Span> + 'a> {
61     attr.meta_item_list().map(|meta_item_list| {
62         meta_item_list
63             .into_iter()
64             .map(|nested_meta_item| nested_meta_item.span())
65     })
66 }
67
68 // The shape of the arguments to a function-like attribute.
69 fn argument_shape(
70     left: usize,
71     right: usize,
72     combine: bool,
73     shape: Shape,
74     context: &RewriteContext<'_>,
75 ) -> Option<Shape> {
76     match context.config.indent_style() {
77         IndentStyle::Block => {
78             if combine {
79                 shape.offset_left(left)
80             } else {
81                 Some(
82                     shape
83                         .block_indent(context.config.tab_spaces())
84                         .with_max_width(context.config),
85                 )
86             }
87         }
88         IndentStyle::Visual => shape
89             .visual_indent(0)
90             .shrink_left(left)
91             .and_then(|s| s.sub_width(right)),
92     }
93 }
94
95 fn format_derive(
96     derive_args: &[Span],
97     prefix: &str,
98     shape: Shape,
99     context: &RewriteContext<'_>,
100 ) -> Option<String> {
101     let mut result = String::with_capacity(128);
102     result.push_str(prefix);
103     result.push_str("[derive(");
104
105     let argument_shape = argument_shape(10 + prefix.len(), 2, false, shape, context)?;
106     let item_str = format_arg_list(
107         derive_args.iter(),
108         |_| DUMMY_SP.lo(),
109         |_| DUMMY_SP.hi(),
110         |sp| Some(context.snippet(**sp).to_owned()),
111         DUMMY_SP,
112         context,
113         argument_shape,
114         // 10 = "[derive()]", 3 = "()" and "]"
115         shape.offset_left(10 + prefix.len())?.sub_width(3)?,
116         None,
117         false,
118     )?;
119
120     result.push_str(&item_str);
121     if item_str.starts_with('\n') {
122         result.push(',');
123         result.push_str(&shape.indent.to_string_with_newline(context.config));
124     }
125     result.push_str(")]");
126     Some(result)
127 }
128
129 /// Returns the first group of attributes that fills the given predicate.
130 /// We consider two doc comments are in different group if they are separated by normal comments.
131 fn take_while_with_pred<'a, P>(
132     context: &RewriteContext<'_>,
133     attrs: &'a [ast::Attribute],
134     pred: P,
135 ) -> &'a [ast::Attribute]
136 where
137     P: Fn(&ast::Attribute) -> bool,
138 {
139     let mut len = 0;
140     let mut iter = attrs.iter().peekable();
141
142     while let Some(attr) = iter.next() {
143         if pred(attr) {
144             len += 1;
145         } else {
146             break;
147         }
148         if let Some(next_attr) = iter.peek() {
149             // Extract comments between two attributes.
150             let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo());
151             let snippet = context.snippet(span_between_attr);
152             if count_newlines(snippet) >= 2 || snippet.contains('/') {
153                 break;
154             }
155         }
156     }
157
158     &attrs[..len]
159 }
160
161 /// Rewrite the any doc comments which come before any other attributes.
162 fn rewrite_initial_doc_comments(
163     context: &RewriteContext<'_>,
164     attrs: &[ast::Attribute],
165     shape: Shape,
166 ) -> Option<(usize, Option<String>)> {
167     if attrs.is_empty() {
168         return Some((0, None));
169     }
170     // Rewrite doc comments
171     let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_sugared_doc);
172     if !sugared_docs.is_empty() {
173         let snippet = sugared_docs
174             .iter()
175             .map(|a| context.snippet(a.span))
176             .collect::<Vec<_>>()
177             .join("\n");
178         return Some((
179             sugared_docs.len(),
180             Some(rewrite_doc_comment(
181                 &snippet,
182                 shape.comment(context.config),
183                 context.config,
184             )?),
185         ));
186     }
187
188     Some((0, None))
189 }
190
191 impl Rewrite for ast::NestedMetaItem {
192     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
193         match self {
194             ast::NestedMetaItem::MetaItem(ref meta_item) => meta_item.rewrite(context, shape),
195             ast::NestedMetaItem::Literal(ref l) => rewrite_literal(context, l, shape),
196         }
197     }
198 }
199
200 fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
201     // Look at before and after comment and see if there are any empty lines.
202     let comment_begin = comment.find('/');
203     let len = comment_begin.unwrap_or_else(|| comment.len());
204     let mlb = count_newlines(&comment[..len]) > 1;
205     let mla = if comment_begin.is_none() {
206         mlb
207     } else {
208         comment
209             .chars()
210             .rev()
211             .take_while(|c| c.is_whitespace())
212             .filter(|&c| c == '\n')
213             .count()
214             > 1
215     };
216     (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" })
217 }
218
219 impl Rewrite for ast::MetaItem {
220     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
221         Some(match self.node {
222             ast::MetaItemKind::Word => {
223                 rewrite_path(context, PathContext::Type, None, &self.path, shape)?
224             }
225             ast::MetaItemKind::List(ref list) => {
226                 let path = rewrite_path(context, PathContext::Type, None, &self.path, shape)?;
227                 let has_trailing_comma = crate::expr::span_ends_with_comma(context, self.span);
228                 overflow::rewrite_with_parens(
229                     context,
230                     &path,
231                     list.iter(),
232                     // 1 = "]"
233                     shape.sub_width(1)?,
234                     self.span,
235                     context.config.width_heuristics().attr_fn_like_width,
236                     Some(if has_trailing_comma {
237                         SeparatorTactic::Always
238                     } else {
239                         SeparatorTactic::Never
240                     }),
241                 )?
242             }
243             ast::MetaItemKind::NameValue(ref literal) => {
244                 let path = rewrite_path(context, PathContext::Type, None, &self.path, shape)?;
245                 // 3 = ` = `
246                 let lit_shape = shape.shrink_left(path.len() + 3)?;
247                 // `rewrite_literal` returns `None` when `literal` exceeds max
248                 // width. Since a literal is basically unformattable unless it
249                 // is a string literal (and only if `format_strings` is set),
250                 // we might be better off ignoring the fact that the attribute
251                 // is longer than the max width and contiue on formatting.
252                 // See #2479 for example.
253                 let value = rewrite_literal(context, literal, lit_shape)
254                     .unwrap_or_else(|| context.snippet(literal.span).to_owned());
255                 format!("{} = {}", path, value)
256             }
257         })
258     }
259 }
260
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
316 impl Rewrite for ast::Attribute {
317     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
318         let snippet = context.snippet(self.span);
319         if self.is_sugared_doc {
320             rewrite_doc_comment(snippet, shape.comment(context.config), context.config)
321         } else {
322             let should_skip = self
323                 .ident()
324                 .map(|s| context.skip_context.skip_attribute(&s.name.as_str()))
325                 .unwrap_or(false);
326             let prefix = attr_prefix(self);
327
328             if should_skip || contains_comment(snippet) {
329                 return Some(snippet.to_owned());
330             }
331
332             if let Some(ref meta) = self.meta() {
333                 // This attribute is possibly a doc attribute needing normalization to a doc comment
334                 if context.config.normalize_doc_attributes() && meta.check_name(sym::doc) {
335                     if let Some(ref literal) = meta.value_str() {
336                         let comment_style = match self.style {
337                             ast::AttrStyle::Inner => CommentStyle::Doc,
338                             ast::AttrStyle::Outer => CommentStyle::TripleSlash,
339                         };
340
341                         let literal_str = literal.as_str();
342                         let doc_comment_formatter =
343                             DocCommentFormatter::new(&*literal_str, comment_style);
344                         let doc_comment = format!("{}", doc_comment_formatter);
345                         return rewrite_doc_comment(
346                             &doc_comment,
347                             shape.comment(context.config),
348                             context.config,
349                         );
350                     }
351                 }
352
353                 // 1 = `[`
354                 let shape = shape.offset_left(prefix.len() + 1)?;
355                 Some(
356                     meta.rewrite(context, shape)
357                         .map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)),
358                 )
359             } else {
360                 Some(snippet.to_owned())
361             }
362         }
363     }
364 }
365
366 impl<'a> Rewrite for [ast::Attribute] {
367     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
368         if self.is_empty() {
369             return Some(String::new());
370         }
371
372         // The current remaining attributes.
373         let mut attrs = self;
374         let mut result = String::new();
375
376         // This is not just a simple map because we need to handle doc comments
377         // (where we take as many doc comment attributes as possible) and possibly
378         // merging derives into a single attribute.
379         loop {
380             if attrs.is_empty() {
381                 return Some(result);
382             }
383
384             // Handle doc comments.
385             let (doc_comment_len, doc_comment_str) =
386                 rewrite_initial_doc_comments(context, attrs, shape)?;
387             if doc_comment_len > 0 {
388                 let doc_comment_str = doc_comment_str.expect("doc comments, but no result");
389                 result.push_str(&doc_comment_str);
390
391                 let missing_span = attrs
392                     .get(doc_comment_len)
393                     .map(|next| mk_sp(attrs[doc_comment_len - 1].span.hi(), next.span.lo()));
394                 if let Some(missing_span) = missing_span {
395                     let snippet = context.snippet(missing_span);
396                     let (mla, mlb) = has_newlines_before_after_comment(snippet);
397                     let comment = crate::comment::recover_missing_comment_in_span(
398                         missing_span,
399                         shape.with_max_width(context.config),
400                         context,
401                         0,
402                     )?;
403                     let comment = if comment.is_empty() {
404                         format!("\n{}", mlb)
405                     } else {
406                         format!("{}{}\n{}", mla, comment, mlb)
407                     };
408                     result.push_str(&comment);
409                     result.push_str(&shape.indent.to_string(context.config));
410                 }
411
412                 attrs = &attrs[doc_comment_len..];
413
414                 continue;
415             }
416
417             // Handle derives if we will merge them.
418             if context.config.merge_derives() && is_derive(&attrs[0]) {
419                 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)?;
427                 result.push_str(&derive_str);
428
429                 let missing_span = attrs
430                     .get(derives.len())
431                     .map(|next| mk_sp(attrs[derives.len() - 1].span.hi(), next.span.lo()));
432                 if let Some(missing_span) = missing_span {
433                     let comment = crate::comment::recover_missing_comment_in_span(
434                         missing_span,
435                         shape.with_max_width(context.config),
436                         context,
437                         0,
438                     )?;
439                     result.push_str(&comment);
440                     if let Some(next) = attrs.get(derives.len()) {
441                         if next.is_sugared_doc {
442                             let snippet = context.snippet(missing_span);
443                             let (_, mlb) = has_newlines_before_after_comment(snippet);
444                             result.push_str(&mlb);
445                         }
446                     }
447                     result.push('\n');
448                     result.push_str(&shape.indent.to_string(context.config));
449                 }
450
451                 attrs = &attrs[derives.len()..];
452
453                 continue;
454             }
455
456             // If we get here, then we have a regular attribute, just handle one
457             // at a time.
458
459             let formatted_attr = attrs[0].rewrite(context, shape)?;
460             result.push_str(&formatted_attr);
461
462             let missing_span = attrs
463                 .get(1)
464                 .map(|next| mk_sp(attrs[0].span.hi(), next.span.lo()));
465             if let Some(missing_span) = missing_span {
466                 let comment = crate::comment::recover_missing_comment_in_span(
467                     missing_span,
468                     shape.with_max_width(context.config),
469                     context,
470                     0,
471                 )?;
472                 result.push_str(&comment);
473                 if let Some(next) = attrs.get(1) {
474                     if next.is_sugared_doc {
475                         let snippet = context.snippet(missing_span);
476                         let (_, mlb) = has_newlines_before_after_comment(snippet);
477                         result.push_str(&mlb);
478                     }
479                 }
480                 result.push('\n');
481                 result.push_str(&shape.indent.to_string(context.config));
482             }
483
484             attrs = &attrs[1..];
485         }
486     }
487 }
488
489 fn attr_prefix(attr: &ast::Attribute) -> &'static str {
490     match attr.style {
491         ast::AttrStyle::Inner => "#!",
492         ast::AttrStyle::Outer => "#",
493     }
494 }
495
496 pub(crate) trait MetaVisitor<'ast> {
497     fn visit_meta_item(&mut self, meta_item: &'ast ast::MetaItem) {
498         match meta_item.node {
499             ast::MetaItemKind::Word => self.visit_meta_word(meta_item),
500             ast::MetaItemKind::List(ref list) => self.visit_meta_list(meta_item, list),
501             ast::MetaItemKind::NameValue(ref lit) => self.visit_meta_name_value(meta_item, lit),
502         }
503     }
504
505     fn visit_meta_list(
506         &mut self,
507         _meta_item: &'ast ast::MetaItem,
508         list: &'ast [ast::NestedMetaItem],
509     ) {
510         for nm in list {
511             self.visit_nested_meta_item(nm);
512         }
513     }
514
515     fn visit_meta_word(&mut self, _meta_item: &'ast ast::MetaItem) {}
516
517     fn visit_meta_name_value(&mut self, _meta_item: &'ast ast::MetaItem, _lit: &'ast ast::Lit) {}
518
519     fn visit_nested_meta_item(&mut self, nm: &'ast ast::NestedMetaItem) {
520         match nm {
521             ast::NestedMetaItem::MetaItem(ref meta_item) => self.visit_meta_item(meta_item),
522             ast::NestedMetaItem::Literal(ref lit) => self.visit_literal(lit),
523         }
524     }
525
526     fn visit_literal(&mut self, _lit: &'ast ast::Lit) {}
527 }