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
25pub(crate) fn is_short_pattern(
37 context: &RewriteContext<'_>,
38 pat: &ast::Pat,
39 pat_str: &str,
40) -> bool {
41 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 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 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 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 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 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 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 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 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 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}