Skip to main content

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.offset_left(1, self.span)?.sub_width(1, self.span)?,
350                )
351                .map(|inner_pat| format!("({})", inner_pat)),
352            PatKind::Guard(..) => Ok(context.snippet(self.span).to_string()),
353            PatKind::Deref(_) => Err(RewriteError::Unknown),
354            PatKind::Err(_) => Err(RewriteError::Unknown),
355        }
356    }
357}
358
359pub(crate) fn rewrite_range_pat<T: Rewrite>(
360    context: &RewriteContext<'_>,
361    shape: Shape,
362    lhs: &Option<Box<T>>,
363    rhs: &Option<Box<T>>,
364    end_kind: &rustc_span::source_map::Spanned<RangeEnd>,
365    span: Span,
366) -> RewriteResult {
367    let infix = match end_kind.node {
368        RangeEnd::Included(RangeSyntax::DotDotDot) => "...",
369        RangeEnd::Included(RangeSyntax::DotDotEq) => "..=",
370        RangeEnd::Excluded => "..",
371    };
372    let infix = if context.config.spaces_around_ranges() {
373        let lhs_spacing = match lhs {
374            None => "",
375            Some(_) => " ",
376        };
377        let rhs_spacing = match rhs {
378            None => "",
379            Some(_) => " ",
380        };
381        format!("{lhs_spacing}{infix}{rhs_spacing}")
382    } else {
383        infix.to_owned()
384    };
385    let lspan = span.with_hi(end_kind.span.lo());
386    let rspan = span.with_lo(end_kind.span.hi());
387    rewrite_pair(
388        &RangeOperand {
389            operand: lhs,
390            span: lspan,
391        },
392        &RangeOperand {
393            operand: rhs,
394            span: rspan,
395        },
396        PairParts::infix(&infix),
397        context,
398        shape,
399        SeparatorPlace::Front,
400    )
401}
402
403fn rewrite_struct_pat(
404    qself: &Option<Box<ast::QSelf>>,
405    path: &ast::Path,
406    fields: &[ast::PatField],
407    ellipsis: bool,
408    span: Span,
409    context: &RewriteContext<'_>,
410    shape: Shape,
411) -> RewriteResult {
412    // 2 =  ` {`
413    let path_shape = shape.sub_width(2, span)?;
414    let path_str = rewrite_path(context, PathContext::Expr, qself, path, path_shape)?;
415
416    if fields.is_empty() && !ellipsis {
417        return Ok(format!("{path_str} {{}}"));
418    }
419
420    let (ellipsis_str, terminator) = if ellipsis { (", ..", "..") } else { ("", "}") };
421
422    // 3 = ` { `, 2 = ` }`.
423    let (h_shape, v_shape) = struct_lit_shape(
424        shape,
425        context,
426        path_str.len() + 3,
427        ellipsis_str.len() + 2,
428        span,
429    )?;
430
431    let items = itemize_list(
432        context.snippet_provider,
433        fields.iter(),
434        terminator,
435        ",",
436        |f| {
437            if f.attrs.is_empty() {
438                f.span.lo()
439            } else {
440                f.attrs.first().unwrap().span.lo()
441            }
442        },
443        |f| f.span.hi(),
444        |f| f.rewrite_result(context, v_shape),
445        context.snippet_provider.span_after(span, "{"),
446        span.hi(),
447        false,
448    );
449    let item_vec = items.collect::<Vec<_>>();
450
451    let tactic = struct_lit_tactic(h_shape, context, &item_vec);
452    let nested_shape = shape_for_tactic(tactic, h_shape, v_shape);
453    let fmt = struct_lit_formatting(nested_shape, tactic, context, false);
454
455    let mut fields_str = write_list(&item_vec, &fmt)?;
456    let one_line_width = h_shape.map_or(0, |shape| shape.width);
457
458    let has_trailing_comma = fmt.needs_trailing_separator();
459
460    if ellipsis {
461        if fields_str.contains('\n') || fields_str.len() > one_line_width {
462            // Add a missing trailing comma.
463            if !has_trailing_comma {
464                fields_str.push(',');
465            }
466            fields_str.push('\n');
467            fields_str.push_str(&nested_shape.indent.to_string(context.config));
468        } else {
469            if !fields_str.is_empty() {
470                // there are preceding struct fields being matched on
471                if has_trailing_comma {
472                    fields_str.push(' ');
473                } else {
474                    fields_str.push_str(", ");
475                }
476            }
477        }
478        fields_str.push_str("..");
479    }
480
481    // ast::Pat doesn't have attrs so use &[]
482    let fields_str = wrap_struct_field(context, &[], &fields_str, shape, v_shape, one_line_width)?;
483    Ok(format!("{path_str} {{{fields_str}}}"))
484}
485
486impl Rewrite for PatField {
487    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
488        self.rewrite_result(context, shape).ok()
489    }
490
491    fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
492        let hi_pos = if let Some(last) = self.attrs.last() {
493            last.span.hi()
494        } else {
495            self.pat.span.lo()
496        };
497
498        let attrs_str = if self.attrs.is_empty() {
499            String::from("")
500        } else {
501            self.attrs.rewrite_result(context, shape)?
502        };
503
504        let pat_str = self.pat.rewrite_result(context, shape)?;
505        if self.is_shorthand {
506            combine_strs_with_missing_comments(
507                context,
508                &attrs_str,
509                &pat_str,
510                mk_sp(hi_pos, self.pat.span.lo()),
511                shape,
512                false,
513            )
514        } else {
515            let nested_shape = shape.block_indent(context.config.tab_spaces());
516            let id_str = rewrite_ident(context, self.ident);
517            let one_line_width = id_str.len() + 2 + pat_str.len();
518            let pat_and_id_str = if one_line_width <= shape.width {
519                format!("{id_str}: {pat_str}")
520            } else {
521                format!(
522                    "{}:\n{}{}",
523                    id_str,
524                    nested_shape.indent.to_string(context.config),
525                    self.pat.rewrite_result(context, nested_shape)?
526                )
527            };
528            combine_strs_with_missing_comments(
529                context,
530                &attrs_str,
531                &pat_and_id_str,
532                mk_sp(hi_pos, self.pat.span.lo()),
533                nested_shape,
534                false,
535            )
536        }
537    }
538}
539
540#[derive(Debug)]
541pub(crate) enum TuplePatField<'a> {
542    Pat(&'a ast::Pat),
543    Dotdot(Span),
544}
545
546impl<'a> Rewrite for TuplePatField<'a> {
547    fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
548        self.rewrite_result(context, shape).ok()
549    }
550
551    fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
552        match *self {
553            TuplePatField::Pat(p) => p.rewrite_result(context, shape),
554            TuplePatField::Dotdot(_) => Ok("..".to_string()),
555        }
556    }
557}
558
559impl<'a> Spanned for TuplePatField<'a> {
560    fn span(&self) -> Span {
561        match *self {
562            TuplePatField::Pat(p) => p.span(),
563            TuplePatField::Dotdot(span) => span,
564        }
565    }
566}
567
568impl<'a> TuplePatField<'a> {
569    fn is_dotdot(&self) -> bool {
570        match self {
571            TuplePatField::Pat(pat) => matches!(pat.kind, ast::PatKind::Rest),
572            TuplePatField::Dotdot(_) => true,
573        }
574    }
575}
576
577pub(crate) fn can_be_overflowed_pat(
578    context: &RewriteContext<'_>,
579    pat: &TuplePatField<'_>,
580    len: usize,
581) -> bool {
582    match *pat {
583        TuplePatField::Pat(pat) => match pat.kind {
584            ast::PatKind::Path(..)
585            | ast::PatKind::Tuple(..)
586            | ast::PatKind::Struct(..)
587            | ast::PatKind::TupleStruct(..) => context.use_block_indent() && len == 1,
588            ast::PatKind::Ref(ref p, _, _) | ast::PatKind::Box(ref p) => {
589                can_be_overflowed_pat(context, &TuplePatField::Pat(p), len)
590            }
591            ast::PatKind::Expr(ref expr) => can_be_overflowed_expr(context, expr, len),
592            _ => false,
593        },
594        TuplePatField::Dotdot(..) => false,
595    }
596}
597
598fn rewrite_tuple_pat(
599    pats: &[ast::Pat],
600    path_str: Option<String>,
601    span: Span,
602    context: &RewriteContext<'_>,
603    shape: Shape,
604) -> RewriteResult {
605    if pats.is_empty() {
606        return Ok(format!("{}()", path_str.unwrap_or_default()));
607    }
608    let mut pat_vec: Vec<_> = pats.iter().map(TuplePatField::Pat).collect();
609
610    let wildcard_suffix_len = count_wildcard_suffix_len(context, &pat_vec, span, shape);
611    let (pat_vec, span) = if context.config.condense_wildcard_suffixes() && wildcard_suffix_len >= 2
612    {
613        let new_item_count = 1 + pat_vec.len() - wildcard_suffix_len;
614        let sp = pat_vec[new_item_count - 1].span();
615        let snippet = context.snippet(sp);
616        let lo = sp.lo() + BytePos(snippet.find_uncommented("_").unwrap() as u32);
617        pat_vec[new_item_count - 1] = TuplePatField::Dotdot(mk_sp_lo_plus_one(lo));
618        (
619            &pat_vec[..new_item_count],
620            mk_sp(span.lo(), lo + BytePos(1)),
621        )
622    } else {
623        (&pat_vec[..], span)
624    };
625
626    let is_last_pat_dotdot = pat_vec.last().map_or(false, |p| p.is_dotdot());
627    let add_comma = path_str.is_none() && pat_vec.len() == 1 && !is_last_pat_dotdot;
628    let path_str = path_str.unwrap_or_default();
629
630    overflow::rewrite_with_parens(
631        context,
632        &path_str,
633        pat_vec.iter(),
634        shape,
635        span,
636        context.config.max_width(),
637        if add_comma {
638            Some(SeparatorTactic::Always)
639        } else {
640            None
641        },
642    )
643}
644
645fn count_wildcard_suffix_len(
646    context: &RewriteContext<'_>,
647    patterns: &[TuplePatField<'_>],
648    span: Span,
649    shape: Shape,
650) -> usize {
651    let mut suffix_len = 0;
652
653    let items: Vec<_> = itemize_list(
654        context.snippet_provider,
655        patterns.iter(),
656        ")",
657        ",",
658        |item| item.span().lo(),
659        |item| item.span().hi(),
660        |item| item.rewrite_result(context, shape),
661        context.snippet_provider.span_after(span, "("),
662        span.hi() - BytePos(1),
663        false,
664    )
665    .collect();
666
667    for item in items
668        .iter()
669        .rev()
670        .take_while(|i| matches!(i.item, Ok(ref internal_string) if internal_string == "_"))
671    {
672        suffix_len += 1;
673
674        if item.has_comment() {
675            break;
676        }
677    }
678
679    suffix_len
680}