]> git.lizzy.rs Git - rust.git/blob - src/patterns.rs
feat(config): expose all width heurstic options
[rust.git] / src / patterns.rs
1 use rustc_ast::ast::{self, BindingMode, Pat, PatField, PatKind, RangeEnd, RangeSyntax};
2 use rustc_ast::ptr;
3 use rustc_span::{BytePos, Span};
4
5 use crate::comment::{combine_strs_with_missing_comments, FindUncommented};
6 use crate::config::lists::*;
7 use crate::expr::{can_be_overflowed_expr, rewrite_unary_prefix, wrap_struct_field};
8 use crate::lists::{
9     definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape,
10     struct_lit_tactic, write_list, ListFormatting, ListItem, Separator,
11 };
12 use crate::macros::{rewrite_macro, MacroPosition};
13 use crate::overflow;
14 use crate::pairs::{rewrite_pair, PairParts};
15 use crate::rewrite::{Rewrite, RewriteContext};
16 use crate::shape::Shape;
17 use crate::source_map::SpanUtils;
18 use crate::spanned::Spanned;
19 use crate::types::{rewrite_path, PathContext};
20 use crate::utils::{format_mutability, mk_sp, mk_sp_lo_plus_one, rewrite_ident};
21
22 /// Returns `true` if the given pattern is "short".
23 /// A short pattern is defined by the following grammar:
24 ///
25 /// [small, ntp]:
26 ///     - single token
27 ///     - `&[single-line, ntp]`
28 ///
29 /// [small]:
30 ///     - `[small, ntp]`
31 ///     - unary tuple constructor `([small, ntp])`
32 ///     - `&[small]`
33 pub(crate) fn is_short_pattern(pat: &ast::Pat, pat_str: &str) -> bool {
34     // We also require that the pattern is reasonably 'small' with its literal width.
35     pat_str.len() <= 20 && !pat_str.contains('\n') && is_short_pattern_inner(pat)
36 }
37
38 fn is_short_pattern_inner(pat: &ast::Pat) -> bool {
39     match pat.kind {
40         ast::PatKind::Rest | ast::PatKind::Wild | ast::PatKind::Lit(_) => true,
41         ast::PatKind::Ident(_, _, ref pat) => pat.is_none(),
42         ast::PatKind::Struct(..)
43         | ast::PatKind::MacCall(..)
44         | ast::PatKind::Slice(..)
45         | ast::PatKind::Path(..)
46         | ast::PatKind::Range(..) => false,
47         ast::PatKind::Tuple(ref subpats) => subpats.len() <= 1,
48         ast::PatKind::TupleStruct(ref path, ref subpats) => {
49             path.segments.len() <= 1 && subpats.len() <= 1
50         }
51         ast::PatKind::Box(ref p) | ast::PatKind::Ref(ref p, _) | ast::PatKind::Paren(ref p) => {
52             is_short_pattern_inner(&*p)
53         }
54         PatKind::Or(ref pats) => pats.iter().all(|p| is_short_pattern_inner(p)),
55     }
56 }
57
58 struct RangeOperand<'a>(&'a Option<ptr::P<ast::Expr>>);
59
60 impl<'a> Rewrite for RangeOperand<'a> {
61     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
62         match &self.0 {
63             None => Some("".to_owned()),
64             Some(ref exp) => exp.rewrite(context, shape),
65         }
66     }
67 }
68
69 impl Rewrite for Pat {
70     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
71         match self.kind {
72             PatKind::Or(ref pats) => {
73                 let pat_strs = pats
74                     .iter()
75                     .map(|p| p.rewrite(context, shape))
76                     .collect::<Option<Vec<_>>>()?;
77
78                 let use_mixed_layout = pats
79                     .iter()
80                     .zip(pat_strs.iter())
81                     .all(|(pat, pat_str)| is_short_pattern(pat, pat_str));
82                 let items: Vec<_> = pat_strs.into_iter().map(ListItem::from_str).collect();
83                 let tactic = if use_mixed_layout {
84                     DefinitiveListTactic::Mixed
85                 } else {
86                     definitive_tactic(
87                         &items,
88                         ListTactic::HorizontalVertical,
89                         Separator::VerticalBar,
90                         shape.width,
91                     )
92                 };
93                 let fmt = ListFormatting::new(shape, context.config)
94                     .tactic(tactic)
95                     .separator(" |")
96                     .separator_place(context.config.binop_separator())
97                     .ends_with_newline(false);
98                 write_list(&items, &fmt)
99             }
100             PatKind::Box(ref pat) => rewrite_unary_prefix(context, "box ", &**pat, shape),
101             PatKind::Ident(binding_mode, ident, ref sub_pat) => {
102                 let (prefix, mutability) = match binding_mode {
103                     BindingMode::ByRef(mutability) => ("ref", mutability),
104                     BindingMode::ByValue(mutability) => ("", mutability),
105                 };
106                 let mut_infix = format_mutability(mutability).trim();
107                 let id_str = rewrite_ident(context, ident);
108                 let sub_pat = match *sub_pat {
109                     Some(ref p) => {
110                         // 2 - `@ `.
111                         let width = shape
112                             .width
113                             .checked_sub(prefix.len() + mut_infix.len() + id_str.len() + 2)?;
114                         let lo = context.snippet_provider.span_after(self.span, "@");
115                         combine_strs_with_missing_comments(
116                             context,
117                             "@",
118                             &p.rewrite(context, Shape::legacy(width, shape.indent))?,
119                             mk_sp(lo, p.span.lo()),
120                             shape,
121                             true,
122                         )?
123                     }
124                     None => "".to_owned(),
125                 };
126
127                 // combine prefix and mut
128                 let (first_lo, first) = if !prefix.is_empty() && !mut_infix.is_empty() {
129                     let hi = context.snippet_provider.span_before(self.span, "mut");
130                     let lo = context.snippet_provider.span_after(self.span, "ref");
131                     (
132                         context.snippet_provider.span_after(self.span, "mut"),
133                         combine_strs_with_missing_comments(
134                             context,
135                             prefix,
136                             mut_infix,
137                             mk_sp(lo, hi),
138                             shape,
139                             true,
140                         )?,
141                     )
142                 } else if !prefix.is_empty() {
143                     (
144                         context.snippet_provider.span_after(self.span, "ref"),
145                         prefix.to_owned(),
146                     )
147                 } else if !mut_infix.is_empty() {
148                     (
149                         context.snippet_provider.span_after(self.span, "mut"),
150                         mut_infix.to_owned(),
151                     )
152                 } else {
153                     (self.span.lo(), "".to_owned())
154                 };
155
156                 let next = if !sub_pat.is_empty() {
157                     let hi = context.snippet_provider.span_before(self.span, "@");
158                     combine_strs_with_missing_comments(
159                         context,
160                         id_str,
161                         &sub_pat,
162                         mk_sp(ident.span.hi(), hi),
163                         shape,
164                         true,
165                     )?
166                 } else {
167                     id_str.to_owned()
168                 };
169
170                 combine_strs_with_missing_comments(
171                     context,
172                     &first,
173                     &next,
174                     mk_sp(first_lo, ident.span.lo()),
175                     shape,
176                     true,
177                 )
178             }
179             PatKind::Wild => {
180                 if 1 <= shape.width {
181                     Some("_".to_owned())
182                 } else {
183                     None
184                 }
185             }
186             PatKind::Rest => {
187                 if 1 <= shape.width {
188                     Some("..".to_owned())
189                 } else {
190                     None
191                 }
192             }
193             PatKind::Range(ref lhs, ref rhs, ref end_kind) => {
194                 let infix = match end_kind.node {
195                     RangeEnd::Included(RangeSyntax::DotDotDot) => "...",
196                     RangeEnd::Included(RangeSyntax::DotDotEq) => "..=",
197                     RangeEnd::Excluded => "..",
198                 };
199                 let infix = if context.config.spaces_around_ranges() {
200                     let lhs_spacing = match lhs {
201                         None => "",
202                         Some(_) => " ",
203                     };
204                     let rhs_spacing = match rhs {
205                         None => "",
206                         Some(_) => " ",
207                     };
208                     format!("{}{}{}", lhs_spacing, infix, rhs_spacing)
209                 } else {
210                     infix.to_owned()
211                 };
212                 rewrite_pair(
213                     &RangeOperand(lhs),
214                     &RangeOperand(rhs),
215                     PairParts::infix(&infix),
216                     context,
217                     shape,
218                     SeparatorPlace::Front,
219                 )
220             }
221             PatKind::Ref(ref pat, mutability) => {
222                 let prefix = format!("&{}", format_mutability(mutability));
223                 rewrite_unary_prefix(context, &prefix, &**pat, shape)
224             }
225             PatKind::Tuple(ref items) => rewrite_tuple_pat(items, None, self.span, context, shape),
226             PatKind::Path(ref q_self, ref path) => {
227                 rewrite_path(context, PathContext::Expr, q_self.as_ref(), path, shape)
228             }
229             PatKind::TupleStruct(ref path, ref pat_vec) => {
230                 let path_str = rewrite_path(context, PathContext::Expr, None, path, shape)?;
231                 rewrite_tuple_pat(pat_vec, Some(path_str), self.span, context, shape)
232             }
233             PatKind::Lit(ref expr) => expr.rewrite(context, shape),
234             PatKind::Slice(ref slice_pat) => {
235                 let rw: Vec<String> = slice_pat
236                     .iter()
237                     .map(|p| {
238                         if let Some(rw) = p.rewrite(context, shape) {
239                             rw
240                         } else {
241                             format!("{}", context.snippet(p.span))
242                         }
243                     })
244                     .collect();
245                 Some(format!("[{}]", rw.join(", ")))
246             }
247             PatKind::Struct(ref path, ref fields, ellipsis) => {
248                 rewrite_struct_pat(path, fields, ellipsis, self.span, context, shape)
249             }
250             PatKind::MacCall(ref mac) => {
251                 rewrite_macro(mac, None, context, shape, MacroPosition::Pat)
252             }
253             PatKind::Paren(ref pat) => pat
254                 .rewrite(context, shape.offset_left(1)?.sub_width(1)?)
255                 .map(|inner_pat| format!("({})", inner_pat)),
256         }
257     }
258 }
259
260 fn rewrite_struct_pat(
261     path: &ast::Path,
262     fields: &[ast::PatField],
263     ellipsis: bool,
264     span: Span,
265     context: &RewriteContext<'_>,
266     shape: Shape,
267 ) -> Option<String> {
268     // 2 =  ` {`
269     let path_shape = shape.sub_width(2)?;
270     let path_str = rewrite_path(context, PathContext::Expr, None, path, path_shape)?;
271
272     if fields.is_empty() && !ellipsis {
273         return Some(format!("{} {{}}", path_str));
274     }
275
276     let (ellipsis_str, terminator) = if ellipsis { (", ..", "..") } else { ("", "}") };
277
278     // 3 = ` { `, 2 = ` }`.
279     let (h_shape, v_shape) =
280         struct_lit_shape(shape, context, path_str.len() + 3, ellipsis_str.len() + 2)?;
281
282     let items = itemize_list(
283         context.snippet_provider,
284         fields.iter(),
285         terminator,
286         ",",
287         |f| {
288             if f.attrs.is_empty() {
289                 f.span.lo()
290             } else {
291                 f.attrs.first().unwrap().span.lo()
292             }
293         },
294         |f| f.span.hi(),
295         |f| f.rewrite(context, v_shape),
296         context.snippet_provider.span_after(span, "{"),
297         span.hi(),
298         false,
299     );
300     let item_vec = items.collect::<Vec<_>>();
301
302     let tactic = struct_lit_tactic(h_shape, context, &item_vec);
303     let nested_shape = shape_for_tactic(tactic, h_shape, v_shape);
304     let fmt = struct_lit_formatting(nested_shape, tactic, context, false);
305
306     let mut fields_str = write_list(&item_vec, &fmt)?;
307     let one_line_width = h_shape.map_or(0, |shape| shape.width);
308
309     if ellipsis {
310         if fields_str.contains('\n') || fields_str.len() > one_line_width {
311             // Add a missing trailing comma.
312             if context.config.trailing_comma() == SeparatorTactic::Never {
313                 fields_str.push_str(",");
314             }
315             fields_str.push_str("\n");
316             fields_str.push_str(&nested_shape.indent.to_string(context.config));
317             fields_str.push_str("..");
318         } else {
319             if !fields_str.is_empty() {
320                 // there are preceding struct fields being matched on
321                 if tactic == DefinitiveListTactic::Vertical {
322                     // if the tactic is Vertical, write_list already added a trailing ,
323                     fields_str.push_str(" ");
324                 } else {
325                     fields_str.push_str(", ");
326                 }
327             }
328             fields_str.push_str("..");
329         }
330     }
331
332     // ast::Pat doesn't have attrs so use &[]
333     let fields_str = wrap_struct_field(context, &[], &fields_str, shape, v_shape, one_line_width)?;
334     Some(format!("{} {{{}}}", path_str, fields_str))
335 }
336
337 impl Rewrite for PatField {
338     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
339         let hi_pos = if let Some(last) = self.attrs.last() {
340             last.span.hi()
341         } else {
342             self.pat.span.lo()
343         };
344
345         let attrs_str = if self.attrs.is_empty() {
346             String::from("")
347         } else {
348             self.attrs.rewrite(context, shape)?
349         };
350
351         let pat_str = self.pat.rewrite(context, shape)?;
352         if self.is_shorthand {
353             combine_strs_with_missing_comments(
354                 context,
355                 &attrs_str,
356                 &pat_str,
357                 mk_sp(hi_pos, self.pat.span.lo()),
358                 shape,
359                 false,
360             )
361         } else {
362             let nested_shape = shape.block_indent(context.config.tab_spaces());
363             let id_str = rewrite_ident(context, self.ident);
364             let one_line_width = id_str.len() + 2 + pat_str.len();
365             let pat_and_id_str = if one_line_width <= shape.width {
366                 format!("{}: {}", id_str, pat_str)
367             } else {
368                 format!(
369                     "{}:\n{}{}",
370                     id_str,
371                     nested_shape.indent.to_string(context.config),
372                     self.pat.rewrite(context, nested_shape)?
373                 )
374             };
375             combine_strs_with_missing_comments(
376                 context,
377                 &attrs_str,
378                 &pat_and_id_str,
379                 mk_sp(hi_pos, self.pat.span.lo()),
380                 nested_shape,
381                 false,
382             )
383         }
384     }
385 }
386
387 #[derive(Debug)]
388 pub(crate) enum TuplePatField<'a> {
389     Pat(&'a ptr::P<ast::Pat>),
390     Dotdot(Span),
391 }
392
393 impl<'a> Rewrite for TuplePatField<'a> {
394     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
395         match *self {
396             TuplePatField::Pat(p) => p.rewrite(context, shape),
397             TuplePatField::Dotdot(_) => Some("..".to_string()),
398         }
399     }
400 }
401
402 impl<'a> Spanned for TuplePatField<'a> {
403     fn span(&self) -> Span {
404         match *self {
405             TuplePatField::Pat(p) => p.span(),
406             TuplePatField::Dotdot(span) => span,
407         }
408     }
409 }
410
411 impl<'a> TuplePatField<'a> {
412     fn is_dotdot(&self) -> bool {
413         match self {
414             TuplePatField::Pat(pat) => match pat.kind {
415                 ast::PatKind::Rest => true,
416                 _ => false,
417             },
418             TuplePatField::Dotdot(_) => true,
419         }
420     }
421 }
422
423 pub(crate) fn can_be_overflowed_pat(
424     context: &RewriteContext<'_>,
425     pat: &TuplePatField<'_>,
426     len: usize,
427 ) -> bool {
428     match *pat {
429         TuplePatField::Pat(pat) => match pat.kind {
430             ast::PatKind::Path(..)
431             | ast::PatKind::Tuple(..)
432             | ast::PatKind::Struct(..)
433             | ast::PatKind::TupleStruct(..) => context.use_block_indent() && len == 1,
434             ast::PatKind::Ref(ref p, _) | ast::PatKind::Box(ref p) => {
435                 can_be_overflowed_pat(context, &TuplePatField::Pat(p), len)
436             }
437             ast::PatKind::Lit(ref expr) => can_be_overflowed_expr(context, expr, len),
438             _ => false,
439         },
440         TuplePatField::Dotdot(..) => false,
441     }
442 }
443
444 fn rewrite_tuple_pat(
445     pats: &[ptr::P<ast::Pat>],
446     path_str: Option<String>,
447     span: Span,
448     context: &RewriteContext<'_>,
449     shape: Shape,
450 ) -> Option<String> {
451     let mut pat_vec: Vec<_> = pats.iter().map(|x| TuplePatField::Pat(x)).collect();
452
453     if pat_vec.is_empty() {
454         return Some(format!("{}()", path_str.unwrap_or_default()));
455     }
456     let wildcard_suffix_len = count_wildcard_suffix_len(context, &pat_vec, span, shape);
457     let (pat_vec, span) = if context.config.condense_wildcard_suffixes() && wildcard_suffix_len >= 2
458     {
459         let new_item_count = 1 + pat_vec.len() - wildcard_suffix_len;
460         let sp = pat_vec[new_item_count - 1].span();
461         let snippet = context.snippet(sp);
462         let lo = sp.lo() + BytePos(snippet.find_uncommented("_").unwrap() as u32);
463         pat_vec[new_item_count - 1] = TuplePatField::Dotdot(mk_sp_lo_plus_one(lo));
464         (
465             &pat_vec[..new_item_count],
466             mk_sp(span.lo(), lo + BytePos(1)),
467         )
468     } else {
469         (&pat_vec[..], span)
470     };
471
472     let is_last_pat_dotdot = pat_vec.last().map_or(false, |p| p.is_dotdot());
473     let add_comma = path_str.is_none() && pat_vec.len() == 1 && !is_last_pat_dotdot;
474     let path_str = path_str.unwrap_or_default();
475
476     overflow::rewrite_with_parens(
477         &context,
478         &path_str,
479         pat_vec.iter(),
480         shape,
481         span,
482         context.config.max_width(),
483         if add_comma {
484             Some(SeparatorTactic::Always)
485         } else {
486             None
487         },
488     )
489 }
490
491 fn count_wildcard_suffix_len(
492     context: &RewriteContext<'_>,
493     patterns: &[TuplePatField<'_>],
494     span: Span,
495     shape: Shape,
496 ) -> usize {
497     let mut suffix_len = 0;
498
499     let items: Vec<_> = itemize_list(
500         context.snippet_provider,
501         patterns.iter(),
502         ")",
503         ",",
504         |item| item.span().lo(),
505         |item| item.span().hi(),
506         |item| item.rewrite(context, shape),
507         context.snippet_provider.span_after(span, "("),
508         span.hi() - BytePos(1),
509         false,
510     )
511     .collect();
512
513     for item in items.iter().rev().take_while(|i| match i.item {
514         Some(ref internal_string) if internal_string == "_" => true,
515         _ => false,
516     }) {
517         suffix_len += 1;
518
519         if item.has_comment() {
520             break;
521         }
522     }
523
524     suffix_len
525 }