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