1 //! Format match expression.
5 use rustc_ast
::{ast, ptr}
;
6 use rustc_span
::{BytePos, Span}
;
8 use crate::comment
::{combine_strs_with_missing_comments, rewrite_comment}
;
9 use crate::config
::lists
::*;
10 use crate::config
::{Config, ControlBraceStyle, IndentStyle, MatchArmLeadingPipe, Version}
;
12 format_expr
, is_empty_block
, is_simple_block
, is_unsafe_block
, prefer_next_line
, rewrite_cond
,
15 use crate::lists
::{itemize_list, write_list, ListFormatting}
;
16 use crate::rewrite
::{Rewrite, RewriteContext}
;
17 use crate::shape
::Shape
;
18 use crate::source_map
::SpanUtils
;
19 use crate::spanned
::Spanned
;
21 contains_skip
, extra_offset
, first_line_width
, inner_attributes
, last_line_extendable
, mk_sp
,
22 semicolon_for_expr
, trimmed_last_line_width
, unicode_str_width
,
25 /// A simple wrapper type against `ast::Arm`. Used inside `write_list()`.
26 struct ArmWrapper
<'a
> {
28 /// `true` if the arm is the last one in match expression. Used to decide on whether we should
29 /// add trailing comma to the match arm when `config.trailing_comma() == Never`.
31 /// Holds a byte position of `|` at the beginning of the arm pattern, if available.
32 beginning_vert
: Option
<BytePos
>,
35 impl<'a
> ArmWrapper
<'a
> {
36 fn new(arm
: &'a ast
::Arm
, is_last
: bool
, beginning_vert
: Option
<BytePos
>) -> ArmWrapper
<'a
> {
45 impl<'a
> Spanned
for ArmWrapper
<'a
> {
46 fn span(&self) -> Span
{
47 if let Some(lo
) = self.beginning_vert
{
48 let lo
= std
::cmp
::min(lo
, self.arm
.span().lo());
49 mk_sp(lo
, self.arm
.span().hi())
56 impl<'a
> Rewrite
for ArmWrapper
<'a
> {
57 fn rewrite(&self, context
: &RewriteContext
<'_
>, shape
: Shape
) -> Option
<String
> {
63 self.beginning_vert
.is_some(),
68 pub(crate) fn rewrite_match(
69 context
: &RewriteContext
<'_
>,
74 attrs
: &[ast
::Attribute
],
76 // Do not take the rhs overhead from the upper expressions into account
77 // when rewriting match condition.
78 let cond_shape
= Shape
{
79 width
: context
.budget(shape
.used_width()),
83 let cond_shape
= match context
.config
.indent_style() {
84 IndentStyle
::Visual
=> cond_shape
.shrink_left(6)?
,
85 IndentStyle
::Block
=> cond_shape
.offset_left(6)?
,
87 let cond_str
= cond
.rewrite(context
, cond_shape
)?
;
88 let alt_block_sep
= &shape
.indent
.to_string_with_newline(context
.config
);
89 let block_sep
= match context
.config
.control_brace_style() {
90 ControlBraceStyle
::AlwaysNextLine
=> alt_block_sep
,
91 _
if last_line_extendable(&cond_str
) => " ",
93 _
if cond_str
.contains('
\n'
) || cond_str
.len() + 2 > cond_shape
.width
=> alt_block_sep
,
97 let nested_indent_str
= shape
99 .block_indent(context
.config
)
100 .to_string(context
.config
);
102 let inner_attrs
= &inner_attributes(attrs
);
103 let inner_attrs_str
= if inner_attrs
.is_empty() {
107 .rewrite(context
, shape
)
108 .map(|s
| format
!("{}{}\n", nested_indent_str
, s
))?
111 let open_brace_pos
= if inner_attrs
.is_empty() {
112 let hi
= if arms
.is_empty() {
119 .span_after(mk_sp(cond
.span
.hi(), hi
), "{")
121 inner_attrs
[inner_attrs
.len() - 1].span
.hi()
125 let snippet
= context
.snippet(mk_sp(open_brace_pos
, span
.hi() - BytePos(1)));
126 if snippet
.trim().is_empty() {
127 Some(format
!("match {cond_str} {{}}"))
129 // Empty match with comments or inner attributes? We are not going to bother, sorry ;)
130 Some(context
.snippet(span
).to_owned())
133 let span_after_cond
= mk_sp(cond
.span
.hi(), span
.hi());
135 "match {}{}{{\n{}{}{}\n{}}}",
140 rewrite_match_arms(context
, arms
, shape
, span_after_cond
, open_brace_pos
)?
,
141 shape
.indent
.to_string(context
.config
),
146 fn arm_comma(config
: &Config
, body
: &ast
::Expr
, is_last
: bool
) -> &'
static str {
147 if is_last
&& config
.trailing_comma() == SeparatorTactic
::Never
{
149 } else if config
.match_block_trailing_comma() {
151 } else if let ast
::ExprKind
::Block(ref block
, _
) = body
.kind
{
152 if let ast
::BlockCheckMode
::Default
= block
.rules
{
162 /// Collect a byte position of the beginning `|` for each arm, if available.
163 fn collect_beginning_verts(
164 context
: &RewriteContext
<'_
>,
166 ) -> Vec
<Option
<BytePos
>> {
172 .then(|| a
.pat
.span().lo())
177 fn rewrite_match_arms(
178 context
: &RewriteContext
<'_
>,
182 open_brace_pos
: BytePos
,
183 ) -> Option
<String
> {
184 let arm_shape
= shape
185 .block_indent(context
.config
.tab_spaces())
186 .with_max_width(context
.config
);
188 let arm_len
= arms
.len();
189 let is_last_iter
= repeat(false)
190 .take(arm_len
.saturating_sub(1))
191 .chain(repeat(true));
192 let beginning_verts
= collect_beginning_verts(context
, arms
);
193 let items
= itemize_list(
194 context
.snippet_provider
,
197 .zip(beginning_verts
.into_iter())
198 .map(|((arm
, is_last
), beginning_vert
)| ArmWrapper
::new(arm
, is_last
, beginning_vert
)),
201 |arm
| arm
.span().lo(),
202 |arm
| arm
.span().hi(),
203 |arm
| arm
.rewrite(context
, arm_shape
),
208 let arms_vec
: Vec
<_
> = items
.collect();
209 // We will add/remove commas inside `arm.rewrite()`, and hence no separator here.
210 let fmt
= ListFormatting
::new(arm_shape
, context
.config
)
212 .preserve_newline(true);
214 write_list(&arms_vec
, &fmt
)
217 fn rewrite_match_arm(
218 context
: &RewriteContext
<'_
>,
222 has_leading_pipe
: bool
,
223 ) -> Option
<String
> {
224 let (missing_span
, attrs_str
) = if !arm
.attrs
.is_empty() {
225 if contains_skip(&arm
.attrs
) {
226 let (_
, body
) = flatten_arm_body(context
, &arm
.body
, None
);
227 // `arm.span()` does not include trailing comma, add it manually.
230 context
.snippet(arm
.span()),
231 arm_comma(context
.config
, body
, is_last
),
234 let missing_span
= mk_sp(arm
.attrs
[arm
.attrs
.len() - 1].span
.hi(), arm
.pat
.span
.lo());
235 (missing_span
, arm
.attrs
.rewrite(context
, shape
)?
)
237 (mk_sp(arm
.span().lo(), arm
.span().lo()), String
::new())
240 // Leading pipe offset
242 let (pipe_offset
, pipe_str
) = match context
.config
.match_arm_leading_pipes() {
243 MatchArmLeadingPipe
::Never
=> (0, ""),
244 MatchArmLeadingPipe
::Preserve
if !has_leading_pipe
=> (0, ""),
245 MatchArmLeadingPipe
::Preserve
| MatchArmLeadingPipe
::Always
=> (2, "| "),
249 let pat_shape
= match &arm
.body
.kind
{
250 ast
::ExprKind
::Block(_
, Some(label
)) => {
251 // Some block with a label ` => 'label: {`
253 let label_len
= label
.ident
.as_str().len();
254 shape
.sub_width(7 + label_len
)?
.offset_left(pipe_offset
)?
258 shape
.sub_width(5)?
.offset_left(pipe_offset
)?
261 let pats_str
= arm
.pat
.rewrite(context
, pat_shape
)?
;
264 let block_like_pat
= trimmed_last_line_width(&pats_str
) <= context
.config
.tab_spaces();
265 let new_line_guard
= pats_str
.contains('
\n'
) && !block_like_pat
;
266 let guard_str
= rewrite_guard(
270 trimmed_last_line_width(&pats_str
),
274 let lhs_str
= combine_strs_with_missing_comments(
277 &format
!("{pipe_str}{pats_str}{guard_str}"),
283 let arrow_span
= mk_sp(arm
.pat
.span
.hi(), arm
.body
.span().lo());
289 guard_str
.contains('
\n'
),
295 fn stmt_is_expr_mac(stmt
: &ast
::Stmt
) -> bool
{
296 if let ast
::StmtKind
::Expr(expr
) = &stmt
.kind
{
297 if let ast
::ExprKind
::MacCall(_
) = &expr
.kind
{
304 fn block_can_be_flattened
<'a
>(
305 context
: &RewriteContext
<'_
>,
307 ) -> Option
<&'a ast
::Block
> {
309 ast
::ExprKind
::Block(ref block
, label
)
311 && !is_unsafe_block(block
)
312 && !context
.inside_macro()
313 && is_simple_block(context
, block
, Some(&expr
.attrs
))
314 && !stmt_is_expr_mac(&block
.stmts
[0]) =>
323 // @extend: true if the arm body can be put next to `=>`
324 // @body: flattened body, if the body is block with a single expression
325 fn flatten_arm_body
<'a
>(
326 context
: &'a RewriteContext
<'_
>,
328 opt_shape
: Option
<Shape
>,
329 ) -> (bool
, &'a ast
::Expr
) {
331 |expr
| !context
.config
.force_multiline_blocks() && can_flatten_block_around_this(expr
);
333 if let Some(block
) = block_can_be_flattened(context
, body
) {
334 if let ast
::StmtKind
::Expr(ref expr
) = block
.stmts
[0].kind
{
335 if let ast
::ExprKind
::Block(..) = expr
.kind
{
336 if expr
.attrs
.is_empty() {
337 flatten_arm_body(context
, expr
, None
)
342 let cond_becomes_muti_line
= opt_shape
343 .and_then(|shape
| rewrite_cond(context
, expr
, shape
))
344 .map_or(false, |cond
| cond
.contains('
\n'
));
345 if cond_becomes_muti_line
{
348 (can_extend(expr
), &*expr
)
355 (can_extend(body
), &*body
)
359 fn rewrite_match_body(
360 context
: &RewriteContext
<'_
>,
361 body
: &ptr
::P
<ast
::Expr
>,
367 ) -> Option
<String
> {
368 let (extend
, body
) = flatten_arm_body(
371 shape
.offset_left(extra_offset(pats_str
, shape
) + 4),
373 let (is_block
, is_empty_block
) = if let ast
::ExprKind
::Block(ref block
, _
) = body
.kind
{
374 (true, is_empty_block(context
, block
, Some(&body
.attrs
)))
379 let comma
= arm_comma(context
.config
, body
, is_last
);
380 let alt_block_sep
= &shape
.indent
.to_string_with_newline(context
.config
);
382 let combine_orig_body
= |body_str
: &str| {
383 let block_sep
= match context
.config
.control_brace_style() {
384 ControlBraceStyle
::AlwaysNextLine
if is_block
=> alt_block_sep
,
388 Some(format
!("{} =>{}{}{}", pats_str
, block_sep
, body_str
, comma
))
391 let next_line_indent
= if !is_block
|| is_empty_block
{
392 shape
.indent
.block_indent(context
.config
)
397 let forbid_same_line
=
398 (has_guard
&& pats_str
.contains('
\n'
) && !is_empty_block
) || !body
.attrs
.is_empty();
400 // Look for comments between `=>` and the start of the body.
401 let arrow_comment
= {
402 let arrow_snippet
= context
.snippet(arrow_span
).trim();
403 // search for the arrow starting from the end of the snippet since there may be a match
404 // expression within the guard
405 let arrow_index
= arrow_snippet
.rfind("=>").unwrap();
407 let comment_str
= arrow_snippet
[arrow_index
+ 2..].trim();
408 if comment_str
.is_empty() {
411 rewrite_comment(comment_str
, false, shape
, context
.config
)?
415 let combine_next_line_body
= |body_str
: &str| {
416 let nested_indent_str
= next_line_indent
.to_string_with_newline(context
.config
);
419 let mut result
= pats_str
.to_owned();
420 result
.push_str(" =>");
421 if !arrow_comment
.is_empty() {
422 result
.push_str(&nested_indent_str
);
423 result
.push_str(&arrow_comment
);
425 result
.push_str(&nested_indent_str
);
426 result
.push_str(body_str
);
427 result
.push_str(comma
);
431 let indent_str
= shape
.indent
.to_string_with_newline(context
.config
);
432 let (body_prefix
, body_suffix
) =
433 if context
.config
.match_arm_blocks() && !context
.inside_macro() {
434 let comma
= if context
.config
.match_block_trailing_comma() {
439 let semicolon
= if context
.config
.version() == Version
::One
{
442 if semicolon_for_expr(context
, body
) {
448 ("{", format!("{}{}}}{}", semicolon
, indent_str
, comma
))
450 ("", String
::from(","))
453 let block_sep
= match context
.config
.control_brace_style() {
454 ControlBraceStyle
::AlwaysNextLine
=> format
!("{}{}", alt_block_sep
, body_prefix
),
455 _
if body_prefix
.is_empty() => "".to_owned(),
456 _
if forbid_same_line
|| !arrow_comment
.is_empty() => {
457 format
!("{}{}", alt_block_sep
, body_prefix
)
459 _
=> format
!(" {}", body_prefix
),
460 } + &nested_indent_str
;
462 let mut result
= pats_str
.to_owned();
463 result
.push_str(" =>");
464 if !arrow_comment
.is_empty() {
465 result
.push_str(&indent_str
);
466 result
.push_str(&arrow_comment
);
468 result
.push_str(&block_sep
);
469 result
.push_str(body_str
);
470 result
.push_str(&body_suffix
);
474 // Let's try and get the arm body on the same line as the condition.
476 let orig_body_shape
= shape
477 .offset_left(extra_offset(pats_str
, shape
) + 4)
478 .and_then(|shape
| shape
.sub_width(comma
.len()));
479 let orig_body
= if forbid_same_line
|| !arrow_comment
.is_empty() {
481 } else if let Some(body_shape
) = orig_body_shape
{
482 let rewrite
= nop_block_collapse(
483 format_expr(body
, ExprType
::Statement
, context
, body_shape
),
490 || (!body_str
.contains('
\n'
)
491 && unicode_str_width(body_str
) <= body_shape
.width
) =>
493 return combine_orig_body(body_str
);
500 let orig_budget
= orig_body_shape
.map_or(0, |shape
| shape
.width
);
502 // Try putting body on the next line and see if it looks better.
503 let next_line_body_shape
= Shape
::indented(next_line_indent
, context
.config
);
504 let next_line_body
= nop_block_collapse(
505 format_expr(body
, ExprType
::Statement
, context
, next_line_body_shape
),
506 next_line_body_shape
.width
,
508 match (orig_body
, next_line_body
) {
509 (Some(ref orig_str
), Some(ref next_line_str
))
510 if prefer_next_line(orig_str
, next_line_str
, RhsTactics
::Default
) =>
512 combine_next_line_body(next_line_str
)
514 (Some(ref orig_str
), _
) if extend
&& first_line_width(orig_str
) <= orig_budget
=> {
515 combine_orig_body(orig_str
)
517 (Some(ref orig_str
), Some(ref next_line_str
)) if orig_str
.contains('
\n'
) => {
518 combine_next_line_body(next_line_str
)
520 (None
, Some(ref next_line_str
)) => combine_next_line_body(next_line_str
),
521 (None
, None
) => None
,
522 (Some(ref orig_str
), _
) => combine_orig_body(orig_str
),
526 // The `if ...` guard on a match arm.
528 context
: &RewriteContext
<'_
>,
529 guard
: &Option
<ptr
::P
<ast
::Expr
>>,
531 // The amount of space used up on this line for the pattern in
532 // the arm (excludes offset).
533 pattern_width
: usize,
534 multiline_pattern
: bool
,
535 ) -> Option
<String
> {
536 if let Some(ref guard
) = *guard
{
537 // First try to fit the guard string on the same line as the pattern.
538 // 4 = ` if `, 5 = ` => {`
539 let cond_shape
= shape
540 .offset_left(pattern_width
+ 4)
541 .and_then(|s
| s
.sub_width(5));
542 if !multiline_pattern
{
543 if let Some(cond_shape
) = cond_shape
{
544 if let Some(cond_str
) = guard
.rewrite(context
, cond_shape
) {
545 if !cond_str
.contains('
\n'
) || pattern_width
<= context
.config
.tab_spaces() {
546 return Some(format
!(" if {cond_str}"));
552 // Not enough space to put the guard after the pattern, try a newline.
553 // 3 = `if `, 5 = ` => {`
554 let cond_shape
= Shape
::indented(shape
.indent
.block_indent(context
.config
), context
.config
)
556 .and_then(|s
| s
.sub_width(5));
557 if let Some(cond_shape
) = cond_shape
{
558 if let Some(cond_str
) = guard
.rewrite(context
, cond_shape
) {
561 cond_shape
.indent
.to_string_with_newline(context
.config
),
573 fn nop_block_collapse(block_str
: Option
<String
>, budget
: usize) -> Option
<String
> {
574 debug
!("nop_block_collapse {:?} {}", block_str
, budget
);
575 block_str
.map(|block_str
| {
576 if block_str
.starts_with('
{'
)
578 && (block_str
[1..].find(|c
: char| !c
.is_whitespace()).unwrap() == block_str
.len() - 2)
587 fn can_flatten_block_around_this(body
: &ast
::Expr
) -> bool
{
589 // We do not allow `if` to stay on the same line, since we could easily mistake
590 // `pat => if cond { ... }` and `pat if cond => { ... }`.
591 ast
::ExprKind
::If(..) => false,
592 // We do not allow collapsing a block around expression with condition
593 // to avoid it being cluttered with match arm.
594 ast
::ExprKind
::ForLoop(..) | ast
::ExprKind
::While(..) => false,
595 ast
::ExprKind
::Loop(..)
596 | ast
::ExprKind
::Match(..)
597 | ast
::ExprKind
::Block(..)
598 | ast
::ExprKind
::Closure(..)
599 | ast
::ExprKind
::Array(..)
600 | ast
::ExprKind
::Call(..)
601 | ast
::ExprKind
::MethodCall(..)
602 | ast
::ExprKind
::MacCall(..)
603 | ast
::ExprKind
::Struct(..)
604 | ast
::ExprKind
::Tup(..) => true,
605 ast
::ExprKind
::AddrOf(_
, _
, ref expr
)
606 | ast
::ExprKind
::Try(ref expr
)
607 | ast
::ExprKind
::Unary(_
, ref expr
)
608 | ast
::ExprKind
::Index(ref expr
, _
, _
)
609 | ast
::ExprKind
::Cast(ref expr
, _
) => can_flatten_block_around_this(expr
),