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