rustfmt_nightly/
patterns.rs

1use rustc_ast::ast::{self, BindingMode, ByRef, Pat, PatField, PatKind, RangeEnd, RangeSyntax};
2use rustc_span::{BytePos, Span};
3
4use crate::comment::{FindUncommented, combine_strs_with_missing_comments};
5use crate::config::StyleEdition;
6use crate::config::lists::*;
7use crate::expr::{can_be_overflowed_expr, rewrite_unary_prefix, wrap_struct_field};
8use crate::lists::{
9    ListFormatting, ListItem, Separator, definitive_tactic, itemize_list, shape_for_tactic,
10    struct_lit_formatting, struct_lit_shape, struct_lit_tactic, write_list,
11};
12use crate::macros::{MacroPosition, rewrite_macro};
13use crate::overflow;
14use crate::pairs::{PairParts, rewrite_pair};
15use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
16use crate::shape::Shape;
17use crate::source_map::SpanUtils;
18use crate::spanned::Spanned;
19use crate::types::{PathContext, rewrite_path};
20use 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]`
33pub(crate) fn is_short_pattern(
34    context: &RewriteContext<'_>,
35    pat: &ast::Pat,
36    pat_str: &str,
37) -> bool {
38    // We also require that the pattern is reasonably 'small' with its literal width.
39    pat_str.len() <= 20 && !pat_str.contains('\n') && is_short_pattern_inner(context, pat)
40}
41
42fn is_short_pattern_inner(context: &RewriteContext<'_>, pat: &ast::Pat) -> bool {
43    match &pat.kind {
44        ast::PatKind::Missing => unreachable!(),
45        ast::PatKind::Rest | ast::PatKind::Never | ast::PatKind::Wild | ast::PatKind::Err(_) => {
46            true
47        }
48        ast::PatKind::Expr(expr) => match &expr.kind {
49            ast::ExprKind::Lit(_) => true,
50            ast::ExprKind::Unary(ast::UnOp::Neg, expr) => match &expr.kind {
51                ast::ExprKind::Lit(_) => true,
52                _ => unreachable!(),
53            },
54            ast::ExprKind::ConstBlock(_) | ast::ExprKind::Path(..) => {
55                context.config.style_edition() <= StyleEdition::Edition2024
56            }
57            _ => unreachable!(),
58        },
59        ast::PatKind::Ident(_, _, ref pat) => pat.is_none(),
60        ast::PatKind::Struct(..)
61        | ast::PatKind::MacCall(..)
62        | ast::PatKind::Slice(..)
63        | ast::PatKind::Path(..)
64        | ast::PatKind::Range(..)
65        | ast::PatKind::Guard(..) => false,
66        ast::PatKind::Tuple(ref subpats) => subpats.len() <= 1,
67        ast::PatKind::TupleStruct(_, ref path, ref subpats) => {
68            path.segments.len() <= 1 && subpats.len() <= 1
69        }
70        ast::PatKind::Box(ref p)
71        | PatKind::Deref(ref p)
72        | ast::PatKind::Ref(ref p, _)
73        | ast::PatKind::Paren(ref p) => is_short_pattern_inner(context, &*p),
74        PatKind::Or(ref pats) => pats.iter().all(|p| is_short_pattern_inner(context, p)),
75    }
76}
77
78pub(crate) struct RangeOperand<'a, T> {
79    pub operand: &'a Option<Box<T>>,
80    pub span: Span,
81}
82
83impl<'a, T: Rewrite> Rewrite for RangeOperand<'a, T> {
84    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
85        self.rewrite_result(context, shape).ok()
86    }
87
88    fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
89        match &self.operand {
90            None => Ok("".to_owned()),
91            Some(ref exp) => exp.rewrite_result(context, shape),
92        }
93    }
94}
95
96impl Rewrite for Pat {
97    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
98        self.rewrite_result(context, shape).ok()
99    }
100
101    fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
102        match self.kind {
103            PatKind::Missing => unreachable!(),
104            PatKind::Or(ref pats) => {
105                let pat_strs = pats
106                    .iter()
107                    .map(|p| p.rewrite_result(context, shape))
108                    .collect::<Result<Vec<_>, RewriteError>>()?;
109
110                let use_mixed_layout = pats
111                    .iter()
112                    .zip(pat_strs.iter())
113                    .all(|(pat, pat_str)| is_short_pattern(context, pat, pat_str));
114                let items: Vec<_> = pat_strs.into_iter().map(ListItem::from_str).collect();
115                let tactic = if use_mixed_layout {
116                    DefinitiveListTactic::Mixed
117                } else {
118                    definitive_tactic(
119                        &items,
120                        ListTactic::HorizontalVertical,
121                        Separator::VerticalBar,
122                        shape.width,
123                    )
124                };
125                let fmt = ListFormatting::new(shape, context.config)
126                    .tactic(tactic)
127                    .separator(" |")
128                    .separator_place(context.config.binop_separator())
129                    .ends_with_newline(false);
130                write_list(&items, &fmt)
131            }
132            PatKind::Box(ref pat) => rewrite_unary_prefix(context, "box ", &**pat, shape),
133            PatKind::Ident(BindingMode(by_ref, mutability), ident, ref sub_pat) => {
134                let mut_prefix = format_mutability(mutability).trim();
135
136                let (ref_kw, mut_infix) = match by_ref {
137                    ByRef::Yes(rmutbl) => ("ref", format_mutability(rmutbl).trim()),
138                    ByRef::No => ("", ""),
139                };
140                let id_str = rewrite_ident(context, ident);
141                let sub_pat = match *sub_pat {
142                    Some(ref p) => {
143                        // 2 - `@ `.
144                        let width = shape
145                            .width
146                            .checked_sub(
147                                mut_prefix.len()
148                                    + ref_kw.len()
149                                    + mut_infix.len()
150                                    + id_str.len()
151                                    + 2,
152                            )
153                            .max_width_error(shape.width, p.span())?;
154                        let lo = context.snippet_provider.span_after(self.span, "@");
155                        combine_strs_with_missing_comments(
156                            context,
157                            "@",
158                            &p.rewrite_result(context, Shape::legacy(width, shape.indent))?,
159                            mk_sp(lo, p.span.lo()),
160                            shape,
161                            true,
162                        )?
163                    }
164                    None => "".to_owned(),
165                };
166
167                // combine prefix and ref
168                let (first_lo, first) = match (mut_prefix.is_empty(), ref_kw.is_empty()) {
169                    (false, false) => {
170                        let lo = context.snippet_provider.span_after(self.span, "mut");
171                        let hi = context.snippet_provider.span_before(self.span, "ref");
172                        (
173                            context.snippet_provider.span_after(self.span, "ref"),
174                            combine_strs_with_missing_comments(
175                                context,
176                                mut_prefix,
177                                ref_kw,
178                                mk_sp(lo, hi),
179                                shape,
180                                true,
181                            )?,
182                        )
183                    }
184                    (false, true) => (
185                        context.snippet_provider.span_after(self.span, "mut"),
186                        mut_prefix.to_owned(),
187                    ),
188                    (true, false) => (
189                        context.snippet_provider.span_after(self.span, "ref"),
190                        ref_kw.to_owned(),
191                    ),
192                    (true, true) => (self.span.lo(), "".to_owned()),
193                };
194
195                // combine result of above and mut
196                let (second_lo, second) = match (first.is_empty(), mut_infix.is_empty()) {
197                    (false, false) => {
198                        let lo = context.snippet_provider.span_after(self.span, "ref");
199                        let end_span = mk_sp(first_lo, self.span.hi());
200                        let hi = context.snippet_provider.span_before(end_span, "mut");
201                        (
202                            context.snippet_provider.span_after(end_span, "mut"),
203                            combine_strs_with_missing_comments(
204                                context,
205                                &first,
206                                mut_infix,
207                                mk_sp(lo, hi),
208                                shape,
209                                true,
210                            )?,
211                        )
212                    }
213                    (false, true) => (first_lo, first),
214                    (true, false) => unreachable!("mut_infix necessarily follows a ref"),
215                    (true, true) => (self.span.lo(), "".to_owned()),
216                };
217
218                let next = if !sub_pat.is_empty() {
219                    let hi = context.snippet_provider.span_before(self.span, "@");
220                    combine_strs_with_missing_comments(
221                        context,
222                        id_str,
223                        &sub_pat,
224                        mk_sp(ident.span.hi(), hi),
225                        shape,
226                        true,
227                    )?
228                } else {
229                    id_str.to_owned()
230                };
231
232                combine_strs_with_missing_comments(
233                    context,
234                    &second,
235                    &next,
236                    mk_sp(second_lo, ident.span.lo()),
237                    shape,
238                    true,
239                )
240            }
241            PatKind::Wild => {
242                if 1 <= shape.width {
243                    Ok("_".to_owned())
244                } else {
245                    Err(RewriteError::ExceedsMaxWidth {
246                        configured_width: 1,
247                        span: self.span,
248                    })
249                }
250            }
251            PatKind::Rest => {
252                if 1 <= shape.width {
253                    Ok("..".to_owned())
254                } else {
255                    Err(RewriteError::ExceedsMaxWidth {
256                        configured_width: 1,
257                        span: self.span,
258                    })
259                }
260            }
261            PatKind::Never => Err(RewriteError::Unknown),
262            PatKind::Range(ref lhs, ref rhs, ref end_kind) => {
263                rewrite_range_pat(context, shape, lhs, rhs, end_kind, self.span)
264            }
265            PatKind::Ref(ref pat, mutability) => {
266                let prefix = format!("&{}", format_mutability(mutability));
267                rewrite_unary_prefix(context, &prefix, &**pat, shape)
268            }
269            PatKind::Tuple(ref items) => rewrite_tuple_pat(items, None, self.span, context, shape),
270            PatKind::Path(ref q_self, ref path) => {
271                rewrite_path(context, PathContext::Expr, q_self, path, shape)
272            }
273            PatKind::TupleStruct(ref q_self, ref path, ref pat_vec) => {
274                let path_str = rewrite_path(context, PathContext::Expr, q_self, path, shape)?;
275                rewrite_tuple_pat(pat_vec, Some(path_str), self.span, context, shape)
276            }
277            PatKind::Expr(ref expr) => expr.rewrite_result(context, shape),
278            PatKind::Slice(ref slice_pat)
279                if context.config.style_edition() <= StyleEdition::Edition2021 =>
280            {
281                let rw: Vec<String> = slice_pat
282                    .iter()
283                    .map(|p| {
284                        if let Ok(rw) = p.rewrite_result(context, shape) {
285                            rw
286                        } else {
287                            context.snippet(p.span).to_string()
288                        }
289                    })
290                    .collect();
291                Ok(format!("[{}]", rw.join(", ")))
292            }
293            PatKind::Slice(ref slice_pat) => overflow::rewrite_with_square_brackets(
294                context,
295                "",
296                slice_pat.iter(),
297                shape,
298                self.span,
299                None,
300                None,
301            ),
302            PatKind::Struct(ref qself, ref path, ref fields, rest) => rewrite_struct_pat(
303                qself,
304                path,
305                fields,
306                rest == ast::PatFieldsRest::Rest,
307                self.span,
308                context,
309                shape,
310            ),
311            PatKind::MacCall(ref mac) => rewrite_macro(mac, context, shape, MacroPosition::Pat),
312            PatKind::Paren(ref pat) => pat
313                .rewrite_result(
314                    context,
315                    shape
316                        .offset_left(1)
317                        .and_then(|s| s.sub_width(1))
318                        .max_width_error(shape.width, self.span)?,
319                )
320                .map(|inner_pat| format!("({})", inner_pat)),
321            PatKind::Guard(..) => Ok(context.snippet(self.span).to_string()),
322            PatKind::Deref(_) => Err(RewriteError::Unknown),
323            PatKind::Err(_) => Err(RewriteError::Unknown),
324        }
325    }
326}
327
328pub fn rewrite_range_pat<T: Rewrite>(
329    context: &RewriteContext<'_>,
330    shape: Shape,
331    lhs: &Option<Box<T>>,
332    rhs: &Option<Box<T>>,
333    end_kind: &rustc_span::source_map::Spanned<RangeEnd>,
334    span: Span,
335) -> RewriteResult {
336    let infix = match end_kind.node {
337        RangeEnd::Included(RangeSyntax::DotDotDot) => "...",
338        RangeEnd::Included(RangeSyntax::DotDotEq) => "..=",
339        RangeEnd::Excluded => "..",
340    };
341    let infix = if context.config.spaces_around_ranges() {
342        let lhs_spacing = match lhs {
343            None => "",
344            Some(_) => " ",
345        };
346        let rhs_spacing = match rhs {
347            None => "",
348            Some(_) => " ",
349        };
350        format!("{lhs_spacing}{infix}{rhs_spacing}")
351    } else {
352        infix.to_owned()
353    };
354    let lspan = span.with_hi(end_kind.span.lo());
355    let rspan = span.with_lo(end_kind.span.hi());
356    rewrite_pair(
357        &RangeOperand {
358            operand: lhs,
359            span: lspan,
360        },
361        &RangeOperand {
362            operand: rhs,
363            span: rspan,
364        },
365        PairParts::infix(&infix),
366        context,
367        shape,
368        SeparatorPlace::Front,
369    )
370}
371
372fn rewrite_struct_pat(
373    qself: &Option<Box<ast::QSelf>>,
374    path: &ast::Path,
375    fields: &[ast::PatField],
376    ellipsis: bool,
377    span: Span,
378    context: &RewriteContext<'_>,
379    shape: Shape,
380) -> RewriteResult {
381    // 2 =  ` {`
382    let path_shape = shape.sub_width(2).max_width_error(shape.width, span)?;
383    let path_str = rewrite_path(context, PathContext::Expr, qself, path, path_shape)?;
384
385    if fields.is_empty() && !ellipsis {
386        return Ok(format!("{path_str} {{}}"));
387    }
388
389    let (ellipsis_str, terminator) = if ellipsis { (", ..", "..") } else { ("", "}") };
390
391    // 3 = ` { `, 2 = ` }`.
392    let (h_shape, v_shape) =
393        struct_lit_shape(shape, context, path_str.len() + 3, ellipsis_str.len() + 2)
394            .max_width_error(shape.width, span)?;
395
396    let items = itemize_list(
397        context.snippet_provider,
398        fields.iter(),
399        terminator,
400        ",",
401        |f| {
402            if f.attrs.is_empty() {
403                f.span.lo()
404            } else {
405                f.attrs.first().unwrap().span.lo()
406            }
407        },
408        |f| f.span.hi(),
409        |f| f.rewrite_result(context, v_shape),
410        context.snippet_provider.span_after(span, "{"),
411        span.hi(),
412        false,
413    );
414    let item_vec = items.collect::<Vec<_>>();
415
416    let tactic = struct_lit_tactic(h_shape, context, &item_vec);
417    let nested_shape = shape_for_tactic(tactic, h_shape, v_shape);
418    let fmt = struct_lit_formatting(nested_shape, tactic, context, false);
419
420    let mut fields_str = write_list(&item_vec, &fmt)?;
421    let one_line_width = h_shape.map_or(0, |shape| shape.width);
422
423    let has_trailing_comma = fmt.needs_trailing_separator();
424
425    if ellipsis {
426        if fields_str.contains('\n') || fields_str.len() > one_line_width {
427            // Add a missing trailing comma.
428            if !has_trailing_comma {
429                fields_str.push(',');
430            }
431            fields_str.push('\n');
432            fields_str.push_str(&nested_shape.indent.to_string(context.config));
433        } else {
434            if !fields_str.is_empty() {
435                // there are preceding struct fields being matched on
436                if has_trailing_comma {
437                    fields_str.push(' ');
438                } else {
439                    fields_str.push_str(", ");
440                }
441            }
442        }
443        fields_str.push_str("..");
444    }
445
446    // ast::Pat doesn't have attrs so use &[]
447    let fields_str = wrap_struct_field(context, &[], &fields_str, shape, v_shape, one_line_width)?;
448    Ok(format!("{path_str} {{{fields_str}}}"))
449}
450
451impl Rewrite for PatField {
452    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
453        self.rewrite_result(context, shape).ok()
454    }
455
456    fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
457        let hi_pos = if let Some(last) = self.attrs.last() {
458            last.span.hi()
459        } else {
460            self.pat.span.lo()
461        };
462
463        let attrs_str = if self.attrs.is_empty() {
464            String::from("")
465        } else {
466            self.attrs.rewrite_result(context, shape)?
467        };
468
469        let pat_str = self.pat.rewrite_result(context, shape)?;
470        if self.is_shorthand {
471            combine_strs_with_missing_comments(
472                context,
473                &attrs_str,
474                &pat_str,
475                mk_sp(hi_pos, self.pat.span.lo()),
476                shape,
477                false,
478            )
479        } else {
480            let nested_shape = shape.block_indent(context.config.tab_spaces());
481            let id_str = rewrite_ident(context, self.ident);
482            let one_line_width = id_str.len() + 2 + pat_str.len();
483            let pat_and_id_str = if one_line_width <= shape.width {
484                format!("{id_str}: {pat_str}")
485            } else {
486                format!(
487                    "{}:\n{}{}",
488                    id_str,
489                    nested_shape.indent.to_string(context.config),
490                    self.pat.rewrite_result(context, nested_shape)?
491                )
492            };
493            combine_strs_with_missing_comments(
494                context,
495                &attrs_str,
496                &pat_and_id_str,
497                mk_sp(hi_pos, self.pat.span.lo()),
498                nested_shape,
499                false,
500            )
501        }
502    }
503}
504
505#[derive(Debug)]
506pub(crate) enum TuplePatField<'a> {
507    Pat(&'a Box<ast::Pat>),
508    Dotdot(Span),
509}
510
511impl<'a> Rewrite for TuplePatField<'a> {
512    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
513        self.rewrite_result(context, shape).ok()
514    }
515
516    fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
517        match *self {
518            TuplePatField::Pat(p) => p.rewrite_result(context, shape),
519            TuplePatField::Dotdot(_) => Ok("..".to_string()),
520        }
521    }
522}
523
524impl<'a> Spanned for TuplePatField<'a> {
525    fn span(&self) -> Span {
526        match *self {
527            TuplePatField::Pat(p) => p.span(),
528            TuplePatField::Dotdot(span) => span,
529        }
530    }
531}
532
533impl<'a> TuplePatField<'a> {
534    fn is_dotdot(&self) -> bool {
535        match self {
536            TuplePatField::Pat(pat) => matches!(pat.kind, ast::PatKind::Rest),
537            TuplePatField::Dotdot(_) => true,
538        }
539    }
540}
541
542pub(crate) fn can_be_overflowed_pat(
543    context: &RewriteContext<'_>,
544    pat: &TuplePatField<'_>,
545    len: usize,
546) -> bool {
547    match *pat {
548        TuplePatField::Pat(pat) => match pat.kind {
549            ast::PatKind::Path(..)
550            | ast::PatKind::Tuple(..)
551            | ast::PatKind::Struct(..)
552            | ast::PatKind::TupleStruct(..) => context.use_block_indent() && len == 1,
553            ast::PatKind::Ref(ref p, _) | ast::PatKind::Box(ref p) => {
554                can_be_overflowed_pat(context, &TuplePatField::Pat(p), len)
555            }
556            ast::PatKind::Expr(ref expr) => can_be_overflowed_expr(context, expr, len),
557            _ => false,
558        },
559        TuplePatField::Dotdot(..) => false,
560    }
561}
562
563fn rewrite_tuple_pat(
564    pats: &[Box<ast::Pat>],
565    path_str: Option<String>,
566    span: Span,
567    context: &RewriteContext<'_>,
568    shape: Shape,
569) -> RewriteResult {
570    if pats.is_empty() {
571        return Ok(format!("{}()", path_str.unwrap_or_default()));
572    }
573    let mut pat_vec: Vec<_> = pats.iter().map(TuplePatField::Pat).collect();
574
575    let wildcard_suffix_len = count_wildcard_suffix_len(context, &pat_vec, span, shape);
576    let (pat_vec, span) = if context.config.condense_wildcard_suffixes() && wildcard_suffix_len >= 2
577    {
578        let new_item_count = 1 + pat_vec.len() - wildcard_suffix_len;
579        let sp = pat_vec[new_item_count - 1].span();
580        let snippet = context.snippet(sp);
581        let lo = sp.lo() + BytePos(snippet.find_uncommented("_").unwrap() as u32);
582        pat_vec[new_item_count - 1] = TuplePatField::Dotdot(mk_sp_lo_plus_one(lo));
583        (
584            &pat_vec[..new_item_count],
585            mk_sp(span.lo(), lo + BytePos(1)),
586        )
587    } else {
588        (&pat_vec[..], span)
589    };
590
591    let is_last_pat_dotdot = pat_vec.last().map_or(false, |p| p.is_dotdot());
592    let add_comma = path_str.is_none() && pat_vec.len() == 1 && !is_last_pat_dotdot;
593    let path_str = path_str.unwrap_or_default();
594
595    overflow::rewrite_with_parens(
596        context,
597        &path_str,
598        pat_vec.iter(),
599        shape,
600        span,
601        context.config.max_width(),
602        if add_comma {
603            Some(SeparatorTactic::Always)
604        } else {
605            None
606        },
607    )
608}
609
610fn count_wildcard_suffix_len(
611    context: &RewriteContext<'_>,
612    patterns: &[TuplePatField<'_>],
613    span: Span,
614    shape: Shape,
615) -> usize {
616    let mut suffix_len = 0;
617
618    let items: Vec<_> = itemize_list(
619        context.snippet_provider,
620        patterns.iter(),
621        ")",
622        ",",
623        |item| item.span().lo(),
624        |item| item.span().hi(),
625        |item| item.rewrite_result(context, shape),
626        context.snippet_provider.span_after(span, "("),
627        span.hi() - BytePos(1),
628        false,
629    )
630    .collect();
631
632    for item in items
633        .iter()
634        .rev()
635        .take_while(|i| matches!(i.item, Ok(ref internal_string) if internal_string == "_"))
636    {
637        suffix_len += 1;
638
639        if item.has_comment() {
640            break;
641        }
642    }
643
644    suffix_len
645}