From 88589f2ad891f115e44cab2f37993885c82c40d8 Mon Sep 17 00:00:00 2001 From: Seiichi Uchida Date: Wed, 21 Mar 2018 22:02:18 +0900 Subject: [PATCH] Add matches module `matches` module contains `rewrite_match` and related stuffs. --- src/expr.rs | 551 +----------------------------------------------- src/lib.rs | 1 + src/matches.rs | 531 ++++++++++++++++++++++++++++++++++++++++++++++ src/patterns.rs | 34 +++ 4 files changed, 573 insertions(+), 544 deletions(-) create mode 100644 src/matches.rs diff --git a/src/expr.rs b/src/expr.rs index e144c8307b6..e6cd6ee046f 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -10,7 +10,6 @@ use std::borrow::Cow; use std::cmp::min; -use std::iter::repeat; use config::lists::*; use syntax::{ast, ptr}; @@ -25,17 +24,17 @@ use config::{Config, ControlBraceStyle, IndentStyle}; use lists::{definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape, struct_lit_tactic, write_list, ListFormatting, ListItem, Separator}; use macros::{rewrite_macro, MacroArg, MacroPosition}; +use matches::rewrite_match; use overflow; -use patterns::{can_be_overflowed_pat, TuplePatField}; +use patterns::{can_be_overflowed_pat, is_short_pattern, TuplePatField}; use rewrite::{Rewrite, RewriteContext}; use shape::{Indent, Shape}; use spanned::Spanned; use string::{rewrite_string, StringFormat}; use types::{can_be_overflowed_type, rewrite_path, PathContext}; -use utils::{colon_spaces, contains_skip, count_newlines, extra_offset, first_line_width, - inner_attributes, last_line_extendable, last_line_width, mk_sp, outer_attributes, - paren_overhead, ptr_vec_to_ref_vec, semicolon_for_stmt, trimmed_last_line_width, - wrap_str}; +use utils::{colon_spaces, contains_skip, count_newlines, first_line_width, inner_attributes, + last_line_extendable, last_line_width, mk_sp, outer_attributes, paren_overhead, + ptr_vec_to_ref_vec, semicolon_for_stmt, wrap_str}; use vertical::rewrite_with_alignment; use visitor::FmtVisitor; @@ -566,19 +565,6 @@ fn array_tactic( } } -fn nop_block_collapse(block_str: Option, budget: usize) -> Option { - debug!("nop_block_collapse {:?} {}", block_str, budget); - block_str.map(|block_str| { - if block_str.starts_with('{') && budget >= 2 - && (block_str[1..].find(|c: char| !c.is_whitespace()).unwrap() == block_str.len() - 2) - { - "{}".to_owned() - } else { - block_str.to_owned() - } - }) -} - fn rewrite_empty_block( context: &RewriteContext, block: &ast::Block, @@ -1320,503 +1306,7 @@ pub fn is_unsafe_block(block: &ast::Block) -> bool { } } -/// A simple wrapper type against `ast::Arm`. Used inside `write_list()`. -struct ArmWrapper<'a> { - pub arm: &'a ast::Arm, - /// True if the arm is the last one in match expression. Used to decide on whether we should add - /// trailing comma to the match arm when `config.trailing_comma() == Never`. - pub is_last: bool, - /// Holds a byte position of `|` at the beginning of the arm pattern, if available. - pub beginning_vert: Option, -} - -impl<'a> ArmWrapper<'a> { - pub fn new( - arm: &'a ast::Arm, - is_last: bool, - beginning_vert: Option, - ) -> ArmWrapper<'a> { - ArmWrapper { - arm, - is_last, - beginning_vert, - } - } -} - -impl<'a> Spanned for ArmWrapper<'a> { - fn span(&self) -> Span { - if let Some(lo) = self.beginning_vert { - mk_sp(lo, self.arm.span().hi()) - } else { - self.arm.span() - } - } -} - -impl<'a> Rewrite for ArmWrapper<'a> { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option { - rewrite_match_arm(context, self.arm, shape, self.is_last, self.beginning_vert) - } -} - -fn rewrite_match( - context: &RewriteContext, - cond: &ast::Expr, - arms: &[ast::Arm], - shape: Shape, - span: Span, - attrs: &[ast::Attribute], -) -> Option { - // Do not take the rhs overhead from the upper expressions into account - // when rewriting match condition. - let cond_shape = Shape { - width: context.budget(shape.used_width()), - ..shape - }; - // 6 = `match ` - let cond_shape = match context.config.indent_style() { - IndentStyle::Visual => cond_shape.shrink_left(6)?, - IndentStyle::Block => cond_shape.offset_left(6)?, - }; - let cond_str = cond.rewrite(context, cond_shape)?; - let alt_block_sep = &shape.indent.to_string_with_newline(context.config); - let block_sep = match context.config.control_brace_style() { - ControlBraceStyle::AlwaysNextLine => alt_block_sep, - _ if last_line_extendable(&cond_str) => " ", - // 2 = ` {` - _ if cond_str.contains('\n') || cond_str.len() + 2 > cond_shape.width => alt_block_sep, - _ => " ", - }; - - let nested_indent_str = shape - .indent - .block_indent(context.config) - .to_string(context.config); - // Inner attributes. - let inner_attrs = &inner_attributes(attrs); - let inner_attrs_str = if inner_attrs.is_empty() { - String::new() - } else { - inner_attrs - .rewrite(context, shape) - .map(|s| format!("{}{}\n", nested_indent_str, s))? - }; - - let open_brace_pos = if inner_attrs.is_empty() { - let hi = if arms.is_empty() { - span.hi() - } else { - arms[0].span().lo() - }; - context - .snippet_provider - .span_after(mk_sp(cond.span.hi(), hi), "{") - } else { - inner_attrs[inner_attrs.len() - 1].span().hi() - }; - - if arms.is_empty() { - let snippet = context.snippet(mk_sp(open_brace_pos, span.hi() - BytePos(1))); - if snippet.trim().is_empty() { - Some(format!("match {} {{}}", cond_str)) - } else { - // Empty match with comments or inner attributes? We are not going to bother, sorry ;) - Some(context.snippet(span).to_owned()) - } - } else { - Some(format!( - "match {}{}{{\n{}{}{}\n{}}}", - cond_str, - block_sep, - inner_attrs_str, - nested_indent_str, - rewrite_match_arms(context, arms, shape, span, open_brace_pos)?, - shape.indent.to_string(context.config), - )) - } -} - -fn arm_comma(config: &Config, body: &ast::Expr, is_last: bool) -> &'static str { - if is_last && config.trailing_comma() == SeparatorTactic::Never { - "" - } else if config.match_block_trailing_comma() { - "," - } else if let ast::ExprKind::Block(ref block) = body.node { - if let ast::BlockCheckMode::Default = block.rules { - "" - } else { - "," - } - } else { - "," - } -} - -/// Collect a byte position of the beginning `|` for each arm, if available. -fn collect_beginning_verts( - context: &RewriteContext, - arms: &[ast::Arm], - span: Span, -) -> Vec> { - let mut beginning_verts = Vec::with_capacity(arms.len()); - let mut lo = context.snippet_provider.span_after(span, "{"); - for arm in arms { - let hi = arm.pats[0].span.lo(); - let missing_span = mk_sp(lo, hi); - beginning_verts.push(context.snippet_provider.opt_span_before(missing_span, "|")); - lo = arm.span().hi(); - } - beginning_verts -} - -fn rewrite_match_arms( - context: &RewriteContext, - arms: &[ast::Arm], - shape: Shape, - span: Span, - open_brace_pos: BytePos, -) -> Option { - let arm_shape = shape - .block_indent(context.config.tab_spaces()) - .with_max_width(context.config); - - let arm_len = arms.len(); - let is_last_iter = repeat(false) - .take(arm_len.checked_sub(1).unwrap_or(0)) - .chain(repeat(true)); - let beginning_verts = collect_beginning_verts(context, arms, span); - let items = itemize_list( - context.snippet_provider, - arms.iter() - .zip(is_last_iter) - .zip(beginning_verts.into_iter()) - .map(|((arm, is_last), beginning_vert)| ArmWrapper::new(arm, is_last, beginning_vert)), - "}", - "|", - |arm| arm.span().lo(), - |arm| arm.span().hi(), - |arm| arm.rewrite(context, arm_shape), - open_brace_pos, - span.hi(), - false, - ); - let arms_vec: Vec<_> = items.collect(); - let fmt = ListFormatting { - tactic: DefinitiveListTactic::Vertical, - // We will add/remove commas inside `arm.rewrite()`, and hence no separator here. - separator: "", - trailing_separator: SeparatorTactic::Never, - separator_place: SeparatorPlace::Back, - shape: arm_shape, - ends_with_newline: true, - preserve_newline: true, - config: context.config, - }; - - write_list(&arms_vec, &fmt) -} - -fn rewrite_match_arm( - context: &RewriteContext, - arm: &ast::Arm, - shape: Shape, - is_last: bool, - beginning_vert: Option, -) -> Option { - let (missing_span, attrs_str) = if !arm.attrs.is_empty() { - if contains_skip(&arm.attrs) { - let (_, body) = flatten_arm_body(context, &arm.body); - // `arm.span()` does not include trailing comma, add it manually. - return Some(format!( - "{}{}", - context.snippet(arm.span()), - arm_comma(context.config, body, is_last), - )); - } - let missing_span = mk_sp( - arm.attrs[arm.attrs.len() - 1].span.hi(), - arm.pats[0].span.lo(), - ); - (missing_span, arm.attrs.rewrite(context, shape)?) - } else { - (mk_sp(arm.span().lo(), arm.span().lo()), String::new()) - }; - let pats_str = rewrite_match_pattern( - context, - &ptr_vec_to_ref_vec(&arm.pats), - &arm.guard, - beginning_vert.is_some(), - shape, - ).and_then(|pats_str| { - combine_strs_with_missing_comments( - context, - &attrs_str, - &pats_str, - missing_span, - shape, - false, - ) - })?; - rewrite_match_body( - context, - &arm.body, - &pats_str, - shape, - arm.guard.is_some(), - is_last, - ) -} - -/// Returns true if the given pattern is short. A short pattern is defined by the following grammer: -/// -/// [small, ntp]: -/// - single token -/// - `&[single-line, ntp]` -/// -/// [small]: -/// - `[small, ntp]` -/// - unary tuple constructor `([small, ntp])` -/// - `&[small]` -fn is_short_pattern(pat: &ast::Pat, pat_str: &str) -> bool { - // We also require that the pattern is reasonably 'small' with its literal width. - pat_str.len() <= 20 && !pat_str.contains('\n') && is_short_pattern_inner(pat) -} - -fn is_short_pattern_inner(pat: &ast::Pat) -> bool { - match pat.node { - ast::PatKind::Wild | ast::PatKind::Lit(_) => true, - ast::PatKind::Ident(_, _, ref pat) => pat.is_none(), - ast::PatKind::Struct(..) - | ast::PatKind::Mac(..) - | ast::PatKind::Slice(..) - | ast::PatKind::Path(..) - | ast::PatKind::Range(..) => false, - ast::PatKind::Tuple(ref subpats, _) => subpats.len() <= 1, - ast::PatKind::TupleStruct(ref path, ref subpats, _) => { - path.segments.len() <= 1 && subpats.len() <= 1 - } - ast::PatKind::Box(ref p) | ast::PatKind::Ref(ref p, _) | ast::PatKind::Paren(ref p) => { - is_short_pattern_inner(&*p) - } - } -} - -fn rewrite_match_pattern( - context: &RewriteContext, - pats: &[&ast::Pat], - guard: &Option>, - has_beginning_vert: bool, - shape: Shape, -) -> Option { - // Patterns - // 5 = ` => {` - // 2 = `| ` - let pat_shape = shape - .sub_width(5)? - .offset_left(if has_beginning_vert { 2 } else { 0 })?; - let pats_str = rewrite_multiple_patterns(context, pats, pat_shape)?; - let beginning_vert = if has_beginning_vert { "| " } else { "" }; - - // Guard - let guard_str = rewrite_guard(context, guard, shape, trimmed_last_line_width(&pats_str))?; - - Some(format!("{}{}{}", beginning_vert, pats_str, guard_str)) -} - -// (extend, body) -// @extend: true if the arm body can be put next to `=>` -// @body: flattened body, if the body is block with a single expression -fn flatten_arm_body<'a>(context: &'a RewriteContext, body: &'a ast::Expr) -> (bool, &'a ast::Expr) { - match body.node { - ast::ExprKind::Block(ref block) - if !is_unsafe_block(block) - && is_simple_block(block, Some(&body.attrs), context.codemap) => - { - if let ast::StmtKind::Expr(ref expr) = block.stmts[0].node { - ( - !context.config.force_multiline_blocks() && can_extend_match_arm_body(expr), - &*expr, - ) - } else { - (false, &*body) - } - } - _ => ( - !context.config.force_multiline_blocks() && body.can_be_overflowed(context, 1), - &*body, - ), - } -} - -fn rewrite_match_body( - context: &RewriteContext, - body: &ptr::P, - pats_str: &str, - shape: Shape, - has_guard: bool, - is_last: bool, -) -> Option { - let (extend, body) = flatten_arm_body(context, body); - let (is_block, is_empty_block) = if let ast::ExprKind::Block(ref block) = body.node { - ( - true, - is_empty_block(block, Some(&body.attrs), context.codemap), - ) - } else { - (false, false) - }; - - let comma = arm_comma(context.config, body, is_last); - let alt_block_sep = &shape.indent.to_string_with_newline(context.config); - - let combine_orig_body = |body_str: &str| { - let block_sep = match context.config.control_brace_style() { - ControlBraceStyle::AlwaysNextLine if is_block => alt_block_sep, - _ => " ", - }; - - Some(format!("{} =>{}{}{}", pats_str, block_sep, body_str, comma)) - }; - - let forbid_same_line = has_guard && pats_str.contains('\n') && !is_empty_block; - let next_line_indent = if !is_block || is_empty_block { - shape.indent.block_indent(context.config) - } else { - shape.indent - }; - let combine_next_line_body = |body_str: &str| { - if is_block { - return Some(format!( - "{} =>{}{}", - pats_str, - next_line_indent.to_string_with_newline(context.config), - body_str - )); - } - - let indent_str = shape.indent.to_string_with_newline(context.config); - let nested_indent_str = next_line_indent.to_string_with_newline(context.config); - let (body_prefix, body_suffix) = if context.config.match_arm_blocks() { - let comma = if context.config.match_block_trailing_comma() { - "," - } else { - "" - }; - ("{", format!("{}}}{}", indent_str, comma)) - } else { - ("", String::from(",")) - }; - - let block_sep = match context.config.control_brace_style() { - ControlBraceStyle::AlwaysNextLine => format!("{}{}", alt_block_sep, body_prefix), - _ if body_prefix.is_empty() => "".to_owned(), - _ if forbid_same_line => format!("{}{}", alt_block_sep, body_prefix), - _ => format!(" {}", body_prefix), - } + &nested_indent_str; - - Some(format!( - "{} =>{}{}{}", - pats_str, block_sep, body_str, body_suffix - )) - }; - - // Let's try and get the arm body on the same line as the condition. - // 4 = ` => `.len() - let orig_body_shape = shape - .offset_left(extra_offset(pats_str, shape) + 4) - .and_then(|shape| shape.sub_width(comma.len())); - let orig_body = if let Some(body_shape) = orig_body_shape { - let rewrite = nop_block_collapse( - format_expr(body, ExprType::Statement, context, body_shape), - body_shape.width, - ); - - match rewrite { - Some(ref body_str) - if !forbid_same_line - && (is_block - || (!body_str.contains('\n') && body_str.len() <= body_shape.width)) => - { - return combine_orig_body(body_str); - } - _ => rewrite, - } - } else { - None - }; - let orig_budget = orig_body_shape.map_or(0, |shape| shape.width); - - // Try putting body on the next line and see if it looks better. - let next_line_body_shape = Shape::indented(next_line_indent, context.config); - let next_line_body = nop_block_collapse( - format_expr(body, ExprType::Statement, context, next_line_body_shape), - next_line_body_shape.width, - ); - match (orig_body, next_line_body) { - (Some(ref orig_str), Some(ref next_line_str)) - if forbid_same_line - || prefer_next_line(orig_str, next_line_str, RhsTactics::Default) => - { - combine_next_line_body(next_line_str) - } - (Some(ref orig_str), _) if extend && first_line_width(orig_str) <= orig_budget => { - combine_orig_body(orig_str) - } - (Some(ref orig_str), Some(ref next_line_str)) if orig_str.contains('\n') => { - combine_next_line_body(next_line_str) - } - (None, Some(ref next_line_str)) => combine_next_line_body(next_line_str), - (None, None) => None, - (Some(ref orig_str), _) => combine_orig_body(orig_str), - } -} - -// The `if ...` guard on a match arm. -fn rewrite_guard( - context: &RewriteContext, - guard: &Option>, - shape: Shape, - // The amount of space used up on this line for the pattern in - // the arm (excludes offset). - pattern_width: usize, -) -> Option { - if let Some(ref guard) = *guard { - // First try to fit the guard string on the same line as the pattern. - // 4 = ` if `, 5 = ` => {` - let cond_shape = shape - .offset_left(pattern_width + 4) - .and_then(|s| s.sub_width(5)); - if let Some(cond_shape) = cond_shape { - if let Some(cond_str) = guard.rewrite(context, cond_shape) { - if !cond_str.contains('\n') || pattern_width <= context.config.tab_spaces() { - return Some(format!(" if {}", cond_str)); - } - } - } - - // Not enough space to put the guard after the pattern, try a newline. - // 3 = `if `, 5 = ` => {` - let cond_shape = Shape::indented(shape.indent.block_indent(context.config), context.config) - .offset_left(3) - .and_then(|s| s.sub_width(5)); - if let Some(cond_shape) = cond_shape { - if let Some(cond_str) = guard.rewrite(context, cond_shape) { - return Some(format!( - "{}if {}", - cond_shape.indent.to_string_with_newline(context.config), - cond_str - )); - } - } - - None - } else { - Some(String::new()) - } -} - -fn rewrite_multiple_patterns( +pub fn rewrite_multiple_patterns( context: &RewriteContext, pats: &[&ast::Pat], shape: Shape, @@ -1852,33 +1342,6 @@ fn rewrite_multiple_patterns( write_list(&items, &fmt) } -fn can_extend_match_arm_body(body: &ast::Expr) -> bool { - match body.node { - // We do not allow `if` to stay on the same line, since we could easily mistake - // `pat => if cond { ... }` and `pat if cond => { ... }`. - ast::ExprKind::If(..) | ast::ExprKind::IfLet(..) => false, - ast::ExprKind::ForLoop(..) - | ast::ExprKind::Loop(..) - | ast::ExprKind::While(..) - | ast::ExprKind::WhileLet(..) - | ast::ExprKind::Match(..) - | ast::ExprKind::Block(..) - | ast::ExprKind::Closure(..) - | ast::ExprKind::Array(..) - | ast::ExprKind::Call(..) - | ast::ExprKind::MethodCall(..) - | ast::ExprKind::Mac(..) - | ast::ExprKind::Struct(..) - | ast::ExprKind::Tup(..) => true, - ast::ExprKind::AddrOf(_, ref expr) - | ast::ExprKind::Box(ref expr) - | ast::ExprKind::Try(ref expr) - | ast::ExprKind::Unary(_, ref expr) - | ast::ExprKind::Cast(ref expr, _) => can_extend_match_arm_body(expr), - _ => false, - } -} - pub fn rewrite_literal(context: &RewriteContext, l: &ast::Lit, shape: Shape) -> Option { match l.node { ast::LitKind::Str(_, ast::StrStyle::Cooked) => rewrite_string_lit(context, l.span, shape), @@ -2663,7 +2126,7 @@ fn choose_rhs( } } -fn prefer_next_line(orig_rhs: &str, next_line_rhs: &str, rhs_tactics: RhsTactics) -> bool { +pub fn prefer_next_line(orig_rhs: &str, next_line_rhs: &str, rhs_tactics: RhsTactics) -> bool { rhs_tactics == RhsTactics::ForceNextLine || !next_line_rhs.contains('\n') || count_newlines(orig_rhs) > count_newlines(next_line_rhs) + 1 } diff --git a/src/lib.rs b/src/lib.rs index eff414b5708..683a75cb32a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,7 @@ mod issues; mod items; mod lists; mod macros; +mod matches; mod missed_spans; pub mod modules; mod overflow; diff --git a/src/matches.rs b/src/matches.rs new file mode 100644 index 00000000000..b97fbd87417 --- /dev/null +++ b/src/matches.rs @@ -0,0 +1,531 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Format match expression. + +use std::iter::repeat; + +use config::lists::*; +use syntax::{ast, ptr}; +use syntax::codemap::{BytePos, Span}; + +use codemap::SpanUtils; +use comment::combine_strs_with_missing_comments; +use config::{Config, ControlBraceStyle, IndentStyle}; +use expr::{format_expr, is_empty_block, is_simple_block, is_unsafe_block, prefer_next_line, + rewrite_multiple_patterns, ExprType, RhsTactics, ToExpr}; +use lists::{itemize_list, write_list, ListFormatting}; +use rewrite::{Rewrite, RewriteContext}; +use shape::Shape; +use spanned::Spanned; +use utils::{contains_skip, extra_offset, first_line_width, inner_attributes, last_line_extendable, + mk_sp, ptr_vec_to_ref_vec, trimmed_last_line_width}; + +/// A simple wrapper type against `ast::Arm`. Used inside `write_list()`. +struct ArmWrapper<'a> { + pub arm: &'a ast::Arm, + /// True if the arm is the last one in match expression. Used to decide on whether we should add + /// trailing comma to the match arm when `config.trailing_comma() == Never`. + pub is_last: bool, + /// Holds a byte position of `|` at the beginning of the arm pattern, if available. + pub beginning_vert: Option, +} + +impl<'a> ArmWrapper<'a> { + pub fn new( + arm: &'a ast::Arm, + is_last: bool, + beginning_vert: Option, + ) -> ArmWrapper<'a> { + ArmWrapper { + arm, + is_last, + beginning_vert, + } + } +} + +impl<'a> Spanned for ArmWrapper<'a> { + fn span(&self) -> Span { + if let Some(lo) = self.beginning_vert { + mk_sp(lo, self.arm.span().hi()) + } else { + self.arm.span() + } + } +} + +impl<'a> Rewrite for ArmWrapper<'a> { + fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option { + rewrite_match_arm(context, self.arm, shape, self.is_last, self.beginning_vert) + } +} + +pub fn rewrite_match( + context: &RewriteContext, + cond: &ast::Expr, + arms: &[ast::Arm], + shape: Shape, + span: Span, + attrs: &[ast::Attribute], +) -> Option { + // Do not take the rhs overhead from the upper expressions into account + // when rewriting match condition. + let cond_shape = Shape { + width: context.budget(shape.used_width()), + ..shape + }; + // 6 = `match ` + let cond_shape = match context.config.indent_style() { + IndentStyle::Visual => cond_shape.shrink_left(6)?, + IndentStyle::Block => cond_shape.offset_left(6)?, + }; + let cond_str = cond.rewrite(context, cond_shape)?; + let alt_block_sep = &shape.indent.to_string_with_newline(context.config); + let block_sep = match context.config.control_brace_style() { + ControlBraceStyle::AlwaysNextLine => alt_block_sep, + _ if last_line_extendable(&cond_str) => " ", + // 2 = ` {` + _ if cond_str.contains('\n') || cond_str.len() + 2 > cond_shape.width => alt_block_sep, + _ => " ", + }; + + let nested_indent_str = shape + .indent + .block_indent(context.config) + .to_string(context.config); + // Inner attributes. + let inner_attrs = &inner_attributes(attrs); + let inner_attrs_str = if inner_attrs.is_empty() { + String::new() + } else { + inner_attrs + .rewrite(context, shape) + .map(|s| format!("{}{}\n", nested_indent_str, s))? + }; + + let open_brace_pos = if inner_attrs.is_empty() { + let hi = if arms.is_empty() { + span.hi() + } else { + arms[0].span().lo() + }; + context + .snippet_provider + .span_after(mk_sp(cond.span.hi(), hi), "{") + } else { + inner_attrs[inner_attrs.len() - 1].span().hi() + }; + + if arms.is_empty() { + let snippet = context.snippet(mk_sp(open_brace_pos, span.hi() - BytePos(1))); + if snippet.trim().is_empty() { + Some(format!("match {} {{}}", cond_str)) + } else { + // Empty match with comments or inner attributes? We are not going to bother, sorry ;) + Some(context.snippet(span).to_owned()) + } + } else { + Some(format!( + "match {}{}{{\n{}{}{}\n{}}}", + cond_str, + block_sep, + inner_attrs_str, + nested_indent_str, + rewrite_match_arms(context, arms, shape, span, open_brace_pos)?, + shape.indent.to_string(context.config), + )) + } +} + +fn arm_comma(config: &Config, body: &ast::Expr, is_last: bool) -> &'static str { + if is_last && config.trailing_comma() == SeparatorTactic::Never { + "" + } else if config.match_block_trailing_comma() { + "," + } else if let ast::ExprKind::Block(ref block) = body.node { + if let ast::BlockCheckMode::Default = block.rules { + "" + } else { + "," + } + } else { + "," + } +} + +/// Collect a byte position of the beginning `|` for each arm, if available. +fn collect_beginning_verts( + context: &RewriteContext, + arms: &[ast::Arm], + span: Span, +) -> Vec> { + let mut beginning_verts = Vec::with_capacity(arms.len()); + let mut lo = context.snippet_provider.span_after(span, "{"); + for arm in arms { + let hi = arm.pats[0].span.lo(); + let missing_span = mk_sp(lo, hi); + beginning_verts.push(context.snippet_provider.opt_span_before(missing_span, "|")); + lo = arm.span().hi(); + } + beginning_verts +} + +fn rewrite_match_arms( + context: &RewriteContext, + arms: &[ast::Arm], + shape: Shape, + span: Span, + open_brace_pos: BytePos, +) -> Option { + let arm_shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + + let arm_len = arms.len(); + let is_last_iter = repeat(false) + .take(arm_len.checked_sub(1).unwrap_or(0)) + .chain(repeat(true)); + let beginning_verts = collect_beginning_verts(context, arms, span); + let items = itemize_list( + context.snippet_provider, + arms.iter() + .zip(is_last_iter) + .zip(beginning_verts.into_iter()) + .map(|((arm, is_last), beginning_vert)| ArmWrapper::new(arm, is_last, beginning_vert)), + "}", + "|", + |arm| arm.span().lo(), + |arm| arm.span().hi(), + |arm| arm.rewrite(context, arm_shape), + open_brace_pos, + span.hi(), + false, + ); + let arms_vec: Vec<_> = items.collect(); + let fmt = ListFormatting { + tactic: DefinitiveListTactic::Vertical, + // We will add/remove commas inside `arm.rewrite()`, and hence no separator here. + separator: "", + trailing_separator: SeparatorTactic::Never, + separator_place: SeparatorPlace::Back, + shape: arm_shape, + ends_with_newline: true, + preserve_newline: true, + config: context.config, + }; + + write_list(&arms_vec, &fmt) +} + +fn rewrite_match_arm( + context: &RewriteContext, + arm: &ast::Arm, + shape: Shape, + is_last: bool, + beginning_vert: Option, +) -> Option { + let (missing_span, attrs_str) = if !arm.attrs.is_empty() { + if contains_skip(&arm.attrs) { + let (_, body) = flatten_arm_body(context, &arm.body); + // `arm.span()` does not include trailing comma, add it manually. + return Some(format!( + "{}{}", + context.snippet(arm.span()), + arm_comma(context.config, body, is_last), + )); + } + let missing_span = mk_sp( + arm.attrs[arm.attrs.len() - 1].span.hi(), + arm.pats[0].span.lo(), + ); + (missing_span, arm.attrs.rewrite(context, shape)?) + } else { + (mk_sp(arm.span().lo(), arm.span().lo()), String::new()) + }; + let pats_str = rewrite_match_pattern( + context, + &ptr_vec_to_ref_vec(&arm.pats), + &arm.guard, + beginning_vert.is_some(), + shape, + ).and_then(|pats_str| { + combine_strs_with_missing_comments( + context, + &attrs_str, + &pats_str, + missing_span, + shape, + false, + ) + })?; + rewrite_match_body( + context, + &arm.body, + &pats_str, + shape, + arm.guard.is_some(), + is_last, + ) +} + +fn rewrite_match_pattern( + context: &RewriteContext, + pats: &[&ast::Pat], + guard: &Option>, + has_beginning_vert: bool, + shape: Shape, +) -> Option { + // Patterns + // 5 = ` => {` + // 2 = `| ` + let pat_shape = shape + .sub_width(5)? + .offset_left(if has_beginning_vert { 2 } else { 0 })?; + let pats_str = rewrite_multiple_patterns(context, pats, pat_shape)?; + let beginning_vert = if has_beginning_vert { "| " } else { "" }; + + // Guard + let guard_str = rewrite_guard(context, guard, shape, trimmed_last_line_width(&pats_str))?; + + Some(format!("{}{}{}", beginning_vert, pats_str, guard_str)) +} + +// (extend, body) +// @extend: true if the arm body can be put next to `=>` +// @body: flattened body, if the body is block with a single expression +fn flatten_arm_body<'a>(context: &'a RewriteContext, body: &'a ast::Expr) -> (bool, &'a ast::Expr) { + match body.node { + ast::ExprKind::Block(ref block) + if !is_unsafe_block(block) + && is_simple_block(block, Some(&body.attrs), context.codemap) => + { + if let ast::StmtKind::Expr(ref expr) = block.stmts[0].node { + ( + !context.config.force_multiline_blocks() && can_extend_match_arm_body(expr), + &*expr, + ) + } else { + (false, &*body) + } + } + _ => ( + !context.config.force_multiline_blocks() && body.can_be_overflowed(context, 1), + &*body, + ), + } +} + +fn rewrite_match_body( + context: &RewriteContext, + body: &ptr::P, + pats_str: &str, + shape: Shape, + has_guard: bool, + is_last: bool, +) -> Option { + let (extend, body) = flatten_arm_body(context, body); + let (is_block, is_empty_block) = if let ast::ExprKind::Block(ref block) = body.node { + ( + true, + is_empty_block(block, Some(&body.attrs), context.codemap), + ) + } else { + (false, false) + }; + + let comma = arm_comma(context.config, body, is_last); + let alt_block_sep = &shape.indent.to_string_with_newline(context.config); + + let combine_orig_body = |body_str: &str| { + let block_sep = match context.config.control_brace_style() { + ControlBraceStyle::AlwaysNextLine if is_block => alt_block_sep, + _ => " ", + }; + + Some(format!("{} =>{}{}{}", pats_str, block_sep, body_str, comma)) + }; + + let forbid_same_line = has_guard && pats_str.contains('\n') && !is_empty_block; + let next_line_indent = if !is_block || is_empty_block { + shape.indent.block_indent(context.config) + } else { + shape.indent + }; + let combine_next_line_body = |body_str: &str| { + if is_block { + return Some(format!( + "{} =>{}{}", + pats_str, + next_line_indent.to_string_with_newline(context.config), + body_str + )); + } + + let indent_str = shape.indent.to_string_with_newline(context.config); + let nested_indent_str = next_line_indent.to_string_with_newline(context.config); + let (body_prefix, body_suffix) = if context.config.match_arm_blocks() { + let comma = if context.config.match_block_trailing_comma() { + "," + } else { + "" + }; + ("{", format!("{}}}{}", indent_str, comma)) + } else { + ("", String::from(",")) + }; + + let block_sep = match context.config.control_brace_style() { + ControlBraceStyle::AlwaysNextLine => format!("{}{}", alt_block_sep, body_prefix), + _ if body_prefix.is_empty() => "".to_owned(), + _ if forbid_same_line => format!("{}{}", alt_block_sep, body_prefix), + _ => format!(" {}", body_prefix), + } + &nested_indent_str; + + Some(format!( + "{} =>{}{}{}", + pats_str, block_sep, body_str, body_suffix + )) + }; + + // Let's try and get the arm body on the same line as the condition. + // 4 = ` => `.len() + let orig_body_shape = shape + .offset_left(extra_offset(pats_str, shape) + 4) + .and_then(|shape| shape.sub_width(comma.len())); + let orig_body = if let Some(body_shape) = orig_body_shape { + let rewrite = nop_block_collapse( + format_expr(body, ExprType::Statement, context, body_shape), + body_shape.width, + ); + + match rewrite { + Some(ref body_str) + if !forbid_same_line + && (is_block + || (!body_str.contains('\n') && body_str.len() <= body_shape.width)) => + { + return combine_orig_body(body_str); + } + _ => rewrite, + } + } else { + None + }; + let orig_budget = orig_body_shape.map_or(0, |shape| shape.width); + + // Try putting body on the next line and see if it looks better. + let next_line_body_shape = Shape::indented(next_line_indent, context.config); + let next_line_body = nop_block_collapse( + format_expr(body, ExprType::Statement, context, next_line_body_shape), + next_line_body_shape.width, + ); + match (orig_body, next_line_body) { + (Some(ref orig_str), Some(ref next_line_str)) + if forbid_same_line + || prefer_next_line(orig_str, next_line_str, RhsTactics::Default) => + { + combine_next_line_body(next_line_str) + } + (Some(ref orig_str), _) if extend && first_line_width(orig_str) <= orig_budget => { + combine_orig_body(orig_str) + } + (Some(ref orig_str), Some(ref next_line_str)) if orig_str.contains('\n') => { + combine_next_line_body(next_line_str) + } + (None, Some(ref next_line_str)) => combine_next_line_body(next_line_str), + (None, None) => None, + (Some(ref orig_str), _) => combine_orig_body(orig_str), + } +} + +// The `if ...` guard on a match arm. +fn rewrite_guard( + context: &RewriteContext, + guard: &Option>, + shape: Shape, + // The amount of space used up on this line for the pattern in + // the arm (excludes offset). + pattern_width: usize, +) -> Option { + if let Some(ref guard) = *guard { + // First try to fit the guard string on the same line as the pattern. + // 4 = ` if `, 5 = ` => {` + let cond_shape = shape + .offset_left(pattern_width + 4) + .and_then(|s| s.sub_width(5)); + if let Some(cond_shape) = cond_shape { + if let Some(cond_str) = guard.rewrite(context, cond_shape) { + if !cond_str.contains('\n') || pattern_width <= context.config.tab_spaces() { + return Some(format!(" if {}", cond_str)); + } + } + } + + // Not enough space to put the guard after the pattern, try a newline. + // 3 = `if `, 5 = ` => {` + let cond_shape = Shape::indented(shape.indent.block_indent(context.config), context.config) + .offset_left(3) + .and_then(|s| s.sub_width(5)); + if let Some(cond_shape) = cond_shape { + if let Some(cond_str) = guard.rewrite(context, cond_shape) { + return Some(format!( + "{}if {}", + cond_shape.indent.to_string_with_newline(context.config), + cond_str + )); + } + } + + None + } else { + Some(String::new()) + } +} + +fn nop_block_collapse(block_str: Option, budget: usize) -> Option { + debug!("nop_block_collapse {:?} {}", block_str, budget); + block_str.map(|block_str| { + if block_str.starts_with('{') && budget >= 2 + && (block_str[1..].find(|c: char| !c.is_whitespace()).unwrap() == block_str.len() - 2) + { + "{}".to_owned() + } else { + block_str.to_owned() + } + }) +} + +fn can_extend_match_arm_body(body: &ast::Expr) -> bool { + match body.node { + // We do not allow `if` to stay on the same line, since we could easily mistake + // `pat => if cond { ... }` and `pat if cond => { ... }`. + ast::ExprKind::If(..) | ast::ExprKind::IfLet(..) => false, + ast::ExprKind::ForLoop(..) + | ast::ExprKind::Loop(..) + | ast::ExprKind::While(..) + | ast::ExprKind::WhileLet(..) + | ast::ExprKind::Match(..) + | ast::ExprKind::Block(..) + | ast::ExprKind::Closure(..) + | ast::ExprKind::Array(..) + | ast::ExprKind::Call(..) + | ast::ExprKind::MethodCall(..) + | ast::ExprKind::Mac(..) + | ast::ExprKind::Struct(..) + | ast::ExprKind::Tup(..) => true, + ast::ExprKind::AddrOf(_, ref expr) + | ast::ExprKind::Box(ref expr) + | ast::ExprKind::Try(ref expr) + | ast::ExprKind::Unary(_, ref expr) + | ast::ExprKind::Cast(ref expr, _) => can_extend_match_arm_body(expr), + _ => false, + } +} diff --git a/src/patterns.rs b/src/patterns.rs index 54b7e0f9a4c..cfff023bafc 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -27,6 +27,40 @@ use spanned::Spanned; use types::{rewrite_path, PathContext}; use utils::{format_mutability, mk_sp}; +/// Returns true if the given pattern is short. A short pattern is defined by the following grammer: +/// +/// [small, ntp]: +/// - single token +/// - `&[single-line, ntp]` +/// +/// [small]: +/// - `[small, ntp]` +/// - unary tuple constructor `([small, ntp])` +/// - `&[small]` +pub fn is_short_pattern(pat: &ast::Pat, pat_str: &str) -> bool { + // We also require that the pattern is reasonably 'small' with its literal width. + pat_str.len() <= 20 && !pat_str.contains('\n') && is_short_pattern_inner(pat) +} + +fn is_short_pattern_inner(pat: &ast::Pat) -> bool { + match pat.node { + ast::PatKind::Wild | ast::PatKind::Lit(_) => true, + ast::PatKind::Ident(_, _, ref pat) => pat.is_none(), + ast::PatKind::Struct(..) + | ast::PatKind::Mac(..) + | ast::PatKind::Slice(..) + | ast::PatKind::Path(..) + | ast::PatKind::Range(..) => false, + ast::PatKind::Tuple(ref subpats, _) => subpats.len() <= 1, + ast::PatKind::TupleStruct(ref path, ref subpats, _) => { + path.segments.len() <= 1 && subpats.len() <= 1 + } + ast::PatKind::Box(ref p) | ast::PatKind::Ref(ref p, _) | ast::PatKind::Paren(ref p) => { + is_short_pattern_inner(&*p) + } + } +} + impl Rewrite for Pat { fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option { match self.node {