Skip to main content

rustfmt_nightly/
patterns.rs

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