1use std::iter::repeat;
4
5use rustc_ast::{MatchKind, ast};
6use rustc_span::{BytePos, Span};
7use tracing::debug;
8
9use crate::comment::{FindUncommented, combine_strs_with_missing_comments, rewrite_comment};
10use crate::config::lists::*;
11use crate::config::{Config, ControlBraceStyle, IndentStyle, MatchArmLeadingPipe, StyleEdition};
12use crate::expr::{
13 ExprType, RhsTactics, format_expr, is_empty_block, is_simple_block, is_unsafe_block,
14 prefer_next_line, rewrite_cond,
15};
16use crate::lists::{ListFormatting, itemize_list, write_list};
17use crate::rewrite::{Rewrite, RewriteContext, RewriteError, RewriteErrorExt, RewriteResult};
18use crate::shape::Shape;
19use crate::source_map::SpanUtils;
20use crate::spanned::Spanned;
21use crate::utils::{
22 contains_skip, extra_offset, first_line_width, inner_attributes, last_line_extendable, mk_sp,
23 semicolon_for_expr, trimmed_last_line_width, unicode_str_width,
24};
25
26struct ArmWrapper<'a> {
28 arm: &'a ast::Arm,
29 is_last: bool,
32 beginning_vert: Option<BytePos>,
34}
35
36impl<'a> ArmWrapper<'a> {
37 fn new(arm: &'a ast::Arm, is_last: bool, beginning_vert: Option<BytePos>) -> ArmWrapper<'a> {
38 ArmWrapper {
39 arm,
40 is_last,
41 beginning_vert,
42 }
43 }
44}
45
46impl<'a> Spanned for ArmWrapper<'a> {
47 fn span(&self) -> Span {
48 if let Some(lo) = self.beginning_vert {
49 let lo = std::cmp::min(lo, self.arm.span().lo());
50 mk_sp(lo, self.arm.span().hi())
51 } else {
52 self.arm.span()
53 }
54 }
55}
56
57impl<'a> Rewrite for ArmWrapper<'a> {
58 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
59 self.rewrite_result(context, shape).ok()
60 }
61
62 fn rewrite_result(&self, context: &RewriteContext<'_>, shape: Shape) -> RewriteResult {
63 rewrite_match_arm(
64 context,
65 self.arm,
66 shape,
67 self.is_last,
68 self.beginning_vert.is_some(),
69 )
70 }
71}
72
73pub(crate) fn rewrite_match(
74 context: &RewriteContext<'_>,
75 cond: &ast::Expr,
76 arms: &[ast::Arm],
77 shape: Shape,
78 span: Span,
79 attrs: &[ast::Attribute],
80 match_kind: MatchKind,
81) -> RewriteResult {
82 let cond_shape = Shape {
85 width: context.budget(shape.used_width()),
86 ..shape
87 };
88 let cond_shape = match context.config.indent_style() {
90 IndentStyle::Visual => cond_shape.shrink_left(6, span)?,
91 IndentStyle::Block => cond_shape.offset_left(6, span)?,
92 };
93 let cond_str = cond.rewrite_result(context, cond_shape)?;
94 let alt_block_sep = &shape.indent.to_string_with_newline(context.config);
95 let block_sep = match context.config.control_brace_style() {
96 ControlBraceStyle::AlwaysNextLine => alt_block_sep,
97 _ if last_line_extendable(&cond_str) => " ",
98 _ if cond_str.contains('\n') || cond_str.len() + 2 > cond_shape.width => alt_block_sep,
100 _ => " ",
101 };
102
103 let nested_indent = if context.config.match_arm_indent() {
104 shape.indent.block_indent(context.config)
105 } else {
106 shape.indent
107 };
108 let nested_indent_str = nested_indent.to_string(context.config);
109
110 let inner_attrs = &inner_attributes(attrs);
112 let inner_attrs_str = if inner_attrs.is_empty() {
113 String::new()
114 } else {
115 let shape = if context.config.style_edition() <= StyleEdition::Edition2021
116 || !context.config.match_arm_indent()
117 {
118 shape
119 } else {
120 shape.block_indent(context.config.tab_spaces())
121 };
122 inner_attrs
123 .rewrite_result(context, shape)
124 .map(|s| format!("{}{}\n", nested_indent_str, s))?
125 };
126
127 let open_brace_pos = if inner_attrs.is_empty() {
128 let hi = if arms.is_empty() {
129 span.hi()
130 } else {
131 arms[0].span().lo()
132 };
133 context
134 .snippet_provider
135 .span_after(mk_sp(cond.span.hi(), hi), "{")
136 } else {
137 inner_attrs[inner_attrs.len() - 1].span.hi()
138 };
139
140 if arms.is_empty() {
141 let snippet = context.snippet(mk_sp(open_brace_pos, span.hi() - BytePos(1)));
142 if snippet.trim().is_empty() {
143 Ok(format!("match {cond_str} {{}}"))
144 } else {
145 Ok(context.snippet(span).to_owned())
147 }
148 } else {
149 let span_after_cond = mk_sp(cond.span.hi(), span.hi());
150
151 match match_kind {
152 MatchKind::Prefix => Ok(format!(
153 "match {}{}{{\n{}{}{}\n{}}}",
154 cond_str,
155 block_sep,
156 inner_attrs_str,
157 nested_indent_str,
158 rewrite_match_arms(context, arms, shape, span_after_cond, open_brace_pos)?,
159 shape.indent.to_string(context.config),
160 )),
161 MatchKind::Postfix => Ok(format!(
162 "{}.match{}{{\n{}{}{}\n{}}}",
163 cond_str,
164 block_sep,
165 inner_attrs_str,
166 nested_indent_str,
167 rewrite_match_arms(context, arms, shape, span_after_cond, open_brace_pos)?,
168 shape.indent.to_string(context.config),
169 )),
170 }
171 }
172}
173
174fn arm_comma(config: &Config, body: &ast::Expr, is_last: bool) -> &'static str {
175 if is_last && config.trailing_comma() == SeparatorTactic::Never {
176 ""
177 } else if config.match_block_trailing_comma() {
178 ","
179 } else if let ast::ExprKind::Block(ref block, _) = body.kind {
180 if let ast::BlockCheckMode::Default = block.rules {
181 ""
182 } else {
183 ","
184 }
185 } else {
186 ","
187 }
188}
189
190fn collect_beginning_verts(
192 context: &RewriteContext<'_>,
193 arms: &[ast::Arm],
194) -> Vec<Option<BytePos>> {
195 arms.iter()
196 .map(|a| {
197 context
198 .snippet(a.pat.span)
199 .starts_with('|')
200 .then(|| a.pat.span().lo())
201 })
202 .collect()
203}
204
205fn rewrite_match_arms(
206 context: &RewriteContext<'_>,
207 arms: &[ast::Arm],
208 shape: Shape,
209 span: Span,
210 open_brace_pos: BytePos,
211) -> RewriteResult {
212 let arm_shape = if context.config.match_arm_indent() {
213 shape.block_indent(context.config.tab_spaces())
214 } else {
215 shape
216 }
217 .with_max_width(context.config);
218
219 let arm_len = arms.len();
220 let is_last_iter = repeat(false)
221 .take(arm_len.saturating_sub(1))
222 .chain(repeat(true));
223 let beginning_verts = collect_beginning_verts(context, arms);
224 let items = itemize_list(
225 context.snippet_provider,
226 arms.iter()
227 .zip(is_last_iter)
228 .zip(beginning_verts.into_iter())
229 .map(|((arm, is_last), beginning_vert)| ArmWrapper::new(arm, is_last, beginning_vert)),
230 "}",
231 "|",
232 |arm| arm.span().lo(),
233 |arm| arm.span().hi(),
234 |arm| arm.rewrite_result(context, arm_shape),
235 open_brace_pos,
236 span.hi(),
237 false,
238 );
239 let arms_vec: Vec<_> = items.collect();
240 let fmt = ListFormatting::new(arm_shape, context.config)
242 .separator("")
243 .preserve_newline(true);
244
245 write_list(&arms_vec, &fmt)
246}
247
248fn rewrite_match_arm(
249 context: &RewriteContext<'_>,
250 arm: &ast::Arm,
251 shape: Shape,
252 is_last: bool,
253 has_leading_pipe: bool,
254) -> RewriteResult {
255 let (missing_span, attrs_str) = if !arm.attrs.is_empty() {
256 if contains_skip(&arm.attrs) {
257 let (_, body) = flatten_arm_body(context, arm.body.as_deref().unknown_error()?, None);
258 return Ok(format!(
260 "{}{}",
261 context.snippet(arm.span()),
262 arm_comma(context.config, body, is_last),
263 ));
264 }
265 let missing_span = mk_sp(arm.attrs[arm.attrs.len() - 1].span.hi(), arm.pat.span.lo());
266 (missing_span, arm.attrs.rewrite_result(context, shape)?)
267 } else {
268 (mk_sp(arm.span().lo(), arm.span().lo()), String::new())
269 };
270
271 let (pipe_offset, pipe_str) = match context.config.match_arm_leading_pipes() {
274 MatchArmLeadingPipe::Never => (0, ""),
275 MatchArmLeadingPipe::Preserve if !has_leading_pipe => (0, ""),
276 MatchArmLeadingPipe::Preserve | MatchArmLeadingPipe::Always => (2, "| "),
277 };
278
279 let pat_shape = match &arm.body.as_ref().unknown_error()?.kind {
281 ast::ExprKind::Block(_, Some(label)) => {
282 let label_len = label.ident.as_str().len();
285 shape
286 .sub_width(7 + label_len, arm.span)?
287 .offset_left(pipe_offset, arm.span)?
288 }
289 _ => {
290 shape
292 .sub_width(5, arm.span)?
293 .offset_left(pipe_offset, arm.span)?
294 }
295 };
296 let pats_str = arm.pat.rewrite_result(context, pat_shape)?;
297
298 let block_like_pat = trimmed_last_line_width(&pats_str) <= context.config.tab_spaces();
300 let new_line_guard = pats_str.contains('\n') && !block_like_pat;
301 let guard_str = rewrite_guard(
302 context,
303 &arm.guard,
304 shape,
305 trimmed_last_line_width(&pats_str),
306 new_line_guard,
307 )?;
308
309 let lhs_str = combine_strs_with_missing_comments(
310 context,
311 &attrs_str,
312 &format!("{pipe_str}{pats_str}{guard_str}"),
313 missing_span,
314 shape,
315 false,
316 )?;
317
318 let arrow_span = mk_sp(
319 arm.pat.span.hi(),
320 arm.body.as_ref().unknown_error()?.span().lo(),
321 );
322 rewrite_match_body(
323 context,
324 arm.body.as_ref().unknown_error()?,
325 &lhs_str,
326 shape,
327 guard_str.contains('\n'),
328 arrow_span,
329 is_last,
330 )
331}
332
333fn stmt_is_expr_mac(stmt: &ast::Stmt) -> bool {
334 if let ast::StmtKind::Expr(expr) = &stmt.kind {
335 if let ast::ExprKind::MacCall(_) = &expr.kind {
336 return true;
337 }
338 }
339 false
340}
341
342fn block_can_be_flattened<'a>(
343 context: &RewriteContext<'_>,
344 expr: &'a ast::Expr,
345) -> Option<&'a ast::Block> {
346 match expr.kind {
347 ast::ExprKind::Block(ref block, label)
348 if label.is_none()
349 && !is_unsafe_block(block)
350 && !context.inside_macro()
351 && is_simple_block(context, block, Some(&expr.attrs))
352 && !stmt_is_expr_mac(&block.stmts[0]) =>
353 {
354 Some(&*block)
355 }
356 _ => None,
357 }
358}
359
360fn flatten_arm_body<'a>(
364 context: &'a RewriteContext<'_>,
365 body: &'a ast::Expr,
366 opt_shape: Option<Shape>,
367) -> (bool, &'a ast::Expr) {
368 let can_extend =
369 |expr| !context.config.force_multiline_blocks() && can_flatten_block_around_this(expr);
370
371 if let Some(block) = block_can_be_flattened(context, body) {
372 if let ast::StmtKind::Expr(ref expr) = block.stmts[0].kind {
373 if let ast::ExprKind::Block(..) = expr.kind {
374 if expr.attrs.is_empty() {
375 flatten_arm_body(context, expr, None)
376 } else {
377 (true, body)
378 }
379 } else {
380 let cond_becomes_multi_line = opt_shape
381 .and_then(|shape| rewrite_cond(context, expr, shape))
382 .map_or(false, |cond| cond.contains('\n'));
383 if cond_becomes_multi_line {
384 (false, &*body)
385 } else {
386 (can_extend(expr), &*expr)
387 }
388 }
389 } else {
390 (false, &*body)
391 }
392 } else {
393 (can_extend(body), &*body)
394 }
395}
396
397fn rewrite_match_body(
398 context: &RewriteContext<'_>,
399 body: &Box<ast::Expr>,
400 pats_str: &str,
401 shape: Shape,
402 has_guard: bool,
403 arrow_span: Span,
404 is_last: bool,
405) -> RewriteResult {
406 let (extend, body) = flatten_arm_body(
407 context,
408 body,
409 shape.offset_left_opt(extra_offset(pats_str, shape) + 4),
410 );
411 let (is_block, is_empty_block) = if let ast::ExprKind::Block(ref block, _) = body.kind {
412 (true, is_empty_block(context, block, Some(&body.attrs)))
413 } else {
414 (false, false)
415 };
416
417 let comma = arm_comma(context.config, body, is_last);
418 let alt_block_sep = &shape.indent.to_string_with_newline(context.config);
419
420 let combine_orig_body = |body_str: &str| {
421 let block_sep = match context.config.control_brace_style() {
422 ControlBraceStyle::AlwaysNextLine if is_block => alt_block_sep,
423 _ => " ",
424 };
425
426 Ok(format!("{} =>{}{}{}", pats_str, block_sep, body_str, comma))
427 };
428
429 let next_line_indent = if !is_block || is_empty_block {
430 shape.indent.block_indent(context.config)
431 } else {
432 shape.indent
433 };
434
435 let forbid_same_line =
436 (has_guard && pats_str.contains('\n') && !is_empty_block) || !body.attrs.is_empty();
437
438 let arrow_comment = {
440 let arrow_snippet = context.snippet(arrow_span).trim();
441 let arrow_index = if context.config.style_edition() <= StyleEdition::Edition2021 {
444 arrow_snippet.rfind("=>").unwrap()
445 } else {
446 arrow_snippet.find_last_uncommented("=>").unwrap()
447 };
448 let comment_str = arrow_snippet[arrow_index + 2..].trim();
450 if comment_str.is_empty() {
451 String::new()
452 } else {
453 rewrite_comment(comment_str, false, shape, context.config)?
454 }
455 };
456
457 let combine_next_line_body = |body_str: &str| {
458 let nested_indent_str = next_line_indent.to_string_with_newline(context.config);
459
460 if is_block {
461 let mut result = pats_str.to_owned();
462 result.push_str(" =>");
463 if !arrow_comment.is_empty() {
464 result.push_str(&nested_indent_str);
465 result.push_str(&arrow_comment);
466 }
467 result.push_str(&nested_indent_str);
468 result.push_str(body_str);
469 result.push_str(comma);
470 return Ok(result);
471 }
472
473 let indent_str = shape.indent.to_string_with_newline(context.config);
474 let (body_prefix, body_suffix) =
475 if context.config.match_arm_blocks() && !context.inside_macro() {
476 let comma = if context.config.match_block_trailing_comma() {
477 ","
478 } else {
479 ""
480 };
481 let semicolon = if context.config.style_edition() <= StyleEdition::Edition2021 {
482 ""
483 } else {
484 if semicolon_for_expr(context, body) {
485 ";"
486 } else {
487 ""
488 }
489 };
490 ("{", format!("{}{}}}{}", semicolon, indent_str, comma))
491 } else {
492 ("", String::from(","))
493 };
494
495 let block_sep = match context.config.control_brace_style() {
496 _ if body_prefix.is_empty() => "".to_owned(),
497 ControlBraceStyle::AlwaysNextLine => format!("{}{}", alt_block_sep, body_prefix),
498 _ if forbid_same_line || !arrow_comment.is_empty() => {
499 format!("{}{}", alt_block_sep, body_prefix)
500 }
501 _ => format!(" {}", body_prefix),
502 } + &nested_indent_str;
503
504 let mut result = pats_str.to_owned();
505 result.push_str(" =>");
506 if !arrow_comment.is_empty() {
507 result.push_str(&indent_str);
508 result.push_str(&arrow_comment);
509 }
510 result.push_str(&block_sep);
511 result.push_str(body_str);
512 result.push_str(&body_suffix);
513 Ok(result)
514 };
515
516 let orig_body_shape = shape
519 .offset_left_opt(extra_offset(pats_str, shape) + 4)
520 .and_then(|shape| shape.sub_width_opt(comma.len()));
521 let orig_body = if forbid_same_line || !arrow_comment.is_empty() {
522 Err(RewriteError::Unknown)
523 } else if let Some(body_shape) = orig_body_shape {
524 let rewrite = nop_block_collapse(
525 format_expr(body, ExprType::Statement, context, body_shape),
526 body_shape.width,
527 );
528
529 match rewrite {
530 Ok(ref body_str)
531 if is_block
532 || (!body_str.contains('\n')
533 && unicode_str_width(body_str) <= body_shape.width) =>
534 {
535 return combine_orig_body(body_str);
536 }
537 _ => rewrite,
538 }
539 } else {
540 Err(RewriteError::Unknown)
541 };
542 let orig_budget = orig_body_shape.map_or(0, |shape| shape.width);
543
544 let next_line_body_shape = Shape::indented(next_line_indent, context.config);
546 let next_line_body = nop_block_collapse(
547 format_expr(body, ExprType::Statement, context, next_line_body_shape),
548 next_line_body_shape.width,
549 );
550 match (orig_body, next_line_body) {
551 (Ok(ref orig_str), Ok(ref next_line_str))
552 if prefer_next_line(orig_str, next_line_str, RhsTactics::Default) =>
553 {
554 combine_next_line_body(next_line_str)
555 }
556 (Ok(ref orig_str), _) if extend && first_line_width(orig_str) <= orig_budget => {
557 combine_orig_body(orig_str)
558 }
559 (Ok(ref orig_str), Ok(ref next_line_str)) if orig_str.contains('\n') => {
560 combine_next_line_body(next_line_str)
561 }
562 (Err(_), Ok(ref next_line_str)) => combine_next_line_body(next_line_str),
563 (Err(_), Err(next_line_err)) => Err(next_line_err),
567 (Ok(ref orig_str), _) => combine_orig_body(orig_str),
568 }
569}
570
571fn rewrite_guard(
573 context: &RewriteContext<'_>,
574 guard: &Option<Box<ast::Expr>>,
575 shape: Shape,
576 pattern_width: usize,
579 multiline_pattern: bool,
580) -> RewriteResult {
581 if let Some(ref guard) = *guard {
582 let cond_shape = shape
585 .offset_left_opt(pattern_width + 4)
586 .and_then(|s| s.sub_width_opt(5));
587 if !multiline_pattern {
588 if let Some(cond_shape) = cond_shape {
589 if let Ok(cond_str) = guard.rewrite_result(context, cond_shape) {
590 if !cond_str.contains('\n') || pattern_width <= context.config.tab_spaces() {
591 return Ok(format!(" if {cond_str}"));
592 }
593 }
594 }
595 }
596
597 let cond_shape = Shape::indented(shape.indent.block_indent(context.config), context.config)
600 .offset_left(3, guard.span)?
601 .sub_width(5, guard.span)?;
602 let cond_str = guard.rewrite_result(context, cond_shape)?;
603 Ok(format!(
604 "{}if {}",
605 cond_shape.indent.to_string_with_newline(context.config),
606 cond_str
607 ))
608 } else {
609 Ok(String::new())
610 }
611}
612
613fn nop_block_collapse(block_str: RewriteResult, budget: usize) -> RewriteResult {
614 debug!("nop_block_collapse {:?} {}", block_str, budget);
615 block_str.map(|block_str| {
616 if block_str.starts_with('{')
617 && budget >= 2
618 && (block_str[1..].find(|c: char| !c.is_whitespace()).unwrap() == block_str.len() - 2)
619 {
620 String::from("{}")
621 } else {
622 block_str
623 }
624 })
625}
626
627fn can_flatten_block_around_this(body: &ast::Expr) -> bool {
628 match body.kind {
629 ast::ExprKind::If(..) => false,
632 ast::ExprKind::ForLoop { .. } | ast::ExprKind::While(..) => false,
635 ast::ExprKind::Loop(..)
636 | ast::ExprKind::Match(..)
637 | ast::ExprKind::Block(..)
638 | ast::ExprKind::Closure(..)
639 | ast::ExprKind::Array(..)
640 | ast::ExprKind::Call(..)
641 | ast::ExprKind::MethodCall(..)
642 | ast::ExprKind::MacCall(..)
643 | ast::ExprKind::Struct(..)
644 | ast::ExprKind::Tup(..) => true,
645 ast::ExprKind::AddrOf(_, _, ref expr)
646 | ast::ExprKind::Try(ref expr)
647 | ast::ExprKind::Unary(_, ref expr)
648 | ast::ExprKind::Index(ref expr, _, _)
649 | ast::ExprKind::Cast(ref expr, _) => can_flatten_block_around_this(expr),
650 _ => false,
651 }
652}