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